- Now wallpaper can be changed directly from chat screen
- Improved way of applying themes' wallpapers - Username copied to clipboard if clicked in profile screen - Bug fixes
This commit is contained in:
parent
30f85a08f3
commit
8b5e9e2f0d
@ -10,12 +10,18 @@ dependencies {
|
||||
//compile 'com.google.android.gms:play-services:7.5.0'
|
||||
compile 'net.hockeyapp.android:HockeySDK:3.5.+'
|
||||
compile 'com.googlecode.mp4parser:isoparser:1.0.+'
|
||||
compile 'com.android.support:multidex:1.0.1'
|
||||
compile 'org.apache.httpcomponents:httpmime:4.2.1'
|
||||
//compile 'com.android.support:multidex:1.0.1'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 22
|
||||
buildToolsVersion '23.0.0'
|
||||
buildToolsVersion '23.0.1'
|
||||
|
||||
packagingOptions {
|
||||
exclude 'META-INF/NOTICE.txt'
|
||||
exclude 'META-INF/LICENSE.txt'
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
@ -82,8 +88,8 @@ android {
|
||||
applicationId "org.telegram.plus"
|
||||
minSdkVersion 8
|
||||
targetSdkVersion 22
|
||||
versionCode 597
|
||||
versionName "3.1.3.3"
|
||||
multiDexEnabled true
|
||||
versionCode 630
|
||||
versionName "3.2.2.1"
|
||||
//multiDexEnabled true
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@
|
||||
<activity android:name="net.hockeyapp.android.UpdateActivity" />
|
||||
|
||||
<receiver
|
||||
android:name="org.telegram.android.GcmBroadcastReceiver"
|
||||
android:name="org.telegram.messenger.GcmBroadcastReceiver"
|
||||
android:permission="com.google.android.c2dm.permission.SEND" >
|
||||
<intent-filter>
|
||||
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
|
||||
@ -49,7 +49,7 @@
|
||||
|
||||
<provider
|
||||
android:authorities="org.telegram.plus.beta.android.provider.content"
|
||||
android:name="org.telegram.android.ModuleContentProvider"
|
||||
android:name="org.telegram.messenger.ModuleContentProvider"
|
||||
android:label="Plus beta"
|
||||
android:exported="true"
|
||||
android:permission="org.telegram.plus.beta.android.provider.ACCESS" />
|
||||
|
@ -34,7 +34,7 @@
|
||||
<activity android:name="net.hockeyapp.android.UpdateActivity" />
|
||||
|
||||
<receiver
|
||||
android:name="org.telegram.android.GcmBroadcastReceiver"
|
||||
android:name="org.telegram.messenger.GcmBroadcastReceiver"
|
||||
android:permission="com.google.android.c2dm.permission.SEND" >
|
||||
<intent-filter>
|
||||
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
|
||||
@ -47,7 +47,7 @@
|
||||
|
||||
<provider
|
||||
android:authorities="org.telegram.plus.android.provider.content"
|
||||
android:name="org.telegram.android.ModuleContentProvider"
|
||||
android:name="org.telegram.messenger.ModuleContentProvider"
|
||||
android:label="@string/AppName"
|
||||
android:exported="true"
|
||||
android:permission="org.telegram.plus.android.provider.ACCESS" />
|
||||
|
@ -2,7 +2,89 @@ LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_CFLAGS := -Wall -DANDROID -DHAVE_MALLOC_H -DHAVE_PTHREAD -DWEBP_USE_THREAD -finline-functions -ffast-math -ffunction-sections -fdata-sections -O2
|
||||
LOCAL_MODULE := crypto
|
||||
|
||||
ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
|
||||
LOCAL_SRC_FILES := ./boringssl/lib/libcrypto_armeabi-v7a.a
|
||||
else
|
||||
ifeq ($(TARGET_ARCH_ABI),armeabi)
|
||||
LOCAL_SRC_FILES := ./boringssl/lib/libcrypto_armeabi.a
|
||||
else
|
||||
ifeq ($(TARGET_ARCH_ABI),x86)
|
||||
LOCAL_SRC_FILES := ./boringssl/lib/libcrypto_x86.a
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_CPP_EXTENSION := .cc
|
||||
LOCAL_ARM_MODE := arm
|
||||
LOCAL_MODULE := breakpad
|
||||
LOCAL_CPPFLAGS := -Wall -std=c++11 -DANDROID -finline-functions -ffast-math -Os -fno-strict-aliasing
|
||||
|
||||
LOCAL_C_INCLUDES := \
|
||||
./breakpad/common/android/include \
|
||||
./breakpad
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
./breakpad/client/linux/crash_generation/crash_generation_client.cc \
|
||||
./breakpad/client/linux/dump_writer_common/ucontext_reader.cc \
|
||||
./breakpad/client/linux/dump_writer_common/thread_info.cc \
|
||||
./breakpad/client/linux/handler/exception_handler.cc \
|
||||
./breakpad/client/linux/handler/minidump_descriptor.cc \
|
||||
./breakpad/client/linux/log/log.cc \
|
||||
./breakpad/client/linux/microdump_writer/microdump_writer.cc \
|
||||
./breakpad/client/linux/minidump_writer/linux_dumper.cc \
|
||||
./breakpad/client/linux/minidump_writer/linux_ptrace_dumper.cc \
|
||||
./breakpad/client/linux/minidump_writer/minidump_writer.cc \
|
||||
./breakpad/client/minidump_file_writer.cc \
|
||||
./breakpad/common/android/breakpad_getcontext.S \
|
||||
./breakpad/common/convert_UTF.c \
|
||||
./breakpad/common/md5.cc \
|
||||
./breakpad/common/string_conversion.cc \
|
||||
./breakpad/common/linux/elfutils.cc \
|
||||
./breakpad/common/linux/file_id.cc \
|
||||
./breakpad/common/linux/guid_creator.cc \
|
||||
./breakpad/common/linux/linux_libc_support.cc \
|
||||
./breakpad/common/linux/memory_mapped_file.cc \
|
||||
./breakpad/common/linux/safe_readlink.cc
|
||||
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_CPPFLAGS := -Wall -std=c++11 -DANDROID -frtti -DHAVE_PTHREAD -finline-functions -ffast-math -Os
|
||||
LOCAL_C_INCLUDES += ./boringssl/include/
|
||||
LOCAL_ARM_MODE := arm
|
||||
LOCAL_MODULE := tgnet
|
||||
LOCAL_STATIC_LIBRARIES := crypto
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
./tgnet/BuffersStorage.cpp \
|
||||
./tgnet/ByteArray.cpp \
|
||||
./tgnet/ByteStream.cpp \
|
||||
./tgnet/Connection.cpp \
|
||||
./tgnet/ConnectionSession.cpp \
|
||||
./tgnet/ConnectionsManager.cpp \
|
||||
./tgnet/ConnectionSocket.cpp \
|
||||
./tgnet/Datacenter.cpp \
|
||||
./tgnet/EventObject.cpp \
|
||||
./tgnet/FileLog.cpp \
|
||||
./tgnet/MTProtoScheme.cpp \
|
||||
./tgnet/NativeByteBuffer.cpp \
|
||||
./tgnet/Request.cpp \
|
||||
./tgnet/Timer.cpp \
|
||||
./tgnet/TLObject.cpp \
|
||||
./tgnet/Config.cpp
|
||||
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_CFLAGS := -Wall -DANDROID -DHAVE_MALLOC_H -DHAVE_PTHREAD -DWEBP_USE_THREAD -finline-functions -ffast-math -ffunction-sections -fdata-sections -Os
|
||||
LOCAL_C_INCLUDES += ./libwebp/src
|
||||
LOCAL_ARM_MODE := arm
|
||||
LOCAL_STATIC_LIBRARIES := cpufeatures
|
||||
@ -93,7 +175,7 @@ else
|
||||
LOCAL_ARM_MODE := arm
|
||||
endif
|
||||
LOCAL_MODULE := sqlite
|
||||
LOCAL_CFLAGS := -w -std=gnu99 -O2 -DNULL=0 -DSOCKLEN_T=socklen_t -DLOCALE_NOT_USED -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64
|
||||
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 += -DANDROID_NDK -DDISABLE_IMPORTGL -fno-strict-aliasing -fprefetch-loop-arrays -DAVOID_TABLES -DANDROID_TILE_BASED_DECODE -DANDROID_ARMV6_IDCT -DHAVE_STRCHRNUL=0
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
@ -103,13 +185,14 @@ include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_PRELINK_MODULE := false
|
||||
LOCAL_STATIC_LIBRARIES := webp sqlite
|
||||
LOCAL_MODULE := tmessages.8
|
||||
LOCAL_CFLAGS := -w -std=gnu99 -O2 -DNULL=0 -DSOCKLEN_T=socklen_t -DLOCALE_NOT_USED -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64
|
||||
LOCAL_STATIC_LIBRARIES := webp sqlite tgnet breakpad
|
||||
|
||||
LOCAL_MODULE := tmessages.12
|
||||
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
|
||||
LOCAL_CPPFLAGS := -DBSD=1 -ffast-math -O2 -funroll-loops
|
||||
LOCAL_LDLIBS := -ljnigraphics -llog
|
||||
LOCAL_CPPFLAGS := -DBSD=1 -ffast-math -Os -funroll-loops -std=c++11
|
||||
LOCAL_LDLIBS := -ljnigraphics -llog -lz
|
||||
ifeq ($(TARGET_ARCH_ABI),armeabi)
|
||||
LOCAL_ARM_MODE := thumb
|
||||
else
|
||||
@ -267,24 +350,6 @@ LOCAL_SRC_FILES += \
|
||||
./giflib/dgif_lib.c \
|
||||
./giflib/gifalloc.c
|
||||
|
||||
LOCAL_SRC_FILES += \
|
||||
./aes/aes_ige.c \
|
||||
./aes/aes_misc.c
|
||||
|
||||
ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
|
||||
LOCAL_SRC_FILES += ./aes/aes_arm.S
|
||||
else
|
||||
ifeq ($(TARGET_ARCH_ABI),armeabi)
|
||||
LOCAL_SRC_FILES += ./aes/aes_arm.S
|
||||
else
|
||||
ifeq ($(TARGET_ARCH_ABI),x86)
|
||||
LOCAL_SRC_FILES += ./aes/aes_core.c
|
||||
else
|
||||
LOCAL_SRC_FILES += ./aes/aes_core.c
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
LOCAL_C_INCLUDES := \
|
||||
./opus/include \
|
||||
./opus/silk \
|
||||
@ -292,7 +357,10 @@ LOCAL_C_INCLUDES := \
|
||||
./opus/celt \
|
||||
./opus/ \
|
||||
./opus/opusfile \
|
||||
./libyuv/include
|
||||
./libyuv/include \
|
||||
./boringssl/include \
|
||||
./breakpad/common/android/include \
|
||||
./breakpad
|
||||
|
||||
LOCAL_SRC_FILES += \
|
||||
./libjpeg/jcapimin.c \
|
||||
@ -393,7 +461,9 @@ LOCAL_SRC_FILES += \
|
||||
./gif.c \
|
||||
./utils.c \
|
||||
./image.c \
|
||||
./video.c
|
||||
./video.c \
|
||||
./TgNetWrapper.cpp \
|
||||
./NativeLoader.cpp
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
|
@ -1,2 +1,4 @@
|
||||
APP_PLATFORM := android-9
|
||||
APP_ABI := armeabi armeabi-v7a x86
|
||||
NDK_TOOLCHAIN_VERSION := 4.8
|
||||
APP_STL := gnustl_static
|
@ -1,147 +0,0 @@
|
||||
/* crypto/aes/aes.h -*- mode:C; c-file-style: "eay" -*- */
|
||||
/* ====================================================================
|
||||
* Copyright (c) 1998-2002 The OpenSSL Project. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. 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.
|
||||
*
|
||||
* 3. All advertising materials mentioning features or use of this
|
||||
* software must display the following acknowledgment:
|
||||
* "This product includes software developed by the OpenSSL Project
|
||||
* for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
|
||||
*
|
||||
* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
|
||||
* endorse or promote products derived from this software without
|
||||
* prior written permission. For written permission, please contact
|
||||
* openssl-core@openssl.org.
|
||||
*
|
||||
* 5. Products derived from this software may not be called "OpenSSL"
|
||||
* nor may "OpenSSL" appear in their names without prior written
|
||||
* permission of the OpenSSL Project.
|
||||
*
|
||||
* 6. Redistributions of any form whatsoever must retain the following
|
||||
* acknowledgment:
|
||||
* "This product includes software developed by the OpenSSL Project
|
||||
* for use in the OpenSSL Toolkit (http://www.openssl.org/)"
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
|
||||
* EXPRESSED 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 OpenSSL PROJECT OR
|
||||
* ITS 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 HEADER_AES_H
|
||||
#define HEADER_AES_H
|
||||
|
||||
//#include <openssl/opensslconf.h>
|
||||
|
||||
#ifdef OPENSSL_NO_AES
|
||||
#error AES is disabled.
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#define AES_ENCRYPT 1
|
||||
#define AES_DECRYPT 0
|
||||
|
||||
/* Because array size can't be a const in C, the following two are macros.
|
||||
Both sizes are in bytes. */
|
||||
#define AES_MAXNR 14
|
||||
#define AES_BLOCK_SIZE 16
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* This should be a hidden type, but EVP requires that the size be known */
|
||||
struct aes_key_st {
|
||||
#ifdef AES_LONG
|
||||
unsigned long rd_key[4 *(AES_MAXNR + 1)];
|
||||
#else
|
||||
unsigned int rd_key[4 *(AES_MAXNR + 1)];
|
||||
#endif
|
||||
int rounds;
|
||||
};
|
||||
typedef struct aes_key_st AES_KEY;
|
||||
|
||||
const char *AES_options(void);
|
||||
|
||||
int AES_set_encrypt_key(const unsigned char *userKey, const int bits,
|
||||
AES_KEY *key);
|
||||
int AES_set_decrypt_key(const unsigned char *userKey, const int bits,
|
||||
AES_KEY *key);
|
||||
|
||||
int private_AES_set_encrypt_key(const unsigned char *userKey, const int bits,
|
||||
AES_KEY *key);
|
||||
int private_AES_set_decrypt_key(const unsigned char *userKey, const int bits,
|
||||
AES_KEY *key);
|
||||
|
||||
void AES_encrypt(const unsigned char *in, unsigned char *out,
|
||||
const AES_KEY *key);
|
||||
void AES_decrypt(const unsigned char *in, unsigned char *out,
|
||||
const AES_KEY *key);
|
||||
|
||||
void AES_ecb_encrypt(const unsigned char *in, unsigned char *out,
|
||||
const AES_KEY *key, const int enc);
|
||||
void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
|
||||
size_t length, const AES_KEY *key,
|
||||
unsigned char *ivec, const int enc);
|
||||
void AES_cfb128_encrypt(const unsigned char *in, unsigned char *out,
|
||||
size_t length, const AES_KEY *key,
|
||||
unsigned char *ivec, int *num, const int enc);
|
||||
void AES_cfb1_encrypt(const unsigned char *in, unsigned char *out,
|
||||
size_t length, const AES_KEY *key,
|
||||
unsigned char *ivec, int *num, const int enc);
|
||||
void AES_cfb8_encrypt(const unsigned char *in, unsigned char *out,
|
||||
size_t length, const AES_KEY *key,
|
||||
unsigned char *ivec, int *num, const int enc);
|
||||
void AES_ofb128_encrypt(const unsigned char *in, unsigned char *out,
|
||||
size_t length, const AES_KEY *key,
|
||||
unsigned char *ivec, int *num);
|
||||
void AES_ctr128_encrypt(const unsigned char *in, unsigned char *out,
|
||||
size_t length, const AES_KEY *key,
|
||||
unsigned char ivec[AES_BLOCK_SIZE],
|
||||
unsigned char ecount_buf[AES_BLOCK_SIZE],
|
||||
unsigned int *num);
|
||||
/* NB: the IV is _two_ blocks long */
|
||||
void AES_ige_encrypt(const unsigned char *in, unsigned char *out,
|
||||
size_t length, const AES_KEY *key,
|
||||
unsigned char *ivec, const int enc);
|
||||
/* NB: the IV is _four_ blocks long */
|
||||
void AES_bi_ige_encrypt(const unsigned char *in, unsigned char *out,
|
||||
size_t length, const AES_KEY *key,
|
||||
const AES_KEY *key2, const unsigned char *ivec,
|
||||
const int enc);
|
||||
|
||||
int AES_wrap_key(AES_KEY *key, const unsigned char *iv,
|
||||
unsigned char *out,
|
||||
const unsigned char *in, unsigned int inlen);
|
||||
int AES_unwrap_key(AES_KEY *key, const unsigned char *iv,
|
||||
unsigned char *out,
|
||||
const unsigned char *in, unsigned int inlen);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* !HEADER_AES_H */
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,325 +0,0 @@
|
||||
/* crypto/aes/aes_ige.c -*- mode:C; c-file-style: "eay" -*- */
|
||||
/* ====================================================================
|
||||
* Copyright (c) 2006 The OpenSSL Project. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. 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.
|
||||
*
|
||||
* 3. All advertising materials mentioning features or use of this
|
||||
* software must display the following acknowledgment:
|
||||
* "This product includes software developed by the OpenSSL Project
|
||||
* for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
|
||||
*
|
||||
* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
|
||||
* endorse or promote products derived from this software without
|
||||
* prior written permission. For written permission, please contact
|
||||
* openssl-core@openssl.org.
|
||||
*
|
||||
* 5. Products derived from this software may not be called "OpenSSL"
|
||||
* nor may "OpenSSL" appear in their names without prior written
|
||||
* permission of the OpenSSL Project.
|
||||
*
|
||||
* 6. Redistributions of any form whatsoever must retain the following
|
||||
* acknowledgment:
|
||||
* "This product includes software developed by the OpenSSL Project
|
||||
* for use in the OpenSSL Toolkit (http://www.openssl.org/)"
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
|
||||
* EXPRESSED 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 OpenSSL PROJECT OR
|
||||
* ITS 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.
|
||||
* ====================================================================
|
||||
*
|
||||
*/
|
||||
|
||||
//#include "cryptlib.h"
|
||||
|
||||
#include "aes.h"
|
||||
#include "aes_locl.h"
|
||||
#include <assert.h>
|
||||
#define OPENSSL_assert assert
|
||||
|
||||
#define N_WORDS (AES_BLOCK_SIZE / sizeof(unsigned long))
|
||||
typedef struct {
|
||||
unsigned long data[N_WORDS];
|
||||
} aes_block_t;
|
||||
|
||||
/* XXX: probably some better way to do this */
|
||||
#if defined(__i386__) || defined(__x86_64__)
|
||||
#define UNALIGNED_MEMOPS_ARE_FAST 1
|
||||
#else
|
||||
#define UNALIGNED_MEMOPS_ARE_FAST 0
|
||||
#endif
|
||||
|
||||
#if UNALIGNED_MEMOPS_ARE_FAST
|
||||
#define load_block(d, s) (d) = *(const aes_block_t *)(s)
|
||||
#define store_block(d, s) *(aes_block_t *)(d) = (s)
|
||||
#else
|
||||
#define load_block(d, s) memcpy((d).data, (s), AES_BLOCK_SIZE)
|
||||
#define store_block(d, s) memcpy((d), (s).data, AES_BLOCK_SIZE)
|
||||
#endif
|
||||
|
||||
/* N.B. The IV for this mode is _twice_ the block size */
|
||||
|
||||
void AES_ige_encrypt(const unsigned char *in, unsigned char *out,
|
||||
size_t length, const AES_KEY *key,
|
||||
unsigned char *ivec, const int enc)
|
||||
{
|
||||
size_t n;
|
||||
size_t len = length;
|
||||
|
||||
OPENSSL_assert(in && out && key && ivec);
|
||||
OPENSSL_assert((AES_ENCRYPT == enc)||(AES_DECRYPT == enc));
|
||||
OPENSSL_assert((length%AES_BLOCK_SIZE) == 0);
|
||||
|
||||
len = length / AES_BLOCK_SIZE;
|
||||
|
||||
if (AES_ENCRYPT == enc)
|
||||
{
|
||||
if (in != out &&
|
||||
(UNALIGNED_MEMOPS_ARE_FAST || ((size_t)in|(size_t)out|(size_t)ivec)%sizeof(long)==0))
|
||||
{
|
||||
aes_block_t *ivp = (aes_block_t *)ivec;
|
||||
aes_block_t *iv2p = (aes_block_t *)(ivec + AES_BLOCK_SIZE);
|
||||
|
||||
while (len)
|
||||
{
|
||||
aes_block_t *inp = (aes_block_t *)in;
|
||||
aes_block_t *outp = (aes_block_t *)out;
|
||||
|
||||
for(n=0 ; n < N_WORDS; ++n)
|
||||
outp->data[n] = inp->data[n] ^ ivp->data[n];
|
||||
AES_encrypt((unsigned char *)outp->data, (unsigned char *)outp->data, key);
|
||||
for(n=0 ; n < N_WORDS; ++n)
|
||||
outp->data[n] ^= iv2p->data[n];
|
||||
ivp = outp;
|
||||
iv2p = inp;
|
||||
--len;
|
||||
in += AES_BLOCK_SIZE;
|
||||
out += AES_BLOCK_SIZE;
|
||||
}
|
||||
memcpy(ivec, ivp->data, AES_BLOCK_SIZE);
|
||||
memcpy(ivec + AES_BLOCK_SIZE, iv2p->data, AES_BLOCK_SIZE);
|
||||
}
|
||||
else
|
||||
{
|
||||
aes_block_t tmp, tmp2;
|
||||
aes_block_t iv;
|
||||
aes_block_t iv2;
|
||||
|
||||
load_block(iv, ivec);
|
||||
load_block(iv2, ivec + AES_BLOCK_SIZE);
|
||||
|
||||
while (len)
|
||||
{
|
||||
load_block(tmp, in);
|
||||
for(n=0 ; n < N_WORDS; ++n)
|
||||
tmp2.data[n] = tmp.data[n] ^ iv.data[n];
|
||||
AES_encrypt((unsigned char *)tmp2.data, (unsigned char *)tmp2.data, key);
|
||||
for(n=0 ; n < N_WORDS; ++n)
|
||||
tmp2.data[n] ^= iv2.data[n];
|
||||
store_block(out, tmp2);
|
||||
iv = tmp2;
|
||||
iv2 = tmp;
|
||||
--len;
|
||||
in += AES_BLOCK_SIZE;
|
||||
out += AES_BLOCK_SIZE;
|
||||
}
|
||||
memcpy(ivec, iv.data, AES_BLOCK_SIZE);
|
||||
memcpy(ivec + AES_BLOCK_SIZE, iv2.data, AES_BLOCK_SIZE);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (in != out &&
|
||||
(UNALIGNED_MEMOPS_ARE_FAST || ((size_t)in|(size_t)out|(size_t)ivec)%sizeof(long)==0))
|
||||
{
|
||||
aes_block_t *ivp = (aes_block_t *)ivec;
|
||||
aes_block_t *iv2p = (aes_block_t *)(ivec + AES_BLOCK_SIZE);
|
||||
|
||||
while (len)
|
||||
{
|
||||
aes_block_t tmp;
|
||||
aes_block_t *inp = (aes_block_t *)in;
|
||||
aes_block_t *outp = (aes_block_t *)out;
|
||||
|
||||
for(n=0 ; n < N_WORDS; ++n)
|
||||
tmp.data[n] = inp->data[n] ^ iv2p->data[n];
|
||||
AES_decrypt((unsigned char *)tmp.data, (unsigned char *)outp->data, key);
|
||||
for(n=0 ; n < N_WORDS; ++n)
|
||||
outp->data[n] ^= ivp->data[n];
|
||||
ivp = inp;
|
||||
iv2p = outp;
|
||||
--len;
|
||||
in += AES_BLOCK_SIZE;
|
||||
out += AES_BLOCK_SIZE;
|
||||
}
|
||||
memcpy(ivec, ivp->data, AES_BLOCK_SIZE);
|
||||
memcpy(ivec + AES_BLOCK_SIZE, iv2p->data, AES_BLOCK_SIZE);
|
||||
}
|
||||
else
|
||||
{
|
||||
aes_block_t tmp, tmp2;
|
||||
aes_block_t iv;
|
||||
aes_block_t iv2;
|
||||
|
||||
load_block(iv, ivec);
|
||||
load_block(iv2, ivec + AES_BLOCK_SIZE);
|
||||
|
||||
while (len)
|
||||
{
|
||||
load_block(tmp, in);
|
||||
tmp2 = tmp;
|
||||
for(n=0 ; n < N_WORDS; ++n)
|
||||
tmp.data[n] ^= iv2.data[n];
|
||||
AES_decrypt((unsigned char *)tmp.data, (unsigned char *)tmp.data, key);
|
||||
for(n=0 ; n < N_WORDS; ++n)
|
||||
tmp.data[n] ^= iv.data[n];
|
||||
store_block(out, tmp);
|
||||
iv = tmp2;
|
||||
iv2 = tmp;
|
||||
--len;
|
||||
in += AES_BLOCK_SIZE;
|
||||
out += AES_BLOCK_SIZE;
|
||||
}
|
||||
memcpy(ivec, iv.data, AES_BLOCK_SIZE);
|
||||
memcpy(ivec + AES_BLOCK_SIZE, iv2.data, AES_BLOCK_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Note that its effectively impossible to do biIGE in anything other
|
||||
* than a single pass, so no provision is made for chaining.
|
||||
*/
|
||||
|
||||
/* N.B. The IV for this mode is _four times_ the block size */
|
||||
|
||||
void AES_bi_ige_encrypt(const unsigned char *in, unsigned char *out,
|
||||
size_t length, const AES_KEY *key,
|
||||
const AES_KEY *key2, const unsigned char *ivec,
|
||||
const int enc)
|
||||
{
|
||||
size_t n;
|
||||
size_t len = length;
|
||||
unsigned char tmp[AES_BLOCK_SIZE];
|
||||
unsigned char tmp2[AES_BLOCK_SIZE];
|
||||
unsigned char tmp3[AES_BLOCK_SIZE];
|
||||
unsigned char prev[AES_BLOCK_SIZE];
|
||||
const unsigned char *iv;
|
||||
const unsigned char *iv2;
|
||||
|
||||
OPENSSL_assert(in && out && key && ivec);
|
||||
OPENSSL_assert((AES_ENCRYPT == enc)||(AES_DECRYPT == enc));
|
||||
OPENSSL_assert((length%AES_BLOCK_SIZE) == 0);
|
||||
|
||||
if (AES_ENCRYPT == enc)
|
||||
{
|
||||
/* XXX: Do a separate case for when in != out (strictly should
|
||||
check for overlap, too) */
|
||||
|
||||
/* First the forward pass */
|
||||
iv = ivec;
|
||||
iv2 = ivec + AES_BLOCK_SIZE;
|
||||
while (len >= AES_BLOCK_SIZE)
|
||||
{
|
||||
for(n=0 ; n < AES_BLOCK_SIZE ; ++n)
|
||||
out[n] = in[n] ^ iv[n];
|
||||
AES_encrypt(out, out, key);
|
||||
for(n=0 ; n < AES_BLOCK_SIZE ; ++n)
|
||||
out[n] ^= iv2[n];
|
||||
iv = out;
|
||||
memcpy(prev, in, AES_BLOCK_SIZE);
|
||||
iv2 = prev;
|
||||
len -= AES_BLOCK_SIZE;
|
||||
in += AES_BLOCK_SIZE;
|
||||
out += AES_BLOCK_SIZE;
|
||||
}
|
||||
|
||||
/* And now backwards */
|
||||
iv = ivec + AES_BLOCK_SIZE*2;
|
||||
iv2 = ivec + AES_BLOCK_SIZE*3;
|
||||
len = length;
|
||||
while(len >= AES_BLOCK_SIZE)
|
||||
{
|
||||
out -= AES_BLOCK_SIZE;
|
||||
/* XXX: reduce copies by alternating between buffers */
|
||||
memcpy(tmp, out, AES_BLOCK_SIZE);
|
||||
for(n=0 ; n < AES_BLOCK_SIZE ; ++n)
|
||||
out[n] ^= iv[n];
|
||||
/* hexdump(stdout, "out ^ iv", out, AES_BLOCK_SIZE); */
|
||||
AES_encrypt(out, out, key);
|
||||
/* hexdump(stdout,"enc", out, AES_BLOCK_SIZE); */
|
||||
/* hexdump(stdout,"iv2", iv2, AES_BLOCK_SIZE); */
|
||||
for(n=0 ; n < AES_BLOCK_SIZE ; ++n)
|
||||
out[n] ^= iv2[n];
|
||||
/* hexdump(stdout,"out", out, AES_BLOCK_SIZE); */
|
||||
iv = out;
|
||||
memcpy(prev, tmp, AES_BLOCK_SIZE);
|
||||
iv2 = prev;
|
||||
len -= AES_BLOCK_SIZE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* First backwards */
|
||||
iv = ivec + AES_BLOCK_SIZE*2;
|
||||
iv2 = ivec + AES_BLOCK_SIZE*3;
|
||||
in += length;
|
||||
out += length;
|
||||
while (len >= AES_BLOCK_SIZE)
|
||||
{
|
||||
in -= AES_BLOCK_SIZE;
|
||||
out -= AES_BLOCK_SIZE;
|
||||
memcpy(tmp, in, AES_BLOCK_SIZE);
|
||||
memcpy(tmp2, in, AES_BLOCK_SIZE);
|
||||
for(n=0 ; n < AES_BLOCK_SIZE ; ++n)
|
||||
tmp[n] ^= iv2[n];
|
||||
AES_decrypt(tmp, out, key);
|
||||
for(n=0 ; n < AES_BLOCK_SIZE ; ++n)
|
||||
out[n] ^= iv[n];
|
||||
memcpy(tmp3, tmp2, AES_BLOCK_SIZE);
|
||||
iv = tmp3;
|
||||
iv2 = out;
|
||||
len -= AES_BLOCK_SIZE;
|
||||
}
|
||||
|
||||
/* And now forwards */
|
||||
iv = ivec;
|
||||
iv2 = ivec + AES_BLOCK_SIZE;
|
||||
len = length;
|
||||
while (len >= AES_BLOCK_SIZE)
|
||||
{
|
||||
memcpy(tmp, out, AES_BLOCK_SIZE);
|
||||
memcpy(tmp2, out, AES_BLOCK_SIZE);
|
||||
for(n=0 ; n < AES_BLOCK_SIZE ; ++n)
|
||||
tmp[n] ^= iv2[n];
|
||||
AES_decrypt(tmp, out, key);
|
||||
for(n=0 ; n < AES_BLOCK_SIZE ; ++n)
|
||||
out[n] ^= iv[n];
|
||||
memcpy(tmp3, tmp2, AES_BLOCK_SIZE);
|
||||
iv = tmp3;
|
||||
iv2 = out;
|
||||
len -= AES_BLOCK_SIZE;
|
||||
in += AES_BLOCK_SIZE;
|
||||
out += AES_BLOCK_SIZE;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
/* crypto/aes/aes.h -*- mode:C; c-file-style: "eay" -*- */
|
||||
/* ====================================================================
|
||||
* Copyright (c) 1998-2002 The OpenSSL Project. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. 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.
|
||||
*
|
||||
* 3. All advertising materials mentioning features or use of this
|
||||
* software must display the following acknowledgment:
|
||||
* "This product includes software developed by the OpenSSL Project
|
||||
* for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
|
||||
*
|
||||
* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
|
||||
* endorse or promote products derived from this software without
|
||||
* prior written permission. For written permission, please contact
|
||||
* openssl-core@openssl.org.
|
||||
*
|
||||
* 5. Products derived from this software may not be called "OpenSSL"
|
||||
* nor may "OpenSSL" appear in their names without prior written
|
||||
* permission of the OpenSSL Project.
|
||||
*
|
||||
* 6. Redistributions of any form whatsoever must retain the following
|
||||
* acknowledgment:
|
||||
* "This product includes software developed by the OpenSSL Project
|
||||
* for use in the OpenSSL Toolkit (http://www.openssl.org/)"
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
|
||||
* EXPRESSED 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 OpenSSL PROJECT OR
|
||||
* ITS 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 HEADER_AES_LOCL_H
|
||||
#define HEADER_AES_LOCL_H
|
||||
|
||||
//#include <openssl/e_os2.h>
|
||||
|
||||
#ifdef OPENSSL_NO_AES
|
||||
#error AES is disabled.
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_AMD64) || defined(_M_X64))
|
||||
# define SWAP(x) (_lrotl(x, 8) & 0x00ff00ff | _lrotr(x, 8) & 0xff00ff00)
|
||||
# define GETU32(p) SWAP(*((u32 *)(p)))
|
||||
# define PUTU32(ct, st) { *((u32 *)(ct)) = SWAP((st)); }
|
||||
#else
|
||||
# define GETU32(pt) (((u32)(pt)[0] << 24) ^ ((u32)(pt)[1] << 16) ^ ((u32)(pt)[2] << 8) ^ ((u32)(pt)[3]))
|
||||
# define PUTU32(ct, st) { (ct)[0] = (u8)((st) >> 24); (ct)[1] = (u8)((st) >> 16); (ct)[2] = (u8)((st) >> 8); (ct)[3] = (u8)(st); }
|
||||
#endif
|
||||
|
||||
#ifdef AES_LONG
|
||||
typedef unsigned long u32;
|
||||
#else
|
||||
typedef unsigned int u32;
|
||||
#endif
|
||||
typedef unsigned short u16;
|
||||
typedef unsigned char u8;
|
||||
|
||||
#define MAXKC (256/32)
|
||||
#define MAXKB (256/8)
|
||||
#define MAXNR 14
|
||||
|
||||
/* This controls loop-unrolling in aes_core.c */
|
||||
#undef FULL_UNROLL
|
||||
|
||||
#endif /* !HEADER_AES_LOCL_H */
|
@ -1,85 +0,0 @@
|
||||
/* crypto/aes/aes_misc.c -*- mode:C; c-file-style: "eay" -*- */
|
||||
/* ====================================================================
|
||||
* Copyright (c) 1998-2002 The OpenSSL Project. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. 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.
|
||||
*
|
||||
* 3. All advertising materials mentioning features or use of this
|
||||
* software must display the following acknowledgment:
|
||||
* "This product includes software developed by the OpenSSL Project
|
||||
* for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
|
||||
*
|
||||
* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
|
||||
* endorse or promote products derived from this software without
|
||||
* prior written permission. For written permission, please contact
|
||||
* openssl-core@openssl.org.
|
||||
*
|
||||
* 5. Products derived from this software may not be called "OpenSSL"
|
||||
* nor may "OpenSSL" appear in their names without prior written
|
||||
* permission of the OpenSSL Project.
|
||||
*
|
||||
* 6. Redistributions of any form whatsoever must retain the following
|
||||
* acknowledgment:
|
||||
* "This product includes software developed by the OpenSSL Project
|
||||
* for use in the OpenSSL Toolkit (http://www.openssl.org/)"
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
|
||||
* EXPRESSED 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 OpenSSL PROJECT OR
|
||||
* ITS 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.
|
||||
* ====================================================================
|
||||
*
|
||||
*/
|
||||
|
||||
//#include <openssl/opensslv.h>
|
||||
//#include <openssl/crypto.h>
|
||||
#include "aes.h"
|
||||
#include "aes_locl.h"
|
||||
|
||||
const char AES_version[]="AES" ;//OPENSSL_VERSION_PTEXT;
|
||||
|
||||
const char *AES_options(void) {
|
||||
#ifdef FULL_UNROLL
|
||||
return "aes(full)";
|
||||
#else
|
||||
return "aes(partial)";
|
||||
#endif
|
||||
}
|
||||
|
||||
/* FIPS wrapper functions to block low level AES calls in FIPS mode */
|
||||
|
||||
int AES_set_encrypt_key(const unsigned char *userKey, const int bits,
|
||||
AES_KEY *key)
|
||||
{
|
||||
#ifdef OPENSSL_FIPS
|
||||
fips_cipher_abort(AES);
|
||||
#endif
|
||||
return private_AES_set_encrypt_key(userKey, bits, key);
|
||||
}
|
||||
|
||||
int AES_set_decrypt_key(const unsigned char *userKey, const int bits,
|
||||
AES_KEY *key)
|
||||
{
|
||||
#ifdef OPENSSL_FIPS
|
||||
fips_cipher_abort(AES);
|
||||
#endif
|
||||
return private_AES_set_decrypt_key(userKey, bits, key);
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
#ifndef __ARM_ARCH_H__
|
||||
#define __ARM_ARCH_H__
|
||||
|
||||
#if !defined(__ARM_ARCH__)
|
||||
# if defined(__CC_ARM)
|
||||
# define __ARM_ARCH__ __TARGET_ARCH_ARM
|
||||
# if defined(__BIG_ENDIAN)
|
||||
# define __ARMEB__
|
||||
# else
|
||||
# define __ARMEL__
|
||||
# endif
|
||||
# elif defined(__GNUC__)
|
||||
/*
|
||||
* Why doesn't gcc define __ARM_ARCH__? Instead it defines
|
||||
* bunch of below macros. See all_architectires[] table in
|
||||
* gcc/config/arm/arm.c. On a side note it defines
|
||||
* __ARMEL__/__ARMEB__ for little-/big-endian.
|
||||
*/
|
||||
# if defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || \
|
||||
defined(__ARM_ARCH_7R__)|| defined(__ARM_ARCH_7M__) || \
|
||||
defined(__ARM_ARCH_7EM__)
|
||||
# define __ARM_ARCH__ 7
|
||||
# elif defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || \
|
||||
defined(__ARM_ARCH_6K__)|| defined(__ARM_ARCH_6M__) || \
|
||||
defined(__ARM_ARCH_6Z__)|| defined(__ARM_ARCH_6ZK__) || \
|
||||
defined(__ARM_ARCH_6T2__)
|
||||
# define __ARM_ARCH__ 6
|
||||
# elif defined(__ARM_ARCH_5__) || defined(__ARM_ARCH_5T__) || \
|
||||
defined(__ARM_ARCH_5E__)|| defined(__ARM_ARCH_5TE__) || \
|
||||
defined(__ARM_ARCH_5TEJ__)
|
||||
# define __ARM_ARCH__ 5
|
||||
# elif defined(__ARM_ARCH_4__) || defined(__ARM_ARCH_4T__)
|
||||
# define __ARM_ARCH__ 4
|
||||
# else
|
||||
# error "unsupported ARM architecture"
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef OPENSSL_FIPSCANISTER
|
||||
#include <openssl/fipssyms.h>
|
||||
#endif
|
||||
|
||||
#if !__ASSEMBLER__
|
||||
extern unsigned int OPENSSL_armcap_P;
|
||||
|
||||
#define ARMV7_NEON (1<<0)
|
||||
#define ARMV7_TICK (1<<1)
|
||||
#endif
|
||||
|
||||
#endif
|
@ -504,7 +504,7 @@ int writeFrame(uint8_t *framePcmBytes, unsigned int frameByteCount) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
JNIEXPORT int Java_org_telegram_android_MediaController_startRecord(JNIEnv *env, jclass class, jstring path) {
|
||||
JNIEXPORT int Java_org_telegram_messenger_MediaController_startRecord(JNIEnv *env, jclass class, jstring path) {
|
||||
const char *pathStr = (*env)->GetStringUTFChars(env, path, 0);
|
||||
|
||||
int result = initRecorder(pathStr);
|
||||
@ -516,12 +516,12 @@ JNIEXPORT int Java_org_telegram_android_MediaController_startRecord(JNIEnv *env,
|
||||
return result;
|
||||
}
|
||||
|
||||
JNIEXPORT int Java_org_telegram_android_MediaController_writeFrame(JNIEnv *env, jclass class, jobject frame, jint len) {
|
||||
JNIEXPORT int Java_org_telegram_messenger_MediaController_writeFrame(JNIEnv *env, jclass class, jobject frame, jint len) {
|
||||
jbyte *frameBytes = (*env)->GetDirectBufferAddress(env, frame);
|
||||
return writeFrame(frameBytes, len);
|
||||
}
|
||||
|
||||
JNIEXPORT void Java_org_telegram_android_MediaController_stopRecord(JNIEnv *env, jclass class) {
|
||||
JNIEXPORT void Java_org_telegram_messenger_MediaController_stopRecord(JNIEnv *env, jclass class) {
|
||||
cleanupRecorder();
|
||||
}
|
||||
|
||||
@ -618,22 +618,22 @@ void fillBuffer(uint8_t *buffer, int capacity, int *args) {
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT jlong Java_org_telegram_android_MediaController_getTotalPcmDuration(JNIEnv *env, jclass class) {
|
||||
JNIEXPORT jlong Java_org_telegram_messenger_MediaController_getTotalPcmDuration(JNIEnv *env, jclass class) {
|
||||
return _totalPcmDuration;
|
||||
}
|
||||
|
||||
JNIEXPORT void Java_org_telegram_android_MediaController_readOpusFile(JNIEnv *env, jclass class, jobject buffer, jint capacity, jintArray args) {
|
||||
JNIEXPORT void Java_org_telegram_messenger_MediaController_readOpusFile(JNIEnv *env, jclass class, jobject buffer, jint capacity, jintArray args) {
|
||||
jint *argsArr = (*env)->GetIntArrayElements(env, args, 0);
|
||||
jbyte *bufferBytes = (*env)->GetDirectBufferAddress(env, buffer);
|
||||
fillBuffer(bufferBytes, capacity, argsArr);
|
||||
(*env)->ReleaseIntArrayElements(env, args, argsArr, 0);
|
||||
}
|
||||
|
||||
JNIEXPORT int Java_org_telegram_android_MediaController_seekOpusFile(JNIEnv *env, jclass class, jfloat position) {
|
||||
JNIEXPORT int Java_org_telegram_messenger_MediaController_seekOpusFile(JNIEnv *env, jclass class, jfloat position) {
|
||||
return seekPlayer(position);
|
||||
}
|
||||
|
||||
JNIEXPORT int Java_org_telegram_android_MediaController_openOpusFile(JNIEnv *env, jclass class, jstring path) {
|
||||
JNIEXPORT int Java_org_telegram_messenger_MediaController_openOpusFile(JNIEnv *env, jclass class, jstring path) {
|
||||
const char *pathStr = (*env)->GetStringUTFChars(env, path, 0);
|
||||
|
||||
int result = initPlayer(pathStr);
|
||||
@ -645,11 +645,11 @@ JNIEXPORT int Java_org_telegram_android_MediaController_openOpusFile(JNIEnv *env
|
||||
return result;
|
||||
}
|
||||
|
||||
JNIEXPORT void Java_org_telegram_android_MediaController_closeOpusFile(JNIEnv *env, jclass class) {
|
||||
JNIEXPORT void Java_org_telegram_messenger_MediaController_closeOpusFile(JNIEnv *env, jclass class) {
|
||||
cleanupPlayer();
|
||||
}
|
||||
|
||||
JNIEXPORT int Java_org_telegram_android_MediaController_isOpusFile(JNIEnv *env, jclass class, jstring path) {
|
||||
JNIEXPORT int Java_org_telegram_messenger_MediaController_isOpusFile(JNIEnv *env, jclass class, jstring path) {
|
||||
const char *pathStr = (*env)->GetStringUTFChars(env, path, 0);
|
||||
|
||||
int result = 0;
|
||||
|
@ -4,12 +4,14 @@
|
||||
#include <sys/types.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdlib.h>
|
||||
#include "aes/aes.h"
|
||||
#include <openssl/aes.h>
|
||||
#include "utils.h"
|
||||
#include "sqlite.h"
|
||||
#include "gif.h"
|
||||
#include "image.h"
|
||||
|
||||
int registerNativeTgNetFunctions(JavaVM *vm, JNIEnv *env);
|
||||
|
||||
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
|
||||
JNIEnv *env = 0;
|
||||
srand(time(NULL));
|
||||
@ -26,6 +28,10 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (registerNativeTgNetFunctions(vm, env) != JNI_TRUE) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
gifOnJNILoad(vm, reserved, env);
|
||||
|
||||
return JNI_VERSION_1_6;
|
||||
@ -51,43 +57,3 @@ JNIEXPORT void Java_org_telegram_messenger_Utilities_aesIgeEncryption(JNIEnv *en
|
||||
(*env)->ReleaseByteArrayElements(env, key, keyBuff, JNI_ABORT);
|
||||
(*env)->ReleaseByteArrayElements(env, iv, ivBuff, 0);
|
||||
}
|
||||
|
||||
uint64_t gcd(uint64_t a, uint64_t b){
|
||||
while(a != 0 && b != 0) {
|
||||
while((b & 1) == 0) b >>= 1;
|
||||
while((a & 1) == 0) a >>= 1;
|
||||
if(a > b) a -= b; else b -= a;
|
||||
}
|
||||
return b == 0 ? a : b;
|
||||
}
|
||||
|
||||
JNIEXPORT jlong Java_org_telegram_messenger_Utilities_doPQNative(JNIEnv* env, jclass class, jlong _what) {
|
||||
uint64_t what = _what;
|
||||
int it = 0, i, j;
|
||||
uint64_t g = 0;
|
||||
for (i = 0; i < 3 || it < 1000; i++){
|
||||
int q = ((lrand48() & 15) + 17) % what;
|
||||
uint64_t x = (long long)lrand48() % (what - 1) + 1, y = x;
|
||||
int lim = 1 << (i + 18), j;
|
||||
for(j = 1; j < lim; j++){
|
||||
++it;
|
||||
uint64_t a = x, b = x, c = q;
|
||||
while(b){
|
||||
if(b & 1){
|
||||
c += a;
|
||||
if(c >= what) c -= what;
|
||||
}
|
||||
a += a;
|
||||
if(a >= what) a -= what;
|
||||
b >>= 1;
|
||||
}
|
||||
x = c;
|
||||
uint64_t z = x < y ? what + x - y : x - y;
|
||||
g = gcd(z, what);
|
||||
if(g != 1) break;
|
||||
if(!(j & (j - 1))) y = x;
|
||||
}
|
||||
if(g > 1 && g < what) break;
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -23,7 +23,7 @@
|
||||
**
|
||||
** The official C-language API documentation for SQLite is derived
|
||||
** from comments in this file. This file is the authoritative source
|
||||
** on how SQLite interfaces are suppose to operate.
|
||||
** on how SQLite interfaces are supposed to operate.
|
||||
**
|
||||
** The name of this file under configuration management is "sqlite.h.in".
|
||||
** The makefile makes some minor changes to this file (such as inserting
|
||||
@ -111,9 +111,9 @@ extern "C" {
|
||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||
** [sqlite_version()] and [sqlite_source_id()].
|
||||
*/
|
||||
#define SQLITE_VERSION "3.8.10"
|
||||
#define SQLITE_VERSION_NUMBER 3008010
|
||||
#define SQLITE_SOURCE_ID "2015-05-07 11:53:08 cf975957b9ae671f34bb65f049acf351e650d437"
|
||||
#define SQLITE_VERSION "3.8.11.1"
|
||||
#define SQLITE_VERSION_NUMBER 3008011
|
||||
#define SQLITE_SOURCE_ID "2015-07-29 20:00:57 cf538e2783e468bbc25e7cb2a9ee64d3e0e80b2f"
|
||||
|
||||
/*
|
||||
** CAPI3REF: Run-Time Library Version Numbers
|
||||
@ -963,6 +963,14 @@ struct sqlite3_io_methods {
|
||||
** circumstances in order to fix a problem with priority inversion.
|
||||
** Applications should <em>not</em> use this file-control.
|
||||
**
|
||||
** <li>[[SQLITE_FCNTL_ZIPVFS]]
|
||||
** The [SQLITE_FCNTL_ZIPVFS] opcode is implemented by zipvfs only. All other
|
||||
** VFS should return SQLITE_NOTFOUND for this opcode.
|
||||
**
|
||||
** <li>[[SQLITE_FCNTL_RBU]]
|
||||
** The [SQLITE_FCNTL_RBU] opcode is implemented by the special VFS used by
|
||||
** the RBU extension only. All other VFS should return SQLITE_NOTFOUND for
|
||||
** this opcode.
|
||||
** </ul>
|
||||
*/
|
||||
#define SQLITE_FCNTL_LOCKSTATE 1
|
||||
@ -988,6 +996,8 @@ struct sqlite3_io_methods {
|
||||
#define SQLITE_FCNTL_COMMIT_PHASETWO 22
|
||||
#define SQLITE_FCNTL_WIN32_SET_HANDLE 23
|
||||
#define SQLITE_FCNTL_WAL_BLOCK 24
|
||||
#define SQLITE_FCNTL_ZIPVFS 25
|
||||
#define SQLITE_FCNTL_RBU 26
|
||||
|
||||
/* deprecated names */
|
||||
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
|
||||
@ -3390,7 +3400,9 @@ SQLITE_API int SQLITE_STDCALL sqlite3_stmt_busy(sqlite3_stmt*);
|
||||
** Some interfaces require a protected sqlite3_value. Other interfaces
|
||||
** will accept either a protected or an unprotected sqlite3_value.
|
||||
** Every interface that accepts sqlite3_value arguments specifies
|
||||
** whether or not it requires a protected sqlite3_value.
|
||||
** whether or not it requires a protected sqlite3_value. The
|
||||
** [sqlite3_value_dup()] interface can be used to construct a new
|
||||
** protected sqlite3_value from an unprotected sqlite3_value.
|
||||
**
|
||||
** The terms "protected" and "unprotected" refer to whether or not
|
||||
** a mutex is held. An internal mutex is held for a protected
|
||||
@ -3550,6 +3562,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_bind_text64(sqlite3_stmt*, int, const char
|
||||
void(*)(void*), unsigned char encoding);
|
||||
SQLITE_API int SQLITE_STDCALL sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*);
|
||||
SQLITE_API int SQLITE_STDCALL sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n);
|
||||
SQLITE_API int SQLITE_STDCALL sqlite3_bind_zeroblob64(sqlite3_stmt*, int, sqlite3_uint64);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Number Of SQL Parameters
|
||||
@ -3893,8 +3906,6 @@ SQLITE_API int SQLITE_STDCALL sqlite3_data_count(sqlite3_stmt *pStmt);
|
||||
** KEYWORDS: {column access functions}
|
||||
** METHOD: sqlite3_stmt
|
||||
**
|
||||
** These routines form the "result set" interface.
|
||||
**
|
||||
** ^These routines return information about a single column of the current
|
||||
** result row of a query. ^In every case the first argument is a pointer
|
||||
** to the [prepared statement] that is being evaluated (the [sqlite3_stmt*]
|
||||
@ -3954,13 +3965,14 @@ SQLITE_API int SQLITE_STDCALL sqlite3_data_count(sqlite3_stmt *pStmt);
|
||||
** even empty strings, are always zero-terminated. ^The return
|
||||
** value from sqlite3_column_blob() for a zero-length BLOB is a NULL pointer.
|
||||
**
|
||||
** ^The object returned by [sqlite3_column_value()] is an
|
||||
** [unprotected sqlite3_value] object. An unprotected sqlite3_value object
|
||||
** may only be used with [sqlite3_bind_value()] and [sqlite3_result_value()].
|
||||
** <b>Warning:</b> ^The object returned by [sqlite3_column_value()] is an
|
||||
** [unprotected sqlite3_value] object. In a multithreaded environment,
|
||||
** an unprotected sqlite3_value object may only be used safely with
|
||||
** [sqlite3_bind_value()] and [sqlite3_result_value()].
|
||||
** If the [unprotected sqlite3_value] object returned by
|
||||
** [sqlite3_column_value()] is used in any other way, including calls
|
||||
** to routines like [sqlite3_value_int()], [sqlite3_value_text()],
|
||||
** or [sqlite3_value_bytes()], then the behavior is undefined.
|
||||
** or [sqlite3_value_bytes()], the behavior is not threadsafe.
|
||||
**
|
||||
** These routines attempt to convert the value where appropriate. ^For
|
||||
** example, if the internal representation is FLOAT and a text result
|
||||
@ -3991,12 +4003,6 @@ SQLITE_API int SQLITE_STDCALL sqlite3_data_count(sqlite3_stmt *pStmt);
|
||||
** </table>
|
||||
** </blockquote>)^
|
||||
**
|
||||
** The table above makes reference to standard C library functions atoi()
|
||||
** and atof(). SQLite does not really use these functions. It has its
|
||||
** own equivalent internal routines. The atoi() and atof() names are
|
||||
** used in the table for brevity and because they are familiar to most
|
||||
** C programmers.
|
||||
**
|
||||
** Note that when type conversions occur, pointers returned by prior
|
||||
** calls to sqlite3_column_blob(), sqlite3_column_text(), and/or
|
||||
** sqlite3_column_text16() may be invalidated.
|
||||
@ -4021,7 +4027,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_data_count(sqlite3_stmt *pStmt);
|
||||
** of conversion are done in place when it is possible, but sometimes they
|
||||
** are not possible and in those cases prior pointers are invalidated.
|
||||
**
|
||||
** The safest and easiest to remember policy is to invoke these routines
|
||||
** The safest policy is to invoke these routines
|
||||
** in one of the following ways:
|
||||
**
|
||||
** <ul>
|
||||
@ -4041,7 +4047,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_data_count(sqlite3_stmt *pStmt);
|
||||
** ^The pointers returned are valid until a type conversion occurs as
|
||||
** described above, or until [sqlite3_step()] or [sqlite3_reset()] or
|
||||
** [sqlite3_finalize()] is called. ^The memory space used to hold strings
|
||||
** and BLOBs is freed automatically. Do <b>not</b> pass the pointers returned
|
||||
** and BLOBs is freed automatically. Do <em>not</em> pass the pointers returned
|
||||
** from [sqlite3_column_blob()], [sqlite3_column_text()], etc. into
|
||||
** [sqlite3_free()].
|
||||
**
|
||||
@ -4291,7 +4297,7 @@ SQLITE_API SQLITE_DEPRECATED int SQLITE_STDCALL sqlite3_memory_alarm(void(*)(voi
|
||||
#endif
|
||||
|
||||
/*
|
||||
** CAPI3REF: Obtaining SQL Function Parameter Values
|
||||
** CAPI3REF: Obtaining SQL Values
|
||||
** METHOD: sqlite3_value
|
||||
**
|
||||
** The C-language implementation of SQL functions and aggregates uses
|
||||
@ -4349,6 +4355,23 @@ SQLITE_API const void *SQLITE_STDCALL sqlite3_value_text16be(sqlite3_value*);
|
||||
SQLITE_API int SQLITE_STDCALL sqlite3_value_type(sqlite3_value*);
|
||||
SQLITE_API int SQLITE_STDCALL sqlite3_value_numeric_type(sqlite3_value*);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Copy And Free SQL Values
|
||||
** METHOD: sqlite3_value
|
||||
**
|
||||
** ^The sqlite3_value_dup(V) interface makes a copy of the [sqlite3_value]
|
||||
** object D and returns a pointer to that copy. ^The [sqlite3_value] returned
|
||||
** is a [protected sqlite3_value] object even if the input is not.
|
||||
** ^The sqlite3_value_dup(V) interface returns NULL if V is NULL or if a
|
||||
** memory allocation fails.
|
||||
**
|
||||
** ^The sqlite3_value_free(V) interface frees an [sqlite3_value] object
|
||||
** previously obtained from [sqlite3_value_dup()]. ^If V is a NULL pointer
|
||||
** then sqlite3_value_free(V) is a harmless no-op.
|
||||
*/
|
||||
SQLITE_API SQLITE_EXPERIMENTAL sqlite3_value *SQLITE_STDCALL sqlite3_value_dup(const sqlite3_value*);
|
||||
SQLITE_API SQLITE_EXPERIMENTAL void SQLITE_STDCALL sqlite3_value_free(sqlite3_value*);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Obtain Aggregate Function Context
|
||||
** METHOD: sqlite3_context
|
||||
@ -4512,9 +4535,9 @@ typedef void (*sqlite3_destructor_type)(void*);
|
||||
** to by the second parameter and which is N bytes long where N is the
|
||||
** third parameter.
|
||||
**
|
||||
** ^The sqlite3_result_zeroblob() interfaces set the result of
|
||||
** the application-defined function to be a BLOB containing all zero
|
||||
** bytes and N bytes in size, where N is the value of the 2nd parameter.
|
||||
** ^The sqlite3_result_zeroblob(C,N) and sqlite3_result_zeroblob64(C,N)
|
||||
** interfaces set the result of the application-defined function to be
|
||||
** a BLOB containing all zero bytes and N bytes in size.
|
||||
**
|
||||
** ^The sqlite3_result_double() interface sets the result from
|
||||
** an application-defined function to be a floating point value specified
|
||||
@ -4596,7 +4619,7 @@ typedef void (*sqlite3_destructor_type)(void*);
|
||||
** from [sqlite3_malloc()] before it returns.
|
||||
**
|
||||
** ^The sqlite3_result_value() interface sets the result of
|
||||
** the application-defined function to be a copy the
|
||||
** the application-defined function to be a copy of the
|
||||
** [unprotected sqlite3_value] object specified by the 2nd parameter. ^The
|
||||
** sqlite3_result_value() interface makes a copy of the [sqlite3_value]
|
||||
** so that the [sqlite3_value] specified in the parameter may change or
|
||||
@ -4629,6 +4652,7 @@ SQLITE_API void SQLITE_STDCALL sqlite3_result_text16le(sqlite3_context*, const v
|
||||
SQLITE_API void SQLITE_STDCALL sqlite3_result_text16be(sqlite3_context*, const void*, int,void(*)(void*));
|
||||
SQLITE_API void SQLITE_STDCALL sqlite3_result_value(sqlite3_context*, sqlite3_value*);
|
||||
SQLITE_API void SQLITE_STDCALL sqlite3_result_zeroblob(sqlite3_context*, int n);
|
||||
SQLITE_API int SQLITE_STDCALL sqlite3_result_zeroblob64(sqlite3_context*, sqlite3_uint64 n);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Define New Collating Sequences
|
||||
@ -5872,7 +5896,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_blob_open(
|
||||
**
|
||||
** ^This function sets the database handle error code and message.
|
||||
*/
|
||||
SQLITE_API SQLITE_EXPERIMENTAL int SQLITE_STDCALL sqlite3_blob_reopen(sqlite3_blob *, sqlite3_int64);
|
||||
SQLITE_API int SQLITE_STDCALL sqlite3_blob_reopen(sqlite3_blob *, sqlite3_int64);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Close A BLOB Handle
|
||||
@ -6269,6 +6293,9 @@ SQLITE_API int SQLITE_STDCALL sqlite3_mutex_notheld(sqlite3_mutex*);
|
||||
#define SQLITE_MUTEX_STATIC_APP1 8 /* For use by application */
|
||||
#define SQLITE_MUTEX_STATIC_APP2 9 /* For use by application */
|
||||
#define SQLITE_MUTEX_STATIC_APP3 10 /* For use by application */
|
||||
#define SQLITE_MUTEX_STATIC_VFS1 11 /* For use by built-in VFS */
|
||||
#define SQLITE_MUTEX_STATIC_VFS2 12 /* For use by extension VFS */
|
||||
#define SQLITE_MUTEX_STATIC_VFS3 13 /* For use by application VFS */
|
||||
|
||||
/*
|
||||
** CAPI3REF: Retrieve the mutex for a database connection
|
||||
@ -7682,7 +7709,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_vtab_on_conflict(sqlite3 *);
|
||||
**
|
||||
** See also: [sqlite3_stmt_scanstatus_reset()]
|
||||
*/
|
||||
SQLITE_API SQLITE_EXPERIMENTAL int SQLITE_STDCALL sqlite3_stmt_scanstatus(
|
||||
SQLITE_API int SQLITE_STDCALL sqlite3_stmt_scanstatus(
|
||||
sqlite3_stmt *pStmt, /* Prepared statement for which info desired */
|
||||
int idx, /* Index of loop to report on */
|
||||
int iScanStatusOp, /* Information desired. SQLITE_SCANSTAT_* */
|
||||
@ -7698,7 +7725,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int SQLITE_STDCALL sqlite3_stmt_scanstatus(
|
||||
** This API is only available if the library is built with pre-processor
|
||||
** symbol [SQLITE_ENABLE_STMT_SCANSTATUS] defined.
|
||||
*/
|
||||
SQLITE_API SQLITE_EXPERIMENTAL void SQLITE_STDCALL sqlite3_stmt_scanstatus_reset(sqlite3_stmt*);
|
||||
SQLITE_API void SQLITE_STDCALL sqlite3_stmt_scanstatus_reset(sqlite3_stmt*);
|
||||
|
||||
|
||||
/*
|
||||
@ -7813,6 +7840,8 @@ struct sqlite3_rtree_query_info {
|
||||
int eParentWithin; /* Visibility of parent node */
|
||||
int eWithin; /* OUT: Visiblity */
|
||||
sqlite3_rtree_dbl rScore; /* OUT: Write the score here */
|
||||
/* The following fields are only available in 3.8.11 and later */
|
||||
sqlite3_value **apSqlParam; /* Original SQL values of parameters */
|
||||
};
|
||||
|
||||
/*
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -123,25 +123,25 @@
|
||||
android:windowSoftInputMode="adjustResize|stateHidden">
|
||||
</activity>
|
||||
|
||||
<receiver android:name="org.telegram.android.AutoMessageHeardReceiver">
|
||||
<receiver android:name=".AutoMessageHeardReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="org.telegram.messenger.ACTION_MESSAGE_HEARD"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name="org.telegram.android.AutoMessageReplyReceiver">
|
||||
<receiver android:name=".AutoMessageReplyReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="org.telegram.messenger.ACTION_MESSAGE_REPLY"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name="org.telegram.android.SmsListener">
|
||||
<receiver android:name=".SmsListener">
|
||||
<intent-filter>
|
||||
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service android:name="org.telegram.android.AuthenticatorService" android:exported="true">
|
||||
<service android:name=".AuthenticatorService" android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.accounts.AccountAuthenticator"/>
|
||||
</intent-filter>
|
||||
@ -149,7 +149,7 @@
|
||||
android:resource="@xml/auth"/>
|
||||
</service>
|
||||
|
||||
<service android:name="org.telegram.android.ContactsSyncAdapterService" android:exported="true">
|
||||
<service android:name=".ContactsSyncAdapterService" android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.SyncAdapter" />
|
||||
</intent-filter>
|
||||
@ -159,12 +159,12 @@
|
||||
android:resource="@xml/contacts" />
|
||||
</service>
|
||||
|
||||
<service android:name="org.telegram.android.NotificationsService" android:enabled="true"/>
|
||||
<service android:name="org.telegram.android.NotificationRepeat" android:exported="false"/>
|
||||
<service android:name="org.telegram.android.VideoEncodingService" android:enabled="true"/>
|
||||
<service android:name="org.telegram.android.MusicPlayerService" android:exported="true" android:enabled="true"/>
|
||||
<service android:name=".NotificationsService" android:enabled="true"/>
|
||||
<service android:name=".NotificationRepeat" android:exported="false"/>
|
||||
<service android:name=".VideoEncodingService" android:enabled="true"/>
|
||||
<service android:name=".MusicPlayerService" android:exported="true" android:enabled="true"/>
|
||||
|
||||
<receiver android:name="org.telegram.android.MusicPlayerReceiver" >
|
||||
<receiver android:name=".MusicPlayerReceiver" >
|
||||
<intent-filter>
|
||||
<action android:name="org.telegram.android.musicplayer.close" />
|
||||
<action android:name="org.telegram.android.musicplayer.pause" />
|
||||
@ -176,14 +176,14 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name="org.telegram.android.AppStartReceiver" android:enabled="true">
|
||||
<receiver android:name=".AppStartReceiver" android:enabled="true">
|
||||
<intent-filter>
|
||||
<action android:name="org.telegram.start" />
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name="org.telegram.android.WearReplyReceiver" android:enabled="true"/>
|
||||
<receiver android:name=".WearReplyReceiver" android:enabled="true"/>
|
||||
|
||||
<uses-library android:name="com.sec.android.app.multiwindow" android:required="false" />
|
||||
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
|
||||
|
@ -1,94 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, https://code.google.com/p/pyronet/
|
||||
* 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 the <ORGANIZATION> nor the names of its 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 HOLDER 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.
|
||||
*/
|
||||
|
||||
package jawnae.pyronet;
|
||||
|
||||
import org.telegram.messenger.BuffersStorage;
|
||||
import org.telegram.messenger.ByteBufferDesc;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class ByteStream {
|
||||
private final ArrayList<ByteBufferDesc> queue;
|
||||
|
||||
public ByteStream() {
|
||||
this.queue = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void append(ByteBufferDesc buf) {
|
||||
if (buf == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
this.queue.add(buf);
|
||||
}
|
||||
|
||||
public boolean hasData() {
|
||||
int size = this.queue.size();
|
||||
for (ByteBufferDesc aQueue : this.queue) {
|
||||
if (aQueue.hasRemaining()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void get(ByteBuffer dst) {
|
||||
if (dst == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
|
||||
for (ByteBufferDesc bufferDesc : this.queue) {
|
||||
ByteBuffer data = bufferDesc.buffer.slice();
|
||||
|
||||
if (data.remaining() > dst.remaining()) {
|
||||
data.limit(dst.remaining());
|
||||
dst.put(data);
|
||||
break;
|
||||
}
|
||||
|
||||
dst.put(data);
|
||||
|
||||
if (!dst.hasRemaining()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void discard(int count) {
|
||||
int original = count;
|
||||
|
||||
while (count > 0) {
|
||||
ByteBufferDesc data = this.queue.get(0);
|
||||
|
||||
if (count < data.buffer.remaining()) {
|
||||
data.position(data.position() + count);
|
||||
count = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
this.queue.remove(0);
|
||||
BuffersStorage.getInstance().reuseFreeBuffer(data);
|
||||
count -= data.buffer.remaining();
|
||||
}
|
||||
|
||||
if (count != 0) {
|
||||
throw new PyroException("discarded " + (original - count) + "/" + original + " bytes");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,531 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, https://code.google.com/p/pyronet/
|
||||
* 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 the <ORGANIZATION> nor the names of its 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 HOLDER 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.
|
||||
*/
|
||||
|
||||
package jawnae.pyronet;
|
||||
|
||||
import org.telegram.messenger.ByteBufferDesc;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.NotYetConnectedException;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
public class PyroClient {
|
||||
private final PyroSelector selector;
|
||||
|
||||
private final SelectionKey key;
|
||||
|
||||
private final ByteStream outbound;
|
||||
|
||||
// called by PyroSelector.connect()
|
||||
PyroClient(PyroSelector selector, InetSocketAddress bind,
|
||||
InetSocketAddress host) throws IOException {
|
||||
this(selector, PyroClient.bindAndConfigure(selector,
|
||||
SocketChannel.open(), bind));
|
||||
|
||||
((SocketChannel) this.key.channel()).connect(host);
|
||||
}
|
||||
|
||||
// called by PyroClient and PyroServer
|
||||
PyroClient(PyroSelector selector, SelectionKey key) {
|
||||
this.selector = selector;
|
||||
this.selector.checkThread();
|
||||
|
||||
this.key = key;
|
||||
this.key.attach(this);
|
||||
|
||||
this.outbound = new ByteStream();
|
||||
this.listeners = new CopyOnWriteArrayList<>();
|
||||
this.lastEventTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
private final List<PyroClientListener> listeners;
|
||||
|
||||
public void addListener(PyroClientListener listener) {
|
||||
this.selector.checkThread();
|
||||
|
||||
this.listeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeListener(PyroClientListener listener) {
|
||||
this.selector.checkThread();
|
||||
|
||||
this.listeners.remove(listener);
|
||||
}
|
||||
|
||||
public void removeListeners() {
|
||||
this.selector.checkThread();
|
||||
|
||||
this.listeners.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the PyroSelector that created this client
|
||||
*/
|
||||
|
||||
public PyroSelector selector() {
|
||||
return this.selector;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
private Object attachment;
|
||||
|
||||
/**
|
||||
* Attach any object to a client, for example to store session information
|
||||
*/
|
||||
|
||||
public void attach(Object attachment) {
|
||||
this.attachment = attachment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the previously attached object, or <code>null</code> if none is
|
||||
* set
|
||||
*/
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T attachment() {
|
||||
return (T) this.attachment;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/**
|
||||
* Returns the local socket address (host+port)
|
||||
*/
|
||||
|
||||
public InetSocketAddress getLocalAddress() {
|
||||
Socket s = ((SocketChannel) key.channel()).socket();
|
||||
return (InetSocketAddress) s.getLocalSocketAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the remove socket address (host+port)
|
||||
*/
|
||||
|
||||
public InetSocketAddress getRemoteAddress() {
|
||||
Socket s = ((SocketChannel) key.channel()).socket();
|
||||
return (InetSocketAddress) s.getRemoteSocketAddress();
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
public void setTimeout(int ms) throws IOException {
|
||||
this.selector.checkThread();
|
||||
|
||||
((SocketChannel) key.channel()).socket().setSoTimeout(ms);
|
||||
|
||||
// prevent a call to setTimeout from immediately causing a timeout
|
||||
this.lastEventTime = System.currentTimeMillis();
|
||||
this.timeout = ms;
|
||||
}
|
||||
|
||||
public void setLinger(boolean enabled, int seconds) throws IOException {
|
||||
this.selector.checkThread();
|
||||
|
||||
((SocketChannel) key.channel()).socket().setSoLinger(enabled, seconds);
|
||||
}
|
||||
|
||||
public void setKeepAlive(boolean enabled) throws IOException {
|
||||
this.selector.checkThread();
|
||||
|
||||
((SocketChannel) key.channel()).socket().setKeepAlive(enabled);
|
||||
}
|
||||
|
||||
private boolean doEagerWrite = false;
|
||||
|
||||
/**
|
||||
* If enabled, causes calls to write() to make an attempt to write the
|
||||
* bytes, without waiting for the selector to signal writable state.
|
||||
*/
|
||||
|
||||
public void setEagerWrite(boolean enabled) {
|
||||
this.doEagerWrite = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will enqueue the bytes to send them<br>
|
||||
* 1. when the selector is ready to write, if eagerWrite is disabled
|
||||
* (default)<br>
|
||||
* 2. immediately, if eagerWrite is enabled<br>
|
||||
* The ByteBuffer instance is kept, not copied, and thus should not be
|
||||
* modified
|
||||
*
|
||||
* @throws PyroException
|
||||
* when shutdown() has been called.
|
||||
*/
|
||||
|
||||
public void write(ByteBufferDesc data) throws PyroException {
|
||||
this.selector.checkThread();
|
||||
|
||||
if (!this.key.isValid()) {
|
||||
// graceful, as this is meant to be async
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.doShutdown) {
|
||||
throw new PyroException("shutting down");
|
||||
}
|
||||
|
||||
this.outbound.append(data);
|
||||
|
||||
if (this.doEagerWrite) {
|
||||
try {
|
||||
this.onReadyToWrite(System.currentTimeMillis());
|
||||
} catch (NotYetConnectedException exc) {
|
||||
this.adjustWriteOp();
|
||||
} catch (IOException exc) {
|
||||
this.onConnectionError(exc);
|
||||
key.cancel();
|
||||
}
|
||||
} else {
|
||||
this.adjustWriteOp();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes as many as possible bytes to the socket buffer
|
||||
*/
|
||||
|
||||
public int flush() {
|
||||
int total = 0;
|
||||
|
||||
while (this.outbound.hasData()) {
|
||||
int written;
|
||||
|
||||
try {
|
||||
written = this.onReadyToWrite(System.currentTimeMillis());
|
||||
} catch (IOException exc) {
|
||||
written = 0;
|
||||
}
|
||||
|
||||
if (written == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
total += written;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes an attempt to write all outbound bytes, fails on failure.
|
||||
*
|
||||
* @throws PyroException
|
||||
* on failure
|
||||
*/
|
||||
|
||||
public int flushOrDie() throws PyroException {
|
||||
int total = 0;
|
||||
|
||||
while (this.outbound.hasData()) {
|
||||
int written;
|
||||
|
||||
try {
|
||||
written = this.onReadyToWrite(System.currentTimeMillis());
|
||||
} catch (IOException exc) {
|
||||
written = 0;
|
||||
}
|
||||
|
||||
if (written == 0) {
|
||||
throw new PyroException("failed to flush, wrote " + total
|
||||
+ " bytes");
|
||||
}
|
||||
|
||||
total += written;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether there are bytes left in the outbound queue.
|
||||
*/
|
||||
|
||||
public boolean hasDataEnqueued() {
|
||||
this.selector.checkThread();
|
||||
|
||||
return this.outbound.hasData();
|
||||
}
|
||||
|
||||
private boolean doShutdown = false;
|
||||
|
||||
/**
|
||||
* Gracefully shuts down the connection. The connection is closed after the
|
||||
* last outbound bytes are sent. Enqueuing new bytes after shutdown, is not
|
||||
* allowed and will throw an exception
|
||||
*/
|
||||
|
||||
public void shutdown() {
|
||||
this.selector.checkThread();
|
||||
|
||||
this.doShutdown = true;
|
||||
|
||||
if (!this.hasDataEnqueued()) {
|
||||
this.dropConnection();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately drop the connection, regardless of any pending outbound
|
||||
* bytes. Actual behaviour depends on the socket linger settings.
|
||||
*/
|
||||
|
||||
public void dropConnection() {
|
||||
this.selector.checkThread();
|
||||
|
||||
if (this.isDisconnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Runnable drop = new Runnable() {
|
||||
@Override
|
||||
@SuppressWarnings("synthetic-access")
|
||||
public void run() {
|
||||
try {
|
||||
if (key.channel().isOpen()) {
|
||||
(key.channel()).close();
|
||||
}
|
||||
} catch (Exception exc) {
|
||||
selector().scheduleTask(this);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
drop.run();
|
||||
|
||||
this.onConnectionError("local");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the connection is connected to a remote client.
|
||||
*/
|
||||
|
||||
public boolean isDisconnected() {
|
||||
this.selector.checkThread();
|
||||
|
||||
return !this.key.channel().isOpen();
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
void onInterestOp(long now) {
|
||||
if (!key.isValid()) {
|
||||
this.onConnectionError("remote");
|
||||
} else {
|
||||
try {
|
||||
if (key.isConnectable())
|
||||
this.onReadyToConnect(now);
|
||||
if (key.isReadable())
|
||||
this.onReadyToRead(now);
|
||||
if (key.isWritable())
|
||||
this.onReadyToWrite(now);
|
||||
} catch (Exception exc) {
|
||||
this.onConnectionError(exc);
|
||||
key.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long timeout = 0L;
|
||||
|
||||
private long lastEventTime;
|
||||
|
||||
boolean didTimeout(long now) {
|
||||
return this.timeout != 0 && (now - this.lastEventTime) > this.timeout;
|
||||
}
|
||||
|
||||
private void onReadyToConnect(long now) throws IOException {
|
||||
this.selector.checkThread();
|
||||
this.lastEventTime = now;
|
||||
|
||||
this.selector.adjustInterestOp(key, SelectionKey.OP_CONNECT, false);
|
||||
boolean result = ((SocketChannel) key.channel()).finishConnect();
|
||||
|
||||
for (PyroClientListener listener: this.listeners)
|
||||
listener.connectedClient(this);
|
||||
}
|
||||
|
||||
private void onReadyToRead(long now) throws IOException {
|
||||
this.selector.checkThread();
|
||||
this.lastEventTime = now;
|
||||
|
||||
SocketChannel channel = (SocketChannel) key.channel();
|
||||
|
||||
ByteBuffer buffer = this.selector.networkBuffer;
|
||||
|
||||
// read from channel
|
||||
buffer.clear();
|
||||
int bytes = channel.read(buffer);
|
||||
if (bytes == -1)
|
||||
throw new EOFException();
|
||||
buffer.flip();
|
||||
|
||||
for (PyroClientListener listener: this.listeners)
|
||||
listener.receivedData(this, buffer);
|
||||
}
|
||||
|
||||
private int onReadyToWrite(long now) throws IOException {
|
||||
this.selector.checkThread();
|
||||
//this.lastEventTime = now;
|
||||
|
||||
int sent = 0;
|
||||
|
||||
// copy outbound bytes into network buffer
|
||||
ByteBuffer buffer = this.selector.networkBuffer;
|
||||
buffer.clear();
|
||||
this.outbound.get(buffer);
|
||||
buffer.flip();
|
||||
|
||||
// write to channel
|
||||
if (buffer.hasRemaining()) {
|
||||
SocketChannel channel = (SocketChannel) key.channel();
|
||||
sent = channel.write(buffer);
|
||||
}
|
||||
|
||||
if (sent > 0) {
|
||||
this.outbound.discard(sent);
|
||||
}
|
||||
|
||||
for (PyroClientListener listener: this.listeners)
|
||||
listener.sentData(this, sent);
|
||||
|
||||
this.adjustWriteOp();
|
||||
|
||||
if (this.doShutdown && !this.outbound.hasData()) {
|
||||
this.dropConnection();
|
||||
}
|
||||
|
||||
return sent;
|
||||
}
|
||||
|
||||
void onConnectionError(final Object cause) {
|
||||
this.selector.checkThread();
|
||||
|
||||
try {
|
||||
// if the key is invalid, the channel may remain open!!
|
||||
this.key.channel().close();
|
||||
} catch (Exception exc) {
|
||||
// type: java.io.IOException
|
||||
// message:
|
||||
// "A non-blocking socket operation could not be completed immediately"
|
||||
|
||||
// try again later
|
||||
this.selector.scheduleTask(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
PyroClient.this.onConnectionError(cause);
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (cause instanceof ConnectException) {
|
||||
for (PyroClientListener listener: this.listeners)
|
||||
listener.unconnectableClient(this, (Exception)cause);
|
||||
} else if (cause instanceof EOFException) // after read=-1
|
||||
{
|
||||
for (PyroClientListener listener: this.listeners)
|
||||
listener.disconnectedClient(this);
|
||||
} else if (cause instanceof IOException) {
|
||||
for (PyroClientListener listener: this.listeners)
|
||||
listener.droppedClient(this, (IOException) cause);
|
||||
} else if (!(cause instanceof String)) {
|
||||
for (PyroClientListener listener: this.listeners)
|
||||
listener.unconnectableClient(this, null);
|
||||
} else if (cause.equals("local")) {
|
||||
for (PyroClientListener listener: this.listeners)
|
||||
listener.disconnectedClient(this);
|
||||
} else if (cause.equals("remote")) {
|
||||
for (PyroClientListener listener: this.listeners)
|
||||
listener.droppedClient(this, null);
|
||||
} else {
|
||||
throw new IllegalStateException("illegal cause: " + cause);
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName() + "[" + this.getAddressText()
|
||||
+ "]";
|
||||
}
|
||||
|
||||
private String getAddressText() {
|
||||
if (!this.key.channel().isOpen())
|
||||
return "closed";
|
||||
|
||||
InetSocketAddress sockaddr = this.getRemoteAddress();
|
||||
if (sockaddr == null)
|
||||
return "connecting";
|
||||
InetAddress inetaddr = sockaddr.getAddress();
|
||||
return inetaddr.getHostAddress() + "@" + sockaddr.getPort();
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
void adjustWriteOp() {
|
||||
this.selector.checkThread();
|
||||
|
||||
boolean interested = this.outbound.hasData();
|
||||
|
||||
this.selector.adjustInterestOp(this.key, SelectionKey.OP_WRITE,
|
||||
interested);
|
||||
}
|
||||
|
||||
static SelectionKey bindAndConfigure(PyroSelector selector,
|
||||
SocketChannel channel, InetSocketAddress bind) throws IOException {
|
||||
selector.checkThread();
|
||||
|
||||
channel.socket().bind(bind);
|
||||
|
||||
return configure(selector, channel, true);
|
||||
}
|
||||
|
||||
static SelectionKey configure(PyroSelector selector,
|
||||
SocketChannel channel, boolean connect) throws IOException {
|
||||
selector.checkThread();
|
||||
|
||||
channel.configureBlocking(false);
|
||||
// channel.socket().setSoLinger(false, 0); // this will b0rk your
|
||||
// connections
|
||||
channel.socket().setSoLinger(true, 4);
|
||||
channel.socket().setReuseAddress(false);
|
||||
channel.socket().setKeepAlive(false);
|
||||
channel.socket().setTcpNoDelay(true);
|
||||
channel.socket().setReceiveBufferSize(PyroSelector.BUFFER_SIZE);
|
||||
channel.socket().setSendBufferSize(PyroSelector.BUFFER_SIZE);
|
||||
|
||||
int ops = SelectionKey.OP_READ;
|
||||
if (connect)
|
||||
ops |= SelectionKey.OP_CONNECT;
|
||||
|
||||
return selector.register(channel, ops);
|
||||
}
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, https://code.google.com/p/pyronet/
|
||||
* 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 the <ORGANIZATION> nor the names of its 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 HOLDER 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.
|
||||
*/
|
||||
|
||||
package jawnae.pyronet;
|
||||
|
||||
import org.telegram.messenger.BuildVars;
|
||||
import org.telegram.messenger.FileLog;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class PyroClientAdapter implements PyroClientListener {
|
||||
public void connectedClient(PyroClient client) {
|
||||
//
|
||||
}
|
||||
|
||||
public void unconnectableClient(PyroClient client, Exception cause) {
|
||||
if (BuildVars.DEBUG_VERSION) {
|
||||
FileLog.e("tmessages", "unconnectable");
|
||||
}
|
||||
}
|
||||
|
||||
public void droppedClient(PyroClient client, IOException cause) {
|
||||
if (cause != null) {
|
||||
if (BuildVars.DEBUG_VERSION) {
|
||||
FileLog.e("tmessages", this.getClass().getSimpleName() + ".droppedClient() caught exception: " + cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void disconnectedClient(PyroClient client) {
|
||||
//
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
public void receivedData(PyroClient client, ByteBuffer data) {
|
||||
//
|
||||
}
|
||||
|
||||
public void sentData(PyroClient client, int bytes) {
|
||||
//
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, https://code.google.com/p/pyronet/
|
||||
* 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 the <ORGANIZATION> nor the names of its 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 HOLDER 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.
|
||||
*/
|
||||
|
||||
package jawnae.pyronet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public interface PyroClientListener {
|
||||
void connectedClient(PyroClient client);
|
||||
|
||||
void unconnectableClient(PyroClient client, Exception cause);
|
||||
|
||||
void droppedClient(PyroClient client, IOException cause);
|
||||
|
||||
void disconnectedClient(PyroClient client);
|
||||
|
||||
//
|
||||
|
||||
void receivedData(PyroClient client, ByteBuffer data);
|
||||
|
||||
void sentData(PyroClient client, int bytes);
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, https://code.google.com/p/pyronet/
|
||||
* 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 the <ORGANIZATION> nor the names of its 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 HOLDER 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.
|
||||
*/
|
||||
|
||||
package jawnae.pyronet;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class PyroException extends RuntimeException {
|
||||
public PyroException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public PyroException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public PyroException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
}
|
@ -1,235 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, https://code.google.com/p/pyronet/
|
||||
* 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 the <ORGANIZATION> nor the names of its 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 HOLDER 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.
|
||||
*/
|
||||
|
||||
package jawnae.pyronet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.CancelledKeyException;
|
||||
import java.nio.channels.SelectableChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.Selector;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
public class PyroSelector {
|
||||
public static boolean DO_NOT_CHECK_NETWORK_THREAD = true;
|
||||
|
||||
public static final int BUFFER_SIZE = 64 * 1024;
|
||||
|
||||
Thread networkThread;
|
||||
|
||||
final Selector nioSelector;
|
||||
|
||||
final ByteBuffer networkBuffer;
|
||||
|
||||
public PyroSelector() {
|
||||
this.networkBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
|
||||
|
||||
try {
|
||||
this.nioSelector = Selector.open();
|
||||
} catch (IOException exc) {
|
||||
throw new PyroException("Failed to open a selector?!", exc);
|
||||
}
|
||||
|
||||
this.networkThread = Thread.currentThread();
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
public final boolean isNetworkThread() {
|
||||
return DO_NOT_CHECK_NETWORK_THREAD || networkThread == Thread.currentThread();
|
||||
}
|
||||
|
||||
public final Thread networkThread() {
|
||||
return this.networkThread;
|
||||
}
|
||||
|
||||
public final void checkThread() {
|
||||
if (DO_NOT_CHECK_NETWORK_THREAD) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isNetworkThread()) {
|
||||
throw new PyroException("call from outside the network-thread, you must schedule tasks");
|
||||
}
|
||||
}
|
||||
|
||||
public PyroClient connect(InetSocketAddress host) throws IOException {
|
||||
return this.connect(host, null);
|
||||
}
|
||||
|
||||
public PyroClient connect(InetSocketAddress host, InetSocketAddress bind) throws IOException {
|
||||
return new PyroClient(this, bind, host);
|
||||
}
|
||||
|
||||
public void select() {
|
||||
this.select(0);
|
||||
}
|
||||
|
||||
public void select(long eventTimeout) {
|
||||
this.checkThread();
|
||||
|
||||
//
|
||||
|
||||
this.executePendingTasks();
|
||||
this.performNioSelect(eventTimeout);
|
||||
|
||||
final long now = System.currentTimeMillis();
|
||||
this.handleSelectedKeys(now);
|
||||
this.handleSocketTimeouts(now);
|
||||
}
|
||||
|
||||
private void executePendingTasks() {
|
||||
while (true) {
|
||||
Runnable task = this.tasks.poll();
|
||||
if (task == null)
|
||||
break;
|
||||
|
||||
try {
|
||||
task.run();
|
||||
} catch (Throwable cause) {
|
||||
cause.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void performNioSelect(long timeout) {
|
||||
int selected;
|
||||
try {
|
||||
selected = nioSelector.select(timeout);
|
||||
} catch (IOException exc) {
|
||||
exc.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSelectedKeys(long now) {
|
||||
Iterator<SelectionKey> keys = nioSelector.selectedKeys().iterator();
|
||||
|
||||
while (keys.hasNext()) {
|
||||
SelectionKey key = keys.next();
|
||||
keys.remove();
|
||||
|
||||
if (key.channel() instanceof SocketChannel) {
|
||||
PyroClient client = (PyroClient) key.attachment();
|
||||
client.onInterestOp(now);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSocketTimeouts(long now) {
|
||||
for (SelectionKey key: nioSelector.keys()) {
|
||||
if (key.channel() instanceof SocketChannel) {
|
||||
PyroClient client = (PyroClient) key.attachment();
|
||||
|
||||
if (client.didTimeout(now)) {
|
||||
try {
|
||||
throw new SocketTimeoutException(
|
||||
"PyroNet detected NIO timeout");
|
||||
} catch (SocketTimeoutException exc) {
|
||||
client.onConnectionError(exc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void spawnNetworkThread(final String name) {
|
||||
// now no thread can access this selector
|
||||
//
|
||||
// N.B.
|
||||
// -- updating this non-volatile field is thread-safe
|
||||
// -- because the current thread can see it (causing it
|
||||
// -- to become UNACCESSIBLE), and all other threads
|
||||
// -- that might not see the change will
|
||||
// -- (continue to) block access to this selector
|
||||
this.networkThread = null;
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// spawned thread can access this selector
|
||||
//
|
||||
// N.B.
|
||||
// -- updating this non-volatile field is thread-safe
|
||||
// -- because the current thread can see it (causing it
|
||||
// -- to become ACCESSIBLE), and all other threads
|
||||
// -- that might not see the change will
|
||||
// -- (continue to) block access to this selector
|
||||
PyroSelector.this.networkThread = Thread.currentThread();
|
||||
|
||||
// start select-loop
|
||||
try {
|
||||
while (true) {
|
||||
PyroSelector.this.select();
|
||||
}
|
||||
} catch (Exception exc) {
|
||||
// this never be caused by Pyro-code
|
||||
throw new IllegalStateException(exc);
|
||||
}
|
||||
}
|
||||
}, name).start();
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
private BlockingQueue<Runnable> tasks = new LinkedBlockingQueue<Runnable>();
|
||||
|
||||
public void scheduleTask(Runnable task) {
|
||||
if (task == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
|
||||
try {
|
||||
this.tasks.put(task);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
wakeup();
|
||||
}
|
||||
|
||||
public void wakeup() {
|
||||
this.nioSelector.wakeup();
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
final SelectionKey register(SelectableChannel channel, int ops)
|
||||
throws IOException {
|
||||
return channel.register(this.nioSelector, ops);
|
||||
}
|
||||
|
||||
final boolean adjustInterestOp(SelectionKey key, int op, boolean state) {
|
||||
this.checkThread();
|
||||
|
||||
try {
|
||||
int ops = key.interestOps();
|
||||
boolean changed = state != ((ops & op) == op);
|
||||
if (changed)
|
||||
key.interestOps(state ? (ops | op) : (ops & ~op));
|
||||
return changed;
|
||||
} catch (CancelledKeyException exc) {
|
||||
// ignore
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@
|
||||
package org.telegram.SQLite;
|
||||
|
||||
import org.telegram.messenger.FileLog;
|
||||
import org.telegram.tgnet.NativeByteBuffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
@ -67,6 +68,11 @@ public class SQLiteCursor {
|
||||
return columnByteBufferValue(preparedStatement.getStatementHandle(), columnIndex, buffer);
|
||||
}
|
||||
|
||||
public int byteBufferValue(int columnIndex, NativeByteBuffer buffer) throws SQLiteException {
|
||||
checkRow();
|
||||
return columnByteBufferValue(preparedStatement.getStatementHandle(), columnIndex, buffer.buffer);
|
||||
}
|
||||
|
||||
public int getTypeOf(int columnIndex) throws SQLiteException {
|
||||
checkRow();
|
||||
return columnType(preparedStatement.getStatementHandle(), columnIndex);
|
||||
|
@ -8,8 +8,8 @@
|
||||
|
||||
package org.telegram.SQLite;
|
||||
|
||||
import org.telegram.messenger.BuildConfig;
|
||||
import org.telegram.messenger.FileLog;
|
||||
import org.telegram.tgnet.NativeByteBuffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashMap;
|
||||
@ -30,8 +30,7 @@ public class SQLitePreparedStatement {
|
||||
public SQLitePreparedStatement(SQLiteDatabase db, String sql, boolean finalize) throws SQLiteException {
|
||||
finalizeAfterQuery = finalize;
|
||||
sqliteStatementHandle = prepare(db.getSQLiteHandle(), sql);
|
||||
/*//if (BuildVars.DEBUG_VERSION) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
/*if (BuildVars.DEBUG_VERSION) {
|
||||
if (hashMap == null) {
|
||||
hashMap = new HashMap<>();
|
||||
}
|
||||
@ -102,8 +101,7 @@ public class SQLitePreparedStatement {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
/*//if (BuildVars.DEBUG_VERSION) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
/*if (BuildVars.DEBUG_VERSION) {
|
||||
hashMap.remove(this);
|
||||
}*/
|
||||
isFinalized = true;
|
||||
@ -125,6 +123,10 @@ public class SQLitePreparedStatement {
|
||||
bindByteBuffer(sqliteStatementHandle, index, value, value.limit());
|
||||
}
|
||||
|
||||
public void bindByteBuffer(int index, NativeByteBuffer value) throws SQLiteException {
|
||||
bindByteBuffer(sqliteStatementHandle, index, value.buffer, value.limit());
|
||||
}
|
||||
|
||||
public void bindString(int index, String value) throws SQLiteException {
|
||||
bindString(sqliteStatementHandle, index, value);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,191 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public abstract class Animator10 implements Cloneable {
|
||||
|
||||
ArrayList<AnimatorListener> mListeners = null;
|
||||
ArrayList<AnimatorPauseListener> mPauseListeners = null;
|
||||
boolean mPaused = false;
|
||||
|
||||
public abstract long getStartDelay();
|
||||
|
||||
public abstract void setStartDelay(long startDelay);
|
||||
|
||||
public abstract Animator10 setDuration(long duration);
|
||||
|
||||
public abstract long getDuration();
|
||||
|
||||
public abstract void setInterpolator(Interpolator value);
|
||||
|
||||
public abstract boolean isRunning();
|
||||
|
||||
public void start() {
|
||||
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
|
||||
}
|
||||
|
||||
public void end() {
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void pause() {
|
||||
if (isStarted() && !mPaused) {
|
||||
mPaused = true;
|
||||
if (mPauseListeners != null) {
|
||||
ArrayList<AnimatorPauseListener> tmpListeners = (ArrayList<AnimatorPauseListener>) mPauseListeners.clone();
|
||||
int numListeners = tmpListeners.size();
|
||||
for (AnimatorPauseListener tmpListener : tmpListeners) {
|
||||
tmpListener.onAnimationPause(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void resume() {
|
||||
if (mPaused) {
|
||||
mPaused = false;
|
||||
if (mPauseListeners != null) {
|
||||
ArrayList<AnimatorPauseListener> tmpListeners = (ArrayList<AnimatorPauseListener>) mPauseListeners.clone();
|
||||
int numListeners = tmpListeners.size();
|
||||
for (AnimatorPauseListener tmpListener : tmpListeners) {
|
||||
tmpListener.onAnimationResume(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isPaused() {
|
||||
return mPaused;
|
||||
}
|
||||
|
||||
public boolean isStarted() {
|
||||
return isRunning();
|
||||
}
|
||||
|
||||
public Interpolator getInterpolator() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void addListener(AnimatorListener listener) {
|
||||
if (mListeners == null) {
|
||||
mListeners = new ArrayList<AnimatorListener>();
|
||||
}
|
||||
mListeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeListener(AnimatorListener listener) {
|
||||
if (mListeners == null) {
|
||||
return;
|
||||
}
|
||||
mListeners.remove(listener);
|
||||
if (mListeners.size() == 0) {
|
||||
mListeners = null;
|
||||
}
|
||||
}
|
||||
|
||||
public ArrayList<AnimatorListener> getListeners() {
|
||||
return mListeners;
|
||||
}
|
||||
|
||||
public void addPauseListener(AnimatorPauseListener listener) {
|
||||
if (mPauseListeners == null) {
|
||||
mPauseListeners = new ArrayList<AnimatorPauseListener>();
|
||||
}
|
||||
mPauseListeners.add(listener);
|
||||
}
|
||||
|
||||
public void removePauseListener(AnimatorPauseListener listener) {
|
||||
if (mPauseListeners == null) {
|
||||
return;
|
||||
}
|
||||
mPauseListeners.remove(listener);
|
||||
if (mPauseListeners.size() == 0) {
|
||||
mPauseListeners = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void removeAllListeners() {
|
||||
if (mListeners != null) {
|
||||
mListeners.clear();
|
||||
mListeners = null;
|
||||
}
|
||||
if (mPauseListeners != null) {
|
||||
mPauseListeners.clear();
|
||||
mPauseListeners = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Animator10 clone() {
|
||||
try {
|
||||
final Animator10 anim = (Animator10) super.clone();
|
||||
if (mListeners != null) {
|
||||
ArrayList<AnimatorListener> oldListeners = mListeners;
|
||||
anim.mListeners = new ArrayList<AnimatorListener>();
|
||||
int numListeners = oldListeners.size();
|
||||
for (AnimatorListener oldListener : oldListeners) {
|
||||
anim.mListeners.add(oldListener);
|
||||
}
|
||||
}
|
||||
if (mPauseListeners != null) {
|
||||
ArrayList<AnimatorPauseListener> oldListeners = mPauseListeners;
|
||||
anim.mPauseListeners = new ArrayList<AnimatorPauseListener>();
|
||||
int numListeners = oldListeners.size();
|
||||
for (AnimatorPauseListener oldListener : oldListeners) {
|
||||
anim.mPauseListeners.add(oldListener);
|
||||
}
|
||||
}
|
||||
return anim;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
public void setupStartValues() {
|
||||
|
||||
}
|
||||
|
||||
public void setupEndValues() {
|
||||
|
||||
}
|
||||
|
||||
public void setTarget(Object target) {
|
||||
|
||||
}
|
||||
|
||||
public interface AnimatorListener {
|
||||
void onAnimationStart(Animator10 animation);
|
||||
void onAnimationEnd(Animator10 animation);
|
||||
void onAnimationCancel(Animator10 animation);
|
||||
void onAnimationRepeat(Animator10 animation);
|
||||
}
|
||||
|
||||
public interface AnimatorPauseListener {
|
||||
void onAnimationPause(Animator10 animation);
|
||||
void onAnimationResume(Animator10 animation);
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
public abstract class AnimatorListenerAdapter10 implements Animator10.AnimatorListener, Animator10.AnimatorPauseListener {
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator10 animation) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator10 animation) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animator10 animation) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(Animator10 animation) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationPause(Animator10 animation) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationResume(Animator10 animation) {
|
||||
|
||||
}
|
||||
}
|
@ -1,705 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public final class AnimatorSet10 extends Animator10 {
|
||||
|
||||
private ArrayList<Animator10> mPlayingSet = new ArrayList<>();
|
||||
private HashMap<Animator10, Node> mNodeMap = new HashMap<>();
|
||||
private ArrayList<Node> mNodes = new ArrayList<>();
|
||||
private ArrayList<Node> mSortedNodes = new ArrayList<>();
|
||||
private boolean mNeedsSort = true;
|
||||
private AnimatorSetListener mSetListener = null;
|
||||
boolean mTerminated = false;
|
||||
private boolean mStarted = false;
|
||||
private long mStartDelay = 0;
|
||||
private ValueAnimator mDelayAnim = null;
|
||||
private long mDuration = -1;
|
||||
private Interpolator mInterpolator = null;
|
||||
|
||||
public void playTogether(Animator10... items) {
|
||||
if (items != null) {
|
||||
mNeedsSort = true;
|
||||
Builder builder = play(items[0]);
|
||||
for (int i = 1; i < items.length; ++i) {
|
||||
builder.with(items[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void playTogether(Collection<Animator10> items) {
|
||||
if (items != null && items.size() > 0) {
|
||||
mNeedsSort = true;
|
||||
Builder builder = null;
|
||||
for (Animator10 anim : items) {
|
||||
if (builder == null) {
|
||||
builder = play(anim);
|
||||
} else {
|
||||
builder.with(anim);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void playSequentially(Animator10... items) {
|
||||
if (items != null) {
|
||||
mNeedsSort = true;
|
||||
if (items.length == 1) {
|
||||
play(items[0]);
|
||||
} else {
|
||||
for (int i = 0; i < items.length - 1; ++i) {
|
||||
play(items[i]).before(items[i+1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void playSequentially(List<Animator10> items) {
|
||||
if (items != null && items.size() > 0) {
|
||||
mNeedsSort = true;
|
||||
if (items.size() == 1) {
|
||||
play(items.get(0));
|
||||
} else {
|
||||
for (int i = 0; i < items.size() - 1; ++i) {
|
||||
play(items.get(i)).before(items.get(i+1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ArrayList<Animator10> getChildAnimations() {
|
||||
ArrayList<Animator10> childList = new ArrayList<>();
|
||||
for (Node node : mNodes) {
|
||||
childList.add(node.animation);
|
||||
}
|
||||
return childList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTarget(Object target) {
|
||||
for (Node node : mNodes) {
|
||||
Animator10 animation = node.animation;
|
||||
if (animation instanceof AnimatorSet10) {
|
||||
animation.setTarget(target);
|
||||
} else if (animation instanceof ObjectAnimator10) {
|
||||
animation.setTarget(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInterpolator(Interpolator interpolator) {
|
||||
mInterpolator = interpolator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Interpolator getInterpolator() {
|
||||
return mInterpolator;
|
||||
}
|
||||
|
||||
public Builder play(Animator10 anim) {
|
||||
if (anim != null) {
|
||||
mNeedsSort = true;
|
||||
return new Builder(anim);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void cancel() {
|
||||
mTerminated = true;
|
||||
if (isStarted()) {
|
||||
ArrayList<AnimatorListener> tmpListeners = null;
|
||||
if (mListeners != null) {
|
||||
tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone();
|
||||
for (AnimatorListener listener : tmpListeners) {
|
||||
listener.onAnimationCancel(this);
|
||||
}
|
||||
}
|
||||
if (mDelayAnim != null && mDelayAnim.isRunning()) {
|
||||
mDelayAnim.cancel();
|
||||
} else if (mSortedNodes.size() > 0) {
|
||||
for (Node node : mSortedNodes) {
|
||||
node.animation.cancel();
|
||||
}
|
||||
}
|
||||
if (tmpListeners != null) {
|
||||
for (AnimatorListener listener : tmpListeners) {
|
||||
listener.onAnimationEnd(this);
|
||||
}
|
||||
}
|
||||
mStarted = false;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void end() {
|
||||
mTerminated = true;
|
||||
if (isStarted()) {
|
||||
if (mSortedNodes.size() != mNodes.size()) {
|
||||
// hasn't been started yet - sort the nodes now, then end them
|
||||
sortNodes();
|
||||
for (Node node : mSortedNodes) {
|
||||
if (mSetListener == null) {
|
||||
mSetListener = new AnimatorSetListener(this);
|
||||
}
|
||||
node.animation.addListener(mSetListener);
|
||||
}
|
||||
}
|
||||
if (mDelayAnim != null) {
|
||||
mDelayAnim.cancel();
|
||||
}
|
||||
if (mSortedNodes.size() > 0) {
|
||||
for (Node node : mSortedNodes) {
|
||||
node.animation.end();
|
||||
}
|
||||
}
|
||||
if (mListeners != null) {
|
||||
ArrayList<AnimatorListener> tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone();
|
||||
for (AnimatorListener listener : tmpListeners) {
|
||||
listener.onAnimationEnd(this);
|
||||
}
|
||||
}
|
||||
mStarted = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
for (Node node : mNodes) {
|
||||
if (node.animation.isRunning()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStarted() {
|
||||
return mStarted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getStartDelay() {
|
||||
return mStartDelay;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStartDelay(long startDelay) {
|
||||
mStartDelay = startDelay;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDuration() {
|
||||
return mDuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnimatorSet10 setDuration(long duration) {
|
||||
if (duration < 0) {
|
||||
throw new IllegalArgumentException("duration must be a value of zero or greater");
|
||||
}
|
||||
mDuration = duration;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setupStartValues() {
|
||||
for (Node node : mNodes) {
|
||||
node.animation.setupStartValues();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setupEndValues() {
|
||||
for (Node node : mNodes) {
|
||||
node.animation.setupEndValues();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pause() {
|
||||
boolean previouslyPaused = mPaused;
|
||||
super.pause();
|
||||
if (!previouslyPaused && mPaused) {
|
||||
if (mDelayAnim != null) {
|
||||
mDelayAnim.pause();
|
||||
} else {
|
||||
for (Node node : mNodes) {
|
||||
node.animation.pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume() {
|
||||
boolean previouslyPaused = mPaused;
|
||||
super.resume();
|
||||
if (previouslyPaused && !mPaused) {
|
||||
if (mDelayAnim != null) {
|
||||
mDelayAnim.resume();
|
||||
} else {
|
||||
for (Node node : mNodes) {
|
||||
node.animation.resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void start() {
|
||||
mTerminated = false;
|
||||
mStarted = true;
|
||||
mPaused = false;
|
||||
|
||||
if (mDuration >= 0) {
|
||||
for (Node node : mNodes) {
|
||||
node.animation.setDuration(mDuration);
|
||||
}
|
||||
}
|
||||
if (mInterpolator != null) {
|
||||
for (Node node : mNodes) {
|
||||
node.animation.setInterpolator(mInterpolator);
|
||||
}
|
||||
}
|
||||
|
||||
sortNodes();
|
||||
|
||||
int numSortedNodes = mSortedNodes.size();
|
||||
for (Node node : mSortedNodes) {
|
||||
ArrayList<AnimatorListener> oldListeners = node.animation.getListeners();
|
||||
if (oldListeners != null && oldListeners.size() > 0) {
|
||||
final ArrayList<AnimatorListener> clonedListeners = new
|
||||
ArrayList<>(oldListeners);
|
||||
|
||||
for (AnimatorListener listener : clonedListeners) {
|
||||
if (listener instanceof DependencyListener ||
|
||||
listener instanceof AnimatorSetListener) {
|
||||
node.animation.removeListener(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final ArrayList<Node> nodesToStart = new ArrayList<>();
|
||||
for (Node node : mSortedNodes) {
|
||||
if (mSetListener == null) {
|
||||
mSetListener = new AnimatorSetListener(this);
|
||||
}
|
||||
if (node.dependencies == null || node.dependencies.size() == 0) {
|
||||
nodesToStart.add(node);
|
||||
} else {
|
||||
int numDependencies = node.dependencies.size();
|
||||
for (int j = 0; j < numDependencies; ++j) {
|
||||
Dependency dependency = node.dependencies.get(j);
|
||||
dependency.node.animation.addListener(
|
||||
new DependencyListener(this, node, dependency.rule));
|
||||
}
|
||||
node.tmpDependencies = (ArrayList<Dependency>) node.dependencies.clone();
|
||||
}
|
||||
node.animation.addListener(mSetListener);
|
||||
}
|
||||
|
||||
if (mStartDelay <= 0) {
|
||||
for (Node node : nodesToStart) {
|
||||
node.animation.start();
|
||||
mPlayingSet.add(node.animation);
|
||||
}
|
||||
} else {
|
||||
mDelayAnim = ValueAnimator.ofFloat(0f, 1f);
|
||||
mDelayAnim.setDuration(mStartDelay);
|
||||
mDelayAnim.addListener(new AnimatorListenerAdapter10() {
|
||||
boolean canceled = false;
|
||||
public void onAnimationCancel(Animator10 anim) {
|
||||
canceled = true;
|
||||
}
|
||||
public void onAnimationEnd(Animator10 anim) {
|
||||
if (!canceled) {
|
||||
int numNodes = nodesToStart.size();
|
||||
for (Node node : nodesToStart) {
|
||||
node.animation.start();
|
||||
mPlayingSet.add(node.animation);
|
||||
}
|
||||
}
|
||||
mDelayAnim = null;
|
||||
}
|
||||
});
|
||||
mDelayAnim.start();
|
||||
}
|
||||
if (mListeners != null) {
|
||||
ArrayList<AnimatorListener> tmpListeners =
|
||||
(ArrayList<AnimatorListener>) mListeners.clone();
|
||||
int numListeners = tmpListeners.size();
|
||||
for (AnimatorListener tmpListener : tmpListeners) {
|
||||
tmpListener.onAnimationStart(this);
|
||||
}
|
||||
}
|
||||
if (mNodes.size() == 0 && mStartDelay == 0) {
|
||||
mStarted = false;
|
||||
if (mListeners != null) {
|
||||
ArrayList<AnimatorListener> tmpListeners =
|
||||
(ArrayList<AnimatorListener>) mListeners.clone();
|
||||
int numListeners = tmpListeners.size();
|
||||
for (AnimatorListener tmpListener : tmpListeners) {
|
||||
tmpListener.onAnimationEnd(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnimatorSet10 clone() {
|
||||
final AnimatorSet10 anim = (AnimatorSet10) super.clone();
|
||||
|
||||
anim.mNeedsSort = true;
|
||||
anim.mTerminated = false;
|
||||
anim.mStarted = false;
|
||||
anim.mPlayingSet = new ArrayList<>();
|
||||
anim.mNodeMap = new HashMap<>();
|
||||
anim.mNodes = new ArrayList<>();
|
||||
anim.mSortedNodes = new ArrayList<>();
|
||||
|
||||
HashMap<Node, Node> nodeCloneMap = new HashMap<>();
|
||||
for (Node node : mNodes) {
|
||||
Node nodeClone = node.clone();
|
||||
nodeCloneMap.put(node, nodeClone);
|
||||
anim.mNodes.add(nodeClone);
|
||||
anim.mNodeMap.put(nodeClone.animation, nodeClone);
|
||||
nodeClone.dependencies = null;
|
||||
nodeClone.tmpDependencies = null;
|
||||
nodeClone.nodeDependents = null;
|
||||
nodeClone.nodeDependencies = null;
|
||||
ArrayList<AnimatorListener> cloneListeners = nodeClone.animation.getListeners();
|
||||
if (cloneListeners != null) {
|
||||
ArrayList<AnimatorListener> listenersToRemove = null;
|
||||
for (AnimatorListener listener : cloneListeners) {
|
||||
if (listener instanceof AnimatorSetListener) {
|
||||
if (listenersToRemove == null) {
|
||||
listenersToRemove = new ArrayList<>();
|
||||
}
|
||||
listenersToRemove.add(listener);
|
||||
}
|
||||
}
|
||||
if (listenersToRemove != null) {
|
||||
for (AnimatorListener listener : listenersToRemove) {
|
||||
cloneListeners.remove(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Node node : mNodes) {
|
||||
Node nodeClone = nodeCloneMap.get(node);
|
||||
if (node.dependencies != null) {
|
||||
for (Dependency dependency : node.dependencies) {
|
||||
Node clonedDependencyNode = nodeCloneMap.get(dependency.node);
|
||||
Dependency cloneDependency = new Dependency(clonedDependencyNode, dependency.rule);
|
||||
nodeClone.addDependency(cloneDependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
return anim;
|
||||
}
|
||||
|
||||
private static class DependencyListener implements AnimatorListener {
|
||||
|
||||
private AnimatorSet10 mAnimatorSet;
|
||||
private Node mNode;
|
||||
private int mRule;
|
||||
|
||||
public DependencyListener(AnimatorSet10 animatorSet, Node node, int rule) {
|
||||
this.mAnimatorSet = animatorSet;
|
||||
this.mNode = node;
|
||||
this.mRule = rule;
|
||||
}
|
||||
|
||||
public void onAnimationCancel(Animator10 animation) {
|
||||
|
||||
}
|
||||
|
||||
public void onAnimationEnd(Animator10 animation) {
|
||||
if (mRule == Dependency.AFTER) {
|
||||
startIfReady(animation);
|
||||
}
|
||||
}
|
||||
|
||||
public void onAnimationRepeat(Animator10 animation) {
|
||||
|
||||
}
|
||||
|
||||
public void onAnimationStart(Animator10 animation) {
|
||||
if (mRule == Dependency.WITH) {
|
||||
startIfReady(animation);
|
||||
}
|
||||
}
|
||||
|
||||
private void startIfReady(Animator10 dependencyAnimation) {
|
||||
if (mAnimatorSet.mTerminated) {
|
||||
return;
|
||||
}
|
||||
Dependency dependencyToRemove = null;
|
||||
int numDependencies = mNode.tmpDependencies.size();
|
||||
for (int i = 0; i < numDependencies; ++i) {
|
||||
Dependency dependency = mNode.tmpDependencies.get(i);
|
||||
if (dependency.rule == mRule && dependency.node.animation == dependencyAnimation) {
|
||||
dependencyToRemove = dependency;
|
||||
dependencyAnimation.removeListener(this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
mNode.tmpDependencies.remove(dependencyToRemove);
|
||||
if (mNode.tmpDependencies.size() == 0) {
|
||||
mNode.animation.start();
|
||||
mAnimatorSet.mPlayingSet.add(mNode.animation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class AnimatorSetListener implements AnimatorListener {
|
||||
|
||||
private AnimatorSet10 mAnimatorSet;
|
||||
|
||||
AnimatorSetListener(AnimatorSet10 animatorSet) {
|
||||
mAnimatorSet = animatorSet;
|
||||
}
|
||||
|
||||
public void onAnimationCancel(Animator10 animation) {
|
||||
if (!mTerminated) {
|
||||
if (mPlayingSet.size() == 0) {
|
||||
if (mListeners != null) {
|
||||
int numListeners = mListeners.size();
|
||||
for (AnimatorListener mListener : mListeners) {
|
||||
mListener.onAnimationCancel(mAnimatorSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void onAnimationEnd(Animator10 animation) {
|
||||
animation.removeListener(this);
|
||||
mPlayingSet.remove(animation);
|
||||
Node animNode = mAnimatorSet.mNodeMap.get(animation);
|
||||
animNode.done = true;
|
||||
if (!mTerminated) {
|
||||
ArrayList<Node> sortedNodes = mAnimatorSet.mSortedNodes;
|
||||
boolean allDone = true;
|
||||
int numSortedNodes = sortedNodes.size();
|
||||
for (Node sortedNode : sortedNodes) {
|
||||
if (!sortedNode.done) {
|
||||
allDone = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allDone) {
|
||||
if (mListeners != null) {
|
||||
ArrayList<AnimatorListener> tmpListeners =
|
||||
(ArrayList<AnimatorListener>) mListeners.clone();
|
||||
int numListeners = tmpListeners.size();
|
||||
for (AnimatorListener tmpListener : tmpListeners) {
|
||||
tmpListener.onAnimationEnd(mAnimatorSet);
|
||||
}
|
||||
}
|
||||
mAnimatorSet.mStarted = false;
|
||||
mAnimatorSet.mPaused = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onAnimationRepeat(Animator10 animation) {
|
||||
|
||||
}
|
||||
|
||||
public void onAnimationStart(Animator10 animation) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void sortNodes() {
|
||||
if (mNeedsSort) {
|
||||
mSortedNodes.clear();
|
||||
ArrayList<Node> roots = new ArrayList<>();
|
||||
int numNodes = mNodes.size();
|
||||
for (Node node : mNodes) {
|
||||
if (node.dependencies == null || node.dependencies.size() == 0) {
|
||||
roots.add(node);
|
||||
}
|
||||
}
|
||||
ArrayList<Node> tmpRoots = new ArrayList<>();
|
||||
while (roots.size() > 0) {
|
||||
int numRoots = roots.size();
|
||||
for (Node root : roots) {
|
||||
mSortedNodes.add(root);
|
||||
if (root.nodeDependents != null) {
|
||||
int numDependents = root.nodeDependents.size();
|
||||
for (int j = 0; j < numDependents; ++j) {
|
||||
Node node = root.nodeDependents.get(j);
|
||||
node.nodeDependencies.remove(root);
|
||||
if (node.nodeDependencies.size() == 0) {
|
||||
tmpRoots.add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
roots.clear();
|
||||
roots.addAll(tmpRoots);
|
||||
tmpRoots.clear();
|
||||
}
|
||||
mNeedsSort = false;
|
||||
if (mSortedNodes.size() != mNodes.size()) {
|
||||
throw new IllegalStateException("Circular dependencies cannot exist in AnimatorSet");
|
||||
}
|
||||
} else {
|
||||
int numNodes = mNodes.size();
|
||||
for (Node node : mNodes) {
|
||||
if (node.dependencies != null && node.dependencies.size() > 0) {
|
||||
int numDependencies = node.dependencies.size();
|
||||
for (int j = 0; j < numDependencies; ++j) {
|
||||
Dependency dependency = node.dependencies.get(j);
|
||||
if (node.nodeDependencies == null) {
|
||||
node.nodeDependencies = new ArrayList<>();
|
||||
}
|
||||
if (!node.nodeDependencies.contains(dependency.node)) {
|
||||
node.nodeDependencies.add(dependency.node);
|
||||
}
|
||||
}
|
||||
}
|
||||
node.done = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class Dependency {
|
||||
static final int WITH = 0;
|
||||
static final int AFTER = 1;
|
||||
public Node node;
|
||||
public int rule;
|
||||
|
||||
public Dependency(Node node, int rule) {
|
||||
this.node = node;
|
||||
this.rule = rule;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Node implements Cloneable {
|
||||
public Animator10 animation;
|
||||
public ArrayList<Dependency> dependencies = null;
|
||||
public ArrayList<Dependency> tmpDependencies = null;
|
||||
public ArrayList<Node> nodeDependencies = null;
|
||||
public ArrayList<Node> nodeDependents = null;
|
||||
public boolean done = false;
|
||||
|
||||
public Node(Animator10 animation) {
|
||||
this.animation = animation;
|
||||
}
|
||||
|
||||
public void addDependency(Dependency dependency) {
|
||||
if (dependencies == null) {
|
||||
dependencies = new ArrayList<>();
|
||||
nodeDependencies = new ArrayList<>();
|
||||
}
|
||||
dependencies.add(dependency);
|
||||
if (!nodeDependencies.contains(dependency.node)) {
|
||||
nodeDependencies.add(dependency.node);
|
||||
}
|
||||
Node dependencyNode = dependency.node;
|
||||
if (dependencyNode.nodeDependents == null) {
|
||||
dependencyNode.nodeDependents = new ArrayList<>();
|
||||
}
|
||||
dependencyNode.nodeDependents.add(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node clone() {
|
||||
try {
|
||||
Node node = (Node) super.clone();
|
||||
node.animation = animation.clone();
|
||||
return node;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Builder {
|
||||
|
||||
private Node mCurrentNode;
|
||||
|
||||
Builder(Animator10 anim) {
|
||||
mCurrentNode = mNodeMap.get(anim);
|
||||
if (mCurrentNode == null) {
|
||||
mCurrentNode = new Node(anim);
|
||||
mNodeMap.put(anim, mCurrentNode);
|
||||
mNodes.add(mCurrentNode);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder with(Animator10 anim) {
|
||||
Node node = mNodeMap.get(anim);
|
||||
if (node == null) {
|
||||
node = new Node(anim);
|
||||
mNodeMap.put(anim, node);
|
||||
mNodes.add(node);
|
||||
}
|
||||
Dependency dependency = new Dependency(mCurrentNode, Dependency.WITH);
|
||||
node.addDependency(dependency);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder before(Animator10 anim) {
|
||||
Node node = mNodeMap.get(anim);
|
||||
if (node == null) {
|
||||
node = new Node(anim);
|
||||
mNodeMap.put(anim, node);
|
||||
mNodes.add(node);
|
||||
}
|
||||
Dependency dependency = new Dependency(mCurrentNode, Dependency.AFTER);
|
||||
node.addDependency(dependency);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder after(Animator10 anim) {
|
||||
Node node = mNodeMap.get(anim);
|
||||
if (node == null) {
|
||||
node = new Node(anim);
|
||||
mNodeMap.put(anim, node);
|
||||
mNodes.add(node);
|
||||
}
|
||||
Dependency dependency = new Dependency(node, Dependency.AFTER);
|
||||
mCurrentNode.addDependency(dependency);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder after(long delay) {
|
||||
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
|
||||
anim.setDuration(delay);
|
||||
after(anim);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
public class FloatEvaluator implements TypeEvaluator<Number> {
|
||||
public Float evaluate(float fraction, Number startValue, Number endValue) {
|
||||
float startFloat = startValue.floatValue();
|
||||
return startFloat + fraction * (endValue.floatValue() - startFloat);
|
||||
}
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import org.telegram.android.Animation.Keyframe.FloatKeyframe;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
class FloatKeyframeSet extends KeyframeSet {
|
||||
private float firstValue;
|
||||
private float lastValue;
|
||||
private float deltaValue;
|
||||
private boolean firstTime = true;
|
||||
|
||||
public FloatKeyframeSet(FloatKeyframe... keyframes) {
|
||||
super(keyframes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue(float fraction) {
|
||||
return getFloatValue(fraction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FloatKeyframeSet clone() {
|
||||
ArrayList<Keyframe> keyframes = mKeyframes;
|
||||
int numKeyframes = mKeyframes.size();
|
||||
FloatKeyframe[] newKeyframes = new FloatKeyframe[numKeyframes];
|
||||
for (int i = 0; i < numKeyframes; ++i) {
|
||||
newKeyframes[i] = (FloatKeyframe) keyframes.get(i).clone();
|
||||
}
|
||||
return new FloatKeyframeSet(newKeyframes);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public float getFloatValue(float fraction) {
|
||||
if (mNumKeyframes == 2) {
|
||||
if (firstTime) {
|
||||
firstTime = false;
|
||||
firstValue = ((FloatKeyframe) mKeyframes.get(0)).getFloatValue();
|
||||
lastValue = ((FloatKeyframe) mKeyframes.get(1)).getFloatValue();
|
||||
deltaValue = lastValue - firstValue;
|
||||
}
|
||||
if (mInterpolator != null) {
|
||||
fraction = mInterpolator.getInterpolation(fraction);
|
||||
}
|
||||
if (mEvaluator == null) {
|
||||
return firstValue + fraction * deltaValue;
|
||||
} else {
|
||||
return ((Number)mEvaluator.evaluate(fraction, firstValue, lastValue)).floatValue();
|
||||
}
|
||||
}
|
||||
if (fraction <= 0f) {
|
||||
final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);
|
||||
final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(1);
|
||||
float prevValue = prevKeyframe.getFloatValue();
|
||||
float nextValue = nextKeyframe.getFloatValue();
|
||||
float prevFraction = prevKeyframe.getFraction();
|
||||
float nextFraction = nextKeyframe.getFraction();
|
||||
final Interpolator interpolator = nextKeyframe.getInterpolator();
|
||||
if (interpolator != null) {
|
||||
fraction = interpolator.getInterpolation(fraction);
|
||||
}
|
||||
float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
|
||||
return mEvaluator == null ? prevValue + intervalFraction * (nextValue - prevValue) : ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).floatValue();
|
||||
} else if (fraction >= 1f) {
|
||||
final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 2);
|
||||
final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 1);
|
||||
float prevValue = prevKeyframe.getFloatValue();
|
||||
float nextValue = nextKeyframe.getFloatValue();
|
||||
float prevFraction = prevKeyframe.getFraction();
|
||||
float nextFraction = nextKeyframe.getFraction();
|
||||
final Interpolator interpolator = nextKeyframe.getInterpolator();
|
||||
if (interpolator != null) {
|
||||
fraction = interpolator.getInterpolation(fraction);
|
||||
}
|
||||
float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
|
||||
return mEvaluator == null ? prevValue + intervalFraction * (nextValue - prevValue) : ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).floatValue();
|
||||
}
|
||||
FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);
|
||||
for (int i = 1; i < mNumKeyframes; ++i) {
|
||||
FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(i);
|
||||
if (fraction < nextKeyframe.getFraction()) {
|
||||
final Interpolator interpolator = nextKeyframe.getInterpolator();
|
||||
if (interpolator != null) {
|
||||
fraction = interpolator.getInterpolation(fraction);
|
||||
}
|
||||
float intervalFraction = (fraction - prevKeyframe.getFraction()) /
|
||||
(nextKeyframe.getFraction() - prevKeyframe.getFraction());
|
||||
float prevValue = prevKeyframe.getFloatValue();
|
||||
float nextValue = nextKeyframe.getFloatValue();
|
||||
return mEvaluator == null ? prevValue + intervalFraction * (nextValue - prevValue) : ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).floatValue();
|
||||
}
|
||||
prevKeyframe = nextKeyframe;
|
||||
}
|
||||
return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).floatValue();
|
||||
}
|
||||
}
|
@ -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.android.Animation;
|
||||
|
||||
public abstract class FloatProperty10<T> extends Property<T, Float> {
|
||||
|
||||
public FloatProperty10(String name) {
|
||||
super(Float.class, name);
|
||||
}
|
||||
|
||||
public abstract void setValue(T object, float value);
|
||||
|
||||
@Override
|
||||
final public void set(T object, Float value) {
|
||||
setValue(object, value.floatValue());
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
public class IntEvaluator implements TypeEvaluator<Integer> {
|
||||
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
|
||||
int startInt = startValue;
|
||||
return (int)(startInt + fraction * (endValue - startInt));
|
||||
}
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import org.telegram.android.Animation.Keyframe.IntKeyframe;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
class IntKeyframeSet extends KeyframeSet {
|
||||
private int firstValue;
|
||||
private int lastValue;
|
||||
private int deltaValue;
|
||||
private boolean firstTime = true;
|
||||
|
||||
public IntKeyframeSet(IntKeyframe... keyframes) {
|
||||
super(keyframes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue(float fraction) {
|
||||
return getIntValue(fraction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntKeyframeSet clone() {
|
||||
ArrayList<Keyframe> keyframes = mKeyframes;
|
||||
int numKeyframes = mKeyframes.size();
|
||||
IntKeyframe[] newKeyframes = new IntKeyframe[numKeyframes];
|
||||
for (int i = 0; i < numKeyframes; ++i) {
|
||||
newKeyframes[i] = (IntKeyframe) keyframes.get(i).clone();
|
||||
}
|
||||
return new IntKeyframeSet(newKeyframes);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public int getIntValue(float fraction) {
|
||||
if (mNumKeyframes == 2) {
|
||||
if (firstTime) {
|
||||
firstTime = false;
|
||||
firstValue = ((IntKeyframe) mKeyframes.get(0)).getIntValue();
|
||||
lastValue = ((IntKeyframe) mKeyframes.get(1)).getIntValue();
|
||||
deltaValue = lastValue - firstValue;
|
||||
}
|
||||
if (mInterpolator != null) {
|
||||
fraction = mInterpolator.getInterpolation(fraction);
|
||||
}
|
||||
if (mEvaluator == null) {
|
||||
return firstValue + (int)(fraction * deltaValue);
|
||||
} else {
|
||||
return ((Number)mEvaluator.evaluate(fraction, firstValue, lastValue)).intValue();
|
||||
}
|
||||
}
|
||||
if (fraction <= 0f) {
|
||||
final IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0);
|
||||
final IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(1);
|
||||
int prevValue = prevKeyframe.getIntValue();
|
||||
int nextValue = nextKeyframe.getIntValue();
|
||||
float prevFraction = prevKeyframe.getFraction();
|
||||
float nextFraction = nextKeyframe.getFraction();
|
||||
final Interpolator interpolator = nextKeyframe.getInterpolator();
|
||||
if (interpolator != null) {
|
||||
fraction = interpolator.getInterpolation(fraction);
|
||||
}
|
||||
float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
|
||||
return mEvaluator == null ? prevValue + (int)(intervalFraction * (nextValue - prevValue)) : ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).intValue();
|
||||
} else if (fraction >= 1f) {
|
||||
final IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(mNumKeyframes - 2);
|
||||
final IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(mNumKeyframes - 1);
|
||||
int prevValue = prevKeyframe.getIntValue();
|
||||
int nextValue = nextKeyframe.getIntValue();
|
||||
float prevFraction = prevKeyframe.getFraction();
|
||||
float nextFraction = nextKeyframe.getFraction();
|
||||
final Interpolator interpolator = nextKeyframe.getInterpolator();
|
||||
if (interpolator != null) {
|
||||
fraction = interpolator.getInterpolation(fraction);
|
||||
}
|
||||
float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
|
||||
return mEvaluator == null ? prevValue + (int)(intervalFraction * (nextValue - prevValue)) : ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).intValue();
|
||||
}
|
||||
IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0);
|
||||
for (int i = 1; i < mNumKeyframes; ++i) {
|
||||
IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(i);
|
||||
if (fraction < nextKeyframe.getFraction()) {
|
||||
final Interpolator interpolator = nextKeyframe.getInterpolator();
|
||||
if (interpolator != null) {
|
||||
fraction = interpolator.getInterpolation(fraction);
|
||||
}
|
||||
float intervalFraction = (fraction - prevKeyframe.getFraction()) / (nextKeyframe.getFraction() - prevKeyframe.getFraction());
|
||||
int prevValue = prevKeyframe.getIntValue();
|
||||
int nextValue = nextKeyframe.getIntValue();
|
||||
return mEvaluator == null ? prevValue + (int)(intervalFraction * (nextValue - prevValue)) : ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).intValue();
|
||||
}
|
||||
prevKeyframe = nextKeyframe;
|
||||
}
|
||||
return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).intValue();
|
||||
}
|
||||
}
|
||||
|
@ -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.android.Animation;
|
||||
|
||||
public abstract class IntProperty<T> extends Property<T, Integer> {
|
||||
|
||||
public IntProperty(String name) {
|
||||
super(Integer.class, name);
|
||||
}
|
||||
|
||||
public abstract void setValue(T object, int value);
|
||||
|
||||
@Override
|
||||
final public void set(T object, Integer value) {
|
||||
setValue(object, value.intValue());
|
||||
}
|
||||
}
|
@ -1,187 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
public abstract class Keyframe implements Cloneable {
|
||||
|
||||
float mFraction;
|
||||
Class mValueType;
|
||||
private Interpolator mInterpolator = null;
|
||||
boolean mHasValue = false;
|
||||
|
||||
public static Keyframe ofInt(float fraction, int value) {
|
||||
return new IntKeyframe(fraction, value);
|
||||
}
|
||||
|
||||
public static Keyframe ofInt(float fraction) {
|
||||
return new IntKeyframe(fraction);
|
||||
}
|
||||
|
||||
public static Keyframe ofFloat(float fraction, float value) {
|
||||
return new FloatKeyframe(fraction, value);
|
||||
}
|
||||
|
||||
public static Keyframe ofFloat(float fraction) {
|
||||
return new FloatKeyframe(fraction);
|
||||
}
|
||||
|
||||
public static Keyframe ofObject(float fraction, Object value) {
|
||||
return new ObjectKeyframe(fraction, value);
|
||||
}
|
||||
|
||||
public static Keyframe ofObject(float fraction) {
|
||||
return new ObjectKeyframe(fraction, null);
|
||||
}
|
||||
|
||||
public boolean hasValue() {
|
||||
return mHasValue;
|
||||
}
|
||||
|
||||
public abstract Object getValue();
|
||||
public abstract void setValue(Object value);
|
||||
|
||||
public float getFraction() {
|
||||
return mFraction;
|
||||
}
|
||||
|
||||
public void setFraction(float fraction) {
|
||||
mFraction = fraction;
|
||||
}
|
||||
|
||||
public Interpolator getInterpolator() {
|
||||
return mInterpolator;
|
||||
}
|
||||
|
||||
public void setInterpolator(Interpolator interpolator) {
|
||||
mInterpolator = interpolator;
|
||||
}
|
||||
|
||||
public Class getType() {
|
||||
return mValueType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract Keyframe clone();
|
||||
|
||||
static class ObjectKeyframe extends Keyframe {
|
||||
|
||||
Object mValue;
|
||||
|
||||
ObjectKeyframe(float fraction, Object value) {
|
||||
mFraction = fraction;
|
||||
mValue = value;
|
||||
mHasValue = (value != null);
|
||||
mValueType = mHasValue ? value.getClass() : Object.class;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
public void setValue(Object value) {
|
||||
mValue = value;
|
||||
mHasValue = (value != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectKeyframe clone() {
|
||||
ObjectKeyframe kfClone = new ObjectKeyframe(getFraction(), mHasValue ? mValue : null);
|
||||
kfClone.setInterpolator(getInterpolator());
|
||||
return kfClone;
|
||||
}
|
||||
}
|
||||
|
||||
static class IntKeyframe extends Keyframe {
|
||||
|
||||
int mValue;
|
||||
|
||||
IntKeyframe(float fraction, int value) {
|
||||
mFraction = fraction;
|
||||
mValue = value;
|
||||
mValueType = int.class;
|
||||
mHasValue = true;
|
||||
}
|
||||
|
||||
IntKeyframe(float fraction) {
|
||||
mFraction = fraction;
|
||||
mValueType = int.class;
|
||||
}
|
||||
|
||||
public int getIntValue() {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
public void setValue(Object value) {
|
||||
if (value != null && value.getClass() == Integer.class) {
|
||||
mValue = (Integer) value;
|
||||
mHasValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntKeyframe clone() {
|
||||
IntKeyframe kfClone = mHasValue ? new IntKeyframe(getFraction(), mValue) : new IntKeyframe(getFraction());
|
||||
kfClone.setInterpolator(getInterpolator());
|
||||
return kfClone;
|
||||
}
|
||||
}
|
||||
|
||||
static class FloatKeyframe extends Keyframe {
|
||||
|
||||
float mValue;
|
||||
|
||||
FloatKeyframe(float fraction, float value) {
|
||||
mFraction = fraction;
|
||||
mValue = value;
|
||||
mValueType = float.class;
|
||||
mHasValue = true;
|
||||
}
|
||||
|
||||
FloatKeyframe(float fraction) {
|
||||
mFraction = fraction;
|
||||
mValueType = float.class;
|
||||
}
|
||||
|
||||
public float getFloatValue() {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
public void setValue(Object value) {
|
||||
if (value != null && value.getClass() == Float.class) {
|
||||
mValue = (Float) value;
|
||||
mHasValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public FloatKeyframe clone() {
|
||||
FloatKeyframe kfClone = mHasValue ? new FloatKeyframe(getFraction(), mValue) : new FloatKeyframe(getFraction());
|
||||
kfClone.setInterpolator(getInterpolator());
|
||||
return kfClone;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,200 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import android.util.Log;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import org.telegram.android.Animation.Keyframe.IntKeyframe;
|
||||
import org.telegram.android.Animation.Keyframe.FloatKeyframe;
|
||||
import org.telegram.android.Animation.Keyframe.ObjectKeyframe;
|
||||
|
||||
class KeyframeSet {
|
||||
|
||||
int mNumKeyframes;
|
||||
|
||||
Keyframe mFirstKeyframe;
|
||||
Keyframe mLastKeyframe;
|
||||
Interpolator mInterpolator;
|
||||
ArrayList<Keyframe> mKeyframes;
|
||||
TypeEvaluator mEvaluator;
|
||||
|
||||
public KeyframeSet(Keyframe... keyframes) {
|
||||
mNumKeyframes = keyframes.length;
|
||||
mKeyframes = new ArrayList<Keyframe>();
|
||||
mKeyframes.addAll(Arrays.asList(keyframes));
|
||||
mFirstKeyframe = mKeyframes.get(0);
|
||||
mLastKeyframe = mKeyframes.get(mNumKeyframes - 1);
|
||||
mInterpolator = mLastKeyframe.getInterpolator();
|
||||
}
|
||||
|
||||
public static KeyframeSet ofInt(int... values) {
|
||||
int numKeyframes = values.length;
|
||||
IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)];
|
||||
if (numKeyframes == 1) {
|
||||
keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f);
|
||||
keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]);
|
||||
} else {
|
||||
keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);
|
||||
for (int i = 1; i < numKeyframes; ++i) {
|
||||
keyframes[i] = (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
|
||||
}
|
||||
}
|
||||
return new IntKeyframeSet(keyframes);
|
||||
}
|
||||
|
||||
public static KeyframeSet ofFloat(float... values) {
|
||||
boolean badValue = false;
|
||||
int numKeyframes = values.length;
|
||||
FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
|
||||
if (numKeyframes == 1) {
|
||||
keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
|
||||
keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
|
||||
if (Float.isNaN(values[0])) {
|
||||
badValue = true;
|
||||
}
|
||||
} else {
|
||||
keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
|
||||
for (int i = 1; i < numKeyframes; ++i) {
|
||||
keyframes[i] = (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
|
||||
if (Float.isNaN(values[i])) {
|
||||
badValue = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (badValue) {
|
||||
Log.w("Animator", "Bad value (NaN) in float animator");
|
||||
}
|
||||
return new FloatKeyframeSet(keyframes);
|
||||
}
|
||||
|
||||
public static KeyframeSet ofKeyframe(Keyframe... keyframes) {
|
||||
int numKeyframes = keyframes.length;
|
||||
boolean hasFloat = false;
|
||||
boolean hasInt = false;
|
||||
boolean hasOther = false;
|
||||
for (Keyframe keyframe : keyframes) {
|
||||
if (keyframe instanceof FloatKeyframe) {
|
||||
hasFloat = true;
|
||||
} else if (keyframe instanceof IntKeyframe) {
|
||||
hasInt = true;
|
||||
} else {
|
||||
hasOther = true;
|
||||
}
|
||||
}
|
||||
if (hasFloat && !hasInt && !hasOther) {
|
||||
FloatKeyframe floatKeyframes[] = new FloatKeyframe[numKeyframes];
|
||||
for (int i = 0; i < numKeyframes; ++i) {
|
||||
floatKeyframes[i] = (FloatKeyframe) keyframes[i];
|
||||
}
|
||||
return new FloatKeyframeSet(floatKeyframes);
|
||||
} else if (hasInt && !hasFloat && !hasOther) {
|
||||
IntKeyframe intKeyframes[] = new IntKeyframe[numKeyframes];
|
||||
for (int i = 0; i < numKeyframes; ++i) {
|
||||
intKeyframes[i] = (IntKeyframe) keyframes[i];
|
||||
}
|
||||
return new IntKeyframeSet(intKeyframes);
|
||||
} else {
|
||||
return new KeyframeSet(keyframes);
|
||||
}
|
||||
}
|
||||
|
||||
public static KeyframeSet ofObject(Object... values) {
|
||||
int numKeyframes = values.length;
|
||||
ObjectKeyframe keyframes[] = new ObjectKeyframe[Math.max(numKeyframes,2)];
|
||||
if (numKeyframes == 1) {
|
||||
keyframes[0] = (ObjectKeyframe) Keyframe.ofObject(0f);
|
||||
keyframes[1] = (ObjectKeyframe) Keyframe.ofObject(1f, values[0]);
|
||||
} else {
|
||||
keyframes[0] = (ObjectKeyframe) Keyframe.ofObject(0f, values[0]);
|
||||
for (int i = 1; i < numKeyframes; ++i) {
|
||||
keyframes[i] = (ObjectKeyframe) Keyframe.ofObject((float) i / (numKeyframes - 1), values[i]);
|
||||
}
|
||||
}
|
||||
return new KeyframeSet(keyframes);
|
||||
}
|
||||
|
||||
public void setEvaluator(TypeEvaluator evaluator) {
|
||||
mEvaluator = evaluator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyframeSet clone() {
|
||||
ArrayList<Keyframe> keyframes = mKeyframes;
|
||||
int numKeyframes = mKeyframes.size();
|
||||
Keyframe[] newKeyframes = new Keyframe[numKeyframes];
|
||||
for (int i = 0; i < numKeyframes; ++i) {
|
||||
newKeyframes[i] = keyframes.get(i).clone();
|
||||
}
|
||||
return new KeyframeSet(newKeyframes);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object getValue(float fraction) {
|
||||
if (mNumKeyframes == 2) {
|
||||
if (mInterpolator != null) {
|
||||
fraction = mInterpolator.getInterpolation(fraction);
|
||||
}
|
||||
return mEvaluator.evaluate(fraction, mFirstKeyframe.getValue(), mLastKeyframe.getValue());
|
||||
}
|
||||
if (fraction <= 0f) {
|
||||
final Keyframe nextKeyframe = mKeyframes.get(1);
|
||||
final Interpolator interpolator = nextKeyframe.getInterpolator();
|
||||
if (interpolator != null) {
|
||||
fraction = interpolator.getInterpolation(fraction);
|
||||
}
|
||||
final float prevFraction = mFirstKeyframe.getFraction();
|
||||
float intervalFraction = (fraction - prevFraction) / (nextKeyframe.getFraction() - prevFraction);
|
||||
return mEvaluator.evaluate(intervalFraction, mFirstKeyframe.getValue(), nextKeyframe.getValue());
|
||||
} else if (fraction >= 1f) {
|
||||
final Keyframe prevKeyframe = mKeyframes.get(mNumKeyframes - 2);
|
||||
final Interpolator interpolator = mLastKeyframe.getInterpolator();
|
||||
if (interpolator != null) {
|
||||
fraction = interpolator.getInterpolation(fraction);
|
||||
}
|
||||
final float prevFraction = prevKeyframe.getFraction();
|
||||
float intervalFraction = (fraction - prevFraction) / (mLastKeyframe.getFraction() - prevFraction);
|
||||
return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(), mLastKeyframe.getValue());
|
||||
}
|
||||
Keyframe prevKeyframe = mFirstKeyframe;
|
||||
for (int i = 1; i < mNumKeyframes; ++i) {
|
||||
Keyframe nextKeyframe = mKeyframes.get(i);
|
||||
if (fraction < nextKeyframe.getFraction()) {
|
||||
final Interpolator interpolator = nextKeyframe.getInterpolator();
|
||||
if (interpolator != null) {
|
||||
fraction = interpolator.getInterpolation(fraction);
|
||||
}
|
||||
final float prevFraction = prevKeyframe.getFraction();
|
||||
float intervalFraction = (fraction - prevFraction) / (nextKeyframe.getFraction() - prevFraction);
|
||||
return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(), nextKeyframe.getValue());
|
||||
}
|
||||
prevKeyframe = nextKeyframe;
|
||||
}
|
||||
return mLastKeyframe.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String returnVal = " ";
|
||||
for (int i = 0; i < mNumKeyframes; ++i) {
|
||||
returnVal += mKeyframes.get(i).getValue() + " ";
|
||||
}
|
||||
return returnVal;
|
||||
}
|
||||
}
|
@ -1,24 +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.android.Animation;
|
||||
|
||||
public class NoSuchPropertyException extends RuntimeException {
|
||||
|
||||
public NoSuchPropertyException(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
}
|
@ -1,488 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public final class ObjectAnimator10 extends ValueAnimator {
|
||||
|
||||
private static final HashMap<String, Property> PROXY_PROPERTIES = new HashMap<String, Property>();
|
||||
|
||||
static {
|
||||
Property<View, Float> ALPHA = new FloatProperty10<View>("alpha") {
|
||||
@Override
|
||||
public void setValue(View object, float value) {
|
||||
View10.wrap(object).setAlpha(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(View object) {
|
||||
return View10.wrap(object).getAlpha();
|
||||
}
|
||||
};
|
||||
|
||||
Property<View, Float> PIVOT_X = new FloatProperty10<View>("pivotX") {
|
||||
@Override
|
||||
public void setValue(View object, float value) {
|
||||
View10.wrap(object).setPivotX(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(View object) {
|
||||
return View10.wrap(object).getPivotX();
|
||||
}
|
||||
};
|
||||
|
||||
Property<View, Float> PIVOT_Y = new FloatProperty10<View>("pivotY") {
|
||||
@Override
|
||||
public void setValue(View object, float value) {
|
||||
View10.wrap(object).setPivotY(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(View object) {
|
||||
return View10.wrap(object).getPivotY();
|
||||
}
|
||||
};
|
||||
|
||||
Property<View, Float> TRANSLATION_X = new FloatProperty10<View>("translationX") {
|
||||
@Override
|
||||
public void setValue(View object, float value) {
|
||||
View10.wrap(object).setTranslationX(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(View object) {
|
||||
return View10.wrap(object).getTranslationX();
|
||||
}
|
||||
};
|
||||
|
||||
Property<View, Float> TRANSLATION_Y = new FloatProperty10<View>("translationY") {
|
||||
@Override
|
||||
public void setValue(View object, float value) {
|
||||
View10.wrap(object).setTranslationY(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(View object) {
|
||||
return View10.wrap(object).getTranslationY();
|
||||
}
|
||||
};
|
||||
|
||||
Property<View, Float> ROTATION = new FloatProperty10<View>("rotation") {
|
||||
@Override
|
||||
public void setValue(View object, float value) {
|
||||
View10.wrap(object).setRotation(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(View object) {
|
||||
return View10.wrap(object).getRotation();
|
||||
}
|
||||
};
|
||||
|
||||
Property<View, Float> ROTATION_X = new FloatProperty10<View>("rotationX") {
|
||||
@Override
|
||||
public void setValue(View object, float value) {
|
||||
View10.wrap(object).setRotationX(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(View object) {
|
||||
return View10.wrap(object).getRotationX();
|
||||
}
|
||||
};
|
||||
|
||||
Property<View, Float> ROTATION_Y = new FloatProperty10<View>("rotationY") {
|
||||
@Override
|
||||
public void setValue(View object, float value) {
|
||||
View10.wrap(object).setRotationY(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(View object) {
|
||||
return View10.wrap(object).getRotationY();
|
||||
}
|
||||
};
|
||||
|
||||
Property<View, Float> SCALE_X = new FloatProperty10<View>("scaleX") {
|
||||
@Override
|
||||
public void setValue(View object, float value) {
|
||||
View10.wrap(object).setScaleX(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(View object) {
|
||||
return View10.wrap(object).getScaleX();
|
||||
}
|
||||
};
|
||||
|
||||
Property<View, Float> SCALE_Y = new FloatProperty10<View>("scaleY") {
|
||||
@Override
|
||||
public void setValue(View object, float value) {
|
||||
View10.wrap(object).setScaleY(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(View object) {
|
||||
return View10.wrap(object).getScaleY();
|
||||
}
|
||||
};
|
||||
|
||||
Property<View, Integer> SCROLL_X = new IntProperty<View>("scrollX") {
|
||||
@Override
|
||||
public void setValue(View object, int value) {
|
||||
View10.wrap(object).setScrollX(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer get(View object) {
|
||||
return View10.wrap(object).getScrollX();
|
||||
}
|
||||
};
|
||||
|
||||
Property<View, Integer> SCROLL_Y = new IntProperty<View>("scrollY") {
|
||||
@Override
|
||||
public void setValue(View object, int value) {
|
||||
View10.wrap(object).setScrollY(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer get(View object) {
|
||||
return View10.wrap(object).getScrollY();
|
||||
}
|
||||
};
|
||||
|
||||
Property<View, Float> X = new FloatProperty10<View>("x") {
|
||||
@Override
|
||||
public void setValue(View object, float value) {
|
||||
View10.wrap(object).setX(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(View object) {
|
||||
return View10.wrap(object).getX();
|
||||
}
|
||||
};
|
||||
|
||||
Property<View, Float> Y = new FloatProperty10<View>("y") {
|
||||
@Override
|
||||
public void setValue(View object, float value) {
|
||||
View10.wrap(object).setY(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(View object) {
|
||||
return View10.wrap(object).getY();
|
||||
}
|
||||
};
|
||||
|
||||
PROXY_PROPERTIES.put("alpha", ALPHA);
|
||||
PROXY_PROPERTIES.put("pivotX", PIVOT_X);
|
||||
PROXY_PROPERTIES.put("pivotY", PIVOT_Y);
|
||||
PROXY_PROPERTIES.put("translationX", TRANSLATION_X);
|
||||
PROXY_PROPERTIES.put("translationY", TRANSLATION_Y);
|
||||
PROXY_PROPERTIES.put("rotation", ROTATION);
|
||||
PROXY_PROPERTIES.put("rotationX", ROTATION_X);
|
||||
PROXY_PROPERTIES.put("rotationY", ROTATION_Y);
|
||||
PROXY_PROPERTIES.put("scaleX", SCALE_X);
|
||||
PROXY_PROPERTIES.put("scaleY", SCALE_Y);
|
||||
PROXY_PROPERTIES.put("scrollX", SCROLL_X);
|
||||
PROXY_PROPERTIES.put("scrollY", SCROLL_Y);
|
||||
PROXY_PROPERTIES.put("x", X);
|
||||
PROXY_PROPERTIES.put("y", Y);
|
||||
}
|
||||
|
||||
private Object mTarget;
|
||||
private String mPropertyName;
|
||||
private Property mProperty;
|
||||
private boolean mAutoCancel = false;
|
||||
|
||||
public void setPropertyName(String propertyName) {
|
||||
if (mValues != null) {
|
||||
PropertyValuesHolder valuesHolder = mValues[0];
|
||||
String oldName = valuesHolder.getPropertyName();
|
||||
valuesHolder.setPropertyName(propertyName);
|
||||
mValuesMap.remove(oldName);
|
||||
mValuesMap.put(propertyName, valuesHolder);
|
||||
}
|
||||
mPropertyName = propertyName;
|
||||
mInitialized = false;
|
||||
}
|
||||
|
||||
public void setProperty(Property property) {
|
||||
if (mValues != null) {
|
||||
PropertyValuesHolder valuesHolder = mValues[0];
|
||||
String oldName = valuesHolder.getPropertyName();
|
||||
valuesHolder.setProperty(property);
|
||||
mValuesMap.remove(oldName);
|
||||
mValuesMap.put(mPropertyName, valuesHolder);
|
||||
}
|
||||
if (mProperty != null) {
|
||||
mPropertyName = property.getName();
|
||||
}
|
||||
mProperty = property;
|
||||
mInitialized = false;
|
||||
}
|
||||
|
||||
public String getPropertyName() {
|
||||
String propertyName = null;
|
||||
if (mPropertyName != null) {
|
||||
propertyName = mPropertyName;
|
||||
} else if (mProperty != null) {
|
||||
propertyName = mProperty.getName();
|
||||
} else if (mValues != null && mValues.length > 0) {
|
||||
for (int i = 0; i < mValues.length; ++i) {
|
||||
if (i == 0) {
|
||||
propertyName = "";
|
||||
} else {
|
||||
propertyName += ",";
|
||||
}
|
||||
propertyName += mValues[i].getPropertyName();
|
||||
}
|
||||
}
|
||||
return propertyName;
|
||||
}
|
||||
|
||||
public ObjectAnimator10() {
|
||||
|
||||
}
|
||||
|
||||
private ObjectAnimator10(Object target, String propertyName) {
|
||||
mTarget = target;
|
||||
setPropertyName(propertyName);
|
||||
}
|
||||
|
||||
private <T> ObjectAnimator10(T target, Property<T, ?> property) {
|
||||
mTarget = target;
|
||||
setProperty(property);
|
||||
}
|
||||
|
||||
public static ObjectAnimator10 ofInt(Object target, String propertyName, int... values) {
|
||||
ObjectAnimator10 anim = new ObjectAnimator10(target, propertyName);
|
||||
anim.setIntValues(values);
|
||||
return anim;
|
||||
}
|
||||
|
||||
public static <T> ObjectAnimator10 ofInt(T target, Property<T, Integer> property, int... values) {
|
||||
ObjectAnimator10 anim = new ObjectAnimator10(target, property);
|
||||
anim.setIntValues(values);
|
||||
return anim;
|
||||
}
|
||||
|
||||
public static ObjectAnimator10 ofFloat(Object target, String propertyName, float... values) {
|
||||
ObjectAnimator10 anim = new ObjectAnimator10(target, propertyName);
|
||||
anim.setFloatValues(values);
|
||||
return anim;
|
||||
}
|
||||
|
||||
public static <T> ObjectAnimator10 ofFloat(T target, Property<T, Float> property, float... values) {
|
||||
ObjectAnimator10 anim = new ObjectAnimator10(target, property);
|
||||
anim.setFloatValues(values);
|
||||
return anim;
|
||||
}
|
||||
|
||||
public static ObjectAnimator10 ofObject(Object target, String propertyName, TypeEvaluator evaluator, Object... values) {
|
||||
ObjectAnimator10 anim = new ObjectAnimator10(target, propertyName);
|
||||
anim.setObjectValues(values);
|
||||
anim.setEvaluator(evaluator);
|
||||
return anim;
|
||||
}
|
||||
|
||||
public static <T, V> ObjectAnimator10 ofObject(T target, Property<T, V> property, TypeEvaluator<V> evaluator, V... values) {
|
||||
ObjectAnimator10 anim = new ObjectAnimator10(target, property);
|
||||
anim.setObjectValues(values);
|
||||
anim.setEvaluator(evaluator);
|
||||
return anim;
|
||||
}
|
||||
|
||||
public static ObjectAnimator10 ofPropertyValuesHolder(Object target, PropertyValuesHolder... values) {
|
||||
ObjectAnimator10 anim = new ObjectAnimator10();
|
||||
anim.mTarget = target;
|
||||
anim.setValues(values);
|
||||
return anim;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void setIntValues(int... values) {
|
||||
if (mValues == null || mValues.length == 0) {
|
||||
if (mProperty != null) {
|
||||
setValues(PropertyValuesHolder.ofInt(mProperty, values));
|
||||
} else {
|
||||
setValues(PropertyValuesHolder.ofInt(mPropertyName, values));
|
||||
}
|
||||
} else {
|
||||
super.setIntValues(values);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void setFloatValues(float... values) {
|
||||
if (mValues == null || mValues.length == 0) {
|
||||
if (mProperty != null) {
|
||||
setValues(PropertyValuesHolder.ofFloat(mProperty, values));
|
||||
} else {
|
||||
setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
|
||||
}
|
||||
} else {
|
||||
super.setFloatValues(values);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setObjectValues(Object... values) {
|
||||
if (mValues == null || mValues.length == 0) {
|
||||
if (mProperty != null) {
|
||||
setValues(PropertyValuesHolder.ofObject(mProperty, null, values));
|
||||
} else {
|
||||
setValues(PropertyValuesHolder.ofObject(mPropertyName, null, values));
|
||||
}
|
||||
} else {
|
||||
super.setObjectValues(values);
|
||||
}
|
||||
}
|
||||
|
||||
public void setAutoCancel(boolean cancel) {
|
||||
mAutoCancel = cancel;
|
||||
}
|
||||
|
||||
private boolean hasSameTargetAndProperties(Animator10 anim) {
|
||||
if (anim instanceof ObjectAnimator10) {
|
||||
PropertyValuesHolder[] theirValues = ((ObjectAnimator10) anim).getValues();
|
||||
if (((ObjectAnimator10) anim).getTarget() == mTarget &&
|
||||
mValues.length == theirValues.length) {
|
||||
for (int i = 0; i < mValues.length; ++i) {
|
||||
PropertyValuesHolder pvhMine = mValues[i];
|
||||
PropertyValuesHolder pvhTheirs = theirValues[i];
|
||||
if (pvhMine.getPropertyName() == null ||
|
||||
!pvhMine.getPropertyName().equals(pvhTheirs.getPropertyName())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
AnimationHandler handler = sAnimationHandler.get();
|
||||
if (handler != null) {
|
||||
int numAnims = handler.mAnimations.size();
|
||||
for (int i = numAnims - 1; i >= 0; i--) {
|
||||
if (handler.mAnimations.get(i) instanceof ObjectAnimator10) {
|
||||
ObjectAnimator10 anim = (ObjectAnimator10) handler.mAnimations.get(i);
|
||||
if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
|
||||
anim.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
numAnims = handler.mPendingAnimations.size();
|
||||
for (int i = numAnims - 1; i >= 0; i--) {
|
||||
if (handler.mPendingAnimations.get(i) instanceof ObjectAnimator10) {
|
||||
ObjectAnimator10 anim = (ObjectAnimator10) handler.mPendingAnimations.get(i);
|
||||
if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
|
||||
anim.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
numAnims = handler.mDelayedAnims.size();
|
||||
for (int i = numAnims - 1; i >= 0; i--) {
|
||||
if (handler.mDelayedAnims.get(i) instanceof ObjectAnimator10) {
|
||||
ObjectAnimator10 anim = (ObjectAnimator10) handler.mDelayedAnims.get(i);
|
||||
if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
|
||||
anim.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
super.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
void initAnimation() {
|
||||
if (!mInitialized) {
|
||||
if ((mProperty == null) && (mTarget instanceof View) && PROXY_PROPERTIES.containsKey(mPropertyName)) {
|
||||
setProperty(PROXY_PROPERTIES.get(mPropertyName));
|
||||
}
|
||||
int numValues = mValues.length;
|
||||
for (PropertyValuesHolder mValue : mValues) {
|
||||
mValue.setupSetterAndGetter(mTarget);
|
||||
}
|
||||
super.initAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectAnimator10 setDuration(long duration) {
|
||||
super.setDuration(duration);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object getTarget() {
|
||||
return mTarget;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTarget(Object target) {
|
||||
if (mTarget != target) {
|
||||
final Object oldTarget = mTarget;
|
||||
mTarget = target;
|
||||
if (oldTarget != null && target != null && oldTarget.getClass() == target.getClass()) {
|
||||
return;
|
||||
}
|
||||
mInitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setupStartValues() {
|
||||
initAnimation();
|
||||
int numValues = mValues.length;
|
||||
for (PropertyValuesHolder mValue : mValues) {
|
||||
mValue.setupStartValue(mTarget);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setupEndValues() {
|
||||
initAnimation();
|
||||
int numValues = mValues.length;
|
||||
for (PropertyValuesHolder mValue : mValues) {
|
||||
mValue.setupEndValue(mTarget);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void animateValue(float fraction) {
|
||||
super.animateValue(fraction);
|
||||
int numValues = mValues.length;
|
||||
for (PropertyValuesHolder mValue : mValues) {
|
||||
mValue.setAnimatedValue(mTarget);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectAnimator10 clone() {
|
||||
return (ObjectAnimator10) super.clone();
|
||||
}
|
||||
}
|
@ -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.android.Animation;
|
||||
|
||||
public abstract class Property<T, V> {
|
||||
|
||||
private final String mName;
|
||||
private final Class<V> mType;
|
||||
|
||||
public static <T, V> Property<T, V> of(Class<T> hostType, Class<V> valueType, String name) {
|
||||
return new ReflectiveProperty<T, V>(hostType, valueType, name);
|
||||
}
|
||||
|
||||
public Property(Class<V> type, String name) {
|
||||
mName = name;
|
||||
mType = type;
|
||||
}
|
||||
|
||||
public boolean isReadOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void set(T object, V value) {
|
||||
throw new UnsupportedOperationException("Property " + getName() +" is read-only");
|
||||
}
|
||||
|
||||
public abstract V get(T object);
|
||||
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public Class<V> getType() {
|
||||
return mType;
|
||||
}
|
||||
}
|
@ -1,545 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
public class PropertyValuesHolder implements Cloneable {
|
||||
|
||||
String mPropertyName;
|
||||
protected Property mProperty;
|
||||
Method mSetter = null;
|
||||
private Method mGetter = null;
|
||||
Class mValueType;
|
||||
KeyframeSet mKeyframeSet = null;
|
||||
|
||||
private static final TypeEvaluator sIntEvaluator = new IntEvaluator();
|
||||
private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator();
|
||||
|
||||
private static Class[] FLOAT_VARIANTS = {float.class, Float.class, double.class, int.class, Double.class, Integer.class};
|
||||
private static Class[] INTEGER_VARIANTS = {int.class, Integer.class, float.class, double.class, Float.class, Double.class};
|
||||
private static Class[] DOUBLE_VARIANTS = {double.class, Double.class, float.class, int.class, Float.class, Integer.class};
|
||||
|
||||
private static final HashMap<Class, HashMap<String, Method>> sSetterPropertyMap = new HashMap<Class, HashMap<String, Method>>();
|
||||
private static final HashMap<Class, HashMap<String, Method>> sGetterPropertyMap = new HashMap<Class, HashMap<String, Method>>();
|
||||
|
||||
final ReentrantReadWriteLock mPropertyMapLock = new ReentrantReadWriteLock();
|
||||
final Object[] mTmpValueArray = new Object[1];
|
||||
|
||||
private TypeEvaluator mEvaluator;
|
||||
|
||||
private Object mAnimatedValue;
|
||||
|
||||
private PropertyValuesHolder(String propertyName) {
|
||||
mPropertyName = propertyName;
|
||||
}
|
||||
|
||||
private PropertyValuesHolder(Property property) {
|
||||
mProperty = property;
|
||||
if (property != null) {
|
||||
mPropertyName = property.getName();
|
||||
}
|
||||
}
|
||||
|
||||
public static PropertyValuesHolder ofInt(String propertyName, int... values) {
|
||||
return new IntPropertyValuesHolder(propertyName, values);
|
||||
}
|
||||
|
||||
public static PropertyValuesHolder ofInt(Property<?, Integer> property, int... values) {
|
||||
return new IntPropertyValuesHolder(property, values);
|
||||
}
|
||||
|
||||
public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
|
||||
return new FloatPropertyValuesHolder(propertyName, values);
|
||||
}
|
||||
|
||||
public static PropertyValuesHolder ofFloat(Property<?, Float> property, float... values) {
|
||||
return new FloatPropertyValuesHolder(property, values);
|
||||
}
|
||||
|
||||
public static PropertyValuesHolder ofObject(String propertyName, TypeEvaluator evaluator,
|
||||
Object... values) {
|
||||
PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName);
|
||||
pvh.setObjectValues(values);
|
||||
pvh.setEvaluator(evaluator);
|
||||
return pvh;
|
||||
}
|
||||
|
||||
public static <V> PropertyValuesHolder ofObject(Property property,
|
||||
TypeEvaluator<V> evaluator, V... values) {
|
||||
PropertyValuesHolder pvh = new PropertyValuesHolder(property);
|
||||
pvh.setObjectValues(values);
|
||||
pvh.setEvaluator(evaluator);
|
||||
return pvh;
|
||||
}
|
||||
|
||||
public static PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values) {
|
||||
KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
|
||||
if (keyframeSet instanceof IntKeyframeSet) {
|
||||
return new IntPropertyValuesHolder(propertyName, (IntKeyframeSet) keyframeSet);
|
||||
} else if (keyframeSet instanceof FloatKeyframeSet) {
|
||||
return new FloatPropertyValuesHolder(propertyName, (FloatKeyframeSet) keyframeSet);
|
||||
} else {
|
||||
PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName);
|
||||
pvh.mKeyframeSet = keyframeSet;
|
||||
pvh.mValueType = values[0].getType();
|
||||
return pvh;
|
||||
}
|
||||
}
|
||||
|
||||
public static PropertyValuesHolder ofKeyframe(Property property, Keyframe... values) {
|
||||
KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
|
||||
if (keyframeSet instanceof IntKeyframeSet) {
|
||||
return new IntPropertyValuesHolder(property, (IntKeyframeSet) keyframeSet);
|
||||
} else if (keyframeSet instanceof FloatKeyframeSet) {
|
||||
return new FloatPropertyValuesHolder(property, (FloatKeyframeSet) keyframeSet);
|
||||
} else {
|
||||
PropertyValuesHolder pvh = new PropertyValuesHolder(property);
|
||||
pvh.mKeyframeSet = keyframeSet;
|
||||
pvh.mValueType = values[0].getType();
|
||||
return pvh;
|
||||
}
|
||||
}
|
||||
|
||||
public void setIntValues(int... values) {
|
||||
mValueType = int.class;
|
||||
mKeyframeSet = KeyframeSet.ofInt(values);
|
||||
}
|
||||
|
||||
public void setFloatValues(float... values) {
|
||||
mValueType = float.class;
|
||||
mKeyframeSet = KeyframeSet.ofFloat(values);
|
||||
}
|
||||
|
||||
public void setKeyframes(Keyframe... values) {
|
||||
int numKeyframes = values.length;
|
||||
Keyframe keyframes[] = new Keyframe[Math.max(numKeyframes, 2)];
|
||||
mValueType = values[0].getType();
|
||||
System.arraycopy(values, 0, keyframes, 0, numKeyframes);
|
||||
mKeyframeSet = new KeyframeSet(keyframes);
|
||||
}
|
||||
|
||||
public void setObjectValues(Object... values) {
|
||||
mValueType = values[0].getClass();
|
||||
mKeyframeSet = KeyframeSet.ofObject(values);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Method getPropertyFunction(Class targetClass, String prefix, Class valueType) {
|
||||
Method returnVal = null;
|
||||
String methodName = getMethodName(prefix, mPropertyName);
|
||||
Class args[] = null;
|
||||
if (valueType == null) {
|
||||
try {
|
||||
returnVal = targetClass.getMethod(methodName);
|
||||
} catch (Throwable e) {
|
||||
try {
|
||||
returnVal = targetClass.getDeclaredMethod(methodName);
|
||||
returnVal.setAccessible(true);
|
||||
} catch (Throwable e2) {
|
||||
e2.printStackTrace();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
args = new Class[1];
|
||||
Class typeVariants[];
|
||||
if (mValueType.equals(Float.class)) {
|
||||
typeVariants = FLOAT_VARIANTS;
|
||||
} else if (mValueType.equals(Integer.class)) {
|
||||
typeVariants = INTEGER_VARIANTS;
|
||||
} else if (mValueType.equals(Double.class)) {
|
||||
typeVariants = DOUBLE_VARIANTS;
|
||||
} else {
|
||||
typeVariants = new Class[1];
|
||||
typeVariants[0] = mValueType;
|
||||
}
|
||||
for (Class typeVariant : typeVariants) {
|
||||
args[0] = typeVariant;
|
||||
try {
|
||||
returnVal = targetClass.getMethod(methodName, args);
|
||||
mValueType = typeVariant;
|
||||
return returnVal;
|
||||
} catch (Throwable e) {
|
||||
try {
|
||||
returnVal = targetClass.getDeclaredMethod(methodName, args);
|
||||
returnVal.setAccessible(true);
|
||||
mValueType = typeVariant;
|
||||
return returnVal;
|
||||
} catch (Throwable e2) {
|
||||
// Swallow the error and keep trying other variants
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
private Method setupSetterOrGetter(Class targetClass, HashMap<Class, HashMap<String, Method>> propertyMapMap, String prefix, Class valueType) {
|
||||
Method setterOrGetter = null;
|
||||
try {
|
||||
mPropertyMapLock.writeLock().lock();
|
||||
HashMap<String, Method> propertyMap = propertyMapMap.get(targetClass);
|
||||
if (propertyMap != null) {
|
||||
setterOrGetter = propertyMap.get(mPropertyName);
|
||||
}
|
||||
if (setterOrGetter == null) {
|
||||
setterOrGetter = getPropertyFunction(targetClass, prefix, valueType);
|
||||
if (propertyMap == null) {
|
||||
propertyMap = new HashMap<String, Method>();
|
||||
propertyMapMap.put(targetClass, propertyMap);
|
||||
}
|
||||
propertyMap.put(mPropertyName, setterOrGetter);
|
||||
}
|
||||
} finally {
|
||||
mPropertyMapLock.writeLock().unlock();
|
||||
}
|
||||
return setterOrGetter;
|
||||
}
|
||||
|
||||
void setupSetter(Class targetClass) {
|
||||
mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", mValueType);
|
||||
}
|
||||
|
||||
private void setupGetter(Class targetClass) {
|
||||
mGetter = setupSetterOrGetter(targetClass, sGetterPropertyMap, "get", null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
void setupSetterAndGetter(Object target) {
|
||||
if (mProperty != null) {
|
||||
try {
|
||||
Object testValue = mProperty.get(target);
|
||||
for (Keyframe kf : mKeyframeSet.mKeyframes) {
|
||||
if (!kf.hasValue()) {
|
||||
kf.setValue(mProperty.get(target));
|
||||
}
|
||||
}
|
||||
return;
|
||||
} catch (Throwable e) {
|
||||
mProperty = null;
|
||||
}
|
||||
}
|
||||
Class targetClass = target.getClass();
|
||||
if (mSetter == null) {
|
||||
setupSetter(targetClass);
|
||||
}
|
||||
for (Keyframe kf : mKeyframeSet.mKeyframes) {
|
||||
if (!kf.hasValue()) {
|
||||
if (mGetter == null) {
|
||||
setupGetter(targetClass);
|
||||
if (mGetter == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
kf.setValue(mGetter.invoke(target));
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void setupValue(Object target, Keyframe kf) {
|
||||
if (mProperty != null) {
|
||||
kf.setValue(mProperty.get(target));
|
||||
}
|
||||
try {
|
||||
if (mGetter == null) {
|
||||
Class targetClass = target.getClass();
|
||||
setupGetter(targetClass);
|
||||
if (mGetter == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
kf.setValue(mGetter.invoke(target));
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
void setupStartValue(Object target) {
|
||||
setupValue(target, mKeyframeSet.mKeyframes.get(0));
|
||||
}
|
||||
|
||||
void setupEndValue(Object target) {
|
||||
setupValue(target, mKeyframeSet.mKeyframes.get(mKeyframeSet.mKeyframes.size() - 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PropertyValuesHolder clone() {
|
||||
try {
|
||||
PropertyValuesHolder newPVH = (PropertyValuesHolder) super.clone();
|
||||
newPVH.mPropertyName = mPropertyName;
|
||||
newPVH.mProperty = mProperty;
|
||||
newPVH.mKeyframeSet = mKeyframeSet.clone();
|
||||
newPVH.mEvaluator = mEvaluator;
|
||||
return newPVH;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
void setAnimatedValue(Object target) {
|
||||
if (mProperty != null) {
|
||||
mProperty.set(target, getAnimatedValue());
|
||||
}
|
||||
if (mSetter != null) {
|
||||
try {
|
||||
mTmpValueArray[0] = getAnimatedValue();
|
||||
mSetter.invoke(target, mTmpValueArray);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void init() {
|
||||
if (mEvaluator == null) {
|
||||
mEvaluator = (mValueType == Integer.class) ? sIntEvaluator : (mValueType == Float.class) ? sFloatEvaluator : null;
|
||||
}
|
||||
if (mEvaluator != null) {
|
||||
mKeyframeSet.setEvaluator(mEvaluator);
|
||||
}
|
||||
}
|
||||
|
||||
public void setEvaluator(TypeEvaluator evaluator) {
|
||||
mEvaluator = evaluator;
|
||||
mKeyframeSet.setEvaluator(evaluator);
|
||||
}
|
||||
|
||||
void calculateValue(float fraction) {
|
||||
mAnimatedValue = mKeyframeSet.getValue(fraction);
|
||||
}
|
||||
|
||||
public void setPropertyName(String propertyName) {
|
||||
mPropertyName = propertyName;
|
||||
}
|
||||
|
||||
public void setProperty(Property property) {
|
||||
mProperty = property;
|
||||
}
|
||||
|
||||
public String getPropertyName() {
|
||||
return mPropertyName;
|
||||
}
|
||||
|
||||
Object getAnimatedValue() {
|
||||
return mAnimatedValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mPropertyName + ": " + mKeyframeSet.toString();
|
||||
}
|
||||
|
||||
static String getMethodName(String prefix, String propertyName) {
|
||||
if (propertyName == null || propertyName.length() == 0) {
|
||||
return prefix;
|
||||
}
|
||||
char firstLetter = Character.toUpperCase(propertyName.charAt(0));
|
||||
String theRest = propertyName.substring(1);
|
||||
return prefix + firstLetter + theRest;
|
||||
}
|
||||
|
||||
static class IntPropertyValuesHolder extends PropertyValuesHolder {
|
||||
private static final HashMap<Class, HashMap<String, Integer>> sJNISetterPropertyMap = new HashMap<Class, HashMap<String, Integer>>();
|
||||
private IntProperty mIntProperty;
|
||||
|
||||
IntKeyframeSet mIntKeyframeSet;
|
||||
int mIntAnimatedValue;
|
||||
|
||||
public IntPropertyValuesHolder(String propertyName, IntKeyframeSet keyframeSet) {
|
||||
super(propertyName);
|
||||
mValueType = int.class;
|
||||
mKeyframeSet = keyframeSet;
|
||||
mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet;
|
||||
}
|
||||
|
||||
public IntPropertyValuesHolder(Property property, IntKeyframeSet keyframeSet) {
|
||||
super(property);
|
||||
mValueType = int.class;
|
||||
mKeyframeSet = keyframeSet;
|
||||
mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet;
|
||||
if (property instanceof IntProperty) {
|
||||
mIntProperty = (IntProperty) mProperty;
|
||||
}
|
||||
}
|
||||
|
||||
public IntPropertyValuesHolder(String propertyName, int... values) {
|
||||
super(propertyName);
|
||||
setIntValues(values);
|
||||
}
|
||||
|
||||
public IntPropertyValuesHolder(Property property, int... values) {
|
||||
super(property);
|
||||
setIntValues(values);
|
||||
if (property instanceof IntProperty) {
|
||||
mIntProperty = (IntProperty) mProperty;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIntValues(int... values) {
|
||||
super.setIntValues(values);
|
||||
mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
void calculateValue(float fraction) {
|
||||
mIntAnimatedValue = mIntKeyframeSet.getIntValue(fraction);
|
||||
}
|
||||
|
||||
@Override
|
||||
Object getAnimatedValue() {
|
||||
return mIntAnimatedValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntPropertyValuesHolder clone() {
|
||||
IntPropertyValuesHolder newPVH = (IntPropertyValuesHolder) super.clone();
|
||||
newPVH.mIntKeyframeSet = (IntKeyframeSet) newPVH.mKeyframeSet;
|
||||
return newPVH;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
void setAnimatedValue(Object target) {
|
||||
if (mIntProperty != null) {
|
||||
mIntProperty.setValue(target, mIntAnimatedValue);
|
||||
return;
|
||||
}
|
||||
if (mProperty != null) {
|
||||
mProperty.set(target, mIntAnimatedValue);
|
||||
return;
|
||||
}
|
||||
if (mSetter != null) {
|
||||
try {
|
||||
mTmpValueArray[0] = mIntAnimatedValue;
|
||||
mSetter.invoke(target, mTmpValueArray);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void setupSetter(Class targetClass) {
|
||||
if (mProperty != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
super.setupSetter(targetClass);
|
||||
}
|
||||
}
|
||||
|
||||
static class FloatPropertyValuesHolder extends PropertyValuesHolder {
|
||||
|
||||
private static final HashMap<Class, HashMap<String, Integer>> sJNISetterPropertyMap = new HashMap<Class, HashMap<String, Integer>>();
|
||||
private FloatProperty10 mFloatProperty;
|
||||
|
||||
FloatKeyframeSet mFloatKeyframeSet;
|
||||
float mFloatAnimatedValue;
|
||||
|
||||
public FloatPropertyValuesHolder(String propertyName, FloatKeyframeSet keyframeSet) {
|
||||
super(propertyName);
|
||||
mValueType = float.class;
|
||||
mKeyframeSet = keyframeSet;
|
||||
mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet;
|
||||
}
|
||||
|
||||
public FloatPropertyValuesHolder(Property property, FloatKeyframeSet keyframeSet) {
|
||||
super(property);
|
||||
mValueType = float.class;
|
||||
mKeyframeSet = keyframeSet;
|
||||
mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet;
|
||||
if (property instanceof FloatProperty10) {
|
||||
mFloatProperty = (FloatProperty10) mProperty;
|
||||
}
|
||||
}
|
||||
|
||||
public FloatPropertyValuesHolder(String propertyName, float... values) {
|
||||
super(propertyName);
|
||||
setFloatValues(values);
|
||||
}
|
||||
|
||||
public FloatPropertyValuesHolder(Property property, float... values) {
|
||||
super(property);
|
||||
setFloatValues(values);
|
||||
if (property instanceof FloatProperty10) {
|
||||
mFloatProperty = (FloatProperty10) mProperty;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFloatValues(float... values) {
|
||||
super.setFloatValues(values);
|
||||
mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
void calculateValue(float fraction) {
|
||||
mFloatAnimatedValue = mFloatKeyframeSet.getFloatValue(fraction);
|
||||
}
|
||||
|
||||
@Override
|
||||
Object getAnimatedValue() {
|
||||
return mFloatAnimatedValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FloatPropertyValuesHolder clone() {
|
||||
FloatPropertyValuesHolder newPVH = (FloatPropertyValuesHolder) super.clone();
|
||||
newPVH.mFloatKeyframeSet = (FloatKeyframeSet) newPVH.mKeyframeSet;
|
||||
return newPVH;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
void setAnimatedValue(Object target) {
|
||||
if (mFloatProperty != null) {
|
||||
mFloatProperty.setValue(target, mFloatAnimatedValue);
|
||||
return;
|
||||
}
|
||||
if (mProperty != null) {
|
||||
mProperty.set(target, mFloatAnimatedValue);
|
||||
return;
|
||||
}
|
||||
if (mSetter != null) {
|
||||
try {
|
||||
mTmpValueArray[0] = mFloatAnimatedValue;
|
||||
mSetter.invoke(target, mTmpValueArray);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void setupSetter(Class targetClass) {
|
||||
if (mProperty != null) {
|
||||
return;
|
||||
}
|
||||
super.setupSetter(targetClass);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,182 +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.android.Animation;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Internal class to automatically generate a Property for a given class/name pair, given the
|
||||
* specification of {@link Property#of(java.lang.Class, java.lang.Class, java.lang.String)}
|
||||
*/
|
||||
class ReflectiveProperty<T, V> extends Property<T, V> {
|
||||
|
||||
private static final String PREFIX_GET = "get";
|
||||
private static final String PREFIX_IS = "is";
|
||||
private static final String PREFIX_SET = "set";
|
||||
private Method mSetter;
|
||||
private Method mGetter;
|
||||
private Field mField;
|
||||
|
||||
/**
|
||||
* For given property name 'name', look for getName/isName method or 'name' field.
|
||||
* Also look for setName method (optional - could be readonly). Failing method getters and
|
||||
* field results in throwing NoSuchPropertyException.
|
||||
*
|
||||
* @param propertyHolder The class on which the methods or field are found
|
||||
* @param name The name of the property, where this name is capitalized and appended to
|
||||
* "get" and "is to search for the appropriate methods. If the get/is methods are not found,
|
||||
* the constructor will search for a field with that exact name.
|
||||
*/
|
||||
public ReflectiveProperty(Class<T> propertyHolder, Class<V> valueType, String name) {
|
||||
// TODO: cache reflection info for each new class/name pair
|
||||
super(valueType, name);
|
||||
char firstLetter = Character.toUpperCase(name.charAt(0));
|
||||
String theRest = name.substring(1);
|
||||
String capitalizedName = firstLetter + theRest;
|
||||
String getterName = PREFIX_GET + capitalizedName;
|
||||
try {
|
||||
mGetter = propertyHolder.getMethod(getterName, (Class<?>[]) null);
|
||||
} catch (NoSuchMethodException e) {
|
||||
try {
|
||||
/* The native implementation uses JNI to do reflection, which allows access to private methods.
|
||||
* getDeclaredMethod(..) does not find superclass methods, so it's implemented as a fallback.
|
||||
*/
|
||||
mGetter = propertyHolder.getDeclaredMethod(getterName, (Class<?>[]) null);
|
||||
mGetter.setAccessible(true);
|
||||
} catch (NoSuchMethodException e2) {
|
||||
// getName() not available - try isName() instead
|
||||
getterName = PREFIX_IS + capitalizedName;
|
||||
try {
|
||||
mGetter = propertyHolder.getMethod(getterName, (Class<?>[]) null);
|
||||
} catch (NoSuchMethodException e3) {
|
||||
try {
|
||||
/* The native implementation uses JNI to do reflection, which allows access to private methods.
|
||||
* getDeclaredMethod(..) does not find superclass methods, so it's implemented as a fallback.
|
||||
*/
|
||||
mGetter = propertyHolder.getDeclaredMethod(getterName, (Class<?>[]) null);
|
||||
mGetter.setAccessible(true);
|
||||
} catch (NoSuchMethodException e4) {
|
||||
// Try public field instead
|
||||
try {
|
||||
mField = propertyHolder.getField(name);
|
||||
Class fieldType = mField.getType();
|
||||
if (!typesMatch(valueType, fieldType)) {
|
||||
throw new NoSuchPropertyException("Underlying type (" + fieldType + ") " +
|
||||
"does not match Property type (" + valueType + ")");
|
||||
}
|
||||
return;
|
||||
} catch (NoSuchFieldException e5) {
|
||||
// no way to access property - throw appropriate exception
|
||||
throw new NoSuchPropertyException("No accessor method or field found for"
|
||||
+ " property with name " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Class getterType = mGetter.getReturnType();
|
||||
// Check to make sure our getter type matches our valueType
|
||||
if (!typesMatch(valueType, getterType)) {
|
||||
throw new NoSuchPropertyException("Underlying type (" + getterType + ") " +
|
||||
"does not match Property type (" + valueType + ")");
|
||||
}
|
||||
String setterName = PREFIX_SET + capitalizedName;
|
||||
try {
|
||||
// mSetter = propertyHolder.getMethod(setterName, getterType);
|
||||
// The native implementation uses JNI to do reflection, which allows access to private methods.
|
||||
mSetter = propertyHolder.getDeclaredMethod(setterName, getterType);
|
||||
mSetter.setAccessible(true);
|
||||
} catch (NoSuchMethodException ignored) {
|
||||
// Okay to not have a setter - just a readonly property
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to check whether the type of the underlying field/method on the target
|
||||
* object matches the type of the Property. The extra checks for primitive types are because
|
||||
* generics will force the Property type to be a class, whereas the type of the underlying
|
||||
* method/field will probably be a primitive type instead. Accept float as matching Float,
|
||||
* etc.
|
||||
*/
|
||||
private boolean typesMatch(Class<V> valueType, Class getterType) {
|
||||
if (getterType != valueType) {
|
||||
if (getterType.isPrimitive()) {
|
||||
return (getterType == float.class && valueType == Float.class) ||
|
||||
(getterType == int.class && valueType == Integer.class) ||
|
||||
(getterType == boolean.class && valueType == Boolean.class) ||
|
||||
(getterType == long.class && valueType == Long.class) ||
|
||||
(getterType == double.class && valueType == Double.class) ||
|
||||
(getterType == short.class && valueType == Short.class) ||
|
||||
(getterType == byte.class && valueType == Byte.class) ||
|
||||
(getterType == char.class && valueType == Character.class);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(T object, V value) {
|
||||
if (mSetter != null) {
|
||||
try {
|
||||
mSetter.invoke(object, value);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new AssertionError();
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException(e.getCause());
|
||||
}
|
||||
} else if (mField != null) {
|
||||
try {
|
||||
mField.set(object, value);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Property " + getName() +" is read-only");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(T object) {
|
||||
if (mGetter != null) {
|
||||
try {
|
||||
return (V) mGetter.invoke(object, (Object[])null);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new AssertionError();
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException(e.getCause());
|
||||
}
|
||||
} else if (mField != null) {
|
||||
try {
|
||||
return (V) mField.get(object);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
// Should not get here: there should always be a non-null getter or field
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false if there is no setter or public field underlying this Property.
|
||||
*/
|
||||
@Override
|
||||
public boolean isReadOnly() {
|
||||
return (mSetter == null && mField == null);
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
public interface TypeEvaluator<T> {
|
||||
T evaluate(float fraction, T startValue, T endValue);
|
||||
}
|
@ -1,675 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
import android.os.Looper;
|
||||
import android.util.AndroidRuntimeException;
|
||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
|
||||
import org.telegram.android.AndroidUtilities;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class ValueAnimator extends Animator10 {
|
||||
|
||||
private static float sDurationScale = 1.0f;
|
||||
static final int STOPPED = 0;
|
||||
static final int RUNNING = 1;
|
||||
static final int SEEKED = 2;
|
||||
|
||||
long mStartTime;
|
||||
long mSeekTime = -1;
|
||||
private long mPauseTime;
|
||||
private boolean mResumed = false;
|
||||
protected static ThreadLocal<AnimationHandler> sAnimationHandler = new ThreadLocal<AnimationHandler>();
|
||||
private static final Interpolator sDefaultInterpolator = new AccelerateDecelerateInterpolator();
|
||||
private boolean mPlayingBackwards = false;
|
||||
private int mCurrentIteration = 0;
|
||||
private float mCurrentFraction = 0f;
|
||||
private boolean mStartedDelay = false;
|
||||
private long mDelayStartTime;
|
||||
int mPlayingState = STOPPED;
|
||||
private boolean mRunning = false;
|
||||
private boolean mStarted = false;
|
||||
private boolean mStartListenersCalled = false;
|
||||
boolean mInitialized = false;
|
||||
|
||||
private long mDuration = (long)(300 * sDurationScale);
|
||||
private long mUnscaledDuration = 300;
|
||||
private long mStartDelay = 0;
|
||||
private long mUnscaledStartDelay = 0;
|
||||
private int mRepeatCount = 0;
|
||||
private int mRepeatMode = RESTART;
|
||||
private Interpolator mInterpolator = sDefaultInterpolator;
|
||||
private ArrayList<AnimatorUpdateListener> mUpdateListeners = null;
|
||||
PropertyValuesHolder[] mValues;
|
||||
HashMap<String, PropertyValuesHolder> mValuesMap;
|
||||
|
||||
public static final int RESTART = 1;
|
||||
public static final int REVERSE = 2;
|
||||
public static final int INFINITE = -1;
|
||||
|
||||
public static void setDurationScale(float durationScale) {
|
||||
sDurationScale = durationScale;
|
||||
}
|
||||
|
||||
public static float getDurationScale() {
|
||||
return sDurationScale;
|
||||
}
|
||||
|
||||
public ValueAnimator() {
|
||||
|
||||
}
|
||||
|
||||
public static ValueAnimator ofInt(int... values) {
|
||||
ValueAnimator anim = new ValueAnimator();
|
||||
anim.setIntValues(values);
|
||||
return anim;
|
||||
}
|
||||
|
||||
public static ValueAnimator ofFloat(float... values) {
|
||||
ValueAnimator anim = new ValueAnimator();
|
||||
anim.setFloatValues(values);
|
||||
return anim;
|
||||
}
|
||||
|
||||
public static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values) {
|
||||
ValueAnimator anim = new ValueAnimator();
|
||||
anim.setValues(values);
|
||||
return anim;
|
||||
}
|
||||
|
||||
public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values) {
|
||||
ValueAnimator anim = new ValueAnimator();
|
||||
anim.setObjectValues(values);
|
||||
anim.setEvaluator(evaluator);
|
||||
return anim;
|
||||
}
|
||||
|
||||
public void setIntValues(int... values) {
|
||||
if (values == null || values.length == 0) {
|
||||
return;
|
||||
}
|
||||
if (mValues == null || mValues.length == 0) {
|
||||
setValues(PropertyValuesHolder.ofInt("", values));
|
||||
} else {
|
||||
PropertyValuesHolder valuesHolder = mValues[0];
|
||||
valuesHolder.setIntValues(values);
|
||||
}
|
||||
mInitialized = false;
|
||||
}
|
||||
|
||||
public void setFloatValues(float... values) {
|
||||
if (values == null || values.length == 0) {
|
||||
return;
|
||||
}
|
||||
if (mValues == null || mValues.length == 0) {
|
||||
setValues(PropertyValuesHolder.ofFloat("", values));
|
||||
} else {
|
||||
PropertyValuesHolder valuesHolder = mValues[0];
|
||||
valuesHolder.setFloatValues(values);
|
||||
}
|
||||
mInitialized = false;
|
||||
}
|
||||
|
||||
public void setObjectValues(Object... values) {
|
||||
if (values == null || values.length == 0) {
|
||||
return;
|
||||
}
|
||||
if (mValues == null || mValues.length == 0) {
|
||||
setValues(PropertyValuesHolder.ofObject("", null, values));
|
||||
} else {
|
||||
PropertyValuesHolder valuesHolder = mValues[0];
|
||||
valuesHolder.setObjectValues(values);
|
||||
}
|
||||
mInitialized = false;
|
||||
}
|
||||
|
||||
public void setValues(PropertyValuesHolder... values) {
|
||||
int numValues = values.length;
|
||||
mValues = values;
|
||||
mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
|
||||
for (PropertyValuesHolder valuesHolder : values) {
|
||||
mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
|
||||
}
|
||||
mInitialized = false;
|
||||
}
|
||||
|
||||
public PropertyValuesHolder[] getValues() {
|
||||
return mValues;
|
||||
}
|
||||
|
||||
void initAnimation() {
|
||||
if (!mInitialized) {
|
||||
int numValues = mValues.length;
|
||||
for (PropertyValuesHolder mValue : mValues) {
|
||||
mValue.init();
|
||||
}
|
||||
mInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
public ValueAnimator setDuration(long duration) {
|
||||
if (duration < 0) {
|
||||
throw new IllegalArgumentException("Animators cannot have negative duration: " + duration);
|
||||
}
|
||||
mUnscaledDuration = duration;
|
||||
mDuration = (long)(duration * sDurationScale);
|
||||
return this;
|
||||
}
|
||||
|
||||
public long getDuration() {
|
||||
return mUnscaledDuration;
|
||||
}
|
||||
|
||||
public void setCurrentPlayTime(long playTime) {
|
||||
initAnimation();
|
||||
long currentTime = AnimationUtils.currentAnimationTimeMillis();
|
||||
if (mPlayingState != RUNNING) {
|
||||
mSeekTime = playTime;
|
||||
mPlayingState = SEEKED;
|
||||
}
|
||||
mStartTime = currentTime - playTime;
|
||||
doAnimationFrame(currentTime);
|
||||
}
|
||||
|
||||
public long getCurrentPlayTime() {
|
||||
if (!mInitialized || mPlayingState == STOPPED) {
|
||||
return 0;
|
||||
}
|
||||
return AnimationUtils.currentAnimationTimeMillis() - mStartTime;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected static class AnimationHandler implements Runnable {
|
||||
|
||||
protected final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>();
|
||||
private final ArrayList<ValueAnimator> mTmpAnimations = new ArrayList<ValueAnimator>();
|
||||
protected final ArrayList<ValueAnimator> mPendingAnimations = new ArrayList<ValueAnimator>();
|
||||
protected final ArrayList<ValueAnimator> mDelayedAnims = new ArrayList<ValueAnimator>();
|
||||
private final ArrayList<ValueAnimator> mEndingAnims = new ArrayList<ValueAnimator>();
|
||||
private final ArrayList<ValueAnimator> mReadyAnims = new ArrayList<ValueAnimator>();
|
||||
|
||||
private boolean mAnimationScheduled;
|
||||
|
||||
public void start() {
|
||||
scheduleAnimation();
|
||||
}
|
||||
|
||||
private void doAnimationFrame(long frameTime) {
|
||||
while (mPendingAnimations.size() > 0) {
|
||||
ArrayList<ValueAnimator> pendingCopy = (ArrayList<ValueAnimator>) mPendingAnimations.clone();
|
||||
mPendingAnimations.clear();
|
||||
int count = pendingCopy.size();
|
||||
for (ValueAnimator anim : pendingCopy) {
|
||||
if (anim.mStartDelay == 0) {
|
||||
anim.startAnimation(this);
|
||||
} else {
|
||||
mDelayedAnims.add(anim);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int numDelayedAnims = mDelayedAnims.size();
|
||||
for (ValueAnimator anim : mDelayedAnims) {
|
||||
if (anim.delayedAnimationFrame(frameTime)) {
|
||||
mReadyAnims.add(anim);
|
||||
}
|
||||
}
|
||||
int numReadyAnims = mReadyAnims.size();
|
||||
if (numReadyAnims > 0) {
|
||||
for (ValueAnimator anim : mReadyAnims) {
|
||||
anim.startAnimation(this);
|
||||
anim.mRunning = true;
|
||||
mDelayedAnims.remove(anim);
|
||||
}
|
||||
mReadyAnims.clear();
|
||||
}
|
||||
|
||||
int numAnims = mAnimations.size();
|
||||
for (ValueAnimator mAnimation : mAnimations) {
|
||||
mTmpAnimations.add(mAnimation);
|
||||
}
|
||||
for (int i = 0; i < numAnims; ++i) {
|
||||
ValueAnimator anim = mTmpAnimations.get(i);
|
||||
if (mAnimations.contains(anim) && anim.doAnimationFrame(frameTime)) {
|
||||
mEndingAnims.add(anim);
|
||||
}
|
||||
}
|
||||
mTmpAnimations.clear();
|
||||
if (mEndingAnims.size() > 0) {
|
||||
for (ValueAnimator mEndingAnim : mEndingAnims) {
|
||||
mEndingAnim.endAnimation(this);
|
||||
}
|
||||
mEndingAnims.clear();
|
||||
}
|
||||
|
||||
if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) {
|
||||
scheduleAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mAnimationScheduled = false;
|
||||
doAnimationFrame(System.nanoTime() / 1000000);
|
||||
}
|
||||
|
||||
private void scheduleAnimation() {
|
||||
if (!mAnimationScheduled) {
|
||||
AndroidUtilities.runOnUIThread(this);
|
||||
mAnimationScheduled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public long getStartDelay() {
|
||||
return mUnscaledStartDelay;
|
||||
}
|
||||
|
||||
public void setStartDelay(long startDelay) {
|
||||
this.mStartDelay = (long)(startDelay * sDurationScale);
|
||||
mUnscaledStartDelay = startDelay;
|
||||
}
|
||||
|
||||
public Object getAnimatedValue() {
|
||||
if (mValues != null && mValues.length > 0) {
|
||||
return mValues[0].getAnimatedValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Object getAnimatedValue(String propertyName) {
|
||||
PropertyValuesHolder valuesHolder = mValuesMap.get(propertyName);
|
||||
if (valuesHolder != null) {
|
||||
return valuesHolder.getAnimatedValue();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setRepeatCount(int value) {
|
||||
mRepeatCount = value;
|
||||
}
|
||||
|
||||
public int getRepeatCount() {
|
||||
return mRepeatCount;
|
||||
}
|
||||
|
||||
public void setRepeatMode(int value) {
|
||||
mRepeatMode = value;
|
||||
}
|
||||
|
||||
public int getRepeatMode() {
|
||||
return mRepeatMode;
|
||||
}
|
||||
|
||||
public void addUpdateListener(AnimatorUpdateListener listener) {
|
||||
if (mUpdateListeners == null) {
|
||||
mUpdateListeners = new ArrayList<AnimatorUpdateListener>();
|
||||
}
|
||||
mUpdateListeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeAllUpdateListeners() {
|
||||
if (mUpdateListeners == null) {
|
||||
return;
|
||||
}
|
||||
mUpdateListeners.clear();
|
||||
mUpdateListeners = null;
|
||||
}
|
||||
|
||||
public void removeUpdateListener(AnimatorUpdateListener listener) {
|
||||
if (mUpdateListeners == null) {
|
||||
return;
|
||||
}
|
||||
mUpdateListeners.remove(listener);
|
||||
if (mUpdateListeners.size() == 0) {
|
||||
mUpdateListeners = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInterpolator(Interpolator value) {
|
||||
if (value != null) {
|
||||
mInterpolator = value;
|
||||
} else {
|
||||
mInterpolator = new LinearInterpolator();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Interpolator getInterpolator() {
|
||||
return mInterpolator;
|
||||
}
|
||||
|
||||
public void setEvaluator(TypeEvaluator value) {
|
||||
if (value != null && mValues != null && mValues.length > 0) {
|
||||
mValues[0].setEvaluator(value);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void notifyStartListeners() {
|
||||
if (mListeners != null && !mStartListenersCalled) {
|
||||
ArrayList<AnimatorListener> tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone();
|
||||
int numListeners = tmpListeners.size();
|
||||
for (AnimatorListener tmpListener : tmpListeners) {
|
||||
tmpListener.onAnimationStart(this);
|
||||
}
|
||||
}
|
||||
mStartListenersCalled = true;
|
||||
}
|
||||
|
||||
private void start(boolean playBackwards) {
|
||||
if (Looper.myLooper() == null) {
|
||||
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
|
||||
}
|
||||
mPlayingBackwards = playBackwards;
|
||||
mCurrentIteration = 0;
|
||||
mPlayingState = STOPPED;
|
||||
mStarted = true;
|
||||
mStartedDelay = false;
|
||||
mPaused = false;
|
||||
AnimationHandler animationHandler = getOrCreateAnimationHandler();
|
||||
animationHandler.mPendingAnimations.add(this);
|
||||
if (mStartDelay == 0) {
|
||||
setCurrentPlayTime(0);
|
||||
mPlayingState = STOPPED;
|
||||
mRunning = true;
|
||||
notifyStartListeners();
|
||||
}
|
||||
animationHandler.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
start(false);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void cancel() {
|
||||
AnimationHandler handler = getOrCreateAnimationHandler();
|
||||
if (mPlayingState != STOPPED || handler.mPendingAnimations.contains(this) || handler.mDelayedAnims.contains(this)) {
|
||||
if ((mStarted || mRunning) && mListeners != null) {
|
||||
if (!mRunning) {
|
||||
notifyStartListeners();
|
||||
}
|
||||
ArrayList<AnimatorListener> tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone();
|
||||
for (AnimatorListener listener : tmpListeners) {
|
||||
listener.onAnimationCancel(this);
|
||||
}
|
||||
}
|
||||
endAnimation(handler);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
AnimationHandler handler = getOrCreateAnimationHandler();
|
||||
if (!handler.mAnimations.contains(this) && !handler.mPendingAnimations.contains(this)) {
|
||||
mStartedDelay = false;
|
||||
startAnimation(handler);
|
||||
mStarted = true;
|
||||
} else if (!mInitialized) {
|
||||
initAnimation();
|
||||
}
|
||||
animateValue(mPlayingBackwards ? 0f : 1f);
|
||||
endAnimation(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume() {
|
||||
if (mPaused) {
|
||||
mResumed = true;
|
||||
}
|
||||
super.resume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pause() {
|
||||
boolean previouslyPaused = mPaused;
|
||||
super.pause();
|
||||
if (!previouslyPaused && mPaused) {
|
||||
mPauseTime = -1;
|
||||
mResumed = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return (mPlayingState == RUNNING || mRunning);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStarted() {
|
||||
return mStarted;
|
||||
}
|
||||
|
||||
public void reverse() {
|
||||
mPlayingBackwards = !mPlayingBackwards;
|
||||
if (mPlayingState == RUNNING) {
|
||||
long currentTime = AnimationUtils.currentAnimationTimeMillis();
|
||||
long currentPlayTime = currentTime - mStartTime;
|
||||
long timeLeft = mDuration - currentPlayTime;
|
||||
mStartTime = currentTime - timeLeft;
|
||||
} else if (mStarted) {
|
||||
end();
|
||||
} else {
|
||||
start(true);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void endAnimation(AnimationHandler handler) {
|
||||
handler.mAnimations.remove(this);
|
||||
handler.mPendingAnimations.remove(this);
|
||||
handler.mDelayedAnims.remove(this);
|
||||
mPlayingState = STOPPED;
|
||||
mPaused = false;
|
||||
if ((mStarted || mRunning) && mListeners != null) {
|
||||
if (!mRunning) {
|
||||
notifyStartListeners();
|
||||
}
|
||||
ArrayList<AnimatorListener> tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone();
|
||||
int numListeners = tmpListeners.size();
|
||||
for (AnimatorListener tmpListener : tmpListeners) {
|
||||
tmpListener.onAnimationEnd(this);
|
||||
}
|
||||
}
|
||||
mRunning = false;
|
||||
mStarted = false;
|
||||
mStartListenersCalled = false;
|
||||
mPlayingBackwards = false;
|
||||
}
|
||||
|
||||
private void startAnimation(AnimationHandler handler) {
|
||||
initAnimation();
|
||||
handler.mAnimations.add(this);
|
||||
if (mStartDelay > 0 && mListeners != null) {
|
||||
notifyStartListeners();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean delayedAnimationFrame(long currentTime) {
|
||||
if (!mStartedDelay) {
|
||||
mStartedDelay = true;
|
||||
mDelayStartTime = currentTime;
|
||||
} else {
|
||||
if (mPaused) {
|
||||
if (mPauseTime < 0) {
|
||||
mPauseTime = currentTime;
|
||||
}
|
||||
return false;
|
||||
} else if (mResumed) {
|
||||
mResumed = false;
|
||||
if (mPauseTime > 0) {
|
||||
mDelayStartTime += (currentTime - mPauseTime);
|
||||
}
|
||||
}
|
||||
long deltaTime = currentTime - mDelayStartTime;
|
||||
if (deltaTime > mStartDelay) {
|
||||
mStartTime = currentTime - (deltaTime - mStartDelay);
|
||||
mPlayingState = RUNNING;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean animationFrame(long currentTime) {
|
||||
boolean done = false;
|
||||
switch (mPlayingState) {
|
||||
case RUNNING:
|
||||
case SEEKED:
|
||||
float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;
|
||||
if (fraction >= 1f) {
|
||||
if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) {
|
||||
if (mListeners != null) {
|
||||
int numListeners = mListeners.size();
|
||||
for (AnimatorListener mListener : mListeners) {
|
||||
mListener.onAnimationRepeat(this);
|
||||
}
|
||||
}
|
||||
if (mRepeatMode == REVERSE) {
|
||||
mPlayingBackwards = !mPlayingBackwards;
|
||||
}
|
||||
mCurrentIteration += (int)fraction;
|
||||
fraction = fraction % 1f;
|
||||
mStartTime += mDuration;
|
||||
} else {
|
||||
done = true;
|
||||
fraction = Math.min(fraction, 1.0f);
|
||||
}
|
||||
}
|
||||
if (mPlayingBackwards) {
|
||||
fraction = 1f - fraction;
|
||||
}
|
||||
animateValue(fraction);
|
||||
break;
|
||||
}
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
final boolean doAnimationFrame(long frameTime) {
|
||||
if (mPlayingState == STOPPED) {
|
||||
mPlayingState = RUNNING;
|
||||
if (mSeekTime < 0) {
|
||||
mStartTime = frameTime;
|
||||
} else {
|
||||
mStartTime = frameTime - mSeekTime;
|
||||
mSeekTime = -1;
|
||||
}
|
||||
}
|
||||
if (mPaused) {
|
||||
if (mPauseTime < 0) {
|
||||
mPauseTime = frameTime;
|
||||
}
|
||||
return false;
|
||||
} else if (mResumed) {
|
||||
mResumed = false;
|
||||
if (mPauseTime > 0) {
|
||||
mStartTime += (frameTime - mPauseTime);
|
||||
}
|
||||
}
|
||||
final long currentTime = Math.max(frameTime, mStartTime);
|
||||
return animationFrame(currentTime);
|
||||
}
|
||||
|
||||
public float getAnimatedFraction() {
|
||||
return mCurrentFraction;
|
||||
}
|
||||
|
||||
void animateValue(float fraction) {
|
||||
fraction = mInterpolator.getInterpolation(fraction);
|
||||
mCurrentFraction = fraction;
|
||||
int numValues = mValues.length;
|
||||
for (PropertyValuesHolder mValue : mValues) {
|
||||
mValue.calculateValue(fraction);
|
||||
}
|
||||
if (mUpdateListeners != null) {
|
||||
int numListeners = mUpdateListeners.size();
|
||||
for (AnimatorUpdateListener mUpdateListener : mUpdateListeners) {
|
||||
mUpdateListener.onAnimationUpdate(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueAnimator clone() {
|
||||
final ValueAnimator anim = (ValueAnimator) super.clone();
|
||||
if (mUpdateListeners != null) {
|
||||
ArrayList<AnimatorUpdateListener> oldListeners = mUpdateListeners;
|
||||
anim.mUpdateListeners = new ArrayList<AnimatorUpdateListener>();
|
||||
int numListeners = oldListeners.size();
|
||||
for (AnimatorUpdateListener oldListener : oldListeners) {
|
||||
anim.mUpdateListeners.add(oldListener);
|
||||
}
|
||||
}
|
||||
anim.mSeekTime = -1;
|
||||
anim.mPlayingBackwards = false;
|
||||
anim.mCurrentIteration = 0;
|
||||
anim.mInitialized = false;
|
||||
anim.mPlayingState = STOPPED;
|
||||
anim.mStartedDelay = false;
|
||||
PropertyValuesHolder[] oldValues = mValues;
|
||||
if (oldValues != null) {
|
||||
int numValues = oldValues.length;
|
||||
anim.mValues = new PropertyValuesHolder[numValues];
|
||||
anim.mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
|
||||
for (int i = 0; i < numValues; ++i) {
|
||||
PropertyValuesHolder newValuesHolder = oldValues[i].clone();
|
||||
anim.mValues[i] = newValuesHolder;
|
||||
anim.mValuesMap.put(newValuesHolder.getPropertyName(), newValuesHolder);
|
||||
}
|
||||
}
|
||||
return anim;
|
||||
}
|
||||
|
||||
public interface AnimatorUpdateListener {
|
||||
void onAnimationUpdate(ValueAnimator animation);
|
||||
}
|
||||
|
||||
public static int getCurrentAnimationsCount() {
|
||||
AnimationHandler handler = sAnimationHandler.get();
|
||||
return handler != null ? handler.mAnimations.size() : 0;
|
||||
}
|
||||
|
||||
public static void clearAllAnimations() {
|
||||
AnimationHandler handler = sAnimationHandler.get();
|
||||
if (handler != null) {
|
||||
handler.mAnimations.clear();
|
||||
handler.mPendingAnimations.clear();
|
||||
handler.mDelayedAnims.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static AnimationHandler getOrCreateAnimationHandler() {
|
||||
AnimationHandler handler = sAnimationHandler.get();
|
||||
if (handler == null) {
|
||||
handler = new AnimationHandler();
|
||||
sAnimationHandler.set(handler);
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
}
|
@ -1,349 +0,0 @@
|
||||
/*
|
||||
Copyright 2012 Jake Wharton
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
import android.graphics.Camera;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.RectF;
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.Transformation;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
public class View10 extends Animation {
|
||||
|
||||
public static boolean NEED_PROXY = Build.VERSION.SDK_INT < 11;
|
||||
|
||||
private static final WeakHashMap<View, View10> PROXIES = new WeakHashMap<>();
|
||||
|
||||
public static View10 wrap(View view) {
|
||||
View10 proxy = PROXIES.get(view);
|
||||
Animation animation = view.getAnimation();
|
||||
if (proxy == null || proxy != animation && animation != null) {
|
||||
proxy = new View10(view);
|
||||
PROXIES.put(view, proxy);
|
||||
} else if (animation == null) {
|
||||
view.setAnimation(proxy);
|
||||
}
|
||||
return proxy;
|
||||
}
|
||||
|
||||
private final WeakReference<View> mView;
|
||||
private final Camera mCamera = new Camera();
|
||||
private boolean mHasPivot;
|
||||
|
||||
private float mAlpha = 1;
|
||||
private float mPivotX;
|
||||
private float mPivotY;
|
||||
private float mRotationX;
|
||||
private float mRotationY;
|
||||
private float mRotationZ;
|
||||
private float mScaleX = 1;
|
||||
private float mScaleY = 1;
|
||||
private float mTranslationX;
|
||||
private float mTranslationY;
|
||||
|
||||
private final RectF mBefore = new RectF();
|
||||
private final RectF mAfter = new RectF();
|
||||
private final Matrix mTempMatrix = new Matrix();
|
||||
|
||||
private View10(View view) {
|
||||
setDuration(0);
|
||||
setFillAfter(true);
|
||||
view.setAnimation(this);
|
||||
mView = new WeakReference<>(view);
|
||||
}
|
||||
|
||||
public float getAlpha() {
|
||||
return mAlpha;
|
||||
}
|
||||
|
||||
public void setAlpha(float alpha) {
|
||||
if (mAlpha != alpha) {
|
||||
mAlpha = alpha;
|
||||
View view = mView.get();
|
||||
if (view != null) {
|
||||
view.invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float getPivotX() {
|
||||
return mPivotX;
|
||||
}
|
||||
|
||||
public void setPivotX(float pivotX) {
|
||||
if (!mHasPivot || mPivotX != pivotX) {
|
||||
prepareForUpdate();
|
||||
mHasPivot = true;
|
||||
mPivotX = pivotX;
|
||||
invalidateAfterUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public float getPivotY() {
|
||||
return mPivotY;
|
||||
}
|
||||
|
||||
public void setPivotY(float pivotY) {
|
||||
if (!mHasPivot || mPivotY != pivotY) {
|
||||
prepareForUpdate();
|
||||
mHasPivot = true;
|
||||
mPivotY = pivotY;
|
||||
invalidateAfterUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public float getRotation() {
|
||||
return mRotationZ;
|
||||
}
|
||||
|
||||
public void setRotation(float rotation) {
|
||||
if (mRotationZ != rotation) {
|
||||
prepareForUpdate();
|
||||
mRotationZ = rotation;
|
||||
invalidateAfterUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public float getRotationX() {
|
||||
return mRotationX;
|
||||
}
|
||||
|
||||
public void setRotationX(float rotationX) {
|
||||
if (mRotationX != rotationX) {
|
||||
prepareForUpdate();
|
||||
mRotationX = rotationX;
|
||||
invalidateAfterUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public float getRotationY() {
|
||||
return mRotationY;
|
||||
}
|
||||
|
||||
public void setRotationY(float rotationY) {
|
||||
if (mRotationY != rotationY) {
|
||||
prepareForUpdate();
|
||||
mRotationY = rotationY;
|
||||
invalidateAfterUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public float getScaleX() {
|
||||
return mScaleX;
|
||||
}
|
||||
|
||||
public void setScaleX(float scaleX) {
|
||||
if (mScaleX != scaleX) {
|
||||
prepareForUpdate();
|
||||
mScaleX = scaleX;
|
||||
invalidateAfterUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public float getScaleY() {
|
||||
return mScaleY;
|
||||
}
|
||||
|
||||
public void setScaleY(float scaleY) {
|
||||
if (mScaleY != scaleY) {
|
||||
prepareForUpdate();
|
||||
mScaleY = scaleY;
|
||||
invalidateAfterUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public int getScrollX() {
|
||||
View view = mView.get();
|
||||
if (view == null) {
|
||||
return 0;
|
||||
}
|
||||
return view.getScrollX();
|
||||
}
|
||||
|
||||
public void setScrollX(int value) {
|
||||
View view = mView.get();
|
||||
if (view != null) {
|
||||
view.scrollTo(value, view.getScrollY());
|
||||
}
|
||||
}
|
||||
|
||||
public int getScrollY() {
|
||||
View view = mView.get();
|
||||
if (view == null) {
|
||||
return 0;
|
||||
}
|
||||
return view.getScrollY();
|
||||
}
|
||||
|
||||
public void setScrollY(int value) {
|
||||
View view = mView.get();
|
||||
if (view != null) {
|
||||
view.scrollTo(view.getScrollX(), value);
|
||||
}
|
||||
}
|
||||
|
||||
public float getTranslationX() {
|
||||
return mTranslationX;
|
||||
}
|
||||
|
||||
public void setTranslationX(float translationX) {
|
||||
if (mTranslationX != translationX) {
|
||||
prepareForUpdate();
|
||||
mTranslationX = translationX;
|
||||
invalidateAfterUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public float getTranslationY() {
|
||||
return mTranslationY;
|
||||
}
|
||||
|
||||
public void setTranslationY(float translationY) {
|
||||
if (mTranslationY != translationY) {
|
||||
prepareForUpdate();
|
||||
mTranslationY = translationY;
|
||||
invalidateAfterUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public float getX() {
|
||||
View view = mView.get();
|
||||
if (view == null) {
|
||||
return 0;
|
||||
}
|
||||
return view.getLeft() + mTranslationX;
|
||||
}
|
||||
|
||||
public void setX(float x) {
|
||||
View view = mView.get();
|
||||
if (view != null) {
|
||||
setTranslationX(x - view.getLeft());
|
||||
}
|
||||
}
|
||||
|
||||
public float getY() {
|
||||
View view = mView.get();
|
||||
if (view == null) {
|
||||
return 0;
|
||||
}
|
||||
return view.getTop() + mTranslationY;
|
||||
}
|
||||
|
||||
public void setY(float y) {
|
||||
View view = mView.get();
|
||||
if (view != null) {
|
||||
setTranslationY(y - view.getTop());
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareForUpdate() {
|
||||
View view = mView.get();
|
||||
if (view != null) {
|
||||
computeRect(mBefore, view);
|
||||
}
|
||||
}
|
||||
|
||||
private void invalidateAfterUpdate() {
|
||||
View view = mView.get();
|
||||
if (view == null || view.getParent() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final RectF after = mAfter;
|
||||
computeRect(after, view);
|
||||
after.union(mBefore);
|
||||
|
||||
((View) view.getParent()).invalidate(
|
||||
(int) Math.floor(after.left),
|
||||
(int) Math.floor(after.top),
|
||||
(int) Math.ceil(after.right),
|
||||
(int) Math.ceil(after.bottom));
|
||||
}
|
||||
|
||||
private void computeRect(final RectF r, View view) {
|
||||
final float w = view.getWidth();
|
||||
final float h = view.getHeight();
|
||||
|
||||
r.set(0, 0, w, h);
|
||||
|
||||
final Matrix m = mTempMatrix;
|
||||
m.reset();
|
||||
transformMatrix(m, view);
|
||||
mTempMatrix.mapRect(r);
|
||||
|
||||
r.offset(view.getLeft(), view.getTop());
|
||||
|
||||
if (r.right < r.left) {
|
||||
final float f = r.right;
|
||||
r.right = r.left;
|
||||
r.left = f;
|
||||
}
|
||||
if (r.bottom < r.top) {
|
||||
final float f = r.top;
|
||||
r.top = r.bottom;
|
||||
r.bottom = f;
|
||||
}
|
||||
}
|
||||
|
||||
private void transformMatrix(Matrix m, View view) {
|
||||
final float w = view.getWidth();
|
||||
final float h = view.getHeight();
|
||||
final boolean hasPivot = mHasPivot;
|
||||
final float pX = hasPivot ? mPivotX : w / 2f;
|
||||
final float pY = hasPivot ? mPivotY : h / 2f;
|
||||
|
||||
final float rX = mRotationX;
|
||||
final float rY = mRotationY;
|
||||
final float rZ = mRotationZ;
|
||||
if ((rX != 0) || (rY != 0) || (rZ != 0)) {
|
||||
final Camera camera = mCamera;
|
||||
camera.save();
|
||||
camera.rotateX(rX);
|
||||
camera.rotateY(rY);
|
||||
camera.rotateZ(-rZ);
|
||||
camera.getMatrix(m);
|
||||
camera.restore();
|
||||
m.preTranslate(-pX, -pY);
|
||||
m.postTranslate(pX, pY);
|
||||
}
|
||||
|
||||
final float sX = mScaleX;
|
||||
final float sY = mScaleY;
|
||||
if ((sX != 1.0f) || (sY != 1.0f)) {
|
||||
m.postScale(sX, sY);
|
||||
final float sPX = -(pX / w) * ((sX * w) - w);
|
||||
final float sPY = -(pY / h) * ((sY * h) - h);
|
||||
m.postTranslate(sPX, sPY);
|
||||
}
|
||||
|
||||
m.postTranslate(mTranslationX, mTranslationY);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyTransformation(float interpolatedTime, Transformation t) {
|
||||
View view = mView.get();
|
||||
if (view != null) {
|
||||
t.setAlpha(mAlpha);
|
||||
transformMatrix(t.getMatrix(), view);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,112 +0,0 @@
|
||||
/*
|
||||
* This is the source code of Telegram for Android v. 1.7.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-2014.
|
||||
*/
|
||||
|
||||
package org.telegram.android.AnimationCompat;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
|
||||
import org.telegram.android.Animation.Animator10;
|
||||
import org.telegram.android.Animation.AnimatorListenerAdapter10;
|
||||
import org.telegram.android.Animation.View10;
|
||||
|
||||
public class AnimatorListenerAdapterProxy {
|
||||
protected Object animatorListenerAdapter;
|
||||
|
||||
public AnimatorListenerAdapterProxy() {
|
||||
if (View10.NEED_PROXY) {
|
||||
animatorListenerAdapter = new AnimatorListenerAdapter10() {
|
||||
@Override
|
||||
public void onAnimationCancel(Animator10 animation) {
|
||||
AnimatorListenerAdapterProxy.this.onAnimationCancel(animation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator10 animation) {
|
||||
AnimatorListenerAdapterProxy.this.onAnimationEnd(animation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animator10 animation) {
|
||||
AnimatorListenerAdapterProxy.this.onAnimationRepeat(animation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(Animator10 animation) {
|
||||
AnimatorListenerAdapterProxy.this.onAnimationStart(animation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationPause(Animator10 animation) {
|
||||
AnimatorListenerAdapterProxy.this.onAnimationPause(animation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationResume(Animator10 animation) {
|
||||
AnimatorListenerAdapterProxy.this.onAnimationResume(animation);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
animatorListenerAdapter = new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
AnimatorListenerAdapterProxy.this.onAnimationCancel(animation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
AnimatorListenerAdapterProxy.this.onAnimationEnd(animation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animator animation) {
|
||||
AnimatorListenerAdapterProxy.this.onAnimationRepeat(animation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
AnimatorListenerAdapterProxy.this.onAnimationStart(animation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationPause(Animator animation) {
|
||||
AnimatorListenerAdapterProxy.this.onAnimationPause(animation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationResume(Animator animation) {
|
||||
AnimatorListenerAdapterProxy.this.onAnimationResume(animation);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public void onAnimationCancel(Object animation) {
|
||||
|
||||
}
|
||||
|
||||
public void onAnimationEnd(Object animation) {
|
||||
|
||||
}
|
||||
|
||||
public void onAnimationRepeat(Object animation) {
|
||||
|
||||
}
|
||||
|
||||
public void onAnimationStart(Object animation) {
|
||||
|
||||
}
|
||||
|
||||
public void onAnimationPause(Object animation) {
|
||||
|
||||
}
|
||||
|
||||
public void onAnimationResume(Object animation) {
|
||||
|
||||
}
|
||||
}
|
@ -1,137 +0,0 @@
|
||||
/*
|
||||
* This is the source code of Telegram for Android v. 1.7.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-2014.
|
||||
*/
|
||||
|
||||
package org.telegram.android.AnimationCompat;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import org.telegram.android.Animation.Animator10;
|
||||
import org.telegram.android.Animation.AnimatorListenerAdapter10;
|
||||
import org.telegram.android.Animation.AnimatorSet10;
|
||||
import org.telegram.android.Animation.View10;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class AnimatorSetProxy {
|
||||
|
||||
private Object animatorSet;
|
||||
|
||||
public static <T, U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
|
||||
return copyOfRange(original, 0, newLength, newType);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T, U> T[] copyOfRange(U[] original, int start, int end, Class<? extends T[]> newType) {
|
||||
if (start > end) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
int originalLength = original.length;
|
||||
if (start < 0 || start > originalLength) {
|
||||
throw new ArrayIndexOutOfBoundsException();
|
||||
}
|
||||
int resultLength = end - start;
|
||||
int copyLength = Math.min(resultLength, originalLength - start);
|
||||
T[] result = (T[]) Array.newInstance(newType.getComponentType(), resultLength);
|
||||
System.arraycopy(original, start, result, 0, copyLength);
|
||||
return result;
|
||||
}
|
||||
|
||||
public AnimatorSetProxy() {
|
||||
if (View10.NEED_PROXY) {
|
||||
animatorSet = new AnimatorSet10();
|
||||
} else {
|
||||
animatorSet = new AnimatorSet();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void playTogether(Object... items) {
|
||||
if (View10.NEED_PROXY) {
|
||||
Animator10[] animators = copyOf(items, items.length, Animator10[].class);
|
||||
((AnimatorSet10) animatorSet).playTogether(animators);
|
||||
} else {
|
||||
Animator[] animators = copyOf(items, items.length, Animator[].class);
|
||||
((AnimatorSet) animatorSet).playTogether(animators);
|
||||
}
|
||||
}
|
||||
|
||||
public void playTogether(ArrayList<Object> items) {
|
||||
if (View10.NEED_PROXY) {
|
||||
ArrayList<Animator10> animators = new ArrayList<>();
|
||||
for (Object obj : items) {
|
||||
animators.add((Animator10)obj);
|
||||
}
|
||||
((AnimatorSet10) animatorSet).playTogether(animators);
|
||||
} else {
|
||||
ArrayList<Animator> animators = new ArrayList<>();
|
||||
for (Object obj : items) {
|
||||
animators.add((Animator)obj);
|
||||
}
|
||||
((AnimatorSet) animatorSet).playTogether(animators);
|
||||
}
|
||||
}
|
||||
|
||||
public AnimatorSetProxy setDuration(long duration) {
|
||||
if (View10.NEED_PROXY) {
|
||||
((AnimatorSet10) animatorSet).setDuration(duration);
|
||||
} else {
|
||||
((AnimatorSet) animatorSet).setDuration(duration);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public AnimatorSetProxy setStartDelay(long delay) {
|
||||
if (View10.NEED_PROXY) {
|
||||
((AnimatorSet10) animatorSet).setStartDelay(delay);
|
||||
} else {
|
||||
((AnimatorSet) animatorSet).setStartDelay(delay);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (View10.NEED_PROXY) {
|
||||
((AnimatorSet10) animatorSet).start();
|
||||
} else {
|
||||
((AnimatorSet) animatorSet).start();
|
||||
}
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
if (View10.NEED_PROXY) {
|
||||
((AnimatorSet10) animatorSet).cancel();
|
||||
} else {
|
||||
((AnimatorSet) animatorSet).cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public void addListener(AnimatorListenerAdapterProxy listener) {
|
||||
if (View10.NEED_PROXY) {
|
||||
((AnimatorSet10) animatorSet).addListener((AnimatorListenerAdapter10) listener.animatorListenerAdapter);
|
||||
} else {
|
||||
((AnimatorSet) animatorSet).addListener((AnimatorListenerAdapter) listener.animatorListenerAdapter);
|
||||
}
|
||||
}
|
||||
|
||||
public void setInterpolator(Interpolator interpolator) {
|
||||
if (View10.NEED_PROXY) {
|
||||
((AnimatorSet10) animatorSet).setInterpolator(interpolator);
|
||||
} else {
|
||||
((AnimatorSet) animatorSet).setInterpolator(interpolator);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return animatorSet == o;
|
||||
}
|
||||
}
|
@ -1,129 +0,0 @@
|
||||
/*
|
||||
* This is the source code of Telegram for Android v. 1.7.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-2014.
|
||||
*/
|
||||
|
||||
package org.telegram.android.AnimationCompat;
|
||||
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import org.telegram.android.Animation.AnimatorListenerAdapter10;
|
||||
import org.telegram.android.Animation.ObjectAnimator10;
|
||||
import org.telegram.android.Animation.View10;
|
||||
|
||||
public class ObjectAnimatorProxy {
|
||||
|
||||
private Object objectAnimator;
|
||||
|
||||
public ObjectAnimatorProxy(Object animator) {
|
||||
objectAnimator = animator;
|
||||
}
|
||||
|
||||
public static Object ofFloat(Object target, String propertyName, float... values) {
|
||||
if (View10.NEED_PROXY) {
|
||||
return ObjectAnimator10.ofFloat(target, propertyName, values);
|
||||
} else {
|
||||
return ObjectAnimator.ofFloat(target, propertyName, values);
|
||||
}
|
||||
}
|
||||
|
||||
public static Object ofInt(Object target, String propertyName, int... values) {
|
||||
if (View10.NEED_PROXY) {
|
||||
return ObjectAnimator10.ofInt(target, propertyName, values);
|
||||
} else {
|
||||
return ObjectAnimator.ofInt(target, propertyName, values);
|
||||
}
|
||||
}
|
||||
|
||||
public static ObjectAnimatorProxy ofFloatProxy(Object target, String propertyName, float... values) {
|
||||
if (View10.NEED_PROXY) {
|
||||
return new ObjectAnimatorProxy(ObjectAnimator10.ofFloat(target, propertyName, values));
|
||||
} else {
|
||||
return new ObjectAnimatorProxy(ObjectAnimator.ofFloat(target, propertyName, values));
|
||||
}
|
||||
}
|
||||
|
||||
public static ObjectAnimatorProxy ofIntProxy(Object target, String propertyName, int... values) {
|
||||
if (View10.NEED_PROXY) {
|
||||
return new ObjectAnimatorProxy(ObjectAnimator10.ofInt(target, propertyName, values));
|
||||
} else {
|
||||
return new ObjectAnimatorProxy(ObjectAnimator.ofInt(target, propertyName, values));
|
||||
}
|
||||
}
|
||||
|
||||
public ObjectAnimatorProxy setDuration(long duration) {
|
||||
if (View10.NEED_PROXY) {
|
||||
((ObjectAnimator10) objectAnimator).setDuration(duration);
|
||||
} else {
|
||||
((ObjectAnimator) objectAnimator).setDuration(duration);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setInterpolator(Interpolator value) {
|
||||
if (View10.NEED_PROXY) {
|
||||
((ObjectAnimator10) objectAnimator).setInterpolator(value);
|
||||
} else {
|
||||
((ObjectAnimator) objectAnimator).setInterpolator(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (View10.NEED_PROXY) {
|
||||
((ObjectAnimator10) objectAnimator).start();
|
||||
} else {
|
||||
((ObjectAnimator) objectAnimator).start();
|
||||
}
|
||||
}
|
||||
|
||||
public void setAutoCancel(boolean cancel) {
|
||||
if (View10.NEED_PROXY) {
|
||||
((ObjectAnimator10) objectAnimator).setAutoCancel(cancel);
|
||||
} else {
|
||||
((ObjectAnimator) objectAnimator).setAutoCancel(cancel);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
if (View10.NEED_PROXY) {
|
||||
return ((ObjectAnimator10) objectAnimator).isRunning();
|
||||
} else {
|
||||
return ((ObjectAnimator) objectAnimator).isRunning();
|
||||
}
|
||||
}
|
||||
|
||||
public void end() {
|
||||
if (View10.NEED_PROXY) {
|
||||
((ObjectAnimator10) objectAnimator).end();
|
||||
} else {
|
||||
((ObjectAnimator) objectAnimator).end();
|
||||
}
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
if (View10.NEED_PROXY) {
|
||||
((ObjectAnimator10) objectAnimator).cancel();
|
||||
} else {
|
||||
((ObjectAnimator) objectAnimator).cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public ObjectAnimatorProxy addListener(AnimatorListenerAdapterProxy listener) {
|
||||
if (View10.NEED_PROXY) {
|
||||
((ObjectAnimator10) objectAnimator).addListener((AnimatorListenerAdapter10) listener.animatorListenerAdapter);
|
||||
} else {
|
||||
((ObjectAnimator) objectAnimator).addListener((AnimatorListenerAdapter) listener.animatorListenerAdapter);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return objectAnimator == o;
|
||||
}
|
||||
}
|
@ -1,248 +0,0 @@
|
||||
/*
|
||||
* This is the source code of Telegram for Android v. 1.7.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-2014.
|
||||
*/
|
||||
|
||||
package org.telegram.android.AnimationCompat;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import org.telegram.android.Animation.View10;
|
||||
|
||||
public class ViewProxy {
|
||||
|
||||
public static float getAlpha(View view) {
|
||||
if (View10.NEED_PROXY) {
|
||||
return View10.wrap(view).getAlpha();
|
||||
} else {
|
||||
return view.getAlpha();
|
||||
}
|
||||
}
|
||||
|
||||
public static void setAlpha(View view, float alpha) {
|
||||
if (View10.NEED_PROXY) {
|
||||
View10.wrap(view).setAlpha(alpha);
|
||||
} else {
|
||||
view.setAlpha(alpha);
|
||||
}
|
||||
}
|
||||
|
||||
public static float getPivotX(View view) {
|
||||
if (View10.NEED_PROXY) {
|
||||
return View10.wrap(view).getPivotX();
|
||||
} else {
|
||||
return view.getPivotX();
|
||||
}
|
||||
}
|
||||
|
||||
public static void setPivotX(View view, float pivotX) {
|
||||
if (View10.NEED_PROXY) {
|
||||
View10.wrap(view).setPivotX(pivotX);
|
||||
} else {
|
||||
view.setPivotX(pivotX);
|
||||
}
|
||||
}
|
||||
|
||||
public static float getPivotY(View view) {
|
||||
if (View10.NEED_PROXY) {
|
||||
return View10.wrap(view).getPivotY();
|
||||
} else {
|
||||
return view.getPivotY();
|
||||
}
|
||||
}
|
||||
|
||||
public static void setPivotY(View view, float pivotY) {
|
||||
if (View10.NEED_PROXY) {
|
||||
View10.wrap(view).setPivotY(pivotY);
|
||||
} else {
|
||||
view.setPivotY(pivotY);
|
||||
}
|
||||
}
|
||||
|
||||
public static float getRotation(View view) {
|
||||
if (View10.NEED_PROXY) {
|
||||
return View10.wrap(view).getRotation();
|
||||
} else {
|
||||
return view.getRotation();
|
||||
}
|
||||
}
|
||||
|
||||
public static void setRotation(View view, float rotation) {
|
||||
if (View10.NEED_PROXY) {
|
||||
View10.wrap(view).setRotation(rotation);
|
||||
} else {
|
||||
view.setRotation(rotation);
|
||||
}
|
||||
}
|
||||
|
||||
public static float getRotationX(View view) {
|
||||
if (View10.NEED_PROXY) {
|
||||
return View10.wrap(view).getRotationX();
|
||||
} else {
|
||||
return view.getRotationX();
|
||||
}
|
||||
}
|
||||
|
||||
public void setRotationX(View view, float rotationX) {
|
||||
if (View10.NEED_PROXY) {
|
||||
View10.wrap(view).setRotationX(rotationX);
|
||||
} else {
|
||||
view.setRotationX(rotationX);
|
||||
}
|
||||
}
|
||||
|
||||
public static float getRotationY(View view) {
|
||||
if (View10.NEED_PROXY) {
|
||||
return View10.wrap(view).getRotationY();
|
||||
} else {
|
||||
return view.getRotationY();
|
||||
}
|
||||
}
|
||||
|
||||
public void setRotationY(View view, float rotationY) {
|
||||
if (View10.NEED_PROXY) {
|
||||
View10.wrap(view).setRotationY(rotationY);
|
||||
} else {
|
||||
view.setRotationY(rotationY);
|
||||
}
|
||||
}
|
||||
|
||||
public static float getScaleX(View view) {
|
||||
if (View10.NEED_PROXY) {
|
||||
return View10.wrap(view).getScaleX();
|
||||
} else {
|
||||
return view.getScaleX();
|
||||
}
|
||||
}
|
||||
|
||||
public static void setScaleX(View view, float scaleX) {
|
||||
if (View10.NEED_PROXY) {
|
||||
View10.wrap(view).setScaleX(scaleX);
|
||||
} else {
|
||||
view.setScaleX(scaleX);
|
||||
}
|
||||
}
|
||||
|
||||
public static float getScaleY(View view) {
|
||||
if (View10.NEED_PROXY) {
|
||||
return View10.wrap(view).getScaleY();
|
||||
} else {
|
||||
return view.getScaleY();
|
||||
}
|
||||
}
|
||||
|
||||
public static void setScaleY(View view, float scaleY) {
|
||||
if (View10.NEED_PROXY) {
|
||||
View10.wrap(view).setScaleY(scaleY);
|
||||
} else {
|
||||
view.setScaleY(scaleY);
|
||||
}
|
||||
}
|
||||
|
||||
public static int getScrollX(View view) {
|
||||
if (View10.NEED_PROXY) {
|
||||
return View10.wrap(view).getScrollX();
|
||||
} else {
|
||||
return view.getScrollX();
|
||||
}
|
||||
}
|
||||
|
||||
public static void setScrollX(View view, int value) {
|
||||
if (View10.NEED_PROXY) {
|
||||
View10.wrap(view).setScrollX(value);
|
||||
} else {
|
||||
view.setScrollX(value);
|
||||
}
|
||||
}
|
||||
|
||||
public static int getScrollY(View view) {
|
||||
if (View10.NEED_PROXY) {
|
||||
return View10.wrap(view).getScrollY();
|
||||
} else {
|
||||
return view.getScrollY();
|
||||
}
|
||||
}
|
||||
|
||||
public static void setScrollY(View view, int value) {
|
||||
if (View10.NEED_PROXY) {
|
||||
View10.wrap(view).setScrollY(value);
|
||||
} else {
|
||||
view.setScrollY(value);
|
||||
}
|
||||
}
|
||||
|
||||
public static float getTranslationX(View view) {
|
||||
if (View10.NEED_PROXY) {
|
||||
return View10.wrap(view).getTranslationX();
|
||||
} else {
|
||||
return view.getTranslationX();
|
||||
}
|
||||
}
|
||||
|
||||
public static void setTranslationX(View view, float translationX) {
|
||||
if (View10.NEED_PROXY) {
|
||||
View10.wrap(view).setTranslationX(translationX);
|
||||
} else {
|
||||
view.setTranslationX(translationX);
|
||||
}
|
||||
}
|
||||
|
||||
public static float getTranslationY(View view) {
|
||||
if (View10.NEED_PROXY) {
|
||||
return View10.wrap(view).getTranslationY();
|
||||
} else {
|
||||
return view.getTranslationY();
|
||||
}
|
||||
}
|
||||
|
||||
public static void setTranslationY(View view, float translationY) {
|
||||
if (View10.NEED_PROXY) {
|
||||
View10.wrap(view).setTranslationY(translationY);
|
||||
} else {
|
||||
view.setTranslationY(translationY);
|
||||
}
|
||||
}
|
||||
|
||||
public static float getX(View view) {
|
||||
if (View10.NEED_PROXY) {
|
||||
return View10.wrap(view).getX();
|
||||
} else {
|
||||
return view.getX();
|
||||
}
|
||||
}
|
||||
|
||||
public static void setX(View view, float x) {
|
||||
if (View10.NEED_PROXY) {
|
||||
View10.wrap(view).setX(x);
|
||||
} else {
|
||||
view.setX(x);
|
||||
}
|
||||
}
|
||||
|
||||
public static float getY(View view) {
|
||||
if (View10.NEED_PROXY) {
|
||||
return View10.wrap(view).getY();
|
||||
} else {
|
||||
return view.getY();
|
||||
}
|
||||
}
|
||||
|
||||
public static void setY(View view, float y) {
|
||||
if (View10.NEED_PROXY) {
|
||||
View10.wrap(view).setY(y);
|
||||
} else {
|
||||
view.setY(y);
|
||||
}
|
||||
}
|
||||
|
||||
public static Object wrap(View view) {
|
||||
if (View10.NEED_PROXY) {
|
||||
return View10.wrap(view);
|
||||
} else {
|
||||
return view;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
/*
|
||||
* This is the source code of Telegram for Android v. 1.4.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-2014.
|
||||
*/
|
||||
|
||||
package org.telegram.android;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import org.telegram.messenger.ApplicationLoader;
|
||||
|
||||
public class AppStartReceiver extends BroadcastReceiver {
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ApplicationLoader.startPushService();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
/*
|
||||
* This is the source code of Telegram for Android v. 1.3.2.
|
||||
* It is licensed under GNU GPL v. 2 or later.
|
||||
* You should have received a copy of the license in this archive (see LICENSE).
|
||||
*
|
||||
* Copyright Nikolai Kudashov, 2013.
|
||||
*/
|
||||
|
||||
package org.telegram.android;
|
||||
|
||||
import android.accounts.AbstractAccountAuthenticator;
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountAuthenticatorResponse;
|
||||
import android.accounts.AccountManager;
|
||||
import android.accounts.NetworkErrorException;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
|
||||
public class AuthenticatorService extends Service {
|
||||
|
||||
private static class Authenticator extends AbstractAccountAuthenticator {
|
||||
private final Context context;
|
||||
|
||||
public Authenticator(Context context) {
|
||||
super(context);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options)
|
||||
throws NetworkErrorException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse response, Account account) throws NetworkErrorException {
|
||||
return super.getAccountRemovalAllowed(response, account);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)
|
||||
throws NetworkErrorException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthTokenLabel(String authTokenType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle hasFeatures(AccountAuthenticatorResponse response,
|
||||
Account account, String[] features)
|
||||
throws NetworkErrorException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)
|
||||
throws NetworkErrorException {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static Authenticator authenticator = null;
|
||||
|
||||
protected Authenticator getAuthenticator() {
|
||||
if (authenticator == null) {
|
||||
authenticator = new Authenticator(this);
|
||||
}
|
||||
return authenticator;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
if (intent.getAction().equals(AccountManager.ACTION_AUTHENTICATOR_INTENT)) {
|
||||
return getAuthenticator().getIBinder();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,65 +0,0 @@
|
||||
/*
|
||||
* This is the source code of Telegram for Android v. 1.3.2.
|
||||
* It is licensed under GNU GPL v. 2 or later.
|
||||
* You should have received a copy of the license in this archive (see LICENSE).
|
||||
*
|
||||
* Copyright Nikolai Kudashov, 2013.
|
||||
*/
|
||||
|
||||
package org.telegram.android;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.OperationCanceledException;
|
||||
import android.app.Service;
|
||||
import android.content.AbstractThreadedSyncAdapter;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SyncResult;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
|
||||
import org.telegram.messenger.FileLog;
|
||||
|
||||
public class ContactsSyncAdapterService extends Service {
|
||||
private static SyncAdapterImpl sSyncAdapter = null;
|
||||
|
||||
public ContactsSyncAdapterService() {
|
||||
super();
|
||||
}
|
||||
|
||||
private static class SyncAdapterImpl extends AbstractThreadedSyncAdapter {
|
||||
private Context mContext;
|
||||
|
||||
public SyncAdapterImpl(Context context) {
|
||||
super(context, true);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
|
||||
try {
|
||||
ContactsSyncAdapterService.performSync(mContext, account, extras, authority, provider, syncResult);
|
||||
} catch (OperationCanceledException e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return getSyncAdapter().getSyncAdapterBinder();
|
||||
}
|
||||
|
||||
private SyncAdapterImpl getSyncAdapter() {
|
||||
if (sSyncAdapter == null) {
|
||||
sSyncAdapter = new SyncAdapterImpl(this);
|
||||
}
|
||||
return sSyncAdapter;
|
||||
}
|
||||
|
||||
private static void performSync(Context context, Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult)
|
||||
throws OperationCanceledException {
|
||||
FileLog.d("telegram", "performSync: " + account.toString());
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
/*
|
||||
* This is the source code of Telegram for Android v. 1.7.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-2014.
|
||||
*/
|
||||
|
||||
package org.telegram.android;
|
||||
|
||||
import org.telegram.messenger.TLObject;
|
||||
|
||||
public class DownloadObject {
|
||||
public TLObject object;
|
||||
public int type;
|
||||
public long id;
|
||||
}
|
@ -1,566 +0,0 @@
|
||||
/*
|
||||
* This is the source code of Telegram for Android v. 1.3.2.
|
||||
* It is licensed under GNU GPL v. 2 or later.
|
||||
* You should have received a copy of the license in this archive (see LICENSE).
|
||||
*
|
||||
* Copyright Nikolai Kudashov, 2013.
|
||||
*/
|
||||
|
||||
package org.telegram.android;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.Spannable;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.DynamicDrawableSpan;
|
||||
import android.text.style.ImageSpan;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.telegram.messenger.FileLog;
|
||||
import org.telegram.messenger.Utilities;
|
||||
import org.telegram.messenger.ApplicationLoader;
|
||||
|
||||
public class Emoji {
|
||||
private static HashMap<Long, DrawableInfo> rects = new HashMap<>();
|
||||
private static int drawImgSize;
|
||||
private static int bigImgSize;
|
||||
private static boolean inited = false;
|
||||
private static Paint placeholderPaint;
|
||||
private static Bitmap emojiBmp[] = new Bitmap[5];
|
||||
private static boolean loadingEmoji[] = new boolean[5];
|
||||
|
||||
private static final int[] cols = {
|
||||
13, 10, 15, 10, 14
|
||||
};
|
||||
|
||||
private static final char[] emojiChars = {
|
||||
0x00A9, 0x00AE, 0x203C, 0x2049, 0x2122, 0x2139, 0x2194, 0x2195, 0x2196, 0x2197,
|
||||
0x2198, 0x2199, 0x21A9, 0x21AA, 0x231A, 0x231B, 0x23E9, 0x23EA, 0x23EB, 0x23EC,
|
||||
0x23F0, 0x23F3, 0x24C2, 0x25AA, 0x25AB, 0x25B6, 0x25C0, 0x25FB, 0x25FC, 0x25FD,
|
||||
0x25FE, 0x2600, 0x2601, 0x260E, 0x2611, 0x2614, 0x2615, 0x261D, 0x263A, 0x2648,
|
||||
0x2649, 0x264A, 0x264B, 0x264C, 0x264D, 0x264E, 0x264F, 0x2650, 0x2651, 0x2652,
|
||||
0x2653, 0x2660, 0x2663, 0x2665, 0x2666, 0x2668, 0x267B, 0x267F, 0x2693, 0x26A0,
|
||||
0x26A1, 0x26AA, 0x26AB, 0x26BD, 0x26BE, 0x26C4, 0x26C5, 0x26CE, 0x26D4, 0x26EA,
|
||||
0x26F2, 0x26F3, 0x26F5, 0x26FA, 0x26FD, 0x2702, 0x2705, 0x2708, 0x2709, 0x270A,
|
||||
0x270B, 0x270C, 0x270F, 0x2712, 0x2714, 0x2716, 0x2728, 0x2733, 0x2734, 0x2744,
|
||||
0x2747, 0x274C, 0x274E, 0x2753, 0x2754, 0x2755, 0x2757, 0x2764, 0x2795, 0x2796,
|
||||
0x2797, 0x27A1, 0x27B0, 0x27BF, 0x2934, 0x2935, 0x2B05, 0x2B06, 0x2B07, 0x2B1B,
|
||||
0x2B1C, 0x2B50, 0x2B55, 0x3030, 0x303D, 0x3297, 0x3299
|
||||
};
|
||||
|
||||
public static long[][] data = {
|
||||
new long[]//189
|
||||
{
|
||||
0x00000000D83DDE04L, 0x00000000D83DDE03L, 0x00000000D83DDE00L, 0x00000000D83DDE0AL, 0x000000000000263AL, 0x00000000D83DDE09L, 0x00000000D83DDE0DL,
|
||||
0x00000000D83DDE18L, 0x00000000D83DDE1AL, 0x00000000D83DDE17L, 0x00000000D83DDE19L, 0x00000000D83DDE1CL, 0x00000000D83DDE1DL, 0x00000000D83DDE1BL,
|
||||
0x00000000D83DDE33L, 0x00000000D83DDE01L, 0x00000000D83DDE14L, 0x00000000D83DDE0CL, 0x00000000D83DDE12L, 0x00000000D83DDE1EL, 0x00000000D83DDE23L,
|
||||
0x00000000D83DDE22L, 0x00000000D83DDE02L, 0x00000000D83DDE2DL, 0x00000000D83DDE2AL, 0x00000000D83DDE25L, 0x00000000D83DDE30L, 0x00000000D83DDE05L,
|
||||
0x00000000D83DDE13L, 0x00000000D83DDE29L, 0x00000000D83DDE2BL, 0x00000000D83DDE28L, 0x00000000D83DDE31L, 0x00000000D83DDE20L, 0x00000000D83DDE21L,
|
||||
0x00000000D83DDE24L, 0x00000000D83DDE16L, 0x00000000D83DDE06L, 0x00000000D83DDE0BL, 0x00000000D83DDE37L, 0x00000000D83DDE0EL, 0x00000000D83DDE34L,
|
||||
0x00000000D83DDE35L, 0x00000000D83DDE32L, 0x00000000D83DDE1FL, 0x00000000D83DDE26L, 0x00000000D83DDE27L, 0x00000000D83DDE08L, 0x00000000D83DDC7FL,
|
||||
0x00000000D83DDE2EL, 0x00000000D83DDE2CL, 0x00000000D83DDE10L, 0x00000000D83DDE15L, 0x00000000D83DDE2FL, 0x00000000D83DDE36L, 0x00000000D83DDE07L,
|
||||
0x00000000D83DDE0FL, 0x00000000D83DDE11L, 0x00000000D83DDC72L, 0x00000000D83DDC73L, 0x00000000D83DDC6EL, 0x00000000D83DDC77L, 0x00000000D83DDC82L,
|
||||
0x00000000D83DDC76L, 0x00000000D83DDC66L, 0x00000000D83DDC67L, 0x00000000D83DDC68L, 0x00000000D83DDC69L, 0x00000000D83DDC74L, 0x00000000D83DDC75L,
|
||||
0x00000000D83DDC71L, 0x00000000D83DDC7CL, 0x00000000D83DDC78L, 0x00000000D83DDE3AL, 0x00000000D83DDE38L, 0x00000000D83DDE3BL, 0x00000000D83DDE3DL,
|
||||
0x00000000D83DDE3CL, 0x00000000D83DDE40L, 0x00000000D83DDE3FL, 0x00000000D83DDE39L, 0x00000000D83DDE3EL, 0x00000000D83DDC79L, 0x00000000D83DDC7AL,
|
||||
0x00000000D83DDE48L, 0x00000000D83DDE49L, 0x00000000D83DDE4AL, 0x00000000D83DDC80L, 0x00000000D83DDC7DL, 0x00000000D83DDCA9L, 0x00000000D83DDD25L,
|
||||
0x0000000000002728L, 0x00000000D83CDF1FL, 0x00000000D83DDCABL, 0x00000000D83DDCA5L, 0x00000000D83DDCA2L, 0x00000000D83DDCA6L, 0x00000000D83DDCA7L,
|
||||
0x00000000D83DDCA4L, 0x00000000D83DDCA8L, 0x00000000D83DDC42L, 0x00000000D83DDC40L, 0x00000000D83DDC43L, 0x00000000D83DDC45L, 0x00000000D83DDC44L,
|
||||
0x00000000D83DDC4DL, 0x00000000D83DDC4EL, 0x00000000D83DDC4CL, 0x00000000D83DDC4AL, 0x000000000000270AL, 0x000000000000270CL, 0x00000000D83DDC4BL,
|
||||
0x000000000000270BL, 0x00000000D83DDC50L, 0x00000000D83DDC46L, 0x00000000D83DDC47L, 0x00000000D83DDC49L, 0x00000000D83DDC48L, 0x00000000D83DDE4CL,
|
||||
0x00000000D83DDE4FL, 0x000000000000261DL, 0x00000000D83DDC4FL, 0x00000000D83DDCAAL, 0x00000000D83DDEB6L, 0x00000000D83CDFC3L, 0x00000000D83DDC83L,
|
||||
0x00000000D83DDC6BL, 0x00000000D83DDC6AL, 0x00000000D83DDC6CL, 0x00000000D83DDC6DL, 0x00000000D83DDC8FL, 0x00000000D83DDC91L, 0x00000000D83DDC6FL,
|
||||
0x00000000D83DDE46L, 0x00000000D83DDE45L, 0x00000000D83DDC81L, 0x00000000D83DDE4BL, 0x00000000D83DDC86L, 0x00000000D83DDC87L, 0x00000000D83DDC85L,
|
||||
0x00000000D83DDC70L, 0x00000000D83DDE4EL, 0x00000000D83DDE4DL, 0x00000000D83DDE47L, 0x00000000D83CDFA9L, 0x00000000D83DDC51L, 0x00000000D83DDC52L,
|
||||
0x00000000D83DDC5FL, 0x00000000D83DDC5EL, 0x00000000D83DDC61L, 0x00000000D83DDC60L, 0x00000000D83DDC62L, 0x00000000D83DDC55L, 0x00000000D83DDC54L,
|
||||
0x00000000D83DDC5AL, 0x00000000D83DDC57L, 0x00000000D83CDFBDL, 0x00000000D83DDC56L, 0x00000000D83DDC58L, 0x00000000D83DDC59L, 0x00000000D83DDCBCL,
|
||||
0x00000000D83DDC5CL, 0x00000000D83DDC5DL, 0x00000000D83DDC5BL, 0x00000000D83DDC53L, 0x00000000D83CDF80L, 0x00000000D83CDF02L, 0x00000000D83DDC84L,
|
||||
0x00000000D83DDC9BL, 0x00000000D83DDC99L, 0x00000000D83DDC9CL, 0x00000000D83DDC9AL, 0x0000000000002764L, 0x00000000D83DDC94L, 0x00000000D83DDC97L,
|
||||
0x00000000D83DDC93L, 0x00000000D83DDC95L, 0x00000000D83DDC96L, 0x00000000D83DDC9EL, 0x00000000D83DDC98L, 0x00000000D83DDC8CL, 0x00000000D83DDC8BL,
|
||||
0x00000000D83DDC8DL, 0x00000000D83DDC8EL, 0x00000000D83DDC64L, 0x00000000D83DDC65L, 0x00000000D83DDCACL, 0x00000000D83DDC63L, 0x00000000D83DDCADL},
|
||||
new long[]//116
|
||||
{
|
||||
0x00000000D83DDC36L, 0x00000000D83DDC3AL, 0x00000000D83DDC31L, 0x00000000D83DDC2DL, 0x00000000D83DDC39L, 0x00000000D83DDC30L, 0x00000000D83DDC38L, 0x00000000D83DDC2FL,
|
||||
0x00000000D83DDC28L, 0x00000000D83DDC3BL, 0x00000000D83DDC37L, 0x00000000D83DDC3DL, 0x00000000D83DDC2EL, 0x00000000D83DDC17L, 0x00000000D83DDC35L,
|
||||
0x00000000D83DDC12L, 0x00000000D83DDC34L, 0x00000000D83DDC11L, 0x00000000D83DDC18L, 0x00000000D83DDC3CL, 0x00000000D83DDC27L, 0x00000000D83DDC26L,
|
||||
0x00000000D83DDC24L, 0x00000000D83DDC25L, 0x00000000D83DDC23L, 0x00000000D83DDC14L, 0x00000000D83DDC0DL, 0x00000000D83DDC22L, 0x00000000D83DDC1BL,
|
||||
0x00000000D83DDC1DL, 0x00000000D83DDC1CL, 0x00000000D83DDC1EL, 0x00000000D83DDC0CL, 0x00000000D83DDC19L, 0x00000000D83DDC1AL, 0x00000000D83DDC20L,
|
||||
0x00000000D83DDC1FL, 0x00000000D83DDC2CL, 0x00000000D83DDC33L, 0x00000000D83DDC0BL, 0x00000000D83DDC04L, 0x00000000D83DDC0FL, 0x00000000D83DDC00L,
|
||||
0x00000000D83DDC03L, 0x00000000D83DDC05L, 0x00000000D83DDC07L, 0x00000000D83DDC09L, 0x00000000D83DDC0EL, 0x00000000D83DDC10L, 0x00000000D83DDC13L,
|
||||
0x00000000D83DDC15L, 0x00000000D83DDC16L, 0x00000000D83DDC01L, 0x00000000D83DDC02L, 0x00000000D83DDC32L, 0x00000000D83DDC21L, 0x00000000D83DDC0AL,
|
||||
0x00000000D83DDC2BL, 0x00000000D83DDC2AL, 0x00000000D83DDC06L, 0x00000000D83DDC08L, 0x00000000D83DDC29L, 0x00000000D83DDC3EL, 0x00000000D83DDC90L,
|
||||
0x00000000D83CDF38L, 0x00000000D83CDF37L, 0x00000000D83CDF40L, 0x00000000D83CDF39L, 0x00000000D83CDF3BL, 0x00000000D83CDF3AL, 0x00000000D83CDF41L,
|
||||
0x00000000D83CDF43L, 0x00000000D83CDF42L, 0x00000000D83CDF3FL, 0x00000000D83CDF3EL, 0x00000000D83CDF44L, 0x00000000D83CDF35L, 0x00000000D83CDF34L,
|
||||
0x00000000D83CDF32L, 0x00000000D83CDF33L, 0x00000000D83CDF30L, 0x00000000D83CDF31L, 0x00000000D83CDF3CL, 0x00000000D83CDF10L, 0x00000000D83CDF1EL,
|
||||
0x00000000D83CDF1DL, 0x00000000D83CDF1AL, 0x00000000D83CDF11L, 0x00000000D83CDF12L, 0x00000000D83CDF13L, 0x00000000D83CDF14L, 0x00000000D83CDF15L,
|
||||
0x00000000D83CDF16L, 0x00000000D83CDF17L, 0x00000000D83CDF18L, 0x00000000D83CDF1CL, 0x00000000D83CDF1BL, 0x00000000D83CDF19L, 0x00000000D83CDF0DL,
|
||||
0x00000000D83CDF0EL, 0x00000000D83CDF0FL, 0x00000000D83CDF0BL, 0x00000000D83CDF0CL, 0x00000000D83CDF20L, 0x0000000000002B50L, 0x0000000000002600L,
|
||||
0x00000000000026C5L, 0x0000000000002601L, 0x00000000000026A1L, 0x0000000000002614L, 0x0000000000002744L, 0x00000000000026C4L, 0x00000000D83CDF00L,
|
||||
0x00000000D83CDF01L, 0x00000000D83CDF08L, 0x00000000D83CDF0AL},
|
||||
new long[]//230
|
||||
{
|
||||
0x00000000D83CDF8DL, 0x00000000D83DDC9DL, 0x00000000D83CDF8EL, 0x00000000D83CDF92L, 0x00000000D83CDF93L, 0x00000000D83CDF8FL, 0x00000000D83CDF86L, 0x00000000D83CDF87L,
|
||||
0x00000000D83CDF90L, 0x00000000D83CDF91L, 0x00000000D83CDF83L, 0x00000000D83DDC7BL, 0x00000000D83CDF85L, 0x00000000D83CDF84L, 0x00000000D83CDF81L,
|
||||
0x00000000D83CDF8BL, 0x00000000D83CDF89L, 0x00000000D83CDF8AL, 0x00000000D83CDF88L, 0x00000000D83CDF8CL, 0x00000000D83DDD2EL, 0x00000000D83CDFA5L,
|
||||
0x00000000D83DDCF7L, 0x00000000D83DDCF9L, 0x00000000D83DDCFCL, 0x00000000D83DDCBFL, 0x00000000D83DDCC0L, 0x00000000D83DDCBDL, 0x00000000D83DDCBEL,
|
||||
0x00000000D83DDCBBL, 0x00000000D83DDCF1L, 0x000000000000260EL, 0x00000000D83DDCDEL, 0x00000000D83DDCDFL, 0x00000000D83DDCE0L, 0x00000000D83DDCE1L,
|
||||
0x00000000D83DDCFAL, 0x00000000D83DDCFBL, 0x00000000D83DDD0AL, 0x00000000D83DDD09L, 0x00000000D83DDD08L, 0x00000000D83DDD07L, 0x00000000D83DDD14L,
|
||||
0x00000000D83DDD15L, 0x00000000D83DDCE2L, 0x00000000D83DDCE3L, 0x00000000000023F3L, 0x000000000000231BL, 0x00000000000023F0L, 0x000000000000231AL,
|
||||
0x00000000D83DDD13L, 0x00000000D83DDD12L, 0x00000000D83DDD0FL, 0x00000000D83DDD10L, 0x00000000D83DDD11L, 0x00000000D83DDD0EL, 0x00000000D83DDCA1L,
|
||||
0x00000000D83DDD26L, 0x00000000D83DDD06L, 0x00000000D83DDD05L, 0x00000000D83DDD0CL, 0x00000000D83DDD0BL, 0x00000000D83DDD0DL, 0x00000000D83DDEC1L, 0x00000000D83DDEC0L,
|
||||
0x00000000D83DDEBFL, 0x00000000D83DDEBDL, 0x00000000D83DDD27L, 0x00000000D83DDD29L, 0x00000000D83DDD28L, 0x00000000D83DDEAAL, 0x00000000D83DDEACL,
|
||||
0x00000000D83DDCA3L, 0x00000000D83DDD2BL, 0x00000000D83DDD2AL, 0x00000000D83DDC8AL, 0x00000000D83DDC89L, 0x00000000D83DDCB0L, 0x00000000D83DDCB4L,
|
||||
0x00000000D83DDCB5L, 0x00000000D83DDCB7L, 0x00000000D83DDCB6L, 0x00000000D83DDCB3L, 0x00000000D83DDCB8L, 0x00000000D83DDCF2L, 0x00000000D83DDCE7L,
|
||||
0x00000000D83DDCE5L, 0x00000000D83DDCE4L, 0x0000000000002709L, 0x00000000D83DDCE9L, 0x00000000D83DDCE8L, 0x00000000D83DDCEFL, 0x00000000D83DDCEBL,
|
||||
0x00000000D83DDCEAL, 0x00000000D83DDCECL, 0x00000000D83DDCEDL, 0x00000000D83DDCEEL, 0x00000000D83DDCE6L, 0x00000000D83DDCDDL, 0x00000000D83DDCC4L,
|
||||
0x00000000D83DDCC3L, 0x00000000D83DDCD1L, 0x00000000D83DDCCAL, 0x00000000D83DDCC8L, 0x00000000D83DDCC9L, 0x00000000D83DDCDCL, 0x00000000D83DDCCBL,
|
||||
0x00000000D83DDCC5L, 0x00000000D83DDCC6L, 0x00000000D83DDCC7L, 0x00000000D83DDCC1L, 0x00000000D83DDCC2L, 0x0000000000002702L, 0x00000000D83DDCCCL,
|
||||
0x00000000D83DDCCEL, 0x0000000000002712L, 0x000000000000270FL, 0x00000000D83DDCCFL, 0x00000000D83DDCD0L, 0x00000000D83DDCD5L, 0x00000000D83DDCD7L,
|
||||
0x00000000D83DDCD8L, 0x00000000D83DDCD9L, 0x00000000D83DDCD3L, 0x00000000D83DDCD4L, 0x00000000D83DDCD2L, 0x00000000D83DDCDAL, 0x00000000D83DDCD6L,
|
||||
0x00000000D83DDD16L, 0x00000000D83DDCDBL, 0x00000000D83DDD2CL, 0x00000000D83DDD2DL, 0x00000000D83DDCF0L, 0x00000000D83CDFA8L, 0x00000000D83CDFACL,
|
||||
0x00000000D83CDFA4L, 0x00000000D83CDFA7L, 0x00000000D83CDFBCL, 0x00000000D83CDFB5L, 0x00000000D83CDFB6L, 0x00000000D83CDFB9L, 0x00000000D83CDFBBL,
|
||||
0x00000000D83CDFBAL, 0x00000000D83CDFB7L, 0x00000000D83CDFB8L, 0x00000000D83DDC7EL, 0x00000000D83CDFAEL, 0x00000000D83CDCCFL, 0x00000000D83CDFB4L,
|
||||
0x00000000D83CDC04L, 0x00000000D83CDFB2L, 0x00000000D83CDFAFL, 0x00000000D83CDFC8L, 0x00000000D83CDFC0L, 0x00000000000026BDL, 0x00000000000026BEL,
|
||||
0x00000000D83CDFBEL, 0x00000000D83CDFB1L, 0x00000000D83CDFC9L, 0x00000000D83CDFB3L, 0x00000000000026F3L, 0x00000000D83DDEB5L, 0x00000000D83DDEB4L,
|
||||
0x00000000D83CDFC1L, 0x00000000D83CDFC7L, 0x00000000D83CDFC6L, 0x00000000D83CDFBFL, 0x00000000D83CDFC2L, 0x00000000D83CDFCAL, 0x00000000D83CDFC4L,
|
||||
0x00000000D83CDFA3L, 0x0000000000002615L, 0x00000000D83CDF75L, 0x00000000D83CDF76L, 0x00000000D83CDF7CL, 0x00000000D83CDF7AL, 0x00000000D83CDF7BL,
|
||||
0x00000000D83CDF78L, 0x00000000D83CDF79L, 0x00000000D83CDF77L, 0x00000000D83CDF74L, 0x00000000D83CDF55L, 0x00000000D83CDF54L, 0x00000000D83CDF5FL,
|
||||
0x00000000D83CDF57L, 0x00000000D83CDF56L, 0x00000000D83CDF5DL, 0x00000000D83CDF5BL, 0x00000000D83CDF64L, 0x00000000D83CDF71L, 0x00000000D83CDF63L,
|
||||
0x00000000D83CDF65L, 0x00000000D83CDF59L, 0x00000000D83CDF58L, 0x00000000D83CDF5AL, 0x00000000D83CDF5CL, 0x00000000D83CDF72L, 0x00000000D83CDF62L,
|
||||
0x00000000D83CDF61L, 0x00000000D83CDF73L, 0x00000000D83CDF5EL, 0x00000000D83CDF69L, 0x00000000D83CDF6EL, 0x00000000D83CDF66L, 0x00000000D83CDF68L,
|
||||
0x00000000D83CDF67L, 0x00000000D83CDF82L, 0x00000000D83CDF70L, 0x00000000D83CDF6AL, 0x00000000D83CDF6BL, 0x00000000D83CDF6CL, 0x00000000D83CDF6DL,
|
||||
0x00000000D83CDF6FL, 0x00000000D83CDF4EL, 0x00000000D83CDF4FL, 0x00000000D83CDF4AL, 0x00000000D83CDF4BL, 0x00000000D83CDF52L, 0x00000000D83CDF47L,
|
||||
0x00000000D83CDF49L, 0x00000000D83CDF53L, 0x00000000D83CDF51L, 0x00000000D83CDF48L, 0x00000000D83CDF4CL, 0x00000000D83CDF50L, 0x00000000D83CDF4DL,
|
||||
0x00000000D83CDF60L, 0x00000000D83CDF46L, 0x00000000D83CDF45L, 0x00000000D83CDF3DL},
|
||||
new long[]//101
|
||||
{
|
||||
0x00000000D83CDFE0L, 0x00000000D83CDFE1L, 0x00000000D83CDFEBL, 0x00000000D83CDFE2L, 0x00000000D83CDFE3L, 0x00000000D83CDFE5L, 0x00000000D83CDFE6L, 0x00000000D83CDFEAL,
|
||||
0x00000000D83CDFE9L, 0x00000000D83CDFE8L, 0x00000000D83DDC92L, 0x00000000000026EAL, 0x00000000D83CDFECL, 0x00000000D83CDFE4L, 0x00000000D83CDF07L,
|
||||
0x00000000D83CDF06L, 0x00000000D83CDFEFL, 0x00000000D83CDFF0L, 0x00000000000026FAL, 0x00000000D83CDFEDL, 0x00000000D83DDDFCL, 0x00000000D83DDDFEL,
|
||||
0x00000000D83DDDFBL, 0x00000000D83CDF04L, 0x00000000D83CDF05L, 0x00000000D83CDF03L, 0x00000000D83DDDFDL, 0x00000000D83CDF09L, 0x00000000D83CDFA0L,
|
||||
0x00000000D83CDFA1L, 0x00000000000026F2L, 0x00000000D83CDFA2L, 0x00000000D83DDEA2L, 0x00000000000026F5L, 0x00000000D83DDEA4L, 0x00000000D83DDEA3L,
|
||||
0x0000000000002693L, 0x00000000D83DDE80L, 0x0000000000002708L, 0x00000000D83DDCBAL, 0x00000000D83DDE81L, 0x00000000D83DDE82L, 0x00000000D83DDE8AL,
|
||||
0x00000000D83DDE89L, 0x00000000D83DDE9EL, 0x00000000D83DDE86L, 0x00000000D83DDE84L, 0x00000000D83DDE85L, 0x00000000D83DDE88L, 0x00000000D83DDE87L,
|
||||
0x00000000D83DDE9DL, 0x00000000D83DDE8BL, 0x00000000D83DDE83L, 0x00000000D83DDE8EL, 0x00000000D83DDE8CL, 0x00000000D83DDE8DL, 0x00000000D83DDE99L,
|
||||
0x00000000D83DDE98L, 0x00000000D83DDE97L, 0x00000000D83DDE95L, 0x00000000D83DDE96L, 0x00000000D83DDE9BL, 0x00000000D83DDE9AL, 0x00000000D83DDEA8L,
|
||||
0x00000000D83DDE93L, 0x00000000D83DDE94L, 0x00000000D83DDE92L, 0x00000000D83DDE91L, 0x00000000D83DDE90L, 0x00000000D83DDEB2L, 0x00000000D83DDEA1L,
|
||||
0x00000000D83DDE9FL, 0x00000000D83DDEA0L, 0x00000000D83DDE9CL, 0x00000000D83DDC88L, 0x00000000D83DDE8FL, 0x00000000D83CDFABL, 0x00000000D83DDEA6L,
|
||||
0x00000000D83DDEA5L, 0x00000000000026A0L, 0x00000000D83DDEA7L, 0x00000000D83DDD30L, 0x00000000000026FDL, 0x00000000D83CDFEEL, 0x00000000D83CDFB0L,
|
||||
0x0000000000002668L, 0x00000000D83DDDFFL, 0x00000000D83CDFAAL, 0x00000000D83CDFADL, 0x00000000D83DDCCDL, 0x00000000D83DDEA9L, 0xD83CDDEFD83CDDF5L,
|
||||
0xD83CDDF0D83CDDF7L, 0xD83CDDE9D83CDDEAL, 0xD83CDDE8D83CDDF3L, 0xD83CDDFAD83CDDF8L, 0xD83CDDEBD83CDDF7L, 0xD83CDDEAD83CDDF8L, 0xD83CDDEED83CDDF9L,
|
||||
0xD83CDDF7D83CDDFAL, 0xD83CDDECD83CDDE7L},
|
||||
new long[]//209
|
||||
{
|
||||
0x00000000003120E3L, 0x00000000003220E3L, 0x00000000003320E3L, 0x00000000003420E3L, 0x00000000003520E3L, 0x00000000003620E3L, 0x00000000003720E3L,
|
||||
0x00000000003820E3L, 0x00000000003920E3L, 0x00000000003020E3L, 0x00000000D83DDD1FL, 0x00000000D83DDD22L, 0x00000000002320E3L, 0x00000000D83DDD23L,
|
||||
0x0000000000002B06L, 0x0000000000002B07L, 0x0000000000002B05L, 0x00000000000027A1L, 0x00000000D83DDD20L, 0x00000000D83DDD21L, 0x00000000D83DDD24L,
|
||||
0x0000000000002197L, 0x0000000000002196L, 0x0000000000002198L, 0x0000000000002199L, 0x0000000000002194L, 0x0000000000002195L, 0x00000000D83DDD04L,
|
||||
0x00000000000025C0L, 0x00000000000025B6L, 0x00000000D83DDD3CL, 0x00000000D83DDD3DL, 0x00000000000021A9L, 0x00000000000021AAL, 0x0000000000002139L,
|
||||
0x00000000000023EAL, 0x00000000000023E9L, 0x00000000000023EBL, 0x00000000000023ECL, 0x0000000000002935L, 0x0000000000002934L, 0x00000000D83CDD97L,
|
||||
0x00000000D83DDD00L, 0x00000000D83DDD01L, 0x00000000D83DDD02L, 0x00000000D83CDD95L, 0x00000000D83CDD99L, 0x00000000D83CDD92L, 0x00000000D83CDD93L,
|
||||
0x00000000D83CDD96L, 0x00000000D83DDCF6L, 0x00000000D83CDFA6L, 0x00000000D83CDE01L, 0x00000000D83CDE2FL, 0x00000000D83CDE33L, 0x00000000D83CDE35L,
|
||||
0x00000000D83CDE32L, 0x00000000D83CDE34L, 0x00000000D83CDE50L, 0x00000000D83CDE39L, 0x00000000D83CDE3AL, 0x00000000D83CDE36L, 0x00000000D83CDE1AL,
|
||||
0x00000000D83DDEBBL, 0x00000000D83DDEB9L, 0x00000000D83DDEBAL, 0x00000000D83DDEBCL, 0x00000000D83DDEBEL, 0x00000000D83DDEB0L, 0x00000000D83DDEAEL,
|
||||
0x00000000D83CDD7FL, 0x000000000000267FL, 0x00000000D83DDEADL, 0x00000000D83CDE37L, 0x00000000D83CDE38L, 0x00000000D83CDE02L, 0x00000000000024C2L,
|
||||
0x00000000D83DDEC2L, 0x00000000D83DDEC4L, 0x00000000D83DDEC5L, 0x00000000D83DDEC3L, 0x00000000D83CDE51L, 0x0000000000003299L, 0x0000000000003297L,
|
||||
0x00000000D83CDD91L, 0x00000000D83CDD98L, 0x00000000D83CDD94L, 0x00000000D83DDEABL,
|
||||
0x00000000D83DDD1EL, 0x00000000D83DDCF5L, 0x00000000D83DDEAFL, 0x00000000D83DDEB1L, 0x00000000D83DDEB3L, 0x00000000D83DDEB7L, 0x00000000D83DDEB8L,
|
||||
0x00000000000026D4L, 0x0000000000002733L, 0x0000000000002747L, 0x000000000000274EL, 0x0000000000002705L, 0x0000000000002734L, 0x00000000D83DDC9FL,
|
||||
0x00000000D83CDD9AL, 0x00000000D83DDCF3L, 0x00000000D83DDCF4L, 0x00000000D83CDD70L, 0x00000000D83CDD71L, 0x00000000D83CDD8EL, 0x00000000D83CDD7EL,
|
||||
0x00000000D83DDCA0L, 0x00000000000027BFL, 0x000000000000267BL, 0x0000000000002648L, 0x0000000000002649L, 0x000000000000264AL, 0x000000000000264BL,
|
||||
0x000000000000264CL, 0x000000000000264DL, 0x000000000000264EL, 0x000000000000264FL, 0x0000000000002650L, 0x0000000000002651L, 0x0000000000002652L,
|
||||
0x0000000000002653L, 0x00000000000026CEL, 0x00000000D83DDD2FL, 0x00000000D83CDFE7L, 0x00000000D83DDCB9L, 0x00000000D83DDCB2L, 0x00000000D83DDCB1L,
|
||||
0x00000000000000A9L, 0x00000000000000AEL, 0x0000000000002122L, 0x000000000000303DL, 0x0000000000003030L, 0x00000000D83DDD1DL, 0x00000000D83DDD1AL,
|
||||
0x00000000D83DDD19L, 0x00000000D83DDD1BL, 0x00000000D83DDD1CL, 0x000000000000274CL, 0x0000000000002B55L, 0x0000000000002757L, 0x000000000000203CL,
|
||||
0x0000000000002049L, 0x0000000000002753L,
|
||||
0x0000000000002755L, 0x0000000000002754L, 0x00000000D83DDD03L, 0x00000000D83DDD5BL, 0x00000000D83DDD67L, 0x00000000D83DDD50L, 0x00000000D83DDD5CL,
|
||||
0x00000000D83DDD51L, 0x00000000D83DDD5DL, 0x00000000D83DDD52L, 0x00000000D83DDD5EL, 0x00000000D83DDD53L, 0x00000000D83DDD5FL, 0x00000000D83DDD54L,
|
||||
0x00000000D83DDD60L, 0x00000000D83DDD55L, 0x00000000D83DDD56L, 0x00000000D83DDD57L, 0x00000000D83DDD58L, 0x00000000D83DDD59L, 0x00000000D83DDD5AL,
|
||||
0x00000000D83DDD61L, 0x00000000D83DDD62L, 0x00000000D83DDD63L, 0x00000000D83DDD64L, 0x00000000D83DDD65L, 0x00000000D83DDD66L, 0x0000000000002716L,
|
||||
0x0000000000002795L, 0x0000000000002796L, 0x0000000000002797L, 0x0000000000002660L, 0x0000000000002665L, 0x0000000000002663L, 0x0000000000002666L,
|
||||
0x00000000D83DDCAEL, 0x00000000D83DDCAFL, 0x0000000000002714L, 0x0000000000002611L, 0x00000000D83DDD18L, 0x00000000D83DDD17L, 0x00000000000027B0L,
|
||||
0x00000000D83DDD31L, 0x00000000D83DDD32L, 0x00000000D83DDD33L, 0x00000000000025FCL, 0x00000000000025FBL, 0x00000000000025FEL, 0x00000000000025FDL,
|
||||
0x00000000000025AAL, 0x00000000000025ABL, 0x00000000D83DDD3AL, 0x0000000000002B1CL, 0x0000000000002B1BL, 0x00000000000026ABL, 0x00000000000026AAL,
|
||||
0x00000000D83DDD34L, 0x00000000D83DDD35L, 0x00000000D83DDD3BL, 0x00000000D83DDD36L, 0x00000000D83DDD37L, 0x00000000D83DDD38L, 0x00000000D83DDD39L}};
|
||||
|
||||
static {
|
||||
int emojiFullSize;
|
||||
if (AndroidUtilities.density <= 1.0f) {
|
||||
emojiFullSize = 32;
|
||||
} else if (AndroidUtilities.density <= 1.5f) {
|
||||
emojiFullSize = 48;
|
||||
} else if (AndroidUtilities.density <= 2.0f) {
|
||||
emojiFullSize = 64;
|
||||
} else {
|
||||
emojiFullSize = 96;
|
||||
}
|
||||
drawImgSize = AndroidUtilities.dp(20);
|
||||
if (AndroidUtilities.isTablet()) {
|
||||
bigImgSize = AndroidUtilities.dp(40);
|
||||
} else {
|
||||
bigImgSize = AndroidUtilities.dp(32);
|
||||
}
|
||||
|
||||
for (int j = 0; j < data.length; j++) {
|
||||
for (int i = 0; i < data[j].length; i++) {
|
||||
Rect rect = new Rect((i % cols[j]) * emojiFullSize, (i / cols[j]) * emojiFullSize, (i % cols[j] + 1) * emojiFullSize, (i / cols[j] + 1) * emojiFullSize);
|
||||
rects.put(data[j][i], new DrawableInfo(rect, (byte) j));
|
||||
}
|
||||
}
|
||||
placeholderPaint = new Paint();
|
||||
placeholderPaint.setColor(0x00000000);
|
||||
}
|
||||
|
||||
private static void loadEmoji(final int page) {
|
||||
try {
|
||||
float scale;
|
||||
int imageResize = 1;
|
||||
if (AndroidUtilities.density <= 1.0f) {
|
||||
scale = 2.0f;
|
||||
imageResize = 2;
|
||||
} else if (AndroidUtilities.density <= 1.5f) {
|
||||
scale = 3.0f;
|
||||
imageResize = 2;
|
||||
} else if (AndroidUtilities.density <= 2.0f) {
|
||||
scale = 2.0f;
|
||||
} else {
|
||||
scale = 3.0f;
|
||||
}
|
||||
|
||||
String imageName;
|
||||
File imageFile;
|
||||
|
||||
try {
|
||||
imageName = String.format(Locale.US, "emoji%.01fx_%d.jpg", scale, page);
|
||||
imageFile = ApplicationLoader.applicationContext.getFileStreamPath(imageName);
|
||||
if (imageFile.exists()) {
|
||||
imageFile.delete();
|
||||
}
|
||||
imageName = String.format(Locale.US, "emoji%.01fx_a_%d.jpg", scale, page);
|
||||
imageFile = ApplicationLoader.applicationContext.getFileStreamPath(imageName);
|
||||
if (imageFile.exists()) {
|
||||
imageFile.delete();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
|
||||
imageName = String.format(Locale.US, "v4_emoji%.01fx_%d.jpg", scale, page);
|
||||
imageFile = ApplicationLoader.applicationContext.getFileStreamPath(imageName);
|
||||
if (!imageFile.exists()) {
|
||||
InputStream is = ApplicationLoader.applicationContext.getAssets().open("emoji/" + imageName);
|
||||
AndroidUtilities.copyFile(is, imageFile);
|
||||
is.close();
|
||||
}
|
||||
|
||||
BitmapFactory.Options opts = new BitmapFactory.Options();
|
||||
opts.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeFile(imageFile.getAbsolutePath(), opts);
|
||||
|
||||
int width = opts.outWidth / imageResize;
|
||||
int height = opts.outHeight / imageResize;
|
||||
int stride = width * 4;
|
||||
|
||||
final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
Utilities.loadBitmap(imageFile.getAbsolutePath(), bitmap, imageResize, width, height, stride);
|
||||
|
||||
imageName = String.format(Locale.US, "v4_emoji%.01fx_a_%d.jpg", scale, page);
|
||||
imageFile = ApplicationLoader.applicationContext.getFileStreamPath(imageName);
|
||||
if (!imageFile.exists()) {
|
||||
InputStream is = ApplicationLoader.applicationContext.getAssets().open("emoji/" + imageName);
|
||||
AndroidUtilities.copyFile(is, imageFile);
|
||||
is.close();
|
||||
}
|
||||
|
||||
Utilities.loadBitmap(imageFile.getAbsolutePath(), bitmap, imageResize, width, height, stride);
|
||||
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
emojiBmp[page] = bitmap;
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.emojiDidLoaded);
|
||||
}
|
||||
});
|
||||
} catch (Throwable x) {
|
||||
FileLog.e("tmessages", "Error loading emoji", x);
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadEmojiAsync(final int page) {
|
||||
if (loadingEmoji[page]) {
|
||||
return;
|
||||
}
|
||||
loadingEmoji[page] = true;
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
loadEmoji(page);
|
||||
loadingEmoji[page] = false;
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public static void invalidateAll(View view) {
|
||||
if (view instanceof ViewGroup) {
|
||||
ViewGroup g = (ViewGroup) view;
|
||||
for (int i = 0; i < g.getChildCount(); i++) {
|
||||
invalidateAll(g.getChildAt(i));
|
||||
}
|
||||
} else if (view instanceof TextView) {
|
||||
view.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static EmojiDrawable getEmojiDrawable(long code) {
|
||||
DrawableInfo info = rects.get(code);
|
||||
if (info == null) {
|
||||
FileLog.e("tmessages", "No emoji drawable for code " + String.format("%016X", code));
|
||||
return null;
|
||||
}
|
||||
EmojiDrawable ed = new EmojiDrawable(info);
|
||||
ed.setBounds(0, 0, drawImgSize, drawImgSize);
|
||||
return ed;
|
||||
}
|
||||
|
||||
public static Drawable getEmojiBigDrawable(long code) {
|
||||
EmojiDrawable ed = getEmojiDrawable(code);
|
||||
if (ed == null) {
|
||||
return null;
|
||||
}
|
||||
ed.setBounds(0, 0, bigImgSize, bigImgSize);
|
||||
ed.fullSize = true;
|
||||
return ed;
|
||||
}
|
||||
|
||||
public static class EmojiDrawable extends Drawable {
|
||||
private DrawableInfo info;
|
||||
private boolean fullSize = false;
|
||||
private static Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
public EmojiDrawable(DrawableInfo i) {
|
||||
info = i;
|
||||
}
|
||||
|
||||
public DrawableInfo getDrawableInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
public Rect getDrawRect() {
|
||||
Rect b = copyBounds();
|
||||
int cX = b.centerX(), cY = b.centerY();
|
||||
b.left = cX - (fullSize ? bigImgSize : drawImgSize) / 2;
|
||||
b.right = cX + (fullSize ? bigImgSize : drawImgSize) / 2;
|
||||
b.top = cY - (fullSize ? bigImgSize : drawImgSize) / 2;
|
||||
b.bottom = cY + (fullSize ? bigImgSize : drawImgSize) / 2;
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
if (emojiBmp[info.page] == null) {
|
||||
loadEmojiAsync(info.page);
|
||||
canvas.drawRect(getBounds(), placeholderPaint);
|
||||
return;
|
||||
}
|
||||
Rect b;
|
||||
if (fullSize) {
|
||||
b = getDrawRect();
|
||||
} else {
|
||||
b = getBounds();
|
||||
}
|
||||
|
||||
if (!canvas.quickReject(b.left, b.top, b.right, b.bottom, Canvas.EdgeType.AA)) {
|
||||
canvas.drawBitmap(emojiBmp[info.page], info.rect, b, paint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static class DrawableInfo {
|
||||
public Rect rect;
|
||||
public byte page;
|
||||
|
||||
public DrawableInfo(Rect r, byte p) {
|
||||
rect = r;
|
||||
page = p;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean inArray(char c, char[] a) {
|
||||
for (char cc : a) {
|
||||
if (cc == c) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isNextCharIsColor(CharSequence cs, int i) {
|
||||
if (i + 2 >= cs.length()) {
|
||||
return false;
|
||||
}
|
||||
int value = cs.charAt(i + 1) << 16 | cs.charAt(i + 2);
|
||||
return value == 0xd83cdffb || value == 0xd83cdffc || value == 0xd83cdffd || value == 0xd83cdffe || value == 0xd83cdfff;
|
||||
}
|
||||
|
||||
public static CharSequence replaceEmoji(CharSequence cs, Paint.FontMetricsInt fontMetrics, int size, boolean createNew) {
|
||||
if (cs == null || cs.length() == 0) {
|
||||
return cs;
|
||||
}
|
||||
//SpannableStringLight.isFieldsAvailable();
|
||||
//SpannableStringLight s = new SpannableStringLight(cs.toString());
|
||||
Spannable s;
|
||||
if (!createNew && cs instanceof Spannable) {
|
||||
s = (Spannable) cs;
|
||||
} else {
|
||||
s = Spannable.Factory.getInstance().newSpannable(cs.toString());
|
||||
}
|
||||
// If showAndroidEmoji is enabled don't replace anything
|
||||
if (android.os.Build.VERSION.SDK_INT >= 19 && ApplicationLoader.SHOW_ANDROID_EMOJI) {
|
||||
return s;
|
||||
}
|
||||
long buf = 0;
|
||||
int emojiCount = 0;
|
||||
//s.setSpansCount(emojiCount);
|
||||
|
||||
try {
|
||||
for (int i = 0; i < cs.length(); i++) {
|
||||
char c = cs.charAt(i);
|
||||
if (c == 0xD83C || c == 0xD83D || (buf != 0 && (buf & 0xFFFFFFFF00000000L) == 0 && (c >= 0xDDE6 && c <= 0xDDFA))) {
|
||||
buf <<= 16;
|
||||
buf |= c;
|
||||
} else if (buf > 0 && (c & 0xF000) == 0xD000) {
|
||||
buf <<= 16;
|
||||
buf |= c;
|
||||
EmojiDrawable d = Emoji.getEmojiDrawable(buf);
|
||||
if (d != null) {
|
||||
boolean nextIsSkinTone = isNextCharIsColor(cs, i);
|
||||
EmojiSpan span = new EmojiSpan(d, DynamicDrawableSpan.ALIGN_BOTTOM, size, fontMetrics);
|
||||
if (c >= 0xDDE6 && c <= 0xDDFA) {
|
||||
s.setSpan(span, i - 3, i + (nextIsSkinTone ? 3 : 1), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
} else {
|
||||
s.setSpan(span, i - 1, i + (nextIsSkinTone ? 3 : 1), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
emojiCount++;
|
||||
if (nextIsSkinTone) {
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
buf = 0;
|
||||
} else if (c == 0x20E3) {
|
||||
if (i > 0) {
|
||||
char c2 = cs.charAt(i - 1);
|
||||
if ((c2 >= '0' && c2 <= '9') || c2 == '#') {
|
||||
buf = c2;
|
||||
buf <<= 16;
|
||||
buf |= c;
|
||||
EmojiDrawable d = Emoji.getEmojiDrawable(buf);
|
||||
if (d != null) {
|
||||
boolean nextIsSkinTone = isNextCharIsColor(cs, i);
|
||||
EmojiSpan span = new EmojiSpan(d, DynamicDrawableSpan.ALIGN_BOTTOM, size, fontMetrics);
|
||||
s.setSpan(span, i - 1, i + (nextIsSkinTone ? 3 : 1), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
emojiCount++;
|
||||
if (nextIsSkinTone) {
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
buf = 0;
|
||||
}
|
||||
}
|
||||
} else if (inArray(c, emojiChars)) {
|
||||
EmojiDrawable d = Emoji.getEmojiDrawable(c);
|
||||
if (d != null) {
|
||||
boolean nextIsSkinTone = isNextCharIsColor(cs, i);
|
||||
EmojiSpan span = new EmojiSpan(d, DynamicDrawableSpan.ALIGN_BOTTOM, size, fontMetrics);
|
||||
s.setSpan(span, i, i + (nextIsSkinTone ? 3 : 1), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
emojiCount++;
|
||||
if (nextIsSkinTone) {
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (emojiCount >= 50) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
return cs;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
public static class EmojiSpan extends ImageSpan {
|
||||
private Paint.FontMetricsInt fontMetrics = null;
|
||||
private int size = AndroidUtilities.dp(20);
|
||||
|
||||
public EmojiSpan(EmojiDrawable d, int verticalAlignment, int s, Paint.FontMetricsInt original) {
|
||||
super(d, verticalAlignment);
|
||||
fontMetrics = original;
|
||||
if (original != null) {
|
||||
size = Math.abs(fontMetrics.descent) + Math.abs(fontMetrics.ascent);
|
||||
if (size == 0) {
|
||||
size = AndroidUtilities.dp(20);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
|
||||
if (fm == null) {
|
||||
fm = new Paint.FontMetricsInt();
|
||||
}
|
||||
|
||||
if (fontMetrics == null) {
|
||||
int sz = super.getSize(paint, text, start, end, fm);
|
||||
|
||||
int offset = AndroidUtilities.dp(8);
|
||||
int w = AndroidUtilities.dp(10);
|
||||
fm.top = -w - offset;
|
||||
fm.bottom = w - offset;
|
||||
fm.ascent = -w - offset;
|
||||
fm.leading = 0;
|
||||
fm.descent = w - offset;
|
||||
|
||||
return sz;
|
||||
} else {
|
||||
if (fm != null) {
|
||||
fm.ascent = fontMetrics.ascent;
|
||||
fm.descent = fontMetrics.descent;
|
||||
|
||||
fm.top = fontMetrics.top;
|
||||
fm.bottom = fontMetrics.bottom;
|
||||
}
|
||||
if (getDrawable() != null) {
|
||||
getDrawable().setBounds(0, 0, size, size);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
/*
|
||||
* This is the source code of Telegram for Android v. 1.3.2.
|
||||
* It is licensed under GNU GPL v. 2 or later.
|
||||
* You should have received a copy of the license in this archive (see LICENSE).
|
||||
*
|
||||
* Copyright Nikolai Kudashov, 2013.
|
||||
*/
|
||||
|
||||
package org.telegram.android;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.telegram.messenger.ConnectionsManager;
|
||||
import org.telegram.messenger.FileLog;
|
||||
import org.telegram.messenger.ApplicationLoader;
|
||||
|
||||
public class GcmBroadcastReceiver extends BroadcastReceiver {
|
||||
|
||||
public static final int NOTIFICATION_ID = 1;
|
||||
|
||||
@Override
|
||||
public void onReceive(final Context context, final Intent intent) {
|
||||
FileLog.d("tmessages", "GCM received intent: " + intent);
|
||||
|
||||
if (intent.getAction().equals("com.google.android.c2dm.intent.RECEIVE")) {
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ApplicationLoader.postInitApplication();
|
||||
|
||||
try {
|
||||
String key = intent.getStringExtra("loc_key");
|
||||
if ("DC_UPDATE".equals(key)) {
|
||||
String data = intent.getStringExtra("custom");
|
||||
JSONObject object = new JSONObject(data);
|
||||
int dc = object.getInt("dc");
|
||||
String addr = object.getString("addr");
|
||||
String[] parts = addr.split(":");
|
||||
if (parts.length != 2) {
|
||||
return;
|
||||
}
|
||||
String ip = parts[0];
|
||||
int port = Integer.parseInt(parts[1]);
|
||||
ConnectionsManager.getInstance().applyDcPushUpdate(dc, ip, port);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
|
||||
ConnectionsManager.getInstance().resumeNetworkMaybe();
|
||||
}
|
||||
});
|
||||
} else if (intent.getAction().equals("com.google.android.c2dm.intent.REGISTRATION")) {
|
||||
String registration = intent.getStringExtra("registration_id");
|
||||
if (intent.getStringExtra("error") != null) {
|
||||
FileLog.e("tmessages", "Registration failed, should try again later.");
|
||||
} else if (intent.getStringExtra("unregistered") != null) {
|
||||
FileLog.e("tmessages", "unregistration done, new messages from the authorized sender will be rejected");
|
||||
} else if (registration != null) {
|
||||
FileLog.e("tmessages", "registration id = " + registration);
|
||||
}
|
||||
}
|
||||
|
||||
setResultCode(Activity.RESULT_OK);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,934 +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.
|
||||
*/
|
||||
|
||||
package org.telegram.android;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapShader;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Shader;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.View;
|
||||
|
||||
import org.telegram.messenger.TLObject;
|
||||
import org.telegram.messenger.TLRPC;
|
||||
import org.telegram.messenger.FileLog;
|
||||
import org.telegram.messenger.Utilities;
|
||||
|
||||
public class ImageReceiver implements NotificationCenter.NotificationCenterDelegate {
|
||||
|
||||
public interface ImageReceiverDelegate {
|
||||
void didSetImage(ImageReceiver imageReceiver, boolean set, boolean thumb);
|
||||
}
|
||||
|
||||
private class SetImageBackup {
|
||||
public TLObject fileLocation;
|
||||
public String httpUrl;
|
||||
public String filter;
|
||||
public Drawable thumb;
|
||||
public TLRPC.FileLocation thumbLocation;
|
||||
public String thumbFilter;
|
||||
public int size;
|
||||
public boolean cacheOnly;
|
||||
public String ext;
|
||||
}
|
||||
|
||||
private View parentView;
|
||||
private Integer tag;
|
||||
private Integer thumbTag;
|
||||
private MessageObject parentMessageObject;
|
||||
private boolean canceledLoading;
|
||||
|
||||
private SetImageBackup setImageBackup;
|
||||
|
||||
private TLObject currentImageLocation;
|
||||
private String currentKey;
|
||||
private String currentThumbKey;
|
||||
private String currentHttpUrl;
|
||||
private String currentFilter;
|
||||
private String currentThumbFilter;
|
||||
private String currentExt;
|
||||
private TLRPC.FileLocation currentThumbLocation;
|
||||
private int currentSize;
|
||||
private boolean currentCacheOnly;
|
||||
private BitmapDrawable currentImage;
|
||||
private BitmapDrawable currentThumb;
|
||||
private Drawable staticThumb;
|
||||
|
||||
private boolean needsQualityThumb;
|
||||
private boolean shouldGenerateQualityThumb;
|
||||
private boolean invalidateAll;
|
||||
|
||||
private int imageX, imageY, imageW, imageH;
|
||||
private Rect drawRegion = new Rect();
|
||||
private boolean isVisible = true;
|
||||
private boolean isAspectFit;
|
||||
private boolean forcePreview;
|
||||
private int roundRadius;
|
||||
private BitmapShader bitmapShader;
|
||||
private Paint roundPaint;
|
||||
private RectF roundRect;
|
||||
private RectF bitmapRect;
|
||||
private Matrix shaderMatrix;
|
||||
private float overrideAlpha = 1.0f;
|
||||
private boolean isPressed;
|
||||
private int orientation;
|
||||
private boolean centerRotation;
|
||||
private ImageReceiverDelegate delegate;
|
||||
private float currentAlpha;
|
||||
private long lastUpdateAlphaTime;
|
||||
private byte crossfadeAlpha = 1;
|
||||
private boolean crossfadeWithThumb;
|
||||
private ColorFilter colorFilter;
|
||||
|
||||
public ImageReceiver() {
|
||||
|
||||
}
|
||||
|
||||
public ImageReceiver(View view) {
|
||||
parentView = view;
|
||||
}
|
||||
|
||||
public void cancelLoadImage() {
|
||||
ImageLoader.getInstance().cancelLoadingForImageReceiver(this, 0);
|
||||
canceledLoading = true;
|
||||
}
|
||||
|
||||
public void setImage(TLObject path, String filter, Drawable thumb, String ext, boolean cacheOnly) {
|
||||
setImage(path, null, filter, thumb, null, null, 0, ext, cacheOnly);
|
||||
}
|
||||
|
||||
public void setImage(TLObject path, String filter, Drawable thumb, int size, String ext, boolean cacheOnly) {
|
||||
setImage(path, null, filter, thumb, null, null, size, ext, cacheOnly);
|
||||
}
|
||||
|
||||
public void setImage(String httpUrl, String filter, Drawable thumb, String ext, int size) {
|
||||
setImage(null, httpUrl, filter, thumb, null, null, size, ext, true);
|
||||
}
|
||||
|
||||
public void setImage(TLObject fileLocation, String filter, TLRPC.FileLocation thumbLocation, String thumbFilter, String ext, boolean cacheOnly) {
|
||||
setImage(fileLocation, null, filter, null, thumbLocation, thumbFilter, 0, ext, cacheOnly);
|
||||
}
|
||||
|
||||
public void setImage(TLObject fileLocation, String filter, TLRPC.FileLocation thumbLocation, String thumbFilter, int size, String ext, boolean cacheOnly) {
|
||||
setImage(fileLocation, null, filter, null, thumbLocation, thumbFilter, size, ext, cacheOnly);
|
||||
}
|
||||
|
||||
public void setImage(TLObject fileLocation, String httpUrl, String filter, Drawable thumb, TLRPC.FileLocation thumbLocation, String thumbFilter, int size, String ext, boolean cacheOnly) {
|
||||
if (setImageBackup != null) {
|
||||
setImageBackup.fileLocation = null;
|
||||
setImageBackup.httpUrl = null;
|
||||
setImageBackup.thumbLocation = null;
|
||||
setImageBackup.thumb = null;
|
||||
}
|
||||
|
||||
if ((fileLocation == null && httpUrl == null && thumbLocation == null)
|
||||
|| (fileLocation != null && !(fileLocation instanceof TLRPC.TL_fileLocation)
|
||||
&& !(fileLocation instanceof TLRPC.TL_fileEncryptedLocation)
|
||||
&& !(fileLocation instanceof TLRPC.TL_document))) {
|
||||
recycleBitmap(null, false);
|
||||
recycleBitmap(null, true);
|
||||
currentKey = null;
|
||||
currentExt = ext;
|
||||
currentThumbKey = null;
|
||||
currentThumbFilter = null;
|
||||
currentImageLocation = null;
|
||||
currentHttpUrl = null;
|
||||
currentFilter = null;
|
||||
currentCacheOnly = false;
|
||||
staticThumb = thumb;
|
||||
currentAlpha = 1;
|
||||
currentThumbLocation = null;
|
||||
currentSize = 0;
|
||||
currentImage = null;
|
||||
bitmapShader = null;
|
||||
ImageLoader.getInstance().cancelLoadingForImageReceiver(this, 0);
|
||||
if (parentView != null) {
|
||||
if (invalidateAll) {
|
||||
parentView.invalidate();
|
||||
} else {
|
||||
parentView.invalidate(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||
}
|
||||
}
|
||||
if (delegate != null) {
|
||||
delegate.didSetImage(this, currentImage != null || currentThumb != null || staticThumb != null, currentImage == null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (!(thumbLocation instanceof TLRPC.TL_fileLocation)) {
|
||||
thumbLocation = null;
|
||||
}
|
||||
|
||||
String key = null;
|
||||
if (fileLocation != null) {
|
||||
if (fileLocation instanceof TLRPC.FileLocation) {
|
||||
TLRPC.FileLocation location = (TLRPC.FileLocation) fileLocation;
|
||||
key = location.volume_id + "_" + location.local_id;
|
||||
} else if (fileLocation instanceof TLRPC.Document) {
|
||||
TLRPC.Document location = (TLRPC.Document) fileLocation;
|
||||
key = location.dc_id + "_" + location.id;
|
||||
}
|
||||
} else if (httpUrl != null) {
|
||||
key = Utilities.MD5(httpUrl);
|
||||
}
|
||||
if (key != null) {
|
||||
if (filter != null) {
|
||||
key += "@" + filter;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentKey != null && key != null && currentKey.equals(key)) {
|
||||
if (delegate != null) {
|
||||
delegate.didSetImage(this, currentImage != null || currentThumb != null || staticThumb != null, currentImage == null);
|
||||
}
|
||||
if (!canceledLoading && !forcePreview) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
String thumbKey = null;
|
||||
if (thumbLocation != null) {
|
||||
thumbKey = thumbLocation.volume_id + "_" + thumbLocation.local_id;
|
||||
if (thumbFilter != null) {
|
||||
thumbKey += "@" + thumbFilter;
|
||||
}
|
||||
}
|
||||
|
||||
recycleBitmap(key, false);
|
||||
recycleBitmap(thumbKey, true);
|
||||
|
||||
currentThumbKey = thumbKey;
|
||||
currentKey = key;
|
||||
currentExt = ext;
|
||||
currentImageLocation = fileLocation;
|
||||
currentHttpUrl = httpUrl;
|
||||
currentFilter = filter;
|
||||
currentThumbFilter = thumbFilter;
|
||||
currentSize = size;
|
||||
currentCacheOnly = cacheOnly;
|
||||
currentThumbLocation = thumbLocation;
|
||||
staticThumb = thumb;
|
||||
bitmapShader = null;
|
||||
currentAlpha = 1.0f;
|
||||
|
||||
if (delegate != null) {
|
||||
delegate.didSetImage(this, currentImage != null || currentThumb != null || staticThumb != null, currentImage == null);
|
||||
}
|
||||
|
||||
ImageLoader.getInstance().loadImageForImageReceiver(this);
|
||||
if (parentView != null) {
|
||||
if (invalidateAll) {
|
||||
parentView.invalidate();
|
||||
} else {
|
||||
parentView.invalidate(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setColorFilter(ColorFilter filter) {
|
||||
colorFilter = filter;
|
||||
}
|
||||
|
||||
public void setDelegate(ImageReceiverDelegate delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
public void setPressed(boolean value) {
|
||||
isPressed = value;
|
||||
}
|
||||
|
||||
public boolean getPressed() {
|
||||
return isPressed;
|
||||
}
|
||||
|
||||
public void setOrientation(int angle, boolean center) {
|
||||
orientation = angle;
|
||||
centerRotation = center;
|
||||
}
|
||||
|
||||
public void setInvalidateAll(boolean value) {
|
||||
invalidateAll = value;
|
||||
}
|
||||
|
||||
public int getOrientation() {
|
||||
return orientation;
|
||||
}
|
||||
|
||||
public void setImageBitmap(Bitmap bitmap) {
|
||||
setImageBitmap(bitmap != null ? new BitmapDrawable(null, bitmap) : null);
|
||||
}
|
||||
|
||||
public void setImageBitmap(Drawable bitmap) {
|
||||
ImageLoader.getInstance().cancelLoadingForImageReceiver(this, 0);
|
||||
recycleBitmap(null, false);
|
||||
recycleBitmap(null, true);
|
||||
staticThumb = bitmap;
|
||||
currentThumbLocation = null;
|
||||
currentKey = null;
|
||||
currentExt = null;
|
||||
currentThumbKey = null;
|
||||
currentImage = null;
|
||||
currentThumbFilter = null;
|
||||
currentImageLocation = null;
|
||||
currentHttpUrl = null;
|
||||
currentFilter = null;
|
||||
currentSize = 0;
|
||||
currentCacheOnly = false;
|
||||
bitmapShader = null;
|
||||
if (setImageBackup != null) {
|
||||
setImageBackup.fileLocation = null;
|
||||
setImageBackup.httpUrl = null;
|
||||
setImageBackup.thumbLocation = null;
|
||||
setImageBackup.thumb = null;
|
||||
}
|
||||
currentAlpha = 1;
|
||||
if (delegate != null) {
|
||||
delegate.didSetImage(this, currentImage != null || currentThumb != null || staticThumb != null, currentImage == null);
|
||||
}
|
||||
if (parentView != null) {
|
||||
if (invalidateAll) {
|
||||
parentView.invalidate();
|
||||
} else {
|
||||
parentView.invalidate(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void clearImage() {
|
||||
recycleBitmap(null, false);
|
||||
recycleBitmap(null, true);
|
||||
if (needsQualityThumb) {
|
||||
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messageThumbGenerated);
|
||||
ImageLoader.getInstance().cancelLoadingForImageReceiver(this, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void onDetachedFromWindow() {
|
||||
if (currentImageLocation != null || currentHttpUrl != null || currentThumbLocation != null || staticThumb != null) {
|
||||
if (setImageBackup == null) {
|
||||
setImageBackup = new SetImageBackup();
|
||||
}
|
||||
setImageBackup.fileLocation = currentImageLocation;
|
||||
setImageBackup.httpUrl = currentHttpUrl;
|
||||
setImageBackup.filter = currentFilter;
|
||||
setImageBackup.thumb = staticThumb;
|
||||
setImageBackup.thumbLocation = currentThumbLocation;
|
||||
setImageBackup.thumbFilter = currentThumbFilter;
|
||||
setImageBackup.size = currentSize;
|
||||
setImageBackup.ext = currentExt;
|
||||
setImageBackup.cacheOnly = currentCacheOnly;
|
||||
}
|
||||
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReplacedPhotoInMemCache);
|
||||
clearImage();
|
||||
}
|
||||
|
||||
public boolean onAttachedToWindow() {
|
||||
NotificationCenter.getInstance().addObserver(this, NotificationCenter.didReplacedPhotoInMemCache);
|
||||
if (setImageBackup != null && (setImageBackup.fileLocation != null || setImageBackup.httpUrl != null || setImageBackup.thumbLocation != null || setImageBackup.thumb != null)) {
|
||||
setImage(setImageBackup.fileLocation, setImageBackup.httpUrl, setImageBackup.filter, setImageBackup.thumb, setImageBackup.thumbLocation, setImageBackup.thumbFilter, setImageBackup.size, setImageBackup.ext, setImageBackup.cacheOnly);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void drawDrawable(Canvas canvas, Drawable drawable, int alpha) {
|
||||
if (drawable instanceof BitmapDrawable) {
|
||||
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
|
||||
|
||||
Paint paint = bitmapDrawable.getPaint();
|
||||
boolean hasFilter = paint != null && paint.getColorFilter() != null;
|
||||
if (hasFilter && !isPressed) {
|
||||
bitmapDrawable.setColorFilter(null);
|
||||
} else if (!hasFilter && isPressed) {
|
||||
bitmapDrawable.setColorFilter(new PorterDuffColorFilter(0xffdddddd, PorterDuff.Mode.MULTIPLY));
|
||||
}
|
||||
if (colorFilter != null) {
|
||||
bitmapDrawable.setColorFilter(colorFilter);
|
||||
}
|
||||
if (bitmapShader != null) {
|
||||
drawRegion.set(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||
if (isVisible) {
|
||||
roundRect.set(drawRegion);
|
||||
shaderMatrix.reset();
|
||||
shaderMatrix.setRectToRect(bitmapRect, roundRect, Matrix.ScaleToFit.FILL);
|
||||
bitmapShader.setLocalMatrix(shaderMatrix);
|
||||
roundPaint.setAlpha(alpha);
|
||||
canvas.drawRoundRect(roundRect, roundRadius, roundRadius, roundPaint);
|
||||
}
|
||||
} else {
|
||||
int bitmapW;
|
||||
int bitmapH;
|
||||
if (orientation == 90 || orientation == 270) {
|
||||
bitmapW = bitmapDrawable.getIntrinsicHeight();
|
||||
bitmapH = bitmapDrawable.getIntrinsicWidth();
|
||||
} else {
|
||||
bitmapW = bitmapDrawable.getIntrinsicWidth();
|
||||
bitmapH = bitmapDrawable.getIntrinsicHeight();
|
||||
}
|
||||
float scaleW = bitmapW / (float) imageW;
|
||||
float scaleH = bitmapH / (float) imageH;
|
||||
|
||||
if (isAspectFit) {
|
||||
float scale = Math.max(scaleW, scaleH);
|
||||
canvas.save();
|
||||
bitmapW /= scale;
|
||||
bitmapH /= scale;
|
||||
drawRegion.set(imageX + (imageW - bitmapW) / 2, imageY + (imageH - bitmapH) / 2, imageX + (imageW + bitmapW) / 2, imageY + (imageH + bitmapH) / 2);
|
||||
bitmapDrawable.setBounds(drawRegion);
|
||||
try {
|
||||
bitmapDrawable.setAlpha(alpha);
|
||||
bitmapDrawable.draw(canvas);
|
||||
} catch (Exception e) {
|
||||
if (bitmapDrawable == currentImage && currentKey != null) {
|
||||
ImageLoader.getInstance().removeImage(currentKey);
|
||||
currentKey = null;
|
||||
} else if (bitmapDrawable == currentThumb && currentThumbKey != null) {
|
||||
ImageLoader.getInstance().removeImage(currentThumbKey);
|
||||
currentThumbKey = null;
|
||||
}
|
||||
setImage(currentImageLocation, currentHttpUrl, currentFilter, currentThumb, currentThumbLocation, currentThumbFilter, currentSize, currentExt, currentCacheOnly);
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
canvas.restore();
|
||||
} else {
|
||||
if (Math.abs(scaleW - scaleH) > 0.00001f) {
|
||||
canvas.save();
|
||||
canvas.clipRect(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||
|
||||
if (orientation != 0) {
|
||||
if (centerRotation) {
|
||||
canvas.rotate(orientation, imageW / 2, imageH / 2);
|
||||
} else {
|
||||
canvas.rotate(orientation, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (bitmapW / scaleH > imageW) {
|
||||
bitmapW /= scaleH;
|
||||
drawRegion.set(imageX - (bitmapW - imageW) / 2, imageY, imageX + (bitmapW + imageW) / 2, imageY + imageH);
|
||||
} else {
|
||||
bitmapH /= scaleW;
|
||||
drawRegion.set(imageX, imageY - (bitmapH - imageH) / 2, imageX + imageW, imageY + (bitmapH + imageH) / 2);
|
||||
}
|
||||
if (orientation == 90 || orientation == 270) {
|
||||
int width = (drawRegion.right - drawRegion.left) / 2;
|
||||
int height = (drawRegion.bottom - drawRegion.top) / 2;
|
||||
int centerX = (drawRegion.right + drawRegion.left) / 2;
|
||||
int centerY = (drawRegion.top + drawRegion.bottom) / 2;
|
||||
bitmapDrawable.setBounds(centerX - height, centerY - width, centerX + height, centerY + width);
|
||||
} else {
|
||||
bitmapDrawable.setBounds(drawRegion);
|
||||
}
|
||||
if (isVisible) {
|
||||
try {
|
||||
bitmapDrawable.setAlpha(alpha);
|
||||
bitmapDrawable.draw(canvas);
|
||||
} catch (Exception e) {
|
||||
if (bitmapDrawable == currentImage && currentKey != null) {
|
||||
ImageLoader.getInstance().removeImage(currentKey);
|
||||
currentKey = null;
|
||||
} else if (bitmapDrawable == currentThumb && currentThumbKey != null) {
|
||||
ImageLoader.getInstance().removeImage(currentThumbKey);
|
||||
currentThumbKey = null;
|
||||
}
|
||||
setImage(currentImageLocation, currentHttpUrl, currentFilter, currentThumb, currentThumbLocation, currentThumbFilter, currentSize, currentExt, currentCacheOnly);
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
|
||||
canvas.restore();
|
||||
} else {
|
||||
canvas.save();
|
||||
if (orientation != 0) {
|
||||
if (centerRotation) {
|
||||
canvas.rotate(orientation, imageW / 2, imageH / 2);
|
||||
} else {
|
||||
canvas.rotate(orientation, 0, 0);
|
||||
}
|
||||
}
|
||||
drawRegion.set(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||
if (orientation == 90 || orientation == 270) {
|
||||
int width = (drawRegion.right - drawRegion.left) / 2;
|
||||
int height = (drawRegion.bottom - drawRegion.top) / 2;
|
||||
int centerX = (drawRegion.right + drawRegion.left) / 2;
|
||||
int centerY = (drawRegion.top + drawRegion.bottom) / 2;
|
||||
bitmapDrawable.setBounds(centerX - height, centerY - width, centerX + height, centerY + width);
|
||||
} else {
|
||||
bitmapDrawable.setBounds(drawRegion);
|
||||
}
|
||||
if (isVisible) {
|
||||
try {
|
||||
bitmapDrawable.setAlpha(alpha);
|
||||
bitmapDrawable.draw(canvas);
|
||||
} catch (Exception e) {
|
||||
if (bitmapDrawable == currentImage && currentKey != null) {
|
||||
ImageLoader.getInstance().removeImage(currentKey);
|
||||
currentKey = null;
|
||||
} else if (bitmapDrawable == currentThumb && currentThumbKey != null) {
|
||||
ImageLoader.getInstance().removeImage(currentThumbKey);
|
||||
currentThumbKey = null;
|
||||
}
|
||||
setImage(currentImageLocation, currentHttpUrl, currentFilter, currentThumb, currentThumbLocation, currentThumbFilter, currentSize, currentExt, currentCacheOnly);
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
drawRegion.set(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||
drawable.setBounds(drawRegion);
|
||||
if (isVisible) {
|
||||
try {
|
||||
drawable.setAlpha(alpha);
|
||||
drawable.draw(canvas);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkAlphaAnimation() {
|
||||
if (currentAlpha != 1) {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
currentAlpha += (currentTime - lastUpdateAlphaTime) / 150.0f;
|
||||
if (currentAlpha > 1) {
|
||||
currentAlpha = 1;
|
||||
}
|
||||
lastUpdateAlphaTime = System.currentTimeMillis();
|
||||
if (parentView != null) {
|
||||
if (invalidateAll) {
|
||||
parentView.invalidate();
|
||||
} else {
|
||||
parentView.invalidate(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean draw(Canvas canvas) {
|
||||
try {
|
||||
BitmapDrawable bitmapDrawable = null;
|
||||
if (!forcePreview && currentImage != null) {
|
||||
bitmapDrawable = currentImage;
|
||||
} else if (staticThumb instanceof BitmapDrawable) {
|
||||
bitmapDrawable = (BitmapDrawable) staticThumb;
|
||||
} else if (currentThumb != null) {
|
||||
bitmapDrawable = currentThumb;
|
||||
}
|
||||
if (bitmapDrawable != null) {
|
||||
if (crossfadeAlpha != 0) {
|
||||
if (crossfadeWithThumb && currentAlpha != 1.0f) {
|
||||
Drawable thumbDrawable = null;
|
||||
if (bitmapDrawable == currentImage) {
|
||||
if (staticThumb != null) {
|
||||
thumbDrawable = staticThumb;
|
||||
} else if (currentThumb != null) {
|
||||
thumbDrawable = currentThumb;
|
||||
}
|
||||
} else if (bitmapDrawable == currentThumb) {
|
||||
if (staticThumb != null) {
|
||||
thumbDrawable = staticThumb;
|
||||
}
|
||||
}
|
||||
if (thumbDrawable != null) {
|
||||
drawDrawable(canvas, thumbDrawable, (int) (overrideAlpha * 255));
|
||||
}
|
||||
}
|
||||
drawDrawable(canvas, bitmapDrawable, (int) (overrideAlpha * currentAlpha * 255));
|
||||
} else {
|
||||
drawDrawable(canvas, bitmapDrawable, (int) (overrideAlpha * 255));
|
||||
}
|
||||
|
||||
checkAlphaAnimation();
|
||||
return true;
|
||||
} else if (staticThumb != null) {
|
||||
drawDrawable(canvas, staticThumb, 255);
|
||||
checkAlphaAnimation();
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Bitmap getBitmap() {
|
||||
if (currentImage != null) {
|
||||
return currentImage.getBitmap();
|
||||
} else if (currentThumb != null) {
|
||||
return currentThumb.getBitmap();
|
||||
} else if (staticThumb instanceof BitmapDrawable) {
|
||||
return ((BitmapDrawable) staticThumb).getBitmap();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getBitmapWidth() {
|
||||
Bitmap bitmap = getBitmap();
|
||||
return orientation == 0 || orientation == 180 ? bitmap.getWidth() : bitmap.getHeight();
|
||||
}
|
||||
|
||||
public int getBitmapHeight() {
|
||||
Bitmap bitmap = getBitmap();
|
||||
return orientation == 0 || orientation == 180 ? bitmap.getHeight() : bitmap.getWidth();
|
||||
}
|
||||
|
||||
public void setVisible(boolean value, boolean invalidate) {
|
||||
if (isVisible == value) {
|
||||
return;
|
||||
}
|
||||
isVisible = value;
|
||||
if (invalidate && parentView != null) {
|
||||
if (invalidateAll) {
|
||||
parentView.invalidate();
|
||||
} else {
|
||||
parentView.invalidate(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getVisible() {
|
||||
return isVisible;
|
||||
}
|
||||
|
||||
public void setAlpha(float value) {
|
||||
overrideAlpha = value;
|
||||
}
|
||||
|
||||
public void setCrossfadeAlpha(byte value) {
|
||||
crossfadeAlpha = value;
|
||||
}
|
||||
|
||||
public boolean hasImage() {
|
||||
return currentImage != null || currentThumb != null || currentKey != null || currentHttpUrl != null || staticThumb != null;
|
||||
}
|
||||
|
||||
public void setAspectFit(boolean value) {
|
||||
isAspectFit = value;
|
||||
}
|
||||
|
||||
public void setParentView(View view) {
|
||||
parentView = view;
|
||||
}
|
||||
|
||||
public void setImageCoords(int x, int y, int width, int height) {
|
||||
imageX = x;
|
||||
imageY = y;
|
||||
imageW = width;
|
||||
imageH = height;
|
||||
}
|
||||
|
||||
public int getImageX() {
|
||||
return imageX;
|
||||
}
|
||||
|
||||
public int getImageX2() {
|
||||
return imageX + imageW;
|
||||
}
|
||||
|
||||
public int getImageY() {
|
||||
return imageY;
|
||||
}
|
||||
|
||||
public int getImageY2() {
|
||||
return imageY + imageH;
|
||||
}
|
||||
|
||||
public int getImageWidth() {
|
||||
return imageW;
|
||||
}
|
||||
|
||||
public int getImageHeight() {
|
||||
return imageH;
|
||||
}
|
||||
|
||||
public String getExt() {
|
||||
return currentExt;
|
||||
}
|
||||
|
||||
public boolean isInsideImage(float x, float y) {
|
||||
return x >= imageX && x <= imageX + imageW && y >= imageY && y <= imageY + imageH;
|
||||
}
|
||||
|
||||
public Rect getDrawRegion() {
|
||||
return drawRegion;
|
||||
}
|
||||
|
||||
public String getFilter() {
|
||||
return currentFilter;
|
||||
}
|
||||
|
||||
public String getThumbFilter() {
|
||||
return currentThumbFilter;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return currentKey;
|
||||
}
|
||||
|
||||
public String getThumbKey() {
|
||||
return currentThumbKey;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return currentSize;
|
||||
}
|
||||
|
||||
public TLObject getImageLocation() {
|
||||
return currentImageLocation;
|
||||
}
|
||||
|
||||
public TLRPC.FileLocation getThumbLocation() {
|
||||
return currentThumbLocation;
|
||||
}
|
||||
|
||||
public String getHttpImageLocation() {
|
||||
return currentHttpUrl;
|
||||
}
|
||||
|
||||
public boolean getCacheOnly() {
|
||||
return currentCacheOnly;
|
||||
}
|
||||
|
||||
public void setForcePreview(boolean value) {
|
||||
forcePreview = value;
|
||||
}
|
||||
|
||||
public boolean isForcePreview() {
|
||||
return forcePreview;
|
||||
}
|
||||
|
||||
public void setRoundRadius(int value) {
|
||||
roundRadius = value;
|
||||
if (roundRadius != 0) {
|
||||
if (roundPaint == null) {
|
||||
roundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
roundRect = new RectF();
|
||||
shaderMatrix = new Matrix();
|
||||
bitmapRect = new RectF();
|
||||
}
|
||||
} else {
|
||||
roundPaint = null;
|
||||
roundRect = null;
|
||||
shaderMatrix = null;
|
||||
bitmapRect = null;
|
||||
}
|
||||
}
|
||||
|
||||
public int getRoundRadius() {
|
||||
return roundRadius;
|
||||
}
|
||||
|
||||
public void setParentMessageObject(MessageObject messageObject) {
|
||||
parentMessageObject = messageObject;
|
||||
}
|
||||
|
||||
public MessageObject getParentMessageObject() {
|
||||
return parentMessageObject;
|
||||
}
|
||||
|
||||
public void setNeedsQualityThumb(boolean value) {
|
||||
needsQualityThumb = value;
|
||||
if (needsQualityThumb) {
|
||||
NotificationCenter.getInstance().addObserver(this, NotificationCenter.messageThumbGenerated);
|
||||
} else {
|
||||
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messageThumbGenerated);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isNeedsQualityThumb() {
|
||||
return needsQualityThumb;
|
||||
}
|
||||
|
||||
public void setShouldGenerateQualityThumb(boolean value) {
|
||||
shouldGenerateQualityThumb = value;
|
||||
}
|
||||
|
||||
public boolean isShouldGenerateQualityThumb() {
|
||||
return shouldGenerateQualityThumb;
|
||||
}
|
||||
|
||||
protected Integer getTag(boolean thumb) {
|
||||
if (thumb) {
|
||||
return thumbTag;
|
||||
} else {
|
||||
return tag;
|
||||
}
|
||||
}
|
||||
|
||||
protected void setTag(Integer value, boolean thumb) {
|
||||
if (thumb) {
|
||||
thumbTag = value;
|
||||
} else {
|
||||
tag = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected void setImageBitmapByKey(BitmapDrawable bitmap, String key, boolean thumb, boolean memCache) {
|
||||
if (bitmap == null || key == null) {
|
||||
return;
|
||||
}
|
||||
if (!thumb) {
|
||||
if (currentKey == null || !key.equals(currentKey)) {
|
||||
return;
|
||||
}
|
||||
ImageLoader.getInstance().incrementUseCount(currentKey);
|
||||
currentImage = bitmap;
|
||||
if (roundRadius != 0 && bitmap instanceof BitmapDrawable) {
|
||||
Bitmap object = bitmap.getBitmap();
|
||||
bitmapShader = new BitmapShader(object, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
|
||||
roundPaint.setShader(bitmapShader);
|
||||
bitmapRect.set(0, 0, object.getWidth(), object.getHeight());
|
||||
}
|
||||
|
||||
if (!memCache && !forcePreview) {
|
||||
if (currentThumb == null && staticThumb == null || currentAlpha == 1.0f) {
|
||||
currentAlpha = 0.0f;
|
||||
lastUpdateAlphaTime = System.currentTimeMillis();
|
||||
crossfadeWithThumb = currentThumb != null || staticThumb != null;
|
||||
}
|
||||
} else {
|
||||
currentAlpha = 1.0f;
|
||||
}
|
||||
|
||||
if (parentView != null) {
|
||||
if (invalidateAll) {
|
||||
parentView.invalidate();
|
||||
} else {
|
||||
parentView.invalidate(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||
}
|
||||
}
|
||||
} else if (currentThumb == null && (currentImage == null || forcePreview)) {
|
||||
if (currentThumbKey == null || !key.equals(currentThumbKey)) {
|
||||
return;
|
||||
}
|
||||
ImageLoader.getInstance().incrementUseCount(currentThumbKey);
|
||||
|
||||
currentThumb = bitmap;
|
||||
|
||||
if (!memCache && crossfadeAlpha != 2) {
|
||||
currentAlpha = 0.0f;
|
||||
lastUpdateAlphaTime = System.currentTimeMillis();
|
||||
crossfadeWithThumb = staticThumb != null && currentKey == null;
|
||||
} else {
|
||||
currentAlpha = 1.0f;
|
||||
}
|
||||
|
||||
if (!(staticThumb instanceof BitmapDrawable) && parentView != null) {
|
||||
if (invalidateAll) {
|
||||
parentView.invalidate();
|
||||
} else {
|
||||
parentView.invalidate(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (delegate != null) {
|
||||
delegate.didSetImage(this, currentImage != null || currentThumb != null || staticThumb != null, currentImage == null);
|
||||
}
|
||||
}
|
||||
|
||||
private void recycleBitmap(String newKey, boolean thumb) {
|
||||
String key;
|
||||
BitmapDrawable image;
|
||||
if (thumb) {
|
||||
if (currentThumb == null) {
|
||||
return;
|
||||
}
|
||||
key = currentThumbKey;
|
||||
image = currentThumb;
|
||||
} else {
|
||||
if (currentImage == null) {
|
||||
return;
|
||||
}
|
||||
key = currentKey;
|
||||
image = currentImage;
|
||||
}
|
||||
BitmapDrawable newBitmap = null;
|
||||
if (newKey != null) {
|
||||
newBitmap = ImageLoader.getInstance().getImageFromMemory(newKey);
|
||||
}
|
||||
if (key == null || image == null || image == newBitmap) {
|
||||
return;
|
||||
}
|
||||
Bitmap bitmap = image.getBitmap();
|
||||
boolean canDelete = ImageLoader.getInstance().decrementUseCount(key);
|
||||
if (!ImageLoader.getInstance().isInCache(key)) {
|
||||
if (ImageLoader.getInstance().runtimeHack != null) {
|
||||
ImageLoader.getInstance().runtimeHack.trackAlloc(bitmap.getRowBytes() * bitmap.getHeight());
|
||||
}
|
||||
if (canDelete) {
|
||||
bitmap.recycle();
|
||||
}
|
||||
}
|
||||
if (thumb) {
|
||||
currentThumb = null;
|
||||
currentThumbKey = null;
|
||||
} else {
|
||||
currentImage = null;
|
||||
currentKey = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didReceivedNotification(int id, Object... args) {
|
||||
if (id == NotificationCenter.messageThumbGenerated) {
|
||||
String key = (String) args[1];
|
||||
if (currentThumbKey != null && currentThumbKey.equals(key)) {
|
||||
if (currentThumb == null) {
|
||||
ImageLoader.getInstance().incrementUseCount(currentThumbKey);
|
||||
}
|
||||
currentThumb = (BitmapDrawable) args[0];
|
||||
if (staticThumb instanceof BitmapDrawable) {
|
||||
staticThumb = null;
|
||||
}
|
||||
if (parentView != null) {
|
||||
if (invalidateAll) {
|
||||
parentView.invalidate();
|
||||
} else {
|
||||
parentView.invalidate(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (id == NotificationCenter.didReplacedPhotoInMemCache) {
|
||||
String oldKey = (String) args[0];
|
||||
if (currentKey != null && currentKey.equals(oldKey)) {
|
||||
currentKey = (String) args[1];
|
||||
currentImageLocation = (TLRPC.FileLocation) args[2];
|
||||
}
|
||||
if (currentThumbKey != null && currentThumbKey.equals(oldKey)) {
|
||||
currentThumbKey = (String) args[1];
|
||||
currentThumbLocation = (TLRPC.FileLocation) args[2];
|
||||
}
|
||||
if (setImageBackup != null) {
|
||||
if (currentKey != null && currentKey.equals(oldKey)) {
|
||||
currentKey = (String) args[1];
|
||||
currentImageLocation = (TLRPC.FileLocation) args[2];
|
||||
}
|
||||
if (currentThumbKey != null && currentThumbKey.equals(oldKey)) {
|
||||
currentThumbKey = (String) args[1];
|
||||
currentThumbLocation = (TLRPC.FileLocation) args[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,253 +0,0 @@
|
||||
/*
|
||||
* This is the source code of Telegram for Android v. 1.3.2.
|
||||
* It is licensed under GNU GPL v. 2 or later.
|
||||
* You should have received a copy of the license in this archive (see LICENSE).
|
||||
*
|
||||
* Copyright Nikolai Kudashov, 2013.
|
||||
*/
|
||||
|
||||
package org.telegram.android;
|
||||
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
/**
|
||||
* Static library version of {@link android.util.LruCache}. Used to write apps
|
||||
* that run on API levels prior to 12. When running on API level 12 or above,
|
||||
* this implementation is still used; it does not try to switch to the
|
||||
* framework's implementation. See the framework SDK documentation for a class
|
||||
* overview.
|
||||
*/
|
||||
public class LruCache {
|
||||
private final LinkedHashMap<String, BitmapDrawable> map;
|
||||
private final LinkedHashMap<String, ArrayList<String>> mapFilters;
|
||||
|
||||
/** Size of this cache in units. Not necessarily the number of elements. */
|
||||
private int size;
|
||||
private int maxSize;
|
||||
|
||||
/**
|
||||
* @param maxSize for caches that do not override {@link #sizeOf}, this is
|
||||
* the maximum number of entries in the cache. For all other caches,
|
||||
* this is the maximum sum of the sizes of the entries in this cache.
|
||||
*/
|
||||
public LruCache(int maxSize) {
|
||||
if (maxSize <= 0) {
|
||||
throw new IllegalArgumentException("maxSize <= 0");
|
||||
}
|
||||
this.maxSize = maxSize;
|
||||
this.map = new LinkedHashMap<>(0, 0.75f, true);
|
||||
this.mapFilters = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value for {@code key} if it exists in the cache or can be
|
||||
* created by {@code #create}. If a value was returned, it is moved to the
|
||||
* head of the queue. This returns null if a value is not cached and cannot
|
||||
* be created.
|
||||
*/
|
||||
public final BitmapDrawable get(String key) {
|
||||
if (key == null) {
|
||||
throw new NullPointerException("key == null");
|
||||
}
|
||||
|
||||
BitmapDrawable mapValue;
|
||||
synchronized (this) {
|
||||
mapValue = map.get(key);
|
||||
if (mapValue != null) {
|
||||
return mapValue;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ArrayList<String> getFilterKeys(String key) {
|
||||
ArrayList<String> arr = mapFilters.get(key);
|
||||
if (arr != null) {
|
||||
return new ArrayList<>(arr);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches {@code value} for {@code key}. The value is moved to the head of
|
||||
* the queue.
|
||||
*
|
||||
* @return the previous value mapped by {@code key}.
|
||||
*/
|
||||
public BitmapDrawable put(String key, BitmapDrawable value) {
|
||||
if (key == null || value == null) {
|
||||
throw new NullPointerException("key == null || value == null");
|
||||
}
|
||||
|
||||
BitmapDrawable previous;
|
||||
synchronized (this) {
|
||||
size += safeSizeOf(key, value);
|
||||
previous = map.put(key, value);
|
||||
if (previous != null) {
|
||||
size -= safeSizeOf(key, previous);
|
||||
}
|
||||
}
|
||||
|
||||
String[] args = key.split("@");
|
||||
if (args.length > 1) {
|
||||
ArrayList<String> arr = mapFilters.get(args[0]);
|
||||
if (arr == null) {
|
||||
arr = new ArrayList<>();
|
||||
mapFilters.put(args[0], arr);
|
||||
}
|
||||
if (!arr.contains(args[1])) {
|
||||
arr.add(args[1]);
|
||||
}
|
||||
}
|
||||
|
||||
if (previous != null) {
|
||||
entryRemoved(false, key, previous, value);
|
||||
}
|
||||
|
||||
trimToSize(maxSize, key);
|
||||
return previous;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param maxSize the maximum size of the cache before returning. May be -1
|
||||
* to evict even 0-sized elements.
|
||||
*/
|
||||
private void trimToSize(int maxSize, String justAdded) {
|
||||
synchronized (this) {
|
||||
Iterator<HashMap.Entry<String, BitmapDrawable>> iterator = map.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
if (size <= maxSize || map.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
HashMap.Entry<String, BitmapDrawable> entry = iterator.next();
|
||||
|
||||
String key = entry.getKey();
|
||||
if (justAdded != null && justAdded.equals(key)) {
|
||||
continue;
|
||||
}
|
||||
BitmapDrawable value = entry.getValue();
|
||||
size -= safeSizeOf(key, value);
|
||||
iterator.remove();
|
||||
|
||||
String[] args = key.split("@");
|
||||
if (args.length > 1) {
|
||||
ArrayList<String> arr = mapFilters.get(args[0]);
|
||||
if (arr != null) {
|
||||
arr.remove(args[1]);
|
||||
if (arr.isEmpty()) {
|
||||
mapFilters.remove(args[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
entryRemoved(true, key, value, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the entry for {@code key} if it exists.
|
||||
*
|
||||
* @return the previous value mapped by {@code key}.
|
||||
*/
|
||||
public final BitmapDrawable remove(String key) {
|
||||
if (key == null) {
|
||||
throw new NullPointerException("key == null");
|
||||
}
|
||||
|
||||
BitmapDrawable previous;
|
||||
synchronized (this) {
|
||||
previous = map.remove(key);
|
||||
if (previous != null) {
|
||||
size -= safeSizeOf(key, previous);
|
||||
}
|
||||
}
|
||||
|
||||
if (previous != null) {
|
||||
String[] args = key.split("@");
|
||||
if (args.length > 1) {
|
||||
ArrayList<String> arr = mapFilters.get(args[0]);
|
||||
if (arr != null) {
|
||||
arr.remove(args[1]);
|
||||
if (arr.isEmpty()) {
|
||||
mapFilters.remove(args[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
entryRemoved(false, key, previous, null);
|
||||
}
|
||||
|
||||
return previous;
|
||||
}
|
||||
|
||||
public boolean contains(String key){
|
||||
return map.containsKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called for entries that have been evicted or removed. This method is
|
||||
* invoked when a value is evicted to make space, removed by a call to
|
||||
* {@link #remove}, or replaced by a call to {@link #put}. The default
|
||||
* implementation does nothing.
|
||||
*
|
||||
* <p>The method is called without synchronization: other threads may
|
||||
* access the cache while this method is executing.
|
||||
*
|
||||
* @param evicted true if the entry is being removed to make space, false
|
||||
* if the removal was caused by a {@link #put} or {@link #remove}.
|
||||
* @param newValue the new value for {@code key}, if it exists. If non-null,
|
||||
* this removal was caused by a {@link #put}. Otherwise it was caused by
|
||||
* an eviction or a {@link #remove}.
|
||||
*/
|
||||
protected void entryRemoved(boolean evicted, String key, BitmapDrawable oldValue, BitmapDrawable newValue) {}
|
||||
|
||||
private int safeSizeOf(String key, BitmapDrawable value) {
|
||||
int result = sizeOf(key, value);
|
||||
if (result < 0) {
|
||||
throw new IllegalStateException("Negative size: " + key + "=" + value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of the entry for {@code key} and {@code value} in
|
||||
* user-defined units. The default implementation returns 1 so that size
|
||||
* is the number of entries and max size is the maximum number of entries.
|
||||
*
|
||||
* <p>An entry's size must not change while it is in the cache.
|
||||
*/
|
||||
protected int sizeOf(String key, BitmapDrawable value) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cache, calling {@link #entryRemoved} on each removed entry.
|
||||
*/
|
||||
public final void evictAll() {
|
||||
trimToSize(-1, null); // -1 will evict 0-sized elements
|
||||
}
|
||||
|
||||
/**
|
||||
* For caches that do not override {@link #sizeOf}, this returns the number
|
||||
* of entries in the cache. For all other caches, this returns the sum of
|
||||
* the sizes of the entries in this cache.
|
||||
*/
|
||||
public synchronized final int size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* For caches that do not override {@link #sizeOf}, this returns the maximum
|
||||
* number of entries in the cache. For all other caches, this returns the
|
||||
* maximum sum of the sizes of the entries in this cache.
|
||||
*/
|
||||
public synchronized final int maxSize() {
|
||||
return maxSize;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,126 +0,0 @@
|
||||
package org.telegram.android;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
|
||||
import org.telegram.messenger.ApplicationLoader;
|
||||
import org.telegram.messenger.BuildConfig;
|
||||
import org.telegram.messenger.Utilities;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class ModuleContentProvider extends ContentProvider {
|
||||
private static final String TAG = "ModuleContentProvider";
|
||||
|
||||
private static final String AUTHORITY = "org.telegram.plus.android.provider.content";
|
||||
private static final String AUTHORITY_BETA = "org.telegram.plus.beta.android.provider.content";
|
||||
|
||||
public static Uri THEME_URI = Uri.parse("content://" + AUTHORITY + "/theme");
|
||||
public static Uri GET_NAME = Uri.parse("content://" + AUTHORITY + "/name");
|
||||
public static Uri SET_NAME = Uri.parse("content://" + AUTHORITY + "/newname");
|
||||
|
||||
/*private static final UriMatcher sUriMatcher;
|
||||
static {
|
||||
sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||
sUriMatcher.addURI(AUTHORITY, "theme", 1);
|
||||
sUriMatcher.addURI(AUTHORITY, "name", 2);
|
||||
}*/
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
Log.d(TAG, "onCreate");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String orderBy) {
|
||||
Log.d(TAG, "query with uri: " + uri.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
if(BuildConfig.DEBUG)GET_NAME = Uri.parse("content://" + AUTHORITY_BETA + "/name");
|
||||
if(uri.equals(GET_NAME)){
|
||||
SharedPreferences themePrefs = ApplicationLoader.applicationContext.getSharedPreferences("theme", Activity.MODE_PRIVATE);
|
||||
return themePrefs.getString("themeName","empty");
|
||||
}else{
|
||||
throw new IllegalArgumentException("Unknown URI " + uri);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
Log.d(TAG, "insert uri: " + uri.toString());
|
||||
if(BuildConfig.DEBUG)SET_NAME = Uri.parse("content://" + AUTHORITY_BETA + "/newname");
|
||||
if(uri.toString().contains(SET_NAME.toString())){
|
||||
String sName = uri.toString();
|
||||
sName = sName.substring(sName.lastIndexOf(":")+1, sName.length());
|
||||
SharedPreferences themePrefs = ApplicationLoader.applicationContext.getSharedPreferences("theme", AndroidUtilities.THEME_PREFS_MODE);
|
||||
SharedPreferences.Editor editor = themePrefs.edit();
|
||||
editor.putString("themeName", sName);
|
||||
editor.commit();
|
||||
}else{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
//Log.d(TAG, "update uri: " + uri.toString());
|
||||
if(BuildConfig.DEBUG)THEME_URI = Uri.parse("content://" + AUTHORITY_BETA + "/theme");
|
||||
if(uri.toString().contains(THEME_URI.toString())){
|
||||
String theme = uri.toString();
|
||||
theme = theme.substring(theme.lastIndexOf(":")+1, theme.length());
|
||||
//Log.d(TAG, theme);
|
||||
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
|
||||
File themeFile = new File(theme);
|
||||
if(themeFile.exists()){
|
||||
applyTheme(theme);
|
||||
return 10;
|
||||
}
|
||||
return 20;//theme doesn't exists
|
||||
}
|
||||
return 30;// MEDIA no mounted
|
||||
}else{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String where, String[] selectionArgs) {
|
||||
//Log.d(TAG, "delete uri: " + uri.toString());
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private void applyTheme(final String xmlFile){
|
||||
String sName = xmlFile.substring(0, xmlFile.lastIndexOf("."));
|
||||
String wName = sName + "_wallpaper.jpg";
|
||||
File wFile = new File(wName);
|
||||
if(wFile.exists()){
|
||||
//Change Stock Background to set Custom Wallpaper
|
||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE);
|
||||
int selectedBackground = preferences.getInt("selectedBackground", 1000001);
|
||||
if (selectedBackground == 1000001) {
|
||||
//File toFile = new File(ApplicationLoader.applicationContext.getFilesDir(), "wallpaper.jpg");
|
||||
//if (!toFile.exists()) {
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
editor.putInt("selectedBackground", 113);
|
||||
editor.putInt("selectedColor", 0);
|
||||
editor.commit();
|
||||
//}
|
||||
}
|
||||
}
|
||||
if(Utilities.loadPrefFromSD(ApplicationLoader.applicationContext, xmlFile) == 4){
|
||||
Utilities.loadWallpaperFromSDPath(ApplicationLoader.applicationContext, wName);
|
||||
AndroidUtilities.needRestart = true;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,202 +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-2014.
|
||||
*/
|
||||
|
||||
package org.telegram.android;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.os.Build;
|
||||
|
||||
import org.telegram.messenger.FileLog;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
public class NativeLoader {
|
||||
|
||||
private final static int LIB_VERSION = 8;
|
||||
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";
|
||||
|
||||
private static volatile boolean nativeLoaded = false;
|
||||
|
||||
private static File getNativeLibraryDir(Context context) {
|
||||
File f = null;
|
||||
if (context != null) {
|
||||
try {
|
||||
f = new File((String)ApplicationInfo.class.getField("nativeLibraryDir").get(context.getApplicationInfo()));
|
||||
} catch (Throwable th) {
|
||||
th.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (f == null) {
|
||||
f = new File(context.getApplicationInfo().dataDir, "lib");
|
||||
}
|
||||
if (f != null && f.isDirectory()) {
|
||||
return f;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean loadFromZip(Context context, File destDir, File destLocalFile, String folder) {
|
||||
try {
|
||||
for (File file : destDir.listFiles()) {
|
||||
file.delete();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
|
||||
ZipFile zipFile = null;
|
||||
InputStream stream = null;
|
||||
try {
|
||||
zipFile = new ZipFile(context.getApplicationInfo().sourceDir);
|
||||
ZipEntry entry = zipFile.getEntry("lib/" + folder + "/" + LIB_SO_NAME);
|
||||
if (entry == null) {
|
||||
throw new Exception("Unable to find file in apk:" + "lib/" + folder + "/" + LIB_NAME);
|
||||
}
|
||||
stream = zipFile.getInputStream(entry);
|
||||
|
||||
OutputStream out = new FileOutputStream(destLocalFile);
|
||||
byte[] buf = new byte[4096];
|
||||
int len;
|
||||
while ((len = stream.read(buf)) > 0) {
|
||||
Thread.yield();
|
||||
out.write(buf, 0, len);
|
||||
}
|
||||
out.close();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 9) {
|
||||
destLocalFile.setReadable(true, false);
|
||||
destLocalFile.setExecutable(true, false);
|
||||
destLocalFile.setWritable(true);
|
||||
}
|
||||
|
||||
try {
|
||||
System.load(destLocalFile.getAbsolutePath());
|
||||
nativeLoaded = true;
|
||||
} catch (Error e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
} finally {
|
||||
if (stream != null) {
|
||||
try {
|
||||
stream.close();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
if (zipFile != null) {
|
||||
try {
|
||||
zipFile.close();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static synchronized void initNativeLibs(Context context) {
|
||||
if (nativeLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
String folder = null;
|
||||
|
||||
try {
|
||||
if (Build.CPU_ABI.equalsIgnoreCase("armeabi-v7a")) {
|
||||
folder = "armeabi-v7a";
|
||||
} else if (Build.CPU_ABI.equalsIgnoreCase("armeabi")) {
|
||||
folder = "armeabi";
|
||||
} else if (Build.CPU_ABI.equalsIgnoreCase("x86")) {
|
||||
folder = "x86";
|
||||
} else if (Build.CPU_ABI.equalsIgnoreCase("mips")) {
|
||||
folder = "mips";
|
||||
} else {
|
||||
folder = "armeabi";
|
||||
FileLog.e("tmessages", "Unsupported arch: " + Build.CPU_ABI);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
folder = "armeabi";
|
||||
}
|
||||
|
||||
|
||||
String javaArch = System.getProperty("os.arch");
|
||||
if (javaArch != null && javaArch.contains("686")) {
|
||||
folder = "x86";
|
||||
}
|
||||
|
||||
File destFile = getNativeLibraryDir(context);
|
||||
if (destFile != null) {
|
||||
destFile = new File(destFile, LIB_SO_NAME);
|
||||
if (destFile.exists()) {
|
||||
FileLog.d("tmessages", "Load normal lib");
|
||||
try {
|
||||
System.loadLibrary(LIB_NAME);
|
||||
nativeLoaded = true;
|
||||
return;
|
||||
} catch (Error e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File destDir = new File(context.getFilesDir(), "lib");
|
||||
destDir.mkdirs();
|
||||
|
||||
File destLocalFile = new File(destDir, LOCALE_LIB_SO_NAME);
|
||||
if (destLocalFile != null && destLocalFile.exists()) {
|
||||
try {
|
||||
FileLog.d("tmessages", "Load local lib");
|
||||
System.load(destLocalFile.getAbsolutePath());
|
||||
nativeLoaded = true;
|
||||
return;
|
||||
} catch (Error e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
destLocalFile.delete();
|
||||
}
|
||||
|
||||
FileLog.e("tmessages", "Library not found, arch = " + folder);
|
||||
|
||||
if (loadFromZip(context, destDir, destLocalFile, folder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
folder = "x86";
|
||||
destLocalFile = new File(context.getFilesDir().getAbsolutePath() + "/libtmessages86.so");
|
||||
if (!loadFromZip(context, destLocalFile, folder)) {
|
||||
destLocalFile = new File(context.getFilesDir().getAbsolutePath() + "/libtmessagesarm.so");
|
||||
folder = "armeabi";
|
||||
loadFromZip(context, destLocalFile, folder);
|
||||
}
|
||||
*/
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
try {
|
||||
System.loadLibrary(LIB_NAME);
|
||||
nativeLoaded = true;
|
||||
} catch (Error e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,236 +0,0 @@
|
||||
/*
|
||||
* This is the source code of Telegram for Android v. 1.3.2.
|
||||
* It is licensed under GNU GPL v. 2 or later.
|
||||
* You should have received a copy of the license in this archive (see LICENSE).
|
||||
*
|
||||
* Copyright Nikolai Kudashov, 2013.
|
||||
*/
|
||||
|
||||
package org.telegram.android;
|
||||
|
||||
import org.telegram.messenger.ApplicationLoader;
|
||||
import org.telegram.messenger.BuildVars;
|
||||
import org.telegram.messenger.FileLog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class NotificationCenter {
|
||||
|
||||
private static int totalEvents = 1;
|
||||
|
||||
public static final int didReceivedNewMessages = totalEvents++;
|
||||
public static final int updateInterfaces = totalEvents++;
|
||||
public static final int dialogsNeedReload = totalEvents++;
|
||||
public static final int closeChats = totalEvents++;
|
||||
public static final int messagesDeleted = totalEvents++;
|
||||
public static final int messagesRead = totalEvents++;
|
||||
public static final int messagesDidLoaded = totalEvents++;
|
||||
public static final int messageReceivedByAck = totalEvents++;
|
||||
public static final int messageReceivedByServer = totalEvents++;
|
||||
public static final int messageSendError = totalEvents++;
|
||||
public static final int contactsDidLoaded = totalEvents++;
|
||||
public static final int chatDidCreated = totalEvents++;
|
||||
public static final int chatDidFailCreate = totalEvents++;
|
||||
public static final int chatInfoDidLoaded = totalEvents++;
|
||||
public static final int mediaDidLoaded = totalEvents++;
|
||||
public static final int mediaCountDidLoaded = totalEvents++;
|
||||
public static final int encryptedChatUpdated = totalEvents++;
|
||||
public static final int messagesReadEncrypted = totalEvents++;
|
||||
public static final int encryptedChatCreated = totalEvents++;
|
||||
public static final int userPhotosLoaded = totalEvents++;
|
||||
public static final int removeAllMessagesFromDialog = totalEvents++;
|
||||
public static final int notificationsSettingsUpdated = totalEvents++;
|
||||
public static final int pushMessagesUpdated = totalEvents++;
|
||||
public static final int blockedUsersDidLoaded = totalEvents++;
|
||||
public static final int openedChatChanged = totalEvents++;
|
||||
public static final int stopEncodingService = totalEvents++;
|
||||
public static final int didCreatedNewDeleteTask = totalEvents++;
|
||||
public static final int mainUserInfoChanged = totalEvents++;
|
||||
public static final int privacyRulesUpdated = totalEvents++;
|
||||
public static final int updateMessageMedia = totalEvents++;
|
||||
public static final int recentImagesDidLoaded = totalEvents++;
|
||||
public static final int replaceMessagesObjects = totalEvents++;
|
||||
public static final int didSetPasscode = totalEvents++;
|
||||
public static final int didSetTwoStepPassword = totalEvents++;
|
||||
public static final int screenStateChanged = totalEvents++;
|
||||
public static final int didLoadedReplyMessages = totalEvents++;
|
||||
public static final int newSessionReceived = totalEvents++;
|
||||
public static final int didReceivedWebpages = totalEvents++;
|
||||
public static final int didReceivedWebpagesInUpdates = totalEvents++;
|
||||
public static final int stickersDidLoaded = totalEvents++;
|
||||
public static final int didReplacedPhotoInMemCache = totalEvents++;
|
||||
public static final int messagesReadContent = totalEvents++;
|
||||
public static final int botInfoDidLoaded = totalEvents++;
|
||||
public static final int botKeyboardDidLoaded = totalEvents++;
|
||||
public static final int chatSearchResultsAvailable = totalEvents++;
|
||||
public static final int musicDidLoaded = totalEvents++;
|
||||
|
||||
public static final int httpFileDidLoaded = totalEvents++;
|
||||
public static final int httpFileDidFailedLoad = totalEvents++;
|
||||
|
||||
public static final int messageThumbGenerated = totalEvents++;
|
||||
|
||||
public static final int wallpapersDidLoaded = totalEvents++;
|
||||
public static final int closeOtherAppActivities = totalEvents++;
|
||||
public static final int didUpdatedConnectionState = totalEvents++;
|
||||
public static final int didReceiveSmsCode = totalEvents++;
|
||||
public static final int emojiDidLoaded = totalEvents++;
|
||||
public static final int appDidLogout = totalEvents++;
|
||||
|
||||
public static final int FileDidUpload = totalEvents++;
|
||||
public static final int FileDidFailUpload = totalEvents++;
|
||||
public static final int FileUploadProgressChanged = totalEvents++;
|
||||
public static final int FileLoadProgressChanged = totalEvents++;
|
||||
public static final int FileDidLoaded = totalEvents++;
|
||||
public static final int FileDidFailedLoad = totalEvents++;
|
||||
public static final int FilePreparingStarted = totalEvents++;
|
||||
public static final int FileNewChunkAvailable = totalEvents++;
|
||||
public static final int FilePreparingFailed = totalEvents++;
|
||||
|
||||
public static final int audioProgressDidChanged = totalEvents++;
|
||||
public static final int audioDidReset = totalEvents++;
|
||||
public static final int audioPlayStateChanged = totalEvents++;
|
||||
public static final int recordProgressChanged = totalEvents++;
|
||||
public static final int recordStarted = totalEvents++;
|
||||
public static final int recordStartError = totalEvents++;
|
||||
public static final int recordStopped = totalEvents++;
|
||||
public static final int screenshotTook = totalEvents++;
|
||||
public static final int albumsDidLoaded = totalEvents++;
|
||||
public static final int audioDidSent = totalEvents++;
|
||||
public static final int audioDidStarted = totalEvents++;
|
||||
public static final int audioRouteChanged = totalEvents++;
|
||||
|
||||
private HashMap<Integer, ArrayList<Object>> observers = new HashMap<>();
|
||||
private HashMap<Integer, Object> removeAfterBroadcast = new HashMap<>();
|
||||
private HashMap<Integer, Object> addAfterBroadcast = new HashMap<>();
|
||||
private ArrayList<DelayedPost> delayedPosts = new ArrayList<>(10);
|
||||
|
||||
private int broadcasting = 0;
|
||||
private boolean animationInProgress;
|
||||
|
||||
public interface NotificationCenterDelegate {
|
||||
void didReceivedNotification(int id, Object... args);
|
||||
}
|
||||
|
||||
private class DelayedPost {
|
||||
|
||||
private DelayedPost(int id, Object[] args) {
|
||||
this.id = id;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
private int id;
|
||||
private Object[] args;
|
||||
}
|
||||
|
||||
private static volatile NotificationCenter Instance = null;
|
||||
|
||||
public static NotificationCenter getInstance() {
|
||||
NotificationCenter localInstance = Instance;
|
||||
if (localInstance == null) {
|
||||
synchronized (NotificationCenter.class) {
|
||||
localInstance = Instance;
|
||||
if (localInstance == null) {
|
||||
Instance = localInstance = new NotificationCenter();
|
||||
}
|
||||
}
|
||||
}
|
||||
return localInstance;
|
||||
}
|
||||
|
||||
public void setAnimationInProgress(boolean value) {
|
||||
animationInProgress = value;
|
||||
if (!animationInProgress && !delayedPosts.isEmpty()) {
|
||||
for (DelayedPost delayedPost : delayedPosts) {
|
||||
postNotificationNameInternal(delayedPost.id, true, delayedPost.args);
|
||||
}
|
||||
delayedPosts.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void postNotificationName(int id, Object... args) {
|
||||
boolean allowDuringAnimation = false;
|
||||
if (id == dialogsNeedReload || id == closeChats || id == messagesDidLoaded || id == mediaCountDidLoaded || id == mediaDidLoaded || id == botInfoDidLoaded || id == botKeyboardDidLoaded) {
|
||||
allowDuringAnimation = true;
|
||||
}
|
||||
postNotificationNameInternal(id, allowDuringAnimation, args);
|
||||
}
|
||||
|
||||
public void postNotificationNameInternal(int id, boolean allowDuringAnimation, Object... args) {
|
||||
if (BuildVars.DEBUG_VERSION) {
|
||||
if (Thread.currentThread() != ApplicationLoader.applicationHandler.getLooper().getThread()) {
|
||||
throw new RuntimeException("postNotificationName allowed only from MAIN thread");
|
||||
}
|
||||
}
|
||||
if (!allowDuringAnimation && animationInProgress) {
|
||||
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);
|
||||
}
|
||||
return;
|
||||
}
|
||||
broadcasting++;
|
||||
ArrayList<Object> objects = observers.get(id);
|
||||
if (objects != null) {
|
||||
for (Object obj : objects) {
|
||||
((NotificationCenterDelegate) obj).didReceivedNotification(id, args);
|
||||
}
|
||||
}
|
||||
broadcasting--;
|
||||
if (broadcasting == 0) {
|
||||
if (!removeAfterBroadcast.isEmpty()) {
|
||||
for (HashMap.Entry<Integer, Object> entry : removeAfterBroadcast.entrySet()) {
|
||||
removeObserver(entry.getValue(), entry.getKey());
|
||||
}
|
||||
removeAfterBroadcast.clear();
|
||||
}
|
||||
if (!addAfterBroadcast.isEmpty()) {
|
||||
for (HashMap.Entry<Integer, Object> entry : addAfterBroadcast.entrySet()) {
|
||||
addObserver(entry.getValue(), entry.getKey());
|
||||
}
|
||||
addAfterBroadcast.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addObserver(Object observer, int id) {
|
||||
if (BuildVars.DEBUG_VERSION) {
|
||||
if (Thread.currentThread() != ApplicationLoader.applicationHandler.getLooper().getThread()) {
|
||||
throw new RuntimeException("addObserver allowed only from MAIN thread");
|
||||
}
|
||||
}
|
||||
if (broadcasting != 0) {
|
||||
addAfterBroadcast.put(id, observer);
|
||||
return;
|
||||
}
|
||||
ArrayList<Object> objects = observers.get(id);
|
||||
if (objects == null) {
|
||||
observers.put(id, (objects = new ArrayList<>()));
|
||||
}
|
||||
if (objects.contains(observer)) {
|
||||
return;
|
||||
}
|
||||
objects.add(observer);
|
||||
}
|
||||
|
||||
public void removeObserver(Object observer, int id) {
|
||||
if (BuildVars.DEBUG_VERSION) {
|
||||
if (Thread.currentThread() != ApplicationLoader.applicationHandler.getLooper().getThread()) {
|
||||
throw new RuntimeException("removeObserver allowed only from MAIN thread");
|
||||
}
|
||||
}
|
||||
if (broadcasting != 0) {
|
||||
removeAfterBroadcast.put(id, observer);
|
||||
return;
|
||||
}
|
||||
ArrayList<Object> objects = observers.get(id);
|
||||
if (objects != null) {
|
||||
objects.remove(observer);
|
||||
if (objects.size() == 0) {
|
||||
observers.remove(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
/*
|
||||
* This is the source code of Telegram for Android v. 1.7.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-2014.
|
||||
*/
|
||||
|
||||
package org.telegram.android;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.content.Intent;
|
||||
|
||||
import org.telegram.messenger.Utilities;
|
||||
|
||||
public class NotificationRepeat extends IntentService {
|
||||
|
||||
public NotificationRepeat() {
|
||||
super("NotificationRepeat");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
NotificationsController.getInstance().repeatNotificationMaybe();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,46 +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-2014.
|
||||
*/
|
||||
|
||||
package org.telegram.android;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.IBinder;
|
||||
|
||||
import org.telegram.messenger.FileLog;
|
||||
import org.telegram.messenger.ApplicationLoader;
|
||||
|
||||
public class NotificationsService extends Service {
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
FileLog.e("tmessages", "service started");
|
||||
ApplicationLoader.postInitApplication();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
FileLog.e("tmessages", "service destroyed");
|
||||
|
||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", MODE_PRIVATE);
|
||||
if (preferences.getBoolean("pushService", true)) {
|
||||
Intent intent = new Intent("org.telegram.start");
|
||||
sendBroadcast(intent);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,34 +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-2014.
|
||||
*/
|
||||
|
||||
package org.telegram.android;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import org.telegram.messenger.ConnectionsManager;
|
||||
import org.telegram.messenger.FileLog;
|
||||
import org.telegram.messenger.ApplicationLoader;
|
||||
|
||||
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");
|
||||
ConnectionsManager.getInstance().setAppPaused(true, true);
|
||||
ApplicationLoader.isScreenOn = false;
|
||||
} else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
|
||||
FileLog.e("tmessages", "screen on");
|
||||
ConnectionsManager.getInstance().setAppPaused(false, true);
|
||||
ApplicationLoader.isScreenOn = true;
|
||||
}
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.screenStateChanged);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,69 +0,0 @@
|
||||
/*
|
||||
* This is the source code of Telegram for Android v. 1.3.2.
|
||||
* It is licensed under GNU GPL v. 2 or later.
|
||||
* You should have received a copy of the license in this archive (see LICENSE).
|
||||
*
|
||||
* Copyright Nikolai Kudashov, 2013.
|
||||
*/
|
||||
|
||||
package org.telegram.android;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.telephony.SmsMessage;
|
||||
|
||||
import org.telegram.messenger.FileLog;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class SmsListener extends BroadcastReceiver {
|
||||
|
||||
private SharedPreferences preferences;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if(intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED")) {
|
||||
if (!AndroidUtilities.isWaitingForSms()) {
|
||||
return;
|
||||
}
|
||||
Bundle bundle = intent.getExtras();
|
||||
SmsMessage[] msgs;
|
||||
if (bundle != null) {
|
||||
try {
|
||||
Object[] pdus = (Object[]) bundle.get("pdus");
|
||||
msgs = new SmsMessage[pdus.length];
|
||||
String wholeString = "";
|
||||
for(int i = 0; i < msgs.length; i++){
|
||||
msgs[i] = SmsMessage.createFromPdu((byte[])pdus[i]);
|
||||
wholeString += msgs[i].getMessageBody();
|
||||
}
|
||||
|
||||
try {
|
||||
Pattern pattern = Pattern.compile("[0-9]+");
|
||||
final Matcher matcher = pattern.matcher(wholeString);
|
||||
if (matcher.find()) {
|
||||
String str = matcher.group(0);
|
||||
if (str.length() >= 3) {
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.didReceiveSmsCode, matcher.group(0));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
|
||||
} catch(Throwable e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
/*
|
||||
* This is the source code of Telegram for Android v. 1.7.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-2014.
|
||||
*/
|
||||
|
||||
package org.telegram.android;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.NotificationManagerCompat;
|
||||
|
||||
import org.telegram.messenger.FileLog;
|
||||
import org.telegram.messenger.R;
|
||||
import org.telegram.messenger.ApplicationLoader;
|
||||
|
||||
public class VideoEncodingService extends Service implements NotificationCenter.NotificationCenterDelegate {
|
||||
|
||||
private NotificationCompat.Builder builder = null;
|
||||
private String path = null;
|
||||
private int currentProgress = 0;
|
||||
|
||||
public VideoEncodingService() {
|
||||
super();
|
||||
NotificationCenter.getInstance().addObserver(this, NotificationCenter.FileUploadProgressChanged);
|
||||
NotificationCenter.getInstance().addObserver(this, NotificationCenter.stopEncodingService);
|
||||
}
|
||||
|
||||
public IBinder onBind(Intent arg2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
stopForeground(true);
|
||||
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.FileUploadProgressChanged);
|
||||
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.stopEncodingService);
|
||||
FileLog.e("tmessages", "destroy video service");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didReceivedNotification(int id, Object... args) {
|
||||
if (id == NotificationCenter.FileUploadProgressChanged) {
|
||||
String fileName = (String)args[0];
|
||||
if (path != null && path.equals(fileName)) {
|
||||
Float progress = (Float) args[1];
|
||||
Boolean enc = (Boolean) args[2];
|
||||
currentProgress = (int)(progress * 100);
|
||||
builder.setProgress(100, currentProgress, currentProgress == 0);
|
||||
NotificationManagerCompat.from(ApplicationLoader.applicationContext).notify(4, builder.build());
|
||||
}
|
||||
} else if (id == NotificationCenter.stopEncodingService) {
|
||||
String filepath = (String)args[0];
|
||||
if (filepath == null || filepath.equals(path)) {
|
||||
stopSelf();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
path = intent.getStringExtra("path");
|
||||
if (path == null) {
|
||||
stopSelf();
|
||||
return Service.START_NOT_STICKY;
|
||||
}
|
||||
FileLog.e("tmessages", "start video service");
|
||||
if (builder == null) {
|
||||
builder = new NotificationCompat.Builder(ApplicationLoader.applicationContext);
|
||||
builder.setSmallIcon(android.R.drawable.stat_sys_upload);
|
||||
builder.setWhen(System.currentTimeMillis());
|
||||
builder.setContentTitle(LocaleController.getString("AppName", R.string.AppName));
|
||||
builder.setTicker(LocaleController.getString("SendingVideo", R.string.SendingVideo));
|
||||
builder.setContentText(LocaleController.getString("SendingVideo", R.string.SendingVideo));
|
||||
}
|
||||
currentProgress = 0;
|
||||
builder.setProgress(100, currentProgress, currentProgress == 0);
|
||||
startForeground(4, builder.build());
|
||||
NotificationManagerCompat.from(ApplicationLoader.applicationContext).notify(4, builder.build());
|
||||
return Service.START_NOT_STICKY;
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
/*
|
||||
* This is the source code of Telegram for Android v. 1.7.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-2014.
|
||||
*/
|
||||
|
||||
package org.telegram.android;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.RemoteInput;
|
||||
|
||||
public class WearReplyReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
|
||||
if (remoteInput == null) {
|
||||
return;
|
||||
}
|
||||
CharSequence text = remoteInput.getCharSequence(NotificationsController.EXTRA_VOICE_REPLY);
|
||||
if (text == null || text.length() == 0) {
|
||||
return;
|
||||
}
|
||||
long dialog_id = intent.getLongExtra("dialog_id", 0);
|
||||
int max_id = intent.getIntExtra("max_id", 0);
|
||||
if (dialog_id == 0 || max_id == 0) {
|
||||
return;
|
||||
}
|
||||
SendMessagesHelper.getInstance().sendMessage(text.toString(), dialog_id, null, null, true);
|
||||
MessagesController.getInstance().markDialogAsRead(dialog_id, max_id, max_id, 0, 0, true, false);
|
||||
}
|
||||
}
|
@ -1,184 +0,0 @@
|
||||
/*
|
||||
* This is the source code of Telegram for Android v. 2.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-2015.
|
||||
*/
|
||||
|
||||
package org.telegram.android.query;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.telegram.SQLite.SQLiteCursor;
|
||||
import org.telegram.SQLite.SQLitePreparedStatement;
|
||||
import org.telegram.android.AndroidUtilities;
|
||||
import org.telegram.android.ImageLoader;
|
||||
import org.telegram.android.MessageObject;
|
||||
import org.telegram.android.MessagesStorage;
|
||||
import org.telegram.android.NotificationCenter;
|
||||
import org.telegram.messenger.ByteBufferDesc;
|
||||
import org.telegram.messenger.ConnectionsManager;
|
||||
import org.telegram.messenger.FileLog;
|
||||
import org.telegram.messenger.RPCRequest;
|
||||
import org.telegram.messenger.TLObject;
|
||||
import org.telegram.messenger.TLRPC;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
|
||||
public class ReplyMessageQuery {
|
||||
|
||||
public static void loadReplyMessagesForMessages(final ArrayList<MessageObject> messages, final long dialog_id) {
|
||||
final ArrayList<Integer> replyMessages = new ArrayList<>();
|
||||
final HashMap<Integer, ArrayList<MessageObject>> replyMessageOwners = new HashMap<>();
|
||||
for (MessageObject messageObject : messages) {
|
||||
if (messageObject.getId() > 0 && messageObject.isReply() && messageObject.replyMessageObject == null) {
|
||||
Integer id = messageObject.messageOwner.reply_to_msg_id;
|
||||
ArrayList<MessageObject> messageObjects = replyMessageOwners.get(id);
|
||||
if (messageObjects == null) {
|
||||
messageObjects = new ArrayList<>();
|
||||
replyMessageOwners.put(id, messageObjects);
|
||||
}
|
||||
messageObjects.add(messageObject);
|
||||
if (!replyMessages.contains(id)) {
|
||||
replyMessages.add(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (replyMessages.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
final ArrayList<TLRPC.Message> result = new ArrayList<>();
|
||||
final ArrayList<TLRPC.User> users = new ArrayList<>();
|
||||
ArrayList<Integer> loadedUsers = new ArrayList<>();
|
||||
ArrayList<Integer> fromUser = new ArrayList<>();
|
||||
|
||||
SQLiteCursor cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid, date FROM messages WHERE mid IN(%s)", TextUtils.join(",", replyMessages)));
|
||||
while (cursor.next()) {
|
||||
ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(cursor.byteArrayLength(0));
|
||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 0) {
|
||||
TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false);
|
||||
message.id = cursor.intValue(1);
|
||||
message.date = cursor.intValue(2);
|
||||
message.dialog_id = dialog_id;
|
||||
fromUser.add(message.from_id);
|
||||
if (message.action != null && message.action.user_id != 0) {
|
||||
fromUser.add(message.action.user_id);
|
||||
}
|
||||
if (message.media != null && message.media.user_id != 0) {
|
||||
fromUser.add(message.media.user_id);
|
||||
}
|
||||
if (message.media != null && message.media.audio != null && message.media.audio.user_id != 0) {
|
||||
fromUser.add(message.media.audio.user_id);
|
||||
}
|
||||
if (message.fwd_from_id != 0) {
|
||||
fromUser.add(message.fwd_from_id);
|
||||
}
|
||||
result.add(message);
|
||||
replyMessages.remove((Integer) message.id);
|
||||
}
|
||||
MessagesStorage.getInstance().getBuffersStorage().reuseFreeBuffer(data);
|
||||
}
|
||||
cursor.dispose();
|
||||
|
||||
StringBuilder usersToLoad = new StringBuilder();
|
||||
for (int uid : fromUser) {
|
||||
if (!loadedUsers.contains(uid)) {
|
||||
if (usersToLoad.length() != 0) {
|
||||
usersToLoad.append(",");
|
||||
}
|
||||
usersToLoad.append(uid);
|
||||
loadedUsers.add(uid);
|
||||
}
|
||||
}
|
||||
if (usersToLoad.length() != 0) {
|
||||
MessagesStorage.getInstance().getUsersInternal(usersToLoad.toString(), users);
|
||||
}
|
||||
broadcastReplyMessages(result, replyMessageOwners, users, dialog_id);
|
||||
|
||||
if (!replyMessages.isEmpty()) {
|
||||
TLRPC.TL_messages_getMessages req = new TLRPC.TL_messages_getMessages();
|
||||
req.id = replyMessages;
|
||||
ConnectionsManager.getInstance().performRpc(req, new RPCRequest.RPCRequestDelegate() {
|
||||
@Override
|
||||
public void run(TLObject response, TLRPC.TL_error error) {
|
||||
if (error == null) {
|
||||
TLRPC.messages_Messages messagesRes = (TLRPC.messages_Messages) response;
|
||||
ImageLoader.saveMessagesThumbs(messagesRes.messages);
|
||||
broadcastReplyMessages(messagesRes.messages, replyMessageOwners, messagesRes.users, dialog_id);
|
||||
MessagesStorage.getInstance().putUsersAndChats(messagesRes.users, null, true, true);
|
||||
saveReplyMessages(replyMessageOwners, messagesRes.messages);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void saveReplyMessages(final HashMap<Integer, ArrayList<MessageObject>> replyMessageOwners, final ArrayList<TLRPC.Message> result) {
|
||||
MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
MessagesStorage.getInstance().getDatabase().beginTransaction();
|
||||
SQLitePreparedStatement state = MessagesStorage.getInstance().getDatabase().executeFast("UPDATE messages SET replydata = ? WHERE mid = ?");
|
||||
for (TLRPC.Message message : result) {
|
||||
ArrayList<MessageObject> messageObjects = replyMessageOwners.get(message.id);
|
||||
if (messageObjects != null) {
|
||||
ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(message.getObjectSize());
|
||||
message.serializeToStream(data);
|
||||
for (MessageObject messageObject : messageObjects) {
|
||||
state.requery();
|
||||
state.bindByteBuffer(1, data.buffer);
|
||||
state.bindInteger(2, messageObject.getId());
|
||||
state.step();
|
||||
}
|
||||
MessagesStorage.getInstance().getBuffersStorage().reuseFreeBuffer(data);
|
||||
}
|
||||
}
|
||||
state.dispose();
|
||||
MessagesStorage.getInstance().getDatabase().commitTransaction();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void broadcastReplyMessages(final ArrayList<TLRPC.Message> result, final HashMap<Integer, ArrayList<MessageObject>> replyMessageOwners, ArrayList<TLRPC.User> users, final long dialog_id) {
|
||||
final HashMap<Integer, TLRPC.User> usersHashMap = new HashMap<>();
|
||||
for (TLRPC.User user : users) {
|
||||
usersHashMap.put(user.id, user);
|
||||
}
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
boolean changed = false;
|
||||
for (TLRPC.Message message : result) {
|
||||
ArrayList<MessageObject> arrayList = replyMessageOwners.get(message.id);
|
||||
if (arrayList != null) {
|
||||
MessageObject messageObject = new MessageObject(message, usersHashMap, false);
|
||||
for (MessageObject m : arrayList) {
|
||||
m.replyMessageObject = messageObject;
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.didLoadedReplyMessages, dialog_id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,451 +0,0 @@
|
||||
/*
|
||||
* This is the source code of Telegram for Android v. 2.0.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-2014.
|
||||
*/
|
||||
|
||||
package org.telegram.android.query;
|
||||
|
||||
import org.telegram.SQLite.SQLiteCursor;
|
||||
import org.telegram.SQLite.SQLitePreparedStatement;
|
||||
import org.telegram.android.AndroidUtilities;
|
||||
import org.telegram.android.ImageLoader;
|
||||
import org.telegram.android.MessageObject;
|
||||
import org.telegram.android.MessagesController;
|
||||
import org.telegram.android.MessagesStorage;
|
||||
import org.telegram.android.NotificationCenter;
|
||||
import org.telegram.messenger.ByteBufferDesc;
|
||||
import org.telegram.messenger.ConnectionsManager;
|
||||
import org.telegram.messenger.FileLog;
|
||||
import org.telegram.messenger.RPCRequest;
|
||||
import org.telegram.messenger.TLObject;
|
||||
import org.telegram.messenger.TLRPC;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
|
||||
public class SharedMediaQuery {
|
||||
|
||||
public final static int MEDIA_PHOTOVIDEO = 0;
|
||||
public final static int MEDIA_FILE = 1;
|
||||
public final static int MEDIA_AUDIO = 2;
|
||||
public final static int MEDIA_URL = 3;
|
||||
|
||||
public static void loadMedia(final long uid, final int offset, final int count, final int max_id, final int type, final boolean fromCache, final int classGuid) {
|
||||
int lower_part = (int)uid;
|
||||
if (fromCache || lower_part == 0) {
|
||||
loadMediaDatabase(uid, offset, count, max_id, type, classGuid);
|
||||
} else {
|
||||
TLRPC.TL_messages_search req = new TLRPC.TL_messages_search();
|
||||
req.offset = offset;
|
||||
req.limit = count;
|
||||
req.max_id = max_id;
|
||||
if (type == MEDIA_PHOTOVIDEO) {
|
||||
req.filter = new TLRPC.TL_inputMessagesFilterPhotoVideo();
|
||||
} else if (type == MEDIA_FILE) {
|
||||
req.filter = new TLRPC.TL_inputMessagesFilterDocument();
|
||||
} else if (type == MEDIA_AUDIO) {
|
||||
req.filter = new TLRPC.TL_inputMessagesFilterAudio();
|
||||
} else if (type == MEDIA_URL) {
|
||||
req.filter = new TLRPC.TL_inputMessagesFilterUrl();
|
||||
}
|
||||
req.q = "";
|
||||
if (uid < 0) {
|
||||
req.peer = new TLRPC.TL_inputPeerChat();
|
||||
req.peer.chat_id = -lower_part;
|
||||
} else {
|
||||
TLRPC.User user = MessagesController.getInstance().getUser(lower_part);
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
req.peer = new TLRPC.TL_inputPeerUser();
|
||||
req.peer.access_hash = user.access_hash;
|
||||
req.peer.user_id = lower_part;
|
||||
}
|
||||
long reqId = ConnectionsManager.getInstance().performRpc(req, new RPCRequest.RPCRequestDelegate() {
|
||||
@Override
|
||||
public void run(TLObject response, TLRPC.TL_error error) {
|
||||
if (error == null) {
|
||||
final TLRPC.messages_Messages res = (TLRPC.messages_Messages) response;
|
||||
processLoadedMedia(res, uid, offset, count, max_id, type, false, classGuid);
|
||||
}
|
||||
}
|
||||
});
|
||||
ConnectionsManager.getInstance().bindRequestToGuid(reqId, classGuid);
|
||||
}
|
||||
}
|
||||
|
||||
public static void getMediaCount(final long uid, final int type, final int classGuid, boolean fromCache) {
|
||||
int lower_part = (int)uid;
|
||||
if (fromCache || lower_part == 0) {
|
||||
getMediaCountDatabase(uid, type, classGuid);
|
||||
} else {
|
||||
TLRPC.TL_messages_search req = new TLRPC.TL_messages_search();
|
||||
req.offset = 0;
|
||||
req.limit = 1;
|
||||
req.max_id = 0;
|
||||
if (type == MEDIA_PHOTOVIDEO) {
|
||||
req.filter = new TLRPC.TL_inputMessagesFilterPhotoVideo();
|
||||
} else if (type == MEDIA_FILE) {
|
||||
req.filter = new TLRPC.TL_inputMessagesFilterDocument();
|
||||
} else if (type == MEDIA_AUDIO) {
|
||||
req.filter = new TLRPC.TL_inputMessagesFilterAudio();
|
||||
} else if (type == MEDIA_URL) {
|
||||
req.filter = new TLRPC.TL_inputMessagesFilterUrl();
|
||||
}
|
||||
req.q = "";
|
||||
if (uid < 0) {
|
||||
req.peer = new TLRPC.TL_inputPeerChat();
|
||||
req.peer.chat_id = -lower_part;
|
||||
} else {
|
||||
TLRPC.User user = MessagesController.getInstance().getUser(lower_part);
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
req.peer = new TLRPC.TL_inputPeerUser();
|
||||
req.peer.access_hash = user.access_hash;
|
||||
req.peer.user_id = lower_part;
|
||||
}
|
||||
long reqId = ConnectionsManager.getInstance().performRpc(req, new RPCRequest.RPCRequestDelegate() {
|
||||
@Override
|
||||
public void run(TLObject response, TLRPC.TL_error error) {
|
||||
if (error == null) {
|
||||
final TLRPC.messages_Messages res = (TLRPC.messages_Messages) response;
|
||||
MessagesStorage.getInstance().putUsersAndChats(res.users, res.chats, true, true);
|
||||
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
MessagesController.getInstance().putUsers(res.users, false);
|
||||
MessagesController.getInstance().putChats(res.chats, false);
|
||||
}
|
||||
});
|
||||
|
||||
if (res instanceof TLRPC.TL_messages_messagesSlice) {
|
||||
processLoadedMediaCount(res.count, uid, type, classGuid, false);
|
||||
} else {
|
||||
processLoadedMediaCount(res.messages.size(), uid, type, classGuid, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
ConnectionsManager.getInstance().bindRequestToGuid(reqId, classGuid);
|
||||
}
|
||||
}
|
||||
|
||||
public static int getMediaType(TLRPC.Message message) {
|
||||
if (message == null) {
|
||||
return -1;
|
||||
}
|
||||
if (message.media instanceof TLRPC.TL_messageMediaPhoto || message.media instanceof TLRPC.TL_messageMediaVideo) {
|
||||
return MEDIA_PHOTOVIDEO;
|
||||
} else if (message.media instanceof TLRPC.TL_messageMediaDocument) {
|
||||
if (MessageObject.isStickerMessage(message)) {
|
||||
return -1;
|
||||
} else {
|
||||
return MEDIA_FILE;
|
||||
}
|
||||
} else if (message.media instanceof TLRPC.TL_messageMediaAudio) {
|
||||
return MEDIA_AUDIO;
|
||||
} else if (!message.entities.isEmpty()) {
|
||||
for (int a = 0; a < message.entities.size(); a++) {
|
||||
TLRPC.MessageEntity entity = message.entities.get(a);
|
||||
if (entity instanceof TLRPC.TL_messageEntityUrl || entity instanceof TLRPC.TL_messageEntityTextUrl || entity instanceof TLRPC.TL_messageEntityEmail) {
|
||||
return MEDIA_URL;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static boolean canAddMessageToMedia(TLRPC.Message message) {
|
||||
if (message instanceof TLRPC.TL_message_secret && message.media instanceof TLRPC.TL_messageMediaPhoto && message.ttl != 0 && message.ttl <= 60) {
|
||||
return false;
|
||||
} else if (message.media instanceof TLRPC.TL_messageMediaPhoto ||
|
||||
message.media instanceof TLRPC.TL_messageMediaVideo ||
|
||||
message.media instanceof TLRPC.TL_messageMediaDocument ||
|
||||
message.media instanceof TLRPC.TL_messageMediaAudio) {
|
||||
return true;
|
||||
} else if (!message.entities.isEmpty()) {
|
||||
for (int a = 0; a < message.entities.size(); a++) {
|
||||
TLRPC.MessageEntity entity = message.entities.get(a);
|
||||
if (entity instanceof TLRPC.TL_messageEntityUrl || entity instanceof TLRPC.TL_messageEntityTextUrl || entity instanceof TLRPC.TL_messageEntityEmail) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void processLoadedMedia(final TLRPC.messages_Messages res, final long uid, int offset, int count, int max_id, final int type, final boolean fromCache, final int classGuid) {
|
||||
int lower_part = (int)uid;
|
||||
if (fromCache && res.messages.isEmpty() && lower_part != 0) {
|
||||
loadMedia(uid, offset, count, max_id, type, false, classGuid);
|
||||
} else {
|
||||
if (!fromCache) {
|
||||
ImageLoader.saveMessagesThumbs(res.messages);
|
||||
MessagesStorage.getInstance().putUsersAndChats(res.users, res.chats, true, true);
|
||||
putMediaDatabase(uid, type, res.messages);
|
||||
}
|
||||
|
||||
final HashMap<Integer, TLRPC.User> usersLocal = new HashMap<>();
|
||||
for (TLRPC.User u : res.users) {
|
||||
usersLocal.put(u.id, u);
|
||||
}
|
||||
final ArrayList<MessageObject> objects = new ArrayList<>();
|
||||
for (TLRPC.Message message : res.messages) {
|
||||
objects.add(new MessageObject(message, usersLocal, true));
|
||||
}
|
||||
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int totalCount;
|
||||
if (res instanceof TLRPC.TL_messages_messagesSlice) {
|
||||
totalCount = res.count;
|
||||
} else {
|
||||
totalCount = res.messages.size();
|
||||
}
|
||||
MessagesController.getInstance().putUsers(res.users, fromCache);
|
||||
MessagesController.getInstance().putChats(res.chats, fromCache);
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.mediaDidLoaded, uid, totalCount, objects, fromCache, classGuid, type);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void processLoadedMediaCount(final int count, final long uid, final int type, final int classGuid, final boolean fromCache) {
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int lower_part = (int)uid;
|
||||
if (fromCache && count == -1 && lower_part != 0) {
|
||||
getMediaCount(uid, type, classGuid, false);
|
||||
} else {
|
||||
if (!fromCache) {
|
||||
putMediaCountDatabase(uid, type, count);
|
||||
}
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.mediaCountDidLoaded, uid, (fromCache && count == -1 ? 0 : count), fromCache, type);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void putMediaCountDatabase(final long uid, final int type, final int count) {
|
||||
MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
SQLitePreparedStatement state2 = MessagesStorage.getInstance().getDatabase().executeFast("REPLACE INTO media_counts_v2 VALUES(?, ?, ?)");
|
||||
state2.requery();
|
||||
state2.bindLong(1, uid);
|
||||
state2.bindInteger(2, type);
|
||||
state2.bindInteger(3, count);
|
||||
state2.step();
|
||||
state2.dispose();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void getMediaCountDatabase(final long uid, final int type, final int classGuid) {
|
||||
MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
int count = -1;
|
||||
SQLiteCursor cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT count FROM media_counts_v2 WHERE uid = %d AND type = %d LIMIT 1", uid, type));
|
||||
if (cursor.next()) {
|
||||
count = cursor.intValue(0);
|
||||
}
|
||||
cursor.dispose();
|
||||
int lower_part = (int)uid;
|
||||
if (count == -1 && lower_part == 0) {
|
||||
cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT COUNT(mid) FROM media_v2 WHERE uid = %d AND type = %d LIMIT 1", uid, type));
|
||||
if (cursor.next()) {
|
||||
count = cursor.intValue(0);
|
||||
}
|
||||
cursor.dispose();
|
||||
|
||||
/*cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, send_state, date FROM messages WHERE uid = %d ORDER BY mid ASC LIMIT %d", uid, 1000));
|
||||
ArrayList<TLRPC.Message> photos = new ArrayList<>();
|
||||
ArrayList<TLRPC.Message> docs = new ArrayList<>();
|
||||
while (cursor.next()) {
|
||||
ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(cursor.byteArrayLength(1));
|
||||
if (data != null && cursor.byteBufferValue(1, data.buffer) != 0) {
|
||||
TLRPC.Message message = (TLRPC.Message) TLClassStore.Instance().TLdeserialize(data, data.readInt32());
|
||||
MessageObject.setIsUnread(message, cursor.intValue(0) != 1);
|
||||
message.date = cursor.intValue(2);
|
||||
message.send_state = cursor.intValue(1);
|
||||
message.dialog_id = uid;
|
||||
if (message.ttl > 60 && message.media instanceof TLRPC.TL_messageMediaPhoto || message.media instanceof TLRPC.TL_messageMediaVideo) {
|
||||
photos.add(message);
|
||||
} else if (message.media instanceof TLRPC.TL_messageMediaDocument) {
|
||||
docs.add(message);
|
||||
}
|
||||
}
|
||||
MessagesStorage.getInstance().getBuffersStorage().reuseFreeBuffer(data);
|
||||
}
|
||||
cursor.dispose();
|
||||
if (!photos.isEmpty() || !docs.isEmpty()) {
|
||||
MessagesStorage.getInstance().getDatabase().beginTransaction();
|
||||
if (!photos.isEmpty()) {
|
||||
putMediaDatabaseInternal(uid, MEDIA_PHOTOVIDEO, photos);
|
||||
}
|
||||
if (docs.isEmpty()) {
|
||||
putMediaDatabaseInternal(uid, MEDIA_FILE, docs);
|
||||
}
|
||||
MessagesStorage.getInstance().getDatabase().commitTransaction();
|
||||
}*/
|
||||
|
||||
if (count != -1) {
|
||||
putMediaCountDatabase(uid, type, count);
|
||||
}
|
||||
}
|
||||
processLoadedMediaCount(count, uid, type, classGuid, true);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void loadMediaDatabase(final long uid, final int offset, final int count, final int max_id, final int type, final int classGuid) {
|
||||
MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
TLRPC.TL_messages_messages res = new TLRPC.TL_messages_messages();
|
||||
try {
|
||||
ArrayList<Integer> loadedUsers = new ArrayList<>();
|
||||
ArrayList<Integer> fromUser = new ArrayList<>();
|
||||
|
||||
SQLiteCursor cursor;
|
||||
|
||||
if ((int)uid != 0) {
|
||||
if (max_id != 0) {
|
||||
cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v2 WHERE uid = %d AND mid < %d AND type = %d ORDER BY date DESC, mid DESC LIMIT %d", uid, max_id, type, count));
|
||||
} else {
|
||||
cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v2 WHERE uid = %d AND type = %d ORDER BY date DESC, mid DESC LIMIT %d,%d", uid, type, offset, count));
|
||||
}
|
||||
} else {
|
||||
if (max_id != 0) {
|
||||
cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT m.data, m.mid, r.random_id FROM media_v2 as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid > %d AND type = %d ORDER BY m.mid ASC LIMIT %d", uid, max_id, type, count));
|
||||
} else {
|
||||
cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT m.data, m.mid, r.random_id FROM media_v2 as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND type = %d ORDER BY m.mid ASC LIMIT %d,%d", uid, type, offset, count));
|
||||
}
|
||||
}
|
||||
|
||||
while (cursor.next()) {
|
||||
ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(cursor.byteArrayLength(0));
|
||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 0) {
|
||||
TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false);
|
||||
message.id = cursor.intValue(1);
|
||||
message.dialog_id = uid;
|
||||
if ((int)uid == 0) {
|
||||
message.random_id = cursor.longValue(2);
|
||||
}
|
||||
res.messages.add(message);
|
||||
fromUser.add(message.from_id);
|
||||
}
|
||||
MessagesStorage.getInstance().getBuffersStorage().reuseFreeBuffer(data);
|
||||
}
|
||||
cursor.dispose();
|
||||
|
||||
StringBuilder usersToLoad = new StringBuilder();
|
||||
for (int uid : fromUser) {
|
||||
if (!loadedUsers.contains(uid)) {
|
||||
if (usersToLoad.length() != 0) {
|
||||
usersToLoad.append(",");
|
||||
}
|
||||
usersToLoad.append(uid);
|
||||
loadedUsers.add(uid);
|
||||
}
|
||||
}
|
||||
if (usersToLoad.length() != 0) {
|
||||
MessagesStorage.getInstance().getUsersInternal(usersToLoad.toString(), res.users);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
res.messages.clear();
|
||||
res.chats.clear();
|
||||
res.users.clear();
|
||||
FileLog.e("tmessages", e);
|
||||
} finally {
|
||||
processLoadedMedia(res, uid, offset, count, max_id, type, true, classGuid);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void putMediaDatabaseInternal(final long uid, final int type, final ArrayList<TLRPC.Message> messages) {
|
||||
try {
|
||||
MessagesStorage.getInstance().getDatabase().beginTransaction();
|
||||
SQLitePreparedStatement state2 = MessagesStorage.getInstance().getDatabase().executeFast("REPLACE INTO media_v2 VALUES(?, ?, ?, ?, ?)");
|
||||
for (TLRPC.Message message : messages) {
|
||||
if (canAddMessageToMedia(message)) {
|
||||
state2.requery();
|
||||
ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(message.getObjectSize());
|
||||
message.serializeToStream(data);
|
||||
state2.bindInteger(1, message.id);
|
||||
state2.bindLong(2, uid);
|
||||
state2.bindInteger(3, message.date);
|
||||
state2.bindInteger(4, type);
|
||||
state2.bindByteBuffer(5, data.buffer);
|
||||
state2.step();
|
||||
MessagesStorage.getInstance().getBuffersStorage().reuseFreeBuffer(data);
|
||||
}
|
||||
}
|
||||
state2.dispose();
|
||||
MessagesStorage.getInstance().getDatabase().commitTransaction();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void putMediaDatabase(final long uid, final int type, final ArrayList<TLRPC.Message> messages) {
|
||||
MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
putMediaDatabaseInternal(uid, type, messages);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void loadMusic(final long uid, final int max_id) {
|
||||
MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final ArrayList<MessageObject> arrayList = new ArrayList<>();
|
||||
try {
|
||||
SQLiteCursor cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v2 WHERE uid = %d AND mid < %d AND type = %d ORDER BY date DESC, mid DESC LIMIT 1000", uid, max_id, MEDIA_FILE));
|
||||
|
||||
while (cursor.next()) {
|
||||
ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(cursor.byteArrayLength(0));
|
||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 0) {
|
||||
TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false);
|
||||
if (MessageObject.isMusicMessage(message)) {
|
||||
message.id = cursor.intValue(1);
|
||||
message.dialog_id = uid;
|
||||
arrayList.add(0, new MessageObject(message, null, false));
|
||||
}
|
||||
}
|
||||
MessagesStorage.getInstance().getBuffersStorage().reuseFreeBuffer(data);
|
||||
}
|
||||
cursor.dispose();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.musicDidLoaded, uid, arrayList);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,483 +0,0 @@
|
||||
/*
|
||||
* This is the source code of Telegram for Android v. 2.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-2015.
|
||||
*/
|
||||
|
||||
package org.telegram.android.query;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Message;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.telegram.SQLite.SQLiteCursor;
|
||||
import org.telegram.SQLite.SQLitePreparedStatement;
|
||||
import org.telegram.android.AndroidUtilities;
|
||||
import org.telegram.android.LocaleController;
|
||||
import org.telegram.android.MessagesStorage;
|
||||
import org.telegram.android.NotificationCenter;
|
||||
import org.telegram.messenger.ByteBufferDesc;
|
||||
import org.telegram.messenger.ConnectionsManager;
|
||||
import org.telegram.messenger.FileLog;
|
||||
import org.telegram.messenger.R;
|
||||
import org.telegram.messenger.RPCRequest;
|
||||
import org.telegram.messenger.TLObject;
|
||||
import org.telegram.messenger.TLRPC;
|
||||
import org.telegram.messenger.Utilities;
|
||||
import org.telegram.ui.ActionBar.BaseFragment;
|
||||
import org.telegram.ui.Components.StickersAlert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class StickersQuery {
|
||||
|
||||
private static String loadHash;
|
||||
private static int loadDate;
|
||||
private static ArrayList<TLRPC.TL_messages_stickerSet> stickerSets = new ArrayList<>();
|
||||
private static HashMap<Long, TLRPC.TL_messages_stickerSet> stickerSetsById = new HashMap<>();
|
||||
private static HashMap<Long, String> stickersByEmoji = new HashMap<>();
|
||||
private static HashMap<Long, TLRPC.Document> stickersById = new HashMap<>();
|
||||
private static HashMap<String, ArrayList<TLRPC.Document>> allStickers = new HashMap<>();
|
||||
|
||||
private static boolean loadingStickers;
|
||||
private static boolean stickersLoaded;
|
||||
|
||||
public static void cleanup() {
|
||||
loadHash = null;
|
||||
loadDate = 0;
|
||||
allStickers.clear();
|
||||
stickerSets.clear();
|
||||
stickersByEmoji.clear();
|
||||
stickerSetsById.clear();
|
||||
loadingStickers = false;
|
||||
stickersLoaded = false;
|
||||
}
|
||||
|
||||
public static void checkStickers() {
|
||||
if (!loadingStickers && (!stickersLoaded || loadDate < (System.currentTimeMillis() / 1000 - 60 * 60))) {
|
||||
loadStickers(true, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isLoadingStickers() {
|
||||
return loadingStickers;
|
||||
}
|
||||
|
||||
public static TLRPC.Document getStickerById(long id) {
|
||||
TLRPC.Document document = stickersById.get(id);
|
||||
if (document != null) {
|
||||
long setId = getStickerSetId(document);
|
||||
TLRPC.TL_messages_stickerSet stickerSet = stickerSetsById.get(setId);
|
||||
if (stickerSet != null && (stickerSet.set.flags & 2) != 0) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return document;
|
||||
}
|
||||
|
||||
public static HashMap<String, ArrayList<TLRPC.Document>> getAllStickers() {
|
||||
return allStickers;
|
||||
}
|
||||
|
||||
public static ArrayList<TLRPC.TL_messages_stickerSet> getStickerSets() {
|
||||
return stickerSets;
|
||||
}
|
||||
|
||||
public static boolean isStickerPackInstalled(long id) {
|
||||
return stickerSetsById.containsKey(id);
|
||||
}
|
||||
|
||||
public static String getEmojiForSticker(long id) {
|
||||
String value = stickersByEmoji.get(id);
|
||||
return value != null ? value : "";
|
||||
}
|
||||
|
||||
public static void loadStickers(boolean cache, boolean force) {
|
||||
if (loadingStickers) {
|
||||
return;
|
||||
}
|
||||
loadingStickers = true;
|
||||
if (cache) {
|
||||
MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ArrayList<TLRPC.TL_messages_stickerSet> newStickerArray = null;
|
||||
int date = 0;
|
||||
String hash = null;
|
||||
SQLiteCursor cursor = null;
|
||||
try {
|
||||
cursor = MessagesStorage.getInstance().getDatabase().queryFinalized("SELECT data, date, hash FROM stickers_v2 WHERE 1");
|
||||
if (cursor.next()) {
|
||||
ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(cursor.byteArrayLength(0));
|
||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 0) {
|
||||
if (newStickerArray == null) {
|
||||
newStickerArray = new ArrayList<>();
|
||||
}
|
||||
int count = data.readInt32(false);
|
||||
for (int a = 0; a < count; a++) {
|
||||
TLRPC.TL_messages_stickerSet stickerSet = TLRPC.TL_messages_stickerSet.TLdeserialize(data, data.readInt32(false), false);
|
||||
newStickerArray.add(stickerSet);
|
||||
}
|
||||
}
|
||||
date = cursor.intValue(1);
|
||||
hash = cursor.stringValue(2);
|
||||
MessagesStorage.getInstance().getBuffersStorage().reuseFreeBuffer(data);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
FileLog.e("tmessages", e);
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.dispose();
|
||||
}
|
||||
}
|
||||
processLoadedStickers(newStickerArray, true, date, hash);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
TLRPC.TL_messages_getAllStickers req = new TLRPC.TL_messages_getAllStickers();
|
||||
req.hash = loadHash == null || force ? "" : loadHash;
|
||||
ConnectionsManager.getInstance().performRpc(req, new RPCRequest.RPCRequestDelegate() {
|
||||
@Override
|
||||
public void run(final TLObject response, final TLRPC.TL_error error) {
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (response instanceof TLRPC.TL_messages_allStickers) {
|
||||
final HashMap<Long, TLRPC.TL_messages_stickerSet> newStickerSets = new HashMap<>();
|
||||
final ArrayList<TLRPC.TL_messages_stickerSet> newStickerArray = new ArrayList<>();
|
||||
final TLRPC.TL_messages_allStickers res = (TLRPC.TL_messages_allStickers) response;
|
||||
|
||||
for (int a = 0; a < res.sets.size(); a++) {
|
||||
final TLRPC.StickerSet stickerSet = res.sets.get(a);
|
||||
|
||||
TLRPC.TL_messages_stickerSet oldSet = stickerSetsById.get(stickerSet.id);
|
||||
if (oldSet != null && oldSet.set.hash == stickerSet.hash) {
|
||||
oldSet.set.flags = stickerSet.flags;
|
||||
newStickerSets.put(oldSet.set.id, oldSet);
|
||||
newStickerArray.add(oldSet);
|
||||
|
||||
if (newStickerSets.size() == res.sets.size()) {
|
||||
processLoadedStickers(newStickerArray, false, (int) (System.currentTimeMillis() / 1000), res.hash);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
newStickerArray.add(null);
|
||||
final int index = a;
|
||||
|
||||
TLRPC.TL_messages_getStickerSet req = new TLRPC.TL_messages_getStickerSet();
|
||||
req.stickerset = new TLRPC.TL_inputStickerSetID();
|
||||
req.stickerset.id = stickerSet.id;
|
||||
req.stickerset.access_hash = stickerSet.access_hash;
|
||||
|
||||
ConnectionsManager.getInstance().performRpc(req, new RPCRequest.RPCRequestDelegate() {
|
||||
@Override
|
||||
public void run(final TLObject response, final TLRPC.TL_error error) {
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
TLRPC.TL_messages_stickerSet res1 = (TLRPC.TL_messages_stickerSet) response;
|
||||
newStickerArray.set(index, res1);
|
||||
newStickerSets.put(stickerSet.id, res1);
|
||||
if (newStickerSets.size() == res.sets.size()) {
|
||||
processLoadedStickers(newStickerArray, false, (int) (System.currentTimeMillis() / 1000), res.hash);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
processLoadedStickers(null, false, (int) (System.currentTimeMillis() / 1000), error == null ? "" : null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void putStickersToCache(final ArrayList<TLRPC.TL_messages_stickerSet> stickers, final int date, final String hash) {
|
||||
MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
SQLitePreparedStatement state = MessagesStorage.getInstance().getDatabase().executeFast("REPLACE INTO stickers_v2 VALUES(?, ?, ?, ?)");
|
||||
state.requery();
|
||||
int size = 4;
|
||||
for (int a = 0; a < stickers.size(); a++) {
|
||||
size += stickers.get(a).getObjectSize();
|
||||
}
|
||||
ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(size);
|
||||
data.writeInt32(stickers.size());
|
||||
for (int a = 0; a < stickers.size(); a++) {
|
||||
stickers.get(a).serializeToStream(data);
|
||||
}
|
||||
state.bindInteger(1, 1);
|
||||
state.bindByteBuffer(2, data.buffer);
|
||||
state.bindInteger(3, date);
|
||||
state.bindString(4, hash);
|
||||
state.step();
|
||||
MessagesStorage.getInstance().getBuffersStorage().reuseFreeBuffer(data);
|
||||
state.dispose();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static long getStickerSetId(TLRPC.Document document) {
|
||||
for (TLRPC.DocumentAttribute attribute : document.attributes) {
|
||||
if (attribute instanceof TLRPC.TL_documentAttributeSticker) {
|
||||
if (attribute.stickerset instanceof TLRPC.TL_inputStickerSetID) {
|
||||
return attribute.stickerset.id;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static void processLoadedStickers(final ArrayList<TLRPC.TL_messages_stickerSet> res, final boolean cache, final int date, final String hash) {
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
loadingStickers = false;
|
||||
stickersLoaded = true;
|
||||
}
|
||||
});
|
||||
Utilities.stageQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (cache && (res == null || date < (int) (System.currentTimeMillis() / 1000 - 60 * 60)) || !cache && res == null && hash == null) {
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (res != null && cache && hash != null) {
|
||||
loadHash = hash;
|
||||
}
|
||||
loadStickers(false, false);
|
||||
}
|
||||
}, res == null && !cache ? 1000 : 0);
|
||||
if (res == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (res != null) {
|
||||
try {
|
||||
final ArrayList<TLRPC.TL_messages_stickerSet> stickerSetsNew = new ArrayList<>();
|
||||
final HashMap<Long, TLRPC.TL_messages_stickerSet> stickerSetsByIdNew = new HashMap<>();
|
||||
final HashMap<Long, String> stickersByEmojiNew = new HashMap<>();
|
||||
final HashMap<Long, TLRPC.Document> stickersByIdNew = new HashMap<>();
|
||||
final HashMap<String, ArrayList<TLRPC.Document>> allStickersNew = new HashMap<>();
|
||||
|
||||
for (int a = 0; a < res.size(); a++) {
|
||||
TLRPC.TL_messages_stickerSet stickerSet = res.get(a);
|
||||
if (stickerSet == null) {
|
||||
continue;
|
||||
}
|
||||
stickerSetsNew.add(stickerSet);
|
||||
stickerSetsByIdNew.put(stickerSet.set.id, stickerSet);
|
||||
|
||||
for (int b = 0; b < stickerSet.documents.size(); b++) {
|
||||
TLRPC.Document document = stickerSet.documents.get(b);
|
||||
if (document == null || document instanceof TLRPC.TL_documentEmpty) {
|
||||
continue;
|
||||
}
|
||||
stickersByIdNew.put(document.id, document);
|
||||
}
|
||||
if ((stickerSet.set.flags & 2) == 0) {
|
||||
for (int b = 0; b < stickerSet.packs.size(); b++) {
|
||||
TLRPC.TL_stickerPack stickerPack = stickerSet.packs.get(b);
|
||||
if (stickerPack == null || stickerPack.emoticon == null) {
|
||||
continue;
|
||||
}
|
||||
stickerPack.emoticon = stickerPack.emoticon.replace("\uFE0F", "");
|
||||
ArrayList<TLRPC.Document> arrayList = allStickersNew.get(stickerPack.emoticon);
|
||||
if (arrayList == null) {
|
||||
arrayList = new ArrayList<>();
|
||||
allStickersNew.put(stickerPack.emoticon, arrayList);
|
||||
}
|
||||
for (int c = 0; c < stickerPack.documents.size(); c++) {
|
||||
Long id = stickerPack.documents.get(c);
|
||||
if (!stickersByEmojiNew.containsKey(id)) {
|
||||
stickersByEmojiNew.put(id, stickerPack.emoticon);
|
||||
}
|
||||
arrayList.add(stickersByIdNew.get(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!cache) {
|
||||
putStickersToCache(stickerSetsNew, date, hash);
|
||||
}
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
stickersById = stickersByIdNew;
|
||||
stickerSetsById = stickerSetsByIdNew;
|
||||
stickerSets = stickerSetsNew;
|
||||
allStickers = allStickersNew;
|
||||
stickersByEmoji = stickersByEmojiNew;
|
||||
loadHash = hash;
|
||||
loadDate = date;
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.stickersDidLoaded);
|
||||
}
|
||||
});
|
||||
} catch (Throwable e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void loadStickers(final BaseFragment fragment, final TLRPC.InputStickerSet stickerSet) {
|
||||
if (fragment == null || stickerSet == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ProgressDialog progressDialog = new ProgressDialog(fragment.getParentActivity());
|
||||
progressDialog.setMessage(LocaleController.getString("Loading", R.string.Loading));
|
||||
progressDialog.setCanceledOnTouchOutside(false);
|
||||
progressDialog.setCancelable(false);
|
||||
|
||||
TLRPC.TL_messages_getStickerSet req = new TLRPC.TL_messages_getStickerSet();
|
||||
req.stickerset = stickerSet;
|
||||
|
||||
final long reqId = ConnectionsManager.getInstance().performRpc(req, new RPCRequest.RPCRequestDelegate() {
|
||||
@Override
|
||||
public void run(final TLObject response, final TLRPC.TL_error error) {
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
progressDialog.dismiss();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
if (fragment != null && fragment.getParentActivity() != null && !fragment.getParentActivity().isFinishing()) {
|
||||
if (error == null) {
|
||||
final TLRPC.TL_messages_stickerSet res = (TLRPC.TL_messages_stickerSet) response;
|
||||
|
||||
StickersAlert alert = new StickersAlert(fragment.getParentActivity(), res);
|
||||
if (res.set == null || !StickersQuery.isStickerPackInstalled(res.set.id)) {
|
||||
alert.setButton(AlertDialog.BUTTON_POSITIVE, LocaleController.getString("AddStickers", R.string.AddStickers), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
TLRPC.TL_messages_installStickerSet req = new TLRPC.TL_messages_installStickerSet();
|
||||
req.stickerset = stickerSet;
|
||||
ConnectionsManager.getInstance().performRpc(req, new RPCRequest.RPCRequestDelegate() {
|
||||
@Override
|
||||
public void run(TLObject response, final TLRPC.TL_error error) {
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (fragment != null && fragment.getParentActivity() != null) {
|
||||
if (error == null) {
|
||||
Toast.makeText(fragment.getParentActivity(), LocaleController.getString("AddStickersInstalled", R.string.AddStickersInstalled), Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
Toast.makeText(fragment.getParentActivity(), LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
loadStickers(false, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
alert.setButton(AlertDialog.BUTTON_NEUTRAL, LocaleController.getString("StickersRemove", R.string.StickersRemove), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
removeStickersSet(fragment.getParentActivity(), res.set, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
alert.setButton(AlertDialog.BUTTON_NEGATIVE, LocaleController.getString("Close", R.string.Close), (Message) null);
|
||||
fragment.setVisibleDialog(alert);
|
||||
alert.show();
|
||||
} else {
|
||||
Toast.makeText(fragment.getParentActivity(), LocaleController.getString("AddStickersNotFound", R.string.AddStickersNotFound), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, LocaleController.getString("Cancel", R.string.Cancel), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
ConnectionsManager.getInstance().cancelRpc(reqId, true);
|
||||
try {
|
||||
dialog.dismiss();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
fragment.setVisibleDialog(progressDialog);
|
||||
progressDialog.show();
|
||||
}
|
||||
|
||||
public static void removeStickersSet(final Context context, TLRPC.StickerSet stickerSet, int hide) {
|
||||
TLRPC.TL_inputStickerSetID stickerSetID = new TLRPC.TL_inputStickerSetID();
|
||||
stickerSetID.access_hash = stickerSet.access_hash;
|
||||
stickerSetID.id = stickerSet.id;
|
||||
if (hide != 0) {
|
||||
if (hide == 1) {
|
||||
stickerSet.flags |= 2;
|
||||
} else {
|
||||
stickerSet.flags &= ~2;
|
||||
}
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.stickersDidLoaded);
|
||||
TLRPC.TL_messages_installStickerSet req = new TLRPC.TL_messages_installStickerSet();
|
||||
req.stickerset = stickerSetID;
|
||||
req.disabled = hide == 1;
|
||||
ConnectionsManager.getInstance().performRpc(req, new RPCRequest.RPCRequestDelegate() {
|
||||
@Override
|
||||
public void run(TLObject response, final TLRPC.TL_error error) {
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
loadStickers(false, true);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
TLRPC.TL_messages_uninstallStickerSet req = new TLRPC.TL_messages_uninstallStickerSet();
|
||||
req.stickerset = stickerSetID;
|
||||
ConnectionsManager.getInstance().performRpc(req, new RPCRequest.RPCRequestDelegate() {
|
||||
@Override
|
||||
public void run(TLObject response, final TLRPC.TL_error error) {
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (error == null) {
|
||||
Toast.makeText(context, LocaleController.getString("StickersRemoved", R.string.StickersRemoved), Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
Toast.makeText(context, LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
loadStickers(false, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,647 +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.android.support.util;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* RecyclerView.Adapter}.
|
||||
* <p>
|
||||
* It keeps items ordered using the {@link Callback#compare(Object, Object)} method and uses
|
||||
* binary search to retrieve items. If the sorting criteria of your items may change, make sure you
|
||||
* call appropriate methods while editing them to avoid data inconsistencies.
|
||||
* <p>
|
||||
* You can control the order of items and change notifications via the {@link Callback} parameter.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class SortedList<T> {
|
||||
|
||||
/**
|
||||
* Used by {@link #indexOf(Object)} when he item cannot be found in the list.
|
||||
*/
|
||||
public static final int INVALID_POSITION = -1;
|
||||
|
||||
private static final int MIN_CAPACITY = 10;
|
||||
private static final int CAPACITY_GROWTH = MIN_CAPACITY;
|
||||
private static final int INSERTION = 1;
|
||||
private static final int DELETION = 1 << 1;
|
||||
private static final int LOOKUP = 1 << 2;
|
||||
T[] mData;
|
||||
|
||||
/**
|
||||
* The callback instance that controls the behavior of the SortedList and get notified when
|
||||
* changes happen.
|
||||
*/
|
||||
private Callback mCallback;
|
||||
|
||||
private BatchedCallback mBatchedCallback;
|
||||
|
||||
private int mSize;
|
||||
private final Class<T> mTClass;
|
||||
|
||||
/**
|
||||
* Creates a new SortedList of type T.
|
||||
*
|
||||
* @param klass The class of the contents of the SortedList.
|
||||
* @param callback The callback that controls the behavior of SortedList.
|
||||
*/
|
||||
public SortedList(Class<T> klass, Callback<T> callback) {
|
||||
this(klass, callback, MIN_CAPACITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new SortedList of type T.
|
||||
*
|
||||
* @param klass The class of the contents of the SortedList.
|
||||
* @param callback The callback that controls the behavior of SortedList.
|
||||
* @param initialCapacity The initial capacity to hold items.
|
||||
*/
|
||||
public SortedList(Class<T> klass, Callback<T> callback, int initialCapacity) {
|
||||
mTClass = klass;
|
||||
mData = (T[]) Array.newInstance(klass, initialCapacity);
|
||||
mCallback = callback;
|
||||
mSize = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of items in the list.
|
||||
*
|
||||
* @return The number of items in the list.
|
||||
*/
|
||||
public int size() {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given item to the list. If this is a new item, SortedList calls
|
||||
* {@link Callback#onInserted(int, int)}.
|
||||
* <p>
|
||||
* If the item already exists in the list and its sorting criteria is not changed, it is
|
||||
* replaced with the existing Item. SortedList uses
|
||||
* {@link Callback#areItemsTheSame(Object, Object)} to check if two items are the same item
|
||||
* and uses {@link Callback#areContentsTheSame(Object, Object)} to decide whether it should
|
||||
* call {@link Callback#onChanged(int, int)} or not. In both cases, it always removes the
|
||||
* reference to the old item and puts the new item into the backing array even if
|
||||
* {@link Callback#areContentsTheSame(Object, Object)} returns false.
|
||||
* <p>
|
||||
* If the sorting criteria of the item is changed, SortedList won't be able to find
|
||||
* its duplicate in the list which will result in having a duplicate of the Item in the list.
|
||||
* If you need to update sorting criteria of an item that already exists in the list,
|
||||
* use {@link #updateItemAt(int, Object)}. You can find the index of the item using
|
||||
* {@link #indexOf(Object)} before you update the object.
|
||||
*
|
||||
* @param item The item to be added into the list.
|
||||
* @return The index of the newly added item.
|
||||
* @see {@link Callback#compare(Object, Object)}
|
||||
* @see {@link Callback#areItemsTheSame(Object, Object)}
|
||||
* @see {@link Callback#areContentsTheSame(Object, Object)}}
|
||||
*/
|
||||
public int add(T item) {
|
||||
return add(item, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Batches adapter updates that happen between calling this method until calling
|
||||
* {@link #endBatchedUpdates()}. For example, if you add multiple items in a loop
|
||||
* and they are placed into consecutive indices, SortedList calls
|
||||
* {@link Callback#onInserted(int, int)} only once with the proper item count. If an event
|
||||
* cannot be merged with the previous event, the previous event is dispatched
|
||||
* to the callback instantly.
|
||||
* <p>
|
||||
* After running your data updates, you <b>must</b> call {@link #endBatchedUpdates()}
|
||||
* which will dispatch any deferred data change event to the current callback.
|
||||
* <p>
|
||||
* A sample implementation may look like this:
|
||||
* <pre>
|
||||
* mSortedList.beginBatchedUpdates();
|
||||
* try {
|
||||
* mSortedList.add(item1)
|
||||
* mSortedList.add(item2)
|
||||
* mSortedList.remove(item3)
|
||||
* ...
|
||||
* } finally {
|
||||
* mSortedList.endBatchedUpdates();
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* Instead of using this method to batch calls, you can use a Callback that extends
|
||||
* {@link BatchedCallback}. In that case, you must make sure that you are manually calling
|
||||
* {@link BatchedCallback#dispatchLastEvent()} right after you complete your data changes.
|
||||
* Failing to do so may create data inconsistencies with the Callback.
|
||||
* <p>
|
||||
* If the current Callback in an instance of {@link BatchedCallback}, calling this method
|
||||
* has no effect.
|
||||
*/
|
||||
public void beginBatchedUpdates() {
|
||||
if (mCallback instanceof BatchedCallback) {
|
||||
return;
|
||||
}
|
||||
if (mBatchedCallback == null) {
|
||||
mBatchedCallback = new BatchedCallback(mCallback);
|
||||
}
|
||||
mCallback = mBatchedCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends the update transaction and dispatches any remaining event to the callback.
|
||||
*/
|
||||
public void endBatchedUpdates() {
|
||||
if (mCallback instanceof BatchedCallback) {
|
||||
((BatchedCallback) mCallback).dispatchLastEvent();
|
||||
}
|
||||
if (mCallback == mBatchedCallback) {
|
||||
mCallback = mBatchedCallback.mWrappedCallback;
|
||||
}
|
||||
}
|
||||
|
||||
private int add(T item, boolean notify) {
|
||||
int index = findIndexOf(item, INSERTION);
|
||||
if (index == INVALID_POSITION) {
|
||||
index = 0;
|
||||
} else if (index < mSize) {
|
||||
T existing = mData[index];
|
||||
if (mCallback.areItemsTheSame(existing, item)) {
|
||||
if (mCallback.areContentsTheSame(existing, item)) {
|
||||
//no change but still replace the item
|
||||
mData[index] = item;
|
||||
return index;
|
||||
} else {
|
||||
mData[index] = item;
|
||||
mCallback.onChanged(index, 1);
|
||||
return index;
|
||||
}
|
||||
}
|
||||
}
|
||||
addToData(index, item);
|
||||
if (notify) {
|
||||
mCallback.onInserted(index, 1);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the provided item from the list and calls {@link Callback#onRemoved(int, int)}.
|
||||
*
|
||||
* @param item The item to be removed from the list.
|
||||
* @return True if item is removed, false if item cannot be found in the list.
|
||||
*/
|
||||
public boolean remove(T item) {
|
||||
return remove(item, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the item at the given index and calls {@link Callback#onRemoved(int, int)}.
|
||||
*
|
||||
* @param index The index of the item to be removed.
|
||||
* @return The removed item.
|
||||
*/
|
||||
public T removeItemAt(int index) {
|
||||
T item = get(index);
|
||||
removeItemAtIndex(index, true);
|
||||
return item;
|
||||
}
|
||||
|
||||
private boolean remove(T item, boolean notify) {
|
||||
int index = findIndexOf(item, DELETION);
|
||||
if (index == INVALID_POSITION) {
|
||||
return false;
|
||||
}
|
||||
removeItemAtIndex(index, notify);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void removeItemAtIndex(int index, boolean notify) {
|
||||
System.arraycopy(mData, index + 1, mData, index, mSize - index - 1);
|
||||
mSize--;
|
||||
mData[mSize] = null;
|
||||
if (notify) {
|
||||
mCallback.onRemoved(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the item at the given index and calls {@link Callback#onChanged(int, int)} and/or
|
||||
* {@link Callback#onMoved(int, int)} if necessary.
|
||||
* <p>
|
||||
* You can use this method if you need to change an existing Item such that its position in the
|
||||
* list may change.
|
||||
* <p>
|
||||
* If the new object is a different object (<code>get(index) != item</code>) and
|
||||
* {@link Callback#areContentsTheSame(Object, Object)} returns <code>true</code>, SortedList
|
||||
* avoids calling {@link Callback#onChanged(int, int)} otherwise it calls
|
||||
* {@link Callback#onChanged(int, int)}.
|
||||
* <p>
|
||||
* If the new position of the item is different than the provided <code>index</code>,
|
||||
* SortedList
|
||||
* calls {@link Callback#onMoved(int, int)}.
|
||||
*
|
||||
* @param index The index of the item to replace
|
||||
* @param item The item to replace the item at the given Index.
|
||||
* @see #add(Object)
|
||||
*/
|
||||
public void updateItemAt(int index, T item) {
|
||||
final T existing = get(index);
|
||||
// assume changed if the same object is given back
|
||||
boolean contentsChanged = existing == item || !mCallback.areContentsTheSame(existing, item);
|
||||
if (existing != item) {
|
||||
// different items, we can use comparison and may avoid lookup
|
||||
final int cmp = mCallback.compare(existing, item);
|
||||
if (cmp == 0) {
|
||||
mData[index] = item;
|
||||
if (contentsChanged) {
|
||||
mCallback.onChanged(index, 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (contentsChanged) {
|
||||
mCallback.onChanged(index, 1);
|
||||
}
|
||||
// TODO this done in 1 pass to avoid shifting twice.
|
||||
removeItemAtIndex(index, false);
|
||||
int newIndex = add(item, false);
|
||||
if (index != newIndex) {
|
||||
mCallback.onMoved(index, newIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method can be used to recalculate the position of the item at the given index, without
|
||||
* triggering an {@link Callback#onChanged(int, int)} callback.
|
||||
* <p>
|
||||
* If you are editing objects in the list such that their position in the list may change but
|
||||
* you don't want to trigger an onChange animation, you can use this method to re-position it.
|
||||
* If the item changes position, SortedList will call {@link Callback#onMoved(int, int)}
|
||||
* without
|
||||
* calling {@link Callback#onChanged(int, int)}.
|
||||
* <p>
|
||||
* A sample usage may look like:
|
||||
*
|
||||
* <pre>
|
||||
* final int position = mSortedList.indexOf(item);
|
||||
* item.incrementPriority(); // assume items are sorted by priority
|
||||
* mSortedList.recalculatePositionOfItemAt(position);
|
||||
* </pre>
|
||||
* In the example above, because the sorting criteria of the item has been changed,
|
||||
* mSortedList.indexOf(item) will not be able to find the item. This is why the code above
|
||||
* first
|
||||
* gets the position before editing the item, edits it and informs the SortedList that item
|
||||
* should be repositioned.
|
||||
*
|
||||
* @param index The current index of the Item whose position should be re-calculated.
|
||||
* @see #updateItemAt(int, Object)
|
||||
* @see #add(Object)
|
||||
*/
|
||||
public void recalculatePositionOfItemAt(int index) {
|
||||
// TODO can be improved
|
||||
final T item = get(index);
|
||||
removeItemAtIndex(index, false);
|
||||
int newIndex = add(item, false);
|
||||
if (index != newIndex) {
|
||||
mCallback.onMoved(index, newIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the item at the given index.
|
||||
*
|
||||
* @param index The index of the item to retrieve.
|
||||
* @return The item at the given index.
|
||||
* @throws java.lang.IndexOutOfBoundsException if provided index is negative or larger than the
|
||||
* size of the list.
|
||||
*/
|
||||
public T get(int index) throws IndexOutOfBoundsException {
|
||||
if (index >= mSize || index < 0) {
|
||||
throw new IndexOutOfBoundsException("Asked to get item at " + index + " but size is "
|
||||
+ mSize);
|
||||
}
|
||||
return mData[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the position of the provided item.
|
||||
*
|
||||
* @param item The item to query for position.
|
||||
* @return The position of the provided item or {@link #INVALID_POSITION} if item is not in the
|
||||
* list.
|
||||
*/
|
||||
public int indexOf(T item) {
|
||||
return findIndexOf(item, LOOKUP);
|
||||
}
|
||||
|
||||
private int findIndexOf(T item, int reason) {
|
||||
int left = 0;
|
||||
int right = mSize;
|
||||
while (left < right) {
|
||||
final int middle = (left + right) / 2;
|
||||
T myItem = mData[middle];
|
||||
final int cmp = mCallback.compare(myItem, item);
|
||||
if (cmp < 0) {
|
||||
left = middle + 1;
|
||||
} else if (cmp == 0) {
|
||||
if (mCallback.areItemsTheSame(myItem, item)) {
|
||||
return middle;
|
||||
} else {
|
||||
int exact = linearEqualitySearch(item, middle, left, right);
|
||||
if (reason == INSERTION) {
|
||||
return exact == INVALID_POSITION ? middle : exact;
|
||||
} else {
|
||||
return exact;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
right = middle;
|
||||
}
|
||||
}
|
||||
return reason == INSERTION ? left : INVALID_POSITION;
|
||||
}
|
||||
|
||||
private int linearEqualitySearch(T item, int middle, int left, int right) {
|
||||
// go left
|
||||
for (int next = middle - 1; next >= left; next--) {
|
||||
T nextItem = mData[next];
|
||||
int cmp = mCallback.compare(nextItem, item);
|
||||
if (cmp != 0) {
|
||||
break;
|
||||
}
|
||||
if (mCallback.areItemsTheSame(nextItem, item)) {
|
||||
return next;
|
||||
}
|
||||
}
|
||||
for (int next = middle + 1; next < right; next++) {
|
||||
T nextItem = mData[next];
|
||||
int cmp = mCallback.compare(nextItem, item);
|
||||
if (cmp != 0) {
|
||||
break;
|
||||
}
|
||||
if (mCallback.areItemsTheSame(nextItem, item)) {
|
||||
return next;
|
||||
}
|
||||
}
|
||||
return INVALID_POSITION;
|
||||
}
|
||||
|
||||
private void addToData(int index, T item) {
|
||||
if (index > mSize) {
|
||||
throw new IndexOutOfBoundsException(
|
||||
"cannot add item to " + index + " because size is " + mSize);
|
||||
}
|
||||
if (mSize == mData.length) {
|
||||
// we are at the limit enlarge
|
||||
T[] newData = (T[]) Array.newInstance(mTClass, mData.length + CAPACITY_GROWTH);
|
||||
System.arraycopy(mData, 0, newData, 0, index);
|
||||
newData[index] = item;
|
||||
System.arraycopy(mData, index, newData, index + 1, mSize - index);
|
||||
mData = newData;
|
||||
} else {
|
||||
// just shift, we fit
|
||||
System.arraycopy(mData, index, mData, index + 1, mSize - index);
|
||||
mData[index] = item;
|
||||
}
|
||||
mSize++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all items from the SortedList.
|
||||
*/
|
||||
public void clear() {
|
||||
if (mSize == 0) {
|
||||
return;
|
||||
}
|
||||
final int prevSize = mSize;
|
||||
Arrays.fill(mData, 0, prevSize, null);
|
||||
mSize = 0;
|
||||
mCallback.onRemoved(0, prevSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* The class that controls the behavior of the {@link SortedList}.
|
||||
* <p>
|
||||
* It defines how items should be sorted and how duplicates should be handled.
|
||||
* <p>
|
||||
* SortedList calls the callback methods on this class to notify changes about the underlying
|
||||
* data.
|
||||
*/
|
||||
public static abstract class Callback<T2> {
|
||||
|
||||
/**
|
||||
* Similar to {@link java.util.Comparator#compare(Object, Object)}, should compare two and
|
||||
* return how they should be ordered.
|
||||
*
|
||||
* @param o1 The first object to compare.
|
||||
* @param o2 The second object to compare.
|
||||
* @return a negative integer, zero, or a positive integer as the
|
||||
* first argument is less than, equal to, or greater than the
|
||||
* second.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @param position The position of the item which has been updated.
|
||||
* @param count The number of items which has changed.
|
||||
*/
|
||||
abstract public void onChanged(int position, int 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
|
||||
* {@link #onChanged(int, int)} or not.
|
||||
* <p>
|
||||
* SortedList uses this method to check equality instead of {@link Object#equals(Object)}
|
||||
* so
|
||||
* that you can change its behavior depending on your UI.
|
||||
* <p>
|
||||
* For example, if you are using SortedList with a {@link android.support.v7.widget.RecyclerView.Adapter
|
||||
* RecyclerView.Adapter}, you should
|
||||
* return whether the items' visual representations are the same or not.
|
||||
*
|
||||
* @param oldItem The previous representation of the object.
|
||||
* @param newItem The new object that replaces the previous one.
|
||||
* @return True if the contents of the items are the same or false if they are different.
|
||||
*/
|
||||
abstract public boolean areContentsTheSame(T2 oldItem, T2 newItem);
|
||||
|
||||
/**
|
||||
* Called by the SortedList to decide whether two object represent the same Item or not.
|
||||
* <p>
|
||||
* For example, if your items have unique ids, this method should check their equality.
|
||||
*
|
||||
* @param item1 The first item to check.
|
||||
* @param item2 The second item to check.
|
||||
* @return True if the two items represent the same object or false if they are different.
|
||||
*/
|
||||
abstract public boolean areItemsTheSame(T2 item1, T2 item2);
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback implementation that can batch notify events dispatched by the SortedList.
|
||||
* <p>
|
||||
* This class can be useful if you want to do multiple operations on a SortedList but don't
|
||||
* want to dispatch each event one by one, which may result in a performance issue.
|
||||
* <p>
|
||||
* For example, if you are going to add multiple items to a SortedList, BatchedCallback call
|
||||
* convert individual <code>onInserted(index, 1)</code> calls into one
|
||||
* <code>onInserted(index, N)</code> if items are added into consecutive indices. This change
|
||||
* can help RecyclerView resolve changes much more easily.
|
||||
* <p>
|
||||
* If consecutive changes in the SortedList are not suitable for batching, BatchingCallback
|
||||
* dispatches them as soon as such case is detected. After your edits on the SortedList is
|
||||
* complete, you <b>must</b> always call {@link BatchedCallback#dispatchLastEvent()} to flush
|
||||
* all changes to the Callback.
|
||||
*/
|
||||
public static class BatchedCallback<T2> extends Callback<T2> {
|
||||
|
||||
private final Callback<T2> 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;
|
||||
|
||||
/**
|
||||
* Creates a new BatchedCallback that wraps the provided Callback.
|
||||
*
|
||||
* @param wrappedCallback The Callback which should received the data change callbacks.
|
||||
* Other method calls (e.g. {@link #compare(Object, Object)} from
|
||||
* the SortedList are directly forwarded to this Callback.
|
||||
*/
|
||||
public BatchedCallback(Callback<T2> wrappedCallback) {
|
||||
mWrappedCallback = wrappedCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(T2 o1, T2 o2) {
|
||||
return mWrappedCallback.compare(o1, 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;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoved(int fromPosition, int toPosition) {
|
||||
dispatchLastEvent();//moves are not merged
|
||||
mWrappedCallback.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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(T2 oldItem, T2 newItem) {
|
||||
return mWrappedCallback.areContentsTheSame(oldItem, newItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(T2 item1, T2 item2) {
|
||||
return mWrappedCallback.areItemsTheSame(item1, item2);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method dispatches any pending event notifications to the wrapped Callback.
|
||||
* You <b>must</b> 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,736 +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.android.support.widget;
|
||||
|
||||
import android.support.v4.util.Pools;
|
||||
import android.util.Log;
|
||||
|
||||
import org.telegram.android.support.widget.OpReorderer;
|
||||
import org.telegram.android.support.widget.RecyclerView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.telegram.android.support.widget.RecyclerView.*;
|
||||
|
||||
/**
|
||||
* Helper class that can enqueue and process adapter update operations.
|
||||
* <p>
|
||||
* To support animations, RecyclerView presents an older version the Adapter to best represent
|
||||
* previous state of the layout. Sometimes, this is not trivial when items are removed that were
|
||||
* not laid out, in which case, RecyclerView has no way of providing that item's view for
|
||||
* animations.
|
||||
* <p>
|
||||
* AdapterHelper creates an UpdateOp for each adapter data change then pre-processes them. During
|
||||
* pre processing, AdapterHelper finds out which UpdateOps can be deferred to second layout pass
|
||||
* and which cannot. For the UpdateOps that cannot be deferred, AdapterHelper will change them
|
||||
* according to previously deferred operation and dispatch them before the first layout pass. It
|
||||
* also takes care of updating deferred UpdateOps since order of operations is changed by this
|
||||
* process.
|
||||
* <p>
|
||||
* Although operations may be forwarded to LayoutManager in different orders, resulting data set
|
||||
* is guaranteed to be the consistent.
|
||||
*/
|
||||
class AdapterHelper implements OpReorderer.Callback {
|
||||
|
||||
final static int POSITION_TYPE_INVISIBLE = 0;
|
||||
|
||||
final static int POSITION_TYPE_NEW_OR_LAID_OUT = 1;
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final String TAG = "AHT";
|
||||
|
||||
private Pools.Pool<UpdateOp> mUpdateOpPool = new Pools.SimplePool<UpdateOp>(UpdateOp.POOL_SIZE);
|
||||
|
||||
final ArrayList<UpdateOp> mPendingUpdates = new ArrayList<UpdateOp>();
|
||||
|
||||
final ArrayList<UpdateOp> mPostponedList = new ArrayList<UpdateOp>();
|
||||
|
||||
final Callback mCallback;
|
||||
|
||||
Runnable mOnItemProcessedCallback;
|
||||
|
||||
final boolean mDisableRecycler;
|
||||
|
||||
final OpReorderer mOpReorderer;
|
||||
|
||||
AdapterHelper(Callback callback) {
|
||||
this(callback, false);
|
||||
}
|
||||
|
||||
AdapterHelper(Callback callback, boolean disableRecycler) {
|
||||
mCallback = callback;
|
||||
mDisableRecycler = disableRecycler;
|
||||
mOpReorderer = new OpReorderer(this);
|
||||
}
|
||||
|
||||
AdapterHelper addUpdateOp(UpdateOp... ops) {
|
||||
Collections.addAll(mPendingUpdates, ops);
|
||||
return this;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
recycleUpdateOpsAndClearList(mPendingUpdates);
|
||||
recycleUpdateOpsAndClearList(mPostponedList);
|
||||
}
|
||||
|
||||
void preProcess() {
|
||||
mOpReorderer.reorderOps(mPendingUpdates);
|
||||
final int count = mPendingUpdates.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
UpdateOp op = mPendingUpdates.get(i);
|
||||
switch (op.cmd) {
|
||||
case UpdateOp.ADD:
|
||||
applyAdd(op);
|
||||
break;
|
||||
case UpdateOp.REMOVE:
|
||||
applyRemove(op);
|
||||
break;
|
||||
case UpdateOp.UPDATE:
|
||||
applyUpdate(op);
|
||||
break;
|
||||
case UpdateOp.MOVE:
|
||||
applyMove(op);
|
||||
break;
|
||||
}
|
||||
if (mOnItemProcessedCallback != null) {
|
||||
mOnItemProcessedCallback.run();
|
||||
}
|
||||
}
|
||||
mPendingUpdates.clear();
|
||||
}
|
||||
|
||||
void consumePostponedUpdates() {
|
||||
final int count = mPostponedList.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
mCallback.onDispatchSecondPass(mPostponedList.get(i));
|
||||
}
|
||||
recycleUpdateOpsAndClearList(mPostponedList);
|
||||
}
|
||||
|
||||
private void applyMove(UpdateOp op) {
|
||||
// MOVE ops are pre-processed so at this point, we know that item is still in the adapter.
|
||||
// otherwise, it would be converted into a REMOVE operation
|
||||
postponeAndUpdateViewHolders(op);
|
||||
}
|
||||
|
||||
private void applyRemove(UpdateOp op) {
|
||||
int tmpStart = op.positionStart;
|
||||
int tmpCount = 0;
|
||||
int tmpEnd = op.positionStart + op.itemCount;
|
||||
int type = -1;
|
||||
for (int position = op.positionStart; position < tmpEnd; position++) {
|
||||
boolean typeChanged = false;
|
||||
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.
|
||||
// * For existing ViewHolders, we'll fake its existence in the pre-layout phase.
|
||||
// * For items that are added and removed in the same process cycle, they won't
|
||||
// have any effect in pre-layout since their add ops are already deferred to
|
||||
// post-layout pass.
|
||||
if (type == POSITION_TYPE_INVISIBLE) {
|
||||
// Looks like we have other updates that we cannot merge with this one.
|
||||
// Create an UpdateOp and dispatch it to LayoutManager.
|
||||
UpdateOp newOp = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount);
|
||||
dispatchAndUpdateViewHolders(newOp);
|
||||
typeChanged = true;
|
||||
}
|
||||
type = POSITION_TYPE_NEW_OR_LAID_OUT;
|
||||
} else {
|
||||
// This update cannot be recovered because we don't have a ViewHolder representing
|
||||
// this position. Instead, post it to LayoutManager immediately
|
||||
if (type == POSITION_TYPE_NEW_OR_LAID_OUT) {
|
||||
// Looks like we have other updates that we cannot merge with this one.
|
||||
// Create UpdateOp op and dispatch it to LayoutManager.
|
||||
UpdateOp newOp = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount);
|
||||
postponeAndUpdateViewHolders(newOp);
|
||||
typeChanged = true;
|
||||
}
|
||||
type = POSITION_TYPE_INVISIBLE;
|
||||
}
|
||||
if (typeChanged) {
|
||||
position -= tmpCount; // also equal to tmpStart
|
||||
tmpEnd -= tmpCount;
|
||||
tmpCount = 1;
|
||||
} else {
|
||||
tmpCount++;
|
||||
}
|
||||
}
|
||||
if (tmpCount != op.itemCount) { // all 1 effect
|
||||
recycleUpdateOp(op);
|
||||
op = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount);
|
||||
}
|
||||
if (type == POSITION_TYPE_INVISIBLE) {
|
||||
dispatchAndUpdateViewHolders(op);
|
||||
} else {
|
||||
postponeAndUpdateViewHolders(op);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyUpdate(UpdateOp op) {
|
||||
int tmpStart = op.positionStart;
|
||||
int tmpCount = 0;
|
||||
int tmpEnd = op.positionStart + op.itemCount;
|
||||
int type = -1;
|
||||
for (int position = op.positionStart; position < tmpEnd; position++) {
|
||||
ViewHolder vh = mCallback.findViewHolder(position);
|
||||
if (vh != null || canFindInPreLayout(position)) { // deferred
|
||||
if (type == POSITION_TYPE_INVISIBLE) {
|
||||
UpdateOp newOp = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount);
|
||||
dispatchAndUpdateViewHolders(newOp);
|
||||
tmpCount = 0;
|
||||
tmpStart = position;
|
||||
}
|
||||
type = POSITION_TYPE_NEW_OR_LAID_OUT;
|
||||
} else { // applied
|
||||
if (type == POSITION_TYPE_NEW_OR_LAID_OUT) {
|
||||
UpdateOp newOp = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount);
|
||||
postponeAndUpdateViewHolders(newOp);
|
||||
tmpCount = 0;
|
||||
tmpStart = position;
|
||||
}
|
||||
type = POSITION_TYPE_INVISIBLE;
|
||||
}
|
||||
tmpCount++;
|
||||
}
|
||||
if (tmpCount != op.itemCount) { // all 1 effect
|
||||
recycleUpdateOp(op);
|
||||
op = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount);
|
||||
}
|
||||
if (type == POSITION_TYPE_INVISIBLE) {
|
||||
dispatchAndUpdateViewHolders(op);
|
||||
} else {
|
||||
postponeAndUpdateViewHolders(op);
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchAndUpdateViewHolders(UpdateOp op) {
|
||||
// tricky part.
|
||||
// traverse all postpones and revert their changes on this op if necessary, apply updated
|
||||
// dispatch to them since now they are after this op.
|
||||
if (op.cmd == UpdateOp.ADD || op.cmd == UpdateOp.MOVE) {
|
||||
throw new IllegalArgumentException("should not dispatch add or move for pre layout");
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "dispatch (pre)" + op);
|
||||
Log.d(TAG, "postponed state before:");
|
||||
for (UpdateOp updateOp : mPostponedList) {
|
||||
Log.d(TAG, updateOp.toString());
|
||||
}
|
||||
Log.d(TAG, "----");
|
||||
}
|
||||
|
||||
// handle each pos 1 by 1 to ensure continuity. If it breaks, dispatch partial
|
||||
// TODO Since move ops are pushed to end, we should not need this anymore
|
||||
int tmpStart = updatePositionWithPostponed(op.positionStart, op.cmd);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "pos:" + op.positionStart + ",updatedPos:" + tmpStart);
|
||||
}
|
||||
int tmpCnt = 1;
|
||||
int offsetPositionForPartial = op.positionStart;
|
||||
final int positionMultiplier;
|
||||
switch (op.cmd) {
|
||||
case UpdateOp.UPDATE:
|
||||
positionMultiplier = 1;
|
||||
break;
|
||||
case UpdateOp.REMOVE:
|
||||
positionMultiplier = 0;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("op should be remove or update." + op);
|
||||
}
|
||||
for (int p = 1; p < op.itemCount; p++) {
|
||||
final int pos = op.positionStart + (positionMultiplier * p);
|
||||
int updatedPos = updatePositionWithPostponed(pos, op.cmd);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "pos:" + pos + ",updatedPos:" + updatedPos);
|
||||
}
|
||||
boolean continuous = false;
|
||||
switch (op.cmd) {
|
||||
case UpdateOp.UPDATE:
|
||||
continuous = updatedPos == tmpStart + 1;
|
||||
break;
|
||||
case UpdateOp.REMOVE:
|
||||
continuous = updatedPos == tmpStart;
|
||||
break;
|
||||
}
|
||||
if (continuous) {
|
||||
tmpCnt++;
|
||||
} else {
|
||||
// need to dispatch this separately
|
||||
UpdateOp tmp = obtainUpdateOp(op.cmd, tmpStart, tmpCnt);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "need to dispatch separately " + tmp);
|
||||
}
|
||||
dispatchFirstPassAndUpdateViewHolders(tmp, offsetPositionForPartial);
|
||||
recycleUpdateOp(tmp);
|
||||
if (op.cmd == UpdateOp.UPDATE) {
|
||||
offsetPositionForPartial += tmpCnt;
|
||||
}
|
||||
tmpStart = updatedPos;// need to remove previously dispatched
|
||||
tmpCnt = 1;
|
||||
}
|
||||
}
|
||||
recycleUpdateOp(op);
|
||||
if (tmpCnt > 0) {
|
||||
UpdateOp tmp = obtainUpdateOp(op.cmd, tmpStart, tmpCnt);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "dispatching:" + tmp);
|
||||
}
|
||||
dispatchFirstPassAndUpdateViewHolders(tmp, offsetPositionForPartial);
|
||||
recycleUpdateOp(tmp);
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "post dispatch");
|
||||
Log.d(TAG, "postponed state after:");
|
||||
for (UpdateOp updateOp : mPostponedList) {
|
||||
Log.d(TAG, updateOp.toString());
|
||||
}
|
||||
Log.d(TAG, "----");
|
||||
}
|
||||
}
|
||||
|
||||
void dispatchFirstPassAndUpdateViewHolders(UpdateOp op, int offsetStart) {
|
||||
mCallback.onDispatchFirstPass(op);
|
||||
switch (op.cmd) {
|
||||
case UpdateOp.REMOVE:
|
||||
mCallback.offsetPositionsForRemovingInvisible(offsetStart, op.itemCount);
|
||||
break;
|
||||
case UpdateOp.UPDATE:
|
||||
mCallback.markViewHoldersUpdated(offsetStart, op.itemCount);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("only remove and update ops can be dispatched"
|
||||
+ " in first pass");
|
||||
}
|
||||
}
|
||||
|
||||
private int updatePositionWithPostponed(int pos, int cmd) {
|
||||
final int count = mPostponedList.size();
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
UpdateOp postponed = mPostponedList.get(i);
|
||||
if (postponed.cmd == UpdateOp.MOVE) {
|
||||
int start, end;
|
||||
if (postponed.positionStart < postponed.itemCount) {
|
||||
start = postponed.positionStart;
|
||||
end = postponed.itemCount;
|
||||
} else {
|
||||
start = postponed.itemCount;
|
||||
end = postponed.positionStart;
|
||||
}
|
||||
if (pos >= start && pos <= end) {
|
||||
//i'm affected
|
||||
if (start == postponed.positionStart) {
|
||||
if (cmd == UpdateOp.ADD) {
|
||||
postponed.itemCount++;
|
||||
} else if (cmd == UpdateOp.REMOVE) {
|
||||
postponed.itemCount--;
|
||||
}
|
||||
// op moved to left, move it right to revert
|
||||
pos++;
|
||||
} else {
|
||||
if (cmd == UpdateOp.ADD) {
|
||||
postponed.positionStart++;
|
||||
} else if (cmd == UpdateOp.REMOVE) {
|
||||
postponed.positionStart--;
|
||||
}
|
||||
// op was moved right, move left to revert
|
||||
pos--;
|
||||
}
|
||||
} else if (pos < postponed.positionStart) {
|
||||
// postponed MV is outside the dispatched OP. if it is before, offset
|
||||
if (cmd == UpdateOp.ADD) {
|
||||
postponed.positionStart++;
|
||||
postponed.itemCount++;
|
||||
} else if (cmd == UpdateOp.REMOVE) {
|
||||
postponed.positionStart--;
|
||||
postponed.itemCount--;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (postponed.positionStart <= pos) {
|
||||
if (postponed.cmd == UpdateOp.ADD) {
|
||||
pos -= postponed.itemCount;
|
||||
} else if (postponed.cmd == UpdateOp.REMOVE) {
|
||||
pos += postponed.itemCount;
|
||||
}
|
||||
} else {
|
||||
if (cmd == UpdateOp.ADD) {
|
||||
postponed.positionStart++;
|
||||
} else if (cmd == UpdateOp.REMOVE) {
|
||||
postponed.positionStart--;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "dispath (step" + i + ")");
|
||||
Log.d(TAG, "postponed state:" + i + ", pos:" + pos);
|
||||
for (UpdateOp updateOp : mPostponedList) {
|
||||
Log.d(TAG, updateOp.toString());
|
||||
}
|
||||
Log.d(TAG, "----");
|
||||
}
|
||||
}
|
||||
for (int i = mPostponedList.size() - 1; i >= 0; i--) {
|
||||
UpdateOp op = mPostponedList.get(i);
|
||||
if (op.cmd == UpdateOp.MOVE) {
|
||||
if (op.itemCount == op.positionStart || op.itemCount < 0) {
|
||||
mPostponedList.remove(i);
|
||||
recycleUpdateOp(op);
|
||||
}
|
||||
} else if (op.itemCount <= 0) {
|
||||
mPostponedList.remove(i);
|
||||
recycleUpdateOp(op);
|
||||
}
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
private boolean canFindInPreLayout(int position) {
|
||||
final int count = mPostponedList.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
UpdateOp op = mPostponedList.get(i);
|
||||
if (op.cmd == UpdateOp.MOVE) {
|
||||
if (findPositionOffset(op.itemCount, i + 1) == position) {
|
||||
return true;
|
||||
}
|
||||
} else if (op.cmd == UpdateOp.ADD) {
|
||||
// TODO optimize.
|
||||
final int end = op.positionStart + op.itemCount;
|
||||
for (int pos = op.positionStart; pos < end; pos++) {
|
||||
if (findPositionOffset(pos, i + 1) == position) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void applyAdd(UpdateOp op) {
|
||||
postponeAndUpdateViewHolders(op);
|
||||
}
|
||||
|
||||
private void postponeAndUpdateViewHolders(UpdateOp op) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "postponing " + op);
|
||||
}
|
||||
mPostponedList.add(op);
|
||||
switch (op.cmd) {
|
||||
case UpdateOp.ADD:
|
||||
mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount);
|
||||
break;
|
||||
case UpdateOp.MOVE:
|
||||
mCallback.offsetPositionsForMove(op.positionStart, op.itemCount);
|
||||
break;
|
||||
case UpdateOp.REMOVE:
|
||||
mCallback.offsetPositionsForRemovingLaidOutOrNewView(op.positionStart,
|
||||
op.itemCount);
|
||||
break;
|
||||
case UpdateOp.UPDATE:
|
||||
mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown update op type for " + op);
|
||||
}
|
||||
}
|
||||
|
||||
boolean hasPendingUpdates() {
|
||||
return mPendingUpdates.size() > 0;
|
||||
}
|
||||
|
||||
int findPositionOffset(int position) {
|
||||
return findPositionOffset(position, 0);
|
||||
}
|
||||
|
||||
int findPositionOffset(int position, int firstPostponedItem) {
|
||||
int count = mPostponedList.size();
|
||||
for (int i = firstPostponedItem; i < count; ++i) {
|
||||
UpdateOp op = mPostponedList.get(i);
|
||||
if (op.cmd == UpdateOp.MOVE) {
|
||||
if (op.positionStart == position) {
|
||||
position = op.itemCount;
|
||||
} else {
|
||||
if (op.positionStart < position) {
|
||||
position--; // like a remove
|
||||
}
|
||||
if (op.itemCount <= position) {
|
||||
position++; // like an add
|
||||
}
|
||||
}
|
||||
} else if (op.positionStart <= position) {
|
||||
if (op.cmd == UpdateOp.REMOVE) {
|
||||
if (position < op.positionStart + op.itemCount) {
|
||||
return -1;
|
||||
}
|
||||
position -= op.itemCount;
|
||||
} else if (op.cmd == UpdateOp.ADD) {
|
||||
position += op.itemCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
return position;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if updates should be processed.
|
||||
*/
|
||||
boolean onItemRangeChanged(int positionStart, int itemCount) {
|
||||
mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount));
|
||||
return mPendingUpdates.size() == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if updates should be processed.
|
||||
*/
|
||||
boolean onItemRangeInserted(int positionStart, int itemCount) {
|
||||
mPendingUpdates.add(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount));
|
||||
return mPendingUpdates.size() == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if updates should be processed.
|
||||
*/
|
||||
boolean onItemRangeRemoved(int positionStart, int itemCount) {
|
||||
mPendingUpdates.add(obtainUpdateOp(UpdateOp.REMOVE, positionStart, itemCount));
|
||||
return mPendingUpdates.size() == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if updates should be processed.
|
||||
*/
|
||||
boolean onItemRangeMoved(int from, int to, int itemCount) {
|
||||
if (from == to) {
|
||||
return false;//no-op
|
||||
}
|
||||
if (itemCount != 1) {
|
||||
throw new IllegalArgumentException("Moving more than 1 item is not supported yet");
|
||||
}
|
||||
mPendingUpdates.add(obtainUpdateOp(UpdateOp.MOVE, from, to));
|
||||
return mPendingUpdates.size() == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips pre-processing and applies all updates in one pass.
|
||||
*/
|
||||
void consumeUpdatesInOnePass() {
|
||||
// we still consume postponed updates (if there is) in case there was a pre-process call
|
||||
// w/o a matching consumePostponedUpdates.
|
||||
consumePostponedUpdates();
|
||||
final int count = mPendingUpdates.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
UpdateOp op = mPendingUpdates.get(i);
|
||||
switch (op.cmd) {
|
||||
case UpdateOp.ADD:
|
||||
mCallback.onDispatchSecondPass(op);
|
||||
mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount);
|
||||
break;
|
||||
case UpdateOp.REMOVE:
|
||||
mCallback.onDispatchSecondPass(op);
|
||||
mCallback.offsetPositionsForRemovingInvisible(op.positionStart, op.itemCount);
|
||||
break;
|
||||
case UpdateOp.UPDATE:
|
||||
mCallback.onDispatchSecondPass(op);
|
||||
mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount);
|
||||
break;
|
||||
case UpdateOp.MOVE:
|
||||
mCallback.onDispatchSecondPass(op);
|
||||
mCallback.offsetPositionsForMove(op.positionStart, op.itemCount);
|
||||
break;
|
||||
}
|
||||
if (mOnItemProcessedCallback != null) {
|
||||
mOnItemProcessedCallback.run();
|
||||
}
|
||||
}
|
||||
recycleUpdateOpsAndClearList(mPendingUpdates);
|
||||
}
|
||||
|
||||
public int applyPendingUpdatesToPosition(int position) {
|
||||
final int size = mPendingUpdates.size();
|
||||
for (int i = 0; i < size; i ++) {
|
||||
UpdateOp op = mPendingUpdates.get(i);
|
||||
switch (op.cmd) {
|
||||
case UpdateOp.ADD:
|
||||
if (op.positionStart <= position) {
|
||||
position += op.itemCount;
|
||||
}
|
||||
break;
|
||||
case UpdateOp.REMOVE:
|
||||
if (op.positionStart <= position) {
|
||||
final int end = op.positionStart + op.itemCount;
|
||||
if (end > position) {
|
||||
return RecyclerView.NO_POSITION;
|
||||
}
|
||||
position -= op.itemCount;
|
||||
}
|
||||
break;
|
||||
case UpdateOp.MOVE:
|
||||
if (op.positionStart == position) {
|
||||
position = op.itemCount;//position end
|
||||
} else {
|
||||
if (op.positionStart < position) {
|
||||
position -= 1;
|
||||
}
|
||||
if (op.itemCount <= position) {
|
||||
position += 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queued operation to happen when child views are updated.
|
||||
*/
|
||||
static class UpdateOp {
|
||||
|
||||
static final int ADD = 0;
|
||||
|
||||
static final int REMOVE = 1;
|
||||
|
||||
static final int UPDATE = 2;
|
||||
|
||||
static final int MOVE = 3;
|
||||
|
||||
static final int POOL_SIZE = 30;
|
||||
|
||||
int cmd;
|
||||
|
||||
int positionStart;
|
||||
|
||||
// holds the target position if this is a MOVE
|
||||
int itemCount;
|
||||
|
||||
UpdateOp(int cmd, int positionStart, int itemCount) {
|
||||
this.cmd = cmd;
|
||||
this.positionStart = positionStart;
|
||||
this.itemCount = itemCount;
|
||||
}
|
||||
|
||||
String cmdToString() {
|
||||
switch (cmd) {
|
||||
case ADD:
|
||||
return "add";
|
||||
case REMOVE:
|
||||
return "rm";
|
||||
case UPDATE:
|
||||
return "up";
|
||||
case MOVE:
|
||||
return "mv";
|
||||
}
|
||||
return "??";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + cmdToString() + ",s:" + positionStart + "c:" + itemCount + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UpdateOp op = (UpdateOp) o;
|
||||
|
||||
if (cmd != op.cmd) {
|
||||
return false;
|
||||
}
|
||||
if (cmd == MOVE && Math.abs(itemCount - positionStart) == 1) {
|
||||
// reverse of this is also true
|
||||
if (itemCount == op.positionStart && positionStart == op.itemCount) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (itemCount != op.itemCount) {
|
||||
return false;
|
||||
}
|
||||
if (positionStart != op.positionStart) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = cmd;
|
||||
result = 31 * result + positionStart;
|
||||
result = 31 * result + itemCount;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateOp obtainUpdateOp(int cmd, int positionStart, int itemCount) {
|
||||
UpdateOp op = mUpdateOpPool.acquire();
|
||||
if (op == null) {
|
||||
op = new UpdateOp(cmd, positionStart, itemCount);
|
||||
} else {
|
||||
op.cmd = cmd;
|
||||
op.positionStart = positionStart;
|
||||
op.itemCount = itemCount;
|
||||
}
|
||||
return op;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recycleUpdateOp(UpdateOp op) {
|
||||
if (!mDisableRecycler) {
|
||||
mUpdateOpPool.release(op);
|
||||
}
|
||||
}
|
||||
|
||||
void recycleUpdateOpsAndClearList(List<UpdateOp> ops) {
|
||||
final int count = ops.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
recycleUpdateOp(ops.get(i));
|
||||
}
|
||||
ops.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Contract between AdapterHelper and RecyclerView.
|
||||
*/
|
||||
static interface Callback {
|
||||
|
||||
ViewHolder findViewHolder(int position);
|
||||
|
||||
void offsetPositionsForRemovingInvisible(int positionStart, int itemCount);
|
||||
|
||||
void offsetPositionsForRemovingLaidOutOrNewView(int positionStart, int itemCount);
|
||||
|
||||
void markViewHoldersUpdated(int positionStart, int itemCount);
|
||||
|
||||
void onDispatchFirstPass(UpdateOp updateOp);
|
||||
|
||||
void onDispatchSecondPass(UpdateOp updateOp);
|
||||
|
||||
void offsetPositionsForAdd(int positionStart, int itemCount);
|
||||
|
||||
void offsetPositionsForMove(int from, int to);
|
||||
}
|
||||
}
|
@ -1,487 +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.android.support.widget;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Helper class to manage children.
|
||||
* <p>
|
||||
* It wraps a RecyclerView and adds ability to hide some children. There are two sets of methods
|
||||
* provided by this class. <b>Regular</b> methods are the ones that replicate ViewGroup methods
|
||||
* like getChildAt, getChildCount etc. These methods ignore hidden children.
|
||||
* <p>
|
||||
* When RecyclerView needs direct access to the view group children, it can call unfiltered
|
||||
* methods like get getUnfilteredChildCount or getUnfilteredChildAt.
|
||||
*/
|
||||
class ChildHelper {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final String TAG = "ChildrenHelper";
|
||||
|
||||
final Callback mCallback;
|
||||
|
||||
final Bucket mBucket;
|
||||
|
||||
final List<View> mHiddenViews;
|
||||
|
||||
ChildHelper(Callback callback) {
|
||||
mCallback = callback;
|
||||
mBucket = new Bucket();
|
||||
mHiddenViews = new ArrayList<View>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a view to the ViewGroup
|
||||
*
|
||||
* @param child View to add.
|
||||
* @param hidden If set to true, this item will be invisible from regular methods.
|
||||
*/
|
||||
void addView(View child, boolean hidden) {
|
||||
addView(child, -1, hidden);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a view to the ViewGroup at an index
|
||||
*
|
||||
* @param child View to add.
|
||||
* @param index Index of the child from the regular perspective (excluding hidden views).
|
||||
* ChildHelper offsets this index to actual ViewGroup index.
|
||||
* @param hidden If set to true, this item will be invisible from regular methods.
|
||||
*/
|
||||
void addView(View child, int index, boolean hidden) {
|
||||
final int offset;
|
||||
if (index < 0) {
|
||||
offset = mCallback.getChildCount();
|
||||
} else {
|
||||
offset = getOffset(index);
|
||||
}
|
||||
mBucket.insert(offset, hidden);
|
||||
if (hidden) {
|
||||
mHiddenViews.add(child);
|
||||
}
|
||||
mCallback.addView(child, offset);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "addViewAt " + index + ",h:" + hidden + ", " + this);
|
||||
}
|
||||
}
|
||||
|
||||
private int getOffset(int index) {
|
||||
if (index < 0) {
|
||||
return -1; //anything below 0 won't work as diff will be undefined.
|
||||
}
|
||||
final int limit = mCallback.getChildCount();
|
||||
int offset = index;
|
||||
while (offset < limit) {
|
||||
final int removedBefore = mBucket.countOnesBefore(offset);
|
||||
final int diff = index - (offset - removedBefore);
|
||||
if (diff == 0) {
|
||||
while (mBucket.get(offset)) { // ensure this offset is not hidden
|
||||
offset ++;
|
||||
}
|
||||
return offset;
|
||||
} else {
|
||||
offset += diff;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the provided View from underlying RecyclerView.
|
||||
*
|
||||
* @param view The view to remove.
|
||||
*/
|
||||
void removeView(View view) {
|
||||
int index = mCallback.indexOfChild(view);
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
if (mBucket.remove(index)) {
|
||||
mHiddenViews.remove(view);
|
||||
}
|
||||
mCallback.removeViewAt(index);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "remove View off:" + index + "," + this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the view at the provided index from RecyclerView.
|
||||
*
|
||||
* @param index Index of the child from the regular perspective (excluding hidden views).
|
||||
* ChildHelper offsets this index to actual ViewGroup index.
|
||||
*/
|
||||
void removeViewAt(int index) {
|
||||
final int offset = getOffset(index);
|
||||
final View view = mCallback.getChildAt(offset);
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
if (mBucket.remove(offset)) {
|
||||
mHiddenViews.remove(view);
|
||||
}
|
||||
mCallback.removeViewAt(offset);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "removeViewAt " + index + ", off:" + offset + ", " + this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the child at provided index.
|
||||
*
|
||||
* @param index Index of the child to return in regular perspective.
|
||||
*/
|
||||
View getChildAt(int index) {
|
||||
final int offset = getOffset(index);
|
||||
return mCallback.getChildAt(offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all views from the ViewGroup including the hidden ones.
|
||||
*/
|
||||
void removeAllViewsUnfiltered() {
|
||||
mBucket.reset();
|
||||
mHiddenViews.clear();
|
||||
mCallback.removeAllViews();
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "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.
|
||||
*/
|
||||
View findHiddenNonRemovedView(int position, int type) {
|
||||
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() &&
|
||||
(type == RecyclerView.INVALID_TYPE || holder.getItemViewType() == type)) {
|
||||
return view;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches the provided view to the underlying ViewGroup.
|
||||
*
|
||||
* @param child Child to attach.
|
||||
* @param index Index of the child to attach in regular perspective.
|
||||
* @param layoutParams LayoutParams for the child.
|
||||
* @param hidden If set to true, this item will be invisible to the regular methods.
|
||||
*/
|
||||
void attachViewToParent(View child, int index, ViewGroup.LayoutParams layoutParams,
|
||||
boolean hidden) {
|
||||
final int offset;
|
||||
if (index < 0) {
|
||||
offset = mCallback.getChildCount();
|
||||
} else {
|
||||
offset = getOffset(index);
|
||||
}
|
||||
mBucket.insert(offset, hidden);
|
||||
if (hidden) {
|
||||
mHiddenViews.add(child);
|
||||
}
|
||||
mCallback.attachViewToParent(child, offset, layoutParams);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "attach view to parent index:" + index + ",off:" + offset + "," +
|
||||
"h:" + hidden + ", " + this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of children that are not hidden.
|
||||
*
|
||||
* @return Number of children that are not hidden.
|
||||
* @see #getChildAt(int)
|
||||
*/
|
||||
int getChildCount() {
|
||||
return mCallback.getChildCount() - mHiddenViews.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of children.
|
||||
*
|
||||
* @return The total number of children including the hidden views.
|
||||
* @see #getUnfilteredChildAt(int)
|
||||
*/
|
||||
int getUnfilteredChildCount() {
|
||||
return mCallback.getChildCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a child by ViewGroup offset. ChildHelper won't offset this index.
|
||||
*
|
||||
* @param index ViewGroup index of the child to return.
|
||||
* @return The view in the provided index.
|
||||
*/
|
||||
View getUnfilteredChildAt(int index) {
|
||||
return mCallback.getChildAt(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detaches the view at the provided index.
|
||||
*
|
||||
* @param index Index of the child to return in regular perspective.
|
||||
*/
|
||||
void detachViewFromParent(int index) {
|
||||
final int offset = getOffset(index);
|
||||
mBucket.remove(offset);
|
||||
mCallback.detachViewFromParent(offset);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "detach view from parent " + index + ", off:" + offset);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the child in regular perspective.
|
||||
*
|
||||
* @param child The child whose index will be returned.
|
||||
* @return The regular perspective index of the child or -1 if it does not exists.
|
||||
*/
|
||||
int indexOfChild(View child) {
|
||||
final int index = mCallback.indexOfChild(child);
|
||||
if (index == -1) {
|
||||
return -1;
|
||||
}
|
||||
if (mBucket.get(index)) {
|
||||
if (DEBUG) {
|
||||
throw new IllegalArgumentException("cannot get index of a hidden child");
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
// reverse the index
|
||||
return index - mBucket.countOnesBefore(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a View is visible to LayoutManager or not.
|
||||
*
|
||||
* @param view The child view to check. Should be a child of the Callback.
|
||||
* @return True if the View is not visible to LayoutManager
|
||||
*/
|
||||
boolean isHidden(View view) {
|
||||
return mHiddenViews.contains(view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a child view as hidden.
|
||||
*
|
||||
* @param view The view to hide.
|
||||
*/
|
||||
void hide(View view) {
|
||||
final int offset = mCallback.indexOfChild(view);
|
||||
if (offset < 0) {
|
||||
throw new IllegalArgumentException("view is not a child, cannot hide " + view);
|
||||
}
|
||||
if (DEBUG && mBucket.get(offset)) {
|
||||
throw new RuntimeException("trying to hide same view twice, how come ? " + view);
|
||||
}
|
||||
mBucket.set(offset);
|
||||
mHiddenViews.add(view);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "hiding child " + view + " at offset " + offset+ ", " + this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mBucket.toString() + ", hidden list:" + mHiddenViews.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a view from the ViewGroup if it is hidden.
|
||||
*
|
||||
* @param view The view to remove.
|
||||
* @return True if the View is found and it is hidden. False otherwise.
|
||||
*/
|
||||
boolean removeViewIfHidden(View view) {
|
||||
final int index = mCallback.indexOfChild(view);
|
||||
if (index == -1) {
|
||||
if (mHiddenViews.remove(view) && DEBUG) {
|
||||
throw new IllegalStateException("view is in hidden list but not in view group");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (mBucket.get(index)) {
|
||||
mBucket.remove(index);
|
||||
if (!mHiddenViews.remove(view) && DEBUG) {
|
||||
throw new IllegalStateException(
|
||||
"removed a hidden view but it is not in hidden views list");
|
||||
}
|
||||
mCallback.removeViewAt(index);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bitset implementation that provides methods to offset indices.
|
||||
*/
|
||||
static class Bucket {
|
||||
|
||||
final static int BITS_PER_WORD = Long.SIZE;
|
||||
|
||||
final static long LAST_BIT = 1L << (Long.SIZE - 1);
|
||||
|
||||
long mData = 0;
|
||||
|
||||
Bucket next;
|
||||
|
||||
void set(int index) {
|
||||
if (index >= BITS_PER_WORD) {
|
||||
ensureNext();
|
||||
next.set(index - BITS_PER_WORD);
|
||||
} else {
|
||||
mData |= 1L << index;
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureNext() {
|
||||
if (next == null) {
|
||||
next = new Bucket();
|
||||
}
|
||||
}
|
||||
|
||||
void clear(int index) {
|
||||
if (index >= BITS_PER_WORD) {
|
||||
if (next != null) {
|
||||
next.clear(index - BITS_PER_WORD);
|
||||
}
|
||||
} else {
|
||||
mData &= ~(1L << index);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
boolean get(int index) {
|
||||
if (index >= BITS_PER_WORD) {
|
||||
ensureNext();
|
||||
return next.get(index - BITS_PER_WORD);
|
||||
} else {
|
||||
return (mData & (1L << index)) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
void reset() {
|
||||
mData = 0;
|
||||
if (next != null) {
|
||||
next.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void insert(int index, boolean value) {
|
||||
if (index >= BITS_PER_WORD) {
|
||||
ensureNext();
|
||||
next.insert(index - BITS_PER_WORD, value);
|
||||
} else {
|
||||
final boolean lastBit = (mData & LAST_BIT) != 0;
|
||||
long mask = (1L << index) - 1;
|
||||
final long before = mData & mask;
|
||||
final long after = ((mData & ~mask)) << 1;
|
||||
mData = before | after;
|
||||
if (value) {
|
||||
set(index);
|
||||
} else {
|
||||
clear(index);
|
||||
}
|
||||
if (lastBit || next != null) {
|
||||
ensureNext();
|
||||
next.insert(0, lastBit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean remove(int index) {
|
||||
if (index >= BITS_PER_WORD) {
|
||||
ensureNext();
|
||||
return next.remove(index - BITS_PER_WORD);
|
||||
} else {
|
||||
long mask = (1L << index);
|
||||
final boolean value = (mData & mask) != 0;
|
||||
mData &= ~mask;
|
||||
mask = mask - 1;
|
||||
final long before = mData & mask;
|
||||
// cannot use >> because it adds one.
|
||||
final long after = Long.rotateRight(mData & ~mask, 1);
|
||||
mData = before | after;
|
||||
if (next != null) {
|
||||
if (next.get(0)) {
|
||||
set(BITS_PER_WORD - 1);
|
||||
}
|
||||
next.remove(0);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
int countOnesBefore(int index) {
|
||||
if (next == null) {
|
||||
if (index >= BITS_PER_WORD) {
|
||||
return Long.bitCount(mData);
|
||||
}
|
||||
return Long.bitCount(mData & ((1L << index) - 1));
|
||||
}
|
||||
if (index < BITS_PER_WORD) {
|
||||
return Long.bitCount(mData & ((1L << index) - 1));
|
||||
} else {
|
||||
return next.countOnesBefore(index - BITS_PER_WORD) + Long.bitCount(mData);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return next == null ? Long.toBinaryString(mData)
|
||||
: next.toString() + "xx" + Long.toBinaryString(mData);
|
||||
}
|
||||
}
|
||||
|
||||
static interface Callback {
|
||||
|
||||
int getChildCount();
|
||||
|
||||
void addView(View child, int index);
|
||||
|
||||
int indexOfChild(View view);
|
||||
|
||||
void removeViewAt(int index);
|
||||
|
||||
View getChildAt(int offset);
|
||||
|
||||
void removeAllViews();
|
||||
|
||||
RecyclerView.ViewHolder getChildViewHolder(View view);
|
||||
|
||||
void attachViewToParent(View child, int index, ViewGroup.LayoutParams layoutParams);
|
||||
|
||||
void detachViewFromParent(int offset);
|
||||
}
|
||||
}
|
@ -1,632 +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.android.support.widget;
|
||||
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v4.view.ViewPropertyAnimatorCompat;
|
||||
import android.support.v4.view.ViewPropertyAnimatorListener;
|
||||
|
||||
import org.telegram.android.support.widget.RecyclerView.ViewHolder;
|
||||
import android.view.View;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This implementation of {@link RecyclerView.ItemAnimator} provides basic
|
||||
* animations on remove, add, and move events that happen to the items in
|
||||
* a RecyclerView. RecyclerView uses a DefaultItemAnimator by default.
|
||||
*
|
||||
* @see RecyclerView#setItemAnimator(RecyclerView.ItemAnimator)
|
||||
*/
|
||||
public class DefaultItemAnimator extends RecyclerView.ItemAnimator {
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private ArrayList<ViewHolder> mPendingRemovals = new ArrayList<ViewHolder>();
|
||||
private ArrayList<ViewHolder> mPendingAdditions = new ArrayList<ViewHolder>();
|
||||
private ArrayList<MoveInfo> mPendingMoves = new ArrayList<MoveInfo>();
|
||||
private ArrayList<ChangeInfo> mPendingChanges = new ArrayList<ChangeInfo>();
|
||||
|
||||
private ArrayList<ArrayList<ViewHolder>> mAdditionsList =
|
||||
new ArrayList<ArrayList<ViewHolder>>();
|
||||
private ArrayList<ArrayList<MoveInfo>> mMovesList = new ArrayList<ArrayList<MoveInfo>>();
|
||||
private ArrayList<ArrayList<ChangeInfo>> mChangesList = new ArrayList<ArrayList<ChangeInfo>>();
|
||||
|
||||
private ArrayList<ViewHolder> mAddAnimations = new ArrayList<ViewHolder>();
|
||||
private ArrayList<ViewHolder> mMoveAnimations = new ArrayList<ViewHolder>();
|
||||
private ArrayList<ViewHolder> mRemoveAnimations = new ArrayList<ViewHolder>();
|
||||
private ArrayList<ViewHolder> mChangeAnimations = new ArrayList<ViewHolder>();
|
||||
|
||||
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) {
|
||||
this.holder = holder;
|
||||
this.fromX = fromX;
|
||||
this.fromY = fromY;
|
||||
this.toX = toX;
|
||||
this.toY = toY;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ChangeInfo {
|
||||
public ViewHolder oldHolder, newHolder;
|
||||
public int fromX, fromY, toX, toY;
|
||||
private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder) {
|
||||
this.oldHolder = oldHolder;
|
||||
this.newHolder = newHolder;
|
||||
}
|
||||
|
||||
private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder,
|
||||
int fromX, int fromY, int toX, int toY) {
|
||||
this(oldHolder, newHolder);
|
||||
this.fromX = fromX;
|
||||
this.fromY = fromY;
|
||||
this.toX = toX;
|
||||
this.toY = toY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChangeInfo{" +
|
||||
"oldHolder=" + oldHolder +
|
||||
", newHolder=" + newHolder +
|
||||
", fromX=" + fromX +
|
||||
", fromY=" + fromY +
|
||||
", toX=" + toX +
|
||||
", toY=" + toY +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runPendingAnimations() {
|
||||
boolean removalsPending = !mPendingRemovals.isEmpty();
|
||||
boolean movesPending = !mPendingMoves.isEmpty();
|
||||
boolean changesPending = !mPendingChanges.isEmpty();
|
||||
boolean additionsPending = !mPendingAdditions.isEmpty();
|
||||
if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
|
||||
// nothing to animate
|
||||
return;
|
||||
}
|
||||
// First, remove stuff
|
||||
for (ViewHolder holder : mPendingRemovals) {
|
||||
animateRemoveImpl(holder);
|
||||
}
|
||||
mPendingRemovals.clear();
|
||||
// Next, move stuff
|
||||
if (movesPending) {
|
||||
final ArrayList<MoveInfo> moves = new ArrayList<MoveInfo>();
|
||||
moves.addAll(mPendingMoves);
|
||||
mMovesList.add(moves);
|
||||
mPendingMoves.clear();
|
||||
Runnable mover = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (MoveInfo moveInfo : moves) {
|
||||
animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
|
||||
moveInfo.toX, moveInfo.toY);
|
||||
}
|
||||
moves.clear();
|
||||
mMovesList.remove(moves);
|
||||
}
|
||||
};
|
||||
if (removalsPending) {
|
||||
View view = moves.get(0).holder.itemView;
|
||||
ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
|
||||
} else {
|
||||
mover.run();
|
||||
}
|
||||
}
|
||||
// Next, change stuff, to run in parallel with move animations
|
||||
if (changesPending) {
|
||||
final ArrayList<ChangeInfo> changes = new ArrayList<ChangeInfo>();
|
||||
changes.addAll(mPendingChanges);
|
||||
mChangesList.add(changes);
|
||||
mPendingChanges.clear();
|
||||
Runnable changer = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (ChangeInfo change : changes) {
|
||||
animateChangeImpl(change);
|
||||
}
|
||||
changes.clear();
|
||||
mChangesList.remove(changes);
|
||||
}
|
||||
};
|
||||
if (removalsPending) {
|
||||
ViewHolder holder = changes.get(0).oldHolder;
|
||||
ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());
|
||||
} else {
|
||||
changer.run();
|
||||
}
|
||||
}
|
||||
// Next, add stuff
|
||||
if (additionsPending) {
|
||||
final ArrayList<ViewHolder> additions = new ArrayList<ViewHolder>();
|
||||
additions.addAll(mPendingAdditions);
|
||||
mAdditionsList.add(additions);
|
||||
mPendingAdditions.clear();
|
||||
Runnable adder = new Runnable() {
|
||||
public void run() {
|
||||
for (ViewHolder holder : additions) {
|
||||
animateAddImpl(holder);
|
||||
}
|
||||
additions.clear();
|
||||
mAdditionsList.remove(additions);
|
||||
}
|
||||
};
|
||||
if (removalsPending || movesPending || changesPending) {
|
||||
long removeDuration = removalsPending ? getRemoveDuration() : 0;
|
||||
long moveDuration = movesPending ? getMoveDuration() : 0;
|
||||
long changeDuration = changesPending ? getChangeDuration() : 0;
|
||||
long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
|
||||
View view = additions.get(0).itemView;
|
||||
ViewCompat.postOnAnimationDelayed(view, adder, totalDelay);
|
||||
} else {
|
||||
adder.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean animateRemove(final ViewHolder holder) {
|
||||
endAnimation(holder);
|
||||
mPendingRemovals.add(holder);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void animateRemoveImpl(final ViewHolder holder) {
|
||||
final View view = holder.itemView;
|
||||
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
|
||||
mRemoveAnimations.add(holder);
|
||||
animation.setDuration(getRemoveDuration())
|
||||
.alpha(0).setListener(new VpaListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {
|
||||
dispatchRemoveStarting(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {
|
||||
animation.setListener(null);
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
dispatchRemoveFinished(holder);
|
||||
mRemoveAnimations.remove(holder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean animateAdd(final ViewHolder holder) {
|
||||
endAnimation(holder);
|
||||
ViewCompat.setAlpha(holder.itemView, 0);
|
||||
mPendingAdditions.add(holder);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void animateAddImpl(final ViewHolder holder) {
|
||||
final View view = holder.itemView;
|
||||
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
|
||||
mAddAnimations.add(holder);
|
||||
animation.alpha(1).setDuration(getAddDuration()).
|
||||
setListener(new VpaListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {
|
||||
dispatchAddStarting(holder);
|
||||
}
|
||||
@Override
|
||||
public void onAnimationCancel(View view) {
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {
|
||||
animation.setListener(null);
|
||||
dispatchAddFinished(holder);
|
||||
mAddAnimations.remove(holder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean animateMove(final ViewHolder holder, int fromX, int fromY,
|
||||
int toX, int toY) {
|
||||
final View view = holder.itemView;
|
||||
fromX += ViewCompat.getTranslationX(holder.itemView);
|
||||
fromY += ViewCompat.getTranslationY(holder.itemView);
|
||||
endAnimation(holder);
|
||||
int deltaX = toX - fromX;
|
||||
int deltaY = toY - fromY;
|
||||
if (deltaX == 0 && deltaY == 0) {
|
||||
dispatchMoveFinished(holder);
|
||||
return false;
|
||||
}
|
||||
if (deltaX != 0) {
|
||||
ViewCompat.setTranslationX(view, -deltaX);
|
||||
}
|
||||
if (deltaY != 0) {
|
||||
ViewCompat.setTranslationY(view, -deltaY);
|
||||
}
|
||||
mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY));
|
||||
return true;
|
||||
}
|
||||
|
||||
private 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;
|
||||
if (deltaX != 0) {
|
||||
ViewCompat.animate(view).translationX(0);
|
||||
}
|
||||
if (deltaY != 0) {
|
||||
ViewCompat.animate(view).translationY(0);
|
||||
}
|
||||
// TODO: make EndActions end listeners instead, since end actions aren't called when
|
||||
// vpas are canceled (and can't end them. why?)
|
||||
// need listener functionality in VPACompat for this. Ick.
|
||||
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
|
||||
mMoveAnimations.add(holder);
|
||||
animation.setDuration(getMoveDuration()).setListener(new VpaListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {
|
||||
dispatchMoveStarting(holder);
|
||||
}
|
||||
@Override
|
||||
public void onAnimationCancel(View view) {
|
||||
if (deltaX != 0) {
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
}
|
||||
if (deltaY != 0) {
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {
|
||||
animation.setListener(null);
|
||||
dispatchMoveFinished(holder);
|
||||
mMoveAnimations.remove(holder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder,
|
||||
int fromX, int fromY, int toX, int toY) {
|
||||
final float prevTranslationX = ViewCompat.getTranslationX(oldHolder.itemView);
|
||||
final float prevTranslationY = ViewCompat.getTranslationY(oldHolder.itemView);
|
||||
final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView);
|
||||
endAnimation(oldHolder);
|
||||
int deltaX = (int) (toX - fromX - prevTranslationX);
|
||||
int deltaY = (int) (toY - fromY - prevTranslationY);
|
||||
// recover prev translation state after ending animation
|
||||
ViewCompat.setTranslationX(oldHolder.itemView, prevTranslationX);
|
||||
ViewCompat.setTranslationY(oldHolder.itemView, prevTranslationY);
|
||||
ViewCompat.setAlpha(oldHolder.itemView, prevAlpha);
|
||||
if (newHolder != null && newHolder.itemView != null) {
|
||||
// carry over translation values
|
||||
endAnimation(newHolder);
|
||||
ViewCompat.setTranslationX(newHolder.itemView, -deltaX);
|
||||
ViewCompat.setTranslationY(newHolder.itemView, -deltaY);
|
||||
ViewCompat.setAlpha(newHolder.itemView, 0);
|
||||
}
|
||||
mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void animateChangeImpl(final ChangeInfo changeInfo) {
|
||||
final ViewHolder holder = changeInfo.oldHolder;
|
||||
final View view = holder == null ? null : holder.itemView;
|
||||
final ViewHolder newHolder = changeInfo.newHolder;
|
||||
final View newView = newHolder != null ? newHolder.itemView : null;
|
||||
if (view != null) {
|
||||
final ViewPropertyAnimatorCompat oldViewAnim = ViewCompat.animate(view).setDuration(
|
||||
getChangeDuration());
|
||||
mChangeAnimations.add(changeInfo.oldHolder);
|
||||
oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
|
||||
oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
|
||||
oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {
|
||||
dispatchChangeStarting(changeInfo.oldHolder, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {
|
||||
oldViewAnim.setListener(null);
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
dispatchChangeFinished(changeInfo.oldHolder, true);
|
||||
mChangeAnimations.remove(changeInfo.oldHolder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
if (newView != null) {
|
||||
final ViewPropertyAnimatorCompat newViewAnimation = ViewCompat.animate(newView);
|
||||
mChangeAnimations.add(changeInfo.newHolder);
|
||||
newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()).
|
||||
alpha(1).setListener(new VpaListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {
|
||||
dispatchChangeStarting(changeInfo.newHolder, false);
|
||||
}
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {
|
||||
newViewAnimation.setListener(null);
|
||||
ViewCompat.setAlpha(newView, 1);
|
||||
ViewCompat.setTranslationX(newView, 0);
|
||||
ViewCompat.setTranslationY(newView, 0);
|
||||
dispatchChangeFinished(changeInfo.newHolder, false);
|
||||
mChangeAnimations.remove(changeInfo.newHolder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
private void endChangeAnimation(List<ChangeInfo> infoList, ViewHolder item) {
|
||||
for (int i = infoList.size() - 1; i >= 0; i--) {
|
||||
ChangeInfo changeInfo = infoList.get(i);
|
||||
if (endChangeAnimationIfNecessary(changeInfo, item)) {
|
||||
if (changeInfo.oldHolder == null && changeInfo.newHolder == null) {
|
||||
infoList.remove(changeInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void endChangeAnimationIfNecessary(ChangeInfo changeInfo) {
|
||||
if (changeInfo.oldHolder != null) {
|
||||
endChangeAnimationIfNecessary(changeInfo, changeInfo.oldHolder);
|
||||
}
|
||||
if (changeInfo.newHolder != null) {
|
||||
endChangeAnimationIfNecessary(changeInfo, changeInfo.newHolder);
|
||||
}
|
||||
}
|
||||
private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, ViewHolder item) {
|
||||
boolean oldItem = false;
|
||||
if (changeInfo.newHolder == item) {
|
||||
changeInfo.newHolder = null;
|
||||
} else if (changeInfo.oldHolder == item) {
|
||||
changeInfo.oldHolder = null;
|
||||
oldItem = true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
ViewCompat.setAlpha(item.itemView, 1);
|
||||
ViewCompat.setTranslationX(item.itemView, 0);
|
||||
ViewCompat.setTranslationY(item.itemView, 0);
|
||||
dispatchChangeFinished(item, oldItem);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endAnimation(ViewHolder item) {
|
||||
final View view = item.itemView;
|
||||
// this will trigger end callback which should set properties to their target values.
|
||||
ViewCompat.animate(view).cancel();
|
||||
// TODO if some other animations are chained to end, how do we cancel them as well?
|
||||
for (int i = mPendingMoves.size() - 1; i >= 0; i--) {
|
||||
MoveInfo moveInfo = mPendingMoves.get(i);
|
||||
if (moveInfo.holder == item) {
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
dispatchMoveFinished(item);
|
||||
mPendingMoves.remove(i);
|
||||
}
|
||||
}
|
||||
endChangeAnimation(mPendingChanges, item);
|
||||
if (mPendingRemovals.remove(item)) {
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
dispatchRemoveFinished(item);
|
||||
}
|
||||
if (mPendingAdditions.remove(item)) {
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
dispatchAddFinished(item);
|
||||
}
|
||||
|
||||
for (int i = mChangesList.size() - 1; i >= 0; i--) {
|
||||
ArrayList<ChangeInfo> changes = mChangesList.get(i);
|
||||
endChangeAnimation(changes, item);
|
||||
if (changes.isEmpty()) {
|
||||
mChangesList.remove(i);
|
||||
}
|
||||
}
|
||||
for (int i = mMovesList.size() - 1; i >= 0; i--) {
|
||||
ArrayList<MoveInfo> moves = mMovesList.get(i);
|
||||
for (int j = moves.size() - 1; j >= 0; j--) {
|
||||
MoveInfo moveInfo = moves.get(j);
|
||||
if (moveInfo.holder == item) {
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
dispatchMoveFinished(item);
|
||||
moves.remove(j);
|
||||
if (moves.isEmpty()) {
|
||||
mMovesList.remove(i);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = mAdditionsList.size() - 1; i >= 0; i--) {
|
||||
ArrayList<ViewHolder> additions = mAdditionsList.get(i);
|
||||
if (additions.remove(item)) {
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
dispatchAddFinished(item);
|
||||
if (additions.isEmpty()) {
|
||||
mAdditionsList.remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// animations should be ended by the cancel above.
|
||||
if (mRemoveAnimations.remove(item) && DEBUG) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in "
|
||||
+ "mRemoveAnimations list");
|
||||
}
|
||||
|
||||
if (mAddAnimations.remove(item) && DEBUG) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in "
|
||||
+ "mAddAnimations list");
|
||||
}
|
||||
|
||||
if (mChangeAnimations.remove(item) && DEBUG) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in "
|
||||
+ "mChangeAnimations list");
|
||||
}
|
||||
|
||||
if (mMoveAnimations.remove(item) && DEBUG) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in "
|
||||
+ "mMoveAnimations list");
|
||||
}
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return (!mPendingAdditions.isEmpty() ||
|
||||
!mPendingChanges.isEmpty() ||
|
||||
!mPendingMoves.isEmpty() ||
|
||||
!mPendingRemovals.isEmpty() ||
|
||||
!mMoveAnimations.isEmpty() ||
|
||||
!mRemoveAnimations.isEmpty() ||
|
||||
!mAddAnimations.isEmpty() ||
|
||||
!mChangeAnimations.isEmpty() ||
|
||||
!mMovesList.isEmpty() ||
|
||||
!mAdditionsList.isEmpty() ||
|
||||
!mChangesList.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the state of currently pending and running animations. If there are none
|
||||
* pending/running, call {@link #dispatchAnimationsFinished()} to notify any
|
||||
* listeners.
|
||||
*/
|
||||
private void dispatchFinishedWhenDone() {
|
||||
if (!isRunning()) {
|
||||
dispatchAnimationsFinished();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endAnimations() {
|
||||
int count = mPendingMoves.size();
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
MoveInfo item = mPendingMoves.get(i);
|
||||
View view = item.holder.itemView;
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
dispatchMoveFinished(item.holder);
|
||||
mPendingMoves.remove(i);
|
||||
}
|
||||
count = mPendingRemovals.size();
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
ViewHolder item = mPendingRemovals.get(i);
|
||||
dispatchRemoveFinished(item);
|
||||
mPendingRemovals.remove(i);
|
||||
}
|
||||
count = mPendingAdditions.size();
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
ViewHolder item = mPendingAdditions.get(i);
|
||||
View view = item.itemView;
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
dispatchAddFinished(item);
|
||||
mPendingAdditions.remove(i);
|
||||
}
|
||||
count = mPendingChanges.size();
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
endChangeAnimationIfNecessary(mPendingChanges.get(i));
|
||||
}
|
||||
mPendingChanges.clear();
|
||||
if (!isRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int listCount = mMovesList.size();
|
||||
for (int i = listCount - 1; i >= 0; i--) {
|
||||
ArrayList<MoveInfo> moves = mMovesList.get(i);
|
||||
count = moves.size();
|
||||
for (int j = count - 1; j >= 0; j--) {
|
||||
MoveInfo moveInfo = moves.get(j);
|
||||
ViewHolder item = moveInfo.holder;
|
||||
View view = item.itemView;
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
dispatchMoveFinished(moveInfo.holder);
|
||||
moves.remove(j);
|
||||
if (moves.isEmpty()) {
|
||||
mMovesList.remove(moves);
|
||||
}
|
||||
}
|
||||
}
|
||||
listCount = mAdditionsList.size();
|
||||
for (int i = listCount - 1; i >= 0; i--) {
|
||||
ArrayList<ViewHolder> additions = mAdditionsList.get(i);
|
||||
count = additions.size();
|
||||
for (int j = count - 1; j >= 0; j--) {
|
||||
ViewHolder item = additions.get(j);
|
||||
View view = item.itemView;
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
dispatchAddFinished(item);
|
||||
additions.remove(j);
|
||||
if (additions.isEmpty()) {
|
||||
mAdditionsList.remove(additions);
|
||||
}
|
||||
}
|
||||
}
|
||||
listCount = mChangesList.size();
|
||||
for (int i = listCount - 1; i >= 0; i--) {
|
||||
ArrayList<ChangeInfo> changes = mChangesList.get(i);
|
||||
count = changes.size();
|
||||
for (int j = count - 1; j >= 0; j--) {
|
||||
endChangeAnimationIfNecessary(changes.get(j));
|
||||
if (changes.isEmpty()) {
|
||||
mChangesList.remove(changes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cancelAll(mRemoveAnimations);
|
||||
cancelAll(mMoveAnimations);
|
||||
cancelAll(mAddAnimations);
|
||||
cancelAll(mChangeAnimations);
|
||||
|
||||
dispatchAnimationsFinished();
|
||||
}
|
||||
|
||||
void cancelAll(List<ViewHolder> viewHolders) {
|
||||
for (int i = viewHolders.size() - 1; i >= 0; i--) {
|
||||
ViewCompat.animate(viewHolders.get(i).itemView).cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private static class VpaListenerAdapter implements ViewPropertyAnimatorListener {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(View view) {}
|
||||
};
|
||||
}
|
@ -1,889 +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 languag`e governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.telegram.android.support.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.util.SparseIntArray;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.telegram.android.support.widget.RecyclerView;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* A {@link RecyclerView.LayoutManager} implementations that lays out items in a grid.
|
||||
* <p>
|
||||
* By default, each item occupies 1 span. You can change it by providing a custom
|
||||
* {@link SpanSizeLookup} instance via {@link #setSpanSizeLookup(SpanSizeLookup)}.
|
||||
*/
|
||||
public class GridLayoutManager extends LinearLayoutManager {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
private static final String TAG = "GridLayoutManager";
|
||||
public static final int DEFAULT_SPAN_COUNT = -1;
|
||||
/**
|
||||
* The measure spec for the scroll direction.
|
||||
*/
|
||||
static final int MAIN_DIR_SPEC =
|
||||
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
|
||||
/**
|
||||
* Span size have been changed but we've not done a new layout calculation.
|
||||
*/
|
||||
boolean mPendingSpanCountChange = false;
|
||||
int mSpanCount = DEFAULT_SPAN_COUNT;
|
||||
/**
|
||||
* Right borders for each span.
|
||||
* <p>For <b>i-th</b> item start is {@link #mCachedBorders}[i-1] + 1
|
||||
* and end is {@link #mCachedBorders}[i].
|
||||
*/
|
||||
int [] mCachedBorders;
|
||||
/**
|
||||
* Temporary array to keep views in layoutChunk method
|
||||
*/
|
||||
View[] mSet;
|
||||
final SparseIntArray mPreLayoutSpanSizeCache = new SparseIntArray();
|
||||
final SparseIntArray mPreLayoutSpanIndexCache = new SparseIntArray();
|
||||
SpanSizeLookup mSpanSizeLookup = new DefaultSpanSizeLookup();
|
||||
// re-used variable to acquire decor insets from RecyclerView
|
||||
final Rect mDecorInsets = new Rect();
|
||||
|
||||
/**
|
||||
* Creates a vertical GridLayoutManager
|
||||
*
|
||||
* @param context Current context, will be used to access resources.
|
||||
* @param spanCount The number of columns in the grid
|
||||
*/
|
||||
public GridLayoutManager(Context context, int spanCount) {
|
||||
super(context);
|
||||
setSpanCount(spanCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context Current context, will be used to access resources.
|
||||
* @param spanCount The number of columns or rows in the grid
|
||||
* @param orientation Layout orientation. Should be {@link #HORIZONTAL} or {@link
|
||||
* #VERTICAL}.
|
||||
* @param reverseLayout When set to true, layouts from end to start.
|
||||
*/
|
||||
public GridLayoutManager(Context context, int spanCount, int orientation,
|
||||
boolean reverseLayout) {
|
||||
super(context, orientation, reverseLayout);
|
||||
setSpanCount(spanCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* stackFromEnd is not supported by GridLayoutManager. Consider using
|
||||
* {@link #setReverseLayout(boolean)}.
|
||||
*/
|
||||
@Override
|
||||
public void setStackFromEnd(boolean stackFromEnd) {
|
||||
if (stackFromEnd) {
|
||||
throw new UnsupportedOperationException(
|
||||
"GridLayoutManager does not support stack from end."
|
||||
+ " Consider using reverse layout");
|
||||
}
|
||||
super.setStackFromEnd(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCountForAccessibility(RecyclerView.Recycler recycler,
|
||||
RecyclerView.State state) {
|
||||
if (mOrientation == HORIZONTAL) {
|
||||
return mSpanCount;
|
||||
}
|
||||
if (state.getItemCount() < 1) {
|
||||
return 0;
|
||||
}
|
||||
return getSpanGroupIndex(recycler, state, state.getItemCount() - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCountForAccessibility(RecyclerView.Recycler recycler,
|
||||
RecyclerView.State state) {
|
||||
if (mOrientation == VERTICAL) {
|
||||
return mSpanCount;
|
||||
}
|
||||
if (state.getItemCount() < 1) {
|
||||
return 0;
|
||||
}
|
||||
return getSpanGroupIndex(recycler, state, state.getItemCount() - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler,
|
||||
RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) {
|
||||
ViewGroup.LayoutParams lp = host.getLayoutParams();
|
||||
if (!(lp instanceof LayoutParams)) {
|
||||
super.onInitializeAccessibilityNodeInfoForItem(host, info);
|
||||
return;
|
||||
}
|
||||
LayoutParams glp = (LayoutParams) lp;
|
||||
int spanGroupIndex = getSpanGroupIndex(recycler, state, glp.getViewLayoutPosition());
|
||||
if (mOrientation == HORIZONTAL) {
|
||||
info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
|
||||
glp.getSpanIndex(), glp.getSpanSize(),
|
||||
spanGroupIndex, 1,
|
||||
mSpanCount > 1 && glp.getSpanSize() == mSpanCount, false));
|
||||
} else { // VERTICAL
|
||||
info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
|
||||
spanGroupIndex , 1,
|
||||
glp.getSpanIndex(), glp.getSpanSize(),
|
||||
mSpanCount > 1 && glp.getSpanSize() == mSpanCount, false));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
|
||||
if (state.isPreLayout()) {
|
||||
cachePreLayoutSpanMapping();
|
||||
}
|
||||
super.onLayoutChildren(recycler, state);
|
||||
if (DEBUG) {
|
||||
validateChildOrder();
|
||||
}
|
||||
clearPreLayoutSpanMappingCache();
|
||||
if (!state.isPreLayout()) {
|
||||
mPendingSpanCountChange = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void clearPreLayoutSpanMappingCache() {
|
||||
mPreLayoutSpanSizeCache.clear();
|
||||
mPreLayoutSpanIndexCache.clear();
|
||||
}
|
||||
|
||||
private void cachePreLayoutSpanMapping() {
|
||||
final int childCount = getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
final LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
|
||||
final int viewPosition = lp.getViewLayoutPosition();
|
||||
mPreLayoutSpanSizeCache.put(viewPosition, lp.getSpanSize());
|
||||
mPreLayoutSpanIndexCache.put(viewPosition, lp.getSpanIndex());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
|
||||
mSpanSizeLookup.invalidateSpanIndexCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemsChanged(RecyclerView recyclerView) {
|
||||
mSpanSizeLookup.invalidateSpanIndexCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
|
||||
mSpanSizeLookup.invalidateSpanIndexCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) {
|
||||
mSpanSizeLookup.invalidateSpanIndexCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) {
|
||||
mSpanSizeLookup.invalidateSpanIndexCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
|
||||
return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
|
||||
return new LayoutParams(c, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
|
||||
if (lp instanceof ViewGroup.MarginLayoutParams) {
|
||||
return new LayoutParams((ViewGroup.MarginLayoutParams) lp);
|
||||
} else {
|
||||
return new LayoutParams(lp);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkLayoutParams(RecyclerView.LayoutParams lp) {
|
||||
return lp instanceof LayoutParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the source to get the number of spans occupied by each item in the adapter.
|
||||
*
|
||||
* @param spanSizeLookup {@link SpanSizeLookup} instance to be used to query number of spans
|
||||
* occupied by each item
|
||||
*/
|
||||
public void setSpanSizeLookup(SpanSizeLookup spanSizeLookup) {
|
||||
mSpanSizeLookup = spanSizeLookup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current {@link SpanSizeLookup} used by the GridLayoutManager.
|
||||
*
|
||||
* @return The current {@link SpanSizeLookup} used by the GridLayoutManager.
|
||||
*/
|
||||
public SpanSizeLookup getSpanSizeLookup() {
|
||||
return mSpanSizeLookup;
|
||||
}
|
||||
|
||||
private void updateMeasurements() {
|
||||
int totalSpace;
|
||||
if (getOrientation() == VERTICAL) {
|
||||
totalSpace = getWidth() - getPaddingRight() - getPaddingLeft();
|
||||
} else {
|
||||
totalSpace = getHeight() - getPaddingBottom() - getPaddingTop();
|
||||
}
|
||||
calculateItemBorders(totalSpace);
|
||||
}
|
||||
|
||||
private void calculateItemBorders(int totalSpace) {
|
||||
if (mCachedBorders == null || mCachedBorders.length != mSpanCount + 1
|
||||
|| mCachedBorders[mCachedBorders.length - 1] != totalSpace) {
|
||||
mCachedBorders = new int[mSpanCount + 1];
|
||||
}
|
||||
mCachedBorders[0] = 0;
|
||||
int sizePerSpan = totalSpace / mSpanCount;
|
||||
int sizePerSpanRemainder = totalSpace % mSpanCount;
|
||||
int consumedPixels = 0;
|
||||
int additionalSize = 0;
|
||||
for (int i = 1; i <= mSpanCount; i++) {
|
||||
int itemSize = sizePerSpan;
|
||||
additionalSize += sizePerSpanRemainder;
|
||||
if (additionalSize > 0 && (mSpanCount - additionalSize) < sizePerSpanRemainder) {
|
||||
itemSize += 1;
|
||||
additionalSize -= mSpanCount;
|
||||
}
|
||||
consumedPixels += itemSize;
|
||||
mCachedBorders[i] = consumedPixels;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void onAnchorReady(RecyclerView.State state, AnchorInfo anchorInfo) {
|
||||
super.onAnchorReady(state, anchorInfo);
|
||||
updateMeasurements();
|
||||
if (state.getItemCount() > 0 && !state.isPreLayout()) {
|
||||
ensureAnchorIsInFirstSpan(anchorInfo);
|
||||
}
|
||||
if (mSet == null || mSet.length != mSpanCount) {
|
||||
mSet = new View[mSpanCount];
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureAnchorIsInFirstSpan(AnchorInfo anchorInfo) {
|
||||
int span = mSpanSizeLookup.getCachedSpanIndex(anchorInfo.mPosition, mSpanCount);
|
||||
while (span > 0 && anchorInfo.mPosition > 0) {
|
||||
anchorInfo.mPosition--;
|
||||
span = mSpanSizeLookup.getCachedSpanIndex(anchorInfo.mPosition, mSpanCount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
View findReferenceChild(int start, int end, int itemCount) {
|
||||
ensureLayoutState();
|
||||
View invalidMatch = null;
|
||||
View outOfBoundsMatch = null;
|
||||
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);
|
||||
if (position >= 0 && position < itemCount) {
|
||||
final int span = mSpanSizeLookup.getCachedSpanIndex(position, mSpanCount);
|
||||
if (span != 0) {
|
||||
continue;
|
||||
}
|
||||
if (((RecyclerView.LayoutParams) view.getLayoutParams()).isItemRemoved()) {
|
||||
if (invalidMatch == null) {
|
||||
invalidMatch = view; // removed item, least preferred
|
||||
}
|
||||
} else if (mOrientationHelper.getDecoratedStart(view) >= boundsEnd ||
|
||||
mOrientationHelper.getDecoratedEnd(view) < boundsStart) {
|
||||
if (outOfBoundsMatch == null) {
|
||||
outOfBoundsMatch = view; // item is not visible, less preferred
|
||||
}
|
||||
} else {
|
||||
return view;
|
||||
}
|
||||
}
|
||||
}
|
||||
return outOfBoundsMatch != null ? outOfBoundsMatch : invalidMatch;
|
||||
}
|
||||
|
||||
private int getSpanGroupIndex(RecyclerView.Recycler recycler, RecyclerView.State state,
|
||||
int viewPosition) {
|
||||
if (!state.isPreLayout()) {
|
||||
return mSpanSizeLookup.getSpanGroupIndex(viewPosition, mSpanCount);
|
||||
}
|
||||
final int adapterPosition = recycler.convertPreLayoutPositionToPostLayout(viewPosition);
|
||||
if (adapterPosition == -1) {
|
||||
if (DEBUG) {
|
||||
throw new RuntimeException("Cannot find span group index for position "
|
||||
+ viewPosition);
|
||||
}
|
||||
Log.w(TAG, "Cannot find span size for pre layout position. " + viewPosition);
|
||||
return 0;
|
||||
}
|
||||
return mSpanSizeLookup.getSpanGroupIndex(adapterPosition, mSpanCount);
|
||||
}
|
||||
|
||||
private int getSpanIndex(RecyclerView.Recycler recycler, RecyclerView.State state, int pos) {
|
||||
if (!state.isPreLayout()) {
|
||||
return mSpanSizeLookup.getCachedSpanIndex(pos, mSpanCount);
|
||||
}
|
||||
final int cached = mPreLayoutSpanIndexCache.get(pos, -1);
|
||||
if (cached != -1) {
|
||||
return cached;
|
||||
}
|
||||
final int adapterPosition = recycler.convertPreLayoutPositionToPostLayout(pos);
|
||||
if (adapterPosition == -1) {
|
||||
if (DEBUG) {
|
||||
throw new RuntimeException("Cannot find span index for pre layout position. It is"
|
||||
+ " not cached, not in the adapter. Pos:" + pos);
|
||||
}
|
||||
Log.w(TAG, "Cannot find span size for pre layout position. It is"
|
||||
+ " not cached, not in the adapter. Pos:" + pos);
|
||||
return 0;
|
||||
}
|
||||
return mSpanSizeLookup.getCachedSpanIndex(adapterPosition, mSpanCount);
|
||||
}
|
||||
|
||||
private int getSpanSize(RecyclerView.Recycler recycler, RecyclerView.State state, int pos) {
|
||||
if (!state.isPreLayout()) {
|
||||
return mSpanSizeLookup.getSpanSize(pos);
|
||||
}
|
||||
final int cached = mPreLayoutSpanSizeCache.get(pos, -1);
|
||||
if (cached != -1) {
|
||||
return cached;
|
||||
}
|
||||
final int adapterPosition = recycler.convertPreLayoutPositionToPostLayout(pos);
|
||||
if (adapterPosition == -1) {
|
||||
if (DEBUG) {
|
||||
throw new RuntimeException("Cannot find span size for pre layout position. It is"
|
||||
+ " not cached, not in the adapter. Pos:" + pos);
|
||||
}
|
||||
Log.w(TAG, "Cannot find span size for pre layout position. It is"
|
||||
+ " not cached, not in the adapter. Pos:" + pos);
|
||||
return 1;
|
||||
}
|
||||
return mSpanSizeLookup.getSpanSize(adapterPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
|
||||
LayoutState layoutState, LayoutChunkResult result) {
|
||||
final boolean layingOutInPrimaryDirection =
|
||||
layoutState.mItemDirection == LayoutState.ITEM_DIRECTION_TAIL;
|
||||
int count = 0;
|
||||
int consumedSpanCount = 0;
|
||||
int remainingSpan = mSpanCount;
|
||||
if (!layingOutInPrimaryDirection) {
|
||||
int itemSpanIndex = getSpanIndex(recycler, state, layoutState.mCurrentPosition);
|
||||
int itemSpanSize = getSpanSize(recycler, state, layoutState.mCurrentPosition);
|
||||
remainingSpan = itemSpanIndex + itemSpanSize;
|
||||
}
|
||||
while (count < mSpanCount && layoutState.hasMore(state) && remainingSpan > 0) {
|
||||
int pos = layoutState.mCurrentPosition;
|
||||
final int spanSize = getSpanSize(recycler, state, pos);
|
||||
if (spanSize > mSpanCount) {
|
||||
throw new IllegalArgumentException("Item at position " + pos + " requires " +
|
||||
spanSize + " spans but GridLayoutManager has only " + mSpanCount
|
||||
+ " spans.");
|
||||
}
|
||||
remainingSpan -= spanSize;
|
||||
if (remainingSpan < 0) {
|
||||
break; // item did not fit into this row or column
|
||||
}
|
||||
View view = layoutState.next(recycler);
|
||||
if (view == null) {
|
||||
break;
|
||||
}
|
||||
consumedSpanCount += spanSize;
|
||||
mSet[count] = view;
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
result.mFinished = true;
|
||||
return;
|
||||
}
|
||||
|
||||
int maxSize = 0;
|
||||
|
||||
// we should assign spans before item decor offsets are calculated
|
||||
assignSpans(recycler, state, count, consumedSpanCount, layingOutInPrimaryDirection);
|
||||
for (int i = 0; i < count; i++) {
|
||||
View view = mSet[i];
|
||||
if (layoutState.mScrapList == null) {
|
||||
if (layingOutInPrimaryDirection) {
|
||||
addView(view);
|
||||
} else {
|
||||
addView(view, 0);
|
||||
}
|
||||
} else {
|
||||
if (layingOutInPrimaryDirection) {
|
||||
addDisappearingView(view);
|
||||
} else {
|
||||
addDisappearingView(view, 0);
|
||||
}
|
||||
}
|
||||
|
||||
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
|
||||
final int spec = View.MeasureSpec.makeMeasureSpec(
|
||||
mCachedBorders[lp.mSpanIndex + lp.mSpanSize] -
|
||||
mCachedBorders[lp.mSpanIndex],
|
||||
View.MeasureSpec.EXACTLY);
|
||||
if (mOrientation == VERTICAL) {
|
||||
measureChildWithDecorationsAndMargin(view, spec, getMainDirSpec(lp.height));
|
||||
} else {
|
||||
measureChildWithDecorationsAndMargin(view, getMainDirSpec(lp.width), spec);
|
||||
}
|
||||
final int size = mOrientationHelper.getDecoratedMeasurement(view);
|
||||
if (size > maxSize) {
|
||||
maxSize = size;
|
||||
}
|
||||
}
|
||||
|
||||
// views that did not measure the maxSize has to be re-measured
|
||||
final int maxMeasureSpec = getMainDirSpec(maxSize);
|
||||
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 = View.MeasureSpec.makeMeasureSpec(
|
||||
mCachedBorders[lp.mSpanIndex + lp.mSpanSize] -
|
||||
mCachedBorders[lp.mSpanIndex],
|
||||
View.MeasureSpec.EXACTLY);
|
||||
if (mOrientation == VERTICAL) {
|
||||
measureChildWithDecorationsAndMargin(view, spec, maxMeasureSpec);
|
||||
} else {
|
||||
measureChildWithDecorationsAndMargin(view, maxMeasureSpec, spec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.mConsumed = maxSize;
|
||||
|
||||
int left = 0, right = 0, top = 0, bottom = 0;
|
||||
if (mOrientation == VERTICAL) {
|
||||
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
|
||||
bottom = layoutState.mOffset;
|
||||
top = bottom - maxSize;
|
||||
} else {
|
||||
top = layoutState.mOffset;
|
||||
bottom = top + maxSize;
|
||||
}
|
||||
} else {
|
||||
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
|
||||
right = layoutState.mOffset;
|
||||
left = right - maxSize;
|
||||
} else {
|
||||
left = layoutState.mOffset;
|
||||
right = left + maxSize;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < count; i++) {
|
||||
View view = mSet[i];
|
||||
LayoutParams params = (LayoutParams) view.getLayoutParams();
|
||||
if (mOrientation == VERTICAL) {
|
||||
left = getPaddingLeft() + mCachedBorders[params.mSpanIndex];
|
||||
right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
|
||||
} else {
|
||||
top = getPaddingTop() + mCachedBorders[params.mSpanIndex];
|
||||
bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
|
||||
}
|
||||
// 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);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
|
||||
+ (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
|
||||
+ (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin)
|
||||
+ ", span:" + params.mSpanIndex + ", spanSize:" + params.mSpanSize);
|
||||
}
|
||||
// Consume the available space if the view is not removed OR changed
|
||||
if (params.isItemRemoved() || params.isItemChanged()) {
|
||||
result.mIgnoreConsumed = true;
|
||||
}
|
||||
result.mFocusable |= view.isFocusable();
|
||||
}
|
||||
Arrays.fill(mSet, null);
|
||||
}
|
||||
|
||||
private int getMainDirSpec(int dim) {
|
||||
if (dim < 0) {
|
||||
return MAIN_DIR_SPEC;
|
||||
} else {
|
||||
return View.MeasureSpec.makeMeasureSpec(dim, View.MeasureSpec.EXACTLY);
|
||||
}
|
||||
}
|
||||
|
||||
private void measureChildWithDecorationsAndMargin(View child, int widthSpec, int heightSpec) {
|
||||
calculateItemDecorationsForChild(child, mDecorInsets);
|
||||
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
|
||||
widthSpec = updateSpecWithExtra(widthSpec, lp.leftMargin + mDecorInsets.left,
|
||||
lp.rightMargin + mDecorInsets.right);
|
||||
heightSpec = updateSpecWithExtra(heightSpec, lp.topMargin + mDecorInsets.top,
|
||||
lp.bottomMargin + mDecorInsets.bottom);
|
||||
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(
|
||||
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;
|
||||
// make sure we traverse from min position to max position
|
||||
if (layingOutInPrimaryDirection) {
|
||||
start = 0;
|
||||
end = count;
|
||||
diff = 1;
|
||||
} else {
|
||||
start = count - 1;
|
||||
end = -1;
|
||||
diff = -1;
|
||||
}
|
||||
if (mOrientation == VERTICAL && isLayoutRTL()) { // start from last span
|
||||
span = mSpanCount - 1;
|
||||
spanDiff = -1;
|
||||
} else {
|
||||
span = 0;
|
||||
spanDiff = 1;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of spans laid out by this grid.
|
||||
*
|
||||
* @return The number of spans
|
||||
* @see #setSpanCount(int)
|
||||
*/
|
||||
public int getSpanCount() {
|
||||
return mSpanCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of spans to be laid out.
|
||||
* <p>
|
||||
* If {@link #getOrientation()} is {@link #VERTICAL}, this is the number of columns.
|
||||
* If {@link #getOrientation()} is {@link #HORIZONTAL}, this is the number of rows.
|
||||
*
|
||||
* @param spanCount The total number of spans in the grid
|
||||
* @see #getSpanCount()
|
||||
*/
|
||||
public void setSpanCount(int spanCount) {
|
||||
if (spanCount == mSpanCount) {
|
||||
return;
|
||||
}
|
||||
mPendingSpanCountChange = true;
|
||||
if (spanCount < 1) {
|
||||
throw new IllegalArgumentException("Span count should be at least 1. Provided "
|
||||
+ spanCount);
|
||||
}
|
||||
mSpanCount = spanCount;
|
||||
mSpanSizeLookup.invalidateSpanIndexCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper class to provide the number of spans each item occupies.
|
||||
* <p>
|
||||
* Default implementation sets each item to occupy exactly 1 span.
|
||||
*
|
||||
* @see GridLayoutManager#setSpanSizeLookup(SpanSizeLookup)
|
||||
*/
|
||||
public static abstract class SpanSizeLookup {
|
||||
|
||||
final SparseIntArray mSpanIndexCache = new SparseIntArray();
|
||||
|
||||
private boolean mCacheSpanIndices = false;
|
||||
|
||||
/**
|
||||
* Returns the number of span occupied by the item at <code>position</code>.
|
||||
*
|
||||
* @param position The adapter position of the item
|
||||
* @return The number of spans occupied by the item at the provided position
|
||||
*/
|
||||
abstract public int getSpanSize(int position);
|
||||
|
||||
/**
|
||||
* Sets whether the results of {@link #getSpanIndex(int, int)} method should be cached or
|
||||
* not. By default these values are not cached. If you are not overriding
|
||||
* {@link #getSpanIndex(int, int)}, you should set this to true for better performance.
|
||||
*
|
||||
* @param cacheSpanIndices Whether results of getSpanIndex should be cached or not.
|
||||
*/
|
||||
public void setSpanIndexCacheEnabled(boolean cacheSpanIndices) {
|
||||
mCacheSpanIndices = cacheSpanIndices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the span index cache. GridLayoutManager automatically calls this method when
|
||||
* adapter changes occur.
|
||||
*/
|
||||
public void invalidateSpanIndexCache() {
|
||||
mSpanIndexCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether results of {@link #getSpanIndex(int, int)} method are cached or not.
|
||||
*
|
||||
* @return True if results of {@link #getSpanIndex(int, int)} are cached.
|
||||
*/
|
||||
public boolean isSpanIndexCacheEnabled() {
|
||||
return mCacheSpanIndices;
|
||||
}
|
||||
|
||||
int getCachedSpanIndex(int position, int spanCount) {
|
||||
if (!mCacheSpanIndices) {
|
||||
return getSpanIndex(position, spanCount);
|
||||
}
|
||||
final int existing = mSpanIndexCache.get(position, -1);
|
||||
if (existing != -1) {
|
||||
return existing;
|
||||
}
|
||||
final int value = getSpanIndex(position, spanCount);
|
||||
mSpanIndexCache.put(position, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the final span index of the provided position.
|
||||
* <p>
|
||||
* If you have a faster way to calculate span index for your items, you should override
|
||||
* this method. Otherwise, you should enable span index cache
|
||||
* ({@link #setSpanIndexCacheEnabled(boolean)}) for better performance. When caching is
|
||||
* disabled, default implementation traverses all items from 0 to
|
||||
* <code>position</code>. When caching is enabled, it calculates from the closest cached
|
||||
* value before the <code>position</code>.
|
||||
* <p>
|
||||
* If you override this method, you need to make sure it is consistent with
|
||||
* {@link #getSpanSize(int)}. GridLayoutManager does not call this method for
|
||||
* each item. It is called only for the reference item and rest of the items
|
||||
* are assigned to spans based on the reference item. For example, you cannot assign a
|
||||
* position to span 2 while span 1 is empty.
|
||||
* <p>
|
||||
* Note that span offsets always start with 0 and are not affected by RTL.
|
||||
*
|
||||
* @param position The position of the item
|
||||
* @param spanCount The total number of spans in the grid
|
||||
* @return The final span position of the item. Should be between 0 (inclusive) and
|
||||
* <code>spanCount</code>(exclusive)
|
||||
*/
|
||||
public int getSpanIndex(int position, int spanCount) {
|
||||
int positionSpanSize = getSpanSize(position);
|
||||
if (positionSpanSize == spanCount) {
|
||||
return 0; // quick return for full-span items
|
||||
}
|
||||
int span = 0;
|
||||
int startPos = 0;
|
||||
// If caching is enabled, try to jump
|
||||
if (mCacheSpanIndices && mSpanIndexCache.size() > 0) {
|
||||
int prevKey = findReferenceIndexFromCache(position);
|
||||
if (prevKey >= 0) {
|
||||
span = mSpanIndexCache.get(prevKey) + getSpanSize(prevKey);
|
||||
startPos = prevKey + 1;
|
||||
}
|
||||
}
|
||||
for (int i = startPos; i < position; i++) {
|
||||
int size = getSpanSize(i);
|
||||
span += size;
|
||||
if (span == spanCount) {
|
||||
span = 0;
|
||||
} else if (span > spanCount) {
|
||||
// did not fit, moving to next row / column
|
||||
span = size;
|
||||
}
|
||||
}
|
||||
if (span + positionSpanSize <= spanCount) {
|
||||
return span;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int findReferenceIndexFromCache(int position) {
|
||||
int lo = 0;
|
||||
int hi = mSpanIndexCache.size() - 1;
|
||||
|
||||
while (lo <= hi) {
|
||||
final int mid = (lo + hi) >>> 1;
|
||||
final int midVal = mSpanIndexCache.keyAt(mid);
|
||||
if (midVal < position) {
|
||||
lo = mid + 1;
|
||||
} else {
|
||||
hi = mid - 1;
|
||||
}
|
||||
}
|
||||
int index = lo - 1;
|
||||
if (index >= 0 && index < mSpanIndexCache.size()) {
|
||||
return mSpanIndexCache.keyAt(index);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the group this position belongs.
|
||||
* <p>
|
||||
* For example, if grid has 3 columns and each item occupies 1 span, span group index
|
||||
* for item 1 will be 0, item 5 will be 1.
|
||||
*
|
||||
* @param adapterPosition The position in adapter
|
||||
* @param spanCount The total number of spans in the grid
|
||||
* @return The index of the span group including the item at the given adapter position
|
||||
*/
|
||||
public int getSpanGroupIndex(int adapterPosition, int spanCount) {
|
||||
int span = 0;
|
||||
int group = 0;
|
||||
int positionSpanSize = getSpanSize(adapterPosition);
|
||||
for (int i = 0; i < adapterPosition; i++) {
|
||||
int size = getSpanSize(i);
|
||||
span += size;
|
||||
if (span == spanCount) {
|
||||
span = 0;
|
||||
group++;
|
||||
} else if (span > spanCount) {
|
||||
// did not fit, moving to next row / column
|
||||
span = size;
|
||||
group++;
|
||||
}
|
||||
}
|
||||
if (span + positionSpanSize > spanCount) {
|
||||
group++;
|
||||
}
|
||||
return group;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsPredictiveItemAnimations() {
|
||||
return mPendingSavedState == null && !mPendingSpanCountChange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation for {@link SpanSizeLookup}. Each item occupies 1 span.
|
||||
*/
|
||||
public static final class DefaultSpanSizeLookup extends SpanSizeLookup {
|
||||
|
||||
@Override
|
||||
public int getSpanSize(int position) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSpanIndex(int position, int spanCount) {
|
||||
return position % spanCount;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LayoutParams used by GridLayoutManager.
|
||||
* <p>
|
||||
* Note that if the orientation is {@link #VERTICAL}, the width parameter is ignored and if the
|
||||
* orientation is {@link #HORIZONTAL} the height parameter is ignored because child view is
|
||||
* expected to fill all of the space given to it.
|
||||
*/
|
||||
public static class LayoutParams extends RecyclerView.LayoutParams {
|
||||
|
||||
/**
|
||||
* Span Id for Views that are not laid out yet.
|
||||
*/
|
||||
public static final int INVALID_SPAN_ID = -1;
|
||||
|
||||
private int mSpanIndex = INVALID_SPAN_ID;
|
||||
|
||||
private int mSpanSize = 0;
|
||||
|
||||
public LayoutParams(Context c, AttributeSet attrs) {
|
||||
super(c, attrs);
|
||||
}
|
||||
|
||||
public LayoutParams(int width, int height) {
|
||||
super(width, height);
|
||||
}
|
||||
|
||||
public LayoutParams(ViewGroup.MarginLayoutParams source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
public LayoutParams(ViewGroup.LayoutParams source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
public LayoutParams(RecyclerView.LayoutParams source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current span index of this View. If the View is not laid out yet, the return
|
||||
* value is <code>undefined</code>.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* If the View occupies multiple spans, span with the minimum index is returned.
|
||||
*
|
||||
* @return The span index of the View.
|
||||
*/
|
||||
public int getSpanIndex() {
|
||||
return mSpanIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of spans occupied by this View. If the View not laid out yet, the
|
||||
* return value is <code>undefined</code>.
|
||||
*
|
||||
* @return The number of spans occupied by this View.
|
||||
*/
|
||||
public int getSpanSize() {
|
||||
return mSpanSize;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,102 +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 languag`e governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.android.support.widget;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* Helper class that keeps temporary state while {LayoutManager} is filling out the empty
|
||||
* space.
|
||||
*/
|
||||
class LayoutState {
|
||||
|
||||
final static String TAG = "LayoutState";
|
||||
|
||||
final static int LAYOUT_START = -1;
|
||||
|
||||
final static int LAYOUT_END = 1;
|
||||
|
||||
final static int INVALID_LAYOUT = Integer.MIN_VALUE;
|
||||
|
||||
final static int ITEM_DIRECTION_HEAD = -1;
|
||||
|
||||
final static int ITEM_DIRECTION_TAIL = 1;
|
||||
|
||||
final static int SCOLLING_OFFSET_NaN = Integer.MIN_VALUE;
|
||||
|
||||
/**
|
||||
* Number of pixels that we should fill, in the layout direction.
|
||||
*/
|
||||
int mAvailable;
|
||||
|
||||
/**
|
||||
* Current position on the adapter to get the next item.
|
||||
*/
|
||||
int mCurrentPosition;
|
||||
|
||||
/**
|
||||
* Defines the direction in which the data adapter is traversed.
|
||||
* Should be {@link #ITEM_DIRECTION_HEAD} or {@link #ITEM_DIRECTION_TAIL}
|
||||
*/
|
||||
int mItemDirection;
|
||||
|
||||
/**
|
||||
* Defines the direction in which the layout is filled.
|
||||
* Should be {@link #LAYOUT_START} or {@link #LAYOUT_END}
|
||||
*/
|
||||
int mLayoutDirection;
|
||||
|
||||
/**
|
||||
* This is the target pixel closest to the start of the layout that we are trying to fill
|
||||
*/
|
||||
int mStartLine = 0;
|
||||
|
||||
/**
|
||||
* This is the target pixel closest to the end of the layout that we are trying to fill
|
||||
*/
|
||||
int mEndLine = 0;
|
||||
|
||||
/**
|
||||
* @return true if there are more items in the data adapter
|
||||
*/
|
||||
boolean hasMore(RecyclerView.State state) {
|
||||
return mCurrentPosition >= 0 && mCurrentPosition < state.getItemCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the view for the next element that we should render.
|
||||
* Also updates current item index to the next item, based on {@link #mItemDirection}
|
||||
*
|
||||
* @return The next element that we should render.
|
||||
*/
|
||||
View next(RecyclerView.Recycler recycler) {
|
||||
final View view = recycler.getViewForPosition(mCurrentPosition);
|
||||
mCurrentPosition += mItemDirection;
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LayoutState{" +
|
||||
"mAvailable=" + mAvailable +
|
||||
", mCurrentPosition=" + mCurrentPosition +
|
||||
", mItemDirection=" + mItemDirection +
|
||||
", mLayoutDirection=" + mLayoutDirection +
|
||||
", mStartLine=" + mStartLine +
|
||||
", mEndLine=" + mEndLine +
|
||||
'}';
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,338 +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.android.support.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.PointF;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
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.
|
||||
*/
|
||||
abstract public class LinearSmoothScroller extends RecyclerView.SmoothScroller {
|
||||
|
||||
private static final String TAG = "LinearSmoothScroller";
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final float MILLISECONDS_PER_INCH = 25f;
|
||||
|
||||
private static final int TARGET_SEEK_SCROLL_DISTANCE_PX = 10000;
|
||||
|
||||
/**
|
||||
* Align child view's left or top with parent view's left or top
|
||||
*
|
||||
* @see #calculateDtToFit(int, int, int, int, int)
|
||||
* @see #calculateDxToMakeVisible(android.view.View, int)
|
||||
* @see #calculateDyToMakeVisible(android.view.View, int)
|
||||
*/
|
||||
public static final int SNAP_TO_START = -1;
|
||||
|
||||
/**
|
||||
* Align child view's right or bottom with parent view's right or bottom
|
||||
*
|
||||
* @see #calculateDtToFit(int, int, int, int, int)
|
||||
* @see #calculateDxToMakeVisible(android.view.View, int)
|
||||
* @see #calculateDyToMakeVisible(android.view.View, int)
|
||||
*/
|
||||
public static final int SNAP_TO_END = 1;
|
||||
|
||||
/**
|
||||
* <p>Decides if the child should be snapped from start or end, depending on where it
|
||||
* currently is in relation to its parent.</p>
|
||||
* <p>For instance, if the view is virtually on the left of RecyclerView, using
|
||||
* {@code SNAP_TO_ANY} is the same as using {@code SNAP_TO_START}</p>
|
||||
*
|
||||
* @see #calculateDtToFit(int, int, int, int, int)
|
||||
* @see #calculateDxToMakeVisible(android.view.View, int)
|
||||
* @see #calculateDyToMakeVisible(android.view.View, int)
|
||||
*/
|
||||
public static final int SNAP_TO_ANY = 0;
|
||||
|
||||
// Trigger a scroll to a further distance than TARGET_SEEK_SCROLL_DISTANCE_PX so that if target
|
||||
// view is not laid out until interim target position is reached, we can detect the case before
|
||||
// scrolling slows down and reschedule another interim target scroll
|
||||
private static final float TARGET_SEEK_EXTRA_SCROLL_RATIO = 1.2f;
|
||||
|
||||
protected final LinearInterpolator mLinearInterpolator = new LinearInterpolator();
|
||||
|
||||
protected final DecelerateInterpolator mDecelerateInterpolator = new DecelerateInterpolator();
|
||||
|
||||
protected PointF mTargetVector;
|
||||
|
||||
private final float MILLISECONDS_PER_PX;
|
||||
|
||||
// Temporary variables to keep track of the interim scroll target. These values do not
|
||||
// point to a real item position, rather point to an estimated location pixels.
|
||||
protected int mInterimTargetDx = 0, mInterimTargetDy = 0;
|
||||
|
||||
public LinearSmoothScroller(Context context) {
|
||||
MILLISECONDS_PER_PX = calculateSpeedPerPixel(context.getResources().getDisplayMetrics());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void onStart() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
|
||||
final int dx = calculateDxToMakeVisible(targetView, getHorizontalSnapPreference());
|
||||
final int dy = calculateDyToMakeVisible(targetView, getVerticalSnapPreference());
|
||||
final int distance = (int) Math.sqrt(dx * dx + dy * dy);
|
||||
final int time = calculateTimeForDeceleration(distance);
|
||||
if (time > 0) {
|
||||
action.update(-dx, -dy, time, mDecelerateInterpolator);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void onSeekTargetStep(int dx, int dy, RecyclerView.State state, Action action) {
|
||||
if (getChildCount() == 0) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
if (DEBUG && mTargetVector != null
|
||||
&& ((mTargetVector.x * dx < 0 || mTargetVector.y * dy < 0))) {
|
||||
throw new IllegalStateException("Scroll happened in the opposite direction"
|
||||
+ " of the target. Some calculations are wrong");
|
||||
}
|
||||
mInterimTargetDx = clampApplyScroll(mInterimTargetDx, dx);
|
||||
mInterimTargetDy = clampApplyScroll(mInterimTargetDy, dy);
|
||||
|
||||
if (mInterimTargetDx == 0 && mInterimTargetDy == 0) {
|
||||
updateActionForInterimTarget(action);
|
||||
} // everything is valid, keep going
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void onStop() {
|
||||
mInterimTargetDx = mInterimTargetDy = 0;
|
||||
mTargetVector = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the scroll speed.
|
||||
*
|
||||
* @param displayMetrics DisplayMetrics to be used for real dimension calculations
|
||||
* @return The time (in ms) it should take for each pixel. For instance, if returned value is
|
||||
* 2 ms, it means scrolling 1000 pixels with LinearInterpolation should take 2 seconds.
|
||||
*/
|
||||
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
|
||||
return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Calculates the time for deceleration so that transition from LinearInterpolator to
|
||||
* DecelerateInterpolator looks smooth.</p>
|
||||
*
|
||||
* @param dx Distance to scroll
|
||||
* @return Time for DecelerateInterpolator to smoothly traverse the distance when transitioning
|
||||
* from LinearInterpolation
|
||||
*/
|
||||
protected int calculateTimeForDeceleration(int dx) {
|
||||
// we want to cover same area with the linear interpolator for the first 10% of the
|
||||
// interpolation. After that, deceleration will take control.
|
||||
// area under curve (1-(1-x)^2) can be calculated as (1 - x/3) * x * x
|
||||
// which gives 0.100028 when x = .3356
|
||||
// this is why we divide linear scrolling time with .3356
|
||||
return (int) Math.ceil(calculateTimeForScrolling(dx) / .3356);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the time it should take to scroll the given distance (in pixels)
|
||||
*
|
||||
* @param dx Distance in pixels that we want to scroll
|
||||
* @return Time in milliseconds
|
||||
* @see #calculateSpeedPerPixel(android.util.DisplayMetrics)
|
||||
*/
|
||||
protected int calculateTimeForScrolling(int dx) {
|
||||
// In a case where dx is very small, rounding may return 0 although dx > 0.
|
||||
// To avoid that issue, ceil the result so that if dx > 0, we'll always return positive
|
||||
// time.
|
||||
return (int) Math.ceil(Math.abs(dx) * MILLISECONDS_PER_PX);
|
||||
}
|
||||
|
||||
/**
|
||||
* When scrolling towards a child view, this method defines whether we should align the left
|
||||
* or the right edge of the child with the parent RecyclerView.
|
||||
*
|
||||
* @return SNAP_TO_START, SNAP_TO_END or SNAP_TO_ANY; depending on the current target vector
|
||||
* @see #SNAP_TO_START
|
||||
* @see #SNAP_TO_END
|
||||
* @see #SNAP_TO_ANY
|
||||
*/
|
||||
protected int getHorizontalSnapPreference() {
|
||||
return mTargetVector == null || mTargetVector.x == 0 ? SNAP_TO_ANY :
|
||||
mTargetVector.x > 0 ? SNAP_TO_END : SNAP_TO_START;
|
||||
}
|
||||
|
||||
/**
|
||||
* When scrolling towards a child view, this method defines whether we should align the top
|
||||
* or the bottom edge of the child with the parent RecyclerView.
|
||||
*
|
||||
* @return SNAP_TO_START, SNAP_TO_END or SNAP_TO_ANY; depending on the current target vector
|
||||
* @see #SNAP_TO_START
|
||||
* @see #SNAP_TO_END
|
||||
* @see #SNAP_TO_ANY
|
||||
*/
|
||||
protected int getVerticalSnapPreference() {
|
||||
return mTargetVector == null || mTargetVector.y == 0 ? SNAP_TO_ANY :
|
||||
mTargetVector.y > 0 ? SNAP_TO_END : SNAP_TO_START;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the target scroll position is not a child of the RecyclerView, this method calculates
|
||||
* a direction vector towards that child and triggers a smooth scroll.
|
||||
*
|
||||
* @see #computeScrollVectorForPosition(int)
|
||||
*/
|
||||
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();
|
||||
stop();
|
||||
instantScrollToPosition(target);
|
||||
return;
|
||||
}
|
||||
normalize(scrollVector);
|
||||
mTargetVector = scrollVector;
|
||||
|
||||
mInterimTargetDx = (int) (TARGET_SEEK_SCROLL_DISTANCE_PX * scrollVector.x);
|
||||
mInterimTargetDy = (int) (TARGET_SEEK_SCROLL_DISTANCE_PX * scrollVector.y);
|
||||
final int time = calculateTimeForScrolling(TARGET_SEEK_SCROLL_DISTANCE_PX);
|
||||
// To avoid UI hiccups, trigger a smooth scroll to a distance little further than the
|
||||
// interim target. Since we track the distance travelled in onSeekTargetStep callback, it
|
||||
// won't actually scroll more than what we need.
|
||||
action.update((int) (mInterimTargetDx * TARGET_SEEK_EXTRA_SCROLL_RATIO)
|
||||
, (int) (mInterimTargetDy * TARGET_SEEK_EXTRA_SCROLL_RATIO)
|
||||
, (int) (time * TARGET_SEEK_EXTRA_SCROLL_RATIO), mLinearInterpolator);
|
||||
}
|
||||
|
||||
private int clampApplyScroll(int tmpDt, int dt) {
|
||||
final int before = tmpDt;
|
||||
tmpDt -= dt;
|
||||
if (before * tmpDt <= 0) { // changed sign, reached 0 or was 0, reset
|
||||
return 0;
|
||||
}
|
||||
return tmpDt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for {@link #calculateDxToMakeVisible(android.view.View, int)} and
|
||||
* {@link #calculateDyToMakeVisible(android.view.View, int)}
|
||||
*/
|
||||
public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int
|
||||
snapPreference) {
|
||||
switch (snapPreference) {
|
||||
case SNAP_TO_START:
|
||||
return boxStart - viewStart;
|
||||
case SNAP_TO_END:
|
||||
return boxEnd - viewEnd;
|
||||
case SNAP_TO_ANY:
|
||||
final int dtStart = boxStart - viewStart;
|
||||
if (dtStart > 0) {
|
||||
return dtStart;
|
||||
}
|
||||
final int dtEnd = boxEnd - viewEnd;
|
||||
if (dtEnd < 0) {
|
||||
return dtEnd;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("snap preference should be one of the"
|
||||
+ " constants defined in SmoothScroller, starting with SNAP_");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the vertical scroll amount necessary to make the given view fully visible
|
||||
* inside the RecyclerView.
|
||||
*
|
||||
* @param view The view which we want to make fully visible
|
||||
* @param snapPreference The edge which the view should snap to when entering the visible
|
||||
* area. One of {@link #SNAP_TO_START}, {@link #SNAP_TO_END} or
|
||||
* {@link #SNAP_TO_END}.
|
||||
* @return The vertical scroll amount necessary to make the view visible with the given
|
||||
* snap preference.
|
||||
*/
|
||||
public int calculateDyToMakeVisible(View view, int snapPreference) {
|
||||
final RecyclerView.LayoutManager layoutManager = getLayoutManager();
|
||||
if (!layoutManager.canScrollVertically()) {
|
||||
return 0;
|
||||
}
|
||||
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
|
||||
view.getLayoutParams();
|
||||
final int top = layoutManager.getDecoratedTop(view) - params.topMargin;
|
||||
final int bottom = layoutManager.getDecoratedBottom(view) + params.bottomMargin;
|
||||
final int start = layoutManager.getPaddingTop();
|
||||
final int end = layoutManager.getHeight() - layoutManager.getPaddingBottom();
|
||||
return calculateDtToFit(top, bottom, start, end, snapPreference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the horizontal scroll amount necessary to make the given view fully visible
|
||||
* inside the RecyclerView.
|
||||
*
|
||||
* @param view The view which we want to make fully visible
|
||||
* @param snapPreference The edge which the view should snap to when entering the visible
|
||||
* area. One of {@link #SNAP_TO_START}, {@link #SNAP_TO_END} or
|
||||
* {@link #SNAP_TO_END}
|
||||
* @return The vertical scroll amount necessary to make the view visible with the given
|
||||
* snap preference.
|
||||
*/
|
||||
public int calculateDxToMakeVisible(View view, int snapPreference) {
|
||||
final RecyclerView.LayoutManager layoutManager = getLayoutManager();
|
||||
if (!layoutManager.canScrollHorizontally()) {
|
||||
return 0;
|
||||
}
|
||||
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
|
||||
view.getLayoutParams();
|
||||
final int left = layoutManager.getDecoratedLeft(view) - params.leftMargin;
|
||||
final int right = layoutManager.getDecoratedRight(view) + params.rightMargin;
|
||||
final int start = layoutManager.getPaddingLeft();
|
||||
final int end = layoutManager.getWidth() - layoutManager.getPaddingRight();
|
||||
return calculateDtToFit(left, right, start, end, snapPreference);
|
||||
}
|
||||
|
||||
abstract public PointF computeScrollVectorForPosition(int targetPosition);
|
||||
}
|
@ -1,237 +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.android.support.widget;
|
||||
|
||||
import org.telegram.android.support.widget.AdapterHelper.UpdateOp;
|
||||
import static org.telegram.android.support.widget.AdapterHelper.UpdateOp.ADD;
|
||||
import static org.telegram.android.support.widget.AdapterHelper.UpdateOp.MOVE;
|
||||
import static org.telegram.android.support.widget.AdapterHelper.UpdateOp.REMOVE;
|
||||
import static org.telegram.android.support.widget.AdapterHelper.UpdateOp.UPDATE;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
class OpReorderer {
|
||||
|
||||
final Callback mCallback;
|
||||
|
||||
public OpReorderer(Callback callback) {
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
void reorderOps(List<UpdateOp> ops) {
|
||||
// since move operations breaks continuity, their effects on ADD/RM are hard to handle.
|
||||
// we push them to the end of the list so that they can be handled easily.
|
||||
int badMove;
|
||||
while ((badMove = getLastMoveOutOfOrder(ops)) != -1) {
|
||||
swapMoveOp(ops, badMove, badMove + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void swapMoveOp(List<UpdateOp> list, int badMove, int next) {
|
||||
final UpdateOp moveOp = list.get(badMove);
|
||||
final UpdateOp nextOp = list.get(next);
|
||||
switch (nextOp.cmd) {
|
||||
case REMOVE:
|
||||
swapMoveRemove(list, badMove, moveOp, next, nextOp);
|
||||
break;
|
||||
case ADD:
|
||||
swapMoveAdd(list, badMove, moveOp, next, nextOp);
|
||||
break;
|
||||
case UPDATE:
|
||||
swapMoveUpdate(list, badMove, moveOp, next, nextOp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void swapMoveRemove(List<UpdateOp> list, int movePos, UpdateOp moveOp,
|
||||
int removePos, UpdateOp removeOp) {
|
||||
UpdateOp extraRm = null;
|
||||
// check if move is nulled out by remove
|
||||
boolean revertedMove = false;
|
||||
final boolean moveIsBackwards;
|
||||
|
||||
if (moveOp.positionStart < moveOp.itemCount) {
|
||||
moveIsBackwards = false;
|
||||
if (removeOp.positionStart == moveOp.positionStart
|
||||
&& removeOp.itemCount == moveOp.itemCount - moveOp.positionStart) {
|
||||
revertedMove = true;
|
||||
}
|
||||
} else {
|
||||
moveIsBackwards = true;
|
||||
if (removeOp.positionStart == moveOp.itemCount + 1 &&
|
||||
removeOp.itemCount == moveOp.positionStart - moveOp.itemCount) {
|
||||
revertedMove = true;
|
||||
}
|
||||
}
|
||||
|
||||
// going in reverse, first revert the effect of add
|
||||
if (moveOp.itemCount < removeOp.positionStart) {
|
||||
removeOp.positionStart--;
|
||||
} else if (moveOp.itemCount < removeOp.positionStart + removeOp.itemCount) {
|
||||
// move is removed.
|
||||
removeOp.itemCount--;
|
||||
moveOp.cmd = REMOVE;
|
||||
moveOp.itemCount = 1;
|
||||
if (removeOp.itemCount == 0) {
|
||||
list.remove(removePos);
|
||||
mCallback.recycleUpdateOp(removeOp);
|
||||
}
|
||||
// no need to swap, it is already a remove
|
||||
return;
|
||||
}
|
||||
|
||||
// now affect of add is consumed. now apply effect of first remove
|
||||
if (moveOp.positionStart <= removeOp.positionStart) {
|
||||
removeOp.positionStart++;
|
||||
} else if (moveOp.positionStart < removeOp.positionStart + removeOp.itemCount) {
|
||||
final int remaining = removeOp.positionStart + removeOp.itemCount
|
||||
- moveOp.positionStart;
|
||||
extraRm = mCallback.obtainUpdateOp(REMOVE, moveOp.positionStart + 1, remaining);
|
||||
removeOp.itemCount = moveOp.positionStart - removeOp.positionStart;
|
||||
}
|
||||
|
||||
// if effects of move is reverted by remove, we are done.
|
||||
if (revertedMove) {
|
||||
list.set(movePos, removeOp);
|
||||
list.remove(removePos);
|
||||
mCallback.recycleUpdateOp(moveOp);
|
||||
return;
|
||||
}
|
||||
|
||||
// now find out the new locations for move actions
|
||||
if (moveIsBackwards) {
|
||||
if (extraRm != null) {
|
||||
if (moveOp.positionStart > extraRm.positionStart) {
|
||||
moveOp.positionStart -= extraRm.itemCount;
|
||||
}
|
||||
if (moveOp.itemCount > extraRm.positionStart) {
|
||||
moveOp.itemCount -= extraRm.itemCount;
|
||||
}
|
||||
}
|
||||
if (moveOp.positionStart > removeOp.positionStart) {
|
||||
moveOp.positionStart -= removeOp.itemCount;
|
||||
}
|
||||
if (moveOp.itemCount > removeOp.positionStart) {
|
||||
moveOp.itemCount -= removeOp.itemCount;
|
||||
}
|
||||
} else {
|
||||
if (extraRm != null) {
|
||||
if (moveOp.positionStart >= extraRm.positionStart) {
|
||||
moveOp.positionStart -= extraRm.itemCount;
|
||||
}
|
||||
if (moveOp.itemCount >= extraRm.positionStart) {
|
||||
moveOp.itemCount -= extraRm.itemCount;
|
||||
}
|
||||
}
|
||||
if (moveOp.positionStart >= removeOp.positionStart) {
|
||||
moveOp.positionStart -= removeOp.itemCount;
|
||||
}
|
||||
if (moveOp.itemCount >= removeOp.positionStart) {
|
||||
moveOp.itemCount -= removeOp.itemCount;
|
||||
}
|
||||
}
|
||||
|
||||
list.set(movePos, removeOp);
|
||||
if (moveOp.positionStart != moveOp.itemCount) {
|
||||
list.set(removePos, moveOp);
|
||||
} else {
|
||||
list.remove(removePos);
|
||||
}
|
||||
if (extraRm != null) {
|
||||
list.add(movePos, extraRm);
|
||||
}
|
||||
}
|
||||
|
||||
private void swapMoveAdd(List<UpdateOp> list, int move, UpdateOp moveOp, int add,
|
||||
UpdateOp addOp) {
|
||||
int offset = 0;
|
||||
// going in reverse, first revert the effect of add
|
||||
if (moveOp.itemCount < addOp.positionStart) {
|
||||
offset--;
|
||||
}
|
||||
if (moveOp.positionStart < addOp.positionStart) {
|
||||
offset++;
|
||||
}
|
||||
if (addOp.positionStart <= moveOp.positionStart) {
|
||||
moveOp.positionStart += addOp.itemCount;
|
||||
}
|
||||
if (addOp.positionStart <= moveOp.itemCount) {
|
||||
moveOp.itemCount += addOp.itemCount;
|
||||
}
|
||||
addOp.positionStart += offset;
|
||||
list.set(move, addOp);
|
||||
list.set(add, moveOp);
|
||||
}
|
||||
|
||||
void swapMoveUpdate(List<UpdateOp> list, int move, UpdateOp moveOp, int update,
|
||||
UpdateOp updateOp) {
|
||||
UpdateOp extraUp1 = null;
|
||||
UpdateOp extraUp2 = null;
|
||||
// going in reverse, first revert the effect of add
|
||||
if (moveOp.itemCount < updateOp.positionStart) {
|
||||
updateOp.positionStart--;
|
||||
} else if (moveOp.itemCount < updateOp.positionStart + updateOp.itemCount) {
|
||||
// moved item is updated. add an update for it
|
||||
updateOp.itemCount--;
|
||||
extraUp1 = mCallback.obtainUpdateOp(UPDATE, moveOp.positionStart, 1);
|
||||
}
|
||||
// now affect of add is consumed. now apply effect of first remove
|
||||
if (moveOp.positionStart <= updateOp.positionStart) {
|
||||
updateOp.positionStart++;
|
||||
} else if (moveOp.positionStart < updateOp.positionStart + updateOp.itemCount) {
|
||||
final int remaining = updateOp.positionStart + updateOp.itemCount
|
||||
- moveOp.positionStart;
|
||||
extraUp2 = mCallback.obtainUpdateOp(UPDATE, moveOp.positionStart + 1, remaining);
|
||||
updateOp.itemCount -= remaining;
|
||||
}
|
||||
list.set(update, moveOp);
|
||||
if (updateOp.itemCount > 0) {
|
||||
list.set(move, updateOp);
|
||||
} else {
|
||||
list.remove(move);
|
||||
mCallback.recycleUpdateOp(updateOp);
|
||||
}
|
||||
if (extraUp1 != null) {
|
||||
list.add(move, extraUp1);
|
||||
}
|
||||
if (extraUp2 != null) {
|
||||
list.add(move, extraUp2);
|
||||
}
|
||||
}
|
||||
|
||||
private int getLastMoveOutOfOrder(List<UpdateOp> list) {
|
||||
boolean foundNonMove = false;
|
||||
for (int i = list.size() - 1; i >= 0; i--) {
|
||||
final UpdateOp op1 = list.get(i);
|
||||
if (op1.cmd == MOVE) {
|
||||
if (foundNonMove) {
|
||||
return i;
|
||||
}
|
||||
} else {
|
||||
foundNonMove = true;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
|
||||
UpdateOp obtainUpdateOp(int cmd, int startPosition, int itemCount);
|
||||
|
||||
void recycleUpdateOp(UpdateOp op);
|
||||
}
|
||||
}
|
@ -1,338 +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.android.support.widget;
|
||||
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
/**
|
||||
* Helper class for LayoutManagers to abstract measurements depending on the View's orientation.
|
||||
* <p>
|
||||
* It is developed to easily support vertical and horizontal orientations in a LayoutManager but
|
||||
* can also be used to abstract calls around view bounds and child measurements with margins and
|
||||
* decorations.
|
||||
*
|
||||
* @see #createHorizontalHelper(RecyclerView.LayoutManager)
|
||||
* @see #createVerticalHelper(RecyclerView.LayoutManager)
|
||||
*/
|
||||
public abstract class OrientationHelper {
|
||||
|
||||
private static final int INVALID_SIZE = Integer.MIN_VALUE;
|
||||
|
||||
protected final RecyclerView.LayoutManager mLayoutManager;
|
||||
|
||||
public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
|
||||
|
||||
public static final int VERTICAL = LinearLayout.VERTICAL;
|
||||
|
||||
private int mLastTotalSpace = INVALID_SIZE;
|
||||
|
||||
private OrientationHelper(RecyclerView.LayoutManager layoutManager) {
|
||||
mLayoutManager = layoutManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this method after onLayout method is complete if state is NOT pre-layout.
|
||||
* This method records information like layout bounds that might be useful in the next layout
|
||||
* calculations.
|
||||
*/
|
||||
public void onLayoutComplete() {
|
||||
mLastTotalSpace = getTotalSpace();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the layout space change between the previous layout pass and current layout pass.
|
||||
* <p>
|
||||
* Make sure you call {@link #onLayoutComplete()} at the end of your LayoutManager's
|
||||
* {@link RecyclerView.LayoutManager#onLayoutChildren(RecyclerView.Recycler,
|
||||
* RecyclerView.State)} method.
|
||||
*
|
||||
* @return The difference between the current total space and previous layout's total space.
|
||||
* @see #onLayoutComplete()
|
||||
*/
|
||||
public int getTotalSpaceChange() {
|
||||
return INVALID_SIZE == mLastTotalSpace ? 0 : getTotalSpace() - mLastTotalSpace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start of the view including its decoration and margin.
|
||||
* <p>
|
||||
* For example, for the horizontal helper, if a View's left is at pixel 20, has 2px left
|
||||
* decoration and 3px left margin, returned value will be 15px.
|
||||
*
|
||||
* @param view The view element to check
|
||||
* @return The first pixel of the element
|
||||
* @see #getDecoratedEnd(android.view.View)
|
||||
*/
|
||||
public abstract int getDecoratedStart(View view);
|
||||
|
||||
/**
|
||||
* Returns the end of the view including its decoration and margin.
|
||||
* <p>
|
||||
* For example, for the horizontal helper, if a View's right is at pixel 200, has 2px right
|
||||
* decoration and 3px right margin, returned value will be 205.
|
||||
*
|
||||
* @param view The view element to check
|
||||
* @return The last pixel of the element
|
||||
* @see #getDecoratedStart(android.view.View)
|
||||
*/
|
||||
public abstract int getDecoratedEnd(View view);
|
||||
|
||||
/**
|
||||
* Returns the space occupied by this View in the current orientation including decorations and
|
||||
* margins.
|
||||
*
|
||||
* @param view The view element to check
|
||||
* @return Total space occupied by this view
|
||||
* @see #getDecoratedMeasurementInOther(View)
|
||||
*/
|
||||
public abstract int getDecoratedMeasurement(View view);
|
||||
|
||||
/**
|
||||
* Returns the space occupied by this View in the perpendicular orientation including
|
||||
* decorations and margins.
|
||||
*
|
||||
* @param view The view element to check
|
||||
* @return Total space occupied by this view in the perpendicular orientation to current one
|
||||
* @see #getDecoratedMeasurement(View)
|
||||
*/
|
||||
public abstract int getDecoratedMeasurementInOther(View view);
|
||||
|
||||
/**
|
||||
* Returns the start position of the layout after the start padding is added.
|
||||
*
|
||||
* @return The very first pixel we can draw.
|
||||
*/
|
||||
public abstract int getStartAfterPadding();
|
||||
|
||||
/**
|
||||
* Returns the end position of the layout after the end padding is removed.
|
||||
*
|
||||
* @return The end boundary for this layout.
|
||||
*/
|
||||
public abstract int getEndAfterPadding();
|
||||
|
||||
/**
|
||||
* Returns the end position of the layout without taking padding into account.
|
||||
*
|
||||
* @return The end boundary for this layout without considering padding.
|
||||
*/
|
||||
public abstract int getEnd();
|
||||
|
||||
/**
|
||||
* Offsets all children's positions by the given amount.
|
||||
*
|
||||
* @param amount Value to add to each child's layout parameters
|
||||
*/
|
||||
public abstract void offsetChildren(int amount);
|
||||
|
||||
/**
|
||||
* Returns the total space to layout. This number is the difference between
|
||||
* {@link #getEndAfterPadding()} and {@link #getStartAfterPadding()}.
|
||||
*
|
||||
* @return Total space to layout children
|
||||
*/
|
||||
public abstract int getTotalSpace();
|
||||
|
||||
/**
|
||||
* Offsets the child in this orientation.
|
||||
*
|
||||
* @param view View to offset
|
||||
* @param offset offset amount
|
||||
*/
|
||||
public abstract void offsetChild(View view, int offset);
|
||||
|
||||
/**
|
||||
* Returns the padding at the end of the layout. For horizontal helper, this is the right
|
||||
* padding and for vertical helper, this is the bottom padding. This method does not check
|
||||
* whether the layout is RTL or not.
|
||||
*
|
||||
* @return The padding at the end of the layout.
|
||||
*/
|
||||
public abstract int getEndPadding();
|
||||
|
||||
/**
|
||||
* Creates an OrientationHelper for the given LayoutManager and orientation.
|
||||
*
|
||||
* @param layoutManager LayoutManager to attach to
|
||||
* @param orientation Desired orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL}
|
||||
* @return A new OrientationHelper
|
||||
*/
|
||||
public static OrientationHelper createOrientationHelper(
|
||||
RecyclerView.LayoutManager layoutManager, int orientation) {
|
||||
switch (orientation) {
|
||||
case HORIZONTAL:
|
||||
return createHorizontalHelper(layoutManager);
|
||||
case VERTICAL:
|
||||
return createVerticalHelper(layoutManager);
|
||||
}
|
||||
throw new IllegalArgumentException("invalid orientation");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a horizontal OrientationHelper for the given LayoutManager.
|
||||
*
|
||||
* @param layoutManager The LayoutManager to attach to.
|
||||
* @return A new OrientationHelper
|
||||
*/
|
||||
public static OrientationHelper createHorizontalHelper(
|
||||
RecyclerView.LayoutManager layoutManager) {
|
||||
return new OrientationHelper(layoutManager) {
|
||||
@Override
|
||||
public int getEndAfterPadding() {
|
||||
return mLayoutManager.getWidth() - mLayoutManager.getPaddingRight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEnd() {
|
||||
return mLayoutManager.getWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offsetChildren(int amount) {
|
||||
mLayoutManager.offsetChildrenHorizontal(amount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartAfterPadding() {
|
||||
return mLayoutManager.getPaddingLeft();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecoratedMeasurement(View view) {
|
||||
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
|
||||
view.getLayoutParams();
|
||||
return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin
|
||||
+ params.rightMargin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecoratedMeasurementInOther(View view) {
|
||||
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
|
||||
view.getLayoutParams();
|
||||
return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin
|
||||
+ params.bottomMargin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecoratedEnd(View view) {
|
||||
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
|
||||
view.getLayoutParams();
|
||||
return mLayoutManager.getDecoratedRight(view) + params.rightMargin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecoratedStart(View view) {
|
||||
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
|
||||
view.getLayoutParams();
|
||||
return mLayoutManager.getDecoratedLeft(view) - params.leftMargin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTotalSpace() {
|
||||
return mLayoutManager.getWidth() - mLayoutManager.getPaddingLeft()
|
||||
- mLayoutManager.getPaddingRight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offsetChild(View view, int offset) {
|
||||
view.offsetLeftAndRight(offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEndPadding() {
|
||||
return mLayoutManager.getPaddingRight();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a vertical OrientationHelper for the given LayoutManager.
|
||||
*
|
||||
* @param layoutManager The LayoutManager to attach to.
|
||||
* @return A new OrientationHelper
|
||||
*/
|
||||
public static OrientationHelper createVerticalHelper(RecyclerView.LayoutManager layoutManager) {
|
||||
return new OrientationHelper(layoutManager) {
|
||||
@Override
|
||||
public int getEndAfterPadding() {
|
||||
return mLayoutManager.getHeight() - mLayoutManager.getPaddingBottom();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEnd() {
|
||||
return mLayoutManager.getHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offsetChildren(int amount) {
|
||||
mLayoutManager.offsetChildrenVertical(amount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartAfterPadding() {
|
||||
return mLayoutManager.getPaddingTop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecoratedMeasurement(View view) {
|
||||
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
|
||||
view.getLayoutParams();
|
||||
return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin
|
||||
+ params.bottomMargin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecoratedMeasurementInOther(View view) {
|
||||
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
|
||||
view.getLayoutParams();
|
||||
return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin
|
||||
+ params.rightMargin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecoratedEnd(View view) {
|
||||
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
|
||||
view.getLayoutParams();
|
||||
return mLayoutManager.getDecoratedBottom(view) + params.bottomMargin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecoratedStart(View view) {
|
||||
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
|
||||
view.getLayoutParams();
|
||||
return mLayoutManager.getDecoratedTop(view) - params.topMargin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTotalSpace() {
|
||||
return mLayoutManager.getHeight() - mLayoutManager.getPaddingTop()
|
||||
- mLayoutManager.getPaddingBottom();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offsetChild(View view, int offset) {
|
||||
view.offsetTopAndBottom(offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEndPadding() {
|
||||
return mLayoutManager.getPaddingBottom();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -1,459 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.telegram.android.support.widget;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Like a SparseArray, but with the ability to offset key ranges for bulk insertions/deletions.
|
||||
*/
|
||||
class PositionMap<E> implements Cloneable {
|
||||
private static final Object DELETED = new Object();
|
||||
private boolean mGarbage = false;
|
||||
|
||||
private int[] mKeys;
|
||||
private Object[] mValues;
|
||||
private int mSize;
|
||||
|
||||
/**
|
||||
* Creates a new SparseArray containing no mappings.
|
||||
*/
|
||||
public PositionMap() {
|
||||
this(10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new PositionMap containing no mappings that will not
|
||||
* require any additional memory allocation to store the specified
|
||||
* number of mappings. If you supply an initial capacity of 0, the
|
||||
* sparse array will be initialized with a light-weight representation
|
||||
* not requiring any additional array allocations.
|
||||
*/
|
||||
public PositionMap(int initialCapacity) {
|
||||
if (initialCapacity == 0) {
|
||||
mKeys = ContainerHelpers.EMPTY_INTS;
|
||||
mValues = ContainerHelpers.EMPTY_OBJECTS;
|
||||
} else {
|
||||
initialCapacity = idealIntArraySize(initialCapacity);
|
||||
mKeys = new int[initialCapacity];
|
||||
mValues = new Object[initialCapacity];
|
||||
}
|
||||
mSize = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public PositionMap<E> clone() {
|
||||
PositionMap<E> clone = null;
|
||||
try {
|
||||
clone = (PositionMap<E>) super.clone();
|
||||
clone.mKeys = mKeys.clone();
|
||||
clone.mValues = mValues.clone();
|
||||
} catch (CloneNotSupportedException cnse) {
|
||||
/* ignore */
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Object mapped from the specified key, or <code>null</code>
|
||||
* if no such mapping has been made.
|
||||
*/
|
||||
public E get(int key) {
|
||||
return get(key, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Object mapped from the specified key, or the specified Object
|
||||
* if no such mapping has been made.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public E get(int key, E valueIfKeyNotFound) {
|
||||
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
|
||||
|
||||
if (i < 0 || mValues[i] == DELETED) {
|
||||
return valueIfKeyNotFound;
|
||||
} else {
|
||||
return (E) mValues[i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the mapping from the specified key, if there was any.
|
||||
*/
|
||||
public void delete(int key) {
|
||||
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
|
||||
|
||||
if (i >= 0) {
|
||||
if (mValues[i] != DELETED) {
|
||||
mValues[i] = DELETED;
|
||||
mGarbage = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for {@link #delete(int)}.
|
||||
*/
|
||||
public void remove(int key) {
|
||||
delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the mapping at the specified index.
|
||||
*/
|
||||
public void removeAt(int index) {
|
||||
if (mValues[index] != DELETED) {
|
||||
mValues[index] = DELETED;
|
||||
mGarbage = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a range of mappings as a batch.
|
||||
*
|
||||
* @param index Index to begin at
|
||||
* @param size Number of mappings to remove
|
||||
*/
|
||||
public void removeAtRange(int index, int size) {
|
||||
final int end = Math.min(mSize, index + size);
|
||||
for (int i = index; i < end; i++) {
|
||||
removeAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
public void insertKeyRange(int keyStart, int count) {
|
||||
|
||||
}
|
||||
|
||||
public void removeKeyRange(ArrayList<E> removedItems, int keyStart, int count) {
|
||||
|
||||
}
|
||||
|
||||
private void gc() {
|
||||
// Log.e("SparseArray", "gc start with " + mSize);
|
||||
|
||||
int n = mSize;
|
||||
int o = 0;
|
||||
int[] keys = mKeys;
|
||||
Object[] values = mValues;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
Object val = values[i];
|
||||
|
||||
if (val != DELETED) {
|
||||
if (i != o) {
|
||||
keys[o] = keys[i];
|
||||
values[o] = val;
|
||||
values[i] = null;
|
||||
}
|
||||
|
||||
o++;
|
||||
}
|
||||
}
|
||||
|
||||
mGarbage = false;
|
||||
mSize = o;
|
||||
|
||||
// Log.e("SparseArray", "gc end with " + mSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a mapping from the specified key to the specified value,
|
||||
* replacing the previous mapping from the specified key if there
|
||||
* was one.
|
||||
*/
|
||||
public void put(int key, E value) {
|
||||
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
|
||||
|
||||
if (i >= 0) {
|
||||
mValues[i] = value;
|
||||
} else {
|
||||
i = ~i;
|
||||
|
||||
if (i < mSize && mValues[i] == DELETED) {
|
||||
mKeys[i] = key;
|
||||
mValues[i] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
if (mGarbage && mSize >= mKeys.length) {
|
||||
gc();
|
||||
|
||||
// Search again because indices may have changed.
|
||||
i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
|
||||
}
|
||||
|
||||
if (mSize >= mKeys.length) {
|
||||
int n = idealIntArraySize(mSize + 1);
|
||||
|
||||
int[] nkeys = new int[n];
|
||||
Object[] nvalues = new Object[n];
|
||||
|
||||
// Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
|
||||
System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
|
||||
System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
|
||||
|
||||
mKeys = nkeys;
|
||||
mValues = nvalues;
|
||||
}
|
||||
|
||||
if (mSize - i != 0) {
|
||||
// Log.e("SparseArray", "move " + (mSize - i));
|
||||
System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
|
||||
System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
|
||||
}
|
||||
|
||||
mKeys[i] = key;
|
||||
mValues[i] = value;
|
||||
mSize++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of key-value mappings that this SparseArray
|
||||
* currently stores.
|
||||
*/
|
||||
public int size() {
|
||||
if (mGarbage) {
|
||||
gc();
|
||||
}
|
||||
|
||||
return mSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an index in the range <code>0...size()-1</code>, returns
|
||||
* the key from the <code>index</code>th key-value mapping that this
|
||||
* SparseArray stores.
|
||||
*/
|
||||
public int keyAt(int index) {
|
||||
if (mGarbage) {
|
||||
gc();
|
||||
}
|
||||
|
||||
return mKeys[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an index in the range <code>0...size()-1</code>, returns
|
||||
* the value from the <code>index</code>th key-value mapping that this
|
||||
* SparseArray stores.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public E valueAt(int index) {
|
||||
if (mGarbage) {
|
||||
gc();
|
||||
}
|
||||
|
||||
return (E) mValues[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an index in the range <code>0...size()-1</code>, sets a new
|
||||
* value for the <code>index</code>th key-value mapping that this
|
||||
* SparseArray stores.
|
||||
*/
|
||||
public void setValueAt(int index, E value) {
|
||||
if (mGarbage) {
|
||||
gc();
|
||||
}
|
||||
|
||||
mValues[index] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index for which {@link #keyAt} would return the
|
||||
* specified key, or a negative number if the specified
|
||||
* key is not mapped.
|
||||
*/
|
||||
public int indexOfKey(int key) {
|
||||
if (mGarbage) {
|
||||
gc();
|
||||
}
|
||||
|
||||
return ContainerHelpers.binarySearch(mKeys, mSize, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an index for which {@link #valueAt} would return the
|
||||
* specified key, or a negative number if no keys map to the
|
||||
* specified value.
|
||||
* <p>Beware that this is a linear search, unlike lookups by key,
|
||||
* and that multiple keys can map to the same value and this will
|
||||
* find only one of them.
|
||||
* <p>Note also that unlike most collections' {@code indexOf} methods,
|
||||
* this method compares values using {@code ==} rather than {@code equals}.
|
||||
*/
|
||||
public int indexOfValue(E value) {
|
||||
if (mGarbage) {
|
||||
gc();
|
||||
}
|
||||
|
||||
for (int i = 0; i < mSize; i++)
|
||||
if (mValues[i] == value)
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all key-value mappings from this SparseArray.
|
||||
*/
|
||||
public void clear() {
|
||||
int n = mSize;
|
||||
Object[] values = mValues;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
values[i] = null;
|
||||
}
|
||||
|
||||
mSize = 0;
|
||||
mGarbage = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts a key/value pair into the array, optimizing for the case where
|
||||
* the key is greater than all existing keys in the array.
|
||||
*/
|
||||
public void append(int key, E value) {
|
||||
if (mSize != 0 && key <= mKeys[mSize - 1]) {
|
||||
put(key, value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mGarbage && mSize >= mKeys.length) {
|
||||
gc();
|
||||
}
|
||||
|
||||
int pos = mSize;
|
||||
if (pos >= mKeys.length) {
|
||||
int n = idealIntArraySize(pos + 1);
|
||||
|
||||
int[] nkeys = new int[n];
|
||||
Object[] nvalues = new Object[n];
|
||||
|
||||
// Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
|
||||
System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
|
||||
System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
|
||||
|
||||
mKeys = nkeys;
|
||||
mValues = nvalues;
|
||||
}
|
||||
|
||||
mKeys[pos] = key;
|
||||
mValues[pos] = value;
|
||||
mSize = pos + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>This implementation composes a string by iterating over its mappings. If
|
||||
* this map contains itself as a value, the string "(this Map)"
|
||||
* will appear in its place.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
if (size() <= 0) {
|
||||
return "{}";
|
||||
}
|
||||
|
||||
StringBuilder buffer = new StringBuilder(mSize * 28);
|
||||
buffer.append('{');
|
||||
for (int i=0; i<mSize; i++) {
|
||||
if (i > 0) {
|
||||
buffer.append(", ");
|
||||
}
|
||||
int key = keyAt(i);
|
||||
buffer.append(key);
|
||||
buffer.append('=');
|
||||
Object value = valueAt(i);
|
||||
if (value != this) {
|
||||
buffer.append(value);
|
||||
} else {
|
||||
buffer.append("(this Map)");
|
||||
}
|
||||
}
|
||||
buffer.append('}');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
static int idealByteArraySize(int need) {
|
||||
for (int i = 4; i < 32; i++)
|
||||
if (need <= (1 << i) - 12)
|
||||
return (1 << i) - 12;
|
||||
|
||||
return need;
|
||||
}
|
||||
|
||||
static int idealBooleanArraySize(int need) {
|
||||
return idealByteArraySize(need);
|
||||
}
|
||||
|
||||
static int idealShortArraySize(int need) {
|
||||
return idealByteArraySize(need * 2) / 2;
|
||||
}
|
||||
|
||||
static int idealCharArraySize(int need) {
|
||||
return idealByteArraySize(need * 2) / 2;
|
||||
}
|
||||
|
||||
static int idealIntArraySize(int need) {
|
||||
return idealByteArraySize(need * 4) / 4;
|
||||
}
|
||||
|
||||
static int idealFloatArraySize(int need) {
|
||||
return idealByteArraySize(need * 4) / 4;
|
||||
}
|
||||
|
||||
static int idealObjectArraySize(int need) {
|
||||
return idealByteArraySize(need * 4) / 4;
|
||||
}
|
||||
|
||||
static int idealLongArraySize(int need) {
|
||||
return idealByteArraySize(need * 8) / 8;
|
||||
}
|
||||
|
||||
static class ContainerHelpers {
|
||||
static final boolean[] EMPTY_BOOLEANS = new boolean[0];
|
||||
static final int[] EMPTY_INTS = new int[0];
|
||||
static final long[] EMPTY_LONGS = new long[0];
|
||||
static final Object[] EMPTY_OBJECTS = new Object[0];
|
||||
|
||||
// This is Arrays.binarySearch(), but doesn't do any argument validation.
|
||||
static int binarySearch(int[] array, int size, int value) {
|
||||
int lo = 0;
|
||||
int hi = size - 1;
|
||||
|
||||
while (lo <= hi) {
|
||||
final int mid = (lo + hi) >>> 1;
|
||||
final int midVal = array[mid];
|
||||
|
||||
if (midVal < value) {
|
||||
lo = mid + 1;
|
||||
} else if (midVal > value) {
|
||||
hi = mid - 1;
|
||||
} else {
|
||||
return mid; // value found
|
||||
}
|
||||
}
|
||||
return ~lo; // value not present
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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.android.support.widget;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.view.AccessibilityDelegateCompat;
|
||||
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
|
||||
import android.view.View;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
|
||||
/**
|
||||
* The AccessibilityDelegate used by RecyclerView.
|
||||
* <p>
|
||||
* This class handles basic accessibility actions and delegates them to LayoutManager.
|
||||
*/
|
||||
public class RecyclerViewAccessibilityDelegate extends AccessibilityDelegateCompat {
|
||||
final RecyclerView mRecyclerView;
|
||||
|
||||
|
||||
public RecyclerViewAccessibilityDelegate(RecyclerView recyclerView) {
|
||||
mRecyclerView = recyclerView;
|
||||
}
|
||||
|
||||
private boolean shouldIgnore() {
|
||||
return mRecyclerView.hasPendingAdapterUpdates();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performAccessibilityAction(View host, int action, Bundle args) {
|
||||
if (super.performAccessibilityAction(host, action, args)) {
|
||||
return true;
|
||||
}
|
||||
if (!shouldIgnore() && mRecyclerView.getLayoutManager() != null) {
|
||||
return mRecyclerView.getLayoutManager().performAccessibilityAction(action, args);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
|
||||
super.onInitializeAccessibilityNodeInfo(host, info);
|
||||
info.setClassName(RecyclerView.class.getName());
|
||||
if (!shouldIgnore() && mRecyclerView.getLayoutManager() != null) {
|
||||
mRecyclerView.getLayoutManager().onInitializeAccessibilityNodeInfo(info);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
|
||||
super.onInitializeAccessibilityEvent(host, event);
|
||||
event.setClassName(RecyclerView.class.getName());
|
||||
if (host instanceof RecyclerView && !shouldIgnore()) {
|
||||
RecyclerView rv = (RecyclerView) host;
|
||||
if (rv.getLayoutManager() != null) {
|
||||
rv.getLayoutManager().onInitializeAccessibilityEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AccessibilityDelegateCompat getItemDelegate() {
|
||||
return mItemDelegate;
|
||||
}
|
||||
|
||||
final AccessibilityDelegateCompat mItemDelegate = new AccessibilityDelegateCompat() {
|
||||
@Override
|
||||
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
|
||||
super.onInitializeAccessibilityNodeInfo(host, info);
|
||||
if (!shouldIgnore() && mRecyclerView.getLayoutManager() != null) {
|
||||
mRecyclerView.getLayoutManager().
|
||||
onInitializeAccessibilityNodeInfoForItem(host, info);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performAccessibilityAction(View host, int action, Bundle args) {
|
||||
if (super.performAccessibilityAction(host, action, args)) {
|
||||
return true;
|
||||
}
|
||||
if (!shouldIgnore() && mRecyclerView.getLayoutManager() != null) {
|
||||
return mRecyclerView.getLayoutManager().
|
||||
performAccessibilityActionForItem(host, action, args);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
@ -1,98 +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.android.support.widget;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* A helper class to do scroll offset calculations.
|
||||
*/
|
||||
class ScrollbarHelper {
|
||||
|
||||
/**
|
||||
* @param startChild View closest to start of the list. (top or left)
|
||||
* @param endChild View closest to end of the list (bottom or right)
|
||||
*/
|
||||
static int computeScrollOffset(RecyclerView.State state, OrientationHelper orientation,
|
||||
View startChild, View endChild, RecyclerView.LayoutManager lm,
|
||||
boolean smoothScrollbarEnabled, boolean reverseLayout) {
|
||||
if (lm.getChildCount() == 0 || state.getItemCount() == 0 || startChild == null ||
|
||||
endChild == null) {
|
||||
return 0;
|
||||
}
|
||||
final int minPosition = Math.min(lm.getPosition(startChild),
|
||||
lm.getPosition(endChild));
|
||||
final int maxPosition = Math.max(lm.getPosition(startChild),
|
||||
lm.getPosition(endChild));
|
||||
final int itemsBefore = reverseLayout
|
||||
? Math.max(0, state.getItemCount() - maxPosition - 1)
|
||||
: Math.max(0, minPosition);
|
||||
if (!smoothScrollbarEnabled) {
|
||||
return itemsBefore;
|
||||
}
|
||||
final int laidOutArea = Math.abs(orientation.getDecoratedEnd(endChild) -
|
||||
orientation.getDecoratedStart(startChild));
|
||||
final int itemRange = Math.abs(lm.getPosition(startChild) -
|
||||
lm.getPosition(endChild)) + 1;
|
||||
final float avgSizePerRow = (float) laidOutArea / itemRange;
|
||||
|
||||
return Math.round(itemsBefore * avgSizePerRow + (orientation.getStartAfterPadding()
|
||||
- orientation.getDecoratedStart(startChild)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param startChild View closest to start of the list. (top or left)
|
||||
* @param endChild View closest to end of the list (bottom or right)
|
||||
*/
|
||||
static int computeScrollExtent(RecyclerView.State state, OrientationHelper orientation,
|
||||
View startChild, View endChild, RecyclerView.LayoutManager lm,
|
||||
boolean smoothScrollbarEnabled) {
|
||||
if (lm.getChildCount() == 0 || state.getItemCount() == 0 || startChild == null ||
|
||||
endChild == null) {
|
||||
return 0;
|
||||
}
|
||||
if (!smoothScrollbarEnabled) {
|
||||
return Math.abs(lm.getPosition(startChild) - lm.getPosition(endChild)) + 1;
|
||||
}
|
||||
final int extend = orientation.getDecoratedEnd(endChild)
|
||||
- orientation.getDecoratedStart(startChild);
|
||||
return Math.min(orientation.getTotalSpace(), extend);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param startChild View closest to start of the list. (top or left)
|
||||
* @param endChild View closest to end of the list (bottom or right)
|
||||
*/
|
||||
static int computeScrollRange(RecyclerView.State state, OrientationHelper orientation,
|
||||
View startChild, View endChild, RecyclerView.LayoutManager lm,
|
||||
boolean smoothScrollbarEnabled) {
|
||||
if (lm.getChildCount() == 0 || state.getItemCount() == 0 || startChild == null ||
|
||||
endChild == null) {
|
||||
return 0;
|
||||
}
|
||||
if (!smoothScrollbarEnabled) {
|
||||
return state.getItemCount();
|
||||
}
|
||||
// smooth scrollbar enabled. try to estimate better.
|
||||
final int laidOutArea = orientation.getDecoratedEnd(endChild) -
|
||||
orientation.getDecoratedStart(startChild);
|
||||
final int laidOutRange = Math.abs(lm.getPosition(startChild) -
|
||||
lm.getPosition(endChild))
|
||||
+ 1;
|
||||
// estimate a size for full list.
|
||||
return (int) ((float) laidOutArea / laidOutRange * state.getItemCount());
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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.android.support.widget.util;
|
||||
|
||||
import org.telegram.android.support.util.SortedList;
|
||||
import org.telegram.android.support.widget.RecyclerView;
|
||||
|
||||
/**
|
||||
* A {@link SortedList.Callback} implementation that can bind a {@link SortedList} to a
|
||||
* {@link RecyclerView.Adapter}.
|
||||
*/
|
||||
public abstract class SortedListAdapterCallback<T2> extends SortedList.Callback<T2> {
|
||||
|
||||
final RecyclerView.Adapter mAdapter;
|
||||
|
||||
/**
|
||||
* Creates a {@link SortedList.Callback} that will forward data change events to the provided
|
||||
* Adapter.
|
||||
*
|
||||
* @param adapter The Adapter instance which should receive events from the SortedList.
|
||||
*/
|
||||
public SortedListAdapterCallback(RecyclerView.Adapter adapter) {
|
||||
mAdapter = adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInserted(int position, int count) {
|
||||
mAdapter.notifyItemRangeInserted(position, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoved(int position, int count) {
|
||||
mAdapter.notifyItemRangeRemoved(position, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoved(int fromPosition, int toPosition) {
|
||||
mAdapter.notifyItemMoved(fromPosition, toPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(int position, int count) {
|
||||
mAdapter.notifyItemRangeChanged(position, count);
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.telegram.android.time;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.ParsePosition;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* <p>DateParser is the "missing" interface for the parsing methods of
|
||||
* {@link java.text.DateFormat}.</p>
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public interface DateParser {
|
||||
|
||||
/**
|
||||
* Equivalent to DateFormat.parse(String).
|
||||
* <p/>
|
||||
* See {@link java.text.DateFormat#parse(String)} for more information.
|
||||
*
|
||||
* @param source A <code>String</code> whose beginning should be parsed.
|
||||
* @return A <code>Date</code> parsed from the string
|
||||
* @throws ParseException if the beginning of the specified string cannot be parsed.
|
||||
*/
|
||||
Date parse(String source) throws ParseException;
|
||||
|
||||
/**
|
||||
* Equivalent to DateFormat.parse(String, ParsePosition).
|
||||
* <p/>
|
||||
* See {@link java.text.DateFormat#parse(String, ParsePosition)} for more information.
|
||||
*
|
||||
* @param source A <code>String</code>, part of which should be parsed.
|
||||
* @param pos A <code>ParsePosition</code> object with index and error index information
|
||||
* as described above.
|
||||
* @return A <code>Date</code> parsed from the string. In case of error, returns null.
|
||||
* @throws NullPointerException if text or pos is null.
|
||||
*/
|
||||
Date parse(String source, ParsePosition pos);
|
||||
|
||||
// Accessors
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>Get the pattern used by this parser.</p>
|
||||
*
|
||||
* @return the pattern, {@link java.text.SimpleDateFormat} compatible
|
||||
*/
|
||||
String getPattern();
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Get the time zone used by this parser.
|
||||
* </p>
|
||||
* <p/>
|
||||
* <p>
|
||||
* The default {@link TimeZone} used to create a {@link Date} when the {@link TimeZone} is not specified by
|
||||
* the format pattern.
|
||||
* </p>
|
||||
*
|
||||
* @return the time zone
|
||||
*/
|
||||
TimeZone getTimeZone();
|
||||
|
||||
/**
|
||||
* <p>Get the locale used by this parser.</p>
|
||||
*
|
||||
* @return the locale
|
||||
*/
|
||||
Locale getLocale();
|
||||
|
||||
/**
|
||||
* Parses text from a string to produce a Date.
|
||||
*
|
||||
* @param source A <code>String</code> whose beginning should be parsed.
|
||||
* @return a <code>java.util.Date</code> object
|
||||
* @throws ParseException if the beginning of the specified string cannot be parsed.
|
||||
* @see java.text.DateFormat#parseObject(String)
|
||||
*/
|
||||
Object parseObject(String source) throws ParseException;
|
||||
|
||||
/**
|
||||
* Parse a date/time string according to the given parse position.
|
||||
*
|
||||
* @param source A <code>String</code> whose beginning should be parsed.
|
||||
* @param pos the parse position
|
||||
* @return a <code>java.util.Date</code> object
|
||||
* @see java.text.DateFormat#parseObject(String, ParsePosition)
|
||||
*/
|
||||
Object parseObject(String source, ParsePosition pos);
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.telegram.android.time;
|
||||
|
||||
import java.text.FieldPosition;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* <p>DatePrinter is the "missing" interface for the format methods of
|
||||
* {@link java.text.DateFormat}.</p>
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public interface DatePrinter {
|
||||
|
||||
/**
|
||||
* <p>Formats a millisecond {@code long} value.</p>
|
||||
*
|
||||
* @param millis the millisecond value to format
|
||||
* @return the formatted string
|
||||
* @since 2.1
|
||||
*/
|
||||
String format(long millis);
|
||||
|
||||
/**
|
||||
* <p>Formats a {@code Date} object using a {@code GregorianCalendar}.</p>
|
||||
*
|
||||
* @param date the date to format
|
||||
* @return the formatted string
|
||||
*/
|
||||
String format(Date date);
|
||||
|
||||
/**
|
||||
* <p>Formats a {@code Calendar} object.</p>
|
||||
*
|
||||
* @param calendar the calendar to format
|
||||
* @return the formatted string
|
||||
*/
|
||||
String format(Calendar calendar);
|
||||
|
||||
/**
|
||||
* <p>Formats a milliseond {@code long} value into the
|
||||
* supplied {@code StringBuffer}.</p>
|
||||
*
|
||||
* @param millis the millisecond value to format
|
||||
* @param buf the buffer to format into
|
||||
* @return the specified string buffer
|
||||
*/
|
||||
StringBuffer format(long millis, StringBuffer buf);
|
||||
|
||||
/**
|
||||
* <p>Formats a {@code Date} object into the
|
||||
* supplied {@code StringBuffer} using a {@code GregorianCalendar}.</p>
|
||||
*
|
||||
* @param date the date to format
|
||||
* @param buf the buffer to format into
|
||||
* @return the specified string buffer
|
||||
*/
|
||||
StringBuffer format(Date date, StringBuffer buf);
|
||||
|
||||
/**
|
||||
* <p>Formats a {@code Calendar} object into the
|
||||
* supplied {@code StringBuffer}.</p>
|
||||
*
|
||||
* @param calendar the calendar to format
|
||||
* @param buf the buffer to format into
|
||||
* @return the specified string buffer
|
||||
*/
|
||||
StringBuffer format(Calendar calendar, StringBuffer buf);
|
||||
|
||||
// Accessors
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>Gets the pattern used by this printer.</p>
|
||||
*
|
||||
* @return the pattern, {@link java.text.SimpleDateFormat} compatible
|
||||
*/
|
||||
String getPattern();
|
||||
|
||||
/**
|
||||
* <p>Gets the time zone used by this printer.</p>
|
||||
* <p/>
|
||||
* <p>This zone is always used for {@code Date} printing. </p>
|
||||
*
|
||||
* @return the time zone
|
||||
*/
|
||||
TimeZone getTimeZone();
|
||||
|
||||
/**
|
||||
* <p>Gets the locale used by this printer.</p>
|
||||
*
|
||||
* @return the locale
|
||||
*/
|
||||
Locale getLocale();
|
||||
|
||||
/**
|
||||
* <p>Formats a {@code Date}, {@code Calendar} or
|
||||
* {@code Long} (milliseconds) object.</p>
|
||||
* <p/>
|
||||
* See {@link java.text.DateFormat#format(Object, StringBuffer, FieldPosition)}
|
||||
*
|
||||
* @param obj the object to format
|
||||
* @param toAppendTo the buffer to append to
|
||||
* @param pos the position - ignored
|
||||
* @return the buffer passed in
|
||||
*/
|
||||
StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user