diff --git a/TMessagesProj/build.gradle b/TMessagesProj/build.gradle index 631253d77..deae20092 100644 --- a/TMessagesProj/build.gradle +++ b/TMessagesProj/build.gradle @@ -9,11 +9,11 @@ configurations { } dependencies { - compile 'com.google.android.gms:play-services-gcm:11.0.2' - compile 'com.google.android.gms:play-services-maps:11.0.2' - compile 'com.google.android.gms:play-services-vision:11.0.2' - compile 'com.google.android.gms:play-services-wallet:11.0.2' - compile 'com.google.android.gms:play-services-wearable:11.0.2' + compile 'com.google.android.gms:play-services-gcm:11.0.4' + compile 'com.google.android.gms:play-services-maps:11.0.4' + compile 'com.google.android.gms:play-services-vision:11.0.4' + compile 'com.google.android.gms:play-services-wallet:11.0.4' + compile 'com.google.android.gms:play-services-wearable:11.0.4' compile 'com.android.support:support-core-ui:25.3.1' compile 'com.android.support:support-compat:25.3.1' compile 'com.android.support:support-core-utils:25.3.1' @@ -25,8 +25,9 @@ dependencies { } android { - compileSdkVersion 25 - buildToolsVersion '25.0.2' + compileSdkVersion 26 + buildToolsVersion '26.0.2' + useLibrary 'org.apache.http.legacy' defaultConfig.applicationId = "org.telegram.messenger" @@ -90,7 +91,7 @@ android { } } - defaultConfig.versionCode = 1043 + defaultConfig.versionCode = 1154 sourceSets.debug { manifest.srcFile 'config/debug/AndroidManifest.xml' @@ -104,6 +105,8 @@ android { manifest.srcFile 'config/foss/AndroidManifest.xml' } + flavorDimensions "minApi" + productFlavors { x86 { ndk { @@ -155,14 +158,21 @@ android { } applicationVariants.all { variant -> - def abiVersion = variant.productFlavors.get(0).versionCode - variant.mergedFlavor.versionCode = defaultConfig.versionCode * 10 + abiVersion + variant.outputs.all { output -> + output.processManifest.doLast { + def abiVersion = variant.productFlavors.get(0).versionCode + String manifestPath = "$manifestOutputDirectory/AndroidManifest.xml" + def manifestContent = file(manifestPath).getText() + manifestContent = manifestContent.replace(String.format('android:versionCode="%d"', abiVersion), String.format('android:versionCode="%s"', defaultConfig.versionCode * 10 + abiVersion)) + file(manifestPath).write(manifestContent) + } + } } defaultConfig { minSdkVersion 16 targetSdkVersion 25 - versionName "4.2.1" + versionName "4.6.0" externalNativeBuild { ndkBuild { diff --git a/TMessagesProj/jni/Android.mk b/TMessagesProj/jni/Android.mk index c4c8f94db..a417e1cc7 100755 --- a/TMessagesProj/jni/Android.mk +++ b/TMessagesProj/jni/Android.mk @@ -72,42 +72,6 @@ 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 := \ -./jni/breakpad/common/android/include \ -./jni/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_MODULE := voip LOCAL_CPPFLAGS := -Wall -std=c++11 -DANDROID -finline-functions -ffast-math -Os -fno-strict-aliasing -O3 -frtti -D__STDC_LIMIT_MACROS LOCAL_CFLAGS := -O3 -DUSE_KISS_FFT -fexceptions -DWEBRTC_APM_DEBUG_DUMP=0 -DWEBRTC_POSIX -D__STDC_LIMIT_MACROS @@ -399,7 +363,7 @@ LOCAL_CFLAGS += -Drestrict='' -D__EMX__ -DOPUS_BUILD -DFIXED_POINT -DUSE_ALLOCA LOCAL_CFLAGS += -DANDROID_NDK -DDISABLE_IMPORTGL -fno-strict-aliasing -fprefetch-loop-arrays -DAVOID_TABLES -DANDROID_TILE_BASED_DECODE -DANDROID_ARMV6_IDCT -ffast-math -D__STDC_CONSTANT_MACROS LOCAL_CPPFLAGS := -DBSD=1 -ffast-math -Os -funroll-loops -std=c++11 LOCAL_LDLIBS := -ljnigraphics -llog -lz -latomic -lOpenSLES -lEGL -lGLESv2 -LOCAL_STATIC_LIBRARIES := webp sqlite tgnet breakpad avformat avcodec avutil voip +LOCAL_STATIC_LIBRARIES := webp sqlite tgnet avformat avcodec avutil voip LOCAL_SRC_FILES := \ ./opus/src/opus.c \ @@ -600,9 +564,8 @@ LOCAL_C_INCLUDES := \ ./jni/opus/opusfile \ ./jni/libyuv/include \ ./jni/boringssl/include \ -./jni/breakpad/common/android/include \ -./jni/breakpad \ ./jni/ffmpeg/include \ +./jni/emoji \ ./jni/intro LOCAL_SRC_FILES += \ @@ -666,6 +629,8 @@ LOCAL_SRC_FILES += \ ./SqliteWrapper.cpp \ ./TgNetWrapper.cpp \ ./NativeLoader.cpp \ +./emoji/emoji_suggestions_data.cpp \ +./emoji/emoji_suggestions.cpp \ ./libtgvoip/client/android/tg_voip_jni.cpp include $(BUILD_SHARED_LIBRARY) diff --git a/TMessagesProj/jni/NativeLoader.cpp b/TMessagesProj/jni/NativeLoader.cpp index f2eb1ca98..c1e291707 100644 --- a/TMessagesProj/jni/NativeLoader.cpp +++ b/TMessagesProj/jni/NativeLoader.cpp @@ -1,7 +1,5 @@ #include #include -#include "breakpad/client/linux/handler/exception_handler.h" -#include "breakpad/client/linux/handler/minidump_descriptor.h" /*static google_breakpad::ExceptionHandler *exceptionHandler; @@ -19,4 +17,4 @@ extern "C" { exceptionHandler = new google_breakpad::ExceptionHandler(descriptor, NULL, callback, NULL, true, -1); }*/ } -} \ No newline at end of file +} diff --git a/TMessagesProj/jni/breakpad/LICENSE b/TMessagesProj/jni/breakpad/LICENSE deleted file mode 100644 index 95207bdf6..000000000 --- a/TMessagesProj/jni/breakpad/LICENSE +++ /dev/null @@ -1,50 +0,0 @@ -Copyright (c) 2006, Google Inc. -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 Google Inc. 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 -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - --------------------------------------------------------------------- - -Copyright 2001-2004 Unicode, Inc. - -Disclaimer - -This source code is provided as is by Unicode, Inc. No claims are -made as to fitness for any particular purpose. No warranties of any -kind are expressed or implied. The recipient agrees to determine -applicability of information provided. If this file has been -purchased on magnetic or optical media from Unicode, Inc., the -sole remedy for any claim will be exchange of defective media -within 90 days of receipt. - -Limitations on Rights to Redistribute This Code - -Unicode, Inc. hereby grants the right to freely use the information -supplied in this file in the creation of products supporting the -Unicode Standard, and to make copies of this file in any form -for internal or external distribution as long as this notice -remains attached. diff --git a/TMessagesProj/jni/breakpad/client/linux/crash_generation/client_info.h b/TMessagesProj/jni/breakpad/client/linux/crash_generation/client_info.h deleted file mode 100644 index d0a184a63..000000000 --- a/TMessagesProj/jni/breakpad/client/linux/crash_generation/client_info.h +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2010 Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef CLIENT_LINUX_CRASH_GENERATION_CLIENT_INFO_H_ -#define CLIENT_LINUX_CRASH_GENERATION_CLIENT_INFO_H_ - -namespace google_breakpad { - -class CrashGenerationServer; - -class ClientInfo { - public: - ClientInfo(pid_t pid, CrashGenerationServer* crash_server) - : crash_server_(crash_server), - pid_(pid) {} - - CrashGenerationServer* crash_server() const { return crash_server_; } - pid_t pid() const { return pid_; } - - private: - CrashGenerationServer* crash_server_; - pid_t pid_; -}; - -} - -#endif // CLIENT_LINUX_CRASH_GENERATION_CLIENT_INFO_H_ diff --git a/TMessagesProj/jni/breakpad/client/linux/crash_generation/crash_generation_client.cc b/TMessagesProj/jni/breakpad/client/linux/crash_generation/crash_generation_client.cc deleted file mode 100644 index d8bfbbad2..000000000 --- a/TMessagesProj/jni/breakpad/client/linux/crash_generation/crash_generation_client.cc +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) 2010 Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "client/linux/crash_generation/crash_generation_client.h" - -#include -#include -#include - -#include - -#include "common/linux/eintr_wrapper.h" -#include "common/linux/ignore_ret.h" -#include "third_party/lss/linux_syscall_support.h" - -namespace google_breakpad { - -namespace { - -class CrashGenerationClientImpl : public CrashGenerationClient { - public: - explicit CrashGenerationClientImpl(int server_fd) : server_fd_(server_fd) {} - virtual ~CrashGenerationClientImpl() {} - - virtual bool RequestDump(const void* blob, size_t blob_size) { - int fds[2]; - if (sys_pipe(fds) < 0) - return false; - static const unsigned kControlMsgSize = CMSG_SPACE(sizeof(int)); - - struct kernel_iovec iov; - iov.iov_base = const_cast(blob); - iov.iov_len = blob_size; - - struct kernel_msghdr msg = { 0 }; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - char cmsg[kControlMsgSize] = ""; - msg.msg_control = cmsg; - msg.msg_controllen = sizeof(cmsg); - - struct cmsghdr* hdr = CMSG_FIRSTHDR(&msg); - hdr->cmsg_level = SOL_SOCKET; - hdr->cmsg_type = SCM_RIGHTS; - hdr->cmsg_len = CMSG_LEN(sizeof(int)); - int* p = reinterpret_cast(CMSG_DATA(hdr)); - *p = fds[1]; - - ssize_t ret = HANDLE_EINTR(sys_sendmsg(server_fd_, &msg, 0)); - sys_close(fds[1]); - if (ret < 0) { - sys_close(fds[0]); - return false; - } - - // Wait for an ACK from the server. - char b; - IGNORE_RET(HANDLE_EINTR(sys_read(fds[0], &b, 1))); - sys_close(fds[0]); - - return true; - } - - private: - int server_fd_; - - DISALLOW_COPY_AND_ASSIGN(CrashGenerationClientImpl); -}; - -} // namespace - -// static -CrashGenerationClient* CrashGenerationClient::TryCreate(int server_fd) { - if (server_fd < 0) - return NULL; - return new CrashGenerationClientImpl(server_fd); -} - -} // namespace google_breakpad diff --git a/TMessagesProj/jni/breakpad/client/linux/crash_generation/crash_generation_client.h b/TMessagesProj/jni/breakpad/client/linux/crash_generation/crash_generation_client.h deleted file mode 100644 index 4e68424ae..000000000 --- a/TMessagesProj/jni/breakpad/client/linux/crash_generation/crash_generation_client.h +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2010 Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_ -#define CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_ - -#include "common/basictypes.h" - -#include - -namespace google_breakpad { - -// CrashGenerationClient is an interface for implementing out-of-process crash -// dumping. The default implementation, accessed via the TryCreate() factory, -// works in conjunction with the CrashGenerationServer to generate a minidump -// via a remote process. -class CrashGenerationClient { - public: - CrashGenerationClient() {} - virtual ~CrashGenerationClient() {} - - // Request the crash server to generate a dump. |blob| is an opaque - // CrashContext pointer from exception_handler.h. - // Returns true if the dump was successful; false otherwise. - virtual bool RequestDump(const void* blob, size_t blob_size) = 0; - - // Returns a new CrashGenerationClient if |server_fd| is valid and - // connects to a CrashGenerationServer. Otherwise, return NULL. - // The returned CrashGenerationClient* is owned by the caller of - // this function. - static CrashGenerationClient* TryCreate(int server_fd); - - private: - DISALLOW_COPY_AND_ASSIGN(CrashGenerationClient); -}; - -} // namespace google_breakpad - -#endif // CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_ diff --git a/TMessagesProj/jni/breakpad/client/linux/dump_writer_common/mapping_info.h b/TMessagesProj/jni/breakpad/client/linux/dump_writer_common/mapping_info.h deleted file mode 100644 index 5f247cfd4..000000000 --- a/TMessagesProj/jni/breakpad/client/linux/dump_writer_common/mapping_info.h +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) 2014, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef CLIENT_LINUX_DUMP_WRITER_COMMON_MAPPING_INFO_H_ -#define CLIENT_LINUX_DUMP_WRITER_COMMON_MAPPING_INFO_H_ - -#include -#include -#include - -#include "google_breakpad/common/minidump_format.h" - -namespace google_breakpad { - -// One of these is produced for each mapping in the process (i.e. line in -// /proc/$x/maps). -struct MappingInfo { - uintptr_t start_addr; - size_t size; - size_t offset; // offset into the backed file. - bool exec; // true if the mapping has the execute bit set. - char name[NAME_MAX]; -}; - -struct MappingEntry { - MappingInfo first; - uint8_t second[sizeof(MDGUID)]; -}; - -// A list of -typedef std::list MappingList; - -} // namespace google_breakpad - -#endif // CLIENT_LINUX_DUMP_WRITER_COMMON_MAPPING_INFO_H_ diff --git a/TMessagesProj/jni/breakpad/client/linux/dump_writer_common/raw_context_cpu.h b/TMessagesProj/jni/breakpad/client/linux/dump_writer_common/raw_context_cpu.h deleted file mode 100644 index e2ef45df5..000000000 --- a/TMessagesProj/jni/breakpad/client/linux/dump_writer_common/raw_context_cpu.h +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2014, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef CLIENT_LINUX_DUMP_WRITER_COMMON_RAW_CONTEXT_CPU_H -#define CLIENT_LINUX_DUMP_WRITER_COMMON_RAW_CONTEXT_CPU_H - -#include "google_breakpad/common/minidump_format.h" - -namespace google_breakpad { - -#if defined(__i386__) -typedef MDRawContextX86 RawContextCPU; -#elif defined(__x86_64) -typedef MDRawContextAMD64 RawContextCPU; -#elif defined(__ARM_EABI__) -typedef MDRawContextARM RawContextCPU; -#elif defined(__aarch64__) -typedef MDRawContextARM64 RawContextCPU; -#elif defined(__mips__) -typedef MDRawContextMIPS RawContextCPU; -#else -#error "This code has not been ported to your platform yet." -#endif - -} // namespace google_breakpad - -#endif // CLIENT_LINUX_DUMP_WRITER_COMMON_RAW_CONTEXT_CPU_H diff --git a/TMessagesProj/jni/breakpad/client/linux/dump_writer_common/thread_info.cc b/TMessagesProj/jni/breakpad/client/linux/dump_writer_common/thread_info.cc deleted file mode 100644 index 9956d4450..000000000 --- a/TMessagesProj/jni/breakpad/client/linux/dump_writer_common/thread_info.cc +++ /dev/null @@ -1,299 +0,0 @@ -// Copyright (c) 2014, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "client/linux/dump_writer_common/thread_info.h" - -#include -#include - -#include "common/linux/linux_libc_support.h" -#include "google_breakpad/common/minidump_format.h" - -namespace { - -#if defined(__i386__) -// Write a uint16_t to memory -// out: memory location to write to -// v: value to write. -void U16(void* out, uint16_t v) { - my_memcpy(out, &v, sizeof(v)); -} - -// Write a uint32_t to memory -// out: memory location to write to -// v: value to write. -void U32(void* out, uint32_t v) { - my_memcpy(out, &v, sizeof(v)); -} -#endif - -} - -namespace google_breakpad { - -#if defined(__i386__) - -uintptr_t ThreadInfo::GetInstructionPointer() const { - return regs.eip; -} - -void ThreadInfo::FillCPUContext(RawContextCPU* out) const { - out->context_flags = MD_CONTEXT_X86_ALL; - - out->dr0 = dregs[0]; - out->dr1 = dregs[1]; - out->dr2 = dregs[2]; - out->dr3 = dregs[3]; - // 4 and 5 deliberatly omitted because they aren't included in the minidump - // format. - out->dr6 = dregs[6]; - out->dr7 = dregs[7]; - - out->gs = regs.xgs; - out->fs = regs.xfs; - out->es = regs.xes; - out->ds = regs.xds; - - out->edi = regs.edi; - out->esi = regs.esi; - out->ebx = regs.ebx; - out->edx = regs.edx; - out->ecx = regs.ecx; - out->eax = regs.eax; - - out->ebp = regs.ebp; - out->eip = regs.eip; - out->cs = regs.xcs; - out->eflags = regs.eflags; - out->esp = regs.esp; - out->ss = regs.xss; - - out->float_save.control_word = fpregs.cwd; - out->float_save.status_word = fpregs.swd; - out->float_save.tag_word = fpregs.twd; - out->float_save.error_offset = fpregs.fip; - out->float_save.error_selector = fpregs.fcs; - out->float_save.data_offset = fpregs.foo; - out->float_save.data_selector = fpregs.fos; - - // 8 registers * 10 bytes per register. - my_memcpy(out->float_save.register_area, fpregs.st_space, 10 * 8); - - // This matches the Intel fpsave format. - U16(out->extended_registers + 0, fpregs.cwd); - U16(out->extended_registers + 2, fpregs.swd); - U16(out->extended_registers + 4, fpregs.twd); - U16(out->extended_registers + 6, fpxregs.fop); - U32(out->extended_registers + 8, fpxregs.fip); - U16(out->extended_registers + 12, fpxregs.fcs); - U32(out->extended_registers + 16, fpregs.foo); - U16(out->extended_registers + 20, fpregs.fos); - U32(out->extended_registers + 24, fpxregs.mxcsr); - - my_memcpy(out->extended_registers + 32, &fpxregs.st_space, 128); - my_memcpy(out->extended_registers + 160, &fpxregs.xmm_space, 128); -} - -#elif defined(__x86_64) - -uintptr_t ThreadInfo::GetInstructionPointer() const { - return regs.rip; -} - -void ThreadInfo::FillCPUContext(RawContextCPU* out) const { - out->context_flags = MD_CONTEXT_AMD64_FULL | - MD_CONTEXT_AMD64_SEGMENTS; - - out->cs = regs.cs; - - out->ds = regs.ds; - out->es = regs.es; - out->fs = regs.fs; - out->gs = regs.gs; - - out->ss = regs.ss; - out->eflags = regs.eflags; - - out->dr0 = dregs[0]; - out->dr1 = dregs[1]; - out->dr2 = dregs[2]; - out->dr3 = dregs[3]; - // 4 and 5 deliberatly omitted because they aren't included in the minidump - // format. - out->dr6 = dregs[6]; - out->dr7 = dregs[7]; - - out->rax = regs.rax; - out->rcx = regs.rcx; - out->rdx = regs.rdx; - out->rbx = regs.rbx; - - out->rsp = regs.rsp; - - out->rbp = regs.rbp; - out->rsi = regs.rsi; - out->rdi = regs.rdi; - out->r8 = regs.r8; - out->r9 = regs.r9; - out->r10 = regs.r10; - out->r11 = regs.r11; - out->r12 = regs.r12; - out->r13 = regs.r13; - out->r14 = regs.r14; - out->r15 = regs.r15; - - out->rip = regs.rip; - - out->flt_save.control_word = fpregs.cwd; - out->flt_save.status_word = fpregs.swd; - out->flt_save.tag_word = fpregs.ftw; - out->flt_save.error_opcode = fpregs.fop; - out->flt_save.error_offset = fpregs.rip; - out->flt_save.error_selector = 0; // We don't have this. - out->flt_save.data_offset = fpregs.rdp; - out->flt_save.data_selector = 0; // We don't have this. - out->flt_save.mx_csr = fpregs.mxcsr; - out->flt_save.mx_csr_mask = fpregs.mxcr_mask; - - my_memcpy(&out->flt_save.float_registers, &fpregs.st_space, 8 * 16); - my_memcpy(&out->flt_save.xmm_registers, &fpregs.xmm_space, 16 * 16); -} - -#elif defined(__ARM_EABI__) - -uintptr_t ThreadInfo::GetInstructionPointer() const { - return regs.uregs[15]; -} - -void ThreadInfo::FillCPUContext(RawContextCPU* out) const { - out->context_flags = MD_CONTEXT_ARM_FULL; - - for (int i = 0; i < MD_CONTEXT_ARM_GPR_COUNT; ++i) - out->iregs[i] = regs.uregs[i]; - // No CPSR register in ThreadInfo(it's not accessible via ptrace) - out->cpsr = 0; -#if !defined(__ANDROID__) - out->float_save.fpscr = fpregs.fpsr | - (static_cast(fpregs.fpcr) << 32); - // TODO: sort this out, actually collect floating point registers - my_memset(&out->float_save.regs, 0, sizeof(out->float_save.regs)); - my_memset(&out->float_save.extra, 0, sizeof(out->float_save.extra)); -#endif -} - -#elif defined(__aarch64__) - -uintptr_t ThreadInfo::GetInstructionPointer() const { - return regs.pc; -} - -void ThreadInfo::FillCPUContext(RawContextCPU* out) const { - out->context_flags = MD_CONTEXT_ARM64_FULL; - - out->cpsr = static_cast(regs.pstate); - for (int i = 0; i < MD_CONTEXT_ARM64_REG_SP; ++i) - out->iregs[i] = regs.regs[i]; - out->iregs[MD_CONTEXT_ARM64_REG_SP] = regs.sp; - out->iregs[MD_CONTEXT_ARM64_REG_PC] = regs.pc; - - out->float_save.fpsr = fpregs.fpsr; - out->float_save.fpcr = fpregs.fpcr; - my_memcpy(&out->float_save.regs, &fpregs.vregs, - MD_FLOATINGSAVEAREA_ARM64_FPR_COUNT * 16); -} - -#elif defined(__mips__) - -uintptr_t ThreadInfo::GetInstructionPointer() const { - return mcontext.pc; -} - -void ThreadInfo::FillCPUContext(RawContextCPU* out) const { - out->context_flags = MD_CONTEXT_MIPS_FULL; - - for (int i = 0; i < MD_CONTEXT_MIPS_GPR_COUNT; ++i) - out->iregs[i] = mcontext.gregs[i]; - - out->mdhi = mcontext.mdhi; - out->mdlo = mcontext.mdlo; - out->dsp_control = mcontext.dsp; - - out->hi[0] = mcontext.hi1; - out->lo[0] = mcontext.lo1; - out->hi[1] = mcontext.hi2; - out->lo[1] = mcontext.lo2; - out->hi[2] = mcontext.hi3; - out->lo[2] = mcontext.lo3; - - out->epc = mcontext.pc; - out->badvaddr = 0; // Not stored in mcontext - out->status = 0; // Not stored in mcontext - out->cause = 0; // Not stored in mcontext - - for (int i = 0; i < MD_FLOATINGSAVEAREA_MIPS_FPR_COUNT; ++i) - out->float_save.regs[i] = mcontext.fpregs.fp_r.fp_fregs[i]._fp_fregs; - - out->float_save.fpcsr = mcontext.fpc_csr; -#if _MIPS_SIM == _ABIO32 - out->float_save.fir = mcontext.fpc_eir; -#endif -} -#endif // __mips__ - -void ThreadInfo::GetGeneralPurposeRegisters(void** gp_regs, size_t* size) { - assert(gp_regs || size); -#if defined(__mips__) - if (gp_regs) - *gp_regs = mcontext.gregs; - if (size) - *size = sizeof(mcontext.gregs); -#else - if (gp_regs) - *gp_regs = ®s; - if (size) - *size = sizeof(regs); -#endif -} - -void ThreadInfo::GetFloatingPointRegisters(void** fp_regs, size_t* size) { - assert(fp_regs || size); -#if defined(__mips__) - if (fp_regs) - *fp_regs = &mcontext.fpregs; - if (size) - *size = sizeof(mcontext.fpregs); -#else - if (fp_regs) - *fp_regs = &fpregs; - if (size) - *size = sizeof(fpregs); -#endif -} - -} // namespace google_breakpad diff --git a/TMessagesProj/jni/breakpad/client/linux/dump_writer_common/thread_info.h b/TMessagesProj/jni/breakpad/client/linux/dump_writer_common/thread_info.h deleted file mode 100644 index 99093d2e0..000000000 --- a/TMessagesProj/jni/breakpad/client/linux/dump_writer_common/thread_info.h +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) 2014, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef CLIENT_LINUX_DUMP_WRITER_COMMON_THREAD_INFO_H_ -#define CLIENT_LINUX_DUMP_WRITER_COMMON_THREAD_INFO_H_ - -#include -#include - -#include "client/linux/dump_writer_common/raw_context_cpu.h" -#include "common/memory.h" -#include "google_breakpad/common/minidump_format.h" - -namespace google_breakpad { - -#if defined(__i386) || defined(__x86_64) -typedef __typeof__(((struct user*) 0)->u_debugreg[0]) debugreg_t; -#endif - -// We produce one of these structures for each thread in the crashed process. -struct ThreadInfo { - pid_t tgid; // thread group id - pid_t ppid; // parent process - - uintptr_t stack_pointer; // thread stack pointer - - -#if defined(__i386) || defined(__x86_64) - user_regs_struct regs; - user_fpregs_struct fpregs; - static const unsigned kNumDebugRegisters = 8; - debugreg_t dregs[8]; -#if defined(__i386) - user_fpxregs_struct fpxregs; -#endif // defined(__i386) - -#elif defined(__ARM_EABI__) - // Mimicking how strace does this(see syscall.c, search for GETREGS) - struct user_regs regs; - struct user_fpregs fpregs; -#elif defined(__aarch64__) - // Use the structures defined in - struct user_regs_struct regs; - struct user_fpsimd_struct fpregs; -#elif defined(__mips__) - // Use the structure defined in . - mcontext_t mcontext; -#endif - - // Returns the instruction pointer (platform-dependent impl.). - uintptr_t GetInstructionPointer() const; - - // Fills a RawContextCPU using the context in the ThreadInfo object. - void FillCPUContext(RawContextCPU* out) const; - - // Returns the pointer and size of general purpose register area. - void GetGeneralPurposeRegisters(void** gp_regs, size_t* size); - - // Returns the pointer and size of float point register area. - void GetFloatingPointRegisters(void** fp_regs, size_t* size); -}; - -} // namespace google_breakpad - -#endif // CLIENT_LINUX_DUMP_WRITER_COMMON_THREAD_INFO_H_ diff --git a/TMessagesProj/jni/breakpad/client/linux/dump_writer_common/ucontext_reader.cc b/TMessagesProj/jni/breakpad/client/linux/dump_writer_common/ucontext_reader.cc deleted file mode 100644 index d37fdeb01..000000000 --- a/TMessagesProj/jni/breakpad/client/linux/dump_writer_common/ucontext_reader.cc +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright (c) 2014, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "client/linux/dump_writer_common/ucontext_reader.h" - -#include "common/linux/linux_libc_support.h" -#include "google_breakpad/common/minidump_format.h" - -namespace google_breakpad { - -// Minidump defines register structures which are different from the raw -// structures which we get from the kernel. These are platform specific -// functions to juggle the ucontext and user structures into minidump format. - -#if defined(__i386__) - -uintptr_t UContextReader::GetStackPointer(const struct ucontext* uc) { - return uc->uc_mcontext.gregs[REG_ESP]; -} - -uintptr_t UContextReader::GetInstructionPointer(const struct ucontext* uc) { - return uc->uc_mcontext.gregs[REG_EIP]; -} - -void UContextReader::FillCPUContext(RawContextCPU *out, const ucontext *uc, - const struct _libc_fpstate* fp) { - const greg_t* regs = uc->uc_mcontext.gregs; - - out->context_flags = MD_CONTEXT_X86_FULL | - MD_CONTEXT_X86_FLOATING_POINT; - - out->gs = regs[REG_GS]; - out->fs = regs[REG_FS]; - out->es = regs[REG_ES]; - out->ds = regs[REG_DS]; - - out->edi = regs[REG_EDI]; - out->esi = regs[REG_ESI]; - out->ebx = regs[REG_EBX]; - out->edx = regs[REG_EDX]; - out->ecx = regs[REG_ECX]; - out->eax = regs[REG_EAX]; - - out->ebp = regs[REG_EBP]; - out->eip = regs[REG_EIP]; - out->cs = regs[REG_CS]; - out->eflags = regs[REG_EFL]; - out->esp = regs[REG_UESP]; - out->ss = regs[REG_SS]; - - out->float_save.control_word = fp->cw; - out->float_save.status_word = fp->sw; - out->float_save.tag_word = fp->tag; - out->float_save.error_offset = fp->ipoff; - out->float_save.error_selector = fp->cssel; - out->float_save.data_offset = fp->dataoff; - out->float_save.data_selector = fp->datasel; - - // 8 registers * 10 bytes per register. - my_memcpy(out->float_save.register_area, fp->_st, 10 * 8); -} - -#elif defined(__x86_64) - -uintptr_t UContextReader::GetStackPointer(const struct ucontext* uc) { - return uc->uc_mcontext.gregs[REG_RSP]; -} - -uintptr_t UContextReader::GetInstructionPointer(const struct ucontext* uc) { - return uc->uc_mcontext.gregs[REG_RIP]; -} - -void UContextReader::FillCPUContext(RawContextCPU *out, const ucontext *uc, - const struct _libc_fpstate* fpregs) { - const greg_t* regs = uc->uc_mcontext.gregs; - - out->context_flags = MD_CONTEXT_AMD64_FULL; - - out->cs = regs[REG_CSGSFS] & 0xffff; - - out->fs = (regs[REG_CSGSFS] >> 32) & 0xffff; - out->gs = (regs[REG_CSGSFS] >> 16) & 0xffff; - - out->eflags = regs[REG_EFL]; - - out->rax = regs[REG_RAX]; - out->rcx = regs[REG_RCX]; - out->rdx = regs[REG_RDX]; - out->rbx = regs[REG_RBX]; - - out->rsp = regs[REG_RSP]; - out->rbp = regs[REG_RBP]; - out->rsi = regs[REG_RSI]; - out->rdi = regs[REG_RDI]; - out->r8 = regs[REG_R8]; - out->r9 = regs[REG_R9]; - out->r10 = regs[REG_R10]; - out->r11 = regs[REG_R11]; - out->r12 = regs[REG_R12]; - out->r13 = regs[REG_R13]; - out->r14 = regs[REG_R14]; - out->r15 = regs[REG_R15]; - - out->rip = regs[REG_RIP]; - - out->flt_save.control_word = fpregs->cwd; - out->flt_save.status_word = fpregs->swd; - out->flt_save.tag_word = fpregs->ftw; - out->flt_save.error_opcode = fpregs->fop; - out->flt_save.error_offset = fpregs->rip; - out->flt_save.data_offset = fpregs->rdp; - out->flt_save.error_selector = 0; // We don't have this. - out->flt_save.data_selector = 0; // We don't have this. - out->flt_save.mx_csr = fpregs->mxcsr; - out->flt_save.mx_csr_mask = fpregs->mxcr_mask; - my_memcpy(&out->flt_save.float_registers, &fpregs->_st, 8 * 16); - my_memcpy(&out->flt_save.xmm_registers, &fpregs->_xmm, 16 * 16); -} - -#elif defined(__ARM_EABI__) - -uintptr_t UContextReader::GetStackPointer(const struct ucontext* uc) { - return uc->uc_mcontext.arm_sp; -} - -uintptr_t UContextReader::GetInstructionPointer(const struct ucontext* uc) { - return uc->uc_mcontext.arm_pc; -} - -void UContextReader::FillCPUContext(RawContextCPU *out, const ucontext *uc) { - out->context_flags = MD_CONTEXT_ARM_FULL; - - out->iregs[0] = uc->uc_mcontext.arm_r0; - out->iregs[1] = uc->uc_mcontext.arm_r1; - out->iregs[2] = uc->uc_mcontext.arm_r2; - out->iregs[3] = uc->uc_mcontext.arm_r3; - out->iregs[4] = uc->uc_mcontext.arm_r4; - out->iregs[5] = uc->uc_mcontext.arm_r5; - out->iregs[6] = uc->uc_mcontext.arm_r6; - out->iregs[7] = uc->uc_mcontext.arm_r7; - out->iregs[8] = uc->uc_mcontext.arm_r8; - out->iregs[9] = uc->uc_mcontext.arm_r9; - out->iregs[10] = uc->uc_mcontext.arm_r10; - - out->iregs[11] = uc->uc_mcontext.arm_fp; - out->iregs[12] = uc->uc_mcontext.arm_ip; - out->iregs[13] = uc->uc_mcontext.arm_sp; - out->iregs[14] = uc->uc_mcontext.arm_lr; - out->iregs[15] = uc->uc_mcontext.arm_pc; - - out->cpsr = uc->uc_mcontext.arm_cpsr; - - // TODO: fix this after fixing ExceptionHandler - out->float_save.fpscr = 0; - my_memset(&out->float_save.regs, 0, sizeof(out->float_save.regs)); - my_memset(&out->float_save.extra, 0, sizeof(out->float_save.extra)); -} - -#elif defined(__aarch64__) - -uintptr_t UContextReader::GetStackPointer(const struct ucontext* uc) { - return uc->uc_mcontext.sp; -} - -uintptr_t UContextReader::GetInstructionPointer(const struct ucontext* uc) { - return uc->uc_mcontext.pc; -} - -void UContextReader::FillCPUContext(RawContextCPU *out, const ucontext *uc, - const struct fpsimd_context* fpregs) { - out->context_flags = MD_CONTEXT_ARM64_FULL; - - out->cpsr = static_cast(uc->uc_mcontext.pstate); - for (int i = 0; i < MD_CONTEXT_ARM64_REG_SP; ++i) - out->iregs[i] = uc->uc_mcontext.regs[i]; - out->iregs[MD_CONTEXT_ARM64_REG_SP] = uc->uc_mcontext.sp; - out->iregs[MD_CONTEXT_ARM64_REG_PC] = uc->uc_mcontext.pc; - - out->float_save.fpsr = fpregs->fpsr; - out->float_save.fpcr = fpregs->fpcr; - my_memcpy(&out->float_save.regs, &fpregs->vregs, - MD_FLOATINGSAVEAREA_ARM64_FPR_COUNT * 16); -} - -#elif defined(__mips__) - -uintptr_t UContextReader::GetStackPointer(const struct ucontext* uc) { - return uc->uc_mcontext.gregs[MD_CONTEXT_MIPS_REG_SP]; -} - -uintptr_t UContextReader::GetInstructionPointer(const struct ucontext* uc) { - return uc->uc_mcontext.pc; -} - -void UContextReader::FillCPUContext(RawContextCPU *out, const ucontext *uc) { - out->context_flags = MD_CONTEXT_MIPS_FULL; - - for (int i = 0; i < MD_CONTEXT_MIPS_GPR_COUNT; ++i) - out->iregs[i] = uc->uc_mcontext.gregs[i]; - - out->mdhi = uc->uc_mcontext.mdhi; - out->mdlo = uc->uc_mcontext.mdlo; - - out->hi[0] = uc->uc_mcontext.hi1; - out->hi[1] = uc->uc_mcontext.hi2; - out->hi[2] = uc->uc_mcontext.hi3; - out->lo[0] = uc->uc_mcontext.lo1; - out->lo[1] = uc->uc_mcontext.lo2; - out->lo[2] = uc->uc_mcontext.lo3; - out->dsp_control = uc->uc_mcontext.dsp; - - out->epc = uc->uc_mcontext.pc; - out->badvaddr = 0; // Not reported in signal context. - out->status = 0; // Not reported in signal context. - out->cause = 0; // Not reported in signal context. - - for (int i = 0; i < MD_FLOATINGSAVEAREA_MIPS_FPR_COUNT; ++i) - out->float_save.regs[i] = uc->uc_mcontext.fpregs.fp_r.fp_dregs[i]; - - out->float_save.fpcsr = uc->uc_mcontext.fpc_csr; -#if _MIPS_SIM == _ABIO32 - out->float_save.fir = uc->uc_mcontext.fpc_eir; // Unused. -#endif -} -#endif - -} // namespace google_breakpad diff --git a/TMessagesProj/jni/breakpad/client/linux/dump_writer_common/ucontext_reader.h b/TMessagesProj/jni/breakpad/client/linux/dump_writer_common/ucontext_reader.h deleted file mode 100644 index b6e77b4b5..000000000 --- a/TMessagesProj/jni/breakpad/client/linux/dump_writer_common/ucontext_reader.h +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) 2014, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef CLIENT_LINUX_DUMP_WRITER_COMMON_UCONTEXT_READER_H -#define CLIENT_LINUX_DUMP_WRITER_COMMON_UCONTEXT_READER_H - -#include -#include - -#include "client/linux/dump_writer_common/raw_context_cpu.h" -#include "common/memory.h" -#include "google_breakpad/common/minidump_format.h" - -namespace google_breakpad { - -// Wraps platform-dependent implementations of accessors to ucontext structs. -struct UContextReader { - static uintptr_t GetStackPointer(const struct ucontext* uc); - - static uintptr_t GetInstructionPointer(const struct ucontext* uc); - - // Juggle a arch-specific ucontext into a minidump format - // out: the minidump structure - // info: the collection of register structures. -#if defined(__i386__) || defined(__x86_64) - static void FillCPUContext(RawContextCPU *out, const ucontext *uc, - const struct _libc_fpstate* fp); -#elif defined(__aarch64__) - static void FillCPUContext(RawContextCPU *out, const ucontext *uc, - const struct fpsimd_context* fpregs); -#else - static void FillCPUContext(RawContextCPU *out, const ucontext *uc); -#endif -}; - -} // namespace google_breakpad - -#endif // CLIENT_LINUX_DUMP_WRITER_COMMON_UCONTEXT_READER_H diff --git a/TMessagesProj/jni/breakpad/client/linux/handler/exception_handler.cc b/TMessagesProj/jni/breakpad/client/linux/handler/exception_handler.cc deleted file mode 100644 index 9b20fe251..000000000 --- a/TMessagesProj/jni/breakpad/client/linux/handler/exception_handler.cc +++ /dev/null @@ -1,754 +0,0 @@ -// Copyright (c) 2010 Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// The ExceptionHandler object installs signal handlers for a number of -// signals. We rely on the signal handler running on the thread which crashed -// in order to identify it. This is true of the synchronous signals (SEGV etc), -// but not true of ABRT. Thus, if you send ABRT to yourself in a program which -// uses ExceptionHandler, you need to use tgkill to direct it to the current -// thread. -// -// The signal flow looks like this: -// -// SignalHandler (uses a global stack of ExceptionHandler objects to find -// | one to handle the signal. If the first rejects it, try -// | the second etc...) -// V -// HandleSignal ----------------------------| (clones a new process which -// | | shares an address space with -// (wait for cloned | the crashed process. This -// process) | allows us to ptrace the crashed -// | | process) -// V V -// (set signal handler to ThreadEntry (static function to bounce -// SIG_DFL and rethrow, | back into the object) -// killing the crashed | -// process) V -// DoDump (writes minidump) -// | -// V -// sys_exit -// - -// This code is a little fragmented. Different functions of the ExceptionHandler -// class run in a number of different contexts. Some of them run in a normal -// context and are easy to code, others run in a compromised context and the -// restrictions at the top of minidump_writer.cc apply: no libc and use the -// alternative malloc. Each function should have comment above it detailing the -// context which it runs in. - -#include "client/linux/handler/exception_handler.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include "common/basictypes.h" -#include "common/linux/linux_libc_support.h" -#include "common/memory.h" -#include "client/linux/log/log.h" -#include "client/linux/microdump_writer/microdump_writer.h" -#include "client/linux/minidump_writer/linux_dumper.h" -#include "client/linux/minidump_writer/minidump_writer.h" -#include "common/linux/eintr_wrapper.h" -#include "third_party/lss/linux_syscall_support.h" - -#if defined(__ANDROID__) -#include "linux/sched.h" -#endif - -#ifndef PR_SET_PTRACER -#define PR_SET_PTRACER 0x59616d61 -#endif - -// A wrapper for the tgkill syscall: send a signal to a specific thread. -static int tgkill(pid_t tgid, pid_t tid, int sig) { - return syscall(__NR_tgkill, tgid, tid, sig); - return 0; -} - -namespace google_breakpad { - -namespace { -// The list of signals which we consider to be crashes. The default action for -// all these signals must be Core (see man 7 signal) because we rethrow the -// signal after handling it and expect that it'll be fatal. -const int kExceptionSignals[] = { - SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS -}; -const int kNumHandledSignals = - sizeof(kExceptionSignals) / sizeof(kExceptionSignals[0]); -struct sigaction old_handlers[kNumHandledSignals]; -bool handlers_installed = false; - -// InstallAlternateStackLocked will store the newly installed stack in new_stack -// and (if it exists) the previously installed stack in old_stack. -stack_t old_stack; -stack_t new_stack; -bool stack_installed = false; - -// Create an alternative stack to run the signal handlers on. This is done since -// the signal might have been caused by a stack overflow. -// Runs before crashing: normal context. -void InstallAlternateStackLocked() { - if (stack_installed) - return; - - memset(&old_stack, 0, sizeof(old_stack)); - memset(&new_stack, 0, sizeof(new_stack)); - - // SIGSTKSZ may be too small to prevent the signal handlers from overrunning - // the alternative stack. Ensure that the size of the alternative stack is - // large enough. - static const unsigned kSigStackSize = std::max(16384, SIGSTKSZ); - - // Only set an alternative stack if there isn't already one, or if the current - // one is too small. - if (sys_sigaltstack(NULL, &old_stack) == -1 || !old_stack.ss_sp || - old_stack.ss_size < kSigStackSize) { - new_stack.ss_sp = calloc(1, kSigStackSize); - new_stack.ss_size = kSigStackSize; - - if (sys_sigaltstack(&new_stack, NULL) == -1) { - free(new_stack.ss_sp); - return; - } - stack_installed = true; - } -} - -// Runs before crashing: normal context. -void RestoreAlternateStackLocked() { - if (!stack_installed) - return; - - stack_t current_stack; - if (sys_sigaltstack(NULL, ¤t_stack) == -1) - return; - - // Only restore the old_stack if the current alternative stack is the one - // installed by the call to InstallAlternateStackLocked. - if (current_stack.ss_sp == new_stack.ss_sp) { - if (old_stack.ss_sp) { - if (sys_sigaltstack(&old_stack, NULL) == -1) - return; - } else { - stack_t disable_stack; - disable_stack.ss_flags = SS_DISABLE; - if (sys_sigaltstack(&disable_stack, NULL) == -1) - return; - } - } - - free(new_stack.ss_sp); - stack_installed = false; -} - -void InstallDefaultHandler(int sig) { -#if defined(__ANDROID__) - // Android L+ expose signal and sigaction symbols that override the system - // ones. There is a bug in these functions where a request to set the handler - // to SIG_DFL is ignored. In that case, an infinite loop is entered as the - // signal is repeatedly sent to breakpad's signal handler. - // To work around this, directly call the system's sigaction. - struct kernel_sigaction sa; - memset(&sa, 0, sizeof(sa)); - sys_sigemptyset(&sa.sa_mask); - sa.sa_handler_ = SIG_DFL; - sa.sa_flags = SA_RESTART; - sys_rt_sigaction(sig, &sa, NULL, sizeof(kernel_sigset_t)); -#else - signal(sig, SIG_DFL); -#endif -} - -// The global exception handler stack. This is needed because there may exist -// multiple ExceptionHandler instances in a process. Each will have itself -// registered in this stack. -std::vector* g_handler_stack_ = NULL; -pthread_mutex_t g_handler_stack_mutex_ = PTHREAD_MUTEX_INITIALIZER; - -} // namespace - -// Runs before crashing: normal context. -ExceptionHandler::ExceptionHandler(const MinidumpDescriptor& descriptor, - FilterCallback filter, - MinidumpCallback callback, - void* callback_context, - bool install_handler, - const int server_fd) - : filter_(filter), - callback_(callback), - callback_context_(callback_context), - minidump_descriptor_(descriptor), - crash_handler_(NULL) { - if (server_fd >= 0) - crash_generation_client_.reset(CrashGenerationClient::TryCreate(server_fd)); - - if (!IsOutOfProcess() && !minidump_descriptor_.IsFD() && - !minidump_descriptor_.IsMicrodumpOnConsole()) - minidump_descriptor_.UpdatePath(); - -#if defined(__ANDROID__) - if (minidump_descriptor_.IsMicrodumpOnConsole()) - logger::initializeCrashLogWriter(); -#endif - - pthread_mutex_lock(&g_handler_stack_mutex_); - if (!g_handler_stack_) - g_handler_stack_ = new std::vector; - if (install_handler) { - InstallAlternateStackLocked(); - InstallHandlersLocked(); - } - g_handler_stack_->push_back(this); - pthread_mutex_unlock(&g_handler_stack_mutex_); -} - -// Runs before crashing: normal context. -ExceptionHandler::~ExceptionHandler() { - pthread_mutex_lock(&g_handler_stack_mutex_); - std::vector::iterator handler = - std::find(g_handler_stack_->begin(), g_handler_stack_->end(), this); - g_handler_stack_->erase(handler); - if (g_handler_stack_->empty()) { - delete g_handler_stack_; - g_handler_stack_ = NULL; - RestoreAlternateStackLocked(); - RestoreHandlersLocked(); - } - pthread_mutex_unlock(&g_handler_stack_mutex_); -} - -// Runs before crashing: normal context. -// static -bool ExceptionHandler::InstallHandlersLocked() { - if (handlers_installed) - return false; - - // Fail if unable to store all the old handlers. - for (int i = 0; i < kNumHandledSignals; ++i) { - if (sigaction(kExceptionSignals[i], NULL, &old_handlers[i]) == -1) - return false; - } - - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sigemptyset(&sa.sa_mask); - - // Mask all exception signals when we're handling one of them. - for (int i = 0; i < kNumHandledSignals; ++i) - sigaddset(&sa.sa_mask, kExceptionSignals[i]); - - sa.sa_sigaction = SignalHandler; - sa.sa_flags = SA_ONSTACK | SA_SIGINFO; - - for (int i = 0; i < kNumHandledSignals; ++i) { - if (sigaction(kExceptionSignals[i], &sa, NULL) == -1) { - // At this point it is impractical to back out changes, and so failure to - // install a signal is intentionally ignored. - } - } - handlers_installed = true; - return true; -} - -// This function runs in a compromised context: see the top of the file. -// Runs on the crashing thread. -// static -void ExceptionHandler::RestoreHandlersLocked() { - if (!handlers_installed) - return; - - for (int i = 0; i < kNumHandledSignals; ++i) { - if (sigaction(kExceptionSignals[i], &old_handlers[i], NULL) == -1) { - InstallDefaultHandler(kExceptionSignals[i]); - } - } - handlers_installed = false; -} - -// void ExceptionHandler::set_crash_handler(HandlerCallback callback) { -// crash_handler_ = callback; -// } - -// This function runs in a compromised context: see the top of the file. -// Runs on the crashing thread. -// static -void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) { - // All the exception signals are blocked at this point. - pthread_mutex_lock(&g_handler_stack_mutex_); - - // Sometimes, Breakpad runs inside a process where some other buggy code - // saves and restores signal handlers temporarily with 'signal' - // instead of 'sigaction'. This loses the SA_SIGINFO flag associated - // with this function. As a consequence, the values of 'info' and 'uc' - // become totally bogus, generally inducing a crash. - // - // The following code tries to detect this case. When it does, it - // resets the signal handlers with sigaction + SA_SIGINFO and returns. - // This forces the signal to be thrown again, but this time the kernel - // will call the function with the right arguments. - struct sigaction cur_handler; - if (sigaction(sig, NULL, &cur_handler) == 0 && - (cur_handler.sa_flags & SA_SIGINFO) == 0) { - // Reset signal handler with the right flags. - sigemptyset(&cur_handler.sa_mask); - sigaddset(&cur_handler.sa_mask, sig); - - cur_handler.sa_sigaction = SignalHandler; - cur_handler.sa_flags = SA_ONSTACK | SA_SIGINFO; - - if (sigaction(sig, &cur_handler, NULL) == -1) { - // When resetting the handler fails, try to reset the - // default one to avoid an infinite loop here. - InstallDefaultHandler(sig); - } - pthread_mutex_unlock(&g_handler_stack_mutex_); - return; - } - - bool handled = false; - for (int i = g_handler_stack_->size() - 1; !handled && i >= 0; --i) { - handled = (*g_handler_stack_)[i]->HandleSignal(sig, info, uc); - } - - // Upon returning from this signal handler, sig will become unmasked and then - // it will be retriggered. If one of the ExceptionHandlers handled it - // successfully, restore the default handler. Otherwise, restore the - // previously installed handler. Then, when the signal is retriggered, it will - // be delivered to the appropriate handler. - if (handled) { - InstallDefaultHandler(sig); - } else { - RestoreHandlersLocked(); - } - - pthread_mutex_unlock(&g_handler_stack_mutex_); - - // info->si_code <= 0 iff SI_FROMUSER (SI_FROMKERNEL otherwise). - if (info->si_code <= 0 || sig == SIGABRT) { - // This signal was triggered by somebody sending us the signal with kill(). - // In order to retrigger it, we have to queue a new signal by calling - // kill() ourselves. The special case (si_pid == 0 && sig == SIGABRT) is - // due to the kernel sending a SIGABRT from a user request via SysRQ. - if (tgkill(getpid(), syscall(__NR_gettid), sig) < 0) { - // If we failed to kill ourselves (e.g. because a sandbox disallows us - // to do so), we instead resort to terminating our process. This will - // result in an incorrect exit code. - _exit(1); - } - } else { - // This was a synchronous signal triggered by a hard fault (e.g. SIGSEGV). - // No need to reissue the signal. It will automatically trigger again, - // when we return from the signal handler. - } -} - -struct ThreadArgument { - pid_t pid; // the crashing process - const MinidumpDescriptor* minidump_descriptor; - ExceptionHandler* handler; - const void* context; // a CrashContext structure - size_t context_size; -}; - -// This is the entry function for the cloned process. We are in a compromised -// context here: see the top of the file. -// static -int ExceptionHandler::ThreadEntry(void *arg) { - const ThreadArgument *thread_arg = reinterpret_cast(arg); - - // Block here until the crashing process unblocks us when - // we're allowed to use ptrace - thread_arg->handler->WaitForContinueSignal(); - - return thread_arg->handler->DoDump(thread_arg->pid, thread_arg->context, - thread_arg->context_size) == false; -} - -// This function runs in a compromised context: see the top of the file. -// Runs on the crashing thread. -bool ExceptionHandler::HandleSignal(int sig, siginfo_t* info, void* uc) { - if (filter_ && !filter_(callback_context_)) - return false; - - // Allow ourselves to be dumped if the signal is trusted. - bool signal_trusted = info->si_code > 0; - bool signal_pid_trusted = info->si_code == SI_USER || - info->si_code == SI_TKILL; - if (signal_trusted || (signal_pid_trusted && info->si_pid == getpid())) { - sys_prctl(PR_SET_DUMPABLE, 1, 0, 0, 0); - } - CrashContext context; - // Fill in all the holes in the struct to make Valgrind happy. - memset(&context, 0, sizeof(context)); - memcpy(&context.siginfo, info, sizeof(siginfo_t)); - memcpy(&context.context, uc, sizeof(struct ucontext)); -#if defined(__aarch64__) - struct ucontext *uc_ptr = (struct ucontext*)uc; - struct fpsimd_context *fp_ptr = - (struct fpsimd_context*)&uc_ptr->uc_mcontext.__reserved; - if (fp_ptr->head.magic == FPSIMD_MAGIC) { - memcpy(&context.float_state, fp_ptr, sizeof(context.float_state)); - } -#elif !defined(__ARM_EABI__) && !defined(__mips__) - // FP state is not part of user ABI on ARM Linux. - // In case of MIPS Linux FP state is already part of struct ucontext - // and 'float_state' is not a member of CrashContext. - struct ucontext *uc_ptr = (struct ucontext*)uc; - if (uc_ptr->uc_mcontext.fpregs) { - memcpy(&context.float_state, - uc_ptr->uc_mcontext.fpregs, - sizeof(context.float_state)); - } -#endif - context.tid = syscall(__NR_gettid); - if (crash_handler_ != NULL) { - if (crash_handler_(&context, sizeof(context), callback_context_)) { - return true; - } - } - return GenerateDump(&context); -} - -// This is a public interface to HandleSignal that allows the client to -// generate a crash dump. This function may run in a compromised context. -bool ExceptionHandler::SimulateSignalDelivery(int sig) { - siginfo_t siginfo = {}; - // Mimic a trusted signal to allow tracing the process (see - // ExceptionHandler::HandleSignal(). - siginfo.si_code = SI_USER; - siginfo.si_pid = getpid(); - struct ucontext context; - getcontext(&context); - return HandleSignal(sig, &siginfo, &context); -} - -// This function may run in a compromised context: see the top of the file. -bool ExceptionHandler::GenerateDump(CrashContext *context) { - if (IsOutOfProcess()) - return crash_generation_client_->RequestDump(context, sizeof(*context)); - - // Allocating too much stack isn't a problem, and better to err on the side - // of caution than smash it into random locations. - static const unsigned kChildStackSize = 16000; - PageAllocator allocator; - uint8_t* stack = reinterpret_cast(allocator.Alloc(kChildStackSize)); - if (!stack) - return false; - // clone() needs the top-most address. (scrub just to be safe) - stack += kChildStackSize; - my_memset(stack - 16, 0, 16); - - ThreadArgument thread_arg; - thread_arg.handler = this; - thread_arg.minidump_descriptor = &minidump_descriptor_; - thread_arg.pid = getpid(); - thread_arg.context = context; - thread_arg.context_size = sizeof(*context); - - // We need to explicitly enable ptrace of parent processes on some - // kernels, but we need to know the PID of the cloned process before we - // can do this. Create a pipe here which we can use to block the - // cloned process after creating it, until we have explicitly enabled ptrace - if (sys_pipe(fdes) == -1) { - // Creating the pipe failed. We'll log an error but carry on anyway, - // as we'll probably still get a useful crash report. All that will happen - // is the write() and read() calls will fail with EBADF - static const char no_pipe_msg[] = "ExceptionHandler::GenerateDump " - "sys_pipe failed:"; - logger::write(no_pipe_msg, sizeof(no_pipe_msg) - 1); - logger::write(strerror(errno), strlen(strerror(errno))); - logger::write("\n", 1); - - // Ensure fdes[0] and fdes[1] are invalid file descriptors. - fdes[0] = fdes[1] = -1; - } - - const pid_t child = sys_clone( - ThreadEntry, stack, CLONE_FILES | CLONE_FS | CLONE_UNTRACED, - &thread_arg, NULL, NULL, NULL); - if (child == -1) { - sys_close(fdes[0]); - sys_close(fdes[1]); - return false; - } - - // Allow the child to ptrace us - sys_prctl(PR_SET_PTRACER, child, 0, 0, 0); - SendContinueSignalToChild(); - int status; - const int r = HANDLE_EINTR(sys_waitpid(child, &status, __WALL)); - - sys_close(fdes[0]); - sys_close(fdes[1]); - - if (r == -1) { - static const char msg[] = "ExceptionHandler::GenerateDump waitpid failed:"; - logger::write(msg, sizeof(msg) - 1); - logger::write(strerror(errno), strlen(strerror(errno))); - logger::write("\n", 1); - } - - bool success = r != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0; - if (callback_) - success = callback_(minidump_descriptor_, callback_context_, success); - return success; -} - -// This function runs in a compromised context: see the top of the file. -void ExceptionHandler::SendContinueSignalToChild() { - static const char okToContinueMessage = 'a'; - int r; - r = HANDLE_EINTR(sys_write(fdes[1], &okToContinueMessage, sizeof(char))); - if (r == -1) { - static const char msg[] = "ExceptionHandler::SendContinueSignalToChild " - "sys_write failed:"; - logger::write(msg, sizeof(msg) - 1); - logger::write(strerror(errno), strlen(strerror(errno))); - logger::write("\n", 1); - } -} - -// This function runs in a compromised context: see the top of the file. -// Runs on the cloned process. -void ExceptionHandler::WaitForContinueSignal() { - int r; - char receivedMessage; - r = HANDLE_EINTR(sys_read(fdes[0], &receivedMessage, sizeof(char))); - if (r == -1) { - static const char msg[] = "ExceptionHandler::WaitForContinueSignal " - "sys_read failed:"; - logger::write(msg, sizeof(msg) - 1); - logger::write(strerror(errno), strlen(strerror(errno))); - logger::write("\n", 1); - } -} - -// This function runs in a compromised context: see the top of the file. -// Runs on the cloned process. -bool ExceptionHandler::DoDump(pid_t crashing_process, const void* context, - size_t context_size) { - if (minidump_descriptor_.IsMicrodumpOnConsole()) { - return google_breakpad::WriteMicrodump( - crashing_process, - context, - context_size, - mapping_list_, - minidump_descriptor_.microdump_build_fingerprint(), - minidump_descriptor_.microdump_product_info()); - } - if (minidump_descriptor_.IsFD()) { - return google_breakpad::WriteMinidump(minidump_descriptor_.fd(), - minidump_descriptor_.size_limit(), - crashing_process, - context, - context_size, - mapping_list_, - app_memory_list_); - } - return google_breakpad::WriteMinidump(minidump_descriptor_.path(), - minidump_descriptor_.size_limit(), - crashing_process, - context, - context_size, - mapping_list_, - app_memory_list_); -} - -// static -bool ExceptionHandler::WriteMinidump(const string& dump_path, - MinidumpCallback callback, - void* callback_context) { - MinidumpDescriptor descriptor(dump_path); - ExceptionHandler eh(descriptor, NULL, callback, callback_context, false, -1); - return eh.WriteMinidump(); -} - -// In order to making using EBP to calculate the desired value for ESP -// a valid operation, ensure that this function is compiled with a -// frame pointer using the following attribute. This attribute -// is supported on GCC but not on clang. -#if defined(__i386__) && defined(__GNUC__) && !defined(__clang__) -__attribute__((optimize("no-omit-frame-pointer"))) -#endif -bool ExceptionHandler::WriteMinidump() { - if (!IsOutOfProcess() && !minidump_descriptor_.IsFD() && - !minidump_descriptor_.IsMicrodumpOnConsole()) { - // Update the path of the minidump so that this can be called multiple times - // and new files are created for each minidump. This is done before the - // generation happens, as clients may want to access the MinidumpDescriptor - // after this call to find the exact path to the minidump file. - minidump_descriptor_.UpdatePath(); - } else if (minidump_descriptor_.IsFD()) { - // Reposition the FD to its beginning and resize it to get rid of the - // previous minidump info. - lseek(minidump_descriptor_.fd(), 0, SEEK_SET); - ignore_result(ftruncate(minidump_descriptor_.fd(), 0)); - } - - // Allow this process to be dumped. - sys_prctl(PR_SET_DUMPABLE, 1, 0, 0, 0); - - CrashContext context; - int getcontext_result = getcontext(&context.context); - if (getcontext_result) - return false; - -#if defined(__i386__) - // In CPUFillFromUContext in minidumpwriter.cc the stack pointer is retrieved - // from REG_UESP instead of from REG_ESP. REG_UESP is the user stack pointer - // and it only makes sense when running in kernel mode with a different stack - // pointer. When WriteMiniDump is called during normal processing REG_UESP is - // zero which leads to bad minidump files. - if (!context.context.uc_mcontext.gregs[REG_UESP]) { - // If REG_UESP is set to REG_ESP then that includes the stack space for the - // CrashContext object in this function, which is about 128 KB. Since the - // Linux dumper only records 32 KB of stack this would mean that nothing - // useful would be recorded. A better option is to set REG_UESP to REG_EBP, - // perhaps with a small negative offset in case there is any code that - // objects to them being equal. - context.context.uc_mcontext.gregs[REG_UESP] = - context.context.uc_mcontext.gregs[REG_EBP] - 16; - // The stack saving is based off of REG_ESP so it must be set to match the - // new REG_UESP. - context.context.uc_mcontext.gregs[REG_ESP] = - context.context.uc_mcontext.gregs[REG_UESP]; - } -#endif - -#if !defined(__ARM_EABI__) && !defined(__aarch64__) && !defined(__mips__) - // FPU state is not part of ARM EABI ucontext_t. - memcpy(&context.float_state, context.context.uc_mcontext.fpregs, - sizeof(context.float_state)); -#endif - context.tid = sys_gettid(); - - // Add an exception stream to the minidump for better reporting. - memset(&context.siginfo, 0, sizeof(context.siginfo)); - context.siginfo.si_signo = MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED; -#if defined(__i386__) - context.siginfo.si_addr = - reinterpret_cast(context.context.uc_mcontext.gregs[REG_EIP]); -#elif defined(__x86_64__) - context.siginfo.si_addr = - reinterpret_cast(context.context.uc_mcontext.gregs[REG_RIP]); -#elif defined(__arm__) - context.siginfo.si_addr = - reinterpret_cast(context.context.uc_mcontext.arm_pc); -#elif defined(__aarch64__) - context.siginfo.si_addr = - reinterpret_cast(context.context.uc_mcontext.pc); -#elif defined(__mips__) - context.siginfo.si_addr = - reinterpret_cast(context.context.uc_mcontext.pc); -#else -#error "This code has not been ported to your platform yet." -#endif - - return GenerateDump(&context); -} - -void ExceptionHandler::AddMappingInfo(const string& name, - const uint8_t identifier[sizeof(MDGUID)], - uintptr_t start_address, - size_t mapping_size, - size_t file_offset) { - MappingInfo info; - info.start_addr = start_address; - info.size = mapping_size; - info.offset = file_offset; - strncpy(info.name, name.c_str(), sizeof(info.name) - 1); - info.name[sizeof(info.name) - 1] = '\0'; - - MappingEntry mapping; - mapping.first = info; - memcpy(mapping.second, identifier, sizeof(MDGUID)); - mapping_list_.push_back(mapping); -} - -void ExceptionHandler::RegisterAppMemory(void* ptr, size_t length) { - AppMemoryList::iterator iter = - std::find(app_memory_list_.begin(), app_memory_list_.end(), ptr); - if (iter != app_memory_list_.end()) { - // Don't allow registering the same pointer twice. - return; - } - - AppMemory app_memory; - app_memory.ptr = ptr; - app_memory.length = length; - app_memory_list_.push_back(app_memory); -} - -void ExceptionHandler::UnregisterAppMemory(void* ptr) { - AppMemoryList::iterator iter = - std::find(app_memory_list_.begin(), app_memory_list_.end(), ptr); - if (iter != app_memory_list_.end()) { - app_memory_list_.erase(iter); - } -} - -// static -bool ExceptionHandler::WriteMinidumpForChild(pid_t child, - pid_t child_blamed_thread, - const string& dump_path, - MinidumpCallback callback, - void* callback_context) { - // This function is not run in a compromised context. - MinidumpDescriptor descriptor(dump_path); - descriptor.UpdatePath(); - if (!google_breakpad::WriteMinidump(descriptor.path(), - child, - child_blamed_thread)) - return false; - - return callback ? callback(descriptor, callback_context, true) : true; -} - -} // namespace google_breakpad diff --git a/TMessagesProj/jni/breakpad/client/linux/handler/exception_handler.h b/TMessagesProj/jni/breakpad/client/linux/handler/exception_handler.h deleted file mode 100644 index 591c31085..000000000 --- a/TMessagesProj/jni/breakpad/client/linux/handler/exception_handler.h +++ /dev/null @@ -1,278 +0,0 @@ -// Copyright (c) 2010 Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_ -#define CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_ - -#include -#include -#include -#include - -#include - -#include "client/linux/crash_generation/crash_generation_client.h" -#include "client/linux/handler/minidump_descriptor.h" -#include "client/linux/minidump_writer/minidump_writer.h" -#include "common/scoped_ptr.h" -#include "common/using_std_string.h" -#include "google_breakpad/common/minidump_format.h" - -namespace google_breakpad { - -// ExceptionHandler -// -// ExceptionHandler can write a minidump file when an exception occurs, -// or when WriteMinidump() is called explicitly by your program. -// -// To have the exception handler write minidumps when an uncaught exception -// (crash) occurs, you should create an instance early in the execution -// of your program, and keep it around for the entire time you want to -// have crash handling active (typically, until shutdown). -// (NOTE): There should be only be one this kind of exception handler -// object per process. -// -// If you want to write minidumps without installing the exception handler, -// you can create an ExceptionHandler with install_handler set to false, -// then call WriteMinidump. You can also use this technique if you want to -// use different minidump callbacks for different call sites. -// -// In either case, a callback function is called when a minidump is written, -// which receives the full path or file descriptor of the minidump. The -// caller can collect and write additional application state to that minidump, -// and launch an external crash-reporting application. -// -// Caller should try to make the callbacks as crash-friendly as possible, -// it should avoid use heap memory allocation as much as possible. - -class ExceptionHandler { - public: - // A callback function to run before Breakpad performs any substantial - // processing of an exception. A FilterCallback is called before writing - // a minidump. |context| is the parameter supplied by the user as - // callback_context when the handler was created. - // - // If a FilterCallback returns true, Breakpad will continue processing, - // attempting to write a minidump. If a FilterCallback returns false, - // Breakpad will immediately report the exception as unhandled without - // writing a minidump, allowing another handler the opportunity to handle it. - typedef bool (*FilterCallback)(void *context); - - // A callback function to run after the minidump has been written. - // |descriptor| contains the file descriptor or file path containing the - // minidump. |context| is the parameter supplied by the user as - // callback_context when the handler was created. |succeeded| indicates - // whether a minidump file was successfully written. - // - // If an exception occurred and the callback returns true, Breakpad will - // treat the exception as fully-handled, suppressing any other handlers from - // being notified of the exception. If the callback returns false, Breakpad - // will treat the exception as unhandled, and allow another handler to handle - // it. If there are no other handlers, Breakpad will report the exception to - // the system as unhandled, allowing a debugger or native crash dialog the - // opportunity to handle the exception. Most callback implementations - // should normally return the value of |succeeded|, or when they wish to - // not report an exception of handled, false. Callbacks will rarely want to - // return true directly (unless |succeeded| is true). - typedef bool (*MinidumpCallback)(const MinidumpDescriptor& descriptor, - void* context, - bool succeeded); - - // In certain cases, a user may wish to handle the generation of the minidump - // themselves. In this case, they can install a handler callback which is - // called when a crash has occurred. If this function returns true, no other - // processing of occurs and the process will shortly be crashed. If this - // returns false, the normal processing continues. - typedef bool (*HandlerCallback)(const void* crash_context, - size_t crash_context_size, - void* context); - - // Creates a new ExceptionHandler instance to handle writing minidumps. - // Before writing a minidump, the optional |filter| callback will be called. - // Its return value determines whether or not Breakpad should write a - // minidump. The minidump content will be written to the file path or file - // descriptor from |descriptor|, and the optional |callback| is called after - // writing the dump file, as described above. - // If install_handler is true, then a minidump will be written whenever - // an unhandled exception occurs. If it is false, minidumps will only - // be written when WriteMinidump is called. - // If |server_fd| is valid, the minidump is generated out-of-process. If it - // is -1, in-process generation will always be used. - ExceptionHandler(const MinidumpDescriptor& descriptor, - FilterCallback filter, - MinidumpCallback callback, - void* callback_context, - bool install_handler, - const int server_fd); - ~ExceptionHandler(); - - const MinidumpDescriptor& minidump_descriptor() const { - return minidump_descriptor_; - } - - void set_minidump_descriptor(const MinidumpDescriptor& descriptor) { - minidump_descriptor_ = descriptor; - } - - void set_crash_handler(HandlerCallback callback) { - crash_handler_ = callback; - } - - void set_crash_generation_client(CrashGenerationClient* client) { - crash_generation_client_.reset(client); - } - - // Writes a minidump immediately. This can be used to capture the execution - // state independently of a crash. - // Returns true on success. - // If the ExceptionHandler has been created with a path, a new file is - // generated for each minidump. The file path can be retrieved in the - // MinidumpDescriptor passed to the MinidumpCallback or by accessing the - // MinidumpDescriptor directly from the ExceptionHandler (with - // minidump_descriptor()). - // If the ExceptionHandler has been created with a file descriptor, the file - // descriptor is repositioned to its beginning and the previous generated - // minidump is overwritten. - // Note that this method is not supposed to be called from a compromised - // context as it uses the heap. - bool WriteMinidump(); - - // Convenience form of WriteMinidump which does not require an - // ExceptionHandler instance. - static bool WriteMinidump(const string& dump_path, - MinidumpCallback callback, - void* callback_context); - - // Write a minidump of |child| immediately. This can be used to - // capture the execution state of |child| independently of a crash. - // Pass a meaningful |child_blamed_thread| to make that thread in - // the child process the one from which a crash signature is - // extracted. - // - // WARNING: the return of this function *must* happen before - // the code that will eventually reap |child| executes. - // Otherwise there's a pernicious race condition in which |child| - // exits, is reaped, another process created with its pid, then that - // new process dumped. - static bool WriteMinidumpForChild(pid_t child, - pid_t child_blamed_thread, - const string& dump_path, - MinidumpCallback callback, - void* callback_context); - - // This structure is passed to minidump_writer.h:WriteMinidump via an opaque - // blob. It shouldn't be needed in any user code. - struct CrashContext { - siginfo_t siginfo; - pid_t tid; // the crashing thread. - struct ucontext context; -#if !defined(__ARM_EABI__) && !defined(__mips__) - // #ifdef this out because FP state is not part of user ABI for Linux ARM. - // In case of MIPS Linux FP state is already part of struct - // ucontext so 'float_state' is not required. - fpstate_t float_state; -#endif - }; - - // Returns whether out-of-process dump generation is used or not. - bool IsOutOfProcess() const { - return crash_generation_client_.get() != NULL; - } - - // Add information about a memory mapping. This can be used if - // a custom library loader is used that maps things in a way - // that the linux dumper can't handle by reading the maps file. - void AddMappingInfo(const string& name, - const uint8_t identifier[sizeof(MDGUID)], - uintptr_t start_address, - size_t mapping_size, - size_t file_offset); - - // Register a block of memory of length bytes starting at address ptr - // to be copied to the minidump when a crash happens. - void RegisterAppMemory(void* ptr, size_t length); - - // Unregister a block of memory that was registered with RegisterAppMemory. - void UnregisterAppMemory(void* ptr); - - // Force signal handling for the specified signal. - bool SimulateSignalDelivery(int sig); - - // Report a crash signal from an SA_SIGINFO signal handler. - bool HandleSignal(int sig, siginfo_t* info, void* uc); - - private: - // Save the old signal handlers and install new ones. - static bool InstallHandlersLocked(); - // Restore the old signal handlers. - static void RestoreHandlersLocked(); - - void PreresolveSymbols(); - bool GenerateDump(CrashContext *context); - void SendContinueSignalToChild(); - void WaitForContinueSignal(); - - static void SignalHandler(int sig, siginfo_t* info, void* uc); - static int ThreadEntry(void* arg); - bool DoDump(pid_t crashing_process, const void* context, - size_t context_size); - - const FilterCallback filter_; - const MinidumpCallback callback_; - void* const callback_context_; - - scoped_ptr crash_generation_client_; - - MinidumpDescriptor minidump_descriptor_; - - // Must be volatile. The compiler is unaware of the code which runs in - // the signal handler which reads this variable. Without volatile the - // compiler is free to optimise away writes to this variable which it - // believes are never read. - volatile HandlerCallback crash_handler_; - - // We need to explicitly enable ptrace of parent processes on some - // kernels, but we need to know the PID of the cloned process before we - // can do this. We create a pipe which we can use to block the - // cloned process after creating it, until we have explicitly enabled - // ptrace. This is used to store the file descriptors for the pipe - int fdes[2]; - - // Callers can add extra info about mappings for cases where the - // dumper code cannot extract enough information from /proc//maps. - MappingList mapping_list_; - - // Callers can request additional memory regions to be included in - // the dump. - AppMemoryList app_memory_list_; -}; - -} // namespace google_breakpad - -#endif // CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_ diff --git a/TMessagesProj/jni/breakpad/client/linux/handler/minidump_descriptor.cc b/TMessagesProj/jni/breakpad/client/linux/handler/minidump_descriptor.cc deleted file mode 100644 index c601d35f0..000000000 --- a/TMessagesProj/jni/breakpad/client/linux/handler/minidump_descriptor.cc +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) 2012 Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include - -#include "client/linux/handler/minidump_descriptor.h" - -#include "common/linux/guid_creator.h" - -namespace google_breakpad { - -//static -const MinidumpDescriptor::MicrodumpOnConsole - MinidumpDescriptor::kMicrodumpOnConsole = {}; - -MinidumpDescriptor::MinidumpDescriptor(const MinidumpDescriptor& descriptor) - : mode_(descriptor.mode_), - fd_(descriptor.fd_), - directory_(descriptor.directory_), - c_path_(NULL), - size_limit_(descriptor.size_limit_), - microdump_build_fingerprint_(descriptor.microdump_build_fingerprint_), - microdump_product_info_(descriptor.microdump_product_info_) { - // The copy constructor is not allowed to be called on a MinidumpDescriptor - // with a valid path_, as getting its c_path_ would require the heap which - // can cause problems in compromised environments. - assert(descriptor.path_.empty()); -} - -MinidumpDescriptor& MinidumpDescriptor::operator=( - const MinidumpDescriptor& descriptor) { - assert(descriptor.path_.empty()); - - mode_ = descriptor.mode_; - fd_ = descriptor.fd_; - directory_ = descriptor.directory_; - path_.clear(); - if (c_path_) { - // This descriptor already had a path set, so generate a new one. - c_path_ = NULL; - UpdatePath(); - } - size_limit_ = descriptor.size_limit_; - microdump_build_fingerprint_ = descriptor.microdump_build_fingerprint_; - microdump_product_info_ = descriptor.microdump_product_info_; - return *this; -} - -void MinidumpDescriptor::UpdatePath() { - assert(mode_ == kWriteMinidumpToFile && !directory_.empty()); - - GUID guid; - char guid_str[kGUIDStringLength + 1]; - if (!CreateGUID(&guid) || !GUIDToString(&guid, guid_str, sizeof(guid_str))) { - assert(false); - } - - path_.clear(); - path_ = directory_ + "/" + guid_str + ".dmp"; - c_path_ = path_.c_str(); -} - -void MinidumpDescriptor::SetMicrodumpBuildFingerprint( - const char* build_fingerprint) { - assert(mode_ == kWriteMicrodumpToConsole); - microdump_build_fingerprint_ = build_fingerprint; -} - -void MinidumpDescriptor::SetMicrodumpProductInfo(const char* product_info) { - assert(mode_ == kWriteMicrodumpToConsole); - microdump_product_info_ = product_info; -} - -} // namespace google_breakpad diff --git a/TMessagesProj/jni/breakpad/client/linux/handler/minidump_descriptor.h b/TMessagesProj/jni/breakpad/client/linux/handler/minidump_descriptor.h deleted file mode 100644 index 3584c6922..000000000 --- a/TMessagesProj/jni/breakpad/client/linux/handler/minidump_descriptor.h +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright (c) 2012 Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef CLIENT_LINUX_HANDLER_MINIDUMP_DESCRIPTOR_H_ -#define CLIENT_LINUX_HANDLER_MINIDUMP_DESCRIPTOR_H_ - -#include -#include - -#include - -#include "common/using_std_string.h" - -// This class describes how a crash dump should be generated, either: -// - Writing a full minidump to a file in a given directory (the actual path, -// inside the directory, is determined by this class). -// - Writing a full minidump to a given fd. -// - Writing a reduced microdump to the console (logcat on Android). -namespace google_breakpad { - -class MinidumpDescriptor { - public: - struct MicrodumpOnConsole {}; - static const MicrodumpOnConsole kMicrodumpOnConsole; - - MinidumpDescriptor() : mode_(kUninitialized), - fd_(-1), - size_limit_(-1), - microdump_build_fingerprint_(NULL), - microdump_product_info_(NULL) {} - - explicit MinidumpDescriptor(const string& directory) - : mode_(kWriteMinidumpToFile), - fd_(-1), - directory_(directory), - c_path_(NULL), - size_limit_(-1), - microdump_build_fingerprint_(NULL), - microdump_product_info_(NULL) { - assert(!directory.empty()); - } - - explicit MinidumpDescriptor(int fd) - : mode_(kWriteMinidumpToFd), - fd_(fd), - c_path_(NULL), - size_limit_(-1), - microdump_build_fingerprint_(NULL), - microdump_product_info_(NULL) { - assert(fd != -1); - } - - explicit MinidumpDescriptor(const MicrodumpOnConsole&) - : mode_(kWriteMicrodumpToConsole), - fd_(-1), - size_limit_(-1), - microdump_build_fingerprint_(NULL), - microdump_product_info_(NULL) {} - - explicit MinidumpDescriptor(const MinidumpDescriptor& descriptor); - MinidumpDescriptor& operator=(const MinidumpDescriptor& descriptor); - - static MinidumpDescriptor getMicrodumpDescriptor(); - - bool IsFD() const { return mode_ == kWriteMinidumpToFd; } - - int fd() const { return fd_; } - - string directory() const { return directory_; } - - const char* path() const { return c_path_; } - - bool IsMicrodumpOnConsole() const { - return mode_ == kWriteMicrodumpToConsole; - } - - // Updates the path so it is unique. - // Should be called from a normal context: this methods uses the heap. - void UpdatePath(); - - off_t size_limit() const { return size_limit_; } - void set_size_limit(off_t limit) { size_limit_ = limit; } - - // TODO(primiano): make this and product info (below) just part of the - // microdump ctor once it is rolled stably into Chrome. ETA: June 2015. - void SetMicrodumpBuildFingerprint(const char* build_fingerprint); - const char* microdump_build_fingerprint() const { - return microdump_build_fingerprint_; - } - - void SetMicrodumpProductInfo(const char* product_info); - const char* microdump_product_info() const { - return microdump_product_info_; - } - - private: - enum DumpMode { - kUninitialized = 0, - kWriteMinidumpToFile, - kWriteMinidumpToFd, - kWriteMicrodumpToConsole - }; - - // Specifies the dump mode (see DumpMode). - DumpMode mode_; - - // The file descriptor where the minidump is generated. - int fd_; - - // The directory where the minidump should be generated. - string directory_; - - // The full path to the generated minidump. - string path_; - - // The C string of |path_|. Precomputed so it can be access from a compromised - // context. - const char* c_path_; - - off_t size_limit_; - - // The product name/version and build fingerprint that should be appended to - // the dump (microdump only). Microdumps don't have the ability of appending - // extra metadata after the dump is generated (as opposite to minidumps - // MIME fields), therefore the product details must be provided upfront. - // The string pointers are supposed to be valid through all the lifetime of - // the process (read: the caller has to guarantee that they are stored in - // global static storage). - const char* microdump_build_fingerprint_; - const char* microdump_product_info_; -}; - -} // namespace google_breakpad - -#endif // CLIENT_LINUX_HANDLER_MINIDUMP_DESCRIPTOR_H_ diff --git a/TMessagesProj/jni/breakpad/client/linux/log/log.cc b/TMessagesProj/jni/breakpad/client/linux/log/log.cc deleted file mode 100644 index fc23aa6d5..000000000 --- a/TMessagesProj/jni/breakpad/client/linux/log/log.cc +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) 2012 Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "client/linux/log/log.h" - -#if defined(__ANDROID__) -#include -#include -#else -#include "third_party/lss/linux_syscall_support.h" -#endif - -namespace logger { - -#if defined(__ANDROID__) -namespace { - -// __android_log_buf_write() is not exported in the NDK and is being used by -// dynamic runtime linking. Its declaration is taken from Android's -// system/core/include/log/log.h. -using AndroidLogBufferWriteFunc = int (*)(int bufID, int prio, const char *tag, - const char *text); -const int kAndroidCrashLogId = 4; // From LOG_ID_CRASH in log.h. -const char kAndroidLogTag[] = "google-breakpad"; - -bool g_crash_log_initialized = false; -AndroidLogBufferWriteFunc g_android_log_buf_write = nullptr; - -} // namespace - -void initializeCrashLogWriter() { - if (g_crash_log_initialized) - return; - g_android_log_buf_write = reinterpret_cast( - dlsym(RTLD_DEFAULT, "__android_log_buf_write")); - g_crash_log_initialized = true; -} - -int writeToCrashLog(const char* buf) { - // Try writing to the crash log ring buffer. If not available, fall back to - // the standard log buffer. - if (g_android_log_buf_write) { - return g_android_log_buf_write(kAndroidCrashLogId, ANDROID_LOG_FATAL, - kAndroidLogTag, buf); - } - return __android_log_write(ANDROID_LOG_FATAL, kAndroidLogTag, buf); -} -#endif - -int write(const char* buf, size_t nbytes) { -#if defined(__ANDROID__) - return __android_log_write(ANDROID_LOG_WARN, kAndroidLogTag, buf); -#else - return sys_write(2, buf, nbytes); -#endif -} - -} // namespace logger diff --git a/TMessagesProj/jni/breakpad/client/linux/log/log.h b/TMessagesProj/jni/breakpad/client/linux/log/log.h deleted file mode 100644 index f94bbd5fb..000000000 --- a/TMessagesProj/jni/breakpad/client/linux/log/log.h +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2012, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef CLIENT_LINUX_LOG_LOG_H_ -#define CLIENT_LINUX_LOG_LOG_H_ - -#include - -namespace logger { - -int write(const char* buf, size_t nbytes); - -// In the case of Android the log can be written to the default system log -// (default behavior of write() above, or to the crash log (see -// writeToCrashLog() below). -#if defined(__ANDROID__) - -// The logger must be initialized in a non-compromised context. -void initializeCrashLogWriter(); - -// Once initialized, writeToCrashLog is safe to use in a compromised context, -// even if the initialization failed, in which case this will silently fall -// back on write(). -int writeToCrashLog(const char* buf); -#endif - -} // namespace logger - -#endif // CLIENT_LINUX_LOG_LOG_H_ diff --git a/TMessagesProj/jni/breakpad/client/linux/microdump_writer/microdump_writer.cc b/TMessagesProj/jni/breakpad/client/linux/microdump_writer/microdump_writer.cc deleted file mode 100644 index d2eaa6ee6..000000000 --- a/TMessagesProj/jni/breakpad/client/linux/microdump_writer/microdump_writer.cc +++ /dev/null @@ -1,425 +0,0 @@ -// Copyright (c) 2014, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// This translation unit generates microdumps into the console (logcat on -// Android). See crbug.com/410294 for more info and design docs. - -#include "client/linux/microdump_writer/microdump_writer.h" - -#include - -#include "client/linux/dump_writer_common/thread_info.h" -#include "client/linux/dump_writer_common/ucontext_reader.h" -#include "client/linux/handler/exception_handler.h" -#include "client/linux/log/log.h" -#include "client/linux/minidump_writer/linux_ptrace_dumper.h" -#include "common/linux/linux_libc_support.h" - -namespace { - -using google_breakpad::ExceptionHandler; -using google_breakpad::LinuxDumper; -using google_breakpad::LinuxPtraceDumper; -using google_breakpad::MappingInfo; -using google_breakpad::MappingList; -using google_breakpad::RawContextCPU; -using google_breakpad::ThreadInfo; -using google_breakpad::UContextReader; - -const size_t kLineBufferSize = 2048; - -class MicrodumpWriter { - public: - MicrodumpWriter(const ExceptionHandler::CrashContext* context, - const MappingList& mappings, - const char* build_fingerprint, - const char* product_info, - LinuxDumper* dumper) - : ucontext_(context ? &context->context : NULL), -#if !defined(__ARM_EABI__) && !defined(__mips__) - float_state_(context ? &context->float_state : NULL), -#endif - dumper_(dumper), - mapping_list_(mappings), - build_fingerprint_(build_fingerprint), - product_info_(product_info), - log_line_(NULL) { - log_line_ = reinterpret_cast(Alloc(kLineBufferSize)); - if (log_line_) - log_line_[0] = '\0'; // Clear out the log line buffer. - } - - ~MicrodumpWriter() { dumper_->ThreadsResume(); } - - bool Init() { - // In the exceptional case where the system was out of memory and there - // wasn't even room to allocate the line buffer, bail out. There is nothing - // useful we can possibly achieve without the ability to Log. At least let's - // try to not crash. - if (!dumper_->Init() || !log_line_) - return false; - return dumper_->ThreadsSuspend() && dumper_->LateInit(); - } - - bool Dump() { - bool success; - LogLine("-----BEGIN BREAKPAD MICRODUMP-----"); - DumpProductInformation(); - DumpOSInformation(); - success = DumpCrashingThread(); - if (success) - success = DumpMappings(); - LogLine("-----END BREAKPAD MICRODUMP-----"); - dumper_->ThreadsResume(); - return success; - } - - private: - // Writes one line to the system log. - void LogLine(const char* msg) { -#if defined(__ANDROID__) - logger::writeToCrashLog(msg); -#else - logger::write(msg, my_strlen(msg)); - logger::write("\n", 1); -#endif - } - - // Stages the given string in the current line buffer. - void LogAppend(const char* str) { - my_strlcat(log_line_, str, kLineBufferSize); - } - - // As above (required to take precedence over template specialization below). - void LogAppend(char* str) { - LogAppend(const_cast(str)); - } - - // Stages the hex repr. of the given int type in the current line buffer. - template - void LogAppend(T value) { - // Make enough room to hex encode the largest int type + NUL. - static const char HEX[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'A', 'B', 'C', 'D', 'E', 'F'}; - char hexstr[sizeof(T) * 2 + 1]; - for (int i = sizeof(T) * 2 - 1; i >= 0; --i, value >>= 4) - hexstr[i] = HEX[static_cast(value) & 0x0F]; - hexstr[sizeof(T) * 2] = '\0'; - LogAppend(hexstr); - } - - // Stages the buffer content hex-encoded in the current line buffer. - void LogAppend(const void* buf, size_t length) { - const uint8_t* ptr = reinterpret_cast(buf); - for (size_t i = 0; i < length; ++i, ++ptr) - LogAppend(*ptr); - } - - // Writes out the current line buffer on the system log. - void LogCommitLine() { - LogLine(log_line_); - my_strlcpy(log_line_, "", kLineBufferSize); - } - - void DumpProductInformation() { - LogAppend("V "); - if (product_info_) { - LogAppend(product_info_); - } else { - LogAppend("UNKNOWN:0.0.0.0"); - } - LogCommitLine(); - } - - void DumpOSInformation() { - const uint8_t n_cpus = static_cast(sysconf(_SC_NPROCESSORS_CONF)); - -#if defined(__ANDROID__) - const char kOSId[] = "A"; -#else - const char kOSId[] = "L"; -#endif - -// Dump the runtime architecture. On multiarch devices it might not match the -// hw architecture (the one returned by uname()), for instance in the case of -// a 32-bit app running on a aarch64 device. -#if defined(__aarch64__) - const char kArch[] = "arm64"; -#elif defined(__ARMEL__) - const char kArch[] = "arm"; -#elif defined(__x86_64__) - const char kArch[] = "x86_64"; -#elif defined(__i386__) - const char kArch[] = "x86"; -#elif defined(__mips__) - const char kArch[] = "mips"; -#else -#error "This code has not been ported to your platform yet" -#endif - - LogAppend("O "); - LogAppend(kOSId); - LogAppend(" "); - LogAppend(kArch); - LogAppend(" "); - LogAppend(n_cpus); - LogAppend(" "); - - // Dump the HW architecture (e.g., armv7l, aarch64). - struct utsname uts; - const bool has_uts_info = (uname(&uts) == 0); - const char* hwArch = has_uts_info ? uts.machine : "unknown_hw_arch"; - LogAppend(hwArch); - LogAppend(" "); - - // If the client has attached a build fingerprint to the MinidumpDescriptor - // use that one. Otherwise try to get some basic info from uname(). - if (build_fingerprint_) { - LogAppend(build_fingerprint_); - } else if (has_uts_info) { - LogAppend(uts.release); - LogAppend(" "); - LogAppend(uts.version); - } else { - LogAppend("no build fingerprint available"); - } - LogCommitLine(); - } - - bool DumpThreadStack(uint32_t thread_id, - uintptr_t stack_pointer, - int max_stack_len, - uint8_t** stack_copy) { - *stack_copy = NULL; - const void* stack; - size_t stack_len; - - if (!dumper_->GetStackInfo(&stack, &stack_len, stack_pointer)) { - // The stack pointer might not be available. In this case we don't hard - // fail, just produce a (almost useless) microdump w/o a stack section. - return true; - } - - LogAppend("S 0 "); - LogAppend(stack_pointer); - LogAppend(" "); - LogAppend(reinterpret_cast(stack)); - LogAppend(" "); - LogAppend(stack_len); - LogCommitLine(); - - if (max_stack_len >= 0 && - stack_len > static_cast(max_stack_len)) { - stack_len = max_stack_len; - } - - *stack_copy = reinterpret_cast(Alloc(stack_len)); - dumper_->CopyFromProcess(*stack_copy, thread_id, stack, stack_len); - - // Dump the content of the stack, splicing it into chunks which size is - // compatible with the max logcat line size (see LOGGER_ENTRY_MAX_PAYLOAD). - const size_t STACK_DUMP_CHUNK_SIZE = 384; - for (size_t stack_off = 0; stack_off < stack_len; - stack_off += STACK_DUMP_CHUNK_SIZE) { - LogAppend("S "); - LogAppend(reinterpret_cast(stack) + stack_off); - LogAppend(" "); - LogAppend(*stack_copy + stack_off, - std::min(STACK_DUMP_CHUNK_SIZE, stack_len - stack_off)); - LogCommitLine(); - } - return true; - } - - // Write information about the crashing thread. - bool DumpCrashingThread() { - const unsigned num_threads = dumper_->threads().size(); - - for (unsigned i = 0; i < num_threads; ++i) { - MDRawThread thread; - my_memset(&thread, 0, sizeof(thread)); - thread.thread_id = dumper_->threads()[i]; - - // Dump only the crashing thread. - if (static_cast(thread.thread_id) != dumper_->crash_thread()) - continue; - - assert(ucontext_); - assert(!dumper_->IsPostMortem()); - - uint8_t* stack_copy; - const uintptr_t stack_ptr = UContextReader::GetStackPointer(ucontext_); - if (!DumpThreadStack(thread.thread_id, stack_ptr, -1, &stack_copy)) - return false; - - RawContextCPU cpu; - my_memset(&cpu, 0, sizeof(RawContextCPU)); -#if !defined(__ARM_EABI__) && !defined(__mips__) - UContextReader::FillCPUContext(&cpu, ucontext_, float_state_); -#else - UContextReader::FillCPUContext(&cpu, ucontext_); -#endif - DumpCPUState(&cpu); - } - return true; - } - - void DumpCPUState(RawContextCPU* cpu) { - LogAppend("C "); - LogAppend(cpu, sizeof(*cpu)); - LogCommitLine(); - } - - // If there is caller-provided information about this mapping - // in the mapping_list_ list, return true. Otherwise, return false. - bool HaveMappingInfo(const MappingInfo& mapping) { - for (MappingList::const_iterator iter = mapping_list_.begin(); - iter != mapping_list_.end(); - ++iter) { - // Ignore any mappings that are wholly contained within - // mappings in the mapping_info_ list. - if (mapping.start_addr >= iter->first.start_addr && - (mapping.start_addr + mapping.size) <= - (iter->first.start_addr + iter->first.size)) { - return true; - } - } - return false; - } - - // Dump information about the provided |mapping|. If |identifier| is non-NULL, - // use it instead of calculating a file ID from the mapping. - void DumpModule(const MappingInfo& mapping, - bool member, - unsigned int mapping_id, - const uint8_t* identifier) { - MDGUID module_identifier; - if (identifier) { - // GUID was provided by caller. - my_memcpy(&module_identifier, identifier, sizeof(MDGUID)); - } else { - dumper_->ElfFileIdentifierForMapping( - mapping, - member, - mapping_id, - reinterpret_cast(&module_identifier)); - } - - char file_name[NAME_MAX]; - char file_path[NAME_MAX]; - LinuxDumper::GetMappingEffectiveNameAndPath( - mapping, file_path, sizeof(file_path), file_name, sizeof(file_name)); - - LogAppend("M "); - LogAppend(static_cast(mapping.start_addr)); - LogAppend(" "); - LogAppend(mapping.offset); - LogAppend(" "); - LogAppend(mapping.size); - LogAppend(" "); - LogAppend(module_identifier.data1); - LogAppend(module_identifier.data2); - LogAppend(module_identifier.data3); - LogAppend(module_identifier.data4[0]); - LogAppend(module_identifier.data4[1]); - LogAppend(module_identifier.data4[2]); - LogAppend(module_identifier.data4[3]); - LogAppend(module_identifier.data4[4]); - LogAppend(module_identifier.data4[5]); - LogAppend(module_identifier.data4[6]); - LogAppend(module_identifier.data4[7]); - LogAppend("0 "); // Age is always 0 on Linux. - LogAppend(file_name); - LogCommitLine(); - } - - // Write information about the mappings in effect. - bool DumpMappings() { - // First write all the mappings from the dumper - for (unsigned i = 0; i < dumper_->mappings().size(); ++i) { - const MappingInfo& mapping = *dumper_->mappings()[i]; - if (mapping.name[0] == 0 || // only want modules with filenames. - !mapping.exec || // only want executable mappings. - mapping.size < 4096 || // too small to get a signature for. - HaveMappingInfo(mapping)) { - continue; - } - - DumpModule(mapping, true, i, NULL); - } - // Next write all the mappings provided by the caller - for (MappingList::const_iterator iter = mapping_list_.begin(); - iter != mapping_list_.end(); - ++iter) { - DumpModule(iter->first, false, 0, iter->second); - } - return true; - } - - void* Alloc(unsigned bytes) { return dumper_->allocator()->Alloc(bytes); } - - const struct ucontext* const ucontext_; -#if !defined(__ARM_EABI__) && !defined(__mips__) - const google_breakpad::fpstate_t* const float_state_; -#endif - LinuxDumper* dumper_; - const MappingList& mapping_list_; - const char* const build_fingerprint_; - const char* const product_info_; - char* log_line_; -}; -} // namespace - -namespace google_breakpad { - -bool WriteMicrodump(pid_t crashing_process, - const void* blob, - size_t blob_size, - const MappingList& mappings, - const char* build_fingerprint, - const char* product_info) { - LinuxPtraceDumper dumper(crashing_process); - const ExceptionHandler::CrashContext* context = NULL; - if (blob) { - if (blob_size != sizeof(ExceptionHandler::CrashContext)) - return false; - context = reinterpret_cast(blob); - dumper.set_crash_address( - reinterpret_cast(context->siginfo.si_addr)); - dumper.set_crash_signal(context->siginfo.si_signo); - dumper.set_crash_thread(context->tid); - } - MicrodumpWriter writer(context, mappings, build_fingerprint, product_info, - &dumper); - if (!writer.Init()) - return false; - return writer.Dump(); -} - -} // namespace google_breakpad diff --git a/TMessagesProj/jni/breakpad/client/linux/microdump_writer/microdump_writer.h b/TMessagesProj/jni/breakpad/client/linux/microdump_writer/microdump_writer.h deleted file mode 100644 index e21855836..000000000 --- a/TMessagesProj/jni/breakpad/client/linux/microdump_writer/microdump_writer.h +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) 2014, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef CLIENT_LINUX_MINIDUMP_WRITER_MICRODUMP_WRITER_H_ -#define CLIENT_LINUX_MINIDUMP_WRITER_MICRODUMP_WRITER_H_ - -#include -#include - -#include "client/linux/dump_writer_common/mapping_info.h" - -namespace google_breakpad { - -// Writes a microdump (a reduced dump containing only the state of the crashing -// thread) on the console (logcat on Android). These functions do not malloc nor -// use libc functions which may. Thus, it can be used in contexts where the -// state of the heap may be corrupt. -// Args: -// crashing_process: the pid of the crashing process. This must be trusted. -// blob: a blob of data from the crashing process. See exception_handler.h -// blob_size: the length of |blob| in bytes. -// mappings: a list of additional mappings provided by the application. -// build_fingerprint: a (optional) C string which determines the OS -// build fingerprint (e.g., aosp/occam/mako:5.1.1/LMY47W/1234:eng/dev-keys). -// product_info: a (optional) C string which determines the product name and -// version (e.g., WebView:42.0.2311.136). -// -// Returns true iff successful. -bool WriteMicrodump(pid_t crashing_process, - const void* blob, - size_t blob_size, - const MappingList& mappings, - const char* build_fingerprint, - const char* product_info); - -} // namespace google_breakpad - -#endif // CLIENT_LINUX_MINIDUMP_WRITER_MICRODUMP_WRITER_H_ diff --git a/TMessagesProj/jni/breakpad/client/linux/minidump_writer/cpu_set.h b/TMessagesProj/jni/breakpad/client/linux/minidump_writer/cpu_set.h deleted file mode 100644 index 1cca9aa5a..000000000 --- a/TMessagesProj/jni/breakpad/client/linux/minidump_writer/cpu_set.h +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) 2013, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef CLIENT_LINUX_MINIDUMP_WRITER_CPU_SET_H_ -#define CLIENT_LINUX_MINIDUMP_WRITER_CPU_SET_H_ - -#include -#include -#include - -#include "common/linux/linux_libc_support.h" -#include "third_party/lss/linux_syscall_support.h" - -namespace google_breakpad { - -// Helper class used to model a set of CPUs, as read from sysfs -// files like /sys/devices/system/cpu/present -// See See http://www.kernel.org/doc/Documentation/cputopology.txt -class CpuSet { -public: - // The maximum number of supported CPUs. - static const size_t kMaxCpus = 1024; - - CpuSet() { - my_memset(mask_, 0, sizeof(mask_)); - } - - // Parse a sysfs file to extract the corresponding CPU set. - bool ParseSysFile(int fd) { - char buffer[512]; - int ret = sys_read(fd, buffer, sizeof(buffer)-1); - if (ret < 0) - return false; - - buffer[ret] = '\0'; - - // Expected format: comma-separated list of items, where each - // item can be a decimal integer, or two decimal integers separated - // by a dash. - // E.g.: - // 0 - // 0,1,2,3 - // 0-3 - // 1,10-23 - const char* p = buffer; - const char* p_end = p + ret; - while (p < p_end) { - // Skip leading space, if any - while (p < p_end && my_isspace(*p)) - p++; - - // Find start and size of current item. - const char* item = p; - size_t item_len = static_cast(p_end - p); - const char* item_next = - static_cast(my_memchr(p, ',', item_len)); - if (item_next != NULL) { - p = item_next + 1; - item_len = static_cast(item_next - item); - } else { - p = p_end; - item_next = p_end; - } - - // Ignore trailing spaces. - while (item_next > item && my_isspace(item_next[-1])) - item_next--; - - // skip empty items. - if (item_next == item) - continue; - - // read first decimal value. - uintptr_t start = 0; - const char* next = my_read_decimal_ptr(&start, item); - uintptr_t end = start; - if (*next == '-') - my_read_decimal_ptr(&end, next+1); - - while (start <= end) - SetBit(start++); - } - return true; - } - - // Intersect this CPU set with another one. - void IntersectWith(const CpuSet& other) { - for (size_t nn = 0; nn < kMaskWordCount; ++nn) - mask_[nn] &= other.mask_[nn]; - } - - // Return the number of CPUs in this set. - int GetCount() { - int result = 0; - for (size_t nn = 0; nn < kMaskWordCount; ++nn) { - result += __builtin_popcount(mask_[nn]); - } - return result; - } - -private: - void SetBit(uintptr_t index) { - size_t nn = static_cast(index); - if (nn < kMaxCpus) - mask_[nn / kMaskWordBits] |= (1U << (nn % kMaskWordBits)); - } - - typedef uint32_t MaskWordType; - static const size_t kMaskWordBits = 8*sizeof(MaskWordType); - static const size_t kMaskWordCount = - (kMaxCpus + kMaskWordBits - 1) / kMaskWordBits; - - MaskWordType mask_[kMaskWordCount]; -}; - -} // namespace google_breakpad - -#endif // CLIENT_LINUX_MINIDUMP_WRITER_CPU_SET_H_ diff --git a/TMessagesProj/jni/breakpad/client/linux/minidump_writer/directory_reader.h b/TMessagesProj/jni/breakpad/client/linux/minidump_writer/directory_reader.h deleted file mode 100644 index a4bde1803..000000000 --- a/TMessagesProj/jni/breakpad/client/linux/minidump_writer/directory_reader.h +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) 2009, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_ -#define CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_ - -#include -#include -#include -#include -#include -#include - -#include "common/linux/linux_libc_support.h" -#include "third_party/lss/linux_syscall_support.h" - -namespace google_breakpad { - -// A class for enumerating a directory without using diropen/readdir or other -// functions which may allocate memory. -class DirectoryReader { - public: - DirectoryReader(int fd) - : fd_(fd), - buf_used_(0) { - } - - // Return the next entry from the directory - // name: (output) the NUL terminated entry name - // - // Returns true iff successful (false on EOF). - // - // After calling this, one must call |PopEntry| otherwise you'll get the same - // entry over and over. - bool GetNextEntry(const char** name) { - struct kernel_dirent* const dent = - reinterpret_cast(buf_); - - if (buf_used_ == 0) { - // need to read more entries. - const int n = sys_getdents(fd_, dent, sizeof(buf_)); - if (n < 0) { - return false; - } else if (n == 0) { - hit_eof_ = true; - } else { - buf_used_ += n; - } - } - - if (buf_used_ == 0 && hit_eof_) - return false; - - assert(buf_used_ > 0); - - *name = dent->d_name; - return true; - } - - void PopEntry() { - if (!buf_used_) - return; - - const struct kernel_dirent* const dent = - reinterpret_cast(buf_); - - buf_used_ -= dent->d_reclen; - my_memmove(buf_, buf_ + dent->d_reclen, buf_used_); - } - - private: - const int fd_; - bool hit_eof_; - unsigned buf_used_; - uint8_t buf_[sizeof(struct kernel_dirent) + NAME_MAX + 1]; -}; - -} // namespace google_breakpad - -#endif // CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_ diff --git a/TMessagesProj/jni/breakpad/client/linux/minidump_writer/line_reader.h b/TMessagesProj/jni/breakpad/client/linux/minidump_writer/line_reader.h deleted file mode 100644 index 779cfeb60..000000000 --- a/TMessagesProj/jni/breakpad/client/linux/minidump_writer/line_reader.h +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) 2009, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_ -#define CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_ - -#include -#include -#include - -#include "common/linux/linux_libc_support.h" -#include "third_party/lss/linux_syscall_support.h" - -namespace google_breakpad { - -// A class for reading a file, line by line, without using fopen/fgets or other -// functions which may allocate memory. -class LineReader { - public: - LineReader(int fd) - : fd_(fd), - hit_eof_(false), - buf_used_(0) { - } - - // The maximum length of a line. - static const size_t kMaxLineLen = 512; - - // Return the next line from the file. - // line: (output) a pointer to the start of the line. The line is NUL - // terminated. - // len: (output) the length of the line (not inc the NUL byte) - // - // Returns true iff successful (false on EOF). - // - // One must call |PopLine| after this function, otherwise you'll continue to - // get the same line over and over. - bool GetNextLine(const char **line, unsigned *len) { - for (;;) { - if (buf_used_ == 0 && hit_eof_) - return false; - - for (unsigned i = 0; i < buf_used_; ++i) { - if (buf_[i] == '\n' || buf_[i] == 0) { - buf_[i] = 0; - *len = i; - *line = buf_; - return true; - } - } - - if (buf_used_ == sizeof(buf_)) { - // we scanned the whole buffer and didn't find an end-of-line marker. - // This line is too long to process. - return false; - } - - // We didn't find any end-of-line terminators in the buffer. However, if - // this is the last line in the file it might not have one: - if (hit_eof_) { - assert(buf_used_); - // There's room for the NUL because of the buf_used_ == sizeof(buf_) - // check above. - buf_[buf_used_] = 0; - *len = buf_used_; - buf_used_ += 1; // since we appended the NUL. - *line = buf_; - return true; - } - - // Otherwise, we should pull in more data from the file - const ssize_t n = sys_read(fd_, buf_ + buf_used_, - sizeof(buf_) - buf_used_); - if (n < 0) { - return false; - } else if (n == 0) { - hit_eof_ = true; - } else { - buf_used_ += n; - } - - // At this point, we have either set the hit_eof_ flag, or we have more - // data to process... - } - } - - void PopLine(unsigned len) { - // len doesn't include the NUL byte at the end. - - assert(buf_used_ >= len + 1); - buf_used_ -= len + 1; - my_memmove(buf_, buf_ + len + 1, buf_used_); - } - - private: - const int fd_; - - bool hit_eof_; - unsigned buf_used_; - char buf_[kMaxLineLen]; -}; - -} // namespace google_breakpad - -#endif // CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_ diff --git a/TMessagesProj/jni/breakpad/client/linux/minidump_writer/linux_dumper.cc b/TMessagesProj/jni/breakpad/client/linux/minidump_writer/linux_dumper.cc deleted file mode 100644 index 4df68a847..000000000 --- a/TMessagesProj/jni/breakpad/client/linux/minidump_writer/linux_dumper.cc +++ /dev/null @@ -1,612 +0,0 @@ -// Copyright (c) 2010, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// linux_dumper.cc: Implement google_breakpad::LinuxDumper. -// See linux_dumper.h for details. - -// This code deals with the mechanics of getting information about a crashed -// process. Since this code may run in a compromised address space, the same -// rules apply as detailed at the top of minidump_writer.h: no libc calls and -// use the alternative allocator. - -#include "client/linux/minidump_writer/linux_dumper.h" - -#include -#include -#include -#include -#include -#include - -# if __WORDSIZE == 64 -# define UINTPTR_MAX (18446744073709551615UL) -# else -# define UINTPTR_MAX (4294967295U) -# endif - - -#include "client/linux/minidump_writer/line_reader.h" -#include "common/linux/elfutils.h" -#include "common/linux/file_id.h" -#include "common/linux/linux_libc_support.h" -#include "common/linux/memory_mapped_file.h" -#include "common/linux/safe_readlink.h" -#include "third_party/lss/linux_syscall_support.h" - -#if defined(__ANDROID__) - -// Android packed relocations definitions are not yet available from the -// NDK header files, so we have to provide them manually here. -#ifndef DT_LOOS -#define DT_LOOS 0x6000000d -#endif -#ifndef DT_ANDROID_REL -static const int DT_ANDROID_REL = DT_LOOS + 2; -#endif -#ifndef DT_ANDROID_RELA -static const int DT_ANDROID_RELA = DT_LOOS + 4; -#endif - -#endif // __ANDROID __ - -static const char kMappedFileUnsafePrefix[] = "/dev/"; -static const char kDeletedSuffix[] = " (deleted)"; -static const char kReservedFlags[] = " ---p"; - -inline static bool IsMappedFileOpenUnsafe( - const google_breakpad::MappingInfo& mapping) { - // It is unsafe to attempt to open a mapped file that lives under /dev, - // because the semantics of the open may be driver-specific so we'd risk - // hanging the crash dumper. And a file in /dev/ almost certainly has no - // ELF file identifier anyways. - return my_strncmp(mapping.name, - kMappedFileUnsafePrefix, - sizeof(kMappedFileUnsafePrefix) - 1) == 0; -} - -namespace google_breakpad { - -// All interesting auvx entry types are below AT_SYSINFO_EHDR -#define AT_MAX AT_SYSINFO_EHDR - -LinuxDumper::LinuxDumper(pid_t pid) - : pid_(pid), - crash_address_(0), - crash_signal_(0), - crash_thread_(pid), - threads_(&allocator_, 8), - mappings_(&allocator_), - auxv_(&allocator_, AT_MAX + 1) { - // The passed-in size to the constructor (above) is only a hint. - // Must call .resize() to do actual initialization of the elements. - auxv_.resize(AT_MAX + 1); -} - -LinuxDumper::~LinuxDumper() { -} - -bool LinuxDumper::Init() { - return ReadAuxv() && EnumerateThreads() && EnumerateMappings(); -} - -bool LinuxDumper::LateInit() { -#if defined(__ANDROID__) - LatePostprocessMappings(); -#endif - return true; -} - -bool -LinuxDumper::ElfFileIdentifierForMapping(const MappingInfo& mapping, - bool member, - unsigned int mapping_id, - uint8_t identifier[sizeof(MDGUID)]) { - assert(!member || mapping_id < mappings_.size()); - my_memset(identifier, 0, sizeof(MDGUID)); - if (IsMappedFileOpenUnsafe(mapping)) - return false; - - // Special-case linux-gate because it's not a real file. - if (my_strcmp(mapping.name, kLinuxGateLibraryName) == 0) { - void* linux_gate = NULL; - if (pid_ == sys_getpid()) { - linux_gate = reinterpret_cast(mapping.start_addr); - } else { - linux_gate = allocator_.Alloc(mapping.size); - CopyFromProcess(linux_gate, pid_, - reinterpret_cast(mapping.start_addr), - mapping.size); - } - return FileID::ElfFileIdentifierFromMappedFile(linux_gate, identifier); - } - - char filename[NAME_MAX]; - size_t filename_len = my_strlen(mapping.name); - if (filename_len >= NAME_MAX) { - assert(false); - return false; - } - my_memcpy(filename, mapping.name, filename_len); - filename[filename_len] = '\0'; - bool filename_modified = HandleDeletedFileInMapping(filename); - - MemoryMappedFile mapped_file(filename, mapping.offset); - if (!mapped_file.data() || mapped_file.size() < SELFMAG) - return false; - - bool success = - FileID::ElfFileIdentifierFromMappedFile(mapped_file.data(), identifier); - if (success && member && filename_modified) { - mappings_[mapping_id]->name[filename_len - - sizeof(kDeletedSuffix) + 1] = '\0'; - } - - return success; -} - -namespace { -bool ElfFileSoNameFromMappedFile( - const void* elf_base, char* soname, size_t soname_size) { - if (!IsValidElf(elf_base)) { - // Not ELF - return false; - } - - const void* segment_start; - size_t segment_size; - int elf_class; - if (!FindElfSection(elf_base, ".dynamic", SHT_DYNAMIC, - &segment_start, &segment_size, &elf_class)) { - // No dynamic section - return false; - } - - const void* dynstr_start; - size_t dynstr_size; - if (!FindElfSection(elf_base, ".dynstr", SHT_STRTAB, - &dynstr_start, &dynstr_size, &elf_class)) { - // No dynstr section - return false; - } - - const ElfW(Dyn)* dynamic = static_cast(segment_start); - size_t dcount = segment_size / sizeof(ElfW(Dyn)); - for (const ElfW(Dyn)* dyn = dynamic; dyn < dynamic + dcount; ++dyn) { - if (dyn->d_tag == DT_SONAME) { - const char* dynstr = static_cast(dynstr_start); - if (dyn->d_un.d_val >= dynstr_size) { - // Beyond the end of the dynstr section - return false; - } - const char* str = dynstr + dyn->d_un.d_val; - const size_t maxsize = dynstr_size - dyn->d_un.d_val; - my_strlcpy(soname, str, maxsize < soname_size ? maxsize : soname_size); - return true; - } - } - - // Did not find SONAME - return false; -} - -// Find the shared object name (SONAME) by examining the ELF information -// for |mapping|. If the SONAME is found copy it into the passed buffer -// |soname| and return true. The size of the buffer is |soname_size|. -// The SONAME will be truncated if it is too long to fit in the buffer. -bool ElfFileSoName( - const MappingInfo& mapping, char* soname, size_t soname_size) { - if (IsMappedFileOpenUnsafe(mapping)) { - // Not safe - return false; - } - - char filename[NAME_MAX]; - size_t filename_len = my_strlen(mapping.name); - if (filename_len >= NAME_MAX) { - assert(false); - // name too long - return false; - } - - my_memcpy(filename, mapping.name, filename_len); - filename[filename_len] = '\0'; - - MemoryMappedFile mapped_file(filename, mapping.offset); - if (!mapped_file.data() || mapped_file.size() < SELFMAG) { - // mmap failed - return false; - } - - return ElfFileSoNameFromMappedFile(mapped_file.data(), soname, soname_size); -} - -} // namespace - - -// static -void LinuxDumper::GetMappingEffectiveNameAndPath(const MappingInfo& mapping, - char* file_path, - size_t file_path_size, - char* file_name, - size_t file_name_size) { - my_strlcpy(file_path, mapping.name, file_path_size); - - // If an executable is mapped from a non-zero offset, this is likely because - // the executable was loaded directly from inside an archive file (e.g., an - // apk on Android). We try to find the name of the shared object (SONAME) by - // looking in the file for ELF sections. - bool mapped_from_archive = false; - if (mapping.exec && mapping.offset != 0) - mapped_from_archive = ElfFileSoName(mapping, file_name, file_name_size); - - if (mapped_from_archive) { - // Some tools (e.g., stackwalk) extract the basename from the pathname. In - // this case, we append the file_name to the mapped archive path as follows: - // file_name := libname.so - // file_path := /path/to/ARCHIVE.APK/libname.so - if (my_strlen(file_path) + 1 + my_strlen(file_name) < file_path_size) { - my_strlcat(file_path, "/", file_path_size); - my_strlcat(file_path, file_name, file_path_size); - } - } else { - // Common case: - // file_path := /path/to/libname.so - // file_name := libname.so - const char* basename = my_strrchr(file_path, '/'); - basename = basename == NULL ? file_path : (basename + 1); - my_strlcpy(file_name, basename, file_name_size); - } -} - -bool LinuxDumper::ReadAuxv() { - char auxv_path[NAME_MAX]; - if (!BuildProcPath(auxv_path, pid_, "auxv")) { - return false; - } - - int fd = sys_open(auxv_path, O_RDONLY, 0); - if (fd < 0) { - return false; - } - - elf_aux_entry one_aux_entry; - bool res = false; - while (sys_read(fd, - &one_aux_entry, - sizeof(elf_aux_entry)) == sizeof(elf_aux_entry) && - one_aux_entry.a_type != AT_NULL) { - if (one_aux_entry.a_type <= AT_MAX) { - auxv_[one_aux_entry.a_type] = one_aux_entry.a_un.a_val; - res = true; - } - } - sys_close(fd); - return res; -} - -bool LinuxDumper::EnumerateMappings() { - char maps_path[NAME_MAX]; - if (!BuildProcPath(maps_path, pid_, "maps")) - return false; - - // linux_gate_loc is the beginning of the kernel's mapping of - // linux-gate.so in the process. It doesn't actually show up in the - // maps list as a filename, but it can be found using the AT_SYSINFO_EHDR - // aux vector entry, which gives the information necessary to special - // case its entry when creating the list of mappings. - // See http://www.trilithium.com/johan/2005/08/linux-gate/ for more - // information. - const void* linux_gate_loc = - reinterpret_cast(auxv_[AT_SYSINFO_EHDR]); - // Although the initial executable is usually the first mapping, it's not - // guaranteed (see http://crosbug.com/25355); therefore, try to use the - // actual entry point to find the mapping. - const void* entry_point_loc = reinterpret_cast(auxv_[AT_ENTRY]); - - const int fd = sys_open(maps_path, O_RDONLY, 0); - if (fd < 0) - return false; - LineReader* const line_reader = new(allocator_) LineReader(fd); - - const char* line; - unsigned line_len; - while (line_reader->GetNextLine(&line, &line_len)) { - uintptr_t start_addr, end_addr, offset; - - const char* i1 = my_read_hex_ptr(&start_addr, line); - if (*i1 == '-') { - const char* i2 = my_read_hex_ptr(&end_addr, i1 + 1); - if (*i2 == ' ') { - bool exec = (*(i2 + 3) == 'x'); - const char* i3 = my_read_hex_ptr(&offset, i2 + 6 /* skip ' rwxp ' */); - if (*i3 == ' ') { - const char* name = NULL; - // Only copy name if the name is a valid path name, or if - // it's the VDSO image. - if (((name = my_strchr(line, '/')) == NULL) && - linux_gate_loc && - reinterpret_cast(start_addr) == linux_gate_loc) { - name = kLinuxGateLibraryName; - offset = 0; - } - // Merge adjacent mappings with the same name into one module, - // assuming they're a single library mapped by the dynamic linker - if (name && !mappings_.empty()) { - MappingInfo* module = mappings_.back(); - if ((start_addr == module->start_addr + module->size) && - (my_strlen(name) == my_strlen(module->name)) && - (my_strncmp(name, module->name, my_strlen(name)) == 0)) { - module->size = end_addr - module->start_addr; - line_reader->PopLine(line_len); - continue; - } - } - // Also merge mappings that result from address ranges that the - // linker reserved but which a loaded library did not use. These - // appear as an anonymous private mapping with no access flags set - // and which directly follow an executable mapping. - if (!name && !mappings_.empty()) { - MappingInfo* module = mappings_.back(); - if ((start_addr == module->start_addr + module->size) && - module->exec && - module->name[0] == '/' && - offset == 0 && my_strncmp(i2, - kReservedFlags, - sizeof(kReservedFlags) - 1) == 0) { - module->size = end_addr - module->start_addr; - line_reader->PopLine(line_len); - continue; - } - } - MappingInfo* const module = new(allocator_) MappingInfo; - my_memset(module, 0, sizeof(MappingInfo)); - module->start_addr = start_addr; - module->size = end_addr - start_addr; - module->offset = offset; - module->exec = exec; - if (name != NULL) { - const unsigned l = my_strlen(name); - if (l < sizeof(module->name)) - my_memcpy(module->name, name, l); - } - // If this is the entry-point mapping, and it's not already the - // first one, then we need to make it be first. This is because - // the minidump format assumes the first module is the one that - // corresponds to the main executable (as codified in - // processor/minidump.cc:MinidumpModuleList::GetMainModule()). - if (entry_point_loc && - (entry_point_loc >= - reinterpret_cast(module->start_addr)) && - (entry_point_loc < - reinterpret_cast(module->start_addr+module->size)) && - !mappings_.empty()) { - // push the module onto the front of the list. - mappings_.resize(mappings_.size() + 1); - for (size_t idx = mappings_.size() - 1; idx > 0; idx--) - mappings_[idx] = mappings_[idx - 1]; - mappings_[0] = module; - } else { - mappings_.push_back(module); - } - } - } - } - line_reader->PopLine(line_len); - } - - sys_close(fd); - - return !mappings_.empty(); -} - -#if defined(__ANDROID__) - -bool LinuxDumper::GetLoadedElfHeader(uintptr_t start_addr, ElfW(Ehdr)* ehdr) { - CopyFromProcess(ehdr, pid_, - reinterpret_cast(start_addr), - sizeof(*ehdr)); - return my_memcmp(&ehdr->e_ident, ELFMAG, SELFMAG) == 0; -} - -void LinuxDumper::ParseLoadedElfProgramHeaders(ElfW(Ehdr)* ehdr, - uintptr_t start_addr, - uintptr_t* min_vaddr_ptr, - uintptr_t* dyn_vaddr_ptr, - size_t* dyn_count_ptr) { - uintptr_t phdr_addr = start_addr + ehdr->e_phoff; - - const uintptr_t max_addr = UINTPTR_MAX; - uintptr_t min_vaddr = max_addr; - uintptr_t dyn_vaddr = 0; - size_t dyn_count = 0; - - for (size_t i = 0; i < ehdr->e_phnum; ++i) { - ElfW(Phdr) phdr; - CopyFromProcess(&phdr, pid_, - reinterpret_cast(phdr_addr), - sizeof(phdr)); - if (phdr.p_type == PT_LOAD && phdr.p_vaddr < min_vaddr) { - min_vaddr = phdr.p_vaddr; - } - if (phdr.p_type == PT_DYNAMIC) { - dyn_vaddr = phdr.p_vaddr; - dyn_count = phdr.p_memsz / sizeof(ElfW(Dyn)); - } - phdr_addr += sizeof(phdr); - } - - *min_vaddr_ptr = min_vaddr; - *dyn_vaddr_ptr = dyn_vaddr; - *dyn_count_ptr = dyn_count; -} - -bool LinuxDumper::HasAndroidPackedRelocations(uintptr_t load_bias, - uintptr_t dyn_vaddr, - size_t dyn_count) { - uintptr_t dyn_addr = load_bias + dyn_vaddr; - for (size_t i = 0; i < dyn_count; ++i) { - ElfW(Dyn) dyn; - CopyFromProcess(&dyn, pid_, - reinterpret_cast(dyn_addr), - sizeof(dyn)); - if (dyn.d_tag == DT_ANDROID_REL || dyn.d_tag == DT_ANDROID_RELA) { - return true; - } - dyn_addr += sizeof(dyn); - } - return false; -} - -uintptr_t LinuxDumper::GetEffectiveLoadBias(ElfW(Ehdr)* ehdr, - uintptr_t start_addr) { - uintptr_t min_vaddr = 0; - uintptr_t dyn_vaddr = 0; - size_t dyn_count = 0; - ParseLoadedElfProgramHeaders(ehdr, start_addr, - &min_vaddr, &dyn_vaddr, &dyn_count); - // If |min_vaddr| is non-zero and we find Android packed relocation tags, - // return the effective load bias. - if (min_vaddr != 0) { - const uintptr_t load_bias = start_addr - min_vaddr; - if (HasAndroidPackedRelocations(load_bias, dyn_vaddr, dyn_count)) { - return load_bias; - } - } - // Either |min_vaddr| is zero, or it is non-zero but we did not find the - // expected Android packed relocations tags. - return start_addr; -} - -void LinuxDumper::LatePostprocessMappings() { - for (size_t i = 0; i < mappings_.size(); ++i) { - // Only consider exec mappings that indicate a file path was mapped, and - // where the ELF header indicates a mapped shared library. - MappingInfo* mapping = mappings_[i]; - if (!(mapping->exec && mapping->name[0] == '/')) { - continue; - } - ElfW(Ehdr) ehdr; - if (!GetLoadedElfHeader(mapping->start_addr, &ehdr)) { - continue; - } - if (ehdr.e_type == ET_DYN) { - // Compute the effective load bias for this mapped library, and update - // the mapping to hold that rather than |start_addr|, at the same time - // adjusting |size| to account for the change in |start_addr|. Where - // the library does not contain Android packed relocations, - // GetEffectiveLoadBias() returns |start_addr| and the mapping entry - // is not changed. - const uintptr_t load_bias = GetEffectiveLoadBias(&ehdr, - mapping->start_addr); - mapping->size += mapping->start_addr - load_bias; - mapping->start_addr = load_bias; - } - } -} - -#endif // __ANDROID__ - -// Get information about the stack, given the stack pointer. We don't try to -// walk the stack since we might not have all the information needed to do -// unwind. So we just grab, up to, 32k of stack. -bool LinuxDumper::GetStackInfo(const void** stack, size_t* stack_len, - uintptr_t int_stack_pointer) { - // Move the stack pointer to the bottom of the page that it's in. - const uintptr_t page_size = getpagesize(); - - uint8_t* const stack_pointer = - reinterpret_cast(int_stack_pointer & ~(page_size - 1)); - - // The number of bytes of stack which we try to capture. - static const ptrdiff_t kStackToCapture = 32 * 1024; - - const MappingInfo* mapping = FindMapping(stack_pointer); - if (!mapping) - return false; - const ptrdiff_t offset = stack_pointer - - reinterpret_cast(mapping->start_addr); - const ptrdiff_t distance_to_end = - static_cast(mapping->size) - offset; - *stack_len = distance_to_end > kStackToCapture ? - kStackToCapture : distance_to_end; - *stack = stack_pointer; - return true; -} - -// Find the mapping which the given memory address falls in. -const MappingInfo* LinuxDumper::FindMapping(const void* address) const { - const uintptr_t addr = (uintptr_t) address; - - for (size_t i = 0; i < mappings_.size(); ++i) { - const uintptr_t start = static_cast(mappings_[i]->start_addr); - if (addr >= start && addr - start < mappings_[i]->size) - return mappings_[i]; - } - - return NULL; -} - -bool LinuxDumper::HandleDeletedFileInMapping(char* path) const { - static const size_t kDeletedSuffixLen = sizeof(kDeletedSuffix) - 1; - - // Check for ' (deleted)' in |path|. - // |path| has to be at least as long as "/x (deleted)". - const size_t path_len = my_strlen(path); - if (path_len < kDeletedSuffixLen + 2) - return false; - if (my_strncmp(path + path_len - kDeletedSuffixLen, kDeletedSuffix, - kDeletedSuffixLen) != 0) { - return false; - } - - // Check |path| against the /proc/pid/exe 'symlink'. - char exe_link[NAME_MAX]; - char new_path[NAME_MAX]; - if (!BuildProcPath(exe_link, pid_, "exe")) - return false; - if (!SafeReadLink(exe_link, new_path)) - return false; - if (my_strcmp(path, new_path) != 0) - return false; - - // Check to see if someone actually named their executable 'foo (deleted)'. - struct kernel_stat exe_stat; - struct kernel_stat new_path_stat; - if (sys_stat(exe_link, &exe_stat) == 0 && - sys_stat(new_path, &new_path_stat) == 0 && - exe_stat.st_dev == new_path_stat.st_dev && - exe_stat.st_ino == new_path_stat.st_ino) { - return false; - } - - my_memcpy(path, exe_link, NAME_MAX); - return true; -} - -} // namespace google_breakpad diff --git a/TMessagesProj/jni/breakpad/client/linux/minidump_writer/linux_dumper.h b/TMessagesProj/jni/breakpad/client/linux/minidump_writer/linux_dumper.h deleted file mode 100644 index 6a3a100f3..000000000 --- a/TMessagesProj/jni/breakpad/client/linux/minidump_writer/linux_dumper.h +++ /dev/null @@ -1,254 +0,0 @@ -// Copyright (c) 2010, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// linux_dumper.h: Define the google_breakpad::LinuxDumper class, which -// is a base class for extracting information of a crashed process. It -// was originally a complete implementation using the ptrace API, but -// has been refactored to allow derived implementations supporting both -// ptrace and core dump. A portion of the original implementation is now -// in google_breakpad::LinuxPtraceDumper (see linux_ptrace_dumper.h for -// details). - -#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_ -#define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_ - -#include -#if defined(__ANDROID__) -#include -#endif -#include -#include -#include -#include - -#include "client/linux/dump_writer_common/mapping_info.h" -#include "client/linux/dump_writer_common/thread_info.h" -#include "common/memory.h" -#include "google_breakpad/common/minidump_format.h" - -namespace google_breakpad { - -// Typedef for our parsing of the auxv variables in /proc/pid/auxv. -#if defined(__i386) || defined(__ARM_EABI__) || \ - (defined(__mips__) && _MIPS_SIM == _ABIO32) -typedef Elf32_auxv_t elf_aux_entry; -#elif defined(__x86_64) || defined(__aarch64__) || \ - (defined(__mips__) && _MIPS_SIM != _ABIO32) -typedef Elf64_auxv_t elf_aux_entry; -#endif - -typedef __typeof__(((elf_aux_entry*) 0)->a_un.a_val) elf_aux_val_t; - -// When we find the VDSO mapping in the process's address space, this -// is the name we use for it when writing it to the minidump. -// This should always be less than NAME_MAX! -const char kLinuxGateLibraryName[] = "linux-gate.so"; - -class LinuxDumper { - public: - explicit LinuxDumper(pid_t pid); - - virtual ~LinuxDumper(); - - // Parse the data for |threads| and |mappings|. - virtual bool Init(); - - // Take any actions that could not be taken in Init(). LateInit() is - // called after all other caller's initialization is complete, and in - // particular after it has called ThreadsSuspend(), so that ptrace is - // available. - virtual bool LateInit(); - - // Return true if the dumper performs a post-mortem dump. - virtual bool IsPostMortem() const = 0; - - // Suspend/resume all threads in the given process. - virtual bool ThreadsSuspend() = 0; - virtual bool ThreadsResume() = 0; - - // Read information about the |index|-th thread of |threads_|. - // Returns true on success. One must have called |ThreadsSuspend| first. - virtual bool GetThreadInfoByIndex(size_t index, ThreadInfo* info) = 0; - - // These are only valid after a call to |Init|. - const wasteful_vector &threads() { return threads_; } - const wasteful_vector &mappings() { return mappings_; } - const MappingInfo* FindMapping(const void* address) const; - const wasteful_vector& auxv() { return auxv_; } - - // Find a block of memory to take as the stack given the top of stack pointer. - // stack: (output) the lowest address in the memory area - // stack_len: (output) the length of the memory area - // stack_top: the current top of the stack - bool GetStackInfo(const void** stack, size_t* stack_len, uintptr_t stack_top); - - PageAllocator* allocator() { return &allocator_; } - - // Copy content of |length| bytes from a given process |child|, - // starting from |src|, into |dest|. Returns true on success. - virtual bool CopyFromProcess(void* dest, pid_t child, const void* src, - size_t length) = 0; - - // Builds a proc path for a certain pid for a node (/proc//). - // |path| is a character array of at least NAME_MAX bytes to return the - // result.|node| is the final node without any slashes. Returns true on - // success. - virtual bool BuildProcPath(char* path, pid_t pid, const char* node) const = 0; - - // Generate a File ID from the .text section of a mapped entry. - // If not a member, mapping_id is ignored. This method can also manipulate the - // |mapping|.name to truncate "(deleted)" from the file name if necessary. - bool ElfFileIdentifierForMapping(const MappingInfo& mapping, - bool member, - unsigned int mapping_id, - uint8_t identifier[sizeof(MDGUID)]); - - uintptr_t crash_address() const { return crash_address_; } - void set_crash_address(uintptr_t crash_address) { - crash_address_ = crash_address; - } - - int crash_signal() const { return crash_signal_; } - void set_crash_signal(int crash_signal) { crash_signal_ = crash_signal; } - - pid_t crash_thread() const { return crash_thread_; } - void set_crash_thread(pid_t crash_thread) { crash_thread_ = crash_thread; } - - // Extracts the effective path and file name of from |mapping|. In most cases - // the effective name/path are just the mapping's path and basename. In some - // other cases, however, a library can be mapped from an archive (e.g., when - // loading .so libs from an apk on Android) and this method is able to - // reconstruct the original file name. - static void GetMappingEffectiveNameAndPath(const MappingInfo& mapping, - char* file_path, - size_t file_path_size, - char* file_name, - size_t file_name_size); - - protected: - bool ReadAuxv(); - - virtual bool EnumerateMappings(); - - virtual bool EnumerateThreads() = 0; - - // For the case where a running program has been deleted, it'll show up in - // /proc/pid/maps as "/path/to/program (deleted)". If this is the case, then - // see if '/path/to/program (deleted)' matches /proc/pid/exe and return - // /proc/pid/exe in |path| so ELF identifier generation works correctly. This - // also checks to see if '/path/to/program (deleted)' exists, so it does not - // get fooled by a poorly named binary. - // For programs that don't end with ' (deleted)', this is a no-op. - // This assumes |path| is a buffer with length NAME_MAX. - // Returns true if |path| is modified. - bool HandleDeletedFileInMapping(char* path) const; - - // ID of the crashed process. - const pid_t pid_; - - // Virtual address at which the process crashed. - uintptr_t crash_address_; - - // Signal that terminated the crashed process. - int crash_signal_; - - // ID of the crashed thread. - pid_t crash_thread_; - - mutable PageAllocator allocator_; - - // IDs of all the threads. - wasteful_vector threads_; - - // Info from /proc//maps. - wasteful_vector mappings_; - - // Info from /proc//auxv - wasteful_vector auxv_; - -#if defined(__ANDROID__) - private: - // Android M and later support packed ELF relocations in shared libraries. - // Packing relocations changes the vaddr of the LOAD segments, such that - // the effective load bias is no longer the same as the start address of - // the memory mapping containing the executable parts of the library. The - // packing is applied to the stripped library run on the target, but not to - // any other library, and in particular not to the library used to generate - // breakpad symbols. As a result, we need to adjust the |start_addr| for - // any mapping that results from a shared library that contains Android - // packed relocations, so that it properly represents the effective library - // load bias. The following functions support this adjustment. - - // Check that a given mapping at |start_addr| is for an ELF shared library. - // If it is, place the ELF header in |ehdr| and return true. - // The first LOAD segment in an ELF shared library has offset zero, so the - // ELF file header is at the start of this map entry, and in already mapped - // memory. - bool GetLoadedElfHeader(uintptr_t start_addr, ElfW(Ehdr)* ehdr); - - // For the ELF file mapped at |start_addr|, iterate ELF program headers to - // find the min vaddr of all program header LOAD segments, the vaddr for - // the DYNAMIC segment, and a count of DYNAMIC entries. Return values in - // |min_vaddr_ptr|, |dyn_vaddr_ptr|, and |dyn_count_ptr|. - // The program header table is also in already mapped memory. - void ParseLoadedElfProgramHeaders(ElfW(Ehdr)* ehdr, - uintptr_t start_addr, - uintptr_t* min_vaddr_ptr, - uintptr_t* dyn_vaddr_ptr, - size_t* dyn_count_ptr); - - // Search the DYNAMIC tags for the ELF file with the given |load_bias|, and - // return true if the tags indicate that the file contains Android packed - // relocations. Dynamic tags are found at |dyn_vaddr| past the |load_bias|. - bool HasAndroidPackedRelocations(uintptr_t load_bias, - uintptr_t dyn_vaddr, - size_t dyn_count); - - // If the ELF file mapped at |start_addr| contained Android packed - // relocations, return the load bias that the system linker (or Chromium - // crazy linker) will have used. If the file did not contain Android - // packed relocations, returns |start_addr|, indicating that no adjustment - // is necessary. - // The effective load bias is |start_addr| adjusted downwards by the - // min vaddr in the library LOAD segments. - uintptr_t GetEffectiveLoadBias(ElfW(Ehdr)* ehdr, uintptr_t start_addr); - - // Called from LateInit(). Iterates |mappings_| and rewrites the |start_addr| - // field of any that represent ELF shared libraries with Android packed - // relocations, so that |start_addr| is the load bias that the system linker - // (or Chromium crazy linker) used. This value matches the addresses produced - // when the non-relocation-packed library is used for breakpad symbol - // generation. - void LatePostprocessMappings(); -#endif // __ANDROID__ -}; - -} // namespace google_breakpad - -#endif // CLIENT_LINUX_HANDLER_LINUX_DUMPER_H_ diff --git a/TMessagesProj/jni/breakpad/client/linux/minidump_writer/linux_ptrace_dumper.cc b/TMessagesProj/jni/breakpad/client/linux/minidump_writer/linux_ptrace_dumper.cc deleted file mode 100644 index c35e0e958..000000000 --- a/TMessagesProj/jni/breakpad/client/linux/minidump_writer/linux_ptrace_dumper.cc +++ /dev/null @@ -1,355 +0,0 @@ -// Copyright (c) 2012, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// linux_ptrace_dumper.cc: Implement google_breakpad::LinuxPtraceDumper. -// See linux_ptrace_dumper.h for detals. -// This class was originally splitted from google_breakpad::LinuxDumper. - -// This code deals with the mechanics of getting information about a crashed -// process. Since this code may run in a compromised address space, the same -// rules apply as detailed at the top of minidump_writer.h: no libc calls and -// use the alternative allocator. - -#include "client/linux/minidump_writer/linux_ptrace_dumper.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(__i386) -#include -#endif - -#include "client/linux/minidump_writer/directory_reader.h" -#include "client/linux/minidump_writer/line_reader.h" -#include "common/linux/linux_libc_support.h" -#include "third_party/lss/linux_syscall_support.h" - -// Suspends a thread by attaching to it. -static bool SuspendThread(pid_t pid) { - // This may fail if the thread has just died or debugged. - errno = 0; - if (sys_ptrace(PTRACE_ATTACH, pid, NULL, NULL) != 0 && - errno != 0) { - return false; - } - while (sys_waitpid(pid, NULL, __WALL) < 0) { - if (errno != EINTR) { - sys_ptrace(PTRACE_DETACH, pid, NULL, NULL); - return false; - } - } -#if defined(__i386) || defined(__x86_64) - // On x86, the stack pointer is NULL or -1, when executing trusted code in - // the seccomp sandbox. Not only does this cause difficulties down the line - // when trying to dump the thread's stack, it also results in the minidumps - // containing information about the trusted threads. This information is - // generally completely meaningless and just pollutes the minidumps. - // We thus test the stack pointer and exclude any threads that are part of - // the seccomp sandbox's trusted code. - user_regs_struct regs; - if (sys_ptrace(PTRACE_GETREGS, pid, NULL, ®s) == -1 || -#if defined(__i386) - !regs.esp -#elif defined(__x86_64) - !regs.rsp -#endif - ) { - sys_ptrace(PTRACE_DETACH, pid, NULL, NULL); - return false; - } -#endif - return true; -} - -// Resumes a thread by detaching from it. -static bool ResumeThread(pid_t pid) { - return sys_ptrace(PTRACE_DETACH, pid, NULL, NULL) >= 0; -} - -namespace google_breakpad { - -LinuxPtraceDumper::LinuxPtraceDumper(pid_t pid) - : LinuxDumper(pid), - threads_suspended_(false) { -} - -bool LinuxPtraceDumper::BuildProcPath(char* path, pid_t pid, - const char* node) const { - if (!path || !node || pid <= 0) - return false; - - size_t node_len = my_strlen(node); - if (node_len == 0) - return false; - - const unsigned pid_len = my_uint_len(pid); - const size_t total_length = 6 + pid_len + 1 + node_len; - if (total_length >= NAME_MAX) - return false; - - my_memcpy(path, "/proc/", 6); - my_uitos(path + 6, pid, pid_len); - path[6 + pid_len] = '/'; - my_memcpy(path + 6 + pid_len + 1, node, node_len); - path[total_length] = '\0'; - return true; -} - -bool LinuxPtraceDumper::CopyFromProcess(void* dest, pid_t child, - const void* src, size_t length) { - unsigned long tmp = 55; - size_t done = 0; - static const size_t word_size = sizeof(tmp); - uint8_t* const local = (uint8_t*) dest; - uint8_t* const remote = (uint8_t*) src; - - while (done < length) { - const size_t l = (length - done > word_size) ? word_size : (length - done); - if (sys_ptrace(PTRACE_PEEKDATA, child, remote + done, &tmp) == -1) { - tmp = 0; - } - my_memcpy(local + done, &tmp, l); - done += l; - } - return true; -} - -// Read thread info from /proc/$pid/status. -// Fill out the |tgid|, |ppid| and |pid| members of |info|. If unavailable, -// these members are set to -1. Returns true iff all three members are -// available. -bool LinuxPtraceDumper::GetThreadInfoByIndex(size_t index, ThreadInfo* info) { - if (index >= threads_.size()) - return false; - - pid_t tid = threads_[index]; - - assert(info != NULL); - char status_path[NAME_MAX]; - if (!BuildProcPath(status_path, tid, "status")) - return false; - - const int fd = sys_open(status_path, O_RDONLY, 0); - if (fd < 0) - return false; - - LineReader* const line_reader = new(allocator_) LineReader(fd); - const char* line; - unsigned line_len; - - info->ppid = info->tgid = -1; - - while (line_reader->GetNextLine(&line, &line_len)) { - if (my_strncmp("Tgid:\t", line, 6) == 0) { - my_strtoui(&info->tgid, line + 6); - } else if (my_strncmp("PPid:\t", line, 6) == 0) { - my_strtoui(&info->ppid, line + 6); - } - - line_reader->PopLine(line_len); - } - sys_close(fd); - - if (info->ppid == -1 || info->tgid == -1) - return false; - -#ifdef PTRACE_GETREGSET - struct iovec io; - info->GetGeneralPurposeRegisters(&io.iov_base, &io.iov_len); - if (sys_ptrace(PTRACE_GETREGSET, tid, (void*)NT_PRSTATUS, (void*)&io) == -1) { - return false; - } - - info->GetFloatingPointRegisters(&io.iov_base, &io.iov_len); - if (sys_ptrace(PTRACE_GETREGSET, tid, (void*)NT_FPREGSET, (void*)&io) == -1) { - return false; - } -#else // PTRACE_GETREGSET - void* gp_addr; - info->GetGeneralPurposeRegisters(&gp_addr, NULL); - if (sys_ptrace(PTRACE_GETREGS, tid, NULL, gp_addr) == -1) { - return false; - } - -#if !(defined(__ANDROID__) && defined(__ARM_EABI__)) - // When running an arm build on an arm64 device, attempting to get the - // floating point registers fails. On Android, the floating point registers - // aren't written to the cpu context anyway, so just don't get them here. - // See http://crbug.com/508324 - void* fp_addr; - info->GetFloatingPointRegisters(&fp_addr, NULL); - if (sys_ptrace(PTRACE_GETFPREGS, tid, NULL, fp_addr) == -1) { - return false; - } -#endif -#endif // PTRACE_GETREGSET - -#if defined(__i386) -#if !defined(bit_FXSAVE) // e.g. Clang -#define bit_FXSAVE bit_FXSR -#endif - // Detect if the CPU supports the FXSAVE/FXRSTOR instructions - int eax, ebx, ecx, edx; - __cpuid(1, eax, ebx, ecx, edx); - if (edx & bit_FXSAVE) { - if (sys_ptrace(PTRACE_GETFPXREGS, tid, NULL, &info->fpxregs) == -1) { - return false; - } - } else { - memset(&info->fpxregs, 0, sizeof(info->fpxregs)); - } -#endif // defined(__i386) - -#if defined(__i386) || defined(__x86_64) - for (unsigned i = 0; i < ThreadInfo::kNumDebugRegisters; ++i) { - if (sys_ptrace( - PTRACE_PEEKUSER, tid, - reinterpret_cast (offsetof(struct user, - u_debugreg[0]) + i * - sizeof(debugreg_t)), - &info->dregs[i]) == -1) { - return false; - } - } -#endif - -#if defined(__mips__) - sys_ptrace(PTRACE_PEEKUSER, tid, - reinterpret_cast(DSP_BASE), &info->mcontext.hi1); - sys_ptrace(PTRACE_PEEKUSER, tid, - reinterpret_cast(DSP_BASE + 1), &info->mcontext.lo1); - sys_ptrace(PTRACE_PEEKUSER, tid, - reinterpret_cast(DSP_BASE + 2), &info->mcontext.hi2); - sys_ptrace(PTRACE_PEEKUSER, tid, - reinterpret_cast(DSP_BASE + 3), &info->mcontext.lo2); - sys_ptrace(PTRACE_PEEKUSER, tid, - reinterpret_cast(DSP_BASE + 4), &info->mcontext.hi3); - sys_ptrace(PTRACE_PEEKUSER, tid, - reinterpret_cast(DSP_BASE + 5), &info->mcontext.lo3); - sys_ptrace(PTRACE_PEEKUSER, tid, - reinterpret_cast(DSP_CONTROL), &info->mcontext.dsp); -#endif - - const uint8_t* stack_pointer; -#if defined(__i386) - my_memcpy(&stack_pointer, &info->regs.esp, sizeof(info->regs.esp)); -#elif defined(__x86_64) - my_memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp)); -#elif defined(__ARM_EABI__) - my_memcpy(&stack_pointer, &info->regs.ARM_sp, sizeof(info->regs.ARM_sp)); -#elif defined(__aarch64__) - my_memcpy(&stack_pointer, &info->regs.sp, sizeof(info->regs.sp)); -#elif defined(__mips__) - stack_pointer = - reinterpret_cast(info->mcontext.gregs[MD_CONTEXT_MIPS_REG_SP]); -#else -#error "This code hasn't been ported to your platform yet." -#endif - info->stack_pointer = reinterpret_cast(stack_pointer); - - return true; -} - -bool LinuxPtraceDumper::IsPostMortem() const { - return false; -} - -bool LinuxPtraceDumper::ThreadsSuspend() { - if (threads_suspended_) - return true; - for (size_t i = 0; i < threads_.size(); ++i) { - if (!SuspendThread(threads_[i])) { - // If the thread either disappeared before we could attach to it, or if - // it was part of the seccomp sandbox's trusted code, it is OK to - // silently drop it from the minidump. - if (i < threads_.size() - 1) { - my_memmove(&threads_[i], &threads_[i + 1], - (threads_.size() - i - 1) * sizeof(threads_[i])); - } - threads_.resize(threads_.size() - 1); - --i; - } - } - threads_suspended_ = true; - return threads_.size() > 0; -} - -bool LinuxPtraceDumper::ThreadsResume() { - if (!threads_suspended_) - return false; - bool good = true; - for (size_t i = 0; i < threads_.size(); ++i) - good &= ResumeThread(threads_[i]); - threads_suspended_ = false; - return good; -} - -// Parse /proc/$pid/task to list all the threads of the process identified by -// pid. -bool LinuxPtraceDumper::EnumerateThreads() { - char task_path[NAME_MAX]; - if (!BuildProcPath(task_path, pid_, "task")) - return false; - - const int fd = sys_open(task_path, O_RDONLY | O_DIRECTORY, 0); - if (fd < 0) - return false; - DirectoryReader* dir_reader = new(allocator_) DirectoryReader(fd); - - // The directory may contain duplicate entries which we filter by assuming - // that they are consecutive. - int last_tid = -1; - const char* dent_name; - while (dir_reader->GetNextEntry(&dent_name)) { - if (my_strcmp(dent_name, ".") && - my_strcmp(dent_name, "..")) { - int tid = 0; - if (my_strtoui(&tid, dent_name) && - last_tid != tid) { - last_tid = tid; - threads_.push_back(tid); - } - } - dir_reader->PopEntry(); - } - - sys_close(fd); - return true; -} - -} // namespace google_breakpad diff --git a/TMessagesProj/jni/breakpad/client/linux/minidump_writer/linux_ptrace_dumper.h b/TMessagesProj/jni/breakpad/client/linux/minidump_writer/linux_ptrace_dumper.h deleted file mode 100644 index 2ce834b0f..000000000 --- a/TMessagesProj/jni/breakpad/client/linux/minidump_writer/linux_ptrace_dumper.h +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) 2012, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// linux_ptrace_dumper.h: Define the google_breakpad::LinuxPtraceDumper -// class, which is derived from google_breakpad::LinuxDumper to extract -// information from a crashed process via ptrace. -// This class was originally splitted from google_breakpad::LinuxDumper. - -#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINUX_PTRACE_DUMPER_H_ -#define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_PTRACE_DUMPER_H_ - -#include "client/linux/minidump_writer/linux_dumper.h" - -namespace google_breakpad { - -class LinuxPtraceDumper : public LinuxDumper { - public: - // Constructs a dumper for extracting information of a given process - // with a process ID of |pid|. - explicit LinuxPtraceDumper(pid_t pid); - - // Implements LinuxDumper::BuildProcPath(). - // Builds a proc path for a certain pid for a node (/proc//). - // |path| is a character array of at least NAME_MAX bytes to return the - // result. |node| is the final node without any slashes. Returns true on - // success. - virtual bool BuildProcPath(char* path, pid_t pid, const char* node) const; - - // Implements LinuxDumper::CopyFromProcess(). - // Copies content of |length| bytes from a given process |child|, - // starting from |src|, into |dest|. This method uses ptrace to extract - // the content from the target process. Always returns true. - virtual bool CopyFromProcess(void* dest, pid_t child, const void* src, - size_t length); - - // Implements LinuxDumper::GetThreadInfoByIndex(). - // Reads information about the |index|-th thread of |threads_|. - // Returns true on success. One must have called |ThreadsSuspend| first. - virtual bool GetThreadInfoByIndex(size_t index, ThreadInfo* info); - - // Implements LinuxDumper::IsPostMortem(). - // Always returns false to indicate this dumper performs a dump of - // a crashed process via ptrace. - virtual bool IsPostMortem() const; - - // Implements LinuxDumper::ThreadsSuspend(). - // Suspends all threads in the given process. Returns true on success. - virtual bool ThreadsSuspend(); - - // Implements LinuxDumper::ThreadsResume(). - // Resumes all threads in the given process. Returns true on success. - virtual bool ThreadsResume(); - - protected: - // Implements LinuxDumper::EnumerateThreads(). - // Enumerates all threads of the given process into |threads_|. - virtual bool EnumerateThreads(); - - private: - // Set to true if all threads of the crashed process are suspended. - bool threads_suspended_; -}; - -} // namespace google_breakpad - -#endif // CLIENT_LINUX_HANDLER_LINUX_PTRACE_DUMPER_H_ diff --git a/TMessagesProj/jni/breakpad/client/linux/minidump_writer/minidump_writer.cc b/TMessagesProj/jni/breakpad/client/linux/minidump_writer/minidump_writer.cc deleted file mode 100644 index 4b1e6f878..000000000 --- a/TMessagesProj/jni/breakpad/client/linux/minidump_writer/minidump_writer.cc +++ /dev/null @@ -1,1367 +0,0 @@ -// Copyright (c) 2010, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// This code writes out minidump files: -// http://msdn.microsoft.com/en-us/library/ms680378(VS.85,loband).aspx -// -// Minidumps are a Microsoft format which Breakpad uses for recording crash -// dumps. This code has to run in a compromised environment (the address space -// may have received SIGSEGV), thus the following rules apply: -// * You may not enter the dynamic linker. This means that we cannot call -// any symbols in a shared library (inc libc). Because of this we replace -// libc functions in linux_libc_support.h. -// * You may not call syscalls via the libc wrappers. This rule is a subset -// of the first rule but it bears repeating. We have direct wrappers -// around the system calls in linux_syscall_support.h. -// * You may not malloc. There's an alternative allocator in memory.h and -// a canonical instance in the LinuxDumper object. We use the placement -// new form to allocate objects and we don't delete them. - -#include "client/linux/handler/minidump_descriptor.h" -#include "client/linux/minidump_writer/minidump_writer.h" -#include "client/minidump_file_writer-inl.h" - -#include -#include -#include -#include -#include -#if defined(__ANDROID__) -#include -#endif -#include -#include -#include -#include -#include -#include - -#include - -#include "client/linux/dump_writer_common/thread_info.h" -#include "client/linux/dump_writer_common/ucontext_reader.h" -#include "client/linux/handler/exception_handler.h" -#include "client/linux/minidump_writer/cpu_set.h" -#include "client/linux/minidump_writer/line_reader.h" -#include "client/linux/minidump_writer/linux_dumper.h" -#include "client/linux/minidump_writer/linux_ptrace_dumper.h" -#include "client/linux/minidump_writer/proc_cpuinfo_reader.h" -#include "client/minidump_file_writer.h" -#include "common/linux/linux_libc_support.h" -#include "common/minidump_type_helper.h" -#include "google_breakpad/common/minidump_format.h" -#include "third_party/lss/linux_syscall_support.h" - -namespace { - -using google_breakpad::AppMemoryList; -using google_breakpad::ExceptionHandler; -using google_breakpad::CpuSet; -using google_breakpad::LineReader; -using google_breakpad::LinuxDumper; -using google_breakpad::LinuxPtraceDumper; -using google_breakpad::MDTypeHelper; -using google_breakpad::MappingEntry; -using google_breakpad::MappingInfo; -using google_breakpad::MappingList; -using google_breakpad::MinidumpFileWriter; -using google_breakpad::PageAllocator; -using google_breakpad::ProcCpuInfoReader; -using google_breakpad::RawContextCPU; -using google_breakpad::ThreadInfo; -using google_breakpad::TypedMDRVA; -using google_breakpad::UContextReader; -using google_breakpad::UntypedMDRVA; -using google_breakpad::wasteful_vector; - -typedef MDTypeHelper::MDRawDebug MDRawDebug; -typedef MDTypeHelper::MDRawLinkMap MDRawLinkMap; - -class MinidumpWriter { - public: - // The following kLimit* constants are for when minidump_size_limit_ is set - // and the minidump size might exceed it. - // - // Estimate for how big each thread's stack will be (in bytes). - static const unsigned kLimitAverageThreadStackLength = 8 * 1024; - // Number of threads whose stack size we don't want to limit. These base - // threads will simply be the first N threads returned by the dumper (although - // the crashing thread will never be limited). Threads beyond this count are - // the extra threads. - static const unsigned kLimitBaseThreadCount = 20; - // Maximum stack size to dump for any extra thread (in bytes). - static const unsigned kLimitMaxExtraThreadStackLen = 2 * 1024; - // Make sure this number of additional bytes can fit in the minidump - // (exclude the stack data). - static const unsigned kLimitMinidumpFudgeFactor = 64 * 1024; - - MinidumpWriter(const char* minidump_path, - int minidump_fd, - const ExceptionHandler::CrashContext* context, - const MappingList& mappings, - const AppMemoryList& appmem, - LinuxDumper* dumper) - : fd_(minidump_fd), - path_(minidump_path), - ucontext_(context ? &context->context : NULL), -#if !defined(__ARM_EABI__) && !defined(__mips__) - float_state_(context ? &context->float_state : NULL), -#endif - dumper_(dumper), - minidump_size_limit_(-1), - memory_blocks_(dumper_->allocator()), - mapping_list_(mappings), - app_memory_list_(appmem) { - // Assert there should be either a valid fd or a valid path, not both. - assert(fd_ != -1 || minidump_path); - assert(fd_ == -1 || !minidump_path); - } - - bool Init() { - if (!dumper_->Init()) - return false; - - if (fd_ != -1) - minidump_writer_.SetFile(fd_); - else if (!minidump_writer_.Open(path_)) - return false; - - return dumper_->ThreadsSuspend() && dumper_->LateInit(); - } - - ~MinidumpWriter() { - // Don't close the file descriptor when it's been provided explicitly. - // Callers might still need to use it. - if (fd_ == -1) - minidump_writer_.Close(); - dumper_->ThreadsResume(); - } - - bool Dump() { - // A minidump file contains a number of tagged streams. This is the number - // of stream which we write. - unsigned kNumWriters = 13; - - TypedMDRVA header(&minidump_writer_); - TypedMDRVA dir(&minidump_writer_); - if (!header.Allocate()) - return false; - if (!dir.AllocateArray(kNumWriters)) - return false; - my_memset(header.get(), 0, sizeof(MDRawHeader)); - - header.get()->signature = MD_HEADER_SIGNATURE; - header.get()->version = MD_HEADER_VERSION; - header.get()->time_date_stamp = time(NULL); - header.get()->stream_count = kNumWriters; - header.get()->stream_directory_rva = dir.position(); - - unsigned dir_index = 0; - MDRawDirectory dirent; - - if (!WriteThreadListStream(&dirent)) - return false; - dir.CopyIndex(dir_index++, &dirent); - - if (!WriteMappings(&dirent)) - return false; - dir.CopyIndex(dir_index++, &dirent); - - if (!WriteAppMemory()) - return false; - - if (!WriteMemoryListStream(&dirent)) - return false; - dir.CopyIndex(dir_index++, &dirent); - - if (!WriteExceptionStream(&dirent)) - return false; - dir.CopyIndex(dir_index++, &dirent); - - if (!WriteSystemInfoStream(&dirent)) - return false; - dir.CopyIndex(dir_index++, &dirent); - - dirent.stream_type = MD_LINUX_CPU_INFO; - if (!WriteFile(&dirent.location, "/proc/cpuinfo")) - NullifyDirectoryEntry(&dirent); - dir.CopyIndex(dir_index++, &dirent); - - dirent.stream_type = MD_LINUX_PROC_STATUS; - if (!WriteProcFile(&dirent.location, GetCrashThread(), "status")) - NullifyDirectoryEntry(&dirent); - dir.CopyIndex(dir_index++, &dirent); - - dirent.stream_type = MD_LINUX_LSB_RELEASE; - if (!WriteFile(&dirent.location, "/etc/lsb-release")) - NullifyDirectoryEntry(&dirent); - dir.CopyIndex(dir_index++, &dirent); - - dirent.stream_type = MD_LINUX_CMD_LINE; - if (!WriteProcFile(&dirent.location, GetCrashThread(), "cmdline")) - NullifyDirectoryEntry(&dirent); - dir.CopyIndex(dir_index++, &dirent); - - dirent.stream_type = MD_LINUX_ENVIRON; - if (!WriteProcFile(&dirent.location, GetCrashThread(), "environ")) - NullifyDirectoryEntry(&dirent); - dir.CopyIndex(dir_index++, &dirent); - - dirent.stream_type = MD_LINUX_AUXV; - if (!WriteProcFile(&dirent.location, GetCrashThread(), "auxv")) - NullifyDirectoryEntry(&dirent); - dir.CopyIndex(dir_index++, &dirent); - - dirent.stream_type = MD_LINUX_MAPS; - if (!WriteProcFile(&dirent.location, GetCrashThread(), "maps")) - NullifyDirectoryEntry(&dirent); - dir.CopyIndex(dir_index++, &dirent); - - dirent.stream_type = MD_LINUX_DSO_DEBUG; - if (!WriteDSODebugStream(&dirent)) - NullifyDirectoryEntry(&dirent); - dir.CopyIndex(dir_index++, &dirent); - - // If you add more directory entries, don't forget to update kNumWriters, - // above. - - dumper_->ThreadsResume(); - return true; - } - - bool FillThreadStack(MDRawThread* thread, uintptr_t stack_pointer, - int max_stack_len, uint8_t** stack_copy) { - *stack_copy = NULL; - const void* stack; - size_t stack_len; - if (dumper_->GetStackInfo(&stack, &stack_len, stack_pointer)) { - UntypedMDRVA memory(&minidump_writer_); - if (max_stack_len >= 0 && - stack_len > static_cast(max_stack_len)) { - stack_len = max_stack_len; - } - if (!memory.Allocate(stack_len)) - return false; - *stack_copy = reinterpret_cast(Alloc(stack_len)); - dumper_->CopyFromProcess(*stack_copy, thread->thread_id, stack, - stack_len); - memory.Copy(*stack_copy, stack_len); - thread->stack.start_of_memory_range = - reinterpret_cast(stack); - thread->stack.memory = memory.location(); - memory_blocks_.push_back(thread->stack); - } else { - thread->stack.start_of_memory_range = stack_pointer; - thread->stack.memory.data_size = 0; - thread->stack.memory.rva = minidump_writer_.position(); - } - return true; - } - - // Write information about the threads. - bool WriteThreadListStream(MDRawDirectory* dirent) { - const unsigned num_threads = dumper_->threads().size(); - - TypedMDRVA list(&minidump_writer_); - if (!list.AllocateObjectAndArray(num_threads, sizeof(MDRawThread))) - return false; - - dirent->stream_type = MD_THREAD_LIST_STREAM; - dirent->location = list.location(); - - *list.get() = num_threads; - - // If there's a minidump size limit, check if it might be exceeded. Since - // most of the space is filled with stack data, just check against that. - // If this expects to exceed the limit, set extra_thread_stack_len such - // that any thread beyond the first kLimitBaseThreadCount threads will - // have only kLimitMaxExtraThreadStackLen bytes dumped. - int extra_thread_stack_len = -1; // default to no maximum - if (minidump_size_limit_ >= 0) { - const unsigned estimated_total_stack_size = num_threads * - kLimitAverageThreadStackLength; - const off_t estimated_minidump_size = minidump_writer_.position() + - estimated_total_stack_size + kLimitMinidumpFudgeFactor; - if (estimated_minidump_size > minidump_size_limit_) - extra_thread_stack_len = kLimitMaxExtraThreadStackLen; - } - - for (unsigned i = 0; i < num_threads; ++i) { - MDRawThread thread; - my_memset(&thread, 0, sizeof(thread)); - thread.thread_id = dumper_->threads()[i]; - - // We have a different source of information for the crashing thread. If - // we used the actual state of the thread we would find it running in the - // signal handler with the alternative stack, which would be deeply - // unhelpful. - if (static_cast(thread.thread_id) == GetCrashThread() && - ucontext_ && - !dumper_->IsPostMortem()) { - uint8_t* stack_copy; - const uintptr_t stack_ptr = UContextReader::GetStackPointer(ucontext_); - if (!FillThreadStack(&thread, stack_ptr, -1, &stack_copy)) - return false; - - // Copy 256 bytes around crashing instruction pointer to minidump. - const size_t kIPMemorySize = 256; - uint64_t ip = UContextReader::GetInstructionPointer(ucontext_); - // Bound it to the upper and lower bounds of the memory map - // it's contained within. If it's not in mapped memory, - // don't bother trying to write it. - bool ip_is_mapped = false; - MDMemoryDescriptor ip_memory_d; - for (unsigned j = 0; j < dumper_->mappings().size(); ++j) { - const MappingInfo& mapping = *dumper_->mappings()[j]; - if (ip >= mapping.start_addr && - ip < mapping.start_addr + mapping.size) { - ip_is_mapped = true; - // Try to get 128 bytes before and after the IP, but - // settle for whatever's available. - ip_memory_d.start_of_memory_range = - std::max(mapping.start_addr, - uintptr_t(ip - (kIPMemorySize / 2))); - uintptr_t end_of_range = - std::min(uintptr_t(ip + (kIPMemorySize / 2)), - uintptr_t(mapping.start_addr + mapping.size)); - ip_memory_d.memory.data_size = - end_of_range - ip_memory_d.start_of_memory_range; - break; - } - } - - if (ip_is_mapped) { - UntypedMDRVA ip_memory(&minidump_writer_); - if (!ip_memory.Allocate(ip_memory_d.memory.data_size)) - return false; - uint8_t* memory_copy = - reinterpret_cast(Alloc(ip_memory_d.memory.data_size)); - dumper_->CopyFromProcess( - memory_copy, - thread.thread_id, - reinterpret_cast(ip_memory_d.start_of_memory_range), - ip_memory_d.memory.data_size); - ip_memory.Copy(memory_copy, ip_memory_d.memory.data_size); - ip_memory_d.memory = ip_memory.location(); - memory_blocks_.push_back(ip_memory_d); - } - - TypedMDRVA cpu(&minidump_writer_); - if (!cpu.Allocate()) - return false; - my_memset(cpu.get(), 0, sizeof(RawContextCPU)); -#if !defined(__ARM_EABI__) && !defined(__mips__) - UContextReader::FillCPUContext(cpu.get(), ucontext_, float_state_); -#else - UContextReader::FillCPUContext(cpu.get(), ucontext_); -#endif - thread.thread_context = cpu.location(); - crashing_thread_context_ = cpu.location(); - } else { - ThreadInfo info; - if (!dumper_->GetThreadInfoByIndex(i, &info)) - return false; - - uint8_t* stack_copy; - int max_stack_len = -1; // default to no maximum for this thread - if (minidump_size_limit_ >= 0 && i >= kLimitBaseThreadCount) - max_stack_len = extra_thread_stack_len; - if (!FillThreadStack(&thread, info.stack_pointer, max_stack_len, - &stack_copy)) - return false; - - TypedMDRVA cpu(&minidump_writer_); - if (!cpu.Allocate()) - return false; - my_memset(cpu.get(), 0, sizeof(RawContextCPU)); - info.FillCPUContext(cpu.get()); - thread.thread_context = cpu.location(); - if (dumper_->threads()[i] == GetCrashThread()) { - crashing_thread_context_ = cpu.location(); - if (!dumper_->IsPostMortem()) { - // This is the crashing thread of a live process, but - // no context was provided, so set the crash address - // while the instruction pointer is already here. - dumper_->set_crash_address(info.GetInstructionPointer()); - } - } - } - - list.CopyIndexAfterObject(i, &thread, sizeof(thread)); - } - - return true; - } - - // Write application-provided memory regions. - bool WriteAppMemory() { - for (AppMemoryList::const_iterator iter = app_memory_list_.begin(); - iter != app_memory_list_.end(); - ++iter) { - uint8_t* data_copy = - reinterpret_cast(dumper_->allocator()->Alloc(iter->length)); - dumper_->CopyFromProcess(data_copy, GetCrashThread(), iter->ptr, - iter->length); - - UntypedMDRVA memory(&minidump_writer_); - if (!memory.Allocate(iter->length)) { - return false; - } - memory.Copy(data_copy, iter->length); - MDMemoryDescriptor desc; - desc.start_of_memory_range = reinterpret_cast(iter->ptr); - desc.memory = memory.location(); - memory_blocks_.push_back(desc); - } - - return true; - } - - static bool ShouldIncludeMapping(const MappingInfo& mapping) { - if (mapping.name[0] == 0 || // only want modules with filenames. - // Only want to include one mapping per shared lib. - // Avoid filtering executable mappings. - (mapping.offset != 0 && !mapping.exec) || - mapping.size < 4096) { // too small to get a signature for. - return false; - } - - return true; - } - - // If there is caller-provided information about this mapping - // in the mapping_list_ list, return true. Otherwise, return false. - bool HaveMappingInfo(const MappingInfo& mapping) { - for (MappingList::const_iterator iter = mapping_list_.begin(); - iter != mapping_list_.end(); - ++iter) { - // Ignore any mappings that are wholly contained within - // mappings in the mapping_info_ list. - if (mapping.start_addr >= iter->first.start_addr && - (mapping.start_addr + mapping.size) <= - (iter->first.start_addr + iter->first.size)) { - return true; - } - } - return false; - } - - // Write information about the mappings in effect. Because we are using the - // minidump format, the information about the mappings is pretty limited. - // Because of this, we also include the full, unparsed, /proc/$x/maps file in - // another stream in the file. - bool WriteMappings(MDRawDirectory* dirent) { - const unsigned num_mappings = dumper_->mappings().size(); - unsigned num_output_mappings = mapping_list_.size(); - - for (unsigned i = 0; i < dumper_->mappings().size(); ++i) { - const MappingInfo& mapping = *dumper_->mappings()[i]; - if (ShouldIncludeMapping(mapping) && !HaveMappingInfo(mapping)) - num_output_mappings++; - } - - TypedMDRVA list(&minidump_writer_); - if (num_output_mappings) { - if (!list.AllocateObjectAndArray(num_output_mappings, MD_MODULE_SIZE)) - return false; - } else { - // Still create the module list stream, although it will have zero - // modules. - if (!list.Allocate()) - return false; - } - - dirent->stream_type = MD_MODULE_LIST_STREAM; - dirent->location = list.location(); - *list.get() = num_output_mappings; - - // First write all the mappings from the dumper - unsigned int j = 0; - for (unsigned i = 0; i < num_mappings; ++i) { - const MappingInfo& mapping = *dumper_->mappings()[i]; - if (!ShouldIncludeMapping(mapping) || HaveMappingInfo(mapping)) - continue; - - MDRawModule mod; - if (!FillRawModule(mapping, true, i, mod, NULL)) - return false; - list.CopyIndexAfterObject(j++, &mod, MD_MODULE_SIZE); - } - // Next write all the mappings provided by the caller - for (MappingList::const_iterator iter = mapping_list_.begin(); - iter != mapping_list_.end(); - ++iter) { - MDRawModule mod; - if (!FillRawModule(iter->first, false, 0, mod, iter->second)) - return false; - list.CopyIndexAfterObject(j++, &mod, MD_MODULE_SIZE); - } - - return true; - } - - // Fill the MDRawModule |mod| with information about the provided - // |mapping|. If |identifier| is non-NULL, use it instead of calculating - // a file ID from the mapping. - bool FillRawModule(const MappingInfo& mapping, - bool member, - unsigned int mapping_id, - MDRawModule& mod, - const uint8_t* identifier) { - my_memset(&mod, 0, MD_MODULE_SIZE); - - mod.base_of_image = mapping.start_addr; - mod.size_of_image = mapping.size; - - uint8_t cv_buf[MDCVInfoPDB70_minsize + NAME_MAX]; - uint8_t* cv_ptr = cv_buf; - - const uint32_t cv_signature = MD_CVINFOPDB70_SIGNATURE; - my_memcpy(cv_ptr, &cv_signature, sizeof(cv_signature)); - cv_ptr += sizeof(cv_signature); - uint8_t* signature = cv_ptr; - cv_ptr += sizeof(MDGUID); - if (identifier) { - // GUID was provided by caller. - my_memcpy(signature, identifier, sizeof(MDGUID)); - } else { - // Note: ElfFileIdentifierForMapping() can manipulate the |mapping.name|. - dumper_->ElfFileIdentifierForMapping(mapping, member, - mapping_id, signature); - } - my_memset(cv_ptr, 0, sizeof(uint32_t)); // Set age to 0 on Linux. - cv_ptr += sizeof(uint32_t); - - char file_name[NAME_MAX]; - char file_path[NAME_MAX]; - LinuxDumper::GetMappingEffectiveNameAndPath( - mapping, file_path, sizeof(file_path), file_name, sizeof(file_name)); - - const size_t file_name_len = my_strlen(file_name); - UntypedMDRVA cv(&minidump_writer_); - if (!cv.Allocate(MDCVInfoPDB70_minsize + file_name_len + 1)) - return false; - - // Write pdb_file_name - my_memcpy(cv_ptr, file_name, file_name_len + 1); - cv.Copy(cv_buf, MDCVInfoPDB70_minsize + file_name_len + 1); - - mod.cv_record = cv.location(); - - MDLocationDescriptor ld; - if (!minidump_writer_.WriteString(file_path, my_strlen(file_path), &ld)) - return false; - mod.module_name_rva = ld.rva; - return true; - } - - bool WriteMemoryListStream(MDRawDirectory* dirent) { - TypedMDRVA list(&minidump_writer_); - if (memory_blocks_.size()) { - if (!list.AllocateObjectAndArray(memory_blocks_.size(), - sizeof(MDMemoryDescriptor))) - return false; - } else { - // Still create the memory list stream, although it will have zero - // memory blocks. - if (!list.Allocate()) - return false; - } - - dirent->stream_type = MD_MEMORY_LIST_STREAM; - dirent->location = list.location(); - - *list.get() = memory_blocks_.size(); - - for (size_t i = 0; i < memory_blocks_.size(); ++i) { - list.CopyIndexAfterObject(i, &memory_blocks_[i], - sizeof(MDMemoryDescriptor)); - } - return true; - } - - bool WriteExceptionStream(MDRawDirectory* dirent) { - TypedMDRVA exc(&minidump_writer_); - if (!exc.Allocate()) - return false; - my_memset(exc.get(), 0, sizeof(MDRawExceptionStream)); - - dirent->stream_type = MD_EXCEPTION_STREAM; - dirent->location = exc.location(); - - exc.get()->thread_id = GetCrashThread(); - exc.get()->exception_record.exception_code = dumper_->crash_signal(); - exc.get()->exception_record.exception_address = dumper_->crash_address(); - exc.get()->thread_context = crashing_thread_context_; - - return true; - } - - bool WriteSystemInfoStream(MDRawDirectory* dirent) { - TypedMDRVA si(&minidump_writer_); - if (!si.Allocate()) - return false; - my_memset(si.get(), 0, sizeof(MDRawSystemInfo)); - - dirent->stream_type = MD_SYSTEM_INFO_STREAM; - dirent->location = si.location(); - - WriteCPUInformation(si.get()); - WriteOSInformation(si.get()); - - return true; - } - - bool WriteDSODebugStream(MDRawDirectory* dirent) { - ElfW(Phdr)* phdr = reinterpret_cast(dumper_->auxv()[AT_PHDR]); - char* base; - int phnum = dumper_->auxv()[AT_PHNUM]; - if (!phnum || !phdr) - return false; - - // Assume the program base is at the beginning of the same page as the PHDR - base = reinterpret_cast(reinterpret_cast(phdr) & ~0xfff); - - // Search for the program PT_DYNAMIC segment - ElfW(Addr) dyn_addr = 0; - for (; phnum >= 0; phnum--, phdr++) { - ElfW(Phdr) ph; - if (!dumper_->CopyFromProcess(&ph, GetCrashThread(), phdr, sizeof(ph))) - return false; - - // Adjust base address with the virtual address of the PT_LOAD segment - // corresponding to offset 0 - if (ph.p_type == PT_LOAD && ph.p_offset == 0) { - base -= ph.p_vaddr; - } - if (ph.p_type == PT_DYNAMIC) { - dyn_addr = ph.p_vaddr; - } - } - if (!dyn_addr) - return false; - - ElfW(Dyn) *dynamic = reinterpret_cast(dyn_addr + base); - - // The dynamic linker makes information available that helps gdb find all - // DSOs loaded into the program. If this information is indeed available, - // dump it to a MD_LINUX_DSO_DEBUG stream. - struct r_debug* r_debug = NULL; - uint32_t dynamic_length = 0; - - for (int i = 0; ; ++i) { - ElfW(Dyn) dyn; - dynamic_length += sizeof(dyn); - if (!dumper_->CopyFromProcess(&dyn, GetCrashThread(), dynamic + i, - sizeof(dyn))) { - return false; - } - -#ifdef __mips__ - if (dyn.d_tag == DT_MIPS_RLD_MAP) { - r_debug = reinterpret_cast(dyn.d_un.d_ptr); - continue; - } -#else - if (dyn.d_tag == DT_DEBUG) { - r_debug = reinterpret_cast(dyn.d_un.d_ptr); - continue; - } -#endif - else if (dyn.d_tag == DT_NULL) { - break; - } - } - - // The "r_map" field of that r_debug struct contains a linked list of all - // loaded DSOs. - // Our list of DSOs potentially is different from the ones in the crashing - // process. So, we have to be careful to never dereference pointers - // directly. Instead, we use CopyFromProcess() everywhere. - // See for a more detailed discussion of the how the dynamic - // loader communicates with debuggers. - - // Count the number of loaded DSOs - int dso_count = 0; - struct r_debug debug_entry; - if (!dumper_->CopyFromProcess(&debug_entry, GetCrashThread(), r_debug, - sizeof(debug_entry))) { - return false; - } - for (struct link_map* ptr = debug_entry.r_map; ptr; ) { - struct link_map map; - if (!dumper_->CopyFromProcess(&map, GetCrashThread(), ptr, sizeof(map))) - return false; - - ptr = map.l_next; - dso_count++; - } - - MDRVA linkmap_rva = minidump_writer_.kInvalidMDRVA; - if (dso_count > 0) { - // If we have at least one DSO, create an array of MDRawLinkMap - // entries in the minidump file. - TypedMDRVA linkmap(&minidump_writer_); - if (!linkmap.AllocateArray(dso_count)) - return false; - linkmap_rva = linkmap.location().rva; - int idx = 0; - - // Iterate over DSOs and write their information to mini dump - for (struct link_map* ptr = debug_entry.r_map; ptr; ) { - struct link_map map; - if (!dumper_->CopyFromProcess(&map, GetCrashThread(), ptr, sizeof(map))) - return false; - - ptr = map.l_next; - char filename[257] = { 0 }; - if (map.l_name) { - dumper_->CopyFromProcess(filename, GetCrashThread(), map.l_name, - sizeof(filename) - 1); - } - MDLocationDescriptor location; - if (!minidump_writer_.WriteString(filename, 0, &location)) - return false; - MDRawLinkMap entry; - entry.name = location.rva; - entry.addr = map.l_addr; - entry.ld = reinterpret_cast(map.l_ld); - linkmap.CopyIndex(idx++, &entry); - } - } - - // Write MD_LINUX_DSO_DEBUG record - TypedMDRVA debug(&minidump_writer_); - if (!debug.AllocateObjectAndArray(1, dynamic_length)) - return false; - my_memset(debug.get(), 0, sizeof(MDRawDebug)); - dirent->stream_type = MD_LINUX_DSO_DEBUG; - dirent->location = debug.location(); - - debug.get()->version = debug_entry.r_version; - debug.get()->map = linkmap_rva; - debug.get()->dso_count = dso_count; - debug.get()->brk = debug_entry.r_brk; - debug.get()->ldbase = debug_entry.r_ldbase; - debug.get()->dynamic = reinterpret_cast(dynamic); - - wasteful_vector dso_debug_data(dumper_->allocator(), dynamic_length); - // The passed-in size to the constructor (above) is only a hint. - // Must call .resize() to do actual initialization of the elements. - dso_debug_data.resize(dynamic_length); - dumper_->CopyFromProcess(&dso_debug_data[0], GetCrashThread(), dynamic, - dynamic_length); - debug.CopyIndexAfterObject(0, &dso_debug_data[0], dynamic_length); - - return true; - } - - void set_minidump_size_limit(off_t limit) { minidump_size_limit_ = limit; } - - private: - void* Alloc(unsigned bytes) { - return dumper_->allocator()->Alloc(bytes); - } - - pid_t GetCrashThread() const { - return dumper_->crash_thread(); - } - - void NullifyDirectoryEntry(MDRawDirectory* dirent) { - dirent->stream_type = 0; - dirent->location.data_size = 0; - dirent->location.rva = 0; - } - -#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) - bool WriteCPUInformation(MDRawSystemInfo* sys_info) { - char vendor_id[sizeof(sys_info->cpu.x86_cpu_info.vendor_id) + 1] = {0}; - static const char vendor_id_name[] = "vendor_id"; - - struct CpuInfoEntry { - const char* info_name; - int value; - bool found; - } cpu_info_table[] = { - { "processor", -1, false }, -#if defined(__i386__) || defined(__x86_64__) - { "model", 0, false }, - { "stepping", 0, false }, - { "cpu family", 0, false }, -#endif - }; - - // processor_architecture should always be set, do this first - sys_info->processor_architecture = -#if defined(__mips__) - MD_CPU_ARCHITECTURE_MIPS; -#elif defined(__i386__) - MD_CPU_ARCHITECTURE_X86; -#else - MD_CPU_ARCHITECTURE_AMD64; -#endif - - const int fd = sys_open("/proc/cpuinfo", O_RDONLY, 0); - if (fd < 0) - return false; - - { - PageAllocator allocator; - ProcCpuInfoReader* const reader = new(allocator) ProcCpuInfoReader(fd); - const char* field; - while (reader->GetNextField(&field)) { - for (size_t i = 0; - i < sizeof(cpu_info_table) / sizeof(cpu_info_table[0]); - i++) { - CpuInfoEntry* entry = &cpu_info_table[i]; - if (i > 0 && entry->found) { - // except for the 'processor' field, ignore repeated values. - continue; - } - if (!my_strcmp(field, entry->info_name)) { - size_t value_len; - const char* value = reader->GetValueAndLen(&value_len); - if (value_len == 0) - continue; - - uintptr_t val; - if (my_read_decimal_ptr(&val, value) == value) - continue; - - entry->value = static_cast(val); - entry->found = true; - } - } - - // special case for vendor_id - if (!my_strcmp(field, vendor_id_name)) { - size_t value_len; - const char* value = reader->GetValueAndLen(&value_len); - if (value_len > 0) - my_strlcpy(vendor_id, value, sizeof(vendor_id)); - } - } - sys_close(fd); - } - - // make sure we got everything we wanted - for (size_t i = 0; - i < sizeof(cpu_info_table) / sizeof(cpu_info_table[0]); - i++) { - if (!cpu_info_table[i].found) { - return false; - } - } - // cpu_info_table[0] holds the last cpu id listed in /proc/cpuinfo, - // assuming this is the highest id, change it to the number of CPUs - // by adding one. - cpu_info_table[0].value++; - - sys_info->number_of_processors = cpu_info_table[0].value; -#if defined(__i386__) || defined(__x86_64__) - sys_info->processor_level = cpu_info_table[3].value; - sys_info->processor_revision = cpu_info_table[1].value << 8 | - cpu_info_table[2].value; -#endif - - if (vendor_id[0] != '\0') { - my_memcpy(sys_info->cpu.x86_cpu_info.vendor_id, vendor_id, - sizeof(sys_info->cpu.x86_cpu_info.vendor_id)); - } - return true; - } -#elif defined(__arm__) || defined(__aarch64__) - bool WriteCPUInformation(MDRawSystemInfo* sys_info) { - // The CPUID value is broken up in several entries in /proc/cpuinfo. - // This table is used to rebuild it from the entries. - const struct CpuIdEntry { - const char* field; - char format; - char bit_lshift; - char bit_length; - } cpu_id_entries[] = { - { "CPU implementer", 'x', 24, 8 }, - { "CPU variant", 'x', 20, 4 }, - { "CPU part", 'x', 4, 12 }, - { "CPU revision", 'd', 0, 4 }, - }; - - // The ELF hwcaps are listed in the "Features" entry as textual tags. - // This table is used to rebuild them. - const struct CpuFeaturesEntry { - const char* tag; - uint32_t hwcaps; - } cpu_features_entries[] = { -#if defined(__arm__) - { "swp", MD_CPU_ARM_ELF_HWCAP_SWP }, - { "half", MD_CPU_ARM_ELF_HWCAP_HALF }, - { "thumb", MD_CPU_ARM_ELF_HWCAP_THUMB }, - { "26bit", MD_CPU_ARM_ELF_HWCAP_26BIT }, - { "fastmult", MD_CPU_ARM_ELF_HWCAP_FAST_MULT }, - { "fpa", MD_CPU_ARM_ELF_HWCAP_FPA }, - { "vfp", MD_CPU_ARM_ELF_HWCAP_VFP }, - { "edsp", MD_CPU_ARM_ELF_HWCAP_EDSP }, - { "java", MD_CPU_ARM_ELF_HWCAP_JAVA }, - { "iwmmxt", MD_CPU_ARM_ELF_HWCAP_IWMMXT }, - { "crunch", MD_CPU_ARM_ELF_HWCAP_CRUNCH }, - { "thumbee", MD_CPU_ARM_ELF_HWCAP_THUMBEE }, - { "neon", MD_CPU_ARM_ELF_HWCAP_NEON }, - { "vfpv3", MD_CPU_ARM_ELF_HWCAP_VFPv3 }, - { "vfpv3d16", MD_CPU_ARM_ELF_HWCAP_VFPv3D16 }, - { "tls", MD_CPU_ARM_ELF_HWCAP_TLS }, - { "vfpv4", MD_CPU_ARM_ELF_HWCAP_VFPv4 }, - { "idiva", MD_CPU_ARM_ELF_HWCAP_IDIVA }, - { "idivt", MD_CPU_ARM_ELF_HWCAP_IDIVT }, - { "idiv", MD_CPU_ARM_ELF_HWCAP_IDIVA | MD_CPU_ARM_ELF_HWCAP_IDIVT }, -#elif defined(__aarch64__) - // No hwcaps on aarch64. -#endif - }; - - // processor_architecture should always be set, do this first - sys_info->processor_architecture = -#if defined(__aarch64__) - MD_CPU_ARCHITECTURE_ARM64; -#else - MD_CPU_ARCHITECTURE_ARM; -#endif - - // /proc/cpuinfo is not readable under various sandboxed environments - // (e.g. Android services with the android:isolatedProcess attribute) - // prepare for this by setting default values now, which will be - // returned when this happens. - // - // Note: Bogus values are used to distinguish between failures (to - // read /sys and /proc files) and really badly configured kernels. - sys_info->number_of_processors = 0; - sys_info->processor_level = 1U; // There is no ARMv1 - sys_info->processor_revision = 42; - sys_info->cpu.arm_cpu_info.cpuid = 0; - sys_info->cpu.arm_cpu_info.elf_hwcaps = 0; - - // Counting the number of CPUs involves parsing two sysfs files, - // because the content of /proc/cpuinfo will only mirror the number - // of 'online' cores, and thus will vary with time. - // See http://www.kernel.org/doc/Documentation/cputopology.txt - { - CpuSet cpus_present; - CpuSet cpus_possible; - - int fd = sys_open("/sys/devices/system/cpu/present", O_RDONLY, 0); - if (fd >= 0) { - cpus_present.ParseSysFile(fd); - sys_close(fd); - - fd = sys_open("/sys/devices/system/cpu/possible", O_RDONLY, 0); - if (fd >= 0) { - cpus_possible.ParseSysFile(fd); - sys_close(fd); - - cpus_present.IntersectWith(cpus_possible); - int cpu_count = cpus_present.GetCount(); - if (cpu_count > 255) - cpu_count = 255; - sys_info->number_of_processors = static_cast(cpu_count); - } - } - } - - // Parse /proc/cpuinfo to reconstruct the CPUID value, as well - // as the ELF hwcaps field. For the latter, it would be easier to - // read /proc/self/auxv but unfortunately, this file is not always - // readable from regular Android applications on later versions - // (>= 4.1) of the Android platform. - const int fd = sys_open("/proc/cpuinfo", O_RDONLY, 0); - if (fd < 0) { - // Do not return false here to allow the minidump generation - // to happen properly. - return true; - } - - { - PageAllocator allocator; - ProcCpuInfoReader* const reader = - new(allocator) ProcCpuInfoReader(fd); - const char* field; - while (reader->GetNextField(&field)) { - for (size_t i = 0; - i < sizeof(cpu_id_entries)/sizeof(cpu_id_entries[0]); - ++i) { - const CpuIdEntry* entry = &cpu_id_entries[i]; - if (my_strcmp(entry->field, field) != 0) - continue; - uintptr_t result = 0; - const char* value = reader->GetValue(); - const char* p = value; - if (value[0] == '0' && value[1] == 'x') { - p = my_read_hex_ptr(&result, value+2); - } else if (entry->format == 'x') { - p = my_read_hex_ptr(&result, value); - } else { - p = my_read_decimal_ptr(&result, value); - } - if (p == value) - continue; - - result &= (1U << entry->bit_length)-1; - result <<= entry->bit_lshift; - sys_info->cpu.arm_cpu_info.cpuid |= - static_cast(result); - } -#if defined(__arm__) - // Get the architecture version from the "Processor" field. - // Note that it is also available in the "CPU architecture" field, - // however, some existing kernels are misconfigured and will report - // invalid values here (e.g. 6, while the CPU is ARMv7-A based). - // The "Processor" field doesn't have this issue. - if (!my_strcmp(field, "Processor")) { - size_t value_len; - const char* value = reader->GetValueAndLen(&value_len); - // Expected format: (v) - // Where is some text like "ARMv7 Processor rev 2" - // and is a decimal corresponding to the ARM - // architecture number. is either 'l' or 'b' - // and corresponds to the endianess, it is ignored here. - while (value_len > 0 && my_isspace(value[value_len-1])) - value_len--; - - size_t nn = value_len; - while (nn > 0 && value[nn-1] != '(') - nn--; - if (nn > 0 && value[nn] == 'v') { - uintptr_t arch_level = 5; - my_read_decimal_ptr(&arch_level, value + nn + 1); - sys_info->processor_level = static_cast(arch_level); - } - } -#elif defined(__aarch64__) - // The aarch64 architecture does not provide the architecture level - // in the Processor field, so we instead check the "CPU architecture" - // field. - if (!my_strcmp(field, "CPU architecture")) { - uintptr_t arch_level = 0; - const char* value = reader->GetValue(); - const char* p = value; - p = my_read_decimal_ptr(&arch_level, value); - if (p == value) - continue; - sys_info->processor_level = static_cast(arch_level); - } -#endif - // Rebuild the ELF hwcaps from the 'Features' field. - if (!my_strcmp(field, "Features")) { - size_t value_len; - const char* value = reader->GetValueAndLen(&value_len); - - // Parse each space-separated tag. - while (value_len > 0) { - const char* tag = value; - size_t tag_len = value_len; - const char* p = my_strchr(tag, ' '); - if (p != NULL) { - tag_len = static_cast(p - tag); - value += tag_len + 1; - value_len -= tag_len + 1; - } else { - tag_len = strlen(tag); - value_len = 0; - } - for (size_t i = 0; - i < sizeof(cpu_features_entries)/ - sizeof(cpu_features_entries[0]); - ++i) { - const CpuFeaturesEntry* entry = &cpu_features_entries[i]; - if (tag_len == strlen(entry->tag) && - !memcmp(tag, entry->tag, tag_len)) { - sys_info->cpu.arm_cpu_info.elf_hwcaps |= entry->hwcaps; - break; - } - } - } - } - } - sys_close(fd); - } - - return true; - } -#else -# error "Unsupported CPU" -#endif - - bool WriteFile(MDLocationDescriptor* result, const char* filename) { - const int fd = sys_open(filename, O_RDONLY, 0); - if (fd < 0) - return false; - - // We can't stat the files because several of the files that we want to - // read are kernel seqfiles, which always have a length of zero. So we have - // to read as much as we can into a buffer. - static const unsigned kBufSize = 1024 - 2*sizeof(void*); - struct Buffers { - Buffers* next; - size_t len; - uint8_t data[kBufSize]; - } *buffers = reinterpret_cast(Alloc(sizeof(Buffers))); - buffers->next = NULL; - buffers->len = 0; - - size_t total = 0; - for (Buffers* bufptr = buffers;;) { - ssize_t r; - do { - r = sys_read(fd, &bufptr->data[bufptr->len], kBufSize - bufptr->len); - } while (r == -1 && errno == EINTR); - - if (r < 1) - break; - - total += r; - bufptr->len += r; - if (bufptr->len == kBufSize) { - bufptr->next = reinterpret_cast(Alloc(sizeof(Buffers))); - bufptr = bufptr->next; - bufptr->next = NULL; - bufptr->len = 0; - } - } - sys_close(fd); - - if (!total) - return false; - - UntypedMDRVA memory(&minidump_writer_); - if (!memory.Allocate(total)) - return false; - for (MDRVA pos = memory.position(); buffers; buffers = buffers->next) { - // Check for special case of a zero-length buffer. This should only - // occur if a file's size happens to be a multiple of the buffer's - // size, in which case the final sys_read() will have resulted in - // zero bytes being read after the final buffer was just allocated. - if (buffers->len == 0) { - // This can only occur with final buffer. - assert(buffers->next == NULL); - continue; - } - memory.Copy(pos, &buffers->data, buffers->len); - pos += buffers->len; - } - *result = memory.location(); - return true; - } - - bool WriteOSInformation(MDRawSystemInfo* sys_info) { -#if defined(__ANDROID__) - sys_info->platform_id = MD_OS_ANDROID; -#else - sys_info->platform_id = MD_OS_LINUX; -#endif - - struct utsname uts; - if (uname(&uts)) - return false; - - static const size_t buf_len = 512; - char buf[buf_len] = {0}; - size_t space_left = buf_len - 1; - const char* info_table[] = { - uts.sysname, - uts.release, - uts.version, - uts.machine, - NULL - }; - bool first_item = true; - for (const char** cur_info = info_table; *cur_info; cur_info++) { - static const char separator[] = " "; - size_t separator_len = sizeof(separator) - 1; - size_t info_len = my_strlen(*cur_info); - if (info_len == 0) - continue; - - if (space_left < info_len + (first_item ? 0 : separator_len)) - break; - - if (!first_item) { - my_strlcat(buf, separator, sizeof(buf)); - space_left -= separator_len; - } - - first_item = false; - my_strlcat(buf, *cur_info, sizeof(buf)); - space_left -= info_len; - } - - MDLocationDescriptor location; - if (!minidump_writer_.WriteString(buf, 0, &location)) - return false; - sys_info->csd_version_rva = location.rva; - - return true; - } - - bool WriteProcFile(MDLocationDescriptor* result, pid_t pid, - const char* filename) { - char buf[NAME_MAX]; - if (!dumper_->BuildProcPath(buf, pid, filename)) - return false; - return WriteFile(result, buf); - } - - // Only one of the 2 member variables below should be set to a valid value. - const int fd_; // File descriptor where the minidum should be written. - const char* path_; // Path to the file where the minidum should be written. - - const struct ucontext* const ucontext_; // also from the signal handler -#if !defined(__ARM_EABI__) && !defined(__mips__) - const google_breakpad::fpstate_t* const float_state_; // ditto -#endif - LinuxDumper* dumper_; - MinidumpFileWriter minidump_writer_; - off_t minidump_size_limit_; - MDLocationDescriptor crashing_thread_context_; - // Blocks of memory written to the dump. These are all currently - // written while writing the thread list stream, but saved here - // so a memory list stream can be written afterwards. - wasteful_vector memory_blocks_; - // Additional information about some mappings provided by the caller. - const MappingList& mapping_list_; - // Additional memory regions to be included in the dump, - // provided by the caller. - const AppMemoryList& app_memory_list_; -}; - - -bool WriteMinidumpImpl(const char* minidump_path, - int minidump_fd, - off_t minidump_size_limit, - pid_t crashing_process, - const void* blob, size_t blob_size, - const MappingList& mappings, - const AppMemoryList& appmem) { - LinuxPtraceDumper dumper(crashing_process); - const ExceptionHandler::CrashContext* context = NULL; - if (blob) { - if (blob_size != sizeof(ExceptionHandler::CrashContext)) - return false; - context = reinterpret_cast(blob); - dumper.set_crash_address( - reinterpret_cast(context->siginfo.si_addr)); - dumper.set_crash_signal(context->siginfo.si_signo); - dumper.set_crash_thread(context->tid); - } - MinidumpWriter writer(minidump_path, minidump_fd, context, mappings, - appmem, &dumper); - // Set desired limit for file size of minidump (-1 means no limit). - writer.set_minidump_size_limit(minidump_size_limit); - if (!writer.Init()) - return false; - return writer.Dump(); -} - -} // namespace - -namespace google_breakpad { - -bool WriteMinidump(const char* minidump_path, pid_t crashing_process, - const void* blob, size_t blob_size) { - return WriteMinidumpImpl(minidump_path, -1, -1, - crashing_process, blob, blob_size, - MappingList(), AppMemoryList()); -} - -bool WriteMinidump(int minidump_fd, pid_t crashing_process, - const void* blob, size_t blob_size) { - return WriteMinidumpImpl(NULL, minidump_fd, -1, - crashing_process, blob, blob_size, - MappingList(), AppMemoryList()); -} - -bool WriteMinidump(const char* minidump_path, pid_t process, - pid_t process_blamed_thread) { - LinuxPtraceDumper dumper(process); - // MinidumpWriter will set crash address - dumper.set_crash_signal(MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED); - dumper.set_crash_thread(process_blamed_thread); - MinidumpWriter writer(minidump_path, -1, NULL, MappingList(), - AppMemoryList(), &dumper); - if (!writer.Init()) - return false; - return writer.Dump(); -} - -bool WriteMinidump(const char* minidump_path, pid_t crashing_process, - const void* blob, size_t blob_size, - const MappingList& mappings, - const AppMemoryList& appmem) { - return WriteMinidumpImpl(minidump_path, -1, -1, crashing_process, - blob, blob_size, - mappings, appmem); -} - -bool WriteMinidump(int minidump_fd, pid_t crashing_process, - const void* blob, size_t blob_size, - const MappingList& mappings, - const AppMemoryList& appmem) { - return WriteMinidumpImpl(NULL, minidump_fd, -1, crashing_process, - blob, blob_size, - mappings, appmem); -} - -bool WriteMinidump(const char* minidump_path, off_t minidump_size_limit, - pid_t crashing_process, - const void* blob, size_t blob_size, - const MappingList& mappings, - const AppMemoryList& appmem) { - return WriteMinidumpImpl(minidump_path, -1, minidump_size_limit, - crashing_process, blob, blob_size, - mappings, appmem); -} - -bool WriteMinidump(int minidump_fd, off_t minidump_size_limit, - pid_t crashing_process, - const void* blob, size_t blob_size, - const MappingList& mappings, - const AppMemoryList& appmem) { - return WriteMinidumpImpl(NULL, minidump_fd, minidump_size_limit, - crashing_process, blob, blob_size, - mappings, appmem); -} - -bool WriteMinidump(const char* filename, - const MappingList& mappings, - const AppMemoryList& appmem, - LinuxDumper* dumper) { - MinidumpWriter writer(filename, -1, NULL, mappings, appmem, dumper); - if (!writer.Init()) - return false; - return writer.Dump(); -} - -} // namespace google_breakpad diff --git a/TMessagesProj/jni/breakpad/client/linux/minidump_writer/minidump_writer.h b/TMessagesProj/jni/breakpad/client/linux/minidump_writer/minidump_writer.h deleted file mode 100644 index d13fb120b..000000000 --- a/TMessagesProj/jni/breakpad/client/linux/minidump_writer/minidump_writer.h +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) 2009, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_ -#define CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_ - -#include -#include -#include -#include - -#include -#include - -#include "client/linux/minidump_writer/linux_dumper.h" -#include "google_breakpad/common/minidump_format.h" - -namespace google_breakpad { - -class ExceptionHandler; - -#if defined(__aarch64__) -typedef struct fpsimd_context fpstate_t; -#elif !defined(__ARM_EABI__) && !defined(__mips__) -typedef struct _libc_fpstate fpstate_t; -#endif - -// These entries store a list of memory regions that the client wants included -// in the minidump. -struct AppMemory { - void* ptr; - size_t length; - - bool operator==(const struct AppMemory& other) const { - return ptr == other.ptr; - } - - bool operator==(const void* other) const { - return ptr == other; - } -}; -typedef std::list AppMemoryList; - -// Writes a minidump to the filesystem. These functions do not malloc nor use -// libc functions which may. Thus, it can be used in contexts where the state -// of the heap may be corrupt. -// minidump_path: the path to the file to write to. This is opened O_EXCL and -// fails open fails. -// crashing_process: the pid of the crashing process. This must be trusted. -// blob: a blob of data from the crashing process. See exception_handler.h -// blob_size: the length of |blob|, in bytes -// -// Returns true iff successful. -bool WriteMinidump(const char* minidump_path, pid_t crashing_process, - const void* blob, size_t blob_size); -// Same as above but takes an open file descriptor instead of a path. -bool WriteMinidump(int minidump_fd, pid_t crashing_process, - const void* blob, size_t blob_size); - -// Alternate form of WriteMinidump() that works with processes that -// are not expected to have crashed. If |process_blamed_thread| is -// meaningful, it will be the one from which a crash signature is -// extracted. It is not expected that this function will be called -// from a compromised context, but it is safe to do so. -bool WriteMinidump(const char* minidump_path, pid_t process, - pid_t process_blamed_thread); - -// These overloads also allow passing a list of known mappings and -// a list of additional memory regions to be included in the minidump. -bool WriteMinidump(const char* minidump_path, pid_t crashing_process, - const void* blob, size_t blob_size, - const MappingList& mappings, - const AppMemoryList& appdata); -bool WriteMinidump(int minidump_fd, pid_t crashing_process, - const void* blob, size_t blob_size, - const MappingList& mappings, - const AppMemoryList& appdata); - -// These overloads also allow passing a file size limit for the minidump. -bool WriteMinidump(const char* minidump_path, off_t minidump_size_limit, - pid_t crashing_process, - const void* blob, size_t blob_size, - const MappingList& mappings, - const AppMemoryList& appdata); -bool WriteMinidump(int minidump_fd, off_t minidump_size_limit, - pid_t crashing_process, - const void* blob, size_t blob_size, - const MappingList& mappings, - const AppMemoryList& appdata); - -bool WriteMinidump(const char* filename, - const MappingList& mappings, - const AppMemoryList& appdata, - LinuxDumper* dumper); - -} // namespace google_breakpad - -#endif // CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_ diff --git a/TMessagesProj/jni/breakpad/client/linux/minidump_writer/proc_cpuinfo_reader.h b/TMessagesProj/jni/breakpad/client/linux/minidump_writer/proc_cpuinfo_reader.h deleted file mode 100644 index d9461bf30..000000000 --- a/TMessagesProj/jni/breakpad/client/linux/minidump_writer/proc_cpuinfo_reader.h +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (c) 2013, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef CLIENT_LINUX_MINIDUMP_WRITER_PROC_CPUINFO_READER_H_ -#define CLIENT_LINUX_MINIDUMP_WRITER_PROC_CPUINFO_READER_H_ - -#include -#include -#include - -#include "client/linux/minidump_writer/line_reader.h" -#include "common/linux/linux_libc_support.h" -#include "third_party/lss/linux_syscall_support.h" - -namespace google_breakpad { - -// A class for reading /proc/cpuinfo without using fopen/fgets or other -// functions which may allocate memory. -class ProcCpuInfoReader { -public: - ProcCpuInfoReader(int fd) - : line_reader_(fd), pop_count_(-1) { - } - - // Return the next field name, or NULL in case of EOF. - // field: (output) Pointer to zero-terminated field name. - // Returns true on success, or false on EOF or error (line too long). - bool GetNextField(const char** field) { - for (;;) { - const char* line; - unsigned line_len; - - // Try to read next line. - if (pop_count_ >= 0) { - line_reader_.PopLine(pop_count_); - pop_count_ = -1; - } - - if (!line_reader_.GetNextLine(&line, &line_len)) - return false; - - pop_count_ = static_cast(line_len); - - const char* line_end = line + line_len; - - // Expected format: + ':' - // Note that: - // - empty lines happen. - // - can contain spaces. - // - some fields have an empty - char* sep = static_cast(my_memchr(line, ':', line_len)); - if (sep == NULL) - continue; - - // Record the value. Skip leading space after the column to get - // its start. - const char* val = sep+1; - while (val < line_end && my_isspace(*val)) - val++; - - value_ = val; - value_len_ = static_cast(line_end - val); - - // Remove trailing spaces before the column to properly 0-terminate - // the field name. - while (sep > line && my_isspace(sep[-1])) - sep--; - - if (sep == line) - continue; - - // zero-terminate field name. - *sep = '\0'; - - *field = line; - return true; - } - } - - // Return the field value. This must be called after a succesful - // call to GetNextField(). - const char* GetValue() { - assert(value_); - return value_; - } - - // Same as GetValue(), but also returns the length in characters of - // the value. - const char* GetValueAndLen(size_t* length) { - assert(value_); - *length = value_len_; - return value_; - } - -private: - LineReader line_reader_; - int pop_count_; - const char* value_; - size_t value_len_; -}; - -} // namespace google_breakpad - -#endif // CLIENT_LINUX_MINIDUMP_WRITER_PROC_CPUINFO_READER_H_ diff --git a/TMessagesProj/jni/breakpad/client/minidump_file_writer-inl.h b/TMessagesProj/jni/breakpad/client/minidump_file_writer-inl.h deleted file mode 100644 index 0e12e00b6..000000000 --- a/TMessagesProj/jni/breakpad/client/minidump_file_writer-inl.h +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) 2006, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// minidump_file_writer-inl.h: Minidump file writer implementation. -// -// See minidump_file_writer.h for documentation. - -#ifndef CLIENT_MINIDUMP_FILE_WRITER_INL_H__ -#define CLIENT_MINIDUMP_FILE_WRITER_INL_H__ - -#include - -#include "client/minidump_file_writer.h" -#include "google_breakpad/common/minidump_size.h" - -namespace google_breakpad { - -template -inline bool TypedMDRVA::Allocate() { - allocation_state_ = SINGLE_OBJECT; - return UntypedMDRVA::Allocate(minidump_size::size()); -} - -template -inline bool TypedMDRVA::Allocate(size_t additional) { - allocation_state_ = SINGLE_OBJECT; - return UntypedMDRVA::Allocate(minidump_size::size() + additional); -} - -template -inline bool TypedMDRVA::AllocateArray(size_t count) { - assert(count); - allocation_state_ = ARRAY; - return UntypedMDRVA::Allocate(minidump_size::size() * count); -} - -template -inline bool TypedMDRVA::AllocateObjectAndArray(size_t count, - size_t length) { - assert(count && length); - allocation_state_ = SINGLE_OBJECT_WITH_ARRAY; - return UntypedMDRVA::Allocate(minidump_size::size() + count * length); -} - -template -inline bool TypedMDRVA::CopyIndex(unsigned int index, MDType *item) { - assert(allocation_state_ == ARRAY); - return writer_->Copy( - static_cast(position_ + index * minidump_size::size()), - item, minidump_size::size()); -} - -template -inline bool TypedMDRVA::CopyIndexAfterObject(unsigned int index, - const void *src, - size_t length) { - assert(allocation_state_ == SINGLE_OBJECT_WITH_ARRAY); - return writer_->Copy( - static_cast(position_ + minidump_size::size() - + index * length), - src, length); -} - -template -inline bool TypedMDRVA::Flush() { - return writer_->Copy(position_, &data_, minidump_size::size()); -} - -} // namespace google_breakpad - -#endif // CLIENT_MINIDUMP_FILE_WRITER_INL_H__ diff --git a/TMessagesProj/jni/breakpad/client/minidump_file_writer.cc b/TMessagesProj/jni/breakpad/client/minidump_file_writer.cc deleted file mode 100644 index 9e9053353..000000000 --- a/TMessagesProj/jni/breakpad/client/minidump_file_writer.cc +++ /dev/null @@ -1,284 +0,0 @@ -// Copyright (c) 2006, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// minidump_file_writer.cc: Minidump file writer implementation. -// -// See minidump_file_writer.h for documentation. - -#include -#include -#include -#include -#include - -#include "client/minidump_file_writer-inl.h" -#include "common/linux/linux_libc_support.h" -#include "common/string_conversion.h" -#if defined(__linux__) && __linux__ -#include "third_party/lss/linux_syscall_support.h" -#endif - -namespace google_breakpad { - -const MDRVA MinidumpFileWriter::kInvalidMDRVA = static_cast(-1); - -MinidumpFileWriter::MinidumpFileWriter() - : file_(-1), - close_file_when_destroyed_(true), - position_(0), - size_(0) { -} - -MinidumpFileWriter::~MinidumpFileWriter() { - if (close_file_when_destroyed_) - Close(); -} - -bool MinidumpFileWriter::Open(const char *path) { - assert(file_ == -1); -#if defined(__linux__) && __linux__ - file_ = sys_open(path, O_WRONLY | O_CREAT | O_EXCL, 0600); -#else - file_ = open(path, O_WRONLY | O_CREAT | O_EXCL, 0600); -#endif - - return file_ != -1; -} - -void MinidumpFileWriter::SetFile(const int file) { - assert(file_ == -1); - file_ = file; - close_file_when_destroyed_ = false; -} - -bool MinidumpFileWriter::Close() { - bool result = true; - - if (file_ != -1) { - if (-1 == ftruncate(file_, position_)) { - return false; - } -#if defined(__linux__) && __linux__ - result = (sys_close(file_) == 0); -#else - result = (close(file_) == 0); -#endif - file_ = -1; - } - - return result; -} - -bool MinidumpFileWriter::CopyStringToMDString(const wchar_t *str, - unsigned int length, - TypedMDRVA *mdstring) { - bool result = true; - if (sizeof(wchar_t) == sizeof(uint16_t)) { - // Shortcut if wchar_t is the same size as MDString's buffer - result = mdstring->Copy(str, mdstring->get()->length); - } else { - uint16_t out[2]; - int out_idx = 0; - - // Copy the string character by character - while (length && result) { - UTF32ToUTF16Char(*str, out); - if (!out[0]) - return false; - - // Process one character at a time - --length; - ++str; - - // Append the one or two UTF-16 characters. The first one will be non- - // zero, but the second one may be zero, depending on the conversion from - // UTF-32. - int out_count = out[1] ? 2 : 1; - size_t out_size = sizeof(uint16_t) * out_count; - result = mdstring->CopyIndexAfterObject(out_idx, out, out_size); - out_idx += out_count; - } - } - return result; -} - -bool MinidumpFileWriter::CopyStringToMDString(const char *str, - unsigned int length, - TypedMDRVA *mdstring) { - bool result = true; - uint16_t out[2]; - int out_idx = 0; - - // Copy the string character by character - while (length && result) { - int conversion_count = UTF8ToUTF16Char(str, length, out); - if (!conversion_count) - return false; - - // Move the pointer along based on the nubmer of converted characters - length -= conversion_count; - str += conversion_count; - - // Append the one or two UTF-16 characters - int out_count = out[1] ? 2 : 1; - size_t out_size = sizeof(uint16_t) * out_count; - result = mdstring->CopyIndexAfterObject(out_idx, out, out_size); - out_idx += out_count; - } - return result; -} - -template -bool MinidumpFileWriter::WriteStringCore(const CharType *str, - unsigned int length, - MDLocationDescriptor *location) { - assert(str); - assert(location); - // Calculate the mdstring length by either limiting to |length| as passed in - // or by finding the location of the NULL character. - unsigned int mdstring_length = 0; - if (!length) - length = INT_MAX; - for (; mdstring_length < length && str[mdstring_length]; ++mdstring_length) - ; - - // Allocate the string buffer - TypedMDRVA mdstring(this); - if (!mdstring.AllocateObjectAndArray(mdstring_length + 1, sizeof(uint16_t))) - return false; - - // Set length excluding the NULL and copy the string - mdstring.get()->length = - static_cast(mdstring_length * sizeof(uint16_t)); - bool result = CopyStringToMDString(str, mdstring_length, &mdstring); - - // NULL terminate - if (result) { - uint16_t ch = 0; - result = mdstring.CopyIndexAfterObject(mdstring_length, &ch, sizeof(ch)); - - if (result) - *location = mdstring.location(); - } - - return result; -} - -bool MinidumpFileWriter::WriteString(const wchar_t *str, unsigned int length, - MDLocationDescriptor *location) { - return WriteStringCore(str, length, location); -} - -bool MinidumpFileWriter::WriteString(const char *str, unsigned int length, - MDLocationDescriptor *location) { - return WriteStringCore(str, length, location); -} - -bool MinidumpFileWriter::WriteMemory(const void *src, size_t size, - MDMemoryDescriptor *output) { - assert(src); - assert(output); - UntypedMDRVA mem(this); - - if (!mem.Allocate(size)) - return false; - if (!mem.Copy(src, mem.size())) - return false; - - output->start_of_memory_range = reinterpret_cast(src); - output->memory = mem.location(); - - return true; -} - -MDRVA MinidumpFileWriter::Allocate(size_t size) { - assert(size); - assert(file_ != -1); - size_t aligned_size = (size + 7) & ~7; // 64-bit alignment - - if (position_ + aligned_size > size_) { - size_t growth = aligned_size; - size_t minimal_growth = getpagesize(); - - // Ensure that the file grows by at least the size of a memory page - if (growth < minimal_growth) - growth = minimal_growth; - - size_t new_size = size_ + growth; - if (ftruncate(file_, new_size) != 0) - return kInvalidMDRVA; - - size_ = new_size; - } - - MDRVA current_position = position_; - position_ += static_cast(aligned_size); - - return current_position; -} - -bool MinidumpFileWriter::Copy(MDRVA position, const void *src, ssize_t size) { - assert(src); - assert(size); - assert(file_ != -1); - - // Ensure that the data will fit in the allocated space - if (static_cast(size + position) > size_) - return false; - - // Seek and write the data -#if defined(__linux__) && __linux__ - if (sys_lseek(file_, position, SEEK_SET) == static_cast(position)) { - if (sys_write(file_, src, size) == size) { -#else - if (lseek(file_, position, SEEK_SET) == static_cast(position)) { - if (write(file_, src, size) == size) { -#endif - return true; - } - } - - return false; -} - -bool UntypedMDRVA::Allocate(size_t size) { - assert(size_ == 0); - size_ = size; - position_ = writer_->Allocate(size_); - return position_ != MinidumpFileWriter::kInvalidMDRVA; -} - -bool UntypedMDRVA::Copy(MDRVA pos, const void *src, size_t size) { - assert(src); - assert(size); - assert(pos + size <= position_ + size_); - return writer_->Copy(pos, src, size); -} - -} // namespace google_breakpad diff --git a/TMessagesProj/jni/breakpad/client/minidump_file_writer.h b/TMessagesProj/jni/breakpad/client/minidump_file_writer.h deleted file mode 100644 index ce32b6d08..000000000 --- a/TMessagesProj/jni/breakpad/client/minidump_file_writer.h +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright (c) 2006, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// minidump_file_writer.h: Implements file-based minidump generation. It's -// intended to be used with the Google Breakpad open source crash handling -// project. - -#ifndef CLIENT_MINIDUMP_FILE_WRITER_H__ -#define CLIENT_MINIDUMP_FILE_WRITER_H__ - -#include - -#include "google_breakpad/common/minidump_format.h" - -namespace google_breakpad { - -class UntypedMDRVA; -template class TypedMDRVA; - -// The user of this class can Open() a file and add minidump streams, data, and -// strings using the definitions in minidump_format.h. Since this class is -// expected to be used in a situation where the current process may be -// damaged, it will not allocate heap memory. -// Sample usage: -// MinidumpFileWriter writer; -// writer.Open("/tmp/minidump.dmp"); -// TypedMDRVA header(&writer_); -// header.Allocate(); -// header->get()->signature = MD_HEADER_SIGNATURE; -// : -// writer.Close(); -// -// An alternative is to use SetFile and provide a file descriptor: -// MinidumpFileWriter writer; -// writer.SetFile(minidump_fd); -// TypedMDRVA header(&writer_); -// header.Allocate(); -// header->get()->signature = MD_HEADER_SIGNATURE; -// : -// writer.Close(); - -class MinidumpFileWriter { -public: - // Invalid MDRVA (Minidump Relative Virtual Address) - // returned on failed allocation - static const MDRVA kInvalidMDRVA; - - MinidumpFileWriter(); - ~MinidumpFileWriter(); - - // Open |path| as the destination of the minidump data. If |path| already - // exists, then Open() will fail. - // Return true on success, or false on failure. - bool Open(const char *path); - - // Sets the file descriptor |file| as the destination of the minidump data. - // Can be used as an alternative to Open() when a file descriptor is - // available. - // Note that |fd| is not closed when the instance of MinidumpFileWriter is - // destroyed. - void SetFile(const int file); - - // Close the current file (that was either created when Open was called, or - // specified with SetFile). - // Return true on success, or false on failure. - bool Close(); - - // Copy the contents of |str| to a MDString and write it to the file. - // |str| is expected to be either UTF-16 or UTF-32 depending on the size - // of wchar_t. - // Maximum |length| of characters to copy from |str|, or specify 0 to use the - // entire NULL terminated string. Copying will stop at the first NULL. - // |location| the allocated location - // Return true on success, or false on failure - bool WriteString(const wchar_t *str, unsigned int length, - MDLocationDescriptor *location); - - // Same as above, except with |str| as a UTF-8 string - bool WriteString(const char *str, unsigned int length, - MDLocationDescriptor *location); - - // Write |size| bytes starting at |src| into the current position. - // Return true on success and set |output| to position, or false on failure - bool WriteMemory(const void *src, size_t size, MDMemoryDescriptor *output); - - // Copies |size| bytes from |src| to |position| - // Return true on success, or false on failure - bool Copy(MDRVA position, const void *src, ssize_t size); - - // Return the current position for writing to the minidump - inline MDRVA position() const { return position_; } - - private: - friend class UntypedMDRVA; - - // Allocates an area of |size| bytes. - // Returns the position of the allocation, or kInvalidMDRVA if it was - // unable to allocate the bytes. - MDRVA Allocate(size_t size); - - // The file descriptor for the output file. - int file_; - - // Whether |file_| should be closed when the instance is destroyed. - bool close_file_when_destroyed_; - - // Current position in buffer - MDRVA position_; - - // Current allocated size - size_t size_; - - // Copy |length| characters from |str| to |mdstring|. These are distinct - // because the underlying MDString is a UTF-16 based string. The wchar_t - // variant may need to create a MDString that has more characters than the - // source |str|, whereas the UTF-8 variant may coalesce characters to form - // a single UTF-16 character. - bool CopyStringToMDString(const wchar_t *str, unsigned int length, - TypedMDRVA *mdstring); - bool CopyStringToMDString(const char *str, unsigned int length, - TypedMDRVA *mdstring); - - // The common templated code for writing a string - template - bool WriteStringCore(const CharType *str, unsigned int length, - MDLocationDescriptor *location); -}; - -// Represents an untyped allocated chunk -class UntypedMDRVA { - public: - explicit UntypedMDRVA(MinidumpFileWriter *writer) - : writer_(writer), - position_(writer->position()), - size_(0) {} - - // Allocates |size| bytes. Must not call more than once. - // Return true on success, or false on failure - bool Allocate(size_t size); - - // Returns the current position or kInvalidMDRVA if allocation failed - inline MDRVA position() const { return position_; } - - // Number of bytes allocated - inline size_t size() const { return size_; } - - // Return size and position - inline MDLocationDescriptor location() const { - MDLocationDescriptor location = { static_cast(size_), - position_ }; - return location; - } - - // Copy |size| bytes starting at |src| into the minidump at |position| - // Return true on success, or false on failure - bool Copy(MDRVA position, const void *src, size_t size); - - // Copy |size| bytes from |src| to the current position - inline bool Copy(const void *src, size_t size) { - return Copy(position_, src, size); - } - - protected: - // Writer we associate with - MinidumpFileWriter *writer_; - - // Position of the start of the data - MDRVA position_; - - // Allocated size - size_t size_; -}; - -// Represents a Minidump object chunk. Additional memory can be allocated at -// the end of the object as a: -// - single allocation -// - Array of MDType objects -// - A MDType object followed by an array -template -class TypedMDRVA : public UntypedMDRVA { - public: - // Constructs an unallocated MDRVA - explicit TypedMDRVA(MinidumpFileWriter *writer) - : UntypedMDRVA(writer), - data_(), - allocation_state_(UNALLOCATED) {} - - inline ~TypedMDRVA() { - // Ensure that the data_ object is written out - if (allocation_state_ != ARRAY) - Flush(); - } - - // Address of object data_ of MDType. This is not declared const as the - // typical usage will be to access the underlying |data_| object as to - // alter its contents. - MDType *get() { return &data_; } - - // Allocates minidump_size::size() bytes. - // Must not call more than once. - // Return true on success, or false on failure - bool Allocate(); - - // Allocates minidump_size::size() + |additional| bytes. - // Must not call more than once. - // Return true on success, or false on failure - bool Allocate(size_t additional); - - // Allocate an array of |count| elements of MDType. - // Must not call more than once. - // Return true on success, or false on failure - bool AllocateArray(size_t count); - - // Allocate an array of |count| elements of |size| after object of MDType - // Must not call more than once. - // Return true on success, or false on failure - bool AllocateObjectAndArray(size_t count, size_t size); - - // Copy |item| to |index| - // Must have been allocated using AllocateArray(). - // Return true on success, or false on failure - bool CopyIndex(unsigned int index, MDType *item); - - // Copy |size| bytes starting at |str| to |index| - // Must have been allocated using AllocateObjectAndArray(). - // Return true on success, or false on failure - bool CopyIndexAfterObject(unsigned int index, const void *src, size_t size); - - // Write data_ - bool Flush(); - - private: - enum AllocationState { - UNALLOCATED = 0, - SINGLE_OBJECT, - ARRAY, - SINGLE_OBJECT_WITH_ARRAY - }; - - MDType data_; - AllocationState allocation_state_; -}; - -} // namespace google_breakpad - -#endif // CLIENT_MINIDUMP_FILE_WRITER_H__ diff --git a/TMessagesProj/jni/breakpad/common/android/breakpad_getcontext.S b/TMessagesProj/jni/breakpad/common/android/breakpad_getcontext.S deleted file mode 100644 index fd6326adf..000000000 --- a/TMessagesProj/jni/breakpad/common/android/breakpad_getcontext.S +++ /dev/null @@ -1,489 +0,0 @@ -// Copyright (c) 2012, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// A minimalistic implementation of getcontext() to be used by -// Google Breakpad on Android. - -#include "common/android/ucontext_constants.h" - -/* int getcontext (ucontext_t *ucp) */ - -#if defined(__arm__) - - .text - .global breakpad_getcontext - .hidden breakpad_getcontext - .type breakpad_getcontext, #function - .align 0 - .fnstart -breakpad_getcontext: - - /* First, save r4-r11 */ - add r1, r0, #(MCONTEXT_GREGS_OFFSET + 4*4) - stm r1, {r4-r11} - - /* r12 is a scratch register, don't save it */ - - /* Save sp and lr explicitly. */ - /* - sp can't be stored with stmia in Thumb-2 */ - /* - STM instructions that store sp and pc are deprecated in ARM */ - str sp, [r0, #(MCONTEXT_GREGS_OFFSET + 13*4)] - str lr, [r0, #(MCONTEXT_GREGS_OFFSET + 14*4)] - - /* Save the caller's address in 'pc' */ - str lr, [r0, #(MCONTEXT_GREGS_OFFSET + 15*4)] - - /* Save ucontext_t* pointer across next call */ - mov r4, r0 - - /* Call sigprocmask(SIG_BLOCK, NULL, &(ucontext->uc_sigmask)) */ - mov r0, #0 /* SIG_BLOCK */ - mov r1, #0 /* NULL */ - add r2, r4, #UCONTEXT_SIGMASK_OFFSET - bl sigprocmask(PLT) - - /* Intentionally do not save the FPU state here. This is because on - * Linux/ARM, one should instead use ptrace(PTRACE_GETFPREGS) or - * ptrace(PTRACE_GETVFPREGS) to get it. - * - * Note that a real implementation of getcontext() would need to save - * this here to allow setcontext()/swapcontext() to work correctly. - */ - - /* Restore the values of r4 and lr */ - mov r0, r4 - ldr lr, [r0, #(MCONTEXT_GREGS_OFFSET + 14*4)] - ldr r4, [r0, #(MCONTEXT_GREGS_OFFSET + 4*4)] - - /* Return 0 */ - mov r0, #0 - bx lr - - .fnend - .size breakpad_getcontext, . - breakpad_getcontext - -#elif defined(__aarch64__) - -#define _NSIG 64 -#define __NR_rt_sigprocmask 135 - - .text - .global breakpad_getcontext - .hidden breakpad_getcontext - .type breakpad_getcontext, #function - .align 4 - .cfi_startproc -breakpad_getcontext: - - /* The saved context will return to the getcontext() call point - with a return value of 0 */ - str xzr, [x0, MCONTEXT_GREGS_OFFSET + 0 * REGISTER_SIZE] - - stp x18, x19, [x0, MCONTEXT_GREGS_OFFSET + 18 * REGISTER_SIZE] - stp x20, x21, [x0, MCONTEXT_GREGS_OFFSET + 20 * REGISTER_SIZE] - stp x22, x23, [x0, MCONTEXT_GREGS_OFFSET + 22 * REGISTER_SIZE] - stp x24, x25, [x0, MCONTEXT_GREGS_OFFSET + 24 * REGISTER_SIZE] - stp x26, x27, [x0, MCONTEXT_GREGS_OFFSET + 26 * REGISTER_SIZE] - stp x28, x29, [x0, MCONTEXT_GREGS_OFFSET + 28 * REGISTER_SIZE] - str x30, [x0, MCONTEXT_GREGS_OFFSET + 30 * REGISTER_SIZE] - - /* Place LR into the saved PC, this will ensure that when - switching to this saved context with setcontext() control - will pass back to the caller of getcontext(), we have - already arranged to return the appropriate return value in x0 - above. */ - str x30, [x0, MCONTEXT_PC_OFFSET] - - /* Save the current SP */ - mov x2, sp - str x2, [x0, MCONTEXT_SP_OFFSET] - - /* Initialize the pstate. */ - str xzr, [x0, MCONTEXT_PSTATE_OFFSET] - - /* Figure out where to place the first context extension - block. */ - add x2, x0, #MCONTEXT_EXTENSION_OFFSET - - /* Write the context extension fpsimd header. */ - mov w3, #(FPSIMD_MAGIC & 0xffff) - movk w3, #(FPSIMD_MAGIC >> 16), lsl #16 - str w3, [x2, #FPSIMD_CONTEXT_MAGIC_OFFSET] - mov w3, #FPSIMD_CONTEXT_SIZE - str w3, [x2, #FPSIMD_CONTEXT_SIZE_OFFSET] - - /* Fill in the FP SIMD context. */ - add x3, x2, #(FPSIMD_CONTEXT_VREGS_OFFSET + 8 * SIMD_REGISTER_SIZE) - stp d8, d9, [x3], #(2 * SIMD_REGISTER_SIZE) - stp d10, d11, [x3], #(2 * SIMD_REGISTER_SIZE) - stp d12, d13, [x3], #(2 * SIMD_REGISTER_SIZE) - stp d14, d15, [x3], #(2 * SIMD_REGISTER_SIZE) - - add x3, x2, FPSIMD_CONTEXT_FPSR_OFFSET - - mrs x4, fpsr - str w4, [x3] - - mrs x4, fpcr - str w4, [x3, FPSIMD_CONTEXT_FPCR_OFFSET - FPSIMD_CONTEXT_FPSR_OFFSET] - - /* Write the termination context extension header. */ - add x2, x2, #FPSIMD_CONTEXT_SIZE - - str xzr, [x2, #FPSIMD_CONTEXT_MAGIC_OFFSET] - str xzr, [x2, #FPSIMD_CONTEXT_SIZE_OFFSET] - - /* Grab the signal mask */ - /* rt_sigprocmask (SIG_BLOCK, NULL, &ucp->uc_sigmask, _NSIG8) */ - add x2, x0, #UCONTEXT_SIGMASK_OFFSET - mov x0, #0 /* SIG_BLOCK */ - mov x1, #0 /* NULL */ - mov x3, #(_NSIG / 8) - mov x8, #__NR_rt_sigprocmask - svc 0 - - /* Return x0 for success */ - mov x0, 0 - ret - - .cfi_endproc - .size breakpad_getcontext, . - breakpad_getcontext - -#elif defined(__i386__) - - .text - .global breakpad_getcontext - .hidden breakpad_getcontext - .align 4 - .type breakpad_getcontext, @function - -breakpad_getcontext: - - movl 4(%esp), %eax /* eax = uc */ - - /* Save register values */ - movl %ecx, MCONTEXT_ECX_OFFSET(%eax) - movl %edx, MCONTEXT_EDX_OFFSET(%eax) - movl %ebx, MCONTEXT_EBX_OFFSET(%eax) - movl %edi, MCONTEXT_EDI_OFFSET(%eax) - movl %esi, MCONTEXT_ESI_OFFSET(%eax) - movl %ebp, MCONTEXT_EBP_OFFSET(%eax) - - movl (%esp), %edx /* return address */ - lea 4(%esp), %ecx /* exclude return address from stack */ - mov %edx, MCONTEXT_EIP_OFFSET(%eax) - mov %ecx, MCONTEXT_ESP_OFFSET(%eax) - - xorl %ecx, %ecx - movw %fs, %cx - mov %ecx, MCONTEXT_FS_OFFSET(%eax) - - movl $0, MCONTEXT_EAX_OFFSET(%eax) - - /* Save floating point state to fpregstate, then update - * the fpregs pointer to point to it */ - leal UCONTEXT_FPREGS_MEM_OFFSET(%eax), %ecx - fnstenv (%ecx) - fldenv (%ecx) - mov %ecx, UCONTEXT_FPREGS_OFFSET(%eax) - - /* Save signal mask: sigprocmask(SIGBLOCK, NULL, &uc->uc_sigmask) */ - leal UCONTEXT_SIGMASK_OFFSET(%eax), %edx - xorl %ecx, %ecx - push %edx /* &uc->uc_sigmask */ - push %ecx /* NULL */ - push %ecx /* SIGBLOCK == 0 on i386 */ - call sigprocmask@PLT - addl $12, %esp - - movl $0, %eax - ret - - .size breakpad_getcontext, . - breakpad_getcontext - -#elif defined(__mips__) - -// This implementation is inspired by implementation of getcontext in glibc. -#if _MIPS_SIM == _ABIO32 -#include -#include -#include -#else -#include -#include -#endif - -// from asm/asm.h -#if _MIPS_SIM == _ABIO32 -#define ALSZ 7 -#define ALMASK ~7 -#define SZREG 4 -#else // _MIPS_SIM != _ABIO32 -#define ALSZ 15 -#define ALMASK ~15 -#define SZREG 8 -#endif - -#include // for __NR_rt_sigprocmask - -#define _NSIG8 128 / 8 -#define SIG_BLOCK 1 - - - .text -LOCALS_NUM = 1 // save gp on stack -FRAME_SIZE = ((LOCALS_NUM * SZREG) + ALSZ) & ALMASK - -GP_FRAME_OFFSET = FRAME_SIZE - (1 * SZREG) -MCONTEXT_REG_SIZE = 8 - -#if _MIPS_SIM == _ABIO32 - -NESTED (breakpad_getcontext, FRAME_SIZE, ra) - .mask 0x00000000, 0 - .fmask 0x00000000, 0 - - .set noreorder - .cpload t9 - .set reorder - - move a2, sp -#define _SP a2 - - addiu sp, -FRAME_SIZE - .cprestore GP_FRAME_OFFSET - - sw s0, (16 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) - sw s1, (17 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) - sw s2, (18 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) - sw s3, (19 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) - sw s4, (20 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) - sw s5, (21 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) - sw s6, (22 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) - sw s7, (23 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) - sw _SP, (29 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) - sw fp, (30 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) - sw ra, (31 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) - sw ra, MCONTEXT_PC_OFFSET(a0) - -#ifdef __mips_hard_float - s.d fs0, (20 * MCONTEXT_REG_SIZE + MCONTEXT_FPREGS_OFFSET)(a0) - s.d fs1, (22 * MCONTEXT_REG_SIZE + MCONTEXT_FPREGS_OFFSET)(a0) - s.d fs2, (24 * MCONTEXT_REG_SIZE + MCONTEXT_FPREGS_OFFSET)(a0) - s.d fs3, (26 * MCONTEXT_REG_SIZE + MCONTEXT_FPREGS_OFFSET)(a0) - s.d fs4, (28 * MCONTEXT_REG_SIZE + MCONTEXT_FPREGS_OFFSET)(a0) - s.d fs5, (30 * MCONTEXT_REG_SIZE + MCONTEXT_FPREGS_OFFSET)(a0) - - cfc1 v1, fcr31 - sw v1, MCONTEXT_FPC_CSR(a0) -#endif // __mips_hard_float - - /* rt_sigprocmask (SIG_BLOCK, NULL, &ucp->uc_sigmask, _NSIG8) */ - li a3, _NSIG8 - addu a2, a0, UCONTEXT_SIGMASK_OFFSET - move a1, zero - li a0, SIG_BLOCK - li v0, __NR_rt_sigprocmask - syscall - - addiu sp, FRAME_SIZE - jr ra - -END (breakpad_getcontext) -#else - -#ifndef NESTED -/* - * NESTED - declare nested routine entry point - */ -#define NESTED(symbol, framesize, rpc) \ - .globl symbol; \ - .align 2; \ - .type symbol,@function; \ - .ent symbol,0; \ -symbol: .frame sp, framesize, rpc; -#endif - -/* - * END - mark end of function - */ -#ifndef END -# define END(function) \ - .end function; \ - .size function,.-function -#endif - -/* int getcontext (ucontext_t *ucp) */ - -NESTED (breakpad_getcontext, FRAME_SIZE, ra) - .mask 0x10000000, 0 - .fmask 0x00000000, 0 - - move a2, sp -#define _SP a2 - move a3, gp -#define _GP a3 - - daddiu sp, -FRAME_SIZE - .cpsetup $25, GP_FRAME_OFFSET, breakpad_getcontext - - /* Store a magic flag. */ - li v1, 1 - sd v1, (0 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) /* zero */ - - sd s0, (16 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) - sd s1, (17 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) - sd s2, (18 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) - sd s3, (19 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) - sd s4, (20 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) - sd s5, (21 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) - sd s6, (22 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) - sd s7, (23 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) - sd _GP, (28 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) - sd _SP, (29 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) - sd s8, (30 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) - sd ra, (31 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) - sd ra, MCONTEXT_PC_OFFSET(a0) - -#ifdef __mips_hard_float - s.d $f24, (24 * MCONTEXT_REG_SIZE + MCONTEXT_FPREGS_OFFSET)(a0) - s.d $f25, (25 * MCONTEXT_REG_SIZE + MCONTEXT_FPREGS_OFFSET)(a0) - s.d $f26, (26 * MCONTEXT_REG_SIZE + MCONTEXT_FPREGS_OFFSET)(a0) - s.d $f27, (27 * MCONTEXT_REG_SIZE + MCONTEXT_FPREGS_OFFSET)(a0) - s.d $f28, (28 * MCONTEXT_REG_SIZE + MCONTEXT_FPREGS_OFFSET)(a0) - s.d $f29, (29 * MCONTEXT_REG_SIZE + MCONTEXT_FPREGS_OFFSET)(a0) - s.d $f30, (30 * MCONTEXT_REG_SIZE + MCONTEXT_FPREGS_OFFSET)(a0) - s.d $f31, (31 * MCONTEXT_REG_SIZE + MCONTEXT_FPREGS_OFFSET)(a0) - - cfc1 v1, $31 - sw v1, MCONTEXT_FPC_CSR(a0) -#endif /* __mips_hard_float */ - -/* rt_sigprocmask (SIG_BLOCK, NULL, &ucp->uc_sigmask, _NSIG8) */ - li a3, _NSIG8 - daddu a2, a0, UCONTEXT_SIGMASK_OFFSET - move a1, zero - li a0, SIG_BLOCK - - li v0, __NR_rt_sigprocmask - syscall - - .cpreturn - daddiu sp, FRAME_SIZE - move v0, zero - jr ra - -END (breakpad_getcontext) -#endif // _MIPS_SIM == _ABIO32 - -#elif defined(__x86_64__) -/* The x64 implementation of breakpad_getcontext was derived in part - from the implementation of libunwind which requires the following - notice. */ -/* libunwind - a platform-independent unwind library - Copyright (C) 2008 Google, Inc - Contributed by Paul Pluzhnikov - Copyright (C) 2010 Konstantin Belousov - -This file is part of libunwind. - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - - .text - .global breakpad_getcontext - .hidden breakpad_getcontext - .align 4 - .type breakpad_getcontext, @function - -breakpad_getcontext: - .cfi_startproc - - /* Callee saved: RBX, RBP, R12-R15 */ - movq %r12, MCONTEXT_GREGS_R12(%rdi) - movq %r13, MCONTEXT_GREGS_R13(%rdi) - movq %r14, MCONTEXT_GREGS_R14(%rdi) - movq %r15, MCONTEXT_GREGS_R15(%rdi) - movq %rbp, MCONTEXT_GREGS_RBP(%rdi) - movq %rbx, MCONTEXT_GREGS_RBX(%rdi) - - /* Save argument registers (not strictly needed, but setcontext - restores them, so don't restore garbage). */ - movq %r8, MCONTEXT_GREGS_R8(%rdi) - movq %r9, MCONTEXT_GREGS_R9(%rdi) - movq %rdi, MCONTEXT_GREGS_RDI(%rdi) - movq %rsi, MCONTEXT_GREGS_RSI(%rdi) - movq %rdx, MCONTEXT_GREGS_RDX(%rdi) - movq %rax, MCONTEXT_GREGS_RAX(%rdi) - movq %rcx, MCONTEXT_GREGS_RCX(%rdi) - - /* Save fp state (not needed, except for setcontext not - restoring garbage). */ - leaq MCONTEXT_FPREGS_MEM(%rdi),%r8 - movq %r8, MCONTEXT_FPREGS_PTR(%rdi) - fnstenv (%r8) - stmxcsr FPREGS_OFFSET_MXCSR(%r8) - - leaq 8(%rsp), %rax /* exclude this call. */ - movq %rax, MCONTEXT_GREGS_RSP(%rdi) - - movq 0(%rsp), %rax - movq %rax, MCONTEXT_GREGS_RIP(%rdi) - - /* Save signal mask: sigprocmask(SIGBLOCK, NULL, &uc->uc_sigmask) */ - leaq UCONTEXT_SIGMASK_OFFSET(%rdi), %rdx // arg3 - xorq %rsi, %rsi // arg2 NULL - xorq %rdi, %rdi // arg1 SIGBLOCK == 0 - call sigprocmask@PLT - - /* Always return 0 for success, even if sigprocmask failed. */ - xorl %eax, %eax - ret - .cfi_endproc - .size breakpad_getcontext, . - breakpad_getcontext - -#else -#error "This file has not been ported for your CPU!" -#endif diff --git a/TMessagesProj/jni/breakpad/common/android/include/elf.h b/TMessagesProj/jni/breakpad/common/android/include/elf.h deleted file mode 100644 index b2a28df44..000000000 --- a/TMessagesProj/jni/breakpad/common/android/include/elf.h +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright (c) 2012, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef GOOGLE_BREAKPAD_COMMON_ANDROID_INCLUDE_ELF_H -#define GOOGLE_BREAKPAD_COMMON_ANDROID_INCLUDE_ELF_H - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -// The Android provides BSD-based definitions for the ElfXX_Nhdr -// types -// always source-compatible with the GLibc/kernel ones. To overcome this -// issue without modifying a lot of code in Breakpad, use an ugly macro -// renaming trick with #include_next - -// Avoid conflict with BSD-based definition of ElfXX_Nhdr. -// Unfortunately, their field member names do not use a 'n_' prefix. -#define Elf32_Nhdr __bsd_Elf32_Nhdr -#define Elf64_Nhdr __bsd_Elf64_Nhdr - -// In case they are defined by the NDK version -#define Elf32_auxv_t __bionic_Elf32_auxv_t -#define Elf64_auxv_t __bionic_Elf64_auxv_t - -#define Elf32_Dyn __bionic_Elf32_Dyn -#define Elf64_Dyn __bionic_Elf64_Dyn - -#include_next - -#undef Elf32_Nhdr -#undef Elf64_Nhdr - -typedef struct { - Elf32_Word n_namesz; - Elf32_Word n_descsz; - Elf32_Word n_type; -} Elf32_Nhdr; - -typedef struct { - Elf64_Word n_namesz; - Elf64_Word n_descsz; - Elf64_Word n_type; -} Elf64_Nhdr; - -#undef Elf32_auxv_t -#undef Elf64_auxv_t - -typedef struct { - uint32_t a_type; - union { - uint32_t a_val; - } a_un; -} Elf32_auxv_t; - -typedef struct { - uint64_t a_type; - union { - uint64_t a_val; - } a_un; -} Elf64_auxv_t; - -#undef Elf32_Dyn -#undef Elf64_Dyn - -typedef struct { - Elf32_Sword d_tag; - union { - Elf32_Word d_val; - Elf32_Addr d_ptr; - } d_un; -} Elf32_Dyn; - -typedef struct { - Elf64_Sxword d_tag; - union { - Elf64_Xword d_val; - Elf64_Addr d_ptr; - } d_un; -} Elf64_Dyn; - - -// __WORDSIZE is GLibc-specific and used by Google Breakpad on Linux. -#ifndef __WORDSIZE -#if defined(__i386__) || defined(__ARM_EABI__) || defined(__mips__) -#define __WORDSIZE 32 -#elif defined(__x86_64__) || defined(__aarch64__) -#define __WORDSIZE 64 -#else -#error "Unsupported Android CPU ABI" -#endif -#endif - -// The Android headers don't always define this constant. -#ifndef EM_X86_64 -#define EM_X86_64 62 -#endif - -#ifndef EM_PPC64 -#define EM_PPC64 21 -#endif - -#ifndef EM_S390 -#define EM_S390 22 -#endif - -#if !defined(AT_SYSINFO_EHDR) -#define AT_SYSINFO_EHDR 33 -#endif - -#if !defined(NT_PRSTATUS) -#define NT_PRSTATUS 1 -#endif - -#if !defined(NT_PRPSINFO) -#define NT_PRPSINFO 3 -#endif - -#if !defined(NT_AUXV) -#define NT_AUXV 6 -#endif - -#if !defined(NT_PRXFPREG) -#define NT_PRXFPREG 0x46e62b7f -#endif - -#if !defined(NT_FPREGSET) -#define NT_FPREGSET 2 -#endif - -#if !defined(SHT_MIPS_DWARF) -#define SHT_MIPS_DWARF 0x7000001e -#endif - -#ifdef __cplusplus -} // extern "C" -#endif // __cplusplus - -#endif // GOOGLE_BREAKPAD_COMMON_ANDROID_INCLUDE_ELF_H diff --git a/TMessagesProj/jni/breakpad/common/android/include/link.h b/TMessagesProj/jni/breakpad/common/android/include/link.h deleted file mode 100644 index e7ff8e2d8..000000000 --- a/TMessagesProj/jni/breakpad/common/android/include/link.h +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2012, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef GOOGLE_BREAKPAD_ANDROID_INCLUDE_LINK_H -#define GOOGLE_BREAKPAD_ANDROID_INCLUDE_LINK_H - -/* Android doesn't provide all the data-structures required in its . - Provide custom version here. */ -#include_next - -// TODO(rmcilroy): Remove this file once the ndk is updated for other -// architectures - crbug.com/358831 -#if !defined(__aarch64__) && !defined(__x86_64__) && \ - !(defined(__mips__) && _MIPS_SIM == _ABI64) - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -struct r_debug { - int r_version; - struct link_map* r_map; - ElfW(Addr) r_brk; - enum { - RT_CONSISTENT, - RT_ADD, - RT_DELETE } r_state; - ElfW(Addr) r_ldbase; -}; - -struct link_map { - ElfW(Addr) l_addr; - char* l_name; - ElfW(Dyn)* l_ld; - struct link_map* l_next; - struct link_map* l_prev; -}; - -#ifdef __cplusplus -} // extern "C" -#endif // __cplusplus - -#endif // !defined(__aarch64__) && !defined(__x86_64__) - -#endif /* GOOGLE_BREAKPAD_ANDROID_INCLUDE_LINK_H */ diff --git a/TMessagesProj/jni/breakpad/common/android/include/sgidefs.h b/TMessagesProj/jni/breakpad/common/android/include/sgidefs.h deleted file mode 100644 index 33796dcf7..000000000 --- a/TMessagesProj/jni/breakpad/common/android/include/sgidefs.h +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2013, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef GOOGLE_BREAKPAD_ANDROID_INCLUDE_SGIDEFS_H -#define GOOGLE_BREAKPAD_ANDROID_INCLUDE_SGIDEFS_H - -#ifdef __mips__ - -// Android doesn't contain sgidefs.h, but does have which -// contains what we need. -#include - -#endif // __mips__ - -#endif // GOOGLE_BREAKPAD_ANDROID_INCLUDE_SGIDEFS_H diff --git a/TMessagesProj/jni/breakpad/common/android/include/stab.h b/TMessagesProj/jni/breakpad/common/android/include/stab.h deleted file mode 100644 index cd9290215..000000000 --- a/TMessagesProj/jni/breakpad/common/android/include/stab.h +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) 2012, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef GOOGLE_BREAKPAD_COMMON_ANDROID_INCLUDE_STAB_H -#define GOOGLE_BREAKPAD_COMMON_ANDROID_INCLUDE_STAB_H - -#include - -#ifdef __BIONIC_HAVE_STAB_H -#include -#else - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -#define _STAB_CODE_LIST \ - _STAB_CODE_DEF(UNDF,0x00) \ - _STAB_CODE_DEF(GSYM,0x20) \ - _STAB_CODE_DEF(FNAME,0x22) \ - _STAB_CODE_DEF(FUN,0x24) \ - _STAB_CODE_DEF(STSYM,0x26) \ - _STAB_CODE_DEF(LCSYM,0x28) \ - _STAB_CODE_DEF(MAIN,0x2a) \ - _STAB_CODE_DEF(PC,0x30) \ - _STAB_CODE_DEF(NSYMS,0x32) \ - _STAB_CODE_DEF(NOMAP,0x34) \ - _STAB_CODE_DEF(OBJ,0x38) \ - _STAB_CODE_DEF(OPT,0x3c) \ - _STAB_CODE_DEF(RSYM,0x40) \ - _STAB_CODE_DEF(M2C,0x42) \ - _STAB_CODE_DEF(SLINE,0x44) \ - _STAB_CODE_DEF(DSLINE,0x46) \ - _STAB_CODE_DEF(BSLINE,0x48) \ - _STAB_CODE_DEF(BROWS,0x48) \ - _STAB_CODE_DEF(DEFD,0x4a) \ - _STAB_CODE_DEF(EHDECL,0x50) \ - _STAB_CODE_DEF(MOD2,0x50) \ - _STAB_CODE_DEF(CATCH,0x54) \ - _STAB_CODE_DEF(SSYM,0x60) \ - _STAB_CODE_DEF(SO,0x64) \ - _STAB_CODE_DEF(LSYM,0x80) \ - _STAB_CODE_DEF(BINCL,0x82) \ - _STAB_CODE_DEF(SOL,0x84) \ - _STAB_CODE_DEF(PSYM,0xa0) \ - _STAB_CODE_DEF(EINCL,0xa2) \ - _STAB_CODE_DEF(ENTRY,0xa4) \ - _STAB_CODE_DEF(LBRAC,0xc0) \ - _STAB_CODE_DEF(EXCL,0xc2) \ - _STAB_CODE_DEF(SCOPE,0xc4) \ - _STAB_CODE_DEF(RBRAC,0xe0) \ - _STAB_CODE_DEF(BCOMM,0xe2) \ - _STAB_CODE_DEF(ECOMM,0xe4) \ - _STAB_CODE_DEF(ECOML,0xe8) \ - _STAB_CODE_DEF(NBTEXT,0xf0) \ - _STAB_CODE_DEF(NBDATA,0xf2) \ - _STAB_CODE_DEF(NBBSS,0xf4) \ - _STAB_CODE_DEF(NBSTS,0xf6) \ - _STAB_CODE_DEF(NBLCS,0xf8) \ - _STAB_CODE_DEF(LENG,0xfe) - -enum __stab_debug_code { -#define _STAB_CODE_DEF(x,y) N_##x = y, -_STAB_CODE_LIST -#undef _STAB_CODE_DEF -}; - -#ifdef __cplusplus -} // extern "C" -#endif // __cplusplus - -#endif // __BIONIC_HAVE_STAB_H - -#endif // GOOGLE_BREAKPAD_COMMON_ANDROID_INCLUDE_STAB_H diff --git a/TMessagesProj/jni/breakpad/common/android/include/sys/procfs.h b/TMessagesProj/jni/breakpad/common/android/include/sys/procfs.h deleted file mode 100644 index 27223ea34..000000000 --- a/TMessagesProj/jni/breakpad/common/android/include/sys/procfs.h +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) 2012, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef GOOGLE_BREAKPAD_COMMON_ANDROID_SYS_PROCFS_H -#define GOOGLE_BREAKPAD_COMMON_ANDROID_SYS_PROCFS_H - -#ifdef __BIONIC_HAVE_SYS_PROCFS_H - -#include_next - -#else - -#include -#include -#if defined (__mips__) -#include -#endif -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -#if defined(__x86_64__) || defined(__aarch64__) -typedef unsigned long long elf_greg_t; -#else -typedef unsigned long elf_greg_t; -#endif - -#ifdef __arm__ -#define ELF_NGREG (sizeof(struct user_regs) / sizeof(elf_greg_t)) -#elif defined(__aarch64__) -#define ELF_NGREG (sizeof(struct user_pt_regs) / sizeof(elf_greg_t)) -#elif defined(__mips__) -#define ELF_NGREG 45 -#else -#define ELF_NGREG (sizeof(struct user_regs_struct) / sizeof(elf_greg_t)) -#endif - -typedef elf_greg_t elf_gregset_t[ELF_NGREG]; - -struct elf_siginfo { - int si_signo; - int si_code; - int si_errno; -}; - -struct elf_prstatus { - struct elf_siginfo pr_info; - short pr_cursig; - unsigned long pr_sigpend; - unsigned long pr_sighold; - pid_t pr_pid; - pid_t pr_ppid; - pid_t pr_pgrp; - pid_t pd_sid; - struct timeval pr_utime; - struct timeval pr_stime; - struct timeval pr_cutime; - struct timeval pr_cstime; - elf_gregset_t pr_reg; - int pr_fpvalid; -}; - -#define ELF_PRARGSZ 80 - -struct elf_prpsinfo { - char pr_state; - char pr_sname; - char pr_zomb; - char pr_nice; - unsigned long pr_flags; -#ifdef __x86_64__ - unsigned int pr_uid; - unsigned int pr_gid; -#elif defined(__mips__) - unsigned long pr_uid; - unsigned long pr_gid; -#else - unsigned short pr_uid; - unsigned short pr_gid; -#endif - int pr_pid; - int pr_ppid; - int pr_pgrp; - int pr_sid; - char pr_fname[16]; - char pr_psargs[ELF_PRARGSZ]; -}; - -#ifdef __cplusplus -} // extern "C" -#endif // __cplusplus - -#endif // __BIONIC_HAVE_SYS_PROCFS_H - -#endif // GOOGLE_BREAKPAD_COMMON_ANDROID_SYS_PROCFS_H diff --git a/TMessagesProj/jni/breakpad/common/android/include/sys/signal.h b/TMessagesProj/jni/breakpad/common/android/include/sys/signal.h deleted file mode 100644 index 20c81e937..000000000 --- a/TMessagesProj/jni/breakpad/common/android/include/sys/signal.h +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2012, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef GOOGLE_BREAKPAD_COMMON_ANDROID_INCLUDE_SYS_SIGNAL_H -#define GOOGLE_BREAKPAD_COMMON_ANDROID_INCLUDE_SYS_SIGNAL_H - -#include - -#endif // GOOGLE_BREAKPAD_COMMON_ANDROID_INCLUDE_SYS_SIGNAL_H diff --git a/TMessagesProj/jni/breakpad/common/android/include/sys/user.h b/TMessagesProj/jni/breakpad/common/android/include/sys/user.h deleted file mode 100644 index 5f0360475..000000000 --- a/TMessagesProj/jni/breakpad/common/android/include/sys/user.h +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) 2012, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef GOOGLE_BREAKPAD_COMMON_ANDROID_INCLUDE_SYS_USER_H -#define GOOGLE_BREAKPAD_COMMON_ANDROID_INCLUDE_SYS_USER_H - -// The purpose of this file is to glue the mismatching headers (Android NDK vs -// glibc) and therefore avoid doing otherwise awkward #ifdefs in the code. -// The following quirks are currently handled by this file: -// - i386: Use the Android NDK but alias user_fxsr_struct > user_fpxregs_struct. -// - aarch64: Add missing user_regs_struct and user_fpsimd_struct structs. -// - Other platforms: Just use the Android NDK unchanged. - -// TODO(primiano): remove these changes after Chromium has stably rolled to -// an NDK with the appropriate fixes. - -#include_next - -#ifdef __i386__ -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus -typedef struct user_fxsr_struct user_fpxregs_struct; -#ifdef __cplusplus -} // extern "C" -#endif // __cplusplus -#endif // __i386__ - -#ifdef __aarch64__ -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus -struct user_regs_struct { - __u64 regs[31]; - __u64 sp; - __u64 pc; - __u64 pstate; -}; -struct user_fpsimd_struct { - __uint128_t vregs[32]; - __u32 fpsr; - __u32 fpcr; -}; -#ifdef __cplusplus -} // extern "C" -#endif // __cplusplus -#endif // __aarch64__ - -#endif // GOOGLE_BREAKPAD_COMMON_ANDROID_INCLUDE_SYS_USER_H diff --git a/TMessagesProj/jni/breakpad/common/android/include/ucontext.h b/TMessagesProj/jni/breakpad/common/android/include/ucontext.h deleted file mode 100644 index 29db8adee..000000000 --- a/TMessagesProj/jni/breakpad/common/android/include/ucontext.h +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) 2012, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef GOOGLE_BREAKPAD_COMMON_ANDROID_INCLUDE_UCONTEXT_H -#define GOOGLE_BREAKPAD_COMMON_ANDROID_INCLUDE_UCONTEXT_H - -#include - -#ifdef __BIONIC_UCONTEXT_H -#include -#else - -#include - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -// Provided by src/android/common/breakpad_getcontext.S -int breakpad_getcontext(ucontext_t* ucp); - -#define getcontext(x) breakpad_getcontext(x) - -#ifdef __cplusplus -} // extern "C" -#endif // __cplusplus - -#endif // __BIONIC_UCONTEXT_H - -#endif // GOOGLE_BREAKPAD_COMMON_ANDROID_INCLUDE_UCONTEXT_H diff --git a/TMessagesProj/jni/breakpad/common/android/ucontext_constants.h b/TMessagesProj/jni/breakpad/common/android/ucontext_constants.h deleted file mode 100644 index 1932d5739..000000000 --- a/TMessagesProj/jni/breakpad/common/android/ucontext_constants.h +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) 2012, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// This header can be included either from a C, C++ or Assembly file. -// Its purpose is to contain constants that must match the offsets of -// various fields in ucontext_t. -// -// They should match the definitions from -// src/common/android/include/sys/ucontext.h -// -// Used by src/common/android/breakpad_getcontext.S -// Tested by src/common/android/testing/breakpad_getcontext_unittest.cc - -#ifndef GOOGLEBREAKPAD_COMMON_ANDROID_UCONTEXT_CONSTANTS_H -#define GOOGLEBREAKPAD_COMMON_ANDROID_UCONTEXT_CONSTANTS_H - -#if defined(__arm__) - -#define MCONTEXT_GREGS_OFFSET 32 -#define UCONTEXT_SIGMASK_OFFSET 104 - -#elif defined(__aarch64__) - -#define UCONTEXT_SIGMASK_OFFSET 40 - -#define MCONTEXT_GREGS_OFFSET 184 -#define MCONTEXT_SP_OFFSET 432 -#define MCONTEXT_PC_OFFSET 440 -#define MCONTEXT_PSTATE_OFFSET 448 -#define MCONTEXT_EXTENSION_OFFSET 464 - -#define FPSIMD_MAGIC 0x46508001 - -#define FPSIMD_CONTEXT_MAGIC_OFFSET 0 -#define FPSIMD_CONTEXT_SIZE_OFFSET 4 -#define FPSIMD_CONTEXT_FPSR_OFFSET 8 -#define FPSIMD_CONTEXT_FPCR_OFFSET 12 -#define FPSIMD_CONTEXT_VREGS_OFFSET 16 -#define FPSIMD_CONTEXT_SIZE 528 - -#define REGISTER_SIZE 8 -#define SIMD_REGISTER_SIZE 16 - -#elif defined(__i386__) - -#define MCONTEXT_GREGS_OFFSET 20 -#define MCONTEXT_GS_OFFSET (MCONTEXT_GREGS_OFFSET + 0*4) -#define MCONTEXT_FS_OFFSET (MCONTEXT_GREGS_OFFSET + 1*4) -#define MCONTEXT_ES_OFFSET (MCONTEXT_GREGS_OFFSET + 2*4) -#define MCONTEXT_DS_OFFSET (MCONTEXT_GREGS_OFFSET + 3*4) -#define MCONTEXT_EDI_OFFSET (MCONTEXT_GREGS_OFFSET + 4*4) -#define MCONTEXT_ESI_OFFSET (MCONTEXT_GREGS_OFFSET + 5*4) -#define MCONTEXT_EBP_OFFSET (MCONTEXT_GREGS_OFFSET + 6*4) -#define MCONTEXT_ESP_OFFSET (MCONTEXT_GREGS_OFFSET + 7*4) -#define MCONTEXT_EBX_OFFSET (MCONTEXT_GREGS_OFFSET + 8*4) -#define MCONTEXT_EDX_OFFSET (MCONTEXT_GREGS_OFFSET + 9*4) -#define MCONTEXT_ECX_OFFSET (MCONTEXT_GREGS_OFFSET + 10*4) -#define MCONTEXT_EAX_OFFSET (MCONTEXT_GREGS_OFFSET + 11*4) -#define MCONTEXT_TRAPNO_OFFSET (MCONTEXT_GREGS_OFFSET + 12*4) -#define MCONTEXT_ERR_OFFSET (MCONTEXT_GREGS_OFFSET + 13*4) -#define MCONTEXT_EIP_OFFSET (MCONTEXT_GREGS_OFFSET + 14*4) -#define MCONTEXT_CS_OFFSET (MCONTEXT_GREGS_OFFSET + 15*4) -#define MCONTEXT_EFL_OFFSET (MCONTEXT_GREGS_OFFSET + 16*4) -#define MCONTEXT_UESP_OFFSET (MCONTEXT_GREGS_OFFSET + 17*4) -#define MCONTEXT_SS_OFFSET (MCONTEXT_GREGS_OFFSET + 18*4) - -#define UCONTEXT_SIGMASK_OFFSET 108 - -#define UCONTEXT_FPREGS_OFFSET 96 -#define UCONTEXT_FPREGS_MEM_OFFSET 116 - -#elif defined(__mips__) - -#if _MIPS_SIM == _ABIO32 -#define MCONTEXT_PC_OFFSET 32 -#define MCONTEXT_GREGS_OFFSET 40 -#define MCONTEXT_FPREGS_OFFSET 296 -#define MCONTEXT_FPC_CSR 556 -#define UCONTEXT_SIGMASK_OFFSET 616 -#else -#define MCONTEXT_GREGS_OFFSET 40 -#define MCONTEXT_FPREGS_OFFSET 296 -#define MCONTEXT_PC_OFFSET 616 -#define MCONTEXT_FPC_CSR 624 -#define UCONTEXT_SIGMASK_OFFSET 640 -#endif - -#elif defined(__x86_64__) - -#define MCONTEXT_GREGS_OFFSET 40 -#define UCONTEXT_SIGMASK_OFFSET 296 - -#define MCONTEXT_GREGS_R8 40 -#define MCONTEXT_GREGS_R9 48 -#define MCONTEXT_GREGS_R10 56 -#define MCONTEXT_GREGS_R11 64 -#define MCONTEXT_GREGS_R12 72 -#define MCONTEXT_GREGS_R13 80 -#define MCONTEXT_GREGS_R14 88 -#define MCONTEXT_GREGS_R15 96 -#define MCONTEXT_GREGS_RDI 104 -#define MCONTEXT_GREGS_RSI 112 -#define MCONTEXT_GREGS_RBP 120 -#define MCONTEXT_GREGS_RBX 128 -#define MCONTEXT_GREGS_RDX 136 -#define MCONTEXT_GREGS_RAX 144 -#define MCONTEXT_GREGS_RCX 152 -#define MCONTEXT_GREGS_RSP 160 -#define MCONTEXT_GREGS_RIP 168 -#define MCONTEXT_FPREGS_PTR 224 -#define MCONTEXT_FPREGS_MEM 304 -#define FPREGS_OFFSET_MXCSR 24 - -#else -#error "This header has not been ported for your CPU" -#endif - -#endif // GOOGLEBREAKPAD_COMMON_ANDROID_UCONTEXT_CONSTANTS_H diff --git a/TMessagesProj/jni/breakpad/common/basictypes.h b/TMessagesProj/jni/breakpad/common/basictypes.h deleted file mode 100644 index 9426c1f6c..000000000 --- a/TMessagesProj/jni/breakpad/common/basictypes.h +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) 2011 Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef COMMON_BASICTYPES_H_ -#define COMMON_BASICTYPES_H_ - -// A macro to disallow the copy constructor and operator= functions -// This should be used in the private: declarations for a class -#ifndef DISALLOW_COPY_AND_ASSIGN -#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ - TypeName(const TypeName&); \ - void operator=(const TypeName&) -#endif // DISALLOW_COPY_AND_ASSIGN - -namespace google_breakpad { - -// Used to explicitly mark the return value of a function as unused. If you are -// really sure you don't want to do anything with the return value of a function -// that has been marked with __attribute__((warn_unused_result)), wrap it with -// this. Example: -// -// scoped_ptr my_var = ...; -// if (TakeOwnership(my_var.get()) == SUCCESS) -// ignore_result(my_var.release()); -// -template -inline void ignore_result(const T&) { -} - -} // namespace google_breakpad - -#endif // COMMON_BASICTYPES_H_ diff --git a/TMessagesProj/jni/breakpad/common/byte_cursor.h b/TMessagesProj/jni/breakpad/common/byte_cursor.h deleted file mode 100644 index accd54e0a..000000000 --- a/TMessagesProj/jni/breakpad/common/byte_cursor.h +++ /dev/null @@ -1,265 +0,0 @@ -// -*- mode: c++ -*- - -// Copyright (c) 2010, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// Original author: Jim Blandy - -// byte_cursor.h: Classes for parsing values from a buffer of bytes. -// The ByteCursor class provides a convenient interface for reading -// fixed-size integers of arbitrary endianness, being thorough about -// checking for buffer overruns. - -#ifndef COMMON_BYTE_CURSOR_H_ -#define COMMON_BYTE_CURSOR_H_ - -#include -#include -#include -#include -#include - -#include "common/using_std_string.h" - -namespace google_breakpad { - -// A buffer holding a series of bytes. -struct ByteBuffer { - ByteBuffer() : start(0), end(0) { } - ByteBuffer(const uint8_t *set_start, size_t set_size) - : start(set_start), end(set_start + set_size) { } - ~ByteBuffer() { }; - - // Equality operators. Useful in unit tests, and when we're using - // ByteBuffers to refer to regions of a larger buffer. - bool operator==(const ByteBuffer &that) const { - return start == that.start && end == that.end; - } - bool operator!=(const ByteBuffer &that) const { - return start != that.start || end != that.end; - } - - // Not C++ style guide compliant, but this definitely belongs here. - size_t Size() const { - assert(start <= end); - return end - start; - } - - const uint8_t *start, *end; -}; - -// A cursor pointing into a ByteBuffer that can parse numbers of various -// widths and representations, strings, and data blocks, advancing through -// the buffer as it goes. All ByteCursor operations check that accesses -// haven't gone beyond the end of the enclosing ByteBuffer. -class ByteCursor { - public: - // Create a cursor reading bytes from the start of BUFFER. By default, the - // cursor reads multi-byte values in little-endian form. - ByteCursor(const ByteBuffer *buffer, bool big_endian = false) - : buffer_(buffer), here_(buffer->start), - big_endian_(big_endian), complete_(true) { } - - // Accessor and setter for this cursor's endianness flag. - bool big_endian() const { return big_endian_; } - void set_big_endian(bool big_endian) { big_endian_ = big_endian; } - - // Accessor and setter for this cursor's current position. The setter - // returns a reference to this cursor. - const uint8_t *here() const { return here_; } - ByteCursor &set_here(const uint8_t *here) { - assert(buffer_->start <= here && here <= buffer_->end); - here_ = here; - return *this; - } - - // Return the number of bytes available to read at the cursor. - size_t Available() const { return size_t(buffer_->end - here_); } - - // Return true if this cursor is at the end of its buffer. - bool AtEnd() const { return Available() == 0; } - - // When used as a boolean value this cursor converts to true if all - // prior reads have been completed, or false if we ran off the end - // of the buffer. - operator bool() const { return complete_; } - - // Read a SIZE-byte integer at this cursor, signed if IS_SIGNED is true, - // unsigned otherwise, using the cursor's established endianness, and set - // *RESULT to the number. If we read off the end of our buffer, clear - // this cursor's complete_ flag, and store a dummy value in *RESULT. - // Return a reference to this cursor. - template - ByteCursor &Read(size_t size, bool is_signed, T *result) { - if (CheckAvailable(size)) { - T v = 0; - if (big_endian_) { - for (size_t i = 0; i < size; i++) - v = (v << 8) + here_[i]; - } else { - // This loop condition looks weird, but size_t is unsigned, so - // decrementing i after it is zero yields the largest size_t value. - for (size_t i = size - 1; i < size; i--) - v = (v << 8) + here_[i]; - } - if (is_signed && size < sizeof(T)) { - size_t sign_bit = (T)1 << (size * 8 - 1); - v = (v ^ sign_bit) - sign_bit; - } - here_ += size; - *result = v; - } else { - *result = (T) 0xdeadbeef; - } - return *this; - } - - // Read an integer, using the cursor's established endianness and - // *RESULT's size and signedness, and set *RESULT to the number. If we - // read off the end of our buffer, clear this cursor's complete_ flag. - // Return a reference to this cursor. - template - ByteCursor &operator>>(T &result) { - bool T_is_signed = (T)-1 < 0; - return Read(sizeof(T), T_is_signed, &result); - } - - // Copy the SIZE bytes at the cursor to BUFFER, and advance this - // cursor to the end of them. If we read off the end of our buffer, - // clear this cursor's complete_ flag, and set *POINTER to NULL. - // Return a reference to this cursor. - ByteCursor &Read(uint8_t *buffer, size_t size) { - if (CheckAvailable(size)) { - memcpy(buffer, here_, size); - here_ += size; - } - return *this; - } - - // Set STR to a copy of the '\0'-terminated string at the cursor. If the - // byte buffer does not contain a terminating zero, clear this cursor's - // complete_ flag, and set STR to the empty string. Return a reference to - // this cursor. - ByteCursor &CString(string *str) { - const uint8_t *end - = static_cast(memchr(here_, '\0', Available())); - if (end) { - str->assign(reinterpret_cast(here_), end - here_); - here_ = end + 1; - } else { - str->clear(); - here_ = buffer_->end; - complete_ = false; - } - return *this; - } - - // Like CString(STR), but extract the string from a fixed-width buffer - // LIMIT bytes long, which may or may not contain a terminating '\0' - // byte. Specifically: - // - // - If there are not LIMIT bytes available at the cursor, clear the - // cursor's complete_ flag and set STR to the empty string. - // - // - Otherwise, if the LIMIT bytes at the cursor contain any '\0' - // characters, set *STR to a copy of the bytes before the first '\0', - // and advance the cursor by LIMIT bytes. - // - // - Otherwise, set *STR to a copy of those LIMIT bytes, and advance the - // cursor by LIMIT bytes. - ByteCursor &CString(string *str, size_t limit) { - if (CheckAvailable(limit)) { - const uint8_t *end - = static_cast(memchr(here_, '\0', limit)); - if (end) - str->assign(reinterpret_cast(here_), end - here_); - else - str->assign(reinterpret_cast(here_), limit); - here_ += limit; - } else { - str->clear(); - } - return *this; - } - - // Set *POINTER to point to the SIZE bytes at the cursor, and advance - // this cursor to the end of them. If SIZE is omitted, don't move the - // cursor. If we read off the end of our buffer, clear this cursor's - // complete_ flag, and set *POINTER to NULL. Return a reference to this - // cursor. - ByteCursor &PointTo(const uint8_t **pointer, size_t size = 0) { - if (CheckAvailable(size)) { - *pointer = here_; - here_ += size; - } else { - *pointer = NULL; - } - return *this; - } - - // Skip SIZE bytes at the cursor. If doing so would advance us off - // the end of our buffer, clear this cursor's complete_ flag, and - // set *POINTER to NULL. Return a reference to this cursor. - ByteCursor &Skip(size_t size) { - if (CheckAvailable(size)) - here_ += size; - return *this; - } - - private: - // If there are at least SIZE bytes available to read from the buffer, - // return true. Otherwise, set here_ to the end of the buffer, set - // complete_ to false, and return false. - bool CheckAvailable(size_t size) { - if (Available() >= size) { - return true; - } else { - here_ = buffer_->end; - complete_ = false; - return false; - } - } - - // The buffer we're reading bytes from. - const ByteBuffer *buffer_; - - // The next byte within buffer_ that we'll read. - const uint8_t *here_; - - // True if we should read numbers in big-endian form; false if we - // should read in little-endian form. - bool big_endian_; - - // True if we've been able to read all we've been asked to. - bool complete_; -}; - -} // namespace google_breakpad - -#endif // COMMON_BYTE_CURSOR_H_ diff --git a/TMessagesProj/jni/breakpad/common/convert_UTF.c b/TMessagesProj/jni/breakpad/common/convert_UTF.c deleted file mode 100644 index 12a3c8917..000000000 --- a/TMessagesProj/jni/breakpad/common/convert_UTF.c +++ /dev/null @@ -1,554 +0,0 @@ -/* - * Copyright © 1991-2015 Unicode, Inc. All rights reserved. - * Distributed under the Terms of Use in - * http://www.unicode.org/copyright.html. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of the Unicode data files and any associated documentation - * (the "Data Files") or Unicode software and any associated documentation - * (the "Software") to deal in the Data Files or Software - * without restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, and/or sell copies of - * the Data Files or Software, and to permit persons to whom the Data Files - * or Software are furnished to do so, provided that - * (a) this copyright and permission notice appear with all copies - * of the Data Files or Software, - * (b) this copyright and permission notice appear in associated - * documentation, and - * (c) there is clear notice in each modified Data File or in the Software - * as well as in the documentation associated with the Data File(s) or - * Software that the data or software has been modified. - * - * THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF - * ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE - * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT OF THIRD PARTY RIGHTS. - * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS - * NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL - * DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, - * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER - * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - * PERFORMANCE OF THE DATA FILES OR SOFTWARE. - * - * Except as contained in this notice, the name of a copyright holder - * shall not be used in advertising or otherwise to promote the sale, - * use or other dealings in these Data Files or Software without prior - * written authorization of the copyright holder. - */ - -/* --------------------------------------------------------------------- - -Conversions between UTF32, UTF-16, and UTF-8. Source code file. -Author: Mark E. Davis, 1994. -Rev History: Rick McGowan, fixes & updates May 2001. -Sept 2001: fixed const & error conditions per -mods suggested by S. Parent & A. Lillich. -June 2002: Tim Dodd added detection and handling of incomplete -source sequences, enhanced error detection, added casts -to eliminate compiler warnings. -July 2003: slight mods to back out aggressive FFFE detection. -Jan 2004: updated switches in from-UTF8 conversions. -Oct 2004: updated to use UNI_MAX_LEGAL_UTF32 in UTF-32 conversions. - -See the header file "ConvertUTF.h" for complete documentation. - ------------------------------------------------------------------------- */ - - -#include "convert_UTF.h" -#ifdef CVTUTF_DEBUG -#include -#endif - -static const int halfShift = 10; /* used for shifting by 10 bits */ - -static const UTF32 halfBase = 0x0010000UL; -static const UTF32 halfMask = 0x3FFUL; - -#define UNI_SUR_HIGH_START (UTF32)0xD800 -#define UNI_SUR_HIGH_END (UTF32)0xDBFF -#define UNI_SUR_LOW_START (UTF32)0xDC00 -#define UNI_SUR_LOW_END (UTF32)0xDFFF - -#ifndef false -#define false 0 -#endif -#ifndef true -#define true 1 -#endif - -/* --------------------------------------------------------------------- */ - -ConversionResult ConvertUTF32toUTF16 (const UTF32** sourceStart, const UTF32* sourceEnd, - UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF32* source = *sourceStart; - UTF16* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch; - if (target >= targetEnd) { - result = targetExhausted; break; - } - ch = *source++; - if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ - /* UTF-16 surrogate values are illegal in UTF-32; 0xffff or 0xfffe are both reserved values */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { - if (flags == strictConversion) { - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } else { - *target++ = UNI_REPLACEMENT_CHAR; - } - } else { - *target++ = (UTF16)ch; /* normal case */ - } - } else if (ch > UNI_MAX_LEGAL_UTF32) { - if (flags == strictConversion) { - result = sourceIllegal; - } else { - *target++ = UNI_REPLACEMENT_CHAR; - } - } else { - /* target is a character in range 0xFFFF - 0x10FFFF. */ - if (target + 1 >= targetEnd) { - --source; /* Back up source pointer! */ - result = targetExhausted; break; - } - ch -= halfBase; - *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); - *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); - } - } -*sourceStart = source; -*targetStart = target; -return result; -} - -/* --------------------------------------------------------------------- */ - -ConversionResult ConvertUTF16toUTF32 (const UTF16** sourceStart, const UTF16* sourceEnd, - UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF16* source = *sourceStart; - UTF32* target = *targetStart; - UTF32 ch, ch2; - while (source < sourceEnd) { - const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ - ch = *source++; - /* If we have a surrogate pair, convert to UTF32 first. */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { - /* If the 16 bits following the high surrogate are in the source buffer... */ - if (source < sourceEnd) { - ch2 = *source; - /* If it's a low surrogate, convert to UTF32. */ - if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { - ch = ((ch - UNI_SUR_HIGH_START) << halfShift) - + (ch2 - UNI_SUR_LOW_START) + halfBase; - ++source; - } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - } else { /* We don't have the 16 bits following the high surrogate. */ - --source; /* return to the high surrogate */ - result = sourceExhausted; - break; - } - } else if (flags == strictConversion) { - /* UTF-16 surrogate values are illegal in UTF-32 */ - if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - } - if (target >= targetEnd) { - source = oldSource; /* Back up source pointer! */ - result = targetExhausted; break; - } - *target++ = ch; - } - *sourceStart = source; - *targetStart = target; -#ifdef CVTUTF_DEBUG - if (result == sourceIllegal) { - fprintf(stderr, "ConvertUTF16toUTF32 illegal seq 0x%04x,%04x\n", ch, ch2); - fflush(stderr); - } -#endif - return result; -} - -/* --------------------------------------------------------------------- */ - -/* - * Index into the table below with the first byte of a UTF-8 sequence to - * get the number of trailing bytes that are supposed to follow it. - * Note that *legal* UTF-8 values can't have 4 or 5-bytes. The table is - * left as-is for anyone who may want to do such conversion, which was - * allowed in earlier algorithms. - */ -static const char trailingBytesForUTF8[256] = { - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 -}; - -/* - * Magic values subtracted from a buffer value during UTF8 conversion. - * This table contains as many values as there might be trailing bytes - * in a UTF-8 sequence. - */ -static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, - 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; - -/* - * Once the bits are split out into bytes of UTF-8, this is a mask OR-ed - * into the first byte, depending on how many bytes follow. There are - * as many entries in this table as there are UTF-8 sequence types. - * (I.e., one byte sequence, two byte... etc.). Remember that sequencs - * for *legal* UTF-8 will be 4 or fewer bytes total. - */ -static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; - -/* --------------------------------------------------------------------- */ - -/* The interface converts a whole buffer to avoid function-call overhead. -* Constants have been gathered. Loops & conditionals have been removed as -* much as possible for efficiency, in favor of drop-through switches. -* (See "Note A" at the bottom of the file for equivalent code.) -* If your compiler supports it, the "isLegalUTF8" call can be turned -* into an inline function. -*/ - -/* --------------------------------------------------------------------- */ - -ConversionResult ConvertUTF16toUTF8 (const UTF16** sourceStart, const UTF16* sourceEnd, - UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF16* source = *sourceStart; - UTF8* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch; - unsigned short bytesToWrite = 0; - const UTF32 byteMask = 0xBF; - const UTF32 byteMark = 0x80; - const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ - ch = *source++; - /* If we have a surrogate pair, convert to UTF32 first. */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { - /* If the 16 bits following the high surrogate are in the source buffer... */ - if (source < sourceEnd) { - UTF32 ch2 = *source; - /* If it's a low surrogate, convert to UTF32. */ - if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { - ch = ((ch - UNI_SUR_HIGH_START) << halfShift) - + (ch2 - UNI_SUR_LOW_START) + halfBase; - ++source; - } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - } else { /* We don't have the 16 bits following the high surrogate. */ - --source; /* return to the high surrogate */ - result = sourceExhausted; - break; - } - } else if (flags == strictConversion) { - /* UTF-16 surrogate values are illegal in UTF-32 */ - if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - } - /* Figure out how many bytes the result will require */ - if (ch < (UTF32)0x80) { bytesToWrite = 1; - } else if (ch < (UTF32)0x800) { bytesToWrite = 2; - } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; - } else if (ch < (UTF32)0x110000) { bytesToWrite = 4; - } else { bytesToWrite = 3; - ch = UNI_REPLACEMENT_CHAR; - } - - target += bytesToWrite; - if (target > targetEnd) { - source = oldSource; /* Back up source pointer! */ - target -= bytesToWrite; result = targetExhausted; break; - } - switch (bytesToWrite) { /* note: everything falls through. */ - case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 1: *--target = (UTF8)(ch | firstByteMark[bytesToWrite]); - } - target += bytesToWrite; - } -*sourceStart = source; -*targetStart = target; -return result; -} - -/* --------------------------------------------------------------------- */ - -/* - * Utility routine to tell whether a sequence of bytes is legal UTF-8. - * This must be called with the length pre-determined by the first byte. - * If not calling this from ConvertUTF8to*, then the length can be set by: - * length = trailingBytesForUTF8[*source]+1; - * and the sequence is illegal right away if there aren't that many bytes - * available. - * If presented with a length > 4, this returns false. The Unicode - * definition of UTF-8 goes up to 4-byte sequences. - */ - -static Boolean isLegalUTF8(const UTF8 *source, int length) { - UTF8 a; - const UTF8 *srcptr = source+length; - switch (length) { - default: return false; - /* Everything else falls through when "true"... */ - case 4: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; - case 3: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; - case 2: if ((a = (*--srcptr)) > 0xBF) return false; - - switch (*source) { - /* no fall-through in this inner switch */ - case 0xE0: if (a < 0xA0) return false; break; - case 0xED: if (a > 0x9F) return false; break; - case 0xF0: if (a < 0x90) return false; break; - case 0xF4: if (a > 0x8F) return false; break; - default: if (a < 0x80) return false; - } - - case 1: if (*source >= 0x80 && *source < 0xC2) return false; - } - if (*source > 0xF4) return false; - return true; -} - -/* --------------------------------------------------------------------- */ - -/* - * Exported function to return whether a UTF-8 sequence is legal or not. - * This is not used here; it's just exported. - */ -Boolean isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd) { - int length = trailingBytesForUTF8[*source]+1; - if (source+length > sourceEnd) { - return false; - } - return isLegalUTF8(source, length); -} - -/* --------------------------------------------------------------------- */ - -ConversionResult ConvertUTF8toUTF16 (const UTF8** sourceStart, const UTF8* sourceEnd, - UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF8* source = *sourceStart; - UTF16* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch = 0; - unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; - if (source + extraBytesToRead >= sourceEnd) { - result = sourceExhausted; break; - } - /* Do this check whether lenient or strict */ - if (! isLegalUTF8(source, extraBytesToRead+1)) { - result = sourceIllegal; - break; - } - /* - * The cases all fall through. See "Note A" below. - */ - switch (extraBytesToRead) { - case 5: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ - case 4: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ - case 3: ch += *source++; ch <<= 6; - case 2: ch += *source++; ch <<= 6; - case 1: ch += *source++; ch <<= 6; - case 0: ch += *source++; - } - ch -= offsetsFromUTF8[extraBytesToRead]; - - if (target >= targetEnd) { - source -= (extraBytesToRead+1); /* Back up source pointer! */ - result = targetExhausted; break; - } - if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ - /* UTF-16 surrogate values are illegal in UTF-32 */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { - if (flags == strictConversion) { - source -= (extraBytesToRead+1); /* return to the illegal value itself */ - result = sourceIllegal; - break; - } else { - *target++ = UNI_REPLACEMENT_CHAR; - } - } else { - *target++ = (UTF16)ch; /* normal case */ - } - } else if (ch > UNI_MAX_UTF16) { - if (flags == strictConversion) { - result = sourceIllegal; - source -= (extraBytesToRead+1); /* return to the start */ - break; /* Bail out; shouldn't continue */ - } else { - *target++ = UNI_REPLACEMENT_CHAR; - } - } else { - /* target is a character in range 0xFFFF - 0x10FFFF. */ - if (target + 1 >= targetEnd) { - source -= (extraBytesToRead+1); /* Back up source pointer! */ - result = targetExhausted; break; - } - ch -= halfBase; - *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); - *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); - } - } -*sourceStart = source; -*targetStart = target; -return result; -} - -/* --------------------------------------------------------------------- */ - -ConversionResult ConvertUTF32toUTF8 (const UTF32** sourceStart, const UTF32* sourceEnd, - UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF32* source = *sourceStart; - UTF8* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch; - unsigned short bytesToWrite = 0; - const UTF32 byteMask = 0xBF; - const UTF32 byteMark = 0x80; - ch = *source++; - if (flags == strictConversion ) { - /* UTF-16 surrogate values are illegal in UTF-32 */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - } - /* - * Figure out how many bytes the result will require. Turn any - * illegally large UTF32 things (> Plane 17) into replacement chars. - */ - if (ch < (UTF32)0x80) { bytesToWrite = 1; - } else if (ch < (UTF32)0x800) { bytesToWrite = 2; - } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; - } else if (ch <= UNI_MAX_LEGAL_UTF32) { bytesToWrite = 4; - } else { bytesToWrite = 3; - ch = UNI_REPLACEMENT_CHAR; - result = sourceIllegal; - } - - target += bytesToWrite; - if (target > targetEnd) { - --source; /* Back up source pointer! */ - target -= bytesToWrite; result = targetExhausted; break; - } - switch (bytesToWrite) { /* note: everything falls through. */ - case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 1: *--target = (UTF8) (ch | firstByteMark[bytesToWrite]); - } - target += bytesToWrite; - } -*sourceStart = source; -*targetStart = target; -return result; -} - -/* --------------------------------------------------------------------- */ - -ConversionResult ConvertUTF8toUTF32 (const UTF8** sourceStart, const UTF8* sourceEnd, - UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF8* source = *sourceStart; - UTF32* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch = 0; - unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; - if (source + extraBytesToRead >= sourceEnd) { - result = sourceExhausted; break; - } - /* Do this check whether lenient or strict */ - if (! isLegalUTF8(source, extraBytesToRead+1)) { - result = sourceIllegal; - break; - } - /* - * The cases all fall through. See "Note A" below. - */ - switch (extraBytesToRead) { - case 5: ch += *source++; ch <<= 6; - case 4: ch += *source++; ch <<= 6; - case 3: ch += *source++; ch <<= 6; - case 2: ch += *source++; ch <<= 6; - case 1: ch += *source++; ch <<= 6; - case 0: ch += *source++; - } - ch -= offsetsFromUTF8[extraBytesToRead]; - - if (target >= targetEnd) { - source -= (extraBytesToRead+1); /* Back up the source pointer! */ - result = targetExhausted; break; - } - if (ch <= UNI_MAX_LEGAL_UTF32) { - /* - * UTF-16 surrogate values are illegal in UTF-32, and anything - * over Plane 17 (> 0x10FFFF) is illegal. - */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { - if (flags == strictConversion) { - source -= (extraBytesToRead+1); /* return to the illegal value itself */ - result = sourceIllegal; - break; - } else { - *target++ = UNI_REPLACEMENT_CHAR; - } - } else { - *target++ = ch; - } - } else { /* i.e., ch > UNI_MAX_LEGAL_UTF32 */ - result = sourceIllegal; - *target++ = UNI_REPLACEMENT_CHAR; - } - } - *sourceStart = source; - *targetStart = target; - return result; -} - -/* --------------------------------------------------------------------- - -Note A. -The fall-through switches in UTF-8 reading code save a -temp variable, some decrements & conditionals. The switches -are equivalent to the following loop: -{ - int tmpBytesToRead = extraBytesToRead+1; - do { - ch += *source++; - --tmpBytesToRead; - if (tmpBytesToRead) ch <<= 6; - } while (tmpBytesToRead > 0); -} -In UTF-8 writing code, the switches on "bytesToWrite" are -similarly unrolled loops. - ---------------------------------------------------------------------- */ diff --git a/TMessagesProj/jni/breakpad/common/convert_UTF.h b/TMessagesProj/jni/breakpad/common/convert_UTF.h deleted file mode 100644 index 644d09950..000000000 --- a/TMessagesProj/jni/breakpad/common/convert_UTF.h +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright © 1991-2015 Unicode, Inc. All rights reserved. - * Distributed under the Terms of Use in - * http://www.unicode.org/copyright.html. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of the Unicode data files and any associated documentation - * (the "Data Files") or Unicode software and any associated documentation - * (the "Software") to deal in the Data Files or Software - * without restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, and/or sell copies of - * the Data Files or Software, and to permit persons to whom the Data Files - * or Software are furnished to do so, provided that - * (a) this copyright and permission notice appear with all copies - * of the Data Files or Software, - * (b) this copyright and permission notice appear in associated - * documentation, and - * (c) there is clear notice in each modified Data File or in the Software - * as well as in the documentation associated with the Data File(s) or - * Software that the data or software has been modified. - * - * THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF - * ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE - * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT OF THIRD PARTY RIGHTS. - * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS - * NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL - * DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, - * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER - * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - * PERFORMANCE OF THE DATA FILES OR SOFTWARE. - * - * Except as contained in this notice, the name of a copyright holder - * shall not be used in advertising or otherwise to promote the sale, - * use or other dealings in these Data Files or Software without prior - * written authorization of the copyright holder. - */ - -#ifndef COMMON_CONVERT_UTF_H_ -#define COMMON_CONVERT_UTF_H_ - -/* --------------------------------------------------------------------- - -Conversions between UTF32, UTF-16, and UTF-8. Header file. - -Several funtions are included here, forming a complete set of -conversions between the three formats. UTF-7 is not included -here, but is handled in a separate source file. - -Each of these routines takes pointers to input buffers and output -buffers. The input buffers are const. - -Each routine converts the text between *sourceStart and sourceEnd, -putting the result into the buffer between *targetStart and -targetEnd. Note: the end pointers are *after* the last item: e.g. -*(sourceEnd - 1) is the last item. - -The return result indicates whether the conversion was successful, -and if not, whether the problem was in the source or target buffers. -(Only the first encountered problem is indicated.) - -After the conversion, *sourceStart and *targetStart are both -updated to point to the end of last text successfully converted in -the respective buffers. - -Input parameters: -sourceStart - pointer to a pointer to the source buffer. -The contents of this are modified on return so that -it points at the next thing to be converted. -targetStart - similarly, pointer to pointer to the target buffer. -sourceEnd, targetEnd - respectively pointers to the ends of the -two buffers, for overflow checking only. - -These conversion functions take a ConversionFlags argument. When this -flag is set to strict, both irregular sequences and isolated surrogates -will cause an error. When the flag is set to lenient, both irregular -sequences and isolated surrogates are converted. - -Whether the flag is strict or lenient, all illegal sequences will cause -an error return. This includes sequences such as: , , -or in UTF-8, and values above 0x10FFFF in UTF-32. Conformant code -must check for illegal sequences. - -When the flag is set to lenient, characters over 0x10FFFF are converted -to the replacement character; otherwise (when the flag is set to strict) -they constitute an error. - -Output parameters: -The value "sourceIllegal" is returned from some routines if the input -sequence is malformed. When "sourceIllegal" is returned, the source -value will point to the illegal value that caused the problem. E.g., -in UTF-8 when a sequence is malformed, it points to the start of the -malformed sequence. - -Author: Mark E. Davis, 1994. -Rev History: Rick McGowan, fixes & updates May 2001. -Fixes & updates, Sept 2001. - ------------------------------------------------------------------------- */ - -/* --------------------------------------------------------------------- -The following 4 definitions are compiler-specific. -The C standard does not guarantee that wchar_t has at least -16 bits, so wchar_t is no less portable than unsigned short! -All should be unsigned values to avoid sign extension during -bit mask & shift operations. ------------------------------------------------------------------------- */ - -typedef unsigned long UTF32; /* at least 32 bits */ -typedef unsigned short UTF16; /* at least 16 bits */ -typedef unsigned char UTF8; /* typically 8 bits */ -typedef unsigned char Boolean; /* 0 or 1 */ - -/* Some fundamental constants */ -#define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD -#define UNI_MAX_BMP (UTF32)0x0000FFFF -#define UNI_MAX_UTF16 (UTF32)0x0010FFFF -#define UNI_MAX_UTF32 (UTF32)0x7FFFFFFF -#define UNI_MAX_LEGAL_UTF32 (UTF32)0x0010FFFF - -typedef enum { - conversionOK, /* conversion successful */ - sourceExhausted, /* partial character in source, but hit end */ - targetExhausted, /* insuff. room in target for conversion */ - sourceIllegal /* source sequence is illegal/malformed */ -} ConversionResult; - -typedef enum { - strictConversion = 0, - lenientConversion -} ConversionFlags; - -/* This is for C++ and does no harm in C */ -#ifdef __cplusplus -extern "C" { -#endif - -ConversionResult ConvertUTF8toUTF16 (const UTF8** sourceStart, const UTF8* sourceEnd, - UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags); - -ConversionResult ConvertUTF16toUTF8 (const UTF16** sourceStart, const UTF16* sourceEnd, - UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); - -ConversionResult ConvertUTF8toUTF32 (const UTF8** sourceStart, const UTF8* sourceEnd, - UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags); - -ConversionResult ConvertUTF32toUTF8 (const UTF32** sourceStart, const UTF32* sourceEnd, - UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); - -ConversionResult ConvertUTF16toUTF32 (const UTF16** sourceStart, const UTF16* sourceEnd, - UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags); - -ConversionResult ConvertUTF32toUTF16 (const UTF32** sourceStart, const UTF32* sourceEnd, - UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags); - -Boolean isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd); - -#ifdef __cplusplus -} -#endif - -/* --------------------------------------------------------------------- */ - -#endif // COMMON_CONVERT_UTF_H_ diff --git a/TMessagesProj/jni/breakpad/common/linux/eintr_wrapper.h b/TMessagesProj/jni/breakpad/common/linux/eintr_wrapper.h deleted file mode 100644 index 3f1d18481..000000000 --- a/TMessagesProj/jni/breakpad/common/linux/eintr_wrapper.h +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) 2010 Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef COMMON_LINUX_EINTR_WRAPPER_H_ -#define COMMON_LINUX_EINTR_WRAPPER_H_ - -#include - -// This provides a wrapper around system calls which may be interrupted by a -// signal and return EINTR. See man 7 signal. -// - -#define HANDLE_EINTR(x) ({ \ - __typeof__(x) eintr_wrapper_result; \ - do { \ - eintr_wrapper_result = (x); \ - } while (eintr_wrapper_result == -1 && errno == EINTR); \ - eintr_wrapper_result; \ -}) - -#define IGNORE_EINTR(x) ({ \ - __typeof__(x) eintr_wrapper_result; \ - do { \ - eintr_wrapper_result = (x); \ - if (eintr_wrapper_result == -1 && errno == EINTR) { \ - eintr_wrapper_result = 0; \ - } \ - } while (0); \ - eintr_wrapper_result; \ -}) - -#endif // COMMON_LINUX_EINTR_WRAPPER_H_ diff --git a/TMessagesProj/jni/breakpad/common/linux/elf_gnu_compat.h b/TMessagesProj/jni/breakpad/common/linux/elf_gnu_compat.h deleted file mode 100644 index f870cbc7d..000000000 --- a/TMessagesProj/jni/breakpad/common/linux/elf_gnu_compat.h +++ /dev/null @@ -1,46 +0,0 @@ -// -*- mode: C++ -*- - -// Copyright (c) 2013, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// Original author: Lei Zhang - -// elf_gnu_compat.h: #defines unique to glibc's elf.h. - -#ifndef COMMON_LINUX_ELF_GNU_COMPAT_H_ -#define COMMON_LINUX_ELF_GNU_COMPAT_H_ - -#include - -// A note type on GNU systems corresponding to the .note.gnu.build-id section. -#ifndef NT_GNU_BUILD_ID -#define NT_GNU_BUILD_ID 3 -#endif - -#endif // COMMON_LINUX_ELF_GNU_COMPAT_H_ diff --git a/TMessagesProj/jni/breakpad/common/linux/elfutils-inl.h b/TMessagesProj/jni/breakpad/common/linux/elfutils-inl.h deleted file mode 100644 index e56b37a9f..000000000 --- a/TMessagesProj/jni/breakpad/common/linux/elfutils-inl.h +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) 2012, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef COMMON_LINUX_ELFUTILS_INL_H__ -#define COMMON_LINUX_ELFUTILS_INL_H__ - -#include "common/linux/linux_libc_support.h" -#include "elfutils.h" - -namespace google_breakpad { - -template -const T* GetOffset(const typename ElfClass::Ehdr* elf_header, - typename ElfClass::Off offset) { - return reinterpret_cast(reinterpret_cast(elf_header) + - offset); -} - -template -const typename ElfClass::Shdr* FindElfSectionByName( - const char* name, - typename ElfClass::Word section_type, - const typename ElfClass::Shdr* sections, - const char* section_names, - const char* names_end, - int nsection) { - assert(name != NULL); - assert(sections != NULL); - assert(nsection > 0); - - int name_len = my_strlen(name); - if (name_len == 0) - return NULL; - - for (int i = 0; i < nsection; ++i) { - const char* section_name = section_names + sections[i].sh_name; - if (sections[i].sh_type == section_type && - names_end - section_name >= name_len + 1 && - my_strcmp(name, section_name) == 0) { - return sections + i; - } - } - return NULL; -} - -} // namespace google_breakpad - -#endif // COMMON_LINUX_ELFUTILS_INL_H__ diff --git a/TMessagesProj/jni/breakpad/common/linux/elfutils.cc b/TMessagesProj/jni/breakpad/common/linux/elfutils.cc deleted file mode 100644 index a79391c13..000000000 --- a/TMessagesProj/jni/breakpad/common/linux/elfutils.cc +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright (c) 2012, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "common/linux/elfutils.h" - -#include -#include - -#include "common/linux/linux_libc_support.h" -#include "common/linux/elfutils-inl.h" - -namespace google_breakpad { - -namespace { - -template -void FindElfClassSection(const char *elf_base, - const char *section_name, - typename ElfClass::Word section_type, - const void **section_start, - size_t *section_size) { - typedef typename ElfClass::Ehdr Ehdr; - typedef typename ElfClass::Shdr Shdr; - - assert(elf_base); - assert(section_start); - assert(section_size); - - assert(my_strncmp(elf_base, ELFMAG, SELFMAG) == 0); - - const Ehdr* elf_header = reinterpret_cast(elf_base); - assert(elf_header->e_ident[EI_CLASS] == ElfClass::kClass); - - const Shdr* sections = - GetOffset(elf_header, elf_header->e_shoff); - const Shdr* section_names = sections + elf_header->e_shstrndx; - const char* names = - GetOffset(elf_header, section_names->sh_offset); - const char *names_end = names + section_names->sh_size; - - const Shdr* section = - FindElfSectionByName(section_name, section_type, - sections, names, names_end, - elf_header->e_shnum); - - if (section != NULL && section->sh_size > 0) { - *section_start = elf_base + section->sh_offset; - *section_size = section->sh_size; - } -} - -template -void FindElfClassSegment(const char *elf_base, - typename ElfClass::Word segment_type, - const void **segment_start, - size_t *segment_size) { - typedef typename ElfClass::Ehdr Ehdr; - typedef typename ElfClass::Phdr Phdr; - - assert(elf_base); - assert(segment_start); - assert(segment_size); - - assert(my_strncmp(elf_base, ELFMAG, SELFMAG) == 0); - - const Ehdr* elf_header = reinterpret_cast(elf_base); - assert(elf_header->e_ident[EI_CLASS] == ElfClass::kClass); - - const Phdr* phdrs = - GetOffset(elf_header, elf_header->e_phoff); - - for (int i = 0; i < elf_header->e_phnum; ++i) { - if (phdrs[i].p_type == segment_type) { - *segment_start = elf_base + phdrs[i].p_offset; - *segment_size = phdrs[i].p_filesz; - return; - } - } -} - -} // namespace - -bool IsValidElf(const void* elf_base) { - return my_strncmp(reinterpret_cast(elf_base), - ELFMAG, SELFMAG) == 0; -} - -int ElfClass(const void* elf_base) { - const ElfW(Ehdr)* elf_header = - reinterpret_cast(elf_base); - - return elf_header->e_ident[EI_CLASS]; -} - -bool FindElfSection(const void *elf_mapped_base, - const char *section_name, - uint32_t section_type, - const void **section_start, - size_t *section_size, - int *elfclass) { - assert(elf_mapped_base); - assert(section_start); - assert(section_size); - - *section_start = NULL; - *section_size = 0; - - if (!IsValidElf(elf_mapped_base)) - return false; - - int cls = ElfClass(elf_mapped_base); - if (elfclass) { - *elfclass = cls; - } - - const char* elf_base = - static_cast(elf_mapped_base); - - if (cls == ELFCLASS32) { - FindElfClassSection(elf_base, section_name, section_type, - section_start, section_size); - return *section_start != NULL; - } else if (cls == ELFCLASS64) { - FindElfClassSection(elf_base, section_name, section_type, - section_start, section_size); - return *section_start != NULL; - } - - return false; -} - -bool FindElfSegment(const void *elf_mapped_base, - uint32_t segment_type, - const void **segment_start, - size_t *segment_size, - int *elfclass) { - assert(elf_mapped_base); - assert(segment_start); - assert(segment_size); - - *segment_start = NULL; - *segment_size = 0; - - if (!IsValidElf(elf_mapped_base)) - return false; - - int cls = ElfClass(elf_mapped_base); - if (elfclass) { - *elfclass = cls; - } - - const char* elf_base = - static_cast(elf_mapped_base); - - if (cls == ELFCLASS32) { - FindElfClassSegment(elf_base, segment_type, - segment_start, segment_size); - return *segment_start != NULL; - } else if (cls == ELFCLASS64) { - FindElfClassSegment(elf_base, segment_type, - segment_start, segment_size); - return *segment_start != NULL; - } - - return false; -} - -} // namespace google_breakpad diff --git a/TMessagesProj/jni/breakpad/common/linux/elfutils.h b/TMessagesProj/jni/breakpad/common/linux/elfutils.h deleted file mode 100644 index dccdc235e..000000000 --- a/TMessagesProj/jni/breakpad/common/linux/elfutils.h +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) 2012, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// elfutils.h: Utilities for dealing with ELF files. -// - -#ifndef COMMON_LINUX_ELFUTILS_H_ -#define COMMON_LINUX_ELFUTILS_H_ - -#include -#include -#include - -namespace google_breakpad { - -// Traits classes so consumers can write templatized code to deal -// with specific ELF bits. -struct ElfClass32 { - typedef Elf32_Addr Addr; - typedef Elf32_Ehdr Ehdr; - typedef Elf32_Nhdr Nhdr; - typedef Elf32_Phdr Phdr; - typedef Elf32_Shdr Shdr; - typedef Elf32_Half Half; - typedef Elf32_Off Off; - typedef Elf32_Word Word; - static const int kClass = ELFCLASS32; - static const size_t kAddrSize = sizeof(Elf32_Addr); -}; - -struct ElfClass64 { - typedef Elf64_Addr Addr; - typedef Elf64_Ehdr Ehdr; - typedef Elf64_Nhdr Nhdr; - typedef Elf64_Phdr Phdr; - typedef Elf64_Shdr Shdr; - typedef Elf64_Half Half; - typedef Elf64_Off Off; - typedef Elf64_Word Word; - static const int kClass = ELFCLASS64; - static const size_t kAddrSize = sizeof(Elf64_Addr); -}; - -bool IsValidElf(const void* elf_header); -int ElfClass(const void* elf_base); - -// Attempt to find a section named |section_name| of type |section_type| -// in the ELF binary data at |elf_mapped_base|. On success, returns true -// and sets |*section_start| to point to the start of the section data, -// and |*section_size| to the size of the section's data. If |elfclass| -// is not NULL, set |*elfclass| to the ELF file class. -bool FindElfSection(const void *elf_mapped_base, - const char *section_name, - uint32_t section_type, - const void **section_start, - size_t *section_size, - int *elfclass); - -// Internal helper method, exposed for convenience for callers -// that already have more info. -template -const typename ElfClass::Shdr* -FindElfSectionByName(const char* name, - typename ElfClass::Word section_type, - const typename ElfClass::Shdr* sections, - const char* section_names, - const char* names_end, - int nsection); - -// Attempt to find the first segment of type |segment_type| in the ELF -// binary data at |elf_mapped_base|. On success, returns true and sets -// |*segment_start| to point to the start of the segment data, and -// and |*segment_size| to the size of the segment's data. If |elfclass| -// is not NULL, set |*elfclass| to the ELF file class. -bool FindElfSegment(const void *elf_mapped_base, - uint32_t segment_type, - const void **segment_start, - size_t *segment_size, - int *elfclass); - -// Convert an offset from an Elf header into a pointer to the mapped -// address in the current process. Takes an extra template parameter -// to specify the return type to avoid having to dynamic_cast the -// result. -template -const T* -GetOffset(const typename ElfClass::Ehdr* elf_header, - typename ElfClass::Off offset); - -} // namespace google_breakpad - -#endif // COMMON_LINUX_ELFUTILS_H_ diff --git a/TMessagesProj/jni/breakpad/common/linux/file_id.cc b/TMessagesProj/jni/breakpad/common/linux/file_id.cc deleted file mode 100644 index 00b37313a..000000000 --- a/TMessagesProj/jni/breakpad/common/linux/file_id.cc +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright (c) 2006, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// file_id.cc: Return a unique identifier for a file -// -// See file_id.h for documentation -// - -#include "common/linux/file_id.h" - -#include -#include -#include - -#include - -#include "common/linux/elf_gnu_compat.h" -#include "common/linux/elfutils.h" -#include "common/linux/linux_libc_support.h" -#include "common/linux/memory_mapped_file.h" -#include "third_party/lss/linux_syscall_support.h" - -namespace google_breakpad { - -FileID::FileID(const char* path) : path_(path) {} - -// ELF note name and desc are 32-bits word padded. -#define NOTE_PADDING(a) ((a + 3) & ~3) - -// These functions are also used inside the crashed process, so be safe -// and use the syscall/libc wrappers instead of direct syscalls or libc. - -template -static bool ElfClassBuildIDNoteIdentifier(const void *section, size_t length, - uint8_t identifier[kMDGUIDSize]) { - typedef typename ElfClass::Nhdr Nhdr; - - const void* section_end = reinterpret_cast(section) + length; - const Nhdr* note_header = reinterpret_cast(section); - while (reinterpret_cast(note_header) < section_end) { - if (note_header->n_type == NT_GNU_BUILD_ID) - break; - note_header = reinterpret_cast( - reinterpret_cast(note_header) + sizeof(Nhdr) + - NOTE_PADDING(note_header->n_namesz) + - NOTE_PADDING(note_header->n_descsz)); - } - if (reinterpret_cast(note_header) >= section_end || - note_header->n_descsz == 0) { - return false; - } - - const char* build_id = reinterpret_cast(note_header) + - sizeof(Nhdr) + NOTE_PADDING(note_header->n_namesz); - // Copy as many bits of the build ID as will fit - // into the GUID space. - my_memset(identifier, 0, kMDGUIDSize); - memcpy(identifier, build_id, - std::min(kMDGUIDSize, (size_t)note_header->n_descsz)); - - return true; -} - -// Attempt to locate a .note.gnu.build-id section in an ELF binary -// and copy as many bytes of it as will fit into |identifier|. -static bool FindElfBuildIDNote(const void *elf_mapped_base, - uint8_t identifier[kMDGUIDSize]) { - void* note_section; - size_t note_size; - int elfclass; - if ((!FindElfSegment(elf_mapped_base, PT_NOTE, - (const void**)¬e_section, ¬e_size, &elfclass) || - note_size == 0) && - (!FindElfSection(elf_mapped_base, ".note.gnu.build-id", SHT_NOTE, - (const void**)¬e_section, ¬e_size, &elfclass) || - note_size == 0)) { - return false; - } - - if (elfclass == ELFCLASS32) { - return ElfClassBuildIDNoteIdentifier(note_section, note_size, - identifier); - } else if (elfclass == ELFCLASS64) { - return ElfClassBuildIDNoteIdentifier(note_section, note_size, - identifier); - } - - return false; -} - -// Attempt to locate the .text section of an ELF binary and generate -// a simple hash by XORing the first page worth of bytes into |identifier|. -static bool HashElfTextSection(const void *elf_mapped_base, - uint8_t identifier[kMDGUIDSize]) { - void* text_section; - size_t text_size; - if (!FindElfSection(elf_mapped_base, ".text", SHT_PROGBITS, - (const void**)&text_section, &text_size, NULL) || - text_size == 0) { - return false; - } - - my_memset(identifier, 0, kMDGUIDSize); - const uint8_t* ptr = reinterpret_cast(text_section); - const uint8_t* ptr_end = ptr + std::min(text_size, static_cast(4096)); - while (ptr < ptr_end) { - for (unsigned i = 0; i < kMDGUIDSize; i++) - identifier[i] ^= ptr[i]; - ptr += kMDGUIDSize; - } - return true; -} - -// static -bool FileID::ElfFileIdentifierFromMappedFile(const void* base, - uint8_t identifier[kMDGUIDSize]) { - // Look for a build id note first. - if (FindElfBuildIDNote(base, identifier)) - return true; - - // Fall back on hashing the first page of the text section. - return HashElfTextSection(base, identifier); -} - -bool FileID::ElfFileIdentifier(uint8_t identifier[kMDGUIDSize]) { - MemoryMappedFile mapped_file(path_.c_str(), 0); - if (!mapped_file.data()) // Should probably check if size >= ElfW(Ehdr)? - return false; - - return ElfFileIdentifierFromMappedFile(mapped_file.data(), identifier); -} - -// static -void FileID::ConvertIdentifierToString(const uint8_t identifier[kMDGUIDSize], - char* buffer, int buffer_length) { - uint8_t identifier_swapped[kMDGUIDSize]; - - // Endian-ness swap to match dump processor expectation. - memcpy(identifier_swapped, identifier, kMDGUIDSize); - uint32_t* data1 = reinterpret_cast(identifier_swapped); - *data1 = htonl(*data1); - uint16_t* data2 = reinterpret_cast(identifier_swapped + 4); - *data2 = htons(*data2); - uint16_t* data3 = reinterpret_cast(identifier_swapped + 6); - *data3 = htons(*data3); - - int buffer_idx = 0; - for (unsigned int idx = 0; - (buffer_idx < buffer_length) && (idx < kMDGUIDSize); - ++idx) { - int hi = (identifier_swapped[idx] >> 4) & 0x0F; - int lo = (identifier_swapped[idx]) & 0x0F; - - if (idx == 4 || idx == 6 || idx == 8 || idx == 10) - buffer[buffer_idx++] = '-'; - - buffer[buffer_idx++] = (hi >= 10) ? 'A' + hi - 10 : '0' + hi; - buffer[buffer_idx++] = (lo >= 10) ? 'A' + lo - 10 : '0' + lo; - } - - // NULL terminate - buffer[(buffer_idx < buffer_length) ? buffer_idx : buffer_idx - 1] = 0; -} - -} // namespace google_breakpad diff --git a/TMessagesProj/jni/breakpad/common/linux/file_id.h b/TMessagesProj/jni/breakpad/common/linux/file_id.h deleted file mode 100644 index 2642722a6..000000000 --- a/TMessagesProj/jni/breakpad/common/linux/file_id.h +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) 2006, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// file_id.h: Return a unique identifier for a file -// - -#ifndef COMMON_LINUX_FILE_ID_H__ -#define COMMON_LINUX_FILE_ID_H__ - -#include -#include - -#include "common/linux/guid_creator.h" - -namespace google_breakpad { - -static const size_t kMDGUIDSize = sizeof(MDGUID); - -class FileID { - public: - explicit FileID(const char* path); - ~FileID() {} - - // Load the identifier for the elf file path specified in the constructor into - // |identifier|. Return false if the identifier could not be created for the - // file. - // The current implementation will look for a .note.gnu.build-id - // section and use that as the file id, otherwise it falls back to - // XORing the first 4096 bytes of the .text section to generate an identifier. - bool ElfFileIdentifier(uint8_t identifier[kMDGUIDSize]); - - // Load the identifier for the elf file mapped into memory at |base| into - // |identifier|. Return false if the identifier could not be created for the - // file. - static bool ElfFileIdentifierFromMappedFile(const void* base, - uint8_t identifier[kMDGUIDSize]); - - // Convert the |identifier| data to a NULL terminated string. The string will - // be formatted as a UUID (e.g., 22F065BB-FC9C-49F7-80FE-26A7CEBD7BCE). - // The |buffer| should be at least 37 bytes long to receive all of the data - // and termination. Shorter buffers will contain truncated data. - static void ConvertIdentifierToString(const uint8_t identifier[kMDGUIDSize], - char* buffer, int buffer_length); - - private: - // Storage for the path specified - std::string path_; -}; - -} // namespace google_breakpad - -#endif // COMMON_LINUX_FILE_ID_H__ diff --git a/TMessagesProj/jni/breakpad/common/linux/guid_creator.cc b/TMessagesProj/jni/breakpad/common/linux/guid_creator.cc deleted file mode 100644 index bfb308ee2..000000000 --- a/TMessagesProj/jni/breakpad/common/linux/guid_creator.cc +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) 2006, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "common/linux/guid_creator.h" - -#include -#include -#include -#include -#include -#include - -// -// GUIDGenerator -// -// This class is used to generate random GUID. -// Currently use random number to generate a GUID since Linux has -// no native GUID generator. This should be OK since we don't expect -// crash to happen very offen. -// -class GUIDGenerator { - public: - static uint32_t BytesToUInt32(const uint8_t bytes[]) { - return ((uint32_t) bytes[0] - | ((uint32_t) bytes[1] << 8) - | ((uint32_t) bytes[2] << 16) - | ((uint32_t) bytes[3] << 24)); - } - - static void UInt32ToBytes(uint8_t bytes[], uint32_t n) { - bytes[0] = n & 0xff; - bytes[1] = (n >> 8) & 0xff; - bytes[2] = (n >> 16) & 0xff; - bytes[3] = (n >> 24) & 0xff; - } - - static bool CreateGUID(GUID *guid) { - InitOnce(); - guid->data1 = random(); - guid->data2 = (uint16_t)(random()); - guid->data3 = (uint16_t)(random()); - UInt32ToBytes(&guid->data4[0], random()); - UInt32ToBytes(&guid->data4[4], random()); - return true; - } - - private: - static void InitOnce() { - pthread_once(&once_control, &InitOnceImpl); - } - - static void InitOnceImpl() { - srandom(time(NULL)); - } - - static pthread_once_t once_control; -}; - -pthread_once_t GUIDGenerator::once_control = PTHREAD_ONCE_INIT; - -bool CreateGUID(GUID *guid) { - return GUIDGenerator::CreateGUID(guid); -} - -// Parse guid to string. -bool GUIDToString(const GUID *guid, char *buf, int buf_len) { - // Should allow more space the the max length of GUID. - assert(buf_len > kGUIDStringLength); - int num = snprintf(buf, buf_len, kGUIDFormatString, - guid->data1, guid->data2, guid->data3, - GUIDGenerator::BytesToUInt32(&(guid->data4[0])), - GUIDGenerator::BytesToUInt32(&(guid->data4[4]))); - if (num != kGUIDStringLength) - return false; - - buf[num] = '\0'; - return true; -} diff --git a/TMessagesProj/jni/breakpad/common/linux/guid_creator.h b/TMessagesProj/jni/breakpad/common/linux/guid_creator.h deleted file mode 100644 index c86d856c4..000000000 --- a/TMessagesProj/jni/breakpad/common/linux/guid_creator.h +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) 2006, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef COMMON_LINUX_GUID_CREATOR_H__ -#define COMMON_LINUX_GUID_CREATOR_H__ - -#include "google_breakpad/common/minidump_format.h" - -typedef MDGUID GUID; - -// Format string for parsing GUID. -#define kGUIDFormatString "%08x-%04x-%04x-%08x-%08x" -// Length of GUID string. Don't count the ending '\0'. -#define kGUIDStringLength 36 - -// Create a guid. -bool CreateGUID(GUID *guid); - -// Get the string from guid. -bool GUIDToString(const GUID *guid, char *buf, int buf_len); - -#endif diff --git a/TMessagesProj/jni/breakpad/common/linux/ignore_ret.h b/TMessagesProj/jni/breakpad/common/linux/ignore_ret.h deleted file mode 100644 index f60384bba..000000000 --- a/TMessagesProj/jni/breakpad/common/linux/ignore_ret.h +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2012 Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef COMMON_LINUX_IGNORE_RET_H_ -#define COMMON_LINUX_IGNORE_RET_H_ - -// Some compilers are prone to warn about unused return values. In cases where -// either a) the call cannot fail, or b) there is nothing that can be done when -// the call fails, IGNORE_RET() can be used to mark the return code as ignored. -// This avoids spurious compiler warnings. - -#define IGNORE_RET(x) do { if (x); } while (0) - -#endif // COMMON_LINUX_IGNORE_RET_H_ diff --git a/TMessagesProj/jni/breakpad/common/linux/linux_libc_support.cc b/TMessagesProj/jni/breakpad/common/linux/linux_libc_support.cc deleted file mode 100644 index 08b0325e6..000000000 --- a/TMessagesProj/jni/breakpad/common/linux/linux_libc_support.cc +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright (c) 2012, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// This source file provides replacements for libc functions that we need. If -// we call the libc functions directly we risk crashing in the dynamic linker -// as it tries to resolve uncached PLT entries. - -#include "common/linux/linux_libc_support.h" - -#include - -extern "C" { - -size_t my_strlen(const char* s) { - size_t len = 0; - while (*s++) len++; - return len; -} - -int my_strcmp(const char* a, const char* b) { - for (;;) { - if (*a < *b) - return -1; - else if (*a > *b) - return 1; - else if (*a == 0) - return 0; - a++; - b++; - } -} - -int my_strncmp(const char* a, const char* b, size_t len) { - for (size_t i = 0; i < len; ++i) { - if (*a < *b) - return -1; - else if (*a > *b) - return 1; - else if (*a == 0) - return 0; - a++; - b++; - } - - return 0; -} - -// Parse a non-negative integer. -// result: (output) the resulting non-negative integer -// s: a NUL terminated string -// Return true iff successful. -bool my_strtoui(int* result, const char* s) { - if (*s == 0) - return false; - int r = 0; - for (;; s++) { - if (*s == 0) - break; - const int old_r = r; - r *= 10; - if (*s < '0' || *s > '9') - return false; - r += *s - '0'; - if (r < old_r) - return false; - } - - *result = r; - return true; -} - -// Return the length of the given unsigned integer when expressed in base 10. -unsigned my_uint_len(uintmax_t i) { - if (!i) - return 1; - - int len = 0; - while (i) { - len++; - i /= 10; - } - - return len; -} - -// Convert an unsigned integer to a string -// output: (output) the resulting string is written here. This buffer must be -// large enough to hold the resulting string. Call |my_uint_len| to get the -// required length. -// i: the unsigned integer to serialise. -// i_len: the length of the integer in base 10 (see |my_uint_len|). -void my_uitos(char* output, uintmax_t i, unsigned i_len) { - for (unsigned index = i_len; index; --index, i /= 10) - output[index - 1] = '0' + (i % 10); -} - -const char* my_strchr(const char* haystack, char needle) { - while (*haystack && *haystack != needle) - haystack++; - if (*haystack == needle) - return haystack; - return (const char*) 0; -} - -const char* my_strrchr(const char* haystack, char needle) { - const char* ret = NULL; - while (*haystack) { - if (*haystack == needle) - ret = haystack; - haystack++; - } - return ret; -} - -void* my_memchr(const void* src, int needle, size_t src_len) { - const unsigned char* p = (const unsigned char*)src; - const unsigned char* p_end = p + src_len; - for (; p < p_end; ++p) { - if (*p == needle) - return (void*)p; - } - return NULL; -} - -// Read a hex value -// result: (output) the resulting value -// s: a string -// Returns a pointer to the first invalid charactor. -const char* my_read_hex_ptr(uintptr_t* result, const char* s) { - uintptr_t r = 0; - - for (;; ++s) { - if (*s >= '0' && *s <= '9') { - r <<= 4; - r += *s - '0'; - } else if (*s >= 'a' && *s <= 'f') { - r <<= 4; - r += (*s - 'a') + 10; - } else if (*s >= 'A' && *s <= 'F') { - r <<= 4; - r += (*s - 'A') + 10; - } else { - break; - } - } - - *result = r; - return s; -} - -const char* my_read_decimal_ptr(uintptr_t* result, const char* s) { - uintptr_t r = 0; - - for (;; ++s) { - if (*s >= '0' && *s <= '9') { - r *= 10; - r += *s - '0'; - } else { - break; - } - } - *result = r; - return s; -} - -void my_memset(void* ip, char c, size_t len) { - char* p = (char *) ip; - while (len--) - *p++ = c; -} - -size_t my_strlcpy(char* s1, const char* s2, size_t len) { - size_t pos1 = 0; - size_t pos2 = 0; - - while (s2[pos2] != '\0') { - if (pos1 + 1 < len) { - s1[pos1] = s2[pos2]; - pos1++; - } - pos2++; - } - if (len > 0) - s1[pos1] = '\0'; - - return pos2; -} - -size_t my_strlcat(char* s1, const char* s2, size_t len) { - size_t pos1 = 0; - - while (pos1 < len && s1[pos1] != '\0') - pos1++; - - if (pos1 == len) - return pos1; - - return pos1 + my_strlcpy(s1 + pos1, s2, len - pos1); -} - -int my_isspace(int ch) { - // Matches the C locale. - const char spaces[] = " \t\f\n\r\t\v"; - for (size_t i = 0; i < sizeof(spaces); i++) { - if (ch == spaces[i]) - return 1; - } - return 0; -} - -} // extern "C" diff --git a/TMessagesProj/jni/breakpad/common/linux/linux_libc_support.h b/TMessagesProj/jni/breakpad/common/linux/linux_libc_support.h deleted file mode 100644 index ec5a8d6b6..000000000 --- a/TMessagesProj/jni/breakpad/common/linux/linux_libc_support.h +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) 2009, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// This header provides replacements for libc functions that we need. We if -// call the libc functions directly we risk crashing in the dynamic linker as -// it tries to resolve uncached PLT entries. - -#ifndef CLIENT_LINUX_LINUX_LIBC_SUPPORT_H_ -#define CLIENT_LINUX_LINUX_LIBC_SUPPORT_H_ - -#include -#include -#include - -extern "C" { - -extern size_t my_strlen(const char* s); - -extern int my_strcmp(const char* a, const char* b); - -extern int my_strncmp(const char* a, const char* b, size_t len); - -// Parse a non-negative integer. -// result: (output) the resulting non-negative integer -// s: a NUL terminated string -// Return true iff successful. -extern bool my_strtoui(int* result, const char* s); - -// Return the length of the given unsigned integer when expressed in base 10. -extern unsigned my_uint_len(uintmax_t i); - -// Convert an unsigned integer to a string -// output: (output) the resulting string is written here. This buffer must be -// large enough to hold the resulting string. Call |my_uint_len| to get the -// required length. -// i: the unsigned integer to serialise. -// i_len: the length of the integer in base 10 (see |my_uint_len|). -extern void my_uitos(char* output, uintmax_t i, unsigned i_len); - -extern const char* my_strchr(const char* haystack, char needle); - -extern const char* my_strrchr(const char* haystack, char needle); - -// Read a hex value -// result: (output) the resulting value -// s: a string -// Returns a pointer to the first invalid charactor. -extern const char* my_read_hex_ptr(uintptr_t* result, const char* s); - -extern const char* my_read_decimal_ptr(uintptr_t* result, const char* s); - -extern void my_memset(void* ip, char c, size_t len); - -extern void* my_memchr(const void* src, int c, size_t len); - -// The following are considered safe to use in a compromised environment. -// Besides, this gives the compiler an opportunity to optimize their calls. -#define my_memcpy memcpy -#define my_memmove memmove -#define my_memcmp memcmp - -extern size_t my_strlcpy(char* s1, const char* s2, size_t len); - -extern size_t my_strlcat(char* s1, const char* s2, size_t len); - -extern int my_isspace(int ch); - -} // extern "C" - -#endif // CLIENT_LINUX_LINUX_LIBC_SUPPORT_H_ diff --git a/TMessagesProj/jni/breakpad/common/linux/memory_mapped_file.cc b/TMessagesProj/jni/breakpad/common/linux/memory_mapped_file.cc deleted file mode 100644 index 592b66c8d..000000000 --- a/TMessagesProj/jni/breakpad/common/linux/memory_mapped_file.cc +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) 2011, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// memory_mapped_file.cc: Implement google_breakpad::MemoryMappedFile. -// See memory_mapped_file.h for details. - -#include "common/linux/memory_mapped_file.h" - -#include -#include -#if defined(__ANDROID__) -#include -#endif -#include - -#include "common/memory_range.h" -#include "third_party/lss/linux_syscall_support.h" - -namespace google_breakpad { - -MemoryMappedFile::MemoryMappedFile() {} - -MemoryMappedFile::MemoryMappedFile(const char* path, size_t offset) { - Map(path, offset); -} - -MemoryMappedFile::~MemoryMappedFile() { - Unmap(); -} - -#include - -bool MemoryMappedFile::Map(const char* path, size_t offset) { - Unmap(); - - int fd = sys_open(path, O_RDONLY, 0); - if (fd == -1) { - return false; - } - -#if defined(__x86_64__) || defined(__aarch64__) || \ - (defined(__mips__) && _MIPS_SIM == _ABI64) - - struct kernel_stat st; - if (sys_fstat(fd, &st) == -1 || st.st_size < 0) { -#else - struct kernel_stat64 st; - if (sys_fstat64(fd, &st) == -1 || st.st_size < 0) { -#endif - sys_close(fd); - return false; - } - - // Strangely file size can be negative, but we check above that it is not. - size_t file_len = static_cast(st.st_size); - // If the file does not extend beyond the offset, simply use an empty - // MemoryRange and return true. Don't bother to call mmap() - // even though mmap() can handle an empty file on some platforms. - if (offset >= file_len) { - sys_close(fd); - return true; - } - -#if defined(__x86_64__) || defined(__aarch64__) || \ - (defined(__mips__) && _MIPS_SIM == _ABI64) - void* data = sys_mmap(NULL, file_len, PROT_READ, MAP_PRIVATE, fd, offset); -#else - if ((offset & 4095) != 0) { - // Not page aligned. - sys_close(fd); - return false; - } - void* data = sys_mmap2( - NULL, file_len, PROT_READ, MAP_PRIVATE, fd, offset >> 12); -#endif - sys_close(fd); - if (data == MAP_FAILED) { - return false; - } - - content_.Set(data, file_len - offset); - return true; -} - -void MemoryMappedFile::Unmap() { - if (content_.data()) { - sys_munmap(const_cast(content_.data()), content_.length()); - content_.Set(NULL, 0); - } -} - -} // namespace google_breakpad diff --git a/TMessagesProj/jni/breakpad/common/linux/memory_mapped_file.h b/TMessagesProj/jni/breakpad/common/linux/memory_mapped_file.h deleted file mode 100644 index fa660cc91..000000000 --- a/TMessagesProj/jni/breakpad/common/linux/memory_mapped_file.h +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) 2011, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// memory_mapped_file.h: Define the google_breakpad::MemoryMappedFile -// class, which maps a file into memory for read-only access. - -#ifndef COMMON_LINUX_MEMORY_MAPPED_FILE_H_ -#define COMMON_LINUX_MEMORY_MAPPED_FILE_H_ - -#include -#include "common/basictypes.h" -#include "common/memory_range.h" - -namespace google_breakpad { - -// A utility class for mapping a file into memory for read-only access of -// the file content. Its implementation avoids calling into libc functions -// by directly making system calls for open, close, mmap, and munmap. -class MemoryMappedFile { - public: - MemoryMappedFile(); - - // Constructor that calls Map() to map a file at |path| into memory. - // If Map() fails, the object behaves as if it is default constructed. - MemoryMappedFile(const char* path, size_t offset); - - ~MemoryMappedFile(); - - // Maps a file at |path| into memory, which can then be accessed via - // content() as a MemoryRange object or via data(), and returns true on - // success. Mapping an empty file will succeed but with data() and size() - // returning NULL and 0, respectively. An existing mapping is unmapped - // before a new mapping is created. - bool Map(const char* path, size_t offset); - - // Unmaps the memory for the mapped file. It's a no-op if no file is - // mapped. - void Unmap(); - - // Returns a MemoryRange object that covers the memory for the mapped - // file. The MemoryRange object is empty if no file is mapped. - const MemoryRange& content() const { return content_; } - - // Returns a pointer to the beginning of the memory for the mapped file. - // or NULL if no file is mapped or the mapped file is empty. - const void* data() const { return content_.data(); } - - // Returns the size in bytes of the mapped file, or zero if no file - // is mapped. - size_t size() const { return content_.length(); } - - private: - // Mapped file content as a MemoryRange object. - MemoryRange content_; - - DISALLOW_COPY_AND_ASSIGN(MemoryMappedFile); -}; - -} // namespace google_breakpad - -#endif // COMMON_LINUX_MEMORY_MAPPED_FILE_H_ diff --git a/TMessagesProj/jni/breakpad/common/linux/safe_readlink.cc b/TMessagesProj/jni/breakpad/common/linux/safe_readlink.cc deleted file mode 100644 index 870c28af3..000000000 --- a/TMessagesProj/jni/breakpad/common/linux/safe_readlink.cc +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2011, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// safe_readlink.cc: Implement google_breakpad::SafeReadLink. -// See safe_readlink.h for details. - -#include - -#include "third_party/lss/linux_syscall_support.h" - -namespace google_breakpad { - -bool SafeReadLink(const char* path, char* buffer, size_t buffer_size) { - // sys_readlink() does not add a NULL byte to |buffer|. In order to return - // a NULL-terminated string in |buffer|, |buffer_size| should be at least - // one byte longer than the expected path length. Also, sys_readlink() - // returns the actual path length on success, which does not count the - // NULL byte, so |result_size| should be less than |buffer_size|. - ssize_t result_size = sys_readlink(path, buffer, buffer_size); - if (result_size >= 0 && static_cast(result_size) < buffer_size) { - buffer[result_size] = '\0'; - return true; - } - return false; -} - -} // namespace google_breakpad diff --git a/TMessagesProj/jni/breakpad/common/linux/safe_readlink.h b/TMessagesProj/jni/breakpad/common/linux/safe_readlink.h deleted file mode 100644 index 4ae131b58..000000000 --- a/TMessagesProj/jni/breakpad/common/linux/safe_readlink.h +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2011, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// safe_readlink.h: Define the google_breakpad::SafeReadLink function, -// which wraps sys_readlink and gurantees the result is NULL-terminated. - -#ifndef COMMON_LINUX_SAFE_READLINK_H_ -#define COMMON_LINUX_SAFE_READLINK_H_ - -#include - -namespace google_breakpad { - -// This function wraps sys_readlink() and performs the same functionalty, -// but guarantees |buffer| is NULL-terminated if sys_readlink() returns -// no error. It takes the same arguments as sys_readlink(), but unlike -// sys_readlink(), it returns true on success. -// -// |buffer_size| specifies the size of |buffer| in bytes. As this function -// always NULL-terminates |buffer| on success, |buffer_size| should be -// at least one byte longer than the expected path length (e.g. PATH_MAX, -// which is typically defined as the maximum length of a path name -// including the NULL byte). -// -// The implementation of this function calls sys_readlink() instead of -// readlink(), it can thus be used in the context where calling to libc -// functions is discouraged. -bool SafeReadLink(const char* path, char* buffer, size_t buffer_size); - -// Same as the three-argument version of SafeReadLink() but deduces the -// size of |buffer| if it is a char array of known size. -template -bool SafeReadLink(const char* path, char (&buffer)[N]) { - return SafeReadLink(path, buffer, sizeof(buffer)); -} - -} // namespace google_breakpad - -#endif // COMMON_LINUX_SAFE_READLINK_H_ diff --git a/TMessagesProj/jni/breakpad/common/md5.cc b/TMessagesProj/jni/breakpad/common/md5.cc deleted file mode 100644 index a0d9a1bdd..000000000 --- a/TMessagesProj/jni/breakpad/common/md5.cc +++ /dev/null @@ -1,251 +0,0 @@ -/* - * written by Colin Plumb in 1993, no copyright is claimed. - * This code is in the public domain; do with it what you wish. - * - * Equivalent code is available from RSA Data Security, Inc. - * This code has been tested against that, and is equivalent, - * except that you don't need to include two pages of legalese - * with every copy. - * - * To compute the message digest of a chunk of bytes, declare an - * MD5Context structure, pass it to MD5Init, call MD5Update as - * needed on buffers full of bytes, and then call MD5Final, which - * will fill a supplied 16-byte array with the digest. - */ - -#include - -#include "common/md5.h" - -namespace google_breakpad { - -#ifndef WORDS_BIGENDIAN -#define byteReverse(buf, len) /* Nothing */ -#else -/* - * Note: this code is harmless on little-endian machines. - */ -static void byteReverse(unsigned char *buf, unsigned longs) -{ - u32 t; - do { - t = (u32) ((unsigned) buf[3] << 8 | buf[2]) << 16 | - ((unsigned) buf[1] << 8 | buf[0]); - *(u32 *) buf = t; - buf += 4; - } while (--longs); -} -#endif - -static void MD5Transform(u32 buf[4], u32 const in[16]); - -/* - * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious - * initialization constants. - */ -void MD5Init(struct MD5Context *ctx) -{ - ctx->buf[0] = 0x67452301; - ctx->buf[1] = 0xefcdab89; - ctx->buf[2] = 0x98badcfe; - ctx->buf[3] = 0x10325476; - - ctx->bits[0] = 0; - ctx->bits[1] = 0; -} - -/* - * Update context to reflect the concatenation of another buffer full - * of bytes. - */ -void MD5Update(struct MD5Context *ctx, unsigned char const *buf, size_t len) -{ - u32 t; - - /* Update bitcount */ - - t = ctx->bits[0]; - if ((ctx->bits[0] = t + ((u32) len << 3)) < t) - ctx->bits[1]++; /* Carry from low to high */ - ctx->bits[1] += len >> 29; - - t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ - - /* Handle any leading odd-sized chunks */ - - if (t) { - unsigned char *p = (unsigned char *) ctx->in + t; - - t = 64 - t; - if (len < t) { - memcpy(p, buf, len); - return; - } - memcpy(p, buf, t); - byteReverse(ctx->in, 16); - MD5Transform(ctx->buf, (u32 *) ctx->in); - buf += t; - len -= t; - } - /* Process data in 64-byte chunks */ - - while (len >= 64) { - memcpy(ctx->in, buf, 64); - byteReverse(ctx->in, 16); - MD5Transform(ctx->buf, (u32 *) ctx->in); - buf += 64; - len -= 64; - } - - /* Handle any remaining bytes of data. */ - - memcpy(ctx->in, buf, len); -} - -/* - * Final wrapup - pad to 64-byte boundary with the bit pattern - * 1 0* (64-bit count of bits processed, MSB-first) - */ -void MD5Final(unsigned char digest[16], struct MD5Context *ctx) -{ - unsigned count; - unsigned char *p; - - /* Compute number of bytes mod 64 */ - count = (ctx->bits[0] >> 3) & 0x3F; - - /* Set the first char of padding to 0x80. This is safe since there is - always at least one byte free */ - p = ctx->in + count; - *p++ = 0x80; - - /* Bytes of padding needed to make 64 bytes */ - count = 64 - 1 - count; - - /* Pad out to 56 mod 64 */ - if (count < 8) { - /* Two lots of padding: Pad the first block to 64 bytes */ - memset(p, 0, count); - byteReverse(ctx->in, 16); - MD5Transform(ctx->buf, (u32 *) ctx->in); - - /* Now fill the next block with 56 bytes */ - memset(ctx->in, 0, 56); - } else { - /* Pad block to 56 bytes */ - memset(p, 0, count - 8); - } - byteReverse(ctx->in, 14); - - /* Append length in bits and transform */ - ((u32 *) ctx->in)[14] = ctx->bits[0]; - ((u32 *) ctx->in)[15] = ctx->bits[1]; - - MD5Transform(ctx->buf, (u32 *) ctx->in); - byteReverse((unsigned char *) ctx->buf, 4); - memcpy(digest, ctx->buf, 16); - memset(ctx, 0, sizeof(*ctx)); /* In case it's sensitive */ -} - -/* The four core functions - F1 is optimized somewhat */ - -/* #define F1(x, y, z) (x & y | ~x & z) */ -#define F1(x, y, z) (z ^ (x & (y ^ z))) -#define F2(x, y, z) F1(z, x, y) -#define F3(x, y, z) (x ^ y ^ z) -#define F4(x, y, z) (y ^ (x | ~z)) - -/* This is the central step in the MD5 algorithm. */ -#define MD5STEP(f, w, x, y, z, data, s) \ - ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) - -/* - * The core of the MD5 algorithm, this alters an existing MD5 hash to - * reflect the addition of 16 longwords of new data. MD5Update blocks - * the data and converts bytes into longwords for this routine. - */ -static void MD5Transform(u32 buf[4], u32 const in[16]) -{ - u32 a, b, c, d; - - a = buf[0]; - b = buf[1]; - c = buf[2]; - d = buf[3]; - - MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); - MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); - MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); - MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); - MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); - MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); - MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); - MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); - MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); - MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); - MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); - MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); - MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); - MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); - MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); - MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); - - MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); - MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); - MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); - MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); - MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); - MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); - MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); - MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); - MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); - MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); - MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); - MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); - MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); - MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); - MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); - MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); - - MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); - MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); - MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); - MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); - MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); - MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); - MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); - MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); - MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); - MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); - MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); - MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); - MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); - MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); - MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); - MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); - - MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); - MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); - MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); - MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); - MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); - MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); - MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); - MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); - MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); - MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); - MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); - MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); - MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); - MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); - MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); - MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); - - buf[0] += a; - buf[1] += b; - buf[2] += c; - buf[3] += d; -} - -} // namespace google_breakpad - diff --git a/TMessagesProj/jni/breakpad/common/md5.h b/TMessagesProj/jni/breakpad/common/md5.h deleted file mode 100644 index 2ab0ab95a..000000000 --- a/TMessagesProj/jni/breakpad/common/md5.h +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2007 Google Inc. All Rights Reserved. -// Author: liuli@google.com (Liu Li) -#ifndef COMMON_MD5_H__ -#define COMMON_MD5_H__ - -#include - -namespace google_breakpad { - -typedef uint32_t u32; -typedef uint8_t u8; - -struct MD5Context { - u32 buf[4]; - u32 bits[2]; - u8 in[64]; -}; - -void MD5Init(struct MD5Context *ctx); - -void MD5Update(struct MD5Context *ctx, unsigned char const *buf, size_t len); - -void MD5Final(unsigned char digest[16], struct MD5Context *ctx); - -} // namespace google_breakpad - -#endif // COMMON_MD5_H__ diff --git a/TMessagesProj/jni/breakpad/common/memory.h b/TMessagesProj/jni/breakpad/common/memory.h deleted file mode 100644 index d6aa137d3..000000000 --- a/TMessagesProj/jni/breakpad/common/memory.h +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright (c) 2009, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef GOOGLE_BREAKPAD_COMMON_MEMORY_H_ -#define GOOGLE_BREAKPAD_COMMON_MEMORY_H_ - -#include -#include -#include -#include - -#include -#include - -#if defined(MEMORY_SANITIZER) -#include -#endif - -#ifdef __APPLE__ -#define sys_mmap mmap -#define sys_mmap2 mmap -#define sys_munmap munmap -#define MAP_ANONYMOUS MAP_ANON -#else -#include "third_party/lss/linux_syscall_support.h" -#endif - -namespace google_breakpad { - -// This is very simple allocator which fetches pages from the kernel directly. -// Thus, it can be used even when the heap may be corrupted. -// -// There is no free operation. The pages are only freed when the object is -// destroyed. -class PageAllocator { - public: - PageAllocator() - : page_size_(getpagesize()), - last_(NULL), - current_page_(NULL), - page_offset_(0) { - } - - ~PageAllocator() { - FreeAll(); - } - - void *Alloc(size_t bytes) { - if (!bytes) - return NULL; - - if (current_page_ && page_size_ - page_offset_ >= bytes) { - uint8_t *const ret = current_page_ + page_offset_; - page_offset_ += bytes; - if (page_offset_ == page_size_) { - page_offset_ = 0; - current_page_ = NULL; - } - - return ret; - } - - const size_t pages = - (bytes + sizeof(PageHeader) + page_size_ - 1) / page_size_; - uint8_t *const ret = GetNPages(pages); - if (!ret) - return NULL; - - page_offset_ = - (page_size_ - (page_size_ * pages - (bytes + sizeof(PageHeader)))) % - page_size_; - current_page_ = page_offset_ ? ret + page_size_ * (pages - 1) : NULL; - - return ret + sizeof(PageHeader); - } - - // Checks whether the page allocator owns the passed-in pointer. - // This method exists for testing pursposes only. - bool OwnsPointer(const void* p) { - for (PageHeader* header = last_; header; header = header->next) { - const char* current = reinterpret_cast(header); - if ((p >= current) && (p < current + header->num_pages * page_size_)) - return true; - } - - return false; - } - - private: - uint8_t *GetNPages(size_t num_pages) { -#if defined(__x86_64__) || defined(__aarch64__) || defined(__aarch64__) || \ - ((defined(__mips__) && _MIPS_SIM == _ABI64)) - void *a = sys_mmap(NULL, page_size_ * num_pages, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); -#else - void *a = sys_mmap2(NULL, page_size_ * num_pages, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); -#endif - if (a == MAP_FAILED) - return NULL; - -#if defined(MEMORY_SANITIZER) - // We need to indicate to MSan that memory allocated through sys_mmap is - // initialized, since linux_syscall_support.h doesn't have MSan hooks. - __msan_unpoison(a, page_size_ * num_pages); -#endif - - struct PageHeader *header = reinterpret_cast(a); - header->next = last_; - header->num_pages = num_pages; - last_ = header; - - return reinterpret_cast(a); - } - - void FreeAll() { - PageHeader *next; - - for (PageHeader *cur = last_; cur; cur = next) { - next = cur->next; - sys_munmap(cur, cur->num_pages * page_size_); - } - } - - struct PageHeader { - PageHeader *next; // pointer to the start of the next set of pages. - size_t num_pages; // the number of pages in this set. - }; - - const size_t page_size_; - PageHeader *last_; - uint8_t *current_page_; - size_t page_offset_; -}; - -// Wrapper to use with STL containers -template -struct PageStdAllocator : public std::allocator { - typedef typename std::allocator::pointer pointer; - typedef typename std::allocator::size_type size_type; - - explicit PageStdAllocator(PageAllocator& allocator): allocator_(allocator) {} - template PageStdAllocator(const PageStdAllocator& other) - : allocator_(other.allocator_) {} - - inline pointer allocate(size_type n, const void* = 0) { - return static_cast(allocator_.Alloc(sizeof(T) * n)); - } - - inline void deallocate(pointer, size_type) { - // The PageAllocator doesn't free. - } - - template struct rebind { - typedef PageStdAllocator other; - }; - - private: - // Silly workaround for the gcc from Android's ndk (gcc 4.6), which will - // otherwise complain that `other.allocator_` is private in the constructor - // code. - template friend struct PageStdAllocator; - - PageAllocator& allocator_; -}; - -// A wasteful vector is a std::vector, except that it allocates memory from a -// PageAllocator. It's wasteful because, when resizing, it always allocates a -// whole new array since the PageAllocator doesn't support realloc. -template -class wasteful_vector : public std::vector > { - public: - wasteful_vector(PageAllocator* allocator, unsigned size_hint = 16) - : std::vector >(PageStdAllocator(*allocator)) { - std::vector >::reserve(size_hint); - } -}; - -} // namespace google_breakpad - -inline void* operator new(size_t nbytes, - google_breakpad::PageAllocator& allocator) { - return allocator.Alloc(nbytes); -} - -#endif // GOOGLE_BREAKPAD_COMMON_MEMORY_H_ diff --git a/TMessagesProj/jni/breakpad/common/memory_range.h b/TMessagesProj/jni/breakpad/common/memory_range.h deleted file mode 100644 index 41dd2da62..000000000 --- a/TMessagesProj/jni/breakpad/common/memory_range.h +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright (c) 2011, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// memory_range.h: Define the google_breakpad::MemoryRange class, which -// is a lightweight wrapper with a pointer and a length to encapsulate -// a contiguous range of memory. - -#ifndef COMMON_MEMORY_RANGE_H_ -#define COMMON_MEMORY_RANGE_H_ - -#include - -#include "google_breakpad/common/breakpad_types.h" - -namespace google_breakpad { - -// A lightweight wrapper with a pointer and a length to encapsulate a -// contiguous range of memory. It provides helper methods for checked -// access of a subrange of the memory. Its implemementation does not -// allocate memory or call into libc functions, and is thus safer to use -// in a crashed environment. -class MemoryRange { - public: - MemoryRange() : data_(NULL), length_(0) {} - - MemoryRange(const void* data, size_t length) { - Set(data, length); - } - - // Returns true if this memory range contains no data. - bool IsEmpty() const { - // Set() guarantees that |length_| is zero if |data_| is NULL. - return length_ == 0; - } - - // Resets to an empty range. - void Reset() { - data_ = NULL; - length_ = 0; - } - - // Sets this memory range to point to |data| and its length to |length|. - void Set(const void* data, size_t length) { - data_ = reinterpret_cast(data); - // Always set |length_| to zero if |data_| is NULL. - length_ = data ? length : 0; - } - - // Returns true if this range covers a subrange of |sub_length| bytes - // at |sub_offset| bytes of this memory range, or false otherwise. - bool Covers(size_t sub_offset, size_t sub_length) const { - // The following checks verify that: - // 1. sub_offset is within [ 0 .. length_ - 1 ] - // 2. sub_offset + sub_length is within - // [ sub_offset .. length_ ] - return sub_offset < length_ && - sub_offset + sub_length >= sub_offset && - sub_offset + sub_length <= length_; - } - - // Returns a raw data pointer to a subrange of |sub_length| bytes at - // |sub_offset| bytes of this memory range, or NULL if the subrange - // is out of bounds. - const void* GetData(size_t sub_offset, size_t sub_length) const { - return Covers(sub_offset, sub_length) ? (data_ + sub_offset) : NULL; - } - - // Same as the two-argument version of GetData() but uses sizeof(DataType) - // as the subrange length and returns an |DataType| pointer for convenience. - template - const DataType* GetData(size_t sub_offset) const { - return reinterpret_cast( - GetData(sub_offset, sizeof(DataType))); - } - - // Returns a raw pointer to the |element_index|-th element of an array - // of elements of length |element_size| starting at |sub_offset| bytes - // of this memory range, or NULL if the element is out of bounds. - const void* GetArrayElement(size_t element_offset, - size_t element_size, - unsigned element_index) const { - size_t sub_offset = element_offset + element_index * element_size; - return GetData(sub_offset, element_size); - } - - // Same as the three-argument version of GetArrayElement() but deduces - // the element size using sizeof(ElementType) and returns an |ElementType| - // pointer for convenience. - template - const ElementType* GetArrayElement(size_t element_offset, - unsigned element_index) const { - return reinterpret_cast( - GetArrayElement(element_offset, sizeof(ElementType), element_index)); - } - - // Returns a subrange of |sub_length| bytes at |sub_offset| bytes of - // this memory range, or an empty range if the subrange is out of bounds. - MemoryRange Subrange(size_t sub_offset, size_t sub_length) const { - return Covers(sub_offset, sub_length) ? - MemoryRange(data_ + sub_offset, sub_length) : MemoryRange(); - } - - // Returns a pointer to the beginning of this memory range. - const uint8_t* data() const { return data_; } - - // Returns the length, in bytes, of this memory range. - size_t length() const { return length_; } - - private: - // Pointer to the beginning of this memory range. - const uint8_t* data_; - - // Length, in bytes, of this memory range. - size_t length_; -}; - -} // namespace google_breakpad - -#endif // COMMON_MEMORY_RANGE_H_ diff --git a/TMessagesProj/jni/breakpad/common/minidump_type_helper.h b/TMessagesProj/jni/breakpad/common/minidump_type_helper.h deleted file mode 100644 index 5a7d5a6a8..000000000 --- a/TMessagesProj/jni/breakpad/common/minidump_type_helper.h +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) 2014, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef GOOGLE_BREAKPAD_COMMON_MINIDUMP_TYPE_HELPER_H_ -#define GOOGLE_BREAKPAD_COMMON_MINIDUMP_TYPE_HELPER_H_ - -#include - -#include "google_breakpad/common/minidump_format.h" - -namespace google_breakpad { - -template -struct MDTypeHelper; - -template <> -struct MDTypeHelper { - typedef MDRawDebug32 MDRawDebug; - typedef MDRawLinkMap32 MDRawLinkMap; -}; - -template <> -struct MDTypeHelper { - typedef MDRawDebug64 MDRawDebug; - typedef MDRawLinkMap64 MDRawLinkMap; -}; - -} // namespace google_breakpad - -#endif // GOOGLE_BREAKPAD_COMMON_MINIDUMP_TYPE_HELPER_H_ diff --git a/TMessagesProj/jni/breakpad/common/scoped_ptr.h b/TMessagesProj/jni/breakpad/common/scoped_ptr.h deleted file mode 100644 index d137c1868..000000000 --- a/TMessagesProj/jni/breakpad/common/scoped_ptr.h +++ /dev/null @@ -1,404 +0,0 @@ -// Copyright 2013 Google Inc. 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// Scopers help you manage ownership of a pointer, helping you easily manage the -// a pointer within a scope, and automatically destroying the pointer at the -// end of a scope. There are two main classes you will use, which correspond -// to the operators new/delete and new[]/delete[]. -// -// Example usage (scoped_ptr): -// { -// scoped_ptr foo(new Foo("wee")); -// } // foo goes out of scope, releasing the pointer with it. -// -// { -// scoped_ptr foo; // No pointer managed. -// foo.reset(new Foo("wee")); // Now a pointer is managed. -// foo.reset(new Foo("wee2")); // Foo("wee") was destroyed. -// foo.reset(new Foo("wee3")); // Foo("wee2") was destroyed. -// foo->Method(); // Foo::Method() called. -// foo.get()->Method(); // Foo::Method() called. -// SomeFunc(foo.release()); // SomeFunc takes ownership, foo no longer -// // manages a pointer. -// foo.reset(new Foo("wee4")); // foo manages a pointer again. -// foo.reset(); // Foo("wee4") destroyed, foo no longer -// // manages a pointer. -// } // foo wasn't managing a pointer, so nothing was destroyed. -// -// Example usage (scoped_array): -// { -// scoped_array foo(new Foo[100]); -// foo.get()->Method(); // Foo::Method on the 0th element. -// foo[10].Method(); // Foo::Method on the 10th element. -// } - -#ifndef COMMON_SCOPED_PTR_H_ -#define COMMON_SCOPED_PTR_H_ - -// This is an implementation designed to match the anticipated future TR2 -// implementation of the scoped_ptr class, and its closely-related brethren, -// scoped_array, scoped_ptr_malloc. - -#include -#include -#include - -namespace google_breakpad { - -// A scoped_ptr is like a T*, except that the destructor of scoped_ptr -// automatically deletes the pointer it holds (if any). -// That is, scoped_ptr owns the T object that it points to. -// Like a T*, a scoped_ptr may hold either NULL or a pointer to a T object. -// Also like T*, scoped_ptr is thread-compatible, and once you -// dereference it, you get the threadsafety guarantees of T. -// -// The size of a scoped_ptr is small: -// sizeof(scoped_ptr) == sizeof(C*) -template -class scoped_ptr { - public: - - // The element type - typedef C element_type; - - // Constructor. Defaults to initializing with NULL. - // There is no way to create an uninitialized scoped_ptr. - // The input parameter must be allocated with new. - explicit scoped_ptr(C* p = NULL) : ptr_(p) { } - - // Destructor. If there is a C object, delete it. - // We don't need to test ptr_ == NULL because C++ does that for us. - ~scoped_ptr() { - enum { type_must_be_complete = sizeof(C) }; - delete ptr_; - } - - // Reset. Deletes the current owned object, if any. - // Then takes ownership of a new object, if given. - // this->reset(this->get()) works. - void reset(C* p = NULL) { - if (p != ptr_) { - enum { type_must_be_complete = sizeof(C) }; - delete ptr_; - ptr_ = p; - } - } - - // Accessors to get the owned object. - // operator* and operator-> will assert() if there is no current object. - C& operator*() const { - assert(ptr_ != NULL); - return *ptr_; - } - C* operator->() const { - assert(ptr_ != NULL); - return ptr_; - } - C* get() const { return ptr_; } - - // Comparison operators. - // These return whether two scoped_ptr refer to the same object, not just to - // two different but equal objects. - bool operator==(C* p) const { return ptr_ == p; } - bool operator!=(C* p) const { return ptr_ != p; } - - // Swap two scoped pointers. - void swap(scoped_ptr& p2) { - C* tmp = ptr_; - ptr_ = p2.ptr_; - p2.ptr_ = tmp; - } - - // Release a pointer. - // The return value is the current pointer held by this object. - // If this object holds a NULL pointer, the return value is NULL. - // After this operation, this object will hold a NULL pointer, - // and will not own the object any more. - C* release() { - C* retVal = ptr_; - ptr_ = NULL; - return retVal; - } - - private: - C* ptr_; - - // Forbid comparison of scoped_ptr types. If C2 != C, it totally doesn't - // make sense, and if C2 == C, it still doesn't make sense because you should - // never have the same object owned by two different scoped_ptrs. - template bool operator==(scoped_ptr const& p2) const; - template bool operator!=(scoped_ptr const& p2) const; - - // Disallow evil constructors - scoped_ptr(const scoped_ptr&); - void operator=(const scoped_ptr&); -}; - -// Free functions -template -void swap(scoped_ptr& p1, scoped_ptr& p2) { - p1.swap(p2); -} - -template -bool operator==(C* p1, const scoped_ptr& p2) { - return p1 == p2.get(); -} - -template -bool operator!=(C* p1, const scoped_ptr& p2) { - return p1 != p2.get(); -} - -// scoped_array is like scoped_ptr, except that the caller must allocate -// with new [] and the destructor deletes objects with delete []. -// -// As with scoped_ptr, a scoped_array either points to an object -// or is NULL. A scoped_array owns the object that it points to. -// scoped_array is thread-compatible, and once you index into it, -// the returned objects have only the threadsafety guarantees of T. -// -// Size: sizeof(scoped_array) == sizeof(C*) -template -class scoped_array { - public: - - // The element type - typedef C element_type; - - // Constructor. Defaults to intializing with NULL. - // There is no way to create an uninitialized scoped_array. - // The input parameter must be allocated with new []. - explicit scoped_array(C* p = NULL) : array_(p) { } - - // Destructor. If there is a C object, delete it. - // We don't need to test ptr_ == NULL because C++ does that for us. - ~scoped_array() { - enum { type_must_be_complete = sizeof(C) }; - delete[] array_; - } - - // Reset. Deletes the current owned object, if any. - // Then takes ownership of a new object, if given. - // this->reset(this->get()) works. - void reset(C* p = NULL) { - if (p != array_) { - enum { type_must_be_complete = sizeof(C) }; - delete[] array_; - array_ = p; - } - } - - // Get one element of the current object. - // Will assert() if there is no current object, or index i is negative. - C& operator[](ptrdiff_t i) const { - assert(i >= 0); - assert(array_ != NULL); - return array_[i]; - } - - // Get a pointer to the zeroth element of the current object. - // If there is no current object, return NULL. - C* get() const { - return array_; - } - - // Comparison operators. - // These return whether two scoped_array refer to the same object, not just to - // two different but equal objects. - bool operator==(C* p) const { return array_ == p; } - bool operator!=(C* p) const { return array_ != p; } - - // Swap two scoped arrays. - void swap(scoped_array& p2) { - C* tmp = array_; - array_ = p2.array_; - p2.array_ = tmp; - } - - // Release an array. - // The return value is the current pointer held by this object. - // If this object holds a NULL pointer, the return value is NULL. - // After this operation, this object will hold a NULL pointer, - // and will not own the object any more. - C* release() { - C* retVal = array_; - array_ = NULL; - return retVal; - } - - private: - C* array_; - - // Forbid comparison of different scoped_array types. - template bool operator==(scoped_array const& p2) const; - template bool operator!=(scoped_array const& p2) const; - - // Disallow evil constructors - scoped_array(const scoped_array&); - void operator=(const scoped_array&); -}; - -// Free functions -template -void swap(scoped_array& p1, scoped_array& p2) { - p1.swap(p2); -} - -template -bool operator==(C* p1, const scoped_array& p2) { - return p1 == p2.get(); -} - -template -bool operator!=(C* p1, const scoped_array& p2) { - return p1 != p2.get(); -} - -// This class wraps the c library function free() in a class that can be -// passed as a template argument to scoped_ptr_malloc below. -class ScopedPtrMallocFree { - public: - inline void operator()(void* x) const { - free(x); - } -}; - -// scoped_ptr_malloc<> is similar to scoped_ptr<>, but it accepts a -// second template argument, the functor used to free the object. - -template -class scoped_ptr_malloc { - public: - - // The element type - typedef C element_type; - - // Constructor. Defaults to initializing with NULL. - // There is no way to create an uninitialized scoped_ptr. - // The input parameter must be allocated with an allocator that matches the - // Free functor. For the default Free functor, this is malloc, calloc, or - // realloc. - explicit scoped_ptr_malloc(C* p = NULL): ptr_(p) {} - - // Destructor. If there is a C object, call the Free functor. - ~scoped_ptr_malloc() { - reset(); - } - - // Reset. Calls the Free functor on the current owned object, if any. - // Then takes ownership of a new object, if given. - // this->reset(this->get()) works. - void reset(C* p = NULL) { - if (ptr_ != p) { - FreeProc free_proc; - free_proc(ptr_); - ptr_ = p; - } - } - - // Get the current object. - // operator* and operator-> will cause an assert() failure if there is - // no current object. - C& operator*() const { - assert(ptr_ != NULL); - return *ptr_; - } - - C* operator->() const { - assert(ptr_ != NULL); - return ptr_; - } - - C* get() const { - return ptr_; - } - - // Comparison operators. - // These return whether a scoped_ptr_malloc and a plain pointer refer - // to the same object, not just to two different but equal objects. - // For compatibility with the boost-derived implementation, these - // take non-const arguments. - bool operator==(C* p) const { - return ptr_ == p; - } - - bool operator!=(C* p) const { - return ptr_ != p; - } - - // Swap two scoped pointers. - void swap(scoped_ptr_malloc & b) { - C* tmp = b.ptr_; - b.ptr_ = ptr_; - ptr_ = tmp; - } - - // Release a pointer. - // The return value is the current pointer held by this object. - // If this object holds a NULL pointer, the return value is NULL. - // After this operation, this object will hold a NULL pointer, - // and will not own the object any more. - C* release() { - C* tmp = ptr_; - ptr_ = NULL; - return tmp; - } - - private: - C* ptr_; - - // no reason to use these: each scoped_ptr_malloc should have its own object - template - bool operator==(scoped_ptr_malloc const& p) const; - template - bool operator!=(scoped_ptr_malloc const& p) const; - - // Disallow evil constructors - scoped_ptr_malloc(const scoped_ptr_malloc&); - void operator=(const scoped_ptr_malloc&); -}; - -template inline -void swap(scoped_ptr_malloc& a, scoped_ptr_malloc& b) { - a.swap(b); -} - -template inline -bool operator==(C* p, const scoped_ptr_malloc& b) { - return p == b.get(); -} - -template inline -bool operator!=(C* p, const scoped_ptr_malloc& b) { - return p != b.get(); -} - -} // namespace google_breakpad - -#endif // COMMON_SCOPED_PTR_H_ diff --git a/TMessagesProj/jni/breakpad/common/string_conversion.cc b/TMessagesProj/jni/breakpad/common/string_conversion.cc deleted file mode 100644 index 9c0d623fc..000000000 --- a/TMessagesProj/jni/breakpad/common/string_conversion.cc +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (c) 2006, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include - -#include "common/convert_UTF.h" -#include "common/scoped_ptr.h" -#include "common/string_conversion.h" -#include "common/using_std_string.h" - -namespace google_breakpad { - -using std::vector; - -void UTF8ToUTF16(const char *in, vector *out) { - size_t source_length = strlen(in); - const UTF8 *source_ptr = reinterpret_cast(in); - const UTF8 *source_end_ptr = source_ptr + source_length; - // Erase the contents and zero fill to the expected size - out->clear(); - out->insert(out->begin(), source_length, 0); - uint16_t *target_ptr = &(*out)[0]; - uint16_t *target_end_ptr = target_ptr + out->capacity() * sizeof(uint16_t); - ConversionResult result = ConvertUTF8toUTF16(&source_ptr, source_end_ptr, - &target_ptr, target_end_ptr, - strictConversion); - - // Resize to be the size of the # of converted characters + NULL - out->resize(result == conversionOK ? target_ptr - &(*out)[0] + 1: 0); -} - -int UTF8ToUTF16Char(const char *in, int in_length, uint16_t out[2]) { - const UTF8 *source_ptr = reinterpret_cast(in); - const UTF8 *source_end_ptr = source_ptr + sizeof(char); - uint16_t *target_ptr = out; - uint16_t *target_end_ptr = target_ptr + 2 * sizeof(uint16_t); - out[0] = out[1] = 0; - - // Process one character at a time - while (1) { - ConversionResult result = ConvertUTF8toUTF16(&source_ptr, source_end_ptr, - &target_ptr, target_end_ptr, - strictConversion); - - if (result == conversionOK) - return static_cast(source_ptr - reinterpret_cast(in)); - - // Add another character to the input stream and try again - source_ptr = reinterpret_cast(in); - ++source_end_ptr; - - if (source_end_ptr > reinterpret_cast(in) + in_length) - break; - } - - return 0; -} - -void UTF32ToUTF16(const wchar_t *in, vector *out) { - size_t source_length = wcslen(in); - const UTF32 *source_ptr = reinterpret_cast(in); - const UTF32 *source_end_ptr = source_ptr + source_length; - // Erase the contents and zero fill to the expected size - out->clear(); - out->insert(out->begin(), source_length, 0); - uint16_t *target_ptr = &(*out)[0]; - uint16_t *target_end_ptr = target_ptr + out->capacity() * sizeof(uint16_t); - ConversionResult result = ConvertUTF32toUTF16(&source_ptr, source_end_ptr, - &target_ptr, target_end_ptr, - strictConversion); - - // Resize to be the size of the # of converted characters + NULL - out->resize(result == conversionOK ? target_ptr - &(*out)[0] + 1: 0); -} - -void UTF32ToUTF16Char(wchar_t in, uint16_t out[2]) { - const UTF32 *source_ptr = reinterpret_cast(&in); - const UTF32 *source_end_ptr = source_ptr + 1; - uint16_t *target_ptr = out; - uint16_t *target_end_ptr = target_ptr + 2 * sizeof(uint16_t); - out[0] = out[1] = 0; - ConversionResult result = ConvertUTF32toUTF16(&source_ptr, source_end_ptr, - &target_ptr, target_end_ptr, - strictConversion); - - if (result != conversionOK) { - out[0] = out[1] = 0; - } -} - -static inline uint16_t Swap(uint16_t value) { - return (value >> 8) | static_cast(value << 8); -} - -string UTF16ToUTF8(const vector &in, bool swap) { - const UTF16 *source_ptr = &in[0]; - scoped_array source_buffer; - - // If we're to swap, we need to make a local copy and swap each byte pair - if (swap) { - int idx = 0; - source_buffer.reset(new uint16_t[in.size()]); - UTF16 *source_buffer_ptr = source_buffer.get(); - for (vector::const_iterator it = in.begin(); - it != in.end(); ++it, ++idx) - source_buffer_ptr[idx] = Swap(*it); - - source_ptr = source_buffer.get(); - } - - // The maximum expansion would be 4x the size of the input string. - const UTF16 *source_end_ptr = source_ptr + in.size(); - size_t target_capacity = in.size() * 4; - scoped_array target_buffer(new UTF8[target_capacity]); - UTF8 *target_ptr = target_buffer.get(); - UTF8 *target_end_ptr = target_ptr + target_capacity; - ConversionResult result = ConvertUTF16toUTF8(&source_ptr, source_end_ptr, - &target_ptr, target_end_ptr, - strictConversion); - - if (result == conversionOK) { - const char *targetPtr = reinterpret_cast(target_buffer.get()); - return targetPtr; - } - - return ""; -} - -} // namespace google_breakpad diff --git a/TMessagesProj/jni/breakpad/common/string_conversion.h b/TMessagesProj/jni/breakpad/common/string_conversion.h deleted file mode 100644 index b9ba96a2e..000000000 --- a/TMessagesProj/jni/breakpad/common/string_conversion.h +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) 2006, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// string_conversion.h: Conversion between different UTF-8/16/32 encodings. - -#ifndef COMMON_STRING_CONVERSION_H__ -#define COMMON_STRING_CONVERSION_H__ - -#include -#include - -#include "common/using_std_string.h" -#include "google_breakpad/common/breakpad_types.h" - -namespace google_breakpad { - -using std::vector; - -// Convert |in| to UTF-16 into |out|. Use platform byte ordering. If the -// conversion failed, |out| will be zero length. -void UTF8ToUTF16(const char *in, vector *out); - -// Convert at least one character (up to a maximum of |in_length|) from |in| -// to UTF-16 into |out|. Return the number of characters consumed from |in|. -// Any unused characters in |out| will be initialized to 0. No memory will -// be allocated by this routine. -int UTF8ToUTF16Char(const char *in, int in_length, uint16_t out[2]); - -// Convert |in| to UTF-16 into |out|. Use platform byte ordering. If the -// conversion failed, |out| will be zero length. -void UTF32ToUTF16(const wchar_t *in, vector *out); - -// Convert |in| to UTF-16 into |out|. Any unused characters in |out| will be -// initialized to 0. No memory will be allocated by this routine. -void UTF32ToUTF16Char(wchar_t in, uint16_t out[2]); - -// Convert |in| to UTF-8. If |swap| is true, swap bytes before converting. -string UTF16ToUTF8(const vector &in, bool swap); - -} // namespace google_breakpad - -#endif // COMMON_STRING_CONVERSION_H__ diff --git a/TMessagesProj/jni/breakpad/common/symbol_data.h b/TMessagesProj/jni/breakpad/common/symbol_data.h deleted file mode 100644 index 2cf15a855..000000000 --- a/TMessagesProj/jni/breakpad/common/symbol_data.h +++ /dev/null @@ -1,42 +0,0 @@ -// -*- mode: c++ -*- - -// Copyright (c) 2013 Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef COMMON_SYMBOL_DATA_H_ -#define COMMON_SYMBOL_DATA_H_ - -// Control what data is used from the symbol file. -enum SymbolData { - ALL_SYMBOL_DATA, - NO_CFI, - ONLY_CFI -}; - -#endif // COMMON_SYMBOL_DATA_H_ diff --git a/TMessagesProj/jni/breakpad/common/unordered.h b/TMessagesProj/jni/breakpad/common/unordered.h deleted file mode 100644 index ec665cc02..000000000 --- a/TMessagesProj/jni/breakpad/common/unordered.h +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2010 Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// Include this file to use unordered_map and unordered_set. If tr1 -// or C++11 is not available, you can switch to using hash_set and -// hash_map by defining BP_USE_HASH_SET. - -#ifndef COMMON_UNORDERED_H_ -#define COMMON_UNORDERED_H_ - -#if defined(BP_USE_HASH_SET) -#include -#include - -// For hash. -#include "util/hash/hash.h" - -template > -struct unordered_map : public hash_map {}; -template > -struct unordered_set : public hash_set {}; - -#elif defined(_LIBCPP_VERSION) // c++11 -#include -#include -using std::unordered_map; -using std::unordered_set; - -#else // Fallback to tr1::unordered -#include -#include -using std::tr1::unordered_map; -using std::tr1::unordered_set; -#endif - -#endif // COMMON_UNORDERED_H_ diff --git a/TMessagesProj/jni/breakpad/common/using_std_string.h b/TMessagesProj/jni/breakpad/common/using_std_string.h deleted file mode 100644 index 13c1da59c..000000000 --- a/TMessagesProj/jni/breakpad/common/using_std_string.h +++ /dev/null @@ -1,65 +0,0 @@ -// -*- mode: C++ -*- - -// Copyright (c) 2012, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// Original author: Ivan Penkov - -// using_std_string.h: Allows building this code in environments where -// global string (::string) exists. -// -// The problem: -// ------------- -// Let's say you want to build this code in an environment where a global -// string type is defined (i.e. ::string). Now, let's suppose that ::string -// is different that std::string and you'd like to have the option to easily -// choose between the two string types. Ideally you'd like to control which -// string type is chosen by simply #defining an identifier. -// -// The solution: -// ------------- -// #define HAS_GLOBAL_STRING somewhere in a global header file and then -// globally replace std::string with string. Then include this header -// file everywhere where string is used. If you want to revert back to -// using std::string, simply remove the #define (HAS_GLOBAL_STRING). - -#ifndef THIRD_PARTY_BREAKPAD_SRC_COMMON_USING_STD_STRING_H_ -#define THIRD_PARTY_BREAKPAD_SRC_COMMON_USING_STD_STRING_H_ - -#ifdef HAS_GLOBAL_STRING - typedef ::string google_breakpad_string; -#else - using std::string; - typedef std::string google_breakpad_string; -#endif - -// Inicates that type google_breakpad_string is defined -#define HAS_GOOGLE_BREAKPAD_STRING - -#endif // THIRD_PARTY_BREAKPAD_SRC_COMMON_USING_STD_STRING_H_ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/common/breakpad_types.h b/TMessagesProj/jni/breakpad/google_breakpad/common/breakpad_types.h deleted file mode 100644 index e92436ff2..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/common/breakpad_types.h +++ /dev/null @@ -1,86 +0,0 @@ -/* Copyright (c) 2006, Google Inc. - * 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 Google Inc. 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 - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -/* breakpad_types.h: Precise-width types - * - * (This is C99 source, please don't corrupt it with C++.) - * - * This file ensures that types uintN_t are defined for N = 8, 16, 32, and - * 64. Types of precise widths are crucial to the task of writing data - * structures on one platform and reading them on another. - * - * Author: Mark Mentovai */ - -#ifndef GOOGLE_BREAKPAD_COMMON_BREAKPAD_TYPES_H__ -#define GOOGLE_BREAKPAD_COMMON_BREAKPAD_TYPES_H__ - -#ifndef _WIN32 - -#ifndef __STDC_FORMAT_MACROS -#define __STDC_FORMAT_MACROS -#endif /* __STDC_FORMAT_MACROS */ -#include - -#else /* !_WIN32 */ - -#if _MSC_VER >= 1600 -#include -#elif defined(BREAKPAD_CUSTOM_STDINT_H) -/* Visual C++ Pre-2010 did not ship a stdint.h, so allow - * consumers of this library to provide their own because - * there are often subtle type incompatibilities. - */ -#include BREAKPAD_CUSTOM_STDINT_H -#else -#include - -typedef unsigned __int8 uint8_t; -typedef unsigned __int16 uint16_t; -typedef __int32 int32_t; -typedef unsigned __int32 uint32_t; -typedef unsigned __int64 uint64_t; -#endif - -#endif /* !_WIN32 */ - -typedef struct { - uint64_t high; - uint64_t low; -} uint128_struct; - -typedef uint64_t breakpad_time_t; - -/* Try to get PRIx64 from inttypes.h, but if it's not defined, fall back to - * llx, which is the format string for "long long" - this is a 64-bit - * integral type on many systems. */ -#ifndef PRIx64 -#define PRIx64 "llx" -#endif /* !PRIx64 */ - -#endif /* GOOGLE_BREAKPAD_COMMON_BREAKPAD_TYPES_H__ */ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_cpu_amd64.h b/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_cpu_amd64.h deleted file mode 100644 index 4256706d7..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_cpu_amd64.h +++ /dev/null @@ -1,235 +0,0 @@ -/* Copyright (c) 2006, Google Inc. - * 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 Google Inc. 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 - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -/* minidump_format.h: A cross-platform reimplementation of minidump-related - * portions of DbgHelp.h from the Windows Platform SDK. - * - * (This is C99 source, please don't corrupt it with C++.) - * - * This file contains the necessary definitions to read minidump files - * produced on amd64. These files may be read on any platform provided - * that the alignments of these structures on the processing system are - * identical to the alignments of these structures on the producing system. - * For this reason, precise-sized types are used. The structures defined - * by this file have been laid out to minimize alignment problems by ensuring - * ensuring that all members are aligned on their natural boundaries. In - * In some cases, tail-padding may be significant when different ABIs specify - * different tail-padding behaviors. To avoid problems when reading or - * writing affected structures, MD_*_SIZE macros are provided where needed, - * containing the useful size of the structures without padding. - * - * Structures that are defined by Microsoft to contain a zero-length array - * are instead defined here to contain an array with one element, as - * zero-length arrays are forbidden by standard C and C++. In these cases, - * *_minsize constants are provided to be used in place of sizeof. For a - * cleaner interface to these sizes when using C++, see minidump_size.h. - * - * These structures are also sufficient to populate minidump files. - * - * These definitions may be extended to support handling minidump files - * for other CPUs and other operating systems. - * - * Because precise data type sizes are crucial for this implementation to - * function properly and portably in terms of interoperability with minidumps - * produced by DbgHelp on Windows, a set of primitive types with known sizes - * are used as the basis of each structure defined by this file. DbgHelp - * on Windows is assumed to be the reference implementation; this file - * seeks to provide a cross-platform compatible implementation. To avoid - * collisions with the types and values defined and used by DbgHelp in the - * event that this implementation is used on Windows, each type and value - * defined here is given a new name, beginning with "MD". Names of the - * equivalent types and values in the Windows Platform SDK are given in - * comments. - * - * Author: Mark Mentovai - * Change to split into its own file: Neal Sidhwaney */ - -#ifndef GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_AMD64_H__ -#define GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_AMD64_H__ - - -/* - * AMD64 support, see WINNT.H - */ - -typedef struct { - uint16_t control_word; - uint16_t status_word; - uint8_t tag_word; - uint8_t reserved1; - uint16_t error_opcode; - uint32_t error_offset; - uint16_t error_selector; - uint16_t reserved2; - uint32_t data_offset; - uint16_t data_selector; - uint16_t reserved3; - uint32_t mx_csr; - uint32_t mx_csr_mask; - uint128_struct float_registers[8]; - uint128_struct xmm_registers[16]; - uint8_t reserved4[96]; -} MDXmmSaveArea32AMD64; /* XMM_SAVE_AREA32 */ - -#define MD_CONTEXT_AMD64_VR_COUNT 26 - -typedef struct { - /* - * Register parameter home addresses. - */ - uint64_t p1_home; - uint64_t p2_home; - uint64_t p3_home; - uint64_t p4_home; - uint64_t p5_home; - uint64_t p6_home; - - /* The next field determines the layout of the structure, and which parts - * of it are populated */ - uint32_t context_flags; - uint32_t mx_csr; - - /* The next register is included with MD_CONTEXT_AMD64_CONTROL */ - uint16_t cs; - - /* The next 4 registers are included with MD_CONTEXT_AMD64_SEGMENTS */ - uint16_t ds; - uint16_t es; - uint16_t fs; - uint16_t gs; - - /* The next 2 registers are included with MD_CONTEXT_AMD64_CONTROL */ - uint16_t ss; - uint32_t eflags; - - /* The next 6 registers are included with MD_CONTEXT_AMD64_DEBUG_REGISTERS */ - uint64_t dr0; - uint64_t dr1; - uint64_t dr2; - uint64_t dr3; - uint64_t dr6; - uint64_t dr7; - - /* The next 4 registers are included with MD_CONTEXT_AMD64_INTEGER */ - uint64_t rax; - uint64_t rcx; - uint64_t rdx; - uint64_t rbx; - - /* The next register is included with MD_CONTEXT_AMD64_CONTROL */ - uint64_t rsp; - - /* The next 11 registers are included with MD_CONTEXT_AMD64_INTEGER */ - uint64_t rbp; - uint64_t rsi; - uint64_t rdi; - uint64_t r8; - uint64_t r9; - uint64_t r10; - uint64_t r11; - uint64_t r12; - uint64_t r13; - uint64_t r14; - uint64_t r15; - - /* The next register is included with MD_CONTEXT_AMD64_CONTROL */ - uint64_t rip; - - /* The next set of registers are included with - * MD_CONTEXT_AMD64_FLOATING_POINT - */ - union { - MDXmmSaveArea32AMD64 flt_save; - struct { - uint128_struct header[2]; - uint128_struct legacy[8]; - uint128_struct xmm0; - uint128_struct xmm1; - uint128_struct xmm2; - uint128_struct xmm3; - uint128_struct xmm4; - uint128_struct xmm5; - uint128_struct xmm6; - uint128_struct xmm7; - uint128_struct xmm8; - uint128_struct xmm9; - uint128_struct xmm10; - uint128_struct xmm11; - uint128_struct xmm12; - uint128_struct xmm13; - uint128_struct xmm14; - uint128_struct xmm15; - } sse_registers; - }; - - uint128_struct vector_register[MD_CONTEXT_AMD64_VR_COUNT]; - uint64_t vector_control; - - /* The next 5 registers are included with MD_CONTEXT_AMD64_DEBUG_REGISTERS */ - uint64_t debug_control; - uint64_t last_branch_to_rip; - uint64_t last_branch_from_rip; - uint64_t last_exception_to_rip; - uint64_t last_exception_from_rip; - -} MDRawContextAMD64; /* CONTEXT */ - -/* For (MDRawContextAMD64).context_flags. These values indicate the type of - * context stored in the structure. The high 24 bits identify the CPU, the - * low 8 bits identify the type of context saved. */ -#define MD_CONTEXT_AMD64 0x00100000 /* CONTEXT_AMD64 */ -#define MD_CONTEXT_AMD64_CONTROL (MD_CONTEXT_AMD64 | 0x00000001) - /* CONTEXT_CONTROL */ -#define MD_CONTEXT_AMD64_INTEGER (MD_CONTEXT_AMD64 | 0x00000002) - /* CONTEXT_INTEGER */ -#define MD_CONTEXT_AMD64_SEGMENTS (MD_CONTEXT_AMD64 | 0x00000004) - /* CONTEXT_SEGMENTS */ -#define MD_CONTEXT_AMD64_FLOATING_POINT (MD_CONTEXT_AMD64 | 0x00000008) - /* CONTEXT_FLOATING_POINT */ -#define MD_CONTEXT_AMD64_DEBUG_REGISTERS (MD_CONTEXT_AMD64 | 0x00000010) - /* CONTEXT_DEBUG_REGISTERS */ -#define MD_CONTEXT_AMD64_XSTATE (MD_CONTEXT_AMD64 | 0x00000040) - /* CONTEXT_XSTATE */ - -/* WinNT.h refers to CONTEXT_MMX_REGISTERS but doesn't appear to define it - * I think it really means CONTEXT_FLOATING_POINT. - */ - -#define MD_CONTEXT_AMD64_FULL (MD_CONTEXT_AMD64_CONTROL | \ - MD_CONTEXT_AMD64_INTEGER | \ - MD_CONTEXT_AMD64_FLOATING_POINT) - /* CONTEXT_FULL */ - -#define MD_CONTEXT_AMD64_ALL (MD_CONTEXT_AMD64_FULL | \ - MD_CONTEXT_AMD64_SEGMENTS | \ - MD_CONTEXT_X86_DEBUG_REGISTERS) - /* CONTEXT_ALL */ - - -#endif /* GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_AMD64_H__ */ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_cpu_arm.h b/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_cpu_arm.h deleted file mode 100644 index 6a7113833..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_cpu_arm.h +++ /dev/null @@ -1,151 +0,0 @@ -/* Copyright (c) 2009, Google Inc. - * 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 Google Inc. 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 - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -/* minidump_format.h: A cross-platform reimplementation of minidump-related - * portions of DbgHelp.h from the Windows Platform SDK. - * - * (This is C99 source, please don't corrupt it with C++.) - * - * This file contains the necessary definitions to read minidump files - * produced on ARM. These files may be read on any platform provided - * that the alignments of these structures on the processing system are - * identical to the alignments of these structures on the producing system. - * For this reason, precise-sized types are used. The structures defined - * by this file have been laid out to minimize alignment problems by - * ensuring that all members are aligned on their natural boundaries. - * In some cases, tail-padding may be significant when different ABIs specify - * different tail-padding behaviors. To avoid problems when reading or - * writing affected structures, MD_*_SIZE macros are provided where needed, - * containing the useful size of the structures without padding. - * - * Structures that are defined by Microsoft to contain a zero-length array - * are instead defined here to contain an array with one element, as - * zero-length arrays are forbidden by standard C and C++. In these cases, - * *_minsize constants are provided to be used in place of sizeof. For a - * cleaner interface to these sizes when using C++, see minidump_size.h. - * - * These structures are also sufficient to populate minidump files. - * - * Because precise data type sizes are crucial for this implementation to - * function properly and portably, a set of primitive types with known sizes - * are used as the basis of each structure defined by this file. - * - * Author: Julian Seward - */ - -/* - * ARM support - */ - -#ifndef GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_ARM_H__ -#define GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_ARM_H__ - -#define MD_FLOATINGSAVEAREA_ARM_FPR_COUNT 32 -#define MD_FLOATINGSAVEAREA_ARM_FPEXTRA_COUNT 8 - -/* - * Note that these structures *do not* map directly to the CONTEXT - * structure defined in WinNT.h in the Windows Mobile SDK. That structure - * does not accomodate VFPv3, and I'm unsure if it was ever used in the - * wild anyway, as Windows CE only seems to produce "cedumps" which - * are not exactly minidumps. - */ -typedef struct { - uint64_t fpscr; /* FPU status register */ - - /* 32 64-bit floating point registers, d0 .. d31. */ - uint64_t regs[MD_FLOATINGSAVEAREA_ARM_FPR_COUNT]; - - /* Miscellaneous control words */ - uint32_t extra[MD_FLOATINGSAVEAREA_ARM_FPEXTRA_COUNT]; -} MDFloatingSaveAreaARM; - -#define MD_CONTEXT_ARM_GPR_COUNT 16 - -typedef struct { - /* The next field determines the layout of the structure, and which parts - * of it are populated - */ - uint32_t context_flags; - - /* 16 32-bit integer registers, r0 .. r15 - * Note the following fixed uses: - * r13 is the stack pointer - * r14 is the link register - * r15 is the program counter - */ - uint32_t iregs[MD_CONTEXT_ARM_GPR_COUNT]; - - /* CPSR (flags, basically): 32 bits: - bit 31 - N (negative) - bit 30 - Z (zero) - bit 29 - C (carry) - bit 28 - V (overflow) - bit 27 - Q (saturation flag, sticky) - All other fields -- ignore */ - uint32_t cpsr; - - /* The next field is included with MD_CONTEXT_ARM_FLOATING_POINT */ - MDFloatingSaveAreaARM float_save; - -} MDRawContextARM; - -/* Indices into iregs for registers with a dedicated or conventional - * purpose. - */ -enum MDARMRegisterNumbers { - MD_CONTEXT_ARM_REG_IOS_FP = 7, - MD_CONTEXT_ARM_REG_FP = 11, - MD_CONTEXT_ARM_REG_SP = 13, - MD_CONTEXT_ARM_REG_LR = 14, - MD_CONTEXT_ARM_REG_PC = 15 -}; - -/* For (MDRawContextARM).context_flags. These values indicate the type of - * context stored in the structure. */ -/* CONTEXT_ARM from the Windows CE 5.0 SDK. This value isn't correct - * because this bit can be used for flags. Presumably this value was - * never actually used in minidumps, but only in "CEDumps" which - * are a whole parallel minidump file format for Windows CE. - * Therefore, Breakpad defines its own value for ARM CPUs. - */ -#define MD_CONTEXT_ARM_OLD 0x00000040 -/* This value was chosen to avoid likely conflicts with MD_CONTEXT_* - * for other CPUs. */ -#define MD_CONTEXT_ARM 0x40000000 -#define MD_CONTEXT_ARM_INTEGER (MD_CONTEXT_ARM | 0x00000002) -#define MD_CONTEXT_ARM_FLOATING_POINT (MD_CONTEXT_ARM | 0x00000004) - -#define MD_CONTEXT_ARM_FULL (MD_CONTEXT_ARM_INTEGER | \ - MD_CONTEXT_ARM_FLOATING_POINT) - -#define MD_CONTEXT_ARM_ALL (MD_CONTEXT_ARM_INTEGER | \ - MD_CONTEXT_ARM_FLOATING_POINT) - -#endif /* GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_ARM_H__ */ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_cpu_arm64.h b/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_cpu_arm64.h deleted file mode 100644 index 5ace0d9de..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_cpu_arm64.h +++ /dev/null @@ -1,140 +0,0 @@ -/* Copyright 2013 Google Inc. - * 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 Google Inc. 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 - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -/* minidump_format.h: A cross-platform reimplementation of minidump-related - * portions of DbgHelp.h from the Windows Platform SDK. - * - * (This is C99 source, please don't corrupt it with C++.) - * - * This file contains the necessary definitions to read minidump files - * produced on ARM. These files may be read on any platform provided - * that the alignments of these structures on the processing system are - * identical to the alignments of these structures on the producing system. - * For this reason, precise-sized types are used. The structures defined - * by this file have been laid out to minimize alignment problems by - * ensuring that all members are aligned on their natural boundaries. - * In some cases, tail-padding may be significant when different ABIs specify - * different tail-padding behaviors. To avoid problems when reading or - * writing affected structures, MD_*_SIZE macros are provided where needed, - * containing the useful size of the structures without padding. - * - * Structures that are defined by Microsoft to contain a zero-length array - * are instead defined here to contain an array with one element, as - * zero-length arrays are forbidden by standard C and C++. In these cases, - * *_minsize constants are provided to be used in place of sizeof. For a - * cleaner interface to these sizes when using C++, see minidump_size.h. - * - * These structures are also sufficient to populate minidump files. - * - * Because precise data type sizes are crucial for this implementation to - * function properly and portably, a set of primitive types with known sizes - * are used as the basis of each structure defined by this file. - * - * Author: Colin Blundell - */ - -/* - * ARM64 support - */ - -#ifndef GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_ARM64_H__ -#define GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_ARM64_H__ - -#define MD_FLOATINGSAVEAREA_ARM64_FPR_COUNT 32 - -typedef struct { - uint32_t fpsr; /* FPU status register */ - uint32_t fpcr; /* FPU control register */ - - /* 32 128-bit floating point registers, d0 .. d31. */ - uint128_struct regs[MD_FLOATINGSAVEAREA_ARM64_FPR_COUNT]; -} MDFloatingSaveAreaARM64; - -#define MD_CONTEXT_ARM64_GPR_COUNT 33 - -/* Use the same 32-bit alignment when accessing this structure from 64-bit code - * as is used natively in 32-bit code. */ -#pragma pack(push, 4) - -typedef struct { - /* The next field determines the layout of the structure, and which parts - * of it are populated - */ - uint64_t context_flags; - - /* 33 64-bit integer registers, x0 .. x31 + the PC - * Note the following fixed uses: - * x29 is the frame pointer - * x30 is the link register - * x31 is the stack pointer - * The PC is effectively x32. - */ - uint64_t iregs[MD_CONTEXT_ARM64_GPR_COUNT]; - - /* CPSR (flags, basically): 32 bits: - bit 31 - N (negative) - bit 30 - Z (zero) - bit 29 - C (carry) - bit 28 - V (overflow) - bit 27 - Q (saturation flag, sticky) - All other fields -- ignore */ - uint32_t cpsr; - - /* The next field is included with MD_CONTEXT64_ARM_FLOATING_POINT */ - MDFloatingSaveAreaARM64 float_save; - -} MDRawContextARM64; - -#pragma pack(pop) - -/* Indices into iregs for registers with a dedicated or conventional - * purpose. - */ -enum MDARM64RegisterNumbers { - MD_CONTEXT_ARM64_REG_FP = 29, - MD_CONTEXT_ARM64_REG_LR = 30, - MD_CONTEXT_ARM64_REG_SP = 31, - MD_CONTEXT_ARM64_REG_PC = 32 -}; - -/* For (MDRawContextARM64).context_flags. These values indicate the type of - * context stored in the structure. MD_CONTEXT_ARM64 is Breakpad-defined. - * This value was chosen to avoid likely conflicts with MD_CONTEXT_* - * for other CPUs. */ -#define MD_CONTEXT_ARM64 0x80000000 -#define MD_CONTEXT_ARM64_INTEGER (MD_CONTEXT_ARM64 | 0x00000002) -#define MD_CONTEXT_ARM64_FLOATING_POINT (MD_CONTEXT_ARM64 | 0x00000004) - -#define MD_CONTEXT_ARM64_FULL (MD_CONTEXT_ARM64_INTEGER | \ - MD_CONTEXT_ARM64_FLOATING_POINT) - -#define MD_CONTEXT_ARM64_ALL (MD_CONTEXT_ARM64_INTEGER | \ - MD_CONTEXT_ARM64_FLOATING_POINT) - -#endif /* GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_ARM64_H__ */ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_cpu_mips.h b/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_cpu_mips.h deleted file mode 100644 index 6cbe3023f..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_cpu_mips.h +++ /dev/null @@ -1,160 +0,0 @@ -/* Copyright (c) 2013, Google Inc. - * 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 Google Inc. 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 - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -/* minidump_format.h: A cross-platform reimplementation of minidump-related - * portions of DbgHelp.h from the Windows Platform SDK. - * - * (This is C99 source, please don't corrupt it with C++.) - * - * This file contains the necessary definitions to read minidump files - * produced on MIPS. These files may be read on any platform provided - * that the alignments of these structures on the processing system are - * identical to the alignments of these structures on the producing system. - * For this reason, precise-sized types are used. The structures defined - * by this file have been laid out to minimize alignment problems by - * ensuring that all members are aligned on their natural boundaries. - * In some cases, tail-padding may be significant when different ABIs specify - * different tail-padding behaviors. To avoid problems when reading or - * writing affected structures, MD_*_SIZE macros are provided where needed, - * containing the useful size of the structures without padding. - * - * Structures that are defined by Microsoft to contain a zero-length array - * are instead defined here to contain an array with one element, as - * zero-length arrays are forbidden by standard C and C++. In these cases, - * *_minsize constants are provided to be used in place of sizeof. For a - * cleaner interface to these sizes when using C++, see minidump_size.h. - * - * These structures are also sufficient to populate minidump files. - * - * Because precise data type sizes are crucial for this implementation to - * function properly and portably, a set of primitive types with known sizes - * are used as the basis of each structure defined by this file. - * - * Author: Chris Dearman - */ - -/* - * MIPS support - */ - -#ifndef GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_MIPS_H__ -#define GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_MIPS_H__ - -#define MD_CONTEXT_MIPS_GPR_COUNT 32 -#define MD_FLOATINGSAVEAREA_MIPS_FPR_COUNT 32 -#define MD_CONTEXT_MIPS_DSP_COUNT 3 - -/* - * Note that these structures *do not* map directly to the CONTEXT - * structure defined in WinNT.h in the Windows Mobile SDK. That structure - * does not accomodate VFPv3, and I'm unsure if it was ever used in the - * wild anyway, as Windows CE only seems to produce "cedumps" which - * are not exactly minidumps. - */ -typedef struct { - /* 32 64-bit floating point registers, f0..f31 */ - uint64_t regs[MD_FLOATINGSAVEAREA_MIPS_FPR_COUNT]; - - uint32_t fpcsr; /* FPU status register. */ - uint32_t fir; /* FPU implementation register. */ -} MDFloatingSaveAreaMIPS; - -typedef struct { - /* The next field determines the layout of the structure, and which parts - * of it are populated. - */ - uint32_t context_flags; - uint32_t _pad0; - - /* 32 64-bit integer registers, r0..r31. - * Note the following fixed uses: - * r29 is the stack pointer. - * r31 is the return address. - */ - uint64_t iregs[MD_CONTEXT_MIPS_GPR_COUNT]; - - /* multiply/divide result. */ - uint64_t mdhi, mdlo; - - /* DSP accumulators. */ - uint32_t hi[MD_CONTEXT_MIPS_DSP_COUNT]; - uint32_t lo[MD_CONTEXT_MIPS_DSP_COUNT]; - uint32_t dsp_control; - uint32_t _pad1; - - uint64_t epc; - uint64_t badvaddr; - uint32_t status; - uint32_t cause; - - /* The next field is included with MD_CONTEXT_MIPS_FLOATING_POINT. */ - MDFloatingSaveAreaMIPS float_save; - -} MDRawContextMIPS; - -/* Indices into iregs for registers with a dedicated or conventional - * purpose. - */ -enum MDMIPSRegisterNumbers { - MD_CONTEXT_MIPS_REG_S0 = 16, - MD_CONTEXT_MIPS_REG_S1 = 17, - MD_CONTEXT_MIPS_REG_S2 = 18, - MD_CONTEXT_MIPS_REG_S3 = 19, - MD_CONTEXT_MIPS_REG_S4 = 20, - MD_CONTEXT_MIPS_REG_S5 = 21, - MD_CONTEXT_MIPS_REG_S6 = 22, - MD_CONTEXT_MIPS_REG_S7 = 23, - MD_CONTEXT_MIPS_REG_GP = 28, - MD_CONTEXT_MIPS_REG_SP = 29, - MD_CONTEXT_MIPS_REG_FP = 30, - MD_CONTEXT_MIPS_REG_RA = 31, -}; - -/* For (MDRawContextMIPS).context_flags. These values indicate the type of - * context stored in the structure. */ -/* CONTEXT_MIPS from the Windows CE 5.0 SDK. This value isn't correct - * because this bit can be used for flags. Presumably this value was - * never actually used in minidumps, but only in "CEDumps" which - * are a whole parallel minidump file format for Windows CE. - * Therefore, Breakpad defines its own value for MIPS CPUs. - */ -#define MD_CONTEXT_MIPS 0x00040000 -#define MD_CONTEXT_MIPS_INTEGER (MD_CONTEXT_MIPS | 0x00000002) -#define MD_CONTEXT_MIPS_FLOATING_POINT (MD_CONTEXT_MIPS | 0x00000004) -#define MD_CONTEXT_MIPS_DSP (MD_CONTEXT_MIPS | 0x00000008) - -#define MD_CONTEXT_MIPS_FULL (MD_CONTEXT_MIPS_INTEGER | \ - MD_CONTEXT_MIPS_FLOATING_POINT | \ - MD_CONTEXT_MIPS_DSP) - -#define MD_CONTEXT_MIPS_ALL (MD_CONTEXT_MIPS_INTEGER | \ - MD_CONTEXT_MIPS_FLOATING_POINT \ - MD_CONTEXT_MIPS_DSP) - -#endif // GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_MIPS_H__ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_cpu_ppc.h b/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_cpu_ppc.h deleted file mode 100644 index b24cc4243..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_cpu_ppc.h +++ /dev/null @@ -1,168 +0,0 @@ -/* Copyright (c) 2006, Google Inc. - * 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 Google Inc. 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 - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -/* minidump_format.h: A cross-platform reimplementation of minidump-related - * portions of DbgHelp.h from the Windows Platform SDK. - * - * (This is C99 source, please don't corrupt it with C++.) - * - * This file contains the necessary definitions to read minidump files - * produced on ppc. These files may be read on any platform provided - * that the alignments of these structures on the processing system are - * identical to the alignments of these structures on the producing system. - * For this reason, precise-sized types are used. The structures defined - * by this file have been laid out to minimize alignment problems by ensuring - * ensuring that all members are aligned on their natural boundaries. In - * In some cases, tail-padding may be significant when different ABIs specify - * different tail-padding behaviors. To avoid problems when reading or - * writing affected structures, MD_*_SIZE macros are provided where needed, - * containing the useful size of the structures without padding. - * - * Structures that are defined by Microsoft to contain a zero-length array - * are instead defined here to contain an array with one element, as - * zero-length arrays are forbidden by standard C and C++. In these cases, - * *_minsize constants are provided to be used in place of sizeof. For a - * cleaner interface to these sizes when using C++, see minidump_size.h. - * - * These structures are also sufficient to populate minidump files. - * - * These definitions may be extended to support handling minidump files - * for other CPUs and other operating systems. - * - * Because precise data type sizes are crucial for this implementation to - * function properly and portably in terms of interoperability with minidumps - * produced by DbgHelp on Windows, a set of primitive types with known sizes - * are used as the basis of each structure defined by this file. DbgHelp - * on Windows is assumed to be the reference implementation; this file - * seeks to provide a cross-platform compatible implementation. To avoid - * collisions with the types and values defined and used by DbgHelp in the - * event that this implementation is used on Windows, each type and value - * defined here is given a new name, beginning with "MD". Names of the - * equivalent types and values in the Windows Platform SDK are given in - * comments. - * - * Author: Mark Mentovai - * Change to split into its own file: Neal Sidhwaney */ - -/* - * Breakpad minidump extension for PowerPC support. Based on Darwin/Mac OS X' - * mach/ppc/_types.h - */ - -#ifndef GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_PPC_H__ -#define GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_PPC_H__ - -#define MD_FLOATINGSAVEAREA_PPC_FPR_COUNT 32 - -typedef struct { - /* fpregs is a double[32] in mach/ppc/_types.h, but a uint64_t is used - * here for precise sizing. */ - uint64_t fpregs[MD_FLOATINGSAVEAREA_PPC_FPR_COUNT]; - uint32_t fpscr_pad; - uint32_t fpscr; /* Status/control */ -} MDFloatingSaveAreaPPC; /* Based on ppc_float_state */ - - -#define MD_VECTORSAVEAREA_PPC_VR_COUNT 32 - -typedef struct { - /* Vector registers (including vscr) are 128 bits, but mach/ppc/_types.h - * exposes them as four 32-bit quantities. */ - uint128_struct save_vr[MD_VECTORSAVEAREA_PPC_VR_COUNT]; - uint128_struct save_vscr; /* Status/control */ - uint32_t save_pad5[4]; - uint32_t save_vrvalid; /* Indicates which vector registers are saved */ - uint32_t save_pad6[7]; -} MDVectorSaveAreaPPC; /* ppc_vector_state */ - - -#define MD_CONTEXT_PPC_GPR_COUNT 32 - -/* Use the same 32-bit alignment when accessing this structure from 64-bit code - * as is used natively in 32-bit code. #pragma pack is a MSVC extension - * supported by gcc. */ -#if defined(__SUNPRO_C) || defined(__SUNPRO_CC) -#pragma pack(4) -#else -#pragma pack(push, 4) -#endif - -typedef struct { - /* context_flags is not present in ppc_thread_state, but it aids - * identification of MDRawContextPPC among other raw context types, - * and it guarantees alignment when we get to float_save. */ - uint32_t context_flags; - - uint32_t srr0; /* Machine status save/restore: stores pc - * (instruction) */ - uint32_t srr1; /* Machine status save/restore: stores msr - * (ps, program/machine state) */ - /* ppc_thread_state contains 32 fields, r0 .. r31. Here, an array is - * used for brevity. */ - uint32_t gpr[MD_CONTEXT_PPC_GPR_COUNT]; - uint32_t cr; /* Condition */ - uint32_t xer; /* Integer (fiXed-point) exception */ - uint32_t lr; /* Link */ - uint32_t ctr; /* Count */ - uint32_t mq; /* Multiply/Quotient (PPC 601, POWER only) */ - uint32_t vrsave; /* Vector save */ - - /* float_save and vector_save aren't present in ppc_thread_state, but - * are represented in separate structures that still define a thread's - * context. */ - MDFloatingSaveAreaPPC float_save; - MDVectorSaveAreaPPC vector_save; -} MDRawContextPPC; /* Based on ppc_thread_state */ - -/* Indices into gpr for registers with a dedicated or conventional purpose. */ -enum MDPPCRegisterNumbers { - MD_CONTEXT_PPC_REG_SP = 1 -}; - -#if defined(__SUNPRO_C) || defined(__SUNPRO_CC) -#pragma pack(0) -#else -#pragma pack(pop) -#endif - -/* For (MDRawContextPPC).context_flags. These values indicate the type of - * context stored in the structure. MD_CONTEXT_PPC is Breakpad-defined. Its - * value was chosen to avoid likely conflicts with MD_CONTEXT_* for other - * CPUs. */ -#define MD_CONTEXT_PPC 0x20000000 -#define MD_CONTEXT_PPC_BASE (MD_CONTEXT_PPC | 0x00000001) -#define MD_CONTEXT_PPC_FLOATING_POINT (MD_CONTEXT_PPC | 0x00000008) -#define MD_CONTEXT_PPC_VECTOR (MD_CONTEXT_PPC | 0x00000020) - -#define MD_CONTEXT_PPC_FULL MD_CONTEXT_PPC_BASE -#define MD_CONTEXT_PPC_ALL (MD_CONTEXT_PPC_FULL | \ - MD_CONTEXT_PPC_FLOATING_POINT | \ - MD_CONTEXT_PPC_VECTOR) - -#endif /* GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_PPC_H__ */ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_cpu_ppc64.h b/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_cpu_ppc64.h deleted file mode 100644 index 61f419386..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_cpu_ppc64.h +++ /dev/null @@ -1,134 +0,0 @@ -/* Copyright (c) 2008, Google Inc. - * 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 Google Inc. 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 - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -/* minidump_format.h: A cross-platform reimplementation of minidump-related - * portions of DbgHelp.h from the Windows Platform SDK. - * - * (This is C99 source, please don't corrupt it with C++.) - * - * This file contains the necessary definitions to read minidump files - * produced on ppc64. These files may be read on any platform provided - * that the alignments of these structures on the processing system are - * identical to the alignments of these structures on the producing system. - * For this reason, precise-sized types are used. The structures defined - * by this file have been laid out to minimize alignment problems by ensuring - * ensuring that all members are aligned on their natural boundaries. In - * In some cases, tail-padding may be significant when different ABIs specify - * different tail-padding behaviors. To avoid problems when reading or - * writing affected structures, MD_*_SIZE macros are provided where needed, - * containing the useful size of the structures without padding. - * - * Structures that are defined by Microsoft to contain a zero-length array - * are instead defined here to contain an array with one element, as - * zero-length arrays are forbidden by standard C and C++. In these cases, - * *_minsize constants are provided to be used in place of sizeof. For a - * cleaner interface to these sizes when using C++, see minidump_size.h. - * - * These structures are also sufficient to populate minidump files. - * - * These definitions may be extended to support handling minidump files - * for other CPUs and other operating systems. - * - * Because precise data type sizes are crucial for this implementation to - * function properly and portably in terms of interoperability with minidumps - * produced by DbgHelp on Windows, a set of primitive types with known sizes - * are used as the basis of each structure defined by this file. DbgHelp - * on Windows is assumed to be the reference implementation; this file - * seeks to provide a cross-platform compatible implementation. To avoid - * collisions with the types and values defined and used by DbgHelp in the - * event that this implementation is used on Windows, each type and value - * defined here is given a new name, beginning with "MD". Names of the - * equivalent types and values in the Windows Platform SDK are given in - * comments. - * - * Author: Neal Sidhwaney */ - - -/* - * Breakpad minidump extension for PPC64 support. Based on Darwin/Mac OS X' - * mach/ppc/_types.h - */ - -#ifndef GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_PPC64_H__ -#define GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_PPC64_H__ - -#include "minidump_cpu_ppc.h" - -// these types are the same in ppc64 & ppc -typedef MDFloatingSaveAreaPPC MDFloatingSaveAreaPPC64; -typedef MDVectorSaveAreaPPC MDVectorSaveAreaPPC64; - -#define MD_CONTEXT_PPC64_GPR_COUNT MD_CONTEXT_PPC_GPR_COUNT - -typedef struct { - /* context_flags is not present in ppc_thread_state, but it aids - * identification of MDRawContextPPC among other raw context types, - * and it guarantees alignment when we get to float_save. */ - uint64_t context_flags; - - uint64_t srr0; /* Machine status save/restore: stores pc - * (instruction) */ - uint64_t srr1; /* Machine status save/restore: stores msr - * (ps, program/machine state) */ - /* ppc_thread_state contains 32 fields, r0 .. r31. Here, an array is - * used for brevity. */ - uint64_t gpr[MD_CONTEXT_PPC64_GPR_COUNT]; - uint64_t cr; /* Condition */ - uint64_t xer; /* Integer (fiXed-point) exception */ - uint64_t lr; /* Link */ - uint64_t ctr; /* Count */ - uint64_t vrsave; /* Vector save */ - - /* float_save and vector_save aren't present in ppc_thread_state, but - * are represented in separate structures that still define a thread's - * context. */ - MDFloatingSaveAreaPPC float_save; - MDVectorSaveAreaPPC vector_save; -} MDRawContextPPC64; /* Based on ppc_thread_state */ - -/* Indices into gpr for registers with a dedicated or conventional purpose. */ -enum MDPPC64RegisterNumbers { - MD_CONTEXT_PPC64_REG_SP = 1 -}; - -/* For (MDRawContextPPC).context_flags. These values indicate the type of - * context stored in the structure. MD_CONTEXT_PPC is Breakpad-defined. Its - * value was chosen to avoid likely conflicts with MD_CONTEXT_* for other - * CPUs. */ -#define MD_CONTEXT_PPC64 0x01000000 -#define MD_CONTEXT_PPC64_BASE (MD_CONTEXT_PPC64 | 0x00000001) -#define MD_CONTEXT_PPC64_FLOATING_POINT (MD_CONTEXT_PPC64 | 0x00000008) -#define MD_CONTEXT_PPC64_VECTOR (MD_CONTEXT_PPC64 | 0x00000020) - -#define MD_CONTEXT_PPC64_FULL MD_CONTEXT_PPC64_BASE -#define MD_CONTEXT_PPC64_ALL (MD_CONTEXT_PPC64_FULL | \ - MD_CONTEXT_PPC64_FLOATING_POINT | \ - MD_CONTEXT_PPC64_VECTOR) - -#endif /* GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_PPC64_H__ */ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_cpu_sparc.h b/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_cpu_sparc.h deleted file mode 100644 index 95c08b174..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_cpu_sparc.h +++ /dev/null @@ -1,163 +0,0 @@ -/* Copyright (c) 2006, Google Inc. - * 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 Google Inc. 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 - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -/* minidump_format.h: A cross-platform reimplementation of minidump-related - * portions of DbgHelp.h from the Windows Platform SDK. - * - * (This is C99 source, please don't corrupt it with C++.) - * - * This file contains the necessary definitions to read minidump files - * produced on sparc. These files may be read on any platform provided - * that the alignments of these structures on the processing system are - * identical to the alignments of these structures on the producing system. - * For this reason, precise-sized types are used. The structures defined - * by this file have been laid out to minimize alignment problems by ensuring - * ensuring that all members are aligned on their natural boundaries. In - * In some cases, tail-padding may be significant when different ABIs specify - * different tail-padding behaviors. To avoid problems when reading or - * writing affected structures, MD_*_SIZE macros are provided where needed, - * containing the useful size of the structures without padding. - * - * Structures that are defined by Microsoft to contain a zero-length array - * are instead defined here to contain an array with one element, as - * zero-length arrays are forbidden by standard C and C++. In these cases, - * *_minsize constants are provided to be used in place of sizeof. For a - * cleaner interface to these sizes when using C++, see minidump_size.h. - * - * These structures are also sufficient to populate minidump files. - * - * These definitions may be extended to support handling minidump files - * for other CPUs and other operating systems. - * - * Because precise data type sizes are crucial for this implementation to - * function properly and portably in terms of interoperability with minidumps - * produced by DbgHelp on Windows, a set of primitive types with known sizes - * are used as the basis of each structure defined by this file. DbgHelp - * on Windows is assumed to be the reference implementation; this file - * seeks to provide a cross-platform compatible implementation. To avoid - * collisions with the types and values defined and used by DbgHelp in the - * event that this implementation is used on Windows, each type and value - * defined here is given a new name, beginning with "MD". Names of the - * equivalent types and values in the Windows Platform SDK are given in - * comments. - * - * Author: Mark Mentovai - * Change to split into its own file: Neal Sidhwaney */ - -/* - * SPARC support, see (solaris)sys/procfs_isa.h also - */ - -#ifndef GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_SPARC_H__ -#define GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_SPARC_H__ - -#define MD_FLOATINGSAVEAREA_SPARC_FPR_COUNT 32 - -typedef struct { - - /* FPU floating point regs */ - uint64_t regs[MD_FLOATINGSAVEAREA_SPARC_FPR_COUNT]; - - uint64_t filler; - uint64_t fsr; /* FPU status register */ -} MDFloatingSaveAreaSPARC; /* FLOATING_SAVE_AREA */ - -#define MD_CONTEXT_SPARC_GPR_COUNT 32 - -typedef struct { - /* The next field determines the layout of the structure, and which parts - * of it are populated - */ - uint32_t context_flags; - uint32_t flag_pad; - /* - * General register access (SPARC). - * Don't confuse definitions here with definitions in . - * Registers are 32 bits for ILP32, 64 bits for LP64. - * SPARC V7/V8 is for 32bit, SPARC V9 is for 64bit - */ - - /* 32 Integer working registers */ - - /* g_r[0-7] global registers(g0-g7) - * g_r[8-15] out registers(o0-o7) - * g_r[16-23] local registers(l0-l7) - * g_r[24-31] in registers(i0-i7) - */ - uint64_t g_r[MD_CONTEXT_SPARC_GPR_COUNT]; - - /* several control registers */ - - /* Processor State register(PSR) for SPARC V7/V8 - * Condition Code register (CCR) for SPARC V9 - */ - uint64_t ccr; - - uint64_t pc; /* Program Counter register (PC) */ - uint64_t npc; /* Next Program Counter register (nPC) */ - uint64_t y; /* Y register (Y) */ - - /* Address Space Identifier register (ASI) for SPARC V9 - * WIM for SPARC V7/V8 - */ - uint64_t asi; - - /* Floating-Point Registers State register (FPRS) for SPARC V9 - * TBR for for SPARC V7/V8 - */ - uint64_t fprs; - - /* The next field is included with MD_CONTEXT_SPARC_FLOATING_POINT */ - MDFloatingSaveAreaSPARC float_save; - -} MDRawContextSPARC; /* CONTEXT_SPARC */ - -/* Indices into g_r for registers with a dedicated or conventional purpose. */ -enum MDSPARCRegisterNumbers { - MD_CONTEXT_SPARC_REG_SP = 14 -}; - -/* For (MDRawContextSPARC).context_flags. These values indicate the type of - * context stored in the structure. MD_CONTEXT_SPARC is Breakpad-defined. Its - * value was chosen to avoid likely conflicts with MD_CONTEXT_* for other - * CPUs. */ -#define MD_CONTEXT_SPARC 0x10000000 -#define MD_CONTEXT_SPARC_CONTROL (MD_CONTEXT_SPARC | 0x00000001) -#define MD_CONTEXT_SPARC_INTEGER (MD_CONTEXT_SPARC | 0x00000002) -#define MD_CONTEXT_SAPARC_FLOATING_POINT (MD_CONTEXT_SPARC | 0x00000004) -#define MD_CONTEXT_SAPARC_EXTRA (MD_CONTEXT_SPARC | 0x00000008) - -#define MD_CONTEXT_SPARC_FULL (MD_CONTEXT_SPARC_CONTROL | \ - MD_CONTEXT_SPARC_INTEGER) - -#define MD_CONTEXT_SPARC_ALL (MD_CONTEXT_SPARC_FULL | \ - MD_CONTEXT_SAPARC_FLOATING_POINT | \ - MD_CONTEXT_SAPARC_EXTRA) - -#endif /* GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_SPARC_H__ */ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_cpu_x86.h b/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_cpu_x86.h deleted file mode 100644 index e09cb7cb5..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_cpu_x86.h +++ /dev/null @@ -1,174 +0,0 @@ -/* Copyright (c) 2006, Google Inc. - * 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 Google Inc. 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 - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -/* minidump_format.h: A cross-platform reimplementation of minidump-related - * portions of DbgHelp.h from the Windows Platform SDK. - * - * (This is C99 source, please don't corrupt it with C++.) - * - * This file contains the necessary definitions to read minidump files - * produced on x86. These files may be read on any platform provided - * that the alignments of these structures on the processing system are - * identical to the alignments of these structures on the producing system. - * For this reason, precise-sized types are used. The structures defined - * by this file have been laid out to minimize alignment problems by ensuring - * ensuring that all members are aligned on their natural boundaries. In - * In some cases, tail-padding may be significant when different ABIs specify - * different tail-padding behaviors. To avoid problems when reading or - * writing affected structures, MD_*_SIZE macros are provided where needed, - * containing the useful size of the structures without padding. - * - * Structures that are defined by Microsoft to contain a zero-length array - * are instead defined here to contain an array with one element, as - * zero-length arrays are forbidden by standard C and C++. In these cases, - * *_minsize constants are provided to be used in place of sizeof. For a - * cleaner interface to these sizes when using C++, see minidump_size.h. - * - * These structures are also sufficient to populate minidump files. - * - * These definitions may be extended to support handling minidump files - * for other CPUs and other operating systems. - * - * Because precise data type sizes are crucial for this implementation to - * function properly and portably in terms of interoperability with minidumps - * produced by DbgHelp on Windows, a set of primitive types with known sizes - * are used as the basis of each structure defined by this file. DbgHelp - * on Windows is assumed to be the reference implementation; this file - * seeks to provide a cross-platform compatible implementation. To avoid - * collisions with the types and values defined and used by DbgHelp in the - * event that this implementation is used on Windows, each type and value - * defined here is given a new name, beginning with "MD". Names of the - * equivalent types and values in the Windows Platform SDK are given in - * comments. - * - * Author: Mark Mentovai */ - -#ifndef GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_X86_H__ -#define GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_X86_H__ - -#define MD_FLOATINGSAVEAREA_X86_REGISTERAREA_SIZE 80 - /* SIZE_OF_80387_REGISTERS */ - -typedef struct { - uint32_t control_word; - uint32_t status_word; - uint32_t tag_word; - uint32_t error_offset; - uint32_t error_selector; - uint32_t data_offset; - uint32_t data_selector; - - /* register_area contains eight 80-bit (x87 "long double") quantities for - * floating-point registers %st0 (%mm0) through %st7 (%mm7). */ - uint8_t register_area[MD_FLOATINGSAVEAREA_X86_REGISTERAREA_SIZE]; - uint32_t cr0_npx_state; -} MDFloatingSaveAreaX86; /* FLOATING_SAVE_AREA */ - - -#define MD_CONTEXT_X86_EXTENDED_REGISTERS_SIZE 512 - /* MAXIMUM_SUPPORTED_EXTENSION */ - -typedef struct { - /* The next field determines the layout of the structure, and which parts - * of it are populated */ - uint32_t context_flags; - - /* The next 6 registers are included with MD_CONTEXT_X86_DEBUG_REGISTERS */ - uint32_t dr0; - uint32_t dr1; - uint32_t dr2; - uint32_t dr3; - uint32_t dr6; - uint32_t dr7; - - /* The next field is included with MD_CONTEXT_X86_FLOATING_POINT */ - MDFloatingSaveAreaX86 float_save; - - /* The next 4 registers are included with MD_CONTEXT_X86_SEGMENTS */ - uint32_t gs; - uint32_t fs; - uint32_t es; - uint32_t ds; - /* The next 6 registers are included with MD_CONTEXT_X86_INTEGER */ - uint32_t edi; - uint32_t esi; - uint32_t ebx; - uint32_t edx; - uint32_t ecx; - uint32_t eax; - - /* The next 6 registers are included with MD_CONTEXT_X86_CONTROL */ - uint32_t ebp; - uint32_t eip; - uint32_t cs; /* WinNT.h says "must be sanitized" */ - uint32_t eflags; /* WinNT.h says "must be sanitized" */ - uint32_t esp; - uint32_t ss; - - /* The next field is included with MD_CONTEXT_X86_EXTENDED_REGISTERS. - * It contains vector (MMX/SSE) registers. It it laid out in the - * format used by the fxsave and fsrstor instructions, so it includes - * a copy of the x87 floating-point registers as well. See FXSAVE in - * "Intel Architecture Software Developer's Manual, Volume 2." */ - uint8_t extended_registers[ - MD_CONTEXT_X86_EXTENDED_REGISTERS_SIZE]; -} MDRawContextX86; /* CONTEXT */ - -/* For (MDRawContextX86).context_flags. These values indicate the type of - * context stored in the structure. The high 24 bits identify the CPU, the - * low 8 bits identify the type of context saved. */ -#define MD_CONTEXT_X86 0x00010000 - /* CONTEXT_i386, CONTEXT_i486: identifies CPU */ -#define MD_CONTEXT_X86_CONTROL (MD_CONTEXT_X86 | 0x00000001) - /* CONTEXT_CONTROL */ -#define MD_CONTEXT_X86_INTEGER (MD_CONTEXT_X86 | 0x00000002) - /* CONTEXT_INTEGER */ -#define MD_CONTEXT_X86_SEGMENTS (MD_CONTEXT_X86 | 0x00000004) - /* CONTEXT_SEGMENTS */ -#define MD_CONTEXT_X86_FLOATING_POINT (MD_CONTEXT_X86 | 0x00000008) - /* CONTEXT_FLOATING_POINT */ -#define MD_CONTEXT_X86_DEBUG_REGISTERS (MD_CONTEXT_X86 | 0x00000010) - /* CONTEXT_DEBUG_REGISTERS */ -#define MD_CONTEXT_X86_EXTENDED_REGISTERS (MD_CONTEXT_X86 | 0x00000020) - /* CONTEXT_EXTENDED_REGISTERS */ -#define MD_CONTEXT_X86_XSTATE (MD_CONTEXT_X86 | 0x00000040) - /* CONTEXT_XSTATE */ - -#define MD_CONTEXT_X86_FULL (MD_CONTEXT_X86_CONTROL | \ - MD_CONTEXT_X86_INTEGER | \ - MD_CONTEXT_X86_SEGMENTS) - /* CONTEXT_FULL */ - -#define MD_CONTEXT_X86_ALL (MD_CONTEXT_X86_FULL | \ - MD_CONTEXT_X86_FLOATING_POINT | \ - MD_CONTEXT_X86_DEBUG_REGISTERS | \ - MD_CONTEXT_X86_EXTENDED_REGISTERS) - /* CONTEXT_ALL */ - -#endif /* GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_X86_H__ */ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_exception_linux.h b/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_exception_linux.h deleted file mode 100644 index 9e7e4f1e1..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_exception_linux.h +++ /dev/null @@ -1,87 +0,0 @@ -/* Copyright (c) 2006, Google Inc. - * 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 Google Inc. 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 - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -/* minidump_exception_linux.h: A definition of exception codes for - * Linux - * - * (This is C99 source, please don't corrupt it with C++.) - * - * Author: Mark Mentovai - * Split into its own file: Neal Sidhwaney */ - - -#ifndef GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_LINUX_H__ -#define GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_LINUX_H__ - -#include - -#include "google_breakpad/common/breakpad_types.h" - - -/* For (MDException).exception_code. These values come from bits/signum.h. - */ -typedef enum { - MD_EXCEPTION_CODE_LIN_SIGHUP = 1, /* Hangup (POSIX) */ - MD_EXCEPTION_CODE_LIN_SIGINT = 2, /* Interrupt (ANSI) */ - MD_EXCEPTION_CODE_LIN_SIGQUIT = 3, /* Quit (POSIX) */ - MD_EXCEPTION_CODE_LIN_SIGILL = 4, /* Illegal instruction (ANSI) */ - MD_EXCEPTION_CODE_LIN_SIGTRAP = 5, /* Trace trap (POSIX) */ - MD_EXCEPTION_CODE_LIN_SIGABRT = 6, /* Abort (ANSI) */ - MD_EXCEPTION_CODE_LIN_SIGBUS = 7, /* BUS error (4.2 BSD) */ - MD_EXCEPTION_CODE_LIN_SIGFPE = 8, /* Floating-point exception (ANSI) */ - MD_EXCEPTION_CODE_LIN_SIGKILL = 9, /* Kill, unblockable (POSIX) */ - MD_EXCEPTION_CODE_LIN_SIGUSR1 = 10, /* User-defined signal 1 (POSIX). */ - MD_EXCEPTION_CODE_LIN_SIGSEGV = 11, /* Segmentation violation (ANSI) */ - MD_EXCEPTION_CODE_LIN_SIGUSR2 = 12, /* User-defined signal 2 (POSIX) */ - MD_EXCEPTION_CODE_LIN_SIGPIPE = 13, /* Broken pipe (POSIX) */ - MD_EXCEPTION_CODE_LIN_SIGALRM = 14, /* Alarm clock (POSIX) */ - MD_EXCEPTION_CODE_LIN_SIGTERM = 15, /* Termination (ANSI) */ - MD_EXCEPTION_CODE_LIN_SIGSTKFLT = 16, /* Stack faultd */ - MD_EXCEPTION_CODE_LIN_SIGCHLD = 17, /* Child status has changed (POSIX) */ - MD_EXCEPTION_CODE_LIN_SIGCONT = 18, /* Continue (POSIX) */ - MD_EXCEPTION_CODE_LIN_SIGSTOP = 19, /* Stop, unblockable (POSIX) */ - MD_EXCEPTION_CODE_LIN_SIGTSTP = 20, /* Keyboard stop (POSIX) */ - MD_EXCEPTION_CODE_LIN_SIGTTIN = 21, /* Background read from tty (POSIX) */ - MD_EXCEPTION_CODE_LIN_SIGTTOU = 22, /* Background write to tty (POSIX) */ - MD_EXCEPTION_CODE_LIN_SIGURG = 23, - /* Urgent condition on socket (4.2 BSD) */ - MD_EXCEPTION_CODE_LIN_SIGXCPU = 24, /* CPU limit exceeded (4.2 BSD) */ - MD_EXCEPTION_CODE_LIN_SIGXFSZ = 25, - /* File size limit exceeded (4.2 BSD) */ - MD_EXCEPTION_CODE_LIN_SIGVTALRM = 26, /* Virtual alarm clock (4.2 BSD) */ - MD_EXCEPTION_CODE_LIN_SIGPROF = 27, /* Profiling alarm clock (4.2 BSD) */ - MD_EXCEPTION_CODE_LIN_SIGWINCH = 28, /* Window size change (4.3 BSD, Sun) */ - MD_EXCEPTION_CODE_LIN_SIGIO = 29, /* I/O now possible (4.2 BSD) */ - MD_EXCEPTION_CODE_LIN_SIGPWR = 30, /* Power failure restart (System V) */ - MD_EXCEPTION_CODE_LIN_SIGSYS = 31, /* Bad system call */ - MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED = 0xFFFFFFFF /* No exception, - dump requested. */ -} MDExceptionCodeLinux; - -#endif /* GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_LINUX_H__ */ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_exception_mac.h b/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_exception_mac.h deleted file mode 100644 index 91c1c0974..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_exception_mac.h +++ /dev/null @@ -1,205 +0,0 @@ -/* Copyright (c) 2006, Google Inc. - * 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 Google Inc. 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 - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -/* minidump_exception_mac.h: A definition of exception codes for Mac - * OS X - * - * (This is C99 source, please don't corrupt it with C++.) - * - * Author: Mark Mentovai - * Split into its own file: Neal Sidhwaney */ - - -#ifndef GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_MAC_H__ -#define GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_MAC_H__ - -#include - -#include "google_breakpad/common/breakpad_types.h" - -/* For (MDException).exception_code. Breakpad minidump extension for Mac OS X - * support. Based on Darwin/Mac OS X' mach/exception_types.h. This is - * what Mac OS X calls an "exception", not a "code". */ -typedef enum { - /* Exception code. The high 16 bits of exception_code contains one of - * these values. */ - MD_EXCEPTION_MAC_BAD_ACCESS = 1, /* code can be a kern_return_t */ - /* EXC_BAD_ACCESS */ - MD_EXCEPTION_MAC_BAD_INSTRUCTION = 2, /* code is CPU-specific */ - /* EXC_BAD_INSTRUCTION */ - MD_EXCEPTION_MAC_ARITHMETIC = 3, /* code is CPU-specific */ - /* EXC_ARITHMETIC */ - MD_EXCEPTION_MAC_EMULATION = 4, /* code is CPU-specific */ - /* EXC_EMULATION */ - MD_EXCEPTION_MAC_SOFTWARE = 5, - /* EXC_SOFTWARE */ - MD_EXCEPTION_MAC_BREAKPOINT = 6, /* code is CPU-specific */ - /* EXC_BREAKPOINT */ - MD_EXCEPTION_MAC_SYSCALL = 7, - /* EXC_SYSCALL */ - MD_EXCEPTION_MAC_MACH_SYSCALL = 8, - /* EXC_MACH_SYSCALL */ - MD_EXCEPTION_MAC_RPC_ALERT = 9 - /* EXC_RPC_ALERT */ -} MDExceptionMac; - -/* For (MDException).exception_flags. Breakpad minidump extension for Mac OS X - * support. Based on Darwin/Mac OS X' mach/ppc/exception.h and - * mach/i386/exception.h. This is what Mac OS X calls a "code". */ -typedef enum { - /* With MD_EXCEPTION_BAD_ACCESS. These are relevant kern_return_t values - * from mach/kern_return.h. */ - MD_EXCEPTION_CODE_MAC_INVALID_ADDRESS = 1, - /* KERN_INVALID_ADDRESS */ - MD_EXCEPTION_CODE_MAC_PROTECTION_FAILURE = 2, - /* KERN_PROTECTION_FAILURE */ - MD_EXCEPTION_CODE_MAC_NO_ACCESS = 8, - /* KERN_NO_ACCESS */ - MD_EXCEPTION_CODE_MAC_MEMORY_FAILURE = 9, - /* KERN_MEMORY_FAILURE */ - MD_EXCEPTION_CODE_MAC_MEMORY_ERROR = 10, - /* KERN_MEMORY_ERROR */ - - /* With MD_EXCEPTION_SOFTWARE */ - MD_EXCEPTION_CODE_MAC_BAD_SYSCALL = 0x00010000, /* Mach SIGSYS */ - MD_EXCEPTION_CODE_MAC_BAD_PIPE = 0x00010001, /* Mach SIGPIPE */ - MD_EXCEPTION_CODE_MAC_ABORT = 0x00010002, /* Mach SIGABRT */ - /* Custom values */ - MD_EXCEPTION_CODE_MAC_NS_EXCEPTION = 0xDEADC0DE, /* uncaught NSException */ - - /* With MD_EXCEPTION_MAC_BAD_ACCESS on arm */ - MD_EXCEPTION_CODE_MAC_ARM_DA_ALIGN = 0x0101, /* EXC_ARM_DA_ALIGN */ - MD_EXCEPTION_CODE_MAC_ARM_DA_DEBUG = 0x0102, /* EXC_ARM_DA_DEBUG */ - - /* With MD_EXCEPTION_MAC_BAD_INSTRUCTION on arm */ - MD_EXCEPTION_CODE_MAC_ARM_UNDEFINED = 1, /* EXC_ARM_UNDEFINED */ - - /* With MD_EXCEPTION_MAC_BREAKPOINT on arm */ - MD_EXCEPTION_CODE_MAC_ARM_BREAKPOINT = 1, /* EXC_ARM_BREAKPOINT */ - - /* With MD_EXCEPTION_MAC_BAD_ACCESS on ppc */ - MD_EXCEPTION_CODE_MAC_PPC_VM_PROT_READ = 0x0101, - /* EXC_PPC_VM_PROT_READ */ - MD_EXCEPTION_CODE_MAC_PPC_BADSPACE = 0x0102, - /* EXC_PPC_BADSPACE */ - MD_EXCEPTION_CODE_MAC_PPC_UNALIGNED = 0x0103, - /* EXC_PPC_UNALIGNED */ - - /* With MD_EXCEPTION_MAC_BAD_INSTRUCTION on ppc */ - MD_EXCEPTION_CODE_MAC_PPC_INVALID_SYSCALL = 1, - /* EXC_PPC_INVALID_SYSCALL */ - MD_EXCEPTION_CODE_MAC_PPC_UNIMPLEMENTED_INSTRUCTION = 2, - /* EXC_PPC_UNIPL_INST */ - MD_EXCEPTION_CODE_MAC_PPC_PRIVILEGED_INSTRUCTION = 3, - /* EXC_PPC_PRIVINST */ - MD_EXCEPTION_CODE_MAC_PPC_PRIVILEGED_REGISTER = 4, - /* EXC_PPC_PRIVREG */ - MD_EXCEPTION_CODE_MAC_PPC_TRACE = 5, - /* EXC_PPC_TRACE */ - MD_EXCEPTION_CODE_MAC_PPC_PERFORMANCE_MONITOR = 6, - /* EXC_PPC_PERFMON */ - - /* With MD_EXCEPTION_MAC_ARITHMETIC on ppc */ - MD_EXCEPTION_CODE_MAC_PPC_OVERFLOW = 1, - /* EXC_PPC_OVERFLOW */ - MD_EXCEPTION_CODE_MAC_PPC_ZERO_DIVIDE = 2, - /* EXC_PPC_ZERO_DIVIDE */ - MD_EXCEPTION_CODE_MAC_PPC_FLOAT_INEXACT = 3, - /* EXC_FLT_INEXACT */ - MD_EXCEPTION_CODE_MAC_PPC_FLOAT_ZERO_DIVIDE = 4, - /* EXC_PPC_FLT_ZERO_DIVIDE */ - MD_EXCEPTION_CODE_MAC_PPC_FLOAT_UNDERFLOW = 5, - /* EXC_PPC_FLT_UNDERFLOW */ - MD_EXCEPTION_CODE_MAC_PPC_FLOAT_OVERFLOW = 6, - /* EXC_PPC_FLT_OVERFLOW */ - MD_EXCEPTION_CODE_MAC_PPC_FLOAT_NOT_A_NUMBER = 7, - /* EXC_PPC_FLT_NOT_A_NUMBER */ - - /* With MD_EXCEPTION_MAC_EMULATION on ppc */ - MD_EXCEPTION_CODE_MAC_PPC_NO_EMULATION = 8, - /* EXC_PPC_NOEMULATION */ - MD_EXCEPTION_CODE_MAC_PPC_ALTIVEC_ASSIST = 9, - /* EXC_PPC_ALTIVECASSIST */ - - /* With MD_EXCEPTION_MAC_SOFTWARE on ppc */ - MD_EXCEPTION_CODE_MAC_PPC_TRAP = 0x00000001, /* EXC_PPC_TRAP */ - MD_EXCEPTION_CODE_MAC_PPC_MIGRATE = 0x00010100, /* EXC_PPC_MIGRATE */ - - /* With MD_EXCEPTION_MAC_BREAKPOINT on ppc */ - MD_EXCEPTION_CODE_MAC_PPC_BREAKPOINT = 1, /* EXC_PPC_BREAKPOINT */ - - /* With MD_EXCEPTION_MAC_BAD_INSTRUCTION on x86, see also x86 interrupt - * values below. */ - MD_EXCEPTION_CODE_MAC_X86_INVALID_OPERATION = 1, /* EXC_I386_INVOP */ - - /* With MD_EXCEPTION_MAC_ARITHMETIC on x86 */ - MD_EXCEPTION_CODE_MAC_X86_DIV = 1, /* EXC_I386_DIV */ - MD_EXCEPTION_CODE_MAC_X86_INTO = 2, /* EXC_I386_INTO */ - MD_EXCEPTION_CODE_MAC_X86_NOEXT = 3, /* EXC_I386_NOEXT */ - MD_EXCEPTION_CODE_MAC_X86_EXTOVR = 4, /* EXC_I386_EXTOVR */ - MD_EXCEPTION_CODE_MAC_X86_EXTERR = 5, /* EXC_I386_EXTERR */ - MD_EXCEPTION_CODE_MAC_X86_EMERR = 6, /* EXC_I386_EMERR */ - MD_EXCEPTION_CODE_MAC_X86_BOUND = 7, /* EXC_I386_BOUND */ - MD_EXCEPTION_CODE_MAC_X86_SSEEXTERR = 8, /* EXC_I386_SSEEXTERR */ - - /* With MD_EXCEPTION_MAC_BREAKPOINT on x86 */ - MD_EXCEPTION_CODE_MAC_X86_SGL = 1, /* EXC_I386_SGL */ - MD_EXCEPTION_CODE_MAC_X86_BPT = 2, /* EXC_I386_BPT */ - - /* With MD_EXCEPTION_MAC_BAD_INSTRUCTION on x86. These are the raw - * x86 interrupt codes. Most of these are mapped to other Mach - * exceptions and codes, are handled, or should not occur in user space. - * A few of these will do occur with MD_EXCEPTION_MAC_BAD_INSTRUCTION. */ - /* EXC_I386_DIVERR = 0: mapped to EXC_ARITHMETIC/EXC_I386_DIV */ - /* EXC_I386_SGLSTP = 1: mapped to EXC_BREAKPOINT/EXC_I386_SGL */ - /* EXC_I386_NMIFLT = 2: should not occur in user space */ - /* EXC_I386_BPTFLT = 3: mapped to EXC_BREAKPOINT/EXC_I386_BPT */ - /* EXC_I386_INTOFLT = 4: mapped to EXC_ARITHMETIC/EXC_I386_INTO */ - /* EXC_I386_BOUNDFLT = 5: mapped to EXC_ARITHMETIC/EXC_I386_BOUND */ - /* EXC_I386_INVOPFLT = 6: mapped to EXC_BAD_INSTRUCTION/EXC_I386_INVOP */ - /* EXC_I386_NOEXTFLT = 7: should be handled by the kernel */ - /* EXC_I386_DBLFLT = 8: should be handled (if possible) by the kernel */ - /* EXC_I386_EXTOVRFLT = 9: mapped to EXC_BAD_ACCESS/(PROT_READ|PROT_EXEC) */ - MD_EXCEPTION_CODE_MAC_X86_INVALID_TASK_STATE_SEGMENT = 10, - /* EXC_INVTSSFLT */ - MD_EXCEPTION_CODE_MAC_X86_SEGMENT_NOT_PRESENT = 11, - /* EXC_SEGNPFLT */ - MD_EXCEPTION_CODE_MAC_X86_STACK_FAULT = 12, - /* EXC_STKFLT */ - MD_EXCEPTION_CODE_MAC_X86_GENERAL_PROTECTION_FAULT = 13, - /* EXC_GPFLT */ - /* EXC_I386_PGFLT = 14: should not occur in user space */ - /* EXC_I386_EXTERRFLT = 16: mapped to EXC_ARITHMETIC/EXC_I386_EXTERR */ - MD_EXCEPTION_CODE_MAC_X86_ALIGNMENT_FAULT = 17 - /* EXC_ALIGNFLT (for vector operations) */ - /* EXC_I386_ENOEXTFLT = 32: should be handled by the kernel */ - /* EXC_I386_ENDPERR = 33: should not occur */ -} MDExceptionCodeMac; - -#endif /* GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_MAC_OSX_H__ */ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_exception_ps3.h b/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_exception_ps3.h deleted file mode 100644 index adff5a6bb..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_exception_ps3.h +++ /dev/null @@ -1,67 +0,0 @@ -/* Copyright (c) 2013, Google Inc. - * 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 Google Inc. 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 - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -/* minidump_exception_ps3.h: A definition of exception codes for - * PS3 */ - - -#ifndef GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_PS3_H__ -#define GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_PS3_H__ - -#include - -#include "google_breakpad/common/breakpad_types.h" - -typedef enum { - MD_EXCEPTION_CODE_PS3_UNKNOWN = 0, - MD_EXCEPTION_CODE_PS3_TRAP_EXCEP = 1, - MD_EXCEPTION_CODE_PS3_PRIV_INSTR = 2, - MD_EXCEPTION_CODE_PS3_ILLEGAL_INSTR = 3, - MD_EXCEPTION_CODE_PS3_INSTR_STORAGE = 4, - MD_EXCEPTION_CODE_PS3_INSTR_SEGMENT = 5, - MD_EXCEPTION_CODE_PS3_DATA_STORAGE = 6, - MD_EXCEPTION_CODE_PS3_DATA_SEGMENT = 7, - MD_EXCEPTION_CODE_PS3_FLOAT_POINT = 8, - MD_EXCEPTION_CODE_PS3_DABR_MATCH = 9, - MD_EXCEPTION_CODE_PS3_ALIGN_EXCEP = 10, - MD_EXCEPTION_CODE_PS3_MEMORY_ACCESS = 11, - MD_EXCEPTION_CODE_PS3_COPRO_ALIGN = 12, - MD_EXCEPTION_CODE_PS3_COPRO_INVALID_COM = 13, - MD_EXCEPTION_CODE_PS3_COPRO_ERR = 14, - MD_EXCEPTION_CODE_PS3_COPRO_FIR = 15, - MD_EXCEPTION_CODE_PS3_COPRO_DATA_SEGMENT = 16, - MD_EXCEPTION_CODE_PS3_COPRO_DATA_STORAGE = 17, - MD_EXCEPTION_CODE_PS3_COPRO_STOP_INSTR = 18, - MD_EXCEPTION_CODE_PS3_COPRO_HALT_INSTR = 19, - MD_EXCEPTION_CODE_PS3_COPRO_HALTINST_UNKNOWN = 20, - MD_EXCEPTION_CODE_PS3_COPRO_MEMORY_ACCESS = 21, - MD_EXCEPTION_CODE_PS3_GRAPHIC = 22 -} MDExceptionCodePS3; - -#endif /* GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_PS3_H__ */ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_exception_solaris.h b/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_exception_solaris.h deleted file mode 100644 index f18ddf424..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_exception_solaris.h +++ /dev/null @@ -1,94 +0,0 @@ -/* Copyright (c) 2006, Google Inc. - * 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 Google Inc. 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 - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -/* minidump_exception_solaris.h: A definition of exception codes for - * Solaris - * - * (This is C99 source, please don't corrupt it with C++.) - * - * Author: Mark Mentovai - * Split into its own file: Neal Sidhwaney */ - - -#ifndef GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_SOLARIS_H__ -#define GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_SOLARIS_H__ - -#include - -#include "google_breakpad/common/breakpad_types.h" - -/* For (MDException).exception_code. These values come from sys/iso/signal_iso.h - */ -typedef enum { - MD_EXCEPTION_CODE_SOL_SIGHUP = 1, /* Hangup */ - MD_EXCEPTION_CODE_SOL_SIGINT = 2, /* interrupt (rubout) */ - MD_EXCEPTION_CODE_SOL_SIGQUIT = 3, /* quit (ASCII FS) */ - MD_EXCEPTION_CODE_SOL_SIGILL = 4, /* illegal instruction (not reset when caught) */ - MD_EXCEPTION_CODE_SOL_SIGTRAP = 5, /* trace trap (not reset when caught) */ - MD_EXCEPTION_CODE_SOL_SIGIOT = 6, /* IOT instruction */ - MD_EXCEPTION_CODE_SOL_SIGABRT = 6, /* used by abort, replace SIGIOT in the future */ - MD_EXCEPTION_CODE_SOL_SIGEMT = 7, /* EMT instruction */ - MD_EXCEPTION_CODE_SOL_SIGFPE = 8, /* floating point exception */ - MD_EXCEPTION_CODE_SOL_SIGKILL = 9, /* kill (cannot be caught or ignored) */ - MD_EXCEPTION_CODE_SOL_SIGBUS = 10, /* bus error */ - MD_EXCEPTION_CODE_SOL_SIGSEGV = 11, /* segmentation violation */ - MD_EXCEPTION_CODE_SOL_SIGSYS = 12, /* bad argument to system call */ - MD_EXCEPTION_CODE_SOL_SIGPIPE = 13, /* write on a pipe with no one to read it */ - MD_EXCEPTION_CODE_SOL_SIGALRM = 14, /* alarm clock */ - MD_EXCEPTION_CODE_SOL_SIGTERM = 15, /* software termination signal from kill */ - MD_EXCEPTION_CODE_SOL_SIGUSR1 = 16, /* user defined signal 1 */ - MD_EXCEPTION_CODE_SOL_SIGUSR2 = 17, /* user defined signal 2 */ - MD_EXCEPTION_CODE_SOL_SIGCLD = 18, /* child status change */ - MD_EXCEPTION_CODE_SOL_SIGCHLD = 18, /* child status change alias (POSIX) */ - MD_EXCEPTION_CODE_SOL_SIGPWR = 19, /* power-fail restart */ - MD_EXCEPTION_CODE_SOL_SIGWINCH = 20, /* window size change */ - MD_EXCEPTION_CODE_SOL_SIGURG = 21, /* urgent socket condition */ - MD_EXCEPTION_CODE_SOL_SIGPOLL = 22, /* pollable event occurred */ - MD_EXCEPTION_CODE_SOL_SIGIO = 22, /* socket I/O possible (SIGPOLL alias) */ - MD_EXCEPTION_CODE_SOL_SIGSTOP = 23, /* stop (cannot be caught or ignored) */ - MD_EXCEPTION_CODE_SOL_SIGTSTP = 24, /* user stop requested from tty */ - MD_EXCEPTION_CODE_SOL_SIGCONT = 25, /* stopped process has been continued */ - MD_EXCEPTION_CODE_SOL_SIGTTIN = 26, /* background tty read attempted */ - MD_EXCEPTION_CODE_SOL_SIGTTOU = 27, /* background tty write attempted */ - MD_EXCEPTION_CODE_SOL_SIGVTALRM = 28, /* virtual timer expired */ - MD_EXCEPTION_CODE_SOL_SIGPROF = 29, /* profiling timer expired */ - MD_EXCEPTION_CODE_SOL_SIGXCPU = 30, /* exceeded cpu limit */ - MD_EXCEPTION_CODE_SOL_SIGXFSZ = 31, /* exceeded file size limit */ - MD_EXCEPTION_CODE_SOL_SIGWAITING = 32, /* reserved signal no longer used by threading code */ - MD_EXCEPTION_CODE_SOL_SIGLWP = 33, /* reserved signal no longer used by threading code */ - MD_EXCEPTION_CODE_SOL_SIGFREEZE = 34, /* special signal used by CPR */ - MD_EXCEPTION_CODE_SOL_SIGTHAW = 35, /* special signal used by CPR */ - MD_EXCEPTION_CODE_SOL_SIGCANCEL = 36, /* reserved signal for thread cancellation */ - MD_EXCEPTION_CODE_SOL_SIGLOST = 37, /* resource lost (eg, record-lock lost) */ - MD_EXCEPTION_CODE_SOL_SIGXRES = 38, /* resource control exceeded */ - MD_EXCEPTION_CODE_SOL_SIGJVM1 = 39, /* reserved signal for Java Virtual Machine */ - MD_EXCEPTION_CODE_SOL_SIGJVM2 = 40 /* reserved signal for Java Virtual Machine */ -} MDExceptionCodeSolaris; - -#endif /* GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_SOLARIS_H__ */ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_exception_win32.h b/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_exception_win32.h deleted file mode 100644 index e4cd59edd..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_exception_win32.h +++ /dev/null @@ -1,2261 +0,0 @@ -/* Copyright (c) 2006, Google Inc. - * 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 Google Inc. 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 - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -/* minidump_exception_win32.h: Definitions of exception codes for - * Win32 platform - * - * (This is C99 source, please don't corrupt it with C++.) - * - * Author: Mark Mentovai - * Split into its own file: Neal Sidhwaney */ - - -#ifndef GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_WIN32_H__ -#define GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_WIN32_H__ - -#include - -#include "google_breakpad/common/breakpad_types.h" - - -/* For (MDException).exception_code. These values come from WinBase.h - * and WinNT.h (names beginning with EXCEPTION_ are in WinBase.h, - * they are STATUS_ in WinNT.h). */ -typedef enum { - MD_EXCEPTION_CODE_WIN_CONTROL_C = 0x40010005, - /* DBG_CONTROL_C */ - MD_EXCEPTION_CODE_WIN_GUARD_PAGE_VIOLATION = 0x80000001, - /* EXCEPTION_GUARD_PAGE */ - MD_EXCEPTION_CODE_WIN_DATATYPE_MISALIGNMENT = 0x80000002, - /* EXCEPTION_DATATYPE_MISALIGNMENT */ - MD_EXCEPTION_CODE_WIN_BREAKPOINT = 0x80000003, - /* EXCEPTION_BREAKPOINT */ - MD_EXCEPTION_CODE_WIN_SINGLE_STEP = 0x80000004, - /* EXCEPTION_SINGLE_STEP */ - MD_EXCEPTION_CODE_WIN_ACCESS_VIOLATION = 0xc0000005, - /* EXCEPTION_ACCESS_VIOLATION */ - MD_EXCEPTION_CODE_WIN_IN_PAGE_ERROR = 0xc0000006, - /* EXCEPTION_IN_PAGE_ERROR */ - MD_EXCEPTION_CODE_WIN_INVALID_HANDLE = 0xc0000008, - /* EXCEPTION_INVALID_HANDLE */ - MD_EXCEPTION_CODE_WIN_ILLEGAL_INSTRUCTION = 0xc000001d, - /* EXCEPTION_ILLEGAL_INSTRUCTION */ - MD_EXCEPTION_CODE_WIN_NONCONTINUABLE_EXCEPTION = 0xc0000025, - /* EXCEPTION_NONCONTINUABLE_EXCEPTION */ - MD_EXCEPTION_CODE_WIN_INVALID_DISPOSITION = 0xc0000026, - /* EXCEPTION_INVALID_DISPOSITION */ - MD_EXCEPTION_CODE_WIN_ARRAY_BOUNDS_EXCEEDED = 0xc000008c, - /* EXCEPTION_BOUNDS_EXCEEDED */ - MD_EXCEPTION_CODE_WIN_FLOAT_DENORMAL_OPERAND = 0xc000008d, - /* EXCEPTION_FLT_DENORMAL_OPERAND */ - MD_EXCEPTION_CODE_WIN_FLOAT_DIVIDE_BY_ZERO = 0xc000008e, - /* EXCEPTION_FLT_DIVIDE_BY_ZERO */ - MD_EXCEPTION_CODE_WIN_FLOAT_INEXACT_RESULT = 0xc000008f, - /* EXCEPTION_FLT_INEXACT_RESULT */ - MD_EXCEPTION_CODE_WIN_FLOAT_INVALID_OPERATION = 0xc0000090, - /* EXCEPTION_FLT_INVALID_OPERATION */ - MD_EXCEPTION_CODE_WIN_FLOAT_OVERFLOW = 0xc0000091, - /* EXCEPTION_FLT_OVERFLOW */ - MD_EXCEPTION_CODE_WIN_FLOAT_STACK_CHECK = 0xc0000092, - /* EXCEPTION_FLT_STACK_CHECK */ - MD_EXCEPTION_CODE_WIN_FLOAT_UNDERFLOW = 0xc0000093, - /* EXCEPTION_FLT_UNDERFLOW */ - MD_EXCEPTION_CODE_WIN_INTEGER_DIVIDE_BY_ZERO = 0xc0000094, - /* EXCEPTION_INT_DIVIDE_BY_ZERO */ - MD_EXCEPTION_CODE_WIN_INTEGER_OVERFLOW = 0xc0000095, - /* EXCEPTION_INT_OVERFLOW */ - MD_EXCEPTION_CODE_WIN_PRIVILEGED_INSTRUCTION = 0xc0000096, - /* EXCEPTION_PRIV_INSTRUCTION */ - MD_EXCEPTION_CODE_WIN_STACK_OVERFLOW = 0xc00000fd, - /* EXCEPTION_STACK_OVERFLOW */ - MD_EXCEPTION_CODE_WIN_POSSIBLE_DEADLOCK = 0xc0000194, - /* EXCEPTION_POSSIBLE_DEADLOCK */ - MD_EXCEPTION_CODE_WIN_STACK_BUFFER_OVERRUN = 0xc0000409, - /* STATUS_STACK_BUFFER_OVERRUN */ - MD_EXCEPTION_CODE_WIN_HEAP_CORRUPTION = 0xc0000374, - /* STATUS_HEAP_CORRUPTION */ - MD_EXCEPTION_CODE_WIN_UNHANDLED_CPP_EXCEPTION = 0xe06d7363 - /* Per http://support.microsoft.com/kb/185294, - generated by Visual C++ compiler */ -} MDExceptionCodeWin; - - -/* For (MDException).exception_information[2], when (MDException).exception_code - * is MD_EXCEPTION_CODE_WIN_IN_PAGE_ERROR. This describes the underlying reason - * for the error. These values come from ntstatus.h. - * - * The content of this enum was created from ntstatus.h in the 8.1 SDK with - * - * egrep '#define [A-Z_0-9]+\s+\(\(NTSTATUS\)0xC[0-9A-F]+L\)' ntstatus.h - * | tr -d '\r' - * | sed -r 's@#define ([A-Z_0-9]+)\s+\(\(NTSTATUS\)(0xC[0-9A-F]+)L\).*@\2 \1@' - * | sort - * | sed -r 's@(0xC[0-9A-F]+) ([A-Z_0-9]+)@ MD_NTSTATUS_WIN_\2 = \1,@' - * - * With easy copy to clipboard with - * | xclip -selection c # on linux - * | clip # on windows - * | pbcopy # on mac - * - * and then the last comma manually removed. */ -typedef enum { - MD_NTSTATUS_WIN_STATUS_UNSUCCESSFUL = 0xC0000001, - MD_NTSTATUS_WIN_STATUS_NOT_IMPLEMENTED = 0xC0000002, - MD_NTSTATUS_WIN_STATUS_INVALID_INFO_CLASS = 0xC0000003, - MD_NTSTATUS_WIN_STATUS_INFO_LENGTH_MISMATCH = 0xC0000004, - MD_NTSTATUS_WIN_STATUS_ACCESS_VIOLATION = 0xC0000005, - MD_NTSTATUS_WIN_STATUS_IN_PAGE_ERROR = 0xC0000006, - MD_NTSTATUS_WIN_STATUS_PAGEFILE_QUOTA = 0xC0000007, - MD_NTSTATUS_WIN_STATUS_INVALID_HANDLE = 0xC0000008, - MD_NTSTATUS_WIN_STATUS_BAD_INITIAL_STACK = 0xC0000009, - MD_NTSTATUS_WIN_STATUS_BAD_INITIAL_PC = 0xC000000A, - MD_NTSTATUS_WIN_STATUS_INVALID_CID = 0xC000000B, - MD_NTSTATUS_WIN_STATUS_TIMER_NOT_CANCELED = 0xC000000C, - MD_NTSTATUS_WIN_STATUS_INVALID_PARAMETER = 0xC000000D, - MD_NTSTATUS_WIN_STATUS_NO_SUCH_DEVICE = 0xC000000E, - MD_NTSTATUS_WIN_STATUS_NO_SUCH_FILE = 0xC000000F, - MD_NTSTATUS_WIN_STATUS_INVALID_DEVICE_REQUEST = 0xC0000010, - MD_NTSTATUS_WIN_STATUS_END_OF_FILE = 0xC0000011, - MD_NTSTATUS_WIN_STATUS_WRONG_VOLUME = 0xC0000012, - MD_NTSTATUS_WIN_STATUS_NO_MEDIA_IN_DEVICE = 0xC0000013, - MD_NTSTATUS_WIN_STATUS_UNRECOGNIZED_MEDIA = 0xC0000014, - MD_NTSTATUS_WIN_STATUS_NONEXISTENT_SECTOR = 0xC0000015, - MD_NTSTATUS_WIN_STATUS_MORE_PROCESSING_REQUIRED = 0xC0000016, - MD_NTSTATUS_WIN_STATUS_NO_MEMORY = 0xC0000017, - MD_NTSTATUS_WIN_STATUS_CONFLICTING_ADDRESSES = 0xC0000018, - MD_NTSTATUS_WIN_STATUS_NOT_MAPPED_VIEW = 0xC0000019, - MD_NTSTATUS_WIN_STATUS_UNABLE_TO_FREE_VM = 0xC000001A, - MD_NTSTATUS_WIN_STATUS_UNABLE_TO_DELETE_SECTION = 0xC000001B, - MD_NTSTATUS_WIN_STATUS_INVALID_SYSTEM_SERVICE = 0xC000001C, - MD_NTSTATUS_WIN_STATUS_ILLEGAL_INSTRUCTION = 0xC000001D, - MD_NTSTATUS_WIN_STATUS_INVALID_LOCK_SEQUENCE = 0xC000001E, - MD_NTSTATUS_WIN_STATUS_INVALID_VIEW_SIZE = 0xC000001F, - MD_NTSTATUS_WIN_STATUS_INVALID_FILE_FOR_SECTION = 0xC0000020, - MD_NTSTATUS_WIN_STATUS_ALREADY_COMMITTED = 0xC0000021, - MD_NTSTATUS_WIN_STATUS_ACCESS_DENIED = 0xC0000022, - MD_NTSTATUS_WIN_STATUS_BUFFER_TOO_SMALL = 0xC0000023, - MD_NTSTATUS_WIN_STATUS_OBJECT_TYPE_MISMATCH = 0xC0000024, - MD_NTSTATUS_WIN_STATUS_NONCONTINUABLE_EXCEPTION = 0xC0000025, - MD_NTSTATUS_WIN_STATUS_INVALID_DISPOSITION = 0xC0000026, - MD_NTSTATUS_WIN_STATUS_UNWIND = 0xC0000027, - MD_NTSTATUS_WIN_STATUS_BAD_STACK = 0xC0000028, - MD_NTSTATUS_WIN_STATUS_INVALID_UNWIND_TARGET = 0xC0000029, - MD_NTSTATUS_WIN_STATUS_NOT_LOCKED = 0xC000002A, - MD_NTSTATUS_WIN_STATUS_PARITY_ERROR = 0xC000002B, - MD_NTSTATUS_WIN_STATUS_UNABLE_TO_DECOMMIT_VM = 0xC000002C, - MD_NTSTATUS_WIN_STATUS_NOT_COMMITTED = 0xC000002D, - MD_NTSTATUS_WIN_STATUS_INVALID_PORT_ATTRIBUTES = 0xC000002E, - MD_NTSTATUS_WIN_STATUS_PORT_MESSAGE_TOO_LONG = 0xC000002F, - MD_NTSTATUS_WIN_STATUS_INVALID_PARAMETER_MIX = 0xC0000030, - MD_NTSTATUS_WIN_STATUS_INVALID_QUOTA_LOWER = 0xC0000031, - MD_NTSTATUS_WIN_STATUS_DISK_CORRUPT_ERROR = 0xC0000032, - MD_NTSTATUS_WIN_STATUS_OBJECT_NAME_INVALID = 0xC0000033, - MD_NTSTATUS_WIN_STATUS_OBJECT_NAME_NOT_FOUND = 0xC0000034, - MD_NTSTATUS_WIN_STATUS_OBJECT_NAME_COLLISION = 0xC0000035, - MD_NTSTATUS_WIN_STATUS_PORT_DISCONNECTED = 0xC0000037, - MD_NTSTATUS_WIN_STATUS_DEVICE_ALREADY_ATTACHED = 0xC0000038, - MD_NTSTATUS_WIN_STATUS_OBJECT_PATH_INVALID = 0xC0000039, - MD_NTSTATUS_WIN_STATUS_OBJECT_PATH_NOT_FOUND = 0xC000003A, - MD_NTSTATUS_WIN_STATUS_OBJECT_PATH_SYNTAX_BAD = 0xC000003B, - MD_NTSTATUS_WIN_STATUS_DATA_OVERRUN = 0xC000003C, - MD_NTSTATUS_WIN_STATUS_DATA_LATE_ERROR = 0xC000003D, - MD_NTSTATUS_WIN_STATUS_DATA_ERROR = 0xC000003E, - MD_NTSTATUS_WIN_STATUS_CRC_ERROR = 0xC000003F, - MD_NTSTATUS_WIN_STATUS_SECTION_TOO_BIG = 0xC0000040, - MD_NTSTATUS_WIN_STATUS_PORT_CONNECTION_REFUSED = 0xC0000041, - MD_NTSTATUS_WIN_STATUS_INVALID_PORT_HANDLE = 0xC0000042, - MD_NTSTATUS_WIN_STATUS_SHARING_VIOLATION = 0xC0000043, - MD_NTSTATUS_WIN_STATUS_QUOTA_EXCEEDED = 0xC0000044, - MD_NTSTATUS_WIN_STATUS_INVALID_PAGE_PROTECTION = 0xC0000045, - MD_NTSTATUS_WIN_STATUS_MUTANT_NOT_OWNED = 0xC0000046, - MD_NTSTATUS_WIN_STATUS_SEMAPHORE_LIMIT_EXCEEDED = 0xC0000047, - MD_NTSTATUS_WIN_STATUS_PORT_ALREADY_SET = 0xC0000048, - MD_NTSTATUS_WIN_STATUS_SECTION_NOT_IMAGE = 0xC0000049, - MD_NTSTATUS_WIN_STATUS_SUSPEND_COUNT_EXCEEDED = 0xC000004A, - MD_NTSTATUS_WIN_STATUS_THREAD_IS_TERMINATING = 0xC000004B, - MD_NTSTATUS_WIN_STATUS_BAD_WORKING_SET_LIMIT = 0xC000004C, - MD_NTSTATUS_WIN_STATUS_INCOMPATIBLE_FILE_MAP = 0xC000004D, - MD_NTSTATUS_WIN_STATUS_SECTION_PROTECTION = 0xC000004E, - MD_NTSTATUS_WIN_STATUS_EAS_NOT_SUPPORTED = 0xC000004F, - MD_NTSTATUS_WIN_STATUS_EA_TOO_LARGE = 0xC0000050, - MD_NTSTATUS_WIN_STATUS_NONEXISTENT_EA_ENTRY = 0xC0000051, - MD_NTSTATUS_WIN_STATUS_NO_EAS_ON_FILE = 0xC0000052, - MD_NTSTATUS_WIN_STATUS_EA_CORRUPT_ERROR = 0xC0000053, - MD_NTSTATUS_WIN_STATUS_FILE_LOCK_CONFLICT = 0xC0000054, - MD_NTSTATUS_WIN_STATUS_LOCK_NOT_GRANTED = 0xC0000055, - MD_NTSTATUS_WIN_STATUS_DELETE_PENDING = 0xC0000056, - MD_NTSTATUS_WIN_STATUS_CTL_FILE_NOT_SUPPORTED = 0xC0000057, - MD_NTSTATUS_WIN_STATUS_UNKNOWN_REVISION = 0xC0000058, - MD_NTSTATUS_WIN_STATUS_REVISION_MISMATCH = 0xC0000059, - MD_NTSTATUS_WIN_STATUS_INVALID_OWNER = 0xC000005A, - MD_NTSTATUS_WIN_STATUS_INVALID_PRIMARY_GROUP = 0xC000005B, - MD_NTSTATUS_WIN_STATUS_NO_IMPERSONATION_TOKEN = 0xC000005C, - MD_NTSTATUS_WIN_STATUS_CANT_DISABLE_MANDATORY = 0xC000005D, - MD_NTSTATUS_WIN_STATUS_NO_LOGON_SERVERS = 0xC000005E, - MD_NTSTATUS_WIN_STATUS_NO_SUCH_LOGON_SESSION = 0xC000005F, - MD_NTSTATUS_WIN_STATUS_NO_SUCH_PRIVILEGE = 0xC0000060, - MD_NTSTATUS_WIN_STATUS_PRIVILEGE_NOT_HELD = 0xC0000061, - MD_NTSTATUS_WIN_STATUS_INVALID_ACCOUNT_NAME = 0xC0000062, - MD_NTSTATUS_WIN_STATUS_USER_EXISTS = 0xC0000063, - MD_NTSTATUS_WIN_STATUS_NO_SUCH_USER = 0xC0000064, - MD_NTSTATUS_WIN_STATUS_GROUP_EXISTS = 0xC0000065, - MD_NTSTATUS_WIN_STATUS_NO_SUCH_GROUP = 0xC0000066, - MD_NTSTATUS_WIN_STATUS_MEMBER_IN_GROUP = 0xC0000067, - MD_NTSTATUS_WIN_STATUS_MEMBER_NOT_IN_GROUP = 0xC0000068, - MD_NTSTATUS_WIN_STATUS_LAST_ADMIN = 0xC0000069, - MD_NTSTATUS_WIN_STATUS_WRONG_PASSWORD = 0xC000006A, - MD_NTSTATUS_WIN_STATUS_ILL_FORMED_PASSWORD = 0xC000006B, - MD_NTSTATUS_WIN_STATUS_PASSWORD_RESTRICTION = 0xC000006C, - MD_NTSTATUS_WIN_STATUS_LOGON_FAILURE = 0xC000006D, - MD_NTSTATUS_WIN_STATUS_ACCOUNT_RESTRICTION = 0xC000006E, - MD_NTSTATUS_WIN_STATUS_INVALID_LOGON_HOURS = 0xC000006F, - MD_NTSTATUS_WIN_STATUS_INVALID_WORKSTATION = 0xC0000070, - MD_NTSTATUS_WIN_STATUS_PASSWORD_EXPIRED = 0xC0000071, - MD_NTSTATUS_WIN_STATUS_ACCOUNT_DISABLED = 0xC0000072, - MD_NTSTATUS_WIN_STATUS_NONE_MAPPED = 0xC0000073, - MD_NTSTATUS_WIN_STATUS_TOO_MANY_LUIDS_REQUESTED = 0xC0000074, - MD_NTSTATUS_WIN_STATUS_LUIDS_EXHAUSTED = 0xC0000075, - MD_NTSTATUS_WIN_STATUS_INVALID_SUB_AUTHORITY = 0xC0000076, - MD_NTSTATUS_WIN_STATUS_INVALID_ACL = 0xC0000077, - MD_NTSTATUS_WIN_STATUS_INVALID_SID = 0xC0000078, - MD_NTSTATUS_WIN_STATUS_INVALID_SECURITY_DESCR = 0xC0000079, - MD_NTSTATUS_WIN_STATUS_PROCEDURE_NOT_FOUND = 0xC000007A, - MD_NTSTATUS_WIN_STATUS_INVALID_IMAGE_FORMAT = 0xC000007B, - MD_NTSTATUS_WIN_STATUS_NO_TOKEN = 0xC000007C, - MD_NTSTATUS_WIN_STATUS_BAD_INHERITANCE_ACL = 0xC000007D, - MD_NTSTATUS_WIN_STATUS_RANGE_NOT_LOCKED = 0xC000007E, - MD_NTSTATUS_WIN_STATUS_DISK_FULL = 0xC000007F, - MD_NTSTATUS_WIN_STATUS_SERVER_DISABLED = 0xC0000080, - MD_NTSTATUS_WIN_STATUS_SERVER_NOT_DISABLED = 0xC0000081, - MD_NTSTATUS_WIN_STATUS_TOO_MANY_GUIDS_REQUESTED = 0xC0000082, - MD_NTSTATUS_WIN_STATUS_GUIDS_EXHAUSTED = 0xC0000083, - MD_NTSTATUS_WIN_STATUS_INVALID_ID_AUTHORITY = 0xC0000084, - MD_NTSTATUS_WIN_STATUS_AGENTS_EXHAUSTED = 0xC0000085, - MD_NTSTATUS_WIN_STATUS_INVALID_VOLUME_LABEL = 0xC0000086, - MD_NTSTATUS_WIN_STATUS_SECTION_NOT_EXTENDED = 0xC0000087, - MD_NTSTATUS_WIN_STATUS_NOT_MAPPED_DATA = 0xC0000088, - MD_NTSTATUS_WIN_STATUS_RESOURCE_DATA_NOT_FOUND = 0xC0000089, - MD_NTSTATUS_WIN_STATUS_RESOURCE_TYPE_NOT_FOUND = 0xC000008A, - MD_NTSTATUS_WIN_STATUS_RESOURCE_NAME_NOT_FOUND = 0xC000008B, - MD_NTSTATUS_WIN_STATUS_ARRAY_BOUNDS_EXCEEDED = 0xC000008C, - MD_NTSTATUS_WIN_STATUS_FLOAT_DENORMAL_OPERAND = 0xC000008D, - MD_NTSTATUS_WIN_STATUS_FLOAT_DIVIDE_BY_ZERO = 0xC000008E, - MD_NTSTATUS_WIN_STATUS_FLOAT_INEXACT_RESULT = 0xC000008F, - MD_NTSTATUS_WIN_STATUS_FLOAT_INVALID_OPERATION = 0xC0000090, - MD_NTSTATUS_WIN_STATUS_FLOAT_OVERFLOW = 0xC0000091, - MD_NTSTATUS_WIN_STATUS_FLOAT_STACK_CHECK = 0xC0000092, - MD_NTSTATUS_WIN_STATUS_FLOAT_UNDERFLOW = 0xC0000093, - MD_NTSTATUS_WIN_STATUS_INTEGER_DIVIDE_BY_ZERO = 0xC0000094, - MD_NTSTATUS_WIN_STATUS_INTEGER_OVERFLOW = 0xC0000095, - MD_NTSTATUS_WIN_STATUS_PRIVILEGED_INSTRUCTION = 0xC0000096, - MD_NTSTATUS_WIN_STATUS_TOO_MANY_PAGING_FILES = 0xC0000097, - MD_NTSTATUS_WIN_STATUS_FILE_INVALID = 0xC0000098, - MD_NTSTATUS_WIN_STATUS_ALLOTTED_SPACE_EXCEEDED = 0xC0000099, - MD_NTSTATUS_WIN_STATUS_INSUFFICIENT_RESOURCES = 0xC000009A, - MD_NTSTATUS_WIN_STATUS_DFS_EXIT_PATH_FOUND = 0xC000009B, - MD_NTSTATUS_WIN_STATUS_DEVICE_DATA_ERROR = 0xC000009C, - MD_NTSTATUS_WIN_STATUS_DEVICE_NOT_CONNECTED = 0xC000009D, - MD_NTSTATUS_WIN_STATUS_DEVICE_POWER_FAILURE = 0xC000009E, - MD_NTSTATUS_WIN_STATUS_FREE_VM_NOT_AT_BASE = 0xC000009F, - MD_NTSTATUS_WIN_STATUS_MEMORY_NOT_ALLOCATED = 0xC00000A0, - MD_NTSTATUS_WIN_STATUS_WORKING_SET_QUOTA = 0xC00000A1, - MD_NTSTATUS_WIN_STATUS_MEDIA_WRITE_PROTECTED = 0xC00000A2, - MD_NTSTATUS_WIN_STATUS_DEVICE_NOT_READY = 0xC00000A3, - MD_NTSTATUS_WIN_STATUS_INVALID_GROUP_ATTRIBUTES = 0xC00000A4, - MD_NTSTATUS_WIN_STATUS_BAD_IMPERSONATION_LEVEL = 0xC00000A5, - MD_NTSTATUS_WIN_STATUS_CANT_OPEN_ANONYMOUS = 0xC00000A6, - MD_NTSTATUS_WIN_STATUS_BAD_VALIDATION_CLASS = 0xC00000A7, - MD_NTSTATUS_WIN_STATUS_BAD_TOKEN_TYPE = 0xC00000A8, - MD_NTSTATUS_WIN_STATUS_BAD_MASTER_BOOT_RECORD = 0xC00000A9, - MD_NTSTATUS_WIN_STATUS_INSTRUCTION_MISALIGNMENT = 0xC00000AA, - MD_NTSTATUS_WIN_STATUS_INSTANCE_NOT_AVAILABLE = 0xC00000AB, - MD_NTSTATUS_WIN_STATUS_PIPE_NOT_AVAILABLE = 0xC00000AC, - MD_NTSTATUS_WIN_STATUS_INVALID_PIPE_STATE = 0xC00000AD, - MD_NTSTATUS_WIN_STATUS_PIPE_BUSY = 0xC00000AE, - MD_NTSTATUS_WIN_STATUS_ILLEGAL_FUNCTION = 0xC00000AF, - MD_NTSTATUS_WIN_STATUS_PIPE_DISCONNECTED = 0xC00000B0, - MD_NTSTATUS_WIN_STATUS_PIPE_CLOSING = 0xC00000B1, - MD_NTSTATUS_WIN_STATUS_PIPE_CONNECTED = 0xC00000B2, - MD_NTSTATUS_WIN_STATUS_PIPE_LISTENING = 0xC00000B3, - MD_NTSTATUS_WIN_STATUS_INVALID_READ_MODE = 0xC00000B4, - MD_NTSTATUS_WIN_STATUS_IO_TIMEOUT = 0xC00000B5, - MD_NTSTATUS_WIN_STATUS_FILE_FORCED_CLOSED = 0xC00000B6, - MD_NTSTATUS_WIN_STATUS_PROFILING_NOT_STARTED = 0xC00000B7, - MD_NTSTATUS_WIN_STATUS_PROFILING_NOT_STOPPED = 0xC00000B8, - MD_NTSTATUS_WIN_STATUS_COULD_NOT_INTERPRET = 0xC00000B9, - MD_NTSTATUS_WIN_STATUS_FILE_IS_A_DIRECTORY = 0xC00000BA, - MD_NTSTATUS_WIN_STATUS_NOT_SUPPORTED = 0xC00000BB, - MD_NTSTATUS_WIN_STATUS_REMOTE_NOT_LISTENING = 0xC00000BC, - MD_NTSTATUS_WIN_STATUS_DUPLICATE_NAME = 0xC00000BD, - MD_NTSTATUS_WIN_STATUS_BAD_NETWORK_PATH = 0xC00000BE, - MD_NTSTATUS_WIN_STATUS_NETWORK_BUSY = 0xC00000BF, - MD_NTSTATUS_WIN_STATUS_DEVICE_DOES_NOT_EXIST = 0xC00000C0, - MD_NTSTATUS_WIN_STATUS_TOO_MANY_COMMANDS = 0xC00000C1, - MD_NTSTATUS_WIN_STATUS_ADAPTER_HARDWARE_ERROR = 0xC00000C2, - MD_NTSTATUS_WIN_STATUS_INVALID_NETWORK_RESPONSE = 0xC00000C3, - MD_NTSTATUS_WIN_STATUS_UNEXPECTED_NETWORK_ERROR = 0xC00000C4, - MD_NTSTATUS_WIN_STATUS_BAD_REMOTE_ADAPTER = 0xC00000C5, - MD_NTSTATUS_WIN_STATUS_PRINT_QUEUE_FULL = 0xC00000C6, - MD_NTSTATUS_WIN_STATUS_NO_SPOOL_SPACE = 0xC00000C7, - MD_NTSTATUS_WIN_STATUS_PRINT_CANCELLED = 0xC00000C8, - MD_NTSTATUS_WIN_STATUS_NETWORK_NAME_DELETED = 0xC00000C9, - MD_NTSTATUS_WIN_STATUS_NETWORK_ACCESS_DENIED = 0xC00000CA, - MD_NTSTATUS_WIN_STATUS_BAD_DEVICE_TYPE = 0xC00000CB, - MD_NTSTATUS_WIN_STATUS_BAD_NETWORK_NAME = 0xC00000CC, - MD_NTSTATUS_WIN_STATUS_TOO_MANY_NAMES = 0xC00000CD, - MD_NTSTATUS_WIN_STATUS_TOO_MANY_SESSIONS = 0xC00000CE, - MD_NTSTATUS_WIN_STATUS_SHARING_PAUSED = 0xC00000CF, - MD_NTSTATUS_WIN_STATUS_REQUEST_NOT_ACCEPTED = 0xC00000D0, - MD_NTSTATUS_WIN_STATUS_REDIRECTOR_PAUSED = 0xC00000D1, - MD_NTSTATUS_WIN_STATUS_NET_WRITE_FAULT = 0xC00000D2, - MD_NTSTATUS_WIN_STATUS_PROFILING_AT_LIMIT = 0xC00000D3, - MD_NTSTATUS_WIN_STATUS_NOT_SAME_DEVICE = 0xC00000D4, - MD_NTSTATUS_WIN_STATUS_FILE_RENAMED = 0xC00000D5, - MD_NTSTATUS_WIN_STATUS_VIRTUAL_CIRCUIT_CLOSED = 0xC00000D6, - MD_NTSTATUS_WIN_STATUS_NO_SECURITY_ON_OBJECT = 0xC00000D7, - MD_NTSTATUS_WIN_STATUS_CANT_WAIT = 0xC00000D8, - MD_NTSTATUS_WIN_STATUS_PIPE_EMPTY = 0xC00000D9, - MD_NTSTATUS_WIN_STATUS_CANT_ACCESS_DOMAIN_INFO = 0xC00000DA, - MD_NTSTATUS_WIN_STATUS_CANT_TERMINATE_SELF = 0xC00000DB, - MD_NTSTATUS_WIN_STATUS_INVALID_SERVER_STATE = 0xC00000DC, - MD_NTSTATUS_WIN_STATUS_INVALID_DOMAIN_STATE = 0xC00000DD, - MD_NTSTATUS_WIN_STATUS_INVALID_DOMAIN_ROLE = 0xC00000DE, - MD_NTSTATUS_WIN_STATUS_NO_SUCH_DOMAIN = 0xC00000DF, - MD_NTSTATUS_WIN_STATUS_DOMAIN_EXISTS = 0xC00000E0, - MD_NTSTATUS_WIN_STATUS_DOMAIN_LIMIT_EXCEEDED = 0xC00000E1, - MD_NTSTATUS_WIN_STATUS_OPLOCK_NOT_GRANTED = 0xC00000E2, - MD_NTSTATUS_WIN_STATUS_INVALID_OPLOCK_PROTOCOL = 0xC00000E3, - MD_NTSTATUS_WIN_STATUS_INTERNAL_DB_CORRUPTION = 0xC00000E4, - MD_NTSTATUS_WIN_STATUS_INTERNAL_ERROR = 0xC00000E5, - MD_NTSTATUS_WIN_STATUS_GENERIC_NOT_MAPPED = 0xC00000E6, - MD_NTSTATUS_WIN_STATUS_BAD_DESCRIPTOR_FORMAT = 0xC00000E7, - MD_NTSTATUS_WIN_STATUS_INVALID_USER_BUFFER = 0xC00000E8, - MD_NTSTATUS_WIN_STATUS_UNEXPECTED_IO_ERROR = 0xC00000E9, - MD_NTSTATUS_WIN_STATUS_UNEXPECTED_MM_CREATE_ERR = 0xC00000EA, - MD_NTSTATUS_WIN_STATUS_UNEXPECTED_MM_MAP_ERROR = 0xC00000EB, - MD_NTSTATUS_WIN_STATUS_UNEXPECTED_MM_EXTEND_ERR = 0xC00000EC, - MD_NTSTATUS_WIN_STATUS_NOT_LOGON_PROCESS = 0xC00000ED, - MD_NTSTATUS_WIN_STATUS_LOGON_SESSION_EXISTS = 0xC00000EE, - MD_NTSTATUS_WIN_STATUS_INVALID_PARAMETER_1 = 0xC00000EF, - MD_NTSTATUS_WIN_STATUS_INVALID_PARAMETER_2 = 0xC00000F0, - MD_NTSTATUS_WIN_STATUS_INVALID_PARAMETER_3 = 0xC00000F1, - MD_NTSTATUS_WIN_STATUS_INVALID_PARAMETER_4 = 0xC00000F2, - MD_NTSTATUS_WIN_STATUS_INVALID_PARAMETER_5 = 0xC00000F3, - MD_NTSTATUS_WIN_STATUS_INVALID_PARAMETER_6 = 0xC00000F4, - MD_NTSTATUS_WIN_STATUS_INVALID_PARAMETER_7 = 0xC00000F5, - MD_NTSTATUS_WIN_STATUS_INVALID_PARAMETER_8 = 0xC00000F6, - MD_NTSTATUS_WIN_STATUS_INVALID_PARAMETER_9 = 0xC00000F7, - MD_NTSTATUS_WIN_STATUS_INVALID_PARAMETER_10 = 0xC00000F8, - MD_NTSTATUS_WIN_STATUS_INVALID_PARAMETER_11 = 0xC00000F9, - MD_NTSTATUS_WIN_STATUS_INVALID_PARAMETER_12 = 0xC00000FA, - MD_NTSTATUS_WIN_STATUS_REDIRECTOR_NOT_STARTED = 0xC00000FB, - MD_NTSTATUS_WIN_STATUS_REDIRECTOR_STARTED = 0xC00000FC, - MD_NTSTATUS_WIN_STATUS_STACK_OVERFLOW = 0xC00000FD, - MD_NTSTATUS_WIN_STATUS_NO_SUCH_PACKAGE = 0xC00000FE, - MD_NTSTATUS_WIN_STATUS_BAD_FUNCTION_TABLE = 0xC00000FF, - MD_NTSTATUS_WIN_STATUS_VARIABLE_NOT_FOUND = 0xC0000100, - MD_NTSTATUS_WIN_STATUS_DIRECTORY_NOT_EMPTY = 0xC0000101, - MD_NTSTATUS_WIN_STATUS_FILE_CORRUPT_ERROR = 0xC0000102, - MD_NTSTATUS_WIN_STATUS_NOT_A_DIRECTORY = 0xC0000103, - MD_NTSTATUS_WIN_STATUS_BAD_LOGON_SESSION_STATE = 0xC0000104, - MD_NTSTATUS_WIN_STATUS_LOGON_SESSION_COLLISION = 0xC0000105, - MD_NTSTATUS_WIN_STATUS_NAME_TOO_LONG = 0xC0000106, - MD_NTSTATUS_WIN_STATUS_FILES_OPEN = 0xC0000107, - MD_NTSTATUS_WIN_STATUS_CONNECTION_IN_USE = 0xC0000108, - MD_NTSTATUS_WIN_STATUS_MESSAGE_NOT_FOUND = 0xC0000109, - MD_NTSTATUS_WIN_STATUS_PROCESS_IS_TERMINATING = 0xC000010A, - MD_NTSTATUS_WIN_STATUS_INVALID_LOGON_TYPE = 0xC000010B, - MD_NTSTATUS_WIN_STATUS_NO_GUID_TRANSLATION = 0xC000010C, - MD_NTSTATUS_WIN_STATUS_CANNOT_IMPERSONATE = 0xC000010D, - MD_NTSTATUS_WIN_STATUS_IMAGE_ALREADY_LOADED = 0xC000010E, - MD_NTSTATUS_WIN_STATUS_ABIOS_NOT_PRESENT = 0xC000010F, - MD_NTSTATUS_WIN_STATUS_ABIOS_LID_NOT_EXIST = 0xC0000110, - MD_NTSTATUS_WIN_STATUS_ABIOS_LID_ALREADY_OWNED = 0xC0000111, - MD_NTSTATUS_WIN_STATUS_ABIOS_NOT_LID_OWNER = 0xC0000112, - MD_NTSTATUS_WIN_STATUS_ABIOS_INVALID_COMMAND = 0xC0000113, - MD_NTSTATUS_WIN_STATUS_ABIOS_INVALID_LID = 0xC0000114, - MD_NTSTATUS_WIN_STATUS_ABIOS_SELECTOR_NOT_AVAILABLE = 0xC0000115, - MD_NTSTATUS_WIN_STATUS_ABIOS_INVALID_SELECTOR = 0xC0000116, - MD_NTSTATUS_WIN_STATUS_NO_LDT = 0xC0000117, - MD_NTSTATUS_WIN_STATUS_INVALID_LDT_SIZE = 0xC0000118, - MD_NTSTATUS_WIN_STATUS_INVALID_LDT_OFFSET = 0xC0000119, - MD_NTSTATUS_WIN_STATUS_INVALID_LDT_DESCRIPTOR = 0xC000011A, - MD_NTSTATUS_WIN_STATUS_INVALID_IMAGE_NE_FORMAT = 0xC000011B, - MD_NTSTATUS_WIN_STATUS_RXACT_INVALID_STATE = 0xC000011C, - MD_NTSTATUS_WIN_STATUS_RXACT_COMMIT_FAILURE = 0xC000011D, - MD_NTSTATUS_WIN_STATUS_MAPPED_FILE_SIZE_ZERO = 0xC000011E, - MD_NTSTATUS_WIN_STATUS_TOO_MANY_OPENED_FILES = 0xC000011F, - MD_NTSTATUS_WIN_STATUS_CANCELLED = 0xC0000120, - MD_NTSTATUS_WIN_STATUS_CANNOT_DELETE = 0xC0000121, - MD_NTSTATUS_WIN_STATUS_INVALID_COMPUTER_NAME = 0xC0000122, - MD_NTSTATUS_WIN_STATUS_FILE_DELETED = 0xC0000123, - MD_NTSTATUS_WIN_STATUS_SPECIAL_ACCOUNT = 0xC0000124, - MD_NTSTATUS_WIN_STATUS_SPECIAL_GROUP = 0xC0000125, - MD_NTSTATUS_WIN_STATUS_SPECIAL_USER = 0xC0000126, - MD_NTSTATUS_WIN_STATUS_MEMBERS_PRIMARY_GROUP = 0xC0000127, - MD_NTSTATUS_WIN_STATUS_FILE_CLOSED = 0xC0000128, - MD_NTSTATUS_WIN_STATUS_TOO_MANY_THREADS = 0xC0000129, - MD_NTSTATUS_WIN_STATUS_THREAD_NOT_IN_PROCESS = 0xC000012A, - MD_NTSTATUS_WIN_STATUS_TOKEN_ALREADY_IN_USE = 0xC000012B, - MD_NTSTATUS_WIN_STATUS_PAGEFILE_QUOTA_EXCEEDED = 0xC000012C, - MD_NTSTATUS_WIN_STATUS_COMMITMENT_LIMIT = 0xC000012D, - MD_NTSTATUS_WIN_STATUS_INVALID_IMAGE_LE_FORMAT = 0xC000012E, - MD_NTSTATUS_WIN_STATUS_INVALID_IMAGE_NOT_MZ = 0xC000012F, - MD_NTSTATUS_WIN_STATUS_INVALID_IMAGE_PROTECT = 0xC0000130, - MD_NTSTATUS_WIN_STATUS_INVALID_IMAGE_WIN_16 = 0xC0000131, - MD_NTSTATUS_WIN_STATUS_LOGON_SERVER_CONFLICT = 0xC0000132, - MD_NTSTATUS_WIN_STATUS_TIME_DIFFERENCE_AT_DC = 0xC0000133, - MD_NTSTATUS_WIN_STATUS_SYNCHRONIZATION_REQUIRED = 0xC0000134, - MD_NTSTATUS_WIN_STATUS_DLL_NOT_FOUND = 0xC0000135, - MD_NTSTATUS_WIN_STATUS_OPEN_FAILED = 0xC0000136, - MD_NTSTATUS_WIN_STATUS_IO_PRIVILEGE_FAILED = 0xC0000137, - MD_NTSTATUS_WIN_STATUS_ORDINAL_NOT_FOUND = 0xC0000138, - MD_NTSTATUS_WIN_STATUS_ENTRYPOINT_NOT_FOUND = 0xC0000139, - MD_NTSTATUS_WIN_STATUS_CONTROL_C_EXIT = 0xC000013A, - MD_NTSTATUS_WIN_STATUS_LOCAL_DISCONNECT = 0xC000013B, - MD_NTSTATUS_WIN_STATUS_REMOTE_DISCONNECT = 0xC000013C, - MD_NTSTATUS_WIN_STATUS_REMOTE_RESOURCES = 0xC000013D, - MD_NTSTATUS_WIN_STATUS_LINK_FAILED = 0xC000013E, - MD_NTSTATUS_WIN_STATUS_LINK_TIMEOUT = 0xC000013F, - MD_NTSTATUS_WIN_STATUS_INVALID_CONNECTION = 0xC0000140, - MD_NTSTATUS_WIN_STATUS_INVALID_ADDRESS = 0xC0000141, - MD_NTSTATUS_WIN_STATUS_DLL_INIT_FAILED = 0xC0000142, - MD_NTSTATUS_WIN_STATUS_MISSING_SYSTEMFILE = 0xC0000143, - MD_NTSTATUS_WIN_STATUS_UNHANDLED_EXCEPTION = 0xC0000144, - MD_NTSTATUS_WIN_STATUS_APP_INIT_FAILURE = 0xC0000145, - MD_NTSTATUS_WIN_STATUS_PAGEFILE_CREATE_FAILED = 0xC0000146, - MD_NTSTATUS_WIN_STATUS_NO_PAGEFILE = 0xC0000147, - MD_NTSTATUS_WIN_STATUS_INVALID_LEVEL = 0xC0000148, - MD_NTSTATUS_WIN_STATUS_WRONG_PASSWORD_CORE = 0xC0000149, - MD_NTSTATUS_WIN_STATUS_ILLEGAL_FLOAT_CONTEXT = 0xC000014A, - MD_NTSTATUS_WIN_STATUS_PIPE_BROKEN = 0xC000014B, - MD_NTSTATUS_WIN_STATUS_REGISTRY_CORRUPT = 0xC000014C, - MD_NTSTATUS_WIN_STATUS_REGISTRY_IO_FAILED = 0xC000014D, - MD_NTSTATUS_WIN_STATUS_NO_EVENT_PAIR = 0xC000014E, - MD_NTSTATUS_WIN_STATUS_UNRECOGNIZED_VOLUME = 0xC000014F, - MD_NTSTATUS_WIN_STATUS_SERIAL_NO_DEVICE_INITED = 0xC0000150, - MD_NTSTATUS_WIN_STATUS_NO_SUCH_ALIAS = 0xC0000151, - MD_NTSTATUS_WIN_STATUS_MEMBER_NOT_IN_ALIAS = 0xC0000152, - MD_NTSTATUS_WIN_STATUS_MEMBER_IN_ALIAS = 0xC0000153, - MD_NTSTATUS_WIN_STATUS_ALIAS_EXISTS = 0xC0000154, - MD_NTSTATUS_WIN_STATUS_LOGON_NOT_GRANTED = 0xC0000155, - MD_NTSTATUS_WIN_STATUS_TOO_MANY_SECRETS = 0xC0000156, - MD_NTSTATUS_WIN_STATUS_SECRET_TOO_LONG = 0xC0000157, - MD_NTSTATUS_WIN_STATUS_INTERNAL_DB_ERROR = 0xC0000158, - MD_NTSTATUS_WIN_STATUS_FULLSCREEN_MODE = 0xC0000159, - MD_NTSTATUS_WIN_STATUS_TOO_MANY_CONTEXT_IDS = 0xC000015A, - MD_NTSTATUS_WIN_STATUS_LOGON_TYPE_NOT_GRANTED = 0xC000015B, - MD_NTSTATUS_WIN_STATUS_NOT_REGISTRY_FILE = 0xC000015C, - MD_NTSTATUS_WIN_STATUS_NT_CROSS_ENCRYPTION_REQUIRED = 0xC000015D, - MD_NTSTATUS_WIN_STATUS_DOMAIN_CTRLR_CONFIG_ERROR = 0xC000015E, - MD_NTSTATUS_WIN_STATUS_FT_MISSING_MEMBER = 0xC000015F, - MD_NTSTATUS_WIN_STATUS_ILL_FORMED_SERVICE_ENTRY = 0xC0000160, - MD_NTSTATUS_WIN_STATUS_ILLEGAL_CHARACTER = 0xC0000161, - MD_NTSTATUS_WIN_STATUS_UNMAPPABLE_CHARACTER = 0xC0000162, - MD_NTSTATUS_WIN_STATUS_UNDEFINED_CHARACTER = 0xC0000163, - MD_NTSTATUS_WIN_STATUS_FLOPPY_VOLUME = 0xC0000164, - MD_NTSTATUS_WIN_STATUS_FLOPPY_ID_MARK_NOT_FOUND = 0xC0000165, - MD_NTSTATUS_WIN_STATUS_FLOPPY_WRONG_CYLINDER = 0xC0000166, - MD_NTSTATUS_WIN_STATUS_FLOPPY_UNKNOWN_ERROR = 0xC0000167, - MD_NTSTATUS_WIN_STATUS_FLOPPY_BAD_REGISTERS = 0xC0000168, - MD_NTSTATUS_WIN_STATUS_DISK_RECALIBRATE_FAILED = 0xC0000169, - MD_NTSTATUS_WIN_STATUS_DISK_OPERATION_FAILED = 0xC000016A, - MD_NTSTATUS_WIN_STATUS_DISK_RESET_FAILED = 0xC000016B, - MD_NTSTATUS_WIN_STATUS_SHARED_IRQ_BUSY = 0xC000016C, - MD_NTSTATUS_WIN_STATUS_FT_ORPHANING = 0xC000016D, - MD_NTSTATUS_WIN_STATUS_BIOS_FAILED_TO_CONNECT_INTERRUPT = 0xC000016E, - MD_NTSTATUS_WIN_STATUS_PARTITION_FAILURE = 0xC0000172, - MD_NTSTATUS_WIN_STATUS_INVALID_BLOCK_LENGTH = 0xC0000173, - MD_NTSTATUS_WIN_STATUS_DEVICE_NOT_PARTITIONED = 0xC0000174, - MD_NTSTATUS_WIN_STATUS_UNABLE_TO_LOCK_MEDIA = 0xC0000175, - MD_NTSTATUS_WIN_STATUS_UNABLE_TO_UNLOAD_MEDIA = 0xC0000176, - MD_NTSTATUS_WIN_STATUS_EOM_OVERFLOW = 0xC0000177, - MD_NTSTATUS_WIN_STATUS_NO_MEDIA = 0xC0000178, - MD_NTSTATUS_WIN_STATUS_NO_SUCH_MEMBER = 0xC000017A, - MD_NTSTATUS_WIN_STATUS_INVALID_MEMBER = 0xC000017B, - MD_NTSTATUS_WIN_STATUS_KEY_DELETED = 0xC000017C, - MD_NTSTATUS_WIN_STATUS_NO_LOG_SPACE = 0xC000017D, - MD_NTSTATUS_WIN_STATUS_TOO_MANY_SIDS = 0xC000017E, - MD_NTSTATUS_WIN_STATUS_LM_CROSS_ENCRYPTION_REQUIRED = 0xC000017F, - MD_NTSTATUS_WIN_STATUS_KEY_HAS_CHILDREN = 0xC0000180, - MD_NTSTATUS_WIN_STATUS_CHILD_MUST_BE_VOLATILE = 0xC0000181, - MD_NTSTATUS_WIN_STATUS_DEVICE_CONFIGURATION_ERROR = 0xC0000182, - MD_NTSTATUS_WIN_STATUS_DRIVER_INTERNAL_ERROR = 0xC0000183, - MD_NTSTATUS_WIN_STATUS_INVALID_DEVICE_STATE = 0xC0000184, - MD_NTSTATUS_WIN_STATUS_IO_DEVICE_ERROR = 0xC0000185, - MD_NTSTATUS_WIN_STATUS_DEVICE_PROTOCOL_ERROR = 0xC0000186, - MD_NTSTATUS_WIN_STATUS_BACKUP_CONTROLLER = 0xC0000187, - MD_NTSTATUS_WIN_STATUS_LOG_FILE_FULL = 0xC0000188, - MD_NTSTATUS_WIN_STATUS_TOO_LATE = 0xC0000189, - MD_NTSTATUS_WIN_STATUS_NO_TRUST_LSA_SECRET = 0xC000018A, - MD_NTSTATUS_WIN_STATUS_NO_TRUST_SAM_ACCOUNT = 0xC000018B, - MD_NTSTATUS_WIN_STATUS_TRUSTED_DOMAIN_FAILURE = 0xC000018C, - MD_NTSTATUS_WIN_STATUS_TRUSTED_RELATIONSHIP_FAILURE = 0xC000018D, - MD_NTSTATUS_WIN_STATUS_EVENTLOG_FILE_CORRUPT = 0xC000018E, - MD_NTSTATUS_WIN_STATUS_EVENTLOG_CANT_START = 0xC000018F, - MD_NTSTATUS_WIN_STATUS_TRUST_FAILURE = 0xC0000190, - MD_NTSTATUS_WIN_STATUS_MUTANT_LIMIT_EXCEEDED = 0xC0000191, - MD_NTSTATUS_WIN_STATUS_NETLOGON_NOT_STARTED = 0xC0000192, - MD_NTSTATUS_WIN_STATUS_ACCOUNT_EXPIRED = 0xC0000193, - MD_NTSTATUS_WIN_STATUS_POSSIBLE_DEADLOCK = 0xC0000194, - MD_NTSTATUS_WIN_STATUS_NETWORK_CREDENTIAL_CONFLICT = 0xC0000195, - MD_NTSTATUS_WIN_STATUS_REMOTE_SESSION_LIMIT = 0xC0000196, - MD_NTSTATUS_WIN_STATUS_EVENTLOG_FILE_CHANGED = 0xC0000197, - MD_NTSTATUS_WIN_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT = 0xC0000198, - MD_NTSTATUS_WIN_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT = 0xC0000199, - MD_NTSTATUS_WIN_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT = 0xC000019A, - MD_NTSTATUS_WIN_STATUS_DOMAIN_TRUST_INCONSISTENT = 0xC000019B, - MD_NTSTATUS_WIN_STATUS_FS_DRIVER_REQUIRED = 0xC000019C, - MD_NTSTATUS_WIN_STATUS_IMAGE_ALREADY_LOADED_AS_DLL = 0xC000019D, - MD_NTSTATUS_WIN_STATUS_INCOMPATIBLE_WITH_GLOBAL_SHORT_NAME_REGISTRY_SETTING = 0xC000019E, - MD_NTSTATUS_WIN_STATUS_SHORT_NAMES_NOT_ENABLED_ON_VOLUME = 0xC000019F, - MD_NTSTATUS_WIN_STATUS_SECURITY_STREAM_IS_INCONSISTENT = 0xC00001A0, - MD_NTSTATUS_WIN_STATUS_INVALID_LOCK_RANGE = 0xC00001A1, - MD_NTSTATUS_WIN_STATUS_INVALID_ACE_CONDITION = 0xC00001A2, - MD_NTSTATUS_WIN_STATUS_IMAGE_SUBSYSTEM_NOT_PRESENT = 0xC00001A3, - MD_NTSTATUS_WIN_STATUS_NOTIFICATION_GUID_ALREADY_DEFINED = 0xC00001A4, - MD_NTSTATUS_WIN_STATUS_INVALID_EXCEPTION_HANDLER = 0xC00001A5, - MD_NTSTATUS_WIN_STATUS_DUPLICATE_PRIVILEGES = 0xC00001A6, - MD_NTSTATUS_WIN_STATUS_NOT_ALLOWED_ON_SYSTEM_FILE = 0xC00001A7, - MD_NTSTATUS_WIN_STATUS_REPAIR_NEEDED = 0xC00001A8, - MD_NTSTATUS_WIN_STATUS_QUOTA_NOT_ENABLED = 0xC00001A9, - MD_NTSTATUS_WIN_STATUS_NO_APPLICATION_PACKAGE = 0xC00001AA, - MD_NTSTATUS_WIN_STATUS_NETWORK_OPEN_RESTRICTION = 0xC0000201, - MD_NTSTATUS_WIN_STATUS_NO_USER_SESSION_KEY = 0xC0000202, - MD_NTSTATUS_WIN_STATUS_USER_SESSION_DELETED = 0xC0000203, - MD_NTSTATUS_WIN_STATUS_RESOURCE_LANG_NOT_FOUND = 0xC0000204, - MD_NTSTATUS_WIN_STATUS_INSUFF_SERVER_RESOURCES = 0xC0000205, - MD_NTSTATUS_WIN_STATUS_INVALID_BUFFER_SIZE = 0xC0000206, - MD_NTSTATUS_WIN_STATUS_INVALID_ADDRESS_COMPONENT = 0xC0000207, - MD_NTSTATUS_WIN_STATUS_INVALID_ADDRESS_WILDCARD = 0xC0000208, - MD_NTSTATUS_WIN_STATUS_TOO_MANY_ADDRESSES = 0xC0000209, - MD_NTSTATUS_WIN_STATUS_ADDRESS_ALREADY_EXISTS = 0xC000020A, - MD_NTSTATUS_WIN_STATUS_ADDRESS_CLOSED = 0xC000020B, - MD_NTSTATUS_WIN_STATUS_CONNECTION_DISCONNECTED = 0xC000020C, - MD_NTSTATUS_WIN_STATUS_CONNECTION_RESET = 0xC000020D, - MD_NTSTATUS_WIN_STATUS_TOO_MANY_NODES = 0xC000020E, - MD_NTSTATUS_WIN_STATUS_TRANSACTION_ABORTED = 0xC000020F, - MD_NTSTATUS_WIN_STATUS_TRANSACTION_TIMED_OUT = 0xC0000210, - MD_NTSTATUS_WIN_STATUS_TRANSACTION_NO_RELEASE = 0xC0000211, - MD_NTSTATUS_WIN_STATUS_TRANSACTION_NO_MATCH = 0xC0000212, - MD_NTSTATUS_WIN_STATUS_TRANSACTION_RESPONDED = 0xC0000213, - MD_NTSTATUS_WIN_STATUS_TRANSACTION_INVALID_ID = 0xC0000214, - MD_NTSTATUS_WIN_STATUS_TRANSACTION_INVALID_TYPE = 0xC0000215, - MD_NTSTATUS_WIN_STATUS_NOT_SERVER_SESSION = 0xC0000216, - MD_NTSTATUS_WIN_STATUS_NOT_CLIENT_SESSION = 0xC0000217, - MD_NTSTATUS_WIN_STATUS_CANNOT_LOAD_REGISTRY_FILE = 0xC0000218, - MD_NTSTATUS_WIN_STATUS_DEBUG_ATTACH_FAILED = 0xC0000219, - MD_NTSTATUS_WIN_STATUS_SYSTEM_PROCESS_TERMINATED = 0xC000021A, - MD_NTSTATUS_WIN_STATUS_DATA_NOT_ACCEPTED = 0xC000021B, - MD_NTSTATUS_WIN_STATUS_NO_BROWSER_SERVERS_FOUND = 0xC000021C, - MD_NTSTATUS_WIN_STATUS_VDM_HARD_ERROR = 0xC000021D, - MD_NTSTATUS_WIN_STATUS_DRIVER_CANCEL_TIMEOUT = 0xC000021E, - MD_NTSTATUS_WIN_STATUS_REPLY_MESSAGE_MISMATCH = 0xC000021F, - MD_NTSTATUS_WIN_STATUS_MAPPED_ALIGNMENT = 0xC0000220, - MD_NTSTATUS_WIN_STATUS_IMAGE_CHECKSUM_MISMATCH = 0xC0000221, - MD_NTSTATUS_WIN_STATUS_LOST_WRITEBEHIND_DATA = 0xC0000222, - MD_NTSTATUS_WIN_STATUS_CLIENT_SERVER_PARAMETERS_INVALID = 0xC0000223, - MD_NTSTATUS_WIN_STATUS_PASSWORD_MUST_CHANGE = 0xC0000224, - MD_NTSTATUS_WIN_STATUS_NOT_FOUND = 0xC0000225, - MD_NTSTATUS_WIN_STATUS_NOT_TINY_STREAM = 0xC0000226, - MD_NTSTATUS_WIN_STATUS_RECOVERY_FAILURE = 0xC0000227, - MD_NTSTATUS_WIN_STATUS_STACK_OVERFLOW_READ = 0xC0000228, - MD_NTSTATUS_WIN_STATUS_FAIL_CHECK = 0xC0000229, - MD_NTSTATUS_WIN_STATUS_DUPLICATE_OBJECTID = 0xC000022A, - MD_NTSTATUS_WIN_STATUS_OBJECTID_EXISTS = 0xC000022B, - MD_NTSTATUS_WIN_STATUS_CONVERT_TO_LARGE = 0xC000022C, - MD_NTSTATUS_WIN_STATUS_RETRY = 0xC000022D, - MD_NTSTATUS_WIN_STATUS_FOUND_OUT_OF_SCOPE = 0xC000022E, - MD_NTSTATUS_WIN_STATUS_ALLOCATE_BUCKET = 0xC000022F, - MD_NTSTATUS_WIN_STATUS_PROPSET_NOT_FOUND = 0xC0000230, - MD_NTSTATUS_WIN_STATUS_MARSHALL_OVERFLOW = 0xC0000231, - MD_NTSTATUS_WIN_STATUS_INVALID_VARIANT = 0xC0000232, - MD_NTSTATUS_WIN_STATUS_DOMAIN_CONTROLLER_NOT_FOUND = 0xC0000233, - MD_NTSTATUS_WIN_STATUS_ACCOUNT_LOCKED_OUT = 0xC0000234, - MD_NTSTATUS_WIN_STATUS_HANDLE_NOT_CLOSABLE = 0xC0000235, - MD_NTSTATUS_WIN_STATUS_CONNECTION_REFUSED = 0xC0000236, - MD_NTSTATUS_WIN_STATUS_GRACEFUL_DISCONNECT = 0xC0000237, - MD_NTSTATUS_WIN_STATUS_ADDRESS_ALREADY_ASSOCIATED = 0xC0000238, - MD_NTSTATUS_WIN_STATUS_ADDRESS_NOT_ASSOCIATED = 0xC0000239, - MD_NTSTATUS_WIN_STATUS_CONNECTION_INVALID = 0xC000023A, - MD_NTSTATUS_WIN_STATUS_CONNECTION_ACTIVE = 0xC000023B, - MD_NTSTATUS_WIN_STATUS_NETWORK_UNREACHABLE = 0xC000023C, - MD_NTSTATUS_WIN_STATUS_HOST_UNREACHABLE = 0xC000023D, - MD_NTSTATUS_WIN_STATUS_PROTOCOL_UNREACHABLE = 0xC000023E, - MD_NTSTATUS_WIN_STATUS_PORT_UNREACHABLE = 0xC000023F, - MD_NTSTATUS_WIN_STATUS_REQUEST_ABORTED = 0xC0000240, - MD_NTSTATUS_WIN_STATUS_CONNECTION_ABORTED = 0xC0000241, - MD_NTSTATUS_WIN_STATUS_BAD_COMPRESSION_BUFFER = 0xC0000242, - MD_NTSTATUS_WIN_STATUS_USER_MAPPED_FILE = 0xC0000243, - MD_NTSTATUS_WIN_STATUS_AUDIT_FAILED = 0xC0000244, - MD_NTSTATUS_WIN_STATUS_TIMER_RESOLUTION_NOT_SET = 0xC0000245, - MD_NTSTATUS_WIN_STATUS_CONNECTION_COUNT_LIMIT = 0xC0000246, - MD_NTSTATUS_WIN_STATUS_LOGIN_TIME_RESTRICTION = 0xC0000247, - MD_NTSTATUS_WIN_STATUS_LOGIN_WKSTA_RESTRICTION = 0xC0000248, - MD_NTSTATUS_WIN_STATUS_IMAGE_MP_UP_MISMATCH = 0xC0000249, - MD_NTSTATUS_WIN_STATUS_INSUFFICIENT_LOGON_INFO = 0xC0000250, - MD_NTSTATUS_WIN_STATUS_BAD_DLL_ENTRYPOINT = 0xC0000251, - MD_NTSTATUS_WIN_STATUS_BAD_SERVICE_ENTRYPOINT = 0xC0000252, - MD_NTSTATUS_WIN_STATUS_LPC_REPLY_LOST = 0xC0000253, - MD_NTSTATUS_WIN_STATUS_IP_ADDRESS_CONFLICT1 = 0xC0000254, - MD_NTSTATUS_WIN_STATUS_IP_ADDRESS_CONFLICT2 = 0xC0000255, - MD_NTSTATUS_WIN_STATUS_REGISTRY_QUOTA_LIMIT = 0xC0000256, - MD_NTSTATUS_WIN_STATUS_PATH_NOT_COVERED = 0xC0000257, - MD_NTSTATUS_WIN_STATUS_NO_CALLBACK_ACTIVE = 0xC0000258, - MD_NTSTATUS_WIN_STATUS_LICENSE_QUOTA_EXCEEDED = 0xC0000259, - MD_NTSTATUS_WIN_STATUS_PWD_TOO_SHORT = 0xC000025A, - MD_NTSTATUS_WIN_STATUS_PWD_TOO_RECENT = 0xC000025B, - MD_NTSTATUS_WIN_STATUS_PWD_HISTORY_CONFLICT = 0xC000025C, - MD_NTSTATUS_WIN_STATUS_PLUGPLAY_NO_DEVICE = 0xC000025E, - MD_NTSTATUS_WIN_STATUS_UNSUPPORTED_COMPRESSION = 0xC000025F, - MD_NTSTATUS_WIN_STATUS_INVALID_HW_PROFILE = 0xC0000260, - MD_NTSTATUS_WIN_STATUS_INVALID_PLUGPLAY_DEVICE_PATH = 0xC0000261, - MD_NTSTATUS_WIN_STATUS_DRIVER_ORDINAL_NOT_FOUND = 0xC0000262, - MD_NTSTATUS_WIN_STATUS_DRIVER_ENTRYPOINT_NOT_FOUND = 0xC0000263, - MD_NTSTATUS_WIN_STATUS_RESOURCE_NOT_OWNED = 0xC0000264, - MD_NTSTATUS_WIN_STATUS_TOO_MANY_LINKS = 0xC0000265, - MD_NTSTATUS_WIN_STATUS_QUOTA_LIST_INCONSISTENT = 0xC0000266, - MD_NTSTATUS_WIN_STATUS_FILE_IS_OFFLINE = 0xC0000267, - MD_NTSTATUS_WIN_STATUS_EVALUATION_EXPIRATION = 0xC0000268, - MD_NTSTATUS_WIN_STATUS_ILLEGAL_DLL_RELOCATION = 0xC0000269, - MD_NTSTATUS_WIN_STATUS_LICENSE_VIOLATION = 0xC000026A, - MD_NTSTATUS_WIN_STATUS_DLL_INIT_FAILED_LOGOFF = 0xC000026B, - MD_NTSTATUS_WIN_STATUS_DRIVER_UNABLE_TO_LOAD = 0xC000026C, - MD_NTSTATUS_WIN_STATUS_DFS_UNAVAILABLE = 0xC000026D, - MD_NTSTATUS_WIN_STATUS_VOLUME_DISMOUNTED = 0xC000026E, - MD_NTSTATUS_WIN_STATUS_WX86_INTERNAL_ERROR = 0xC000026F, - MD_NTSTATUS_WIN_STATUS_WX86_FLOAT_STACK_CHECK = 0xC0000270, - MD_NTSTATUS_WIN_STATUS_VALIDATE_CONTINUE = 0xC0000271, - MD_NTSTATUS_WIN_STATUS_NO_MATCH = 0xC0000272, - MD_NTSTATUS_WIN_STATUS_NO_MORE_MATCHES = 0xC0000273, - MD_NTSTATUS_WIN_STATUS_NOT_A_REPARSE_POINT = 0xC0000275, - MD_NTSTATUS_WIN_STATUS_IO_REPARSE_TAG_INVALID = 0xC0000276, - MD_NTSTATUS_WIN_STATUS_IO_REPARSE_TAG_MISMATCH = 0xC0000277, - MD_NTSTATUS_WIN_STATUS_IO_REPARSE_DATA_INVALID = 0xC0000278, - MD_NTSTATUS_WIN_STATUS_IO_REPARSE_TAG_NOT_HANDLED = 0xC0000279, - MD_NTSTATUS_WIN_STATUS_PWD_TOO_LONG = 0xC000027A, - MD_NTSTATUS_WIN_STATUS_STOWED_EXCEPTION = 0xC000027B, - MD_NTSTATUS_WIN_STATUS_REPARSE_POINT_NOT_RESOLVED = 0xC0000280, - MD_NTSTATUS_WIN_STATUS_DIRECTORY_IS_A_REPARSE_POINT = 0xC0000281, - MD_NTSTATUS_WIN_STATUS_RANGE_LIST_CONFLICT = 0xC0000282, - MD_NTSTATUS_WIN_STATUS_SOURCE_ELEMENT_EMPTY = 0xC0000283, - MD_NTSTATUS_WIN_STATUS_DESTINATION_ELEMENT_FULL = 0xC0000284, - MD_NTSTATUS_WIN_STATUS_ILLEGAL_ELEMENT_ADDRESS = 0xC0000285, - MD_NTSTATUS_WIN_STATUS_MAGAZINE_NOT_PRESENT = 0xC0000286, - MD_NTSTATUS_WIN_STATUS_REINITIALIZATION_NEEDED = 0xC0000287, - MD_NTSTATUS_WIN_STATUS_ENCRYPTION_FAILED = 0xC000028A, - MD_NTSTATUS_WIN_STATUS_DECRYPTION_FAILED = 0xC000028B, - MD_NTSTATUS_WIN_STATUS_RANGE_NOT_FOUND = 0xC000028C, - MD_NTSTATUS_WIN_STATUS_NO_RECOVERY_POLICY = 0xC000028D, - MD_NTSTATUS_WIN_STATUS_NO_EFS = 0xC000028E, - MD_NTSTATUS_WIN_STATUS_WRONG_EFS = 0xC000028F, - MD_NTSTATUS_WIN_STATUS_NO_USER_KEYS = 0xC0000290, - MD_NTSTATUS_WIN_STATUS_FILE_NOT_ENCRYPTED = 0xC0000291, - MD_NTSTATUS_WIN_STATUS_NOT_EXPORT_FORMAT = 0xC0000292, - MD_NTSTATUS_WIN_STATUS_FILE_ENCRYPTED = 0xC0000293, - MD_NTSTATUS_WIN_STATUS_WMI_GUID_NOT_FOUND = 0xC0000295, - MD_NTSTATUS_WIN_STATUS_WMI_INSTANCE_NOT_FOUND = 0xC0000296, - MD_NTSTATUS_WIN_STATUS_WMI_ITEMID_NOT_FOUND = 0xC0000297, - MD_NTSTATUS_WIN_STATUS_WMI_TRY_AGAIN = 0xC0000298, - MD_NTSTATUS_WIN_STATUS_SHARED_POLICY = 0xC0000299, - MD_NTSTATUS_WIN_STATUS_POLICY_OBJECT_NOT_FOUND = 0xC000029A, - MD_NTSTATUS_WIN_STATUS_POLICY_ONLY_IN_DS = 0xC000029B, - MD_NTSTATUS_WIN_STATUS_VOLUME_NOT_UPGRADED = 0xC000029C, - MD_NTSTATUS_WIN_STATUS_REMOTE_STORAGE_NOT_ACTIVE = 0xC000029D, - MD_NTSTATUS_WIN_STATUS_REMOTE_STORAGE_MEDIA_ERROR = 0xC000029E, - MD_NTSTATUS_WIN_STATUS_NO_TRACKING_SERVICE = 0xC000029F, - MD_NTSTATUS_WIN_STATUS_SERVER_SID_MISMATCH = 0xC00002A0, - MD_NTSTATUS_WIN_STATUS_DS_NO_ATTRIBUTE_OR_VALUE = 0xC00002A1, - MD_NTSTATUS_WIN_STATUS_DS_INVALID_ATTRIBUTE_SYNTAX = 0xC00002A2, - MD_NTSTATUS_WIN_STATUS_DS_ATTRIBUTE_TYPE_UNDEFINED = 0xC00002A3, - MD_NTSTATUS_WIN_STATUS_DS_ATTRIBUTE_OR_VALUE_EXISTS = 0xC00002A4, - MD_NTSTATUS_WIN_STATUS_DS_BUSY = 0xC00002A5, - MD_NTSTATUS_WIN_STATUS_DS_UNAVAILABLE = 0xC00002A6, - MD_NTSTATUS_WIN_STATUS_DS_NO_RIDS_ALLOCATED = 0xC00002A7, - MD_NTSTATUS_WIN_STATUS_DS_NO_MORE_RIDS = 0xC00002A8, - MD_NTSTATUS_WIN_STATUS_DS_INCORRECT_ROLE_OWNER = 0xC00002A9, - MD_NTSTATUS_WIN_STATUS_DS_RIDMGR_INIT_ERROR = 0xC00002AA, - MD_NTSTATUS_WIN_STATUS_DS_OBJ_CLASS_VIOLATION = 0xC00002AB, - MD_NTSTATUS_WIN_STATUS_DS_CANT_ON_NON_LEAF = 0xC00002AC, - MD_NTSTATUS_WIN_STATUS_DS_CANT_ON_RDN = 0xC00002AD, - MD_NTSTATUS_WIN_STATUS_DS_CANT_MOD_OBJ_CLASS = 0xC00002AE, - MD_NTSTATUS_WIN_STATUS_DS_CROSS_DOM_MOVE_FAILED = 0xC00002AF, - MD_NTSTATUS_WIN_STATUS_DS_GC_NOT_AVAILABLE = 0xC00002B0, - MD_NTSTATUS_WIN_STATUS_DIRECTORY_SERVICE_REQUIRED = 0xC00002B1, - MD_NTSTATUS_WIN_STATUS_REPARSE_ATTRIBUTE_CONFLICT = 0xC00002B2, - MD_NTSTATUS_WIN_STATUS_CANT_ENABLE_DENY_ONLY = 0xC00002B3, - MD_NTSTATUS_WIN_STATUS_FLOAT_MULTIPLE_FAULTS = 0xC00002B4, - MD_NTSTATUS_WIN_STATUS_FLOAT_MULTIPLE_TRAPS = 0xC00002B5, - MD_NTSTATUS_WIN_STATUS_DEVICE_REMOVED = 0xC00002B6, - MD_NTSTATUS_WIN_STATUS_JOURNAL_DELETE_IN_PROGRESS = 0xC00002B7, - MD_NTSTATUS_WIN_STATUS_JOURNAL_NOT_ACTIVE = 0xC00002B8, - MD_NTSTATUS_WIN_STATUS_NOINTERFACE = 0xC00002B9, - MD_NTSTATUS_WIN_STATUS_DS_RIDMGR_DISABLED = 0xC00002BA, - MD_NTSTATUS_WIN_STATUS_DS_ADMIN_LIMIT_EXCEEDED = 0xC00002C1, - MD_NTSTATUS_WIN_STATUS_DRIVER_FAILED_SLEEP = 0xC00002C2, - MD_NTSTATUS_WIN_STATUS_MUTUAL_AUTHENTICATION_FAILED = 0xC00002C3, - MD_NTSTATUS_WIN_STATUS_CORRUPT_SYSTEM_FILE = 0xC00002C4, - MD_NTSTATUS_WIN_STATUS_DATATYPE_MISALIGNMENT_ERROR = 0xC00002C5, - MD_NTSTATUS_WIN_STATUS_WMI_READ_ONLY = 0xC00002C6, - MD_NTSTATUS_WIN_STATUS_WMI_SET_FAILURE = 0xC00002C7, - MD_NTSTATUS_WIN_STATUS_COMMITMENT_MINIMUM = 0xC00002C8, - MD_NTSTATUS_WIN_STATUS_REG_NAT_CONSUMPTION = 0xC00002C9, - MD_NTSTATUS_WIN_STATUS_TRANSPORT_FULL = 0xC00002CA, - MD_NTSTATUS_WIN_STATUS_DS_SAM_INIT_FAILURE = 0xC00002CB, - MD_NTSTATUS_WIN_STATUS_ONLY_IF_CONNECTED = 0xC00002CC, - MD_NTSTATUS_WIN_STATUS_DS_SENSITIVE_GROUP_VIOLATION = 0xC00002CD, - MD_NTSTATUS_WIN_STATUS_PNP_RESTART_ENUMERATION = 0xC00002CE, - MD_NTSTATUS_WIN_STATUS_JOURNAL_ENTRY_DELETED = 0xC00002CF, - MD_NTSTATUS_WIN_STATUS_DS_CANT_MOD_PRIMARYGROUPID = 0xC00002D0, - MD_NTSTATUS_WIN_STATUS_SYSTEM_IMAGE_BAD_SIGNATURE = 0xC00002D1, - MD_NTSTATUS_WIN_STATUS_PNP_REBOOT_REQUIRED = 0xC00002D2, - MD_NTSTATUS_WIN_STATUS_POWER_STATE_INVALID = 0xC00002D3, - MD_NTSTATUS_WIN_STATUS_DS_INVALID_GROUP_TYPE = 0xC00002D4, - MD_NTSTATUS_WIN_STATUS_DS_NO_NEST_GLOBALGROUP_IN_MIXEDDOMAIN = 0xC00002D5, - MD_NTSTATUS_WIN_STATUS_DS_NO_NEST_LOCALGROUP_IN_MIXEDDOMAIN = 0xC00002D6, - MD_NTSTATUS_WIN_STATUS_DS_GLOBAL_CANT_HAVE_LOCAL_MEMBER = 0xC00002D7, - MD_NTSTATUS_WIN_STATUS_DS_GLOBAL_CANT_HAVE_UNIVERSAL_MEMBER = 0xC00002D8, - MD_NTSTATUS_WIN_STATUS_DS_UNIVERSAL_CANT_HAVE_LOCAL_MEMBER = 0xC00002D9, - MD_NTSTATUS_WIN_STATUS_DS_GLOBAL_CANT_HAVE_CROSSDOMAIN_MEMBER = 0xC00002DA, - MD_NTSTATUS_WIN_STATUS_DS_LOCAL_CANT_HAVE_CROSSDOMAIN_LOCAL_MEMBER = 0xC00002DB, - MD_NTSTATUS_WIN_STATUS_DS_HAVE_PRIMARY_MEMBERS = 0xC00002DC, - MD_NTSTATUS_WIN_STATUS_WMI_NOT_SUPPORTED = 0xC00002DD, - MD_NTSTATUS_WIN_STATUS_INSUFFICIENT_POWER = 0xC00002DE, - MD_NTSTATUS_WIN_STATUS_SAM_NEED_BOOTKEY_PASSWORD = 0xC00002DF, - MD_NTSTATUS_WIN_STATUS_SAM_NEED_BOOTKEY_FLOPPY = 0xC00002E0, - MD_NTSTATUS_WIN_STATUS_DS_CANT_START = 0xC00002E1, - MD_NTSTATUS_WIN_STATUS_DS_INIT_FAILURE = 0xC00002E2, - MD_NTSTATUS_WIN_STATUS_SAM_INIT_FAILURE = 0xC00002E3, - MD_NTSTATUS_WIN_STATUS_DS_GC_REQUIRED = 0xC00002E4, - MD_NTSTATUS_WIN_STATUS_DS_LOCAL_MEMBER_OF_LOCAL_ONLY = 0xC00002E5, - MD_NTSTATUS_WIN_STATUS_DS_NO_FPO_IN_UNIVERSAL_GROUPS = 0xC00002E6, - MD_NTSTATUS_WIN_STATUS_DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED = 0xC00002E7, - MD_NTSTATUS_WIN_STATUS_MULTIPLE_FAULT_VIOLATION = 0xC00002E8, - MD_NTSTATUS_WIN_STATUS_CURRENT_DOMAIN_NOT_ALLOWED = 0xC00002E9, - MD_NTSTATUS_WIN_STATUS_CANNOT_MAKE = 0xC00002EA, - MD_NTSTATUS_WIN_STATUS_SYSTEM_SHUTDOWN = 0xC00002EB, - MD_NTSTATUS_WIN_STATUS_DS_INIT_FAILURE_CONSOLE = 0xC00002EC, - MD_NTSTATUS_WIN_STATUS_DS_SAM_INIT_FAILURE_CONSOLE = 0xC00002ED, - MD_NTSTATUS_WIN_STATUS_UNFINISHED_CONTEXT_DELETED = 0xC00002EE, - MD_NTSTATUS_WIN_STATUS_NO_TGT_REPLY = 0xC00002EF, - MD_NTSTATUS_WIN_STATUS_OBJECTID_NOT_FOUND = 0xC00002F0, - MD_NTSTATUS_WIN_STATUS_NO_IP_ADDRESSES = 0xC00002F1, - MD_NTSTATUS_WIN_STATUS_WRONG_CREDENTIAL_HANDLE = 0xC00002F2, - MD_NTSTATUS_WIN_STATUS_CRYPTO_SYSTEM_INVALID = 0xC00002F3, - MD_NTSTATUS_WIN_STATUS_MAX_REFERRALS_EXCEEDED = 0xC00002F4, - MD_NTSTATUS_WIN_STATUS_MUST_BE_KDC = 0xC00002F5, - MD_NTSTATUS_WIN_STATUS_STRONG_CRYPTO_NOT_SUPPORTED = 0xC00002F6, - MD_NTSTATUS_WIN_STATUS_TOO_MANY_PRINCIPALS = 0xC00002F7, - MD_NTSTATUS_WIN_STATUS_NO_PA_DATA = 0xC00002F8, - MD_NTSTATUS_WIN_STATUS_PKINIT_NAME_MISMATCH = 0xC00002F9, - MD_NTSTATUS_WIN_STATUS_SMARTCARD_LOGON_REQUIRED = 0xC00002FA, - MD_NTSTATUS_WIN_STATUS_KDC_INVALID_REQUEST = 0xC00002FB, - MD_NTSTATUS_WIN_STATUS_KDC_UNABLE_TO_REFER = 0xC00002FC, - MD_NTSTATUS_WIN_STATUS_KDC_UNKNOWN_ETYPE = 0xC00002FD, - MD_NTSTATUS_WIN_STATUS_SHUTDOWN_IN_PROGRESS = 0xC00002FE, - MD_NTSTATUS_WIN_STATUS_SERVER_SHUTDOWN_IN_PROGRESS = 0xC00002FF, - MD_NTSTATUS_WIN_STATUS_NOT_SUPPORTED_ON_SBS = 0xC0000300, - MD_NTSTATUS_WIN_STATUS_WMI_GUID_DISCONNECTED = 0xC0000301, - MD_NTSTATUS_WIN_STATUS_WMI_ALREADY_DISABLED = 0xC0000302, - MD_NTSTATUS_WIN_STATUS_WMI_ALREADY_ENABLED = 0xC0000303, - MD_NTSTATUS_WIN_STATUS_MFT_TOO_FRAGMENTED = 0xC0000304, - MD_NTSTATUS_WIN_STATUS_COPY_PROTECTION_FAILURE = 0xC0000305, - MD_NTSTATUS_WIN_STATUS_CSS_AUTHENTICATION_FAILURE = 0xC0000306, - MD_NTSTATUS_WIN_STATUS_CSS_KEY_NOT_PRESENT = 0xC0000307, - MD_NTSTATUS_WIN_STATUS_CSS_KEY_NOT_ESTABLISHED = 0xC0000308, - MD_NTSTATUS_WIN_STATUS_CSS_SCRAMBLED_SECTOR = 0xC0000309, - MD_NTSTATUS_WIN_STATUS_CSS_REGION_MISMATCH = 0xC000030A, - MD_NTSTATUS_WIN_STATUS_CSS_RESETS_EXHAUSTED = 0xC000030B, - MD_NTSTATUS_WIN_STATUS_PASSWORD_CHANGE_REQUIRED = 0xC000030C, - MD_NTSTATUS_WIN_STATUS_PKINIT_FAILURE = 0xC0000320, - MD_NTSTATUS_WIN_STATUS_SMARTCARD_SUBSYSTEM_FAILURE = 0xC0000321, - MD_NTSTATUS_WIN_STATUS_NO_KERB_KEY = 0xC0000322, - MD_NTSTATUS_WIN_STATUS_HOST_DOWN = 0xC0000350, - MD_NTSTATUS_WIN_STATUS_UNSUPPORTED_PREAUTH = 0xC0000351, - MD_NTSTATUS_WIN_STATUS_EFS_ALG_BLOB_TOO_BIG = 0xC0000352, - MD_NTSTATUS_WIN_STATUS_PORT_NOT_SET = 0xC0000353, - MD_NTSTATUS_WIN_STATUS_DEBUGGER_INACTIVE = 0xC0000354, - MD_NTSTATUS_WIN_STATUS_DS_VERSION_CHECK_FAILURE = 0xC0000355, - MD_NTSTATUS_WIN_STATUS_AUDITING_DISABLED = 0xC0000356, - MD_NTSTATUS_WIN_STATUS_PRENT4_MACHINE_ACCOUNT = 0xC0000357, - MD_NTSTATUS_WIN_STATUS_DS_AG_CANT_HAVE_UNIVERSAL_MEMBER = 0xC0000358, - MD_NTSTATUS_WIN_STATUS_INVALID_IMAGE_WIN_32 = 0xC0000359, - MD_NTSTATUS_WIN_STATUS_INVALID_IMAGE_WIN_64 = 0xC000035A, - MD_NTSTATUS_WIN_STATUS_BAD_BINDINGS = 0xC000035B, - MD_NTSTATUS_WIN_STATUS_NETWORK_SESSION_EXPIRED = 0xC000035C, - MD_NTSTATUS_WIN_STATUS_APPHELP_BLOCK = 0xC000035D, - MD_NTSTATUS_WIN_STATUS_ALL_SIDS_FILTERED = 0xC000035E, - MD_NTSTATUS_WIN_STATUS_NOT_SAFE_MODE_DRIVER = 0xC000035F, - MD_NTSTATUS_WIN_STATUS_ACCESS_DISABLED_BY_POLICY_DEFAULT = 0xC0000361, - MD_NTSTATUS_WIN_STATUS_ACCESS_DISABLED_BY_POLICY_PATH = 0xC0000362, - MD_NTSTATUS_WIN_STATUS_ACCESS_DISABLED_BY_POLICY_PUBLISHER = 0xC0000363, - MD_NTSTATUS_WIN_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER = 0xC0000364, - MD_NTSTATUS_WIN_STATUS_FAILED_DRIVER_ENTRY = 0xC0000365, - MD_NTSTATUS_WIN_STATUS_DEVICE_ENUMERATION_ERROR = 0xC0000366, - MD_NTSTATUS_WIN_STATUS_MOUNT_POINT_NOT_RESOLVED = 0xC0000368, - MD_NTSTATUS_WIN_STATUS_INVALID_DEVICE_OBJECT_PARAMETER = 0xC0000369, - MD_NTSTATUS_WIN_STATUS_MCA_OCCURED = 0xC000036A, - MD_NTSTATUS_WIN_STATUS_DRIVER_BLOCKED_CRITICAL = 0xC000036B, - MD_NTSTATUS_WIN_STATUS_DRIVER_BLOCKED = 0xC000036C, - MD_NTSTATUS_WIN_STATUS_DRIVER_DATABASE_ERROR = 0xC000036D, - MD_NTSTATUS_WIN_STATUS_SYSTEM_HIVE_TOO_LARGE = 0xC000036E, - MD_NTSTATUS_WIN_STATUS_INVALID_IMPORT_OF_NON_DLL = 0xC000036F, - MD_NTSTATUS_WIN_STATUS_NO_SECRETS = 0xC0000371, - MD_NTSTATUS_WIN_STATUS_ACCESS_DISABLED_NO_SAFER_UI_BY_POLICY = 0xC0000372, - MD_NTSTATUS_WIN_STATUS_FAILED_STACK_SWITCH = 0xC0000373, - MD_NTSTATUS_WIN_STATUS_HEAP_CORRUPTION = 0xC0000374, - MD_NTSTATUS_WIN_STATUS_SMARTCARD_WRONG_PIN = 0xC0000380, - MD_NTSTATUS_WIN_STATUS_SMARTCARD_CARD_BLOCKED = 0xC0000381, - MD_NTSTATUS_WIN_STATUS_SMARTCARD_CARD_NOT_AUTHENTICATED = 0xC0000382, - MD_NTSTATUS_WIN_STATUS_SMARTCARD_NO_CARD = 0xC0000383, - MD_NTSTATUS_WIN_STATUS_SMARTCARD_NO_KEY_CONTAINER = 0xC0000384, - MD_NTSTATUS_WIN_STATUS_SMARTCARD_NO_CERTIFICATE = 0xC0000385, - MD_NTSTATUS_WIN_STATUS_SMARTCARD_NO_KEYSET = 0xC0000386, - MD_NTSTATUS_WIN_STATUS_SMARTCARD_IO_ERROR = 0xC0000387, - MD_NTSTATUS_WIN_STATUS_DOWNGRADE_DETECTED = 0xC0000388, - MD_NTSTATUS_WIN_STATUS_SMARTCARD_CERT_REVOKED = 0xC0000389, - MD_NTSTATUS_WIN_STATUS_ISSUING_CA_UNTRUSTED = 0xC000038A, - MD_NTSTATUS_WIN_STATUS_REVOCATION_OFFLINE_C = 0xC000038B, - MD_NTSTATUS_WIN_STATUS_PKINIT_CLIENT_FAILURE = 0xC000038C, - MD_NTSTATUS_WIN_STATUS_SMARTCARD_CERT_EXPIRED = 0xC000038D, - MD_NTSTATUS_WIN_STATUS_DRIVER_FAILED_PRIOR_UNLOAD = 0xC000038E, - MD_NTSTATUS_WIN_STATUS_SMARTCARD_SILENT_CONTEXT = 0xC000038F, - MD_NTSTATUS_WIN_STATUS_PER_USER_TRUST_QUOTA_EXCEEDED = 0xC0000401, - MD_NTSTATUS_WIN_STATUS_ALL_USER_TRUST_QUOTA_EXCEEDED = 0xC0000402, - MD_NTSTATUS_WIN_STATUS_USER_DELETE_TRUST_QUOTA_EXCEEDED = 0xC0000403, - MD_NTSTATUS_WIN_STATUS_DS_NAME_NOT_UNIQUE = 0xC0000404, - MD_NTSTATUS_WIN_STATUS_DS_DUPLICATE_ID_FOUND = 0xC0000405, - MD_NTSTATUS_WIN_STATUS_DS_GROUP_CONVERSION_ERROR = 0xC0000406, - MD_NTSTATUS_WIN_STATUS_VOLSNAP_PREPARE_HIBERNATE = 0xC0000407, - MD_NTSTATUS_WIN_STATUS_USER2USER_REQUIRED = 0xC0000408, - MD_NTSTATUS_WIN_STATUS_STACK_BUFFER_OVERRUN = 0xC0000409, - MD_NTSTATUS_WIN_STATUS_NO_S4U_PROT_SUPPORT = 0xC000040A, - MD_NTSTATUS_WIN_STATUS_CROSSREALM_DELEGATION_FAILURE = 0xC000040B, - MD_NTSTATUS_WIN_STATUS_REVOCATION_OFFLINE_KDC = 0xC000040C, - MD_NTSTATUS_WIN_STATUS_ISSUING_CA_UNTRUSTED_KDC = 0xC000040D, - MD_NTSTATUS_WIN_STATUS_KDC_CERT_EXPIRED = 0xC000040E, - MD_NTSTATUS_WIN_STATUS_KDC_CERT_REVOKED = 0xC000040F, - MD_NTSTATUS_WIN_STATUS_PARAMETER_QUOTA_EXCEEDED = 0xC0000410, - MD_NTSTATUS_WIN_STATUS_HIBERNATION_FAILURE = 0xC0000411, - MD_NTSTATUS_WIN_STATUS_DELAY_LOAD_FAILED = 0xC0000412, - MD_NTSTATUS_WIN_STATUS_AUTHENTICATION_FIREWALL_FAILED = 0xC0000413, - MD_NTSTATUS_WIN_STATUS_VDM_DISALLOWED = 0xC0000414, - MD_NTSTATUS_WIN_STATUS_HUNG_DISPLAY_DRIVER_THREAD = 0xC0000415, - MD_NTSTATUS_WIN_STATUS_INSUFFICIENT_RESOURCE_FOR_SPECIFIED_SHARED_SECTION_SIZE = 0xC0000416, - MD_NTSTATUS_WIN_STATUS_INVALID_CRUNTIME_PARAMETER = 0xC0000417, - MD_NTSTATUS_WIN_STATUS_NTLM_BLOCKED = 0xC0000418, - MD_NTSTATUS_WIN_STATUS_DS_SRC_SID_EXISTS_IN_FOREST = 0xC0000419, - MD_NTSTATUS_WIN_STATUS_DS_DOMAIN_NAME_EXISTS_IN_FOREST = 0xC000041A, - MD_NTSTATUS_WIN_STATUS_DS_FLAT_NAME_EXISTS_IN_FOREST = 0xC000041B, - MD_NTSTATUS_WIN_STATUS_INVALID_USER_PRINCIPAL_NAME = 0xC000041C, - MD_NTSTATUS_WIN_STATUS_FATAL_USER_CALLBACK_EXCEPTION = 0xC000041D, - MD_NTSTATUS_WIN_STATUS_ASSERTION_FAILURE = 0xC0000420, - MD_NTSTATUS_WIN_STATUS_VERIFIER_STOP = 0xC0000421, - MD_NTSTATUS_WIN_STATUS_CALLBACK_POP_STACK = 0xC0000423, - MD_NTSTATUS_WIN_STATUS_INCOMPATIBLE_DRIVER_BLOCKED = 0xC0000424, - MD_NTSTATUS_WIN_STATUS_HIVE_UNLOADED = 0xC0000425, - MD_NTSTATUS_WIN_STATUS_COMPRESSION_DISABLED = 0xC0000426, - MD_NTSTATUS_WIN_STATUS_FILE_SYSTEM_LIMITATION = 0xC0000427, - MD_NTSTATUS_WIN_STATUS_INVALID_IMAGE_HASH = 0xC0000428, - MD_NTSTATUS_WIN_STATUS_NOT_CAPABLE = 0xC0000429, - MD_NTSTATUS_WIN_STATUS_REQUEST_OUT_OF_SEQUENCE = 0xC000042A, - MD_NTSTATUS_WIN_STATUS_IMPLEMENTATION_LIMIT = 0xC000042B, - MD_NTSTATUS_WIN_STATUS_ELEVATION_REQUIRED = 0xC000042C, - MD_NTSTATUS_WIN_STATUS_NO_SECURITY_CONTEXT = 0xC000042D, - MD_NTSTATUS_WIN_STATUS_PKU2U_CERT_FAILURE = 0xC000042F, - MD_NTSTATUS_WIN_STATUS_BEYOND_VDL = 0xC0000432, - MD_NTSTATUS_WIN_STATUS_ENCOUNTERED_WRITE_IN_PROGRESS = 0xC0000433, - MD_NTSTATUS_WIN_STATUS_PTE_CHANGED = 0xC0000434, - MD_NTSTATUS_WIN_STATUS_PURGE_FAILED = 0xC0000435, - MD_NTSTATUS_WIN_STATUS_CRED_REQUIRES_CONFIRMATION = 0xC0000440, - MD_NTSTATUS_WIN_STATUS_CS_ENCRYPTION_INVALID_SERVER_RESPONSE = 0xC0000441, - MD_NTSTATUS_WIN_STATUS_CS_ENCRYPTION_UNSUPPORTED_SERVER = 0xC0000442, - MD_NTSTATUS_WIN_STATUS_CS_ENCRYPTION_EXISTING_ENCRYPTED_FILE = 0xC0000443, - MD_NTSTATUS_WIN_STATUS_CS_ENCRYPTION_NEW_ENCRYPTED_FILE = 0xC0000444, - MD_NTSTATUS_WIN_STATUS_CS_ENCRYPTION_FILE_NOT_CSE = 0xC0000445, - MD_NTSTATUS_WIN_STATUS_INVALID_LABEL = 0xC0000446, - MD_NTSTATUS_WIN_STATUS_DRIVER_PROCESS_TERMINATED = 0xC0000450, - MD_NTSTATUS_WIN_STATUS_AMBIGUOUS_SYSTEM_DEVICE = 0xC0000451, - MD_NTSTATUS_WIN_STATUS_SYSTEM_DEVICE_NOT_FOUND = 0xC0000452, - MD_NTSTATUS_WIN_STATUS_RESTART_BOOT_APPLICATION = 0xC0000453, - MD_NTSTATUS_WIN_STATUS_INSUFFICIENT_NVRAM_RESOURCES = 0xC0000454, - MD_NTSTATUS_WIN_STATUS_INVALID_SESSION = 0xC0000455, - MD_NTSTATUS_WIN_STATUS_THREAD_ALREADY_IN_SESSION = 0xC0000456, - MD_NTSTATUS_WIN_STATUS_THREAD_NOT_IN_SESSION = 0xC0000457, - MD_NTSTATUS_WIN_STATUS_INVALID_WEIGHT = 0xC0000458, - MD_NTSTATUS_WIN_STATUS_REQUEST_PAUSED = 0xC0000459, - MD_NTSTATUS_WIN_STATUS_NO_RANGES_PROCESSED = 0xC0000460, - MD_NTSTATUS_WIN_STATUS_DISK_RESOURCES_EXHAUSTED = 0xC0000461, - MD_NTSTATUS_WIN_STATUS_NEEDS_REMEDIATION = 0xC0000462, - MD_NTSTATUS_WIN_STATUS_DEVICE_FEATURE_NOT_SUPPORTED = 0xC0000463, - MD_NTSTATUS_WIN_STATUS_DEVICE_UNREACHABLE = 0xC0000464, - MD_NTSTATUS_WIN_STATUS_INVALID_TOKEN = 0xC0000465, - MD_NTSTATUS_WIN_STATUS_SERVER_UNAVAILABLE = 0xC0000466, - MD_NTSTATUS_WIN_STATUS_FILE_NOT_AVAILABLE = 0xC0000467, - MD_NTSTATUS_WIN_STATUS_DEVICE_INSUFFICIENT_RESOURCES = 0xC0000468, - MD_NTSTATUS_WIN_STATUS_PACKAGE_UPDATING = 0xC0000469, - MD_NTSTATUS_WIN_STATUS_NOT_READ_FROM_COPY = 0xC000046A, - MD_NTSTATUS_WIN_STATUS_FT_WRITE_FAILURE = 0xC000046B, - MD_NTSTATUS_WIN_STATUS_FT_DI_SCAN_REQUIRED = 0xC000046C, - MD_NTSTATUS_WIN_STATUS_OBJECT_NOT_EXTERNALLY_BACKED = 0xC000046D, - MD_NTSTATUS_WIN_STATUS_EXTERNAL_BACKING_PROVIDER_UNKNOWN = 0xC000046E, - MD_NTSTATUS_WIN_STATUS_DATA_CHECKSUM_ERROR = 0xC0000470, - MD_NTSTATUS_WIN_STATUS_INTERMIXED_KERNEL_EA_OPERATION = 0xC0000471, - MD_NTSTATUS_WIN_STATUS_TRIM_READ_ZERO_NOT_SUPPORTED = 0xC0000472, - MD_NTSTATUS_WIN_STATUS_TOO_MANY_SEGMENT_DESCRIPTORS = 0xC0000473, - MD_NTSTATUS_WIN_STATUS_INVALID_OFFSET_ALIGNMENT = 0xC0000474, - MD_NTSTATUS_WIN_STATUS_INVALID_FIELD_IN_PARAMETER_LIST = 0xC0000475, - MD_NTSTATUS_WIN_STATUS_OPERATION_IN_PROGRESS = 0xC0000476, - MD_NTSTATUS_WIN_STATUS_INVALID_INITIATOR_TARGET_PATH = 0xC0000477, - MD_NTSTATUS_WIN_STATUS_SCRUB_DATA_DISABLED = 0xC0000478, - MD_NTSTATUS_WIN_STATUS_NOT_REDUNDANT_STORAGE = 0xC0000479, - MD_NTSTATUS_WIN_STATUS_RESIDENT_FILE_NOT_SUPPORTED = 0xC000047A, - MD_NTSTATUS_WIN_STATUS_COMPRESSED_FILE_NOT_SUPPORTED = 0xC000047B, - MD_NTSTATUS_WIN_STATUS_DIRECTORY_NOT_SUPPORTED = 0xC000047C, - MD_NTSTATUS_WIN_STATUS_IO_OPERATION_TIMEOUT = 0xC000047D, - MD_NTSTATUS_WIN_STATUS_SYSTEM_NEEDS_REMEDIATION = 0xC000047E, - MD_NTSTATUS_WIN_STATUS_APPX_INTEGRITY_FAILURE_CLR_NGEN = 0xC000047F, - MD_NTSTATUS_WIN_STATUS_SHARE_UNAVAILABLE = 0xC0000480, - MD_NTSTATUS_WIN_STATUS_APISET_NOT_HOSTED = 0xC0000481, - MD_NTSTATUS_WIN_STATUS_APISET_NOT_PRESENT = 0xC0000482, - MD_NTSTATUS_WIN_STATUS_DEVICE_HARDWARE_ERROR = 0xC0000483, - MD_NTSTATUS_WIN_STATUS_INVALID_TASK_NAME = 0xC0000500, - MD_NTSTATUS_WIN_STATUS_INVALID_TASK_INDEX = 0xC0000501, - MD_NTSTATUS_WIN_STATUS_THREAD_ALREADY_IN_TASK = 0xC0000502, - MD_NTSTATUS_WIN_STATUS_CALLBACK_BYPASS = 0xC0000503, - MD_NTSTATUS_WIN_STATUS_UNDEFINED_SCOPE = 0xC0000504, - MD_NTSTATUS_WIN_STATUS_INVALID_CAP = 0xC0000505, - MD_NTSTATUS_WIN_STATUS_NOT_GUI_PROCESS = 0xC0000506, - MD_NTSTATUS_WIN_STATUS_FAIL_FAST_EXCEPTION = 0xC0000602, - MD_NTSTATUS_WIN_STATUS_IMAGE_CERT_REVOKED = 0xC0000603, - MD_NTSTATUS_WIN_STATUS_DYNAMIC_CODE_BLOCKED = 0xC0000604, - MD_NTSTATUS_WIN_STATUS_PORT_CLOSED = 0xC0000700, - MD_NTSTATUS_WIN_STATUS_MESSAGE_LOST = 0xC0000701, - MD_NTSTATUS_WIN_STATUS_INVALID_MESSAGE = 0xC0000702, - MD_NTSTATUS_WIN_STATUS_REQUEST_CANCELED = 0xC0000703, - MD_NTSTATUS_WIN_STATUS_RECURSIVE_DISPATCH = 0xC0000704, - MD_NTSTATUS_WIN_STATUS_LPC_RECEIVE_BUFFER_EXPECTED = 0xC0000705, - MD_NTSTATUS_WIN_STATUS_LPC_INVALID_CONNECTION_USAGE = 0xC0000706, - MD_NTSTATUS_WIN_STATUS_LPC_REQUESTS_NOT_ALLOWED = 0xC0000707, - MD_NTSTATUS_WIN_STATUS_RESOURCE_IN_USE = 0xC0000708, - MD_NTSTATUS_WIN_STATUS_HARDWARE_MEMORY_ERROR = 0xC0000709, - MD_NTSTATUS_WIN_STATUS_THREADPOOL_HANDLE_EXCEPTION = 0xC000070A, - MD_NTSTATUS_WIN_STATUS_THREADPOOL_SET_EVENT_ON_COMPLETION_FAILED = 0xC000070B, - MD_NTSTATUS_WIN_STATUS_THREADPOOL_RELEASE_SEMAPHORE_ON_COMPLETION_FAILED = 0xC000070C, - MD_NTSTATUS_WIN_STATUS_THREADPOOL_RELEASE_MUTEX_ON_COMPLETION_FAILED = 0xC000070D, - MD_NTSTATUS_WIN_STATUS_THREADPOOL_FREE_LIBRARY_ON_COMPLETION_FAILED = 0xC000070E, - MD_NTSTATUS_WIN_STATUS_THREADPOOL_RELEASED_DURING_OPERATION = 0xC000070F, - MD_NTSTATUS_WIN_STATUS_CALLBACK_RETURNED_WHILE_IMPERSONATING = 0xC0000710, - MD_NTSTATUS_WIN_STATUS_APC_RETURNED_WHILE_IMPERSONATING = 0xC0000711, - MD_NTSTATUS_WIN_STATUS_PROCESS_IS_PROTECTED = 0xC0000712, - MD_NTSTATUS_WIN_STATUS_MCA_EXCEPTION = 0xC0000713, - MD_NTSTATUS_WIN_STATUS_CERTIFICATE_MAPPING_NOT_UNIQUE = 0xC0000714, - MD_NTSTATUS_WIN_STATUS_SYMLINK_CLASS_DISABLED = 0xC0000715, - MD_NTSTATUS_WIN_STATUS_INVALID_IDN_NORMALIZATION = 0xC0000716, - MD_NTSTATUS_WIN_STATUS_NO_UNICODE_TRANSLATION = 0xC0000717, - MD_NTSTATUS_WIN_STATUS_ALREADY_REGISTERED = 0xC0000718, - MD_NTSTATUS_WIN_STATUS_CONTEXT_MISMATCH = 0xC0000719, - MD_NTSTATUS_WIN_STATUS_PORT_ALREADY_HAS_COMPLETION_LIST = 0xC000071A, - MD_NTSTATUS_WIN_STATUS_CALLBACK_RETURNED_THREAD_PRIORITY = 0xC000071B, - MD_NTSTATUS_WIN_STATUS_INVALID_THREAD = 0xC000071C, - MD_NTSTATUS_WIN_STATUS_CALLBACK_RETURNED_TRANSACTION = 0xC000071D, - MD_NTSTATUS_WIN_STATUS_CALLBACK_RETURNED_LDR_LOCK = 0xC000071E, - MD_NTSTATUS_WIN_STATUS_CALLBACK_RETURNED_LANG = 0xC000071F, - MD_NTSTATUS_WIN_STATUS_CALLBACK_RETURNED_PRI_BACK = 0xC0000720, - MD_NTSTATUS_WIN_STATUS_CALLBACK_RETURNED_THREAD_AFFINITY = 0xC0000721, - MD_NTSTATUS_WIN_STATUS_DISK_REPAIR_DISABLED = 0xC0000800, - MD_NTSTATUS_WIN_STATUS_DS_DOMAIN_RENAME_IN_PROGRESS = 0xC0000801, - MD_NTSTATUS_WIN_STATUS_DISK_QUOTA_EXCEEDED = 0xC0000802, - MD_NTSTATUS_WIN_STATUS_CONTENT_BLOCKED = 0xC0000804, - MD_NTSTATUS_WIN_STATUS_BAD_CLUSTERS = 0xC0000805, - MD_NTSTATUS_WIN_STATUS_VOLUME_DIRTY = 0xC0000806, - MD_NTSTATUS_WIN_STATUS_DISK_REPAIR_UNSUCCESSFUL = 0xC0000808, - MD_NTSTATUS_WIN_STATUS_CORRUPT_LOG_OVERFULL = 0xC0000809, - MD_NTSTATUS_WIN_STATUS_CORRUPT_LOG_CORRUPTED = 0xC000080A, - MD_NTSTATUS_WIN_STATUS_CORRUPT_LOG_UNAVAILABLE = 0xC000080B, - MD_NTSTATUS_WIN_STATUS_CORRUPT_LOG_DELETED_FULL = 0xC000080C, - MD_NTSTATUS_WIN_STATUS_CORRUPT_LOG_CLEARED = 0xC000080D, - MD_NTSTATUS_WIN_STATUS_ORPHAN_NAME_EXHAUSTED = 0xC000080E, - MD_NTSTATUS_WIN_STATUS_PROACTIVE_SCAN_IN_PROGRESS = 0xC000080F, - MD_NTSTATUS_WIN_STATUS_ENCRYPTED_IO_NOT_POSSIBLE = 0xC0000810, - MD_NTSTATUS_WIN_STATUS_CORRUPT_LOG_UPLEVEL_RECORDS = 0xC0000811, - MD_NTSTATUS_WIN_STATUS_FILE_CHECKED_OUT = 0xC0000901, - MD_NTSTATUS_WIN_STATUS_CHECKOUT_REQUIRED = 0xC0000902, - MD_NTSTATUS_WIN_STATUS_BAD_FILE_TYPE = 0xC0000903, - MD_NTSTATUS_WIN_STATUS_FILE_TOO_LARGE = 0xC0000904, - MD_NTSTATUS_WIN_STATUS_FORMS_AUTH_REQUIRED = 0xC0000905, - MD_NTSTATUS_WIN_STATUS_VIRUS_INFECTED = 0xC0000906, - MD_NTSTATUS_WIN_STATUS_VIRUS_DELETED = 0xC0000907, - MD_NTSTATUS_WIN_STATUS_BAD_MCFG_TABLE = 0xC0000908, - MD_NTSTATUS_WIN_STATUS_CANNOT_BREAK_OPLOCK = 0xC0000909, - MD_NTSTATUS_WIN_STATUS_BAD_KEY = 0xC000090A, - MD_NTSTATUS_WIN_STATUS_BAD_DATA = 0xC000090B, - MD_NTSTATUS_WIN_STATUS_NO_KEY = 0xC000090C, - MD_NTSTATUS_WIN_STATUS_FILE_HANDLE_REVOKED = 0xC0000910, - MD_NTSTATUS_WIN_STATUS_WOW_ASSERTION = 0xC0009898, - MD_NTSTATUS_WIN_STATUS_INVALID_SIGNATURE = 0xC000A000, - MD_NTSTATUS_WIN_STATUS_HMAC_NOT_SUPPORTED = 0xC000A001, - MD_NTSTATUS_WIN_STATUS_AUTH_TAG_MISMATCH = 0xC000A002, - MD_NTSTATUS_WIN_STATUS_INVALID_STATE_TRANSITION = 0xC000A003, - MD_NTSTATUS_WIN_STATUS_INVALID_KERNEL_INFO_VERSION = 0xC000A004, - MD_NTSTATUS_WIN_STATUS_INVALID_PEP_INFO_VERSION = 0xC000A005, - MD_NTSTATUS_WIN_STATUS_IPSEC_QUEUE_OVERFLOW = 0xC000A010, - MD_NTSTATUS_WIN_STATUS_ND_QUEUE_OVERFLOW = 0xC000A011, - MD_NTSTATUS_WIN_STATUS_HOPLIMIT_EXCEEDED = 0xC000A012, - MD_NTSTATUS_WIN_STATUS_PROTOCOL_NOT_SUPPORTED = 0xC000A013, - MD_NTSTATUS_WIN_STATUS_FASTPATH_REJECTED = 0xC000A014, - MD_NTSTATUS_WIN_STATUS_LOST_WRITEBEHIND_DATA_NETWORK_DISCONNECTED = 0xC000A080, - MD_NTSTATUS_WIN_STATUS_LOST_WRITEBEHIND_DATA_NETWORK_SERVER_ERROR = 0xC000A081, - MD_NTSTATUS_WIN_STATUS_LOST_WRITEBEHIND_DATA_LOCAL_DISK_ERROR = 0xC000A082, - MD_NTSTATUS_WIN_STATUS_XML_PARSE_ERROR = 0xC000A083, - MD_NTSTATUS_WIN_STATUS_XMLDSIG_ERROR = 0xC000A084, - MD_NTSTATUS_WIN_STATUS_WRONG_COMPARTMENT = 0xC000A085, - MD_NTSTATUS_WIN_STATUS_AUTHIP_FAILURE = 0xC000A086, - MD_NTSTATUS_WIN_STATUS_DS_OID_MAPPED_GROUP_CANT_HAVE_MEMBERS = 0xC000A087, - MD_NTSTATUS_WIN_STATUS_DS_OID_NOT_FOUND = 0xC000A088, - MD_NTSTATUS_WIN_STATUS_INCORRECT_ACCOUNT_TYPE = 0xC000A089, - MD_NTSTATUS_WIN_STATUS_HASH_NOT_SUPPORTED = 0xC000A100, - MD_NTSTATUS_WIN_STATUS_HASH_NOT_PRESENT = 0xC000A101, - MD_NTSTATUS_WIN_STATUS_SECONDARY_IC_PROVIDER_NOT_REGISTERED = 0xC000A121, - MD_NTSTATUS_WIN_STATUS_GPIO_CLIENT_INFORMATION_INVALID = 0xC000A122, - MD_NTSTATUS_WIN_STATUS_GPIO_VERSION_NOT_SUPPORTED = 0xC000A123, - MD_NTSTATUS_WIN_STATUS_GPIO_INVALID_REGISTRATION_PACKET = 0xC000A124, - MD_NTSTATUS_WIN_STATUS_GPIO_OPERATION_DENIED = 0xC000A125, - MD_NTSTATUS_WIN_STATUS_GPIO_INCOMPATIBLE_CONNECT_MODE = 0xC000A126, - MD_NTSTATUS_WIN_STATUS_CANNOT_SWITCH_RUNLEVEL = 0xC000A141, - MD_NTSTATUS_WIN_STATUS_INVALID_RUNLEVEL_SETTING = 0xC000A142, - MD_NTSTATUS_WIN_STATUS_RUNLEVEL_SWITCH_TIMEOUT = 0xC000A143, - MD_NTSTATUS_WIN_STATUS_RUNLEVEL_SWITCH_AGENT_TIMEOUT = 0xC000A145, - MD_NTSTATUS_WIN_STATUS_RUNLEVEL_SWITCH_IN_PROGRESS = 0xC000A146, - MD_NTSTATUS_WIN_STATUS_NOT_APPCONTAINER = 0xC000A200, - MD_NTSTATUS_WIN_STATUS_NOT_SUPPORTED_IN_APPCONTAINER = 0xC000A201, - MD_NTSTATUS_WIN_STATUS_INVALID_PACKAGE_SID_LENGTH = 0xC000A202, - MD_NTSTATUS_WIN_STATUS_APP_DATA_NOT_FOUND = 0xC000A281, - MD_NTSTATUS_WIN_STATUS_APP_DATA_EXPIRED = 0xC000A282, - MD_NTSTATUS_WIN_STATUS_APP_DATA_CORRUPT = 0xC000A283, - MD_NTSTATUS_WIN_STATUS_APP_DATA_LIMIT_EXCEEDED = 0xC000A284, - MD_NTSTATUS_WIN_STATUS_APP_DATA_REBOOT_REQUIRED = 0xC000A285, - MD_NTSTATUS_WIN_STATUS_OFFLOAD_READ_FLT_NOT_SUPPORTED = 0xC000A2A1, - MD_NTSTATUS_WIN_STATUS_OFFLOAD_WRITE_FLT_NOT_SUPPORTED = 0xC000A2A2, - MD_NTSTATUS_WIN_STATUS_OFFLOAD_READ_FILE_NOT_SUPPORTED = 0xC000A2A3, - MD_NTSTATUS_WIN_STATUS_OFFLOAD_WRITE_FILE_NOT_SUPPORTED = 0xC000A2A4, - MD_NTSTATUS_WIN_DBG_NO_STATE_CHANGE = 0xC0010001, - MD_NTSTATUS_WIN_DBG_APP_NOT_IDLE = 0xC0010002, - MD_NTSTATUS_WIN_RPC_NT_INVALID_STRING_BINDING = 0xC0020001, - MD_NTSTATUS_WIN_RPC_NT_WRONG_KIND_OF_BINDING = 0xC0020002, - MD_NTSTATUS_WIN_RPC_NT_INVALID_BINDING = 0xC0020003, - MD_NTSTATUS_WIN_RPC_NT_PROTSEQ_NOT_SUPPORTED = 0xC0020004, - MD_NTSTATUS_WIN_RPC_NT_INVALID_RPC_PROTSEQ = 0xC0020005, - MD_NTSTATUS_WIN_RPC_NT_INVALID_STRING_UUID = 0xC0020006, - MD_NTSTATUS_WIN_RPC_NT_INVALID_ENDPOINT_FORMAT = 0xC0020007, - MD_NTSTATUS_WIN_RPC_NT_INVALID_NET_ADDR = 0xC0020008, - MD_NTSTATUS_WIN_RPC_NT_NO_ENDPOINT_FOUND = 0xC0020009, - MD_NTSTATUS_WIN_RPC_NT_INVALID_TIMEOUT = 0xC002000A, - MD_NTSTATUS_WIN_RPC_NT_OBJECT_NOT_FOUND = 0xC002000B, - MD_NTSTATUS_WIN_RPC_NT_ALREADY_REGISTERED = 0xC002000C, - MD_NTSTATUS_WIN_RPC_NT_TYPE_ALREADY_REGISTERED = 0xC002000D, - MD_NTSTATUS_WIN_RPC_NT_ALREADY_LISTENING = 0xC002000E, - MD_NTSTATUS_WIN_RPC_NT_NO_PROTSEQS_REGISTERED = 0xC002000F, - MD_NTSTATUS_WIN_RPC_NT_NOT_LISTENING = 0xC0020010, - MD_NTSTATUS_WIN_RPC_NT_UNKNOWN_MGR_TYPE = 0xC0020011, - MD_NTSTATUS_WIN_RPC_NT_UNKNOWN_IF = 0xC0020012, - MD_NTSTATUS_WIN_RPC_NT_NO_BINDINGS = 0xC0020013, - MD_NTSTATUS_WIN_RPC_NT_NO_PROTSEQS = 0xC0020014, - MD_NTSTATUS_WIN_RPC_NT_CANT_CREATE_ENDPOINT = 0xC0020015, - MD_NTSTATUS_WIN_RPC_NT_OUT_OF_RESOURCES = 0xC0020016, - MD_NTSTATUS_WIN_RPC_NT_SERVER_UNAVAILABLE = 0xC0020017, - MD_NTSTATUS_WIN_RPC_NT_SERVER_TOO_BUSY = 0xC0020018, - MD_NTSTATUS_WIN_RPC_NT_INVALID_NETWORK_OPTIONS = 0xC0020019, - MD_NTSTATUS_WIN_RPC_NT_NO_CALL_ACTIVE = 0xC002001A, - MD_NTSTATUS_WIN_RPC_NT_CALL_FAILED = 0xC002001B, - MD_NTSTATUS_WIN_RPC_NT_CALL_FAILED_DNE = 0xC002001C, - MD_NTSTATUS_WIN_RPC_NT_PROTOCOL_ERROR = 0xC002001D, - MD_NTSTATUS_WIN_RPC_NT_UNSUPPORTED_TRANS_SYN = 0xC002001F, - MD_NTSTATUS_WIN_RPC_NT_UNSUPPORTED_TYPE = 0xC0020021, - MD_NTSTATUS_WIN_RPC_NT_INVALID_TAG = 0xC0020022, - MD_NTSTATUS_WIN_RPC_NT_INVALID_BOUND = 0xC0020023, - MD_NTSTATUS_WIN_RPC_NT_NO_ENTRY_NAME = 0xC0020024, - MD_NTSTATUS_WIN_RPC_NT_INVALID_NAME_SYNTAX = 0xC0020025, - MD_NTSTATUS_WIN_RPC_NT_UNSUPPORTED_NAME_SYNTAX = 0xC0020026, - MD_NTSTATUS_WIN_RPC_NT_UUID_NO_ADDRESS = 0xC0020028, - MD_NTSTATUS_WIN_RPC_NT_DUPLICATE_ENDPOINT = 0xC0020029, - MD_NTSTATUS_WIN_RPC_NT_UNKNOWN_AUTHN_TYPE = 0xC002002A, - MD_NTSTATUS_WIN_RPC_NT_MAX_CALLS_TOO_SMALL = 0xC002002B, - MD_NTSTATUS_WIN_RPC_NT_STRING_TOO_LONG = 0xC002002C, - MD_NTSTATUS_WIN_RPC_NT_PROTSEQ_NOT_FOUND = 0xC002002D, - MD_NTSTATUS_WIN_RPC_NT_PROCNUM_OUT_OF_RANGE = 0xC002002E, - MD_NTSTATUS_WIN_RPC_NT_BINDING_HAS_NO_AUTH = 0xC002002F, - MD_NTSTATUS_WIN_RPC_NT_UNKNOWN_AUTHN_SERVICE = 0xC0020030, - MD_NTSTATUS_WIN_RPC_NT_UNKNOWN_AUTHN_LEVEL = 0xC0020031, - MD_NTSTATUS_WIN_RPC_NT_INVALID_AUTH_IDENTITY = 0xC0020032, - MD_NTSTATUS_WIN_RPC_NT_UNKNOWN_AUTHZ_SERVICE = 0xC0020033, - MD_NTSTATUS_WIN_EPT_NT_INVALID_ENTRY = 0xC0020034, - MD_NTSTATUS_WIN_EPT_NT_CANT_PERFORM_OP = 0xC0020035, - MD_NTSTATUS_WIN_EPT_NT_NOT_REGISTERED = 0xC0020036, - MD_NTSTATUS_WIN_RPC_NT_NOTHING_TO_EXPORT = 0xC0020037, - MD_NTSTATUS_WIN_RPC_NT_INCOMPLETE_NAME = 0xC0020038, - MD_NTSTATUS_WIN_RPC_NT_INVALID_VERS_OPTION = 0xC0020039, - MD_NTSTATUS_WIN_RPC_NT_NO_MORE_MEMBERS = 0xC002003A, - MD_NTSTATUS_WIN_RPC_NT_NOT_ALL_OBJS_UNEXPORTED = 0xC002003B, - MD_NTSTATUS_WIN_RPC_NT_INTERFACE_NOT_FOUND = 0xC002003C, - MD_NTSTATUS_WIN_RPC_NT_ENTRY_ALREADY_EXISTS = 0xC002003D, - MD_NTSTATUS_WIN_RPC_NT_ENTRY_NOT_FOUND = 0xC002003E, - MD_NTSTATUS_WIN_RPC_NT_NAME_SERVICE_UNAVAILABLE = 0xC002003F, - MD_NTSTATUS_WIN_RPC_NT_INVALID_NAF_ID = 0xC0020040, - MD_NTSTATUS_WIN_RPC_NT_CANNOT_SUPPORT = 0xC0020041, - MD_NTSTATUS_WIN_RPC_NT_NO_CONTEXT_AVAILABLE = 0xC0020042, - MD_NTSTATUS_WIN_RPC_NT_INTERNAL_ERROR = 0xC0020043, - MD_NTSTATUS_WIN_RPC_NT_ZERO_DIVIDE = 0xC0020044, - MD_NTSTATUS_WIN_RPC_NT_ADDRESS_ERROR = 0xC0020045, - MD_NTSTATUS_WIN_RPC_NT_FP_DIV_ZERO = 0xC0020046, - MD_NTSTATUS_WIN_RPC_NT_FP_UNDERFLOW = 0xC0020047, - MD_NTSTATUS_WIN_RPC_NT_FP_OVERFLOW = 0xC0020048, - MD_NTSTATUS_WIN_RPC_NT_CALL_IN_PROGRESS = 0xC0020049, - MD_NTSTATUS_WIN_RPC_NT_NO_MORE_BINDINGS = 0xC002004A, - MD_NTSTATUS_WIN_RPC_NT_GROUP_MEMBER_NOT_FOUND = 0xC002004B, - MD_NTSTATUS_WIN_EPT_NT_CANT_CREATE = 0xC002004C, - MD_NTSTATUS_WIN_RPC_NT_INVALID_OBJECT = 0xC002004D, - MD_NTSTATUS_WIN_RPC_NT_NO_INTERFACES = 0xC002004F, - MD_NTSTATUS_WIN_RPC_NT_CALL_CANCELLED = 0xC0020050, - MD_NTSTATUS_WIN_RPC_NT_BINDING_INCOMPLETE = 0xC0020051, - MD_NTSTATUS_WIN_RPC_NT_COMM_FAILURE = 0xC0020052, - MD_NTSTATUS_WIN_RPC_NT_UNSUPPORTED_AUTHN_LEVEL = 0xC0020053, - MD_NTSTATUS_WIN_RPC_NT_NO_PRINC_NAME = 0xC0020054, - MD_NTSTATUS_WIN_RPC_NT_NOT_RPC_ERROR = 0xC0020055, - MD_NTSTATUS_WIN_RPC_NT_SEC_PKG_ERROR = 0xC0020057, - MD_NTSTATUS_WIN_RPC_NT_NOT_CANCELLED = 0xC0020058, - MD_NTSTATUS_WIN_RPC_NT_INVALID_ASYNC_HANDLE = 0xC0020062, - MD_NTSTATUS_WIN_RPC_NT_INVALID_ASYNC_CALL = 0xC0020063, - MD_NTSTATUS_WIN_RPC_NT_PROXY_ACCESS_DENIED = 0xC0020064, - MD_NTSTATUS_WIN_RPC_NT_COOKIE_AUTH_FAILED = 0xC0020065, - MD_NTSTATUS_WIN_RPC_NT_NO_MORE_ENTRIES = 0xC0030001, - MD_NTSTATUS_WIN_RPC_NT_SS_CHAR_TRANS_OPEN_FAIL = 0xC0030002, - MD_NTSTATUS_WIN_RPC_NT_SS_CHAR_TRANS_SHORT_FILE = 0xC0030003, - MD_NTSTATUS_WIN_RPC_NT_SS_IN_NULL_CONTEXT = 0xC0030004, - MD_NTSTATUS_WIN_RPC_NT_SS_CONTEXT_MISMATCH = 0xC0030005, - MD_NTSTATUS_WIN_RPC_NT_SS_CONTEXT_DAMAGED = 0xC0030006, - MD_NTSTATUS_WIN_RPC_NT_SS_HANDLES_MISMATCH = 0xC0030007, - MD_NTSTATUS_WIN_RPC_NT_SS_CANNOT_GET_CALL_HANDLE = 0xC0030008, - MD_NTSTATUS_WIN_RPC_NT_NULL_REF_POINTER = 0xC0030009, - MD_NTSTATUS_WIN_RPC_NT_ENUM_VALUE_OUT_OF_RANGE = 0xC003000A, - MD_NTSTATUS_WIN_RPC_NT_BYTE_COUNT_TOO_SMALL = 0xC003000B, - MD_NTSTATUS_WIN_RPC_NT_BAD_STUB_DATA = 0xC003000C, - MD_NTSTATUS_WIN_RPC_NT_INVALID_ES_ACTION = 0xC0030059, - MD_NTSTATUS_WIN_RPC_NT_WRONG_ES_VERSION = 0xC003005A, - MD_NTSTATUS_WIN_RPC_NT_WRONG_STUB_VERSION = 0xC003005B, - MD_NTSTATUS_WIN_RPC_NT_INVALID_PIPE_OBJECT = 0xC003005C, - MD_NTSTATUS_WIN_RPC_NT_INVALID_PIPE_OPERATION = 0xC003005D, - MD_NTSTATUS_WIN_RPC_NT_WRONG_PIPE_VERSION = 0xC003005E, - MD_NTSTATUS_WIN_RPC_NT_PIPE_CLOSED = 0xC003005F, - MD_NTSTATUS_WIN_RPC_NT_PIPE_DISCIPLINE_ERROR = 0xC0030060, - MD_NTSTATUS_WIN_RPC_NT_PIPE_EMPTY = 0xC0030061, - MD_NTSTATUS_WIN_STATUS_PNP_BAD_MPS_TABLE = 0xC0040035, - MD_NTSTATUS_WIN_STATUS_PNP_TRANSLATION_FAILED = 0xC0040036, - MD_NTSTATUS_WIN_STATUS_PNP_IRQ_TRANSLATION_FAILED = 0xC0040037, - MD_NTSTATUS_WIN_STATUS_PNP_INVALID_ID = 0xC0040038, - MD_NTSTATUS_WIN_STATUS_IO_REISSUE_AS_CACHED = 0xC0040039, - MD_NTSTATUS_WIN_STATUS_CTX_WINSTATION_NAME_INVALID = 0xC00A0001, - MD_NTSTATUS_WIN_STATUS_CTX_INVALID_PD = 0xC00A0002, - MD_NTSTATUS_WIN_STATUS_CTX_PD_NOT_FOUND = 0xC00A0003, - MD_NTSTATUS_WIN_STATUS_CTX_CLOSE_PENDING = 0xC00A0006, - MD_NTSTATUS_WIN_STATUS_CTX_NO_OUTBUF = 0xC00A0007, - MD_NTSTATUS_WIN_STATUS_CTX_MODEM_INF_NOT_FOUND = 0xC00A0008, - MD_NTSTATUS_WIN_STATUS_CTX_INVALID_MODEMNAME = 0xC00A0009, - MD_NTSTATUS_WIN_STATUS_CTX_RESPONSE_ERROR = 0xC00A000A, - MD_NTSTATUS_WIN_STATUS_CTX_MODEM_RESPONSE_TIMEOUT = 0xC00A000B, - MD_NTSTATUS_WIN_STATUS_CTX_MODEM_RESPONSE_NO_CARRIER = 0xC00A000C, - MD_NTSTATUS_WIN_STATUS_CTX_MODEM_RESPONSE_NO_DIALTONE = 0xC00A000D, - MD_NTSTATUS_WIN_STATUS_CTX_MODEM_RESPONSE_BUSY = 0xC00A000E, - MD_NTSTATUS_WIN_STATUS_CTX_MODEM_RESPONSE_VOICE = 0xC00A000F, - MD_NTSTATUS_WIN_STATUS_CTX_TD_ERROR = 0xC00A0010, - MD_NTSTATUS_WIN_STATUS_CTX_LICENSE_CLIENT_INVALID = 0xC00A0012, - MD_NTSTATUS_WIN_STATUS_CTX_LICENSE_NOT_AVAILABLE = 0xC00A0013, - MD_NTSTATUS_WIN_STATUS_CTX_LICENSE_EXPIRED = 0xC00A0014, - MD_NTSTATUS_WIN_STATUS_CTX_WINSTATION_NOT_FOUND = 0xC00A0015, - MD_NTSTATUS_WIN_STATUS_CTX_WINSTATION_NAME_COLLISION = 0xC00A0016, - MD_NTSTATUS_WIN_STATUS_CTX_WINSTATION_BUSY = 0xC00A0017, - MD_NTSTATUS_WIN_STATUS_CTX_BAD_VIDEO_MODE = 0xC00A0018, - MD_NTSTATUS_WIN_STATUS_CTX_GRAPHICS_INVALID = 0xC00A0022, - MD_NTSTATUS_WIN_STATUS_CTX_NOT_CONSOLE = 0xC00A0024, - MD_NTSTATUS_WIN_STATUS_CTX_CLIENT_QUERY_TIMEOUT = 0xC00A0026, - MD_NTSTATUS_WIN_STATUS_CTX_CONSOLE_DISCONNECT = 0xC00A0027, - MD_NTSTATUS_WIN_STATUS_CTX_CONSOLE_CONNECT = 0xC00A0028, - MD_NTSTATUS_WIN_STATUS_CTX_SHADOW_DENIED = 0xC00A002A, - MD_NTSTATUS_WIN_STATUS_CTX_WINSTATION_ACCESS_DENIED = 0xC00A002B, - MD_NTSTATUS_WIN_STATUS_CTX_INVALID_WD = 0xC00A002E, - MD_NTSTATUS_WIN_STATUS_CTX_WD_NOT_FOUND = 0xC00A002F, - MD_NTSTATUS_WIN_STATUS_CTX_SHADOW_INVALID = 0xC00A0030, - MD_NTSTATUS_WIN_STATUS_CTX_SHADOW_DISABLED = 0xC00A0031, - MD_NTSTATUS_WIN_STATUS_RDP_PROTOCOL_ERROR = 0xC00A0032, - MD_NTSTATUS_WIN_STATUS_CTX_CLIENT_LICENSE_NOT_SET = 0xC00A0033, - MD_NTSTATUS_WIN_STATUS_CTX_CLIENT_LICENSE_IN_USE = 0xC00A0034, - MD_NTSTATUS_WIN_STATUS_CTX_SHADOW_ENDED_BY_MODE_CHANGE = 0xC00A0035, - MD_NTSTATUS_WIN_STATUS_CTX_SHADOW_NOT_RUNNING = 0xC00A0036, - MD_NTSTATUS_WIN_STATUS_CTX_LOGON_DISABLED = 0xC00A0037, - MD_NTSTATUS_WIN_STATUS_CTX_SECURITY_LAYER_ERROR = 0xC00A0038, - MD_NTSTATUS_WIN_STATUS_TS_INCOMPATIBLE_SESSIONS = 0xC00A0039, - MD_NTSTATUS_WIN_STATUS_TS_VIDEO_SUBSYSTEM_ERROR = 0xC00A003A, - MD_NTSTATUS_WIN_STATUS_MUI_FILE_NOT_FOUND = 0xC00B0001, - MD_NTSTATUS_WIN_STATUS_MUI_INVALID_FILE = 0xC00B0002, - MD_NTSTATUS_WIN_STATUS_MUI_INVALID_RC_CONFIG = 0xC00B0003, - MD_NTSTATUS_WIN_STATUS_MUI_INVALID_LOCALE_NAME = 0xC00B0004, - MD_NTSTATUS_WIN_STATUS_MUI_INVALID_ULTIMATEFALLBACK_NAME = 0xC00B0005, - MD_NTSTATUS_WIN_STATUS_MUI_FILE_NOT_LOADED = 0xC00B0006, - MD_NTSTATUS_WIN_STATUS_RESOURCE_ENUM_USER_STOP = 0xC00B0007, - MD_NTSTATUS_WIN_STATUS_CLUSTER_INVALID_NODE = 0xC0130001, - MD_NTSTATUS_WIN_STATUS_CLUSTER_NODE_EXISTS = 0xC0130002, - MD_NTSTATUS_WIN_STATUS_CLUSTER_JOIN_IN_PROGRESS = 0xC0130003, - MD_NTSTATUS_WIN_STATUS_CLUSTER_NODE_NOT_FOUND = 0xC0130004, - MD_NTSTATUS_WIN_STATUS_CLUSTER_LOCAL_NODE_NOT_FOUND = 0xC0130005, - MD_NTSTATUS_WIN_STATUS_CLUSTER_NETWORK_EXISTS = 0xC0130006, - MD_NTSTATUS_WIN_STATUS_CLUSTER_NETWORK_NOT_FOUND = 0xC0130007, - MD_NTSTATUS_WIN_STATUS_CLUSTER_NETINTERFACE_EXISTS = 0xC0130008, - MD_NTSTATUS_WIN_STATUS_CLUSTER_NETINTERFACE_NOT_FOUND = 0xC0130009, - MD_NTSTATUS_WIN_STATUS_CLUSTER_INVALID_REQUEST = 0xC013000A, - MD_NTSTATUS_WIN_STATUS_CLUSTER_INVALID_NETWORK_PROVIDER = 0xC013000B, - MD_NTSTATUS_WIN_STATUS_CLUSTER_NODE_DOWN = 0xC013000C, - MD_NTSTATUS_WIN_STATUS_CLUSTER_NODE_UNREACHABLE = 0xC013000D, - MD_NTSTATUS_WIN_STATUS_CLUSTER_NODE_NOT_MEMBER = 0xC013000E, - MD_NTSTATUS_WIN_STATUS_CLUSTER_JOIN_NOT_IN_PROGRESS = 0xC013000F, - MD_NTSTATUS_WIN_STATUS_CLUSTER_INVALID_NETWORK = 0xC0130010, - MD_NTSTATUS_WIN_STATUS_CLUSTER_NO_NET_ADAPTERS = 0xC0130011, - MD_NTSTATUS_WIN_STATUS_CLUSTER_NODE_UP = 0xC0130012, - MD_NTSTATUS_WIN_STATUS_CLUSTER_NODE_PAUSED = 0xC0130013, - MD_NTSTATUS_WIN_STATUS_CLUSTER_NODE_NOT_PAUSED = 0xC0130014, - MD_NTSTATUS_WIN_STATUS_CLUSTER_NO_SECURITY_CONTEXT = 0xC0130015, - MD_NTSTATUS_WIN_STATUS_CLUSTER_NETWORK_NOT_INTERNAL = 0xC0130016, - MD_NTSTATUS_WIN_STATUS_CLUSTER_POISONED = 0xC0130017, - MD_NTSTATUS_WIN_STATUS_CLUSTER_NON_CSV_PATH = 0xC0130018, - MD_NTSTATUS_WIN_STATUS_CLUSTER_CSV_VOLUME_NOT_LOCAL = 0xC0130019, - MD_NTSTATUS_WIN_STATUS_CLUSTER_CSV_READ_OPLOCK_BREAK_IN_PROGRESS = 0xC0130020, - MD_NTSTATUS_WIN_STATUS_CLUSTER_CSV_AUTO_PAUSE_ERROR = 0xC0130021, - MD_NTSTATUS_WIN_STATUS_CLUSTER_CSV_REDIRECTED = 0xC0130022, - MD_NTSTATUS_WIN_STATUS_CLUSTER_CSV_NOT_REDIRECTED = 0xC0130023, - MD_NTSTATUS_WIN_STATUS_CLUSTER_CSV_VOLUME_DRAINING = 0xC0130024, - MD_NTSTATUS_WIN_STATUS_CLUSTER_CSV_SNAPSHOT_CREATION_IN_PROGRESS = 0xC0130025, - MD_NTSTATUS_WIN_STATUS_CLUSTER_CSV_VOLUME_DRAINING_SUCCEEDED_DOWNLEVEL = 0xC0130026, - MD_NTSTATUS_WIN_STATUS_ACPI_INVALID_OPCODE = 0xC0140001, - MD_NTSTATUS_WIN_STATUS_ACPI_STACK_OVERFLOW = 0xC0140002, - MD_NTSTATUS_WIN_STATUS_ACPI_ASSERT_FAILED = 0xC0140003, - MD_NTSTATUS_WIN_STATUS_ACPI_INVALID_INDEX = 0xC0140004, - MD_NTSTATUS_WIN_STATUS_ACPI_INVALID_ARGUMENT = 0xC0140005, - MD_NTSTATUS_WIN_STATUS_ACPI_FATAL = 0xC0140006, - MD_NTSTATUS_WIN_STATUS_ACPI_INVALID_SUPERNAME = 0xC0140007, - MD_NTSTATUS_WIN_STATUS_ACPI_INVALID_ARGTYPE = 0xC0140008, - MD_NTSTATUS_WIN_STATUS_ACPI_INVALID_OBJTYPE = 0xC0140009, - MD_NTSTATUS_WIN_STATUS_ACPI_INVALID_TARGETTYPE = 0xC014000A, - MD_NTSTATUS_WIN_STATUS_ACPI_INCORRECT_ARGUMENT_COUNT = 0xC014000B, - MD_NTSTATUS_WIN_STATUS_ACPI_ADDRESS_NOT_MAPPED = 0xC014000C, - MD_NTSTATUS_WIN_STATUS_ACPI_INVALID_EVENTTYPE = 0xC014000D, - MD_NTSTATUS_WIN_STATUS_ACPI_HANDLER_COLLISION = 0xC014000E, - MD_NTSTATUS_WIN_STATUS_ACPI_INVALID_DATA = 0xC014000F, - MD_NTSTATUS_WIN_STATUS_ACPI_INVALID_REGION = 0xC0140010, - MD_NTSTATUS_WIN_STATUS_ACPI_INVALID_ACCESS_SIZE = 0xC0140011, - MD_NTSTATUS_WIN_STATUS_ACPI_ACQUIRE_GLOBAL_LOCK = 0xC0140012, - MD_NTSTATUS_WIN_STATUS_ACPI_ALREADY_INITIALIZED = 0xC0140013, - MD_NTSTATUS_WIN_STATUS_ACPI_NOT_INITIALIZED = 0xC0140014, - MD_NTSTATUS_WIN_STATUS_ACPI_INVALID_MUTEX_LEVEL = 0xC0140015, - MD_NTSTATUS_WIN_STATUS_ACPI_MUTEX_NOT_OWNED = 0xC0140016, - MD_NTSTATUS_WIN_STATUS_ACPI_MUTEX_NOT_OWNER = 0xC0140017, - MD_NTSTATUS_WIN_STATUS_ACPI_RS_ACCESS = 0xC0140018, - MD_NTSTATUS_WIN_STATUS_ACPI_INVALID_TABLE = 0xC0140019, - MD_NTSTATUS_WIN_STATUS_ACPI_REG_HANDLER_FAILED = 0xC0140020, - MD_NTSTATUS_WIN_STATUS_ACPI_POWER_REQUEST_FAILED = 0xC0140021, - MD_NTSTATUS_WIN_STATUS_SXS_SECTION_NOT_FOUND = 0xC0150001, - MD_NTSTATUS_WIN_STATUS_SXS_CANT_GEN_ACTCTX = 0xC0150002, - MD_NTSTATUS_WIN_STATUS_SXS_INVALID_ACTCTXDATA_FORMAT = 0xC0150003, - MD_NTSTATUS_WIN_STATUS_SXS_ASSEMBLY_NOT_FOUND = 0xC0150004, - MD_NTSTATUS_WIN_STATUS_SXS_MANIFEST_FORMAT_ERROR = 0xC0150005, - MD_NTSTATUS_WIN_STATUS_SXS_MANIFEST_PARSE_ERROR = 0xC0150006, - MD_NTSTATUS_WIN_STATUS_SXS_ACTIVATION_CONTEXT_DISABLED = 0xC0150007, - MD_NTSTATUS_WIN_STATUS_SXS_KEY_NOT_FOUND = 0xC0150008, - MD_NTSTATUS_WIN_STATUS_SXS_VERSION_CONFLICT = 0xC0150009, - MD_NTSTATUS_WIN_STATUS_SXS_WRONG_SECTION_TYPE = 0xC015000A, - MD_NTSTATUS_WIN_STATUS_SXS_THREAD_QUERIES_DISABLED = 0xC015000B, - MD_NTSTATUS_WIN_STATUS_SXS_ASSEMBLY_MISSING = 0xC015000C, - MD_NTSTATUS_WIN_STATUS_SXS_PROCESS_DEFAULT_ALREADY_SET = 0xC015000E, - MD_NTSTATUS_WIN_STATUS_SXS_EARLY_DEACTIVATION = 0xC015000F, - MD_NTSTATUS_WIN_STATUS_SXS_INVALID_DEACTIVATION = 0xC0150010, - MD_NTSTATUS_WIN_STATUS_SXS_MULTIPLE_DEACTIVATION = 0xC0150011, - MD_NTSTATUS_WIN_STATUS_SXS_SYSTEM_DEFAULT_ACTIVATION_CONTEXT_EMPTY = 0xC0150012, - MD_NTSTATUS_WIN_STATUS_SXS_PROCESS_TERMINATION_REQUESTED = 0xC0150013, - MD_NTSTATUS_WIN_STATUS_SXS_CORRUPT_ACTIVATION_STACK = 0xC0150014, - MD_NTSTATUS_WIN_STATUS_SXS_CORRUPTION = 0xC0150015, - MD_NTSTATUS_WIN_STATUS_SXS_INVALID_IDENTITY_ATTRIBUTE_VALUE = 0xC0150016, - MD_NTSTATUS_WIN_STATUS_SXS_INVALID_IDENTITY_ATTRIBUTE_NAME = 0xC0150017, - MD_NTSTATUS_WIN_STATUS_SXS_IDENTITY_DUPLICATE_ATTRIBUTE = 0xC0150018, - MD_NTSTATUS_WIN_STATUS_SXS_IDENTITY_PARSE_ERROR = 0xC0150019, - MD_NTSTATUS_WIN_STATUS_SXS_COMPONENT_STORE_CORRUPT = 0xC015001A, - MD_NTSTATUS_WIN_STATUS_SXS_FILE_HASH_MISMATCH = 0xC015001B, - MD_NTSTATUS_WIN_STATUS_SXS_MANIFEST_IDENTITY_SAME_BUT_CONTENTS_DIFFERENT = 0xC015001C, - MD_NTSTATUS_WIN_STATUS_SXS_IDENTITIES_DIFFERENT = 0xC015001D, - MD_NTSTATUS_WIN_STATUS_SXS_ASSEMBLY_IS_NOT_A_DEPLOYMENT = 0xC015001E, - MD_NTSTATUS_WIN_STATUS_SXS_FILE_NOT_PART_OF_ASSEMBLY = 0xC015001F, - MD_NTSTATUS_WIN_STATUS_ADVANCED_INSTALLER_FAILED = 0xC0150020, - MD_NTSTATUS_WIN_STATUS_XML_ENCODING_MISMATCH = 0xC0150021, - MD_NTSTATUS_WIN_STATUS_SXS_MANIFEST_TOO_BIG = 0xC0150022, - MD_NTSTATUS_WIN_STATUS_SXS_SETTING_NOT_REGISTERED = 0xC0150023, - MD_NTSTATUS_WIN_STATUS_SXS_TRANSACTION_CLOSURE_INCOMPLETE = 0xC0150024, - MD_NTSTATUS_WIN_STATUS_SMI_PRIMITIVE_INSTALLER_FAILED = 0xC0150025, - MD_NTSTATUS_WIN_STATUS_GENERIC_COMMAND_FAILED = 0xC0150026, - MD_NTSTATUS_WIN_STATUS_SXS_FILE_HASH_MISSING = 0xC0150027, - MD_NTSTATUS_WIN_STATUS_TRANSACTIONAL_CONFLICT = 0xC0190001, - MD_NTSTATUS_WIN_STATUS_INVALID_TRANSACTION = 0xC0190002, - MD_NTSTATUS_WIN_STATUS_TRANSACTION_NOT_ACTIVE = 0xC0190003, - MD_NTSTATUS_WIN_STATUS_TM_INITIALIZATION_FAILED = 0xC0190004, - MD_NTSTATUS_WIN_STATUS_RM_NOT_ACTIVE = 0xC0190005, - MD_NTSTATUS_WIN_STATUS_RM_METADATA_CORRUPT = 0xC0190006, - MD_NTSTATUS_WIN_STATUS_TRANSACTION_NOT_JOINED = 0xC0190007, - MD_NTSTATUS_WIN_STATUS_DIRECTORY_NOT_RM = 0xC0190008, - MD_NTSTATUS_WIN_STATUS_TRANSACTIONS_UNSUPPORTED_REMOTE = 0xC019000A, - MD_NTSTATUS_WIN_STATUS_LOG_RESIZE_INVALID_SIZE = 0xC019000B, - MD_NTSTATUS_WIN_STATUS_REMOTE_FILE_VERSION_MISMATCH = 0xC019000C, - MD_NTSTATUS_WIN_STATUS_CRM_PROTOCOL_ALREADY_EXISTS = 0xC019000F, - MD_NTSTATUS_WIN_STATUS_TRANSACTION_PROPAGATION_FAILED = 0xC0190010, - MD_NTSTATUS_WIN_STATUS_CRM_PROTOCOL_NOT_FOUND = 0xC0190011, - MD_NTSTATUS_WIN_STATUS_TRANSACTION_SUPERIOR_EXISTS = 0xC0190012, - MD_NTSTATUS_WIN_STATUS_TRANSACTION_REQUEST_NOT_VALID = 0xC0190013, - MD_NTSTATUS_WIN_STATUS_TRANSACTION_NOT_REQUESTED = 0xC0190014, - MD_NTSTATUS_WIN_STATUS_TRANSACTION_ALREADY_ABORTED = 0xC0190015, - MD_NTSTATUS_WIN_STATUS_TRANSACTION_ALREADY_COMMITTED = 0xC0190016, - MD_NTSTATUS_WIN_STATUS_TRANSACTION_INVALID_MARSHALL_BUFFER = 0xC0190017, - MD_NTSTATUS_WIN_STATUS_CURRENT_TRANSACTION_NOT_VALID = 0xC0190018, - MD_NTSTATUS_WIN_STATUS_LOG_GROWTH_FAILED = 0xC0190019, - MD_NTSTATUS_WIN_STATUS_OBJECT_NO_LONGER_EXISTS = 0xC0190021, - MD_NTSTATUS_WIN_STATUS_STREAM_MINIVERSION_NOT_FOUND = 0xC0190022, - MD_NTSTATUS_WIN_STATUS_STREAM_MINIVERSION_NOT_VALID = 0xC0190023, - MD_NTSTATUS_WIN_STATUS_MINIVERSION_INACCESSIBLE_FROM_SPECIFIED_TRANSACTION = 0xC0190024, - MD_NTSTATUS_WIN_STATUS_CANT_OPEN_MINIVERSION_WITH_MODIFY_INTENT = 0xC0190025, - MD_NTSTATUS_WIN_STATUS_CANT_CREATE_MORE_STREAM_MINIVERSIONS = 0xC0190026, - MD_NTSTATUS_WIN_STATUS_HANDLE_NO_LONGER_VALID = 0xC0190028, - MD_NTSTATUS_WIN_STATUS_LOG_CORRUPTION_DETECTED = 0xC0190030, - MD_NTSTATUS_WIN_STATUS_RM_DISCONNECTED = 0xC0190032, - MD_NTSTATUS_WIN_STATUS_ENLISTMENT_NOT_SUPERIOR = 0xC0190033, - MD_NTSTATUS_WIN_STATUS_FILE_IDENTITY_NOT_PERSISTENT = 0xC0190036, - MD_NTSTATUS_WIN_STATUS_CANT_BREAK_TRANSACTIONAL_DEPENDENCY = 0xC0190037, - MD_NTSTATUS_WIN_STATUS_CANT_CROSS_RM_BOUNDARY = 0xC0190038, - MD_NTSTATUS_WIN_STATUS_TXF_DIR_NOT_EMPTY = 0xC0190039, - MD_NTSTATUS_WIN_STATUS_INDOUBT_TRANSACTIONS_EXIST = 0xC019003A, - MD_NTSTATUS_WIN_STATUS_TM_VOLATILE = 0xC019003B, - MD_NTSTATUS_WIN_STATUS_ROLLBACK_TIMER_EXPIRED = 0xC019003C, - MD_NTSTATUS_WIN_STATUS_TXF_ATTRIBUTE_CORRUPT = 0xC019003D, - MD_NTSTATUS_WIN_STATUS_EFS_NOT_ALLOWED_IN_TRANSACTION = 0xC019003E, - MD_NTSTATUS_WIN_STATUS_TRANSACTIONAL_OPEN_NOT_ALLOWED = 0xC019003F, - MD_NTSTATUS_WIN_STATUS_TRANSACTED_MAPPING_UNSUPPORTED_REMOTE = 0xC0190040, - MD_NTSTATUS_WIN_STATUS_TRANSACTION_REQUIRED_PROMOTION = 0xC0190043, - MD_NTSTATUS_WIN_STATUS_CANNOT_EXECUTE_FILE_IN_TRANSACTION = 0xC0190044, - MD_NTSTATUS_WIN_STATUS_TRANSACTIONS_NOT_FROZEN = 0xC0190045, - MD_NTSTATUS_WIN_STATUS_TRANSACTION_FREEZE_IN_PROGRESS = 0xC0190046, - MD_NTSTATUS_WIN_STATUS_NOT_SNAPSHOT_VOLUME = 0xC0190047, - MD_NTSTATUS_WIN_STATUS_NO_SAVEPOINT_WITH_OPEN_FILES = 0xC0190048, - MD_NTSTATUS_WIN_STATUS_SPARSE_NOT_ALLOWED_IN_TRANSACTION = 0xC0190049, - MD_NTSTATUS_WIN_STATUS_TM_IDENTITY_MISMATCH = 0xC019004A, - MD_NTSTATUS_WIN_STATUS_FLOATED_SECTION = 0xC019004B, - MD_NTSTATUS_WIN_STATUS_CANNOT_ACCEPT_TRANSACTED_WORK = 0xC019004C, - MD_NTSTATUS_WIN_STATUS_CANNOT_ABORT_TRANSACTIONS = 0xC019004D, - MD_NTSTATUS_WIN_STATUS_TRANSACTION_NOT_FOUND = 0xC019004E, - MD_NTSTATUS_WIN_STATUS_RESOURCEMANAGER_NOT_FOUND = 0xC019004F, - MD_NTSTATUS_WIN_STATUS_ENLISTMENT_NOT_FOUND = 0xC0190050, - MD_NTSTATUS_WIN_STATUS_TRANSACTIONMANAGER_NOT_FOUND = 0xC0190051, - MD_NTSTATUS_WIN_STATUS_TRANSACTIONMANAGER_NOT_ONLINE = 0xC0190052, - MD_NTSTATUS_WIN_STATUS_TRANSACTIONMANAGER_RECOVERY_NAME_COLLISION = 0xC0190053, - MD_NTSTATUS_WIN_STATUS_TRANSACTION_NOT_ROOT = 0xC0190054, - MD_NTSTATUS_WIN_STATUS_TRANSACTION_OBJECT_EXPIRED = 0xC0190055, - MD_NTSTATUS_WIN_STATUS_COMPRESSION_NOT_ALLOWED_IN_TRANSACTION = 0xC0190056, - MD_NTSTATUS_WIN_STATUS_TRANSACTION_RESPONSE_NOT_ENLISTED = 0xC0190057, - MD_NTSTATUS_WIN_STATUS_TRANSACTION_RECORD_TOO_LONG = 0xC0190058, - MD_NTSTATUS_WIN_STATUS_NO_LINK_TRACKING_IN_TRANSACTION = 0xC0190059, - MD_NTSTATUS_WIN_STATUS_OPERATION_NOT_SUPPORTED_IN_TRANSACTION = 0xC019005A, - MD_NTSTATUS_WIN_STATUS_TRANSACTION_INTEGRITY_VIOLATED = 0xC019005B, - MD_NTSTATUS_WIN_STATUS_TRANSACTIONMANAGER_IDENTITY_MISMATCH = 0xC019005C, - MD_NTSTATUS_WIN_STATUS_RM_CANNOT_BE_FROZEN_FOR_SNAPSHOT = 0xC019005D, - MD_NTSTATUS_WIN_STATUS_TRANSACTION_MUST_WRITETHROUGH = 0xC019005E, - MD_NTSTATUS_WIN_STATUS_TRANSACTION_NO_SUPERIOR = 0xC019005F, - MD_NTSTATUS_WIN_STATUS_EXPIRED_HANDLE = 0xC0190060, - MD_NTSTATUS_WIN_STATUS_TRANSACTION_NOT_ENLISTED = 0xC0190061, - MD_NTSTATUS_WIN_STATUS_LOG_SECTOR_INVALID = 0xC01A0001, - MD_NTSTATUS_WIN_STATUS_LOG_SECTOR_PARITY_INVALID = 0xC01A0002, - MD_NTSTATUS_WIN_STATUS_LOG_SECTOR_REMAPPED = 0xC01A0003, - MD_NTSTATUS_WIN_STATUS_LOG_BLOCK_INCOMPLETE = 0xC01A0004, - MD_NTSTATUS_WIN_STATUS_LOG_INVALID_RANGE = 0xC01A0005, - MD_NTSTATUS_WIN_STATUS_LOG_BLOCKS_EXHAUSTED = 0xC01A0006, - MD_NTSTATUS_WIN_STATUS_LOG_READ_CONTEXT_INVALID = 0xC01A0007, - MD_NTSTATUS_WIN_STATUS_LOG_RESTART_INVALID = 0xC01A0008, - MD_NTSTATUS_WIN_STATUS_LOG_BLOCK_VERSION = 0xC01A0009, - MD_NTSTATUS_WIN_STATUS_LOG_BLOCK_INVALID = 0xC01A000A, - MD_NTSTATUS_WIN_STATUS_LOG_READ_MODE_INVALID = 0xC01A000B, - MD_NTSTATUS_WIN_STATUS_LOG_METADATA_CORRUPT = 0xC01A000D, - MD_NTSTATUS_WIN_STATUS_LOG_METADATA_INVALID = 0xC01A000E, - MD_NTSTATUS_WIN_STATUS_LOG_METADATA_INCONSISTENT = 0xC01A000F, - MD_NTSTATUS_WIN_STATUS_LOG_RESERVATION_INVALID = 0xC01A0010, - MD_NTSTATUS_WIN_STATUS_LOG_CANT_DELETE = 0xC01A0011, - MD_NTSTATUS_WIN_STATUS_LOG_CONTAINER_LIMIT_EXCEEDED = 0xC01A0012, - MD_NTSTATUS_WIN_STATUS_LOG_START_OF_LOG = 0xC01A0013, - MD_NTSTATUS_WIN_STATUS_LOG_POLICY_ALREADY_INSTALLED = 0xC01A0014, - MD_NTSTATUS_WIN_STATUS_LOG_POLICY_NOT_INSTALLED = 0xC01A0015, - MD_NTSTATUS_WIN_STATUS_LOG_POLICY_INVALID = 0xC01A0016, - MD_NTSTATUS_WIN_STATUS_LOG_POLICY_CONFLICT = 0xC01A0017, - MD_NTSTATUS_WIN_STATUS_LOG_PINNED_ARCHIVE_TAIL = 0xC01A0018, - MD_NTSTATUS_WIN_STATUS_LOG_RECORD_NONEXISTENT = 0xC01A0019, - MD_NTSTATUS_WIN_STATUS_LOG_RECORDS_RESERVED_INVALID = 0xC01A001A, - MD_NTSTATUS_WIN_STATUS_LOG_SPACE_RESERVED_INVALID = 0xC01A001B, - MD_NTSTATUS_WIN_STATUS_LOG_TAIL_INVALID = 0xC01A001C, - MD_NTSTATUS_WIN_STATUS_LOG_FULL = 0xC01A001D, - MD_NTSTATUS_WIN_STATUS_LOG_MULTIPLEXED = 0xC01A001E, - MD_NTSTATUS_WIN_STATUS_LOG_DEDICATED = 0xC01A001F, - MD_NTSTATUS_WIN_STATUS_LOG_ARCHIVE_NOT_IN_PROGRESS = 0xC01A0020, - MD_NTSTATUS_WIN_STATUS_LOG_ARCHIVE_IN_PROGRESS = 0xC01A0021, - MD_NTSTATUS_WIN_STATUS_LOG_EPHEMERAL = 0xC01A0022, - MD_NTSTATUS_WIN_STATUS_LOG_NOT_ENOUGH_CONTAINERS = 0xC01A0023, - MD_NTSTATUS_WIN_STATUS_LOG_CLIENT_ALREADY_REGISTERED = 0xC01A0024, - MD_NTSTATUS_WIN_STATUS_LOG_CLIENT_NOT_REGISTERED = 0xC01A0025, - MD_NTSTATUS_WIN_STATUS_LOG_FULL_HANDLER_IN_PROGRESS = 0xC01A0026, - MD_NTSTATUS_WIN_STATUS_LOG_CONTAINER_READ_FAILED = 0xC01A0027, - MD_NTSTATUS_WIN_STATUS_LOG_CONTAINER_WRITE_FAILED = 0xC01A0028, - MD_NTSTATUS_WIN_STATUS_LOG_CONTAINER_OPEN_FAILED = 0xC01A0029, - MD_NTSTATUS_WIN_STATUS_LOG_CONTAINER_STATE_INVALID = 0xC01A002A, - MD_NTSTATUS_WIN_STATUS_LOG_STATE_INVALID = 0xC01A002B, - MD_NTSTATUS_WIN_STATUS_LOG_PINNED = 0xC01A002C, - MD_NTSTATUS_WIN_STATUS_LOG_METADATA_FLUSH_FAILED = 0xC01A002D, - MD_NTSTATUS_WIN_STATUS_LOG_INCONSISTENT_SECURITY = 0xC01A002E, - MD_NTSTATUS_WIN_STATUS_LOG_APPENDED_FLUSH_FAILED = 0xC01A002F, - MD_NTSTATUS_WIN_STATUS_LOG_PINNED_RESERVATION = 0xC01A0030, - MD_NTSTATUS_WIN_STATUS_VIDEO_HUNG_DISPLAY_DRIVER_THREAD = 0xC01B00EA, - MD_NTSTATUS_WIN_STATUS_FLT_NO_HANDLER_DEFINED = 0xC01C0001, - MD_NTSTATUS_WIN_STATUS_FLT_CONTEXT_ALREADY_DEFINED = 0xC01C0002, - MD_NTSTATUS_WIN_STATUS_FLT_INVALID_ASYNCHRONOUS_REQUEST = 0xC01C0003, - MD_NTSTATUS_WIN_STATUS_FLT_DISALLOW_FAST_IO = 0xC01C0004, - MD_NTSTATUS_WIN_STATUS_FLT_INVALID_NAME_REQUEST = 0xC01C0005, - MD_NTSTATUS_WIN_STATUS_FLT_NOT_SAFE_TO_POST_OPERATION = 0xC01C0006, - MD_NTSTATUS_WIN_STATUS_FLT_NOT_INITIALIZED = 0xC01C0007, - MD_NTSTATUS_WIN_STATUS_FLT_FILTER_NOT_READY = 0xC01C0008, - MD_NTSTATUS_WIN_STATUS_FLT_POST_OPERATION_CLEANUP = 0xC01C0009, - MD_NTSTATUS_WIN_STATUS_FLT_INTERNAL_ERROR = 0xC01C000A, - MD_NTSTATUS_WIN_STATUS_FLT_DELETING_OBJECT = 0xC01C000B, - MD_NTSTATUS_WIN_STATUS_FLT_MUST_BE_NONPAGED_POOL = 0xC01C000C, - MD_NTSTATUS_WIN_STATUS_FLT_DUPLICATE_ENTRY = 0xC01C000D, - MD_NTSTATUS_WIN_STATUS_FLT_CBDQ_DISABLED = 0xC01C000E, - MD_NTSTATUS_WIN_STATUS_FLT_DO_NOT_ATTACH = 0xC01C000F, - MD_NTSTATUS_WIN_STATUS_FLT_DO_NOT_DETACH = 0xC01C0010, - MD_NTSTATUS_WIN_STATUS_FLT_INSTANCE_ALTITUDE_COLLISION = 0xC01C0011, - MD_NTSTATUS_WIN_STATUS_FLT_INSTANCE_NAME_COLLISION = 0xC01C0012, - MD_NTSTATUS_WIN_STATUS_FLT_FILTER_NOT_FOUND = 0xC01C0013, - MD_NTSTATUS_WIN_STATUS_FLT_VOLUME_NOT_FOUND = 0xC01C0014, - MD_NTSTATUS_WIN_STATUS_FLT_INSTANCE_NOT_FOUND = 0xC01C0015, - MD_NTSTATUS_WIN_STATUS_FLT_CONTEXT_ALLOCATION_NOT_FOUND = 0xC01C0016, - MD_NTSTATUS_WIN_STATUS_FLT_INVALID_CONTEXT_REGISTRATION = 0xC01C0017, - MD_NTSTATUS_WIN_STATUS_FLT_NAME_CACHE_MISS = 0xC01C0018, - MD_NTSTATUS_WIN_STATUS_FLT_NO_DEVICE_OBJECT = 0xC01C0019, - MD_NTSTATUS_WIN_STATUS_FLT_VOLUME_ALREADY_MOUNTED = 0xC01C001A, - MD_NTSTATUS_WIN_STATUS_FLT_ALREADY_ENLISTED = 0xC01C001B, - MD_NTSTATUS_WIN_STATUS_FLT_CONTEXT_ALREADY_LINKED = 0xC01C001C, - MD_NTSTATUS_WIN_STATUS_FLT_NO_WAITER_FOR_REPLY = 0xC01C0020, - MD_NTSTATUS_WIN_STATUS_FLT_REGISTRATION_BUSY = 0xC01C0023, - MD_NTSTATUS_WIN_STATUS_MONITOR_NO_DESCRIPTOR = 0xC01D0001, - MD_NTSTATUS_WIN_STATUS_MONITOR_UNKNOWN_DESCRIPTOR_FORMAT = 0xC01D0002, - MD_NTSTATUS_WIN_STATUS_MONITOR_INVALID_DESCRIPTOR_CHECKSUM = 0xC01D0003, - MD_NTSTATUS_WIN_STATUS_MONITOR_INVALID_STANDARD_TIMING_BLOCK = 0xC01D0004, - MD_NTSTATUS_WIN_STATUS_MONITOR_WMI_DATABLOCK_REGISTRATION_FAILED = 0xC01D0005, - MD_NTSTATUS_WIN_STATUS_MONITOR_INVALID_SERIAL_NUMBER_MONDSC_BLOCK = 0xC01D0006, - MD_NTSTATUS_WIN_STATUS_MONITOR_INVALID_USER_FRIENDLY_MONDSC_BLOCK = 0xC01D0007, - MD_NTSTATUS_WIN_STATUS_MONITOR_NO_MORE_DESCRIPTOR_DATA = 0xC01D0008, - MD_NTSTATUS_WIN_STATUS_MONITOR_INVALID_DETAILED_TIMING_BLOCK = 0xC01D0009, - MD_NTSTATUS_WIN_STATUS_MONITOR_INVALID_MANUFACTURE_DATE = 0xC01D000A, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_NOT_EXCLUSIVE_MODE_OWNER = 0xC01E0000, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INSUFFICIENT_DMA_BUFFER = 0xC01E0001, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_DISPLAY_ADAPTER = 0xC01E0002, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_ADAPTER_WAS_RESET = 0xC01E0003, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_DRIVER_MODEL = 0xC01E0004, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_PRESENT_MODE_CHANGED = 0xC01E0005, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_PRESENT_OCCLUDED = 0xC01E0006, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_PRESENT_DENIED = 0xC01E0007, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_CANNOTCOLORCONVERT = 0xC01E0008, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_DRIVER_MISMATCH = 0xC01E0009, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_PRESENT_REDIRECTION_DISABLED = 0xC01E000B, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_PRESENT_UNOCCLUDED = 0xC01E000C, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_WINDOWDC_NOT_AVAILABLE = 0xC01E000D, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_WINDOWLESS_PRESENT_DISABLED = 0xC01E000E, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_NO_VIDEO_MEMORY = 0xC01E0100, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_CANT_LOCK_MEMORY = 0xC01E0101, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_ALLOCATION_BUSY = 0xC01E0102, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_TOO_MANY_REFERENCES = 0xC01E0103, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_TRY_AGAIN_LATER = 0xC01E0104, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_TRY_AGAIN_NOW = 0xC01E0105, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_ALLOCATION_INVALID = 0xC01E0106, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_UNSWIZZLING_APERTURE_UNAVAILABLE = 0xC01E0107, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_UNSWIZZLING_APERTURE_UNSUPPORTED = 0xC01E0108, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_CANT_EVICT_PINNED_ALLOCATION = 0xC01E0109, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_ALLOCATION_USAGE = 0xC01E0110, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_CANT_RENDER_LOCKED_ALLOCATION = 0xC01E0111, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_ALLOCATION_CLOSED = 0xC01E0112, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_ALLOCATION_INSTANCE = 0xC01E0113, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_ALLOCATION_HANDLE = 0xC01E0114, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_WRONG_ALLOCATION_DEVICE = 0xC01E0115, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_ALLOCATION_CONTENT_LOST = 0xC01E0116, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_GPU_EXCEPTION_ON_DEVICE = 0xC01E0200, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_VIDPN_TOPOLOGY = 0xC01E0300, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_VIDPN_TOPOLOGY_NOT_SUPPORTED = 0xC01E0301, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_VIDPN_TOPOLOGY_CURRENTLY_NOT_SUPPORTED = 0xC01E0302, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_VIDPN = 0xC01E0303, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_VIDEO_PRESENT_SOURCE = 0xC01E0304, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_VIDEO_PRESENT_TARGET = 0xC01E0305, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_VIDPN_MODALITY_NOT_SUPPORTED = 0xC01E0306, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_VIDPN_SOURCEMODESET = 0xC01E0308, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_VIDPN_TARGETMODESET = 0xC01E0309, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_FREQUENCY = 0xC01E030A, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_ACTIVE_REGION = 0xC01E030B, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_TOTAL_REGION = 0xC01E030C, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_VIDEO_PRESENT_SOURCE_MODE = 0xC01E0310, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_VIDEO_PRESENT_TARGET_MODE = 0xC01E0311, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_PINNED_MODE_MUST_REMAIN_IN_SET = 0xC01E0312, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_PATH_ALREADY_IN_TOPOLOGY = 0xC01E0313, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_MODE_ALREADY_IN_MODESET = 0xC01E0314, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_VIDEOPRESENTSOURCESET = 0xC01E0315, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_VIDEOPRESENTTARGETSET = 0xC01E0316, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_SOURCE_ALREADY_IN_SET = 0xC01E0317, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_TARGET_ALREADY_IN_SET = 0xC01E0318, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_VIDPN_PRESENT_PATH = 0xC01E0319, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_NO_RECOMMENDED_VIDPN_TOPOLOGY = 0xC01E031A, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_MONITOR_FREQUENCYRANGESET = 0xC01E031B, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_MONITOR_FREQUENCYRANGE = 0xC01E031C, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_FREQUENCYRANGE_NOT_IN_SET = 0xC01E031D, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_FREQUENCYRANGE_ALREADY_IN_SET = 0xC01E031F, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_STALE_MODESET = 0xC01E0320, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_MONITOR_SOURCEMODESET = 0xC01E0321, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_MONITOR_SOURCE_MODE = 0xC01E0322, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_NO_RECOMMENDED_FUNCTIONAL_VIDPN = 0xC01E0323, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_MODE_ID_MUST_BE_UNIQUE = 0xC01E0324, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_EMPTY_ADAPTER_MONITOR_MODE_SUPPORT_INTERSECTION = 0xC01E0325, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_VIDEO_PRESENT_TARGETS_LESS_THAN_SOURCES = 0xC01E0326, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_PATH_NOT_IN_TOPOLOGY = 0xC01E0327, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_ADAPTER_MUST_HAVE_AT_LEAST_ONE_SOURCE = 0xC01E0328, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_ADAPTER_MUST_HAVE_AT_LEAST_ONE_TARGET = 0xC01E0329, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_MONITORDESCRIPTORSET = 0xC01E032A, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_MONITORDESCRIPTOR = 0xC01E032B, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_MONITORDESCRIPTOR_NOT_IN_SET = 0xC01E032C, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_MONITORDESCRIPTOR_ALREADY_IN_SET = 0xC01E032D, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_MONITORDESCRIPTOR_ID_MUST_BE_UNIQUE = 0xC01E032E, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_VIDPN_TARGET_SUBSET_TYPE = 0xC01E032F, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_RESOURCES_NOT_RELATED = 0xC01E0330, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_SOURCE_ID_MUST_BE_UNIQUE = 0xC01E0331, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_TARGET_ID_MUST_BE_UNIQUE = 0xC01E0332, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_NO_AVAILABLE_VIDPN_TARGET = 0xC01E0333, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_MONITOR_COULD_NOT_BE_ASSOCIATED_WITH_ADAPTER = 0xC01E0334, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_NO_VIDPNMGR = 0xC01E0335, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_NO_ACTIVE_VIDPN = 0xC01E0336, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_STALE_VIDPN_TOPOLOGY = 0xC01E0337, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_MONITOR_NOT_CONNECTED = 0xC01E0338, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_SOURCE_NOT_IN_TOPOLOGY = 0xC01E0339, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_PRIMARYSURFACE_SIZE = 0xC01E033A, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_VISIBLEREGION_SIZE = 0xC01E033B, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_STRIDE = 0xC01E033C, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_PIXELFORMAT = 0xC01E033D, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_COLORBASIS = 0xC01E033E, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_PIXELVALUEACCESSMODE = 0xC01E033F, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_TARGET_NOT_IN_TOPOLOGY = 0xC01E0340, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_NO_DISPLAY_MODE_MANAGEMENT_SUPPORT = 0xC01E0341, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_VIDPN_SOURCE_IN_USE = 0xC01E0342, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_CANT_ACCESS_ACTIVE_VIDPN = 0xC01E0343, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_PATH_IMPORTANCE_ORDINAL = 0xC01E0344, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_PATH_CONTENT_GEOMETRY_TRANSFORMATION = 0xC01E0345, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_PATH_CONTENT_GEOMETRY_TRANSFORMATION_NOT_SUPPORTED = 0xC01E0346, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_GAMMA_RAMP = 0xC01E0347, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_GAMMA_RAMP_NOT_SUPPORTED = 0xC01E0348, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_MULTISAMPLING_NOT_SUPPORTED = 0xC01E0349, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_MODE_NOT_IN_MODESET = 0xC01E034A, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_VIDPN_TOPOLOGY_RECOMMENDATION_REASON = 0xC01E034D, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_PATH_CONTENT_TYPE = 0xC01E034E, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_COPYPROTECTION_TYPE = 0xC01E034F, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_UNASSIGNED_MODESET_ALREADY_EXISTS = 0xC01E0350, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_SCANLINE_ORDERING = 0xC01E0352, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_TOPOLOGY_CHANGES_NOT_ALLOWED = 0xC01E0353, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_NO_AVAILABLE_IMPORTANCE_ORDINALS = 0xC01E0354, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INCOMPATIBLE_PRIVATE_FORMAT = 0xC01E0355, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_MODE_PRUNING_ALGORITHM = 0xC01E0356, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_MONITOR_CAPABILITY_ORIGIN = 0xC01E0357, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_MONITOR_FREQUENCYRANGE_CONSTRAINT = 0xC01E0358, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_MAX_NUM_PATHS_REACHED = 0xC01E0359, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_CANCEL_VIDPN_TOPOLOGY_AUGMENTATION = 0xC01E035A, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_CLIENT_TYPE = 0xC01E035B, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_CLIENTVIDPN_NOT_SET = 0xC01E035C, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_SPECIFIED_CHILD_ALREADY_CONNECTED = 0xC01E0400, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_CHILD_DESCRIPTOR_NOT_SUPPORTED = 0xC01E0401, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_NOT_A_LINKED_ADAPTER = 0xC01E0430, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_LEADLINK_NOT_ENUMERATED = 0xC01E0431, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_CHAINLINKS_NOT_ENUMERATED = 0xC01E0432, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_ADAPTER_CHAIN_NOT_READY = 0xC01E0433, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_CHAINLINKS_NOT_STARTED = 0xC01E0434, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_CHAINLINKS_NOT_POWERED_ON = 0xC01E0435, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INCONSISTENT_DEVICE_LINK_STATE = 0xC01E0436, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_NOT_POST_DEVICE_DRIVER = 0xC01E0438, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_ADAPTER_ACCESS_NOT_EXCLUDED = 0xC01E043B, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_OPM_NOT_SUPPORTED = 0xC01E0500, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_COPP_NOT_SUPPORTED = 0xC01E0501, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_UAB_NOT_SUPPORTED = 0xC01E0502, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_OPM_INVALID_ENCRYPTED_PARAMETERS = 0xC01E0503, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_OPM_NO_PROTECTED_OUTPUTS_EXIST = 0xC01E0505, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_OPM_INTERNAL_ERROR = 0xC01E050B, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_OPM_INVALID_HANDLE = 0xC01E050C, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_PVP_INVALID_CERTIFICATE_LENGTH = 0xC01E050E, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_OPM_SPANNING_MODE_ENABLED = 0xC01E050F, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_OPM_THEATER_MODE_ENABLED = 0xC01E0510, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_PVP_HFS_FAILED = 0xC01E0511, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_OPM_INVALID_SRM = 0xC01E0512, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_OPM_OUTPUT_DOES_NOT_SUPPORT_HDCP = 0xC01E0513, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_OPM_OUTPUT_DOES_NOT_SUPPORT_ACP = 0xC01E0514, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_OPM_OUTPUT_DOES_NOT_SUPPORT_CGMSA = 0xC01E0515, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_OPM_HDCP_SRM_NEVER_SET = 0xC01E0516, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_OPM_RESOLUTION_TOO_HIGH = 0xC01E0517, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_OPM_ALL_HDCP_HARDWARE_ALREADY_IN_USE = 0xC01E0518, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_OPM_PROTECTED_OUTPUT_NO_LONGER_EXISTS = 0xC01E051A, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_OPM_PROTECTED_OUTPUT_DOES_NOT_HAVE_COPP_SEMANTICS = 0xC01E051C, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_OPM_INVALID_INFORMATION_REQUEST = 0xC01E051D, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_OPM_DRIVER_INTERNAL_ERROR = 0xC01E051E, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_OPM_PROTECTED_OUTPUT_DOES_NOT_HAVE_OPM_SEMANTICS = 0xC01E051F, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_OPM_SIGNALING_NOT_SUPPORTED = 0xC01E0520, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_OPM_INVALID_CONFIGURATION_REQUEST = 0xC01E0521, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_I2C_NOT_SUPPORTED = 0xC01E0580, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_I2C_DEVICE_DOES_NOT_EXIST = 0xC01E0581, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_I2C_ERROR_TRANSMITTING_DATA = 0xC01E0582, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_I2C_ERROR_RECEIVING_DATA = 0xC01E0583, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_DDCCI_VCP_NOT_SUPPORTED = 0xC01E0584, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_DDCCI_INVALID_DATA = 0xC01E0585, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_DDCCI_MONITOR_RETURNED_INVALID_TIMING_STATUS_BYTE = 0xC01E0586, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_DDCCI_INVALID_CAPABILITIES_STRING = 0xC01E0587, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_MCA_INTERNAL_ERROR = 0xC01E0588, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_DDCCI_INVALID_MESSAGE_COMMAND = 0xC01E0589, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_DDCCI_INVALID_MESSAGE_LENGTH = 0xC01E058A, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_DDCCI_INVALID_MESSAGE_CHECKSUM = 0xC01E058B, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_PHYSICAL_MONITOR_HANDLE = 0xC01E058C, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_MONITOR_NO_LONGER_EXISTS = 0xC01E058D, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_ONLY_CONSOLE_SESSION_SUPPORTED = 0xC01E05E0, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_NO_DISPLAY_DEVICE_CORRESPONDS_TO_NAME = 0xC01E05E1, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_DISPLAY_DEVICE_NOT_ATTACHED_TO_DESKTOP = 0xC01E05E2, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_MIRRORING_DEVICES_NOT_SUPPORTED = 0xC01E05E3, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INVALID_POINTER = 0xC01E05E4, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_NO_MONITORS_CORRESPOND_TO_DISPLAY_DEVICE = 0xC01E05E5, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_PARAMETER_ARRAY_TOO_SMALL = 0xC01E05E6, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_INTERNAL_ERROR = 0xC01E05E7, - MD_NTSTATUS_WIN_STATUS_GRAPHICS_SESSION_TYPE_CHANGE_IN_PROGRESS = 0xC01E05E8, - MD_NTSTATUS_WIN_STATUS_FVE_LOCKED_VOLUME = 0xC0210000, - MD_NTSTATUS_WIN_STATUS_FVE_NOT_ENCRYPTED = 0xC0210001, - MD_NTSTATUS_WIN_STATUS_FVE_BAD_INFORMATION = 0xC0210002, - MD_NTSTATUS_WIN_STATUS_FVE_TOO_SMALL = 0xC0210003, - MD_NTSTATUS_WIN_STATUS_FVE_FAILED_WRONG_FS = 0xC0210004, - MD_NTSTATUS_WIN_STATUS_FVE_BAD_PARTITION_SIZE = 0xC0210005, - MD_NTSTATUS_WIN_STATUS_FVE_FS_NOT_EXTENDED = 0xC0210006, - MD_NTSTATUS_WIN_STATUS_FVE_FS_MOUNTED = 0xC0210007, - MD_NTSTATUS_WIN_STATUS_FVE_NO_LICENSE = 0xC0210008, - MD_NTSTATUS_WIN_STATUS_FVE_ACTION_NOT_ALLOWED = 0xC0210009, - MD_NTSTATUS_WIN_STATUS_FVE_BAD_DATA = 0xC021000A, - MD_NTSTATUS_WIN_STATUS_FVE_VOLUME_NOT_BOUND = 0xC021000B, - MD_NTSTATUS_WIN_STATUS_FVE_NOT_DATA_VOLUME = 0xC021000C, - MD_NTSTATUS_WIN_STATUS_FVE_CONV_READ_ERROR = 0xC021000D, - MD_NTSTATUS_WIN_STATUS_FVE_CONV_WRITE_ERROR = 0xC021000E, - MD_NTSTATUS_WIN_STATUS_FVE_OVERLAPPED_UPDATE = 0xC021000F, - MD_NTSTATUS_WIN_STATUS_FVE_FAILED_SECTOR_SIZE = 0xC0210010, - MD_NTSTATUS_WIN_STATUS_FVE_FAILED_AUTHENTICATION = 0xC0210011, - MD_NTSTATUS_WIN_STATUS_FVE_NOT_OS_VOLUME = 0xC0210012, - MD_NTSTATUS_WIN_STATUS_FVE_KEYFILE_NOT_FOUND = 0xC0210013, - MD_NTSTATUS_WIN_STATUS_FVE_KEYFILE_INVALID = 0xC0210014, - MD_NTSTATUS_WIN_STATUS_FVE_KEYFILE_NO_VMK = 0xC0210015, - MD_NTSTATUS_WIN_STATUS_FVE_TPM_DISABLED = 0xC0210016, - MD_NTSTATUS_WIN_STATUS_FVE_TPM_SRK_AUTH_NOT_ZERO = 0xC0210017, - MD_NTSTATUS_WIN_STATUS_FVE_TPM_INVALID_PCR = 0xC0210018, - MD_NTSTATUS_WIN_STATUS_FVE_TPM_NO_VMK = 0xC0210019, - MD_NTSTATUS_WIN_STATUS_FVE_PIN_INVALID = 0xC021001A, - MD_NTSTATUS_WIN_STATUS_FVE_AUTH_INVALID_APPLICATION = 0xC021001B, - MD_NTSTATUS_WIN_STATUS_FVE_AUTH_INVALID_CONFIG = 0xC021001C, - MD_NTSTATUS_WIN_STATUS_FVE_DEBUGGER_ENABLED = 0xC021001D, - MD_NTSTATUS_WIN_STATUS_FVE_DRY_RUN_FAILED = 0xC021001E, - MD_NTSTATUS_WIN_STATUS_FVE_BAD_METADATA_POINTER = 0xC021001F, - MD_NTSTATUS_WIN_STATUS_FVE_OLD_METADATA_COPY = 0xC0210020, - MD_NTSTATUS_WIN_STATUS_FVE_REBOOT_REQUIRED = 0xC0210021, - MD_NTSTATUS_WIN_STATUS_FVE_RAW_ACCESS = 0xC0210022, - MD_NTSTATUS_WIN_STATUS_FVE_RAW_BLOCKED = 0xC0210023, - MD_NTSTATUS_WIN_STATUS_FVE_NO_AUTOUNLOCK_MASTER_KEY = 0xC0210024, - MD_NTSTATUS_WIN_STATUS_FVE_MOR_FAILED = 0xC0210025, - MD_NTSTATUS_WIN_STATUS_FVE_NO_FEATURE_LICENSE = 0xC0210026, - MD_NTSTATUS_WIN_STATUS_FVE_POLICY_USER_DISABLE_RDV_NOT_ALLOWED = 0xC0210027, - MD_NTSTATUS_WIN_STATUS_FVE_CONV_RECOVERY_FAILED = 0xC0210028, - MD_NTSTATUS_WIN_STATUS_FVE_VIRTUALIZED_SPACE_TOO_BIG = 0xC0210029, - MD_NTSTATUS_WIN_STATUS_FVE_INVALID_DATUM_TYPE = 0xC021002A, - MD_NTSTATUS_WIN_STATUS_FVE_VOLUME_TOO_SMALL = 0xC0210030, - MD_NTSTATUS_WIN_STATUS_FVE_ENH_PIN_INVALID = 0xC0210031, - MD_NTSTATUS_WIN_STATUS_FVE_FULL_ENCRYPTION_NOT_ALLOWED_ON_TP_STORAGE = 0xC0210032, - MD_NTSTATUS_WIN_STATUS_FVE_WIPE_NOT_ALLOWED_ON_TP_STORAGE = 0xC0210033, - MD_NTSTATUS_WIN_STATUS_FVE_NOT_ALLOWED_ON_CSV_STACK = 0xC0210034, - MD_NTSTATUS_WIN_STATUS_FVE_NOT_ALLOWED_ON_CLUSTER = 0xC0210035, - MD_NTSTATUS_WIN_STATUS_FVE_NOT_ALLOWED_TO_UPGRADE_WHILE_CONVERTING = 0xC0210036, - MD_NTSTATUS_WIN_STATUS_FVE_WIPE_CANCEL_NOT_APPLICABLE = 0xC0210037, - MD_NTSTATUS_WIN_STATUS_FVE_EDRIVE_DRY_RUN_FAILED = 0xC0210038, - MD_NTSTATUS_WIN_STATUS_FVE_SECUREBOOT_DISABLED = 0xC0210039, - MD_NTSTATUS_WIN_STATUS_FVE_SECUREBOOT_CONFIG_CHANGE = 0xC021003A, - MD_NTSTATUS_WIN_STATUS_FVE_DEVICE_LOCKEDOUT = 0xC021003B, - MD_NTSTATUS_WIN_STATUS_FVE_VOLUME_EXTEND_PREVENTS_EOW_DECRYPT = 0xC021003C, - MD_NTSTATUS_WIN_STATUS_FVE_NOT_DE_VOLUME = 0xC021003D, - MD_NTSTATUS_WIN_STATUS_FVE_PROTECTION_DISABLED = 0xC021003E, - MD_NTSTATUS_WIN_STATUS_FVE_PROTECTION_CANNOT_BE_DISABLED = 0xC021003F, - MD_NTSTATUS_WIN_STATUS_FWP_CALLOUT_NOT_FOUND = 0xC0220001, - MD_NTSTATUS_WIN_STATUS_FWP_CONDITION_NOT_FOUND = 0xC0220002, - MD_NTSTATUS_WIN_STATUS_FWP_FILTER_NOT_FOUND = 0xC0220003, - MD_NTSTATUS_WIN_STATUS_FWP_LAYER_NOT_FOUND = 0xC0220004, - MD_NTSTATUS_WIN_STATUS_FWP_PROVIDER_NOT_FOUND = 0xC0220005, - MD_NTSTATUS_WIN_STATUS_FWP_PROVIDER_CONTEXT_NOT_FOUND = 0xC0220006, - MD_NTSTATUS_WIN_STATUS_FWP_SUBLAYER_NOT_FOUND = 0xC0220007, - MD_NTSTATUS_WIN_STATUS_FWP_NOT_FOUND = 0xC0220008, - MD_NTSTATUS_WIN_STATUS_FWP_ALREADY_EXISTS = 0xC0220009, - MD_NTSTATUS_WIN_STATUS_FWP_IN_USE = 0xC022000A, - MD_NTSTATUS_WIN_STATUS_FWP_DYNAMIC_SESSION_IN_PROGRESS = 0xC022000B, - MD_NTSTATUS_WIN_STATUS_FWP_WRONG_SESSION = 0xC022000C, - MD_NTSTATUS_WIN_STATUS_FWP_NO_TXN_IN_PROGRESS = 0xC022000D, - MD_NTSTATUS_WIN_STATUS_FWP_TXN_IN_PROGRESS = 0xC022000E, - MD_NTSTATUS_WIN_STATUS_FWP_TXN_ABORTED = 0xC022000F, - MD_NTSTATUS_WIN_STATUS_FWP_SESSION_ABORTED = 0xC0220010, - MD_NTSTATUS_WIN_STATUS_FWP_INCOMPATIBLE_TXN = 0xC0220011, - MD_NTSTATUS_WIN_STATUS_FWP_TIMEOUT = 0xC0220012, - MD_NTSTATUS_WIN_STATUS_FWP_NET_EVENTS_DISABLED = 0xC0220013, - MD_NTSTATUS_WIN_STATUS_FWP_INCOMPATIBLE_LAYER = 0xC0220014, - MD_NTSTATUS_WIN_STATUS_FWP_KM_CLIENTS_ONLY = 0xC0220015, - MD_NTSTATUS_WIN_STATUS_FWP_LIFETIME_MISMATCH = 0xC0220016, - MD_NTSTATUS_WIN_STATUS_FWP_BUILTIN_OBJECT = 0xC0220017, - MD_NTSTATUS_WIN_STATUS_FWP_TOO_MANY_CALLOUTS = 0xC0220018, - MD_NTSTATUS_WIN_STATUS_FWP_NOTIFICATION_DROPPED = 0xC0220019, - MD_NTSTATUS_WIN_STATUS_FWP_TRAFFIC_MISMATCH = 0xC022001A, - MD_NTSTATUS_WIN_STATUS_FWP_INCOMPATIBLE_SA_STATE = 0xC022001B, - MD_NTSTATUS_WIN_STATUS_FWP_NULL_POINTER = 0xC022001C, - MD_NTSTATUS_WIN_STATUS_FWP_INVALID_ENUMERATOR = 0xC022001D, - MD_NTSTATUS_WIN_STATUS_FWP_INVALID_FLAGS = 0xC022001E, - MD_NTSTATUS_WIN_STATUS_FWP_INVALID_NET_MASK = 0xC022001F, - MD_NTSTATUS_WIN_STATUS_FWP_INVALID_RANGE = 0xC0220020, - MD_NTSTATUS_WIN_STATUS_FWP_INVALID_INTERVAL = 0xC0220021, - MD_NTSTATUS_WIN_STATUS_FWP_ZERO_LENGTH_ARRAY = 0xC0220022, - MD_NTSTATUS_WIN_STATUS_FWP_NULL_DISPLAY_NAME = 0xC0220023, - MD_NTSTATUS_WIN_STATUS_FWP_INVALID_ACTION_TYPE = 0xC0220024, - MD_NTSTATUS_WIN_STATUS_FWP_INVALID_WEIGHT = 0xC0220025, - MD_NTSTATUS_WIN_STATUS_FWP_MATCH_TYPE_MISMATCH = 0xC0220026, - MD_NTSTATUS_WIN_STATUS_FWP_TYPE_MISMATCH = 0xC0220027, - MD_NTSTATUS_WIN_STATUS_FWP_OUT_OF_BOUNDS = 0xC0220028, - MD_NTSTATUS_WIN_STATUS_FWP_RESERVED = 0xC0220029, - MD_NTSTATUS_WIN_STATUS_FWP_DUPLICATE_CONDITION = 0xC022002A, - MD_NTSTATUS_WIN_STATUS_FWP_DUPLICATE_KEYMOD = 0xC022002B, - MD_NTSTATUS_WIN_STATUS_FWP_ACTION_INCOMPATIBLE_WITH_LAYER = 0xC022002C, - MD_NTSTATUS_WIN_STATUS_FWP_ACTION_INCOMPATIBLE_WITH_SUBLAYER = 0xC022002D, - MD_NTSTATUS_WIN_STATUS_FWP_CONTEXT_INCOMPATIBLE_WITH_LAYER = 0xC022002E, - MD_NTSTATUS_WIN_STATUS_FWP_CONTEXT_INCOMPATIBLE_WITH_CALLOUT = 0xC022002F, - MD_NTSTATUS_WIN_STATUS_FWP_INCOMPATIBLE_AUTH_METHOD = 0xC0220030, - MD_NTSTATUS_WIN_STATUS_FWP_INCOMPATIBLE_DH_GROUP = 0xC0220031, - MD_NTSTATUS_WIN_STATUS_FWP_EM_NOT_SUPPORTED = 0xC0220032, - MD_NTSTATUS_WIN_STATUS_FWP_NEVER_MATCH = 0xC0220033, - MD_NTSTATUS_WIN_STATUS_FWP_PROVIDER_CONTEXT_MISMATCH = 0xC0220034, - MD_NTSTATUS_WIN_STATUS_FWP_INVALID_PARAMETER = 0xC0220035, - MD_NTSTATUS_WIN_STATUS_FWP_TOO_MANY_SUBLAYERS = 0xC0220036, - MD_NTSTATUS_WIN_STATUS_FWP_CALLOUT_NOTIFICATION_FAILED = 0xC0220037, - MD_NTSTATUS_WIN_STATUS_FWP_INVALID_AUTH_TRANSFORM = 0xC0220038, - MD_NTSTATUS_WIN_STATUS_FWP_INVALID_CIPHER_TRANSFORM = 0xC0220039, - MD_NTSTATUS_WIN_STATUS_FWP_INCOMPATIBLE_CIPHER_TRANSFORM = 0xC022003A, - MD_NTSTATUS_WIN_STATUS_FWP_INVALID_TRANSFORM_COMBINATION = 0xC022003B, - MD_NTSTATUS_WIN_STATUS_FWP_DUPLICATE_AUTH_METHOD = 0xC022003C, - MD_NTSTATUS_WIN_STATUS_FWP_INVALID_TUNNEL_ENDPOINT = 0xC022003D, - MD_NTSTATUS_WIN_STATUS_FWP_L2_DRIVER_NOT_READY = 0xC022003E, - MD_NTSTATUS_WIN_STATUS_FWP_KEY_DICTATOR_ALREADY_REGISTERED = 0xC022003F, - MD_NTSTATUS_WIN_STATUS_FWP_KEY_DICTATION_INVALID_KEYING_MATERIAL = 0xC0220040, - MD_NTSTATUS_WIN_STATUS_FWP_CONNECTIONS_DISABLED = 0xC0220041, - MD_NTSTATUS_WIN_STATUS_FWP_INVALID_DNS_NAME = 0xC0220042, - MD_NTSTATUS_WIN_STATUS_FWP_STILL_ON = 0xC0220043, - MD_NTSTATUS_WIN_STATUS_FWP_IKEEXT_NOT_RUNNING = 0xC0220044, - MD_NTSTATUS_WIN_STATUS_FWP_TCPIP_NOT_READY = 0xC0220100, - MD_NTSTATUS_WIN_STATUS_FWP_INJECT_HANDLE_CLOSING = 0xC0220101, - MD_NTSTATUS_WIN_STATUS_FWP_INJECT_HANDLE_STALE = 0xC0220102, - MD_NTSTATUS_WIN_STATUS_FWP_CANNOT_PEND = 0xC0220103, - MD_NTSTATUS_WIN_STATUS_FWP_DROP_NOICMP = 0xC0220104, - MD_NTSTATUS_WIN_STATUS_NDIS_CLOSING = 0xC0230002, - MD_NTSTATUS_WIN_STATUS_NDIS_BAD_VERSION = 0xC0230004, - MD_NTSTATUS_WIN_STATUS_NDIS_BAD_CHARACTERISTICS = 0xC0230005, - MD_NTSTATUS_WIN_STATUS_NDIS_ADAPTER_NOT_FOUND = 0xC0230006, - MD_NTSTATUS_WIN_STATUS_NDIS_OPEN_FAILED = 0xC0230007, - MD_NTSTATUS_WIN_STATUS_NDIS_DEVICE_FAILED = 0xC0230008, - MD_NTSTATUS_WIN_STATUS_NDIS_MULTICAST_FULL = 0xC0230009, - MD_NTSTATUS_WIN_STATUS_NDIS_MULTICAST_EXISTS = 0xC023000A, - MD_NTSTATUS_WIN_STATUS_NDIS_MULTICAST_NOT_FOUND = 0xC023000B, - MD_NTSTATUS_WIN_STATUS_NDIS_REQUEST_ABORTED = 0xC023000C, - MD_NTSTATUS_WIN_STATUS_NDIS_RESET_IN_PROGRESS = 0xC023000D, - MD_NTSTATUS_WIN_STATUS_NDIS_INVALID_PACKET = 0xC023000F, - MD_NTSTATUS_WIN_STATUS_NDIS_INVALID_DEVICE_REQUEST = 0xC0230010, - MD_NTSTATUS_WIN_STATUS_NDIS_ADAPTER_NOT_READY = 0xC0230011, - MD_NTSTATUS_WIN_STATUS_NDIS_INVALID_LENGTH = 0xC0230014, - MD_NTSTATUS_WIN_STATUS_NDIS_INVALID_DATA = 0xC0230015, - MD_NTSTATUS_WIN_STATUS_NDIS_BUFFER_TOO_SHORT = 0xC0230016, - MD_NTSTATUS_WIN_STATUS_NDIS_INVALID_OID = 0xC0230017, - MD_NTSTATUS_WIN_STATUS_NDIS_ADAPTER_REMOVED = 0xC0230018, - MD_NTSTATUS_WIN_STATUS_NDIS_UNSUPPORTED_MEDIA = 0xC0230019, - MD_NTSTATUS_WIN_STATUS_NDIS_GROUP_ADDRESS_IN_USE = 0xC023001A, - MD_NTSTATUS_WIN_STATUS_NDIS_FILE_NOT_FOUND = 0xC023001B, - MD_NTSTATUS_WIN_STATUS_NDIS_ERROR_READING_FILE = 0xC023001C, - MD_NTSTATUS_WIN_STATUS_NDIS_ALREADY_MAPPED = 0xC023001D, - MD_NTSTATUS_WIN_STATUS_NDIS_RESOURCE_CONFLICT = 0xC023001E, - MD_NTSTATUS_WIN_STATUS_NDIS_MEDIA_DISCONNECTED = 0xC023001F, - MD_NTSTATUS_WIN_STATUS_NDIS_INVALID_ADDRESS = 0xC0230022, - MD_NTSTATUS_WIN_STATUS_NDIS_PAUSED = 0xC023002A, - MD_NTSTATUS_WIN_STATUS_NDIS_INTERFACE_NOT_FOUND = 0xC023002B, - MD_NTSTATUS_WIN_STATUS_NDIS_UNSUPPORTED_REVISION = 0xC023002C, - MD_NTSTATUS_WIN_STATUS_NDIS_INVALID_PORT = 0xC023002D, - MD_NTSTATUS_WIN_STATUS_NDIS_INVALID_PORT_STATE = 0xC023002E, - MD_NTSTATUS_WIN_STATUS_NDIS_LOW_POWER_STATE = 0xC023002F, - MD_NTSTATUS_WIN_STATUS_NDIS_REINIT_REQUIRED = 0xC0230030, - MD_NTSTATUS_WIN_STATUS_NDIS_NOT_SUPPORTED = 0xC02300BB, - MD_NTSTATUS_WIN_STATUS_NDIS_OFFLOAD_POLICY = 0xC023100F, - MD_NTSTATUS_WIN_STATUS_NDIS_OFFLOAD_CONNECTION_REJECTED = 0xC0231012, - MD_NTSTATUS_WIN_STATUS_NDIS_OFFLOAD_PATH_REJECTED = 0xC0231013, - MD_NTSTATUS_WIN_STATUS_NDIS_DOT11_AUTO_CONFIG_ENABLED = 0xC0232000, - MD_NTSTATUS_WIN_STATUS_NDIS_DOT11_MEDIA_IN_USE = 0xC0232001, - MD_NTSTATUS_WIN_STATUS_NDIS_DOT11_POWER_STATE_INVALID = 0xC0232002, - MD_NTSTATUS_WIN_STATUS_NDIS_PM_WOL_PATTERN_LIST_FULL = 0xC0232003, - MD_NTSTATUS_WIN_STATUS_NDIS_PM_PROTOCOL_OFFLOAD_LIST_FULL = 0xC0232004, - MD_NTSTATUS_WIN_STATUS_TPM_ERROR_MASK = 0xC0290000, - MD_NTSTATUS_WIN_STATUS_TPM_AUTHFAIL = 0xC0290001, - MD_NTSTATUS_WIN_STATUS_TPM_BADINDEX = 0xC0290002, - MD_NTSTATUS_WIN_STATUS_TPM_BAD_PARAMETER = 0xC0290003, - MD_NTSTATUS_WIN_STATUS_TPM_AUDITFAILURE = 0xC0290004, - MD_NTSTATUS_WIN_STATUS_TPM_CLEAR_DISABLED = 0xC0290005, - MD_NTSTATUS_WIN_STATUS_TPM_DEACTIVATED = 0xC0290006, - MD_NTSTATUS_WIN_STATUS_TPM_DISABLED = 0xC0290007, - MD_NTSTATUS_WIN_STATUS_TPM_DISABLED_CMD = 0xC0290008, - MD_NTSTATUS_WIN_STATUS_TPM_FAIL = 0xC0290009, - MD_NTSTATUS_WIN_STATUS_TPM_BAD_ORDINAL = 0xC029000A, - MD_NTSTATUS_WIN_STATUS_TPM_INSTALL_DISABLED = 0xC029000B, - MD_NTSTATUS_WIN_STATUS_TPM_INVALID_KEYHANDLE = 0xC029000C, - MD_NTSTATUS_WIN_STATUS_TPM_KEYNOTFOUND = 0xC029000D, - MD_NTSTATUS_WIN_STATUS_TPM_INAPPROPRIATE_ENC = 0xC029000E, - MD_NTSTATUS_WIN_STATUS_TPM_MIGRATEFAIL = 0xC029000F, - MD_NTSTATUS_WIN_STATUS_TPM_INVALID_PCR_INFO = 0xC0290010, - MD_NTSTATUS_WIN_STATUS_TPM_NOSPACE = 0xC0290011, - MD_NTSTATUS_WIN_STATUS_TPM_NOSRK = 0xC0290012, - MD_NTSTATUS_WIN_STATUS_TPM_NOTSEALED_BLOB = 0xC0290013, - MD_NTSTATUS_WIN_STATUS_TPM_OWNER_SET = 0xC0290014, - MD_NTSTATUS_WIN_STATUS_TPM_RESOURCES = 0xC0290015, - MD_NTSTATUS_WIN_STATUS_TPM_SHORTRANDOM = 0xC0290016, - MD_NTSTATUS_WIN_STATUS_TPM_SIZE = 0xC0290017, - MD_NTSTATUS_WIN_STATUS_TPM_WRONGPCRVAL = 0xC0290018, - MD_NTSTATUS_WIN_STATUS_TPM_BAD_PARAM_SIZE = 0xC0290019, - MD_NTSTATUS_WIN_STATUS_TPM_SHA_THREAD = 0xC029001A, - MD_NTSTATUS_WIN_STATUS_TPM_SHA_ERROR = 0xC029001B, - MD_NTSTATUS_WIN_STATUS_TPM_FAILEDSELFTEST = 0xC029001C, - MD_NTSTATUS_WIN_STATUS_TPM_AUTH2FAIL = 0xC029001D, - MD_NTSTATUS_WIN_STATUS_TPM_BADTAG = 0xC029001E, - MD_NTSTATUS_WIN_STATUS_TPM_IOERROR = 0xC029001F, - MD_NTSTATUS_WIN_STATUS_TPM_ENCRYPT_ERROR = 0xC0290020, - MD_NTSTATUS_WIN_STATUS_TPM_DECRYPT_ERROR = 0xC0290021, - MD_NTSTATUS_WIN_STATUS_TPM_INVALID_AUTHHANDLE = 0xC0290022, - MD_NTSTATUS_WIN_STATUS_TPM_NO_ENDORSEMENT = 0xC0290023, - MD_NTSTATUS_WIN_STATUS_TPM_INVALID_KEYUSAGE = 0xC0290024, - MD_NTSTATUS_WIN_STATUS_TPM_WRONG_ENTITYTYPE = 0xC0290025, - MD_NTSTATUS_WIN_STATUS_TPM_INVALID_POSTINIT = 0xC0290026, - MD_NTSTATUS_WIN_STATUS_TPM_INAPPROPRIATE_SIG = 0xC0290027, - MD_NTSTATUS_WIN_STATUS_TPM_BAD_KEY_PROPERTY = 0xC0290028, - MD_NTSTATUS_WIN_STATUS_TPM_BAD_MIGRATION = 0xC0290029, - MD_NTSTATUS_WIN_STATUS_TPM_BAD_SCHEME = 0xC029002A, - MD_NTSTATUS_WIN_STATUS_TPM_BAD_DATASIZE = 0xC029002B, - MD_NTSTATUS_WIN_STATUS_TPM_BAD_MODE = 0xC029002C, - MD_NTSTATUS_WIN_STATUS_TPM_BAD_PRESENCE = 0xC029002D, - MD_NTSTATUS_WIN_STATUS_TPM_BAD_VERSION = 0xC029002E, - MD_NTSTATUS_WIN_STATUS_TPM_NO_WRAP_TRANSPORT = 0xC029002F, - MD_NTSTATUS_WIN_STATUS_TPM_AUDITFAIL_UNSUCCESSFUL = 0xC0290030, - MD_NTSTATUS_WIN_STATUS_TPM_AUDITFAIL_SUCCESSFUL = 0xC0290031, - MD_NTSTATUS_WIN_STATUS_TPM_NOTRESETABLE = 0xC0290032, - MD_NTSTATUS_WIN_STATUS_TPM_NOTLOCAL = 0xC0290033, - MD_NTSTATUS_WIN_STATUS_TPM_BAD_TYPE = 0xC0290034, - MD_NTSTATUS_WIN_STATUS_TPM_INVALID_RESOURCE = 0xC0290035, - MD_NTSTATUS_WIN_STATUS_TPM_NOTFIPS = 0xC0290036, - MD_NTSTATUS_WIN_STATUS_TPM_INVALID_FAMILY = 0xC0290037, - MD_NTSTATUS_WIN_STATUS_TPM_NO_NV_PERMISSION = 0xC0290038, - MD_NTSTATUS_WIN_STATUS_TPM_REQUIRES_SIGN = 0xC0290039, - MD_NTSTATUS_WIN_STATUS_TPM_KEY_NOTSUPPORTED = 0xC029003A, - MD_NTSTATUS_WIN_STATUS_TPM_AUTH_CONFLICT = 0xC029003B, - MD_NTSTATUS_WIN_STATUS_TPM_AREA_LOCKED = 0xC029003C, - MD_NTSTATUS_WIN_STATUS_TPM_BAD_LOCALITY = 0xC029003D, - MD_NTSTATUS_WIN_STATUS_TPM_READ_ONLY = 0xC029003E, - MD_NTSTATUS_WIN_STATUS_TPM_PER_NOWRITE = 0xC029003F, - MD_NTSTATUS_WIN_STATUS_TPM_FAMILYCOUNT = 0xC0290040, - MD_NTSTATUS_WIN_STATUS_TPM_WRITE_LOCKED = 0xC0290041, - MD_NTSTATUS_WIN_STATUS_TPM_BAD_ATTRIBUTES = 0xC0290042, - MD_NTSTATUS_WIN_STATUS_TPM_INVALID_STRUCTURE = 0xC0290043, - MD_NTSTATUS_WIN_STATUS_TPM_KEY_OWNER_CONTROL = 0xC0290044, - MD_NTSTATUS_WIN_STATUS_TPM_BAD_COUNTER = 0xC0290045, - MD_NTSTATUS_WIN_STATUS_TPM_NOT_FULLWRITE = 0xC0290046, - MD_NTSTATUS_WIN_STATUS_TPM_CONTEXT_GAP = 0xC0290047, - MD_NTSTATUS_WIN_STATUS_TPM_MAXNVWRITES = 0xC0290048, - MD_NTSTATUS_WIN_STATUS_TPM_NOOPERATOR = 0xC0290049, - MD_NTSTATUS_WIN_STATUS_TPM_RESOURCEMISSING = 0xC029004A, - MD_NTSTATUS_WIN_STATUS_TPM_DELEGATE_LOCK = 0xC029004B, - MD_NTSTATUS_WIN_STATUS_TPM_DELEGATE_FAMILY = 0xC029004C, - MD_NTSTATUS_WIN_STATUS_TPM_DELEGATE_ADMIN = 0xC029004D, - MD_NTSTATUS_WIN_STATUS_TPM_TRANSPORT_NOTEXCLUSIVE = 0xC029004E, - MD_NTSTATUS_WIN_STATUS_TPM_OWNER_CONTROL = 0xC029004F, - MD_NTSTATUS_WIN_STATUS_TPM_DAA_RESOURCES = 0xC0290050, - MD_NTSTATUS_WIN_STATUS_TPM_DAA_INPUT_DATA0 = 0xC0290051, - MD_NTSTATUS_WIN_STATUS_TPM_DAA_INPUT_DATA1 = 0xC0290052, - MD_NTSTATUS_WIN_STATUS_TPM_DAA_ISSUER_SETTINGS = 0xC0290053, - MD_NTSTATUS_WIN_STATUS_TPM_DAA_TPM_SETTINGS = 0xC0290054, - MD_NTSTATUS_WIN_STATUS_TPM_DAA_STAGE = 0xC0290055, - MD_NTSTATUS_WIN_STATUS_TPM_DAA_ISSUER_VALIDITY = 0xC0290056, - MD_NTSTATUS_WIN_STATUS_TPM_DAA_WRONG_W = 0xC0290057, - MD_NTSTATUS_WIN_STATUS_TPM_BAD_HANDLE = 0xC0290058, - MD_NTSTATUS_WIN_STATUS_TPM_BAD_DELEGATE = 0xC0290059, - MD_NTSTATUS_WIN_STATUS_TPM_BADCONTEXT = 0xC029005A, - MD_NTSTATUS_WIN_STATUS_TPM_TOOMANYCONTEXTS = 0xC029005B, - MD_NTSTATUS_WIN_STATUS_TPM_MA_TICKET_SIGNATURE = 0xC029005C, - MD_NTSTATUS_WIN_STATUS_TPM_MA_DESTINATION = 0xC029005D, - MD_NTSTATUS_WIN_STATUS_TPM_MA_SOURCE = 0xC029005E, - MD_NTSTATUS_WIN_STATUS_TPM_MA_AUTHORITY = 0xC029005F, - MD_NTSTATUS_WIN_STATUS_TPM_PERMANENTEK = 0xC0290061, - MD_NTSTATUS_WIN_STATUS_TPM_BAD_SIGNATURE = 0xC0290062, - MD_NTSTATUS_WIN_STATUS_TPM_NOCONTEXTSPACE = 0xC0290063, - MD_NTSTATUS_WIN_STATUS_TPM_COMMAND_BLOCKED = 0xC0290400, - MD_NTSTATUS_WIN_STATUS_TPM_INVALID_HANDLE = 0xC0290401, - MD_NTSTATUS_WIN_STATUS_TPM_DUPLICATE_VHANDLE = 0xC0290402, - MD_NTSTATUS_WIN_STATUS_TPM_EMBEDDED_COMMAND_BLOCKED = 0xC0290403, - MD_NTSTATUS_WIN_STATUS_TPM_EMBEDDED_COMMAND_UNSUPPORTED = 0xC0290404, - MD_NTSTATUS_WIN_STATUS_TPM_RETRY = 0xC0290800, - MD_NTSTATUS_WIN_STATUS_TPM_NEEDS_SELFTEST = 0xC0290801, - MD_NTSTATUS_WIN_STATUS_TPM_DOING_SELFTEST = 0xC0290802, - MD_NTSTATUS_WIN_STATUS_TPM_DEFEND_LOCK_RUNNING = 0xC0290803, - MD_NTSTATUS_WIN_STATUS_TPM_COMMAND_CANCELED = 0xC0291001, - MD_NTSTATUS_WIN_STATUS_TPM_TOO_MANY_CONTEXTS = 0xC0291002, - MD_NTSTATUS_WIN_STATUS_TPM_NOT_FOUND = 0xC0291003, - MD_NTSTATUS_WIN_STATUS_TPM_ACCESS_DENIED = 0xC0291004, - MD_NTSTATUS_WIN_STATUS_TPM_INSUFFICIENT_BUFFER = 0xC0291005, - MD_NTSTATUS_WIN_STATUS_TPM_PPI_FUNCTION_UNSUPPORTED = 0xC0291006, - MD_NTSTATUS_WIN_STATUS_PCP_ERROR_MASK = 0xC0292000, - MD_NTSTATUS_WIN_STATUS_PCP_DEVICE_NOT_READY = 0xC0292001, - MD_NTSTATUS_WIN_STATUS_PCP_INVALID_HANDLE = 0xC0292002, - MD_NTSTATUS_WIN_STATUS_PCP_INVALID_PARAMETER = 0xC0292003, - MD_NTSTATUS_WIN_STATUS_PCP_FLAG_NOT_SUPPORTED = 0xC0292004, - MD_NTSTATUS_WIN_STATUS_PCP_NOT_SUPPORTED = 0xC0292005, - MD_NTSTATUS_WIN_STATUS_PCP_BUFFER_TOO_SMALL = 0xC0292006, - MD_NTSTATUS_WIN_STATUS_PCP_INTERNAL_ERROR = 0xC0292007, - MD_NTSTATUS_WIN_STATUS_PCP_AUTHENTICATION_FAILED = 0xC0292008, - MD_NTSTATUS_WIN_STATUS_PCP_AUTHENTICATION_IGNORED = 0xC0292009, - MD_NTSTATUS_WIN_STATUS_PCP_POLICY_NOT_FOUND = 0xC029200A, - MD_NTSTATUS_WIN_STATUS_PCP_PROFILE_NOT_FOUND = 0xC029200B, - MD_NTSTATUS_WIN_STATUS_PCP_VALIDATION_FAILED = 0xC029200C, - MD_NTSTATUS_WIN_STATUS_PCP_DEVICE_NOT_FOUND = 0xC029200D, - MD_NTSTATUS_WIN_STATUS_HV_INVALID_HYPERCALL_CODE = 0xC0350002, - MD_NTSTATUS_WIN_STATUS_HV_INVALID_HYPERCALL_INPUT = 0xC0350003, - MD_NTSTATUS_WIN_STATUS_HV_INVALID_ALIGNMENT = 0xC0350004, - MD_NTSTATUS_WIN_STATUS_HV_INVALID_PARAMETER = 0xC0350005, - MD_NTSTATUS_WIN_STATUS_HV_ACCESS_DENIED = 0xC0350006, - MD_NTSTATUS_WIN_STATUS_HV_INVALID_PARTITION_STATE = 0xC0350007, - MD_NTSTATUS_WIN_STATUS_HV_OPERATION_DENIED = 0xC0350008, - MD_NTSTATUS_WIN_STATUS_HV_UNKNOWN_PROPERTY = 0xC0350009, - MD_NTSTATUS_WIN_STATUS_HV_PROPERTY_VALUE_OUT_OF_RANGE = 0xC035000A, - MD_NTSTATUS_WIN_STATUS_HV_INSUFFICIENT_MEMORY = 0xC035000B, - MD_NTSTATUS_WIN_STATUS_HV_PARTITION_TOO_DEEP = 0xC035000C, - MD_NTSTATUS_WIN_STATUS_HV_INVALID_PARTITION_ID = 0xC035000D, - MD_NTSTATUS_WIN_STATUS_HV_INVALID_VP_INDEX = 0xC035000E, - MD_NTSTATUS_WIN_STATUS_HV_INVALID_PORT_ID = 0xC0350011, - MD_NTSTATUS_WIN_STATUS_HV_INVALID_CONNECTION_ID = 0xC0350012, - MD_NTSTATUS_WIN_STATUS_HV_INSUFFICIENT_BUFFERS = 0xC0350013, - MD_NTSTATUS_WIN_STATUS_HV_NOT_ACKNOWLEDGED = 0xC0350014, - MD_NTSTATUS_WIN_STATUS_HV_ACKNOWLEDGED = 0xC0350016, - MD_NTSTATUS_WIN_STATUS_HV_INVALID_SAVE_RESTORE_STATE = 0xC0350017, - MD_NTSTATUS_WIN_STATUS_HV_INVALID_SYNIC_STATE = 0xC0350018, - MD_NTSTATUS_WIN_STATUS_HV_OBJECT_IN_USE = 0xC0350019, - MD_NTSTATUS_WIN_STATUS_HV_INVALID_PROXIMITY_DOMAIN_INFO = 0xC035001A, - MD_NTSTATUS_WIN_STATUS_HV_NO_DATA = 0xC035001B, - MD_NTSTATUS_WIN_STATUS_HV_INACTIVE = 0xC035001C, - MD_NTSTATUS_WIN_STATUS_HV_NO_RESOURCES = 0xC035001D, - MD_NTSTATUS_WIN_STATUS_HV_FEATURE_UNAVAILABLE = 0xC035001E, - MD_NTSTATUS_WIN_STATUS_HV_INSUFFICIENT_BUFFER = 0xC0350033, - MD_NTSTATUS_WIN_STATUS_HV_INSUFFICIENT_DEVICE_DOMAINS = 0xC0350038, - MD_NTSTATUS_WIN_STATUS_HV_INVALID_LP_INDEX = 0xC0350041, - MD_NTSTATUS_WIN_STATUS_HV_NOT_PRESENT = 0xC0351000, - MD_NTSTATUS_WIN_STATUS_IPSEC_BAD_SPI = 0xC0360001, - MD_NTSTATUS_WIN_STATUS_IPSEC_SA_LIFETIME_EXPIRED = 0xC0360002, - MD_NTSTATUS_WIN_STATUS_IPSEC_WRONG_SA = 0xC0360003, - MD_NTSTATUS_WIN_STATUS_IPSEC_REPLAY_CHECK_FAILED = 0xC0360004, - MD_NTSTATUS_WIN_STATUS_IPSEC_INVALID_PACKET = 0xC0360005, - MD_NTSTATUS_WIN_STATUS_IPSEC_INTEGRITY_CHECK_FAILED = 0xC0360006, - MD_NTSTATUS_WIN_STATUS_IPSEC_CLEAR_TEXT_DROP = 0xC0360007, - MD_NTSTATUS_WIN_STATUS_IPSEC_AUTH_FIREWALL_DROP = 0xC0360008, - MD_NTSTATUS_WIN_STATUS_IPSEC_THROTTLE_DROP = 0xC0360009, - MD_NTSTATUS_WIN_STATUS_IPSEC_DOSP_BLOCK = 0xC0368000, - MD_NTSTATUS_WIN_STATUS_IPSEC_DOSP_RECEIVED_MULTICAST = 0xC0368001, - MD_NTSTATUS_WIN_STATUS_IPSEC_DOSP_INVALID_PACKET = 0xC0368002, - MD_NTSTATUS_WIN_STATUS_IPSEC_DOSP_STATE_LOOKUP_FAILED = 0xC0368003, - MD_NTSTATUS_WIN_STATUS_IPSEC_DOSP_MAX_ENTRIES = 0xC0368004, - MD_NTSTATUS_WIN_STATUS_IPSEC_DOSP_KEYMOD_NOT_ALLOWED = 0xC0368005, - MD_NTSTATUS_WIN_STATUS_IPSEC_DOSP_MAX_PER_IP_RATELIMIT_QUEUES = 0xC0368006, - MD_NTSTATUS_WIN_STATUS_VID_DUPLICATE_HANDLER = 0xC0370001, - MD_NTSTATUS_WIN_STATUS_VID_TOO_MANY_HANDLERS = 0xC0370002, - MD_NTSTATUS_WIN_STATUS_VID_QUEUE_FULL = 0xC0370003, - MD_NTSTATUS_WIN_STATUS_VID_HANDLER_NOT_PRESENT = 0xC0370004, - MD_NTSTATUS_WIN_STATUS_VID_INVALID_OBJECT_NAME = 0xC0370005, - MD_NTSTATUS_WIN_STATUS_VID_PARTITION_NAME_TOO_LONG = 0xC0370006, - MD_NTSTATUS_WIN_STATUS_VID_MESSAGE_QUEUE_NAME_TOO_LONG = 0xC0370007, - MD_NTSTATUS_WIN_STATUS_VID_PARTITION_ALREADY_EXISTS = 0xC0370008, - MD_NTSTATUS_WIN_STATUS_VID_PARTITION_DOES_NOT_EXIST = 0xC0370009, - MD_NTSTATUS_WIN_STATUS_VID_PARTITION_NAME_NOT_FOUND = 0xC037000A, - MD_NTSTATUS_WIN_STATUS_VID_MESSAGE_QUEUE_ALREADY_EXISTS = 0xC037000B, - MD_NTSTATUS_WIN_STATUS_VID_EXCEEDED_MBP_ENTRY_MAP_LIMIT = 0xC037000C, - MD_NTSTATUS_WIN_STATUS_VID_MB_STILL_REFERENCED = 0xC037000D, - MD_NTSTATUS_WIN_STATUS_VID_CHILD_GPA_PAGE_SET_CORRUPTED = 0xC037000E, - MD_NTSTATUS_WIN_STATUS_VID_INVALID_NUMA_SETTINGS = 0xC037000F, - MD_NTSTATUS_WIN_STATUS_VID_INVALID_NUMA_NODE_INDEX = 0xC0370010, - MD_NTSTATUS_WIN_STATUS_VID_NOTIFICATION_QUEUE_ALREADY_ASSOCIATED = 0xC0370011, - MD_NTSTATUS_WIN_STATUS_VID_INVALID_MEMORY_BLOCK_HANDLE = 0xC0370012, - MD_NTSTATUS_WIN_STATUS_VID_PAGE_RANGE_OVERFLOW = 0xC0370013, - MD_NTSTATUS_WIN_STATUS_VID_INVALID_MESSAGE_QUEUE_HANDLE = 0xC0370014, - MD_NTSTATUS_WIN_STATUS_VID_INVALID_GPA_RANGE_HANDLE = 0xC0370015, - MD_NTSTATUS_WIN_STATUS_VID_NO_MEMORY_BLOCK_NOTIFICATION_QUEUE = 0xC0370016, - MD_NTSTATUS_WIN_STATUS_VID_MEMORY_BLOCK_LOCK_COUNT_EXCEEDED = 0xC0370017, - MD_NTSTATUS_WIN_STATUS_VID_INVALID_PPM_HANDLE = 0xC0370018, - MD_NTSTATUS_WIN_STATUS_VID_MBPS_ARE_LOCKED = 0xC0370019, - MD_NTSTATUS_WIN_STATUS_VID_MESSAGE_QUEUE_CLOSED = 0xC037001A, - MD_NTSTATUS_WIN_STATUS_VID_VIRTUAL_PROCESSOR_LIMIT_EXCEEDED = 0xC037001B, - MD_NTSTATUS_WIN_STATUS_VID_STOP_PENDING = 0xC037001C, - MD_NTSTATUS_WIN_STATUS_VID_INVALID_PROCESSOR_STATE = 0xC037001D, - MD_NTSTATUS_WIN_STATUS_VID_EXCEEDED_KM_CONTEXT_COUNT_LIMIT = 0xC037001E, - MD_NTSTATUS_WIN_STATUS_VID_KM_INTERFACE_ALREADY_INITIALIZED = 0xC037001F, - MD_NTSTATUS_WIN_STATUS_VID_MB_PROPERTY_ALREADY_SET_RESET = 0xC0370020, - MD_NTSTATUS_WIN_STATUS_VID_MMIO_RANGE_DESTROYED = 0xC0370021, - MD_NTSTATUS_WIN_STATUS_VID_INVALID_CHILD_GPA_PAGE_SET = 0xC0370022, - MD_NTSTATUS_WIN_STATUS_VID_RESERVE_PAGE_SET_IS_BEING_USED = 0xC0370023, - MD_NTSTATUS_WIN_STATUS_VID_RESERVE_PAGE_SET_TOO_SMALL = 0xC0370024, - MD_NTSTATUS_WIN_STATUS_VID_MBP_ALREADY_LOCKED_USING_RESERVED_PAGE = 0xC0370025, - MD_NTSTATUS_WIN_STATUS_VID_MBP_COUNT_EXCEEDED_LIMIT = 0xC0370026, - MD_NTSTATUS_WIN_STATUS_VID_SAVED_STATE_CORRUPT = 0xC0370027, - MD_NTSTATUS_WIN_STATUS_VID_SAVED_STATE_UNRECOGNIZED_ITEM = 0xC0370028, - MD_NTSTATUS_WIN_STATUS_VID_SAVED_STATE_INCOMPATIBLE = 0xC0370029, - MD_NTSTATUS_WIN_STATUS_VOLMGR_DATABASE_FULL = 0xC0380001, - MD_NTSTATUS_WIN_STATUS_VOLMGR_DISK_CONFIGURATION_CORRUPTED = 0xC0380002, - MD_NTSTATUS_WIN_STATUS_VOLMGR_DISK_CONFIGURATION_NOT_IN_SYNC = 0xC0380003, - MD_NTSTATUS_WIN_STATUS_VOLMGR_PACK_CONFIG_UPDATE_FAILED = 0xC0380004, - MD_NTSTATUS_WIN_STATUS_VOLMGR_DISK_CONTAINS_NON_SIMPLE_VOLUME = 0xC0380005, - MD_NTSTATUS_WIN_STATUS_VOLMGR_DISK_DUPLICATE = 0xC0380006, - MD_NTSTATUS_WIN_STATUS_VOLMGR_DISK_DYNAMIC = 0xC0380007, - MD_NTSTATUS_WIN_STATUS_VOLMGR_DISK_ID_INVALID = 0xC0380008, - MD_NTSTATUS_WIN_STATUS_VOLMGR_DISK_INVALID = 0xC0380009, - MD_NTSTATUS_WIN_STATUS_VOLMGR_DISK_LAST_VOTER = 0xC038000A, - MD_NTSTATUS_WIN_STATUS_VOLMGR_DISK_LAYOUT_INVALID = 0xC038000B, - MD_NTSTATUS_WIN_STATUS_VOLMGR_DISK_LAYOUT_NON_BASIC_BETWEEN_BASIC_PARTITIONS = 0xC038000C, - MD_NTSTATUS_WIN_STATUS_VOLMGR_DISK_LAYOUT_NOT_CYLINDER_ALIGNED = 0xC038000D, - MD_NTSTATUS_WIN_STATUS_VOLMGR_DISK_LAYOUT_PARTITIONS_TOO_SMALL = 0xC038000E, - MD_NTSTATUS_WIN_STATUS_VOLMGR_DISK_LAYOUT_PRIMARY_BETWEEN_LOGICAL_PARTITIONS = 0xC038000F, - MD_NTSTATUS_WIN_STATUS_VOLMGR_DISK_LAYOUT_TOO_MANY_PARTITIONS = 0xC0380010, - MD_NTSTATUS_WIN_STATUS_VOLMGR_DISK_MISSING = 0xC0380011, - MD_NTSTATUS_WIN_STATUS_VOLMGR_DISK_NOT_EMPTY = 0xC0380012, - MD_NTSTATUS_WIN_STATUS_VOLMGR_DISK_NOT_ENOUGH_SPACE = 0xC0380013, - MD_NTSTATUS_WIN_STATUS_VOLMGR_DISK_REVECTORING_FAILED = 0xC0380014, - MD_NTSTATUS_WIN_STATUS_VOLMGR_DISK_SECTOR_SIZE_INVALID = 0xC0380015, - MD_NTSTATUS_WIN_STATUS_VOLMGR_DISK_SET_NOT_CONTAINED = 0xC0380016, - MD_NTSTATUS_WIN_STATUS_VOLMGR_DISK_USED_BY_MULTIPLE_MEMBERS = 0xC0380017, - MD_NTSTATUS_WIN_STATUS_VOLMGR_DISK_USED_BY_MULTIPLE_PLEXES = 0xC0380018, - MD_NTSTATUS_WIN_STATUS_VOLMGR_DYNAMIC_DISK_NOT_SUPPORTED = 0xC0380019, - MD_NTSTATUS_WIN_STATUS_VOLMGR_EXTENT_ALREADY_USED = 0xC038001A, - MD_NTSTATUS_WIN_STATUS_VOLMGR_EXTENT_NOT_CONTIGUOUS = 0xC038001B, - MD_NTSTATUS_WIN_STATUS_VOLMGR_EXTENT_NOT_IN_PUBLIC_REGION = 0xC038001C, - MD_NTSTATUS_WIN_STATUS_VOLMGR_EXTENT_NOT_SECTOR_ALIGNED = 0xC038001D, - MD_NTSTATUS_WIN_STATUS_VOLMGR_EXTENT_OVERLAPS_EBR_PARTITION = 0xC038001E, - MD_NTSTATUS_WIN_STATUS_VOLMGR_EXTENT_VOLUME_LENGTHS_DO_NOT_MATCH = 0xC038001F, - MD_NTSTATUS_WIN_STATUS_VOLMGR_FAULT_TOLERANT_NOT_SUPPORTED = 0xC0380020, - MD_NTSTATUS_WIN_STATUS_VOLMGR_INTERLEAVE_LENGTH_INVALID = 0xC0380021, - MD_NTSTATUS_WIN_STATUS_VOLMGR_MAXIMUM_REGISTERED_USERS = 0xC0380022, - MD_NTSTATUS_WIN_STATUS_VOLMGR_MEMBER_IN_SYNC = 0xC0380023, - MD_NTSTATUS_WIN_STATUS_VOLMGR_MEMBER_INDEX_DUPLICATE = 0xC0380024, - MD_NTSTATUS_WIN_STATUS_VOLMGR_MEMBER_INDEX_INVALID = 0xC0380025, - MD_NTSTATUS_WIN_STATUS_VOLMGR_MEMBER_MISSING = 0xC0380026, - MD_NTSTATUS_WIN_STATUS_VOLMGR_MEMBER_NOT_DETACHED = 0xC0380027, - MD_NTSTATUS_WIN_STATUS_VOLMGR_MEMBER_REGENERATING = 0xC0380028, - MD_NTSTATUS_WIN_STATUS_VOLMGR_ALL_DISKS_FAILED = 0xC0380029, - MD_NTSTATUS_WIN_STATUS_VOLMGR_NO_REGISTERED_USERS = 0xC038002A, - MD_NTSTATUS_WIN_STATUS_VOLMGR_NO_SUCH_USER = 0xC038002B, - MD_NTSTATUS_WIN_STATUS_VOLMGR_NOTIFICATION_RESET = 0xC038002C, - MD_NTSTATUS_WIN_STATUS_VOLMGR_NUMBER_OF_MEMBERS_INVALID = 0xC038002D, - MD_NTSTATUS_WIN_STATUS_VOLMGR_NUMBER_OF_PLEXES_INVALID = 0xC038002E, - MD_NTSTATUS_WIN_STATUS_VOLMGR_PACK_DUPLICATE = 0xC038002F, - MD_NTSTATUS_WIN_STATUS_VOLMGR_PACK_ID_INVALID = 0xC0380030, - MD_NTSTATUS_WIN_STATUS_VOLMGR_PACK_INVALID = 0xC0380031, - MD_NTSTATUS_WIN_STATUS_VOLMGR_PACK_NAME_INVALID = 0xC0380032, - MD_NTSTATUS_WIN_STATUS_VOLMGR_PACK_OFFLINE = 0xC0380033, - MD_NTSTATUS_WIN_STATUS_VOLMGR_PACK_HAS_QUORUM = 0xC0380034, - MD_NTSTATUS_WIN_STATUS_VOLMGR_PACK_WITHOUT_QUORUM = 0xC0380035, - MD_NTSTATUS_WIN_STATUS_VOLMGR_PARTITION_STYLE_INVALID = 0xC0380036, - MD_NTSTATUS_WIN_STATUS_VOLMGR_PARTITION_UPDATE_FAILED = 0xC0380037, - MD_NTSTATUS_WIN_STATUS_VOLMGR_PLEX_IN_SYNC = 0xC0380038, - MD_NTSTATUS_WIN_STATUS_VOLMGR_PLEX_INDEX_DUPLICATE = 0xC0380039, - MD_NTSTATUS_WIN_STATUS_VOLMGR_PLEX_INDEX_INVALID = 0xC038003A, - MD_NTSTATUS_WIN_STATUS_VOLMGR_PLEX_LAST_ACTIVE = 0xC038003B, - MD_NTSTATUS_WIN_STATUS_VOLMGR_PLEX_MISSING = 0xC038003C, - MD_NTSTATUS_WIN_STATUS_VOLMGR_PLEX_REGENERATING = 0xC038003D, - MD_NTSTATUS_WIN_STATUS_VOLMGR_PLEX_TYPE_INVALID = 0xC038003E, - MD_NTSTATUS_WIN_STATUS_VOLMGR_PLEX_NOT_RAID5 = 0xC038003F, - MD_NTSTATUS_WIN_STATUS_VOLMGR_PLEX_NOT_SIMPLE = 0xC0380040, - MD_NTSTATUS_WIN_STATUS_VOLMGR_STRUCTURE_SIZE_INVALID = 0xC0380041, - MD_NTSTATUS_WIN_STATUS_VOLMGR_TOO_MANY_NOTIFICATION_REQUESTS = 0xC0380042, - MD_NTSTATUS_WIN_STATUS_VOLMGR_TRANSACTION_IN_PROGRESS = 0xC0380043, - MD_NTSTATUS_WIN_STATUS_VOLMGR_UNEXPECTED_DISK_LAYOUT_CHANGE = 0xC0380044, - MD_NTSTATUS_WIN_STATUS_VOLMGR_VOLUME_CONTAINS_MISSING_DISK = 0xC0380045, - MD_NTSTATUS_WIN_STATUS_VOLMGR_VOLUME_ID_INVALID = 0xC0380046, - MD_NTSTATUS_WIN_STATUS_VOLMGR_VOLUME_LENGTH_INVALID = 0xC0380047, - MD_NTSTATUS_WIN_STATUS_VOLMGR_VOLUME_LENGTH_NOT_SECTOR_SIZE_MULTIPLE = 0xC0380048, - MD_NTSTATUS_WIN_STATUS_VOLMGR_VOLUME_NOT_MIRRORED = 0xC0380049, - MD_NTSTATUS_WIN_STATUS_VOLMGR_VOLUME_NOT_RETAINED = 0xC038004A, - MD_NTSTATUS_WIN_STATUS_VOLMGR_VOLUME_OFFLINE = 0xC038004B, - MD_NTSTATUS_WIN_STATUS_VOLMGR_VOLUME_RETAINED = 0xC038004C, - MD_NTSTATUS_WIN_STATUS_VOLMGR_NUMBER_OF_EXTENTS_INVALID = 0xC038004D, - MD_NTSTATUS_WIN_STATUS_VOLMGR_DIFFERENT_SECTOR_SIZE = 0xC038004E, - MD_NTSTATUS_WIN_STATUS_VOLMGR_BAD_BOOT_DISK = 0xC038004F, - MD_NTSTATUS_WIN_STATUS_VOLMGR_PACK_CONFIG_OFFLINE = 0xC0380050, - MD_NTSTATUS_WIN_STATUS_VOLMGR_PACK_CONFIG_ONLINE = 0xC0380051, - MD_NTSTATUS_WIN_STATUS_VOLMGR_NOT_PRIMARY_PACK = 0xC0380052, - MD_NTSTATUS_WIN_STATUS_VOLMGR_PACK_LOG_UPDATE_FAILED = 0xC0380053, - MD_NTSTATUS_WIN_STATUS_VOLMGR_NUMBER_OF_DISKS_IN_PLEX_INVALID = 0xC0380054, - MD_NTSTATUS_WIN_STATUS_VOLMGR_NUMBER_OF_DISKS_IN_MEMBER_INVALID = 0xC0380055, - MD_NTSTATUS_WIN_STATUS_VOLMGR_VOLUME_MIRRORED = 0xC0380056, - MD_NTSTATUS_WIN_STATUS_VOLMGR_PLEX_NOT_SIMPLE_SPANNED = 0xC0380057, - MD_NTSTATUS_WIN_STATUS_VOLMGR_NO_VALID_LOG_COPIES = 0xC0380058, - MD_NTSTATUS_WIN_STATUS_VOLMGR_PRIMARY_PACK_PRESENT = 0xC0380059, - MD_NTSTATUS_WIN_STATUS_VOLMGR_NUMBER_OF_DISKS_INVALID = 0xC038005A, - MD_NTSTATUS_WIN_STATUS_VOLMGR_MIRROR_NOT_SUPPORTED = 0xC038005B, - MD_NTSTATUS_WIN_STATUS_VOLMGR_RAID5_NOT_SUPPORTED = 0xC038005C, - MD_NTSTATUS_WIN_STATUS_BCD_TOO_MANY_ELEMENTS = 0xC0390002, - MD_NTSTATUS_WIN_STATUS_VHD_DRIVE_FOOTER_MISSING = 0xC03A0001, - MD_NTSTATUS_WIN_STATUS_VHD_DRIVE_FOOTER_CHECKSUM_MISMATCH = 0xC03A0002, - MD_NTSTATUS_WIN_STATUS_VHD_DRIVE_FOOTER_CORRUPT = 0xC03A0003, - MD_NTSTATUS_WIN_STATUS_VHD_FORMAT_UNKNOWN = 0xC03A0004, - MD_NTSTATUS_WIN_STATUS_VHD_FORMAT_UNSUPPORTED_VERSION = 0xC03A0005, - MD_NTSTATUS_WIN_STATUS_VHD_SPARSE_HEADER_CHECKSUM_MISMATCH = 0xC03A0006, - MD_NTSTATUS_WIN_STATUS_VHD_SPARSE_HEADER_UNSUPPORTED_VERSION = 0xC03A0007, - MD_NTSTATUS_WIN_STATUS_VHD_SPARSE_HEADER_CORRUPT = 0xC03A0008, - MD_NTSTATUS_WIN_STATUS_VHD_BLOCK_ALLOCATION_FAILURE = 0xC03A0009, - MD_NTSTATUS_WIN_STATUS_VHD_BLOCK_ALLOCATION_TABLE_CORRUPT = 0xC03A000A, - MD_NTSTATUS_WIN_STATUS_VHD_INVALID_BLOCK_SIZE = 0xC03A000B, - MD_NTSTATUS_WIN_STATUS_VHD_BITMAP_MISMATCH = 0xC03A000C, - MD_NTSTATUS_WIN_STATUS_VHD_PARENT_VHD_NOT_FOUND = 0xC03A000D, - MD_NTSTATUS_WIN_STATUS_VHD_CHILD_PARENT_ID_MISMATCH = 0xC03A000E, - MD_NTSTATUS_WIN_STATUS_VHD_CHILD_PARENT_TIMESTAMP_MISMATCH = 0xC03A000F, - MD_NTSTATUS_WIN_STATUS_VHD_METADATA_READ_FAILURE = 0xC03A0010, - MD_NTSTATUS_WIN_STATUS_VHD_METADATA_WRITE_FAILURE = 0xC03A0011, - MD_NTSTATUS_WIN_STATUS_VHD_INVALID_SIZE = 0xC03A0012, - MD_NTSTATUS_WIN_STATUS_VHD_INVALID_FILE_SIZE = 0xC03A0013, - MD_NTSTATUS_WIN_STATUS_VIRTDISK_PROVIDER_NOT_FOUND = 0xC03A0014, - MD_NTSTATUS_WIN_STATUS_VIRTDISK_NOT_VIRTUAL_DISK = 0xC03A0015, - MD_NTSTATUS_WIN_STATUS_VHD_PARENT_VHD_ACCESS_DENIED = 0xC03A0016, - MD_NTSTATUS_WIN_STATUS_VHD_CHILD_PARENT_SIZE_MISMATCH = 0xC03A0017, - MD_NTSTATUS_WIN_STATUS_VHD_DIFFERENCING_CHAIN_CYCLE_DETECTED = 0xC03A0018, - MD_NTSTATUS_WIN_STATUS_VHD_DIFFERENCING_CHAIN_ERROR_IN_PARENT = 0xC03A0019, - MD_NTSTATUS_WIN_STATUS_VIRTUAL_DISK_LIMITATION = 0xC03A001A, - MD_NTSTATUS_WIN_STATUS_VHD_INVALID_TYPE = 0xC03A001B, - MD_NTSTATUS_WIN_STATUS_VHD_INVALID_STATE = 0xC03A001C, - MD_NTSTATUS_WIN_STATUS_VIRTDISK_UNSUPPORTED_DISK_SECTOR_SIZE = 0xC03A001D, - MD_NTSTATUS_WIN_STATUS_VIRTDISK_DISK_ALREADY_OWNED = 0xC03A001E, - MD_NTSTATUS_WIN_STATUS_VIRTDISK_DISK_ONLINE_AND_WRITABLE = 0xC03A001F, - MD_NTSTATUS_WIN_STATUS_CTLOG_TRACKING_NOT_INITIALIZED = 0xC03A0020, - MD_NTSTATUS_WIN_STATUS_CTLOG_LOGFILE_SIZE_EXCEEDED_MAXSIZE = 0xC03A0021, - MD_NTSTATUS_WIN_STATUS_CTLOG_VHD_CHANGED_OFFLINE = 0xC03A0022, - MD_NTSTATUS_WIN_STATUS_CTLOG_INVALID_TRACKING_STATE = 0xC03A0023, - MD_NTSTATUS_WIN_STATUS_CTLOG_INCONSISTENT_TRACKING_FILE = 0xC03A0024, - MD_NTSTATUS_WIN_STATUS_VHD_METADATA_FULL = 0xC03A0028, - MD_NTSTATUS_WIN_STATUS_RKF_KEY_NOT_FOUND = 0xC0400001, - MD_NTSTATUS_WIN_STATUS_RKF_DUPLICATE_KEY = 0xC0400002, - MD_NTSTATUS_WIN_STATUS_RKF_BLOB_FULL = 0xC0400003, - MD_NTSTATUS_WIN_STATUS_RKF_STORE_FULL = 0xC0400004, - MD_NTSTATUS_WIN_STATUS_RKF_FILE_BLOCKED = 0xC0400005, - MD_NTSTATUS_WIN_STATUS_RKF_ACTIVE_KEY = 0xC0400006, - MD_NTSTATUS_WIN_STATUS_RDBSS_RESTART_OPERATION = 0xC0410001, - MD_NTSTATUS_WIN_STATUS_RDBSS_CONTINUE_OPERATION = 0xC0410002, - MD_NTSTATUS_WIN_STATUS_RDBSS_POST_OPERATION = 0xC0410003, - MD_NTSTATUS_WIN_STATUS_BTH_ATT_INVALID_HANDLE = 0xC0420001, - MD_NTSTATUS_WIN_STATUS_BTH_ATT_READ_NOT_PERMITTED = 0xC0420002, - MD_NTSTATUS_WIN_STATUS_BTH_ATT_WRITE_NOT_PERMITTED = 0xC0420003, - MD_NTSTATUS_WIN_STATUS_BTH_ATT_INVALID_PDU = 0xC0420004, - MD_NTSTATUS_WIN_STATUS_BTH_ATT_INSUFFICIENT_AUTHENTICATION = 0xC0420005, - MD_NTSTATUS_WIN_STATUS_BTH_ATT_REQUEST_NOT_SUPPORTED = 0xC0420006, - MD_NTSTATUS_WIN_STATUS_BTH_ATT_INVALID_OFFSET = 0xC0420007, - MD_NTSTATUS_WIN_STATUS_BTH_ATT_INSUFFICIENT_AUTHORIZATION = 0xC0420008, - MD_NTSTATUS_WIN_STATUS_BTH_ATT_PREPARE_QUEUE_FULL = 0xC0420009, - MD_NTSTATUS_WIN_STATUS_BTH_ATT_ATTRIBUTE_NOT_FOUND = 0xC042000A, - MD_NTSTATUS_WIN_STATUS_BTH_ATT_ATTRIBUTE_NOT_LONG = 0xC042000B, - MD_NTSTATUS_WIN_STATUS_BTH_ATT_INSUFFICIENT_ENCRYPTION_KEY_SIZE = 0xC042000C, - MD_NTSTATUS_WIN_STATUS_BTH_ATT_INVALID_ATTRIBUTE_VALUE_LENGTH = 0xC042000D, - MD_NTSTATUS_WIN_STATUS_BTH_ATT_UNLIKELY = 0xC042000E, - MD_NTSTATUS_WIN_STATUS_BTH_ATT_INSUFFICIENT_ENCRYPTION = 0xC042000F, - MD_NTSTATUS_WIN_STATUS_BTH_ATT_UNSUPPORTED_GROUP_TYPE = 0xC0420010, - MD_NTSTATUS_WIN_STATUS_BTH_ATT_INSUFFICIENT_RESOURCES = 0xC0420011, - MD_NTSTATUS_WIN_STATUS_BTH_ATT_UNKNOWN_ERROR = 0xC0421000, - MD_NTSTATUS_WIN_STATUS_SECUREBOOT_ROLLBACK_DETECTED = 0xC0430001, - MD_NTSTATUS_WIN_STATUS_SECUREBOOT_POLICY_VIOLATION = 0xC0430002, - MD_NTSTATUS_WIN_STATUS_SECUREBOOT_INVALID_POLICY = 0xC0430003, - MD_NTSTATUS_WIN_STATUS_SECUREBOOT_POLICY_PUBLISHER_NOT_FOUND = 0xC0430004, - MD_NTSTATUS_WIN_STATUS_SECUREBOOT_POLICY_NOT_SIGNED = 0xC0430005, - MD_NTSTATUS_WIN_STATUS_SECUREBOOT_FILE_REPLACED = 0xC0430007, - MD_NTSTATUS_WIN_STATUS_AUDIO_ENGINE_NODE_NOT_FOUND = 0xC0440001, - MD_NTSTATUS_WIN_STATUS_HDAUDIO_EMPTY_CONNECTION_LIST = 0xC0440002, - MD_NTSTATUS_WIN_STATUS_HDAUDIO_CONNECTION_LIST_NOT_SUPPORTED = 0xC0440003, - MD_NTSTATUS_WIN_STATUS_HDAUDIO_NO_LOGICAL_DEVICES_CREATED = 0xC0440004, - MD_NTSTATUS_WIN_STATUS_HDAUDIO_NULL_LINKED_LIST_ENTRY = 0xC0440005, - MD_NTSTATUS_WIN_STATUS_VOLSNAP_BOOTFILE_NOT_VALID = 0xC0500003, - MD_NTSTATUS_WIN_STATUS_IO_PREEMPTED = 0xC0510001, - MD_NTSTATUS_WIN_STATUS_SVHDX_ERROR_STORED = 0xC05C0000, - MD_NTSTATUS_WIN_STATUS_SVHDX_ERROR_NOT_AVAILABLE = 0xC05CFF00, - MD_NTSTATUS_WIN_STATUS_SVHDX_UNIT_ATTENTION_AVAILABLE = 0xC05CFF01, - MD_NTSTATUS_WIN_STATUS_SVHDX_UNIT_ATTENTION_CAPACITY_DATA_CHANGED = 0xC05CFF02, - MD_NTSTATUS_WIN_STATUS_SVHDX_UNIT_ATTENTION_RESERVATIONS_PREEMPTED = 0xC05CFF03, - MD_NTSTATUS_WIN_STATUS_SVHDX_UNIT_ATTENTION_RESERVATIONS_RELEASED = 0xC05CFF04, - MD_NTSTATUS_WIN_STATUS_SVHDX_UNIT_ATTENTION_REGISTRATIONS_PREEMPTED = 0xC05CFF05, - MD_NTSTATUS_WIN_STATUS_SVHDX_UNIT_ATTENTION_OPERATING_DEFINITION_CHANGED = 0xC05CFF06, - MD_NTSTATUS_WIN_STATUS_SVHDX_RESERVATION_CONFLICT = 0xC05CFF07, - MD_NTSTATUS_WIN_STATUS_SVHDX_WRONG_FILE_TYPE = 0xC05CFF08, - MD_NTSTATUS_WIN_STATUS_SVHDX_VERSION_MISMATCH = 0xC05CFF09, - MD_NTSTATUS_WIN_STATUS_VHD_SHARED = 0xC05CFF0A, - MD_NTSTATUS_WIN_STATUS_SPACES_RESILIENCY_TYPE_INVALID = 0xC0E70003, - MD_NTSTATUS_WIN_STATUS_SPACES_DRIVE_SECTOR_SIZE_INVALID = 0xC0E70004, - MD_NTSTATUS_WIN_STATUS_SPACES_INTERLEAVE_LENGTH_INVALID = 0xC0E70009, - MD_NTSTATUS_WIN_STATUS_SPACES_NUMBER_OF_COLUMNS_INVALID = 0xC0E7000A, - MD_NTSTATUS_WIN_STATUS_SPACES_NOT_ENOUGH_DRIVES = 0xC0E7000B -} MDNTStatusCodeWin; - -// These constants are defined in the MSDN documentation of -// the EXCEPTION_RECORD structure. -typedef enum { - MD_ACCESS_VIOLATION_WIN_READ = 0, - MD_ACCESS_VIOLATION_WIN_WRITE = 1, - MD_ACCESS_VIOLATION_WIN_EXEC = 8 -} MDAccessViolationTypeWin; - -// These constants are defined in the MSDN documentation of -// the EXCEPTION_RECORD structure. -typedef enum { - MD_IN_PAGE_ERROR_WIN_READ = 0, - MD_IN_PAGE_ERROR_WIN_WRITE = 1, - MD_IN_PAGE_ERROR_WIN_EXEC = 8 -} MDInPageErrorTypeWin; - -#endif /* GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_WIN32_H__ */ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_format.h b/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_format.h deleted file mode 100644 index 17a5abba3..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_format.h +++ /dev/null @@ -1,972 +0,0 @@ -/* Copyright (c) 2006, Google Inc. - * 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 Google Inc. 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 - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -/* minidump_format.h: A cross-platform reimplementation of minidump-related - * portions of DbgHelp.h from the Windows Platform SDK. - * - * (This is C99 source, please don't corrupt it with C++.) - * - * Structures that are defined by Microsoft to contain a zero-length array - * are instead defined here to contain an array with one element, as - * zero-length arrays are forbidden by standard C and C++. In these cases, - * *_minsize constants are provided to be used in place of sizeof. For a - * cleaner interface to these sizes when using C++, see minidump_size.h. - * - * These structures are also sufficient to populate minidump files. - * - * These definitions may be extended to support handling minidump files - * for other CPUs and other operating systems. - * - * Because precise data type sizes are crucial for this implementation to - * function properly and portably in terms of interoperability with minidumps - * produced by DbgHelp on Windows, a set of primitive types with known sizes - * are used as the basis of each structure defined by this file. DbgHelp - * on Windows is assumed to be the reference implementation; this file - * seeks to provide a cross-platform compatible implementation. To avoid - * collisions with the types and values defined and used by DbgHelp in the - * event that this implementation is used on Windows, each type and value - * defined here is given a new name, beginning with "MD". Names of the - * equivalent types and values in the Windows Platform SDK are given in - * comments. - * - * Author: Mark Mentovai */ - - -#ifndef GOOGLE_BREAKPAD_COMMON_MINIDUMP_FORMAT_H__ -#define GOOGLE_BREAKPAD_COMMON_MINIDUMP_FORMAT_H__ - -#include - -#include "google_breakpad/common/breakpad_types.h" - - -#if defined(_MSC_VER) -/* Disable "zero-sized array in struct/union" warnings when compiling in - * MSVC. DbgHelp.h does this too. */ -#pragma warning(push) -#pragma warning(disable:4200) -#endif /* _MSC_VER */ - - -/* - * guiddef.h - */ - -typedef struct { - uint32_t data1; - uint16_t data2; - uint16_t data3; - uint8_t data4[8]; -} MDGUID; /* GUID */ - - -/* - * WinNT.h - */ - -/* Non-x86 CPU identifiers found in the high 24 bits of - * (MDRawContext*).context_flags. These aren't used by Breakpad, but are - * defined here for reference, to avoid assigning values that conflict - * (although some values already conflict). */ -#define MD_CONTEXT_IA64 0x00080000 /* CONTEXT_IA64 */ -/* Additional values from winnt.h in the Windows CE 5.0 SDK: */ -#define MD_CONTEXT_SHX 0x000000c0 /* CONTEXT_SH4 (Super-H, includes SH3) */ -#define MD_CONTEXT_ALPHA 0x00020000 /* CONTEXT_ALPHA */ - -/* As of Windows 7 SP1, the number of flag bits has increased to - * include 0x40 (CONTEXT_XSTATE): - * http://msdn.microsoft.com/en-us/library/hh134238%28v=vs.85%29.aspx */ -#define MD_CONTEXT_CPU_MASK 0xffffff00 - - -/* This is a base type for MDRawContextX86 and MDRawContextPPC. This - * structure should never be allocated directly. The actual structure type - * can be determined by examining the context_flags field. */ -typedef struct { - uint32_t context_flags; -} MDRawContextBase; - -#include "minidump_cpu_amd64.h" -#include "minidump_cpu_arm.h" -#include "minidump_cpu_arm64.h" -#include "minidump_cpu_mips.h" -#include "minidump_cpu_ppc.h" -#include "minidump_cpu_ppc64.h" -#include "minidump_cpu_sparc.h" -#include "minidump_cpu_x86.h" - -/* - * WinVer.h - */ - - -typedef struct { - uint32_t signature; - uint32_t struct_version; - uint32_t file_version_hi; - uint32_t file_version_lo; - uint32_t product_version_hi; - uint32_t product_version_lo; - uint32_t file_flags_mask; /* Identifies valid bits in fileFlags */ - uint32_t file_flags; - uint32_t file_os; - uint32_t file_type; - uint32_t file_subtype; - uint32_t file_date_hi; - uint32_t file_date_lo; -} MDVSFixedFileInfo; /* VS_FIXEDFILEINFO */ - -/* For (MDVSFixedFileInfo).signature */ -#define MD_VSFIXEDFILEINFO_SIGNATURE 0xfeef04bd - /* VS_FFI_SIGNATURE */ - -/* For (MDVSFixedFileInfo).version */ -#define MD_VSFIXEDFILEINFO_VERSION 0x00010000 - /* VS_FFI_STRUCVERSION */ - -/* For (MDVSFixedFileInfo).file_flags_mask and - * (MDVSFixedFileInfo).file_flags */ -#define MD_VSFIXEDFILEINFO_FILE_FLAGS_DEBUG 0x00000001 - /* VS_FF_DEBUG */ -#define MD_VSFIXEDFILEINFO_FILE_FLAGS_PRERELEASE 0x00000002 - /* VS_FF_PRERELEASE */ -#define MD_VSFIXEDFILEINFO_FILE_FLAGS_PATCHED 0x00000004 - /* VS_FF_PATCHED */ -#define MD_VSFIXEDFILEINFO_FILE_FLAGS_PRIVATEBUILD 0x00000008 - /* VS_FF_PRIVATEBUILD */ -#define MD_VSFIXEDFILEINFO_FILE_FLAGS_INFOINFERRED 0x00000010 - /* VS_FF_INFOINFERRED */ -#define MD_VSFIXEDFILEINFO_FILE_FLAGS_SPECIALBUILD 0x00000020 - /* VS_FF_SPECIALBUILD */ - -/* For (MDVSFixedFileInfo).file_os: high 16 bits */ -#define MD_VSFIXEDFILEINFO_FILE_OS_UNKNOWN 0 /* VOS_UNKNOWN */ -#define MD_VSFIXEDFILEINFO_FILE_OS_DOS (1 << 16) /* VOS_DOS */ -#define MD_VSFIXEDFILEINFO_FILE_OS_OS216 (2 << 16) /* VOS_OS216 */ -#define MD_VSFIXEDFILEINFO_FILE_OS_OS232 (3 << 16) /* VOS_OS232 */ -#define MD_VSFIXEDFILEINFO_FILE_OS_NT (4 << 16) /* VOS_NT */ -#define MD_VSFIXEDFILEINFO_FILE_OS_WINCE (5 << 16) /* VOS_WINCE */ -/* Low 16 bits */ -#define MD_VSFIXEDFILEINFO_FILE_OS__BASE 0 /* VOS__BASE */ -#define MD_VSFIXEDFILEINFO_FILE_OS__WINDOWS16 1 /* VOS__WINDOWS16 */ -#define MD_VSFIXEDFILEINFO_FILE_OS__PM16 2 /* VOS__PM16 */ -#define MD_VSFIXEDFILEINFO_FILE_OS__PM32 3 /* VOS__PM32 */ -#define MD_VSFIXEDFILEINFO_FILE_OS__WINDOWS32 4 /* VOS__WINDOWS32 */ - -/* For (MDVSFixedFileInfo).file_type */ -#define MD_VSFIXEDFILEINFO_FILE_TYPE_UNKNOWN 0 /* VFT_UNKNOWN */ -#define MD_VSFIXEDFILEINFO_FILE_TYPE_APP 1 /* VFT_APP */ -#define MD_VSFIXEDFILEINFO_FILE_TYPE_DLL 2 /* VFT_DLL */ -#define MD_VSFIXEDFILEINFO_FILE_TYPE_DRV 3 /* VFT_DLL */ -#define MD_VSFIXEDFILEINFO_FILE_TYPE_FONT 4 /* VFT_FONT */ -#define MD_VSFIXEDFILEINFO_FILE_TYPE_VXD 5 /* VFT_VXD */ -#define MD_VSFIXEDFILEINFO_FILE_TYPE_STATIC_LIB 7 /* VFT_STATIC_LIB */ - -/* For (MDVSFixedFileInfo).file_subtype */ -#define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_UNKNOWN 0 - /* VFT2_UNKNOWN */ -/* with file_type = MD_VSFIXEDFILEINFO_FILETYPE_DRV */ -#define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_PRINTER 1 - /* VFT2_DRV_PRINTER */ -#define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_KEYBOARD 2 - /* VFT2_DRV_KEYBOARD */ -#define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_LANGUAGE 3 - /* VFT2_DRV_LANGUAGE */ -#define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_DISPLAY 4 - /* VFT2_DRV_DISPLAY */ -#define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_MOUSE 5 - /* VFT2_DRV_MOUSE */ -#define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_NETWORK 6 - /* VFT2_DRV_NETWORK */ -#define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_SYSTEM 7 - /* VFT2_DRV_SYSTEM */ -#define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_INSTALLABLE 8 - /* VFT2_DRV_INSTALLABLE */ -#define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_SOUND 9 - /* VFT2_DRV_SOUND */ -#define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_COMM 10 - /* VFT2_DRV_COMM */ -#define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_INPUTMETHOD 11 - /* VFT2_DRV_INPUTMETHOD */ -#define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_VERSIONED_PRINTER 12 - /* VFT2_DRV_VERSIONED_PRINTER */ -/* with file_type = MD_VSFIXEDFILEINFO_FILETYPE_FONT */ -#define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_FONT_RASTER 1 - /* VFT2_FONT_RASTER */ -#define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_FONT_VECTOR 2 - /* VFT2_FONT_VECTOR */ -#define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_FONT_TRUETYPE 3 - /* VFT2_FONT_TRUETYPE */ - - -/* - * DbgHelp.h - */ - - -/* An MDRVA is an offset into the minidump file. The beginning of the - * MDRawHeader is at offset 0. */ -typedef uint32_t MDRVA; /* RVA */ - -typedef struct { - uint32_t data_size; - MDRVA rva; -} MDLocationDescriptor; /* MINIDUMP_LOCATION_DESCRIPTOR */ - - -typedef struct { - /* The base address of the memory range on the host that produced the - * minidump. */ - uint64_t start_of_memory_range; - - MDLocationDescriptor memory; -} MDMemoryDescriptor; /* MINIDUMP_MEMORY_DESCRIPTOR */ - - -typedef struct { - uint32_t signature; - uint32_t version; - uint32_t stream_count; - MDRVA stream_directory_rva; /* A |stream_count|-sized array of - * MDRawDirectory structures. */ - uint32_t checksum; /* Can be 0. In fact, that's all that's - * been found in minidump files. */ - uint32_t time_date_stamp; /* time_t */ - uint64_t flags; -} MDRawHeader; /* MINIDUMP_HEADER */ - -/* For (MDRawHeader).signature and (MDRawHeader).version. Note that only the - * low 16 bits of (MDRawHeader).version are MD_HEADER_VERSION. Per the - * documentation, the high 16 bits are implementation-specific. */ -#define MD_HEADER_SIGNATURE 0x504d444d /* 'PMDM' */ - /* MINIDUMP_SIGNATURE */ -#define MD_HEADER_VERSION 0x0000a793 /* 42899 */ - /* MINIDUMP_VERSION */ - -/* For (MDRawHeader).flags: */ -typedef enum { - /* MD_NORMAL is the standard type of minidump. It includes full - * streams for the thread list, module list, exception, system info, - * and miscellaneous info. A memory list stream is also present, - * pointing to the same stack memory contained in the thread list, - * as well as a 256-byte region around the instruction address that - * was executing when the exception occurred. Stack memory is from - * 4 bytes below a thread's stack pointer up to the top of the - * memory region encompassing the stack. */ - MD_NORMAL = 0x00000000, - MD_WITH_DATA_SEGS = 0x00000001, - MD_WITH_FULL_MEMORY = 0x00000002, - MD_WITH_HANDLE_DATA = 0x00000004, - MD_FILTER_MEMORY = 0x00000008, - MD_SCAN_MEMORY = 0x00000010, - MD_WITH_UNLOADED_MODULES = 0x00000020, - MD_WITH_INDIRECTLY_REFERENCED_MEMORY = 0x00000040, - MD_FILTER_MODULE_PATHS = 0x00000080, - MD_WITH_PROCESS_THREAD_DATA = 0x00000100, - MD_WITH_PRIVATE_READ_WRITE_MEMORY = 0x00000200, - MD_WITHOUT_OPTIONAL_DATA = 0x00000400, - MD_WITH_FULL_MEMORY_INFO = 0x00000800, - MD_WITH_THREAD_INFO = 0x00001000, - MD_WITH_CODE_SEGS = 0x00002000, - MD_WITHOUT_AUXILLIARY_SEGS = 0x00004000, - MD_WITH_FULL_AUXILLIARY_STATE = 0x00008000, - MD_WITH_PRIVATE_WRITE_COPY_MEMORY = 0x00010000, - MD_IGNORE_INACCESSIBLE_MEMORY = 0x00020000, - MD_WITH_TOKEN_INFORMATION = 0x00040000 -} MDType; /* MINIDUMP_TYPE */ - - -typedef struct { - uint32_t stream_type; - MDLocationDescriptor location; -} MDRawDirectory; /* MINIDUMP_DIRECTORY */ - -/* For (MDRawDirectory).stream_type */ -typedef enum { - MD_UNUSED_STREAM = 0, - MD_RESERVED_STREAM_0 = 1, - MD_RESERVED_STREAM_1 = 2, - MD_THREAD_LIST_STREAM = 3, /* MDRawThreadList */ - MD_MODULE_LIST_STREAM = 4, /* MDRawModuleList */ - MD_MEMORY_LIST_STREAM = 5, /* MDRawMemoryList */ - MD_EXCEPTION_STREAM = 6, /* MDRawExceptionStream */ - MD_SYSTEM_INFO_STREAM = 7, /* MDRawSystemInfo */ - MD_THREAD_EX_LIST_STREAM = 8, - MD_MEMORY_64_LIST_STREAM = 9, - MD_COMMENT_STREAM_A = 10, - MD_COMMENT_STREAM_W = 11, - MD_HANDLE_DATA_STREAM = 12, - MD_FUNCTION_TABLE_STREAM = 13, - MD_UNLOADED_MODULE_LIST_STREAM = 14, - MD_MISC_INFO_STREAM = 15, /* MDRawMiscInfo */ - MD_MEMORY_INFO_LIST_STREAM = 16, /* MDRawMemoryInfoList */ - MD_THREAD_INFO_LIST_STREAM = 17, - MD_HANDLE_OPERATION_LIST_STREAM = 18, - MD_LAST_RESERVED_STREAM = 0x0000ffff, - - /* Breakpad extension types. 0x4767 = "Gg" */ - MD_BREAKPAD_INFO_STREAM = 0x47670001, /* MDRawBreakpadInfo */ - MD_ASSERTION_INFO_STREAM = 0x47670002, /* MDRawAssertionInfo */ - /* These are additional minidump stream values which are specific to - * the linux breakpad implementation. */ - MD_LINUX_CPU_INFO = 0x47670003, /* /proc/cpuinfo */ - MD_LINUX_PROC_STATUS = 0x47670004, /* /proc/$x/status */ - MD_LINUX_LSB_RELEASE = 0x47670005, /* /etc/lsb-release */ - MD_LINUX_CMD_LINE = 0x47670006, /* /proc/$x/cmdline */ - MD_LINUX_ENVIRON = 0x47670007, /* /proc/$x/environ */ - MD_LINUX_AUXV = 0x47670008, /* /proc/$x/auxv */ - MD_LINUX_MAPS = 0x47670009, /* /proc/$x/maps */ - MD_LINUX_DSO_DEBUG = 0x4767000A /* MDRawDebug{32,64} */ -} MDStreamType; /* MINIDUMP_STREAM_TYPE */ - - -typedef struct { - uint32_t length; /* Length of buffer in bytes (not characters), - * excluding 0-terminator */ - uint16_t buffer[1]; /* UTF-16-encoded, 0-terminated */ -} MDString; /* MINIDUMP_STRING */ - -static const size_t MDString_minsize = offsetof(MDString, buffer[0]); - - -typedef struct { - uint32_t thread_id; - uint32_t suspend_count; - uint32_t priority_class; - uint32_t priority; - uint64_t teb; /* Thread environment block */ - MDMemoryDescriptor stack; - MDLocationDescriptor thread_context; /* MDRawContext[CPU] */ -} MDRawThread; /* MINIDUMP_THREAD */ - - -typedef struct { - uint32_t number_of_threads; - MDRawThread threads[1]; -} MDRawThreadList; /* MINIDUMP_THREAD_LIST */ - -static const size_t MDRawThreadList_minsize = offsetof(MDRawThreadList, - threads[0]); - - -typedef struct { - uint64_t base_of_image; - uint32_t size_of_image; - uint32_t checksum; /* 0 if unknown */ - uint32_t time_date_stamp; /* time_t */ - MDRVA module_name_rva; /* MDString, pathname or filename */ - MDVSFixedFileInfo version_info; - - /* The next field stores a CodeView record and is populated when a module's - * debug information resides in a PDB file. It identifies the PDB file. */ - MDLocationDescriptor cv_record; - - /* The next field is populated when a module's debug information resides - * in a DBG file. It identifies the DBG file. This field is effectively - * obsolete with modules built by recent toolchains. */ - MDLocationDescriptor misc_record; - - /* Alignment problem: reserved0 and reserved1 are defined by the platform - * SDK as 64-bit quantities. However, that results in a structure whose - * alignment is unpredictable on different CPUs and ABIs. If the ABI - * specifies full alignment of 64-bit quantities in structures (as ppc - * does), there will be padding between miscRecord and reserved0. If - * 64-bit quantities can be aligned on 32-bit boundaries (as on x86), - * this padding will not exist. (Note that the structure up to this point - * contains 1 64-bit member followed by 21 32-bit members.) - * As a workaround, reserved0 and reserved1 are instead defined here as - * four 32-bit quantities. This should be harmless, as there are - * currently no known uses for these fields. */ - uint32_t reserved0[2]; - uint32_t reserved1[2]; -} MDRawModule; /* MINIDUMP_MODULE */ - -/* The inclusion of a 64-bit type in MINIDUMP_MODULE forces the struct to - * be tail-padded out to a multiple of 64 bits under some ABIs (such as PPC). - * This doesn't occur on systems that don't tail-pad in this manner. Define - * this macro to be the usable size of the MDRawModule struct, and use it in - * place of sizeof(MDRawModule). */ -#define MD_MODULE_SIZE 108 - - -/* (MDRawModule).cv_record can reference MDCVInfoPDB20 or MDCVInfoPDB70. - * Ref.: http://www.debuginfo.com/articles/debuginfomatch.html - * MDCVInfoPDB70 is the expected structure type with recent toolchains. */ - -typedef struct { - uint32_t signature; - uint32_t offset; /* Offset to debug data (expect 0 in minidump) */ -} MDCVHeader; - -typedef struct { - MDCVHeader cv_header; - uint32_t signature; /* time_t debug information created */ - uint32_t age; /* revision of PDB file */ - uint8_t pdb_file_name[1]; /* Pathname or filename of PDB file */ -} MDCVInfoPDB20; - -static const size_t MDCVInfoPDB20_minsize = offsetof(MDCVInfoPDB20, - pdb_file_name[0]); - -#define MD_CVINFOPDB20_SIGNATURE 0x3031424e /* cvHeader.signature = '01BN' */ - -typedef struct { - uint32_t cv_signature; - MDGUID signature; /* GUID, identifies PDB file */ - uint32_t age; /* Identifies incremental changes to PDB file */ - uint8_t pdb_file_name[1]; /* Pathname or filename of PDB file, - * 0-terminated 8-bit character data (UTF-8?) */ -} MDCVInfoPDB70; - -static const size_t MDCVInfoPDB70_minsize = offsetof(MDCVInfoPDB70, - pdb_file_name[0]); - -#define MD_CVINFOPDB70_SIGNATURE 0x53445352 /* cvSignature = 'SDSR' */ - -typedef struct { - uint32_t data1[2]; - uint32_t data2; - uint32_t data3; - uint32_t data4; - uint32_t data5[3]; - uint8_t extra[2]; -} MDCVInfoELF; - -/* In addition to the two CodeView record formats above, used for linking - * to external pdb files, it is possible for debugging data to be carried - * directly in the CodeView record itself. These signature values will - * be found in the first 4 bytes of the CodeView record. Additional values - * not commonly experienced in the wild are given by "Microsoft Symbol and - * Type Information", http://www.x86.org/ftp/manuals/tools/sym.pdf, section - * 7.2. An in-depth description of the CodeView 4.1 format is given by - * "Undocumented Windows 2000 Secrets", Windows 2000 Debugging Support/ - * Microsoft Symbol File Internals/CodeView Subsections, - * http://www.rawol.com/features/undocumented/sbs-w2k-1-windows-2000-debugging-support.pdf - */ -#define MD_CVINFOCV41_SIGNATURE 0x3930424e /* '90BN', CodeView 4.10. */ -#define MD_CVINFOCV50_SIGNATURE 0x3131424e /* '11BN', CodeView 5.0, - * MS C7-format (/Z7). */ - -#define MD_CVINFOUNKNOWN_SIGNATURE 0xffffffff /* An unlikely value. */ - -/* (MDRawModule).miscRecord can reference MDImageDebugMisc. The Windows - * structure is actually defined in WinNT.h. This structure is effectively - * obsolete with modules built by recent toolchains. */ - -typedef struct { - uint32_t data_type; /* IMAGE_DEBUG_TYPE_*, not defined here because - * this debug record type is mostly obsolete. */ - uint32_t length; /* Length of entire MDImageDebugMisc structure */ - uint8_t unicode; /* True if data is multibyte */ - uint8_t reserved[3]; - uint8_t data[1]; -} MDImageDebugMisc; /* IMAGE_DEBUG_MISC */ - -static const size_t MDImageDebugMisc_minsize = offsetof(MDImageDebugMisc, - data[0]); - - -typedef struct { - uint32_t number_of_modules; - MDRawModule modules[1]; -} MDRawModuleList; /* MINIDUMP_MODULE_LIST */ - -static const size_t MDRawModuleList_minsize = offsetof(MDRawModuleList, - modules[0]); - - -typedef struct { - uint32_t number_of_memory_ranges; - MDMemoryDescriptor memory_ranges[1]; -} MDRawMemoryList; /* MINIDUMP_MEMORY_LIST */ - -static const size_t MDRawMemoryList_minsize = offsetof(MDRawMemoryList, - memory_ranges[0]); - - -#define MD_EXCEPTION_MAXIMUM_PARAMETERS 15 - -typedef struct { - uint32_t exception_code; /* Windows: MDExceptionCodeWin, - * Mac OS X: MDExceptionMac, - * Linux: MDExceptionCodeLinux. */ - uint32_t exception_flags; /* Windows: 1 if noncontinuable, - Mac OS X: MDExceptionCodeMac. */ - uint64_t exception_record; /* Address (in the minidump-producing host's - * memory) of another MDException, for - * nested exceptions. */ - uint64_t exception_address; /* The address that caused the exception. - * Mac OS X: exception subcode (which is - * typically the address). */ - uint32_t number_parameters; /* Number of valid elements in - * exception_information. */ - uint32_t __align; - uint64_t exception_information[MD_EXCEPTION_MAXIMUM_PARAMETERS]; -} MDException; /* MINIDUMP_EXCEPTION */ - -#include "minidump_exception_linux.h" -#include "minidump_exception_mac.h" -#include "minidump_exception_ps3.h" -#include "minidump_exception_solaris.h" -#include "minidump_exception_win32.h" - -typedef struct { - uint32_t thread_id; /* Thread in which the exception - * occurred. Corresponds to - * (MDRawThread).thread_id. */ - uint32_t __align; - MDException exception_record; - MDLocationDescriptor thread_context; /* MDRawContext[CPU] */ -} MDRawExceptionStream; /* MINIDUMP_EXCEPTION_STREAM */ - - -typedef union { - struct { - uint32_t vendor_id[3]; /* cpuid 0: ebx, edx, ecx */ - uint32_t version_information; /* cpuid 1: eax */ - uint32_t feature_information; /* cpuid 1: edx */ - uint32_t amd_extended_cpu_features; /* cpuid 0x80000001, ebx */ - } x86_cpu_info; - struct { - uint32_t cpuid; - uint32_t elf_hwcaps; /* linux specific, 0 otherwise */ - } arm_cpu_info; - struct { - uint64_t processor_features[2]; - } other_cpu_info; -} MDCPUInformation; /* CPU_INFORMATION */ - -/* For (MDCPUInformation).arm_cpu_info.elf_hwcaps. - * This matches the Linux kernel definitions from */ -typedef enum { - MD_CPU_ARM_ELF_HWCAP_SWP = (1 << 0), - MD_CPU_ARM_ELF_HWCAP_HALF = (1 << 1), - MD_CPU_ARM_ELF_HWCAP_THUMB = (1 << 2), - MD_CPU_ARM_ELF_HWCAP_26BIT = (1 << 3), - MD_CPU_ARM_ELF_HWCAP_FAST_MULT = (1 << 4), - MD_CPU_ARM_ELF_HWCAP_FPA = (1 << 5), - MD_CPU_ARM_ELF_HWCAP_VFP = (1 << 6), - MD_CPU_ARM_ELF_HWCAP_EDSP = (1 << 7), - MD_CPU_ARM_ELF_HWCAP_JAVA = (1 << 8), - MD_CPU_ARM_ELF_HWCAP_IWMMXT = (1 << 9), - MD_CPU_ARM_ELF_HWCAP_CRUNCH = (1 << 10), - MD_CPU_ARM_ELF_HWCAP_THUMBEE = (1 << 11), - MD_CPU_ARM_ELF_HWCAP_NEON = (1 << 12), - MD_CPU_ARM_ELF_HWCAP_VFPv3 = (1 << 13), - MD_CPU_ARM_ELF_HWCAP_VFPv3D16 = (1 << 14), - MD_CPU_ARM_ELF_HWCAP_TLS = (1 << 15), - MD_CPU_ARM_ELF_HWCAP_VFPv4 = (1 << 16), - MD_CPU_ARM_ELF_HWCAP_IDIVA = (1 << 17), - MD_CPU_ARM_ELF_HWCAP_IDIVT = (1 << 18), -} MDCPUInformationARMElfHwCaps; - -typedef struct { - /* The next 3 fields and numberOfProcessors are from the SYSTEM_INFO - * structure as returned by GetSystemInfo */ - uint16_t processor_architecture; - uint16_t processor_level; /* x86: 5 = 586, 6 = 686, ... */ - /* ARM: 6 = ARMv6, 7 = ARMv7 ... */ - uint16_t processor_revision; /* x86: 0xMMSS, where MM=model, - * SS=stepping */ - /* ARM: 0 */ - - uint8_t number_of_processors; - uint8_t product_type; /* Windows: VER_NT_* from WinNT.h */ - - /* The next 5 fields are from the OSVERSIONINFO structure as returned - * by GetVersionEx */ - uint32_t major_version; - uint32_t minor_version; - uint32_t build_number; - uint32_t platform_id; - MDRVA csd_version_rva; /* MDString further identifying the - * host OS. - * Windows: name of the installed OS - * service pack. - * Mac OS X: the Apple OS build number - * (sw_vers -buildVersion). - * Linux: uname -srvmo */ - - uint16_t suite_mask; /* Windows: VER_SUITE_* from WinNT.h */ - uint16_t reserved2; - - MDCPUInformation cpu; -} MDRawSystemInfo; /* MINIDUMP_SYSTEM_INFO */ - -/* For (MDRawSystemInfo).processor_architecture: */ -typedef enum { - MD_CPU_ARCHITECTURE_X86 = 0, /* PROCESSOR_ARCHITECTURE_INTEL */ - MD_CPU_ARCHITECTURE_MIPS = 1, /* PROCESSOR_ARCHITECTURE_MIPS */ - MD_CPU_ARCHITECTURE_ALPHA = 2, /* PROCESSOR_ARCHITECTURE_ALPHA */ - MD_CPU_ARCHITECTURE_PPC = 3, /* PROCESSOR_ARCHITECTURE_PPC */ - MD_CPU_ARCHITECTURE_SHX = 4, /* PROCESSOR_ARCHITECTURE_SHX - * (Super-H) */ - MD_CPU_ARCHITECTURE_ARM = 5, /* PROCESSOR_ARCHITECTURE_ARM */ - MD_CPU_ARCHITECTURE_IA64 = 6, /* PROCESSOR_ARCHITECTURE_IA64 */ - MD_CPU_ARCHITECTURE_ALPHA64 = 7, /* PROCESSOR_ARCHITECTURE_ALPHA64 */ - MD_CPU_ARCHITECTURE_MSIL = 8, /* PROCESSOR_ARCHITECTURE_MSIL - * (Microsoft Intermediate Language) */ - MD_CPU_ARCHITECTURE_AMD64 = 9, /* PROCESSOR_ARCHITECTURE_AMD64 */ - MD_CPU_ARCHITECTURE_X86_WIN64 = 10, - /* PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 (WoW64) */ - MD_CPU_ARCHITECTURE_SPARC = 0x8001, /* Breakpad-defined value for SPARC */ - MD_CPU_ARCHITECTURE_PPC64 = 0x8002, /* Breakpad-defined value for PPC64 */ - MD_CPU_ARCHITECTURE_ARM64 = 0x8003, /* Breakpad-defined value for ARM64 */ - MD_CPU_ARCHITECTURE_UNKNOWN = 0xffff /* PROCESSOR_ARCHITECTURE_UNKNOWN */ -} MDCPUArchitecture; - -/* For (MDRawSystemInfo).platform_id: */ -typedef enum { - MD_OS_WIN32S = 0, /* VER_PLATFORM_WIN32s (Windows 3.1) */ - MD_OS_WIN32_WINDOWS = 1, /* VER_PLATFORM_WIN32_WINDOWS (Windows 95-98-Me) */ - MD_OS_WIN32_NT = 2, /* VER_PLATFORM_WIN32_NT (Windows NT, 2000+) */ - MD_OS_WIN32_CE = 3, /* VER_PLATFORM_WIN32_CE, VER_PLATFORM_WIN32_HH - * (Windows CE, Windows Mobile, "Handheld") */ - - /* The following values are Breakpad-defined. */ - MD_OS_UNIX = 0x8000, /* Generic Unix-ish */ - MD_OS_MAC_OS_X = 0x8101, /* Mac OS X/Darwin */ - MD_OS_IOS = 0x8102, /* iOS */ - MD_OS_LINUX = 0x8201, /* Linux */ - MD_OS_SOLARIS = 0x8202, /* Solaris */ - MD_OS_ANDROID = 0x8203, /* Android */ - MD_OS_PS3 = 0x8204, /* PS3 */ - MD_OS_NACL = 0x8205 /* Native Client (NaCl) */ -} MDOSPlatform; - -typedef struct { - uint16_t year; - uint16_t month; - uint16_t day_of_week; - uint16_t day; - uint16_t hour; - uint16_t minute; - uint16_t second; - uint16_t milliseconds; -} MDSystemTime; /* SYSTEMTIME */ - -typedef struct { - /* Required field. The bias is the difference, in minutes, between - * Coordinated Universal Time (UTC) and local time. - * Formula: UTC = local time + bias */ - int32_t bias; - /* A description for standard time. For example, "EST" could indicate Eastern - * Standard Time. In practice this contains the full time zone names. This - * string can be empty. */ - uint16_t standard_name[32]; /* UTF-16-encoded, 0-terminated */ - /* A MDSystemTime structure that contains a date and local time when the - * transition from daylight saving time to standard time occurs on this - * operating system. If the time zone does not support daylight saving time, - * the month member in the MDSystemTime structure is zero. */ - MDSystemTime standard_date; - /* The bias value to be used during local time translations that occur during - * standard time. */ - int32_t standard_bias; - /* A description for daylight saving time. For example, "PDT" could indicate - * Pacific Daylight Time. In practice this contains the full time zone names. - * This string can be empty. */ - uint16_t daylight_name[32]; /* UTF-16-encoded, 0-terminated */ - /* A MDSystemTime structure that contains a date and local time when the - * transition from standard time to daylight saving time occurs on this - * operating system. If the time zone does not support daylight saving time, - * the month member in the MDSystemTime structure is zero.*/ - MDSystemTime daylight_date; - /* The bias value to be used during local time translations that occur during - * daylight saving time. */ - int32_t daylight_bias; -} MDTimeZoneInformation; /* TIME_ZONE_INFORMATION */ - -/* MAX_PATH from windef.h */ -#define MD_MAX_PATH 260 - -/* The miscellaneous information stream contains a variety - * of small pieces of information. A member is valid if - * it's within the available size and its corresponding - * bit is set. */ -typedef struct { - uint32_t size_of_info; /* Length of entire MDRawMiscInfo structure. */ - uint32_t flags1; - - /* The next field is only valid if flags1 contains - * MD_MISCINFO_FLAGS1_PROCESS_ID. */ - uint32_t process_id; - - /* The next 3 fields are only valid if flags1 contains - * MD_MISCINFO_FLAGS1_PROCESS_TIMES. */ - uint32_t process_create_time; /* time_t process started */ - uint32_t process_user_time; /* seconds of user CPU time */ - uint32_t process_kernel_time; /* seconds of kernel CPU time */ - - /* The following fields are not present in MINIDUMP_MISC_INFO but are - * in MINIDUMP_MISC_INFO_2. When this struct is populated, these values - * may not be set. Use flags1 and size_of_info to determine whether these - * values are present. These are only valid when flags1 contains - * MD_MISCINFO_FLAGS1_PROCESSOR_POWER_INFO. */ - uint32_t processor_max_mhz; - uint32_t processor_current_mhz; - uint32_t processor_mhz_limit; - uint32_t processor_max_idle_state; - uint32_t processor_current_idle_state; - - /* The following fields are not present in MINIDUMP_MISC_INFO_2 but are - * in MINIDUMP_MISC_INFO_3. When this struct is populated, these values - * may not be set. Use flags1 and size_of_info to determine whether these - * values are present. */ - - /* The following field is only valid if flags1 contains - * MD_MISCINFO_FLAGS1_PROCESS_INTEGRITY. */ - uint32_t process_integrity_level; - - /* The following field is only valid if flags1 contains - * MD_MISCINFO_FLAGS1_PROCESS_EXECUTE_FLAGS. */ - uint32_t process_execute_flags; - - /* The following field is only valid if flags1 contains - * MD_MISCINFO_FLAGS1_PROTECTED_PROCESS. */ - uint32_t protected_process; - - /* The following 2 fields are only valid if flags1 contains - * MD_MISCINFO_FLAGS1_TIMEZONE. */ - uint32_t time_zone_id; - MDTimeZoneInformation time_zone; - - /* The following fields are not present in MINIDUMP_MISC_INFO_3 but are - * in MINIDUMP_MISC_INFO_4. When this struct is populated, these values - * may not be set. Use flags1 and size_of_info to determine whether these - * values are present. */ - - /* The following 2 fields are only valid if flags1 contains - * MD_MISCINFO_FLAGS1_BUILDSTRING. */ - uint16_t build_string[MD_MAX_PATH]; /* UTF-16-encoded, 0-terminated */ - uint16_t dbg_bld_str[40]; /* UTF-16-encoded, 0-terminated */ -} MDRawMiscInfo; /* MINIDUMP_MISC_INFO, MINIDUMP_MISC_INFO_2, - * MINIDUMP_MISC_INFO_3, MINIDUMP_MISC_INFO_4, - * MINIDUMP_MISC_INFO_N */ - -static const size_t MD_MISCINFO_SIZE = - offsetof(MDRawMiscInfo, processor_max_mhz); -static const size_t MD_MISCINFO2_SIZE = - offsetof(MDRawMiscInfo, process_integrity_level); -static const size_t MD_MISCINFO3_SIZE = - offsetof(MDRawMiscInfo, build_string[0]); -static const size_t MD_MISCINFO4_SIZE = sizeof(MDRawMiscInfo); - -/* For (MDRawMiscInfo).flags1. These values indicate which fields in the - * MDRawMiscInfoStructure are valid. */ -typedef enum { - MD_MISCINFO_FLAGS1_PROCESS_ID = 0x00000001, - /* MINIDUMP_MISC1_PROCESS_ID */ - MD_MISCINFO_FLAGS1_PROCESS_TIMES = 0x00000002, - /* MINIDUMP_MISC1_PROCESS_TIMES */ - MD_MISCINFO_FLAGS1_PROCESSOR_POWER_INFO = 0x00000004, - /* MINIDUMP_MISC1_PROCESSOR_POWER_INFO */ - MD_MISCINFO_FLAGS1_PROCESS_INTEGRITY = 0x00000010, - /* MINIDUMP_MISC3_PROCESS_INTEGRITY */ - MD_MISCINFO_FLAGS1_PROCESS_EXECUTE_FLAGS = 0x00000020, - /* MINIDUMP_MISC3_PROCESS_EXECUTE_FLAGS */ - MD_MISCINFO_FLAGS1_TIMEZONE = 0x00000040, - /* MINIDUMP_MISC3_TIMEZONE */ - MD_MISCINFO_FLAGS1_PROTECTED_PROCESS = 0x00000080, - /* MINIDUMP_MISC3_PROTECTED_PROCESS */ - MD_MISCINFO_FLAGS1_BUILDSTRING = 0x00000100, - /* MINIDUMP_MISC4_BUILDSTRING */ -} MDMiscInfoFlags1; - -/* - * Around DbgHelp version 6.0, the style of new LIST structures changed - * from including an array of length 1 at the end of the struct to - * represent the variable-length data to including explicit - * "size of header", "size of entry" and "number of entries" fields - * in the header, presumably to allow backwards-compatibly-extending - * the structures in the future. The actual list entries follow the - * header data directly in this case. - */ - -typedef struct { - uint32_t size_of_header; /* sizeof(MDRawMemoryInfoList) */ - uint32_t size_of_entry; /* sizeof(MDRawMemoryInfo) */ - uint64_t number_of_entries; -} MDRawMemoryInfoList; /* MINIDUMP_MEMORY_INFO_LIST */ - -typedef struct { - uint64_t base_address; /* Base address of a region of pages */ - uint64_t allocation_base; /* Base address of a range of pages - * within this region. */ - uint32_t allocation_protection; /* Memory protection when this region - * was originally allocated: - * MDMemoryProtection */ - uint32_t __alignment1; - uint64_t region_size; - uint32_t state; /* MDMemoryState */ - uint32_t protection; /* MDMemoryProtection */ - uint32_t type; /* MDMemoryType */ - uint32_t __alignment2; -} MDRawMemoryInfo; /* MINIDUMP_MEMORY_INFO */ - -/* For (MDRawMemoryInfo).state */ -typedef enum { - MD_MEMORY_STATE_COMMIT = 0x1000, /* physical storage has been allocated */ - MD_MEMORY_STATE_RESERVE = 0x2000, /* reserved, but no physical storage */ - MD_MEMORY_STATE_FREE = 0x10000 /* available to be allocated */ -} MDMemoryState; - -/* For (MDRawMemoryInfo).allocation_protection and .protection */ -typedef enum { - MD_MEMORY_PROTECT_NOACCESS = 0x01, /* PAGE_NOACCESS */ - MD_MEMORY_PROTECT_READONLY = 0x02, /* PAGE_READONLY */ - MD_MEMORY_PROTECT_READWRITE = 0x04, /* PAGE_READWRITE */ - MD_MEMORY_PROTECT_WRITECOPY = 0x08, /* PAGE_WRITECOPY */ - MD_MEMORY_PROTECT_EXECUTE = 0x10, /* PAGE_EXECUTE */ - MD_MEMORY_PROTECT_EXECUTE_READ = 0x20, /* PAGE_EXECUTE_READ */ - MD_MEMORY_PROTECT_EXECUTE_READWRITE = 0x40, /* PAGE_EXECUTE_READWRITE */ - MD_MEMORY_PROTECT_EXECUTE_WRITECOPY = 0x80, /* PAGE_EXECUTE_WRITECOPY */ - /* These options can be combined with the previous flags. */ - MD_MEMORY_PROTECT_GUARD = 0x100, /* PAGE_GUARD */ - MD_MEMORY_PROTECT_NOCACHE = 0x200, /* PAGE_NOCACHE */ - MD_MEMORY_PROTECT_WRITECOMBINE = 0x400, /* PAGE_WRITECOMBINE */ -} MDMemoryProtection; - -/* Used to mask the mutually exclusive options from the combinable flags. */ -const uint32_t MD_MEMORY_PROTECTION_ACCESS_MASK = 0xFF; - -/* For (MDRawMemoryInfo).type */ -typedef enum { - MD_MEMORY_TYPE_PRIVATE = 0x20000, /* not shared by other processes */ - MD_MEMORY_TYPE_MAPPED = 0x40000, /* mapped into the view of a section */ - MD_MEMORY_TYPE_IMAGE = 0x1000000 /* mapped into the view of an image */ -} MDMemoryType; - -/* - * Breakpad extension types - */ - - -typedef struct { - /* validity is a bitmask with values from MDBreakpadInfoValidity, indicating - * which of the other fields in the structure are valid. */ - uint32_t validity; - - /* Thread ID of the handler thread. dump_thread_id should correspond to - * the thread_id of an MDRawThread in the minidump's MDRawThreadList if - * a dedicated thread in that list was used to produce the minidump. If - * the MDRawThreadList does not contain a dedicated thread used to produce - * the minidump, this field should be set to 0 and the validity field - * must not contain MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID. */ - uint32_t dump_thread_id; - - /* Thread ID of the thread that requested the minidump be produced. As - * with dump_thread_id, requesting_thread_id should correspond to the - * thread_id of an MDRawThread in the minidump's MDRawThreadList. For - * minidumps produced as a result of an exception, requesting_thread_id - * will be the same as the MDRawExceptionStream's thread_id field. For - * minidumps produced "manually" at the program's request, - * requesting_thread_id will indicate which thread caused the dump to be - * written. If the minidump was produced at the request of something - * other than a thread in the MDRawThreadList, this field should be set - * to 0 and the validity field must not contain - * MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID. */ - uint32_t requesting_thread_id; -} MDRawBreakpadInfo; - -/* For (MDRawBreakpadInfo).validity: */ -typedef enum { - /* When set, the dump_thread_id field is valid. */ - MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID = 1 << 0, - - /* When set, the requesting_thread_id field is valid. */ - MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID = 1 << 1 -} MDBreakpadInfoValidity; - -typedef struct { - /* expression, function, and file are 0-terminated UTF-16 strings. They - * may be truncated if necessary, but should always be 0-terminated when - * written to a file. - * Fixed-length strings are used because MiniDumpWriteDump doesn't offer - * a way for user streams to point to arbitrary RVAs for strings. */ - uint16_t expression[128]; /* Assertion that failed... */ - uint16_t function[128]; /* ...within this function... */ - uint16_t file[128]; /* ...in this file... */ - uint32_t line; /* ...at this line. */ - uint32_t type; -} MDRawAssertionInfo; - -/* For (MDRawAssertionInfo).type: */ -typedef enum { - MD_ASSERTION_INFO_TYPE_UNKNOWN = 0, - - /* Used for assertions that would be raised by the MSVC CRT but are - * directed to an invalid parameter handler instead. */ - MD_ASSERTION_INFO_TYPE_INVALID_PARAMETER, - - /* Used for assertions that would be raised by the MSVC CRT but are - * directed to a pure virtual call handler instead. */ - MD_ASSERTION_INFO_TYPE_PURE_VIRTUAL_CALL -} MDAssertionInfoData; - -/* These structs are used to store the DSO debug data in Linux minidumps, - * which is necessary for converting minidumps to usable coredumps. - * Because of a historical accident, several fields are variably encoded - * according to client word size, so tools potentially need to support both. */ - -typedef struct { - uint32_t addr; - MDRVA name; - uint32_t ld; -} MDRawLinkMap32; - -typedef struct { - uint32_t version; - MDRVA map; /* array of MDRawLinkMap32 */ - uint32_t dso_count; - uint32_t brk; - uint32_t ldbase; - uint32_t dynamic; -} MDRawDebug32; - -typedef struct { - uint64_t addr; - MDRVA name; - uint64_t ld; -} MDRawLinkMap64; - -typedef struct { - uint32_t version; - MDRVA map; /* array of MDRawLinkMap64 */ - uint32_t dso_count; - uint64_t brk; - uint64_t ldbase; - uint64_t dynamic; -} MDRawDebug64; - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif /* _MSC_VER */ - - -#endif /* GOOGLE_BREAKPAD_COMMON_MINIDUMP_FORMAT_H__ */ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_size.h b/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_size.h deleted file mode 100644 index 918544b66..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/common/minidump_size.h +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) 2007, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -// minidump_size.h: Provides a C++ template for programmatic access to -// the sizes of various types defined in minidump_format.h. -// -// Author: Mark Mentovai - -#ifndef GOOGLE_BREAKPAD_COMMON_MINIDUMP_SIZE_H__ -#define GOOGLE_BREAKPAD_COMMON_MINIDUMP_SIZE_H__ - -#include - -#include "google_breakpad/common/minidump_format.h" - -namespace google_breakpad { - -template -class minidump_size { - public: - static size_t size() { return sizeof(T); } -}; - -// Explicit specializations for variable-length types. The size returned -// for these should be the size for an object without its variable-length -// section. - -template<> -class minidump_size { - public: - static size_t size() { return MDString_minsize; } -}; - -template<> -class minidump_size { - public: - static size_t size() { return MDRawThreadList_minsize; } -}; - -template<> -class minidump_size { - public: - static size_t size() { return MDCVInfoPDB20_minsize; } -}; - -template<> -class minidump_size { - public: - static size_t size() { return MDCVInfoPDB70_minsize; } -}; - -template<> -class minidump_size { - public: - static size_t size() { return MDImageDebugMisc_minsize; } -}; - -template<> -class minidump_size { - public: - static size_t size() { return MDRawModuleList_minsize; } -}; - -template<> -class minidump_size { - public: - static size_t size() { return MDRawMemoryList_minsize; } -}; - -// Explicit specialization for MDRawModule, for which sizeof may include -// tail-padding on some architectures but not others. - -template<> -class minidump_size { - public: - static size_t size() { return MD_MODULE_SIZE; } -}; - -} // namespace google_breakpad - -#endif // GOOGLE_BREAKPAD_COMMON_MINIDUMP_SIZE_H__ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/processor/basic_source_line_resolver.h b/TMessagesProj/jni/breakpad/google_breakpad/processor/basic_source_line_resolver.h deleted file mode 100644 index 6bb6d8639..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/processor/basic_source_line_resolver.h +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) 2010 Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// basic_source_line_resolver.h: BasicSourceLineResolver is derived from -// SourceLineResolverBase, and is a concrete implementation of -// SourceLineResolverInterface, using address map files produced by a -// compatible writer, e.g. PDBSourceLineWriter. -// -// see "processor/source_line_resolver_base.h" -// and "source_line_resolver_interface.h" for more documentation. - -#ifndef GOOGLE_BREAKPAD_PROCESSOR_BASIC_SOURCE_LINE_RESOLVER_H__ -#define GOOGLE_BREAKPAD_PROCESSOR_BASIC_SOURCE_LINE_RESOLVER_H__ - -#include -#include - -#include "common/using_std_string.h" -#include "google_breakpad/processor/source_line_resolver_base.h" - -namespace google_breakpad { - -using std::map; - -class BasicSourceLineResolver : public SourceLineResolverBase { - public: - BasicSourceLineResolver(); - virtual ~BasicSourceLineResolver() { } - - using SourceLineResolverBase::LoadModule; - using SourceLineResolverBase::LoadModuleUsingMapBuffer; - using SourceLineResolverBase::LoadModuleUsingMemoryBuffer; - using SourceLineResolverBase::ShouldDeleteMemoryBufferAfterLoadModule; - using SourceLineResolverBase::UnloadModule; - using SourceLineResolverBase::HasModule; - using SourceLineResolverBase::IsModuleCorrupt; - using SourceLineResolverBase::FillSourceLineInfo; - using SourceLineResolverBase::FindWindowsFrameInfo; - using SourceLineResolverBase::FindCFIFrameInfo; - - private: - // friend declarations: - friend class BasicModuleFactory; - friend class ModuleComparer; - friend class ModuleSerializer; - template friend class SimpleSerializer; - - // Function derives from SourceLineResolverBase::Function. - struct Function; - // Module implements SourceLineResolverBase::Module interface. - class Module; - - // Disallow unwanted copy ctor and assignment operator - BasicSourceLineResolver(const BasicSourceLineResolver&); - void operator=(const BasicSourceLineResolver&); -}; - -// Helper class, containing useful methods for parsing of Breakpad symbol files. -class SymbolParseHelper { - public: - // Parses a |file_line| declaration. Returns true on success. - // Format: FILE . - // Notice, that this method modifies the input |file_line| which is why it - // can't be const. On success, , and are stored in |*index|, - // and |*filename|. No allocation is done, |*filename| simply points inside - // |file_line|. - static bool ParseFile(char *file_line, // in - long *index, // out - char **filename); // out - - // Parses a |function_line| declaration. Returns true on success. - // Format: FUNC
. - // Notice, that this method modifies the input |function_line| which is why it - // can't be const. On success,
, , , and - // are stored in |*address|, |*size|, |*stack_param_size|, and |*name|. - // No allocation is done, |*name| simply points inside |function_line|. - static bool ParseFunction(char *function_line, // in - uint64_t *address, // out - uint64_t *size, // out - long *stack_param_size, // out - char **name); // out - - // Parses a |line| declaration. Returns true on success. - // Format:
- // Notice, that this method modifies the input |function_line| which is why - // it can't be const. On success,
, , , and - // are stored in |*address|, |*size|, |*line_number|, and - // |*source_file|. - static bool ParseLine(char *line_line, // in - uint64_t *address, // out - uint64_t *size, // out - long *line_number, // out - long *source_file); // out - - // Parses a |public_line| declaration. Returns true on success. - // Format: PUBLIC
- // Notice, that this method modifies the input |function_line| which is why - // it can't be const. On success,
, , - // are stored in |*address|, |*stack_param_size|, and |*name|. - // No allocation is done, |*name| simply points inside |public_line|. - static bool ParsePublicSymbol(char *public_line, // in - uint64_t *address, // out - long *stack_param_size, // out - char **name); // out - - private: - // Used for success checks after strtoull and strtol. - static bool IsValidAfterNumber(char *after_number); - - // Only allow static methods. - SymbolParseHelper(); - SymbolParseHelper(const SymbolParseHelper&); - void operator=(const SymbolParseHelper&); -}; - -} // namespace google_breakpad - -#endif // GOOGLE_BREAKPAD_PROCESSOR_BASIC_SOURCE_LINE_RESOLVER_H__ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/processor/call_stack.h b/TMessagesProj/jni/breakpad/google_breakpad/processor/call_stack.h deleted file mode 100644 index 21f595e7b..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/processor/call_stack.h +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) 2006, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// call_stack.h: A call stack comprised of stack frames. -// -// This class manages a vector of stack frames. It is used instead of -// exposing the vector directly to allow the CallStack to own StackFrame -// pointers without having to publicly export the linked_ptr class. A -// CallStack must be composed of pointers instead of objects to allow for -// CPU-specific StackFrame subclasses. -// -// By convention, the stack frame at index 0 is the innermost callee frame, -// and the frame at the highest index in a call stack is the outermost -// caller. CallStack only allows stacks to be built by pushing frames, -// beginning with the innermost callee frame. -// -// Author: Mark Mentovai - -#ifndef GOOGLE_BREAKPAD_PROCESSOR_CALL_STACK_H__ -#define GOOGLE_BREAKPAD_PROCESSOR_CALL_STACK_H__ - -#include - -namespace google_breakpad { - -using std::vector; - -struct StackFrame; -template class linked_ptr; - -class CallStack { - public: - CallStack() { Clear(); } - ~CallStack(); - - // Resets the CallStack to its initial empty state - void Clear(); - - const vector* frames() const { return &frames_; } - - private: - // Stackwalker is responsible for building the frames_ vector. - friend class Stackwalker; - - // Storage for pushed frames. - vector frames_; -}; - -} // namespace google_breakpad - -#endif // GOOGLE_BREAKPAD_PROCSSOR_CALL_STACK_H__ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/processor/code_module.h b/TMessagesProj/jni/breakpad/google_breakpad/processor/code_module.h deleted file mode 100644 index 4e8928243..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/processor/code_module.h +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) 2006, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// code_module.h: Carries information about code modules that are loaded -// into a process. -// -// Author: Mark Mentovai - -#ifndef GOOGLE_BREAKPAD_PROCESSOR_CODE_MODULE_H__ -#define GOOGLE_BREAKPAD_PROCESSOR_CODE_MODULE_H__ - -#include - -#include "common/using_std_string.h" -#include "google_breakpad/common/breakpad_types.h" - -namespace google_breakpad { - -class CodeModule { - public: - virtual ~CodeModule() {} - - // The base address of this code module as it was loaded by the process. - // (uint64_t)-1 on error. - virtual uint64_t base_address() const = 0; - - // The size of the code module. 0 on error. - virtual uint64_t size() const = 0; - - // The path or file name that the code module was loaded from. Empty on - // error. - virtual string code_file() const = 0; - - // An identifying string used to discriminate between multiple versions and - // builds of the same code module. This may contain a uuid, timestamp, - // version number, or any combination of this or other information, in an - // implementation-defined format. Empty on error. - virtual string code_identifier() const = 0; - - // The filename containing debugging information associated with the code - // module. If debugging information is stored in a file separate from the - // code module itself (as is the case when .pdb or .dSYM files are used), - // this will be different from code_file. If debugging information is - // stored in the code module itself (possibly prior to stripping), this - // will be the same as code_file. Empty on error. - virtual string debug_file() const = 0; - - // An identifying string similar to code_identifier, but identifies a - // specific version and build of the associated debug file. This may be - // the same as code_identifier when the debug_file and code_file are - // identical or when the same identifier is used to identify distinct - // debug and code files. - virtual string debug_identifier() const = 0; - - // A human-readable representation of the code module's version. Empty on - // error. - virtual string version() const = 0; - - // Creates a new copy of this CodeModule object, which the caller takes - // ownership of. The new CodeModule may be of a different concrete class - // than the CodeModule being copied, but will behave identically to the - // copied CodeModule as far as the CodeModule interface is concerned. - virtual const CodeModule* Copy() const = 0; -}; - -} // namespace google_breakpad - -#endif // GOOGLE_BREAKPAD_PROCESSOR_CODE_MODULE_H__ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/processor/code_modules.h b/TMessagesProj/jni/breakpad/google_breakpad/processor/code_modules.h deleted file mode 100644 index a38579af6..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/processor/code_modules.h +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) 2006, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// code_modules.h: Contains all of the CodeModule objects that were loaded -// into a single process. -// -// Author: Mark Mentovai - -#ifndef GOOGLE_BREAKPAD_PROCESSOR_CODE_MODULES_H__ -#define GOOGLE_BREAKPAD_PROCESSOR_CODE_MODULES_H__ - -#include "google_breakpad/common/breakpad_types.h" - -namespace google_breakpad { - -class CodeModule; - -class CodeModules { - public: - virtual ~CodeModules() {} - - // The number of contained CodeModule objects. - virtual unsigned int module_count() const = 0; - - // Random access to modules. Returns the module whose code is present - // at the address indicated by |address|. If no module is present at this - // address, returns NULL. Ownership of the returned CodeModule is retained - // by the CodeModules object; pointers returned by this method are valid for - // comparison with pointers returned by the other Get methods. - virtual const CodeModule* GetModuleForAddress(uint64_t address) const = 0; - - // Returns the module corresponding to the main executable. If there is - // no main executable, returns NULL. Ownership of the returned CodeModule - // is retained by the CodeModules object; pointers returned by this method - // are valid for comparison with pointers returned by the other Get - // methods. - virtual const CodeModule* GetMainModule() const = 0; - - // Sequential access to modules. A sequence number of 0 corresponds to the - // module residing lowest in memory. If the sequence number is out of - // range, returns NULL. Ownership of the returned CodeModule is retained - // by the CodeModules object; pointers returned by this method are valid for - // comparison with pointers returned by the other Get methods. - virtual const CodeModule* GetModuleAtSequence( - unsigned int sequence) const = 0; - - // Sequential access to modules. This is similar to GetModuleAtSequence, - // except no ordering requirement is enforced. A CodeModules implementation - // may return CodeModule objects from GetModuleAtIndex in any order it - // wishes, provided that the order remain the same throughout the life of - // the CodeModules object. Typically, GetModuleAtIndex would be used by - // a caller to enumerate all CodeModule objects quickly when the enumeration - // does not require any ordering. If the index argument is out of range, - // returns NULL. Ownership of the returned CodeModule is retained by - // the CodeModules object; pointers returned by this method are valid for - // comparison with pointers returned by the other Get methods. - virtual const CodeModule* GetModuleAtIndex(unsigned int index) const = 0; - - // Creates a new copy of this CodeModules object, which the caller takes - // ownership of. The new object will also contain copies of the existing - // object's child CodeModule objects. The new CodeModules object may be of - // a different concrete class than the object being copied, but will behave - // identically to the copied object as far as the CodeModules and CodeModule - // interfaces are concerned, except that the order that GetModuleAtIndex - // returns objects in may differ between a copy and the original CodeModules - // object. - virtual const CodeModules* Copy() const = 0; -}; - -} // namespace google_breakpad - -#endif // GOOGLE_BREAKPAD_PROCESSOR_CODE_MODULES_H__ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/processor/dump_context.h b/TMessagesProj/jni/breakpad/google_breakpad/processor/dump_context.h deleted file mode 100644 index df80bf7ef..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/processor/dump_context.h +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) 2014 Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// dump_context.h: A (mini/micro) dump CPU-specific context. - -#ifndef GOOGLE_BREAKPAD_PROCESSOR_DUMP_CONTEXT_H__ -#define GOOGLE_BREAKPAD_PROCESSOR_DUMP_CONTEXT_H__ - -#include "google_breakpad/common/minidump_format.h" -#include "google_breakpad/processor/dump_object.h" - -namespace google_breakpad { - -// DumpContext carries a CPU-specific MDRawContext structure, which contains CPU -// context such as register states. -class DumpContext : public DumpObject { - public: - virtual ~DumpContext(); - - // Returns an MD_CONTEXT_* value such as MD_CONTEXT_X86 or MD_CONTEXT_PPC - // identifying the CPU type that the context was collected from. The - // returned value will identify the CPU only, and will have any other - // MD_CONTEXT_* bits masked out. Returns 0 on failure. - uint32_t GetContextCPU() const; - - // Return the raw value of |context_flags_| - uint32_t GetContextFlags() const; - - // Returns raw CPU-specific context data for the named CPU type. If the - // context data does not match the CPU type or does not exist, returns NULL. - const MDRawContextAMD64* GetContextAMD64() const; - const MDRawContextARM* GetContextARM() const; - const MDRawContextARM64* GetContextARM64() const; - const MDRawContextMIPS* GetContextMIPS() const; - const MDRawContextPPC* GetContextPPC() const; - const MDRawContextPPC64* GetContextPPC64() const; - const MDRawContextSPARC* GetContextSPARC() const; - const MDRawContextX86* GetContextX86() const; - - // A convenience method to get the instruction pointer out of the - // MDRawContext, since it varies per-CPU architecture. - bool GetInstructionPointer(uint64_t* ip) const; - - // Similar to the GetInstructionPointer method, this method gets the stack - // pointer for all CPU architectures. - bool GetStackPointer(uint64_t* sp) const; - - // Print a human-readable representation of the object to stdout. - void Print(); - - protected: - DumpContext(); - - // Sets row CPU-specific context data for the names CPU type. - void SetContextFlags(uint32_t context_flags); - void SetContextX86(MDRawContextX86* x86); - void SetContextPPC(MDRawContextPPC* ppc); - void SetContextPPC64(MDRawContextPPC64* ppc64); - void SetContextAMD64(MDRawContextAMD64* amd64); - void SetContextSPARC(MDRawContextSPARC* ctx_sparc); - void SetContextARM(MDRawContextARM* arm); - void SetContextARM64(MDRawContextARM64* arm64); - void SetContextMIPS(MDRawContextMIPS* ctx_mips); - - // Free the CPU-specific context structure. - void FreeContext(); - - private: - // The CPU-specific context structure. - union { - MDRawContextBase* base; - MDRawContextX86* x86; - MDRawContextPPC* ppc; - MDRawContextPPC64* ppc64; - MDRawContextAMD64* amd64; - // on Solaris SPARC, sparc is defined as a numeric constant, - // so variables can NOT be named as sparc - MDRawContextSPARC* ctx_sparc; - MDRawContextARM* arm; - MDRawContextARM64* arm64; - MDRawContextMIPS* ctx_mips; - } context_; - - // Store this separately because of the weirdo AMD64 context - uint32_t context_flags_; -}; - -} // namespace google_breakpad - -#endif // GOOGLE_BREAKPAD_PROCESSOR_DUMP_CONTEXT_H__ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/processor/dump_object.h b/TMessagesProj/jni/breakpad/google_breakpad/processor/dump_object.h deleted file mode 100644 index 112f687f4..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/processor/dump_object.h +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2014 Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// dump_object.h: A base class for all mini/micro dump object. - -#ifndef GOOGLE_BREAKPAD_PROCESSOR_DUMP_OBJECT_H__ -#define GOOGLE_BREAKPAD_PROCESSOR_DUMP_OBJECT_H__ - -namespace google_breakpad { - -// DumpObject is the base of various mini/micro dump's objects. -class DumpObject { - public: - DumpObject(); - - bool valid() const { return valid_; } - - protected: - // DumpObjects are not valid when created. When a subclass populates its own - // fields, it can set valid_ to true. Accessors and mutators may wish to - // consider or alter the valid_ state as they interact with objects. - bool valid_; -}; - -} // namespace google_breakpad - -#endif // GOOGLE_BREAKPAD_PROCESSOR_DUMP_OBJECT_H__ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/processor/exploitability.h b/TMessagesProj/jni/breakpad/google_breakpad/processor/exploitability.h deleted file mode 100644 index 014413c94..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/processor/exploitability.h +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) 2010 Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// exploitability_engine.h: Generic exploitability engine. -// -// The Exploitability class is an abstract base class providing common -// generic methods that apply to exploitability engines for specific platforms. -// Specific implementations will extend this class by providing run -// methods to fill in the exploitability_ enumeration of the ProcessState -// for a crash. -// -// Author: Cris Neckar - -#ifndef GOOGLE_BREAKPAD_PROCESSOR_EXPLOITABILITY_H_ -#define GOOGLE_BREAKPAD_PROCESSOR_EXPLOITABILITY_H_ - -#include "google_breakpad/common/breakpad_types.h" -#include "google_breakpad/processor/minidump.h" -#include "google_breakpad/processor/process_state.h" - -namespace google_breakpad { - -class Exploitability { - public: - virtual ~Exploitability() {} - - static Exploitability *ExploitabilityForPlatform(Minidump *dump, - ProcessState *process_state); - - // The boolean parameter signals whether the exploitability engine is - // enabled to call out to objdump for disassembly. This is disabled by - // default. It is used to check the identity of the instruction that - // caused the program to crash. This should not be enabled if there are - // portability concerns. - static Exploitability *ExploitabilityForPlatform(Minidump *dump, - ProcessState *process_state, - bool enable_objdump); - - ExploitabilityRating CheckExploitability(); - bool AddressIsAscii(uint64_t); - - protected: - Exploitability(Minidump *dump, - ProcessState *process_state); - - Minidump *dump_; - ProcessState *process_state_; - SystemInfo *system_info_; - - private: - virtual ExploitabilityRating CheckPlatformExploitability() = 0; -}; - -} // namespace google_breakpad - -#endif // GOOGLE_BREAKPAD_PROCESSOR_EXPLOITABILITY_H_ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/processor/fast_source_line_resolver.h b/TMessagesProj/jni/breakpad/google_breakpad/processor/fast_source_line_resolver.h deleted file mode 100644 index fdf910776..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/processor/fast_source_line_resolver.h +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) 2010 Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// fast_source_line_resolver.h: FastSourceLineResolver is derived from -// SourceLineResolverBase, and is a concrete implementation of -// SourceLineResolverInterface. -// -// FastSourceLineResolver is a sibling class of BasicSourceLineResolver. The -// difference is FastSourceLineResolver loads a serialized memory chunk of data -// which can be used directly a Module without parsing or copying of underlying -// data. Therefore loading a symbol in FastSourceLineResolver is much faster -// and more memory-efficient than BasicSourceLineResolver. -// -// See "source_line_resolver_base.h" and -// "google_breakpad/source_line_resolver_interface.h" for more reference. -// -// Author: Siyang Xie (lambxsy@google.com) - -#ifndef GOOGLE_BREAKPAD_PROCESSOR_FAST_SOURCE_LINE_RESOLVER_H__ -#define GOOGLE_BREAKPAD_PROCESSOR_FAST_SOURCE_LINE_RESOLVER_H__ - -#include -#include - -#include "google_breakpad/processor/source_line_resolver_base.h" - -namespace google_breakpad { - -using std::map; - -class FastSourceLineResolver : public SourceLineResolverBase { - public: - FastSourceLineResolver(); - virtual ~FastSourceLineResolver() { } - - using SourceLineResolverBase::FillSourceLineInfo; - using SourceLineResolverBase::FindCFIFrameInfo; - using SourceLineResolverBase::FindWindowsFrameInfo; - using SourceLineResolverBase::HasModule; - using SourceLineResolverBase::IsModuleCorrupt; - using SourceLineResolverBase::LoadModule; - using SourceLineResolverBase::LoadModuleUsingMapBuffer; - using SourceLineResolverBase::LoadModuleUsingMemoryBuffer; - using SourceLineResolverBase::UnloadModule; - - private: - // Friend declarations. - friend class ModuleComparer; - friend class ModuleSerializer; - friend class FastModuleFactory; - - // Nested types that will derive from corresponding nested types defined in - // SourceLineResolverBase. - struct Line; - struct Function; - struct PublicSymbol; - class Module; - - // Deserialize raw memory data to construct a WindowsFrameInfo object. - static WindowsFrameInfo CopyWFI(const char *raw_memory); - - // FastSourceLineResolver requires the memory buffer stays alive during the - // lifetime of a corresponding module, therefore it needs to redefine this - // virtual method. - virtual bool ShouldDeleteMemoryBufferAfterLoadModule(); - - // Disallow unwanted copy ctor and assignment operator - FastSourceLineResolver(const FastSourceLineResolver&); - void operator=(const FastSourceLineResolver&); -}; - -} // namespace google_breakpad - -#endif // GOOGLE_BREAKPAD_PROCESSOR_FAST_SOURCE_LINE_RESOLVER_H__ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/processor/memory_region.h b/TMessagesProj/jni/breakpad/google_breakpad/processor/memory_region.h deleted file mode 100644 index 30f88df49..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/processor/memory_region.h +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) 2010 Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// memory_region.h: Access to memory regions. -// -// A MemoryRegion provides virtual access to a range of memory. It is an -// abstraction allowing the actual source of memory to be independent of -// methods which need to access a virtual memory space. -// -// Author: Mark Mentovai - -#ifndef GOOGLE_BREAKPAD_PROCESSOR_MEMORY_REGION_H__ -#define GOOGLE_BREAKPAD_PROCESSOR_MEMORY_REGION_H__ - - -#include "google_breakpad/common/breakpad_types.h" - - -namespace google_breakpad { - - -class MemoryRegion { - public: - virtual ~MemoryRegion() {} - - // The base address of this memory region. - virtual uint64_t GetBase() const = 0; - - // The size of this memory region. - virtual uint32_t GetSize() const = 0; - - // Access to data of various sizes within the memory region. address - // is a pointer to read, and it must lie within the memory region as - // defined by its base address and size. The location pointed to by - // value is set to the value at address. Byte-swapping is performed - // if necessary so that the value is appropriate for the running - // program. Returns true on success. Fails and returns false if address - // is out of the region's bounds (after considering the width of value), - // or for other types of errors. - virtual bool GetMemoryAtAddress(uint64_t address, uint8_t* value) const = 0; - virtual bool GetMemoryAtAddress(uint64_t address, uint16_t* value) const = 0; - virtual bool GetMemoryAtAddress(uint64_t address, uint32_t* value) const = 0; - virtual bool GetMemoryAtAddress(uint64_t address, uint64_t* value) const = 0; - - // Print a human-readable representation of the object to stdout. - virtual void Print() const = 0; -}; - - -} // namespace google_breakpad - - -#endif // GOOGLE_BREAKPAD_PROCESSOR_MEMORY_REGION_H__ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/processor/microdump.h b/TMessagesProj/jni/breakpad/google_breakpad/processor/microdump.h deleted file mode 100644 index abdaecb19..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/processor/microdump.h +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) 2014 Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// microdump.h: A microdump reader. Microdump is a minified variant of a -// minidump (see minidump.h for documentation) which contains the minimum -// amount of information required to get a stack trace for the crashing thread. -// The information contained in a microdump is: -// - the crashing thread stack -// - system information (os type / version) -// - cpu context (state of the registers) -// - list of mmaps - -#ifndef GOOGLE_BREAKPAD_PROCESSOR_MICRODUMP_H__ -#define GOOGLE_BREAKPAD_PROCESSOR_MICRODUMP_H__ - -#include -#include - -#include "common/scoped_ptr.h" -#include "common/using_std_string.h" -#include "google_breakpad/processor/dump_context.h" -#include "google_breakpad/processor/memory_region.h" -#include "google_breakpad/processor/system_info.h" -#include "processor/basic_code_modules.h" - -namespace google_breakpad { - -// MicrodumpModuleList contains all of the loaded code modules for a process -// in the form of MicrodumpModules. It maintains a vector of these modules -// and provides access to a code module corresponding to a specific address. -class MicrodumpModules : public BasicCodeModules { - public: - // Takes over ownership of |module|. - void Add(const CodeModule* module); -}; - -// MicrodumpContext carries a CPU-specific context. -// See dump_context.h for documentation. -class MicrodumpContext : public DumpContext { - public: - virtual void SetContextARM(MDRawContextARM* arm); - virtual void SetContextARM64(MDRawContextARM64* arm64); -}; - -// This class provides access to microdump memory regions. -// See memory_region.h for documentation. -class MicrodumpMemoryRegion : public MemoryRegion { - public: - MicrodumpMemoryRegion(); - virtual ~MicrodumpMemoryRegion() {} - - // Set this region's address and contents. If we have placed an - // instance of this class in a test fixture class, individual tests - // can use this to provide the region's contents. - void Init(uint64_t base_address, const std::vector& contents); - - virtual uint64_t GetBase() const; - virtual uint32_t GetSize() const; - - virtual bool GetMemoryAtAddress(uint64_t address, uint8_t* value) const; - virtual bool GetMemoryAtAddress(uint64_t address, uint16_t* value) const; - virtual bool GetMemoryAtAddress(uint64_t address, uint32_t* value) const; - virtual bool GetMemoryAtAddress(uint64_t address, uint64_t* value) const; - - // Print a human-readable representation of the object to stdout. - virtual void Print() const; - - private: - // Fetch a little-endian value from ADDRESS in contents_ whose size - // is BYTES, and store it in *VALUE. Returns true on success. - template - bool GetMemoryLittleEndian(uint64_t address, ValueType* value) const; - - uint64_t base_address_; - std::vector contents_; -}; - -// Microdump is the user's interface to a microdump file. It provides access to -// the microdump's context, memory regions and modules. -class Microdump { - public: - explicit Microdump(const string& contents); - virtual ~Microdump() {} - - DumpContext* GetContext() { return context_.get(); } - MicrodumpMemoryRegion* GetMemory() { return stack_region_.get(); } - MicrodumpModules* GetModules() { return modules_.get(); } - SystemInfo* GetSystemInfo() { return system_info_.get(); } - - private: - scoped_ptr context_; - scoped_ptr stack_region_; - scoped_ptr modules_; - scoped_ptr system_info_; -}; - -} // namespace google_breakpad - -#endif // GOOGLE_BREAKPAD_PROCESSOR_MICRODUMP_H__ - diff --git a/TMessagesProj/jni/breakpad/google_breakpad/processor/microdump_processor.h b/TMessagesProj/jni/breakpad/google_breakpad/processor/microdump_processor.h deleted file mode 100644 index 1322a01c7..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/processor/microdump_processor.h +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) 2014, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// The processor for microdump (a reduced dump containing only the state of the -// crashing thread). See crbug.com/410294 for more info and design docs. - -#ifndef GOOGLE_BREAKPAD_PROCESSOR_MICRODUMP_PROCESSOR_H__ -#define GOOGLE_BREAKPAD_PROCESSOR_MICRODUMP_PROCESSOR_H__ - -#include - -#include "common/using_std_string.h" -#include "google_breakpad/processor/process_result.h" - -namespace google_breakpad { - -class ProcessState; -class StackFrameSymbolizer; - -class MicrodumpProcessor { - public: - // Initializes the MicrodumpProcessor with a stack frame symbolizer. - // Does not take ownership of frame_symbolizer, which must NOT be NULL. - explicit MicrodumpProcessor(StackFrameSymbolizer* frame_symbolizer); - - virtual ~MicrodumpProcessor(); - - // Processes the microdump contents and fills process_state with the result. - google_breakpad::ProcessResult Process(const string& microdump_contents, - ProcessState* process_state); - private: - StackFrameSymbolizer* frame_symbolizer_; -}; - -} // namespace google_breakpad - -#endif // GOOGLE_BREAKPAD_PROCESSOR_MICRODUMP_PROCESSOR_H__ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/processor/minidump.h b/TMessagesProj/jni/breakpad/google_breakpad/processor/minidump.h deleted file mode 100644 index 2b5025e4f..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/processor/minidump.h +++ /dev/null @@ -1,1130 +0,0 @@ -// Copyright (c) 2010 Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// minidump.h: A minidump reader. -// -// The basic structure of this module tracks the structure of the minidump -// file itself. At the top level, a minidump file is represented by a -// Minidump object. Like most other classes in this module, Minidump -// provides a Read method that initializes the object with information from -// the file. Most of the classes in this file are wrappers around the -// "raw" structures found in the minidump file itself, and defined in -// minidump_format.h. For example, each thread is represented by a -// MinidumpThread object, whose parameters are specified in an MDRawThread -// structure. A properly byte-swapped MDRawThread can be obtained from a -// MinidumpThread easily by calling its thread() method. -// -// Most of the module lazily reads only the portion of the minidump file -// necessary to fulfill the user's request. Calling Minidump::Read -// only reads the minidump's directory. The thread list is not read until -// it is needed, and even once it's read, the memory regions for each -// thread's stack aren't read until they're needed. This strategy avoids -// unnecessary file input, and allocating memory for data in which the user -// has no interest. Note that although memory allocations for a typical -// minidump file are not particularly large, it is possible for legitimate -// minidumps to be sizable. A full-memory minidump, for example, contains -// a snapshot of the entire mapped memory space. Even a normal minidump, -// with stack memory only, can be large if, for example, the dump was -// generated in response to a crash that occurred due to an infinite- -// recursion bug that caused the stack's limits to be exceeded. Finally, -// some users of this library will unfortunately find themselves in the -// position of having to process potentially-hostile minidumps that might -// attempt to cause problems by forcing the minidump processor to over- -// allocate memory. -// -// Memory management in this module is based on a strict -// you-don't-own-anything policy. The only object owned by the user is -// the top-level Minidump object, the creation and destruction of which -// must be the user's own responsibility. All other objects obtained -// through interaction with this module are ultimately owned by the -// Minidump object, and will be freed upon the Minidump object's destruction. -// Because memory regions can potentially involve large allocations, a -// FreeMemory method is provided by MinidumpMemoryRegion, allowing the user -// to release data when it is no longer needed. Use of this method is -// optional but recommended. If freed data is later required, it will -// be read back in from the minidump file again. -// -// There is one exception to this memory management policy: -// Minidump::ReadString will return a string object to the user, and the user -// is responsible for its deletion. -// -// Author: Mark Mentovai - -#ifndef GOOGLE_BREAKPAD_PROCESSOR_MINIDUMP_H__ -#define GOOGLE_BREAKPAD_PROCESSOR_MINIDUMP_H__ - -#include - -#ifndef _WIN32 -#include -#endif - -#include -#include -#include -#include - -#include "common/basictypes.h" -#include "common/using_std_string.h" -#include "google_breakpad/processor/code_module.h" -#include "google_breakpad/processor/code_modules.h" -#include "google_breakpad/processor/dump_context.h" -#include "google_breakpad/processor/dump_object.h" -#include "google_breakpad/processor/memory_region.h" -#include "google_breakpad/processor/proc_maps_linux.h" - - -namespace google_breakpad { - - -using std::map; -using std::vector; - - -class Minidump; -template class RangeMap; - - -// MinidumpObject is the base of all Minidump* objects except for Minidump -// itself. -class MinidumpObject : public DumpObject { - public: - virtual ~MinidumpObject() {} - - protected: - explicit MinidumpObject(Minidump* minidump); - - // Refers to the Minidump object that is the ultimate parent of this - // Some MinidumpObjects are owned by other MinidumpObjects, but at the - // root of the ownership tree is always a Minidump. The Minidump object - // is kept here for access to its seeking and reading facilities, and - // for access to data about the minidump file itself, such as whether - // it should be byte-swapped. - Minidump* minidump_; -}; - - -// This class exists primarily to provide a virtual destructor in a base -// class common to all objects that might be stored in -// Minidump::mStreamObjects. Some object types will never be stored in -// Minidump::mStreamObjects, but are represented as streams and adhere to the -// same interface, and may be derived from this class. -class MinidumpStream : public MinidumpObject { - public: - virtual ~MinidumpStream() {} - - protected: - explicit MinidumpStream(Minidump* minidump); - - private: - // Populate (and validate) the MinidumpStream. minidump_ is expected - // to be positioned at the beginning of the stream, so that the next - // read from the minidump will be at the beginning of the stream. - // expected_size should be set to the stream's length as contained in - // the MDRawDirectory record or other identifying record. A class - // that implements MinidumpStream can compare expected_size to a - // known size as an integrity check. - virtual bool Read(uint32_t expected_size) = 0; -}; - - -// MinidumpContext carries a CPU-specific MDRawContext structure, which -// contains CPU context such as register states. Each thread has its -// own context, and the exception record, if present, also has its own -// context. Note that if the exception record is present, the context it -// refers to is probably what the user wants to use for the exception -// thread, instead of that thread's own context. The exception thread's -// context (as opposed to the exception record's context) will contain -// context for the exception handler (which performs minidump generation), -// and not the context that caused the exception (which is probably what the -// user wants). -class MinidumpContext : public DumpContext { - public: - virtual ~MinidumpContext(); - - protected: - explicit MinidumpContext(Minidump* minidump); - - private: - friend class MinidumpThread; - friend class MinidumpException; - - bool Read(uint32_t expected_size); - - // If the minidump contains a SYSTEM_INFO_STREAM, makes sure that the - // system info stream gives an appropriate CPU type matching the context - // CPU type in context_cpu_type. Returns false if the CPU type does not - // match. Returns true if the CPU type matches or if the minidump does - // not contain a system info stream. - bool CheckAgainstSystemInfo(uint32_t context_cpu_type); - - // Refers to the Minidump object that is the ultimate parent of this - // Some MinidumpObjects are owned by other MinidumpObjects, but at the - // root of the ownership tree is always a Minidump. The Minidump object - // is kept here for access to its seeking and reading facilities, and - // for access to data about the minidump file itself, such as whether - // it should be byte-swapped. - Minidump* minidump_; -}; - - -// MinidumpMemoryRegion does not wrap any MDRaw structure, and only contains -// a reference to an MDMemoryDescriptor. This object is intended to wrap -// portions of a minidump file that contain memory dumps. In normal -// minidumps, each MinidumpThread owns a MinidumpMemoryRegion corresponding -// to the thread's stack memory. MinidumpMemoryList also gives access to -// memory regions in its list as MinidumpMemoryRegions. This class -// adheres to MemoryRegion so that it may be used as a data provider to -// the Stackwalker family of classes. -class MinidumpMemoryRegion : public MinidumpObject, - public MemoryRegion { - public: - virtual ~MinidumpMemoryRegion(); - - static void set_max_bytes(uint32_t max_bytes) { max_bytes_ = max_bytes; } - static uint32_t max_bytes() { return max_bytes_; } - - // Returns a pointer to the base of the memory region. Returns the - // cached value if available, otherwise, reads the minidump file and - // caches the memory region. - const uint8_t* GetMemory() const; - - // The address of the base of the memory region. - uint64_t GetBase() const; - - // The size, in bytes, of the memory region. - uint32_t GetSize() const; - - // Frees the cached memory region, if cached. - void FreeMemory(); - - // Obtains the value of memory at the pointer specified by address. - bool GetMemoryAtAddress(uint64_t address, uint8_t* value) const; - bool GetMemoryAtAddress(uint64_t address, uint16_t* value) const; - bool GetMemoryAtAddress(uint64_t address, uint32_t* value) const; - bool GetMemoryAtAddress(uint64_t address, uint64_t* value) const; - - // Print a human-readable representation of the object to stdout. - void Print() const; - - protected: - explicit MinidumpMemoryRegion(Minidump* minidump); - - private: - friend class MinidumpThread; - friend class MinidumpMemoryList; - - // Identify the base address and size of the memory region, and the - // location it may be found in the minidump file. - void SetDescriptor(MDMemoryDescriptor* descriptor); - - // Implementation for GetMemoryAtAddress - template bool GetMemoryAtAddressInternal(uint64_t address, - T* value) const; - - // The largest memory region that will be read from a minidump. The - // default is 1MB. - static uint32_t max_bytes_; - - // Base address and size of the memory region, and its position in the - // minidump file. - MDMemoryDescriptor* descriptor_; - - // Cached memory. - mutable vector* memory_; -}; - - -// MinidumpThread contains information about a thread of execution, -// including a snapshot of the thread's stack and CPU context. For -// the thread that caused an exception, the context carried by -// MinidumpException is probably desired instead of the CPU context -// provided here. -// Note that a MinidumpThread may be valid() even if it does not -// contain a memory region or context. -class MinidumpThread : public MinidumpObject { - public: - virtual ~MinidumpThread(); - - const MDRawThread* thread() const { return valid_ ? &thread_ : NULL; } - // GetMemory may return NULL even if the MinidumpThread is valid, - // if the thread memory cannot be read. - virtual MinidumpMemoryRegion* GetMemory(); - // GetContext may return NULL even if the MinidumpThread is valid. - virtual MinidumpContext* GetContext(); - - // The thread ID is used to determine if a thread is the exception thread, - // so a special getter is provided to retrieve this data from the - // MDRawThread structure. Returns false if the thread ID cannot be - // determined. - virtual bool GetThreadID(uint32_t *thread_id) const; - - // Print a human-readable representation of the object to stdout. - void Print(); - - // Returns the start address of the thread stack memory region. Returns 0 if - // MinidumpThread is invalid. Note that this method can be called even when - // the thread memory cannot be read and GetMemory returns NULL. - virtual uint64_t GetStartOfStackMemoryRange() const; - - protected: - explicit MinidumpThread(Minidump* minidump); - - private: - // These objects are managed by MinidumpThreadList. - friend class MinidumpThreadList; - - // This works like MinidumpStream::Read, but is driven by - // MinidumpThreadList. No size checking is done, because - // MinidumpThreadList handles that directly. - bool Read(); - - MDRawThread thread_; - MinidumpMemoryRegion* memory_; - MinidumpContext* context_; -}; - - -// MinidumpThreadList contains all of the threads (as MinidumpThreads) in -// a process. -class MinidumpThreadList : public MinidumpStream { - public: - virtual ~MinidumpThreadList(); - - static void set_max_threads(uint32_t max_threads) { - max_threads_ = max_threads; - } - static uint32_t max_threads() { return max_threads_; } - - virtual unsigned int thread_count() const { - return valid_ ? thread_count_ : 0; - } - - // Sequential access to threads. - virtual MinidumpThread* GetThreadAtIndex(unsigned int index) const; - - // Random access to threads. - MinidumpThread* GetThreadByID(uint32_t thread_id); - - // Print a human-readable representation of the object to stdout. - void Print(); - - protected: - explicit MinidumpThreadList(Minidump* aMinidump); - - private: - friend class Minidump; - - typedef map IDToThreadMap; - typedef vector MinidumpThreads; - - static const uint32_t kStreamType = MD_THREAD_LIST_STREAM; - - bool Read(uint32_t aExpectedSize); - - // The largest number of threads that will be read from a minidump. The - // default is 256. - static uint32_t max_threads_; - - // Access to threads using the thread ID as the key. - IDToThreadMap id_to_thread_map_; - - // The list of threads. - MinidumpThreads* threads_; - uint32_t thread_count_; -}; - - -// MinidumpModule wraps MDRawModule, which contains information about loaded -// code modules. Access is provided to various data referenced indirectly -// by MDRawModule, such as the module's name and a specification for where -// to locate debugging information for the module. -class MinidumpModule : public MinidumpObject, - public CodeModule { - public: - virtual ~MinidumpModule(); - - static void set_max_cv_bytes(uint32_t max_cv_bytes) { - max_cv_bytes_ = max_cv_bytes; - } - static uint32_t max_cv_bytes() { return max_cv_bytes_; } - - static void set_max_misc_bytes(uint32_t max_misc_bytes) { - max_misc_bytes_ = max_misc_bytes; - } - static uint32_t max_misc_bytes() { return max_misc_bytes_; } - - const MDRawModule* module() const { return valid_ ? &module_ : NULL; } - - // CodeModule implementation - virtual uint64_t base_address() const { - return valid_ ? module_.base_of_image : static_cast(-1); - } - virtual uint64_t size() const { return valid_ ? module_.size_of_image : 0; } - virtual string code_file() const; - virtual string code_identifier() const; - virtual string debug_file() const; - virtual string debug_identifier() const; - virtual string version() const; - virtual const CodeModule* Copy() const; - - // The CodeView record, which contains information to locate the module's - // debugging information (pdb). This is returned as uint8_t* because - // the data can be of types MDCVInfoPDB20* or MDCVInfoPDB70*, or it may be - // of a type unknown to Breakpad, in which case the raw data will still be - // returned but no byte-swapping will have been performed. Check the - // record's signature in the first four bytes to differentiate between - // the various types. Current toolchains generate modules which carry - // MDCVInfoPDB70 by default. Returns a pointer to the CodeView record on - // success, and NULL on failure. On success, the optional |size| argument - // is set to the size of the CodeView record. - const uint8_t* GetCVRecord(uint32_t* size); - - // The miscellaneous debug record, which is obsolete. Current toolchains - // do not generate this type of debugging information (dbg), and this - // field is not expected to be present. Returns a pointer to the debugging - // record on success, and NULL on failure. On success, the optional |size| - // argument is set to the size of the debugging record. - const MDImageDebugMisc* GetMiscRecord(uint32_t* size); - - // Print a human-readable representation of the object to stdout. - void Print(); - - private: - // These objects are managed by MinidumpModuleList. - friend class MinidumpModuleList; - - explicit MinidumpModule(Minidump* minidump); - - // This works like MinidumpStream::Read, but is driven by - // MinidumpModuleList. No size checking is done, because - // MinidumpModuleList handles that directly. - bool Read(); - - // Reads indirectly-referenced data, including the module name, CodeView - // record, and miscellaneous debugging record. This is necessary to allow - // MinidumpModuleList to fully construct MinidumpModule objects without - // requiring seeks to read a contiguous set of MinidumpModule objects. - // All auxiliary data should be available when Read is called, in order to - // allow the CodeModule getters to be const methods. - bool ReadAuxiliaryData(); - - // The largest number of bytes that will be read from a minidump for a - // CodeView record or miscellaneous debugging record, respectively. The - // default for each is 1024. - static uint32_t max_cv_bytes_; - static uint32_t max_misc_bytes_; - - // True after a successful Read. This is different from valid_, which is - // not set true until ReadAuxiliaryData also completes successfully. - // module_valid_ is only used by ReadAuxiliaryData and the functions it - // calls to determine whether the object is ready for auxiliary data to - // be read. - bool module_valid_; - - // True if debug info was read from the module. Certain modules - // may contain debug records in formats we don't support, - // so we can just set this to false to ignore them. - bool has_debug_info_; - - MDRawModule module_; - - // Cached module name. - const string* name_; - - // Cached CodeView record - this is MDCVInfoPDB20 or (likely) - // MDCVInfoPDB70, or possibly something else entirely. Stored as a uint8_t - // because the structure contains a variable-sized string and its exact - // size cannot be known until it is processed. - vector* cv_record_; - - // If cv_record_ is present, cv_record_signature_ contains a copy of the - // CodeView record's first four bytes, for ease of determinining the - // type of structure that cv_record_ contains. - uint32_t cv_record_signature_; - - // Cached MDImageDebugMisc (usually not present), stored as uint8_t - // because the structure contains a variable-sized string and its exact - // size cannot be known until it is processed. - vector* misc_record_; -}; - - -// MinidumpModuleList contains all of the loaded code modules for a process -// in the form of MinidumpModules. It maintains a map of these modules -// so that it may easily provide a code module corresponding to a specific -// address. -class MinidumpModuleList : public MinidumpStream, - public CodeModules { - public: - virtual ~MinidumpModuleList(); - - static void set_max_modules(uint32_t max_modules) { - max_modules_ = max_modules; - } - static uint32_t max_modules() { return max_modules_; } - - // CodeModules implementation. - virtual unsigned int module_count() const { - return valid_ ? module_count_ : 0; - } - virtual const MinidumpModule* GetModuleForAddress(uint64_t address) const; - virtual const MinidumpModule* GetMainModule() const; - virtual const MinidumpModule* GetModuleAtSequence( - unsigned int sequence) const; - virtual const MinidumpModule* GetModuleAtIndex(unsigned int index) const; - virtual const CodeModules* Copy() const; - - // Print a human-readable representation of the object to stdout. - void Print(); - - protected: - explicit MinidumpModuleList(Minidump* minidump); - - private: - friend class Minidump; - - typedef vector MinidumpModules; - - static const uint32_t kStreamType = MD_MODULE_LIST_STREAM; - - bool Read(uint32_t expected_size); - - // The largest number of modules that will be read from a minidump. The - // default is 1024. - static uint32_t max_modules_; - - // Access to modules using addresses as the key. - RangeMap *range_map_; - - MinidumpModules *modules_; - uint32_t module_count_; -}; - - -// MinidumpMemoryList corresponds to a minidump's MEMORY_LIST_STREAM stream, -// which references the snapshots of all of the memory regions contained -// within the minidump. For a normal minidump, this includes stack memory -// (also referenced by each MinidumpThread, in fact, the MDMemoryDescriptors -// here and in MDRawThread both point to exactly the same data in a -// minidump file, conserving space), as well as a 256-byte snapshot of memory -// surrounding the instruction pointer in the case of an exception. Other -// types of minidumps may contain significantly more memory regions. Full- -// memory minidumps contain all of a process' mapped memory. -class MinidumpMemoryList : public MinidumpStream { - public: - virtual ~MinidumpMemoryList(); - - static void set_max_regions(uint32_t max_regions) { - max_regions_ = max_regions; - } - static uint32_t max_regions() { return max_regions_; } - - unsigned int region_count() const { return valid_ ? region_count_ : 0; } - - // Sequential access to memory regions. - MinidumpMemoryRegion* GetMemoryRegionAtIndex(unsigned int index); - - // Random access to memory regions. Returns the region encompassing - // the address identified by address. - virtual MinidumpMemoryRegion* GetMemoryRegionForAddress(uint64_t address); - - // Print a human-readable representation of the object to stdout. - void Print(); - - private: - friend class Minidump; - friend class MockMinidumpMemoryList; - - typedef vector MemoryDescriptors; - typedef vector MemoryRegions; - - static const uint32_t kStreamType = MD_MEMORY_LIST_STREAM; - - explicit MinidumpMemoryList(Minidump* minidump); - - bool Read(uint32_t expected_size); - - // The largest number of memory regions that will be read from a minidump. - // The default is 256. - static uint32_t max_regions_; - - // Access to memory regions using addresses as the key. - RangeMap *range_map_; - - // The list of descriptors. This is maintained separately from the list - // of regions, because MemoryRegion doesn't own its MemoryDescriptor, it - // maintains a pointer to it. descriptors_ provides the storage for this - // purpose. - MemoryDescriptors *descriptors_; - - // The list of regions. - MemoryRegions *regions_; - uint32_t region_count_; -}; - - -// MinidumpException wraps MDRawExceptionStream, which contains information -// about the exception that caused the minidump to be generated, if the -// minidump was generated in an exception handler called as a result of an -// exception. It also provides access to a MinidumpContext object, which -// contains the CPU context for the exception thread at the time the exception -// occurred. -class MinidumpException : public MinidumpStream { - public: - virtual ~MinidumpException(); - - const MDRawExceptionStream* exception() const { - return valid_ ? &exception_ : NULL; - } - - // The thread ID is used to determine if a thread is the exception thread, - // so a special getter is provided to retrieve this data from the - // MDRawExceptionStream structure. Returns false if the thread ID cannot - // be determined. - bool GetThreadID(uint32_t *thread_id) const; - - MinidumpContext* GetContext(); - - // Print a human-readable representation of the object to stdout. - void Print(); - - private: - friend class Minidump; - - static const uint32_t kStreamType = MD_EXCEPTION_STREAM; - - explicit MinidumpException(Minidump* minidump); - - bool Read(uint32_t expected_size); - - MDRawExceptionStream exception_; - MinidumpContext* context_; -}; - -// MinidumpAssertion wraps MDRawAssertionInfo, which contains information -// about an assertion that caused the minidump to be generated. -class MinidumpAssertion : public MinidumpStream { - public: - virtual ~MinidumpAssertion(); - - const MDRawAssertionInfo* assertion() const { - return valid_ ? &assertion_ : NULL; - } - - string expression() const { - return valid_ ? expression_ : ""; - } - - string function() const { - return valid_ ? function_ : ""; - } - - string file() const { - return valid_ ? file_ : ""; - } - - // Print a human-readable representation of the object to stdout. - void Print(); - - private: - friend class Minidump; - - static const uint32_t kStreamType = MD_ASSERTION_INFO_STREAM; - - explicit MinidumpAssertion(Minidump* minidump); - - bool Read(uint32_t expected_size); - - MDRawAssertionInfo assertion_; - string expression_; - string function_; - string file_; -}; - - -// MinidumpSystemInfo wraps MDRawSystemInfo and provides information about -// the system on which the minidump was generated. See also MinidumpMiscInfo. -class MinidumpSystemInfo : public MinidumpStream { - public: - virtual ~MinidumpSystemInfo(); - - const MDRawSystemInfo* system_info() const { - return valid_ ? &system_info_ : NULL; - } - - // GetOS and GetCPU return textual representations of the operating system - // and CPU that produced the minidump. Unlike most other Minidump* methods, - // they return string objects, not weak pointers. Defined values for - // GetOS() are "mac", "windows", and "linux". Defined values for GetCPU - // are "x86" and "ppc". These methods return an empty string when their - // values are unknown. - string GetOS(); - string GetCPU(); - - // I don't know what CSD stands for, but this field is documented as - // returning a textual representation of the OS service pack. On other - // platforms, this provides additional information about an OS version - // level beyond major.minor.micro. Returns NULL if unknown. - const string* GetCSDVersion(); - - // If a CPU vendor string can be determined, returns a pointer to it, - // otherwise, returns NULL. CPU vendor strings can be determined from - // x86 CPUs with CPUID 0. - const string* GetCPUVendor(); - - // Print a human-readable representation of the object to stdout. - void Print(); - - protected: - explicit MinidumpSystemInfo(Minidump* minidump); - MDRawSystemInfo system_info_; - - // Textual representation of the OS service pack, for minidumps produced - // by MiniDumpWriteDump on Windows. - const string* csd_version_; - - private: - friend class Minidump; - - static const uint32_t kStreamType = MD_SYSTEM_INFO_STREAM; - - bool Read(uint32_t expected_size); - - // A string identifying the CPU vendor, if known. - const string* cpu_vendor_; -}; - - -// MinidumpMiscInfo wraps MDRawMiscInfo and provides information about -// the process that generated the minidump, and optionally additional system -// information. See also MinidumpSystemInfo. -class MinidumpMiscInfo : public MinidumpStream { - public: - const MDRawMiscInfo* misc_info() const { - return valid_ ? &misc_info_ : NULL; - } - - // Print a human-readable representation of the object to stdout. - void Print(); - - private: - friend class Minidump; - friend class TestMinidumpMiscInfo; - - static const uint32_t kStreamType = MD_MISC_INFO_STREAM; - - explicit MinidumpMiscInfo(Minidump* minidump_); - - bool Read(uint32_t expected_size_); - - MDRawMiscInfo misc_info_; - - // Populated by Read. Contains the converted strings from the corresponding - // UTF-16 fields in misc_info_ - string standard_name_; - string daylight_name_; - string build_string_; - string dbg_bld_str_; -}; - - -// MinidumpBreakpadInfo wraps MDRawBreakpadInfo, which is an optional stream in -// a minidump that provides additional information about the process state -// at the time the minidump was generated. -class MinidumpBreakpadInfo : public MinidumpStream { - public: - const MDRawBreakpadInfo* breakpad_info() const { - return valid_ ? &breakpad_info_ : NULL; - } - - // These thread IDs are used to determine if threads deserve special - // treatment, so special getters are provided to retrieve this data from - // the MDRawBreakpadInfo structure. The getters return false if the thread - // IDs cannot be determined. - bool GetDumpThreadID(uint32_t *thread_id) const; - bool GetRequestingThreadID(uint32_t *thread_id) const; - - // Print a human-readable representation of the object to stdout. - void Print(); - - private: - friend class Minidump; - - static const uint32_t kStreamType = MD_BREAKPAD_INFO_STREAM; - - explicit MinidumpBreakpadInfo(Minidump* minidump_); - - bool Read(uint32_t expected_size_); - - MDRawBreakpadInfo breakpad_info_; -}; - -// MinidumpMemoryInfo wraps MDRawMemoryInfo, which provides information -// about mapped memory regions in a process, including their ranges -// and protection. -class MinidumpMemoryInfo : public MinidumpObject { - public: - const MDRawMemoryInfo* info() const { return valid_ ? &memory_info_ : NULL; } - - // The address of the base of the memory region. - uint64_t GetBase() const { return valid_ ? memory_info_.base_address : 0; } - - // The size, in bytes, of the memory region. - uint64_t GetSize() const { return valid_ ? memory_info_.region_size : 0; } - - // Return true if the memory protection allows execution. - bool IsExecutable() const; - - // Return true if the memory protection allows writing. - bool IsWritable() const; - - // Print a human-readable representation of the object to stdout. - void Print(); - - private: - // These objects are managed by MinidumpMemoryInfoList. - friend class MinidumpMemoryInfoList; - - explicit MinidumpMemoryInfo(Minidump* minidump_); - - // This works like MinidumpStream::Read, but is driven by - // MinidumpMemoryInfoList. No size checking is done, because - // MinidumpMemoryInfoList handles that directly. - bool Read(); - - MDRawMemoryInfo memory_info_; -}; - -// MinidumpMemoryInfoList contains a list of information about -// mapped memory regions for a process in the form of MDRawMemoryInfo. -// It maintains a map of these structures so that it may easily provide -// info corresponding to a specific address. -class MinidumpMemoryInfoList : public MinidumpStream { - public: - virtual ~MinidumpMemoryInfoList(); - - unsigned int info_count() const { return valid_ ? info_count_ : 0; } - - const MinidumpMemoryInfo* GetMemoryInfoForAddress(uint64_t address) const; - const MinidumpMemoryInfo* GetMemoryInfoAtIndex(unsigned int index) const; - - // Print a human-readable representation of the object to stdout. - void Print(); - - private: - friend class Minidump; - - typedef vector MinidumpMemoryInfos; - - static const uint32_t kStreamType = MD_MEMORY_INFO_LIST_STREAM; - - explicit MinidumpMemoryInfoList(Minidump* minidump_); - - bool Read(uint32_t expected_size); - - // Access to memory info using addresses as the key. - RangeMap *range_map_; - - MinidumpMemoryInfos* infos_; - uint32_t info_count_; -}; - -// MinidumpLinuxMaps wraps information about a single mapped memory region -// from /proc/self/maps. -class MinidumpLinuxMaps : public MinidumpObject { - public: - // The memory address of the base of the mapped region. - uint64_t GetBase() const { return valid_ ? region_.start : 0; } - // The size of the mapped region. - uint64_t GetSize() const { return valid_ ? region_.end - region_.start : 0; } - - // The permissions of the mapped region. - bool IsReadable() const { - return valid_ ? region_.permissions & MappedMemoryRegion::READ : false; - } - bool IsWriteable() const { - return valid_ ? region_.permissions & MappedMemoryRegion::WRITE : false; - } - bool IsExecutable() const { - return valid_ ? region_.permissions & MappedMemoryRegion::EXECUTE : false; - } - bool IsPrivate() const { - return valid_ ? region_.permissions & MappedMemoryRegion::PRIVATE : false; - } - - // The offset of the mapped region. - uint64_t GetOffset() const { return valid_ ? region_.offset : 0; } - - // The major device number. - uint8_t GetMajorDevice() const { return valid_ ? region_.major_device : 0; } - // The minor device number. - uint8_t GetMinorDevice() const { return valid_ ? region_.minor_device : 0; } - - // The inode of the mapped region. - uint64_t GetInode() const { return valid_ ? region_.inode : 0; } - - // The pathname of the mapped region. - const string GetPathname() const { return valid_ ? region_.path : ""; } - - // Print the contents of this mapping. - void Print() const; - - private: - // These objects are managed by MinidumpLinuxMapsList. - friend class MinidumpLinuxMapsList; - - // This caller owns the pointer. - explicit MinidumpLinuxMaps(Minidump *minidump); - - // The memory region struct that this class wraps. - MappedMemoryRegion region_; - - DISALLOW_COPY_AND_ASSIGN(MinidumpLinuxMaps); -}; - -// MinidumpLinuxMapsList corresponds to the Linux-exclusive MD_LINUX_MAPS -// stream, which contains the contents of /prod/self/maps, which contains -// the mapped memory regions and their access permissions. -class MinidumpLinuxMapsList : public MinidumpStream { - public: - virtual ~MinidumpLinuxMapsList(); - - // Get number of mappings. - unsigned int get_maps_count() const { return valid_ ? maps_count_ : 0; } - - // Get mapping at the given memory address. The caller owns the pointer. - const MinidumpLinuxMaps *GetLinuxMapsForAddress(uint64_t address) const; - // Get mapping at the given index. The caller owns the pointer. - const MinidumpLinuxMaps *GetLinuxMapsAtIndex(unsigned int index) const; - - // Print the contents of /proc/self/maps to stdout. - void Print() const; - - private: - friend class Minidump; - - typedef vector MinidumpLinuxMappings; - - static const uint32_t kStreamType = MD_LINUX_MAPS; - - // The caller owns the pointer. - explicit MinidumpLinuxMapsList(Minidump *minidump); - - // Read and load the contents of the process mapping data. - // The stream should have data in the form of /proc/self/maps. - // This method returns whether the stream was read successfully. - bool Read(uint32_t expected_size); - - // The list of individual mappings. - MinidumpLinuxMappings *maps_; - // The number of mappings. - uint32_t maps_count_; - - DISALLOW_COPY_AND_ASSIGN(MinidumpLinuxMapsList); -}; - -// Minidump is the user's interface to a minidump file. It wraps MDRawHeader -// and provides access to the minidump's top-level stream directory. -class Minidump { - public: - // path is the pathname of a file containing the minidump. - explicit Minidump(const string& path); - // input is an istream wrapping minidump data. Minidump holds a - // weak pointer to input, and the caller must ensure that the stream - // is valid as long as the Minidump object is. - explicit Minidump(std::istream& input); - - virtual ~Minidump(); - - // path may be empty if the minidump was not opened from a file - virtual string path() const { - return path_; - } - static void set_max_streams(uint32_t max_streams) { - max_streams_ = max_streams; - } - static uint32_t max_streams() { return max_streams_; } - - static void set_max_string_length(uint32_t max_string_length) { - max_string_length_ = max_string_length; - } - static uint32_t max_string_length() { return max_string_length_; } - - virtual const MDRawHeader* header() const { return valid_ ? &header_ : NULL; } - - // Reads the CPU information from the system info stream and generates the - // appropriate CPU flags. The returned context_cpu_flags are the same as - // if the CPU type bits were set in the context_flags of a context record. - // On success, context_cpu_flags will have the flags that identify the CPU. - // If a system info stream is missing, context_cpu_flags will be 0. - // Returns true if the current position in the stream was not changed. - // Returns false when the current location in the stream was changed and the - // attempt to restore the original position failed. - bool GetContextCPUFlagsFromSystemInfo(uint32_t* context_cpu_flags); - - // Reads the minidump file's header and top-level stream directory. - // The minidump is expected to be positioned at the beginning of the - // header. Read() sets up the stream list and map, and validates the - // Minidump object. - virtual bool Read(); - - // The next set of methods are stubs that call GetStream. They exist to - // force code generation of the templatized API within the module, and - // to avoid exposing an ugly API (GetStream needs to accept a garbage - // parameter). - virtual MinidumpThreadList* GetThreadList(); - virtual MinidumpModuleList* GetModuleList(); - virtual MinidumpMemoryList* GetMemoryList(); - virtual MinidumpException* GetException(); - virtual MinidumpAssertion* GetAssertion(); - virtual MinidumpSystemInfo* GetSystemInfo(); - virtual MinidumpMiscInfo* GetMiscInfo(); - virtual MinidumpBreakpadInfo* GetBreakpadInfo(); - virtual MinidumpMemoryInfoList* GetMemoryInfoList(); - - // The next method also calls GetStream, but is exclusive for Linux dumps. - virtual MinidumpLinuxMapsList *GetLinuxMapsList(); - - // The next set of methods are provided for users who wish to access - // data in minidump files directly, while leveraging the rest of - // this class and related classes to handle the basic minidump - // structure and known stream types. - - unsigned int GetDirectoryEntryCount() const { - return valid_ ? header_.stream_count : 0; - } - const MDRawDirectory* GetDirectoryEntryAtIndex(unsigned int index) const; - - // The next 2 methods are lower-level I/O routines. They use fd_. - - // Reads count bytes from the minidump at the current position into - // the storage area pointed to by bytes. bytes must be of sufficient - // size. After the read, the file position is advanced by count. - bool ReadBytes(void* bytes, size_t count); - - // Sets the position of the minidump file to offset. - bool SeekSet(off_t offset); - - // Returns the current position of the minidump file. - off_t Tell(); - - // The next 2 methods are medium-level I/O routines. - - // ReadString returns a string which is owned by the caller! offset - // specifies the offset that a length-encoded string is stored at in the - // minidump file. - string* ReadString(off_t offset); - - // SeekToStreamType positions the file at the beginning of a stream - // identified by stream_type, and informs the caller of the stream's - // length by setting *stream_length. Because stream_map maps each stream - // type to only one stream in the file, this might mislead the user into - // thinking that the stream that this seeks to is the only stream with - // type stream_type. That can't happen for streams that these classes - // deal with directly, because they're only supposed to be present in the - // file singly, and that's verified when stream_map_ is built. Users who - // are looking for other stream types should be aware of this - // possibility, and consider using GetDirectoryEntryAtIndex (possibly - // with GetDirectoryEntryCount) if expecting multiple streams of the same - // type in a single minidump file. - bool SeekToStreamType(uint32_t stream_type, uint32_t* stream_length); - - bool swap() const { return valid_ ? swap_ : false; } - - // Print a human-readable representation of the object to stdout. - void Print(); - - private: - // MinidumpStreamInfo is used in the MinidumpStreamMap. It lets - // the Minidump object locate interesting streams quickly, and - // provides a convenient place to stash MinidumpStream objects. - struct MinidumpStreamInfo { - MinidumpStreamInfo() : stream_index(0), stream(NULL) {} - ~MinidumpStreamInfo() { delete stream; } - - // Index into the MinidumpDirectoryEntries vector - unsigned int stream_index; - - // Pointer to the stream if cached, or NULL if not yet populated - MinidumpStream* stream; - }; - - typedef vector MinidumpDirectoryEntries; - typedef map MinidumpStreamMap; - - template T* GetStream(T** stream); - - // Opens the minidump file, or if already open, seeks to the beginning. - bool Open(); - - // The largest number of top-level streams that will be read from a minidump. - // Note that streams are only read (and only consume memory) as needed, - // when directed by the caller. The default is 128. - static uint32_t max_streams_; - - // The maximum length of a UTF-16 string that will be read from a minidump - // in 16-bit words. The default is 1024. UTF-16 strings are converted - // to UTF-8 when stored in memory, and each UTF-16 word will be represented - // by as many as 3 bytes in UTF-8. - static unsigned int max_string_length_; - - MDRawHeader header_; - - // The list of streams. - MinidumpDirectoryEntries* directory_; - - // Access to streams using the stream type as the key. - MinidumpStreamMap* stream_map_; - - // The pathname of the minidump file to process, set in the constructor. - // This may be empty if the minidump was opened directly from a stream. - const string path_; - - // The stream for all file I/O. Used by ReadBytes and SeekSet. - // Set based on the path in Open, or directly in the constructor. - std::istream* stream_; - - // swap_ is true if the minidump file should be byte-swapped. If the - // minidump was produced by a CPU that is other-endian than the CPU - // processing the minidump, this will be true. If the two CPUs are - // same-endian, this will be false. - bool swap_; - - // Validity of the Minidump structure, false immediately after - // construction or after a failed Read(); true following a successful - // Read(). - bool valid_; -}; - - -} // namespace google_breakpad - - -#endif // GOOGLE_BREAKPAD_PROCESSOR_MINIDUMP_H__ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/processor/minidump_processor.h b/TMessagesProj/jni/breakpad/google_breakpad/processor/minidump_processor.h deleted file mode 100644 index 387115ef7..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/processor/minidump_processor.h +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) 2006, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef GOOGLE_BREAKPAD_PROCESSOR_MINIDUMP_PROCESSOR_H__ -#define GOOGLE_BREAKPAD_PROCESSOR_MINIDUMP_PROCESSOR_H__ - -#include -#include - -#include "common/using_std_string.h" -#include "google_breakpad/common/breakpad_types.h" -#include "google_breakpad/processor/process_result.h" - -namespace google_breakpad { - -class Minidump; -class ProcessState; -class StackFrameSymbolizer; -class SourceLineResolverInterface; -class SymbolSupplier; -struct SystemInfo; - -class MinidumpProcessor { - public: - // Initializes this MinidumpProcessor. supplier should be an - // implementation of the SymbolSupplier abstract base class. - MinidumpProcessor(SymbolSupplier* supplier, - SourceLineResolverInterface* resolver); - - // Initializes the MinidumpProcessor with the option of - // enabling the exploitability framework to analyze dumps - // for probable security relevance. - MinidumpProcessor(SymbolSupplier* supplier, - SourceLineResolverInterface* resolver, - bool enable_exploitability); - - // Initializes the MinidumpProcessor with source line resolver helper, and - // the option of enabling the exploitability framework to analyze dumps - // for probable security relevance. - // Does not take ownership of resolver_helper, which must NOT be NULL. - MinidumpProcessor(StackFrameSymbolizer* stack_frame_symbolizer, - bool enable_exploitability); - - ~MinidumpProcessor(); - - // Processes the minidump file and fills process_state with the result. - ProcessResult Process(const string &minidump_file, - ProcessState* process_state); - - // Processes the minidump structure and fills process_state with the - // result. - ProcessResult Process(Minidump* minidump, - ProcessState* process_state); - // Populates the cpu_* fields of the |info| parameter with textual - // representations of the CPU type that the minidump in |dump| was - // produced on. Returns false if this information is not available in - // the minidump. - static bool GetCPUInfo(Minidump* dump, SystemInfo* info); - - // Populates the os_* fields of the |info| parameter with textual - // representations of the operating system that the minidump in |dump| - // was produced on. Returns false if this information is not available in - // the minidump. - static bool GetOSInfo(Minidump* dump, SystemInfo* info); - - // Populates the |process_create_time| parameter with the create time of the - // crashed process. Returns false if this information is not available in - // the minidump |dump|. - static bool GetProcessCreateTime(Minidump* dump, - uint32_t* process_create_time); - - // Returns a textual representation of the reason that a crash occurred, - // if the minidump in dump was produced as a result of a crash. Returns - // an empty string if this information cannot be determined. If address - // is non-NULL, it will be set to contain the address that caused the - // exception, if this information is available. This will be a code - // address when the crash was caused by problems such as illegal - // instructions or divisions by zero, or a data address when the crash - // was caused by a memory access violation. - static string GetCrashReason(Minidump* dump, uint64_t* address); - - // This function returns true if the passed-in error code is - // something unrecoverable(i.e. retry should not happen). For - // instance, if the minidump is corrupt, then it makes no sense to - // retry as we won't be able to glean additional information. - // However, as an example of the other case, the symbol supplier can - // return an error code indicating it was 'interrupted', which can - // happen of the symbols are fetched from a remote store, and a - // retry might be successful later on. - // You should not call this method with PROCESS_OK! Test for - // that separately before calling this. - static bool IsErrorUnrecoverable(ProcessResult p) { - assert(p != PROCESS_OK); - return (p != PROCESS_SYMBOL_SUPPLIER_INTERRUPTED); - } - - // Returns a textual representation of an assertion included - // in the minidump. Returns an empty string if this information - // does not exist or cannot be determined. - static string GetAssertion(Minidump* dump); - - void set_enable_objdump(bool enabled) { enable_objdump_ = enabled; } - - private: - StackFrameSymbolizer* frame_symbolizer_; - // Indicate whether resolver_helper_ is owned by this instance. - bool own_frame_symbolizer_; - - // This flag enables the exploitability scanner which attempts to - // guess how likely it is that the crash represents an exploitable - // memory corruption issue. - bool enable_exploitability_; - - // This flag permits the exploitability scanner to shell out to objdump - // for purposes of disassembly. - bool enable_objdump_; -}; - -} // namespace google_breakpad - -#endif // GOOGLE_BREAKPAD_PROCESSOR_MINIDUMP_PROCESSOR_H__ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/processor/proc_maps_linux.h b/TMessagesProj/jni/breakpad/google_breakpad/processor/proc_maps_linux.h deleted file mode 100644 index b8e6eb926..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/processor/proc_maps_linux.h +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef BASE_DEBUG_PROC_MAPS_LINUX_H_ -#define BASE_DEBUG_PROC_MAPS_LINUX_H_ - -#include -#include - -#include "common/using_std_string.h" -#include "google_breakpad/common/breakpad_types.h" - -namespace google_breakpad { - -// Describes a region of mapped memory and the path of the file mapped. -struct MappedMemoryRegion { - enum Permission { - READ = 1 << 0, - WRITE = 1 << 1, - EXECUTE = 1 << 2, - PRIVATE = 1 << 3, // If set, region is private, otherwise it is shared. - }; - - // The address range [start,end) of mapped memory. - uint64_t start; - uint64_t end; - - // Byte offset into |path| of the range mapped into memory. - uint64_t offset; - - // Bitmask of read/write/execute/private/shared permissions. - uint8_t permissions; - - // Major and minor devices. - uint8_t major_device; - uint8_t minor_device; - - // Value of the inode. - uint64_t inode; - - // Name of the file mapped into memory. - // - // NOTE: path names aren't guaranteed to point at valid files. For example, - // "[heap]" and "[stack]" are used to represent the location of the process' - // heap and stack, respectively. - string path; - - // The line from /proc//maps that this struct represents. - string line; -}; - -// Parses /proc//maps input data and stores in |regions|. Returns true -// and updates |regions| if and only if all of |input| was successfully parsed. -bool ParseProcMaps(const std::string& input, - std::vector* regions); - -} // namespace google_breakpad - -#endif // BASE_DEBUG_PROC_MAPS_LINUX_H_ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/processor/process_result.h b/TMessagesProj/jni/breakpad/google_breakpad/processor/process_result.h deleted file mode 100644 index 15c7213e9..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/processor/process_result.h +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) 2014, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef GOOGLE_BREAKPAD_PROCESSOR_PROCESS_RESULT_H__ -#define GOOGLE_BREAKPAD_PROCESSOR_PROCESS_RESULT_H__ - -namespace google_breakpad { - -// Return type for MinidumpProcessor or MicrodumpProcessor's Process() -enum ProcessResult { - PROCESS_OK, // The dump was processed - // successfully. - - PROCESS_ERROR_MINIDUMP_NOT_FOUND, // The minidump file was not - // found. - - PROCESS_ERROR_NO_MINIDUMP_HEADER, // The minidump file had no - // header. - - PROCESS_ERROR_NO_THREAD_LIST, // The minidump file has no - // thread list. - - PROCESS_ERROR_GETTING_THREAD, // There was an error getting one - // thread's data from th dump. - - PROCESS_ERROR_GETTING_THREAD_ID, // There was an error getting a - // thread id from the thread's - // data. - - PROCESS_ERROR_DUPLICATE_REQUESTING_THREADS, // There was more than one - // requesting thread. - - PROCESS_SYMBOL_SUPPLIER_INTERRUPTED // The dump processing was - // interrupted by the - // SymbolSupplier(not fatal). -}; - -} // namespace google_breakpad - -#endif // GOOGLE_BREAKPAD_PROCESSOR_PROCESS_RESULT_H__ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/processor/process_state.h b/TMessagesProj/jni/breakpad/google_breakpad/processor/process_state.h deleted file mode 100644 index 728656f2b..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/processor/process_state.h +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright (c) 2006, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// process_state.h: A snapshot of a process, in a fully-digested state. -// -// Author: Mark Mentovai - -#ifndef GOOGLE_BREAKPAD_PROCESSOR_PROCESS_STATE_H__ -#define GOOGLE_BREAKPAD_PROCESSOR_PROCESS_STATE_H__ - -#include -#include - -#include "common/using_std_string.h" -#include "google_breakpad/common/breakpad_types.h" -#include "google_breakpad/processor/system_info.h" -#include "google_breakpad/processor/minidump.h" - -namespace google_breakpad { - -using std::vector; - -class CallStack; -class CodeModules; - -enum ExploitabilityRating { - EXPLOITABILITY_HIGH, // The crash likely represents - // a exploitable memory corruption - // vulnerability. - - EXPLOITABILITY_MEDIUM, // The crash appears to corrupt - // memory in a way which may be - // exploitable in some situations. - - EXPLOITABLITY_MEDIUM = EXPLOITABILITY_MEDIUM, // an old misspelling - - EXPLOITABILITY_LOW, // The crash either does not corrupt - // memory directly or control over - // the affected data is limited. The - // issue may still be exploitable - // on certain platforms or situations. - - EXPLOITABILITY_INTERESTING, // The crash does not appear to be - // directly exploitable. However it - // represents a condition which should - // be further analyzed. - - EXPLOITABILITY_NONE, // The crash does not appear to represent - // an exploitable condition. - - EXPLOITABILITY_NOT_ANALYZED, // The crash was not analyzed for - // exploitability because the engine - // was disabled. - - EXPLOITABILITY_ERR_NOENGINE, // The supplied minidump's platform does - // not have a exploitability engine - // associated with it. - - EXPLOITABILITY_ERR_PROCESSING // An error occured within the - // exploitability engine and no rating - // was calculated. -}; - -class ProcessState { - public: - ProcessState() : modules_(NULL) { Clear(); } - ~ProcessState(); - - // Resets the ProcessState to its default values - void Clear(); - - // Accessors. See the data declarations below. - uint32_t time_date_stamp() const { return time_date_stamp_; } - uint32_t process_create_time() const { return process_create_time_; } - bool crashed() const { return crashed_; } - string crash_reason() const { return crash_reason_; } - uint64_t crash_address() const { return crash_address_; } - string assertion() const { return assertion_; } - int requesting_thread() const { return requesting_thread_; } - const vector* threads() const { return &threads_; } - const vector* thread_memory_regions() const { - return &thread_memory_regions_; - } - const SystemInfo* system_info() const { return &system_info_; } - const CodeModules* modules() const { return modules_; } - const vector* modules_without_symbols() const { - return &modules_without_symbols_; - } - const vector* modules_with_corrupt_symbols() const { - return &modules_with_corrupt_symbols_; - } - ExploitabilityRating exploitability() const { return exploitability_; } - - private: - // MinidumpProcessor and MicrodumpProcessor are responsible for building - // ProcessState objects. - friend class MinidumpProcessor; - friend class MicrodumpProcessor; - - // The time-date stamp of the minidump (time_t format) - uint32_t time_date_stamp_; - - // The time-date stamp when the process was created (time_t format) - uint32_t process_create_time_; - - // True if the process crashed, false if the dump was produced outside - // of an exception handler. - bool crashed_; - - // If the process crashed, the type of crash. OS- and possibly CPU- - // specific. For example, "EXCEPTION_ACCESS_VIOLATION" (Windows), - // "EXC_BAD_ACCESS / KERN_INVALID_ADDRESS" (Mac OS X), "SIGSEGV" - // (other Unix). - string crash_reason_; - - // If the process crashed, and if crash_reason implicates memory, - // the memory address that caused the crash. For data access errors, - // this will be the data address that caused the fault. For code errors, - // this will be the address of the instruction that caused the fault. - uint64_t crash_address_; - - // If there was an assertion that was hit, a textual representation - // of that assertion, possibly including the file and line at which - // it occurred. - string assertion_; - - // The index of the thread that requested a dump be written in the - // threads vector. If a dump was produced as a result of a crash, this - // will point to the thread that crashed. If the dump was produced as - // by user code without crashing, and the dump contains extended Breakpad - // information, this will point to the thread that requested the dump. - // If the dump was not produced as a result of an exception and no - // extended Breakpad information is present, this field will be set to -1, - // indicating that the dump thread is not available. - int requesting_thread_; - - // Stacks for each thread (except possibly the exception handler - // thread) at the time of the crash. - vector threads_; - vector thread_memory_regions_; - - // OS and CPU information. - SystemInfo system_info_; - - // The modules that were loaded into the process represented by the - // ProcessState. - const CodeModules *modules_; - - // The modules that didn't have symbols when the report was processed. - vector modules_without_symbols_; - - // The modules that had corrupt symbols when the report was processed. - vector modules_with_corrupt_symbols_; - - // The exploitability rating as determined by the exploitability - // engine. When the exploitability engine is not enabled this - // defaults to EXPLOITABILITY_NOT_ANALYZED. - ExploitabilityRating exploitability_; -}; - -} // namespace google_breakpad - -#endif // GOOGLE_BREAKPAD_PROCESSOR_PROCESS_STATE_H__ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/processor/source_line_resolver_base.h b/TMessagesProj/jni/breakpad/google_breakpad/processor/source_line_resolver_base.h deleted file mode 100644 index c720b0c32..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/processor/source_line_resolver_base.h +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) 2010 Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// source_line_resolver_base.h: SourceLineResolverBase, an (incomplete) -// implementation of SourceLineResolverInterface. It serves as a common base -// class for concrete implementations: FastSourceLineResolver and -// BasicSourceLineResolver. It is designed for refactoring that removes -// code redundancy in the two concrete source line resolver classes. -// -// See "google_breakpad/processor/source_line_resolver_interface.h" for more -// documentation. - -// Author: Siyang Xie (lambxsy@google.com) - -#ifndef GOOGLE_BREAKPAD_PROCESSOR_SOURCE_LINE_RESOLVER_BASE_H__ -#define GOOGLE_BREAKPAD_PROCESSOR_SOURCE_LINE_RESOLVER_BASE_H__ - -#include -#include -#include - -#include "google_breakpad/processor/source_line_resolver_interface.h" - -namespace google_breakpad { - -using std::map; -using std::set; - -// Forward declaration. -// ModuleFactory is a simple factory interface for creating a Module instance -// at run-time. -class ModuleFactory; - -class SourceLineResolverBase : public SourceLineResolverInterface { - public: - // Read the symbol_data from a file with given file_name. - // The part of code was originally in BasicSourceLineResolver::Module's - // LoadMap() method. - // Place dynamically allocated heap buffer in symbol_data. Caller has the - // ownership of the buffer, and should call delete [] to free the buffer. - static bool ReadSymbolFile(const string &file_name, - char **symbol_data, - size_t *symbol_data_size); - - protected: - // Users are not allowed create SourceLineResolverBase instance directly. - SourceLineResolverBase(ModuleFactory *module_factory); - virtual ~SourceLineResolverBase(); - - // Virtual methods inherited from SourceLineResolverInterface. - virtual bool LoadModule(const CodeModule *module, const string &map_file); - virtual bool LoadModuleUsingMapBuffer(const CodeModule *module, - const string &map_buffer); - virtual bool LoadModuleUsingMemoryBuffer(const CodeModule *module, - char *memory_buffer, - size_t memory_buffer_size); - virtual bool ShouldDeleteMemoryBufferAfterLoadModule(); - virtual void UnloadModule(const CodeModule *module); - virtual bool HasModule(const CodeModule *module); - virtual bool IsModuleCorrupt(const CodeModule *module); - virtual void FillSourceLineInfo(StackFrame *frame); - virtual WindowsFrameInfo *FindWindowsFrameInfo(const StackFrame *frame); - virtual CFIFrameInfo *FindCFIFrameInfo(const StackFrame *frame); - - // Nested structs and classes. - struct Line; - struct Function; - struct PublicSymbol; - struct CompareString { - bool operator()(const string &s1, const string &s2) const; - }; - // Module is an interface for an in-memory symbol file. - class Module; - class AutoFileCloser; - - // All of the modules that are loaded. - typedef map ModuleMap; - ModuleMap *modules_; - - // The loaded modules that were detecting to be corrupt during load. - typedef set ModuleSet; - ModuleSet *corrupt_modules_; - - // All of heap-allocated buffers that are owned locally by resolver. - typedef std::map MemoryMap; - MemoryMap *memory_buffers_; - - // Creates a concrete module at run-time. - ModuleFactory *module_factory_; - - private: - // ModuleFactory needs to have access to protected type Module. - friend class ModuleFactory; - - // Disallow unwanted copy ctor and assignment operator - SourceLineResolverBase(const SourceLineResolverBase&); - void operator=(const SourceLineResolverBase&); -}; - -} // namespace google_breakpad - -#endif // GOOGLE_BREAKPAD_PROCESSOR_SOURCE_LINE_RESOLVER_BASE_H__ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/processor/source_line_resolver_interface.h b/TMessagesProj/jni/breakpad/google_breakpad/processor/source_line_resolver_interface.h deleted file mode 100644 index a694bf2ea..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/processor/source_line_resolver_interface.h +++ /dev/null @@ -1,117 +0,0 @@ -// -*- mode: C++ -*- - -// Copyright (c) 2010 Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// Abstract interface to return function/file/line info for a memory address. - -#ifndef GOOGLE_BREAKPAD_PROCESSOR_SOURCE_LINE_RESOLVER_INTERFACE_H__ -#define GOOGLE_BREAKPAD_PROCESSOR_SOURCE_LINE_RESOLVER_INTERFACE_H__ - -#include - -#include "common/using_std_string.h" -#include "google_breakpad/common/breakpad_types.h" -#include "google_breakpad/processor/code_module.h" - -namespace google_breakpad { - -struct StackFrame; -struct WindowsFrameInfo; -class CFIFrameInfo; - -class SourceLineResolverInterface { - public: - typedef uint64_t MemAddr; - - virtual ~SourceLineResolverInterface() {} - - // Adds a module to this resolver, returning true on success. - // - // module should have at least the code_file, debug_file, - // and debug_identifier members populated. - // - // map_file should contain line/address mappings for this module. - virtual bool LoadModule(const CodeModule *module, - const string &map_file) = 0; - // Same as above, but takes the contents of a pre-read map buffer - virtual bool LoadModuleUsingMapBuffer(const CodeModule *module, - const string &map_buffer) = 0; - - // Add an interface to load symbol using C-String data instead of string. - // This is useful in the optimization design for avoiding unnecessary copying - // of symbol data, in order to improve memory efficiency. - // LoadModuleUsingMemoryBuffer() does NOT take ownership of memory_buffer. - // LoadModuleUsingMemoryBuffer() null terminates the passed in buffer, if - // the last character is not a null terminator. - virtual bool LoadModuleUsingMemoryBuffer(const CodeModule *module, - char *memory_buffer, - size_t memory_buffer_size) = 0; - - // Return true if the memory buffer should be deleted immediately after - // LoadModuleUsingMemoryBuffer(). Return false if the memory buffer has to be - // alive during the lifetime of the corresponding Module. - virtual bool ShouldDeleteMemoryBufferAfterLoadModule() = 0; - - // Request that the specified module be unloaded from this resolver. - // A resolver may choose to ignore such a request. - virtual void UnloadModule(const CodeModule *module) = 0; - - // Returns true if the module has been loaded. - virtual bool HasModule(const CodeModule *module) = 0; - - // Returns true if the module has been loaded and it is corrupt. - virtual bool IsModuleCorrupt(const CodeModule *module) = 0; - - // Fills in the function_base, function_name, source_file_name, - // and source_line fields of the StackFrame. The instruction and - // module_name fields must already be filled in. - virtual void FillSourceLineInfo(StackFrame *frame) = 0; - - // If Windows stack walking information is available covering - // FRAME's instruction address, return a WindowsFrameInfo structure - // describing it. If the information is not available, returns NULL. - // A NULL return value does not indicate an error. The caller takes - // ownership of any returned WindowsFrameInfo object. - virtual WindowsFrameInfo *FindWindowsFrameInfo(const StackFrame *frame) = 0; - - // If CFI stack walking information is available covering ADDRESS, - // return a CFIFrameInfo structure describing it. If the information - // is not available, return NULL. The caller takes ownership of any - // returned CFIFrameInfo object. - virtual CFIFrameInfo *FindCFIFrameInfo(const StackFrame *frame) = 0; - - protected: - // SourceLineResolverInterface cannot be instantiated except by subclasses - SourceLineResolverInterface() {} -}; - -} // namespace google_breakpad - -#endif // GOOGLE_BREAKPAD_PROCESSOR_SOURCE_LINE_RESOLVER_INTERFACE_H__ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/processor/stack_frame.h b/TMessagesProj/jni/breakpad/google_breakpad/processor/stack_frame.h deleted file mode 100644 index b55eb9c75..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/processor/stack_frame.h +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) 2006, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef GOOGLE_BREAKPAD_PROCESSOR_STACK_FRAME_H__ -#define GOOGLE_BREAKPAD_PROCESSOR_STACK_FRAME_H__ - -#include - -#include "common/using_std_string.h" -#include "google_breakpad/common/breakpad_types.h" - -namespace google_breakpad { - -class CodeModule; - -struct StackFrame { - // Indicates how well the instruction pointer derived during - // stack walking is trusted. Since the stack walker can resort to - // stack scanning, it can wind up with dubious frames. - // In rough order of "trust metric". - enum FrameTrust { - FRAME_TRUST_NONE, // Unknown - FRAME_TRUST_SCAN, // Scanned the stack, found this - FRAME_TRUST_CFI_SCAN, // Found while scanning stack using call frame info - FRAME_TRUST_FP, // Derived from frame pointer - FRAME_TRUST_CFI, // Derived from call frame info - FRAME_TRUST_PREWALKED, // Explicitly provided by some external stack walker. - FRAME_TRUST_CONTEXT // Given as instruction pointer in a context - }; - - StackFrame() - : instruction(), - module(NULL), - function_name(), - function_base(), - source_file_name(), - source_line(), - source_line_base(), - trust(FRAME_TRUST_NONE) {} - virtual ~StackFrame() {} - - // Return a string describing how this stack frame was found - // by the stackwalker. - string trust_description() const { - switch (trust) { - case StackFrame::FRAME_TRUST_CONTEXT: - return "given as instruction pointer in context"; - case StackFrame::FRAME_TRUST_PREWALKED: - return "recovered by external stack walker"; - case StackFrame::FRAME_TRUST_CFI: - return "call frame info"; - case StackFrame::FRAME_TRUST_CFI_SCAN: - return "call frame info with scanning"; - case StackFrame::FRAME_TRUST_FP: - return "previous frame's frame pointer"; - case StackFrame::FRAME_TRUST_SCAN: - return "stack scanning"; - default: - return "unknown"; - } - }; - - // Return the actual return address, as saved on the stack or in a - // register. See the comments for 'instruction', below, for details. - virtual uint64_t ReturnAddress() const { return instruction; } - - // The program counter location as an absolute virtual address. - // - // - For the innermost called frame in a stack, this will be an exact - // program counter or instruction pointer value. - // - // - For all other frames, this address is within the instruction that - // caused execution to branch to this frame's callee (although it may - // not point to the exact beginning of that instruction). This ensures - // that, when we look up the source code location for this frame, we - // get the source location of the call, not of the point at which - // control will resume when the call returns, which may be on the next - // line. (If the compiler knows the callee never returns, it may even - // place the call instruction at the very end of the caller's machine - // code, such that the "return address" (which will never be used) - // immediately after the call instruction is in an entirely different - // function, perhaps even from a different source file.) - // - // On some architectures, the return address as saved on the stack or in - // a register is fine for looking up the point of the call. On others, it - // requires adjustment. ReturnAddress returns the address as saved by the - // machine. - uint64_t instruction; - - // The module in which the instruction resides. - const CodeModule *module; - - // The function name, may be omitted if debug symbols are not available. - string function_name; - - // The start address of the function, may be omitted if debug symbols - // are not available. - uint64_t function_base; - - // The source file name, may be omitted if debug symbols are not available. - string source_file_name; - - // The (1-based) source line number, may be omitted if debug symbols are - // not available. - int source_line; - - // The start address of the source line, may be omitted if debug symbols - // are not available. - uint64_t source_line_base; - - // Amount of trust the stack walker has in the instruction pointer - // of this frame. - FrameTrust trust; -}; - -} // namespace google_breakpad - -#endif // GOOGLE_BREAKPAD_PROCESSOR_STACK_FRAME_H__ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/processor/stack_frame_cpu.h b/TMessagesProj/jni/breakpad/google_breakpad/processor/stack_frame_cpu.h deleted file mode 100644 index dc5d8ae67..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/processor/stack_frame_cpu.h +++ /dev/null @@ -1,405 +0,0 @@ -// -*- mode: c++ -*- - -// Copyright (c) 2010 Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// stack_frame_cpu.h: CPU-specific StackFrame extensions. -// -// These types extend the StackFrame structure to carry CPU-specific register -// state. They are defined in this header instead of stack_frame.h to -// avoid the need to include minidump_format.h when only the generic -// StackFrame type is needed. -// -// Author: Mark Mentovai - -#ifndef GOOGLE_BREAKPAD_PROCESSOR_STACK_FRAME_CPU_H__ -#define GOOGLE_BREAKPAD_PROCESSOR_STACK_FRAME_CPU_H__ - -#include "google_breakpad/common/minidump_format.h" -#include "google_breakpad/processor/stack_frame.h" - -namespace google_breakpad { - -struct WindowsFrameInfo; -class CFIFrameInfo; - -struct StackFrameX86 : public StackFrame { - // ContextValidity has one entry for each relevant hardware pointer - // register (%eip and %esp) and one entry for each general-purpose - // register. It's worthwhile having validity flags for caller-saves - // registers: they are valid in the youngest frame, and such a frame - // might save a callee-saves register in a caller-saves register, but - // SimpleCFIWalker won't touch registers unless they're marked as valid. - enum ContextValidity { - CONTEXT_VALID_NONE = 0, - CONTEXT_VALID_EIP = 1 << 0, - CONTEXT_VALID_ESP = 1 << 1, - CONTEXT_VALID_EBP = 1 << 2, - CONTEXT_VALID_EAX = 1 << 3, - CONTEXT_VALID_EBX = 1 << 4, - CONTEXT_VALID_ECX = 1 << 5, - CONTEXT_VALID_EDX = 1 << 6, - CONTEXT_VALID_ESI = 1 << 7, - CONTEXT_VALID_EDI = 1 << 8, - CONTEXT_VALID_ALL = -1 - }; - - StackFrameX86() - : context(), - context_validity(CONTEXT_VALID_NONE), - windows_frame_info(NULL), - cfi_frame_info(NULL) {} - ~StackFrameX86(); - - // Overriden to return the return address as saved on the stack. - virtual uint64_t ReturnAddress() const; - - // Register state. This is only fully valid for the topmost frame in a - // stack. In other frames, the values of nonvolatile registers may be - // present, given sufficient debugging information. Refer to - // context_validity. - MDRawContextX86 context; - - // context_validity is actually ContextValidity, but int is used because - // the OR operator doesn't work well with enumerated types. This indicates - // which fields in context are valid. - int context_validity; - - // Any stack walking information we found describing this.instruction. - // These may be NULL if there is no such information for that address. - WindowsFrameInfo *windows_frame_info; - CFIFrameInfo *cfi_frame_info; -}; - -struct StackFramePPC : public StackFrame { - // ContextValidity should eventually contain entries for the validity of - // other nonvolatile (callee-save) registers as in - // StackFrameX86::ContextValidity, but the ppc stackwalker doesn't currently - // locate registers other than the ones listed here. - enum ContextValidity { - CONTEXT_VALID_NONE = 0, - CONTEXT_VALID_SRR0 = 1 << 0, - CONTEXT_VALID_GPR1 = 1 << 1, - CONTEXT_VALID_ALL = -1 - }; - - StackFramePPC() : context(), context_validity(CONTEXT_VALID_NONE) {} - - // Register state. This is only fully valid for the topmost frame in a - // stack. In other frames, the values of nonvolatile registers may be - // present, given sufficient debugging information. Refer to - // context_validity. - MDRawContextPPC context; - - // context_validity is actually ContextValidity, but int is used because - // the OR operator doesn't work well with enumerated types. This indicates - // which fields in context are valid. - int context_validity; -}; - -struct StackFramePPC64 : public StackFrame { - // ContextValidity should eventually contain entries for the validity of - // other nonvolatile (callee-save) registers as in - // StackFrameX86::ContextValidity, but the ppc stackwalker doesn't currently - // locate registers other than the ones listed here. - enum ContextValidity { - CONTEXT_VALID_NONE = 0, - CONTEXT_VALID_SRR0 = 1 << 0, - CONTEXT_VALID_GPR1 = 1 << 1, - CONTEXT_VALID_ALL = -1 - }; - - StackFramePPC64() : context(), context_validity(CONTEXT_VALID_NONE) {} - - // Register state. This is only fully valid for the topmost frame in a - // stack. In other frames, the values of nonvolatile registers may be - // present, given sufficient debugging information. Refer to - // context_validity. - MDRawContextPPC64 context; - - // context_validity is actually ContextValidity, but int is used because - // the OR operator doesn't work well with enumerated types. This indicates - // which fields in context are valid. - int context_validity; -}; - -struct StackFrameAMD64 : public StackFrame { - // ContextValidity has one entry for each register that we might be able - // to recover. - enum ContextValidity { - CONTEXT_VALID_NONE = 0, - CONTEXT_VALID_RAX = 1 << 0, - CONTEXT_VALID_RDX = 1 << 1, - CONTEXT_VALID_RCX = 1 << 2, - CONTEXT_VALID_RBX = 1 << 3, - CONTEXT_VALID_RSI = 1 << 4, - CONTEXT_VALID_RDI = 1 << 5, - CONTEXT_VALID_RBP = 1 << 6, - CONTEXT_VALID_RSP = 1 << 7, - CONTEXT_VALID_R8 = 1 << 8, - CONTEXT_VALID_R9 = 1 << 9, - CONTEXT_VALID_R10 = 1 << 10, - CONTEXT_VALID_R11 = 1 << 11, - CONTEXT_VALID_R12 = 1 << 12, - CONTEXT_VALID_R13 = 1 << 13, - CONTEXT_VALID_R14 = 1 << 14, - CONTEXT_VALID_R15 = 1 << 15, - CONTEXT_VALID_RIP = 1 << 16, - CONTEXT_VALID_ALL = -1 - }; - - StackFrameAMD64() : context(), context_validity(CONTEXT_VALID_NONE) {} - - // Overriden to return the return address as saved on the stack. - virtual uint64_t ReturnAddress() const; - - // Register state. This is only fully valid for the topmost frame in a - // stack. In other frames, which registers are present depends on what - // debugging information we had available. Refer to context_validity. - MDRawContextAMD64 context; - - // For each register in context whose value has been recovered, we set - // the corresponding CONTEXT_VALID_ bit in context_validity. - // - // context_validity's type should actually be ContextValidity, but - // we use int instead because the bitwise inclusive or operator - // yields an int when applied to enum values, and C++ doesn't - // silently convert from ints to enums. - int context_validity; -}; - -struct StackFrameSPARC : public StackFrame { - // to be confirmed - enum ContextValidity { - CONTEXT_VALID_NONE = 0, - CONTEXT_VALID_PC = 1 << 0, - CONTEXT_VALID_SP = 1 << 1, - CONTEXT_VALID_FP = 1 << 2, - CONTEXT_VALID_ALL = -1 - }; - - StackFrameSPARC() : context(), context_validity(CONTEXT_VALID_NONE) {} - - // Register state. This is only fully valid for the topmost frame in a - // stack. In other frames, the values of nonvolatile registers may be - // present, given sufficient debugging information. Refer to - // context_validity. - MDRawContextSPARC context; - - // context_validity is actually ContextValidity, but int is used because - // the OR operator doesn't work well with enumerated types. This indicates - // which fields in context are valid. - int context_validity; -}; - -struct StackFrameARM : public StackFrame { - // A flag for each register we might know. - enum ContextValidity { - CONTEXT_VALID_NONE = 0, - CONTEXT_VALID_R0 = 1 << 0, - CONTEXT_VALID_R1 = 1 << 1, - CONTEXT_VALID_R2 = 1 << 2, - CONTEXT_VALID_R3 = 1 << 3, - CONTEXT_VALID_R4 = 1 << 4, - CONTEXT_VALID_R5 = 1 << 5, - CONTEXT_VALID_R6 = 1 << 6, - CONTEXT_VALID_R7 = 1 << 7, - CONTEXT_VALID_R8 = 1 << 8, - CONTEXT_VALID_R9 = 1 << 9, - CONTEXT_VALID_R10 = 1 << 10, - CONTEXT_VALID_R11 = 1 << 11, - CONTEXT_VALID_R12 = 1 << 12, - CONTEXT_VALID_R13 = 1 << 13, - CONTEXT_VALID_R14 = 1 << 14, - CONTEXT_VALID_R15 = 1 << 15, - CONTEXT_VALID_ALL = ~CONTEXT_VALID_NONE, - - // Aliases for registers with dedicated or conventional roles. - CONTEXT_VALID_FP = CONTEXT_VALID_R11, - CONTEXT_VALID_SP = CONTEXT_VALID_R13, - CONTEXT_VALID_LR = CONTEXT_VALID_R14, - CONTEXT_VALID_PC = CONTEXT_VALID_R15 - }; - - StackFrameARM() : context(), context_validity(CONTEXT_VALID_NONE) {} - - // Return the ContextValidity flag for register rN. - static ContextValidity RegisterValidFlag(int n) { - return ContextValidity(1 << n); - } - - // Register state. This is only fully valid for the topmost frame in a - // stack. In other frames, the values of nonvolatile registers may be - // present, given sufficient debugging information. Refer to - // context_validity. - MDRawContextARM context; - - // For each register in context whose value has been recovered, we set - // the corresponding CONTEXT_VALID_ bit in context_validity. - // - // context_validity's type should actually be ContextValidity, but - // we use int instead because the bitwise inclusive or operator - // yields an int when applied to enum values, and C++ doesn't - // silently convert from ints to enums. - int context_validity; -}; - -struct StackFrameARM64 : public StackFrame { - // A flag for each register we might know. Note that we can't use an enum - // here as there are 33 values to represent. - static const uint64_t CONTEXT_VALID_NONE = 0; - static const uint64_t CONTEXT_VALID_X0 = 1ULL << 0; - static const uint64_t CONTEXT_VALID_X1 = 1ULL << 1; - static const uint64_t CONTEXT_VALID_X2 = 1ULL << 2; - static const uint64_t CONTEXT_VALID_X3 = 1ULL << 3; - static const uint64_t CONTEXT_VALID_X4 = 1ULL << 4; - static const uint64_t CONTEXT_VALID_X5 = 1ULL << 5; - static const uint64_t CONTEXT_VALID_X6 = 1ULL << 6; - static const uint64_t CONTEXT_VALID_X7 = 1ULL << 7; - static const uint64_t CONTEXT_VALID_X8 = 1ULL << 8; - static const uint64_t CONTEXT_VALID_X9 = 1ULL << 9; - static const uint64_t CONTEXT_VALID_X10 = 1ULL << 10; - static const uint64_t CONTEXT_VALID_X11 = 1ULL << 11; - static const uint64_t CONTEXT_VALID_X12 = 1ULL << 12; - static const uint64_t CONTEXT_VALID_X13 = 1ULL << 13; - static const uint64_t CONTEXT_VALID_X14 = 1ULL << 14; - static const uint64_t CONTEXT_VALID_X15 = 1ULL << 15; - static const uint64_t CONTEXT_VALID_X16 = 1ULL << 16; - static const uint64_t CONTEXT_VALID_X17 = 1ULL << 17; - static const uint64_t CONTEXT_VALID_X18 = 1ULL << 18; - static const uint64_t CONTEXT_VALID_X19 = 1ULL << 19; - static const uint64_t CONTEXT_VALID_X20 = 1ULL << 20; - static const uint64_t CONTEXT_VALID_X21 = 1ULL << 21; - static const uint64_t CONTEXT_VALID_X22 = 1ULL << 22; - static const uint64_t CONTEXT_VALID_X23 = 1ULL << 23; - static const uint64_t CONTEXT_VALID_X24 = 1ULL << 24; - static const uint64_t CONTEXT_VALID_X25 = 1ULL << 25; - static const uint64_t CONTEXT_VALID_X26 = 1ULL << 26; - static const uint64_t CONTEXT_VALID_X27 = 1ULL << 27; - static const uint64_t CONTEXT_VALID_X28 = 1ULL << 28; - static const uint64_t CONTEXT_VALID_X29 = 1ULL << 29; - static const uint64_t CONTEXT_VALID_X30 = 1ULL << 30; - static const uint64_t CONTEXT_VALID_X31 = 1ULL << 31; - static const uint64_t CONTEXT_VALID_X32 = 1ULL << 32; - static const uint64_t CONTEXT_VALID_ALL = ~CONTEXT_VALID_NONE; - - // Aliases for registers with dedicated or conventional roles. - static const uint64_t CONTEXT_VALID_FP = CONTEXT_VALID_X29; - static const uint64_t CONTEXT_VALID_LR = CONTEXT_VALID_X30; - static const uint64_t CONTEXT_VALID_SP = CONTEXT_VALID_X31; - static const uint64_t CONTEXT_VALID_PC = CONTEXT_VALID_X32; - - StackFrameARM64() : context(), - context_validity(CONTEXT_VALID_NONE) {} - - // Return the validity flag for register xN. - static uint64_t RegisterValidFlag(int n) { - return 1ULL << n; - } - - // Register state. This is only fully valid for the topmost frame in a - // stack. In other frames, the values of nonvolatile registers may be - // present, given sufficient debugging information. Refer to - // context_validity. - MDRawContextARM64 context; - - // For each register in context whose value has been recovered, we set - // the corresponding CONTEXT_VALID_ bit in context_validity. - uint64_t context_validity; -}; - -struct StackFrameMIPS : public StackFrame { - // MIPS callee save registers for o32 ABI (32bit registers) are: - // 1. $s0-$s7, - // 2. $sp, $fp - // 3. $f20-$f31 - // - // The register structure is available at - // http://en.wikipedia.org/wiki/MIPS_architecture#Compiler_register_usage - -#define INDEX_MIPS_REG_S0 MD_CONTEXT_MIPS_REG_S0 // 16 -#define INDEX_MIPS_REG_S7 MD_CONTEXT_MIPS_REG_S7 // 23 -#define INDEX_MIPS_REG_GP MD_CONTEXT_MIPS_REG_GP // 28 -#define INDEX_MIPS_REG_RA MD_CONTEXT_MIPS_REG_RA // 31 -#define INDEX_MIPS_REG_PC 34 -#define SHIFT_MIPS_REG_S0 0 -#define SHIFT_MIPS_REG_GP 8 -#define SHIFT_MIPS_REG_PC 12 - - enum ContextValidity { - CONTEXT_VALID_NONE = 0, - CONTEXT_VALID_S0 = 1 << 0, // $16 - CONTEXT_VALID_S1 = 1 << 1, // $17 - CONTEXT_VALID_S2 = 1 << 2, // $18 - CONTEXT_VALID_S3 = 1 << 3, // $19 - CONTEXT_VALID_S4 = 1 << 4, // $20 - CONTEXT_VALID_S5 = 1 << 5, // $21 - CONTEXT_VALID_S6 = 1 << 6, // $22 - CONTEXT_VALID_S7 = 1 << 7, // $23 - // GP is not calee-save for o32 abi. - CONTEXT_VALID_GP = 1 << 8, // $28 - CONTEXT_VALID_SP = 1 << 9, // $29 - CONTEXT_VALID_FP = 1 << 10, // $30 - CONTEXT_VALID_RA = 1 << 11, // $31 - CONTEXT_VALID_PC = 1 << 12, // $34 - CONTEXT_VALID_ALL = ~CONTEXT_VALID_NONE - }; - - // Return the ContextValidity flag for register rN. - static ContextValidity RegisterValidFlag(int n) { - if (n >= INDEX_MIPS_REG_S0 && n <= INDEX_MIPS_REG_S7) - return ContextValidity(1 << (n - INDEX_MIPS_REG_S0 + SHIFT_MIPS_REG_S0)); - else if (n >= INDEX_MIPS_REG_GP && n <= INDEX_MIPS_REG_RA) - return ContextValidity(1 << (n - INDEX_MIPS_REG_GP + SHIFT_MIPS_REG_GP)); - else if (n == INDEX_MIPS_REG_PC) - return ContextValidity(1 << SHIFT_MIPS_REG_PC); - - return CONTEXT_VALID_NONE; - } - - StackFrameMIPS() : context(), context_validity(CONTEXT_VALID_NONE) {} - - // Register state. This is only fully valid for the topmost frame in a - // stack. In other frames, which registers are present depends on what - // debugging information were available. Refer to 'context_validity' below. - MDRawContextMIPS context; - - // For each register in context whose value has been recovered, - // the corresponding CONTEXT_VALID_ bit in 'context_validity' is set. - // - // context_validity's type should actually be ContextValidity, but - // type int is used instead because the bitwise inclusive or operator - // yields an int when applied to enum values, and C++ doesn't - // silently convert from ints to enums. - int context_validity; -}; - -} // namespace google_breakpad - -#endif // GOOGLE_BREAKPAD_PROCESSOR_STACK_FRAME_CPU_H__ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/processor/stack_frame_symbolizer.h b/TMessagesProj/jni/breakpad/google_breakpad/processor/stack_frame_symbolizer.h deleted file mode 100644 index 074907cb1..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/processor/stack_frame_symbolizer.h +++ /dev/null @@ -1,108 +0,0 @@ -// -*- mode: C++ -*- - -// Copyright (c) 2012 Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// Helper class that encapsulates the logic of how symbol supplier interacts -// with source line resolver to fill stack frame information. - -#ifndef GOOGLE_BREAKPAD_PROCESSOR_STACK_FRAME_SYMBOLIZER_H__ -#define GOOGLE_BREAKPAD_PROCESSOR_STACK_FRAME_SYMBOLIZER_H__ - -#include -#include - -#include "common/using_std_string.h" -#include "google_breakpad/common/breakpad_types.h" -#include "google_breakpad/processor/code_module.h" - -namespace google_breakpad { -class CFIFrameInfo; -class CodeModules; -class SymbolSupplier; -class SourceLineResolverInterface; -struct StackFrame; -struct SystemInfo; -struct WindowsFrameInfo; - -class StackFrameSymbolizer { - public: - enum SymbolizerResult { - // Symbol data was found and successfully loaded in resolver. - // This does NOT guarantee source line info is found within symbol file. - kNoError, - // This indicates non-critical error, such as, no code module found for - // frame's instruction, no symbol file, or resolver failed to load symbol. - kError, - // This indicates error for which stack walk should be interrupted - // and retried in future. - kInterrupt, - // Symbol data was found and loaded in resolver however some corruptions - // were detected. - kWarningCorruptSymbols, - }; - - StackFrameSymbolizer(SymbolSupplier* supplier, - SourceLineResolverInterface* resolver); - - virtual ~StackFrameSymbolizer() { } - - // Encapsulate the step of resolving source line info for a stack frame. - // "frame" must not be NULL. - virtual SymbolizerResult FillSourceLineInfo(const CodeModules* modules, - const SystemInfo* system_info, - StackFrame* stack_frame); - - virtual WindowsFrameInfo* FindWindowsFrameInfo(const StackFrame* frame); - - virtual CFIFrameInfo* FindCFIFrameInfo(const StackFrame* frame); - - // Reset internal (locally owned) data as if the helper is re-instantiated. - // A typical case is to call Reset() after processing an individual report - // before start to process next one, in order to reset internal information - // about missing symbols found so far. - virtual void Reset() { no_symbol_modules_.clear(); } - - // Returns true if there is valid implementation for stack symbolization. - virtual bool HasImplementation() { return resolver_ && supplier_; } - - SourceLineResolverInterface* resolver() { return resolver_; } - SymbolSupplier* supplier() { return supplier_; } - - protected: - SymbolSupplier* supplier_; - SourceLineResolverInterface* resolver_; - // A list of modules known to have symbols missing. This helps avoid - // repeated lookups for the missing symbols within one minidump. - std::set no_symbol_modules_; -}; - -} // namespace google_breakpad - -#endif // GOOGLE_BREAKPAD_PROCESSOR_STACK_FRAME_SYMBOLIZER_H__ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/processor/stackwalker.h b/TMessagesProj/jni/breakpad/google_breakpad/processor/stackwalker.h deleted file mode 100644 index a1bd3e7fe..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/processor/stackwalker.h +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright (c) 2010 Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// stackwalker.h: Generic stackwalker. -// -// The Stackwalker class is an abstract base class providing common generic -// methods that apply to stacks from all systems. Specific implementations -// will extend this class by providing GetContextFrame and GetCallerFrame -// methods to fill in system-specific data in a StackFrame structure. -// Stackwalker assembles these StackFrame strucutres into a CallStack. -// -// Author: Mark Mentovai - - -#ifndef GOOGLE_BREAKPAD_PROCESSOR_STACKWALKER_H__ -#define GOOGLE_BREAKPAD_PROCESSOR_STACKWALKER_H__ - -#include -#include -#include - -#include "common/using_std_string.h" -#include "google_breakpad/common/breakpad_types.h" -#include "google_breakpad/processor/code_modules.h" -#include "google_breakpad/processor/memory_region.h" -#include "google_breakpad/processor/stack_frame_symbolizer.h" - -namespace google_breakpad { - -class CallStack; -class DumpContext; -class StackFrameSymbolizer; - -using std::set; -using std::vector; - -class Stackwalker { - public: - virtual ~Stackwalker() {} - - // Populates the given CallStack by calling GetContextFrame and - // GetCallerFrame. The frames are further processed to fill all available - // data. Returns true if the stackwalk completed, or false if it was - // interrupted by SymbolSupplier::GetSymbolFile(). - // Upon return, |modules_without_symbols| will be populated with pointers to - // the code modules (CodeModule*) that DON'T have symbols. - // |modules_with_corrupt_symbols| will be populated with pointers to the - // modules which have corrupt symbols. |modules_without_symbols| and - // |modules_with_corrupt_symbols| DO NOT take ownership of the code modules. - // The lifetime of these code modules is the same as the lifetime of the - // CodeModules passed to the StackWalker constructor (which currently - // happens to be the lifetime of the Breakpad's ProcessingState object). - // There is a check for duplicate modules so no duplicates are expected. - bool Walk(CallStack* stack, - vector* modules_without_symbols, - vector* modules_with_corrupt_symbols); - - // Returns a new concrete subclass suitable for the CPU that a stack was - // generated on, according to the CPU type indicated by the context - // argument. If no suitable concrete subclass exists, returns NULL. - static Stackwalker* StackwalkerForCPU( - const SystemInfo* system_info, - DumpContext* context, - MemoryRegion* memory, - const CodeModules* modules, - StackFrameSymbolizer* resolver_helper); - - static void set_max_frames(uint32_t max_frames) { - max_frames_ = max_frames; - max_frames_set_ = true; - } - static uint32_t max_frames() { return max_frames_; } - - static void set_max_frames_scanned(uint32_t max_frames_scanned) { - max_frames_scanned_ = max_frames_scanned; - } - - protected: - // system_info identifies the operating system, NULL or empty if unknown. - // memory identifies a MemoryRegion that provides the stack memory - // for the stack to walk. modules, if non-NULL, is a CodeModules - // object that is used to look up which code module each stack frame is - // associated with. frame_symbolizer is a StackFrameSymbolizer object that - // encapsulates the logic of how source line resolver interacts with symbol - // supplier to symbolize stack frame and look up caller frame information - // (see stack_frame_symbolizer.h). - // frame_symbolizer MUST NOT be NULL (asserted). - Stackwalker(const SystemInfo* system_info, - MemoryRegion* memory, - const CodeModules* modules, - StackFrameSymbolizer* frame_symbolizer); - - // This can be used to filter out potential return addresses when - // the stack walker resorts to stack scanning. - // Returns true if any of: - // * This address is within a loaded module, but we don't have symbols - // for that module. - // * This address is within a loaded module for which we have symbols, - // and falls inside a function in that module. - // Returns false otherwise. - bool InstructionAddressSeemsValid(uint64_t address); - - // The default number of words to search through on the stack - // for a return address. - static const int kRASearchWords; - - template - bool ScanForReturnAddress(InstructionType location_start, - InstructionType* location_found, - InstructionType* ip_found, - bool is_context_frame) { - // When searching for the caller of the context frame, - // allow the scanner to look farther down the stack. - const int search_words = is_context_frame ? - kRASearchWords * 4 : - kRASearchWords; - - return ScanForReturnAddress(location_start, location_found, ip_found, - search_words); - } - - // Scan the stack starting at location_start, looking for an address - // that looks like a valid instruction pointer. Addresses must - // 1) be contained in the current stack memory - // 2) pass the checks in InstructionAddressSeemsValid - // - // Returns true if a valid-looking instruction pointer was found. - // When returning true, sets location_found to the address at which - // the value was found, and ip_found to the value contained at that - // location in memory. - template - bool ScanForReturnAddress(InstructionType location_start, - InstructionType* location_found, - InstructionType* ip_found, - int searchwords) { - for (InstructionType location = location_start; - location <= location_start + searchwords * sizeof(InstructionType); - location += sizeof(InstructionType)) { - InstructionType ip; - if (!memory_->GetMemoryAtAddress(location, &ip)) - break; - - if (modules_ && modules_->GetModuleForAddress(ip) && - InstructionAddressSeemsValid(ip)) { - *ip_found = ip; - *location_found = location; - return true; - } - } - // nothing found - return false; - } - - // Information about the system that produced the minidump. Subclasses - // and the SymbolSupplier may find this information useful. - const SystemInfo* system_info_; - - // The stack memory to walk. Subclasses will require this region to - // get information from the stack. - MemoryRegion* memory_; - - // A list of modules, for populating each StackFrame's module information. - // This field is optional and may be NULL. - const CodeModules* modules_; - - protected: - // The StackFrameSymbolizer implementation. - StackFrameSymbolizer* frame_symbolizer_; - - private: - // Obtains the context frame, the innermost called procedure in a stack - // trace. Returns NULL on failure. GetContextFrame allocates a new - // StackFrame (or StackFrame subclass), ownership of which is taken by - // the caller. - virtual StackFrame* GetContextFrame() = 0; - - // Obtains a caller frame. Each call to GetCallerFrame should return the - // frame that called the last frame returned by GetContextFrame or - // GetCallerFrame. To aid this purpose, stack contains the CallStack - // made of frames that have already been walked. GetCallerFrame should - // return NULL on failure or when there are no more caller frames (when - // the end of the stack has been reached). GetCallerFrame allocates a new - // StackFrame (or StackFrame subclass), ownership of which is taken by - // the caller. |stack_scan_allowed| controls whether stack scanning is - // an allowable frame-recovery method, since it is desirable to be able to - // disable stack scanning in performance-critical use cases. - virtual StackFrame* GetCallerFrame(const CallStack* stack, - bool stack_scan_allowed) = 0; - - // The maximum number of frames Stackwalker will walk through. - // This defaults to 1024 to prevent infinite loops. - static uint32_t max_frames_; - - // Keep track of whether max_frames_ has been set by the user, since - // it affects whether or not an error message is printed in the case - // where an unwind got stopped by the limit. - static bool max_frames_set_; - - // The maximum number of stack-scanned and otherwise untrustworthy - // frames allowed. Stack-scanning can be expensive, so the option to - // disable or limit it is helpful in cases where unwind performance is - // important. This defaults to 1024, the same as max_frames_. - static uint32_t max_frames_scanned_; -}; - -} // namespace google_breakpad - - -#endif // GOOGLE_BREAKPAD_PROCESSOR_STACKWALKER_H__ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/processor/symbol_supplier.h b/TMessagesProj/jni/breakpad/google_breakpad/processor/symbol_supplier.h deleted file mode 100644 index a042081f3..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/processor/symbol_supplier.h +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) 2006, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// The caller may implement the SymbolSupplier abstract base class -// to provide symbols for a given module. - -#ifndef GOOGLE_BREAKPAD_PROCESSOR_SYMBOL_SUPPLIER_H__ -#define GOOGLE_BREAKPAD_PROCESSOR_SYMBOL_SUPPLIER_H__ - -#include -#include "common/using_std_string.h" - -namespace google_breakpad { - -class CodeModule; -struct SystemInfo; - -class SymbolSupplier { - public: - // Result type for GetSymbolFile - enum SymbolResult { - // no symbols were found, but continue processing - NOT_FOUND, - - // symbols were found, and the path has been placed in symbol_file - FOUND, - - // stops processing the minidump immediately - INTERRUPT - }; - - virtual ~SymbolSupplier() {} - - // Retrieves the symbol file for the given CodeModule, placing the - // path in symbol_file if successful. system_info contains strings - // identifying the operating system and CPU; SymbolSupplier may use - // to help locate the symbol file. system_info may be NULL or its - // fields may be empty if these values are unknown. symbol_file - // must be a pointer to a valid string - virtual SymbolResult GetSymbolFile(const CodeModule *module, - const SystemInfo *system_info, - string *symbol_file) = 0; - // Same as above, except also places symbol data into symbol_data. - // If symbol_data is NULL, the data is not returned. - // TODO(nealsid) Once we have symbol data caching behavior implemented - // investigate making all symbol suppliers implement all methods, - // and make this pure virtual - virtual SymbolResult GetSymbolFile(const CodeModule *module, - const SystemInfo *system_info, - string *symbol_file, - string *symbol_data) = 0; - - // Same as above, except allocates data buffer on heap and then places the - // symbol data into the buffer as C-string. - // SymbolSupplier is responsible for deleting the data buffer. After the call - // to GetCStringSymbolData(), the caller should call FreeSymbolData(const - // Module *module) once the data buffer is no longer needed. - // If symbol_data is not NULL, symbol supplier won't return FOUND unless it - // returns a valid buffer in symbol_data, e.g., returns INTERRUPT on memory - // allocation failure. - virtual SymbolResult GetCStringSymbolData(const CodeModule *module, - const SystemInfo *system_info, - string *symbol_file, - char **symbol_data, - size_t *symbol_data_size) = 0; - - // Frees the data buffer allocated for the module in GetCStringSymbolData. - virtual void FreeSymbolData(const CodeModule *module) = 0; -}; - -} // namespace google_breakpad - -#endif // GOOGLE_BREAKPAD_PROCESSOR_SYMBOL_SUPPLIER_H__ diff --git a/TMessagesProj/jni/breakpad/google_breakpad/processor/system_info.h b/TMessagesProj/jni/breakpad/google_breakpad/processor/system_info.h deleted file mode 100644 index 9583d9e89..000000000 --- a/TMessagesProj/jni/breakpad/google_breakpad/processor/system_info.h +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) 2006, Google Inc. -// 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 Google Inc. 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 -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// system_info.h: Information about the system that was running a program -// when a crash report was produced. -// -// Author: Mark Mentovai - -#ifndef GOOGLE_BREAKPAD_PROCESSOR_SYSTEM_INFO_H__ -#define GOOGLE_BREAKPAD_PROCESSOR_SYSTEM_INFO_H__ - -#include - -#include "common/using_std_string.h" - -namespace google_breakpad { - -struct SystemInfo { - public: - SystemInfo() : os(), os_short(), os_version(), cpu(), cpu_info(), - cpu_count(0) {} - - // Resets the SystemInfo object to its default values. - void Clear() { - os.clear(); - os_short.clear(); - os_version.clear(); - cpu.clear(); - cpu_info.clear(); - cpu_count = 0; - } - - // A string identifying the operating system, such as "Windows NT", - // "Mac OS X", or "Linux". If the information is present in the dump but - // its value is unknown, this field will contain a numeric value. If - // the information is not present in the dump, this field will be empty. - string os; - - // A short form of the os string, using lowercase letters and no spaces, - // suitable for use in a filesystem. Possible values include "windows", - // "mac", "linux" and "nacl". Empty if the information is not present - // in the dump or if the OS given by the dump is unknown. The values - // stored in this field should match those used by - // MinidumpSystemInfo::GetOS. - string os_short; - - // A string identifying the version of the operating system, such as - // "5.1.2600 Service Pack 2" or "10.4.8 8L2127". If the dump does not - // contain this information, this field will be empty. - string os_version; - - // A string identifying the basic CPU family, such as "x86" or "ppc". - // If this information is present in the dump but its value is unknown, - // this field will contain a numeric value. If the information is not - // present in the dump, this field will be empty. The values stored in - // this field should match those used by MinidumpSystemInfo::GetCPU. - string cpu; - - // A string further identifying the specific CPU, such as - // "GenuineIntel level 6 model 13 stepping 8". If the information is not - // present in the dump, or additional identifying information is not - // defined for the CPU family, this field will be empty. - string cpu_info; - - // The number of processors in the system. Will be greater than one for - // multi-core systems. - int cpu_count; -}; - -} // namespace google_breakpad - -#endif // GOOGLE_BREAKPAD_PROCESSOR_SYSTEM_INFO_H__ diff --git a/TMessagesProj/jni/breakpad/third_party/lss/linux_syscall_support.h b/TMessagesProj/jni/breakpad/third_party/lss/linux_syscall_support.h deleted file mode 100644 index 1fe0ae89e..000000000 --- a/TMessagesProj/jni/breakpad/third_party/lss/linux_syscall_support.h +++ /dev/null @@ -1,4029 +0,0 @@ -/* Copyright (c) 2005-2011, Google Inc. - * 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 Google Inc. 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 - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * --- - * Author: Markus Gutschke - */ - -/* This file includes Linux-specific support functions common to the - * coredumper and the thread lister; primarily, this is a collection - * of direct system calls, and a couple of symbols missing from - * standard header files. - * There are a few options that the including file can set to control - * the behavior of this file: - * - * SYS_CPLUSPLUS: - * The entire header file will normally be wrapped in 'extern "C" { }", - * making it suitable for compilation as both C and C++ source. If you - * do not want to do this, you can set the SYS_CPLUSPLUS macro to inhibit - * the wrapping. N.B. doing so will suppress inclusion of all prerequisite - * system header files, too. It is the caller's responsibility to provide - * the necessary definitions. - * - * SYS_ERRNO: - * All system calls will update "errno" unless overriden by setting the - * SYS_ERRNO macro prior to including this file. SYS_ERRNO should be - * an l-value. - * - * SYS_INLINE: - * New symbols will be defined "static inline", unless overridden by - * the SYS_INLINE macro. - * - * SYS_LINUX_SYSCALL_SUPPORT_H - * This macro is used to avoid multiple inclusions of this header file. - * If you need to include this file more than once, make sure to - * unset SYS_LINUX_SYSCALL_SUPPORT_H before each inclusion. - * - * SYS_PREFIX: - * New system calls will have a prefix of "sys_" unless overridden by - * the SYS_PREFIX macro. Valid values for this macro are [0..9] which - * results in prefixes "sys[0..9]_". It is also possible to set this - * macro to -1, which avoids all prefixes. - * - * SYS_SYSCALL_ENTRYPOINT: - * Some applications (such as sandboxes that filter system calls), need - * to be able to run custom-code each time a system call is made. If this - * macro is defined, it expands to the name of a "common" symbol. If - * this symbol is assigned a non-NULL pointer value, it is used as the - * address of the system call entrypoint. - * A pointer to this symbol can be obtained by calling - * get_syscall_entrypoint() - * - * This file defines a few internal symbols that all start with "LSS_". - * Do not access these symbols from outside this file. They are not part - * of the supported API. - */ -#ifndef SYS_LINUX_SYSCALL_SUPPORT_H -#define SYS_LINUX_SYSCALL_SUPPORT_H - -/* We currently only support x86-32, x86-64, ARM, MIPS, and PPC on Linux. - * Porting to other related platforms should not be difficult. - */ -#if (defined(__i386__) || defined(__x86_64__) || defined(__ARM_ARCH_3__) || \ - defined(__mips__) || defined(__PPC__) || defined(__ARM_EABI__) || \ - defined(__aarch64__)) \ - && (defined(__linux) || defined(__ANDROID__)) - -#ifndef SYS_CPLUSPLUS -#ifdef __cplusplus -/* Some system header files in older versions of gcc neglect to properly - * handle being included from C++. As it appears to be harmless to have - * multiple nested 'extern "C"' blocks, just add another one here. - */ -extern "C" { -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __mips__ -/* Include definitions of the ABI currently in use. */ -#include -#endif -#endif - -/* The Android NDK's #defines these macros as aliases - * to their non-64 counterparts. To avoid naming conflict, remove them. */ -#ifdef __ANDROID__ - /* These are restored by the corresponding #pragma pop_macro near - * the end of this file. */ -# pragma push_macro("stat64") -# pragma push_macro("fstat64") -# pragma push_macro("lstat64") -# undef stat64 -# undef fstat64 -# undef lstat64 -#endif - -/* As glibc often provides subtly incompatible data structures (and implicit - * wrapper functions that convert them), we provide our own kernel data - * structures for use by the system calls. - * These structures have been developed by using Linux 2.6.23 headers for - * reference. Note though, we do not care about exact API compatibility - * with the kernel, and in fact the kernel often does not have a single - * API that works across architectures. Instead, we try to mimic the glibc - * API where reasonable, and only guarantee ABI compatibility with the - * kernel headers. - * Most notably, here are a few changes that were made to the structures - * defined by kernel headers: - * - * - we only define structures, but not symbolic names for kernel data - * types. For the latter, we directly use the native C datatype - * (i.e. "unsigned" instead of "mode_t"). - * - in a few cases, it is possible to define identical structures for - * both 32bit (e.g. i386) and 64bit (e.g. x86-64) platforms by - * standardizing on the 64bit version of the data types. In particular, - * this means that we use "unsigned" where the 32bit headers say - * "unsigned long". - * - overall, we try to minimize the number of cases where we need to - * conditionally define different structures. - * - the "struct kernel_sigaction" class of structures have been - * modified to more closely mimic glibc's API by introducing an - * anonymous union for the function pointer. - * - a small number of field names had to have an underscore appended to - * them, because glibc defines a global macro by the same name. - */ - -/* include/linux/dirent.h */ -struct kernel_dirent64 { - unsigned long long d_ino; - long long d_off; - unsigned short d_reclen; - unsigned char d_type; - char d_name[256]; -}; - -/* include/linux/dirent.h */ -#if defined(__aarch64__) -// aarch64 only defines dirent64, just uses that for dirent too. -#define kernel_dirent kernel_dirent64 -#else -struct kernel_dirent { - long d_ino; - long d_off; - unsigned short d_reclen; - char d_name[256]; -}; -#endif - -/* include/linux/uio.h */ -struct kernel_iovec { - void *iov_base; - unsigned long iov_len; -}; - -/* include/linux/socket.h */ -struct kernel_msghdr { - void *msg_name; - int msg_namelen; - struct kernel_iovec*msg_iov; - unsigned long msg_iovlen; - void *msg_control; - unsigned long msg_controllen; - unsigned msg_flags; -}; - -/* include/asm-generic/poll.h */ -struct kernel_pollfd { - int fd; - short events; - short revents; -}; - -/* include/linux/resource.h */ -struct kernel_rlimit { - unsigned long rlim_cur; - unsigned long rlim_max; -}; - -/* include/linux/time.h */ -struct kernel_timespec { - long tv_sec; - long tv_nsec; -}; - -/* include/linux/time.h */ -struct kernel_timeval { - long tv_sec; - long tv_usec; -}; - -/* include/linux/resource.h */ -struct kernel_rusage { - struct kernel_timeval ru_utime; - struct kernel_timeval ru_stime; - long ru_maxrss; - long ru_ixrss; - long ru_idrss; - long ru_isrss; - long ru_minflt; - long ru_majflt; - long ru_nswap; - long ru_inblock; - long ru_oublock; - long ru_msgsnd; - long ru_msgrcv; - long ru_nsignals; - long ru_nvcsw; - long ru_nivcsw; -}; - -#if defined(__i386__) || defined(__ARM_EABI__) || defined(__ARM_ARCH_3__) \ - || defined(__PPC__) - -/* include/asm-{arm,i386,mips,ppc}/signal.h */ -struct kernel_old_sigaction { - union { - void (*sa_handler_)(int); - void (*sa_sigaction_)(int, siginfo_t *, void *); - }; - unsigned long sa_mask; - unsigned long sa_flags; - void (*sa_restorer)(void); -} __attribute__((packed,aligned(4))); -#elif (defined(__mips__) && _MIPS_SIM == _MIPS_SIM_ABI32) - #define kernel_old_sigaction kernel_sigaction -#elif defined(__aarch64__) - // No kernel_old_sigaction defined for arm64. -#endif - -/* Some kernel functions (e.g. sigaction() in 2.6.23) require that the - * exactly match the size of the signal set, even though the API was - * intended to be extensible. We define our own KERNEL_NSIG to deal with - * this. - * Please note that glibc provides signals [1.._NSIG-1], whereas the - * kernel (and this header) provides the range [1..KERNEL_NSIG]. The - * actual number of signals is obviously the same, but the constants - * differ by one. - */ -#ifdef __mips__ -#define KERNEL_NSIG 128 -#else -#define KERNEL_NSIG 64 -#endif - -/* include/asm-{arm,aarch64,i386,mips,x86_64}/signal.h */ -struct kernel_sigset_t { - unsigned long sig[(KERNEL_NSIG + 8*sizeof(unsigned long) - 1)/ - (8*sizeof(unsigned long))]; -}; - -/* include/asm-{arm,i386,mips,x86_64,ppc}/signal.h */ -struct kernel_sigaction { -#ifdef __mips__ - unsigned long sa_flags; - union { - void (*sa_handler_)(int); - void (*sa_sigaction_)(int, siginfo_t *, void *); - }; - struct kernel_sigset_t sa_mask; -#else - union { - void (*sa_handler_)(int); - void (*sa_sigaction_)(int, siginfo_t *, void *); - }; - unsigned long sa_flags; - void (*sa_restorer)(void); - struct kernel_sigset_t sa_mask; -#endif -}; - -/* include/linux/socket.h */ -struct kernel_sockaddr { - unsigned short sa_family; - char sa_data[14]; -}; - -/* include/asm-{arm,aarch64,i386,mips,ppc}/stat.h */ -#ifdef __mips__ -#if _MIPS_SIM == _MIPS_SIM_ABI64 -struct kernel_stat { -#else -struct kernel_stat64 { -#endif - unsigned st_dev; - unsigned __pad0[3]; - unsigned long long st_ino; - unsigned st_mode; - unsigned st_nlink; - unsigned st_uid; - unsigned st_gid; - unsigned st_rdev; - unsigned __pad1[3]; - long long st_size; - unsigned st_atime_; - unsigned st_atime_nsec_; - unsigned st_mtime_; - unsigned st_mtime_nsec_; - unsigned st_ctime_; - unsigned st_ctime_nsec_; - unsigned st_blksize; - unsigned __pad2; - unsigned long long st_blocks; -}; -#elif defined __PPC__ -struct kernel_stat64 { - unsigned long long st_dev; - unsigned long long st_ino; - unsigned st_mode; - unsigned st_nlink; - unsigned st_uid; - unsigned st_gid; - unsigned long long st_rdev; - unsigned short int __pad2; - long long st_size; - long st_blksize; - long long st_blocks; - long st_atime_; - unsigned long st_atime_nsec_; - long st_mtime_; - unsigned long st_mtime_nsec_; - long st_ctime_; - unsigned long st_ctime_nsec_; - unsigned long __unused4; - unsigned long __unused5; -}; -#else -struct kernel_stat64 { - unsigned long long st_dev; - unsigned char __pad0[4]; - unsigned __st_ino; - unsigned st_mode; - unsigned st_nlink; - unsigned st_uid; - unsigned st_gid; - unsigned long long st_rdev; - unsigned char __pad3[4]; - long long st_size; - unsigned st_blksize; - unsigned long long st_blocks; - unsigned st_atime_; - unsigned st_atime_nsec_; - unsigned st_mtime_; - unsigned st_mtime_nsec_; - unsigned st_ctime_; - unsigned st_ctime_nsec_; - unsigned long long st_ino; -}; -#endif - -/* include/asm-{arm,aarch64,i386,mips,x86_64,ppc}/stat.h */ -#if defined(__i386__) || defined(__ARM_ARCH_3__) || defined(__ARM_EABI__) -struct kernel_stat { - /* The kernel headers suggest that st_dev and st_rdev should be 32bit - * quantities encoding 12bit major and 20bit minor numbers in an interleaved - * format. In reality, we do not see useful data in the top bits. So, - * we'll leave the padding in here, until we find a better solution. - */ - unsigned short st_dev; - short pad1; - unsigned st_ino; - unsigned short st_mode; - unsigned short st_nlink; - unsigned short st_uid; - unsigned short st_gid; - unsigned short st_rdev; - short pad2; - unsigned st_size; - unsigned st_blksize; - unsigned st_blocks; - unsigned st_atime_; - unsigned st_atime_nsec_; - unsigned st_mtime_; - unsigned st_mtime_nsec_; - unsigned st_ctime_; - unsigned st_ctime_nsec_; - unsigned __unused4; - unsigned __unused5; -}; -#elif defined(__x86_64__) -struct kernel_stat { - uint64_t st_dev; - uint64_t st_ino; - uint64_t st_nlink; - unsigned st_mode; - unsigned st_uid; - unsigned st_gid; - unsigned __pad0; - uint64_t st_rdev; - int64_t st_size; - int64_t st_blksize; - int64_t st_blocks; - uint64_t st_atime_; - uint64_t st_atime_nsec_; - uint64_t st_mtime_; - uint64_t st_mtime_nsec_; - uint64_t st_ctime_; - uint64_t st_ctime_nsec_; - int64_t __unused4[3]; -}; -#elif defined(__PPC__) -struct kernel_stat { - unsigned st_dev; - unsigned long st_ino; // ino_t - unsigned long st_mode; // mode_t - unsigned short st_nlink; // nlink_t - unsigned st_uid; // uid_t - unsigned st_gid; // gid_t - unsigned st_rdev; - long st_size; // off_t - unsigned long st_blksize; - unsigned long st_blocks; - unsigned long st_atime_; - unsigned long st_atime_nsec_; - unsigned long st_mtime_; - unsigned long st_mtime_nsec_; - unsigned long st_ctime_; - unsigned long st_ctime_nsec_; - unsigned long __unused4; - unsigned long __unused5; -}; -#elif (defined(__mips__) && _MIPS_SIM != _MIPS_SIM_ABI64) -struct kernel_stat { - unsigned st_dev; - int st_pad1[3]; - unsigned st_ino; - unsigned st_mode; - unsigned st_nlink; - unsigned st_uid; - unsigned st_gid; - unsigned st_rdev; - int st_pad2[2]; - long st_size; - int st_pad3; - long st_atime_; - long st_atime_nsec_; - long st_mtime_; - long st_mtime_nsec_; - long st_ctime_; - long st_ctime_nsec_; - int st_blksize; - int st_blocks; - int st_pad4[14]; -}; -#elif defined(__aarch64__) -struct kernel_stat { - unsigned long st_dev; - unsigned long st_ino; - unsigned int st_mode; - unsigned int st_nlink; - unsigned int st_uid; - unsigned int st_gid; - unsigned long st_rdev; - unsigned long __pad1; - long st_size; - int st_blksize; - int __pad2; - long st_blocks; - long st_atime_; - unsigned long st_atime_nsec_; - long st_mtime_; - unsigned long st_mtime_nsec_; - long st_ctime_; - unsigned long st_ctime_nsec_; - unsigned int __unused4; - unsigned int __unused5; -}; -#endif - -/* include/asm-{arm,aarch64,i386,mips,x86_64,ppc}/statfs.h */ -#ifdef __mips__ -#if _MIPS_SIM != _MIPS_SIM_ABI64 -struct kernel_statfs64 { - unsigned long f_type; - unsigned long f_bsize; - unsigned long f_frsize; - unsigned long __pad; - unsigned long long f_blocks; - unsigned long long f_bfree; - unsigned long long f_files; - unsigned long long f_ffree; - unsigned long long f_bavail; - struct { int val[2]; } f_fsid; - unsigned long f_namelen; - unsigned long f_spare[6]; -}; -#endif -#elif !defined(__x86_64__) -struct kernel_statfs64 { - unsigned long f_type; - unsigned long f_bsize; - unsigned long long f_blocks; - unsigned long long f_bfree; - unsigned long long f_bavail; - unsigned long long f_files; - unsigned long long f_ffree; - struct { int val[2]; } f_fsid; - unsigned long f_namelen; - unsigned long f_frsize; - unsigned long f_spare[5]; -}; -#endif - -/* include/asm-{arm,i386,mips,x86_64,ppc,generic}/statfs.h */ -#ifdef __mips__ -struct kernel_statfs { - long f_type; - long f_bsize; - long f_frsize; - long f_blocks; - long f_bfree; - long f_files; - long f_ffree; - long f_bavail; - struct { int val[2]; } f_fsid; - long f_namelen; - long f_spare[6]; -}; -#elif defined(__x86_64__) -struct kernel_statfs { - /* x86_64 actually defines all these fields as signed, whereas all other */ - /* platforms define them as unsigned. Leaving them at unsigned should not */ - /* cause any problems. Make sure these are 64-bit even on x32. */ - uint64_t f_type; - uint64_t f_bsize; - uint64_t f_blocks; - uint64_t f_bfree; - uint64_t f_bavail; - uint64_t f_files; - uint64_t f_ffree; - struct { int val[2]; } f_fsid; - uint64_t f_namelen; - uint64_t f_frsize; - uint64_t f_spare[5]; -}; -#else -struct kernel_statfs { - unsigned long f_type; - unsigned long f_bsize; - unsigned long f_blocks; - unsigned long f_bfree; - unsigned long f_bavail; - unsigned long f_files; - unsigned long f_ffree; - struct { int val[2]; } f_fsid; - unsigned long f_namelen; - unsigned long f_frsize; - unsigned long f_spare[5]; -}; -#endif - - -/* Definitions missing from the standard header files */ -#ifndef O_DIRECTORY -#if defined(__ARM_ARCH_3__) || defined(__ARM_EABI__) || defined(__aarch64__) -#define O_DIRECTORY 0040000 -#else -#define O_DIRECTORY 0200000 -#endif -#endif -#ifndef NT_PRXFPREG -#define NT_PRXFPREG 0x46e62b7f -#endif -#ifndef PTRACE_GETFPXREGS -#define PTRACE_GETFPXREGS ((enum __ptrace_request)18) -#endif -#ifndef PR_GET_DUMPABLE -#define PR_GET_DUMPABLE 3 -#endif -#ifndef PR_SET_DUMPABLE -#define PR_SET_DUMPABLE 4 -#endif -#ifndef PR_GET_SECCOMP -#define PR_GET_SECCOMP 21 -#endif -#ifndef PR_SET_SECCOMP -#define PR_SET_SECCOMP 22 -#endif -#ifndef AT_FDCWD -#define AT_FDCWD (-100) -#endif -#ifndef AT_SYMLINK_NOFOLLOW -#define AT_SYMLINK_NOFOLLOW 0x100 -#endif -#ifndef AT_REMOVEDIR -#define AT_REMOVEDIR 0x200 -#endif -#ifndef MREMAP_FIXED -#define MREMAP_FIXED 2 -#endif -#ifndef SA_RESTORER -#define SA_RESTORER 0x04000000 -#endif -#ifndef CPUCLOCK_PROF -#define CPUCLOCK_PROF 0 -#endif -#ifndef CPUCLOCK_VIRT -#define CPUCLOCK_VIRT 1 -#endif -#ifndef CPUCLOCK_SCHED -#define CPUCLOCK_SCHED 2 -#endif -#ifndef CPUCLOCK_PERTHREAD_MASK -#define CPUCLOCK_PERTHREAD_MASK 4 -#endif -#ifndef MAKE_PROCESS_CPUCLOCK -#define MAKE_PROCESS_CPUCLOCK(pid, clock) \ - ((~(int)(pid) << 3) | (int)(clock)) -#endif -#ifndef MAKE_THREAD_CPUCLOCK -#define MAKE_THREAD_CPUCLOCK(tid, clock) \ - ((~(int)(tid) << 3) | (int)((clock) | CPUCLOCK_PERTHREAD_MASK)) -#endif - -#ifndef FUTEX_WAIT -#define FUTEX_WAIT 0 -#endif -#ifndef FUTEX_WAKE -#define FUTEX_WAKE 1 -#endif -#ifndef FUTEX_FD -#define FUTEX_FD 2 -#endif -#ifndef FUTEX_REQUEUE -#define FUTEX_REQUEUE 3 -#endif -#ifndef FUTEX_CMP_REQUEUE -#define FUTEX_CMP_REQUEUE 4 -#endif -#ifndef FUTEX_WAKE_OP -#define FUTEX_WAKE_OP 5 -#endif -#ifndef FUTEX_LOCK_PI -#define FUTEX_LOCK_PI 6 -#endif -#ifndef FUTEX_UNLOCK_PI -#define FUTEX_UNLOCK_PI 7 -#endif -#ifndef FUTEX_TRYLOCK_PI -#define FUTEX_TRYLOCK_PI 8 -#endif -#ifndef FUTEX_PRIVATE_FLAG -#define FUTEX_PRIVATE_FLAG 128 -#endif -#ifndef FUTEX_CMD_MASK -#define FUTEX_CMD_MASK ~FUTEX_PRIVATE_FLAG -#endif -#ifndef FUTEX_WAIT_PRIVATE -#define FUTEX_WAIT_PRIVATE (FUTEX_WAIT | FUTEX_PRIVATE_FLAG) -#endif -#ifndef FUTEX_WAKE_PRIVATE -#define FUTEX_WAKE_PRIVATE (FUTEX_WAKE | FUTEX_PRIVATE_FLAG) -#endif -#ifndef FUTEX_REQUEUE_PRIVATE -#define FUTEX_REQUEUE_PRIVATE (FUTEX_REQUEUE | FUTEX_PRIVATE_FLAG) -#endif -#ifndef FUTEX_CMP_REQUEUE_PRIVATE -#define FUTEX_CMP_REQUEUE_PRIVATE (FUTEX_CMP_REQUEUE | FUTEX_PRIVATE_FLAG) -#endif -#ifndef FUTEX_WAKE_OP_PRIVATE -#define FUTEX_WAKE_OP_PRIVATE (FUTEX_WAKE_OP | FUTEX_PRIVATE_FLAG) -#endif -#ifndef FUTEX_LOCK_PI_PRIVATE -#define FUTEX_LOCK_PI_PRIVATE (FUTEX_LOCK_PI | FUTEX_PRIVATE_FLAG) -#endif -#ifndef FUTEX_UNLOCK_PI_PRIVATE -#define FUTEX_UNLOCK_PI_PRIVATE (FUTEX_UNLOCK_PI | FUTEX_PRIVATE_FLAG) -#endif -#ifndef FUTEX_TRYLOCK_PI_PRIVATE -#define FUTEX_TRYLOCK_PI_PRIVATE (FUTEX_TRYLOCK_PI | FUTEX_PRIVATE_FLAG) -#endif - - -#if defined(__x86_64__) -#ifndef ARCH_SET_GS -#define ARCH_SET_GS 0x1001 -#endif -#ifndef ARCH_GET_GS -#define ARCH_GET_GS 0x1004 -#endif -#endif - -#if defined(__i386__) -#ifndef __NR_quotactl -#define __NR_quotactl 131 -#endif -#ifndef __NR_setresuid -#define __NR_setresuid 164 -#define __NR_getresuid 165 -#define __NR_setresgid 170 -#define __NR_getresgid 171 -#endif -#ifndef __NR_rt_sigaction -#define __NR_rt_sigreturn 173 -#define __NR_rt_sigaction 174 -#define __NR_rt_sigprocmask 175 -#define __NR_rt_sigpending 176 -#define __NR_rt_sigsuspend 179 -#endif -#ifndef __NR_pread64 -#define __NR_pread64 180 -#endif -#ifndef __NR_pwrite64 -#define __NR_pwrite64 181 -#endif -#ifndef __NR_ugetrlimit -#define __NR_ugetrlimit 191 -#endif -#ifndef __NR_stat64 -#define __NR_stat64 195 -#endif -#ifndef __NR_fstat64 -#define __NR_fstat64 197 -#endif -#ifndef __NR_setresuid32 -#define __NR_setresuid32 208 -#define __NR_getresuid32 209 -#define __NR_setresgid32 210 -#define __NR_getresgid32 211 -#endif -#ifndef __NR_setfsuid32 -#define __NR_setfsuid32 215 -#define __NR_setfsgid32 216 -#endif -#ifndef __NR_getdents64 -#define __NR_getdents64 220 -#endif -#ifndef __NR_gettid -#define __NR_gettid 224 -#endif -#ifndef __NR_readahead -#define __NR_readahead 225 -#endif -#ifndef __NR_setxattr -#define __NR_setxattr 226 -#endif -#ifndef __NR_lsetxattr -#define __NR_lsetxattr 227 -#endif -#ifndef __NR_getxattr -#define __NR_getxattr 229 -#endif -#ifndef __NR_lgetxattr -#define __NR_lgetxattr 230 -#endif -#ifndef __NR_listxattr -#define __NR_listxattr 232 -#endif -#ifndef __NR_llistxattr -#define __NR_llistxattr 233 -#endif -#ifndef __NR_tkill -#define __NR_tkill 238 -#endif -#ifndef __NR_futex -#define __NR_futex 240 -#endif -#ifndef __NR_sched_setaffinity -#define __NR_sched_setaffinity 241 -#define __NR_sched_getaffinity 242 -#endif -#ifndef __NR_set_tid_address -#define __NR_set_tid_address 258 -#endif -#ifndef __NR_clock_gettime -#define __NR_clock_gettime 265 -#endif -#ifndef __NR_clock_getres -#define __NR_clock_getres 266 -#endif -#ifndef __NR_statfs64 -#define __NR_statfs64 268 -#endif -#ifndef __NR_fstatfs64 -#define __NR_fstatfs64 269 -#endif -#ifndef __NR_fadvise64_64 -#define __NR_fadvise64_64 272 -#endif -#ifndef __NR_ioprio_set -#define __NR_ioprio_set 289 -#endif -#ifndef __NR_ioprio_get -#define __NR_ioprio_get 290 -#endif -#ifndef __NR_openat -#define __NR_openat 295 -#endif -#ifndef __NR_fstatat64 -#define __NR_fstatat64 300 -#endif -#ifndef __NR_unlinkat -#define __NR_unlinkat 301 -#endif -#ifndef __NR_move_pages -#define __NR_move_pages 317 -#endif -#ifndef __NR_getcpu -#define __NR_getcpu 318 -#endif -#ifndef __NR_fallocate -#define __NR_fallocate 324 -#endif -/* End of i386 definitions */ -#elif defined(__ARM_ARCH_3__) || defined(__ARM_EABI__) -#ifndef __NR_setresuid -#define __NR_setresuid (__NR_SYSCALL_BASE + 164) -#define __NR_getresuid (__NR_SYSCALL_BASE + 165) -#define __NR_setresgid (__NR_SYSCALL_BASE + 170) -#define __NR_getresgid (__NR_SYSCALL_BASE + 171) -#endif -#ifndef __NR_rt_sigaction -#define __NR_rt_sigreturn (__NR_SYSCALL_BASE + 173) -#define __NR_rt_sigaction (__NR_SYSCALL_BASE + 174) -#define __NR_rt_sigprocmask (__NR_SYSCALL_BASE + 175) -#define __NR_rt_sigpending (__NR_SYSCALL_BASE + 176) -#define __NR_rt_sigsuspend (__NR_SYSCALL_BASE + 179) -#endif -#ifndef __NR_pread64 -#define __NR_pread64 (__NR_SYSCALL_BASE + 180) -#endif -#ifndef __NR_pwrite64 -#define __NR_pwrite64 (__NR_SYSCALL_BASE + 181) -#endif -#ifndef __NR_ugetrlimit -#define __NR_ugetrlimit (__NR_SYSCALL_BASE + 191) -#endif -#ifndef __NR_stat64 -#define __NR_stat64 (__NR_SYSCALL_BASE + 195) -#endif -#ifndef __NR_fstat64 -#define __NR_fstat64 (__NR_SYSCALL_BASE + 197) -#endif -#ifndef __NR_setresuid32 -#define __NR_setresuid32 (__NR_SYSCALL_BASE + 208) -#define __NR_getresuid32 (__NR_SYSCALL_BASE + 209) -#define __NR_setresgid32 (__NR_SYSCALL_BASE + 210) -#define __NR_getresgid32 (__NR_SYSCALL_BASE + 211) -#endif -#ifndef __NR_setfsuid32 -#define __NR_setfsuid32 (__NR_SYSCALL_BASE + 215) -#define __NR_setfsgid32 (__NR_SYSCALL_BASE + 216) -#endif -#ifndef __NR_getdents64 -#define __NR_getdents64 (__NR_SYSCALL_BASE + 217) -#endif -#ifndef __NR_gettid -#define __NR_gettid (__NR_SYSCALL_BASE + 224) -#endif -#ifndef __NR_readahead -#define __NR_readahead (__NR_SYSCALL_BASE + 225) -#endif -#ifndef __NR_setxattr -#define __NR_setxattr (__NR_SYSCALL_BASE + 226) -#endif -#ifndef __NR_lsetxattr -#define __NR_lsetxattr (__NR_SYSCALL_BASE + 227) -#endif -#ifndef __NR_getxattr -#define __NR_getxattr (__NR_SYSCALL_BASE + 229) -#endif -#ifndef __NR_lgetxattr -#define __NR_lgetxattr (__NR_SYSCALL_BASE + 230) -#endif -#ifndef __NR_listxattr -#define __NR_listxattr (__NR_SYSCALL_BASE + 232) -#endif -#ifndef __NR_llistxattr -#define __NR_llistxattr (__NR_SYSCALL_BASE + 233) -#endif -#ifndef __NR_tkill -#define __NR_tkill (__NR_SYSCALL_BASE + 238) -#endif -#ifndef __NR_futex -#define __NR_futex (__NR_SYSCALL_BASE + 240) -#endif -#ifndef __NR_sched_setaffinity -#define __NR_sched_setaffinity (__NR_SYSCALL_BASE + 241) -#define __NR_sched_getaffinity (__NR_SYSCALL_BASE + 242) -#endif -#ifndef __NR_set_tid_address -#define __NR_set_tid_address (__NR_SYSCALL_BASE + 256) -#endif -#ifndef __NR_clock_gettime -#define __NR_clock_gettime (__NR_SYSCALL_BASE + 263) -#endif -#ifndef __NR_clock_getres -#define __NR_clock_getres (__NR_SYSCALL_BASE + 264) -#endif -#ifndef __NR_statfs64 -#define __NR_statfs64 (__NR_SYSCALL_BASE + 266) -#endif -#ifndef __NR_fstatfs64 -#define __NR_fstatfs64 (__NR_SYSCALL_BASE + 267) -#endif -#ifndef __NR_ioprio_set -#define __NR_ioprio_set (__NR_SYSCALL_BASE + 314) -#endif -#ifndef __NR_ioprio_get -#define __NR_ioprio_get (__NR_SYSCALL_BASE + 315) -#endif -#ifndef __NR_move_pages -#define __NR_move_pages (__NR_SYSCALL_BASE + 344) -#endif -#ifndef __NR_getcpu -#define __NR_getcpu (__NR_SYSCALL_BASE + 345) -#endif -/* End of ARM 3/EABI definitions */ -#elif defined(__aarch64__) -#ifndef __NR_setxattr -#define __NR_setxattr 5 -#endif -#ifndef __NR_lsetxattr -#define __NR_lsetxattr 6 -#endif -#ifndef __NR_getxattr -#define __NR_getxattr 8 -#endif -#ifndef __NR_lgetxattr -#define __NR_lgetxattr 9 -#endif -#ifndef __NR_listxattr -#define __NR_listxattr 11 -#endif -#ifndef __NR_llistxattr -#define __NR_llistxattr 12 -#endif -#ifndef __NR_ioprio_set -#define __NR_ioprio_set 30 -#endif -#ifndef __NR_ioprio_get -#define __NR_ioprio_get 31 -#endif -#ifndef __NR_unlinkat -#define __NR_unlinkat 35 -#endif -#ifndef __NR_fallocate -#define __NR_fallocate 47 -#endif -#ifndef __NR_openat -#define __NR_openat 56 -#endif -#ifndef __NR_quotactl -#define __NR_quotactl 60 -#endif -#ifndef __NR_getdents64 -#define __NR_getdents64 61 -#endif -#ifndef __NR_getdents -#define __NR_getdents __NR_getdents64 -#endif -#ifndef __NR_pread64 -#define __NR_pread64 67 -#endif -#ifndef __NR_pwrite64 -#define __NR_pwrite64 68 -#endif -#ifndef __NR_ppoll -#define __NR_ppoll 73 -#endif -#ifndef __NR_readlinkat -#define __NR_readlinkat 78 -#endif -#ifndef __NR_newfstatat -#define __NR_newfstatat 79 -#endif -#ifndef __NR_set_tid_address -#define __NR_set_tid_address 96 -#endif -#ifndef __NR_futex -#define __NR_futex 98 -#endif -#ifndef __NR_clock_gettime -#define __NR_clock_gettime 113 -#endif -#ifndef __NR_clock_getres -#define __NR_clock_getres 114 -#endif -#ifndef __NR_sched_setaffinity -#define __NR_sched_setaffinity 122 -#define __NR_sched_getaffinity 123 -#endif -#ifndef __NR_tkill -#define __NR_tkill 130 -#endif -#ifndef __NR_setresuid -#define __NR_setresuid 147 -#define __NR_getresuid 148 -#define __NR_setresgid 149 -#define __NR_getresgid 150 -#endif -#ifndef __NR_gettid -#define __NR_gettid 178 -#endif -#ifndef __NR_readahead -#define __NR_readahead 213 -#endif -#ifndef __NR_fadvise64 -#define __NR_fadvise64 223 -#endif -#ifndef __NR_move_pages -#define __NR_move_pages 239 -#endif -/* End of aarch64 definitions */ -#elif defined(__x86_64__) -#ifndef __NR_pread64 -#define __NR_pread64 17 -#endif -#ifndef __NR_pwrite64 -#define __NR_pwrite64 18 -#endif -#ifndef __NR_setresuid -#define __NR_setresuid 117 -#define __NR_getresuid 118 -#define __NR_setresgid 119 -#define __NR_getresgid 120 -#endif -#ifndef __NR_quotactl -#define __NR_quotactl 179 -#endif -#ifndef __NR_gettid -#define __NR_gettid 186 -#endif -#ifndef __NR_readahead -#define __NR_readahead 187 -#endif -#ifndef __NR_setxattr -#define __NR_setxattr 188 -#endif -#ifndef __NR_lsetxattr -#define __NR_lsetxattr 189 -#endif -#ifndef __NR_getxattr -#define __NR_getxattr 191 -#endif -#ifndef __NR_lgetxattr -#define __NR_lgetxattr 192 -#endif -#ifndef __NR_listxattr -#define __NR_listxattr 194 -#endif -#ifndef __NR_llistxattr -#define __NR_llistxattr 195 -#endif -#ifndef __NR_tkill -#define __NR_tkill 200 -#endif -#ifndef __NR_futex -#define __NR_futex 202 -#endif -#ifndef __NR_sched_setaffinity -#define __NR_sched_setaffinity 203 -#define __NR_sched_getaffinity 204 -#endif -#ifndef __NR_getdents64 -#define __NR_getdents64 217 -#endif -#ifndef __NR_set_tid_address -#define __NR_set_tid_address 218 -#endif -#ifndef __NR_fadvise64 -#define __NR_fadvise64 221 -#endif -#ifndef __NR_clock_gettime -#define __NR_clock_gettime 228 -#endif -#ifndef __NR_clock_getres -#define __NR_clock_getres 229 -#endif -#ifndef __NR_ioprio_set -#define __NR_ioprio_set 251 -#endif -#ifndef __NR_ioprio_get -#define __NR_ioprio_get 252 -#endif -#ifndef __NR_openat -#define __NR_openat 257 -#endif -#ifndef __NR_newfstatat -#define __NR_newfstatat 262 -#endif -#ifndef __NR_unlinkat -#define __NR_unlinkat 263 -#endif -#ifndef __NR_move_pages -#define __NR_move_pages 279 -#endif -#ifndef __NR_fallocate -#define __NR_fallocate 285 -#endif -/* End of x86-64 definitions */ -#elif defined(__mips__) -#if _MIPS_SIM == _MIPS_SIM_ABI32 -#ifndef __NR_setresuid -#define __NR_setresuid (__NR_Linux + 185) -#define __NR_getresuid (__NR_Linux + 186) -#define __NR_setresgid (__NR_Linux + 190) -#define __NR_getresgid (__NR_Linux + 191) -#endif -#ifndef __NR_rt_sigaction -#define __NR_rt_sigreturn (__NR_Linux + 193) -#define __NR_rt_sigaction (__NR_Linux + 194) -#define __NR_rt_sigprocmask (__NR_Linux + 195) -#define __NR_rt_sigpending (__NR_Linux + 196) -#define __NR_rt_sigsuspend (__NR_Linux + 199) -#endif -#ifndef __NR_pread64 -#define __NR_pread64 (__NR_Linux + 200) -#endif -#ifndef __NR_pwrite64 -#define __NR_pwrite64 (__NR_Linux + 201) -#endif -#ifndef __NR_stat64 -#define __NR_stat64 (__NR_Linux + 213) -#endif -#ifndef __NR_fstat64 -#define __NR_fstat64 (__NR_Linux + 215) -#endif -#ifndef __NR_getdents64 -#define __NR_getdents64 (__NR_Linux + 219) -#endif -#ifndef __NR_gettid -#define __NR_gettid (__NR_Linux + 222) -#endif -#ifndef __NR_readahead -#define __NR_readahead (__NR_Linux + 223) -#endif -#ifndef __NR_setxattr -#define __NR_setxattr (__NR_Linux + 224) -#endif -#ifndef __NR_lsetxattr -#define __NR_lsetxattr (__NR_Linux + 225) -#endif -#ifndef __NR_getxattr -#define __NR_getxattr (__NR_Linux + 227) -#endif -#ifndef __NR_lgetxattr -#define __NR_lgetxattr (__NR_Linux + 228) -#endif -#ifndef __NR_listxattr -#define __NR_listxattr (__NR_Linux + 230) -#endif -#ifndef __NR_llistxattr -#define __NR_llistxattr (__NR_Linux + 231) -#endif -#ifndef __NR_tkill -#define __NR_tkill (__NR_Linux + 236) -#endif -#ifndef __NR_futex -#define __NR_futex (__NR_Linux + 238) -#endif -#ifndef __NR_sched_setaffinity -#define __NR_sched_setaffinity (__NR_Linux + 239) -#define __NR_sched_getaffinity (__NR_Linux + 240) -#endif -#ifndef __NR_set_tid_address -#define __NR_set_tid_address (__NR_Linux + 252) -#endif -#ifndef __NR_statfs64 -#define __NR_statfs64 (__NR_Linux + 255) -#endif -#ifndef __NR_fstatfs64 -#define __NR_fstatfs64 (__NR_Linux + 256) -#endif -#ifndef __NR_clock_gettime -#define __NR_clock_gettime (__NR_Linux + 263) -#endif -#ifndef __NR_clock_getres -#define __NR_clock_getres (__NR_Linux + 264) -#endif -#ifndef __NR_openat -#define __NR_openat (__NR_Linux + 288) -#endif -#ifndef __NR_fstatat -#define __NR_fstatat (__NR_Linux + 293) -#endif -#ifndef __NR_unlinkat -#define __NR_unlinkat (__NR_Linux + 294) -#endif -#ifndef __NR_move_pages -#define __NR_move_pages (__NR_Linux + 308) -#endif -#ifndef __NR_getcpu -#define __NR_getcpu (__NR_Linux + 312) -#endif -#ifndef __NR_ioprio_set -#define __NR_ioprio_set (__NR_Linux + 314) -#endif -#ifndef __NR_ioprio_get -#define __NR_ioprio_get (__NR_Linux + 315) -#endif -/* End of MIPS (old 32bit API) definitions */ -#elif _MIPS_SIM == _MIPS_SIM_ABI64 -#ifndef __NR_pread64 -#define __NR_pread64 (__NR_Linux + 16) -#endif -#ifndef __NR_pwrite64 -#define __NR_pwrite64 (__NR_Linux + 17) -#endif -#ifndef __NR_setresuid -#define __NR_setresuid (__NR_Linux + 115) -#define __NR_getresuid (__NR_Linux + 116) -#define __NR_setresgid (__NR_Linux + 117) -#define __NR_getresgid (__NR_Linux + 118) -#endif -#ifndef __NR_gettid -#define __NR_gettid (__NR_Linux + 178) -#endif -#ifndef __NR_readahead -#define __NR_readahead (__NR_Linux + 179) -#endif -#ifndef __NR_setxattr -#define __NR_setxattr (__NR_Linux + 180) -#endif -#ifndef __NR_lsetxattr -#define __NR_lsetxattr (__NR_Linux + 181) -#endif -#ifndef __NR_getxattr -#define __NR_getxattr (__NR_Linux + 183) -#endif -#ifndef __NR_lgetxattr -#define __NR_lgetxattr (__NR_Linux + 184) -#endif -#ifndef __NR_listxattr -#define __NR_listxattr (__NR_Linux + 186) -#endif -#ifndef __NR_llistxattr -#define __NR_llistxattr (__NR_Linux + 187) -#endif -#ifndef __NR_tkill -#define __NR_tkill (__NR_Linux + 192) -#endif -#ifndef __NR_futex -#define __NR_futex (__NR_Linux + 194) -#endif -#ifndef __NR_sched_setaffinity -#define __NR_sched_setaffinity (__NR_Linux + 195) -#define __NR_sched_getaffinity (__NR_Linux + 196) -#endif -#ifndef __NR_set_tid_address -#define __NR_set_tid_address (__NR_Linux + 212) -#endif -#ifndef __NR_clock_gettime -#define __NR_clock_gettime (__NR_Linux + 222) -#endif -#ifndef __NR_clock_getres -#define __NR_clock_getres (__NR_Linux + 223) -#endif -#ifndef __NR_openat -#define __NR_openat (__NR_Linux + 247) -#endif -#ifndef __NR_fstatat -#define __NR_fstatat (__NR_Linux + 252) -#endif -#ifndef __NR_unlinkat -#define __NR_unlinkat (__NR_Linux + 253) -#endif -#ifndef __NR_move_pages -#define __NR_move_pages (__NR_Linux + 267) -#endif -#ifndef __NR_getcpu -#define __NR_getcpu (__NR_Linux + 271) -#endif -#ifndef __NR_ioprio_set -#define __NR_ioprio_set (__NR_Linux + 273) -#endif -#ifndef __NR_ioprio_get -#define __NR_ioprio_get (__NR_Linux + 274) -#endif -/* End of MIPS (64bit API) definitions */ -#else -#ifndef __NR_setresuid -#define __NR_setresuid (__NR_Linux + 115) -#define __NR_getresuid (__NR_Linux + 116) -#define __NR_setresgid (__NR_Linux + 117) -#define __NR_getresgid (__NR_Linux + 118) -#endif -#ifndef __NR_gettid -#define __NR_gettid (__NR_Linux + 178) -#endif -#ifndef __NR_readahead -#define __NR_readahead (__NR_Linux + 179) -#endif -#ifndef __NR_setxattr -#define __NR_setxattr (__NR_Linux + 180) -#endif -#ifndef __NR_lsetxattr -#define __NR_lsetxattr (__NR_Linux + 181) -#endif -#ifndef __NR_getxattr -#define __NR_getxattr (__NR_Linux + 183) -#endif -#ifndef __NR_lgetxattr -#define __NR_lgetxattr (__NR_Linux + 184) -#endif -#ifndef __NR_listxattr -#define __NR_listxattr (__NR_Linux + 186) -#endif -#ifndef __NR_llistxattr -#define __NR_llistxattr (__NR_Linux + 187) -#endif -#ifndef __NR_tkill -#define __NR_tkill (__NR_Linux + 192) -#endif -#ifndef __NR_futex -#define __NR_futex (__NR_Linux + 194) -#endif -#ifndef __NR_sched_setaffinity -#define __NR_sched_setaffinity (__NR_Linux + 195) -#define __NR_sched_getaffinity (__NR_Linux + 196) -#endif -#ifndef __NR_set_tid_address -#define __NR_set_tid_address (__NR_Linux + 213) -#endif -#ifndef __NR_statfs64 -#define __NR_statfs64 (__NR_Linux + 217) -#endif -#ifndef __NR_fstatfs64 -#define __NR_fstatfs64 (__NR_Linux + 218) -#endif -#ifndef __NR_clock_gettime -#define __NR_clock_gettime (__NR_Linux + 226) -#endif -#ifndef __NR_clock_getres -#define __NR_clock_getres (__NR_Linux + 227) -#endif -#ifndef __NR_openat -#define __NR_openat (__NR_Linux + 251) -#endif -#ifndef __NR_fstatat -#define __NR_fstatat (__NR_Linux + 256) -#endif -#ifndef __NR_unlinkat -#define __NR_unlinkat (__NR_Linux + 257) -#endif -#ifndef __NR_move_pages -#define __NR_move_pages (__NR_Linux + 271) -#endif -#ifndef __NR_getcpu -#define __NR_getcpu (__NR_Linux + 275) -#endif -#ifndef __NR_ioprio_set -#define __NR_ioprio_set (__NR_Linux + 277) -#endif -#ifndef __NR_ioprio_get -#define __NR_ioprio_get (__NR_Linux + 278) -#endif -/* End of MIPS (new 32bit API) definitions */ -#endif -/* End of MIPS definitions */ -#elif defined(__PPC__) -#ifndef __NR_setfsuid -#define __NR_setfsuid 138 -#define __NR_setfsgid 139 -#endif -#ifndef __NR_setresuid -#define __NR_setresuid 164 -#define __NR_getresuid 165 -#define __NR_setresgid 169 -#define __NR_getresgid 170 -#endif -#ifndef __NR_rt_sigaction -#define __NR_rt_sigreturn 172 -#define __NR_rt_sigaction 173 -#define __NR_rt_sigprocmask 174 -#define __NR_rt_sigpending 175 -#define __NR_rt_sigsuspend 178 -#endif -#ifndef __NR_pread64 -#define __NR_pread64 179 -#endif -#ifndef __NR_pwrite64 -#define __NR_pwrite64 180 -#endif -#ifndef __NR_ugetrlimit -#define __NR_ugetrlimit 190 -#endif -#ifndef __NR_readahead -#define __NR_readahead 191 -#endif -#ifndef __NR_stat64 -#define __NR_stat64 195 -#endif -#ifndef __NR_fstat64 -#define __NR_fstat64 197 -#endif -#ifndef __NR_getdents64 -#define __NR_getdents64 202 -#endif -#ifndef __NR_gettid -#define __NR_gettid 207 -#endif -#ifndef __NR_tkill -#define __NR_tkill 208 -#endif -#ifndef __NR_setxattr -#define __NR_setxattr 209 -#endif -#ifndef __NR_lsetxattr -#define __NR_lsetxattr 210 -#endif -#ifndef __NR_getxattr -#define __NR_getxattr 212 -#endif -#ifndef __NR_lgetxattr -#define __NR_lgetxattr 213 -#endif -#ifndef __NR_listxattr -#define __NR_listxattr 215 -#endif -#ifndef __NR_llistxattr -#define __NR_llistxattr 216 -#endif -#ifndef __NR_futex -#define __NR_futex 221 -#endif -#ifndef __NR_sched_setaffinity -#define __NR_sched_setaffinity 222 -#define __NR_sched_getaffinity 223 -#endif -#ifndef __NR_set_tid_address -#define __NR_set_tid_address 232 -#endif -#ifndef __NR_clock_gettime -#define __NR_clock_gettime 246 -#endif -#ifndef __NR_clock_getres -#define __NR_clock_getres 247 -#endif -#ifndef __NR_statfs64 -#define __NR_statfs64 252 -#endif -#ifndef __NR_fstatfs64 -#define __NR_fstatfs64 253 -#endif -#ifndef __NR_fadvise64_64 -#define __NR_fadvise64_64 254 -#endif -#ifndef __NR_ioprio_set -#define __NR_ioprio_set 273 -#endif -#ifndef __NR_ioprio_get -#define __NR_ioprio_get 274 -#endif -#ifndef __NR_openat -#define __NR_openat 286 -#endif -#ifndef __NR_fstatat64 -#define __NR_fstatat64 291 -#endif -#ifndef __NR_unlinkat -#define __NR_unlinkat 292 -#endif -#ifndef __NR_move_pages -#define __NR_move_pages 301 -#endif -#ifndef __NR_getcpu -#define __NR_getcpu 302 -#endif -/* End of powerpc defininitions */ -#endif - - -/* After forking, we must make sure to only call system calls. */ -#if defined(__BOUNDED_POINTERS__) - #error "Need to port invocations of syscalls for bounded ptrs" -#else - /* The core dumper and the thread lister get executed after threads - * have been suspended. As a consequence, we cannot call any functions - * that acquire locks. Unfortunately, libc wraps most system calls - * (e.g. in order to implement pthread_atfork, and to make calls - * cancellable), which means we cannot call these functions. Instead, - * we have to call syscall() directly. - */ - #undef LSS_ERRNO - #ifdef SYS_ERRNO - /* Allow the including file to override the location of errno. This can - * be useful when using clone() with the CLONE_VM option. - */ - #define LSS_ERRNO SYS_ERRNO - #else - #define LSS_ERRNO errno - #endif - - #undef LSS_INLINE - #ifdef SYS_INLINE - #define LSS_INLINE SYS_INLINE - #else - #define LSS_INLINE static inline - #endif - - /* Allow the including file to override the prefix used for all new - * system calls. By default, it will be set to "sys_". - */ - #undef LSS_NAME - #ifndef SYS_PREFIX - #define LSS_NAME(name) sys_##name - #elif defined(SYS_PREFIX) && SYS_PREFIX < 0 - #define LSS_NAME(name) name - #elif defined(SYS_PREFIX) && SYS_PREFIX == 0 - #define LSS_NAME(name) sys0_##name - #elif defined(SYS_PREFIX) && SYS_PREFIX == 1 - #define LSS_NAME(name) sys1_##name - #elif defined(SYS_PREFIX) && SYS_PREFIX == 2 - #define LSS_NAME(name) sys2_##name - #elif defined(SYS_PREFIX) && SYS_PREFIX == 3 - #define LSS_NAME(name) sys3_##name - #elif defined(SYS_PREFIX) && SYS_PREFIX == 4 - #define LSS_NAME(name) sys4_##name - #elif defined(SYS_PREFIX) && SYS_PREFIX == 5 - #define LSS_NAME(name) sys5_##name - #elif defined(SYS_PREFIX) && SYS_PREFIX == 6 - #define LSS_NAME(name) sys6_##name - #elif defined(SYS_PREFIX) && SYS_PREFIX == 7 - #define LSS_NAME(name) sys7_##name - #elif defined(SYS_PREFIX) && SYS_PREFIX == 8 - #define LSS_NAME(name) sys8_##name - #elif defined(SYS_PREFIX) && SYS_PREFIX == 9 - #define LSS_NAME(name) sys9_##name - #endif - - #undef LSS_RETURN - #if (defined(__i386__) || defined(__x86_64__) || defined(__ARM_ARCH_3__) \ - || defined(__ARM_EABI__) || defined(__aarch64__)) - /* Failing system calls return a negative result in the range of - * -1..-4095. These are "errno" values with the sign inverted. - */ - #define LSS_RETURN(type, res) \ - do { \ - if ((unsigned long)(res) >= (unsigned long)(-4095)) { \ - LSS_ERRNO = -(res); \ - res = -1; \ - } \ - return (type) (res); \ - } while (0) - #elif defined(__mips__) - /* On MIPS, failing system calls return -1, and set errno in a - * separate CPU register. - */ - #define LSS_RETURN(type, res, err) \ - do { \ - if (err) { \ - unsigned long __errnovalue = (res); \ - LSS_ERRNO = __errnovalue; \ - res = -1; \ - } \ - return (type) (res); \ - } while (0) - #elif defined(__PPC__) - /* On PPC, failing system calls return -1, and set errno in a - * separate CPU register. See linux/unistd.h. - */ - #define LSS_RETURN(type, res, err) \ - do { \ - if (err & 0x10000000 ) { \ - LSS_ERRNO = (res); \ - res = -1; \ - } \ - return (type) (res); \ - } while (0) - #endif - #if defined(__i386__) - /* In PIC mode (e.g. when building shared libraries), gcc for i386 - * reserves ebx. Unfortunately, most distribution ship with implementations - * of _syscallX() which clobber ebx. - * Also, most definitions of _syscallX() neglect to mark "memory" as being - * clobbered. This causes problems with compilers, that do a better job - * at optimizing across __asm__ calls. - * So, we just have to redefine all of the _syscallX() macros. - */ - #undef LSS_ENTRYPOINT - #ifdef SYS_SYSCALL_ENTRYPOINT - static inline void (**LSS_NAME(get_syscall_entrypoint)(void))(void) { - void (**entrypoint)(void); - asm volatile(".bss\n" - ".align 8\n" - ".globl " SYS_SYSCALL_ENTRYPOINT "\n" - ".common " SYS_SYSCALL_ENTRYPOINT ",8,8\n" - ".previous\n" - /* This logically does 'lea "SYS_SYSCALL_ENTRYPOINT", %0' */ - "call 0f\n" - "0:pop %0\n" - "add $_GLOBAL_OFFSET_TABLE_+[.-0b], %0\n" - "mov " SYS_SYSCALL_ENTRYPOINT "@GOT(%0), %0\n" - : "=r"(entrypoint)); - return entrypoint; - } - - #define LSS_ENTRYPOINT ".bss\n" \ - ".align 8\n" \ - ".globl " SYS_SYSCALL_ENTRYPOINT "\n" \ - ".common " SYS_SYSCALL_ENTRYPOINT ",8,8\n" \ - ".previous\n" \ - /* Check the SYS_SYSCALL_ENTRYPOINT vector */ \ - "push %%eax\n" \ - "call 10000f\n" \ - "10000:pop %%eax\n" \ - "add $_GLOBAL_OFFSET_TABLE_+[.-10000b], %%eax\n" \ - "mov " SYS_SYSCALL_ENTRYPOINT \ - "@GOT(%%eax), %%eax\n" \ - "mov 0(%%eax), %%eax\n" \ - "test %%eax, %%eax\n" \ - "jz 10002f\n" \ - "push %%eax\n" \ - "call 10001f\n" \ - "10001:pop %%eax\n" \ - "add $(10003f-10001b), %%eax\n" \ - "xchg 4(%%esp), %%eax\n" \ - "ret\n" \ - "10002:pop %%eax\n" \ - "int $0x80\n" \ - "10003:\n" - #else - #define LSS_ENTRYPOINT "int $0x80\n" - #endif - #undef LSS_BODY - #define LSS_BODY(type,args...) \ - long __res; \ - __asm__ __volatile__("push %%ebx\n" \ - "movl %2,%%ebx\n" \ - LSS_ENTRYPOINT \ - "pop %%ebx" \ - args \ - : "esp", "memory"); \ - LSS_RETURN(type,__res) - #undef _syscall0 - #define _syscall0(type,name) \ - type LSS_NAME(name)(void) { \ - long __res; \ - __asm__ volatile(LSS_ENTRYPOINT \ - : "=a" (__res) \ - : "0" (__NR_##name) \ - : "esp", "memory"); \ - LSS_RETURN(type,__res); \ - } - #undef _syscall1 - #define _syscall1(type,name,type1,arg1) \ - type LSS_NAME(name)(type1 arg1) { \ - LSS_BODY(type, \ - : "=a" (__res) \ - : "0" (__NR_##name), "ri" ((long)(arg1))); \ - } - #undef _syscall2 - #define _syscall2(type,name,type1,arg1,type2,arg2) \ - type LSS_NAME(name)(type1 arg1,type2 arg2) { \ - LSS_BODY(type, \ - : "=a" (__res) \ - : "0" (__NR_##name),"ri" ((long)(arg1)), "c" ((long)(arg2))); \ - } - #undef _syscall3 - #define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \ - type LSS_NAME(name)(type1 arg1,type2 arg2,type3 arg3) { \ - LSS_BODY(type, \ - : "=a" (__res) \ - : "0" (__NR_##name), "ri" ((long)(arg1)), "c" ((long)(arg2)), \ - "d" ((long)(arg3))); \ - } - #undef _syscall4 - #define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4) { \ - LSS_BODY(type, \ - : "=a" (__res) \ - : "0" (__NR_##name), "ri" ((long)(arg1)), "c" ((long)(arg2)), \ - "d" ((long)(arg3)),"S" ((long)(arg4))); \ - } - #undef _syscall5 - #define _syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \ - type5,arg5) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4, \ - type5 arg5) { \ - long __res; \ - __asm__ __volatile__("push %%ebx\n" \ - "movl %2,%%ebx\n" \ - "movl %1,%%eax\n" \ - LSS_ENTRYPOINT \ - "pop %%ebx" \ - : "=a" (__res) \ - : "i" (__NR_##name), "ri" ((long)(arg1)), \ - "c" ((long)(arg2)), "d" ((long)(arg3)), \ - "S" ((long)(arg4)), "D" ((long)(arg5)) \ - : "esp", "memory"); \ - LSS_RETURN(type,__res); \ - } - #undef _syscall6 - #define _syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \ - type5,arg5,type6,arg6) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4, \ - type5 arg5, type6 arg6) { \ - long __res; \ - struct { long __a1; long __a6; } __s = { (long)arg1, (long) arg6 }; \ - __asm__ __volatile__("push %%ebp\n" \ - "push %%ebx\n" \ - "movl 4(%2),%%ebp\n" \ - "movl 0(%2), %%ebx\n" \ - "movl %1,%%eax\n" \ - LSS_ENTRYPOINT \ - "pop %%ebx\n" \ - "pop %%ebp" \ - : "=a" (__res) \ - : "i" (__NR_##name), "0" ((long)(&__s)), \ - "c" ((long)(arg2)), "d" ((long)(arg3)), \ - "S" ((long)(arg4)), "D" ((long)(arg5)) \ - : "esp", "memory"); \ - LSS_RETURN(type,__res); \ - } - LSS_INLINE int LSS_NAME(clone)(int (*fn)(void *), void *child_stack, - int flags, void *arg, int *parent_tidptr, - void *newtls, int *child_tidptr) { - long __res; - __asm__ __volatile__(/* if (fn == NULL) - * return -EINVAL; - */ - "movl %3,%%ecx\n" - "jecxz 1f\n" - - /* if (child_stack == NULL) - * return -EINVAL; - */ - "movl %4,%%ecx\n" - "jecxz 1f\n" - - /* Set up alignment of the child stack: - * child_stack = (child_stack & ~0xF) - 20; - */ - "andl $-16,%%ecx\n" - "subl $20,%%ecx\n" - - /* Push "arg" and "fn" onto the stack that will be - * used by the child. - */ - "movl %6,%%eax\n" - "movl %%eax,4(%%ecx)\n" - "movl %3,%%eax\n" - "movl %%eax,(%%ecx)\n" - - /* %eax = syscall(%eax = __NR_clone, - * %ebx = flags, - * %ecx = child_stack, - * %edx = parent_tidptr, - * %esi = newtls, - * %edi = child_tidptr) - * Also, make sure that %ebx gets preserved as it is - * used in PIC mode. - */ - "movl %8,%%esi\n" - "movl %7,%%edx\n" - "movl %5,%%eax\n" - "movl %9,%%edi\n" - "pushl %%ebx\n" - "movl %%eax,%%ebx\n" - "movl %2,%%eax\n" - LSS_ENTRYPOINT - - /* In the parent: restore %ebx - * In the child: move "fn" into %ebx - */ - "popl %%ebx\n" - - /* if (%eax != 0) - * return %eax; - */ - "test %%eax,%%eax\n" - "jnz 1f\n" - - /* In the child, now. Terminate frame pointer chain. - */ - "movl $0,%%ebp\n" - - /* Call "fn". "arg" is already on the stack. - */ - "call *%%ebx\n" - - /* Call _exit(%ebx). Unfortunately older versions - * of gcc restrict the number of arguments that can - * be passed to asm(). So, we need to hard-code the - * system call number. - */ - "movl %%eax,%%ebx\n" - "movl $1,%%eax\n" - LSS_ENTRYPOINT - - /* Return to parent. - */ - "1:\n" - : "=a" (__res) - : "0"(-EINVAL), "i"(__NR_clone), - "m"(fn), "m"(child_stack), "m"(flags), "m"(arg), - "m"(parent_tidptr), "m"(newtls), "m"(child_tidptr) - : "esp", "memory", "ecx", "edx", "esi", "edi"); - LSS_RETURN(int, __res); - } - - #define __NR__fadvise64_64 __NR_fadvise64_64 - LSS_INLINE _syscall6(int, _fadvise64_64, int, fd, - unsigned, offset_lo, unsigned, offset_hi, - unsigned, len_lo, unsigned, len_hi, - int, advice) - - LSS_INLINE int LSS_NAME(fadvise64)(int fd, loff_t offset, - loff_t len, int advice) { - return LSS_NAME(_fadvise64_64)(fd, - (unsigned)offset, (unsigned)(offset >>32), - (unsigned)len, (unsigned)(len >> 32), - advice); - } - - #define __NR__fallocate __NR_fallocate - LSS_INLINE _syscall6(int, _fallocate, int, fd, - int, mode, - unsigned, offset_lo, unsigned, offset_hi, - unsigned, len_lo, unsigned, len_hi) - - LSS_INLINE int LSS_NAME(fallocate)(int fd, int mode, - loff_t offset, loff_t len) { - union { loff_t off; unsigned w[2]; } o = { offset }, l = { len }; - return LSS_NAME(_fallocate)(fd, mode, o.w[0], o.w[1], l.w[0], l.w[1]); - } - - LSS_INLINE _syscall1(int, set_thread_area, void *, u) - LSS_INLINE _syscall1(int, get_thread_area, void *, u) - - LSS_INLINE void (*LSS_NAME(restore_rt)(void))(void) { - /* On i386, the kernel does not know how to return from a signal - * handler. Instead, it relies on user space to provide a - * restorer function that calls the {rt_,}sigreturn() system call. - * Unfortunately, we cannot just reference the glibc version of this - * function, as glibc goes out of its way to make it inaccessible. - */ - void (*res)(void); - __asm__ __volatile__("call 2f\n" - "0:.align 16\n" - "1:movl %1,%%eax\n" - LSS_ENTRYPOINT - "2:popl %0\n" - "addl $(1b-0b),%0\n" - : "=a" (res) - : "i" (__NR_rt_sigreturn)); - return res; - } - LSS_INLINE void (*LSS_NAME(restore)(void))(void) { - /* On i386, the kernel does not know how to return from a signal - * handler. Instead, it relies on user space to provide a - * restorer function that calls the {rt_,}sigreturn() system call. - * Unfortunately, we cannot just reference the glibc version of this - * function, as glibc goes out of its way to make it inaccessible. - */ - void (*res)(void); - __asm__ __volatile__("call 2f\n" - "0:.align 16\n" - "1:pop %%eax\n" - "movl %1,%%eax\n" - LSS_ENTRYPOINT - "2:popl %0\n" - "addl $(1b-0b),%0\n" - : "=a" (res) - : "i" (__NR_sigreturn)); - return res; - } - #elif defined(__x86_64__) - /* There are no known problems with any of the _syscallX() macros - * currently shipping for x86_64, but we still need to be able to define - * our own version so that we can override the location of the errno - * location (e.g. when using the clone() system call with the CLONE_VM - * option). - */ - #undef LSS_ENTRYPOINT - #ifdef SYS_SYSCALL_ENTRYPOINT - static inline void (**LSS_NAME(get_syscall_entrypoint)(void))(void) { - void (**entrypoint)(void); - asm volatile(".bss\n" - ".align 8\n" - ".globl " SYS_SYSCALL_ENTRYPOINT "\n" - ".common " SYS_SYSCALL_ENTRYPOINT ",8,8\n" - ".previous\n" - "mov " SYS_SYSCALL_ENTRYPOINT "@GOTPCREL(%%rip), %0\n" - : "=r"(entrypoint)); - return entrypoint; - } - - #define LSS_ENTRYPOINT \ - ".bss\n" \ - ".align 8\n" \ - ".globl " SYS_SYSCALL_ENTRYPOINT "\n" \ - ".common " SYS_SYSCALL_ENTRYPOINT ",8,8\n" \ - ".previous\n" \ - "mov " SYS_SYSCALL_ENTRYPOINT "@GOTPCREL(%%rip), %%rcx\n" \ - "mov 0(%%rcx), %%rcx\n" \ - "test %%rcx, %%rcx\n" \ - "jz 10001f\n" \ - "call *%%rcx\n" \ - "jmp 10002f\n" \ - "10001:syscall\n" \ - "10002:\n" - - #else - #define LSS_ENTRYPOINT "syscall\n" - #endif - - /* The x32 ABI has 32 bit longs, but the syscall interface is 64 bit. - * We need to explicitly cast to an unsigned 64 bit type to avoid implicit - * sign extension. We can't cast pointers directly because those are - * 32 bits, and gcc will dump ugly warnings about casting from a pointer - * to an integer of a different size. - */ - #undef LSS_SYSCALL_ARG - #define LSS_SYSCALL_ARG(a) ((uint64_t)(uintptr_t)(a)) - #undef _LSS_RETURN - #define _LSS_RETURN(type, res, cast) \ - do { \ - if ((uint64_t)(res) >= (uint64_t)(-4095)) { \ - LSS_ERRNO = -(res); \ - res = -1; \ - } \ - return (type)(cast)(res); \ - } while (0) - #undef LSS_RETURN - #define LSS_RETURN(type, res) _LSS_RETURN(type, res, uintptr_t) - - #undef _LSS_BODY - #define _LSS_BODY(nr, type, name, cast, ...) \ - long long __res; \ - __asm__ __volatile__(LSS_BODY_ASM##nr LSS_ENTRYPOINT \ - : "=a" (__res) \ - : "0" (__NR_##name) LSS_BODY_ARG##nr(__VA_ARGS__) \ - : LSS_BODY_CLOBBER##nr "r11", "rcx", "memory"); \ - _LSS_RETURN(type, __res, cast) - #undef LSS_BODY - #define LSS_BODY(nr, type, name, args...) \ - _LSS_BODY(nr, type, name, uintptr_t, ## args) - - #undef LSS_BODY_ASM0 - #undef LSS_BODY_ASM1 - #undef LSS_BODY_ASM2 - #undef LSS_BODY_ASM3 - #undef LSS_BODY_ASM4 - #undef LSS_BODY_ASM5 - #undef LSS_BODY_ASM6 - #define LSS_BODY_ASM0 - #define LSS_BODY_ASM1 LSS_BODY_ASM0 - #define LSS_BODY_ASM2 LSS_BODY_ASM1 - #define LSS_BODY_ASM3 LSS_BODY_ASM2 - #define LSS_BODY_ASM4 LSS_BODY_ASM3 "movq %5,%%r10;" - #define LSS_BODY_ASM5 LSS_BODY_ASM4 "movq %6,%%r8;" - #define LSS_BODY_ASM6 LSS_BODY_ASM5 "movq %7,%%r9;" - - #undef LSS_BODY_CLOBBER0 - #undef LSS_BODY_CLOBBER1 - #undef LSS_BODY_CLOBBER2 - #undef LSS_BODY_CLOBBER3 - #undef LSS_BODY_CLOBBER4 - #undef LSS_BODY_CLOBBER5 - #undef LSS_BODY_CLOBBER6 - #define LSS_BODY_CLOBBER0 - #define LSS_BODY_CLOBBER1 LSS_BODY_CLOBBER0 - #define LSS_BODY_CLOBBER2 LSS_BODY_CLOBBER1 - #define LSS_BODY_CLOBBER3 LSS_BODY_CLOBBER2 - #define LSS_BODY_CLOBBER4 LSS_BODY_CLOBBER3 "r10", - #define LSS_BODY_CLOBBER5 LSS_BODY_CLOBBER4 "r8", - #define LSS_BODY_CLOBBER6 LSS_BODY_CLOBBER5 "r9", - - #undef LSS_BODY_ARG0 - #undef LSS_BODY_ARG1 - #undef LSS_BODY_ARG2 - #undef LSS_BODY_ARG3 - #undef LSS_BODY_ARG4 - #undef LSS_BODY_ARG5 - #undef LSS_BODY_ARG6 - #define LSS_BODY_ARG0() - #define LSS_BODY_ARG1(arg1) \ - LSS_BODY_ARG0(), "D" (arg1) - #define LSS_BODY_ARG2(arg1, arg2) \ - LSS_BODY_ARG1(arg1), "S" (arg2) - #define LSS_BODY_ARG3(arg1, arg2, arg3) \ - LSS_BODY_ARG2(arg1, arg2), "d" (arg3) - #define LSS_BODY_ARG4(arg1, arg2, arg3, arg4) \ - LSS_BODY_ARG3(arg1, arg2, arg3), "r" (arg4) - #define LSS_BODY_ARG5(arg1, arg2, arg3, arg4, arg5) \ - LSS_BODY_ARG4(arg1, arg2, arg3, arg4), "r" (arg5) - #define LSS_BODY_ARG6(arg1, arg2, arg3, arg4, arg5, arg6) \ - LSS_BODY_ARG5(arg1, arg2, arg3, arg4, arg5), "r" (arg6) - - #undef _syscall0 - #define _syscall0(type,name) \ - type LSS_NAME(name)(void) { \ - LSS_BODY(0, type, name); \ - } - #undef _syscall1 - #define _syscall1(type,name,type1,arg1) \ - type LSS_NAME(name)(type1 arg1) { \ - LSS_BODY(1, type, name, LSS_SYSCALL_ARG(arg1)); \ - } - #undef _syscall2 - #define _syscall2(type,name,type1,arg1,type2,arg2) \ - type LSS_NAME(name)(type1 arg1, type2 arg2) { \ - LSS_BODY(2, type, name, LSS_SYSCALL_ARG(arg1), LSS_SYSCALL_ARG(arg2));\ - } - #undef _syscall3 - #define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3) { \ - LSS_BODY(3, type, name, LSS_SYSCALL_ARG(arg1), LSS_SYSCALL_ARG(arg2), \ - LSS_SYSCALL_ARG(arg3)); \ - } - #undef _syscall4 - #define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4) { \ - LSS_BODY(4, type, name, LSS_SYSCALL_ARG(arg1), LSS_SYSCALL_ARG(arg2), \ - LSS_SYSCALL_ARG(arg3), LSS_SYSCALL_ARG(arg4));\ - } - #undef _syscall5 - #define _syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \ - type5,arg5) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4, \ - type5 arg5) { \ - LSS_BODY(5, type, name, LSS_SYSCALL_ARG(arg1), LSS_SYSCALL_ARG(arg2), \ - LSS_SYSCALL_ARG(arg3), LSS_SYSCALL_ARG(arg4), \ - LSS_SYSCALL_ARG(arg5)); \ - } - #undef _syscall6 - #define _syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \ - type5,arg5,type6,arg6) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4, \ - type5 arg5, type6 arg6) { \ - LSS_BODY(6, type, name, LSS_SYSCALL_ARG(arg1), LSS_SYSCALL_ARG(arg2), \ - LSS_SYSCALL_ARG(arg3), LSS_SYSCALL_ARG(arg4), \ - LSS_SYSCALL_ARG(arg5), LSS_SYSCALL_ARG(arg6));\ - } - LSS_INLINE int LSS_NAME(clone)(int (*fn)(void *), void *child_stack, - int flags, void *arg, int *parent_tidptr, - void *newtls, int *child_tidptr) { - long long __res; - { - __asm__ __volatile__(/* if (fn == NULL) - * return -EINVAL; - */ - "testq %4,%4\n" - "jz 1f\n" - - /* if (child_stack == NULL) - * return -EINVAL; - */ - "testq %5,%5\n" - "jz 1f\n" - - /* childstack -= 2*sizeof(void *); - */ - "subq $16,%5\n" - - /* Push "arg" and "fn" onto the stack that will be - * used by the child. - */ - "movq %7,8(%5)\n" - "movq %4,0(%5)\n" - - /* %rax = syscall(%rax = __NR_clone, - * %rdi = flags, - * %rsi = child_stack, - * %rdx = parent_tidptr, - * %r8 = new_tls, - * %r10 = child_tidptr) - */ - "movq %2,%%rax\n" - "movq %9,%%r8\n" - "movq %10,%%r10\n" - LSS_ENTRYPOINT - - /* if (%rax != 0) - * return; - */ - "testq %%rax,%%rax\n" - "jnz 1f\n" - - /* In the child. Terminate frame pointer chain. - */ - "xorq %%rbp,%%rbp\n" - - /* Call "fn(arg)". - */ - "popq %%rax\n" - "popq %%rdi\n" - "call *%%rax\n" - - /* Call _exit(%ebx). - */ - "movq %%rax,%%rdi\n" - "movq %3,%%rax\n" - LSS_ENTRYPOINT - - /* Return to parent. - */ - "1:\n" - : "=a" (__res) - : "0"(-EINVAL), "i"(__NR_clone), "i"(__NR_exit), - "r"(LSS_SYSCALL_ARG(fn)), - "S"(LSS_SYSCALL_ARG(child_stack)), - "D"(LSS_SYSCALL_ARG(flags)), - "r"(LSS_SYSCALL_ARG(arg)), - "d"(LSS_SYSCALL_ARG(parent_tidptr)), - "r"(LSS_SYSCALL_ARG(newtls)), - "r"(LSS_SYSCALL_ARG(child_tidptr)) - : "rsp", "memory", "r8", "r10", "r11", "rcx"); - } - LSS_RETURN(int, __res); - } - LSS_INLINE _syscall2(int, arch_prctl, int, c, void *, a) - - /* Need to make sure loff_t isn't truncated to 32-bits under x32. */ - LSS_INLINE int LSS_NAME(fadvise64)(int fd, loff_t offset, loff_t len, - int advice) { - LSS_BODY(4, int, fadvise64, LSS_SYSCALL_ARG(fd), (uint64_t)(offset), - (uint64_t)(len), LSS_SYSCALL_ARG(advice)); - } - - LSS_INLINE void (*LSS_NAME(restore_rt)(void))(void) { - /* On x86-64, the kernel does not know how to return from - * a signal handler. Instead, it relies on user space to provide a - * restorer function that calls the rt_sigreturn() system call. - * Unfortunately, we cannot just reference the glibc version of this - * function, as glibc goes out of its way to make it inaccessible. - */ - long long res; - __asm__ __volatile__("jmp 2f\n" - ".align 16\n" - "1:movq %1,%%rax\n" - LSS_ENTRYPOINT - "2:leaq 1b(%%rip),%0\n" - : "=r" (res) - : "i" (__NR_rt_sigreturn)); - return (void (*)(void))(uintptr_t)res; - } - #elif defined(__ARM_ARCH_3__) - /* Most definitions of _syscallX() neglect to mark "memory" as being - * clobbered. This causes problems with compilers, that do a better job - * at optimizing across __asm__ calls. - * So, we just have to redefine all of the _syscallX() macros. - */ - #undef LSS_REG - #define LSS_REG(r,a) register long __r##r __asm__("r"#r) = (long)a - #undef LSS_BODY - #define LSS_BODY(type,name,args...) \ - register long __res_r0 __asm__("r0"); \ - long __res; \ - __asm__ __volatile__ (__syscall(name) \ - : "=r"(__res_r0) : args : "lr", "memory"); \ - __res = __res_r0; \ - LSS_RETURN(type, __res) - #undef _syscall0 - #define _syscall0(type, name) \ - type LSS_NAME(name)(void) { \ - LSS_BODY(type, name); \ - } - #undef _syscall1 - #define _syscall1(type, name, type1, arg1) \ - type LSS_NAME(name)(type1 arg1) { \ - LSS_REG(0, arg1); LSS_BODY(type, name, "r"(__r0)); \ - } - #undef _syscall2 - #define _syscall2(type, name, type1, arg1, type2, arg2) \ - type LSS_NAME(name)(type1 arg1, type2 arg2) { \ - LSS_REG(0, arg1); LSS_REG(1, arg2); \ - LSS_BODY(type, name, "r"(__r0), "r"(__r1)); \ - } - #undef _syscall3 - #define _syscall3(type, name, type1, arg1, type2, arg2, type3, arg3) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3) { \ - LSS_REG(0, arg1); LSS_REG(1, arg2); LSS_REG(2, arg3); \ - LSS_BODY(type, name, "r"(__r0), "r"(__r1), "r"(__r2)); \ - } - #undef _syscall4 - #define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4) { \ - LSS_REG(0, arg1); LSS_REG(1, arg2); LSS_REG(2, arg3); \ - LSS_REG(3, arg4); \ - LSS_BODY(type, name, "r"(__r0), "r"(__r1), "r"(__r2), "r"(__r3)); \ - } - #undef _syscall5 - #define _syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \ - type5,arg5) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4, \ - type5 arg5) { \ - LSS_REG(0, arg1); LSS_REG(1, arg2); LSS_REG(2, arg3); \ - LSS_REG(3, arg4); LSS_REG(4, arg5); \ - LSS_BODY(type, name, "r"(__r0), "r"(__r1), "r"(__r2), "r"(__r3), \ - "r"(__r4)); \ - } - #undef _syscall6 - #define _syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \ - type5,arg5,type6,arg6) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4, \ - type5 arg5, type6 arg6) { \ - LSS_REG(0, arg1); LSS_REG(1, arg2); LSS_REG(2, arg3); \ - LSS_REG(3, arg4); LSS_REG(4, arg5); LSS_REG(5, arg6); \ - LSS_BODY(type, name, "r"(__r0), "r"(__r1), "r"(__r2), "r"(__r3), \ - "r"(__r4), "r"(__r5)); \ - } - LSS_INLINE int LSS_NAME(clone)(int (*fn)(void *), void *child_stack, - int flags, void *arg, int *parent_tidptr, - void *newtls, int *child_tidptr) { - long __res; - { - register int __flags __asm__("r0") = flags; - register void *__stack __asm__("r1") = child_stack; - register void *__ptid __asm__("r2") = parent_tidptr; - register void *__tls __asm__("r3") = newtls; - register int *__ctid __asm__("r4") = child_tidptr; - __asm__ __volatile__(/* if (fn == NULL || child_stack == NULL) - * return -EINVAL; - */ - "cmp %2,#0\n" - "cmpne %3,#0\n" - "moveq %0,%1\n" - "beq 1f\n" - - /* Push "arg" and "fn" onto the stack that will be - * used by the child. - */ - "str %5,[%3,#-4]!\n" - "str %2,[%3,#-4]!\n" - - /* %r0 = syscall(%r0 = flags, - * %r1 = child_stack, - * %r2 = parent_tidptr, - * %r3 = newtls, - * %r4 = child_tidptr) - */ - __syscall(clone)"\n" - - /* if (%r0 != 0) - * return %r0; - */ - "movs %0,r0\n" - "bne 1f\n" - - /* In the child, now. Call "fn(arg)". - */ - "ldr r0,[sp, #4]\n" - "mov lr,pc\n" - "ldr pc,[sp]\n" - - /* Call _exit(%r0). - */ - __syscall(exit)"\n" - "1:\n" - : "=r" (__res) - : "i"(-EINVAL), - "r"(fn), "r"(__stack), "r"(__flags), "r"(arg), - "r"(__ptid), "r"(__tls), "r"(__ctid) - : "cc", "lr", "memory"); - } - LSS_RETURN(int, __res); - } - #elif defined(__ARM_EABI__) - /* Most definitions of _syscallX() neglect to mark "memory" as being - * clobbered. This causes problems with compilers, that do a better job - * at optimizing across __asm__ calls. - * So, we just have to redefine all fo the _syscallX() macros. - */ - #undef LSS_REG - #define LSS_REG(r,a) register long __r##r __asm__("r"#r) = (long)a - #undef LSS_BODY - #define LSS_BODY(type,name,args...) \ - register long __res_r0 __asm__("r0"); \ - long __res; \ - __asm__ __volatile__ ("push {r7}\n" \ - "mov r7, %1\n" \ - "swi 0x0\n" \ - "pop {r7}\n" \ - : "=r"(__res_r0) \ - : "i"(__NR_##name) , ## args \ - : "lr", "memory"); \ - __res = __res_r0; \ - LSS_RETURN(type, __res) - #undef _syscall0 - #define _syscall0(type, name) \ - type LSS_NAME(name)(void) { \ - LSS_BODY(type, name); \ - } - #undef _syscall1 - #define _syscall1(type, name, type1, arg1) \ - type LSS_NAME(name)(type1 arg1) { \ - LSS_REG(0, arg1); LSS_BODY(type, name, "r"(__r0)); \ - } - #undef _syscall2 - #define _syscall2(type, name, type1, arg1, type2, arg2) \ - type LSS_NAME(name)(type1 arg1, type2 arg2) { \ - LSS_REG(0, arg1); LSS_REG(1, arg2); \ - LSS_BODY(type, name, "r"(__r0), "r"(__r1)); \ - } - #undef _syscall3 - #define _syscall3(type, name, type1, arg1, type2, arg2, type3, arg3) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3) { \ - LSS_REG(0, arg1); LSS_REG(1, arg2); LSS_REG(2, arg3); \ - LSS_BODY(type, name, "r"(__r0), "r"(__r1), "r"(__r2)); \ - } - #undef _syscall4 - #define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4) { \ - LSS_REG(0, arg1); LSS_REG(1, arg2); LSS_REG(2, arg3); \ - LSS_REG(3, arg4); \ - LSS_BODY(type, name, "r"(__r0), "r"(__r1), "r"(__r2), "r"(__r3)); \ - } - #undef _syscall5 - #define _syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \ - type5,arg5) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4, \ - type5 arg5) { \ - LSS_REG(0, arg1); LSS_REG(1, arg2); LSS_REG(2, arg3); \ - LSS_REG(3, arg4); LSS_REG(4, arg5); \ - LSS_BODY(type, name, "r"(__r0), "r"(__r1), "r"(__r2), "r"(__r3), \ - "r"(__r4)); \ - } - #undef _syscall6 - #define _syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \ - type5,arg5,type6,arg6) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4, \ - type5 arg5, type6 arg6) { \ - LSS_REG(0, arg1); LSS_REG(1, arg2); LSS_REG(2, arg3); \ - LSS_REG(3, arg4); LSS_REG(4, arg5); LSS_REG(5, arg6); \ - LSS_BODY(type, name, "r"(__r0), "r"(__r1), "r"(__r2), "r"(__r3), \ - "r"(__r4), "r"(__r5)); \ - } - LSS_INLINE int LSS_NAME(clone)(int (*fn)(void *), void *child_stack, - int flags, void *arg, int *parent_tidptr, - void *newtls, int *child_tidptr) { - long __res; - { - register int __flags __asm__("r0") = flags; - register void *__stack __asm__("r1") = child_stack; - register void *__ptid __asm__("r2") = parent_tidptr; - register void *__tls __asm__("r3") = newtls; - register int *__ctid __asm__("r4") = child_tidptr; - __asm__ __volatile__(/* if (fn == NULL || child_stack == NULL) - * return -EINVAL; - */ -#ifdef __thumb2__ - "push {r7}\n" -#endif - "cmp %2,#0\n" - "it ne\n" - "cmpne %3,#0\n" - "it eq\n" - "moveq %0,%1\n" - "beq 1f\n" - - /* Push "arg" and "fn" onto the stack that will be - * used by the child. - */ - "str %5,[%3,#-4]!\n" - "str %2,[%3,#-4]!\n" - - /* %r0 = syscall(%r0 = flags, - * %r1 = child_stack, - * %r2 = parent_tidptr, - * %r3 = newtls, - * %r4 = child_tidptr) - */ - "mov r7, %9\n" - "swi 0x0\n" - - /* if (%r0 != 0) - * return %r0; - */ - "movs %0,r0\n" - "bne 1f\n" - - /* In the child, now. Call "fn(arg)". - */ - "ldr r0,[sp, #4]\n" - - /* When compiling for Thumb-2 the "MOV LR,PC" here - * won't work because it loads PC+4 into LR, - * whereas the LDR is a 4-byte instruction. - * This results in the child thread always - * crashing with an "Illegal Instruction" when it - * returned into the middle of the LDR instruction - * The instruction sequence used instead was - * recommended by - * "https://wiki.edubuntu.org/ARM/Thumb2PortingHowto#Quick_Reference". - */ - #ifdef __thumb2__ - "ldr r7,[sp]\n" - "blx r7\n" - #else - "mov lr,pc\n" - "ldr pc,[sp]\n" - #endif - - /* Call _exit(%r0). - */ - "mov r7, %10\n" - "swi 0x0\n" - "1:\n" -#ifdef __thumb2__ - "pop {r7}" -#endif - : "=r" (__res) - : "i"(-EINVAL), - "r"(fn), "r"(__stack), "r"(__flags), "r"(arg), - "r"(__ptid), "r"(__tls), "r"(__ctid), - "i"(__NR_clone), "i"(__NR_exit) -#ifdef __thumb2__ - : "cc", "lr", "memory"); -#else - : "cc", "r7", "lr", "memory"); -#endif - } - LSS_RETURN(int, __res); - } - #elif defined(__aarch64__) - /* Most definitions of _syscallX() neglect to mark "memory" as being - * clobbered. This causes problems with compilers, that do a better job - * at optimizing across __asm__ calls. - * So, we just have to redefine all of the _syscallX() macros. - */ - #undef LSS_REG - #define LSS_REG(r,a) register int64_t __r##r __asm__("x"#r) = (int64_t)a - #undef LSS_BODY - #define LSS_BODY(type,name,args...) \ - register int64_t __res_x0 __asm__("x0"); \ - int64_t __res; \ - __asm__ __volatile__ ("mov x8, %1\n" \ - "svc 0x0\n" \ - : "=r"(__res_x0) \ - : "i"(__NR_##name) , ## args \ - : "x8", "memory"); \ - __res = __res_x0; \ - LSS_RETURN(type, __res) - #undef _syscall0 - #define _syscall0(type, name) \ - type LSS_NAME(name)(void) { \ - LSS_BODY(type, name); \ - } - #undef _syscall1 - #define _syscall1(type, name, type1, arg1) \ - type LSS_NAME(name)(type1 arg1) { \ - LSS_REG(0, arg1); LSS_BODY(type, name, "r"(__r0)); \ - } - #undef _syscall2 - #define _syscall2(type, name, type1, arg1, type2, arg2) \ - type LSS_NAME(name)(type1 arg1, type2 arg2) { \ - LSS_REG(0, arg1); LSS_REG(1, arg2); \ - LSS_BODY(type, name, "r"(__r0), "r"(__r1)); \ - } - #undef _syscall3 - #define _syscall3(type, name, type1, arg1, type2, arg2, type3, arg3) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3) { \ - LSS_REG(0, arg1); LSS_REG(1, arg2); LSS_REG(2, arg3); \ - LSS_BODY(type, name, "r"(__r0), "r"(__r1), "r"(__r2)); \ - } - #undef _syscall4 - #define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4) { \ - LSS_REG(0, arg1); LSS_REG(1, arg2); LSS_REG(2, arg3); \ - LSS_REG(3, arg4); \ - LSS_BODY(type, name, "r"(__r0), "r"(__r1), "r"(__r2), "r"(__r3)); \ - } - #undef _syscall5 - #define _syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \ - type5,arg5) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4, \ - type5 arg5) { \ - LSS_REG(0, arg1); LSS_REG(1, arg2); LSS_REG(2, arg3); \ - LSS_REG(3, arg4); LSS_REG(4, arg5); \ - LSS_BODY(type, name, "r"(__r0), "r"(__r1), "r"(__r2), "r"(__r3), \ - "r"(__r4)); \ - } - #undef _syscall6 - #define _syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \ - type5,arg5,type6,arg6) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4, \ - type5 arg5, type6 arg6) { \ - LSS_REG(0, arg1); LSS_REG(1, arg2); LSS_REG(2, arg3); \ - LSS_REG(3, arg4); LSS_REG(4, arg5); LSS_REG(5, arg6); \ - LSS_BODY(type, name, "r"(__r0), "r"(__r1), "r"(__r2), "r"(__r3), \ - "r"(__r4), "r"(__r5)); \ - } - - LSS_INLINE int LSS_NAME(clone)(int (*fn)(void *), void *child_stack, - int flags, void *arg, int *parent_tidptr, - void *newtls, int *child_tidptr) { - int64_t __res; - { - register uint64_t __flags __asm__("x0") = flags; - register void *__stack __asm__("x1") = child_stack; - register void *__ptid __asm__("x2") = parent_tidptr; - register void *__tls __asm__("x3") = newtls; - register int *__ctid __asm__("x4") = child_tidptr; - __asm__ __volatile__(/* Push "arg" and "fn" onto the stack that will be - * used by the child. - */ - "stp %1, %4, [%2, #-16]!\n" - - /* %x0 = syscall(%x0 = flags, - * %x1 = child_stack, - * %x2 = parent_tidptr, - * %x3 = newtls, - * %x4 = child_tidptr) - */ - "mov x8, %8\n" - "svc 0x0\n" - - /* if (%r0 != 0) - * return %r0; - */ - "mov %0, x0\n" - "cbnz x0, 1f\n" - - /* In the child, now. Call "fn(arg)". - */ - "ldp x1, x0, [sp], #16\n" - "blr x1\n" - - /* Call _exit(%r0). - */ - "mov x8, %9\n" - "svc 0x0\n" - "1:\n" - : "=r" (__res) - : "r"(fn), "r"(__stack), "r"(__flags), "r"(arg), - "r"(__ptid), "r"(__tls), "r"(__ctid), - "i"(__NR_clone), "i"(__NR_exit) - : "cc", "x8", "memory"); - } - LSS_RETURN(int, __res); - } - #elif defined(__mips__) - #undef LSS_REG - #define LSS_REG(r,a) register unsigned long __r##r __asm__("$"#r) = \ - (unsigned long)(a) - #undef LSS_BODY - #undef LSS_SYSCALL_CLOBBERS - #if _MIPS_SIM == _MIPS_SIM_ABI32 - #define LSS_SYSCALL_CLOBBERS "$1", "$3", "$8", "$9", "$10", \ - "$11", "$12", "$13", "$14", "$15", \ - "$24", "$25", "hi", "lo", "memory" - #else - #define LSS_SYSCALL_CLOBBERS "$1", "$3", "$10", "$11", "$12", \ - "$13", "$14", "$15", "$24", "$25", \ - "hi", "lo", "memory" - #endif - #define LSS_BODY(type,name,r7,...) \ - register unsigned long __v0 __asm__("$2") = __NR_##name; \ - __asm__ __volatile__ ("syscall\n" \ - : "+r"(__v0), r7 (__r7) \ - : "0"(__v0), ##__VA_ARGS__ \ - : LSS_SYSCALL_CLOBBERS); \ - LSS_RETURN(type, __v0, __r7) - #undef _syscall0 - #define _syscall0(type, name) \ - type LSS_NAME(name)(void) { \ - register unsigned long __r7 __asm__("$7"); \ - LSS_BODY(type, name, "=r"); \ - } - #undef _syscall1 - #define _syscall1(type, name, type1, arg1) \ - type LSS_NAME(name)(type1 arg1) { \ - register unsigned long __r7 __asm__("$7"); \ - LSS_REG(4, arg1); LSS_BODY(type, name, "=r", "r"(__r4)); \ - } - #undef _syscall2 - #define _syscall2(type, name, type1, arg1, type2, arg2) \ - type LSS_NAME(name)(type1 arg1, type2 arg2) { \ - register unsigned long __r7 __asm__("$7"); \ - LSS_REG(4, arg1); LSS_REG(5, arg2); \ - LSS_BODY(type, name, "=r", "r"(__r4), "r"(__r5)); \ - } - #undef _syscall3 - #define _syscall3(type, name, type1, arg1, type2, arg2, type3, arg3) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3) { \ - register unsigned long __r7 __asm__("$7"); \ - LSS_REG(4, arg1); LSS_REG(5, arg2); LSS_REG(6, arg3); \ - LSS_BODY(type, name, "=r", "r"(__r4), "r"(__r5), "r"(__r6)); \ - } - #undef _syscall4 - #define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4) { \ - LSS_REG(4, arg1); LSS_REG(5, arg2); LSS_REG(6, arg3); \ - LSS_REG(7, arg4); \ - LSS_BODY(type, name, "+r", "r"(__r4), "r"(__r5), "r"(__r6)); \ - } - #undef _syscall5 - #if _MIPS_SIM == _MIPS_SIM_ABI32 - /* The old 32bit MIPS system call API passes the fifth and sixth argument - * on the stack, whereas the new APIs use registers "r8" and "r9". - */ - #define _syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \ - type5,arg5) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4, \ - type5 arg5) { \ - LSS_REG(4, arg1); LSS_REG(5, arg2); LSS_REG(6, arg3); \ - LSS_REG(7, arg4); \ - register unsigned long __v0 __asm__("$2") = __NR_##name; \ - __asm__ __volatile__ (".set noreorder\n" \ - "subu $29, 32\n" \ - "sw %5, 16($29)\n" \ - "syscall\n" \ - "addiu $29, 32\n" \ - ".set reorder\n" \ - : "+r"(__v0), "+r" (__r7) \ - : "r"(__r4), "r"(__r5), \ - "r"(__r6), "r" ((unsigned long)arg5) \ - : "$8", "$9", "$10", "$11", "$12", \ - "$13", "$14", "$15", "$24", "$25", \ - "memory"); \ - LSS_RETURN(type, __v0, __r7); \ - } - #else - #define _syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \ - type5,arg5) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4, \ - type5 arg5) { \ - LSS_REG(4, arg1); LSS_REG(5, arg2); LSS_REG(6, arg3); \ - LSS_REG(7, arg4); LSS_REG(8, arg5); \ - LSS_BODY(type, name, "+r", "r"(__r4), "r"(__r5), "r"(__r6), \ - "r"(__r8)); \ - } - #endif - #undef _syscall6 - #if _MIPS_SIM == _MIPS_SIM_ABI32 - /* The old 32bit MIPS system call API passes the fifth and sixth argument - * on the stack, whereas the new APIs use registers "r8" and "r9". - */ - #define _syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \ - type5,arg5,type6,arg6) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4, \ - type5 arg5, type6 arg6) { \ - LSS_REG(4, arg1); LSS_REG(5, arg2); LSS_REG(6, arg3); \ - LSS_REG(7, arg4); \ - register unsigned long __v0 __asm__("$2") = __NR_##name; \ - __asm__ __volatile__ (".set noreorder\n" \ - "subu $29, 32\n" \ - "sw %5, 16($29)\n" \ - "sw %6, 20($29)\n" \ - "syscall\n" \ - "addiu $29, 32\n" \ - ".set reorder\n" \ - : "+r"(__v0), "+r" (__r7) \ - : "r"(__r4), "r"(__r5), \ - "r"(__r6), "r" ((unsigned long)arg5), \ - "r" ((unsigned long)arg6) \ - : "$8", "$9", "$10", "$11", "$12", \ - "$13", "$14", "$15", "$24", "$25", \ - "memory"); \ - LSS_RETURN(type, __v0, __r7); \ - } - #else - #define _syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \ - type5,arg5,type6,arg6) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4, \ - type5 arg5,type6 arg6) { \ - LSS_REG(4, arg1); LSS_REG(5, arg2); LSS_REG(6, arg3); \ - LSS_REG(7, arg4); LSS_REG(8, arg5); LSS_REG(9, arg6); \ - LSS_BODY(type, name, "+r", "r"(__r4), "r"(__r5), "r"(__r6), \ - "r"(__r8), "r"(__r9)); \ - } - #endif - LSS_INLINE int LSS_NAME(clone)(int (*fn)(void *), void *child_stack, - int flags, void *arg, int *parent_tidptr, - void *newtls, int *child_tidptr) { - register unsigned long __v0 __asm__("$2"); - register unsigned long __r7 __asm__("$7") = (unsigned long)newtls; - { - register int __flags __asm__("$4") = flags; - register void *__stack __asm__("$5") = child_stack; - register void *__ptid __asm__("$6") = parent_tidptr; - register int *__ctid __asm__("$8") = child_tidptr; - __asm__ __volatile__( - #if _MIPS_SIM == _MIPS_SIM_ABI32 && _MIPS_SZPTR == 32 - "subu $29,24\n" - #elif _MIPS_SIM == _MIPS_SIM_NABI32 - "sub $29,16\n" - #else - "dsubu $29,16\n" - #endif - - /* if (fn == NULL || child_stack == NULL) - * return -EINVAL; - */ - "li %0,%2\n" - "beqz %5,1f\n" - "beqz %6,1f\n" - - /* Push "arg" and "fn" onto the stack that will be - * used by the child. - */ - #if _MIPS_SIM == _MIPS_SIM_ABI32 && _MIPS_SZPTR == 32 - "subu %6,32\n" - "sw %5,0(%6)\n" - "sw %8,4(%6)\n" - #elif _MIPS_SIM == _MIPS_SIM_NABI32 - "sub %6,32\n" - "sw %5,0(%6)\n" - "sw %8,8(%6)\n" - #else - "dsubu %6,32\n" - "sd %5,0(%6)\n" - "sd %8,8(%6)\n" - #endif - - /* $7 = syscall($4 = flags, - * $5 = child_stack, - * $6 = parent_tidptr, - * $7 = newtls, - * $8 = child_tidptr) - */ - "li $2,%3\n" - "syscall\n" - - /* if ($7 != 0) - * return $2; - */ - "bnez $7,1f\n" - "bnez $2,1f\n" - - /* In the child, now. Call "fn(arg)". - */ - #if _MIPS_SIM == _MIPS_SIM_ABI32 && _MIPS_SZPTR == 32 - "lw $25,0($29)\n" - "lw $4,4($29)\n" - #elif _MIPS_SIM == _MIPS_SIM_NABI32 - "lw $25,0($29)\n" - "lw $4,8($29)\n" - #else - "ld $25,0($29)\n" - "ld $4,8($29)\n" - #endif - "jalr $25\n" - - /* Call _exit($2) - */ - "move $4,$2\n" - "li $2,%4\n" - "syscall\n" - - "1:\n" - #if _MIPS_SIM == _MIPS_SIM_ABI32 && _MIPS_SZPTR == 32 - "addu $29, 24\n" - #elif _MIPS_SIM == _MIPS_SIM_NABI32 - "add $29, 16\n" - #else - "daddu $29,16\n" - #endif - : "+r" (__v0), "+r" (__r7) - : "i"(-EINVAL), "i"(__NR_clone), "i"(__NR_exit), - "r"(fn), "r"(__stack), "r"(__flags), "r"(arg), - "r"(__ptid), "r"(__r7), "r"(__ctid) - : "$9", "$10", "$11", "$12", "$13", "$14", "$15", - "$24", "$25", "memory"); - } - LSS_RETURN(int, __v0, __r7); - } - #elif defined (__PPC__) - #undef LSS_LOADARGS_0 - #define LSS_LOADARGS_0(name, dummy...) \ - __sc_0 = __NR_##name - #undef LSS_LOADARGS_1 - #define LSS_LOADARGS_1(name, arg1) \ - LSS_LOADARGS_0(name); \ - __sc_3 = (unsigned long) (arg1) - #undef LSS_LOADARGS_2 - #define LSS_LOADARGS_2(name, arg1, arg2) \ - LSS_LOADARGS_1(name, arg1); \ - __sc_4 = (unsigned long) (arg2) - #undef LSS_LOADARGS_3 - #define LSS_LOADARGS_3(name, arg1, arg2, arg3) \ - LSS_LOADARGS_2(name, arg1, arg2); \ - __sc_5 = (unsigned long) (arg3) - #undef LSS_LOADARGS_4 - #define LSS_LOADARGS_4(name, arg1, arg2, arg3, arg4) \ - LSS_LOADARGS_3(name, arg1, arg2, arg3); \ - __sc_6 = (unsigned long) (arg4) - #undef LSS_LOADARGS_5 - #define LSS_LOADARGS_5(name, arg1, arg2, arg3, arg4, arg5) \ - LSS_LOADARGS_4(name, arg1, arg2, arg3, arg4); \ - __sc_7 = (unsigned long) (arg5) - #undef LSS_LOADARGS_6 - #define LSS_LOADARGS_6(name, arg1, arg2, arg3, arg4, arg5, arg6) \ - LSS_LOADARGS_5(name, arg1, arg2, arg3, arg4, arg5); \ - __sc_8 = (unsigned long) (arg6) - #undef LSS_ASMINPUT_0 - #define LSS_ASMINPUT_0 "0" (__sc_0) - #undef LSS_ASMINPUT_1 - #define LSS_ASMINPUT_1 LSS_ASMINPUT_0, "1" (__sc_3) - #undef LSS_ASMINPUT_2 - #define LSS_ASMINPUT_2 LSS_ASMINPUT_1, "2" (__sc_4) - #undef LSS_ASMINPUT_3 - #define LSS_ASMINPUT_3 LSS_ASMINPUT_2, "3" (__sc_5) - #undef LSS_ASMINPUT_4 - #define LSS_ASMINPUT_4 LSS_ASMINPUT_3, "4" (__sc_6) - #undef LSS_ASMINPUT_5 - #define LSS_ASMINPUT_5 LSS_ASMINPUT_4, "5" (__sc_7) - #undef LSS_ASMINPUT_6 - #define LSS_ASMINPUT_6 LSS_ASMINPUT_5, "6" (__sc_8) - #undef LSS_BODY - #define LSS_BODY(nr, type, name, args...) \ - long __sc_ret, __sc_err; \ - { \ - register unsigned long __sc_0 __asm__ ("r0"); \ - register unsigned long __sc_3 __asm__ ("r3"); \ - register unsigned long __sc_4 __asm__ ("r4"); \ - register unsigned long __sc_5 __asm__ ("r5"); \ - register unsigned long __sc_6 __asm__ ("r6"); \ - register unsigned long __sc_7 __asm__ ("r7"); \ - register unsigned long __sc_8 __asm__ ("r8"); \ - \ - LSS_LOADARGS_##nr(name, args); \ - __asm__ __volatile__ \ - ("sc\n\t" \ - "mfcr %0" \ - : "=&r" (__sc_0), \ - "=&r" (__sc_3), "=&r" (__sc_4), \ - "=&r" (__sc_5), "=&r" (__sc_6), \ - "=&r" (__sc_7), "=&r" (__sc_8) \ - : LSS_ASMINPUT_##nr \ - : "cr0", "ctr", "memory", \ - "r9", "r10", "r11", "r12"); \ - __sc_ret = __sc_3; \ - __sc_err = __sc_0; \ - } \ - LSS_RETURN(type, __sc_ret, __sc_err) - #undef _syscall0 - #define _syscall0(type, name) \ - type LSS_NAME(name)(void) { \ - LSS_BODY(0, type, name); \ - } - #undef _syscall1 - #define _syscall1(type, name, type1, arg1) \ - type LSS_NAME(name)(type1 arg1) { \ - LSS_BODY(1, type, name, arg1); \ - } - #undef _syscall2 - #define _syscall2(type, name, type1, arg1, type2, arg2) \ - type LSS_NAME(name)(type1 arg1, type2 arg2) { \ - LSS_BODY(2, type, name, arg1, arg2); \ - } - #undef _syscall3 - #define _syscall3(type, name, type1, arg1, type2, arg2, type3, arg3) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3) { \ - LSS_BODY(3, type, name, arg1, arg2, arg3); \ - } - #undef _syscall4 - #define _syscall4(type, name, type1, arg1, type2, arg2, type3, arg3, \ - type4, arg4) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4) { \ - LSS_BODY(4, type, name, arg1, arg2, arg3, arg4); \ - } - #undef _syscall5 - #define _syscall5(type, name, type1, arg1, type2, arg2, type3, arg3, \ - type4, arg4, type5, arg5) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4, \ - type5 arg5) { \ - LSS_BODY(5, type, name, arg1, arg2, arg3, arg4, arg5); \ - } - #undef _syscall6 - #define _syscall6(type, name, type1, arg1, type2, arg2, type3, arg3, \ - type4, arg4, type5, arg5, type6, arg6) \ - type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4, \ - type5 arg5, type6 arg6) { \ - LSS_BODY(6, type, name, arg1, arg2, arg3, arg4, arg5, arg6); \ - } - /* clone function adapted from glibc 2.3.6 clone.S */ - /* TODO(csilvers): consider wrapping some args up in a struct, like we - * do for i386's _syscall6, so we can compile successfully on gcc 2.95 - */ - LSS_INLINE int LSS_NAME(clone)(int (*fn)(void *), void *child_stack, - int flags, void *arg, int *parent_tidptr, - void *newtls, int *child_tidptr) { - long __ret, __err; - { - register int (*__fn)(void *) __asm__ ("r8") = fn; - register void *__cstack __asm__ ("r4") = child_stack; - register int __flags __asm__ ("r3") = flags; - register void * __arg __asm__ ("r9") = arg; - register int * __ptidptr __asm__ ("r5") = parent_tidptr; - register void * __newtls __asm__ ("r6") = newtls; - register int * __ctidptr __asm__ ("r7") = child_tidptr; - __asm__ __volatile__( - /* check for fn == NULL - * and child_stack == NULL - */ - "cmpwi cr0, %6, 0\n\t" - "cmpwi cr1, %7, 0\n\t" - "cror cr0*4+eq, cr1*4+eq, cr0*4+eq\n\t" - "beq- cr0, 1f\n\t" - - /* set up stack frame for child */ - "clrrwi %7, %7, 4\n\t" - "li 0, 0\n\t" - "stwu 0, -16(%7)\n\t" - - /* fn, arg, child_stack are saved across the syscall: r28-30 */ - "mr 28, %6\n\t" - "mr 29, %7\n\t" - "mr 27, %9\n\t" - - /* syscall */ - "li 0, %4\n\t" - /* flags already in r3 - * child_stack already in r4 - * ptidptr already in r5 - * newtls already in r6 - * ctidptr already in r7 - */ - "sc\n\t" - - /* Test if syscall was successful */ - "cmpwi cr1, 3, 0\n\t" - "crandc cr1*4+eq, cr1*4+eq, cr0*4+so\n\t" - "bne- cr1, 1f\n\t" - - /* Do the function call */ - "mtctr 28\n\t" - "mr 3, 27\n\t" - "bctrl\n\t" - - /* Call _exit(r3) */ - "li 0, %5\n\t" - "sc\n\t" - - /* Return to parent */ - "1:\n" - "mfcr %1\n\t" - "mr %0, 3\n\t" - : "=r" (__ret), "=r" (__err) - : "0" (-1), "1" (EINVAL), - "i" (__NR_clone), "i" (__NR_exit), - "r" (__fn), "r" (__cstack), "r" (__flags), - "r" (__arg), "r" (__ptidptr), "r" (__newtls), - "r" (__ctidptr) - : "cr0", "cr1", "memory", "ctr", - "r0", "r29", "r27", "r28"); - } - LSS_RETURN(int, __ret, __err); - } - #endif - #define __NR__exit __NR_exit - #define __NR__gettid __NR_gettid - #define __NR__mremap __NR_mremap - LSS_INLINE _syscall1(void *, brk, void *, e) - LSS_INLINE _syscall1(int, chdir, const char *,p) - LSS_INLINE _syscall1(int, close, int, f) - LSS_INLINE _syscall2(int, clock_getres, int, c, - struct kernel_timespec*, t) - LSS_INLINE _syscall2(int, clock_gettime, int, c, - struct kernel_timespec*, t) - LSS_INLINE _syscall1(int, dup, int, f) - #if !defined(__aarch64__) - // The dup2 syscall has been deprecated on aarch64. We polyfill it below. - LSS_INLINE _syscall2(int, dup2, int, s, - int, d) - #endif - LSS_INLINE _syscall3(int, execve, const char*, f, - const char*const*,a,const char*const*, e) - LSS_INLINE _syscall1(int, _exit, int, e) - LSS_INLINE _syscall1(int, exit_group, int, e) - LSS_INLINE _syscall3(int, fcntl, int, f, - int, c, long, a) - #if !defined(__aarch64__) - // The fork syscall has been deprecated on aarch64. We polyfill it below. - LSS_INLINE _syscall0(pid_t, fork) - #endif - LSS_INLINE _syscall2(int, fstat, int, f, - struct kernel_stat*, b) - LSS_INLINE _syscall2(int, fstatfs, int, f, - struct kernel_statfs*, b) - #if defined(__x86_64__) - /* Need to make sure off_t isn't truncated to 32-bits under x32. */ - LSS_INLINE int LSS_NAME(ftruncate)(int f, off_t l) { - LSS_BODY(2, int, ftruncate, LSS_SYSCALL_ARG(f), (uint64_t)(l)); - } - #else - LSS_INLINE _syscall2(int, ftruncate, int, f, - off_t, l) - #endif - LSS_INLINE _syscall4(int, futex, int*, a, - int, o, int, v, - struct kernel_timespec*, t) - LSS_INLINE _syscall3(int, getdents, int, f, - struct kernel_dirent*, d, int, c) - LSS_INLINE _syscall3(int, getdents64, int, f, - struct kernel_dirent64*, d, int, c) - LSS_INLINE _syscall0(gid_t, getegid) - LSS_INLINE _syscall0(uid_t, geteuid) - #if !defined(__aarch64__) - // The getgprp syscall has been deprecated on aarch64. - LSS_INLINE _syscall0(pid_t, getpgrp) - #endif - LSS_INLINE _syscall0(pid_t, getpid) - LSS_INLINE _syscall0(pid_t, getppid) - LSS_INLINE _syscall2(int, getpriority, int, a, - int, b) - LSS_INLINE _syscall3(int, getresgid, gid_t *, r, - gid_t *, e, gid_t *, s) - LSS_INLINE _syscall3(int, getresuid, uid_t *, r, - uid_t *, e, uid_t *, s) -#if !defined(__ARM_EABI__) - LSS_INLINE _syscall2(int, getrlimit, int, r, - struct kernel_rlimit*, l) -#endif - LSS_INLINE _syscall1(pid_t, getsid, pid_t, p) - LSS_INLINE _syscall0(pid_t, _gettid) - LSS_INLINE _syscall2(pid_t, gettimeofday, struct kernel_timeval*, t, - void*, tz) - LSS_INLINE _syscall5(int, setxattr, const char *,p, - const char *, n, const void *,v, - size_t, s, int, f) - LSS_INLINE _syscall5(int, lsetxattr, const char *,p, - const char *, n, const void *,v, - size_t, s, int, f) - LSS_INLINE _syscall4(ssize_t, getxattr, const char *,p, - const char *, n, void *, v, size_t, s) - LSS_INLINE _syscall4(ssize_t, lgetxattr, const char *,p, - const char *, n, void *, v, size_t, s) - LSS_INLINE _syscall3(ssize_t, listxattr, const char *,p, - char *, l, size_t, s) - LSS_INLINE _syscall3(ssize_t, llistxattr, const char *,p, - char *, l, size_t, s) - LSS_INLINE _syscall3(int, ioctl, int, d, - int, r, void *, a) - LSS_INLINE _syscall2(int, ioprio_get, int, which, - int, who) - LSS_INLINE _syscall3(int, ioprio_set, int, which, - int, who, int, ioprio) - LSS_INLINE _syscall2(int, kill, pid_t, p, - int, s) - #if defined(__x86_64__) - /* Need to make sure off_t isn't truncated to 32-bits under x32. */ - LSS_INLINE off_t LSS_NAME(lseek)(int f, off_t o, int w) { - _LSS_BODY(3, off_t, lseek, off_t, LSS_SYSCALL_ARG(f), (uint64_t)(o), - LSS_SYSCALL_ARG(w)); - } - #else - LSS_INLINE _syscall3(off_t, lseek, int, f, - off_t, o, int, w) - #endif - LSS_INLINE _syscall2(int, munmap, void*, s, - size_t, l) - LSS_INLINE _syscall6(long, move_pages, pid_t, p, - unsigned long, n, void **,g, int *, d, - int *, s, int, f) - LSS_INLINE _syscall3(int, mprotect, const void *,a, - size_t, l, int, p) - LSS_INLINE _syscall5(void*, _mremap, void*, o, - size_t, os, size_t, ns, - unsigned long, f, void *, a) - #if !defined(__aarch64__) - // The open and poll syscalls have been deprecated on aarch64. We polyfill - // them below. - LSS_INLINE _syscall3(int, open, const char*, p, - int, f, int, m) - LSS_INLINE _syscall3(int, poll, struct kernel_pollfd*, u, - unsigned int, n, int, t) - #endif - LSS_INLINE _syscall5(int, prctl, int, option, - unsigned long, arg2, - unsigned long, arg3, - unsigned long, arg4, - unsigned long, arg5) - LSS_INLINE _syscall4(long, ptrace, int, r, - pid_t, p, void *, a, void *, d) - #if defined(__NR_quotactl) - // Defined on x86_64 / i386 only - LSS_INLINE _syscall4(int, quotactl, int, cmd, const char *, special, - int, id, caddr_t, addr) - #endif - LSS_INLINE _syscall3(ssize_t, read, int, f, - void *, b, size_t, c) - #if !defined(__aarch64__) - // The readlink syscall has been deprecated on aarch64. We polyfill below. - LSS_INLINE _syscall3(int, readlink, const char*, p, - char*, b, size_t, s) - #endif - LSS_INLINE _syscall4(int, rt_sigaction, int, s, - const struct kernel_sigaction*, a, - struct kernel_sigaction*, o, size_t, c) - LSS_INLINE _syscall2(int, rt_sigpending, struct kernel_sigset_t *, s, - size_t, c) - LSS_INLINE _syscall4(int, rt_sigprocmask, int, h, - const struct kernel_sigset_t*, s, - struct kernel_sigset_t*, o, size_t, c) - LSS_INLINE _syscall2(int, rt_sigsuspend, - const struct kernel_sigset_t*, s, size_t, c) - LSS_INLINE _syscall3(int, sched_getaffinity,pid_t, p, - unsigned int, l, unsigned long *, m) - LSS_INLINE _syscall3(int, sched_setaffinity,pid_t, p, - unsigned int, l, unsigned long *, m) - LSS_INLINE _syscall0(int, sched_yield) - LSS_INLINE _syscall1(long, set_tid_address, int *, t) - LSS_INLINE _syscall1(int, setfsgid, gid_t, g) - LSS_INLINE _syscall1(int, setfsuid, uid_t, u) - LSS_INLINE _syscall1(int, setuid, uid_t, u) - LSS_INLINE _syscall1(int, setgid, gid_t, g) - LSS_INLINE _syscall2(int, setpgid, pid_t, p, - pid_t, g) - LSS_INLINE _syscall3(int, setpriority, int, a, - int, b, int, p) - LSS_INLINE _syscall3(int, setresgid, gid_t, r, - gid_t, e, gid_t, s) - LSS_INLINE _syscall3(int, setresuid, uid_t, r, - uid_t, e, uid_t, s) - LSS_INLINE _syscall2(int, setrlimit, int, r, - const struct kernel_rlimit*, l) - LSS_INLINE _syscall0(pid_t, setsid) - LSS_INLINE _syscall2(int, sigaltstack, const stack_t*, s, - const stack_t*, o) - #if defined(__NR_sigreturn) - LSS_INLINE _syscall1(int, sigreturn, unsigned long, u) - #endif - #if !defined(__aarch64__) - // The stat syscall has been deprecated on aarch64. We polyfill it below. - LSS_INLINE _syscall2(int, stat, const char*, f, - struct kernel_stat*, b) - #endif - LSS_INLINE _syscall2(int, statfs, const char*, f, - struct kernel_statfs*, b) - LSS_INLINE _syscall3(int, tgkill, pid_t, p, - pid_t, t, int, s) - LSS_INLINE _syscall2(int, tkill, pid_t, p, - int, s) - #if !defined(__aarch64__) - // The unlink syscall has been deprecated on aarch64. We polyfill it below. - LSS_INLINE _syscall1(int, unlink, const char*, f) - #endif - LSS_INLINE _syscall3(ssize_t, write, int, f, - const void *, b, size_t, c) - LSS_INLINE _syscall3(ssize_t, writev, int, f, - const struct kernel_iovec*, v, size_t, c) - #if defined(__NR_getcpu) - LSS_INLINE _syscall3(long, getcpu, unsigned *, cpu, - unsigned *, node, void *, unused) - #endif - #if defined(__x86_64__) || \ - (defined(__mips__) && _MIPS_SIM != _MIPS_SIM_ABI32) - LSS_INLINE _syscall3(int, recvmsg, int, s, - struct kernel_msghdr*, m, int, f) - LSS_INLINE _syscall3(int, sendmsg, int, s, - const struct kernel_msghdr*, m, int, f) - LSS_INLINE _syscall6(int, sendto, int, s, - const void*, m, size_t, l, - int, f, - const struct kernel_sockaddr*, a, int, t) - LSS_INLINE _syscall2(int, shutdown, int, s, - int, h) - LSS_INLINE _syscall3(int, socket, int, d, - int, t, int, p) - LSS_INLINE _syscall4(int, socketpair, int, d, - int, t, int, p, int*, s) - #endif - #if defined(__x86_64__) - /* Need to make sure loff_t isn't truncated to 32-bits under x32. */ - LSS_INLINE int LSS_NAME(fallocate)(int f, int mode, loff_t offset, - loff_t len) { - LSS_BODY(4, int, fallocate, LSS_SYSCALL_ARG(f), LSS_SYSCALL_ARG(mode), - (uint64_t)(offset), (uint64_t)(len)); - } - - LSS_INLINE int LSS_NAME(getresgid32)(gid_t *rgid, - gid_t *egid, - gid_t *sgid) { - return LSS_NAME(getresgid)(rgid, egid, sgid); - } - - LSS_INLINE int LSS_NAME(getresuid32)(uid_t *ruid, - uid_t *euid, - uid_t *suid) { - return LSS_NAME(getresuid)(ruid, euid, suid); - } - - /* Need to make sure __off64_t isn't truncated to 32-bits under x32. */ - LSS_INLINE void* LSS_NAME(mmap)(void *s, size_t l, int p, int f, int d, - int64_t o) { - LSS_BODY(6, void*, mmap, LSS_SYSCALL_ARG(s), LSS_SYSCALL_ARG(l), - LSS_SYSCALL_ARG(p), LSS_SYSCALL_ARG(f), - LSS_SYSCALL_ARG(d), (uint64_t)(o)); - } - - LSS_INLINE _syscall4(int, newfstatat, int, d, - const char *, p, - struct kernel_stat*, b, int, f) - - LSS_INLINE int LSS_NAME(setfsgid32)(gid_t gid) { - return LSS_NAME(setfsgid)(gid); - } - - LSS_INLINE int LSS_NAME(setfsuid32)(uid_t uid) { - return LSS_NAME(setfsuid)(uid); - } - - LSS_INLINE int LSS_NAME(setresgid32)(gid_t rgid, gid_t egid, gid_t sgid) { - return LSS_NAME(setresgid)(rgid, egid, sgid); - } - - LSS_INLINE int LSS_NAME(setresuid32)(uid_t ruid, uid_t euid, uid_t suid) { - return LSS_NAME(setresuid)(ruid, euid, suid); - } - - LSS_INLINE int LSS_NAME(sigaction)(int signum, - const struct kernel_sigaction *act, - struct kernel_sigaction *oldact) { - /* On x86_64, the kernel requires us to always set our own - * SA_RESTORER in order to be able to return from a signal handler. - * This function must have a "magic" signature that the "gdb" - * (and maybe the kernel?) can recognize. - */ - if (act != NULL && !(act->sa_flags & SA_RESTORER)) { - struct kernel_sigaction a = *act; - a.sa_flags |= SA_RESTORER; - a.sa_restorer = LSS_NAME(restore_rt)(); - return LSS_NAME(rt_sigaction)(signum, &a, oldact, - (KERNEL_NSIG+7)/8); - } else { - return LSS_NAME(rt_sigaction)(signum, act, oldact, - (KERNEL_NSIG+7)/8); - } - } - - LSS_INLINE int LSS_NAME(sigpending)(struct kernel_sigset_t *set) { - return LSS_NAME(rt_sigpending)(set, (KERNEL_NSIG+7)/8); - } - - LSS_INLINE int LSS_NAME(sigprocmask)(int how, - const struct kernel_sigset_t *set, - struct kernel_sigset_t *oldset) { - return LSS_NAME(rt_sigprocmask)(how, set, oldset, (KERNEL_NSIG+7)/8); - } - - LSS_INLINE int LSS_NAME(sigsuspend)(const struct kernel_sigset_t *set) { - return LSS_NAME(rt_sigsuspend)(set, (KERNEL_NSIG+7)/8); - } - #endif - #if defined(__x86_64__) || defined(__ARM_ARCH_3__) || \ - defined(__ARM_EABI__) || defined(__aarch64__) || \ - (defined(__mips__) && _MIPS_SIM != _MIPS_SIM_ABI32) - LSS_INLINE _syscall4(pid_t, wait4, pid_t, p, - int*, s, int, o, - struct kernel_rusage*, r) - - LSS_INLINE pid_t LSS_NAME(waitpid)(pid_t pid, int *status, int options){ - return LSS_NAME(wait4)(pid, status, options, 0); - } - #endif - #if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) - LSS_INLINE _syscall4(int, openat, int, d, const char *, p, int, f, int, m) - LSS_INLINE _syscall3(int, unlinkat, int, d, const char *, p, int, f) - #endif - #if defined(__i386__) || defined(__ARM_ARCH_3__) || defined(__ARM_EABI__) - #define __NR__getresgid32 __NR_getresgid32 - #define __NR__getresuid32 __NR_getresuid32 - #define __NR__setfsgid32 __NR_setfsgid32 - #define __NR__setfsuid32 __NR_setfsuid32 - #define __NR__setresgid32 __NR_setresgid32 - #define __NR__setresuid32 __NR_setresuid32 -#if defined(__ARM_EABI__) - LSS_INLINE _syscall2(int, ugetrlimit, int, r, - struct kernel_rlimit*, l) -#endif - LSS_INLINE _syscall3(int, _getresgid32, gid_t *, r, - gid_t *, e, gid_t *, s) - LSS_INLINE _syscall3(int, _getresuid32, uid_t *, r, - uid_t *, e, uid_t *, s) - LSS_INLINE _syscall1(int, _setfsgid32, gid_t, f) - LSS_INLINE _syscall1(int, _setfsuid32, uid_t, f) - LSS_INLINE _syscall3(int, _setresgid32, gid_t, r, - gid_t, e, gid_t, s) - LSS_INLINE _syscall3(int, _setresuid32, uid_t, r, - uid_t, e, uid_t, s) - - LSS_INLINE int LSS_NAME(getresgid32)(gid_t *rgid, - gid_t *egid, - gid_t *sgid) { - int rc; - if ((rc = LSS_NAME(_getresgid32)(rgid, egid, sgid)) < 0 && - LSS_ERRNO == ENOSYS) { - if ((rgid == NULL) || (egid == NULL) || (sgid == NULL)) { - return EFAULT; - } - // Clear the high bits first, since getresgid only sets 16 bits - *rgid = *egid = *sgid = 0; - rc = LSS_NAME(getresgid)(rgid, egid, sgid); - } - return rc; - } - - LSS_INLINE int LSS_NAME(getresuid32)(uid_t *ruid, - uid_t *euid, - uid_t *suid) { - int rc; - if ((rc = LSS_NAME(_getresuid32)(ruid, euid, suid)) < 0 && - LSS_ERRNO == ENOSYS) { - if ((ruid == NULL) || (euid == NULL) || (suid == NULL)) { - return EFAULT; - } - // Clear the high bits first, since getresuid only sets 16 bits - *ruid = *euid = *suid = 0; - rc = LSS_NAME(getresuid)(ruid, euid, suid); - } - return rc; - } - - LSS_INLINE int LSS_NAME(setfsgid32)(gid_t gid) { - int rc; - if ((rc = LSS_NAME(_setfsgid32)(gid)) < 0 && - LSS_ERRNO == ENOSYS) { - if ((unsigned int)gid & ~0xFFFFu) { - rc = EINVAL; - } else { - rc = LSS_NAME(setfsgid)(gid); - } - } - return rc; - } - - LSS_INLINE int LSS_NAME(setfsuid32)(uid_t uid) { - int rc; - if ((rc = LSS_NAME(_setfsuid32)(uid)) < 0 && - LSS_ERRNO == ENOSYS) { - if ((unsigned int)uid & ~0xFFFFu) { - rc = EINVAL; - } else { - rc = LSS_NAME(setfsuid)(uid); - } - } - return rc; - } - - LSS_INLINE int LSS_NAME(setresgid32)(gid_t rgid, gid_t egid, gid_t sgid) { - int rc; - if ((rc = LSS_NAME(_setresgid32)(rgid, egid, sgid)) < 0 && - LSS_ERRNO == ENOSYS) { - if ((unsigned int)rgid & ~0xFFFFu || - (unsigned int)egid & ~0xFFFFu || - (unsigned int)sgid & ~0xFFFFu) { - rc = EINVAL; - } else { - rc = LSS_NAME(setresgid)(rgid, egid, sgid); - } - } - return rc; - } - - LSS_INLINE int LSS_NAME(setresuid32)(uid_t ruid, uid_t euid, uid_t suid) { - int rc; - if ((rc = LSS_NAME(_setresuid32)(ruid, euid, suid)) < 0 && - LSS_ERRNO == ENOSYS) { - if ((unsigned int)ruid & ~0xFFFFu || - (unsigned int)euid & ~0xFFFFu || - (unsigned int)suid & ~0xFFFFu) { - rc = EINVAL; - } else { - rc = LSS_NAME(setresuid)(ruid, euid, suid); - } - } - return rc; - } - #endif - LSS_INLINE int LSS_NAME(sigemptyset)(struct kernel_sigset_t *set) { - memset(&set->sig, 0, sizeof(set->sig)); - return 0; - } - - LSS_INLINE int LSS_NAME(sigfillset)(struct kernel_sigset_t *set) { - memset(&set->sig, -1, sizeof(set->sig)); - return 0; - } - - LSS_INLINE int LSS_NAME(sigaddset)(struct kernel_sigset_t *set, - int signum) { - if (signum < 1 || signum > (int)(8*sizeof(set->sig))) { - LSS_ERRNO = EINVAL; - return -1; - } else { - set->sig[(signum - 1)/(8*sizeof(set->sig[0]))] - |= 1UL << ((signum - 1) % (8*sizeof(set->sig[0]))); - return 0; - } - } - - LSS_INLINE int LSS_NAME(sigdelset)(struct kernel_sigset_t *set, - int signum) { - if (signum < 1 || signum > (int)(8*sizeof(set->sig))) { - LSS_ERRNO = EINVAL; - return -1; - } else { - set->sig[(signum - 1)/(8*sizeof(set->sig[0]))] - &= ~(1UL << ((signum - 1) % (8*sizeof(set->sig[0])))); - return 0; - } - } - - LSS_INLINE int LSS_NAME(sigismember)(struct kernel_sigset_t *set, - int signum) { - if (signum < 1 || signum > (int)(8*sizeof(set->sig))) { - LSS_ERRNO = EINVAL; - return -1; - } else { - return !!(set->sig[(signum - 1)/(8*sizeof(set->sig[0]))] & - (1UL << ((signum - 1) % (8*sizeof(set->sig[0]))))); - } - } - #if defined(__i386__) || defined(__ARM_ARCH_3__) || \ - defined(__ARM_EABI__) || \ - (defined(__mips__) && _MIPS_SIM == _MIPS_SIM_ABI32) || defined(__PPC__) - #define __NR__sigaction __NR_sigaction - #define __NR__sigpending __NR_sigpending - #define __NR__sigprocmask __NR_sigprocmask - #define __NR__sigsuspend __NR_sigsuspend - #define __NR__socketcall __NR_socketcall - LSS_INLINE _syscall2(int, fstat64, int, f, - struct kernel_stat64 *, b) - LSS_INLINE _syscall5(int, _llseek, uint, fd, - unsigned long, hi, unsigned long, lo, - loff_t *, res, uint, wh) -#if !defined(__ARM_EABI__) - LSS_INLINE _syscall1(void*, mmap, void*, a) -#endif - LSS_INLINE _syscall6(void*, mmap2, void*, s, - size_t, l, int, p, - int, f, int, d, - off_t, o) - LSS_INLINE _syscall3(int, _sigaction, int, s, - const struct kernel_old_sigaction*, a, - struct kernel_old_sigaction*, o) - LSS_INLINE _syscall1(int, _sigpending, unsigned long*, s) - LSS_INLINE _syscall3(int, _sigprocmask, int, h, - const unsigned long*, s, - unsigned long*, o) - #ifdef __PPC__ - LSS_INLINE _syscall1(int, _sigsuspend, unsigned long, s) - #else - LSS_INLINE _syscall3(int, _sigsuspend, const void*, a, - int, b, - unsigned long, s) - #endif - LSS_INLINE _syscall2(int, stat64, const char *, p, - struct kernel_stat64 *, b) - - LSS_INLINE int LSS_NAME(sigaction)(int signum, - const struct kernel_sigaction *act, - struct kernel_sigaction *oldact) { - int old_errno = LSS_ERRNO; - int rc; - struct kernel_sigaction a; - if (act != NULL) { - a = *act; - #ifdef __i386__ - /* On i386, the kernel requires us to always set our own - * SA_RESTORER when using realtime signals. Otherwise, it does not - * know how to return from a signal handler. This function must have - * a "magic" signature that the "gdb" (and maybe the kernel?) can - * recognize. - * Apparently, a SA_RESTORER is implicitly set by the kernel, when - * using non-realtime signals. - * - * TODO: Test whether ARM needs a restorer - */ - if (!(a.sa_flags & SA_RESTORER)) { - a.sa_flags |= SA_RESTORER; - a.sa_restorer = (a.sa_flags & SA_SIGINFO) - ? LSS_NAME(restore_rt)() : LSS_NAME(restore)(); - } - #endif - } - rc = LSS_NAME(rt_sigaction)(signum, act ? &a : act, oldact, - (KERNEL_NSIG+7)/8); - if (rc < 0 && LSS_ERRNO == ENOSYS) { - struct kernel_old_sigaction oa, ooa, *ptr_a = &oa, *ptr_oa = &ooa; - if (!act) { - ptr_a = NULL; - } else { - oa.sa_handler_ = act->sa_handler_; - memcpy(&oa.sa_mask, &act->sa_mask, sizeof(oa.sa_mask)); - #ifndef __mips__ - oa.sa_restorer = act->sa_restorer; - #endif - oa.sa_flags = act->sa_flags; - } - if (!oldact) { - ptr_oa = NULL; - } - LSS_ERRNO = old_errno; - rc = LSS_NAME(_sigaction)(signum, ptr_a, ptr_oa); - if (rc == 0 && oldact) { - if (act) { - memcpy(oldact, act, sizeof(*act)); - } else { - memset(oldact, 0, sizeof(*oldact)); - } - oldact->sa_handler_ = ptr_oa->sa_handler_; - oldact->sa_flags = ptr_oa->sa_flags; - memcpy(&oldact->sa_mask, &ptr_oa->sa_mask, sizeof(ptr_oa->sa_mask)); - #ifndef __mips__ - oldact->sa_restorer = ptr_oa->sa_restorer; - #endif - } - } - return rc; - } - - LSS_INLINE int LSS_NAME(sigpending)(struct kernel_sigset_t *set) { - int old_errno = LSS_ERRNO; - int rc = LSS_NAME(rt_sigpending)(set, (KERNEL_NSIG+7)/8); - if (rc < 0 && LSS_ERRNO == ENOSYS) { - LSS_ERRNO = old_errno; - LSS_NAME(sigemptyset)(set); - rc = LSS_NAME(_sigpending)(&set->sig[0]); - } - return rc; - } - - LSS_INLINE int LSS_NAME(sigprocmask)(int how, - const struct kernel_sigset_t *set, - struct kernel_sigset_t *oldset) { - int olderrno = LSS_ERRNO; - int rc = LSS_NAME(rt_sigprocmask)(how, set, oldset, (KERNEL_NSIG+7)/8); - if (rc < 0 && LSS_ERRNO == ENOSYS) { - LSS_ERRNO = olderrno; - if (oldset) { - LSS_NAME(sigemptyset)(oldset); - } - rc = LSS_NAME(_sigprocmask)(how, - set ? &set->sig[0] : NULL, - oldset ? &oldset->sig[0] : NULL); - } - return rc; - } - - LSS_INLINE int LSS_NAME(sigsuspend)(const struct kernel_sigset_t *set) { - int olderrno = LSS_ERRNO; - int rc = LSS_NAME(rt_sigsuspend)(set, (KERNEL_NSIG+7)/8); - if (rc < 0 && LSS_ERRNO == ENOSYS) { - LSS_ERRNO = olderrno; - rc = LSS_NAME(_sigsuspend)( - #ifndef __PPC__ - set, 0, - #endif - set->sig[0]); - } - return rc; - } - #endif - #if defined(__PPC__) - #undef LSS_SC_LOADARGS_0 - #define LSS_SC_LOADARGS_0(dummy...) - #undef LSS_SC_LOADARGS_1 - #define LSS_SC_LOADARGS_1(arg1) \ - __sc_4 = (unsigned long) (arg1) - #undef LSS_SC_LOADARGS_2 - #define LSS_SC_LOADARGS_2(arg1, arg2) \ - LSS_SC_LOADARGS_1(arg1); \ - __sc_5 = (unsigned long) (arg2) - #undef LSS_SC_LOADARGS_3 - #define LSS_SC_LOADARGS_3(arg1, arg2, arg3) \ - LSS_SC_LOADARGS_2(arg1, arg2); \ - __sc_6 = (unsigned long) (arg3) - #undef LSS_SC_LOADARGS_4 - #define LSS_SC_LOADARGS_4(arg1, arg2, arg3, arg4) \ - LSS_SC_LOADARGS_3(arg1, arg2, arg3); \ - __sc_7 = (unsigned long) (arg4) - #undef LSS_SC_LOADARGS_5 - #define LSS_SC_LOADARGS_5(arg1, arg2, arg3, arg4, arg5) \ - LSS_SC_LOADARGS_4(arg1, arg2, arg3, arg4); \ - __sc_8 = (unsigned long) (arg5) - #undef LSS_SC_BODY - #define LSS_SC_BODY(nr, type, opt, args...) \ - long __sc_ret, __sc_err; \ - { \ - register unsigned long __sc_0 __asm__ ("r0") = __NR_socketcall; \ - register unsigned long __sc_3 __asm__ ("r3") = opt; \ - register unsigned long __sc_4 __asm__ ("r4"); \ - register unsigned long __sc_5 __asm__ ("r5"); \ - register unsigned long __sc_6 __asm__ ("r6"); \ - register unsigned long __sc_7 __asm__ ("r7"); \ - register unsigned long __sc_8 __asm__ ("r8"); \ - LSS_SC_LOADARGS_##nr(args); \ - __asm__ __volatile__ \ - ("stwu 1, -48(1)\n\t" \ - "stw 4, 20(1)\n\t" \ - "stw 5, 24(1)\n\t" \ - "stw 6, 28(1)\n\t" \ - "stw 7, 32(1)\n\t" \ - "stw 8, 36(1)\n\t" \ - "addi 4, 1, 20\n\t" \ - "sc\n\t" \ - "mfcr %0" \ - : "=&r" (__sc_0), \ - "=&r" (__sc_3), "=&r" (__sc_4), \ - "=&r" (__sc_5), "=&r" (__sc_6), \ - "=&r" (__sc_7), "=&r" (__sc_8) \ - : LSS_ASMINPUT_##nr \ - : "cr0", "ctr", "memory"); \ - __sc_ret = __sc_3; \ - __sc_err = __sc_0; \ - } \ - LSS_RETURN(type, __sc_ret, __sc_err) - - LSS_INLINE ssize_t LSS_NAME(recvmsg)(int s,struct kernel_msghdr *msg, - int flags){ - LSS_SC_BODY(3, ssize_t, 17, s, msg, flags); - } - - LSS_INLINE ssize_t LSS_NAME(sendmsg)(int s, - const struct kernel_msghdr *msg, - int flags) { - LSS_SC_BODY(3, ssize_t, 16, s, msg, flags); - } - - // TODO(csilvers): why is this ifdef'ed out? -#if 0 - LSS_INLINE ssize_t LSS_NAME(sendto)(int s, const void *buf, size_t len, - int flags, - const struct kernel_sockaddr *to, - unsigned int tolen) { - LSS_BODY(6, ssize_t, 11, s, buf, len, flags, to, tolen); - } -#endif - - LSS_INLINE int LSS_NAME(shutdown)(int s, int how) { - LSS_SC_BODY(2, int, 13, s, how); - } - - LSS_INLINE int LSS_NAME(socket)(int domain, int type, int protocol) { - LSS_SC_BODY(3, int, 1, domain, type, protocol); - } - - LSS_INLINE int LSS_NAME(socketpair)(int d, int type, int protocol, - int sv[2]) { - LSS_SC_BODY(4, int, 8, d, type, protocol, sv); - } - #endif - #if defined(__ARM_EABI__) || defined (__aarch64__) - LSS_INLINE _syscall3(ssize_t, recvmsg, int, s, struct kernel_msghdr*, msg, - int, flags) - LSS_INLINE _syscall3(ssize_t, sendmsg, int, s, const struct kernel_msghdr*, - msg, int, flags) - LSS_INLINE _syscall6(ssize_t, sendto, int, s, const void*, buf, size_t,len, - int, flags, const struct kernel_sockaddr*, to, - unsigned int, tolen) - LSS_INLINE _syscall2(int, shutdown, int, s, int, how) - LSS_INLINE _syscall3(int, socket, int, domain, int, type, int, protocol) - LSS_INLINE _syscall4(int, socketpair, int, d, int, type, int, protocol, - int*, sv) - #endif - #if defined(__i386__) || defined(__ARM_ARCH_3__) || \ - (defined(__mips__) && _MIPS_SIM == _MIPS_SIM_ABI32) - #define __NR__socketcall __NR_socketcall - LSS_INLINE _syscall2(int, _socketcall, int, c, - va_list, a) - LSS_INLINE int LSS_NAME(socketcall)(int op, ...) { - int rc; - va_list ap; - va_start(ap, op); - rc = LSS_NAME(_socketcall)(op, ap); - va_end(ap); - return rc; - } - - LSS_INLINE ssize_t LSS_NAME(recvmsg)(int s,struct kernel_msghdr *msg, - int flags){ - return (ssize_t)LSS_NAME(socketcall)(17, s, msg, flags); - } - - LSS_INLINE ssize_t LSS_NAME(sendmsg)(int s, - const struct kernel_msghdr *msg, - int flags) { - return (ssize_t)LSS_NAME(socketcall)(16, s, msg, flags); - } - - LSS_INLINE ssize_t LSS_NAME(sendto)(int s, const void *buf, size_t len, - int flags, - const struct kernel_sockaddr *to, - unsigned int tolen) { - return (ssize_t)LSS_NAME(socketcall)(11, s, buf, len, flags, to, tolen); - } - - LSS_INLINE int LSS_NAME(shutdown)(int s, int how) { - return LSS_NAME(socketcall)(13, s, how); - } - - LSS_INLINE int LSS_NAME(socket)(int domain, int type, int protocol) { - return LSS_NAME(socketcall)(1, domain, type, protocol); - } - - LSS_INLINE int LSS_NAME(socketpair)(int d, int type, int protocol, - int sv[2]) { - return LSS_NAME(socketcall)(8, d, type, protocol, sv); - } - #endif - #if defined(__i386__) || defined(__PPC__) - LSS_INLINE _syscall4(int, fstatat64, int, d, - const char *, p, - struct kernel_stat64 *, b, int, f) - #endif - #if defined(__i386__) || defined(__PPC__) || \ - (defined(__mips__) && _MIPS_SIM == _MIPS_SIM_ABI32) - LSS_INLINE _syscall3(pid_t, waitpid, pid_t, p, - int*, s, int, o) - #endif - #if defined(__mips__) - /* sys_pipe() on MIPS has non-standard calling conventions, as it returns - * both file handles through CPU registers. - */ - LSS_INLINE int LSS_NAME(pipe)(int *p) { - register unsigned long __v0 __asm__("$2") = __NR_pipe; - register unsigned long __v1 __asm__("$3"); - register unsigned long __r7 __asm__("$7"); - __asm__ __volatile__ ("syscall\n" - : "+r"(__v0), "=r"(__v1), "=r" (__r7) - : "0"(__v0) - : "$8", "$9", "$10", "$11", "$12", - "$13", "$14", "$15", "$24", "$25", "memory"); - if (__r7) { - unsigned long __errnovalue = __v0; - LSS_ERRNO = __errnovalue; - return -1; - } else { - p[0] = __v0; - p[1] = __v1; - return 0; - } - } - #elif !defined(__aarch64__) - // The unlink syscall has been deprecated on aarch64. We polyfill it below. - LSS_INLINE _syscall1(int, pipe, int *, p) - #endif - /* TODO(csilvers): see if ppc can/should support this as well */ - #if defined(__i386__) || defined(__ARM_ARCH_3__) || \ - defined(__ARM_EABI__) || \ - (defined(__mips__) && _MIPS_SIM != _MIPS_SIM_ABI64) - #define __NR__statfs64 __NR_statfs64 - #define __NR__fstatfs64 __NR_fstatfs64 - LSS_INLINE _syscall3(int, _statfs64, const char*, p, - size_t, s,struct kernel_statfs64*, b) - LSS_INLINE _syscall3(int, _fstatfs64, int, f, - size_t, s,struct kernel_statfs64*, b) - LSS_INLINE int LSS_NAME(statfs64)(const char *p, - struct kernel_statfs64 *b) { - return LSS_NAME(_statfs64)(p, sizeof(*b), b); - } - LSS_INLINE int LSS_NAME(fstatfs64)(int f,struct kernel_statfs64 *b) { - return LSS_NAME(_fstatfs64)(f, sizeof(*b), b); - } - #endif - - LSS_INLINE int LSS_NAME(execv)(const char *path, const char *const argv[]) { - extern char **environ; - return LSS_NAME(execve)(path, argv, (const char *const *)environ); - } - - LSS_INLINE pid_t LSS_NAME(gettid)(void) { - pid_t tid = LSS_NAME(_gettid)(); - if (tid != -1) { - return tid; - } - return LSS_NAME(getpid)(); - } - - LSS_INLINE void *LSS_NAME(mremap)(void *old_address, size_t old_size, - size_t new_size, int flags, ...) { - va_list ap; - void *new_address, *rc; - va_start(ap, flags); - new_address = va_arg(ap, void *); - rc = LSS_NAME(_mremap)(old_address, old_size, new_size, - flags, new_address); - va_end(ap); - return rc; - } - - LSS_INLINE int LSS_NAME(ptrace_detach)(pid_t pid) { - /* PTRACE_DETACH can sometimes forget to wake up the tracee and it - * then sends job control signals to the real parent, rather than to - * the tracer. We reduce the risk of this happening by starting a - * whole new time slice, and then quickly sending a SIGCONT signal - * right after detaching from the tracee. - * - * We use tkill to ensure that we only issue a wakeup for the thread being - * detached. Large multi threaded apps can take a long time in the kernel - * processing SIGCONT. - */ - int rc, err; - LSS_NAME(sched_yield)(); - rc = LSS_NAME(ptrace)(PTRACE_DETACH, pid, (void *)0, (void *)0); - err = LSS_ERRNO; - LSS_NAME(tkill)(pid, SIGCONT); - /* Old systems don't have tkill */ - if (LSS_ERRNO == ENOSYS) - LSS_NAME(kill)(pid, SIGCONT); - LSS_ERRNO = err; - return rc; - } - - LSS_INLINE int LSS_NAME(raise)(int sig) { - return LSS_NAME(kill)(LSS_NAME(getpid)(), sig); - } - - LSS_INLINE int LSS_NAME(setpgrp)(void) { - return LSS_NAME(setpgid)(0, 0); - } - - LSS_INLINE int LSS_NAME(sysconf)(int name) { - extern int __getpagesize(void); - switch (name) { - case _SC_OPEN_MAX: { - struct kernel_rlimit limit; -#if defined(__ARM_EABI__) - return LSS_NAME(ugetrlimit)(RLIMIT_NOFILE, &limit) < 0 - ? 8192 : limit.rlim_cur; -#else - return LSS_NAME(getrlimit)(RLIMIT_NOFILE, &limit) < 0 - ? 8192 : limit.rlim_cur; -#endif - } - case _SC_PAGESIZE: - return __getpagesize(); - default: - LSS_ERRNO = ENOSYS; - return -1; - } - } - #if defined(__x86_64__) - /* Need to make sure loff_t isn't truncated to 32-bits under x32. */ - LSS_INLINE ssize_t LSS_NAME(pread64)(int f, void *b, size_t c, loff_t o) { - LSS_BODY(4, ssize_t, pread64, LSS_SYSCALL_ARG(f), LSS_SYSCALL_ARG(b), - LSS_SYSCALL_ARG(c), (uint64_t)(o)); - } - - LSS_INLINE ssize_t LSS_NAME(pwrite64)(int f, const void *b, size_t c, - loff_t o) { - LSS_BODY(4, ssize_t, pwrite64, LSS_SYSCALL_ARG(f), LSS_SYSCALL_ARG(b), - LSS_SYSCALL_ARG(c), (uint64_t)(o)); - } - - LSS_INLINE int LSS_NAME(readahead)(int f, loff_t o, unsigned c) { - LSS_BODY(3, int, readahead, LSS_SYSCALL_ARG(f), (uint64_t)(o), - LSS_SYSCALL_ARG(c)); - } - #elif defined(__mips__) && _MIPS_SIM == _MIPS_SIM_ABI64 - LSS_INLINE _syscall4(ssize_t, pread64, int, f, - void *, b, size_t, c, - loff_t, o) - LSS_INLINE _syscall4(ssize_t, pwrite64, int, f, - const void *, b, size_t, c, - loff_t, o) - LSS_INLINE _syscall3(int, readahead, int, f, - loff_t, o, unsigned, c) - #else - #define __NR__pread64 __NR_pread64 - #define __NR__pwrite64 __NR_pwrite64 - #define __NR__readahead __NR_readahead - #if defined(__ARM_EABI__) || defined(__mips__) - /* On ARM and MIPS, a 64-bit parameter has to be in an even-odd register - * pair. Hence these calls ignore their fourth argument (r3) so that their - * fifth and sixth make such a pair (r4,r5). - */ - #define LSS_LLARG_PAD 0, - LSS_INLINE _syscall6(ssize_t, _pread64, int, f, - void *, b, size_t, c, - unsigned, skip, unsigned, o1, unsigned, o2) - LSS_INLINE _syscall6(ssize_t, _pwrite64, int, f, - const void *, b, size_t, c, - unsigned, skip, unsigned, o1, unsigned, o2) - LSS_INLINE _syscall5(int, _readahead, int, f, - unsigned, skip, - unsigned, o1, unsigned, o2, size_t, c) - #else - #define LSS_LLARG_PAD - LSS_INLINE _syscall5(ssize_t, _pread64, int, f, - void *, b, size_t, c, unsigned, o1, - unsigned, o2) - LSS_INLINE _syscall5(ssize_t, _pwrite64, int, f, - const void *, b, size_t, c, unsigned, o1, - long, o2) - LSS_INLINE _syscall4(int, _readahead, int, f, - unsigned, o1, unsigned, o2, size_t, c) - #endif - /* We force 64bit-wide parameters onto the stack, then access each - * 32-bit component individually. This guarantees that we build the - * correct parameters independent of the native byte-order of the - * underlying architecture. - */ - LSS_INLINE ssize_t LSS_NAME(pread64)(int fd, void *buf, size_t count, - loff_t off) { - union { loff_t off; unsigned arg[2]; } o = { off }; - return LSS_NAME(_pread64)(fd, buf, count, - LSS_LLARG_PAD o.arg[0], o.arg[1]); - } - LSS_INLINE ssize_t LSS_NAME(pwrite64)(int fd, const void *buf, - size_t count, loff_t off) { - union { loff_t off; unsigned arg[2]; } o = { off }; - return LSS_NAME(_pwrite64)(fd, buf, count, - LSS_LLARG_PAD o.arg[0], o.arg[1]); - } - LSS_INLINE int LSS_NAME(readahead)(int fd, loff_t off, int len) { - union { loff_t off; unsigned arg[2]; } o = { off }; - return LSS_NAME(_readahead)(fd, LSS_LLARG_PAD o.arg[0], o.arg[1], len); - } - #endif -#endif - -#if defined(__aarch64__) - LSS_INLINE _syscall3(int, dup3, int, s, int, d, int, f) - LSS_INLINE _syscall6(void *, mmap, void *, addr, size_t, length, int, prot, - int, flags, int, fd, int64_t, offset) - LSS_INLINE _syscall4(int, newfstatat, int, dirfd, const char *, pathname, - struct kernel_stat *, buf, int, flags) - LSS_INLINE _syscall2(int, pipe2, int *, pipefd, int, flags) - LSS_INLINE _syscall5(int, ppoll, struct kernel_pollfd *, u, - unsigned int, n, const struct kernel_timespec *, t, - const kernel_sigset_t *, sigmask, size_t, s) - LSS_INLINE _syscall4(int, readlinkat, int, d, const char *, p, char *, b, - size_t, s) -#endif - -/* - * Polyfills for deprecated syscalls. - */ - -#if defined(__aarch64__) - LSS_INLINE int LSS_NAME(dup2)(int s, int d) { - return LSS_NAME(dup3)(s, d, 0); - } - - LSS_INLINE int LSS_NAME(open)(const char *pathname, int flags, int mode) { - return LSS_NAME(openat)(AT_FDCWD, pathname, flags, mode); - } - - LSS_INLINE int LSS_NAME(unlink)(const char *pathname) { - return LSS_NAME(unlinkat)(AT_FDCWD, pathname, 0); - } - - LSS_INLINE int LSS_NAME(readlink)(const char *pathname, char *buffer, - size_t size) { - return LSS_NAME(readlinkat)(AT_FDCWD, pathname, buffer, size); - } - - LSS_INLINE pid_t LSS_NAME(pipe)(int *pipefd) { - return LSS_NAME(pipe2)(pipefd, 0); - } - - LSS_INLINE int LSS_NAME(poll)(struct kernel_pollfd *fds, unsigned int nfds, - int timeout) { - struct kernel_timespec timeout_ts; - struct kernel_timespec *timeout_ts_p = NULL; - - if (timeout >= 0) { - timeout_ts.tv_sec = timeout / 1000; - timeout_ts.tv_nsec = (timeout % 1000) * 1000000; - timeout_ts_p = &timeout_ts; - } - return LSS_NAME(ppoll)(fds, nfds, timeout_ts_p, NULL, 0); - } - - LSS_INLINE int LSS_NAME(stat)(const char *pathname, - struct kernel_stat *buf) { - return LSS_NAME(newfstatat)(AT_FDCWD, pathname, buf, 0); - } - - LSS_INLINE pid_t LSS_NAME(fork)(void) { - // No fork syscall on aarch64 - implement by means of the clone syscall. - // Note that this does not reset glibc's cached view of the PID/TID, so - // some glibc interfaces might go wrong in the forked subprocess. - int flags = SIGCHLD; - void *child_stack = NULL; - void *parent_tidptr = NULL; - void *newtls = NULL; - void *child_tidptr = NULL; - - LSS_REG(0, flags); - LSS_REG(1, child_stack); - LSS_REG(2, parent_tidptr); - LSS_REG(3, newtls); - LSS_REG(4, child_tidptr); - LSS_BODY(pid_t, clone, "r"(__r0), "r"(__r1), "r"(__r2), "r"(__r3), - "r"(__r4)); - } -#endif - -#ifdef __ANDROID__ - /* These restore the original values of these macros saved by the - * corresponding #pragma push_macro near the top of this file. */ -# pragma pop_macro("stat64") -# pragma pop_macro("fstat64") -# pragma pop_macro("lstat64") -#endif - -#if defined(__cplusplus) && !defined(SYS_CPLUSPLUS) -} -#endif - -#endif -#endif diff --git a/TMessagesProj/jni/emoji/emoji_suggestions.cpp b/TMessagesProj/jni/emoji/emoji_suggestions.cpp new file mode 100755 index 000000000..03f85279a --- /dev/null +++ b/TMessagesProj/jni/emoji/emoji_suggestions.cpp @@ -0,0 +1,452 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ + +#include "emoji_suggestions.h" + +#include +#include +#include "emoji_suggestions_data.h" + +checksum Crc32Table[256]; +class Crc32Initializer { +public: + Crc32Initializer() { + checksum poly = 0x04C11DB7U; + for (auto i = 0; i != 256; ++i) { + Crc32Table[i] = reflect(i, 8) << 24; + for (auto j = 0; j != 8; ++j) { + Crc32Table[i] = (Crc32Table[i] << 1) ^ (Crc32Table[i] & (1 << 31) ? poly : 0); + } + Crc32Table[i] = reflect(Crc32Table[i], 32); + } + } + +private: + checksum reflect(checksum val, char ch) { + checksum result = 0; + for (int i = 1; i < (ch + 1); ++i) { + if (val & 1) { + result |= 1 << (ch - i); + } + val >>= 1; + } + return result; + } + +}; + +checksum countChecksum(const void *data, std::size_t size) { + static Crc32Initializer InitTable; + + auto buffer = static_cast(data); + auto result = checksum(0xFFFFFFFFU); + for (auto i = std::size_t(0); i != size; ++i) { + result = (result >> 8) ^ Crc32Table[(result & 0xFFU) ^ buffer[i]]; + } + return (result ^ 0xFFFFFFFFU); +} + +class string_span { +public: + string_span() = default; + string_span(const utf16string *data, std::size_t size) : begin_(data), size_(size) { + } + string_span(const std::vector &data) : begin_(data.data()), size_(data.size()) { + } + string_span(const string_span &other) = default; + string_span &operator=(const string_span &other) = default; + + const utf16string *begin() const { + return begin_; + } + const utf16string *end() const { + return begin_ + size_; + } + std::size_t size() const { + return size_; + } + + string_span subspan(std::size_t offset, std::size_t size) { + return string_span(begin_ + offset, size); + } + +private: + const utf16string *begin_ = nullptr; + std::size_t size_ = 0; + +}; + +bool IsNumber(utf16char ch) { + return (ch >= '0' && ch <= '9'); +} + +bool IsLetterOrNumber(utf16char ch) { + return (ch >= 'a' && ch <= 'z') || IsNumber(ch); +} + +using Replacement = Replacement; + +class Completer { +public: + Completer(utf16string query); + + std::vector resolve(); + +private: + struct Result { + const Replacement *replacement; + int wordsUsed; + }; + + static std::vector NormalizeQuery(utf16string query); + void addResult(const Replacement *replacement); + bool isDuplicateOfLastResult(const Replacement *replacement) const; + bool isBetterThanLastResult(const Replacement *replacement) const; + void processInitialList(); + void filterInitialList(); + void initWordsTracking(); + bool matchQueryForCurrentItem(); + bool matchQueryTailStartingFrom(int position); + string_span findWordsStartingWith(utf16char ch); + int findEqualCharsCount(int position, const utf16string *word); + std::vector prepareResult(); + bool startsWithQuery(utf16string word); + bool isExactMatch(utf16string replacement); + + std::vector _result; + + utf16string _initialQuery; + const std::vector _query; + const utf16char *_queryBegin = nullptr; + int _querySize = 0; + + const std::vector *_initialList = nullptr; + + string_span _currentItemWords; + int _currentItemWordsUsedCount = 0; + + class UsedWordGuard { + public: + UsedWordGuard(std::vector &map, int index); + UsedWordGuard(const UsedWordGuard &other) = delete; + UsedWordGuard(UsedWordGuard &&other); + UsedWordGuard &operator=(const UsedWordGuard &other) = delete; + UsedWordGuard &operator=(UsedWordGuard &&other) = delete; + explicit operator bool() const; + ~UsedWordGuard(); + + private: + std::vector &_map; + int _index = 0; + bool _guarded = false; + + }; + std::vector _currentItemWordsUsedMap; + +}; + +Completer::UsedWordGuard::UsedWordGuard(std::vector &map, int index) : _map(map), _index(index) { + if (!_map[_index]) { + _guarded = _map[_index] = 1; + } +} + +Completer::UsedWordGuard::UsedWordGuard(UsedWordGuard &&other) : _map(other._map), _index(other._index), _guarded(other._guarded) { + other._guarded = 0; +} + +Completer::UsedWordGuard::operator bool() const { + return _guarded; +} + +Completer::UsedWordGuard::~UsedWordGuard() { + if (_guarded) { + _map[_index] = 0; + } +} + +Completer::Completer(utf16string query) : _initialQuery(query), _query(NormalizeQuery(query)) { +} + +// Remove all non-letters-or-numbers. +// Leave '-' and '+' only if they're followed by a number or +// at the end of the query (so it is possibly followed by a number). +std::vector Completer::NormalizeQuery(utf16string query) { + auto result = std::vector(); + result.reserve(query.size()); + auto copyFrom = query.data(); + auto e = copyFrom + query.size(); + auto copyTo = result.data(); + for (auto i = query.data(); i != e; ++i) { + if (IsLetterOrNumber(*i)) { + continue; + } else if (*i == '-' || *i == '+') { + if (i + 1 == e || IsNumber(*(i + 1))) { + continue; + } + } + if (i > copyFrom) { + result.resize(result.size() + (i - copyFrom)); + memcpy(copyTo, copyFrom, (i - copyFrom) * sizeof(utf16char)); + copyTo += (i - copyFrom); + } + copyFrom = i + 1; + } + if (e > copyFrom) { + result.resize(result.size() + (e - copyFrom)); + memcpy(copyTo, copyFrom, (e - copyFrom) * sizeof(utf16char)); + copyTo += (e - copyFrom); + } + return result; +} + +std::vector Completer::resolve() { + _queryBegin = _query.data(); + _querySize = _query.size(); + if (!_querySize) { + return std::vector(); + } + _initialList = GetReplacements(*_queryBegin); + if (!_initialList) { + return std::vector(); + } + _result.reserve(_initialList->size()); + processInitialList(); + return prepareResult(); +} + +bool Completer::isDuplicateOfLastResult(const Replacement *item) const { + if (_result.empty()) { + return false; + } + return (_result.back().replacement->emoji == item->emoji); +} + +bool Completer::isBetterThanLastResult(const Replacement *item) const { + auto &last = _result.back(); + if (_currentItemWordsUsedCount < last.wordsUsed) { + return true; + } + + auto firstCharOfQuery = _query[0]; + auto firstCharAfterColonLast = last.replacement->replacement[1]; + auto firstCharAfterColonCurrent = item->replacement[1]; + auto goodLast = (firstCharAfterColonLast == firstCharOfQuery); + auto goodCurrent = (firstCharAfterColonCurrent == firstCharOfQuery); + return !goodLast && goodCurrent; +} + +void Completer::addResult(const Replacement *item) { + if (!isDuplicateOfLastResult(item)) { + _result.push_back({ item, _currentItemWordsUsedCount }); + } else if (isBetterThanLastResult(item)) { + _result.back() = { item, _currentItemWordsUsedCount }; + } +} + +void Completer::processInitialList() { + if (_querySize > 1) { + filterInitialList(); + } else { + _currentItemWordsUsedCount = 1; + for (auto item : *_initialList) { + addResult(item); + } + } +} + +void Completer::initWordsTracking() { + auto maxWordsCount = 0; + for (auto item : *_initialList) { + auto wordsCount = item->words.size(); + if (maxWordsCount < wordsCount) { + maxWordsCount = wordsCount; + } + } + _currentItemWordsUsedMap = std::vector(maxWordsCount, 0); +} + +void Completer::filterInitialList() { + initWordsTracking(); + for (auto item : *_initialList) { + _currentItemWords = string_span(item->words); + _currentItemWordsUsedCount = 1; + if (matchQueryForCurrentItem()) { + addResult(item); + } + _currentItemWordsUsedCount = 0; + } +} + +bool Completer::matchQueryForCurrentItem() { + if (_currentItemWords.size() < 2) { + return startsWithQuery(*_currentItemWords.begin()); + } + return matchQueryTailStartingFrom(0); +} + +bool Completer::startsWithQuery(utf16string word) { + if (word.size() < _query.size()) { + return false; + } + for (auto i = std::size_t(0), size = _query.size(); i != size; ++i) { + if (word[i] != _query[i]) { + return false; + } + } + return true; +} + +bool Completer::isExactMatch(utf16string replacement) { + if (replacement.size() != _initialQuery.size() + 1) { + return false; + } + for (auto i = std::size_t(0), size = _initialQuery.size(); i != size; ++i) { + if (replacement[i] != _initialQuery[i]) { + return false; + } + } + return true; +} + +bool Completer::matchQueryTailStartingFrom(int position) { + auto charsLeftToMatch = (_querySize - position); + if (!charsLeftToMatch) { + return true; + } + + auto firstCharToMatch = *(_queryBegin + position); + auto foundWords = findWordsStartingWith(firstCharToMatch); + + for (auto word = foundWords.begin(), foundWordsEnd = word + foundWords.size(); word != foundWordsEnd; ++word) { + auto wordIndex = word - _currentItemWords.begin(); + if (auto guard = UsedWordGuard(_currentItemWordsUsedMap, wordIndex)) { + ++_currentItemWordsUsedCount; + auto equalCharsCount = findEqualCharsCount(position, word); + for (auto check = equalCharsCount; check != 0; --check) { + if (matchQueryTailStartingFrom(position + check)) { + return true; + } + } + --_currentItemWordsUsedCount; + } + } + return false; +} + +int Completer::findEqualCharsCount(int position, const utf16string *word) { + auto charsLeft = (_querySize - position); + auto wordBegin = word->data(); + auto wordSize = word->size(); + auto possibleEqualCharsCount = (charsLeft > wordSize ? wordSize : charsLeft); + for (auto equalTill = 1; equalTill != possibleEqualCharsCount; ++equalTill) { + auto wordCh = *(wordBegin + equalTill); + auto queryCh = *(_queryBegin + position + equalTill); + if (wordCh != queryCh) { + return equalTill; + } + } + return possibleEqualCharsCount; +} + +std::vector Completer::prepareResult() { + auto firstCharOfQuery = _query[0]; + std::stable_partition(_result.begin(), _result.end(), [firstCharOfQuery](Result &result) { + auto firstCharAfterColon = result.replacement->replacement[1]; + return (firstCharAfterColon == firstCharOfQuery); + }); + std::stable_partition(_result.begin(), _result.end(), [](Result &result) { + return (result.wordsUsed < 2); + }); + std::stable_partition(_result.begin(), _result.end(), [](Result &result) { + return (result.wordsUsed < 3); + }); + std::stable_partition(_result.begin(), _result.end(), [this](Result &result) { + return isExactMatch(result.replacement->replacement); + }); + + auto result = std::vector(); + result.reserve(_result.size()); + for (auto &item : _result) { + result.emplace_back(item.replacement->emoji, item.replacement->replacement, item.replacement->replacement); + } + return result; +} + +string_span Completer::findWordsStartingWith(utf16char ch) { + auto begin = std::lower_bound(_currentItemWords.begin(), _currentItemWords.end(), ch, [](utf16string word, utf16char ch) { + return word[0] < ch; + }); + auto end = std::upper_bound(_currentItemWords.begin(), _currentItemWords.end(), ch, [](utf16char ch, utf16string word) { + return ch < word[0]; + }); + return _currentItemWords.subspan(begin - _currentItemWords.begin(), end - begin); +} + +std::vector GetSuggestions(utf16string query) { + return Completer(query).resolve(); +} + +int GetSuggestionMaxLength() { + return kReplacementMaxLength; +} + +jclass jclass_Suggestion = nullptr; +jmethodID jclass_Suggestion_constructor; + +extern "C" { + +jobjectArray +Java_org_telegram_messenger_Emoji_getSuggestion(JNIEnv *env, jobject object, jstring query) { + const jchar *raw = env->GetStringChars(query, 0); + jsize len = env->GetStringLength(query); + std::vector suggestions = GetSuggestions(utf16string(raw, len)); + env->ReleaseStringChars(query, raw); + + if (suggestions.empty()) { + return nullptr; + } + + if (jclass_Suggestion == nullptr) { + jclass_Suggestion = (jclass) env->NewGlobalRef(env->FindClass("org/telegram/messenger/EmojiSuggestion")); + jclass_Suggestion_constructor = env->GetMethodID(jclass_Suggestion, "", + "(Ljava/lang/String;Ljava/lang/String;)V"); + } + + jobjectArray result = env->NewObjectArray(suggestions.size(), jclass_Suggestion, nullptr); + size_t size = suggestions.size(); + for (size_t a = 0; a < size; a++) { + Suggestion &suggestion = suggestions[a]; + utf16string emoji = suggestion.emoji(); + utf16string label = suggestion.label(); + jstring emojiStr = env->NewString(emoji.data(), emoji.size()); + jstring labelStr = env->NewString(label.data(), label.size()); + object = env->NewObject(jclass_Suggestion, jclass_Suggestion_constructor, emojiStr, labelStr); + env->SetObjectArrayElement(result, a, object); + env->DeleteLocalRef(object); + env->DeleteLocalRef(emojiStr); + env->DeleteLocalRef(labelStr); + } + + return result; +} + +} diff --git a/TMessagesProj/jni/emoji/emoji_suggestions.h b/TMessagesProj/jni/emoji/emoji_suggestions.h new file mode 100755 index 000000000..3c8da9281 --- /dev/null +++ b/TMessagesProj/jni/emoji/emoji_suggestions.h @@ -0,0 +1,97 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include +#include + +using small = unsigned char; +using medium = unsigned short; +using utf16char = unsigned short; + +static_assert(sizeof(utf16char) == 2, "Bad UTF-16 character size."); + +class utf16string { +public: + utf16string() = default; + utf16string(const utf16char *data, std::size_t size) : data_(data), size_(size) { + } + utf16string(const utf16string &other) = default; + utf16string &operator=(const utf16string &other) = default; + + const utf16char *data() const { + return data_; + } + std::size_t size() const { + return size_; + } + + utf16char operator[](int index) const { + return data_[index]; + } + +private: + const utf16char *data_ = nullptr; + std::size_t size_ = 0; + +}; + +inline bool operator==(utf16string a, utf16string b) { + return (a.size() == b.size()) && (!a.size() || !memcmp(a.data(), b.data(), a.size() * sizeof(utf16char))); +} + +using checksum = unsigned int; +checksum countChecksum(const void *data, std::size_t size); + +utf16string GetReplacementEmoji(utf16string replacement); + +class Suggestion { +public: + Suggestion() = default; + Suggestion(utf16string emoji, utf16string label, utf16string replacement) : emoji_(emoji), label_(label), replacement_(replacement) { + } + Suggestion(const Suggestion &other) = default; + Suggestion &operator=(const Suggestion &other) = default; + + utf16string emoji() const { + return emoji_; + } + utf16string label() const { + return label_; + } + utf16string replacement() const { + return replacement_; + } + +private: + utf16string emoji_; + utf16string label_; + utf16string replacement_; + +}; + +std::vector GetSuggestions(utf16string query); + +inline utf16string GetSuggestionEmoji(utf16string replacement) { + return GetReplacementEmoji(replacement); +} + +int GetSuggestionMaxLength(); diff --git a/TMessagesProj/jni/emoji/emoji_suggestions_data.cpp b/TMessagesProj/jni/emoji/emoji_suggestions_data.cpp new file mode 100755 index 000000000..c6b570c4f --- /dev/null +++ b/TMessagesProj/jni/emoji/emoji_suggestions_data.cpp @@ -0,0 +1,6356 @@ +/* +WARNING! All changes made in this file will be lost! +Created from 'empty' by 'codegen_emoji' + +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ +#include "emoji_suggestions_data.h" + +#include + +struct ReplacementStruct { + small emojiSize; + small replacementSize; + small wordsCount; +}; + +const utf16char ReplacementData[] = { +0xd83d, 0xde00, 0x3a, 0x67, 0x72, 0x69, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x3a, +0x67, 0x72, 0x69, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0xd83d, 0xde03, 0x3a, 0x73, +0x6d, 0x69, 0x6c, 0x65, 0x79, 0x3a, 0x73, 0x6d, 0x69, 0x6c, 0x65, 0x79, +0xd83d, 0xde04, 0x3a, 0x73, 0x6d, 0x69, 0x6c, 0x65, 0x3a, 0x73, 0x6d, 0x69, +0x6c, 0x65, 0xd83d, 0xde01, 0x3a, 0x67, 0x72, 0x69, 0x6e, 0x3a, 0x67, 0x72, +0x69, 0x6e, 0xd83d, 0xde06, 0x3a, 0x73, 0x61, 0x74, 0x69, 0x73, 0x66, 0x69, +0x65, 0x64, 0x3a, 0x73, 0x61, 0x74, 0x69, 0x73, 0x66, 0x69, 0x65, 0x64, +0xd83d, 0xde06, 0x3a, 0x6c, 0x61, 0x75, 0x67, 0x68, 0x69, 0x6e, 0x67, 0x3a, +0x6c, 0x61, 0x75, 0x67, 0x68, 0x69, 0x6e, 0x67, 0xd83d, 0xde05, 0x3a, 0x73, +0x77, 0x65, 0x61, 0x74, 0x5f, 0x73, 0x6d, 0x69, 0x6c, 0x65, 0x3a, 0x73, +0x6d, 0x69, 0x6c, 0x65, 0x73, 0x77, 0x65, 0x61, 0x74, 0xd83d, 0xde02, 0x3a, +0x6a, 0x6f, 0x79, 0x3a, 0x6a, 0x6f, 0x79, 0xd83e, 0xdd23, 0x3a, 0x72, 0x6f, +0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x6e, 0x5f, 0x74, 0x68, 0x65, +0x5f, 0x66, 0x6c, 0x6f, 0x6f, 0x72, 0x5f, 0x6c, 0x61, 0x75, 0x67, 0x68, +0x69, 0x6e, 0x67, 0x3a, 0x66, 0x6c, 0x6f, 0x6f, 0x72, 0x6c, 0x61, 0x75, +0x67, 0x68, 0x69, 0x6e, 0x67, 0x6f, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x69, +0x6e, 0x67, 0x74, 0x68, 0x65, 0xd83e, 0xdd23, 0x3a, 0x72, 0x6f, 0x66, 0x6c, +0x3a, 0x72, 0x6f, 0x66, 0x6c, 0x263a, 0xfe0f, 0x3a, 0x72, 0x65, 0x6c, 0x61, +0x78, 0x65, 0x64, 0x3a, 0x72, 0x65, 0x6c, 0x61, 0x78, 0x65, 0x64, 0xd83d, +0xde0a, 0x3a, 0x62, 0x6c, 0x75, 0x73, 0x68, 0x3a, 0x62, 0x6c, 0x75, 0x73, +0x68, 0xd83d, 0xde07, 0x3a, 0x69, 0x6e, 0x6e, 0x6f, 0x63, 0x65, 0x6e, 0x74, +0x3a, 0x69, 0x6e, 0x6e, 0x6f, 0x63, 0x65, 0x6e, 0x74, 0xd83d, 0xde42, 0x3a, +0x73, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6c, 0x79, 0x5f, 0x73, 0x6d, 0x69, +0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x61, 0x63, 0x65, 0x3a, 0x66, 0x61, +0x63, 0x65, 0x73, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6c, 0x79, 0x73, 0x6d, +0x69, 0x6c, 0x69, 0x6e, 0x67, 0xd83d, 0xde42, 0x3a, 0x73, 0x6c, 0x69, 0x67, +0x68, 0x74, 0x5f, 0x73, 0x6d, 0x69, 0x6c, 0x65, 0x3a, 0x73, 0x6c, 0x69, +0x67, 0x68, 0x74, 0x73, 0x6d, 0x69, 0x6c, 0x65, 0xd83d, 0xde43, 0x3a, 0x75, +0x70, 0x73, 0x69, 0x64, 0x65, 0x5f, 0x64, 0x6f, 0x77, 0x6e, 0x5f, 0x66, +0x61, 0x63, 0x65, 0x3a, 0x64, 0x6f, 0x77, 0x6e, 0x66, 0x61, 0x63, 0x65, +0x75, 0x70, 0x73, 0x69, 0x64, 0x65, 0xd83d, 0xde43, 0x3a, 0x75, 0x70, 0x73, +0x69, 0x64, 0x65, 0x5f, 0x64, 0x6f, 0x77, 0x6e, 0x3a, 0x64, 0x6f, 0x77, +0x6e, 0x75, 0x70, 0x73, 0x69, 0x64, 0x65, 0xd83d, 0xde09, 0x3a, 0x77, 0x69, +0x6e, 0x6b, 0x3a, 0x77, 0x69, 0x6e, 0x6b, 0xd83d, 0xde0c, 0x3a, 0x72, 0x65, +0x6c, 0x69, 0x65, 0x76, 0x65, 0x64, 0x3a, 0x72, 0x65, 0x6c, 0x69, 0x65, +0x76, 0x65, 0x64, 0xd83d, 0xde0d, 0x3a, 0x68, 0x65, 0x61, 0x72, 0x74, 0x5f, +0x65, 0x79, 0x65, 0x73, 0x3a, 0x65, 0x79, 0x65, 0x73, 0x68, 0x65, 0x61, +0x72, 0x74, 0xd83d, 0xde18, 0x3a, 0x6b, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, +0x5f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x3a, 0x68, 0x65, 0x61, 0x72, 0x74, +0x6b, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0xd83d, 0xde17, 0x3a, 0x6b, 0x69, +0x73, 0x73, 0x69, 0x6e, 0x67, 0x3a, 0x6b, 0x69, 0x73, 0x73, 0x69, 0x6e, +0x67, 0xd83d, 0xde19, 0x3a, 0x6b, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, +0x73, 0x6d, 0x69, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x79, 0x65, 0x73, +0x3a, 0x65, 0x79, 0x65, 0x73, 0x6b, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, +0x73, 0x6d, 0x69, 0x6c, 0x69, 0x6e, 0x67, 0xd83d, 0xde1a, 0x3a, 0x6b, 0x69, +0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, +0x5f, 0x65, 0x79, 0x65, 0x73, 0x3a, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, +0x65, 0x79, 0x65, 0x73, 0x6b, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0xd83d, +0xde0b, 0x3a, 0x79, 0x75, 0x6d, 0x3a, 0x79, 0x75, 0x6d, 0xd83d, 0xde1c, 0x3a, +0x73, 0x74, 0x75, 0x63, 0x6b, 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x74, 0x6f, +0x6e, 0x67, 0x75, 0x65, 0x5f, 0x77, 0x69, 0x6e, 0x6b, 0x69, 0x6e, 0x67, +0x5f, 0x65, 0x79, 0x65, 0x3a, 0x65, 0x79, 0x65, 0x6f, 0x75, 0x74, 0x73, +0x74, 0x75, 0x63, 0x6b, 0x74, 0x6f, 0x6e, 0x67, 0x75, 0x65, 0x77, 0x69, +0x6e, 0x6b, 0x69, 0x6e, 0x67, 0xd83d, 0xde1d, 0x3a, 0x73, 0x74, 0x75, 0x63, +0x6b, 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x74, 0x6f, 0x6e, 0x67, 0x75, 0x65, +0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x65, 0x79, 0x65, 0x73, +0x3a, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x65, 0x79, 0x65, 0x73, 0x6f, +0x75, 0x74, 0x73, 0x74, 0x75, 0x63, 0x6b, 0x74, 0x6f, 0x6e, 0x67, 0x75, +0x65, 0xd83d, 0xde1b, 0x3a, 0x73, 0x74, 0x75, 0x63, 0x6b, 0x5f, 0x6f, 0x75, +0x74, 0x5f, 0x74, 0x6f, 0x6e, 0x67, 0x75, 0x65, 0x3a, 0x6f, 0x75, 0x74, +0x73, 0x74, 0x75, 0x63, 0x6b, 0x74, 0x6f, 0x6e, 0x67, 0x75, 0x65, 0xd83e, +0xdd11, 0x3a, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x75, 0x74, +0x68, 0x5f, 0x66, 0x61, 0x63, 0x65, 0x3a, 0x66, 0x61, 0x63, 0x65, 0x6d, +0x6f, 0x6e, 0x65, 0x79, 0x6d, 0x6f, 0x75, 0x74, 0x68, 0xd83e, 0xdd11, 0x3a, +0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x75, 0x74, 0x68, 0x3a, +0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x6d, 0x6f, 0x75, 0x74, 0x68, 0xd83e, 0xdd17, +0x3a, 0x68, 0x75, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x61, 0x63, +0x65, 0x3a, 0x66, 0x61, 0x63, 0x65, 0x68, 0x75, 0x67, 0x67, 0x69, 0x6e, +0x67, 0xd83e, 0xdd17, 0x3a, 0x68, 0x75, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x3a, +0x68, 0x75, 0x67, 0x67, 0x69, 0x6e, 0x67, 0xd83e, 0xdd13, 0x3a, 0x6e, 0x65, +0x72, 0x64, 0x5f, 0x66, 0x61, 0x63, 0x65, 0x3a, 0x66, 0x61, 0x63, 0x65, +0x6e, 0x65, 0x72, 0x64, 0xd83e, 0xdd13, 0x3a, 0x6e, 0x65, 0x72, 0x64, 0x3a, +0x6e, 0x65, 0x72, 0x64, 0xd83d, 0xde0e, 0x3a, 0x73, 0x75, 0x6e, 0x67, 0x6c, +0x61, 0x73, 0x73, 0x65, 0x73, 0x3a, 0x73, 0x75, 0x6e, 0x67, 0x6c, 0x61, +0x73, 0x73, 0x65, 0x73, 0xd83e, 0xdd21, 0x3a, 0x63, 0x6c, 0x6f, 0x77, 0x6e, +0x5f, 0x66, 0x61, 0x63, 0x65, 0x3a, 0x63, 0x6c, 0x6f, 0x77, 0x6e, 0x66, +0x61, 0x63, 0x65, 0xd83e, 0xdd21, 0x3a, 0x63, 0x6c, 0x6f, 0x77, 0x6e, 0x3a, +0x63, 0x6c, 0x6f, 0x77, 0x6e, 0xd83e, 0xdd20, 0x3a, 0x66, 0x61, 0x63, 0x65, +0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x63, 0x6f, 0x77, 0x62, 0x6f, 0x79, +0x5f, 0x68, 0x61, 0x74, 0x3a, 0x63, 0x6f, 0x77, 0x62, 0x6f, 0x79, 0x66, +0x61, 0x63, 0x65, 0x68, 0x61, 0x74, 0x77, 0x69, 0x74, 0x68, 0xd83e, 0xdd20, +0x3a, 0x63, 0x6f, 0x77, 0x62, 0x6f, 0x79, 0x3a, 0x63, 0x6f, 0x77, 0x62, +0x6f, 0x79, 0xd83d, 0xde0f, 0x3a, 0x73, 0x6d, 0x69, 0x72, 0x6b, 0x3a, 0x73, +0x6d, 0x69, 0x72, 0x6b, 0xd83d, 0xde12, 0x3a, 0x75, 0x6e, 0x61, 0x6d, 0x75, +0x73, 0x65, 0x64, 0x3a, 0x75, 0x6e, 0x61, 0x6d, 0x75, 0x73, 0x65, 0x64, +0xd83d, 0xde1e, 0x3a, 0x64, 0x69, 0x73, 0x61, 0x70, 0x70, 0x6f, 0x69, 0x6e, +0x74, 0x65, 0x64, 0x3a, 0x64, 0x69, 0x73, 0x61, 0x70, 0x70, 0x6f, 0x69, +0x6e, 0x74, 0x65, 0x64, 0xd83d, 0xde14, 0x3a, 0x70, 0x65, 0x6e, 0x73, 0x69, +0x76, 0x65, 0x3a, 0x70, 0x65, 0x6e, 0x73, 0x69, 0x76, 0x65, 0xd83d, 0xde1f, +0x3a, 0x77, 0x6f, 0x72, 0x72, 0x69, 0x65, 0x64, 0x3a, 0x77, 0x6f, 0x72, +0x72, 0x69, 0x65, 0x64, 0xd83d, 0xde15, 0x3a, 0x63, 0x6f, 0x6e, 0x66, 0x75, +0x73, 0x65, 0x64, 0x3a, 0x63, 0x6f, 0x6e, 0x66, 0x75, 0x73, 0x65, 0x64, +0xd83d, 0xde41, 0x3a, 0x73, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6c, 0x79, 0x5f, +0x66, 0x72, 0x6f, 0x77, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x61, 0x63, +0x65, 0x3a, 0x66, 0x61, 0x63, 0x65, 0x66, 0x72, 0x6f, 0x77, 0x6e, 0x69, +0x6e, 0x67, 0x73, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6c, 0x79, 0xd83d, 0xde41, +0x3a, 0x73, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x66, 0x72, 0x6f, 0x77, +0x6e, 0x3a, 0x66, 0x72, 0x6f, 0x77, 0x6e, 0x73, 0x6c, 0x69, 0x67, 0x68, +0x74, 0x2639, 0xfe0f, 0x3a, 0x77, 0x68, 0x69, 0x74, 0x65, 0x5f, 0x66, 0x72, +0x6f, 0x77, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x61, 0x63, 0x65, 0x3a, +0x66, 0x61, 0x63, 0x65, 0x66, 0x72, 0x6f, 0x77, 0x6e, 0x69, 0x6e, 0x67, +0x77, 0x68, 0x69, 0x74, 0x65, 0x2639, 0xfe0f, 0x3a, 0x66, 0x72, 0x6f, 0x77, +0x6e, 0x69, 0x6e, 0x67, 0x32, 0x3a, 0x66, 0x72, 0x6f, 0x77, 0x6e, 0x69, +0x6e, 0x67, 0x32, 0xd83d, 0xde23, 0x3a, 0x70, 0x65, 0x72, 0x73, 0x65, 0x76, +0x65, 0x72, 0x65, 0x3a, 0x70, 0x65, 0x72, 0x73, 0x65, 0x76, 0x65, 0x72, +0x65, 0xd83d, 0xde16, 0x3a, 0x63, 0x6f, 0x6e, 0x66, 0x6f, 0x75, 0x6e, 0x64, +0x65, 0x64, 0x3a, 0x63, 0x6f, 0x6e, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x65, +0x64, 0xd83d, 0xde2b, 0x3a, 0x74, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x66, 0x61, +0x63, 0x65, 0x3a, 0x66, 0x61, 0x63, 0x65, 0x74, 0x69, 0x72, 0x65, 0x64, +0xd83d, 0xde29, 0x3a, 0x77, 0x65, 0x61, 0x72, 0x79, 0x3a, 0x77, 0x65, 0x61, +0x72, 0x79, 0xd83d, 0xde24, 0x3a, 0x74, 0x72, 0x69, 0x75, 0x6d, 0x70, 0x68, +0x3a, 0x74, 0x72, 0x69, 0x75, 0x6d, 0x70, 0x68, 0xd83d, 0xde20, 0x3a, 0x61, +0x6e, 0x67, 0x72, 0x79, 0x3a, 0x61, 0x6e, 0x67, 0x72, 0x79, 0xd83d, 0xde21, +0x3a, 0x72, 0x61, 0x67, 0x65, 0x3a, 0x72, 0x61, 0x67, 0x65, 0xd83d, 0xde36, +0x3a, 0x6e, 0x6f, 0x5f, 0x6d, 0x6f, 0x75, 0x74, 0x68, 0x3a, 0x6d, 0x6f, +0x75, 0x74, 0x68, 0x6e, 0x6f, 0xd83d, 0xde10, 0x3a, 0x6e, 0x65, 0x75, 0x74, +0x72, 0x61, 0x6c, 0x5f, 0x66, 0x61, 0x63, 0x65, 0x3a, 0x66, 0x61, 0x63, +0x65, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x61, 0x6c, 0xd83d, 0xde11, 0x3a, 0x65, +0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x6c, 0x65, 0x73, +0x73, 0x3a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, +0x6c, 0x65, 0x73, 0x73, 0xd83d, 0xde2f, 0x3a, 0x68, 0x75, 0x73, 0x68, 0x65, +0x64, 0x3a, 0x68, 0x75, 0x73, 0x68, 0x65, 0x64, 0xd83d, 0xde26, 0x3a, 0x66, +0x72, 0x6f, 0x77, 0x6e, 0x69, 0x6e, 0x67, 0x3a, 0x66, 0x72, 0x6f, 0x77, +0x6e, 0x69, 0x6e, 0x67, 0xd83d, 0xde27, 0x3a, 0x61, 0x6e, 0x67, 0x75, 0x69, +0x73, 0x68, 0x65, 0x64, 0x3a, 0x61, 0x6e, 0x67, 0x75, 0x69, 0x73, 0x68, +0x65, 0x64, 0xd83d, 0xde2e, 0x3a, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x6d, 0x6f, +0x75, 0x74, 0x68, 0x3a, 0x6d, 0x6f, 0x75, 0x74, 0x68, 0x6f, 0x70, 0x65, +0x6e, 0xd83d, 0xde32, 0x3a, 0x61, 0x73, 0x74, 0x6f, 0x6e, 0x69, 0x73, 0x68, +0x65, 0x64, 0x3a, 0x61, 0x73, 0x74, 0x6f, 0x6e, 0x69, 0x73, 0x68, 0x65, +0x64, 0xd83d, 0xde35, 0x3a, 0x64, 0x69, 0x7a, 0x7a, 0x79, 0x5f, 0x66, 0x61, +0x63, 0x65, 0x3a, 0x64, 0x69, 0x7a, 0x7a, 0x79, 0x66, 0x61, 0x63, 0x65, +0xd83d, 0xde33, 0x3a, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x65, 0x64, 0x3a, 0x66, +0x6c, 0x75, 0x73, 0x68, 0x65, 0x64, 0xd83d, 0xde31, 0x3a, 0x73, 0x63, 0x72, +0x65, 0x61, 0x6d, 0x3a, 0x73, 0x63, 0x72, 0x65, 0x61, 0x6d, 0xd83d, 0xde28, +0x3a, 0x66, 0x65, 0x61, 0x72, 0x66, 0x75, 0x6c, 0x3a, 0x66, 0x65, 0x61, +0x72, 0x66, 0x75, 0x6c, 0xd83d, 0xde30, 0x3a, 0x63, 0x6f, 0x6c, 0x64, 0x5f, +0x73, 0x77, 0x65, 0x61, 0x74, 0x3a, 0x63, 0x6f, 0x6c, 0x64, 0x73, 0x77, +0x65, 0x61, 0x74, 0xd83d, 0xde22, 0x3a, 0x63, 0x72, 0x79, 0x3a, 0x63, 0x72, +0x79, 0xd83d, 0xde25, 0x3a, 0x64, 0x69, 0x73, 0x61, 0x70, 0x70, 0x6f, 0x69, +0x6e, 0x74, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x6c, 0x69, 0x65, 0x76, 0x65, +0x64, 0x3a, 0x64, 0x69, 0x73, 0x61, 0x70, 0x70, 0x6f, 0x69, 0x6e, 0x74, +0x65, 0x64, 0x72, 0x65, 0x6c, 0x69, 0x65, 0x76, 0x65, 0x64, 0xd83e, 0xdd24, +0x3a, 0x64, 0x72, 0x6f, 0x6f, 0x6c, 0x3a, 0x64, 0x72, 0x6f, 0x6f, 0x6c, +0xd83e, 0xdd24, 0x3a, 0x64, 0x72, 0x6f, 0x6f, 0x6c, 0x69, 0x6e, 0x67, 0x5f, +0x66, 0x61, 0x63, 0x65, 0x3a, 0x64, 0x72, 0x6f, 0x6f, 0x6c, 0x69, 0x6e, +0x67, 0x66, 0x61, 0x63, 0x65, 0xd83d, 0xde2d, 0x3a, 0x73, 0x6f, 0x62, 0x3a, +0x73, 0x6f, 0x62, 0xd83d, 0xde13, 0x3a, 0x73, 0x77, 0x65, 0x61, 0x74, 0x3a, +0x73, 0x77, 0x65, 0x61, 0x74, 0xd83d, 0xde2a, 0x3a, 0x73, 0x6c, 0x65, 0x65, +0x70, 0x79, 0x3a, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x79, 0xd83d, 0xde34, 0x3a, +0x73, 0x6c, 0x65, 0x65, 0x70, 0x69, 0x6e, 0x67, 0x3a, 0x73, 0x6c, 0x65, +0x65, 0x70, 0x69, 0x6e, 0x67, 0xd83d, 0xde44, 0x3a, 0x66, 0x61, 0x63, 0x65, +0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x72, 0x6f, 0x6c, 0x6c, 0x69, 0x6e, +0x67, 0x5f, 0x65, 0x79, 0x65, 0x73, 0x3a, 0x65, 0x79, 0x65, 0x73, 0x66, +0x61, 0x63, 0x65, 0x72, 0x6f, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x77, 0x69, +0x74, 0x68, 0xd83d, 0xde44, 0x3a, 0x72, 0x6f, 0x6c, 0x6c, 0x69, 0x6e, 0x67, +0x5f, 0x65, 0x79, 0x65, 0x73, 0x3a, 0x65, 0x79, 0x65, 0x73, 0x72, 0x6f, +0x6c, 0x6c, 0x69, 0x6e, 0x67, 0xd83e, 0xdd14, 0x3a, 0x68, 0x6d, 0x6d, 0x3a, +0x68, 0x6d, 0x6d, 0xd83e, 0xdd14, 0x3a, 0x74, 0x68, 0x69, 0x6e, 0x6b, 0x69, +0x6e, 0x67, 0x5f, 0x66, 0x61, 0x63, 0x65, 0x3a, 0x66, 0x61, 0x63, 0x65, +0x74, 0x68, 0x69, 0x6e, 0x6b, 0x69, 0x6e, 0x67, 0xd83e, 0xdd14, 0x3a, 0x74, +0x68, 0x69, 0x6e, 0x6b, 0x69, 0x6e, 0x67, 0x3a, 0x74, 0x68, 0x69, 0x6e, +0x6b, 0x69, 0x6e, 0x67, 0xd83e, 0xdd25, 0x3a, 0x6c, 0x69, 0x61, 0x72, 0x3a, +0x6c, 0x69, 0x61, 0x72, 0xd83e, 0xdd25, 0x3a, 0x6c, 0x79, 0x69, 0x6e, 0x67, +0x5f, 0x66, 0x61, 0x63, 0x65, 0x3a, 0x66, 0x61, 0x63, 0x65, 0x6c, 0x79, +0x69, 0x6e, 0x67, 0xd83d, 0xde2c, 0x3a, 0x67, 0x72, 0x69, 0x6d, 0x61, 0x63, +0x69, 0x6e, 0x67, 0x3a, 0x67, 0x72, 0x69, 0x6d, 0x61, 0x63, 0x69, 0x6e, +0x67, 0xd83e, 0xdd10, 0x3a, 0x7a, 0x69, 0x70, 0x70, 0x65, 0x72, 0x5f, 0x6d, +0x6f, 0x75, 0x74, 0x68, 0x5f, 0x66, 0x61, 0x63, 0x65, 0x3a, 0x66, 0x61, +0x63, 0x65, 0x6d, 0x6f, 0x75, 0x74, 0x68, 0x7a, 0x69, 0x70, 0x70, 0x65, +0x72, 0xd83e, 0xdd10, 0x3a, 0x7a, 0x69, 0x70, 0x70, 0x65, 0x72, 0x5f, 0x6d, +0x6f, 0x75, 0x74, 0x68, 0x3a, 0x6d, 0x6f, 0x75, 0x74, 0x68, 0x7a, 0x69, +0x70, 0x70, 0x65, 0x72, 0xd83e, 0xdd22, 0x3a, 0x73, 0x69, 0x63, 0x6b, 0x3a, +0x73, 0x69, 0x63, 0x6b, 0xd83e, 0xdd22, 0x3a, 0x6e, 0x61, 0x75, 0x73, 0x65, +0x61, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x61, 0x63, 0x65, 0x3a, 0x66, 0x61, +0x63, 0x65, 0x6e, 0x61, 0x75, 0x73, 0x65, 0x61, 0x74, 0x65, 0x64, 0xd83e, +0xdd27, 0x3a, 0x73, 0x6e, 0x65, 0x65, 0x7a, 0x65, 0x3a, 0x73, 0x6e, 0x65, +0x65, 0x7a, 0x65, 0xd83e, 0xdd27, 0x3a, 0x73, 0x6e, 0x65, 0x65, 0x7a, 0x69, +0x6e, 0x67, 0x5f, 0x66, 0x61, 0x63, 0x65, 0x3a, 0x66, 0x61, 0x63, 0x65, +0x73, 0x6e, 0x65, 0x65, 0x7a, 0x69, 0x6e, 0x67, 0xd83d, 0xde37, 0x3a, 0x6d, +0x61, 0x73, 0x6b, 0x3a, 0x6d, 0x61, 0x73, 0x6b, 0xd83e, 0xdd12, 0x3a, 0x66, +0x61, 0x63, 0x65, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x74, 0x68, 0x65, +0x72, 0x6d, 0x6f, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x3a, 0x66, 0x61, 0x63, +0x65, 0x74, 0x68, 0x65, 0x72, 0x6d, 0x6f, 0x6d, 0x65, 0x74, 0x65, 0x72, +0x77, 0x69, 0x74, 0x68, 0xd83e, 0xdd12, 0x3a, 0x74, 0x68, 0x65, 0x72, 0x6d, +0x6f, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x66, 0x61, 0x63, 0x65, 0x3a, +0x66, 0x61, 0x63, 0x65, 0x74, 0x68, 0x65, 0x72, 0x6d, 0x6f, 0x6d, 0x65, +0x74, 0x65, 0x72, 0xd83e, 0xdd15, 0x3a, 0x66, 0x61, 0x63, 0x65, 0x5f, 0x77, +0x69, 0x74, 0x68, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x5f, 0x62, 0x61, 0x6e, +0x64, 0x61, 0x67, 0x65, 0x3a, 0x62, 0x61, 0x6e, 0x64, 0x61, 0x67, 0x65, +0x66, 0x61, 0x63, 0x65, 0x68, 0x65, 0x61, 0x64, 0x77, 0x69, 0x74, 0x68, +0xd83e, 0xdd15, 0x3a, 0x68, 0x65, 0x61, 0x64, 0x5f, 0x62, 0x61, 0x6e, 0x64, +0x61, 0x67, 0x65, 0x3a, 0x62, 0x61, 0x6e, 0x64, 0x61, 0x67, 0x65, 0x68, +0x65, 0x61, 0x64, 0xd83d, 0xde08, 0x3a, 0x73, 0x6d, 0x69, 0x6c, 0x69, 0x6e, +0x67, 0x5f, 0x69, 0x6d, 0x70, 0x3a, 0x69, 0x6d, 0x70, 0x73, 0x6d, 0x69, +0x6c, 0x69, 0x6e, 0x67, 0xd83d, 0xdc7f, 0x3a, 0x69, 0x6d, 0x70, 0x3a, 0x69, +0x6d, 0x70, 0xd83d, 0xdc79, 0x3a, 0x6a, 0x61, 0x70, 0x61, 0x6e, 0x65, 0x73, +0x65, 0x5f, 0x6f, 0x67, 0x72, 0x65, 0x3a, 0x6a, 0x61, 0x70, 0x61, 0x6e, +0x65, 0x73, 0x65, 0x6f, 0x67, 0x72, 0x65, 0xd83d, 0xdc7a, 0x3a, 0x6a, 0x61, +0x70, 0x61, 0x6e, 0x65, 0x73, 0x65, 0x5f, 0x67, 0x6f, 0x62, 0x6c, 0x69, +0x6e, 0x3a, 0x67, 0x6f, 0x62, 0x6c, 0x69, 0x6e, 0x6a, 0x61, 0x70, 0x61, +0x6e, 0x65, 0x73, 0x65, 0xd83d, 0xdca9, 0x3a, 0x70, 0x6f, 0x6f, 0x3a, 0x70, +0x6f, 0x6f, 0xd83d, 0xdca9, 0x3a, 0x68, 0x61, 0x6e, 0x6b, 0x65, 0x79, 0x3a, +0x68, 0x61, 0x6e, 0x6b, 0x65, 0x79, 0xd83d, 0xdca9, 0x3a, 0x73, 0x68, 0x69, +0x74, 0x3a, 0x73, 0x68, 0x69, 0x74, 0xd83d, 0xdca9, 0x3a, 0x70, 0x6f, 0x6f, +0x70, 0x3a, 0x70, 0x6f, 0x6f, 0x70, 0xd83d, 0xdc7b, 0x3a, 0x67, 0x68, 0x6f, +0x73, 0x74, 0x3a, 0x67, 0x68, 0x6f, 0x73, 0x74, 0xd83d, 0xdc80, 0x3a, 0x73, +0x6b, 0x65, 0x6c, 0x65, 0x74, 0x6f, 0x6e, 0x3a, 0x73, 0x6b, 0x65, 0x6c, +0x65, 0x74, 0x6f, 0x6e, 0xd83d, 0xdc80, 0x3a, 0x73, 0x6b, 0x75, 0x6c, 0x6c, +0x3a, 0x73, 0x6b, 0x75, 0x6c, 0x6c, 0x2620, 0xfe0f, 0x3a, 0x73, 0x6b, 0x75, +0x6c, 0x6c, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x63, 0x72, 0x6f, 0x73, 0x73, +0x62, 0x6f, 0x6e, 0x65, 0x73, 0x3a, 0x61, 0x6e, 0x64, 0x63, 0x72, 0x6f, +0x73, 0x73, 0x62, 0x6f, 0x6e, 0x65, 0x73, 0x73, 0x6b, 0x75, 0x6c, 0x6c, +0x2620, 0xfe0f, 0x3a, 0x73, 0x6b, 0x75, 0x6c, 0x6c, 0x5f, 0x63, 0x72, 0x6f, +0x73, 0x73, 0x62, 0x6f, 0x6e, 0x65, 0x73, 0x3a, 0x63, 0x72, 0x6f, 0x73, +0x73, 0x62, 0x6f, 0x6e, 0x65, 0x73, 0x73, 0x6b, 0x75, 0x6c, 0x6c, 0xd83d, +0xdc7d, 0x3a, 0x61, 0x6c, 0x69, 0x65, 0x6e, 0x3a, 0x61, 0x6c, 0x69, 0x65, +0x6e, 0xd83d, 0xdc7e, 0x3a, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x6e, +0x76, 0x61, 0x64, 0x65, 0x72, 0x3a, 0x69, 0x6e, 0x76, 0x61, 0x64, 0x65, +0x72, 0x73, 0x70, 0x61, 0x63, 0x65, 0xd83e, 0xdd16, 0x3a, 0x72, 0x6f, 0x62, +0x6f, 0x74, 0x5f, 0x66, 0x61, 0x63, 0x65, 0x3a, 0x66, 0x61, 0x63, 0x65, +0x72, 0x6f, 0x62, 0x6f, 0x74, 0xd83e, 0xdd16, 0x3a, 0x72, 0x6f, 0x62, 0x6f, +0x74, 0x3a, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0xd83c, 0xdf83, 0x3a, 0x6a, 0x61, +0x63, 0x6b, 0x5f, 0x6f, 0x5f, 0x6c, 0x61, 0x6e, 0x74, 0x65, 0x72, 0x6e, +0x3a, 0x6a, 0x61, 0x63, 0x6b, 0x6c, 0x61, 0x6e, 0x74, 0x65, 0x72, 0x6e, +0x6f, 0xd83d, 0xde3a, 0x3a, 0x73, 0x6d, 0x69, 0x6c, 0x65, 0x79, 0x5f, 0x63, +0x61, 0x74, 0x3a, 0x63, 0x61, 0x74, 0x73, 0x6d, 0x69, 0x6c, 0x65, 0x79, +0xd83d, 0xde38, 0x3a, 0x73, 0x6d, 0x69, 0x6c, 0x65, 0x5f, 0x63, 0x61, 0x74, +0x3a, 0x63, 0x61, 0x74, 0x73, 0x6d, 0x69, 0x6c, 0x65, 0xd83d, 0xde39, 0x3a, +0x6a, 0x6f, 0x79, 0x5f, 0x63, 0x61, 0x74, 0x3a, 0x63, 0x61, 0x74, 0x6a, +0x6f, 0x79, 0xd83d, 0xde3b, 0x3a, 0x68, 0x65, 0x61, 0x72, 0x74, 0x5f, 0x65, +0x79, 0x65, 0x73, 0x5f, 0x63, 0x61, 0x74, 0x3a, 0x63, 0x61, 0x74, 0x65, +0x79, 0x65, 0x73, 0x68, 0x65, 0x61, 0x72, 0x74, 0xd83d, 0xde3c, 0x3a, 0x73, +0x6d, 0x69, 0x72, 0x6b, 0x5f, 0x63, 0x61, 0x74, 0x3a, 0x63, 0x61, 0x74, +0x73, 0x6d, 0x69, 0x72, 0x6b, 0xd83d, 0xde3d, 0x3a, 0x6b, 0x69, 0x73, 0x73, +0x69, 0x6e, 0x67, 0x5f, 0x63, 0x61, 0x74, 0x3a, 0x63, 0x61, 0x74, 0x6b, +0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0xd83d, 0xde40, 0x3a, 0x73, 0x63, 0x72, +0x65, 0x61, 0x6d, 0x5f, 0x63, 0x61, 0x74, 0x3a, 0x63, 0x61, 0x74, 0x73, +0x63, 0x72, 0x65, 0x61, 0x6d, 0xd83d, 0xde3f, 0x3a, 0x63, 0x72, 0x79, 0x69, +0x6e, 0x67, 0x5f, 0x63, 0x61, 0x74, 0x5f, 0x66, 0x61, 0x63, 0x65, 0x3a, +0x63, 0x61, 0x74, 0x63, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x66, 0x61, 0x63, +0x65, 0xd83d, 0xde3e, 0x3a, 0x70, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x5f, +0x63, 0x61, 0x74, 0x3a, 0x63, 0x61, 0x74, 0x70, 0x6f, 0x75, 0x74, 0x69, +0x6e, 0x67, 0xd83d, 0xdc50, 0x3a, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x68, 0x61, +0x6e, 0x64, 0x73, 0x3a, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x6f, 0x70, 0x65, +0x6e, 0xd83d, 0xde4c, 0x3a, 0x72, 0x61, 0x69, 0x73, 0x65, 0x64, 0x5f, 0x68, +0x61, 0x6e, 0x64, 0x73, 0x3a, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x72, 0x61, +0x69, 0x73, 0x65, 0x64, 0xd83d, 0xdc4f, 0x3a, 0x63, 0x6c, 0x61, 0x70, 0x3a, +0x63, 0x6c, 0x61, 0x70, 0xd83d, 0xde4f, 0x3a, 0x70, 0x72, 0x61, 0x79, 0x3a, +0x70, 0x72, 0x61, 0x79, 0xd83e, 0xdd1d, 0x3a, 0x73, 0x68, 0x61, 0x6b, 0x69, +0x6e, 0x67, 0x5f, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x3a, 0x68, 0x61, 0x6e, +0x64, 0x73, 0x73, 0x68, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0xd83e, 0xdd1d, 0x3a, +0x68, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x3a, 0x68, 0x61, +0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0xd83d, 0xdc4d, 0x3a, 0x6c, 0x69, +0x6b, 0x65, 0x3a, 0x6c, 0x69, 0x6b, 0x65, 0xd83d, 0xdc4d, 0x3a, 0x74, 0x68, +0x75, 0x6d, 0x62, 0x75, 0x70, 0x3a, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x75, +0x70, 0xd83d, 0xdc4d, 0x3a, 0x2b, 0x31, 0x3a, 0x2b, 0x31, 0xd83d, 0xdc4d, 0x3a, +0x74, 0x68, 0x75, 0x6d, 0x62, 0x73, 0x75, 0x70, 0x3a, 0x74, 0x68, 0x75, +0x6d, 0x62, 0x73, 0x75, 0x70, 0xd83d, 0xdc4e, 0x3a, 0x64, 0x69, 0x73, 0x6c, +0x69, 0x6b, 0x65, 0x3a, 0x64, 0x69, 0x73, 0x6c, 0x69, 0x6b, 0x65, 0xd83d, +0xdc4e, 0x3a, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x64, 0x6f, 0x77, 0x6e, 0x3a, +0x74, 0x68, 0x75, 0x6d, 0x62, 0x64, 0x6f, 0x77, 0x6e, 0xd83d, 0xdc4e, 0x3a, +0x2d, 0x31, 0x3a, 0x2d, 0x31, 0xd83d, 0xdc4e, 0x3a, 0x74, 0x68, 0x75, 0x6d, +0x62, 0x73, 0x64, 0x6f, 0x77, 0x6e, 0x3a, 0x74, 0x68, 0x75, 0x6d, 0x62, +0x73, 0x64, 0x6f, 0x77, 0x6e, 0xd83d, 0xdc4a, 0x3a, 0x70, 0x75, 0x6e, 0x63, +0x68, 0x3a, 0x70, 0x75, 0x6e, 0x63, 0x68, 0x270a, 0xfe0f, 0x3a, 0x66, 0x69, +0x73, 0x74, 0x3a, 0x66, 0x69, 0x73, 0x74, 0xd83e, 0xdd1b, 0x3a, 0x6c, 0x65, +0x66, 0x74, 0x5f, 0x66, 0x69, 0x73, 0x74, 0x3a, 0x66, 0x69, 0x73, 0x74, +0x6c, 0x65, 0x66, 0x74, 0xd83e, 0xdd1b, 0x3a, 0x6c, 0x65, 0x66, 0x74, 0x5f, +0x66, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x69, 0x73, 0x74, 0x3a, +0x66, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x66, 0x69, 0x73, 0x74, 0x6c, 0x65, +0x66, 0x74, 0xd83e, 0xdd1c, 0x3a, 0x72, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x66, +0x69, 0x73, 0x74, 0x3a, 0x66, 0x69, 0x73, 0x74, 0x72, 0x69, 0x67, 0x68, +0x74, 0xd83e, 0xdd1c, 0x3a, 0x72, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x66, 0x61, +0x63, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x69, 0x73, 0x74, 0x3a, 0x66, 0x61, +0x63, 0x69, 0x6e, 0x67, 0x66, 0x69, 0x73, 0x74, 0x72, 0x69, 0x67, 0x68, +0x74, 0xd83e, 0xdd1e, 0x3a, 0x68, 0x61, 0x6e, 0x64, 0x5f, 0x77, 0x69, 0x74, +0x68, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x61, 0x6e, 0x64, 0x5f, +0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, +0x72, 0x5f, 0x63, 0x72, 0x6f, 0x73, 0x73, 0x65, 0x64, 0x3a, 0x61, 0x6e, +0x64, 0x63, 0x72, 0x6f, 0x73, 0x73, 0x65, 0x64, 0x66, 0x69, 0x6e, 0x67, +0x65, 0x72, 0x68, 0x61, 0x6e, 0x64, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x6d, +0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x69, 0x74, 0x68, 0xd83e, 0xdd1e, 0x3a, +0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x73, 0x5f, 0x63, 0x72, 0x6f, 0x73, +0x73, 0x65, 0x64, 0x3a, 0x63, 0x72, 0x6f, 0x73, 0x73, 0x65, 0x64, 0x66, +0x69, 0x6e, 0x67, 0x65, 0x72, 0x73, 0x270c, 0xfe0f, 0x3a, 0x76, 0x3a, 0x76, +0xd83e, 0xdd18, 0x3a, 0x73, 0x69, 0x67, 0x6e, 0x5f, 0x6f, 0x66, 0x5f, 0x74, +0x68, 0x65, 0x5f, 0x68, 0x6f, 0x72, 0x6e, 0x73, 0x3a, 0x68, 0x6f, 0x72, +0x6e, 0x73, 0x6f, 0x66, 0x73, 0x69, 0x67, 0x6e, 0x74, 0x68, 0x65, 0xd83e, +0xdd18, 0x3a, 0x6d, 0x65, 0x74, 0x61, 0x6c, 0x3a, 0x6d, 0x65, 0x74, 0x61, +0x6c, 0xd83d, 0xdc4c, 0x3a, 0x6f, 0x6b, 0x5f, 0x68, 0x61, 0x6e, 0x64, 0x3a, +0x68, 0x61, 0x6e, 0x64, 0x6f, 0x6b, 0xd83d, 0xdc48, 0x3a, 0x70, 0x6f, 0x69, +0x6e, 0x74, 0x5f, 0x6c, 0x65, 0x66, 0x74, 0x3a, 0x6c, 0x65, 0x66, 0x74, +0x70, 0x6f, 0x69, 0x6e, 0x74, 0xd83d, 0xdc49, 0x3a, 0x70, 0x6f, 0x69, 0x6e, +0x74, 0x5f, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x70, 0x6f, 0x69, 0x6e, +0x74, 0x72, 0x69, 0x67, 0x68, 0x74, 0xd83d, 0xdc46, 0x3a, 0x70, 0x6f, 0x69, +0x6e, 0x74, 0x5f, 0x75, 0x70, 0x5f, 0x32, 0x3a, 0x32, 0x70, 0x6f, 0x69, +0x6e, 0x74, 0x75, 0x70, 0xd83d, 0xdc47, 0x3a, 0x70, 0x6f, 0x69, 0x6e, 0x74, +0x5f, 0x64, 0x6f, 0x77, 0x6e, 0x3a, 0x64, 0x6f, 0x77, 0x6e, 0x70, 0x6f, +0x69, 0x6e, 0x74, 0x261d, 0xfe0f, 0x3a, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, +0x75, 0x70, 0x3a, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x75, 0x70, 0x270b, 0xfe0f, +0x3a, 0x72, 0x61, 0x69, 0x73, 0x65, 0x64, 0x5f, 0x68, 0x61, 0x6e, 0x64, +0x3a, 0x68, 0x61, 0x6e, 0x64, 0x72, 0x61, 0x69, 0x73, 0x65, 0x64, 0xd83e, +0xdd1a, 0x3a, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x6f, 0x66, 0x5f, 0x68, 0x61, +0x6e, 0x64, 0x3a, 0x62, 0x61, 0x63, 0x6b, 0x68, 0x61, 0x6e, 0x64, 0x6f, +0x66, 0xd83e, 0xdd1a, 0x3a, 0x72, 0x61, 0x69, 0x73, 0x65, 0x64, 0x5f, 0x62, +0x61, 0x63, 0x6b, 0x5f, 0x6f, 0x66, 0x5f, 0x68, 0x61, 0x6e, 0x64, 0x3a, +0x62, 0x61, 0x63, 0x6b, 0x68, 0x61, 0x6e, 0x64, 0x6f, 0x66, 0x72, 0x61, +0x69, 0x73, 0x65, 0x64, 0xd83d, 0xdd90, 0x3a, 0x72, 0x61, 0x69, 0x73, 0x65, +0x64, 0x5f, 0x68, 0x61, 0x6e, 0x64, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, +0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x73, 0x5f, 0x73, 0x70, 0x6c, 0x61, +0x79, 0x65, 0x64, 0x3a, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x73, 0x68, +0x61, 0x6e, 0x64, 0x72, 0x61, 0x69, 0x73, 0x65, 0x64, 0x73, 0x70, 0x6c, +0x61, 0x79, 0x65, 0x64, 0x77, 0x69, 0x74, 0x68, 0xd83d, 0xdd90, 0x3a, 0x68, +0x61, 0x6e, 0x64, 0x5f, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x3a, +0x68, 0x61, 0x6e, 0x64, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x64, 0xd83d, +0xdd96, 0x3a, 0x72, 0x61, 0x69, 0x73, 0x65, 0x64, 0x5f, 0x68, 0x61, 0x6e, +0x64, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x74, 0x5f, +0x62, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x5f, 0x6d, 0x69, 0x64, 0x64, +0x6c, 0x65, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x72, 0x69, 0x6e, 0x67, 0x5f, +0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x73, 0x3a, 0x61, 0x6e, 0x64, 0x62, +0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, +0x73, 0x68, 0x61, 0x6e, 0x64, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x70, +0x61, 0x72, 0x74, 0x72, 0x61, 0x69, 0x73, 0x65, 0x64, 0x72, 0x69, 0x6e, +0x67, 0x77, 0x69, 0x74, 0x68, 0xd83d, 0xdd96, 0x3a, 0x76, 0x75, 0x6c, 0x63, +0x61, 0x6e, 0x3a, 0x76, 0x75, 0x6c, 0x63, 0x61, 0x6e, 0xd83d, 0xdc4b, 0x3a, +0x77, 0x61, 0x76, 0x65, 0x3a, 0x77, 0x61, 0x76, 0x65, 0xd83e, 0xdd19, 0x3a, +0x63, 0x61, 0x6c, 0x6c, 0x5f, 0x6d, 0x65, 0x5f, 0x68, 0x61, 0x6e, 0x64, +0x3a, 0x63, 0x61, 0x6c, 0x6c, 0x68, 0x61, 0x6e, 0x64, 0x6d, 0x65, 0xd83e, +0xdd19, 0x3a, 0x63, 0x61, 0x6c, 0x6c, 0x5f, 0x6d, 0x65, 0x3a, 0x63, 0x61, +0x6c, 0x6c, 0x6d, 0x65, 0xd83d, 0xdcaa, 0x3a, 0x6d, 0x75, 0x73, 0x63, 0x6c, +0x65, 0x3a, 0x6d, 0x75, 0x73, 0x63, 0x6c, 0x65, 0xd83d, 0xdd95, 0x3a, 0x72, +0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x64, 0x5f, 0x68, 0x61, 0x6e, 0x64, +0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, +0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x5f, 0x65, 0x78, 0x74, 0x65, +0x6e, 0x64, 0x65, 0x64, 0x3a, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, +0x64, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x68, 0x61, 0x6e, 0x64, 0x6d, +0x69, 0x64, 0x64, 0x6c, 0x65, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, +0x64, 0x77, 0x69, 0x74, 0x68, 0xd83d, 0xdd95, 0x3a, 0x6d, 0x69, 0x64, 0x64, +0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x3a, 0x66, 0x69, +0x6e, 0x67, 0x65, 0x72, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x270d, 0xfe0f, +0x3a, 0x77, 0x72, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x61, 0x6e, +0x64, 0x3a, 0x68, 0x61, 0x6e, 0x64, 0x77, 0x72, 0x69, 0x74, 0x69, 0x6e, +0x67, 0xd83e, 0xdd33, 0x3a, 0x73, 0x65, 0x6c, 0x66, 0x69, 0x65, 0x3a, 0x73, +0x65, 0x6c, 0x66, 0x69, 0x65, 0xd83d, 0xdc85, 0x3a, 0x6e, 0x61, 0x69, 0x6c, +0x5f, 0x63, 0x61, 0x72, 0x65, 0x3a, 0x63, 0x61, 0x72, 0x65, 0x6e, 0x61, +0x69, 0x6c, 0xd83d, 0xdc8d, 0x3a, 0x72, 0x69, 0x6e, 0x67, 0x3a, 0x72, 0x69, +0x6e, 0x67, 0xd83d, 0xdc84, 0x3a, 0x6c, 0x69, 0x70, 0x73, 0x74, 0x69, 0x63, +0x6b, 0x3a, 0x6c, 0x69, 0x70, 0x73, 0x74, 0x69, 0x63, 0x6b, 0xd83d, 0xdc8b, +0x3a, 0x6b, 0x69, 0x73, 0x73, 0x3a, 0x6b, 0x69, 0x73, 0x73, 0xd83d, 0xdc44, +0x3a, 0x6c, 0x69, 0x70, 0x73, 0x3a, 0x6c, 0x69, 0x70, 0x73, 0xd83d, 0xdc45, +0x3a, 0x74, 0x6f, 0x6e, 0x67, 0x75, 0x65, 0x3a, 0x74, 0x6f, 0x6e, 0x67, +0x75, 0x65, 0xd83d, 0xdc42, 0x3a, 0x65, 0x61, 0x72, 0x3a, 0x65, 0x61, 0x72, +0xd83d, 0xdc43, 0x3a, 0x6e, 0x6f, 0x73, 0x65, 0x3a, 0x6e, 0x6f, 0x73, 0x65, +0xd83d, 0xdc63, 0x3a, 0x66, 0x6f, 0x6f, 0x74, 0x70, 0x72, 0x69, 0x6e, 0x74, +0x73, 0x3a, 0x66, 0x6f, 0x6f, 0x74, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x73, +0xd83d, 0xdc41, 0x3a, 0x65, 0x79, 0x65, 0x3a, 0x65, 0x79, 0x65, 0xd83d, 0xdc40, +0x3a, 0x65, 0x79, 0x65, 0x73, 0x3a, 0x65, 0x79, 0x65, 0x73, 0xd83d, 0xdde3, +0x3a, 0x73, 0x70, 0x65, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x65, +0x61, 0x64, 0x5f, 0x69, 0x6e, 0x5f, 0x73, 0x69, 0x6c, 0x68, 0x6f, 0x75, +0x65, 0x74, 0x74, 0x65, 0x3a, 0x68, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x73, +0x69, 0x6c, 0x68, 0x6f, 0x75, 0x65, 0x74, 0x74, 0x65, 0x73, 0x70, 0x65, +0x61, 0x6b, 0x69, 0x6e, 0x67, 0xd83d, 0xdde3, 0x3a, 0x73, 0x70, 0x65, 0x61, +0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x3a, 0x68, 0x65, +0x61, 0x64, 0x73, 0x70, 0x65, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0xd83d, 0xdc64, +0x3a, 0x62, 0x75, 0x73, 0x74, 0x5f, 0x69, 0x6e, 0x5f, 0x73, 0x69, 0x6c, +0x68, 0x6f, 0x75, 0x65, 0x74, 0x74, 0x65, 0x3a, 0x62, 0x75, 0x73, 0x74, +0x69, 0x6e, 0x73, 0x69, 0x6c, 0x68, 0x6f, 0x75, 0x65, 0x74, 0x74, 0x65, +0xd83d, 0xdc65, 0x3a, 0x62, 0x75, 0x73, 0x74, 0x73, 0x5f, 0x69, 0x6e, 0x5f, +0x73, 0x69, 0x6c, 0x68, 0x6f, 0x75, 0x65, 0x74, 0x74, 0x65, 0x3a, 0x62, +0x75, 0x73, 0x74, 0x73, 0x69, 0x6e, 0x73, 0x69, 0x6c, 0x68, 0x6f, 0x75, +0x65, 0x74, 0x74, 0x65, 0xd83d, 0xdc76, 0x3a, 0x62, 0x61, 0x62, 0x79, 0x3a, +0x62, 0x61, 0x62, 0x79, 0xd83d, 0xdc66, 0x3a, 0x62, 0x6f, 0x79, 0x3a, 0x62, +0x6f, 0x79, 0xd83d, 0xdc67, 0x3a, 0x67, 0x69, 0x72, 0x6c, 0x3a, 0x67, 0x69, +0x72, 0x6c, 0xd83d, 0xdc68, 0x3a, 0x6d, 0x61, 0x6e, 0x3a, 0x6d, 0x61, 0x6e, +0xd83d, 0xdc69, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x3a, 0x77, 0x6f, 0x6d, +0x61, 0x6e, 0xd83d, 0xdc71, 0x200d, 0x2640, 0xfe0f, 0x3a, 0x62, 0x6c, 0x6f, 0x6e, +0x64, 0x2d, 0x68, 0x61, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x77, 0x6f, 0x6d, +0x61, 0x6e, 0x3a, 0x62, 0x6c, 0x6f, 0x6e, 0x64, 0x68, 0x61, 0x69, 0x72, +0x65, 0x64, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83d, 0xdc71, 0x3a, 0x62, 0x6c, +0x6f, 0x6e, 0x64, 0x2d, 0x68, 0x61, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x6d, +0x61, 0x6e, 0x3a, 0x62, 0x6c, 0x6f, 0x6e, 0x64, 0x68, 0x61, 0x69, 0x72, +0x65, 0x64, 0x6d, 0x61, 0x6e, 0xd83d, 0xdc71, 0x3a, 0x70, 0x65, 0x72, 0x73, +0x6f, 0x6e, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x62, 0x6c, 0x6f, 0x6e, +0x64, 0x5f, 0x68, 0x61, 0x69, 0x72, 0x3a, 0x62, 0x6c, 0x6f, 0x6e, 0x64, +0x68, 0x61, 0x69, 0x72, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x77, 0x69, +0x74, 0x68, 0xd83d, 0xdc71, 0x3a, 0x62, 0x6c, 0x6f, 0x6e, 0x64, 0x5f, 0x68, +0x61, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, +0x3a, 0x62, 0x6c, 0x6f, 0x6e, 0x64, 0x68, 0x61, 0x69, 0x72, 0x65, 0x64, +0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0xd83d, 0xdc74, 0x3a, 0x6f, 0x6c, 0x64, +0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x3a, 0x6d, 0x61, 0x6e, 0x6f, 0x6c, +0x64, 0x65, 0x72, 0xd83d, 0xdc75, 0x3a, 0x67, 0x72, 0x61, 0x6e, 0x64, 0x6d, +0x61, 0x3a, 0x67, 0x72, 0x61, 0x6e, 0x64, 0x6d, 0x61, 0xd83d, 0xdc75, 0x3a, +0x6f, 0x6c, 0x64, 0x65, 0x72, 0x5f, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x3a, +0x6f, 0x6c, 0x64, 0x65, 0x72, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83d, 0xdc72, +0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x67, 0x75, +0x61, 0x5f, 0x70, 0x69, 0x5f, 0x6d, 0x61, 0x6f, 0x3a, 0x67, 0x75, 0x61, +0x6d, 0x61, 0x6e, 0x6d, 0x61, 0x6f, 0x70, 0x69, 0x77, 0x69, 0x74, 0x68, +0xd83d, 0xdc72, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, +0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x65, 0x5f, 0x63, 0x61, 0x70, 0x3a, +0x63, 0x61, 0x70, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x65, 0x6d, 0x61, +0x6e, 0x77, 0x69, 0x74, 0x68, 0xd83d, 0xdc73, 0x200d, 0x2640, 0xfe0f, 0x3a, 0x77, +0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x77, 0x65, 0x61, 0x72, 0x69, 0x6e, 0x67, +0x5f, 0x74, 0x75, 0x72, 0x62, 0x61, 0x6e, 0x3a, 0x74, 0x75, 0x72, 0x62, +0x61, 0x6e, 0x77, 0x65, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x77, 0x6f, 0x6d, +0x61, 0x6e, 0xd83d, 0xdc73, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x77, 0x65, 0x61, +0x72, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x75, 0x72, 0x62, 0x61, 0x6e, 0x3a, +0x6d, 0x61, 0x6e, 0x74, 0x75, 0x72, 0x62, 0x61, 0x6e, 0x77, 0x65, 0x61, +0x72, 0x69, 0x6e, 0x67, 0xd83d, 0xdc73, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x77, +0x69, 0x74, 0x68, 0x5f, 0x74, 0x75, 0x72, 0x62, 0x61, 0x6e, 0x3a, 0x6d, +0x61, 0x6e, 0x74, 0x75, 0x72, 0x62, 0x61, 0x6e, 0x77, 0x69, 0x74, 0x68, +0xd83d, 0xdc73, 0x3a, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x5f, 0x77, 0x65, +0x61, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x75, 0x72, 0x62, 0x61, 0x6e, +0x3a, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x74, 0x75, 0x72, 0x62, 0x61, +0x6e, 0x77, 0x65, 0x61, 0x72, 0x69, 0x6e, 0x67, 0xd83d, 0xdc6e, 0x200d, 0x2640, +0xfe0f, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x6c, 0x69, +0x63, 0x65, 0x5f, 0x6f, 0x66, 0x66, 0x69, 0x63, 0x65, 0x72, 0x3a, 0x6f, +0x66, 0x66, 0x69, 0x63, 0x65, 0x72, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x65, +0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83d, 0xdc6e, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, +0x70, 0x6f, 0x6c, 0x69, 0x63, 0x65, 0x5f, 0x6f, 0x66, 0x66, 0x69, 0x63, +0x65, 0x72, 0x3a, 0x6d, 0x61, 0x6e, 0x6f, 0x66, 0x66, 0x69, 0x63, 0x65, +0x72, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x65, 0xd83d, 0xdc6e, 0x3a, 0x63, 0x6f, +0x70, 0x3a, 0x63, 0x6f, 0x70, 0xd83d, 0xdc6e, 0x3a, 0x70, 0x6f, 0x6c, 0x69, +0x63, 0x65, 0x5f, 0x6f, 0x66, 0x66, 0x69, 0x63, 0x65, 0x72, 0x3a, 0x6f, +0x66, 0x66, 0x69, 0x63, 0x65, 0x72, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x65, +0xd83d, 0xdc77, 0x200d, 0x2640, 0xfe0f, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, +0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, +0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x3a, 0x63, 0x6f, 0x6e, 0x73, +0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x77, 0x6f, 0x6d, 0x61, +0x6e, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0xd83d, 0xdc77, 0x3a, 0x6d, 0x61, +0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, +0x6f, 0x6e, 0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x3a, 0x63, 0x6f, +0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x6d, 0x61, +0x6e, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0xd83d, 0xdc77, 0x3a, 0x63, 0x6f, +0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x77, +0x6f, 0x72, 0x6b, 0x65, 0x72, 0x3a, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, +0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, +0xd83d, 0xdc82, 0x200d, 0x2640, 0xfe0f, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, +0x67, 0x75, 0x61, 0x72, 0x64, 0x3a, 0x67, 0x75, 0x61, 0x72, 0x64, 0x77, +0x6f, 0x6d, 0x61, 0x6e, 0xd83d, 0xdc82, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x67, +0x75, 0x61, 0x72, 0x64, 0x3a, 0x67, 0x75, 0x61, 0x72, 0x64, 0x6d, 0x61, +0x6e, 0xd83d, 0xdc82, 0x3a, 0x67, 0x75, 0x61, 0x72, 0x64, 0x73, 0x6d, 0x61, +0x6e, 0x3a, 0x67, 0x75, 0x61, 0x72, 0x64, 0x73, 0x6d, 0x61, 0x6e, 0xd83d, +0xdc82, 0x3a, 0x67, 0x75, 0x61, 0x72, 0x64, 0x3a, 0x67, 0x75, 0x61, 0x72, +0x64, 0xd83d, 0xdd75, 0xfe0f, 0x200d, 0x2640, 0xfe0f, 0x3a, 0x77, 0x6f, 0x6d, 0x61, +0x6e, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x3a, +0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x77, 0x6f, 0x6d, +0x61, 0x6e, 0xd83d, 0xdd75, 0xfe0f, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x64, 0x65, +0x74, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x3a, 0x64, 0x65, 0x74, 0x65, +0x63, 0x74, 0x69, 0x76, 0x65, 0x6d, 0x61, 0x6e, 0xd83d, 0xdd75, 0xfe0f, 0x3a, +0x73, 0x6c, 0x65, 0x75, 0x74, 0x68, 0x5f, 0x6f, 0x72, 0x5f, 0x73, 0x70, +0x79, 0x3a, 0x6f, 0x72, 0x73, 0x6c, 0x65, 0x75, 0x74, 0x68, 0x73, 0x70, +0x79, 0xd83d, 0xdd75, 0xfe0f, 0x3a, 0x73, 0x70, 0x79, 0x3a, 0x73, 0x70, 0x79, +0xd83d, 0xdd75, 0xfe0f, 0x3a, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x69, 0x76, +0x65, 0x3a, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0xd83d, +0xdc69, 0x200d, 0x2695, 0xfe0f, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x68, +0x65, 0x61, 0x6c, 0x74, 0x68, 0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, +0x3a, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x77, 0x6f, 0x6d, 0x61, 0x6e, +0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0xd83d, 0xdc68, 0x200d, 0x2695, 0xfe0f, 0x3a, +0x6d, 0x61, 0x6e, 0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x5f, 0x77, +0x6f, 0x72, 0x6b, 0x65, 0x72, 0x3a, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, +0x6d, 0x61, 0x6e, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0xd83d, 0xdc69, 0x200d, +0xd83c, 0xdf3e, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x66, 0x61, 0x72, +0x6d, 0x65, 0x72, 0x3a, 0x66, 0x61, 0x72, 0x6d, 0x65, 0x72, 0x77, 0x6f, +0x6d, 0x61, 0x6e, 0xd83d, 0xdc68, 0x200d, 0xd83c, 0xdf3e, 0x3a, 0x6d, 0x61, 0x6e, +0x5f, 0x66, 0x61, 0x72, 0x6d, 0x65, 0x72, 0x3a, 0x66, 0x61, 0x72, 0x6d, +0x65, 0x72, 0x6d, 0x61, 0x6e, 0xd83d, 0xdc69, 0x200d, 0xd83c, 0xdf73, 0x3a, 0x77, +0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x63, 0x6f, 0x6f, 0x6b, 0x3a, 0x63, 0x6f, +0x6f, 0x6b, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83d, 0xdc68, 0x200d, 0xd83c, 0xdf73, +0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x63, 0x6f, 0x6f, 0x6b, 0x3a, 0x63, 0x6f, +0x6f, 0x6b, 0x6d, 0x61, 0x6e, 0xd83d, 0xdc69, 0x200d, 0xd83c, 0xdf93, 0x3a, 0x77, +0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, +0x3a, 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x77, 0x6f, 0x6d, 0x61, +0x6e, 0xd83d, 0xdc68, 0x200d, 0xd83c, 0xdf93, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x73, +0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x3a, 0x6d, 0x61, 0x6e, 0x73, 0x74, +0x75, 0x64, 0x65, 0x6e, 0x74, 0xd83d, 0xdc69, 0x200d, 0xd83c, 0xdfa4, 0x3a, 0x77, +0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x73, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x3a, +0x73, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83d, +0xdc68, 0x200d, 0xd83c, 0xdfa4, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x73, 0x69, 0x6e, +0x67, 0x65, 0x72, 0x3a, 0x6d, 0x61, 0x6e, 0x73, 0x69, 0x6e, 0x67, 0x65, +0x72, 0xd83d, 0xdc69, 0x200d, 0xd83c, 0xdfeb, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, +0x5f, 0x74, 0x65, 0x61, 0x63, 0x68, 0x65, 0x72, 0x3a, 0x74, 0x65, 0x61, +0x63, 0x68, 0x65, 0x72, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83d, 0xdc68, 0x200d, +0xd83c, 0xdfeb, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x74, 0x65, 0x61, 0x63, 0x68, +0x65, 0x72, 0x3a, 0x6d, 0x61, 0x6e, 0x74, 0x65, 0x61, 0x63, 0x68, 0x65, +0x72, 0xd83d, 0xdc69, 0x200d, 0xd83c, 0xdfed, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, +0x5f, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x5f, 0x77, 0x6f, 0x72, +0x6b, 0x65, 0x72, 0x3a, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x77, +0x6f, 0x6d, 0x61, 0x6e, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0xd83d, 0xdc68, +0x200d, 0xd83c, 0xdfed, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x66, 0x61, 0x63, 0x74, +0x6f, 0x72, 0x79, 0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x3a, 0x66, +0x61, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x6d, 0x61, 0x6e, 0x77, 0x6f, 0x72, +0x6b, 0x65, 0x72, 0xd83d, 0xdc69, 0x200d, 0xd83d, 0xdcbb, 0x3a, 0x77, 0x6f, 0x6d, +0x61, 0x6e, 0x5f, 0x74, 0x65, 0x63, 0x68, 0x6e, 0x6f, 0x6c, 0x6f, 0x67, +0x69, 0x73, 0x74, 0x3a, 0x74, 0x65, 0x63, 0x68, 0x6e, 0x6f, 0x6c, 0x6f, +0x67, 0x69, 0x73, 0x74, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83d, 0xdc68, 0x200d, +0xd83d, 0xdcbb, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x74, 0x65, 0x63, 0x68, 0x6e, +0x6f, 0x6c, 0x6f, 0x67, 0x69, 0x73, 0x74, 0x3a, 0x6d, 0x61, 0x6e, 0x74, +0x65, 0x63, 0x68, 0x6e, 0x6f, 0x6c, 0x6f, 0x67, 0x69, 0x73, 0x74, 0xd83d, +0xdc69, 0x200d, 0xd83d, 0xdcbc, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x6f, +0x66, 0x66, 0x69, 0x63, 0x65, 0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, +0x3a, 0x6f, 0x66, 0x66, 0x69, 0x63, 0x65, 0x77, 0x6f, 0x6d, 0x61, 0x6e, +0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0xd83d, 0xdc68, 0x200d, 0xd83d, 0xdcbc, 0x3a, +0x6d, 0x61, 0x6e, 0x5f, 0x6f, 0x66, 0x66, 0x69, 0x63, 0x65, 0x5f, 0x77, +0x6f, 0x72, 0x6b, 0x65, 0x72, 0x3a, 0x6d, 0x61, 0x6e, 0x6f, 0x66, 0x66, +0x69, 0x63, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0xd83d, 0xdc69, 0x200d, +0xd83d, 0xdd27, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x6d, 0x65, 0x63, +0x68, 0x61, 0x6e, 0x69, 0x63, 0x3a, 0x6d, 0x65, 0x63, 0x68, 0x61, 0x6e, +0x69, 0x63, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83d, 0xdc68, 0x200d, 0xd83d, 0xdd27, +0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x6d, 0x65, 0x63, 0x68, 0x61, 0x6e, 0x69, +0x63, 0x3a, 0x6d, 0x61, 0x6e, 0x6d, 0x65, 0x63, 0x68, 0x61, 0x6e, 0x69, +0x63, 0xd83d, 0xdc69, 0x200d, 0xd83d, 0xdd2c, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, +0x5f, 0x73, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x73, 0x74, 0x3a, 0x73, +0x63, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x73, 0x74, 0x77, 0x6f, 0x6d, 0x61, +0x6e, 0xd83d, 0xdc68, 0x200d, 0xd83d, 0xdd2c, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x73, +0x63, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x73, 0x74, 0x3a, 0x6d, 0x61, 0x6e, +0x73, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x73, 0x74, 0xd83d, 0xdc69, 0x200d, +0xd83c, 0xdfa8, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x61, 0x72, 0x74, +0x69, 0x73, 0x74, 0x3a, 0x61, 0x72, 0x74, 0x69, 0x73, 0x74, 0x77, 0x6f, +0x6d, 0x61, 0x6e, 0xd83d, 0xdc68, 0x200d, 0xd83c, 0xdfa8, 0x3a, 0x6d, 0x61, 0x6e, +0x5f, 0x61, 0x72, 0x74, 0x69, 0x73, 0x74, 0x3a, 0x61, 0x72, 0x74, 0x69, +0x73, 0x74, 0x6d, 0x61, 0x6e, 0xd83d, 0xdc69, 0x200d, 0xd83d, 0xde92, 0x3a, 0x77, +0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x66, 0x69, 0x72, 0x65, 0x66, 0x69, 0x67, +0x68, 0x74, 0x65, 0x72, 0x3a, 0x66, 0x69, 0x72, 0x65, 0x66, 0x69, 0x67, +0x68, 0x74, 0x65, 0x72, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83d, 0xdc68, 0x200d, +0xd83d, 0xde92, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x66, 0x69, 0x72, 0x65, 0x66, +0x69, 0x67, 0x68, 0x74, 0x65, 0x72, 0x3a, 0x66, 0x69, 0x72, 0x65, 0x66, +0x69, 0x67, 0x68, 0x74, 0x65, 0x72, 0x6d, 0x61, 0x6e, 0xd83d, 0xdc69, 0x200d, +0x2708, 0xfe0f, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x70, 0x69, 0x6c, +0x6f, 0x74, 0x3a, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x77, 0x6f, 0x6d, 0x61, +0x6e, 0xd83d, 0xdc68, 0x200d, 0x2708, 0xfe0f, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x70, +0x69, 0x6c, 0x6f, 0x74, 0x3a, 0x6d, 0x61, 0x6e, 0x70, 0x69, 0x6c, 0x6f, +0x74, 0xd83d, 0xdc69, 0x200d, 0xd83d, 0xde80, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, +0x5f, 0x61, 0x73, 0x74, 0x72, 0x6f, 0x6e, 0x61, 0x75, 0x74, 0x3a, 0x61, +0x73, 0x74, 0x72, 0x6f, 0x6e, 0x61, 0x75, 0x74, 0x77, 0x6f, 0x6d, 0x61, +0x6e, 0xd83d, 0xdc68, 0x200d, 0xd83d, 0xde80, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x61, +0x73, 0x74, 0x72, 0x6f, 0x6e, 0x61, 0x75, 0x74, 0x3a, 0x61, 0x73, 0x74, +0x72, 0x6f, 0x6e, 0x61, 0x75, 0x74, 0x6d, 0x61, 0x6e, 0xd83d, 0xdc69, 0x200d, +0x2696, 0xfe0f, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x6a, 0x75, 0x64, +0x67, 0x65, 0x3a, 0x6a, 0x75, 0x64, 0x67, 0x65, 0x77, 0x6f, 0x6d, 0x61, +0x6e, 0xd83d, 0xdc68, 0x200d, 0x2696, 0xfe0f, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x6a, +0x75, 0x64, 0x67, 0x65, 0x3a, 0x6a, 0x75, 0x64, 0x67, 0x65, 0x6d, 0x61, +0x6e, 0xd83e, 0xdd36, 0x3a, 0x6d, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x63, +0x68, 0x72, 0x69, 0x73, 0x74, 0x6d, 0x61, 0x73, 0x3a, 0x63, 0x68, 0x72, +0x69, 0x73, 0x74, 0x6d, 0x61, 0x73, 0x6d, 0x6f, 0x74, 0x68, 0x65, 0x72, +0xd83e, 0xdd36, 0x3a, 0x6d, 0x72, 0x73, 0x5f, 0x63, 0x6c, 0x61, 0x75, 0x73, +0x3a, 0x63, 0x6c, 0x61, 0x75, 0x73, 0x6d, 0x72, 0x73, 0xd83c, 0xdf85, 0x3a, +0x73, 0x61, 0x6e, 0x74, 0x61, 0x3a, 0x73, 0x61, 0x6e, 0x74, 0x61, 0xd83d, +0xdc78, 0x3a, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x65, 0x73, 0x73, 0x3a, 0x70, +0x72, 0x69, 0x6e, 0x63, 0x65, 0x73, 0x73, 0xd83e, 0xdd34, 0x3a, 0x70, 0x72, +0x69, 0x6e, 0x63, 0x65, 0x3a, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x65, 0xd83d, +0xdc70, 0x3a, 0x62, 0x72, 0x69, 0x64, 0x65, 0x5f, 0x77, 0x69, 0x74, 0x68, +0x5f, 0x76, 0x65, 0x69, 0x6c, 0x3a, 0x62, 0x72, 0x69, 0x64, 0x65, 0x76, +0x65, 0x69, 0x6c, 0x77, 0x69, 0x74, 0x68, 0xd83e, 0xdd35, 0x3a, 0x6d, 0x61, +0x6e, 0x5f, 0x69, 0x6e, 0x5f, 0x74, 0x75, 0x78, 0x65, 0x64, 0x6f, 0x3a, +0x69, 0x6e, 0x6d, 0x61, 0x6e, 0x74, 0x75, 0x78, 0x65, 0x64, 0x6f, 0xd83d, +0xdc7c, 0x3a, 0x61, 0x6e, 0x67, 0x65, 0x6c, 0x3a, 0x61, 0x6e, 0x67, 0x65, +0x6c, 0xd83e, 0xdd30, 0x3a, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x69, 0x6e, +0x67, 0x5f, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x3a, 0x65, 0x78, 0x70, 0x65, +0x63, 0x74, 0x69, 0x6e, 0x67, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83e, 0xdd30, +0x3a, 0x70, 0x72, 0x65, 0x67, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x77, 0x6f, +0x6d, 0x61, 0x6e, 0x3a, 0x70, 0x72, 0x65, 0x67, 0x6e, 0x61, 0x6e, 0x74, +0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83d, 0xde47, 0x200d, 0x2640, 0xfe0f, 0x3a, 0x77, +0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x62, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x3a, +0x62, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83d, +0xde47, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x62, 0x6f, 0x77, 0x69, 0x6e, 0x67, +0x3a, 0x62, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0xd83d, 0xde47, +0x3a, 0x62, 0x6f, 0x77, 0x3a, 0x62, 0x6f, 0x77, 0xd83d, 0xde47, 0x3a, 0x70, +0x65, 0x72, 0x73, 0x6f, 0x6e, 0x5f, 0x62, 0x6f, 0x77, 0x69, 0x6e, 0x67, +0x3a, 0x62, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x70, 0x65, 0x72, 0x73, 0x6f, +0x6e, 0xd83d, 0xdc81, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x74, 0x69, +0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x61, 0x6e, 0x64, 0x3a, 0x68, +0x61, 0x6e, 0x64, 0x74, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x77, 0x6f, +0x6d, 0x61, 0x6e, 0xd83d, 0xdc81, 0x3a, 0x69, 0x6e, 0x66, 0x6f, 0x72, 0x6d, +0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x65, 0x73, 0x6b, 0x5f, 0x70, +0x65, 0x72, 0x73, 0x6f, 0x6e, 0x3a, 0x64, 0x65, 0x73, 0x6b, 0x69, 0x6e, +0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x70, 0x65, 0x72, +0x73, 0x6f, 0x6e, 0xd83d, 0xdc81, 0x3a, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, +0x5f, 0x74, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x61, 0x6e, +0x64, 0x3a, 0x68, 0x61, 0x6e, 0x64, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, +0x74, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0xd83d, 0xdc81, 0x200d, 0x2642, 0xfe0f, +0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x74, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, +0x5f, 0x68, 0x61, 0x6e, 0x64, 0x3a, 0x68, 0x61, 0x6e, 0x64, 0x6d, 0x61, +0x6e, 0x74, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0xd83d, 0xde45, 0x3a, 0x77, +0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x67, 0x65, 0x73, 0x74, 0x75, 0x72, 0x69, +0x6e, 0x67, 0x5f, 0x6e, 0x6f, 0x3a, 0x67, 0x65, 0x73, 0x74, 0x75, 0x72, +0x69, 0x6e, 0x67, 0x6e, 0x6f, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83d, 0xde45, +0x3a, 0x6e, 0x6f, 0x5f, 0x67, 0x6f, 0x6f, 0x64, 0x3a, 0x67, 0x6f, 0x6f, +0x64, 0x6e, 0x6f, 0xd83d, 0xde45, 0x3a, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, +0x5f, 0x67, 0x65, 0x73, 0x74, 0x75, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6e, +0x6f, 0x3a, 0x67, 0x65, 0x73, 0x74, 0x75, 0x72, 0x69, 0x6e, 0x67, 0x6e, +0x6f, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0xd83d, 0xde45, 0x200d, 0x2642, 0xfe0f, +0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x67, 0x65, 0x73, 0x74, 0x75, 0x72, 0x69, +0x6e, 0x67, 0x5f, 0x6e, 0x6f, 0x3a, 0x67, 0x65, 0x73, 0x74, 0x75, 0x72, +0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x6e, 0x6f, 0xd83d, 0xde46, 0x3a, 0x77, +0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x67, 0x65, 0x73, 0x74, 0x75, 0x72, 0x69, +0x6e, 0x67, 0x5f, 0x6f, 0x6b, 0x3a, 0x67, 0x65, 0x73, 0x74, 0x75, 0x72, +0x69, 0x6e, 0x67, 0x6f, 0x6b, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83d, 0xde46, +0x3a, 0x6f, 0x6b, 0x5f, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x3a, 0x6f, 0x6b, +0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83d, 0xde46, 0x3a, 0x70, 0x65, 0x72, 0x73, +0x6f, 0x6e, 0x5f, 0x67, 0x65, 0x73, 0x74, 0x75, 0x72, 0x69, 0x6e, 0x67, +0x5f, 0x6f, 0x6b, 0x3a, 0x67, 0x65, 0x73, 0x74, 0x75, 0x72, 0x69, 0x6e, +0x67, 0x6f, 0x6b, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0xd83d, 0xde46, 0x200d, +0x2642, 0xfe0f, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x67, 0x65, 0x73, 0x74, 0x75, +0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x6b, 0x3a, 0x67, 0x65, 0x73, 0x74, +0x75, 0x72, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x6f, 0x6b, 0xd83d, 0xde4b, +0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x72, 0x61, 0x69, 0x73, 0x69, +0x6e, 0x67, 0x5f, 0x68, 0x61, 0x6e, 0x64, 0x3a, 0x68, 0x61, 0x6e, 0x64, +0x72, 0x61, 0x69, 0x73, 0x69, 0x6e, 0x67, 0x77, 0x6f, 0x6d, 0x61, 0x6e, +0xd83d, 0xde4b, 0x3a, 0x72, 0x61, 0x69, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x68, +0x61, 0x6e, 0x64, 0x3a, 0x68, 0x61, 0x6e, 0x64, 0x72, 0x61, 0x69, 0x73, +0x69, 0x6e, 0x67, 0xd83d, 0xde4b, 0x3a, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, +0x5f, 0x72, 0x61, 0x69, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x61, 0x6e, +0x64, 0x3a, 0x68, 0x61, 0x6e, 0x64, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, +0x72, 0x61, 0x69, 0x73, 0x69, 0x6e, 0x67, 0xd83d, 0xde4b, 0x200d, 0x2642, 0xfe0f, +0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x72, 0x61, 0x69, 0x73, 0x69, 0x6e, 0x67, +0x5f, 0x68, 0x61, 0x6e, 0x64, 0x3a, 0x68, 0x61, 0x6e, 0x64, 0x6d, 0x61, +0x6e, 0x72, 0x61, 0x69, 0x73, 0x69, 0x6e, 0x67, 0xd83e, 0xdd26, 0x200d, 0x2640, +0xfe0f, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x66, 0x61, 0x63, 0x65, +0x70, 0x61, 0x6c, 0x6d, 0x69, 0x6e, 0x67, 0x3a, 0x66, 0x61, 0x63, 0x65, +0x70, 0x61, 0x6c, 0x6d, 0x69, 0x6e, 0x67, 0x77, 0x6f, 0x6d, 0x61, 0x6e, +0xd83e, 0xdd26, 0x200d, 0x2642, 0xfe0f, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x66, 0x61, +0x63, 0x65, 0x70, 0x61, 0x6c, 0x6d, 0x69, 0x6e, 0x67, 0x3a, 0x66, 0x61, +0x63, 0x65, 0x70, 0x61, 0x6c, 0x6d, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, +0xd83e, 0xdd26, 0x200d, 0x2642, 0xfe0f, 0x3a, 0x66, 0x61, 0x63, 0x65, 0x70, 0x61, +0x6c, 0x6d, 0x3a, 0x66, 0x61, 0x63, 0x65, 0x70, 0x61, 0x6c, 0x6d, 0xd83e, +0xdd26, 0x200d, 0x2642, 0xfe0f, 0x3a, 0x66, 0x61, 0x63, 0x65, 0x5f, 0x70, 0x61, +0x6c, 0x6d, 0x3a, 0x66, 0x61, 0x63, 0x65, 0x70, 0x61, 0x6c, 0x6d, 0xd83e, +0xdd26, 0x200d, 0x2642, 0xfe0f, 0x3a, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x5f, +0x66, 0x61, 0x63, 0x65, 0x70, 0x61, 0x6c, 0x6d, 0x69, 0x6e, 0x67, 0x3a, +0x66, 0x61, 0x63, 0x65, 0x70, 0x61, 0x6c, 0x6d, 0x69, 0x6e, 0x67, 0x70, +0x65, 0x72, 0x73, 0x6f, 0x6e, 0xd83e, 0xdd37, 0x200d, 0x2640, 0xfe0f, 0x3a, 0x77, +0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x73, 0x68, 0x72, 0x75, 0x67, 0x67, 0x69, +0x6e, 0x67, 0x3a, 0x73, 0x68, 0x72, 0x75, 0x67, 0x67, 0x69, 0x6e, 0x67, +0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83e, 0xdd37, 0x200d, 0x2640, 0xfe0f, 0x3a, 0x73, +0x68, 0x72, 0x75, 0x67, 0x3a, 0x73, 0x68, 0x72, 0x75, 0x67, 0xd83e, 0xdd37, +0x200d, 0x2640, 0xfe0f, 0x3a, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x5f, 0x73, +0x68, 0x72, 0x75, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x3a, 0x70, 0x65, 0x72, +0x73, 0x6f, 0x6e, 0x73, 0x68, 0x72, 0x75, 0x67, 0x67, 0x69, 0x6e, 0x67, +0xd83e, 0xdd37, 0x200d, 0x2642, 0xfe0f, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x73, 0x68, +0x72, 0x75, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x3a, 0x6d, 0x61, 0x6e, 0x73, +0x68, 0x72, 0x75, 0x67, 0x67, 0x69, 0x6e, 0x67, 0xd83d, 0xde4e, 0x3a, 0x77, +0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, +0x3a, 0x70, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x77, 0x6f, 0x6d, 0x61, +0x6e, 0xd83d, 0xde4e, 0x3a, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x5f, 0x77, +0x69, 0x74, 0x68, 0x5f, 0x70, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x5f, +0x66, 0x61, 0x63, 0x65, 0x3a, 0x66, 0x61, 0x63, 0x65, 0x70, 0x65, 0x72, +0x73, 0x6f, 0x6e, 0x70, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x77, 0x69, +0x74, 0x68, 0xd83d, 0xde4e, 0x3a, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x5f, +0x70, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x3a, 0x70, 0x65, 0x72, 0x73, +0x6f, 0x6e, 0x70, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0xd83d, 0xde4e, 0x200d, +0x2642, 0xfe0f, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x75, 0x74, 0x69, +0x6e, 0x67, 0x3a, 0x6d, 0x61, 0x6e, 0x70, 0x6f, 0x75, 0x74, 0x69, 0x6e, +0x67, 0xd83d, 0xde4d, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x66, 0x72, +0x6f, 0x77, 0x6e, 0x69, 0x6e, 0x67, 0x3a, 0x66, 0x72, 0x6f, 0x77, 0x6e, +0x69, 0x6e, 0x67, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83d, 0xde4d, 0x3a, 0x70, +0x65, 0x72, 0x73, 0x6f, 0x6e, 0x5f, 0x66, 0x72, 0x6f, 0x77, 0x6e, 0x69, +0x6e, 0x67, 0x3a, 0x66, 0x72, 0x6f, 0x77, 0x6e, 0x69, 0x6e, 0x67, 0x70, +0x65, 0x72, 0x73, 0x6f, 0x6e, 0xd83d, 0xde4d, 0x200d, 0x2642, 0xfe0f, 0x3a, 0x6d, +0x61, 0x6e, 0x5f, 0x66, 0x72, 0x6f, 0x77, 0x6e, 0x69, 0x6e, 0x67, 0x3a, +0x66, 0x72, 0x6f, 0x77, 0x6e, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0xd83d, +0xdc87, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x67, 0x65, 0x74, 0x74, +0x69, 0x6e, 0x67, 0x5f, 0x68, 0x61, 0x69, 0x72, 0x63, 0x75, 0x74, 0x3a, +0x67, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x68, 0x61, 0x69, 0x72, 0x63, +0x75, 0x74, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83d, 0xdc87, 0x3a, 0x68, 0x61, +0x69, 0x72, 0x63, 0x75, 0x74, 0x3a, 0x68, 0x61, 0x69, 0x72, 0x63, 0x75, +0x74, 0xd83d, 0xdc87, 0x3a, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x5f, 0x67, +0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x61, 0x69, 0x72, 0x63, +0x75, 0x74, 0x3a, 0x67, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x68, 0x61, +0x69, 0x72, 0x63, 0x75, 0x74, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0xd83d, +0xdc87, 0x200d, 0x2642, 0xfe0f, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x67, 0x65, 0x74, +0x74, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x61, 0x69, 0x72, 0x63, 0x75, 0x74, +0x3a, 0x67, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x68, 0x61, 0x69, 0x72, +0x63, 0x75, 0x74, 0x6d, 0x61, 0x6e, 0xd83d, 0xdc86, 0x3a, 0x77, 0x6f, 0x6d, +0x61, 0x6e, 0x5f, 0x67, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x66, +0x61, 0x63, 0x65, 0x5f, 0x6d, 0x61, 0x73, 0x73, 0x61, 0x67, 0x65, 0x3a, +0x66, 0x61, 0x63, 0x65, 0x67, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x6d, +0x61, 0x73, 0x73, 0x61, 0x67, 0x65, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83d, +0xdc86, 0x3a, 0x6d, 0x61, 0x73, 0x73, 0x61, 0x67, 0x65, 0x3a, 0x6d, 0x61, +0x73, 0x73, 0x61, 0x67, 0x65, 0xd83d, 0xdc86, 0x3a, 0x70, 0x65, 0x72, 0x73, +0x6f, 0x6e, 0x5f, 0x67, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x6d, +0x61, 0x73, 0x73, 0x61, 0x67, 0x65, 0x3a, 0x67, 0x65, 0x74, 0x74, 0x69, +0x6e, 0x67, 0x6d, 0x61, 0x73, 0x73, 0x61, 0x67, 0x65, 0x70, 0x65, 0x72, +0x73, 0x6f, 0x6e, 0xd83d, 0xdc86, 0x200d, 0x2642, 0xfe0f, 0x3a, 0x6d, 0x61, 0x6e, +0x5f, 0x67, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x61, 0x63, +0x65, 0x5f, 0x6d, 0x61, 0x73, 0x73, 0x61, 0x67, 0x65, 0x3a, 0x66, 0x61, +0x63, 0x65, 0x67, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, +0x6d, 0x61, 0x73, 0x73, 0x61, 0x67, 0x65, 0xd83d, 0xdd74, 0x3a, 0x6d, 0x61, +0x6e, 0x5f, 0x69, 0x6e, 0x5f, 0x62, 0x75, 0x73, 0x69, 0x6e, 0x65, 0x73, +0x73, 0x5f, 0x73, 0x75, 0x69, 0x74, 0x5f, 0x6c, 0x65, 0x76, 0x69, 0x74, +0x61, 0x74, 0x69, 0x6e, 0x67, 0x3a, 0x62, 0x75, 0x73, 0x69, 0x6e, 0x65, +0x73, 0x73, 0x69, 0x6e, 0x6c, 0x65, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, +0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x73, 0x75, 0x69, 0x74, 0xd83d, 0xdc83, 0x3a, +0x64, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x3a, 0x64, 0x61, 0x6e, 0x63, 0x65, +0x72, 0xd83d, 0xdd7a, 0x3a, 0x6d, 0x61, 0x6c, 0x65, 0x5f, 0x64, 0x61, 0x6e, +0x63, 0x65, 0x72, 0x3a, 0x64, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x6d, 0x61, +0x6c, 0x65, 0xd83d, 0xdd7a, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x64, 0x61, 0x6e, +0x63, 0x69, 0x6e, 0x67, 0x3a, 0x64, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, +0x6d, 0x61, 0x6e, 0xd83d, 0xdc6f, 0x3a, 0x77, 0x6f, 0x6d, 0x65, 0x6e, 0x5f, +0x77, 0x69, 0x74, 0x68, 0x5f, 0x62, 0x75, 0x6e, 0x6e, 0x79, 0x5f, 0x65, +0x61, 0x72, 0x73, 0x5f, 0x70, 0x61, 0x72, 0x74, 0x79, 0x69, 0x6e, 0x67, +0x3a, 0x62, 0x75, 0x6e, 0x6e, 0x79, 0x65, 0x61, 0x72, 0x73, 0x70, 0x61, +0x72, 0x74, 0x79, 0x69, 0x6e, 0x67, 0x77, 0x69, 0x74, 0x68, 0x77, 0x6f, +0x6d, 0x65, 0x6e, 0xd83d, 0xdc6f, 0x3a, 0x64, 0x61, 0x6e, 0x63, 0x65, 0x72, +0x73, 0x3a, 0x64, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x73, 0xd83d, 0xdc6f, 0x3a, +0x70, 0x65, 0x6f, 0x70, 0x6c, 0x65, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, +0x62, 0x75, 0x6e, 0x6e, 0x79, 0x5f, 0x65, 0x61, 0x72, 0x73, 0x5f, 0x70, +0x61, 0x72, 0x74, 0x79, 0x69, 0x6e, 0x67, 0x3a, 0x62, 0x75, 0x6e, 0x6e, +0x79, 0x65, 0x61, 0x72, 0x73, 0x70, 0x61, 0x72, 0x74, 0x79, 0x69, 0x6e, +0x67, 0x70, 0x65, 0x6f, 0x70, 0x6c, 0x65, 0x77, 0x69, 0x74, 0x68, 0xd83d, +0xdc6f, 0x200d, 0x2642, 0xfe0f, 0x3a, 0x6d, 0x65, 0x6e, 0x5f, 0x77, 0x69, 0x74, +0x68, 0x5f, 0x62, 0x75, 0x6e, 0x6e, 0x79, 0x5f, 0x65, 0x61, 0x72, 0x73, +0x5f, 0x70, 0x61, 0x72, 0x74, 0x79, 0x69, 0x6e, 0x67, 0x3a, 0x62, 0x75, +0x6e, 0x6e, 0x79, 0x65, 0x61, 0x72, 0x73, 0x6d, 0x65, 0x6e, 0x70, 0x61, +0x72, 0x74, 0x79, 0x69, 0x6e, 0x67, 0x77, 0x69, 0x74, 0x68, 0xd83d, 0xdeb6, +0x200d, 0x2640, 0xfe0f, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x77, 0x61, +0x6c, 0x6b, 0x69, 0x6e, 0x67, 0x3a, 0x77, 0x61, 0x6c, 0x6b, 0x69, 0x6e, +0x67, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83d, 0xdeb6, 0x3a, 0x6d, 0x61, 0x6e, +0x5f, 0x77, 0x61, 0x6c, 0x6b, 0x69, 0x6e, 0x67, 0x3a, 0x6d, 0x61, 0x6e, +0x77, 0x61, 0x6c, 0x6b, 0x69, 0x6e, 0x67, 0xd83d, 0xdeb6, 0x3a, 0x77, 0x61, +0x6c, 0x6b, 0x69, 0x6e, 0x67, 0x3a, 0x77, 0x61, 0x6c, 0x6b, 0x69, 0x6e, +0x67, 0xd83d, 0xdeb6, 0x3a, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x5f, 0x77, +0x61, 0x6c, 0x6b, 0x69, 0x6e, 0x67, 0x3a, 0x70, 0x65, 0x72, 0x73, 0x6f, +0x6e, 0x77, 0x61, 0x6c, 0x6b, 0x69, 0x6e, 0x67, 0xd83c, 0xdfc3, 0x200d, 0x2640, +0xfe0f, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x72, 0x75, 0x6e, 0x6e, +0x69, 0x6e, 0x67, 0x3a, 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x77, +0x6f, 0x6d, 0x61, 0x6e, 0xd83c, 0xdfc3, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x72, +0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x3a, 0x6d, 0x61, 0x6e, 0x72, 0x75, +0x6e, 0x6e, 0x69, 0x6e, 0x67, 0xd83c, 0xdfc3, 0x3a, 0x72, 0x75, 0x6e, 0x6e, +0x65, 0x72, 0x3a, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0xd83c, 0xdfc3, 0x3a, +0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x5f, 0x72, 0x75, 0x6e, 0x6e, 0x69, +0x6e, 0x67, 0x3a, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x72, 0x75, 0x6e, +0x6e, 0x69, 0x6e, 0x67, 0xd83d, 0xdc6b, 0x3a, 0x63, 0x6f, 0x75, 0x70, 0x6c, +0x65, 0x3a, 0x63, 0x6f, 0x75, 0x70, 0x6c, 0x65, 0xd83d, 0xdc6d, 0x3a, 0x74, +0x77, 0x6f, 0x5f, 0x77, 0x6f, 0x6d, 0x65, 0x6e, 0x5f, 0x68, 0x6f, 0x6c, +0x64, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x3a, 0x68, +0x61, 0x6e, 0x64, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x74, +0x77, 0x6f, 0x77, 0x6f, 0x6d, 0x65, 0x6e, 0xd83d, 0xdc6c, 0x3a, 0x74, 0x77, +0x6f, 0x5f, 0x6d, 0x65, 0x6e, 0x5f, 0x68, 0x6f, 0x6c, 0x64, 0x69, 0x6e, +0x67, 0x5f, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x3a, 0x68, 0x61, 0x6e, 0x64, +0x73, 0x68, 0x6f, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x6d, 0x65, 0x6e, 0x74, +0x77, 0x6f, 0xd83d, 0xdc91, 0x3a, 0x63, 0x6f, 0x75, 0x70, 0x6c, 0x65, 0x5f, +0x77, 0x69, 0x74, 0x68, 0x5f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x3a, 0x63, +0x6f, 0x75, 0x70, 0x6c, 0x65, 0x68, 0x65, 0x61, 0x72, 0x74, 0x77, 0x69, +0x74, 0x68, 0xd83d, 0xdc91, 0x3a, 0x63, 0x6f, 0x75, 0x70, 0x6c, 0x65, 0x5f, +0x77, 0x69, 0x74, 0x68, 0x5f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x5f, 0x77, +0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x6d, 0x61, 0x6e, 0x3a, 0x63, 0x6f, 0x75, +0x70, 0x6c, 0x65, 0x68, 0x65, 0x61, 0x72, 0x74, 0x6d, 0x61, 0x6e, 0x77, +0x69, 0x74, 0x68, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83d, 0xdc69, 0x200d, 0x2764, +0xfe0f, 0x200d, 0xd83d, 0xdc69, 0x3a, 0x63, 0x6f, 0x75, 0x70, 0x6c, 0x65, 0x5f, +0x77, 0x69, 0x74, 0x68, 0x5f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x5f, 0x77, +0x77, 0x3a, 0x63, 0x6f, 0x75, 0x70, 0x6c, 0x65, 0x68, 0x65, 0x61, 0x72, +0x74, 0x77, 0x69, 0x74, 0x68, 0x77, 0x77, 0xd83d, 0xdc69, 0x200d, 0x2764, 0xfe0f, +0x200d, 0xd83d, 0xdc69, 0x3a, 0x63, 0x6f, 0x75, 0x70, 0x6c, 0x65, 0x5f, 0x77, +0x77, 0x3a, 0x63, 0x6f, 0x75, 0x70, 0x6c, 0x65, 0x77, 0x77, 0xd83d, 0xdc68, +0x200d, 0x2764, 0xfe0f, 0x200d, 0xd83d, 0xdc68, 0x3a, 0x63, 0x6f, 0x75, 0x70, 0x6c, +0x65, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x68, 0x65, 0x61, 0x72, 0x74, +0x5f, 0x6d, 0x6d, 0x3a, 0x63, 0x6f, 0x75, 0x70, 0x6c, 0x65, 0x68, 0x65, +0x61, 0x72, 0x74, 0x6d, 0x6d, 0x77, 0x69, 0x74, 0x68, 0xd83d, 0xdc68, 0x200d, +0x2764, 0xfe0f, 0x200d, 0xd83d, 0xdc68, 0x3a, 0x63, 0x6f, 0x75, 0x70, 0x6c, 0x65, +0x5f, 0x6d, 0x6d, 0x3a, 0x63, 0x6f, 0x75, 0x70, 0x6c, 0x65, 0x6d, 0x6d, +0xd83d, 0xdc8f, 0x3a, 0x63, 0x6f, 0x75, 0x70, 0x6c, 0x65, 0x6b, 0x69, 0x73, +0x73, 0x3a, 0x63, 0x6f, 0x75, 0x70, 0x6c, 0x65, 0x6b, 0x69, 0x73, 0x73, +0xd83d, 0xdc8f, 0x3a, 0x6b, 0x69, 0x73, 0x73, 0x5f, 0x77, 0x6f, 0x6d, 0x61, +0x6e, 0x5f, 0x6d, 0x61, 0x6e, 0x3a, 0x6b, 0x69, 0x73, 0x73, 0x6d, 0x61, +0x6e, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83d, 0xdc69, 0x200d, 0x2764, 0xfe0f, 0x200d, +0xd83d, 0xdc8b, 0x200d, 0xd83d, 0xdc69, 0x3a, 0x63, 0x6f, 0x75, 0x70, 0x6c, 0x65, +0x6b, 0x69, 0x73, 0x73, 0x5f, 0x77, 0x77, 0x3a, 0x63, 0x6f, 0x75, 0x70, +0x6c, 0x65, 0x6b, 0x69, 0x73, 0x73, 0x77, 0x77, 0xd83d, 0xdc69, 0x200d, 0x2764, +0xfe0f, 0x200d, 0xd83d, 0xdc8b, 0x200d, 0xd83d, 0xdc69, 0x3a, 0x6b, 0x69, 0x73, 0x73, +0x5f, 0x77, 0x77, 0x3a, 0x6b, 0x69, 0x73, 0x73, 0x77, 0x77, 0xd83d, 0xdc68, +0x200d, 0x2764, 0xfe0f, 0x200d, 0xd83d, 0xdc8b, 0x200d, 0xd83d, 0xdc68, 0x3a, 0x63, 0x6f, +0x75, 0x70, 0x6c, 0x65, 0x6b, 0x69, 0x73, 0x73, 0x5f, 0x6d, 0x6d, 0x3a, +0x63, 0x6f, 0x75, 0x70, 0x6c, 0x65, 0x6b, 0x69, 0x73, 0x73, 0x6d, 0x6d, +0xd83d, 0xdc68, 0x200d, 0x2764, 0xfe0f, 0x200d, 0xd83d, 0xdc8b, 0x200d, 0xd83d, 0xdc68, 0x3a, +0x6b, 0x69, 0x73, 0x73, 0x5f, 0x6d, 0x6d, 0x3a, 0x6b, 0x69, 0x73, 0x73, +0x6d, 0x6d, 0xd83d, 0xdc6a, 0x3a, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x3a, +0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0xd83d, 0xdc6a, 0x3a, 0x66, 0x61, 0x6d, +0x69, 0x6c, 0x79, 0x5f, 0x6d, 0x61, 0x6e, 0x5f, 0x77, 0x6f, 0x6d, 0x61, +0x6e, 0x5f, 0x62, 0x6f, 0x79, 0x3a, 0x62, 0x6f, 0x79, 0x66, 0x61, 0x6d, +0x69, 0x6c, 0x79, 0x6d, 0x61, 0x6e, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83d, +0xdc68, 0x200d, 0xd83d, 0xdc69, 0x200d, 0xd83d, 0xdc67, 0x3a, 0x66, 0x61, 0x6d, 0x69, +0x6c, 0x79, 0x5f, 0x6d, 0x77, 0x67, 0x3a, 0x66, 0x61, 0x6d, 0x69, 0x6c, +0x79, 0x6d, 0x77, 0x67, 0xd83d, 0xdc68, 0x200d, 0xd83d, 0xdc69, 0x200d, 0xd83d, 0xdc67, +0x200d, 0xd83d, 0xdc66, 0x3a, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x5f, 0x6d, +0x77, 0x67, 0x62, 0x3a, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x6d, 0x77, +0x67, 0x62, 0xd83d, 0xdc68, 0x200d, 0xd83d, 0xdc69, 0x200d, 0xd83d, 0xdc66, 0x200d, 0xd83d, +0xdc66, 0x3a, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x5f, 0x6d, 0x77, 0x62, +0x62, 0x3a, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x6d, 0x77, 0x62, 0x62, +0xd83d, 0xdc68, 0x200d, 0xd83d, 0xdc69, 0x200d, 0xd83d, 0xdc67, 0x200d, 0xd83d, 0xdc67, 0x3a, +0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x5f, 0x6d, 0x77, 0x67, 0x67, 0x3a, +0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x6d, 0x77, 0x67, 0x67, 0xd83d, 0xdc69, +0x200d, 0xd83d, 0xdc69, 0x200d, 0xd83d, 0xdc66, 0x3a, 0x66, 0x61, 0x6d, 0x69, 0x6c, +0x79, 0x5f, 0x77, 0x77, 0x62, 0x3a, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, +0x77, 0x77, 0x62, 0xd83d, 0xdc69, 0x200d, 0xd83d, 0xdc69, 0x200d, 0xd83d, 0xdc67, 0x3a, +0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x5f, 0x77, 0x77, 0x67, 0x3a, 0x66, +0x61, 0x6d, 0x69, 0x6c, 0x79, 0x77, 0x77, 0x67, 0xd83d, 0xdc69, 0x200d, 0xd83d, +0xdc69, 0x200d, 0xd83d, 0xdc67, 0x200d, 0xd83d, 0xdc66, 0x3a, 0x66, 0x61, 0x6d, 0x69, +0x6c, 0x79, 0x5f, 0x77, 0x77, 0x67, 0x62, 0x3a, 0x66, 0x61, 0x6d, 0x69, +0x6c, 0x79, 0x77, 0x77, 0x67, 0x62, 0xd83d, 0xdc69, 0x200d, 0xd83d, 0xdc69, 0x200d, +0xd83d, 0xdc66, 0x200d, 0xd83d, 0xdc66, 0x3a, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, +0x5f, 0x77, 0x77, 0x62, 0x62, 0x3a, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, +0x77, 0x77, 0x62, 0x62, 0xd83d, 0xdc69, 0x200d, 0xd83d, 0xdc69, 0x200d, 0xd83d, 0xdc67, +0x200d, 0xd83d, 0xdc67, 0x3a, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x5f, 0x77, +0x77, 0x67, 0x67, 0x3a, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x77, 0x77, +0x67, 0x67, 0xd83d, 0xdc68, 0x200d, 0xd83d, 0xdc68, 0x200d, 0xd83d, 0xdc66, 0x3a, 0x66, +0x61, 0x6d, 0x69, 0x6c, 0x79, 0x5f, 0x6d, 0x6d, 0x62, 0x3a, 0x66, 0x61, +0x6d, 0x69, 0x6c, 0x79, 0x6d, 0x6d, 0x62, 0xd83d, 0xdc68, 0x200d, 0xd83d, 0xdc68, +0x200d, 0xd83d, 0xdc67, 0x3a, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x5f, 0x6d, +0x6d, 0x67, 0x3a, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x6d, 0x6d, 0x67, +0xd83d, 0xdc68, 0x200d, 0xd83d, 0xdc68, 0x200d, 0xd83d, 0xdc67, 0x200d, 0xd83d, 0xdc66, 0x3a, +0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x5f, 0x6d, 0x6d, 0x67, 0x62, 0x3a, +0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x6d, 0x6d, 0x67, 0x62, 0xd83d, 0xdc68, +0x200d, 0xd83d, 0xdc68, 0x200d, 0xd83d, 0xdc66, 0x200d, 0xd83d, 0xdc66, 0x3a, 0x66, 0x61, +0x6d, 0x69, 0x6c, 0x79, 0x5f, 0x6d, 0x6d, 0x62, 0x62, 0x3a, 0x66, 0x61, +0x6d, 0x69, 0x6c, 0x79, 0x6d, 0x6d, 0x62, 0x62, 0xd83d, 0xdc68, 0x200d, 0xd83d, +0xdc68, 0x200d, 0xd83d, 0xdc67, 0x200d, 0xd83d, 0xdc67, 0x3a, 0x66, 0x61, 0x6d, 0x69, +0x6c, 0x79, 0x5f, 0x6d, 0x6d, 0x67, 0x67, 0x3a, 0x66, 0x61, 0x6d, 0x69, +0x6c, 0x79, 0x6d, 0x6d, 0x67, 0x67, 0xd83d, 0xdc69, 0x200d, 0xd83d, 0xdc66, 0x3a, +0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x5f, 0x77, 0x6f, 0x6d, 0x61, 0x6e, +0x5f, 0x62, 0x6f, 0x79, 0x3a, 0x62, 0x6f, 0x79, 0x66, 0x61, 0x6d, 0x69, +0x6c, 0x79, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83d, 0xdc69, 0x200d, 0xd83d, 0xdc67, +0x3a, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x5f, 0x77, 0x6f, 0x6d, 0x61, +0x6e, 0x5f, 0x67, 0x69, 0x72, 0x6c, 0x3a, 0x66, 0x61, 0x6d, 0x69, 0x6c, +0x79, 0x67, 0x69, 0x72, 0x6c, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83d, 0xdc69, +0x200d, 0xd83d, 0xdc67, 0x200d, 0xd83d, 0xdc66, 0x3a, 0x66, 0x61, 0x6d, 0x69, 0x6c, +0x79, 0x5f, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x67, 0x69, 0x72, 0x6c, +0x5f, 0x62, 0x6f, 0x79, 0x3a, 0x62, 0x6f, 0x79, 0x66, 0x61, 0x6d, 0x69, +0x6c, 0x79, 0x67, 0x69, 0x72, 0x6c, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83d, +0xdc69, 0x200d, 0xd83d, 0xdc66, 0x200d, 0xd83d, 0xdc66, 0x3a, 0x66, 0x61, 0x6d, 0x69, +0x6c, 0x79, 0x5f, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x62, 0x6f, 0x79, +0x5f, 0x62, 0x6f, 0x79, 0x3a, 0x62, 0x6f, 0x79, 0x62, 0x6f, 0x79, 0x66, +0x61, 0x6d, 0x69, 0x6c, 0x79, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83d, 0xdc69, +0x200d, 0xd83d, 0xdc67, 0x200d, 0xd83d, 0xdc67, 0x3a, 0x66, 0x61, 0x6d, 0x69, 0x6c, +0x79, 0x5f, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x67, 0x69, 0x72, 0x6c, +0x5f, 0x67, 0x69, 0x72, 0x6c, 0x3a, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, +0x67, 0x69, 0x72, 0x6c, 0x67, 0x69, 0x72, 0x6c, 0x77, 0x6f, 0x6d, 0x61, +0x6e, 0xd83d, 0xdc68, 0x200d, 0xd83d, 0xdc66, 0x3a, 0x66, 0x61, 0x6d, 0x69, 0x6c, +0x79, 0x5f, 0x6d, 0x61, 0x6e, 0x5f, 0x62, 0x6f, 0x79, 0x3a, 0x62, 0x6f, +0x79, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x6d, 0x61, 0x6e, 0xd83d, 0xdc68, +0x200d, 0xd83d, 0xdc67, 0x3a, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x5f, 0x6d, +0x61, 0x6e, 0x5f, 0x67, 0x69, 0x72, 0x6c, 0x3a, 0x66, 0x61, 0x6d, 0x69, +0x6c, 0x79, 0x67, 0x69, 0x72, 0x6c, 0x6d, 0x61, 0x6e, 0xd83d, 0xdc68, 0x200d, +0xd83d, 0xdc67, 0x200d, 0xd83d, 0xdc66, 0x3a, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, +0x5f, 0x6d, 0x61, 0x6e, 0x5f, 0x67, 0x69, 0x72, 0x6c, 0x5f, 0x62, 0x6f, +0x79, 0x3a, 0x62, 0x6f, 0x79, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x67, +0x69, 0x72, 0x6c, 0x6d, 0x61, 0x6e, 0xd83d, 0xdc68, 0x200d, 0xd83d, 0xdc66, 0x200d, +0xd83d, 0xdc66, 0x3a, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x5f, 0x6d, 0x61, +0x6e, 0x5f, 0x62, 0x6f, 0x79, 0x5f, 0x62, 0x6f, 0x79, 0x3a, 0x62, 0x6f, +0x79, 0x62, 0x6f, 0x79, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x6d, 0x61, +0x6e, 0xd83d, 0xdc68, 0x200d, 0xd83d, 0xdc67, 0x200d, 0xd83d, 0xdc67, 0x3a, 0x66, 0x61, +0x6d, 0x69, 0x6c, 0x79, 0x5f, 0x6d, 0x61, 0x6e, 0x5f, 0x67, 0x69, 0x72, +0x6c, 0x5f, 0x67, 0x69, 0x72, 0x6c, 0x3a, 0x66, 0x61, 0x6d, 0x69, 0x6c, +0x79, 0x67, 0x69, 0x72, 0x6c, 0x67, 0x69, 0x72, 0x6c, 0x6d, 0x61, 0x6e, +0xd83d, 0xdc5a, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x73, 0x5f, 0x63, 0x6c, +0x6f, 0x74, 0x68, 0x65, 0x73, 0x3a, 0x63, 0x6c, 0x6f, 0x74, 0x68, 0x65, +0x73, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x73, 0xd83d, 0xdc55, 0x3a, 0x73, 0x68, +0x69, 0x72, 0x74, 0x3a, 0x73, 0x68, 0x69, 0x72, 0x74, 0xd83d, 0xdc56, 0x3a, +0x6a, 0x65, 0x61, 0x6e, 0x73, 0x3a, 0x6a, 0x65, 0x61, 0x6e, 0x73, 0xd83d, +0xdc54, 0x3a, 0x6e, 0x65, 0x63, 0x6b, 0x74, 0x69, 0x65, 0x3a, 0x6e, 0x65, +0x63, 0x6b, 0x74, 0x69, 0x65, 0xd83d, 0xdc57, 0x3a, 0x64, 0x72, 0x65, 0x73, +0x73, 0x3a, 0x64, 0x72, 0x65, 0x73, 0x73, 0xd83d, 0xdc59, 0x3a, 0x62, 0x69, +0x6b, 0x69, 0x6e, 0x69, 0x3a, 0x62, 0x69, 0x6b, 0x69, 0x6e, 0x69, 0xd83d, +0xdc58, 0x3a, 0x6b, 0x69, 0x6d, 0x6f, 0x6e, 0x6f, 0x3a, 0x6b, 0x69, 0x6d, +0x6f, 0x6e, 0x6f, 0xd83d, 0xdc60, 0x3a, 0x68, 0x69, 0x67, 0x68, 0x5f, 0x68, +0x65, 0x65, 0x6c, 0x3a, 0x68, 0x65, 0x65, 0x6c, 0x68, 0x69, 0x67, 0x68, +0xd83d, 0xdc61, 0x3a, 0x73, 0x61, 0x6e, 0x64, 0x61, 0x6c, 0x3a, 0x73, 0x61, +0x6e, 0x64, 0x61, 0x6c, 0xd83d, 0xdc62, 0x3a, 0x62, 0x6f, 0x6f, 0x74, 0x3a, +0x62, 0x6f, 0x6f, 0x74, 0xd83d, 0xdc5e, 0x3a, 0x6d, 0x61, 0x6e, 0x73, 0x5f, +0x73, 0x68, 0x6f, 0x65, 0x3a, 0x6d, 0x61, 0x6e, 0x73, 0x73, 0x68, 0x6f, +0x65, 0xd83d, 0xdc5f, 0x3a, 0x61, 0x74, 0x68, 0x6c, 0x65, 0x74, 0x69, 0x63, +0x5f, 0x73, 0x68, 0x6f, 0x65, 0x3a, 0x61, 0x74, 0x68, 0x6c, 0x65, 0x74, +0x69, 0x63, 0x73, 0x68, 0x6f, 0x65, 0xd83d, 0xdc52, 0x3a, 0x77, 0x6f, 0x6d, +0x61, 0x6e, 0x73, 0x5f, 0x68, 0x61, 0x74, 0x3a, 0x68, 0x61, 0x74, 0x77, +0x6f, 0x6d, 0x61, 0x6e, 0x73, 0xd83c, 0xdfa9, 0x3a, 0x74, 0x6f, 0x70, 0x68, +0x61, 0x74, 0x3a, 0x74, 0x6f, 0x70, 0x68, 0x61, 0x74, 0xd83c, 0xdf93, 0x3a, +0x6d, 0x6f, 0x72, 0x74, 0x61, 0x72, 0x5f, 0x62, 0x6f, 0x61, 0x72, 0x64, +0x3a, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x6d, 0x6f, 0x72, 0x74, 0x61, 0x72, +0xd83d, 0xdc51, 0x3a, 0x63, 0x72, 0x6f, 0x77, 0x6e, 0x3a, 0x63, 0x72, 0x6f, +0x77, 0x6e, 0x26d1, 0x3a, 0x68, 0x65, 0x6c, 0x6d, 0x65, 0x74, 0x5f, 0x77, +0x69, 0x74, 0x68, 0x5f, 0x77, 0x68, 0x69, 0x74, 0x65, 0x5f, 0x63, 0x72, +0x6f, 0x73, 0x73, 0x3a, 0x63, 0x72, 0x6f, 0x73, 0x73, 0x68, 0x65, 0x6c, +0x6d, 0x65, 0x74, 0x77, 0x68, 0x69, 0x74, 0x65, 0x77, 0x69, 0x74, 0x68, +0x26d1, 0x3a, 0x68, 0x65, 0x6c, 0x6d, 0x65, 0x74, 0x5f, 0x77, 0x69, 0x74, +0x68, 0x5f, 0x63, 0x72, 0x6f, 0x73, 0x73, 0x3a, 0x63, 0x72, 0x6f, 0x73, +0x73, 0x68, 0x65, 0x6c, 0x6d, 0x65, 0x74, 0x77, 0x69, 0x74, 0x68, 0xd83c, +0xdf92, 0x3a, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x5f, 0x73, 0x61, 0x74, +0x63, 0x68, 0x65, 0x6c, 0x3a, 0x73, 0x61, 0x74, 0x63, 0x68, 0x65, 0x6c, +0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0xd83d, 0xdc5d, 0x3a, 0x70, 0x6f, 0x75, +0x63, 0x68, 0x3a, 0x70, 0x6f, 0x75, 0x63, 0x68, 0xd83d, 0xdc5b, 0x3a, 0x70, +0x75, 0x72, 0x73, 0x65, 0x3a, 0x70, 0x75, 0x72, 0x73, 0x65, 0xd83d, 0xdc5c, +0x3a, 0x68, 0x61, 0x6e, 0x64, 0x62, 0x61, 0x67, 0x3a, 0x68, 0x61, 0x6e, +0x64, 0x62, 0x61, 0x67, 0xd83d, 0xdcbc, 0x3a, 0x62, 0x72, 0x69, 0x65, 0x66, +0x63, 0x61, 0x73, 0x65, 0x3a, 0x62, 0x72, 0x69, 0x65, 0x66, 0x63, 0x61, +0x73, 0x65, 0xd83d, 0xdc53, 0x3a, 0x65, 0x79, 0x65, 0x67, 0x6c, 0x61, 0x73, +0x73, 0x65, 0x73, 0x3a, 0x65, 0x79, 0x65, 0x67, 0x6c, 0x61, 0x73, 0x73, +0x65, 0x73, 0xd83d, 0xdd76, 0x3a, 0x64, 0x61, 0x72, 0x6b, 0x5f, 0x73, 0x75, +0x6e, 0x67, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x3a, 0x64, 0x61, 0x72, +0x6b, 0x73, 0x75, 0x6e, 0x67, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0xd83c, +0xdf02, 0x3a, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x75, 0x6d, 0x62, +0x72, 0x65, 0x6c, 0x6c, 0x61, 0x3a, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, +0x75, 0x6d, 0x62, 0x72, 0x65, 0x6c, 0x6c, 0x61, 0x2602, 0xfe0f, 0x3a, 0x75, +0x6d, 0x62, 0x72, 0x65, 0x6c, 0x6c, 0x61, 0x32, 0x3a, 0x75, 0x6d, 0x62, +0x72, 0x65, 0x6c, 0x6c, 0x61, 0x32, 0xd83d, 0xdc36, 0x3a, 0x64, 0x6f, 0x67, +0x3a, 0x64, 0x6f, 0x67, 0xd83d, 0xdc31, 0x3a, 0x63, 0x61, 0x74, 0x3a, 0x63, +0x61, 0x74, 0xd83d, 0xdc2d, 0x3a, 0x6d, 0x6f, 0x75, 0x73, 0x65, 0x3a, 0x6d, +0x6f, 0x75, 0x73, 0x65, 0xd83d, 0xdc39, 0x3a, 0x68, 0x61, 0x6d, 0x73, 0x74, +0x65, 0x72, 0x3a, 0x68, 0x61, 0x6d, 0x73, 0x74, 0x65, 0x72, 0xd83d, 0xdc30, +0x3a, 0x72, 0x61, 0x62, 0x62, 0x69, 0x74, 0x3a, 0x72, 0x61, 0x62, 0x62, +0x69, 0x74, 0xd83e, 0xdd8a, 0x3a, 0x66, 0x6f, 0x78, 0x5f, 0x66, 0x61, 0x63, +0x65, 0x3a, 0x66, 0x61, 0x63, 0x65, 0x66, 0x6f, 0x78, 0xd83e, 0xdd8a, 0x3a, +0x66, 0x6f, 0x78, 0x3a, 0x66, 0x6f, 0x78, 0xd83d, 0xdc3b, 0x3a, 0x62, 0x65, +0x61, 0x72, 0x3a, 0x62, 0x65, 0x61, 0x72, 0xd83d, 0xdc3c, 0x3a, 0x70, 0x61, +0x6e, 0x64, 0x61, 0x5f, 0x66, 0x61, 0x63, 0x65, 0x3a, 0x66, 0x61, 0x63, +0x65, 0x70, 0x61, 0x6e, 0x64, 0x61, 0xd83d, 0xdc28, 0x3a, 0x6b, 0x6f, 0x61, +0x6c, 0x61, 0x3a, 0x6b, 0x6f, 0x61, 0x6c, 0x61, 0xd83d, 0xdc2f, 0x3a, 0x74, +0x69, 0x67, 0x65, 0x72, 0x3a, 0x74, 0x69, 0x67, 0x65, 0x72, 0xd83e, 0xdd81, +0x3a, 0x6c, 0x69, 0x6f, 0x6e, 0x3a, 0x6c, 0x69, 0x6f, 0x6e, 0xd83e, 0xdd81, +0x3a, 0x6c, 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x61, 0x63, 0x65, 0x3a, 0x66, +0x61, 0x63, 0x65, 0x6c, 0x69, 0x6f, 0x6e, 0xd83d, 0xdc2e, 0x3a, 0x63, 0x6f, +0x77, 0x3a, 0x63, 0x6f, 0x77, 0xd83d, 0xdc37, 0x3a, 0x70, 0x69, 0x67, 0x3a, +0x70, 0x69, 0x67, 0xd83d, 0xdc3d, 0x3a, 0x70, 0x69, 0x67, 0x5f, 0x6e, 0x6f, +0x73, 0x65, 0x3a, 0x6e, 0x6f, 0x73, 0x65, 0x70, 0x69, 0x67, 0xd83d, 0xdc38, +0x3a, 0x66, 0x72, 0x6f, 0x67, 0x3a, 0x66, 0x72, 0x6f, 0x67, 0xd83d, 0xdc35, +0x3a, 0x6d, 0x6f, 0x6e, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x61, 0x63, 0x65, +0x3a, 0x66, 0x61, 0x63, 0x65, 0x6d, 0x6f, 0x6e, 0x6b, 0x65, 0x79, 0xd83d, +0xde48, 0x3a, 0x73, 0x65, 0x65, 0x5f, 0x6e, 0x6f, 0x5f, 0x65, 0x76, 0x69, +0x6c, 0x3a, 0x65, 0x76, 0x69, 0x6c, 0x6e, 0x6f, 0x73, 0x65, 0x65, 0xd83d, +0xde49, 0x3a, 0x68, 0x65, 0x61, 0x72, 0x5f, 0x6e, 0x6f, 0x5f, 0x65, 0x76, +0x69, 0x6c, 0x3a, 0x65, 0x76, 0x69, 0x6c, 0x68, 0x65, 0x61, 0x72, 0x6e, +0x6f, 0xd83d, 0xde4a, 0x3a, 0x73, 0x70, 0x65, 0x61, 0x6b, 0x5f, 0x6e, 0x6f, +0x5f, 0x65, 0x76, 0x69, 0x6c, 0x3a, 0x65, 0x76, 0x69, 0x6c, 0x6e, 0x6f, +0x73, 0x70, 0x65, 0x61, 0x6b, 0xd83d, 0xdc12, 0x3a, 0x6d, 0x6f, 0x6e, 0x6b, +0x65, 0x79, 0x3a, 0x6d, 0x6f, 0x6e, 0x6b, 0x65, 0x79, 0xd83d, 0xdc14, 0x3a, +0x63, 0x68, 0x69, 0x63, 0x6b, 0x65, 0x6e, 0x3a, 0x63, 0x68, 0x69, 0x63, +0x6b, 0x65, 0x6e, 0xd83d, 0xdc27, 0x3a, 0x70, 0x65, 0x6e, 0x67, 0x75, 0x69, +0x6e, 0x3a, 0x70, 0x65, 0x6e, 0x67, 0x75, 0x69, 0x6e, 0xd83d, 0xdc26, 0x3a, +0x62, 0x69, 0x72, 0x64, 0x3a, 0x62, 0x69, 0x72, 0x64, 0xd83d, 0xdc24, 0x3a, +0x62, 0x61, 0x62, 0x79, 0x5f, 0x63, 0x68, 0x69, 0x63, 0x6b, 0x3a, 0x62, +0x61, 0x62, 0x79, 0x63, 0x68, 0x69, 0x63, 0x6b, 0xd83d, 0xdc23, 0x3a, 0x68, +0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x69, 0x63, +0x6b, 0x3a, 0x63, 0x68, 0x69, 0x63, 0x6b, 0x68, 0x61, 0x74, 0x63, 0x68, +0x69, 0x6e, 0x67, 0xd83d, 0xdc25, 0x3a, 0x68, 0x61, 0x74, 0x63, 0x68, 0x65, +0x64, 0x5f, 0x63, 0x68, 0x69, 0x63, 0x6b, 0x3a, 0x63, 0x68, 0x69, 0x63, +0x6b, 0x68, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0xd83e, 0xdd86, 0x3a, 0x64, +0x75, 0x63, 0x6b, 0x3a, 0x64, 0x75, 0x63, 0x6b, 0xd83e, 0xdd85, 0x3a, 0x65, +0x61, 0x67, 0x6c, 0x65, 0x3a, 0x65, 0x61, 0x67, 0x6c, 0x65, 0xd83e, 0xdd89, +0x3a, 0x6f, 0x77, 0x6c, 0x3a, 0x6f, 0x77, 0x6c, 0xd83e, 0xdd87, 0x3a, 0x62, +0x61, 0x74, 0x3a, 0x62, 0x61, 0x74, 0xd83d, 0xdc3a, 0x3a, 0x77, 0x6f, 0x6c, +0x66, 0x3a, 0x77, 0x6f, 0x6c, 0x66, 0xd83d, 0xdc17, 0x3a, 0x62, 0x6f, 0x61, +0x72, 0x3a, 0x62, 0x6f, 0x61, 0x72, 0xd83d, 0xdc34, 0x3a, 0x68, 0x6f, 0x72, +0x73, 0x65, 0x3a, 0x68, 0x6f, 0x72, 0x73, 0x65, 0xd83e, 0xdd84, 0x3a, 0x75, +0x6e, 0x69, 0x63, 0x6f, 0x72, 0x6e, 0x5f, 0x66, 0x61, 0x63, 0x65, 0x3a, +0x66, 0x61, 0x63, 0x65, 0x75, 0x6e, 0x69, 0x63, 0x6f, 0x72, 0x6e, 0xd83e, +0xdd84, 0x3a, 0x75, 0x6e, 0x69, 0x63, 0x6f, 0x72, 0x6e, 0x3a, 0x75, 0x6e, +0x69, 0x63, 0x6f, 0x72, 0x6e, 0xd83d, 0xdc1d, 0x3a, 0x62, 0x65, 0x65, 0x3a, +0x62, 0x65, 0x65, 0xd83d, 0xdc1b, 0x3a, 0x62, 0x75, 0x67, 0x3a, 0x62, 0x75, +0x67, 0xd83e, 0xdd8b, 0x3a, 0x62, 0x75, 0x74, 0x74, 0x65, 0x72, 0x66, 0x6c, +0x79, 0x3a, 0x62, 0x75, 0x74, 0x74, 0x65, 0x72, 0x66, 0x6c, 0x79, 0xd83d, +0xdc0c, 0x3a, 0x73, 0x6e, 0x61, 0x69, 0x6c, 0x3a, 0x73, 0x6e, 0x61, 0x69, +0x6c, 0xd83d, 0xdc1a, 0x3a, 0x73, 0x68, 0x65, 0x6c, 0x6c, 0x3a, 0x73, 0x68, +0x65, 0x6c, 0x6c, 0xd83d, 0xdc1e, 0x3a, 0x62, 0x65, 0x65, 0x74, 0x6c, 0x65, +0x3a, 0x62, 0x65, 0x65, 0x74, 0x6c, 0x65, 0xd83d, 0xdc1c, 0x3a, 0x61, 0x6e, +0x74, 0x3a, 0x61, 0x6e, 0x74, 0xd83d, 0xdd77, 0x3a, 0x73, 0x70, 0x69, 0x64, +0x65, 0x72, 0x3a, 0x73, 0x70, 0x69, 0x64, 0x65, 0x72, 0xd83d, 0xdd78, 0x3a, +0x73, 0x70, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x77, 0x65, 0x62, 0x3a, 0x73, +0x70, 0x69, 0x64, 0x65, 0x72, 0x77, 0x65, 0x62, 0xd83d, 0xdc22, 0x3a, 0x74, +0x75, 0x72, 0x74, 0x6c, 0x65, 0x3a, 0x74, 0x75, 0x72, 0x74, 0x6c, 0x65, +0xd83d, 0xdc0d, 0x3a, 0x73, 0x6e, 0x61, 0x6b, 0x65, 0x3a, 0x73, 0x6e, 0x61, +0x6b, 0x65, 0xd83e, 0xdd8e, 0x3a, 0x6c, 0x69, 0x7a, 0x61, 0x72, 0x64, 0x3a, +0x6c, 0x69, 0x7a, 0x61, 0x72, 0x64, 0xd83e, 0xdd82, 0x3a, 0x73, 0x63, 0x6f, +0x72, 0x70, 0x69, 0x6f, 0x6e, 0x3a, 0x73, 0x63, 0x6f, 0x72, 0x70, 0x69, +0x6f, 0x6e, 0xd83e, 0xdd80, 0x3a, 0x63, 0x72, 0x61, 0x62, 0x3a, 0x63, 0x72, +0x61, 0x62, 0xd83e, 0xdd91, 0x3a, 0x73, 0x71, 0x75, 0x69, 0x64, 0x3a, 0x73, +0x71, 0x75, 0x69, 0x64, 0xd83d, 0xdc19, 0x3a, 0x6f, 0x63, 0x74, 0x6f, 0x70, +0x75, 0x73, 0x3a, 0x6f, 0x63, 0x74, 0x6f, 0x70, 0x75, 0x73, 0xd83e, 0xdd90, +0x3a, 0x73, 0x68, 0x72, 0x69, 0x6d, 0x70, 0x3a, 0x73, 0x68, 0x72, 0x69, +0x6d, 0x70, 0xd83d, 0xdc20, 0x3a, 0x74, 0x72, 0x6f, 0x70, 0x69, 0x63, 0x61, +0x6c, 0x5f, 0x66, 0x69, 0x73, 0x68, 0x3a, 0x66, 0x69, 0x73, 0x68, 0x74, +0x72, 0x6f, 0x70, 0x69, 0x63, 0x61, 0x6c, 0xd83d, 0xdc1f, 0x3a, 0x66, 0x69, +0x73, 0x68, 0x3a, 0x66, 0x69, 0x73, 0x68, 0xd83d, 0xdc21, 0x3a, 0x62, 0x6c, +0x6f, 0x77, 0x66, 0x69, 0x73, 0x68, 0x3a, 0x62, 0x6c, 0x6f, 0x77, 0x66, +0x69, 0x73, 0x68, 0xd83d, 0xdc2c, 0x3a, 0x64, 0x6f, 0x6c, 0x70, 0x68, 0x69, +0x6e, 0x3a, 0x64, 0x6f, 0x6c, 0x70, 0x68, 0x69, 0x6e, 0xd83e, 0xdd88, 0x3a, +0x73, 0x68, 0x61, 0x72, 0x6b, 0x3a, 0x73, 0x68, 0x61, 0x72, 0x6b, 0xd83d, +0xdc33, 0x3a, 0x77, 0x68, 0x61, 0x6c, 0x65, 0x3a, 0x77, 0x68, 0x61, 0x6c, +0x65, 0xd83d, 0xdc0b, 0x3a, 0x77, 0x68, 0x61, 0x6c, 0x65, 0x32, 0x3a, 0x77, +0x68, 0x61, 0x6c, 0x65, 0x32, 0xd83d, 0xdc0a, 0x3a, 0x63, 0x72, 0x6f, 0x63, +0x6f, 0x64, 0x69, 0x6c, 0x65, 0x3a, 0x63, 0x72, 0x6f, 0x63, 0x6f, 0x64, +0x69, 0x6c, 0x65, 0xd83d, 0xdc06, 0x3a, 0x6c, 0x65, 0x6f, 0x70, 0x61, 0x72, +0x64, 0x3a, 0x6c, 0x65, 0x6f, 0x70, 0x61, 0x72, 0x64, 0xd83d, 0xdc05, 0x3a, +0x74, 0x69, 0x67, 0x65, 0x72, 0x32, 0x3a, 0x74, 0x69, 0x67, 0x65, 0x72, +0x32, 0xd83d, 0xdc03, 0x3a, 0x77, 0x61, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x75, +0x66, 0x66, 0x61, 0x6c, 0x6f, 0x3a, 0x62, 0x75, 0x66, 0x66, 0x61, 0x6c, +0x6f, 0x77, 0x61, 0x74, 0x65, 0x72, 0xd83d, 0xdc02, 0x3a, 0x6f, 0x78, 0x3a, +0x6f, 0x78, 0xd83d, 0xdc04, 0x3a, 0x63, 0x6f, 0x77, 0x32, 0x3a, 0x63, 0x6f, +0x77, 0x32, 0xd83e, 0xdd8c, 0x3a, 0x64, 0x65, 0x65, 0x72, 0x3a, 0x64, 0x65, +0x65, 0x72, 0xd83d, 0xdc2a, 0x3a, 0x64, 0x72, 0x6f, 0x6d, 0x65, 0x64, 0x61, +0x72, 0x79, 0x5f, 0x63, 0x61, 0x6d, 0x65, 0x6c, 0x3a, 0x63, 0x61, 0x6d, +0x65, 0x6c, 0x64, 0x72, 0x6f, 0x6d, 0x65, 0x64, 0x61, 0x72, 0x79, 0xd83d, +0xdc2b, 0x3a, 0x63, 0x61, 0x6d, 0x65, 0x6c, 0x3a, 0x63, 0x61, 0x6d, 0x65, +0x6c, 0xd83d, 0xdc18, 0x3a, 0x65, 0x6c, 0x65, 0x70, 0x68, 0x61, 0x6e, 0x74, +0x3a, 0x65, 0x6c, 0x65, 0x70, 0x68, 0x61, 0x6e, 0x74, 0xd83e, 0xdd8f, 0x3a, +0x72, 0x68, 0x69, 0x6e, 0x6f, 0x63, 0x65, 0x72, 0x6f, 0x73, 0x3a, 0x72, +0x68, 0x69, 0x6e, 0x6f, 0x63, 0x65, 0x72, 0x6f, 0x73, 0xd83e, 0xdd8f, 0x3a, +0x72, 0x68, 0x69, 0x6e, 0x6f, 0x3a, 0x72, 0x68, 0x69, 0x6e, 0x6f, 0xd83e, +0xdd8d, 0x3a, 0x67, 0x6f, 0x72, 0x69, 0x6c, 0x6c, 0x61, 0x3a, 0x67, 0x6f, +0x72, 0x69, 0x6c, 0x6c, 0x61, 0xd83d, 0xdc0e, 0x3a, 0x72, 0x61, 0x63, 0x65, +0x68, 0x6f, 0x72, 0x73, 0x65, 0x3a, 0x72, 0x61, 0x63, 0x65, 0x68, 0x6f, +0x72, 0x73, 0x65, 0xd83d, 0xdc16, 0x3a, 0x70, 0x69, 0x67, 0x32, 0x3a, 0x70, +0x69, 0x67, 0x32, 0xd83d, 0xdc10, 0x3a, 0x67, 0x6f, 0x61, 0x74, 0x3a, 0x67, +0x6f, 0x61, 0x74, 0xd83d, 0xdc0f, 0x3a, 0x72, 0x61, 0x6d, 0x3a, 0x72, 0x61, +0x6d, 0xd83d, 0xdc11, 0x3a, 0x73, 0x68, 0x65, 0x65, 0x70, 0x3a, 0x73, 0x68, +0x65, 0x65, 0x70, 0xd83d, 0xdc15, 0x3a, 0x64, 0x6f, 0x67, 0x32, 0x3a, 0x64, +0x6f, 0x67, 0x32, 0xd83d, 0xdc29, 0x3a, 0x70, 0x6f, 0x6f, 0x64, 0x6c, 0x65, +0x3a, 0x70, 0x6f, 0x6f, 0x64, 0x6c, 0x65, 0xd83d, 0xdc08, 0x3a, 0x63, 0x61, +0x74, 0x32, 0x3a, 0x63, 0x61, 0x74, 0x32, 0xd83d, 0xdc13, 0x3a, 0x72, 0x6f, +0x6f, 0x73, 0x74, 0x65, 0x72, 0x3a, 0x72, 0x6f, 0x6f, 0x73, 0x74, 0x65, +0x72, 0xd83e, 0xdd83, 0x3a, 0x74, 0x75, 0x72, 0x6b, 0x65, 0x79, 0x3a, 0x74, +0x75, 0x72, 0x6b, 0x65, 0x79, 0xd83d, 0xdd4a, 0x3a, 0x64, 0x6f, 0x76, 0x65, +0x5f, 0x6f, 0x66, 0x5f, 0x70, 0x65, 0x61, 0x63, 0x65, 0x3a, 0x64, 0x6f, +0x76, 0x65, 0x6f, 0x66, 0x70, 0x65, 0x61, 0x63, 0x65, 0xd83d, 0xdd4a, 0x3a, +0x64, 0x6f, 0x76, 0x65, 0x3a, 0x64, 0x6f, 0x76, 0x65, 0xd83d, 0xdc07, 0x3a, +0x72, 0x61, 0x62, 0x62, 0x69, 0x74, 0x32, 0x3a, 0x72, 0x61, 0x62, 0x62, +0x69, 0x74, 0x32, 0xd83d, 0xdc01, 0x3a, 0x6d, 0x6f, 0x75, 0x73, 0x65, 0x32, +0x3a, 0x6d, 0x6f, 0x75, 0x73, 0x65, 0x32, 0xd83d, 0xdc00, 0x3a, 0x72, 0x61, +0x74, 0x3a, 0x72, 0x61, 0x74, 0xd83d, 0xdc3f, 0x3a, 0x63, 0x68, 0x69, 0x70, +0x6d, 0x75, 0x6e, 0x6b, 0x3a, 0x63, 0x68, 0x69, 0x70, 0x6d, 0x75, 0x6e, +0x6b, 0xd83d, 0xdc3e, 0x3a, 0x70, 0x61, 0x77, 0x5f, 0x70, 0x72, 0x69, 0x6e, +0x74, 0x73, 0x3a, 0x70, 0x61, 0x77, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x73, +0xd83d, 0xdc3e, 0x3a, 0x66, 0x65, 0x65, 0x74, 0x3a, 0x66, 0x65, 0x65, 0x74, +0xd83d, 0xdc09, 0x3a, 0x64, 0x72, 0x61, 0x67, 0x6f, 0x6e, 0x3a, 0x64, 0x72, +0x61, 0x67, 0x6f, 0x6e, 0xd83d, 0xdc32, 0x3a, 0x64, 0x72, 0x61, 0x67, 0x6f, +0x6e, 0x5f, 0x66, 0x61, 0x63, 0x65, 0x3a, 0x64, 0x72, 0x61, 0x67, 0x6f, +0x6e, 0x66, 0x61, 0x63, 0x65, 0xd83c, 0xdf35, 0x3a, 0x63, 0x61, 0x63, 0x74, +0x75, 0x73, 0x3a, 0x63, 0x61, 0x63, 0x74, 0x75, 0x73, 0xd83c, 0xdf84, 0x3a, +0x63, 0x68, 0x72, 0x69, 0x73, 0x74, 0x6d, 0x61, 0x73, 0x5f, 0x74, 0x72, +0x65, 0x65, 0x3a, 0x63, 0x68, 0x72, 0x69, 0x73, 0x74, 0x6d, 0x61, 0x73, +0x74, 0x72, 0x65, 0x65, 0xd83c, 0xdf32, 0x3a, 0x65, 0x76, 0x65, 0x72, 0x67, +0x72, 0x65, 0x65, 0x6e, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x3a, 0x65, 0x76, +0x65, 0x72, 0x67, 0x72, 0x65, 0x65, 0x6e, 0x74, 0x72, 0x65, 0x65, 0xd83c, +0xdf33, 0x3a, 0x64, 0x65, 0x63, 0x69, 0x64, 0x75, 0x6f, 0x75, 0x73, 0x5f, +0x74, 0x72, 0x65, 0x65, 0x3a, 0x64, 0x65, 0x63, 0x69, 0x64, 0x75, 0x6f, +0x75, 0x73, 0x74, 0x72, 0x65, 0x65, 0xd83c, 0xdf34, 0x3a, 0x70, 0x61, 0x6c, +0x6d, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x3a, 0x70, 0x61, 0x6c, 0x6d, 0x74, +0x72, 0x65, 0x65, 0xd83c, 0xdf31, 0x3a, 0x73, 0x65, 0x65, 0x64, 0x6c, 0x69, +0x6e, 0x67, 0x3a, 0x73, 0x65, 0x65, 0x64, 0x6c, 0x69, 0x6e, 0x67, 0xd83c, +0xdf3f, 0x3a, 0x68, 0x65, 0x72, 0x62, 0x3a, 0x68, 0x65, 0x72, 0x62, 0x2618, +0xfe0f, 0x3a, 0x73, 0x68, 0x61, 0x6d, 0x72, 0x6f, 0x63, 0x6b, 0x3a, 0x73, +0x68, 0x61, 0x6d, 0x72, 0x6f, 0x63, 0x6b, 0xd83c, 0xdf40, 0x3a, 0x66, 0x6f, +0x75, 0x72, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x63, 0x6c, 0x6f, 0x76, +0x65, 0x72, 0x3a, 0x63, 0x6c, 0x6f, 0x76, 0x65, 0x72, 0x66, 0x6f, 0x75, +0x72, 0x6c, 0x65, 0x61, 0x66, 0xd83c, 0xdf8d, 0x3a, 0x62, 0x61, 0x6d, 0x62, +0x6f, 0x6f, 0x3a, 0x62, 0x61, 0x6d, 0x62, 0x6f, 0x6f, 0xd83c, 0xdf8b, 0x3a, +0x74, 0x61, 0x6e, 0x61, 0x62, 0x61, 0x74, 0x61, 0x5f, 0x74, 0x72, 0x65, +0x65, 0x3a, 0x74, 0x61, 0x6e, 0x61, 0x62, 0x61, 0x74, 0x61, 0x74, 0x72, +0x65, 0x65, 0xd83c, 0xdf43, 0x3a, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x3a, +0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0xd83c, 0xdf42, 0x3a, 0x66, 0x61, 0x6c, +0x6c, 0x65, 0x6e, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x3a, 0x66, 0x61, 0x6c, +0x6c, 0x65, 0x6e, 0x6c, 0x65, 0x61, 0x66, 0xd83c, 0xdf41, 0x3a, 0x6d, 0x61, +0x70, 0x6c, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x3a, 0x6c, 0x65, 0x61, +0x66, 0x6d, 0x61, 0x70, 0x6c, 0x65, 0xd83c, 0xdf44, 0x3a, 0x6d, 0x75, 0x73, +0x68, 0x72, 0x6f, 0x6f, 0x6d, 0x3a, 0x6d, 0x75, 0x73, 0x68, 0x72, 0x6f, +0x6f, 0x6d, 0xd83c, 0xdf3e, 0x3a, 0x65, 0x61, 0x72, 0x5f, 0x6f, 0x66, 0x5f, +0x72, 0x69, 0x63, 0x65, 0x3a, 0x65, 0x61, 0x72, 0x6f, 0x66, 0x72, 0x69, +0x63, 0x65, 0xd83d, 0xdc90, 0x3a, 0x62, 0x6f, 0x75, 0x71, 0x75, 0x65, 0x74, +0x3a, 0x62, 0x6f, 0x75, 0x71, 0x75, 0x65, 0x74, 0xd83c, 0xdf37, 0x3a, 0x74, +0x75, 0x6c, 0x69, 0x70, 0x3a, 0x74, 0x75, 0x6c, 0x69, 0x70, 0xd83c, 0xdf39, +0x3a, 0x72, 0x6f, 0x73, 0x65, 0x3a, 0x72, 0x6f, 0x73, 0x65, 0xd83e, 0xdd40, +0x3a, 0x77, 0x69, 0x6c, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x6c, 0x6f, 0x77, +0x65, 0x72, 0x3a, 0x66, 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x77, 0x69, 0x6c, +0x74, 0x65, 0x64, 0xd83e, 0xdd40, 0x3a, 0x77, 0x69, 0x6c, 0x74, 0x65, 0x64, +0x5f, 0x72, 0x6f, 0x73, 0x65, 0x3a, 0x72, 0x6f, 0x73, 0x65, 0x77, 0x69, +0x6c, 0x74, 0x65, 0x64, 0xd83c, 0xdf3b, 0x3a, 0x73, 0x75, 0x6e, 0x66, 0x6c, +0x6f, 0x77, 0x65, 0x72, 0x3a, 0x73, 0x75, 0x6e, 0x66, 0x6c, 0x6f, 0x77, +0x65, 0x72, 0xd83c, 0xdf3c, 0x3a, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, +0x3a, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0xd83c, 0xdf38, 0x3a, 0x63, +0x68, 0x65, 0x72, 0x72, 0x79, 0x5f, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, +0x6d, 0x3a, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x63, 0x68, 0x65, +0x72, 0x72, 0x79, 0xd83c, 0xdf3a, 0x3a, 0x68, 0x69, 0x62, 0x69, 0x73, 0x63, +0x75, 0x73, 0x3a, 0x68, 0x69, 0x62, 0x69, 0x73, 0x63, 0x75, 0x73, 0xd83c, +0xdf0e, 0x3a, 0x65, 0x61, 0x72, 0x74, 0x68, 0x5f, 0x61, 0x6d, 0x65, 0x72, +0x69, 0x63, 0x61, 0x73, 0x3a, 0x61, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, +0x73, 0x65, 0x61, 0x72, 0x74, 0x68, 0xd83c, 0xdf0d, 0x3a, 0x65, 0x61, 0x72, +0x74, 0x68, 0x5f, 0x61, 0x66, 0x72, 0x69, 0x63, 0x61, 0x3a, 0x61, 0x66, +0x72, 0x69, 0x63, 0x61, 0x65, 0x61, 0x72, 0x74, 0x68, 0xd83c, 0xdf0f, 0x3a, +0x65, 0x61, 0x72, 0x74, 0x68, 0x5f, 0x61, 0x73, 0x69, 0x61, 0x3a, 0x61, +0x73, 0x69, 0x61, 0x65, 0x61, 0x72, 0x74, 0x68, 0xd83c, 0xdf15, 0x3a, 0x66, +0x75, 0x6c, 0x6c, 0x5f, 0x6d, 0x6f, 0x6f, 0x6e, 0x3a, 0x66, 0x75, 0x6c, +0x6c, 0x6d, 0x6f, 0x6f, 0x6e, 0xd83c, 0xdf16, 0x3a, 0x77, 0x61, 0x6e, 0x69, +0x6e, 0x67, 0x5f, 0x67, 0x69, 0x62, 0x62, 0x6f, 0x75, 0x73, 0x5f, 0x6d, +0x6f, 0x6f, 0x6e, 0x3a, 0x67, 0x69, 0x62, 0x62, 0x6f, 0x75, 0x73, 0x6d, +0x6f, 0x6f, 0x6e, 0x77, 0x61, 0x6e, 0x69, 0x6e, 0x67, 0xd83c, 0xdf17, 0x3a, +0x6c, 0x61, 0x73, 0x74, 0x5f, 0x71, 0x75, 0x61, 0x72, 0x74, 0x65, 0x72, +0x5f, 0x6d, 0x6f, 0x6f, 0x6e, 0x3a, 0x6c, 0x61, 0x73, 0x74, 0x6d, 0x6f, +0x6f, 0x6e, 0x71, 0x75, 0x61, 0x72, 0x74, 0x65, 0x72, 0xd83c, 0xdf18, 0x3a, +0x77, 0x61, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x72, 0x65, 0x73, 0x63, +0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x6f, 0x6f, 0x6e, 0x3a, 0x63, 0x72, 0x65, +0x73, 0x63, 0x65, 0x6e, 0x74, 0x6d, 0x6f, 0x6f, 0x6e, 0x77, 0x61, 0x6e, +0x69, 0x6e, 0x67, 0xd83c, 0xdf11, 0x3a, 0x6e, 0x65, 0x77, 0x5f, 0x6d, 0x6f, +0x6f, 0x6e, 0x3a, 0x6d, 0x6f, 0x6f, 0x6e, 0x6e, 0x65, 0x77, 0xd83c, 0xdf12, +0x3a, 0x77, 0x61, 0x78, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x72, 0x65, 0x73, +0x63, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x6f, 0x6f, 0x6e, 0x3a, 0x63, 0x72, +0x65, 0x73, 0x63, 0x65, 0x6e, 0x74, 0x6d, 0x6f, 0x6f, 0x6e, 0x77, 0x61, +0x78, 0x69, 0x6e, 0x67, 0xd83c, 0xdf13, 0x3a, 0x66, 0x69, 0x72, 0x73, 0x74, +0x5f, 0x71, 0x75, 0x61, 0x72, 0x74, 0x65, 0x72, 0x5f, 0x6d, 0x6f, 0x6f, +0x6e, 0x3a, 0x66, 0x69, 0x72, 0x73, 0x74, 0x6d, 0x6f, 0x6f, 0x6e, 0x71, +0x75, 0x61, 0x72, 0x74, 0x65, 0x72, 0xd83c, 0xdf14, 0x3a, 0x77, 0x61, 0x78, +0x69, 0x6e, 0x67, 0x5f, 0x67, 0x69, 0x62, 0x62, 0x6f, 0x75, 0x73, 0x5f, +0x6d, 0x6f, 0x6f, 0x6e, 0x3a, 0x67, 0x69, 0x62, 0x62, 0x6f, 0x75, 0x73, +0x6d, 0x6f, 0x6f, 0x6e, 0x77, 0x61, 0x78, 0x69, 0x6e, 0x67, 0xd83c, 0xdf1a, +0x3a, 0x6e, 0x65, 0x77, 0x5f, 0x6d, 0x6f, 0x6f, 0x6e, 0x5f, 0x77, 0x69, +0x74, 0x68, 0x5f, 0x66, 0x61, 0x63, 0x65, 0x3a, 0x66, 0x61, 0x63, 0x65, +0x6d, 0x6f, 0x6f, 0x6e, 0x6e, 0x65, 0x77, 0x77, 0x69, 0x74, 0x68, 0xd83c, +0xdf1d, 0x3a, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x6d, 0x6f, 0x6f, 0x6e, 0x5f, +0x77, 0x69, 0x74, 0x68, 0x5f, 0x66, 0x61, 0x63, 0x65, 0x3a, 0x66, 0x61, +0x63, 0x65, 0x66, 0x75, 0x6c, 0x6c, 0x6d, 0x6f, 0x6f, 0x6e, 0x77, 0x69, +0x74, 0x68, 0xd83c, 0xdf1e, 0x3a, 0x73, 0x75, 0x6e, 0x5f, 0x77, 0x69, 0x74, +0x68, 0x5f, 0x66, 0x61, 0x63, 0x65, 0x3a, 0x66, 0x61, 0x63, 0x65, 0x73, +0x75, 0x6e, 0x77, 0x69, 0x74, 0x68, 0xd83c, 0xdf1b, 0x3a, 0x66, 0x69, 0x72, +0x73, 0x74, 0x5f, 0x71, 0x75, 0x61, 0x72, 0x74, 0x65, 0x72, 0x5f, 0x6d, +0x6f, 0x6f, 0x6e, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x66, 0x61, 0x63, +0x65, 0x3a, 0x66, 0x61, 0x63, 0x65, 0x66, 0x69, 0x72, 0x73, 0x74, 0x6d, +0x6f, 0x6f, 0x6e, 0x71, 0x75, 0x61, 0x72, 0x74, 0x65, 0x72, 0x77, 0x69, +0x74, 0x68, 0xd83c, 0xdf1c, 0x3a, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x71, 0x75, +0x61, 0x72, 0x74, 0x65, 0x72, 0x5f, 0x6d, 0x6f, 0x6f, 0x6e, 0x5f, 0x77, +0x69, 0x74, 0x68, 0x5f, 0x66, 0x61, 0x63, 0x65, 0x3a, 0x66, 0x61, 0x63, +0x65, 0x6c, 0x61, 0x73, 0x74, 0x6d, 0x6f, 0x6f, 0x6e, 0x71, 0x75, 0x61, +0x72, 0x74, 0x65, 0x72, 0x77, 0x69, 0x74, 0x68, 0xd83c, 0xdf19, 0x3a, 0x63, +0x72, 0x65, 0x73, 0x63, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x6f, 0x6f, 0x6e, +0x3a, 0x63, 0x72, 0x65, 0x73, 0x63, 0x65, 0x6e, 0x74, 0x6d, 0x6f, 0x6f, +0x6e, 0xd83d, 0xdcab, 0x3a, 0x64, 0x69, 0x7a, 0x7a, 0x79, 0x3a, 0x64, 0x69, +0x7a, 0x7a, 0x79, 0x2b50, 0xfe0f, 0x3a, 0x73, 0x74, 0x61, 0x72, 0x3a, 0x73, +0x74, 0x61, 0x72, 0xd83c, 0xdf1f, 0x3a, 0x73, 0x74, 0x61, 0x72, 0x32, 0x3a, +0x73, 0x74, 0x61, 0x72, 0x32, 0x2728, 0x3a, 0x73, 0x70, 0x61, 0x72, 0x6b, +0x6c, 0x65, 0x73, 0x3a, 0x73, 0x70, 0x61, 0x72, 0x6b, 0x6c, 0x65, 0x73, +0x26a1, 0xfe0f, 0x3a, 0x7a, 0x61, 0x70, 0x3a, 0x7a, 0x61, 0x70, 0xd83d, 0xdd25, +0x3a, 0x66, 0x6c, 0x61, 0x6d, 0x65, 0x3a, 0x66, 0x6c, 0x61, 0x6d, 0x65, +0xd83d, 0xdd25, 0x3a, 0x66, 0x69, 0x72, 0x65, 0x3a, 0x66, 0x69, 0x72, 0x65, +0xd83d, 0xdca5, 0x3a, 0x62, 0x6f, 0x6f, 0x6d, 0x3a, 0x62, 0x6f, 0x6f, 0x6d, +0x2604, 0xfe0f, 0x3a, 0x63, 0x6f, 0x6d, 0x65, 0x74, 0x3a, 0x63, 0x6f, 0x6d, +0x65, 0x74, 0x2600, 0xfe0f, 0x3a, 0x73, 0x75, 0x6e, 0x6e, 0x79, 0x3a, 0x73, +0x75, 0x6e, 0x6e, 0x79, 0xd83c, 0xdf24, 0x3a, 0x77, 0x68, 0x69, 0x74, 0x65, +0x5f, 0x73, 0x75, 0x6e, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x73, 0x6d, +0x61, 0x6c, 0x6c, 0x5f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x3a, 0x63, 0x6c, +0x6f, 0x75, 0x64, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x73, 0x75, 0x6e, 0x77, +0x68, 0x69, 0x74, 0x65, 0x77, 0x69, 0x74, 0x68, 0xd83c, 0xdf24, 0x3a, 0x77, +0x68, 0x69, 0x74, 0x65, 0x5f, 0x73, 0x75, 0x6e, 0x5f, 0x73, 0x6d, 0x61, +0x6c, 0x6c, 0x5f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x3a, 0x63, 0x6c, 0x6f, +0x75, 0x64, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x73, 0x75, 0x6e, 0x77, 0x68, +0x69, 0x74, 0x65, 0x26c5, 0xfe0f, 0x3a, 0x70, 0x61, 0x72, 0x74, 0x6c, 0x79, +0x5f, 0x73, 0x75, 0x6e, 0x6e, 0x79, 0x3a, 0x70, 0x61, 0x72, 0x74, 0x6c, +0x79, 0x73, 0x75, 0x6e, 0x6e, 0x79, 0xd83c, 0xdf25, 0x3a, 0x77, 0x68, 0x69, +0x74, 0x65, 0x5f, 0x73, 0x75, 0x6e, 0x5f, 0x62, 0x65, 0x68, 0x69, 0x6e, +0x64, 0x5f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x3a, 0x62, 0x65, 0x68, 0x69, +0x6e, 0x64, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x73, 0x75, 0x6e, 0x77, 0x68, +0x69, 0x74, 0x65, 0xd83c, 0xdf25, 0x3a, 0x77, 0x68, 0x69, 0x74, 0x65, 0x5f, +0x73, 0x75, 0x6e, 0x5f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x3a, 0x63, 0x6c, +0x6f, 0x75, 0x64, 0x73, 0x75, 0x6e, 0x77, 0x68, 0x69, 0x74, 0x65, 0xd83c, +0xdf26, 0x3a, 0x77, 0x68, 0x69, 0x74, 0x65, 0x5f, 0x73, 0x75, 0x6e, 0x5f, +0x62, 0x65, 0x68, 0x69, 0x6e, 0x64, 0x5f, 0x63, 0x6c, 0x6f, 0x75, 0x64, +0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x72, 0x61, 0x69, 0x6e, 0x3a, 0x62, +0x65, 0x68, 0x69, 0x6e, 0x64, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x72, 0x61, +0x69, 0x6e, 0x73, 0x75, 0x6e, 0x77, 0x68, 0x69, 0x74, 0x65, 0x77, 0x69, +0x74, 0x68, 0xd83c, 0xdf26, 0x3a, 0x77, 0x68, 0x69, 0x74, 0x65, 0x5f, 0x73, +0x75, 0x6e, 0x5f, 0x72, 0x61, 0x69, 0x6e, 0x5f, 0x63, 0x6c, 0x6f, 0x75, +0x64, 0x3a, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x72, 0x61, 0x69, 0x6e, 0x73, +0x75, 0x6e, 0x77, 0x68, 0x69, 0x74, 0x65, 0xd83c, 0xdf08, 0x3a, 0x72, 0x61, +0x69, 0x6e, 0x62, 0x6f, 0x77, 0x3a, 0x72, 0x61, 0x69, 0x6e, 0x62, 0x6f, +0x77, 0x2601, 0xfe0f, 0x3a, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x3a, 0x63, 0x6c, +0x6f, 0x75, 0x64, 0xd83c, 0xdf27, 0x3a, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, +0x77, 0x69, 0x74, 0x68, 0x5f, 0x72, 0x61, 0x69, 0x6e, 0x3a, 0x63, 0x6c, +0x6f, 0x75, 0x64, 0x72, 0x61, 0x69, 0x6e, 0x77, 0x69, 0x74, 0x68, 0xd83c, +0xdf27, 0x3a, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x72, 0x61, 0x69, 0x6e, +0x3a, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x72, 0x61, 0x69, 0x6e, 0x26c8, 0x3a, +0x74, 0x68, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6c, 0x6f, 0x75, +0x64, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x72, 0x61, 0x69, 0x6e, 0x3a, 0x61, +0x6e, 0x64, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x72, 0x61, 0x69, 0x6e, 0x74, +0x68, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x26c8, 0x3a, 0x74, 0x68, 0x75, 0x6e, +0x64, 0x65, 0x72, 0x5f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x72, 0x61, +0x69, 0x6e, 0x3a, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x72, 0x61, 0x69, 0x6e, +0x74, 0x68, 0x75, 0x6e, 0x64, 0x65, 0x72, 0xd83c, 0xdf29, 0x3a, 0x63, 0x6c, +0x6f, 0x75, 0x64, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x6c, 0x69, 0x67, +0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x3a, 0x63, 0x6c, 0x6f, 0x75, 0x64, +0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x77, 0x69, 0x74, +0x68, 0xd83c, 0xdf29, 0x3a, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x6c, 0x69, +0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x3a, 0x63, 0x6c, 0x6f, 0x75, +0x64, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0xd83c, 0xdf28, +0x3a, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, +0x73, 0x6e, 0x6f, 0x77, 0x3a, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x73, 0x6e, +0x6f, 0x77, 0x77, 0x69, 0x74, 0x68, 0xd83c, 0xdf28, 0x3a, 0x63, 0x6c, 0x6f, +0x75, 0x64, 0x5f, 0x73, 0x6e, 0x6f, 0x77, 0x3a, 0x63, 0x6c, 0x6f, 0x75, +0x64, 0x73, 0x6e, 0x6f, 0x77, 0x2603, 0xfe0f, 0x3a, 0x73, 0x6e, 0x6f, 0x77, +0x6d, 0x61, 0x6e, 0x32, 0x3a, 0x73, 0x6e, 0x6f, 0x77, 0x6d, 0x61, 0x6e, +0x32, 0x26c4, 0xfe0f, 0x3a, 0x73, 0x6e, 0x6f, 0x77, 0x6d, 0x61, 0x6e, 0x3a, +0x73, 0x6e, 0x6f, 0x77, 0x6d, 0x61, 0x6e, 0x2744, 0xfe0f, 0x3a, 0x73, 0x6e, +0x6f, 0x77, 0x66, 0x6c, 0x61, 0x6b, 0x65, 0x3a, 0x73, 0x6e, 0x6f, 0x77, +0x66, 0x6c, 0x61, 0x6b, 0x65, 0xd83c, 0xdf2c, 0x3a, 0x77, 0x69, 0x6e, 0x64, +0x5f, 0x62, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x61, 0x63, +0x65, 0x3a, 0x62, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x66, 0x61, 0x63, +0x65, 0x77, 0x69, 0x6e, 0x64, 0xd83d, 0xdca8, 0x3a, 0x64, 0x61, 0x73, 0x68, +0x3a, 0x64, 0x61, 0x73, 0x68, 0xd83c, 0xdf2a, 0x3a, 0x63, 0x6c, 0x6f, 0x75, +0x64, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x74, 0x6f, 0x72, 0x6e, 0x61, +0x64, 0x6f, 0x3a, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x74, 0x6f, 0x72, 0x6e, +0x61, 0x64, 0x6f, 0x77, 0x69, 0x74, 0x68, 0xd83c, 0xdf2a, 0x3a, 0x63, 0x6c, +0x6f, 0x75, 0x64, 0x5f, 0x74, 0x6f, 0x72, 0x6e, 0x61, 0x64, 0x6f, 0x3a, +0x63, 0x6c, 0x6f, 0x75, 0x64, 0x74, 0x6f, 0x72, 0x6e, 0x61, 0x64, 0x6f, +0xd83c, 0xdf2b, 0x3a, 0x66, 0x6f, 0x67, 0x3a, 0x66, 0x6f, 0x67, 0xd83c, 0xdf0a, +0x3a, 0x6f, 0x63, 0x65, 0x61, 0x6e, 0x3a, 0x6f, 0x63, 0x65, 0x61, 0x6e, +0xd83d, 0xdca7, 0x3a, 0x64, 0x72, 0x6f, 0x70, 0x6c, 0x65, 0x74, 0x3a, 0x64, +0x72, 0x6f, 0x70, 0x6c, 0x65, 0x74, 0xd83d, 0xdca6, 0x3a, 0x73, 0x77, 0x65, +0x61, 0x74, 0x5f, 0x64, 0x72, 0x6f, 0x70, 0x73, 0x3a, 0x64, 0x72, 0x6f, +0x70, 0x73, 0x73, 0x77, 0x65, 0x61, 0x74, 0x2614, 0xfe0f, 0x3a, 0x75, 0x6d, +0x62, 0x72, 0x65, 0x6c, 0x6c, 0x61, 0x3a, 0x75, 0x6d, 0x62, 0x72, 0x65, +0x6c, 0x6c, 0x61, 0xd83c, 0xdf4f, 0x3a, 0x67, 0x72, 0x65, 0x65, 0x6e, 0x5f, +0x61, 0x70, 0x70, 0x6c, 0x65, 0x3a, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x67, +0x72, 0x65, 0x65, 0x6e, 0xd83c, 0xdf4e, 0x3a, 0x61, 0x70, 0x70, 0x6c, 0x65, +0x3a, 0x61, 0x70, 0x70, 0x6c, 0x65, 0xd83c, 0xdf50, 0x3a, 0x70, 0x65, 0x61, +0x72, 0x3a, 0x70, 0x65, 0x61, 0x72, 0xd83c, 0xdf4a, 0x3a, 0x74, 0x61, 0x6e, +0x67, 0x65, 0x72, 0x69, 0x6e, 0x65, 0x3a, 0x74, 0x61, 0x6e, 0x67, 0x65, +0x72, 0x69, 0x6e, 0x65, 0xd83c, 0xdf4b, 0x3a, 0x6c, 0x65, 0x6d, 0x6f, 0x6e, +0x3a, 0x6c, 0x65, 0x6d, 0x6f, 0x6e, 0xd83c, 0xdf4c, 0x3a, 0x62, 0x61, 0x6e, +0x61, 0x6e, 0x61, 0x3a, 0x62, 0x61, 0x6e, 0x61, 0x6e, 0x61, 0xd83c, 0xdf49, +0x3a, 0x77, 0x61, 0x74, 0x65, 0x72, 0x6d, 0x65, 0x6c, 0x6f, 0x6e, 0x3a, +0x77, 0x61, 0x74, 0x65, 0x72, 0x6d, 0x65, 0x6c, 0x6f, 0x6e, 0xd83c, 0xdf47, +0x3a, 0x67, 0x72, 0x61, 0x70, 0x65, 0x73, 0x3a, 0x67, 0x72, 0x61, 0x70, +0x65, 0x73, 0xd83c, 0xdf53, 0x3a, 0x73, 0x74, 0x72, 0x61, 0x77, 0x62, 0x65, +0x72, 0x72, 0x79, 0x3a, 0x73, 0x74, 0x72, 0x61, 0x77, 0x62, 0x65, 0x72, +0x72, 0x79, 0xd83c, 0xdf48, 0x3a, 0x6d, 0x65, 0x6c, 0x6f, 0x6e, 0x3a, 0x6d, +0x65, 0x6c, 0x6f, 0x6e, 0xd83c, 0xdf52, 0x3a, 0x63, 0x68, 0x65, 0x72, 0x72, +0x69, 0x65, 0x73, 0x3a, 0x63, 0x68, 0x65, 0x72, 0x72, 0x69, 0x65, 0x73, +0xd83c, 0xdf51, 0x3a, 0x70, 0x65, 0x61, 0x63, 0x68, 0x3a, 0x70, 0x65, 0x61, +0x63, 0x68, 0xd83c, 0xdf4d, 0x3a, 0x70, 0x69, 0x6e, 0x65, 0x61, 0x70, 0x70, +0x6c, 0x65, 0x3a, 0x70, 0x69, 0x6e, 0x65, 0x61, 0x70, 0x70, 0x6c, 0x65, +0xd83e, 0xdd5d, 0x3a, 0x6b, 0x69, 0x77, 0x69, 0x66, 0x72, 0x75, 0x69, 0x74, +0x3a, 0x6b, 0x69, 0x77, 0x69, 0x66, 0x72, 0x75, 0x69, 0x74, 0xd83e, 0xdd5d, +0x3a, 0x6b, 0x69, 0x77, 0x69, 0x3a, 0x6b, 0x69, 0x77, 0x69, 0xd83e, 0xdd51, +0x3a, 0x61, 0x76, 0x6f, 0x63, 0x61, 0x64, 0x6f, 0x3a, 0x61, 0x76, 0x6f, +0x63, 0x61, 0x64, 0x6f, 0xd83c, 0xdf45, 0x3a, 0x74, 0x6f, 0x6d, 0x61, 0x74, +0x6f, 0x3a, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x6f, 0xd83c, 0xdf46, 0x3a, 0x65, +0x67, 0x67, 0x70, 0x6c, 0x61, 0x6e, 0x74, 0x3a, 0x65, 0x67, 0x67, 0x70, +0x6c, 0x61, 0x6e, 0x74, 0xd83e, 0xdd52, 0x3a, 0x63, 0x75, 0x63, 0x75, 0x6d, +0x62, 0x65, 0x72, 0x3a, 0x63, 0x75, 0x63, 0x75, 0x6d, 0x62, 0x65, 0x72, +0xd83e, 0xdd55, 0x3a, 0x63, 0x61, 0x72, 0x72, 0x6f, 0x74, 0x3a, 0x63, 0x61, +0x72, 0x72, 0x6f, 0x74, 0xd83c, 0xdf3d, 0x3a, 0x63, 0x6f, 0x72, 0x6e, 0x3a, +0x63, 0x6f, 0x72, 0x6e, 0xd83c, 0xdf36, 0x3a, 0x68, 0x6f, 0x74, 0x5f, 0x70, +0x65, 0x70, 0x70, 0x65, 0x72, 0x3a, 0x68, 0x6f, 0x74, 0x70, 0x65, 0x70, +0x70, 0x65, 0x72, 0xd83e, 0xdd54, 0x3a, 0x70, 0x6f, 0x74, 0x61, 0x74, 0x6f, +0x3a, 0x70, 0x6f, 0x74, 0x61, 0x74, 0x6f, 0xd83c, 0xdf60, 0x3a, 0x73, 0x77, +0x65, 0x65, 0x74, 0x5f, 0x70, 0x6f, 0x74, 0x61, 0x74, 0x6f, 0x3a, 0x70, +0x6f, 0x74, 0x61, 0x74, 0x6f, 0x73, 0x77, 0x65, 0x65, 0x74, 0xd83c, 0xdf30, +0x3a, 0x63, 0x68, 0x65, 0x73, 0x74, 0x6e, 0x75, 0x74, 0x3a, 0x63, 0x68, +0x65, 0x73, 0x74, 0x6e, 0x75, 0x74, 0xd83e, 0xdd5c, 0x3a, 0x73, 0x68, 0x65, +0x6c, 0x6c, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x61, 0x6e, 0x75, 0x74, 0x3a, +0x70, 0x65, 0x61, 0x6e, 0x75, 0x74, 0x73, 0x68, 0x65, 0x6c, 0x6c, 0x65, +0x64, 0xd83e, 0xdd5c, 0x3a, 0x70, 0x65, 0x61, 0x6e, 0x75, 0x74, 0x73, 0x3a, +0x70, 0x65, 0x61, 0x6e, 0x75, 0x74, 0x73, 0xd83c, 0xdf6f, 0x3a, 0x68, 0x6f, +0x6e, 0x65, 0x79, 0x5f, 0x70, 0x6f, 0x74, 0x3a, 0x68, 0x6f, 0x6e, 0x65, +0x79, 0x70, 0x6f, 0x74, 0xd83e, 0xdd50, 0x3a, 0x63, 0x72, 0x6f, 0x69, 0x73, +0x73, 0x61, 0x6e, 0x74, 0x3a, 0x63, 0x72, 0x6f, 0x69, 0x73, 0x73, 0x61, +0x6e, 0x74, 0xd83c, 0xdf5e, 0x3a, 0x62, 0x72, 0x65, 0x61, 0x64, 0x3a, 0x62, +0x72, 0x65, 0x61, 0x64, 0xd83e, 0xdd56, 0x3a, 0x62, 0x61, 0x67, 0x75, 0x65, +0x74, 0x74, 0x65, 0x5f, 0x62, 0x72, 0x65, 0x61, 0x64, 0x3a, 0x62, 0x61, +0x67, 0x75, 0x65, 0x74, 0x74, 0x65, 0x62, 0x72, 0x65, 0x61, 0x64, 0xd83e, +0xdd56, 0x3a, 0x66, 0x72, 0x65, 0x6e, 0x63, 0x68, 0x5f, 0x62, 0x72, 0x65, +0x61, 0x64, 0x3a, 0x62, 0x72, 0x65, 0x61, 0x64, 0x66, 0x72, 0x65, 0x6e, +0x63, 0x68, 0xd83e, 0xddc0, 0x3a, 0x63, 0x68, 0x65, 0x65, 0x73, 0x65, 0x5f, +0x77, 0x65, 0x64, 0x67, 0x65, 0x3a, 0x63, 0x68, 0x65, 0x65, 0x73, 0x65, +0x77, 0x65, 0x64, 0x67, 0x65, 0xd83e, 0xddc0, 0x3a, 0x63, 0x68, 0x65, 0x65, +0x73, 0x65, 0x3a, 0x63, 0x68, 0x65, 0x65, 0x73, 0x65, 0xd83e, 0xdd5a, 0x3a, +0x65, 0x67, 0x67, 0x3a, 0x65, 0x67, 0x67, 0xd83c, 0xdf73, 0x3a, 0x63, 0x6f, +0x6f, 0x6b, 0x69, 0x6e, 0x67, 0x3a, 0x63, 0x6f, 0x6f, 0x6b, 0x69, 0x6e, +0x67, 0xd83e, 0xdd53, 0x3a, 0x62, 0x61, 0x63, 0x6f, 0x6e, 0x3a, 0x62, 0x61, +0x63, 0x6f, 0x6e, 0xd83e, 0xdd5e, 0x3a, 0x70, 0x61, 0x6e, 0x63, 0x61, 0x6b, +0x65, 0x73, 0x3a, 0x70, 0x61, 0x6e, 0x63, 0x61, 0x6b, 0x65, 0x73, 0xd83c, +0xdf64, 0x3a, 0x66, 0x72, 0x69, 0x65, 0x64, 0x5f, 0x73, 0x68, 0x72, 0x69, +0x6d, 0x70, 0x3a, 0x66, 0x72, 0x69, 0x65, 0x64, 0x73, 0x68, 0x72, 0x69, +0x6d, 0x70, 0xd83c, 0xdf57, 0x3a, 0x70, 0x6f, 0x75, 0x6c, 0x74, 0x72, 0x79, +0x5f, 0x6c, 0x65, 0x67, 0x3a, 0x6c, 0x65, 0x67, 0x70, 0x6f, 0x75, 0x6c, +0x74, 0x72, 0x79, 0xd83c, 0xdf56, 0x3a, 0x6d, 0x65, 0x61, 0x74, 0x5f, 0x6f, +0x6e, 0x5f, 0x62, 0x6f, 0x6e, 0x65, 0x3a, 0x62, 0x6f, 0x6e, 0x65, 0x6d, +0x65, 0x61, 0x74, 0x6f, 0x6e, 0xd83c, 0xdf55, 0x3a, 0x70, 0x69, 0x7a, 0x7a, +0x61, 0x3a, 0x70, 0x69, 0x7a, 0x7a, 0x61, 0xd83c, 0xdf2d, 0x3a, 0x68, 0x6f, +0x74, 0x5f, 0x64, 0x6f, 0x67, 0x3a, 0x64, 0x6f, 0x67, 0x68, 0x6f, 0x74, +0xd83c, 0xdf2d, 0x3a, 0x68, 0x6f, 0x74, 0x64, 0x6f, 0x67, 0x3a, 0x68, 0x6f, +0x74, 0x64, 0x6f, 0x67, 0xd83c, 0xdf54, 0x3a, 0x68, 0x61, 0x6d, 0x62, 0x75, +0x72, 0x67, 0x65, 0x72, 0x3a, 0x68, 0x61, 0x6d, 0x62, 0x75, 0x72, 0x67, +0x65, 0x72, 0xd83c, 0xdf5f, 0x3a, 0x66, 0x72, 0x69, 0x65, 0x73, 0x3a, 0x66, +0x72, 0x69, 0x65, 0x73, 0xd83e, 0xdd59, 0x3a, 0x73, 0x74, 0x75, 0x66, 0x66, +0x65, 0x64, 0x5f, 0x70, 0x69, 0x74, 0x61, 0x3a, 0x70, 0x69, 0x74, 0x61, +0x73, 0x74, 0x75, 0x66, 0x66, 0x65, 0x64, 0xd83e, 0xdd59, 0x3a, 0x73, 0x74, +0x75, 0x66, 0x66, 0x65, 0x64, 0x5f, 0x66, 0x6c, 0x61, 0x74, 0x62, 0x72, +0x65, 0x61, 0x64, 0x3a, 0x66, 0x6c, 0x61, 0x74, 0x62, 0x72, 0x65, 0x61, +0x64, 0x73, 0x74, 0x75, 0x66, 0x66, 0x65, 0x64, 0xd83c, 0xdf2e, 0x3a, 0x74, +0x61, 0x63, 0x6f, 0x3a, 0x74, 0x61, 0x63, 0x6f, 0xd83c, 0xdf2f, 0x3a, 0x62, +0x75, 0x72, 0x72, 0x69, 0x74, 0x6f, 0x3a, 0x62, 0x75, 0x72, 0x72, 0x69, +0x74, 0x6f, 0xd83e, 0xdd57, 0x3a, 0x67, 0x72, 0x65, 0x65, 0x6e, 0x5f, 0x73, +0x61, 0x6c, 0x61, 0x64, 0x3a, 0x67, 0x72, 0x65, 0x65, 0x6e, 0x73, 0x61, +0x6c, 0x61, 0x64, 0xd83e, 0xdd57, 0x3a, 0x73, 0x61, 0x6c, 0x61, 0x64, 0x3a, +0x73, 0x61, 0x6c, 0x61, 0x64, 0xd83e, 0xdd58, 0x3a, 0x70, 0x61, 0x65, 0x6c, +0x6c, 0x61, 0x3a, 0x70, 0x61, 0x65, 0x6c, 0x6c, 0x61, 0xd83e, 0xdd58, 0x3a, +0x73, 0x68, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x70, 0x61, 0x6e, 0x5f, +0x6f, 0x66, 0x5f, 0x66, 0x6f, 0x6f, 0x64, 0x3a, 0x66, 0x6f, 0x6f, 0x64, +0x6f, 0x66, 0x70, 0x61, 0x6e, 0x73, 0x68, 0x61, 0x6c, 0x6c, 0x6f, 0x77, +0xd83c, 0xdf5d, 0x3a, 0x73, 0x70, 0x61, 0x67, 0x68, 0x65, 0x74, 0x74, 0x69, +0x3a, 0x73, 0x70, 0x61, 0x67, 0x68, 0x65, 0x74, 0x74, 0x69, 0xd83c, 0xdf5c, +0x3a, 0x72, 0x61, 0x6d, 0x65, 0x6e, 0x3a, 0x72, 0x61, 0x6d, 0x65, 0x6e, +0xd83c, 0xdf72, 0x3a, 0x73, 0x74, 0x65, 0x77, 0x3a, 0x73, 0x74, 0x65, 0x77, +0xd83c, 0xdf65, 0x3a, 0x66, 0x69, 0x73, 0x68, 0x5f, 0x63, 0x61, 0x6b, 0x65, +0x3a, 0x63, 0x61, 0x6b, 0x65, 0x66, 0x69, 0x73, 0x68, 0xd83c, 0xdf63, 0x3a, +0x73, 0x75, 0x73, 0x68, 0x69, 0x3a, 0x73, 0x75, 0x73, 0x68, 0x69, 0xd83c, +0xdf71, 0x3a, 0x62, 0x65, 0x6e, 0x74, 0x6f, 0x3a, 0x62, 0x65, 0x6e, 0x74, +0x6f, 0xd83c, 0xdf5b, 0x3a, 0x63, 0x75, 0x72, 0x72, 0x79, 0x3a, 0x63, 0x75, +0x72, 0x72, 0x79, 0xd83c, 0xdf59, 0x3a, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x62, +0x61, 0x6c, 0x6c, 0x3a, 0x62, 0x61, 0x6c, 0x6c, 0x72, 0x69, 0x63, 0x65, +0xd83c, 0xdf5a, 0x3a, 0x72, 0x69, 0x63, 0x65, 0x3a, 0x72, 0x69, 0x63, 0x65, +0xd83c, 0xdf58, 0x3a, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x72, 0x61, 0x63, +0x6b, 0x65, 0x72, 0x3a, 0x63, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x72, 0x72, +0x69, 0x63, 0x65, 0xd83c, 0xdf62, 0x3a, 0x6f, 0x64, 0x65, 0x6e, 0x3a, 0x6f, +0x64, 0x65, 0x6e, 0xd83c, 0xdf61, 0x3a, 0x64, 0x61, 0x6e, 0x67, 0x6f, 0x3a, +0x64, 0x61, 0x6e, 0x67, 0x6f, 0xd83c, 0xdf67, 0x3a, 0x73, 0x68, 0x61, 0x76, +0x65, 0x64, 0x5f, 0x69, 0x63, 0x65, 0x3a, 0x69, 0x63, 0x65, 0x73, 0x68, +0x61, 0x76, 0x65, 0x64, 0xd83c, 0xdf68, 0x3a, 0x69, 0x63, 0x65, 0x5f, 0x63, +0x72, 0x65, 0x61, 0x6d, 0x3a, 0x63, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x63, +0x65, 0xd83c, 0xdf66, 0x3a, 0x69, 0x63, 0x65, 0x63, 0x72, 0x65, 0x61, 0x6d, +0x3a, 0x69, 0x63, 0x65, 0x63, 0x72, 0x65, 0x61, 0x6d, 0xd83c, 0xdf70, 0x3a, +0x63, 0x61, 0x6b, 0x65, 0x3a, 0x63, 0x61, 0x6b, 0x65, 0xd83c, 0xdf82, 0x3a, +0x62, 0x69, 0x72, 0x74, 0x68, 0x64, 0x61, 0x79, 0x3a, 0x62, 0x69, 0x72, +0x74, 0x68, 0x64, 0x61, 0x79, 0xd83c, 0xdf6e, 0x3a, 0x66, 0x6c, 0x61, 0x6e, +0x3a, 0x66, 0x6c, 0x61, 0x6e, 0xd83c, 0xdf6e, 0x3a, 0x70, 0x75, 0x64, 0x64, +0x69, 0x6e, 0x67, 0x3a, 0x70, 0x75, 0x64, 0x64, 0x69, 0x6e, 0x67, 0xd83c, +0xdf6e, 0x3a, 0x63, 0x75, 0x73, 0x74, 0x61, 0x72, 0x64, 0x3a, 0x63, 0x75, +0x73, 0x74, 0x61, 0x72, 0x64, 0xd83c, 0xdf6d, 0x3a, 0x6c, 0x6f, 0x6c, 0x6c, +0x69, 0x70, 0x6f, 0x70, 0x3a, 0x6c, 0x6f, 0x6c, 0x6c, 0x69, 0x70, 0x6f, +0x70, 0xd83c, 0xdf6c, 0x3a, 0x63, 0x61, 0x6e, 0x64, 0x79, 0x3a, 0x63, 0x61, +0x6e, 0x64, 0x79, 0xd83c, 0xdf6b, 0x3a, 0x63, 0x68, 0x6f, 0x63, 0x6f, 0x6c, +0x61, 0x74, 0x65, 0x5f, 0x62, 0x61, 0x72, 0x3a, 0x62, 0x61, 0x72, 0x63, +0x68, 0x6f, 0x63, 0x6f, 0x6c, 0x61, 0x74, 0x65, 0xd83c, 0xdf7f, 0x3a, 0x70, +0x6f, 0x70, 0x63, 0x6f, 0x72, 0x6e, 0x3a, 0x70, 0x6f, 0x70, 0x63, 0x6f, +0x72, 0x6e, 0xd83c, 0xdf69, 0x3a, 0x64, 0x6f, 0x75, 0x67, 0x68, 0x6e, 0x75, +0x74, 0x3a, 0x64, 0x6f, 0x75, 0x67, 0x68, 0x6e, 0x75, 0x74, 0xd83c, 0xdf6a, +0x3a, 0x63, 0x6f, 0x6f, 0x6b, 0x69, 0x65, 0x3a, 0x63, 0x6f, 0x6f, 0x6b, +0x69, 0x65, 0xd83e, 0xdd5b, 0x3a, 0x67, 0x6c, 0x61, 0x73, 0x73, 0x5f, 0x6f, +0x66, 0x5f, 0x6d, 0x69, 0x6c, 0x6b, 0x3a, 0x67, 0x6c, 0x61, 0x73, 0x73, +0x6d, 0x69, 0x6c, 0x6b, 0x6f, 0x66, 0xd83e, 0xdd5b, 0x3a, 0x6d, 0x69, 0x6c, +0x6b, 0x3a, 0x6d, 0x69, 0x6c, 0x6b, 0xd83c, 0xdf7c, 0x3a, 0x62, 0x61, 0x62, +0x79, 0x5f, 0x62, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x3a, 0x62, 0x61, 0x62, +0x79, 0x62, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x2615, 0xfe0f, 0x3a, 0x63, 0x6f, +0x66, 0x66, 0x65, 0x65, 0x3a, 0x63, 0x6f, 0x66, 0x66, 0x65, 0x65, 0xd83c, +0xdf75, 0x3a, 0x74, 0x65, 0x61, 0x3a, 0x74, 0x65, 0x61, 0xd83c, 0xdf76, 0x3a, +0x73, 0x61, 0x6b, 0x65, 0x3a, 0x73, 0x61, 0x6b, 0x65, 0xd83c, 0xdf7a, 0x3a, +0x62, 0x65, 0x65, 0x72, 0x3a, 0x62, 0x65, 0x65, 0x72, 0xd83c, 0xdf7b, 0x3a, +0x62, 0x65, 0x65, 0x72, 0x73, 0x3a, 0x62, 0x65, 0x65, 0x72, 0x73, 0xd83e, +0xdd42, 0x3a, 0x63, 0x6c, 0x69, 0x6e, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x67, +0x6c, 0x61, 0x73, 0x73, 0x3a, 0x63, 0x6c, 0x69, 0x6e, 0x6b, 0x69, 0x6e, +0x67, 0x67, 0x6c, 0x61, 0x73, 0x73, 0xd83e, 0xdd42, 0x3a, 0x63, 0x68, 0x61, +0x6d, 0x70, 0x61, 0x67, 0x6e, 0x65, 0x5f, 0x67, 0x6c, 0x61, 0x73, 0x73, +0x3a, 0x63, 0x68, 0x61, 0x6d, 0x70, 0x61, 0x67, 0x6e, 0x65, 0x67, 0x6c, +0x61, 0x73, 0x73, 0xd83c, 0xdf77, 0x3a, 0x77, 0x69, 0x6e, 0x65, 0x5f, 0x67, +0x6c, 0x61, 0x73, 0x73, 0x3a, 0x67, 0x6c, 0x61, 0x73, 0x73, 0x77, 0x69, +0x6e, 0x65, 0xd83e, 0xdd43, 0x3a, 0x77, 0x68, 0x69, 0x73, 0x6b, 0x79, 0x3a, +0x77, 0x68, 0x69, 0x73, 0x6b, 0x79, 0xd83e, 0xdd43, 0x3a, 0x74, 0x75, 0x6d, +0x62, 0x6c, 0x65, 0x72, 0x5f, 0x67, 0x6c, 0x61, 0x73, 0x73, 0x3a, 0x67, +0x6c, 0x61, 0x73, 0x73, 0x74, 0x75, 0x6d, 0x62, 0x6c, 0x65, 0x72, 0xd83c, +0xdf78, 0x3a, 0x63, 0x6f, 0x63, 0x6b, 0x74, 0x61, 0x69, 0x6c, 0x3a, 0x63, +0x6f, 0x63, 0x6b, 0x74, 0x61, 0x69, 0x6c, 0xd83c, 0xdf79, 0x3a, 0x74, 0x72, +0x6f, 0x70, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x64, 0x72, 0x69, 0x6e, 0x6b, +0x3a, 0x64, 0x72, 0x69, 0x6e, 0x6b, 0x74, 0x72, 0x6f, 0x70, 0x69, 0x63, +0x61, 0x6c, 0xd83c, 0xdf7e, 0x3a, 0x62, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x5f, +0x77, 0x69, 0x74, 0x68, 0x5f, 0x70, 0x6f, 0x70, 0x70, 0x69, 0x6e, 0x67, +0x5f, 0x63, 0x6f, 0x72, 0x6b, 0x3a, 0x62, 0x6f, 0x74, 0x74, 0x6c, 0x65, +0x63, 0x6f, 0x72, 0x6b, 0x70, 0x6f, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x77, +0x69, 0x74, 0x68, 0xd83c, 0xdf7e, 0x3a, 0x63, 0x68, 0x61, 0x6d, 0x70, 0x61, +0x67, 0x6e, 0x65, 0x3a, 0x63, 0x68, 0x61, 0x6d, 0x70, 0x61, 0x67, 0x6e, +0x65, 0xd83e, 0xdd44, 0x3a, 0x73, 0x70, 0x6f, 0x6f, 0x6e, 0x3a, 0x73, 0x70, +0x6f, 0x6f, 0x6e, 0xd83c, 0xdf74, 0x3a, 0x66, 0x6f, 0x72, 0x6b, 0x5f, 0x61, +0x6e, 0x64, 0x5f, 0x6b, 0x6e, 0x69, 0x66, 0x65, 0x3a, 0x61, 0x6e, 0x64, +0x66, 0x6f, 0x72, 0x6b, 0x6b, 0x6e, 0x69, 0x66, 0x65, 0xd83c, 0xdf7d, 0x3a, +0x66, 0x6f, 0x72, 0x6b, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x6b, 0x6e, 0x69, +0x66, 0x65, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x70, 0x6c, 0x61, 0x74, +0x65, 0x3a, 0x61, 0x6e, 0x64, 0x66, 0x6f, 0x72, 0x6b, 0x6b, 0x6e, 0x69, +0x66, 0x65, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x77, 0x69, 0x74, 0x68, 0xd83c, +0xdf7d, 0x3a, 0x66, 0x6f, 0x72, 0x6b, 0x5f, 0x6b, 0x6e, 0x69, 0x66, 0x65, +0x5f, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x3a, 0x66, 0x6f, 0x72, 0x6b, 0x6b, +0x6e, 0x69, 0x66, 0x65, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x26bd, 0xfe0f, 0x3a, +0x73, 0x6f, 0x63, 0x63, 0x65, 0x72, 0x3a, 0x73, 0x6f, 0x63, 0x63, 0x65, +0x72, 0xd83c, 0xdfc0, 0x3a, 0x62, 0x61, 0x73, 0x6b, 0x65, 0x74, 0x62, 0x61, +0x6c, 0x6c, 0x3a, 0x62, 0x61, 0x73, 0x6b, 0x65, 0x74, 0x62, 0x61, 0x6c, +0x6c, 0xd83c, 0xdfc8, 0x3a, 0x66, 0x6f, 0x6f, 0x74, 0x62, 0x61, 0x6c, 0x6c, +0x3a, 0x66, 0x6f, 0x6f, 0x74, 0x62, 0x61, 0x6c, 0x6c, 0x26be, 0xfe0f, 0x3a, +0x62, 0x61, 0x73, 0x65, 0x62, 0x61, 0x6c, 0x6c, 0x3a, 0x62, 0x61, 0x73, +0x65, 0x62, 0x61, 0x6c, 0x6c, 0xd83c, 0xdfbe, 0x3a, 0x74, 0x65, 0x6e, 0x6e, +0x69, 0x73, 0x3a, 0x74, 0x65, 0x6e, 0x6e, 0x69, 0x73, 0xd83c, 0xdfd0, 0x3a, +0x76, 0x6f, 0x6c, 0x6c, 0x65, 0x79, 0x62, 0x61, 0x6c, 0x6c, 0x3a, 0x76, +0x6f, 0x6c, 0x6c, 0x65, 0x79, 0x62, 0x61, 0x6c, 0x6c, 0xd83c, 0xdfc9, 0x3a, +0x72, 0x75, 0x67, 0x62, 0x79, 0x5f, 0x66, 0x6f, 0x6f, 0x74, 0x62, 0x61, +0x6c, 0x6c, 0x3a, 0x66, 0x6f, 0x6f, 0x74, 0x62, 0x61, 0x6c, 0x6c, 0x72, +0x75, 0x67, 0x62, 0x79, 0xd83c, 0xdfb1, 0x3a, 0x38, 0x62, 0x61, 0x6c, 0x6c, +0x3a, 0x38, 0x62, 0x61, 0x6c, 0x6c, 0xd83c, 0xdfd3, 0x3a, 0x74, 0x61, 0x62, +0x6c, 0x65, 0x5f, 0x74, 0x65, 0x6e, 0x6e, 0x69, 0x73, 0x3a, 0x74, 0x61, +0x62, 0x6c, 0x65, 0x74, 0x65, 0x6e, 0x6e, 0x69, 0x73, 0xd83c, 0xdfd3, 0x3a, +0x70, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x6f, 0x6e, 0x67, 0x3a, 0x70, 0x69, +0x6e, 0x67, 0x70, 0x6f, 0x6e, 0x67, 0xd83c, 0xdff8, 0x3a, 0x62, 0x61, 0x64, +0x6d, 0x69, 0x6e, 0x74, 0x6f, 0x6e, 0x3a, 0x62, 0x61, 0x64, 0x6d, 0x69, +0x6e, 0x74, 0x6f, 0x6e, 0xd83e, 0xdd45, 0x3a, 0x67, 0x6f, 0x61, 0x6c, 0x5f, +0x6e, 0x65, 0x74, 0x3a, 0x67, 0x6f, 0x61, 0x6c, 0x6e, 0x65, 0x74, 0xd83e, +0xdd45, 0x3a, 0x67, 0x6f, 0x61, 0x6c, 0x3a, 0x67, 0x6f, 0x61, 0x6c, 0xd83c, +0xdfd2, 0x3a, 0x68, 0x6f, 0x63, 0x6b, 0x65, 0x79, 0x3a, 0x68, 0x6f, 0x63, +0x6b, 0x65, 0x79, 0xd83c, 0xdfd1, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, +0x68, 0x6f, 0x63, 0x6b, 0x65, 0x79, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, +0x68, 0x6f, 0x63, 0x6b, 0x65, 0x79, 0xd83c, 0xdfcf, 0x3a, 0x63, 0x72, 0x69, +0x63, 0x6b, 0x65, 0x74, 0x5f, 0x62, 0x61, 0x74, 0x5f, 0x62, 0x61, 0x6c, +0x6c, 0x3a, 0x62, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x74, 0x63, 0x72, 0x69, +0x63, 0x6b, 0x65, 0x74, 0xd83c, 0xdfcf, 0x3a, 0x63, 0x72, 0x69, 0x63, 0x6b, +0x65, 0x74, 0x5f, 0x67, 0x61, 0x6d, 0x65, 0x3a, 0x63, 0x72, 0x69, 0x63, +0x6b, 0x65, 0x74, 0x67, 0x61, 0x6d, 0x65, 0x26f3, 0xfe0f, 0x3a, 0x67, 0x6f, +0x6c, 0x66, 0x3a, 0x67, 0x6f, 0x6c, 0x66, 0xd83c, 0xdff9, 0x3a, 0x61, 0x72, +0x63, 0x68, 0x65, 0x72, 0x79, 0x3a, 0x61, 0x72, 0x63, 0x68, 0x65, 0x72, +0x79, 0xd83c, 0xdff9, 0x3a, 0x62, 0x6f, 0x77, 0x5f, 0x61, 0x6e, 0x64, 0x5f, +0x61, 0x72, 0x72, 0x6f, 0x77, 0x3a, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x72, +0x6f, 0x77, 0x62, 0x6f, 0x77, 0xd83c, 0xdfa3, 0x3a, 0x66, 0x69, 0x73, 0x68, +0x69, 0x6e, 0x67, 0x5f, 0x70, 0x6f, 0x6c, 0x65, 0x5f, 0x61, 0x6e, 0x64, +0x5f, 0x66, 0x69, 0x73, 0x68, 0x3a, 0x61, 0x6e, 0x64, 0x66, 0x69, 0x73, +0x68, 0x66, 0x69, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x70, 0x6f, 0x6c, 0x65, +0xd83e, 0xdd4a, 0x3a, 0x62, 0x6f, 0x78, 0x69, 0x6e, 0x67, 0x5f, 0x67, 0x6c, +0x6f, 0x76, 0x65, 0x73, 0x3a, 0x62, 0x6f, 0x78, 0x69, 0x6e, 0x67, 0x67, +0x6c, 0x6f, 0x76, 0x65, 0x73, 0xd83e, 0xdd4a, 0x3a, 0x62, 0x6f, 0x78, 0x69, +0x6e, 0x67, 0x5f, 0x67, 0x6c, 0x6f, 0x76, 0x65, 0x3a, 0x62, 0x6f, 0x78, +0x69, 0x6e, 0x67, 0x67, 0x6c, 0x6f, 0x76, 0x65, 0xd83e, 0xdd4b, 0x3a, 0x6b, +0x61, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, +0x6d, 0x3a, 0x6b, 0x61, 0x72, 0x61, 0x74, 0x65, 0x75, 0x6e, 0x69, 0x66, +0x6f, 0x72, 0x6d, 0xd83e, 0xdd4b, 0x3a, 0x6d, 0x61, 0x72, 0x74, 0x69, 0x61, +0x6c, 0x5f, 0x61, 0x72, 0x74, 0x73, 0x5f, 0x75, 0x6e, 0x69, 0x66, 0x6f, +0x72, 0x6d, 0x3a, 0x61, 0x72, 0x74, 0x73, 0x6d, 0x61, 0x72, 0x74, 0x69, +0x61, 0x6c, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x26f8, 0x3a, 0x69, +0x63, 0x65, 0x5f, 0x73, 0x6b, 0x61, 0x74, 0x65, 0x3a, 0x69, 0x63, 0x65, +0x73, 0x6b, 0x61, 0x74, 0x65, 0xd83c, 0xdfbf, 0x3a, 0x73, 0x6b, 0x69, 0x3a, +0x73, 0x6b, 0x69, 0x26f7, 0x3a, 0x73, 0x6b, 0x69, 0x65, 0x72, 0x3a, 0x73, +0x6b, 0x69, 0x65, 0x72, 0xd83c, 0xdfc2, 0x3a, 0x73, 0x6e, 0x6f, 0x77, 0x62, +0x6f, 0x61, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x73, 0x6e, 0x6f, 0x77, 0x62, +0x6f, 0x61, 0x72, 0x64, 0x65, 0x72, 0xd83c, 0xdfcb, 0xfe0f, 0x200d, 0x2640, 0xfe0f, +0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x6c, 0x69, 0x66, 0x74, 0x69, +0x6e, 0x67, 0x5f, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x73, 0x3a, 0x6c, +0x69, 0x66, 0x74, 0x69, 0x6e, 0x67, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, +0x73, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83c, 0xdfcb, 0xfe0f, 0x3a, 0x6d, 0x61, +0x6e, 0x5f, 0x6c, 0x69, 0x66, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x77, 0x65, +0x69, 0x67, 0x68, 0x74, 0x73, 0x3a, 0x6c, 0x69, 0x66, 0x74, 0x69, 0x6e, +0x67, 0x6d, 0x61, 0x6e, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x73, 0xd83c, +0xdfcb, 0xfe0f, 0x3a, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x6c, 0x69, +0x66, 0x74, 0x65, 0x72, 0x3a, 0x6c, 0x69, 0x66, 0x74, 0x65, 0x72, 0x77, +0x65, 0x69, 0x67, 0x68, 0x74, 0xd83c, 0xdfcb, 0xfe0f, 0x3a, 0x6c, 0x69, 0x66, +0x74, 0x65, 0x72, 0x3a, 0x6c, 0x69, 0x66, 0x74, 0x65, 0x72, 0xd83c, 0xdfcb, +0xfe0f, 0x3a, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x5f, 0x6c, 0x69, 0x66, +0x74, 0x69, 0x6e, 0x67, 0x5f, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x73, +0x3a, 0x6c, 0x69, 0x66, 0x74, 0x69, 0x6e, 0x67, 0x70, 0x65, 0x72, 0x73, +0x6f, 0x6e, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x73, 0xd83e, 0xdd3a, 0x3a, +0x66, 0x65, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x66, 0x65, 0x6e, 0x63, +0x69, 0x6e, 0x67, 0xd83e, 0xdd3a, 0x3a, 0x66, 0x65, 0x6e, 0x63, 0x65, 0x72, +0x3a, 0x66, 0x65, 0x6e, 0x63, 0x65, 0x72, 0xd83e, 0xdd3a, 0x3a, 0x70, 0x65, +0x72, 0x73, 0x6f, 0x6e, 0x5f, 0x66, 0x65, 0x6e, 0x63, 0x69, 0x6e, 0x67, +0x3a, 0x66, 0x65, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x70, 0x65, 0x72, 0x73, +0x6f, 0x6e, 0xd83e, 0xdd3c, 0x200d, 0x2640, 0xfe0f, 0x3a, 0x77, 0x6f, 0x6d, 0x65, +0x6e, 0x5f, 0x77, 0x72, 0x65, 0x73, 0x74, 0x6c, 0x69, 0x6e, 0x67, 0x3a, +0x77, 0x6f, 0x6d, 0x65, 0x6e, 0x77, 0x72, 0x65, 0x73, 0x74, 0x6c, 0x69, +0x6e, 0x67, 0xd83e, 0xdd3c, 0x200d, 0x2642, 0xfe0f, 0x3a, 0x6d, 0x65, 0x6e, 0x5f, +0x77, 0x72, 0x65, 0x73, 0x74, 0x6c, 0x69, 0x6e, 0x67, 0x3a, 0x6d, 0x65, +0x6e, 0x77, 0x72, 0x65, 0x73, 0x74, 0x6c, 0x69, 0x6e, 0x67, 0xd83e, 0xdd3c, +0x200d, 0x2642, 0xfe0f, 0x3a, 0x77, 0x72, 0x65, 0x73, 0x74, 0x6c, 0x69, 0x6e, +0x67, 0x3a, 0x77, 0x72, 0x65, 0x73, 0x74, 0x6c, 0x69, 0x6e, 0x67, 0xd83e, +0xdd3c, 0x200d, 0x2642, 0xfe0f, 0x3a, 0x77, 0x72, 0x65, 0x73, 0x74, 0x6c, 0x65, +0x72, 0x73, 0x3a, 0x77, 0x72, 0x65, 0x73, 0x74, 0x6c, 0x65, 0x72, 0x73, +0xd83e, 0xdd3c, 0x200d, 0x2642, 0xfe0f, 0x3a, 0x70, 0x65, 0x6f, 0x70, 0x6c, 0x65, +0x5f, 0x77, 0x72, 0x65, 0x73, 0x74, 0x6c, 0x69, 0x6e, 0x67, 0x3a, 0x70, +0x65, 0x6f, 0x70, 0x6c, 0x65, 0x77, 0x72, 0x65, 0x73, 0x74, 0x6c, 0x69, +0x6e, 0x67, 0xd83e, 0xdd38, 0x200d, 0x2640, 0xfe0f, 0x3a, 0x77, 0x6f, 0x6d, 0x61, +0x6e, 0x5f, 0x63, 0x61, 0x72, 0x74, 0x77, 0x68, 0x65, 0x65, 0x6c, 0x69, +0x6e, 0x67, 0x3a, 0x63, 0x61, 0x72, 0x74, 0x77, 0x68, 0x65, 0x65, 0x6c, +0x69, 0x6e, 0x67, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83e, 0xdd38, 0x200d, 0x2642, +0xfe0f, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x63, 0x61, 0x72, 0x74, 0x77, 0x68, +0x65, 0x65, 0x6c, 0x69, 0x6e, 0x67, 0x3a, 0x63, 0x61, 0x72, 0x74, 0x77, +0x68, 0x65, 0x65, 0x6c, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0xd83e, 0xdd38, +0x200d, 0x2642, 0xfe0f, 0x3a, 0x63, 0x61, 0x72, 0x74, 0x77, 0x68, 0x65, 0x65, +0x6c, 0x3a, 0x63, 0x61, 0x72, 0x74, 0x77, 0x68, 0x65, 0x65, 0x6c, 0xd83e, +0xdd38, 0x200d, 0x2642, 0xfe0f, 0x3a, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x5f, +0x64, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x61, 0x72, 0x74, 0x77, 0x68, +0x65, 0x65, 0x6c, 0x3a, 0x63, 0x61, 0x72, 0x74, 0x77, 0x68, 0x65, 0x65, +0x6c, 0x64, 0x6f, 0x69, 0x6e, 0x67, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, +0x26f9, 0xfe0f, 0x200d, 0x2640, 0xfe0f, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, +0x62, 0x6f, 0x75, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x61, 0x6c, +0x6c, 0x3a, 0x62, 0x61, 0x6c, 0x6c, 0x62, 0x6f, 0x75, 0x6e, 0x63, 0x69, +0x6e, 0x67, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x26f9, 0xfe0f, 0x3a, 0x6d, 0x61, +0x6e, 0x5f, 0x62, 0x6f, 0x75, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x62, +0x61, 0x6c, 0x6c, 0x3a, 0x62, 0x61, 0x6c, 0x6c, 0x62, 0x6f, 0x75, 0x6e, +0x63, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x26f9, 0xfe0f, 0x3a, 0x70, 0x65, +0x72, 0x73, 0x6f, 0x6e, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x62, 0x61, +0x6c, 0x6c, 0x3a, 0x62, 0x61, 0x6c, 0x6c, 0x70, 0x65, 0x72, 0x73, 0x6f, +0x6e, 0x77, 0x69, 0x74, 0x68, 0x26f9, 0xfe0f, 0x3a, 0x62, 0x61, 0x73, 0x6b, +0x65, 0x74, 0x62, 0x61, 0x6c, 0x6c, 0x5f, 0x70, 0x6c, 0x61, 0x79, 0x65, +0x72, 0x3a, 0x62, 0x61, 0x73, 0x6b, 0x65, 0x74, 0x62, 0x61, 0x6c, 0x6c, +0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x26f9, 0xfe0f, 0x3a, 0x70, 0x65, 0x72, +0x73, 0x6f, 0x6e, 0x5f, 0x62, 0x6f, 0x75, 0x6e, 0x63, 0x69, 0x6e, 0x67, +0x5f, 0x62, 0x61, 0x6c, 0x6c, 0x3a, 0x62, 0x61, 0x6c, 0x6c, 0x62, 0x6f, +0x75, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, +0xd83e, 0xdd3e, 0x200d, 0x2640, 0xfe0f, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, +0x70, 0x6c, 0x61, 0x79, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x61, 0x6e, 0x64, +0x62, 0x61, 0x6c, 0x6c, 0x3a, 0x68, 0x61, 0x6e, 0x64, 0x62, 0x61, 0x6c, +0x6c, 0x70, 0x6c, 0x61, 0x79, 0x69, 0x6e, 0x67, 0x77, 0x6f, 0x6d, 0x61, +0x6e, 0xd83e, 0xdd3e, 0x200d, 0x2640, 0xfe0f, 0x3a, 0x68, 0x61, 0x6e, 0x64, 0x62, +0x61, 0x6c, 0x6c, 0x3a, 0x68, 0x61, 0x6e, 0x64, 0x62, 0x61, 0x6c, 0x6c, +0xd83e, 0xdd3e, 0x200d, 0x2640, 0xfe0f, 0x3a, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, +0x5f, 0x70, 0x6c, 0x61, 0x79, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x61, 0x6e, +0x64, 0x62, 0x61, 0x6c, 0x6c, 0x3a, 0x68, 0x61, 0x6e, 0x64, 0x62, 0x61, +0x6c, 0x6c, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x70, 0x6c, 0x61, 0x79, +0x69, 0x6e, 0x67, 0xd83e, 0xdd3e, 0x200d, 0x2642, 0xfe0f, 0x3a, 0x6d, 0x61, 0x6e, +0x5f, 0x70, 0x6c, 0x61, 0x79, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x61, 0x6e, +0x64, 0x62, 0x61, 0x6c, 0x6c, 0x3a, 0x68, 0x61, 0x6e, 0x64, 0x62, 0x61, +0x6c, 0x6c, 0x6d, 0x61, 0x6e, 0x70, 0x6c, 0x61, 0x79, 0x69, 0x6e, 0x67, +0xd83c, 0xdfcc, 0xfe0f, 0x200d, 0x2640, 0xfe0f, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, +0x5f, 0x67, 0x6f, 0x6c, 0x66, 0x69, 0x6e, 0x67, 0x3a, 0x67, 0x6f, 0x6c, +0x66, 0x69, 0x6e, 0x67, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83c, 0xdfcc, 0xfe0f, +0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x67, 0x6f, 0x6c, 0x66, 0x69, 0x6e, 0x67, +0x3a, 0x67, 0x6f, 0x6c, 0x66, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0xd83c, +0xdfcc, 0xfe0f, 0x3a, 0x67, 0x6f, 0x6c, 0x66, 0x65, 0x72, 0x3a, 0x67, 0x6f, +0x6c, 0x66, 0x65, 0x72, 0xd83c, 0xdfcc, 0xfe0f, 0x3a, 0x70, 0x65, 0x72, 0x73, +0x6f, 0x6e, 0x5f, 0x67, 0x6f, 0x6c, 0x66, 0x69, 0x6e, 0x67, 0x3a, 0x67, +0x6f, 0x6c, 0x66, 0x69, 0x6e, 0x67, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, +0xd83c, 0xdfc4, 0x200d, 0x2640, 0xfe0f, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, +0x73, 0x75, 0x72, 0x66, 0x69, 0x6e, 0x67, 0x3a, 0x73, 0x75, 0x72, 0x66, +0x69, 0x6e, 0x67, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83c, 0xdfc4, 0x3a, 0x6d, +0x61, 0x6e, 0x5f, 0x73, 0x75, 0x72, 0x66, 0x69, 0x6e, 0x67, 0x3a, 0x6d, +0x61, 0x6e, 0x73, 0x75, 0x72, 0x66, 0x69, 0x6e, 0x67, 0xd83c, 0xdfc4, 0x3a, +0x73, 0x75, 0x72, 0x66, 0x65, 0x72, 0x3a, 0x73, 0x75, 0x72, 0x66, 0x65, +0x72, 0xd83c, 0xdfc4, 0x3a, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x5f, 0x73, +0x75, 0x72, 0x66, 0x69, 0x6e, 0x67, 0x3a, 0x70, 0x65, 0x72, 0x73, 0x6f, +0x6e, 0x73, 0x75, 0x72, 0x66, 0x69, 0x6e, 0x67, 0xd83c, 0xdfca, 0x200d, 0x2640, +0xfe0f, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x73, 0x77, 0x69, 0x6d, +0x6d, 0x69, 0x6e, 0x67, 0x3a, 0x73, 0x77, 0x69, 0x6d, 0x6d, 0x69, 0x6e, +0x67, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83c, 0xdfca, 0x3a, 0x6d, 0x61, 0x6e, +0x5f, 0x73, 0x77, 0x69, 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x3a, 0x6d, 0x61, +0x6e, 0x73, 0x77, 0x69, 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0xd83c, 0xdfca, 0x3a, +0x73, 0x77, 0x69, 0x6d, 0x6d, 0x65, 0x72, 0x3a, 0x73, 0x77, 0x69, 0x6d, +0x6d, 0x65, 0x72, 0xd83c, 0xdfca, 0x3a, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, +0x5f, 0x73, 0x77, 0x69, 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x3a, 0x70, 0x65, +0x72, 0x73, 0x6f, 0x6e, 0x73, 0x77, 0x69, 0x6d, 0x6d, 0x69, 0x6e, 0x67, +0xd83e, 0xdd3d, 0x200d, 0x2640, 0xfe0f, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, +0x70, 0x6c, 0x61, 0x79, 0x69, 0x6e, 0x67, 0x5f, 0x77, 0x61, 0x74, 0x65, +0x72, 0x5f, 0x70, 0x6f, 0x6c, 0x6f, 0x3a, 0x70, 0x6c, 0x61, 0x79, 0x69, +0x6e, 0x67, 0x70, 0x6f, 0x6c, 0x6f, 0x77, 0x61, 0x74, 0x65, 0x72, 0x77, +0x6f, 0x6d, 0x61, 0x6e, 0xd83e, 0xdd3d, 0x200d, 0x2642, 0xfe0f, 0x3a, 0x6d, 0x61, +0x6e, 0x5f, 0x70, 0x6c, 0x61, 0x79, 0x69, 0x6e, 0x67, 0x5f, 0x77, 0x61, +0x74, 0x65, 0x72, 0x5f, 0x70, 0x6f, 0x6c, 0x6f, 0x3a, 0x6d, 0x61, 0x6e, +0x70, 0x6c, 0x61, 0x79, 0x69, 0x6e, 0x67, 0x70, 0x6f, 0x6c, 0x6f, 0x77, +0x61, 0x74, 0x65, 0x72, 0xd83e, 0xdd3d, 0x200d, 0x2642, 0xfe0f, 0x3a, 0x77, 0x61, +0x74, 0x65, 0x72, 0x5f, 0x70, 0x6f, 0x6c, 0x6f, 0x3a, 0x70, 0x6f, 0x6c, +0x6f, 0x77, 0x61, 0x74, 0x65, 0x72, 0xd83e, 0xdd3d, 0x200d, 0x2642, 0xfe0f, 0x3a, +0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x5f, 0x70, 0x6c, 0x61, 0x79, 0x69, +0x6e, 0x67, 0x5f, 0x77, 0x61, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x6f, 0x6c, +0x6f, 0x3a, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x70, 0x6c, 0x61, 0x79, +0x69, 0x6e, 0x67, 0x70, 0x6f, 0x6c, 0x6f, 0x77, 0x61, 0x74, 0x65, 0x72, +0xd83d, 0xdea3, 0x200d, 0x2640, 0xfe0f, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, +0x72, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x6f, 0x61, 0x74, 0x3a, +0x62, 0x6f, 0x61, 0x74, 0x72, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x77, 0x6f, +0x6d, 0x61, 0x6e, 0xd83d, 0xdea3, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x72, 0x6f, +0x77, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x6f, 0x61, 0x74, 0x3a, 0x62, 0x6f, +0x61, 0x74, 0x6d, 0x61, 0x6e, 0x72, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0xd83d, +0xdea3, 0x3a, 0x72, 0x6f, 0x77, 0x62, 0x6f, 0x61, 0x74, 0x3a, 0x72, 0x6f, +0x77, 0x62, 0x6f, 0x61, 0x74, 0xd83d, 0xdea3, 0x3a, 0x70, 0x65, 0x72, 0x73, +0x6f, 0x6e, 0x5f, 0x72, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x6f, +0x61, 0x74, 0x3a, 0x62, 0x6f, 0x61, 0x74, 0x70, 0x65, 0x72, 0x73, 0x6f, +0x6e, 0x72, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0xd83c, 0xdfc7, 0x3a, 0x68, 0x6f, +0x72, 0x73, 0x65, 0x5f, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x68, +0x6f, 0x72, 0x73, 0x65, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0xd83d, 0xdeb4, +0x200d, 0x2640, 0xfe0f, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x62, 0x69, +0x6b, 0x69, 0x6e, 0x67, 0x3a, 0x62, 0x69, 0x6b, 0x69, 0x6e, 0x67, 0x77, +0x6f, 0x6d, 0x61, 0x6e, 0xd83d, 0xdeb4, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x62, +0x69, 0x6b, 0x69, 0x6e, 0x67, 0x3a, 0x62, 0x69, 0x6b, 0x69, 0x6e, 0x67, +0x6d, 0x61, 0x6e, 0xd83d, 0xdeb4, 0x3a, 0x62, 0x69, 0x63, 0x79, 0x63, 0x6c, +0x69, 0x73, 0x74, 0x3a, 0x62, 0x69, 0x63, 0x79, 0x63, 0x6c, 0x69, 0x73, +0x74, 0xd83d, 0xdeb4, 0x3a, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x5f, 0x62, +0x69, 0x6b, 0x69, 0x6e, 0x67, 0x3a, 0x62, 0x69, 0x6b, 0x69, 0x6e, 0x67, +0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0xd83d, 0xdeb5, 0x200d, 0x2640, 0xfe0f, 0x3a, +0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x61, +0x69, 0x6e, 0x5f, 0x62, 0x69, 0x6b, 0x69, 0x6e, 0x67, 0x3a, 0x62, 0x69, +0x6b, 0x69, 0x6e, 0x67, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, +0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83d, 0xdeb5, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, +0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x5f, 0x62, 0x69, 0x6b, +0x69, 0x6e, 0x67, 0x3a, 0x62, 0x69, 0x6b, 0x69, 0x6e, 0x67, 0x6d, 0x61, +0x6e, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0xd83d, 0xdeb5, 0x3a, +0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x5f, 0x62, 0x69, 0x63, +0x79, 0x63, 0x6c, 0x69, 0x73, 0x74, 0x3a, 0x62, 0x69, 0x63, 0x79, 0x63, +0x6c, 0x69, 0x73, 0x74, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, +0xd83d, 0xdeb5, 0x3a, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x5f, 0x6d, 0x6f, +0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x5f, 0x62, 0x69, 0x6b, 0x69, 0x6e, +0x67, 0x3a, 0x62, 0x69, 0x6b, 0x69, 0x6e, 0x67, 0x6d, 0x6f, 0x75, 0x6e, +0x74, 0x61, 0x69, 0x6e, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0xd83c, 0xdfbd, +0x3a, 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x68, 0x69, +0x72, 0x74, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x73, 0x61, 0x73, 0x68, +0x3a, 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x61, 0x73, 0x68, +0x73, 0x68, 0x69, 0x72, 0x74, 0x77, 0x69, 0x74, 0x68, 0xd83c, 0xdfc5, 0x3a, +0x73, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x5f, 0x6d, 0x65, 0x64, 0x61, 0x6c, +0x3a, 0x6d, 0x65, 0x64, 0x61, 0x6c, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x73, +0xd83c, 0xdfc5, 0x3a, 0x6d, 0x65, 0x64, 0x61, 0x6c, 0x3a, 0x6d, 0x65, 0x64, +0x61, 0x6c, 0xd83c, 0xdf96, 0x3a, 0x6d, 0x69, 0x6c, 0x69, 0x74, 0x61, 0x72, +0x79, 0x5f, 0x6d, 0x65, 0x64, 0x61, 0x6c, 0x3a, 0x6d, 0x65, 0x64, 0x61, +0x6c, 0x6d, 0x69, 0x6c, 0x69, 0x74, 0x61, 0x72, 0x79, 0xd83e, 0xdd47, 0x3a, +0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, +0x6d, 0x65, 0x64, 0x61, 0x6c, 0x3a, 0x66, 0x69, 0x72, 0x73, 0x74, 0x6d, +0x65, 0x64, 0x61, 0x6c, 0x70, 0x6c, 0x61, 0x63, 0x65, 0xd83e, 0xdd47, 0x3a, +0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x3a, +0x66, 0x69, 0x72, 0x73, 0x74, 0x70, 0x6c, 0x61, 0x63, 0x65, 0xd83e, 0xdd48, +0x3a, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x5f, 0x70, 0x6c, 0x61, 0x63, +0x65, 0x5f, 0x6d, 0x65, 0x64, 0x61, 0x6c, 0x3a, 0x6d, 0x65, 0x64, 0x61, +0x6c, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, +0xd83e, 0xdd48, 0x3a, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x5f, 0x70, 0x6c, +0x61, 0x63, 0x65, 0x3a, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x65, 0x63, +0x6f, 0x6e, 0x64, 0xd83e, 0xdd49, 0x3a, 0x74, 0x68, 0x69, 0x72, 0x64, 0x5f, +0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x64, 0x61, 0x6c, 0x3a, +0x6d, 0x65, 0x64, 0x61, 0x6c, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x74, 0x68, +0x69, 0x72, 0x64, 0xd83e, 0xdd49, 0x3a, 0x74, 0x68, 0x69, 0x72, 0x64, 0x5f, +0x70, 0x6c, 0x61, 0x63, 0x65, 0x3a, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x74, +0x68, 0x69, 0x72, 0x64, 0xd83c, 0xdfc6, 0x3a, 0x74, 0x72, 0x6f, 0x70, 0x68, +0x79, 0x3a, 0x74, 0x72, 0x6f, 0x70, 0x68, 0x79, 0xd83c, 0xdff5, 0x3a, 0x72, +0x6f, 0x73, 0x65, 0x74, 0x74, 0x65, 0x3a, 0x72, 0x6f, 0x73, 0x65, 0x74, +0x74, 0x65, 0xd83c, 0xdf97, 0x3a, 0x72, 0x65, 0x6d, 0x69, 0x6e, 0x64, 0x65, +0x72, 0x5f, 0x72, 0x69, 0x62, 0x62, 0x6f, 0x6e, 0x3a, 0x72, 0x65, 0x6d, +0x69, 0x6e, 0x64, 0x65, 0x72, 0x72, 0x69, 0x62, 0x62, 0x6f, 0x6e, 0xd83c, +0xdfab, 0x3a, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x3a, 0x74, 0x69, 0x63, +0x6b, 0x65, 0x74, 0xd83c, 0xdf9f, 0x3a, 0x61, 0x64, 0x6d, 0x69, 0x73, 0x73, +0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x3a, +0x61, 0x64, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x74, 0x69, 0x63, +0x6b, 0x65, 0x74, 0x73, 0xd83c, 0xdf9f, 0x3a, 0x74, 0x69, 0x63, 0x6b, 0x65, +0x74, 0x73, 0x3a, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x73, 0xd83c, 0xdfaa, +0x3a, 0x63, 0x69, 0x72, 0x63, 0x75, 0x73, 0x5f, 0x74, 0x65, 0x6e, 0x74, +0x3a, 0x63, 0x69, 0x72, 0x63, 0x75, 0x73, 0x74, 0x65, 0x6e, 0x74, 0xd83e, +0xdd39, 0x200d, 0x2640, 0xfe0f, 0x3a, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x5f, 0x6a, +0x75, 0x67, 0x67, 0x6c, 0x69, 0x6e, 0x67, 0x3a, 0x6a, 0x75, 0x67, 0x67, +0x6c, 0x69, 0x6e, 0x67, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0xd83e, 0xdd39, 0x200d, +0x2642, 0xfe0f, 0x3a, 0x6d, 0x61, 0x6e, 0x5f, 0x6a, 0x75, 0x67, 0x67, 0x6c, +0x69, 0x6e, 0x67, 0x3a, 0x6a, 0x75, 0x67, 0x67, 0x6c, 0x69, 0x6e, 0x67, +0x6d, 0x61, 0x6e, 0xd83e, 0xdd39, 0x200d, 0x2642, 0xfe0f, 0x3a, 0x6a, 0x75, 0x67, +0x67, 0x6c, 0x65, 0x72, 0x3a, 0x6a, 0x75, 0x67, 0x67, 0x6c, 0x65, 0x72, +0xd83e, 0xdd39, 0x200d, 0x2642, 0xfe0f, 0x3a, 0x6a, 0x75, 0x67, 0x67, 0x6c, 0x69, +0x6e, 0x67, 0x3a, 0x6a, 0x75, 0x67, 0x67, 0x6c, 0x69, 0x6e, 0x67, 0xd83e, +0xdd39, 0x200d, 0x2642, 0xfe0f, 0x3a, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x5f, +0x6a, 0x75, 0x67, 0x67, 0x6c, 0x69, 0x6e, 0x67, 0x3a, 0x6a, 0x75, 0x67, +0x67, 0x6c, 0x69, 0x6e, 0x67, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0xd83c, +0xdfad, 0x3a, 0x70, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x69, 0x6e, 0x67, +0x5f, 0x61, 0x72, 0x74, 0x73, 0x3a, 0x61, 0x72, 0x74, 0x73, 0x70, 0x65, +0x72, 0x66, 0x6f, 0x72, 0x6d, 0x69, 0x6e, 0x67, 0xd83c, 0xdfa8, 0x3a, 0x61, +0x72, 0x74, 0x3a, 0x61, 0x72, 0x74, 0xd83c, 0xdfac, 0x3a, 0x63, 0x6c, 0x61, +0x70, 0x70, 0x65, 0x72, 0x3a, 0x63, 0x6c, 0x61, 0x70, 0x70, 0x65, 0x72, +0xd83c, 0xdfa4, 0x3a, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x70, 0x68, 0x6f, 0x6e, +0x65, 0x3a, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x70, 0x68, 0x6f, 0x6e, 0x65, +0xd83c, 0xdfa7, 0x3a, 0x68, 0x65, 0x61, 0x64, 0x70, 0x68, 0x6f, 0x6e, 0x65, +0x73, 0x3a, 0x68, 0x65, 0x61, 0x64, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x73, +0xd83c, 0xdfbc, 0x3a, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x73, +0x63, 0x6f, 0x72, 0x65, 0x3a, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x61, 0x6c, +0x73, 0x63, 0x6f, 0x72, 0x65, 0xd83c, 0xdfb9, 0x3a, 0x6d, 0x75, 0x73, 0x69, +0x63, 0x61, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x62, 0x6f, 0x61, 0x72, 0x64, +0x3a, 0x6b, 0x65, 0x79, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x6d, 0x75, 0x73, +0x69, 0x63, 0x61, 0x6c, 0xd83e, 0xdd41, 0x3a, 0x64, 0x72, 0x75, 0x6d, 0x5f, +0x77, 0x69, 0x74, 0x68, 0x5f, 0x64, 0x72, 0x75, 0x6d, 0x73, 0x74, 0x69, +0x63, 0x6b, 0x73, 0x3a, 0x64, 0x72, 0x75, 0x6d, 0x64, 0x72, 0x75, 0x6d, +0x73, 0x74, 0x69, 0x63, 0x6b, 0x73, 0x77, 0x69, 0x74, 0x68, 0xd83e, 0xdd41, +0x3a, 0x64, 0x72, 0x75, 0x6d, 0x3a, 0x64, 0x72, 0x75, 0x6d, 0xd83c, 0xdfb7, +0x3a, 0x73, 0x61, 0x78, 0x6f, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x3a, 0x73, +0x61, 0x78, 0x6f, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0xd83c, 0xdfba, 0x3a, 0x74, +0x72, 0x75, 0x6d, 0x70, 0x65, 0x74, 0x3a, 0x74, 0x72, 0x75, 0x6d, 0x70, +0x65, 0x74, 0xd83c, 0xdfb8, 0x3a, 0x67, 0x75, 0x69, 0x74, 0x61, 0x72, 0x3a, +0x67, 0x75, 0x69, 0x74, 0x61, 0x72, 0xd83c, 0xdfbb, 0x3a, 0x76, 0x69, 0x6f, +0x6c, 0x69, 0x6e, 0x3a, 0x76, 0x69, 0x6f, 0x6c, 0x69, 0x6e, 0xd83c, 0xdfb2, +0x3a, 0x67, 0x61, 0x6d, 0x65, 0x5f, 0x64, 0x69, 0x65, 0x3a, 0x64, 0x69, +0x65, 0x67, 0x61, 0x6d, 0x65, 0xd83c, 0xdfaf, 0x3a, 0x64, 0x61, 0x72, 0x74, +0x3a, 0x64, 0x61, 0x72, 0x74, 0xd83c, 0xdfb3, 0x3a, 0x62, 0x6f, 0x77, 0x6c, +0x69, 0x6e, 0x67, 0x3a, 0x62, 0x6f, 0x77, 0x6c, 0x69, 0x6e, 0x67, 0xd83c, +0xdfae, 0x3a, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x5f, 0x67, 0x61, 0x6d, 0x65, +0x3a, 0x67, 0x61, 0x6d, 0x65, 0x76, 0x69, 0x64, 0x65, 0x6f, 0xd83c, 0xdfb0, +0x3a, 0x73, 0x6c, 0x6f, 0x74, 0x5f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, +0x65, 0x3a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x6c, 0x6f, +0x74, 0xd83d, 0xde97, 0x3a, 0x72, 0x65, 0x64, 0x5f, 0x63, 0x61, 0x72, 0x3a, +0x63, 0x61, 0x72, 0x72, 0x65, 0x64, 0xd83d, 0xde95, 0x3a, 0x74, 0x61, 0x78, +0x69, 0x3a, 0x74, 0x61, 0x78, 0x69, 0xd83d, 0xde99, 0x3a, 0x62, 0x6c, 0x75, +0x65, 0x5f, 0x63, 0x61, 0x72, 0x3a, 0x62, 0x6c, 0x75, 0x65, 0x63, 0x61, +0x72, 0xd83d, 0xde8c, 0x3a, 0x62, 0x75, 0x73, 0x3a, 0x62, 0x75, 0x73, 0xd83d, +0xde8e, 0x3a, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x79, 0x62, 0x75, 0x73, +0x3a, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x79, 0x62, 0x75, 0x73, 0xd83c, +0xdfce, 0x3a, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x61, 0x72, +0x3a, 0x63, 0x61, 0x72, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0xd83c, 0xdfce, +0x3a, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x63, 0x61, 0x72, 0x3a, 0x63, 0x61, +0x72, 0x72, 0x61, 0x63, 0x65, 0xd83d, 0xde93, 0x3a, 0x70, 0x6f, 0x6c, 0x69, +0x63, 0x65, 0x5f, 0x63, 0x61, 0x72, 0x3a, 0x63, 0x61, 0x72, 0x70, 0x6f, +0x6c, 0x69, 0x63, 0x65, 0xd83d, 0xde91, 0x3a, 0x61, 0x6d, 0x62, 0x75, 0x6c, +0x61, 0x6e, 0x63, 0x65, 0x3a, 0x61, 0x6d, 0x62, 0x75, 0x6c, 0x61, 0x6e, +0x63, 0x65, 0xd83d, 0xde92, 0x3a, 0x66, 0x69, 0x72, 0x65, 0x5f, 0x65, 0x6e, +0x67, 0x69, 0x6e, 0x65, 0x3a, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x66, +0x69, 0x72, 0x65, 0xd83d, 0xde90, 0x3a, 0x6d, 0x69, 0x6e, 0x69, 0x62, 0x75, +0x73, 0x3a, 0x6d, 0x69, 0x6e, 0x69, 0x62, 0x75, 0x73, 0xd83d, 0xde9a, 0x3a, +0x74, 0x72, 0x75, 0x63, 0x6b, 0x3a, 0x74, 0x72, 0x75, 0x63, 0x6b, 0xd83d, +0xde9b, 0x3a, 0x61, 0x72, 0x74, 0x69, 0x63, 0x75, 0x6c, 0x61, 0x74, 0x65, +0x64, 0x5f, 0x6c, 0x6f, 0x72, 0x72, 0x79, 0x3a, 0x61, 0x72, 0x74, 0x69, +0x63, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x6c, 0x6f, 0x72, 0x72, 0x79, +0xd83d, 0xde9c, 0x3a, 0x74, 0x72, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x3a, 0x74, +0x72, 0x61, 0x63, 0x74, 0x6f, 0x72, 0xd83d, 0xdef4, 0x3a, 0x73, 0x63, 0x6f, +0x6f, 0x74, 0x65, 0x72, 0x3a, 0x73, 0x63, 0x6f, 0x6f, 0x74, 0x65, 0x72, +0xd83d, 0xdeb2, 0x3a, 0x62, 0x69, 0x6b, 0x65, 0x3a, 0x62, 0x69, 0x6b, 0x65, +0xd83d, 0xdef5, 0x3a, 0x6d, 0x6f, 0x74, 0x6f, 0x72, 0x62, 0x69, 0x6b, 0x65, +0x3a, 0x6d, 0x6f, 0x74, 0x6f, 0x72, 0x62, 0x69, 0x6b, 0x65, 0xd83d, 0xdef5, +0x3a, 0x6d, 0x6f, 0x74, 0x6f, 0x72, 0x5f, 0x73, 0x63, 0x6f, 0x6f, 0x74, +0x65, 0x72, 0x3a, 0x6d, 0x6f, 0x74, 0x6f, 0x72, 0x73, 0x63, 0x6f, 0x6f, +0x74, 0x65, 0x72, 0xd83c, 0xdfcd, 0x3a, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, +0x5f, 0x6d, 0x6f, 0x74, 0x6f, 0x72, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x3a, +0x6d, 0x6f, 0x74, 0x6f, 0x72, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x72, 0x61, +0x63, 0x69, 0x6e, 0x67, 0xd83c, 0xdfcd, 0x3a, 0x6d, 0x6f, 0x74, 0x6f, 0x72, +0x63, 0x79, 0x63, 0x6c, 0x65, 0x3a, 0x6d, 0x6f, 0x74, 0x6f, 0x72, 0x63, +0x79, 0x63, 0x6c, 0x65, 0xd83d, 0xdea8, 0x3a, 0x72, 0x6f, 0x74, 0x61, 0x74, +0x69, 0x6e, 0x67, 0x5f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x6c, 0x69, +0x67, 0x68, 0x74, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6e, 0x67, 0xd83d, +0xde94, 0x3a, 0x6f, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x70, +0x6f, 0x6c, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x61, 0x72, 0x3a, 0x63, 0x61, +0x72, 0x6f, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x70, 0x6f, 0x6c, +0x69, 0x63, 0x65, 0xd83d, 0xde8d, 0x3a, 0x6f, 0x6e, 0x63, 0x6f, 0x6d, 0x69, +0x6e, 0x67, 0x5f, 0x62, 0x75, 0x73, 0x3a, 0x62, 0x75, 0x73, 0x6f, 0x6e, +0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0xd83d, 0xde98, 0x3a, 0x6f, 0x6e, 0x63, +0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x75, 0x74, 0x6f, 0x6d, 0x6f, +0x62, 0x69, 0x6c, 0x65, 0x3a, 0x61, 0x75, 0x74, 0x6f, 0x6d, 0x6f, 0x62, +0x69, 0x6c, 0x65, 0x6f, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0xd83d, +0xde96, 0x3a, 0x6f, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x74, +0x61, 0x78, 0x69, 0x3a, 0x6f, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, +0x74, 0x61, 0x78, 0x69, 0xd83d, 0xdea1, 0x3a, 0x61, 0x65, 0x72, 0x69, 0x61, +0x6c, 0x5f, 0x74, 0x72, 0x61, 0x6d, 0x77, 0x61, 0x79, 0x3a, 0x61, 0x65, +0x72, 0x69, 0x61, 0x6c, 0x74, 0x72, 0x61, 0x6d, 0x77, 0x61, 0x79, 0xd83d, +0xdea0, 0x3a, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x5f, 0x63, +0x61, 0x62, 0x6c, 0x65, 0x77, 0x61, 0x79, 0x3a, 0x63, 0x61, 0x62, 0x6c, +0x65, 0x77, 0x61, 0x79, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, +0xd83d, 0xde9f, 0x3a, 0x73, 0x75, 0x73, 0x70, 0x65, 0x6e, 0x73, 0x69, 0x6f, +0x6e, 0x5f, 0x72, 0x61, 0x69, 0x6c, 0x77, 0x61, 0x79, 0x3a, 0x72, 0x61, +0x69, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x75, 0x73, 0x70, 0x65, 0x6e, 0x73, +0x69, 0x6f, 0x6e, 0xd83d, 0xde83, 0x3a, 0x72, 0x61, 0x69, 0x6c, 0x77, 0x61, +0x79, 0x5f, 0x63, 0x61, 0x72, 0x3a, 0x63, 0x61, 0x72, 0x72, 0x61, 0x69, +0x6c, 0x77, 0x61, 0x79, 0xd83d, 0xde8b, 0x3a, 0x74, 0x72, 0x61, 0x69, 0x6e, +0x3a, 0x74, 0x72, 0x61, 0x69, 0x6e, 0xd83d, 0xde9e, 0x3a, 0x6d, 0x6f, 0x75, +0x6e, 0x74, 0x61, 0x69, 0x6e, 0x5f, 0x72, 0x61, 0x69, 0x6c, 0x77, 0x61, +0x79, 0x3a, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x72, 0x61, +0x69, 0x6c, 0x77, 0x61, 0x79, 0xd83d, 0xde9d, 0x3a, 0x6d, 0x6f, 0x6e, 0x6f, +0x72, 0x61, 0x69, 0x6c, 0x3a, 0x6d, 0x6f, 0x6e, 0x6f, 0x72, 0x61, 0x69, +0x6c, 0xd83d, 0xde84, 0x3a, 0x62, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x74, 0x72, +0x61, 0x69, 0x6e, 0x5f, 0x73, 0x69, 0x64, 0x65, 0x3a, 0x62, 0x75, 0x6c, +0x6c, 0x65, 0x74, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x73, 0x69, 0x64, 0x65, +0xd83d, 0xde85, 0x3a, 0x62, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x74, 0x72, 0x61, +0x69, 0x6e, 0x5f, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x3a, 0x62, 0x75, 0x6c, +0x6c, 0x65, 0x74, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x66, 0x72, 0x6f, 0x6e, +0x74, 0xd83d, 0xde88, 0x3a, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x72, 0x61, +0x69, 0x6c, 0x3a, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x72, 0x61, 0x69, 0x6c, +0xd83d, 0xde82, 0x3a, 0x73, 0x74, 0x65, 0x61, 0x6d, 0x5f, 0x6c, 0x6f, 0x63, +0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x76, 0x65, 0x3a, 0x6c, 0x6f, 0x63, 0x6f, +0x6d, 0x6f, 0x74, 0x69, 0x76, 0x65, 0x73, 0x74, 0x65, 0x61, 0x6d, 0xd83d, +0xde86, 0x3a, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x32, 0x3a, 0x74, 0x72, 0x61, +0x69, 0x6e, 0x32, 0xd83d, 0xde87, 0x3a, 0x6d, 0x65, 0x74, 0x72, 0x6f, 0x3a, +0x6d, 0x65, 0x74, 0x72, 0x6f, 0xd83d, 0xde8a, 0x3a, 0x74, 0x72, 0x61, 0x6d, +0x3a, 0x74, 0x72, 0x61, 0x6d, 0xd83d, 0xde89, 0x3a, 0x73, 0x74, 0x61, 0x74, +0x69, 0x6f, 0x6e, 0x3a, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0xd83d, +0xde81, 0x3a, 0x68, 0x65, 0x6c, 0x69, 0x63, 0x6f, 0x70, 0x74, 0x65, 0x72, +0x3a, 0x68, 0x65, 0x6c, 0x69, 0x63, 0x6f, 0x70, 0x74, 0x65, 0x72, 0xd83d, +0xdee9, 0x3a, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x5f, 0x61, 0x69, 0x72, 0x70, +0x6c, 0x61, 0x6e, 0x65, 0x3a, 0x61, 0x69, 0x72, 0x70, 0x6c, 0x61, 0x6e, +0x65, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0xd83d, 0xdee9, 0x3a, 0x61, 0x69, 0x72, +0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5f, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x3a, +0x61, 0x69, 0x72, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x73, 0x6d, 0x61, 0x6c, +0x6c, 0x2708, 0xfe0f, 0x3a, 0x61, 0x69, 0x72, 0x70, 0x6c, 0x61, 0x6e, 0x65, +0x3a, 0x61, 0x69, 0x72, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0xd83d, 0xdeeb, 0x3a, +0x61, 0x69, 0x72, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5f, 0x64, 0x65, 0x70, +0x61, 0x72, 0x74, 0x75, 0x72, 0x65, 0x3a, 0x61, 0x69, 0x72, 0x70, 0x6c, +0x61, 0x6e, 0x65, 0x64, 0x65, 0x70, 0x61, 0x72, 0x74, 0x75, 0x72, 0x65, +0xd83d, 0xdeec, 0x3a, 0x61, 0x69, 0x72, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5f, +0x61, 0x72, 0x72, 0x69, 0x76, 0x69, 0x6e, 0x67, 0x3a, 0x61, 0x69, 0x72, +0x70, 0x6c, 0x61, 0x6e, 0x65, 0x61, 0x72, 0x72, 0x69, 0x76, 0x69, 0x6e, +0x67, 0xd83d, 0xde80, 0x3a, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x3a, 0x72, +0x6f, 0x63, 0x6b, 0x65, 0x74, 0xd83d, 0xdef0, 0x3a, 0x73, 0x61, 0x74, 0x65, +0x6c, 0x6c, 0x69, 0x74, 0x65, 0x5f, 0x6f, 0x72, 0x62, 0x69, 0x74, 0x61, +0x6c, 0x3a, 0x6f, 0x72, 0x62, 0x69, 0x74, 0x61, 0x6c, 0x73, 0x61, 0x74, +0x65, 0x6c, 0x6c, 0x69, 0x74, 0x65, 0xd83d, 0xdcba, 0x3a, 0x73, 0x65, 0x61, +0x74, 0x3a, 0x73, 0x65, 0x61, 0x74, 0xd83d, 0xdef6, 0x3a, 0x6b, 0x61, 0x79, +0x61, 0x6b, 0x3a, 0x6b, 0x61, 0x79, 0x61, 0x6b, 0xd83d, 0xdef6, 0x3a, 0x63, +0x61, 0x6e, 0x6f, 0x65, 0x3a, 0x63, 0x61, 0x6e, 0x6f, 0x65, 0x26f5, 0xfe0f, +0x3a, 0x73, 0x61, 0x69, 0x6c, 0x62, 0x6f, 0x61, 0x74, 0x3a, 0x73, 0x61, +0x69, 0x6c, 0x62, 0x6f, 0x61, 0x74, 0xd83d, 0xdee5, 0x3a, 0x6d, 0x6f, 0x74, +0x6f, 0x72, 0x62, 0x6f, 0x61, 0x74, 0x3a, 0x6d, 0x6f, 0x74, 0x6f, 0x72, +0x62, 0x6f, 0x61, 0x74, 0xd83d, 0xdea4, 0x3a, 0x73, 0x70, 0x65, 0x65, 0x64, +0x62, 0x6f, 0x61, 0x74, 0x3a, 0x73, 0x70, 0x65, 0x65, 0x64, 0x62, 0x6f, +0x61, 0x74, 0xd83d, 0xdef3, 0x3a, 0x70, 0x61, 0x73, 0x73, 0x65, 0x6e, 0x67, +0x65, 0x72, 0x5f, 0x73, 0x68, 0x69, 0x70, 0x3a, 0x70, 0x61, 0x73, 0x73, +0x65, 0x6e, 0x67, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0xd83d, 0xdef3, 0x3a, +0x63, 0x72, 0x75, 0x69, 0x73, 0x65, 0x5f, 0x73, 0x68, 0x69, 0x70, 0x3a, +0x63, 0x72, 0x75, 0x69, 0x73, 0x65, 0x73, 0x68, 0x69, 0x70, 0x26f4, 0x3a, +0x66, 0x65, 0x72, 0x72, 0x79, 0x3a, 0x66, 0x65, 0x72, 0x72, 0x79, 0xd83d, +0xdea2, 0x3a, 0x73, 0x68, 0x69, 0x70, 0x3a, 0x73, 0x68, 0x69, 0x70, 0x2693, +0xfe0f, 0x3a, 0x61, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x3a, 0x61, 0x6e, 0x63, +0x68, 0x6f, 0x72, 0xd83d, 0xdea7, 0x3a, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, +0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x63, 0x6f, 0x6e, 0x73, 0x74, +0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x26fd, 0xfe0f, 0x3a, 0x66, 0x75, +0x65, 0x6c, 0x70, 0x75, 0x6d, 0x70, 0x3a, 0x66, 0x75, 0x65, 0x6c, 0x70, +0x75, 0x6d, 0x70, 0xd83d, 0xde8f, 0x3a, 0x62, 0x75, 0x73, 0x73, 0x74, 0x6f, +0x70, 0x3a, 0x62, 0x75, 0x73, 0x73, 0x74, 0x6f, 0x70, 0xd83d, 0xdea6, 0x3a, +0x76, 0x65, 0x72, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x74, 0x72, 0x61, +0x66, 0x66, 0x69, 0x63, 0x5f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x6c, +0x69, 0x67, 0x68, 0x74, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x76, +0x65, 0x72, 0x74, 0x69, 0x63, 0x61, 0x6c, 0xd83d, 0xdea5, 0x3a, 0x74, 0x72, +0x61, 0x66, 0x66, 0x69, 0x63, 0x5f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x3a, +0x6c, 0x69, 0x67, 0x68, 0x74, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, +0xd83d, 0xddfa, 0x3a, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x5f, 0x6d, 0x61, 0x70, +0x3a, 0x6d, 0x61, 0x70, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0xd83d, 0xddfa, 0x3a, +0x6d, 0x61, 0x70, 0x3a, 0x6d, 0x61, 0x70, 0xd83d, 0xddff, 0x3a, 0x6d, 0x6f, +0x79, 0x61, 0x69, 0x3a, 0x6d, 0x6f, 0x79, 0x61, 0x69, 0xd83d, 0xddfd, 0x3a, +0x73, 0x74, 0x61, 0x74, 0x75, 0x65, 0x5f, 0x6f, 0x66, 0x5f, 0x6c, 0x69, +0x62, 0x65, 0x72, 0x74, 0x79, 0x3a, 0x6c, 0x69, 0x62, 0x65, 0x72, 0x74, +0x79, 0x6f, 0x66, 0x73, 0x74, 0x61, 0x74, 0x75, 0x65, 0x26f2, 0xfe0f, 0x3a, +0x66, 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x3a, 0x66, 0x6f, 0x75, +0x6e, 0x74, 0x61, 0x69, 0x6e, 0xd83d, 0xddfc, 0x3a, 0x74, 0x6f, 0x6b, 0x79, +0x6f, 0x5f, 0x74, 0x6f, 0x77, 0x65, 0x72, 0x3a, 0x74, 0x6f, 0x6b, 0x79, +0x6f, 0x74, 0x6f, 0x77, 0x65, 0x72, 0xd83c, 0xdff0, 0x3a, 0x65, 0x75, 0x72, +0x6f, 0x70, 0x65, 0x61, 0x6e, 0x5f, 0x63, 0x61, 0x73, 0x74, 0x6c, 0x65, +0x3a, 0x63, 0x61, 0x73, 0x74, 0x6c, 0x65, 0x65, 0x75, 0x72, 0x6f, 0x70, +0x65, 0x61, 0x6e, 0xd83c, 0xdfef, 0x3a, 0x6a, 0x61, 0x70, 0x61, 0x6e, 0x65, +0x73, 0x65, 0x5f, 0x63, 0x61, 0x73, 0x74, 0x6c, 0x65, 0x3a, 0x63, 0x61, +0x73, 0x74, 0x6c, 0x65, 0x6a, 0x61, 0x70, 0x61, 0x6e, 0x65, 0x73, 0x65, +0xd83c, 0xdfdf, 0x3a, 0x73, 0x74, 0x61, 0x64, 0x69, 0x75, 0x6d, 0x3a, 0x73, +0x74, 0x61, 0x64, 0x69, 0x75, 0x6d, 0xd83c, 0xdfa1, 0x3a, 0x66, 0x65, 0x72, +0x72, 0x69, 0x73, 0x5f, 0x77, 0x68, 0x65, 0x65, 0x6c, 0x3a, 0x66, 0x65, +0x72, 0x72, 0x69, 0x73, 0x77, 0x68, 0x65, 0x65, 0x6c, 0xd83c, 0xdfa2, 0x3a, +0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x61, 0x73, 0x74, +0x65, 0x72, 0x3a, 0x63, 0x6f, 0x61, 0x73, 0x74, 0x65, 0x72, 0x72, 0x6f, +0x6c, 0x6c, 0x65, 0x72, 0xd83c, 0xdfa0, 0x3a, 0x63, 0x61, 0x72, 0x6f, 0x75, +0x73, 0x65, 0x6c, 0x5f, 0x68, 0x6f, 0x72, 0x73, 0x65, 0x3a, 0x63, 0x61, +0x72, 0x6f, 0x75, 0x73, 0x65, 0x6c, 0x68, 0x6f, 0x72, 0x73, 0x65, 0x26f1, +0x3a, 0x75, 0x6d, 0x62, 0x72, 0x65, 0x6c, 0x6c, 0x61, 0x5f, 0x6f, 0x6e, +0x5f, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x67, 0x72, 0x6f, 0x75, +0x6e, 0x64, 0x6f, 0x6e, 0x75, 0x6d, 0x62, 0x72, 0x65, 0x6c, 0x6c, 0x61, +0x26f1, 0x3a, 0x62, 0x65, 0x61, 0x63, 0x68, 0x5f, 0x75, 0x6d, 0x62, 0x72, +0x65, 0x6c, 0x6c, 0x61, 0x3a, 0x62, 0x65, 0x61, 0x63, 0x68, 0x75, 0x6d, +0x62, 0x72, 0x65, 0x6c, 0x6c, 0x61, 0xd83c, 0xdfd6, 0x3a, 0x62, 0x65, 0x61, +0x63, 0x68, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x75, 0x6d, 0x62, 0x72, +0x65, 0x6c, 0x6c, 0x61, 0x3a, 0x62, 0x65, 0x61, 0x63, 0x68, 0x75, 0x6d, +0x62, 0x72, 0x65, 0x6c, 0x6c, 0x61, 0x77, 0x69, 0x74, 0x68, 0xd83c, 0xdfd6, +0x3a, 0x62, 0x65, 0x61, 0x63, 0x68, 0x3a, 0x62, 0x65, 0x61, 0x63, 0x68, +0xd83c, 0xdfdd, 0x3a, 0x64, 0x65, 0x73, 0x65, 0x72, 0x74, 0x5f, 0x69, 0x73, +0x6c, 0x61, 0x6e, 0x64, 0x3a, 0x64, 0x65, 0x73, 0x65, 0x72, 0x74, 0x69, +0x73, 0x6c, 0x61, 0x6e, 0x64, 0xd83c, 0xdfdd, 0x3a, 0x69, 0x73, 0x6c, 0x61, +0x6e, 0x64, 0x3a, 0x69, 0x73, 0x6c, 0x61, 0x6e, 0x64, 0x26f0, 0x3a, 0x6d, +0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x3a, 0x6d, 0x6f, 0x75, 0x6e, +0x74, 0x61, 0x69, 0x6e, 0xd83c, 0xdfd4, 0x3a, 0x73, 0x6e, 0x6f, 0x77, 0x5f, +0x63, 0x61, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x6d, 0x6f, 0x75, 0x6e, 0x74, +0x61, 0x69, 0x6e, 0x3a, 0x63, 0x61, 0x70, 0x70, 0x65, 0x64, 0x6d, 0x6f, +0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, 0x6e, 0x6f, 0x77, 0xd83c, 0xdfd4, +0x3a, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x6e, +0x6f, 0x77, 0x3a, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, +0x6e, 0x6f, 0x77, 0xd83d, 0xddfb, 0x3a, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, +0x66, 0x75, 0x6a, 0x69, 0x3a, 0x66, 0x75, 0x6a, 0x69, 0x6d, 0x6f, 0x75, +0x6e, 0x74, 0xd83c, 0xdf0b, 0x3a, 0x76, 0x6f, 0x6c, 0x63, 0x61, 0x6e, 0x6f, +0x3a, 0x76, 0x6f, 0x6c, 0x63, 0x61, 0x6e, 0x6f, 0xd83c, 0xdfdc, 0x3a, 0x64, +0x65, 0x73, 0x65, 0x72, 0x74, 0x3a, 0x64, 0x65, 0x73, 0x65, 0x72, 0x74, +0xd83c, 0xdfd5, 0x3a, 0x63, 0x61, 0x6d, 0x70, 0x69, 0x6e, 0x67, 0x3a, 0x63, +0x61, 0x6d, 0x70, 0x69, 0x6e, 0x67, 0x26fa, 0xfe0f, 0x3a, 0x74, 0x65, 0x6e, +0x74, 0x3a, 0x74, 0x65, 0x6e, 0x74, 0xd83d, 0xdee4, 0x3a, 0x72, 0x61, 0x69, +0x6c, 0x72, 0x6f, 0x61, 0x64, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x3a, +0x72, 0x61, 0x69, 0x6c, 0x72, 0x6f, 0x61, 0x64, 0x74, 0x72, 0x61, 0x63, +0x6b, 0xd83d, 0xdee4, 0x3a, 0x72, 0x61, 0x69, 0x6c, 0x77, 0x61, 0x79, 0x5f, +0x74, 0x72, 0x61, 0x63, 0x6b, 0x3a, 0x72, 0x61, 0x69, 0x6c, 0x77, 0x61, +0x79, 0x74, 0x72, 0x61, 0x63, 0x6b, 0xd83d, 0xdee3, 0x3a, 0x6d, 0x6f, 0x74, +0x6f, 0x72, 0x77, 0x61, 0x79, 0x3a, 0x6d, 0x6f, 0x74, 0x6f, 0x72, 0x77, +0x61, 0x79, 0xd83c, 0xdfd7, 0x3a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, +0x67, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, +0x6f, 0x6e, 0x3a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x63, +0x6f, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0xd83c, +0xdfd7, 0x3a, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, +0x6f, 0x6e, 0x5f, 0x73, 0x69, 0x74, 0x65, 0x3a, 0x63, 0x6f, 0x6e, 0x73, +0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x69, 0x74, 0x65, +0xd83c, 0xdfed, 0x3a, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x3a, 0x66, +0x61, 0x63, 0x74, 0x6f, 0x72, 0x79, 0xd83c, 0xdfe0, 0x3a, 0x68, 0x6f, 0x75, +0x73, 0x65, 0x3a, 0x68, 0x6f, 0x75, 0x73, 0x65, 0xd83c, 0xdfe1, 0x3a, 0x68, +0x6f, 0x75, 0x73, 0x65, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x67, 0x61, +0x72, 0x64, 0x65, 0x6e, 0x3a, 0x67, 0x61, 0x72, 0x64, 0x65, 0x6e, 0x68, +0x6f, 0x75, 0x73, 0x65, 0x77, 0x69, 0x74, 0x68, 0xd83c, 0xdfd8, 0x3a, 0x68, +0x6f, 0x75, 0x73, 0x65, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, +0x67, 0x73, 0x3a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, +0x68, 0x6f, 0x75, 0x73, 0x65, 0xd83c, 0xdfd8, 0x3a, 0x68, 0x6f, 0x6d, 0x65, +0x73, 0x3a, 0x68, 0x6f, 0x6d, 0x65, 0x73, 0xd83c, 0xdfda, 0x3a, 0x64, 0x65, +0x72, 0x65, 0x6c, 0x69, 0x63, 0x74, 0x5f, 0x68, 0x6f, 0x75, 0x73, 0x65, +0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x62, 0x75, +0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x64, 0x65, 0x72, 0x65, 0x6c, 0x69, +0x63, 0x74, 0x68, 0x6f, 0x75, 0x73, 0x65, 0xd83c, 0xdfda, 0x3a, 0x68, 0x6f, +0x75, 0x73, 0x65, 0x5f, 0x61, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x65, +0x64, 0x3a, 0x61, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x65, 0x64, 0x68, +0x6f, 0x75, 0x73, 0x65, 0xd83c, 0xdfe2, 0x3a, 0x6f, 0x66, 0x66, 0x69, 0x63, +0x65, 0x3a, 0x6f, 0x66, 0x66, 0x69, 0x63, 0x65, 0xd83c, 0xdfec, 0x3a, 0x64, +0x65, 0x70, 0x61, 0x72, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x74, +0x6f, 0x72, 0x65, 0x3a, 0x64, 0x65, 0x70, 0x61, 0x72, 0x74, 0x6d, 0x65, +0x6e, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0xd83c, 0xdfe3, 0x3a, 0x70, 0x6f, +0x73, 0x74, 0x5f, 0x6f, 0x66, 0x66, 0x69, 0x63, 0x65, 0x3a, 0x6f, 0x66, +0x66, 0x69, 0x63, 0x65, 0x70, 0x6f, 0x73, 0x74, 0xd83c, 0xdfe4, 0x3a, 0x65, +0x75, 0x72, 0x6f, 0x70, 0x65, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x73, 0x74, +0x5f, 0x6f, 0x66, 0x66, 0x69, 0x63, 0x65, 0x3a, 0x65, 0x75, 0x72, 0x6f, +0x70, 0x65, 0x61, 0x6e, 0x6f, 0x66, 0x66, 0x69, 0x63, 0x65, 0x70, 0x6f, +0x73, 0x74, 0xd83c, 0xdfe5, 0x3a, 0x68, 0x6f, 0x73, 0x70, 0x69, 0x74, 0x61, +0x6c, 0x3a, 0x68, 0x6f, 0x73, 0x70, 0x69, 0x74, 0x61, 0x6c, 0xd83c, 0xdfe6, +0x3a, 0x62, 0x61, 0x6e, 0x6b, 0x3a, 0x62, 0x61, 0x6e, 0x6b, 0xd83c, 0xdfe8, +0x3a, 0x68, 0x6f, 0x74, 0x65, 0x6c, 0x3a, 0x68, 0x6f, 0x74, 0x65, 0x6c, +0xd83c, 0xdfea, 0x3a, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x6e, 0x69, 0x65, 0x6e, +0x63, 0x65, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3a, 0x63, 0x6f, 0x6e, +0x76, 0x65, 0x6e, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x74, 0x6f, 0x72, +0x65, 0xd83c, 0xdfeb, 0x3a, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x3a, 0x73, +0x63, 0x68, 0x6f, 0x6f, 0x6c, 0xd83c, 0xdfe9, 0x3a, 0x6c, 0x6f, 0x76, 0x65, +0x5f, 0x68, 0x6f, 0x74, 0x65, 0x6c, 0x3a, 0x68, 0x6f, 0x74, 0x65, 0x6c, +0x6c, 0x6f, 0x76, 0x65, 0xd83d, 0xdc92, 0x3a, 0x77, 0x65, 0x64, 0x64, 0x69, +0x6e, 0x67, 0x3a, 0x77, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0xd83c, 0xdfdb, +0x3a, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x62, +0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x62, 0x75, 0x69, 0x6c, +0x64, 0x69, 0x6e, 0x67, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x69, 0x63, 0x61, +0x6c, 0x26ea, 0xfe0f, 0x3a, 0x63, 0x68, 0x75, 0x72, 0x63, 0x68, 0x3a, 0x63, +0x68, 0x75, 0x72, 0x63, 0x68, 0xd83d, 0xdd4c, 0x3a, 0x6d, 0x6f, 0x73, 0x71, +0x75, 0x65, 0x3a, 0x6d, 0x6f, 0x73, 0x71, 0x75, 0x65, 0xd83d, 0xdd4d, 0x3a, +0x73, 0x79, 0x6e, 0x61, 0x67, 0x6f, 0x67, 0x75, 0x65, 0x3a, 0x73, 0x79, +0x6e, 0x61, 0x67, 0x6f, 0x67, 0x75, 0x65, 0xd83d, 0xdd4b, 0x3a, 0x6b, 0x61, +0x61, 0x62, 0x61, 0x3a, 0x6b, 0x61, 0x61, 0x62, 0x61, 0x26e9, 0x3a, 0x73, +0x68, 0x69, 0x6e, 0x74, 0x6f, 0x5f, 0x73, 0x68, 0x72, 0x69, 0x6e, 0x65, +0x3a, 0x73, 0x68, 0x69, 0x6e, 0x74, 0x6f, 0x73, 0x68, 0x72, 0x69, 0x6e, +0x65, 0xd83d, 0xddfe, 0x3a, 0x6a, 0x61, 0x70, 0x61, 0x6e, 0x3a, 0x6a, 0x61, +0x70, 0x61, 0x6e, 0xd83c, 0xdf91, 0x3a, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x73, +0x63, 0x65, 0x6e, 0x65, 0x3a, 0x72, 0x69, 0x63, 0x65, 0x73, 0x63, 0x65, +0x6e, 0x65, 0xd83c, 0xdfde, 0x3a, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, +0x6c, 0x5f, 0x70, 0x61, 0x72, 0x6b, 0x3a, 0x6e, 0x61, 0x74, 0x69, 0x6f, +0x6e, 0x61, 0x6c, 0x70, 0x61, 0x72, 0x6b, 0xd83c, 0xdfde, 0x3a, 0x70, 0x61, +0x72, 0x6b, 0x3a, 0x70, 0x61, 0x72, 0x6b, 0xd83c, 0xdf05, 0x3a, 0x73, 0x75, +0x6e, 0x72, 0x69, 0x73, 0x65, 0x3a, 0x73, 0x75, 0x6e, 0x72, 0x69, 0x73, +0x65, 0xd83c, 0xdf04, 0x3a, 0x73, 0x75, 0x6e, 0x72, 0x69, 0x73, 0x65, 0x5f, +0x6f, 0x76, 0x65, 0x72, 0x5f, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, +0x6e, 0x73, 0x3a, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, +0x6f, 0x76, 0x65, 0x72, 0x73, 0x75, 0x6e, 0x72, 0x69, 0x73, 0x65, 0xd83c, +0xdf20, 0x3a, 0x73, 0x74, 0x61, 0x72, 0x73, 0x3a, 0x73, 0x74, 0x61, 0x72, +0x73, 0xd83c, 0xdf87, 0x3a, 0x73, 0x70, 0x61, 0x72, 0x6b, 0x6c, 0x65, 0x72, +0x3a, 0x73, 0x70, 0x61, 0x72, 0x6b, 0x6c, 0x65, 0x72, 0xd83c, 0xdf86, 0x3a, +0x66, 0x69, 0x72, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x3a, 0x66, 0x69, +0x72, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0xd83c, 0xdf07, 0x3a, 0x63, 0x69, +0x74, 0x79, 0x5f, 0x73, 0x75, 0x6e, 0x72, 0x69, 0x73, 0x65, 0x3a, 0x63, +0x69, 0x74, 0x79, 0x73, 0x75, 0x6e, 0x72, 0x69, 0x73, 0x65, 0xd83c, 0xdf07, +0x3a, 0x63, 0x69, 0x74, 0x79, 0x5f, 0x73, 0x75, 0x6e, 0x73, 0x65, 0x74, +0x3a, 0x63, 0x69, 0x74, 0x79, 0x73, 0x75, 0x6e, 0x73, 0x65, 0x74, 0xd83c, +0xdf06, 0x3a, 0x63, 0x69, 0x74, 0x79, 0x5f, 0x64, 0x75, 0x73, 0x6b, 0x3a, +0x63, 0x69, 0x74, 0x79, 0x64, 0x75, 0x73, 0x6b, 0xd83c, 0xdfd9, 0x3a, 0x63, +0x69, 0x74, 0x79, 0x73, 0x63, 0x61, 0x70, 0x65, 0x3a, 0x63, 0x69, 0x74, +0x79, 0x73, 0x63, 0x61, 0x70, 0x65, 0xd83c, 0xdf03, 0x3a, 0x6e, 0x69, 0x67, +0x68, 0x74, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x73, 0x74, 0x61, 0x72, +0x73, 0x3a, 0x6e, 0x69, 0x67, 0x68, 0x74, 0x73, 0x74, 0x61, 0x72, 0x73, +0x77, 0x69, 0x74, 0x68, 0xd83c, 0xdf0c, 0x3a, 0x6d, 0x69, 0x6c, 0x6b, 0x79, +0x5f, 0x77, 0x61, 0x79, 0x3a, 0x6d, 0x69, 0x6c, 0x6b, 0x79, 0x77, 0x61, +0x79, 0xd83c, 0xdf09, 0x3a, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x5f, 0x61, +0x74, 0x5f, 0x6e, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x61, 0x74, 0x62, 0x72, +0x69, 0x64, 0x67, 0x65, 0x6e, 0x69, 0x67, 0x68, 0x74, 0xd83c, 0xdf01, 0x3a, +0x66, 0x6f, 0x67, 0x67, 0x79, 0x3a, 0x66, 0x6f, 0x67, 0x67, 0x79, 0x231a, +0xfe0f, 0x3a, 0x77, 0x61, 0x74, 0x63, 0x68, 0x3a, 0x77, 0x61, 0x74, 0x63, +0x68, 0xd83d, 0xdcf1, 0x3a, 0x69, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x3a, 0x69, +0x70, 0x68, 0x6f, 0x6e, 0x65, 0xd83d, 0xdcf2, 0x3a, 0x63, 0x61, 0x6c, 0x6c, +0x69, 0x6e, 0x67, 0x3a, 0x63, 0x61, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0xd83d, +0xdcbb, 0x3a, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x72, 0x3a, 0x63, +0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x72, 0x2328, 0xfe0f, 0x3a, 0x6b, 0x65, +0x79, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x3a, 0x6b, 0x65, 0x79, 0x62, 0x6f, +0x61, 0x72, 0x64, 0xd83d, 0xdda5, 0x3a, 0x64, 0x65, 0x73, 0x6b, 0x74, 0x6f, +0x70, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x72, 0x3a, 0x63, +0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x72, 0x64, 0x65, 0x73, 0x6b, 0x74, +0x6f, 0x70, 0xd83d, 0xdda5, 0x3a, 0x64, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, +0x3a, 0x64, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, 0xd83d, 0xdda8, 0x3a, 0x70, +0x72, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x3a, 0x70, 0x72, 0x69, 0x6e, 0x74, +0x65, 0x72, 0xd83d, 0xddb1, 0x3a, 0x74, 0x68, 0x72, 0x65, 0x65, 0x5f, 0x62, +0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5f, 0x6d, 0x6f, 0x75, 0x73, 0x65, 0x3a, +0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x6d, 0x6f, 0x75, 0x73, 0x65, 0x74, +0x68, 0x72, 0x65, 0x65, 0xd83d, 0xddb1, 0x3a, 0x6d, 0x6f, 0x75, 0x73, 0x65, +0x5f, 0x74, 0x68, 0x72, 0x65, 0x65, 0x5f, 0x62, 0x75, 0x74, 0x74, 0x6f, +0x6e, 0x3a, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x6d, 0x6f, 0x75, 0x73, +0x65, 0x74, 0x68, 0x72, 0x65, 0x65, 0xd83d, 0xddb2, 0x3a, 0x74, 0x72, 0x61, +0x63, 0x6b, 0x62, 0x61, 0x6c, 0x6c, 0x3a, 0x74, 0x72, 0x61, 0x63, 0x6b, +0x62, 0x61, 0x6c, 0x6c, 0xd83d, 0xdd79, 0x3a, 0x6a, 0x6f, 0x79, 0x73, 0x74, +0x69, 0x63, 0x6b, 0x3a, 0x6a, 0x6f, 0x79, 0x73, 0x74, 0x69, 0x63, 0x6b, +0xd83d, 0xdddc, 0x3a, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, +0x6f, 0x6e, 0x3a, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, +0x6f, 0x6e, 0xd83d, 0xdcbd, 0x3a, 0x6d, 0x69, 0x6e, 0x69, 0x64, 0x69, 0x73, +0x63, 0x3a, 0x6d, 0x69, 0x6e, 0x69, 0x64, 0x69, 0x73, 0x63, 0xd83d, 0xdcbe, +0x3a, 0x66, 0x6c, 0x6f, 0x70, 0x70, 0x79, 0x5f, 0x64, 0x69, 0x73, 0x6b, +0x3a, 0x64, 0x69, 0x73, 0x6b, 0x66, 0x6c, 0x6f, 0x70, 0x70, 0x79, 0xd83d, +0xdcbf, 0x3a, 0x63, 0x64, 0x3a, 0x63, 0x64, 0xd83d, 0xdcc0, 0x3a, 0x64, 0x76, +0x64, 0x3a, 0x64, 0x76, 0x64, 0xd83d, 0xdcfc, 0x3a, 0x76, 0x68, 0x73, 0x3a, +0x76, 0x68, 0x73, 0xd83d, 0xdcf7, 0x3a, 0x63, 0x61, 0x6d, 0x65, 0x72, 0x61, +0x3a, 0x63, 0x61, 0x6d, 0x65, 0x72, 0x61, 0xd83d, 0xdcf8, 0x3a, 0x63, 0x61, +0x6d, 0x65, 0x72, 0x61, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x66, 0x6c, +0x61, 0x73, 0x68, 0x3a, 0x63, 0x61, 0x6d, 0x65, 0x72, 0x61, 0x66, 0x6c, +0x61, 0x73, 0x68, 0x77, 0x69, 0x74, 0x68, 0xd83d, 0xdcf9, 0x3a, 0x76, 0x69, +0x64, 0x65, 0x6f, 0x5f, 0x63, 0x61, 0x6d, 0x65, 0x72, 0x61, 0x3a, 0x63, +0x61, 0x6d, 0x65, 0x72, 0x61, 0x76, 0x69, 0x64, 0x65, 0x6f, 0xd83c, 0xdfa5, +0x3a, 0x6d, 0x6f, 0x76, 0x69, 0x65, 0x5f, 0x63, 0x61, 0x6d, 0x65, 0x72, +0x61, 0x3a, 0x63, 0x61, 0x6d, 0x65, 0x72, 0x61, 0x6d, 0x6f, 0x76, 0x69, +0x65, 0xd83d, 0xdcfd, 0x3a, 0x66, 0x69, 0x6c, 0x6d, 0x5f, 0x70, 0x72, 0x6f, +0x6a, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x3a, 0x66, 0x69, 0x6c, 0x6d, 0x70, +0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x6f, 0x72, 0xd83d, 0xdcfd, 0x3a, 0x70, +0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x3a, 0x70, 0x72, 0x6f, +0x6a, 0x65, 0x63, 0x74, 0x6f, 0x72, 0xd83c, 0xdf9e, 0x3a, 0x66, 0x69, 0x6c, +0x6d, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x3a, 0x66, 0x69, 0x6c, +0x6d, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0xd83d, 0xdcde, 0x3a, 0x74, 0x65, +0x6c, 0x65, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x5f, 0x72, 0x65, 0x63, 0x65, +0x69, 0x76, 0x65, 0x72, 0x3a, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, +0x72, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x260e, 0xfe0f, +0x3a, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x3a, 0x74, +0x65, 0x6c, 0x65, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0xd83d, 0xdcdf, 0x3a, 0x70, +0x61, 0x67, 0x65, 0x72, 0x3a, 0x70, 0x61, 0x67, 0x65, 0x72, 0xd83d, 0xdce0, +0x3a, 0x66, 0x61, 0x78, 0x3a, 0x66, 0x61, 0x78, 0xd83d, 0xdcfa, 0x3a, 0x74, +0x76, 0x3a, 0x74, 0x76, 0xd83d, 0xdcfb, 0x3a, 0x72, 0x61, 0x64, 0x69, 0x6f, +0x3a, 0x72, 0x61, 0x64, 0x69, 0x6f, 0xd83c, 0xdf99, 0x3a, 0x73, 0x74, 0x75, +0x64, 0x69, 0x6f, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x70, 0x68, 0x6f, +0x6e, 0x65, 0x3a, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x70, 0x68, 0x6f, 0x6e, +0x65, 0x73, 0x74, 0x75, 0x64, 0x69, 0x6f, 0xd83c, 0xdf99, 0x3a, 0x6d, 0x69, +0x63, 0x72, 0x6f, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x32, 0x3a, 0x6d, 0x69, +0x63, 0x72, 0x6f, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x32, 0xd83c, 0xdf9a, 0x3a, +0x6c, 0x65, 0x76, 0x65, 0x6c, 0x5f, 0x73, 0x6c, 0x69, 0x64, 0x65, 0x72, +0x3a, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x6c, 0x69, 0x64, 0x65, 0x72, +0xd83c, 0xdf9b, 0x3a, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x5f, 0x6b, +0x6e, 0x6f, 0x62, 0x73, 0x3a, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, +0x6b, 0x6e, 0x6f, 0x62, 0x73, 0x23f1, 0x3a, 0x73, 0x74, 0x6f, 0x70, 0x77, +0x61, 0x74, 0x63, 0x68, 0x3a, 0x73, 0x74, 0x6f, 0x70, 0x77, 0x61, 0x74, +0x63, 0x68, 0x23f2, 0x3a, 0x74, 0x69, 0x6d, 0x65, 0x72, 0x5f, 0x63, 0x6c, +0x6f, 0x63, 0x6b, 0x3a, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x74, 0x69, 0x6d, +0x65, 0x72, 0x23f2, 0x3a, 0x74, 0x69, 0x6d, 0x65, 0x72, 0x3a, 0x74, 0x69, +0x6d, 0x65, 0x72, 0x23f0, 0x3a, 0x61, 0x6c, 0x61, 0x72, 0x6d, 0x5f, 0x63, +0x6c, 0x6f, 0x63, 0x6b, 0x3a, 0x61, 0x6c, 0x61, 0x72, 0x6d, 0x63, 0x6c, +0x6f, 0x63, 0x6b, 0xd83d, 0xdd70, 0x3a, 0x6d, 0x61, 0x6e, 0x74, 0x6c, 0x65, +0x70, 0x69, 0x65, 0x63, 0x65, 0x5f, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x3a, +0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x6d, 0x61, 0x6e, 0x74, 0x6c, 0x65, 0x70, +0x69, 0x65, 0x63, 0x65, 0xd83d, 0xdd70, 0x3a, 0x63, 0x6c, 0x6f, 0x63, 0x6b, +0x3a, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x231b, 0xfe0f, 0x3a, 0x68, 0x6f, 0x75, +0x72, 0x67, 0x6c, 0x61, 0x73, 0x73, 0x3a, 0x68, 0x6f, 0x75, 0x72, 0x67, +0x6c, 0x61, 0x73, 0x73, 0x23f3, 0x3a, 0x68, 0x6f, 0x75, 0x72, 0x67, 0x6c, +0x61, 0x73, 0x73, 0x5f, 0x66, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x5f, +0x73, 0x61, 0x6e, 0x64, 0x3a, 0x66, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, +0x68, 0x6f, 0x75, 0x72, 0x67, 0x6c, 0x61, 0x73, 0x73, 0x73, 0x61, 0x6e, +0x64, 0xd83d, 0xdce1, 0x3a, 0x73, 0x61, 0x74, 0x65, 0x6c, 0x6c, 0x69, 0x74, +0x65, 0x3a, 0x73, 0x61, 0x74, 0x65, 0x6c, 0x6c, 0x69, 0x74, 0x65, 0xd83d, +0xdd0b, 0x3a, 0x62, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79, 0x3a, 0x62, 0x61, +0x74, 0x74, 0x65, 0x72, 0x79, 0xd83d, 0xdd0c, 0x3a, 0x65, 0x6c, 0x65, 0x63, +0x74, 0x72, 0x69, 0x63, 0x5f, 0x70, 0x6c, 0x75, 0x67, 0x3a, 0x65, 0x6c, +0x65, 0x63, 0x74, 0x72, 0x69, 0x63, 0x70, 0x6c, 0x75, 0x67, 0xd83d, 0xdca1, +0x3a, 0x62, 0x75, 0x6c, 0x62, 0x3a, 0x62, 0x75, 0x6c, 0x62, 0xd83d, 0xdd26, +0x3a, 0x66, 0x6c, 0x61, 0x73, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x3a, +0x66, 0x6c, 0x61, 0x73, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0xd83d, 0xdd6f, +0x3a, 0x63, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x3a, 0x63, 0x61, 0x6e, 0x64, +0x6c, 0x65, 0xd83d, 0xddd1, 0x3a, 0x77, 0x61, 0x73, 0x74, 0x65, 0x62, 0x61, +0x73, 0x6b, 0x65, 0x74, 0x3a, 0x77, 0x61, 0x73, 0x74, 0x65, 0x62, 0x61, +0x73, 0x6b, 0x65, 0x74, 0xd83d, 0xdee2, 0x3a, 0x6f, 0x69, 0x6c, 0x5f, 0x64, +0x72, 0x75, 0x6d, 0x3a, 0x64, 0x72, 0x75, 0x6d, 0x6f, 0x69, 0x6c, 0xd83d, +0xdee2, 0x3a, 0x6f, 0x69, 0x6c, 0x3a, 0x6f, 0x69, 0x6c, 0xd83d, 0xdcb8, 0x3a, +0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x77, +0x69, 0x6e, 0x67, 0x73, 0x3a, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x77, 0x69, +0x6e, 0x67, 0x73, 0x77, 0x69, 0x74, 0x68, 0xd83d, 0xdcb5, 0x3a, 0x64, 0x6f, +0x6c, 0x6c, 0x61, 0x72, 0x3a, 0x64, 0x6f, 0x6c, 0x6c, 0x61, 0x72, 0xd83d, +0xdcb4, 0x3a, 0x79, 0x65, 0x6e, 0x3a, 0x79, 0x65, 0x6e, 0xd83d, 0xdcb6, 0x3a, +0x65, 0x75, 0x72, 0x6f, 0x3a, 0x65, 0x75, 0x72, 0x6f, 0xd83d, 0xdcb7, 0x3a, +0x70, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x70, 0x6f, 0x75, 0x6e, 0x64, 0xd83d, +0xdcb0, 0x3a, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x62, 0x61, 0x67, 0x3a, 0x6d, +0x6f, 0x6e, 0x65, 0x79, 0x62, 0x61, 0x67, 0xd83d, 0xdcb3, 0x3a, 0x63, 0x72, +0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x3a, 0x63, 0x61, +0x72, 0x64, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0xd83d, 0xdc8e, 0x3a, 0x67, +0x65, 0x6d, 0x3a, 0x67, 0x65, 0x6d, 0x2696, 0xfe0f, 0x3a, 0x73, 0x63, 0x61, +0x6c, 0x65, 0x73, 0x3a, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x73, 0xd83d, 0xdd27, +0x3a, 0x77, 0x72, 0x65, 0x6e, 0x63, 0x68, 0x3a, 0x77, 0x72, 0x65, 0x6e, +0x63, 0x68, 0xd83d, 0xdd28, 0x3a, 0x68, 0x61, 0x6d, 0x6d, 0x65, 0x72, 0x3a, +0x68, 0x61, 0x6d, 0x6d, 0x65, 0x72, 0x2692, 0x3a, 0x68, 0x61, 0x6d, 0x6d, +0x65, 0x72, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x70, 0x69, 0x63, 0x6b, 0x3a, +0x61, 0x6e, 0x64, 0x68, 0x61, 0x6d, 0x6d, 0x65, 0x72, 0x70, 0x69, 0x63, +0x6b, 0x2692, 0x3a, 0x68, 0x61, 0x6d, 0x6d, 0x65, 0x72, 0x5f, 0x70, 0x69, +0x63, 0x6b, 0x3a, 0x68, 0x61, 0x6d, 0x6d, 0x65, 0x72, 0x70, 0x69, 0x63, +0x6b, 0xd83d, 0xdee0, 0x3a, 0x68, 0x61, 0x6d, 0x6d, 0x65, 0x72, 0x5f, 0x61, +0x6e, 0x64, 0x5f, 0x77, 0x72, 0x65, 0x6e, 0x63, 0x68, 0x3a, 0x61, 0x6e, +0x64, 0x68, 0x61, 0x6d, 0x6d, 0x65, 0x72, 0x77, 0x72, 0x65, 0x6e, 0x63, +0x68, 0xd83d, 0xdee0, 0x3a, 0x74, 0x6f, 0x6f, 0x6c, 0x73, 0x3a, 0x74, 0x6f, +0x6f, 0x6c, 0x73, 0x26cf, 0x3a, 0x70, 0x69, 0x63, 0x6b, 0x3a, 0x70, 0x69, +0x63, 0x6b, 0xd83d, 0xdd29, 0x3a, 0x6e, 0x75, 0x74, 0x5f, 0x61, 0x6e, 0x64, +0x5f, 0x62, 0x6f, 0x6c, 0x74, 0x3a, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x6c, +0x74, 0x6e, 0x75, 0x74, 0x2699, 0xfe0f, 0x3a, 0x67, 0x65, 0x61, 0x72, 0x3a, +0x67, 0x65, 0x61, 0x72, 0x26d3, 0x3a, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, +0x3a, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0xd83d, 0xdd2b, 0x3a, 0x67, 0x75, +0x6e, 0x3a, 0x67, 0x75, 0x6e, 0xd83d, 0xdca3, 0x3a, 0x62, 0x6f, 0x6d, 0x62, +0x3a, 0x62, 0x6f, 0x6d, 0x62, 0xd83d, 0xdd2a, 0x3a, 0x6b, 0x6e, 0x69, 0x66, +0x65, 0x3a, 0x6b, 0x6e, 0x69, 0x66, 0x65, 0xd83d, 0xdde1, 0x3a, 0x64, 0x61, +0x67, 0x67, 0x65, 0x72, 0x5f, 0x6b, 0x6e, 0x69, 0x66, 0x65, 0x3a, 0x64, +0x61, 0x67, 0x67, 0x65, 0x72, 0x6b, 0x6e, 0x69, 0x66, 0x65, 0xd83d, 0xdde1, +0x3a, 0x64, 0x61, 0x67, 0x67, 0x65, 0x72, 0x3a, 0x64, 0x61, 0x67, 0x67, +0x65, 0x72, 0x2694, 0xfe0f, 0x3a, 0x63, 0x72, 0x6f, 0x73, 0x73, 0x65, 0x64, +0x5f, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x3a, 0x63, 0x72, 0x6f, 0x73, +0x73, 0x65, 0x64, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0xd83d, 0xdee1, 0x3a, +0x73, 0x68, 0x69, 0x65, 0x6c, 0x64, 0x3a, 0x73, 0x68, 0x69, 0x65, 0x6c, +0x64, 0xd83d, 0xdeac, 0x3a, 0x73, 0x6d, 0x6f, 0x6b, 0x69, 0x6e, 0x67, 0x3a, +0x73, 0x6d, 0x6f, 0x6b, 0x69, 0x6e, 0x67, 0x26b0, 0xfe0f, 0x3a, 0x63, 0x6f, +0x66, 0x66, 0x69, 0x6e, 0x3a, 0x63, 0x6f, 0x66, 0x66, 0x69, 0x6e, 0x26b1, +0xfe0f, 0x3a, 0x66, 0x75, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x75, 0x72, +0x6e, 0x3a, 0x66, 0x75, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x75, 0x72, 0x6e, +0x26b1, 0xfe0f, 0x3a, 0x75, 0x72, 0x6e, 0x3a, 0x75, 0x72, 0x6e, 0xd83c, 0xdffa, +0x3a, 0x61, 0x6d, 0x70, 0x68, 0x6f, 0x72, 0x61, 0x3a, 0x61, 0x6d, 0x70, +0x68, 0x6f, 0x72, 0x61, 0xd83d, 0xdd2e, 0x3a, 0x63, 0x72, 0x79, 0x73, 0x74, +0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c, 0x6c, 0x3a, 0x62, 0x61, 0x6c, 0x6c, +0x63, 0x72, 0x79, 0x73, 0x74, 0x61, 0x6c, 0xd83d, 0xdcff, 0x3a, 0x70, 0x72, +0x61, 0x79, 0x65, 0x72, 0x5f, 0x62, 0x65, 0x61, 0x64, 0x73, 0x3a, 0x62, +0x65, 0x61, 0x64, 0x73, 0x70, 0x72, 0x61, 0x79, 0x65, 0x72, 0xd83d, 0xdc88, +0x3a, 0x62, 0x61, 0x72, 0x62, 0x65, 0x72, 0x3a, 0x62, 0x61, 0x72, 0x62, +0x65, 0x72, 0x2697, 0xfe0f, 0x3a, 0x61, 0x6c, 0x65, 0x6d, 0x62, 0x69, 0x63, +0x3a, 0x61, 0x6c, 0x65, 0x6d, 0x62, 0x69, 0x63, 0xd83d, 0xdd2d, 0x3a, 0x74, +0x65, 0x6c, 0x65, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x3a, 0x74, 0x65, 0x6c, +0x65, 0x73, 0x63, 0x6f, 0x70, 0x65, 0xd83d, 0xdd2c, 0x3a, 0x6d, 0x69, 0x63, +0x72, 0x6f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x3a, 0x6d, 0x69, 0x63, 0x72, +0x6f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0xd83d, 0xdd73, 0x3a, 0x68, 0x6f, 0x6c, +0x65, 0x3a, 0x68, 0x6f, 0x6c, 0x65, 0xd83d, 0xdc8a, 0x3a, 0x70, 0x69, 0x6c, +0x6c, 0x3a, 0x70, 0x69, 0x6c, 0x6c, 0xd83d, 0xdc89, 0x3a, 0x73, 0x79, 0x72, +0x69, 0x6e, 0x67, 0x65, 0x3a, 0x73, 0x79, 0x72, 0x69, 0x6e, 0x67, 0x65, +0xd83c, 0xdf21, 0x3a, 0x74, 0x68, 0x65, 0x72, 0x6d, 0x6f, 0x6d, 0x65, 0x74, +0x65, 0x72, 0x3a, 0x74, 0x68, 0x65, 0x72, 0x6d, 0x6f, 0x6d, 0x65, 0x74, +0x65, 0x72, 0xd83d, 0xdebd, 0x3a, 0x74, 0x6f, 0x69, 0x6c, 0x65, 0x74, 0x3a, +0x74, 0x6f, 0x69, 0x6c, 0x65, 0x74, 0xd83d, 0xdeb0, 0x3a, 0x70, 0x6f, 0x74, +0x61, 0x62, 0x6c, 0x65, 0x5f, 0x77, 0x61, 0x74, 0x65, 0x72, 0x3a, 0x70, +0x6f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x77, 0x61, 0x74, 0x65, 0x72, 0xd83d, +0xdebf, 0x3a, 0x73, 0x68, 0x6f, 0x77, 0x65, 0x72, 0x3a, 0x73, 0x68, 0x6f, +0x77, 0x65, 0x72, 0xd83d, 0xdec1, 0x3a, 0x62, 0x61, 0x74, 0x68, 0x74, 0x75, +0x62, 0x3a, 0x62, 0x61, 0x74, 0x68, 0x74, 0x75, 0x62, 0xd83d, 0xdec0, 0x3a, +0x62, 0x61, 0x74, 0x68, 0x3a, 0x62, 0x61, 0x74, 0x68, 0xd83d, 0xdece, 0x3a, +0x62, 0x65, 0x6c, 0x6c, 0x68, 0x6f, 0x70, 0x5f, 0x62, 0x65, 0x6c, 0x6c, +0x3a, 0x62, 0x65, 0x6c, 0x6c, 0x62, 0x65, 0x6c, 0x6c, 0x68, 0x6f, 0x70, +0xd83d, 0xdece, 0x3a, 0x62, 0x65, 0x6c, 0x6c, 0x68, 0x6f, 0x70, 0x3a, 0x62, +0x65, 0x6c, 0x6c, 0x68, 0x6f, 0x70, 0xd83d, 0xdd11, 0x3a, 0x6b, 0x65, 0x79, +0x3a, 0x6b, 0x65, 0x79, 0xd83d, 0xdddd, 0x3a, 0x6f, 0x6c, 0x64, 0x5f, 0x6b, +0x65, 0x79, 0x3a, 0x6b, 0x65, 0x79, 0x6f, 0x6c, 0x64, 0xd83d, 0xdddd, 0x3a, +0x6b, 0x65, 0x79, 0x32, 0x3a, 0x6b, 0x65, 0x79, 0x32, 0xd83d, 0xdeaa, 0x3a, +0x64, 0x6f, 0x6f, 0x72, 0x3a, 0x64, 0x6f, 0x6f, 0x72, 0xd83d, 0xdecb, 0x3a, +0x63, 0x6f, 0x75, 0x63, 0x68, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x6c, 0x61, +0x6d, 0x70, 0x3a, 0x61, 0x6e, 0x64, 0x63, 0x6f, 0x75, 0x63, 0x68, 0x6c, +0x61, 0x6d, 0x70, 0xd83d, 0xdecb, 0x3a, 0x63, 0x6f, 0x75, 0x63, 0x68, 0x3a, +0x63, 0x6f, 0x75, 0x63, 0x68, 0xd83d, 0xdecf, 0x3a, 0x62, 0x65, 0x64, 0x3a, +0x62, 0x65, 0x64, 0xd83d, 0xdecc, 0x3a, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x69, +0x6e, 0x67, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x64, 0x61, +0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x61, 0x63, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, +0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x69, +0x6e, 0x67, 0xd83d, 0xddbc, 0x3a, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x77, +0x69, 0x74, 0x68, 0x5f, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x3a, +0x66, 0x72, 0x61, 0x6d, 0x65, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, +0x77, 0x69, 0x74, 0x68, 0xd83d, 0xddbc, 0x3a, 0x66, 0x72, 0x61, 0x6d, 0x65, +0x5f, 0x70, 0x68, 0x6f, 0x74, 0x6f, 0x3a, 0x66, 0x72, 0x61, 0x6d, 0x65, +0x70, 0x68, 0x6f, 0x74, 0x6f, 0xd83d, 0xdecd, 0x3a, 0x73, 0x68, 0x6f, 0x70, +0x70, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x61, 0x67, 0x73, 0x3a, 0x62, 0x61, +0x67, 0x73, 0x73, 0x68, 0x6f, 0x70, 0x70, 0x69, 0x6e, 0x67, 0xd83d, 0xded2, +0x3a, 0x73, 0x68, 0x6f, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x72, +0x6f, 0x6c, 0x6c, 0x65, 0x79, 0x3a, 0x73, 0x68, 0x6f, 0x70, 0x70, 0x69, +0x6e, 0x67, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x79, 0xd83d, 0xded2, 0x3a, +0x73, 0x68, 0x6f, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x61, 0x72, +0x74, 0x3a, 0x63, 0x61, 0x72, 0x74, 0x73, 0x68, 0x6f, 0x70, 0x70, 0x69, +0x6e, 0x67, 0xd83c, 0xdf81, 0x3a, 0x67, 0x69, 0x66, 0x74, 0x3a, 0x67, 0x69, +0x66, 0x74, 0xd83c, 0xdf88, 0x3a, 0x62, 0x61, 0x6c, 0x6c, 0x6f, 0x6f, 0x6e, +0x3a, 0x62, 0x61, 0x6c, 0x6c, 0x6f, 0x6f, 0x6e, 0xd83c, 0xdf8f, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x73, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x73, 0xd83c, 0xdf80, +0x3a, 0x72, 0x69, 0x62, 0x62, 0x6f, 0x6e, 0x3a, 0x72, 0x69, 0x62, 0x62, +0x6f, 0x6e, 0xd83c, 0xdf8a, 0x3a, 0x63, 0x6f, 0x6e, 0x66, 0x65, 0x74, 0x74, +0x69, 0x5f, 0x62, 0x61, 0x6c, 0x6c, 0x3a, 0x62, 0x61, 0x6c, 0x6c, 0x63, +0x6f, 0x6e, 0x66, 0x65, 0x74, 0x74, 0x69, 0xd83c, 0xdf89, 0x3a, 0x74, 0x61, +0x64, 0x61, 0x3a, 0x74, 0x61, 0x64, 0x61, 0xd83c, 0xdf8e, 0x3a, 0x64, 0x6f, +0x6c, 0x6c, 0x73, 0x3a, 0x64, 0x6f, 0x6c, 0x6c, 0x73, 0xd83c, 0xdfee, 0x3a, +0x69, 0x7a, 0x61, 0x6b, 0x61, 0x79, 0x61, 0x5f, 0x6c, 0x61, 0x6e, 0x74, +0x65, 0x72, 0x6e, 0x3a, 0x69, 0x7a, 0x61, 0x6b, 0x61, 0x79, 0x61, 0x6c, +0x61, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0xd83c, 0xdf90, 0x3a, 0x77, 0x69, 0x6e, +0x64, 0x5f, 0x63, 0x68, 0x69, 0x6d, 0x65, 0x3a, 0x63, 0x68, 0x69, 0x6d, +0x65, 0x77, 0x69, 0x6e, 0x64, 0x2709, 0xfe0f, 0x3a, 0x65, 0x6e, 0x76, 0x65, +0x6c, 0x6f, 0x70, 0x65, 0x3a, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, +0x65, 0xd83d, 0xdce9, 0x3a, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, +0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x61, 0x72, 0x72, 0x6f, 0x77, 0x3a, +0x61, 0x72, 0x72, 0x6f, 0x77, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, +0x65, 0x77, 0x69, 0x74, 0x68, 0xd83d, 0xdce8, 0x3a, 0x69, 0x6e, 0x63, 0x6f, +0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, +0x65, 0x3a, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x69, 0x6e, +0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0xd83d, 0xdce7, 0x3a, 0x65, 0x6d, 0x61, +0x69, 0x6c, 0x3a, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0xd83d, 0xdce7, 0x3a, 0x65, +0x2d, 0x6d, 0x61, 0x69, 0x6c, 0x3a, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0xd83d, +0xdc8c, 0x3a, 0x6c, 0x6f, 0x76, 0x65, 0x5f, 0x6c, 0x65, 0x74, 0x74, 0x65, +0x72, 0x3a, 0x6c, 0x65, 0x74, 0x74, 0x65, 0x72, 0x6c, 0x6f, 0x76, 0x65, +0xd83d, 0xdce5, 0x3a, 0x69, 0x6e, 0x62, 0x6f, 0x78, 0x5f, 0x74, 0x72, 0x61, +0x79, 0x3a, 0x69, 0x6e, 0x62, 0x6f, 0x78, 0x74, 0x72, 0x61, 0x79, 0xd83d, +0xdce4, 0x3a, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x78, 0x5f, 0x74, 0x72, 0x61, +0x79, 0x3a, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x78, 0x74, 0x72, 0x61, 0x79, +0xd83d, 0xdce6, 0x3a, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x3a, 0x70, +0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0xd83c, 0xdff7, 0x3a, 0x6c, 0x61, 0x62, +0x65, 0x6c, 0x3a, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0xd83d, 0xdcea, 0x3a, 0x6d, +0x61, 0x69, 0x6c, 0x62, 0x6f, 0x78, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x65, +0x64, 0x3a, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x6d, 0x61, 0x69, 0x6c, +0x62, 0x6f, 0x78, 0xd83d, 0xdceb, 0x3a, 0x6d, 0x61, 0x69, 0x6c, 0x62, 0x6f, +0x78, 0x3a, 0x6d, 0x61, 0x69, 0x6c, 0x62, 0x6f, 0x78, 0xd83d, 0xdcec, 0x3a, +0x6d, 0x61, 0x69, 0x6c, 0x62, 0x6f, 0x78, 0x5f, 0x77, 0x69, 0x74, 0x68, +0x5f, 0x6d, 0x61, 0x69, 0x6c, 0x3a, 0x6d, 0x61, 0x69, 0x6c, 0x6d, 0x61, +0x69, 0x6c, 0x62, 0x6f, 0x78, 0x77, 0x69, 0x74, 0x68, 0xd83d, 0xdced, 0x3a, +0x6d, 0x61, 0x69, 0x6c, 0x62, 0x6f, 0x78, 0x5f, 0x77, 0x69, 0x74, 0x68, +0x5f, 0x6e, 0x6f, 0x5f, 0x6d, 0x61, 0x69, 0x6c, 0x3a, 0x6d, 0x61, 0x69, +0x6c, 0x6d, 0x61, 0x69, 0x6c, 0x62, 0x6f, 0x78, 0x6e, 0x6f, 0x77, 0x69, +0x74, 0x68, 0xd83d, 0xdcee, 0x3a, 0x70, 0x6f, 0x73, 0x74, 0x62, 0x6f, 0x78, +0x3a, 0x70, 0x6f, 0x73, 0x74, 0x62, 0x6f, 0x78, 0xd83d, 0xdcef, 0x3a, 0x70, +0x6f, 0x73, 0x74, 0x61, 0x6c, 0x5f, 0x68, 0x6f, 0x72, 0x6e, 0x3a, 0x68, +0x6f, 0x72, 0x6e, 0x70, 0x6f, 0x73, 0x74, 0x61, 0x6c, 0xd83d, 0xdcdc, 0x3a, +0x73, 0x63, 0x72, 0x6f, 0x6c, 0x6c, 0x3a, 0x73, 0x63, 0x72, 0x6f, 0x6c, +0x6c, 0xd83d, 0xdcc3, 0x3a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x77, 0x69, 0x74, +0x68, 0x5f, 0x63, 0x75, 0x72, 0x6c, 0x3a, 0x63, 0x75, 0x72, 0x6c, 0x70, +0x61, 0x67, 0x65, 0x77, 0x69, 0x74, 0x68, 0xd83d, 0xdcc4, 0x3a, 0x70, 0x61, +0x67, 0x65, 0x5f, 0x66, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x75, 0x70, +0x3a, 0x66, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x70, 0x61, 0x67, 0x65, 0x75, +0x70, 0xd83d, 0xdcd1, 0x3a, 0x62, 0x6f, 0x6f, 0x6b, 0x6d, 0x61, 0x72, 0x6b, +0x5f, 0x74, 0x61, 0x62, 0x73, 0x3a, 0x62, 0x6f, 0x6f, 0x6b, 0x6d, 0x61, +0x72, 0x6b, 0x74, 0x61, 0x62, 0x73, 0xd83d, 0xdcca, 0x3a, 0x62, 0x61, 0x72, +0x5f, 0x63, 0x68, 0x61, 0x72, 0x74, 0x3a, 0x62, 0x61, 0x72, 0x63, 0x68, +0x61, 0x72, 0x74, 0xd83d, 0xdcc8, 0x3a, 0x63, 0x68, 0x61, 0x72, 0x74, 0x5f, +0x77, 0x69, 0x74, 0x68, 0x5f, 0x75, 0x70, 0x77, 0x61, 0x72, 0x64, 0x73, +0x5f, 0x74, 0x72, 0x65, 0x6e, 0x64, 0x3a, 0x63, 0x68, 0x61, 0x72, 0x74, +0x74, 0x72, 0x65, 0x6e, 0x64, 0x75, 0x70, 0x77, 0x61, 0x72, 0x64, 0x73, +0x77, 0x69, 0x74, 0x68, 0xd83d, 0xdcc9, 0x3a, 0x63, 0x68, 0x61, 0x72, 0x74, +0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x64, 0x6f, 0x77, 0x6e, 0x77, 0x61, +0x72, 0x64, 0x73, 0x5f, 0x74, 0x72, 0x65, 0x6e, 0x64, 0x3a, 0x63, 0x68, +0x61, 0x72, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x77, 0x61, 0x72, 0x64, 0x73, +0x74, 0x72, 0x65, 0x6e, 0x64, 0x77, 0x69, 0x74, 0x68, 0xd83d, 0xddd2, 0x3a, +0x73, 0x70, 0x69, 0x72, 0x61, 0x6c, 0x5f, 0x6e, 0x6f, 0x74, 0x65, 0x5f, +0x70, 0x61, 0x64, 0x3a, 0x6e, 0x6f, 0x74, 0x65, 0x70, 0x61, 0x64, 0x73, +0x70, 0x69, 0x72, 0x61, 0x6c, 0xd83d, 0xddd2, 0x3a, 0x6e, 0x6f, 0x74, 0x65, +0x70, 0x61, 0x64, 0x5f, 0x73, 0x70, 0x69, 0x72, 0x61, 0x6c, 0x3a, 0x6e, +0x6f, 0x74, 0x65, 0x70, 0x61, 0x64, 0x73, 0x70, 0x69, 0x72, 0x61, 0x6c, +0xd83d, 0xddd3, 0x3a, 0x73, 0x70, 0x69, 0x72, 0x61, 0x6c, 0x5f, 0x63, 0x61, +0x6c, 0x65, 0x6e, 0x64, 0x61, 0x72, 0x5f, 0x70, 0x61, 0x64, 0x3a, 0x63, +0x61, 0x6c, 0x65, 0x6e, 0x64, 0x61, 0x72, 0x70, 0x61, 0x64, 0x73, 0x70, +0x69, 0x72, 0x61, 0x6c, 0xd83d, 0xddd3, 0x3a, 0x63, 0x61, 0x6c, 0x65, 0x6e, +0x64, 0x61, 0x72, 0x5f, 0x73, 0x70, 0x69, 0x72, 0x61, 0x6c, 0x3a, 0x63, +0x61, 0x6c, 0x65, 0x6e, 0x64, 0x61, 0x72, 0x73, 0x70, 0x69, 0x72, 0x61, +0x6c, 0xd83d, 0xdcc6, 0x3a, 0x63, 0x61, 0x6c, 0x65, 0x6e, 0x64, 0x61, 0x72, +0x3a, 0x63, 0x61, 0x6c, 0x65, 0x6e, 0x64, 0x61, 0x72, 0xd83d, 0xdcc5, 0x3a, +0x64, 0x61, 0x74, 0x65, 0x3a, 0x64, 0x61, 0x74, 0x65, 0xd83d, 0xdcc7, 0x3a, +0x63, 0x61, 0x72, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x3a, 0x63, +0x61, 0x72, 0x64, 0x69, 0x6e, 0x64, 0x65, 0x78, 0xd83d, 0xddc3, 0x3a, 0x63, +0x61, 0x72, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x62, 0x6f, 0x78, +0x3a, 0x62, 0x6f, 0x78, 0x63, 0x61, 0x72, 0x64, 0x66, 0x69, 0x6c, 0x65, +0xd83d, 0xddc3, 0x3a, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x62, 0x6f, 0x78, 0x3a, +0x62, 0x6f, 0x78, 0x63, 0x61, 0x72, 0x64, 0xd83d, 0xddf3, 0x3a, 0x62, 0x61, +0x6c, 0x6c, 0x6f, 0x74, 0x5f, 0x62, 0x6f, 0x78, 0x5f, 0x77, 0x69, 0x74, +0x68, 0x5f, 0x62, 0x61, 0x6c, 0x6c, 0x6f, 0x74, 0x3a, 0x62, 0x61, 0x6c, +0x6c, 0x6f, 0x74, 0x62, 0x61, 0x6c, 0x6c, 0x6f, 0x74, 0x62, 0x6f, 0x78, +0x77, 0x69, 0x74, 0x68, 0xd83d, 0xddf3, 0x3a, 0x62, 0x61, 0x6c, 0x6c, 0x6f, +0x74, 0x5f, 0x62, 0x6f, 0x78, 0x3a, 0x62, 0x61, 0x6c, 0x6c, 0x6f, 0x74, +0x62, 0x6f, 0x78, 0xd83d, 0xddc4, 0x3a, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x63, +0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x3a, 0x63, 0x61, 0x62, 0x69, 0x6e, +0x65, 0x74, 0x66, 0x69, 0x6c, 0x65, 0xd83d, 0xdccb, 0x3a, 0x63, 0x6c, 0x69, +0x70, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x3a, 0x63, 0x6c, 0x69, 0x70, 0x62, +0x6f, 0x61, 0x72, 0x64, 0xd83d, 0xdcc1, 0x3a, 0x66, 0x69, 0x6c, 0x65, 0x5f, +0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x3a, 0x66, 0x69, 0x6c, 0x65, 0x66, +0x6f, 0x6c, 0x64, 0x65, 0x72, 0xd83d, 0xdcc2, 0x3a, 0x6f, 0x70, 0x65, 0x6e, +0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, +0x3a, 0x66, 0x69, 0x6c, 0x65, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x6f, +0x70, 0x65, 0x6e, 0xd83d, 0xddc2, 0x3a, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x69, +0x6e, 0x64, 0x65, 0x78, 0x5f, 0x64, 0x69, 0x76, 0x69, 0x64, 0x65, 0x72, +0x73, 0x3a, 0x63, 0x61, 0x72, 0x64, 0x64, 0x69, 0x76, 0x69, 0x64, 0x65, +0x72, 0x73, 0x69, 0x6e, 0x64, 0x65, 0x78, 0xd83d, 0xddc2, 0x3a, 0x64, 0x69, +0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x3a, 0x64, 0x69, 0x76, 0x69, 0x64, +0x65, 0x72, 0x73, 0xd83d, 0xddde, 0x3a, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x64, +0x5f, 0x75, 0x70, 0x5f, 0x6e, 0x65, 0x77, 0x73, 0x70, 0x61, 0x70, 0x65, +0x72, 0x3a, 0x6e, 0x65, 0x77, 0x73, 0x70, 0x61, 0x70, 0x65, 0x72, 0x72, +0x6f, 0x6c, 0x6c, 0x65, 0x64, 0x75, 0x70, 0xd83d, 0xddde, 0x3a, 0x6e, 0x65, +0x77, 0x73, 0x70, 0x61, 0x70, 0x65, 0x72, 0x32, 0x3a, 0x6e, 0x65, 0x77, +0x73, 0x70, 0x61, 0x70, 0x65, 0x72, 0x32, 0xd83d, 0xdcf0, 0x3a, 0x6e, 0x65, +0x77, 0x73, 0x70, 0x61, 0x70, 0x65, 0x72, 0x3a, 0x6e, 0x65, 0x77, 0x73, +0x70, 0x61, 0x70, 0x65, 0x72, 0xd83d, 0xdcd3, 0x3a, 0x6e, 0x6f, 0x74, 0x65, +0x62, 0x6f, 0x6f, 0x6b, 0x3a, 0x6e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, +0x6b, 0xd83d, 0xdcd4, 0x3a, 0x6e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, +0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, +0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x3a, 0x63, +0x6f, 0x76, 0x65, 0x72, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, +0x76, 0x65, 0x6e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x77, 0x69, +0x74, 0x68, 0xd83d, 0xdcd2, 0x3a, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, 0x3a, +0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, 0xd83d, 0xdcd5, 0x3a, 0x63, 0x6c, 0x6f, +0x73, 0x65, 0x64, 0x5f, 0x62, 0x6f, 0x6f, 0x6b, 0x3a, 0x62, 0x6f, 0x6f, +0x6b, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0xd83d, 0xdcd7, 0x3a, 0x67, 0x72, +0x65, 0x65, 0x6e, 0x5f, 0x62, 0x6f, 0x6f, 0x6b, 0x3a, 0x62, 0x6f, 0x6f, +0x6b, 0x67, 0x72, 0x65, 0x65, 0x6e, 0xd83d, 0xdcd8, 0x3a, 0x62, 0x6c, 0x75, +0x65, 0x5f, 0x62, 0x6f, 0x6f, 0x6b, 0x3a, 0x62, 0x6c, 0x75, 0x65, 0x62, +0x6f, 0x6f, 0x6b, 0xd83d, 0xdcd9, 0x3a, 0x6f, 0x72, 0x61, 0x6e, 0x67, 0x65, +0x5f, 0x62, 0x6f, 0x6f, 0x6b, 0x3a, 0x62, 0x6f, 0x6f, 0x6b, 0x6f, 0x72, +0x61, 0x6e, 0x67, 0x65, 0xd83d, 0xdcda, 0x3a, 0x62, 0x6f, 0x6f, 0x6b, 0x73, +0x3a, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0xd83d, 0xdcd6, 0x3a, 0x62, 0x6f, 0x6f, +0x6b, 0x3a, 0x62, 0x6f, 0x6f, 0x6b, 0xd83d, 0xdd16, 0x3a, 0x62, 0x6f, 0x6f, +0x6b, 0x6d, 0x61, 0x72, 0x6b, 0x3a, 0x62, 0x6f, 0x6f, 0x6b, 0x6d, 0x61, +0x72, 0x6b, 0xd83d, 0xdd17, 0x3a, 0x6c, 0x69, 0x6e, 0x6b, 0x3a, 0x6c, 0x69, +0x6e, 0x6b, 0xd83d, 0xdcce, 0x3a, 0x70, 0x61, 0x70, 0x65, 0x72, 0x63, 0x6c, +0x69, 0x70, 0x3a, 0x70, 0x61, 0x70, 0x65, 0x72, 0x63, 0x6c, 0x69, 0x70, +0xd83d, 0xdd87, 0x3a, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x5f, 0x70, 0x61, +0x70, 0x65, 0x72, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x3a, 0x6c, 0x69, 0x6e, +0x6b, 0x65, 0x64, 0x70, 0x61, 0x70, 0x65, 0x72, 0x63, 0x6c, 0x69, 0x70, +0x73, 0xd83d, 0xdd87, 0x3a, 0x70, 0x61, 0x70, 0x65, 0x72, 0x63, 0x6c, 0x69, +0x70, 0x73, 0x3a, 0x70, 0x61, 0x70, 0x65, 0x72, 0x63, 0x6c, 0x69, 0x70, +0x73, 0xd83d, 0xdcd0, 0x3a, 0x74, 0x72, 0x69, 0x61, 0x6e, 0x67, 0x75, 0x6c, +0x61, 0x72, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x72, 0x3a, 0x72, 0x75, 0x6c, +0x65, 0x72, 0x74, 0x72, 0x69, 0x61, 0x6e, 0x67, 0x75, 0x6c, 0x61, 0x72, +0xd83d, 0xdccf, 0x3a, 0x73, 0x74, 0x72, 0x61, 0x69, 0x67, 0x68, 0x74, 0x5f, +0x72, 0x75, 0x6c, 0x65, 0x72, 0x3a, 0x72, 0x75, 0x6c, 0x65, 0x72, 0x73, +0x74, 0x72, 0x61, 0x69, 0x67, 0x68, 0x74, 0xd83d, 0xdccc, 0x3a, 0x70, 0x75, +0x73, 0x68, 0x70, 0x69, 0x6e, 0x3a, 0x70, 0x75, 0x73, 0x68, 0x70, 0x69, +0x6e, 0xd83d, 0xdccd, 0x3a, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x70, 0x75, +0x73, 0x68, 0x70, 0x69, 0x6e, 0x3a, 0x70, 0x75, 0x73, 0x68, 0x70, 0x69, +0x6e, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2702, 0xfe0f, 0x3a, 0x73, 0x63, 0x69, +0x73, 0x73, 0x6f, 0x72, 0x73, 0x3a, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, +0x72, 0x73, 0xd83d, 0xdd8a, 0x3a, 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x6c, +0x65, 0x66, 0x74, 0x5f, 0x62, 0x61, 0x6c, 0x6c, 0x70, 0x6f, 0x69, 0x6e, +0x74, 0x5f, 0x70, 0x65, 0x6e, 0x3a, 0x62, 0x61, 0x6c, 0x6c, 0x70, 0x6f, +0x69, 0x6e, 0x74, 0x6c, 0x65, 0x66, 0x74, 0x6c, 0x6f, 0x77, 0x65, 0x72, +0x70, 0x65, 0x6e, 0xd83d, 0xdd8a, 0x3a, 0x70, 0x65, 0x6e, 0x5f, 0x62, 0x61, +0x6c, 0x6c, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x3a, 0x62, 0x61, 0x6c, 0x6c, +0x70, 0x6f, 0x69, 0x6e, 0x74, 0x70, 0x65, 0x6e, 0xd83d, 0xdd8b, 0x3a, 0x6c, +0x6f, 0x77, 0x65, 0x72, 0x5f, 0x6c, 0x65, 0x66, 0x74, 0x5f, 0x66, 0x6f, +0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x5f, 0x70, 0x65, 0x6e, 0x3a, 0x66, +0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x6c, 0x65, 0x66, 0x74, 0x6c, +0x6f, 0x77, 0x65, 0x72, 0x70, 0x65, 0x6e, 0xd83d, 0xdd8b, 0x3a, 0x70, 0x65, +0x6e, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x3a, 0x66, +0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x70, 0x65, 0x6e, 0x2712, 0xfe0f, +0x3a, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x5f, 0x6e, 0x69, 0x62, 0x3a, 0x62, +0x6c, 0x61, 0x63, 0x6b, 0x6e, 0x69, 0x62, 0xd83d, 0xdd8c, 0x3a, 0x6c, 0x6f, +0x77, 0x65, 0x72, 0x5f, 0x6c, 0x65, 0x66, 0x74, 0x5f, 0x70, 0x61, 0x69, +0x6e, 0x74, 0x62, 0x72, 0x75, 0x73, 0x68, 0x3a, 0x6c, 0x65, 0x66, 0x74, +0x6c, 0x6f, 0x77, 0x65, 0x72, 0x70, 0x61, 0x69, 0x6e, 0x74, 0x62, 0x72, +0x75, 0x73, 0x68, 0xd83d, 0xdd8c, 0x3a, 0x70, 0x61, 0x69, 0x6e, 0x74, 0x62, +0x72, 0x75, 0x73, 0x68, 0x3a, 0x70, 0x61, 0x69, 0x6e, 0x74, 0x62, 0x72, +0x75, 0x73, 0x68, 0xd83d, 0xdd8d, 0x3a, 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x5f, +0x6c, 0x65, 0x66, 0x74, 0x5f, 0x63, 0x72, 0x61, 0x79, 0x6f, 0x6e, 0x3a, +0x63, 0x72, 0x61, 0x79, 0x6f, 0x6e, 0x6c, 0x65, 0x66, 0x74, 0x6c, 0x6f, +0x77, 0x65, 0x72, 0xd83d, 0xdd8d, 0x3a, 0x63, 0x72, 0x61, 0x79, 0x6f, 0x6e, +0x3a, 0x63, 0x72, 0x61, 0x79, 0x6f, 0x6e, 0xd83d, 0xdcdd, 0x3a, 0x6d, 0x65, +0x6d, 0x6f, 0x3a, 0x6d, 0x65, 0x6d, 0x6f, 0xd83d, 0xdcdd, 0x3a, 0x70, 0x65, +0x6e, 0x63, 0x69, 0x6c, 0x3a, 0x70, 0x65, 0x6e, 0x63, 0x69, 0x6c, 0x270f, +0xfe0f, 0x3a, 0x70, 0x65, 0x6e, 0x63, 0x69, 0x6c, 0x32, 0x3a, 0x70, 0x65, +0x6e, 0x63, 0x69, 0x6c, 0x32, 0xd83d, 0xdd0d, 0x3a, 0x6d, 0x61, 0x67, 0x3a, +0x6d, 0x61, 0x67, 0xd83d, 0xdd0e, 0x3a, 0x6d, 0x61, 0x67, 0x5f, 0x72, 0x69, +0x67, 0x68, 0x74, 0x3a, 0x6d, 0x61, 0x67, 0x72, 0x69, 0x67, 0x68, 0x74, +0xd83d, 0xdd0f, 0x3a, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x77, 0x69, 0x74, 0x68, +0x5f, 0x69, 0x6e, 0x6b, 0x5f, 0x70, 0x65, 0x6e, 0x3a, 0x69, 0x6e, 0x6b, +0x6c, 0x6f, 0x63, 0x6b, 0x70, 0x65, 0x6e, 0x77, 0x69, 0x74, 0x68, 0xd83d, +0xdd10, 0x3a, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x6c, 0x6f, 0x63, +0x6b, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x6b, 0x65, 0x79, 0x3a, 0x63, +0x6c, 0x6f, 0x73, 0x65, 0x64, 0x6b, 0x65, 0x79, 0x6c, 0x6f, 0x63, 0x6b, +0x77, 0x69, 0x74, 0x68, 0xd83d, 0xdd12, 0x3a, 0x6c, 0x6f, 0x63, 0x6b, 0x3a, +0x6c, 0x6f, 0x63, 0x6b, 0xd83d, 0xdd13, 0x3a, 0x75, 0x6e, 0x6c, 0x6f, 0x63, +0x6b, 0x3a, 0x75, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x2764, 0xfe0f, 0x3a, 0x68, +0x65, 0x61, 0x72, 0x74, 0x3a, 0x68, 0x65, 0x61, 0x72, 0x74, 0xd83d, 0xdc9b, +0x3a, 0x79, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x68, 0x65, 0x61, 0x72, +0x74, 0x3a, 0x68, 0x65, 0x61, 0x72, 0x74, 0x79, 0x65, 0x6c, 0x6c, 0x6f, +0x77, 0xd83d, 0xdc9a, 0x3a, 0x67, 0x72, 0x65, 0x65, 0x6e, 0x5f, 0x68, 0x65, +0x61, 0x72, 0x74, 0x3a, 0x67, 0x72, 0x65, 0x65, 0x6e, 0x68, 0x65, 0x61, +0x72, 0x74, 0xd83d, 0xdc99, 0x3a, 0x62, 0x6c, 0x75, 0x65, 0x5f, 0x68, 0x65, +0x61, 0x72, 0x74, 0x3a, 0x62, 0x6c, 0x75, 0x65, 0x68, 0x65, 0x61, 0x72, +0x74, 0xd83d, 0xdc9c, 0x3a, 0x70, 0x75, 0x72, 0x70, 0x6c, 0x65, 0x5f, 0x68, +0x65, 0x61, 0x72, 0x74, 0x3a, 0x68, 0x65, 0x61, 0x72, 0x74, 0x70, 0x75, +0x72, 0x70, 0x6c, 0x65, 0xd83d, 0xdda4, 0x3a, 0x62, 0x6c, 0x61, 0x63, 0x6b, +0x5f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x3a, 0x62, 0x6c, 0x61, 0x63, 0x6b, +0x68, 0x65, 0x61, 0x72, 0x74, 0xd83d, 0xdc94, 0x3a, 0x62, 0x72, 0x6f, 0x6b, +0x65, 0x6e, 0x5f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x3a, 0x62, 0x72, 0x6f, +0x6b, 0x65, 0x6e, 0x68, 0x65, 0x61, 0x72, 0x74, 0x2763, 0xfe0f, 0x3a, 0x68, +0x65, 0x61, 0x76, 0x79, 0x5f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x5f, 0x65, +0x78, 0x63, 0x6c, 0x61, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, +0x61, 0x72, 0x6b, 0x5f, 0x6f, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x6e, 0x74, +0x3a, 0x65, 0x78, 0x63, 0x6c, 0x61, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, +0x68, 0x65, 0x61, 0x72, 0x74, 0x68, 0x65, 0x61, 0x76, 0x79, 0x6d, 0x61, +0x72, 0x6b, 0x6f, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x6e, 0x74, 0x2763, 0xfe0f, +0x3a, 0x68, 0x65, 0x61, 0x72, 0x74, 0x5f, 0x65, 0x78, 0x63, 0x6c, 0x61, +0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x65, 0x78, 0x63, 0x6c, 0x61, +0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x68, 0x65, 0x61, 0x72, 0x74, 0xd83d, +0xdc95, 0x3a, 0x74, 0x77, 0x6f, 0x5f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x73, +0x3a, 0x68, 0x65, 0x61, 0x72, 0x74, 0x73, 0x74, 0x77, 0x6f, 0xd83d, 0xdc9e, +0x3a, 0x72, 0x65, 0x76, 0x6f, 0x6c, 0x76, 0x69, 0x6e, 0x67, 0x5f, 0x68, +0x65, 0x61, 0x72, 0x74, 0x73, 0x3a, 0x68, 0x65, 0x61, 0x72, 0x74, 0x73, +0x72, 0x65, 0x76, 0x6f, 0x6c, 0x76, 0x69, 0x6e, 0x67, 0xd83d, 0xdc93, 0x3a, +0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x3a, 0x68, 0x65, +0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0xd83d, 0xdc97, 0x3a, 0x68, 0x65, +0x61, 0x72, 0x74, 0x70, 0x75, 0x6c, 0x73, 0x65, 0x3a, 0x68, 0x65, 0x61, +0x72, 0x74, 0x70, 0x75, 0x6c, 0x73, 0x65, 0xd83d, 0xdc96, 0x3a, 0x73, 0x70, +0x61, 0x72, 0x6b, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x65, 0x61, 0x72, +0x74, 0x3a, 0x68, 0x65, 0x61, 0x72, 0x74, 0x73, 0x70, 0x61, 0x72, 0x6b, +0x6c, 0x69, 0x6e, 0x67, 0xd83d, 0xdc98, 0x3a, 0x63, 0x75, 0x70, 0x69, 0x64, +0x3a, 0x63, 0x75, 0x70, 0x69, 0x64, 0xd83d, 0xdc9d, 0x3a, 0x67, 0x69, 0x66, +0x74, 0x5f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x3a, 0x67, 0x69, 0x66, 0x74, +0x68, 0x65, 0x61, 0x72, 0x74, 0xd83d, 0xdc9f, 0x3a, 0x68, 0x65, 0x61, 0x72, +0x74, 0x5f, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, +0x3a, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x68, +0x65, 0x61, 0x72, 0x74, 0x262e, 0xfe0f, 0x3a, 0x70, 0x65, 0x61, 0x63, 0x65, +0x5f, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x3a, 0x70, 0x65, 0x61, 0x63, +0x65, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x262e, 0xfe0f, 0x3a, 0x70, 0x65, +0x61, 0x63, 0x65, 0x3a, 0x70, 0x65, 0x61, 0x63, 0x65, 0x271d, 0xfe0f, 0x3a, +0x6c, 0x61, 0x74, 0x69, 0x6e, 0x5f, 0x63, 0x72, 0x6f, 0x73, 0x73, 0x3a, +0x63, 0x72, 0x6f, 0x73, 0x73, 0x6c, 0x61, 0x74, 0x69, 0x6e, 0x271d, 0xfe0f, +0x3a, 0x63, 0x72, 0x6f, 0x73, 0x73, 0x3a, 0x63, 0x72, 0x6f, 0x73, 0x73, +0x262a, 0xfe0f, 0x3a, 0x73, 0x74, 0x61, 0x72, 0x5f, 0x61, 0x6e, 0x64, 0x5f, +0x63, 0x72, 0x65, 0x73, 0x63, 0x65, 0x6e, 0x74, 0x3a, 0x61, 0x6e, 0x64, +0x63, 0x72, 0x65, 0x73, 0x63, 0x65, 0x6e, 0x74, 0x73, 0x74, 0x61, 0x72, +0xd83d, 0xdd49, 0x3a, 0x6f, 0x6d, 0x5f, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, +0x3a, 0x6f, 0x6d, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x2638, 0xfe0f, 0x3a, +0x77, 0x68, 0x65, 0x65, 0x6c, 0x5f, 0x6f, 0x66, 0x5f, 0x64, 0x68, 0x61, +0x72, 0x6d, 0x61, 0x3a, 0x64, 0x68, 0x61, 0x72, 0x6d, 0x61, 0x6f, 0x66, +0x77, 0x68, 0x65, 0x65, 0x6c, 0x2721, 0xfe0f, 0x3a, 0x73, 0x74, 0x61, 0x72, +0x5f, 0x6f, 0x66, 0x5f, 0x64, 0x61, 0x76, 0x69, 0x64, 0x3a, 0x64, 0x61, +0x76, 0x69, 0x64, 0x6f, 0x66, 0x73, 0x74, 0x61, 0x72, 0xd83d, 0xdd2f, 0x3a, +0x73, 0x69, 0x78, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x65, 0x64, 0x5f, +0x73, 0x74, 0x61, 0x72, 0x3a, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x65, 0x64, +0x73, 0x69, 0x78, 0x73, 0x74, 0x61, 0x72, 0xd83d, 0xdd4e, 0x3a, 0x6d, 0x65, +0x6e, 0x6f, 0x72, 0x61, 0x68, 0x3a, 0x6d, 0x65, 0x6e, 0x6f, 0x72, 0x61, +0x68, 0x262f, 0xfe0f, 0x3a, 0x79, 0x69, 0x6e, 0x5f, 0x79, 0x61, 0x6e, 0x67, +0x3a, 0x79, 0x61, 0x6e, 0x67, 0x79, 0x69, 0x6e, 0x2626, 0xfe0f, 0x3a, 0x6f, +0x72, 0x74, 0x68, 0x6f, 0x64, 0x6f, 0x78, 0x5f, 0x63, 0x72, 0x6f, 0x73, +0x73, 0x3a, 0x63, 0x72, 0x6f, 0x73, 0x73, 0x6f, 0x72, 0x74, 0x68, 0x6f, +0x64, 0x6f, 0x78, 0xd83d, 0xded0, 0x3a, 0x77, 0x6f, 0x72, 0x73, 0x68, 0x69, +0x70, 0x5f, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x3a, 0x73, 0x79, 0x6d, +0x62, 0x6f, 0x6c, 0x77, 0x6f, 0x72, 0x73, 0x68, 0x69, 0x70, 0xd83d, 0xded0, +0x3a, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x66, 0x5f, 0x77, 0x6f, +0x72, 0x73, 0x68, 0x69, 0x70, 0x3a, 0x6f, 0x66, 0x70, 0x6c, 0x61, 0x63, +0x65, 0x77, 0x6f, 0x72, 0x73, 0x68, 0x69, 0x70, 0x26ce, 0x3a, 0x6f, 0x70, +0x68, 0x69, 0x75, 0x63, 0x68, 0x75, 0x73, 0x3a, 0x6f, 0x70, 0x68, 0x69, +0x75, 0x63, 0x68, 0x75, 0x73, 0x2648, 0xfe0f, 0x3a, 0x61, 0x72, 0x69, 0x65, +0x73, 0x3a, 0x61, 0x72, 0x69, 0x65, 0x73, 0x2649, 0xfe0f, 0x3a, 0x74, 0x61, +0x75, 0x72, 0x75, 0x73, 0x3a, 0x74, 0x61, 0x75, 0x72, 0x75, 0x73, 0x264a, +0xfe0f, 0x3a, 0x67, 0x65, 0x6d, 0x69, 0x6e, 0x69, 0x3a, 0x67, 0x65, 0x6d, +0x69, 0x6e, 0x69, 0x264b, 0xfe0f, 0x3a, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x72, +0x3a, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x264c, 0xfe0f, 0x3a, 0x6c, 0x65, +0x6f, 0x3a, 0x6c, 0x65, 0x6f, 0x264d, 0xfe0f, 0x3a, 0x76, 0x69, 0x72, 0x67, +0x6f, 0x3a, 0x76, 0x69, 0x72, 0x67, 0x6f, 0x264e, 0xfe0f, 0x3a, 0x6c, 0x69, +0x62, 0x72, 0x61, 0x3a, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x264f, 0xfe0f, 0x3a, +0x73, 0x63, 0x6f, 0x72, 0x70, 0x69, 0x75, 0x73, 0x3a, 0x73, 0x63, 0x6f, +0x72, 0x70, 0x69, 0x75, 0x73, 0x2650, 0xfe0f, 0x3a, 0x73, 0x61, 0x67, 0x69, +0x74, 0x74, 0x61, 0x72, 0x69, 0x75, 0x73, 0x3a, 0x73, 0x61, 0x67, 0x69, +0x74, 0x74, 0x61, 0x72, 0x69, 0x75, 0x73, 0x2651, 0xfe0f, 0x3a, 0x63, 0x61, +0x70, 0x72, 0x69, 0x63, 0x6f, 0x72, 0x6e, 0x3a, 0x63, 0x61, 0x70, 0x72, +0x69, 0x63, 0x6f, 0x72, 0x6e, 0x2652, 0xfe0f, 0x3a, 0x61, 0x71, 0x75, 0x61, +0x72, 0x69, 0x75, 0x73, 0x3a, 0x61, 0x71, 0x75, 0x61, 0x72, 0x69, 0x75, +0x73, 0x2653, 0xfe0f, 0x3a, 0x70, 0x69, 0x73, 0x63, 0x65, 0x73, 0x3a, 0x70, +0x69, 0x73, 0x63, 0x65, 0x73, 0xd83c, 0xdd94, 0x3a, 0x69, 0x64, 0x3a, 0x69, +0x64, 0x269b, 0xfe0f, 0x3a, 0x61, 0x74, 0x6f, 0x6d, 0x5f, 0x73, 0x79, 0x6d, +0x62, 0x6f, 0x6c, 0x3a, 0x61, 0x74, 0x6f, 0x6d, 0x73, 0x79, 0x6d, 0x62, +0x6f, 0x6c, 0x269b, 0xfe0f, 0x3a, 0x61, 0x74, 0x6f, 0x6d, 0x3a, 0x61, 0x74, +0x6f, 0x6d, 0xd83c, 0xde51, 0x3a, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x3a, +0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2622, 0xfe0f, 0x3a, 0x72, 0x61, 0x64, +0x69, 0x6f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x73, 0x69, 0x67, +0x6e, 0x3a, 0x72, 0x61, 0x64, 0x69, 0x6f, 0x61, 0x63, 0x74, 0x69, 0x76, +0x65, 0x73, 0x69, 0x67, 0x6e, 0x2622, 0xfe0f, 0x3a, 0x72, 0x61, 0x64, 0x69, +0x6f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x3a, 0x72, 0x61, 0x64, 0x69, +0x6f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x2623, 0xfe0f, 0x3a, 0x62, 0x69, +0x6f, 0x68, 0x61, 0x7a, 0x61, 0x72, 0x64, 0x5f, 0x73, 0x69, 0x67, 0x6e, +0x3a, 0x62, 0x69, 0x6f, 0x68, 0x61, 0x7a, 0x61, 0x72, 0x64, 0x73, 0x69, +0x67, 0x6e, 0x2623, 0xfe0f, 0x3a, 0x62, 0x69, 0x6f, 0x68, 0x61, 0x7a, 0x61, +0x72, 0x64, 0x3a, 0x62, 0x69, 0x6f, 0x68, 0x61, 0x7a, 0x61, 0x72, 0x64, +0xd83d, 0xdcf4, 0x3a, 0x6d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x68, +0x6f, 0x6e, 0x65, 0x5f, 0x6f, 0x66, 0x66, 0x3a, 0x6d, 0x6f, 0x62, 0x69, +0x6c, 0x65, 0x6f, 0x66, 0x66, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0xd83d, 0xdcf3, +0x3a, 0x76, 0x69, 0x62, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, +0x6f, 0x64, 0x65, 0x3a, 0x6d, 0x6f, 0x64, 0x65, 0x76, 0x69, 0x62, 0x72, +0x61, 0x74, 0x69, 0x6f, 0x6e, 0xd83c, 0xde36, 0x3a, 0x75, 0x36, 0x37, 0x30, +0x39, 0x3a, 0x75, 0x36, 0x37, 0x30, 0x39, 0xd83c, 0xde1a, 0xfe0f, 0x3a, 0x75, +0x37, 0x31, 0x32, 0x31, 0x3a, 0x75, 0x37, 0x31, 0x32, 0x31, 0xd83c, 0xde38, +0x3a, 0x75, 0x37, 0x35, 0x33, 0x33, 0x3a, 0x75, 0x37, 0x35, 0x33, 0x33, +0xd83c, 0xde3a, 0x3a, 0x75, 0x35, 0x35, 0x62, 0x36, 0x3a, 0x75, 0x35, 0x35, +0x62, 0x36, 0xd83c, 0xde37, 0x3a, 0x75, 0x36, 0x37, 0x30, 0x38, 0x3a, 0x75, +0x36, 0x37, 0x30, 0x38, 0x2734, 0xfe0f, 0x3a, 0x65, 0x69, 0x67, 0x68, 0x74, +0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x6c, 0x61, +0x63, 0x6b, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x3a, 0x62, 0x6c, 0x61, 0x63, +0x6b, 0x65, 0x69, 0x67, 0x68, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x65, +0x64, 0x73, 0x74, 0x61, 0x72, 0xd83c, 0xdd9a, 0x3a, 0x76, 0x73, 0x3a, 0x76, +0x73, 0xd83d, 0xdcae, 0x3a, 0x77, 0x68, 0x69, 0x74, 0x65, 0x5f, 0x66, 0x6c, +0x6f, 0x77, 0x65, 0x72, 0x3a, 0x66, 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x77, +0x68, 0x69, 0x74, 0x65, 0xd83c, 0xde50, 0x3a, 0x69, 0x64, 0x65, 0x6f, 0x67, +0x72, 0x61, 0x70, 0x68, 0x5f, 0x61, 0x64, 0x76, 0x61, 0x6e, 0x74, 0x61, +0x67, 0x65, 0x3a, 0x61, 0x64, 0x76, 0x61, 0x6e, 0x74, 0x61, 0x67, 0x65, +0x69, 0x64, 0x65, 0x6f, 0x67, 0x72, 0x61, 0x70, 0x68, 0x3299, 0xfe0f, 0x3a, +0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x3a, 0x73, 0x65, 0x63, 0x72, 0x65, +0x74, 0x3297, 0xfe0f, 0x3a, 0x63, 0x6f, 0x6e, 0x67, 0x72, 0x61, 0x74, 0x75, +0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x3a, 0x63, 0x6f, 0x6e, 0x67, +0x72, 0x61, 0x74, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0xd83c, +0xde34, 0x3a, 0x75, 0x35, 0x34, 0x30, 0x38, 0x3a, 0x75, 0x35, 0x34, 0x30, +0x38, 0xd83c, 0xde35, 0x3a, 0x75, 0x36, 0x65, 0x38, 0x30, 0x3a, 0x75, 0x36, +0x65, 0x38, 0x30, 0xd83c, 0xde39, 0x3a, 0x75, 0x35, 0x32, 0x37, 0x32, 0x3a, +0x75, 0x35, 0x32, 0x37, 0x32, 0xd83c, 0xde32, 0x3a, 0x75, 0x37, 0x39, 0x38, +0x31, 0x3a, 0x75, 0x37, 0x39, 0x38, 0x31, 0xd83c, 0xdd70, 0xfe0f, 0x3a, 0x61, +0x3a, 0x61, 0xd83c, 0xdd71, 0xfe0f, 0x3a, 0x62, 0x3a, 0x62, 0xd83c, 0xdd8e, 0x3a, +0x61, 0x62, 0x3a, 0x61, 0x62, 0xd83c, 0xdd91, 0x3a, 0x63, 0x6c, 0x3a, 0x63, +0x6c, 0xd83c, 0xdd7e, 0xfe0f, 0x3a, 0x6f, 0x32, 0x3a, 0x6f, 0x32, 0xd83c, 0xdd98, +0x3a, 0x73, 0x6f, 0x73, 0x3a, 0x73, 0x6f, 0x73, 0x274c, 0x3a, 0x78, 0x3a, +0x78, 0x2b55, 0xfe0f, 0x3a, 0x6f, 0x3a, 0x6f, 0xd83d, 0xded1, 0x3a, 0x73, 0x74, +0x6f, 0x70, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x3a, 0x73, 0x69, 0x67, 0x6e, +0x73, 0x74, 0x6f, 0x70, 0xd83d, 0xded1, 0x3a, 0x6f, 0x63, 0x74, 0x61, 0x67, +0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x3a, 0x6f, 0x63, +0x74, 0x61, 0x67, 0x6f, 0x6e, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x26d4, +0xfe0f, 0x3a, 0x6e, 0x6f, 0x5f, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x3a, 0x65, +0x6e, 0x74, 0x72, 0x79, 0x6e, 0x6f, 0xd83d, 0xdcdb, 0x3a, 0x6e, 0x61, 0x6d, +0x65, 0x5f, 0x62, 0x61, 0x64, 0x67, 0x65, 0x3a, 0x62, 0x61, 0x64, 0x67, +0x65, 0x6e, 0x61, 0x6d, 0x65, 0xd83d, 0xdeab, 0x3a, 0x6e, 0x6f, 0x5f, 0x65, +0x6e, 0x74, 0x72, 0x79, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x3a, 0x65, 0x6e, +0x74, 0x72, 0x79, 0x6e, 0x6f, 0x73, 0x69, 0x67, 0x6e, 0xd83d, 0xdcaf, 0x3a, +0x31, 0x30, 0x30, 0x3a, 0x31, 0x30, 0x30, 0xd83d, 0xdca2, 0x3a, 0x61, 0x6e, +0x67, 0x65, 0x72, 0x3a, 0x61, 0x6e, 0x67, 0x65, 0x72, 0x2668, 0xfe0f, 0x3a, +0x68, 0x6f, 0x74, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x3a, 0x68, +0x6f, 0x74, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x73, 0xd83d, 0xdeb7, 0x3a, +0x6e, 0x6f, 0x5f, 0x70, 0x65, 0x64, 0x65, 0x73, 0x74, 0x72, 0x69, 0x61, +0x6e, 0x73, 0x3a, 0x6e, 0x6f, 0x70, 0x65, 0x64, 0x65, 0x73, 0x74, 0x72, +0x69, 0x61, 0x6e, 0x73, 0xd83d, 0xdeaf, 0x3a, 0x64, 0x6f, 0x5f, 0x6e, 0x6f, +0x74, 0x5f, 0x6c, 0x69, 0x74, 0x74, 0x65, 0x72, 0x3a, 0x64, 0x6f, 0x6c, +0x69, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x6f, 0x74, 0xd83d, 0xdeb3, 0x3a, 0x6e, +0x6f, 0x5f, 0x62, 0x69, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x73, 0x3a, 0x62, +0x69, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x73, 0x6e, 0x6f, 0xd83d, 0xdeb1, 0x3a, +0x6e, 0x6f, 0x6e, 0x2d, 0x70, 0x6f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, +0x77, 0x61, 0x74, 0x65, 0x72, 0x3a, 0x6e, 0x6f, 0x6e, 0x70, 0x6f, 0x74, +0x61, 0x62, 0x6c, 0x65, 0x77, 0x61, 0x74, 0x65, 0x72, 0xd83d, 0xdd1e, 0x3a, +0x75, 0x6e, 0x64, 0x65, 0x72, 0x61, 0x67, 0x65, 0x3a, 0x75, 0x6e, 0x64, +0x65, 0x72, 0x61, 0x67, 0x65, 0xd83d, 0xdcf5, 0x3a, 0x6e, 0x6f, 0x5f, 0x6d, +0x6f, 0x62, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x73, +0x3a, 0x6d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x6e, 0x6f, 0x70, 0x68, 0x6f, +0x6e, 0x65, 0x73, 0xd83d, 0xdead, 0x3a, 0x6e, 0x6f, 0x5f, 0x73, 0x6d, 0x6f, +0x6b, 0x69, 0x6e, 0x67, 0x3a, 0x6e, 0x6f, 0x73, 0x6d, 0x6f, 0x6b, 0x69, +0x6e, 0x67, 0x2757, 0xfe0f, 0x3a, 0x65, 0x78, 0x63, 0x6c, 0x61, 0x6d, 0x61, +0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x65, 0x78, 0x63, 0x6c, 0x61, 0x6d, 0x61, +0x74, 0x69, 0x6f, 0x6e, 0x2755, 0x3a, 0x67, 0x72, 0x65, 0x79, 0x5f, 0x65, +0x78, 0x63, 0x6c, 0x61, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x65, +0x78, 0x63, 0x6c, 0x61, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x67, 0x72, +0x65, 0x79, 0x2753, 0x3a, 0x71, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, +0x3a, 0x71, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x2754, 0x3a, 0x67, +0x72, 0x65, 0x79, 0x5f, 0x71, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, +0x3a, 0x67, 0x72, 0x65, 0x79, 0x71, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, +0x6e, 0x203c, 0xfe0f, 0x3a, 0x62, 0x61, 0x6e, 0x67, 0x62, 0x61, 0x6e, 0x67, +0x3a, 0x62, 0x61, 0x6e, 0x67, 0x62, 0x61, 0x6e, 0x67, 0x2049, 0xfe0f, 0x3a, +0x69, 0x6e, 0x74, 0x65, 0x72, 0x72, 0x6f, 0x62, 0x61, 0x6e, 0x67, 0x3a, +0x69, 0x6e, 0x74, 0x65, 0x72, 0x72, 0x6f, 0x62, 0x61, 0x6e, 0x67, 0xd83d, +0xdd05, 0x3a, 0x6c, 0x6f, 0x77, 0x5f, 0x62, 0x72, 0x69, 0x67, 0x68, 0x74, +0x6e, 0x65, 0x73, 0x73, 0x3a, 0x62, 0x72, 0x69, 0x67, 0x68, 0x74, 0x6e, +0x65, 0x73, 0x73, 0x6c, 0x6f, 0x77, 0xd83d, 0xdd06, 0x3a, 0x68, 0x69, 0x67, +0x68, 0x5f, 0x62, 0x72, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x65, 0x73, 0x73, +0x3a, 0x62, 0x72, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x68, +0x69, 0x67, 0x68, 0x303d, 0xfe0f, 0x3a, 0x70, 0x61, 0x72, 0x74, 0x5f, 0x61, +0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, +0x61, 0x72, 0x6b, 0x3a, 0x61, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, +0x69, 0x6f, 0x6e, 0x6d, 0x61, 0x72, 0x6b, 0x70, 0x61, 0x72, 0x74, 0x26a0, +0xfe0f, 0x3a, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x3a, 0x77, 0x61, +0x72, 0x6e, 0x69, 0x6e, 0x67, 0xd83d, 0xdeb8, 0x3a, 0x63, 0x68, 0x69, 0x6c, +0x64, 0x72, 0x65, 0x6e, 0x5f, 0x63, 0x72, 0x6f, 0x73, 0x73, 0x69, 0x6e, +0x67, 0x3a, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x63, 0x72, +0x6f, 0x73, 0x73, 0x69, 0x6e, 0x67, 0xd83d, 0xdd31, 0x3a, 0x74, 0x72, 0x69, +0x64, 0x65, 0x6e, 0x74, 0x3a, 0x74, 0x72, 0x69, 0x64, 0x65, 0x6e, 0x74, +0x269c, 0xfe0f, 0x3a, 0x66, 0x6c, 0x65, 0x75, 0x72, 0x2d, 0x64, 0x65, 0x2d, +0x6c, 0x69, 0x73, 0x3a, 0x64, 0x65, 0x66, 0x6c, 0x65, 0x75, 0x72, 0x6c, +0x69, 0x73, 0xd83d, 0xdd30, 0x3a, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x6e, 0x65, +0x72, 0x3a, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x267b, 0xfe0f, +0x3a, 0x72, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x3a, 0x72, 0x65, 0x63, +0x79, 0x63, 0x6c, 0x65, 0x2705, 0x3a, 0x77, 0x68, 0x69, 0x74, 0x65, 0x5f, +0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x6d, 0x61, 0x72, 0x6b, 0x3a, 0x63, +0x68, 0x65, 0x63, 0x6b, 0x6d, 0x61, 0x72, 0x6b, 0x77, 0x68, 0x69, 0x74, +0x65, 0xd83c, 0xde2f, 0xfe0f, 0x3a, 0x75, 0x36, 0x33, 0x30, 0x37, 0x3a, 0x75, +0x36, 0x33, 0x30, 0x37, 0xd83d, 0xdcb9, 0x3a, 0x63, 0x68, 0x61, 0x72, 0x74, +0x3a, 0x63, 0x68, 0x61, 0x72, 0x74, 0x2747, 0xfe0f, 0x3a, 0x73, 0x70, 0x61, +0x72, 0x6b, 0x6c, 0x65, 0x3a, 0x73, 0x70, 0x61, 0x72, 0x6b, 0x6c, 0x65, +0x2733, 0xfe0f, 0x3a, 0x65, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x73, 0x70, 0x6f, +0x6b, 0x65, 0x64, 0x5f, 0x61, 0x73, 0x74, 0x65, 0x72, 0x69, 0x73, 0x6b, +0x3a, 0x61, 0x73, 0x74, 0x65, 0x72, 0x69, 0x73, 0x6b, 0x65, 0x69, 0x67, +0x68, 0x74, 0x73, 0x70, 0x6f, 0x6b, 0x65, 0x64, 0x274e, 0x3a, 0x6e, 0x65, +0x67, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x73, 0x71, 0x75, 0x61, 0x72, +0x65, 0x64, 0x5f, 0x63, 0x72, 0x6f, 0x73, 0x73, 0x5f, 0x6d, 0x61, 0x72, +0x6b, 0x3a, 0x63, 0x72, 0x6f, 0x73, 0x73, 0x6d, 0x61, 0x72, 0x6b, 0x6e, +0x65, 0x67, 0x61, 0x74, 0x69, 0x76, 0x65, 0x73, 0x71, 0x75, 0x61, 0x72, +0x65, 0x64, 0xd83c, 0xdf10, 0x3a, 0x67, 0x6c, 0x6f, 0x62, 0x65, 0x5f, 0x77, +0x69, 0x74, 0x68, 0x5f, 0x6d, 0x65, 0x72, 0x69, 0x64, 0x69, 0x61, 0x6e, +0x73, 0x3a, 0x67, 0x6c, 0x6f, 0x62, 0x65, 0x6d, 0x65, 0x72, 0x69, 0x64, +0x69, 0x61, 0x6e, 0x73, 0x77, 0x69, 0x74, 0x68, 0xd83d, 0xdca0, 0x3a, 0x64, +0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x5f, 0x73, 0x68, 0x61, 0x70, 0x65, +0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x61, 0x5f, 0x64, 0x6f, 0x74, 0x5f, +0x69, 0x6e, 0x73, 0x69, 0x64, 0x65, 0x3a, 0x61, 0x64, 0x69, 0x61, 0x6d, +0x6f, 0x6e, 0x64, 0x64, 0x6f, 0x74, 0x69, 0x6e, 0x73, 0x69, 0x64, 0x65, +0x73, 0x68, 0x61, 0x70, 0x65, 0x77, 0x69, 0x74, 0x68, 0x24c2, 0xfe0f, 0x3a, +0x6d, 0x3a, 0x6d, 0xd83c, 0xdf00, 0x3a, 0x63, 0x79, 0x63, 0x6c, 0x6f, 0x6e, +0x65, 0x3a, 0x63, 0x79, 0x63, 0x6c, 0x6f, 0x6e, 0x65, 0xd83d, 0xdca4, 0x3a, +0x7a, 0x7a, 0x7a, 0x3a, 0x7a, 0x7a, 0x7a, 0xd83c, 0xdfe7, 0x3a, 0x61, 0x74, +0x6d, 0x3a, 0x61, 0x74, 0x6d, 0xd83d, 0xdebe, 0x3a, 0x77, 0x63, 0x3a, 0x77, +0x63, 0x267f, 0xfe0f, 0x3a, 0x77, 0x68, 0x65, 0x65, 0x6c, 0x63, 0x68, 0x61, +0x69, 0x72, 0x3a, 0x77, 0x68, 0x65, 0x65, 0x6c, 0x63, 0x68, 0x61, 0x69, +0x72, 0xd83c, 0xdd7f, 0xfe0f, 0x3a, 0x70, 0x61, 0x72, 0x6b, 0x69, 0x6e, 0x67, +0x3a, 0x70, 0x61, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0xd83c, 0xde33, 0x3a, 0x75, +0x37, 0x61, 0x37, 0x61, 0x3a, 0x75, 0x37, 0x61, 0x37, 0x61, 0xd83c, 0xde02, +0x3a, 0x73, 0x61, 0x3a, 0x73, 0x61, 0xd83d, 0xdec2, 0x3a, 0x70, 0x61, 0x73, +0x73, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, +0x6c, 0x3a, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x61, 0x73, +0x73, 0x70, 0x6f, 0x72, 0x74, 0xd83d, 0xdec3, 0x3a, 0x63, 0x75, 0x73, 0x74, +0x6f, 0x6d, 0x73, 0x3a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x73, 0xd83d, +0xdec4, 0x3a, 0x62, 0x61, 0x67, 0x67, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6c, +0x61, 0x69, 0x6d, 0x3a, 0x62, 0x61, 0x67, 0x67, 0x61, 0x67, 0x65, 0x63, +0x6c, 0x61, 0x69, 0x6d, 0xd83d, 0xdec5, 0x3a, 0x6c, 0x65, 0x66, 0x74, 0x5f, +0x6c, 0x75, 0x67, 0x67, 0x61, 0x67, 0x65, 0x3a, 0x6c, 0x65, 0x66, 0x74, +0x6c, 0x75, 0x67, 0x67, 0x61, 0x67, 0x65, 0xd83d, 0xdeb9, 0x3a, 0x6d, 0x65, +0x6e, 0x73, 0x3a, 0x6d, 0x65, 0x6e, 0x73, 0xd83d, 0xdeba, 0x3a, 0x77, 0x6f, +0x6d, 0x65, 0x6e, 0x73, 0x3a, 0x77, 0x6f, 0x6d, 0x65, 0x6e, 0x73, 0xd83d, +0xdebc, 0x3a, 0x62, 0x61, 0x62, 0x79, 0x5f, 0x73, 0x79, 0x6d, 0x62, 0x6f, +0x6c, 0x3a, 0x62, 0x61, 0x62, 0x79, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, +0xd83d, 0xdebb, 0x3a, 0x72, 0x65, 0x73, 0x74, 0x72, 0x6f, 0x6f, 0x6d, 0x3a, +0x72, 0x65, 0x73, 0x74, 0x72, 0x6f, 0x6f, 0x6d, 0xd83d, 0xdeae, 0x3a, 0x70, +0x75, 0x74, 0x5f, 0x6c, 0x69, 0x74, 0x74, 0x65, 0x72, 0x5f, 0x69, 0x6e, +0x5f, 0x69, 0x74, 0x73, 0x5f, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x3a, 0x69, +0x6e, 0x69, 0x74, 0x73, 0x6c, 0x69, 0x74, 0x74, 0x65, 0x72, 0x70, 0x6c, +0x61, 0x63, 0x65, 0x70, 0x75, 0x74, 0xd83c, 0xdfa6, 0x3a, 0x63, 0x69, 0x6e, +0x65, 0x6d, 0x61, 0x3a, 0x63, 0x69, 0x6e, 0x65, 0x6d, 0x61, 0xd83d, 0xdcf6, +0x3a, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x73, 0x74, 0x72, 0x65, +0x6e, 0x67, 0x74, 0x68, 0x3a, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x73, +0x74, 0x72, 0x65, 0x6e, 0x67, 0x74, 0x68, 0xd83c, 0xde01, 0x3a, 0x6b, 0x6f, +0x6b, 0x6f, 0x3a, 0x6b, 0x6f, 0x6b, 0x6f, 0xd83d, 0xdd23, 0x3a, 0x73, 0x79, +0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x3a, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, +0x73, 0x2139, 0xfe0f, 0x3a, 0x69, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, +0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x3a, 0x69, +0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x6f, +0x75, 0x72, 0x63, 0x65, 0xd83d, 0xdd24, 0x3a, 0x61, 0x62, 0x63, 0x3a, 0x61, +0x62, 0x63, 0xd83d, 0xdd21, 0x3a, 0x61, 0x62, 0x63, 0x64, 0x3a, 0x61, 0x62, +0x63, 0x64, 0xd83d, 0xdd20, 0x3a, 0x63, 0x61, 0x70, 0x69, 0x74, 0x61, 0x6c, +0x5f, 0x61, 0x62, 0x63, 0x64, 0x3a, 0x61, 0x62, 0x63, 0x64, 0x63, 0x61, +0x70, 0x69, 0x74, 0x61, 0x6c, 0xd83c, 0xdd96, 0x3a, 0x6e, 0x67, 0x3a, 0x6e, +0x67, 0xd83c, 0xdd97, 0x3a, 0x6f, 0x6b, 0x3a, 0x6f, 0x6b, 0xd83c, 0xdd99, 0x3a, +0x75, 0x70, 0x3a, 0x75, 0x70, 0xd83c, 0xdd92, 0x3a, 0x63, 0x6f, 0x6f, 0x6c, +0x3a, 0x63, 0x6f, 0x6f, 0x6c, 0xd83c, 0xdd95, 0x3a, 0x6e, 0x65, 0x77, 0x3a, +0x6e, 0x65, 0x77, 0xd83c, 0xdd93, 0x3a, 0x66, 0x72, 0x65, 0x65, 0x3a, 0x66, +0x72, 0x65, 0x65, 0x30, 0xfe0f, 0x20e3, 0x3a, 0x7a, 0x65, 0x72, 0x6f, 0x3a, +0x7a, 0x65, 0x72, 0x6f, 0x31, 0xfe0f, 0x20e3, 0x3a, 0x6f, 0x6e, 0x65, 0x3a, +0x6f, 0x6e, 0x65, 0x32, 0xfe0f, 0x20e3, 0x3a, 0x74, 0x77, 0x6f, 0x3a, 0x74, +0x77, 0x6f, 0x33, 0xfe0f, 0x20e3, 0x3a, 0x74, 0x68, 0x72, 0x65, 0x65, 0x3a, +0x74, 0x68, 0x72, 0x65, 0x65, 0x34, 0xfe0f, 0x20e3, 0x3a, 0x66, 0x6f, 0x75, +0x72, 0x3a, 0x66, 0x6f, 0x75, 0x72, 0x35, 0xfe0f, 0x20e3, 0x3a, 0x66, 0x69, +0x76, 0x65, 0x3a, 0x66, 0x69, 0x76, 0x65, 0x36, 0xfe0f, 0x20e3, 0x3a, 0x73, +0x69, 0x78, 0x3a, 0x73, 0x69, 0x78, 0x37, 0xfe0f, 0x20e3, 0x3a, 0x73, 0x65, +0x76, 0x65, 0x6e, 0x3a, 0x73, 0x65, 0x76, 0x65, 0x6e, 0x38, 0xfe0f, 0x20e3, +0x3a, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x65, 0x69, 0x67, 0x68, 0x74, +0x39, 0xfe0f, 0x20e3, 0x3a, 0x6e, 0x69, 0x6e, 0x65, 0x3a, 0x6e, 0x69, 0x6e, +0x65, 0xd83d, 0xdd1f, 0x3a, 0x6b, 0x65, 0x79, 0x63, 0x61, 0x70, 0x5f, 0x74, +0x65, 0x6e, 0x3a, 0x6b, 0x65, 0x79, 0x63, 0x61, 0x70, 0x74, 0x65, 0x6e, +0xd83d, 0xdd22, 0x3a, 0x31, 0x32, 0x33, 0x34, 0x3a, 0x31, 0x32, 0x33, 0x34, +0x23, 0xfe0f, 0x20e3, 0x3a, 0x68, 0x61, 0x73, 0x68, 0x3a, 0x68, 0x61, 0x73, +0x68, 0x2a, 0xfe0f, 0x20e3, 0x3a, 0x6b, 0x65, 0x79, 0x63, 0x61, 0x70, 0x5f, +0x61, 0x73, 0x74, 0x65, 0x72, 0x69, 0x73, 0x6b, 0x3a, 0x61, 0x73, 0x74, +0x65, 0x72, 0x69, 0x73, 0x6b, 0x6b, 0x65, 0x79, 0x63, 0x61, 0x70, 0x2a, +0xfe0f, 0x20e3, 0x3a, 0x61, 0x73, 0x74, 0x65, 0x72, 0x69, 0x73, 0x6b, 0x3a, +0x61, 0x73, 0x74, 0x65, 0x72, 0x69, 0x73, 0x6b, 0x25b6, 0xfe0f, 0x3a, 0x61, +0x72, 0x72, 0x6f, 0x77, 0x5f, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, +0x3a, 0x61, 0x72, 0x72, 0x6f, 0x77, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, +0x64, 0x23f8, 0x3a, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x65, +0x72, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x72, 0x3a, 0x62, +0x61, 0x72, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x76, 0x65, 0x72, 0x74, +0x69, 0x63, 0x61, 0x6c, 0x23f8, 0x3a, 0x70, 0x61, 0x75, 0x73, 0x65, 0x5f, +0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x3a, 0x62, 0x75, 0x74, 0x74, 0x6f, +0x6e, 0x70, 0x61, 0x75, 0x73, 0x65, 0x23ef, 0x3a, 0x70, 0x6c, 0x61, 0x79, +0x5f, 0x70, 0x61, 0x75, 0x73, 0x65, 0x3a, 0x70, 0x61, 0x75, 0x73, 0x65, +0x70, 0x6c, 0x61, 0x79, 0x23f9, 0x3a, 0x73, 0x74, 0x6f, 0x70, 0x5f, 0x62, +0x75, 0x74, 0x74, 0x6f, 0x6e, 0x3a, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, +0x73, 0x74, 0x6f, 0x70, 0x23fa, 0x3a, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, +0x5f, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x3a, 0x62, 0x75, 0x74, 0x74, +0x6f, 0x6e, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x23ed, 0x3a, 0x6e, 0x65, +0x78, 0x74, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x3a, 0x6e, 0x65, 0x78, +0x74, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x23ed, 0x3a, 0x74, 0x72, 0x61, 0x63, +0x6b, 0x5f, 0x6e, 0x65, 0x78, 0x74, 0x3a, 0x6e, 0x65, 0x78, 0x74, 0x74, +0x72, 0x61, 0x63, 0x6b, 0x23ee, 0x3a, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, +0x75, 0x73, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x3a, 0x70, 0x72, 0x65, +0x76, 0x69, 0x6f, 0x75, 0x73, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x23ee, 0x3a, +0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, +0x75, 0x73, 0x3a, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x74, +0x72, 0x61, 0x63, 0x6b, 0x23e9, 0x3a, 0x66, 0x61, 0x73, 0x74, 0x5f, 0x66, +0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x3a, 0x66, 0x61, 0x73, 0x74, 0x66, +0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x23ea, 0x3a, 0x72, 0x65, 0x77, 0x69, +0x6e, 0x64, 0x3a, 0x72, 0x65, 0x77, 0x69, 0x6e, 0x64, 0x23eb, 0x3a, 0x61, +0x72, 0x72, 0x6f, 0x77, 0x5f, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x5f, +0x75, 0x70, 0x3a, 0x61, 0x72, 0x72, 0x6f, 0x77, 0x64, 0x6f, 0x75, 0x62, +0x6c, 0x65, 0x75, 0x70, 0x23ec, 0x3a, 0x61, 0x72, 0x72, 0x6f, 0x77, 0x5f, +0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x5f, 0x64, 0x6f, 0x77, 0x6e, 0x3a, +0x61, 0x72, 0x72, 0x6f, 0x77, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x64, +0x6f, 0x77, 0x6e, 0x25c0, 0xfe0f, 0x3a, 0x61, 0x72, 0x72, 0x6f, 0x77, 0x5f, +0x62, 0x61, 0x63, 0x6b, 0x77, 0x61, 0x72, 0x64, 0x3a, 0x61, 0x72, 0x72, +0x6f, 0x77, 0x62, 0x61, 0x63, 0x6b, 0x77, 0x61, 0x72, 0x64, 0xd83d, 0xdd3c, +0x3a, 0x61, 0x72, 0x72, 0x6f, 0x77, 0x5f, 0x75, 0x70, 0x5f, 0x73, 0x6d, +0x61, 0x6c, 0x6c, 0x3a, 0x61, 0x72, 0x72, 0x6f, 0x77, 0x73, 0x6d, 0x61, +0x6c, 0x6c, 0x75, 0x70, 0xd83d, 0xdd3d, 0x3a, 0x61, 0x72, 0x72, 0x6f, 0x77, +0x5f, 0x64, 0x6f, 0x77, 0x6e, 0x5f, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x3a, +0x61, 0x72, 0x72, 0x6f, 0x77, 0x64, 0x6f, 0x77, 0x6e, 0x73, 0x6d, 0x61, +0x6c, 0x6c, 0x27a1, 0xfe0f, 0x3a, 0x61, 0x72, 0x72, 0x6f, 0x77, 0x5f, 0x72, +0x69, 0x67, 0x68, 0x74, 0x3a, 0x61, 0x72, 0x72, 0x6f, 0x77, 0x72, 0x69, +0x67, 0x68, 0x74, 0x2b05, 0xfe0f, 0x3a, 0x61, 0x72, 0x72, 0x6f, 0x77, 0x5f, +0x6c, 0x65, 0x66, 0x74, 0x3a, 0x61, 0x72, 0x72, 0x6f, 0x77, 0x6c, 0x65, +0x66, 0x74, 0x2b06, 0xfe0f, 0x3a, 0x61, 0x72, 0x72, 0x6f, 0x77, 0x5f, 0x75, +0x70, 0x3a, 0x61, 0x72, 0x72, 0x6f, 0x77, 0x75, 0x70, 0x2b07, 0xfe0f, 0x3a, +0x61, 0x72, 0x72, 0x6f, 0x77, 0x5f, 0x64, 0x6f, 0x77, 0x6e, 0x3a, 0x61, +0x72, 0x72, 0x6f, 0x77, 0x64, 0x6f, 0x77, 0x6e, 0x2197, 0xfe0f, 0x3a, 0x61, +0x72, 0x72, 0x6f, 0x77, 0x5f, 0x75, 0x70, 0x70, 0x65, 0x72, 0x5f, 0x72, +0x69, 0x67, 0x68, 0x74, 0x3a, 0x61, 0x72, 0x72, 0x6f, 0x77, 0x72, 0x69, +0x67, 0x68, 0x74, 0x75, 0x70, 0x70, 0x65, 0x72, 0x2198, 0xfe0f, 0x3a, 0x61, +0x72, 0x72, 0x6f, 0x77, 0x5f, 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x72, +0x69, 0x67, 0x68, 0x74, 0x3a, 0x61, 0x72, 0x72, 0x6f, 0x77, 0x6c, 0x6f, +0x77, 0x65, 0x72, 0x72, 0x69, 0x67, 0x68, 0x74, 0x2199, 0xfe0f, 0x3a, 0x61, +0x72, 0x72, 0x6f, 0x77, 0x5f, 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x6c, +0x65, 0x66, 0x74, 0x3a, 0x61, 0x72, 0x72, 0x6f, 0x77, 0x6c, 0x65, 0x66, +0x74, 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x2196, 0xfe0f, 0x3a, 0x61, 0x72, 0x72, +0x6f, 0x77, 0x5f, 0x75, 0x70, 0x70, 0x65, 0x72, 0x5f, 0x6c, 0x65, 0x66, +0x74, 0x3a, 0x61, 0x72, 0x72, 0x6f, 0x77, 0x6c, 0x65, 0x66, 0x74, 0x75, +0x70, 0x70, 0x65, 0x72, 0x2195, 0xfe0f, 0x3a, 0x61, 0x72, 0x72, 0x6f, 0x77, +0x5f, 0x75, 0x70, 0x5f, 0x64, 0x6f, 0x77, 0x6e, 0x3a, 0x61, 0x72, 0x72, +0x6f, 0x77, 0x64, 0x6f, 0x77, 0x6e, 0x75, 0x70, 0x2194, 0xfe0f, 0x3a, 0x6c, +0x65, 0x66, 0x74, 0x5f, 0x72, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x61, 0x72, +0x72, 0x6f, 0x77, 0x3a, 0x61, 0x72, 0x72, 0x6f, 0x77, 0x6c, 0x65, 0x66, +0x74, 0x72, 0x69, 0x67, 0x68, 0x74, 0x21aa, 0xfe0f, 0x3a, 0x61, 0x72, 0x72, +0x6f, 0x77, 0x5f, 0x72, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x68, 0x6f, 0x6f, +0x6b, 0x3a, 0x61, 0x72, 0x72, 0x6f, 0x77, 0x68, 0x6f, 0x6f, 0x6b, 0x72, +0x69, 0x67, 0x68, 0x74, 0x21a9, 0xfe0f, 0x3a, 0x6c, 0x65, 0x66, 0x74, 0x77, +0x61, 0x72, 0x64, 0x73, 0x5f, 0x61, 0x72, 0x72, 0x6f, 0x77, 0x5f, 0x77, +0x69, 0x74, 0x68, 0x5f, 0x68, 0x6f, 0x6f, 0x6b, 0x3a, 0x61, 0x72, 0x72, +0x6f, 0x77, 0x68, 0x6f, 0x6f, 0x6b, 0x6c, 0x65, 0x66, 0x74, 0x77, 0x61, +0x72, 0x64, 0x73, 0x77, 0x69, 0x74, 0x68, 0x2934, 0xfe0f, 0x3a, 0x61, 0x72, +0x72, 0x6f, 0x77, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x5f, +0x75, 0x70, 0x3a, 0x61, 0x72, 0x72, 0x6f, 0x77, 0x68, 0x65, 0x61, 0x64, +0x69, 0x6e, 0x67, 0x75, 0x70, 0x2935, 0xfe0f, 0x3a, 0x61, 0x72, 0x72, 0x6f, +0x77, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x6f, +0x77, 0x6e, 0x3a, 0x61, 0x72, 0x72, 0x6f, 0x77, 0x64, 0x6f, 0x77, 0x6e, +0x68, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0xd83d, 0xdd00, 0x3a, 0x74, 0x77, +0x69, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x72, 0x69, 0x67, 0x68, 0x74, 0x77, +0x61, 0x72, 0x64, 0x73, 0x5f, 0x61, 0x72, 0x72, 0x6f, 0x77, 0x73, 0x3a, +0x61, 0x72, 0x72, 0x6f, 0x77, 0x73, 0x72, 0x69, 0x67, 0x68, 0x74, 0x77, +0x61, 0x72, 0x64, 0x73, 0x74, 0x77, 0x69, 0x73, 0x74, 0x65, 0x64, 0xd83d, +0xdd01, 0x3a, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x3a, 0x72, 0x65, 0x70, +0x65, 0x61, 0x74, 0xd83d, 0xdd02, 0x3a, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, +0x5f, 0x6f, 0x6e, 0x65, 0x3a, 0x6f, 0x6e, 0x65, 0x72, 0x65, 0x70, 0x65, +0x61, 0x74, 0xd83d, 0xdd04, 0x3a, 0x61, 0x72, 0x72, 0x6f, 0x77, 0x73, 0x5f, +0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x6c, 0x6f, 0x63, 0x6b, +0x77, 0x69, 0x73, 0x65, 0x3a, 0x61, 0x72, 0x72, 0x6f, 0x77, 0x73, 0x63, +0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x77, +0x69, 0x73, 0x65, 0xd83d, 0xdd03, 0x3a, 0x61, 0x72, 0x72, 0x6f, 0x77, 0x73, +0x5f, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x77, 0x69, 0x73, 0x65, 0x3a, 0x61, +0x72, 0x72, 0x6f, 0x77, 0x73, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x77, 0x69, +0x73, 0x65, 0xd83c, 0xdfb5, 0x3a, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x61, 0x6c, +0x5f, 0x6e, 0x6f, 0x74, 0x65, 0x3a, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x61, +0x6c, 0x6e, 0x6f, 0x74, 0x65, 0xd83c, 0xdfb6, 0x3a, 0x6e, 0x6f, 0x74, 0x65, +0x73, 0x3a, 0x6e, 0x6f, 0x74, 0x65, 0x73, 0x2795, 0x3a, 0x68, 0x65, 0x61, +0x76, 0x79, 0x5f, 0x70, 0x6c, 0x75, 0x73, 0x5f, 0x73, 0x69, 0x67, 0x6e, +0x3a, 0x68, 0x65, 0x61, 0x76, 0x79, 0x70, 0x6c, 0x75, 0x73, 0x73, 0x69, +0x67, 0x6e, 0x2796, 0x3a, 0x68, 0x65, 0x61, 0x76, 0x79, 0x5f, 0x6d, 0x69, +0x6e, 0x75, 0x73, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x3a, 0x68, 0x65, 0x61, +0x76, 0x79, 0x6d, 0x69, 0x6e, 0x75, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x2797, +0x3a, 0x68, 0x65, 0x61, 0x76, 0x79, 0x5f, 0x64, 0x69, 0x76, 0x69, 0x73, +0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x3a, 0x64, 0x69, 0x76, +0x69, 0x73, 0x69, 0x6f, 0x6e, 0x68, 0x65, 0x61, 0x76, 0x79, 0x73, 0x69, +0x67, 0x6e, 0x2716, 0xfe0f, 0x3a, 0x68, 0x65, 0x61, 0x76, 0x79, 0x5f, 0x6d, +0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, +0x6e, 0x5f, 0x78, 0x3a, 0x68, 0x65, 0x61, 0x76, 0x79, 0x6d, 0x75, 0x6c, +0x74, 0x69, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x78, +0xd83d, 0xdcb2, 0x3a, 0x68, 0x65, 0x61, 0x76, 0x79, 0x5f, 0x64, 0x6f, 0x6c, +0x6c, 0x61, 0x72, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x3a, 0x64, 0x6f, 0x6c, +0x6c, 0x61, 0x72, 0x68, 0x65, 0x61, 0x76, 0x79, 0x73, 0x69, 0x67, 0x6e, +0xd83d, 0xdcb1, 0x3a, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, +0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x3a, 0x63, 0x75, 0x72, +0x72, 0x65, 0x6e, 0x63, 0x79, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, +0x65, 0x2122, 0x3a, 0x74, 0x6d, 0x3a, 0x74, 0x6d, 0xa9, 0x3a, 0x63, 0x6f, +0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x63, 0x6f, 0x70, 0x79, +0x72, 0x69, 0x67, 0x68, 0x74, 0xae, 0x3a, 0x72, 0x65, 0x67, 0x69, 0x73, +0x74, 0x65, 0x72, 0x65, 0x64, 0x3a, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, +0x65, 0x72, 0x65, 0x64, 0x3030, 0x3a, 0x77, 0x61, 0x76, 0x79, 0x5f, 0x64, +0x61, 0x73, 0x68, 0x3a, 0x64, 0x61, 0x73, 0x68, 0x77, 0x61, 0x76, 0x79, +0x27b0, 0x3a, 0x63, 0x75, 0x72, 0x6c, 0x79, 0x5f, 0x6c, 0x6f, 0x6f, 0x70, +0x3a, 0x63, 0x75, 0x72, 0x6c, 0x79, 0x6c, 0x6f, 0x6f, 0x70, 0x27bf, 0x3a, +0x6c, 0x6f, 0x6f, 0x70, 0x3a, 0x6c, 0x6f, 0x6f, 0x70, 0xd83d, 0xdd1a, 0x3a, +0x65, 0x6e, 0x64, 0x3a, 0x65, 0x6e, 0x64, 0xd83d, 0xdd19, 0x3a, 0x62, 0x61, +0x63, 0x6b, 0x3a, 0x62, 0x61, 0x63, 0x6b, 0xd83d, 0xdd1b, 0x3a, 0x6f, 0x6e, +0x3a, 0x6f, 0x6e, 0xd83d, 0xdd1d, 0x3a, 0x74, 0x6f, 0x70, 0x3a, 0x74, 0x6f, +0x70, 0xd83d, 0xdd1c, 0x3a, 0x73, 0x6f, 0x6f, 0x6e, 0x3a, 0x73, 0x6f, 0x6f, +0x6e, 0x2714, 0xfe0f, 0x3a, 0x68, 0x65, 0x61, 0x76, 0x79, 0x5f, 0x63, 0x68, +0x65, 0x63, 0x6b, 0x5f, 0x6d, 0x61, 0x72, 0x6b, 0x3a, 0x63, 0x68, 0x65, +0x63, 0x6b, 0x68, 0x65, 0x61, 0x76, 0x79, 0x6d, 0x61, 0x72, 0x6b, 0x2611, +0xfe0f, 0x3a, 0x62, 0x61, 0x6c, 0x6c, 0x6f, 0x74, 0x5f, 0x62, 0x6f, 0x78, +0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x3a, +0x62, 0x61, 0x6c, 0x6c, 0x6f, 0x74, 0x62, 0x6f, 0x78, 0x63, 0x68, 0x65, +0x63, 0x6b, 0x77, 0x69, 0x74, 0x68, 0xd83d, 0xdd18, 0x3a, 0x72, 0x61, 0x64, +0x69, 0x6f, 0x5f, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x3a, 0x62, 0x75, +0x74, 0x74, 0x6f, 0x6e, 0x72, 0x61, 0x64, 0x69, 0x6f, 0x26aa, 0xfe0f, 0x3a, +0x77, 0x68, 0x69, 0x74, 0x65, 0x5f, 0x63, 0x69, 0x72, 0x63, 0x6c, 0x65, +0x3a, 0x63, 0x69, 0x72, 0x63, 0x6c, 0x65, 0x77, 0x68, 0x69, 0x74, 0x65, +0x26ab, 0xfe0f, 0x3a, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x5f, 0x63, 0x69, 0x72, +0x63, 0x6c, 0x65, 0x3a, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x63, 0x69, 0x72, +0x63, 0x6c, 0x65, 0xd83d, 0xdd34, 0x3a, 0x72, 0x65, 0x64, 0x5f, 0x63, 0x69, +0x72, 0x63, 0x6c, 0x65, 0x3a, 0x63, 0x69, 0x72, 0x63, 0x6c, 0x65, 0x72, +0x65, 0x64, 0xd83d, 0xdd35, 0x3a, 0x62, 0x6c, 0x75, 0x65, 0x5f, 0x63, 0x69, +0x72, 0x63, 0x6c, 0x65, 0x3a, 0x62, 0x6c, 0x75, 0x65, 0x63, 0x69, 0x72, +0x63, 0x6c, 0x65, 0xd83d, 0xdd3a, 0x3a, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x5f, +0x72, 0x65, 0x64, 0x5f, 0x74, 0x72, 0x69, 0x61, 0x6e, 0x67, 0x6c, 0x65, +0x3a, 0x72, 0x65, 0x64, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x74, 0x72, 0x69, +0x61, 0x6e, 0x67, 0x6c, 0x65, 0xd83d, 0xdd3b, 0x3a, 0x73, 0x6d, 0x61, 0x6c, +0x6c, 0x5f, 0x72, 0x65, 0x64, 0x5f, 0x74, 0x72, 0x69, 0x61, 0x6e, 0x67, +0x6c, 0x65, 0x5f, 0x64, 0x6f, 0x77, 0x6e, 0x3a, 0x64, 0x6f, 0x77, 0x6e, +0x72, 0x65, 0x64, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x74, 0x72, 0x69, 0x61, +0x6e, 0x67, 0x6c, 0x65, 0xd83d, 0xdd38, 0x3a, 0x73, 0x6d, 0x61, 0x6c, 0x6c, +0x5f, 0x6f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x64, 0x69, 0x61, 0x6d, +0x6f, 0x6e, 0x64, 0x3a, 0x64, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x6f, +0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0xd83d, 0xdd39, +0x3a, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x5f, 0x62, 0x6c, 0x75, 0x65, 0x5f, +0x64, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x3a, 0x62, 0x6c, 0x75, 0x65, +0x64, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x73, 0x6d, 0x61, 0x6c, 0x6c, +0xd83d, 0xdd36, 0x3a, 0x6c, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x6f, 0x72, 0x61, +0x6e, 0x67, 0x65, 0x5f, 0x64, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x3a, +0x64, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x6c, 0x61, 0x72, 0x67, 0x65, +0x6f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0xd83d, 0xdd37, 0x3a, 0x6c, 0x61, 0x72, +0x67, 0x65, 0x5f, 0x62, 0x6c, 0x75, 0x65, 0x5f, 0x64, 0x69, 0x61, 0x6d, +0x6f, 0x6e, 0x64, 0x3a, 0x62, 0x6c, 0x75, 0x65, 0x64, 0x69, 0x61, 0x6d, +0x6f, 0x6e, 0x64, 0x6c, 0x61, 0x72, 0x67, 0x65, 0xd83d, 0xdd33, 0x3a, 0x77, +0x68, 0x69, 0x74, 0x65, 0x5f, 0x73, 0x71, 0x75, 0x61, 0x72, 0x65, 0x5f, +0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x3a, 0x62, 0x75, 0x74, 0x74, 0x6f, +0x6e, 0x73, 0x71, 0x75, 0x61, 0x72, 0x65, 0x77, 0x68, 0x69, 0x74, 0x65, +0xd83d, 0xdd32, 0x3a, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x5f, 0x73, 0x71, 0x75, +0x61, 0x72, 0x65, 0x5f, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x3a, 0x62, +0x6c, 0x61, 0x63, 0x6b, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x73, 0x71, +0x75, 0x61, 0x72, 0x65, 0x25aa, 0xfe0f, 0x3a, 0x62, 0x6c, 0x61, 0x63, 0x6b, +0x5f, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x5f, 0x73, 0x71, 0x75, 0x61, 0x72, +0x65, 0x3a, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x73, 0x6d, 0x61, 0x6c, 0x6c, +0x73, 0x71, 0x75, 0x61, 0x72, 0x65, 0x25ab, 0xfe0f, 0x3a, 0x77, 0x68, 0x69, +0x74, 0x65, 0x5f, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x5f, 0x73, 0x71, 0x75, +0x61, 0x72, 0x65, 0x3a, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x73, 0x71, 0x75, +0x61, 0x72, 0x65, 0x77, 0x68, 0x69, 0x74, 0x65, 0x25fe, 0xfe0f, 0x3a, 0x62, +0x6c, 0x61, 0x63, 0x6b, 0x5f, 0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x5f, +0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x5f, 0x73, 0x71, 0x75, 0x61, 0x72, 0x65, +0x3a, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, +0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x73, 0x71, 0x75, 0x61, 0x72, 0x65, 0x25fd, +0xfe0f, 0x3a, 0x77, 0x68, 0x69, 0x74, 0x65, 0x5f, 0x6d, 0x65, 0x64, 0x69, +0x75, 0x6d, 0x5f, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x5f, 0x73, 0x71, 0x75, +0x61, 0x72, 0x65, 0x3a, 0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x73, 0x6d, +0x61, 0x6c, 0x6c, 0x73, 0x71, 0x75, 0x61, 0x72, 0x65, 0x77, 0x68, 0x69, +0x74, 0x65, 0x25fc, 0xfe0f, 0x3a, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x5f, 0x6d, +0x65, 0x64, 0x69, 0x75, 0x6d, 0x5f, 0x73, 0x71, 0x75, 0x61, 0x72, 0x65, +0x3a, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, +0x73, 0x71, 0x75, 0x61, 0x72, 0x65, 0x25fb, 0xfe0f, 0x3a, 0x77, 0x68, 0x69, +0x74, 0x65, 0x5f, 0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x5f, 0x73, 0x71, +0x75, 0x61, 0x72, 0x65, 0x3a, 0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x73, +0x71, 0x75, 0x61, 0x72, 0x65, 0x77, 0x68, 0x69, 0x74, 0x65, 0x2b1b, 0xfe0f, +0x3a, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x5f, 0x6c, 0x61, 0x72, 0x67, 0x65, +0x5f, 0x73, 0x71, 0x75, 0x61, 0x72, 0x65, 0x3a, 0x62, 0x6c, 0x61, 0x63, +0x6b, 0x6c, 0x61, 0x72, 0x67, 0x65, 0x73, 0x71, 0x75, 0x61, 0x72, 0x65, +0x2b1c, 0xfe0f, 0x3a, 0x77, 0x68, 0x69, 0x74, 0x65, 0x5f, 0x6c, 0x61, 0x72, +0x67, 0x65, 0x5f, 0x73, 0x71, 0x75, 0x61, 0x72, 0x65, 0x3a, 0x6c, 0x61, +0x72, 0x67, 0x65, 0x73, 0x71, 0x75, 0x61, 0x72, 0x65, 0x77, 0x68, 0x69, +0x74, 0x65, 0xd83d, 0xdd08, 0x3a, 0x73, 0x70, 0x65, 0x61, 0x6b, 0x65, 0x72, +0x3a, 0x73, 0x70, 0x65, 0x61, 0x6b, 0x65, 0x72, 0xd83d, 0xdd07, 0x3a, 0x6d, +0x75, 0x74, 0x65, 0x3a, 0x6d, 0x75, 0x74, 0x65, 0xd83d, 0xdd09, 0x3a, 0x73, +0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x73, 0x6f, 0x75, 0x6e, 0x64, 0xd83d, 0xdd0a, +0x3a, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x73, 0x6f, 0x75, 0x6e, 0x64, 0x3a, +0x6c, 0x6f, 0x75, 0x64, 0x73, 0x6f, 0x75, 0x6e, 0x64, 0xd83d, 0xdd14, 0x3a, +0x62, 0x65, 0x6c, 0x6c, 0x3a, 0x62, 0x65, 0x6c, 0x6c, 0xd83d, 0xdd15, 0x3a, +0x6e, 0x6f, 0x5f, 0x62, 0x65, 0x6c, 0x6c, 0x3a, 0x62, 0x65, 0x6c, 0x6c, +0x6e, 0x6f, 0xd83d, 0xdce3, 0x3a, 0x6d, 0x65, 0x67, 0x61, 0x3a, 0x6d, 0x65, +0x67, 0x61, 0xd83d, 0xdce2, 0x3a, 0x6c, 0x6f, 0x75, 0x64, 0x73, 0x70, 0x65, +0x61, 0x6b, 0x65, 0x72, 0x3a, 0x6c, 0x6f, 0x75, 0x64, 0x73, 0x70, 0x65, +0x61, 0x6b, 0x65, 0x72, 0xd83d, 0xdde8, 0x3a, 0x6c, 0x65, 0x66, 0x74, 0x5f, +0x73, 0x70, 0x65, 0x65, 0x63, 0x68, 0x5f, 0x62, 0x75, 0x62, 0x62, 0x6c, +0x65, 0x3a, 0x62, 0x75, 0x62, 0x62, 0x6c, 0x65, 0x6c, 0x65, 0x66, 0x74, +0x73, 0x70, 0x65, 0x65, 0x63, 0x68, 0xd83d, 0xdde8, 0x3a, 0x73, 0x70, 0x65, +0x65, 0x63, 0x68, 0x5f, 0x6c, 0x65, 0x66, 0x74, 0x3a, 0x6c, 0x65, 0x66, +0x74, 0x73, 0x70, 0x65, 0x65, 0x63, 0x68, 0xd83d, 0xdc41, 0x200d, 0xd83d, 0xdde8, +0x3a, 0x65, 0x79, 0x65, 0x5f, 0x69, 0x6e, 0x5f, 0x73, 0x70, 0x65, 0x65, +0x63, 0x68, 0x5f, 0x62, 0x75, 0x62, 0x62, 0x6c, 0x65, 0x3a, 0x62, 0x75, +0x62, 0x62, 0x6c, 0x65, 0x65, 0x79, 0x65, 0x69, 0x6e, 0x73, 0x70, 0x65, +0x65, 0x63, 0x68, 0xd83d, 0xdcac, 0x3a, 0x73, 0x70, 0x65, 0x65, 0x63, 0x68, +0x5f, 0x62, 0x61, 0x6c, 0x6c, 0x6f, 0x6f, 0x6e, 0x3a, 0x62, 0x61, 0x6c, +0x6c, 0x6f, 0x6f, 0x6e, 0x73, 0x70, 0x65, 0x65, 0x63, 0x68, 0xd83d, 0xdcad, +0x3a, 0x74, 0x68, 0x6f, 0x75, 0x67, 0x68, 0x74, 0x5f, 0x62, 0x61, 0x6c, +0x6c, 0x6f, 0x6f, 0x6e, 0x3a, 0x62, 0x61, 0x6c, 0x6c, 0x6f, 0x6f, 0x6e, +0x74, 0x68, 0x6f, 0x75, 0x67, 0x68, 0x74, 0xd83d, 0xddef, 0x3a, 0x72, 0x69, +0x67, 0x68, 0x74, 0x5f, 0x61, 0x6e, 0x67, 0x65, 0x72, 0x5f, 0x62, 0x75, +0x62, 0x62, 0x6c, 0x65, 0x3a, 0x61, 0x6e, 0x67, 0x65, 0x72, 0x62, 0x75, +0x62, 0x62, 0x6c, 0x65, 0x72, 0x69, 0x67, 0x68, 0x74, 0xd83d, 0xddef, 0x3a, +0x61, 0x6e, 0x67, 0x65, 0x72, 0x5f, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3a, +0x61, 0x6e, 0x67, 0x65, 0x72, 0x72, 0x69, 0x67, 0x68, 0x74, 0x2660, 0xfe0f, +0x3a, 0x73, 0x70, 0x61, 0x64, 0x65, 0x73, 0x3a, 0x73, 0x70, 0x61, 0x64, +0x65, 0x73, 0x2663, 0xfe0f, 0x3a, 0x63, 0x6c, 0x75, 0x62, 0x73, 0x3a, 0x63, +0x6c, 0x75, 0x62, 0x73, 0x2665, 0xfe0f, 0x3a, 0x68, 0x65, 0x61, 0x72, 0x74, +0x73, 0x3a, 0x68, 0x65, 0x61, 0x72, 0x74, 0x73, 0x2666, 0xfe0f, 0x3a, 0x64, +0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x73, 0x3a, 0x64, 0x69, 0x61, 0x6d, +0x6f, 0x6e, 0x64, 0x73, 0xd83c, 0xdccf, 0x3a, 0x62, 0x6c, 0x61, 0x63, 0x6b, +0x5f, 0x6a, 0x6f, 0x6b, 0x65, 0x72, 0x3a, 0x62, 0x6c, 0x61, 0x63, 0x6b, +0x6a, 0x6f, 0x6b, 0x65, 0x72, 0xd83c, 0xdfb4, 0x3a, 0x66, 0x6c, 0x6f, 0x77, +0x65, 0x72, 0x5f, 0x70, 0x6c, 0x61, 0x79, 0x69, 0x6e, 0x67, 0x5f, 0x63, +0x61, 0x72, 0x64, 0x73, 0x3a, 0x63, 0x61, 0x72, 0x64, 0x73, 0x66, 0x6c, +0x6f, 0x77, 0x65, 0x72, 0x70, 0x6c, 0x61, 0x79, 0x69, 0x6e, 0x67, 0xd83c, +0xdc04, 0xfe0f, 0x3a, 0x6d, 0x61, 0x68, 0x6a, 0x6f, 0x6e, 0x67, 0x3a, 0x6d, +0x61, 0x68, 0x6a, 0x6f, 0x6e, 0x67, 0xd83d, 0xdd50, 0x3a, 0x63, 0x6c, 0x6f, +0x63, 0x6b, 0x31, 0x3a, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x31, 0xd83d, 0xdd51, +0x3a, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x32, 0x3a, 0x63, 0x6c, 0x6f, 0x63, +0x6b, 0x32, 0xd83d, 0xdd52, 0x3a, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x33, 0x3a, +0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x33, 0xd83d, 0xdd53, 0x3a, 0x63, 0x6c, 0x6f, +0x63, 0x6b, 0x34, 0x3a, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x34, 0xd83d, 0xdd54, +0x3a, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x35, 0x3a, 0x63, 0x6c, 0x6f, 0x63, +0x6b, 0x35, 0xd83d, 0xdd55, 0x3a, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x36, 0x3a, +0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x36, 0xd83d, 0xdd56, 0x3a, 0x63, 0x6c, 0x6f, +0x63, 0x6b, 0x37, 0x3a, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x37, 0xd83d, 0xdd57, +0x3a, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x38, 0x3a, 0x63, 0x6c, 0x6f, 0x63, +0x6b, 0x38, 0xd83d, 0xdd58, 0x3a, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x39, 0x3a, +0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x39, 0xd83d, 0xdd59, 0x3a, 0x63, 0x6c, 0x6f, +0x63, 0x6b, 0x31, 0x30, 0x3a, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x31, 0x30, +0xd83d, 0xdd5a, 0x3a, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x31, 0x31, 0x3a, 0x63, +0x6c, 0x6f, 0x63, 0x6b, 0x31, 0x31, 0xd83d, 0xdd5b, 0x3a, 0x63, 0x6c, 0x6f, +0x63, 0x6b, 0x31, 0x32, 0x3a, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x31, 0x32, +0xd83d, 0xdd5c, 0x3a, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x31, 0x33, 0x30, 0x3a, +0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x31, 0x33, 0x30, 0xd83d, 0xdd5d, 0x3a, 0x63, +0x6c, 0x6f, 0x63, 0x6b, 0x32, 0x33, 0x30, 0x3a, 0x63, 0x6c, 0x6f, 0x63, +0x6b, 0x32, 0x33, 0x30, 0xd83d, 0xdd5e, 0x3a, 0x63, 0x6c, 0x6f, 0x63, 0x6b, +0x33, 0x33, 0x30, 0x3a, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x33, 0x33, 0x30, +0xd83d, 0xdd5f, 0x3a, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x34, 0x33, 0x30, 0x3a, +0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x34, 0x33, 0x30, 0xd83d, 0xdd60, 0x3a, 0x63, +0x6c, 0x6f, 0x63, 0x6b, 0x35, 0x33, 0x30, 0x3a, 0x63, 0x6c, 0x6f, 0x63, +0x6b, 0x35, 0x33, 0x30, 0xd83d, 0xdd61, 0x3a, 0x63, 0x6c, 0x6f, 0x63, 0x6b, +0x36, 0x33, 0x30, 0x3a, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x36, 0x33, 0x30, +0xd83d, 0xdd62, 0x3a, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x37, 0x33, 0x30, 0x3a, +0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x37, 0x33, 0x30, 0xd83d, 0xdd63, 0x3a, 0x63, +0x6c, 0x6f, 0x63, 0x6b, 0x38, 0x33, 0x30, 0x3a, 0x63, 0x6c, 0x6f, 0x63, +0x6b, 0x38, 0x33, 0x30, 0xd83d, 0xdd64, 0x3a, 0x63, 0x6c, 0x6f, 0x63, 0x6b, +0x39, 0x33, 0x30, 0x3a, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x39, 0x33, 0x30, +0xd83d, 0xdd65, 0x3a, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x31, 0x30, 0x33, 0x30, +0x3a, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x31, 0x30, 0x33, 0x30, 0xd83d, 0xdd66, +0x3a, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x31, 0x31, 0x33, 0x30, 0x3a, 0x63, +0x6c, 0x6f, 0x63, 0x6b, 0x31, 0x31, 0x33, 0x30, 0xd83d, 0xdd67, 0x3a, 0x63, +0x6c, 0x6f, 0x63, 0x6b, 0x31, 0x32, 0x33, 0x30, 0x3a, 0x63, 0x6c, 0x6f, +0x63, 0x6b, 0x31, 0x32, 0x33, 0x30, 0xd83c, 0xdff3, 0x3a, 0x77, 0x61, 0x76, +0x69, 0x6e, 0x67, 0x5f, 0x77, 0x68, 0x69, 0x74, 0x65, 0x5f, 0x66, 0x6c, +0x61, 0x67, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x77, 0x61, 0x76, 0x69, 0x6e, +0x67, 0x77, 0x68, 0x69, 0x74, 0x65, 0xd83c, 0xdff3, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x5f, 0x77, 0x68, 0x69, 0x74, 0x65, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x77, 0x68, 0x69, 0x74, 0x65, 0xd83c, 0xdff4, 0x3a, 0x77, 0x61, 0x76, 0x69, +0x6e, 0x67, 0x5f, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x5f, 0x66, 0x6c, 0x61, +0x67, 0x3a, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x66, 0x6c, 0x61, 0x67, 0x77, +0x61, 0x76, 0x69, 0x6e, 0x67, 0xd83c, 0xdff4, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x5f, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x3a, 0x62, 0x6c, 0x61, 0x63, 0x6b, +0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdfc1, 0x3a, 0x63, 0x68, 0x65, 0x63, 0x6b, +0x65, 0x72, 0x65, 0x64, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x3a, 0x63, 0x68, +0x65, 0x63, 0x6b, 0x65, 0x72, 0x65, 0x64, 0x66, 0x6c, 0x61, 0x67, 0xd83d, +0xdea9, 0x3a, 0x74, 0x72, 0x69, 0x61, 0x6e, 0x67, 0x75, 0x6c, 0x61, 0x72, +0x5f, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6f, 0x6e, 0x5f, 0x70, 0x6f, 0x73, +0x74, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6f, 0x6e, 0x70, 0x6f, 0x73, 0x74, +0x74, 0x72, 0x69, 0x61, 0x6e, 0x67, 0x75, 0x6c, 0x61, 0x72, 0xd83c, 0xdff3, +0xfe0f, 0x200d, 0xd83c, 0xdf08, 0x3a, 0x67, 0x61, 0x79, 0x5f, 0x70, 0x72, 0x69, +0x64, 0x65, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x67, 0x61, 0x79, 0x70, 0x72, 0x69, 0x64, 0x65, 0xd83c, 0xdff3, 0xfe0f, 0x200d, +0xd83c, 0xdf08, 0x3a, 0x72, 0x61, 0x69, 0x6e, 0x62, 0x6f, 0x77, 0x5f, 0x66, +0x6c, 0x61, 0x67, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x72, 0x61, 0x69, 0x6e, +0x62, 0x6f, 0x77, 0xd83c, 0xdde6, 0xd83c, 0xddeb, 0x3a, 0x61, 0x66, 0x3a, 0x61, +0x66, 0xd83c, 0xdde6, 0xd83c, 0xddeb, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x61, +0x66, 0x3a, 0x61, 0x66, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdde6, 0xd83c, 0xddfd, +0x3a, 0x61, 0x78, 0x3a, 0x61, 0x78, 0xd83c, 0xdde6, 0xd83c, 0xddfd, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x5f, 0x61, 0x78, 0x3a, 0x61, 0x78, 0x66, 0x6c, 0x61, +0x67, 0xd83c, 0xdde6, 0xd83c, 0xddf1, 0x3a, 0x61, 0x6c, 0x3a, 0x61, 0x6c, 0xd83c, +0xdde6, 0xd83c, 0xddf1, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x61, 0x6c, 0x3a, +0x61, 0x6c, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdde9, 0xd83c, 0xddff, 0x3a, 0x64, +0x7a, 0x3a, 0x64, 0x7a, 0xd83c, 0xdde9, 0xd83c, 0xddff, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x5f, 0x64, 0x7a, 0x3a, 0x64, 0x7a, 0x66, 0x6c, 0x61, 0x67, 0xd83c, +0xdde6, 0xd83c, 0xddf8, 0x3a, 0x61, 0x73, 0x3a, 0x61, 0x73, 0xd83c, 0xdde6, 0xd83c, +0xddf8, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x61, 0x73, 0x3a, 0x61, 0x73, +0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdde6, 0xd83c, 0xdde9, 0x3a, 0x61, 0x64, 0x3a, +0x61, 0x64, 0xd83c, 0xdde6, 0xd83c, 0xdde9, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, +0x61, 0x64, 0x3a, 0x61, 0x64, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdde6, 0xd83c, +0xddf4, 0x3a, 0x61, 0x6f, 0x3a, 0x61, 0x6f, 0xd83c, 0xdde6, 0xd83c, 0xddf4, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x5f, 0x61, 0x6f, 0x3a, 0x61, 0x6f, 0x66, 0x6c, +0x61, 0x67, 0xd83c, 0xdde6, 0xd83c, 0xddee, 0x3a, 0x61, 0x69, 0x3a, 0x61, 0x69, +0xd83c, 0xdde6, 0xd83c, 0xddee, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x61, 0x69, +0x3a, 0x61, 0x69, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdde6, 0xd83c, 0xddf6, 0x3a, +0x61, 0x71, 0x3a, 0x61, 0x71, 0xd83c, 0xdde6, 0xd83c, 0xddf6, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x5f, 0x61, 0x71, 0x3a, 0x61, 0x71, 0x66, 0x6c, 0x61, 0x67, +0xd83c, 0xdde6, 0xd83c, 0xddec, 0x3a, 0x61, 0x67, 0x3a, 0x61, 0x67, 0xd83c, 0xdde6, +0xd83c, 0xddec, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x61, 0x67, 0x3a, 0x61, +0x67, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdde6, 0xd83c, 0xddf7, 0x3a, 0x61, 0x72, +0x3a, 0x61, 0x72, 0xd83c, 0xdde6, 0xd83c, 0xddf7, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x5f, 0x61, 0x72, 0x3a, 0x61, 0x72, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdde6, +0xd83c, 0xddf2, 0x3a, 0x61, 0x6d, 0x3a, 0x61, 0x6d, 0xd83c, 0xdde6, 0xd83c, 0xddf2, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x61, 0x6d, 0x3a, 0x61, 0x6d, 0x66, +0x6c, 0x61, 0x67, 0xd83c, 0xdde6, 0xd83c, 0xddfc, 0x3a, 0x61, 0x77, 0x3a, 0x61, +0x77, 0xd83c, 0xdde6, 0xd83c, 0xddfc, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x61, +0x77, 0x3a, 0x61, 0x77, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdde6, 0xd83c, 0xddfa, +0x3a, 0x68, 0x6d, 0x3a, 0x68, 0x6d, 0xd83c, 0xdde6, 0xd83c, 0xddfa, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x5f, 0x68, 0x6d, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x68, +0x6d, 0xd83c, 0xdde6, 0xd83c, 0xddfa, 0x3a, 0x61, 0x75, 0x3a, 0x61, 0x75, 0xd83c, +0xdde6, 0xd83c, 0xddfa, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x61, 0x75, 0x3a, +0x61, 0x75, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdde6, 0xd83c, 0xddf9, 0x3a, 0x61, +0x74, 0x3a, 0x61, 0x74, 0xd83c, 0xdde6, 0xd83c, 0xddf9, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x5f, 0x61, 0x74, 0x3a, 0x61, 0x74, 0x66, 0x6c, 0x61, 0x67, 0xd83c, +0xdde6, 0xd83c, 0xddff, 0x3a, 0x61, 0x7a, 0x3a, 0x61, 0x7a, 0xd83c, 0xdde6, 0xd83c, +0xddff, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x61, 0x7a, 0x3a, 0x61, 0x7a, +0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdde7, 0xd83c, 0xddf8, 0x3a, 0x62, 0x73, 0x3a, +0x62, 0x73, 0xd83c, 0xdde7, 0xd83c, 0xddf8, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, +0x62, 0x73, 0x3a, 0x62, 0x73, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdde7, 0xd83c, +0xdded, 0x3a, 0x62, 0x68, 0x3a, 0x62, 0x68, 0xd83c, 0xdde7, 0xd83c, 0xdded, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x5f, 0x62, 0x68, 0x3a, 0x62, 0x68, 0x66, 0x6c, +0x61, 0x67, 0xd83c, 0xdde7, 0xd83c, 0xdde9, 0x3a, 0x62, 0x64, 0x3a, 0x62, 0x64, +0xd83c, 0xdde7, 0xd83c, 0xdde9, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x62, 0x64, +0x3a, 0x62, 0x64, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdde7, 0xd83c, 0xdde7, 0x3a, +0x62, 0x62, 0x3a, 0x62, 0x62, 0xd83c, 0xdde7, 0xd83c, 0xdde7, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x5f, 0x62, 0x62, 0x3a, 0x62, 0x62, 0x66, 0x6c, 0x61, 0x67, +0xd83c, 0xdde7, 0xd83c, 0xddfe, 0x3a, 0x62, 0x79, 0x3a, 0x62, 0x79, 0xd83c, 0xdde7, +0xd83c, 0xddfe, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x62, 0x79, 0x3a, 0x62, +0x79, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdde7, 0xd83c, 0xddea, 0x3a, 0x62, 0x65, +0x3a, 0x62, 0x65, 0xd83c, 0xdde7, 0xd83c, 0xddea, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x5f, 0x62, 0x65, 0x3a, 0x62, 0x65, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdde7, +0xd83c, 0xddff, 0x3a, 0x62, 0x7a, 0x3a, 0x62, 0x7a, 0xd83c, 0xdde7, 0xd83c, 0xddff, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x62, 0x7a, 0x3a, 0x62, 0x7a, 0x66, +0x6c, 0x61, 0x67, 0xd83c, 0xdde7, 0xd83c, 0xddef, 0x3a, 0x62, 0x6a, 0x3a, 0x62, +0x6a, 0xd83c, 0xdde7, 0xd83c, 0xddef, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x62, +0x6a, 0x3a, 0x62, 0x6a, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdde7, 0xd83c, 0xddf2, +0x3a, 0x62, 0x6d, 0x3a, 0x62, 0x6d, 0xd83c, 0xdde7, 0xd83c, 0xddf2, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x5f, 0x62, 0x6d, 0x3a, 0x62, 0x6d, 0x66, 0x6c, 0x61, +0x67, 0xd83c, 0xdde7, 0xd83c, 0xddf9, 0x3a, 0x62, 0x74, 0x3a, 0x62, 0x74, 0xd83c, +0xdde7, 0xd83c, 0xddf9, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x62, 0x74, 0x3a, +0x62, 0x74, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdde7, 0xd83c, 0xddf4, 0x3a, 0x62, +0x6f, 0x3a, 0x62, 0x6f, 0xd83c, 0xdde7, 0xd83c, 0xddf4, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x5f, 0x62, 0x6f, 0x3a, 0x62, 0x6f, 0x66, 0x6c, 0x61, 0x67, 0xd83c, +0xdde7, 0xd83c, 0xdde6, 0x3a, 0x62, 0x61, 0x3a, 0x62, 0x61, 0xd83c, 0xdde7, 0xd83c, +0xdde6, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x62, 0x61, 0x3a, 0x62, 0x61, +0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdde7, 0xd83c, 0xddfc, 0x3a, 0x62, 0x77, 0x3a, +0x62, 0x77, 0xd83c, 0xdde7, 0xd83c, 0xddfc, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, +0x62, 0x77, 0x3a, 0x62, 0x77, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdde7, 0xd83c, +0xddf7, 0x3a, 0x62, 0x72, 0x3a, 0x62, 0x72, 0xd83c, 0xdde7, 0xd83c, 0xddf7, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x5f, 0x62, 0x72, 0x3a, 0x62, 0x72, 0x66, 0x6c, +0x61, 0x67, 0xd83c, 0xddee, 0xd83c, 0xddf4, 0x3a, 0x69, 0x6f, 0x3a, 0x69, 0x6f, +0xd83c, 0xddee, 0xd83c, 0xddf4, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x69, 0x6f, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x69, 0x6f, 0xd83c, 0xddee, 0xd83c, 0xddf4, 0x3a, +0x64, 0x67, 0x3a, 0x64, 0x67, 0xd83c, 0xddee, 0xd83c, 0xddf4, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x5f, 0x64, 0x67, 0x3a, 0x64, 0x67, 0x66, 0x6c, 0x61, 0x67, +0xd83c, 0xddfb, 0xd83c, 0xddec, 0x3a, 0x76, 0x67, 0x3a, 0x76, 0x67, 0xd83c, 0xddfb, +0xd83c, 0xddec, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x76, 0x67, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x76, 0x67, 0xd83c, 0xdde7, 0xd83c, 0xddf3, 0x3a, 0x62, 0x6e, +0x3a, 0x62, 0x6e, 0xd83c, 0xdde7, 0xd83c, 0xddf3, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x5f, 0x62, 0x6e, 0x3a, 0x62, 0x6e, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdde7, +0xd83c, 0xddec, 0x3a, 0x62, 0x67, 0x3a, 0x62, 0x67, 0xd83c, 0xdde7, 0xd83c, 0xddec, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x62, 0x67, 0x3a, 0x62, 0x67, 0x66, +0x6c, 0x61, 0x67, 0xd83c, 0xdde7, 0xd83c, 0xddeb, 0x3a, 0x62, 0x66, 0x3a, 0x62, +0x66, 0xd83c, 0xdde7, 0xd83c, 0xddeb, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x62, +0x66, 0x3a, 0x62, 0x66, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdde7, 0xd83c, 0xddee, +0x3a, 0x62, 0x69, 0x3a, 0x62, 0x69, 0xd83c, 0xdde7, 0xd83c, 0xddee, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x5f, 0x62, 0x69, 0x3a, 0x62, 0x69, 0x66, 0x6c, 0x61, +0x67, 0xd83c, 0xddf0, 0xd83c, 0xdded, 0x3a, 0x6b, 0x68, 0x3a, 0x6b, 0x68, 0xd83c, +0xddf0, 0xd83c, 0xdded, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x68, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x6b, 0x68, 0xd83c, 0xdde8, 0xd83c, 0xddf2, 0x3a, 0x63, +0x6d, 0x3a, 0x63, 0x6d, 0xd83c, 0xdde8, 0xd83c, 0xddf2, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x5f, 0x63, 0x6d, 0x3a, 0x63, 0x6d, 0x66, 0x6c, 0x61, 0x67, 0xd83c, +0xdde8, 0xd83c, 0xdde6, 0x3a, 0x63, 0x61, 0x3a, 0x63, 0x61, 0xd83c, 0xdde8, 0xd83c, +0xdde6, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x63, 0x61, 0x3a, 0x63, 0x61, +0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xddee, 0xd83c, 0xdde8, 0x3a, 0x69, 0x63, 0x3a, +0x69, 0x63, 0xd83c, 0xddee, 0xd83c, 0xdde8, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, +0x69, 0x63, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x69, 0x63, 0xd83c, 0xdde8, 0xd83c, +0xddfb, 0x3a, 0x63, 0x76, 0x3a, 0x63, 0x76, 0xd83c, 0xdde8, 0xd83c, 0xddfb, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x5f, 0x63, 0x76, 0x3a, 0x63, 0x76, 0x66, 0x6c, +0x61, 0x67, 0xd83c, 0xdde7, 0xd83c, 0xddf6, 0x3a, 0x62, 0x71, 0x3a, 0x62, 0x71, +0xd83c, 0xdde7, 0xd83c, 0xddf6, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x62, 0x71, +0x3a, 0x62, 0x71, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xddf0, 0xd83c, 0xddfe, 0x3a, +0x6b, 0x79, 0x3a, 0x6b, 0x79, 0xd83c, 0xddf0, 0xd83c, 0xddfe, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x5f, 0x6b, 0x79, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6b, 0x79, +0xd83c, 0xdde8, 0xd83c, 0xddeb, 0x3a, 0x63, 0x66, 0x3a, 0x63, 0x66, 0xd83c, 0xdde8, +0xd83c, 0xddeb, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x63, 0x66, 0x3a, 0x63, +0x66, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xddf9, 0xd83c, 0xdde9, 0x3a, 0x74, 0x64, +0x3a, 0x74, 0x64, 0xd83c, 0xddf9, 0xd83c, 0xdde9, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x5f, 0x74, 0x64, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x74, 0x64, 0xd83c, 0xdde8, +0xd83c, 0xddf1, 0x3a, 0x63, 0x68, 0x69, 0x6c, 0x65, 0x3a, 0x63, 0x68, 0x69, +0x6c, 0x65, 0xd83c, 0xdde8, 0xd83c, 0xddf1, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, +0x63, 0x6c, 0x3a, 0x63, 0x6c, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdde8, 0xd83c, +0xddf3, 0x3a, 0x63, 0x6e, 0x3a, 0x63, 0x6e, 0xd83c, 0xdde8, 0xd83c, 0xddf3, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x5f, 0x63, 0x6e, 0x3a, 0x63, 0x6e, 0x66, 0x6c, +0x61, 0x67, 0xd83c, 0xdde8, 0xd83c, 0xddfd, 0x3a, 0x63, 0x78, 0x3a, 0x63, 0x78, +0xd83c, 0xdde8, 0xd83c, 0xddfd, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x63, 0x78, +0x3a, 0x63, 0x78, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdde8, 0xd83c, 0xdde8, 0x3a, +0x63, 0x63, 0x3a, 0x63, 0x63, 0xd83c, 0xdde8, 0xd83c, 0xdde8, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x5f, 0x63, 0x63, 0x3a, 0x63, 0x63, 0x66, 0x6c, 0x61, 0x67, +0xd83c, 0xdde8, 0xd83c, 0xddf4, 0x3a, 0x63, 0x6f, 0x3a, 0x63, 0x6f, 0xd83c, 0xdde8, +0xd83c, 0xddf4, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x63, 0x6f, 0x3a, 0x63, +0x6f, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xddf0, 0xd83c, 0xddf2, 0x3a, 0x6b, 0x6d, +0x3a, 0x6b, 0x6d, 0xd83c, 0xddf0, 0xd83c, 0xddf2, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x5f, 0x6b, 0x6d, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6b, 0x6d, 0xd83c, 0xdde8, +0xd83c, 0xddec, 0x3a, 0x63, 0x67, 0x3a, 0x63, 0x67, 0xd83c, 0xdde8, 0xd83c, 0xddec, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x63, 0x67, 0x3a, 0x63, 0x67, 0x66, +0x6c, 0x61, 0x67, 0xd83c, 0xdde8, 0xd83c, 0xdde9, 0x3a, 0x63, 0x6f, 0x6e, 0x67, +0x6f, 0x3a, 0x63, 0x6f, 0x6e, 0x67, 0x6f, 0xd83c, 0xdde8, 0xd83c, 0xdde9, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x5f, 0x63, 0x64, 0x3a, 0x63, 0x64, 0x66, 0x6c, +0x61, 0x67, 0xd83c, 0xdde8, 0xd83c, 0xddf0, 0x3a, 0x63, 0x6b, 0x3a, 0x63, 0x6b, +0xd83c, 0xdde8, 0xd83c, 0xddf0, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x63, 0x6b, +0x3a, 0x63, 0x6b, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdde8, 0xd83c, 0xddf7, 0x3a, +0x63, 0x72, 0x3a, 0x63, 0x72, 0xd83c, 0xdde8, 0xd83c, 0xddf7, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x5f, 0x63, 0x72, 0x3a, 0x63, 0x72, 0x66, 0x6c, 0x61, 0x67, +0xd83c, 0xdde8, 0xd83c, 0xddee, 0x3a, 0x63, 0x69, 0x3a, 0x63, 0x69, 0xd83c, 0xdde8, +0xd83c, 0xddee, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x63, 0x69, 0x3a, 0x63, +0x69, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdded, 0xd83c, 0xddf7, 0x3a, 0x68, 0x72, +0x3a, 0x68, 0x72, 0xd83c, 0xdded, 0xd83c, 0xddf7, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x5f, 0x68, 0x72, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x68, 0x72, 0xd83c, 0xdde8, +0xd83c, 0xddfa, 0x3a, 0x63, 0x75, 0x3a, 0x63, 0x75, 0xd83c, 0xdde8, 0xd83c, 0xddfa, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x63, 0x75, 0x3a, 0x63, 0x75, 0x66, +0x6c, 0x61, 0x67, 0xd83c, 0xdde8, 0xd83c, 0xddfc, 0x3a, 0x63, 0x77, 0x3a, 0x63, +0x77, 0xd83c, 0xdde8, 0xd83c, 0xddfc, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x63, +0x77, 0x3a, 0x63, 0x77, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdde8, 0xd83c, 0xddfe, +0x3a, 0x63, 0x79, 0x3a, 0x63, 0x79, 0xd83c, 0xdde8, 0xd83c, 0xddfe, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x5f, 0x63, 0x79, 0x3a, 0x63, 0x79, 0x66, 0x6c, 0x61, +0x67, 0xd83c, 0xdde8, 0xd83c, 0xddff, 0x3a, 0x63, 0x7a, 0x3a, 0x63, 0x7a, 0xd83c, +0xdde8, 0xd83c, 0xddff, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x63, 0x7a, 0x3a, +0x63, 0x7a, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdde9, 0xd83c, 0xddf0, 0x3a, 0x64, +0x6b, 0x3a, 0x64, 0x6b, 0xd83c, 0xdde9, 0xd83c, 0xddf0, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x5f, 0x64, 0x6b, 0x3a, 0x64, 0x6b, 0x66, 0x6c, 0x61, 0x67, 0xd83c, +0xdde9, 0xd83c, 0xddef, 0x3a, 0x64, 0x6a, 0x3a, 0x64, 0x6a, 0xd83c, 0xdde9, 0xd83c, +0xddef, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x64, 0x6a, 0x3a, 0x64, 0x6a, +0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdde9, 0xd83c, 0xddf2, 0x3a, 0x64, 0x6d, 0x3a, +0x64, 0x6d, 0xd83c, 0xdde9, 0xd83c, 0xddf2, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, +0x64, 0x6d, 0x3a, 0x64, 0x6d, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xdde9, 0xd83c, +0xddf4, 0x3a, 0x64, 0x6f, 0x3a, 0x64, 0x6f, 0xd83c, 0xdde9, 0xd83c, 0xddf4, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x5f, 0x64, 0x6f, 0x3a, 0x64, 0x6f, 0x66, 0x6c, +0x61, 0x67, 0xd83c, 0xddea, 0xd83c, 0xdde8, 0x3a, 0x65, 0x63, 0x3a, 0x65, 0x63, +0xd83c, 0xddea, 0xd83c, 0xdde8, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x65, 0x63, +0x3a, 0x65, 0x63, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xddea, 0xd83c, 0xddec, 0x3a, +0x65, 0x67, 0x3a, 0x65, 0x67, 0xd83c, 0xddea, 0xd83c, 0xddec, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x5f, 0x65, 0x67, 0x3a, 0x65, 0x67, 0x66, 0x6c, 0x61, 0x67, +0xd83c, 0xddf8, 0xd83c, 0xddfb, 0x3a, 0x73, 0x76, 0x3a, 0x73, 0x76, 0xd83c, 0xddf8, +0xd83c, 0xddfb, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x73, 0x76, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x73, 0x76, 0xd83c, 0xddec, 0xd83c, 0xddf6, 0x3a, 0x67, 0x71, +0x3a, 0x67, 0x71, 0xd83c, 0xddec, 0xd83c, 0xddf6, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x5f, 0x67, 0x71, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x67, 0x71, 0xd83c, 0xddea, +0xd83c, 0xddf7, 0x3a, 0x65, 0x72, 0x3a, 0x65, 0x72, 0xd83c, 0xddea, 0xd83c, 0xddf7, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x65, 0x72, 0x3a, 0x65, 0x72, 0x66, +0x6c, 0x61, 0x67, 0xd83c, 0xddea, 0xd83c, 0xddea, 0x3a, 0x65, 0x65, 0x3a, 0x65, +0x65, 0xd83c, 0xddea, 0xd83c, 0xddea, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x65, +0x65, 0x3a, 0x65, 0x65, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xddea, 0xd83c, 0xddf9, +0x3a, 0x65, 0x74, 0x3a, 0x65, 0x74, 0xd83c, 0xddea, 0xd83c, 0xddf9, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x5f, 0x65, 0x74, 0x3a, 0x65, 0x74, 0x66, 0x6c, 0x61, +0x67, 0xd83c, 0xddea, 0xd83c, 0xddfa, 0x3a, 0x65, 0x75, 0x3a, 0x65, 0x75, 0xd83c, +0xddea, 0xd83c, 0xddfa, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x65, 0x75, 0x3a, +0x65, 0x75, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xddeb, 0xd83c, 0xddf0, 0x3a, 0x66, +0x6b, 0x3a, 0x66, 0x6b, 0xd83c, 0xddeb, 0xd83c, 0xddf0, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x5f, 0x66, 0x6b, 0x3a, 0x66, 0x6b, 0x66, 0x6c, 0x61, 0x67, 0xd83c, +0xddeb, 0xd83c, 0xddf4, 0x3a, 0x66, 0x6f, 0x3a, 0x66, 0x6f, 0xd83c, 0xddeb, 0xd83c, +0xddf4, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x66, 0x6f, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x66, 0x6f, 0xd83c, 0xddeb, 0xd83c, 0xddef, 0x3a, 0x66, 0x6a, 0x3a, +0x66, 0x6a, 0xd83c, 0xddeb, 0xd83c, 0xddef, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, +0x66, 0x6a, 0x3a, 0x66, 0x6a, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xddeb, 0xd83c, +0xddee, 0x3a, 0x66, 0x69, 0x3a, 0x66, 0x69, 0xd83c, 0xddeb, 0xd83c, 0xddee, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x5f, 0x66, 0x69, 0x3a, 0x66, 0x69, 0x66, 0x6c, +0x61, 0x67, 0xd83c, 0xddeb, 0xd83c, 0xddf7, 0x3a, 0x6d, 0x66, 0x3a, 0x6d, 0x66, +0xd83c, 0xddeb, 0xd83c, 0xddf7, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6d, 0x66, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6d, 0x66, 0xd83c, 0xddeb, 0xd83c, 0xddf7, 0x3a, +0x66, 0x72, 0x3a, 0x66, 0x72, 0xd83c, 0xddeb, 0xd83c, 0xddf7, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x5f, 0x66, 0x72, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x66, 0x72, +0xd83c, 0xddeb, 0xd83c, 0xddf7, 0x3a, 0x63, 0x70, 0x3a, 0x63, 0x70, 0xd83c, 0xddeb, +0xd83c, 0xddf7, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x63, 0x70, 0x3a, 0x63, +0x70, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xddec, 0xd83c, 0xddeb, 0x3a, 0x67, 0x66, +0x3a, 0x67, 0x66, 0xd83c, 0xddec, 0xd83c, 0xddeb, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x5f, 0x67, 0x66, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x67, 0x66, 0xd83c, 0xddf5, +0xd83c, 0xddeb, 0x3a, 0x70, 0x66, 0x3a, 0x70, 0x66, 0xd83c, 0xddf5, 0xd83c, 0xddeb, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x70, 0x66, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x70, 0x66, 0xd83c, 0xddf9, 0xd83c, 0xddeb, 0x3a, 0x74, 0x66, 0x3a, 0x74, +0x66, 0xd83c, 0xddf9, 0xd83c, 0xddeb, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x74, +0x66, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x74, 0x66, 0xd83c, 0xddec, 0xd83c, 0xdde6, +0x3a, 0x67, 0x61, 0x3a, 0x67, 0x61, 0xd83c, 0xddec, 0xd83c, 0xdde6, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x5f, 0x67, 0x61, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x67, +0x61, 0xd83c, 0xddec, 0xd83c, 0xddf2, 0x3a, 0x67, 0x6d, 0x3a, 0x67, 0x6d, 0xd83c, +0xddec, 0xd83c, 0xddf2, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x67, 0x6d, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x67, 0x6d, 0xd83c, 0xddec, 0xd83c, 0xddea, 0x3a, 0x67, +0x65, 0x3a, 0x67, 0x65, 0xd83c, 0xddec, 0xd83c, 0xddea, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x5f, 0x67, 0x65, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x67, 0x65, 0xd83c, +0xdde9, 0xd83c, 0xddea, 0x3a, 0x64, 0x65, 0x3a, 0x64, 0x65, 0xd83c, 0xdde9, 0xd83c, +0xddea, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x64, 0x65, 0x3a, 0x64, 0x65, +0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xddec, 0xd83c, 0xdded, 0x3a, 0x67, 0x68, 0x3a, +0x67, 0x68, 0xd83c, 0xddec, 0xd83c, 0xdded, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, +0x67, 0x68, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x67, 0x68, 0xd83c, 0xddec, 0xd83c, +0xddee, 0x3a, 0x67, 0x69, 0x3a, 0x67, 0x69, 0xd83c, 0xddec, 0xd83c, 0xddee, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x5f, 0x67, 0x69, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x67, 0x69, 0xd83c, 0xddec, 0xd83c, 0xddf7, 0x3a, 0x67, 0x72, 0x3a, 0x67, 0x72, +0xd83c, 0xddec, 0xd83c, 0xddf7, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x67, 0x72, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x67, 0x72, 0xd83c, 0xddec, 0xd83c, 0xddf1, 0x3a, +0x67, 0x6c, 0x3a, 0x67, 0x6c, 0xd83c, 0xddec, 0xd83c, 0xddf1, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x5f, 0x67, 0x6c, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x67, 0x6c, +0xd83c, 0xddec, 0xd83c, 0xdde9, 0x3a, 0x67, 0x64, 0x3a, 0x67, 0x64, 0xd83c, 0xddec, +0xd83c, 0xdde9, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x67, 0x64, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x67, 0x64, 0xd83c, 0xddec, 0xd83c, 0xddf5, 0x3a, 0x67, 0x70, +0x3a, 0x67, 0x70, 0xd83c, 0xddec, 0xd83c, 0xddf5, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x5f, 0x67, 0x70, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x67, 0x70, 0xd83c, 0xddec, +0xd83c, 0xddfa, 0x3a, 0x67, 0x75, 0x3a, 0x67, 0x75, 0xd83c, 0xddec, 0xd83c, 0xddfa, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x67, 0x75, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x67, 0x75, 0xd83c, 0xddec, 0xd83c, 0xddf9, 0x3a, 0x67, 0x74, 0x3a, 0x67, +0x74, 0xd83c, 0xddec, 0xd83c, 0xddf9, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x67, +0x74, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x67, 0x74, 0xd83c, 0xddec, 0xd83c, 0xddec, +0x3a, 0x67, 0x67, 0x3a, 0x67, 0x67, 0xd83c, 0xddec, 0xd83c, 0xddec, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x5f, 0x67, 0x67, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x67, +0x67, 0xd83c, 0xddec, 0xd83c, 0xddf3, 0x3a, 0x67, 0x6e, 0x3a, 0x67, 0x6e, 0xd83c, +0xddec, 0xd83c, 0xddf3, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x67, 0x6e, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x67, 0x6e, 0xd83c, 0xddec, 0xd83c, 0xddfc, 0x3a, 0x67, +0x77, 0x3a, 0x67, 0x77, 0xd83c, 0xddec, 0xd83c, 0xddfc, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x5f, 0x67, 0x77, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x67, 0x77, 0xd83c, +0xddec, 0xd83c, 0xddfe, 0x3a, 0x67, 0x79, 0x3a, 0x67, 0x79, 0xd83c, 0xddec, 0xd83c, +0xddfe, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x67, 0x79, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x67, 0x79, 0xd83c, 0xdded, 0xd83c, 0xddf9, 0x3a, 0x68, 0x74, 0x3a, +0x68, 0x74, 0xd83c, 0xdded, 0xd83c, 0xddf9, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, +0x68, 0x74, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x68, 0x74, 0xd83c, 0xdded, 0xd83c, +0xddf3, 0x3a, 0x68, 0x6e, 0x3a, 0x68, 0x6e, 0xd83c, 0xdded, 0xd83c, 0xddf3, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x5f, 0x68, 0x6e, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x68, 0x6e, 0xd83c, 0xdded, 0xd83c, 0xddf0, 0x3a, 0x68, 0x6b, 0x3a, 0x68, 0x6b, +0xd83c, 0xdded, 0xd83c, 0xddf0, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x68, 0x6b, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x68, 0x6b, 0xd83c, 0xdded, 0xd83c, 0xddfa, 0x3a, +0x68, 0x75, 0x3a, 0x68, 0x75, 0xd83c, 0xdded, 0xd83c, 0xddfa, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x5f, 0x68, 0x75, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x68, 0x75, +0xd83c, 0xddee, 0xd83c, 0xddf8, 0x3a, 0x69, 0x73, 0x3a, 0x69, 0x73, 0xd83c, 0xddee, +0xd83c, 0xddf8, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x69, 0x73, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x69, 0x73, 0xd83c, 0xddee, 0xd83c, 0xddf3, 0x3a, 0x69, 0x6e, +0x3a, 0x69, 0x6e, 0xd83c, 0xddee, 0xd83c, 0xddf3, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x5f, 0x69, 0x6e, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x69, 0x6e, 0xd83c, 0xddee, +0xd83c, 0xdde9, 0x3a, 0x69, 0x6e, 0x64, 0x6f, 0x6e, 0x65, 0x73, 0x69, 0x61, +0x3a, 0x69, 0x6e, 0x64, 0x6f, 0x6e, 0x65, 0x73, 0x69, 0x61, 0xd83c, 0xddee, +0xd83c, 0xdde9, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x69, 0x64, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x69, 0x64, 0xd83c, 0xddee, 0xd83c, 0xddf7, 0x3a, 0x69, 0x72, +0x3a, 0x69, 0x72, 0xd83c, 0xddee, 0xd83c, 0xddf7, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x5f, 0x69, 0x72, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x69, 0x72, 0xd83c, 0xddee, +0xd83c, 0xddf6, 0x3a, 0x69, 0x71, 0x3a, 0x69, 0x71, 0xd83c, 0xddee, 0xd83c, 0xddf6, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x69, 0x71, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x69, 0x71, 0xd83c, 0xddee, 0xd83c, 0xddea, 0x3a, 0x69, 0x65, 0x3a, 0x69, +0x65, 0xd83c, 0xddee, 0xd83c, 0xddea, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x69, +0x65, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x69, 0x65, 0xd83c, 0xddee, 0xd83c, 0xddf2, +0x3a, 0x69, 0x6d, 0x3a, 0x69, 0x6d, 0xd83c, 0xddee, 0xd83c, 0xddf2, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x5f, 0x69, 0x6d, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x69, +0x6d, 0xd83c, 0xddee, 0xd83c, 0xddf1, 0x3a, 0x69, 0x6c, 0x3a, 0x69, 0x6c, 0xd83c, +0xddee, 0xd83c, 0xddf1, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x69, 0x6c, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x69, 0x6c, 0xd83c, 0xddee, 0xd83c, 0xddf9, 0x3a, 0x69, +0x74, 0x3a, 0x69, 0x74, 0xd83c, 0xddee, 0xd83c, 0xddf9, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x5f, 0x69, 0x74, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x69, 0x74, 0xd83c, +0xddef, 0xd83c, 0xddf2, 0x3a, 0x6a, 0x6d, 0x3a, 0x6a, 0x6d, 0xd83c, 0xddef, 0xd83c, +0xddf2, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6a, 0x6d, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x6a, 0x6d, 0xd83c, 0xddef, 0xd83c, 0xddf5, 0x3a, 0x6a, 0x70, 0x3a, +0x6a, 0x70, 0xd83c, 0xddef, 0xd83c, 0xddf5, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, +0x6a, 0x70, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6a, 0x70, 0xd83c, 0xdf8c, 0x3a, +0x63, 0x72, 0x6f, 0x73, 0x73, 0x65, 0x64, 0x5f, 0x66, 0x6c, 0x61, 0x67, +0x73, 0x3a, 0x63, 0x72, 0x6f, 0x73, 0x73, 0x65, 0x64, 0x66, 0x6c, 0x61, +0x67, 0x73, 0xd83c, 0xddef, 0xd83c, 0xddea, 0x3a, 0x6a, 0x65, 0x3a, 0x6a, 0x65, +0xd83c, 0xddef, 0xd83c, 0xddea, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6a, 0x65, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6a, 0x65, 0xd83c, 0xddef, 0xd83c, 0xddf4, 0x3a, +0x6a, 0x6f, 0x3a, 0x6a, 0x6f, 0xd83c, 0xddef, 0xd83c, 0xddf4, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x5f, 0x6a, 0x6f, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6a, 0x6f, +0xd83c, 0xddf0, 0xd83c, 0xddff, 0x3a, 0x6b, 0x7a, 0x3a, 0x6b, 0x7a, 0xd83c, 0xddf0, +0xd83c, 0xddff, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x7a, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x6b, 0x7a, 0xd83c, 0xddf0, 0xd83c, 0xddea, 0x3a, 0x6b, 0x65, +0x3a, 0x6b, 0x65, 0xd83c, 0xddf0, 0xd83c, 0xddea, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x5f, 0x6b, 0x65, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6b, 0x65, 0xd83c, 0xddf0, +0xd83c, 0xddee, 0x3a, 0x6b, 0x69, 0x3a, 0x6b, 0x69, 0xd83c, 0xddf0, 0xd83c, 0xddee, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x69, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x6b, 0x69, 0xd83c, 0xddfd, 0xd83c, 0xddf0, 0x3a, 0x78, 0x6b, 0x3a, 0x78, +0x6b, 0xd83c, 0xddfd, 0xd83c, 0xddf0, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x78, +0x6b, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x78, 0x6b, 0xd83c, 0xddf0, 0xd83c, 0xddfc, +0x3a, 0x6b, 0x77, 0x3a, 0x6b, 0x77, 0xd83c, 0xddf0, 0xd83c, 0xddfc, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x77, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6b, +0x77, 0xd83c, 0xddf0, 0xd83c, 0xddec, 0x3a, 0x6b, 0x67, 0x3a, 0x6b, 0x67, 0xd83c, +0xddf0, 0xd83c, 0xddec, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x67, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x6b, 0x67, 0xd83c, 0xddf1, 0xd83c, 0xdde6, 0x3a, 0x6c, +0x61, 0x3a, 0x6c, 0x61, 0xd83c, 0xddf1, 0xd83c, 0xdde6, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x5f, 0x6c, 0x61, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6c, 0x61, 0xd83c, +0xddf1, 0xd83c, 0xddfb, 0x3a, 0x6c, 0x76, 0x3a, 0x6c, 0x76, 0xd83c, 0xddf1, 0xd83c, +0xddfb, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6c, 0x76, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x6c, 0x76, 0xd83c, 0xddf1, 0xd83c, 0xdde7, 0x3a, 0x6c, 0x62, 0x3a, +0x6c, 0x62, 0xd83c, 0xddf1, 0xd83c, 0xdde7, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, +0x6c, 0x62, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6c, 0x62, 0xd83c, 0xddf1, 0xd83c, +0xddf8, 0x3a, 0x6c, 0x73, 0x3a, 0x6c, 0x73, 0xd83c, 0xddf1, 0xd83c, 0xddf8, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6c, 0x73, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x6c, 0x73, 0xd83c, 0xddf1, 0xd83c, 0xddf7, 0x3a, 0x6c, 0x72, 0x3a, 0x6c, 0x72, +0xd83c, 0xddf1, 0xd83c, 0xddf7, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6c, 0x72, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6c, 0x72, 0xd83c, 0xddf1, 0xd83c, 0xddfe, 0x3a, +0x6c, 0x79, 0x3a, 0x6c, 0x79, 0xd83c, 0xddf1, 0xd83c, 0xddfe, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x5f, 0x6c, 0x79, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6c, 0x79, +0xd83c, 0xddf1, 0xd83c, 0xddee, 0x3a, 0x6c, 0x69, 0x3a, 0x6c, 0x69, 0xd83c, 0xddf1, +0xd83c, 0xddee, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6c, 0x69, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x6c, 0x69, 0xd83c, 0xddf1, 0xd83c, 0xddf9, 0x3a, 0x6c, 0x74, +0x3a, 0x6c, 0x74, 0xd83c, 0xddf1, 0xd83c, 0xddf9, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x5f, 0x6c, 0x74, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6c, 0x74, 0xd83c, 0xddf1, +0xd83c, 0xddfa, 0x3a, 0x6c, 0x75, 0x3a, 0x6c, 0x75, 0xd83c, 0xddf1, 0xd83c, 0xddfa, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6c, 0x75, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x6c, 0x75, 0xd83c, 0xddf2, 0xd83c, 0xddf4, 0x3a, 0x6d, 0x6f, 0x3a, 0x6d, +0x6f, 0xd83c, 0xddf2, 0xd83c, 0xddf4, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6d, +0x6f, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6d, 0x6f, 0xd83c, 0xddf2, 0xd83c, 0xddf0, +0x3a, 0x6d, 0x6b, 0x3a, 0x6d, 0x6b, 0xd83c, 0xddf2, 0xd83c, 0xddf0, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x5f, 0x6d, 0x6b, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6d, +0x6b, 0xd83c, 0xddf2, 0xd83c, 0xddec, 0x3a, 0x6d, 0x67, 0x3a, 0x6d, 0x67, 0xd83c, +0xddf2, 0xd83c, 0xddec, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6d, 0x67, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x6d, 0x67, 0xd83c, 0xddf2, 0xd83c, 0xddfc, 0x3a, 0x6d, +0x77, 0x3a, 0x6d, 0x77, 0xd83c, 0xddf2, 0xd83c, 0xddfc, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x5f, 0x6d, 0x77, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6d, 0x77, 0xd83c, +0xddf2, 0xd83c, 0xddfe, 0x3a, 0x6d, 0x79, 0x3a, 0x6d, 0x79, 0xd83c, 0xddf2, 0xd83c, +0xddfe, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6d, 0x79, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x6d, 0x79, 0xd83c, 0xddf2, 0xd83c, 0xddfb, 0x3a, 0x6d, 0x76, 0x3a, +0x6d, 0x76, 0xd83c, 0xddf2, 0xd83c, 0xddfb, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, +0x6d, 0x76, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6d, 0x76, 0xd83c, 0xddf2, 0xd83c, +0xddf1, 0x3a, 0x6d, 0x6c, 0x3a, 0x6d, 0x6c, 0xd83c, 0xddf2, 0xd83c, 0xddf1, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6d, 0x6c, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x6d, 0x6c, 0xd83c, 0xddf2, 0xd83c, 0xddf9, 0x3a, 0x6d, 0x74, 0x3a, 0x6d, 0x74, +0xd83c, 0xddf2, 0xd83c, 0xddf9, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6d, 0x74, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6d, 0x74, 0xd83c, 0xddf2, 0xd83c, 0xdded, 0x3a, +0x6d, 0x68, 0x3a, 0x6d, 0x68, 0xd83c, 0xddf2, 0xd83c, 0xdded, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x5f, 0x6d, 0x68, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6d, 0x68, +0xd83c, 0xddf2, 0xd83c, 0xddf6, 0x3a, 0x6d, 0x71, 0x3a, 0x6d, 0x71, 0xd83c, 0xddf2, +0xd83c, 0xddf6, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6d, 0x71, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x6d, 0x71, 0xd83c, 0xddf2, 0xd83c, 0xddf7, 0x3a, 0x6d, 0x72, +0x3a, 0x6d, 0x72, 0xd83c, 0xddf2, 0xd83c, 0xddf7, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x5f, 0x6d, 0x72, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6d, 0x72, 0xd83c, 0xddf2, +0xd83c, 0xddfa, 0x3a, 0x6d, 0x75, 0x3a, 0x6d, 0x75, 0xd83c, 0xddf2, 0xd83c, 0xddfa, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6d, 0x75, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x6d, 0x75, 0xd83c, 0xddfe, 0xd83c, 0xddf9, 0x3a, 0x79, 0x74, 0x3a, 0x79, +0x74, 0xd83c, 0xddfe, 0xd83c, 0xddf9, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x79, +0x74, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x79, 0x74, 0xd83c, 0xddf2, 0xd83c, 0xddfd, +0x3a, 0x6d, 0x78, 0x3a, 0x6d, 0x78, 0xd83c, 0xddf2, 0xd83c, 0xddfd, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x5f, 0x6d, 0x78, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6d, +0x78, 0xd83c, 0xddeb, 0xd83c, 0xddf2, 0x3a, 0x66, 0x6d, 0x3a, 0x66, 0x6d, 0xd83c, +0xddeb, 0xd83c, 0xddf2, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x66, 0x6d, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x66, 0x6d, 0xd83c, 0xddf2, 0xd83c, 0xdde9, 0x3a, 0x6d, +0x64, 0x3a, 0x6d, 0x64, 0xd83c, 0xddf2, 0xd83c, 0xdde9, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x5f, 0x6d, 0x64, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6d, 0x64, 0xd83c, +0xddf2, 0xd83c, 0xdde8, 0x3a, 0x6d, 0x63, 0x3a, 0x6d, 0x63, 0xd83c, 0xddf2, 0xd83c, +0xdde8, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6d, 0x63, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x6d, 0x63, 0xd83c, 0xddf2, 0xd83c, 0xddf3, 0x3a, 0x6d, 0x6e, 0x3a, +0x6d, 0x6e, 0xd83c, 0xddf2, 0xd83c, 0xddf3, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, +0x6d, 0x6e, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6d, 0x6e, 0xd83c, 0xddf2, 0xd83c, +0xddea, 0x3a, 0x6d, 0x65, 0x3a, 0x6d, 0x65, 0xd83c, 0xddf2, 0xd83c, 0xddea, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6d, 0x65, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x6d, 0x65, 0xd83c, 0xddf2, 0xd83c, 0xddf8, 0x3a, 0x6d, 0x73, 0x3a, 0x6d, 0x73, +0xd83c, 0xddf2, 0xd83c, 0xddf8, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6d, 0x73, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6d, 0x73, 0xd83c, 0xddf2, 0xd83c, 0xdde6, 0x3a, +0x6d, 0x61, 0x3a, 0x6d, 0x61, 0xd83c, 0xddf2, 0xd83c, 0xdde6, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x5f, 0x6d, 0x61, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6d, 0x61, +0xd83c, 0xddf2, 0xd83c, 0xddff, 0x3a, 0x6d, 0x7a, 0x3a, 0x6d, 0x7a, 0xd83c, 0xddf2, +0xd83c, 0xddff, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6d, 0x7a, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x6d, 0x7a, 0xd83c, 0xddf2, 0xd83c, 0xddf2, 0x3a, 0x6d, 0x6d, +0x3a, 0x6d, 0x6d, 0xd83c, 0xddf2, 0xd83c, 0xddf2, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x5f, 0x6d, 0x6d, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6d, 0x6d, 0xd83c, 0xddf3, +0xd83c, 0xdde6, 0x3a, 0x6e, 0x61, 0x3a, 0x6e, 0x61, 0xd83c, 0xddf3, 0xd83c, 0xdde6, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6e, 0x61, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x6e, 0x61, 0xd83c, 0xddf3, 0xd83c, 0xddf7, 0x3a, 0x6e, 0x72, 0x3a, 0x6e, +0x72, 0xd83c, 0xddf3, 0xd83c, 0xddf7, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6e, +0x72, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6e, 0x72, 0xd83c, 0xddf3, 0xd83c, 0xddf5, +0x3a, 0x6e, 0x70, 0x3a, 0x6e, 0x70, 0xd83c, 0xddf3, 0xd83c, 0xddf5, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x5f, 0x6e, 0x70, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6e, +0x70, 0xd83c, 0xddf3, 0xd83c, 0xddf1, 0x3a, 0x6e, 0x6c, 0x3a, 0x6e, 0x6c, 0xd83c, +0xddf3, 0xd83c, 0xddf1, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6e, 0x6c, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x6e, 0x6c, 0xd83c, 0xddf3, 0xd83c, 0xdde8, 0x3a, 0x6e, +0x63, 0x3a, 0x6e, 0x63, 0xd83c, 0xddf3, 0xd83c, 0xdde8, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x5f, 0x6e, 0x63, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6e, 0x63, 0xd83c, +0xddf3, 0xd83c, 0xddff, 0x3a, 0x6e, 0x7a, 0x3a, 0x6e, 0x7a, 0xd83c, 0xddf3, 0xd83c, +0xddff, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6e, 0x7a, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x6e, 0x7a, 0xd83c, 0xddf3, 0xd83c, 0xddee, 0x3a, 0x6e, 0x69, 0x3a, +0x6e, 0x69, 0xd83c, 0xddf3, 0xd83c, 0xddee, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, +0x6e, 0x69, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6e, 0x69, 0xd83c, 0xddf3, 0xd83c, +0xddea, 0x3a, 0x6e, 0x65, 0x3a, 0x6e, 0x65, 0xd83c, 0xddf3, 0xd83c, 0xddea, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6e, 0x65, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x6e, 0x65, 0xd83c, 0xddf3, 0xd83c, 0xddec, 0x3a, 0x6e, 0x69, 0x67, 0x65, 0x72, +0x69, 0x61, 0x3a, 0x6e, 0x69, 0x67, 0x65, 0x72, 0x69, 0x61, 0xd83c, 0xddf3, +0xd83c, 0xddec, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6e, 0x67, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x6e, 0x67, 0xd83c, 0xddf3, 0xd83c, 0xddfa, 0x3a, 0x6e, 0x75, +0x3a, 0x6e, 0x75, 0xd83c, 0xddf3, 0xd83c, 0xddfa, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x5f, 0x6e, 0x75, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6e, 0x75, 0xd83c, 0xddf3, +0xd83c, 0xddeb, 0x3a, 0x6e, 0x66, 0x3a, 0x6e, 0x66, 0xd83c, 0xddf3, 0xd83c, 0xddeb, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6e, 0x66, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x6e, 0x66, 0xd83c, 0xddf0, 0xd83c, 0xddf5, 0x3a, 0x6b, 0x70, 0x3a, 0x6b, +0x70, 0xd83c, 0xddf0, 0xd83c, 0xddf5, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, +0x70, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6b, 0x70, 0xd83c, 0xddf2, 0xd83c, 0xddf5, +0x3a, 0x6d, 0x70, 0x3a, 0x6d, 0x70, 0xd83c, 0xddf2, 0xd83c, 0xddf5, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x5f, 0x6d, 0x70, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6d, +0x70, 0xd83c, 0xddf3, 0xd83c, 0xddf4, 0x3a, 0x73, 0x6a, 0x3a, 0x73, 0x6a, 0xd83c, +0xddf3, 0xd83c, 0xddf4, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x73, 0x6a, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x73, 0x6a, 0xd83c, 0xddf3, 0xd83c, 0xddf4, 0x3a, 0x6e, +0x6f, 0x3a, 0x6e, 0x6f, 0xd83c, 0xddf3, 0xd83c, 0xddf4, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x5f, 0x6e, 0x6f, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6e, 0x6f, 0xd83c, +0xddf3, 0xd83c, 0xddf4, 0x3a, 0x62, 0x76, 0x3a, 0x62, 0x76, 0xd83c, 0xddf3, 0xd83c, +0xddf4, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x62, 0x76, 0x3a, 0x62, 0x76, +0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xddf4, 0xd83c, 0xddf2, 0x3a, 0x6f, 0x6d, 0x3a, +0x6f, 0x6d, 0xd83c, 0xddf4, 0xd83c, 0xddf2, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, +0x6f, 0x6d, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6f, 0x6d, 0xd83c, 0xddf5, 0xd83c, +0xddf0, 0x3a, 0x70, 0x6b, 0x3a, 0x70, 0x6b, 0xd83c, 0xddf5, 0xd83c, 0xddf0, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x5f, 0x70, 0x6b, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x70, 0x6b, 0xd83c, 0xddf5, 0xd83c, 0xddfc, 0x3a, 0x70, 0x77, 0x3a, 0x70, 0x77, +0xd83c, 0xddf5, 0xd83c, 0xddfc, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x70, 0x77, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x70, 0x77, 0xd83c, 0xddf5, 0xd83c, 0xddf8, 0x3a, +0x70, 0x73, 0x3a, 0x70, 0x73, 0xd83c, 0xddf5, 0xd83c, 0xddf8, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x5f, 0x70, 0x73, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x70, 0x73, +0xd83c, 0xddf5, 0xd83c, 0xdde6, 0x3a, 0x70, 0x61, 0x3a, 0x70, 0x61, 0xd83c, 0xddf5, +0xd83c, 0xdde6, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x70, 0x61, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x70, 0x61, 0xd83c, 0xddf5, 0xd83c, 0xddec, 0x3a, 0x70, 0x67, +0x3a, 0x70, 0x67, 0xd83c, 0xddf5, 0xd83c, 0xddec, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x5f, 0x70, 0x67, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x70, 0x67, 0xd83c, 0xddf5, +0xd83c, 0xddfe, 0x3a, 0x70, 0x79, 0x3a, 0x70, 0x79, 0xd83c, 0xddf5, 0xd83c, 0xddfe, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x70, 0x79, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x70, 0x79, 0xd83c, 0xddf5, 0xd83c, 0xddea, 0x3a, 0x70, 0x65, 0x3a, 0x70, +0x65, 0xd83c, 0xddf5, 0xd83c, 0xddea, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x70, +0x65, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x70, 0x65, 0xd83c, 0xddf5, 0xd83c, 0xdded, +0x3a, 0x70, 0x68, 0x3a, 0x70, 0x68, 0xd83c, 0xddf5, 0xd83c, 0xdded, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x5f, 0x70, 0x68, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x70, +0x68, 0xd83c, 0xddf5, 0xd83c, 0xddf3, 0x3a, 0x70, 0x6e, 0x3a, 0x70, 0x6e, 0xd83c, +0xddf5, 0xd83c, 0xddf3, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x70, 0x6e, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x70, 0x6e, 0xd83c, 0xddf5, 0xd83c, 0xddf1, 0x3a, 0x70, +0x6c, 0x3a, 0x70, 0x6c, 0xd83c, 0xddf5, 0xd83c, 0xddf1, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x5f, 0x70, 0x6c, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x70, 0x6c, 0xd83c, +0xddf5, 0xd83c, 0xddf9, 0x3a, 0x70, 0x74, 0x3a, 0x70, 0x74, 0xd83c, 0xddf5, 0xd83c, +0xddf9, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x70, 0x74, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x70, 0x74, 0xd83c, 0xddf5, 0xd83c, 0xddf7, 0x3a, 0x70, 0x72, 0x3a, +0x70, 0x72, 0xd83c, 0xddf5, 0xd83c, 0xddf7, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, +0x70, 0x72, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x70, 0x72, 0xd83c, 0xddf6, 0xd83c, +0xdde6, 0x3a, 0x71, 0x61, 0x3a, 0x71, 0x61, 0xd83c, 0xddf6, 0xd83c, 0xdde6, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x5f, 0x71, 0x61, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x71, 0x61, 0xd83c, 0xddf7, 0xd83c, 0xddea, 0x3a, 0x72, 0x65, 0x3a, 0x72, 0x65, +0xd83c, 0xddf7, 0xd83c, 0xddea, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x72, 0x65, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x72, 0x65, 0xd83c, 0xddf7, 0xd83c, 0xddf4, 0x3a, +0x72, 0x6f, 0x3a, 0x72, 0x6f, 0xd83c, 0xddf7, 0xd83c, 0xddf4, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x5f, 0x72, 0x6f, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x72, 0x6f, +0xd83c, 0xddf7, 0xd83c, 0xddfa, 0x3a, 0x72, 0x75, 0x3a, 0x72, 0x75, 0xd83c, 0xddf7, +0xd83c, 0xddfa, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x72, 0x75, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x72, 0x75, 0xd83c, 0xddf7, 0xd83c, 0xddfc, 0x3a, 0x72, 0x77, +0x3a, 0x72, 0x77, 0xd83c, 0xddf7, 0xd83c, 0xddfc, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x5f, 0x72, 0x77, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x72, 0x77, 0xd83c, 0xddfc, +0xd83c, 0xddf8, 0x3a, 0x77, 0x73, 0x3a, 0x77, 0x73, 0xd83c, 0xddfc, 0xd83c, 0xddf8, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x77, 0x73, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x77, 0x73, 0xd83c, 0xddf8, 0xd83c, 0xddf2, 0x3a, 0x73, 0x6d, 0x3a, 0x73, +0x6d, 0xd83c, 0xddf8, 0xd83c, 0xddf2, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x73, +0x6d, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x6d, 0xd83c, 0xddf8, 0xd83c, 0xddf9, +0x3a, 0x73, 0x74, 0x3a, 0x73, 0x74, 0xd83c, 0xddf8, 0xd83c, 0xddf9, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x5f, 0x73, 0x74, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x73, +0x74, 0xd83c, 0xddf8, 0xd83c, 0xdde6, 0x3a, 0x73, 0x61, 0x75, 0x64, 0x69, 0x3a, +0x73, 0x61, 0x75, 0x64, 0x69, 0xd83c, 0xddf8, 0xd83c, 0xdde6, 0x3a, 0x73, 0x61, +0x75, 0x64, 0x69, 0x61, 0x72, 0x61, 0x62, 0x69, 0x61, 0x3a, 0x73, 0x61, +0x75, 0x64, 0x69, 0x61, 0x72, 0x61, 0x62, 0x69, 0x61, 0xd83c, 0xddf8, 0xd83c, +0xdde6, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x73, 0x61, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x73, 0x61, 0xd83c, 0xddf8, 0xd83c, 0xddf3, 0x3a, 0x73, 0x6e, 0x3a, +0x73, 0x6e, 0xd83c, 0xddf8, 0xd83c, 0xddf3, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, +0x73, 0x6e, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x6e, 0xd83c, 0xddf7, 0xd83c, +0xddf8, 0x3a, 0x72, 0x73, 0x3a, 0x72, 0x73, 0xd83c, 0xddf7, 0xd83c, 0xddf8, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x5f, 0x72, 0x73, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x72, 0x73, 0xd83c, 0xddf8, 0xd83c, 0xdde8, 0x3a, 0x73, 0x63, 0x3a, 0x73, 0x63, +0xd83c, 0xddf8, 0xd83c, 0xdde8, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x73, 0x63, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x63, 0xd83c, 0xddf8, 0xd83c, 0xddf1, 0x3a, +0x73, 0x6c, 0x3a, 0x73, 0x6c, 0xd83c, 0xddf8, 0xd83c, 0xddf1, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x5f, 0x73, 0x6c, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x6c, +0xd83c, 0xddf8, 0xd83c, 0xddec, 0x3a, 0x73, 0x67, 0x3a, 0x73, 0x67, 0xd83c, 0xddf8, +0xd83c, 0xddec, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x73, 0x67, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x73, 0x67, 0xd83c, 0xddf8, 0xd83c, 0xddfd, 0x3a, 0x73, 0x78, +0x3a, 0x73, 0x78, 0xd83c, 0xddf8, 0xd83c, 0xddfd, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x5f, 0x73, 0x78, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x78, 0xd83c, 0xddf8, +0xd83c, 0xddf0, 0x3a, 0x73, 0x6b, 0x3a, 0x73, 0x6b, 0xd83c, 0xddf8, 0xd83c, 0xddf0, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x73, 0x6b, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x73, 0x6b, 0xd83c, 0xddf8, 0xd83c, 0xddee, 0x3a, 0x73, 0x69, 0x3a, 0x73, +0x69, 0xd83c, 0xddf8, 0xd83c, 0xddee, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x73, +0x69, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x69, 0xd83c, 0xddec, 0xd83c, 0xddf8, +0x3a, 0x67, 0x73, 0x3a, 0x67, 0x73, 0xd83c, 0xddec, 0xd83c, 0xddf8, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x5f, 0x67, 0x73, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x67, +0x73, 0xd83c, 0xddf8, 0xd83c, 0xdde7, 0x3a, 0x73, 0x62, 0x3a, 0x73, 0x62, 0xd83c, +0xddf8, 0xd83c, 0xdde7, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x73, 0x62, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x73, 0x62, 0xd83c, 0xddf8, 0xd83c, 0xddf4, 0x3a, 0x73, +0x6f, 0x3a, 0x73, 0x6f, 0xd83c, 0xddf8, 0xd83c, 0xddf4, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x5f, 0x73, 0x6f, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x6f, 0xd83c, +0xddff, 0xd83c, 0xdde6, 0x3a, 0x7a, 0x61, 0x3a, 0x7a, 0x61, 0xd83c, 0xddff, 0xd83c, +0xdde6, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x7a, 0x61, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x7a, 0x61, 0xd83c, 0xddf0, 0xd83c, 0xddf7, 0x3a, 0x6b, 0x72, 0x3a, +0x6b, 0x72, 0xd83c, 0xddf0, 0xd83c, 0xddf7, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, +0x6b, 0x72, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6b, 0x72, 0xd83c, 0xddf8, 0xd83c, +0xddf8, 0x3a, 0x73, 0x73, 0x3a, 0x73, 0x73, 0xd83c, 0xddf8, 0xd83c, 0xddf8, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x5f, 0x73, 0x73, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x73, 0x73, 0xd83c, 0xddea, 0xd83c, 0xddf8, 0x3a, 0x65, 0x73, 0x3a, 0x65, 0x73, +0xd83c, 0xddea, 0xd83c, 0xddf8, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x65, 0x73, +0x3a, 0x65, 0x73, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xddea, 0xd83c, 0xddf8, 0x3a, +0x65, 0x61, 0x3a, 0x65, 0x61, 0xd83c, 0xddea, 0xd83c, 0xddf8, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x5f, 0x65, 0x61, 0x3a, 0x65, 0x61, 0x66, 0x6c, 0x61, 0x67, +0xd83c, 0xddf1, 0xd83c, 0xddf0, 0x3a, 0x6c, 0x6b, 0x3a, 0x6c, 0x6b, 0xd83c, 0xddf1, +0xd83c, 0xddf0, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6c, 0x6b, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x6c, 0x6b, 0xd83c, 0xdde7, 0xd83c, 0xddf1, 0x3a, 0x62, 0x6c, +0x3a, 0x62, 0x6c, 0xd83c, 0xdde7, 0xd83c, 0xddf1, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x5f, 0x62, 0x6c, 0x3a, 0x62, 0x6c, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xddf8, +0xd83c, 0xdded, 0x3a, 0x74, 0x61, 0x3a, 0x74, 0x61, 0xd83c, 0xddf8, 0xd83c, 0xdded, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x74, 0x61, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x74, 0x61, 0xd83c, 0xddf8, 0xd83c, 0xdded, 0x3a, 0x73, 0x68, 0x3a, 0x73, +0x68, 0xd83c, 0xddf8, 0xd83c, 0xdded, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x73, +0x68, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x68, 0xd83c, 0xddf8, 0xd83c, 0xdded, +0x3a, 0x61, 0x63, 0x3a, 0x61, 0x63, 0xd83c, 0xddf8, 0xd83c, 0xdded, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x5f, 0x61, 0x63, 0x3a, 0x61, 0x63, 0x66, 0x6c, 0x61, +0x67, 0xd83c, 0xddf0, 0xd83c, 0xddf3, 0x3a, 0x6b, 0x6e, 0x3a, 0x6b, 0x6e, 0xd83c, +0xddf0, 0xd83c, 0xddf3, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x6e, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x6b, 0x6e, 0xd83c, 0xddf1, 0xd83c, 0xdde8, 0x3a, 0x6c, +0x63, 0x3a, 0x6c, 0x63, 0xd83c, 0xddf1, 0xd83c, 0xdde8, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x5f, 0x6c, 0x63, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x6c, 0x63, 0xd83c, +0xddf5, 0xd83c, 0xddf2, 0x3a, 0x70, 0x6d, 0x3a, 0x70, 0x6d, 0xd83c, 0xddf5, 0xd83c, +0xddf2, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x70, 0x6d, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x70, 0x6d, 0xd83c, 0xddfb, 0xd83c, 0xdde8, 0x3a, 0x76, 0x63, 0x3a, +0x76, 0x63, 0xd83c, 0xddfb, 0xd83c, 0xdde8, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, +0x76, 0x63, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x76, 0x63, 0xd83c, 0xddf8, 0xd83c, +0xdde9, 0x3a, 0x73, 0x64, 0x3a, 0x73, 0x64, 0xd83c, 0xddf8, 0xd83c, 0xdde9, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x5f, 0x73, 0x64, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x73, 0x64, 0xd83c, 0xddf8, 0xd83c, 0xddf7, 0x3a, 0x73, 0x72, 0x3a, 0x73, 0x72, +0xd83c, 0xddf8, 0xd83c, 0xddf7, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x73, 0x72, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x72, 0xd83c, 0xddf8, 0xd83c, 0xddff, 0x3a, +0x73, 0x7a, 0x3a, 0x73, 0x7a, 0xd83c, 0xddf8, 0xd83c, 0xddff, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x5f, 0x73, 0x7a, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x7a, +0xd83c, 0xddf8, 0xd83c, 0xddea, 0x3a, 0x73, 0x65, 0x3a, 0x73, 0x65, 0xd83c, 0xddf8, +0xd83c, 0xddea, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x73, 0x65, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x73, 0x65, 0xd83c, 0xdde8, 0xd83c, 0xdded, 0x3a, 0x63, 0x68, +0x3a, 0x63, 0x68, 0xd83c, 0xdde8, 0xd83c, 0xdded, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x5f, 0x63, 0x68, 0x3a, 0x63, 0x68, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xddf8, +0xd83c, 0xddfe, 0x3a, 0x73, 0x79, 0x3a, 0x73, 0x79, 0xd83c, 0xddf8, 0xd83c, 0xddfe, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x73, 0x79, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x73, 0x79, 0xd83c, 0xddf9, 0xd83c, 0xddfc, 0x3a, 0x74, 0x77, 0x3a, 0x74, +0x77, 0xd83c, 0xddf9, 0xd83c, 0xddfc, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x74, +0x77, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x74, 0x77, 0xd83c, 0xddf9, 0xd83c, 0xddef, +0x3a, 0x74, 0x6a, 0x3a, 0x74, 0x6a, 0xd83c, 0xddf9, 0xd83c, 0xddef, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x5f, 0x74, 0x6a, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x74, +0x6a, 0xd83c, 0xddf9, 0xd83c, 0xddff, 0x3a, 0x74, 0x7a, 0x3a, 0x74, 0x7a, 0xd83c, +0xddf9, 0xd83c, 0xddff, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x74, 0x7a, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x74, 0x7a, 0xd83c, 0xddf9, 0xd83c, 0xdded, 0x3a, 0x74, +0x68, 0x3a, 0x74, 0x68, 0xd83c, 0xddf9, 0xd83c, 0xdded, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x5f, 0x74, 0x68, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x74, 0x68, 0xd83c, +0xddf9, 0xd83c, 0xddf1, 0x3a, 0x74, 0x6c, 0x3a, 0x74, 0x6c, 0xd83c, 0xddf9, 0xd83c, +0xddf1, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x74, 0x6c, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x74, 0x6c, 0xd83c, 0xddf9, 0xd83c, 0xddec, 0x3a, 0x74, 0x67, 0x3a, +0x74, 0x67, 0xd83c, 0xddf9, 0xd83c, 0xddec, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, +0x74, 0x67, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x74, 0x67, 0xd83c, 0xddf9, 0xd83c, +0xddf0, 0x3a, 0x74, 0x6b, 0x3a, 0x74, 0x6b, 0xd83c, 0xddf9, 0xd83c, 0xddf0, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x5f, 0x74, 0x6b, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x74, 0x6b, 0xd83c, 0xddf9, 0xd83c, 0xddf4, 0x3a, 0x74, 0x6f, 0x3a, 0x74, 0x6f, +0xd83c, 0xddf9, 0xd83c, 0xddf4, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x74, 0x6f, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x74, 0x6f, 0xd83c, 0xddf9, 0xd83c, 0xddf9, 0x3a, +0x74, 0x74, 0x3a, 0x74, 0x74, 0xd83c, 0xddf9, 0xd83c, 0xddf9, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x5f, 0x74, 0x74, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x74, 0x74, +0xd83c, 0xddf9, 0xd83c, 0xddf3, 0x3a, 0x74, 0x6e, 0x3a, 0x74, 0x6e, 0xd83c, 0xddf9, +0xd83c, 0xddf3, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x74, 0x6e, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x74, 0x6e, 0xd83c, 0xddf9, 0xd83c, 0xddf7, 0x3a, 0x74, 0x72, +0x3a, 0x74, 0x72, 0xd83c, 0xddf9, 0xd83c, 0xddf7, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x5f, 0x74, 0x72, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x74, 0x72, 0xd83c, 0xddf9, +0xd83c, 0xddf2, 0x3a, 0x74, 0x75, 0x72, 0x6b, 0x6d, 0x65, 0x6e, 0x69, 0x73, +0x74, 0x61, 0x6e, 0x3a, 0x74, 0x75, 0x72, 0x6b, 0x6d, 0x65, 0x6e, 0x69, +0x73, 0x74, 0x61, 0x6e, 0xd83c, 0xddf9, 0xd83c, 0xddf2, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x5f, 0x74, 0x6d, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x74, 0x6d, 0xd83c, +0xddf9, 0xd83c, 0xdde8, 0x3a, 0x74, 0x63, 0x3a, 0x74, 0x63, 0xd83c, 0xddf9, 0xd83c, +0xdde8, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x74, 0x63, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x74, 0x63, 0xd83c, 0xddf9, 0xd83c, 0xddfb, 0x3a, 0x74, 0x75, 0x76, +0x61, 0x6c, 0x75, 0x3a, 0x74, 0x75, 0x76, 0x61, 0x6c, 0x75, 0xd83c, 0xddf9, +0xd83c, 0xddfb, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x74, 0x76, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x74, 0x76, 0xd83c, 0xddfb, 0xd83c, 0xddee, 0x3a, 0x76, 0x69, +0x3a, 0x76, 0x69, 0xd83c, 0xddfb, 0xd83c, 0xddee, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x5f, 0x76, 0x69, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x76, 0x69, 0xd83c, 0xddfa, +0xd83c, 0xddec, 0x3a, 0x75, 0x67, 0x3a, 0x75, 0x67, 0xd83c, 0xddfa, 0xd83c, 0xddec, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x75, 0x67, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x75, 0x67, 0xd83c, 0xddfa, 0xd83c, 0xdde6, 0x3a, 0x75, 0x61, 0x3a, 0x75, +0x61, 0xd83c, 0xddfa, 0xd83c, 0xdde6, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x75, +0x61, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x75, 0x61, 0xd83c, 0xdde6, 0xd83c, 0xddea, +0x3a, 0x61, 0x65, 0x3a, 0x61, 0x65, 0xd83c, 0xdde6, 0xd83c, 0xddea, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x5f, 0x61, 0x65, 0x3a, 0x61, 0x65, 0x66, 0x6c, 0x61, +0x67, 0xd83c, 0xddec, 0xd83c, 0xdde7, 0x3a, 0x67, 0x62, 0x3a, 0x67, 0x62, 0xd83c, +0xddec, 0xd83c, 0xdde7, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x67, 0x62, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x67, 0x62, 0xd83c, 0xddfa, 0xd83c, 0xddf8, 0x3a, 0x75, +0x73, 0x3a, 0x75, 0x73, 0xd83c, 0xddfa, 0xd83c, 0xddf8, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x5f, 0x75, 0x73, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x75, 0x73, 0xd83c, +0xddfa, 0xd83c, 0xddf8, 0x3a, 0x75, 0x6d, 0x3a, 0x75, 0x6d, 0xd83c, 0xddfa, 0xd83c, +0xddf8, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x75, 0x6d, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x75, 0x6d, 0xd83c, 0xddfa, 0xd83c, 0xddfe, 0x3a, 0x75, 0x79, 0x3a, +0x75, 0x79, 0xd83c, 0xddfa, 0xd83c, 0xddfe, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, +0x75, 0x79, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x75, 0x79, 0xd83c, 0xddfa, 0xd83c, +0xddff, 0x3a, 0x75, 0x7a, 0x3a, 0x75, 0x7a, 0xd83c, 0xddfa, 0xd83c, 0xddff, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x5f, 0x75, 0x7a, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x75, 0x7a, 0xd83c, 0xddfb, 0xd83c, 0xddfa, 0x3a, 0x76, 0x75, 0x3a, 0x76, 0x75, +0xd83c, 0xddfb, 0xd83c, 0xddfa, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x76, 0x75, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x76, 0x75, 0xd83c, 0xddfb, 0xd83c, 0xdde6, 0x3a, +0x76, 0x61, 0x3a, 0x76, 0x61, 0xd83c, 0xddfb, 0xd83c, 0xdde6, 0x3a, 0x66, 0x6c, +0x61, 0x67, 0x5f, 0x76, 0x61, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x76, 0x61, +0xd83c, 0xddfb, 0xd83c, 0xddea, 0x3a, 0x76, 0x65, 0x3a, 0x76, 0x65, 0xd83c, 0xddfb, +0xd83c, 0xddea, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x76, 0x65, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x76, 0x65, 0xd83c, 0xddfb, 0xd83c, 0xddf3, 0x3a, 0x76, 0x6e, +0x3a, 0x76, 0x6e, 0xd83c, 0xddfb, 0xd83c, 0xddf3, 0x3a, 0x66, 0x6c, 0x61, 0x67, +0x5f, 0x76, 0x6e, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x76, 0x6e, 0xd83c, 0xddfc, +0xd83c, 0xddeb, 0x3a, 0x77, 0x66, 0x3a, 0x77, 0x66, 0xd83c, 0xddfc, 0xd83c, 0xddeb, +0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x77, 0x66, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x77, 0x66, 0xd83c, 0xddea, 0xd83c, 0xdded, 0x3a, 0x65, 0x68, 0x3a, 0x65, +0x68, 0xd83c, 0xddea, 0xd83c, 0xdded, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x65, +0x68, 0x3a, 0x65, 0x68, 0x66, 0x6c, 0x61, 0x67, 0xd83c, 0xddfe, 0xd83c, 0xddea, +0x3a, 0x79, 0x65, 0x3a, 0x79, 0x65, 0xd83c, 0xddfe, 0xd83c, 0xddea, 0x3a, 0x66, +0x6c, 0x61, 0x67, 0x5f, 0x79, 0x65, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x79, +0x65, 0xd83c, 0xddff, 0xd83c, 0xddf2, 0x3a, 0x7a, 0x6d, 0x3a, 0x7a, 0x6d, 0xd83c, +0xddff, 0xd83c, 0xddf2, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x7a, 0x6d, 0x3a, +0x66, 0x6c, 0x61, 0x67, 0x7a, 0x6d, 0xd83c, 0xddff, 0xd83c, 0xddfc, 0x3a, 0x7a, +0x77, 0x3a, 0x7a, 0x77, 0xd83c, 0xddff, 0xd83c, 0xddfc, 0x3a, 0x66, 0x6c, 0x61, +0x67, 0x5f, 0x7a, 0x77, 0x3a, 0x66, 0x6c, 0x61, 0x67, 0x7a, 0x77 }; + +const small ReplacementWordLengths[] = { +8, 6, 5, 4, 9, 8, 5, 5, 3, 5, 8, 2, +7, 3, 4, 7, 5, 8, 4, 8, 7, 6, 5, 4, +4, 6, 4, 6, 4, 8, 4, 5, 5, 7, 7, 4, +7, 7, 6, 4, 7, 3, 3, 3, 5, 6, 7, 6, +4, 3, 5, 6, 3, 5, 6, 4, 5, 5, 5, 5, +4, 7, 7, 4, 4, 4, 10, 5, 4, 5, 6, 4, +3, 4, 6, 5, 8, 12, 7, 7, 8, 4, 8, 8, +5, 6, 4, 8, 5, 9, 9, 10, 4, 5, 5, 7, +5, 4, 5, 2, 4, 7, 14, 6, 8, 9, 5, 4, +10, 5, 4, 7, 6, 7, 4, 5, 3, 12, 8, 5, +8, 4, 3, 5, 6, 8, 4, 4, 7, 4, 4, 7, +3, 4, 8, 8, 4, 4, 5, 9, 4, 5, 6, 5, +6, 4, 4, 9, 6, 4, 8, 4, 4, 11, 4, 4, +11, 7, 4, 4, 4, 7, 4, 3, 7, 3, 8, 4, +6, 8, 3, 6, 4, 4, 5, 8, 5, 3, 10, 5, +10, 5, 5, 7, 5, 4, 5, 5, 4, 7, 1, 3, +6, 3, 5, 3, 3, 3, 4, 5, 3, 5, 3, 7, +3, 6, 3, 6, 4, 3, 7, 5, 4, 5, 6, 4, +4, 5, 7, 9, 4, 7, 2, 8, 7, 9, 2, 10, +5, 4, 4, 4, 6, 4, 4, 4, 5, 6, 4, 5, +3, 7, 6, 4, 5, 6, 4, 7, 7, 1, 5, 2, +4, 3, 5, 4, 2, 4, 5, 5, 5, 1, 5, 2, +4, 5, 5, 2, 4, 6, 4, 4, 2, 4, 4, 2, +6, 7, 4, 6, 7, 4, 4, 7, 3, 7, 7, 4, +6, 4, 6, 4, 4, 6, 4, 4, 4, 2, 4, 2, +6, 8, 6, 4, 6, 8, 4, 6, 6, 4, 7, 6, +4, 4, 4, 8, 4, 4, 6, 3, 4, 10, 3, 4, +4, 2, 10, 8, 4, 8, 4, 2, 10, 5, 2, 10, +4, 3, 4, 3, 5, 5, 6, 5, 5, 6, 3, 5, +4, 6, 4, 5, 6, 6, 3, 5, 7, 5, 5, 3, +3, 3, 2, 4, 3, 7, 3, 4, 6, 7, 5, 3, +6, 7, 3, 6, 4, 6, 6, 7, 7, 6, 5, 3, +7, 6, 3, 7, 6, 12, 5, 6, 12, 3, 6, 12, +6, 5, 5, 5, 3, 9, 5, 9, 5, 9, 3, 2, +6, 3, 3, 9, 6, 5, 6, 6, 3, 6, 6, 5, +6, 3, 4, 5, 4, 3, 7, 5, 3, 7, 6, 5, +3, 6, 7, 5, 3, 7, 7, 5, 6, 7, 3, 6, +12, 5, 3, 12, 6, 5, 6, 3, 6, 6, 8, 5, +3, 8, 9, 5, 3, 9, 6, 5, 6, 3, 11, 5, +11, 3, 5, 5, 3, 5, 9, 5, 9, 3, 5, 5, +5, 3, 9, 6, 5, 3, 5, 8, 6, 5, 4, 4, +2, 3, 6, 5, 9, 5, 8, 5, 6, 5, 6, 3, +3, 6, 6, 4, 7, 5, 4, 11, 6, 4, 6, 7, +4, 3, 7, 9, 2, 5, 4, 2, 9, 2, 6, 9, +3, 2, 9, 2, 5, 2, 5, 9, 2, 6, 9, 3, +2, 4, 7, 5, 4, 7, 4, 6, 7, 4, 3, 7, +11, 5, 11, 3, 8, 4, 4, 11, 6, 9, 5, 5, +6, 9, 3, 9, 7, 5, 4, 6, 7, 4, 6, 7, +3, 7, 8, 5, 8, 6, 8, 3, 7, 7, 5, 7, +7, 7, 6, 7, 7, 3, 4, 7, 7, 5, 7, 7, +7, 6, 4, 7, 3, 7, 8, 2, 10, 3, 4, 6, +6, 4, 7, 3, 5, 4, 8, 4, 5, 7, 5, 4, +8, 6, 4, 5, 4, 3, 8, 4, 7, 5, 3, 7, +7, 6, 7, 7, 5, 3, 7, 6, 6, 7, 6, 5, +7, 3, 5, 5, 7, 3, 3, 6, 5, 4, 6, 5, +3, 4, 5, 6, 5, 4, 2, 6, 2, 6, 5, 2, +4, 6, 2, 10, 4, 3, 5, 10, 2, 4, 2, 10, +2, 4, 2, 6, 3, 6, 3, 5, 6, 3, 6, 4, +6, 4, 6, 4, 6, 3, 6, 3, 6, 4, 6, 4, +6, 4, 6, 3, 6, 3, 6, 4, 6, 4, 6, 4, +3, 6, 5, 6, 4, 5, 3, 6, 4, 5, 3, 3, +6, 5, 6, 4, 4, 5, 3, 6, 3, 6, 4, 3, +3, 6, 4, 3, 3, 3, 6, 3, 6, 4, 4, 3, +7, 6, 5, 5, 7, 5, 6, 6, 4, 4, 6, 4, +4, 4, 8, 4, 3, 6, 6, 5, 6, 5, 5, 6, +5, 4, 5, 6, 4, 7, 6, 5, 5, 7, 9, 10, +4, 10, 6, 8, 9, 3, 3, 5, 7, 6, 4, 3, +3, 4, 4, 5, 5, 5, 4, 4, 4, 3, 3, 4, +3, 4, 4, 6, 4, 2, 3, 4, 4, 2, 4, 2, +5, 6, 7, 7, 4, 4, 5, 5, 8, 5, 7, 4, +5, 3, 3, 4, 4, 5, 4, 7, 7, 3, 3, 9, +5, 5, 6, 3, 6, 6, 3, 6, 5, 6, 8, 4, +5, 7, 6, 4, 8, 4, 8, 7, 5, 5, 6, 9, +7, 6, 7, 5, 2, 4, 4, 5, 9, 5, 8, 10, +5, 7, 9, 4, 4, 3, 5, 4, 6, 4, 7, 6, +4, 2, 5, 4, 7, 6, 3, 8, 3, 6, 4, 6, +6, 4, 6, 9, 4, 9, 4, 9, 4, 4, 4, 8, +4, 8, 6, 4, 4, 6, 8, 4, 6, 6, 4, 4, +5, 8, 3, 2, 4, 7, 5, 4, 6, 6, 4, 6, +9, 7, 7, 6, 8, 8, 5, 6, 5, 4, 5, 4, +4, 7, 4, 6, 4, 4, 7, 8, 4, 6, 4, 3, +8, 4, 6, 5, 4, 7, 7, 4, 6, 4, 4, 3, +4, 4, 4, 4, 4, 4, 3, 4, 4, 5, 4, 7, +4, 4, 4, 4, 7, 4, 8, 4, 5, 4, 5, 8, +3, 5, 4, 4, 5, 5, 5, 5, 3, 5, 4, 5, +5, 3, 5, 6, 5, 6, 5, 3, 5, 5, 3, 5, +6, 5, 4, 3, 5, 4, 5, 4, 3, 5, 7, 5, +5, 4, 4, 5, 4, 3, 5, 4, 7, 5, 4, 7, +5, 9, 4, 5, 9, 5, 4, 4, 5, 4, 8, 7, +9, 7, 4, 4, 4, 5, 7, 4, 5, 7, 3, 5, +7, 5, 5, 8, 5, 5, 5, 4, 9, 5, 6, 10, +6, 10, 5, 8, 5, 9, 9, 4, 7, 6, 8, 8, +6, 4, 3, 6, 6, 6, 5, 8, 6, 7, 7, 5, +3, 9, 5, 8, 5, 5, 6, 6, 5, 6, 3, 7, +5, 8, 5, 6, 3, 7, 4, 4, 2, 5, 3, 3, +6, 9, 5, 4, 7, 9, 7, 4, 7, 5, 5, 5, +6, 4, 2, 3, 7, 9, 5, 4, 4, 4, 5, 5, +5, 4, 4, 4, 7, 4, 4, 5, 3, 6, 5, 3, +8, 4, 8, 4, 7, 7, 8, 5, 3, 9, 7, 8, +6, 5, 4, 2, 4, 4, 6, 6, 3, 4, 4, 5, +8, 5, 9, 5, 5, 4, 6, 5, 7, 8, 5, 8, +6, 4, 7, 4, 9, 5, 3, 4, 5, 3, 4, 5, +5, 4, 4, 5, 5, 6, 10, 8, 8, 6, 10, 8, +5, 5, 5, 6, 4, 4, 9, 4, 3, 4, 6, 5, +6, 4, 3, 7, 7, 4, 4, 7, 3, 5, 3, 3, +4, 7, 4, 6, 6, 6, 5, 6, 7, 4, 7, 7, +3, 5, 3, 5, 11, 7, 7, 5, 7, 3, 7, 6, +6, 6, 7, 6, 7, 7, 6, 7, 6, 5, 9, 3, +9, 9, 9, 6, 9, 12, 5, 12, 3, 9, 9, 5, +6, 4, 8, 5, 4, 8, 3, 4, 6, 4, 10, 6, +4, 8, 6, 8, 7, 5, 8, 8, 6, 7, 8, 3, +7, 7, 5, 7, 3, 6, 7, 6, 7, 5, 3, 7, +6, 6, 7, 8, 5, 3, 8, 7, 6, 8, 7, 4, +5, 5, 3, 7, 4, 5, 4, 5, 6, 7, 4, 5, +4, 6, 5, 4, 3, 6, 7, 4, 6, 6, 5, 6, +6, 5, 6, 3, 9, 6, 6, 6, 8, 5, 6, 3, +8, 9, 8, 6, 8, 6, 7, 4, 5, 4, 5, 6, +5, 5, 8, 5, 5, 5, 5, 5, 5, 5, 6, 5, +6, 5, 5, 5, 5, 5, 6, 7, 8, 6, 6, 9, +7, 7, 6, 4, 8, 5, 8, 3, 7, 8, 8, 6, +4, 10, 3, 7, 10, 10, 7, 5, 8, 7, 4, 10, +4, 4, 9, 7, 6, 6, 3, 4, 4, 7, 4, 5, +7, 4, 3, 3, 4, 4, 3, 3, 10, 3, 6, 3, +4, 3, 6, 9, 6, 4, 7, 5, 11, 5, 7, 7, +4, 9, 5, 7, 10, 6, 10, 5, 8, 3, 8, 6, +3, 8, 10, 8, 8, 4, 6, 7, 8, 8, 7, 10, +3, 7, 5, 8, 7, 8, 11, 4, 11, 5, 5, 4, +10, 5, 6, 5, 4, 7, 10, 8, 5, 8, 5, 8, +8, 9, 8, 8, 6, 7, 9, 4, 5, 5, 8, 9, +9, 9, 4, 6, 4, 5, 4, 6, 12, 8, 7, 5, +7, 8, 5, 7, 3, 5, 3, 5, 7, 2, 6, 8, +5, 5, 6, 8, 6, 8, 7, 6, 5, 7, 6, 8, +5, 6, 2, 8, 5, 8, 5, 8, 4, 5, 6, 6, +6, 8, 6, 8, 4, 8, 4, 4, 5, 7, 6, 7, +4, 8, 5, 7, 5, 8, 8, 12, 12, 4, 7, 5, +6, 5, 4, 9, 5, 5, 8, 8, 5, 9, 5, 6, +10, 5, 6, 4, 8, 6, 4, 8, 4, 5, 11, 5, +6, 5, 4, 7, 8, 9, 6, 6, 9, 5, 6, 6, +5, 4, 5, 8, 4, 4, 7, 9, 4, 7, 5, 8, +9, 4, 7, 4, 6, 4, 4, 9, 5, 5, 4, 5, +3, 2, 6, 5, 5, 5, 6, 7, 8, 8, 8, 7, +7, 7, 6, 5, 5, 6, 5, 5, 9, 8, 11, 8, +4, 6, 2, 3, 3, 6, 6, 5, 4, 6, 5, 6, +5, 4, 9, 9, 4, 6, 8, 9, 9, 5, 3, 2, +5, 10, 6, 11, 5, 6, 7, 5, 9, 5, 5, 5, +5, 5, 5, 11, 5, 9, 7, 9, 4, 9, 7, 8, +4, 4, 10, 6, 11, 4, 3, 3, 5, 5, 4, 6, +3, 4, 5, 8, 4, 6, 3, 6, 6, 6, 3, 6, +4, 6, 4, 3, 6, 6, 5, 4, 3, 4, 3, 4, +6, 3, 4, 5, 6, 5, 6, 7, 6, 6, 7, 6, +7, 3, 3, 7, 4, 7, 5, 6, 6, 7, 9, 10, +4, 4, 7, 11, 6, 7, 5, 6, 7, 4, 4, 7, +7, 3, 3, 3, 4, 4, 3, 5, 4, 5, 3, 13, +8, 5, 7, 4, 5, 5, 4, 8, 8, 7, 4, 8, +4, 7, 5, 6, 4, 8, 4, 5, 7, 7, 5, 4, +8, 5, 8, 4, 8, 8, 5, 1, 4, 6, 4, 5, +4, 6, 4, 7, 5, 6, 7, 7, 4, 7, 4, 4, +7, 2, 4, 7, 4, 6, 6, 4, 4, 4, 6, 4, +2, 8, 4, 3, 5, 5, 5, 7, 4, 5, 9, 5, +4, 4, 3, 6, 7, 6, 8, 3, 6, 8, 6, 8, +4, 4, 5, 3, 4, 4, 3, 4, 6, 6, 3, 4, +6, 3, 7, 4, 9, 4, 6, 4, 6, 4, 4, 8, +5, 8, 9, 6, 2, 10, 9, 8, 5, 10, 8, 4, +6, 4, 6, 4, 5, 4, 4, 4, 6, 5, 4, 8, +4, 9, 6, 10, 10, 5, 10, 5, 8, 7, 7, 5, +8, 9, 4, 5, 3, 9, 3, 8, 4, 5, 3, 8, +3, 5, 3, 4, 5, 10, 10, 6, 4, 5, 6, 4, +6, 7, 3, 3, 5, 3, 4, 3, 4, 6, 3, 4, +4, 4, 6, 5, 5, 6, 5, 5, 4, 5, 5, 6, +5, 5, 6, 5, 11, 5, 5, 4, 8, 11, 5, 6, +3, 6, 9, 9, 10, 5, 9, 5, 4, 5, 10, 5, +5, 6, 5, 5, 5, 5, 3, 8, 4, 2, 6, 6, +2, 5, 5, 2, 4, 7, 3, 4, 7, 4, 3, 5, +8, 6, 7, 2, 5, 7, 9, 5, 6, 6, 6, 3, +5, 5, 8, 11, 9, 8, 6, 2, 4, 6, 4, 6, +11, 4, 11, 9, 4, 9, 6, 3, 5, 4, 9, 5, +5, 5, 5, 5, 5, 5, 7, 4, 2, 6, 5, 9, +9, 6, 15, 5, 5, 5, 5, 1, 1, 2, 2, 2, +3, 1, 1, 4, 4, 9, 4, 5, 2, 5, 4, 5, +2, 4, 3, 5, 10, 2, 11, 2, 6, 3, 8, 2, +3, 7, 5, 8, 6, 2, 6, 2, 7, 11, 11, 4, +8, 4, 8, 8, 11, 10, 3, 10, 4, 11, 4, 4, +7, 8, 8, 7, 2, 5, 3, 8, 7, 5, 4, 5, +5, 5, 7, 8, 5, 6, 5, 4, 8, 7, 5, 9, +4, 1, 7, 3, 6, 5, 4, 1, 7, 3, 3, 2, +10, 7, 5, 2, 7, 8, 7, 7, 5, 4, 7, 4, +6, 4, 6, 8, 2, 3, 6, 5, 3, 6, 6, 8, +4, 7, 11, 6, 3, 4, 4, 7, 2, 2, 2, 4, +3, 4, 4, 3, 3, 5, 4, 4, 3, 5, 5, 4, +6, 3, 4, 4, 8, 6, 8, 5, 7, 3, 6, 8, +6, 5, 5, 4, 6, 4, 6, 6, 4, 5, 4, 5, +8, 5, 8, 5, 4, 7, 6, 5, 6, 2, 5, 6, +4, 5, 8, 5, 5, 2, 5, 4, 5, 5, 5, 5, +4, 5, 2, 5, 4, 5, 5, 5, 5, 5, 5, 5, +4, 5, 5, 4, 5, 5, 4, 2, 5, 4, 5, 5, +4, 5, 5, 4, 9, 4, 5, 7, 2, 5, 4, 7, +6, 10, 7, 6, 3, 6, 6, 16, 6, 9, 7, 4, +5, 5, 4, 4, 5, 5, 4, 8, 5, 4, 5, 14, +1, 6, 5, 4, 8, 8, 2, 9, 10, 4, 4, 5, +4, 4, 3, 4, 2, 3, 4, 5, 5, 4, 6, 3, +5, 4, 6, 5, 6, 5, 5, 6, 6, 3, 4, 6, +3, 5, 8, 4, 3, 5, 8, 7, 6, 5, 4, 7, +5, 7, 5, 6, 4, 7, 5, 6, 6, 5, 5, 6, +6, 5, 5, 6, 5, 6, 5, 5, 6, 5, 6, 6, +5, 6, 5, 5, 6, 6, 6, 6, 5, 5, 5, 6, +5, 6, 5, 7, 4, 5, 4, 5, 4, 4, 2, 4, +11, 6, 4, 6, 4, 6, 6, 3, 2, 6, 7, 6, +7, 7, 5, 6, 5, 5, 5, 6, 5, 6, 8, 5, +5, 5, 6, 7, 7, 6, 6, 6, 6, 6, 6, 6, +6, 6, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, +8, 8, 9, 9, 9, 4, 6, 5, 4, 5, 5, 4, +6, 5, 4, 9, 4, 4, 2, 4, 10, 4, 3, 5, +4, 7, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, +2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, +2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, +2, 4, 2, 2, 4, 2, 4, 2, 2, 2, 4, 2, +2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, +2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, +2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, +2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, +4, 2, 2, 2, 4, 2, 4, 2, 2, 2, 4, 2, +2, 4, 2, 2, 4, 2, 2, 4, 2, 4, 2, 2, +2, 4, 2, 2, 4, 2, 4, 2, 2, 2, 4, 2, +2, 4, 2, 4, 2, 2, 2, 4, 2, 4, 2, 5, +2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, +2, 4, 2, 4, 2, 2, 2, 4, 5, 2, 4, 2, +2, 4, 2, 2, 4, 2, 2, 4, 2, 4, 2, 2, +2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, +2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, +2, 4, 2, 2, 4, 2, 4, 2, 2, 4, 2, 2, +2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, +2, 4, 2, 4, 2, 2, 2, 4, 2, 2, 4, 2, +4, 2, 2, 4, 2, 2, 2, 4, 2, 4, 2, 2, +4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, +4, 2, 2, 2, 4, 2, 4, 2, 2, 4, 2, 2, +4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, +4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, +4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, +4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 9, +4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, +4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, +4, 2, 7, 5, 2, 4, 2, 2, 4, 2, 2, 4, +2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, +2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, +2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, +2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, +2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, +2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, +2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, +2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, +2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, +2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, +2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, +2, 2, 4, 2, 7, 4, 2, 2, 4, 2, 2, 4, +2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, +2, 2, 2, 4, 2, 4, 2, 2, 4, 2, 2, 4, +2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, +2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, +2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, +2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, +2, 2, 4, 2, 2, 4, 2, 5, 11, 4, 2, 2, +4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, +4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, +4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, +4, 2, 2, 4, 2, 2, 2, 4, 2, 2, 4, 2, +4, 2, 2, 2, 4, 2, 4, 2, 2, 4, 2, 2, +2, 4, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, +4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, +4, 2, 2, 2, 4, 2, 4, 2, 2, 4, 2, 2, +4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, +4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, +4, 2, 2, 4, 2, 12, 4, 2, 2, 4, 2, 6, +4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, +2, 4, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, +4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, +4, 2, 2, 4, 2, 2, 4, 2, 2, 2, 4, 2, +4, 2, 2, 4, 2, 2, 4, 2 }; + +const ReplacementStruct ReplacementInitData[] = { + { small(2), small(10), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(11), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(13), small(2) }, + { small(2), small(5), small(1) }, + { small(2), small(31), small(5) }, + { small(2), small(6), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(23), small(3) }, + { small(2), small(14), small(2) }, + { small(2), small(18), small(3) }, + { small(2), small(13), small(2) }, + { small(2), small(6), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(12), small(2) }, + { small(2), small(15), small(2) }, + { small(2), small(9), small(1) }, + { small(2), small(22), small(3) }, + { small(2), small(21), small(3) }, + { small(2), small(5), small(1) }, + { small(2), small(30), small(5) }, + { small(2), small(30), small(5) }, + { small(2), small(18), small(3) }, + { small(2), small(18), small(3) }, + { small(2), small(13), small(2) }, + { small(2), small(14), small(2) }, + { small(2), small(9), small(1) }, + { small(2), small(11), small(2) }, + { small(2), small(6), small(1) }, + { small(2), small(12), small(1) }, + { small(2), small(12), small(2) }, + { small(2), small(7), small(1) }, + { small(2), small(22), small(4) }, + { small(2), small(8), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(14), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(24), small(3) }, + { small(2), small(14), small(2) }, + { small(2), small(21), small(3) }, + { small(2), small(11), small(1) }, + { small(2), small(11), small(1) }, + { small(2), small(12), small(1) }, + { small(2), small(12), small(2) }, + { small(2), small(7), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(10), small(2) }, + { small(2), small(14), small(2) }, + { small(2), small(16), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(11), small(1) }, + { small(2), small(12), small(2) }, + { small(2), small(12), small(1) }, + { small(2), small(12), small(2) }, + { small(2), small(9), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(12), small(2) }, + { small(2), small(5), small(1) }, + { small(2), small(23), small(2) }, + { small(2), small(7), small(1) }, + { small(2), small(15), small(2) }, + { small(2), small(5), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(24), small(4) }, + { small(2), small(14), small(2) }, + { small(2), small(5), small(1) }, + { small(2), small(15), small(2) }, + { small(2), small(10), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(12), small(2) }, + { small(2), small(11), small(1) }, + { small(2), small(19), small(3) }, + { small(2), small(14), small(2) }, + { small(2), small(6), small(1) }, + { small(2), small(16), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(15), small(2) }, + { small(2), small(6), small(1) }, + { small(2), small(23), small(3) }, + { small(2), small(18), small(2) }, + { small(2), small(24), small(4) }, + { small(2), small(14), small(2) }, + { small(2), small(13), small(2) }, + { small(2), small(5), small(1) }, + { small(2), small(15), small(2) }, + { small(2), small(17), small(2) }, + { small(2), small(5), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(22), small(3) }, + { small(2), small(18), small(2) }, + { small(2), small(7), small(1) }, + { small(2), small(15), small(2) }, + { small(2), small(12), small(2) }, + { small(2), small(7), small(1) }, + { small(2), small(16), small(3) }, + { small(2), small(12), small(2) }, + { small(2), small(11), small(2) }, + { small(2), small(9), small(2) }, + { small(2), small(16), small(3) }, + { small(2), small(11), small(2) }, + { small(2), small(13), small(2) }, + { small(2), small(12), small(2) }, + { small(2), small(17), small(3) }, + { small(2), small(13), small(2) }, + { small(2), small(12), small(2) }, + { small(2), small(14), small(2) }, + { small(2), small(6), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(15), small(2) }, + { small(2), small(11), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(4), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(11), small(1) }, + { small(2), small(4), small(1) }, + { small(2), small(12), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(11), small(2) }, + { small(2), small(18), small(3) }, + { small(2), small(12), small(2) }, + { small(2), small(19), small(3) }, + { small(2), small(43), small(7) }, + { small(2), small(17), small(2) }, + { small(2), small(3), small(1) }, + { small(2), small(19), small(4) }, + { small(2), small(7), small(1) }, + { small(2), small(9), small(2) }, + { small(2), small(12), small(2) }, + { small(2), small(13), small(2) }, + { small(2), small(12), small(3) }, + { small(2), small(12), small(2) }, + { small(2), small(10), small(2) }, + { small(2), small(13), small(2) }, + { small(2), small(14), small(3) }, + { small(2), small(21), small(4) }, + { small(2), small(34), small(5) }, + { small(2), small(14), small(2) }, + { small(2), small(55), small(9) }, + { small(2), small(8), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(14), small(3) }, + { small(2), small(9), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(43), small(6) }, + { small(2), small(15), small(2) }, + { small(2), small(14), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(11), small(2) }, + { small(2), small(6), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(12), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(29), small(4) }, + { small(2), small(15), small(2) }, + { small(2), small(20), small(3) }, + { small(2), small(21), small(3) }, + { small(2), small(6), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(7), small(1) }, + { small(5), small(20), small(3) }, + { small(2), small(18), small(3) }, + { small(2), small(24), small(4) }, + { small(2), small(21), small(3) }, + { small(2), small(11), small(2) }, + { small(2), small(9), small(1) }, + { small(2), small(13), small(2) }, + { small(2), small(21), small(5) }, + { small(2), small(22), small(4) }, + { small(5), small(22), small(3) }, + { small(2), small(20), small(3) }, + { small(2), small(17), small(3) }, + { small(2), small(23), small(3) }, + { small(5), small(22), small(3) }, + { small(2), small(20), small(3) }, + { small(2), small(5), small(1) }, + { small(2), small(16), small(2) }, + { small(5), small(27), small(3) }, + { small(2), small(25), small(3) }, + { small(2), small(21), small(2) }, + { small(5), small(13), small(2) }, + { small(2), small(11), small(2) }, + { small(2), small(11), small(1) }, + { small(2), small(7), small(1) }, + { small(6), small(17), small(2) }, + { small(3), small(15), small(2) }, + { small(3), small(15), small(3) }, + { small(3), small(5), small(1) }, + { small(3), small(11), small(1) }, + { small(5), small(21), small(3) }, + { small(5), small(19), small(3) }, + { small(5), small(14), small(2) }, + { small(5), small(12), small(2) }, + { small(5), small(12), small(2) }, + { small(5), small(10), small(2) }, + { small(5), small(15), small(2) }, + { small(5), small(13), small(2) }, + { small(5), small(14), small(2) }, + { small(5), small(12), small(2) }, + { small(5), small(15), small(2) }, + { small(5), small(13), small(2) }, + { small(5), small(22), small(3) }, + { small(5), small(20), small(3) }, + { small(5), small(20), small(2) }, + { small(5), small(18), small(2) }, + { small(5), small(21), small(3) }, + { small(5), small(19), small(3) }, + { small(5), small(16), small(2) }, + { small(5), small(14), small(2) }, + { small(5), small(17), small(2) }, + { small(5), small(15), small(2) }, + { small(5), small(14), small(2) }, + { small(5), small(12), small(2) }, + { small(5), small(19), small(2) }, + { small(5), small(17), small(2) }, + { small(5), small(13), small(2) }, + { small(5), small(11), small(2) }, + { small(5), small(17), small(2) }, + { small(5), small(15), small(2) }, + { small(5), small(13), small(2) }, + { small(5), small(11), small(2) }, + { small(2), small(18), small(2) }, + { small(2), small(11), small(2) }, + { small(2), small(7), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(17), small(3) }, + { small(2), small(15), small(3) }, + { small(2), small(7), small(1) }, + { small(2), small(17), small(2) }, + { small(2), small(16), small(2) }, + { small(5), small(14), small(2) }, + { small(2), small(12), small(2) }, + { small(2), small(5), small(1) }, + { small(2), small(15), small(2) }, + { small(2), small(20), small(3) }, + { small(2), small(25), small(3) }, + { small(2), small(21), small(3) }, + { small(5), small(18), small(3) }, + { small(2), small(20), small(3) }, + { small(2), small(9), small(2) }, + { small(2), small(21), small(3) }, + { small(5), small(18), small(3) }, + { small(2), small(20), small(3) }, + { small(2), small(10), small(2) }, + { small(2), small(21), small(3) }, + { small(5), small(18), small(3) }, + { small(2), small(20), small(3) }, + { small(2), small(14), small(2) }, + { small(2), small(21), small(3) }, + { small(5), small(18), small(3) }, + { small(5), small(19), small(2) }, + { small(5), small(17), small(2) }, + { small(5), small(10), small(1) }, + { small(5), small(11), small(2) }, + { small(5), small(20), small(2) }, + { small(5), small(17), small(2) }, + { small(5), small(7), small(1) }, + { small(5), small(18), small(2) }, + { small(5), small(15), small(2) }, + { small(2), small(15), small(2) }, + { small(2), small(26), small(4) }, + { small(2), small(16), small(2) }, + { small(5), small(13), small(2) }, + { small(2), small(16), small(2) }, + { small(2), small(17), small(2) }, + { small(5), small(14), small(2) }, + { small(2), small(23), small(3) }, + { small(2), small(9), small(1) }, + { small(2), small(24), small(3) }, + { small(5), small(21), small(3) }, + { small(2), small(28), small(4) }, + { small(2), small(9), small(1) }, + { small(2), small(24), small(3) }, + { small(5), small(26), small(4) }, + { small(2), small(33), small(5) }, + { small(2), small(8), small(1) }, + { small(2), small(13), small(2) }, + { small(2), small(13), small(2) }, + { small(2), small(32), small(5) }, + { small(2), small(9), small(1) }, + { small(2), small(33), small(5) }, + { small(5), small(30), small(5) }, + { small(5), small(15), small(2) }, + { small(2), small(13), small(2) }, + { small(2), small(9), small(1) }, + { small(2), small(16), small(2) }, + { small(5), small(15), small(2) }, + { small(2), small(13), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(16), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(25), small(4) }, + { small(2), small(23), small(4) }, + { small(2), small(19), small(3) }, + { small(2), small(29), small(5) }, + { small(8), small(22), small(4) }, + { small(8), small(11), small(2) }, + { small(8), small(22), small(4) }, + { small(8), small(11), small(2) }, + { small(2), small(12), small(1) }, + { small(2), small(16), small(3) }, + { small(11), small(15), small(2) }, + { small(11), small(9), small(2) }, + { small(11), small(15), small(2) }, + { small(11), small(9), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(22), small(4) }, + { small(8), small(12), small(2) }, + { small(11), small(13), small(2) }, + { small(11), small(13), small(2) }, + { small(11), small(13), small(2) }, + { small(8), small(12), small(2) }, + { small(8), small(12), small(2) }, + { small(11), small(13), small(2) }, + { small(11), small(13), small(2) }, + { small(11), small(13), small(2) }, + { small(8), small(12), small(2) }, + { small(8), small(12), small(2) }, + { small(11), small(13), small(2) }, + { small(11), small(13), small(2) }, + { small(11), small(13), small(2) }, + { small(5), small(18), small(3) }, + { small(5), small(19), small(3) }, + { small(8), small(23), small(4) }, + { small(8), small(22), small(4) }, + { small(8), small(24), small(4) }, + { small(5), small(16), small(3) }, + { small(5), small(17), small(3) }, + { small(8), small(21), small(4) }, + { small(8), small(20), small(4) }, + { small(8), small(22), small(4) }, + { small(2), small(16), small(2) }, + { small(2), small(7), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(11), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(11), small(2) }, + { small(2), small(15), small(2) }, + { small(2), small(12), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(14), small(2) }, + { small(2), small(7), small(1) }, + { small(1), small(25), small(4) }, + { small(1), small(19), small(3) }, + { small(2), small(16), small(2) }, + { small(2), small(7), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(11), small(1) }, + { small(2), small(12), small(1) }, + { small(2), small(17), small(2) }, + { small(2), small(17), small(2) }, + { small(2), small(11), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(10), small(2) }, + { small(2), small(5), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(12), small(2) }, + { small(2), small(7), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(11), small(2) }, + { small(2), small(5), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(10), small(2) }, + { small(2), small(6), small(1) }, + { small(2), small(13), small(2) }, + { small(2), small(13), small(3) }, + { small(2), small(14), small(3) }, + { small(2), small(15), small(3) }, + { small(2), small(8), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(12), small(2) }, + { small(2), small(16), small(2) }, + { small(2), small(15), small(2) }, + { small(2), small(6), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(14), small(2) }, + { small(2), small(9), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(11), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(12), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(15), small(2) }, + { small(2), small(6), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(11), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(15), small(2) }, + { small(2), small(4), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(17), small(2) }, + { small(2), small(7), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(12), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(11), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(15), small(3) }, + { small(2), small(6), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(12), small(2) }, + { small(2), small(6), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(13), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(16), small(2) }, + { small(2), small(16), small(2) }, + { small(2), small(16), small(2) }, + { small(2), small(11), small(2) }, + { small(2), small(10), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(18), small(3) }, + { small(2), small(8), small(1) }, + { small(2), small(15), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(13), small(2) }, + { small(2), small(12), small(2) }, + { small(2), small(10), small(1) }, + { small(2), small(13), small(3) }, + { small(2), small(9), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(15), small(2) }, + { small(2), small(13), small(2) }, + { small(2), small(11), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(16), small(2) }, + { small(2), small(10), small(1) }, + { small(2), small(16), small(2) }, + { small(2), small(14), small(2) }, + { small(2), small(12), small(2) }, + { small(2), small(11), small(2) }, + { small(2), small(21), small(3) }, + { small(2), small(19), small(3) }, + { small(2), small(22), small(3) }, + { small(2), small(10), small(2) }, + { small(2), small(22), small(3) }, + { small(2), small(20), small(3) }, + { small(2), small(21), small(3) }, + { small(2), small(20), small(4) }, + { small(2), small(21), small(4) }, + { small(2), small(15), small(3) }, + { small(2), small(30), small(5) }, + { small(2), small(29), small(5) }, + { small(2), small(15), small(2) }, + { small(2), small(7), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(7), small(1) }, + { small(1), small(10), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(28), small(5) }, + { small(2), small(23), small(4) }, + { small(2), small(14), small(2) }, + { small(2), small(24), small(4) }, + { small(2), small(17), small(3) }, + { small(2), small(34), small(6) }, + { small(2), small(22), small(4) }, + { small(2), small(9), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(17), small(3) }, + { small(2), small(12), small(2) }, + { small(1), small(24), small(4) }, + { small(1), small(20), small(3) }, + { small(2), small(22), small(3) }, + { small(2), small(17), small(2) }, + { small(2), small(17), small(3) }, + { small(2), small(12), small(2) }, + { small(2), small(10), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(11), small(1) }, + { small(2), small(19), small(3) }, + { small(2), small(6), small(1) }, + { small(2), small(20), small(3) }, + { small(2), small(15), small(2) }, + { small(2), small(5), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(13), small(2) }, + { small(2), small(10), small(1) }, + { small(2), small(13), small(2) }, + { small(2), small(7), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(11), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(12), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(12), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(11), small(1) }, + { small(2), small(11), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(12), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(14), small(2) }, + { small(2), small(10), small(1) }, + { small(2), small(16), small(2) }, + { small(2), small(9), small(1) }, + { small(2), small(11), small(2) }, + { small(2), small(11), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(16), small(2) }, + { small(2), small(14), small(2) }, + { small(2), small(14), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(14), small(2) }, + { small(2), small(13), small(2) }, + { small(2), small(14), small(3) }, + { small(2), small(7), small(1) }, + { small(2), small(9), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(11), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(14), small(2) }, + { small(2), small(19), small(2) }, + { small(2), small(6), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(13), small(2) }, + { small(2), small(7), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(21), small(4) }, + { small(2), small(11), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(11), small(2) }, + { small(2), small(7), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(11), small(2) }, + { small(2), small(6), small(1) }, + { small(2), small(14), small(2) }, + { small(2), small(6), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(12), small(2) }, + { small(2), small(11), small(2) }, + { small(2), small(10), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(15), small(2) }, + { small(2), small(9), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(15), small(3) }, + { small(2), small(6), small(1) }, + { small(2), small(13), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(16), small(2) }, + { small(2), small(17), small(2) }, + { small(2), small(12), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(15), small(2) }, + { small(2), small(10), small(1) }, + { small(2), small(16), small(2) }, + { small(2), small(26), small(4) }, + { small(2), small(11), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(16), small(3) }, + { small(2), small(27), small(5) }, + { small(2), small(18), small(3) }, + { small(2), small(8), small(1) }, + { small(2), small(12), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(12), small(1) }, + { small(2), small(16), small(2) }, + { small(2), small(7), small(1) }, + { small(2), small(14), small(2) }, + { small(2), small(11), small(2) }, + { small(2), small(11), small(1) }, + { small(2), small(10), small(2) }, + { small(2), small(6), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(14), small(2) }, + { small(2), small(18), small(3) }, + { small(2), small(14), small(2) }, + { small(2), small(6), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(15), small(3) }, + { small(2), small(23), small(4) }, + { small(2), small(15), small(2) }, + { small(2), small(14), small(2) }, + { small(2), small(16), small(2) }, + { small(2), small(22), small(3) }, + { small(1), small(11), small(2) }, + { small(2), small(5), small(1) }, + { small(1), small(7), small(1) }, + { small(2), small(13), small(1) }, + { small(6), small(23), small(3) }, + { small(3), small(21), small(3) }, + { small(3), small(15), small(2) }, + { small(3), small(8), small(1) }, + { small(3), small(24), small(3) }, + { small(2), small(9), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(16), small(2) }, + { small(5), small(17), small(2) }, + { small(5), small(15), small(2) }, + { small(5), small(11), small(1) }, + { small(5), small(11), small(1) }, + { small(5), small(18), small(2) }, + { small(5), small(20), small(2) }, + { small(5), small(18), small(2) }, + { small(5), small(11), small(1) }, + { small(5), small(24), small(3) }, + { small(5), small(21), small(3) }, + { small(2), small(19), small(3) }, + { small(2), small(18), small(3) }, + { small(2), small(19), small(2) }, + { small(2), small(22), small(3) }, + { small(5), small(24), small(3) }, + { small(5), small(10), small(1) }, + { small(5), small(25), small(3) }, + { small(5), small(22), small(3) }, + { small(6), small(15), small(2) }, + { small(3), small(13), small(2) }, + { small(3), small(8), small(1) }, + { small(3), small(16), small(2) }, + { small(5), small(15), small(2) }, + { small(2), small(13), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(16), small(2) }, + { small(5), small(16), small(2) }, + { small(2), small(14), small(2) }, + { small(2), small(9), small(1) }, + { small(2), small(17), small(2) }, + { small(5), small(26), small(4) }, + { small(5), small(24), small(4) }, + { small(5), small(12), small(2) }, + { small(5), small(27), small(4) }, + { small(5), small(19), small(3) }, + { small(2), small(17), small(3) }, + { small(2), small(9), small(1) }, + { small(2), small(20), small(3) }, + { small(2), small(14), small(2) }, + { small(5), small(14), small(2) }, + { small(2), small(12), small(2) }, + { small(2), small(11), small(1) }, + { small(2), small(15), small(2) }, + { small(5), small(23), small(3) }, + { small(2), small(21), small(3) }, + { small(2), small(20), small(2) }, + { small(2), small(24), small(3) }, + { small(2), small(25), small(4) }, + { small(2), small(14), small(2) }, + { small(2), small(7), small(1) }, + { small(2), small(16), small(2) }, + { small(2), small(19), small(3) }, + { small(2), small(13), small(2) }, + { small(2), small(20), small(3) }, + { small(2), small(14), small(2) }, + { small(2), small(19), small(3) }, + { small(2), small(13), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(17), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(19), small(2) }, + { small(2), small(9), small(1) }, + { small(2), small(13), small(2) }, + { small(5), small(16), small(2) }, + { small(5), small(14), small(2) }, + { small(5), small(9), small(1) }, + { small(5), small(10), small(1) }, + { small(5), small(17), small(2) }, + { small(2), small(17), small(2) }, + { small(2), small(5), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(12), small(1) }, + { small(2), small(12), small(1) }, + { small(2), small(15), small(2) }, + { small(2), small(18), small(2) }, + { small(2), small(22), small(3) }, + { small(2), small(6), small(1) }, + { small(2), small(11), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(10), small(2) }, + { small(2), small(6), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(12), small(2) }, + { small(2), small(14), small(2) }, + { small(2), small(9), small(2) }, + { small(2), small(6), small(1) }, + { small(2), small(10), small(2) }, + { small(2), small(5), small(1) }, + { small(2), small(12), small(1) }, + { small(2), small(12), small(2) }, + { small(2), small(10), small(2) }, + { small(2), small(12), small(2) }, + { small(2), small(11), small(1) }, + { small(2), small(13), small(2) }, + { small(2), small(9), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(19), small(2) }, + { small(2), small(9), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(11), small(1) }, + { small(2), small(15), small(2) }, + { small(2), small(19), small(2) }, + { small(2), small(12), small(1) }, + { small(2), small(16), small(2) }, + { small(2), small(21), small(3) }, + { small(2), small(14), small(2) }, + { small(2), small(21), small(2) }, + { small(2), small(15), small(2) }, + { small(2), small(16), small(2) }, + { small(2), small(19), small(2) }, + { small(2), small(20), small(2) }, + { small(2), small(13), small(2) }, + { small(2), small(7), small(1) }, + { small(2), small(18), small(2) }, + { small(2), small(10), small(1) }, + { small(2), small(18), small(2) }, + { small(2), small(19), small(2) }, + { small(2), small(12), small(2) }, + { small(2), small(18), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(12), small(1) }, + { small(2), small(16), small(2) }, + { small(2), small(16), small(2) }, + { small(2), small(10), small(1) }, + { small(2), small(20), small(2) }, + { small(2), small(19), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(19), small(2) }, + { small(2), small(6), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(11), small(1) }, + { small(2), small(11), small(1) }, + { small(2), small(16), small(2) }, + { small(2), small(13), small(2) }, + { small(1), small(7), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(14), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(24), small(3) }, + { small(2), small(15), small(2) }, + { small(2), small(11), small(2) }, + { small(2), small(5), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(19), small(3) }, + { small(2), small(10), small(1) }, + { small(2), small(13), small(2) }, + { small(2), small(17), small(2) }, + { small(2), small(17), small(2) }, + { small(2), small(9), small(1) }, + { small(2), small(14), small(2) }, + { small(2), small(16), small(2) }, + { small(2), small(16), small(2) }, + { small(1), small(20), small(3) }, + { small(1), small(16), small(2) }, + { small(2), small(21), small(3) }, + { small(2), small(7), small(1) }, + { small(2), small(15), small(2) }, + { small(2), small(8), small(1) }, + { small(1), small(10), small(1) }, + { small(2), small(22), small(3) }, + { small(2), small(15), small(2) }, + { small(2), small(12), small(2) }, + { small(2), small(9), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(16), small(2) }, + { small(2), small(15), small(2) }, + { small(2), small(10), small(1) }, + { small(2), small(23), small(2) }, + { small(2), small(19), small(2) }, + { small(2), small(9), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(19), small(3) }, + { small(2), small(17), small(2) }, + { small(2), small(7), small(1) }, + { small(2), small(25), small(3) }, + { small(2), small(17), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(18), small(2) }, + { small(2), small(13), small(2) }, + { small(2), small(22), small(3) }, + { small(2), small(10), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(19), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(12), small(2) }, + { small(2), small(9), small(1) }, + { small(2), small(20), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(11), small(1) }, + { small(2), small(7), small(1) }, + { small(1), small(15), small(2) }, + { small(2), small(7), small(1) }, + { small(2), small(12), small(2) }, + { small(2), small(15), small(2) }, + { small(2), small(6), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(24), small(3) }, + { small(2), small(7), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(11), small(1) }, + { small(2), small(14), small(2) }, + { small(2), small(13), small(2) }, + { small(2), small(11), small(2) }, + { small(2), small(11), small(1) }, + { small(2), small(18), small(3) }, + { small(2), small(11), small(2) }, + { small(2), small(17), small(3) }, + { small(2), small(7), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(18), small(2) }, + { small(2), small(9), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(20), small(3) }, + { small(2), small(20), small(3) }, + { small(2), small(11), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(13), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(13), small(2) }, + { small(2), small(4), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(19), small(3) }, + { small(2), small(14), small(2) }, + { small(2), small(14), small(2) }, + { small(2), small(16), small(2) }, + { small(2), small(11), small(1) }, + { small(2), small(13), small(2) }, + { small(2), small(20), small(2) }, + { small(2), small(11), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(4), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(19), small(2) }, + { small(2), small(13), small(1) }, + { small(2), small(14), small(2) }, + { small(2), small(15), small(2) }, + { small(1), small(11), small(1) }, + { small(1), small(13), small(2) }, + { small(1), small(7), small(1) }, + { small(1), small(13), small(2) }, + { small(2), small(19), small(2) }, + { small(2), small(7), small(1) }, + { small(2), small(11), small(1) }, + { small(1), small(24), small(3) }, + { small(2), small(11), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(15), small(2) }, + { small(2), small(6), small(1) }, + { small(2), small(12), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(13), small(1) }, + { small(2), small(10), small(2) }, + { small(2), small(5), small(1) }, + { small(2), small(18), small(3) }, + { small(2), small(8), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(13), small(2) }, + { small(2), small(5), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(8), small(1) }, + { small(1), small(17), small(3) }, + { small(1), small(13), small(2) }, + { small(2), small(19), small(3) }, + { small(2), small(7), small(1) }, + { small(1), small(6), small(1) }, + { small(2), small(14), small(3) }, + { small(2), small(6), small(1) }, + { small(1), small(8), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(14), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(16), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(13), small(2) }, + { small(2), small(5), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(14), small(2) }, + { small(2), small(14), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(11), small(1) }, + { small(2), small(12), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(13), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(15), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(14), small(2) }, + { small(2), small(9), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(9), small(2) }, + { small(2), small(6), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(16), small(3) }, + { small(2), small(7), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(24), small(2) }, + { small(2), small(20), small(3) }, + { small(2), small(13), small(2) }, + { small(2), small(15), small(2) }, + { small(2), small(18), small(2) }, + { small(2), small(15), small(2) }, + { small(2), small(6), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(15), small(2) }, + { small(2), small(6), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(17), small(2) }, + { small(2), small(12), small(2) }, + { small(2), small(10), small(1) }, + { small(2), small(21), small(3) }, + { small(2), small(19), small(2) }, + { small(2), small(7), small(1) }, + { small(2), small(8), small(2) }, + { small(2), small(13), small(2) }, + { small(2), small(12), small(2) }, + { small(2), small(13), small(2) }, + { small(2), small(9), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(16), small(2) }, + { small(2), small(9), small(1) }, + { small(2), small(19), small(3) }, + { small(2), small(22), small(4) }, + { small(2), small(9), small(1) }, + { small(2), small(13), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(16), small(3) }, + { small(2), small(16), small(3) }, + { small(2), small(15), small(2) }, + { small(2), small(11), small(2) }, + { small(2), small(26), small(4) }, + { small(2), small(28), small(4) }, + { small(2), small(17), small(3) }, + { small(2), small(16), small(2) }, + { small(2), small(21), small(3) }, + { small(2), small(17), small(2) }, + { small(2), small(10), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(12), small(2) }, + { small(2), small(15), small(3) }, + { small(2), small(10), small(2) }, + { small(2), small(24), small(4) }, + { small(2), small(12), small(2) }, + { small(2), small(14), small(2) }, + { small(2), small(11), small(1) }, + { small(2), small(13), small(2) }, + { small(2), small(18), small(3) }, + { small(2), small(21), small(3) }, + { small(2), small(10), small(1) }, + { small(2), small(21), small(3) }, + { small(2), small(12), small(1) }, + { small(2), small(11), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(32), small(4) }, + { small(2), small(8), small(1) }, + { small(2), small(13), small(2) }, + { small(2), small(12), small(2) }, + { small(2), small(11), small(2) }, + { small(2), small(13), small(2) }, + { small(2), small(7), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(11), small(1) }, + { small(2), small(19), small(2) }, + { small(2), small(12), small(1) }, + { small(2), small(18), small(2) }, + { small(2), small(16), small(2) }, + { small(2), small(9), small(1) }, + { small(2), small(15), small(2) }, + { small(2), small(10), small(1) }, + { small(2), small(26), small(4) }, + { small(2), small(15), small(2) }, + { small(2), small(25), small(4) }, + { small(2), small(14), small(2) }, + { small(2), small(11), small(2) }, + { small(2), small(23), small(3) }, + { small(2), small(12), small(1) }, + { small(2), small(19), small(3) }, + { small(2), small(8), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(11), small(2) }, + { small(2), small(19), small(4) }, + { small(2), small(22), small(4) }, + { small(2), small(6), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(14), small(2) }, + { small(2), small(13), small(2) }, + { small(2), small(12), small(2) }, + { small(2), small(14), small(2) }, + { small(2), small(13), small(2) }, + { small(2), small(14), small(2) }, + { small(2), small(39), small(5) }, + { small(2), small(19), small(2) }, + { small(2), small(12), small(2) }, + { small(2), small(18), small(2) }, + { small(2), small(11), small(1) }, + { small(2), small(12), small(1) }, + { small(2), small(17), small(2) }, + { small(2), small(7), small(1) }, + { small(2), small(12), small(2) }, + { small(2), small(18), small(2) }, + { small(2), small(14), small(2) }, + { small(2), small(7), small(1) }, + { small(2), small(13), small(2) }, + { small(2), small(7), small(1) }, + { small(2), small(19), small(3) }, + { small(2), small(11), small(2) }, + { small(2), small(17), small(3) }, + { small(2), small(15), small(3) }, + { small(2), small(18), small(3) }, + { small(2), small(9), small(1) }, + { small(2), small(10), small(2) }, + { small(2), small(16), small(2) }, + { small(2), small(16), small(2) }, + { small(2), small(18), small(3) }, + { small(1), small(11), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(13), small(1) }, + { small(2), small(11), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(4), small(1) }, + { small(2), small(13), small(2) }, + { small(2), small(6), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(18), small(2) }, + { small(2), small(13), small(1) }, + { small(2), small(16), small(2) }, + { small(2), small(11), small(1) }, + { small(2), small(18), small(3) }, + { small(2), small(16), small(2) }, + { small(2), small(7), small(1) }, + { small(3), small(7), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(26), small(4) }, + { small(2), small(4), small(1) }, + { small(2), small(14), small(2) }, + { small(2), small(21), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(17), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(7), small(1) }, + { small(3), small(3), small(1) }, + { small(3), small(3), small(1) }, + { small(2), small(4), small(1) }, + { small(2), small(4), small(1) }, + { small(3), small(4), small(1) }, + { small(2), small(5), small(1) }, + { small(1), small(3), small(1) }, + { small(2), small(3), small(1) }, + { small(2), small(11), small(2) }, + { small(2), small(16), small(2) }, + { small(2), small(10), small(2) }, + { small(2), small(12), small(2) }, + { small(2), small(15), small(3) }, + { small(2), small(5), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(12), small(1) }, + { small(2), small(16), small(2) }, + { small(2), small(15), small(3) }, + { small(2), small(13), small(2) }, + { small(2), small(19), small(3) }, + { small(2), small(10), small(1) }, + { small(2), small(18), small(3) }, + { small(2), small(12), small(2) }, + { small(2), small(13), small(1) }, + { small(1), small(18), small(2) }, + { small(1), small(10), small(1) }, + { small(1), small(15), small(2) }, + { small(2), small(10), small(1) }, + { small(2), small(13), small(1) }, + { small(2), small(16), small(2) }, + { small(2), small(17), small(2) }, + { small(2), small(23), small(3) }, + { small(2), small(9), small(1) }, + { small(2), small(19), small(2) }, + { small(2), small(9), small(1) }, + { small(2), small(14), small(3) }, + { small(2), small(10), small(1) }, + { small(2), small(9), small(1) }, + { small(1), small(18), small(3) }, + { small(3), small(7), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(23), small(3) }, + { small(1), small(29), small(4) }, + { small(2), small(22), small(3) }, + { small(2), small(33), small(6) }, + { small(2), small(3), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(4), small(1) }, + { small(2), small(12), small(1) }, + { small(3), small(9), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(4), small(1) }, + { small(2), small(18), small(2) }, + { small(2), small(9), small(1) }, + { small(2), small(15), small(2) }, + { small(2), small(14), small(2) }, + { small(2), small(6), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(13), small(2) }, + { small(2), small(10), small(1) }, + { small(2), small(25), small(5) }, + { small(2), small(8), small(1) }, + { small(2), small(17), small(2) }, + { small(2), small(6), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(20), small(2) }, + { small(2), small(5), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(14), small(2) }, + { small(2), small(4), small(1) }, + { small(2), small(4), small(1) }, + { small(2), small(4), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(6), small(1) }, + { small(3), small(6), small(1) }, + { small(3), small(5), small(1) }, + { small(3), small(5), small(1) }, + { small(3), small(7), small(1) }, + { small(3), small(6), small(1) }, + { small(3), small(6), small(1) }, + { small(3), small(5), small(1) }, + { small(3), small(7), small(1) }, + { small(3), small(7), small(1) }, + { small(3), small(6), small(1) }, + { small(2), small(12), small(2) }, + { small(2), small(6), small(1) }, + { small(3), small(6), small(1) }, + { small(3), small(17), small(2) }, + { small(3), small(10), small(1) }, + { small(2), small(15), small(2) }, + { small(1), small(21), small(3) }, + { small(1), small(14), small(2) }, + { small(1), small(12), small(2) }, + { small(1), small(13), small(2) }, + { small(1), small(15), small(2) }, + { small(1), small(12), small(2) }, + { small(1), small(12), small(2) }, + { small(1), small(16), small(2) }, + { small(1), small(16), small(2) }, + { small(1), small(14), small(2) }, + { small(1), small(8), small(1) }, + { small(1), small(17), small(3) }, + { small(1), small(19), small(3) }, + { small(2), small(16), small(2) }, + { small(2), small(16), small(3) }, + { small(2), small(18), small(3) }, + { small(2), small(13), small(2) }, + { small(2), small(12), small(2) }, + { small(2), small(10), small(2) }, + { small(2), small(12), small(2) }, + { small(2), small(19), small(3) }, + { small(2), small(19), small(3) }, + { small(2), small(18), small(3) }, + { small(2), small(18), small(3) }, + { small(2), small(15), small(3) }, + { small(2), small(18), small(3) }, + { small(2), small(18), small(3) }, + { small(2), small(27), small(4) }, + { small(2), small(18), small(3) }, + { small(2), small(20), small(3) }, + { small(2), small(27), small(3) }, + { small(2), small(8), small(1) }, + { small(2), small(12), small(2) }, + { small(2), small(25), small(2) }, + { small(2), small(18), small(2) }, + { small(2), small(14), small(2) }, + { small(2), small(7), small(1) }, + { small(1), small(17), small(3) }, + { small(1), small(18), small(3) }, + { small(1), small(21), small(3) }, + { small(2), small(24), small(3) }, + { small(2), small(19), small(3) }, + { small(2), small(19), small(2) }, + { small(1), small(4), small(1) }, + { small(1), small(11), small(1) }, + { small(1), small(12), small(1) }, + { small(1), small(11), small(2) }, + { small(1), small(12), small(2) }, + { small(1), small(6), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(4), small(1) }, + { small(2), small(5), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(18), small(3) }, + { small(2), small(23), small(4) }, + { small(2), small(14), small(2) }, + { small(2), small(14), small(2) }, + { small(2), small(14), small(2) }, + { small(2), small(12), small(2) }, + { small(2), small(13), small(2) }, + { small(2), small(20), small(3) }, + { small(2), small(25), small(4) }, + { small(2), small(22), small(3) }, + { small(2), small(20), small(3) }, + { small(2), small(22), small(3) }, + { small(2), small(20), small(3) }, + { small(2), small(21), small(3) }, + { small(2), small(21), small(3) }, + { small(2), small(20), small(3) }, + { small(2), small(20), small(3) }, + { small(2), small(27), small(4) }, + { small(2), small(27), small(4) }, + { small(2), small(21), small(3) }, + { small(2), small(21), small(3) }, + { small(2), small(20), small(3) }, + { small(2), small(20), small(3) }, + { small(2), small(9), small(1) }, + { small(2), small(6), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(12), small(2) }, + { small(2), small(6), small(1) }, + { small(2), small(9), small(2) }, + { small(2), small(6), small(1) }, + { small(2), small(13), small(1) }, + { small(2), small(20), small(3) }, + { small(2), small(13), small(2) }, + { small(5), small(22), small(4) }, + { small(2), small(16), small(2) }, + { small(2), small(17), small(2) }, + { small(2), small(20), small(3) }, + { small(2), small(13), small(2) }, + { small(2), small(8), small(1) }, + { small(2), small(7), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(13), small(2) }, + { small(2), small(22), small(3) }, + { small(3), small(9), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(8), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(9), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(10), small(1) }, + { small(2), small(11), small(1) }, + { small(2), small(11), small(1) }, + { small(2), small(11), small(1) }, + { small(2), small(19), small(3) }, + { small(2), small(12), small(2) }, + { small(2), small(19), small(3) }, + { small(2), small(12), small(2) }, + { small(2), small(16), small(2) }, + { small(2), small(25), small(4) }, + { small(6), small(16), small(3) }, + { small(6), small(14), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(7), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(7), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(11), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(2), small(15), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(9), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(7), small(1) }, + { small(4), small(13), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(14), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(8), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, + { small(4), small(4), small(1) }, + { small(4), small(9), small(2) }, +}; + +const medium ReplacementIndices[] = { +131, 135, 1208, 1284, 151, 671, 54, 61, 63, 107, 109, 143, +159, 240, 241, 246, 247, 257, 372, 431, 507, 508, 509, 545, +563, 564, 578, 661, 662, 682, 683, 684, 688, 762, 770, 771, +796, 800, 811, 813, 829, 830, 831, 832, 833, 846, 889, 922, +962, 987, 989, 992, 1006, 1010, 1028, 1031, 1047, 1147, 1158, 1168, +1171, 1172, 1173, 1188, 1195, 1197, 1209, 1226, 1237, 1240, 1244, 1264, +1265, 1266, 1286, 1287, 1288, 1300, 1301, 1302, 1303, 1304, 1305, 1306, +1307, 1308, 1309, 1310, 1311, 1312, 1313, 1314, 1315, 1316, 1317, 1318, +1319, 1322, 1323, 1379, 1380, 1420, 1421, 1422, 1423, 1424, 1425, 1428, +1429, 1430, 1431, 1432, 1433, 1434, 1435, 1436, 1437, 1438, 1439, 1440, +1441, 1442, 1443, 1444, 1445, 1448, 1449, 1450, 1451, 1452, 1453, 1850, +1851, 1906, 1907, 11, 94, 95, 155, 156, 159, 182, 183, 184, +185, 189, 190, 191, 192, 255, 260, 261, 262, 263, 304, 308, +310, 311, 336, 351, 353, 354, 356, 358, 359, 366, 370, 375, +383, 395, 412, 413, 419, 421, 425, 426, 427, 430, 444, 452, +491, 498, 504, 505, 531, 537, 539, 554, 568, 592, 593, 594, +599, 603, 612, 622, 624, 633, 639, 645, 649, 650, 658, 665, +667, 674, 679, 683, 685, 686, 710, 711, 712, 713, 714, 735, +736, 738, 740, 741, 742, 743, 744, 745, 746, 747, 785, 790, +791, 803, 810, 820, 821, 849, 865, 866, 867, 881, 886, 888, +895, 901, 922, 932, 933, 968, 970, 992, 996, 1007, 1008, 1009, +1020, 1021, 1022, 1023, 1030, 1034, 1038, 1041, 1065, 1066, 1076, 1077, +1078, 1079, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1108, 1109, 1112, +1129, 1131, 1132, 1176, 1177, 1185, 1196, 1206, 1213, 1222, 1224, 1225, +1231, 1252, 1256, 1289, 1290, 1292, 1293, 1302, 1339, 1344, 1345, 1347, +1349, 1353, 1355, 1356, 1357, 1358, 1360, 1362, 1364, 1370, 1371, 1374, +1376, 1377, 1378, 1379, 1385, 1414, 1415, 1454, 1455, 1456, 1457, 1458, +1459, 1460, 1461, 1462, 1463, 1464, 1465, 1466, 1467, 1468, 1469, 1470, +1471, 1472, 1473, 1474, 1475, 1476, 1477, 1478, 1479, 1480, 1481, 1488, +1489, 1490, 1491, 1492, 1493, 1494, 1495, 1506, 1507, 1763, 1764, 1844, +1845, 23, 26, 35, 36, 37, 38, 44, 50, 68, 69, 107, +108, 114, 115, 116, 117, 118, 119, 120, 121, 122, 125, 143, +144, 162, 163, 169, 197, 204, 206, 207, 208, 222, 223, 250, +251, 320, 323, 324, 325, 326, 327, 328, 329, 331, 333, 361, +376, 377, 378, 386, 389, 401, 410, 413, 414, 415, 438, 449, +454, 456, 457, 469, 477, 482, 483, 490, 505, 513, 515, 523, +532, 534, 535, 537, 538, 539, 540, 542, 543, 544, 545, 546, +547, 548, 549, 550, 556, 557, 573, 581, 582, 583, 587, 591, +595, 596, 598, 620, 623, 626, 630, 632, 636, 638, 639, 642, +646, 651, 652, 656, 658, 659, 679, 680, 706, 707, 708, 709, +764, 772, 788, 790, 793, 794, 795, 809, 814, 816, 838, 843, +847, 858, 859, 862, 863, 871, 876, 881, 882, 897, 901, 902, +916, 917, 918, 919, 926, 927, 929, 936, 939, 942, 943, 944, +945, 958, 960, 962, 963, 964, 972, 982, 994, 1000, 1003, 1007, +1028, 1029, 1036, 1041, 1045, 1056, 1063, 1066, 1067, 1068, 1071, 1072, +1073, 1075, 1076, 1077, 1080, 1081, 1084, 1090, 1092, 1115, 1116, 1123, +1140, 1145, 1146, 1147, 1154, 1161, 1167, 1190, 1198, 1228, 1233, 1235, +1238, 1242, 1250, 1251, 1252, 1259, 1266, 1270, 1322, 1323, 1331, 1333, +1336, 1343, 1344, 1346, 1347, 1348, 1349, 1382, 1386, 1388, 1389, 1390, +1391, 1392, 1393, 1394, 1395, 1396, 1397, 1398, 1399, 1400, 1401, 1402, +1403, 1404, 1405, 1406, 1407, 1408, 1409, 1410, 1411, 1416, 1498, 1499, +1500, 1501, 1504, 1505, 1510, 1511, 1514, 1515, 1516, 1517, 1518, 1519, +1520, 1521, 1522, 1523, 1526, 1527, 1528, 1529, 1530, 1531, 1532, 1533, +1534, 1535, 1538, 1539, 1540, 1541, 1542, 1543, 1544, 1545, 1582, 1583, +1652, 1868, 1869, 15, 16, 41, 64, 70, 71, 72, 133, 152, +213, 214, 217, 265, 305, 306, 307, 309, 365, 385, 388, 416, +445, 455, 456, 467, 472, 473, 480, 481, 485, 524, 555, 560, +561, 605, 628, 641, 657, 709, 777, 778, 783, 784, 832, 868, +875, 888, 891, 918, 929, 930, 938, 940, 974, 977, 998, 999, +1027, 1043, 1068, 1074, 1084, 1085, 1090, 1142, 1149, 1150, 1212, 1230, +1240, 1289, 1300, 1301, 1304, 1308, 1313, 1318, 1328, 1330, 1335, 1351, +1352, 1353, 1354, 1355, 1384, 1426, 1427, 1484, 1485, 1546, 1547, 1548, +1549, 1550, 1551, 1552, 1553, 1596, 1597, 19, 22, 23, 25, 26, +58, 77, 78, 117, 165, 175, 178, 179, 258, 308, 310, 311, +384, 406, 407, 408, 417, 458, 484, 497, 507, 508, 509, 580, +597, 797, 858, 893, 969, 979, 1046, 1047, 1048, 1049, 1050, 1133, +1134, 1185, 1205, 1207, 1218, 1219, 1237, 1281, 1331, 1338, 1376, 1554, +1555, 1556, 1557, 1562, 1563, 1564, 1565, 1566, 1567, 1568, 1569, 1838, +1839, 1840, 1841, 1928, 1929, 8, 13, 15, 28, 30, 32, 35, +37, 45, 46, 47, 48, 51, 57, 60, 64, 65, 67, 72, +77, 80, 83, 85, 88, 90, 92, 93, 94, 111, 121, 138, +139, 140, 141, 142, 143, 144, 157, 159, 165, 166, 177, 220, +221, 230, 231, 242, 243, 280, 281, 282, 283, 284, 290, 293, +294, 295, 300, 303, 335, 336, 337, 338, 339, 340, 341, 342, +343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, +355, 356, 357, 358, 359, 360, 393, 394, 396, 400, 404, 405, +423, 442, 443, 479, 481, 490, 494, 501, 510, 516, 518, 519, +520, 521, 522, 529, 530, 554, 558, 594, 601, 608, 610, 616, +620, 634, 661, 662, 663, 666, 670, 678, 684, 698, 699, 700, +752, 753, 797, 821, 844, 848, 856, 861, 873, 883, 915, 923, +938, 943, 946, 948, 952, 966, 971, 1004, 1032, 1033, 1039, 1064, +1076, 1080, 1082, 1083, 1110, 1111, 1187, 1230, 1272, 1277, 1278, 1288, +1298, 1386, 1412, 1413, 1414, 1415, 1416, 1417, 1418, 1419, 1421, 1423, +1425, 1427, 1429, 1431, 1433, 1435, 1437, 1439, 1441, 1443, 1445, 1447, +1449, 1451, 1453, 1455, 1457, 1459, 1461, 1463, 1465, 1467, 1469, 1471, +1473, 1475, 1477, 1479, 1481, 1483, 1485, 1487, 1489, 1491, 1493, 1495, +1497, 1499, 1501, 1503, 1505, 1507, 1509, 1511, 1513, 1515, 1517, 1519, +1521, 1523, 1525, 1527, 1529, 1531, 1533, 1535, 1537, 1539, 1541, 1543, +1545, 1547, 1549, 1551, 1553, 1555, 1557, 1559, 1561, 1563, 1565, 1567, +1569, 1570, 1571, 1572, 1573, 1574, 1575, 1576, 1577, 1579, 1580, 1581, +1583, 1585, 1587, 1589, 1591, 1593, 1595, 1597, 1599, 1601, 1603, 1605, +1607, 1609, 1611, 1613, 1615, 1617, 1619, 1621, 1623, 1625, 1627, 1629, +1631, 1633, 1635, 1637, 1639, 1641, 1643, 1645, 1647, 1649, 1651, 1652, +1654, 1656, 1658, 1660, 1662, 1664, 1666, 1668, 1670, 1672, 1674, 1676, +1678, 1680, 1682, 1684, 1686, 1688, 1690, 1692, 1694, 1696, 1698, 1700, +1702, 1704, 1706, 1708, 1710, 1712, 1714, 1715, 1716, 1718, 1720, 1722, +1724, 1726, 1728, 1730, 1732, 1734, 1736, 1738, 1740, 1742, 1744, 1746, +1748, 1750, 1752, 1754, 1756, 1758, 1760, 1762, 1764, 1766, 1768, 1770, +1772, 1774, 1776, 1778, 1780, 1782, 1784, 1786, 1788, 1790, 1792, 1794, +1796, 1798, 1800, 1802, 1804, 1806, 1809, 1811, 1813, 1815, 1817, 1819, +1821, 1823, 1825, 1827, 1829, 1831, 1833, 1835, 1837, 1839, 1841, 1843, +1845, 1847, 1849, 1851, 1853, 1855, 1857, 1859, 1861, 1863, 1865, 1867, +1869, 1871, 1873, 1875, 1877, 1879, 1881, 1883, 1885, 1887, 1889, 1891, +1893, 1895, 1897, 1899, 1901, 1903, 1905, 1907, 1909, 1911, 1913, 1915, +1917, 1919, 1921, 1923, 1925, 1927, 1929, 1931, 1933, 1935, 0, 3, +84, 99, 104, 186, 194, 196, 209, 210, 211, 212, 268, 269, +270, 271, 272, 274, 275, 296, 298, 299, 300, 302, 303, 352, +353, 355, 357, 358, 360, 461, 464, 511, 517, 563, 570, 613, +643, 651, 652, 653, 655, 675, 676, 680, 681, 685, 686, 719, +720, 721, 722, 781, 783, 786, 864, 885, 983, 993, 995, 1037, +1093, 1128, 1141, 1160, 1219, 1221, 1239, 1418, 1560, 1561, 1584, 1585, +1590, 1591, 1592, 1593, 1594, 1595, 1598, 1599, 1600, 1601, 1602, 1603, +1604, 1605, 1606, 1607, 1608, 1609, 1610, 1611, 1612, 1613, 1614, 1615, +1616, 1617, 1618, 1619, 1620, 1621, 1826, 1827, 1908, 1909, 19, 20, +30, 31, 37, 59, 79, 94, 95, 101, 117, 123, 124, 127, +128, 143, 146, 148, 154, 155, 156, 157, 158, 159, 162, 165, +167, 180, 181, 189, 190, 191, 192, 218, 219, 264, 266, 267, +276, 277, 278, 279, 296, 297, 298, 299, 321, 322, 323, 324, +325, 327, 368, 373, 377, 378, 382, 391, 407, 414, 415, 422, +488, 506, 584, 590, 605, 606, 607, 677, 678, 715, 716, 717, +718, 739, 774, 828, 863, 884, 885, 886, 887, 888, 889, 894, +896, 899, 965, 966, 986, 987, 988, 989, 1013, 1061, 1126, 1127, +1128, 1129, 1130, 1131, 1132, 1133, 1134, 1135, 1136, 1137, 1138, 1139, +1141, 1142, 1210, 1225, 1285, 1315, 1316, 1317, 1318, 1326, 1327, 1328, +1329, 1330, 1343, 1383, 1446, 1447, 1536, 1537, 1622, 1623, 1624, 1625, +1626, 1627, 1628, 1629, 12, 96, 97, 110, 143, 180, 182, 183, +256, 265, 304, 629, 630, 631, 689, 868, 869, 925, 1044, 1048, +1052, 1075, 1084, 1122, 1170, 1188, 1223, 1240, 1258, 1263, 1376, 1482, +1483, 1502, 1503, 1630, 1631, 1632, 1633, 1634, 1635, 1636, 1637, 1638, +1639, 1640, 1641, 1642, 1643, 1644, 1645, 1646, 1647, 7, 98, 99, +113, 116, 248, 249, 363, 765, 766, 767, 768, 769, 859, 907, +935, 1385, 1648, 1649, 1650, 1651, 1653, 1654, 1655, 1656, 20, 21, +22, 23, 119, 172, 330, 332, 334, 367, 397, 576, 577, 661, +662, 663, 687, 776, 837, 905, 928, 958, 997, 998, 1024, 1025, +1026, 1123, 1261, 1283, 1286, 1496, 1497, 1508, 1509, 1524, 1525, 1657, +1658, 1659, 1660, 1661, 1662, 1665, 1666, 1667, 1668, 1755, 1756, 1834, +1835, 1852, 1853, 5, 8, 82, 83, 113, 129, 139, 140, 149, +171, 173, 304, 399, 400, 436, 450, 490, 493, 494, 495, 512, +522, 547, 548, 567, 602, 637, 693, 694, 695, 696, 697, 800, +808, 822, 823, 850, 851, 855, 899, 957, 1028, 1044, 1051, 1055, +1091, 1099, 1101, 1108, 1110, 1113, 1115, 1122, 1123, 1124, 1145, 1162, +1164, 1212, 1224, 1230, 1253, 1258, 1306, 1310, 1311, 1312, 1314, 1316, +1336, 1337, 1354, 1355, 1364, 1365, 1369, 1373, 1374, 1375, 1669, 1670, +1671, 1672, 1673, 1674, 1675, 1676, 1677, 1678, 1679, 1680, 1681, 1682, +1683, 1684, 1685, 1686, 1842, 1843, 1854, 1855, 28, 29, 56, 62, +85, 86, 91, 143, 147, 159, 162, 163, 164, 165, 166, 187, +190, 193, 196, 197, 199, 200, 203, 207, 210, 214, 219, 221, +223, 225, 227, 229, 231, 233, 235, 236, 237, 239, 241, 243, +245, 247, 249, 250, 251, 256, 261, 267, 271, 275, 279, 281, +288, 292, 295, 299, 300, 301, 302, 303, 304, 306, 307, 311, +313, 317, 322, 324, 327, 328, 330, 333, 334, 336, 337, 338, +339, 340, 346, 347, 348, 349, 350, 356, 357, 358, 359, 360, +371, 375, 390, 405, 409, 475, 495, 496, 510, 511, 512, 513, +514, 515, 516, 517, 518, 519, 521, 522, 523, 572, 603, 643, +644, 688, 694, 702, 707, 711, 718, 720, 724, 728, 732, 736, +741, 744, 745, 746, 747, 749, 750, 751, 752, 754, 756, 766, +773, 775, 776, 787, 798, 804, 805, 806, 807, 814, 818, 819, +825, 840, 852, 853, 854, 870, 871, 872, 873, 880, 903, 912, +921, 932, 933, 937, 945, 955, 956, 963, 976, 981, 1012, 1050, +1056, 1057, 1058, 1059, 1117, 1120, 1121, 1133, 1152, 1178, 1179, 1216, +1226, 1233, 1238, 1239, 1241, 1254, 1324, 1327, 1329, 1343, 1360, 1361, +1362, 1363, 1367, 1372, 1387, 1578, 1579, 1687, 1688, 1689, 1690, 1691, +1692, 1693, 1694, 1695, 1696, 1697, 1698, 1699, 1700, 1701, 1702, 1703, +1704, 1705, 1706, 1707, 1708, 1709, 1710, 1713, 1714, 1717, 1718, 1719, +1720, 1721, 1722, 1723, 1724, 1725, 1726, 1727, 1728, 1729, 1730, 1731, +1732, 1757, 1758, 32, 33, 56, 57, 88, 169, 176, 268, 269, +270, 271, 364, 403, 406, 407, 408, 514, 518, 675, 909, 920, +922, 992, 1059, 1069, 1070, 1086, 1087, 1088, 1089, 1090, 1112, 1205, +1206, 1207, 1211, 1212, 1213, 1214, 1216, 1217, 1238, 1267, 1271, 1282, +1294, 1295, 1324, 1325, 1371, 1733, 1734, 1735, 1736, 1737, 1738, 1739, +1740, 1741, 1742, 1743, 1744, 1745, 1746, 1747, 1748, 1749, 1750, 1751, +1752, 1753, 1754, 1761, 1762, 8, 25, 26, 27, 62, 98, 113, +123, 146, 148, 155, 156, 193, 195, 202, 203, 205, 215, 234, +235, 272, 273, 274, 275, 418, 440, 453, 472, 497, 559, 603, +616, 627, 643, 809, 810, 811, 812, 835, 855, 864, 890, 892, +893, 912, 974, 975, 1025, 1053, 1083, 1095, 1133, 1148, 1149, 1150, +1154, 1156, 1157, 1178, 1199, 1202, 1204, 1268, 1274, 1321, 1340, 1352, +1354, 1417, 1765, 1766, 42, 49, 100, 103, 122, 126, 137, 149, +150, 151, 152, 153, 159, 191, 192, 196, 201, 202, 203, 205, +244, 245, 253, 254, 259, 263, 265, 266, 270, 274, 278, 283, +284, 287, 289, 290, 291, 292, 294, 298, 302, 308, 310, 311, +315, 319, 380, 381, 396, 402, 403, 411, 463, 468, 472, 478, +486, 536, 565, 574, 575, 584, 585, 586, 588, 589, 590, 600, +602, 604, 609, 615, 616, 635, 640, 658, 662, 663, 673, 684, +697, 700, 705, 709, 712, 713, 714, 715, 717, 718, 722, 726, +730, 731, 732, 733, 734, 738, 743, 747, 752, 753, 754, 755, +756, 757, 769, 770, 795, 809, 842, 892, 893, 909, 910, 931, +946, 947, 951, 969, 980, 987, 988, 991, 1008, 1014, 1018, 1032, +1033, 1054, 1060, 1061, 1063, 1064, 1069, 1071, 1100, 1101, 1102, 1105, +1106, 1108, 1109, 1110, 1111, 1113, 1114, 1118, 1119, 1122, 1130, 1143, +1144, 1151, 1156, 1169, 1178, 1185, 1211, 1214, 1216, 1226, 1247, 1250, +1258, 1290, 1291, 1296, 1297, 1326, 1386, 1417, 1418, 1586, 1587, 1767, +1768, 1769, 1770, 1771, 1772, 1773, 1774, 1775, 1776, 1777, 1778, 1779, +1780, 1781, 1782, 1783, 1784, 1785, 1786, 1787, 1788, 1789, 1790, 1856, +1857, 512, 516, 521, 522, 1220, 1221, 1791, 1792, 8, 9, 10, +18, 55, 70, 77, 78, 111, 112, 124, 141, 142, 150, 154, +156, 157, 159, 165, 170, 276, 277, 278, 279, 316, 317, 318, +319, 392, 459, 460, 462, 465, 470, 474, 476, 497, 500, 502, +539, 540, 541, 543, 544, 545, 546, 618, 624, 625, 626, 670, +735, 736, 737, 738, 739, 748, 759, 760, 788, 793, 794, 806, +808, 815, 816, 818, 822, 834, 862, 878, 879, 908, 949, 954, +1040, 1086, 1103, 1104, 1106, 1121, 1136, 1174, 1175, 1232, 1257, 1293, +1299, 1305, 1309, 1310, 1314, 1315, 1319, 1320, 1321, 1334, 1345, 1348, +1350, 1351, 1379, 1380, 1419, 1793, 1794, 1795, 1796, 1797, 1798, 1799, +1800, 1812, 1813, 1, 2, 4, 6, 13, 14, 22, 25, 26, +27, 34, 39, 45, 46, 66, 68, 73, 74, 75, 76, 87, +89, 90, 96, 102, 105, 106, 107, 108, 110, 114, 115, 118, +120, 127, 146, 157, 158, 168, 180, 181, 182, 183, 215, 216, +224, 225, 226, 227, 238, 239, 252, 285, 286, 287, 288, 304, +362, 369, 371, 372, 379, 385, 406, 408, 428, 429, 432, 433, +435, 437, 439, 441, 446, 466, 487, 489, 503, 520, 525, 526, +527, 533, 534, 535, 536, 537, 538, 539, 540, 549, 550, 551, +552, 553, 561, 571, 586, 588, 601, 609, 610, 613, 614, 616, +617, 619, 621, 629, 648, 660, 664, 689, 690, 691, 692, 723, +724, 725, 726, 727, 728, 729, 730, 748, 749, 754, 755, 775, +779, 787, 802, 805, 815, 820, 823, 827, 829, 830, 835, 836, +839, 841, 842, 843, 845, 855, 860, 871, 872, 882, 891, 897, +898, 904, 906, 908, 911, 912, 913, 914, 916, 917, 920, 955, +957, 959, 966, 967, 984, 1000, 1001, 1002, 1015, 1019, 1031, 1034, +1035, 1036, 1062, 1069, 1070, 1071, 1072, 1104, 1107, 1139, 1143, 1147, +1148, 1150, 1151, 1155, 1165, 1166, 1171, 1174, 1176, 1185, 1189, 1200, +1203, 1204, 1207, 1217, 1236, 1237, 1238, 1240, 1249, 1256, 1260, 1262, +1263, 1279, 1280, 1292, 1303, 1304, 1326, 1327, 1328, 1330, 1342, 1350, +1351, 1352, 1353, 1356, 1357, 1358, 1359, 1360, 1361, 1362, 1363, 1364, +1365, 1366, 1368, 1369, 1374, 1375, 1376, 1377, 1381, 1558, 1559, 1759, +1760, 1803, 1804, 1805, 1806, 1807, 1808, 1809, 1810, 1811, 1814, 1815, +1816, 1817, 1818, 1819, 1820, 1821, 1822, 1823, 1824, 1825, 1828, 1829, +1830, 1831, 1836, 1837, 1848, 1849, 1860, 1861, 1862, 1863, 1864, 1865, +1866, 1867, 1870, 1871, 8, 25, 26, 27, 51, 53, 80, 81, +92, 93, 130, 132, 134, 136, 146, 174, 198, 199, 200, 201, +228, 229, 232, 233, 256, 264, 266, 267, 321, 322, 374, 398, +434, 442, 451, 471, 483, 484, 485, 486, 492, 499, 545, 546, +556, 557, 566, 579, 611, 647, 655, 657, 668, 672, 756, 757, +758, 761, 762, 763, 764, 780, 789, 792, 799, 801, 812, 813, +817, 824, 826, 850, 851, 857, 877, 878, 879, 932, 933, 934, +949, 950, 953, 960, 961, 990, 1011, 1016, 1017, 1035, 1042, 1052, +1053, 1065, 1067, 1068, 1103, 1135, 1159, 1229, 1275, 1276, 1283, 1294, +1295, 1296, 1297, 1319, 1332, 1341, 1350, 1351, 1378, 1417, 1512, 1513, +1588, 1589, 1846, 1847, 1872, 1873, 1874, 1875, 1876, 1877, 1878, 1879, +1880, 1881, 1882, 1883, 1884, 1885, 1886, 1887, 1888, 1889, 1890, 1891, +1892, 1893, 1894, 1895, 1896, 1897, 1898, 1899, 15, 16, 40, 151, +153, 386, 387, 423, 424, 562, 687, 688, 864, 865, 866, 1004, +1005, 1064, 1067, 1086, 1125, 1180, 1181, 1182, 1183, 1184, 1191, 1192, +1193, 1194, 1215, 1234, 1248, 1269, 1300, 1303, 1307, 1309, 1312, 1313, +1317, 1902, 1903, 1904, 1905, 1910, 1911, 1912, 1913, 1914, 1915, 1916, +1917, 145, 160, 255, 669, 782, 786, 850, 874, 941, 944, 1163, +1179, 1186, 1289, 1486, 1487, 1858, 1859, 1900, 1901, 1918, 1919, 1920, +1921, 1922, 1923, 1924, 1925, 17, 25, 37, 43, 47, 52, 77, +92, 94, 143, 157, 159, 161, 165, 167, 188, 189, 191, 195, +196, 197, 198, 199, 200, 201, 202, 206, 207, 208, 209, 213, +218, 219, 220, 222, 224, 226, 228, 230, 231, 232, 234, 235, +236, 238, 240, 242, 244, 246, 248, 255, 258, 259, 260, 264, +268, 272, 273, 276, 280, 285, 289, 290, 293, 296, 300, 308, +310, 311, 312, 313, 314, 315, 316, 321, 323, 324, 325, 326, +327, 330, 331, 332, 336, 341, 342, 343, 344, 345, 351, 352, +353, 354, 355, 361, 373, 377, 378, 420, 433, 447, 448, 452, +501, 502, 511, 513, 515, 517, 518, 519, 520, 521, 522, 534, +535, 537, 538, 539, 540, 543, 547, 549, 554, 556, 569, 595, +653, 654, 658, 662, 693, 694, 695, 697, 701, 702, 703, 704, +705, 706, 710, 712, 715, 719, 723, 727, 731, 732, 733, 734, +735, 740, 744, 748, 765, 777, 852, 861, 866, 885, 900, 920, +921, 924, 943, 973, 976, 985, 989, 1018, 1032, 1045, 1047, 1058, +1059, 1063, 1067, 1068, 1078, 1090, 1122, 1123, 1149, 1155, 1156, 1187, +1214, 1227, 1233, 1239, 1240, 1245, 1246, 1255, 1316, 1335, 1344, 1346, +1356, 1359, 1361, 1363, 1365, 1412, 1413, 1414, 1801, 1802, 1926, 1927, +1201, 1329, 1663, 1664, 24, 978, 1127, 1153, 1711, 1712, 1930, 1931, +85, 86, 528, 1243, 1273, 1832, 1833, 1932, 1933, 1934, 1935 }; + +struct ReplacementIndexStruct { + utf16char ch; + medium count; +}; + +const checksum ReplacementChecksums[] = { + +0xAE1DAB16U, 0x8F27A2AU, 0x465496D5U, 0x892E9135U, 0xD621DD31U, 0x40D5B583U, 0x23598B8CU, 0x174C4A14U, 0x9739B1DAU, 0xF448853DU, 0xF3DF7DF2U, 0x15D5108FU, +0xE8A0D4E8U, 0x8FBCAF62U, 0x95F9E2A9U, 0x47CEA738U, 0x839E1EBCU, 0xA58AFD2AU, 0xAAA850BDU, 0xBBEE1755U, 0x4F9A8CDDU, 0x77C2915FU, 0x42B9AA72U, 0xABB65511U, +0x984E9DE9U, 0x229620B8U, 0x11744AD7U, 0xDEA4DF8BU, 0x3DDDF327U, 0xC9016AA9U, 0xDD058F8EU, 0x33167FFDU, 0xCF4EB7B4U, 0x5E990F1U, 0xB6610B6U, 0xF3DA224AU, +0xDDF16CC9U, 0x26FBBFD8U, 0xF0D58D6U, 0x9F57820DU, 0xCAEFB5CCU, 0x868C5775U, 0x36F0B13EU, 0xC2104FC3U, 0x635ED438U, 0xFE76A6BFU, 0xAF98DEFEU, 0xC4A1CE0BU, +0x24A144DU, 0xA07F4113U, 0xA1B56E69U, 0x7AC6D4C1U, 0xA6737D27U, 0x3841C7E7U, 0x7BD9C74BU, 0x57960B20U, 0xA3016C3FU, 0x47DE3B8DU, 0x347522B4U, 0x3DD97630U, +0x283C812CU, 0x62FCCC72U, 0x2A8707F7U, 0x4CAAF872U, 0x7DE5251FU, 0xA1CE4738U, 0x65284ADEU, 0x3CAC3D83U, 0xBBB37EE8U, 0xB74B407AU, 0xBB721077U, 0xD5827C60U, +0xE79C25ACU, 0xF3A0B08AU, 0x754CB19FU, 0xC53F5EC8U, 0x2FB36C45U, 0x8A111CA0U, 0x16625752U, 0xC7DA2CAAU, 0x50A359D4U, 0x9E85659AU, 0xA6832DF5U, 0x7F7A32B5U, +0x349608E1U, 0x3CA5EFD1U, 0xBEEDA352U, 0x10FF4E8EU, 0x5A37598EU, 0x92500B16U, 0xD5E5CEF4U, 0xF9ABE6FBU, 0xEF0313FDU, 0x5D682C8AU, 0xD1D48498U, 0x4DA7CF6AU, +0x2B17F5C6U, 0xA903A376U, 0xCA823961U, 0x829E9619U, 0x8F456FB4U, 0xCC6B224AU, 0x75B42ABFU, 0xCD7DECEFU, 0x73290221U, 0xDF8E5CD8U, 0x1D568D7CU, 0x7F2FCD63U, +0x539DB28DU, 0xB9BB5841U, 0x88EB5D8DU, 0x30B6C9CCU, 0xEA5F5485U, 0xF282B417U, 0x46508060U, 0x8C9D5E3EU, 0xBC04162BU, 0xFA329E5DU, 0xC885E4CCU, 0xDCBF9DCAU, +0x4715B337U, 0x2FDC1366U, 0x3E3BBA50U, 0x9F2BF2CU, 0x73F18BAU, 0xB561B097U, 0x6DC1E6EFU, 0x9000A7E8U, 0xEE613496U, 0xCAD661B5U, 0xADEFBC1EU, 0x717A2494U, +0x10ED817CU, 0xD5497538U, 0xC264618U, 0xA723C789U, 0x365CA2E0U, 0xC4AE628U, 0xDAD44170U, 0x69B2A699U, 0xFA94A82BU, 0x9500E45FU, 0x641EDBEU, 0xBBC0F4D2U, +0xE21AA1F5U, 0x7FDEA451U, 0x5F8E4A17U, 0x9682906DU, 0x41B84148U, 0x6659DDC9U, 0xBA4A5BADU, 0x14B31920U, 0x1360E730U, 0x590C5708U, 0xF019A4AAU, 0x5089CA96U, +0xF0B0E775U, 0xFDD90E49U, 0x1BF1824BU, 0xE13025FCU, 0xEF137B9DU, 0x7935115AU, 0x718BD26AU, 0xF09E5811U, 0x7BA332A6U, 0xA7615393U, 0x73FAF11U, 0x55936B7CU, +0x141AF304U, 0x65E071DDU, 0x97589D6U, 0x73A15F22U, 0xB2FDF279U, 0xD6B59BAEU, 0x27ABA3E6U, 0x2367BEC9U, 0xE7C938F9U, 0x560FEC09U, 0x12C5119U, 0x5001267U, +0x85080CCDU, 0xA3B79EC5U, 0x960F8445U, 0xF895CC46U, 0x3A3AF35CU, 0xC4A951A1U, 0x6BE7869BU, 0xEA4D4D5BU, 0xAE72E7B2U, 0x4282D2C1U, 0x33ECB6EBU, 0x4415ADC3U, +0xC78A4A6EU, 0xA58D62B2U, 0xF3E3048EU, 0x1B8D6392U, 0x147A0701U, 0xE63DAAF0U, 0x8DDD9273U, 0xF5A4B231U, 0x9FF25F7FU, 0xDDEA0DB3U, 0x5CEB285U, 0x7DB792C7U, +0x750B1EB5U, 0x57AE509CU, 0xC8F777C2U, 0xBCD1360BU, 0x632D2CA8U, 0xBC462336U, 0x602E733DU, 0x2194100DU, 0x2FB69E71U, 0x58D9C98U, 0xD3907616U, 0x40B025B8U, +0x8638D05AU, 0xE4433DD5U, 0xB399E080U, 0xEB05158DU, 0x21E41D29U, 0xA97B497EU, 0x7DF9E406U, 0x2B2DDA74U, 0xBA0D8072U, 0x23F15693U, 0x69AB4478U, 0xE134102FU, +0x8522D229U, 0x1CDE04C8U, 0x1021B3B1U, 0x685893F3U, 0x548FAB48U, 0x3926C75EU, 0xCD7FFCC1U, 0x95E309CCU, 0x70DCC40DU, 0x5809E8BDU, 0xC3D9F1CBU, 0x15C41B45U, +0x461F4802U, 0xCE801C55U, 0x39156F0DU, 0x3AE08EU, 0x8D001528U, 0x51684523U, 0x85F53FF7U, 0x53E8D579U, 0x81AED8F1U, 0x5DC688FAU, 0x88B16330U, 0x43104043U, +0x364A68E3U, 0xA3F2B218U, 0x84AC213BU, 0x752D5FFCU, 0xA15A7026U, 0x2CD52C5U, 0x8C136450U, 0x1A32A58U, 0xBE1AD3FCU, 0x368587ABU, 0x24762692U, 0xDB6866FU, +0xDBFE03C1U, 0x32805E0BU, 0xBDB344CU, 0xB6576FD7U, 0xCE602BD5U, 0xD9A1C5D8U, 0x1E451C58U, 0xA3C947C3U, 0x8A5E6F27U, 0xA76375AEU, 0x5A7B58AAU, 0xE7F70331U, +0x9E18A785U, 0x5E2B2BB3U, 0x4E3D9008U, 0xF3B1CB93U, 0xFE1D8CFCU, 0xC732037FU, 0xE5F2028FU, 0x922F93ECU, 0x3B1CD1U, 0x489442A4U, 0xF7CEB34U, 0xE3308006U, +0x9E89A82AU, 0x88E25FD7U, 0xBA9183F8U, 0xC98F90D9U, 0x111E8936U, 0xAB056753U, 0x3E5DD491U, 0x83D04BE3U, 0x7419BEF7U, 0xDEC343D9U, 0xDF53B75BU, 0x8D38CBC1U, +0x9E65B31DU, 0x49E9647BU, 0x487990F9U, 0x8586DA3AU, 0x8D4BA4A5U, 0xE27A2FD3U, 0xCF0F75B3U, 0x506B7DCCU, 0x7729EE20U, 0x83A892CEU, 0x1724B686U, 0x1D5443E6U, +0xF994FF2AU, 0x606829CBU, 0x4F6BD1AEU, 0xB8F93024U, 0xF6C1C490U, 0x6F3D1271U, 0xE8119B5BU, 0xB7AC0B9EU, 0xA93D2434U, 0x9CBC7723U, 0x5384167CU, 0x6CC02C32U, +0xE3AEFA3BU, 0x96ACCCB8U, 0x93EDFEB5U, 0xB455ABEU, 0xE0468B3U, 0xD2638E77U, 0x85F33C85U, 0x53C0FE60U, 0x40A0CF69U, 0xCE296866U, 0xDD49596FU, 0x89ED9A7CU, +0xB2168218U, 0x2A16FE9FU, 0xC6158813U, 0x96D819A0U, 0xF1CB7821U, 0xF0B3234EU, 0xC76DD37CU, 0xF6999932U, 0xA6540881U, 0xC1476900U, 0xBF852F50U, 0x885BDF62U, +0x2B6EA5F0U, 0x7BA33443U, 0x1CB055C2U, 0x406B945CU, 0x21FBAB96U, 0x25A9EEF7U, 0x29D0280DU, 0xDCD83354U, 0x95D847CAU, 0x193AD0D7U, 0x65149603U, 0x875F1D51U, +0xF2D6AE5EU, 0x9E89C614U, 0x71040E80U, 0x2F6DB3B1U, 0x4FBD2D4DU, 0x382CE581U, 0x8682E54AU, 0xC6CE3C16U, 0xD0CDB310U, 0xC097D2DCU, 0x61442A6DU, 0x803FC77EU, +0xBFABF3D6U, 0x2AB83F69U, 0xB70E7066U, 0x1BED886U, 0xE496DD92U, 0x125067C1U, 0xF2BDF6E8U, 0xA1E56DA4U, 0x1D17DA74U, 0xD6E2180AU, 0x96AAE39BU, 0x9E79ED3CU, +0xFE814EF2U, 0x55E387B6U, 0x38A8B47FU, 0x1BD7ADCCU, 0xB200788AU, 0xC063E892U, 0x7E1CEC20U, 0x62C476EDU, 0xF83527BEU, 0xCB64655U, 0xF85F383EU, 0xEFE50809U, +0xA32D4B9DU, 0x14325108U, 0xC46CF3FCU, 0x8646A4A2U, 0x24231E6EU, 0xE8DC260CU, 0x9CA8A446U, 0x4A5517EBU, 0xCDBD569CU, 0xB4C0F066U, 0xF1C615D9U, 0x14C9784FU, +0xC35ACFA4U, 0xA1F99F39U, 0xDB5D327U, 0x16208387U, 0x487A6530U, 0x8B83FB15U, 0x47AA297BU, 0xA6838556U, 0xFE99F07BU, 0xAAC72C75U, 0xD7EE632BU, 0xCC9E80CU, +0xDC478D7BU, 0x7E2514C1U, 0xEC7A9229U, 0x7EFF1AE2U, 0xDA58F332U, 0x7FFD9AE0U, 0xD62250F0U, 0x36FF2FD3U, 0x5343E85AU, 0x8ADA0754U, 0x7DE1A231U, 0x73105D3AU, +0x9EA10A8CU, 0x454632DU, 0x2AFBA286U, 0x638306DBU, 0xD0EE5B94U, 0xBDBA49B8U, 0x26400C9AU, 0xB8A32501U, 0xED50973AU, 0x96A4B4A6U, 0x532A2EC9U, 0xC01BA957U, +0x11CDDFF8U, 0x38F60BF1U, 0xAA9B52FCU, 0x709E7D5BU, 0xD63F1DE9U, 0xE5D7E3BU, 0xF7AF95C2U, 0x3D792D12U, 0x77418D32U, 0x3AF7B59U, 0xDF7CC644U, 0x796A3A58U, +0x2A0F1115U, 0x8E3D8B1FU, 0x3EE734ECU, 0xA157B457U, 0x19AD5091U, 0xA348A4DCU, 0x4F107817U, 0xD65CF7B3U, 0xBD678059U, 0x5D63C132U, 0xB71ABEBBU, 0x3E76DFA6U, +0x7B931373U, 0x4C62A6D8U, 0x57F03940U, 0xB2A038EEU, 0x5FDF9A23U, 0xA0263983U, 0x7E882A01U, 0x9DFC1084U, 0x7072D927U, 0xD8FDE69FU, 0xE0E7157BU, 0x58F4B5B9U, +0x57E98EE9U, 0x2B7F17D5U, 0xBF5D536FU, 0x19B4B8DDU, 0x670EE546U, 0x759F5B56U, 0x521D3385U, 0x54FF4F92U, 0x2D9CBF6AU, 0xD0F0ACFBU, 0x41F4137CU, 0x3ECD73DCU, +0xD98F534U, 0xAFC9093DU, 0x11EB63E2U, 0x69341E0FU, 0x45C58EDBU, 0xDFF71D29U, 0xBACC8D3CU, 0x795F863CU, 0xDA5B599FU, 0x756A6CFCU, 0x492FD69AU, 0x29F01476U, +0x6E924FB3U, 0xE8B85ECU, 0x2AC4EC3FU, 0xF77C2327U, 0x4D7BA0B7U, 0xC9C38BCEU, 0xC46345A2U, 0xA8C65A6U, 0x1A0CBBA0U, 0xC6B953FBU, 0xF469483CU, 0xE8DBA058U, +0x55E1E9EDU, 0x220DF006U, 0x752BEE8CU, 0xFBB8B0D5U, 0x16F0C1B5U, 0x6F47CA15U, 0xE7DF0A66U, 0x93AA672CU, 0xA4B0DB5U, 0x9798D36AU, 0x210C73B2U, 0x2D884E0BU, +0x2CF555C5U, 0x2594AF17U, 0xF92DA22FU, 0x4C553278U, 0x559C4DCU, 0x2FEF17FCU, 0xF479A777U, 0x13EC6D08U, 0x507AFB0FU, 0x3DBD6B73U, 0xD3DAA910U, 0x56D07CCDU, +0x76F544DDU, 0x8D972880U, 0xFF842DA6U, 0xAB51C2E8U, 0xFE925847U, 0x1266FB35U, 0x9951A06U, 0xD3FF1DF6U, 0x51D302CEU, 0x66FB4072U, 0x3338DADDU, 0x67610EA3U, +0x8FD669ECU, 0xE3411213U, 0xA28A9DA9U, 0x170B62DFU, 0x46D9B7D7U, 0x3BAA86EEU, 0xF0257FF7U, 0x6DBF878BU, 0x520CEC4CU, 0x4A7596BEU, 0x732E55FDU, 0x36069DA1U, +0x17D3C5EEU, 0xCCBE59DCU, 0xF17154B8U, 0xFED05FF9U, 0x70F66BAAU, 0x668A255FU, 0x5E1E3F24U, 0xC6AA9101U, 0xA9DF1308U, 0x1CE8989FU, 0xF2C4756CU, 0x606CB509U, +0x2394B2A6U, 0x16C80794U, 0x2A219240U, 0xAE1222A3U, 0x8CB97B86U, 0x4CACE737U, 0x8EC4A97AU, 0xE6AE7EDU, 0xB911D42CU, 0x6C05079AU, 0x2A97030AU, 0x1E97978CU, +0xA9209129U, 0x859AA93BU, 0xC97EF510U, 0x66821408U, 0x54C18CD7U, 0x66B85367U, 0x5C604485U, 0x3A9FAE2U, 0x3CD0C352U, 0x92F9FA79U, 0xC17824C7U, 0xB803EAFU, +0x538FB0D3U, 0x948FC967U, 0x41D70FEBU, 0x8F21EB7FU, 0x90F8A446U, 0xD768A2BEU, 0xDDABF113U, 0xD17178C2U, 0x56378FD7U, 0xBED2C72U, 0x93D47BEAU, 0xAEA4D915U, +0xF5601C83U, 0x317F54CBU, 0x10AA0C84U, 0xD907DC46U, 0xE342A78AU, 0xBD65E28U, 0xB389E207U, 0x3BD7614EU, 0xC08B5199U, 0x4E118784U, 0x48C31E39U, 0x7CF71C84U, +0xB0B24B2CU, 0x1FE25283U, 0x629F7C2DU, 0x57EF2916U, 0x2B576B96U, 0xCE051BEAU, 0xEC16C919U, 0xBFF3A0CBU, 0xE838218DU, 0xEE352A21U, 0xE38E5B7FU, 0x3A8BB7DFU, +0x1F0ECE93U, 0x5385D9B7U, 0xAC29F8F4U, 0x575DB2ADU, 0xACBF183FU, 0x567EEE14U, 0xE8AD89DBU, 0xE3E9CEBBU, 0xD8CE4D00U, 0xBA789F5AU, 0xF87F77BBU, 0x4A4B351BU, +0xCF96A065U, 0x74744A1FU, 0xD93F0955U, 0x4F6722B3U, 0xFDCD24EEU, 0xE6A9F921U, 0x166CC0D6U, 0xB92186A0U, 0x29211071U, 0xD978BCDDU, 0xB14992FCU, 0x19854E77U, +0x3FE60A5U, 0x58FC1E58U, 0x10475664U, 0xDAFCA3E6U, 0xFDCAE1D1U, 0x82D2915DU, 0xE3DFF22AU, 0x130FDF55U, 0xD3B59A0FU, 0xD5F60290U, 0x6CCD0385U, 0x58438A99U, +0xDE26F199U, 0x3E40DEB9U, 0x706B5E46U, 0x3B8DBFD5U, 0x28CAFF29U, 0xD0A47AE4U, 0x3AA9B57AU, 0x8504AFF7U, 0x5E28BCA3U, 0xFBE90C93U, 0xEAD04C12U, 0xCB24B726U, +0xF6C65A18U, 0x2A34D380U, 0x62E270A6U, 0x6E546C36U, 0x31006A54U, 0x29503BFAU, 0xBF30759DU, 0xA18BD87CU, 0xA2D5259U, 0x34D4508EU, 0xCDF525B8U, 0xD28710F0U, +0xB2863FDDU, 0x9F9E5922U, 0xE1642FD1U, 0x12D001U, 0x16F6CE5BU, 0x70B1EF12U, 0xA6AC059CU, 0xBBEBD881U, 0x472BCFE6U, 0xE62890CU, 0xA80B1464U, 0xC5A27872U, +0x3BEA65ABU, 0xA8392847U, 0x279F356AU, 0x7F03C067U, 0x7D764E6CU, 0xA208B04BU, 0x110FA646U, 0xFC591AA1U, 0x4B671A82U, 0x8737FAAAU, 0x552AF746U, 0x21D61F84U, +0xB82AC965U, 0x3B3F074FU, 0x60BBD08AU, 0x426CB588U, 0xDB906369U, 0x9B6900FEU, 0x3017A86U, 0xEEDF7292U, 0xC60A5E22U, 0x19DDB430U, 0x7B87C150U, 0x49FFE8C2U, +0x875E3C9U, 0xEFB8D540U, 0xBB551FEU, 0x485E57B1U, 0x7171D832U, 0xAA37823U, 0xB678C79CU, 0x9AD6EC38U, 0x6CB5177AU, 0xE42A432DU, 0xA711D2F5U, 0xDF1942E9U, +0x92D028AU, 0xF00C77BCU, 0x7049BC1CU, 0xA2670B26U, 0x20B4D99EU, 0x8BEAF43BU, 0xEA39A146U, 0x14F38D7U, 0x3A72453FU, 0x464E138BU, 0xC1B90DE8U, 0x9EAD10B7U, +0x1E4CB9E4U, 0x2CF77C5BU, 0x8927E59AU, 0x7116D68AU, 0xBB545FEBU, 0x81B4AA98U, 0xD01859CAU, 0xDF71061U, 0x479A64C4U, 0x71B1C5DBU, 0x5964E96BU, 0xF1560394U, +0xF28823A4U, 0xE4E97619U, 0x1295A088U, 0x7049FDAU, 0xE592D573U, 0x3535BE69U, 0x1619B844U, 0xD727F3DEU, 0x13232E2BU, 0x51338796U, 0x4B84738U, 0xF482392DU, +0xAB40F973U, 0x9E36BDD6U, 0x3845F022U, 0xA2F40E8BU, 0xC698595DU, 0x87D8352DU, 0xEEB9CC7FU, 0xC644164DU, 0x4CDBDEAFU, 0xE2DE7491U, 0x31F7EB3DU, 0x9599038U, +0x9D918DA1U, 0x65E89B01U, 0x886E82A6U, 0xFFFE534AU, 0xE151673CU, 0x2026F087U, 0x6D682B4EU, 0xDEF444BDU, 0xF1416D33U, 0xBA2D6D03U, 0xEEFF589AU, 0xD41F5106U, +0xDE088E4CU, 0x999A79F6U, 0xE8AEA3D0U, 0xE93324AEU, 0xAD49F7U, 0x5FE7A23DU, 0xC1728CACU, 0x60B9C80EU, 0xE3F2AFEBU, 0x95F0722U, 0xA039D72EU, 0xD6262CCFU, +0x76C373F8U, 0xB040A838U, 0xCF9DD85FU, 0x89EE399BU, 0x4280BC28U, 0xFC69FC63U, 0x3D05B634U, 0x49B5C357U, 0x6E56C308U, 0x1754FB6U, 0xBB51865DU, 0x8715888CU, +0x1D1552DU, 0xB7351D91U, 0x4537E08EU, 0xAD2738D0U, 0xAE6891D3U, 0x3D480692U, 0x964B44CEU, 0x7F89BA71U, 0x2E2BD03U, 0x7686B89FU, 0xDC21E010U, 0xC76B9756U, +0x51599457U, 0x9E6B1EDU, 0x9A6FBFC1U, 0xBBC871A0U, 0xC3FAEA7CU, 0xFAD6BDE8U, 0x1D426A4FU, 0x54E93201U, 0x23516287U, 0x7D2AAB9U, 0x3D14944U, 0x20F91670U, +0x7A1502CDU, 0x5A8B6DF7U, 0x2D7BE547U, 0xC7686672U, 0x6101B9D7U, 0x6D9E58AFU, 0xEB0B3F26U, 0x92FD1CE6U, 0x10438777U, 0xB9A6DF98U, 0x8F21D6D6U, 0xA5DC42D9U, +0xAA8A7A11U, 0x97ABDF97U, 0xF820B371U, 0x12E7060FU, 0x7898B7E6U, 0xECC6032BU, 0x9CD6CAB9U, 0x3BC2405BU, 0x16974E59U, 0xB1168D81U, 0x2F1E6611U, 0xCEFB7A8CU, +0x399695C7U, 0x116814AFU, 0x588FA645U, 0xC9B298CDU, 0xC2BC200U, 0x264CB5CEU, 0x9C43107EU, 0xA1A8D07EU, 0xE6BF9B30U, 0xDAAEEB22U, 0x9AE019F0U, 0xE91C586BU, +0x2C799952U, 0xE5A1E7CEU, 0x405A2DB6U, 0x804346D2U, 0xBE708250U, 0x67B107D0U, 0xC596A1FBU, 0xB7E8AC33U, 0x829235A2U, 0xA0D9823DU, 0x46D24122U, 0x2B9EA93U, +0xA1C073C6U, 0xC4EAFBEU, 0x9CBCA0CEU, 0x87CB523EU, 0x93F53F24U, 0x30F100CBU, 0xC76E5C71U, 0xC5B21FE9U, 0xEEEE5BF8U, 0xBB647D0EU, 0xE0A73F06U, 0xC685C32DU, +0x1C5C9B9BU, 0x2A44CEAU, 0x9534296EU, 0x5A5EC350U, 0xB3780B29U, 0xE8A4B00U, 0xF8327784U, 0x996FC0C8U, 0xD67C8539U, 0x621659DDU, 0x1FD6996U, 0xB398D6D2U, +0xBBEC03D6U, 0x3F8504F6U, 0x9FA1B8CEU, 0x83ADB8A5U, 0x9004789FU, 0x99ED15F4U, 0x6883E2DU, 0x3C75FFACU, 0xD2391F74U, 0x66EE73F1U, 0x8FA89A21U, 0x801A7503U, +0x522E1784U, 0x36392E2EU, 0x19DF8128U, 0x6EA25305U, 0x846C8437U, 0x4EE0BEACU, 0xF690FDC5U, 0xF6832E1EU, 0xA48C9379U, 0x5F8921B0U, 0xFE3D1F9CU, 0x4B6BADFEU, +0x2DDBBD52U, 0xFA227036U, 0xFE4FE03AU, 0xD4BD8877U, 0xC255594EU, 0x8A61FE32U, 0x63B85E32U, 0xC24637DFU, 0x39229F39U, 0xF9B405A4U, 0x3EBF9329U, 0xF28B6E98U, +0x81DAA8C9U, 0x55ACEF34U, 0x296DE97FU, 0x5953C3AU, 0x7268DC76U, 0x49C2BAAAU, 0x1FD82E79U, 0xE54C1803U, 0x9CD98442U, 0x4C57DE89U, 0x7FD97D63U, 0x5B3CB6F5U, +0x67B3B2E4U, 0x3F4D318AU, 0x3EC0ABB7U, 0xEE3200C0U, 0x3FA3D082U, 0x4D0F7C4AU, 0x892D309CU, 0x56B3DD2CU, 0xEB5F7612U, 0x2EE42882U, 0xCF07E495U, 0xF2A9BC6BU, +0x9DA909A4U, 0x937B3A8U, 0x363217CFU, 0x9AED0006U, 0x60362015U, 0x5226B280U, 0x8524AEA3U, 0xFBC8F58DU, 0x7B172312U, 0x9CCC34DU, 0xA8AE6F61U, 0xE3CA111EU, +0x59FE8325U, 0x6EC95B2U, 0xF676DCD9U, 0x9A250AE3U, 0x99E585E7U, 0xA62C89BU, 0xC631DEF4U, 0x58BC30C1U, 0x470412E1U, 0xC05117F0U, 0xA51D983CU, 0x1E629C01U, +0x541C0E33U, 0xEB80059DU, 0x129D07E0U, 0x48EDAAD6U, 0x8397F485U, 0x1857F8F0U, 0x97497FE1U, 0xA8AEC1F6U, 0xC8027F76U, 0x736E96A8U, 0x1A578E1AU, 0x6B9FD701U, +0x8B4734B7U, 0x7102220U, 0x13FE645BU, 0xE25D1A59U, 0x7A7D69ECU, 0x9295838AU, 0x6761FB08U, 0x493105F6U, 0x13BF90E3U, 0x6691E578U, 0xC741FD85U, 0x62E01643U, +0xE782CAB0U, 0x3223B5F4U, 0xB36E1464U, 0x2C04AA45U, 0x9260D4CBU, 0x660DB966U, 0x41F2BCF7U, 0xF17D8432U, 0x17C405A6U, 0x87274585U, 0x537E9E9EU, 0x7B344BCU, +0x1E023531U, 0xDD6783A8U, 0xFA37738DU, 0xD7D7F72CU, 0xEAADB26BU, 0x5B511B45U, 0x240A687DU, 0x44E66B55U, 0xD0808C97U, 0xF31BFF35U, 0x9EE5345CU, 0x258906C2U, +0x9860CC4AU, 0xB9CCB14FU, 0x5580726EU, 0x26B182EU, 0x4F6C5C89U, 0x5D26775DU, 0xF0CA22ADU, 0x2DD7CAC5U, 0xD0EDAF27U, 0x8C09D6DU, 0xDCFB88F2U, 0x71C63BCFU, +0xAEC8C854U, 0xC3ADC8F6U, 0xE93C7B65U, 0xC698C893U, 0x8E574092U, 0x92FF3CB6U, 0xF8648ABAU, 0xB5BC2991U, 0x5E05BD9DU, 0xB09A78A0U, 0x15AB67C0U, 0x3FADF7CEU, +0x2927A3FDU, 0x445BA5DBU, 0xFA3008B2U, 0x2891DB6BU, 0xDBB845FU, 0x573B85ECU, 0x18F54327U, 0x74F4356BU, 0x95971E70U, 0x4470A211U, 0xB905173DU, 0x414D94A9U, +0xE929B3ADU, 0xAE3B8A7AU, 0xFF18EB1U, 0xEE383B0CU, 0x7B04D607U, 0x693E6DA4U, 0xC3FB577EU, 0x7AC48735U, 0x1A99C4ADU, 0x45B0C657U, 0x5CC05A5U, 0xE24AE587U, +0x884FA08EU, 0x5BCE0610U, 0xCCBADA00U, 0xEEBE62E2U, 0x1BED96D4U, 0xFF9731C4U, 0x5644B2A0U, 0x9119ABCAU, 0x28FC1FE4U, 0x983CF251U, 0xF92F5A3BU, 0xDC9DC061U, +0x5B150730U, 0xCB5F1CBDU, 0x5966470CU, 0xE5C92630U, 0x974502D1U, 0x2047F98CU, 0x444D6B88U, 0xFB8235EU, 0x359D6CU, 0xC34153C2U, 0x2A95D777U, 0x5B6E4E6FU, +0xB408F38U, 0x2423E46EU, 0x4C85DCE9U, 0xBBEEC84BU, 0xE9D46305U, 0x30187C97U, 0x181599U, 0xF4F90026U, 0x952F111CU, 0x5B9D9750U, 0x624304BEU, 0xCE566F2EU, +0xF5B066E3U, 0xC84AEDC4U, 0x26984E70U, 0xD637245CU, 0xAEADB1U, 0x7F65D377U, 0xDD7410C6U, 0xE156A6E8U, 0x2BDB808BU, 0x3107D6E5U, 0xA654151FU, 0x5DBD1349U, +0x29676B1CU, 0xD9B9A12BU, 0x559571C7U, 0x17D9B1EFU, 0x8EBB69EAU, 0x6E834FD7U, 0x22AF6658U, 0x50FD3DEBU, 0xA35B6E1U, 0x87B78513U, 0x5CA68B6U, 0xF3B77C65U, +0xDAE7ACC8U, 0xBD84E707U, 0xC0B1F2EEU, 0x7C5B280BU, 0x3C82C2A7U, 0xF1848767U, 0xCF397063U, 0x11293CECU, 0x86B3A406U, 0xCF0391E2U, 0xCAE6F488U, 0xA2130914U, +0x80443800U, 0x75A52C98U, 0x255D3BC5U, 0x70B8141FU, 0x52A6D7E8U, 0xF3773CB5U, 0x16682961U, 0xE291D23CU, 0xEA1AB08DU, 0x3AF6AC29U, 0xF077AF0BU, 0xDB42975FU, +0xE4B5A5C3U, 0x828767D9U, 0xCE511DFDU, 0x8FA20C6DU, 0xD4FEEEB4U, 0x3B75D536U, 0x81090466U, 0xB210CB77U, 0xA0A56499U, 0x601ADD2U, 0xAB167BEAU, 0xF706C300U, +0x1B058070U, 0x9F01D362U, 0x52CFBC44U, 0x96A9AB7BU, 0x966588D6U, 0xB899A286U, 0x5EE0407AU, 0x681CEAFFU, 0x4C40811U, 0xB20B7269U, 0x367237BFU, 0x428A0B94U, +0xD691C645U, 0xB12CFF27U, 0x9E6383BEU, 0x6176461EU, 0xFDB47228U, 0x51A94E60U, 0xFBD6A3E0U, 0xF187D05EU, 0x4DA143CFU, 0x49C7CCE6U, 0xB4B52BF7U, 0x3BA3A346U, +0xA803AAFEU, 0x9FC8C67BU, 0xA639766CU, 0x4C6B2408U, 0xED09AD3DU, 0xDBBD48U, 0xE0E2196DU, 0xE9FFEE62U, 0xB50384FU, 0xDFE188A2U, 0x3633AE21U, 0xAEF9F8C9U, +0x80178502U, 0xED42BC9CU, 0x85653DDDU, 0x394C79C4U, 0x71477B3DU, 0xF8C674CFU, 0xD4B8C4E9U, 0x2A5947F9U, 0xFC4C64D2U, 0x6B322B31U, 0xB9B20DFBU, 0x57BB1739U, +0x3D915162U, 0x5AAAA1ACU, 0x91EB2A66U, 0x35CA03C9U, 0xDE6D0202U, 0xCB2E089FU, 0x57048634U, 0xEEBBC058U, 0x3813A5F6U, 0x722CE848U, 0x8479A8A6U, 0xE92B8076U, +0x1B1F3F83U, 0x81D8932FU, 0xFDDDB092U, 0x30D5B4C9U, 0xC91CF267U, 0x36AD135BU, 0x71F52515U, 0xC089EF35U, 0x41038328U, 0x6456724BU, 0x1B2B3E23U, 0xD24210BBU, +0xC62FBD71U, 0xFCAD3662U, 0x8E3F7FF2U, 0xE48AE1B8U, 0xD23BC8ABU, 0xEF89F436U, 0x62BCE039U, 0x1A5DE46CU, 0x16236FU, 0xCA241E6DU, 0x9EEE203BU, 0x3D3879E4U, +0x1D1C6180U, 0xD378251U, 0xE2BA4A87U, 0x66DFA1A0U, 0x7418FB2CU, 0x6CB7295CU, 0x9F7002F9U, 0x1F7DE611U, 0x5FA0D6B3U, 0xA466F742U, 0x8E097065U, 0x7F7B3458U, +0x25360EB8U, 0xE8ADA4B0U, 0x18B1FE00U, 0x2AC22D1CU, 0x4186B027U, 0x959C76EAU, 0x8872FA1EU, 0x486317D3U, 0xED35E29BU, 0xB17E878U, 0x8F6CC5FCU, 0x2E8831DBU, +0xFA55FF05U, 0x6FEFF36U, 0xBE942D00U, 0x36B6480EU, 0x4C5D91EFU, 0xEB2C7AC2U, 0xEA33683AU, 0xB8A51C5EU, 0x8BF2227BU, 0xF37E24F6U, 0x864012FU, 0x5DE7786U, +0x3EFEC660U, 0xE2761ACU, 0x54A6C4ABU, 0x4047D505U, 0x37FBD45CU, 0x65D5D78BU, 0xBE3CF030U, 0x4A150EFBU, 0x2F283083U, 0xCB47C04DU, 0x973EDABU, 0xD6BC301FU, +0xD792EACU, 0xEEA55E7U, 0xAB40BD74U, 0x2CAC2CA8U, 0x9B8691CAU, 0xF0EF8D89U, 0xA743045FU, 0xB76BFEDU, 0x76DD731AU, 0x3752687U, 0x968C7740U, 0x770D8687U, +0xF6DE8D96U, 0x91B3A983U, 0x7228545U, 0xA8134914U, 0xFD9A01E6U, 0x72386ADDU, 0x845A80A6U, 0x10675D75U, 0x31032835U, 0xB6510D2AU, 0x67E9C2D9U, 0x8C073B5FU, +0x348F2BA5U, 0xE4DF38ECU, 0x535EDE00U, 0xE31FD2EFU, 0xAA5BD7DCU, 0x9D963F06U, 0x8D26D2E9U, 0x5D76C1A0U, 0x95131597U, 0x25521978U, 0xA549BA78U, 0xFC809EA1U, +0x9CC1A4E2U, 0x58043720U, 0xE3BD3B7BU, 0xED684815U, 0x8A1B48E8U, 0x9C5BFCA1U, 0x1C5A009AU, 0x7485B1C1U, 0xCA86F28AU, 0x32FA853AU, 0xB3626483U, 0x972DA82DU, +0xE492749AU, 0x35CA2D1DU, 0xC1E25D40U, 0xFAA559E2U, 0x27472E18U, 0xEA8FB72AU, 0x5D846824U, 0x77540CDAU, 0x45717960U, 0x57C4D68EU, 0xEF78B1EBU, 0x72AF8952U, +0xCA13EE37U, 0xD8A641D9U, 0x601A26BCU, 0x387936EAU, 0x80C5518FU, 0x47E9CBACU, 0xFF55ACC9U, 0xEDE00327U, 0x835DF178U, 0xDD2F69BU, 0xC178F605U, 0xCBBDFF1CU, +0x717FF82U, 0x8998F861U, 0x4532F8FFU, 0x9C12EA53U, 0x50B8EACDU, 0x2E8AEB86U, 0xE220EB18U, 0x6CAFECFBU, 0x6D9D258CU, 0xBC42E87CU, 0x61A4E33U, 0xCFB8C4FU, +0x81664E1AU, 0x13BBA529U, 0x5BFB8755U, 0x5B9E60F1U, 0x89633A85U, 0x3E0560D0U, 0x39A51A29U, 0x8EC3407CU, 0xE6DEDAE1U, 0x51B880B4U, 0xC3614311U, 0x74071944U, +0xEEA49D28U, 0x59C2C77DU, 0x236AF20EU, 0x940CA85BU, 0xF46B750FU, 0x430D2F5AU, 0xD1002AD3U, 0x66667086U, 0x44AD55A3U, 0xF3CB0FF6U, 0x31DF5DE0U, 0x86B907B5U, +0x5618FA4DU, 0xE17EA018U, 0x5E62BD84U, 0xE904E7D1U, 0x61C60A7FU, 0xD6A0502AU, 0x796DEC4CU, 0xCE0BB619U, 0xCBCFC2F4U, 0x7CA998A1U, 0x7373A591U, 0xC415FFC4U, +0x93ACD2A2U, 0x24CA88F7U, 0x6830EF86U, 0xDF56B5D3U, 0xEF283F18U, 0x584E654DU, 0xA5FE80A0U, 0x1298DAF5U, 0x8095DF7CU, 0x37F38529U, 0x78D0FE2U, 0xB0EB55B7U, +0x1D42E7C5U, 0xAA24BD90U, 0x1538A00CU, 0xA25EFA59U, 0x4521F793U, 0xF247ADC6U, 0xD8F6CF2AU, 0x6F90957FU, 0xF5E7D73FU, 0x42818D6AU, 0x72FF07A1U, 0xC5995DF4U, +0x92207092U, 0x25462AC7U, 0xE75278D1U, 0x50342284U, 0xD08C88E3U, 0x67EAD2B6U, 0x1838F762U, 0xAF5EAD37U, 0x6112CC53U, 0xD6749606U, 0x2F0C6FC3U, 0x986A3596U, +0xCA4360C4U, 0x7D253A91U, 0xB74B2F4EU, 0x2D751BU, 0xFF7482BU, 0xB891127EU, 0x5794587DU, 0xE0F20228U, 0xC8276ED0U, 0x7F413485U, 0x13AA1C8FU, 0xA4CC46DAU, +0x597CA337U, 0xEE1AF962U, 0x52EE48DAU, 0xE588128FU, 0x94B2CC11U, 0x23D49644U, 0xC239270DU, 0x755F7D58U, 0x20825E2AU, 0x97E4047FU, 0xC4AB9B8EU, 0x73CDC1DBU, +0x70716126U, 0xC7173B73U, 0xAC65B06DU, 0x1C7021BFU, 0x11FB361U, 0xB679E934U, 0x746DBB22U, 0xC30BE177U, 0xF3756BBCU, 0x441331E9U, 0xB9A3D404U, 0xEC58E51U, +0xFFF99EE2U, 0x489FC4B7U, 0x7C17FCEBU, 0xCB71A6BEU, 0x4106B2AAU, 0xD9C40950U, 0x36C14353U, 0x81A71906U, 0x1BD05B46U, 0xACB60113U, 0x9CC88BD8U, 0x2BAED18DU, +0x7117AB85U, 0xC671F1D0U, 0x860763FFU, 0x316139AAU, 0x2C0EAB74U, 0x9B68F121U, 0xCCD1DC47U, 0x7BB78612U, 0xDE6473A9U, 0x690229FCU, 0x2BC473EBU, 0x9CA229BEU, +0x9378148EU, 0x241E4EDBU, 0xEAF2C37U, 0xB9C97662U, 0xA4A6E4BCU, 0x13C0BEE9U, 0x252C88A1U, 0x924AD2F4U, 0xAA4E1FF6U, 0x1D2845A3U, 0x9764CE8AU, 0x200294DFU, +0x92F4B6BEU, 0x2592ECEBU, 0xCD89B85BU, 0x7AEFE20EU, 0x47D77DU, 0xB7218D28U, 0xE8E2E787U, 0x5F84BDD2U, 0x505E80E2U, 0xE738DAB7U, 0x660CD2E0U, 0xD16A88B5U, +0xE96E45B7U, 0x5E081FE2U, 0xDEB0B585U, 0x69D6EFD0U, 0xCC051A6BU, 0x7B63403EU, 0xFEA1FAFEU, 0x49C7A0ABU, 0x4B1DCAF5U, 0xFC7B90A0U, 0xB1D993CDU, 0x6BFC998U, +0x5F3AD998U, 0xE85C83CDU, 0x41E9EBBBU, 0xF68FB1EEU, 0xDA78A9ADU, 0x6D1EF3F8U, 0xC2EDE121U, 0x758BBB74U, 0x883B5E99U, 0x3F5D04CCU, 0x4D8F7676U, 0xFAE92C23U, +0xCB1B04D8U, 0x7C7D5E8DU, 0xBFE5AEABU, 0x883F4FEU, 0x759C9CEU, 0xB03F939BU, 0x80411950U, 0x37274305U, 0x308739FCU, 0x87E163A9U, 0xF5331113U, 0x42554B46U, +0x2A48D1DBU, 0x9D2E8B8EU, 0x1D9621E9U, 0xAAF07BBCU, 0xA52A468CU, 0x124C1CD9U, 0xE786BEFDU, 0x50E0E4A8U, 0x9A8EF177U, 0x2DE8AB22U, 0xB79FE962U, 0xF9B337U, +0x57409E51U, 0xE026C404U, 0x547CF459U, 0xE31AAE0CU, 0x6BD843A2U, 0xDCBE19F7U, 0x5C06B390U, 0xEB60E9C5U, 0xECC0933CU, 0x5BA6C969U, 0x2F71F45U, 0xB5914510U, +0xA0849007U, 0x17E2CA52U, 0x5D8F4DD1U, 0x785F2A36U, 0xBA4B7820U, 0xD2D2275U, 0xA8FED7CEU, 0x1F988D9BU, 0x77851706U, 0xC0E34D53U, 0xB2313FE9U, 0x55765BCU, +0xA8D588CU, 0xBDEB02D9U, 0x9F2027FCU, 0x28467DA9U, 0x34A54D47U, 0x83C31712U, 0x96D6C205U, 0x21B09850U, 0x335DBD17U, 0xF11165A8U, 0x46773FFDU, 0x9EAC85CCU, +0x29CADF99U, 0x3237F1C4U, 0x8551AB91U, 0x3A4DB60DU, 0x8D2BEC58U, 0x709B09B5U, 0xC7FD53E0U, 0x5FD0B10BU, 0xE8B6EB5EU, 0xC05D2919U, 0x773B734CU, 0x90447E86U, +0x272224D3U, 0xA82A11E2U, 0x1F4C4BB7U, 0x65E47EC4U, 0xD2822491U, 0xBA9FBE0CU, 0xDF9E459U, 0x523A8EF6U, 0xE55CD4A3U, 0xEA86E993U, 0x5DE0B3C6U, 0x3D876E92U, +0x8AE134C7U, 0x6D9E390DU, 0xDAF86358U, 0xCFEDB64FU, 0x788BEC1AU, 0x7751D12AU, 0xC0378B7FU, 0x83A9B574U, 0x34CFEF21U, 0xCCB2223U, 0xBBAD7876U, 0x461D9D9BU, +0xF17BC7CEU, 0x1604CA04U, 0xA1629051U, 0xF6DBBD37U, 0x41BDE762U, 0xAEB8AD61U, 0x19DEF734U, 0x911C1A9AU, 0x267A40CFU, 0x4B165EAU, 0xB3D73FBFU, 0x1E7E8DCDU, +0xA918D798U, 0x336F95D8U, 0x8409CF8DU, 0x21DA3A36U, 0x96BC6063U, 0xBC0D028FU, 0xB6B58DAU, 0x9CF62567U, 0x2B907F32U, 0x4E67DA52U, 0xF9018007U, 0x43678D3CU, +0xF401D769U, 0x54A83275U, 0xE3CE6820U, 0xC97F0ACCU, 0x7E195099U, 0x3B15D211U, 0x8C738844U, 0xEC145510U, 0x5B720F45U, 0x99665D53U, 0x2E000706U, 0x6376C247U, +0xD4109812U, 0xE46E12D9U, 0x5308488CU, 0x29A07DFFU, 0x9EC627AAU, 0xE5E2B0E9U, 0x5284EABCU, 0xA74E4898U, 0x102812CDU, 0xD478013U, 0xBA21DA46U, 0x17886834U, +0xA0EE3261U, 0x4FEB7862U, 0xF88D2237U, 0x62FA6077U, 0xD59C3A22U, 0x20569806U, 0x9730C253U, 0x6A8027BEU, 0xDDE67DEBU, 0x318A297CU, 0x77EFB560U, 0x3A997021U, +0x8DFF2A74U, 0x78358850U, 0xCF53D205U, 0x5D8A11A0U, 0xEAEC4BF5U, 0x8BD3F2BDU, 0x3CB5A8E8U, 0x8DAB26ADU, 0x3ACD7CF8U, 0x53DC7DAU, 0xB25B9D8FU, 0x5FEE1FB4U, +0xE88845E1U, 0x6468DCF4U, 0xD30E86A1U, 0xB3833366U, 0x4E56933U, 0xA94CDB41U, 0x1E2A8114U, 0x262E4C16U, 0x91481643U, 0xDC3ED302U, 0x6B588957U, 0xF9558CDEU, +0x4E33D68BU, 0x4993AC72U, 0xFEF5F627U, 0x535C4455U, 0xE43A1E00U, 0xA1369C88U, 0x1650C6DDU, 0x845DC354U, 0x333B9901U, 0x2E540BDFU, 0x9932518AU, 0xBBF974AFU, +0xC9F2EFAU, 0x9E922B73U, 0x29F47126U, 0x176200A7U, 0xA0045AF2U, 0x1E94E55EU, 0xA9F2BF0BU, 0x7129053AU, 0xC64F5F6FU, 0x4E8DB2C1U, 0xF9EBE894U, 0xE4847A4AU, +0x53E2201FU, 0x3B2B7CAEU, 0x8C4D26FBU, 0x107C1E14U, 0xA71A4441U, 0x3D6D0601U, 0x8A0B5C54U, 0xD1F96FEAU, 0x701F8A2FU, 0xEDCCFBF9U, 0x2C9B1FAU, 0xB5AFEBAFU, +0x6BE6ED1DU, 0xDC80B748U, 0xF0A36927U, 0x47C53372U, 0xA8C07971U, 0x1FA62324U, 0x7FC1FE70U, 0xC8A7A425U, 0x77BBB9B9U, 0xC0DDE3ECU, 0x351741C8U, 0x82711B9DU, +0x9F1E8943U, 0x2878D316U, 0x38FD7E35U, 0x8F9B2460U, 0x481F0E42U, 0xFF795417U, 0xBA75D69FU, 0xD138CCAU, 0x7DA5F064U, 0xCAC3AA31U, 0xF783D92BU, 0x40E5837EU, +0xA0BA3EB8U, 0x17DC64EDU, 0x7535DF3EU, 0xC253856BU, 0x8F25402AU, 0x38431A7FU, 0xC797F186U, 0x70F1ABD3U, 0x604AA84FU, 0xD72CF21AU, 0x47AF9114U, 0xF0C9CB41U, +0x27A2EE26U, 0x90C4B473U, 0xBEBDCAB7U, 0x9DB90E2U, 0xED4C310CU, 0x5A2A6B59U, 0x223D969U, 0xB545833CU, 0x96E86CBAU, 0x218E36EFU, 0xA06EF894U, 0x1708A2C1U, +0x6D74519EU, 0xDA120BCBU, 0x180659DDU, 0xAF600388U, 0xDDB27132U, 0x6AD42B67U, 0xD5C836FBU, 0x62AE6CAEU, 0x2474ECBDU, 0x9312B6E8U, 0xCF07DEDCU, 0x78618489U, +0x32DD9957U, 0x85BBC302U, 0x90AE1615U, 0x27C84C40U, 0xC0B7418AU, 0x77D11BDFU, 0x3AA7DE9EU, 0x8DC184CBU, 0xB5C549C9U, 0x2A3139CU, 0x62C4CEC8U, 0xD5A2949DU, +0x28127170U, 0x9F742B25U, 0xA770E627U, 0x1016BC72U, 0x206836B9U, 0x970E6CECU, 0x1FCC8142U, 0xA8AADB17U, 0x5036965U, 0xB2653330U, 0x52862C08U, 0xBA1F74F9U, +0xEDA6599FU, 0x5AC003CAU, 0x47526A2CU, 0x3D07A467U, 0xCFD318F0U, 0x78B542A5U, 0xA9981D6DU, 0x1EFE4738U, 0x8CF342B1U, 0x3B9518E4U, 0x9BD6956BU, 0x2CB0CF3EU, +0xD0584ECFU, 0x673E149AU, 0x76E3DDA5U, 0xC18587F0U, 0xC625FD09U, 0x7143A75CU, 0x195E3DC1U, 0xAE386794U, 0xBEB922FU, 0xBC8DC87AU, 0xD51CF0D7U, 0x627AAA82U, +0xA67301FU, 0xBD016A4AU, 0x8505A748U, 0x3263FD1DU, 0x52042049U, 0xE5627A1CU, 0x5CECDB03U, 0xEB8A8156U, 0xF22D0FA0U, 0x454B55F5U, 0x7453159DU, 0xC3354FC8U, +0x37734FDCU, 0x80151589U, 0x8D7F827U, 0xBFB1A272U }; + +const ReplacementIndexStruct ReplacementIndexData[] = { + { utf16char(43), medium(1) }, + { utf16char(45), medium(1) }, + { utf16char(49), medium(2) }, + { utf16char(50), medium(1) }, + { utf16char(56), medium(1) }, + { utf16char(97), medium(129) }, + { utf16char(98), medium(226) }, + { utf16char(99), medium(290) }, + { utf16char(100), medium(100) }, + { utf16char(101), medium(70) }, + { utf16char(102), medium(437) }, + { utf16char(103), medium(108) }, + { utf16char(104), medium(138) }, + { utf16char(105), medium(53) }, + { utf16char(106), medium(25) }, + { utf16char(107), medium(53) }, + { utf16char(108), medium(101) }, + { utf16char(109), medium(247) }, + { utf16char(110), medium(74) }, + { utf16char(111), medium(71) }, + { utf16char(112), medium(201) }, + { utf16char(113), medium(8) }, + { utf16char(114), medium(114) }, + { utf16char(115), medium(289) }, + { utf16char(116), medium(148) }, + { utf16char(117), medium(53) }, + { utf16char(118), medium(28) }, + { utf16char(119), medium(211) }, + { utf16char(120), medium(4) }, + { utf16char(121), medium(8) }, + { utf16char(122), medium(11) }, +}; + +std::vector Replacements; +std::map> ReplacementsMap; +std::map ReplacementsHash; + +void InitReplacements() { + if (!Replacements.empty()) { + return; + } + auto data = ReplacementData; + auto takeString = [&data](int size) { + auto result = utf16string(data, size); + data += size; + return result; + }; + auto wordSize = ReplacementWordLengths; + + Replacements.reserve(1936); + for (auto item : ReplacementInitData) { + auto emoji = takeString(item.emojiSize); + auto replacement = takeString(item.replacementSize); + auto words = std::vector(); + words.reserve(item.wordsCount); + for (auto i = 0; i != item.wordsCount; ++i) { + words.push_back(takeString(*wordSize++)); + } + Replacements.push_back({ std::move(emoji), std::move(replacement), std::move(words) }); + } + + auto indices = ReplacementIndices; + auto items = &Replacements[0]; + for (auto item : ReplacementIndexData) { + auto index = std::vector(); + index.reserve(item.count); + for (auto i = 0; i != item.count; ++i) { + index.push_back(items + (*indices++)); + } + ReplacementsMap.emplace(item.ch, std::move(index)); + } + + for (auto checksum : ReplacementChecksums) { + ReplacementsHash.emplace(checksum, items++); + } +} + +const std::vector *GetReplacements(utf16char first) { + if (ReplacementsMap.empty()) { + InitReplacements(); + } + auto it = ReplacementsMap.find(first); + return (it == ReplacementsMap.cend()) ? nullptr : &it->second; +} + +utf16string GetReplacementEmoji(utf16string replacement) { + auto code = countChecksum(replacement.data(), replacement.size() * sizeof(utf16char)); + auto it = ReplacementsHash.find(code); + return (it == ReplacementsHash.cend()) ? utf16string() : it->second->emoji; +} diff --git a/TMessagesProj/jni/emoji/emoji_suggestions_data.h b/TMessagesProj/jni/emoji/emoji_suggestions_data.h new file mode 100755 index 000000000..68d9692e2 --- /dev/null +++ b/TMessagesProj/jni/emoji/emoji_suggestions_data.h @@ -0,0 +1,38 @@ +/* +WARNING! All changes made in this file will be lost! +Created from 'empty' by 'codegen_emoji' + +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "emoji_suggestions.h" + +struct Replacement { + utf16string emoji; + utf16string replacement; + std::vector words; +}; + +constexpr auto kReplacementMaxLength = 55; + +void InitReplacements(); +const std::vector *GetReplacements(utf16char first); +utf16string GetReplacementEmoji(utf16string replacement); diff --git a/TMessagesProj/jni/intro/IntroRenderer.c b/TMessagesProj/jni/intro/IntroRenderer.c index b820f5cee..9c20edc79 100644 --- a/TMessagesProj/jni/intro/IntroRenderer.c +++ b/TMessagesProj/jni/intro/IntroRenderer.c @@ -3,7 +3,7 @@ #include #include -static int is_initialized = 0; +static int32_t is_initialized = 0; static float _coefficientsX[TIMING_NUM][4], _coefficientsY[TIMING_NUM][4]; static const float _c0x = 0.0; static const float _c0y = 0.0; @@ -89,9 +89,9 @@ static const float r2 = 70; static double ms0; static float date, date0; static float duration_const = 0.3f; -static int direct; -static int i; -static int current_page, prev_page; +static int32_t direct; +static int32_t i; +static int32_t current_page, prev_page; static float time; static mat4x4 ic_matrix; static LayerParams ic_pin_layer, ic_cam_layer, ic_videocam_layer, ic_smile_layer, ic_bubble_layer, ic_pencil_layer; @@ -99,7 +99,7 @@ static float time_local = 0; static float knot_delays[4]; static float offset_y; static float ribbonLength = 86.5f; -static int starsFar = 500; +static int32_t starsFar = 500; static float scroll_offset; static float calculated_speedometer_sin; @@ -121,12 +121,12 @@ int anim_smile_blink_one; int anim_smile_stage; static float scale; float anim_pin_start_time, anim_pin_duration; -static int anim_pencil_period; +static int32_t anim_pencil_period; static mat4x4 private_matrix; float cloud_scroll_offset; static inline void vec2_add(vec2 r, vec2 a, vec2 b) { - int i; + int32_t i; for (i = 0; i < 2; ++i) { r[i] = a[i] + b[i]; } @@ -134,9 +134,9 @@ static inline void vec2_add(vec2 r, vec2 a, vec2 b) { static inline float vec2_mul_inner(vec2 a, vec2 b) { float p = 0.f; - int i; + int32_t i; for (i = 0; i < 2; ++i) { - p += b[i]*a[i]; + p += b[i] * a[i]; } return p; } @@ -146,7 +146,7 @@ static inline float vec2_len(vec2 v) { } static inline void vec2_scale(vec2 r, vec2 v, float s) { - int i; + int32_t i; for (i = 0; i < 2; ++i) { r[i] = v[i] * s; } @@ -158,7 +158,7 @@ static inline void vec2_norm(vec2 r, vec2 v) { } static inline void mat4x4_identity(mat4x4 M) { - int i, j; + int32_t i, j; for (i = 0; i < 4; ++i) { for (j = 0; j < 4; ++j) { M[i][j] = i == j ? 1.f : 0.f; @@ -167,7 +167,7 @@ static inline void mat4x4_identity(mat4x4 M) { } static inline void mat4x4_dup(mat4x4 M, mat4x4 N) { - int i, j; + int32_t i, j; for (i = 0; i < 4; ++i) { for (j = 0; j < 4; ++j) { M[i][j] = N[i][j]; @@ -176,7 +176,7 @@ static inline void mat4x4_dup(mat4x4 M, mat4x4 N) { } static inline void vec4_scale(vec4 r, vec4 v, float s) { - int i; + int32_t i; for (i = 0; i < 4; ++i) { r[i] = v[i] * s; } @@ -189,7 +189,7 @@ static inline void mat4x4_scale_aniso(mat4x4 M, mat4x4 a, float x, float y, floa } static inline void mat4x4_mul(mat4x4 M, mat4x4 a, mat4x4 b) { - int k, r, c; + int32_t k, r, c; for (c = 0; c < 4; ++c) { for (r = 0; r < 4; ++r) { M[c][r] = 0.f; @@ -201,7 +201,7 @@ static inline void mat4x4_mul(mat4x4 M, mat4x4 a, mat4x4 b) { } static inline void mat4x4_mul_vec4(vec4 r, mat4x4 M, vec4 v) { - int i, j; + int32_t i, j; for (j = 0; j < 4; ++j) { r[j] = 0.f; for (i = 0; i < 4; ++i) { @@ -235,15 +235,15 @@ static inline void mat4x4_rotate_Z(mat4x4 Q, float angle) { mat4x4_rotate_Z2(Q, temp, angle); } -static inline void mat4x4_translate_in_place(mat4x4 m, float x, float y, float z) { - int i; +static inline void mat4x4_translate_in_place(mat4x4 m, float x, float y, float z) { + int32_t i; for (i = 0; i < 4; ++i) { m[3][i] += m[0][i] * x + m[1][i] * y + m[2][i] * z; } } static inline float deg_to_radf(float deg) { - return deg * (float)M_PI / 180.0f; + return deg * (float) M_PI / 180.0f; } static inline float MAXf(float a, float b) { @@ -279,7 +279,7 @@ GLuint build_program(const GLchar * vertex_shader_source, const GLint vertex_sha return link_program(vertex_shader, fragment_shader); } -GLuint create_vbo(const size_t size, const GLvoid* data, const GLenum usage) { +GLuint create_vbo(const GLsizeiptr size, const GLvoid* data, const GLenum usage) { GLuint vbo_object; glGenBuffers(1, &vbo_object); glBindBuffer(GL_ARRAY_BUFFER, vbo_object); @@ -291,8 +291,8 @@ GLuint create_vbo(const size_t size, const GLvoid* data, const GLenum usage) { TextureProgram get_texture_program(GLuint program) { return (TextureProgram) { program, - glGetAttribLocation(program, "a_Position"), - glGetAttribLocation(program, "a_TextureCoordinates"), + (GLuint) glGetAttribLocation(program, "a_Position"), + (GLuint) glGetAttribLocation(program, "a_TextureCoordinates"), glGetUniformLocation(program, "u_MvpMatrix"), glGetUniformLocation(program, "u_TextureUnit"), glGetUniformLocation(program, "u_Alpha")}; @@ -301,7 +301,7 @@ TextureProgram get_texture_program(GLuint program) { ColorProgram get_color_program(GLuint program) { return (ColorProgram) { program, - glGetAttribLocation(program, "a_Position"), + (GLuint) glGetAttribLocation(program, "a_Position"), glGetUniformLocation(program, "u_MvpMatrix"), glGetUniformLocation(program, "u_Color"), glGetUniformLocation(program, "u_Alpha")}; @@ -311,8 +311,8 @@ float frand(float from, float to) { return (float) (((double) random() / RAND_MAX) * (to - from) + from); } -int irand(int from, int to) { - return (int) (((double) random() / RAND_MAX) * (to - from + 1) + from); +int irand(int32_t from, int32_t to) { + return (int32_t) (((double) random() / RAND_MAX) * (to - from + 1) + from); } int signrand() { @@ -320,16 +320,16 @@ int signrand() { } static inline float evaluateAtParameterWithCoefficients(float t, float coefficients[]) { - return coefficients[0] + t*coefficients[1] + t*t*coefficients[2] + t*t*t*coefficients[3]; + return coefficients[0] + t * coefficients[1] + t * t * coefficients[2] + t * t * t * coefficients[3]; } static inline float evaluateDerivationAtParameterWithCoefficients(float t, float coefficients[]) { - return coefficients[1] + 2*t*coefficients[2] + 3*t*t*coefficients[3]; + return coefficients[1] + 2 * t * coefficients[2] + 3 * t * t * coefficients[3]; } static inline float calcParameterViaNewtonRaphsonUsingXAndCoefficientsForX(float x, float coefficientsX[]) { float t = x; - int i; + int32_t i; for (i = 0; i < 10; i++) { float x2 = evaluateAtParameterWithCoefficients(t, coefficientsX) - x; float d = evaluateDerivationAtParameterWithCoefficients(t, coefficientsX); @@ -339,10 +339,6 @@ static inline float calcParameterViaNewtonRaphsonUsingXAndCoefficientsForX(float return t; } -static inline float calcParameterUsingXAndCoefficientsForX (float x, float coefficientsX[]) { - return calcParameterViaNewtonRaphsonUsingXAndCoefficientsForX(x, coefficientsX); -} - float timing(float x, timing_type type) { if (is_initialized == 0) { is_initialized = 1; @@ -372,7 +368,7 @@ float timing(float x, timing_type type) { c[Linear][1] = 0.0; c[Linear][2] = 1.0; c[Linear][3] = 1.0; - int i; + int32_t i; for (i = 0; i < TIMING_NUM; i++) { float _c1x = c[i][0]; float _c1y = c[i][1]; @@ -392,7 +388,7 @@ float timing(float x, timing_type type) { if (x == 0.0 || x == 1.0) { return x; } - float t = calcParameterUsingXAndCoefficientsForX(x, _coefficientsX[type]); + float t = calcParameterViaNewtonRaphsonUsingXAndCoefficientsForX(x, _coefficientsX[type]); float y = evaluateAtParameterWithCoefficients(t, _coefficientsY[type]); return y; } @@ -404,157 +400,157 @@ void set_y_offset_objects(float a) { void setup_shaders() { const char *vshader = "uniform mat4 u_MvpMatrix;" - "attribute vec4 a_Position;" - "void main(){" - " gl_Position = u_MvpMatrix * a_Position;" - "}"; + "attribute vec4 a_Position;" + "void main(){" + " gl_Position = u_MvpMatrix * a_Position;" + "}"; const char *fshader = "precision lowp float;" - "uniform vec4 u_Color;" - "uniform float u_Alpha;" - "void main() {" - " gl_FragColor = u_Color;" - " gl_FragColor.w*=u_Alpha;" - "}"; + "uniform vec4 u_Color;" + "uniform float u_Alpha;" + "void main() {" + " gl_FragColor = u_Color;" + " gl_FragColor.w*=u_Alpha;" + "}"; - color_program = get_color_program(build_program(vshader, (GLint)strlen(vshader), fshader, (GLint)strlen(fshader))); + color_program = get_color_program(build_program(vshader, (GLint) strlen(vshader), fshader, (GLint) strlen(fshader))); const char *vshader_texture = "uniform mat4 u_MvpMatrix;" - "attribute vec4 a_Position;" - "attribute vec2 a_TextureCoordinates;" - "varying vec2 v_TextureCoordinates;" - "void main(){" - " v_TextureCoordinates = a_TextureCoordinates;" - " gl_Position = u_MvpMatrix * a_Position;" - "}"; + "attribute vec4 a_Position;" + "attribute vec2 a_TextureCoordinates;" + "varying vec2 v_TextureCoordinates;" + "void main(){" + " v_TextureCoordinates = a_TextureCoordinates;" + " gl_Position = u_MvpMatrix * a_Position;" + "}"; const char *fshader_texture = "precision lowp float;" - "uniform sampler2D u_TextureUnit;" - "varying vec2 v_TextureCoordinates;" - "uniform float u_Alpha;" - "void main(){" - " gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);" - " gl_FragColor.w *= u_Alpha;" - "}"; + "uniform sampler2D u_TextureUnit;" + "varying vec2 v_TextureCoordinates;" + "uniform float u_Alpha;" + "void main(){" + " gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);" + " gl_FragColor.w *= u_Alpha;" + "}"; - texture_program = get_texture_program(build_program(vshader_texture, (GLint)strlen(vshader_texture), fshader_texture, (GLint)strlen(fshader_texture))); + texture_program = get_texture_program(build_program(vshader_texture, (GLint) strlen(vshader_texture), fshader_texture, (GLint) strlen(fshader_texture))); const char *vshader_texture_blue = "uniform mat4 u_MvpMatrix;" - "attribute vec4 a_Position;" - "attribute vec2 a_TextureCoordinates;" - "varying vec2 v_TextureCoordinates;" - "void main(){" - " v_TextureCoordinates = a_TextureCoordinates;" - " gl_Position = u_MvpMatrix * a_Position;" - "}"; + "attribute vec4 a_Position;" + "attribute vec2 a_TextureCoordinates;" + "varying vec2 v_TextureCoordinates;" + "void main(){" + " v_TextureCoordinates = a_TextureCoordinates;" + " gl_Position = u_MvpMatrix * a_Position;" + "}"; - const char *fshader_texture_blue = + const char *fshader_texture_blue = "precision lowp float;" - "uniform sampler2D u_TextureUnit;" - "varying vec2 v_TextureCoordinates;" - "uniform float u_Alpha;" - "void main(){" - " gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);" - " float p = u_Alpha*gl_FragColor.w;" - " gl_FragColor = vec4(0,0.6,0.898,p);" - "}"; + "uniform sampler2D u_TextureUnit;" + "varying vec2 v_TextureCoordinates;" + "uniform float u_Alpha;" + "void main(){" + " gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);" + " float p = u_Alpha*gl_FragColor.w;" + " gl_FragColor = vec4(0,0.6,0.898,p);" + "}"; - texture_program_blue = get_texture_program(build_program(vshader_texture_blue, (GLint)strlen(vshader_texture_blue), fshader_texture_blue, (GLint)strlen(fshader_texture_blue))); + texture_program_blue = get_texture_program(build_program(vshader_texture_blue, (GLint) strlen(vshader_texture_blue), fshader_texture_blue, (GLint) strlen(fshader_texture_blue))); - const char *vshader_texture_red = + const char *vshader_texture_red = "uniform mat4 u_MvpMatrix;" - "attribute vec4 a_Position;" - "attribute vec2 a_TextureCoordinates;" - "varying vec2 v_TextureCoordinates;" - "void main(){" - " v_TextureCoordinates = a_TextureCoordinates;" - " gl_Position = u_MvpMatrix * a_Position;" - "}"; + "attribute vec4 a_Position;" + "attribute vec2 a_TextureCoordinates;" + "varying vec2 v_TextureCoordinates;" + "void main(){" + " v_TextureCoordinates = a_TextureCoordinates;" + " gl_Position = u_MvpMatrix * a_Position;" + "}"; - const char *fshader_texture_red = + const char *fshader_texture_red = "precision lowp float;" - "uniform sampler2D u_TextureUnit;" - "varying vec2 v_TextureCoordinates;" - "uniform float u_Alpha;" - "void main(){" - " gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);" - " float p = gl_FragColor.w*u_Alpha;" - " gl_FragColor = vec4(210./255.,57./255.,41./255.,p);" - "}"; + "uniform sampler2D u_TextureUnit;" + "varying vec2 v_TextureCoordinates;" + "uniform float u_Alpha;" + "void main(){" + " gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);" + " float p = gl_FragColor.w*u_Alpha;" + " gl_FragColor = vec4(210./255.,57./255.,41./255.,p);" + "}"; - texture_program_red = get_texture_program(build_program(vshader_texture_red, (GLint)strlen(vshader_texture_red), fshader_texture_red, (GLint)strlen(fshader_texture_red))); + texture_program_red = get_texture_program(build_program(vshader_texture_red, (GLint) strlen(vshader_texture_red), fshader_texture_red, (GLint) strlen(fshader_texture_red))); vshader = "uniform mat4 u_MvpMatrix;" - "attribute vec4 a_Position;" - "attribute vec2 a_TextureCoordinates;" - "varying vec2 v_TextureCoordinates;" - "void main(){" - " v_TextureCoordinates = a_TextureCoordinates;" - " gl_Position = u_MvpMatrix * a_Position;" - "}"; + "attribute vec4 a_Position;" + "attribute vec2 a_TextureCoordinates;" + "varying vec2 v_TextureCoordinates;" + "void main(){" + " v_TextureCoordinates = a_TextureCoordinates;" + " gl_Position = u_MvpMatrix * a_Position;" + "}"; - fshader = + fshader = "precision lowp float;" - "uniform sampler2D u_TextureUnit;" - "varying vec2 v_TextureCoordinates;" - "uniform float u_Alpha;" - "void main(){" - " gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);" - " float p = u_Alpha*gl_FragColor.w;" - " gl_FragColor = vec4(246./255., 73./255., 55./255., p);" - "}"; + "uniform sampler2D u_TextureUnit;" + "varying vec2 v_TextureCoordinates;" + "uniform float u_Alpha;" + "void main(){" + " gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);" + " float p = u_Alpha*gl_FragColor.w;" + " gl_FragColor = vec4(246./255., 73./255., 55./255., p);" + "}"; - texture_program_light_red = get_texture_program(build_program(vshader, (GLint)strlen(vshader), fshader, (GLint)strlen(fshader))); + texture_program_light_red = get_texture_program(build_program(vshader, (GLint) strlen(vshader), fshader, (GLint) strlen(fshader))); - vshader = + vshader = "uniform mat4 u_MvpMatrix;" - "attribute vec4 a_Position;" - "attribute vec2 a_TextureCoordinates;" - "varying vec2 v_TextureCoordinates;" - "void main(){" - " v_TextureCoordinates = a_TextureCoordinates;" - " gl_Position = u_MvpMatrix * a_Position;" - "}"; + "attribute vec4 a_Position;" + "attribute vec2 a_TextureCoordinates;" + "varying vec2 v_TextureCoordinates;" + "void main(){" + " v_TextureCoordinates = a_TextureCoordinates;" + " gl_Position = u_MvpMatrix * a_Position;" + "}"; - fshader = + fshader = "precision lowp float;" - "uniform sampler2D u_TextureUnit;" - "varying vec2 v_TextureCoordinates;" - "uniform float u_Alpha;" - "void main(){" - " gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);" - " float p = u_Alpha*gl_FragColor.w;" - " gl_FragColor = vec4(42./255.,180./255.,247./255.,p);" - "}"; + "uniform sampler2D u_TextureUnit;" + "varying vec2 v_TextureCoordinates;" + "uniform float u_Alpha;" + "void main(){" + " gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);" + " float p = u_Alpha*gl_FragColor.w;" + " gl_FragColor = vec4(42./255.,180./255.,247./255.,p);" + "}"; - texture_program_light_blue = get_texture_program(build_program(vshader, (GLint)strlen(vshader), fshader, (GLint)strlen(fshader))); + texture_program_light_blue = get_texture_program(build_program(vshader, (GLint) strlen(vshader), fshader, (GLint) strlen(fshader))); - vshader = + vshader = "uniform mat4 u_MvpMatrix;" - "attribute vec4 a_Position;" - "attribute vec2 a_TextureCoordinates;" - "varying vec2 v_TextureCoordinates;" - "void main(){" - " v_TextureCoordinates = a_TextureCoordinates;" - " gl_Position = u_MvpMatrix * a_Position;" - "}"; + "attribute vec4 a_Position;" + "attribute vec2 a_TextureCoordinates;" + "varying vec2 v_TextureCoordinates;" + "void main(){" + " v_TextureCoordinates = a_TextureCoordinates;" + " gl_Position = u_MvpMatrix * a_Position;" + "}"; - fshader = + fshader = "precision lowp float;" - "uniform sampler2D u_TextureUnit;" - "varying vec2 v_TextureCoordinates;" - "uniform float u_Alpha;" - "void main(){" - " gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);" - " gl_FragColor *= u_Alpha;" - "}"; + "uniform sampler2D u_TextureUnit;" + "varying vec2 v_TextureCoordinates;" + "uniform float u_Alpha;" + "void main(){" + " gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);" + " gl_FragColor *= u_Alpha;" + "}"; - texture_program_one = get_texture_program(build_program(vshader, (GLint)strlen(vshader), fshader, (GLint)strlen(fshader))); + texture_program_one = get_texture_program(build_program(vshader, (GLint) strlen(vshader), fshader, (GLint) strlen(fshader))); } CPoint CPointMake(float x, float y) { @@ -568,11 +564,11 @@ CSize CSizeMake(float width, float height) { } float D2R(float a) { - return (float)(a * M_PI / 180.0); + return (float) (a * M_PI / 180.0); } float R2D(float a) { - return (float)(a * 180.0 / M_PI); + return (float) (a * 180.0 / M_PI); } xyz xyzMake(float x, float y, float z) { @@ -677,7 +673,7 @@ void draw_textured_shape(const TexturedShape* shape, mat4x4 view_projection_matr mat4x4_mul_vec4(pos, model_view_projection_matrix, vertex); vec4 p_NDC = {pos[0] / pos[3], pos[1] / pos[3], pos[2] / pos[3], pos[3] / pos[3]}; vec4 p_window = {p_NDC[0] * width, -p_NDC[1] * height, 0, 0}; - int d = 160; + int32_t d = 160; if (fabs(p_window[0]) > d || p_window[1] > y_offset_absolute * 2 + d || p_window[1] < y_offset_absolute * 2 - d) { return; } @@ -715,16 +711,12 @@ void draw_textured_shape(const TexturedShape* shape, mat4x4 view_projection_matr } } -static inline int size_of_rounded_rectangle_in_vertices(int round_count) { - return 4*(2+round_count)+2; -} - -static inline void gen_rounded_rectangle(CPoint* out, CSize size, float radius, int round_count) { - int offset = 0; +static inline void gen_rounded_rectangle(CPoint* out, CSize size, float radius, int32_t round_count) { + int32_t offset = 0; out[offset++] = CPointMake(0, 0); float k = (float) (M_PI / 2 / (round_count + 1)); - int i = 0; - int n = 0; + int32_t i = 0; + int32_t n = 0; for (i = (round_count + 2) * n; i <= round_count + 1 + (round_count + 1) * n; i++) { out[offset++] = CPointMake(size.width / 2 - radius + cosf(i * k) * radius, size.height / 2 - radius + sinf(i * k) * radius); } @@ -743,8 +735,8 @@ static inline void gen_rounded_rectangle(CPoint* out, CSize size, float radius, out[offset] = CPointMake(size.width / 2, size.height / 2 - radius); } -Shape create_rounded_rectangle(CSize size, float radius, int round_count, const vec4 color) { - int real_vertex_count = size_of_rounded_rectangle_in_vertices(round_count); +Shape create_rounded_rectangle(CSize size, float radius, int32_t round_count, const vec4 color) { + int32_t real_vertex_count = 4 * (2 + round_count) + 2; Params params = default_params(); params.const_params.datasize = sizeof(CPoint) * real_vertex_count * 2; @@ -753,7 +745,7 @@ Shape create_rounded_rectangle(CSize size, float radius, int round_count, const params.var_params.size = size; params.var_params.radius = radius; - CPoint *data = malloc(params.const_params.datasize); + CPoint *data = malloc((size_t) params.const_params.datasize); gen_rounded_rectangle(data, params.var_params.size, params.var_params.radius, params.const_params.round_count); return (Shape) {{color[0], color[1], color[2], color[3]}, data, create_vbo(params.const_params.datasize, data, GL_DYNAMIC_DRAW), real_vertex_count, params}; } @@ -770,10 +762,6 @@ void change_rounded_rectangle(Shape* shape, CSize size, float radius) { } } -static inline int size_of_segmented_square_in_vertices() { - return 7; -} - static inline CPoint square_point(float angle, float radius) { CPoint p = {0.0f, 0.0f}; if (angle <= M_PI / 2 * 0.5f || angle > M_PI / 2 * 3.5f) { @@ -795,7 +783,7 @@ static inline CPoint square_texture_point(CPoint p, float side_length) { static inline void gen_segmented_square(CPoint* out, float side_length, float start_angle, float end_angle) { CPoint p; float radius = side_length; - int offset = 0; + int32_t offset = 0; float k = 1; float da = D2R(-2.6f * 2) * k; p = CPointMake(sinf(start_angle + end_angle) * 6 * k, -cosf(start_angle + end_angle) * 6 * k); @@ -804,9 +792,9 @@ static inline void gen_segmented_square(CPoint* out, float side_length, float st p = square_point(start_angle + da, radius); out[offset++] = p; out[offset++] = square_texture_point(p, side_length); - int q = 0; - int i; - for (i = (int) start_angle; i < floorf(R2D(start_angle + end_angle + da)); i++) { + int32_t q = 0; + int32_t i; + for (i = (int32_t) start_angle; i < floorf(R2D(start_angle + end_angle + da)); i++) { if ((i + 45) % 90 == 0) { p = square_point(D2R(i), radius); out[offset++] = p; @@ -825,11 +813,11 @@ static inline void gen_segmented_square(CPoint* out, float side_length, float st } TexturedShape create_segmented_square(float side_length, float start_angle, float end_angle, GLuint texture) { - int real_vertex_count = size_of_segmented_square_in_vertices(); + int32_t real_vertex_count = 7; Params params = default_params(); params.const_params.datasize = sizeof(CPoint) * real_vertex_count * 2 * 2; params.const_params.triangle_mode = GL_TRIANGLE_FAN; - CPoint *data = malloc(params.const_params.datasize); + CPoint *data = malloc((size_t) params.const_params.datasize); gen_segmented_square(data, side_length, start_angle, end_angle); return (TexturedShape) {texture, data, create_vbo(params.const_params.datasize, data, GL_DYNAMIC_DRAW), real_vertex_count, params}; } @@ -854,11 +842,11 @@ static inline void gen_rectangle(CPoint* out, CSize size) { } Shape create_rectangle(CSize size, const vec4 color) { - int real_vertex_count = 4; + int32_t real_vertex_count = 4; Params params = default_params(); params.const_params.datasize = sizeof(CPoint) * real_vertex_count; params.const_params.triangle_mode = GL_TRIANGLE_STRIP; - CPoint *data = malloc(params.const_params.datasize); + CPoint *data = malloc((size_t) params.const_params.datasize); gen_rectangle(data, size); return (Shape) {{color[0], color[1], color[2], color[3]}, data, create_vbo(params.const_params.datasize, data, GL_DYNAMIC_DRAW), real_vertex_count, params}; } @@ -879,11 +867,11 @@ static inline void gen_textured_rectangle(CPoint* out, CSize size) { } TexturedShape create_textured_rectangle(CSize size, GLuint texture) { - int real_vertex_count = 4; + int32_t real_vertex_count = 4; Params params = default_params(); params.const_params.datasize = sizeof(CPoint) * real_vertex_count * 2; params.const_params.triangle_mode = GL_TRIANGLE_STRIP; - CPoint *data = malloc(params.const_params.datasize); + CPoint *data = malloc((size_t) params.const_params.datasize); gen_textured_rectangle(data, size); return (TexturedShape) {texture, data, create_vbo(params.const_params.datasize, data, GL_STATIC_DRAW), real_vertex_count, params}; } @@ -896,12 +884,12 @@ static inline void gen_ribbon(CPoint* out, float length) { } Shape create_ribbon(float length, const vec4 color) { - int real_vertex_count = 4; - Params params=default_params(); + int32_t real_vertex_count = 4; + Params params = default_params(); params.const_params.datasize = sizeof(CPoint) * real_vertex_count; params.const_params.triangle_mode = GL_TRIANGLE_STRIP; params.var_params.side_length = length; - CPoint *data = malloc(params.const_params.datasize); + CPoint *data = malloc((size_t) params.const_params.datasize); gen_ribbon(data, length); return (Shape) {{color[0], color[1], color[2], color[3]}, data, create_vbo(params.const_params.datasize, data, GL_DYNAMIC_DRAW), real_vertex_count, params}; } @@ -916,35 +904,31 @@ void change_ribbon(Shape* shape, float length) { } } -static inline int size_of_segmented_circle_in_vertices(int num_points) { - return 1 + (num_points + 1); -} - -static inline void gen_circle(CPoint* out, float radius, int vertex_count) { - int offset = 0; +static inline void gen_circle(CPoint* out, float radius, int32_t vertex_count) { + int32_t offset = 0; out[offset++] = CPointMake(0, 0); - int i; + int32_t i; for (i = 0; i <= vertex_count; i++) { out[offset++] = CPointMake(radius * (cosf(2 * (float) M_PI * (i / (float) vertex_count))), radius * sinf(2 * (float) M_PI * (i / (float) vertex_count))); } } -Shape create_circle(float radius, int vertex_count, const vec4 color) { - int real_vertex_count = size_of_segmented_circle_in_vertices(vertex_count); +Shape create_circle(float radius, int32_t vertex_count, const vec4 color) { + int32_t real_vertex_count = vertex_count + 2; Params params = default_params(); params.const_params.datasize = sizeof(CPoint) * real_vertex_count; params.const_params.triangle_mode = GL_TRIANGLE_FAN; params.const_params.round_count = vertex_count; - CPoint *data = (CPoint *) malloc(params.const_params.datasize); + CPoint *data = (CPoint *) malloc((size_t) params.const_params.datasize); gen_circle(data, radius, vertex_count); return (Shape) {{color[0], color[1], color[2], color[3]}, data, create_vbo(params.const_params.datasize, data, GL_STATIC_DRAW), real_vertex_count, params}; } -int size_of_infinity_in_vertices(int segment_count) { +int size_of_infinity_in_vertices(int32_t segment_count) { return (segment_count + 1) * 2; } -static inline void gen_infinity(CPoint* out, float width, float angle, int segment_count) { +static inline void gen_infinity(CPoint* out, float width, float angle, int32_t segment_count) { CPoint path[13]; path[0] = CPointMake(53, 23); path[1] = CPointMake(49, 31); @@ -959,13 +943,13 @@ static inline void gen_infinity(CPoint* out, float width, float angle, int segme path[10] = CPointMake(39, 0); path[11] = CPointMake(48, 15); path[12] = CPointMake(52, 21); - int offset = 0; - int seg; + int32_t offset = 0; + int32_t seg; for (seg = 0; seg <= segment_count; seg++) { float tt = ((float) seg / (float) segment_count) * angle; - int q = 4; + int32_t q = 4; float tstep = 1.f / q; - int n = (int) floor(tt / tstep); + int32_t n = (int32_t) floor(tt / tstep); CPoint a = path[0 + 3 * n];; CPoint p1 = path[1 + 3 * n]; CPoint p2 = path[2 + 3 * n]; @@ -998,15 +982,15 @@ static inline void gen_infinity(CPoint* out, float width, float angle, int segme } } -Shape create_infinity(float width, float angle, int segment_count, const vec4 color) { - int real_vertex_count = size_of_infinity_in_vertices(segment_count); +Shape create_infinity(float width, float angle, int32_t segment_count, const vec4 color) { + int32_t real_vertex_count = size_of_infinity_in_vertices(segment_count); Params params = default_params(); params.const_params.datasize = sizeof(CPoint) * real_vertex_count; params.const_params.triangle_mode = GL_TRIANGLE_STRIP; params.const_params.round_count = segment_count; params.var_params.width = width; params.var_params.angle = angle; - CPoint *data = malloc(params.const_params.datasize); + CPoint *data = malloc((size_t) params.const_params.datasize); gen_infinity(data, width, angle, segment_count); return (Shape) {{color[0], color[1], color[2], color[3]}, data, create_vbo(params.const_params.datasize, data, GL_DYNAMIC_DRAW), real_vertex_count, params}; } @@ -1021,16 +1005,12 @@ void change_infinity(Shape* shape, float angle) { } } -static inline int size_of_rounded_rectangle_stroked_in_vertices(int round_count) { - return 4 * (2 + round_count) * 2 + 2; -} - -static inline void gen_rounded_rectangle_stroked(CPoint* out, CSize size, float radius, float stroke_width, int round_count) { - int offset = 0; +static inline void gen_rounded_rectangle_stroked(CPoint* out, CSize size, float radius, float stroke_width, int32_t round_count) { + int32_t offset = 0; float k = (float) (M_PI / 2 / (round_count + 1)); float inner_radius = radius - stroke_width; - int i = 0; - int n = 0; + int32_t i = 0; + int32_t n = 0; for (i = (round_count + 2) * n; i <= round_count + 1 + (round_count + 1) * n; i++) { out[offset++] = CPointMake(size.width / 2 - radius + cosf(i * k) * radius, size.height / 2 - radius + sinf(i * k) * radius); out[offset++] = CPointMake(size.width / 2 - radius + cosf(i * k) * inner_radius, size.height / 2 - radius + sinf(i * k) * inner_radius); @@ -1055,21 +1035,21 @@ static inline void gen_rounded_rectangle_stroked(CPoint* out, CSize size, float out[offset] = CPointMake(size.width / 2 - radius + cosf(i * k) * inner_radius, size.height / 2 - radius + sinf(i * k) * inner_radius); } -Shape create_rounded_rectangle_stroked(CSize size, float radius, float stroke_width, int round_count, const vec4 color) { - int real_vertex_count = size_of_rounded_rectangle_stroked_in_vertices(round_count); +Shape create_rounded_rectangle_stroked(CSize size, float radius, float stroke_width, int32_t round_count, const vec4 color) { + int32_t real_vertex_count = 4 * (2 + round_count) * 2 + 2; Params params = default_params(); params.const_params.round_count = round_count; params.const_params.datasize = sizeof(CPoint) * real_vertex_count * 2; params.var_params.size = size; params.var_params.radius = radius; params.var_params.width = stroke_width; - CPoint *data = (CPoint *) malloc(params.const_params.datasize); + CPoint *data = (CPoint *) malloc((size_t) params.const_params.datasize); gen_rounded_rectangle_stroked(data, params.var_params.size, params.var_params.radius, params.var_params.width, params.const_params.round_count); params.const_params.triangle_mode = GL_TRIANGLE_STRIP; return (Shape) {{color[0], color[1], color[2], color[3]}, data, create_vbo(params.const_params.datasize, data, GL_DYNAMIC_DRAW), real_vertex_count, params}; } -void change_rounded_rectangle_stroked(Shape* shape, CSize size, float radius, __unused float stroke_width) { +void change_rounded_rectangle_stroked(Shape* shape, CSize size, float radius) { if ((*shape).params.var_params.size.width != size.width || (*shape).params.var_params.size.height != size.height || (*shape).params.var_params.radius != radius) { (*shape).params.var_params.size.width = size.width; (*shape).params.var_params.size.height = size.height; @@ -1094,16 +1074,15 @@ float t(float start_value, float end_value, float start_time, float duration, ti } float t_reversed(float end_value, float start_value, float start_time, float duration, timing_type type) { - if (time>start_time+duration) { + if (time > start_time + duration) { return end_value; } if (type == Linear) { - return start_value + (end_value - start_value)*MINf(duration+start_time, MAXf(0.0f, (time - start_time))) /duration; + return start_value + (end_value - start_value) * MINf(duration + start_time, MAXf(0.0f, (time - start_time))) / duration; } - return start_value + (end_value - start_value) * timing(MINf(duration+start_time, MAXf(0.0f, (time - start_time))) / duration, type); + return start_value + (end_value - start_value) * timing(MINf(duration + start_time, MAXf(0.0f, (time - start_time))) / duration, type); } - float t_local(float start_value, float end_value, float start_time, float duration, timing_type type) { if (type == Sin) { return start_value + (end_value - start_value) * sinf(MINf(MAXf((time_local - start_time) / duration * (float) M_PI, 0), (float) M_PI)); @@ -1121,19 +1100,15 @@ float t_local(float start_value, float end_value, float start_time, float durati xyz star_create_position(float far) { starsFar = 1500; - - int minR = 100; - int maxR = 1000; - + int32_t minR = 100; + int32_t maxR = 1000; return xyzMake(signrand() * frand(minR, maxR), signrand() * frand(minR, maxR), far); } -xyz star_initial_position(int randZ, int forward) { +xyz star_initial_position(int32_t randZ, int32_t forward) { starsFar = 1500; - - int minR = 100; - int maxR = 1000; - + int32_t minR = 100; + int32_t maxR = 1000; float z = 0; if (forward == 1) { if (randZ == 0) { @@ -1179,9 +1154,8 @@ void draw_stars() { star.params.scale = xyzMake(s, s, 1); float far = starsFar; - float k = 10.; - star.params.alpha = (1 - (-stars[i].position.z) / far) * k; - star.params.alpha = star.params.alpha * star.params.alpha / k; + star.params.alpha = (1 - (-stars[i].position.z) / far) * 10.0f; + star.params.alpha = star.params.alpha * star.params.alpha / 10.0f; draw_textured_shape(&star, stars_matrix, NORMAL); @@ -1192,8 +1166,8 @@ void draw_stars() { set_y_offset_objects(offset_y); } -static inline void mat4x4_plain(mat4x4 M, int width, int height) { - int i, j; +static inline void mat4x4_plain(mat4x4 M, int32_t width, int32_t height) { + int32_t i, j; for (i = 0; i < 4; ++i) { for (j = 0; j < 4; ++j) { M[i][j] = 0.0f; @@ -1209,11 +1183,10 @@ static inline void mat4x4_plain(mat4x4 M, int width, int height) { M[3][3] = (float) width / 2.0f; } -static inline void mat4x4_stars(mat4x4 m, float y_fov_in_degrees, float aspect, float n, float f, int width, int height) { - int is_iOS = 0; +static inline void mat4x4_stars(mat4x4 m, float y_fov_in_degrees, float aspect, float n, float f, int32_t width, int32_t height) { if (height >= width) { float k = (float) width / (float) height; - float q = !is_iOS ? 1.4f : 0.7f; + float q = 1.4f; m[0][0] = 1.0f / q; m[1][0] = 0.0f; m[2][0] = 0.0f; @@ -1235,7 +1208,7 @@ static inline void mat4x4_stars(mat4x4 m, float y_fov_in_degrees, float aspect, m[3][3] = width * k; } else { float k = (float) height / (float) width; - float q = !is_iOS ? 2.0f : 0.7f; + float q = 2.0f; m[0][0] = 1.0f / q; m[1][0] = 0.0f; @@ -1263,7 +1236,7 @@ static inline void mat4x4_stars(mat4x4 m, float y_fov_in_degrees, float aspect, void rglNormalDraw() { glDisable(GL_DEPTH_TEST); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glColorMask(1,1,1,1); + glColorMask(1, 1, 1, 1); glDepthMask(0); } @@ -1278,7 +1251,7 @@ void rglMaskDraw() { } void rglNormalDrawThroughMask() { - glColorMask(1,1,1,1); + glColorMask(1, 1, 1, 1); glDepthFunc(GL_LESS); glDepthMask(0); } @@ -1343,12 +1316,12 @@ static void reset_ic() { anim_pencil_period = 1; } -static void draw_ic(int type) { +static void draw_ic(int32_t type) { float rotation; float beginTimeK; float commonDelay; float beginY = 250; - int bounce; + int32_t bounce; texture_program_type COLOR, LIGHT_COLOR; if (type == 0) { beginTimeK = 2.0f; @@ -1460,7 +1433,7 @@ static void draw_ic(int type) { anim_cam_old_position = anim_cam_position; anim_cam_start_time = time_local; anim_cam_next_time = time_local + 10000000; - int r = irand(0, 1); + int32_t r = irand(0, 1); if (r == 0) { anim_cam_position = CPointMake(-8 + 4, 0); anim_cam_angle = signrand() * 10; @@ -1566,7 +1539,7 @@ static void draw_ic(int type) { anim_smile_blink_one = 0; } - int stop_time = 5; + int32_t stop_time = 5; float eye_scale = t_local(1, 0, anim_smile_blink_start_time, 0.1f, Sin); ic_smile_eye.params.scale = xyzMake(1, eye_scale, 1); if (time > stop_time) ic_smile_eye.params.scale = xyzMake(1, 1, 1); @@ -1686,7 +1659,7 @@ static void draw_ic(int type) { draw_textured_shape(&ic_pencil, ic_matrix, COLOR); } -void draw_safe(int type, float alpha, float screw_alpha) { +void draw_safe(int32_t type, float alpha, float screw_alpha) { float screw_distance = 53; private_screw.params.alpha = alpha * screw_alpha; @@ -1726,8 +1699,8 @@ JNIEXPORT void Java_org_telegram_messenger_Intro_onDrawFrame(JNIEnv *env, jclass } for (i = 0; i < 10; i++) { - int j1 = irand(0, 3); - int j2 = irand(0, 3); + int32_t j1 = irand(0, 3); + int32_t j2 = irand(0, 3); float temp = knot_delays[j1]; knot_delays[j1] = knot_delays[j2]; knot_delays[j2] = temp; @@ -1896,28 +1869,28 @@ JNIEXPORT void Java_org_telegram_messenger_Intro_onDrawFrame(JNIEnv *env, jclass mask1.params.rotation = private_scroll_offset + t_reversed(180.0f + 90.0f + 90.0f, 180.0f + 90.0f + 90.0f + 90.0f, 0, duration_const * k, EaseOut); k = 1.0f * private_back_k; - int sublayer2_radius = 33; + int32_t sublayer2_radius = 33; cloud_extra_mask1.params.position = xyzMake(t_reversed(0, -122 / 2, 0, duration_const * k, EaseOut), t_reversed(0, 54 / 2 - 1, 0, duration_const * k, EaseOut), cloud_extra_mask1.params.position.z); scale = t_reversed(0, sublayer2_radius, 0, duration_const * k, EaseOut); cloud_extra_mask1.params.scale = xyzMake(scale, scale, 1); draw_shape(&cloud_extra_mask1, main_matrix); k = 1.15f * private_back_k; - int sublayer3_radius = 94 / 4; + int32_t sublayer3_radius = 94 / 4; cloud_extra_mask2.params.position = xyzMake(t_reversed(0, -84 / 2, 0, duration_const * k, EaseOut), t_reversed(0, -29 / 2, 0, duration_const * k, EaseOut), cloud_extra_mask2.params.position.z); scale = t_reversed(0, sublayer3_radius, 0, duration_const * k, EaseOut); cloud_extra_mask2.params.scale = xyzMake(scale, scale, 1); draw_shape(&cloud_extra_mask2, main_matrix); k = 1.3f * private_back_k; - int sublayer4_radius = 124 / 4; + int32_t sublayer4_radius = 124 / 4; cloud_extra_mask3.params.position = xyzMake(t_reversed(0, 128 / 2, 0, duration_const * k, EaseOut), t_reversed(0, 56 / 2, 0, duration_const * k, EaseOut), cloud_extra_mask3.params.position.z); scale = t_reversed(0, sublayer4_radius, 0, duration_const * k, EaseOut); cloud_extra_mask3.params.scale = xyzMake(scale, scale, 1); draw_shape(&cloud_extra_mask3, main_matrix); k = 1.5f * private_back_k; - int sublayer5_radius = 64; + int32_t sublayer5_radius = 64; cloud_extra_mask4.params.position = xyzMake(t_reversed(0, 0, 0, duration_const * k, EaseOut), t_reversed(0, 50, 0, duration_const * k, EaseOut), cloud_extra_mask4.params.position.z); scale = t_reversed(0, sublayer5_radius, 0, duration_const * k, EaseOut); cloud_extra_mask4.params.scale = xyzMake(scale, scale, 1); @@ -1931,7 +1904,7 @@ JNIEXPORT void Java_org_telegram_messenger_Intro_onDrawFrame(JNIEnv *env, jclass mask1.params.rotation = t(180.0f + 90.0f + 90.0f, 180.0f + 90.0f + 90.0f + 90.0f, 0, duration_const * k, EaseOut); k = 1.0f; - int sublayer2_radius = 33; + int32_t sublayer2_radius = 33; cloud_extra_mask1.params.position = xyzMake(t(0, -122 / 2, 0, duration_const * k, EaseOut), t(0, 54 / 2 - 1, 0, duration_const * k, EaseOut), cloud_extra_mask1.params.position.z); scale = t(0, sublayer2_radius, 0, duration_const * k, EaseOut); cloud_extra_mask1.params.scale = xyzMake(scale, scale, 1); @@ -1939,7 +1912,7 @@ JNIEXPORT void Java_org_telegram_messenger_Intro_onDrawFrame(JNIEnv *env, jclass k = 1.15; - int sublayer3_radius = 94 / 4; + int32_t sublayer3_radius = 94 / 4; cloud_extra_mask2.params.position = xyzMake(t(0, -84 / 2, 0, duration_const * k, EaseOut), t(0, -29 / 2, 0, duration_const * k, EaseOut), cloud_extra_mask2.params.position.z); scale = t(0, sublayer3_radius, 0, duration_const * k, EaseOut); cloud_extra_mask2.params.scale = xyzMake(scale, scale, 1); @@ -1947,7 +1920,7 @@ JNIEXPORT void Java_org_telegram_messenger_Intro_onDrawFrame(JNIEnv *env, jclass k = 1.3; - int sublayer4_radius = 124 / 4; + int32_t sublayer4_radius = 124 / 4; cloud_extra_mask3.params.position = xyzMake(t(0, 128 / 2, 0, duration_const * k, EaseOut), t(0, 56 / 2, 0, duration_const * k, EaseOut), cloud_extra_mask3.params.position.z); scale = t(0, sublayer4_radius, 0, duration_const * k, EaseOut); cloud_extra_mask3.params.scale = xyzMake(scale, scale, 1); @@ -1955,7 +1928,7 @@ JNIEXPORT void Java_org_telegram_messenger_Intro_onDrawFrame(JNIEnv *env, jclass k = 1.5f; - int sublayer5_radius = 64; + int32_t sublayer5_radius = 64; cloud_extra_mask4.params.position = xyzMake(t(0, 0, 0, duration_const * k, EaseOut), t(0, 50, 0, duration_const * k, EaseOut), cloud_extra_mask4.params.position.z); scale = t(0, sublayer5_radius, 0, duration_const * k, EaseOut); cloud_extra_mask4.params.scale = xyzMake(scale, scale, 1); @@ -1964,9 +1937,9 @@ JNIEXPORT void Java_org_telegram_messenger_Intro_onDrawFrame(JNIEnv *env, jclass } draw_shape(&mask1, main_matrix); - int rr = 30; - int seg = 15; - int ang = 180; + int32_t rr = 30; + int32_t seg = 15; + int32_t ang = 180; rglNormalDrawThroughMask(); if (current_page == 0) { if (direct == 0) { @@ -2238,7 +2211,7 @@ JNIEXPORT void Java_org_telegram_messenger_Intro_onDrawFrame(JNIEnv *env, jclass glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); ribbon1.params.alpha = ribbon2.params.alpha = ribbon3.params.alpha = ribbon4.params.alpha = t(0, 1, 0, dur, EaseInEaseOut); - int ribbon_k = time > duration_const ? 1 : 0; + int32_t ribbon_k = time > duration_const ? 1 : 0; change_ribbon(&ribbon1, ribbonLength - 8.0f * ribbon_k - free_scroll_offset / 5.0f * (30 - 8 * ribbon_k)); ribbon1.params.position.x = scroll_offset * 30 * 0 + t(-dribbon, 0, 0, duration_const, EaseInEaseOut); @@ -2305,10 +2278,10 @@ JNIEXPORT void Java_org_telegram_messenger_Intro_onDrawFrame(JNIEnv *env, jclass ribbon2.params.rotation = t_reversed(90, 90 + a2, 0, duration_const, EaseOut); ribbon4.params.rotation = t_reversed(270, 270 + a2, 0, duration_const, EaseOut); - float k = .9; + float k = 0.9f; ribbon2.params.alpha = ribbon4.params.alpha = t_reversed(1, 0, duration_const * 0.5f, duration_const * 0.1f, Linear); - int ribbon_k = 0; + int32_t ribbon_k = 0; change_ribbon(&ribbon1, t_reversed(ribbonLength - 8.0f * ribbon_k, 0, 0, duration_const * 0.9f, Linear) - free_scroll_offset / 5.0f * (30 - 8 * ribbon_k)); ribbon1.params.position.x = 0; draw_shape(&ribbon1, ribbons_layer); @@ -2375,10 +2348,10 @@ JNIEXPORT void Java_org_telegram_messenger_Intro_onDrawFrame(JNIEnv *env, jclass ribbon2.params.rotation = t(90, 90 + a2, 0, duration_const, EaseOut); ribbon4.params.rotation = t(270, 270 + a2, 0, duration_const, EaseOut); - float k = .5; + float k = 0.5f; ribbon2.params.alpha = ribbon4.params.alpha = t(1, 0, duration_const * k * 0.5f, duration_const * k * 0.1f, Linear); - int ribbon_k = time > duration_const ? 1 : 0; + int32_t ribbon_k = time > duration_const ? 1 : 0; change_ribbon(&ribbon1, t(ribbonLength - 8.0f * ribbon_k - free_scroll_offset / 5.0f * (30 - 8 * ribbon_k), 0, 0, duration_const * 0.9f, Linear)); draw_shape(&ribbon1, ribbons_layer); @@ -2443,7 +2416,7 @@ JNIEXPORT void Java_org_telegram_messenger_Intro_onDrawFrame(JNIEnv *env, jclass private_stroke.params.alpha = t(1, 0, 0, duration_const, Linear); private_stroke.params.position = xyzMake(0, t(0, -6, 0, duration_const, EaseOut), 0); scale = t_reversed(63 * 2.0f, 63 * 2, 0, duration_const, EaseOut); - change_rounded_rectangle_stroked(&private_stroke, CSizeMake(scale, scale), scale / 2.0f, 9); + change_rounded_rectangle_stroked(&private_stroke, CSizeMake(scale, scale), scale / 2.0f); draw_shape(&private_stroke, main_matrix); float infinityDurK = 1.1; @@ -2526,7 +2499,7 @@ JNIEXPORT void Java_org_telegram_messenger_Intro_onDrawFrame(JNIEnv *env, jclass private_stroke.params.alpha = 1; private_stroke.params.position = xyzMake(0, 0, 0); scale = t(63, 63 * 2, 0, duration_const, EaseOut); - change_rounded_rectangle_stroked(&private_stroke, CSizeMake(scale, scale), scale / 2.0f, 9); + change_rounded_rectangle_stroked(&private_stroke, CSizeMake(scale, scale), scale / 2.0f); draw_shape(&private_stroke, main_matrix); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); @@ -2548,7 +2521,7 @@ JNIEXPORT void Java_org_telegram_messenger_Intro_onDrawFrame(JNIEnv *env, jclass private_stroke.params.alpha = t(0, 1, 0, duration_const * 0.25f, Linear); private_stroke.params.position = xyzMake(0, 0, 0); scale = t(63, 63 * 2, 0, duration_const * private_back_k, EaseOut); - change_rounded_rectangle_stroked(&private_stroke, CSizeMake(scale, scale), scale / 2.0f, 9); + change_rounded_rectangle_stroked(&private_stroke, CSizeMake(scale, scale), scale / 2.0f); draw_shape(&private_stroke, main_matrix); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); @@ -2579,7 +2552,7 @@ JNIEXPORT void Java_org_telegram_messenger_Intro_onDrawFrame(JNIEnv *env, jclass private_stroke.params.rotation = private_scroll_offset; private_stroke.params.alpha = t(1, 0, 0, duration_const * private_fade_k * 0.5f, EaseOut); scale = t(244 / 2, r2 * 2, 0, duration_const, EaseOut); - change_rounded_rectangle_stroked(&private_stroke, CSizeMake(scale, scale), scale / 2.0f, 9); + change_rounded_rectangle_stroked(&private_stroke, CSizeMake(scale, scale), scale / 2.0f); draw_shape(&private_stroke, main_matrix); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); @@ -2600,7 +2573,7 @@ JNIEXPORT void Java_org_telegram_messenger_Intro_setScrollOffset(JNIEnv *env, jc scroll_offset = a_offset; } -JNIEXPORT void Java_org_telegram_messenger_Intro_setPage(JNIEnv *env, jclass class, int page) { +JNIEXPORT void Java_org_telegram_messenger_Intro_setPage(JNIEnv *env, jclass class, int32_t page) { if (current_page == page) { return; } else { @@ -2717,7 +2690,7 @@ JNIEXPORT void Java_org_telegram_messenger_Intro_onSurfaceCreated(JNIEnv *env, j mask1 = create_rounded_rectangle(CSizeMake(60, 60), 0, 16, black_color); - telegram_sphere = create_textured_rectangle(CSizeMake(148, 148), telegram_sphere_texture); + telegram_sphere = create_textured_rectangle(CSizeMake(150, 150), telegram_sphere_texture); telegram_plane = create_textured_rectangle(CSizeMake(82, 74), telegram_plane_texture); telegram_plane.params.anchor = xyzMake(6, -5, 0); @@ -2730,7 +2703,7 @@ JNIEXPORT void Java_org_telegram_messenger_Intro_onSurfaceCreated(JNIEnv *env, j fast_arrow = create_textured_rectangle(CSizeMake(164 / 2, 44 / 2), fast_arrow_texture); fast_arrow.params.anchor.x = fast_arrow_shadow.params.anchor.x = -19; - int ang = 180; + int32_t ang = 180; spiral = create_segmented_square(r1, D2R(35 + 1), D2R(35 + 1 - 10 + ang), fast_spiral_texture); vec4 free_bg_color = {246 / 255.0f, 73 / 255.0f, 55 / 255.0f, 1}; @@ -2815,7 +2788,7 @@ JNIEXPORT void Java_org_telegram_messenger_Intro_onSurfaceCreated(JNIEnv *env, j private_screw = create_textured_rectangle(CSizeMake(30 / 3, 30 / 3), private_screw_texture); private_stroke = create_rounded_rectangle_stroked(CSizeMake(244 / 2, 244 / 2), 21, 9, 16, white_color); - int cloud_polygons_count = 64; + int32_t cloud_polygons_count = 64; cloud_extra_mask1 = create_circle(1, cloud_polygons_count, black_color); cloud_extra_mask2 = create_circle(1, cloud_polygons_count, black_color); cloud_extra_mask3 = create_circle(1, cloud_polygons_count, black_color); @@ -2828,14 +2801,14 @@ JNIEXPORT void Java_org_telegram_messenger_Intro_onSurfaceCreated(JNIEnv *env, j cloud_bg = create_rectangle(CSizeMake(160 * 2, 160 * 2), cloud_color); } -JNIEXPORT void Java_org_telegram_messenger_Intro_onSurfaceChanged(JNIEnv *env, jclass class, int a_width_px, int a_height_px, float a_scale_factor, int a1) { +JNIEXPORT void Java_org_telegram_messenger_Intro_onSurfaceChanged(JNIEnv *env, jclass class, int32_t a_width_px, int32_t a_height_px, float a_scale_factor, int32_t a1) { glViewport(0, 0, a_width_px, a_height_px); - width = (int) (a_width_px / a_scale_factor); - height = (int) (a_height_px / a_scale_factor); + width = (int32_t) (a_width_px / a_scale_factor); + height = (int32_t) (a_height_px / a_scale_factor); scale_factor = a_scale_factor; - mat4x4_plain(main_matrix, (int)((float)a_width_px / a_scale_factor), (int)((float)a_height_px / a_scale_factor)); - offset_y = a1*main_matrix[1][1]; + mat4x4_plain(main_matrix, (int32_t) ((float) a_width_px / a_scale_factor), (int32_t) ((float) a_height_px / a_scale_factor)); + offset_y = a1 * main_matrix[1][1]; set_y_offset_objects(offset_y); y_offset_absolute = a1; - mat4x4_stars(stars_matrix, 45, 1, -1000, 0, (int)((float)a_width_px / a_scale_factor), (int)((float)a_height_px / a_scale_factor)); + mat4x4_stars(stars_matrix, 45, 1, -1000, 0, (int32_t) ((float) a_width_px / a_scale_factor), (int32_t) ((float) a_height_px / a_scale_factor)); } \ No newline at end of file diff --git a/TMessagesProj/jni/intro/IntroRenderer.h b/TMessagesProj/jni/intro/IntroRenderer.h index 27a2688f5..812f1d35a 100644 --- a/TMessagesProj/jni/intro/IntroRenderer.h +++ b/TMessagesProj/jni/intro/IntroRenderer.h @@ -42,8 +42,8 @@ typedef struct { typedef struct { GLuint program; - GLint a_position_location; - GLint a_texture_coordinates_location; + GLuint a_position_location; + GLuint a_texture_coordinates_location; GLint u_mvp_matrix_location; GLint u_texture_unit_location; GLint u_alpha_loaction; @@ -51,7 +51,7 @@ typedef struct { typedef struct { GLuint program; - GLint a_position_location; + GLuint a_position_location; GLint u_mvp_matrix_location; GLint u_color_location; GLint u_alpha_loaction; @@ -68,7 +68,7 @@ typedef struct { } VarParams; typedef struct { - size_t datasize; + GLsizeiptr datasize; int round_count; GLenum triangle_mode; int is_star; diff --git a/TMessagesProj/jni/sqlite/sqlite3.c b/TMessagesProj/jni/sqlite/sqlite3.c index af83f89f3..ea5ba16b6 100644 --- a/TMessagesProj/jni/sqlite/sqlite3.c +++ b/TMessagesProj/jni/sqlite/sqlite3.c @@ -1,6 +1,6 @@ /****************************************************************************** ** This file is an amalgamation of many separate C source files from SQLite -** version 3.18.0. By combining all the individual C code files into this +** version 3.20.1. By combining all the individual C code files into this ** single large file, the entire code can be compiled as a single translation ** unit. This allows many compilers to do optimizations that would not be ** possible if the files were compiled separately. Performance improvements @@ -22,6 +22,758 @@ #ifndef SQLITE_PRIVATE # define SQLITE_PRIVATE static #endif +/************** Begin file ctime.c *******************************************/ +/* +** 2010 February 23 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file implements routines used to report what compile-time options +** SQLite was built with. +*/ + +#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS + +/* +** Include the configuration header output by 'configure' if we're using the +** autoconf-based build +*/ +#if defined(_HAVE_SQLITE_CONFIG_H) && !defined(SQLITECONFIG_H) +#include "config.h" +#define SQLITECONFIG_H 1 +#endif + +/* These macros are provided to "stringify" the value of the define +** for those options in which the value is meaningful. */ +#define CTIMEOPT_VAL_(opt) #opt +#define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt) + +/* +** An array of names of all compile-time options. This array should +** be sorted A-Z. +** +** This array looks large, but in a typical installation actually uses +** only a handful of compile-time options, so most times this array is usually +** rather short and uses little memory space. +*/ +static const char * const sqlite3azCompileOpt[] = { + +/* +** BEGIN CODE GENERATED BY tool/mkctime.tcl +*/ +#if SQLITE_32BIT_ROWID + "32BIT_ROWID", +#endif +#if SQLITE_4_BYTE_ALIGNED_MALLOC + "4_BYTE_ALIGNED_MALLOC", +#endif +#if SQLITE_64BIT_STATS + "64BIT_STATS", +#endif +#if SQLITE_ALLOW_COVERING_INDEX_SCAN + "ALLOW_COVERING_INDEX_SCAN", +#endif +#if SQLITE_ALLOW_URI_AUTHORITY + "ALLOW_URI_AUTHORITY", +#endif +#ifdef SQLITE_BITMASK_TYPE + "BITMASK_TYPE=" CTIMEOPT_VAL(SQLITE_BITMASK_TYPE), +#endif +#if SQLITE_BUG_COMPATIBLE_20160819 + "BUG_COMPATIBLE_20160819", +#endif +#if SQLITE_CASE_SENSITIVE_LIKE + "CASE_SENSITIVE_LIKE", +#endif +#if SQLITE_CHECK_PAGES + "CHECK_PAGES", +#endif +#if defined(__clang__) && defined(__clang_major__) + "COMPILER=clang-" CTIMEOPT_VAL(__clang_major__) "." + CTIMEOPT_VAL(__clang_minor__) "." + CTIMEOPT_VAL(__clang_patchlevel__), +#elif defined(_MSC_VER) + "COMPILER=msvc-" CTIMEOPT_VAL(_MSC_VER), +#elif defined(__GNUC__) && defined(__VERSION__) + "COMPILER=gcc-" __VERSION__, +#endif +#if SQLITE_COVERAGE_TEST + "COVERAGE_TEST", +#endif +#if SQLITE_DEBUG + "DEBUG", +#endif +#if SQLITE_DEFAULT_AUTOMATIC_INDEX + "DEFAULT_AUTOMATIC_INDEX", +#endif +#if SQLITE_DEFAULT_AUTOVACUUM + "DEFAULT_AUTOVACUUM", +#endif +#ifdef SQLITE_DEFAULT_CACHE_SIZE + "DEFAULT_CACHE_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_CACHE_SIZE), +#endif +#if SQLITE_DEFAULT_CKPTFULLFSYNC + "DEFAULT_CKPTFULLFSYNC", +#endif +#ifdef SQLITE_DEFAULT_FILE_FORMAT + "DEFAULT_FILE_FORMAT=" CTIMEOPT_VAL(SQLITE_DEFAULT_FILE_FORMAT), +#endif +#ifdef SQLITE_DEFAULT_FILE_PERMISSIONS + "DEFAULT_FILE_PERMISSIONS=" CTIMEOPT_VAL(SQLITE_DEFAULT_FILE_PERMISSIONS), +#endif +#if SQLITE_DEFAULT_FOREIGN_KEYS + "DEFAULT_FOREIGN_KEYS", +#endif +#ifdef SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT + "DEFAULT_JOURNAL_SIZE_LIMIT=" CTIMEOPT_VAL(SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT), +#endif +#ifdef SQLITE_DEFAULT_LOCKING_MODE + "DEFAULT_LOCKING_MODE=" CTIMEOPT_VAL(SQLITE_DEFAULT_LOCKING_MODE), +#endif +#ifdef SQLITE_DEFAULT_LOOKASIDE + "DEFAULT_LOOKASIDE=" CTIMEOPT_VAL(SQLITE_DEFAULT_LOOKASIDE), +#endif +#if SQLITE_DEFAULT_MEMSTATUS + "DEFAULT_MEMSTATUS", +#endif +#ifdef SQLITE_DEFAULT_MMAP_SIZE + "DEFAULT_MMAP_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_MMAP_SIZE), +#endif +#ifdef SQLITE_DEFAULT_PAGE_SIZE + "DEFAULT_PAGE_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_PAGE_SIZE), +#endif +#ifdef SQLITE_DEFAULT_PCACHE_INITSZ + "DEFAULT_PCACHE_INITSZ=" CTIMEOPT_VAL(SQLITE_DEFAULT_PCACHE_INITSZ), +#endif +#ifdef SQLITE_DEFAULT_PROXYDIR_PERMISSIONS + "DEFAULT_PROXYDIR_PERMISSIONS=" CTIMEOPT_VAL(SQLITE_DEFAULT_PROXYDIR_PERMISSIONS), +#endif +#if SQLITE_DEFAULT_RECURSIVE_TRIGGERS + "DEFAULT_RECURSIVE_TRIGGERS", +#endif +#ifdef SQLITE_DEFAULT_ROWEST + "DEFAULT_ROWEST=" CTIMEOPT_VAL(SQLITE_DEFAULT_ROWEST), +#endif +#ifdef SQLITE_DEFAULT_SECTOR_SIZE + "DEFAULT_SECTOR_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_SECTOR_SIZE), +#endif +#ifdef SQLITE_DEFAULT_SYNCHRONOUS + "DEFAULT_SYNCHRONOUS=" CTIMEOPT_VAL(SQLITE_DEFAULT_SYNCHRONOUS), +#endif +#ifdef SQLITE_DEFAULT_WAL_AUTOCHECKPOINT + "DEFAULT_WAL_AUTOCHECKPOINT=" CTIMEOPT_VAL(SQLITE_DEFAULT_WAL_AUTOCHECKPOINT), +#endif +#ifdef SQLITE_DEFAULT_WAL_SYNCHRONOUS + "DEFAULT_WAL_SYNCHRONOUS=" CTIMEOPT_VAL(SQLITE_DEFAULT_WAL_SYNCHRONOUS), +#endif +#ifdef SQLITE_DEFAULT_WORKER_THREADS + "DEFAULT_WORKER_THREADS=" CTIMEOPT_VAL(SQLITE_DEFAULT_WORKER_THREADS), +#endif +#if SQLITE_DIRECT_OVERFLOW_READ + "DIRECT_OVERFLOW_READ", +#endif +#if SQLITE_DISABLE_DIRSYNC + "DISABLE_DIRSYNC", +#endif +#if SQLITE_DISABLE_FTS3_UNICODE + "DISABLE_FTS3_UNICODE", +#endif +#if SQLITE_DISABLE_FTS4_DEFERRED + "DISABLE_FTS4_DEFERRED", +#endif +#if SQLITE_DISABLE_INTRINSIC + "DISABLE_INTRINSIC", +#endif +#if SQLITE_DISABLE_LFS + "DISABLE_LFS", +#endif +#if SQLITE_DISABLE_PAGECACHE_OVERFLOW_STATS + "DISABLE_PAGECACHE_OVERFLOW_STATS", +#endif +#if SQLITE_DISABLE_SKIPAHEAD_DISTINCT + "DISABLE_SKIPAHEAD_DISTINCT", +#endif +#ifdef SQLITE_ENABLE_8_3_NAMES + "ENABLE_8_3_NAMES=" CTIMEOPT_VAL(SQLITE_ENABLE_8_3_NAMES), +#endif +#if SQLITE_ENABLE_API_ARMOR + "ENABLE_API_ARMOR", +#endif +#if SQLITE_ENABLE_ATOMIC_WRITE + "ENABLE_ATOMIC_WRITE", +#endif +#if SQLITE_ENABLE_CEROD + "ENABLE_CEROD", +#endif +#if SQLITE_ENABLE_COLUMN_METADATA + "ENABLE_COLUMN_METADATA", +#endif +#if SQLITE_ENABLE_COLUMN_USED_MASK + "ENABLE_COLUMN_USED_MASK", +#endif +#if SQLITE_ENABLE_COSTMULT + "ENABLE_COSTMULT", +#endif +#if SQLITE_ENABLE_CURSOR_HINTS + "ENABLE_CURSOR_HINTS", +#endif +#if SQLITE_ENABLE_DBSTAT_VTAB + "ENABLE_DBSTAT_VTAB", +#endif +#if SQLITE_ENABLE_EXPENSIVE_ASSERT + "ENABLE_EXPENSIVE_ASSERT", +#endif +#if SQLITE_ENABLE_FTS1 + "ENABLE_FTS1", +#endif +#if SQLITE_ENABLE_FTS2 + "ENABLE_FTS2", +#endif +#if SQLITE_ENABLE_FTS3 + "ENABLE_FTS3", +#endif +#if SQLITE_ENABLE_FTS3_PARENTHESIS + "ENABLE_FTS3_PARENTHESIS", +#endif +#if SQLITE_ENABLE_FTS3_TOKENIZER + "ENABLE_FTS3_TOKENIZER", +#endif +#if SQLITE_ENABLE_FTS4 + "ENABLE_FTS4", +#endif +#if SQLITE_ENABLE_FTS5 + "ENABLE_FTS5", +#endif +#if SQLITE_ENABLE_HIDDEN_COLUMNS + "ENABLE_HIDDEN_COLUMNS", +#endif +#if SQLITE_ENABLE_ICU + "ENABLE_ICU", +#endif +#if SQLITE_ENABLE_IOTRACE + "ENABLE_IOTRACE", +#endif +#if SQLITE_ENABLE_JSON1 + "ENABLE_JSON1", +#endif +#if SQLITE_ENABLE_LOAD_EXTENSION + "ENABLE_LOAD_EXTENSION", +#endif +#ifdef SQLITE_ENABLE_LOCKING_STYLE + "ENABLE_LOCKING_STYLE=" CTIMEOPT_VAL(SQLITE_ENABLE_LOCKING_STYLE), +#endif +#if SQLITE_ENABLE_MEMORY_MANAGEMENT + "ENABLE_MEMORY_MANAGEMENT", +#endif +#if SQLITE_ENABLE_MEMSYS3 + "ENABLE_MEMSYS3", +#endif +#if SQLITE_ENABLE_MEMSYS5 + "ENABLE_MEMSYS5", +#endif +#if SQLITE_ENABLE_MULTIPLEX + "ENABLE_MULTIPLEX", +#endif +#if SQLITE_ENABLE_NULL_TRIM + "ENABLE_NULL_TRIM", +#endif +#if SQLITE_ENABLE_OVERSIZE_CELL_CHECK + "ENABLE_OVERSIZE_CELL_CHECK", +#endif +#if SQLITE_ENABLE_PREUPDATE_HOOK + "ENABLE_PREUPDATE_HOOK", +#endif +#if SQLITE_ENABLE_QPSG + "ENABLE_QPSG", +#endif +#if SQLITE_ENABLE_RBU + "ENABLE_RBU", +#endif +#if SQLITE_ENABLE_RTREE + "ENABLE_RTREE", +#endif +#if SQLITE_ENABLE_SELECTTRACE + "ENABLE_SELECTTRACE", +#endif +#if SQLITE_ENABLE_SESSION + "ENABLE_SESSION", +#endif +#if SQLITE_ENABLE_SNAPSHOT + "ENABLE_SNAPSHOT", +#endif +#if SQLITE_ENABLE_SQLLOG + "ENABLE_SQLLOG", +#endif +#if defined(SQLITE_ENABLE_STAT4) + "ENABLE_STAT4", +#elif defined(SQLITE_ENABLE_STAT3) + "ENABLE_STAT3", +#endif +#if SQLITE_ENABLE_STMTVTAB + "ENABLE_STMTVTAB", +#endif +#if SQLITE_ENABLE_STMT_SCANSTATUS + "ENABLE_STMT_SCANSTATUS", +#endif +#if SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION + "ENABLE_UNKNOWN_SQL_FUNCTION", +#endif +#if SQLITE_ENABLE_UNLOCK_NOTIFY + "ENABLE_UNLOCK_NOTIFY", +#endif +#if SQLITE_ENABLE_UPDATE_DELETE_LIMIT + "ENABLE_UPDATE_DELETE_LIMIT", +#endif +#if SQLITE_ENABLE_URI_00_ERROR + "ENABLE_URI_00_ERROR", +#endif +#if SQLITE_ENABLE_VFSTRACE + "ENABLE_VFSTRACE", +#endif +#if SQLITE_ENABLE_WHERETRACE + "ENABLE_WHERETRACE", +#endif +#if SQLITE_ENABLE_ZIPVFS + "ENABLE_ZIPVFS", +#endif +#if SQLITE_EXPLAIN_ESTIMATED_ROWS + "EXPLAIN_ESTIMATED_ROWS", +#endif +#if SQLITE_EXTRA_IFNULLROW + "EXTRA_IFNULLROW", +#endif +#ifdef SQLITE_EXTRA_INIT + "EXTRA_INIT=" CTIMEOPT_VAL(SQLITE_EXTRA_INIT), +#endif +#ifdef SQLITE_EXTRA_SHUTDOWN + "EXTRA_SHUTDOWN=" CTIMEOPT_VAL(SQLITE_EXTRA_SHUTDOWN), +#endif +#ifdef SQLITE_FTS3_MAX_EXPR_DEPTH + "FTS3_MAX_EXPR_DEPTH=" CTIMEOPT_VAL(SQLITE_FTS3_MAX_EXPR_DEPTH), +#endif +#if SQLITE_FTS5_ENABLE_TEST_MI + "FTS5_ENABLE_TEST_MI", +#endif +#if SQLITE_FTS5_NO_WITHOUT_ROWID + "FTS5_NO_WITHOUT_ROWID", +#endif +#if SQLITE_HAS_CODEC + "HAS_CODEC", +#endif +#if HAVE_ISNAN || SQLITE_HAVE_ISNAN + "HAVE_ISNAN", +#endif +#if SQLITE_HOMEGROWN_RECURSIVE_MUTEX + "HOMEGROWN_RECURSIVE_MUTEX", +#endif +#if SQLITE_IGNORE_AFP_LOCK_ERRORS + "IGNORE_AFP_LOCK_ERRORS", +#endif +#if SQLITE_IGNORE_FLOCK_LOCK_ERRORS + "IGNORE_FLOCK_LOCK_ERRORS", +#endif +#if SQLITE_INLINE_MEMCPY + "INLINE_MEMCPY", +#endif +#if SQLITE_INT64_TYPE + "INT64_TYPE", +#endif +#ifdef SQLITE_INTEGRITY_CHECK_ERROR_MAX + "INTEGRITY_CHECK_ERROR_MAX=" CTIMEOPT_VAL(SQLITE_INTEGRITY_CHECK_ERROR_MAX), +#endif +#if SQLITE_LIKE_DOESNT_MATCH_BLOBS + "LIKE_DOESNT_MATCH_BLOBS", +#endif +#if SQLITE_LOCK_TRACE + "LOCK_TRACE", +#endif +#if SQLITE_LOG_CACHE_SPILL + "LOG_CACHE_SPILL", +#endif +#ifdef SQLITE_MALLOC_SOFT_LIMIT + "MALLOC_SOFT_LIMIT=" CTIMEOPT_VAL(SQLITE_MALLOC_SOFT_LIMIT), +#endif +#ifdef SQLITE_MAX_ATTACHED + "MAX_ATTACHED=" CTIMEOPT_VAL(SQLITE_MAX_ATTACHED), +#endif +#ifdef SQLITE_MAX_COLUMN + "MAX_COLUMN=" CTIMEOPT_VAL(SQLITE_MAX_COLUMN), +#endif +#ifdef SQLITE_MAX_COMPOUND_SELECT + "MAX_COMPOUND_SELECT=" CTIMEOPT_VAL(SQLITE_MAX_COMPOUND_SELECT), +#endif +#ifdef SQLITE_MAX_DEFAULT_PAGE_SIZE + "MAX_DEFAULT_PAGE_SIZE=" CTIMEOPT_VAL(SQLITE_MAX_DEFAULT_PAGE_SIZE), +#endif +#ifdef SQLITE_MAX_EXPR_DEPTH + "MAX_EXPR_DEPTH=" CTIMEOPT_VAL(SQLITE_MAX_EXPR_DEPTH), +#endif +#ifdef SQLITE_MAX_FUNCTION_ARG + "MAX_FUNCTION_ARG=" CTIMEOPT_VAL(SQLITE_MAX_FUNCTION_ARG), +#endif +#ifdef SQLITE_MAX_LENGTH + "MAX_LENGTH=" CTIMEOPT_VAL(SQLITE_MAX_LENGTH), +#endif +#ifdef SQLITE_MAX_LIKE_PATTERN_LENGTH + "MAX_LIKE_PATTERN_LENGTH=" CTIMEOPT_VAL(SQLITE_MAX_LIKE_PATTERN_LENGTH), +#endif +#ifdef SQLITE_MAX_MEMORY + "MAX_MEMORY=" CTIMEOPT_VAL(SQLITE_MAX_MEMORY), +#endif +#ifdef SQLITE_MAX_MMAP_SIZE + "MAX_MMAP_SIZE=" CTIMEOPT_VAL(SQLITE_MAX_MMAP_SIZE), +#endif +#ifdef SQLITE_MAX_MMAP_SIZE_ + "MAX_MMAP_SIZE_=" CTIMEOPT_VAL(SQLITE_MAX_MMAP_SIZE_), +#endif +#ifdef SQLITE_MAX_PAGE_COUNT + "MAX_PAGE_COUNT=" CTIMEOPT_VAL(SQLITE_MAX_PAGE_COUNT), +#endif +#ifdef SQLITE_MAX_PAGE_SIZE + "MAX_PAGE_SIZE=" CTIMEOPT_VAL(SQLITE_MAX_PAGE_SIZE), +#endif +#ifdef SQLITE_MAX_SCHEMA_RETRY + "MAX_SCHEMA_RETRY=" CTIMEOPT_VAL(SQLITE_MAX_SCHEMA_RETRY), +#endif +#ifdef SQLITE_MAX_SQL_LENGTH + "MAX_SQL_LENGTH=" CTIMEOPT_VAL(SQLITE_MAX_SQL_LENGTH), +#endif +#ifdef SQLITE_MAX_TRIGGER_DEPTH + "MAX_TRIGGER_DEPTH=" CTIMEOPT_VAL(SQLITE_MAX_TRIGGER_DEPTH), +#endif +#ifdef SQLITE_MAX_VARIABLE_NUMBER + "MAX_VARIABLE_NUMBER=" CTIMEOPT_VAL(SQLITE_MAX_VARIABLE_NUMBER), +#endif +#ifdef SQLITE_MAX_VDBE_OP + "MAX_VDBE_OP=" CTIMEOPT_VAL(SQLITE_MAX_VDBE_OP), +#endif +#ifdef SQLITE_MAX_WORKER_THREADS + "MAX_WORKER_THREADS=" CTIMEOPT_VAL(SQLITE_MAX_WORKER_THREADS), +#endif +#if SQLITE_MEMDEBUG + "MEMDEBUG", +#endif +#if SQLITE_MIXED_ENDIAN_64BIT_FLOAT + "MIXED_ENDIAN_64BIT_FLOAT", +#endif +#if SQLITE_MMAP_READWRITE + "MMAP_READWRITE", +#endif +#if SQLITE_MUTEX_NOOP + "MUTEX_NOOP", +#endif +#if SQLITE_MUTEX_NREF + "MUTEX_NREF", +#endif +#if SQLITE_MUTEX_OMIT + "MUTEX_OMIT", +#endif +#if SQLITE_MUTEX_PTHREADS + "MUTEX_PTHREADS", +#endif +#if SQLITE_MUTEX_W32 + "MUTEX_W32", +#endif +#if SQLITE_NEED_ERR_NAME + "NEED_ERR_NAME", +#endif +#if SQLITE_NOINLINE + "NOINLINE", +#endif +#if SQLITE_NO_SYNC + "NO_SYNC", +#endif +#if SQLITE_OMIT_ALTERTABLE + "OMIT_ALTERTABLE", +#endif +#if SQLITE_OMIT_ANALYZE + "OMIT_ANALYZE", +#endif +#if SQLITE_OMIT_ATTACH + "OMIT_ATTACH", +#endif +#if SQLITE_OMIT_AUTHORIZATION + "OMIT_AUTHORIZATION", +#endif +#if SQLITE_OMIT_AUTOINCREMENT + "OMIT_AUTOINCREMENT", +#endif +#if SQLITE_OMIT_AUTOINIT + "OMIT_AUTOINIT", +#endif +#if SQLITE_OMIT_AUTOMATIC_INDEX + "OMIT_AUTOMATIC_INDEX", +#endif +#if SQLITE_OMIT_AUTORESET + "OMIT_AUTORESET", +#endif +#if SQLITE_OMIT_AUTOVACUUM + "OMIT_AUTOVACUUM", +#endif +#if SQLITE_OMIT_BETWEEN_OPTIMIZATION + "OMIT_BETWEEN_OPTIMIZATION", +#endif +#if SQLITE_OMIT_BLOB_LITERAL + "OMIT_BLOB_LITERAL", +#endif +#if SQLITE_OMIT_BTREECOUNT + "OMIT_BTREECOUNT", +#endif +#if SQLITE_OMIT_CAST + "OMIT_CAST", +#endif +#if SQLITE_OMIT_CHECK + "OMIT_CHECK", +#endif +#if SQLITE_OMIT_COMPLETE + "OMIT_COMPLETE", +#endif +#if SQLITE_OMIT_COMPOUND_SELECT + "OMIT_COMPOUND_SELECT", +#endif +#if SQLITE_OMIT_CONFLICT_CLAUSE + "OMIT_CONFLICT_CLAUSE", +#endif +#if SQLITE_OMIT_CTE + "OMIT_CTE", +#endif +#if SQLITE_OMIT_DATETIME_FUNCS + "OMIT_DATETIME_FUNCS", +#endif +#if SQLITE_OMIT_DECLTYPE + "OMIT_DECLTYPE", +#endif +#if SQLITE_OMIT_DEPRECATED + "OMIT_DEPRECATED", +#endif +#if SQLITE_OMIT_DISKIO + "OMIT_DISKIO", +#endif +#if SQLITE_OMIT_EXPLAIN + "OMIT_EXPLAIN", +#endif +#if SQLITE_OMIT_FLAG_PRAGMAS + "OMIT_FLAG_PRAGMAS", +#endif +#if SQLITE_OMIT_FLOATING_POINT + "OMIT_FLOATING_POINT", +#endif +#if SQLITE_OMIT_FOREIGN_KEY + "OMIT_FOREIGN_KEY", +#endif +#if SQLITE_OMIT_GET_TABLE + "OMIT_GET_TABLE", +#endif +#if SQLITE_OMIT_HEX_INTEGER + "OMIT_HEX_INTEGER", +#endif +#if SQLITE_OMIT_INCRBLOB + "OMIT_INCRBLOB", +#endif +#if SQLITE_OMIT_INTEGRITY_CHECK + "OMIT_INTEGRITY_CHECK", +#endif +#if SQLITE_OMIT_LIKE_OPTIMIZATION + "OMIT_LIKE_OPTIMIZATION", +#endif +#if SQLITE_OMIT_LOAD_EXTENSION + "OMIT_LOAD_EXTENSION", +#endif +#if SQLITE_OMIT_LOCALTIME + "OMIT_LOCALTIME", +#endif +#if SQLITE_OMIT_LOOKASIDE + "OMIT_LOOKASIDE", +#endif +#if SQLITE_OMIT_MEMORYDB + "OMIT_MEMORYDB", +#endif +#if SQLITE_OMIT_OR_OPTIMIZATION + "OMIT_OR_OPTIMIZATION", +#endif +#if SQLITE_OMIT_PAGER_PRAGMAS + "OMIT_PAGER_PRAGMAS", +#endif +#if SQLITE_OMIT_PARSER_TRACE + "OMIT_PARSER_TRACE", +#endif +#if SQLITE_OMIT_POPEN + "OMIT_POPEN", +#endif +#if SQLITE_OMIT_PRAGMA + "OMIT_PRAGMA", +#endif +#if SQLITE_OMIT_PROGRESS_CALLBACK + "OMIT_PROGRESS_CALLBACK", +#endif +#if SQLITE_OMIT_QUICKBALANCE + "OMIT_QUICKBALANCE", +#endif +#if SQLITE_OMIT_REINDEX + "OMIT_REINDEX", +#endif +#if SQLITE_OMIT_SCHEMA_PRAGMAS + "OMIT_SCHEMA_PRAGMAS", +#endif +#if SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS + "OMIT_SCHEMA_VERSION_PRAGMAS", +#endif +#if SQLITE_OMIT_SHARED_CACHE + "OMIT_SHARED_CACHE", +#endif +#if SQLITE_OMIT_SHUTDOWN_DIRECTORIES + "OMIT_SHUTDOWN_DIRECTORIES", +#endif +#if SQLITE_OMIT_SUBQUERY + "OMIT_SUBQUERY", +#endif +#if SQLITE_OMIT_TCL_VARIABLE + "OMIT_TCL_VARIABLE", +#endif +#if SQLITE_OMIT_TEMPDB + "OMIT_TEMPDB", +#endif +#if SQLITE_OMIT_TEST_CONTROL + "OMIT_TEST_CONTROL", +#endif +#if SQLITE_OMIT_TRACE + "OMIT_TRACE", +#endif +#if SQLITE_OMIT_TRIGGER + "OMIT_TRIGGER", +#endif +#if SQLITE_OMIT_TRUNCATE_OPTIMIZATION + "OMIT_TRUNCATE_OPTIMIZATION", +#endif +#if SQLITE_OMIT_UTF16 + "OMIT_UTF16", +#endif +#if SQLITE_OMIT_VACUUM + "OMIT_VACUUM", +#endif +#if SQLITE_OMIT_VIEW + "OMIT_VIEW", +#endif +#if SQLITE_OMIT_VIRTUALTABLE + "OMIT_VIRTUALTABLE", +#endif +#if SQLITE_OMIT_WAL + "OMIT_WAL", +#endif +#if SQLITE_OMIT_WSD + "OMIT_WSD", +#endif +#if SQLITE_OMIT_XFER_OPT + "OMIT_XFER_OPT", +#endif +#if SQLITE_PCACHE_SEPARATE_HEADER + "PCACHE_SEPARATE_HEADER", +#endif +#if SQLITE_PERFORMANCE_TRACE + "PERFORMANCE_TRACE", +#endif +#if SQLITE_POWERSAFE_OVERWRITE + "POWERSAFE_OVERWRITE", +#endif +#if SQLITE_PREFER_PROXY_LOCKING + "PREFER_PROXY_LOCKING", +#endif +#if SQLITE_PROXY_DEBUG + "PROXY_DEBUG", +#endif +#if SQLITE_REVERSE_UNORDERED_SELECTS + "REVERSE_UNORDERED_SELECTS", +#endif +#if SQLITE_RTREE_INT_ONLY + "RTREE_INT_ONLY", +#endif +#if SQLITE_SECURE_DELETE + "SECURE_DELETE", +#endif +#if SQLITE_SMALL_STACK + "SMALL_STACK", +#endif +#ifdef SQLITE_SORTER_PMASZ + "SORTER_PMASZ=" CTIMEOPT_VAL(SQLITE_SORTER_PMASZ), +#endif +#if SQLITE_SOUNDEX + "SOUNDEX", +#endif +#ifdef SQLITE_STAT4_SAMPLES + "STAT4_SAMPLES=" CTIMEOPT_VAL(SQLITE_STAT4_SAMPLES), +#endif +#ifdef SQLITE_STMTJRNL_SPILL + "STMTJRNL_SPILL=" CTIMEOPT_VAL(SQLITE_STMTJRNL_SPILL), +#endif +#if SQLITE_SUBSTR_COMPATIBILITY + "SUBSTR_COMPATIBILITY", +#endif +#if SQLITE_SYSTEM_MALLOC + "SYSTEM_MALLOC", +#endif +#if SQLITE_TCL + "TCL", +#endif +#ifdef SQLITE_TEMP_STORE + "TEMP_STORE=" CTIMEOPT_VAL(SQLITE_TEMP_STORE), +#endif +#if SQLITE_TEST + "TEST", +#endif +#if defined(SQLITE_THREADSAFE) + "THREADSAFE=" CTIMEOPT_VAL(SQLITE_THREADSAFE), +#elif defined(THREADSAFE) + "THREADSAFE=" CTIMEOPT_VAL(THREADSAFE), +#else + "THREADSAFE=1", +#endif +#if SQLITE_UNLINK_AFTER_CLOSE + "UNLINK_AFTER_CLOSE", +#endif +#if SQLITE_UNTESTABLE + "UNTESTABLE", +#endif +#if SQLITE_USER_AUTHENTICATION + "USER_AUTHENTICATION", +#endif +#if SQLITE_USE_ALLOCA + "USE_ALLOCA", +#endif +#if SQLITE_USE_FCNTL_TRACE + "USE_FCNTL_TRACE", +#endif +#if SQLITE_USE_URI + "USE_URI", +#endif +#if SQLITE_VDBE_COVERAGE + "VDBE_COVERAGE", +#endif +#if SQLITE_WIN32_MALLOC + "WIN32_MALLOC", +#endif +#if SQLITE_ZERO_MALLOC + "ZERO_MALLOC", +#endif +/* +** END CODE GENERATED BY tool/mkctime.tcl +*/ +}; + +SQLITE_PRIVATE const char **sqlite3CompileOptions(int *pnOpt){ + *pnOpt = sizeof(sqlite3azCompileOpt) / sizeof(sqlite3azCompileOpt[0]); + return (const char**)sqlite3azCompileOpt; +} + +#endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */ + +/************** End of ctime.c ***********************************************/ /************** Begin file sqliteInt.h ***************************************/ /* ** 2001 September 15 @@ -276,7 +1028,7 @@ /************** Include sqlite3.h in the middle of sqliteInt.h ***************/ /************** Begin file sqlite3.h *****************************************/ /* -** 2001 September 15 +** 2001-09-15 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: @@ -398,9 +1150,9 @@ extern "C" { ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.18.0" -#define SQLITE_VERSION_NUMBER 3018000 -#define SQLITE_SOURCE_ID "2017-03-28 18:48:43 424a0d380332858ee55bdebc4af3789f74e70a2b3ba1cf29d84b9b4bcf3e2e37" +#define SQLITE_VERSION "3.20.1" +#define SQLITE_VERSION_NUMBER 3020001 +#define SQLITE_SOURCE_ID "2017-08-24 16:21:36 8d3a7ea6c5690d6b7c3767558f4f01b511c55463e3f9e64506801fe9b74dce34" /* ** CAPI3REF: Run-Time Library Version Numbers @@ -694,7 +1446,7 @@ SQLITE_API int sqlite3_exec( */ #define SQLITE_OK 0 /* Successful result */ /* beginning-of-error-codes */ -#define SQLITE_ERROR 1 /* SQL error or missing database */ +#define SQLITE_ERROR 1 /* Generic error */ #define SQLITE_INTERNAL 2 /* Internal logic error in SQLite */ #define SQLITE_PERM 3 /* Access permission denied */ #define SQLITE_ABORT 4 /* Callback routine requested an abort */ @@ -709,7 +1461,7 @@ SQLITE_API int sqlite3_exec( #define SQLITE_FULL 13 /* Insertion failed because database is full */ #define SQLITE_CANTOPEN 14 /* Unable to open the database file */ #define SQLITE_PROTOCOL 15 /* Database lock protocol error */ -#define SQLITE_EMPTY 16 /* Database is empty */ +#define SQLITE_EMPTY 16 /* Not used */ #define SQLITE_SCHEMA 17 /* The database schema changed */ #define SQLITE_TOOBIG 18 /* String or BLOB exceeds size limit */ #define SQLITE_CONSTRAINT 19 /* Abort due to constraint violation */ @@ -717,7 +1469,7 @@ SQLITE_API int sqlite3_exec( #define SQLITE_MISUSE 21 /* Library used incorrectly */ #define SQLITE_NOLFS 22 /* Uses OS features not supported on host */ #define SQLITE_AUTH 23 /* Authorization denied */ -#define SQLITE_FORMAT 24 /* Auxiliary database format error */ +#define SQLITE_FORMAT 24 /* Not used */ #define SQLITE_RANGE 25 /* 2nd parameter to sqlite3_bind out of range */ #define SQLITE_NOTADB 26 /* File opened that is not a database file */ #define SQLITE_NOTICE 27 /* Notifications from sqlite3_log() */ @@ -1134,7 +1886,7 @@ struct sqlite3_io_methods { ** opcode allows these two values (10 retries and 25 milliseconds of delay) ** to be adjusted. The values are changed for all database connections ** within the same process. The argument is a pointer to an array of two -** integers where the first integer i the new retry count and the second +** integers where the first integer is the new retry count and the second ** integer is the delay. If either integer is negative, then the setting ** is not changed but instead the prior value of that setting is written ** into the array entry, allowing the current retry settings to be @@ -2284,6 +3036,17 @@ struct sqlite3_mem_methods { ** have been disabled - 0 if they are not disabled, 1 if they are. ** ** +**
SQLITE_DBCONFIG_ENABLE_QPSG
+**
^(The SQLITE_DBCONFIG_ENABLE_QPSG option activates or deactivates +** the [query planner stability guarantee] (QPSG). When the QPSG is active, +** a single SQL query statement will always use the same algorithm regardless +** of values of [bound parameters].)^ The QPSG disables some query optimizations +** that look at the values of bound parameters, which can make some queries +** slower. But the QPSG has the advantage of more predictable behavior. With +** the QPSG active, SQLite will always use the same query plan in the field as +** was used during testing in the lab. +**
+** ** */ #define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */ @@ -2293,6 +3056,7 @@ struct sqlite3_mem_methods { #define SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER 1004 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION 1005 /* int int* */ #define SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE 1006 /* int int* */ +#define SQLITE_DBCONFIG_ENABLE_QPSG 1007 /* int int* */ /* @@ -2488,9 +3252,6 @@ SQLITE_API int sqlite3_total_changes(sqlite3*); ** ^A call to sqlite3_interrupt(D) that occurs when there are no running ** SQL statements is a no-op and has no effect on SQL statements ** that are started after the sqlite3_interrupt() call returns. -** -** If the database connection closes while [sqlite3_interrupt()] -** is running then bad things will likely happen. */ SQLITE_API void sqlite3_interrupt(sqlite3*); @@ -2953,12 +3714,14 @@ SQLITE_API void sqlite3_randomness(int N, void *P); /* ** CAPI3REF: Compile-Time Authorization Callbacks ** METHOD: sqlite3 +** KEYWORDS: {authorizer callback} ** ** ^This routine registers an authorizer callback with a particular ** [database connection], supplied in the first argument. ** ^The authorizer callback is invoked as SQL statements are being compiled ** by [sqlite3_prepare()] or its variants [sqlite3_prepare_v2()], -** [sqlite3_prepare16()] and [sqlite3_prepare16_v2()]. ^At various +** [sqlite3_prepare_v3()], [sqlite3_prepare16()], [sqlite3_prepare16_v2()], +** and [sqlite3_prepare16_v3()]. ^At various ** points during the compilation process, as logic is being created ** to perform various actions, the authorizer callback is invoked to ** see if those actions are allowed. ^The authorizer callback should @@ -2980,8 +3743,10 @@ SQLITE_API void sqlite3_randomness(int N, void *P); ** parameter to the sqlite3_set_authorizer() interface. ^The second parameter ** to the callback is an integer [SQLITE_COPY | action code] that specifies ** the particular action to be authorized. ^The third through sixth parameters -** to the callback are zero-terminated strings that contain additional -** details about the action to be authorized. +** to the callback are either NULL pointers or zero-terminated strings +** that contain additional details about the action to be authorized. +** Applications must always be prepared to encounter a NULL pointer in any +** of the third through the sixth parameters of the authorization callback. ** ** ^If the action code is [SQLITE_READ] ** and the callback returns [SQLITE_IGNORE] then the @@ -2990,6 +3755,10 @@ SQLITE_API void sqlite3_randomness(int N, void *P); ** been read if [SQLITE_OK] had been returned. The [SQLITE_IGNORE] ** return can be used to deny an untrusted user access to individual ** columns of a table. +** ^When a table is referenced by a [SELECT] but no column values are +** extracted from that table (for example in a query like +** "SELECT count(*) FROM tab") then the [SQLITE_READ] authorizer callback +** is invoked once for that table with a column name that is an empty string. ** ^If the action code is [SQLITE_DELETE] and the callback returns ** [SQLITE_IGNORE] then the [DELETE] operation proceeds but the ** [truncate optimization] is disabled and all rows are deleted individually. @@ -3741,6 +4510,29 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); #define SQLITE_LIMIT_TRIGGER_DEPTH 10 #define SQLITE_LIMIT_WORKER_THREADS 11 +/* +** CAPI3REF: Prepare Flags +** +** These constants define various flags that can be passed into +** "prepFlags" parameter of the [sqlite3_prepare_v3()] and +** [sqlite3_prepare16_v3()] interfaces. +** +** New flags may be added in future releases of SQLite. +** +**
+** [[SQLITE_PREPARE_PERSISTENT]] ^(
SQLITE_PREPARE_PERSISTENT
+**
The SQLITE_PREPARE_PERSISTENT flag is a hint to the query planner +** that the prepared statement will be retained for a long time and +** probably reused many times.)^ ^Without this flag, [sqlite3_prepare_v3()] +** and [sqlite3_prepare16_v3()] assume that the prepared statement will +** be used just once or at most a few times and then destroyed using +** [sqlite3_finalize()] relatively soon. The current implementation acts +** on this hint by avoiding the use of [lookaside memory] so as not to +** deplete the limited store of lookaside memory. Future versions of +** SQLite may act on this hint differently. +**
+*/ +#define SQLITE_PREPARE_PERSISTENT 0x01 /* ** CAPI3REF: Compiling An SQL Statement @@ -3748,17 +4540,29 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); ** METHOD: sqlite3 ** CONSTRUCTOR: sqlite3_stmt ** -** To execute an SQL query, it must first be compiled into a byte-code -** program using one of these routines. +** To execute an SQL statement, it must first be compiled into a byte-code +** program using one of these routines. Or, in other words, these routines +** are constructors for the [prepared statement] object. +** +** The preferred routine to use is [sqlite3_prepare_v2()]. The +** [sqlite3_prepare()] interface is legacy and should be avoided. +** [sqlite3_prepare_v3()] has an extra "prepFlags" option that is used +** for special purposes. +** +** The use of the UTF-8 interfaces is preferred, as SQLite currently +** does all parsing using UTF-8. The UTF-16 interfaces are provided +** as a convenience. The UTF-16 interfaces work by converting the +** input text into UTF-8, then invoking the corresponding UTF-8 interface. ** ** The first argument, "db", is a [database connection] obtained from a ** prior successful call to [sqlite3_open()], [sqlite3_open_v2()] or ** [sqlite3_open16()]. The database connection must not have been closed. ** ** The second argument, "zSql", is the statement to be compiled, encoded -** as either UTF-8 or UTF-16. The sqlite3_prepare() and sqlite3_prepare_v2() -** interfaces use UTF-8, and sqlite3_prepare16() and sqlite3_prepare16_v2() -** use UTF-16. +** as either UTF-8 or UTF-16. The sqlite3_prepare(), sqlite3_prepare_v2(), +** and sqlite3_prepare_v3() +** interfaces use UTF-8, and sqlite3_prepare16(), sqlite3_prepare16_v2(), +** and sqlite3_prepare16_v3() use UTF-16. ** ** ^If the nByte argument is negative, then zSql is read up to the ** first zero terminator. ^If nByte is positive, then it is the @@ -3785,10 +4589,11 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); ** ^On success, the sqlite3_prepare() family of routines return [SQLITE_OK]; ** otherwise an [error code] is returned. ** -** The sqlite3_prepare_v2() and sqlite3_prepare16_v2() interfaces are -** recommended for all new programs. The two older interfaces are retained -** for backwards compatibility, but their use is discouraged. -** ^In the "v2" interfaces, the prepared statement +** The sqlite3_prepare_v2(), sqlite3_prepare_v3(), sqlite3_prepare16_v2(), +** and sqlite3_prepare16_v3() interfaces are recommended for all new programs. +** The older interfaces (sqlite3_prepare() and sqlite3_prepare16()) +** are retained for backwards compatibility, but their use is discouraged. +** ^In the "vX" interfaces, the prepared statement ** that is returned (the [sqlite3_stmt] object) contains a copy of the ** original SQL text. This causes the [sqlite3_step()] interface to ** behave differently in three ways: @@ -3821,6 +4626,12 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); ** or [GLOB] operator or if the parameter is compared to an indexed column ** and the [SQLITE_ENABLE_STAT3] compile-time option is enabled. ** +** +**

^sqlite3_prepare_v3() differs from sqlite3_prepare_v2() only in having +** the extra prepFlags parameter, which is a bit array consisting of zero or +** more of the [SQLITE_PREPARE_PERSISTENT|SQLITE_PREPARE_*] flags. ^The +** sqlite3_prepare_v2() interface works exactly the same as +** sqlite3_prepare_v3() with a zero prepFlags parameter. ** */ SQLITE_API int sqlite3_prepare( @@ -3837,6 +4648,14 @@ SQLITE_API int sqlite3_prepare_v2( sqlite3_stmt **ppStmt, /* OUT: Statement handle */ const char **pzTail /* OUT: Pointer to unused portion of zSql */ ); +SQLITE_API int sqlite3_prepare_v3( + sqlite3 *db, /* Database handle */ + const char *zSql, /* SQL statement, UTF-8 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + unsigned int prepFlags, /* Zero or more SQLITE_PREPARE_ flags */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const char **pzTail /* OUT: Pointer to unused portion of zSql */ +); SQLITE_API int sqlite3_prepare16( sqlite3 *db, /* Database handle */ const void *zSql, /* SQL statement, UTF-16 encoded */ @@ -3851,6 +4670,14 @@ SQLITE_API int sqlite3_prepare16_v2( sqlite3_stmt **ppStmt, /* OUT: Statement handle */ const void **pzTail /* OUT: Pointer to unused portion of zSql */ ); +SQLITE_API int sqlite3_prepare16_v3( + sqlite3 *db, /* Database handle */ + const void *zSql, /* SQL statement, UTF-16 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + unsigned int prepFlags, /* Zero or more SQLITE_PREPARE_ flags */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const void **pzTail /* OUT: Pointer to unused portion of zSql */ +); /* ** CAPI3REF: Retrieving Statement SQL @@ -3858,7 +4685,8 @@ SQLITE_API int sqlite3_prepare16_v2( ** ** ^The sqlite3_sql(P) interface returns a pointer to a copy of the UTF-8 ** SQL text used to create [prepared statement] P if P was -** created by either [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()]. +** created by [sqlite3_prepare_v2()], [sqlite3_prepare_v3()], +** [sqlite3_prepare16_v2()], or [sqlite3_prepare16_v3()]. ** ^The sqlite3_expanded_sql(P) interface returns a pointer to a UTF-8 ** string containing the SQL text of prepared statement P with ** [bound parameters] expanded. @@ -3982,7 +4810,7 @@ SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt*); ** The [sqlite3_value_blob | sqlite3_value_type()] family of ** interfaces require protected sqlite3_value objects. */ -typedef struct Mem sqlite3_value; +typedef struct sqlite3_value sqlite3_value; /* ** CAPI3REF: SQL Function Context Object @@ -4084,6 +4912,15 @@ typedef struct sqlite3_context sqlite3_context; ** [sqlite3_blob_open | incremental BLOB I/O] routines. ** ^A negative value for the zeroblob results in a zero-length BLOB. ** +** ^The sqlite3_bind_pointer(S,I,P,T,D) routine causes the I-th parameter in +** [prepared statement] S to have an SQL value of NULL, but to also be +** associated with the pointer P of type T. ^D is either a NULL pointer or +** a pointer to a destructor function for P. ^SQLite will invoke the +** destructor D with a single argument of P when it is finished using +** P. The T parameter should be a static string, preferably a string +** literal. The sqlite3_bind_pointer() routine is part of the +** [pointer passing interface] added for SQLite 3.20.0. +** ** ^If any of the sqlite3_bind_*() routines are called with a NULL pointer ** for the [prepared statement] or with a prepared statement for which ** [sqlite3_step()] has been called more recently than [sqlite3_reset()], @@ -4117,6 +4954,7 @@ SQLITE_API int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void(*) SQLITE_API int sqlite3_bind_text64(sqlite3_stmt*, int, const char*, sqlite3_uint64, void(*)(void*), unsigned char encoding); SQLITE_API int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*); +SQLITE_API int sqlite3_bind_pointer(sqlite3_stmt*, int, void*, const char*,void(*)(void*)); SQLITE_API int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n); SQLITE_API int sqlite3_bind_zeroblob64(sqlite3_stmt*, int, sqlite3_uint64); @@ -4160,8 +4998,8 @@ SQLITE_API int sqlite3_bind_parameter_count(sqlite3_stmt*); ** ^If the value N is out of range or if the N-th parameter is ** nameless, then NULL is returned. ^The returned string is ** always in UTF-8 encoding even if the named parameter was -** originally specified as UTF-16 in [sqlite3_prepare16()] or -** [sqlite3_prepare16_v2()]. +** originally specified as UTF-16 in [sqlite3_prepare16()], +** [sqlite3_prepare16_v2()], or [sqlite3_prepare16_v3()]. ** ** See also: [sqlite3_bind_blob|sqlite3_bind()], ** [sqlite3_bind_parameter_count()], and @@ -4178,7 +5016,8 @@ SQLITE_API const char *sqlite3_bind_parameter_name(sqlite3_stmt*, int); ** parameter to [sqlite3_bind_blob|sqlite3_bind()]. ^A zero ** is returned if no matching parameter is found. ^The parameter ** name must be given in UTF-8 even if the original statement -** was prepared from UTF-16 text using [sqlite3_prepare16_v2()]. +** was prepared from UTF-16 text using [sqlite3_prepare16_v2()] or +** [sqlite3_prepare16_v3()]. ** ** See also: [sqlite3_bind_blob|sqlite3_bind()], ** [sqlite3_bind_parameter_count()], and @@ -4332,16 +5171,18 @@ SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int); ** CAPI3REF: Evaluate An SQL Statement ** METHOD: sqlite3_stmt ** -** After a [prepared statement] has been prepared using either -** [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()] or one of the legacy +** After a [prepared statement] has been prepared using any of +** [sqlite3_prepare_v2()], [sqlite3_prepare_v3()], [sqlite3_prepare16_v2()], +** or [sqlite3_prepare16_v3()] or one of the legacy ** interfaces [sqlite3_prepare()] or [sqlite3_prepare16()], this function ** must be called one or more times to evaluate the statement. ** ** The details of the behavior of the sqlite3_step() interface depend -** on whether the statement was prepared using the newer "v2" interface -** [sqlite3_prepare_v2()] and [sqlite3_prepare16_v2()] or the older legacy -** interface [sqlite3_prepare()] and [sqlite3_prepare16()]. The use of the -** new "v2" interface is recommended for new applications but the legacy +** on whether the statement was prepared using the newer "vX" interfaces +** [sqlite3_prepare_v3()], [sqlite3_prepare_v2()], [sqlite3_prepare16_v3()], +** [sqlite3_prepare16_v2()] or the older legacy +** interfaces [sqlite3_prepare()] and [sqlite3_prepare16()]. The use of the +** new "vX" interface is recommended for new applications but the legacy ** interface will continue to be supported. ** ** ^In the legacy interface, the return value will be either [SQLITE_BUSY], @@ -4387,7 +5228,7 @@ SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int); ** other than [SQLITE_ROW] before any subsequent invocation of ** sqlite3_step(). Failure to reset the prepared statement using ** [sqlite3_reset()] would result in an [SQLITE_MISUSE] return from -** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1], +** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1]), ** sqlite3_step() began ** calling [sqlite3_reset()] automatically in this circumstance rather ** than returning [SQLITE_MISUSE]. This is not considered a compatibility @@ -4402,10 +5243,11 @@ SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int); ** specific [error codes] that better describes the error. ** We admit that this is a goofy design. The problem has been fixed ** with the "v2" interface. If you prepare all of your SQL statements -** using either [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()] instead +** using [sqlite3_prepare_v3()] or [sqlite3_prepare_v2()] +** or [sqlite3_prepare16_v2()] or [sqlite3_prepare16_v3()] instead ** of the legacy [sqlite3_prepare()] and [sqlite3_prepare16()] interfaces, ** then the more specific [error codes] are returned directly -** by sqlite3_step(). The use of the "v2" interface is recommended. +** by sqlite3_step(). The use of the "vX" interfaces is recommended. */ SQLITE_API int sqlite3_step(sqlite3_stmt*); @@ -4467,6 +5309,28 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); ** KEYWORDS: {column access functions} ** METHOD: sqlite3_stmt ** +** Summary: +**

+**
sqlite3_column_blobBLOB result +**
sqlite3_column_doubleREAL result +**
sqlite3_column_int32-bit INTEGER result +**
sqlite3_column_int6464-bit INTEGER result +**
sqlite3_column_textUTF-8 TEXT result +**
sqlite3_column_text16UTF-16 TEXT result +**
sqlite3_column_valueThe result as an +** [sqlite3_value|unprotected sqlite3_value] object. +**
    +**
sqlite3_column_bytesSize of a BLOB +** or a UTF-8 TEXT result in bytes +**
sqlite3_column_bytes16   +** →  Size of UTF-16 +** TEXT in bytes +**
sqlite3_column_typeDefault +** datatype of the result +**
+** +** Details: +** ** ^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*] @@ -4488,16 +5352,29 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); ** are called from a different thread while any of these routines ** are pending, then the results are undefined. ** +** The first six interfaces (_blob, _double, _int, _int64, _text, and _text16) +** each return the value of a result column in a specific data format. If +** the result column is not initially in the requested format (for example, +** if the query returns an integer but the sqlite3_column_text() interface +** is used to extract the value) then an automatic type conversion is performed. +** ** ^The sqlite3_column_type() routine returns the ** [SQLITE_INTEGER | datatype code] for the initial data type ** of the result column. ^The returned value is one of [SQLITE_INTEGER], -** [SQLITE_FLOAT], [SQLITE_TEXT], [SQLITE_BLOB], or [SQLITE_NULL]. The value -** returned by sqlite3_column_type() is only meaningful if no type -** conversions have occurred as described below. After a type conversion, -** the value returned by sqlite3_column_type() is undefined. Future +** [SQLITE_FLOAT], [SQLITE_TEXT], [SQLITE_BLOB], or [SQLITE_NULL]. +** The return value of sqlite3_column_type() can be used to decide which +** of the first six interface should be used to extract the column value. +** The value returned by sqlite3_column_type() is only meaningful if no +** automatic type conversions have occurred for the value in question. +** After a type conversion, the result of calling sqlite3_column_type() +** is undefined, though harmless. Future ** versions of SQLite may change the behavior of sqlite3_column_type() ** following a type conversion. ** +** If the result is a BLOB or a TEXT string, then the sqlite3_column_bytes() +** or sqlite3_column_bytes16() interfaces can be used to determine the size +** of that BLOB or string. +** ** ^If the result is a BLOB or UTF-8 string then the sqlite3_column_bytes() ** routine returns the number of bytes in that BLOB or string. ** ^If the result is a UTF-16 string, then sqlite3_column_bytes() converts @@ -4534,9 +5411,13 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); ** [sqlite3_column_value()] is used in any other way, including calls ** to routines like [sqlite3_value_int()], [sqlite3_value_text()], ** or [sqlite3_value_bytes()], the behavior is not threadsafe. +** Hence, the sqlite3_column_value() interface +** is normally only useful within the implementation of +** [application-defined SQL functions] or [virtual tables], not within +** top-level application code. ** -** These routines attempt to convert the value where appropriate. ^For -** example, if the internal representation is FLOAT and a text result +** The these routines may attempt to convert the datatype of the result. +** ^For example, if the internal representation is FLOAT and a text result ** is requested, [sqlite3_snprintf()] is used internally to perform the ** conversion automatically. ^(The following table details the conversions ** that are applied: @@ -4608,7 +5489,7 @@ SQLITE_API int 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 not pass the pointers returned +** and BLOBs is freed automatically. Do not pass the pointers returned ** from [sqlite3_column_blob()], [sqlite3_column_text()], etc. into ** [sqlite3_free()]. ** @@ -4619,15 +5500,15 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); ** [SQLITE_NOMEM].)^ */ SQLITE_API const void *sqlite3_column_blob(sqlite3_stmt*, int iCol); -SQLITE_API int sqlite3_column_bytes(sqlite3_stmt*, int iCol); -SQLITE_API int sqlite3_column_bytes16(sqlite3_stmt*, int iCol); SQLITE_API double sqlite3_column_double(sqlite3_stmt*, int iCol); SQLITE_API int sqlite3_column_int(sqlite3_stmt*, int iCol); SQLITE_API sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol); SQLITE_API const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol); SQLITE_API const void *sqlite3_column_text16(sqlite3_stmt*, int iCol); -SQLITE_API int sqlite3_column_type(sqlite3_stmt*, int iCol); SQLITE_API sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_bytes(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_bytes16(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_type(sqlite3_stmt*, int iCol); /* ** CAPI3REF: Destroy A Prepared Statement Object @@ -4861,21 +5742,40 @@ SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int6 ** CAPI3REF: Obtaining SQL Values ** METHOD: sqlite3_value ** -** The C-language implementation of SQL functions and aggregates uses -** this set of interface routines to access the parameter values on -** the function or aggregate. +** Summary: +**
+**
sqlite3_value_blobBLOB value +**
sqlite3_value_doubleREAL value +**
sqlite3_value_int32-bit INTEGER value +**
sqlite3_value_int6464-bit INTEGER value +**
sqlite3_value_pointerPointer value +**
sqlite3_value_textUTF-8 TEXT value +**
sqlite3_value_text16UTF-16 TEXT value in +** the native byteorder +**
sqlite3_value_text16beUTF-16be TEXT value +**
sqlite3_value_text16leUTF-16le TEXT value +**
    +**
sqlite3_value_bytesSize of a BLOB +** or a UTF-8 TEXT in bytes +**
sqlite3_value_bytes16   +** →  Size of UTF-16 +** TEXT in bytes +**
sqlite3_value_typeDefault +** datatype of the value +**
sqlite3_value_numeric_type   +** →  Best numeric datatype of the value +**
** -** The xFunc (for scalar functions) or xStep (for aggregates) parameters -** to [sqlite3_create_function()] and [sqlite3_create_function16()] -** define callbacks that implement the SQL functions and aggregates. -** The 3rd parameter to these callbacks is an array of pointers to -** [protected sqlite3_value] objects. There is one [sqlite3_value] object for -** each parameter to the SQL function. These routines are used to -** extract values from the [sqlite3_value] objects. +** Details: +** +** These routines extract type, size, and content information from +** [protected sqlite3_value] objects. Protected sqlite3_value objects +** are used to pass parameter information into implementation of +** [application-defined SQL functions] and [virtual tables]. ** ** These routines work only with [protected sqlite3_value] objects. ** Any attempt to use these routines on an [unprotected sqlite3_value] -** object results in undefined behavior. +** is not threadsafe. ** ** ^These routines work just like the corresponding [column access functions] ** except that these routines take a single [protected sqlite3_value] object @@ -4886,6 +5786,24 @@ SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int6 ** sqlite3_value_text16be() and sqlite3_value_text16le() interfaces ** extract UTF-16 strings as big-endian and little-endian respectively. ** +** ^If [sqlite3_value] object V was initialized +** using [sqlite3_bind_pointer(S,I,P,X,D)] or [sqlite3_result_pointer(C,P,X,D)] +** and if X and Y are strings that compare equal according to strcmp(X,Y), +** then sqlite3_value_pointer(V,Y) will return the pointer P. ^Otherwise, +** sqlite3_value_pointer(V,Y) returns a NULL. The sqlite3_bind_pointer() +** routine is part of the [pointer passing interface] added for SQLite 3.20.0. +** +** ^(The sqlite3_value_type(V) interface returns the +** [SQLITE_INTEGER | datatype code] for the initial datatype of the +** [sqlite3_value] object V. The returned value is one of [SQLITE_INTEGER], +** [SQLITE_FLOAT], [SQLITE_TEXT], [SQLITE_BLOB], or [SQLITE_NULL].)^ +** Other interfaces might change the datatype for an sqlite3_value object. +** For example, if the datatype is initially SQLITE_INTEGER and +** sqlite3_value_text(V) is called to extract a text value for that +** integer, then subsequent calls to sqlite3_value_type(V) might return +** SQLITE_TEXT. Whether or not a persistent internal datatype conversion +** occurs is undefined and may change from one release of SQLite to the next. +** ** ^(The sqlite3_value_numeric_type() interface attempts to apply ** numeric affinity to the value. This means that an attempt is ** made to convert the value to an integer or floating point. If @@ -4904,15 +5822,16 @@ SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int6 ** the SQL function that supplied the [sqlite3_value*] parameters. */ SQLITE_API const void *sqlite3_value_blob(sqlite3_value*); -SQLITE_API int sqlite3_value_bytes(sqlite3_value*); -SQLITE_API int sqlite3_value_bytes16(sqlite3_value*); SQLITE_API double sqlite3_value_double(sqlite3_value*); SQLITE_API int sqlite3_value_int(sqlite3_value*); SQLITE_API sqlite3_int64 sqlite3_value_int64(sqlite3_value*); +SQLITE_API void *sqlite3_value_pointer(sqlite3_value*, const char*); SQLITE_API const unsigned char *sqlite3_value_text(sqlite3_value*); SQLITE_API const void *sqlite3_value_text16(sqlite3_value*); SQLITE_API const void *sqlite3_value_text16le(sqlite3_value*); SQLITE_API const void *sqlite3_value_text16be(sqlite3_value*); +SQLITE_API int sqlite3_value_bytes(sqlite3_value*); +SQLITE_API int sqlite3_value_bytes16(sqlite3_value*); SQLITE_API int sqlite3_value_type(sqlite3_value*); SQLITE_API int sqlite3_value_numeric_type(sqlite3_value*); @@ -4925,10 +5844,6 @@ SQLITE_API int sqlite3_value_numeric_type(sqlite3_value*); ** information can be used to pass a limited amount of context from ** one SQL function to another. Use the [sqlite3_result_subtype()] ** routine to set the subtype for the return value of an SQL function. -** -** SQLite makes no use of subtype itself. It merely passes the subtype -** from the result of one [application-defined SQL function] into the -** input of another. */ SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value*); @@ -5036,10 +5951,11 @@ SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context*); ** the compiled regular expression can be reused on multiple ** invocations of the same function. ** -** ^The sqlite3_get_auxdata() interface returns a pointer to the metadata -** associated by the sqlite3_set_auxdata() function with the Nth argument -** value to the application-defined function. ^If there is no metadata -** associated with the function argument, this sqlite3_get_auxdata() interface +** ^The sqlite3_get_auxdata(C,N) interface returns a pointer to the metadata +** associated by the sqlite3_set_auxdata(C,N,P,X) function with the Nth argument +** value to the application-defined function. ^N is zero for the left-most +** function argument. ^If there is no metadata +** associated with the function argument, the sqlite3_get_auxdata(C,N) interface ** returns a NULL pointer. ** ** ^The sqlite3_set_auxdata(C,N,P,X) interface saves P as metadata for the N-th @@ -5070,6 +5986,10 @@ SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context*); ** function parameters that are compile-time constants, including literal ** values and [parameters] and expressions composed from the same.)^ ** +** The value of the N parameter to these interfaces should be non-negative. +** Future enhancements may make use of negative N values to define new +** kinds of function caching behavior. +** ** These routines must be called from the same thread in which ** the SQL function is running. */ @@ -5193,7 +6113,7 @@ typedef void (*sqlite3_destructor_type)(void*); ** when it has finished using that result. ** ^If the 4th parameter to the sqlite3_result_text* interfaces ** or sqlite3_result_blob is the special constant SQLITE_TRANSIENT -** then SQLite makes a copy of the result into space obtained from +** then SQLite makes a copy of the result into space obtained ** from [sqlite3_malloc()] before it returns. ** ** ^The sqlite3_result_value() interface sets the result of @@ -5206,6 +6126,17 @@ typedef void (*sqlite3_destructor_type)(void*); ** [unprotected sqlite3_value] object is required, so either ** kind of [sqlite3_value] object can be used with this interface. ** +** ^The sqlite3_result_pointer(C,P,T,D) interface sets the result to an +** SQL NULL value, just like [sqlite3_result_null(C)], except that it +** also associates the host-language pointer P or type T with that +** NULL value such that the pointer can be retrieved within an +** [application-defined SQL function] using [sqlite3_value_pointer()]. +** ^If the D parameter is not NULL, then it is a pointer to a destructor +** for the P parameter. ^SQLite invokes D with P as its only argument +** when SQLite is finished with P. The T parameter should be a static +** string and preferably a string literal. The sqlite3_result_pointer() +** routine is part of the [pointer passing interface] added for SQLite 3.20.0. +** ** If these routines are called from within the different thread ** than the one containing the application-defined function that received ** the [sqlite3_context] pointer, the results are undefined. @@ -5229,6 +6160,7 @@ SQLITE_API void sqlite3_result_text16(sqlite3_context*, const void*, int, void(* SQLITE_API void sqlite3_result_text16le(sqlite3_context*, const void*, int,void(*)(void*)); SQLITE_API void sqlite3_result_text16be(sqlite3_context*, const void*, int,void(*)(void*)); SQLITE_API void sqlite3_result_value(sqlite3_context*, sqlite3_value*); +SQLITE_API void sqlite3_result_pointer(sqlite3_context*, void*,const char*,void(*)(void*)); SQLITE_API void sqlite3_result_zeroblob(sqlite3_context*, int n); SQLITE_API int sqlite3_result_zeroblob64(sqlite3_context*, sqlite3_uint64 n); @@ -5888,7 +6820,9 @@ SQLITE_API SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N); ** ^If the column-name parameter to sqlite3_table_column_metadata() is a ** NULL pointer, then this routine simply checks for the existence of the ** table and returns SQLITE_OK if the table exists and SQLITE_ERROR if it -** does not. +** does not. If the table name parameter T in a call to +** sqlite3_table_column_metadata(X,D,T,C,...) is NULL then the result is +** undefined behavior. ** ** ^The column is identified by the second, third and fourth parameters to ** this function. ^(The second parameter is either the name of the database @@ -7401,6 +8335,24 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); ** used as a proxy for the total work done by the prepared statement. ** If the number of virtual machine operations exceeds 2147483647 ** then the value returned by this statement status code is undefined. +** +** [[SQLITE_STMTSTATUS_REPREPARE]]
SQLITE_STMTSTATUS_REPREPARE
+**
^This is the number of times that the prepare statement has been +** automatically regenerated due to schema changes or change to +** [bound parameters] that might affect the query plan. +** +** [[SQLITE_STMTSTATUS_RUN]]
SQLITE_STMTSTATUS_RUN
+**
^This is the number of times that the prepared statement has +** been run. A single "run" for the purposes of this counter is one +** or more calls to [sqlite3_step()] followed by a call to [sqlite3_reset()]. +** The counter is incremented on the first [sqlite3_step()] call of each +** cycle. +** +** [[SQLITE_STMTSTATUS_MEMUSED]]
SQLITE_STMTSTATUS_MEMUSED
+**
^This is the approximate number of bytes of heap memory +** used to store the prepared statement. ^This value is not actually +** a counter, and so the resetFlg parameter to sqlite3_stmt_status() +** is ignored when the opcode is SQLITE_STMTSTATUS_MEMUSED. **
** */ @@ -7408,6 +8360,9 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); #define SQLITE_STMTSTATUS_SORT 2 #define SQLITE_STMTSTATUS_AUTOINDEX 3 #define SQLITE_STMTSTATUS_VM_STEP 4 +#define SQLITE_STMTSTATUS_REPREPARE 5 +#define SQLITE_STMTSTATUS_RUN 6 +#define SQLITE_STMTSTATUS_MEMUSED 99 /* ** CAPI3REF: Custom Page Cache Object @@ -9664,7 +10619,7 @@ typedef struct sqlite3_changegroup sqlite3_changegroup; ** sqlite3changegroup_output() functions, also available are the streaming ** versions sqlite3changegroup_add_strm() and sqlite3changegroup_output_strm(). */ -int sqlite3changegroup_new(sqlite3_changegroup **pp); +SQLITE_API int sqlite3changegroup_new(sqlite3_changegroup **pp); /* ** CAPI3REF: Add A Changeset To A Changegroup @@ -9741,7 +10696,7 @@ int sqlite3changegroup_new(sqlite3_changegroup **pp); ** ** If no error occurs, SQLITE_OK is returned. */ -int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData); +SQLITE_API int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData); /* ** CAPI3REF: Obtain A Composite Changeset From A Changegroup @@ -9767,7 +10722,7 @@ int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData); ** responsibility of the caller to eventually free the buffer using a ** call to sqlite3_free(). */ -int sqlite3changegroup_output( +SQLITE_API int sqlite3changegroup_output( sqlite3_changegroup*, int *pnData, /* OUT: Size of output buffer in bytes */ void **ppData /* OUT: Pointer to output buffer */ @@ -9776,7 +10731,7 @@ int sqlite3changegroup_output( /* ** CAPI3REF: Delete A Changegroup Object */ -void sqlite3changegroup_delete(sqlite3_changegroup*); +SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*); /* ** CAPI3REF: Apply A Changeset To A Database @@ -10165,11 +11120,11 @@ SQLITE_API int sqlite3session_patchset_strm( int (*xOutput)(void *pOut, const void *pData, int nData), void *pOut ); -int sqlite3changegroup_add_strm(sqlite3_changegroup*, +SQLITE_API int sqlite3changegroup_add_strm(sqlite3_changegroup*, int (*xInput)(void *pIn, void *pData, int *pnData), void *pIn ); -int sqlite3changegroup_output_strm(sqlite3_changegroup*, +SQLITE_API int sqlite3changegroup_output_strm(sqlite3_changegroup*, int (*xOutput)(void *pOut, const void *pData, int nData), void *pOut ); @@ -10774,8 +11729,9 @@ struct fts5_api { ** Include the configuration header output by 'configure' if we're using the ** autoconf-based build */ -#ifdef _HAVE_SQLITE_CONFIG_H -#include "config.h" +#if defined(_HAVE_SQLITE_CONFIG_H) && !defined(SQLITECONFIG_H) +/* #include "config.h" */ +#define SQLITECONFIG_H 1 #endif /************** Include sqliteLimit.h in the middle of sqliteInt.h ***********/ @@ -11085,6 +12041,11 @@ struct fts5_api { ** ** Older versions of SQLite used an optional THREADSAFE macro. ** We support that for legacy. +** +** To ensure that the correct value of "THREADSAFE" is reported when querying +** for compile-time options at runtime (e.g. "PRAGMA compile_options"), this +** logic is partially replicated in ctime.c. If it is updated here, it should +** also be updated there. */ #if !defined(SQLITE_THREADSAFE) # if defined(THREADSAFE) @@ -11453,76 +12414,76 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); #define TK_AS 24 #define TK_WITHOUT 25 #define TK_COMMA 26 -#define TK_OR 27 -#define TK_AND 28 -#define TK_IS 29 -#define TK_MATCH 30 -#define TK_LIKE_KW 31 -#define TK_BETWEEN 32 -#define TK_IN 33 -#define TK_ISNULL 34 -#define TK_NOTNULL 35 -#define TK_NE 36 -#define TK_EQ 37 -#define TK_GT 38 -#define TK_LE 39 -#define TK_LT 40 -#define TK_GE 41 -#define TK_ESCAPE 42 -#define TK_BITAND 43 -#define TK_BITOR 44 -#define TK_LSHIFT 45 -#define TK_RSHIFT 46 -#define TK_PLUS 47 -#define TK_MINUS 48 -#define TK_STAR 49 -#define TK_SLASH 50 -#define TK_REM 51 -#define TK_CONCAT 52 -#define TK_COLLATE 53 -#define TK_BITNOT 54 -#define TK_ID 55 -#define TK_INDEXED 56 -#define TK_ABORT 57 -#define TK_ACTION 58 -#define TK_AFTER 59 -#define TK_ANALYZE 60 -#define TK_ASC 61 -#define TK_ATTACH 62 -#define TK_BEFORE 63 -#define TK_BY 64 -#define TK_CASCADE 65 -#define TK_CAST 66 -#define TK_COLUMNKW 67 -#define TK_CONFLICT 68 -#define TK_DATABASE 69 -#define TK_DESC 70 -#define TK_DETACH 71 -#define TK_EACH 72 -#define TK_FAIL 73 -#define TK_FOR 74 -#define TK_IGNORE 75 -#define TK_INITIALLY 76 -#define TK_INSTEAD 77 -#define TK_NO 78 -#define TK_KEY 79 -#define TK_OF 80 -#define TK_OFFSET 81 -#define TK_PRAGMA 82 -#define TK_RAISE 83 -#define TK_RECURSIVE 84 -#define TK_REPLACE 85 -#define TK_RESTRICT 86 -#define TK_ROW 87 -#define TK_TRIGGER 88 -#define TK_VACUUM 89 -#define TK_VIEW 90 -#define TK_VIRTUAL 91 -#define TK_WITH 92 -#define TK_REINDEX 93 -#define TK_RENAME 94 -#define TK_CTIME_KW 95 -#define TK_ANY 96 +#define TK_ID 27 +#define TK_ABORT 28 +#define TK_ACTION 29 +#define TK_AFTER 30 +#define TK_ANALYZE 31 +#define TK_ASC 32 +#define TK_ATTACH 33 +#define TK_BEFORE 34 +#define TK_BY 35 +#define TK_CASCADE 36 +#define TK_CAST 37 +#define TK_COLUMNKW 38 +#define TK_CONFLICT 39 +#define TK_DATABASE 40 +#define TK_DESC 41 +#define TK_DETACH 42 +#define TK_EACH 43 +#define TK_FAIL 44 +#define TK_FOR 45 +#define TK_IGNORE 46 +#define TK_INITIALLY 47 +#define TK_INSTEAD 48 +#define TK_LIKE_KW 49 +#define TK_MATCH 50 +#define TK_NO 51 +#define TK_KEY 52 +#define TK_OF 53 +#define TK_OFFSET 54 +#define TK_PRAGMA 55 +#define TK_RAISE 56 +#define TK_RECURSIVE 57 +#define TK_REPLACE 58 +#define TK_RESTRICT 59 +#define TK_ROW 60 +#define TK_TRIGGER 61 +#define TK_VACUUM 62 +#define TK_VIEW 63 +#define TK_VIRTUAL 64 +#define TK_WITH 65 +#define TK_REINDEX 66 +#define TK_RENAME 67 +#define TK_CTIME_KW 68 +#define TK_ANY 69 +#define TK_OR 70 +#define TK_AND 71 +#define TK_IS 72 +#define TK_BETWEEN 73 +#define TK_IN 74 +#define TK_ISNULL 75 +#define TK_NOTNULL 76 +#define TK_NE 77 +#define TK_EQ 78 +#define TK_GT 79 +#define TK_LE 80 +#define TK_LT 81 +#define TK_GE 82 +#define TK_ESCAPE 83 +#define TK_BITAND 84 +#define TK_BITOR 85 +#define TK_LSHIFT 86 +#define TK_RSHIFT 87 +#define TK_PLUS 88 +#define TK_MINUS 89 +#define TK_STAR 90 +#define TK_SLASH 91 +#define TK_REM 92 +#define TK_CONCAT 93 +#define TK_COLLATE 94 +#define TK_BITNOT 95 +#define TK_INDEXED 96 #define TK_STRING 97 #define TK_JOIN_KW 98 #define TK_CONSTRAINT 99 @@ -11586,10 +12547,11 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); #define TK_REGISTER 157 #define TK_VECTOR 158 #define TK_SELECT_COLUMN 159 -#define TK_ASTERISK 160 -#define TK_SPAN 161 -#define TK_SPACE 162 -#define TK_ILLEGAL 163 +#define TK_IF_NULL_ROW 160 +#define TK_ASTERISK 161 +#define TK_SPAN 162 +#define TK_SPACE 163 +#define TK_ILLEGAL 164 /* The token codes above must all fit in 8 bits */ #define TKFLG_MASK 0xff @@ -11674,7 +12636,6 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); */ #ifndef SQLITE_TEMP_STORE # define SQLITE_TEMP_STORE 1 -# define SQLITE_TEMP_STORE_xc 1 /* Exclude from ctime.c */ #endif /* @@ -11975,7 +12936,6 @@ typedef INT16_TYPE LogEst; # else # define SQLITE_MAX_MMAP_SIZE 0 # endif -# define SQLITE_MAX_MMAP_SIZE_xc 1 /* exclude from ctime.c */ #endif /* @@ -11985,7 +12945,6 @@ typedef INT16_TYPE LogEst; */ #ifndef SQLITE_DEFAULT_MMAP_SIZE # define SQLITE_DEFAULT_MMAP_SIZE 0 -# define SQLITE_DEFAULT_MMAP_SIZE_xc 1 /* Exclude from ctime.c */ #endif #if SQLITE_DEFAULT_MMAP_SIZE>SQLITE_MAX_MMAP_SIZE # undef SQLITE_DEFAULT_MMAP_SIZE @@ -12460,7 +13419,7 @@ struct BtreePayload { const void *pKey; /* Key content for indexes. NULL for tables */ sqlite3_int64 nKey; /* Size of pKey for indexes. PRIMARY KEY for tabs */ const void *pData; /* Data for tables. NULL for indexes */ - struct Mem *aMem; /* First of nMem value in the unpacked pKey */ + sqlite3_value *aMem; /* First of nMem value in the unpacked pKey */ u16 nMem; /* Number of aMem[] value. Might be zero */ int nData; /* Size of pData. 0 if none. */ int nZero; /* Extra zero data appended after pData,nData */ @@ -12470,9 +13429,9 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(BtCursor*, const BtreePayload *pPayload, int flags, int seekResult); SQLITE_PRIVATE int sqlite3BtreeFirst(BtCursor*, int *pRes); SQLITE_PRIVATE int sqlite3BtreeLast(BtCursor*, int *pRes); -SQLITE_PRIVATE int sqlite3BtreeNext(BtCursor*, int *pRes); +SQLITE_PRIVATE int sqlite3BtreeNext(BtCursor*, int flags); SQLITE_PRIVATE int sqlite3BtreeEof(BtCursor*); -SQLITE_PRIVATE int sqlite3BtreePrevious(BtCursor*, int *pRes); +SQLITE_PRIVATE int sqlite3BtreePrevious(BtCursor*, int flags); SQLITE_PRIVATE i64 sqlite3BtreeIntegerKey(BtCursor*); SQLITE_PRIVATE int sqlite3BtreePayload(BtCursor*, u32 offset, u32 amt, void*); SQLITE_PRIVATE const void *sqlite3BtreePayloadFetch(BtCursor*, u32 *pAmt); @@ -12590,7 +13549,7 @@ typedef struct Vdbe Vdbe; ** The names of the following types declared in vdbeInt.h are required ** for the VdbeOp definition. */ -typedef struct Mem Mem; +typedef struct sqlite3_value Mem; typedef struct SubProgram SubProgram; /* @@ -12623,7 +13582,7 @@ struct VdbeOp { #ifdef SQLITE_ENABLE_CURSOR_HINTS Expr *pExpr; /* Used when p4type is P4_EXPR */ #endif - int (*xAdvance)(BtCursor *, int *); + int (*xAdvance)(BtCursor *, int); } p4; #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS char *zComment; /* Comment to improve readability */ @@ -12667,24 +13626,26 @@ typedef struct VdbeOpList VdbeOpList; /* ** Allowed values of VdbeOp.p4type */ -#define P4_NOTUSED 0 /* The P4 parameter is not used */ -#define P4_DYNAMIC (-1) /* Pointer to a string obtained from sqliteMalloc() */ -#define P4_STATIC (-2) /* Pointer to a static string */ -#define P4_COLLSEQ (-3) /* P4 is a pointer to a CollSeq structure */ -#define P4_FUNCDEF (-4) /* P4 is a pointer to a FuncDef structure */ -#define P4_KEYINFO (-5) /* P4 is a pointer to a KeyInfo structure */ -#define P4_EXPR (-6) /* P4 is a pointer to an Expr tree */ -#define P4_MEM (-7) /* P4 is a pointer to a Mem* structure */ -#define P4_TRANSIENT 0 /* P4 is a pointer to a transient string */ -#define P4_VTAB (-8) /* P4 is a pointer to an sqlite3_vtab structure */ -#define P4_REAL (-9) /* P4 is a 64-bit floating point value */ -#define P4_INT64 (-10) /* P4 is a 64-bit signed integer */ -#define P4_INT32 (-11) /* P4 is a 32-bit signed integer */ -#define P4_INTARRAY (-12) /* P4 is a vector of 32-bit integers */ -#define P4_SUBPROGRAM (-13) /* P4 is a pointer to a SubProgram structure */ -#define P4_ADVANCE (-14) /* P4 is a pointer to BtreeNext() or BtreePrev() */ -#define P4_TABLE (-15) /* P4 is a pointer to a Table structure */ -#define P4_FUNCCTX (-16) /* P4 is a pointer to an sqlite3_context object */ +#define P4_NOTUSED 0 /* The P4 parameter is not used */ +#define P4_TRANSIENT 0 /* P4 is a pointer to a transient string */ +#define P4_STATIC (-1) /* Pointer to a static string */ +#define P4_COLLSEQ (-2) /* P4 is a pointer to a CollSeq structure */ +#define P4_INT32 (-3) /* P4 is a 32-bit signed integer */ +#define P4_SUBPROGRAM (-4) /* P4 is a pointer to a SubProgram structure */ +#define P4_ADVANCE (-5) /* P4 is a pointer to BtreeNext() or BtreePrev() */ +#define P4_TABLE (-6) /* P4 is a pointer to a Table structure */ +/* Above do not own any resources. Must free those below */ +#define P4_FREE_IF_LE (-7) +#define P4_DYNAMIC (-7) /* Pointer to memory from sqliteMalloc() */ +#define P4_FUNCDEF (-8) /* P4 is a pointer to a FuncDef structure */ +#define P4_KEYINFO (-9) /* P4 is a pointer to a KeyInfo structure */ +#define P4_EXPR (-10) /* P4 is a pointer to an Expr tree */ +#define P4_MEM (-11) /* P4 is a pointer to a Mem* structure */ +#define P4_VTAB (-12) /* P4 is a pointer to an sqlite3_vtab structure */ +#define P4_REAL (-13) /* P4 is a 64-bit floating point value */ +#define P4_INT64 (-14) /* P4 is a 64-bit signed integer */ +#define P4_INTARRAY (-15) /* P4 is a vector of 32-bit integers */ +#define P4_FUNCCTX (-16) /* P4 is a pointer to an sqlite3_context object */ /* Error message codes for OP_Halt */ #define P5_ConstraintNotNull 1 @@ -12750,90 +13711,90 @@ typedef struct VdbeOpList VdbeOpList; #define OP_Once 20 #define OP_If 21 #define OP_IfNot 22 -#define OP_SeekLT 23 /* synopsis: key=r[P3@P4] */ -#define OP_SeekLE 24 /* synopsis: key=r[P3@P4] */ -#define OP_SeekGE 25 /* synopsis: key=r[P3@P4] */ -#define OP_SeekGT 26 /* synopsis: key=r[P3@P4] */ -#define OP_Or 27 /* same as TK_OR, synopsis: r[P3]=(r[P1] || r[P2]) */ -#define OP_And 28 /* same as TK_AND, synopsis: r[P3]=(r[P1] && r[P2]) */ -#define OP_NoConflict 29 /* synopsis: key=r[P3@P4] */ -#define OP_NotFound 30 /* synopsis: key=r[P3@P4] */ -#define OP_Found 31 /* synopsis: key=r[P3@P4] */ -#define OP_SeekRowid 32 /* synopsis: intkey=r[P3] */ -#define OP_NotExists 33 /* synopsis: intkey=r[P3] */ -#define OP_IsNull 34 /* same as TK_ISNULL, synopsis: if r[P1]==NULL goto P2 */ -#define OP_NotNull 35 /* same as TK_NOTNULL, synopsis: if r[P1]!=NULL goto P2 */ -#define OP_Ne 36 /* same as TK_NE, synopsis: IF r[P3]!=r[P1] */ -#define OP_Eq 37 /* same as TK_EQ, synopsis: IF r[P3]==r[P1] */ -#define OP_Gt 38 /* same as TK_GT, synopsis: IF r[P3]>r[P1] */ -#define OP_Le 39 /* same as TK_LE, synopsis: IF r[P3]<=r[P1] */ -#define OP_Lt 40 /* same as TK_LT, synopsis: IF r[P3]=r[P1] */ -#define OP_ElseNotEq 42 /* same as TK_ESCAPE */ -#define OP_BitAnd 43 /* same as TK_BITAND, synopsis: r[P3]=r[P1]&r[P2] */ -#define OP_BitOr 44 /* same as TK_BITOR, synopsis: r[P3]=r[P1]|r[P2] */ -#define OP_ShiftLeft 45 /* same as TK_LSHIFT, synopsis: r[P3]=r[P2]<>r[P1] */ -#define OP_Add 47 /* same as TK_PLUS, synopsis: r[P3]=r[P1]+r[P2] */ -#define OP_Subtract 48 /* same as TK_MINUS, synopsis: r[P3]=r[P2]-r[P1] */ -#define OP_Multiply 49 /* same as TK_STAR, synopsis: r[P3]=r[P1]*r[P2] */ -#define OP_Divide 50 /* same as TK_SLASH, synopsis: r[P3]=r[P2]/r[P1] */ -#define OP_Remainder 51 /* same as TK_REM, synopsis: r[P3]=r[P2]%r[P1] */ -#define OP_Concat 52 /* same as TK_CONCAT, synopsis: r[P3]=r[P2]+r[P1] */ -#define OP_Last 53 -#define OP_BitNot 54 /* same as TK_BITNOT, synopsis: r[P1]= ~r[P1] */ -#define OP_IfSmaller 55 -#define OP_SorterSort 56 -#define OP_Sort 57 -#define OP_Rewind 58 -#define OP_IdxLE 59 /* synopsis: key=r[P3@P4] */ -#define OP_IdxGT 60 /* synopsis: key=r[P3@P4] */ -#define OP_IdxLT 61 /* synopsis: key=r[P3@P4] */ -#define OP_IdxGE 62 /* synopsis: key=r[P3@P4] */ -#define OP_RowSetRead 63 /* synopsis: r[P3]=rowset(P1) */ -#define OP_RowSetTest 64 /* synopsis: if r[P3] in rowset(P1) goto P2 */ -#define OP_Program 65 -#define OP_FkIfZero 66 /* synopsis: if fkctr[P1]==0 goto P2 */ -#define OP_IfPos 67 /* synopsis: if r[P1]>0 then r[P1]-=P3, goto P2 */ -#define OP_IfNotZero 68 /* synopsis: if r[P1]!=0 then r[P1]--, goto P2 */ -#define OP_DecrJumpZero 69 /* synopsis: if (--r[P1])==0 goto P2 */ -#define OP_IncrVacuum 70 -#define OP_VNext 71 -#define OP_Init 72 /* synopsis: Start at P2 */ -#define OP_Return 73 -#define OP_EndCoroutine 74 -#define OP_HaltIfNull 75 /* synopsis: if r[P3]=null halt */ -#define OP_Halt 76 -#define OP_Integer 77 /* synopsis: r[P2]=P1 */ -#define OP_Int64 78 /* synopsis: r[P2]=P4 */ -#define OP_String 79 /* synopsis: r[P2]='P4' (len=P1) */ -#define OP_Null 80 /* synopsis: r[P2..P3]=NULL */ -#define OP_SoftNull 81 /* synopsis: r[P1]=NULL */ -#define OP_Blob 82 /* synopsis: r[P2]=P4 (len=P1) */ -#define OP_Variable 83 /* synopsis: r[P2]=parameter(P1,P4) */ -#define OP_Move 84 /* synopsis: r[P2@P3]=r[P1@P3] */ -#define OP_Copy 85 /* synopsis: r[P2@P3+1]=r[P1@P3+1] */ -#define OP_SCopy 86 /* synopsis: r[P2]=r[P1] */ -#define OP_IntCopy 87 /* synopsis: r[P2]=r[P1] */ -#define OP_ResultRow 88 /* synopsis: output=r[P1@P2] */ -#define OP_CollSeq 89 -#define OP_Function0 90 /* synopsis: r[P3]=func(r[P2@P5]) */ -#define OP_Function 91 /* synopsis: r[P3]=func(r[P2@P5]) */ -#define OP_AddImm 92 /* synopsis: r[P1]=r[P1]+P2 */ -#define OP_RealAffinity 93 -#define OP_Cast 94 /* synopsis: affinity(r[P1]) */ -#define OP_Permutation 95 -#define OP_Compare 96 /* synopsis: r[P1@P3] <-> r[P2@P3] */ +#define OP_IfNullRow 23 /* synopsis: if P1.nullRow then r[P3]=NULL, goto P2 */ +#define OP_SeekLT 24 /* synopsis: key=r[P3@P4] */ +#define OP_SeekLE 25 /* synopsis: key=r[P3@P4] */ +#define OP_SeekGE 26 /* synopsis: key=r[P3@P4] */ +#define OP_SeekGT 27 /* synopsis: key=r[P3@P4] */ +#define OP_NoConflict 28 /* synopsis: key=r[P3@P4] */ +#define OP_NotFound 29 /* synopsis: key=r[P3@P4] */ +#define OP_Found 30 /* synopsis: key=r[P3@P4] */ +#define OP_SeekRowid 31 /* synopsis: intkey=r[P3] */ +#define OP_NotExists 32 /* synopsis: intkey=r[P3] */ +#define OP_Last 33 +#define OP_IfSmaller 34 +#define OP_SorterSort 35 +#define OP_Sort 36 +#define OP_Rewind 37 +#define OP_IdxLE 38 /* synopsis: key=r[P3@P4] */ +#define OP_IdxGT 39 /* synopsis: key=r[P3@P4] */ +#define OP_IdxLT 40 /* synopsis: key=r[P3@P4] */ +#define OP_IdxGE 41 /* synopsis: key=r[P3@P4] */ +#define OP_RowSetRead 42 /* synopsis: r[P3]=rowset(P1) */ +#define OP_RowSetTest 43 /* synopsis: if r[P3] in rowset(P1) goto P2 */ +#define OP_Program 44 +#define OP_FkIfZero 45 /* synopsis: if fkctr[P1]==0 goto P2 */ +#define OP_IfPos 46 /* synopsis: if r[P1]>0 then r[P1]-=P3, goto P2 */ +#define OP_IfNotZero 47 /* synopsis: if r[P1]!=0 then r[P1]--, goto P2 */ +#define OP_DecrJumpZero 48 /* synopsis: if (--r[P1])==0 goto P2 */ +#define OP_IncrVacuum 49 +#define OP_VNext 50 +#define OP_Init 51 /* synopsis: Start at P2 */ +#define OP_Return 52 +#define OP_EndCoroutine 53 +#define OP_HaltIfNull 54 /* synopsis: if r[P3]=null halt */ +#define OP_Halt 55 +#define OP_Integer 56 /* synopsis: r[P2]=P1 */ +#define OP_Int64 57 /* synopsis: r[P2]=P4 */ +#define OP_String 58 /* synopsis: r[P2]='P4' (len=P1) */ +#define OP_Null 59 /* synopsis: r[P2..P3]=NULL */ +#define OP_SoftNull 60 /* synopsis: r[P1]=NULL */ +#define OP_Blob 61 /* synopsis: r[P2]=P4 (len=P1) */ +#define OP_Variable 62 /* synopsis: r[P2]=parameter(P1,P4) */ +#define OP_Move 63 /* synopsis: r[P2@P3]=r[P1@P3] */ +#define OP_Copy 64 /* synopsis: r[P2@P3+1]=r[P1@P3+1] */ +#define OP_SCopy 65 /* synopsis: r[P2]=r[P1] */ +#define OP_IntCopy 66 /* synopsis: r[P2]=r[P1] */ +#define OP_ResultRow 67 /* synopsis: output=r[P1@P2] */ +#define OP_CollSeq 68 +#define OP_AddImm 69 /* synopsis: r[P1]=r[P1]+P2 */ +#define OP_Or 70 /* same as TK_OR, synopsis: r[P3]=(r[P1] || r[P2]) */ +#define OP_And 71 /* same as TK_AND, synopsis: r[P3]=(r[P1] && r[P2]) */ +#define OP_RealAffinity 72 +#define OP_Cast 73 /* synopsis: affinity(r[P1]) */ +#define OP_Permutation 74 +#define OP_IsNull 75 /* same as TK_ISNULL, synopsis: if r[P1]==NULL goto P2 */ +#define OP_NotNull 76 /* same as TK_NOTNULL, synopsis: if r[P1]!=NULL goto P2 */ +#define OP_Ne 77 /* same as TK_NE, synopsis: IF r[P3]!=r[P1] */ +#define OP_Eq 78 /* same as TK_EQ, synopsis: IF r[P3]==r[P1] */ +#define OP_Gt 79 /* same as TK_GT, synopsis: IF r[P3]>r[P1] */ +#define OP_Le 80 /* same as TK_LE, synopsis: IF r[P3]<=r[P1] */ +#define OP_Lt 81 /* same as TK_LT, synopsis: IF r[P3]=r[P1] */ +#define OP_ElseNotEq 83 /* same as TK_ESCAPE */ +#define OP_BitAnd 84 /* same as TK_BITAND, synopsis: r[P3]=r[P1]&r[P2] */ +#define OP_BitOr 85 /* same as TK_BITOR, synopsis: r[P3]=r[P1]|r[P2] */ +#define OP_ShiftLeft 86 /* same as TK_LSHIFT, synopsis: r[P3]=r[P2]<>r[P1] */ +#define OP_Add 88 /* same as TK_PLUS, synopsis: r[P3]=r[P1]+r[P2] */ +#define OP_Subtract 89 /* same as TK_MINUS, synopsis: r[P3]=r[P2]-r[P1] */ +#define OP_Multiply 90 /* same as TK_STAR, synopsis: r[P3]=r[P1]*r[P2] */ +#define OP_Divide 91 /* same as TK_SLASH, synopsis: r[P3]=r[P2]/r[P1] */ +#define OP_Remainder 92 /* same as TK_REM, synopsis: r[P3]=r[P2]%r[P1] */ +#define OP_Concat 93 /* same as TK_CONCAT, synopsis: r[P3]=r[P2]+r[P1] */ +#define OP_Compare 94 /* synopsis: r[P1@P3] <-> r[P2@P3] */ +#define OP_BitNot 95 /* same as TK_BITNOT, synopsis: r[P1]= ~r[P1] */ +#define OP_Column 96 /* synopsis: r[P3]=PX */ #define OP_String8 97 /* same as TK_STRING, synopsis: r[P2]='P4' */ -#define OP_Column 98 /* synopsis: r[P3]=PX */ -#define OP_Affinity 99 /* synopsis: affinity(r[P1@P2]) */ -#define OP_MakeRecord 100 /* synopsis: r[P3]=mkrec(r[P1@P2]) */ -#define OP_Count 101 /* synopsis: r[P2]=count() */ -#define OP_ReadCookie 102 -#define OP_SetCookie 103 -#define OP_ReopenIdx 104 /* synopsis: root=P2 iDb=P3 */ -#define OP_OpenRead 105 /* synopsis: root=P2 iDb=P3 */ -#define OP_OpenWrite 106 /* synopsis: root=P2 iDb=P3 */ +#define OP_Affinity 98 /* synopsis: affinity(r[P1@P2]) */ +#define OP_MakeRecord 99 /* synopsis: r[P3]=mkrec(r[P1@P2]) */ +#define OP_Count 100 /* synopsis: r[P2]=count() */ +#define OP_ReadCookie 101 +#define OP_SetCookie 102 +#define OP_ReopenIdx 103 /* synopsis: root=P2 iDb=P3 */ +#define OP_OpenRead 104 /* synopsis: root=P2 iDb=P3 */ +#define OP_OpenWrite 105 /* synopsis: root=P2 iDb=P3 */ +#define OP_OpenDup 106 #define OP_OpenAutoindex 107 /* synopsis: nColumn=P2 */ #define OP_OpenEphemeral 108 /* synopsis: nColumn=P2 */ #define OP_SorterOpen 109 @@ -12855,7 +13816,7 @@ typedef struct VdbeOpList VdbeOpList; #define OP_SorterInsert 125 /* synopsis: key=r[P2] */ #define OP_IdxInsert 126 /* synopsis: key=r[P2] */ #define OP_IdxDelete 127 /* synopsis: key=r[P2@P3] */ -#define OP_Seek 128 /* synopsis: Move P3 to P1.rowid */ +#define OP_DeferredSeek 128 /* synopsis: Move P3 to P1.rowid if needed */ #define OP_IdxRowid 129 /* synopsis: r[P2]=rowid */ #define OP_Destroy 130 #define OP_Clear 131 @@ -12888,9 +13849,13 @@ typedef struct VdbeOpList VdbeOpList; #define OP_VRename 158 #define OP_Pagecount 159 #define OP_MaxPgcnt 160 -#define OP_CursorHint 161 -#define OP_Noop 162 -#define OP_Explain 163 +#define OP_PureFunc0 161 +#define OP_Function0 162 /* synopsis: r[P3]=func(r[P2@P5]) */ +#define OP_PureFunc 163 +#define OP_Function 164 /* synopsis: r[P3]=func(r[P2@P5]) */ +#define OP_CursorHint 165 +#define OP_Noop 166 +#define OP_Explain 167 /* Properties such as "out2" or "jump" that are specified in ** comments following the "case" for each opcode in the vdbe.c @@ -12905,17 +13870,17 @@ typedef struct VdbeOpList VdbeOpList; #define OPFLG_INITIALIZER {\ /* 0 */ 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01,\ /* 8 */ 0x00, 0x10, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01,\ -/* 16 */ 0x03, 0x03, 0x01, 0x12, 0x01, 0x03, 0x03, 0x09,\ -/* 24 */ 0x09, 0x09, 0x09, 0x26, 0x26, 0x09, 0x09, 0x09,\ -/* 32 */ 0x09, 0x09, 0x03, 0x03, 0x0b, 0x0b, 0x0b, 0x0b,\ -/* 40 */ 0x0b, 0x0b, 0x01, 0x26, 0x26, 0x26, 0x26, 0x26,\ -/* 48 */ 0x26, 0x26, 0x26, 0x26, 0x26, 0x01, 0x12, 0x01,\ -/* 56 */ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x23,\ -/* 64 */ 0x0b, 0x01, 0x01, 0x03, 0x03, 0x03, 0x01, 0x01,\ -/* 72 */ 0x01, 0x02, 0x02, 0x08, 0x00, 0x10, 0x10, 0x10,\ -/* 80 */ 0x10, 0x00, 0x10, 0x10, 0x00, 0x00, 0x10, 0x10,\ -/* 88 */ 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x00,\ -/* 96 */ 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00,\ +/* 16 */ 0x03, 0x03, 0x01, 0x12, 0x01, 0x03, 0x03, 0x01,\ +/* 24 */ 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,\ +/* 32 */ 0x09, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,\ +/* 40 */ 0x01, 0x01, 0x23, 0x0b, 0x01, 0x01, 0x03, 0x03,\ +/* 48 */ 0x03, 0x01, 0x01, 0x01, 0x02, 0x02, 0x08, 0x00,\ +/* 56 */ 0x10, 0x10, 0x10, 0x10, 0x00, 0x10, 0x10, 0x00,\ +/* 64 */ 0x00, 0x10, 0x10, 0x00, 0x00, 0x02, 0x26, 0x26,\ +/* 72 */ 0x02, 0x02, 0x00, 0x03, 0x03, 0x0b, 0x0b, 0x0b,\ +/* 80 */ 0x0b, 0x0b, 0x0b, 0x01, 0x26, 0x26, 0x26, 0x26,\ +/* 88 */ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x00, 0x12,\ +/* 96 */ 0x00, 0x10, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00,\ /* 104 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ /* 112 */ 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00,\ /* 120 */ 0x00, 0x00, 0x00, 0x10, 0x00, 0x04, 0x04, 0x00,\ @@ -12923,7 +13888,8 @@ typedef struct VdbeOpList VdbeOpList; /* 136 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,\ /* 144 */ 0x10, 0x00, 0x04, 0x1a, 0x00, 0x00, 0x00, 0x00,\ /* 152 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,\ -/* 160 */ 0x10, 0x00, 0x00, 0x00,} +/* 160 */ 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ +} /* The sqlite3P2Values() routine is able to run faster if it knows ** the value of the largest JUMP opcode. The smaller the maximum @@ -12931,11 +13897,17 @@ typedef struct VdbeOpList VdbeOpList; ** generated this include file strives to group all JUMP opcodes ** together near the beginning of the list. */ -#define SQLITE_MX_JUMP_OPCODE 72 /* Maximum JUMP opcode */ +#define SQLITE_MX_JUMP_OPCODE 83 /* Maximum JUMP opcode */ /************** End of opcodes.h *********************************************/ /************** Continuing where we left off in vdbe.h ***********************/ +/* +** Additional non-public SQLITE_PREPARE_* flags +*/ +#define SQLITE_PREPARE_SAVESQL 0x80 /* Preserve SQL text */ +#define SQLITE_PREPARE_MASK 0x0f /* Mask of public flags */ + /* ** Prototypes for the VDBE interface. See comments on the implementation ** for a description of what each of these routines does. @@ -12993,7 +13965,8 @@ SQLITE_PRIVATE void sqlite3VdbeSetNumCols(Vdbe*,int); SQLITE_PRIVATE int sqlite3VdbeSetColName(Vdbe*, int, int, const char *, void(*)(void*)); SQLITE_PRIVATE void sqlite3VdbeCountChanges(Vdbe*); SQLITE_PRIVATE sqlite3 *sqlite3VdbeDb(Vdbe*); -SQLITE_PRIVATE void sqlite3VdbeSetSql(Vdbe*, const char *z, int n, int); +SQLITE_PRIVATE u8 sqlite3VdbePrepareFlags(Vdbe*); +SQLITE_PRIVATE void sqlite3VdbeSetSql(Vdbe*, const char *z, int n, u8); SQLITE_PRIVATE void sqlite3VdbeSwap(Vdbe*,Vdbe*); SQLITE_PRIVATE VdbeOp *sqlite3VdbeTakeOpArray(Vdbe*, int*, int*); SQLITE_PRIVATE sqlite3_value *sqlite3VdbeGetBoundValue(Vdbe*, int, u8); @@ -13015,6 +13988,8 @@ SQLITE_PRIVATE RecordCompare sqlite3VdbeFindCompare(UnpackedRecord*); SQLITE_PRIVATE void sqlite3VdbeLinkSubProgram(Vdbe *, SubProgram *); #endif +SQLITE_PRIVATE int sqlite3NotPureFunc(sqlite3_context*); + /* Use SQLITE_ENABLE_COMMENTS to enable generation of extra comments on ** each VDBE opcode. ** @@ -13359,6 +14334,7 @@ struct PgHdr { sqlite3_pcache_page *pPage; /* Pcache object page handle */ void *pData; /* Page data */ void *pExtra; /* Extra content */ + PCache *pCache; /* PRIVATE: Cache that owns this page */ PgHdr *pDirty; /* Transient list of dirty sorted by pgno */ Pager *pPager; /* The pager this page is part of */ Pgno pgno; /* Page number for this page */ @@ -13368,12 +14344,11 @@ struct PgHdr { u16 flags; /* PGHDR flags defined below */ /********************************************************************** - ** Elements above are public. All that follows is private to pcache.c - ** and should not be accessed by other modules. + ** Elements above, except pCache, are public. All that follow are + ** private to pcache.c and should not be accessed by other modules. + ** pCache is grouped with the public elements for efficiency. */ i16 nRef; /* Number of users of this page */ - PCache *pCache; /* Cache that owns this page */ - PgHdr *pDirtyNext; /* Next element in list of dirty pages */ PgHdr *pDirtyPrev; /* Previous element in list of dirty pages */ }; @@ -14215,8 +15190,8 @@ struct sqlite3 { ** SQLITE_CkptFullFSync == PAGER_CKPT_FULLFSYNC ** SQLITE_CacheSpill == PAGER_CACHE_SPILL */ -#define SQLITE_VdbeTrace 0x00000001 /* True to trace VDBE execution */ -#define SQLITE_InternChanges 0x00000002 /* Uncommitted Hash table changes */ +#define SQLITE_WriteSchema 0x00000001 /* OK to update SQLITE_MASTER */ +#define SQLITE_LegacyFileFmt 0x00000002 /* Create new databases in format 1 */ #define SQLITE_FullColNames 0x00000004 /* Show full column names on SELECT */ #define SQLITE_FullFSync 0x00000008 /* Use full fsync on the backend */ #define SQLITE_CkptFullFSync 0x00000010 /* Use full fsync for checkpoint */ @@ -14227,29 +15202,34 @@ struct sqlite3 { /* the count using a callback. */ #define SQLITE_NullCallback 0x00000100 /* Invoke the callback once if the */ /* result set is empty */ -#define SQLITE_SqlTrace 0x00000200 /* Debug print SQL as it executes */ -#define SQLITE_VdbeListing 0x00000400 /* Debug listings of VDBE programs */ -#define SQLITE_WriteSchema 0x00000800 /* OK to update SQLITE_MASTER */ -#define SQLITE_VdbeAddopTrace 0x00001000 /* Trace sqlite3VdbeAddOp() calls */ -#define SQLITE_IgnoreChecks 0x00002000 /* Do not enforce check constraints */ -#define SQLITE_ReadUncommitted 0x0004000 /* For shared-cache mode */ -#define SQLITE_LegacyFileFmt 0x00008000 /* Create new databases in format 1 */ -#define SQLITE_RecoveryMode 0x00010000 /* Ignore schema errors */ -#define SQLITE_ReverseOrder 0x00020000 /* Reverse unordered SELECTs */ -#define SQLITE_RecTriggers 0x00040000 /* Enable recursive triggers */ -#define SQLITE_ForeignKeys 0x00080000 /* Enforce foreign key constraints */ -#define SQLITE_AutoIndex 0x00100000 /* Enable automatic indexes */ -#define SQLITE_PreferBuiltin 0x00200000 /* Preference to built-in funcs */ -#define SQLITE_LoadExtension 0x00400000 /* Enable load_extension */ -#define SQLITE_LoadExtFunc 0x00800000 /* Enable load_extension() SQL func */ -#define SQLITE_EnableTrigger 0x01000000 /* True to enable triggers */ -#define SQLITE_DeferFKs 0x02000000 /* Defer all FK constraints */ -#define SQLITE_QueryOnly 0x04000000 /* Disable database changes */ -#define SQLITE_VdbeEQP 0x08000000 /* Debug EXPLAIN QUERY PLAN */ -#define SQLITE_Vacuum 0x10000000 /* Currently in a VACUUM */ -#define SQLITE_CellSizeCk 0x20000000 /* Check btree cell sizes on load */ -#define SQLITE_Fts3Tokenizer 0x40000000 /* Enable fts3_tokenizer(2) */ -#define SQLITE_NoCkptOnClose 0x80000000 /* No checkpoint on close()/DETACH */ +#define SQLITE_IgnoreChecks 0x00000200 /* Do not enforce check constraints */ +#define SQLITE_ReadUncommit 0x00000400 /* READ UNCOMMITTED in shared-cache */ +#define SQLITE_NoCkptOnClose 0x00000800 /* No checkpoint on close()/DETACH */ +#define SQLITE_ReverseOrder 0x00001000 /* Reverse unordered SELECTs */ +#define SQLITE_RecTriggers 0x00002000 /* Enable recursive triggers */ +#define SQLITE_ForeignKeys 0x00004000 /* Enforce foreign key constraints */ +#define SQLITE_AutoIndex 0x00008000 /* Enable automatic indexes */ +#define SQLITE_LoadExtension 0x00010000 /* Enable load_extension */ +#define SQLITE_EnableTrigger 0x00020000 /* True to enable triggers */ +#define SQLITE_DeferFKs 0x00040000 /* Defer all FK constraints */ +#define SQLITE_QueryOnly 0x00080000 /* Disable database changes */ +#define SQLITE_CellSizeCk 0x00100000 /* Check btree cell sizes on load */ +#define SQLITE_Fts3Tokenizer 0x00200000 /* Enable fts3_tokenizer(2) */ +#define SQLITE_EnableQPSG 0x00400000 /* Query Planner Stability Guarantee */ +/* The next four values are not used by PRAGMAs or by sqlite3_dbconfig() and +** could be factored out into a separate bit vector of the sqlite3 object. */ +#define SQLITE_InternChanges 0x00800000 /* Uncommitted Hash table changes */ +#define SQLITE_LoadExtFunc 0x01000000 /* Enable load_extension() SQL func */ +#define SQLITE_PreferBuiltin 0x02000000 /* Preference to built-in funcs */ +#define SQLITE_Vacuum 0x04000000 /* Currently in a VACUUM */ +/* Flags used only if debugging */ +#ifdef SQLITE_DEBUG +#define SQLITE_SqlTrace 0x08000000 /* Debug print SQL as it executes */ +#define SQLITE_VdbeListing 0x10000000 /* Debug listings of VDBE programs */ +#define SQLITE_VdbeTrace 0x20000000 /* True to trace VDBE execution */ +#define SQLITE_VdbeAddopTrace 0x40000000 /* Trace sqlite3VdbeAddOp() calls */ +#define SQLITE_VdbeEQP 0x80000000 /* Debug EXPLAIN QUERY PLAN */ +#endif /* @@ -14269,6 +15249,7 @@ struct sqlite3 { #define SQLITE_Transitive 0x0200 /* Transitive constraints */ #define SQLITE_OmitNoopJoin 0x0400 /* Omit unused tables in joins */ #define SQLITE_Stat34 0x0800 /* Use STAT3 or STAT4 data */ +#define SQLITE_CountOfView 0x1000 /* The count-of-view optimization */ #define SQLITE_CursorHints 0x2000 /* Add OP_CursorHint opcodes */ #define SQLITE_AllOpts 0xffff /* All optimizations */ @@ -14387,7 +15368,14 @@ struct FuncDestructor { ** Like FUNCTION except it omits the SQLITE_FUNC_CONSTANT flag and ** adds the SQLITE_FUNC_SLOCHNG flag. Used for date & time functions ** and functions like sqlite_version() that can change, but not during -** a single query. +** a single query. The iArg is ignored. The user-data is always set +** to a NULL pointer. The bNC parameter is not used. +** +** PURE_DATE(zName, nArg, iArg, bNC, xFunc) +** Used for "pure" date/time functions, this macro is like DFUNCTION +** except that it does set the SQLITE_FUNC_CONSTANT flags. iArg is +** ignored and the user-data for these functions is set to an +** arbitrary non-NULL pointer. The bNC parameter is not used. ** ** AGGREGATE(zName, nArg, iArg, bNC, xStep, xFinal) ** Used to create an aggregate function definition implemented by @@ -14410,8 +15398,11 @@ struct FuncDestructor { {nArg, SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0} } #define DFUNCTION(zName, nArg, iArg, bNC, xFunc) \ - {nArg, SQLITE_FUNC_SLOCHNG|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ - SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0} } + {nArg, SQLITE_FUNC_SLOCHNG|SQLITE_UTF8, \ + 0, 0, xFunc, 0, #zName, {0} } +#define PURE_DATE(zName, nArg, iArg, bNC, xFunc) \ + {nArg, SQLITE_FUNC_SLOCHNG|SQLITE_UTF8|SQLITE_FUNC_CONSTANT, \ + (void*)&sqlite3Config, 0, xFunc, 0, #zName, {0} } #define FUNCTION2(zName, nArg, iArg, bNC, xFunc, extraFlags) \ {nArg,SQLITE_FUNC_CONSTANT|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL)|extraFlags,\ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0} } @@ -15131,8 +16122,8 @@ struct Expr { */ #define EP_FromJoin 0x000001 /* Originates in ON/USING clause of outer join */ #define EP_Agg 0x000002 /* Contains one or more aggregate functions */ -#define EP_Resolved 0x000004 /* IDs have been resolved to COLUMNs */ -#define EP_Error 0x000008 /* Expression contains one or more errors */ + /* 0x000004 // available for use */ + /* 0x000008 // available for use */ #define EP_Distinct 0x000010 /* Aggregate function with DISTINCT keyword */ #define EP_VarSelect 0x000020 /* pSelect is correlated, not constant */ #define EP_DblQuoted 0x000040 /* token.z was originally in "..." */ @@ -15211,6 +16202,7 @@ struct Expr { */ struct ExprList { int nExpr; /* Number of expressions on the list */ + int nAlloc; /* Number of a[] slots allocated */ struct ExprList_item { /* For each expression in the list */ Expr *pExpr; /* The parse tree for this expression */ char *zName; /* Token associated with this expression */ @@ -15226,7 +16218,7 @@ struct ExprList { } x; int iConstExprReg; /* Register in which Expr value is cached */ } u; - } *a; /* Alloc a power of two greater or equal to nExpr */ + } a[1]; /* One slot for each expression in the list */ }; /* @@ -15598,10 +16590,10 @@ struct Select { */ struct SelectDest { u8 eDest; /* How to dispose of the results. On of SRT_* above. */ - char *zAffSdst; /* Affinity used when eDest==SRT_Set */ int iSDParm; /* A parameter used by the eDest disposal method */ int iSdst; /* Base register where results are written */ int nSdst; /* Number of registers allocated */ + char *zAffSdst; /* Affinity used when eDest==SRT_Set */ ExprList *pOrderBy; /* Key columns for SRT_Queue and SRT_DistQueue */ }; @@ -15711,8 +16703,8 @@ struct Parse { int nMem; /* Number of memory cells used so far */ int nOpAlloc; /* Number of slots allocated for Vdbe.aOp[] */ int szOpAlloc; /* Bytes of memory space allocated for Vdbe.aOp[] */ - int ckBase; /* Base register of data during check constraints */ - int iSelfTab; /* Table of an index whose exprs are being coded */ + int iSelfTab; /* Table for associated with an index on expr, or negative + ** of the base register during check-constraint eval */ int iCacheLevel; /* ColCache valid when aColCache[].iLevel<=iCacheLevel */ int iCacheCnt; /* Counter used to generate aColCache[].lru values */ int nLabel; /* Number of labels used */ @@ -16083,14 +17075,17 @@ struct Walker { int walkerDepth; /* Number of subqueries */ u8 eCode; /* A small processing code */ union { /* Extra data for callback */ - NameContext *pNC; /* Naming context */ - int n; /* A counter */ - int iCur; /* A cursor number */ - SrcList *pSrcList; /* FROM clause */ - struct SrcCount *pSrcCount; /* Counting column references */ - struct CCurHint *pCCurHint; /* Used by codeCursorHint() */ - int *aiCol; /* array of column indexes */ - struct IdxCover *pIdxCover; /* Check for index coverage */ + NameContext *pNC; /* Naming context */ + int n; /* A counter */ + int iCur; /* A cursor number */ + SrcList *pSrcList; /* FROM clause */ + struct SrcCount *pSrcCount; /* Counting column references */ + struct CCurHint *pCCurHint; /* Used by codeCursorHint() */ + int *aiCol; /* array of column indexes */ + struct IdxCover *pIdxCover; /* Check for index coverage */ + struct IdxExprTrans *pIdxTrans; /* Convert indexed expr to column */ + ExprList *pGroupBy; /* GROUP BY clause */ + struct HavingToWhereCtx *pHavingCtx; /* HAVING to WHERE clause ctx */ } u; }; @@ -16101,6 +17096,10 @@ SQLITE_PRIVATE int sqlite3WalkSelect(Walker*, Select*); SQLITE_PRIVATE int sqlite3WalkSelectExpr(Walker*, Select*); SQLITE_PRIVATE int sqlite3WalkSelectFrom(Walker*, Select*); SQLITE_PRIVATE int sqlite3ExprWalkNoop(Walker*, Expr*); +SQLITE_PRIVATE int sqlite3SelectWalkNoop(Walker*, Select*); +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE void sqlite3SelectWalkAssert2(Walker*, Select*); +#endif /* ** Return code from the parse-tree walking primitives and their @@ -16162,11 +17161,14 @@ SQLITE_PRIVATE int sqlite3CantopenError(int); #ifdef SQLITE_DEBUG SQLITE_PRIVATE int sqlite3NomemError(int); SQLITE_PRIVATE int sqlite3IoerrnomemError(int); +SQLITE_PRIVATE int sqlite3CorruptPgnoError(int,Pgno); # define SQLITE_NOMEM_BKPT sqlite3NomemError(__LINE__) # define SQLITE_IOERR_NOMEM_BKPT sqlite3IoerrnomemError(__LINE__) +# define SQLITE_CORRUPT_PGNO(P) sqlite3CorruptPgnoError(__LINE__,(P)) #else # define SQLITE_NOMEM_BKPT SQLITE_NOMEM # define SQLITE_IOERR_NOMEM_BKPT SQLITE_IOERR_NOMEM +# define SQLITE_CORRUPT_PGNO(P) sqlite3CorruptError(__LINE__) #endif /* @@ -16244,6 +17246,7 @@ SQLITE_PRIVATE void *sqlite3Realloc(void*, u64); SQLITE_PRIVATE void *sqlite3DbReallocOrFree(sqlite3 *, void *, u64); SQLITE_PRIVATE void *sqlite3DbRealloc(sqlite3 *, void *, u64); SQLITE_PRIVATE void sqlite3DbFree(sqlite3*, void*); +SQLITE_PRIVATE void sqlite3DbFreeNN(sqlite3*, void*); SQLITE_PRIVATE int sqlite3MallocSize(void*); SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3*, void*); SQLITE_PRIVATE void *sqlite3ScratchMalloc(int); @@ -16534,10 +17537,10 @@ SQLITE_PRIVATE void sqlite3UnlinkAndDeleteIndex(sqlite3*,int,const char*); SQLITE_PRIVATE void sqlite3Vacuum(Parse*,Token*); SQLITE_PRIVATE int sqlite3RunVacuum(char**, sqlite3*, int); SQLITE_PRIVATE char *sqlite3NameFromToken(sqlite3*, Token*); -SQLITE_PRIVATE int sqlite3ExprCompare(Expr*, Expr*, int); +SQLITE_PRIVATE int sqlite3ExprCompare(Parse*,Expr*, Expr*, int); SQLITE_PRIVATE int sqlite3ExprCompareSkip(Expr*, Expr*, int); SQLITE_PRIVATE int sqlite3ExprListCompare(ExprList*, ExprList*, int); -SQLITE_PRIVATE int sqlite3ExprImpliesExpr(Expr*, Expr*, int); +SQLITE_PRIVATE int sqlite3ExprImpliesExpr(Parse*,Expr*, Expr*, int); SQLITE_PRIVATE void sqlite3ExprAnalyzeAggregates(NameContext*, Expr*); SQLITE_PRIVATE void sqlite3ExprAnalyzeAggList(NameContext*,ExprList*); SQLITE_PRIVATE int sqlite3ExprCoveredByIndex(Expr*, int iCur, Index *pIdx); @@ -16551,14 +17554,14 @@ SQLITE_PRIVATE void sqlite3RollbackAll(sqlite3*,int); SQLITE_PRIVATE void sqlite3CodeVerifySchema(Parse*, int); SQLITE_PRIVATE void sqlite3CodeVerifyNamedSchema(Parse*, const char *zDb); SQLITE_PRIVATE void sqlite3BeginTransaction(Parse*, int); -SQLITE_PRIVATE void sqlite3CommitTransaction(Parse*); -SQLITE_PRIVATE void sqlite3RollbackTransaction(Parse*); +SQLITE_PRIVATE void sqlite3EndTransaction(Parse*,int); SQLITE_PRIVATE void sqlite3Savepoint(Parse*, int, Token*); SQLITE_PRIVATE void sqlite3CloseSavepoints(sqlite3 *); SQLITE_PRIVATE void sqlite3LeaveMutexAndCloseZombie(sqlite3*); SQLITE_PRIVATE int sqlite3ExprIsConstant(Expr*); SQLITE_PRIVATE int sqlite3ExprIsConstantNotJoin(Expr*); SQLITE_PRIVATE int sqlite3ExprIsConstantOrFunction(Expr*, u8); +SQLITE_PRIVATE int sqlite3ExprIsConstantOrGroupBy(Parse*, Expr*, ExprList*); SQLITE_PRIVATE int sqlite3ExprIsTableConstant(Expr*,int); #ifdef SQLITE_ENABLE_CURSOR_HINTS SQLITE_PRIVATE int sqlite3ExprContainsSubquery(Expr*); @@ -16672,7 +17675,9 @@ SQLITE_PRIVATE int sqlite3FixTriggerStep(DbFixer*, TriggerStep*); SQLITE_PRIVATE int sqlite3AtoF(const char *z, double*, int, u8); SQLITE_PRIVATE int sqlite3GetInt32(const char *, int*); SQLITE_PRIVATE int sqlite3Atoi(const char*); +#ifndef SQLITE_OMIT_UTF16 SQLITE_PRIVATE int sqlite3Utf16ByteLen(const void *pData, int nChar); +#endif SQLITE_PRIVATE int sqlite3Utf8CharLen(const char *pData, int nByte); SQLITE_PRIVATE u32 sqlite3Utf8Read(const u8**); SQLITE_PRIVATE LogEst sqlite3LogEst(u64); @@ -16761,7 +17766,9 @@ SQLITE_PRIVATE void sqlite3ValueSetStr(sqlite3_value*, int, const void *,u8, SQLITE_PRIVATE void sqlite3ValueSetNull(sqlite3_value*); SQLITE_PRIVATE void sqlite3ValueFree(sqlite3_value*); SQLITE_PRIVATE sqlite3_value *sqlite3ValueNew(sqlite3 *); +#ifndef SQLITE_OMIT_UTF16 SQLITE_PRIVATE char *sqlite3Utf16to8(sqlite3 *, const void*, int, u8); +#endif SQLITE_PRIVATE int sqlite3ValueFromExpr(sqlite3 *, Expr *, u8, u8, sqlite3_value **); SQLITE_PRIVATE void sqlite3ValueApplyAffinity(sqlite3_value *, u8, u8); #ifndef SQLITE_AMALGAMATION @@ -17125,6 +18132,10 @@ SQLITE_PRIVATE Expr *sqlite3VectorFieldSubexpr(Expr*, int); SQLITE_PRIVATE Expr *sqlite3ExprForVectorField(Parse*,Expr*,int); SQLITE_PRIVATE void sqlite3VectorErrorMsg(Parse*, Expr*); +#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS +SQLITE_PRIVATE const char **sqlite3CompileOptions(int *pnOpt); +#endif + #endif /* SQLITEINT_H */ /************** End of sqliteInt.h *******************************************/ @@ -17268,9 +18279,16 @@ SQLITE_PRIVATE const unsigned char sqlite3CtypeMap[256] = { ** EVIDENCE-OF: R-43642-56306 By default, URI handling is globally ** disabled. The default value may be changed by compiling with the ** SQLITE_USE_URI symbol defined. +** +** URI filenames are enabled by default if SQLITE_HAS_CODEC is +** enabled. */ #ifndef SQLITE_USE_URI -# define SQLITE_USE_URI 0 +# ifdef SQLITE_HAS_CODEC +# define SQLITE_USE_URI 1 +# else +# define SQLITE_USE_URI 0 +# endif #endif /* EVIDENCE-OF: R-38720-18127 The default setting is determined by the @@ -17422,472 +18440,6 @@ SQLITE_PRIVATE const unsigned char sqlite3OpcodeProperty[] = OPFLG_INITIALIZER; SQLITE_PRIVATE const char sqlite3StrBINARY[] = "BINARY"; /************** End of global.c **********************************************/ -/************** Begin file ctime.c *******************************************/ -/* -** 2010 February 23 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -************************************************************************* -** -** This file implements routines used to report what compile-time options -** SQLite was built with. -*/ - -#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS - -/* #include "sqliteInt.h" */ - -/* -** An array of names of all compile-time options. This array should -** be sorted A-Z. -** -** This array looks large, but in a typical installation actually uses -** only a handful of compile-time options, so most times this array is usually -** rather short and uses little memory space. -*/ -static const char * const azCompileOpt[] = { - -/* These macros are provided to "stringify" the value of the define -** for those options in which the value is meaningful. */ -#define CTIMEOPT_VAL_(opt) #opt -#define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt) - -#if SQLITE_32BIT_ROWID - "32BIT_ROWID", -#endif -#if SQLITE_4_BYTE_ALIGNED_MALLOC - "4_BYTE_ALIGNED_MALLOC", -#endif -#if SQLITE_CASE_SENSITIVE_LIKE - "CASE_SENSITIVE_LIKE", -#endif -#if SQLITE_CHECK_PAGES - "CHECK_PAGES", -#endif -#if defined(__clang__) && defined(__clang_major__) - "COMPILER=clang-" CTIMEOPT_VAL(__clang_major__) "." - CTIMEOPT_VAL(__clang_minor__) "." - CTIMEOPT_VAL(__clang_patchlevel__), -#elif defined(_MSC_VER) - "COMPILER=msvc-" CTIMEOPT_VAL(_MSC_VER), -#elif defined(__GNUC__) && defined(__VERSION__) - "COMPILER=gcc-" __VERSION__, -#endif -#if SQLITE_COVERAGE_TEST - "COVERAGE_TEST", -#endif -#ifdef SQLITE_DEBUG - "DEBUG", -#endif -#if SQLITE_DEFAULT_LOCKING_MODE - "DEFAULT_LOCKING_MODE=" CTIMEOPT_VAL(SQLITE_DEFAULT_LOCKING_MODE), -#endif -#if defined(SQLITE_DEFAULT_MMAP_SIZE) && !defined(SQLITE_DEFAULT_MMAP_SIZE_xc) - "DEFAULT_MMAP_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_MMAP_SIZE), -#endif -#if SQLITE_DEFAULT_SYNCHRONOUS - "DEFAULT_SYNCHRONOUS=" CTIMEOPT_VAL(SQLITE_DEFAULT_SYNCHRONOUS), -#endif -#if SQLITE_DEFAULT_WAL_SYNCHRONOUS - "DEFAULT_WAL_SYNCHRONOUS=" CTIMEOPT_VAL(SQLITE_DEFAULT_WAL_SYNCHRONOUS), -#endif -#if SQLITE_DIRECT_OVERFLOW_READ - "DIRECT_OVERFLOW_READ", -#endif -#if SQLITE_DISABLE_DIRSYNC - "DISABLE_DIRSYNC", -#endif -#if SQLITE_DISABLE_LFS - "DISABLE_LFS", -#endif -#if SQLITE_ENABLE_8_3_NAMES - "ENABLE_8_3_NAMES=" CTIMEOPT_VAL(SQLITE_ENABLE_8_3_NAMES), -#endif -#if SQLITE_ENABLE_API_ARMOR - "ENABLE_API_ARMOR", -#endif -#if SQLITE_ENABLE_ATOMIC_WRITE - "ENABLE_ATOMIC_WRITE", -#endif -#if SQLITE_ENABLE_CEROD - "ENABLE_CEROD", -#endif -#if SQLITE_ENABLE_COLUMN_METADATA - "ENABLE_COLUMN_METADATA", -#endif -#if SQLITE_ENABLE_DBSTAT_VTAB - "ENABLE_DBSTAT_VTAB", -#endif -#if SQLITE_ENABLE_EXPENSIVE_ASSERT - "ENABLE_EXPENSIVE_ASSERT", -#endif -#if SQLITE_ENABLE_FTS1 - "ENABLE_FTS1", -#endif -#if SQLITE_ENABLE_FTS2 - "ENABLE_FTS2", -#endif -#if SQLITE_ENABLE_FTS3 - "ENABLE_FTS3", -#endif -#if SQLITE_ENABLE_FTS3_PARENTHESIS - "ENABLE_FTS3_PARENTHESIS", -#endif -#if SQLITE_ENABLE_FTS4 - "ENABLE_FTS4", -#endif -#if SQLITE_ENABLE_FTS5 - "ENABLE_FTS5", -#endif -#if SQLITE_ENABLE_ICU - "ENABLE_ICU", -#endif -#if SQLITE_ENABLE_IOTRACE - "ENABLE_IOTRACE", -#endif -#if SQLITE_ENABLE_JSON1 - "ENABLE_JSON1", -#endif -#if SQLITE_ENABLE_LOAD_EXTENSION - "ENABLE_LOAD_EXTENSION", -#endif -#if SQLITE_ENABLE_LOCKING_STYLE - "ENABLE_LOCKING_STYLE=" CTIMEOPT_VAL(SQLITE_ENABLE_LOCKING_STYLE), -#endif -#if SQLITE_ENABLE_MEMORY_MANAGEMENT - "ENABLE_MEMORY_MANAGEMENT", -#endif -#if SQLITE_ENABLE_MEMSYS3 - "ENABLE_MEMSYS3", -#endif -#if SQLITE_ENABLE_MEMSYS5 - "ENABLE_MEMSYS5", -#endif -#if SQLITE_ENABLE_OVERSIZE_CELL_CHECK - "ENABLE_OVERSIZE_CELL_CHECK", -#endif -#if SQLITE_ENABLE_RTREE - "ENABLE_RTREE", -#endif -#if defined(SQLITE_ENABLE_STAT4) - "ENABLE_STAT4", -#elif defined(SQLITE_ENABLE_STAT3) - "ENABLE_STAT3", -#endif -#if SQLITE_ENABLE_UNLOCK_NOTIFY - "ENABLE_UNLOCK_NOTIFY", -#endif -#if SQLITE_ENABLE_UPDATE_DELETE_LIMIT - "ENABLE_UPDATE_DELETE_LIMIT", -#endif -#if defined(SQLITE_ENABLE_URI_00_ERROR) - "ENABLE_URI_00_ERROR", -#endif -#if SQLITE_HAS_CODEC - "HAS_CODEC", -#endif -#if HAVE_ISNAN || SQLITE_HAVE_ISNAN - "HAVE_ISNAN", -#endif -#if SQLITE_HOMEGROWN_RECURSIVE_MUTEX - "HOMEGROWN_RECURSIVE_MUTEX", -#endif -#if SQLITE_IGNORE_AFP_LOCK_ERRORS - "IGNORE_AFP_LOCK_ERRORS", -#endif -#if SQLITE_IGNORE_FLOCK_LOCK_ERRORS - "IGNORE_FLOCK_LOCK_ERRORS", -#endif -#ifdef SQLITE_INT64_TYPE - "INT64_TYPE", -#endif -#ifdef SQLITE_LIKE_DOESNT_MATCH_BLOBS - "LIKE_DOESNT_MATCH_BLOBS", -#endif -#if SQLITE_LOCK_TRACE - "LOCK_TRACE", -#endif -#if defined(SQLITE_MAX_MMAP_SIZE) && !defined(SQLITE_MAX_MMAP_SIZE_xc) - "MAX_MMAP_SIZE=" CTIMEOPT_VAL(SQLITE_MAX_MMAP_SIZE), -#endif -#ifdef SQLITE_MAX_SCHEMA_RETRY - "MAX_SCHEMA_RETRY=" CTIMEOPT_VAL(SQLITE_MAX_SCHEMA_RETRY), -#endif -#if SQLITE_MEMDEBUG - "MEMDEBUG", -#endif -#if SQLITE_MIXED_ENDIAN_64BIT_FLOAT - "MIXED_ENDIAN_64BIT_FLOAT", -#endif -#if SQLITE_NO_SYNC - "NO_SYNC", -#endif -#if SQLITE_OMIT_ALTERTABLE - "OMIT_ALTERTABLE", -#endif -#if SQLITE_OMIT_ANALYZE - "OMIT_ANALYZE", -#endif -#if SQLITE_OMIT_ATTACH - "OMIT_ATTACH", -#endif -#if SQLITE_OMIT_AUTHORIZATION - "OMIT_AUTHORIZATION", -#endif -#if SQLITE_OMIT_AUTOINCREMENT - "OMIT_AUTOINCREMENT", -#endif -#if SQLITE_OMIT_AUTOINIT - "OMIT_AUTOINIT", -#endif -#if SQLITE_OMIT_AUTOMATIC_INDEX - "OMIT_AUTOMATIC_INDEX", -#endif -#if SQLITE_OMIT_AUTORESET - "OMIT_AUTORESET", -#endif -#if SQLITE_OMIT_AUTOVACUUM - "OMIT_AUTOVACUUM", -#endif -#if SQLITE_OMIT_BETWEEN_OPTIMIZATION - "OMIT_BETWEEN_OPTIMIZATION", -#endif -#if SQLITE_OMIT_BLOB_LITERAL - "OMIT_BLOB_LITERAL", -#endif -#if SQLITE_OMIT_BTREECOUNT - "OMIT_BTREECOUNT", -#endif -#if SQLITE_OMIT_CAST - "OMIT_CAST", -#endif -#if SQLITE_OMIT_CHECK - "OMIT_CHECK", -#endif -#if SQLITE_OMIT_COMPLETE - "OMIT_COMPLETE", -#endif -#if SQLITE_OMIT_COMPOUND_SELECT - "OMIT_COMPOUND_SELECT", -#endif -#if SQLITE_OMIT_CTE - "OMIT_CTE", -#endif -#if SQLITE_OMIT_DATETIME_FUNCS - "OMIT_DATETIME_FUNCS", -#endif -#if SQLITE_OMIT_DECLTYPE - "OMIT_DECLTYPE", -#endif -#if SQLITE_OMIT_DEPRECATED - "OMIT_DEPRECATED", -#endif -#if SQLITE_OMIT_DISKIO - "OMIT_DISKIO", -#endif -#if SQLITE_OMIT_EXPLAIN - "OMIT_EXPLAIN", -#endif -#if SQLITE_OMIT_FLAG_PRAGMAS - "OMIT_FLAG_PRAGMAS", -#endif -#if SQLITE_OMIT_FLOATING_POINT - "OMIT_FLOATING_POINT", -#endif -#if SQLITE_OMIT_FOREIGN_KEY - "OMIT_FOREIGN_KEY", -#endif -#if SQLITE_OMIT_GET_TABLE - "OMIT_GET_TABLE", -#endif -#if SQLITE_OMIT_INCRBLOB - "OMIT_INCRBLOB", -#endif -#if SQLITE_OMIT_INTEGRITY_CHECK - "OMIT_INTEGRITY_CHECK", -#endif -#if SQLITE_OMIT_LIKE_OPTIMIZATION - "OMIT_LIKE_OPTIMIZATION", -#endif -#if SQLITE_OMIT_LOAD_EXTENSION - "OMIT_LOAD_EXTENSION", -#endif -#if SQLITE_OMIT_LOCALTIME - "OMIT_LOCALTIME", -#endif -#if SQLITE_OMIT_LOOKASIDE - "OMIT_LOOKASIDE", -#endif -#if SQLITE_OMIT_MEMORYDB - "OMIT_MEMORYDB", -#endif -#if SQLITE_OMIT_OR_OPTIMIZATION - "OMIT_OR_OPTIMIZATION", -#endif -#if SQLITE_OMIT_PAGER_PRAGMAS - "OMIT_PAGER_PRAGMAS", -#endif -#if SQLITE_OMIT_PRAGMA - "OMIT_PRAGMA", -#endif -#if SQLITE_OMIT_PROGRESS_CALLBACK - "OMIT_PROGRESS_CALLBACK", -#endif -#if SQLITE_OMIT_QUICKBALANCE - "OMIT_QUICKBALANCE", -#endif -#if SQLITE_OMIT_REINDEX - "OMIT_REINDEX", -#endif -#if SQLITE_OMIT_SCHEMA_PRAGMAS - "OMIT_SCHEMA_PRAGMAS", -#endif -#if SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS - "OMIT_SCHEMA_VERSION_PRAGMAS", -#endif -#if SQLITE_OMIT_SHARED_CACHE - "OMIT_SHARED_CACHE", -#endif -#if SQLITE_OMIT_SUBQUERY - "OMIT_SUBQUERY", -#endif -#if SQLITE_OMIT_TCL_VARIABLE - "OMIT_TCL_VARIABLE", -#endif -#if SQLITE_OMIT_TEMPDB - "OMIT_TEMPDB", -#endif -#if SQLITE_OMIT_TRACE - "OMIT_TRACE", -#endif -#if SQLITE_OMIT_TRIGGER - "OMIT_TRIGGER", -#endif -#if SQLITE_OMIT_TRUNCATE_OPTIMIZATION - "OMIT_TRUNCATE_OPTIMIZATION", -#endif -#if SQLITE_OMIT_UTF16 - "OMIT_UTF16", -#endif -#if SQLITE_OMIT_VACUUM - "OMIT_VACUUM", -#endif -#if SQLITE_OMIT_VIEW - "OMIT_VIEW", -#endif -#if SQLITE_OMIT_VIRTUALTABLE - "OMIT_VIRTUALTABLE", -#endif -#if SQLITE_OMIT_WAL - "OMIT_WAL", -#endif -#if SQLITE_OMIT_WSD - "OMIT_WSD", -#endif -#if SQLITE_OMIT_XFER_OPT - "OMIT_XFER_OPT", -#endif -#if SQLITE_PERFORMANCE_TRACE - "PERFORMANCE_TRACE", -#endif -#if SQLITE_PROXY_DEBUG - "PROXY_DEBUG", -#endif -#if SQLITE_RTREE_INT_ONLY - "RTREE_INT_ONLY", -#endif -#if SQLITE_SECURE_DELETE - "SECURE_DELETE", -#endif -#if SQLITE_SMALL_STACK - "SMALL_STACK", -#endif -#if SQLITE_SOUNDEX - "SOUNDEX", -#endif -#if SQLITE_SYSTEM_MALLOC - "SYSTEM_MALLOC", -#endif -#if SQLITE_TCL - "TCL", -#endif -#if defined(SQLITE_TEMP_STORE) && !defined(SQLITE_TEMP_STORE_xc) - "TEMP_STORE=" CTIMEOPT_VAL(SQLITE_TEMP_STORE), -#endif -#if SQLITE_TEST - "TEST", -#endif -#if defined(SQLITE_THREADSAFE) - "THREADSAFE=" CTIMEOPT_VAL(SQLITE_THREADSAFE), -#endif -#if SQLITE_UNTESTABLE - "UNTESTABLE" -#endif -#if SQLITE_USE_ALLOCA - "USE_ALLOCA", -#endif -#if SQLITE_USER_AUTHENTICATION - "USER_AUTHENTICATION", -#endif -#if SQLITE_WIN32_MALLOC - "WIN32_MALLOC", -#endif -#if SQLITE_ZERO_MALLOC - "ZERO_MALLOC" -#endif -}; - -/* -** Given the name of a compile-time option, return true if that option -** was used and false if not. -** -** The name can optionally begin with "SQLITE_" but the "SQLITE_" prefix -** is not required for a match. -*/ -SQLITE_API int sqlite3_compileoption_used(const char *zOptName){ - int i, n; - -#if SQLITE_ENABLE_API_ARMOR - if( zOptName==0 ){ - (void)SQLITE_MISUSE_BKPT; - return 0; - } -#endif - if( sqlite3StrNICmp(zOptName, "SQLITE_", 7)==0 ) zOptName += 7; - n = sqlite3Strlen30(zOptName); - - /* Since ArraySize(azCompileOpt) is normally in single digits, a - ** linear search is adequate. No need for a binary search. */ - for(i=0; i=0 && NaDb[] entries referenced */ yDbMask lockMask; /* Subset of btreeMask that requires a lock */ - u32 aCounter[5]; /* Counters used by sqlite3_stmt_status() */ + u32 aCounter[7]; /* Counters used by sqlite3_stmt_status() */ char *zSql; /* Text of the SQL statement that generated this */ void *pFree; /* Free this when deleting the vdbe */ VdbeFrame *pFrame; /* Parent frame */ @@ -18384,6 +18938,7 @@ SQLITE_PRIVATE void sqlite3VdbeMemSetInt64(Mem*, i64); #else SQLITE_PRIVATE void sqlite3VdbeMemSetDouble(Mem*, double); #endif +SQLITE_PRIVATE void sqlite3VdbeMemSetPointer(Mem*, void*, const char*, void(*)(void*)); SQLITE_PRIVATE void sqlite3VdbeMemInit(Mem*,sqlite3*,u16); SQLITE_PRIVATE void sqlite3VdbeMemSetNull(Mem*); SQLITE_PRIVATE void sqlite3VdbeMemSetZeroBlob(Mem*,int); @@ -18415,7 +18970,7 @@ SQLITE_PRIVATE int sqlite3VdbeSorterInit(sqlite3 *, int, VdbeCursor *); SQLITE_PRIVATE void sqlite3VdbeSorterReset(sqlite3 *, VdbeSorter *); SQLITE_PRIVATE void sqlite3VdbeSorterClose(sqlite3 *, VdbeCursor *); SQLITE_PRIVATE int sqlite3VdbeSorterRowkey(const VdbeCursor *, Mem *); -SQLITE_PRIVATE int sqlite3VdbeSorterNext(sqlite3 *, const VdbeCursor *, int *); +SQLITE_PRIVATE int sqlite3VdbeSorterNext(sqlite3 *, const VdbeCursor *); SQLITE_PRIVATE int sqlite3VdbeSorterRewind(const VdbeCursor *, int *); SQLITE_PRIVATE int sqlite3VdbeSorterWrite(const VdbeCursor *, Mem *); SQLITE_PRIVATE int sqlite3VdbeSorterCompare(const VdbeCursor *, Mem *, int, int *); @@ -18443,12 +18998,14 @@ SQLITE_PRIVATE int sqlite3VdbeCheckFk(Vdbe *, int); # define sqlite3VdbeCheckFk(p,i) 0 #endif -SQLITE_PRIVATE int sqlite3VdbeMemTranslate(Mem*, u8); #ifdef SQLITE_DEBUG SQLITE_PRIVATE void sqlite3VdbePrintSql(Vdbe*); SQLITE_PRIVATE void sqlite3VdbeMemPrettyPrint(Mem *pMem, char *zBuf); #endif -SQLITE_PRIVATE int sqlite3VdbeMemHandleBom(Mem *pMem); +#ifndef SQLITE_OMIT_UTF16 +SQLITE_PRIVATE int sqlite3VdbeMemTranslate(Mem*, u8); +SQLITE_PRIVATE int sqlite3VdbeMemHandleBom(Mem *pMem); +#endif #ifndef SQLITE_OMIT_INCRBLOB SQLITE_PRIVATE int sqlite3VdbeMemExpandBlob(Mem *); @@ -19187,7 +19744,7 @@ static int parseDateOrTime( return 0; }else if( parseHhMmSs(zDate, p)==0 ){ return 0; - }else if( sqlite3StrICmp(zDate,"now")==0){ + }else if( sqlite3StrICmp(zDate,"now")==0 && sqlite3NotPureFunc(context) ){ return setDateTimeToCurrent(context, p); }else if( sqlite3AtoF(zDate, &r, sqlite3Strlen30(zDate), SQLITE_UTF8) ){ setRawDateNumber(p, r); @@ -19224,8 +19781,10 @@ static void computeYMD(DateTime *p){ p->Y = 2000; p->M = 1; p->D = 1; + }else if( !validJulianDay(p->iJD) ){ + datetimeError(p); + return; }else{ - assert( validJulianDay(p->iJD) ); Z = (int)((p->iJD + 43200000)/86400000); A = (int)((Z - 1867216.25)/36524.25); A = Z + 1 + A - (A/4); @@ -19468,7 +20027,7 @@ static int parseModifier( ** Assuming the current time value is UTC (a.k.a. GMT), shift it to ** show local time. */ - if( sqlite3_stricmp(z, "localtime")==0 ){ + if( sqlite3_stricmp(z, "localtime")==0 && sqlite3NotPureFunc(pCtx) ){ computeJD(p); p->iJD += localtimeOffset(p, pCtx, &rc); clearYMD_HMS_TZ(p); @@ -19494,7 +20053,7 @@ static int parseModifier( } } #ifndef SQLITE_OMIT_LOCALTIME - else if( sqlite3_stricmp(z, "utc")==0 ){ + else if( sqlite3_stricmp(z, "utc")==0 && sqlite3NotPureFunc(pCtx) ){ if( p->tzSet==0 ){ sqlite3_int64 c1; computeJD(p); @@ -20030,11 +20589,11 @@ static void currentTimeFunc( SQLITE_PRIVATE void sqlite3RegisterDateTimeFunctions(void){ static FuncDef aDateTimeFuncs[] = { #ifndef SQLITE_OMIT_DATETIME_FUNCS - DFUNCTION(julianday, -1, 0, 0, juliandayFunc ), - DFUNCTION(date, -1, 0, 0, dateFunc ), - DFUNCTION(time, -1, 0, 0, timeFunc ), - DFUNCTION(datetime, -1, 0, 0, datetimeFunc ), - DFUNCTION(strftime, -1, 0, 0, strftimeFunc ), + PURE_DATE(julianday, -1, 0, 0, juliandayFunc ), + PURE_DATE(date, -1, 0, 0, dateFunc ), + PURE_DATE(time, -1, 0, 0, timeFunc ), + PURE_DATE(datetime, -1, 0, 0, datetimeFunc ), + PURE_DATE(strftime, -1, 0, 0, strftimeFunc ), DFUNCTION(current_time, 0, 0, 0, ctimeFunc ), DFUNCTION(current_timestamp, 0, 0, 0, ctimestampFunc), DFUNCTION(current_date, 0, 0, 0, cdateFunc ), @@ -24661,11 +25220,12 @@ static SQLITE_NOINLINE void measureAllocationSize(sqlite3 *db, void *p){ /* ** Free memory that might be associated with a particular database -** connection. +** connection. Calling sqlite3DbFree(D,X) for X==0 is a harmless no-op. +** The sqlite3DbFreeNN(D,X) version requires that X be non-NULL. */ -SQLITE_PRIVATE void sqlite3DbFree(sqlite3 *db, void *p){ +SQLITE_PRIVATE void sqlite3DbFreeNN(sqlite3 *db, void *p){ assert( db==0 || sqlite3_mutex_held(db->mutex) ); - if( p==0 ) return; + assert( p!=0 ); if( db ){ if( db->pnBytesFreed ){ measureAllocationSize(db, p); @@ -24689,6 +25249,10 @@ SQLITE_PRIVATE void sqlite3DbFree(sqlite3 *db, void *p){ sqlite3MemdebugSetType(p, MEMTYPE_HEAP); sqlite3_free(p); } +SQLITE_PRIVATE void sqlite3DbFree(sqlite3 *db, void *p){ + assert( db==0 || sqlite3_mutex_held(db->mutex) ); + if( p ) sqlite3DbFreeNN(db, p); +} /* ** Change the size of an existing memory allocation @@ -26381,7 +26945,7 @@ SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 m SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ const char *zBinOp = 0; /* Binary operator */ const char *zUniOp = 0; /* Unary operator */ - char zFlgs[30]; + char zFlgs[60]; pView = sqlite3TreeViewPush(pView, moreToFollow); if( pExpr==0 ){ sqlite3TreeViewLine(pView, "nil"); @@ -26389,7 +26953,12 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m return; } if( pExpr->flags ){ - sqlite3_snprintf(sizeof(zFlgs),zFlgs," flags=0x%x",pExpr->flags); + if( ExprHasProperty(pExpr, EP_FromJoin) ){ + sqlite3_snprintf(sizeof(zFlgs),zFlgs," flags=0x%x iRJT=%d", + pExpr->flags, pExpr->iRightJoinTable); + }else{ + sqlite3_snprintf(sizeof(zFlgs),zFlgs," flags=0x%x",pExpr->flags); + } }else{ zFlgs[0] = 0; } @@ -26520,17 +27089,17 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m } #ifndef SQLITE_OMIT_SUBQUERY case TK_EXISTS: { - sqlite3TreeViewLine(pView, "EXISTS-expr"); + sqlite3TreeViewLine(pView, "EXISTS-expr flags=0x%x", pExpr->flags); sqlite3TreeViewSelect(pView, pExpr->x.pSelect, 0); break; } case TK_SELECT: { - sqlite3TreeViewLine(pView, "SELECT-expr"); + sqlite3TreeViewLine(pView, "SELECT-expr flags=0x%x", pExpr->flags); sqlite3TreeViewSelect(pView, pExpr->x.pSelect, 0); break; } case TK_IN: { - sqlite3TreeViewLine(pView, "IN"); + sqlite3TreeViewLine(pView, "IN flags=0x%x", pExpr->flags); sqlite3TreeViewExpr(pView, pExpr->pLeft, 1); if( ExprHasProperty(pExpr, EP_xIsSelect) ){ sqlite3TreeViewSelect(pView, pExpr->x.pSelect, 0); @@ -26608,6 +27177,11 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m sqlite3TreeViewSelect(pView, pExpr->pLeft->x.pSelect, 0); break; } + case TK_IF_NULL_ROW: { + sqlite3TreeViewLine(pView, "IF-NULL-ROW %d", pExpr->iTable); + sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); + break; + } default: { sqlite3TreeViewLine(pView, "op=%d", pExpr->op); break; @@ -27413,7 +27987,9 @@ translate_out: #endif return SQLITE_OK; } +#endif /* SQLITE_OMIT_UTF16 */ +#ifndef SQLITE_OMIT_UTF16 /* ** This routine checks for a byte-order mark at the beginning of the ** UTF-16 string stored in *pMem. If one is present, it is removed and @@ -28327,6 +28903,7 @@ SQLITE_PRIVATE int sqlite3GetInt32(const char *zNum, int *pValue){ } } #endif + if( !sqlite3Isdigit(zNum[0]) ) return 0; while( zNum[0]=='0' ) zNum++; for(i=0; i<11 && (c = zNum[i] - '0')>=0 && c<=9; i++){ v = v*10 + c; @@ -29323,8 +29900,9 @@ static int rehash(Hash *pH, unsigned int new_size){ } /* This function (for internal use only) locates an element in an -** hash table that matches the given key. The hash for this key is -** also computed and returned in the *pH parameter. +** hash table that matches the given key. If no element is found, +** a pointer to a static null element with HashElem.data==0 is returned. +** If pH is not NULL, then the hash for this key is written to *pH. */ static HashElem *findElementWithHash( const Hash *pH, /* The pH to be searched */ @@ -29334,6 +29912,7 @@ static HashElem *findElementWithHash( HashElem *elem; /* Used to loop thru the element list */ int count; /* Number of elements left to test */ unsigned int h; /* The computed hash */ + static HashElem nullElement = { 0, 0, 0, 0 }; if( pH->ht ){ /*OPTIMIZATION-IF-TRUE*/ struct _ht *pEntry; @@ -29346,7 +29925,7 @@ static HashElem *findElementWithHash( elem = pH->first; count = pH->count; } - *pHash = h; + if( pHash ) *pHash = h; while( count-- ){ assert( elem!=0 ); if( sqlite3StrICmp(elem->pKey,pKey)==0 ){ @@ -29354,7 +29933,7 @@ static HashElem *findElementWithHash( } elem = elem->next; } - return 0; + return &nullElement; } /* Remove a single entry from the hash table given a pointer to that @@ -29396,13 +29975,9 @@ static void removeElementGivenHash( ** found, or NULL if there is no match. */ SQLITE_PRIVATE void *sqlite3HashFind(const Hash *pH, const char *pKey){ - HashElem *elem; /* The element that matches key */ - unsigned int h; /* A hash on key */ - assert( pH!=0 ); assert( pKey!=0 ); - elem = findElementWithHash(pH, pKey, &h); - return elem ? elem->data : 0; + return findElementWithHash(pH, pKey, 0)->data; } /* Insert an element into the hash table pH. The key is pKey @@ -29427,7 +30002,7 @@ SQLITE_PRIVATE void *sqlite3HashInsert(Hash *pH, const char *pKey, void *data){ assert( pH!=0 ); assert( pKey!=0 ); elem = findElementWithHash(pH,pKey,&h); - if( elem ){ + if( elem->data ){ void *old_data = elem->data; if( data==0 ){ removeElementGivenHash(pH,elem,h); @@ -29490,90 +30065,90 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){ /* 20 */ "Once" OpHelp(""), /* 21 */ "If" OpHelp(""), /* 22 */ "IfNot" OpHelp(""), - /* 23 */ "SeekLT" OpHelp("key=r[P3@P4]"), - /* 24 */ "SeekLE" OpHelp("key=r[P3@P4]"), - /* 25 */ "SeekGE" OpHelp("key=r[P3@P4]"), - /* 26 */ "SeekGT" OpHelp("key=r[P3@P4]"), - /* 27 */ "Or" OpHelp("r[P3]=(r[P1] || r[P2])"), - /* 28 */ "And" OpHelp("r[P3]=(r[P1] && r[P2])"), - /* 29 */ "NoConflict" OpHelp("key=r[P3@P4]"), - /* 30 */ "NotFound" OpHelp("key=r[P3@P4]"), - /* 31 */ "Found" OpHelp("key=r[P3@P4]"), - /* 32 */ "SeekRowid" OpHelp("intkey=r[P3]"), - /* 33 */ "NotExists" OpHelp("intkey=r[P3]"), - /* 34 */ "IsNull" OpHelp("if r[P1]==NULL goto P2"), - /* 35 */ "NotNull" OpHelp("if r[P1]!=NULL goto P2"), - /* 36 */ "Ne" OpHelp("IF r[P3]!=r[P1]"), - /* 37 */ "Eq" OpHelp("IF r[P3]==r[P1]"), - /* 38 */ "Gt" OpHelp("IF r[P3]>r[P1]"), - /* 39 */ "Le" OpHelp("IF r[P3]<=r[P1]"), - /* 40 */ "Lt" OpHelp("IF r[P3]=r[P1]"), - /* 42 */ "ElseNotEq" OpHelp(""), - /* 43 */ "BitAnd" OpHelp("r[P3]=r[P1]&r[P2]"), - /* 44 */ "BitOr" OpHelp("r[P3]=r[P1]|r[P2]"), - /* 45 */ "ShiftLeft" OpHelp("r[P3]=r[P2]<>r[P1]"), - /* 47 */ "Add" OpHelp("r[P3]=r[P1]+r[P2]"), - /* 48 */ "Subtract" OpHelp("r[P3]=r[P2]-r[P1]"), - /* 49 */ "Multiply" OpHelp("r[P3]=r[P1]*r[P2]"), - /* 50 */ "Divide" OpHelp("r[P3]=r[P2]/r[P1]"), - /* 51 */ "Remainder" OpHelp("r[P3]=r[P2]%r[P1]"), - /* 52 */ "Concat" OpHelp("r[P3]=r[P2]+r[P1]"), - /* 53 */ "Last" OpHelp(""), - /* 54 */ "BitNot" OpHelp("r[P1]= ~r[P1]"), - /* 55 */ "IfSmaller" OpHelp(""), - /* 56 */ "SorterSort" OpHelp(""), - /* 57 */ "Sort" OpHelp(""), - /* 58 */ "Rewind" OpHelp(""), - /* 59 */ "IdxLE" OpHelp("key=r[P3@P4]"), - /* 60 */ "IdxGT" OpHelp("key=r[P3@P4]"), - /* 61 */ "IdxLT" OpHelp("key=r[P3@P4]"), - /* 62 */ "IdxGE" OpHelp("key=r[P3@P4]"), - /* 63 */ "RowSetRead" OpHelp("r[P3]=rowset(P1)"), - /* 64 */ "RowSetTest" OpHelp("if r[P3] in rowset(P1) goto P2"), - /* 65 */ "Program" OpHelp(""), - /* 66 */ "FkIfZero" OpHelp("if fkctr[P1]==0 goto P2"), - /* 67 */ "IfPos" OpHelp("if r[P1]>0 then r[P1]-=P3, goto P2"), - /* 68 */ "IfNotZero" OpHelp("if r[P1]!=0 then r[P1]--, goto P2"), - /* 69 */ "DecrJumpZero" OpHelp("if (--r[P1])==0 goto P2"), - /* 70 */ "IncrVacuum" OpHelp(""), - /* 71 */ "VNext" OpHelp(""), - /* 72 */ "Init" OpHelp("Start at P2"), - /* 73 */ "Return" OpHelp(""), - /* 74 */ "EndCoroutine" OpHelp(""), - /* 75 */ "HaltIfNull" OpHelp("if r[P3]=null halt"), - /* 76 */ "Halt" OpHelp(""), - /* 77 */ "Integer" OpHelp("r[P2]=P1"), - /* 78 */ "Int64" OpHelp("r[P2]=P4"), - /* 79 */ "String" OpHelp("r[P2]='P4' (len=P1)"), - /* 80 */ "Null" OpHelp("r[P2..P3]=NULL"), - /* 81 */ "SoftNull" OpHelp("r[P1]=NULL"), - /* 82 */ "Blob" OpHelp("r[P2]=P4 (len=P1)"), - /* 83 */ "Variable" OpHelp("r[P2]=parameter(P1,P4)"), - /* 84 */ "Move" OpHelp("r[P2@P3]=r[P1@P3]"), - /* 85 */ "Copy" OpHelp("r[P2@P3+1]=r[P1@P3+1]"), - /* 86 */ "SCopy" OpHelp("r[P2]=r[P1]"), - /* 87 */ "IntCopy" OpHelp("r[P2]=r[P1]"), - /* 88 */ "ResultRow" OpHelp("output=r[P1@P2]"), - /* 89 */ "CollSeq" OpHelp(""), - /* 90 */ "Function0" OpHelp("r[P3]=func(r[P2@P5])"), - /* 91 */ "Function" OpHelp("r[P3]=func(r[P2@P5])"), - /* 92 */ "AddImm" OpHelp("r[P1]=r[P1]+P2"), - /* 93 */ "RealAffinity" OpHelp(""), - /* 94 */ "Cast" OpHelp("affinity(r[P1])"), - /* 95 */ "Permutation" OpHelp(""), - /* 96 */ "Compare" OpHelp("r[P1@P3] <-> r[P2@P3]"), + /* 23 */ "IfNullRow" OpHelp("if P1.nullRow then r[P3]=NULL, goto P2"), + /* 24 */ "SeekLT" OpHelp("key=r[P3@P4]"), + /* 25 */ "SeekLE" OpHelp("key=r[P3@P4]"), + /* 26 */ "SeekGE" OpHelp("key=r[P3@P4]"), + /* 27 */ "SeekGT" OpHelp("key=r[P3@P4]"), + /* 28 */ "NoConflict" OpHelp("key=r[P3@P4]"), + /* 29 */ "NotFound" OpHelp("key=r[P3@P4]"), + /* 30 */ "Found" OpHelp("key=r[P3@P4]"), + /* 31 */ "SeekRowid" OpHelp("intkey=r[P3]"), + /* 32 */ "NotExists" OpHelp("intkey=r[P3]"), + /* 33 */ "Last" OpHelp(""), + /* 34 */ "IfSmaller" OpHelp(""), + /* 35 */ "SorterSort" OpHelp(""), + /* 36 */ "Sort" OpHelp(""), + /* 37 */ "Rewind" OpHelp(""), + /* 38 */ "IdxLE" OpHelp("key=r[P3@P4]"), + /* 39 */ "IdxGT" OpHelp("key=r[P3@P4]"), + /* 40 */ "IdxLT" OpHelp("key=r[P3@P4]"), + /* 41 */ "IdxGE" OpHelp("key=r[P3@P4]"), + /* 42 */ "RowSetRead" OpHelp("r[P3]=rowset(P1)"), + /* 43 */ "RowSetTest" OpHelp("if r[P3] in rowset(P1) goto P2"), + /* 44 */ "Program" OpHelp(""), + /* 45 */ "FkIfZero" OpHelp("if fkctr[P1]==0 goto P2"), + /* 46 */ "IfPos" OpHelp("if r[P1]>0 then r[P1]-=P3, goto P2"), + /* 47 */ "IfNotZero" OpHelp("if r[P1]!=0 then r[P1]--, goto P2"), + /* 48 */ "DecrJumpZero" OpHelp("if (--r[P1])==0 goto P2"), + /* 49 */ "IncrVacuum" OpHelp(""), + /* 50 */ "VNext" OpHelp(""), + /* 51 */ "Init" OpHelp("Start at P2"), + /* 52 */ "Return" OpHelp(""), + /* 53 */ "EndCoroutine" OpHelp(""), + /* 54 */ "HaltIfNull" OpHelp("if r[P3]=null halt"), + /* 55 */ "Halt" OpHelp(""), + /* 56 */ "Integer" OpHelp("r[P2]=P1"), + /* 57 */ "Int64" OpHelp("r[P2]=P4"), + /* 58 */ "String" OpHelp("r[P2]='P4' (len=P1)"), + /* 59 */ "Null" OpHelp("r[P2..P3]=NULL"), + /* 60 */ "SoftNull" OpHelp("r[P1]=NULL"), + /* 61 */ "Blob" OpHelp("r[P2]=P4 (len=P1)"), + /* 62 */ "Variable" OpHelp("r[P2]=parameter(P1,P4)"), + /* 63 */ "Move" OpHelp("r[P2@P3]=r[P1@P3]"), + /* 64 */ "Copy" OpHelp("r[P2@P3+1]=r[P1@P3+1]"), + /* 65 */ "SCopy" OpHelp("r[P2]=r[P1]"), + /* 66 */ "IntCopy" OpHelp("r[P2]=r[P1]"), + /* 67 */ "ResultRow" OpHelp("output=r[P1@P2]"), + /* 68 */ "CollSeq" OpHelp(""), + /* 69 */ "AddImm" OpHelp("r[P1]=r[P1]+P2"), + /* 70 */ "Or" OpHelp("r[P3]=(r[P1] || r[P2])"), + /* 71 */ "And" OpHelp("r[P3]=(r[P1] && r[P2])"), + /* 72 */ "RealAffinity" OpHelp(""), + /* 73 */ "Cast" OpHelp("affinity(r[P1])"), + /* 74 */ "Permutation" OpHelp(""), + /* 75 */ "IsNull" OpHelp("if r[P1]==NULL goto P2"), + /* 76 */ "NotNull" OpHelp("if r[P1]!=NULL goto P2"), + /* 77 */ "Ne" OpHelp("IF r[P3]!=r[P1]"), + /* 78 */ "Eq" OpHelp("IF r[P3]==r[P1]"), + /* 79 */ "Gt" OpHelp("IF r[P3]>r[P1]"), + /* 80 */ "Le" OpHelp("IF r[P3]<=r[P1]"), + /* 81 */ "Lt" OpHelp("IF r[P3]=r[P1]"), + /* 83 */ "ElseNotEq" OpHelp(""), + /* 84 */ "BitAnd" OpHelp("r[P3]=r[P1]&r[P2]"), + /* 85 */ "BitOr" OpHelp("r[P3]=r[P1]|r[P2]"), + /* 86 */ "ShiftLeft" OpHelp("r[P3]=r[P2]<>r[P1]"), + /* 88 */ "Add" OpHelp("r[P3]=r[P1]+r[P2]"), + /* 89 */ "Subtract" OpHelp("r[P3]=r[P2]-r[P1]"), + /* 90 */ "Multiply" OpHelp("r[P3]=r[P1]*r[P2]"), + /* 91 */ "Divide" OpHelp("r[P3]=r[P2]/r[P1]"), + /* 92 */ "Remainder" OpHelp("r[P3]=r[P2]%r[P1]"), + /* 93 */ "Concat" OpHelp("r[P3]=r[P2]+r[P1]"), + /* 94 */ "Compare" OpHelp("r[P1@P3] <-> r[P2@P3]"), + /* 95 */ "BitNot" OpHelp("r[P1]= ~r[P1]"), + /* 96 */ "Column" OpHelp("r[P3]=PX"), /* 97 */ "String8" OpHelp("r[P2]='P4'"), - /* 98 */ "Column" OpHelp("r[P3]=PX"), - /* 99 */ "Affinity" OpHelp("affinity(r[P1@P2])"), - /* 100 */ "MakeRecord" OpHelp("r[P3]=mkrec(r[P1@P2])"), - /* 101 */ "Count" OpHelp("r[P2]=count()"), - /* 102 */ "ReadCookie" OpHelp(""), - /* 103 */ "SetCookie" OpHelp(""), - /* 104 */ "ReopenIdx" OpHelp("root=P2 iDb=P3"), - /* 105 */ "OpenRead" OpHelp("root=P2 iDb=P3"), - /* 106 */ "OpenWrite" OpHelp("root=P2 iDb=P3"), + /* 98 */ "Affinity" OpHelp("affinity(r[P1@P2])"), + /* 99 */ "MakeRecord" OpHelp("r[P3]=mkrec(r[P1@P2])"), + /* 100 */ "Count" OpHelp("r[P2]=count()"), + /* 101 */ "ReadCookie" OpHelp(""), + /* 102 */ "SetCookie" OpHelp(""), + /* 103 */ "ReopenIdx" OpHelp("root=P2 iDb=P3"), + /* 104 */ "OpenRead" OpHelp("root=P2 iDb=P3"), + /* 105 */ "OpenWrite" OpHelp("root=P2 iDb=P3"), + /* 106 */ "OpenDup" OpHelp(""), /* 107 */ "OpenAutoindex" OpHelp("nColumn=P2"), /* 108 */ "OpenEphemeral" OpHelp("nColumn=P2"), /* 109 */ "SorterOpen" OpHelp(""), @@ -29595,7 +30170,7 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){ /* 125 */ "SorterInsert" OpHelp("key=r[P2]"), /* 126 */ "IdxInsert" OpHelp("key=r[P2]"), /* 127 */ "IdxDelete" OpHelp("key=r[P2@P3]"), - /* 128 */ "Seek" OpHelp("Move P3 to P1.rowid"), + /* 128 */ "DeferredSeek" OpHelp("Move P3 to P1.rowid if needed"), /* 129 */ "IdxRowid" OpHelp("r[P2]=rowid"), /* 130 */ "Destroy" OpHelp(""), /* 131 */ "Clear" OpHelp(""), @@ -29628,9 +30203,13 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){ /* 158 */ "VRename" OpHelp(""), /* 159 */ "Pagecount" OpHelp(""), /* 160 */ "MaxPgcnt" OpHelp(""), - /* 161 */ "CursorHint" OpHelp(""), - /* 162 */ "Noop" OpHelp(""), - /* 163 */ "Explain" OpHelp(""), + /* 161 */ "PureFunc0" OpHelp(""), + /* 162 */ "Function0" OpHelp("r[P3]=func(r[P2@P5])"), + /* 163 */ "PureFunc" OpHelp(""), + /* 164 */ "Function" OpHelp("r[P3]=func(r[P2@P5])"), + /* 165 */ "CursorHint" OpHelp(""), + /* 166 */ "Noop" OpHelp(""), + /* 167 */ "Explain" OpHelp(""), }; return azName[i]; } @@ -45245,8 +45824,7 @@ static int pcache1InitBulk(PCache1 *pCache){ sqlite3EndBenignMalloc(); if( zBulk ){ int nBulk = sqlite3MallocSize(zBulk)/pCache->szAlloc; - int i; - for(i=0; iszPage]; pX->page.pBuf = zBulk; pX->page.pExtra = &pX[1]; @@ -45255,7 +45833,7 @@ static int pcache1InitBulk(PCache1 *pCache){ pX->pNext = pCache->pFree; pCache->pFree = pX; zBulk += pCache->szAlloc; - } + }while( --nBulk ); } return pCache->pFree!=0; } @@ -46171,7 +46749,7 @@ SQLITE_PRIVATE int sqlite3PcacheReleaseMemory(int nReq){ int nFree = 0; assert( sqlite3_mutex_notheld(pcache1.grp.mutex) ); assert( sqlite3_mutex_notheld(pcache1.mutex) ); - if( sqlite3GlobalConfig.nPage==0 ){ + if( sqlite3GlobalConfig.pPage==0 ){ PgHdr1 *p; pcache1EnterMutex(&pcache1.grp); while( (nReq<0 || nFreesubjInMemory==0); +#endif assert( (isMainJrnl&~1)==0 ); /* isMainJrnl is 0 or 1 */ assert( (isSavepnt&~1)==0 ); /* isSavepnt is 0 or 1 */ @@ -49254,14 +49837,34 @@ static int pager_playback_one_page( i64 ofst = (pgno-1)*(i64)pPager->pageSize; testcase( !isSavepnt && pPg!=0 && (pPg->flags&PGHDR_NEED_SYNC)!=0 ); assert( !pagerUseWal(pPager) ); + + /* Write the data read from the journal back into the database file. + ** This is usually safe even for an encrypted database - as the data + ** was encrypted before it was written to the journal file. The exception + ** is if the data was just read from an in-memory sub-journal. In that + ** case it must be encrypted here before it is copied into the database + ** file. */ +#ifdef SQLITE_HAS_CODEC + if( !jrnlEnc ){ + CODEC2(pPager, aData, pgno, 7, rc=SQLITE_NOMEM_BKPT, aData); + rc = sqlite3OsWrite(pPager->fd, (u8 *)aData, pPager->pageSize, ofst); + CODEC1(pPager, aData, pgno, 3, rc=SQLITE_NOMEM_BKPT); + }else +#endif rc = sqlite3OsWrite(pPager->fd, (u8 *)aData, pPager->pageSize, ofst); + if( pgno>pPager->dbFileSize ){ pPager->dbFileSize = pgno; } if( pPager->pBackup ){ - CODEC1(pPager, aData, pgno, 3, rc=SQLITE_NOMEM_BKPT); +#ifdef SQLITE_HAS_CODEC + if( jrnlEnc ){ + CODEC1(pPager, aData, pgno, 3, rc=SQLITE_NOMEM_BKPT); + sqlite3BackupUpdate(pPager->pBackup, pgno, (u8*)aData); + CODEC2(pPager, aData, pgno, 7, rc=SQLITE_NOMEM_BKPT,aData); + }else +#endif sqlite3BackupUpdate(pPager->pBackup, pgno, (u8*)aData); - CODEC2(pPager, aData, pgno, 7, rc=SQLITE_NOMEM_BKPT, aData); } }else if( !isMainJrnl && pPg==0 ){ /* If this is a rollback of a savepoint and data was not written to @@ -49313,7 +49916,9 @@ static int pager_playback_one_page( } /* Decode the page just read from disk */ - CODEC1(pPager, pData, pPg->pgno, 3, rc=SQLITE_NOMEM_BKPT); +#if SQLITE_HAS_CODEC + if( jrnlEnc ){ CODEC1(pPager, pData, pPg->pgno, 3, rc=SQLITE_NOMEM_BKPT); } +#endif sqlite3PcacheRelease(pPg); } return rc; @@ -50099,7 +50704,7 @@ static int pagerPagecount(Pager *pPager, Pgno *pnPage){ nPage = sqlite3WalDbsize(pPager->pWal); /* If the number of pages in the database is not available from the - ** WAL sub-system, determine the page counte based on the size of + ** WAL sub-system, determine the page count based on the size of ** the database file. If the size of the database file is not an ** integer multiple of the page-size, round up the result. */ @@ -50150,23 +50755,21 @@ static int pagerOpenWalIfPresent(Pager *pPager){ if( !pPager->tempFile ){ int isWal; /* True if WAL file exists */ - Pgno nPage; /* Size of the database file */ - - rc = pagerPagecount(pPager, &nPage); - if( rc ) return rc; - if( nPage==0 ){ - rc = sqlite3OsDelete(pPager->pVfs, pPager->zWal, 0); - if( rc==SQLITE_IOERR_DELETE_NOENT ) rc = SQLITE_OK; - isWal = 0; - }else{ - rc = sqlite3OsAccess( - pPager->pVfs, pPager->zWal, SQLITE_ACCESS_EXISTS, &isWal - ); - } + rc = sqlite3OsAccess( + pPager->pVfs, pPager->zWal, SQLITE_ACCESS_EXISTS, &isWal + ); if( rc==SQLITE_OK ){ if( isWal ){ - testcase( sqlite3PcachePagecount(pPager->pPCache)==0 ); - rc = sqlite3PagerOpenWal(pPager, 0); + Pgno nPage; /* Size of the database file */ + + rc = pagerPagecount(pPager, &nPage); + if( rc ) return rc; + if( nPage==0 ){ + rc = sqlite3OsDelete(pPager->pVfs, pPager->zWal, 0); + }else{ + testcase( sqlite3PcachePagecount(pPager->pPCache)==0 ); + rc = sqlite3PagerOpenWal(pPager, 0); + } }else if( pPager->journalMode==PAGER_JOURNALMODE_WAL ){ pPager->journalMode = PAGER_JOURNALMODE_DELETE; } @@ -51325,8 +51928,13 @@ static int subjournalPage(PgHdr *pPg){ void *pData = pPg->pData; i64 offset = (i64)pPager->nSubRec*(4+pPager->pageSize); char *pData2; - - CODEC2(pPager, pData, pPg->pgno, 7, return SQLITE_NOMEM_BKPT, pData2); + +#if SQLITE_HAS_CODEC + if( !pPager->subjInMemory ){ + CODEC2(pPager, pData, pPg->pgno, 7, return SQLITE_NOMEM_BKPT, pData2); + }else +#endif + pData2 = pData; PAGERTRACE(("STMT-JOURNAL %d page %d\n", PAGERID(pPager), pPg->pgno)); rc = write32bits(pPager->sjfd, offset, pPg->pgno); if( rc==SQLITE_OK ){ @@ -52104,19 +52712,14 @@ SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager){ ** detected. The chance of an undetected change is so small that ** it can be neglected. */ - Pgno nPage = 0; char dbFileVers[sizeof(pPager->dbFileVers)]; - rc = pagerPagecount(pPager, &nPage); - if( rc ) goto failed; - - if( nPage>0 ){ - IOTRACE(("CKVERS %p %d\n", pPager, sizeof(dbFileVers))); - rc = sqlite3OsRead(pPager->fd, &dbFileVers, sizeof(dbFileVers), 24); - if( rc!=SQLITE_OK && rc!=SQLITE_IOERR_SHORT_READ ){ + IOTRACE(("CKVERS %p %d\n", pPager, sizeof(dbFileVers))); + rc = sqlite3OsRead(pPager->fd, &dbFileVers, sizeof(dbFileVers), 24); + if( rc!=SQLITE_OK ){ + if( rc!=SQLITE_IOERR_SHORT_READ ){ goto failed; } - }else{ memset(dbFileVers, 0, sizeof(dbFileVers)); } @@ -58409,10 +59012,12 @@ struct BtShared { #define BTS_READ_ONLY 0x0001 /* Underlying file is readonly */ #define BTS_PAGESIZE_FIXED 0x0002 /* Page size can no longer be changed */ #define BTS_SECURE_DELETE 0x0004 /* PRAGMA secure_delete is enabled */ -#define BTS_INITIALLY_EMPTY 0x0008 /* Database was empty at trans start */ -#define BTS_NO_WAL 0x0010 /* Do not open write-ahead-log files */ -#define BTS_EXCLUSIVE 0x0020 /* pWriter has an exclusive lock */ -#define BTS_PENDING 0x0040 /* Waiting for read-locks to clear */ +#define BTS_OVERWRITE 0x0008 /* Overwrite deleted content with zeros */ +#define BTS_FAST_SECURE 0x000c /* Combination of the previous two */ +#define BTS_INITIALLY_EMPTY 0x0010 /* Database was empty at trans start */ +#define BTS_NO_WAL 0x0020 /* Do not open write-ahead-log files */ +#define BTS_EXCLUSIVE 0x0040 /* pWriter has an exclusive lock */ +#define BTS_PENDING 0x0080 /* Waiting for read-locks to clear */ /* ** An instance of the following structure is used to hold information @@ -58478,10 +59083,10 @@ struct BtCursor { ** initialized. */ i8 iPage; /* Index of current page in apPage */ u8 curIntKey; /* Value of apPage[0]->intKey */ - struct KeyInfo *pKeyInfo; /* Argument passed to comparison function */ - void *padding1; /* Make object size a multiple of 16 */ - u16 aiIdx[BTCURSOR_MAX_DEPTH]; /* Current index in apPage[i] */ - MemPage *apPage[BTCURSOR_MAX_DEPTH]; /* Pages from root to current page */ + u16 ix; /* Current index for apPage[iPage] */ + u16 aiIdx[BTCURSOR_MAX_DEPTH-1]; /* Current index in apPage[i] */ + struct KeyInfo *pKeyInfo; /* Arg passed to comparison function */ + MemPage *apPage[BTCURSOR_MAX_DEPTH]; /* Pages from root to current page */ }; /* @@ -59110,7 +59715,7 @@ static int hasSharedCacheTableLock( ** Return true immediately. */ if( (pBtree->sharable==0) - || (eLockType==READ_LOCK && (pBtree->db->flags & SQLITE_ReadUncommitted)) + || (eLockType==READ_LOCK && (pBtree->db->flags & SQLITE_ReadUncommit)) ){ return 1; } @@ -59187,7 +59792,7 @@ static int hasReadConflicts(Btree *pBtree, Pgno iRoot){ for(p=pBtree->pBt->pCursor; p; p=p->pNext){ if( p->pgnoRoot==iRoot && p->pBtree!=pBtree - && 0==(p->pBtree->db->flags & SQLITE_ReadUncommitted) + && 0==(p->pBtree->db->flags & SQLITE_ReadUncommit) ){ return 1; } @@ -59209,7 +59814,7 @@ static int querySharedCacheTableLock(Btree *p, Pgno iTab, u8 eLock){ assert( sqlite3BtreeHoldsMutex(p) ); assert( eLock==READ_LOCK || eLock==WRITE_LOCK ); assert( p->db!=0 ); - assert( !(p->db->flags&SQLITE_ReadUncommitted)||eLock==WRITE_LOCK||iTab==1 ); + assert( !(p->db->flags&SQLITE_ReadUncommit)||eLock==WRITE_LOCK||iTab==1 ); /* If requesting a write-lock, then the Btree must have an open write ** transaction on this file. And, obviously, for this to be so there @@ -59287,7 +59892,7 @@ static int setSharedCacheTableLock(Btree *p, Pgno iTable, u8 eLock){ ** obtain a read-lock using this function. The only read-lock obtained ** by a connection in read-uncommitted mode is on the sqlite_master ** table, and that lock is obtained in BtreeBeginTrans(). */ - assert( 0==(p->db->flags&SQLITE_ReadUncommitted) || eLock==WRITE_LOCK ); + assert( 0==(p->db->flags&SQLITE_ReadUncommit) || eLock==WRITE_LOCK ); /* This function should only be called on a sharable b-tree after it ** has been determined that no other b-tree holds a conflicting lock. */ @@ -59457,6 +60062,7 @@ static void invalidateAllOverflowCache(BtShared *pBt){ */ static void invalidateIncrblobCursors( Btree *pBtree, /* The database file to check */ + Pgno pgnoRoot, /* The table that might be changing */ i64 iRow, /* The rowid that might be changing */ int isClearTable /* True if all rows are being deleted */ ){ @@ -59467,7 +60073,7 @@ static void invalidateIncrblobCursors( for(p=pBtree->pBt->pCursor; p; p=p->pNext){ if( (p->curFlags & BTCF_Incrblob)!=0 ){ pBtree->hasIncrblobCur = 1; - if( isClearTable || p->info.nKey==iRow ){ + if( p->pgnoRoot==pgnoRoot && (isClearTable || p->info.nKey==iRow) ){ p->eState = CURSOR_INVALID; } } @@ -59476,7 +60082,7 @@ static void invalidateIncrblobCursors( #else /* Stub function when INCRBLOB is omitted */ - #define invalidateIncrblobCursors(x,y,z) + #define invalidateIncrblobCursors(w,x,y,z) #endif /* SQLITE_OMIT_INCRBLOB */ /* @@ -59728,7 +60334,7 @@ static int btreeMoveto( if( pIdxKey==0 ) return SQLITE_NOMEM_BKPT; sqlite3VdbeRecordUnpack(pCur->pKeyInfo, (int)nKey, pKey, pIdxKey); if( pIdxKey->nField==0 ){ - rc = SQLITE_CORRUPT_BKPT; + rc = SQLITE_CORRUPT_PGNO(pCur->apPage[pCur->iPage]->pgno); goto moveto_done; } }else{ @@ -59957,7 +60563,7 @@ static int ptrmapGet(BtShared *pBt, Pgno key, u8 *pEType, Pgno *pPgno){ if( pPgno ) *pPgno = get4byte(&pPtrmap[offset+1]); sqlite3PagerUnref(pDbPage); - if( *pEType<1 || *pEType>5 ) return SQLITE_CORRUPT_BKPT; + if( *pEType<1 || *pEType>5 ) return SQLITE_CORRUPT_PGNO(iPtrmap); return SQLITE_OK; } @@ -60342,7 +60948,7 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){ int sz = get2byte(&data[iFree+2]); int top = get2byte(&data[hdr+5]); if( iFree2 ){ - if( iFree+sz>iFree2 ) return SQLITE_CORRUPT_BKPT; + if( iFree+sz>iFree2 ) return SQLITE_CORRUPT_PGNO(pPage->pgno); sz2 = get2byte(&data[iFree2+2]); assert( iFree+sz+sz2+iFree2-(iFree+sz) <= usableSize ); memmove(&data[iFree+sz+sz2], &data[iFree+sz], iFree2-(iFree+sz)); @@ -60373,13 +60979,13 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){ ** if PRAGMA cell_size_check=ON. */ if( pciCellLast ){ - return SQLITE_CORRUPT_BKPT; + return SQLITE_CORRUPT_PGNO(pPage->pgno); } assert( pc>=iCellFirst && pc<=iCellLast ); size = pPage->xCellSize(pPage, &src[pc]); cbrk -= size; if( cbrkusableSize ){ - return SQLITE_CORRUPT_BKPT; + return SQLITE_CORRUPT_PGNO(pPage->pgno); } assert( cbrk+size<=usableSize && cbrk>=iCellFirst ); testcase( cbrk+size==usableSize ); @@ -60399,7 +61005,7 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){ defragment_out: if( data[hdr+7]+cbrk-iCellFirst!=pPage->nFree ){ - return SQLITE_CORRUPT_BKPT; + return SQLITE_CORRUPT_PGNO(pPage->pgno); } assert( cbrk>=iCellFirst ); put2byte(&data[hdr+5], cbrk); @@ -60438,7 +61044,7 @@ static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){ /* EVIDENCE-OF: R-06866-39125 Freeblocks are always connected in order of ** increasing offset. */ if( pc>usableSize-4 || pcpgno); return 0; } /* EVIDENCE-OF: R-22710-53328 The third and fourth bytes of each @@ -60449,7 +61055,7 @@ static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){ testcase( x==4 ); testcase( x==3 ); if( pc < pPg->cellOffset+2*pPg->nCell || size+pc > usableSize ){ - *pRc = SQLITE_CORRUPT_BKPT; + *pRc = SQLITE_CORRUPT_PGNO(pPg->pgno); return 0; }else if( x<4 ){ /* EVIDENCE-OF: R-11498-58022 In a well-formed b-tree page, the total @@ -60516,7 +61122,7 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){ if( top==0 && pPage->pBt->usableSize==65536 ){ top = 65536; }else{ - return SQLITE_CORRUPT_BKPT; + return SQLITE_CORRUPT_PGNO(pPage->pgno); } } @@ -60597,7 +61203,7 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ /* Overwrite deleted information with zeros when the secure_delete ** option is enabled */ - if( pPage->pBt->btsFlags & BTS_SECURE_DELETE ){ + if( pPage->pBt->btsFlags & BTS_FAST_SECURE ){ memset(&data[iStart], 0, iSize); } @@ -60612,11 +61218,11 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ while( (iFreeBlk = get2byte(&data[iPtr]))pgno); } iPtr = iFreeBlk; } - if( iFreeBlk>iLast ) return SQLITE_CORRUPT_BKPT; + if( iFreeBlk>iLast ) return SQLITE_CORRUPT_PGNO(pPage->pgno); assert( iFreeBlk>iPtr || iFreeBlk==0 ); /* At this point: @@ -60627,9 +61233,11 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ */ if( iFreeBlk && iEnd+3>=iFreeBlk ){ nFrag = iFreeBlk - iEnd; - if( iEnd>iFreeBlk ) return SQLITE_CORRUPT_BKPT; + if( iEnd>iFreeBlk ) return SQLITE_CORRUPT_PGNO(pPage->pgno); iEnd = iFreeBlk + get2byte(&data[iFreeBlk+2]); - if( iEnd > pPage->pBt->usableSize ) return SQLITE_CORRUPT_BKPT; + if( iEnd > pPage->pBt->usableSize ){ + return SQLITE_CORRUPT_PGNO(pPage->pgno); + } iSize = iEnd - iStart; iFreeBlk = get2byte(&data[iFreeBlk]); } @@ -60641,20 +61249,20 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ if( iPtr>hdr+1 ){ int iPtrEnd = iPtr + get2byte(&data[iPtr+2]); if( iPtrEnd+3>=iStart ){ - if( iPtrEnd>iStart ) return SQLITE_CORRUPT_BKPT; + if( iPtrEnd>iStart ) return SQLITE_CORRUPT_PGNO(pPage->pgno); nFrag += iStart - iPtrEnd; iSize = iEnd - iPtr; iStart = iPtr; } } - if( nFrag>data[hdr+7] ) return SQLITE_CORRUPT_BKPT; + if( nFrag>data[hdr+7] ) return SQLITE_CORRUPT_PGNO(pPage->pgno); data[hdr+7] -= nFrag; } if( iStart==get2byte(&data[hdr+5]) ){ /* The new freeblock is at the beginning of the cell content area, ** so just extend the cell content area rather than create another ** freelist entry */ - if( iPtr!=hdr+1 ) return SQLITE_CORRUPT_BKPT; + if( iPtr!=hdr+1 ) return SQLITE_CORRUPT_PGNO(pPage->pgno); put2byte(&data[hdr+1], iFreeBlk); put2byte(&data[hdr+5], iEnd); }else{ @@ -60722,7 +61330,7 @@ static int decodeFlags(MemPage *pPage, int flagByte){ }else{ /* EVIDENCE-OF: R-47608-56469 Any other value for the b-tree page type is ** an error. */ - return SQLITE_CORRUPT_BKPT; + return SQLITE_CORRUPT_PGNO(pPage->pgno); } pPage->max1bytePayload = pBt->max1bytePayload; return SQLITE_OK; @@ -60738,6 +61346,16 @@ static int decodeFlags(MemPage *pPage, int flagByte){ ** we failed to detect any corruption. */ static int btreeInitPage(MemPage *pPage){ + int pc; /* Address of a freeblock within pPage->aData[] */ + u8 hdr; /* Offset to beginning of page header */ + u8 *data; /* Equal to pPage->aData */ + BtShared *pBt; /* The main btree structure */ + int usableSize; /* Amount of usable space on each page */ + u16 cellOffset; /* Offset from start of page to first cell pointer */ + int nFree; /* Number of unused bytes on the page */ + int top; /* First byte of the cell content area */ + int iCellFirst; /* First allowable cell or freeblock offset */ + int iCellLast; /* Last possible cell or freeblock offset */ assert( pPage->pBt!=0 ); assert( pPage->pBt->db!=0 ); @@ -60745,127 +61363,119 @@ static int btreeInitPage(MemPage *pPage){ assert( pPage->pgno==sqlite3PagerPagenumber(pPage->pDbPage) ); assert( pPage == sqlite3PagerGetExtra(pPage->pDbPage) ); assert( pPage->aData == sqlite3PagerGetData(pPage->pDbPage) ); + assert( pPage->isInit==0 ); - if( !pPage->isInit ){ - int pc; /* Address of a freeblock within pPage->aData[] */ - u8 hdr; /* Offset to beginning of page header */ - u8 *data; /* Equal to pPage->aData */ - BtShared *pBt; /* The main btree structure */ - int usableSize; /* Amount of usable space on each page */ - u16 cellOffset; /* Offset from start of page to first cell pointer */ - int nFree; /* Number of unused bytes on the page */ - int top; /* First byte of the cell content area */ - int iCellFirst; /* First allowable cell or freeblock offset */ - int iCellLast; /* Last possible cell or freeblock offset */ - - pBt = pPage->pBt; - - hdr = pPage->hdrOffset; - data = pPage->aData; - /* EVIDENCE-OF: R-28594-02890 The one-byte flag at offset 0 indicating - ** the b-tree page type. */ - if( decodeFlags(pPage, data[hdr]) ) return SQLITE_CORRUPT_BKPT; - assert( pBt->pageSize>=512 && pBt->pageSize<=65536 ); - pPage->maskPage = (u16)(pBt->pageSize - 1); - pPage->nOverflow = 0; - usableSize = pBt->usableSize; - pPage->cellOffset = cellOffset = hdr + 8 + pPage->childPtrSize; - pPage->aDataEnd = &data[usableSize]; - pPage->aCellIdx = &data[cellOffset]; - pPage->aDataOfst = &data[pPage->childPtrSize]; - /* EVIDENCE-OF: R-58015-48175 The two-byte integer at offset 5 designates - ** the start of the cell content area. A zero value for this integer is - ** interpreted as 65536. */ - top = get2byteNotZero(&data[hdr+5]); - /* EVIDENCE-OF: R-37002-32774 The two-byte integer at offset 3 gives the - ** number of cells on the page. */ - pPage->nCell = get2byte(&data[hdr+3]); - if( pPage->nCell>MX_CELL(pBt) ){ - /* To many cells for a single page. The page must be corrupt */ - return SQLITE_CORRUPT_BKPT; - } - testcase( pPage->nCell==MX_CELL(pBt) ); - /* EVIDENCE-OF: R-24089-57979 If a page contains no cells (which is only - ** possible for a root page of a table that contains no rows) then the - ** offset to the cell content area will equal the page size minus the - ** bytes of reserved space. */ - assert( pPage->nCell>0 || top==usableSize || CORRUPT_DB ); - - /* A malformed database page might cause us to read past the end - ** of page when parsing a cell. - ** - ** The following block of code checks early to see if a cell extends - ** past the end of a page boundary and causes SQLITE_CORRUPT to be - ** returned if it does. - */ - iCellFirst = cellOffset + 2*pPage->nCell; - iCellLast = usableSize - 4; - if( pBt->db->flags & SQLITE_CellSizeCk ){ - int i; /* Index into the cell pointer array */ - int sz; /* Size of a cell */ - - if( !pPage->leaf ) iCellLast--; - for(i=0; inCell; i++){ - pc = get2byteAligned(&data[cellOffset+i*2]); - testcase( pc==iCellFirst ); - testcase( pc==iCellLast ); - if( pciCellLast ){ - return SQLITE_CORRUPT_BKPT; - } - sz = pPage->xCellSize(pPage, &data[pc]); - testcase( pc+sz==usableSize ); - if( pc+sz>usableSize ){ - return SQLITE_CORRUPT_BKPT; - } - } - if( !pPage->leaf ) iCellLast++; - } - - /* Compute the total free space on the page - ** EVIDENCE-OF: R-23588-34450 The two-byte integer at offset 1 gives the - ** start of the first freeblock on the page, or is zero if there are no - ** freeblocks. */ - pc = get2byte(&data[hdr+1]); - nFree = data[hdr+7] + top; /* Init nFree to non-freeblock free space */ - if( pc>0 ){ - u32 next, size; - if( pciCellLast ){ - return SQLITE_CORRUPT_BKPT; /* Freeblock off the end of the page */ - } - next = get2byte(&data[pc]); - size = get2byte(&data[pc+2]); - nFree = nFree + size; - if( next<=pc+size+3 ) break; - pc = next; - } - if( next>0 ){ - return SQLITE_CORRUPT_BKPT; /* Freeblock not in ascending order */ - } - if( pc+size>(unsigned int)usableSize ){ - return SQLITE_CORRUPT_BKPT; /* Last freeblock extends past page end */ - } - } - - /* At this point, nFree contains the sum of the offset to the start - ** of the cell-content area plus the number of free bytes within - ** the cell-content area. If this is greater than the usable-size - ** of the page, then the page must be corrupted. This check also - ** serves to verify that the offset to the start of the cell-content - ** area, according to the page header, lies within the page. - */ - if( nFree>usableSize ){ - return SQLITE_CORRUPT_BKPT; - } - pPage->nFree = (u16)(nFree - iCellFirst); - pPage->isInit = 1; + pBt = pPage->pBt; + hdr = pPage->hdrOffset; + data = pPage->aData; + /* EVIDENCE-OF: R-28594-02890 The one-byte flag at offset 0 indicating + ** the b-tree page type. */ + if( decodeFlags(pPage, data[hdr]) ){ + return SQLITE_CORRUPT_PGNO(pPage->pgno); } + assert( pBt->pageSize>=512 && pBt->pageSize<=65536 ); + pPage->maskPage = (u16)(pBt->pageSize - 1); + pPage->nOverflow = 0; + usableSize = pBt->usableSize; + pPage->cellOffset = cellOffset = hdr + 8 + pPage->childPtrSize; + pPage->aDataEnd = &data[usableSize]; + pPage->aCellIdx = &data[cellOffset]; + pPage->aDataOfst = &data[pPage->childPtrSize]; + /* EVIDENCE-OF: R-58015-48175 The two-byte integer at offset 5 designates + ** the start of the cell content area. A zero value for this integer is + ** interpreted as 65536. */ + top = get2byteNotZero(&data[hdr+5]); + /* EVIDENCE-OF: R-37002-32774 The two-byte integer at offset 3 gives the + ** number of cells on the page. */ + pPage->nCell = get2byte(&data[hdr+3]); + if( pPage->nCell>MX_CELL(pBt) ){ + /* To many cells for a single page. The page must be corrupt */ + return SQLITE_CORRUPT_PGNO(pPage->pgno); + } + testcase( pPage->nCell==MX_CELL(pBt) ); + /* EVIDENCE-OF: R-24089-57979 If a page contains no cells (which is only + ** possible for a root page of a table that contains no rows) then the + ** offset to the cell content area will equal the page size minus the + ** bytes of reserved space. */ + assert( pPage->nCell>0 || top==usableSize || CORRUPT_DB ); + + /* A malformed database page might cause us to read past the end + ** of page when parsing a cell. + ** + ** The following block of code checks early to see if a cell extends + ** past the end of a page boundary and causes SQLITE_CORRUPT to be + ** returned if it does. + */ + iCellFirst = cellOffset + 2*pPage->nCell; + iCellLast = usableSize - 4; + if( pBt->db->flags & SQLITE_CellSizeCk ){ + int i; /* Index into the cell pointer array */ + int sz; /* Size of a cell */ + + if( !pPage->leaf ) iCellLast--; + for(i=0; inCell; i++){ + pc = get2byteAligned(&data[cellOffset+i*2]); + testcase( pc==iCellFirst ); + testcase( pc==iCellLast ); + if( pciCellLast ){ + return SQLITE_CORRUPT_PGNO(pPage->pgno); + } + sz = pPage->xCellSize(pPage, &data[pc]); + testcase( pc+sz==usableSize ); + if( pc+sz>usableSize ){ + return SQLITE_CORRUPT_PGNO(pPage->pgno); + } + } + if( !pPage->leaf ) iCellLast++; + } + + /* Compute the total free space on the page + ** EVIDENCE-OF: R-23588-34450 The two-byte integer at offset 1 gives the + ** start of the first freeblock on the page, or is zero if there are no + ** freeblocks. */ + pc = get2byte(&data[hdr+1]); + nFree = data[hdr+7] + top; /* Init nFree to non-freeblock free space */ + if( pc>0 ){ + u32 next, size; + if( pcpgno); + } + while( 1 ){ + if( pc>iCellLast ){ + /* Freeblock off the end of the page */ + return SQLITE_CORRUPT_PGNO(pPage->pgno); + } + next = get2byte(&data[pc]); + size = get2byte(&data[pc+2]); + nFree = nFree + size; + if( next<=pc+size+3 ) break; + pc = next; + } + if( next>0 ){ + /* Freeblock not in ascending order */ + return SQLITE_CORRUPT_PGNO(pPage->pgno); + } + if( pc+size>(unsigned int)usableSize ){ + /* Last freeblock extends past page end */ + return SQLITE_CORRUPT_PGNO(pPage->pgno); + } + } + + /* At this point, nFree contains the sum of the offset to the start + ** of the cell-content area plus the number of free bytes within + ** the cell-content area. If this is greater than the usable-size + ** of the page, then the page must be corrupted. This check also + ** serves to verify that the offset to the start of the cell-content + ** area, according to the page header, lies within the page. + */ + if( nFree>usableSize ){ + return SQLITE_CORRUPT_PGNO(pPage->pgno); + } + pPage->nFree = (u16)(nFree - iCellFirst); + pPage->isInit = 1; return SQLITE_OK; } @@ -60884,7 +61494,7 @@ static void zeroPage(MemPage *pPage, int flags){ assert( sqlite3PagerGetData(pPage->pDbPage) == data ); assert( sqlite3PagerIswriteable(pPage->pDbPage) ); assert( sqlite3_mutex_held(pBt->mutex) ); - if( pBt->btsFlags & BTS_SECURE_DELETE ){ + if( pBt->btsFlags & BTS_FAST_SECURE ){ memset(&data[hdr], 0, pBt->usableSize - hdr); } data[hdr] = (char)flags; @@ -61029,7 +61639,7 @@ static int getAndInitPage( /* If obtaining a child page for a cursor, we must verify that the page is ** compatible with the root page. */ if( pCur && ((*ppPage)->nCell<1 || (*ppPage)->intKey!=pCur->curIntKey) ){ - rc = SQLITE_CORRUPT_BKPT; + rc = SQLITE_CORRUPT_PGNO(pgno); releasePage(*ppPage); goto getAndInitPage_error; } @@ -61307,8 +61917,10 @@ SQLITE_PRIVATE int sqlite3BtreeOpen( pBt->pCursor = 0; pBt->pPage1 = 0; if( sqlite3PagerIsreadonly(pBt->pPager) ) pBt->btsFlags |= BTS_READ_ONLY; -#ifdef SQLITE_SECURE_DELETE +#if defined(SQLITE_SECURE_DELETE) pBt->btsFlags |= BTS_SECURE_DELETE; +#elif defined(SQLITE_FAST_SECURE_DELETE) + pBt->btsFlags |= BTS_OVERWRITE; #endif /* EVIDENCE-OF: R-51873-39618 The page size for a database file is ** determined by the 2-byte integer located at an offset of 16 bytes from @@ -61756,19 +62368,34 @@ SQLITE_PRIVATE int sqlite3BtreeMaxPageCount(Btree *p, int mxPage){ } /* -** Set the BTS_SECURE_DELETE flag if newFlag is 0 or 1. If newFlag is -1, -** then make no changes. Always return the value of the BTS_SECURE_DELETE -** setting after the change. +** Change the values for the BTS_SECURE_DELETE and BTS_OVERWRITE flags: +** +** newFlag==0 Both BTS_SECURE_DELETE and BTS_OVERWRITE are cleared +** newFlag==1 BTS_SECURE_DELETE set and BTS_OVERWRITE is cleared +** newFlag==2 BTS_SECURE_DELETE cleared and BTS_OVERWRITE is set +** newFlag==(-1) No changes +** +** This routine acts as a query if newFlag is less than zero +** +** With BTS_OVERWRITE set, deleted content is overwritten by zeros, but +** freelist leaf pages are not written back to the database. Thus in-page +** deleted content is cleared, but freelist deleted content is not. +** +** With BTS_SECURE_DELETE, operation is like BTS_OVERWRITE with the addition +** that freelist leaf pages are written back into the database, increasing +** the amount of disk I/O. */ SQLITE_PRIVATE int sqlite3BtreeSecureDelete(Btree *p, int newFlag){ int b; if( p==0 ) return 0; sqlite3BtreeEnter(p); + assert( BTS_OVERWRITE==BTS_SECURE_DELETE*2 ); + assert( BTS_FAST_SECURE==(BTS_OVERWRITE|BTS_SECURE_DELETE) ); if( newFlag>=0 ){ - p->pBt->btsFlags &= ~BTS_SECURE_DELETE; - if( newFlag ) p->pBt->btsFlags |= BTS_SECURE_DELETE; - } - b = (p->pBt->btsFlags & BTS_SECURE_DELETE)!=0; + p->pBt->btsFlags &= ~BTS_FAST_SECURE; + p->pBt->btsFlags |= BTS_SECURE_DELETE*newFlag; + } + b = (p->pBt->btsFlags & BTS_FAST_SECURE)/BTS_SECURE_DELETE; sqlite3BtreeLeave(p); return b; } @@ -61974,7 +62601,7 @@ static int lockBtree(BtShared *pBt){ pageSize-usableSize); return rc; } - if( (pBt->db->flags & SQLITE_RecoveryMode)==0 && nPage>nPageFile ){ + if( (pBt->db->flags & SQLITE_WriteSchema)==0 && nPage>nPageFile ){ rc = SQLITE_CORRUPT_BKPT; goto page1_init_failed; } @@ -62317,7 +62944,7 @@ static int setChildPtrmaps(MemPage *pPage){ Pgno pgno = pPage->pgno; assert( sqlite3_mutex_held(pPage->pBt->mutex) ); - rc = btreeInitPage(pPage); + rc = pPage->isInit ? SQLITE_OK : btreeInitPage(pPage); if( rc!=SQLITE_OK ) return rc; nCell = pPage->nCell; @@ -62360,7 +62987,7 @@ static int modifyPagePointer(MemPage *pPage, Pgno iFrom, Pgno iTo, u8 eType){ if( eType==PTRMAP_OVERFLOW2 ){ /* The pointer is always the first 4 bytes of the page in this case. */ if( get4byte(pPage->aData)!=iFrom ){ - return SQLITE_CORRUPT_BKPT; + return SQLITE_CORRUPT_PGNO(pPage->pgno); } put4byte(pPage->aData, iTo); }else{ @@ -62368,7 +62995,7 @@ static int modifyPagePointer(MemPage *pPage, Pgno iFrom, Pgno iTo, u8 eType){ int nCell; int rc; - rc = btreeInitPage(pPage); + rc = pPage->isInit ? SQLITE_OK : btreeInitPage(pPage); if( rc ) return rc; nCell = pPage->nCell; @@ -62379,7 +63006,7 @@ static int modifyPagePointer(MemPage *pPage, Pgno iFrom, Pgno iTo, u8 eType){ pPage->xParseCell(pPage, pCell, &info); if( info.nLocal pPage->aData+pPage->pBt->usableSize ){ - return SQLITE_CORRUPT_BKPT; + return SQLITE_CORRUPT_PGNO(pPage->pgno); } if( iFrom==get4byte(pCell+info.nSize-4) ){ put4byte(pCell+info.nSize-4, iTo); @@ -62397,7 +63024,7 @@ static int modifyPagePointer(MemPage *pPage, Pgno iFrom, Pgno iTo, u8 eType){ if( i==nCell ){ if( eType!=PTRMAP_BTREE || get4byte(&pPage->aData[pPage->hdrOffset+8])!=iFrom ){ - return SQLITE_CORRUPT_BKPT; + return SQLITE_CORRUPT_PGNO(pPage->pgno); } put4byte(&pPage->aData[pPage->hdrOffset+8], iTo); } @@ -63274,7 +63901,7 @@ SQLITE_PRIVATE int sqlite3BtreeCloseCursor(BtCursor *pCur){ CellInfo info; int iPage = pCur->iPage; memset(&info, 0, sizeof(info)); - btreeParseCell(pCur->apPage[iPage], pCur->aiIdx[iPage], &info); + btreeParseCell(pCur->apPage[iPage], pCur->ix, &info); assert( CORRUPT_DB || memcmp(&info, &pCur->info, sizeof(info))==0 ); } #else @@ -63284,7 +63911,7 @@ static SQLITE_NOINLINE void getCellInfo(BtCursor *pCur){ if( pCur->info.nSize==0 ){ int iPage = pCur->iPage; pCur->curFlags |= BTCF_ValidNKey; - btreeParseCell(pCur->apPage[iPage],pCur->aiIdx[iPage],&pCur->info); + btreeParseCell(pCur->apPage[iPage],pCur->ix,&pCur->info); }else{ assertCellInfo(pCur); } @@ -63491,7 +64118,7 @@ static int accessPayload( assert( pPage ); assert( eOp==0 || eOp==1 ); assert( pCur->eState==CURSOR_VALID ); - assert( pCur->aiIdx[pCur->iPage]nCell ); + assert( pCur->ixnCell ); assert( cursorHoldsMutex(pCur) ); getCellInfo(pCur); @@ -63505,7 +64132,7 @@ static int accessPayload( ** &aPayload[pCur->info.nLocal] > &pPage->aData[pBt->usableSize] ** but is recast into its current form to avoid integer overflow problems */ - return SQLITE_CORRUPT_BKPT; + return SQLITE_CORRUPT_PGNO(pPage->pgno); } /* Check if data must be read/written to/from the btree page itself. */ @@ -63652,7 +64279,8 @@ static int accessPayload( } if( rc==SQLITE_OK && amt>0 ){ - return SQLITE_CORRUPT_BKPT; /* Overflow chain ends prematurely */ + /* Overflow chain ends prematurely */ + return SQLITE_CORRUPT_PGNO(pPage->pgno); } return rc; } @@ -63678,7 +64306,7 @@ SQLITE_PRIVATE int sqlite3BtreePayload(BtCursor *pCur, u32 offset, u32 amt, void assert( cursorHoldsMutex(pCur) ); assert( pCur->eState==CURSOR_VALID ); assert( pCur->iPage>=0 && pCur->apPage[pCur->iPage] ); - assert( pCur->aiIdx[pCur->iPage]apPage[pCur->iPage]->nCell ); + assert( pCur->ixapPage[pCur->iPage]->nCell ); return accessPayload(pCur, offset, amt, (unsigned char*)pBuf, 0); } @@ -63740,7 +64368,7 @@ static const void *fetchPayload( assert( pCur->eState==CURSOR_VALID ); assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); assert( cursorOwnsBtShared(pCur) ); - assert( pCur->aiIdx[pCur->iPage]apPage[pCur->iPage]->nCell ); + assert( pCur->ixapPage[pCur->iPage]->nCell ); assert( pCur->info.nSize>0 ); assert( pCur->info.pPayload>pCur->apPage[pCur->iPage]->aData || CORRUPT_DB ); assert( pCur->info.pPayloadapPage[pCur->iPage]->aDataEnd ||CORRUPT_DB); @@ -63791,8 +64419,8 @@ static int moveToChild(BtCursor *pCur, u32 newPgno){ } pCur->info.nSize = 0; pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl); - pCur->iPage++; - pCur->aiIdx[pCur->iPage] = 0; + pCur->aiIdx[pCur->iPage++] = pCur->ix; + pCur->ix = 0; return getAndInitPage(pBt, newPgno, &pCur->apPage[pCur->iPage], pCur, pCur->curPagerFlags); } @@ -63840,6 +64468,7 @@ static void moveToParent(BtCursor *pCur){ testcase( pCur->aiIdx[pCur->iPage-1] > pCur->apPage[pCur->iPage-1]->nCell ); pCur->info.nSize = 0; pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl); + pCur->ix = pCur->aiIdx[pCur->iPage-1]; releasePageNotNull(pCur->apPage[pCur->iPage--]); } @@ -63917,11 +64546,11 @@ static int moveToRoot(BtCursor *pCur){ ** (or the freelist). */ assert( pRoot->intKey==1 || pRoot->intKey==0 ); if( pRoot->isInit==0 || (pCur->pKeyInfo==0)!=pRoot->intKey ){ - return SQLITE_CORRUPT_BKPT; + return SQLITE_CORRUPT_PGNO(pCur->apPage[pCur->iPage]->pgno); } skip_init: - pCur->aiIdx[0] = 0; + pCur->ix = 0; pCur->info.nSize = 0; pCur->curFlags &= ~(BTCF_AtLast|BTCF_ValidNKey|BTCF_ValidOvfl); @@ -63955,8 +64584,8 @@ static int moveToLeftmost(BtCursor *pCur){ assert( cursorOwnsBtShared(pCur) ); assert( pCur->eState==CURSOR_VALID ); while( rc==SQLITE_OK && !(pPage = pCur->apPage[pCur->iPage])->leaf ){ - assert( pCur->aiIdx[pCur->iPage]nCell ); - pgno = get4byte(findCell(pPage, pCur->aiIdx[pCur->iPage])); + assert( pCur->ixnCell ); + pgno = get4byte(findCell(pPage, pCur->ix)); rc = moveToChild(pCur, pgno); } return rc; @@ -63981,11 +64610,11 @@ static int moveToRightmost(BtCursor *pCur){ assert( pCur->eState==CURSOR_VALID ); while( !(pPage = pCur->apPage[pCur->iPage])->leaf ){ pgno = get4byte(&pPage->aData[pPage->hdrOffset+8]); - pCur->aiIdx[pCur->iPage] = pPage->nCell; + pCur->ix = pPage->nCell; rc = moveToChild(pCur, pgno); if( rc ) return rc; } - pCur->aiIdx[pCur->iPage] = pPage->nCell-1; + pCur->ix = pPage->nCell-1; assert( pCur->info.nSize==0 ); assert( (pCur->curFlags & BTCF_ValidNKey)==0 ); return SQLITE_OK; @@ -64033,7 +64662,7 @@ SQLITE_PRIVATE int sqlite3BtreeLast(BtCursor *pCur, int *pRes){ for(ii=0; iiiPage; ii++){ assert( pCur->aiIdx[ii]==pCur->apPage[ii]->nCell ); } - assert( pCur->aiIdx[pCur->iPage]==pCur->apPage[pCur->iPage]->nCell-1 ); + assert( pCur->ix==pCur->apPage[pCur->iPage]->nCell-1 ); assert( pCur->apPage[pCur->iPage]->leaf ); #endif return SQLITE_OK; @@ -64122,16 +64751,19 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( /* If the requested key is one more than the previous key, then ** try to get there using sqlite3BtreeNext() rather than a full ** binary search. This is an optimization only. The correct answer - ** is still obtained without this ase, only a little more slowely */ + ** is still obtained without this case, only a little more slowely */ if( pCur->info.nKey+1==intKey && !pCur->skipNext ){ *pRes = 0; - rc = sqlite3BtreeNext(pCur, pRes); - if( rc ) return rc; - if( *pRes==0 ){ + rc = sqlite3BtreeNext(pCur, 0); + if( rc==SQLITE_OK ){ getCellInfo(pCur); if( pCur->info.nKey==intKey ){ return SQLITE_OK; } + }else if( rc==SQLITE_DONE ){ + rc = SQLITE_OK; + }else{ + return rc; } } } @@ -64180,14 +64812,16 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( upr = pPage->nCell-1; assert( biasRight==0 || biasRight==1 ); idx = upr>>(1-biasRight); /* idx = biasRight ? upr : (lwr+upr)/2; */ - pCur->aiIdx[pCur->iPage] = (u16)idx; + pCur->ix = (u16)idx; if( xRecordCompare==0 ){ for(;;){ i64 nCellKey; pCell = findCellPastPtr(pPage, idx); if( pPage->intKeyLeaf ){ while( 0x80 <= *(pCell++) ){ - if( pCell>=pPage->aDataEnd ) return SQLITE_CORRUPT_BKPT; + if( pCell>=pPage->aDataEnd ){ + return SQLITE_CORRUPT_PGNO(pPage->pgno); + } } } getVarint(pCell, (u64*)&nCellKey); @@ -64199,7 +64833,7 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( if( lwr>upr ){ c = +1; break; } }else{ assert( nCellKey==intKey ); - pCur->aiIdx[pCur->iPage] = (u16)idx; + pCur->ix = (u16)idx; if( !pPage->leaf ){ lwr = idx; goto moveto_next_layer; @@ -64260,7 +64894,7 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( testcase( nCell==1 ); /* Invalid key size: 0x80 0x80 0x01 */ testcase( nCell==2 ); /* Minimum legal index key size */ if( nCell<2 ){ - rc = SQLITE_CORRUPT_BKPT; + rc = SQLITE_CORRUPT_PGNO(pPage->pgno); goto moveto_finish; } pCellKey = sqlite3Malloc( nCell+18 ); @@ -64268,7 +64902,7 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( rc = SQLITE_NOMEM_BKPT; goto moveto_finish; } - pCur->aiIdx[pCur->iPage] = (u16)idx; + pCur->ix = (u16)idx; rc = accessPayload(pCur, 0, nCell, (unsigned char*)pCellKey, 0); pCur->curFlags &= ~BTCF_ValidOvfl; if( rc ){ @@ -64290,7 +64924,7 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( assert( c==0 ); *pRes = 0; rc = SQLITE_OK; - pCur->aiIdx[pCur->iPage] = (u16)idx; + pCur->ix = (u16)idx; if( pIdxKey->errCode ) rc = SQLITE_CORRUPT; goto moveto_finish; } @@ -64302,8 +64936,8 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( assert( lwr==upr+1 || (pPage->intKey && !pPage->leaf) ); assert( pPage->isInit ); if( pPage->leaf ){ - assert( pCur->aiIdx[pCur->iPage]apPage[pCur->iPage]->nCell ); - pCur->aiIdx[pCur->iPage] = (u16)idx; + assert( pCur->ixapPage[pCur->iPage]->nCell ); + pCur->ix = (u16)idx; *pRes = c; rc = SQLITE_OK; goto moveto_finish; @@ -64314,7 +64948,7 @@ moveto_next_layer: }else{ chldPg = get4byte(findCell(pPage, lwr)); } - pCur->aiIdx[pCur->iPage] = (u16)lwr; + pCur->ix = (u16)lwr; rc = moveToChild(pCur, chldPg); if( rc ) break; } @@ -64365,10 +64999,12 @@ SQLITE_PRIVATE i64 sqlite3BtreeRowCountEst(BtCursor *pCur){ } /* -** Advance the cursor to the next entry in the database. If -** successful then set *pRes=0. If the cursor -** was already pointing to the last entry in the database before -** this routine was called, then set *pRes=1. +** Advance the cursor to the next entry in the database. +** Return value: +** +** SQLITE_OK success +** SQLITE_DONE cursor is already pointing at the last element +** otherwise some kind of error occurred ** ** The main entry point is sqlite3BtreeNext(). That routine is optimized ** for the common case of merely incrementing the cell counter BtCursor.aiIdx @@ -64376,23 +65012,19 @@ SQLITE_PRIVATE i64 sqlite3BtreeRowCountEst(BtCursor *pCur){ ** routine is called when it is necessary to move to a different page or ** to restore the cursor. ** -** The calling function will set *pRes to 0 or 1. The initial *pRes value -** will be 1 if the cursor being stepped corresponds to an SQL index and -** if this routine could have been skipped if that SQL index had been -** a unique index. Otherwise the caller will have set *pRes to zero. -** Zero is the common case. The btree implementation is free to use the -** initial *pRes value as a hint to improve performance, but the current -** SQLite btree implementation does not. (Note that the comdb2 btree -** implementation does use this hint, however.) +** If bit 0x01 of the F argument in sqlite3BtreeNext(C,F) is 1, then the +** cursor corresponds to an SQL index and this routine could have been +** skipped if the SQL index had been a unique index. The F argument +** is a hint to the implement. SQLite btree implementation does not use +** this hint, but COMDB2 does. */ -static SQLITE_NOINLINE int btreeNext(BtCursor *pCur, int *pRes){ +static SQLITE_NOINLINE int btreeNext(BtCursor *pCur){ int rc; int idx; MemPage *pPage; assert( cursorOwnsBtShared(pCur) ); assert( pCur->skipNext==0 || pCur->eState!=CURSOR_VALID ); - assert( *pRes==0 ); if( pCur->eState!=CURSOR_VALID ){ assert( (pCur->curFlags & BTCF_ValidOvfl)==0 ); rc = restoreCursorPosition(pCur); @@ -64400,8 +65032,7 @@ static SQLITE_NOINLINE int btreeNext(BtCursor *pCur, int *pRes){ return rc; } if( CURSOR_INVALID==pCur->eState ){ - *pRes = 1; - return SQLITE_OK; + return SQLITE_DONE; } if( pCur->skipNext ){ assert( pCur->eState==CURSOR_VALID || pCur->eState==CURSOR_SKIPNEXT ); @@ -64415,7 +65046,7 @@ static SQLITE_NOINLINE int btreeNext(BtCursor *pCur, int *pRes){ } pPage = pCur->apPage[pCur->iPage]; - idx = ++pCur->aiIdx[pCur->iPage]; + idx = ++pCur->ix; assert( pPage->isInit ); /* If the database file is corrupt, it is possible for the value of idx @@ -64433,15 +65064,14 @@ static SQLITE_NOINLINE int btreeNext(BtCursor *pCur, int *pRes){ } do{ if( pCur->iPage==0 ){ - *pRes = 1; pCur->eState = CURSOR_INVALID; - return SQLITE_OK; + return SQLITE_DONE; } moveToParent(pCur); pPage = pCur->apPage[pCur->iPage]; - }while( pCur->aiIdx[pCur->iPage]>=pPage->nCell ); + }while( pCur->ix>=pPage->nCell ); if( pPage->intKey ){ - return sqlite3BtreeNext(pCur, pRes); + return sqlite3BtreeNext(pCur, 0); }else{ return SQLITE_OK; } @@ -64452,20 +65082,19 @@ static SQLITE_NOINLINE int btreeNext(BtCursor *pCur, int *pRes){ return moveToLeftmost(pCur); } } -SQLITE_PRIVATE int sqlite3BtreeNext(BtCursor *pCur, int *pRes){ +SQLITE_PRIVATE int sqlite3BtreeNext(BtCursor *pCur, int flags){ MemPage *pPage; + UNUSED_PARAMETER( flags ); /* Used in COMDB2 but not native SQLite */ assert( cursorOwnsBtShared(pCur) ); - assert( pRes!=0 ); - assert( *pRes==0 || *pRes==1 ); + assert( flags==0 || flags==1 ); assert( pCur->skipNext==0 || pCur->eState!=CURSOR_VALID ); pCur->info.nSize = 0; pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl); - *pRes = 0; - if( pCur->eState!=CURSOR_VALID ) return btreeNext(pCur, pRes); + if( pCur->eState!=CURSOR_VALID ) return btreeNext(pCur); pPage = pCur->apPage[pCur->iPage]; - if( (++pCur->aiIdx[pCur->iPage])>=pPage->nCell ){ - pCur->aiIdx[pCur->iPage]--; - return btreeNext(pCur, pRes); + if( (++pCur->ix)>=pPage->nCell ){ + pCur->ix--; + return btreeNext(pCur); } if( pPage->leaf ){ return SQLITE_OK; @@ -64475,10 +65104,12 @@ SQLITE_PRIVATE int sqlite3BtreeNext(BtCursor *pCur, int *pRes){ } /* -** Step the cursor to the back to the previous entry in the database. If -** successful then set *pRes=0. If the cursor -** was already pointing to the first entry in the database before -** this routine was called, then set *pRes=1. +** Step the cursor to the back to the previous entry in the database. +** Return values: +** +** SQLITE_OK success +** SQLITE_DONE the cursor is already on the first element of the table +** otherwise some kind of error occurred ** ** The main entry point is sqlite3BtreePrevious(). That routine is optimized ** for the common case of merely decrementing the cell counter BtCursor.aiIdx @@ -64486,22 +65117,17 @@ SQLITE_PRIVATE int sqlite3BtreeNext(BtCursor *pCur, int *pRes){ ** helper routine is called when it is necessary to move to a different page ** or to restore the cursor. ** -** The calling function will set *pRes to 0 or 1. The initial *pRes value -** will be 1 if the cursor being stepped corresponds to an SQL index and -** if this routine could have been skipped if that SQL index had been -** a unique index. Otherwise the caller will have set *pRes to zero. -** Zero is the common case. The btree implementation is free to use the -** initial *pRes value as a hint to improve performance, but the current -** SQLite btree implementation does not. (Note that the comdb2 btree -** implementation does use this hint, however.) +** If bit 0x01 of the F argument to sqlite3BtreePrevious(C,F) is 1, then +** the cursor corresponds to an SQL index and this routine could have been +** skipped if the SQL index had been a unique index. The F argument is a +** hint to the implement. The native SQLite btree implementation does not +** use this hint, but COMDB2 does. */ -static SQLITE_NOINLINE int btreePrevious(BtCursor *pCur, int *pRes){ +static SQLITE_NOINLINE int btreePrevious(BtCursor *pCur){ int rc; MemPage *pPage; assert( cursorOwnsBtShared(pCur) ); - assert( pRes!=0 ); - assert( *pRes==0 ); assert( pCur->skipNext==0 || pCur->eState!=CURSOR_VALID ); assert( (pCur->curFlags & (BTCF_AtLast|BTCF_ValidOvfl|BTCF_ValidNKey))==0 ); assert( pCur->info.nSize==0 ); @@ -64511,8 +65137,7 @@ static SQLITE_NOINLINE int btreePrevious(BtCursor *pCur, int *pRes){ return rc; } if( CURSOR_INVALID==pCur->eState ){ - *pRes = 1; - return SQLITE_OK; + return SQLITE_DONE; } if( pCur->skipNext ){ assert( pCur->eState==CURSOR_VALID || pCur->eState==CURSOR_SKIPNEXT ); @@ -64528,47 +65153,45 @@ static SQLITE_NOINLINE int btreePrevious(BtCursor *pCur, int *pRes){ pPage = pCur->apPage[pCur->iPage]; assert( pPage->isInit ); if( !pPage->leaf ){ - int idx = pCur->aiIdx[pCur->iPage]; + int idx = pCur->ix; rc = moveToChild(pCur, get4byte(findCell(pPage, idx))); if( rc ) return rc; rc = moveToRightmost(pCur); }else{ - while( pCur->aiIdx[pCur->iPage]==0 ){ + while( pCur->ix==0 ){ if( pCur->iPage==0 ){ pCur->eState = CURSOR_INVALID; - *pRes = 1; - return SQLITE_OK; + return SQLITE_DONE; } moveToParent(pCur); } assert( pCur->info.nSize==0 ); assert( (pCur->curFlags & (BTCF_ValidOvfl))==0 ); - pCur->aiIdx[pCur->iPage]--; + pCur->ix--; pPage = pCur->apPage[pCur->iPage]; if( pPage->intKey && !pPage->leaf ){ - rc = sqlite3BtreePrevious(pCur, pRes); + rc = sqlite3BtreePrevious(pCur, 0); }else{ rc = SQLITE_OK; } } return rc; } -SQLITE_PRIVATE int sqlite3BtreePrevious(BtCursor *pCur, int *pRes){ +SQLITE_PRIVATE int sqlite3BtreePrevious(BtCursor *pCur, int flags){ assert( cursorOwnsBtShared(pCur) ); - assert( pRes!=0 ); - assert( *pRes==0 || *pRes==1 ); + assert( flags==0 || flags==1 ); assert( pCur->skipNext==0 || pCur->eState!=CURSOR_VALID ); - *pRes = 0; + UNUSED_PARAMETER( flags ); /* Used in COMDB2 but not native SQLite */ pCur->curFlags &= ~(BTCF_AtLast|BTCF_ValidOvfl|BTCF_ValidNKey); pCur->info.nSize = 0; if( pCur->eState!=CURSOR_VALID - || pCur->aiIdx[pCur->iPage]==0 + || pCur->ix==0 || pCur->apPage[pCur->iPage]->leaf==0 ){ - return btreePrevious(pCur, pRes); + return btreePrevious(pCur); } - pCur->aiIdx[pCur->iPage]--; + pCur->ix--; return SQLITE_OK; } @@ -64674,7 +65297,7 @@ static int allocateBtreePage( } testcase( iTrunk==mxPage ); if( iTrunk>mxPage || nSearch++ > n ){ - rc = SQLITE_CORRUPT_BKPT; + rc = SQLITE_CORRUPT_PGNO(pPrevTrunk ? pPrevTrunk->pgno : 1); }else{ rc = btreeGetUnusedPage(pBt, iTrunk, &pTrunk, 0); } @@ -64703,7 +65326,7 @@ static int allocateBtreePage( TRACE(("ALLOCATE: %d trunk - %d free pages left\n", *pPgno, n-1)); }else if( k>(u32)(pBt->usableSize/4 - 2) ){ /* Value of k is out of range. Database corruption */ - rc = SQLITE_CORRUPT_BKPT; + rc = SQLITE_CORRUPT_PGNO(iTrunk); goto end_allocate_page; #ifndef SQLITE_OMIT_AUTOVACUUM }else if( searchList @@ -64737,7 +65360,7 @@ static int allocateBtreePage( MemPage *pNewTrunk; Pgno iNewTrunk = get4byte(&pTrunk->aData[8]); if( iNewTrunk>mxPage ){ - rc = SQLITE_CORRUPT_BKPT; + rc = SQLITE_CORRUPT_PGNO(iTrunk); goto end_allocate_page; } testcase( iNewTrunk==mxPage ); @@ -64802,7 +65425,7 @@ static int allocateBtreePage( iPage = get4byte(&aData[8+closest*4]); testcase( iPage==mxPage ); if( iPage>mxPage ){ - rc = SQLITE_CORRUPT_BKPT; + rc = SQLITE_CORRUPT_PGNO(iTrunk); goto end_allocate_page; } testcase( iPage==mxPage ); @@ -65072,7 +65695,8 @@ static int clearCell( return SQLITE_OK; /* No overflow pages. Return without doing anything */ } if( pCell+pInfo->nSize-1 > pPage->aData+pPage->maskPage ){ - return SQLITE_CORRUPT_BKPT; /* Cell extends past end of page */ + /* Cell extends past end of page */ + return SQLITE_CORRUPT_PGNO(pPage->pgno); } ovflPgno = get4byte(pCell + pInfo->nSize - 4); assert( pBt->usableSize > 4 ); @@ -66167,7 +66791,7 @@ static int balance_nonroot( ** In this case, temporarily copy the cell into the aOvflSpace[] ** buffer. It will be copied out again as soon as the aSpace[] buffer ** is allocated. */ - if( pBt->btsFlags & BTS_SECURE_DELETE ){ + if( pBt->btsFlags & BTS_FAST_SECURE ){ int iOff; iOff = SQLITE_PTR_TO_INT(apDiv[i]) - SQLITE_PTR_TO_INT(pParent->aData); @@ -66890,8 +67514,8 @@ static int balance(BtCursor *pCur){ rc = balance_deeper(pPage, &pCur->apPage[1]); if( rc==SQLITE_OK ){ pCur->iPage = 1; + pCur->ix = 0; pCur->aiIdx[0] = 0; - pCur->aiIdx[1] = 0; assert( pCur->apPage[1]->nOverflow ); } }else{ @@ -67068,7 +67692,7 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( assert( pX->pKey==0 ); /* If this is an insert into a table b-tree, invalidate any incrblob ** cursors open on the row being replaced */ - invalidateIncrblobCursors(p, pX->nKey, 0); + invalidateIncrblobCursors(p, pCur->pgnoRoot, pX->nKey, 0); /* If BTREE_SAVEPOSITION is set, the cursor must already be pointing ** to a row with the same key as the new entry being inserted. */ @@ -67080,9 +67704,6 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( ** btreeMoveto() call */ if( (pCur->curFlags&BTCF_ValidNKey)!=0 && pX->nKey==pCur->info.nKey ){ loc = 0; - }else if( (pCur->curFlags&BTCF_ValidNKey)!=0 && pX->nKey>0 - && pCur->info.nKey==pX->nKey-1 ){ - loc = -1; }else if( loc==0 ){ rc = sqlite3BtreeMovetoUnpacked(pCur, 0, pX->nKey, flags!=0, &loc); if( rc ) return rc; @@ -67120,7 +67741,7 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( if( rc ) goto end_insert; assert( szNew==pPage->xCellSize(pPage, newCell) ); assert( szNew <= MX_CELL_SIZE(pBt) ); - idx = pCur->aiIdx[pCur->iPage]; + idx = pCur->ix; if( loc==0 ){ CellInfo info; assert( idxnCell ); @@ -67133,12 +67754,18 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( memcpy(newCell, oldCell, 4); } rc = clearCell(pPage, oldCell, &info); - if( info.nSize==szNew && info.nLocal==info.nPayload ){ + if( info.nSize==szNew && info.nLocal==info.nPayload + && (!ISAUTOVACUUM || szNewminLocal) + ){ /* Overwrite the old cell with the new if they are the same size. ** We could also try to do this if the old cell is smaller, then add ** the leftover space to the free list. But experiments show that ** doing that is no faster then skipping this optimization and just - ** calling dropCell() and insertCell(). */ + ** calling dropCell() and insertCell(). + ** + ** This optimization cannot be used on an autovacuum database if the + ** new entry uses overflow pages, as the insertCell() call below is + ** necessary to add the PTRMAP_OVERFLOW1 pointer-map entry. */ assert( rc==SQLITE_OK ); /* clearCell never fails when nLocal==nPayload */ if( oldCell+szNew > pPage->aDataEnd ) return SQLITE_CORRUPT_BKPT; memcpy(oldCell, newCell, szNew); @@ -67148,7 +67775,8 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( if( rc ) goto end_insert; }else if( loc<0 && pPage->nCell>0 ){ assert( pPage->leaf ); - idx = ++pCur->aiIdx[pCur->iPage]; + idx = ++pCur->ix; + pCur->curFlags &= ~BTCF_ValidNKey; }else{ assert( pPage->leaf ); } @@ -67244,12 +67872,12 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ assert( pCur->curFlags & BTCF_WriteFlag ); assert( hasSharedCacheTableLock(p, pCur->pgnoRoot, pCur->pKeyInfo!=0, 2) ); assert( !hasReadConflicts(p, pCur->pgnoRoot) ); - assert( pCur->aiIdx[pCur->iPage]apPage[pCur->iPage]->nCell ); + assert( pCur->ixapPage[pCur->iPage]->nCell ); assert( pCur->eState==CURSOR_VALID ); assert( (flags & ~(BTREE_SAVEPOSITION | BTREE_AUXDELETE))==0 ); iCellDepth = pCur->iPage; - iCellIdx = pCur->aiIdx[iCellDepth]; + iCellIdx = pCur->ix; pPage = pCur->apPage[iCellDepth]; pCell = findCell(pPage, iCellIdx); @@ -67283,8 +67911,8 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ ** sub-tree headed by the child page of the cell being deleted. This makes ** balancing the tree following the delete operation easier. */ if( !pPage->leaf ){ - int notUsed = 0; - rc = sqlite3BtreePrevious(pCur, ¬Used); + rc = sqlite3BtreePrevious(pCur, 0); + assert( rc!=SQLITE_DONE ); if( rc ) return rc; } @@ -67298,7 +67926,7 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ /* If this is a delete operation to remove a row from a table b-tree, ** invalidate any incrblob cursors open on the row being deleted. */ if( pCur->pKeyInfo==0 ){ - invalidateIncrblobCursors(p, pCur->info.nKey, 0); + invalidateIncrblobCursors(p, pCur->pgnoRoot, pCur->info.nKey, 0); } /* Make the page containing the entry to be deleted writable. Then free any @@ -67366,7 +67994,7 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ pCur->eState = CURSOR_SKIPNEXT; if( iCellIdx>=pPage->nCell ){ pCur->skipNext = -1; - pCur->aiIdx[iCellDepth] = pPage->nCell-1; + pCur->ix = pPage->nCell-1; }else{ pCur->skipNext = 1; } @@ -67625,7 +68253,7 @@ SQLITE_PRIVATE int sqlite3BtreeClearTable(Btree *p, int iTable, int *pnChange){ /* Invalidate all incrblob cursors open on table iTable (assuming iTable ** is the root of a table b-tree - if it is not, the following call is ** a no-op). */ - invalidateIncrblobCursors(p, 0, 1); + invalidateIncrblobCursors(p, (Pgno)iTable, 0, 1); rc = clearDatabasePage(pBt, (Pgno)iTable, 0, pnChange); } sqlite3BtreeLeave(p); @@ -67879,16 +68507,16 @@ SQLITE_PRIVATE int sqlite3BtreeCount(BtCursor *pCur, i64 *pnEntry){ return moveToRoot(pCur); } moveToParent(pCur); - }while ( pCur->aiIdx[pCur->iPage]>=pCur->apPage[pCur->iPage]->nCell ); + }while ( pCur->ix>=pCur->apPage[pCur->iPage]->nCell ); - pCur->aiIdx[pCur->iPage]++; + pCur->ix++; pPage = pCur->apPage[pCur->iPage]; } /* Descend to the child node of the cell that the cursor currently ** points at. This is the right-child if (iIdx==pPage->nCell). */ - iIdx = pCur->aiIdx[pCur->iPage]; + iIdx = pCur->ix; if( iIdx==pPage->nCell ){ rc = moveToChild(pCur, get4byte(&pPage->aData[pPage->hdrOffset+8])); }else{ @@ -68273,6 +68901,7 @@ static int checkTreePage( checkAppendMsg(pCheck, "Rowid %lld out of order", info.nKey); } maxKey = info.nKey; + keyCanBeEqual = 0; /* Only the first key on the page may ==maxKey */ } /* Check the content overflow list */ @@ -69645,7 +70274,7 @@ copy_finished: */ SQLITE_PRIVATE int sqlite3VdbeCheckMemInvariants(Mem *p){ /* If MEM_Dyn is set then Mem.xDel!=0. - ** Mem.xDel is might not be initialized if MEM_Dyn is clear. + ** Mem.xDel might not be initialized if MEM_Dyn is clear. */ assert( (p->flags & MEM_Dyn)==0 || p->xDel!=0 ); @@ -69658,6 +70287,35 @@ SQLITE_PRIVATE int sqlite3VdbeCheckMemInvariants(Mem *p){ /* Cannot be both MEM_Int and MEM_Real at the same time */ assert( (p->flags & (MEM_Int|MEM_Real))!=(MEM_Int|MEM_Real) ); + if( p->flags & MEM_Null ){ + /* Cannot be both MEM_Null and some other type */ + assert( (p->flags & (MEM_Int|MEM_Real|MEM_Str|MEM_Blob + |MEM_RowSet|MEM_Frame|MEM_Agg|MEM_Zero))==0 ); + + /* If MEM_Null is set, then either the value is a pure NULL (the usual + ** case) or it is a pointer set using sqlite3_bind_pointer() or + ** sqlite3_result_pointer(). If a pointer, then MEM_Term must also be + ** set. + */ + if( (p->flags & (MEM_Term|MEM_Subtype))==(MEM_Term|MEM_Subtype) ){ + /* This is a pointer type. There may be a flag to indicate what to + ** do with the pointer. */ + assert( ((p->flags&MEM_Dyn)!=0 ? 1 : 0) + + ((p->flags&MEM_Ephem)!=0 ? 1 : 0) + + ((p->flags&MEM_Static)!=0 ? 1 : 0) <= 1 ); + + /* No other bits set */ + assert( (p->flags & ~(MEM_Null|MEM_Term|MEM_Subtype + |MEM_Dyn|MEM_Ephem|MEM_Static))==0 ); + }else{ + /* A pure NULL might have other flags, such as MEM_Static, MEM_Dyn, + ** MEM_Ephem, MEM_Cleared, or MEM_Subtype */ + } + }else{ + /* The MEM_Cleared bit is only allowed on NULLs */ + assert( (p->flags & MEM_Cleared)==0 ); + } + /* The szMalloc field holds the correct memory allocation size */ assert( p->szMalloc==0 || p->szMalloc==sqlite3DbMallocSize(p->db,p->zMalloc) ); @@ -69743,26 +70401,24 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3VdbeMemGrow(Mem *pMem, int n, int bPre assert( pMem->szMalloc==0 || pMem->szMalloc==sqlite3DbMallocSize(pMem->db, pMem->zMalloc) ); - if( pMem->szMallocszMalloc>0 && pMem->z==pMem->zMalloc ){ - pMem->z = pMem->zMalloc = sqlite3DbReallocOrFree(pMem->db, pMem->z, n); - bPreserve = 0; - }else{ - if( pMem->szMalloc>0 ) sqlite3DbFree(pMem->db, pMem->zMalloc); - pMem->zMalloc = sqlite3DbMallocRaw(pMem->db, n); - } - if( pMem->zMalloc==0 ){ - sqlite3VdbeMemSetNull(pMem); - pMem->z = 0; - pMem->szMalloc = 0; - return SQLITE_NOMEM_BKPT; - }else{ - pMem->szMalloc = sqlite3DbMallocSize(pMem->db, pMem->zMalloc); - } + if( n<32 ) n = 32; + if( bPreserve && pMem->szMalloc>0 && pMem->z==pMem->zMalloc ){ + pMem->z = pMem->zMalloc = sqlite3DbReallocOrFree(pMem->db, pMem->z, n); + bPreserve = 0; + }else{ + if( pMem->szMalloc>0 ) sqlite3DbFreeNN(pMem->db, pMem->zMalloc); + pMem->zMalloc = sqlite3DbMallocRaw(pMem->db, n); + } + if( pMem->zMalloc==0 ){ + sqlite3VdbeMemSetNull(pMem); + pMem->z = 0; + pMem->szMalloc = 0; + return SQLITE_NOMEM_BKPT; + }else{ + pMem->szMalloc = sqlite3DbMallocSize(pMem->db, pMem->zMalloc); } - if( bPreserve && pMem->z && pMem->z!=pMem->zMalloc ){ + if( bPreserve && pMem->z && ALWAYS(pMem->z!=pMem->zMalloc) ){ memcpy(pMem->zMalloc, pMem->z, pMem->n); } if( (pMem->flags&MEM_Dyn)!=0 ){ @@ -69959,7 +70615,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemFinalize(Mem *pMem, FuncDef *pFunc){ ctx.pFunc = pFunc; pFunc->xFinalize(&ctx); /* IMP: R-24505-23230 */ assert( (pMem->flags & MEM_Dyn)==0 ); - if( pMem->szMalloc>0 ) sqlite3DbFree(pMem->db, pMem->zMalloc); + if( pMem->szMalloc>0 ) sqlite3DbFreeNN(pMem->db, pMem->zMalloc); memcpy(pMem, &t, sizeof(t)); rc = ctx.isError; } @@ -70010,7 +70666,7 @@ static SQLITE_NOINLINE void vdbeMemClear(Mem *p){ vdbeMemClearExternAndSetNull(p); } if( p->szMalloc ){ - sqlite3DbFree(p->db, p->zMalloc); + sqlite3DbFreeNN(p->db, p->zMalloc); p->szMalloc = 0; } p->z = 0; @@ -70038,7 +70694,7 @@ SQLITE_PRIVATE void sqlite3VdbeMemRelease(Mem *p){ ** If the double is out of range of a 64-bit signed integer then ** return the closest available 64-bit signed integer. */ -static i64 doubleToInt64(double r){ +static SQLITE_NOINLINE i64 doubleToInt64(double r){ #ifdef SQLITE_OMIT_FLOATING_POINT /* When floating-point is omitted, double and int64 are the same thing */ return r; @@ -70074,6 +70730,11 @@ static i64 doubleToInt64(double r){ ** ** If pMem represents a string value, its encoding might be changed. */ +static SQLITE_NOINLINE i64 memIntValue(Mem *pMem){ + i64 value = 0; + sqlite3Atoi64(pMem->z, &value, pMem->n, pMem->enc); + return value; +} SQLITE_PRIVATE i64 sqlite3VdbeIntValue(Mem *pMem){ int flags; assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); @@ -70084,10 +70745,8 @@ SQLITE_PRIVATE i64 sqlite3VdbeIntValue(Mem *pMem){ }else if( flags & MEM_Real ){ return doubleToInt64(pMem->u.r); }else if( flags & (MEM_Str|MEM_Blob) ){ - i64 value = 0; assert( pMem->z || pMem->n==0 ); - sqlite3Atoi64(pMem->z, &value, pMem->n, pMem->enc); - return value; + return memIntValue(pMem); }else{ return 0; } @@ -70099,6 +70758,12 @@ SQLITE_PRIVATE i64 sqlite3VdbeIntValue(Mem *pMem){ ** value. If it is a string or blob, try to convert it to a double. ** If it is a NULL, return 0.0. */ +static SQLITE_NOINLINE double memRealValue(Mem *pMem){ + /* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */ + double val = (double)0; + sqlite3AtoF(pMem->z, &val, pMem->n, pMem->enc); + return val; +} SQLITE_PRIVATE double sqlite3VdbeRealValue(Mem *pMem){ assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( EIGHT_BYTE_ALIGNMENT(pMem) ); @@ -70107,10 +70772,7 @@ SQLITE_PRIVATE double sqlite3VdbeRealValue(Mem *pMem){ }else if( pMem->flags & MEM_Int ){ return (double)pMem->u.i; }else if( pMem->flags & (MEM_Str|MEM_Blob) ){ - /* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */ - double val = (double)0; - sqlite3AtoF(pMem->z, &val, pMem->n, pMem->enc); - return val; + return memRealValue(pMem); }else{ /* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */ return (double)0; @@ -70315,6 +70977,27 @@ SQLITE_PRIVATE void sqlite3VdbeMemSetInt64(Mem *pMem, i64 val){ } } +/* A no-op destructor */ +static void sqlite3NoopDestructor(void *p){ UNUSED_PARAMETER(p); } + +/* +** Set the value stored in *pMem should already be a NULL. +** Also store a pointer to go with it. +*/ +SQLITE_PRIVATE void sqlite3VdbeMemSetPointer( + Mem *pMem, + void *pPtr, + const char *zPType, + void (*xDestructor)(void*) +){ + assert( pMem->flags==MEM_Null ); + pMem->u.zPType = zPType ? zPType : ""; + pMem->z = pPtr; + pMem->flags = MEM_Null|MEM_Dyn|MEM_Subtype|MEM_Term; + pMem->eSubtype = 'p'; + pMem->xDel = xDestructor ? xDestructor : sqlite3NoopDestructor; +} + #ifndef SQLITE_OMIT_FLOATING_POINT /* ** Delete any previous value and set the value stored in *pMem to val, @@ -70735,7 +71418,7 @@ static sqlite3_value *valueNew(sqlite3 *db, struct ValueNewStat4Ctx *p){ pRec->aMem[i].db = db; } }else{ - sqlite3DbFree(db, pRec); + sqlite3DbFreeNN(db, pRec); pRec = 0; } } @@ -70847,7 +71530,7 @@ static int valueFromFunction( for(i=0; ipLeft,enc,affinity,&pVal) + if( SQLITE_OK==valueFromExpr(db,pExpr->pLeft,enc,affinity,&pVal,pCtx) && pVal!=0 ){ sqlite3VdbeMemNumerify(pVal); @@ -71046,7 +71729,7 @@ static void recordFunc( putVarint32(&aRet[1], iSerial); sqlite3VdbeSerialPut(&aRet[1+nSerial], argv[0], iSerial); sqlite3_result_blob(context, aRet, nRet, SQLITE_TRANSIENT); - sqlite3DbFree(db, aRet); + sqlite3DbFreeNN(db, aRet); } } @@ -71092,14 +71775,13 @@ static int stat4ValueFromExpr( /* Skip over any TK_COLLATE nodes */ pExpr = sqlite3ExprSkipCollate(pExpr); + assert( pExpr==0 || pExpr->op!=TK_REGISTER || pExpr->op2!=TK_VARIABLE ); if( !pExpr ){ pVal = valueNew(db, pAlloc); if( pVal ){ sqlite3VdbeMemSetNull((Mem*)pVal); } - }else if( pExpr->op==TK_VARIABLE - || NEVER(pExpr->op==TK_REGISTER && pExpr->op2==TK_VARIABLE) - ){ + }else if( pExpr->op==TK_VARIABLE && (db->flags & SQLITE_EnableQPSG)==0 ){ Vdbe *v; int iBindVar = pExpr->iColumn; sqlite3VdbeSetVarmask(pParse->pVdbe, iBindVar); @@ -71107,9 +71789,7 @@ static int stat4ValueFromExpr( pVal = valueNew(db, pAlloc); if( pVal ){ rc = sqlite3VdbeMemCopy((Mem*)pVal, &v->aVar[iBindVar-1]); - if( rc==SQLITE_OK ){ - sqlite3ValueApplyAffinity(pVal, affinity, ENC(db)); - } + sqlite3ValueApplyAffinity(pVal, affinity, ENC(db)); pVal->db = pParse->db; } } @@ -71273,7 +71953,7 @@ SQLITE_PRIVATE void sqlite3Stat4ProbeFree(UnpackedRecord *pRec){ sqlite3VdbeMemRelease(&aMem[i]); } sqlite3KeyInfoUnref(pRec->pKeyInfo); - sqlite3DbFree(db, pRec); + sqlite3DbFreeNN(db, pRec); } } #endif /* ifdef SQLITE_ENABLE_STAT4 */ @@ -71297,7 +71977,7 @@ SQLITE_PRIVATE void sqlite3ValueSetStr( SQLITE_PRIVATE void sqlite3ValueFree(sqlite3_value *v){ if( !v ) return; sqlite3VdbeMemRelease((Mem *)v); - sqlite3DbFree(((Mem*)v)->db, v); + sqlite3DbFreeNN(((Mem*)v)->db, v); } /* @@ -71383,16 +72063,14 @@ SQLITE_PRIVATE void sqlite3VdbeError(Vdbe *p, const char *zFormat, ...){ /* ** Remember the SQL string for a prepared statement. */ -SQLITE_PRIVATE void sqlite3VdbeSetSql(Vdbe *p, const char *z, int n, int isPrepareV2){ - assert( isPrepareV2==1 || isPrepareV2==0 ); +SQLITE_PRIVATE void sqlite3VdbeSetSql(Vdbe *p, const char *z, int n, u8 prepFlags){ if( p==0 ) return; - if( !isPrepareV2 ) p->expmask = 0; -#if defined(SQLITE_OMIT_TRACE) && !defined(SQLITE_ENABLE_SQLLOG) - if( !isPrepareV2 ) return; -#endif + p->prepFlags = prepFlags; + if( (prepFlags & SQLITE_PREPARE_SAVESQL)==0 ){ + p->expmask = 0; + } assert( p->zSql==0 ); p->zSql = sqlite3DbStrNDup(p->db, z, n); - p->isPrepareV2 = (u8)isPrepareV2; } /* @@ -71414,8 +72092,10 @@ SQLITE_PRIVATE void sqlite3VdbeSwap(Vdbe *pA, Vdbe *pB){ zTmp = pA->zSql; pA->zSql = pB->zSql; pB->zSql = zTmp; - pB->isPrepareV2 = pA->isPrepareV2; pB->expmask = pA->expmask; + pB->prepFlags = pA->prepFlags; + memcpy(pB->aCounter, pA->aCounter, sizeof(pB->aCounter)); + pB->aCounter[SQLITE_STMTSTATUS_REPREPARE]++; } /* @@ -71571,6 +72251,9 @@ SQLITE_PRIVATE int sqlite3VdbeLoadString(Vdbe *p, int iDest, const char *zStr){ ** "s" character in zTypes[], the register is a string if the argument is ** not NULL, or OP_Null if the value is a null pointer. For each "i" character ** in zTypes[], the register is initialized to an integer. +** +** If the input string does not end with "X" then an OP_ResultRow instruction +** is generated for the values inserted. */ SQLITE_PRIVATE void sqlite3VdbeMultiLoad(Vdbe *p, int iDest, const char *zTypes, ...){ va_list ap; @@ -71580,12 +72263,15 @@ SQLITE_PRIVATE void sqlite3VdbeMultiLoad(Vdbe *p, int iDest, const char *zTypes, for(i=0; (c = zTypes[i])!=0; i++){ if( c=='s' ){ const char *z = va_arg(ap, const char*); - sqlite3VdbeAddOp4(p, z==0 ? OP_Null : OP_String8, 0, iDest++, 0, z, 0); + sqlite3VdbeAddOp4(p, z==0 ? OP_Null : OP_String8, 0, iDest+i, 0, z, 0); + }else if( c=='i' ){ + sqlite3VdbeAddOp2(p, OP_Integer, va_arg(ap, int), iDest+i); }else{ - assert( c=='i' ); - sqlite3VdbeAddOp2(p, OP_Integer, va_arg(ap, int), iDest++); + goto skip_op_resultrow; } } + sqlite3VdbeAddOp2(p, OP_ResultRow, iDest, i); +skip_op_resultrow: va_end(ap); } @@ -72140,7 +72826,7 @@ SQLITE_PRIVATE void sqlite3VdbeJumpHere(Vdbe *p, int addr){ */ static void freeEphemeralFunction(sqlite3 *db, FuncDef *pDef){ if( (pDef->funcFlags & SQLITE_FUNC_EPHEM)!=0 ){ - sqlite3DbFree(db, pDef); + sqlite3DbFreeNN(db, pDef); } } @@ -72151,11 +72837,11 @@ static void vdbeFreeOpArray(sqlite3 *, Op *, int); */ static SQLITE_NOINLINE void freeP4Mem(sqlite3 *db, Mem *p){ if( p->szMalloc ) sqlite3DbFree(db, p->zMalloc); - sqlite3DbFree(db, p); + sqlite3DbFreeNN(db, p); } static SQLITE_NOINLINE void freeP4FuncCtx(sqlite3 *db, sqlite3_context *p){ freeEphemeralFunction(db, p->pFunc); - sqlite3DbFree(db, p); + sqlite3DbFreeNN(db, p); } static void freeP4(sqlite3 *db, int p4type, void *p4){ assert( db ); @@ -72208,14 +72894,14 @@ static void freeP4(sqlite3 *db, int p4type, void *p4){ static void vdbeFreeOpArray(sqlite3 *db, Op *aOp, int nOp){ if( aOp ){ Op *pOp; - for(pOp=aOp; pOp<&aOp[nOp]; pOp++){ - if( pOp->p4type ) freeP4(db, pOp->p4type, pOp->p4.p); + for(pOp=&aOp[nOp-1]; pOp>=aOp; pOp--){ + if( pOp->p4type <= P4_FREE_IF_LE ) freeP4(db, pOp->p4type, pOp->p4.p); #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS sqlite3DbFree(db, pOp->zComment); #endif } + sqlite3DbFreeNN(db, aOp); } - sqlite3DbFree(db, aOp); } /* @@ -72888,7 +73574,7 @@ static void releaseMemArray(Mem *p, int N){ if( p->flags&(MEM_Agg|MEM_Dyn|MEM_Frame|MEM_RowSet) ){ sqlite3VdbeMemRelease(p); }else if( p->szMalloc ){ - sqlite3DbFree(db, p->zMalloc); + sqlite3DbFreeNN(db, p->zMalloc); p->szMalloc = 0; } @@ -73364,8 +74050,8 @@ SQLITE_PRIVATE void sqlite3VdbeFreeCursor(Vdbe *p, VdbeCursor *pCx){ break; } case CURTYPE_BTREE: { - if( pCx->pBtx ){ - sqlite3BtreeClose(pCx->pBtx); + if( pCx->isEphemeral ){ + if( pCx->pBtx ) sqlite3BtreeClose(pCx->pBtx); /* The pCx->pCursor will be close automatically, if it exists, by ** the call above. */ }else{ @@ -73489,17 +74175,18 @@ static void Cleanup(Vdbe *p){ ** be called on an SQL statement before sqlite3_step(). */ SQLITE_PRIVATE void sqlite3VdbeSetNumCols(Vdbe *p, int nResColumn){ - Mem *pColName; int n; sqlite3 *db = p->db; - releaseMemArray(p->aColName, p->nResColumn*COLNAME_N); - sqlite3DbFree(db, p->aColName); + if( p->nResColumn ){ + releaseMemArray(p->aColName, p->nResColumn*COLNAME_N); + sqlite3DbFree(db, p->aColName); + } n = nResColumn*COLNAME_N; p->nResColumn = (u16)nResColumn; - p->aColName = pColName = (Mem*)sqlite3DbMallocRawNN(db, sizeof(Mem)*n ); + p->aColName = (Mem*)sqlite3DbMallocRawNN(db, sizeof(Mem)*n ); if( p->aColName==0 ) return; - initMemArray(p->aColName, n, p->db, MEM_Null); + initMemArray(p->aColName, n, db, MEM_Null); } /* @@ -74149,10 +74836,10 @@ SQLITE_PRIVATE int sqlite3VdbeTransferError(Vdbe *p){ sqlite3ValueSetStr(db->pErr, -1, p->zErrMsg, SQLITE_UTF8, SQLITE_TRANSIENT); sqlite3EndBenignMalloc(); db->bBenignMalloc--; - db->errCode = rc; - }else{ - sqlite3Error(db, rc); + }else if( db->pErr ){ + sqlite3ValueSetNull(db->pErr); } + db->errCode = rc; return rc; } @@ -74259,7 +74946,6 @@ SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe *p){ } } #endif - p->iCurrentTime = 0; p->magic = VDBE_MAGIC_RESET; return p->rc & db->errMask; } @@ -74298,16 +74984,18 @@ SQLITE_PRIVATE void sqlite3VdbeDeleteAuxData(sqlite3 *db, AuxData **pp, int iOp, while( *pp ){ AuxData *pAux = *pp; if( (iOp<0) - || (pAux->iOp==iOp && (pAux->iArg>31 || !(mask & MASKBIT32(pAux->iArg)))) + || (pAux->iAuxOp==iOp + && pAux->iAuxArg>=0 + && (pAux->iAuxArg>31 || !(mask & MASKBIT32(pAux->iAuxArg)))) ){ - testcase( pAux->iArg==31 ); - if( pAux->xDelete ){ - pAux->xDelete(pAux->pAux); + testcase( pAux->iAuxArg==31 ); + if( pAux->xDeleteAux ){ + pAux->xDeleteAux(pAux->pAux); } - *pp = pAux->pNext; + *pp = pAux->pNextAux; sqlite3DbFree(db, pAux); }else{ - pp= &pAux->pNext; + pp= &pAux->pNextAux; } } } @@ -74369,7 +75057,7 @@ SQLITE_PRIVATE void sqlite3VdbeDelete(Vdbe *p){ } p->magic = VDBE_MAGIC_DEAD; p->db = 0; - sqlite3DbFree(db, p); + sqlite3DbFreeNN(db, p); } /* @@ -75059,7 +75747,6 @@ static int vdbeCompareMemString( }else{ int rc; const void *v1, *v2; - int n1, n2; Mem c1; Mem c2; sqlite3VdbeMemInit(&c1, pMem1->db, MEM_Null); @@ -75067,11 +75754,13 @@ static int vdbeCompareMemString( sqlite3VdbeMemShallowCopy(&c1, pMem1, MEM_Ephem); sqlite3VdbeMemShallowCopy(&c2, pMem2, MEM_Ephem); v1 = sqlite3ValueText((sqlite3_value*)&c1, pColl->enc); - n1 = v1==0 ? 0 : c1.n; v2 = sqlite3ValueText((sqlite3_value*)&c2, pColl->enc); - n2 = v2==0 ? 0 : c2.n; - rc = pColl->xCmp(pColl->pUser, n1, v1, n2, v2); - if( (v1==0 || v2==0) && prcErr ) *prcErr = SQLITE_NOMEM_BKPT; + if( (v1==0 || v2==0) ){ + if( prcErr ) *prcErr = SQLITE_NOMEM_BKPT; + rc = 0; + }else{ + rc = pColl->xCmp(pColl->pUser, c1.n, v1, c2.n, v2); + } sqlite3VdbeMemRelease(&c1); sqlite3VdbeMemRelease(&c2); return rc; @@ -75856,6 +76545,13 @@ SQLITE_PRIVATE sqlite3 *sqlite3VdbeDb(Vdbe *v){ return v->db; } +/* +** Return the SQLITE_PREPARE flags for a Vdbe. +*/ +SQLITE_PRIVATE u8 sqlite3VdbePrepareFlags(Vdbe *v){ + return v->prepFlags; +} + /* ** Return a pointer to an sqlite3_value structure containing the value bound ** parameter iVar of VM v. Except, if the value is an SQL NULL, return @@ -75868,6 +76564,7 @@ SQLITE_PRIVATE sqlite3_value *sqlite3VdbeGetBoundValue(Vdbe *v, int iVar, u8 aff assert( iVar>0 ); if( v ){ Mem *pMem = &v->aVar[iVar-1]; + assert( (v->db->flags & SQLITE_EnableQPSG)==0 ); if( 0==(pMem->flags & MEM_Null) ){ sqlite3_value *pRet = sqlite3ValueNew(v->db); if( pRet ){ @@ -75887,6 +76584,7 @@ SQLITE_PRIVATE sqlite3_value *sqlite3VdbeGetBoundValue(Vdbe *v, int iVar, u8 aff */ SQLITE_PRIVATE void sqlite3VdbeSetVarmask(Vdbe *v, int iVar){ assert( iVar>0 ); + assert( (v->db->flags & SQLITE_EnableQPSG)==0 ); if( iVar>=32 ){ v->expmask |= 0x80000000; }else{ @@ -75894,6 +76592,28 @@ SQLITE_PRIVATE void sqlite3VdbeSetVarmask(Vdbe *v, int iVar){ } } +/* +** Cause a function to throw an error if it was call from OP_PureFunc +** rather than OP_Function. +** +** OP_PureFunc means that the function must be deterministic, and should +** throw an error if it is given inputs that would make it non-deterministic. +** This routine is invoked by date/time functions that use non-deterministic +** features such as 'now'. +*/ +SQLITE_PRIVATE int sqlite3NotPureFunc(sqlite3_context *pCtx){ +#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 + if( pCtx->pVdbe==0 ) return 1; +#endif + if( pCtx->pVdbe->aOp[pCtx->iOp].opcode==OP_PureFunc ){ + sqlite3_result_error(pCtx, + "non-deterministic function in index expression or CHECK constraint", + -1); + return 0; + } + return 1; +} + #ifndef SQLITE_OMIT_VIRTUALTABLE /* ** Transfer error message text from an sqlite3_vtab.zErrMsg (text stored @@ -75928,7 +76648,7 @@ static void vdbeFreeUnpacked(sqlite3 *db, int nField, UnpackedRecord *p){ Mem *pMem = &p->aMem[i]; if( pMem->zMalloc ) sqlite3VdbeMemRelease(pMem); } - sqlite3DbFree(db, p); + sqlite3DbFreeNN(db, p); } } #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ @@ -75995,7 +76715,7 @@ SQLITE_PRIVATE void sqlite3VdbePreUpdateHook( for(i=0; inField; i++){ sqlite3VdbeMemRelease(&preupdate.aNew[i]); } - sqlite3DbFree(db, preupdate.aNew); + sqlite3DbFreeNN(db, preupdate.aNew); } } #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ @@ -76158,7 +76878,7 @@ SQLITE_API int sqlite3_clear_bindings(sqlite3_stmt *pStmt){ sqlite3VdbeMemRelease(&p->aVar[i]); p->aVar[i].flags = MEM_Null; } - assert( p->isPrepareV2 || p->expmask==0 ); + assert( (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 || p->expmask==0 ); if( p->expmask ){ p->expired = 1; } @@ -76203,6 +76923,19 @@ SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value *pVal){ Mem *pMem = (Mem*)pVal; return ((pMem->flags & MEM_Subtype) ? pMem->eSubtype : 0); } +SQLITE_API void *sqlite3_value_pointer(sqlite3_value *pVal, const char *zPType){ + Mem *p = (Mem*)pVal; + if( (p->flags&(MEM_TypeMask|MEM_Term|MEM_Subtype)) == + (MEM_Null|MEM_Term|MEM_Subtype) + && zPType!=0 + && p->eSubtype=='p' + && strcmp(p->u.zPType, zPType)==0 + ){ + return (void*)p->z; + }else{ + return 0; + } +} SQLITE_API const unsigned char *sqlite3_value_text(sqlite3_value *pVal){ return (const unsigned char *)sqlite3ValueText(pVal, SQLITE_UTF8); } @@ -76381,6 +77114,18 @@ SQLITE_API void sqlite3_result_null(sqlite3_context *pCtx){ assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); sqlite3VdbeMemSetNull(pCtx->pOut); } +SQLITE_API void sqlite3_result_pointer( + sqlite3_context *pCtx, + void *pPtr, + const char *zPType, + void (*xDestructor)(void*) +){ + Mem *pOut = pCtx->pOut; + assert( sqlite3_mutex_held(pOut->db->mutex) ); + sqlite3VdbeMemRelease(pOut); + pOut->flags = MEM_Null; + sqlite3VdbeMemSetPointer(pOut, pPtr, zPType, xDestructor); +} SQLITE_API void sqlite3_result_subtype(sqlite3_context *pCtx, unsigned int eSubtype){ Mem *pOut = pCtx->pOut; assert( sqlite3_mutex_held(pOut->db->mutex) ); @@ -76637,8 +77382,11 @@ end_of_step: || (rc&0xff)==SQLITE_BUSY || rc==SQLITE_MISUSE ); assert( (p->rc!=SQLITE_ROW && p->rc!=SQLITE_DONE) || p->rc==p->rcApp ); - if( p->isPrepareV2 && rc!=SQLITE_ROW && rc!=SQLITE_DONE ){ - /* If this statement was prepared using sqlite3_prepare_v2(), and an + if( (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 + && rc!=SQLITE_ROW + && rc!=SQLITE_DONE + ){ + /* If this statement was prepared using saved SQL and an ** error has occurred, then return the error code in p->rc to the ** caller. Set the error code in the database handle to the same value. */ @@ -76808,6 +77556,12 @@ SQLITE_API void *sqlite3_aggregate_context(sqlite3_context *p, int nByte){ /* ** Return the auxiliary data pointer, if any, for the iArg'th argument to ** the user-function defined by pCtx. +** +** The left-most argument is 0. +** +** Undocumented behavior: If iArg is negative then access a cache of +** auxiliary data pointers that is available to all functions within a +** single prepared statement. The iArg values must match. */ SQLITE_API void *sqlite3_get_auxdata(sqlite3_context *pCtx, int iArg){ AuxData *pAuxData; @@ -76818,17 +77572,24 @@ SQLITE_API void *sqlite3_get_auxdata(sqlite3_context *pCtx, int iArg){ #else assert( pCtx->pVdbe!=0 ); #endif - for(pAuxData=pCtx->pVdbe->pAuxData; pAuxData; pAuxData=pAuxData->pNext){ - if( pAuxData->iOp==pCtx->iOp && pAuxData->iArg==iArg ) break; + for(pAuxData=pCtx->pVdbe->pAuxData; pAuxData; pAuxData=pAuxData->pNextAux){ + if( pAuxData->iAuxArg==iArg && (pAuxData->iAuxOp==pCtx->iOp || iArg<0) ){ + return pAuxData->pAux; + } } - - return (pAuxData ? pAuxData->pAux : 0); + return 0; } /* ** Set the auxiliary data pointer and delete function, for the iArg'th ** argument to the user-function defined by pCtx. Any previous value is ** deleted by calling the delete function specified when it was set. +** +** The left-most argument is 0. +** +** Undocumented behavior: If iArg is negative then make the data available +** to all functions within the current prepared statement using iArg as an +** access code. */ SQLITE_API void sqlite3_set_auxdata( sqlite3_context *pCtx, @@ -76840,33 +77601,34 @@ SQLITE_API void sqlite3_set_auxdata( Vdbe *pVdbe = pCtx->pVdbe; assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); - if( iArg<0 ) goto failed; #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 if( pVdbe==0 ) goto failed; #else assert( pVdbe!=0 ); #endif - for(pAuxData=pVdbe->pAuxData; pAuxData; pAuxData=pAuxData->pNext){ - if( pAuxData->iOp==pCtx->iOp && pAuxData->iArg==iArg ) break; + for(pAuxData=pVdbe->pAuxData; pAuxData; pAuxData=pAuxData->pNextAux){ + if( pAuxData->iAuxArg==iArg && (pAuxData->iAuxOp==pCtx->iOp || iArg<0) ){ + break; + } } if( pAuxData==0 ){ pAuxData = sqlite3DbMallocZero(pVdbe->db, sizeof(AuxData)); if( !pAuxData ) goto failed; - pAuxData->iOp = pCtx->iOp; - pAuxData->iArg = iArg; - pAuxData->pNext = pVdbe->pAuxData; + pAuxData->iAuxOp = pCtx->iOp; + pAuxData->iAuxArg = iArg; + pAuxData->pNextAux = pVdbe->pAuxData; pVdbe->pAuxData = pAuxData; if( pCtx->fErrorOrAux==0 ){ pCtx->isError = 0; pCtx->fErrorOrAux = 1; } - }else if( pAuxData->xDelete ){ - pAuxData->xDelete(pAuxData->pAux); + }else if( pAuxData->xDeleteAux ){ + pAuxData->xDeleteAux(pAuxData->pAux); } pAuxData->pAux = pAux; - pAuxData->xDelete = xDelete; + pAuxData->xDeleteAux = xDelete; return; failed: @@ -77263,7 +78025,7 @@ static int vdbeUnbind(Vdbe *p, int i){ ** as if there had been a schema change, on the first sqlite3_step() call ** following any change to the bindings of that parameter. */ - assert( p->isPrepareV2 || p->expmask==0 ); + assert( (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 || p->expmask==0 ); if( p->expmask!=0 && (p->expmask & (i>=31 ? 0x80000000 : (u32)1<expired = 1; } @@ -77293,8 +78055,10 @@ static int bindText( if( rc==SQLITE_OK && encoding!=0 ){ rc = sqlite3VdbeChangeEncoding(pVar, ENC(p->db)); } - sqlite3Error(p->db, rc); - rc = sqlite3ApiExit(p->db, rc); + if( rc ){ + sqlite3Error(p->db, rc); + rc = sqlite3ApiExit(p->db, rc); + } } sqlite3_mutex_leave(p->db->mutex); }else if( xDel!=SQLITE_STATIC && xDel!=SQLITE_TRANSIENT ){ @@ -77365,6 +78129,24 @@ SQLITE_API int sqlite3_bind_null(sqlite3_stmt *pStmt, int i){ } return rc; } +SQLITE_API int sqlite3_bind_pointer( + sqlite3_stmt *pStmt, + int i, + void *pPtr, + const char *zPTtype, + void (*xDestructor)(void*) +){ + int rc; + Vdbe *p = (Vdbe*)pStmt; + rc = vdbeUnbind(p, i); + if( rc==SQLITE_OK ){ + sqlite3VdbeMemSetPointer(&p->aVar[i-1], pPtr, zPTtype, xDestructor); + sqlite3_mutex_leave(p->db->mutex); + }else if( xDestructor ){ + xDestructor(pPtr); + } + return rc; +} SQLITE_API int sqlite3_bind_text( sqlite3_stmt *pStmt, int i, @@ -77527,11 +78309,11 @@ SQLITE_API int sqlite3_transfer_bindings(sqlite3_stmt *pFromStmt, sqlite3_stmt * if( pFrom->nVar!=pTo->nVar ){ return SQLITE_ERROR; } - assert( pTo->isPrepareV2 || pTo->expmask==0 ); + assert( (pTo->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 || pTo->expmask==0 ); if( pTo->expmask ){ pTo->expired = 1; } - assert( pFrom->isPrepareV2 || pFrom->expmask==0 ); + assert( (pFrom->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 || pFrom->expmask==0 ); if( pFrom->expmask ){ pFrom->expired = 1; } @@ -77601,8 +78383,19 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt *pStmt, int op, int resetFlag){ return 0; } #endif - v = pVdbe->aCounter[op]; - if( resetFlag ) pVdbe->aCounter[op] = 0; + if( op==SQLITE_STMTSTATUS_MEMUSED ){ + sqlite3 *db = pVdbe->db; + sqlite3_mutex_enter(db->mutex); + v = 0; + db->pnBytesFreed = (int*)&v; + sqlite3VdbeClearObject(db, pVdbe); + sqlite3DbFree(db, pVdbe); + db->pnBytesFreed = 0; + sqlite3_mutex_leave(db->mutex); + }else{ + v = pVdbe->aCounter[op]; + if( resetFlag ) pVdbe->aCounter[op] = 0; + } return (int)v; } @@ -78581,6 +79374,7 @@ static void registerTrace(int iReg, Mem *p){ printf("REG[%d] = ", iReg); memTracePrint(p); printf("\n"); + sqlite3VdbeCheckMemInvariants(p); } #endif @@ -78756,7 +79550,7 @@ SQLITE_PRIVATE int sqlite3VdbeExec( int iCompare = 0; /* Result of last comparison */ unsigned nVmStep = 0; /* Number of virtual machine steps */ #ifndef SQLITE_OMIT_PROGRESS_CALLBACK - unsigned nProgressLimit = 0;/* Invoke xProgress() when nVmStep reaches this */ + unsigned nProgressLimit; /* Invoke xProgress() when nVmStep reaches this */ #endif Mem *aMem = p->aMem; /* Copy of p->aMem */ Mem *pIn1 = 0; /* 1st input operand */ @@ -78788,6 +79582,8 @@ SQLITE_PRIVATE int sqlite3VdbeExec( u32 iPrior = p->aCounter[SQLITE_STMTSTATUS_VM_STEP]; assert( 0 < db->nProgressOps ); nProgressLimit = db->nProgressOps - (iPrior % db->nProgressOps); + }else{ + nProgressLimit = 0xffffffff; } #endif #ifdef SQLITE_DEBUG @@ -78947,7 +79743,7 @@ jump_to_p2_and_check_for_interrupt: pOp = &aOp[pOp->p2 - 1]; /* Opcodes that are used as the bottom of a loop (OP_Next, OP_Prev, - ** OP_VNext, OP_RowSetNext, or OP_SorterNext) all jump here upon + ** OP_VNext, or OP_SorterNext) all jump here upon ** completion. Check to see if sqlite3_interrupt() has been called ** or if the progress callback needs to be invoked. ** @@ -78965,7 +79761,7 @@ check_for_interrupt: ** If the progress callback returns non-zero, exit the virtual machine with ** a return code SQLITE_ABORT. */ - if( db->xProgress!=0 && nVmStep>=nProgressLimit ){ + if( nVmStep>=nProgressLimit && db->xProgress!=0 ){ assert( db->nProgressOps!=0 ); nProgressLimit = nVmStep + db->nProgressOps - (nVmStep%db->nProgressOps); if( db->xProgress(db->pProgressArg) ){ @@ -79335,7 +80131,7 @@ case OP_Null: { /* out2 */ case OP_SoftNull: { assert( pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor) ); pOut = &aMem[pOp->p1]; - pOut->flags = (pOut->flags|MEM_Null)&~MEM_Undefined; + pOut->flags = (pOut->flags&~(MEM_Undefined|MEM_AffMask))|MEM_Null; break; } @@ -79507,7 +80303,7 @@ case OP_ResultRow: { /* Run the progress counter just before returning. */ if( db->xProgress!=0 - && nVmStep>=nProgressLimit + && nVmStep>=nProgressLimit && db->xProgress(db->pProgressArg)!=0 ){ rc = SQLITE_INTERRUPT; @@ -79678,7 +80474,6 @@ case OP_Remainder: { /* same as TK_REM, in1, in2, out3 */ type2 = numericType(pIn2); pOut = &aMem[pOp->p3]; flags = pIn1->flags | pIn2->flags; - if( (flags & MEM_Null)!=0 ) goto arithmetic_result_is_null; if( (type1 & type2 & MEM_Int)!=0 ){ iA = pIn1->u.i; iB = pIn2->u.i; @@ -79702,6 +80497,8 @@ case OP_Remainder: { /* same as TK_REM, in1, in2, out3 */ } pOut->u.i = iB; MemSetTypeFlag(pOut, MEM_Int); + }else if( (flags & MEM_Null)!=0 ){ + goto arithmetic_result_is_null; }else{ bIntint = 0; fp_math: @@ -79749,7 +80546,7 @@ arithmetic_result_is_null: /* Opcode: CollSeq P1 * * P4 ** -** P4 is a pointer to a CollSeq struct. If the next call to a user function +** P4 is a pointer to a CollSeq object. If the next call to a user function ** or aggregate calls sqlite3GetFuncCollSeq(), this collation sequence will ** be returned. This is used by the built-in min(), max() and nullif() ** functions. @@ -79770,117 +80567,6 @@ case OP_CollSeq: { break; } -/* Opcode: Function0 P1 P2 P3 P4 P5 -** Synopsis: r[P3]=func(r[P2@P5]) -** -** Invoke a user function (P4 is a pointer to a FuncDef object that -** defines the function) with P5 arguments taken from register P2 and -** successors. The result of the function is stored in register P3. -** Register P3 must not be one of the function inputs. -** -** P1 is a 32-bit bitmask indicating whether or not each argument to the -** function was determined to be constant at compile time. If the first -** argument was constant then bit 0 of P1 is set. This is used to determine -** whether meta data associated with a user function argument using the -** sqlite3_set_auxdata() API may be safely retained until the next -** invocation of this opcode. -** -** See also: Function, AggStep, AggFinal -*/ -/* Opcode: Function P1 P2 P3 P4 P5 -** Synopsis: r[P3]=func(r[P2@P5]) -** -** Invoke a user function (P4 is a pointer to an sqlite3_context object that -** contains a pointer to the function to be run) with P5 arguments taken -** from register P2 and successors. The result of the function is stored -** in register P3. Register P3 must not be one of the function inputs. -** -** P1 is a 32-bit bitmask indicating whether or not each argument to the -** function was determined to be constant at compile time. If the first -** argument was constant then bit 0 of P1 is set. This is used to determine -** whether meta data associated with a user function argument using the -** sqlite3_set_auxdata() API may be safely retained until the next -** invocation of this opcode. -** -** SQL functions are initially coded as OP_Function0 with P4 pointing -** to a FuncDef object. But on first evaluation, the P4 operand is -** automatically converted into an sqlite3_context object and the operation -** changed to this OP_Function opcode. In this way, the initialization of -** the sqlite3_context object occurs only once, rather than once for each -** evaluation of the function. -** -** See also: Function0, AggStep, AggFinal -*/ -case OP_Function0: { - int n; - sqlite3_context *pCtx; - - assert( pOp->p4type==P4_FUNCDEF ); - n = pOp->p5; - assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) ); - assert( n==0 || (pOp->p2>0 && pOp->p2+n<=(p->nMem+1 - p->nCursor)+1) ); - assert( pOp->p3p2 || pOp->p3>=pOp->p2+n ); - pCtx = sqlite3DbMallocRawNN(db, sizeof(*pCtx) + (n-1)*sizeof(sqlite3_value*)); - if( pCtx==0 ) goto no_mem; - pCtx->pOut = 0; - pCtx->pFunc = pOp->p4.pFunc; - pCtx->iOp = (int)(pOp - aOp); - pCtx->pVdbe = p; - pCtx->argc = n; - pOp->p4type = P4_FUNCCTX; - pOp->p4.pCtx = pCtx; - pOp->opcode = OP_Function; - /* Fall through into OP_Function */ -} -case OP_Function: { - int i; - sqlite3_context *pCtx; - - assert( pOp->p4type==P4_FUNCCTX ); - pCtx = pOp->p4.pCtx; - - /* If this function is inside of a trigger, the register array in aMem[] - ** might change from one evaluation to the next. The next block of code - ** checks to see if the register array has changed, and if so it - ** reinitializes the relavant parts of the sqlite3_context object */ - pOut = &aMem[pOp->p3]; - if( pCtx->pOut != pOut ){ - pCtx->pOut = pOut; - for(i=pCtx->argc-1; i>=0; i--) pCtx->argv[i] = &aMem[pOp->p2+i]; - } - - memAboutToChange(p, pOut); -#ifdef SQLITE_DEBUG - for(i=0; iargc; i++){ - assert( memIsValid(pCtx->argv[i]) ); - REGISTER_TRACE(pOp->p2+i, pCtx->argv[i]); - } -#endif - MemSetTypeFlag(pOut, MEM_Null); - pCtx->fErrorOrAux = 0; - (*pCtx->pFunc->xSFunc)(pCtx, pCtx->argc, pCtx->argv);/* IMP: R-24505-23230 */ - - /* If the function returned an error, throw an exception */ - if( pCtx->fErrorOrAux ){ - if( pCtx->isError ){ - sqlite3VdbeError(p, "%s", sqlite3_value_text(pOut)); - rc = pCtx->isError; - } - sqlite3VdbeDeleteAuxData(db, &p->pAuxData, pCtx->iOp, pOp->p1); - if( rc ) goto abort_due_to_error; - } - - /* Copy the result of the function into register P3 */ - if( pOut->flags & (MEM_Str|MEM_Blob) ){ - sqlite3VdbeChangeEncoding(pOut, encoding); - if( sqlite3VdbeMemTooBig(pOut) ) goto too_big; - } - - REGISTER_TRACE(pOp->p3, pOut); - UPDATE_MAX_BLOBSIZE(pOut); - break; -} - /* Opcode: BitAnd P1 P2 P3 * * ** Synopsis: r[P3]=r[P1]&r[P2] ** @@ -80030,11 +80716,11 @@ case OP_RealAffinity: { /* in1 */ ** Force the value in register P1 to be the type defined by P2. ** **
    -**
  • TEXT -**
  • BLOB -**
  • NUMERIC -**
  • INTEGER -**
  • REAL +**
  • P2=='A' → BLOB +**
  • P2=='B' → TEXT +**
  • P2=='C' → NUMERIC +**
  • P2=='D' → INTEGER +**
  • P2=='E' → REAL **
** ** A NULL value is not changed by this routine. It remains NULL. @@ -80613,6 +81299,24 @@ case OP_NotNull: { /* same as TK_NOTNULL, jump, in1 */ break; } +/* Opcode: IfNullRow P1 P2 P3 * * +** Synopsis: if P1.nullRow then r[P3]=NULL, goto P2 +** +** Check the cursor P1 to see if it is currently pointing at a NULL row. +** If it is, then set register P3 to NULL and jump immediately to P2. +** If P1 is not on a NULL row, then fall through without making any +** changes. +*/ +case OP_IfNullRow: { /* jump */ + assert( pOp->p1>=0 && pOp->p1nCursor ); + assert( p->apCsr[pOp->p1]!=0 ); + if( p->apCsr[pOp->p1]->nullRow ){ + sqlite3VdbeMemSetNull(aMem + pOp->p3); + goto jump_to_p2; + } + break; +} + /* Opcode: Column P1 P2 P3 P4 P5 ** Synopsis: r[P3]=PX ** @@ -80624,7 +81328,7 @@ case OP_NotNull: { /* same as TK_NOTNULL, jump, in1 */ ** ** The value extracted is stored in register P3. ** -** If the column contains fewer than P2 fields, then extract a NULL. Or, +** If the record contains fewer than P2 fields, then extract a NULL. Or, ** if the P4 argument is a P4_MEM use the value of the P4 argument as ** the result. ** @@ -80633,7 +81337,7 @@ case OP_NotNull: { /* same as TK_NOTNULL, jump, in1 */ ** The first OP_Column against a pseudo-table after the value of the content ** register has changed should have this bit set. ** -** If the OPFLAG_LENGTHARG and OPFLAG_TYPEOFARG bits are set on P5 when +** If the OPFLAG_LENGTHARG and OPFLAG_TYPEOFARG bits are set on P5 then ** the result is guaranteed to only be used as the argument of a length() ** or typeof() function, respectively. The loading of large blobs can be ** skipped for length() and all content loading can be skipped for typeof(). @@ -80659,7 +81363,9 @@ case OP_Column: { pC = p->apCsr[pOp->p1]; p2 = pOp->p2; - /* If the cursor cache is stale, bring it up-to-date */ + /* If the cursor cache is stale (meaning it is not currently point at + ** the correct row) then bring it up-to-date by doing the necessary + ** B-Tree seek. */ rc = sqlite3VdbeCursorMoveto(&pC, &p2); if( rc ) goto abort_due_to_error; @@ -80888,24 +81594,24 @@ op_column_out: ** ** Apply affinities to a range of P2 registers starting with P1. ** -** P4 is a string that is P2 characters long. The nth character of the -** string indicates the column affinity that should be used for the nth +** P4 is a string that is P2 characters long. The N-th character of the +** string indicates the column affinity that should be used for the N-th ** memory cell in the range. */ case OP_Affinity: { const char *zAffinity; /* The affinity to be applied */ - char cAff; /* A single character of affinity */ zAffinity = pOp->p4.z; assert( zAffinity!=0 ); + assert( pOp->p2>0 ); assert( zAffinity[pOp->p2]==0 ); pIn1 = &aMem[pOp->p1]; - while( (cAff = *(zAffinity++))!=0 ){ + do{ assert( pIn1 <= &p->aMem[(p->nMem+1 - p->nCursor)] ); assert( memIsValid(pIn1) ); - applyAffinity(pIn1, cAff, encoding); + applyAffinity(pIn1, *(zAffinity++), encoding); pIn1++; - } + }while( zAffinity[0] ); break; } @@ -80916,8 +81622,8 @@ case OP_Affinity: { ** use as a data record in a database table or as a key ** in an index. The OP_Column opcode can decode the record later. ** -** P4 may be a string that is P2 characters long. The nth character of the -** string indicates the column affinity that should be used for the nth +** P4 may be a string that is P2 characters long. The N-th character of the +** string indicates the column affinity that should be used for the N-th ** field of the index key. ** ** The mapping from character to affinity is given by the SQLITE_AFF_ @@ -81076,7 +81782,6 @@ case OP_MakeRecord: { pOut->u.nZero = nZero; pOut->flags |= MEM_Zero; } - pOut->enc = SQLITE_UTF8; /* In case the blob is ever converted to text */ REGISTER_TRACE(pOp->p3, pOut); UPDATE_MAX_BLOBSIZE(pOut); break; @@ -81706,6 +82411,37 @@ open_cursor_set_hints: break; } +/* Opcode: OpenDup P1 P2 * * * +** +** Open a new cursor P1 that points to the same ephemeral table as +** cursor P2. The P2 cursor must have been opened by a prior OP_OpenEphemeral +** opcode. Only ephemeral cursors may be duplicated. +** +** Duplicate ephemeral cursors are used for self-joins of materialized views. +*/ +case OP_OpenDup: { + VdbeCursor *pOrig; /* The original cursor to be duplicated */ + VdbeCursor *pCx; /* The new cursor */ + + pOrig = p->apCsr[pOp->p2]; + assert( pOrig->pBtx!=0 ); /* Only ephemeral cursors can be duplicated */ + + pCx = allocateCursor(p, pOp->p1, pOrig->nField, -1, CURTYPE_BTREE); + if( pCx==0 ) goto no_mem; + pCx->nullRow = 1; + pCx->isEphemeral = 1; + pCx->pKeyInfo = pOrig->pKeyInfo; + pCx->isTable = pOrig->isTable; + rc = sqlite3BtreeCursor(pOrig->pBtx, MASTER_ROOT, BTREE_WRCSR, + pCx->pKeyInfo, pCx->uc.pCursor); + /* The sqlite3BtreeCursor() routine can only fail for the first cursor + ** opened for a database. Since there is already an open cursor when this + ** opcode is run, the sqlite3BtreeCursor() cannot fail */ + assert( rc==SQLITE_OK ); + break; +} + + /* Opcode: OpenEphemeral P1 P2 * P4 P5 ** Synopsis: nColumn=P2 ** @@ -82111,8 +82847,15 @@ case OP_SeekGT: { /* jump, in3 */ if( oc>=OP_SeekGE ){ assert( oc==OP_SeekGE || oc==OP_SeekGT ); if( res<0 || (res==0 && oc==OP_SeekGT) ){ res = 0; - rc = sqlite3BtreeNext(pC->uc.pCursor, &res); - if( rc!=SQLITE_OK ) goto abort_due_to_error; + rc = sqlite3BtreeNext(pC->uc.pCursor, 0); + if( rc!=SQLITE_OK ){ + if( rc==SQLITE_DONE ){ + rc = SQLITE_OK; + res = 1; + }else{ + goto abort_due_to_error; + } + } }else{ res = 0; } @@ -82120,8 +82863,15 @@ case OP_SeekGT: { /* jump, in3 */ assert( oc==OP_SeekLT || oc==OP_SeekLE ); if( res>0 || (res==0 && oc==OP_SeekLT) ){ res = 0; - rc = sqlite3BtreePrevious(pC->uc.pCursor, &res); - if( rc!=SQLITE_OK ) goto abort_due_to_error; + rc = sqlite3BtreePrevious(pC->uc.pCursor, 0); + if( rc!=SQLITE_OK ){ + if( rc==SQLITE_DONE ){ + rc = SQLITE_OK; + res = 1; + }else{ + goto abort_due_to_error; + } + } }else{ /* res might be negative because the table is empty. Check to ** see if this is the case. @@ -82241,10 +82991,12 @@ case OP_Found: { /* jump, in3 */ pIdxKey = &r; pFree = 0; }else{ + assert( pIn3->flags & MEM_Blob ); + rc = ExpandBlob(pIn3); + assert( rc==SQLITE_OK || rc==SQLITE_NOMEM ); + if( rc ) goto no_mem; pFree = pIdxKey = sqlite3VdbeAllocUnpackedRecord(pC->pKeyInfo); if( pIdxKey==0 ) goto no_mem; - assert( pIn3->flags & MEM_Blob ); - (void)ExpandBlob(pIn3); sqlite3VdbeRecordUnpack(pC->pKeyInfo, pIn3->n, pIn3->z, pIdxKey); } pIdxKey->default_rc = 0; @@ -82261,7 +83013,7 @@ case OP_Found: { /* jump, in3 */ } } rc = sqlite3BtreeMovetoUnpacked(pC->uc.pCursor, pIdxKey, 0, 0, &res); - if( pFree ) sqlite3DbFree(db, pFree); + if( pFree ) sqlite3DbFreeNN(db, pFree); if( rc!=SQLITE_OK ){ goto abort_due_to_error; } @@ -83225,12 +83977,10 @@ case OP_Rewind: { /* jump */ */ case OP_SorterNext: { /* jump */ VdbeCursor *pC; - int res; pC = p->apCsr[pOp->p1]; assert( isSorter(pC) ); - res = 0; - rc = sqlite3VdbeSorterNext(db, pC, &res); + rc = sqlite3VdbeSorterNext(db, pC); goto next_tail; case OP_PrevIfOpen: /* jump */ case OP_NextIfOpen: /* jump */ @@ -83241,12 +83991,9 @@ case OP_Next: /* jump */ assert( pOp->p1>=0 && pOp->p1nCursor ); assert( pOp->p5aCounter) ); pC = p->apCsr[pOp->p1]; - res = pOp->p3; assert( pC!=0 ); assert( pC->deferredMoveto==0 ); assert( pC->eCurType==CURTYPE_BTREE ); - assert( res==0 || (res==1 && pC->isTable==0) ); - testcase( res==1 ); assert( pOp->opcode!=OP_Next || pOp->p4.xAdvance==sqlite3BtreeNext ); assert( pOp->opcode!=OP_Prev || pOp->p4.xAdvance==sqlite3BtreePrevious ); assert( pOp->opcode!=OP_NextIfOpen || pOp->p4.xAdvance==sqlite3BtreeNext ); @@ -83261,21 +84008,21 @@ case OP_Next: /* jump */ || pC->seekOp==OP_SeekLT || pC->seekOp==OP_SeekLE || pC->seekOp==OP_Last ); - rc = pOp->p4.xAdvance(pC->uc.pCursor, &res); + rc = pOp->p4.xAdvance(pC->uc.pCursor, pOp->p3); next_tail: pC->cacheStatus = CACHE_STALE; - VdbeBranchTaken(res==0,2); - if( rc ) goto abort_due_to_error; - if( res==0 ){ + VdbeBranchTaken(rc==SQLITE_OK,2); + if( rc==SQLITE_OK ){ pC->nullRow = 0; p->aCounter[pOp->p5]++; #ifdef SQLITE_TEST sqlite3_search_count++; #endif goto jump_to_p2_and_check_for_interrupt; - }else{ - pC->nullRow = 1; } + if( rc!=SQLITE_DONE ) goto abort_due_to_error; + rc = SQLITE_OK; + pC->nullRow = 1; goto check_for_interrupt; } @@ -83386,8 +84133,8 @@ case OP_IdxDelete: { break; } -/* Opcode: Seek P1 * P3 P4 * -** Synopsis: Move P3 to P1.rowid +/* Opcode: DeferredSeek P1 * P3 P4 * +** Synopsis: Move P3 to P1.rowid if needed ** ** P1 is an open index cursor and P3 is a cursor on the corresponding ** table. This opcode does a deferred seek of the P3 table cursor @@ -83414,11 +84161,11 @@ case OP_IdxDelete: { ** ** See also: Rowid, MakeRecord. */ -case OP_Seek: -case OP_IdxRowid: { /* out2 */ - VdbeCursor *pC; /* The P1 index cursor */ - VdbeCursor *pTabCur; /* The P2 table cursor (OP_Seek only) */ - i64 rowid; /* Rowid that P1 current points to */ +case OP_DeferredSeek: +case OP_IdxRowid: { /* out2 */ + VdbeCursor *pC; /* The P1 index cursor */ + VdbeCursor *pTabCur; /* The P2 table cursor (OP_DeferredSeek only) */ + i64 rowid; /* Rowid that P1 current points to */ assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; @@ -83444,7 +84191,7 @@ case OP_IdxRowid: { /* out2 */ if( rc!=SQLITE_OK ){ goto abort_due_to_error; } - if( pOp->opcode==OP_Seek ){ + if( pOp->opcode==OP_DeferredSeek ){ assert( pOp->p3>=0 && pOp->p3nCursor ); pTabCur = p->apCsr[pOp->p3]; assert( pTabCur!=0 ); @@ -83571,10 +84318,17 @@ case OP_IdxGE: { /* jump */ ** might be moved into the newly deleted root page in order to keep all ** root pages contiguous at the beginning of the database. The former ** value of the root page that moved - its value before the move occurred - -** is stored in register P2. If no page -** movement was required (because the table being dropped was already -** the last one in the database) then a zero is stored in register P2. -** If AUTOVACUUM is disabled then a zero is stored in register P2. +** is stored in register P2. If no page movement was required (because the +** table being dropped was already the last one in the database) then a +** zero is stored in register P2. If AUTOVACUUM is disabled then a zero +** is stored in register P2. +** +** This opcode throws an error if there are any active reader VMs when +** it is invoked. This is done to avoid the difficulty associated with +** updating existing cursors when a root page is moved in an AUTOVACUUM +** database. This error is thrown even if the database is not an AUTOVACUUM +** db in order to avoid introducing an incompatibility between autovacuum +** and non-autovacuum modes. ** ** See also: Clear */ @@ -83779,7 +84533,7 @@ case OP_ParseSchema: { assert( !db->mallocFailed ); rc = sqlite3_exec(db, zSql, sqlite3InitCallback, &initData, 0); if( rc==SQLITE_OK ) rc = initData.rc; - sqlite3DbFree(db, zSql); + sqlite3DbFreeNN(db, zSql); db->init.busy = 0; } } @@ -83907,7 +84661,7 @@ case OP_IntegrityCk: { /* Opcode: RowSetAdd P1 P2 * * * ** Synopsis: rowset(P1)=r[P2] ** -** Insert the integer value held by register P2 into a boolean index +** Insert the integer value held by register P2 into a RowSet object ** held in register P1. ** ** An assertion fails if P2 is not an integer. @@ -83927,8 +84681,9 @@ case OP_RowSetAdd: { /* in1, in2 */ /* Opcode: RowSetRead P1 P2 P3 * * ** Synopsis: r[P3]=rowset(P1) ** -** Extract the smallest value from boolean index P1 and put that value into -** register P3. Or, if boolean index P1 is initially empty, leave P3 +** Extract the smallest value from the RowSet object in P1 +** and put that value into register P3. +** Or, if RowSet object P1 is initially empty, leave P3 ** unchanged and jump to instruction P2. */ case OP_RowSetRead: { /* jump, in1, out3 */ @@ -83959,15 +84714,14 @@ case OP_RowSetRead: { /* jump, in1, out3 */ ** integer in P3 into the RowSet and continue on to the ** next opcode. ** -** The RowSet object is optimized for the case where successive sets -** of integers, where each set contains no duplicates. Each set -** of values is identified by a unique P4 value. The first set -** must have P4==0, the final set P4=-1. P4 must be either -1 or -** non-negative. For non-negative values of P4 only the lower 4 -** bits are significant. +** The RowSet object is optimized for the case where sets of integers +** are inserted in distinct phases, which each set contains no duplicates. +** Each set is identified by a unique P4 value. The first set +** must have P4==0, the final set must have P4==-1, and for all other sets +** must have P4>0. ** ** This allows optimizations: (a) when P4==0 there is no need to test -** the rowset object for P3, as it is guaranteed not to contain it, +** the RowSet object for P3, as it is guaranteed not to contain it, ** (b) when P4==-1 there is no need to insert the value, as it will ** never be tested for, and (c) when a value that is part of set X is ** inserted, there is no need to search to see if the same value was @@ -84683,7 +85437,7 @@ case OP_Expire: { */ case OP_TableLock: { u8 isWriteLock = (u8)pOp->p3; - if( isWriteLock || 0==(db->flags&SQLITE_ReadUncommitted) ){ + if( isWriteLock || 0==(db->flags&SQLITE_ReadUncommit) ){ int p1 = pOp->p1; assert( p1>=0 && p1nDb ); assert( DbMaskTest(p->btreeMask, p1) ); @@ -85112,6 +85866,121 @@ case OP_MaxPgcnt: { /* out2 */ } #endif +/* Opcode: Function0 P1 P2 P3 P4 P5 +** Synopsis: r[P3]=func(r[P2@P5]) +** +** Invoke a user function (P4 is a pointer to a FuncDef object that +** defines the function) with P5 arguments taken from register P2 and +** successors. The result of the function is stored in register P3. +** Register P3 must not be one of the function inputs. +** +** P1 is a 32-bit bitmask indicating whether or not each argument to the +** function was determined to be constant at compile time. If the first +** argument was constant then bit 0 of P1 is set. This is used to determine +** whether meta data associated with a user function argument using the +** sqlite3_set_auxdata() API may be safely retained until the next +** invocation of this opcode. +** +** See also: Function, AggStep, AggFinal +*/ +/* Opcode: Function P1 P2 P3 P4 P5 +** Synopsis: r[P3]=func(r[P2@P5]) +** +** Invoke a user function (P4 is a pointer to an sqlite3_context object that +** contains a pointer to the function to be run) with P5 arguments taken +** from register P2 and successors. The result of the function is stored +** in register P3. Register P3 must not be one of the function inputs. +** +** P1 is a 32-bit bitmask indicating whether or not each argument to the +** function was determined to be constant at compile time. If the first +** argument was constant then bit 0 of P1 is set. This is used to determine +** whether meta data associated with a user function argument using the +** sqlite3_set_auxdata() API may be safely retained until the next +** invocation of this opcode. +** +** SQL functions are initially coded as OP_Function0 with P4 pointing +** to a FuncDef object. But on first evaluation, the P4 operand is +** automatically converted into an sqlite3_context object and the operation +** changed to this OP_Function opcode. In this way, the initialization of +** the sqlite3_context object occurs only once, rather than once for each +** evaluation of the function. +** +** See also: Function0, AggStep, AggFinal +*/ +case OP_PureFunc0: +case OP_Function0: { + int n; + sqlite3_context *pCtx; + + assert( pOp->p4type==P4_FUNCDEF ); + n = pOp->p5; + assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) ); + assert( n==0 || (pOp->p2>0 && pOp->p2+n<=(p->nMem+1 - p->nCursor)+1) ); + assert( pOp->p3p2 || pOp->p3>=pOp->p2+n ); + pCtx = sqlite3DbMallocRawNN(db, sizeof(*pCtx) + (n-1)*sizeof(sqlite3_value*)); + if( pCtx==0 ) goto no_mem; + pCtx->pOut = 0; + pCtx->pFunc = pOp->p4.pFunc; + pCtx->iOp = (int)(pOp - aOp); + pCtx->pVdbe = p; + pCtx->argc = n; + pOp->p4type = P4_FUNCCTX; + pOp->p4.pCtx = pCtx; + assert( OP_PureFunc == OP_PureFunc0+2 ); + assert( OP_Function == OP_Function0+2 ); + pOp->opcode += 2; + /* Fall through into OP_Function */ +} +case OP_PureFunc: +case OP_Function: { + int i; + sqlite3_context *pCtx; + + assert( pOp->p4type==P4_FUNCCTX ); + pCtx = pOp->p4.pCtx; + + /* If this function is inside of a trigger, the register array in aMem[] + ** might change from one evaluation to the next. The next block of code + ** checks to see if the register array has changed, and if so it + ** reinitializes the relavant parts of the sqlite3_context object */ + pOut = &aMem[pOp->p3]; + if( pCtx->pOut != pOut ){ + pCtx->pOut = pOut; + for(i=pCtx->argc-1; i>=0; i--) pCtx->argv[i] = &aMem[pOp->p2+i]; + } + + memAboutToChange(p, pOut); +#ifdef SQLITE_DEBUG + for(i=0; iargc; i++){ + assert( memIsValid(pCtx->argv[i]) ); + REGISTER_TRACE(pOp->p2+i, pCtx->argv[i]); + } +#endif + MemSetTypeFlag(pOut, MEM_Null); + pCtx->fErrorOrAux = 0; + (*pCtx->pFunc->xSFunc)(pCtx, pCtx->argc, pCtx->argv);/* IMP: R-24505-23230 */ + + /* If the function returned an error, throw an exception */ + if( pCtx->fErrorOrAux ){ + if( pCtx->isError ){ + sqlite3VdbeError(p, "%s", sqlite3_value_text(pOut)); + rc = pCtx->isError; + } + sqlite3VdbeDeleteAuxData(db, &p->pAuxData, pCtx->iOp, pOp->p1); + if( rc ) goto abort_due_to_error; + } + + /* Copy the result of the function into register P3 */ + if( pOut->flags & (MEM_Str|MEM_Blob) ){ + sqlite3VdbeChangeEncoding(pOut, encoding); + if( sqlite3VdbeMemTooBig(pOut) ) goto too_big; + } + + REGISTER_TRACE(pOp->p3, pOut); + UPDATE_MAX_BLOBSIZE(pOut); + break; +} + /* Opcode: Init P1 P2 * P4 * ** Synopsis: Start at P2 @@ -85191,6 +86060,7 @@ case OP_Init: { /* jump */ pOp->p1 = 0; } pOp->p1++; + p->aCounter[SQLITE_STMTSTATUS_RUN]++; goto jump_to_p2; } @@ -86664,9 +87534,9 @@ static int vdbeSorterCompareText( int n2; int res; - getVarint32(&p1[1], n1); n1 = (n1 - 13) / 2; - getVarint32(&p2[1], n2); n2 = (n2 - 13) / 2; - res = memcmp(v1, v2, MIN(n1, n2)); + getVarint32(&p1[1], n1); + getVarint32(&p2[1], n2); + res = memcmp(v1, v2, (MIN(n1, n2) - 13)/2); if( res==0 ){ res = n1 - n2; } @@ -86707,37 +87577,36 @@ static int vdbeSorterCompareInt( assert( (s1>0 && s1<7) || s1==8 || s1==9 ); assert( (s2>0 && s2<7) || s2==8 || s2==9 ); - if( s1>7 && s2>7 ){ + if( s1==s2 ){ + /* The two values have the same sign. Compare using memcmp(). */ + static const u8 aLen[] = {0, 1, 2, 3, 4, 6, 8, 0, 0, 0 }; + const u8 n = aLen[s1]; + int i; + res = 0; + for(i=0; i7 && s2>7 ){ res = s1 - s2; }else{ - if( s1==s2 ){ - if( (*v1 ^ *v2) & 0x80 ){ - /* The two values have different signs */ - res = (*v1 & 0x80) ? -1 : +1; - }else{ - /* The two values have the same sign. Compare using memcmp(). */ - static const u8 aLen[] = {0, 1, 2, 3, 4, 6, 8 }; - int i; - res = 0; - for(i=0; i7 ){ + res = +1; + }else if( s1>7 ){ + res = -1; }else{ - if( s2>7 ){ - res = +1; - }else if( s1>7 ){ - res = -1; - }else{ - res = s1 - s2; - } - assert( res!=0 ); + res = s1 - s2; + } + assert( res!=0 ); - if( res>0 ){ - if( *v1 & 0x80 ) res = -1; - }else{ - if( *v2 & 0x80 ) res = +1; - } + if( res>0 ){ + if( *v1 & 0x80 ) res = -1; + }else{ + if( *v2 & 0x80 ) res = +1; } } @@ -88462,9 +89331,13 @@ SQLITE_PRIVATE int sqlite3VdbeSorterRewind(const VdbeCursor *pCsr, int *pbEof){ } /* -** Advance to the next element in the sorter. +** Advance to the next element in the sorter. Return value: +** +** SQLITE_OK success +** SQLITE_DONE end of data +** otherwise some kind of error. */ -SQLITE_PRIVATE int sqlite3VdbeSorterNext(sqlite3 *db, const VdbeCursor *pCsr, int *pbEof){ +SQLITE_PRIVATE int sqlite3VdbeSorterNext(sqlite3 *db, const VdbeCursor *pCsr){ VdbeSorter *pSorter; int rc; /* Return code */ @@ -88478,21 +89351,22 @@ SQLITE_PRIVATE int sqlite3VdbeSorterNext(sqlite3 *db, const VdbeCursor *pCsr, in #if SQLITE_MAX_WORKER_THREADS>0 if( pSorter->bUseThreads ){ rc = vdbePmaReaderNext(pSorter->pReader); - *pbEof = (pSorter->pReader->pFd==0); + if( rc==SQLITE_OK && pSorter->pReader->pFd==0 ) rc = SQLITE_DONE; }else #endif /*if( !pSorter->bUseThreads )*/ { + int res = 0; assert( pSorter->pMerger!=0 ); assert( pSorter->pMerger->pTask==(&pSorter->aTask[0]) ); - rc = vdbeMergeEngineStep(pSorter->pMerger, pbEof); + rc = vdbeMergeEngineStep(pSorter->pMerger, &res); + if( rc==SQLITE_OK && res ) rc = SQLITE_DONE; } }else{ SorterRecord *pFree = pSorter->list.pList; pSorter->list.pList = pFree->u.pNext; pFree->u.pNext = 0; if( pSorter->list.aMemory==0 ) vdbeSorterRecordFree(db, pFree); - *pbEof = !pSorter->list.pList; - rc = SQLITE_OK; + rc = pSorter->list.pList ? SQLITE_OK : SQLITE_DONE; } return rc; } @@ -89063,15 +89937,17 @@ static SQLITE_NOINLINE int walkExpr(Walker *pWalker, Expr *pExpr){ testcase( ExprHasProperty(pExpr, EP_TokenOnly) ); testcase( ExprHasProperty(pExpr, EP_Reduced) ); rc = pWalker->xExprCallback(pWalker, pExpr); - if( rc || ExprHasProperty(pExpr,(EP_TokenOnly|EP_Leaf)) ){ - return rc & WRC_Abort; - } - if( pExpr->pLeft && walkExpr(pWalker, pExpr->pLeft) ) return WRC_Abort; - if( pExpr->pRight && walkExpr(pWalker, pExpr->pRight) ) return WRC_Abort; - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ - if( sqlite3WalkSelect(pWalker, pExpr->x.pSelect) ) return WRC_Abort; - }else if( pExpr->x.pList ){ - if( sqlite3WalkExprList(pWalker, pExpr->x.pList) ) return WRC_Abort; + if( rc ) return rc & WRC_Abort; + if( !ExprHasProperty(pExpr,(EP_TokenOnly|EP_Leaf)) ){ + if( pExpr->pLeft && walkExpr(pWalker, pExpr->pLeft) ) return WRC_Abort; + assert( pExpr->x.pList==0 || pExpr->pRight==0 ); + if( pExpr->pRight ){ + if( walkExpr(pWalker, pExpr->pRight) ) return WRC_Abort; + }else if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + if( sqlite3WalkSelect(pWalker, pExpr->x.pSelect) ) return WRC_Abort; + }else if( pExpr->x.pList ){ + if( sqlite3WalkExprList(pWalker, pExpr->x.pList) ) return WRC_Abort; + } } return WRC_Continue; } @@ -89126,7 +90002,7 @@ SQLITE_PRIVATE int sqlite3WalkSelectFrom(Walker *pWalker, Select *p){ pSrc = p->pSrc; if( ALWAYS(pSrc) ){ for(i=pSrc->nSrc, pItem=pSrc->a; i>0; i--, pItem++){ - if( sqlite3WalkSelect(pWalker, pItem->pSelect) ){ + if( pItem->pSelect && sqlite3WalkSelect(pWalker, pItem->pSelect) ){ return WRC_Abort; } if( pItem->fg.isTabFunc @@ -89146,8 +90022,9 @@ SQLITE_PRIVATE int sqlite3WalkSelectFrom(Walker *pWalker, Select *p){ ** ** If it is not NULL, the xSelectCallback() callback is invoked before ** the walk of the expressions and FROM clause. The xSelectCallback2() -** method, if it is not NULL, is invoked following the walk of the -** expressions and FROM clause. +** method is invoked following the walk of the expressions and FROM clause, +** but only if both xSelectCallback and xSelectCallback2 are both non-NULL +** and if the expressions and FROM clause both return WRC_Continue; ** ** Return WRC_Continue under normal conditions. Return WRC_Abort if ** there is an abort request. @@ -89157,29 +90034,22 @@ SQLITE_PRIVATE int sqlite3WalkSelectFrom(Walker *pWalker, Select *p){ */ SQLITE_PRIVATE int sqlite3WalkSelect(Walker *pWalker, Select *p){ int rc; - if( p==0 || (pWalker->xSelectCallback==0 && pWalker->xSelectCallback2==0) ){ - return WRC_Continue; - } - rc = WRC_Continue; - pWalker->walkerDepth++; - while( p ){ - if( pWalker->xSelectCallback ){ - rc = pWalker->xSelectCallback(pWalker, p); - if( rc ) break; - } + if( p==0 ) return WRC_Continue; + if( pWalker->xSelectCallback==0 ) return WRC_Continue; + do{ + rc = pWalker->xSelectCallback(pWalker, p); + if( rc ) return rc & WRC_Abort; if( sqlite3WalkSelectExpr(pWalker, p) || sqlite3WalkSelectFrom(pWalker, p) ){ - pWalker->walkerDepth--; return WRC_Abort; } if( pWalker->xSelectCallback2 ){ pWalker->xSelectCallback2(pWalker, p); } p = p->pPrior; - } - pWalker->walkerDepth--; - return rc & WRC_Abort; + }while( p!=0 ); + return WRC_Continue; } /************** End of walker.c **********************************************/ @@ -89664,6 +90534,7 @@ static int lookupName( sqlite3ExprDelete(db, pExpr->pRight); pExpr->pRight = 0; pExpr->op = (isTrigger ? TK_TRIGGER : TK_COLUMN); + ExprSetProperty(pExpr, EP_Leaf); lookupname_end: if( cnt==1 ){ assert( pNC!=0 ); @@ -89702,7 +90573,6 @@ SQLITE_PRIVATE Expr *sqlite3CreateColumnExpr(sqlite3 *db, SrcList *pSrc, int iSr testcase( iCol==BMS-1 ); pItem->colUsed |= ((Bitmask)1)<<(iCol>=BMS ? BMS-1 : iCol); } - ExprSetProperty(p, EP_Resolved); } return p; } @@ -89762,8 +90632,6 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ pParse = pNC->pParse; assert( pParse==pWalker->pParse ); - if( ExprHasProperty(pExpr, EP_Resolved) ) return WRC_Prune; - ExprSetProperty(pExpr, EP_Resolved); #ifndef NDEBUG if( pNC->pSrcList && pNC->pSrcList->nAlloc>0 ){ SrcList *pSrcList = pNC->pSrcList; @@ -90096,7 +90964,7 @@ static int resolveOrderByTermToExprList( ** result-set entry. */ for(i=0; inExpr; i++){ - if( sqlite3ExprCompare(pEList->a[i].pExpr, pE, -1)<2 ){ + if( sqlite3ExprCompare(0, pEList->a[i].pExpr, pE, -1)<2 ){ return i+1; } } @@ -90330,7 +91198,7 @@ static int resolveOrderGroupBy( return 1; } for(j=0; jpEList->nExpr; j++){ - if( sqlite3ExprCompare(pE, pSelect->pEList->a[j].pExpr, -1)==0 ){ + if( sqlite3ExprCompare(0, pE, pSelect->pEList->a[j].pExpr, -1)==0 ){ pItem->u.x.iOrderByCol = j+1; } } @@ -90616,37 +91484,29 @@ SQLITE_PRIVATE int sqlite3ResolveExprNames( u16 savedHasAgg; Walker w; - if( pExpr==0 ) return 0; -#if SQLITE_MAX_EXPR_DEPTH>0 - { - Parse *pParse = pNC->pParse; - if( sqlite3ExprCheckHeight(pParse, pExpr->nHeight+pNC->pParse->nHeight) ){ - return 1; - } - pParse->nHeight += pExpr->nHeight; - } -#endif + if( pExpr==0 ) return SQLITE_OK; savedHasAgg = pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg); pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg); w.pParse = pNC->pParse; w.xExprCallback = resolveExprStep; w.xSelectCallback = resolveSelectStep; w.xSelectCallback2 = 0; - w.walkerDepth = 0; - w.eCode = 0; w.u.pNC = pNC; +#if SQLITE_MAX_EXPR_DEPTH>0 + w.pParse->nHeight += pExpr->nHeight; + if( sqlite3ExprCheckHeight(w.pParse, w.pParse->nHeight) ){ + return SQLITE_ERROR; + } +#endif sqlite3WalkExpr(&w, pExpr); #if SQLITE_MAX_EXPR_DEPTH>0 - pNC->pParse->nHeight -= pExpr->nHeight; + w.pParse->nHeight -= pExpr->nHeight; #endif - if( pNC->nErr>0 || w.pParse->nErr>0 ){ - ExprSetProperty(pExpr, EP_Error); - } if( pNC->ncFlags & NC_HasAgg ){ ExprSetProperty(pExpr, EP_Agg); } pNC->ncFlags |= savedHasAgg; - return ExprHasProperty(pExpr, EP_Error); + return pNC->nErr>0 || w.pParse->nErr>0; } /* @@ -90687,9 +91547,9 @@ SQLITE_PRIVATE void sqlite3ResolveSelectNames( Walker w; assert( p!=0 ); - memset(&w, 0, sizeof(w)); w.xExprCallback = resolveExprStep; w.xSelectCallback = resolveSelectStep; + w.xSelectCallback2 = 0; w.pParse = pParse; w.u.pNC = pOuterNC; sqlite3WalkSelect(&w, p); @@ -90792,7 +91652,7 @@ SQLITE_PRIVATE char sqlite3ExprAffinity(Expr *pExpr){ return sqlite3AffinityType(pExpr->u.zToken, 0); } #endif - if( op==TK_AGG_COLUMN || op==TK_COLUMN ){ + if( (op==TK_AGG_COLUMN || op==TK_COLUMN) && pExpr->pTab ){ return sqlite3TableColumnAffinity(pExpr->pTab, pExpr->iColumn); } if( op==TK_SELECT_COLUMN ){ @@ -91086,7 +91946,6 @@ SQLITE_PRIVATE int sqlite3ExprVectorSize(Expr *pExpr){ } } -#ifndef SQLITE_OMIT_SUBQUERY /* ** Return a pointer to a subexpression of pVector that is the i-th ** column of the vector (numbered starting with 0). The caller must @@ -91114,9 +91973,7 @@ SQLITE_PRIVATE Expr *sqlite3VectorFieldSubexpr(Expr *pVector, int i){ } return pVector; } -#endif /* !defined(SQLITE_OMIT_SUBQUERY) */ -#ifndef SQLITE_OMIT_SUBQUERY /* ** Compute and return a new Expr object which when passed to ** sqlite3ExprCode() will generate all necessary code to compute @@ -91174,7 +92031,6 @@ SQLITE_PRIVATE Expr *sqlite3ExprForVectorField( } return pRet; } -#endif /* !define(SQLITE_OMIT_SUBQUERY) */ /* ** If expression pExpr is of type TK_SELECT, generate code to evaluate @@ -91482,7 +92338,7 @@ SQLITE_PRIVATE Expr *sqlite3ExprAlloc( pNew->iAgg = -1; if( pToken ){ if( nExtra==0 ){ - pNew->flags |= EP_IntValue; + pNew->flags |= EP_IntValue|EP_Leaf; pNew->u.iValue = iValue; }else{ pNew->u.zToken = (char*)&pNew[1]; @@ -91690,7 +92546,7 @@ SQLITE_PRIVATE void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr, u32 n z = pExpr->u.zToken; assert( z!=0 ); assert( z[0]!=0 ); - assert( n==sqlite3Strlen30(z) ); + assert( n==(u32)sqlite3Strlen30(z) ); if( z[1]==0 ){ /* Wildcard of the form "?". Assign the next variable number */ assert( z[0]=='?' ); @@ -91763,8 +92619,9 @@ static SQLITE_NOINLINE void sqlite3ExprDeleteNN(sqlite3 *db, Expr *p){ /* The Expr.x union is never used at the same time as Expr.pRight */ assert( p->x.pList==0 || p->pRight==0 ); if( p->pLeft && p->op!=TK_SELECT_COLUMN ) sqlite3ExprDeleteNN(db, p->pLeft); - sqlite3ExprDelete(db, p->pRight); - if( ExprHasProperty(p, EP_xIsSelect) ){ + if( p->pRight ){ + sqlite3ExprDeleteNN(db, p->pRight); + }else if( ExprHasProperty(p, EP_xIsSelect) ){ sqlite3SelectDelete(db, p->x.pSelect); }else{ sqlite3ExprListDelete(db, p->x.pList); @@ -91772,7 +92629,7 @@ static SQLITE_NOINLINE void sqlite3ExprDeleteNN(sqlite3 *db, Expr *p){ } if( ExprHasProperty(p, EP_MemToken) ) sqlite3DbFree(db, p->u.zToken); if( !ExprHasProperty(p, EP_Static) ){ - sqlite3DbFree(db, p); + sqlite3DbFreeNN(db, p); } } SQLITE_PRIVATE void sqlite3ExprDelete(sqlite3 *db, Expr *p){ @@ -92039,15 +92896,11 @@ SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3 *db, ExprList *p, int flags) Expr *pPriorSelectCol = 0; assert( db!=0 ); if( p==0 ) return 0; - pNew = sqlite3DbMallocRawNN(db, sizeof(*pNew) ); + pNew = sqlite3DbMallocRawNN(db, + sizeof(*pNew)+sizeof(pNew->a[0])*(p->nExpr-1) ); if( pNew==0 ) return 0; - pNew->nExpr = i = p->nExpr; - if( (flags & EXPRDUP_REDUCE)==0 ) for(i=1; inExpr; i+=i){} - pNew->a = pItem = sqlite3DbMallocRawNN(db, i*sizeof(p->a[0]) ); - if( pItem==0 ){ - sqlite3DbFree(db, pNew); - return 0; - } + pNew->nAlloc = pNew->nExpr = p->nExpr; + pItem = pNew->a; pOldItem = p->a; for(i=0; inExpr; i++, pItem++, pOldItem++){ Expr *pOldExpr = pOldItem->pExpr; @@ -92138,7 +92991,7 @@ SQLITE_PRIVATE IdList *sqlite3IdListDup(sqlite3 *db, IdList *p){ pNew->nId = p->nId; pNew->a = sqlite3DbMallocRawNN(db, p->nId*sizeof(p->a[0]) ); if( pNew->a==0 ){ - sqlite3DbFree(db, pNew); + sqlite3DbFreeNN(db, pNew); return 0; } /* Note that because the size of the allocation for p->a[] is not @@ -92209,6 +93062,7 @@ SQLITE_PRIVATE ExprList *sqlite3ExprListAppend( ExprList *pList, /* List to which to append. Might be NULL */ Expr *pExpr /* Expression to be appended. Might be NULL */ ){ + struct ExprList_item *pItem; sqlite3 *db = pParse->db; assert( db!=0 ); if( pList==0 ){ @@ -92217,23 +93071,22 @@ SQLITE_PRIVATE ExprList *sqlite3ExprListAppend( goto no_mem; } pList->nExpr = 0; - pList->a = sqlite3DbMallocRawNN(db, sizeof(pList->a[0])); - if( pList->a==0 ) goto no_mem; - }else if( (pList->nExpr & (pList->nExpr-1))==0 ){ - struct ExprList_item *a; - assert( pList->nExpr>0 ); - a = sqlite3DbRealloc(db, pList->a, pList->nExpr*2*sizeof(pList->a[0])); - if( a==0 ){ + pList->nAlloc = 1; + }else if( pList->nExpr==pList->nAlloc ){ + ExprList *pNew; + pNew = sqlite3DbRealloc(db, pList, + sizeof(*pList)+(2*pList->nAlloc - 1)*sizeof(pList->a[0])); + if( pNew==0 ){ goto no_mem; } - pList->a = a; - } - assert( pList->a!=0 ); - if( 1 ){ - struct ExprList_item *pItem = &pList->a[pList->nExpr++]; - memset(pItem, 0, sizeof(*pItem)); - pItem->pExpr = pExpr; + pList = pNew; + pList->nAlloc *= 2; } + pItem = &pList->a[pList->nExpr++]; + assert( offsetof(struct ExprList_item,zName)==sizeof(pItem->pExpr) ); + assert( offsetof(struct ExprList_item,pExpr)==0 ); + memset(&pItem->zName,0,sizeof(*pItem)-offsetof(struct ExprList_item,zName)); + pItem->pExpr = pExpr; return pList; no_mem: @@ -92290,20 +93143,19 @@ SQLITE_PRIVATE ExprList *sqlite3ExprListAppendVector( } } - if( pExpr->op==TK_SELECT ){ - if( pList && pList->a[iFirst].pExpr ){ - Expr *pFirst = pList->a[iFirst].pExpr; - assert( pFirst->op==TK_SELECT_COLUMN ); + if( !db->mallocFailed && pExpr->op==TK_SELECT && ALWAYS(pList!=0) ){ + Expr *pFirst = pList->a[iFirst].pExpr; + assert( pFirst!=0 ); + assert( pFirst->op==TK_SELECT_COLUMN ); - /* Store the SELECT statement in pRight so it will be deleted when - ** sqlite3ExprListDelete() is called */ - pFirst->pRight = pExpr; - pExpr = 0; + /* Store the SELECT statement in pRight so it will be deleted when + ** sqlite3ExprListDelete() is called */ + pFirst->pRight = pExpr; + pExpr = 0; - /* Remember the size of the LHS in iTable so that we can check that - ** the RHS and LHS sizes match during code generation. */ - pFirst->iTable = pColumns->nId; - } + /* Remember the size of the LHS in iTable so that we can check that + ** the RHS and LHS sizes match during code generation. */ + pFirst->iTable = pColumns->nId; } vector_append_error: @@ -92397,16 +93249,16 @@ SQLITE_PRIVATE void sqlite3ExprListCheckLength( ** Delete an entire expression list. */ static SQLITE_NOINLINE void exprListDeleteNN(sqlite3 *db, ExprList *pList){ - int i; - struct ExprList_item *pItem; - assert( pList->a!=0 || pList->nExpr==0 ); - for(pItem=pList->a, i=0; inExpr; i++, pItem++){ + int i = pList->nExpr; + struct ExprList_item *pItem = pList->a; + assert( pList->nExpr>0 ); + do{ sqlite3ExprDelete(db, pItem->pExpr); sqlite3DbFree(db, pItem->zName); sqlite3DbFree(db, pItem->zSpan); - } - sqlite3DbFree(db, pList->a); - sqlite3DbFree(db, pList); + pItem++; + }while( --i>0 ); + sqlite3DbFreeNN(db, pList); } SQLITE_PRIVATE void sqlite3ExprListDelete(sqlite3 *db, ExprList *pList){ if( pList ) exprListDeleteNN(db, pList); @@ -92485,10 +93337,12 @@ static int exprNodeIsConstant(Walker *pWalker, Expr *pExpr){ testcase( pExpr->op==TK_AGG_COLUMN ); if( pWalker->eCode==3 && pExpr->iTable==pWalker->u.iCur ){ return WRC_Continue; - }else{ - pWalker->eCode = 0; - return WRC_Abort; } + /* Fall through */ + case TK_IF_NULL_ROW: + testcase( pExpr->op==TK_IF_NULL_ROW ); + pWalker->eCode = 0; + return WRC_Abort; case TK_VARIABLE: if( pWalker->eCode==5 ){ /* Silently convert bound parameters that appear inside of CREATE @@ -92515,10 +93369,12 @@ static int selectNodeIsConstant(Walker *pWalker, Select *NotUsed){ } static int exprIsConst(Expr *p, int initFlag, int iCur){ Walker w; - memset(&w, 0, sizeof(w)); w.eCode = initFlag; w.xExprCallback = exprNodeIsConstant; w.xSelectCallback = selectNodeIsConstant; +#ifdef SQLITE_DEBUG + w.xSelectCallback2 = sqlite3SelectWalkAssert2; +#endif w.u.iCur = iCur; sqlite3WalkExpr(&w, p); return w.eCode; @@ -92556,6 +93412,65 @@ SQLITE_PRIVATE int sqlite3ExprIsTableConstant(Expr *p, int iCur){ return exprIsConst(p, 3, iCur); } + +/* +** sqlite3WalkExpr() callback used by sqlite3ExprIsConstantOrGroupBy(). +*/ +static int exprNodeIsConstantOrGroupBy(Walker *pWalker, Expr *pExpr){ + ExprList *pGroupBy = pWalker->u.pGroupBy; + int i; + + /* Check if pExpr is identical to any GROUP BY term. If so, consider + ** it constant. */ + for(i=0; inExpr; i++){ + Expr *p = pGroupBy->a[i].pExpr; + if( sqlite3ExprCompare(0, pExpr, p, -1)<2 ){ + CollSeq *pColl = sqlite3ExprCollSeq(pWalker->pParse, p); + if( pColl==0 || sqlite3_stricmp("BINARY", pColl->zName)==0 ){ + return WRC_Prune; + } + } + } + + /* Check if pExpr is a sub-select. If so, consider it variable. */ + if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + pWalker->eCode = 0; + return WRC_Abort; + } + + return exprNodeIsConstant(pWalker, pExpr); +} + +/* +** Walk the expression tree passed as the first argument. Return non-zero +** if the expression consists entirely of constants or copies of terms +** in pGroupBy that sort with the BINARY collation sequence. +** +** This routine is used to determine if a term of the HAVING clause can +** be promoted into the WHERE clause. In order for such a promotion to work, +** the value of the HAVING clause term must be the same for all members of +** a "group". The requirement that the GROUP BY term must be BINARY +** assumes that no other collating sequence will have a finer-grained +** grouping than binary. In other words (A=B COLLATE binary) implies +** A=B in every other collating sequence. The requirement that the +** GROUP BY be BINARY is stricter than necessary. It would also work +** to promote HAVING clauses that use the same alternative collating +** sequence as the GROUP BY term, but that is much harder to check, +** alternative collating sequences are uncommon, and this is only an +** optimization, so we take the easy way out and simply require the +** GROUP BY to use the BINARY collating sequence. +*/ +SQLITE_PRIVATE int sqlite3ExprIsConstantOrGroupBy(Parse *pParse, Expr *p, ExprList *pGroupBy){ + Walker w; + w.eCode = 1; + w.xExprCallback = exprNodeIsConstantOrGroupBy; + w.xSelectCallback = 0; + w.u.pGroupBy = pGroupBy; + w.pParse = pParse; + sqlite3WalkExpr(&w, p); + return w.eCode; +} + /* ** Walk an expression tree. Return non-zero if the expression is constant ** or a function call with constant arguments. Return and 0 if there @@ -92577,10 +93492,12 @@ SQLITE_PRIVATE int sqlite3ExprIsConstantOrFunction(Expr *p, u8 isInit){ */ SQLITE_PRIVATE int sqlite3ExprContainsSubquery(Expr *p){ Walker w; - memset(&w, 0, sizeof(w)); w.eCode = 1; w.xExprCallback = sqlite3ExprWalkNoop; w.xSelectCallback = selectNodeIsConstant; +#ifdef SQLITE_DEBUG + w.xSelectCallback2 = sqlite3SelectWalkAssert2; +#endif sqlite3WalkExpr(&w, p); return w.eCode==0; } @@ -93915,8 +94832,9 @@ SQLITE_PRIVATE void sqlite3ExprCodeLoadIndexColumn( if( iTabCol==XN_EXPR ){ assert( pIdx->aColExpr ); assert( pIdx->aColExpr->nExpr>iIdxCol ); - pParse->iSelfTab = iTabCur; + pParse->iSelfTab = iTabCur + 1; sqlite3ExprCodeCopy(pParse, pIdx->aColExpr->a[iIdxCol].pExpr, regOut); + pParse->iSelfTab = 0; }else{ sqlite3ExprCodeGetColumnOfTable(pParse->pVdbe, pIdx->pTable, iTabCur, iTabCol, regOut); @@ -93933,6 +94851,10 @@ SQLITE_PRIVATE void sqlite3ExprCodeGetColumnOfTable( int iCol, /* Index of the column to extract */ int regOut /* Extract the value into this register */ ){ + if( pTab==0 ){ + sqlite3VdbeAddOp3(v, OP_Column, iTabCur, iCol, regOut); + return; + } if( iCol<0 || iCol==pTab->iPKey ){ sqlite3VdbeAddOp2(v, OP_Rowid, iTabCur, regOut); }else{ @@ -94089,7 +95011,11 @@ static int exprCodeVector(Parse *pParse, Expr *p, int *piFreeable){ }else{ *piFreeable = 0; if( p->op==TK_SELECT ){ +#if SQLITE_OMIT_SUBQUERY + iResult = 0; +#else iResult = sqlite3CodeSubselect(pParse, p, 0, 0); +#endif }else{ int i; iResult = pParse->nMem+1; @@ -94152,13 +95078,13 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) case TK_COLUMN: { int iTab = pExpr->iTable; if( iTab<0 ){ - if( pParse->ckBase>0 ){ + if( pParse->iSelfTab<0 ){ /* Generating CHECK constraints or inserting into partial index */ - return pExpr->iColumn + pParse->ckBase; + return pExpr->iColumn - pParse->iSelfTab; }else{ /* Coding an expression that is part of an index where column names ** in the index refer to the table to which the index belongs */ - iTab = pParse->iSelfTab; + iTab = pParse->iSelfTab - 1; } } return sqlite3ExprCodeGetColumn(pParse, pExpr->pTab, @@ -94495,8 +95421,8 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) if( !pColl ) pColl = db->pDfltColl; sqlite3VdbeAddOp4(v, OP_CollSeq, 0, 0, 0, (char *)pColl, P4_COLLSEQ); } - sqlite3VdbeAddOp4(v, OP_Function0, constMask, r1, target, - (char*)pDef, P4_FUNCDEF); + sqlite3VdbeAddOp4(v, pParse->iSelfTab ? OP_PureFunc0 : OP_Function0, + constMask, r1, target, (char*)pDef, P4_FUNCDEF); sqlite3VdbeChangeP5(v, (u8)nFarg); if( nFarg && constMask==0 ){ sqlite3ReleaseTempRange(pParse, r1, nFarg); @@ -94626,6 +95552,17 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) break; } + case TK_IF_NULL_ROW: { + int addrINR; + addrINR = sqlite3VdbeAddOp1(v, OP_IfNullRow, pExpr->iTable); + sqlite3ExprCachePush(pParse); + inReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target); + sqlite3ExprCachePop(pParse); + sqlite3VdbeJumpHere(v, addrINR); + sqlite3VdbeChangeP3(v, addrINR, inReg); + break; + } + /* ** Form A: ** CASE x WHEN e1 THEN r1 WHEN e2 THEN r2 ... WHEN eN THEN rN ELSE y END @@ -94764,7 +95701,7 @@ SQLITE_PRIVATE int sqlite3ExprCodeAtInit( struct ExprList_item *pItem; int i; for(pItem=p->a, i=p->nExpr; i>0; pItem++, i--){ - if( pItem->reusable && sqlite3ExprCompare(pItem->pExpr,pExpr,-1)==0 ){ + if( pItem->reusable && sqlite3ExprCompare(0,pItem->pExpr,pExpr,-1)==0 ){ return pItem->u.iConstExprReg; } } @@ -95319,6 +96256,41 @@ SQLITE_PRIVATE void sqlite3ExprIfFalseDup(Parse *pParse, Expr *pExpr, int dest,i sqlite3ExprDelete(db, pCopy); } +/* +** Expression pVar is guaranteed to be an SQL variable. pExpr may be any +** type of expression. +** +** If pExpr is a simple SQL value - an integer, real, string, blob +** or NULL value - then the VDBE currently being prepared is configured +** to re-prepare each time a new value is bound to variable pVar. +** +** Additionally, if pExpr is a simple SQL value and the value is the +** same as that currently bound to variable pVar, non-zero is returned. +** Otherwise, if the values are not the same or if pExpr is not a simple +** SQL value, zero is returned. +*/ +static int exprCompareVariable(Parse *pParse, Expr *pVar, Expr *pExpr){ + int res = 0; + int iVar; + sqlite3_value *pL, *pR = 0; + + sqlite3ValueFromExpr(pParse->db, pExpr, SQLITE_UTF8, SQLITE_AFF_BLOB, &pR); + if( pR ){ + iVar = pVar->iColumn; + sqlite3VdbeSetVarmask(pParse->pVdbe, iVar); + pL = sqlite3VdbeGetBoundValue(pParse->pReprepare, iVar, SQLITE_AFF_BLOB); + if( pL ){ + if( sqlite3_value_type(pL)==SQLITE_TEXT ){ + sqlite3_value_text(pL); /* Make sure the encoding is UTF-8 */ + } + res = 0==sqlite3MemCompare(pL, pR, 0); + } + sqlite3ValueFree(pR); + sqlite3ValueFree(pL); + } + + return res; +} /* ** Do a deep comparison of two expression trees. Return 0 if the two @@ -95341,12 +96313,22 @@ SQLITE_PRIVATE void sqlite3ExprIfFalseDup(Parse *pParse, Expr *pExpr, int dest,i ** this routine is used, it does not hurt to get an extra 2 - that ** just might result in some slightly slower code. But returning ** an incorrect 0 or 1 could lead to a malfunction. +** +** If pParse is not NULL then TK_VARIABLE terms in pA with bindings in +** pParse->pReprepare can be matched against literals in pB. The +** pParse->pVdbe->expmask bitmask is updated for each variable referenced. +** If pParse is NULL (the normal case) then any TK_VARIABLE term in +** Argument pParse should normally be NULL. If it is not NULL and pA or +** pB causes a return value of 2. */ -SQLITE_PRIVATE int sqlite3ExprCompare(Expr *pA, Expr *pB, int iTab){ +SQLITE_PRIVATE int sqlite3ExprCompare(Parse *pParse, Expr *pA, Expr *pB, int iTab){ u32 combinedFlags; if( pA==0 || pB==0 ){ return pB==pA ? 0 : 2; } + if( pParse && pA->op==TK_VARIABLE && exprCompareVariable(pParse, pA, pB) ){ + return 0; + } combinedFlags = pA->flags | pB->flags; if( combinedFlags & EP_IntValue ){ if( (pA->flags&pB->flags&EP_IntValue)!=0 && pA->u.iValue==pB->u.iValue ){ @@ -95355,10 +96337,10 @@ SQLITE_PRIVATE int sqlite3ExprCompare(Expr *pA, Expr *pB, int iTab){ return 2; } if( pA->op!=pB->op ){ - if( pA->op==TK_COLLATE && sqlite3ExprCompare(pA->pLeft, pB, iTab)<2 ){ + if( pA->op==TK_COLLATE && sqlite3ExprCompare(pParse, pA->pLeft,pB,iTab)<2 ){ return 1; } - if( pB->op==TK_COLLATE && sqlite3ExprCompare(pA, pB->pLeft, iTab)<2 ){ + if( pB->op==TK_COLLATE && sqlite3ExprCompare(pParse, pA,pB->pLeft,iTab)<2 ){ return 1; } return 2; @@ -95373,8 +96355,8 @@ SQLITE_PRIVATE int sqlite3ExprCompare(Expr *pA, Expr *pB, int iTab){ if( (pA->flags & EP_Distinct)!=(pB->flags & EP_Distinct) ) return 2; if( ALWAYS((combinedFlags & EP_TokenOnly)==0) ){ if( combinedFlags & EP_xIsSelect ) return 2; - if( sqlite3ExprCompare(pA->pLeft, pB->pLeft, iTab) ) return 2; - if( sqlite3ExprCompare(pA->pRight, pB->pRight, iTab) ) return 2; + if( sqlite3ExprCompare(pParse, pA->pLeft, pB->pLeft, iTab) ) return 2; + if( sqlite3ExprCompare(pParse, pA->pRight, pB->pRight, iTab) ) return 2; if( sqlite3ExprListCompare(pA->x.pList, pB->x.pList, iTab) ) return 2; if( ALWAYS((combinedFlags & EP_Reduced)==0) && pA->op!=TK_STRING ){ if( pA->iColumn!=pB->iColumn ) return 2; @@ -95409,7 +96391,7 @@ SQLITE_PRIVATE int sqlite3ExprListCompare(ExprList *pA, ExprList *pB, int iTab){ Expr *pExprA = pA->a[i].pExpr; Expr *pExprB = pB->a[i].pExpr; if( pA->a[i].sortOrder!=pB->a[i].sortOrder ) return 1; - if( sqlite3ExprCompare(pExprA, pExprB, iTab) ) return 1; + if( sqlite3ExprCompare(0, pExprA, pExprB, iTab) ) return 1; } return 0; } @@ -95419,7 +96401,7 @@ SQLITE_PRIVATE int sqlite3ExprListCompare(ExprList *pA, ExprList *pB, int iTab){ ** are ignored. */ SQLITE_PRIVATE int sqlite3ExprCompareSkip(Expr *pA, Expr *pB, int iTab){ - return sqlite3ExprCompare( + return sqlite3ExprCompare(0, sqlite3ExprSkipCollate(pA), sqlite3ExprSkipCollate(pB), iTab); @@ -95441,24 +96423,29 @@ SQLITE_PRIVATE int sqlite3ExprCompareSkip(Expr *pA, Expr *pB, int iTab){ ** When comparing TK_COLUMN nodes between pE1 and pE2, if pE2 has ** Expr.iTable<0 then assume a table number given by iTab. ** +** If pParse is not NULL, then the values of bound variables in pE1 are +** compared against literal values in pE2 and pParse->pVdbe->expmask is +** modified to record which bound variables are referenced. If pParse +** is NULL, then false will be returned if pE1 contains any bound variables. +** ** When in doubt, return false. Returning true might give a performance ** improvement. Returning false might cause a performance reduction, but ** it will always give the correct answer and is hence always safe. */ -SQLITE_PRIVATE int sqlite3ExprImpliesExpr(Expr *pE1, Expr *pE2, int iTab){ - if( sqlite3ExprCompare(pE1, pE2, iTab)==0 ){ +SQLITE_PRIVATE int sqlite3ExprImpliesExpr(Parse *pParse, Expr *pE1, Expr *pE2, int iTab){ + if( sqlite3ExprCompare(pParse, pE1, pE2, iTab)==0 ){ return 1; } if( pE2->op==TK_OR - && (sqlite3ExprImpliesExpr(pE1, pE2->pLeft, iTab) - || sqlite3ExprImpliesExpr(pE1, pE2->pRight, iTab) ) + && (sqlite3ExprImpliesExpr(pParse, pE1, pE2->pLeft, iTab) + || sqlite3ExprImpliesExpr(pParse, pE1, pE2->pRight, iTab) ) ){ return 1; } if( pE2->op==TK_NOTNULL && pE1->op!=TK_ISNULL && pE1->op!=TK_IS ){ Expr *pX = sqlite3ExprSkipCollate(pE1->pLeft); testcase( pX!=pE1->pLeft ); - if( sqlite3ExprCompare(pX, pE2->pLeft, iTab)==0 ) return 1; + if( sqlite3ExprCompare(pParse, pX, pE2->pLeft, iTab)==0 ) return 1; } return 0; } @@ -95566,8 +96553,8 @@ SQLITE_PRIVATE int sqlite3FunctionUsesThisSrc(Expr *pExpr, SrcList *pSrcList){ Walker w; struct SrcCount cnt; assert( pExpr->op==TK_AGG_FUNCTION ); - memset(&w, 0, sizeof(w)); w.xExprCallback = exprSrcCount; + w.xSelectCallback = 0; w.u.pSrcCount = &cnt; cnt.pSrc = pSrcList; cnt.nThis = 0; @@ -95699,7 +96686,7 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ */ struct AggInfo_func *pItem = pAggInfo->aFunc; for(i=0; inFunc; i++, pItem++){ - if( sqlite3ExprCompare(pItem->pExpr, pExpr, -1)==0 ){ + if( sqlite3ExprCompare(0, pItem->pExpr, pExpr, -1)==0 ){ break; } } @@ -95739,10 +96726,14 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ return WRC_Continue; } static int analyzeAggregatesInSelect(Walker *pWalker, Select *pSelect){ - UNUSED_PARAMETER(pWalker); UNUSED_PARAMETER(pSelect); + pWalker->walkerDepth++; return WRC_Continue; } +static void analyzeAggregatesInSelectEnd(Walker *pWalker, Select *pSelect){ + UNUSED_PARAMETER(pSelect); + pWalker->walkerDepth--; +} /* ** Analyze the pExpr expression looking for aggregate functions and @@ -95755,9 +96746,10 @@ static int analyzeAggregatesInSelect(Walker *pWalker, Select *pSelect){ */ SQLITE_PRIVATE void sqlite3ExprAnalyzeAggregates(NameContext *pNC, Expr *pExpr){ Walker w; - memset(&w, 0, sizeof(w)); w.xExprCallback = analyzeAggregate; w.xSelectCallback = analyzeAggregatesInSelect; + w.xSelectCallback2 = analyzeAggregatesInSelectEnd; + w.walkerDepth = 0; w.u.pNC = pNC; assert( pNC->pSrcList!=0 ); sqlite3WalkExpr(&w, pExpr); @@ -95858,8 +96850,8 @@ SQLITE_PRIVATE void sqlite3ClearTempRegCache(Parse *pParse){ SQLITE_PRIVATE int sqlite3NoTempsInRange(Parse *pParse, int iFirst, int iLast){ int i; if( pParse->nRangeReg>0 - && pParse->iRangeReg+pParse->nRangeRegiRangeReg>=iFirst + && pParse->iRangeReg+pParse->nRangeReg > iFirst + && pParse->iRangeReg <= iLast ){ return 0; } @@ -96251,7 +97243,7 @@ static void reloadTableSchema(Parse *pParse, Table *pTab, const char *zName){ ** Or, if zName is not a system table, zero is returned. */ static int isSystemTable(Parse *pParse, const char *zName){ - if( sqlite3Strlen30(zName)>6 && 0==sqlite3StrNICmp(zName, "sqlite_", 7) ){ + if( 0==sqlite3StrNICmp(zName, "sqlite_", 7) ){ sqlite3ErrorMsg(pParse, "table %s may not be altered", zName); return 1; } @@ -98670,7 +99662,8 @@ static void attachFunc( char *zPath = 0; char *zErr = 0; unsigned int flags; - Db *aNew; + Db *aNew; /* New array of Db pointers */ + Db *pNew; /* Db object for the newly attached database */ char *zErrDyn = 0; sqlite3_vfs *pVfs; @@ -98718,8 +99711,8 @@ static void attachFunc( if( aNew==0 ) return; } db->aDb = aNew; - aNew = &db->aDb[db->nDb]; - memset(aNew, 0, sizeof(*aNew)); + pNew = &db->aDb[db->nDb]; + memset(pNew, 0, sizeof(*pNew)); /* Open the database file. If the btree is successfully opened, use ** it to obtain the database schema. At this point the schema may @@ -98735,7 +99728,7 @@ static void attachFunc( } assert( pVfs ); flags |= SQLITE_OPEN_MAIN_DB; - rc = sqlite3BtreeOpen(pVfs, zPath, db, &aNew->pBt, 0, flags); + rc = sqlite3BtreeOpen(pVfs, zPath, db, &pNew->pBt, 0, flags); sqlite3_free( zPath ); db->nDb++; db->skipBtreeMutex = 0; @@ -98744,28 +99737,28 @@ static void attachFunc( zErrDyn = sqlite3MPrintf(db, "database is already attached"); }else if( rc==SQLITE_OK ){ Pager *pPager; - aNew->pSchema = sqlite3SchemaGet(db, aNew->pBt); - if( !aNew->pSchema ){ + pNew->pSchema = sqlite3SchemaGet(db, pNew->pBt); + if( !pNew->pSchema ){ rc = SQLITE_NOMEM_BKPT; - }else if( aNew->pSchema->file_format && aNew->pSchema->enc!=ENC(db) ){ + }else if( pNew->pSchema->file_format && pNew->pSchema->enc!=ENC(db) ){ zErrDyn = sqlite3MPrintf(db, "attached databases must use the same text encoding as main database"); rc = SQLITE_ERROR; } - sqlite3BtreeEnter(aNew->pBt); - pPager = sqlite3BtreePager(aNew->pBt); + sqlite3BtreeEnter(pNew->pBt); + pPager = sqlite3BtreePager(pNew->pBt); sqlite3PagerLockingMode(pPager, db->dfltLockMode); - sqlite3BtreeSecureDelete(aNew->pBt, + sqlite3BtreeSecureDelete(pNew->pBt, sqlite3BtreeSecureDelete(db->aDb[0].pBt,-1) ); #ifndef SQLITE_OMIT_PAGER_PRAGMAS - sqlite3BtreeSetPagerFlags(aNew->pBt, + sqlite3BtreeSetPagerFlags(pNew->pBt, PAGER_SYNCHRONOUS_FULL | (db->flags & PAGER_FLAGS_MASK)); #endif - sqlite3BtreeLeave(aNew->pBt); + sqlite3BtreeLeave(pNew->pBt); } - aNew->safety_level = SQLITE_DEFAULT_SYNCHRONOUS+1; - aNew->zDbSName = sqlite3DbStrDup(db, zName); - if( rc==SQLITE_OK && aNew->zDbSName==0 ){ + pNew->safety_level = SQLITE_DEFAULT_SYNCHRONOUS+1; + pNew->zDbSName = sqlite3DbStrDup(db, zName); + if( rc==SQLITE_OK && pNew->zDbSName==0 ){ rc = SQLITE_NOMEM_BKPT; } @@ -99403,6 +100396,18 @@ SQLITE_PRIVATE int sqlite3AuthCheck( if( db->xAuth==0 ){ return SQLITE_OK; } + + /* EVIDENCE-OF: R-43249-19882 The third through sixth parameters to the + ** callback are either NULL pointers or zero-terminated strings that + ** contain additional details about the action to be authorized. + ** + ** The following testcase() macros show that any of the 3rd through 6th + ** parameters can be either NULL or a string. */ + testcase( zArg1==0 ); + testcase( zArg2==0 ); + testcase( zArg3==0 ); + testcase( pParse->zAuthContext==0 ); + rc = db->xAuth(db->pAuthArg, code, zArg1, zArg2, zArg3, pParse->zAuthContext #ifdef SQLITE_USER_AUTHENTICATION ,db->auth.zAuthUser @@ -100390,7 +101395,11 @@ SQLITE_PRIVATE void sqlite3StartTable( pTable->iPKey = -1; pTable->pSchema = db->aDb[iDb].pSchema; pTable->nTabRef = 1; +#ifdef SQLITE_DEFAULT_ROWEST + pTable->nRowLogEst = sqlite3LogEst(SQLITE_DEFAULT_ROWEST); +#else pTable->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); +#endif assert( pParse->pNewTable==0 ); pParse->pNewTable = pTable; @@ -101185,15 +102194,6 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){ }else{ pPk = sqlite3PrimaryKeyIndex(pTab); - /* Bypass the creation of the PRIMARY KEY btree and the sqlite_master - ** table entry. This is only required if currently generating VDBE - ** code for a CREATE TABLE (not when parsing one as part of reading - ** a database schema). */ - if( v ){ - assert( db->init.busy==0 ); - sqlite3VdbeChangeOpcode(v, pPk->tnum, OP_Goto); - } - /* ** Remove all redundant columns from the PRIMARY KEY. For example, change ** "PRIMARY KEY(a,b,a,b,c,b,c,d)" into just "PRIMARY KEY(a,b,c,d)". Later @@ -101213,6 +102213,15 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){ if( !db->init.imposterTable ) pPk->uniqNotNull = 1; nPk = pPk->nKeyCol; + /* Bypass the creation of the PRIMARY KEY btree and the sqlite_master + ** table entry. This is only required if currently generating VDBE + ** code for a CREATE TABLE (not when parsing one as part of reading + ** a database schema). */ + if( v && pPk->tnum>0 ){ + assert( db->init.busy==0 ); + sqlite3VdbeChangeOpcode(v, pPk->tnum, OP_Goto); + } + /* The root page of the PRIMARY KEY is the table root page */ pPk->tnum = pTab->tnum; @@ -103074,7 +104083,7 @@ SQLITE_PRIVATE void sqlite3IdListDelete(sqlite3 *db, IdList *pList){ sqlite3DbFree(db, pList->a[i].zName); } sqlite3DbFree(db, pList->a); - sqlite3DbFree(db, pList); + sqlite3DbFreeNN(db, pList); } /* @@ -103219,12 +104228,12 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListAppend( pDatabase = 0; } if( pDatabase ){ - Token *pTemp = pDatabase; - pDatabase = pTable; - pTable = pTemp; + pItem->zName = sqlite3NameFromToken(db, pDatabase); + pItem->zDatabase = sqlite3NameFromToken(db, pTable); + }else{ + pItem->zName = sqlite3NameFromToken(db, pTable); + pItem->zDatabase = 0; } - pItem->zName = sqlite3NameFromToken(db, pTable); - pItem->zDatabase = sqlite3NameFromToken(db, pDatabase); return pList; } @@ -103264,7 +104273,7 @@ SQLITE_PRIVATE void sqlite3SrcListDelete(sqlite3 *db, SrcList *pList){ sqlite3ExprDelete(db, pItem->pOn); sqlite3IdListDelete(db, pItem->pUsing); } - sqlite3DbFree(db, pList); + sqlite3DbFreeNN(db, pList); } /* @@ -103413,36 +104422,25 @@ SQLITE_PRIVATE void sqlite3BeginTransaction(Parse *pParse, int type){ } /* -** Generate VDBE code for a COMMIT statement. +** Generate VDBE code for a COMMIT or ROLLBACK statement. +** Code for ROLLBACK is generated if eType==TK_ROLLBACK. Otherwise +** code is generated for a COMMIT. */ -SQLITE_PRIVATE void sqlite3CommitTransaction(Parse *pParse){ +SQLITE_PRIVATE void sqlite3EndTransaction(Parse *pParse, int eType){ Vdbe *v; + int isRollback; assert( pParse!=0 ); assert( pParse->db!=0 ); - if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, "COMMIT", 0, 0) ){ + assert( eType==TK_COMMIT || eType==TK_END || eType==TK_ROLLBACK ); + isRollback = eType==TK_ROLLBACK; + if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, + isRollback ? "ROLLBACK" : "COMMIT", 0, 0) ){ return; } v = sqlite3GetVdbe(pParse); if( v ){ - sqlite3VdbeAddOp1(v, OP_AutoCommit, 1); - } -} - -/* -** Generate VDBE code for a ROLLBACK statement. -*/ -SQLITE_PRIVATE void sqlite3RollbackTransaction(Parse *pParse){ - Vdbe *v; - - assert( pParse!=0 ); - assert( pParse->db!=0 ); - if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, "ROLLBACK", 0, 0) ){ - return; - } - v = sqlite3GetVdbe(pParse); - if( v ){ - sqlite3VdbeAddOp2(v, OP_AutoCommit, 1, 1); + sqlite3VdbeAddOp2(v, OP_AutoCommit, 1, isRollback); } } @@ -103632,7 +104630,9 @@ SQLITE_PRIVATE void sqlite3UniqueConstraint( assert( pIdx->aiColumn[j]>=0 ); zCol = pTab->aCol[pIdx->aiColumn[j]].zName; if( j ) sqlite3StrAccumAppend(&errMsg, ", ", 2); - sqlite3XPrintf(&errMsg, "%s.%s", pTab->zName, zCol); + sqlite3StrAccumAppendAll(&errMsg, pTab->zName); + sqlite3StrAccumAppend(&errMsg, ".", 1); + sqlite3StrAccumAppendAll(&errMsg, zCol); } } zErr = sqlite3StrAccumFinish(&errMsg); @@ -104021,7 +105021,7 @@ SQLITE_PRIVATE CollSeq *sqlite3GetCollSeq( ** from the main database is substituted, if one is available. */ SQLITE_PRIVATE int sqlite3CheckCollSeq(Parse *pParse, CollSeq *pColl){ - if( pColl ){ + if( pColl && pColl->xCmp==0 ){ const char *zName = pColl->zName; sqlite3 *db = pParse->db; CollSeq *p = sqlite3GetCollSeq(pParse, ENC(db), pColl, zName); @@ -104057,8 +105057,8 @@ static CollSeq *findCollSeqEntry( pColl = sqlite3HashFind(&db->aCollSeq, zName); if( 0==pColl && create ){ - int nName = sqlite3Strlen30(zName); - pColl = sqlite3DbMallocZero(db, 3*sizeof(*pColl) + nName + 1); + int nName = sqlite3Strlen30(zName) + 1; + pColl = sqlite3DbMallocZero(db, 3*sizeof(*pColl) + nName); if( pColl ){ CollSeq *pDel = 0; pColl[0].zName = (char*)&pColl[3]; @@ -104068,7 +105068,6 @@ static CollSeq *findCollSeqEntry( pColl[2].zName = (char*)&pColl[3]; pColl[2].enc = SQLITE_UTF16BE; memcpy(pColl[0].zName, zName, nName); - pColl[0].zName[nName] = 0; pDel = sqlite3HashInsert(&db->aCollSeq, pColl[0].zName, pColl); /* If a malloc() failure occurred in sqlite3HashInsert(), it will @@ -104208,7 +105207,8 @@ SQLITE_PRIVATE void sqlite3InsertBuiltinFuncs( FuncDef *pOther; const char *zName = aDef[i].zName; int nName = sqlite3Strlen30(zName); - int h = (sqlite3UpperToLower[(u8)zName[0]] + nName) % SQLITE_FUNC_HASH_SZ; + int h = (zName[0] + nName) % SQLITE_FUNC_HASH_SZ; + assert( zName[0]>='a' && zName[0]<='z' ); pOther = functionSearch(h, zName); if( pOther ){ assert( pOther!=&aDef[i] && pOther->pNext!=&aDef[i] ); @@ -104738,7 +105738,14 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( /* Special case: A DELETE without a WHERE clause deletes everything. ** It is easier just to erase the whole table. Prior to version 3.6.5, ** this optimization caused the row change count (the value returned by - ** API function sqlite3_count_changes) to be set incorrectly. */ + ** API function sqlite3_count_changes) to be set incorrectly. + ** + ** The "rcauth==SQLITE_OK" terms is the + ** IMPLEMENTATION-OF: R-17228-37124 If the action code is SQLITE_DELETE and + ** the callback returns SQLITE_IGNORE then the DELETE operation proceeds but + ** the truncate optimization is disabled and all rows are deleted + ** individually. + */ if( rcauth==SQLITE_OK && pWhere==0 && !bComplex @@ -104840,7 +105847,7 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iEphCur, iKey, iPk, nPk); }else{ /* Add the rowid of the row to be deleted to the RowSet */ - nKey = 1; /* OP_Seek always uses a single rowid */ + nKey = 1; /* OP_DeferredSeek always uses a single rowid */ sqlite3VdbeAddOp2(v, OP_RowSetAdd, iRowSet, iKey); } } @@ -105233,10 +106240,11 @@ SQLITE_PRIVATE int sqlite3GenerateIndexKey( if( piPartIdxLabel ){ if( pIdx->pPartIdxWhere ){ *piPartIdxLabel = sqlite3VdbeMakeLabel(v); - pParse->iSelfTab = iDataCur; + pParse->iSelfTab = iDataCur + 1; sqlite3ExprCachePush(pParse); sqlite3ExprIfFalseDup(pParse, pIdx->pPartIdxWhere, *piPartIdxLabel, SQLITE_JUMPIFNULL); + pParse->iSelfTab = 0; }else{ *piPartIdxLabel = 0; } @@ -105364,16 +106372,20 @@ static void typeofFunc( int NotUsed, sqlite3_value **argv ){ - const char *z = 0; + static const char *azType[] = { "integer", "real", "text", "blob", "null" }; + int i = sqlite3_value_type(argv[0]) - 1; UNUSED_PARAMETER(NotUsed); - switch( sqlite3_value_type(argv[0]) ){ - case SQLITE_INTEGER: z = "integer"; break; - case SQLITE_TEXT: z = "text"; break; - case SQLITE_FLOAT: z = "real"; break; - case SQLITE_BLOB: z = "blob"; break; - default: z = "null"; break; - } - sqlite3_result_text(context, z, -1, SQLITE_STATIC); + assert( i>=0 && iisDeferred, nIncr); - if( pWInfo ){ - sqlite3WhereEnd(pWInfo); + if( pParse->nErr==0 ){ + pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0, 0, 0, 0); + sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr); + if( pWInfo ){ + sqlite3WhereEnd(pWInfo); + } } /* Clean up the WHERE clause constructed above. */ @@ -108248,8 +109262,16 @@ SQLITE_PRIVATE u32 sqlite3FkOldmask( ** UPDATE statement modifies the rowid fields of the table. ** ** If any foreign key processing will be required, this function returns -** true. If there is no foreign key related processing, this function -** returns false. +** non-zero. If there is no foreign key related processing, this function +** returns zero. +** +** For an UPDATE, this function returns 2 if: +** +** * There are any FKs for which pTab is the child and the parent table, or +** * the UPDATE modifies one or more parent keys for which the action is +** not "NO ACTION" (i.e. is CASCADE, SET DEFAULT or SET NULL). +** +** Or, assuming some other foreign key processing is required, 1. */ SQLITE_PRIVATE int sqlite3FkRequired( Parse *pParse, /* Parse context */ @@ -108257,12 +109279,13 @@ SQLITE_PRIVATE int sqlite3FkRequired( int *aChange, /* Non-NULL for UPDATE operations */ int chngRowid /* True for UPDATE that affects rowid */ ){ + int eRet = 0; if( pParse->db->flags&SQLITE_ForeignKeys ){ if( !aChange ){ /* A DELETE operation. Foreign key processing is required if the ** table in question is either the child or parent table for any ** foreign key constraint. */ - return (sqlite3FkReferences(pTab) || pTab->pFKey); + eRet = (sqlite3FkReferences(pTab) || pTab->pFKey); }else{ /* This is an UPDATE. Foreign key processing is only required if the ** operation modifies one or more child or parent key columns. */ @@ -108270,16 +109293,22 @@ SQLITE_PRIVATE int sqlite3FkRequired( /* Check if any child key columns are being modified. */ for(p=pTab->pFKey; p; p=p->pNextFrom){ - if( fkChildIsModified(pTab, p, aChange, chngRowid) ) return 1; + if( 0==sqlite3_stricmp(pTab->zName, p->zTo) ) return 2; + if( fkChildIsModified(pTab, p, aChange, chngRowid) ){ + eRet = 1; + } } /* Check if any parent key columns are being modified. */ for(p=sqlite3FkReferences(pTab); p; p=p->pNextTo){ - if( fkParentIsModified(pTab, p, aChange, chngRowid) ) return 1; + if( fkParentIsModified(pTab, p, aChange, chngRowid) ){ + if( p->aAction[1]!=OE_None ) return 2; + eRet = 1; + } } } } - return 0; + return eRet; } /* @@ -109089,10 +110118,10 @@ SQLITE_PRIVATE void sqlite3Insert( #endif db = pParse->db; - memset(&dest, 0, sizeof(dest)); if( pParse->nErr || db->mallocFailed ){ goto insert_cleanup; } + dest.iSDParm = 0; /* Suppress a harmless compiler warning */ /* If the Select object is really just a simple VALUES() list with a ** single row (the common case) then keep that one row of values @@ -109901,7 +110930,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( #ifndef SQLITE_OMIT_CHECK if( pTab->pCheck && (db->flags & SQLITE_IgnoreChecks)==0 ){ ExprList *pCheck = pTab->pCheck; - pParse->ckBase = regNewData+1; + pParse->iSelfTab = -(regNewData+1); onError = overrideError!=OE_Default ? overrideError : OE_Abort; for(i=0; inExpr; i++){ int allOk; @@ -109921,6 +110950,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( } sqlite3VdbeResolveLabel(v, allOk); } + pParse->iSelfTab = 0; } #endif /* !defined(SQLITE_OMIT_CHECK) */ @@ -110065,10 +111095,10 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( /* Skip partial indices for which the WHERE clause is not true */ if( pIdx->pPartIdxWhere ){ sqlite3VdbeAddOp2(v, OP_Null, 0, aRegIdx[ix]); - pParse->ckBase = regNewData+1; + pParse->iSelfTab = -(regNewData+1); sqlite3ExprIfFalseDup(pParse, pIdx->pPartIdxWhere, addrUniqueOk, SQLITE_JUMPIFNULL); - pParse->ckBase = 0; + pParse->iSelfTab = 0; } /* Create a record for this index entry as it should appear after @@ -110079,9 +111109,9 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( int iField = pIdx->aiColumn[i]; int x; if( iField==XN_EXPR ){ - pParse->ckBase = regNewData+1; + pParse->iSelfTab = -(regNewData+1); sqlite3ExprCodeCopy(pParse, pIdx->aColExpr->a[i].pExpr, regIdx+i); - pParse->ckBase = 0; + pParse->iSelfTab = 0; VdbeComment((v, "%s column %d", pIdx->zName, i)); }else{ if( iField==XN_ROWID || iField==pTab->iPKey ){ @@ -110466,7 +111496,7 @@ static int xferCompatibleIndex(Index *pDest, Index *pSrc){ } if( pSrc->aiColumn[i]==XN_EXPR ){ assert( pSrc->aColExpr!=0 && pDest->aColExpr!=0 ); - if( sqlite3ExprCompare(pSrc->aColExpr->a[i].pExpr, + if( sqlite3ExprCompare(0, pSrc->aColExpr->a[i].pExpr, pDest->aColExpr->a[i].pExpr, -1)!=0 ){ return 0; /* Different expressions in the index */ } @@ -110478,7 +111508,7 @@ static int xferCompatibleIndex(Index *pDest, Index *pSrc){ return 0; /* Different collating sequences */ } } - if( sqlite3ExprCompare(pSrc->pPartIdxWhere, pDest->pPartIdxWhere, -1) ){ + if( sqlite3ExprCompare(0, pSrc->pPartIdxWhere, pDest->pPartIdxWhere, -1) ){ return 0; /* Different WHERE clauses */ } @@ -110958,11 +111988,8 @@ exec_out: rc = sqlite3ApiExit(db, rc); if( rc!=SQLITE_OK && pzErrMsg ){ - int nErrMsg = 1 + sqlite3Strlen30(sqlite3_errmsg(db)); - *pzErrMsg = sqlite3Malloc(nErrMsg); - if( *pzErrMsg ){ - memcpy(*pzErrMsg, sqlite3_errmsg(db), nErrMsg); - }else{ + *pzErrMsg = sqlite3DbStrDup(0, sqlite3_errmsg(db)); + if( *pzErrMsg==0 ){ rc = SQLITE_NOMEM_BKPT; sqlite3Error(db, SQLITE_NOMEM); } @@ -111283,6 +112310,14 @@ struct sqlite3_api_routines { char *(*expanded_sql)(sqlite3_stmt*); /* Version 3.18.0 and later */ void (*set_last_insert_rowid)(sqlite3*,sqlite3_int64); + /* Version 3.20.0 and later */ + int (*prepare_v3)(sqlite3*,const char*,int,unsigned int, + sqlite3_stmt**,const char**); + int (*prepare16_v3)(sqlite3*,const void*,int,unsigned int, + sqlite3_stmt**,const void**); + int (*bind_pointer)(sqlite3_stmt*,int,void*,const char*,void(*)(void*)); + void (*result_pointer)(sqlite3_context*,void*,const char*,void(*)(void*)); + void *(*value_pointer)(sqlite3_value*,const char*); }; /* @@ -111543,6 +112578,12 @@ typedef int (*sqlite3_loadext_entry)( #define sqlite3_expanded_sql sqlite3_api->expanded_sql /* Version 3.18.0 and later */ #define sqlite3_set_last_insert_rowid sqlite3_api->set_last_insert_rowid +/* Version 3.20.0 and later */ +#define sqlite3_prepare_v3 sqlite3_api->prepare_v3 +#define sqlite3_prepare16_v3 sqlite3_api->prepare16_v3 +#define sqlite3_bind_pointer sqlite3_api->bind_pointer +#define sqlite3_result_pointer sqlite3_api->result_pointer +#define sqlite3_value_pointer sqlite3_api->value_pointer #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) @@ -111598,6 +112639,7 @@ typedef int (*sqlite3_loadext_entry)( # define sqlite3_open16 0 # define sqlite3_prepare16 0 # define sqlite3_prepare16_v2 0 +# define sqlite3_prepare16_v3 0 # define sqlite3_result_error16 0 # define sqlite3_result_text16 0 # define sqlite3_result_text16be 0 @@ -111970,7 +113012,13 @@ static const sqlite3_api_routines sqlite3Apis = { sqlite3_trace_v2, sqlite3_expanded_sql, /* Version 3.18.0 and later */ - sqlite3_set_last_insert_rowid + sqlite3_set_last_insert_rowid, + /* Version 3.20.0 and later */ + sqlite3_prepare_v3, + sqlite3_prepare16_v3, + sqlite3_bind_pointer, + sqlite3_result_pointer, + sqlite3_value_pointer }; /* @@ -112392,35 +113440,38 @@ SQLITE_PRIVATE void sqlite3AutoLoadExtensions(sqlite3 *db){ #define PragTyp_ENCODING 12 #define PragTyp_FOREIGN_KEY_CHECK 13 #define PragTyp_FOREIGN_KEY_LIST 14 -#define PragTyp_INCREMENTAL_VACUUM 15 -#define PragTyp_INDEX_INFO 16 -#define PragTyp_INDEX_LIST 17 -#define PragTyp_INTEGRITY_CHECK 18 -#define PragTyp_JOURNAL_MODE 19 -#define PragTyp_JOURNAL_SIZE_LIMIT 20 -#define PragTyp_LOCK_PROXY_FILE 21 -#define PragTyp_LOCKING_MODE 22 -#define PragTyp_PAGE_COUNT 23 -#define PragTyp_MMAP_SIZE 24 -#define PragTyp_OPTIMIZE 25 -#define PragTyp_PAGE_SIZE 26 -#define PragTyp_SECURE_DELETE 27 -#define PragTyp_SHRINK_MEMORY 28 -#define PragTyp_SOFT_HEAP_LIMIT 29 -#define PragTyp_SYNCHRONOUS 30 -#define PragTyp_TABLE_INFO 31 -#define PragTyp_TEMP_STORE 32 -#define PragTyp_TEMP_STORE_DIRECTORY 33 -#define PragTyp_THREADS 34 -#define PragTyp_WAL_AUTOCHECKPOINT 35 -#define PragTyp_WAL_CHECKPOINT 36 -#define PragTyp_ACTIVATE_EXTENSIONS 37 -#define PragTyp_HEXKEY 38 -#define PragTyp_KEY 39 -#define PragTyp_REKEY 40 -#define PragTyp_LOCK_STATUS 41 -#define PragTyp_PARSER_TRACE 42 -#define PragTyp_STATS 43 +#define PragTyp_FUNCTION_LIST 15 +#define PragTyp_INCREMENTAL_VACUUM 16 +#define PragTyp_INDEX_INFO 17 +#define PragTyp_INDEX_LIST 18 +#define PragTyp_INTEGRITY_CHECK 19 +#define PragTyp_JOURNAL_MODE 20 +#define PragTyp_JOURNAL_SIZE_LIMIT 21 +#define PragTyp_LOCK_PROXY_FILE 22 +#define PragTyp_LOCKING_MODE 23 +#define PragTyp_PAGE_COUNT 24 +#define PragTyp_MMAP_SIZE 25 +#define PragTyp_MODULE_LIST 26 +#define PragTyp_OPTIMIZE 27 +#define PragTyp_PAGE_SIZE 28 +#define PragTyp_PRAGMA_LIST 29 +#define PragTyp_SECURE_DELETE 30 +#define PragTyp_SHRINK_MEMORY 31 +#define PragTyp_SOFT_HEAP_LIMIT 32 +#define PragTyp_SYNCHRONOUS 33 +#define PragTyp_TABLE_INFO 34 +#define PragTyp_TEMP_STORE 35 +#define PragTyp_TEMP_STORE_DIRECTORY 36 +#define PragTyp_THREADS 37 +#define PragTyp_WAL_AUTOCHECKPOINT 38 +#define PragTyp_WAL_CHECKPOINT 39 +#define PragTyp_ACTIVATE_EXTENSIONS 40 +#define PragTyp_HEXKEY 41 +#define PragTyp_KEY 42 +#define PragTyp_REKEY 43 +#define PragTyp_LOCK_STATUS 44 +#define PragTyp_PARSER_TRACE 45 +#define PragTyp_STATS 46 /* Property flags associated with various pragma. */ #define PragFlg_NeedSchema 0x01 /* Force schema load before running */ @@ -112466,26 +113517,29 @@ static const char *const pragCName[] = { /* 26 */ "seq", /* Used by: database_list */ /* 27 */ "name", /* 28 */ "file", - /* 29 */ "seq", /* Used by: collation_list */ - /* 30 */ "name", - /* 31 */ "id", /* Used by: foreign_key_list */ - /* 32 */ "seq", - /* 33 */ "table", - /* 34 */ "from", - /* 35 */ "to", - /* 36 */ "on_update", - /* 37 */ "on_delete", - /* 38 */ "match", - /* 39 */ "table", /* Used by: foreign_key_check */ - /* 40 */ "rowid", - /* 41 */ "parent", - /* 42 */ "fkid", - /* 43 */ "busy", /* Used by: wal_checkpoint */ - /* 44 */ "log", - /* 45 */ "checkpointed", - /* 46 */ "timeout", /* Used by: busy_timeout */ - /* 47 */ "database", /* Used by: lock_status */ - /* 48 */ "status", + /* 29 */ "name", /* Used by: function_list */ + /* 30 */ "builtin", + /* 31 */ "name", /* Used by: module_list pragma_list */ + /* 32 */ "seq", /* Used by: collation_list */ + /* 33 */ "name", + /* 34 */ "id", /* Used by: foreign_key_list */ + /* 35 */ "seq", + /* 36 */ "table", + /* 37 */ "from", + /* 38 */ "to", + /* 39 */ "on_update", + /* 40 */ "on_delete", + /* 41 */ "match", + /* 42 */ "table", /* Used by: foreign_key_check */ + /* 43 */ "rowid", + /* 44 */ "parent", + /* 45 */ "fkid", + /* 46 */ "busy", /* Used by: wal_checkpoint */ + /* 47 */ "log", + /* 48 */ "checkpointed", + /* 49 */ "timeout", /* Used by: busy_timeout */ + /* 50 */ "database", /* Used by: lock_status */ + /* 51 */ "status", }; /* Definitions of all built-in pragmas */ @@ -112531,7 +113585,7 @@ static const PragmaName aPragmaName[] = { {/* zName: */ "busy_timeout", /* ePragTyp: */ PragTyp_BUSY_TIMEOUT, /* ePragFlg: */ PragFlg_Result0, - /* ColNames: */ 46, 1, + /* ColNames: */ 49, 1, /* iArg: */ 0 }, #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) {/* zName: */ "cache_size", @@ -112568,7 +113622,7 @@ static const PragmaName aPragmaName[] = { {/* zName: */ "collation_list", /* ePragTyp: */ PragTyp_COLLATION_LIST, /* ePragFlg: */ PragFlg_Result0, - /* ColNames: */ 29, 2, + /* ColNames: */ 32, 2, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_COMPILEOPTION_DIAGS) @@ -112639,15 +113693,15 @@ static const PragmaName aPragmaName[] = { #if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER) {/* zName: */ "foreign_key_check", /* ePragTyp: */ PragTyp_FOREIGN_KEY_CHECK, - /* ePragFlg: */ PragFlg_NeedSchema, - /* ColNames: */ 39, 4, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0, + /* ColNames: */ 42, 4, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FOREIGN_KEY) {/* zName: */ "foreign_key_list", /* ePragTyp: */ PragTyp_FOREIGN_KEY_LIST, /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, - /* ColNames: */ 31, 8, + /* ColNames: */ 34, 8, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) @@ -112678,6 +113732,15 @@ static const PragmaName aPragmaName[] = { /* ColNames: */ 0, 0, /* iArg: */ SQLITE_FullFSync }, #endif +#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) +#if defined(SQLITE_INTROSPECTION_PRAGMAS) + {/* zName: */ "function_list", + /* ePragTyp: */ PragTyp_FUNCTION_LIST, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 29, 2, + /* iArg: */ 0 }, +#endif +#endif #if defined(SQLITE_HAS_CODEC) {/* zName: */ "hexkey", /* ePragTyp: */ PragTyp_HEXKEY, @@ -112726,7 +113789,7 @@ static const PragmaName aPragmaName[] = { #if !defined(SQLITE_OMIT_INTEGRITY_CHECK) {/* zName: */ "integrity_check", /* ePragTyp: */ PragTyp_INTEGRITY_CHECK, - /* ePragFlg: */ PragFlg_NeedSchema, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_Result1, /* ColNames: */ 0, 0, /* iArg: */ 0 }, #endif @@ -112767,7 +113830,7 @@ static const PragmaName aPragmaName[] = { {/* zName: */ "lock_status", /* ePragTyp: */ PragTyp_LOCK_STATUS, /* ePragFlg: */ PragFlg_Result0, - /* ColNames: */ 47, 2, + /* ColNames: */ 50, 2, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) @@ -112786,10 +113849,21 @@ static const PragmaName aPragmaName[] = { /* ePragFlg: */ 0, /* ColNames: */ 0, 0, /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) +#if !defined(SQLITE_OMIT_VIRTUALTABLE) +#if defined(SQLITE_INTROSPECTION_PRAGMAS) + {/* zName: */ "module_list", + /* ePragTyp: */ PragTyp_MODULE_LIST, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 31, 1, + /* iArg: */ 0 }, +#endif +#endif #endif {/* zName: */ "optimize", /* ePragTyp: */ PragTyp_OPTIMIZE, - /* ePragFlg: */ PragFlg_Result1, + /* ePragFlg: */ PragFlg_Result1|PragFlg_NeedSchema, /* ColNames: */ 0, 0, /* iArg: */ 0 }, #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) @@ -112811,6 +113885,13 @@ static const PragmaName aPragmaName[] = { /* ColNames: */ 0, 0, /* iArg: */ 0 }, #endif +#if defined(SQLITE_INTROSPECTION_PRAGMAS) + {/* zName: */ "pragma_list", + /* ePragTyp: */ PragTyp_PRAGMA_LIST, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 31, 1, + /* iArg: */ 0 }, +#endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) {/* zName: */ "query_only", /* ePragTyp: */ PragTyp_FLAG, @@ -112821,7 +113902,7 @@ static const PragmaName aPragmaName[] = { #if !defined(SQLITE_OMIT_INTEGRITY_CHECK) {/* zName: */ "quick_check", /* ePragTyp: */ PragTyp_INTEGRITY_CHECK, - /* ePragFlg: */ PragFlg_NeedSchema, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_Result1, /* ColNames: */ 0, 0, /* iArg: */ 0 }, #endif @@ -112830,7 +113911,7 @@ static const PragmaName aPragmaName[] = { /* ePragTyp: */ PragTyp_FLAG, /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, /* ColNames: */ 0, 0, - /* iArg: */ SQLITE_ReadUncommitted }, + /* iArg: */ SQLITE_ReadUncommit }, {/* zName: */ "recursive_triggers", /* ePragTyp: */ PragTyp_FLAG, /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, @@ -112974,7 +114055,7 @@ static const PragmaName aPragmaName[] = { {/* zName: */ "wal_checkpoint", /* ePragTyp: */ PragTyp_WAL_CHECKPOINT, /* ePragFlg: */ PragFlg_NeedSchema, - /* ColNames: */ 43, 3, + /* ColNames: */ 46, 3, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) @@ -112982,10 +114063,10 @@ static const PragmaName aPragmaName[] = { /* ePragTyp: */ PragTyp_FLAG, /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, /* ColNames: */ 0, 0, - /* iArg: */ SQLITE_WriteSchema|SQLITE_RecoveryMode }, + /* iArg: */ SQLITE_WriteSchema }, #endif }; -/* Number of pragmas: 60 on by default, 74 total. */ +/* Number of pragmas: 60 on by default, 77 total. */ /************** End of pragma.h **********************************************/ /************** Continuing where we left off in pragma.c *********************/ @@ -113475,18 +114556,22 @@ SQLITE_PRIVATE void sqlite3Pragma( /* ** PRAGMA [schema.]secure_delete - ** PRAGMA [schema.]secure_delete=ON/OFF + ** PRAGMA [schema.]secure_delete=ON/OFF/FAST ** ** The first form reports the current setting for the ** secure_delete flag. The second form changes the secure_delete - ** flag setting and reports thenew value. + ** flag setting and reports the new value. */ case PragTyp_SECURE_DELETE: { Btree *pBt = pDb->pBt; int b = -1; assert( pBt!=0 ); if( zRight ){ - b = sqlite3GetBoolean(zRight, 0); + if( sqlite3_stricmp(zRight, "fast")==0 ){ + b = 2; + }else{ + b = sqlite3GetBoolean(zRight, 0); + } } if( pId2->n==0 && b>=0 ){ int ii; @@ -114068,7 +115153,6 @@ SQLITE_PRIVATE void sqlite3Pragma( pCol->notNull ? 1 : 0, pCol->pDflt ? pCol->pDflt->u.zToken : 0, k); - sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 6); } } } @@ -114088,9 +115172,8 @@ SQLITE_PRIVATE void sqlite3Pragma( pTab->szTabRow, pTab->nRowLogEst, pTab->tabFlags); - sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 5); for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ - sqlite3VdbeMultiLoad(v, 2, "siii", + sqlite3VdbeMultiLoad(v, 2, "siiiX", pIdx->zName, pIdx->szIdxRow, pIdx->aiRowLogEst[0], @@ -114123,10 +115206,10 @@ SQLITE_PRIVATE void sqlite3Pragma( assert( pParse->nMem<=pPragma->nPragCName ); for(i=0; iaiColumn[i]; - sqlite3VdbeMultiLoad(v, 1, "iis", i, cnum, + sqlite3VdbeMultiLoad(v, 1, "iisX", i, cnum, cnum<0 ? 0 : pTab->aCol[cnum].zName); if( pPragma->iArg ){ - sqlite3VdbeMultiLoad(v, 4, "isi", + sqlite3VdbeMultiLoad(v, 4, "isiX", pIdx->aSortOrder[i], pIdx->azColl[i], inKeyCol); @@ -114153,7 +115236,6 @@ SQLITE_PRIVATE void sqlite3Pragma( IsUniqueIndex(pIdx), azOrigin[pIdx->idxType], pIdx->pPartIdxWhere!=0); - sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 5); } } } @@ -114169,7 +115251,6 @@ SQLITE_PRIVATE void sqlite3Pragma( i, db->aDb[i].zDbSName, sqlite3BtreeGetFilename(db->aDb[i].pBt)); - sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 3); } } break; @@ -114181,10 +115262,53 @@ SQLITE_PRIVATE void sqlite3Pragma( for(p=sqliteHashFirst(&db->aCollSeq); p; p=sqliteHashNext(p)){ CollSeq *pColl = (CollSeq *)sqliteHashData(p); sqlite3VdbeMultiLoad(v, 1, "is", i++, pColl->zName); + } + } + break; + +#ifdef SQLITE_INTROSPECTION_PRAGMAS + case PragTyp_FUNCTION_LIST: { + int i; + HashElem *j; + FuncDef *p; + pParse->nMem = 2; + for(i=0; iu.pHash ){ + sqlite3VdbeMultiLoad(v, 1, "si", p->zName, 1); + sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 2); + } + } + for(j=sqliteHashFirst(&db->aFunc); j; j=sqliteHashNext(j)){ + p = (FuncDef*)sqliteHashData(j); + sqlite3VdbeMultiLoad(v, 1, "si", p->zName, 0); sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 2); } } break; + +#ifndef SQLITE_OMIT_VIRTUALTABLE + case PragTyp_MODULE_LIST: { + HashElem *j; + pParse->nMem = 1; + for(j=sqliteHashFirst(&db->aModule); j; j=sqliteHashNext(j)){ + Module *pMod = (Module*)sqliteHashData(j); + sqlite3VdbeMultiLoad(v, 1, "s", pMod->zName); + sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1); + } + } + break; +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + + case PragTyp_PRAGMA_LIST: { + int i; + for(i=0; iaAction[1]), /* ON UPDATE */ actionName(pFK->aAction[0]), /* ON DELETE */ "NONE"); - sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 8); } ++i; pFK = pFK->pNextFrom; @@ -114289,34 +115412,38 @@ SQLITE_PRIVATE void sqlite3Pragma( assert( x==0 ); } addrOk = sqlite3VdbeMakeLabel(v); - if( pParent && pIdx==0 ){ - int iKey = pFK->aCol[0].iFrom; - assert( iKey>=0 && iKeynCol ); - if( iKey!=pTab->iPKey ){ - sqlite3VdbeAddOp3(v, OP_Column, 0, iKey, regRow); - sqlite3ColumnDefault(v, pTab, iKey, regRow); - sqlite3VdbeAddOp2(v, OP_IsNull, regRow, addrOk); VdbeCoverage(v); - }else{ - sqlite3VdbeAddOp2(v, OP_Rowid, 0, regRow); - } - sqlite3VdbeAddOp3(v, OP_SeekRowid, i, 0, regRow); VdbeCoverage(v); - sqlite3VdbeGoto(v, addrOk); - sqlite3VdbeJumpHere(v, sqlite3VdbeCurrentAddr(v)-2); - }else{ - for(j=0; jnCol; j++){ - sqlite3ExprCodeGetColumnOfTable(v, pTab, 0, - aiCols ? aiCols[j] : pFK->aCol[j].iFrom, regRow+j); - sqlite3VdbeAddOp2(v, OP_IsNull, regRow+j, addrOk); VdbeCoverage(v); - } - if( pParent ){ - sqlite3VdbeAddOp4(v, OP_MakeRecord, regRow, pFK->nCol, regKey, - sqlite3IndexAffinityStr(db,pIdx), pFK->nCol); - sqlite3VdbeAddOp4Int(v, OP_Found, i, addrOk, regKey, 0); - VdbeCoverage(v); - } + + /* Generate code to read the child key values into registers + ** regRow..regRow+n. If any of the child key values are NULL, this + ** row cannot cause an FK violation. Jump directly to addrOk in + ** this case. */ + for(j=0; jnCol; j++){ + int iCol = aiCols ? aiCols[j] : pFK->aCol[j].iFrom; + sqlite3ExprCodeGetColumnOfTable(v, pTab, 0, iCol, regRow+j); + sqlite3VdbeAddOp2(v, OP_IsNull, regRow+j, addrOk); VdbeCoverage(v); } - sqlite3VdbeAddOp2(v, OP_Rowid, 0, regResult+1); - sqlite3VdbeMultiLoad(v, regResult+2, "si", pFK->zTo, i-1); + + /* Generate code to query the parent index for a matching parent + ** key. If a match is found, jump to addrOk. */ + if( pIdx ){ + sqlite3VdbeAddOp4(v, OP_MakeRecord, regRow, pFK->nCol, regKey, + sqlite3IndexAffinityStr(db,pIdx), pFK->nCol); + sqlite3VdbeAddOp4Int(v, OP_Found, i, addrOk, regKey, 0); + VdbeCoverage(v); + }else if( pParent ){ + int jmp = sqlite3VdbeCurrentAddr(v)+2; + sqlite3VdbeAddOp3(v, OP_SeekRowid, i, jmp, regRow); VdbeCoverage(v); + sqlite3VdbeGoto(v, addrOk); + assert( pFK->nCol==1 ); + } + + /* Generate code to report an FK violation to the caller. */ + if( HasRowid(pTab) ){ + sqlite3VdbeAddOp2(v, OP_Rowid, 0, regResult+1); + }else{ + sqlite3VdbeAddOp2(v, OP_Null, 0, regResult+1); + } + sqlite3VdbeMultiLoad(v, regResult+2, "siX", pFK->zTo, i-1); sqlite3VdbeAddOp2(v, OP_ResultRow, regResult, 4); sqlite3VdbeResolveLabel(v, addrOk); sqlite3DbFree(db, aiCols); @@ -114442,6 +115569,7 @@ SQLITE_PRIVATE void sqlite3Pragma( /* Make sure sufficient number of registers have been allocated */ pParse->nMem = MAX( pParse->nMem, 8+mxIdx ); + sqlite3ClearTempRegCache(pParse); /* Do the b-tree integrity checks */ sqlite3VdbeAddOp4(v, OP_IntegrityCk, 2, cnt, 1, (char*)aRoot,P4_INTARRAY); @@ -114501,25 +115629,29 @@ SQLITE_PRIVATE void sqlite3Pragma( } /* Verify CHECK constraints */ if( pTab->pCheck && (db->flags & SQLITE_IgnoreChecks)==0 ){ - int addrCkFault = sqlite3VdbeMakeLabel(v); - int addrCkOk = sqlite3VdbeMakeLabel(v); - ExprList *pCheck = pTab->pCheck; - char *zErr; - int k; - pParse->iSelfTab = iDataCur; - sqlite3ExprCachePush(pParse); - for(k=pCheck->nExpr-1; k>0; k--){ - sqlite3ExprIfFalse(pParse, pCheck->a[k].pExpr, addrCkFault, 0); + ExprList *pCheck = sqlite3ExprListDup(db, pTab->pCheck, 0); + if( db->mallocFailed==0 ){ + int addrCkFault = sqlite3VdbeMakeLabel(v); + int addrCkOk = sqlite3VdbeMakeLabel(v); + char *zErr; + int k; + pParse->iSelfTab = iDataCur + 1; + sqlite3ExprCachePush(pParse); + for(k=pCheck->nExpr-1; k>0; k--){ + sqlite3ExprIfFalse(pParse, pCheck->a[k].pExpr, addrCkFault, 0); + } + sqlite3ExprIfTrue(pParse, pCheck->a[0].pExpr, addrCkOk, + SQLITE_JUMPIFNULL); + sqlite3VdbeResolveLabel(v, addrCkFault); + pParse->iSelfTab = 0; + zErr = sqlite3MPrintf(db, "CHECK constraint failed in %s", + pTab->zName); + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); + integrityCheckResultRow(v, 3); + sqlite3VdbeResolveLabel(v, addrCkOk); + sqlite3ExprCachePop(pParse); } - sqlite3ExprIfTrue(pParse, pCheck->a[0].pExpr, addrCkOk, - SQLITE_JUMPIFNULL); - sqlite3VdbeResolveLabel(v, addrCkFault); - zErr = sqlite3MPrintf(db, "CHECK constraint failed in %s", - pTab->zName); - sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); - integrityCheckResultRow(v, 3); - sqlite3VdbeResolveLabel(v, addrCkOk); - sqlite3ExprCachePop(pParse); + sqlite3ExprListDelete(db, pCheck); } /* Validate index entries for the current row */ for(j=0, pIdx=pTab->pIndex; pIdx && !isQuick; pIdx=pIdx->pNext, j++){ @@ -114853,7 +115985,8 @@ SQLITE_PRIVATE void sqlite3Pragma( ** 0x0008 (Not yet implemented) Create indexes that might have ** been helpful to recent queries ** - ** The default MASK is and always shall be 0xfffe. 0xfffe means perform all ** of the optimizations listed above except Debug Mode, including new + ** The default MASK is and always shall be 0xfffe. 0xfffe means perform all + ** of the optimizations listed above except Debug Mode, including new ** optimizations that have not yet been invented. If new optimizations are ** ever added that should be off by default, those off-by-default ** optimizations will have bitmasks of 0x10000 or larger. @@ -115015,7 +116148,6 @@ SQLITE_PRIVATE void sqlite3Pragma( zState = azLockName[j]; } sqlite3VdbeMultiLoad(v, 1, "ss", db->aDb[i].zDbSName, zState); - sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 2); } break; } @@ -115281,10 +116413,14 @@ static int pragmaVtabFilter( pragmaVtabCursorClear(pCsr); j = (pTab->pName->mPragFlg & PragFlg_Result1)!=0 ? 0 : 1; for(i=0; iazArg) ); - pCsr->azArg[j] = sqlite3_mprintf("%s", sqlite3_value_text(argv[i])); - if( pCsr->azArg[j]==0 ){ - return SQLITE_NOMEM; + assert( pCsr->azArg[j]==0 ); + if( zText ){ + pCsr->azArg[j] = sqlite3_mprintf("%s", zText); + if( pCsr->azArg[j]==0 ){ + return SQLITE_NOMEM; + } } } sqlite3StrAccumInit(&acc, 0, 0, 0, pTab->db->aLimit[SQLITE_LIMIT_SQL_LENGTH]); @@ -115417,7 +116553,7 @@ static void corruptSchema( const char *zExtra /* Error information */ ){ sqlite3 *db = pData->db; - if( !db->mallocFailed && (db->flags & SQLITE_RecoveryMode)==0 ){ + if( !db->mallocFailed && (db->flags & SQLITE_WriteSchema)==0 ){ char *z; if( zObj==0 ) zObj = "?"; z = sqlite3MPrintf(db, "malformed database schema (%s)", zObj); @@ -115704,8 +116840,8 @@ static int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg){ rc = SQLITE_NOMEM_BKPT; sqlite3ResetAllSchemasOfConnection(db); } - if( rc==SQLITE_OK || (db->flags&SQLITE_RecoveryMode)){ - /* Black magic: If the SQLITE_RecoveryMode flag is set, then consider + if( rc==SQLITE_OK || (db->flags&SQLITE_WriteSchema)){ + /* Black magic: If the SQLITE_WriteSchema flag is set, then consider ** the schema loaded, even if errors occurred. In this situation the ** current sqlite3_prepare() operation will fail, but the following one ** will attempt to compile the supplied statement against whatever subset @@ -115905,7 +117041,7 @@ static int sqlite3Prepare( sqlite3 *db, /* Database handle. */ const char *zSql, /* UTF-8 encoded SQL statement. */ int nBytes, /* Length of zSql in bytes. */ - int saveSqlFlag, /* True to copy SQL text into the sqlite3_stmt */ + u32 prepFlags, /* Zero or more SQLITE_PREPARE_* flags */ Vdbe *pReprepare, /* VM being reprepared */ sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ const char **pzTail /* OUT: End of parsed string */ @@ -115922,6 +117058,14 @@ static int sqlite3Prepare( /* assert( !db->mallocFailed ); // not true with SQLITE_USE_ALLOCA */ assert( sqlite3_mutex_held(db->mutex) ); + /* For a long-term use prepared statement avoid the use of + ** lookaside memory. + */ + if( prepFlags & SQLITE_PREPARE_PERSISTENT ){ + sParse.disableLookaside++; + db->lookaside.bDisable++; + } + /* Check to verify that it is possible to get a read lock on all ** database schemas. The inability to get a read lock indicates that ** some other database connection is holding a write-lock, which in @@ -115953,7 +117097,7 @@ static int sqlite3Prepare( if( rc ){ const char *zDb = db->aDb[i].zDbSName; sqlite3ErrorWithMsg(db, rc, "database schema is locked: %s", zDb); - testcase( db->flags & SQLITE_ReadUncommitted ); + testcase( db->flags & SQLITE_ReadUncommit ); goto end_prepare; } } @@ -116021,8 +117165,7 @@ static int sqlite3Prepare( #endif if( db->init.busy==0 ){ - Vdbe *pVdbe = sParse.pVdbe; - sqlite3VdbeSetSql(pVdbe, zSql, (int)(sParse.zTail-zSql), saveSqlFlag); + sqlite3VdbeSetSql(sParse.pVdbe, zSql, (int)(sParse.zTail-zSql), prepFlags); } if( sParse.pVdbe && (rc!=SQLITE_OK || db->mallocFailed) ){ sqlite3VdbeFinalize(sParse.pVdbe); @@ -116056,7 +117199,7 @@ static int sqlite3LockAndPrepare( sqlite3 *db, /* Database handle. */ const char *zSql, /* UTF-8 encoded SQL statement. */ int nBytes, /* Length of zSql in bytes. */ - int saveSqlFlag, /* True to copy SQL text into the sqlite3_stmt */ + u32 prepFlags, /* Zero or more SQLITE_PREPARE_* flags */ Vdbe *pOld, /* VM being reprepared */ sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ const char **pzTail /* OUT: End of parsed string */ @@ -116072,10 +117215,10 @@ static int sqlite3LockAndPrepare( } sqlite3_mutex_enter(db->mutex); sqlite3BtreeEnterAll(db); - rc = sqlite3Prepare(db, zSql, nBytes, saveSqlFlag, pOld, ppStmt, pzTail); + rc = sqlite3Prepare(db, zSql, nBytes, prepFlags, pOld, ppStmt, pzTail); if( rc==SQLITE_SCHEMA ){ sqlite3_finalize(*ppStmt); - rc = sqlite3Prepare(db, zSql, nBytes, saveSqlFlag, pOld, ppStmt, pzTail); + rc = sqlite3Prepare(db, zSql, nBytes, prepFlags, pOld, ppStmt, pzTail); } sqlite3BtreeLeaveAll(db); sqlite3_mutex_leave(db->mutex); @@ -116096,13 +117239,15 @@ SQLITE_PRIVATE int sqlite3Reprepare(Vdbe *p){ sqlite3_stmt *pNew; const char *zSql; sqlite3 *db; + u8 prepFlags; assert( sqlite3_mutex_held(sqlite3VdbeDb(p)->mutex) ); zSql = sqlite3_sql((sqlite3_stmt *)p); assert( zSql!=0 ); /* Reprepare only called for prepare_v2() statements */ db = sqlite3VdbeDb(p); assert( sqlite3_mutex_held(db->mutex) ); - rc = sqlite3LockAndPrepare(db, zSql, -1, 0, p, &pNew, 0); + prepFlags = sqlite3VdbePrepareFlags(p); + rc = sqlite3LockAndPrepare(db, zSql, -1, prepFlags, p, &pNew, 0); if( rc ){ if( rc==SQLITE_NOMEM ){ sqlite3OomFault(db); @@ -116148,8 +117293,36 @@ SQLITE_API int sqlite3_prepare_v2( const char **pzTail /* OUT: End of parsed string */ ){ int rc; - rc = sqlite3LockAndPrepare(db,zSql,nBytes,1,0,ppStmt,pzTail); - assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 ); /* VERIFY: F13021 */ + /* EVIDENCE-OF: R-37923-12173 The sqlite3_prepare_v2() interface works + ** exactly the same as sqlite3_prepare_v3() with a zero prepFlags + ** parameter. + ** + ** Proof in that the 5th parameter to sqlite3LockAndPrepare is 0 */ + rc = sqlite3LockAndPrepare(db,zSql,nBytes,SQLITE_PREPARE_SAVESQL,0, + ppStmt,pzTail); + assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 ); + return rc; +} +SQLITE_API int sqlite3_prepare_v3( + sqlite3 *db, /* Database handle. */ + const char *zSql, /* UTF-8 encoded SQL statement. */ + int nBytes, /* Length of zSql in bytes. */ + unsigned int prepFlags, /* Zero or more SQLITE_PREPARE_* flags */ + sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ + const char **pzTail /* OUT: End of parsed string */ +){ + int rc; + /* EVIDENCE-OF: R-56861-42673 sqlite3_prepare_v3() differs from + ** sqlite3_prepare_v2() only in having the extra prepFlags parameter, + ** which is a bit array consisting of zero or more of the + ** SQLITE_PREPARE_* flags. + ** + ** Proof by comparison to the implementation of sqlite3_prepare_v2() + ** directly above. */ + rc = sqlite3LockAndPrepare(db,zSql,nBytes, + SQLITE_PREPARE_SAVESQL|(prepFlags&SQLITE_PREPARE_MASK), + 0,ppStmt,pzTail); + assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 ); return rc; } @@ -116162,7 +117335,7 @@ static int sqlite3Prepare16( sqlite3 *db, /* Database handle. */ const void *zSql, /* UTF-16 encoded SQL statement. */ int nBytes, /* Length of zSql in bytes. */ - int saveSqlFlag, /* True to save SQL text into the sqlite3_stmt */ + u32 prepFlags, /* Zero or more SQLITE_PREPARE_* flags */ sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ const void **pzTail /* OUT: End of parsed string */ ){ @@ -116190,7 +117363,7 @@ static int sqlite3Prepare16( sqlite3_mutex_enter(db->mutex); zSql8 = sqlite3Utf16to8(db, zSql, nBytes, SQLITE_UTF16NATIVE); if( zSql8 ){ - rc = sqlite3LockAndPrepare(db, zSql8, -1, saveSqlFlag, 0, ppStmt, &zTail8); + rc = sqlite3LockAndPrepare(db, zSql8, -1, prepFlags, 0, ppStmt, &zTail8); } if( zTail8 && pzTail ){ @@ -116236,7 +117409,22 @@ SQLITE_API int sqlite3_prepare16_v2( const void **pzTail /* OUT: End of parsed string */ ){ int rc; - rc = sqlite3Prepare16(db,zSql,nBytes,1,ppStmt,pzTail); + rc = sqlite3Prepare16(db,zSql,nBytes,SQLITE_PREPARE_SAVESQL,ppStmt,pzTail); + assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 ); /* VERIFY: F13021 */ + return rc; +} +SQLITE_API int sqlite3_prepare16_v3( + sqlite3 *db, /* Database handle. */ + const void *zSql, /* UTF-16 encoded SQL statement. */ + int nBytes, /* Length of zSql in bytes. */ + unsigned int prepFlags, /* Zero or more SQLITE_PREPARE_* flags */ + sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ + const void **pzTail /* OUT: End of parsed string */ +){ + int rc; + rc = sqlite3Prepare16(db,zSql,nBytes, + SQLITE_PREPARE_SAVESQL|(prepFlags&SQLITE_PREPARE_MASK), + ppStmt,pzTail); assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 ); /* VERIFY: F13021 */ return rc; } @@ -116323,7 +117511,7 @@ static void clearSelect(sqlite3 *db, Select *p, int bFree){ sqlite3ExprDelete(db, p->pLimit); sqlite3ExprDelete(db, p->pOffset); if( p->pWith ) sqlite3WithDelete(db, p->pWith); - if( bFree ) sqlite3DbFree(db, p); + if( bFree ) sqlite3DbFreeNN(db, p); p = pPrior; bFree = 1; } @@ -116359,14 +117547,13 @@ SQLITE_PRIVATE Select *sqlite3SelectNew( ){ Select *pNew; Select standin; - sqlite3 *db = pParse->db; - pNew = sqlite3DbMallocRawNN(db, sizeof(*pNew) ); + pNew = sqlite3DbMallocRawNN(pParse->db, sizeof(*pNew) ); if( pNew==0 ){ - assert( db->mallocFailed ); + assert( pParse->db->mallocFailed ); pNew = &standin; } if( pEList==0 ){ - pEList = sqlite3ExprListAppend(pParse, 0, sqlite3Expr(db,TK_ASTERISK,0)); + pEList = sqlite3ExprListAppend(pParse, 0, sqlite3Expr(pParse->db,TK_ASTERISK,0)); } pNew->pEList = pEList; pNew->op = TK_SELECT; @@ -116379,7 +117566,7 @@ SQLITE_PRIVATE Select *sqlite3SelectNew( pNew->addrOpenEphm[0] = -1; pNew->addrOpenEphm[1] = -1; pNew->nSelectRow = 0; - if( pSrc==0 ) pSrc = sqlite3DbMallocZero(db, sizeof(*pSrc)); + if( pSrc==0 ) pSrc = sqlite3DbMallocZero(pParse->db, sizeof(*pSrc)); pNew->pSrc = pSrc; pNew->pWhere = pWhere; pNew->pGroupBy = pGroupBy; @@ -116390,9 +117577,9 @@ SQLITE_PRIVATE Select *sqlite3SelectNew( pNew->pLimit = pLimit; pNew->pOffset = pOffset; pNew->pWith = 0; - assert( pOffset==0 || pLimit!=0 || pParse->nErr>0 || db->mallocFailed!=0 ); - if( db->mallocFailed ) { - clearSelect(db, pNew, pNew!=&standin); + assert( pOffset==0 || pLimit!=0 || pParse->nErr>0 || pParse->db->mallocFailed!=0 ); + if( pParse->db->mallocFailed ) { + clearSelect(pParse->db, pNew, pNew!=&standin); pNew = 0; }else{ assert( pNew->pSrc!=0 || pParse->nErr>0 ); @@ -117279,7 +118466,7 @@ static void selectInnerLoop( ** X extra columns. */ SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoAlloc(sqlite3 *db, int N, int X){ - int nExtra = (N+X)*(sizeof(CollSeq*)+1); + int nExtra = (N+X)*(sizeof(CollSeq*)+1) - sizeof(CollSeq*); KeyInfo *p = sqlite3DbMallocRawNN(db, sizeof(KeyInfo) + nExtra); if( p ){ p->aSortOrder = (u8*)&p->aColl[N+X]; @@ -117302,7 +118489,7 @@ SQLITE_PRIVATE void sqlite3KeyInfoUnref(KeyInfo *p){ if( p ){ assert( p->nRef>0 ); p->nRef--; - if( p->nRef==0 ) sqlite3DbFree(p->db, p); + if( p->nRef==0 ) sqlite3DbFreeNN(p->db, p); } } @@ -117686,13 +118873,10 @@ static const char *columnTypeImpl( ** of the SELECT statement. Return the declaration type and origin ** data for the result-set column of the sub-select. */ - if( iCol>=0 && ALWAYS(iColpEList->nExpr) ){ + if( iCol>=0 && iColpEList->nExpr ){ /* If iCol is less than zero, then the expression requests the ** rowid of the sub-select or view. This expression is legal (see ** test case misc2.2.2) - it always evaluates to NULL. - ** - ** The ALWAYS() is because iCol>=pS->pEList->nExpr will have been - ** caught already by name resolution. */ NameContext sNC; Expr *p = pS->pEList->a[iCol].pExpr; @@ -117777,6 +118961,7 @@ static void generateColumnTypes( NameContext sNC; sNC.pSrcList = pTabList; sNC.pParse = pParse; + sNC.pNext = 0; for(i=0; inExpr; i++){ Expr *p = pEList->a[i].pExpr; const char *zType; @@ -117801,20 +118986,49 @@ static void generateColumnTypes( #endif /* !defined(SQLITE_OMIT_DECLTYPE) */ } + /* -** Generate code that will tell the VDBE the names of columns -** in the result set. This information is used to provide the -** azCol[] values in the callback. +** Compute the column names for a SELECT statement. +** +** The only guarantee that SQLite makes about column names is that if the +** column has an AS clause assigning it a name, that will be the name used. +** That is the only documented guarantee. However, countless applications +** developed over the years have made baseless assumptions about column names +** and will break if those assumptions changes. Hence, use extreme caution +** when modifying this routine to avoid breaking legacy. +** +** See Also: sqlite3ColumnsFromExprList() +** +** The PRAGMA short_column_names and PRAGMA full_column_names settings are +** deprecated. The default setting is short=ON, full=OFF. 99.9% of all +** applications should operate this way. Nevertheless, we need to support the +** other modes for legacy: +** +** short=OFF, full=OFF: Column name is the text of the expression has it +** originally appears in the SELECT statement. In +** other words, the zSpan of the result expression. +** +** short=ON, full=OFF: (This is the default setting). If the result +** refers directly to a table column, then the result +** column name is just the table column name: COLUMN. +** Otherwise use zSpan. +** +** full=ON, short=ANY: If the result refers directly to a table column, +** then the result column name with the table name +** prefix, ex: TABLE.COLUMN. Otherwise use zSpan. */ static void generateColumnNames( Parse *pParse, /* Parser context */ - SrcList *pTabList, /* List of tables */ - ExprList *pEList /* Expressions defining the result set */ + Select *pSelect /* Generate column names for this SELECT statement */ ){ Vdbe *v = pParse->pVdbe; - int i, j; + int i; + Table *pTab; + SrcList *pTabList; + ExprList *pEList; sqlite3 *db = pParse->db; - int fullNames, shortNames; + int fullName; /* TABLE.COLUMN if no AS clause and is a direct table ref */ + int srcName; /* COLUMN or TABLE.COLUMN if no AS clause and is direct */ #ifndef SQLITE_OMIT_EXPLAIN /* If this is an EXPLAIN, skip this step */ @@ -117824,28 +119038,29 @@ static void generateColumnNames( #endif if( pParse->colNamesSet || db->mallocFailed ) return; + /* Column names are determined by the left-most term of a compound select */ + while( pSelect->pPrior ) pSelect = pSelect->pPrior; + pTabList = pSelect->pSrc; + pEList = pSelect->pEList; assert( v!=0 ); assert( pTabList!=0 ); pParse->colNamesSet = 1; - fullNames = (db->flags & SQLITE_FullColNames)!=0; - shortNames = (db->flags & SQLITE_ShortColNames)!=0; + fullName = (db->flags & SQLITE_FullColNames)!=0; + srcName = (db->flags & SQLITE_ShortColNames)!=0 || fullName; sqlite3VdbeSetNumCols(v, pEList->nExpr); for(i=0; inExpr; i++){ - Expr *p; - p = pEList->a[i].pExpr; - if( NEVER(p==0) ) continue; + Expr *p = pEList->a[i].pExpr; + + assert( p!=0 ); if( pEList->a[i].zName ){ + /* An AS clause always takes first priority */ char *zName = pEList->a[i].zName; sqlite3VdbeSetColName(v, i, COLNAME_NAME, zName, SQLITE_TRANSIENT); - }else if( p->op==TK_COLUMN || p->op==TK_AGG_COLUMN ){ - Table *pTab; + }else if( srcName && p->op==TK_COLUMN ){ char *zCol; int iCol = p->iColumn; - for(j=0; ALWAYS(jnSrc); j++){ - if( pTabList->a[j].iCursor==p->iTable ) break; - } - assert( jnSrc ); - pTab = pTabList->a[j].pTab; + pTab = p->pTab; + assert( pTab!=0 ); if( iCol<0 ) iCol = pTab->iPKey; assert( iCol==-1 || (iCol>=0 && iColnCol) ); if( iCol<0 ){ @@ -117853,10 +119068,7 @@ static void generateColumnNames( }else{ zCol = pTab->aCol[iCol].zName; } - if( !shortNames && !fullNames ){ - sqlite3VdbeSetColName(v, i, COLNAME_NAME, - sqlite3DbStrDup(db, pEList->a[i].zSpan), SQLITE_DYNAMIC); - }else if( fullNames ){ + if( fullName ){ char *zName = 0; zName = sqlite3MPrintf(db, "%s.%s", pTab->zName, zCol); sqlite3VdbeSetColName(v, i, COLNAME_NAME, zName, SQLITE_DYNAMIC); @@ -117884,6 +119096,15 @@ static void generateColumnNames( ** ** Return SQLITE_OK on success. If a memory allocation error occurs, ** store NULL in *paCol and 0 in *pnCol and return SQLITE_NOMEM. +** +** The only guarantee that SQLite makes about column names is that if the +** column has an AS clause assigning it a name, that will be the name used. +** That is the only documented guarantee. However, countless applications +** developed over the years have made baseless assumptions about column names +** and will break if those assumptions changes. Hence, use extreme caution +** when modifying this routine to avoid breaking legacy. +** +** See Also: generateColumnNames() */ SQLITE_PRIVATE int sqlite3ColumnsFromExprList( Parse *pParse, /* Parsing context */ @@ -117896,7 +119117,6 @@ SQLITE_PRIVATE int sqlite3ColumnsFromExprList( u32 cnt; /* Index added to make the name unique */ Column *aCol, *pCol; /* For looping over result columns */ int nCol; /* Number of columns in the result set */ - Expr *p; /* Expression for a single result column */ char *zName; /* Column name */ int nName; /* Size of name in zName[] */ Hash ht; /* Hash table of column names */ @@ -117917,20 +119137,18 @@ SQLITE_PRIVATE int sqlite3ColumnsFromExprList( for(i=0, pCol=aCol; imallocFailed; i++, pCol++){ /* Get an appropriate name for the column */ - p = sqlite3ExprSkipCollate(pEList->a[i].pExpr); if( (zName = pEList->a[i].zName)!=0 ){ /* If the column contains an "AS " phrase, use as the name */ }else{ - Expr *pColExpr = p; /* The expression that is the result column name */ - Table *pTab; /* Table associated with this expression */ + Expr *pColExpr = sqlite3ExprSkipCollate(pEList->a[i].pExpr); while( pColExpr->op==TK_DOT ){ pColExpr = pColExpr->pRight; assert( pColExpr!=0 ); } - if( pColExpr->op==TK_COLUMN && ALWAYS(pColExpr->pTab!=0) ){ + if( pColExpr->op==TK_COLUMN && pColExpr->pTab!=0 ){ /* For columns use the column name name */ int iCol = pColExpr->iColumn; - pTab = pColExpr->pTab; + Table *pTab = pColExpr->pTab; if( iCol<0 ) iCol = pTab->iPKey; zName = iCol>=0 ? pTab->aCol[iCol].zName : "rowid"; }else if( pColExpr->op==TK_ID ){ @@ -117941,7 +119159,11 @@ SQLITE_PRIVATE int sqlite3ColumnsFromExprList( zName = pEList->a[i].zSpan; } } - zName = sqlite3MPrintf(db, "%s", zName); + if( zName ){ + zName = sqlite3DbStrDup(db, zName); + }else{ + zName = sqlite3MPrintf(db,"column%d",i+1); + } /* Make sure the column name is unique. If the name is not unique, ** append an integer to the name so that it becomes unique. @@ -118670,11 +119892,6 @@ static int multiSelect( if( dest.eDest!=priorOp ){ int iCont, iBreak, iStart; assert( p->pEList ); - if( dest.eDest==SRT_Output ){ - Select *pFirst = p; - while( pFirst->pPrior ) pFirst = pFirst->pPrior; - generateColumnNames(pParse, pFirst->pSrc, pFirst->pEList); - } iBreak = sqlite3VdbeMakeLabel(v); iCont = sqlite3VdbeMakeLabel(v); computeLimitRegisters(pParse, p, iBreak); @@ -118745,11 +119962,6 @@ static int multiSelect( ** tables. */ assert( p->pEList ); - if( dest.eDest==SRT_Output ){ - Select *pFirst = p; - while( pFirst->pPrior ) pFirst = pFirst->pPrior; - generateColumnNames(pParse, pFirst->pSrc, pFirst->pEList); - } iBreak = sqlite3VdbeMakeLabel(v); iCont = sqlite3VdbeMakeLabel(v); computeLimitRegisters(pParse, p, iBreak); @@ -119147,7 +120359,7 @@ static int multiSelectOrderBy( if( pNew==0 ) return SQLITE_NOMEM_BKPT; pNew->flags |= EP_IntValue; pNew->u.iValue = i; - pOrderBy = sqlite3ExprListAppend(pParse, pOrderBy, pNew); + p->pOrderBy = pOrderBy = sqlite3ExprListAppend(pParse, pOrderBy, pNew); if( pOrderBy ) pOrderBy->a[nOrderBy++].u.x.iOrderByCol = (u16)i; } } @@ -119357,14 +120569,6 @@ static int multiSelectOrderBy( */ sqlite3VdbeResolveLabel(v, labelEnd); - /* Set the number of output columns - */ - if( pDest->eDest==SRT_Output ){ - Select *pFirst = pPrior; - while( pFirst->pPrior ) pFirst = pFirst->pPrior; - generateColumnNames(pParse, pFirst->pSrc, pFirst->pEList); - } - /* Reassembly the compound query so that it will be freed correctly ** by the calling function */ if( p->pPrior ){ @@ -119381,9 +120585,24 @@ static int multiSelectOrderBy( #endif #if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) + +/* An instance of the SubstContext object describes an substitution edit +** to be performed on a parse tree. +** +** All references to columns in table iTable are to be replaced by corresponding +** expressions in pEList. +*/ +typedef struct SubstContext { + Parse *pParse; /* The parsing context */ + int iTable; /* Replace references to this table */ + int iNewTable; /* New table number */ + int isLeftJoin; /* Add TK_IF_NULL_ROW opcodes on each replacement */ + ExprList *pEList; /* Replacement expressions */ +} SubstContext; + /* Forward Declarations */ -static void substExprList(Parse*, ExprList*, int, ExprList*); -static void substSelect(Parse*, Select *, int, ExprList*, int); +static void substExprList(SubstContext*, ExprList*); +static void substSelect(SubstContext*, Select*, int); /* ** Scan through the expression pExpr. Replace every reference to @@ -119394,84 +120613,95 @@ static void substSelect(Parse*, Select *, int, ExprList*, int); ** This routine is part of the flattening procedure. A subquery ** whose result set is defined by pEList appears as entry in the ** FROM clause of a SELECT such that the VDBE cursor assigned to that -** FORM clause entry is iTable. This routine make the necessary +** FORM clause entry is iTable. This routine makes the necessary ** changes to pExpr so that it refers directly to the source table ** of the subquery rather the result set of the subquery. */ static Expr *substExpr( - Parse *pParse, /* Report errors here */ - Expr *pExpr, /* Expr in which substitution occurs */ - int iTable, /* Table to be substituted */ - ExprList *pEList /* Substitute expressions */ + SubstContext *pSubst, /* Description of the substitution */ + Expr *pExpr /* Expr in which substitution occurs */ ){ - sqlite3 *db = pParse->db; if( pExpr==0 ) return 0; - if( pExpr->op==TK_COLUMN && pExpr->iTable==iTable ){ + if( ExprHasProperty(pExpr, EP_FromJoin) && pExpr->iRightJoinTable==pSubst->iTable ){ + pExpr->iRightJoinTable = pSubst->iNewTable; + } + if( pExpr->op==TK_COLUMN && pExpr->iTable==pSubst->iTable ){ if( pExpr->iColumn<0 ){ pExpr->op = TK_NULL; }else{ Expr *pNew; - Expr *pCopy = pEList->a[pExpr->iColumn].pExpr; - assert( pEList!=0 && pExpr->iColumnnExpr ); + Expr *pCopy = pSubst->pEList->a[pExpr->iColumn].pExpr; + Expr ifNullRow; + assert( pSubst->pEList!=0 && pExpr->iColumnpEList->nExpr ); assert( pExpr->pLeft==0 && pExpr->pRight==0 ); if( sqlite3ExprIsVector(pCopy) ){ - sqlite3VectorErrorMsg(pParse, pCopy); + sqlite3VectorErrorMsg(pSubst->pParse, pCopy); }else{ + sqlite3 *db = pSubst->pParse->db; + if( pSubst->isLeftJoin && pCopy->op!=TK_COLUMN ){ + memset(&ifNullRow, 0, sizeof(ifNullRow)); + ifNullRow.op = TK_IF_NULL_ROW; + ifNullRow.pLeft = pCopy; + ifNullRow.iTable = pSubst->iNewTable; + pCopy = &ifNullRow; + } pNew = sqlite3ExprDup(db, pCopy, 0); - if( pNew && (pExpr->flags & EP_FromJoin) ){ + if( pNew && pSubst->isLeftJoin ){ + ExprSetProperty(pNew, EP_CanBeNull); + } + if( pNew && ExprHasProperty(pExpr,EP_FromJoin) ){ pNew->iRightJoinTable = pExpr->iRightJoinTable; - pNew->flags |= EP_FromJoin; + ExprSetProperty(pNew, EP_FromJoin); } sqlite3ExprDelete(db, pExpr); pExpr = pNew; } } }else{ - pExpr->pLeft = substExpr(pParse, pExpr->pLeft, iTable, pEList); - pExpr->pRight = substExpr(pParse, pExpr->pRight, iTable, pEList); + if( pExpr->op==TK_IF_NULL_ROW && pExpr->iTable==pSubst->iTable ){ + pExpr->iTable = pSubst->iNewTable; + } + pExpr->pLeft = substExpr(pSubst, pExpr->pLeft); + pExpr->pRight = substExpr(pSubst, pExpr->pRight); if( ExprHasProperty(pExpr, EP_xIsSelect) ){ - substSelect(pParse, pExpr->x.pSelect, iTable, pEList, 1); + substSelect(pSubst, pExpr->x.pSelect, 1); }else{ - substExprList(pParse, pExpr->x.pList, iTable, pEList); + substExprList(pSubst, pExpr->x.pList); } } return pExpr; } static void substExprList( - Parse *pParse, /* Report errors here */ - ExprList *pList, /* List to scan and in which to make substitutes */ - int iTable, /* Table to be substituted */ - ExprList *pEList /* Substitute values */ + SubstContext *pSubst, /* Description of the substitution */ + ExprList *pList /* List to scan and in which to make substitutes */ ){ int i; if( pList==0 ) return; for(i=0; inExpr; i++){ - pList->a[i].pExpr = substExpr(pParse, pList->a[i].pExpr, iTable, pEList); + pList->a[i].pExpr = substExpr(pSubst, pList->a[i].pExpr); } } static void substSelect( - Parse *pParse, /* Report errors here */ - Select *p, /* SELECT statement in which to make substitutions */ - int iTable, /* Table to be replaced */ - ExprList *pEList, /* Substitute values */ - int doPrior /* Do substitutes on p->pPrior too */ + SubstContext *pSubst, /* Description of the substitution */ + Select *p, /* SELECT statement in which to make substitutions */ + int doPrior /* Do substitutes on p->pPrior too */ ){ SrcList *pSrc; struct SrcList_item *pItem; int i; if( !p ) return; do{ - substExprList(pParse, p->pEList, iTable, pEList); - substExprList(pParse, p->pGroupBy, iTable, pEList); - substExprList(pParse, p->pOrderBy, iTable, pEList); - p->pHaving = substExpr(pParse, p->pHaving, iTable, pEList); - p->pWhere = substExpr(pParse, p->pWhere, iTable, pEList); + substExprList(pSubst, p->pEList); + substExprList(pSubst, p->pGroupBy); + substExprList(pSubst, p->pOrderBy); + p->pHaving = substExpr(pSubst, p->pHaving); + p->pWhere = substExpr(pSubst, p->pWhere); pSrc = p->pSrc; assert( pSrc!=0 ); for(i=pSrc->nSrc, pItem=pSrc->a; i>0; i--, pItem++){ - substSelect(pParse, pItem->pSelect, iTable, pEList, 1); + substSelect(pSubst, pItem->pSelect, 1); if( pItem->fg.isTabFunc ){ - substExprList(pParse, pItem->u1.pFuncArg, iTable, pEList); + substExprList(pSubst, pItem->u1.pFuncArg); } } }while( doPrior && (p = p->pPrior)!=0 ); @@ -119514,8 +120744,10 @@ static void substSelect( ** FROM-clause subquery that is a candidate for flattening. (2b is ** due to ticket [2f7170d73bf9abf80] from 2015-02-09.) ** -** (3) The subquery is not the right operand of a left outer join -** (Originally ticket #306. Strengthened by ticket #3300) +** (3) The subquery is not the right operand of a LEFT JOIN +** or (a) the subquery is not itself a join and (b) the FROM clause +** of the subquery does not contain a virtual table and (c) the +** outer query is not an aggregate. ** ** (4) The subquery is not DISTINCT. ** @@ -119527,7 +120759,7 @@ static void substSelect( ** DISTINCT. ** ** (7) The subquery has a FROM clause. TODO: For subqueries without -** A FROM clause, consider adding a FROM close with the special +** A FROM clause, consider adding a FROM clause with the special ** table sqlite_once that consists of a single row containing a ** single NULL. ** @@ -119631,8 +120863,9 @@ static int flattenSubquery( Select *pSub1; /* Pointer to the rightmost select in sub-query */ SrcList *pSrc; /* The FROM clause of the outer query */ SrcList *pSubSrc; /* The FROM clause of the subquery */ - ExprList *pList; /* The result set of the outer query */ int iParent; /* VDBE cursor number of the pSub result set temp table */ + int iNewParent = -1;/* Replacement table for iParent */ + int isLeftJoin = 0; /* True if pSub is the right side of a LEFT JOIN */ int i; /* Loop counter */ Expr *pWhere; /* The WHERE clause */ struct SrcList_item *pSubitem; /* The subquery */ @@ -119659,7 +120892,7 @@ static int flattenSubquery( return 0; /* Restriction (2b) */ } } - + pSubSrc = pSub->pSrc; assert( pSubSrc ); /* Prior to version 3.1.2, when LIMIT and OFFSET had to be simple constants, @@ -119697,10 +120930,9 @@ static int flattenSubquery( return 0; /* Restriction (23) */ } - /* OBSOLETE COMMENT 1: - ** Restriction 3: If the subquery is a join, make sure the subquery is - ** not used as the right operand of an outer join. Examples of why this - ** is not allowed: + /* + ** If the subquery is the right operand of a LEFT JOIN, then the + ** subquery may not be a join itself. Example of why this is not allowed: ** ** t1 LEFT OUTER JOIN (t2 JOIN t3) ** @@ -119710,28 +120942,27 @@ static int flattenSubquery( ** ** which is not at all the same thing. ** - ** OBSOLETE COMMENT 2: - ** Restriction 12: If the subquery is the right operand of a left outer - ** join, make sure the subquery has no WHERE clause. - ** An examples of why this is not allowed: + ** If the subquery is the right operand of a LEFT JOIN, then the outer + ** query cannot be an aggregate. This is an artifact of the way aggregates + ** are processed - there is no mechanism to determine if the LEFT JOIN + ** table should be all-NULL. ** - ** t1 LEFT OUTER JOIN (SELECT * FROM t2 WHERE t2.x>0) - ** - ** If we flatten the above, we would get - ** - ** (t1 LEFT OUTER JOIN t2) WHERE t2.x>0 - ** - ** But the t2.x>0 test will always fail on a NULL row of t2, which - ** effectively converts the OUTER JOIN into an INNER JOIN. - ** - ** THIS OVERRIDES OBSOLETE COMMENTS 1 AND 2 ABOVE: - ** Ticket #3300 shows that flattening the right term of a LEFT JOIN - ** is fraught with danger. Best to avoid the whole thing. If the - ** subquery is the right term of a LEFT JOIN, then do not flatten. + ** See also tickets #306, #350, and #3300. */ if( (pSubitem->fg.jointype & JT_OUTER)!=0 ){ - return 0; + isLeftJoin = 1; + if( pSubSrc->nSrc>1 || isAgg || IsVirtual(pSubSrc->a[0].pTab) ){ + return 0; /* Restriction (3) */ + } } +#ifdef SQLITE_EXTRA_IFNULLROW + else if( iFrom>0 && !isAgg ){ + /* Setting isLeftJoin to -1 causes OP_IfNullRow opcodes to be generated for + ** every reference to any result column from subquery in a join, even though + ** they are not necessary. This will stress-test the OP_IfNullRow opcode. */ + isLeftJoin = -1; + } +#endif /* Restriction 17: If the sub-query is a compound SELECT, then it must ** use only the UNION ALL operator. And none of the simple select queries @@ -119939,6 +121170,7 @@ static int flattenSubquery( sqlite3IdListDelete(db, pSrc->a[i+iFrom].pUsing); assert( pSrc->a[i+iFrom].fg.isTabFunc==0 ); pSrc->a[i+iFrom] = pSubSrc->a[i]; + iNewParent = pSubSrc->a[i].iCursor; memset(&pSubSrc->a[i], 0, sizeof(pSubSrc->a[i])); } pSrc->a[iFrom].fg.jointype = jointype; @@ -119955,14 +121187,6 @@ static int flattenSubquery( ** We look at every expression in the outer query and every place we see ** "a" we substitute "x*3" and every place we see "b" we substitute "y+10". */ - pList = pParent->pEList; - for(i=0; inExpr; i++){ - if( pList->a[i].zName==0 ){ - char *zName = sqlite3DbStrDup(db, pList->a[i].zSpan); - sqlite3Dequote(zName); - pList->a[i].zName = zName; - } - } if( pSub->pOrderBy ){ /* At this point, any non-zero iOrderByCol values indicate that the ** ORDER BY column expression is identical to the iOrderByCol'th @@ -119984,6 +121208,9 @@ static int flattenSubquery( pSub->pOrderBy = 0; } pWhere = sqlite3ExprDup(db, pSub->pWhere, 0); + if( isLeftJoin>0 ){ + setJoinExpr(pWhere, iNewParent); + } if( subqueryIsAgg ){ assert( pParent->pHaving==0 ); pParent->pHaving = pParent->pWhere; @@ -119997,7 +121224,13 @@ static int flattenSubquery( pParent->pWhere = sqlite3ExprAnd(db, pWhere, pParent->pWhere); } if( db->mallocFailed==0 ){ - substSelect(pParse, pParent, iParent, pSub->pEList, 0); + SubstContext x; + x.pParse = pParse; + x.iTable = iParent; + x.iNewTable = iNewParent; + x.isLeftJoin = isLeftJoin; + x.pEList = pSub->pEList; + substSelect(&x, pParent, 0); } /* The flattened query is distinct if either the inner or the @@ -120100,8 +121333,14 @@ static int pushDownWhereTerms( if( sqlite3ExprIsTableConstant(pWhere, iCursor) ){ nChng++; while( pSubq ){ + SubstContext x; pNew = sqlite3ExprDup(pParse->db, pWhere, 0); - pNew = substExpr(pParse, pNew, iCursor, pSubq->pEList); + x.pParse = pParse; + x.iTable = iCursor; + x.iNewTable = iCursor; + x.isLeftJoin = 0; + x.pEList = pSubq->pEList; + pNew = substExpr(&x, pNew); pSubq->pWhere = sqlite3ExprAnd(pParse->db, pSubq->pWhere, pNew); pSubq = pSubq->pPrior; } @@ -120807,6 +122046,25 @@ SQLITE_PRIVATE int sqlite3ExprWalkNoop(Walker *NotUsed, Expr *NotUsed2){ return WRC_Continue; } +/* +** No-op routine for the parse-tree walker for SELECT statements. +** subquery in the parser tree. +*/ +SQLITE_PRIVATE int sqlite3SelectWalkNoop(Walker *NotUsed, Select *NotUsed2){ + UNUSED_PARAMETER2(NotUsed, NotUsed2); + return WRC_Continue; +} + +#if SQLITE_DEBUG +/* +** Always assert. This xSelectCallback2 implementation proves that the +** xSelectCallback2 is never invoked. +*/ +SQLITE_PRIVATE void sqlite3SelectWalkAssert2(Walker *NotUsed, Select *NotUsed2){ + UNUSED_PARAMETER2(NotUsed, NotUsed2); + assert( 0 ); +} +#endif /* ** This routine "expands" a SELECT statement and all of its subqueries. ** For additional information on what it means to "expand" a SELECT @@ -120822,11 +122080,11 @@ SQLITE_PRIVATE int sqlite3ExprWalkNoop(Walker *NotUsed, Expr *NotUsed2){ */ static void sqlite3SelectExpand(Parse *pParse, Select *pSelect){ Walker w; - memset(&w, 0, sizeof(w)); w.xExprCallback = sqlite3ExprWalkNoop; w.pParse = pParse; if( pParse->hasCompound ){ w.xSelectCallback = convertCompoundSelectToSubquery; + w.xSelectCallback2 = 0; sqlite3WalkSelect(&w, pSelect); } w.xSelectCallback = selectExpander; @@ -120886,7 +122144,7 @@ static void selectAddSubqueryTypeInfo(Walker *pWalker, Select *p){ static void sqlite3SelectAddTypeInfo(Parse *pParse, Select *pSelect){ #ifndef SQLITE_OMIT_SUBQUERY Walker w; - memset(&w, 0, sizeof(w)); + w.xSelectCallback = sqlite3SelectWalkNoop; w.xSelectCallback2 = selectAddSubqueryTypeInfo; w.xExprCallback = sqlite3ExprWalkNoop; w.pParse = pParse; @@ -121093,6 +122351,187 @@ static void explainSimpleCount( # define explainSimpleCount(a,b,c) #endif +/* +** Context object for havingToWhereExprCb(). +*/ +struct HavingToWhereCtx { + Expr **ppWhere; + ExprList *pGroupBy; +}; + +/* +** sqlite3WalkExpr() callback used by havingToWhere(). +** +** If the node passed to the callback is a TK_AND node, return +** WRC_Continue to tell sqlite3WalkExpr() to iterate through child nodes. +** +** Otherwise, return WRC_Prune. In this case, also check if the +** sub-expression matches the criteria for being moved to the WHERE +** clause. If so, add it to the WHERE clause and replace the sub-expression +** within the HAVING expression with a constant "1". +*/ +static int havingToWhereExprCb(Walker *pWalker, Expr *pExpr){ + if( pExpr->op!=TK_AND ){ + struct HavingToWhereCtx *p = pWalker->u.pHavingCtx; + if( sqlite3ExprIsConstantOrGroupBy(pWalker->pParse, pExpr, p->pGroupBy) ){ + sqlite3 *db = pWalker->pParse->db; + Expr *pNew = sqlite3ExprAlloc(db, TK_INTEGER, &sqlite3IntTokens[1], 0); + if( pNew ){ + Expr *pWhere = *(p->ppWhere); + SWAP(Expr, *pNew, *pExpr); + pNew = sqlite3ExprAnd(db, pWhere, pNew); + *(p->ppWhere) = pNew; + } + } + return WRC_Prune; + } + return WRC_Continue; +} + +/* +** Transfer eligible terms from the HAVING clause of a query, which is +** processed after grouping, to the WHERE clause, which is processed before +** grouping. For example, the query: +** +** SELECT * FROM WHERE a=? GROUP BY b HAVING b=? AND c=? +** +** can be rewritten as: +** +** SELECT * FROM WHERE a=? AND b=? GROUP BY b HAVING c=? +** +** A term of the HAVING expression is eligible for transfer if it consists +** entirely of constants and expressions that are also GROUP BY terms that +** use the "BINARY" collation sequence. +*/ +static void havingToWhere( + Parse *pParse, + ExprList *pGroupBy, + Expr *pHaving, + Expr **ppWhere +){ + struct HavingToWhereCtx sCtx; + Walker sWalker; + + sCtx.ppWhere = ppWhere; + sCtx.pGroupBy = pGroupBy; + + memset(&sWalker, 0, sizeof(sWalker)); + sWalker.pParse = pParse; + sWalker.xExprCallback = havingToWhereExprCb; + sWalker.u.pHavingCtx = &sCtx; + sqlite3WalkExpr(&sWalker, pHaving); +} + +/* +** Check to see if the pThis entry of pTabList is a self-join of a prior view. +** If it is, then return the SrcList_item for the prior view. If it is not, +** then return 0. +*/ +static struct SrcList_item *isSelfJoinView( + SrcList *pTabList, /* Search for self-joins in this FROM clause */ + struct SrcList_item *pThis /* Search for prior reference to this subquery */ +){ + struct SrcList_item *pItem; + for(pItem = pTabList->a; pItempSelect==0 ) continue; + if( pItem->fg.viaCoroutine ) continue; + if( pItem->zName==0 ) continue; + if( sqlite3_stricmp(pItem->zDatabase, pThis->zDatabase)!=0 ) continue; + if( sqlite3_stricmp(pItem->zName, pThis->zName)!=0 ) continue; + if( sqlite3ExprCompare(0, + pThis->pSelect->pWhere, pItem->pSelect->pWhere, -1) + ){ + /* The view was modified by some other optimization such as + ** pushDownWhereTerms() */ + continue; + } + return pItem; + } + return 0; +} + +#ifdef SQLITE_COUNTOFVIEW_OPTIMIZATION +/* +** Attempt to transform a query of the form +** +** SELECT count(*) FROM (SELECT x FROM t1 UNION ALL SELECT y FROM t2) +** +** Into this: +** +** SELECT (SELECT count(*) FROM t1)+(SELECT count(*) FROM t2) +** +** The transformation only works if all of the following are true: +** +** * The subquery is a UNION ALL of two or more terms +** * There is no WHERE or GROUP BY or HAVING clauses on the subqueries +** * The outer query is a simple count(*) +** +** Return TRUE if the optimization is undertaken. +*/ +static int countOfViewOptimization(Parse *pParse, Select *p){ + Select *pSub, *pPrior; + Expr *pExpr; + Expr *pCount; + sqlite3 *db; + if( (p->selFlags & SF_Aggregate)==0 ) return 0; /* This is an aggregate query */ + if( p->pEList->nExpr!=1 ) return 0; /* Single result column */ + pExpr = p->pEList->a[0].pExpr; + if( pExpr->op!=TK_AGG_FUNCTION ) return 0; /* Result is an aggregate */ + if( sqlite3_stricmp(pExpr->u.zToken,"count") ) return 0; /* Must be count() */ + if( pExpr->x.pList!=0 ) return 0; /* Must be count(*) */ + if( p->pSrc->nSrc!=1 ) return 0; /* One table in the FROM clause */ + pSub = p->pSrc->a[0].pSelect; + if( pSub==0 ) return 0; /* The FROM is a subquery */ + if( pSub->pPrior==0 ) return 0; /* Must be a compound subquery */ + do{ + if( pSub->op!=TK_ALL && pSub->pPrior ) return 0; /* Must be UNION ALL */ + if( pSub->pWhere ) return 0; /* No WHERE clause */ + if( pSub->selFlags & SF_Aggregate ) return 0; /* Not an aggregate */ + pSub = pSub->pPrior; /* Repeat over compound terms */ + }while( pSub ); + + /* If we reach this point, that means it is OK to perform the transformation */ + + db = pParse->db; + pCount = pExpr; + pExpr = 0; + pSub = p->pSrc->a[0].pSelect; + p->pSrc->a[0].pSelect = 0; + sqlite3SrcListDelete(db, p->pSrc); + p->pSrc = sqlite3DbMallocZero(pParse->db, sizeof(*p->pSrc)); + while( pSub ){ + Expr *pTerm; + pPrior = pSub->pPrior; + pSub->pPrior = 0; + pSub->pNext = 0; + pSub->selFlags |= SF_Aggregate; + pSub->selFlags &= ~SF_Compound; + pSub->nSelectRow = 0; + sqlite3ExprListDelete(db, pSub->pEList); + pTerm = pPrior ? sqlite3ExprDup(db, pCount, 0) : pCount; + pSub->pEList = sqlite3ExprListAppend(pParse, 0, pTerm); + pTerm = sqlite3PExpr(pParse, TK_SELECT, 0, 0); + sqlite3PExprAddSelect(pParse, pTerm, pSub); + if( pExpr==0 ){ + pExpr = pTerm; + }else{ + pExpr = sqlite3PExpr(pParse, TK_PLUS, pTerm, pExpr); + } + pSub = pPrior; + } + p->pEList->a[0].pExpr = pExpr; + p->selFlags &= ~SF_Aggregate; + +#if SELECTTRACE_ENABLED + if( sqlite3SelectTrace & 0x400 ){ + SELECTTRACE(0x400,pParse,p,("After count-of-view optimization:\n")); + sqlite3TreeViewSelect(0, p, 0); + } +#endif + return 1; +} +#endif /* SQLITE_COUNTOFVIEW_OPTIMIZATION */ + /* ** Generate code for the SELECT statement given in the p argument. ** @@ -121177,6 +122616,14 @@ SQLITE_PRIVATE int sqlite3Select( } #endif + /* Get a pointer the VDBE under construction, allocating a new VDBE if one + ** does not already exist */ + v = sqlite3GetVdbe(pParse); + if( v==0 ) goto select_end; + if( pDest->eDest==SRT_Output ){ + generateColumnNames(pParse, p); + } + /* Try to flatten subqueries in the FROM clause up into the main query */ #if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) @@ -121212,11 +122659,6 @@ SQLITE_PRIVATE int sqlite3Select( } #endif - /* Get a pointer the VDBE under construction, allocating a new VDBE if one - ** does not already exist */ - v = sqlite3GetVdbe(pParse); - if( v==0 ) goto select_end; - #ifndef SQLITE_OMIT_COMPOUND_SELECT /* Handle compound SELECT statements using the separate multiSelect() ** procedure. @@ -121232,13 +122674,38 @@ SQLITE_PRIVATE int sqlite3Select( } #endif - /* Generate code for all sub-queries in the FROM clause + /* For each term in the FROM clause, do two things: + ** (1) Authorized unreferenced tables + ** (2) Generate code for all sub-queries */ -#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) for(i=0; inSrc; i++){ struct SrcList_item *pItem = &pTabList->a[i]; SelectDest dest; - Select *pSub = pItem->pSelect; + Select *pSub; + + /* Issue SQLITE_READ authorizations with a fake column name for any tables that + ** are referenced but from which no values are extracted. Examples of where these + ** kinds of null SQLITE_READ authorizations would occur: + ** + ** SELECT count(*) FROM t1; -- SQLITE_READ t1."" + ** SELECT t1.* FROM t1, t2; -- SQLITE_READ t2."" + ** + ** The fake column name is an empty string. It is possible for a table to + ** have a column named by the empty string, in which case there is no way to + ** distinguish between an unreferenced table and an actual reference to the + ** "" column. The original design was for the fake column name to be a NULL, + ** which would be unambiguous. But legacy authorization callbacks might + ** assume the column name is non-NULL and segfault. The use of an empty string + ** for the fake column name seems safer. + */ + if( pItem->colUsed==0 ){ + sqlite3AuthCheck(pParse, SQLITE_READ, pItem->zName, "", pItem->zDatabase); + } + +#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) + /* Generate code for all sub-queries in the FROM clause + */ + pSub = pItem->pSelect; if( pSub==0 ) continue; /* Sometimes the code for a subquery will be generated more than @@ -121249,6 +122716,10 @@ SQLITE_PRIVATE int sqlite3Select( ** to be invoked again. */ if( pItem->addrFillSub ){ if( pItem->fg.viaCoroutine==0 ){ + /* The subroutine that manifests the view might be a one-time routine, + ** or it might need to be rerun on each iteration because it + ** encodes a correlated subquery. */ + testcase( sqlite3VdbeGetOp(v, pItem->addrFillSub)->opcode==OP_Once ); sqlite3VdbeAddOp2(v, OP_Gosub, pItem->regReturn, pItem->addrFillSub); } continue; @@ -121323,6 +122794,8 @@ SQLITE_PRIVATE int sqlite3Select( int topAddr; int onceAddr = 0; int retAddr; + struct SrcList_item *pPrior; + assert( pItem->addrFillSub==0 ); pItem->regReturn = ++pParse->nMem; topAddr = sqlite3VdbeAddOp2(v, OP_Integer, 0, pItem->regReturn); @@ -121336,9 +122809,17 @@ SQLITE_PRIVATE int sqlite3Select( }else{ VdbeNoopComment((v, "materialize \"%s\"", pItem->pTab->zName)); } - sqlite3SelectDestInit(&dest, SRT_EphemTab, pItem->iCursor); - explainSetInteger(pItem->iSelectId, (u8)pParse->iNextSelectId); - sqlite3Select(pParse, pSub, &dest); + pPrior = isSelfJoinView(pTabList, pItem); + if( pPrior ){ + sqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pPrior->iCursor); + explainSetInteger(pItem->iSelectId, pPrior->iSelectId); + assert( pPrior->pSelect!=0 ); + pSub->nSelectRow = pPrior->pSelect->nSelectRow; + }else{ + sqlite3SelectDestInit(&dest, SRT_EphemTab, pItem->iCursor); + explainSetInteger(pItem->iSelectId, (u8)pParse->iNextSelectId); + sqlite3Select(pParse, pSub, &dest); + } pItem->pTab->nRowLogEst = pSub->nSelectRow; if( onceAddr ) sqlite3VdbeJumpHere(v, onceAddr); retAddr = sqlite3VdbeAddOp1(v, OP_Return, pItem->regReturn); @@ -121348,8 +122829,8 @@ SQLITE_PRIVATE int sqlite3Select( } if( db->mallocFailed ) goto select_end; pParse->nHeight -= sqlite3SelectExprHeight(p); - } #endif + } /* Various elements of the SELECT copied into local variables for ** convenience */ @@ -121366,6 +122847,16 @@ SQLITE_PRIVATE int sqlite3Select( } #endif +#ifdef SQLITE_COUNTOFVIEW_OPTIMIZATION + if( OptimizationEnabled(db, SQLITE_QueryFlattener|SQLITE_CountOfView) + && countOfViewOptimization(pParse, p) + ){ + if( db->mallocFailed ) goto select_end; + pEList = p->pEList; + pTabList = p->pSrc; + } +#endif + /* If the query is DISTINCT with an ORDER BY but is not an aggregate, and ** if the select-list is the same as the ORDER BY list, then this query ** can be rewritten as a GROUP BY. In other words, this: @@ -121557,6 +123048,11 @@ SQLITE_PRIVATE int sqlite3Select( sqlite3ExprAnalyzeAggList(&sNC, pEList); sqlite3ExprAnalyzeAggList(&sNC, sSort.pOrderBy); if( pHaving ){ + if( pGroupBy ){ + assert( pWhere==p->pWhere ); + havingToWhere(pParse, pGroupBy, pHaving, &p->pWhere); + pWhere = p->pWhere; + } sqlite3ExprAnalyzeAggregates(&sNC, pHaving); } sAggInfo.nAccumulator = sAggInfo.nColumn; @@ -121962,12 +123458,6 @@ SQLITE_PRIVATE int sqlite3Select( select_end: explainSetInteger(pParse->iSelectId, iRestoreSelectId); - /* Identify column names if results of the SELECT are to be output. - */ - if( rc==SQLITE_OK && pDest->eDest==SRT_Output ){ - generateColumnNames(pParse, pTabList, pEList); - } - sqlite3DbFree(db, sAggInfo.aCol); sqlite3DbFree(db, sAggInfo.aFunc); #if SELECTTRACE_ENABLED @@ -122488,6 +123978,7 @@ SQLITE_PRIVATE void sqlite3FinishTrigger( if( v==0 ) goto triggerfinish_cleanup; sqlite3BeginWriteOperation(pParse, 0, iDb); z = sqlite3DbStrNDup(db, (char*)pAll->z, pAll->n); + testcase( z==0 ); sqlite3NestedParse(pParse, "INSERT INTO %Q.%s VALUES('trigger',%Q,%Q,0,'CREATE TRIGGER %q')", db->aDb[iDb].zDbSName, MASTER_NAME, zName, @@ -123569,7 +125060,7 @@ SQLITE_PRIVATE void sqlite3Update( */ for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ int reg; - if( chngKey || hasFK || pIdx->pPartIdxWhere || pIdx==pPk ){ + if( chngKey || hasFK>1 || pIdx->pPartIdxWhere || pIdx==pPk ){ reg = ++pParse->nMem; pParse->nMem += pIdx->nColumn; }else{ @@ -123924,7 +125415,7 @@ SQLITE_PRIVATE void sqlite3Update( assert( regNew==regNewRowid+1 ); #ifdef SQLITE_ENABLE_PREUPDATE_HOOK sqlite3VdbeAddOp3(v, OP_Delete, iDataCur, - OPFLAG_ISUPDATE | ((hasFK || chngKey) ? 0 : OPFLAG_ISNOOP), + OPFLAG_ISUPDATE | ((hasFK>1 || chngKey) ? 0 : OPFLAG_ISNOOP), regNewRowid ); if( eOnePass==ONEPASS_MULTI ){ @@ -123935,7 +125426,7 @@ SQLITE_PRIVATE void sqlite3Update( sqlite3VdbeAppendP4(v, pTab, P4_TABLE); } #else - if( hasFK || chngKey ){ + if( hasFK>1 || chngKey ){ sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, 0); } #endif @@ -124355,7 +125846,7 @@ SQLITE_PRIVATE int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db, int iDb){ extern void sqlite3CodecGetKey(sqlite3*, int, void**, int*); int nKey; char *zKey; - sqlite3CodecGetKey(db, 0, (void**)&zKey, &nKey); + sqlite3CodecGetKey(db, iDb, (void**)&zKey, &nKey); if( nKey ) db->nextPagesize = 0; } #endif @@ -125578,7 +127069,7 @@ SQLITE_PRIVATE FuncDef *sqlite3VtabOverloadFunction( if( NEVER(pExpr==0) ) return pDef; if( pExpr->op!=TK_COLUMN ) return pDef; pTab = pExpr->pTab; - if( NEVER(pTab==0) ) return pDef; + if( pTab==0 ) return pDef; if( !IsVirtual(pTab) ) return pDef; pVtab = sqlite3GetVTable(db, pTab)->pVtab; assert( pVtab!=0 ); @@ -125913,6 +127404,7 @@ struct WhereLoop { u16 nEq; /* Number of equality constraints */ u16 nBtm; /* Size of BTM vector */ u16 nTop; /* Size of TOP vector */ + u16 nIdxCol; /* Index column used for ORDER BY */ Index *pIndex; /* Index used, or NULL */ } btree; struct { /* Information for virtual tables */ @@ -126072,6 +127564,7 @@ struct WhereTerm { #define TERM_LIKECOND 0x200 /* Conditionally this LIKE operator term */ #define TERM_LIKE 0x400 /* The original LIKE operator */ #define TERM_IS 0x800 /* Term.pExpr is an IS operator */ +#define TERM_VARSELECT 0x1000 /* Term.pExpr contains a correlated sub-query */ /* ** An instance of the WhereScan object is used as an iterator for locating @@ -126161,6 +127654,7 @@ struct WhereAndInfo { ** no gaps. */ struct WhereMaskSet { + int bVarSelect; /* Used by sqlite3WhereExprUsage() */ int n; /* Number of assigned cursor values */ int ix[BMS]; /* Cursor assigned to each bit */ }; @@ -126206,6 +127700,7 @@ struct WhereInfo { SrcList *pTabList; /* List of tables in the join */ ExprList *pOrderBy; /* The ORDER BY clause or NULL */ ExprList *pResultSet; /* Result set of the query */ + Expr *pWhere; /* The complete WHERE clause */ LogEst iLimit; /* LIMIT if wctrlFlags has WHERE_USE_LIMIT */ int aiCurOnePass[2]; /* OP_OpenWrite cursors for the ONEPASS opt */ int iContinue; /* Jump here to continue with next record */ @@ -127293,10 +128788,10 @@ static void codeCursorHint( ** ** Normally, this is just: ** -** OP_Seek $iCur $iRowid +** OP_DeferredSeek $iCur $iRowid ** ** However, if the scan currently being coded is a branch of an OR-loop and -** the statement currently being coded is a SELECT, then P3 of the OP_Seek +** the statement currently being coded is a SELECT, then P3 of OP_DeferredSeek ** is set to iIdxCur and P4 is set to point to an array of integers ** containing one entry for each column of the table cursor iCur is open ** on. For each table column, if the column is the i'th column of the @@ -127315,7 +128810,7 @@ static void codeDeferredSeek( assert( iIdxCur>0 ); assert( pIdx->aiColumn[pIdx->nColumn-1]==-1 ); - sqlite3VdbeAddOp3(v, OP_Seek, iIdxCur, 0, iCur); + sqlite3VdbeAddOp3(v, OP_DeferredSeek, iIdxCur, 0, iCur); if( (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE) && DbMaskAllZero(sqlite3ParseToplevel(pParse)->writeMask) ){ @@ -127366,6 +128861,69 @@ static void codeExprOrVector(Parse *pParse, Expr *p, int iReg, int nReg){ } } +/* An instance of the IdxExprTrans object carries information about a +** mapping from an expression on table columns into a column in an index +** down through the Walker. +*/ +typedef struct IdxExprTrans { + Expr *pIdxExpr; /* The index expression */ + int iTabCur; /* The cursor of the corresponding table */ + int iIdxCur; /* The cursor for the index */ + int iIdxCol; /* The column for the index */ +} IdxExprTrans; + +/* The walker node callback used to transform matching expressions into +** a reference to an index column for an index on an expression. +** +** If pExpr matches, then transform it into a reference to the index column +** that contains the value of pExpr. +*/ +static int whereIndexExprTransNode(Walker *p, Expr *pExpr){ + IdxExprTrans *pX = p->u.pIdxTrans; + if( sqlite3ExprCompare(0, pExpr, pX->pIdxExpr, pX->iTabCur)==0 ){ + pExpr->op = TK_COLUMN; + pExpr->iTable = pX->iIdxCur; + pExpr->iColumn = pX->iIdxCol; + pExpr->pTab = 0; + return WRC_Prune; + }else{ + return WRC_Continue; + } +} + +/* +** For an indexes on expression X, locate every instance of expression X in pExpr +** and change that subexpression into a reference to the appropriate column of +** the index. +*/ +static void whereIndexExprTrans( + Index *pIdx, /* The Index */ + int iTabCur, /* Cursor of the table that is being indexed */ + int iIdxCur, /* Cursor of the index itself */ + WhereInfo *pWInfo /* Transform expressions in this WHERE clause */ +){ + int iIdxCol; /* Column number of the index */ + ExprList *aColExpr; /* Expressions that are indexed */ + Walker w; + IdxExprTrans x; + aColExpr = pIdx->aColExpr; + if( aColExpr==0 ) return; /* Not an index on expressions */ + memset(&w, 0, sizeof(w)); + w.xExprCallback = whereIndexExprTransNode; + w.u.pIdxTrans = &x; + x.iTabCur = iTabCur; + x.iIdxCur = iIdxCur; + for(iIdxCol=0; iIdxColnExpr; iIdxCol++){ + if( pIdx->aiColumn[iIdxCol]!=XN_EXPR ) continue; + assert( aColExpr->a[iIdxCol].pExpr!=0 ); + x.iIdxCol = iIdxCol; + x.pIdxExpr = aColExpr->a[iIdxCol].pExpr; + sqlite3WalkExpr(&w, pWInfo->pWhere); + sqlite3WalkExprList(&w, pWInfo->pOrderBy); + sqlite3WalkExprList(&w, pWInfo->pResultSet); + } +} + /* ** Generate code for the start of the iLevel-th loop in the WHERE clause ** implementation described by pWInfo. @@ -127393,6 +128951,8 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( int addrCont; /* Jump here to continue with next cycle */ int iRowidReg = 0; /* Rowid is stored in this register, if not zero */ int iReleaseReg = 0; /* Temp register to free before returning */ + Index *pIdx = 0; /* Index used by loop (if any) */ + int iLoop; /* Iteration of constraint generator loop */ pParse = pWInfo->pParse; v = pParse->pVdbe; @@ -127718,7 +129278,6 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( int endEq; /* True if range end uses ==, >= or <= */ int start_constraints; /* Start of range is constrained */ int nConstraint; /* Number of constraint terms */ - Index *pIdx; /* The index we will be using */ int iIdxCur; /* The VDBE cursor for the index */ int nExtraReg = 0; /* Number of extra registers needed */ int op; /* Instruction opcode */ @@ -127947,6 +129506,13 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( iRowidReg, pPk->nKeyCol); VdbeCoverage(v); } + /* If pIdx is an index on one or more expressions, then look through + ** all the expressions in pWInfo and try to transform matching expressions + ** into reference to index columns. + */ + whereIndexExprTrans(pIdx, iCur, iIdxCur, pWInfo); + + /* Record the instruction used to terminate the loop. */ if( pLoop->wsFlags & WHERE_ONEROW ){ pLevel->op = OP_Noop; @@ -127962,6 +129528,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( }else{ assert( pLevel->p5==0 ); } + if( omitTable ) pIdx = 0; }else #ifndef SQLITE_OMIT_OR_OPTIMIZATION @@ -128279,43 +129846,75 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( /* Insert code to test every subexpression that can be completely ** computed using the current set of tables. + ** + ** This loop may run between one and three times, depending on the + ** constraints to be generated. The value of stack variable iLoop + ** determines the constraints coded by each iteration, as follows: + ** + ** iLoop==1: Code only expressions that are entirely covered by pIdx. + ** iLoop==2: Code remaining expressions that do not contain correlated + ** sub-queries. + ** iLoop==3: Code all remaining expressions. + ** + ** An effort is made to skip unnecessary iterations of the loop. */ - for(pTerm=pWC->a, j=pWC->nTerm; j>0; j--, pTerm++){ - Expr *pE; - int skipLikeAddr = 0; - testcase( pTerm->wtFlags & TERM_VIRTUAL ); - testcase( pTerm->wtFlags & TERM_CODED ); - if( pTerm->wtFlags & (TERM_VIRTUAL|TERM_CODED) ) continue; - if( (pTerm->prereqAll & pLevel->notReady)!=0 ){ - testcase( pWInfo->untestedTerms==0 - && (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)!=0 ); - pWInfo->untestedTerms = 1; - continue; - } - pE = pTerm->pExpr; - assert( pE!=0 ); - if( pLevel->iLeftJoin && !ExprHasProperty(pE, EP_FromJoin) ){ - continue; - } - if( pTerm->wtFlags & TERM_LIKECOND ){ - /* If the TERM_LIKECOND flag is set, that means that the range search - ** is sufficient to guarantee that the LIKE operator is true, so we - ** can skip the call to the like(A,B) function. But this only works - ** for strings. So do not skip the call to the function on the pass - ** that compares BLOBs. */ + iLoop = (pIdx ? 1 : 2); + do{ + int iNext = 0; /* Next value for iLoop */ + for(pTerm=pWC->a, j=pWC->nTerm; j>0; j--, pTerm++){ + Expr *pE; + int skipLikeAddr = 0; + testcase( pTerm->wtFlags & TERM_VIRTUAL ); + testcase( pTerm->wtFlags & TERM_CODED ); + if( pTerm->wtFlags & (TERM_VIRTUAL|TERM_CODED) ) continue; + if( (pTerm->prereqAll & pLevel->notReady)!=0 ){ + testcase( pWInfo->untestedTerms==0 + && (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)!=0 ); + pWInfo->untestedTerms = 1; + continue; + } + pE = pTerm->pExpr; + assert( pE!=0 ); + if( pLevel->iLeftJoin && !ExprHasProperty(pE, EP_FromJoin) ){ + continue; + } + + if( iLoop==1 && !sqlite3ExprCoveredByIndex(pE, pLevel->iTabCur, pIdx) ){ + iNext = 2; + continue; + } + if( iLoop<3 && (pTerm->wtFlags & TERM_VARSELECT) ){ + if( iNext==0 ) iNext = 3; + continue; + } + + if( pTerm->wtFlags & TERM_LIKECOND ){ + /* If the TERM_LIKECOND flag is set, that means that the range search + ** is sufficient to guarantee that the LIKE operator is true, so we + ** can skip the call to the like(A,B) function. But this only works + ** for strings. So do not skip the call to the function on the pass + ** that compares BLOBs. */ #ifdef SQLITE_LIKE_DOESNT_MATCH_BLOBS - continue; + continue; #else - u32 x = pLevel->iLikeRepCntr; - assert( x>0 ); - skipLikeAddr = sqlite3VdbeAddOp1(v, (x&1)? OP_IfNot : OP_If, (int)(x>>1)); - VdbeCoverage(v); + u32 x = pLevel->iLikeRepCntr; + assert( x>0 ); + skipLikeAddr = sqlite3VdbeAddOp1(v, (x&1)?OP_IfNot:OP_If, (int)(x>>1)); + VdbeCoverage(v); #endif + } +#ifdef WHERETRACE_ENABLED /* 0xffff */ + if( sqlite3WhereTrace ){ + VdbeNoopComment((v, "WhereTerm[%d] (%p) priority=%d", + pWC->nTerm-j, pTerm, iLoop)); + } +#endif + sqlite3ExprIfFalse(pParse, pE, addrCont, SQLITE_JUMPIFNULL); + if( skipLikeAddr ) sqlite3VdbeJumpHere(v, skipLikeAddr); + pTerm->wtFlags |= TERM_CODED; } - sqlite3ExprIfFalse(pParse, pE, addrCont, SQLITE_JUMPIFNULL); - if( skipLikeAddr ) sqlite3VdbeJumpHere(v, skipLikeAddr); - pTerm->wtFlags |= TERM_CODED; - } + iLoop = iNext; + }while( iLoop>0 ); /* Insert code to test for implied constraints based on transitivity ** of the "==" operator. @@ -128594,7 +130193,7 @@ static int isLikeOrGlob( pRight = sqlite3ExprSkipCollate(pList->a[0].pExpr); op = pRight->op; - if( op==TK_VARIABLE ){ + if( op==TK_VARIABLE && (db->flags & SQLITE_EnableQPSG)==0 ){ Vdbe *pReprepare = pParse->pReprepare; int iCol = pRight->iColumn; pVal = sqlite3VdbeGetBoundValue(pReprepare, iCol, SQLITE_AFF_BLOB); @@ -128784,8 +130383,8 @@ static void whereCombineDisjuncts( && (eOp & (WO_EQ|WO_GT|WO_GE))!=eOp ) return; assert( pOne->pExpr->pLeft!=0 && pOne->pExpr->pRight!=0 ); assert( pTwo->pExpr->pLeft!=0 && pTwo->pExpr->pRight!=0 ); - if( sqlite3ExprCompare(pOne->pExpr->pLeft, pTwo->pExpr->pLeft, -1) ) return; - if( sqlite3ExprCompare(pOne->pExpr->pRight, pTwo->pExpr->pRight, -1) )return; + if( sqlite3ExprCompare(0,pOne->pExpr->pLeft, pTwo->pExpr->pLeft, -1) ) return; + if( sqlite3ExprCompare(0,pOne->pExpr->pRight, pTwo->pExpr->pRight,-1) )return; /* If we reach this point, it means the two subterms can be combined */ if( (eOp & (eOp-1))!=0 ){ if( eOp & (WO_LT|WO_LE) ){ @@ -129208,8 +130807,8 @@ static Bitmask exprSelectUsage(WhereMaskSet *pMaskSet, Select *pS){ ** Expression pExpr is one operand of a comparison operator that might ** be useful for indexing. This routine checks to see if pExpr appears ** in any index. Return TRUE (1) if pExpr is an indexed term and return -** FALSE (0) if not. If TRUE is returned, also set *piCur to the cursor -** number of the table that is indexed and *piColumn to the column number +** FALSE (0) if not. If TRUE is returned, also set aiCurCol[0] to the cursor +** number of the table that is indexed and aiCurCol[1] to the column number ** of the column that is indexed, or XN_EXPR (-2) if an expression is being ** indexed. ** @@ -129217,18 +130816,37 @@ static Bitmask exprSelectUsage(WhereMaskSet *pMaskSet, Select *pS){ ** true even if that particular column is not indexed, because the column ** might be added to an automatic index later. */ -static int exprMightBeIndexed( +static SQLITE_NOINLINE int exprMightBeIndexed2( SrcList *pFrom, /* The FROM clause */ - int op, /* The specific comparison operator */ Bitmask mPrereq, /* Bitmask of FROM clause terms referenced by pExpr */ - Expr *pExpr, /* An operand of a comparison operator */ - int *piCur, /* Write the referenced table cursor number here */ - int *piColumn /* Write the referenced table column number here */ + int *aiCurCol, /* Write the referenced table cursor and column here */ + Expr *pExpr /* An operand of a comparison operator */ ){ Index *pIdx; int i; int iCur; - + for(i=0; mPrereq>1; i++, mPrereq>>=1){} + iCur = pFrom->a[i].iCursor; + for(pIdx=pFrom->a[i].pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + if( pIdx->aColExpr==0 ) continue; + for(i=0; inKeyCol; i++){ + if( pIdx->aiColumn[i]!=XN_EXPR ) continue; + if( sqlite3ExprCompareSkip(pExpr, pIdx->aColExpr->a[i].pExpr, iCur)==0 ){ + aiCurCol[0] = iCur; + aiCurCol[1] = XN_EXPR; + return 1; + } + } + } + return 0; +} +static int exprMightBeIndexed( + SrcList *pFrom, /* The FROM clause */ + Bitmask mPrereq, /* Bitmask of FROM clause terms referenced by pExpr */ + int *aiCurCol, /* Write the referenced table cursor & column here */ + Expr *pExpr, /* An operand of a comparison operator */ + int op /* The specific comparison operator */ +){ /* If this expression is a vector to the left or right of a ** inequality constraint (>, <, >= or <=), perform the processing ** on the first element of the vector. */ @@ -129240,26 +130858,13 @@ static int exprMightBeIndexed( } if( pExpr->op==TK_COLUMN ){ - *piCur = pExpr->iTable; - *piColumn = pExpr->iColumn; + aiCurCol[0] = pExpr->iTable; + aiCurCol[1] = pExpr->iColumn; return 1; } if( mPrereq==0 ) return 0; /* No table references */ if( (mPrereq&(mPrereq-1))!=0 ) return 0; /* Refs more than one table */ - for(i=0; mPrereq>1; i++, mPrereq>>=1){} - iCur = pFrom->a[i].iCursor; - for(pIdx=pFrom->a[i].pTab->pIndex; pIdx; pIdx=pIdx->pNext){ - if( pIdx->aColExpr==0 ) continue; - for(i=0; inKeyCol; i++){ - if( pIdx->aiColumn[i]!=XN_EXPR ) continue; - if( sqlite3ExprCompareSkip(pExpr, pIdx->aColExpr->a[i].pExpr, iCur)==0 ){ - *piCur = iCur; - *piColumn = XN_EXPR; - return 1; - } - } - } - return 0; + return exprMightBeIndexed2(pFrom,mPrereq,aiCurCol,pExpr); } /* @@ -129323,7 +130928,9 @@ static void exprAnalyze( }else{ pTerm->prereqRight = sqlite3WhereExprUsage(pMaskSet, pExpr->pRight); } + pMaskSet->bVarSelect = 0; prereqAll = sqlite3WhereExprUsage(pMaskSet, pExpr); + if( pMaskSet->bVarSelect ) pTerm->wtFlags |= TERM_VARSELECT; if( ExprHasProperty(pExpr, EP_FromJoin) ){ Bitmask x = sqlite3WhereGetMask(pMaskSet, pExpr->iRightJoinTable); prereqAll |= x; @@ -129339,7 +130946,7 @@ static void exprAnalyze( pTerm->iParent = -1; pTerm->eOperator = 0; if( allowedOp(op) ){ - int iCur, iColumn; + int aiCurCol[2]; Expr *pLeft = sqlite3ExprSkipCollate(pExpr->pLeft); Expr *pRight = sqlite3ExprSkipCollate(pExpr->pRight); u16 opMask = (pTerm->prereqRight & prereqLeft)==0 ? WO_ALL : WO_EQUIV; @@ -129350,14 +130957,14 @@ static void exprAnalyze( pLeft = pLeft->x.pList->a[pTerm->iField-1].pExpr; } - if( exprMightBeIndexed(pSrc, op, prereqLeft, pLeft, &iCur, &iColumn) ){ - pTerm->leftCursor = iCur; - pTerm->u.leftColumn = iColumn; + if( exprMightBeIndexed(pSrc, prereqLeft, aiCurCol, pLeft, op) ){ + pTerm->leftCursor = aiCurCol[0]; + pTerm->u.leftColumn = aiCurCol[1]; pTerm->eOperator = operatorMask(op) & opMask; } if( op==TK_IS ) pTerm->wtFlags |= TERM_IS; if( pRight - && exprMightBeIndexed(pSrc, op, pTerm->prereqRight, pRight, &iCur,&iColumn) + && exprMightBeIndexed(pSrc, pTerm->prereqRight, aiCurCol, pRight, op) ){ WhereTerm *pNew; Expr *pDup; @@ -129387,8 +130994,8 @@ static void exprAnalyze( pNew = pTerm; } exprCommute(pParse, pDup); - pNew->leftCursor = iCur; - pNew->u.leftColumn = iColumn; + pNew->leftCursor = aiCurCol[0]; + pNew->u.leftColumn = aiCurCol[1]; testcase( (prereqLeft | extraRight) != prereqLeft ); pNew->prereqRight = prereqLeft | extraRight; pNew->prereqAll = prereqAll; @@ -129550,6 +131157,9 @@ static void exprAnalyze( Expr *pNewExpr; pNewExpr = sqlite3PExpr(pParse, TK_MATCH, 0, sqlite3ExprDup(db, pRight, 0)); + if( ExprHasProperty(pExpr, EP_FromJoin) && pNewExpr ){ + ExprSetProperty(pNewExpr, EP_FromJoin); + } idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC); testcase( idxNew==0 ); pNewTerm = &pWC->a[idxNew]; @@ -129747,13 +131357,16 @@ SQLITE_PRIVATE Bitmask sqlite3WhereExprUsage(WhereMaskSet *pMaskSet, Expr *p){ Bitmask mask; if( p==0 ) return 0; if( p->op==TK_COLUMN ){ - mask = sqlite3WhereGetMask(pMaskSet, p->iTable); - return mask; + return sqlite3WhereGetMask(pMaskSet, p->iTable); } + mask = (p->op==TK_IF_NULL_ROW) ? sqlite3WhereGetMask(pMaskSet, p->iTable) : 0; assert( !ExprHasProperty(p, EP_TokenOnly) ); - mask = p->pRight ? sqlite3WhereExprUsage(pMaskSet, p->pRight) : 0; if( p->pLeft ) mask |= sqlite3WhereExprUsage(pMaskSet, p->pLeft); - if( ExprHasProperty(p, EP_xIsSelect) ){ + if( p->pRight ){ + mask |= sqlite3WhereExprUsage(pMaskSet, p->pRight); + assert( p->x.pList==0 ); + }else if( ExprHasProperty(p, EP_xIsSelect) ){ + if( ExprHasProperty(p, EP_VarSelect) ) pMaskSet->bVarSelect = 1; mask |= exprSelectUsage(pMaskSet, p->x.pSelect); }else if( p->x.pList ){ mask |= sqlite3WhereExprListUsage(pMaskSet, p->x.pList); @@ -130442,6 +132055,15 @@ static int termCanDriveIndex( char aff; if( pTerm->leftCursor!=pSrc->iCursor ) return 0; if( (pTerm->eOperator & (WO_EQ|WO_IS))==0 ) return 0; + if( (pSrc->fg.jointype & JT_LEFT) + && !ExprHasProperty(pTerm->pExpr, EP_FromJoin) + && (pTerm->eOperator & WO_IS) + ){ + /* Cannot use an IS term from the WHERE clause as an index driver for + ** the RHS of a LEFT JOIN. Such a term can only be used if it is from + ** the ON clause. */ + return 0; + } if( (pTerm->prereqRight & notReady)!=0 ) return 0; if( pTerm->u.leftColumn<0 ) return 0; aff = pSrc->pTab->aCol[pTerm->u.leftColumn].affinity; @@ -131026,7 +132648,7 @@ static int whereKeyStats( iGap = iGap/3; } aStat[0] = iLower + iGap; - aStat[1] = pIdx->aAvgEq[iCol]; + aStat[1] = pIdx->aAvgEq[nField-1]; } /* Restore the pRec->nField value before returning. */ @@ -131620,7 +133242,7 @@ static void whereLoopClearUnion(sqlite3 *db, WhereLoop *p){ p->u.vtab.idxStr = 0; }else if( (p->wsFlags & WHERE_AUTO_INDEX)!=0 && p->u.btree.pIndex!=0 ){ sqlite3DbFree(db, p->u.btree.pIndex->zColAff); - sqlite3DbFree(db, p->u.btree.pIndex); + sqlite3DbFreeNN(db, p->u.btree.pIndex); p->u.btree.pIndex = 0; } } @@ -131630,7 +133252,7 @@ static void whereLoopClearUnion(sqlite3 *db, WhereLoop *p){ ** Deallocate internal memory used by a WhereLoop object */ static void whereLoopClear(sqlite3 *db, WhereLoop *p){ - if( p->aLTerm!=p->aLTermSpace ) sqlite3DbFree(db, p->aLTerm); + if( p->aLTerm!=p->aLTermSpace ) sqlite3DbFreeNN(db, p->aLTerm); whereLoopClearUnion(db, p); whereLoopInit(p); } @@ -131645,7 +133267,7 @@ static int whereLoopResize(sqlite3 *db, WhereLoop *p, int n){ paNew = sqlite3DbMallocRawNN(db, sizeof(p->aLTerm[0])*n); if( paNew==0 ) return SQLITE_NOMEM_BKPT; memcpy(paNew, p->aLTerm, sizeof(p->aLTerm[0])*p->nLSlot); - if( p->aLTerm!=p->aLTermSpace ) sqlite3DbFree(db, p->aLTerm); + if( p->aLTerm!=p->aLTermSpace ) sqlite3DbFreeNN(db, p->aLTerm); p->aLTerm = paNew; p->nLSlot = n; return SQLITE_OK; @@ -131675,7 +133297,7 @@ static int whereLoopXfer(sqlite3 *db, WhereLoop *pTo, WhereLoop *pFrom){ */ static void whereLoopDelete(sqlite3 *db, WhereLoop *p){ whereLoopClear(db, p); - sqlite3DbFree(db, p); + sqlite3DbFreeNN(db, p); } /* @@ -131696,7 +133318,7 @@ static void whereInfoFree(sqlite3 *db, WhereInfo *pWInfo){ pWInfo->pLoops = p->pNextLoop; whereLoopDelete(db, p); } - sqlite3DbFree(db, pWInfo); + sqlite3DbFreeNN(db, pWInfo); } } @@ -131779,16 +133401,17 @@ static void whereLoopAdjustCost(const WhereLoop *p, WhereLoop *pTemplate){ /* ** Search the list of WhereLoops in *ppPrev looking for one that can be -** supplanted by pTemplate. +** replaced by pTemplate. ** -** Return NULL if the WhereLoop list contains an entry that can supplant -** pTemplate, in other words if pTemplate does not belong on the list. +** Return NULL if pTemplate does not belong on the WhereLoop list. +** In other words if pTemplate ought to be dropped from further consideration. ** -** If pX is a WhereLoop that pTemplate can supplant, then return the +** If pX is a WhereLoop that pTemplate can replace, then return the ** link that points to pX. ** -** If pTemplate cannot supplant any existing element of the list but needs -** to be added to the list, then return a pointer to the tail of the list. +** If pTemplate cannot replace any existing element of the list but needs +** to be added to the list as a new entry, then return a pointer to the +** tail of the list. */ static WhereLoop **whereLoopFindLesser( WhereLoop **ppPrev, @@ -131933,8 +133556,10 @@ static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){ if( p!=0 ){ sqlite3DebugPrintf("replace: "); whereLoopPrint(p, pBuilder->pWC); + sqlite3DebugPrintf(" with: "); + }else{ + sqlite3DebugPrintf(" add: "); } - sqlite3DebugPrintf(" add: "); whereLoopPrint(pTemplate, pBuilder->pWC); } #endif @@ -132485,7 +134110,7 @@ static int indexMightHelpWithOrderBy( }else if( (aColExpr = pIndex->aColExpr)!=0 ){ for(jj=0; jjnKeyCol; jj++){ if( pIndex->aiColumn[jj]!=XN_EXPR ) continue; - if( sqlite3ExprCompare(pExpr,aColExpr->a[jj].pExpr,iCursor)==0 ){ + if( sqlite3ExprCompare(0, pExpr,aColExpr->a[jj].pExpr,iCursor)==0 ){ return 1; } } @@ -132518,14 +134143,16 @@ static Bitmask columnsInIndex(Index *pIdx){ static int whereUsablePartialIndex(int iTab, WhereClause *pWC, Expr *pWhere){ int i; WhereTerm *pTerm; + Parse *pParse = pWC->pWInfo->pParse; while( pWhere->op==TK_AND ){ if( !whereUsablePartialIndex(iTab,pWC,pWhere->pLeft) ) return 0; pWhere = pWhere->pRight; } + if( pParse->db->flags & SQLITE_EnableQPSG ) pParse = 0; for(i=0, pTerm=pWC->a; inTerm; i++, pTerm++){ Expr *pExpr = pTerm->pExpr; - if( sqlite3ExprImpliesExpr(pExpr, pWhere, iTab) - && (!ExprHasProperty(pExpr, EP_FromJoin) || pExpr->iRightJoinTable==iTab) + if( (!ExprHasProperty(pExpr, EP_FromJoin) || pExpr->iRightJoinTable==iTab) + && sqlite3ExprImpliesExpr(pParse, pExpr, pWhere, iTab) ){ return 1; } @@ -133087,7 +134714,7 @@ static int whereLoopAddVirtual( } if( p->needToFreeIdxStr ) sqlite3_free(p->idxStr); - sqlite3DbFree(pParse->db, p); + sqlite3DbFreeNN(pParse->db, p); return rc; } #endif /* SQLITE_OMIT_VIRTUALTABLE */ @@ -133271,7 +134898,7 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){ } /* -** Examine a WherePath (with the addition of the extra WhereLoop of the 5th +** Examine a WherePath (with the addition of the extra WhereLoop of the 6th ** parameters) to see if it outputs rows in the requested ORDER BY ** (or GROUP BY) without requiring a separate sort operation. Return N: ** @@ -133366,6 +134993,8 @@ static i8 wherePathSatisfiesOrderBy( if( pLoop->wsFlags & WHERE_VIRTUALTABLE ){ if( pLoop->u.vtab.isOrdered ) obSat = obDone; break; + }else{ + pLoop->u.btree.nIdxCol = 0; } iCur = pWInfo->pTabList->a[pLoop->iTab].iCursor; @@ -133502,7 +135131,8 @@ static i8 wherePathSatisfiesOrderBy( if( pOBExpr->iTable!=iCur ) continue; if( pOBExpr->iColumn!=iColumn ) continue; }else{ - if( sqlite3ExprCompare(pOBExpr,pIndex->aColExpr->a[j].pExpr,iCur) ){ + if( sqlite3ExprCompare(0, + pOBExpr,pIndex->aColExpr->a[j].pExpr,iCur) ){ continue; } } @@ -133511,6 +135141,7 @@ static i8 wherePathSatisfiesOrderBy( if( !pColl ) pColl = db->pDfltColl; if( sqlite3StrICmp(pColl->zName, pIndex->azColl[j])!=0 ) continue; } + pLoop->u.btree.nIdxCol = j+1; isMatch = 1; break; } @@ -133800,6 +135431,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ rUnsorted, rCost)); }else{ rCost = rUnsorted; + rUnsorted -= 2; /* TUNING: Slight bias in favor of no-sort plans */ } /* Check to see if pWLoop should be added to the set of @@ -133831,8 +135463,8 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ ** this candidate as not viable. */ #ifdef WHERETRACE_ENABLED /* 0x4 */ if( sqlite3WhereTrace&0x4 ){ - sqlite3DebugPrintf("Skip %s cost=%-3d,%3d order=%c\n", - wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, + sqlite3DebugPrintf("Skip %s cost=%-3d,%3d,%3d order=%c\n", + wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, rUnsorted, isOrdered>=0 ? isOrdered+'0' : '?'); } #endif @@ -133850,26 +135482,36 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ pTo = &aTo[jj]; #ifdef WHERETRACE_ENABLED /* 0x4 */ if( sqlite3WhereTrace&0x4 ){ - sqlite3DebugPrintf("New %s cost=%-3d,%3d order=%c\n", - wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, + sqlite3DebugPrintf("New %s cost=%-3d,%3d,%3d order=%c\n", + wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, rUnsorted, isOrdered>=0 ? isOrdered+'0' : '?'); } #endif }else{ /* Control reaches here if best-so-far path pTo=aTo[jj] covers the - ** same set of loops and has the sam isOrdered setting as the + ** same set of loops and has the same isOrdered setting as the ** candidate path. Check to see if the candidate should replace - ** pTo or if the candidate should be skipped */ - if( pTo->rCostrCost==rCost && pTo->nRow<=nOut) ){ + ** pTo or if the candidate should be skipped. + ** + ** The conditional is an expanded vector comparison equivalent to: + ** (pTo->rCost,pTo->nRow,pTo->rUnsorted) <= (rCost,nOut,rUnsorted) + */ + if( pTo->rCostrCost==rCost + && (pTo->nRownRow==nOut && pTo->rUnsorted<=rUnsorted) + ) + ) + ){ #ifdef WHERETRACE_ENABLED /* 0x4 */ if( sqlite3WhereTrace&0x4 ){ sqlite3DebugPrintf( - "Skip %s cost=%-3d,%3d order=%c", - wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, + "Skip %s cost=%-3d,%3d,%3d order=%c", + wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, rUnsorted, isOrdered>=0 ? isOrdered+'0' : '?'); - sqlite3DebugPrintf(" vs %s cost=%-3d,%d order=%c\n", + sqlite3DebugPrintf(" vs %s cost=%-3d,%3d,%3d order=%c\n", wherePathName(pTo, iLoop+1, 0), pTo->rCost, pTo->nRow, - pTo->isOrdered>=0 ? pTo->isOrdered+'0' : '?'); + pTo->rUnsorted, pTo->isOrdered>=0 ? pTo->isOrdered+'0' : '?'); } #endif /* Discard the candidate path from further consideration */ @@ -133882,12 +135524,12 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ #ifdef WHERETRACE_ENABLED /* 0x4 */ if( sqlite3WhereTrace&0x4 ){ sqlite3DebugPrintf( - "Update %s cost=%-3d,%3d order=%c", - wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, + "Update %s cost=%-3d,%3d,%3d order=%c", + wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, rUnsorted, isOrdered>=0 ? isOrdered+'0' : '?'); - sqlite3DebugPrintf(" was %s cost=%-3d,%3d order=%c\n", + sqlite3DebugPrintf(" was %s cost=%-3d,%3d,%3d order=%c\n", wherePathName(pTo, iLoop+1, 0), pTo->rCost, pTo->nRow, - pTo->isOrdered>=0 ? pTo->isOrdered+'0' : '?'); + pTo->rUnsorted, pTo->isOrdered>=0 ? pTo->isOrdered+'0' : '?'); } #endif } @@ -133942,7 +135584,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ if( nFrom==0 ){ sqlite3ErrorMsg(pParse, "no query solution"); - sqlite3DbFree(db, pSpace); + sqlite3DbFreeNN(db, pSpace); return SQLITE_ERROR; } @@ -134018,7 +135660,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ pWInfo->nRowOut = pFrom->nRow; /* Free temporary memory and return success */ - sqlite3DbFree(db, pSpace); + sqlite3DbFreeNN(db, pSpace); return SQLITE_OK; } @@ -134096,7 +135738,8 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){ if( pLoop->wsFlags ){ pLoop->nOut = (LogEst)1; pWInfo->a[0].pWLoop = pLoop; - pLoop->maskSelf = sqlite3WhereGetMask(&pWInfo->sMaskSet, iCur); + assert( pWInfo->sMaskSet.n==1 && iCur==pWInfo->sMaskSet.ix[0] ); + pLoop->maskSelf = 1; /* sqlite3WhereGetMask(&pWInfo->sMaskSet, iCur); */ pWInfo->a[0].iTabCur = iCur; pWInfo->nRowOut = 1; if( pWInfo->pOrderBy ) pWInfo->nOBSat = pWInfo->pOrderBy->nExpr; @@ -134111,6 +135754,31 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){ return 0; } +/* +** Helper function for exprIsDeterministic(). +*/ +static int exprNodeIsDeterministic(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_FUNCTION && ExprHasProperty(pExpr, EP_ConstFunc)==0 ){ + pWalker->eCode = 0; + return WRC_Abort; + } + return WRC_Continue; +} + +/* +** Return true if the expression contains no non-deterministic SQL +** functions. Do not consider non-deterministic SQL functions that are +** part of sub-select statements. +*/ +static int exprIsDeterministic(Expr *p){ + Walker w; + memset(&w, 0, sizeof(w)); + w.eCode = 1; + w.xExprCallback = exprNodeIsDeterministic; + sqlite3WalkExpr(&w, p); + return w.eCode; +} + /* ** Generate the beginning of the loop used for WHERE clause processing. ** The return value is a pointer to an opaque structure that contains @@ -134280,6 +135948,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( pWInfo->pParse = pParse; pWInfo->pTabList = pTabList; pWInfo->pOrderBy = pOrderBy; + pWInfo->pWhere = pWhere; pWInfo->pResultSet = pResultSet; pWInfo->aiCurOnePass[0] = pWInfo->aiCurOnePass[1] = -1; pWInfo->nLevel = nTabList; @@ -134308,17 +135977,6 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( sqlite3WhereClauseInit(&pWInfo->sWC, pWInfo); sqlite3WhereSplit(&pWInfo->sWC, pWhere, TK_AND); - /* Special case: a WHERE clause that is constant. Evaluate the - ** expression and either jump over all of the code or fall thru. - */ - for(ii=0; iinTerm; ii++){ - if( nTabList==0 || sqlite3ExprIsConstantNotJoin(sWLB.pWC->a[ii].pExpr) ){ - sqlite3ExprIfFalse(pParse, sWLB.pWC->a[ii].pExpr, pWInfo->iBreak, - SQLITE_JUMPIFNULL); - sWLB.pWC->a[ii].wtFlags |= TERM_CODED; - } - } - /* Special case: No FROM clause */ if( nTabList==0 ){ @@ -134347,9 +136005,13 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( sqlite3WhereTabFuncArgs(pParse, &pTabList->a[ii], &pWInfo->sWC); } #ifdef SQLITE_DEBUG - for(ii=0; iinSrc; ii++){ - Bitmask m = sqlite3WhereGetMask(pMaskSet, pTabList->a[ii].iCursor); - assert( m==MASKBIT(ii) ); + { + Bitmask mx = 0; + for(ii=0; iinSrc; ii++){ + Bitmask m = sqlite3WhereGetMask(pMaskSet, pTabList->a[ii].iCursor); + assert( m>=mx ); + mx = m; + } } #endif @@ -134357,6 +136019,25 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( sqlite3WhereExprAnalyze(pTabList, &pWInfo->sWC); if( db->mallocFailed ) goto whereBeginError; + /* Special case: WHERE terms that do not refer to any tables in the join + ** (constant expressions). Evaluate each such term, and jump over all the + ** generated code if the result is not true. + ** + ** Do not do this if the expression contains non-deterministic functions + ** that are not within a sub-select. This is not strictly required, but + ** preserves SQLite's legacy behaviour in the following two cases: + ** + ** FROM ... WHERE random()>0; -- eval random() once per row + ** FROM ... WHERE (SELECT random())>0; -- eval random() once overall + */ + for(ii=0; iinTerm; ii++){ + WhereTerm *pT = &sWLB.pWC->a[ii]; + if( pT->prereqAll==0 && (nTabList==0 || exprIsDeterministic(pT->pExpr)) ){ + sqlite3ExprIfFalse(pParse, pT->pExpr, pWInfo->iBreak, SQLITE_JUMPIFNULL); + pT->wtFlags |= TERM_CODED; + } + } + if( wctrlFlags & WHERE_WANT_DISTINCT ){ if( isDistinctRedundant(pParse, pTabList, &pWInfo->sWC, pResultSet) ){ /* The DISTINCT marking is pointless. Ignore it. */ @@ -134393,7 +136074,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( static const char zLabel[] = "0123456789abcdefghijklmnopqrstuvwyxz" "ABCDEFGHIJKLMNOPQRSTUVWYXZ"; for(p=pWInfo->pLoops, i=0; p; p=p->pNextLoop, i++){ - p->cId = zLabel[i%sizeof(zLabel)]; + p->cId = zLabel[i%(sizeof(zLabel)-1)]; whereLoopPrint(p, sWLB.pWC); } } @@ -134590,6 +136271,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( if( (pLoop->wsFlags & WHERE_CONSTRAINT)!=0 && (pLoop->wsFlags & (WHERE_COLUMN_RANGE|WHERE_SKIPSCAN))==0 && (pWInfo->wctrlFlags&WHERE_ORDERBY_MIN)==0 + && pWInfo->eDistinct!=WHERE_DISTINCT_ORDERED ){ sqlite3VdbeChangeP5(v, OPFLAG_SEEKEQ); /* Hint to COMDB2 */ } @@ -134678,14 +136360,43 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ int addr; pLevel = &pWInfo->a[i]; pLoop = pLevel->pWLoop; - sqlite3VdbeResolveLabel(v, pLevel->addrCont); if( pLevel->op!=OP_Noop ){ +#ifndef SQLITE_DISABLE_SKIPAHEAD_DISTINCT + int addrSeek = 0; + Index *pIdx; + int n; + if( pWInfo->eDistinct==WHERE_DISTINCT_ORDERED + && (pLoop->wsFlags & WHERE_INDEXED)!=0 + && (pIdx = pLoop->u.btree.pIndex)->hasStat1 + && (n = pLoop->u.btree.nIdxCol)>0 + && pIdx->aiRowLogEst[n]>=36 + ){ + int r1 = pParse->nMem+1; + int j, op; + for(j=0; jiIdxCur, j, r1+j); + } + pParse->nMem += n+1; + op = pLevel->op==OP_Prev ? OP_SeekLT : OP_SeekGT; + addrSeek = sqlite3VdbeAddOp4Int(v, op, pLevel->iIdxCur, 0, r1, n); + VdbeCoverageIf(v, op==OP_SeekLT); + VdbeCoverageIf(v, op==OP_SeekGT); + sqlite3VdbeAddOp2(v, OP_Goto, 1, pLevel->p2); + } +#endif /* SQLITE_DISABLE_SKIPAHEAD_DISTINCT */ + /* The common case: Advance to the next row */ + sqlite3VdbeResolveLabel(v, pLevel->addrCont); sqlite3VdbeAddOp3(v, pLevel->op, pLevel->p1, pLevel->p2, pLevel->p3); sqlite3VdbeChangeP5(v, pLevel->p5); VdbeCoverage(v); VdbeCoverageIf(v, pLevel->op==OP_Next); VdbeCoverageIf(v, pLevel->op==OP_Prev); VdbeCoverageIf(v, pLevel->op==OP_VNext); +#ifndef SQLITE_DISABLE_SKIPAHEAD_DISTINCT + if( addrSeek ) sqlite3VdbeJumpHere(v, addrSeek); +#endif + }else{ + sqlite3VdbeResolveLabel(v, pLevel->addrCont); } if( pLoop->wsFlags & WHERE_IN_ABLE && pLevel->u.in.nIn>0 ){ struct InLoop *pIn; @@ -134808,6 +136519,8 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ }else if( pOp->opcode==OP_Rowid ){ pOp->p1 = pLevel->iIdxCur; pOp->opcode = OP_IdxRowid; + }else if( pOp->opcode==OP_IfNullRow ){ + pOp->p1 = pLevel->iIdxCur; } } } @@ -135117,7 +136830,7 @@ static void disableLookaside(Parse *pParse){ #define YYCODETYPE unsigned char #define YYNOCODE 252 #define YYACTIONTYPE unsigned short int -#define YYWILDCARD 96 +#define YYWILDCARD 69 #define sqlite3ParserTOKENTYPE Token typedef union { int yyinit; @@ -135143,16 +136856,16 @@ typedef union { #define sqlite3ParserARG_FETCH Parse *pParse = yypParser->pParse #define sqlite3ParserARG_STORE yypParser->pParse = pParse #define YYFALLBACK 1 -#define YYNSTATE 456 -#define YYNRULE 332 -#define YY_MAX_SHIFT 455 -#define YY_MIN_SHIFTREDUCE 668 -#define YY_MAX_SHIFTREDUCE 999 -#define YY_MIN_REDUCE 1000 -#define YY_MAX_REDUCE 1331 -#define YY_ERROR_ACTION 1332 -#define YY_ACCEPT_ACTION 1333 -#define YY_NO_ACTION 1334 +#define YYNSTATE 455 +#define YYNRULE 329 +#define YY_MAX_SHIFT 454 +#define YY_MIN_SHIFTREDUCE 664 +#define YY_MAX_SHIFTREDUCE 992 +#define YY_MIN_REDUCE 993 +#define YY_MAX_REDUCE 1321 +#define YY_ERROR_ACTION 1322 +#define YY_ACCEPT_ACTION 1323 +#define YY_NO_ACTION 1324 /************* End control #defines *******************************************/ /* Define the yytestcase() macro to be a no-op if is not already defined @@ -135224,463 +136937,463 @@ typedef union { ** yy_default[] Default action for each state. ** *********** Begin parsing tables **********************************************/ -#define YY_ACTTAB_COUNT (1567) +#define YY_ACTTAB_COUNT (1565) static const YYACTIONTYPE yy_action[] = { - /* 0 */ 325, 832, 351, 825, 5, 203, 203, 819, 99, 100, - /* 10 */ 90, 978, 978, 853, 856, 845, 845, 97, 97, 98, - /* 20 */ 98, 98, 98, 301, 96, 96, 96, 96, 95, 95, - /* 30 */ 94, 94, 94, 93, 351, 325, 976, 976, 824, 824, - /* 40 */ 826, 946, 354, 99, 100, 90, 978, 978, 853, 856, - /* 50 */ 845, 845, 97, 97, 98, 98, 98, 98, 338, 96, - /* 60 */ 96, 96, 96, 95, 95, 94, 94, 94, 93, 351, - /* 70 */ 95, 95, 94, 94, 94, 93, 351, 791, 976, 976, - /* 80 */ 325, 94, 94, 94, 93, 351, 792, 75, 99, 100, - /* 90 */ 90, 978, 978, 853, 856, 845, 845, 97, 97, 98, - /* 100 */ 98, 98, 98, 450, 96, 96, 96, 96, 95, 95, - /* 110 */ 94, 94, 94, 93, 351, 1333, 155, 155, 2, 325, - /* 120 */ 275, 146, 132, 52, 52, 93, 351, 99, 100, 90, - /* 130 */ 978, 978, 853, 856, 845, 845, 97, 97, 98, 98, - /* 140 */ 98, 98, 101, 96, 96, 96, 96, 95, 95, 94, - /* 150 */ 94, 94, 93, 351, 957, 957, 325, 268, 428, 413, - /* 160 */ 411, 61, 752, 752, 99, 100, 90, 978, 978, 853, - /* 170 */ 856, 845, 845, 97, 97, 98, 98, 98, 98, 60, - /* 180 */ 96, 96, 96, 96, 95, 95, 94, 94, 94, 93, - /* 190 */ 351, 325, 270, 329, 273, 277, 958, 959, 250, 99, - /* 200 */ 100, 90, 978, 978, 853, 856, 845, 845, 97, 97, - /* 210 */ 98, 98, 98, 98, 301, 96, 96, 96, 96, 95, - /* 220 */ 95, 94, 94, 94, 93, 351, 325, 937, 1326, 698, - /* 230 */ 706, 1326, 242, 412, 99, 100, 90, 978, 978, 853, - /* 240 */ 856, 845, 845, 97, 97, 98, 98, 98, 98, 347, - /* 250 */ 96, 96, 96, 96, 95, 95, 94, 94, 94, 93, - /* 260 */ 351, 325, 937, 1327, 384, 699, 1327, 381, 379, 99, - /* 270 */ 100, 90, 978, 978, 853, 856, 845, 845, 97, 97, - /* 280 */ 98, 98, 98, 98, 701, 96, 96, 96, 96, 95, - /* 290 */ 95, 94, 94, 94, 93, 351, 325, 92, 89, 178, - /* 300 */ 833, 935, 373, 700, 99, 100, 90, 978, 978, 853, - /* 310 */ 856, 845, 845, 97, 97, 98, 98, 98, 98, 375, - /* 320 */ 96, 96, 96, 96, 95, 95, 94, 94, 94, 93, - /* 330 */ 351, 325, 1275, 946, 354, 818, 935, 739, 739, 99, - /* 340 */ 100, 90, 978, 978, 853, 856, 845, 845, 97, 97, - /* 350 */ 98, 98, 98, 98, 230, 96, 96, 96, 96, 95, - /* 360 */ 95, 94, 94, 94, 93, 351, 325, 968, 227, 92, - /* 370 */ 89, 178, 373, 300, 99, 100, 90, 978, 978, 853, - /* 380 */ 856, 845, 845, 97, 97, 98, 98, 98, 98, 920, - /* 390 */ 96, 96, 96, 96, 95, 95, 94, 94, 94, 93, - /* 400 */ 351, 325, 449, 447, 447, 447, 147, 737, 737, 99, - /* 410 */ 100, 90, 978, 978, 853, 856, 845, 845, 97, 97, - /* 420 */ 98, 98, 98, 98, 296, 96, 96, 96, 96, 95, - /* 430 */ 95, 94, 94, 94, 93, 351, 325, 419, 231, 957, - /* 440 */ 957, 158, 25, 422, 99, 100, 90, 978, 978, 853, - /* 450 */ 856, 845, 845, 97, 97, 98, 98, 98, 98, 450, - /* 460 */ 96, 96, 96, 96, 95, 95, 94, 94, 94, 93, - /* 470 */ 351, 443, 224, 224, 420, 957, 957, 961, 325, 52, - /* 480 */ 52, 958, 959, 176, 415, 78, 99, 100, 90, 978, - /* 490 */ 978, 853, 856, 845, 845, 97, 97, 98, 98, 98, - /* 500 */ 98, 379, 96, 96, 96, 96, 95, 95, 94, 94, - /* 510 */ 94, 93, 351, 325, 428, 418, 298, 958, 959, 961, - /* 520 */ 81, 99, 88, 90, 978, 978, 853, 856, 845, 845, - /* 530 */ 97, 97, 98, 98, 98, 98, 717, 96, 96, 96, - /* 540 */ 96, 95, 95, 94, 94, 94, 93, 351, 325, 842, - /* 550 */ 842, 854, 857, 996, 318, 343, 379, 100, 90, 978, - /* 560 */ 978, 853, 856, 845, 845, 97, 97, 98, 98, 98, - /* 570 */ 98, 450, 96, 96, 96, 96, 95, 95, 94, 94, - /* 580 */ 94, 93, 351, 325, 350, 350, 350, 260, 377, 340, - /* 590 */ 928, 52, 52, 90, 978, 978, 853, 856, 845, 845, - /* 600 */ 97, 97, 98, 98, 98, 98, 361, 96, 96, 96, - /* 610 */ 96, 95, 95, 94, 94, 94, 93, 351, 86, 445, - /* 620 */ 846, 3, 1202, 361, 360, 378, 344, 813, 957, 957, - /* 630 */ 1299, 86, 445, 729, 3, 212, 169, 287, 405, 282, - /* 640 */ 404, 199, 232, 450, 300, 760, 83, 84, 280, 245, - /* 650 */ 262, 365, 251, 85, 352, 352, 92, 89, 178, 83, - /* 660 */ 84, 242, 412, 52, 52, 448, 85, 352, 352, 246, - /* 670 */ 958, 959, 194, 455, 670, 402, 399, 398, 448, 243, - /* 680 */ 221, 114, 434, 776, 361, 450, 397, 268, 747, 224, - /* 690 */ 224, 132, 132, 198, 832, 434, 452, 451, 428, 427, - /* 700 */ 819, 415, 734, 713, 132, 52, 52, 832, 268, 452, - /* 710 */ 451, 734, 194, 819, 363, 402, 399, 398, 450, 1270, - /* 720 */ 1270, 23, 957, 957, 86, 445, 397, 3, 228, 429, - /* 730 */ 894, 824, 824, 826, 827, 19, 203, 720, 52, 52, - /* 740 */ 428, 408, 439, 249, 824, 824, 826, 827, 19, 229, - /* 750 */ 403, 153, 83, 84, 761, 177, 241, 450, 721, 85, - /* 760 */ 352, 352, 120, 157, 958, 959, 58, 976, 409, 355, - /* 770 */ 330, 448, 268, 428, 430, 320, 790, 32, 32, 86, - /* 780 */ 445, 776, 3, 341, 98, 98, 98, 98, 434, 96, - /* 790 */ 96, 96, 96, 95, 95, 94, 94, 94, 93, 351, - /* 800 */ 832, 120, 452, 451, 813, 886, 819, 83, 84, 976, - /* 810 */ 813, 132, 410, 919, 85, 352, 352, 132, 407, 789, - /* 820 */ 957, 957, 92, 89, 178, 916, 448, 262, 370, 261, - /* 830 */ 82, 913, 80, 262, 370, 261, 776, 824, 824, 826, - /* 840 */ 827, 19, 933, 434, 96, 96, 96, 96, 95, 95, - /* 850 */ 94, 94, 94, 93, 351, 832, 74, 452, 451, 957, - /* 860 */ 957, 819, 958, 959, 120, 92, 89, 178, 944, 2, - /* 870 */ 917, 964, 268, 1, 975, 76, 445, 762, 3, 708, - /* 880 */ 900, 900, 387, 957, 957, 757, 918, 371, 740, 778, - /* 890 */ 756, 257, 824, 824, 826, 827, 19, 417, 741, 450, - /* 900 */ 24, 958, 959, 83, 84, 369, 957, 957, 177, 226, - /* 910 */ 85, 352, 352, 884, 315, 314, 313, 215, 311, 10, - /* 920 */ 10, 683, 448, 349, 348, 958, 959, 908, 777, 157, - /* 930 */ 120, 957, 957, 337, 776, 416, 711, 310, 450, 434, - /* 940 */ 450, 321, 450, 791, 103, 200, 175, 450, 958, 959, - /* 950 */ 907, 832, 792, 452, 451, 9, 9, 819, 10, 10, - /* 960 */ 52, 52, 51, 51, 180, 716, 248, 10, 10, 171, - /* 970 */ 170, 167, 339, 958, 959, 247, 984, 702, 702, 450, - /* 980 */ 715, 233, 686, 982, 888, 983, 182, 913, 824, 824, - /* 990 */ 826, 827, 19, 183, 256, 423, 132, 181, 394, 10, - /* 1000 */ 10, 888, 890, 749, 957, 957, 916, 268, 985, 198, - /* 1010 */ 985, 349, 348, 425, 415, 299, 817, 832, 326, 825, - /* 1020 */ 120, 332, 133, 819, 268, 98, 98, 98, 98, 91, - /* 1030 */ 96, 96, 96, 96, 95, 95, 94, 94, 94, 93, - /* 1040 */ 351, 157, 810, 371, 382, 359, 958, 959, 358, 268, - /* 1050 */ 450, 917, 368, 324, 824, 824, 826, 450, 709, 450, - /* 1060 */ 264, 380, 888, 450, 876, 746, 253, 918, 255, 433, - /* 1070 */ 36, 36, 234, 450, 234, 120, 269, 37, 37, 12, - /* 1080 */ 12, 334, 272, 27, 27, 450, 330, 118, 450, 162, - /* 1090 */ 742, 280, 450, 38, 38, 450, 985, 356, 985, 450, - /* 1100 */ 709, 1209, 450, 132, 450, 39, 39, 450, 40, 40, - /* 1110 */ 450, 362, 41, 41, 450, 42, 42, 450, 254, 28, - /* 1120 */ 28, 450, 29, 29, 31, 31, 450, 43, 43, 450, - /* 1130 */ 44, 44, 450, 714, 45, 45, 450, 11, 11, 767, - /* 1140 */ 450, 46, 46, 450, 268, 450, 105, 105, 450, 47, - /* 1150 */ 47, 450, 48, 48, 450, 237, 33, 33, 450, 172, - /* 1160 */ 49, 49, 450, 50, 50, 34, 34, 274, 122, 122, - /* 1170 */ 450, 123, 123, 450, 124, 124, 450, 897, 56, 56, - /* 1180 */ 450, 896, 35, 35, 450, 267, 450, 817, 450, 817, - /* 1190 */ 106, 106, 450, 53, 53, 385, 107, 107, 450, 817, - /* 1200 */ 108, 108, 817, 450, 104, 104, 121, 121, 119, 119, - /* 1210 */ 450, 117, 112, 112, 450, 276, 450, 225, 111, 111, - /* 1220 */ 450, 730, 450, 109, 109, 450, 673, 674, 675, 911, - /* 1230 */ 110, 110, 317, 998, 55, 55, 57, 57, 692, 331, - /* 1240 */ 54, 54, 26, 26, 696, 30, 30, 317, 936, 197, - /* 1250 */ 196, 195, 335, 281, 336, 446, 331, 745, 689, 436, - /* 1260 */ 440, 444, 120, 72, 386, 223, 175, 345, 757, 932, - /* 1270 */ 20, 286, 319, 756, 815, 372, 374, 202, 202, 202, - /* 1280 */ 263, 395, 285, 74, 208, 21, 696, 719, 718, 883, - /* 1290 */ 120, 120, 120, 120, 120, 754, 278, 828, 77, 74, - /* 1300 */ 726, 727, 785, 783, 879, 202, 999, 208, 893, 892, - /* 1310 */ 893, 892, 694, 816, 763, 116, 774, 1289, 431, 432, - /* 1320 */ 302, 999, 390, 303, 823, 697, 691, 680, 159, 289, - /* 1330 */ 679, 883, 681, 951, 291, 218, 293, 7, 316, 828, - /* 1340 */ 173, 805, 259, 364, 252, 910, 376, 713, 295, 435, - /* 1350 */ 308, 168, 954, 993, 135, 400, 990, 284, 881, 880, - /* 1360 */ 205, 927, 925, 59, 333, 62, 144, 156, 130, 72, - /* 1370 */ 802, 366, 367, 393, 137, 185, 189, 160, 139, 383, - /* 1380 */ 67, 895, 140, 141, 142, 148, 389, 812, 775, 266, - /* 1390 */ 219, 190, 154, 391, 912, 875, 271, 406, 191, 322, - /* 1400 */ 682, 733, 192, 342, 732, 724, 731, 711, 723, 421, - /* 1410 */ 705, 71, 323, 6, 204, 771, 288, 79, 297, 346, - /* 1420 */ 772, 704, 290, 283, 703, 770, 292, 294, 966, 239, - /* 1430 */ 769, 102, 861, 438, 426, 240, 424, 442, 73, 213, - /* 1440 */ 688, 238, 22, 453, 952, 214, 217, 216, 454, 677, - /* 1450 */ 676, 671, 753, 125, 115, 235, 126, 669, 353, 166, - /* 1460 */ 127, 244, 179, 357, 306, 304, 305, 307, 113, 891, - /* 1470 */ 327, 889, 811, 328, 134, 128, 136, 138, 743, 258, - /* 1480 */ 906, 184, 143, 129, 909, 186, 63, 64, 145, 187, - /* 1490 */ 905, 65, 8, 66, 13, 188, 202, 898, 265, 149, - /* 1500 */ 987, 388, 150, 685, 161, 392, 285, 193, 279, 396, - /* 1510 */ 151, 401, 68, 14, 15, 722, 69, 236, 831, 131, - /* 1520 */ 830, 859, 70, 751, 16, 414, 755, 4, 174, 220, - /* 1530 */ 222, 784, 201, 152, 779, 77, 74, 17, 18, 874, - /* 1540 */ 860, 858, 915, 863, 914, 207, 206, 941, 163, 437, - /* 1550 */ 947, 942, 164, 209, 1002, 441, 862, 165, 210, 829, - /* 1560 */ 695, 87, 312, 211, 1291, 1290, 309, + /* 0 */ 324, 410, 342, 747, 747, 203, 939, 353, 969, 98, + /* 10 */ 98, 98, 98, 91, 96, 96, 96, 96, 95, 95, + /* 20 */ 94, 94, 94, 93, 350, 1323, 155, 155, 2, 808, + /* 30 */ 971, 971, 98, 98, 98, 98, 20, 96, 96, 96, + /* 40 */ 96, 95, 95, 94, 94, 94, 93, 350, 92, 89, + /* 50 */ 178, 99, 100, 90, 847, 850, 839, 839, 97, 97, + /* 60 */ 98, 98, 98, 98, 350, 96, 96, 96, 96, 95, + /* 70 */ 95, 94, 94, 94, 93, 350, 324, 339, 969, 262, + /* 80 */ 364, 251, 212, 169, 287, 404, 282, 403, 199, 786, + /* 90 */ 242, 411, 21, 950, 378, 280, 93, 350, 787, 95, + /* 100 */ 95, 94, 94, 94, 93, 350, 971, 971, 96, 96, + /* 110 */ 96, 96, 95, 95, 94, 94, 94, 93, 350, 808, + /* 120 */ 328, 242, 411, 1235, 826, 1235, 132, 99, 100, 90, + /* 130 */ 847, 850, 839, 839, 97, 97, 98, 98, 98, 98, + /* 140 */ 449, 96, 96, 96, 96, 95, 95, 94, 94, 94, + /* 150 */ 93, 350, 324, 819, 348, 347, 120, 818, 120, 75, + /* 160 */ 52, 52, 950, 951, 952, 1084, 977, 146, 360, 262, + /* 170 */ 369, 261, 950, 975, 954, 976, 92, 89, 178, 370, + /* 180 */ 230, 370, 971, 971, 1141, 360, 359, 101, 818, 818, + /* 190 */ 820, 383, 24, 1286, 380, 427, 412, 368, 978, 379, + /* 200 */ 978, 1032, 324, 99, 100, 90, 847, 850, 839, 839, + /* 210 */ 97, 97, 98, 98, 98, 98, 372, 96, 96, 96, + /* 220 */ 96, 95, 95, 94, 94, 94, 93, 350, 950, 132, + /* 230 */ 890, 449, 971, 971, 890, 60, 94, 94, 94, 93, + /* 240 */ 350, 950, 951, 952, 954, 103, 360, 950, 384, 333, + /* 250 */ 697, 52, 52, 99, 100, 90, 847, 850, 839, 839, + /* 260 */ 97, 97, 98, 98, 98, 98, 1022, 96, 96, 96, + /* 270 */ 96, 95, 95, 94, 94, 94, 93, 350, 324, 454, + /* 280 */ 995, 449, 227, 61, 157, 243, 343, 114, 1025, 1211, + /* 290 */ 147, 826, 950, 372, 1071, 950, 319, 950, 951, 952, + /* 300 */ 194, 10, 10, 401, 398, 397, 1211, 1213, 971, 971, + /* 310 */ 757, 171, 170, 157, 396, 336, 950, 951, 952, 697, + /* 320 */ 819, 310, 153, 950, 818, 320, 82, 23, 80, 99, + /* 330 */ 100, 90, 847, 850, 839, 839, 97, 97, 98, 98, + /* 340 */ 98, 98, 888, 96, 96, 96, 96, 95, 95, 94, + /* 350 */ 94, 94, 93, 350, 324, 818, 818, 820, 277, 231, + /* 360 */ 300, 950, 951, 952, 950, 951, 952, 1211, 194, 25, + /* 370 */ 449, 401, 398, 397, 950, 354, 300, 449, 950, 74, + /* 380 */ 449, 1, 396, 132, 971, 971, 950, 224, 224, 808, + /* 390 */ 10, 10, 950, 951, 952, 1290, 132, 52, 52, 414, + /* 400 */ 52, 52, 1063, 1063, 338, 99, 100, 90, 847, 850, + /* 410 */ 839, 839, 97, 97, 98, 98, 98, 98, 1114, 96, + /* 420 */ 96, 96, 96, 95, 95, 94, 94, 94, 93, 350, + /* 430 */ 324, 1113, 427, 417, 701, 427, 426, 1260, 1260, 262, + /* 440 */ 369, 261, 950, 950, 951, 952, 752, 950, 951, 952, + /* 450 */ 449, 751, 449, 1058, 1037, 950, 951, 952, 442, 706, + /* 460 */ 971, 971, 1058, 393, 92, 89, 178, 446, 446, 446, + /* 470 */ 51, 51, 52, 52, 438, 773, 1024, 92, 89, 178, + /* 480 */ 172, 99, 100, 90, 847, 850, 839, 839, 97, 97, + /* 490 */ 98, 98, 98, 98, 198, 96, 96, 96, 96, 95, + /* 500 */ 95, 94, 94, 94, 93, 350, 324, 427, 407, 909, + /* 510 */ 694, 950, 951, 952, 92, 89, 178, 224, 224, 157, + /* 520 */ 241, 221, 418, 299, 771, 910, 415, 374, 449, 414, + /* 530 */ 58, 323, 1061, 1061, 1242, 378, 971, 971, 378, 772, + /* 540 */ 448, 911, 362, 735, 296, 681, 9, 9, 52, 52, + /* 550 */ 234, 329, 234, 256, 416, 736, 280, 99, 100, 90, + /* 560 */ 847, 850, 839, 839, 97, 97, 98, 98, 98, 98, + /* 570 */ 449, 96, 96, 96, 96, 95, 95, 94, 94, 94, + /* 580 */ 93, 350, 324, 422, 72, 449, 827, 120, 367, 449, + /* 590 */ 10, 10, 5, 301, 203, 449, 177, 969, 253, 419, + /* 600 */ 255, 771, 200, 175, 233, 10, 10, 836, 836, 36, + /* 610 */ 36, 1289, 971, 971, 724, 37, 37, 348, 347, 424, + /* 620 */ 203, 260, 771, 969, 232, 930, 1316, 870, 337, 1316, + /* 630 */ 421, 848, 851, 99, 100, 90, 847, 850, 839, 839, + /* 640 */ 97, 97, 98, 98, 98, 98, 268, 96, 96, 96, + /* 650 */ 96, 95, 95, 94, 94, 94, 93, 350, 324, 840, + /* 660 */ 449, 978, 813, 978, 1200, 449, 909, 969, 715, 349, + /* 670 */ 349, 349, 928, 177, 449, 930, 1317, 254, 198, 1317, + /* 680 */ 12, 12, 910, 402, 449, 27, 27, 250, 971, 971, + /* 690 */ 118, 716, 162, 969, 38, 38, 268, 176, 911, 771, + /* 700 */ 432, 1265, 939, 353, 39, 39, 316, 991, 324, 99, + /* 710 */ 100, 90, 847, 850, 839, 839, 97, 97, 98, 98, + /* 720 */ 98, 98, 928, 96, 96, 96, 96, 95, 95, 94, + /* 730 */ 94, 94, 93, 350, 449, 329, 449, 357, 971, 971, + /* 740 */ 1041, 316, 929, 340, 893, 893, 386, 669, 670, 671, + /* 750 */ 275, 1318, 317, 992, 40, 40, 41, 41, 268, 99, + /* 760 */ 100, 90, 847, 850, 839, 839, 97, 97, 98, 98, + /* 770 */ 98, 98, 449, 96, 96, 96, 96, 95, 95, 94, + /* 780 */ 94, 94, 93, 350, 324, 449, 355, 449, 992, 449, + /* 790 */ 1016, 330, 42, 42, 786, 270, 449, 273, 449, 228, + /* 800 */ 449, 298, 449, 787, 449, 28, 28, 29, 29, 31, + /* 810 */ 31, 449, 1141, 449, 971, 971, 43, 43, 44, 44, + /* 820 */ 45, 45, 11, 11, 46, 46, 887, 78, 887, 268, + /* 830 */ 268, 105, 105, 47, 47, 99, 100, 90, 847, 850, + /* 840 */ 839, 839, 97, 97, 98, 98, 98, 98, 449, 96, + /* 850 */ 96, 96, 96, 95, 95, 94, 94, 94, 93, 350, + /* 860 */ 324, 449, 117, 449, 1073, 158, 449, 691, 48, 48, + /* 870 */ 229, 1241, 449, 1250, 449, 414, 449, 334, 449, 245, + /* 880 */ 449, 33, 33, 49, 49, 449, 50, 50, 246, 1141, + /* 890 */ 971, 971, 34, 34, 122, 122, 123, 123, 124, 124, + /* 900 */ 56, 56, 268, 81, 249, 35, 35, 197, 196, 195, + /* 910 */ 324, 99, 100, 90, 847, 850, 839, 839, 97, 97, + /* 920 */ 98, 98, 98, 98, 449, 96, 96, 96, 96, 95, + /* 930 */ 95, 94, 94, 94, 93, 350, 449, 691, 449, 1141, + /* 940 */ 971, 971, 968, 1207, 106, 106, 268, 1209, 268, 1266, + /* 950 */ 2, 886, 268, 886, 335, 1040, 53, 53, 107, 107, + /* 960 */ 324, 99, 100, 90, 847, 850, 839, 839, 97, 97, + /* 970 */ 98, 98, 98, 98, 449, 96, 96, 96, 96, 95, + /* 980 */ 95, 94, 94, 94, 93, 350, 449, 1070, 449, 1066, + /* 990 */ 971, 971, 1039, 267, 108, 108, 445, 330, 331, 133, + /* 1000 */ 223, 175, 301, 225, 385, 1255, 104, 104, 121, 121, + /* 1010 */ 324, 99, 88, 90, 847, 850, 839, 839, 97, 97, + /* 1020 */ 98, 98, 98, 98, 1141, 96, 96, 96, 96, 95, + /* 1030 */ 95, 94, 94, 94, 93, 350, 449, 346, 449, 167, + /* 1040 */ 971, 971, 925, 810, 371, 318, 202, 202, 373, 263, + /* 1050 */ 394, 202, 74, 208, 721, 722, 119, 119, 112, 112, + /* 1060 */ 324, 406, 100, 90, 847, 850, 839, 839, 97, 97, + /* 1070 */ 98, 98, 98, 98, 449, 96, 96, 96, 96, 95, + /* 1080 */ 95, 94, 94, 94, 93, 350, 449, 752, 449, 344, + /* 1090 */ 971, 971, 751, 278, 111, 111, 74, 714, 713, 704, + /* 1100 */ 286, 877, 749, 1279, 257, 77, 109, 109, 110, 110, + /* 1110 */ 1230, 285, 1134, 90, 847, 850, 839, 839, 97, 97, + /* 1120 */ 98, 98, 98, 98, 1233, 96, 96, 96, 96, 95, + /* 1130 */ 95, 94, 94, 94, 93, 350, 86, 444, 449, 3, + /* 1140 */ 1193, 449, 1069, 132, 351, 120, 1013, 86, 444, 780, + /* 1150 */ 3, 1091, 202, 376, 447, 351, 1229, 120, 55, 55, + /* 1160 */ 449, 57, 57, 822, 873, 447, 449, 208, 449, 704, + /* 1170 */ 449, 877, 237, 433, 435, 120, 439, 428, 361, 120, + /* 1180 */ 54, 54, 132, 449, 433, 826, 52, 52, 26, 26, + /* 1190 */ 30, 30, 381, 132, 408, 443, 826, 689, 264, 389, + /* 1200 */ 116, 269, 272, 32, 32, 83, 84, 120, 274, 120, + /* 1210 */ 120, 276, 85, 351, 451, 450, 83, 84, 818, 1054, + /* 1220 */ 1038, 427, 429, 85, 351, 451, 450, 120, 120, 818, + /* 1230 */ 377, 218, 281, 822, 1107, 1140, 86, 444, 409, 3, + /* 1240 */ 1087, 1098, 430, 431, 351, 302, 303, 1146, 1021, 818, + /* 1250 */ 818, 820, 821, 19, 447, 1015, 1004, 1003, 1005, 1273, + /* 1260 */ 818, 818, 820, 821, 19, 289, 159, 291, 293, 7, + /* 1270 */ 315, 173, 259, 433, 1129, 363, 252, 1232, 375, 1037, + /* 1280 */ 295, 434, 168, 986, 399, 826, 284, 1204, 1203, 205, + /* 1290 */ 1276, 308, 1249, 86, 444, 983, 3, 1247, 332, 144, + /* 1300 */ 130, 351, 72, 135, 59, 83, 84, 756, 137, 365, + /* 1310 */ 1126, 447, 85, 351, 451, 450, 139, 226, 818, 140, + /* 1320 */ 156, 62, 314, 314, 313, 215, 311, 366, 392, 678, + /* 1330 */ 433, 185, 141, 1234, 142, 160, 148, 1136, 1198, 382, + /* 1340 */ 189, 67, 826, 180, 388, 248, 1218, 1099, 219, 818, + /* 1350 */ 818, 820, 821, 19, 247, 190, 266, 154, 390, 271, + /* 1360 */ 191, 192, 83, 84, 1006, 405, 1057, 182, 321, 85, + /* 1370 */ 351, 451, 450, 1056, 183, 818, 341, 132, 181, 706, + /* 1380 */ 1055, 420, 76, 444, 1029, 3, 322, 1028, 283, 1048, + /* 1390 */ 351, 1095, 1027, 1288, 1047, 71, 204, 6, 288, 290, + /* 1400 */ 447, 1096, 1094, 1093, 79, 292, 818, 818, 820, 821, + /* 1410 */ 19, 294, 297, 437, 345, 441, 102, 1184, 1077, 433, + /* 1420 */ 238, 425, 73, 305, 239, 304, 325, 240, 423, 306, + /* 1430 */ 307, 826, 213, 1012, 22, 945, 452, 214, 216, 217, + /* 1440 */ 453, 1001, 115, 996, 125, 126, 235, 127, 665, 352, + /* 1450 */ 326, 83, 84, 358, 166, 244, 179, 327, 85, 351, + /* 1460 */ 451, 450, 134, 356, 818, 113, 885, 806, 883, 136, + /* 1470 */ 128, 138, 738, 258, 184, 899, 143, 145, 63, 64, + /* 1480 */ 65, 66, 129, 902, 187, 186, 898, 8, 13, 188, + /* 1490 */ 265, 891, 149, 202, 980, 818, 818, 820, 821, 19, + /* 1500 */ 150, 387, 161, 680, 285, 391, 151, 395, 400, 193, + /* 1510 */ 68, 14, 236, 279, 15, 69, 717, 825, 131, 824, + /* 1520 */ 853, 70, 746, 16, 413, 750, 4, 174, 220, 222, + /* 1530 */ 152, 779, 857, 774, 201, 77, 74, 868, 17, 854, + /* 1540 */ 852, 908, 18, 907, 207, 206, 934, 163, 436, 210, + /* 1550 */ 935, 164, 209, 165, 440, 856, 823, 690, 87, 211, + /* 1560 */ 309, 312, 1281, 940, 1280, }; static const YYCODETYPE yy_lookahead[] = { - /* 0 */ 19, 95, 53, 97, 22, 24, 24, 101, 27, 28, - /* 10 */ 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, - /* 20 */ 39, 40, 41, 152, 43, 44, 45, 46, 47, 48, - /* 30 */ 49, 50, 51, 52, 53, 19, 55, 55, 132, 133, - /* 40 */ 134, 1, 2, 27, 28, 29, 30, 31, 32, 33, - /* 50 */ 34, 35, 36, 37, 38, 39, 40, 41, 187, 43, - /* 60 */ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, - /* 70 */ 47, 48, 49, 50, 51, 52, 53, 61, 97, 97, - /* 80 */ 19, 49, 50, 51, 52, 53, 70, 26, 27, 28, - /* 90 */ 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, - /* 100 */ 39, 40, 41, 152, 43, 44, 45, 46, 47, 48, - /* 110 */ 49, 50, 51, 52, 53, 144, 145, 146, 147, 19, - /* 120 */ 16, 22, 92, 172, 173, 52, 53, 27, 28, 29, - /* 130 */ 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, - /* 140 */ 40, 41, 81, 43, 44, 45, 46, 47, 48, 49, - /* 150 */ 50, 51, 52, 53, 55, 56, 19, 152, 207, 208, - /* 160 */ 115, 24, 117, 118, 27, 28, 29, 30, 31, 32, - /* 170 */ 33, 34, 35, 36, 37, 38, 39, 40, 41, 79, - /* 180 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, - /* 190 */ 53, 19, 88, 157, 90, 23, 97, 98, 193, 27, - /* 200 */ 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, - /* 210 */ 38, 39, 40, 41, 152, 43, 44, 45, 46, 47, - /* 220 */ 48, 49, 50, 51, 52, 53, 19, 22, 23, 172, - /* 230 */ 23, 26, 119, 120, 27, 28, 29, 30, 31, 32, - /* 240 */ 33, 34, 35, 36, 37, 38, 39, 40, 41, 187, - /* 250 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, - /* 260 */ 53, 19, 22, 23, 228, 23, 26, 231, 152, 27, - /* 270 */ 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, - /* 280 */ 38, 39, 40, 41, 172, 43, 44, 45, 46, 47, - /* 290 */ 48, 49, 50, 51, 52, 53, 19, 221, 222, 223, - /* 300 */ 23, 96, 152, 172, 27, 28, 29, 30, 31, 32, - /* 310 */ 33, 34, 35, 36, 37, 38, 39, 40, 41, 152, - /* 320 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, - /* 330 */ 53, 19, 0, 1, 2, 23, 96, 190, 191, 27, - /* 340 */ 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, - /* 350 */ 38, 39, 40, 41, 238, 43, 44, 45, 46, 47, - /* 360 */ 48, 49, 50, 51, 52, 53, 19, 185, 218, 221, - /* 370 */ 222, 223, 152, 152, 27, 28, 29, 30, 31, 32, - /* 380 */ 33, 34, 35, 36, 37, 38, 39, 40, 41, 241, - /* 390 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, - /* 400 */ 53, 19, 152, 168, 169, 170, 22, 190, 191, 27, - /* 410 */ 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, - /* 420 */ 38, 39, 40, 41, 152, 43, 44, 45, 46, 47, - /* 430 */ 48, 49, 50, 51, 52, 53, 19, 19, 218, 55, - /* 440 */ 56, 24, 22, 152, 27, 28, 29, 30, 31, 32, - /* 450 */ 33, 34, 35, 36, 37, 38, 39, 40, 41, 152, - /* 460 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, - /* 470 */ 53, 250, 194, 195, 56, 55, 56, 55, 19, 172, - /* 480 */ 173, 97, 98, 152, 206, 138, 27, 28, 29, 30, - /* 490 */ 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - /* 500 */ 41, 152, 43, 44, 45, 46, 47, 48, 49, 50, - /* 510 */ 51, 52, 53, 19, 207, 208, 152, 97, 98, 97, - /* 520 */ 138, 27, 28, 29, 30, 31, 32, 33, 34, 35, - /* 530 */ 36, 37, 38, 39, 40, 41, 181, 43, 44, 45, - /* 540 */ 46, 47, 48, 49, 50, 51, 52, 53, 19, 30, - /* 550 */ 31, 32, 33, 247, 248, 19, 152, 28, 29, 30, - /* 560 */ 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - /* 570 */ 41, 152, 43, 44, 45, 46, 47, 48, 49, 50, - /* 580 */ 51, 52, 53, 19, 168, 169, 170, 238, 19, 53, - /* 590 */ 152, 172, 173, 29, 30, 31, 32, 33, 34, 35, - /* 600 */ 36, 37, 38, 39, 40, 41, 152, 43, 44, 45, - /* 610 */ 46, 47, 48, 49, 50, 51, 52, 53, 19, 20, - /* 620 */ 101, 22, 23, 169, 170, 56, 207, 85, 55, 56, - /* 630 */ 23, 19, 20, 26, 22, 99, 100, 101, 102, 103, - /* 640 */ 104, 105, 238, 152, 152, 210, 47, 48, 112, 152, - /* 650 */ 108, 109, 110, 54, 55, 56, 221, 222, 223, 47, - /* 660 */ 48, 119, 120, 172, 173, 66, 54, 55, 56, 152, - /* 670 */ 97, 98, 99, 148, 149, 102, 103, 104, 66, 154, - /* 680 */ 23, 156, 83, 26, 230, 152, 113, 152, 163, 194, - /* 690 */ 195, 92, 92, 30, 95, 83, 97, 98, 207, 208, - /* 700 */ 101, 206, 179, 180, 92, 172, 173, 95, 152, 97, - /* 710 */ 98, 188, 99, 101, 219, 102, 103, 104, 152, 119, - /* 720 */ 120, 196, 55, 56, 19, 20, 113, 22, 193, 163, - /* 730 */ 11, 132, 133, 134, 135, 136, 24, 65, 172, 173, - /* 740 */ 207, 208, 250, 152, 132, 133, 134, 135, 136, 193, - /* 750 */ 78, 84, 47, 48, 49, 98, 199, 152, 86, 54, - /* 760 */ 55, 56, 196, 152, 97, 98, 209, 55, 163, 244, - /* 770 */ 107, 66, 152, 207, 208, 164, 175, 172, 173, 19, - /* 780 */ 20, 124, 22, 111, 38, 39, 40, 41, 83, 43, - /* 790 */ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, - /* 800 */ 95, 196, 97, 98, 85, 152, 101, 47, 48, 97, - /* 810 */ 85, 92, 207, 193, 54, 55, 56, 92, 49, 175, - /* 820 */ 55, 56, 221, 222, 223, 12, 66, 108, 109, 110, - /* 830 */ 137, 163, 139, 108, 109, 110, 26, 132, 133, 134, - /* 840 */ 135, 136, 152, 83, 43, 44, 45, 46, 47, 48, - /* 850 */ 49, 50, 51, 52, 53, 95, 26, 97, 98, 55, - /* 860 */ 56, 101, 97, 98, 196, 221, 222, 223, 146, 147, - /* 870 */ 57, 171, 152, 22, 26, 19, 20, 49, 22, 179, - /* 880 */ 108, 109, 110, 55, 56, 116, 73, 219, 75, 124, - /* 890 */ 121, 152, 132, 133, 134, 135, 136, 163, 85, 152, - /* 900 */ 232, 97, 98, 47, 48, 237, 55, 56, 98, 5, - /* 910 */ 54, 55, 56, 193, 10, 11, 12, 13, 14, 172, - /* 920 */ 173, 17, 66, 47, 48, 97, 98, 152, 124, 152, - /* 930 */ 196, 55, 56, 186, 124, 152, 106, 160, 152, 83, - /* 940 */ 152, 164, 152, 61, 22, 211, 212, 152, 97, 98, - /* 950 */ 152, 95, 70, 97, 98, 172, 173, 101, 172, 173, - /* 960 */ 172, 173, 172, 173, 60, 181, 62, 172, 173, 47, - /* 970 */ 48, 123, 186, 97, 98, 71, 100, 55, 56, 152, - /* 980 */ 181, 186, 21, 107, 152, 109, 82, 163, 132, 133, - /* 990 */ 134, 135, 136, 89, 16, 207, 92, 93, 19, 172, - /* 1000 */ 173, 169, 170, 195, 55, 56, 12, 152, 132, 30, - /* 1010 */ 134, 47, 48, 186, 206, 225, 152, 95, 114, 97, - /* 1020 */ 196, 245, 246, 101, 152, 38, 39, 40, 41, 42, - /* 1030 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, - /* 1040 */ 53, 152, 163, 219, 152, 141, 97, 98, 193, 152, - /* 1050 */ 152, 57, 91, 164, 132, 133, 134, 152, 55, 152, - /* 1060 */ 152, 237, 230, 152, 103, 193, 88, 73, 90, 75, - /* 1070 */ 172, 173, 183, 152, 185, 196, 152, 172, 173, 172, - /* 1080 */ 173, 217, 152, 172, 173, 152, 107, 22, 152, 24, - /* 1090 */ 193, 112, 152, 172, 173, 152, 132, 242, 134, 152, - /* 1100 */ 97, 140, 152, 92, 152, 172, 173, 152, 172, 173, - /* 1110 */ 152, 100, 172, 173, 152, 172, 173, 152, 140, 172, - /* 1120 */ 173, 152, 172, 173, 172, 173, 152, 172, 173, 152, - /* 1130 */ 172, 173, 152, 152, 172, 173, 152, 172, 173, 213, - /* 1140 */ 152, 172, 173, 152, 152, 152, 172, 173, 152, 172, - /* 1150 */ 173, 152, 172, 173, 152, 210, 172, 173, 152, 26, - /* 1160 */ 172, 173, 152, 172, 173, 172, 173, 152, 172, 173, - /* 1170 */ 152, 172, 173, 152, 172, 173, 152, 59, 172, 173, - /* 1180 */ 152, 63, 172, 173, 152, 193, 152, 152, 152, 152, - /* 1190 */ 172, 173, 152, 172, 173, 77, 172, 173, 152, 152, - /* 1200 */ 172, 173, 152, 152, 172, 173, 172, 173, 172, 173, - /* 1210 */ 152, 22, 172, 173, 152, 152, 152, 22, 172, 173, - /* 1220 */ 152, 152, 152, 172, 173, 152, 7, 8, 9, 163, - /* 1230 */ 172, 173, 22, 23, 172, 173, 172, 173, 166, 167, - /* 1240 */ 172, 173, 172, 173, 55, 172, 173, 22, 23, 108, - /* 1250 */ 109, 110, 217, 152, 217, 166, 167, 163, 163, 163, - /* 1260 */ 163, 163, 196, 130, 217, 211, 212, 217, 116, 23, - /* 1270 */ 22, 101, 26, 121, 23, 23, 23, 26, 26, 26, - /* 1280 */ 23, 23, 112, 26, 26, 37, 97, 100, 101, 55, - /* 1290 */ 196, 196, 196, 196, 196, 23, 23, 55, 26, 26, - /* 1300 */ 7, 8, 23, 152, 23, 26, 96, 26, 132, 132, - /* 1310 */ 134, 134, 23, 152, 152, 26, 152, 122, 152, 191, - /* 1320 */ 152, 96, 234, 152, 152, 152, 152, 152, 197, 210, - /* 1330 */ 152, 97, 152, 152, 210, 233, 210, 198, 150, 97, - /* 1340 */ 184, 201, 239, 214, 214, 201, 239, 180, 214, 227, - /* 1350 */ 200, 198, 155, 67, 243, 176, 69, 175, 175, 175, - /* 1360 */ 122, 159, 159, 240, 159, 240, 22, 220, 27, 130, - /* 1370 */ 201, 18, 159, 18, 189, 158, 158, 220, 192, 159, - /* 1380 */ 137, 236, 192, 192, 192, 189, 74, 189, 159, 235, - /* 1390 */ 159, 158, 22, 177, 201, 201, 159, 107, 158, 177, - /* 1400 */ 159, 174, 158, 76, 174, 182, 174, 106, 182, 125, - /* 1410 */ 174, 107, 177, 22, 159, 216, 215, 137, 159, 53, - /* 1420 */ 216, 176, 215, 174, 174, 216, 215, 215, 174, 229, - /* 1430 */ 216, 129, 224, 177, 126, 229, 127, 177, 128, 25, - /* 1440 */ 162, 226, 26, 161, 13, 153, 6, 153, 151, 151, - /* 1450 */ 151, 151, 205, 165, 178, 178, 165, 4, 3, 22, - /* 1460 */ 165, 142, 15, 94, 202, 204, 203, 201, 16, 23, - /* 1470 */ 249, 23, 120, 249, 246, 111, 131, 123, 20, 16, - /* 1480 */ 1, 125, 123, 111, 56, 64, 37, 37, 131, 122, - /* 1490 */ 1, 37, 5, 37, 22, 107, 26, 80, 140, 80, - /* 1500 */ 87, 72, 107, 20, 24, 19, 112, 105, 23, 79, - /* 1510 */ 22, 79, 22, 22, 22, 58, 22, 79, 23, 68, - /* 1520 */ 23, 23, 26, 116, 22, 26, 23, 22, 122, 23, - /* 1530 */ 23, 56, 64, 22, 124, 26, 26, 64, 64, 23, - /* 1540 */ 23, 23, 23, 11, 23, 22, 26, 23, 22, 24, - /* 1550 */ 1, 23, 22, 26, 251, 24, 23, 22, 122, 23, - /* 1560 */ 23, 22, 15, 122, 122, 122, 23, + /* 0 */ 19, 115, 19, 117, 118, 24, 1, 2, 27, 79, + /* 10 */ 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, + /* 20 */ 90, 91, 92, 93, 94, 144, 145, 146, 147, 58, + /* 30 */ 49, 50, 79, 80, 81, 82, 22, 84, 85, 86, + /* 40 */ 87, 88, 89, 90, 91, 92, 93, 94, 221, 222, + /* 50 */ 223, 70, 71, 72, 73, 74, 75, 76, 77, 78, + /* 60 */ 79, 80, 81, 82, 94, 84, 85, 86, 87, 88, + /* 70 */ 89, 90, 91, 92, 93, 94, 19, 94, 97, 108, + /* 80 */ 109, 110, 99, 100, 101, 102, 103, 104, 105, 32, + /* 90 */ 119, 120, 78, 27, 152, 112, 93, 94, 41, 88, + /* 100 */ 89, 90, 91, 92, 93, 94, 49, 50, 84, 85, + /* 110 */ 86, 87, 88, 89, 90, 91, 92, 93, 94, 58, + /* 120 */ 157, 119, 120, 163, 68, 163, 65, 70, 71, 72, + /* 130 */ 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, + /* 140 */ 152, 84, 85, 86, 87, 88, 89, 90, 91, 92, + /* 150 */ 93, 94, 19, 97, 88, 89, 196, 101, 196, 26, + /* 160 */ 172, 173, 96, 97, 98, 210, 100, 22, 152, 108, + /* 170 */ 109, 110, 27, 107, 27, 109, 221, 222, 223, 219, + /* 180 */ 238, 219, 49, 50, 152, 169, 170, 54, 132, 133, + /* 190 */ 134, 228, 232, 171, 231, 207, 208, 237, 132, 237, + /* 200 */ 134, 179, 19, 70, 71, 72, 73, 74, 75, 76, + /* 210 */ 77, 78, 79, 80, 81, 82, 152, 84, 85, 86, + /* 220 */ 87, 88, 89, 90, 91, 92, 93, 94, 27, 65, + /* 230 */ 30, 152, 49, 50, 34, 52, 90, 91, 92, 93, + /* 240 */ 94, 96, 97, 98, 97, 22, 230, 27, 48, 217, + /* 250 */ 27, 172, 173, 70, 71, 72, 73, 74, 75, 76, + /* 260 */ 77, 78, 79, 80, 81, 82, 172, 84, 85, 86, + /* 270 */ 87, 88, 89, 90, 91, 92, 93, 94, 19, 148, + /* 280 */ 149, 152, 218, 24, 152, 154, 207, 156, 172, 152, + /* 290 */ 22, 68, 27, 152, 163, 27, 164, 96, 97, 98, + /* 300 */ 99, 172, 173, 102, 103, 104, 169, 170, 49, 50, + /* 310 */ 90, 88, 89, 152, 113, 186, 96, 97, 98, 96, + /* 320 */ 97, 160, 57, 27, 101, 164, 137, 196, 139, 70, + /* 330 */ 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, + /* 340 */ 81, 82, 11, 84, 85, 86, 87, 88, 89, 90, + /* 350 */ 91, 92, 93, 94, 19, 132, 133, 134, 23, 218, + /* 360 */ 152, 96, 97, 98, 96, 97, 98, 230, 99, 22, + /* 370 */ 152, 102, 103, 104, 27, 244, 152, 152, 27, 26, + /* 380 */ 152, 22, 113, 65, 49, 50, 27, 194, 195, 58, + /* 390 */ 172, 173, 96, 97, 98, 185, 65, 172, 173, 206, + /* 400 */ 172, 173, 190, 191, 186, 70, 71, 72, 73, 74, + /* 410 */ 75, 76, 77, 78, 79, 80, 81, 82, 175, 84, + /* 420 */ 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, + /* 430 */ 19, 175, 207, 208, 23, 207, 208, 119, 120, 108, + /* 440 */ 109, 110, 27, 96, 97, 98, 116, 96, 97, 98, + /* 450 */ 152, 121, 152, 179, 180, 96, 97, 98, 250, 106, + /* 460 */ 49, 50, 188, 19, 221, 222, 223, 168, 169, 170, + /* 470 */ 172, 173, 172, 173, 250, 124, 172, 221, 222, 223, + /* 480 */ 26, 70, 71, 72, 73, 74, 75, 76, 77, 78, + /* 490 */ 79, 80, 81, 82, 50, 84, 85, 86, 87, 88, + /* 500 */ 89, 90, 91, 92, 93, 94, 19, 207, 208, 12, + /* 510 */ 23, 96, 97, 98, 221, 222, 223, 194, 195, 152, + /* 520 */ 199, 23, 19, 225, 26, 28, 152, 152, 152, 206, + /* 530 */ 209, 164, 190, 191, 241, 152, 49, 50, 152, 124, + /* 540 */ 152, 44, 219, 46, 152, 21, 172, 173, 172, 173, + /* 550 */ 183, 107, 185, 16, 163, 58, 112, 70, 71, 72, + /* 560 */ 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, + /* 570 */ 152, 84, 85, 86, 87, 88, 89, 90, 91, 92, + /* 580 */ 93, 94, 19, 207, 130, 152, 23, 196, 64, 152, + /* 590 */ 172, 173, 22, 152, 24, 152, 98, 27, 61, 96, + /* 600 */ 63, 26, 211, 212, 186, 172, 173, 49, 50, 172, + /* 610 */ 173, 23, 49, 50, 26, 172, 173, 88, 89, 186, + /* 620 */ 24, 238, 124, 27, 238, 22, 23, 103, 187, 26, + /* 630 */ 152, 73, 74, 70, 71, 72, 73, 74, 75, 76, + /* 640 */ 77, 78, 79, 80, 81, 82, 152, 84, 85, 86, + /* 650 */ 87, 88, 89, 90, 91, 92, 93, 94, 19, 101, + /* 660 */ 152, 132, 23, 134, 140, 152, 12, 97, 36, 168, + /* 670 */ 169, 170, 69, 98, 152, 22, 23, 140, 50, 26, + /* 680 */ 172, 173, 28, 51, 152, 172, 173, 193, 49, 50, + /* 690 */ 22, 59, 24, 97, 172, 173, 152, 152, 44, 124, + /* 700 */ 46, 0, 1, 2, 172, 173, 22, 23, 19, 70, + /* 710 */ 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, + /* 720 */ 81, 82, 69, 84, 85, 86, 87, 88, 89, 90, + /* 730 */ 91, 92, 93, 94, 152, 107, 152, 193, 49, 50, + /* 740 */ 181, 22, 23, 111, 108, 109, 110, 7, 8, 9, + /* 750 */ 16, 247, 248, 69, 172, 173, 172, 173, 152, 70, + /* 760 */ 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, + /* 770 */ 81, 82, 152, 84, 85, 86, 87, 88, 89, 90, + /* 780 */ 91, 92, 93, 94, 19, 152, 242, 152, 69, 152, + /* 790 */ 166, 167, 172, 173, 32, 61, 152, 63, 152, 193, + /* 800 */ 152, 152, 152, 41, 152, 172, 173, 172, 173, 172, + /* 810 */ 173, 152, 152, 152, 49, 50, 172, 173, 172, 173, + /* 820 */ 172, 173, 172, 173, 172, 173, 132, 138, 134, 152, + /* 830 */ 152, 172, 173, 172, 173, 70, 71, 72, 73, 74, + /* 840 */ 75, 76, 77, 78, 79, 80, 81, 82, 152, 84, + /* 850 */ 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, + /* 860 */ 19, 152, 22, 152, 195, 24, 152, 27, 172, 173, + /* 870 */ 193, 193, 152, 152, 152, 206, 152, 217, 152, 152, + /* 880 */ 152, 172, 173, 172, 173, 152, 172, 173, 152, 152, + /* 890 */ 49, 50, 172, 173, 172, 173, 172, 173, 172, 173, + /* 900 */ 172, 173, 152, 138, 152, 172, 173, 108, 109, 110, + /* 910 */ 19, 70, 71, 72, 73, 74, 75, 76, 77, 78, + /* 920 */ 79, 80, 81, 82, 152, 84, 85, 86, 87, 88, + /* 930 */ 89, 90, 91, 92, 93, 94, 152, 97, 152, 152, + /* 940 */ 49, 50, 26, 193, 172, 173, 152, 152, 152, 146, + /* 950 */ 147, 132, 152, 134, 217, 181, 172, 173, 172, 173, + /* 960 */ 19, 70, 71, 72, 73, 74, 75, 76, 77, 78, + /* 970 */ 79, 80, 81, 82, 152, 84, 85, 86, 87, 88, + /* 980 */ 89, 90, 91, 92, 93, 94, 152, 193, 152, 193, + /* 990 */ 49, 50, 181, 193, 172, 173, 166, 167, 245, 246, + /* 1000 */ 211, 212, 152, 22, 217, 152, 172, 173, 172, 173, + /* 1010 */ 19, 70, 71, 72, 73, 74, 75, 76, 77, 78, + /* 1020 */ 79, 80, 81, 82, 152, 84, 85, 86, 87, 88, + /* 1030 */ 89, 90, 91, 92, 93, 94, 152, 187, 152, 123, + /* 1040 */ 49, 50, 23, 23, 23, 26, 26, 26, 23, 23, + /* 1050 */ 23, 26, 26, 26, 7, 8, 172, 173, 172, 173, + /* 1060 */ 19, 90, 71, 72, 73, 74, 75, 76, 77, 78, + /* 1070 */ 79, 80, 81, 82, 152, 84, 85, 86, 87, 88, + /* 1080 */ 89, 90, 91, 92, 93, 94, 152, 116, 152, 217, + /* 1090 */ 49, 50, 121, 23, 172, 173, 26, 100, 101, 27, + /* 1100 */ 101, 27, 23, 122, 152, 26, 172, 173, 172, 173, + /* 1110 */ 152, 112, 163, 72, 73, 74, 75, 76, 77, 78, + /* 1120 */ 79, 80, 81, 82, 163, 84, 85, 86, 87, 88, + /* 1130 */ 89, 90, 91, 92, 93, 94, 19, 20, 152, 22, + /* 1140 */ 23, 152, 163, 65, 27, 196, 163, 19, 20, 23, + /* 1150 */ 22, 213, 26, 19, 37, 27, 152, 196, 172, 173, + /* 1160 */ 152, 172, 173, 27, 23, 37, 152, 26, 152, 97, + /* 1170 */ 152, 97, 210, 56, 163, 196, 163, 163, 100, 196, + /* 1180 */ 172, 173, 65, 152, 56, 68, 172, 173, 172, 173, + /* 1190 */ 172, 173, 152, 65, 163, 163, 68, 23, 152, 234, + /* 1200 */ 26, 152, 152, 172, 173, 88, 89, 196, 152, 196, + /* 1210 */ 196, 152, 95, 96, 97, 98, 88, 89, 101, 152, + /* 1220 */ 152, 207, 208, 95, 96, 97, 98, 196, 196, 101, + /* 1230 */ 96, 233, 152, 97, 152, 152, 19, 20, 207, 22, + /* 1240 */ 152, 152, 152, 191, 27, 152, 152, 152, 152, 132, + /* 1250 */ 133, 134, 135, 136, 37, 152, 152, 152, 152, 152, + /* 1260 */ 132, 133, 134, 135, 136, 210, 197, 210, 210, 198, + /* 1270 */ 150, 184, 239, 56, 201, 214, 214, 201, 239, 180, + /* 1280 */ 214, 227, 198, 38, 176, 68, 175, 175, 175, 122, + /* 1290 */ 155, 200, 159, 19, 20, 40, 22, 159, 159, 22, + /* 1300 */ 70, 27, 130, 243, 240, 88, 89, 90, 189, 18, + /* 1310 */ 201, 37, 95, 96, 97, 98, 192, 5, 101, 192, + /* 1320 */ 220, 240, 10, 11, 12, 13, 14, 159, 18, 17, + /* 1330 */ 56, 158, 192, 201, 192, 220, 189, 189, 201, 159, + /* 1340 */ 158, 137, 68, 31, 45, 33, 236, 159, 159, 132, + /* 1350 */ 133, 134, 135, 136, 42, 158, 235, 22, 177, 159, + /* 1360 */ 158, 158, 88, 89, 159, 107, 174, 55, 177, 95, + /* 1370 */ 96, 97, 98, 174, 62, 101, 47, 65, 66, 106, + /* 1380 */ 174, 125, 19, 20, 174, 22, 177, 176, 174, 182, + /* 1390 */ 27, 216, 174, 174, 182, 107, 159, 22, 215, 215, + /* 1400 */ 37, 216, 216, 216, 137, 215, 132, 133, 134, 135, + /* 1410 */ 136, 215, 159, 177, 94, 177, 129, 224, 205, 56, + /* 1420 */ 226, 126, 128, 203, 229, 204, 114, 229, 127, 202, + /* 1430 */ 201, 68, 25, 162, 26, 13, 161, 153, 153, 6, + /* 1440 */ 151, 151, 178, 151, 165, 165, 178, 165, 4, 3, + /* 1450 */ 249, 88, 89, 141, 22, 142, 15, 249, 95, 96, + /* 1460 */ 97, 98, 246, 67, 101, 16, 23, 120, 23, 131, + /* 1470 */ 111, 123, 20, 16, 125, 1, 123, 131, 78, 78, + /* 1480 */ 78, 78, 111, 96, 122, 35, 1, 5, 22, 107, + /* 1490 */ 140, 53, 53, 26, 60, 132, 133, 134, 135, 136, + /* 1500 */ 107, 43, 24, 20, 112, 19, 22, 52, 52, 105, + /* 1510 */ 22, 22, 52, 23, 22, 22, 29, 23, 39, 23, + /* 1520 */ 23, 26, 116, 22, 26, 23, 22, 122, 23, 23, + /* 1530 */ 22, 96, 11, 124, 35, 26, 26, 23, 35, 23, + /* 1540 */ 23, 23, 35, 23, 22, 26, 23, 22, 24, 122, + /* 1550 */ 23, 22, 26, 22, 24, 23, 23, 23, 22, 122, + /* 1560 */ 23, 15, 122, 1, 122, }; -#define YY_SHIFT_USE_DFLT (1567) -#define YY_SHIFT_COUNT (455) -#define YY_SHIFT_MIN (-94) -#define YY_SHIFT_MAX (1549) +#define YY_SHIFT_USE_DFLT (1565) +#define YY_SHIFT_COUNT (454) +#define YY_SHIFT_MIN (-114) +#define YY_SHIFT_MAX (1562) static const short yy_shift_ofst[] = { - /* 0 */ 40, 599, 904, 612, 760, 760, 760, 760, 725, -19, - /* 10 */ 16, 16, 100, 760, 760, 760, 760, 760, 760, 760, - /* 20 */ 876, 876, 573, 542, 719, 600, 61, 137, 172, 207, - /* 30 */ 242, 277, 312, 347, 382, 417, 459, 459, 459, 459, - /* 40 */ 459, 459, 459, 459, 459, 459, 459, 459, 459, 459, - /* 50 */ 459, 459, 459, 494, 459, 529, 564, 564, 705, 760, - /* 60 */ 760, 760, 760, 760, 760, 760, 760, 760, 760, 760, - /* 70 */ 760, 760, 760, 760, 760, 760, 760, 760, 760, 760, - /* 80 */ 760, 760, 760, 760, 760, 760, 760, 760, 760, 760, - /* 90 */ 856, 760, 760, 760, 760, 760, 760, 760, 760, 760, - /* 100 */ 760, 760, 760, 760, 987, 746, 746, 746, 746, 746, - /* 110 */ 801, 23, 32, 949, 961, 979, 964, 964, 949, 73, - /* 120 */ 113, -51, 1567, 1567, 1567, 536, 536, 536, 99, 99, - /* 130 */ 813, 813, 667, 205, 240, 949, 949, 949, 949, 949, - /* 140 */ 949, 949, 949, 949, 949, 949, 949, 949, 949, 949, - /* 150 */ 949, 949, 949, 949, 949, 332, 1011, 422, 422, 113, - /* 160 */ 30, 30, 30, 30, 30, 30, 1567, 1567, 1567, 922, - /* 170 */ -94, -94, 384, 613, 828, 420, 765, 804, 851, 949, - /* 180 */ 949, 949, 949, 949, 949, 949, 949, 949, 949, 949, - /* 190 */ 949, 949, 949, 949, 949, 672, 672, 672, 949, 949, - /* 200 */ 657, 949, 949, 949, -18, 949, 949, 994, 949, 949, - /* 210 */ 949, 949, 949, 949, 949, 949, 949, 949, 772, 1118, - /* 220 */ 712, 712, 712, 810, 45, 769, 1219, 1133, 418, 418, - /* 230 */ 569, 1133, 569, 830, 607, 663, 882, 418, 693, 882, - /* 240 */ 882, 848, 1152, 1065, 1286, 1238, 1238, 1287, 1287, 1238, - /* 250 */ 1344, 1341, 1239, 1353, 1353, 1353, 1353, 1238, 1355, 1239, - /* 260 */ 1344, 1341, 1341, 1239, 1238, 1355, 1243, 1312, 1238, 1238, - /* 270 */ 1355, 1370, 1238, 1355, 1238, 1355, 1370, 1290, 1290, 1290, - /* 280 */ 1327, 1370, 1290, 1301, 1290, 1327, 1290, 1290, 1284, 1304, - /* 290 */ 1284, 1304, 1284, 1304, 1284, 1304, 1238, 1391, 1238, 1280, - /* 300 */ 1370, 1366, 1366, 1370, 1302, 1308, 1310, 1309, 1239, 1414, - /* 310 */ 1416, 1431, 1431, 1440, 1440, 1440, 1440, 1567, 1567, 1567, - /* 320 */ 1567, 1567, 1567, 1567, 1567, 519, 978, 1210, 1225, 104, - /* 330 */ 1141, 1189, 1246, 1248, 1251, 1252, 1253, 1257, 1258, 1273, - /* 340 */ 1003, 1187, 1293, 1170, 1272, 1279, 1234, 1281, 1176, 1177, - /* 350 */ 1289, 1242, 1195, 1453, 1455, 1437, 1319, 1447, 1369, 1452, - /* 360 */ 1446, 1448, 1352, 1345, 1364, 1354, 1458, 1356, 1463, 1479, - /* 370 */ 1359, 1357, 1449, 1450, 1454, 1456, 1372, 1428, 1421, 1367, - /* 380 */ 1489, 1487, 1472, 1388, 1358, 1417, 1470, 1419, 1413, 1429, - /* 390 */ 1395, 1480, 1483, 1486, 1394, 1402, 1488, 1430, 1490, 1491, - /* 400 */ 1485, 1492, 1432, 1457, 1494, 1438, 1451, 1495, 1497, 1498, - /* 410 */ 1496, 1407, 1502, 1503, 1505, 1499, 1406, 1506, 1507, 1475, - /* 420 */ 1468, 1511, 1410, 1509, 1473, 1510, 1474, 1516, 1509, 1517, - /* 430 */ 1518, 1519, 1520, 1521, 1523, 1532, 1524, 1526, 1525, 1527, - /* 440 */ 1528, 1530, 1531, 1527, 1533, 1535, 1536, 1537, 1539, 1436, - /* 450 */ 1441, 1442, 1443, 1543, 1547, 1549, + /* 0 */ 5, 1117, 1312, 1128, 1274, 1274, 1274, 1274, 61, -19, + /* 10 */ 57, 57, 183, 1274, 1274, 1274, 1274, 1274, 1274, 1274, + /* 20 */ 66, 66, 201, -29, 331, 318, 133, 259, 335, 411, + /* 30 */ 487, 563, 639, 689, 765, 841, 891, 891, 891, 891, + /* 40 */ 891, 891, 891, 891, 891, 891, 891, 891, 891, 891, + /* 50 */ 891, 891, 891, 941, 891, 991, 1041, 1041, 1217, 1274, + /* 60 */ 1274, 1274, 1274, 1274, 1274, 1274, 1274, 1274, 1274, 1274, + /* 70 */ 1274, 1274, 1274, 1274, 1274, 1274, 1274, 1274, 1274, 1274, + /* 80 */ 1274, 1274, 1274, 1274, 1274, 1274, 1274, 1274, 1274, 1274, + /* 90 */ 1363, 1274, 1274, 1274, 1274, 1274, 1274, 1274, 1274, 1274, + /* 100 */ 1274, 1274, 1274, 1274, -70, -47, -47, -47, -47, -47, + /* 110 */ 24, 11, 146, 296, 524, 444, 529, 529, 296, 3, + /* 120 */ 2, -30, 1565, 1565, 1565, -17, -17, -17, 145, 145, + /* 130 */ 497, 497, 265, 603, 653, 296, 296, 296, 296, 296, + /* 140 */ 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, + /* 150 */ 296, 296, 296, 296, 296, 701, 1078, 147, 147, 2, + /* 160 */ 164, 164, 164, 164, 164, 164, 1565, 1565, 1565, 223, + /* 170 */ 56, 56, 268, 269, 220, 347, 351, 415, 359, 296, + /* 180 */ 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, + /* 190 */ 296, 296, 296, 296, 296, 632, 632, 632, 296, 296, + /* 200 */ 498, 296, 296, 296, 570, 296, 296, 654, 296, 296, + /* 210 */ 296, 296, 296, 296, 296, 296, 296, 296, 636, 200, + /* 220 */ 596, 596, 596, 575, -114, 971, 740, 454, 503, 503, + /* 230 */ 1134, 454, 1134, 353, 588, 628, 762, 503, 189, 762, + /* 240 */ 762, 916, 330, 668, 1245, 1167, 1167, 1255, 1255, 1167, + /* 250 */ 1277, 1230, 1172, 1291, 1291, 1291, 1291, 1167, 1310, 1172, + /* 260 */ 1277, 1230, 1230, 1172, 1167, 1310, 1204, 1299, 1167, 1167, + /* 270 */ 1310, 1335, 1167, 1310, 1167, 1310, 1335, 1258, 1258, 1258, + /* 280 */ 1329, 1335, 1258, 1273, 1258, 1329, 1258, 1258, 1256, 1288, + /* 290 */ 1256, 1288, 1256, 1288, 1256, 1288, 1167, 1375, 1167, 1267, + /* 300 */ 1335, 1320, 1320, 1335, 1287, 1295, 1294, 1301, 1172, 1407, + /* 310 */ 1408, 1422, 1422, 1433, 1433, 1433, 1565, 1565, 1565, 1565, + /* 320 */ 1565, 1565, 1565, 1565, 558, 537, 684, 719, 734, 799, + /* 330 */ 840, 1019, 14, 1020, 1021, 1025, 1026, 1027, 1070, 1072, + /* 340 */ 997, 1047, 999, 1079, 1126, 1074, 1141, 694, 819, 1174, + /* 350 */ 1136, 981, 1444, 1446, 1432, 1313, 1441, 1396, 1449, 1443, + /* 360 */ 1445, 1347, 1338, 1359, 1348, 1452, 1349, 1457, 1474, 1353, + /* 370 */ 1346, 1400, 1401, 1402, 1403, 1371, 1387, 1450, 1362, 1485, + /* 380 */ 1482, 1466, 1382, 1350, 1438, 1467, 1439, 1434, 1458, 1393, + /* 390 */ 1478, 1483, 1486, 1392, 1404, 1484, 1455, 1488, 1489, 1490, + /* 400 */ 1492, 1456, 1487, 1493, 1460, 1479, 1494, 1496, 1497, 1495, + /* 410 */ 1406, 1501, 1502, 1504, 1498, 1405, 1505, 1506, 1435, 1499, + /* 420 */ 1508, 1409, 1509, 1503, 1510, 1507, 1514, 1509, 1516, 1517, + /* 430 */ 1518, 1519, 1520, 1522, 1521, 1523, 1525, 1524, 1526, 1527, + /* 440 */ 1529, 1530, 1526, 1532, 1531, 1533, 1534, 1536, 1427, 1437, + /* 450 */ 1440, 1442, 1537, 1546, 1562, }; -#define YY_REDUCE_USE_DFLT (-130) -#define YY_REDUCE_COUNT (324) -#define YY_REDUCE_MIN (-129) -#define YY_REDUCE_MAX (1300) +#define YY_REDUCE_USE_DFLT (-174) +#define YY_REDUCE_COUNT (323) +#define YY_REDUCE_MIN (-173) +#define YY_REDUCE_MAX (1292) static const short yy_reduce_ofst[] = { - /* 0 */ -29, 566, 525, 605, -49, 307, 491, 533, 668, 435, - /* 10 */ 601, 644, 148, 747, 786, 795, 419, 788, 827, 790, - /* 20 */ 454, 832, 889, 495, 824, 734, 76, 76, 76, 76, - /* 30 */ 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, - /* 40 */ 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, - /* 50 */ 76, 76, 76, 76, 76, 76, 76, 76, 783, 898, - /* 60 */ 905, 907, 911, 921, 933, 936, 940, 943, 947, 950, - /* 70 */ 952, 955, 958, 962, 965, 969, 974, 977, 980, 984, - /* 80 */ 988, 991, 993, 996, 999, 1002, 1006, 1010, 1018, 1021, - /* 90 */ 1024, 1028, 1032, 1034, 1036, 1040, 1046, 1051, 1058, 1062, - /* 100 */ 1064, 1068, 1070, 1073, 76, 76, 76, 76, 76, 76, - /* 110 */ 76, 76, 76, 855, 36, 523, 235, 416, 777, 76, - /* 120 */ 278, 76, 76, 76, 76, 700, 700, 700, 150, 220, - /* 130 */ 147, 217, 221, 306, 306, 611, 5, 535, 556, 620, - /* 140 */ 720, 872, 897, 116, 864, 349, 1035, 1037, 404, 1047, - /* 150 */ 992, -129, 1050, 492, 62, 722, 879, 1072, 1089, 808, - /* 160 */ 1066, 1094, 1095, 1096, 1097, 1098, 776, 1054, 557, 57, - /* 170 */ 112, 131, 167, 182, 250, 272, 291, 331, 364, 438, - /* 180 */ 497, 517, 591, 653, 690, 739, 775, 798, 892, 908, - /* 190 */ 924, 930, 1015, 1063, 1069, 355, 784, 799, 981, 1101, - /* 200 */ 926, 1151, 1161, 1162, 945, 1164, 1166, 1128, 1168, 1171, - /* 210 */ 1172, 250, 1173, 1174, 1175, 1178, 1180, 1181, 1088, 1102, - /* 220 */ 1119, 1124, 1126, 926, 1131, 1139, 1188, 1140, 1129, 1130, - /* 230 */ 1103, 1144, 1107, 1179, 1156, 1167, 1182, 1134, 1122, 1183, - /* 240 */ 1184, 1150, 1153, 1197, 1111, 1202, 1203, 1123, 1125, 1205, - /* 250 */ 1147, 1185, 1169, 1186, 1190, 1191, 1192, 1213, 1217, 1193, - /* 260 */ 1157, 1196, 1198, 1194, 1220, 1218, 1145, 1154, 1229, 1231, - /* 270 */ 1233, 1216, 1237, 1240, 1241, 1244, 1222, 1227, 1230, 1232, - /* 280 */ 1223, 1235, 1236, 1245, 1249, 1226, 1250, 1254, 1199, 1201, - /* 290 */ 1204, 1207, 1209, 1211, 1214, 1212, 1255, 1208, 1259, 1215, - /* 300 */ 1256, 1200, 1206, 1260, 1247, 1261, 1263, 1262, 1266, 1278, - /* 310 */ 1282, 1292, 1294, 1297, 1298, 1299, 1300, 1221, 1224, 1228, - /* 320 */ 1288, 1291, 1276, 1277, 1295, + /* 0 */ -119, 1014, 131, 1031, -12, 225, 228, 300, -40, -45, + /* 10 */ 243, 256, 293, 129, 218, 418, 79, 376, 433, 298, + /* 20 */ 16, 137, 367, 323, -38, 391, -173, -173, -173, -173, + /* 30 */ -173, -173, -173, -173, -173, -173, -173, -173, -173, -173, + /* 40 */ -173, -173, -173, -173, -173, -173, -173, -173, -173, -173, + /* 50 */ -173, -173, -173, -173, -173, -173, -173, -173, 374, 437, + /* 60 */ 443, 508, 513, 522, 532, 582, 584, 620, 633, 635, + /* 70 */ 637, 644, 646, 648, 650, 652, 659, 661, 696, 709, + /* 80 */ 711, 714, 720, 722, 724, 726, 728, 733, 772, 784, + /* 90 */ 786, 822, 834, 836, 884, 886, 922, 934, 936, 986, + /* 100 */ 989, 1008, 1016, 1018, -173, -173, -173, -173, -173, -173, + /* 110 */ -173, -173, -173, 544, -37, 274, 299, 501, 161, -173, + /* 120 */ 193, -173, -173, -173, -173, 22, 22, 22, 64, 141, + /* 130 */ 212, 342, 208, 504, 504, 132, 494, 606, 677, 678, + /* 140 */ 750, 794, 796, -58, 32, 383, 660, 737, 386, 787, + /* 150 */ 800, 441, 872, 224, 850, 803, 949, 624, 830, 669, + /* 160 */ 961, 979, 983, 1011, 1013, 1032, 753, 789, 321, 94, + /* 170 */ 116, 304, 375, 210, 388, 392, 478, 545, 649, 721, + /* 180 */ 727, 736, 752, 795, 853, 952, 958, 1004, 1040, 1046, + /* 190 */ 1049, 1050, 1056, 1059, 1067, 559, 774, 811, 1068, 1080, + /* 200 */ 938, 1082, 1083, 1088, 962, 1089, 1090, 1052, 1093, 1094, + /* 210 */ 1095, 388, 1096, 1103, 1104, 1105, 1106, 1107, 965, 998, + /* 220 */ 1055, 1057, 1058, 938, 1069, 1071, 1120, 1073, 1061, 1062, + /* 230 */ 1033, 1076, 1039, 1108, 1087, 1099, 1111, 1066, 1054, 1112, + /* 240 */ 1113, 1091, 1084, 1135, 1060, 1133, 1138, 1064, 1081, 1139, + /* 250 */ 1100, 1119, 1109, 1124, 1127, 1140, 1142, 1168, 1173, 1132, + /* 260 */ 1115, 1147, 1148, 1137, 1180, 1182, 1110, 1121, 1188, 1189, + /* 270 */ 1197, 1181, 1200, 1202, 1205, 1203, 1191, 1192, 1199, 1206, + /* 280 */ 1207, 1209, 1210, 1211, 1214, 1212, 1218, 1219, 1175, 1183, + /* 290 */ 1185, 1184, 1186, 1190, 1187, 1196, 1237, 1193, 1253, 1194, + /* 300 */ 1236, 1195, 1198, 1238, 1213, 1221, 1220, 1227, 1229, 1271, + /* 310 */ 1275, 1284, 1285, 1289, 1290, 1292, 1201, 1208, 1216, 1279, + /* 320 */ 1280, 1264, 1268, 1282, }; static const YYACTIONTYPE yy_default[] = { - /* 0 */ 1280, 1270, 1270, 1270, 1202, 1202, 1202, 1202, 1270, 1096, - /* 10 */ 1125, 1125, 1254, 1332, 1332, 1332, 1332, 1332, 1332, 1201, - /* 20 */ 1332, 1332, 1332, 1332, 1270, 1100, 1131, 1332, 1332, 1332, - /* 30 */ 1332, 1203, 1204, 1332, 1332, 1332, 1253, 1255, 1141, 1140, - /* 40 */ 1139, 1138, 1236, 1112, 1136, 1129, 1133, 1203, 1197, 1198, - /* 50 */ 1196, 1200, 1204, 1332, 1132, 1167, 1181, 1166, 1332, 1332, - /* 60 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, - /* 70 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, - /* 80 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, - /* 90 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, - /* 100 */ 1332, 1332, 1332, 1332, 1175, 1180, 1187, 1179, 1176, 1169, - /* 110 */ 1168, 1170, 1171, 1332, 1019, 1067, 1332, 1332, 1332, 1172, - /* 120 */ 1332, 1173, 1184, 1183, 1182, 1261, 1288, 1287, 1332, 1332, - /* 130 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, - /* 140 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, - /* 150 */ 1332, 1332, 1332, 1332, 1332, 1280, 1270, 1025, 1025, 1332, - /* 160 */ 1270, 1270, 1270, 1270, 1270, 1270, 1266, 1100, 1091, 1332, - /* 170 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, - /* 180 */ 1258, 1256, 1332, 1217, 1332, 1332, 1332, 1332, 1332, 1332, - /* 190 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, - /* 200 */ 1332, 1332, 1332, 1332, 1096, 1332, 1332, 1332, 1332, 1332, - /* 210 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1282, 1332, 1231, - /* 220 */ 1096, 1096, 1096, 1098, 1080, 1090, 1004, 1135, 1114, 1114, - /* 230 */ 1321, 1135, 1321, 1042, 1302, 1039, 1125, 1114, 1199, 1125, - /* 240 */ 1125, 1097, 1090, 1332, 1324, 1105, 1105, 1323, 1323, 1105, - /* 250 */ 1146, 1070, 1135, 1076, 1076, 1076, 1076, 1105, 1016, 1135, - /* 260 */ 1146, 1070, 1070, 1135, 1105, 1016, 1235, 1318, 1105, 1105, - /* 270 */ 1016, 1210, 1105, 1016, 1105, 1016, 1210, 1068, 1068, 1068, - /* 280 */ 1057, 1210, 1068, 1042, 1068, 1057, 1068, 1068, 1118, 1113, - /* 290 */ 1118, 1113, 1118, 1113, 1118, 1113, 1105, 1205, 1105, 1332, - /* 300 */ 1210, 1214, 1214, 1210, 1130, 1119, 1128, 1126, 1135, 1022, - /* 310 */ 1060, 1285, 1285, 1281, 1281, 1281, 1281, 1329, 1329, 1266, - /* 320 */ 1297, 1297, 1044, 1044, 1297, 1332, 1332, 1332, 1332, 1332, - /* 330 */ 1332, 1292, 1332, 1219, 1332, 1332, 1332, 1332, 1332, 1332, - /* 340 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, - /* 350 */ 1332, 1332, 1152, 1332, 1000, 1263, 1332, 1332, 1262, 1332, - /* 360 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, - /* 370 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1320, - /* 380 */ 1332, 1332, 1332, 1332, 1332, 1332, 1234, 1233, 1332, 1332, - /* 390 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, - /* 400 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, - /* 410 */ 1332, 1082, 1332, 1332, 1332, 1306, 1332, 1332, 1332, 1332, - /* 420 */ 1332, 1332, 1332, 1127, 1332, 1120, 1332, 1332, 1311, 1332, - /* 430 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1272, - /* 440 */ 1332, 1332, 1332, 1271, 1332, 1332, 1332, 1332, 1332, 1154, - /* 450 */ 1332, 1153, 1157, 1332, 1010, 1332, + /* 0 */ 1270, 1260, 1260, 1260, 1193, 1193, 1193, 1193, 1260, 1088, + /* 10 */ 1117, 1117, 1244, 1322, 1322, 1322, 1322, 1322, 1322, 1192, + /* 20 */ 1322, 1322, 1322, 1322, 1260, 1092, 1123, 1322, 1322, 1322, + /* 30 */ 1322, 1194, 1195, 1322, 1322, 1322, 1243, 1245, 1133, 1132, + /* 40 */ 1131, 1130, 1226, 1104, 1128, 1121, 1125, 1194, 1188, 1189, + /* 50 */ 1187, 1191, 1195, 1322, 1124, 1158, 1172, 1157, 1322, 1322, + /* 60 */ 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, + /* 70 */ 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, + /* 80 */ 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, + /* 90 */ 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, + /* 100 */ 1322, 1322, 1322, 1322, 1166, 1171, 1178, 1170, 1167, 1160, + /* 110 */ 1159, 1161, 1162, 1322, 1011, 1059, 1322, 1322, 1322, 1163, + /* 120 */ 1322, 1164, 1175, 1174, 1173, 1251, 1278, 1277, 1322, 1322, + /* 130 */ 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, + /* 140 */ 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, + /* 150 */ 1322, 1322, 1322, 1322, 1322, 1270, 1260, 1017, 1017, 1322, + /* 160 */ 1260, 1260, 1260, 1260, 1260, 1260, 1256, 1092, 1083, 1322, + /* 170 */ 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, + /* 180 */ 1248, 1246, 1322, 1208, 1322, 1322, 1322, 1322, 1322, 1322, + /* 190 */ 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, + /* 200 */ 1322, 1322, 1322, 1322, 1088, 1322, 1322, 1322, 1322, 1322, + /* 210 */ 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1272, 1322, 1221, + /* 220 */ 1088, 1088, 1088, 1090, 1072, 1082, 997, 1127, 1106, 1106, + /* 230 */ 1311, 1127, 1311, 1034, 1292, 1031, 1117, 1106, 1190, 1117, + /* 240 */ 1117, 1089, 1082, 1322, 1314, 1097, 1097, 1313, 1313, 1097, + /* 250 */ 1138, 1062, 1127, 1068, 1068, 1068, 1068, 1097, 1008, 1127, + /* 260 */ 1138, 1062, 1062, 1127, 1097, 1008, 1225, 1308, 1097, 1097, + /* 270 */ 1008, 1201, 1097, 1008, 1097, 1008, 1201, 1060, 1060, 1060, + /* 280 */ 1049, 1201, 1060, 1034, 1060, 1049, 1060, 1060, 1110, 1105, + /* 290 */ 1110, 1105, 1110, 1105, 1110, 1105, 1097, 1196, 1097, 1322, + /* 300 */ 1201, 1205, 1205, 1201, 1122, 1111, 1120, 1118, 1127, 1014, + /* 310 */ 1052, 1275, 1275, 1271, 1271, 1271, 1319, 1319, 1256, 1287, + /* 320 */ 1287, 1036, 1036, 1287, 1322, 1322, 1322, 1322, 1322, 1322, + /* 330 */ 1282, 1322, 1210, 1322, 1322, 1322, 1322, 1322, 1322, 1322, + /* 340 */ 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, + /* 350 */ 1322, 1143, 1322, 993, 1253, 1322, 1322, 1252, 1322, 1322, + /* 360 */ 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, + /* 370 */ 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1310, 1322, + /* 380 */ 1322, 1322, 1322, 1322, 1322, 1224, 1223, 1322, 1322, 1322, + /* 390 */ 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, + /* 400 */ 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, + /* 410 */ 1074, 1322, 1322, 1322, 1296, 1322, 1322, 1322, 1322, 1322, + /* 420 */ 1322, 1322, 1119, 1322, 1112, 1322, 1322, 1301, 1322, 1322, + /* 430 */ 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1322, 1262, 1322, + /* 440 */ 1322, 1322, 1261, 1322, 1322, 1322, 1322, 1322, 1145, 1322, + /* 450 */ 1144, 1148, 1322, 1002, 1322, }; /********** End of lemon-generated parsing tables *****************************/ @@ -135702,100 +137415,73 @@ static const YYACTIONTYPE yy_default[] = { static const YYCODETYPE yyFallback[] = { 0, /* $ => nothing */ 0, /* SEMI => nothing */ - 55, /* EXPLAIN => ID */ - 55, /* QUERY => ID */ - 55, /* PLAN => ID */ - 55, /* BEGIN => ID */ + 27, /* EXPLAIN => ID */ + 27, /* QUERY => ID */ + 27, /* PLAN => ID */ + 27, /* BEGIN => ID */ 0, /* TRANSACTION => nothing */ - 55, /* DEFERRED => ID */ - 55, /* IMMEDIATE => ID */ - 55, /* EXCLUSIVE => ID */ + 27, /* DEFERRED => ID */ + 27, /* IMMEDIATE => ID */ + 27, /* EXCLUSIVE => ID */ 0, /* COMMIT => nothing */ - 55, /* END => ID */ - 55, /* ROLLBACK => ID */ - 55, /* SAVEPOINT => ID */ - 55, /* RELEASE => ID */ + 27, /* END => ID */ + 27, /* ROLLBACK => ID */ + 27, /* SAVEPOINT => ID */ + 27, /* RELEASE => ID */ 0, /* TO => nothing */ 0, /* TABLE => nothing */ 0, /* CREATE => nothing */ - 55, /* IF => ID */ + 27, /* IF => ID */ 0, /* NOT => nothing */ 0, /* EXISTS => nothing */ - 55, /* TEMP => ID */ + 27, /* TEMP => ID */ 0, /* LP => nothing */ 0, /* RP => nothing */ 0, /* AS => nothing */ - 55, /* WITHOUT => ID */ + 27, /* WITHOUT => ID */ 0, /* COMMA => nothing */ - 0, /* OR => nothing */ - 0, /* AND => nothing */ - 0, /* IS => nothing */ - 55, /* MATCH => ID */ - 55, /* LIKE_KW => ID */ - 0, /* BETWEEN => nothing */ - 0, /* IN => nothing */ - 0, /* ISNULL => nothing */ - 0, /* NOTNULL => nothing */ - 0, /* NE => nothing */ - 0, /* EQ => nothing */ - 0, /* GT => nothing */ - 0, /* LE => nothing */ - 0, /* LT => nothing */ - 0, /* GE => nothing */ - 0, /* ESCAPE => nothing */ - 0, /* BITAND => nothing */ - 0, /* BITOR => nothing */ - 0, /* LSHIFT => nothing */ - 0, /* RSHIFT => nothing */ - 0, /* PLUS => nothing */ - 0, /* MINUS => nothing */ - 0, /* STAR => nothing */ - 0, /* SLASH => nothing */ - 0, /* REM => nothing */ - 0, /* CONCAT => nothing */ - 0, /* COLLATE => nothing */ - 0, /* BITNOT => nothing */ 0, /* ID => nothing */ - 0, /* INDEXED => nothing */ - 55, /* ABORT => ID */ - 55, /* ACTION => ID */ - 55, /* AFTER => ID */ - 55, /* ANALYZE => ID */ - 55, /* ASC => ID */ - 55, /* ATTACH => ID */ - 55, /* BEFORE => ID */ - 55, /* BY => ID */ - 55, /* CASCADE => ID */ - 55, /* CAST => ID */ - 55, /* COLUMNKW => ID */ - 55, /* CONFLICT => ID */ - 55, /* DATABASE => ID */ - 55, /* DESC => ID */ - 55, /* DETACH => ID */ - 55, /* EACH => ID */ - 55, /* FAIL => ID */ - 55, /* FOR => ID */ - 55, /* IGNORE => ID */ - 55, /* INITIALLY => ID */ - 55, /* INSTEAD => ID */ - 55, /* NO => ID */ - 55, /* KEY => ID */ - 55, /* OF => ID */ - 55, /* OFFSET => ID */ - 55, /* PRAGMA => ID */ - 55, /* RAISE => ID */ - 55, /* RECURSIVE => ID */ - 55, /* REPLACE => ID */ - 55, /* RESTRICT => ID */ - 55, /* ROW => ID */ - 55, /* TRIGGER => ID */ - 55, /* VACUUM => ID */ - 55, /* VIEW => ID */ - 55, /* VIRTUAL => ID */ - 55, /* WITH => ID */ - 55, /* REINDEX => ID */ - 55, /* RENAME => ID */ - 55, /* CTIME_KW => ID */ + 27, /* ABORT => ID */ + 27, /* ACTION => ID */ + 27, /* AFTER => ID */ + 27, /* ANALYZE => ID */ + 27, /* ASC => ID */ + 27, /* ATTACH => ID */ + 27, /* BEFORE => ID */ + 27, /* BY => ID */ + 27, /* CASCADE => ID */ + 27, /* CAST => ID */ + 27, /* COLUMNKW => ID */ + 27, /* CONFLICT => ID */ + 27, /* DATABASE => ID */ + 27, /* DESC => ID */ + 27, /* DETACH => ID */ + 27, /* EACH => ID */ + 27, /* FAIL => ID */ + 27, /* FOR => ID */ + 27, /* IGNORE => ID */ + 27, /* INITIALLY => ID */ + 27, /* INSTEAD => ID */ + 27, /* LIKE_KW => ID */ + 27, /* MATCH => ID */ + 27, /* NO => ID */ + 27, /* KEY => ID */ + 27, /* OF => ID */ + 27, /* OFFSET => ID */ + 27, /* PRAGMA => ID */ + 27, /* RAISE => ID */ + 27, /* RECURSIVE => ID */ + 27, /* REPLACE => ID */ + 27, /* RESTRICT => ID */ + 27, /* ROW => ID */ + 27, /* TRIGGER => ID */ + 27, /* VACUUM => ID */ + 27, /* VIEW => ID */ + 27, /* VIRTUAL => ID */ + 27, /* WITH => ID */ + 27, /* REINDEX => ID */ + 27, /* RENAME => ID */ + 27, /* CTIME_KW => ID */ }; #endif /* YYFALLBACK */ @@ -135841,6 +137527,7 @@ struct yyParser { yyStackEntry yystk0; /* First stack entry */ #else yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */ + yyStackEntry *yystackEnd; /* Last entry in the stack */ #endif }; typedef struct yyParser yyParser; @@ -135887,25 +137574,25 @@ static const char *const yyTokenName[] = { "ROLLBACK", "SAVEPOINT", "RELEASE", "TO", "TABLE", "CREATE", "IF", "NOT", "EXISTS", "TEMP", "LP", "RP", - "AS", "WITHOUT", "COMMA", "OR", - "AND", "IS", "MATCH", "LIKE_KW", - "BETWEEN", "IN", "ISNULL", "NOTNULL", - "NE", "EQ", "GT", "LE", - "LT", "GE", "ESCAPE", "BITAND", - "BITOR", "LSHIFT", "RSHIFT", "PLUS", - "MINUS", "STAR", "SLASH", "REM", - "CONCAT", "COLLATE", "BITNOT", "ID", - "INDEXED", "ABORT", "ACTION", "AFTER", - "ANALYZE", "ASC", "ATTACH", "BEFORE", - "BY", "CASCADE", "CAST", "COLUMNKW", - "CONFLICT", "DATABASE", "DESC", "DETACH", - "EACH", "FAIL", "FOR", "IGNORE", - "INITIALLY", "INSTEAD", "NO", "KEY", - "OF", "OFFSET", "PRAGMA", "RAISE", - "RECURSIVE", "REPLACE", "RESTRICT", "ROW", - "TRIGGER", "VACUUM", "VIEW", "VIRTUAL", - "WITH", "REINDEX", "RENAME", "CTIME_KW", - "ANY", "STRING", "JOIN_KW", "CONSTRAINT", + "AS", "WITHOUT", "COMMA", "ID", + "ABORT", "ACTION", "AFTER", "ANALYZE", + "ASC", "ATTACH", "BEFORE", "BY", + "CASCADE", "CAST", "COLUMNKW", "CONFLICT", + "DATABASE", "DESC", "DETACH", "EACH", + "FAIL", "FOR", "IGNORE", "INITIALLY", + "INSTEAD", "LIKE_KW", "MATCH", "NO", + "KEY", "OF", "OFFSET", "PRAGMA", + "RAISE", "RECURSIVE", "REPLACE", "RESTRICT", + "ROW", "TRIGGER", "VACUUM", "VIEW", + "VIRTUAL", "WITH", "REINDEX", "RENAME", + "CTIME_KW", "ANY", "OR", "AND", + "IS", "BETWEEN", "IN", "ISNULL", + "NOTNULL", "NE", "EQ", "GT", + "LE", "LT", "GE", "ESCAPE", + "BITAND", "BITOR", "LSHIFT", "RSHIFT", + "PLUS", "MINUS", "STAR", "SLASH", + "REM", "CONCAT", "COLLATE", "BITNOT", + "INDEXED", "STRING", "JOIN_KW", "CONSTRAINT", "DEFAULT", "NULL", "PRIMARY", "UNIQUE", "CHECK", "REFERENCES", "AUTOINCR", "ON", "INSERT", "DELETE", "UPDATE", "SET", @@ -135959,330 +137646,327 @@ static const char *const yyRuleName[] = { /* 5 */ "transtype ::= DEFERRED", /* 6 */ "transtype ::= IMMEDIATE", /* 7 */ "transtype ::= EXCLUSIVE", - /* 8 */ "cmd ::= COMMIT trans_opt", - /* 9 */ "cmd ::= END trans_opt", - /* 10 */ "cmd ::= ROLLBACK trans_opt", - /* 11 */ "cmd ::= SAVEPOINT nm", - /* 12 */ "cmd ::= RELEASE savepoint_opt nm", - /* 13 */ "cmd ::= ROLLBACK trans_opt TO savepoint_opt nm", - /* 14 */ "create_table ::= createkw temp TABLE ifnotexists nm dbnm", - /* 15 */ "createkw ::= CREATE", - /* 16 */ "ifnotexists ::=", - /* 17 */ "ifnotexists ::= IF NOT EXISTS", - /* 18 */ "temp ::= TEMP", - /* 19 */ "temp ::=", - /* 20 */ "create_table_args ::= LP columnlist conslist_opt RP table_options", - /* 21 */ "create_table_args ::= AS select", - /* 22 */ "table_options ::=", - /* 23 */ "table_options ::= WITHOUT nm", - /* 24 */ "columnname ::= nm typetoken", - /* 25 */ "typetoken ::=", - /* 26 */ "typetoken ::= typename LP signed RP", - /* 27 */ "typetoken ::= typename LP signed COMMA signed RP", - /* 28 */ "typename ::= typename ID|STRING", - /* 29 */ "ccons ::= CONSTRAINT nm", - /* 30 */ "ccons ::= DEFAULT term", - /* 31 */ "ccons ::= DEFAULT LP expr RP", - /* 32 */ "ccons ::= DEFAULT PLUS term", - /* 33 */ "ccons ::= DEFAULT MINUS term", - /* 34 */ "ccons ::= DEFAULT ID|INDEXED", - /* 35 */ "ccons ::= NOT NULL onconf", - /* 36 */ "ccons ::= PRIMARY KEY sortorder onconf autoinc", - /* 37 */ "ccons ::= UNIQUE onconf", - /* 38 */ "ccons ::= CHECK LP expr RP", - /* 39 */ "ccons ::= REFERENCES nm eidlist_opt refargs", - /* 40 */ "ccons ::= defer_subclause", - /* 41 */ "ccons ::= COLLATE ID|STRING", - /* 42 */ "autoinc ::=", - /* 43 */ "autoinc ::= AUTOINCR", - /* 44 */ "refargs ::=", - /* 45 */ "refargs ::= refargs refarg", - /* 46 */ "refarg ::= MATCH nm", - /* 47 */ "refarg ::= ON INSERT refact", - /* 48 */ "refarg ::= ON DELETE refact", - /* 49 */ "refarg ::= ON UPDATE refact", - /* 50 */ "refact ::= SET NULL", - /* 51 */ "refact ::= SET DEFAULT", - /* 52 */ "refact ::= CASCADE", - /* 53 */ "refact ::= RESTRICT", - /* 54 */ "refact ::= NO ACTION", - /* 55 */ "defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt", - /* 56 */ "defer_subclause ::= DEFERRABLE init_deferred_pred_opt", - /* 57 */ "init_deferred_pred_opt ::=", - /* 58 */ "init_deferred_pred_opt ::= INITIALLY DEFERRED", - /* 59 */ "init_deferred_pred_opt ::= INITIALLY IMMEDIATE", - /* 60 */ "conslist_opt ::=", - /* 61 */ "tconscomma ::= COMMA", - /* 62 */ "tcons ::= CONSTRAINT nm", - /* 63 */ "tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf", - /* 64 */ "tcons ::= UNIQUE LP sortlist RP onconf", - /* 65 */ "tcons ::= CHECK LP expr RP onconf", - /* 66 */ "tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt", - /* 67 */ "defer_subclause_opt ::=", - /* 68 */ "onconf ::=", - /* 69 */ "onconf ::= ON CONFLICT resolvetype", - /* 70 */ "orconf ::=", - /* 71 */ "orconf ::= OR resolvetype", - /* 72 */ "resolvetype ::= IGNORE", - /* 73 */ "resolvetype ::= REPLACE", - /* 74 */ "cmd ::= DROP TABLE ifexists fullname", - /* 75 */ "ifexists ::= IF EXISTS", - /* 76 */ "ifexists ::=", - /* 77 */ "cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select", - /* 78 */ "cmd ::= DROP VIEW ifexists fullname", - /* 79 */ "cmd ::= select", - /* 80 */ "select ::= with selectnowith", - /* 81 */ "selectnowith ::= selectnowith multiselect_op oneselect", - /* 82 */ "multiselect_op ::= UNION", - /* 83 */ "multiselect_op ::= UNION ALL", - /* 84 */ "multiselect_op ::= EXCEPT|INTERSECT", - /* 85 */ "oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt", - /* 86 */ "values ::= VALUES LP nexprlist RP", - /* 87 */ "values ::= values COMMA LP exprlist RP", - /* 88 */ "distinct ::= DISTINCT", - /* 89 */ "distinct ::= ALL", - /* 90 */ "distinct ::=", - /* 91 */ "sclp ::=", - /* 92 */ "selcollist ::= sclp expr as", - /* 93 */ "selcollist ::= sclp STAR", - /* 94 */ "selcollist ::= sclp nm DOT STAR", - /* 95 */ "as ::= AS nm", - /* 96 */ "as ::=", - /* 97 */ "from ::=", - /* 98 */ "from ::= FROM seltablist", - /* 99 */ "stl_prefix ::= seltablist joinop", - /* 100 */ "stl_prefix ::=", - /* 101 */ "seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt", - /* 102 */ "seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_opt using_opt", - /* 103 */ "seltablist ::= stl_prefix LP select RP as on_opt using_opt", - /* 104 */ "seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt", - /* 105 */ "dbnm ::=", - /* 106 */ "dbnm ::= DOT nm", - /* 107 */ "fullname ::= nm dbnm", - /* 108 */ "joinop ::= COMMA|JOIN", - /* 109 */ "joinop ::= JOIN_KW JOIN", - /* 110 */ "joinop ::= JOIN_KW nm JOIN", - /* 111 */ "joinop ::= JOIN_KW nm nm JOIN", - /* 112 */ "on_opt ::= ON expr", - /* 113 */ "on_opt ::=", - /* 114 */ "indexed_opt ::=", - /* 115 */ "indexed_opt ::= INDEXED BY nm", - /* 116 */ "indexed_opt ::= NOT INDEXED", - /* 117 */ "using_opt ::= USING LP idlist RP", - /* 118 */ "using_opt ::=", - /* 119 */ "orderby_opt ::=", - /* 120 */ "orderby_opt ::= ORDER BY sortlist", - /* 121 */ "sortlist ::= sortlist COMMA expr sortorder", - /* 122 */ "sortlist ::= expr sortorder", - /* 123 */ "sortorder ::= ASC", - /* 124 */ "sortorder ::= DESC", - /* 125 */ "sortorder ::=", - /* 126 */ "groupby_opt ::=", - /* 127 */ "groupby_opt ::= GROUP BY nexprlist", - /* 128 */ "having_opt ::=", - /* 129 */ "having_opt ::= HAVING expr", - /* 130 */ "limit_opt ::=", - /* 131 */ "limit_opt ::= LIMIT expr", - /* 132 */ "limit_opt ::= LIMIT expr OFFSET expr", - /* 133 */ "limit_opt ::= LIMIT expr COMMA expr", - /* 134 */ "cmd ::= with DELETE FROM fullname indexed_opt where_opt", - /* 135 */ "where_opt ::=", - /* 136 */ "where_opt ::= WHERE expr", - /* 137 */ "cmd ::= with UPDATE orconf fullname indexed_opt SET setlist where_opt", - /* 138 */ "setlist ::= setlist COMMA nm EQ expr", - /* 139 */ "setlist ::= setlist COMMA LP idlist RP EQ expr", - /* 140 */ "setlist ::= nm EQ expr", - /* 141 */ "setlist ::= LP idlist RP EQ expr", - /* 142 */ "cmd ::= with insert_cmd INTO fullname idlist_opt select", - /* 143 */ "cmd ::= with insert_cmd INTO fullname idlist_opt DEFAULT VALUES", - /* 144 */ "insert_cmd ::= INSERT orconf", - /* 145 */ "insert_cmd ::= REPLACE", - /* 146 */ "idlist_opt ::=", - /* 147 */ "idlist_opt ::= LP idlist RP", - /* 148 */ "idlist ::= idlist COMMA nm", - /* 149 */ "idlist ::= nm", - /* 150 */ "expr ::= LP expr RP", - /* 151 */ "term ::= NULL", - /* 152 */ "expr ::= ID|INDEXED", - /* 153 */ "expr ::= JOIN_KW", - /* 154 */ "expr ::= nm DOT nm", - /* 155 */ "expr ::= nm DOT nm DOT nm", - /* 156 */ "term ::= FLOAT|BLOB", - /* 157 */ "term ::= STRING", - /* 158 */ "term ::= INTEGER", - /* 159 */ "expr ::= VARIABLE", - /* 160 */ "expr ::= expr COLLATE ID|STRING", - /* 161 */ "expr ::= CAST LP expr AS typetoken RP", - /* 162 */ "expr ::= ID|INDEXED LP distinct exprlist RP", - /* 163 */ "expr ::= ID|INDEXED LP STAR RP", - /* 164 */ "term ::= CTIME_KW", - /* 165 */ "expr ::= LP nexprlist COMMA expr RP", - /* 166 */ "expr ::= expr AND expr", - /* 167 */ "expr ::= expr OR expr", - /* 168 */ "expr ::= expr LT|GT|GE|LE expr", - /* 169 */ "expr ::= expr EQ|NE expr", - /* 170 */ "expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr", - /* 171 */ "expr ::= expr PLUS|MINUS expr", - /* 172 */ "expr ::= expr STAR|SLASH|REM expr", - /* 173 */ "expr ::= expr CONCAT expr", - /* 174 */ "likeop ::= NOT LIKE_KW|MATCH", - /* 175 */ "expr ::= expr likeop expr", - /* 176 */ "expr ::= expr likeop expr ESCAPE expr", - /* 177 */ "expr ::= expr ISNULL|NOTNULL", - /* 178 */ "expr ::= expr NOT NULL", - /* 179 */ "expr ::= expr IS expr", - /* 180 */ "expr ::= expr IS NOT expr", - /* 181 */ "expr ::= NOT expr", - /* 182 */ "expr ::= BITNOT expr", - /* 183 */ "expr ::= MINUS expr", - /* 184 */ "expr ::= PLUS expr", - /* 185 */ "between_op ::= BETWEEN", - /* 186 */ "between_op ::= NOT BETWEEN", - /* 187 */ "expr ::= expr between_op expr AND expr", - /* 188 */ "in_op ::= IN", - /* 189 */ "in_op ::= NOT IN", - /* 190 */ "expr ::= expr in_op LP exprlist RP", - /* 191 */ "expr ::= LP select RP", - /* 192 */ "expr ::= expr in_op LP select RP", - /* 193 */ "expr ::= expr in_op nm dbnm paren_exprlist", - /* 194 */ "expr ::= EXISTS LP select RP", - /* 195 */ "expr ::= CASE case_operand case_exprlist case_else END", - /* 196 */ "case_exprlist ::= case_exprlist WHEN expr THEN expr", - /* 197 */ "case_exprlist ::= WHEN expr THEN expr", - /* 198 */ "case_else ::= ELSE expr", - /* 199 */ "case_else ::=", - /* 200 */ "case_operand ::= expr", - /* 201 */ "case_operand ::=", - /* 202 */ "exprlist ::=", - /* 203 */ "nexprlist ::= nexprlist COMMA expr", - /* 204 */ "nexprlist ::= expr", - /* 205 */ "paren_exprlist ::=", - /* 206 */ "paren_exprlist ::= LP exprlist RP", - /* 207 */ "cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt", - /* 208 */ "uniqueflag ::= UNIQUE", - /* 209 */ "uniqueflag ::=", - /* 210 */ "eidlist_opt ::=", - /* 211 */ "eidlist_opt ::= LP eidlist RP", - /* 212 */ "eidlist ::= eidlist COMMA nm collate sortorder", - /* 213 */ "eidlist ::= nm collate sortorder", - /* 214 */ "collate ::=", - /* 215 */ "collate ::= COLLATE ID|STRING", - /* 216 */ "cmd ::= DROP INDEX ifexists fullname", - /* 217 */ "cmd ::= VACUUM", - /* 218 */ "cmd ::= VACUUM nm", - /* 219 */ "cmd ::= PRAGMA nm dbnm", - /* 220 */ "cmd ::= PRAGMA nm dbnm EQ nmnum", - /* 221 */ "cmd ::= PRAGMA nm dbnm LP nmnum RP", - /* 222 */ "cmd ::= PRAGMA nm dbnm EQ minus_num", - /* 223 */ "cmd ::= PRAGMA nm dbnm LP minus_num RP", - /* 224 */ "plus_num ::= PLUS INTEGER|FLOAT", - /* 225 */ "minus_num ::= MINUS INTEGER|FLOAT", - /* 226 */ "cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END", - /* 227 */ "trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause", - /* 228 */ "trigger_time ::= BEFORE", - /* 229 */ "trigger_time ::= AFTER", - /* 230 */ "trigger_time ::= INSTEAD OF", - /* 231 */ "trigger_time ::=", - /* 232 */ "trigger_event ::= DELETE|INSERT", - /* 233 */ "trigger_event ::= UPDATE", - /* 234 */ "trigger_event ::= UPDATE OF idlist", - /* 235 */ "when_clause ::=", - /* 236 */ "when_clause ::= WHEN expr", - /* 237 */ "trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI", - /* 238 */ "trigger_cmd_list ::= trigger_cmd SEMI", - /* 239 */ "trnm ::= nm DOT nm", - /* 240 */ "tridxby ::= INDEXED BY nm", - /* 241 */ "tridxby ::= NOT INDEXED", - /* 242 */ "trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist where_opt", - /* 243 */ "trigger_cmd ::= insert_cmd INTO trnm idlist_opt select", - /* 244 */ "trigger_cmd ::= DELETE FROM trnm tridxby where_opt", - /* 245 */ "trigger_cmd ::= select", - /* 246 */ "expr ::= RAISE LP IGNORE RP", - /* 247 */ "expr ::= RAISE LP raisetype COMMA nm RP", - /* 248 */ "raisetype ::= ROLLBACK", - /* 249 */ "raisetype ::= ABORT", - /* 250 */ "raisetype ::= FAIL", - /* 251 */ "cmd ::= DROP TRIGGER ifexists fullname", - /* 252 */ "cmd ::= ATTACH database_kw_opt expr AS expr key_opt", - /* 253 */ "cmd ::= DETACH database_kw_opt expr", - /* 254 */ "key_opt ::=", - /* 255 */ "key_opt ::= KEY expr", - /* 256 */ "cmd ::= REINDEX", - /* 257 */ "cmd ::= REINDEX nm dbnm", - /* 258 */ "cmd ::= ANALYZE", - /* 259 */ "cmd ::= ANALYZE nm dbnm", - /* 260 */ "cmd ::= ALTER TABLE fullname RENAME TO nm", - /* 261 */ "cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist", - /* 262 */ "add_column_fullname ::= fullname", - /* 263 */ "cmd ::= create_vtab", - /* 264 */ "cmd ::= create_vtab LP vtabarglist RP", - /* 265 */ "create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm", - /* 266 */ "vtabarg ::=", - /* 267 */ "vtabargtoken ::= ANY", - /* 268 */ "vtabargtoken ::= lp anylist RP", - /* 269 */ "lp ::= LP", - /* 270 */ "with ::=", - /* 271 */ "with ::= WITH wqlist", - /* 272 */ "with ::= WITH RECURSIVE wqlist", - /* 273 */ "wqlist ::= nm eidlist_opt AS LP select RP", - /* 274 */ "wqlist ::= wqlist COMMA nm eidlist_opt AS LP select RP", - /* 275 */ "input ::= cmdlist", - /* 276 */ "cmdlist ::= cmdlist ecmd", - /* 277 */ "cmdlist ::= ecmd", - /* 278 */ "ecmd ::= SEMI", - /* 279 */ "ecmd ::= explain cmdx SEMI", - /* 280 */ "explain ::=", - /* 281 */ "trans_opt ::=", - /* 282 */ "trans_opt ::= TRANSACTION", - /* 283 */ "trans_opt ::= TRANSACTION nm", - /* 284 */ "savepoint_opt ::= SAVEPOINT", - /* 285 */ "savepoint_opt ::=", - /* 286 */ "cmd ::= create_table create_table_args", - /* 287 */ "columnlist ::= columnlist COMMA columnname carglist", - /* 288 */ "columnlist ::= columnname carglist", - /* 289 */ "nm ::= ID|INDEXED", - /* 290 */ "nm ::= STRING", - /* 291 */ "nm ::= JOIN_KW", - /* 292 */ "typetoken ::= typename", - /* 293 */ "typename ::= ID|STRING", - /* 294 */ "signed ::= plus_num", - /* 295 */ "signed ::= minus_num", - /* 296 */ "carglist ::= carglist ccons", - /* 297 */ "carglist ::=", - /* 298 */ "ccons ::= NULL onconf", - /* 299 */ "conslist_opt ::= COMMA conslist", - /* 300 */ "conslist ::= conslist tconscomma tcons", - /* 301 */ "conslist ::= tcons", - /* 302 */ "tconscomma ::=", - /* 303 */ "defer_subclause_opt ::= defer_subclause", - /* 304 */ "resolvetype ::= raisetype", - /* 305 */ "selectnowith ::= oneselect", - /* 306 */ "oneselect ::= values", - /* 307 */ "sclp ::= selcollist COMMA", - /* 308 */ "as ::= ID|STRING", - /* 309 */ "expr ::= term", - /* 310 */ "likeop ::= LIKE_KW|MATCH", - /* 311 */ "exprlist ::= nexprlist", - /* 312 */ "nmnum ::= plus_num", - /* 313 */ "nmnum ::= nm", - /* 314 */ "nmnum ::= ON", - /* 315 */ "nmnum ::= DELETE", - /* 316 */ "nmnum ::= DEFAULT", - /* 317 */ "plus_num ::= INTEGER|FLOAT", - /* 318 */ "foreach_clause ::=", - /* 319 */ "foreach_clause ::= FOR EACH ROW", - /* 320 */ "trnm ::= nm", - /* 321 */ "tridxby ::=", - /* 322 */ "database_kw_opt ::= DATABASE", - /* 323 */ "database_kw_opt ::=", - /* 324 */ "kwcolumn_opt ::=", - /* 325 */ "kwcolumn_opt ::= COLUMNKW", - /* 326 */ "vtabarglist ::= vtabarg", - /* 327 */ "vtabarglist ::= vtabarglist COMMA vtabarg", - /* 328 */ "vtabarg ::= vtabarg vtabargtoken", - /* 329 */ "anylist ::=", - /* 330 */ "anylist ::= anylist LP anylist RP", - /* 331 */ "anylist ::= anylist ANY", + /* 8 */ "cmd ::= COMMIT|END trans_opt", + /* 9 */ "cmd ::= ROLLBACK trans_opt", + /* 10 */ "cmd ::= SAVEPOINT nm", + /* 11 */ "cmd ::= RELEASE savepoint_opt nm", + /* 12 */ "cmd ::= ROLLBACK trans_opt TO savepoint_opt nm", + /* 13 */ "create_table ::= createkw temp TABLE ifnotexists nm dbnm", + /* 14 */ "createkw ::= CREATE", + /* 15 */ "ifnotexists ::=", + /* 16 */ "ifnotexists ::= IF NOT EXISTS", + /* 17 */ "temp ::= TEMP", + /* 18 */ "temp ::=", + /* 19 */ "create_table_args ::= LP columnlist conslist_opt RP table_options", + /* 20 */ "create_table_args ::= AS select", + /* 21 */ "table_options ::=", + /* 22 */ "table_options ::= WITHOUT nm", + /* 23 */ "columnname ::= nm typetoken", + /* 24 */ "typetoken ::=", + /* 25 */ "typetoken ::= typename LP signed RP", + /* 26 */ "typetoken ::= typename LP signed COMMA signed RP", + /* 27 */ "typename ::= typename ID|STRING", + /* 28 */ "ccons ::= CONSTRAINT nm", + /* 29 */ "ccons ::= DEFAULT term", + /* 30 */ "ccons ::= DEFAULT LP expr RP", + /* 31 */ "ccons ::= DEFAULT PLUS term", + /* 32 */ "ccons ::= DEFAULT MINUS term", + /* 33 */ "ccons ::= DEFAULT ID|INDEXED", + /* 34 */ "ccons ::= NOT NULL onconf", + /* 35 */ "ccons ::= PRIMARY KEY sortorder onconf autoinc", + /* 36 */ "ccons ::= UNIQUE onconf", + /* 37 */ "ccons ::= CHECK LP expr RP", + /* 38 */ "ccons ::= REFERENCES nm eidlist_opt refargs", + /* 39 */ "ccons ::= defer_subclause", + /* 40 */ "ccons ::= COLLATE ID|STRING", + /* 41 */ "autoinc ::=", + /* 42 */ "autoinc ::= AUTOINCR", + /* 43 */ "refargs ::=", + /* 44 */ "refargs ::= refargs refarg", + /* 45 */ "refarg ::= MATCH nm", + /* 46 */ "refarg ::= ON INSERT refact", + /* 47 */ "refarg ::= ON DELETE refact", + /* 48 */ "refarg ::= ON UPDATE refact", + /* 49 */ "refact ::= SET NULL", + /* 50 */ "refact ::= SET DEFAULT", + /* 51 */ "refact ::= CASCADE", + /* 52 */ "refact ::= RESTRICT", + /* 53 */ "refact ::= NO ACTION", + /* 54 */ "defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt", + /* 55 */ "defer_subclause ::= DEFERRABLE init_deferred_pred_opt", + /* 56 */ "init_deferred_pred_opt ::=", + /* 57 */ "init_deferred_pred_opt ::= INITIALLY DEFERRED", + /* 58 */ "init_deferred_pred_opt ::= INITIALLY IMMEDIATE", + /* 59 */ "conslist_opt ::=", + /* 60 */ "tconscomma ::= COMMA", + /* 61 */ "tcons ::= CONSTRAINT nm", + /* 62 */ "tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf", + /* 63 */ "tcons ::= UNIQUE LP sortlist RP onconf", + /* 64 */ "tcons ::= CHECK LP expr RP onconf", + /* 65 */ "tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt", + /* 66 */ "defer_subclause_opt ::=", + /* 67 */ "onconf ::=", + /* 68 */ "onconf ::= ON CONFLICT resolvetype", + /* 69 */ "orconf ::=", + /* 70 */ "orconf ::= OR resolvetype", + /* 71 */ "resolvetype ::= IGNORE", + /* 72 */ "resolvetype ::= REPLACE", + /* 73 */ "cmd ::= DROP TABLE ifexists fullname", + /* 74 */ "ifexists ::= IF EXISTS", + /* 75 */ "ifexists ::=", + /* 76 */ "cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select", + /* 77 */ "cmd ::= DROP VIEW ifexists fullname", + /* 78 */ "cmd ::= select", + /* 79 */ "select ::= with selectnowith", + /* 80 */ "selectnowith ::= selectnowith multiselect_op oneselect", + /* 81 */ "multiselect_op ::= UNION", + /* 82 */ "multiselect_op ::= UNION ALL", + /* 83 */ "multiselect_op ::= EXCEPT|INTERSECT", + /* 84 */ "oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt", + /* 85 */ "values ::= VALUES LP nexprlist RP", + /* 86 */ "values ::= values COMMA LP exprlist RP", + /* 87 */ "distinct ::= DISTINCT", + /* 88 */ "distinct ::= ALL", + /* 89 */ "distinct ::=", + /* 90 */ "sclp ::=", + /* 91 */ "selcollist ::= sclp expr as", + /* 92 */ "selcollist ::= sclp STAR", + /* 93 */ "selcollist ::= sclp nm DOT STAR", + /* 94 */ "as ::= AS nm", + /* 95 */ "as ::=", + /* 96 */ "from ::=", + /* 97 */ "from ::= FROM seltablist", + /* 98 */ "stl_prefix ::= seltablist joinop", + /* 99 */ "stl_prefix ::=", + /* 100 */ "seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt", + /* 101 */ "seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_opt using_opt", + /* 102 */ "seltablist ::= stl_prefix LP select RP as on_opt using_opt", + /* 103 */ "seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt", + /* 104 */ "dbnm ::=", + /* 105 */ "dbnm ::= DOT nm", + /* 106 */ "fullname ::= nm dbnm", + /* 107 */ "joinop ::= COMMA|JOIN", + /* 108 */ "joinop ::= JOIN_KW JOIN", + /* 109 */ "joinop ::= JOIN_KW nm JOIN", + /* 110 */ "joinop ::= JOIN_KW nm nm JOIN", + /* 111 */ "on_opt ::= ON expr", + /* 112 */ "on_opt ::=", + /* 113 */ "indexed_opt ::=", + /* 114 */ "indexed_opt ::= INDEXED BY nm", + /* 115 */ "indexed_opt ::= NOT INDEXED", + /* 116 */ "using_opt ::= USING LP idlist RP", + /* 117 */ "using_opt ::=", + /* 118 */ "orderby_opt ::=", + /* 119 */ "orderby_opt ::= ORDER BY sortlist", + /* 120 */ "sortlist ::= sortlist COMMA expr sortorder", + /* 121 */ "sortlist ::= expr sortorder", + /* 122 */ "sortorder ::= ASC", + /* 123 */ "sortorder ::= DESC", + /* 124 */ "sortorder ::=", + /* 125 */ "groupby_opt ::=", + /* 126 */ "groupby_opt ::= GROUP BY nexprlist", + /* 127 */ "having_opt ::=", + /* 128 */ "having_opt ::= HAVING expr", + /* 129 */ "limit_opt ::=", + /* 130 */ "limit_opt ::= LIMIT expr", + /* 131 */ "limit_opt ::= LIMIT expr OFFSET expr", + /* 132 */ "limit_opt ::= LIMIT expr COMMA expr", + /* 133 */ "cmd ::= with DELETE FROM fullname indexed_opt where_opt", + /* 134 */ "where_opt ::=", + /* 135 */ "where_opt ::= WHERE expr", + /* 136 */ "cmd ::= with UPDATE orconf fullname indexed_opt SET setlist where_opt", + /* 137 */ "setlist ::= setlist COMMA nm EQ expr", + /* 138 */ "setlist ::= setlist COMMA LP idlist RP EQ expr", + /* 139 */ "setlist ::= nm EQ expr", + /* 140 */ "setlist ::= LP idlist RP EQ expr", + /* 141 */ "cmd ::= with insert_cmd INTO fullname idlist_opt select", + /* 142 */ "cmd ::= with insert_cmd INTO fullname idlist_opt DEFAULT VALUES", + /* 143 */ "insert_cmd ::= INSERT orconf", + /* 144 */ "insert_cmd ::= REPLACE", + /* 145 */ "idlist_opt ::=", + /* 146 */ "idlist_opt ::= LP idlist RP", + /* 147 */ "idlist ::= idlist COMMA nm", + /* 148 */ "idlist ::= nm", + /* 149 */ "expr ::= LP expr RP", + /* 150 */ "expr ::= ID|INDEXED", + /* 151 */ "expr ::= JOIN_KW", + /* 152 */ "expr ::= nm DOT nm", + /* 153 */ "expr ::= nm DOT nm DOT nm", + /* 154 */ "term ::= NULL|FLOAT|BLOB", + /* 155 */ "term ::= STRING", + /* 156 */ "term ::= INTEGER", + /* 157 */ "expr ::= VARIABLE", + /* 158 */ "expr ::= expr COLLATE ID|STRING", + /* 159 */ "expr ::= CAST LP expr AS typetoken RP", + /* 160 */ "expr ::= ID|INDEXED LP distinct exprlist RP", + /* 161 */ "expr ::= ID|INDEXED LP STAR RP", + /* 162 */ "term ::= CTIME_KW", + /* 163 */ "expr ::= LP nexprlist COMMA expr RP", + /* 164 */ "expr ::= expr AND expr", + /* 165 */ "expr ::= expr OR expr", + /* 166 */ "expr ::= expr LT|GT|GE|LE expr", + /* 167 */ "expr ::= expr EQ|NE expr", + /* 168 */ "expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr", + /* 169 */ "expr ::= expr PLUS|MINUS expr", + /* 170 */ "expr ::= expr STAR|SLASH|REM expr", + /* 171 */ "expr ::= expr CONCAT expr", + /* 172 */ "likeop ::= NOT LIKE_KW|MATCH", + /* 173 */ "expr ::= expr likeop expr", + /* 174 */ "expr ::= expr likeop expr ESCAPE expr", + /* 175 */ "expr ::= expr ISNULL|NOTNULL", + /* 176 */ "expr ::= expr NOT NULL", + /* 177 */ "expr ::= expr IS expr", + /* 178 */ "expr ::= expr IS NOT expr", + /* 179 */ "expr ::= NOT expr", + /* 180 */ "expr ::= BITNOT expr", + /* 181 */ "expr ::= MINUS expr", + /* 182 */ "expr ::= PLUS expr", + /* 183 */ "between_op ::= BETWEEN", + /* 184 */ "between_op ::= NOT BETWEEN", + /* 185 */ "expr ::= expr between_op expr AND expr", + /* 186 */ "in_op ::= IN", + /* 187 */ "in_op ::= NOT IN", + /* 188 */ "expr ::= expr in_op LP exprlist RP", + /* 189 */ "expr ::= LP select RP", + /* 190 */ "expr ::= expr in_op LP select RP", + /* 191 */ "expr ::= expr in_op nm dbnm paren_exprlist", + /* 192 */ "expr ::= EXISTS LP select RP", + /* 193 */ "expr ::= CASE case_operand case_exprlist case_else END", + /* 194 */ "case_exprlist ::= case_exprlist WHEN expr THEN expr", + /* 195 */ "case_exprlist ::= WHEN expr THEN expr", + /* 196 */ "case_else ::= ELSE expr", + /* 197 */ "case_else ::=", + /* 198 */ "case_operand ::= expr", + /* 199 */ "case_operand ::=", + /* 200 */ "exprlist ::=", + /* 201 */ "nexprlist ::= nexprlist COMMA expr", + /* 202 */ "nexprlist ::= expr", + /* 203 */ "paren_exprlist ::=", + /* 204 */ "paren_exprlist ::= LP exprlist RP", + /* 205 */ "cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt", + /* 206 */ "uniqueflag ::= UNIQUE", + /* 207 */ "uniqueflag ::=", + /* 208 */ "eidlist_opt ::=", + /* 209 */ "eidlist_opt ::= LP eidlist RP", + /* 210 */ "eidlist ::= eidlist COMMA nm collate sortorder", + /* 211 */ "eidlist ::= nm collate sortorder", + /* 212 */ "collate ::=", + /* 213 */ "collate ::= COLLATE ID|STRING", + /* 214 */ "cmd ::= DROP INDEX ifexists fullname", + /* 215 */ "cmd ::= VACUUM", + /* 216 */ "cmd ::= VACUUM nm", + /* 217 */ "cmd ::= PRAGMA nm dbnm", + /* 218 */ "cmd ::= PRAGMA nm dbnm EQ nmnum", + /* 219 */ "cmd ::= PRAGMA nm dbnm LP nmnum RP", + /* 220 */ "cmd ::= PRAGMA nm dbnm EQ minus_num", + /* 221 */ "cmd ::= PRAGMA nm dbnm LP minus_num RP", + /* 222 */ "plus_num ::= PLUS INTEGER|FLOAT", + /* 223 */ "minus_num ::= MINUS INTEGER|FLOAT", + /* 224 */ "cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END", + /* 225 */ "trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause", + /* 226 */ "trigger_time ::= BEFORE|AFTER", + /* 227 */ "trigger_time ::= INSTEAD OF", + /* 228 */ "trigger_time ::=", + /* 229 */ "trigger_event ::= DELETE|INSERT", + /* 230 */ "trigger_event ::= UPDATE", + /* 231 */ "trigger_event ::= UPDATE OF idlist", + /* 232 */ "when_clause ::=", + /* 233 */ "when_clause ::= WHEN expr", + /* 234 */ "trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI", + /* 235 */ "trigger_cmd_list ::= trigger_cmd SEMI", + /* 236 */ "trnm ::= nm DOT nm", + /* 237 */ "tridxby ::= INDEXED BY nm", + /* 238 */ "tridxby ::= NOT INDEXED", + /* 239 */ "trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist where_opt", + /* 240 */ "trigger_cmd ::= insert_cmd INTO trnm idlist_opt select", + /* 241 */ "trigger_cmd ::= DELETE FROM trnm tridxby where_opt", + /* 242 */ "trigger_cmd ::= select", + /* 243 */ "expr ::= RAISE LP IGNORE RP", + /* 244 */ "expr ::= RAISE LP raisetype COMMA nm RP", + /* 245 */ "raisetype ::= ROLLBACK", + /* 246 */ "raisetype ::= ABORT", + /* 247 */ "raisetype ::= FAIL", + /* 248 */ "cmd ::= DROP TRIGGER ifexists fullname", + /* 249 */ "cmd ::= ATTACH database_kw_opt expr AS expr key_opt", + /* 250 */ "cmd ::= DETACH database_kw_opt expr", + /* 251 */ "key_opt ::=", + /* 252 */ "key_opt ::= KEY expr", + /* 253 */ "cmd ::= REINDEX", + /* 254 */ "cmd ::= REINDEX nm dbnm", + /* 255 */ "cmd ::= ANALYZE", + /* 256 */ "cmd ::= ANALYZE nm dbnm", + /* 257 */ "cmd ::= ALTER TABLE fullname RENAME TO nm", + /* 258 */ "cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist", + /* 259 */ "add_column_fullname ::= fullname", + /* 260 */ "cmd ::= create_vtab", + /* 261 */ "cmd ::= create_vtab LP vtabarglist RP", + /* 262 */ "create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm", + /* 263 */ "vtabarg ::=", + /* 264 */ "vtabargtoken ::= ANY", + /* 265 */ "vtabargtoken ::= lp anylist RP", + /* 266 */ "lp ::= LP", + /* 267 */ "with ::=", + /* 268 */ "with ::= WITH wqlist", + /* 269 */ "with ::= WITH RECURSIVE wqlist", + /* 270 */ "wqlist ::= nm eidlist_opt AS LP select RP", + /* 271 */ "wqlist ::= wqlist COMMA nm eidlist_opt AS LP select RP", + /* 272 */ "input ::= cmdlist", + /* 273 */ "cmdlist ::= cmdlist ecmd", + /* 274 */ "cmdlist ::= ecmd", + /* 275 */ "ecmd ::= SEMI", + /* 276 */ "ecmd ::= explain cmdx SEMI", + /* 277 */ "explain ::=", + /* 278 */ "trans_opt ::=", + /* 279 */ "trans_opt ::= TRANSACTION", + /* 280 */ "trans_opt ::= TRANSACTION nm", + /* 281 */ "savepoint_opt ::= SAVEPOINT", + /* 282 */ "savepoint_opt ::=", + /* 283 */ "cmd ::= create_table create_table_args", + /* 284 */ "columnlist ::= columnlist COMMA columnname carglist", + /* 285 */ "columnlist ::= columnname carglist", + /* 286 */ "nm ::= ID|INDEXED", + /* 287 */ "nm ::= STRING", + /* 288 */ "nm ::= JOIN_KW", + /* 289 */ "typetoken ::= typename", + /* 290 */ "typename ::= ID|STRING", + /* 291 */ "signed ::= plus_num", + /* 292 */ "signed ::= minus_num", + /* 293 */ "carglist ::= carglist ccons", + /* 294 */ "carglist ::=", + /* 295 */ "ccons ::= NULL onconf", + /* 296 */ "conslist_opt ::= COMMA conslist", + /* 297 */ "conslist ::= conslist tconscomma tcons", + /* 298 */ "conslist ::= tcons", + /* 299 */ "tconscomma ::=", + /* 300 */ "defer_subclause_opt ::= defer_subclause", + /* 301 */ "resolvetype ::= raisetype", + /* 302 */ "selectnowith ::= oneselect", + /* 303 */ "oneselect ::= values", + /* 304 */ "sclp ::= selcollist COMMA", + /* 305 */ "as ::= ID|STRING", + /* 306 */ "expr ::= term", + /* 307 */ "likeop ::= LIKE_KW|MATCH", + /* 308 */ "exprlist ::= nexprlist", + /* 309 */ "nmnum ::= plus_num", + /* 310 */ "nmnum ::= nm", + /* 311 */ "nmnum ::= ON", + /* 312 */ "nmnum ::= DELETE", + /* 313 */ "nmnum ::= DEFAULT", + /* 314 */ "plus_num ::= INTEGER|FLOAT", + /* 315 */ "foreach_clause ::=", + /* 316 */ "foreach_clause ::= FOR EACH ROW", + /* 317 */ "trnm ::= nm", + /* 318 */ "tridxby ::=", + /* 319 */ "database_kw_opt ::= DATABASE", + /* 320 */ "database_kw_opt ::=", + /* 321 */ "kwcolumn_opt ::=", + /* 322 */ "kwcolumn_opt ::= COLUMNKW", + /* 323 */ "vtabarglist ::= vtabarg", + /* 324 */ "vtabarglist ::= vtabarglist COMMA vtabarg", + /* 325 */ "vtabarg ::= vtabarg vtabargtoken", + /* 326 */ "anylist ::=", + /* 327 */ "anylist ::= anylist LP anylist RP", + /* 328 */ "anylist ::= anylist ANY", }; #endif /* NDEBUG */ @@ -136351,6 +138035,9 @@ SQLITE_PRIVATE void sqlite3ParserInit(void *yypParser){ pParser->yytos = pParser->yystack; pParser->yystack[0].stateno = 0; pParser->yystack[0].major = 0; +#if YYSTACKDEPTH>0 + pParser->yystackEnd = &pParser->yystack[YYSTACKDEPTH-1]; +#endif } #ifndef sqlite3Parser_ENGINEALWAYSONSTACK @@ -136693,7 +138380,7 @@ static void yy_shift( } #endif #if YYSTACKDEPTH>0 - if( yypParser->yytos>=&yypParser->yystack[YYSTACKDEPTH] ){ + if( yypParser->yytos>yypParser->yystackEnd ){ yypParser->yytos--; yyStackOverflow(yypParser); return; @@ -136721,341 +138408,338 @@ static void yy_shift( ** is used during the reduce. */ static const struct { - YYCODETYPE lhs; /* Symbol on the left-hand side of the rule */ - unsigned char nrhs; /* Number of right-hand side symbols in the rule */ + YYCODETYPE lhs; /* Symbol on the left-hand side of the rule */ + signed char nrhs; /* Negative of the number of RHS symbols in the rule */ } yyRuleInfo[] = { - { 147, 1 }, - { 147, 3 }, - { 148, 1 }, - { 149, 3 }, + { 147, -1 }, + { 147, -3 }, + { 148, -1 }, + { 149, -3 }, { 150, 0 }, - { 150, 1 }, - { 150, 1 }, - { 150, 1 }, - { 149, 2 }, - { 149, 2 }, - { 149, 2 }, - { 149, 2 }, - { 149, 3 }, - { 149, 5 }, - { 154, 6 }, - { 156, 1 }, + { 150, -1 }, + { 150, -1 }, + { 150, -1 }, + { 149, -2 }, + { 149, -2 }, + { 149, -2 }, + { 149, -3 }, + { 149, -5 }, + { 154, -6 }, + { 156, -1 }, { 158, 0 }, - { 158, 3 }, - { 157, 1 }, + { 158, -3 }, + { 157, -1 }, { 157, 0 }, - { 155, 5 }, - { 155, 2 }, + { 155, -5 }, + { 155, -2 }, { 162, 0 }, - { 162, 2 }, - { 164, 2 }, + { 162, -2 }, + { 164, -2 }, { 166, 0 }, - { 166, 4 }, - { 166, 6 }, - { 167, 2 }, - { 171, 2 }, - { 171, 2 }, - { 171, 4 }, - { 171, 3 }, - { 171, 3 }, - { 171, 2 }, - { 171, 3 }, - { 171, 5 }, - { 171, 2 }, - { 171, 4 }, - { 171, 4 }, - { 171, 1 }, - { 171, 2 }, + { 166, -4 }, + { 166, -6 }, + { 167, -2 }, + { 171, -2 }, + { 171, -2 }, + { 171, -4 }, + { 171, -3 }, + { 171, -3 }, + { 171, -2 }, + { 171, -3 }, + { 171, -5 }, + { 171, -2 }, + { 171, -4 }, + { 171, -4 }, + { 171, -1 }, + { 171, -2 }, { 176, 0 }, - { 176, 1 }, + { 176, -1 }, { 178, 0 }, - { 178, 2 }, - { 180, 2 }, - { 180, 3 }, - { 180, 3 }, - { 180, 3 }, - { 181, 2 }, - { 181, 2 }, - { 181, 1 }, - { 181, 1 }, - { 181, 2 }, - { 179, 3 }, - { 179, 2 }, + { 178, -2 }, + { 180, -2 }, + { 180, -3 }, + { 180, -3 }, + { 180, -3 }, + { 181, -2 }, + { 181, -2 }, + { 181, -1 }, + { 181, -1 }, + { 181, -2 }, + { 179, -3 }, + { 179, -2 }, { 182, 0 }, - { 182, 2 }, - { 182, 2 }, + { 182, -2 }, + { 182, -2 }, { 161, 0 }, - { 184, 1 }, - { 185, 2 }, - { 185, 7 }, - { 185, 5 }, - { 185, 5 }, - { 185, 10 }, + { 184, -1 }, + { 185, -2 }, + { 185, -7 }, + { 185, -5 }, + { 185, -5 }, + { 185, -10 }, { 188, 0 }, { 174, 0 }, - { 174, 3 }, + { 174, -3 }, { 189, 0 }, - { 189, 2 }, - { 190, 1 }, - { 190, 1 }, - { 149, 4 }, - { 192, 2 }, + { 189, -2 }, + { 190, -1 }, + { 190, -1 }, + { 149, -4 }, + { 192, -2 }, { 192, 0 }, - { 149, 9 }, - { 149, 4 }, - { 149, 1 }, - { 163, 2 }, - { 194, 3 }, - { 197, 1 }, - { 197, 2 }, - { 197, 1 }, - { 195, 9 }, - { 206, 4 }, - { 206, 5 }, - { 198, 1 }, - { 198, 1 }, + { 149, -9 }, + { 149, -4 }, + { 149, -1 }, + { 163, -2 }, + { 194, -3 }, + { 197, -1 }, + { 197, -2 }, + { 197, -1 }, + { 195, -9 }, + { 206, -4 }, + { 206, -5 }, + { 198, -1 }, + { 198, -1 }, { 198, 0 }, { 209, 0 }, - { 199, 3 }, - { 199, 2 }, - { 199, 4 }, - { 210, 2 }, + { 199, -3 }, + { 199, -2 }, + { 199, -4 }, + { 210, -2 }, { 210, 0 }, { 200, 0 }, - { 200, 2 }, - { 212, 2 }, + { 200, -2 }, + { 212, -2 }, { 212, 0 }, - { 211, 7 }, - { 211, 9 }, - { 211, 7 }, - { 211, 7 }, + { 211, -7 }, + { 211, -9 }, + { 211, -7 }, + { 211, -7 }, { 159, 0 }, - { 159, 2 }, - { 193, 2 }, - { 213, 1 }, - { 213, 2 }, - { 213, 3 }, - { 213, 4 }, - { 215, 2 }, + { 159, -2 }, + { 193, -2 }, + { 213, -1 }, + { 213, -2 }, + { 213, -3 }, + { 213, -4 }, + { 215, -2 }, { 215, 0 }, { 214, 0 }, - { 214, 3 }, - { 214, 2 }, - { 216, 4 }, + { 214, -3 }, + { 214, -2 }, + { 216, -4 }, { 216, 0 }, { 204, 0 }, - { 204, 3 }, - { 186, 4 }, - { 186, 2 }, - { 175, 1 }, - { 175, 1 }, + { 204, -3 }, + { 186, -4 }, + { 186, -2 }, + { 175, -1 }, + { 175, -1 }, { 175, 0 }, { 202, 0 }, - { 202, 3 }, + { 202, -3 }, { 203, 0 }, - { 203, 2 }, + { 203, -2 }, { 205, 0 }, - { 205, 2 }, - { 205, 4 }, - { 205, 4 }, - { 149, 6 }, + { 205, -2 }, + { 205, -4 }, + { 205, -4 }, + { 149, -6 }, { 201, 0 }, - { 201, 2 }, - { 149, 8 }, - { 218, 5 }, - { 218, 7 }, - { 218, 3 }, - { 218, 5 }, - { 149, 6 }, - { 149, 7 }, - { 219, 2 }, - { 219, 1 }, + { 201, -2 }, + { 149, -8 }, + { 218, -5 }, + { 218, -7 }, + { 218, -3 }, + { 218, -5 }, + { 149, -6 }, + { 149, -7 }, + { 219, -2 }, + { 219, -1 }, { 220, 0 }, - { 220, 3 }, - { 217, 3 }, - { 217, 1 }, - { 173, 3 }, - { 172, 1 }, - { 173, 1 }, - { 173, 1 }, - { 173, 3 }, - { 173, 5 }, - { 172, 1 }, - { 172, 1 }, - { 172, 1 }, - { 173, 1 }, - { 173, 3 }, - { 173, 6 }, - { 173, 5 }, - { 173, 4 }, - { 172, 1 }, - { 173, 5 }, - { 173, 3 }, - { 173, 3 }, - { 173, 3 }, - { 173, 3 }, - { 173, 3 }, - { 173, 3 }, - { 173, 3 }, - { 173, 3 }, - { 221, 2 }, - { 173, 3 }, - { 173, 5 }, - { 173, 2 }, - { 173, 3 }, - { 173, 3 }, - { 173, 4 }, - { 173, 2 }, - { 173, 2 }, - { 173, 2 }, - { 173, 2 }, - { 222, 1 }, - { 222, 2 }, - { 173, 5 }, - { 223, 1 }, - { 223, 2 }, - { 173, 5 }, - { 173, 3 }, - { 173, 5 }, - { 173, 5 }, - { 173, 4 }, - { 173, 5 }, - { 226, 5 }, - { 226, 4 }, - { 227, 2 }, + { 220, -3 }, + { 217, -3 }, + { 217, -1 }, + { 173, -3 }, + { 173, -1 }, + { 173, -1 }, + { 173, -3 }, + { 173, -5 }, + { 172, -1 }, + { 172, -1 }, + { 172, -1 }, + { 173, -1 }, + { 173, -3 }, + { 173, -6 }, + { 173, -5 }, + { 173, -4 }, + { 172, -1 }, + { 173, -5 }, + { 173, -3 }, + { 173, -3 }, + { 173, -3 }, + { 173, -3 }, + { 173, -3 }, + { 173, -3 }, + { 173, -3 }, + { 173, -3 }, + { 221, -2 }, + { 173, -3 }, + { 173, -5 }, + { 173, -2 }, + { 173, -3 }, + { 173, -3 }, + { 173, -4 }, + { 173, -2 }, + { 173, -2 }, + { 173, -2 }, + { 173, -2 }, + { 222, -1 }, + { 222, -2 }, + { 173, -5 }, + { 223, -1 }, + { 223, -2 }, + { 173, -5 }, + { 173, -3 }, + { 173, -5 }, + { 173, -5 }, + { 173, -4 }, + { 173, -5 }, + { 226, -5 }, + { 226, -4 }, + { 227, -2 }, { 227, 0 }, - { 225, 1 }, + { 225, -1 }, { 225, 0 }, { 208, 0 }, - { 207, 3 }, - { 207, 1 }, + { 207, -3 }, + { 207, -1 }, { 224, 0 }, - { 224, 3 }, - { 149, 12 }, - { 228, 1 }, + { 224, -3 }, + { 149, -12 }, + { 228, -1 }, { 228, 0 }, { 177, 0 }, - { 177, 3 }, - { 187, 5 }, - { 187, 3 }, + { 177, -3 }, + { 187, -5 }, + { 187, -3 }, { 229, 0 }, - { 229, 2 }, - { 149, 4 }, - { 149, 1 }, - { 149, 2 }, - { 149, 3 }, - { 149, 5 }, - { 149, 6 }, - { 149, 5 }, - { 149, 6 }, - { 169, 2 }, - { 170, 2 }, - { 149, 5 }, - { 231, 11 }, - { 233, 1 }, - { 233, 1 }, - { 233, 2 }, + { 229, -2 }, + { 149, -4 }, + { 149, -1 }, + { 149, -2 }, + { 149, -3 }, + { 149, -5 }, + { 149, -6 }, + { 149, -5 }, + { 149, -6 }, + { 169, -2 }, + { 170, -2 }, + { 149, -5 }, + { 231, -11 }, + { 233, -1 }, + { 233, -2 }, { 233, 0 }, - { 234, 1 }, - { 234, 1 }, - { 234, 3 }, + { 234, -1 }, + { 234, -1 }, + { 234, -3 }, { 236, 0 }, - { 236, 2 }, - { 232, 3 }, - { 232, 2 }, - { 238, 3 }, - { 239, 3 }, - { 239, 2 }, - { 237, 7 }, - { 237, 5 }, - { 237, 5 }, - { 237, 1 }, - { 173, 4 }, - { 173, 6 }, - { 191, 1 }, - { 191, 1 }, - { 191, 1 }, - { 149, 4 }, - { 149, 6 }, - { 149, 3 }, + { 236, -2 }, + { 232, -3 }, + { 232, -2 }, + { 238, -3 }, + { 239, -3 }, + { 239, -2 }, + { 237, -7 }, + { 237, -5 }, + { 237, -5 }, + { 237, -1 }, + { 173, -4 }, + { 173, -6 }, + { 191, -1 }, + { 191, -1 }, + { 191, -1 }, + { 149, -4 }, + { 149, -6 }, + { 149, -3 }, { 241, 0 }, - { 241, 2 }, - { 149, 1 }, - { 149, 3 }, - { 149, 1 }, - { 149, 3 }, - { 149, 6 }, - { 149, 7 }, - { 242, 1 }, - { 149, 1 }, - { 149, 4 }, - { 244, 8 }, + { 241, -2 }, + { 149, -1 }, + { 149, -3 }, + { 149, -1 }, + { 149, -3 }, + { 149, -6 }, + { 149, -7 }, + { 242, -1 }, + { 149, -1 }, + { 149, -4 }, + { 244, -8 }, { 246, 0 }, - { 247, 1 }, - { 247, 3 }, - { 248, 1 }, + { 247, -1 }, + { 247, -3 }, + { 248, -1 }, { 196, 0 }, - { 196, 2 }, - { 196, 3 }, - { 250, 6 }, - { 250, 8 }, - { 144, 1 }, - { 145, 2 }, - { 145, 1 }, - { 146, 1 }, - { 146, 3 }, + { 196, -2 }, + { 196, -3 }, + { 250, -6 }, + { 250, -8 }, + { 144, -1 }, + { 145, -2 }, + { 145, -1 }, + { 146, -1 }, + { 146, -3 }, { 147, 0 }, { 151, 0 }, - { 151, 1 }, - { 151, 2 }, - { 153, 1 }, + { 151, -1 }, + { 151, -2 }, + { 153, -1 }, { 153, 0 }, - { 149, 2 }, - { 160, 4 }, - { 160, 2 }, - { 152, 1 }, - { 152, 1 }, - { 152, 1 }, - { 166, 1 }, - { 167, 1 }, - { 168, 1 }, - { 168, 1 }, - { 165, 2 }, + { 149, -2 }, + { 160, -4 }, + { 160, -2 }, + { 152, -1 }, + { 152, -1 }, + { 152, -1 }, + { 166, -1 }, + { 167, -1 }, + { 168, -1 }, + { 168, -1 }, + { 165, -2 }, { 165, 0 }, - { 171, 2 }, - { 161, 2 }, - { 183, 3 }, - { 183, 1 }, + { 171, -2 }, + { 161, -2 }, + { 183, -3 }, + { 183, -1 }, { 184, 0 }, - { 188, 1 }, - { 190, 1 }, - { 194, 1 }, - { 195, 1 }, - { 209, 2 }, - { 210, 1 }, - { 173, 1 }, - { 221, 1 }, - { 208, 1 }, - { 230, 1 }, - { 230, 1 }, - { 230, 1 }, - { 230, 1 }, - { 230, 1 }, - { 169, 1 }, + { 188, -1 }, + { 190, -1 }, + { 194, -1 }, + { 195, -1 }, + { 209, -2 }, + { 210, -1 }, + { 173, -1 }, + { 221, -1 }, + { 208, -1 }, + { 230, -1 }, + { 230, -1 }, + { 230, -1 }, + { 230, -1 }, + { 230, -1 }, + { 169, -1 }, { 235, 0 }, - { 235, 3 }, - { 238, 1 }, + { 235, -3 }, + { 238, -1 }, { 239, 0 }, - { 240, 1 }, + { 240, -1 }, { 240, 0 }, { 243, 0 }, - { 243, 1 }, - { 245, 1 }, - { 245, 3 }, - { 246, 2 }, + { 243, -1 }, + { 245, -1 }, + { 245, -3 }, + { 246, -2 }, { 249, 0 }, - { 249, 4 }, - { 249, 2 }, + { 249, -4 }, + { 249, -2 }, }; static void yy_accept(yyParser*); /* Forward Declaration */ @@ -137078,7 +138762,7 @@ static void yy_reduce( if( yyTraceFILE && yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ){ yysize = yyRuleInfo[yyruleno].nrhs; fprintf(yyTraceFILE, "%sReduce [%s], go to state %d.\n", yyTracePrompt, - yyRuleName[yyruleno], yymsp[-yysize].stateno); + yyRuleName[yyruleno], yymsp[yysize].stateno); } #endif /* NDEBUG */ @@ -137093,7 +138777,7 @@ static void yy_reduce( } #endif #if YYSTACKDEPTH>0 - if( yypParser->yytos>=&yypParser->yystack[YYSTACKDEPTH-1] ){ + if( yypParser->yytos>=yypParser->yystackEnd ){ yyStackOverflow(yypParser); return; } @@ -137139,66 +138823,63 @@ static void yy_reduce( case 7: /* transtype ::= EXCLUSIVE */ yytestcase(yyruleno==7); {yymsp[0].minor.yy194 = yymsp[0].major; /*A-overwrites-X*/} break; - case 8: /* cmd ::= COMMIT trans_opt */ - case 9: /* cmd ::= END trans_opt */ yytestcase(yyruleno==9); -{sqlite3CommitTransaction(pParse);} + case 8: /* cmd ::= COMMIT|END trans_opt */ + case 9: /* cmd ::= ROLLBACK trans_opt */ yytestcase(yyruleno==9); +{sqlite3EndTransaction(pParse,yymsp[-1].major);} break; - case 10: /* cmd ::= ROLLBACK trans_opt */ -{sqlite3RollbackTransaction(pParse);} - break; - case 11: /* cmd ::= SAVEPOINT nm */ + case 10: /* cmd ::= SAVEPOINT nm */ { sqlite3Savepoint(pParse, SAVEPOINT_BEGIN, &yymsp[0].minor.yy0); } break; - case 12: /* cmd ::= RELEASE savepoint_opt nm */ + case 11: /* cmd ::= RELEASE savepoint_opt nm */ { sqlite3Savepoint(pParse, SAVEPOINT_RELEASE, &yymsp[0].minor.yy0); } break; - case 13: /* cmd ::= ROLLBACK trans_opt TO savepoint_opt nm */ + case 12: /* cmd ::= ROLLBACK trans_opt TO savepoint_opt nm */ { sqlite3Savepoint(pParse, SAVEPOINT_ROLLBACK, &yymsp[0].minor.yy0); } break; - case 14: /* create_table ::= createkw temp TABLE ifnotexists nm dbnm */ + case 13: /* create_table ::= createkw temp TABLE ifnotexists nm dbnm */ { sqlite3StartTable(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,yymsp[-4].minor.yy194,0,0,yymsp[-2].minor.yy194); } break; - case 15: /* createkw ::= CREATE */ + case 14: /* createkw ::= CREATE */ {disableLookaside(pParse);} break; - case 16: /* ifnotexists ::= */ - case 19: /* temp ::= */ yytestcase(yyruleno==19); - case 22: /* table_options ::= */ yytestcase(yyruleno==22); - case 42: /* autoinc ::= */ yytestcase(yyruleno==42); - case 57: /* init_deferred_pred_opt ::= */ yytestcase(yyruleno==57); - case 67: /* defer_subclause_opt ::= */ yytestcase(yyruleno==67); - case 76: /* ifexists ::= */ yytestcase(yyruleno==76); - case 90: /* distinct ::= */ yytestcase(yyruleno==90); - case 214: /* collate ::= */ yytestcase(yyruleno==214); + case 15: /* ifnotexists ::= */ + case 18: /* temp ::= */ yytestcase(yyruleno==18); + case 21: /* table_options ::= */ yytestcase(yyruleno==21); + case 41: /* autoinc ::= */ yytestcase(yyruleno==41); + case 56: /* init_deferred_pred_opt ::= */ yytestcase(yyruleno==56); + case 66: /* defer_subclause_opt ::= */ yytestcase(yyruleno==66); + case 75: /* ifexists ::= */ yytestcase(yyruleno==75); + case 89: /* distinct ::= */ yytestcase(yyruleno==89); + case 212: /* collate ::= */ yytestcase(yyruleno==212); {yymsp[1].minor.yy194 = 0;} break; - case 17: /* ifnotexists ::= IF NOT EXISTS */ + case 16: /* ifnotexists ::= IF NOT EXISTS */ {yymsp[-2].minor.yy194 = 1;} break; - case 18: /* temp ::= TEMP */ - case 43: /* autoinc ::= AUTOINCR */ yytestcase(yyruleno==43); + case 17: /* temp ::= TEMP */ + case 42: /* autoinc ::= AUTOINCR */ yytestcase(yyruleno==42); {yymsp[0].minor.yy194 = 1;} break; - case 20: /* create_table_args ::= LP columnlist conslist_opt RP table_options */ + case 19: /* create_table_args ::= LP columnlist conslist_opt RP table_options */ { sqlite3EndTable(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,yymsp[0].minor.yy194,0); } break; - case 21: /* create_table_args ::= AS select */ + case 20: /* create_table_args ::= AS select */ { sqlite3EndTable(pParse,0,0,0,yymsp[0].minor.yy243); sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy243); } break; - case 23: /* table_options ::= WITHOUT nm */ + case 22: /* table_options ::= WITHOUT nm */ { if( yymsp[0].minor.yy0.n==5 && sqlite3_strnicmp(yymsp[0].minor.yy0.z,"rowid",5)==0 ){ yymsp[-1].minor.yy194 = TF_WithoutRowid | TF_NoVisibleRowid; @@ -137208,39 +138889,39 @@ static void yy_reduce( } } break; - case 24: /* columnname ::= nm typetoken */ + case 23: /* columnname ::= nm typetoken */ {sqlite3AddColumn(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0);} break; - case 25: /* typetoken ::= */ - case 60: /* conslist_opt ::= */ yytestcase(yyruleno==60); - case 96: /* as ::= */ yytestcase(yyruleno==96); + case 24: /* typetoken ::= */ + case 59: /* conslist_opt ::= */ yytestcase(yyruleno==59); + case 95: /* as ::= */ yytestcase(yyruleno==95); {yymsp[1].minor.yy0.n = 0; yymsp[1].minor.yy0.z = 0;} break; - case 26: /* typetoken ::= typename LP signed RP */ + case 25: /* typetoken ::= typename LP signed RP */ { yymsp[-3].minor.yy0.n = (int)(&yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n] - yymsp[-3].minor.yy0.z); } break; - case 27: /* typetoken ::= typename LP signed COMMA signed RP */ + case 26: /* typetoken ::= typename LP signed COMMA signed RP */ { yymsp[-5].minor.yy0.n = (int)(&yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n] - yymsp[-5].minor.yy0.z); } break; - case 28: /* typename ::= typename ID|STRING */ + case 27: /* typename ::= typename ID|STRING */ {yymsp[-1].minor.yy0.n=yymsp[0].minor.yy0.n+(int)(yymsp[0].minor.yy0.z-yymsp[-1].minor.yy0.z);} break; - case 29: /* ccons ::= CONSTRAINT nm */ - case 62: /* tcons ::= CONSTRAINT nm */ yytestcase(yyruleno==62); + case 28: /* ccons ::= CONSTRAINT nm */ + case 61: /* tcons ::= CONSTRAINT nm */ yytestcase(yyruleno==61); {pParse->constraintName = yymsp[0].minor.yy0;} break; - case 30: /* ccons ::= DEFAULT term */ - case 32: /* ccons ::= DEFAULT PLUS term */ yytestcase(yyruleno==32); + case 29: /* ccons ::= DEFAULT term */ + case 31: /* ccons ::= DEFAULT PLUS term */ yytestcase(yyruleno==31); {sqlite3AddDefaultValue(pParse,&yymsp[0].minor.yy190);} break; - case 31: /* ccons ::= DEFAULT LP expr RP */ + case 30: /* ccons ::= DEFAULT LP expr RP */ {sqlite3AddDefaultValue(pParse,&yymsp[-1].minor.yy190);} break; - case 33: /* ccons ::= DEFAULT MINUS term */ + case 32: /* ccons ::= DEFAULT MINUS term */ { ExprSpan v; v.pExpr = sqlite3PExpr(pParse, TK_UMINUS, yymsp[0].minor.yy190.pExpr, 0); @@ -137249,142 +138930,142 @@ static void yy_reduce( sqlite3AddDefaultValue(pParse,&v); } break; - case 34: /* ccons ::= DEFAULT ID|INDEXED */ + case 33: /* ccons ::= DEFAULT ID|INDEXED */ { ExprSpan v; spanExpr(&v, pParse, TK_STRING, yymsp[0].minor.yy0); sqlite3AddDefaultValue(pParse,&v); } break; - case 35: /* ccons ::= NOT NULL onconf */ + case 34: /* ccons ::= NOT NULL onconf */ {sqlite3AddNotNull(pParse, yymsp[0].minor.yy194);} break; - case 36: /* ccons ::= PRIMARY KEY sortorder onconf autoinc */ + case 35: /* ccons ::= PRIMARY KEY sortorder onconf autoinc */ {sqlite3AddPrimaryKey(pParse,0,yymsp[-1].minor.yy194,yymsp[0].minor.yy194,yymsp[-2].minor.yy194);} break; - case 37: /* ccons ::= UNIQUE onconf */ + case 36: /* ccons ::= UNIQUE onconf */ {sqlite3CreateIndex(pParse,0,0,0,0,yymsp[0].minor.yy194,0,0,0,0, SQLITE_IDXTYPE_UNIQUE);} break; - case 38: /* ccons ::= CHECK LP expr RP */ + case 37: /* ccons ::= CHECK LP expr RP */ {sqlite3AddCheckConstraint(pParse,yymsp[-1].minor.yy190.pExpr);} break; - case 39: /* ccons ::= REFERENCES nm eidlist_opt refargs */ + case 38: /* ccons ::= REFERENCES nm eidlist_opt refargs */ {sqlite3CreateForeignKey(pParse,0,&yymsp[-2].minor.yy0,yymsp[-1].minor.yy148,yymsp[0].minor.yy194);} break; - case 40: /* ccons ::= defer_subclause */ + case 39: /* ccons ::= defer_subclause */ {sqlite3DeferForeignKey(pParse,yymsp[0].minor.yy194);} break; - case 41: /* ccons ::= COLLATE ID|STRING */ + case 40: /* ccons ::= COLLATE ID|STRING */ {sqlite3AddCollateType(pParse, &yymsp[0].minor.yy0);} break; - case 44: /* refargs ::= */ + case 43: /* refargs ::= */ { yymsp[1].minor.yy194 = OE_None*0x0101; /* EV: R-19803-45884 */} break; - case 45: /* refargs ::= refargs refarg */ + case 44: /* refargs ::= refargs refarg */ { yymsp[-1].minor.yy194 = (yymsp[-1].minor.yy194 & ~yymsp[0].minor.yy497.mask) | yymsp[0].minor.yy497.value; } break; - case 46: /* refarg ::= MATCH nm */ + case 45: /* refarg ::= MATCH nm */ { yymsp[-1].minor.yy497.value = 0; yymsp[-1].minor.yy497.mask = 0x000000; } break; - case 47: /* refarg ::= ON INSERT refact */ + case 46: /* refarg ::= ON INSERT refact */ { yymsp[-2].minor.yy497.value = 0; yymsp[-2].minor.yy497.mask = 0x000000; } break; - case 48: /* refarg ::= ON DELETE refact */ + case 47: /* refarg ::= ON DELETE refact */ { yymsp[-2].minor.yy497.value = yymsp[0].minor.yy194; yymsp[-2].minor.yy497.mask = 0x0000ff; } break; - case 49: /* refarg ::= ON UPDATE refact */ + case 48: /* refarg ::= ON UPDATE refact */ { yymsp[-2].minor.yy497.value = yymsp[0].minor.yy194<<8; yymsp[-2].minor.yy497.mask = 0x00ff00; } break; - case 50: /* refact ::= SET NULL */ + case 49: /* refact ::= SET NULL */ { yymsp[-1].minor.yy194 = OE_SetNull; /* EV: R-33326-45252 */} break; - case 51: /* refact ::= SET DEFAULT */ + case 50: /* refact ::= SET DEFAULT */ { yymsp[-1].minor.yy194 = OE_SetDflt; /* EV: R-33326-45252 */} break; - case 52: /* refact ::= CASCADE */ + case 51: /* refact ::= CASCADE */ { yymsp[0].minor.yy194 = OE_Cascade; /* EV: R-33326-45252 */} break; - case 53: /* refact ::= RESTRICT */ + case 52: /* refact ::= RESTRICT */ { yymsp[0].minor.yy194 = OE_Restrict; /* EV: R-33326-45252 */} break; - case 54: /* refact ::= NO ACTION */ + case 53: /* refact ::= NO ACTION */ { yymsp[-1].minor.yy194 = OE_None; /* EV: R-33326-45252 */} break; - case 55: /* defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */ + case 54: /* defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */ {yymsp[-2].minor.yy194 = 0;} break; - case 56: /* defer_subclause ::= DEFERRABLE init_deferred_pred_opt */ - case 71: /* orconf ::= OR resolvetype */ yytestcase(yyruleno==71); - case 144: /* insert_cmd ::= INSERT orconf */ yytestcase(yyruleno==144); + case 55: /* defer_subclause ::= DEFERRABLE init_deferred_pred_opt */ + case 70: /* orconf ::= OR resolvetype */ yytestcase(yyruleno==70); + case 143: /* insert_cmd ::= INSERT orconf */ yytestcase(yyruleno==143); {yymsp[-1].minor.yy194 = yymsp[0].minor.yy194;} break; - case 58: /* init_deferred_pred_opt ::= INITIALLY DEFERRED */ - case 75: /* ifexists ::= IF EXISTS */ yytestcase(yyruleno==75); - case 186: /* between_op ::= NOT BETWEEN */ yytestcase(yyruleno==186); - case 189: /* in_op ::= NOT IN */ yytestcase(yyruleno==189); - case 215: /* collate ::= COLLATE ID|STRING */ yytestcase(yyruleno==215); + case 57: /* init_deferred_pred_opt ::= INITIALLY DEFERRED */ + case 74: /* ifexists ::= IF EXISTS */ yytestcase(yyruleno==74); + case 184: /* between_op ::= NOT BETWEEN */ yytestcase(yyruleno==184); + case 187: /* in_op ::= NOT IN */ yytestcase(yyruleno==187); + case 213: /* collate ::= COLLATE ID|STRING */ yytestcase(yyruleno==213); {yymsp[-1].minor.yy194 = 1;} break; - case 59: /* init_deferred_pred_opt ::= INITIALLY IMMEDIATE */ + case 58: /* init_deferred_pred_opt ::= INITIALLY IMMEDIATE */ {yymsp[-1].minor.yy194 = 0;} break; - case 61: /* tconscomma ::= COMMA */ + case 60: /* tconscomma ::= COMMA */ {pParse->constraintName.n = 0;} break; - case 63: /* tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf */ + case 62: /* tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf */ {sqlite3AddPrimaryKey(pParse,yymsp[-3].minor.yy148,yymsp[0].minor.yy194,yymsp[-2].minor.yy194,0);} break; - case 64: /* tcons ::= UNIQUE LP sortlist RP onconf */ + case 63: /* tcons ::= UNIQUE LP sortlist RP onconf */ {sqlite3CreateIndex(pParse,0,0,0,yymsp[-2].minor.yy148,yymsp[0].minor.yy194,0,0,0,0, SQLITE_IDXTYPE_UNIQUE);} break; - case 65: /* tcons ::= CHECK LP expr RP onconf */ + case 64: /* tcons ::= CHECK LP expr RP onconf */ {sqlite3AddCheckConstraint(pParse,yymsp[-2].minor.yy190.pExpr);} break; - case 66: /* tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt */ + case 65: /* tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt */ { sqlite3CreateForeignKey(pParse, yymsp[-6].minor.yy148, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy148, yymsp[-1].minor.yy194); sqlite3DeferForeignKey(pParse, yymsp[0].minor.yy194); } break; - case 68: /* onconf ::= */ - case 70: /* orconf ::= */ yytestcase(yyruleno==70); + case 67: /* onconf ::= */ + case 69: /* orconf ::= */ yytestcase(yyruleno==69); {yymsp[1].minor.yy194 = OE_Default;} break; - case 69: /* onconf ::= ON CONFLICT resolvetype */ + case 68: /* onconf ::= ON CONFLICT resolvetype */ {yymsp[-2].minor.yy194 = yymsp[0].minor.yy194;} break; - case 72: /* resolvetype ::= IGNORE */ + case 71: /* resolvetype ::= IGNORE */ {yymsp[0].minor.yy194 = OE_Ignore;} break; - case 73: /* resolvetype ::= REPLACE */ - case 145: /* insert_cmd ::= REPLACE */ yytestcase(yyruleno==145); + case 72: /* resolvetype ::= REPLACE */ + case 144: /* insert_cmd ::= REPLACE */ yytestcase(yyruleno==144); {yymsp[0].minor.yy194 = OE_Replace;} break; - case 74: /* cmd ::= DROP TABLE ifexists fullname */ + case 73: /* cmd ::= DROP TABLE ifexists fullname */ { sqlite3DropTable(pParse, yymsp[0].minor.yy185, 0, yymsp[-1].minor.yy194); } break; - case 77: /* cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select */ + case 76: /* cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select */ { sqlite3CreateView(pParse, &yymsp[-8].minor.yy0, &yymsp[-4].minor.yy0, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy148, yymsp[0].minor.yy243, yymsp[-7].minor.yy194, yymsp[-5].minor.yy194); } break; - case 78: /* cmd ::= DROP VIEW ifexists fullname */ + case 77: /* cmd ::= DROP VIEW ifexists fullname */ { sqlite3DropTable(pParse, yymsp[0].minor.yy185, 1, yymsp[-1].minor.yy194); } break; - case 79: /* cmd ::= select */ + case 78: /* cmd ::= select */ { SelectDest dest = {SRT_Output, 0, 0, 0, 0, 0}; sqlite3Select(pParse, yymsp[0].minor.yy243, &dest); sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy243); } break; - case 80: /* select ::= with selectnowith */ + case 79: /* select ::= with selectnowith */ { Select *p = yymsp[0].minor.yy243; if( p ){ @@ -137396,7 +139077,7 @@ static void yy_reduce( yymsp[-1].minor.yy243 = p; /*A-overwrites-W*/ } break; - case 81: /* selectnowith ::= selectnowith multiselect_op oneselect */ + case 80: /* selectnowith ::= selectnowith multiselect_op oneselect */ { Select *pRhs = yymsp[0].minor.yy243; Select *pLhs = yymsp[-2].minor.yy243; @@ -137420,14 +139101,14 @@ static void yy_reduce( yymsp[-2].minor.yy243 = pRhs; } break; - case 82: /* multiselect_op ::= UNION */ - case 84: /* multiselect_op ::= EXCEPT|INTERSECT */ yytestcase(yyruleno==84); + case 81: /* multiselect_op ::= UNION */ + case 83: /* multiselect_op ::= EXCEPT|INTERSECT */ yytestcase(yyruleno==83); {yymsp[0].minor.yy194 = yymsp[0].major; /*A-overwrites-OP*/} break; - case 83: /* multiselect_op ::= UNION ALL */ + case 82: /* multiselect_op ::= UNION ALL */ {yymsp[-1].minor.yy194 = TK_ALL;} break; - case 85: /* oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */ + case 84: /* oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */ { #if SELECTTRACE_ENABLED Token s = yymsp[-8].minor.yy0; /*A-overwrites-S*/ @@ -137459,12 +139140,12 @@ static void yy_reduce( #endif /* SELECTRACE_ENABLED */ } break; - case 86: /* values ::= VALUES LP nexprlist RP */ + case 85: /* values ::= VALUES LP nexprlist RP */ { yymsp[-3].minor.yy243 = sqlite3SelectNew(pParse,yymsp[-1].minor.yy148,0,0,0,0,0,SF_Values,0,0); } break; - case 87: /* values ::= values COMMA LP exprlist RP */ + case 86: /* values ::= values COMMA LP exprlist RP */ { Select *pRight, *pLeft = yymsp[-4].minor.yy243; pRight = sqlite3SelectNew(pParse,yymsp[-1].minor.yy148,0,0,0,0,0,SF_Values|SF_MultiValue,0,0); @@ -137478,34 +139159,34 @@ static void yy_reduce( } } break; - case 88: /* distinct ::= DISTINCT */ + case 87: /* distinct ::= DISTINCT */ {yymsp[0].minor.yy194 = SF_Distinct;} break; - case 89: /* distinct ::= ALL */ + case 88: /* distinct ::= ALL */ {yymsp[0].minor.yy194 = SF_All;} break; - case 91: /* sclp ::= */ - case 119: /* orderby_opt ::= */ yytestcase(yyruleno==119); - case 126: /* groupby_opt ::= */ yytestcase(yyruleno==126); - case 202: /* exprlist ::= */ yytestcase(yyruleno==202); - case 205: /* paren_exprlist ::= */ yytestcase(yyruleno==205); - case 210: /* eidlist_opt ::= */ yytestcase(yyruleno==210); + case 90: /* sclp ::= */ + case 118: /* orderby_opt ::= */ yytestcase(yyruleno==118); + case 125: /* groupby_opt ::= */ yytestcase(yyruleno==125); + case 200: /* exprlist ::= */ yytestcase(yyruleno==200); + case 203: /* paren_exprlist ::= */ yytestcase(yyruleno==203); + case 208: /* eidlist_opt ::= */ yytestcase(yyruleno==208); {yymsp[1].minor.yy148 = 0;} break; - case 92: /* selcollist ::= sclp expr as */ + case 91: /* selcollist ::= sclp expr as */ { yymsp[-2].minor.yy148 = sqlite3ExprListAppend(pParse, yymsp[-2].minor.yy148, yymsp[-1].minor.yy190.pExpr); if( yymsp[0].minor.yy0.n>0 ) sqlite3ExprListSetName(pParse, yymsp[-2].minor.yy148, &yymsp[0].minor.yy0, 1); sqlite3ExprListSetSpan(pParse,yymsp[-2].minor.yy148,&yymsp[-1].minor.yy190); } break; - case 93: /* selcollist ::= sclp STAR */ + case 92: /* selcollist ::= sclp STAR */ { Expr *p = sqlite3Expr(pParse->db, TK_ASTERISK, 0); yymsp[-1].minor.yy148 = sqlite3ExprListAppend(pParse, yymsp[-1].minor.yy148, p); } break; - case 94: /* selcollist ::= sclp nm DOT STAR */ + case 93: /* selcollist ::= sclp nm DOT STAR */ { Expr *pRight = sqlite3PExpr(pParse, TK_ASTERISK, 0, 0); Expr *pLeft = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[-2].minor.yy0, 1); @@ -137513,47 +139194,47 @@ static void yy_reduce( yymsp[-3].minor.yy148 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy148, pDot); } break; - case 95: /* as ::= AS nm */ - case 106: /* dbnm ::= DOT nm */ yytestcase(yyruleno==106); - case 224: /* plus_num ::= PLUS INTEGER|FLOAT */ yytestcase(yyruleno==224); - case 225: /* minus_num ::= MINUS INTEGER|FLOAT */ yytestcase(yyruleno==225); + case 94: /* as ::= AS nm */ + case 105: /* dbnm ::= DOT nm */ yytestcase(yyruleno==105); + case 222: /* plus_num ::= PLUS INTEGER|FLOAT */ yytestcase(yyruleno==222); + case 223: /* minus_num ::= MINUS INTEGER|FLOAT */ yytestcase(yyruleno==223); {yymsp[-1].minor.yy0 = yymsp[0].minor.yy0;} break; - case 97: /* from ::= */ + case 96: /* from ::= */ {yymsp[1].minor.yy185 = sqlite3DbMallocZero(pParse->db, sizeof(*yymsp[1].minor.yy185));} break; - case 98: /* from ::= FROM seltablist */ + case 97: /* from ::= FROM seltablist */ { yymsp[-1].minor.yy185 = yymsp[0].minor.yy185; sqlite3SrcListShiftJoinType(yymsp[-1].minor.yy185); } break; - case 99: /* stl_prefix ::= seltablist joinop */ + case 98: /* stl_prefix ::= seltablist joinop */ { if( ALWAYS(yymsp[-1].minor.yy185 && yymsp[-1].minor.yy185->nSrc>0) ) yymsp[-1].minor.yy185->a[yymsp[-1].minor.yy185->nSrc-1].fg.jointype = (u8)yymsp[0].minor.yy194; } break; - case 100: /* stl_prefix ::= */ + case 99: /* stl_prefix ::= */ {yymsp[1].minor.yy185 = 0;} break; - case 101: /* seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt */ + case 100: /* seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt */ { yymsp[-6].minor.yy185 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy185,&yymsp[-5].minor.yy0,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,0,yymsp[-1].minor.yy72,yymsp[0].minor.yy254); sqlite3SrcListIndexedBy(pParse, yymsp[-6].minor.yy185, &yymsp[-2].minor.yy0); } break; - case 102: /* seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_opt using_opt */ + case 101: /* seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_opt using_opt */ { yymsp[-8].minor.yy185 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-8].minor.yy185,&yymsp[-7].minor.yy0,&yymsp[-6].minor.yy0,&yymsp[-2].minor.yy0,0,yymsp[-1].minor.yy72,yymsp[0].minor.yy254); sqlite3SrcListFuncArgs(pParse, yymsp[-8].minor.yy185, yymsp[-4].minor.yy148); } break; - case 103: /* seltablist ::= stl_prefix LP select RP as on_opt using_opt */ + case 102: /* seltablist ::= stl_prefix LP select RP as on_opt using_opt */ { yymsp[-6].minor.yy185 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy185,0,0,&yymsp[-2].minor.yy0,yymsp[-4].minor.yy243,yymsp[-1].minor.yy72,yymsp[0].minor.yy254); } break; - case 104: /* seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt */ + case 103: /* seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt */ { if( yymsp[-6].minor.yy185==0 && yymsp[-2].minor.yy0.n==0 && yymsp[-1].minor.yy72==0 && yymsp[0].minor.yy254==0 ){ yymsp[-6].minor.yy185 = yymsp[-4].minor.yy185; @@ -137577,96 +139258,96 @@ static void yy_reduce( } } break; - case 105: /* dbnm ::= */ - case 114: /* indexed_opt ::= */ yytestcase(yyruleno==114); + case 104: /* dbnm ::= */ + case 113: /* indexed_opt ::= */ yytestcase(yyruleno==113); {yymsp[1].minor.yy0.z=0; yymsp[1].minor.yy0.n=0;} break; - case 107: /* fullname ::= nm dbnm */ + case 106: /* fullname ::= nm dbnm */ {yymsp[-1].minor.yy185 = sqlite3SrcListAppend(pParse->db,0,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/} break; - case 108: /* joinop ::= COMMA|JOIN */ + case 107: /* joinop ::= COMMA|JOIN */ { yymsp[0].minor.yy194 = JT_INNER; } break; - case 109: /* joinop ::= JOIN_KW JOIN */ + case 108: /* joinop ::= JOIN_KW JOIN */ {yymsp[-1].minor.yy194 = sqlite3JoinType(pParse,&yymsp[-1].minor.yy0,0,0); /*X-overwrites-A*/} break; - case 110: /* joinop ::= JOIN_KW nm JOIN */ + case 109: /* joinop ::= JOIN_KW nm JOIN */ {yymsp[-2].minor.yy194 = sqlite3JoinType(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,0); /*X-overwrites-A*/} break; - case 111: /* joinop ::= JOIN_KW nm nm JOIN */ + case 110: /* joinop ::= JOIN_KW nm nm JOIN */ {yymsp[-3].minor.yy194 = sqlite3JoinType(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0);/*X-overwrites-A*/} break; - case 112: /* on_opt ::= ON expr */ - case 129: /* having_opt ::= HAVING expr */ yytestcase(yyruleno==129); - case 136: /* where_opt ::= WHERE expr */ yytestcase(yyruleno==136); - case 198: /* case_else ::= ELSE expr */ yytestcase(yyruleno==198); + case 111: /* on_opt ::= ON expr */ + case 128: /* having_opt ::= HAVING expr */ yytestcase(yyruleno==128); + case 135: /* where_opt ::= WHERE expr */ yytestcase(yyruleno==135); + case 196: /* case_else ::= ELSE expr */ yytestcase(yyruleno==196); {yymsp[-1].minor.yy72 = yymsp[0].minor.yy190.pExpr;} break; - case 113: /* on_opt ::= */ - case 128: /* having_opt ::= */ yytestcase(yyruleno==128); - case 135: /* where_opt ::= */ yytestcase(yyruleno==135); - case 199: /* case_else ::= */ yytestcase(yyruleno==199); - case 201: /* case_operand ::= */ yytestcase(yyruleno==201); + case 112: /* on_opt ::= */ + case 127: /* having_opt ::= */ yytestcase(yyruleno==127); + case 134: /* where_opt ::= */ yytestcase(yyruleno==134); + case 197: /* case_else ::= */ yytestcase(yyruleno==197); + case 199: /* case_operand ::= */ yytestcase(yyruleno==199); {yymsp[1].minor.yy72 = 0;} break; - case 115: /* indexed_opt ::= INDEXED BY nm */ + case 114: /* indexed_opt ::= INDEXED BY nm */ {yymsp[-2].minor.yy0 = yymsp[0].minor.yy0;} break; - case 116: /* indexed_opt ::= NOT INDEXED */ + case 115: /* indexed_opt ::= NOT INDEXED */ {yymsp[-1].minor.yy0.z=0; yymsp[-1].minor.yy0.n=1;} break; - case 117: /* using_opt ::= USING LP idlist RP */ + case 116: /* using_opt ::= USING LP idlist RP */ {yymsp[-3].minor.yy254 = yymsp[-1].minor.yy254;} break; - case 118: /* using_opt ::= */ - case 146: /* idlist_opt ::= */ yytestcase(yyruleno==146); + case 117: /* using_opt ::= */ + case 145: /* idlist_opt ::= */ yytestcase(yyruleno==145); {yymsp[1].minor.yy254 = 0;} break; - case 120: /* orderby_opt ::= ORDER BY sortlist */ - case 127: /* groupby_opt ::= GROUP BY nexprlist */ yytestcase(yyruleno==127); + case 119: /* orderby_opt ::= ORDER BY sortlist */ + case 126: /* groupby_opt ::= GROUP BY nexprlist */ yytestcase(yyruleno==126); {yymsp[-2].minor.yy148 = yymsp[0].minor.yy148;} break; - case 121: /* sortlist ::= sortlist COMMA expr sortorder */ + case 120: /* sortlist ::= sortlist COMMA expr sortorder */ { yymsp[-3].minor.yy148 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy148,yymsp[-1].minor.yy190.pExpr); sqlite3ExprListSetSortOrder(yymsp[-3].minor.yy148,yymsp[0].minor.yy194); } break; - case 122: /* sortlist ::= expr sortorder */ + case 121: /* sortlist ::= expr sortorder */ { yymsp[-1].minor.yy148 = sqlite3ExprListAppend(pParse,0,yymsp[-1].minor.yy190.pExpr); /*A-overwrites-Y*/ sqlite3ExprListSetSortOrder(yymsp[-1].minor.yy148,yymsp[0].minor.yy194); } break; - case 123: /* sortorder ::= ASC */ + case 122: /* sortorder ::= ASC */ {yymsp[0].minor.yy194 = SQLITE_SO_ASC;} break; - case 124: /* sortorder ::= DESC */ + case 123: /* sortorder ::= DESC */ {yymsp[0].minor.yy194 = SQLITE_SO_DESC;} break; - case 125: /* sortorder ::= */ + case 124: /* sortorder ::= */ {yymsp[1].minor.yy194 = SQLITE_SO_UNDEFINED;} break; - case 130: /* limit_opt ::= */ + case 129: /* limit_opt ::= */ {yymsp[1].minor.yy354.pLimit = 0; yymsp[1].minor.yy354.pOffset = 0;} break; - case 131: /* limit_opt ::= LIMIT expr */ + case 130: /* limit_opt ::= LIMIT expr */ {yymsp[-1].minor.yy354.pLimit = yymsp[0].minor.yy190.pExpr; yymsp[-1].minor.yy354.pOffset = 0;} break; - case 132: /* limit_opt ::= LIMIT expr OFFSET expr */ + case 131: /* limit_opt ::= LIMIT expr OFFSET expr */ {yymsp[-3].minor.yy354.pLimit = yymsp[-2].minor.yy190.pExpr; yymsp[-3].minor.yy354.pOffset = yymsp[0].minor.yy190.pExpr;} break; - case 133: /* limit_opt ::= LIMIT expr COMMA expr */ + case 132: /* limit_opt ::= LIMIT expr COMMA expr */ {yymsp[-3].minor.yy354.pOffset = yymsp[-2].minor.yy190.pExpr; yymsp[-3].minor.yy354.pLimit = yymsp[0].minor.yy190.pExpr;} break; - case 134: /* cmd ::= with DELETE FROM fullname indexed_opt where_opt */ + case 133: /* cmd ::= with DELETE FROM fullname indexed_opt where_opt */ { sqlite3WithPush(pParse, yymsp[-5].minor.yy285, 1); sqlite3SrcListIndexedBy(pParse, yymsp[-2].minor.yy185, &yymsp[-1].minor.yy0); sqlite3DeleteFrom(pParse,yymsp[-2].minor.yy185,yymsp[0].minor.yy72); } break; - case 137: /* cmd ::= with UPDATE orconf fullname indexed_opt SET setlist where_opt */ + case 136: /* cmd ::= with UPDATE orconf fullname indexed_opt SET setlist where_opt */ { sqlite3WithPush(pParse, yymsp[-7].minor.yy285, 1); sqlite3SrcListIndexedBy(pParse, yymsp[-4].minor.yy185, &yymsp[-3].minor.yy0); @@ -137674,63 +139355,58 @@ static void yy_reduce( sqlite3Update(pParse,yymsp[-4].minor.yy185,yymsp[-1].minor.yy148,yymsp[0].minor.yy72,yymsp[-5].minor.yy194); } break; - case 138: /* setlist ::= setlist COMMA nm EQ expr */ + case 137: /* setlist ::= setlist COMMA nm EQ expr */ { yymsp[-4].minor.yy148 = sqlite3ExprListAppend(pParse, yymsp[-4].minor.yy148, yymsp[0].minor.yy190.pExpr); sqlite3ExprListSetName(pParse, yymsp[-4].minor.yy148, &yymsp[-2].minor.yy0, 1); } break; - case 139: /* setlist ::= setlist COMMA LP idlist RP EQ expr */ + case 138: /* setlist ::= setlist COMMA LP idlist RP EQ expr */ { yymsp[-6].minor.yy148 = sqlite3ExprListAppendVector(pParse, yymsp[-6].minor.yy148, yymsp[-3].minor.yy254, yymsp[0].minor.yy190.pExpr); } break; - case 140: /* setlist ::= nm EQ expr */ + case 139: /* setlist ::= nm EQ expr */ { yylhsminor.yy148 = sqlite3ExprListAppend(pParse, 0, yymsp[0].minor.yy190.pExpr); sqlite3ExprListSetName(pParse, yylhsminor.yy148, &yymsp[-2].minor.yy0, 1); } yymsp[-2].minor.yy148 = yylhsminor.yy148; break; - case 141: /* setlist ::= LP idlist RP EQ expr */ + case 140: /* setlist ::= LP idlist RP EQ expr */ { yymsp[-4].minor.yy148 = sqlite3ExprListAppendVector(pParse, 0, yymsp[-3].minor.yy254, yymsp[0].minor.yy190.pExpr); } break; - case 142: /* cmd ::= with insert_cmd INTO fullname idlist_opt select */ + case 141: /* cmd ::= with insert_cmd INTO fullname idlist_opt select */ { sqlite3WithPush(pParse, yymsp[-5].minor.yy285, 1); sqlite3Insert(pParse, yymsp[-2].minor.yy185, yymsp[0].minor.yy243, yymsp[-1].minor.yy254, yymsp[-4].minor.yy194); } break; - case 143: /* cmd ::= with insert_cmd INTO fullname idlist_opt DEFAULT VALUES */ + case 142: /* cmd ::= with insert_cmd INTO fullname idlist_opt DEFAULT VALUES */ { sqlite3WithPush(pParse, yymsp[-6].minor.yy285, 1); sqlite3Insert(pParse, yymsp[-3].minor.yy185, 0, yymsp[-2].minor.yy254, yymsp[-5].minor.yy194); } break; - case 147: /* idlist_opt ::= LP idlist RP */ + case 146: /* idlist_opt ::= LP idlist RP */ {yymsp[-2].minor.yy254 = yymsp[-1].minor.yy254;} break; - case 148: /* idlist ::= idlist COMMA nm */ + case 147: /* idlist ::= idlist COMMA nm */ {yymsp[-2].minor.yy254 = sqlite3IdListAppend(pParse->db,yymsp[-2].minor.yy254,&yymsp[0].minor.yy0);} break; - case 149: /* idlist ::= nm */ + case 148: /* idlist ::= nm */ {yymsp[0].minor.yy254 = sqlite3IdListAppend(pParse->db,0,&yymsp[0].minor.yy0); /*A-overwrites-Y*/} break; - case 150: /* expr ::= LP expr RP */ + case 149: /* expr ::= LP expr RP */ {spanSet(&yymsp[-2].minor.yy190,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-B*/ yymsp[-2].minor.yy190.pExpr = yymsp[-1].minor.yy190.pExpr;} break; - case 151: /* term ::= NULL */ - case 156: /* term ::= FLOAT|BLOB */ yytestcase(yyruleno==156); - case 157: /* term ::= STRING */ yytestcase(yyruleno==157); -{spanExpr(&yymsp[0].minor.yy190,pParse,yymsp[0].major,yymsp[0].minor.yy0);/*A-overwrites-X*/} - break; - case 152: /* expr ::= ID|INDEXED */ - case 153: /* expr ::= JOIN_KW */ yytestcase(yyruleno==153); + case 150: /* expr ::= ID|INDEXED */ + case 151: /* expr ::= JOIN_KW */ yytestcase(yyruleno==151); {spanExpr(&yymsp[0].minor.yy190,pParse,TK_ID,yymsp[0].minor.yy0); /*A-overwrites-X*/} break; - case 154: /* expr ::= nm DOT nm */ + case 152: /* expr ::= nm DOT nm */ { Expr *temp1 = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[-2].minor.yy0, 1); Expr *temp2 = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[0].minor.yy0, 1); @@ -137738,7 +139414,7 @@ static void yy_reduce( yymsp[-2].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_DOT, temp1, temp2); } break; - case 155: /* expr ::= nm DOT nm DOT nm */ + case 153: /* expr ::= nm DOT nm DOT nm */ { Expr *temp1 = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[-4].minor.yy0, 1); Expr *temp2 = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[-2].minor.yy0, 1); @@ -137748,16 +139424,19 @@ static void yy_reduce( yymsp[-4].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_DOT, temp1, temp4); } break; - case 158: /* term ::= INTEGER */ + case 154: /* term ::= NULL|FLOAT|BLOB */ + case 155: /* term ::= STRING */ yytestcase(yyruleno==155); +{spanExpr(&yymsp[0].minor.yy190,pParse,yymsp[0].major,yymsp[0].minor.yy0); /*A-overwrites-X*/} + break; + case 156: /* term ::= INTEGER */ { yylhsminor.yy190.pExpr = sqlite3ExprAlloc(pParse->db, TK_INTEGER, &yymsp[0].minor.yy0, 1); yylhsminor.yy190.zStart = yymsp[0].minor.yy0.z; yylhsminor.yy190.zEnd = yymsp[0].minor.yy0.z + yymsp[0].minor.yy0.n; - if( yylhsminor.yy190.pExpr ) yylhsminor.yy190.pExpr->flags |= EP_Leaf|EP_Resolved; } yymsp[0].minor.yy190 = yylhsminor.yy190; break; - case 159: /* expr ::= VARIABLE */ + case 157: /* expr ::= VARIABLE */ { if( !(yymsp[0].minor.yy0.z[0]=='#' && sqlite3Isdigit(yymsp[0].minor.yy0.z[1])) ){ u32 n = yymsp[0].minor.yy0.n; @@ -137780,20 +139459,20 @@ static void yy_reduce( } } break; - case 160: /* expr ::= expr COLLATE ID|STRING */ + case 158: /* expr ::= expr COLLATE ID|STRING */ { yymsp[-2].minor.yy190.pExpr = sqlite3ExprAddCollateToken(pParse, yymsp[-2].minor.yy190.pExpr, &yymsp[0].minor.yy0, 1); yymsp[-2].minor.yy190.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; } break; - case 161: /* expr ::= CAST LP expr AS typetoken RP */ + case 159: /* expr ::= CAST LP expr AS typetoken RP */ { spanSet(&yymsp[-5].minor.yy190,&yymsp[-5].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/ yymsp[-5].minor.yy190.pExpr = sqlite3ExprAlloc(pParse->db, TK_CAST, &yymsp[-1].minor.yy0, 1); sqlite3ExprAttachSubtrees(pParse->db, yymsp[-5].minor.yy190.pExpr, yymsp[-3].minor.yy190.pExpr, 0); } break; - case 162: /* expr ::= ID|INDEXED LP distinct exprlist RP */ + case 160: /* expr ::= ID|INDEXED LP distinct exprlist RP */ { if( yymsp[-1].minor.yy148 && yymsp[-1].minor.yy148->nExpr>pParse->db->aLimit[SQLITE_LIMIT_FUNCTION_ARG] ){ sqlite3ErrorMsg(pParse, "too many arguments on function %T", &yymsp[-4].minor.yy0); @@ -137806,21 +139485,21 @@ static void yy_reduce( } yymsp[-4].minor.yy190 = yylhsminor.yy190; break; - case 163: /* expr ::= ID|INDEXED LP STAR RP */ + case 161: /* expr ::= ID|INDEXED LP STAR RP */ { yylhsminor.yy190.pExpr = sqlite3ExprFunction(pParse, 0, &yymsp[-3].minor.yy0); spanSet(&yylhsminor.yy190,&yymsp[-3].minor.yy0,&yymsp[0].minor.yy0); } yymsp[-3].minor.yy190 = yylhsminor.yy190; break; - case 164: /* term ::= CTIME_KW */ + case 162: /* term ::= CTIME_KW */ { yylhsminor.yy190.pExpr = sqlite3ExprFunction(pParse, 0, &yymsp[0].minor.yy0); spanSet(&yylhsminor.yy190, &yymsp[0].minor.yy0, &yymsp[0].minor.yy0); } yymsp[0].minor.yy190 = yylhsminor.yy190; break; - case 165: /* expr ::= LP nexprlist COMMA expr RP */ + case 163: /* expr ::= LP nexprlist COMMA expr RP */ { ExprList *pList = sqlite3ExprListAppend(pParse, yymsp[-3].minor.yy148, yymsp[-1].minor.yy190.pExpr); yylhsminor.yy190.pExpr = sqlite3PExpr(pParse, TK_VECTOR, 0, 0); @@ -137833,20 +139512,20 @@ static void yy_reduce( } yymsp[-4].minor.yy190 = yylhsminor.yy190; break; - case 166: /* expr ::= expr AND expr */ - case 167: /* expr ::= expr OR expr */ yytestcase(yyruleno==167); - case 168: /* expr ::= expr LT|GT|GE|LE expr */ yytestcase(yyruleno==168); - case 169: /* expr ::= expr EQ|NE expr */ yytestcase(yyruleno==169); - case 170: /* expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ yytestcase(yyruleno==170); - case 171: /* expr ::= expr PLUS|MINUS expr */ yytestcase(yyruleno==171); - case 172: /* expr ::= expr STAR|SLASH|REM expr */ yytestcase(yyruleno==172); - case 173: /* expr ::= expr CONCAT expr */ yytestcase(yyruleno==173); + case 164: /* expr ::= expr AND expr */ + case 165: /* expr ::= expr OR expr */ yytestcase(yyruleno==165); + case 166: /* expr ::= expr LT|GT|GE|LE expr */ yytestcase(yyruleno==166); + case 167: /* expr ::= expr EQ|NE expr */ yytestcase(yyruleno==167); + case 168: /* expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ yytestcase(yyruleno==168); + case 169: /* expr ::= expr PLUS|MINUS expr */ yytestcase(yyruleno==169); + case 170: /* expr ::= expr STAR|SLASH|REM expr */ yytestcase(yyruleno==170); + case 171: /* expr ::= expr CONCAT expr */ yytestcase(yyruleno==171); {spanBinaryExpr(pParse,yymsp[-1].major,&yymsp[-2].minor.yy190,&yymsp[0].minor.yy190);} break; - case 174: /* likeop ::= NOT LIKE_KW|MATCH */ + case 172: /* likeop ::= NOT LIKE_KW|MATCH */ {yymsp[-1].minor.yy0=yymsp[0].minor.yy0; yymsp[-1].minor.yy0.n|=0x80000000; /*yymsp[-1].minor.yy0-overwrite-yymsp[0].minor.yy0*/} break; - case 175: /* expr ::= expr likeop expr */ + case 173: /* expr ::= expr likeop expr */ { ExprList *pList; int bNot = yymsp[-1].minor.yy0.n & 0x80000000; @@ -137859,7 +139538,7 @@ static void yy_reduce( if( yymsp[-2].minor.yy190.pExpr ) yymsp[-2].minor.yy190.pExpr->flags |= EP_InfixFunc; } break; - case 176: /* expr ::= expr likeop expr ESCAPE expr */ + case 174: /* expr ::= expr likeop expr ESCAPE expr */ { ExprList *pList; int bNot = yymsp[-3].minor.yy0.n & 0x80000000; @@ -137873,39 +139552,39 @@ static void yy_reduce( if( yymsp[-4].minor.yy190.pExpr ) yymsp[-4].minor.yy190.pExpr->flags |= EP_InfixFunc; } break; - case 177: /* expr ::= expr ISNULL|NOTNULL */ + case 175: /* expr ::= expr ISNULL|NOTNULL */ {spanUnaryPostfix(pParse,yymsp[0].major,&yymsp[-1].minor.yy190,&yymsp[0].minor.yy0);} break; - case 178: /* expr ::= expr NOT NULL */ + case 176: /* expr ::= expr NOT NULL */ {spanUnaryPostfix(pParse,TK_NOTNULL,&yymsp[-2].minor.yy190,&yymsp[0].minor.yy0);} break; - case 179: /* expr ::= expr IS expr */ + case 177: /* expr ::= expr IS expr */ { spanBinaryExpr(pParse,TK_IS,&yymsp[-2].minor.yy190,&yymsp[0].minor.yy190); binaryToUnaryIfNull(pParse, yymsp[0].minor.yy190.pExpr, yymsp[-2].minor.yy190.pExpr, TK_ISNULL); } break; - case 180: /* expr ::= expr IS NOT expr */ + case 178: /* expr ::= expr IS NOT expr */ { spanBinaryExpr(pParse,TK_ISNOT,&yymsp[-3].minor.yy190,&yymsp[0].minor.yy190); binaryToUnaryIfNull(pParse, yymsp[0].minor.yy190.pExpr, yymsp[-3].minor.yy190.pExpr, TK_NOTNULL); } break; - case 181: /* expr ::= NOT expr */ - case 182: /* expr ::= BITNOT expr */ yytestcase(yyruleno==182); + case 179: /* expr ::= NOT expr */ + case 180: /* expr ::= BITNOT expr */ yytestcase(yyruleno==180); {spanUnaryPrefix(&yymsp[-1].minor.yy190,pParse,yymsp[-1].major,&yymsp[0].minor.yy190,&yymsp[-1].minor.yy0);/*A-overwrites-B*/} break; - case 183: /* expr ::= MINUS expr */ + case 181: /* expr ::= MINUS expr */ {spanUnaryPrefix(&yymsp[-1].minor.yy190,pParse,TK_UMINUS,&yymsp[0].minor.yy190,&yymsp[-1].minor.yy0);/*A-overwrites-B*/} break; - case 184: /* expr ::= PLUS expr */ + case 182: /* expr ::= PLUS expr */ {spanUnaryPrefix(&yymsp[-1].minor.yy190,pParse,TK_UPLUS,&yymsp[0].minor.yy190,&yymsp[-1].minor.yy0);/*A-overwrites-B*/} break; - case 185: /* between_op ::= BETWEEN */ - case 188: /* in_op ::= IN */ yytestcase(yyruleno==188); + case 183: /* between_op ::= BETWEEN */ + case 186: /* in_op ::= IN */ yytestcase(yyruleno==186); {yymsp[0].minor.yy194 = 0;} break; - case 187: /* expr ::= expr between_op expr AND expr */ + case 185: /* expr ::= expr between_op expr AND expr */ { ExprList *pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy190.pExpr); pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy190.pExpr); @@ -137919,7 +139598,7 @@ static void yy_reduce( yymsp[-4].minor.yy190.zEnd = yymsp[0].minor.yy190.zEnd; } break; - case 190: /* expr ::= expr in_op LP exprlist RP */ + case 188: /* expr ::= expr in_op LP exprlist RP */ { if( yymsp[-1].minor.yy148==0 ){ /* Expressions of the form @@ -137972,14 +139651,14 @@ static void yy_reduce( yymsp[-4].minor.yy190.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; } break; - case 191: /* expr ::= LP select RP */ + case 189: /* expr ::= LP select RP */ { spanSet(&yymsp[-2].minor.yy190,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-B*/ yymsp[-2].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_SELECT, 0, 0); sqlite3PExprAddSelect(pParse, yymsp[-2].minor.yy190.pExpr, yymsp[-1].minor.yy243); } break; - case 192: /* expr ::= expr in_op LP select RP */ + case 190: /* expr ::= expr in_op LP select RP */ { yymsp[-4].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy190.pExpr, 0); sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy190.pExpr, yymsp[-1].minor.yy243); @@ -137987,7 +139666,7 @@ static void yy_reduce( yymsp[-4].minor.yy190.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; } break; - case 193: /* expr ::= expr in_op nm dbnm paren_exprlist */ + case 191: /* expr ::= expr in_op nm dbnm paren_exprlist */ { SrcList *pSrc = sqlite3SrcListAppend(pParse->db, 0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0); Select *pSelect = sqlite3SelectNew(pParse, 0,pSrc,0,0,0,0,0,0,0); @@ -137998,7 +139677,7 @@ static void yy_reduce( yymsp[-4].minor.yy190.zEnd = yymsp[-1].minor.yy0.z ? &yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n] : &yymsp[-2].minor.yy0.z[yymsp[-2].minor.yy0.n]; } break; - case 194: /* expr ::= EXISTS LP select RP */ + case 192: /* expr ::= EXISTS LP select RP */ { Expr *p; spanSet(&yymsp[-3].minor.yy190,&yymsp[-3].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-B*/ @@ -138006,7 +139685,7 @@ static void yy_reduce( sqlite3PExprAddSelect(pParse, p, yymsp[-1].minor.yy243); } break; - case 195: /* expr ::= CASE case_operand case_exprlist case_else END */ + case 193: /* expr ::= CASE case_operand case_exprlist case_else END */ { spanSet(&yymsp[-4].minor.yy190,&yymsp[-4].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-C*/ yymsp[-4].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_CASE, yymsp[-3].minor.yy72, 0); @@ -138019,80 +139698,80 @@ static void yy_reduce( } } break; - case 196: /* case_exprlist ::= case_exprlist WHEN expr THEN expr */ + case 194: /* case_exprlist ::= case_exprlist WHEN expr THEN expr */ { yymsp[-4].minor.yy148 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy148, yymsp[-2].minor.yy190.pExpr); yymsp[-4].minor.yy148 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy148, yymsp[0].minor.yy190.pExpr); } break; - case 197: /* case_exprlist ::= WHEN expr THEN expr */ + case 195: /* case_exprlist ::= WHEN expr THEN expr */ { yymsp[-3].minor.yy148 = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy190.pExpr); yymsp[-3].minor.yy148 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy148, yymsp[0].minor.yy190.pExpr); } break; - case 200: /* case_operand ::= expr */ + case 198: /* case_operand ::= expr */ {yymsp[0].minor.yy72 = yymsp[0].minor.yy190.pExpr; /*A-overwrites-X*/} break; - case 203: /* nexprlist ::= nexprlist COMMA expr */ + case 201: /* nexprlist ::= nexprlist COMMA expr */ {yymsp[-2].minor.yy148 = sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy148,yymsp[0].minor.yy190.pExpr);} break; - case 204: /* nexprlist ::= expr */ + case 202: /* nexprlist ::= expr */ {yymsp[0].minor.yy148 = sqlite3ExprListAppend(pParse,0,yymsp[0].minor.yy190.pExpr); /*A-overwrites-Y*/} break; - case 206: /* paren_exprlist ::= LP exprlist RP */ - case 211: /* eidlist_opt ::= LP eidlist RP */ yytestcase(yyruleno==211); + case 204: /* paren_exprlist ::= LP exprlist RP */ + case 209: /* eidlist_opt ::= LP eidlist RP */ yytestcase(yyruleno==209); {yymsp[-2].minor.yy148 = yymsp[-1].minor.yy148;} break; - case 207: /* cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */ + case 205: /* cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */ { sqlite3CreateIndex(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0, sqlite3SrcListAppend(pParse->db,0,&yymsp[-4].minor.yy0,0), yymsp[-2].minor.yy148, yymsp[-10].minor.yy194, &yymsp[-11].minor.yy0, yymsp[0].minor.yy72, SQLITE_SO_ASC, yymsp[-8].minor.yy194, SQLITE_IDXTYPE_APPDEF); } break; - case 208: /* uniqueflag ::= UNIQUE */ - case 249: /* raisetype ::= ABORT */ yytestcase(yyruleno==249); + case 206: /* uniqueflag ::= UNIQUE */ + case 246: /* raisetype ::= ABORT */ yytestcase(yyruleno==246); {yymsp[0].minor.yy194 = OE_Abort;} break; - case 209: /* uniqueflag ::= */ + case 207: /* uniqueflag ::= */ {yymsp[1].minor.yy194 = OE_None;} break; - case 212: /* eidlist ::= eidlist COMMA nm collate sortorder */ + case 210: /* eidlist ::= eidlist COMMA nm collate sortorder */ { yymsp[-4].minor.yy148 = parserAddExprIdListTerm(pParse, yymsp[-4].minor.yy148, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy194, yymsp[0].minor.yy194); } break; - case 213: /* eidlist ::= nm collate sortorder */ + case 211: /* eidlist ::= nm collate sortorder */ { yymsp[-2].minor.yy148 = parserAddExprIdListTerm(pParse, 0, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy194, yymsp[0].minor.yy194); /*A-overwrites-Y*/ } break; - case 216: /* cmd ::= DROP INDEX ifexists fullname */ + case 214: /* cmd ::= DROP INDEX ifexists fullname */ {sqlite3DropIndex(pParse, yymsp[0].minor.yy185, yymsp[-1].minor.yy194);} break; - case 217: /* cmd ::= VACUUM */ + case 215: /* cmd ::= VACUUM */ {sqlite3Vacuum(pParse,0);} break; - case 218: /* cmd ::= VACUUM nm */ + case 216: /* cmd ::= VACUUM nm */ {sqlite3Vacuum(pParse,&yymsp[0].minor.yy0);} break; - case 219: /* cmd ::= PRAGMA nm dbnm */ + case 217: /* cmd ::= PRAGMA nm dbnm */ {sqlite3Pragma(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,0,0);} break; - case 220: /* cmd ::= PRAGMA nm dbnm EQ nmnum */ + case 218: /* cmd ::= PRAGMA nm dbnm EQ nmnum */ {sqlite3Pragma(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0,0);} break; - case 221: /* cmd ::= PRAGMA nm dbnm LP nmnum RP */ + case 219: /* cmd ::= PRAGMA nm dbnm LP nmnum RP */ {sqlite3Pragma(pParse,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,&yymsp[-1].minor.yy0,0);} break; - case 222: /* cmd ::= PRAGMA nm dbnm EQ minus_num */ + case 220: /* cmd ::= PRAGMA nm dbnm EQ minus_num */ {sqlite3Pragma(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0,1);} break; - case 223: /* cmd ::= PRAGMA nm dbnm LP minus_num RP */ + case 221: /* cmd ::= PRAGMA nm dbnm LP minus_num RP */ {sqlite3Pragma(pParse,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,&yymsp[-1].minor.yy0,1);} break; - case 226: /* cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */ + case 224: /* cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */ { Token all; all.z = yymsp[-3].minor.yy0.z; @@ -138100,53 +139779,50 @@ static void yy_reduce( sqlite3FinishTrigger(pParse, yymsp[-1].minor.yy145, &all); } break; - case 227: /* trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */ + case 225: /* trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */ { sqlite3BeginTrigger(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0, yymsp[-5].minor.yy194, yymsp[-4].minor.yy332.a, yymsp[-4].minor.yy332.b, yymsp[-2].minor.yy185, yymsp[0].minor.yy72, yymsp[-10].minor.yy194, yymsp[-8].minor.yy194); yymsp[-10].minor.yy0 = (yymsp[-6].minor.yy0.n==0?yymsp[-7].minor.yy0:yymsp[-6].minor.yy0); /*A-overwrites-T*/ } break; - case 228: /* trigger_time ::= BEFORE */ -{ yymsp[0].minor.yy194 = TK_BEFORE; } + case 226: /* trigger_time ::= BEFORE|AFTER */ +{ yymsp[0].minor.yy194 = yymsp[0].major; /*A-overwrites-X*/ } break; - case 229: /* trigger_time ::= AFTER */ -{ yymsp[0].minor.yy194 = TK_AFTER; } - break; - case 230: /* trigger_time ::= INSTEAD OF */ + case 227: /* trigger_time ::= INSTEAD OF */ { yymsp[-1].minor.yy194 = TK_INSTEAD;} break; - case 231: /* trigger_time ::= */ + case 228: /* trigger_time ::= */ { yymsp[1].minor.yy194 = TK_BEFORE; } break; - case 232: /* trigger_event ::= DELETE|INSERT */ - case 233: /* trigger_event ::= UPDATE */ yytestcase(yyruleno==233); + case 229: /* trigger_event ::= DELETE|INSERT */ + case 230: /* trigger_event ::= UPDATE */ yytestcase(yyruleno==230); {yymsp[0].minor.yy332.a = yymsp[0].major; /*A-overwrites-X*/ yymsp[0].minor.yy332.b = 0;} break; - case 234: /* trigger_event ::= UPDATE OF idlist */ + case 231: /* trigger_event ::= UPDATE OF idlist */ {yymsp[-2].minor.yy332.a = TK_UPDATE; yymsp[-2].minor.yy332.b = yymsp[0].minor.yy254;} break; - case 235: /* when_clause ::= */ - case 254: /* key_opt ::= */ yytestcase(yyruleno==254); + case 232: /* when_clause ::= */ + case 251: /* key_opt ::= */ yytestcase(yyruleno==251); { yymsp[1].minor.yy72 = 0; } break; - case 236: /* when_clause ::= WHEN expr */ - case 255: /* key_opt ::= KEY expr */ yytestcase(yyruleno==255); + case 233: /* when_clause ::= WHEN expr */ + case 252: /* key_opt ::= KEY expr */ yytestcase(yyruleno==252); { yymsp[-1].minor.yy72 = yymsp[0].minor.yy190.pExpr; } break; - case 237: /* trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ + case 234: /* trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ { assert( yymsp[-2].minor.yy145!=0 ); yymsp[-2].minor.yy145->pLast->pNext = yymsp[-1].minor.yy145; yymsp[-2].minor.yy145->pLast = yymsp[-1].minor.yy145; } break; - case 238: /* trigger_cmd_list ::= trigger_cmd SEMI */ + case 235: /* trigger_cmd_list ::= trigger_cmd SEMI */ { assert( yymsp[-1].minor.yy145!=0 ); yymsp[-1].minor.yy145->pLast = yymsp[-1].minor.yy145; } break; - case 239: /* trnm ::= nm DOT nm */ + case 236: /* trnm ::= nm DOT nm */ { yymsp[-2].minor.yy0 = yymsp[0].minor.yy0; sqlite3ErrorMsg(pParse, @@ -138154,33 +139830,33 @@ static void yy_reduce( "statements within triggers"); } break; - case 240: /* tridxby ::= INDEXED BY nm */ + case 237: /* tridxby ::= INDEXED BY nm */ { sqlite3ErrorMsg(pParse, "the INDEXED BY clause is not allowed on UPDATE or DELETE statements " "within triggers"); } break; - case 241: /* tridxby ::= NOT INDEXED */ + case 238: /* tridxby ::= NOT INDEXED */ { sqlite3ErrorMsg(pParse, "the NOT INDEXED clause is not allowed on UPDATE or DELETE statements " "within triggers"); } break; - case 242: /* trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist where_opt */ + case 239: /* trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist where_opt */ {yymsp[-6].minor.yy145 = sqlite3TriggerUpdateStep(pParse->db, &yymsp[-4].minor.yy0, yymsp[-1].minor.yy148, yymsp[0].minor.yy72, yymsp[-5].minor.yy194);} break; - case 243: /* trigger_cmd ::= insert_cmd INTO trnm idlist_opt select */ + case 240: /* trigger_cmd ::= insert_cmd INTO trnm idlist_opt select */ {yymsp[-4].minor.yy145 = sqlite3TriggerInsertStep(pParse->db, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy254, yymsp[0].minor.yy243, yymsp[-4].minor.yy194);/*A-overwrites-R*/} break; - case 244: /* trigger_cmd ::= DELETE FROM trnm tridxby where_opt */ + case 241: /* trigger_cmd ::= DELETE FROM trnm tridxby where_opt */ {yymsp[-4].minor.yy145 = sqlite3TriggerDeleteStep(pParse->db, &yymsp[-2].minor.yy0, yymsp[0].minor.yy72);} break; - case 245: /* trigger_cmd ::= select */ + case 242: /* trigger_cmd ::= select */ {yymsp[0].minor.yy145 = sqlite3TriggerSelectStep(pParse->db, yymsp[0].minor.yy243); /*A-overwrites-X*/} break; - case 246: /* expr ::= RAISE LP IGNORE RP */ + case 243: /* expr ::= RAISE LP IGNORE RP */ { spanSet(&yymsp[-3].minor.yy190,&yymsp[-3].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/ yymsp[-3].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_RAISE, 0, 0); @@ -138189,7 +139865,7 @@ static void yy_reduce( } } break; - case 247: /* expr ::= RAISE LP raisetype COMMA nm RP */ + case 244: /* expr ::= RAISE LP raisetype COMMA nm RP */ { spanSet(&yymsp[-5].minor.yy190,&yymsp[-5].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/ yymsp[-5].minor.yy190.pExpr = sqlite3ExprAlloc(pParse->db, TK_RAISE, &yymsp[-1].minor.yy0, 1); @@ -138198,172 +139874,176 @@ static void yy_reduce( } } break; - case 248: /* raisetype ::= ROLLBACK */ + case 245: /* raisetype ::= ROLLBACK */ {yymsp[0].minor.yy194 = OE_Rollback;} break; - case 250: /* raisetype ::= FAIL */ + case 247: /* raisetype ::= FAIL */ {yymsp[0].minor.yy194 = OE_Fail;} break; - case 251: /* cmd ::= DROP TRIGGER ifexists fullname */ + case 248: /* cmd ::= DROP TRIGGER ifexists fullname */ { sqlite3DropTrigger(pParse,yymsp[0].minor.yy185,yymsp[-1].minor.yy194); } break; - case 252: /* cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ + case 249: /* cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ { sqlite3Attach(pParse, yymsp[-3].minor.yy190.pExpr, yymsp[-1].minor.yy190.pExpr, yymsp[0].minor.yy72); } break; - case 253: /* cmd ::= DETACH database_kw_opt expr */ + case 250: /* cmd ::= DETACH database_kw_opt expr */ { sqlite3Detach(pParse, yymsp[0].minor.yy190.pExpr); } break; - case 256: /* cmd ::= REINDEX */ + case 253: /* cmd ::= REINDEX */ {sqlite3Reindex(pParse, 0, 0);} break; - case 257: /* cmd ::= REINDEX nm dbnm */ + case 254: /* cmd ::= REINDEX nm dbnm */ {sqlite3Reindex(pParse, &yymsp[-1].minor.yy0, &yymsp[0].minor.yy0);} break; - case 258: /* cmd ::= ANALYZE */ + case 255: /* cmd ::= ANALYZE */ {sqlite3Analyze(pParse, 0, 0);} break; - case 259: /* cmd ::= ANALYZE nm dbnm */ + case 256: /* cmd ::= ANALYZE nm dbnm */ {sqlite3Analyze(pParse, &yymsp[-1].minor.yy0, &yymsp[0].minor.yy0);} break; - case 260: /* cmd ::= ALTER TABLE fullname RENAME TO nm */ + case 257: /* cmd ::= ALTER TABLE fullname RENAME TO nm */ { sqlite3AlterRenameTable(pParse,yymsp[-3].minor.yy185,&yymsp[0].minor.yy0); } break; - case 261: /* cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */ + case 258: /* cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */ { yymsp[-1].minor.yy0.n = (int)(pParse->sLastToken.z-yymsp[-1].minor.yy0.z) + pParse->sLastToken.n; sqlite3AlterFinishAddColumn(pParse, &yymsp[-1].minor.yy0); } break; - case 262: /* add_column_fullname ::= fullname */ + case 259: /* add_column_fullname ::= fullname */ { disableLookaside(pParse); sqlite3AlterBeginAddColumn(pParse, yymsp[0].minor.yy185); } break; - case 263: /* cmd ::= create_vtab */ + case 260: /* cmd ::= create_vtab */ {sqlite3VtabFinishParse(pParse,0);} break; - case 264: /* cmd ::= create_vtab LP vtabarglist RP */ + case 261: /* cmd ::= create_vtab LP vtabarglist RP */ {sqlite3VtabFinishParse(pParse,&yymsp[0].minor.yy0);} break; - case 265: /* create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ + case 262: /* create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ { sqlite3VtabBeginParse(pParse, &yymsp[-3].minor.yy0, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0, yymsp[-4].minor.yy194); } break; - case 266: /* vtabarg ::= */ + case 263: /* vtabarg ::= */ {sqlite3VtabArgInit(pParse);} break; - case 267: /* vtabargtoken ::= ANY */ - case 268: /* vtabargtoken ::= lp anylist RP */ yytestcase(yyruleno==268); - case 269: /* lp ::= LP */ yytestcase(yyruleno==269); + case 264: /* vtabargtoken ::= ANY */ + case 265: /* vtabargtoken ::= lp anylist RP */ yytestcase(yyruleno==265); + case 266: /* lp ::= LP */ yytestcase(yyruleno==266); {sqlite3VtabArgExtend(pParse,&yymsp[0].minor.yy0);} break; - case 270: /* with ::= */ + case 267: /* with ::= */ {yymsp[1].minor.yy285 = 0;} break; - case 271: /* with ::= WITH wqlist */ + case 268: /* with ::= WITH wqlist */ { yymsp[-1].minor.yy285 = yymsp[0].minor.yy285; } break; - case 272: /* with ::= WITH RECURSIVE wqlist */ + case 269: /* with ::= WITH RECURSIVE wqlist */ { yymsp[-2].minor.yy285 = yymsp[0].minor.yy285; } break; - case 273: /* wqlist ::= nm eidlist_opt AS LP select RP */ + case 270: /* wqlist ::= nm eidlist_opt AS LP select RP */ { yymsp[-5].minor.yy285 = sqlite3WithAdd(pParse, 0, &yymsp[-5].minor.yy0, yymsp[-4].minor.yy148, yymsp[-1].minor.yy243); /*A-overwrites-X*/ } break; - case 274: /* wqlist ::= wqlist COMMA nm eidlist_opt AS LP select RP */ + case 271: /* wqlist ::= wqlist COMMA nm eidlist_opt AS LP select RP */ { yymsp[-7].minor.yy285 = sqlite3WithAdd(pParse, yymsp[-7].minor.yy285, &yymsp[-5].minor.yy0, yymsp[-4].minor.yy148, yymsp[-1].minor.yy243); } break; default: - /* (275) input ::= cmdlist */ yytestcase(yyruleno==275); - /* (276) cmdlist ::= cmdlist ecmd */ yytestcase(yyruleno==276); - /* (277) cmdlist ::= ecmd (OPTIMIZED OUT) */ assert(yyruleno!=277); - /* (278) ecmd ::= SEMI */ yytestcase(yyruleno==278); - /* (279) ecmd ::= explain cmdx SEMI */ yytestcase(yyruleno==279); - /* (280) explain ::= */ yytestcase(yyruleno==280); - /* (281) trans_opt ::= */ yytestcase(yyruleno==281); - /* (282) trans_opt ::= TRANSACTION */ yytestcase(yyruleno==282); - /* (283) trans_opt ::= TRANSACTION nm */ yytestcase(yyruleno==283); - /* (284) savepoint_opt ::= SAVEPOINT */ yytestcase(yyruleno==284); - /* (285) savepoint_opt ::= */ yytestcase(yyruleno==285); - /* (286) cmd ::= create_table create_table_args */ yytestcase(yyruleno==286); - /* (287) columnlist ::= columnlist COMMA columnname carglist */ yytestcase(yyruleno==287); - /* (288) columnlist ::= columnname carglist */ yytestcase(yyruleno==288); - /* (289) nm ::= ID|INDEXED */ yytestcase(yyruleno==289); - /* (290) nm ::= STRING */ yytestcase(yyruleno==290); - /* (291) nm ::= JOIN_KW */ yytestcase(yyruleno==291); - /* (292) typetoken ::= typename */ yytestcase(yyruleno==292); - /* (293) typename ::= ID|STRING */ yytestcase(yyruleno==293); - /* (294) signed ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=294); - /* (295) signed ::= minus_num (OPTIMIZED OUT) */ assert(yyruleno!=295); - /* (296) carglist ::= carglist ccons */ yytestcase(yyruleno==296); - /* (297) carglist ::= */ yytestcase(yyruleno==297); - /* (298) ccons ::= NULL onconf */ yytestcase(yyruleno==298); - /* (299) conslist_opt ::= COMMA conslist */ yytestcase(yyruleno==299); - /* (300) conslist ::= conslist tconscomma tcons */ yytestcase(yyruleno==300); - /* (301) conslist ::= tcons (OPTIMIZED OUT) */ assert(yyruleno!=301); - /* (302) tconscomma ::= */ yytestcase(yyruleno==302); - /* (303) defer_subclause_opt ::= defer_subclause (OPTIMIZED OUT) */ assert(yyruleno!=303); - /* (304) resolvetype ::= raisetype (OPTIMIZED OUT) */ assert(yyruleno!=304); - /* (305) selectnowith ::= oneselect (OPTIMIZED OUT) */ assert(yyruleno!=305); - /* (306) oneselect ::= values */ yytestcase(yyruleno==306); - /* (307) sclp ::= selcollist COMMA */ yytestcase(yyruleno==307); - /* (308) as ::= ID|STRING */ yytestcase(yyruleno==308); - /* (309) expr ::= term (OPTIMIZED OUT) */ assert(yyruleno!=309); - /* (310) likeop ::= LIKE_KW|MATCH */ yytestcase(yyruleno==310); - /* (311) exprlist ::= nexprlist */ yytestcase(yyruleno==311); - /* (312) nmnum ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=312); - /* (313) nmnum ::= nm (OPTIMIZED OUT) */ assert(yyruleno!=313); - /* (314) nmnum ::= ON */ yytestcase(yyruleno==314); - /* (315) nmnum ::= DELETE */ yytestcase(yyruleno==315); - /* (316) nmnum ::= DEFAULT */ yytestcase(yyruleno==316); - /* (317) plus_num ::= INTEGER|FLOAT */ yytestcase(yyruleno==317); - /* (318) foreach_clause ::= */ yytestcase(yyruleno==318); - /* (319) foreach_clause ::= FOR EACH ROW */ yytestcase(yyruleno==319); - /* (320) trnm ::= nm */ yytestcase(yyruleno==320); - /* (321) tridxby ::= */ yytestcase(yyruleno==321); - /* (322) database_kw_opt ::= DATABASE */ yytestcase(yyruleno==322); - /* (323) database_kw_opt ::= */ yytestcase(yyruleno==323); - /* (324) kwcolumn_opt ::= */ yytestcase(yyruleno==324); - /* (325) kwcolumn_opt ::= COLUMNKW */ yytestcase(yyruleno==325); - /* (326) vtabarglist ::= vtabarg */ yytestcase(yyruleno==326); - /* (327) vtabarglist ::= vtabarglist COMMA vtabarg */ yytestcase(yyruleno==327); - /* (328) vtabarg ::= vtabarg vtabargtoken */ yytestcase(yyruleno==328); - /* (329) anylist ::= */ yytestcase(yyruleno==329); - /* (330) anylist ::= anylist LP anylist RP */ yytestcase(yyruleno==330); - /* (331) anylist ::= anylist ANY */ yytestcase(yyruleno==331); + /* (272) input ::= cmdlist */ yytestcase(yyruleno==272); + /* (273) cmdlist ::= cmdlist ecmd */ yytestcase(yyruleno==273); + /* (274) cmdlist ::= ecmd (OPTIMIZED OUT) */ assert(yyruleno!=274); + /* (275) ecmd ::= SEMI */ yytestcase(yyruleno==275); + /* (276) ecmd ::= explain cmdx SEMI */ yytestcase(yyruleno==276); + /* (277) explain ::= */ yytestcase(yyruleno==277); + /* (278) trans_opt ::= */ yytestcase(yyruleno==278); + /* (279) trans_opt ::= TRANSACTION */ yytestcase(yyruleno==279); + /* (280) trans_opt ::= TRANSACTION nm */ yytestcase(yyruleno==280); + /* (281) savepoint_opt ::= SAVEPOINT */ yytestcase(yyruleno==281); + /* (282) savepoint_opt ::= */ yytestcase(yyruleno==282); + /* (283) cmd ::= create_table create_table_args */ yytestcase(yyruleno==283); + /* (284) columnlist ::= columnlist COMMA columnname carglist */ yytestcase(yyruleno==284); + /* (285) columnlist ::= columnname carglist */ yytestcase(yyruleno==285); + /* (286) nm ::= ID|INDEXED */ yytestcase(yyruleno==286); + /* (287) nm ::= STRING */ yytestcase(yyruleno==287); + /* (288) nm ::= JOIN_KW */ yytestcase(yyruleno==288); + /* (289) typetoken ::= typename */ yytestcase(yyruleno==289); + /* (290) typename ::= ID|STRING */ yytestcase(yyruleno==290); + /* (291) signed ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=291); + /* (292) signed ::= minus_num (OPTIMIZED OUT) */ assert(yyruleno!=292); + /* (293) carglist ::= carglist ccons */ yytestcase(yyruleno==293); + /* (294) carglist ::= */ yytestcase(yyruleno==294); + /* (295) ccons ::= NULL onconf */ yytestcase(yyruleno==295); + /* (296) conslist_opt ::= COMMA conslist */ yytestcase(yyruleno==296); + /* (297) conslist ::= conslist tconscomma tcons */ yytestcase(yyruleno==297); + /* (298) conslist ::= tcons (OPTIMIZED OUT) */ assert(yyruleno!=298); + /* (299) tconscomma ::= */ yytestcase(yyruleno==299); + /* (300) defer_subclause_opt ::= defer_subclause (OPTIMIZED OUT) */ assert(yyruleno!=300); + /* (301) resolvetype ::= raisetype (OPTIMIZED OUT) */ assert(yyruleno!=301); + /* (302) selectnowith ::= oneselect (OPTIMIZED OUT) */ assert(yyruleno!=302); + /* (303) oneselect ::= values */ yytestcase(yyruleno==303); + /* (304) sclp ::= selcollist COMMA */ yytestcase(yyruleno==304); + /* (305) as ::= ID|STRING */ yytestcase(yyruleno==305); + /* (306) expr ::= term (OPTIMIZED OUT) */ assert(yyruleno!=306); + /* (307) likeop ::= LIKE_KW|MATCH */ yytestcase(yyruleno==307); + /* (308) exprlist ::= nexprlist */ yytestcase(yyruleno==308); + /* (309) nmnum ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=309); + /* (310) nmnum ::= nm (OPTIMIZED OUT) */ assert(yyruleno!=310); + /* (311) nmnum ::= ON */ yytestcase(yyruleno==311); + /* (312) nmnum ::= DELETE */ yytestcase(yyruleno==312); + /* (313) nmnum ::= DEFAULT */ yytestcase(yyruleno==313); + /* (314) plus_num ::= INTEGER|FLOAT */ yytestcase(yyruleno==314); + /* (315) foreach_clause ::= */ yytestcase(yyruleno==315); + /* (316) foreach_clause ::= FOR EACH ROW */ yytestcase(yyruleno==316); + /* (317) trnm ::= nm */ yytestcase(yyruleno==317); + /* (318) tridxby ::= */ yytestcase(yyruleno==318); + /* (319) database_kw_opt ::= DATABASE */ yytestcase(yyruleno==319); + /* (320) database_kw_opt ::= */ yytestcase(yyruleno==320); + /* (321) kwcolumn_opt ::= */ yytestcase(yyruleno==321); + /* (322) kwcolumn_opt ::= COLUMNKW */ yytestcase(yyruleno==322); + /* (323) vtabarglist ::= vtabarg */ yytestcase(yyruleno==323); + /* (324) vtabarglist ::= vtabarglist COMMA vtabarg */ yytestcase(yyruleno==324); + /* (325) vtabarg ::= vtabarg vtabargtoken */ yytestcase(yyruleno==325); + /* (326) anylist ::= */ yytestcase(yyruleno==326); + /* (327) anylist ::= anylist LP anylist RP */ yytestcase(yyruleno==327); + /* (328) anylist ::= anylist ANY */ yytestcase(yyruleno==328); break; /********** End reduce actions ************************************************/ }; assert( yyrulenoYY_MAX_SHIFT ){ - yyact += YY_MIN_REDUCE - YY_MIN_SHIFTREDUCE; - } - yymsp -= yysize-1; + yyact = yy_find_reduce_action(yymsp[yysize].stateno,(YYCODETYPE)yygoto); + + /* There are no SHIFTREDUCE actions on nonterminals because the table + ** generator has simplified them to pure REDUCE actions. */ + assert( !(yyact>YY_MAX_SHIFT && yyact<=YY_MAX_SHIFTREDUCE) ); + + /* It is not possible for a REDUCE to be followed by an error */ + assert( yyact!=YY_ERROR_ACTION ); + + if( yyact==YY_ACCEPT_ACTION ){ + yypParser->yytos += yysize; + yy_accept(yypParser); + }else{ + yymsp += yysize+1; yypParser->yytos = yymsp; yymsp->stateno = (YYACTIONTYPE)yyact; yymsp->major = (YYCODETYPE)yygoto; yyTraceShift(yypParser, yyact); - }else{ - assert( yyact == YY_ACCEPT_ACTION ); - yypParser->yytos -= yysize; - yy_accept(yypParser); } } @@ -138769,134 +140449,145 @@ const unsigned char ebcdicToAscii[] = { ** on platforms with limited memory. */ /* Hash score: 182 */ +/* zKWText[] encodes 834 bytes of keyword text in 554 bytes */ +/* REINDEXEDESCAPEACHECKEYBEFOREIGNOREGEXPLAINSTEADDATABASELECT */ +/* ABLEFTHENDEFERRABLELSEXCEPTRANSACTIONATURALTERAISEXCLUSIVE */ +/* XISTSAVEPOINTERSECTRIGGEREFERENCESCONSTRAINTOFFSETEMPORARY */ +/* UNIQUERYWITHOUTERELEASEATTACHAVINGROUPDATEBEGINNERECURSIVE */ +/* BETWEENOTNULLIKECASCADELETECASECOLLATECREATECURRENT_DATEDETACH */ +/* IMMEDIATEJOINSERTMATCHPLANALYZEPRAGMABORTVALUESVIRTUALIMITWHEN */ +/* WHERENAMEAFTEREPLACEANDEFAULTAUTOINCREMENTCASTCOLUMNCOMMIT */ +/* CONFLICTCROSSCURRENT_TIMESTAMPRIMARYDEFERREDISTINCTDROPFAIL */ +/* FROMFULLGLOBYIFISNULLORDERESTRICTRIGHTROLLBACKROWUNIONUSING */ +/* VACUUMVIEWINITIALLY */ +static const char zKWText[553] = { + 'R','E','I','N','D','E','X','E','D','E','S','C','A','P','E','A','C','H', + 'E','C','K','E','Y','B','E','F','O','R','E','I','G','N','O','R','E','G', + 'E','X','P','L','A','I','N','S','T','E','A','D','D','A','T','A','B','A', + 'S','E','L','E','C','T','A','B','L','E','F','T','H','E','N','D','E','F', + 'E','R','R','A','B','L','E','L','S','E','X','C','E','P','T','R','A','N', + 'S','A','C','T','I','O','N','A','T','U','R','A','L','T','E','R','A','I', + 'S','E','X','C','L','U','S','I','V','E','X','I','S','T','S','A','V','E', + 'P','O','I','N','T','E','R','S','E','C','T','R','I','G','G','E','R','E', + 'F','E','R','E','N','C','E','S','C','O','N','S','T','R','A','I','N','T', + 'O','F','F','S','E','T','E','M','P','O','R','A','R','Y','U','N','I','Q', + 'U','E','R','Y','W','I','T','H','O','U','T','E','R','E','L','E','A','S', + 'E','A','T','T','A','C','H','A','V','I','N','G','R','O','U','P','D','A', + 'T','E','B','E','G','I','N','N','E','R','E','C','U','R','S','I','V','E', + 'B','E','T','W','E','E','N','O','T','N','U','L','L','I','K','E','C','A', + 'S','C','A','D','E','L','E','T','E','C','A','S','E','C','O','L','L','A', + 'T','E','C','R','E','A','T','E','C','U','R','R','E','N','T','_','D','A', + 'T','E','D','E','T','A','C','H','I','M','M','E','D','I','A','T','E','J', + 'O','I','N','S','E','R','T','M','A','T','C','H','P','L','A','N','A','L', + 'Y','Z','E','P','R','A','G','M','A','B','O','R','T','V','A','L','U','E', + 'S','V','I','R','T','U','A','L','I','M','I','T','W','H','E','N','W','H', + 'E','R','E','N','A','M','E','A','F','T','E','R','E','P','L','A','C','E', + 'A','N','D','E','F','A','U','L','T','A','U','T','O','I','N','C','R','E', + 'M','E','N','T','C','A','S','T','C','O','L','U','M','N','C','O','M','M', + 'I','T','C','O','N','F','L','I','C','T','C','R','O','S','S','C','U','R', + 'R','E','N','T','_','T','I','M','E','S','T','A','M','P','R','I','M','A', + 'R','Y','D','E','F','E','R','R','E','D','I','S','T','I','N','C','T','D', + 'R','O','P','F','A','I','L','F','R','O','M','F','U','L','L','G','L','O', + 'B','Y','I','F','I','S','N','U','L','L','O','R','D','E','R','E','S','T', + 'R','I','C','T','R','I','G','H','T','R','O','L','L','B','A','C','K','R', + 'O','W','U','N','I','O','N','U','S','I','N','G','V','A','C','U','U','M', + 'V','I','E','W','I','N','I','T','I','A','L','L','Y', +}; +/* aKWHash[i] is the hash value for the i-th keyword */ +static const unsigned char aKWHash[127] = { + 76, 105, 117, 74, 0, 45, 0, 0, 82, 0, 77, 0, 0, + 42, 12, 78, 15, 0, 116, 85, 54, 112, 0, 19, 0, 0, + 121, 0, 119, 115, 0, 22, 93, 0, 9, 0, 0, 70, 71, + 0, 69, 6, 0, 48, 90, 102, 0, 118, 101, 0, 0, 44, + 0, 103, 24, 0, 17, 0, 122, 53, 23, 0, 5, 110, 25, + 96, 0, 0, 124, 106, 60, 123, 57, 28, 55, 0, 91, 0, + 100, 26, 0, 99, 0, 0, 0, 95, 92, 97, 88, 109, 14, + 39, 108, 0, 81, 0, 18, 89, 111, 32, 0, 120, 80, 113, + 62, 46, 84, 0, 0, 94, 40, 59, 114, 0, 36, 0, 0, + 29, 0, 86, 63, 64, 0, 20, 61, 0, 56, +}; +/* aKWNext[] forms the hash collision chain. If aKWHash[i]==0 +** then the i-th keyword has no more hash collisions. Otherwise, +** the next keyword with the same hash is aKWHash[i]-1. */ +static const unsigned char aKWNext[124] = { + 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 2, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, + 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 33, 0, 21, 0, 0, 0, 0, 0, 50, + 0, 43, 3, 47, 0, 0, 0, 0, 30, 0, 58, 0, 38, + 0, 0, 0, 1, 66, 0, 0, 67, 0, 41, 0, 0, 0, + 0, 0, 0, 49, 65, 0, 0, 0, 0, 31, 52, 16, 34, + 10, 0, 0, 0, 0, 0, 0, 0, 11, 72, 79, 0, 8, + 0, 104, 98, 0, 107, 0, 87, 0, 75, 51, 0, 27, 37, + 73, 83, 0, 35, 68, 0, 0, +}; +/* aKWLen[i] is the length (in bytes) of the i-th keyword */ +static const unsigned char aKWLen[124] = { + 7, 7, 5, 4, 6, 4, 5, 3, 6, 7, 3, 6, 6, + 7, 7, 3, 8, 2, 6, 5, 4, 4, 3, 10, 4, 6, + 11, 6, 2, 7, 5, 5, 9, 6, 9, 9, 7, 10, 10, + 4, 6, 2, 3, 9, 4, 2, 6, 5, 7, 4, 5, 7, + 6, 6, 5, 6, 5, 5, 9, 7, 7, 3, 2, 4, 4, + 7, 3, 6, 4, 7, 6, 12, 6, 9, 4, 6, 5, 4, + 7, 6, 5, 6, 7, 5, 4, 5, 6, 5, 7, 3, 7, + 13, 2, 2, 4, 6, 6, 8, 5, 17, 12, 7, 8, 8, + 2, 4, 4, 4, 4, 4, 2, 2, 6, 5, 8, 5, 8, + 3, 5, 5, 6, 4, 9, 3, +}; +/* aKWOffset[i] is the index into zKWText[] of the start of +** the text for the i-th keyword. */ +static const unsigned short int aKWOffset[124] = { + 0, 2, 2, 8, 9, 14, 16, 20, 23, 25, 25, 29, 33, + 36, 41, 46, 48, 53, 54, 59, 62, 65, 67, 69, 78, 81, + 86, 91, 95, 96, 101, 105, 109, 117, 122, 128, 136, 142, 152, + 159, 162, 162, 165, 167, 167, 171, 176, 179, 184, 184, 188, 192, + 199, 204, 209, 212, 218, 221, 225, 234, 240, 240, 240, 243, 246, + 250, 251, 255, 261, 265, 272, 278, 290, 296, 305, 307, 313, 318, + 320, 327, 332, 337, 343, 349, 354, 358, 361, 367, 371, 378, 380, + 387, 389, 391, 400, 404, 410, 416, 424, 429, 429, 445, 452, 459, + 460, 467, 471, 475, 479, 483, 486, 488, 490, 496, 500, 508, 513, + 521, 524, 529, 534, 540, 544, 549, +}; +/* aKWCode[i] is the parser symbol code for the i-th keyword */ +static const unsigned char aKWCode[124] = { + TK_REINDEX, TK_INDEXED, TK_INDEX, TK_DESC, TK_ESCAPE, + TK_EACH, TK_CHECK, TK_KEY, TK_BEFORE, TK_FOREIGN, + TK_FOR, TK_IGNORE, TK_LIKE_KW, TK_EXPLAIN, TK_INSTEAD, + TK_ADD, TK_DATABASE, TK_AS, TK_SELECT, TK_TABLE, + TK_JOIN_KW, TK_THEN, TK_END, TK_DEFERRABLE, TK_ELSE, + TK_EXCEPT, TK_TRANSACTION,TK_ACTION, TK_ON, TK_JOIN_KW, + TK_ALTER, TK_RAISE, TK_EXCLUSIVE, TK_EXISTS, TK_SAVEPOINT, + TK_INTERSECT, TK_TRIGGER, TK_REFERENCES, TK_CONSTRAINT, TK_INTO, + TK_OFFSET, TK_OF, TK_SET, TK_TEMP, TK_TEMP, + TK_OR, TK_UNIQUE, TK_QUERY, TK_WITHOUT, TK_WITH, + TK_JOIN_KW, TK_RELEASE, TK_ATTACH, TK_HAVING, TK_GROUP, + TK_UPDATE, TK_BEGIN, TK_JOIN_KW, TK_RECURSIVE, TK_BETWEEN, + TK_NOTNULL, TK_NOT, TK_NO, TK_NULL, TK_LIKE_KW, + TK_CASCADE, TK_ASC, TK_DELETE, TK_CASE, TK_COLLATE, + TK_CREATE, TK_CTIME_KW, TK_DETACH, TK_IMMEDIATE, TK_JOIN, + TK_INSERT, TK_MATCH, TK_PLAN, TK_ANALYZE, TK_PRAGMA, + TK_ABORT, TK_VALUES, TK_VIRTUAL, TK_LIMIT, TK_WHEN, + TK_WHERE, TK_RENAME, TK_AFTER, TK_REPLACE, TK_AND, + TK_DEFAULT, TK_AUTOINCR, TK_TO, TK_IN, TK_CAST, + TK_COLUMNKW, TK_COMMIT, TK_CONFLICT, TK_JOIN_KW, TK_CTIME_KW, + TK_CTIME_KW, TK_PRIMARY, TK_DEFERRED, TK_DISTINCT, TK_IS, + TK_DROP, TK_FAIL, TK_FROM, TK_JOIN_KW, TK_LIKE_KW, + TK_BY, TK_IF, TK_ISNULL, TK_ORDER, TK_RESTRICT, + TK_JOIN_KW, TK_ROLLBACK, TK_ROW, TK_UNION, TK_USING, + TK_VACUUM, TK_VIEW, TK_INITIALLY, TK_ALL, +}; +/* Check to see if z[0..n-1] is a keyword. If it is, write the +** parser symbol code for that keyword into *pType. Always +** return the integer n (the length of the token). */ static int keywordCode(const char *z, int n, int *pType){ - /* zText[] encodes 834 bytes of keywords in 554 bytes */ - /* REINDEXEDESCAPEACHECKEYBEFOREIGNOREGEXPLAINSTEADDATABASELECT */ - /* ABLEFTHENDEFERRABLELSEXCEPTRANSACTIONATURALTERAISEXCLUSIVE */ - /* XISTSAVEPOINTERSECTRIGGEREFERENCESCONSTRAINTOFFSETEMPORARY */ - /* UNIQUERYWITHOUTERELEASEATTACHAVINGROUPDATEBEGINNERECURSIVE */ - /* BETWEENOTNULLIKECASCADELETECASECOLLATECREATECURRENT_DATEDETACH */ - /* IMMEDIATEJOINSERTMATCHPLANALYZEPRAGMABORTVALUESVIRTUALIMITWHEN */ - /* WHERENAMEAFTEREPLACEANDEFAULTAUTOINCREMENTCASTCOLUMNCOMMIT */ - /* CONFLICTCROSSCURRENT_TIMESTAMPRIMARYDEFERREDISTINCTDROPFAIL */ - /* FROMFULLGLOBYIFISNULLORDERESTRICTRIGHTROLLBACKROWUNIONUSING */ - /* VACUUMVIEWINITIALLY */ - static const char zText[553] = { - 'R','E','I','N','D','E','X','E','D','E','S','C','A','P','E','A','C','H', - 'E','C','K','E','Y','B','E','F','O','R','E','I','G','N','O','R','E','G', - 'E','X','P','L','A','I','N','S','T','E','A','D','D','A','T','A','B','A', - 'S','E','L','E','C','T','A','B','L','E','F','T','H','E','N','D','E','F', - 'E','R','R','A','B','L','E','L','S','E','X','C','E','P','T','R','A','N', - 'S','A','C','T','I','O','N','A','T','U','R','A','L','T','E','R','A','I', - 'S','E','X','C','L','U','S','I','V','E','X','I','S','T','S','A','V','E', - 'P','O','I','N','T','E','R','S','E','C','T','R','I','G','G','E','R','E', - 'F','E','R','E','N','C','E','S','C','O','N','S','T','R','A','I','N','T', - 'O','F','F','S','E','T','E','M','P','O','R','A','R','Y','U','N','I','Q', - 'U','E','R','Y','W','I','T','H','O','U','T','E','R','E','L','E','A','S', - 'E','A','T','T','A','C','H','A','V','I','N','G','R','O','U','P','D','A', - 'T','E','B','E','G','I','N','N','E','R','E','C','U','R','S','I','V','E', - 'B','E','T','W','E','E','N','O','T','N','U','L','L','I','K','E','C','A', - 'S','C','A','D','E','L','E','T','E','C','A','S','E','C','O','L','L','A', - 'T','E','C','R','E','A','T','E','C','U','R','R','E','N','T','_','D','A', - 'T','E','D','E','T','A','C','H','I','M','M','E','D','I','A','T','E','J', - 'O','I','N','S','E','R','T','M','A','T','C','H','P','L','A','N','A','L', - 'Y','Z','E','P','R','A','G','M','A','B','O','R','T','V','A','L','U','E', - 'S','V','I','R','T','U','A','L','I','M','I','T','W','H','E','N','W','H', - 'E','R','E','N','A','M','E','A','F','T','E','R','E','P','L','A','C','E', - 'A','N','D','E','F','A','U','L','T','A','U','T','O','I','N','C','R','E', - 'M','E','N','T','C','A','S','T','C','O','L','U','M','N','C','O','M','M', - 'I','T','C','O','N','F','L','I','C','T','C','R','O','S','S','C','U','R', - 'R','E','N','T','_','T','I','M','E','S','T','A','M','P','R','I','M','A', - 'R','Y','D','E','F','E','R','R','E','D','I','S','T','I','N','C','T','D', - 'R','O','P','F','A','I','L','F','R','O','M','F','U','L','L','G','L','O', - 'B','Y','I','F','I','S','N','U','L','L','O','R','D','E','R','E','S','T', - 'R','I','C','T','R','I','G','H','T','R','O','L','L','B','A','C','K','R', - 'O','W','U','N','I','O','N','U','S','I','N','G','V','A','C','U','U','M', - 'V','I','E','W','I','N','I','T','I','A','L','L','Y', - }; - static const unsigned char aHash[127] = { - 76, 105, 117, 74, 0, 45, 0, 0, 82, 0, 77, 0, 0, - 42, 12, 78, 15, 0, 116, 85, 54, 112, 0, 19, 0, 0, - 121, 0, 119, 115, 0, 22, 93, 0, 9, 0, 0, 70, 71, - 0, 69, 6, 0, 48, 90, 102, 0, 118, 101, 0, 0, 44, - 0, 103, 24, 0, 17, 0, 122, 53, 23, 0, 5, 110, 25, - 96, 0, 0, 124, 106, 60, 123, 57, 28, 55, 0, 91, 0, - 100, 26, 0, 99, 0, 0, 0, 95, 92, 97, 88, 109, 14, - 39, 108, 0, 81, 0, 18, 89, 111, 32, 0, 120, 80, 113, - 62, 46, 84, 0, 0, 94, 40, 59, 114, 0, 36, 0, 0, - 29, 0, 86, 63, 64, 0, 20, 61, 0, 56, - }; - static const unsigned char aNext[124] = { - 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 2, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, - 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 33, 0, 21, 0, 0, 0, 0, 0, 50, - 0, 43, 3, 47, 0, 0, 0, 0, 30, 0, 58, 0, 38, - 0, 0, 0, 1, 66, 0, 0, 67, 0, 41, 0, 0, 0, - 0, 0, 0, 49, 65, 0, 0, 0, 0, 31, 52, 16, 34, - 10, 0, 0, 0, 0, 0, 0, 0, 11, 72, 79, 0, 8, - 0, 104, 98, 0, 107, 0, 87, 0, 75, 51, 0, 27, 37, - 73, 83, 0, 35, 68, 0, 0, - }; - static const unsigned char aLen[124] = { - 7, 7, 5, 4, 6, 4, 5, 3, 6, 7, 3, 6, 6, - 7, 7, 3, 8, 2, 6, 5, 4, 4, 3, 10, 4, 6, - 11, 6, 2, 7, 5, 5, 9, 6, 9, 9, 7, 10, 10, - 4, 6, 2, 3, 9, 4, 2, 6, 5, 7, 4, 5, 7, - 6, 6, 5, 6, 5, 5, 9, 7, 7, 3, 2, 4, 4, - 7, 3, 6, 4, 7, 6, 12, 6, 9, 4, 6, 5, 4, - 7, 6, 5, 6, 7, 5, 4, 5, 6, 5, 7, 3, 7, - 13, 2, 2, 4, 6, 6, 8, 5, 17, 12, 7, 8, 8, - 2, 4, 4, 4, 4, 4, 2, 2, 6, 5, 8, 5, 8, - 3, 5, 5, 6, 4, 9, 3, - }; - static const unsigned short int aOffset[124] = { - 0, 2, 2, 8, 9, 14, 16, 20, 23, 25, 25, 29, 33, - 36, 41, 46, 48, 53, 54, 59, 62, 65, 67, 69, 78, 81, - 86, 91, 95, 96, 101, 105, 109, 117, 122, 128, 136, 142, 152, - 159, 162, 162, 165, 167, 167, 171, 176, 179, 184, 184, 188, 192, - 199, 204, 209, 212, 218, 221, 225, 234, 240, 240, 240, 243, 246, - 250, 251, 255, 261, 265, 272, 278, 290, 296, 305, 307, 313, 318, - 320, 327, 332, 337, 343, 349, 354, 358, 361, 367, 371, 378, 380, - 387, 389, 391, 400, 404, 410, 416, 424, 429, 429, 445, 452, 459, - 460, 467, 471, 475, 479, 483, 486, 488, 490, 496, 500, 508, 513, - 521, 524, 529, 534, 540, 544, 549, - }; - static const unsigned char aCode[124] = { - TK_REINDEX, TK_INDEXED, TK_INDEX, TK_DESC, TK_ESCAPE, - TK_EACH, TK_CHECK, TK_KEY, TK_BEFORE, TK_FOREIGN, - TK_FOR, TK_IGNORE, TK_LIKE_KW, TK_EXPLAIN, TK_INSTEAD, - TK_ADD, TK_DATABASE, TK_AS, TK_SELECT, TK_TABLE, - TK_JOIN_KW, TK_THEN, TK_END, TK_DEFERRABLE, TK_ELSE, - TK_EXCEPT, TK_TRANSACTION,TK_ACTION, TK_ON, TK_JOIN_KW, - TK_ALTER, TK_RAISE, TK_EXCLUSIVE, TK_EXISTS, TK_SAVEPOINT, - TK_INTERSECT, TK_TRIGGER, TK_REFERENCES, TK_CONSTRAINT, TK_INTO, - TK_OFFSET, TK_OF, TK_SET, TK_TEMP, TK_TEMP, - TK_OR, TK_UNIQUE, TK_QUERY, TK_WITHOUT, TK_WITH, - TK_JOIN_KW, TK_RELEASE, TK_ATTACH, TK_HAVING, TK_GROUP, - TK_UPDATE, TK_BEGIN, TK_JOIN_KW, TK_RECURSIVE, TK_BETWEEN, - TK_NOTNULL, TK_NOT, TK_NO, TK_NULL, TK_LIKE_KW, - TK_CASCADE, TK_ASC, TK_DELETE, TK_CASE, TK_COLLATE, - TK_CREATE, TK_CTIME_KW, TK_DETACH, TK_IMMEDIATE, TK_JOIN, - TK_INSERT, TK_MATCH, TK_PLAN, TK_ANALYZE, TK_PRAGMA, - TK_ABORT, TK_VALUES, TK_VIRTUAL, TK_LIMIT, TK_WHEN, - TK_WHERE, TK_RENAME, TK_AFTER, TK_REPLACE, TK_AND, - TK_DEFAULT, TK_AUTOINCR, TK_TO, TK_IN, TK_CAST, - TK_COLUMNKW, TK_COMMIT, TK_CONFLICT, TK_JOIN_KW, TK_CTIME_KW, - TK_CTIME_KW, TK_PRIMARY, TK_DEFERRED, TK_DISTINCT, TK_IS, - TK_DROP, TK_FAIL, TK_FROM, TK_JOIN_KW, TK_LIKE_KW, - TK_BY, TK_IF, TK_ISNULL, TK_ORDER, TK_RESTRICT, - TK_JOIN_KW, TK_ROLLBACK, TK_ROW, TK_UNION, TK_USING, - TK_VACUUM, TK_VIEW, TK_INITIALLY, TK_ALL, - }; int i, j; const char *zKW; if( n>=2 ){ i = ((charMap(z[0])*4) ^ (charMap(z[n-1])*3) ^ n) % 127; - for(i=((int)aHash[i])-1; i>=0; i=((int)aNext[i])-1){ - if( aLen[i]!=n ) continue; + for(i=((int)aKWHash[i])-1; i>=0; i=((int)aKWNext[i])-1){ + if( aKWLen[i]!=n ) continue; j = 0; - zKW = &zText[aOffset[i]]; + zKW = &zKWText[aKWOffset[i]]; #ifdef SQLITE_ASCII while( jdb; /* The database connection */ int mxSqlLen; /* Max length of an SQL string */ #ifdef sqlite3Parser_ENGINEALWAYSONSTACK - unsigned char zSpace[sizeof(yyParser)]; /* Space for parser engine object */ + yyParser sEngine; /* Space to hold the Lemon-generated Parser object */ #endif assert( zSql!=0 ); @@ -139395,7 +141086,7 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzEr assert( pzErrMsg!=0 ); /* sqlite3ParserTrace(stdout, "parser: "); */ #ifdef sqlite3Parser_ENGINEALWAYSONSTACK - pEngine = zSpace; + pEngine = &sEngine; sqlite3ParserInit(pEngine); #else pEngine = sqlite3ParserAlloc(sqlite3Malloc); @@ -139504,7 +141195,7 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzEr while( pParse->pAinc ){ AutoincInfo *p = pParse->pAinc; pParse->pAinc = p->pNext; - sqlite3DbFree(db, p); + sqlite3DbFreeNN(db, p); } while( pParse->pZombieTab ){ Table *p = pParse->pZombieTab; @@ -139931,6 +141622,9 @@ SQLITE_PRIVATE int sqlite3IcuInit(sqlite3 *db); #ifdef SQLITE_ENABLE_JSON1 SQLITE_PRIVATE int sqlite3Json1Init(sqlite3*); #endif +#ifdef SQLITE_ENABLE_STMTVTAB +SQLITE_PRIVATE int sqlite3StmtVtabInit(sqlite3*); +#endif #ifdef SQLITE_ENABLE_FTS5 SQLITE_PRIVATE int sqlite3Fts5Init(sqlite3*); #endif @@ -140693,6 +142387,8 @@ SQLITE_API int sqlite3_db_config(sqlite3 *db, int op, ...){ va_start(ap, op); switch( op ){ case SQLITE_DBCONFIG_MAINDBNAME: { + /* IMP: R-06824-28531 */ + /* IMP: R-36257-52125 */ db->aDb[0].zDbSName = va_arg(ap,char*); rc = SQLITE_OK; break; @@ -140714,6 +142410,7 @@ SQLITE_API int sqlite3_db_config(sqlite3 *db, int op, ...){ { SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, SQLITE_Fts3Tokenizer }, { SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, SQLITE_LoadExtension }, { SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE, SQLITE_NoCkptOnClose }, + { SQLITE_DBCONFIG_ENABLE_QPSG, SQLITE_EnableQPSG }, }; unsigned int i; rc = SQLITE_ERROR; /* IMP: R-42790-23372 */ @@ -140770,6 +142467,7 @@ static int binCollFunc( /* EVIDENCE-OF: R-65033-28449 The built-in BINARY collation compares ** strings byte by byte using the memcmp() function from the standard C ** library. */ + assert( pKey1 && pKey2 ); rc = memcmp(pKey1, pKey2, n); if( rc==0 ){ if( padFlag @@ -141302,10 +143000,10 @@ SQLITE_PRIVATE const char *sqlite3ErrName(int rc){ SQLITE_PRIVATE const char *sqlite3ErrStr(int rc){ static const char* const aMsg[] = { /* SQLITE_OK */ "not an error", - /* SQLITE_ERROR */ "SQL logic error or missing database", + /* SQLITE_ERROR */ "SQL logic error", /* SQLITE_INTERNAL */ 0, /* SQLITE_PERM */ "access permission denied", - /* SQLITE_ABORT */ "callback requested query abort", + /* SQLITE_ABORT */ "query aborted", /* SQLITE_BUSY */ "database is locked", /* SQLITE_LOCKED */ "database table is locked", /* SQLITE_NOMEM */ "out of memory", @@ -141317,17 +143015,21 @@ SQLITE_PRIVATE const char *sqlite3ErrStr(int rc){ /* SQLITE_FULL */ "database or disk is full", /* SQLITE_CANTOPEN */ "unable to open database file", /* SQLITE_PROTOCOL */ "locking protocol", - /* SQLITE_EMPTY */ "table contains no data", + /* SQLITE_EMPTY */ 0, /* SQLITE_SCHEMA */ "database schema has changed", /* SQLITE_TOOBIG */ "string or blob too big", /* SQLITE_CONSTRAINT */ "constraint failed", /* SQLITE_MISMATCH */ "datatype mismatch", - /* SQLITE_MISUSE */ "library routine called out of sequence", + /* SQLITE_MISUSE */ "bad parameter or other API misuse", +#ifdef SQLITE_DISABLE_LFS /* SQLITE_NOLFS */ "large file support is disabled", +#else + /* SQLITE_NOLFS */ 0, +#endif /* SQLITE_AUTH */ "authorization denied", - /* SQLITE_FORMAT */ "auxiliary database format error", - /* SQLITE_RANGE */ "bind or column index out of range", - /* SQLITE_NOTADB */ "file is encrypted or is not a database", + /* SQLITE_FORMAT */ 0, + /* SQLITE_RANGE */ "column index out of range", + /* SQLITE_NOTADB */ "file is not a database", }; const char *zErr = "unknown error"; switch( rc ){ @@ -142167,12 +143869,9 @@ SQLITE_API const void *sqlite3_errmsg16(sqlite3 *db){ 'o', 'u', 't', ' ', 'o', 'f', ' ', 'm', 'e', 'm', 'o', 'r', 'y', 0 }; static const u16 misuse[] = { - 'l', 'i', 'b', 'r', 'a', 'r', 'y', ' ', - 'r', 'o', 'u', 't', 'i', 'n', 'e', ' ', - 'c', 'a', 'l', 'l', 'e', 'd', ' ', - 'o', 'u', 't', ' ', - 'o', 'f', ' ', - 's', 'e', 'q', 'u', 'e', 'n', 'c', 'e', 0 + 'b', 'a', 'd', ' ', 'p', 'a', 'r', 'a', 'm', 'e', 't', 'e', 'r', ' ', + 'o', 'r', ' ', 'o', 't', 'h', 'e', 'r', ' ', 'A', 'P', 'I', ' ', + 'm', 'i', 's', 'u', 's', 'e', 0 }; const void *z; @@ -142707,26 +144406,6 @@ static int openDatabase( if( rc ) return rc; #endif - /* Only allow sensible combinations of bits in the flags argument. - ** Throw an error if any non-sense combination is used. If we - ** do not block illegal combinations here, it could trigger - ** assert() statements in deeper layers. Sensible combinations - ** are: - ** - ** 1: SQLITE_OPEN_READONLY - ** 2: SQLITE_OPEN_READWRITE - ** 6: SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE - */ - assert( SQLITE_OPEN_READONLY == 0x01 ); - assert( SQLITE_OPEN_READWRITE == 0x02 ); - assert( SQLITE_OPEN_CREATE == 0x04 ); - testcase( (1<<(flags&7))==0x02 ); /* READONLY */ - testcase( (1<<(flags&7))==0x04 ); /* READWRITE */ - testcase( (1<<(flags&7))==0x40 ); /* READWRITE | CREATE */ - if( ((1<<(flags&7)) & 0x46)==0 ){ - return SQLITE_MISUSE_BKPT; /* IMP: R-65497-44594 */ - } - if( sqlite3GlobalConfig.bCoreMutex==0 ){ isThreadsafe = 0; }else if( flags & SQLITE_OPEN_NOMUTEX ){ @@ -142817,6 +144496,9 @@ static int openDatabase( #endif #if defined(SQLITE_ENABLE_FTS3_TOKENIZER) | SQLITE_Fts3Tokenizer +#endif +#if defined(SQLITE_ENABLE_QPSG) + | SQLITE_EnableQPSG #endif ; sqlite3HashInit(&db->aCollSeq); @@ -142845,9 +144527,30 @@ static int openDatabase( db->pDfltColl = sqlite3FindCollSeq(db, SQLITE_UTF8, sqlite3StrBINARY, 0); assert( db->pDfltColl!=0 ); - /* Parse the filename/URI argument. */ + /* Parse the filename/URI argument + ** + ** Only allow sensible combinations of bits in the flags argument. + ** Throw an error if any non-sense combination is used. If we + ** do not block illegal combinations here, it could trigger + ** assert() statements in deeper layers. Sensible combinations + ** are: + ** + ** 1: SQLITE_OPEN_READONLY + ** 2: SQLITE_OPEN_READWRITE + ** 6: SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE + */ db->openFlags = flags; - rc = sqlite3ParseUri(zVfs, zFilename, &flags, &db->pVfs, &zOpen, &zErrMsg); + assert( SQLITE_OPEN_READONLY == 0x01 ); + assert( SQLITE_OPEN_READWRITE == 0x02 ); + assert( SQLITE_OPEN_CREATE == 0x04 ); + testcase( (1<<(flags&7))==0x02 ); /* READONLY */ + testcase( (1<<(flags&7))==0x04 ); /* READWRITE */ + testcase( (1<<(flags&7))==0x40 ); /* READWRITE | CREATE */ + if( ((1<<(flags&7)) & 0x46)==0 ){ + rc = SQLITE_MISUSE_BKPT; /* IMP: R-65497-44594 */ + }else{ + rc = sqlite3ParseUri(zVfs, zFilename, &flags, &db->pVfs, &zOpen, &zErrMsg); + } if( rc!=SQLITE_OK ){ if( rc==SQLITE_NOMEM ) sqlite3OomFault(db); sqlite3ErrorWithMsg(db, rc, zErrMsg ? "%s" : 0, zErrMsg); @@ -142956,6 +144659,12 @@ static int openDatabase( } #endif +#ifdef SQLITE_ENABLE_STMTVTAB + if( !db->mallocFailed && rc==SQLITE_OK){ + rc = sqlite3StmtVtabInit(db); + } +#endif + /* -DSQLITE_DEFAULT_LOCKING_MODE=1 makes EXCLUSIVE the default locking ** mode. -DSQLITE_DEFAULT_LOCKING_MODE=0 make NORMAL the default locking ** mode. Doing nothing at all also makes NORMAL the default. @@ -142998,16 +144707,18 @@ opendb_out: #endif #if defined(SQLITE_HAS_CODEC) if( rc==SQLITE_OK ){ - const char *zHexKey = sqlite3_uri_parameter(zOpen, "hexkey"); - if( zHexKey && zHexKey[0] ){ + const char *zKey; + if( (zKey = sqlite3_uri_parameter(zOpen, "hexkey"))!=0 && zKey[0] ){ u8 iByte; int i; - char zKey[40]; - for(i=0, iByte=0; i=0 && N=0 ); return 5; } @@ -146459,65 +148230,66 @@ static int fts3InitVtab( break; } } - if( iOpt==SizeofArray(aFts4Opt) ){ - sqlite3Fts3ErrMsg(pzErr, "unrecognized parameter: %s", z); - rc = SQLITE_ERROR; - }else{ - switch( iOpt ){ - case 0: /* MATCHINFO */ - if( strlen(zVal)!=4 || sqlite3_strnicmp(zVal, "fts3", 4) ){ - sqlite3Fts3ErrMsg(pzErr, "unrecognized matchinfo: %s", zVal); - rc = SQLITE_ERROR; - } - bNoDocsize = 1; - break; + switch( iOpt ){ + case 0: /* MATCHINFO */ + if( strlen(zVal)!=4 || sqlite3_strnicmp(zVal, "fts3", 4) ){ + sqlite3Fts3ErrMsg(pzErr, "unrecognized matchinfo: %s", zVal); + rc = SQLITE_ERROR; + } + bNoDocsize = 1; + break; - case 1: /* PREFIX */ - sqlite3_free(zPrefix); - zPrefix = zVal; - zVal = 0; - break; + case 1: /* PREFIX */ + sqlite3_free(zPrefix); + zPrefix = zVal; + zVal = 0; + break; - case 2: /* COMPRESS */ - sqlite3_free(zCompress); - zCompress = zVal; - zVal = 0; - break; + case 2: /* COMPRESS */ + sqlite3_free(zCompress); + zCompress = zVal; + zVal = 0; + break; - case 3: /* UNCOMPRESS */ - sqlite3_free(zUncompress); - zUncompress = zVal; - zVal = 0; - break; + case 3: /* UNCOMPRESS */ + sqlite3_free(zUncompress); + zUncompress = zVal; + zVal = 0; + break; - case 4: /* ORDER */ - if( (strlen(zVal)!=3 || sqlite3_strnicmp(zVal, "asc", 3)) - && (strlen(zVal)!=4 || sqlite3_strnicmp(zVal, "desc", 4)) - ){ - sqlite3Fts3ErrMsg(pzErr, "unrecognized order: %s", zVal); - rc = SQLITE_ERROR; - } - bDescIdx = (zVal[0]=='d' || zVal[0]=='D'); - break; + case 4: /* ORDER */ + if( (strlen(zVal)!=3 || sqlite3_strnicmp(zVal, "asc", 3)) + && (strlen(zVal)!=4 || sqlite3_strnicmp(zVal, "desc", 4)) + ){ + sqlite3Fts3ErrMsg(pzErr, "unrecognized order: %s", zVal); + rc = SQLITE_ERROR; + } + bDescIdx = (zVal[0]=='d' || zVal[0]=='D'); + break; - case 5: /* CONTENT */ - sqlite3_free(zContent); - zContent = zVal; - zVal = 0; - break; + case 5: /* CONTENT */ + sqlite3_free(zContent); + zContent = zVal; + zVal = 0; + break; - case 6: /* LANGUAGEID */ - assert( iOpt==6 ); - sqlite3_free(zLanguageid); - zLanguageid = zVal; - zVal = 0; - break; + case 6: /* LANGUAGEID */ + assert( iOpt==6 ); + sqlite3_free(zLanguageid); + zLanguageid = zVal; + zVal = 0; + break; - case 7: /* NOTINDEXED */ - azNotindexed[nNotindexed++] = zVal; - zVal = 0; - break; - } + case 7: /* NOTINDEXED */ + azNotindexed[nNotindexed++] = zVal; + zVal = 0; + break; + + default: + assert( iOpt==SizeofArray(aFts4Opt) ); + sqlite3Fts3ErrMsg(pzErr, "unrecognized parameter: %s", z); + rc = SQLITE_ERROR; + break; } sqlite3_free(zVal); } @@ -146944,6 +148716,19 @@ static void fts3CursorFinalizeStmt(Fts3Cursor *pCsr){ sqlite3_finalize(pCsr->pStmt); } +/* +** Free all resources currently held by the cursor passed as the only +** argument. +*/ +static void fts3ClearCursor(Fts3Cursor *pCsr){ + fts3CursorFinalizeStmt(pCsr); + sqlite3Fts3FreeDeferredTokens(pCsr); + sqlite3_free(pCsr->aDoclist); + sqlite3Fts3MIBufferFree(pCsr->pMIBuffer); + sqlite3Fts3ExprFree(pCsr->pExpr); + memset(&(&pCsr->base)[1], 0, sizeof(Fts3Cursor)-sizeof(sqlite3_vtab_cursor)); +} + /* ** Close the cursor. For additional information see the documentation ** on the xClose method of the virtual table interface. @@ -146951,11 +148736,7 @@ static void fts3CursorFinalizeStmt(Fts3Cursor *pCsr){ static int fts3CloseMethod(sqlite3_vtab_cursor *pCursor){ Fts3Cursor *pCsr = (Fts3Cursor *)pCursor; assert( ((Fts3Table *)pCsr->base.pVtab)->pSegments==0 ); - fts3CursorFinalizeStmt(pCsr); - sqlite3Fts3ExprFree(pCsr->pExpr); - sqlite3Fts3FreeDeferredTokens(pCsr); - sqlite3_free(pCsr->aDoclist); - sqlite3Fts3MIBufferFree(pCsr->pMIBuffer); + fts3ClearCursor(pCsr); assert( ((Fts3Table *)pCsr->base.pVtab)->pSegments==0 ); sqlite3_free(pCsr); return SQLITE_OK; @@ -146981,7 +148762,7 @@ static int fts3CursorSeekStmt(Fts3Cursor *pCsr){ }else{ zSql = sqlite3_mprintf("SELECT %s WHERE rowid = ?", p->zReadExprlist); if( !zSql ) return SQLITE_NOMEM; - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pCsr->pStmt, 0); + rc = sqlite3_prepare_v3(p->db, zSql,-1,SQLITE_PREPARE_PERSISTENT,&pCsr->pStmt,0); sqlite3_free(zSql); } if( rc==SQLITE_OK ) pCsr->bSeekStmt = 1; @@ -147086,7 +148867,8 @@ static int fts3ScanInteriorNode( isFirstTerm = 0; zCsr += fts3GetVarint32(zCsr, &nSuffix); - if( nPrefix<0 || nSuffix<0 || &zCsr[nSuffix]>zEnd ){ + assert( nPrefix>=0 && nSuffix>=0 ); + if( &zCsr[nSuffix]>zEnd ){ rc = FTS_CORRUPT_VTAB; goto finish_scan; } @@ -147896,7 +149678,7 @@ SQLITE_PRIVATE int sqlite3Fts3FirstFilter( fts3ColumnlistCopy(0, &p); } - while( paDoclist); - sqlite3Fts3MIBufferFree(pCsr->pMIBuffer); - sqlite3Fts3ExprFree(pCsr->pExpr); - memset(&pCursor[1], 0, sizeof(Fts3Cursor)-sizeof(sqlite3_vtab_cursor)); + fts3ClearCursor(pCsr); /* Set the lower and upper bounds on docids to return */ pCsr->iMinDocid = fts3DocidRange(pDocidGe, SMALLEST_INT64); @@ -148517,7 +150295,7 @@ static int fts3FilterMethod( ); } if( zSql ){ - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pCsr->pStmt, 0); + rc = sqlite3_prepare_v3(p->db,zSql,-1,SQLITE_PREPARE_PERSISTENT,&pCsr->pStmt,0); sqlite3_free(zSql); }else{ rc = SQLITE_NOMEM; @@ -148538,7 +150316,12 @@ static int fts3FilterMethod( ** routine to find out if it has reached the end of a result set. */ static int fts3EofMethod(sqlite3_vtab_cursor *pCursor){ - return ((Fts3Cursor *)pCursor)->isEof; + Fts3Cursor *pCsr = (Fts3Cursor*)pCursor; + if( pCsr->isEof ){ + fts3ClearCursor(pCsr); + pCsr->isEof = 1; + } + return pCsr->isEof; } /* @@ -148576,33 +150359,37 @@ static int fts3ColumnMethod( /* The column value supplied by SQLite must be in range. */ assert( iCol>=0 && iCol<=p->nColumn+2 ); - if( iCol==p->nColumn+1 ){ - /* This call is a request for the "docid" column. Since "docid" is an - ** alias for "rowid", use the xRowid() method to obtain the value. - */ - sqlite3_result_int64(pCtx, pCsr->iPrevId); - }else if( iCol==p->nColumn ){ - /* The extra column whose name is the same as the table. - ** Return a blob which is a pointer to the cursor. */ - sqlite3_result_blob(pCtx, &pCsr, sizeof(pCsr), SQLITE_TRANSIENT); - }else if( iCol==p->nColumn+2 && pCsr->pExpr ){ - sqlite3_result_int64(pCtx, pCsr->iLangid); - }else{ - /* The requested column is either a user column (one that contains - ** indexed data), or the language-id column. */ - rc = fts3CursorSeek(0, pCsr); + switch( iCol-p->nColumn ){ + case 0: + /* The special 'table-name' column */ + sqlite3_result_pointer(pCtx, pCsr, "fts3cursor", 0); + break; - if( rc==SQLITE_OK ){ - if( iCol==p->nColumn+2 ){ - int iLangid = 0; - if( p->zLanguageid ){ - iLangid = sqlite3_column_int(pCsr->pStmt, p->nColumn+1); - } - sqlite3_result_int(pCtx, iLangid); - }else if( sqlite3_data_count(pCsr->pStmt)>(iCol+1) ){ + case 1: + /* The docid column */ + sqlite3_result_int64(pCtx, pCsr->iPrevId); + break; + + case 2: + if( pCsr->pExpr ){ + sqlite3_result_int64(pCtx, pCsr->iLangid); + break; + }else if( p->zLanguageid==0 ){ + sqlite3_result_int(pCtx, 0); + break; + }else{ + iCol = p->nColumn; + /* fall-through */ + } + + default: + /* A user column. Or, if this is a full-table scan, possibly the + ** language-id column. Seek the cursor. */ + rc = fts3CursorSeek(0, pCsr); + if( rc==SQLITE_OK && sqlite3_data_count(pCsr->pStmt)-1>iCol ){ sqlite3_result_value(pCtx, sqlite3_column_value(pCsr->pStmt, iCol+1)); } - } + break; } assert( ((Fts3Table *)pCsr->base.pVtab)->pSegments==0 ); @@ -148682,17 +150469,11 @@ static int fts3SyncMethod(sqlite3_vtab *pVtab){ static int fts3SetHasStat(Fts3Table *p){ int rc = SQLITE_OK; if( p->bHasStat==2 ){ - const char *zFmt ="SELECT 1 FROM %Q.sqlite_master WHERE tbl_name='%q_stat'"; - char *zSql = sqlite3_mprintf(zFmt, p->zDb, p->zName); - if( zSql ){ - sqlite3_stmt *pStmt = 0; - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); - if( rc==SQLITE_OK ){ - int bHasStat = (sqlite3_step(pStmt)==SQLITE_ROW); - rc = sqlite3_finalize(pStmt); - if( rc==SQLITE_OK ) p->bHasStat = (u8)bHasStat; - } - sqlite3_free(zSql); + char *zTbl = sqlite3_mprintf("%s_stat", p->zName); + if( zTbl ){ + int res = sqlite3_table_column_metadata(p->db, p->zDb, zTbl, 0,0,0,0,0,0); + sqlite3_free(zTbl); + p->bHasStat = (res==SQLITE_OK); }else{ rc = SQLITE_NOMEM; } @@ -148799,18 +150580,17 @@ static int fts3FunctionArg( sqlite3_value *pVal, /* argv[0] passed to function */ Fts3Cursor **ppCsr /* OUT: Store cursor handle here */ ){ - Fts3Cursor *pRet; - if( sqlite3_value_type(pVal)!=SQLITE_BLOB - || sqlite3_value_bytes(pVal)!=sizeof(Fts3Cursor *) - ){ + int rc; + *ppCsr = (Fts3Cursor*)sqlite3_value_pointer(pVal, "fts3cursor"); + if( (*ppCsr)!=0 ){ + rc = SQLITE_OK; + }else{ char *zErr = sqlite3_mprintf("illegal first argument to %s", zFunc); sqlite3_result_error(pContext, zErr, -1); sqlite3_free(zErr); - return SQLITE_ERROR; + rc = SQLITE_ERROR; } - memcpy(&pRet, sqlite3_value_blob(pVal), sizeof(Fts3Cursor *)); - *ppCsr = pRet; - return SQLITE_OK; + return rc; } /* @@ -149197,7 +150977,7 @@ SQLITE_PRIVATE int sqlite3Fts3Init(sqlite3 *db){ #endif /* Create the virtual table wrapper around the hash-table and overload - ** the two scalar functions. If this is successful, register the + ** the four scalar functions. If this is successful, register the ** module with sqlite. */ if( SQLITE_OK==rc @@ -149780,7 +151560,7 @@ static int fts3EvalIncrPhraseNext( ** one incremental token. In which case the bIncr flag is set. */ assert( p->bIncr==1 ); - if( p->nToken==1 && p->bIncr ){ + if( p->nToken==1 ){ rc = sqlite3Fts3MsrIncrNext(pTab, p->aToken[0].pSegcsr, &pDL->iDocid, &pDL->pList, &pDL->nList ); @@ -150013,6 +151793,7 @@ static void fts3EvalTokenCosts( ** the number of overflow pages consumed by a record B bytes in size. */ static int fts3EvalAverageDocsize(Fts3Cursor *pCsr, int *pnPage){ + int rc = SQLITE_OK; if( pCsr->nRowAvg==0 ){ /* The average document size, which is required to calculate the cost ** of each doclist, has not yet been determined. Read the required @@ -150025,7 +151806,6 @@ static int fts3EvalAverageDocsize(Fts3Cursor *pCsr, int *pnPage){ ** data stored in all rows of each column of the table, from left ** to right. */ - int rc; Fts3Table *p = (Fts3Table*)pCsr->base.pVtab; sqlite3_stmt *pStmt; sqlite3_int64 nDoc = 0; @@ -150052,11 +151832,10 @@ static int fts3EvalAverageDocsize(Fts3Cursor *pCsr, int *pnPage){ pCsr->nRowAvg = (int)(((nByte / nDoc) + p->nPgsz) / p->nPgsz); assert( pCsr->nRowAvg>0 ); rc = sqlite3_reset(pStmt); - if( rc!=SQLITE_OK ) return rc; } *pnPage = pCsr->nRowAvg; - return SQLITE_OK; + return rc; } /* @@ -150406,7 +152185,8 @@ static void fts3EvalNextRow( pExpr->iDocid = pLeft->iDocid; pExpr->bEof = (pLeft->bEof || pRight->bEof); if( pExpr->eType==FTSQUERY_NEAR && pExpr->bEof ){ - if( pRight->pPhrase && pRight->pPhrase->doclist.aAll ){ + assert( pRight->eType==FTSQUERY_PHRASE ); + if( pRight->pPhrase->doclist.aAll ){ Fts3Doclist *pDl = &pRight->pPhrase->doclist; while( *pRc==SQLITE_OK && pRight->bEof==0 ){ memset(pDl->pList, 0, pDl->nList); @@ -150435,7 +152215,7 @@ static void fts3EvalNextRow( if( pRight->bEof || (pLeft->bEof==0 && iCmp<0) ){ fts3EvalNextRow(pCsr, pLeft, pRc); - }else if( pLeft->bEof || (pRight->bEof==0 && iCmp>0) ){ + }else if( pLeft->bEof || iCmp>0 ){ fts3EvalNextRow(pCsr, pRight, pRc); }else{ fts3EvalNextRow(pCsr, pLeft, pRc); @@ -150527,7 +152307,6 @@ static int fts3EvalNearTest(Fts3Expr *pExpr, int *pRc){ */ if( *pRc==SQLITE_OK && pExpr->eType==FTSQUERY_NEAR - && pExpr->bEof==0 && (pExpr->pParent==0 || pExpr->pParent->eType!=FTSQUERY_NEAR) ){ Fts3Expr *p; @@ -150536,42 +152315,39 @@ static int fts3EvalNearTest(Fts3Expr *pExpr, int *pRc){ /* Allocate temporary working space. */ for(p=pExpr; p->pLeft; p=p->pLeft){ + assert( p->pRight->pPhrase->doclist.nList>0 ); nTmp += p->pRight->pPhrase->doclist.nList; } nTmp += p->pPhrase->doclist.nList; - if( nTmp==0 ){ + aTmp = sqlite3_malloc(nTmp*2); + if( !aTmp ){ + *pRc = SQLITE_NOMEM; res = 0; }else{ - aTmp = sqlite3_malloc(nTmp*2); - if( !aTmp ){ - *pRc = SQLITE_NOMEM; - res = 0; - }else{ - char *aPoslist = p->pPhrase->doclist.pList; - int nToken = p->pPhrase->nToken; + char *aPoslist = p->pPhrase->doclist.pList; + int nToken = p->pPhrase->nToken; - for(p=p->pParent;res && p && p->eType==FTSQUERY_NEAR; p=p->pParent){ - Fts3Phrase *pPhrase = p->pRight->pPhrase; - int nNear = p->nNear; - res = fts3EvalNearTrim(nNear, aTmp, &aPoslist, &nToken, pPhrase); - } - - aPoslist = pExpr->pRight->pPhrase->doclist.pList; - nToken = pExpr->pRight->pPhrase->nToken; - for(p=pExpr->pLeft; p && res; p=p->pLeft){ - int nNear; - Fts3Phrase *pPhrase; - assert( p->pParent && p->pParent->pLeft==p ); - nNear = p->pParent->nNear; - pPhrase = ( - p->eType==FTSQUERY_NEAR ? p->pRight->pPhrase : p->pPhrase - ); - res = fts3EvalNearTrim(nNear, aTmp, &aPoslist, &nToken, pPhrase); - } + for(p=p->pParent;res && p && p->eType==FTSQUERY_NEAR; p=p->pParent){ + Fts3Phrase *pPhrase = p->pRight->pPhrase; + int nNear = p->nNear; + res = fts3EvalNearTrim(nNear, aTmp, &aPoslist, &nToken, pPhrase); } - sqlite3_free(aTmp); + aPoslist = pExpr->pRight->pPhrase->doclist.pList; + nToken = pExpr->pRight->pPhrase->nToken; + for(p=pExpr->pLeft; p && res; p=p->pLeft){ + int nNear; + Fts3Phrase *pPhrase; + assert( p->pParent && p->pParent->pLeft==p ); + nNear = p->pParent->nNear; + pPhrase = ( + p->eType==FTSQUERY_NEAR ? p->pRight->pPhrase : p->pPhrase + ); + res = fts3EvalNearTrim(nNear, aTmp, &aPoslist, &nToken, pPhrase); + } } + + sqlite3_free(aTmp); } return res; @@ -155731,7 +157507,8 @@ static int fts3SqlStmt( if( !zSql ){ rc = SQLITE_NOMEM; }else{ - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, NULL); + rc = sqlite3_prepare_v3(p->db, zSql, -1, SQLITE_PREPARE_PERSISTENT, + &pStmt, NULL); sqlite3_free(zSql); assert( rc==SQLITE_OK || pStmt==0 ); p->aStmt[eStmt] = pStmt; @@ -163819,14 +165596,6 @@ struct RtreeGeomCallback { void *pContext; }; - -/* -** Value for the first field of every RtreeMatchArg object. The MATCH -** operator tests that the first field of a blob operand matches this -** value to avoid operating on invalid blobs (which could cause a segfault). -*/ -#define RTREE_GEOMETRY_MAGIC 0x891245AB - /* ** An instance of this structure (in the form of a BLOB) is returned by ** the SQL functions that sqlite3_rtree_geometry_callback() and @@ -163834,7 +165603,7 @@ struct RtreeGeomCallback { ** operand to the MATCH operator of an R-Tree. */ struct RtreeMatchArg { - u32 magic; /* Always RTREE_GEOMETRY_MAGIC */ + u32 iSize; /* Size of this object */ RtreeGeomCallback cb; /* Info about the callback functions */ int nParam; /* Number of parameters to the SQL function */ sqlite3_value **apSqlParam; /* Original SQL parameter values */ @@ -165129,33 +166898,17 @@ static int findLeafNode( ** operator. */ static int deserializeGeometry(sqlite3_value *pValue, RtreeConstraint *pCons){ - RtreeMatchArg *pBlob; /* BLOB returned by geometry function */ + RtreeMatchArg *pBlob, *pSrc; /* BLOB returned by geometry function */ sqlite3_rtree_query_info *pInfo; /* Callback information */ - int nBlob; /* Size of the geometry function blob */ - int nExpected; /* Expected size of the BLOB */ - /* Check that value is actually a blob. */ - if( sqlite3_value_type(pValue)!=SQLITE_BLOB ) return SQLITE_ERROR; - - /* Check that the blob is roughly the right size. */ - nBlob = sqlite3_value_bytes(pValue); - if( nBlob<(int)sizeof(RtreeMatchArg) ){ - return SQLITE_ERROR; - } - - pInfo = (sqlite3_rtree_query_info*)sqlite3_malloc( sizeof(*pInfo)+nBlob ); + pSrc = sqlite3_value_pointer(pValue, "RtreeMatchArg"); + if( pSrc==0 ) return SQLITE_ERROR; + pInfo = (sqlite3_rtree_query_info*) + sqlite3_malloc64( sizeof(*pInfo)+pSrc->iSize ); if( !pInfo ) return SQLITE_NOMEM; memset(pInfo, 0, sizeof(*pInfo)); pBlob = (RtreeMatchArg*)&pInfo[1]; - - memcpy(pBlob, sqlite3_value_blob(pValue), nBlob); - nExpected = (int)(sizeof(RtreeMatchArg) + - pBlob->nParam*sizeof(sqlite3_value*) + - (pBlob->nParam-1)*sizeof(RtreeDValue)); - if( pBlob->magic!=RTREE_GEOMETRY_MAGIC || nBlob!=nExpected ){ - sqlite3_free(pInfo); - return SQLITE_ERROR; - } + memcpy(pBlob, pSrc, pSrc->iSize); pInfo->pContext = pBlob->cb.pContext; pInfo->nParam = pBlob->nParam; pInfo->aParam = pBlob->aParam; @@ -166678,12 +168431,36 @@ static int rtreeRename(sqlite3_vtab *pVtab, const char *zNewName){ , pRtree->zDb, pRtree->zName, zNewName ); if( zSql ){ + nodeBlobReset(pRtree); rc = sqlite3_exec(pRtree->db, zSql, 0, 0, 0); sqlite3_free(zSql); } return rc; } +/* +** The xSavepoint method. +** +** This module does not need to do anything to support savepoints. However, +** it uses this hook to close any open blob handle. This is done because a +** DROP TABLE command - which fortunately always opens a savepoint - cannot +** succeed if there are any open blob handles. i.e. if the blob handle were +** not closed here, the following would fail: +** +** BEGIN; +** INSERT INTO rtree... +** DROP TABLE ; -- Would fail with SQLITE_LOCKED +** COMMIT; +*/ +static int rtreeSavepoint(sqlite3_vtab *pVtab, int iSavepoint){ + Rtree *pRtree = (Rtree *)pVtab; + int iwt = pRtree->inWrTrans; + UNUSED_PARAMETER(iSavepoint); + pRtree->inWrTrans = 0; + nodeBlobReset(pRtree); + pRtree->inWrTrans = iwt; + return SQLITE_OK; +} /* ** This function populates the pRtree->nRowEst variable with an estimate @@ -166730,7 +168507,7 @@ static int rtreeQueryStat1(sqlite3 *db, Rtree *pRtree){ } static sqlite3_module rtreeModule = { - 0, /* iVersion */ + 2, /* iVersion */ rtreeCreate, /* xCreate - create a table */ rtreeConnect, /* xConnect - connect to an existing table */ rtreeBestIndex, /* xBestIndex - Determine search strategy */ @@ -166750,7 +168527,7 @@ static sqlite3_module rtreeModule = { rtreeEndTransaction, /* xRollback - rollback transaction */ 0, /* xFindFunction - function overloading */ rtreeRename, /* xRename - rename the table */ - 0, /* xSavepoint */ + rtreeSavepoint, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ }; @@ -166817,7 +168594,8 @@ static int rtreeSqlInit( for(i=0; iiNodeSize); if( rc!=SQLITE_OK ){ *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db)); + }else if( pRtree->iNodeSize<(512-64) ){ + rc = SQLITE_CORRUPT; + *pzErr = sqlite3_mprintf("undersize RTree blobs in \"%q_node\"", + pRtree->zName); } } @@ -167164,7 +168946,7 @@ static void geomCallback(sqlite3_context *ctx, int nArg, sqlite3_value **aArg){ sqlite3_result_error_nomem(ctx); }else{ int i; - pBlob->magic = RTREE_GEOMETRY_MAGIC; + pBlob->iSize = nBlob; pBlob->cb = pGeomCtx[0]; pBlob->apSqlParam = (sqlite3_value**)&pBlob->aParam[nArg]; pBlob->nParam = nArg; @@ -167181,7 +168963,7 @@ static void geomCallback(sqlite3_context *ctx, int nArg, sqlite3_value **aArg){ sqlite3_result_error_nomem(ctx); rtreeMatchArgFree(pBlob); }else{ - sqlite3_result_blob(ctx, pBlob, nBlob, rtreeMatchArgFree); + sqlite3_result_pointer(ctx, pBlob, "RtreeMatchArg", rtreeMatchArgFree); } } } @@ -168579,10 +170361,10 @@ SQLITE_API int sqlite3rbu_savestate(sqlite3rbu *pRbu); ** ** If an error has already occurred as part of an sqlite3rbu_step() ** or sqlite3rbu_open() call, or if one occurs within this function, an -** SQLite error code is returned. Additionally, *pzErrmsg may be set to -** point to a buffer containing a utf-8 formatted English language error -** message. It is the responsibility of the caller to eventually free any -** such buffer using sqlite3_free(). +** SQLite error code is returned. Additionally, if pzErrmsg is not NULL, +** *pzErrmsg may be set to point to a buffer containing a utf-8 formatted +** English language error message. It is the responsibility of the caller to +** eventually free any such buffer using sqlite3_free(). ** ** Otherwise, if no error occurs, this function returns SQLITE_OK if the ** update has been partially applied, or SQLITE_DONE if it has been @@ -172438,7 +174220,11 @@ SQLITE_API int sqlite3rbu_close(sqlite3rbu *p, char **pzErrmsg){ rbuEditErrmsg(p); rc = p->rc; - *pzErrmsg = p->zErrmsg; + if( pzErrmsg ){ + *pzErrmsg = p->zErrmsg; + }else{ + sqlite3_free(p->zErrmsg); + } sqlite3_free(p->zState); sqlite3_free(p); }else{ @@ -174149,6 +175935,9 @@ SQLITE_PRIVATE int sqlite3DbstatRegister(sqlite3 *db){ 0, /* xRollback */ 0, /* xFindMethod */ 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ }; return sqlite3_create_module(db, "dbstat", &dbstat_module, 0); } @@ -176996,11 +178785,12 @@ static int sessionChangesetNext( p->in.iCurrent = p->in.iNext; op = p->in.aData[p->in.iNext++]; - if( op=='T' || op=='P' ){ + while( op=='T' || op=='P' ){ p->bPatchset = (op=='P'); if( sessionChangesetReadTblhdr(p) ) return p->rc; if( (p->rc = sessionInputBuffer(&p->in, 2)) ) return p->rc; p->in.iCurrent = p->in.iNext; + if( p->in.iNext>=p->in.nData ) return SQLITE_DONE; op = p->in.aData[p->in.iNext++]; } @@ -178907,6 +180697,7 @@ static const char jsonIsSpace[] = { ** but the definitions need to be repeated for separate compilation. */ typedef sqlite3_uint64 u64; typedef unsigned int u32; + typedef unsigned short int u16; typedef unsigned char u8; #endif @@ -178986,8 +180777,19 @@ struct JsonParse { u32 *aUp; /* Index of parent of each node */ u8 oom; /* Set to true if out of memory */ u8 nErr; /* Number of errors seen */ + u16 iDepth; /* Nesting depth */ + int nJson; /* Length of the zJson string in bytes */ }; +/* +** Maximum nesting depth of JSON for this implementation. +** +** This limit is needed to avoid a stack overflow in the recursive +** descent parser. A depth of 2000 is far deeper than any sane JSON +** should go. +*/ +#define JSON_MAX_DEPTH 2000 + /************************************************************************** ** Utility routines for dealing with JsonString objects **************************************************************************/ @@ -179219,6 +181021,14 @@ static void jsonParseReset(JsonParse *pParse){ pParse->aUp = 0; } +/* +** Free a JsonParse object that was obtained from sqlite3_malloc(). +*/ +static void jsonParseFree(JsonParse *pParse){ + jsonParseReset(pParse); + sqlite3_free(pParse); +} + /* ** Convert the JsonNode pNode into a pure JSON string and ** append to pOut. Subsubstructure is also included. Return @@ -179544,15 +181354,18 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ int iThis; int x; JsonNode *pNode; - while( safe_isspace(pParse->zJson[i]) ){ i++; } - if( (c = pParse->zJson[i])=='{' ){ + const char *z = pParse->zJson; + while( safe_isspace(z[i]) ){ i++; } + if( (c = z[i])=='{' ){ /* Parse object */ iThis = jsonParseAddNode(pParse, JSON_OBJECT, 0, 0); if( iThis<0 ) return -1; for(j=i+1;;j++){ - while( safe_isspace(pParse->zJson[j]) ){ j++; } + while( safe_isspace(z[j]) ){ j++; } + if( ++pParse->iDepth > JSON_MAX_DEPTH ) return -1; x = jsonParseValue(pParse, j); if( x<0 ){ + pParse->iDepth--; if( x==(-2) && pParse->nNode==(u32)iThis+1 ) return j+1; return -1; } @@ -179561,14 +181374,15 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ if( pNode->eType!=JSON_STRING ) return -1; pNode->jnFlags |= JNODE_LABEL; j = x; - while( safe_isspace(pParse->zJson[j]) ){ j++; } - if( pParse->zJson[j]!=':' ) return -1; + while( safe_isspace(z[j]) ){ j++; } + if( z[j]!=':' ) return -1; j++; x = jsonParseValue(pParse, j); + pParse->iDepth--; if( x<0 ) return -1; j = x; - while( safe_isspace(pParse->zJson[j]) ){ j++; } - c = pParse->zJson[j]; + while( safe_isspace(z[j]) ){ j++; } + c = z[j]; if( c==',' ) continue; if( c!='}' ) return -1; break; @@ -179580,15 +181394,17 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ iThis = jsonParseAddNode(pParse, JSON_ARRAY, 0, 0); if( iThis<0 ) return -1; for(j=i+1;;j++){ - while( safe_isspace(pParse->zJson[j]) ){ j++; } + while( safe_isspace(z[j]) ){ j++; } + if( ++pParse->iDepth > JSON_MAX_DEPTH ) return -1; x = jsonParseValue(pParse, j); + pParse->iDepth--; if( x<0 ){ if( x==(-3) && pParse->nNode==(u32)iThis+1 ) return j+1; return -1; } j = x; - while( safe_isspace(pParse->zJson[j]) ){ j++; } - c = pParse->zJson[j]; + while( safe_isspace(z[j]) ){ j++; } + c = z[j]; if( c==',' ) continue; if( c!=']' ) return -1; break; @@ -179600,13 +181416,16 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ u8 jnFlags = 0; j = i+1; for(;;){ - c = pParse->zJson[j]; - if( c==0 ) return -1; + c = z[j]; + if( (c & ~0x1f)==0 ){ + /* Control characters are not allowed in strings */ + return -1; + } if( c=='\\' ){ - c = pParse->zJson[++j]; + c = z[++j]; if( c=='"' || c=='\\' || c=='/' || c=='b' || c=='f' || c=='n' || c=='r' || c=='t' - || (c=='u' && jsonIs4Hex(pParse->zJson+j+1)) ){ + || (c=='u' && jsonIs4Hex(z+j+1)) ){ jnFlags = JNODE_ESCAPE; }else{ return -1; @@ -179616,55 +181435,60 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ } j++; } - jsonParseAddNode(pParse, JSON_STRING, j+1-i, &pParse->zJson[i]); + jsonParseAddNode(pParse, JSON_STRING, j+1-i, &z[i]); if( !pParse->oom ) pParse->aNode[pParse->nNode-1].jnFlags = jnFlags; return j+1; }else if( c=='n' - && strncmp(pParse->zJson+i,"null",4)==0 - && !safe_isalnum(pParse->zJson[i+4]) ){ + && strncmp(z+i,"null",4)==0 + && !safe_isalnum(z[i+4]) ){ jsonParseAddNode(pParse, JSON_NULL, 0, 0); return i+4; }else if( c=='t' - && strncmp(pParse->zJson+i,"true",4)==0 - && !safe_isalnum(pParse->zJson[i+4]) ){ + && strncmp(z+i,"true",4)==0 + && !safe_isalnum(z[i+4]) ){ jsonParseAddNode(pParse, JSON_TRUE, 0, 0); return i+4; }else if( c=='f' - && strncmp(pParse->zJson+i,"false",5)==0 - && !safe_isalnum(pParse->zJson[i+5]) ){ + && strncmp(z+i,"false",5)==0 + && !safe_isalnum(z[i+5]) ){ jsonParseAddNode(pParse, JSON_FALSE, 0, 0); return i+5; }else if( c=='-' || (c>='0' && c<='9') ){ /* Parse number */ u8 seenDP = 0; u8 seenE = 0; + assert( '-' < '0' ); + if( c<='0' ){ + j = c=='-' ? i+1 : i; + if( z[j]=='0' && z[j+1]>='0' && z[j+1]<='9' ) return -1; + } j = i+1; for(;; j++){ - c = pParse->zJson[j]; + c = z[j]; if( c>='0' && c<='9' ) continue; if( c=='.' ){ - if( pParse->zJson[j-1]=='-' ) return -1; + if( z[j-1]=='-' ) return -1; if( seenDP ) return -1; seenDP = 1; continue; } if( c=='e' || c=='E' ){ - if( pParse->zJson[j-1]<'0' ) return -1; + if( z[j-1]<'0' ) return -1; if( seenE ) return -1; seenDP = seenE = 1; - c = pParse->zJson[j+1]; + c = z[j+1]; if( c=='+' || c=='-' ){ j++; - c = pParse->zJson[j+1]; + c = z[j+1]; } if( c<'0' || c>'9' ) return -1; continue; } break; } - if( pParse->zJson[j-1]<'0' ) return -1; + if( z[j-1]<'0' ) return -1; jsonParseAddNode(pParse, seenDP ? JSON_REAL : JSON_INT, - j - i, &pParse->zJson[i]); + j - i, &z[i]); return j; }else if( c=='}' ){ return -2; /* End of {...} */ @@ -179696,6 +181520,7 @@ static int jsonParse( i = jsonParseValue(pParse, 0); if( pParse->oom ) i = -1; if( i>0 ){ + assert( pParse->iDepth==0 ); while( safe_isspace(zJson[i]) ) i++; if( zJson[i] ) i = -1; } @@ -179755,6 +181580,49 @@ static int jsonParseFindParents(JsonParse *pParse){ return SQLITE_OK; } +/* +** Magic number used for the JSON parse cache in sqlite3_get_auxdata() +*/ +#define JSON_CACHE_ID (-429938) + +/* +** Obtain a complete parse of the JSON found in the first argument +** of the argv array. Use the sqlite3_get_auxdata() cache for this +** parse if it is available. If the cache is not available or if it +** is no longer valid, parse the JSON again and return the new parse, +** and also register the new parse so that it will be available for +** future sqlite3_get_auxdata() calls. +*/ +static JsonParse *jsonParseCached( + sqlite3_context *pCtx, + sqlite3_value **argv +){ + const char *zJson = (const char*)sqlite3_value_text(argv[0]); + int nJson = sqlite3_value_bytes(argv[0]); + JsonParse *p; + if( zJson==0 ) return 0; + p = (JsonParse*)sqlite3_get_auxdata(pCtx, JSON_CACHE_ID); + if( p && p->nJson==nJson && memcmp(p->zJson,zJson,nJson)==0 ){ + p->nErr = 0; + return p; /* The cached entry matches, so return it */ + } + p = sqlite3_malloc( sizeof(*p) + nJson + 1 ); + if( p==0 ){ + sqlite3_result_error_nomem(pCtx); + return 0; + } + memset(p, 0, sizeof(*p)); + p->zJson = (char*)&p[1]; + memcpy((char*)p->zJson, zJson, nJson+1); + if( jsonParse(p, pCtx, p->zJson) ){ + sqlite3_free(p); + return 0; + } + p->nJson = nJson; + sqlite3_set_auxdata(pCtx, JSON_CACHE_ID, p, (void(*)(void*))jsonParseFree); + return (JsonParse*)sqlite3_get_auxdata(pCtx, JSON_CACHE_ID); +} + /* ** Compare the OBJECT label at pNode against zKey,nKey. Return true on ** a match. @@ -180120,29 +181988,30 @@ static void jsonArrayLengthFunc( int argc, sqlite3_value **argv ){ - JsonParse x; /* The parse */ + JsonParse *p; /* The parse */ sqlite3_int64 n = 0; u32 i; JsonNode *pNode; - if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return; - assert( x.nNode ); + p = jsonParseCached(ctx, argv); + if( p==0 ) return; + assert( p->nNode ); if( argc==2 ){ const char *zPath = (const char*)sqlite3_value_text(argv[1]); - pNode = jsonLookup(&x, zPath, 0, ctx); + pNode = jsonLookup(p, zPath, 0, ctx); }else{ - pNode = x.aNode; + pNode = p->aNode; } if( pNode==0 ){ - x.nErr = 1; - }else if( pNode->eType==JSON_ARRAY ){ + return; + } + if( pNode->eType==JSON_ARRAY ){ assert( (pNode->jnFlags & JNODE_APPEND)==0 ); for(i=1; i<=pNode->n; n++){ i += jsonNodeSize(&pNode[i]); } } - if( x.nErr==0 ) sqlite3_result_int64(ctx, n); - jsonParseReset(&x); + sqlite3_result_int64(ctx, n); } /* @@ -180158,20 +182027,21 @@ static void jsonExtractFunc( int argc, sqlite3_value **argv ){ - JsonParse x; /* The parse */ + JsonParse *p; /* The parse */ JsonNode *pNode; const char *zPath; JsonString jx; int i; if( argc<2 ) return; - if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return; + p = jsonParseCached(ctx, argv); + if( p==0 ) return; jsonInit(&jx, ctx); jsonAppendChar(&jx, '['); for(i=1; inErr ) break; if( argc>2 ){ jsonAppendSeparator(&jx); if( pNode ){ @@ -180189,14 +182059,13 @@ static void jsonExtractFunc( sqlite3_result_subtype(ctx, JSON_SUBTYPE); } jsonReset(&jx); - jsonParseReset(&x); } /* This is the RFC 7396 MergePatch algorithm. */ static JsonNode *jsonMergePatch( JsonParse *pParse, /* The JSON parser that contains the TARGET */ - int iTarget, /* Node of the TARGET in pParse */ + u32 iTarget, /* Node of the TARGET in pParse */ JsonNode *pPatch /* The PATCH */ ){ u32 i, j; @@ -182205,9 +184074,9 @@ static int sqlite3Fts5IndexBeginWrite( /* ** Flush any data stored in the in-memory hash tables to the database. -** If the bCommit flag is true, also close any open blob handles. +** Also close any open blob handles. */ -static int sqlite3Fts5IndexSync(Fts5Index *p, int bCommit); +static int sqlite3Fts5IndexSync(Fts5Index *p); /* ** Discard any data stored in the in-memory hash tables. Do not write it @@ -182377,7 +184246,7 @@ static int sqlite3Fts5StorageDocsize(Fts5Storage *p, i64 iRowid, int *aCol); static int sqlite3Fts5StorageSize(Fts5Storage *p, int iCol, i64 *pnAvg); static int sqlite3Fts5StorageRowCount(Fts5Storage *p, i64 *pnRow); -static int sqlite3Fts5StorageSync(Fts5Storage *p, int bCommit); +static int sqlite3Fts5StorageSync(Fts5Storage *p); static int sqlite3Fts5StorageRollback(Fts5Storage *p); static int sqlite3Fts5StorageConfigValue( @@ -182413,6 +184282,7 @@ struct Fts5Token { /* Parse a MATCH expression. */ static int sqlite3Fts5ExprNew( Fts5Config *pConfig, + int iCol, /* Column on LHS of MATCH operator */ const char *zExpr, Fts5Expr **ppNew, char **pzErr @@ -182497,7 +184367,7 @@ static void sqlite3Fts5ParseNearsetFree(Fts5ExprNearset*); static void sqlite3Fts5ParseNodeFree(Fts5ExprNode*); static void sqlite3Fts5ParseSetDistance(Fts5Parse*, Fts5ExprNearset*, Fts5Token*); -static void sqlite3Fts5ParseSetColset(Fts5Parse*, Fts5ExprNearset*, Fts5Colset*); +static void sqlite3Fts5ParseSetColset(Fts5Parse*, Fts5ExprNode*, Fts5Colset*); static Fts5Colset *sqlite3Fts5ParseColsetInvert(Fts5Parse*, Fts5Colset*); static void sqlite3Fts5ParseFinished(Fts5Parse *pParse, Fts5ExprNode *p); static void sqlite3Fts5ParseNear(Fts5Parse *pParse, Fts5Token*); @@ -182554,12 +184424,12 @@ static int sqlite3Fts5UnicodeFold(int c, int bRemoveDiacritic); #define FTS5_NOT 3 #define FTS5_TERM 4 #define FTS5_COLON 5 -#define FTS5_LP 6 -#define FTS5_RP 7 -#define FTS5_MINUS 8 -#define FTS5_LCP 9 -#define FTS5_RCP 10 -#define FTS5_STRING 11 +#define FTS5_MINUS 6 +#define FTS5_LCP 7 +#define FTS5_RCP 8 +#define FTS5_STRING 9 +#define FTS5_LP 10 +#define FTS5_RP 11 #define FTS5_COMMA 12 #define FTS5_PLUS 13 #define FTS5_STAR 14 @@ -182695,16 +184565,16 @@ typedef union { #define sqlite3Fts5ParserARG_PDECL ,Fts5Parse *pParse #define sqlite3Fts5ParserARG_FETCH Fts5Parse *pParse = fts5yypParser->pParse #define sqlite3Fts5ParserARG_STORE fts5yypParser->pParse = pParse -#define fts5YYNSTATE 29 -#define fts5YYNRULE 26 -#define fts5YY_MAX_SHIFT 28 -#define fts5YY_MIN_SHIFTREDUCE 45 -#define fts5YY_MAX_SHIFTREDUCE 70 -#define fts5YY_MIN_REDUCE 71 -#define fts5YY_MAX_REDUCE 96 -#define fts5YY_ERROR_ACTION 97 -#define fts5YY_ACCEPT_ACTION 98 -#define fts5YY_NO_ACTION 99 +#define fts5YYNSTATE 33 +#define fts5YYNRULE 27 +#define fts5YY_MAX_SHIFT 32 +#define fts5YY_MIN_SHIFTREDUCE 50 +#define fts5YY_MAX_SHIFTREDUCE 76 +#define fts5YY_MIN_REDUCE 77 +#define fts5YY_MAX_REDUCE 103 +#define fts5YY_ERROR_ACTION 104 +#define fts5YY_ACCEPT_ACTION 105 +#define fts5YY_NO_ACTION 106 /************* End control #defines *******************************************/ /* Define the fts5yytestcase() macro to be a no-op if is not already defined @@ -182776,50 +184646,54 @@ typedef union { ** fts5yy_default[] Default action for each state. ** *********** Begin parsing tables **********************************************/ -#define fts5YY_ACTTAB_COUNT (85) +#define fts5YY_ACTTAB_COUNT (98) static const fts5YYACTIONTYPE fts5yy_action[] = { - /* 0 */ 98, 16, 51, 5, 53, 27, 83, 7, 26, 15, - /* 10 */ 51, 5, 53, 27, 13, 69, 26, 48, 51, 5, - /* 20 */ 53, 27, 19, 11, 26, 9, 20, 51, 5, 53, - /* 30 */ 27, 13, 22, 26, 28, 51, 5, 53, 27, 68, - /* 40 */ 1, 26, 19, 11, 17, 9, 52, 10, 53, 27, - /* 50 */ 23, 24, 26, 54, 3, 4, 2, 26, 6, 21, - /* 60 */ 49, 71, 3, 4, 2, 7, 56, 59, 55, 59, - /* 70 */ 4, 2, 12, 69, 58, 60, 18, 67, 62, 69, - /* 80 */ 25, 66, 8, 14, 2, + /* 0 */ 105, 19, 90, 6, 26, 93, 92, 24, 24, 17, + /* 10 */ 90, 6, 26, 16, 92, 54, 24, 18, 90, 6, + /* 20 */ 26, 10, 92, 12, 24, 75, 86, 90, 6, 26, + /* 30 */ 13, 92, 75, 24, 20, 90, 6, 26, 101, 92, + /* 40 */ 56, 24, 27, 90, 6, 26, 100, 92, 21, 24, + /* 50 */ 23, 15, 30, 11, 1, 91, 22, 25, 9, 92, + /* 60 */ 7, 24, 3, 4, 5, 3, 4, 5, 3, 77, + /* 70 */ 4, 5, 3, 61, 23, 15, 60, 11, 80, 12, + /* 80 */ 2, 13, 68, 10, 29, 52, 55, 75, 31, 32, + /* 90 */ 8, 28, 5, 3, 51, 55, 72, 14, }; static const fts5YYCODETYPE fts5yy_lookahead[] = { - /* 0 */ 16, 17, 18, 19, 20, 21, 5, 6, 24, 17, - /* 10 */ 18, 19, 20, 21, 11, 14, 24, 17, 18, 19, - /* 20 */ 20, 21, 8, 9, 24, 11, 17, 18, 19, 20, - /* 30 */ 21, 11, 12, 24, 17, 18, 19, 20, 21, 26, - /* 40 */ 6, 24, 8, 9, 22, 11, 18, 11, 20, 21, - /* 50 */ 24, 25, 24, 20, 1, 2, 3, 24, 23, 24, - /* 60 */ 7, 0, 1, 2, 3, 6, 10, 11, 10, 11, - /* 70 */ 2, 3, 9, 14, 11, 11, 22, 26, 7, 14, - /* 80 */ 13, 11, 5, 11, 3, + /* 0 */ 16, 17, 18, 19, 20, 22, 22, 24, 24, 17, + /* 10 */ 18, 19, 20, 7, 22, 9, 24, 17, 18, 19, + /* 20 */ 20, 10, 22, 9, 24, 14, 17, 18, 19, 20, + /* 30 */ 9, 22, 14, 24, 17, 18, 19, 20, 26, 22, + /* 40 */ 9, 24, 17, 18, 19, 20, 26, 22, 21, 24, + /* 50 */ 6, 7, 13, 9, 10, 18, 21, 20, 5, 22, + /* 60 */ 5, 24, 3, 1, 2, 3, 1, 2, 3, 0, + /* 70 */ 1, 2, 3, 11, 6, 7, 11, 9, 5, 9, + /* 80 */ 10, 9, 11, 10, 12, 8, 9, 14, 24, 25, + /* 90 */ 23, 24, 2, 3, 8, 9, 9, 9, }; -#define fts5YY_SHIFT_USE_DFLT (85) -#define fts5YY_SHIFT_COUNT (28) +#define fts5YY_SHIFT_USE_DFLT (98) +#define fts5YY_SHIFT_COUNT (32) #define fts5YY_SHIFT_MIN (0) -#define fts5YY_SHIFT_MAX (81) +#define fts5YY_SHIFT_MAX (90) static const unsigned char fts5yy_shift_ofst[] = { - /* 0 */ 34, 34, 34, 34, 34, 14, 20, 3, 36, 1, - /* 10 */ 59, 64, 64, 65, 65, 53, 61, 56, 58, 63, - /* 20 */ 68, 67, 70, 67, 71, 72, 67, 77, 81, + /* 0 */ 44, 44, 44, 44, 44, 44, 68, 70, 72, 14, + /* 10 */ 21, 73, 11, 18, 18, 31, 31, 62, 65, 69, + /* 20 */ 90, 77, 86, 6, 39, 53, 55, 59, 39, 87, + /* 30 */ 88, 39, 71, }; -#define fts5YY_REDUCE_USE_DFLT (-17) -#define fts5YY_REDUCE_COUNT (14) -#define fts5YY_REDUCE_MIN (-16) -#define fts5YY_REDUCE_MAX (54) +#define fts5YY_REDUCE_USE_DFLT (-18) +#define fts5YY_REDUCE_COUNT (16) +#define fts5YY_REDUCE_MIN (-17) +#define fts5YY_REDUCE_MAX (67) static const signed char fts5yy_reduce_ofst[] = { - /* 0 */ -16, -8, 0, 9, 17, 28, 26, 35, 33, 13, - /* 10 */ 13, 22, 54, 13, 51, + /* 0 */ -16, -8, 0, 9, 17, 25, 37, -17, 64, -17, + /* 10 */ 67, 12, 12, 12, 20, 27, 35, }; static const fts5YYACTIONTYPE fts5yy_default[] = { - /* 0 */ 97, 97, 97, 97, 97, 76, 91, 97, 97, 96, - /* 10 */ 96, 97, 97, 96, 96, 97, 97, 97, 97, 97, - /* 20 */ 73, 89, 97, 90, 97, 97, 87, 97, 72, + /* 0 */ 104, 104, 104, 104, 104, 104, 89, 104, 98, 104, + /* 10 */ 104, 103, 103, 103, 103, 104, 104, 104, 104, 104, + /* 20 */ 85, 104, 104, 104, 94, 104, 104, 84, 96, 104, + /* 30 */ 104, 97, 104, }; /********** End of lemon-generated parsing tables *****************************/ @@ -182884,6 +184758,7 @@ struct fts5yyParser { fts5yyStackEntry fts5yystk0; /* First stack entry */ #else fts5yyStackEntry fts5yystack[fts5YYSTACKDEPTH]; /* The parser's stack */ + fts5yyStackEntry *fts5yystackEnd; /* Last entry in the stack */ #endif }; typedef struct fts5yyParser fts5yyParser; @@ -182925,11 +184800,11 @@ static void sqlite3Fts5ParserTrace(FILE *TraceFILE, char *zTracePrompt){ ** are required. The following table supplies these names */ static const char *const fts5yyTokenName[] = { "$", "OR", "AND", "NOT", - "TERM", "COLON", "LP", "RP", - "MINUS", "LCP", "RCP", "STRING", + "TERM", "COLON", "MINUS", "LCP", + "RCP", "STRING", "LP", "RP", "COMMA", "PLUS", "STAR", "error", "input", "expr", "cnearset", "exprlist", - "nearset", "colset", "colsetlist", "nearphrases", + "colset", "colsetlist", "nearset", "nearphrases", "phrase", "neardist_opt", "star_opt", }; #endif /* NDEBUG */ @@ -182939,31 +184814,32 @@ static const char *const fts5yyTokenName[] = { */ static const char *const fts5yyRuleName[] = { /* 0 */ "input ::= expr", - /* 1 */ "expr ::= expr AND expr", - /* 2 */ "expr ::= expr OR expr", - /* 3 */ "expr ::= expr NOT expr", - /* 4 */ "expr ::= LP expr RP", - /* 5 */ "expr ::= exprlist", - /* 6 */ "exprlist ::= cnearset", - /* 7 */ "exprlist ::= exprlist cnearset", - /* 8 */ "cnearset ::= nearset", - /* 9 */ "cnearset ::= colset COLON nearset", - /* 10 */ "colset ::= MINUS LCP colsetlist RCP", - /* 11 */ "colset ::= LCP colsetlist RCP", - /* 12 */ "colset ::= STRING", - /* 13 */ "colset ::= MINUS STRING", - /* 14 */ "colsetlist ::= colsetlist STRING", - /* 15 */ "colsetlist ::= STRING", - /* 16 */ "nearset ::= phrase", - /* 17 */ "nearset ::= STRING LP nearphrases neardist_opt RP", - /* 18 */ "nearphrases ::= phrase", - /* 19 */ "nearphrases ::= nearphrases phrase", - /* 20 */ "neardist_opt ::=", - /* 21 */ "neardist_opt ::= COMMA STRING", - /* 22 */ "phrase ::= phrase PLUS STRING star_opt", - /* 23 */ "phrase ::= STRING star_opt", - /* 24 */ "star_opt ::= STAR", - /* 25 */ "star_opt ::=", + /* 1 */ "colset ::= MINUS LCP colsetlist RCP", + /* 2 */ "colset ::= LCP colsetlist RCP", + /* 3 */ "colset ::= STRING", + /* 4 */ "colset ::= MINUS STRING", + /* 5 */ "colsetlist ::= colsetlist STRING", + /* 6 */ "colsetlist ::= STRING", + /* 7 */ "expr ::= expr AND expr", + /* 8 */ "expr ::= expr OR expr", + /* 9 */ "expr ::= expr NOT expr", + /* 10 */ "expr ::= colset COLON LP expr RP", + /* 11 */ "expr ::= LP expr RP", + /* 12 */ "expr ::= exprlist", + /* 13 */ "exprlist ::= cnearset", + /* 14 */ "exprlist ::= exprlist cnearset", + /* 15 */ "cnearset ::= nearset", + /* 16 */ "cnearset ::= colset COLON nearset", + /* 17 */ "nearset ::= phrase", + /* 18 */ "nearset ::= STRING LP nearphrases neardist_opt RP", + /* 19 */ "nearphrases ::= phrase", + /* 20 */ "nearphrases ::= nearphrases phrase", + /* 21 */ "neardist_opt ::=", + /* 22 */ "neardist_opt ::= COMMA STRING", + /* 23 */ "phrase ::= phrase PLUS STRING star_opt", + /* 24 */ "phrase ::= STRING star_opt", + /* 25 */ "star_opt ::= STAR", + /* 26 */ "star_opt ::=", }; #endif /* NDEBUG */ @@ -183032,6 +184908,9 @@ static void sqlite3Fts5ParserInit(void *fts5yypParser){ pParser->fts5yytos = pParser->fts5yystack; pParser->fts5yystack[0].stateno = 0; pParser->fts5yystack[0].major = 0; +#if fts5YYSTACKDEPTH>0 + pParser->fts5yystackEnd = &pParser->fts5yystack[fts5YYSTACKDEPTH-1]; +#endif } #ifndef sqlite3Fts5Parser_ENGINEALWAYSONSTACK @@ -183093,16 +184972,16 @@ static void fts5yy_destructor( sqlite3Fts5ParseNodeFree((fts5yypminor->fts5yy24)); } break; - case 20: /* nearset */ + case 20: /* colset */ + case 21: /* colsetlist */ +{ + sqlite3_free((fts5yypminor->fts5yy11)); +} + break; + case 22: /* nearset */ case 23: /* nearphrases */ { sqlite3Fts5ParseNearsetFree((fts5yypminor->fts5yy46)); -} - break; - case 21: /* colset */ - case 22: /* colsetlist */ -{ - sqlite3_free((fts5yypminor->fts5yy11)); } break; case 24: /* phrase */ @@ -183330,7 +185209,7 @@ static void fts5yy_shift( } #endif #if fts5YYSTACKDEPTH>0 - if( fts5yypParser->fts5yytos>=&fts5yypParser->fts5yystack[fts5YYSTACKDEPTH] ){ + if( fts5yypParser->fts5yytos>fts5yypParser->fts5yystackEnd ){ fts5yypParser->fts5yytos--; fts5yyStackOverflow(fts5yypParser); return; @@ -183358,34 +185237,35 @@ static void fts5yy_shift( ** is used during the reduce. */ static const struct { - fts5YYCODETYPE lhs; /* Symbol on the left-hand side of the rule */ - unsigned char nrhs; /* Number of right-hand side symbols in the rule */ + fts5YYCODETYPE lhs; /* Symbol on the left-hand side of the rule */ + signed char nrhs; /* Negative of the number of RHS symbols in the rule */ } fts5yyRuleInfo[] = { - { 16, 1 }, - { 17, 3 }, - { 17, 3 }, - { 17, 3 }, - { 17, 3 }, - { 17, 1 }, - { 19, 1 }, - { 19, 2 }, - { 18, 1 }, - { 18, 3 }, - { 21, 4 }, - { 21, 3 }, - { 21, 1 }, - { 21, 2 }, - { 22, 2 }, - { 22, 1 }, - { 20, 1 }, - { 20, 5 }, - { 23, 1 }, - { 23, 2 }, + { 16, -1 }, + { 20, -4 }, + { 20, -3 }, + { 20, -1 }, + { 20, -2 }, + { 21, -2 }, + { 21, -1 }, + { 17, -3 }, + { 17, -3 }, + { 17, -3 }, + { 17, -5 }, + { 17, -3 }, + { 17, -1 }, + { 19, -1 }, + { 19, -2 }, + { 18, -1 }, + { 18, -3 }, + { 22, -1 }, + { 22, -5 }, + { 23, -1 }, + { 23, -2 }, { 25, 0 }, - { 25, 2 }, - { 24, 4 }, - { 24, 2 }, - { 26, 1 }, + { 25, -2 }, + { 24, -4 }, + { 24, -2 }, + { 26, -1 }, { 26, 0 }, }; @@ -183409,7 +185289,7 @@ static void fts5yy_reduce( if( fts5yyTraceFILE && fts5yyruleno<(int)(sizeof(fts5yyRuleName)/sizeof(fts5yyRuleName[0])) ){ fts5yysize = fts5yyRuleInfo[fts5yyruleno].nrhs; fprintf(fts5yyTraceFILE, "%sReduce [%s], go to state %d.\n", fts5yyTracePrompt, - fts5yyRuleName[fts5yyruleno], fts5yymsp[-fts5yysize].stateno); + fts5yyRuleName[fts5yyruleno], fts5yymsp[fts5yysize].stateno); } #endif /* NDEBUG */ @@ -183424,7 +185304,7 @@ static void fts5yy_reduce( } #endif #if fts5YYSTACKDEPTH>0 - if( fts5yypParser->fts5yytos>=&fts5yypParser->fts5yystack[fts5YYSTACKDEPTH-1] ){ + if( fts5yypParser->fts5yytos>=fts5yypParser->fts5yystackEnd ){ fts5yyStackOverflow(fts5yypParser); return; } @@ -183453,87 +185333,94 @@ static void fts5yy_reduce( case 0: /* input ::= expr */ { sqlite3Fts5ParseFinished(pParse, fts5yymsp[0].minor.fts5yy24); } break; - case 1: /* expr ::= expr AND expr */ -{ - fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_AND, fts5yymsp[-2].minor.fts5yy24, fts5yymsp[0].minor.fts5yy24, 0); -} - fts5yymsp[-2].minor.fts5yy24 = fts5yylhsminor.fts5yy24; - break; - case 2: /* expr ::= expr OR expr */ -{ - fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_OR, fts5yymsp[-2].minor.fts5yy24, fts5yymsp[0].minor.fts5yy24, 0); -} - fts5yymsp[-2].minor.fts5yy24 = fts5yylhsminor.fts5yy24; - break; - case 3: /* expr ::= expr NOT expr */ -{ - fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_NOT, fts5yymsp[-2].minor.fts5yy24, fts5yymsp[0].minor.fts5yy24, 0); -} - fts5yymsp[-2].minor.fts5yy24 = fts5yylhsminor.fts5yy24; - break; - case 4: /* expr ::= LP expr RP */ -{fts5yymsp[-2].minor.fts5yy24 = fts5yymsp[-1].minor.fts5yy24;} - break; - case 5: /* expr ::= exprlist */ - case 6: /* exprlist ::= cnearset */ fts5yytestcase(fts5yyruleno==6); -{fts5yylhsminor.fts5yy24 = fts5yymsp[0].minor.fts5yy24;} - fts5yymsp[0].minor.fts5yy24 = fts5yylhsminor.fts5yy24; - break; - case 7: /* exprlist ::= exprlist cnearset */ -{ - fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseImplicitAnd(pParse, fts5yymsp[-1].minor.fts5yy24, fts5yymsp[0].minor.fts5yy24); -} - fts5yymsp[-1].minor.fts5yy24 = fts5yylhsminor.fts5yy24; - break; - case 8: /* cnearset ::= nearset */ -{ - fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, fts5yymsp[0].minor.fts5yy46); -} - fts5yymsp[0].minor.fts5yy24 = fts5yylhsminor.fts5yy24; - break; - case 9: /* cnearset ::= colset COLON nearset */ -{ - sqlite3Fts5ParseSetColset(pParse, fts5yymsp[0].minor.fts5yy46, fts5yymsp[-2].minor.fts5yy11); - fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, fts5yymsp[0].minor.fts5yy46); -} - fts5yymsp[-2].minor.fts5yy24 = fts5yylhsminor.fts5yy24; - break; - case 10: /* colset ::= MINUS LCP colsetlist RCP */ + case 1: /* colset ::= MINUS LCP colsetlist RCP */ { fts5yymsp[-3].minor.fts5yy11 = sqlite3Fts5ParseColsetInvert(pParse, fts5yymsp[-1].minor.fts5yy11); } break; - case 11: /* colset ::= LCP colsetlist RCP */ + case 2: /* colset ::= LCP colsetlist RCP */ { fts5yymsp[-2].minor.fts5yy11 = fts5yymsp[-1].minor.fts5yy11; } break; - case 12: /* colset ::= STRING */ + case 3: /* colset ::= STRING */ { fts5yylhsminor.fts5yy11 = sqlite3Fts5ParseColset(pParse, 0, &fts5yymsp[0].minor.fts5yy0); } fts5yymsp[0].minor.fts5yy11 = fts5yylhsminor.fts5yy11; break; - case 13: /* colset ::= MINUS STRING */ + case 4: /* colset ::= MINUS STRING */ { fts5yymsp[-1].minor.fts5yy11 = sqlite3Fts5ParseColset(pParse, 0, &fts5yymsp[0].minor.fts5yy0); fts5yymsp[-1].minor.fts5yy11 = sqlite3Fts5ParseColsetInvert(pParse, fts5yymsp[-1].minor.fts5yy11); } break; - case 14: /* colsetlist ::= colsetlist STRING */ + case 5: /* colsetlist ::= colsetlist STRING */ { fts5yylhsminor.fts5yy11 = sqlite3Fts5ParseColset(pParse, fts5yymsp[-1].minor.fts5yy11, &fts5yymsp[0].minor.fts5yy0); } fts5yymsp[-1].minor.fts5yy11 = fts5yylhsminor.fts5yy11; break; - case 15: /* colsetlist ::= STRING */ + case 6: /* colsetlist ::= STRING */ { fts5yylhsminor.fts5yy11 = sqlite3Fts5ParseColset(pParse, 0, &fts5yymsp[0].minor.fts5yy0); } fts5yymsp[0].minor.fts5yy11 = fts5yylhsminor.fts5yy11; break; - case 16: /* nearset ::= phrase */ + case 7: /* expr ::= expr AND expr */ +{ + fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_AND, fts5yymsp[-2].minor.fts5yy24, fts5yymsp[0].minor.fts5yy24, 0); +} + fts5yymsp[-2].minor.fts5yy24 = fts5yylhsminor.fts5yy24; + break; + case 8: /* expr ::= expr OR expr */ +{ + fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_OR, fts5yymsp[-2].minor.fts5yy24, fts5yymsp[0].minor.fts5yy24, 0); +} + fts5yymsp[-2].minor.fts5yy24 = fts5yylhsminor.fts5yy24; + break; + case 9: /* expr ::= expr NOT expr */ +{ + fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_NOT, fts5yymsp[-2].minor.fts5yy24, fts5yymsp[0].minor.fts5yy24, 0); +} + fts5yymsp[-2].minor.fts5yy24 = fts5yylhsminor.fts5yy24; + break; + case 10: /* expr ::= colset COLON LP expr RP */ +{ + sqlite3Fts5ParseSetColset(pParse, fts5yymsp[-1].minor.fts5yy24, fts5yymsp[-4].minor.fts5yy11); + fts5yylhsminor.fts5yy24 = fts5yymsp[-1].minor.fts5yy24; +} + fts5yymsp[-4].minor.fts5yy24 = fts5yylhsminor.fts5yy24; + break; + case 11: /* expr ::= LP expr RP */ +{fts5yymsp[-2].minor.fts5yy24 = fts5yymsp[-1].minor.fts5yy24;} + break; + case 12: /* expr ::= exprlist */ + case 13: /* exprlist ::= cnearset */ fts5yytestcase(fts5yyruleno==13); +{fts5yylhsminor.fts5yy24 = fts5yymsp[0].minor.fts5yy24;} + fts5yymsp[0].minor.fts5yy24 = fts5yylhsminor.fts5yy24; + break; + case 14: /* exprlist ::= exprlist cnearset */ +{ + fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseImplicitAnd(pParse, fts5yymsp[-1].minor.fts5yy24, fts5yymsp[0].minor.fts5yy24); +} + fts5yymsp[-1].minor.fts5yy24 = fts5yylhsminor.fts5yy24; + break; + case 15: /* cnearset ::= nearset */ +{ + fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, fts5yymsp[0].minor.fts5yy46); +} + fts5yymsp[0].minor.fts5yy24 = fts5yylhsminor.fts5yy24; + break; + case 16: /* cnearset ::= colset COLON nearset */ +{ + fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, fts5yymsp[0].minor.fts5yy46); + sqlite3Fts5ParseSetColset(pParse, fts5yylhsminor.fts5yy24, fts5yymsp[-2].minor.fts5yy11); +} + fts5yymsp[-2].minor.fts5yy24 = fts5yylhsminor.fts5yy24; + break; + case 17: /* nearset ::= phrase */ { fts5yylhsminor.fts5yy46 = sqlite3Fts5ParseNearset(pParse, 0, fts5yymsp[0].minor.fts5yy53); } fts5yymsp[0].minor.fts5yy46 = fts5yylhsminor.fts5yy46; break; - case 17: /* nearset ::= STRING LP nearphrases neardist_opt RP */ + case 18: /* nearset ::= STRING LP nearphrases neardist_opt RP */ { sqlite3Fts5ParseNear(pParse, &fts5yymsp[-4].minor.fts5yy0); sqlite3Fts5ParseSetDistance(pParse, fts5yymsp[-2].minor.fts5yy46, &fts5yymsp[-1].minor.fts5yy0); @@ -183541,40 +185428,40 @@ static void fts5yy_reduce( } fts5yymsp[-4].minor.fts5yy46 = fts5yylhsminor.fts5yy46; break; - case 18: /* nearphrases ::= phrase */ + case 19: /* nearphrases ::= phrase */ { fts5yylhsminor.fts5yy46 = sqlite3Fts5ParseNearset(pParse, 0, fts5yymsp[0].minor.fts5yy53); } fts5yymsp[0].minor.fts5yy46 = fts5yylhsminor.fts5yy46; break; - case 19: /* nearphrases ::= nearphrases phrase */ + case 20: /* nearphrases ::= nearphrases phrase */ { fts5yylhsminor.fts5yy46 = sqlite3Fts5ParseNearset(pParse, fts5yymsp[-1].minor.fts5yy46, fts5yymsp[0].minor.fts5yy53); } fts5yymsp[-1].minor.fts5yy46 = fts5yylhsminor.fts5yy46; break; - case 20: /* neardist_opt ::= */ + case 21: /* neardist_opt ::= */ { fts5yymsp[1].minor.fts5yy0.p = 0; fts5yymsp[1].minor.fts5yy0.n = 0; } break; - case 21: /* neardist_opt ::= COMMA STRING */ + case 22: /* neardist_opt ::= COMMA STRING */ { fts5yymsp[-1].minor.fts5yy0 = fts5yymsp[0].minor.fts5yy0; } break; - case 22: /* phrase ::= phrase PLUS STRING star_opt */ + case 23: /* phrase ::= phrase PLUS STRING star_opt */ { fts5yylhsminor.fts5yy53 = sqlite3Fts5ParseTerm(pParse, fts5yymsp[-3].minor.fts5yy53, &fts5yymsp[-1].minor.fts5yy0, fts5yymsp[0].minor.fts5yy4); } fts5yymsp[-3].minor.fts5yy53 = fts5yylhsminor.fts5yy53; break; - case 23: /* phrase ::= STRING star_opt */ + case 24: /* phrase ::= STRING star_opt */ { fts5yylhsminor.fts5yy53 = sqlite3Fts5ParseTerm(pParse, 0, &fts5yymsp[-1].minor.fts5yy0, fts5yymsp[0].minor.fts5yy4); } fts5yymsp[-1].minor.fts5yy53 = fts5yylhsminor.fts5yy53; break; - case 24: /* star_opt ::= STAR */ + case 25: /* star_opt ::= STAR */ { fts5yymsp[0].minor.fts5yy4 = 1; } break; - case 25: /* star_opt ::= */ + case 26: /* star_opt ::= */ { fts5yymsp[1].minor.fts5yy4 = 0; } break; default: @@ -183584,20 +185471,24 @@ static void fts5yy_reduce( assert( fts5yyrulenofts5YY_MAX_SHIFT ){ - fts5yyact += fts5YY_MIN_REDUCE - fts5YY_MIN_SHIFTREDUCE; - } - fts5yymsp -= fts5yysize-1; + fts5yyact = fts5yy_find_reduce_action(fts5yymsp[fts5yysize].stateno,(fts5YYCODETYPE)fts5yygoto); + + /* There are no SHIFTREDUCE actions on nonterminals because the table + ** generator has simplified them to pure REDUCE actions. */ + assert( !(fts5yyact>fts5YY_MAX_SHIFT && fts5yyact<=fts5YY_MAX_SHIFTREDUCE) ); + + /* It is not possible for a REDUCE to be followed by an error */ + assert( fts5yyact!=fts5YY_ERROR_ACTION ); + + if( fts5yyact==fts5YY_ACCEPT_ACTION ){ + fts5yypParser->fts5yytos += fts5yysize; + fts5yy_accept(fts5yypParser); + }else{ + fts5yymsp += fts5yysize+1; fts5yypParser->fts5yytos = fts5yymsp; fts5yymsp->stateno = (fts5YYACTIONTYPE)fts5yyact; fts5yymsp->major = (fts5YYCODETYPE)fts5yygoto; fts5yyTraceShift(fts5yypParser, fts5yyact); - }else{ - assert( fts5yyact == fts5YY_ACCEPT_ACTION ); - fts5yypParser->fts5yytos -= fts5yysize; - fts5yy_accept(fts5yypParser); } } @@ -184616,9 +186507,11 @@ static void sqlite3Fts5BufferAppendBlob( const u8 *pData ){ assert_nc( *pRc || nData>=0 ); - if( fts5BufferGrow(pRc, pBuf, nData) ) return; - memcpy(&pBuf->p[pBuf->n], pData, nData); - pBuf->n += nData; + if( nData ){ + if( fts5BufferGrow(pRc, pBuf, nData) ) return; + memcpy(&pBuf->p[pBuf->n], pData, nData); + pBuf->n += nData; + } } /* @@ -184795,8 +186688,8 @@ static void *sqlite3Fts5MallocZero(int *pRc, int nByte){ void *pRet = 0; if( *pRc==SQLITE_OK ){ pRet = sqlite3_malloc(nByte); - if( pRet==0 && nByte>0 ){ - *pRc = SQLITE_NOMEM; + if( pRet==0 ){ + if( nByte>0 ) *pRc = SQLITE_NOMEM; }else{ memset(pRet, 0, nByte); } @@ -186117,6 +188010,7 @@ static void fts5ParseFree(void *p){ sqlite3_free(p); } static int sqlite3Fts5ExprNew( Fts5Config *pConfig, /* FTS5 Configuration */ + int iCol, const char *zExpr, /* Expression text */ Fts5Expr **ppNew, char **pzErr @@ -186141,6 +188035,18 @@ static int sqlite3Fts5ExprNew( }while( sParse.rc==SQLITE_OK && t!=FTS5_EOF ); sqlite3Fts5ParserFree(pEngine, fts5ParseFree); + /* If the LHS of the MATCH expression was a user column, apply the + ** implicit column-filter. */ + if( iColnCol && sParse.pExpr && sParse.rc==SQLITE_OK ){ + int n = sizeof(Fts5Colset); + Fts5Colset *pColset = (Fts5Colset*)sqlite3Fts5MallocZero(&sParse.rc, n); + if( pColset ){ + pColset->nCol = 1; + pColset->aiCol[0] = iCol; + sqlite3Fts5ParseSetColset(&sParse, sParse.pExpr, pColset); + } + } + assert( sParse.rc!=SQLITE_OK || sParse.zErr==0 ); if( sParse.rc==SQLITE_OK ){ *ppNew = pNew = sqlite3_malloc(sizeof(Fts5Expr)); @@ -187790,25 +189696,110 @@ static Fts5Colset *sqlite3Fts5ParseColset( return pRet; } +/* +** If argument pOrig is NULL, or if (*pRc) is set to anything other than +** SQLITE_OK when this function is called, NULL is returned. +** +** Otherwise, a copy of (*pOrig) is made into memory obtained from +** sqlite3Fts5MallocZero() and a pointer to it returned. If the allocation +** fails, (*pRc) is set to SQLITE_NOMEM and NULL is returned. +*/ +static Fts5Colset *fts5CloneColset(int *pRc, Fts5Colset *pOrig){ + Fts5Colset *pRet; + if( pOrig ){ + int nByte = sizeof(Fts5Colset) + (pOrig->nCol-1) * sizeof(int); + pRet = (Fts5Colset*)sqlite3Fts5MallocZero(pRc, nByte); + if( pRet ){ + memcpy(pRet, pOrig, nByte); + } + }else{ + pRet = 0; + } + return pRet; +} + +/* +** Remove from colset pColset any columns that are not also in colset pMerge. +*/ +static void fts5MergeColset(Fts5Colset *pColset, Fts5Colset *pMerge){ + int iIn = 0; /* Next input in pColset */ + int iMerge = 0; /* Next input in pMerge */ + int iOut = 0; /* Next output slot in pColset */ + + while( iInnCol && iMergenCol ){ + int iDiff = pColset->aiCol[iIn] - pMerge->aiCol[iMerge]; + if( iDiff==0 ){ + pColset->aiCol[iOut++] = pMerge->aiCol[iMerge]; + iMerge++; + iIn++; + }else if( iDiff>0 ){ + iMerge++; + }else{ + iIn++; + } + } + pColset->nCol = iOut; +} + +/* +** Recursively apply colset pColset to expression node pNode and all of +** its decendents. If (*ppFree) is not NULL, it contains a spare copy +** of pColset. This function may use the spare copy and set (*ppFree) to +** zero, or it may create copies of pColset using fts5CloneColset(). +*/ +static void fts5ParseSetColset( + Fts5Parse *pParse, + Fts5ExprNode *pNode, + Fts5Colset *pColset, + Fts5Colset **ppFree +){ + if( pParse->rc==SQLITE_OK ){ + assert( pNode->eType==FTS5_TERM || pNode->eType==FTS5_STRING + || pNode->eType==FTS5_AND || pNode->eType==FTS5_OR + || pNode->eType==FTS5_NOT || pNode->eType==FTS5_EOF + ); + if( pNode->eType==FTS5_STRING || pNode->eType==FTS5_TERM ){ + Fts5ExprNearset *pNear = pNode->pNear; + if( pNear->pColset ){ + fts5MergeColset(pNear->pColset, pColset); + if( pNear->pColset->nCol==0 ){ + pNode->eType = FTS5_EOF; + pNode->xNext = 0; + } + }else if( *ppFree ){ + pNear->pColset = pColset; + *ppFree = 0; + }else{ + pNear->pColset = fts5CloneColset(&pParse->rc, pColset); + } + }else{ + int i; + assert( pNode->eType!=FTS5_EOF || pNode->nChild==0 ); + for(i=0; inChild; i++){ + fts5ParseSetColset(pParse, pNode->apChild[i], pColset, ppFree); + } + } + } +} + +/* +** Apply colset pColset to expression node pExpr and all of its descendents. +*/ static void sqlite3Fts5ParseSetColset( Fts5Parse *pParse, - Fts5ExprNearset *pNear, + Fts5ExprNode *pExpr, Fts5Colset *pColset ){ + Fts5Colset *pFree = pColset; if( pParse->pConfig->eDetail==FTS5_DETAIL_NONE ){ pParse->rc = SQLITE_ERROR; pParse->zErr = sqlite3_mprintf( "fts5: column queries are not supported (detail=none)" ); - sqlite3_free(pColset); - return; - } - - if( pNear ){ - pNear->pColset = pColset; }else{ - sqlite3_free(pColset); + fts5ParseSetColset(pParse, pExpr, pColset, &pFree); } + sqlite3_free(pFree); } static void fts5ExprAssignXNext(Fts5ExprNode *pNode){ @@ -188262,7 +190253,7 @@ static void fts5ExprFunction( rc = sqlite3Fts5ConfigParse(pGlobal, db, nConfig, azConfig, &pConfig, &zErr); if( rc==SQLITE_OK ){ - rc = sqlite3Fts5ExprNew(pConfig, zExpr, &pExpr, &zErr); + rc = sqlite3Fts5ExprNew(pConfig, pConfig->nCol, zExpr, &pExpr, &zErr); } if( rc==SQLITE_OK ){ char *zText; @@ -188659,9 +190650,10 @@ struct Fts5Hash { /* ** Each entry in the hash table is represented by an object of the -** following type. Each object, its key (zKey[]) and its current data -** are stored in a single memory allocation. The position list data -** immediately follows the key data in memory. +** following type. Each object, its key (a nul-terminated string) and +** its current data are stored in a single memory allocation. The +** key immediately follows the object in memory. The position list +** data immediately follows the key data in memory. ** ** The data that follows the key is in a similar, but not identical format ** to the doclist data stored in the database. It is: @@ -188685,20 +190677,20 @@ struct Fts5HashEntry { int nAlloc; /* Total size of allocation */ int iSzPoslist; /* Offset of space for 4-byte poslist size */ int nData; /* Total bytes of data (incl. structure) */ - int nKey; /* Length of zKey[] in bytes */ + int nKey; /* Length of key in bytes */ u8 bDel; /* Set delete-flag @ iSzPoslist */ u8 bContent; /* Set content-flag (detail=none mode) */ i16 iCol; /* Column of last value written */ int iPos; /* Position of last value written */ i64 iRowid; /* Rowid of last value written */ - char zKey[8]; /* Nul-terminated entry key */ }; /* -** Size of Fts5HashEntry without the zKey[] array. +** Eqivalent to: +** +** char *fts5EntryKey(Fts5HashEntry *pEntry){ return zKey; } */ -#define FTS5_HASHENTRYSIZE (sizeof(Fts5HashEntry)-8) - +#define fts5EntryKey(p) ( ((char *)(&(p)[1])) ) /* @@ -188793,10 +190785,11 @@ static int fts5HashResize(Fts5Hash *pHash){ for(i=0; inSlot; i++){ while( apOld[i] ){ - int iHash; + unsigned int iHash; Fts5HashEntry *p = apOld[i]; apOld[i] = p->pHashNext; - iHash = fts5HashKey(nNew, (u8*)p->zKey, (int)strlen(p->zKey)); + iHash = fts5HashKey(nNew, (u8*)fts5EntryKey(p), + (int)strlen(fts5EntryKey(p))); p->pHashNext = apNew[iHash]; apNew[iHash] = p; } @@ -188867,9 +190860,10 @@ static int sqlite3Fts5HashWrite( /* Attempt to locate an existing hash entry */ iHash = fts5HashKey2(pHash->nSlot, (u8)bByte, (const u8*)pToken, nToken); for(p=pHash->aSlot[iHash]; p; p=p->pHashNext){ - if( p->zKey[0]==bByte + char *zKey = fts5EntryKey(p); + if( zKey[0]==bByte && p->nKey==nToken - && memcmp(&p->zKey[1], pToken, nToken)==0 + && memcmp(&zKey[1], pToken, nToken)==0 ){ break; } @@ -188878,7 +190872,8 @@ static int sqlite3Fts5HashWrite( /* If an existing hash entry cannot be found, create a new one. */ if( p==0 ){ /* Figure out how much space to allocate */ - int nByte = FTS5_HASHENTRYSIZE + (nToken+1) + 1 + 64; + char *zKey; + int nByte = sizeof(Fts5HashEntry) + (nToken+1) + 1 + 64; if( nByte<128 ) nByte = 128; /* Grow the Fts5Hash.aSlot[] array if necessary. */ @@ -188891,14 +190886,15 @@ static int sqlite3Fts5HashWrite( /* Allocate new Fts5HashEntry and add it to the hash table. */ p = (Fts5HashEntry*)sqlite3_malloc(nByte); if( !p ) return SQLITE_NOMEM; - memset(p, 0, FTS5_HASHENTRYSIZE); + memset(p, 0, sizeof(Fts5HashEntry)); p->nAlloc = nByte; - p->zKey[0] = bByte; - memcpy(&p->zKey[1], pToken, nToken); - assert( iHash==fts5HashKey(pHash->nSlot, (u8*)p->zKey, nToken+1) ); + zKey = fts5EntryKey(p); + zKey[0] = bByte; + memcpy(&zKey[1], pToken, nToken); + assert( iHash==fts5HashKey(pHash->nSlot, (u8*)zKey, nToken+1) ); p->nKey = nToken; - p->zKey[nToken+1] = '\0'; - p->nData = nToken+1 + 1 + FTS5_HASHENTRYSIZE; + zKey[nToken+1] = '\0'; + p->nData = nToken+1 + 1 + sizeof(Fts5HashEntry); p->pHashNext = pHash->aSlot[iHash]; pHash->aSlot[iHash] = p; pHash->nEntry++; @@ -189016,9 +191012,11 @@ static Fts5HashEntry *fts5HashEntryMerge( p1 = 0; }else{ int i = 0; - while( p1->zKey[i]==p2->zKey[i] ) i++; + char *zKey1 = fts5EntryKey(p1); + char *zKey2 = fts5EntryKey(p2); + while( zKey1[i]==zKey2[i] ) i++; - if( ((u8)p1->zKey[i])>((u8)p2->zKey[i]) ){ + if( ((u8)zKey1[i])>((u8)zKey2[i]) ){ /* p2 is smaller */ *ppOut = p2; ppOut = &p2->pScanNext; @@ -189061,7 +191059,7 @@ static int fts5HashEntrySort( for(iSlot=0; iSlotnSlot; iSlot++){ Fts5HashEntry *pIter; for(pIter=pHash->aSlot[iSlot]; pIter; pIter=pIter->pHashNext){ - if( pTerm==0 || 0==memcmp(pIter->zKey, pTerm, nTerm) ){ + if( pTerm==0 || 0==memcmp(fts5EntryKey(pIter), pTerm, nTerm) ){ Fts5HashEntry *pEntry = pIter; pEntry->pScanNext = 0; for(i=0; ap[i]; i++){ @@ -189094,16 +191092,18 @@ static int sqlite3Fts5HashQuery( int *pnDoclist /* OUT: Size of doclist in bytes */ ){ unsigned int iHash = fts5HashKey(pHash->nSlot, (const u8*)pTerm, nTerm); + char *zKey = 0; Fts5HashEntry *p; for(p=pHash->aSlot[iHash]; p; p=p->pHashNext){ - if( memcmp(p->zKey, pTerm, nTerm)==0 && p->zKey[nTerm]==0 ) break; + zKey = fts5EntryKey(p); + if( memcmp(zKey, pTerm, nTerm)==0 && zKey[nTerm]==0 ) break; } if( p ){ fts5HashAddPoslistSize(pHash, p); - *ppDoclist = (const u8*)&p->zKey[nTerm+1]; - *pnDoclist = p->nData - (FTS5_HASHENTRYSIZE + nTerm + 1); + *ppDoclist = (const u8*)&zKey[nTerm+1]; + *pnDoclist = p->nData - (sizeof(Fts5HashEntry) + nTerm + 1); }else{ *ppDoclist = 0; *pnDoclist = 0; @@ -189136,11 +191136,12 @@ static void sqlite3Fts5HashScanEntry( ){ Fts5HashEntry *p; if( (p = pHash->pScan) ){ - int nTerm = (int)strlen(p->zKey); + char *zKey = fts5EntryKey(p); + int nTerm = (int)strlen(zKey); fts5HashAddPoslistSize(pHash, p); - *pzTerm = p->zKey; - *ppDoclist = (const u8*)&p->zKey[nTerm+1]; - *pnDoclist = p->nData - (FTS5_HASHENTRYSIZE + nTerm + 1); + *pzTerm = zKey; + *ppDoclist = (const u8*)&zKey[nTerm+1]; + *pnDoclist = p->nData - (sizeof(Fts5HashEntry) + nTerm + 1); }else{ *pzTerm = 0; *ppDoclist = 0; @@ -189779,7 +191780,6 @@ static void fts5CloseReader(Fts5Index *p){ } } - /* ** Retrieve a record from the %_data table. ** @@ -189880,7 +191880,8 @@ static int fts5IndexPrepareStmt( ){ if( p->rc==SQLITE_OK ){ if( zSql ){ - p->rc = sqlite3_prepare_v2(p->pConfig->db, zSql, -1, ppStmt, 0); + p->rc = sqlite3_prepare_v3(p->pConfig->db, zSql, -1, + SQLITE_PREPARE_PERSISTENT, ppStmt, 0); }else{ p->rc = SQLITE_NOMEM; } @@ -189929,7 +191930,8 @@ static void fts5DataDelete(Fts5Index *p, i64 iFirst, i64 iLast){ if( zSql==0 ){ rc = SQLITE_NOMEM; }else{ - rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &p->pDeleter, 0); + rc = sqlite3_prepare_v3(pConfig->db, zSql, -1, + SQLITE_PREPARE_PERSISTENT, &p->pDeleter, 0); sqlite3_free(zSql); } if( rc!=SQLITE_OK ){ @@ -192030,7 +194032,8 @@ static void fts5MultiIterNext2( ){ assert( pIter->bSkipEmpty ); if( p->rc==SQLITE_OK ){ - do { + *pbNewTerm = 0; + do{ int iFirst = pIter->aFirst[1].iFirst; Fts5SegIter *pSeg = &pIter->aSeg[iFirst]; int bNewTerm = 0; @@ -192043,8 +194046,6 @@ static void fts5MultiIterNext2( fts5MultiIterAdvanced(p, pIter, iFirst, 1); fts5MultiIterSetEof(pIter); *pbNewTerm = 1; - }else{ - *pbNewTerm = 0; } fts5AssertMultiIterSetup(p, pIter); @@ -192310,23 +194311,23 @@ static int fts5IndexExtractCol( return p - (*pa); } -static int fts5IndexExtractColset ( +static void fts5IndexExtractColset( + int *pRc, Fts5Colset *pColset, /* Colset to filter on */ const u8 *pPos, int nPos, /* Position list */ Fts5Buffer *pBuf /* Output buffer */ ){ - int rc = SQLITE_OK; - int i; - - fts5BufferZero(pBuf); - for(i=0; inCol; i++){ - const u8 *pSub = pPos; - int nSub = fts5IndexExtractCol(&pSub, nPos, pColset->aiCol[i]); - if( nSub ){ - fts5BufferAppendBlob(&rc, pBuf, nSub, pSub); + if( *pRc==SQLITE_OK ){ + int i; + fts5BufferZero(pBuf); + for(i=0; inCol; i++){ + const u8 *pSub = pPos; + int nSub = fts5IndexExtractCol(&pSub, nPos, pColset->aiCol[i]); + if( nSub ){ + fts5BufferAppendBlob(pRc, pBuf, nSub, pSub); + } } } - return rc; } /* @@ -192450,8 +194451,9 @@ static void fts5IterSetOutputs_Full(Fts5Iter *pIter, Fts5SegIter *pSeg){ pIter->base.nData = fts5IndexExtractCol(&a, pSeg->nPos,pColset->aiCol[0]); pIter->base.pData = a; }else{ + int *pRc = &pIter->pIndex->rc; fts5BufferZero(&pIter->poslist); - fts5IndexExtractColset(pColset, a, pSeg->nPos, &pIter->poslist); + fts5IndexExtractColset(pRc, pColset, a, pSeg->nPos, &pIter->poslist); pIter->base.pData = pIter->poslist.p; pIter->base.nData = pIter->poslist.n; } @@ -192996,9 +194998,6 @@ static void fts5WriteFlushLeaf(Fts5Index *p, Fts5SegWriter *pWriter){ Fts5PageWriter *pPage = &pWriter->writer; i64 iRowid; -static int nCall = 0; -nCall++; - assert( (pPage->pgidx.n==0)==(pWriter->bFirstTermInPage) ); /* Set the szLeaf header field. */ @@ -193347,6 +195346,7 @@ static void fts5IndexMergeLevel( int bOldest; /* True if the output segment is the oldest */ int eDetail = p->pConfig->eDetail; const int flags = FTS5INDEX_QUERY_NOOUTPUT; + int bTermWritten = 0; /* True if current term already output */ assert( iLvlnLevel ); assert( pLvl->nMerge<=pLvl->nSeg ); @@ -193400,18 +195400,22 @@ static void fts5IndexMergeLevel( int nTerm; const u8 *pTerm; - /* Check for key annihilation. */ - if( pSegIter->nPos==0 && (bOldest || pSegIter->bDel==0) ) continue; - pTerm = fts5MultiIterTerm(pIter, &nTerm); if( nTerm!=term.n || memcmp(pTerm, term.p, nTerm) ){ if( pnRem && writer.nLeafWritten>nRem ){ break; } + fts5BufferSet(&p->rc, &term, nTerm, pTerm); + bTermWritten =0; + } + /* Check for key annihilation. */ + if( pSegIter->nPos==0 && (bOldest || pSegIter->bDel==0) ) continue; + + if( p->rc==SQLITE_OK && bTermWritten==0 ){ /* This is a new term. Append a term to the output segment. */ fts5WriteAppendTerm(p, &writer, nTerm, pTerm); - fts5BufferSet(&p->rc, &term, nTerm, pTerm); + bTermWritten = 1; } /* Append the rowid to the output */ @@ -194243,7 +196247,7 @@ static void fts5SetupPrefixIter( if( pData ){ pData->p = (u8*)&pData[1]; pData->nn = pData->szLeaf = doclist.n; - memcpy(pData->p, doclist.p, doclist.n); + if( doclist.n ) memcpy(pData->p, doclist.p, doclist.n); fts5MultiIterNew2(p, pData, bDesc, ppIter); } fts5BufferFree(&doclist); @@ -194282,10 +196286,10 @@ static int sqlite3Fts5IndexBeginWrite(Fts5Index *p, int bDelete, i64 iRowid){ /* ** Commit data to disk. */ -static int sqlite3Fts5IndexSync(Fts5Index *p, int bCommit){ +static int sqlite3Fts5IndexSync(Fts5Index *p){ assert( p->rc==SQLITE_OK ); fts5IndexFlush(p); - if( bCommit ) fts5CloseReader(p); + fts5CloseReader(p); return fts5IndexReturn(p); } @@ -194482,7 +196486,7 @@ static int sqlite3Fts5IndexQuery( if( sqlite3Fts5BufferSize(&p->rc, &buf, nToken+1)==0 ){ int iIdx = 0; /* Index to search */ - memcpy(&buf.p[1], pToken, nToken); + if( nToken ) memcpy(&buf.p[1], pToken, nToken); /* Figure out which index to search and set iIdx accordingly. If this ** is a prefix query for which there is no prefix index, set iIdx to @@ -194531,7 +196535,7 @@ static int sqlite3Fts5IndexQuery( } if( p->rc ){ - sqlite3Fts5IterClose(&pRet->base); + sqlite3Fts5IterClose((Fts5IndexIter*)pRet); pRet = 0; fts5CloseReader(p); } @@ -196149,6 +198153,7 @@ static void fts5SetUniqueFlag(sqlite3_index_info *pIdxInfo){ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ Fts5Table *pTab = (Fts5Table*)pVTab; Fts5Config *pConfig = pTab->pConfig; + const int nCol = pConfig->nCol; int idxFlags = 0; /* Parameter passed through to xFilter() */ int bHasMatch; int iNext; @@ -196174,24 +198179,34 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ int aColMap[3]; aColMap[0] = -1; - aColMap[1] = pConfig->nCol; - aColMap[2] = pConfig->nCol+1; + aColMap[1] = nCol; + aColMap[2] = nCol+1; /* Set idxFlags flags for all WHERE clause terms that will be used. */ for(i=0; inConstraint; i++){ struct sqlite3_index_constraint *p = &pInfo->aConstraint[i]; - int j; - for(j=0; jiColumn==aColMap[pC->iCol] && p->op & pC->op ){ - if( p->usable ){ + int iCol = p->iColumn; + + if( (p->op==SQLITE_INDEX_CONSTRAINT_MATCH && iCol>=0 && iCol<=nCol) + || (p->op==SQLITE_INDEX_CONSTRAINT_EQ && iCol==nCol) + ){ + /* A MATCH operator or equivalent */ + if( p->usable ){ + idxFlags = (idxFlags & 0xFFFF) | FTS5_BI_MATCH | (iCol << 16); + aConstraint[0].iConsIndex = i; + }else{ + /* As there exists an unusable MATCH constraint this is an + ** unusable plan. Set a prohibitively high cost. */ + pInfo->estimatedCost = 1e50; + return SQLITE_OK; + } + }else{ + int j; + for(j=1; jiCol] && p->op & pC->op && p->usable ){ pC->iConsIndex = i; idxFlags |= pC->fts5op; - }else if( j==0 ){ - /* As there exists an unusable MATCH constraint this is an - ** unusable plan. Set a prohibitively high cost. */ - pInfo->estimatedCost = 1e50; - return SQLITE_OK; } } } @@ -196515,7 +198530,8 @@ static int fts5PrepareStatement( if( zSql==0 ){ rc = SQLITE_NOMEM; }else{ - rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &pRet, 0); + rc = sqlite3_prepare_v3(pConfig->db, zSql, -1, + SQLITE_PREPARE_PERSISTENT, &pRet, 0); if( rc!=SQLITE_OK ){ *pConfig->pzErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(pConfig->db)); } @@ -196651,7 +198667,8 @@ static int fts5FindRankFunction(Fts5Cursor *pCsr){ char *zSql = sqlite3Fts5Mprintf(&rc, "SELECT %s", zRankArgs); if( zSql ){ sqlite3_stmt *pStmt = 0; - rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &pStmt, 0); + rc = sqlite3_prepare_v3(pConfig->db, zSql, -1, + SQLITE_PREPARE_PERSISTENT, &pStmt, 0); sqlite3_free(zSql); assert( rc==SQLITE_OK || pCsr->pRankArgStmt==0 ); if( rc==SQLITE_OK ){ @@ -196766,6 +198783,7 @@ static int fts5FilterMethod( sqlite3_value *pRowidEq = 0; /* rowid = ? expression (or NULL) */ sqlite3_value *pRowidLe = 0; /* rowid <= ? expression (or NULL) */ sqlite3_value *pRowidGe = 0; /* rowid >= ? expression (or NULL) */ + int iCol; /* Column on LHS of MATCH operator */ char **pzErrmsg = pConfig->pzErrmsg; UNUSED_PARAM(zUnused); @@ -196796,6 +198814,8 @@ static int fts5FilterMethod( if( BitFlagTest(idxNum, FTS5_BI_ROWID_EQ) ) pRowidEq = apVal[iVal++]; if( BitFlagTest(idxNum, FTS5_BI_ROWID_LE) ) pRowidLe = apVal[iVal++]; if( BitFlagTest(idxNum, FTS5_BI_ROWID_GE) ) pRowidGe = apVal[iVal++]; + iCol = (idxNum>>16); + assert( iCol>=0 && iCol<=pConfig->nCol ); assert( iVal==nVal ); bOrderByRank = ((idxNum & FTS5_BI_ORDER_RANK) ? 1 : 0); pCsr->bDesc = bDesc = ((idxNum & FTS5_BI_ORDER_DESC) ? 1 : 0); @@ -196842,7 +198862,7 @@ static int fts5FilterMethod( rc = fts5SpecialMatch(pTab, pCsr, &zExpr[1]); }else{ char **pzErr = &pTab->base.zErrMsg; - rc = sqlite3Fts5ExprNew(pConfig, zExpr, &pCsr->pExpr, pzErr); + rc = sqlite3Fts5ExprNew(pConfig, iCol, zExpr, &pCsr->pExpr, pzErr); if( rc==SQLITE_OK ){ if( bOrderByRank ){ pCsr->ePlan = FTS5_PLAN_SORTED_MATCH; @@ -197222,7 +199242,7 @@ static int fts5SyncMethod(sqlite3_vtab *pVtab){ fts5CheckTransactionState(pTab, FTS5_SYNC, 0); pTab->pConfig->pzErrmsg = &pTab->base.zErrMsg; fts5TripCursors(pTab); - rc = sqlite3Fts5StorageSync(pTab->pStorage, 1); + rc = sqlite3Fts5StorageSync(pTab->pStorage); pTab->pConfig->pzErrmsg = 0; return rc; } @@ -198033,7 +200053,7 @@ static int fts5SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){ UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */ fts5CheckTransactionState(pTab, FTS5_SAVEPOINT, iSavepoint); fts5TripCursors(pTab); - return sqlite3Fts5StorageSync(pTab->pStorage, 0); + return sqlite3Fts5StorageSync(pTab->pStorage); } /* @@ -198046,7 +200066,7 @@ static int fts5ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){ UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */ fts5CheckTransactionState(pTab, FTS5_RELEASE, iSavepoint); fts5TripCursors(pTab); - return sqlite3Fts5StorageSync(pTab->pStorage, 0); + return sqlite3Fts5StorageSync(pTab->pStorage); } /* @@ -198236,15 +200256,14 @@ static void fts5ModuleDestroy(void *pCtx){ static void fts5Fts5Func( sqlite3_context *pCtx, /* Function call context */ int nArg, /* Number of args */ - sqlite3_value **apUnused /* Function arguments */ + sqlite3_value **apArg /* Function arguments */ ){ Fts5Global *pGlobal = (Fts5Global*)sqlite3_user_data(pCtx); - char buf[8]; - UNUSED_PARAM2(nArg, apUnused); - assert( nArg==0 ); - assert( sizeof(buf)>=sizeof(pGlobal) ); - memcpy(buf, (void*)&pGlobal, sizeof(pGlobal)); - sqlite3_result_blob(pCtx, buf, sizeof(pGlobal), SQLITE_TRANSIENT); + fts5_api **ppApi; + UNUSED_PARAM(nArg); + assert( nArg==1 ); + ppApi = (fts5_api**)sqlite3_value_pointer(apArg[0], "fts5_api_ptr"); + if( ppApi ) *ppApi = &pGlobal->api; } /* @@ -198257,7 +200276,7 @@ static void fts5SourceIdFunc( ){ assert( nArg==0 ); UNUSED_PARAM2(nArg, apUnused); - sqlite3_result_text(pCtx, "fts5: 2017-03-28 18:48:43 424a0d380332858ee55bdebc4af3789f74e70a2b3ba1cf29d84b9b4bcf3e2e37", -1, SQLITE_TRANSIENT); + sqlite3_result_text(pCtx, "fts5: 2017-08-24 16:21:36 8d3a7ea6c5690d6b7c3767558f4f01b511c55463e3f9e64506801fe9b74dce34", -1, SQLITE_TRANSIENT); } static int fts5Init(sqlite3 *db){ @@ -198309,7 +200328,7 @@ static int fts5Init(sqlite3 *db){ if( rc==SQLITE_OK ) rc = sqlite3Fts5VocabInit(pGlobal, db); if( rc==SQLITE_OK ){ rc = sqlite3_create_function( - db, "fts5", 0, SQLITE_UTF8, p, fts5Fts5Func, 0, 0 + db, "fts5", 1, SQLITE_UTF8, p, fts5Fts5Func, 0, 0 ); } if( rc==SQLITE_OK ){ @@ -198511,7 +200530,8 @@ static int fts5StorageGetStmt( if( zSql==0 ){ rc = SQLITE_NOMEM; }else{ - rc = sqlite3_prepare_v2(pC->db, zSql, -1, &p->aStmt[eStmt], 0); + rc = sqlite3_prepare_v3(pC->db, zSql, -1, + SQLITE_PREPARE_PERSISTENT, &p->aStmt[eStmt], 0); sqlite3_free(zSql); if( rc!=SQLITE_OK && pzErrMsg ){ *pzErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(pC->db)); @@ -198593,7 +200613,7 @@ static void fts5StorageRenameOne( static int sqlite3Fts5StorageRename(Fts5Storage *pStorage, const char *zName){ Fts5Config *pConfig = pStorage->pConfig; - int rc = sqlite3Fts5StorageSync(pStorage, 1); + int rc = sqlite3Fts5StorageSync(pStorage); fts5StorageRenameOne(pConfig, &rc, "data", zName); fts5StorageRenameOne(pConfig, &rc, "idx", zName); @@ -199456,15 +201476,15 @@ static int sqlite3Fts5StorageRowCount(Fts5Storage *p, i64 *pnRow){ /* ** Flush any data currently held in-memory to disk. */ -static int sqlite3Fts5StorageSync(Fts5Storage *p, int bCommit){ +static int sqlite3Fts5StorageSync(Fts5Storage *p){ int rc = SQLITE_OK; i64 iLastRowid = sqlite3_last_insert_rowid(p->pConfig->db); if( p->bTotalsValid ){ rc = fts5StorageSaveTotals(p); - if( bCommit ) p->bTotalsValid = 0; + p->bTotalsValid = 0; } if( rc==SQLITE_OK ){ - rc = sqlite3Fts5IndexSync(p->pIndex, bCommit); + rc = sqlite3Fts5IndexSync(p->pIndex); } sqlite3_set_last_insert_rowid(p->pConfig->db, iLastRowid); return rc; @@ -202111,3 +204131,304 @@ static int sqlite3Fts5VocabInit(Fts5Global *pGlobal, sqlite3 *db){ #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS5) */ /************** End of fts5.c ************************************************/ +/************** Begin file stmt.c ********************************************/ +/* +** 2017-05-31 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file demonstrates an eponymous virtual table that returns information +** about all prepared statements for the database connection. +** +** Usage example: +** +** .load ./stmt +** .mode line +** .header on +** SELECT * FROM stmt; +*/ +#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_STMTVTAB) +#if !defined(SQLITEINT_H) +/* #include "sqlite3ext.h" */ +#endif +SQLITE_EXTENSION_INIT1 +/* #include */ +/* #include */ + +#ifndef SQLITE_OMIT_VIRTUALTABLE + +/* stmt_vtab is a subclass of sqlite3_vtab which will +** serve as the underlying representation of a stmt virtual table +*/ +typedef struct stmt_vtab stmt_vtab; +struct stmt_vtab { + sqlite3_vtab base; /* Base class - must be first */ + sqlite3 *db; /* Database connection for this stmt vtab */ +}; + +/* stmt_cursor is a subclass of sqlite3_vtab_cursor which will +** serve as the underlying representation of a cursor that scans +** over rows of the result +*/ +typedef struct stmt_cursor stmt_cursor; +struct stmt_cursor { + sqlite3_vtab_cursor base; /* Base class - must be first */ + sqlite3 *db; /* Database connection for this cursor */ + sqlite3_stmt *pStmt; /* Statement cursor is currently pointing at */ + sqlite3_int64 iRowid; /* The rowid */ +}; + +/* +** The stmtConnect() method is invoked to create a new +** stmt_vtab that describes the stmt virtual table. +** +** Think of this routine as the constructor for stmt_vtab objects. +** +** All this routine needs to do is: +** +** (1) Allocate the stmt_vtab object and initialize all fields. +** +** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the +** result set of queries against stmt will look like. +*/ +static int stmtConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + stmt_vtab *pNew; + int rc; + +/* Column numbers */ +#define STMT_COLUMN_SQL 0 /* SQL for the statement */ +#define STMT_COLUMN_NCOL 1 /* Number of result columns */ +#define STMT_COLUMN_RO 2 /* True if read-only */ +#define STMT_COLUMN_BUSY 3 /* True if currently busy */ +#define STMT_COLUMN_NSCAN 4 /* SQLITE_STMTSTATUS_FULLSCAN_STEP */ +#define STMT_COLUMN_NSORT 5 /* SQLITE_STMTSTATUS_SORT */ +#define STMT_COLUMN_NAIDX 6 /* SQLITE_STMTSTATUS_AUTOINDEX */ +#define STMT_COLUMN_NSTEP 7 /* SQLITE_STMTSTATUS_VM_STEP */ +#define STMT_COLUMN_REPREP 8 /* SQLITE_STMTSTATUS_REPREPARE */ +#define STMT_COLUMN_RUN 9 /* SQLITE_STMTSTATUS_RUN */ +#define STMT_COLUMN_MEM 10 /* SQLITE_STMTSTATUS_MEMUSED */ + + + rc = sqlite3_declare_vtab(db, + "CREATE TABLE x(sql,ncol,ro,busy,nscan,nsort,naidx,nstep," + "reprep,run,mem)"); + if( rc==SQLITE_OK ){ + pNew = sqlite3_malloc( sizeof(*pNew) ); + *ppVtab = (sqlite3_vtab*)pNew; + if( pNew==0 ) return SQLITE_NOMEM; + memset(pNew, 0, sizeof(*pNew)); + pNew->db = db; + } + return rc; +} + +/* +** This method is the destructor for stmt_cursor objects. +*/ +static int stmtDisconnect(sqlite3_vtab *pVtab){ + sqlite3_free(pVtab); + return SQLITE_OK; +} + +/* +** Constructor for a new stmt_cursor object. +*/ +static int stmtOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ + stmt_cursor *pCur; + pCur = sqlite3_malloc( sizeof(*pCur) ); + if( pCur==0 ) return SQLITE_NOMEM; + memset(pCur, 0, sizeof(*pCur)); + pCur->db = ((stmt_vtab*)p)->db; + *ppCursor = &pCur->base; + return SQLITE_OK; +} + +/* +** Destructor for a stmt_cursor. +*/ +static int stmtClose(sqlite3_vtab_cursor *cur){ + sqlite3_free(cur); + return SQLITE_OK; +} + + +/* +** Advance a stmt_cursor to its next row of output. +*/ +static int stmtNext(sqlite3_vtab_cursor *cur){ + stmt_cursor *pCur = (stmt_cursor*)cur; + pCur->iRowid++; + pCur->pStmt = sqlite3_next_stmt(pCur->db, pCur->pStmt); + return SQLITE_OK; +} + +/* +** Return values of columns for the row at which the stmt_cursor +** is currently pointing. +*/ +static int stmtColumn( + sqlite3_vtab_cursor *cur, /* The cursor */ + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ + int i /* Which column to return */ +){ + stmt_cursor *pCur = (stmt_cursor*)cur; + switch( i ){ + case STMT_COLUMN_SQL: { + sqlite3_result_text(ctx, sqlite3_sql(pCur->pStmt), -1, SQLITE_TRANSIENT); + break; + } + case STMT_COLUMN_NCOL: { + sqlite3_result_int(ctx, sqlite3_column_count(pCur->pStmt)); + break; + } + case STMT_COLUMN_RO: { + sqlite3_result_int(ctx, sqlite3_stmt_readonly(pCur->pStmt)); + break; + } + case STMT_COLUMN_BUSY: { + sqlite3_result_int(ctx, sqlite3_stmt_busy(pCur->pStmt)); + break; + } + case STMT_COLUMN_MEM: { + i = SQLITE_STMTSTATUS_MEMUSED + + STMT_COLUMN_NSCAN - SQLITE_STMTSTATUS_FULLSCAN_STEP; + /* Fall thru */ + } + case STMT_COLUMN_NSCAN: + case STMT_COLUMN_NSORT: + case STMT_COLUMN_NAIDX: + case STMT_COLUMN_NSTEP: + case STMT_COLUMN_REPREP: + case STMT_COLUMN_RUN: { + sqlite3_result_int(ctx, sqlite3_stmt_status(pCur->pStmt, + i-STMT_COLUMN_NSCAN+SQLITE_STMTSTATUS_FULLSCAN_STEP, 0)); + break; + } + } + return SQLITE_OK; +} + +/* +** Return the rowid for the current row. In this implementation, the +** rowid is the same as the output value. +*/ +static int stmtRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + stmt_cursor *pCur = (stmt_cursor*)cur; + *pRowid = pCur->iRowid; + return SQLITE_OK; +} + +/* +** Return TRUE if the cursor has been moved off of the last +** row of output. +*/ +static int stmtEof(sqlite3_vtab_cursor *cur){ + stmt_cursor *pCur = (stmt_cursor*)cur; + return pCur->pStmt==0; +} + +/* +** This method is called to "rewind" the stmt_cursor object back +** to the first row of output. This method is always called at least +** once prior to any call to stmtColumn() or stmtRowid() or +** stmtEof(). +*/ +static int stmtFilter( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + stmt_cursor *pCur = (stmt_cursor *)pVtabCursor; + pCur->pStmt = 0; + pCur->iRowid = 0; + return stmtNext(pVtabCursor); +} + +/* +** SQLite will invoke this method one or more times while planning a query +** that uses the stmt virtual table. This routine needs to create +** a query plan for each invocation and compute an estimated cost for that +** plan. +*/ +static int stmtBestIndex( + sqlite3_vtab *tab, + sqlite3_index_info *pIdxInfo +){ + pIdxInfo->estimatedCost = (double)500; + pIdxInfo->estimatedRows = 500; + return SQLITE_OK; +} + +/* +** This following structure defines all the methods for the +** stmt virtual table. +*/ +static sqlite3_module stmtModule = { + 0, /* iVersion */ + 0, /* xCreate */ + stmtConnect, /* xConnect */ + stmtBestIndex, /* xBestIndex */ + stmtDisconnect, /* xDisconnect */ + 0, /* xDestroy */ + stmtOpen, /* xOpen - open a cursor */ + stmtClose, /* xClose - close a cursor */ + stmtFilter, /* xFilter - configure scan constraints */ + stmtNext, /* xNext - advance a cursor */ + stmtEof, /* xEof - check for end of scan */ + stmtColumn, /* xColumn - read data */ + stmtRowid, /* xRowid - read data */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ +}; + +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + +SQLITE_PRIVATE int sqlite3StmtVtabInit(sqlite3 *db){ + int rc = SQLITE_OK; +#ifndef SQLITE_OMIT_VIRTUALTABLE + rc = sqlite3_create_module(db, "sqlite_stmt", &stmtModule, 0); +#endif + return rc; +} + +#ifndef SQLITE_CORE +#ifdef _WIN32 +__declspec(dllexport) +#endif +SQLITE_API int sqlite3_stmt_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + SQLITE_EXTENSION_INIT2(pApi); +#ifndef SQLITE_OMIT_VIRTUALTABLE + rc = sqlite3StmtVtabInit(db); +#endif + return rc; +} +#endif /* SQLITE_CORE */ +#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_STMTVTAB) */ + +/************** End of stmt.c ************************************************/ diff --git a/TMessagesProj/jni/sqlite/sqlite3.h b/TMessagesProj/jni/sqlite/sqlite3.h index 7e6afcbf6..41ccc2198 100644 --- a/TMessagesProj/jni/sqlite/sqlite3.h +++ b/TMessagesProj/jni/sqlite/sqlite3.h @@ -1,5 +1,5 @@ /* -** 2001 September 15 +** 2001-09-15 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: @@ -121,9 +121,9 @@ extern "C" { ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.18.0" -#define SQLITE_VERSION_NUMBER 3018000 -#define SQLITE_SOURCE_ID "2017-03-28 18:48:43 424a0d380332858ee55bdebc4af3789f74e70a2b3ba1cf29d84b9b4bcf3e2e37" +#define SQLITE_VERSION "3.20.1" +#define SQLITE_VERSION_NUMBER 3020001 +#define SQLITE_SOURCE_ID "2017-08-24 16:21:36 8d3a7ea6c5690d6b7c3767558f4f01b511c55463e3f9e64506801fe9b74dce34" /* ** CAPI3REF: Run-Time Library Version Numbers @@ -417,7 +417,7 @@ SQLITE_API int sqlite3_exec( */ #define SQLITE_OK 0 /* Successful result */ /* beginning-of-error-codes */ -#define SQLITE_ERROR 1 /* SQL error or missing database */ +#define SQLITE_ERROR 1 /* Generic error */ #define SQLITE_INTERNAL 2 /* Internal logic error in SQLite */ #define SQLITE_PERM 3 /* Access permission denied */ #define SQLITE_ABORT 4 /* Callback routine requested an abort */ @@ -432,7 +432,7 @@ SQLITE_API int sqlite3_exec( #define SQLITE_FULL 13 /* Insertion failed because database is full */ #define SQLITE_CANTOPEN 14 /* Unable to open the database file */ #define SQLITE_PROTOCOL 15 /* Database lock protocol error */ -#define SQLITE_EMPTY 16 /* Database is empty */ +#define SQLITE_EMPTY 16 /* Not used */ #define SQLITE_SCHEMA 17 /* The database schema changed */ #define SQLITE_TOOBIG 18 /* String or BLOB exceeds size limit */ #define SQLITE_CONSTRAINT 19 /* Abort due to constraint violation */ @@ -440,7 +440,7 @@ SQLITE_API int sqlite3_exec( #define SQLITE_MISUSE 21 /* Library used incorrectly */ #define SQLITE_NOLFS 22 /* Uses OS features not supported on host */ #define SQLITE_AUTH 23 /* Authorization denied */ -#define SQLITE_FORMAT 24 /* Auxiliary database format error */ +#define SQLITE_FORMAT 24 /* Not used */ #define SQLITE_RANGE 25 /* 2nd parameter to sqlite3_bind out of range */ #define SQLITE_NOTADB 26 /* File opened that is not a database file */ #define SQLITE_NOTICE 27 /* Notifications from sqlite3_log() */ @@ -857,7 +857,7 @@ struct sqlite3_io_methods { ** opcode allows these two values (10 retries and 25 milliseconds of delay) ** to be adjusted. The values are changed for all database connections ** within the same process. The argument is a pointer to an array of two -** integers where the first integer i the new retry count and the second +** integers where the first integer is the new retry count and the second ** integer is the delay. If either integer is negative, then the setting ** is not changed but instead the prior value of that setting is written ** into the array entry, allowing the current retry settings to be @@ -2007,6 +2007,17 @@ struct sqlite3_mem_methods { ** have been disabled - 0 if they are not disabled, 1 if they are. ** ** +**
SQLITE_DBCONFIG_ENABLE_QPSG
+**
^(The SQLITE_DBCONFIG_ENABLE_QPSG option activates or deactivates +** the [query planner stability guarantee] (QPSG). When the QPSG is active, +** a single SQL query statement will always use the same algorithm regardless +** of values of [bound parameters].)^ The QPSG disables some query optimizations +** that look at the values of bound parameters, which can make some queries +** slower. But the QPSG has the advantage of more predictable behavior. With +** the QPSG active, SQLite will always use the same query plan in the field as +** was used during testing in the lab. +**
+** ** */ #define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */ @@ -2016,6 +2027,7 @@ struct sqlite3_mem_methods { #define SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER 1004 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION 1005 /* int int* */ #define SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE 1006 /* int int* */ +#define SQLITE_DBCONFIG_ENABLE_QPSG 1007 /* int int* */ /* @@ -2211,9 +2223,6 @@ SQLITE_API int sqlite3_total_changes(sqlite3*); ** ^A call to sqlite3_interrupt(D) that occurs when there are no running ** SQL statements is a no-op and has no effect on SQL statements ** that are started after the sqlite3_interrupt() call returns. -** -** If the database connection closes while [sqlite3_interrupt()] -** is running then bad things will likely happen. */ SQLITE_API void sqlite3_interrupt(sqlite3*); @@ -2676,12 +2685,14 @@ SQLITE_API void sqlite3_randomness(int N, void *P); /* ** CAPI3REF: Compile-Time Authorization Callbacks ** METHOD: sqlite3 +** KEYWORDS: {authorizer callback} ** ** ^This routine registers an authorizer callback with a particular ** [database connection], supplied in the first argument. ** ^The authorizer callback is invoked as SQL statements are being compiled ** by [sqlite3_prepare()] or its variants [sqlite3_prepare_v2()], -** [sqlite3_prepare16()] and [sqlite3_prepare16_v2()]. ^At various +** [sqlite3_prepare_v3()], [sqlite3_prepare16()], [sqlite3_prepare16_v2()], +** and [sqlite3_prepare16_v3()]. ^At various ** points during the compilation process, as logic is being created ** to perform various actions, the authorizer callback is invoked to ** see if those actions are allowed. ^The authorizer callback should @@ -2703,8 +2714,10 @@ SQLITE_API void sqlite3_randomness(int N, void *P); ** parameter to the sqlite3_set_authorizer() interface. ^The second parameter ** to the callback is an integer [SQLITE_COPY | action code] that specifies ** the particular action to be authorized. ^The third through sixth parameters -** to the callback are zero-terminated strings that contain additional -** details about the action to be authorized. +** to the callback are either NULL pointers or zero-terminated strings +** that contain additional details about the action to be authorized. +** Applications must always be prepared to encounter a NULL pointer in any +** of the third through the sixth parameters of the authorization callback. ** ** ^If the action code is [SQLITE_READ] ** and the callback returns [SQLITE_IGNORE] then the @@ -2713,6 +2726,10 @@ SQLITE_API void sqlite3_randomness(int N, void *P); ** been read if [SQLITE_OK] had been returned. The [SQLITE_IGNORE] ** return can be used to deny an untrusted user access to individual ** columns of a table. +** ^When a table is referenced by a [SELECT] but no column values are +** extracted from that table (for example in a query like +** "SELECT count(*) FROM tab") then the [SQLITE_READ] authorizer callback +** is invoked once for that table with a column name that is an empty string. ** ^If the action code is [SQLITE_DELETE] and the callback returns ** [SQLITE_IGNORE] then the [DELETE] operation proceeds but the ** [truncate optimization] is disabled and all rows are deleted individually. @@ -3464,6 +3481,29 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); #define SQLITE_LIMIT_TRIGGER_DEPTH 10 #define SQLITE_LIMIT_WORKER_THREADS 11 +/* +** CAPI3REF: Prepare Flags +** +** These constants define various flags that can be passed into +** "prepFlags" parameter of the [sqlite3_prepare_v3()] and +** [sqlite3_prepare16_v3()] interfaces. +** +** New flags may be added in future releases of SQLite. +** +**
+** [[SQLITE_PREPARE_PERSISTENT]] ^(
SQLITE_PREPARE_PERSISTENT
+**
The SQLITE_PREPARE_PERSISTENT flag is a hint to the query planner +** that the prepared statement will be retained for a long time and +** probably reused many times.)^ ^Without this flag, [sqlite3_prepare_v3()] +** and [sqlite3_prepare16_v3()] assume that the prepared statement will +** be used just once or at most a few times and then destroyed using +** [sqlite3_finalize()] relatively soon. The current implementation acts +** on this hint by avoiding the use of [lookaside memory] so as not to +** deplete the limited store of lookaside memory. Future versions of +** SQLite may act on this hint differently. +**
+*/ +#define SQLITE_PREPARE_PERSISTENT 0x01 /* ** CAPI3REF: Compiling An SQL Statement @@ -3471,17 +3511,29 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); ** METHOD: sqlite3 ** CONSTRUCTOR: sqlite3_stmt ** -** To execute an SQL query, it must first be compiled into a byte-code -** program using one of these routines. +** To execute an SQL statement, it must first be compiled into a byte-code +** program using one of these routines. Or, in other words, these routines +** are constructors for the [prepared statement] object. +** +** The preferred routine to use is [sqlite3_prepare_v2()]. The +** [sqlite3_prepare()] interface is legacy and should be avoided. +** [sqlite3_prepare_v3()] has an extra "prepFlags" option that is used +** for special purposes. +** +** The use of the UTF-8 interfaces is preferred, as SQLite currently +** does all parsing using UTF-8. The UTF-16 interfaces are provided +** as a convenience. The UTF-16 interfaces work by converting the +** input text into UTF-8, then invoking the corresponding UTF-8 interface. ** ** The first argument, "db", is a [database connection] obtained from a ** prior successful call to [sqlite3_open()], [sqlite3_open_v2()] or ** [sqlite3_open16()]. The database connection must not have been closed. ** ** The second argument, "zSql", is the statement to be compiled, encoded -** as either UTF-8 or UTF-16. The sqlite3_prepare() and sqlite3_prepare_v2() -** interfaces use UTF-8, and sqlite3_prepare16() and sqlite3_prepare16_v2() -** use UTF-16. +** as either UTF-8 or UTF-16. The sqlite3_prepare(), sqlite3_prepare_v2(), +** and sqlite3_prepare_v3() +** interfaces use UTF-8, and sqlite3_prepare16(), sqlite3_prepare16_v2(), +** and sqlite3_prepare16_v3() use UTF-16. ** ** ^If the nByte argument is negative, then zSql is read up to the ** first zero terminator. ^If nByte is positive, then it is the @@ -3508,10 +3560,11 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); ** ^On success, the sqlite3_prepare() family of routines return [SQLITE_OK]; ** otherwise an [error code] is returned. ** -** The sqlite3_prepare_v2() and sqlite3_prepare16_v2() interfaces are -** recommended for all new programs. The two older interfaces are retained -** for backwards compatibility, but their use is discouraged. -** ^In the "v2" interfaces, the prepared statement +** The sqlite3_prepare_v2(), sqlite3_prepare_v3(), sqlite3_prepare16_v2(), +** and sqlite3_prepare16_v3() interfaces are recommended for all new programs. +** The older interfaces (sqlite3_prepare() and sqlite3_prepare16()) +** are retained for backwards compatibility, but their use is discouraged. +** ^In the "vX" interfaces, the prepared statement ** that is returned (the [sqlite3_stmt] object) contains a copy of the ** original SQL text. This causes the [sqlite3_step()] interface to ** behave differently in three ways: @@ -3544,6 +3597,12 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); ** or [GLOB] operator or if the parameter is compared to an indexed column ** and the [SQLITE_ENABLE_STAT3] compile-time option is enabled. ** +** +**

^sqlite3_prepare_v3() differs from sqlite3_prepare_v2() only in having +** the extra prepFlags parameter, which is a bit array consisting of zero or +** more of the [SQLITE_PREPARE_PERSISTENT|SQLITE_PREPARE_*] flags. ^The +** sqlite3_prepare_v2() interface works exactly the same as +** sqlite3_prepare_v3() with a zero prepFlags parameter. ** */ SQLITE_API int sqlite3_prepare( @@ -3560,6 +3619,14 @@ SQLITE_API int sqlite3_prepare_v2( sqlite3_stmt **ppStmt, /* OUT: Statement handle */ const char **pzTail /* OUT: Pointer to unused portion of zSql */ ); +SQLITE_API int sqlite3_prepare_v3( + sqlite3 *db, /* Database handle */ + const char *zSql, /* SQL statement, UTF-8 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + unsigned int prepFlags, /* Zero or more SQLITE_PREPARE_ flags */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const char **pzTail /* OUT: Pointer to unused portion of zSql */ +); SQLITE_API int sqlite3_prepare16( sqlite3 *db, /* Database handle */ const void *zSql, /* SQL statement, UTF-16 encoded */ @@ -3574,6 +3641,14 @@ SQLITE_API int sqlite3_prepare16_v2( sqlite3_stmt **ppStmt, /* OUT: Statement handle */ const void **pzTail /* OUT: Pointer to unused portion of zSql */ ); +SQLITE_API int sqlite3_prepare16_v3( + sqlite3 *db, /* Database handle */ + const void *zSql, /* SQL statement, UTF-16 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + unsigned int prepFlags, /* Zero or more SQLITE_PREPARE_ flags */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const void **pzTail /* OUT: Pointer to unused portion of zSql */ +); /* ** CAPI3REF: Retrieving Statement SQL @@ -3581,7 +3656,8 @@ SQLITE_API int sqlite3_prepare16_v2( ** ** ^The sqlite3_sql(P) interface returns a pointer to a copy of the UTF-8 ** SQL text used to create [prepared statement] P if P was -** created by either [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()]. +** created by [sqlite3_prepare_v2()], [sqlite3_prepare_v3()], +** [sqlite3_prepare16_v2()], or [sqlite3_prepare16_v3()]. ** ^The sqlite3_expanded_sql(P) interface returns a pointer to a UTF-8 ** string containing the SQL text of prepared statement P with ** [bound parameters] expanded. @@ -3705,7 +3781,7 @@ SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt*); ** The [sqlite3_value_blob | sqlite3_value_type()] family of ** interfaces require protected sqlite3_value objects. */ -typedef struct Mem sqlite3_value; +typedef struct sqlite3_value sqlite3_value; /* ** CAPI3REF: SQL Function Context Object @@ -3807,6 +3883,15 @@ typedef struct sqlite3_context sqlite3_context; ** [sqlite3_blob_open | incremental BLOB I/O] routines. ** ^A negative value for the zeroblob results in a zero-length BLOB. ** +** ^The sqlite3_bind_pointer(S,I,P,T,D) routine causes the I-th parameter in +** [prepared statement] S to have an SQL value of NULL, but to also be +** associated with the pointer P of type T. ^D is either a NULL pointer or +** a pointer to a destructor function for P. ^SQLite will invoke the +** destructor D with a single argument of P when it is finished using +** P. The T parameter should be a static string, preferably a string +** literal. The sqlite3_bind_pointer() routine is part of the +** [pointer passing interface] added for SQLite 3.20.0. +** ** ^If any of the sqlite3_bind_*() routines are called with a NULL pointer ** for the [prepared statement] or with a prepared statement for which ** [sqlite3_step()] has been called more recently than [sqlite3_reset()], @@ -3840,6 +3925,7 @@ SQLITE_API int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void(*) SQLITE_API int sqlite3_bind_text64(sqlite3_stmt*, int, const char*, sqlite3_uint64, void(*)(void*), unsigned char encoding); SQLITE_API int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*); +SQLITE_API int sqlite3_bind_pointer(sqlite3_stmt*, int, void*, const char*,void(*)(void*)); SQLITE_API int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n); SQLITE_API int sqlite3_bind_zeroblob64(sqlite3_stmt*, int, sqlite3_uint64); @@ -3883,8 +3969,8 @@ SQLITE_API int sqlite3_bind_parameter_count(sqlite3_stmt*); ** ^If the value N is out of range or if the N-th parameter is ** nameless, then NULL is returned. ^The returned string is ** always in UTF-8 encoding even if the named parameter was -** originally specified as UTF-16 in [sqlite3_prepare16()] or -** [sqlite3_prepare16_v2()]. +** originally specified as UTF-16 in [sqlite3_prepare16()], +** [sqlite3_prepare16_v2()], or [sqlite3_prepare16_v3()]. ** ** See also: [sqlite3_bind_blob|sqlite3_bind()], ** [sqlite3_bind_parameter_count()], and @@ -3901,7 +3987,8 @@ SQLITE_API const char *sqlite3_bind_parameter_name(sqlite3_stmt*, int); ** parameter to [sqlite3_bind_blob|sqlite3_bind()]. ^A zero ** is returned if no matching parameter is found. ^The parameter ** name must be given in UTF-8 even if the original statement -** was prepared from UTF-16 text using [sqlite3_prepare16_v2()]. +** was prepared from UTF-16 text using [sqlite3_prepare16_v2()] or +** [sqlite3_prepare16_v3()]. ** ** See also: [sqlite3_bind_blob|sqlite3_bind()], ** [sqlite3_bind_parameter_count()], and @@ -4055,16 +4142,18 @@ SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int); ** CAPI3REF: Evaluate An SQL Statement ** METHOD: sqlite3_stmt ** -** After a [prepared statement] has been prepared using either -** [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()] or one of the legacy +** After a [prepared statement] has been prepared using any of +** [sqlite3_prepare_v2()], [sqlite3_prepare_v3()], [sqlite3_prepare16_v2()], +** or [sqlite3_prepare16_v3()] or one of the legacy ** interfaces [sqlite3_prepare()] or [sqlite3_prepare16()], this function ** must be called one or more times to evaluate the statement. ** ** The details of the behavior of the sqlite3_step() interface depend -** on whether the statement was prepared using the newer "v2" interface -** [sqlite3_prepare_v2()] and [sqlite3_prepare16_v2()] or the older legacy -** interface [sqlite3_prepare()] and [sqlite3_prepare16()]. The use of the -** new "v2" interface is recommended for new applications but the legacy +** on whether the statement was prepared using the newer "vX" interfaces +** [sqlite3_prepare_v3()], [sqlite3_prepare_v2()], [sqlite3_prepare16_v3()], +** [sqlite3_prepare16_v2()] or the older legacy +** interfaces [sqlite3_prepare()] and [sqlite3_prepare16()]. The use of the +** new "vX" interface is recommended for new applications but the legacy ** interface will continue to be supported. ** ** ^In the legacy interface, the return value will be either [SQLITE_BUSY], @@ -4110,7 +4199,7 @@ SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int); ** other than [SQLITE_ROW] before any subsequent invocation of ** sqlite3_step(). Failure to reset the prepared statement using ** [sqlite3_reset()] would result in an [SQLITE_MISUSE] return from -** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1], +** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1]), ** sqlite3_step() began ** calling [sqlite3_reset()] automatically in this circumstance rather ** than returning [SQLITE_MISUSE]. This is not considered a compatibility @@ -4125,10 +4214,11 @@ SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int); ** specific [error codes] that better describes the error. ** We admit that this is a goofy design. The problem has been fixed ** with the "v2" interface. If you prepare all of your SQL statements -** using either [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()] instead +** using [sqlite3_prepare_v3()] or [sqlite3_prepare_v2()] +** or [sqlite3_prepare16_v2()] or [sqlite3_prepare16_v3()] instead ** of the legacy [sqlite3_prepare()] and [sqlite3_prepare16()] interfaces, ** then the more specific [error codes] are returned directly -** by sqlite3_step(). The use of the "v2" interface is recommended. +** by sqlite3_step(). The use of the "vX" interfaces is recommended. */ SQLITE_API int sqlite3_step(sqlite3_stmt*); @@ -4190,6 +4280,28 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); ** KEYWORDS: {column access functions} ** METHOD: sqlite3_stmt ** +** Summary: +**

+**
sqlite3_column_blobBLOB result +**
sqlite3_column_doubleREAL result +**
sqlite3_column_int32-bit INTEGER result +**
sqlite3_column_int6464-bit INTEGER result +**
sqlite3_column_textUTF-8 TEXT result +**
sqlite3_column_text16UTF-16 TEXT result +**
sqlite3_column_valueThe result as an +** [sqlite3_value|unprotected sqlite3_value] object. +**
    +**
sqlite3_column_bytesSize of a BLOB +** or a UTF-8 TEXT result in bytes +**
sqlite3_column_bytes16   +** →  Size of UTF-16 +** TEXT in bytes +**
sqlite3_column_typeDefault +** datatype of the result +**
+** +** Details: +** ** ^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*] @@ -4211,16 +4323,29 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); ** are called from a different thread while any of these routines ** are pending, then the results are undefined. ** +** The first six interfaces (_blob, _double, _int, _int64, _text, and _text16) +** each return the value of a result column in a specific data format. If +** the result column is not initially in the requested format (for example, +** if the query returns an integer but the sqlite3_column_text() interface +** is used to extract the value) then an automatic type conversion is performed. +** ** ^The sqlite3_column_type() routine returns the ** [SQLITE_INTEGER | datatype code] for the initial data type ** of the result column. ^The returned value is one of [SQLITE_INTEGER], -** [SQLITE_FLOAT], [SQLITE_TEXT], [SQLITE_BLOB], or [SQLITE_NULL]. The value -** returned by sqlite3_column_type() is only meaningful if no type -** conversions have occurred as described below. After a type conversion, -** the value returned by sqlite3_column_type() is undefined. Future +** [SQLITE_FLOAT], [SQLITE_TEXT], [SQLITE_BLOB], or [SQLITE_NULL]. +** The return value of sqlite3_column_type() can be used to decide which +** of the first six interface should be used to extract the column value. +** The value returned by sqlite3_column_type() is only meaningful if no +** automatic type conversions have occurred for the value in question. +** After a type conversion, the result of calling sqlite3_column_type() +** is undefined, though harmless. Future ** versions of SQLite may change the behavior of sqlite3_column_type() ** following a type conversion. ** +** If the result is a BLOB or a TEXT string, then the sqlite3_column_bytes() +** or sqlite3_column_bytes16() interfaces can be used to determine the size +** of that BLOB or string. +** ** ^If the result is a BLOB or UTF-8 string then the sqlite3_column_bytes() ** routine returns the number of bytes in that BLOB or string. ** ^If the result is a UTF-16 string, then sqlite3_column_bytes() converts @@ -4257,9 +4382,13 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); ** [sqlite3_column_value()] is used in any other way, including calls ** to routines like [sqlite3_value_int()], [sqlite3_value_text()], ** or [sqlite3_value_bytes()], the behavior is not threadsafe. +** Hence, the sqlite3_column_value() interface +** is normally only useful within the implementation of +** [application-defined SQL functions] or [virtual tables], not within +** top-level application code. ** -** These routines attempt to convert the value where appropriate. ^For -** example, if the internal representation is FLOAT and a text result +** The these routines may attempt to convert the datatype of the result. +** ^For example, if the internal representation is FLOAT and a text result ** is requested, [sqlite3_snprintf()] is used internally to perform the ** conversion automatically. ^(The following table details the conversions ** that are applied: @@ -4331,7 +4460,7 @@ SQLITE_API int 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 not pass the pointers returned +** and BLOBs is freed automatically. Do not pass the pointers returned ** from [sqlite3_column_blob()], [sqlite3_column_text()], etc. into ** [sqlite3_free()]. ** @@ -4342,15 +4471,15 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); ** [SQLITE_NOMEM].)^ */ SQLITE_API const void *sqlite3_column_blob(sqlite3_stmt*, int iCol); -SQLITE_API int sqlite3_column_bytes(sqlite3_stmt*, int iCol); -SQLITE_API int sqlite3_column_bytes16(sqlite3_stmt*, int iCol); SQLITE_API double sqlite3_column_double(sqlite3_stmt*, int iCol); SQLITE_API int sqlite3_column_int(sqlite3_stmt*, int iCol); SQLITE_API sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol); SQLITE_API const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol); SQLITE_API const void *sqlite3_column_text16(sqlite3_stmt*, int iCol); -SQLITE_API int sqlite3_column_type(sqlite3_stmt*, int iCol); SQLITE_API sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_bytes(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_bytes16(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_type(sqlite3_stmt*, int iCol); /* ** CAPI3REF: Destroy A Prepared Statement Object @@ -4584,21 +4713,40 @@ SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int6 ** CAPI3REF: Obtaining SQL Values ** METHOD: sqlite3_value ** -** The C-language implementation of SQL functions and aggregates uses -** this set of interface routines to access the parameter values on -** the function or aggregate. +** Summary: +**
+**
sqlite3_value_blobBLOB value +**
sqlite3_value_doubleREAL value +**
sqlite3_value_int32-bit INTEGER value +**
sqlite3_value_int6464-bit INTEGER value +**
sqlite3_value_pointerPointer value +**
sqlite3_value_textUTF-8 TEXT value +**
sqlite3_value_text16UTF-16 TEXT value in +** the native byteorder +**
sqlite3_value_text16beUTF-16be TEXT value +**
sqlite3_value_text16leUTF-16le TEXT value +**
    +**
sqlite3_value_bytesSize of a BLOB +** or a UTF-8 TEXT in bytes +**
sqlite3_value_bytes16   +** →  Size of UTF-16 +** TEXT in bytes +**
sqlite3_value_typeDefault +** datatype of the value +**
sqlite3_value_numeric_type   +** →  Best numeric datatype of the value +**
** -** The xFunc (for scalar functions) or xStep (for aggregates) parameters -** to [sqlite3_create_function()] and [sqlite3_create_function16()] -** define callbacks that implement the SQL functions and aggregates. -** The 3rd parameter to these callbacks is an array of pointers to -** [protected sqlite3_value] objects. There is one [sqlite3_value] object for -** each parameter to the SQL function. These routines are used to -** extract values from the [sqlite3_value] objects. +** Details: +** +** These routines extract type, size, and content information from +** [protected sqlite3_value] objects. Protected sqlite3_value objects +** are used to pass parameter information into implementation of +** [application-defined SQL functions] and [virtual tables]. ** ** These routines work only with [protected sqlite3_value] objects. ** Any attempt to use these routines on an [unprotected sqlite3_value] -** object results in undefined behavior. +** is not threadsafe. ** ** ^These routines work just like the corresponding [column access functions] ** except that these routines take a single [protected sqlite3_value] object @@ -4609,6 +4757,24 @@ SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int6 ** sqlite3_value_text16be() and sqlite3_value_text16le() interfaces ** extract UTF-16 strings as big-endian and little-endian respectively. ** +** ^If [sqlite3_value] object V was initialized +** using [sqlite3_bind_pointer(S,I,P,X,D)] or [sqlite3_result_pointer(C,P,X,D)] +** and if X and Y are strings that compare equal according to strcmp(X,Y), +** then sqlite3_value_pointer(V,Y) will return the pointer P. ^Otherwise, +** sqlite3_value_pointer(V,Y) returns a NULL. The sqlite3_bind_pointer() +** routine is part of the [pointer passing interface] added for SQLite 3.20.0. +** +** ^(The sqlite3_value_type(V) interface returns the +** [SQLITE_INTEGER | datatype code] for the initial datatype of the +** [sqlite3_value] object V. The returned value is one of [SQLITE_INTEGER], +** [SQLITE_FLOAT], [SQLITE_TEXT], [SQLITE_BLOB], or [SQLITE_NULL].)^ +** Other interfaces might change the datatype for an sqlite3_value object. +** For example, if the datatype is initially SQLITE_INTEGER and +** sqlite3_value_text(V) is called to extract a text value for that +** integer, then subsequent calls to sqlite3_value_type(V) might return +** SQLITE_TEXT. Whether or not a persistent internal datatype conversion +** occurs is undefined and may change from one release of SQLite to the next. +** ** ^(The sqlite3_value_numeric_type() interface attempts to apply ** numeric affinity to the value. This means that an attempt is ** made to convert the value to an integer or floating point. If @@ -4627,15 +4793,16 @@ SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int6 ** the SQL function that supplied the [sqlite3_value*] parameters. */ SQLITE_API const void *sqlite3_value_blob(sqlite3_value*); -SQLITE_API int sqlite3_value_bytes(sqlite3_value*); -SQLITE_API int sqlite3_value_bytes16(sqlite3_value*); SQLITE_API double sqlite3_value_double(sqlite3_value*); SQLITE_API int sqlite3_value_int(sqlite3_value*); SQLITE_API sqlite3_int64 sqlite3_value_int64(sqlite3_value*); +SQLITE_API void *sqlite3_value_pointer(sqlite3_value*, const char*); SQLITE_API const unsigned char *sqlite3_value_text(sqlite3_value*); SQLITE_API const void *sqlite3_value_text16(sqlite3_value*); SQLITE_API const void *sqlite3_value_text16le(sqlite3_value*); SQLITE_API const void *sqlite3_value_text16be(sqlite3_value*); +SQLITE_API int sqlite3_value_bytes(sqlite3_value*); +SQLITE_API int sqlite3_value_bytes16(sqlite3_value*); SQLITE_API int sqlite3_value_type(sqlite3_value*); SQLITE_API int sqlite3_value_numeric_type(sqlite3_value*); @@ -4648,10 +4815,6 @@ SQLITE_API int sqlite3_value_numeric_type(sqlite3_value*); ** information can be used to pass a limited amount of context from ** one SQL function to another. Use the [sqlite3_result_subtype()] ** routine to set the subtype for the return value of an SQL function. -** -** SQLite makes no use of subtype itself. It merely passes the subtype -** from the result of one [application-defined SQL function] into the -** input of another. */ SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value*); @@ -4759,10 +4922,11 @@ SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context*); ** the compiled regular expression can be reused on multiple ** invocations of the same function. ** -** ^The sqlite3_get_auxdata() interface returns a pointer to the metadata -** associated by the sqlite3_set_auxdata() function with the Nth argument -** value to the application-defined function. ^If there is no metadata -** associated with the function argument, this sqlite3_get_auxdata() interface +** ^The sqlite3_get_auxdata(C,N) interface returns a pointer to the metadata +** associated by the sqlite3_set_auxdata(C,N,P,X) function with the Nth argument +** value to the application-defined function. ^N is zero for the left-most +** function argument. ^If there is no metadata +** associated with the function argument, the sqlite3_get_auxdata(C,N) interface ** returns a NULL pointer. ** ** ^The sqlite3_set_auxdata(C,N,P,X) interface saves P as metadata for the N-th @@ -4793,6 +4957,10 @@ SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context*); ** function parameters that are compile-time constants, including literal ** values and [parameters] and expressions composed from the same.)^ ** +** The value of the N parameter to these interfaces should be non-negative. +** Future enhancements may make use of negative N values to define new +** kinds of function caching behavior. +** ** These routines must be called from the same thread in which ** the SQL function is running. */ @@ -4916,7 +5084,7 @@ typedef void (*sqlite3_destructor_type)(void*); ** when it has finished using that result. ** ^If the 4th parameter to the sqlite3_result_text* interfaces ** or sqlite3_result_blob is the special constant SQLITE_TRANSIENT -** then SQLite makes a copy of the result into space obtained from +** then SQLite makes a copy of the result into space obtained ** from [sqlite3_malloc()] before it returns. ** ** ^The sqlite3_result_value() interface sets the result of @@ -4929,6 +5097,17 @@ typedef void (*sqlite3_destructor_type)(void*); ** [unprotected sqlite3_value] object is required, so either ** kind of [sqlite3_value] object can be used with this interface. ** +** ^The sqlite3_result_pointer(C,P,T,D) interface sets the result to an +** SQL NULL value, just like [sqlite3_result_null(C)], except that it +** also associates the host-language pointer P or type T with that +** NULL value such that the pointer can be retrieved within an +** [application-defined SQL function] using [sqlite3_value_pointer()]. +** ^If the D parameter is not NULL, then it is a pointer to a destructor +** for the P parameter. ^SQLite invokes D with P as its only argument +** when SQLite is finished with P. The T parameter should be a static +** string and preferably a string literal. The sqlite3_result_pointer() +** routine is part of the [pointer passing interface] added for SQLite 3.20.0. +** ** If these routines are called from within the different thread ** than the one containing the application-defined function that received ** the [sqlite3_context] pointer, the results are undefined. @@ -4952,6 +5131,7 @@ SQLITE_API void sqlite3_result_text16(sqlite3_context*, const void*, int, void(* SQLITE_API void sqlite3_result_text16le(sqlite3_context*, const void*, int,void(*)(void*)); SQLITE_API void sqlite3_result_text16be(sqlite3_context*, const void*, int,void(*)(void*)); SQLITE_API void sqlite3_result_value(sqlite3_context*, sqlite3_value*); +SQLITE_API void sqlite3_result_pointer(sqlite3_context*, void*,const char*,void(*)(void*)); SQLITE_API void sqlite3_result_zeroblob(sqlite3_context*, int n); SQLITE_API int sqlite3_result_zeroblob64(sqlite3_context*, sqlite3_uint64 n); @@ -5611,7 +5791,9 @@ SQLITE_API SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N); ** ^If the column-name parameter to sqlite3_table_column_metadata() is a ** NULL pointer, then this routine simply checks for the existence of the ** table and returns SQLITE_OK if the table exists and SQLITE_ERROR if it -** does not. +** does not. If the table name parameter T in a call to +** sqlite3_table_column_metadata(X,D,T,C,...) is NULL then the result is +** undefined behavior. ** ** ^The column is identified by the second, third and fourth parameters to ** this function. ^(The second parameter is either the name of the database @@ -7124,6 +7306,24 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); ** used as a proxy for the total work done by the prepared statement. ** If the number of virtual machine operations exceeds 2147483647 ** then the value returned by this statement status code is undefined. +** +** [[SQLITE_STMTSTATUS_REPREPARE]]
SQLITE_STMTSTATUS_REPREPARE
+**
^This is the number of times that the prepare statement has been +** automatically regenerated due to schema changes or change to +** [bound parameters] that might affect the query plan. +** +** [[SQLITE_STMTSTATUS_RUN]]
SQLITE_STMTSTATUS_RUN
+**
^This is the number of times that the prepared statement has +** been run. A single "run" for the purposes of this counter is one +** or more calls to [sqlite3_step()] followed by a call to [sqlite3_reset()]. +** The counter is incremented on the first [sqlite3_step()] call of each +** cycle. +** +** [[SQLITE_STMTSTATUS_MEMUSED]]
SQLITE_STMTSTATUS_MEMUSED
+**
^This is the approximate number of bytes of heap memory +** used to store the prepared statement. ^This value is not actually +** a counter, and so the resetFlg parameter to sqlite3_stmt_status() +** is ignored when the opcode is SQLITE_STMTSTATUS_MEMUSED. **
** */ @@ -7131,6 +7331,9 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); #define SQLITE_STMTSTATUS_SORT 2 #define SQLITE_STMTSTATUS_AUTOINDEX 3 #define SQLITE_STMTSTATUS_VM_STEP 4 +#define SQLITE_STMTSTATUS_REPREPARE 5 +#define SQLITE_STMTSTATUS_RUN 6 +#define SQLITE_STMTSTATUS_MEMUSED 99 /* ** CAPI3REF: Custom Page Cache Object @@ -9387,7 +9590,7 @@ typedef struct sqlite3_changegroup sqlite3_changegroup; ** sqlite3changegroup_output() functions, also available are the streaming ** versions sqlite3changegroup_add_strm() and sqlite3changegroup_output_strm(). */ -int sqlite3changegroup_new(sqlite3_changegroup **pp); +SQLITE_API int sqlite3changegroup_new(sqlite3_changegroup **pp); /* ** CAPI3REF: Add A Changeset To A Changegroup @@ -9464,7 +9667,7 @@ int sqlite3changegroup_new(sqlite3_changegroup **pp); ** ** If no error occurs, SQLITE_OK is returned. */ -int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData); +SQLITE_API int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData); /* ** CAPI3REF: Obtain A Composite Changeset From A Changegroup @@ -9490,7 +9693,7 @@ int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData); ** responsibility of the caller to eventually free the buffer using a ** call to sqlite3_free(). */ -int sqlite3changegroup_output( +SQLITE_API int sqlite3changegroup_output( sqlite3_changegroup*, int *pnData, /* OUT: Size of output buffer in bytes */ void **ppData /* OUT: Pointer to output buffer */ @@ -9499,7 +9702,7 @@ int sqlite3changegroup_output( /* ** CAPI3REF: Delete A Changegroup Object */ -void sqlite3changegroup_delete(sqlite3_changegroup*); +SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*); /* ** CAPI3REF: Apply A Changeset To A Database @@ -9888,11 +10091,11 @@ SQLITE_API int sqlite3session_patchset_strm( int (*xOutput)(void *pOut, const void *pData, int nData), void *pOut ); -int sqlite3changegroup_add_strm(sqlite3_changegroup*, +SQLITE_API int sqlite3changegroup_add_strm(sqlite3_changegroup*, int (*xInput)(void *pIn, void *pData, int *pnData), void *pIn ); -int sqlite3changegroup_output_strm(sqlite3_changegroup*, +SQLITE_API int sqlite3changegroup_output_strm(sqlite3_changegroup*, int (*xOutput)(void *pOut, const void *pData, int nData), void *pOut ); diff --git a/TMessagesProj/jni/tgnet/ApiScheme.cpp b/TMessagesProj/jni/tgnet/ApiScheme.cpp index 0a6cb163b..2ec339007 100644 --- a/TMessagesProj/jni/tgnet/ApiScheme.cpp +++ b/TMessagesProj/jni/tgnet/ApiScheme.cpp @@ -214,6 +214,8 @@ void TL_config::readParams(NativeByteBuffer *stream, bool &error) { edit_time_limit = stream->readInt32(&error); rating_e_decay = stream->readInt32(&error); stickers_recent_limit = stream->readInt32(&error); + stickers_faved_limit = stream->readInt32(&error); + channels_read_media_period = stream->readInt32(&error); if ((flags & 1) != 0) { tmp_sessions = stream->readInt32(&error); } @@ -274,6 +276,8 @@ void TL_config::serializeToStream(NativeByteBuffer *stream) { stream->writeInt32(edit_time_limit); stream->writeInt32(rating_e_decay); stream->writeInt32(stickers_recent_limit); + stream->writeInt32(stickers_faved_limit); + stream->writeInt32(channels_read_media_period); if ((flags & 1) != 0) { stream->writeInt32(tmp_sessions); } diff --git a/TMessagesProj/jni/tgnet/ApiScheme.h b/TMessagesProj/jni/tgnet/ApiScheme.h index 29d529a2c..a0fb93df3 100644 --- a/TMessagesProj/jni/tgnet/ApiScheme.h +++ b/TMessagesProj/jni/tgnet/ApiScheme.h @@ -110,7 +110,7 @@ public: class TL_config : public TLObject { public: - static const uint32_t constructor = 0x7feec888; + static const uint32_t constructor = 0x9c840964; int32_t flags; int32_t date; @@ -134,6 +134,8 @@ public: int32_t edit_time_limit; int32_t rating_e_decay; int32_t stickers_recent_limit; + int32_t stickers_faved_limit; + int32_t channels_read_media_period; int32_t tmp_sessions; int32_t pinned_dialogs_count_max; int32_t call_receive_timeout_ms; diff --git a/TMessagesProj/jni/tgnet/Connection.cpp b/TMessagesProj/jni/tgnet/Connection.cpp index 743f98870..ea7b73be2 100644 --- a/TMessagesProj/jni/tgnet/Connection.cpp +++ b/TMessagesProj/jni/tgnet/Connection.cpp @@ -38,12 +38,16 @@ Connection::~Connection() { } void Connection::suspendConnection() { + suspendConnection(false); +} + +void Connection::suspendConnection(bool idle) { reconnectTimer->stop(); if (connectionState == TcpConnectionStageIdle || connectionState == TcpConnectionStageSuspended) { return; } DEBUG_D("connection(%p, dc%u, type %d) suspend", this, currentDatacenter->getDatacenterId(), connectionType); - connectionState = TcpConnectionStageSuspended; + connectionState = idle ? TcpConnectionStageIdle : TcpConnectionStageSuspended; dropConnection(); ConnectionsManager::getInstance().onConnectionClosed(this, 0); firstPacketSent = false; @@ -283,8 +287,8 @@ void Connection::connect() { } void Connection::reconnect() { - suspendConnection(); - connectionState = TcpConnectionStageReconnecting; + forceNextPort = true; + suspendConnection(true); connect(); } @@ -390,7 +394,7 @@ void Connection::sendData(NativeByteBuffer *buff, bool reportAck) { void Connection::onDisconnected(int reason) { reconnectTimer->stop(); DEBUG_D("connection(%p, dc%u, type %d) disconnected with reason %d", this, currentDatacenter->getDatacenterId(), connectionType, reason); - bool switchToNextPort = wasConnected && !hasSomeDataSinceLastConnect && reason == 2; + bool switchToNextPort = wasConnected && !hasSomeDataSinceLastConnect && reason == 2 || forceNextPort; firstPacketSent = false; if (restOfTheData != nullptr) { restOfTheData->reuse(); @@ -403,15 +407,14 @@ void Connection::onDisconnected(int reason) { connectionState = TcpConnectionStageIdle; } ConnectionsManager::getInstance().onConnectionClosed(this, reason); - usefullData = false; uint32_t datacenterId = currentDatacenter->getDatacenterId(); - if (connectionState == TcpConnectionStageIdle && connectionType == ConnectionTypeGeneric && (currentDatacenter->isHandshaking() || datacenterId == ConnectionsManager::getInstance().currentDatacenterId || datacenterId == ConnectionsManager::getInstance().movingToDatacenterId)) { + if (connectionState == TcpConnectionStageIdle) { connectionState = TcpConnectionStageReconnecting; failedConnectionCount++; if (failedConnectionCount == 1) { - if (hasSomeDataSinceLastConnect) { - willRetryConnectCount = 5; + if (usefullData) { + willRetryConnectCount = 3; } else { willRetryConnectCount = 1; } @@ -423,10 +426,14 @@ void Connection::onDisconnected(int reason) { failedConnectionCount = 0; } } - DEBUG_D("connection(%p, dc%u, type %d) reconnect %s:%hu", this, currentDatacenter->getDatacenterId(), connectionType, hostAddress.c_str(), hostPort); - reconnectTimer->setTimeout(1000, false); - reconnectTimer->start(); + if (connectionType == ConnectionTypeGeneric && (currentDatacenter->isHandshaking() || datacenterId == ConnectionsManager::getInstance().currentDatacenterId || datacenterId == ConnectionsManager::getInstance().movingToDatacenterId)) { + DEBUG_D("connection(%p, dc%u, type %d) reconnect %s:%hu", this, currentDatacenter->getDatacenterId(), connectionType, hostAddress.c_str(), hostPort); + reconnectTimer->setTimeout(1000, false); + reconnectTimer->start(); + } + } + usefullData = false; } void Connection::onConnected() { diff --git a/TMessagesProj/jni/tgnet/Connection.h b/TMessagesProj/jni/tgnet/Connection.h index 280d9295f..7d1e0fc55 100644 --- a/TMessagesProj/jni/tgnet/Connection.h +++ b/TMessagesProj/jni/tgnet/Connection.h @@ -30,6 +30,7 @@ public: void connect(); void suspendConnection(); + void suspendConnection(bool idle); void sendData(NativeByteBuffer *buffer, bool reportAck); bool hasUsefullData(); void setHasUsefullData(); @@ -71,6 +72,7 @@ private: uint32_t willRetryConnectCount = 5; Timer *reconnectTimer; bool usefullData = false; + bool forceNextPort = false; AES_KEY encryptKey; uint8_t encryptIv[16]; diff --git a/TMessagesProj/jni/tgnet/ConnectionSocket.cpp b/TMessagesProj/jni/tgnet/ConnectionSocket.cpp index 1c60c264a..131190534 100644 --- a/TMessagesProj/jni/tgnet/ConnectionSocket.cpp +++ b/TMessagesProj/jni/tgnet/ConnectionSocket.cpp @@ -155,6 +155,9 @@ bool ConnectionSocket::checkSocketError() { int code; socklen_t len = sizeof(int); ret = getsockopt(socketFd, SOL_SOCKET, SO_ERROR, &code, &len); + if (ret != 0 || code != 0) { + DEBUG_E("socket error 0x%x code 0x%x", ret, code); + } return (ret || code) != 0; } @@ -333,6 +336,7 @@ void ConnectionSocket::onEvent(uint32_t events) { } } if ((events & EPOLLRDHUP) || (events & EPOLLHUP)) { + DEBUG_E("socket event has EPOLLHUP"); closeSocket(1); return; } diff --git a/TMessagesProj/jni/tgnet/ConnectionsManager.cpp b/TMessagesProj/jni/tgnet/ConnectionsManager.cpp index 8c626b49f..5d5d55195 100644 --- a/TMessagesProj/jni/tgnet/ConnectionsManager.cpp +++ b/TMessagesProj/jni/tgnet/ConnectionsManager.cpp @@ -752,9 +752,7 @@ void ConnectionsManager::onConnectionDataReceived(Connection *connection, Native } else { if (length < 24 + 32 || (length - 24) % 16 != 0 || !datacenter->decryptServerResponse(keyId, data->bytes() + mark + 8, data->bytes() + mark + 24, length - 24)) { DEBUG_E("connection(%p) unable to decrypt server response", connection); - datacenter->switchTo443Port(); - connection->suspendConnection(); - connection->connect(); + connection->reconnect(); return; } data->position(mark + 24); @@ -1153,7 +1151,7 @@ void ConnectionsManager::processServerResponse(TLObject *message, int64_t messag } else if (typeInfo == typeid(TL_bad_msg_notification)) { TL_bad_msg_notification *result = (TL_bad_msg_notification *) message; - DEBUG_E("bad message: %d", result->error_code); + DEBUG_E("bad message notification %d for messageId 0x%llx, seqno %d", result->error_code, result->bad_msg_id, result->bad_msg_seqno); switch (result->error_code) { case 16: case 17: @@ -1179,6 +1177,19 @@ void ConnectionsManager::processServerResponse(TLObject *message, int64_t messag clearRequestsForDatacenter(datacenter); break; } + case 20: { + for (requestsIter iter = runningRequests.begin(); iter != runningRequests.end(); iter++) { + Request *request = iter->get(); + if (request->respondsToMessageId(result->bad_msg_id)) { + if (request->completed) { + break; + } + connection->addMessageToConfirm(result->bad_msg_id); + request->clear(true); + break; + } + } + } default: break; } @@ -1218,12 +1229,33 @@ void ConnectionsManager::processServerResponse(TLObject *message, int64_t messag if (datacenter->hasAuthKey()) { processRequestQueue(AllConnectionTypes, datacenter->getDatacenterId()); } + } else if (typeInfo == typeid(MsgsStateInfo)) { + MsgsStateInfo *response = (MsgsStateInfo *) message; + DEBUG_D("connection(%p, dc%u, type %d) got %s for messageId 0x%llx", connection, datacenter->getDatacenterId(), connection->getConnectionType(), typeInfo.name(), response->req_msg_id); + + std::map::iterator mIter = resendRequests.find(response->req_msg_id); + if (mIter != resendRequests.end()) { + DEBUG_D("found resend for messageId 0x%llx", mIter->second); + connection->addMessageToConfirm(mIter->second); + for (requestsIter iter = runningRequests.begin(); iter != runningRequests.end(); iter++) { + Request *request = iter->get(); + if (request->respondsToMessageId(mIter->second)) { + if (request->completed) { + break; + } + request->clear(true); + break; + } + } + resendRequests.erase(mIter); + } } else if (dynamic_cast(message)) { MsgDetailedInfo *response = (MsgDetailedInfo *) message; bool requestResend = false; bool confirm = true; + DEBUG_D("connection(%p, dc%u, type %d) got %s for messageId 0x%llx", connection, datacenter->getDatacenterId(), connection->getConnectionType(), typeInfo.name(), response->msg_id); if (typeInfo == typeid(TL_msg_detailed_info)) { for (requestsIter iter = runningRequests.begin(); iter != runningRequests.end(); iter++) { Request *request = iter->get(); @@ -1231,6 +1263,7 @@ void ConnectionsManager::processServerResponse(TLObject *message, int64_t messag if (request->completed) { break; } + DEBUG_D("got TL_msg_detailed_info for rpc request %p - %s", request->rawRequest, typeid(*request->rawRequest).name()); int32_t currentTime = (int32_t) (getCurrentTimeMonotonicMillis() / 1000); if (request->lastResendTime == 0 || abs(currentTime - request->lastResendTime) >= 60) { request->lastResendTime = currentTime; @@ -1261,6 +1294,7 @@ void ConnectionsManager::processServerResponse(TLObject *message, int64_t messag array.push_back(std::unique_ptr(networkMessage)); sendMessagesToConnection(array, connection, false); + resendRequests[networkMessage->message->msg_id] = response->answer_msg_id; } else if (confirm) { connection->addMessageToConfirm(response->answer_msg_id); } @@ -1843,10 +1877,11 @@ void ConnectionsManager::processRequestQueue(uint32_t connectionTypes, uint32_t if (requestDatacenter->isCdnDatacenter) { request->requestFlags |= RequestFlagEnableUnauthorized; } - if (requestDatacenter->lastInitVersion != currentVersion && !request->isInitRequest) { - request->rpcRequest.release(); - request->rpcRequest = wrapInLayer(request->rawRequest, requestDatacenter, request); - request->serializedLength = request->getRpcRequest()->getObjectSize(); + if (requestDatacenter->lastInitVersion != currentVersion && !request->isInitRequest && request->rawRequest->isNeedLayer()) { + DEBUG_D("move %p - %s to requestsQueue because of initConnection", request->rawRequest, typeid(*request->rawRequest).name()); + requestsQueue.push_back(std::move(*iter)); + iter = runningRequests.erase(iter); + continue; } if (!requestDatacenter->hasAuthKey()) { @@ -2628,6 +2663,9 @@ void ConnectionsManager::setProxySettings(std::string address, uint16_t port, st void ConnectionsManager::setLangCode(std::string langCode) { scheduleTask([&, langCode] { + if (currentLangCode.compare(langCode) == 0) { + return; + } currentLangCode = langCode; for (std::map::iterator iter = datacenters.begin(); iter != datacenters.end(); iter++) { iter->second->lastInitVersion = 0; diff --git a/TMessagesProj/jni/tgnet/ConnectionsManager.h b/TMessagesProj/jni/tgnet/ConnectionsManager.h index 6c1db3e43..8f5e5b4b7 100644 --- a/TMessagesProj/jni/tgnet/ConnectionsManager.h +++ b/TMessagesProj/jni/tgnet/ConnectionsManager.h @@ -154,6 +154,7 @@ private: int32_t lastDestroySessionRequestTime; std::map> requestsByGuids; std::map guidsByRequests; + std::map resendRequests; std::string proxyUser = ""; std::string proxyPassword = ""; diff --git a/TMessagesProj/jni/tgnet/Datacenter.cpp b/TMessagesProj/jni/tgnet/Datacenter.cpp index f6dda9a1e..ffa66fa54 100644 --- a/TMessagesProj/jni/tgnet/Datacenter.cpp +++ b/TMessagesProj/jni/tgnet/Datacenter.cpp @@ -117,6 +117,7 @@ Datacenter::Datacenter(NativeByteBuffer *data) { } if (currentVersion >= 4) { authKeyId = data->readInt64(nullptr); + DEBUG_D("dc%d key = 0x%llx", datacenterId, authKeyId); } else { len = data->readUint32(nullptr); if (len != 0) { @@ -163,37 +164,6 @@ Datacenter::Datacenter(NativeByteBuffer *data) { } } -void Datacenter::switchTo443Port() { - for (uint32_t a = 0; a < addressesIpv4.size(); a++) { - if (addressesIpv4[a].port == 443) { - currentAddressNumIpv4 = a; - currentPortNumIpv4 = 0; - break; - } - } - for (uint32_t a = 0; a < addressesIpv6.size(); a++) { - if (addressesIpv6[a].port == 443) { - currentAddressNumIpv6 = a; - currentPortNumIpv6 = 0; - break; - } - } - for (uint32_t a = 0; a < addressesIpv4Download.size(); a++) { - if (addressesIpv4Download[a].port == 443) { - currentAddressNumIpv4Download = a; - currentPortNumIpv4Download = 0; - break; - } - } - for (uint32_t a = 0; a < addressesIpv6Download.size(); a++) { - if (addressesIpv6Download[a].port == 443) { - currentAddressNumIpv6Download = a; - currentPortNumIpv6Download = 0; - break; - } - } -} - std::string Datacenter::getCurrentAddress(uint32_t flags) { uint32_t currentAddressNum; std::vector *addresses; @@ -316,7 +286,7 @@ int32_t Datacenter::getCurrentPort(uint32_t flags) { } } } - if (currentPortNum >= 11) { + if (currentPortNum >= 15) { currentPortNum = 0; if ((flags & TcpAddressFlagTemp) != 0) { currentPortNumIpv4Temp = currentAddressNum; @@ -401,7 +371,7 @@ void Datacenter::nextAddressOrPort(uint32_t flags) { addresses = &addressesIpv4; } } - if (currentPortNum + 1 < 11) { + if (currentPortNum + 1 < 15) { currentPortNum++; } else { if (currentAddressNum + 1 < addresses->size()) { @@ -465,7 +435,9 @@ void Datacenter::resetAddressAndPortNum() { void Datacenter::replaceAddresses(std::vector &newAddresses, uint32_t flags) { isCdnDatacenter = (flags & 8) != 0; - if ((flags & TcpAddressFlagDownload) != 0) { + if ((flags & TcpAddressFlagTemp) != 0) { + addressesIpv4Temp = newAddresses; + } else if ((flags & TcpAddressFlagDownload) != 0) { if ((flags & TcpAddressFlagIpv6) != 0) { addressesIpv6Download = newAddresses; } else { @@ -1084,6 +1056,46 @@ void Datacenter::processHandshakeResponse(TLObject *message, int64_t messageId) "UiggS4UeE8TzIuXFQxw7fzEIlmhIaq3FnwIDAQAB\n" "-----END RSA PUBLIC KEY-----"); serverPublicKeysFingerprints.push_back(0x71e025b6c76033e3LL); + + serverPublicKeys.push_back("-----BEGIN RSA PUBLIC KEY-----\n" + "MIIBCgKCAQEAruw2yP/BCcsJliRoW5eBVBVle9dtjJw+OYED160Wybum9SXtBBLX\n" + "riwt4rROd9csv0t0OHCaTmRqBcQ0J8fxhN6/cpR1GWgOZRUAiQxoMnlt0R93LCX/\n" + "j1dnVa/gVbCjdSxpbrfY2g2L4frzjJvdl84Kd9ORYjDEAyFnEA7dD556OptgLQQ2\n" + "e2iVNq8NZLYTzLp5YpOdO1doK+ttrltggTCy5SrKeLoCPPbOgGsdxJxyz5KKcZnS\n" + "Lj16yE5HvJQn0CNpRdENvRUXe6tBP78O39oJ8BTHp9oIjd6XWXAsp2CvK45Ol8wF\n" + "XGF710w9lwCGNbmNxNYhtIkdqfsEcwR5JwIDAQAB\n" + "-----END RSA PUBLIC KEY-----"); + serverPublicKeysFingerprints.push_back(0xbc35f3509f7b7a5LL); + + serverPublicKeys.push_back("-----BEGIN RSA PUBLIC KEY-----\n" + "MIIBCgKCAQEAvfLHfYH2r9R70w8prHblWt/nDkh+XkgpflqQVcnAfSuTtO05lNPs\n" + "pQmL8Y2XjVT4t8cT6xAkdgfmmvnvRPOOKPi0OfJXoRVylFzAQG/j83u5K3kRLbae\n" + "7fLccVhKZhY46lvsueI1hQdLgNV9n1cQ3TDS2pQOCtovG4eDl9wacrXOJTG2990V\n" + "jgnIKNA0UMoP+KF03qzryqIt3oTvZq03DyWdGK+AZjgBLaDKSnC6qD2cFY81UryR\n" + "WOab8zKkWAnhw2kFpcqhI0jdV5QaSCExvnsjVaX0Y1N0870931/5Jb9ICe4nweZ9\n" + "kSDF/gip3kWLG0o8XQpChDfyvsqB9OLV/wIDAQAB\n" + "-----END RSA PUBLIC KEY-----"); + serverPublicKeysFingerprints.push_back(0x15ae5fa8b5529542LL); + + serverPublicKeys.push_back("-----BEGIN RSA PUBLIC KEY-----\n" + "MIIBCgKCAQEAs/ditzm+mPND6xkhzwFIz6J/968CtkcSE/7Z2qAJiXbmZ3UDJPGr\n" + "zqTDHkO30R8VeRM/Kz2f4nR05GIFiITl4bEjvpy7xqRDspJcCFIOcyXm8abVDhF+\n" + "th6knSU0yLtNKuQVP6voMrnt9MV1X92LGZQLgdHZbPQz0Z5qIpaKhdyA8DEvWWvS\n" + "Uwwc+yi1/gGaybwlzZwqXYoPOhwMebzKUk0xW14htcJrRrq+PXXQbRzTMynseCoP\n" + "Ioke0dtCodbA3qQxQovE16q9zz4Otv2k4j63cz53J+mhkVWAeWxVGI0lltJmWtEY\n" + "K6er8VqqWot3nqmWMXogrgRLggv/NbbooQIDAQAB\n" + "-----END RSA PUBLIC KEY-----"); + serverPublicKeysFingerprints.push_back(0xaeae98e13cd7f94fLL); + + serverPublicKeys.push_back("-----BEGIN RSA PUBLIC KEY-----\n" + "MIIBCgKCAQEAvmpxVY7ld/8DAjz6F6q05shjg8/4p6047bn6/m8yPy1RBsvIyvuD\n" + "uGnP/RzPEhzXQ9UJ5Ynmh2XJZgHoE9xbnfxL5BXHplJhMtADXKM9bWB11PU1Eioc\n" + "3+AXBB8QiNFBn2XI5UkO5hPhbb9mJpjA9Uhw8EdfqJP8QetVsI/xrCEbwEXe0xvi\n" + "fRLJbY08/Gp66KpQvy7g8w7VB8wlgePexW3pT13Ap6vuC+mQuJPyiHvSxjEKHgqe\n" + "Pji9NP3tJUFQjcECqcm0yV7/2d0t/pbCm+ZH1sadZspQCEPPrtbkQBlvHb4OLiIW\n" + "PGHKSMeRFvp3IWcmdJqXahxLCUS1Eh6MAQIDAQAB\n" + "-----END RSA PUBLIC KEY-----"); + serverPublicKeysFingerprints.push_back(0x5a181b2235057d98LL); } size_t count2 = serverPublicKeysFingerprints.size(); @@ -1647,6 +1659,9 @@ NativeByteBuffer *Datacenter::createRequestsData(std::vector length - 32) { error = true; + } else if (paddingLength < 12 || paddingLength > 1024) { + error = true; } messageLength += 32; if (messageLength > length) { @@ -1952,8 +1970,8 @@ TL_help_configSimple *Datacenter::decodeSimpleConfig(NativeByteBuffer *buffer) { BN_bin2bn(bytes, 256, &x); if (BN_mod_exp(&y, &x, rsaKey->e, rsaKey->n, bnContext) == 1) { - uint8_t temp[256]; - /*BN_bn2bin(&y, temp); + /*uint8_t temp[256]; + BN_bn2bin(&y, temp); std::string res = ""; for (int a = 0; a < 256; a++) { char buf[20]; diff --git a/TMessagesProj/jni/tgnet/Datacenter.h b/TMessagesProj/jni/tgnet/Datacenter.h index db72a5748..32748a8fd 100644 --- a/TMessagesProj/jni/tgnet/Datacenter.h +++ b/TMessagesProj/jni/tgnet/Datacenter.h @@ -29,7 +29,6 @@ class Datacenter { public: Datacenter(uint32_t id); Datacenter(NativeByteBuffer *data); - void switchTo443Port(); uint32_t getDatacenterId(); std::string getCurrentAddress(uint32_t flags); int32_t getCurrentPort(uint32_t flags); @@ -69,8 +68,8 @@ private: bool decryptServerResponse(int64_t keyId, uint8_t *key, uint8_t *data, uint32_t length); TLObject *getCurrentHandshakeRequest(); - const int32_t *defaultPorts = new int32_t[11] {-1, 80, -1, 443, -1, 443, -1, 80, -1, 443, -1}; - const int32_t *defaultPorts8888 = new int32_t[11] {-1, 8888, -1, 443, -1, 8888, -1, 80, -1, 8888, -1}; + const int32_t *defaultPorts = new int32_t[15] {-1, 80, -1, 443, -1, 5222, -1, 443, -1, 80, -1, -1, 5222, 443, -1}; + const int32_t *defaultPorts8888 = new int32_t[15] {-1, 8888, -1, 443, -1, 5222, -1, 8888, -1, 80, -1, 5222, -1, 8888, -1}; uint32_t datacenterId; Connection *genericConnection = nullptr; diff --git a/TMessagesProj/jni/tgnet/MTProtoScheme.cpp b/TMessagesProj/jni/tgnet/MTProtoScheme.cpp index b5a9b664a..ebe8a3bbe 100644 --- a/TMessagesProj/jni/tgnet/MTProtoScheme.cpp +++ b/TMessagesProj/jni/tgnet/MTProtoScheme.cpp @@ -31,6 +31,9 @@ TLObject *TLClassStore::TLdeserialize(NativeByteBuffer *stream, uint32_t bytes, case TL_new_session_created::constructor: object = new TL_new_session_created(); break; + case MsgsStateInfo::constructor: + object = new MsgsStateInfo(); + break; case TL_rpc_result::constructor: object = new TL_rpc_result(); ((TL_rpc_result *) object)->readParamsEx(stream, bytes, error); @@ -664,6 +667,11 @@ void TL_msg_resend_req::serializeToStream(NativeByteBuffer *stream) { } } +void MsgsStateInfo::readParams(NativeByteBuffer *stream, bool &error) { + req_msg_id = stream->readInt64(&error); + info = stream->readString(&error); +} + void TL_rpc_error::readParams(NativeByteBuffer *stream, bool &error) { error_code = stream->readInt32(&error); error_message = stream->readString(&error); diff --git a/TMessagesProj/jni/tgnet/MTProtoScheme.h b/TMessagesProj/jni/tgnet/MTProtoScheme.h index 6bdcbd7ee..45c2fda93 100644 --- a/TMessagesProj/jni/tgnet/MTProtoScheme.h +++ b/TMessagesProj/jni/tgnet/MTProtoScheme.h @@ -411,6 +411,17 @@ public: void serializeToStream(NativeByteBuffer *stream); }; +class MsgsStateInfo : public TLObject { + +public: + static const uint32_t constructor = 0x04deb57d; + + int64_t req_msg_id; + std::string info; + + void readParams(NativeByteBuffer *stream, bool &error); +}; + class RpcError : public TLObject { public: diff --git a/TMessagesProj/src/main/AndroidManifest.xml b/TMessagesProj/src/main/AndroidManifest.xml index 8dedb04de..54676c3d5 100644 --- a/TMessagesProj/src/main/AndroidManifest.xml +++ b/TMessagesProj/src/main/AndroidManifest.xml @@ -257,6 +257,7 @@ + @@ -299,14 +300,26 @@ + + + + + + + + + + + + > 32); - TLRPC.User user = null; - TLRPC.Chat chat = null; if (lower_id == 0) { shortcutIntent.putExtra("encId", high_id); TLRPC.EncryptedChat encryptedChat = MessagesController.getInstance().getEncryptedChat(high_id); if (encryptedChat == null) { return null; } - user = MessagesController.getInstance().getUser(encryptedChat.user_id); } else if (lower_id > 0) { shortcutIntent.putExtra("userId", lower_id); - user = MessagesController.getInstance().getUser(lower_id); } else if (lower_id < 0) { - chat = MessagesController.getInstance().getChat(-lower_id); shortcutIntent.putExtra("chatId", -lower_id); } else { return null; } - if (user == null && chat == null) { - return null; - } - - String name; - TLRPC.FileLocation photo = null; - - if (user != null) { - name = ContactsController.formatName(user.first_name, user.last_name); - if (user.photo != null) { - photo = user.photo.photo_small; - } - } else { - name = chat.title; - if (chat.photo != null) { - photo = chat.photo.photo_small; - } - } - shortcutIntent.setAction("com.tmessages.openchat" + did); shortcutIntent.addFlags(0x4000000); + return shortcutIntent; + } + + public static void installShortcut(long did) { + try { + + Intent shortcutIntent = createIntrnalShortcutIntent(did); + + int lower_id = (int) did; + int high_id = (int) (did >> 32); + + TLRPC.User user = null; + TLRPC.Chat chat = null; + if (lower_id == 0) { + TLRPC.EncryptedChat encryptedChat = MessagesController.getInstance().getEncryptedChat(high_id); + if (encryptedChat == null) { + return; + } + user = MessagesController.getInstance().getUser(encryptedChat.user_id); + } else if (lower_id > 0) { + user = MessagesController.getInstance().getUser(lower_id); + } else if (lower_id < 0) { + chat = MessagesController.getInstance().getChat(-lower_id); + } else { + return; + } + if (user == null && chat == null) { + return; + } + + String name; + TLRPC.FileLocation photo = null; + + boolean selfUser = false; + + if (user != null) { + if (UserObject.isUserSelf(user)) { + name = LocaleController.getString("SavedMessages", R.string.SavedMessages); + selfUser = true; + } else { + name = ContactsController.formatName(user.first_name, user.last_name); + if (user.photo != null) { + photo = user.photo.photo_small; + } + } + } else { + name = chat.title; + if (chat.photo != null) { + photo = chat.photo.photo_small; + } + } - Intent addIntent = new Intent(); - addIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); - addIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name); - addIntent.putExtra("duplicate", false); - if (!forDelete) { Bitmap bitmap = null; - if (photo != null) { + if (selfUser || photo != null) { try { - File path = FileLoader.getPathToAttach(photo, true); - bitmap = BitmapFactory.decodeFile(path.toString()); - if (bitmap != null) { + if (!selfUser) { + File path = FileLoader.getPathToAttach(photo, true); + bitmap = BitmapFactory.decodeFile(path.toString()); + } + if (selfUser || bitmap != null) { int size = AndroidUtilities.dp(58); Bitmap result = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); result.eraseColor(Color.TRANSPARENT); Canvas canvas = new Canvas(result); - BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); - if (roundPaint == null) { - roundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - bitmapRect = new RectF(); + if (selfUser) { + AvatarDrawable avatarDrawable = new AvatarDrawable(user); + avatarDrawable.setSavedMessages(1); + avatarDrawable.setBounds(0, 0, size, size); + avatarDrawable.draw(canvas); + } else { + BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); + if (roundPaint == null) { + roundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + bitmapRect = new RectF(); + } + float scale = size / (float) bitmap.getWidth(); + canvas.save(); + canvas.scale(scale, scale); + roundPaint.setShader(shader); + bitmapRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight()); + canvas.drawRoundRect(bitmapRect, bitmap.getWidth(), bitmap.getHeight(), roundPaint); + canvas.restore(); } - float scale = size / (float) bitmap.getWidth(); - canvas.save(); - canvas.scale(scale, scale); - roundPaint.setShader(shader); - bitmapRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight()); - canvas.drawRoundRect(bitmapRect, bitmap.getWidth(), bitmap.getHeight(), roundPaint); - canvas.restore(); Drawable drawable = ApplicationLoader.applicationContext.getResources().getDrawable(R.drawable.book_logo); int w = AndroidUtilities.dp(15); int left = size - w - AndroidUtilities.dp(2); @@ -907,32 +940,60 @@ public class AndroidUtilities { FileLog.e(e); } } - if (bitmap != null) { - addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, bitmap); - } else { - if (user != null) { - if (user.bot) { - addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(ApplicationLoader.applicationContext, R.drawable.book_bot)); - } else { - addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(ApplicationLoader.applicationContext, R.drawable.book_user)); - } - } else if (chat != null) { - if (ChatObject.isChannel(chat) && !chat.megagroup) { - addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(ApplicationLoader.applicationContext, R.drawable.book_channel)); - } else { - addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(ApplicationLoader.applicationContext, R.drawable.book_group)); + + if (Build.VERSION.SDK_INT >= 26) { + ShortcutInfo.Builder pinShortcutInfo = + new ShortcutInfo.Builder(ApplicationLoader.applicationContext, "sdid_" + did) + .setShortLabel(name) + .setIntent(shortcutIntent); + + if (bitmap != null) { + pinShortcutInfo.setIcon(Icon.createWithBitmap(bitmap)); + } else { + if (user != null) { + if (user.bot) { + pinShortcutInfo.setIcon(Icon.createWithResource(ApplicationLoader.applicationContext, R.drawable.book_bot)); + } else { + pinShortcutInfo.setIcon(Icon.createWithResource(ApplicationLoader.applicationContext, R.drawable.book_user)); + } + } else if (chat != null) { + if (ChatObject.isChannel(chat) && !chat.megagroup) { + pinShortcutInfo.setIcon(Icon.createWithResource(ApplicationLoader.applicationContext, R.drawable.book_channel)); + } else { + pinShortcutInfo.setIcon(Icon.createWithResource(ApplicationLoader.applicationContext, R.drawable.book_group)); + } } } - } - } - return addIntent; - } - public static void installShortcut(long did) { - try { - Intent addIntent = createShortcutIntent(did, false); - addIntent.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); - ApplicationLoader.applicationContext.sendBroadcast(addIntent); + ShortcutManager shortcutManager = ApplicationLoader.applicationContext.getSystemService(ShortcutManager.class); + shortcutManager.requestPinShortcut(pinShortcutInfo.build(), null); + } else { + Intent addIntent = new Intent(); + if (bitmap != null) { + addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, bitmap); + } else { + if (user != null) { + if (user.bot) { + addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(ApplicationLoader.applicationContext, R.drawable.book_bot)); + } else { + addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(ApplicationLoader.applicationContext, R.drawable.book_user)); + } + } else if (chat != null) { + if (ChatObject.isChannel(chat) && !chat.megagroup) { + addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(ApplicationLoader.applicationContext, R.drawable.book_channel)); + } else { + addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(ApplicationLoader.applicationContext, R.drawable.book_group)); + } + } + } + + addIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); + addIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name); + addIntent.putExtra("duplicate", false); + + addIntent.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); + ApplicationLoader.applicationContext.sendBroadcast(addIntent); + } } catch (Exception e) { FileLog.e(e); } @@ -940,9 +1001,50 @@ public class AndroidUtilities { public static void uninstallShortcut(long did) { try { - Intent addIntent = createShortcutIntent(did, true); - addIntent.setAction("com.android.launcher.action.UNINSTALL_SHORTCUT"); - ApplicationLoader.applicationContext.sendBroadcast(addIntent); + if (Build.VERSION.SDK_INT >= 26) { + ShortcutManager shortcutManager = ApplicationLoader.applicationContext.getSystemService(ShortcutManager.class); + ArrayList arrayList = new ArrayList<>(); + arrayList.add("sdid_" + did); + shortcutManager.removeDynamicShortcuts(arrayList); + } else { + int lower_id = (int) did; + int high_id = (int) (did >> 32); + + TLRPC.User user = null; + TLRPC.Chat chat = null; + if (lower_id == 0) { + TLRPC.EncryptedChat encryptedChat = MessagesController.getInstance().getEncryptedChat(high_id); + if (encryptedChat == null) { + return; + } + user = MessagesController.getInstance().getUser(encryptedChat.user_id); + } else if (lower_id > 0) { + user = MessagesController.getInstance().getUser(lower_id); + } else if (lower_id < 0) { + chat = MessagesController.getInstance().getChat(-lower_id); + } else { + return; + } + if (user == null && chat == null) { + return; + } + + String name; + + if (user != null) { + name = ContactsController.formatName(user.first_name, user.last_name); + } else { + name = chat.title; + } + + Intent addIntent = new Intent(); + addIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, createIntrnalShortcutIntent(did)); + addIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name); + addIntent.putExtra("duplicate", false); + + addIntent.setAction("com.android.launcher.action.UNINSTALL_SHORTCUT"); + ApplicationLoader.applicationContext.sendBroadcast(addIntent); + } } catch (Exception e) { FileLog.e(e); } @@ -1117,6 +1219,15 @@ public class AndroidUtilities { bolds.add(start); bolds.add(end); } + while ((start = stringBuilder.indexOf("**")) != -1) { + stringBuilder.replace(start, start + 2, ""); + end = stringBuilder.indexOf("**"); + if (end >= 0) { + stringBuilder.replace(end, end + 2, ""); + bolds.add(start); + bolds.add(end); + } + } } SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(stringBuilder); for (int a = 0; a < bolds.size() / 2; a++) { @@ -1348,8 +1459,8 @@ public class AndroidUtilities { } return value; } - } catch (Exception e) { - FileLog.e(e); + } catch (Exception ignore) { + } finally { if (cursor != null) { cursor.close(); @@ -1373,7 +1484,9 @@ public class AndroidUtilities { public static File generatePicturePath() { try { File storageDir = getAlbumDir(); - String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date()); + Date date = new Date(); + date.setTime(System.currentTimeMillis() + Utilities.random.nextInt(1000) + 1); + String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss_SSS", Locale.US).format(date); return new File(storageDir, "IMG_" + timeStamp + ".jpg"); } catch (Exception e) { FileLog.e(e); @@ -1420,7 +1533,7 @@ public class AndroidUtilities { lastIndex = end; } - if (lastIndex != -1 && lastIndex != wholeString.length()) { + if (lastIndex != -1 && lastIndex < wholeString.length()) { builder.append(wholeString.substring(lastIndex, wholeString.length())); } @@ -1430,7 +1543,9 @@ public class AndroidUtilities { public static File generateVideoPath() { try { File storageDir = getAlbumDir(); - String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date()); + Date date = new Date(); + date.setTime(System.currentTimeMillis() + Utilities.random.nextInt(1000) + 1); + String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss_SSS", Locale.US).format(date); return new File(storageDir, "VID_" + timeStamp + ".mp4"); } catch (Exception e) { FileLog.e(e); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ApplicationLoader.java b/TMessagesProj/src/main/java/org/telegram/messenger/ApplicationLoader.java index 8dd8a379b..804d063a6 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ApplicationLoader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ApplicationLoader.java @@ -9,7 +9,6 @@ package org.telegram.messenger; import android.annotation.SuppressLint; -import android.app.Activity; import android.app.AlarmManager; import android.app.Application; import android.app.PendingIntent; @@ -19,23 +18,18 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; import android.content.res.Configuration; import android.os.Build; import android.os.Handler; import android.os.PowerManager; -import android.util.Base64; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GooglePlayServicesUtil; import org.telegram.tgnet.ConnectionsManager; -import org.telegram.tgnet.SerializedData; -import org.telegram.tgnet.TLRPC; import org.telegram.ui.Components.ForegroundDetector; import java.io.File; -import java.io.RandomAccessFile; public class ApplicationLoader extends Application { @@ -49,49 +43,6 @@ public class ApplicationLoader extends Application { public static volatile boolean mainInterfacePausedStageQueue = true; public static volatile long mainInterfacePausedStageQueueTime; - private static void convertConfig() { - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("dataconfig", Context.MODE_PRIVATE); - if (preferences.contains("currentDatacenterId")) { - SerializedData buffer = new SerializedData(32 * 1024); - buffer.writeInt32(2); - buffer.writeBool(preferences.getInt("datacenterSetId", 0) != 0); - buffer.writeBool(true); - buffer.writeInt32(preferences.getInt("currentDatacenterId", 0)); - buffer.writeInt32(preferences.getInt("timeDifference", 0)); - buffer.writeInt32(preferences.getInt("lastDcUpdateTime", 0)); - buffer.writeInt64(preferences.getLong("pushSessionId", 0)); - buffer.writeBool(false); - buffer.writeInt32(0); - try { - String datacentersString = preferences.getString("datacenters", null); - if (datacentersString != null) { - byte[] datacentersBytes = Base64.decode(datacentersString, Base64.DEFAULT); - if (datacentersBytes != null) { - SerializedData data = new SerializedData(datacentersBytes); - buffer.writeInt32(data.readInt32(false)); - buffer.writeBytes(datacentersBytes, 4, datacentersBytes.length - 4); - data.cleanup(); - } - } - } catch (Exception e) { - FileLog.e(e); - } - - try { - File file = new File(getFilesDirFixed(), "tgnet.dat"); - RandomAccessFile fileOutputStream = new RandomAccessFile(file, "rws"); - byte[] bytes = buffer.toByteArray(); - fileOutputStream.writeInt(Integer.reverseBytes(bytes.length)); - fileOutputStream.write(bytes); - fileOutputStream.close(); - } catch (Exception e) { - FileLog.e(e); - } - buffer.cleanup(); - preferences.edit().clear().commit(); - } - } - public static File getFilesDirFixed() { for (int a = 0; a < 10; a++) { File path = ApplicationLoader.applicationContext.getFilesDir(); @@ -116,7 +67,6 @@ public class ApplicationLoader extends Application { } applicationInited = true; - convertConfig(); try { LocaleController.getInstance(); @@ -142,48 +92,10 @@ public class ApplicationLoader extends Application { } UserConfig.loadConfig(); - String deviceModel; - String systemLangCode; - String langCode; - String appVersion; - String systemVersion; - String configPath = getFilesDirFixed().toString(); - - try { - systemLangCode = LocaleController.getSystemLocaleStringIso639(); - langCode = LocaleController.getLocaleStringIso639(); - deviceModel = Build.MANUFACTURER + Build.MODEL; - PackageInfo pInfo = ApplicationLoader.applicationContext.getPackageManager().getPackageInfo(ApplicationLoader.applicationContext.getPackageName(), 0); - appVersion = pInfo.versionName + " (" + pInfo.versionCode + ")"; - systemVersion = "SDK " + Build.VERSION.SDK_INT; - } catch (Exception e) { - systemLangCode = "en"; - langCode = ""; - deviceModel = "Android unknown"; - appVersion = "App version unknown"; - systemVersion = "SDK " + Build.VERSION.SDK_INT; - } - if (systemLangCode.trim().length() == 0) { - langCode = "en"; - } - if (deviceModel.trim().length() == 0) { - deviceModel = "Android unknown"; - } - if (appVersion.trim().length() == 0) { - appVersion = "App version unknown"; - } - if (systemVersion.trim().length() == 0) { - systemVersion = "SDK Unknown"; - } - - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - boolean enablePushConnection = preferences.getBoolean("pushConnection", true); - MessagesController.getInstance(); - ConnectionsManager.getInstance().init(BuildVars.BUILD_VERSION, TLRPC.LAYER, BuildVars.APP_ID, deviceModel, systemVersion, appVersion, langCode, systemLangCode, configPath, FileLog.getNetworkLogPath(), UserConfig.getClientUserId(), enablePushConnection); + ConnectionsManager.getInstance(); if (UserConfig.getCurrentUser() != null) { MessagesController.getInstance().putUser(UserConfig.getCurrentUser(), true); - ConnectionsManager.getInstance().applyCountryPortNumber(UserConfig.getCurrentUser().phone); MessagesController.getInstance().getBlockedUsers(true); SendMessagesHelper.getInstance().checkUnsentMessages(); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java b/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java index 28ac4b3e9..560090b99 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java @@ -1,5 +1,5 @@ /* - * This is the source code of Telegram for Android v. 3.x.x. + * This is the source code of Telegram for Android v. 4.x.x. * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * @@ -11,8 +11,8 @@ package org.telegram.messenger; public class BuildVars { public static boolean DEBUG_VERSION = false; public static boolean DEBUG_PRIVATE_VERSION = false; - public static int BUILD_VERSION = 1043; - public static String BUILD_VERSION_STRING = "4.2"; + public static int BUILD_VERSION = 1154; + public static String BUILD_VERSION_STRING = "4.6"; public static int APP_ID = 0; //obtain your own APP_ID at https://core.telegram.org/api/obtaining_api_id public static String APP_HASH = ""; //obtain your own APP_HASH at https://core.telegram.org/api/obtaining_api_id public static String HOCKEY_APP_HASH = "your-hockeyapp-api-key-here"; @@ -20,5 +20,6 @@ public class BuildVars { public static String BING_SEARCH_KEY = ""; //obtain your own KEY at https://www.bing.com/dev/en-us/dev-center public static String FOURSQUARE_API_KEY = ""; //obtain your own KEY at https://developer.foursquare.com/ public static String FOURSQUARE_API_ID = ""; //obtain your own API_ID at https://developer.foursquare.com/ + public static String GOOGLE_API_KEY = ""; public static String FOURSQUARE_API_VERSION = "20150326"; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/CallReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/CallReceiver.java index 9eccd6a5d..7f332742d 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/CallReceiver.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/CallReceiver.java @@ -11,7 +11,6 @@ package org.telegram.messenger; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import org.telegram.PhoneFormat.PhoneFormat; @@ -22,7 +21,7 @@ public class CallReceiver extends BroadcastReceiver { public void onReceive(final Context context, Intent intent) { if (intent.getAction().equals("android.intent.action.PHONE_STATE")) { String phoneState = intent.getStringExtra(TelephonyManager.EXTRA_STATE); - if (phoneState.equals(TelephonyManager.EXTRA_STATE_RINGING)) { + if (TelephonyManager.EXTRA_STATE_RINGING.equals(phoneState)) { String phoneNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER); NotificationCenter.getInstance().postNotificationName(NotificationCenter.didReceiveCall, PhoneFormat.stripExceptNumbers(phoneNumber)); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ChatObject.java b/TMessagesProj/src/main/java/org/telegram/messenger/ChatObject.java index 1f5e59922..46fe773cd 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ChatObject.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ChatObject.java @@ -34,6 +34,10 @@ public class ChatObject { return chat instanceof TLRPC.TL_channel || chat instanceof TLRPC.TL_channelForbidden; } + public static boolean isMegagroup(TLRPC.Chat chat) { + return (chat instanceof TLRPC.TL_channel || chat instanceof TLRPC.TL_channelForbidden) && chat.megagroup; + } + public static boolean hasAdminRights(TLRPC.Chat chat) { return chat != null && (chat.creator || chat.admin_rights != null && chat.admin_rights.flags != 0); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ContactsController.java b/TMessagesProj/src/main/java/org/telegram/messenger/ContactsController.java index 4296c0d6e..6aed101d8 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ContactsController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ContactsController.java @@ -23,7 +23,6 @@ import android.os.Build; import android.provider.BaseColumns; import android.provider.ContactsContract; import android.text.TextUtils; -import android.util.SparseArray; import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.tgnet.ConnectionsManager; @@ -40,17 +39,18 @@ import java.util.concurrent.ConcurrentHashMap; public class ContactsController { private Account currentAccount; - private boolean loadingContacts = false; + private boolean loadingContacts; private static final Object loadContactsSync = new Object(); - private boolean ignoreChanges = false; - private boolean contactsSyncInProgress = false; + private boolean ignoreChanges; + private boolean contactsSyncInProgress; private final Object observerLock = new Object(); - public boolean contactsLoaded = false; - private boolean contactsBookLoaded = false; + public boolean contactsLoaded; + private boolean contactsBookLoaded; + private boolean migratingContacts; private String lastContactsVersions = ""; private ArrayList delayedContactsUpdate = new ArrayList<>(); - private String inviteText; - private boolean updatingInviteText = false; + private String inviteLink; + private boolean updatingInviteLink; private HashMap sectionsToReplace = new HashMap<>(); private int loadingDeleteInfo; @@ -58,40 +58,42 @@ public class ContactsController { private int loadingLastSeenInfo; private int loadingCallsInfo; private int loadingGroupInfo; - private ArrayList privacyRules = null; - private ArrayList groupPrivacyRules = null; - private ArrayList callPrivacyRules = null; + private ArrayList privacyRules; + private ArrayList groupPrivacyRules; + private ArrayList callPrivacyRules; public static class Contact { - public int id; + public int contact_id; + public String key; public ArrayList phones = new ArrayList<>(); public ArrayList phoneTypes = new ArrayList<>(); public ArrayList shortPhones = new ArrayList<>(); public ArrayList phoneDeleted = new ArrayList<>(); public String first_name; public String last_name; + public int imported; } private String[] projectionPhones = { - ContactsContract.CommonDataKinds.Phone.CONTACT_ID, + ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY, ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.LABEL }; private String[] projectionNames = { - ContactsContract.CommonDataKinds.StructuredName.CONTACT_ID, + ContactsContract.CommonDataKinds.StructuredName.LOOKUP_KEY, ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, ContactsContract.Data.DISPLAY_NAME, ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME }; - public HashMap contactsBook = new HashMap<>(); + public HashMap contactsBook = new HashMap<>(); public HashMap contactsBookSPhones = new HashMap<>(); public ArrayList phoneBookContacts = new ArrayList<>(); public ArrayList contacts = new ArrayList<>(); - public SparseArray contactsDict = new SparseArray<>(); + public ConcurrentHashMap contactsDict = new ConcurrentHashMap<>(20, 1.0f, 2); public HashMap> usersSectionsDict = new HashMap<>(); public ArrayList sortedUsersSectionsArray = new ArrayList<>(); @@ -99,6 +101,7 @@ public class ContactsController { public ArrayList sortedUsersMutualSectionsArray = new ArrayList<>(); public HashMap contactsByPhone = new HashMap<>(); + public HashMap contactsByShortPhone = new HashMap<>(); private int completedRequestsCount; @@ -158,6 +161,7 @@ public class ContactsController { sortedUsersMutualSectionsArray.clear(); delayedContactsUpdate.clear(); contactsByPhone.clear(); + contactsByShortPhone.clear(); loadingContacts = false; contactsSyncInProgress = false; @@ -172,6 +176,7 @@ public class ContactsController { Utilities.globalQueue.postRunnable(new Runnable() { @Override public void run() { + migratingContacts = false; completedRequestsCount = 0; } }); @@ -180,10 +185,10 @@ public class ContactsController { public void checkInviteText() { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); - inviteText = preferences.getString("invitetext", null); - int time = preferences.getInt("invitetexttime", 0); - if (!updatingInviteText && (inviteText == null || time + 86400 < (int) (System.currentTimeMillis() / 1000))) { - updatingInviteText = true; + inviteLink = preferences.getString("invitelink", null); + int time = preferences.getInt("invitelinktime", 0); + if (!updatingInviteLink && (inviteLink == null || Math.abs(System.currentTimeMillis() / 1000 - time) >= 86400)) { + updatingInviteLink = true; TLRPC.TL_help_getInviteText req = new TLRPC.TL_help_getInviteText(); ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override @@ -194,11 +199,11 @@ public class ContactsController { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - updatingInviteText = false; + updatingInviteLink = false; SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); - editor.putString("invitetext", res.message); - editor.putInt("invitetexttime", (int) (System.currentTimeMillis() / 1000)); + editor.putString("invitelink", inviteLink = res.message); + editor.putInt("invitelinktime", (int) (System.currentTimeMillis() / 1000)); editor.commit(); } }); @@ -209,8 +214,17 @@ public class ContactsController { } } - public String getInviteText() { - return inviteText != null ? inviteText : LocaleController.getString("InviteText", R.string.InviteText); + public String getInviteText(int contacts) { + String link = inviteLink == null ? "https://telegram.org/dl" : inviteLink; + if (contacts <= 1) { + return LocaleController.formatString("InviteText2", R.string.InviteText2, link); + } else { + try { + return String.format(LocaleController.getPluralString("InviteTextNum", contacts), contacts, link); + } catch (Exception e) { + return LocaleController.formatString("InviteText2", R.string.InviteText2, link); + } + } } public void checkAppAccount() { @@ -283,7 +297,7 @@ public class ContactsController { public void run() { if (checkContactsInternal()) { FileLog.e("detected contacts change"); - ContactsController.getInstance().performSyncPhoneBook(ContactsController.getInstance().getContactsCopy(ContactsController.getInstance().contactsBook), true, false, true, false); + ContactsController.getInstance().performSyncPhoneBook(ContactsController.getInstance().getContactsCopy(ContactsController.getInstance().contactsBook), true, false, true, false, true, false); } } }); @@ -293,7 +307,26 @@ public class ContactsController { Utilities.globalQueue.postRunnable(new Runnable() { @Override public void run() { - ContactsController.getInstance().performSyncPhoneBook(new HashMap(), true, true, true, true); + ContactsController.getInstance().performSyncPhoneBook(new HashMap(), true, true, true, true, false, false); + } + }); + } + + public void syncPhoneBookByAlert(final HashMap contacts, final boolean first, final boolean schedule, final boolean cancel) { + Utilities.globalQueue.postRunnable(new Runnable() { + @Override + public void run() { + ContactsController.getInstance().performSyncPhoneBook(contacts, true, first, schedule, false, false, cancel); + } + }); + } + + public void resetImportedContacts() { + TLRPC.TL_contacts_resetSaved req = new TLRPC.TL_contacts_resetSaved(); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + } }); } @@ -350,13 +383,14 @@ public class ContactsController { } return; } - loadContacts(true, false); + loadContacts(true, 0); } }); } - private HashMap readContactsFromPhoneBook() { - HashMap contactsMap = new HashMap<>(); + private HashMap readContactsFromPhoneBook() { + HashMap contactsMap = new HashMap<>(); + Cursor pCur = null; try { if (!hasContactsPermission()) { return contactsMap; @@ -364,8 +398,10 @@ public class ContactsController { ContentResolver cr = ApplicationLoader.applicationContext.getContentResolver(); HashMap shortContacts = new HashMap<>(); - ArrayList idsArr = new ArrayList<>(); - Cursor pCur = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, projectionPhones, null, null, null); + ArrayList idsArr = new ArrayList<>(); + pCur = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, projectionPhones, null, null, null); + + int lastContactId = 1; if (pCur != null) { if (pCur.getCount() > 0) { while (pCur.moveToNext()) { @@ -374,7 +410,7 @@ public class ContactsController { continue; } number = PhoneFormat.stripExceptNumbers(number, true); - if (number.length() == 0) { + if (TextUtils.isEmpty(number)) { continue; } @@ -388,19 +424,21 @@ public class ContactsController { continue; } - Integer id = pCur.getInt(0); - if (!idsArr.contains(id)) { - idsArr.add(id); + String lookup_key = pCur.getString(0); + String key = "'" + lookup_key + "'"; + if (!idsArr.contains(key)) { + idsArr.add(key); } int type = pCur.getInt(2); - Contact contact = contactsMap.get(id); + Contact contact = contactsMap.get(lookup_key); if (contact == null) { contact = new Contact(); contact.first_name = ""; contact.last_name = ""; - contact.id = id; - contactsMap.put(id, contact); + contact.key = lookup_key; + contact.contact_id = lastContactId++; + contactsMap.put(lookup_key, contact); } contact.shortPhones.add(shortNumber); @@ -424,19 +462,24 @@ public class ContactsController { shortContacts.put(shortNumber, contact); } } - pCur.close(); + try { + pCur.close(); + } catch (Exception ignore) { + + } + pCur = null; } String ids = TextUtils.join(",", idsArr); - pCur = cr.query(ContactsContract.Data.CONTENT_URI, projectionNames, ContactsContract.CommonDataKinds.StructuredName.CONTACT_ID + " IN (" + ids + ") AND " + ContactsContract.Data.MIMETYPE + " = '" + ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE + "'", null, null); + pCur = cr.query(ContactsContract.Data.CONTENT_URI, projectionNames, ContactsContract.CommonDataKinds.StructuredName.LOOKUP_KEY + " IN (" + ids + ") AND " + ContactsContract.Data.MIMETYPE + " = '" + ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE + "'", null, null); if (pCur != null) { while (pCur.moveToNext()) { - int id = pCur.getInt(0); + String lookup_key = pCur.getString(0); String fname = pCur.getString(1); String sname = pCur.getString(2); String sname2 = pCur.getString(3); String mname = pCur.getString(4); - Contact contact = contactsMap.get(id); + Contact contact = contactsMap.get(lookup_key); if (contact != null && TextUtils.isEmpty(contact.first_name) && TextUtils.isEmpty(contact.last_name)) { contact.first_name = fname; contact.last_name = sname; @@ -458,56 +501,25 @@ public class ContactsController { } } } - pCur.close(); + try { + pCur.close(); + } catch (Exception ignore) { + + } + pCur = null; } - /*try { - pCur = cr.query(ContactsContract.RawContacts.CONTENT_URI, new String[]{"display_name", ContactsContract.RawContacts.SYNC1, ContactsContract.RawContacts.CONTACT_ID}, ContactsContract.RawContacts.ACCOUNT_TYPE + " = " + "'com.whatsapp'", null, null); + } catch (Exception e) { + FileLog.e(e); + contactsMap.clear(); + } finally { + try { if (pCur != null) { - while ((pCur.moveToNext())) { - String phone = pCur.getString(1); - if (phone == null || phone.length() == 0) { - continue; - } - boolean withPlus = phone.startsWith("+"); - phone = Utilities.parseIntToString(phone); - if (phone == null || phone.length() == 0) { - continue; - } - String shortPhone = phone; - if (!withPlus) { - phone = "+" + phone; - } - - if (shortContacts.containsKey(shortPhone)) { - continue; - } - - String name = pCur.getString(0); - if (TextUtils.isEmpty(name)) { - continue; - } - - Contact contact = new Contact(); - contact.first_name = name; - contact.last_name = ""; - contact.id = pCur.getInt(2); - contactsMap.put(contact.id, contact); - - contact.phoneDeleted.add(0); - contact.shortPhones.add(shortPhone); - contact.phones.add(phone); - contact.phoneTypes.add(LocaleController.getString("PhoneMobile", R.string.PhoneMobile)); - shortContacts.put(shortPhone, contact); - } pCur.close(); } } catch (Exception e) { FileLog.e(e); - }*/ - } catch (Exception e) { - FileLog.e(e); - contactsMap.clear(); + } } /*if (BuildVars.DEBUG_VERSION) { for (HashMap.Entry entry : contactsMap.entrySet()) { @@ -529,9 +541,9 @@ public class ContactsController { return contactsMap; } - public HashMap getContactsCopy(HashMap original) { - HashMap ret = new HashMap<>(); - for (HashMap.Entry entry : original.entrySet()) { + public HashMap getContactsCopy(HashMap original) { + HashMap ret = new HashMap<>(); + for (HashMap.Entry entry : original.entrySet()) { Contact copyContact = new Contact(); Contact originalContact = entry.getValue(); copyContact.phoneDeleted.addAll(originalContact.phoneDeleted); @@ -540,13 +552,49 @@ public class ContactsController { copyContact.shortPhones.addAll(originalContact.shortPhones); copyContact.first_name = originalContact.first_name; copyContact.last_name = originalContact.last_name; - copyContact.id = originalContact.id; - ret.put(copyContact.id, copyContact); + copyContact.contact_id = originalContact.contact_id; + copyContact.key = originalContact.key; + ret.put(copyContact.key, copyContact); } return ret; } - protected void performSyncPhoneBook(final HashMap contactHashMap, final boolean request, final boolean first, final boolean schedule, final boolean force) { + protected void migratePhoneBookToV7(final HashMap contactHashMap) { + Utilities.globalQueue.postRunnable(new Runnable() { + @Override + public void run() { + if (migratingContacts) { + return; + } + migratingContacts = true; + HashMap migratedMap = new HashMap<>(); + HashMap contactsMap = readContactsFromPhoneBook(); + final HashMap contactsBookShort = new HashMap<>(); + for (HashMap.Entry entry : contactsMap.entrySet()) { + Contact value = entry.getValue(); + for (int a = 0; a < value.shortPhones.size(); a++) { + contactsBookShort.put(value.shortPhones.get(a), value.key); + } + } + for (HashMap.Entry entry : contactHashMap.entrySet()) { + Contact value = entry.getValue(); + for (int a = 0; a < value.shortPhones.size(); a++) { + String sphone = value.shortPhones.get(a); + String key = contactsBookShort.get(sphone); + if (key != null) { + value.key = key; + migratedMap.put(key, value); + break; + } + } + } + FileLog.d("migrated contacts " + migratedMap.size() + " of " + contactHashMap.size()); + MessagesStorage.getInstance().putCachedPhoneBook(migratedMap, true); + } + }); + } + + protected void performSyncPhoneBook(final HashMap contactHashMap, final boolean request, final boolean first, final boolean schedule, final boolean force, final boolean checkCount, final boolean canceled) { if (!first && !contactsBookLoaded) { return; } @@ -554,6 +602,8 @@ public class ContactsController { @Override public void run() { + int newPhonebookContacts = 0; + int serverContactsInPhonebook = 0; boolean disableDeletion = true; //disable contacts deletion, because phone numbers can't be compared due to different numbers format /*if (schedule) { try { @@ -579,7 +629,7 @@ public class ContactsController { }*/ HashMap contactShortHashMap = new HashMap<>(); - for (HashMap.Entry entry : contactHashMap.entrySet()) { + for (HashMap.Entry entry : contactHashMap.entrySet()) { Contact c = entry.getValue(); for (int a = 0; a < c.shortPhones.size(); a++) { contactShortHashMap.put(c.shortPhones.get(a), c); @@ -590,14 +640,14 @@ public class ContactsController { if (!schedule) { checkContactsInternal(); } - final HashMap contactsMap = readContactsFromPhoneBook(); + final HashMap contactsMap = readContactsFromPhoneBook(); final HashMap contactsBookShort = new HashMap<>(); int oldCount = contactHashMap.size(); ArrayList toImport = new ArrayList<>(); if (!contactHashMap.isEmpty()) { - for (HashMap.Entry pair : contactsMap.entrySet()) { - Integer id = pair.getKey(); + for (HashMap.Entry pair : contactsMap.entrySet()) { + String id = pair.getKey(); Contact value = pair.getValue(); Contact existing = contactHashMap.get(id); if (existing == null) { @@ -605,16 +655,20 @@ public class ContactsController { Contact c = contactShortHashMap.get(value.shortPhones.get(a)); if (c != null) { existing = c; - id = existing.id; + id = existing.key; break; } } } + if (existing != null) { + value.imported = existing.imported; + } boolean nameChanged = existing != null && (!TextUtils.isEmpty(value.first_name) && !existing.first_name.equals(value.first_name) || !TextUtils.isEmpty(value.last_name) && !existing.last_name.equals(value.last_name)); if (existing == null || nameChanged) { for (int a = 0; a < value.phones.size(); a++) { String sphone = value.shortPhones.get(a); + String sphone9 = sphone.substring(Math.max(0, sphone.length() - 7)); contactsBookShort.put(sphone, value); if (existing != null) { int index = existing.shortPhones.indexOf(sphone); @@ -627,12 +681,16 @@ public class ContactsController { } } if (request) { - if (!nameChanged && contactsByPhone.containsKey(sphone)) { - continue; + if (!nameChanged) { + if (contactsByPhone.containsKey(sphone)) { + serverContactsInPhonebook++; + continue; + } + newPhonebookContacts++; } TLRPC.TL_inputPhoneContact imp = new TLRPC.TL_inputPhoneContact(); - imp.client_id = value.id; + imp.client_id = value.contact_id; imp.client_id |= ((long) a) << 32; imp.first_name = value.first_name; imp.last_name = value.last_name; @@ -646,6 +704,7 @@ public class ContactsController { } else { for (int a = 0; a < value.phones.size(); a++) { String sphone = value.shortPhones.get(a); + String sphone9 = sphone.substring(Math.max(0, sphone.length() - 7)); contactsBookShort.put(sphone, value); int index = existing.shortPhones.indexOf(sphone); boolean emptyNameReimport = false; @@ -653,10 +712,15 @@ public class ContactsController { TLRPC.TL_contact contact = contactsByPhone.get(sphone); if (contact != null) { TLRPC.User user = MessagesController.getInstance().getUser(contact.user_id); - if (user != null && TextUtils.isEmpty(user.first_name) && TextUtils.isEmpty(user.last_name) && (!TextUtils.isEmpty(value.first_name) || !TextUtils.isEmpty(value.last_name))) { - index = -1; - emptyNameReimport = true; + if (user != null) { + serverContactsInPhonebook++; + if (TextUtils.isEmpty(user.first_name) && TextUtils.isEmpty(user.last_name) && (!TextUtils.isEmpty(value.first_name) || !TextUtils.isEmpty(value.last_name))) { + index = -1; + emptyNameReimport = true; + } } + } else if (contactsByShortPhone.containsKey(sphone9)) { + serverContactsInPhonebook++; } } if (index == -1) { @@ -666,17 +730,22 @@ public class ContactsController { if (contact != null) { TLRPC.User user = MessagesController.getInstance().getUser(contact.user_id); if (user != null) { + serverContactsInPhonebook++; String firstName = user.first_name != null ? user.first_name : ""; String lastName = user.last_name != null ? user.last_name : ""; if (firstName.equals(value.first_name) && lastName.equals(value.last_name) || TextUtils.isEmpty(value.first_name) && TextUtils.isEmpty(value.last_name)) { continue; } + } else { + newPhonebookContacts++; } + } else if (contactsByShortPhone.containsKey(sphone9)) { + serverContactsInPhonebook++; } } TLRPC.TL_inputPhoneContact imp = new TLRPC.TL_inputPhoneContact(); - imp.client_id = value.id; + imp.client_id = value.contact_id; imp.client_id |= ((long) a) << 32; imp.first_name = value.first_name; imp.last_name = value.last_name; @@ -702,7 +771,7 @@ public class ContactsController { } if (request && !contactHashMap.isEmpty() && !contactsMap.isEmpty()) { if (toImport.isEmpty()) { - MessagesStorage.getInstance().putCachedPhoneBook(contactsMap); + MessagesStorage.getInstance().putCachedPhoneBook(contactsMap, false); } if (!disableDeletion && !contactHashMap.isEmpty()) { AndroidUtilities.runOnUIThread(new Runnable() { @@ -733,7 +802,7 @@ public class ContactsController { contactsPhonesShort.put(user.phone, user); } int removed = 0; - for (HashMap.Entry entry : contactHashMap.entrySet()) { + for (HashMap.Entry entry : contactHashMap.entrySet()) { Contact contact = entry.getValue(); boolean was = false; for (int a = 0; a < contact.shortPhones.size(); a++) { @@ -763,26 +832,30 @@ public class ContactsController { } } } else if (request) { - for (HashMap.Entry pair : contactsMap.entrySet()) { + for (HashMap.Entry pair : contactsMap.entrySet()) { Contact value = pair.getValue(); - int id = pair.getKey(); + String key = pair.getKey(); for (int a = 0; a < value.phones.size(); a++) { if (!force) { - String phone = value.shortPhones.get(a); - TLRPC.TL_contact contact = contactsByPhone.get(phone); + String sphone = value.shortPhones.get(a); + String sphone9 = sphone.substring(Math.max(0, sphone.length() - 7)); + TLRPC.TL_contact contact = contactsByPhone.get(sphone); if (contact != null) { TLRPC.User user = MessagesController.getInstance().getUser(contact.user_id); if (user != null) { + serverContactsInPhonebook++; String firstName = user.first_name != null ? user.first_name : ""; String lastName = user.last_name != null ? user.last_name : ""; if (firstName.equals(value.first_name) && lastName.equals(value.last_name) || TextUtils.isEmpty(value.first_name) && TextUtils.isEmpty(value.last_name)) { continue; } } + } else if (contactsByShortPhone.containsKey(sphone9)) { + serverContactsInPhonebook++; } } TLRPC.TL_inputPhoneContact imp = new TLRPC.TL_inputPhoneContact(); - imp.client_id = id; + imp.client_id = value.contact_id; imp.client_id |= ((long) a) << 32; imp.first_name = value.first_name; imp.last_name = value.last_name; @@ -803,31 +876,90 @@ public class ContactsController { } }*/ - final HashMap contactsMapToSave = new HashMap<>(contactsMap); + final int checkType; + if (checkCount) { + if (newPhonebookContacts >= 30) { + checkType = 1; + } else if (first && contactHashMap.isEmpty() && contactsByPhone.size() - serverContactsInPhonebook > contactsByPhone.size() / 3 * 2) { + checkType = 2; + } else { + checkType = 0; + } + } else { + checkType = 0; + } + FileLog.d("new phone book contacts " + newPhonebookContacts + " serverContactsInPhonebook " + serverContactsInPhonebook + " totalContacts " + contactsByPhone.size()); + if (checkType != 0) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.hasNewContactsToImport, checkType, contactHashMap, first, schedule); + } + }); + return; + } else if (canceled) { + Utilities.stageQueue.postRunnable(new Runnable() { + @Override + public void run() { + contactsBookSPhones = contactsBookShort; + contactsBook = contactsMap; + contactsSyncInProgress = false; + contactsBookLoaded = true; + if (first) { + contactsLoaded = true; + } + if (!delayedContactsUpdate.isEmpty() && contactsLoaded) { + applyContactsUpdates(delayedContactsUpdate, null, null, null); + delayedContactsUpdate.clear(); + } + MessagesStorage.getInstance().putCachedPhoneBook(contactsMap, false); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + updateUnregisteredContacts(contacts); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.contactsDidLoaded); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.contactsImported); + } + }); + } + }); + return; + } + + final boolean[] hasErrors = new boolean[]{false}; + final HashMap contactsMapToSave = new HashMap<>(contactsMap); + final HashMap contactIdToKey = new HashMap<>(); + for (HashMap.Entry entry : contactsMapToSave.entrySet()) { + Contact value = entry.getValue(); + contactIdToKey.put(value.contact_id, value.key); + } completedRequestsCount = 0; - final int count = (int) Math.ceil(toImport.size() / 500.0f); + final int count = (int) Math.ceil(toImport.size() / 500.0); for (int a = 0; a < count; a++) { - ArrayList finalToImport = new ArrayList<>(); - finalToImport.addAll(toImport.subList(a * 500, Math.min((a + 1) * 500, toImport.size()))); - TLRPC.TL_contacts_importContacts req = new TLRPC.TL_contacts_importContacts(); - req.contacts = finalToImport; - req.replace = false; + final TLRPC.TL_contacts_importContacts req = new TLRPC.TL_contacts_importContacts(); + int start = a * 500; + int end = Math.min(start + 500, toImport.size()); + req.contacts = new ArrayList<>(toImport.subList(start, end)); ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override public void run(TLObject response, TLRPC.TL_error error) { completedRequestsCount++; if (error == null) { FileLog.e("contacts imported"); - TLRPC.TL_contacts_importedContacts res = (TLRPC.TL_contacts_importedContacts) response; + final TLRPC.TL_contacts_importedContacts res = (TLRPC.TL_contacts_importedContacts) response; if (!res.retry_contacts.isEmpty()) { for (int a = 0; a < res.retry_contacts.size(); a++) { long id = res.retry_contacts.get(a); - contactsMapToSave.remove((int) id); + contactsMapToSave.remove(contactIdToKey.get((int) id)); } + hasErrors[0] = true; } - - if (completedRequestsCount == count && !contactsMapToSave.isEmpty()) { - MessagesStorage.getInstance().putCachedPhoneBook(contactsMapToSave); + for (int a = 0; a < res.popular_invites.size(); a++) { + TLRPC.TL_popularContact popularContact = res.popular_invites.get(a); + Contact contact = contactsMap.get(contactIdToKey.get((int) popularContact.client_id)); + if (contact != null) { + contact.imported = popularContact.importers; + } } /*if (BuildVars.DEBUG_VERSION) { @@ -844,9 +976,17 @@ public class ContactsController { } processLoadedContacts(cArr, res.users, 2); } else { + for (int a = 0; a < req.contacts.size(); a++) { + TLRPC.TL_inputPhoneContact contact = req.contacts.get(a); + contactsMapToSave.remove(contactIdToKey.get((int) contact.client_id)); + } + hasErrors[0] = true; FileLog.e("import contacts error " + error.text); } if (completedRequestsCount == count) { + if (!contactsMapToSave.isEmpty()) { + MessagesStorage.getInstance().putCachedPhoneBook(contactsMapToSave, false); + } Utilities.stageQueue.postRunnable(new Runnable() { @Override public void run() { @@ -861,6 +1001,20 @@ public class ContactsController { applyContactsUpdates(delayedContactsUpdate, null, null, null); delayedContactsUpdate.clear(); } + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.contactsImported); + } + }); + if (hasErrors[0]) { + Utilities.globalQueue.postRunnable(new Runnable() { + @Override + public void run() { + MessagesStorage.getInstance().getCachedPhoneBook(true); + } + }, 5000); + } } }); } @@ -887,6 +1041,7 @@ public class ContactsController { public void run() { updateUnregisteredContacts(contacts); NotificationCenter.getInstance().postNotificationName(NotificationCenter.contactsDidLoaded); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.contactsImported); } }); } @@ -910,7 +1065,7 @@ public class ContactsController { } }); if (!contactsMap.isEmpty()) { - MessagesStorage.getInstance().putCachedPhoneBook(contactsMap); + MessagesStorage.getInstance().putCachedPhoneBook(contactsMap, false); } } } @@ -923,7 +1078,33 @@ public class ContactsController { } } - public void loadContacts(boolean fromCache, boolean cacheEmpty) { + private int getContactsHash(ArrayList contacts) { + long acc = 0; + contacts = new ArrayList<>(contacts); + Collections.sort(contacts, new Comparator() { + @Override + public int compare(TLRPC.TL_contact tl_contact, TLRPC.TL_contact tl_contact2) { + if (tl_contact.user_id > tl_contact2.user_id) { + return 1; + } else if (tl_contact.user_id < tl_contact2.user_id) { + return -1; + } + return 0; + } + }); + int count = contacts.size(); + for (int a = -1; a < count; a++) { + if (a == -1) { + acc = ((acc * 20261) + 0x80000000L + UserConfig.contactsSavedCount) % 0x80000000L; + } else { + TLRPC.TL_contact set = contacts.get(a); + acc = ((acc * 20261) + 0x80000000L + set.user_id) % 0x80000000L; + } + } + return (int) acc; + } + + public void loadContacts(boolean fromCache, final int hash) { synchronized (loadContactsSync) { loadingContacts = true; } @@ -932,19 +1113,22 @@ public class ContactsController { MessagesStorage.getInstance().getContacts(); } else { FileLog.e("load contacts from server"); + TLRPC.TL_contacts_getContacts req = new TLRPC.TL_contacts_getContacts(); - req.hash = cacheEmpty ? "" : UserConfig.contactsHash; + req.hash = hash; ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override public void run(TLObject response, TLRPC.TL_error error) { if (error == null) { TLRPC.contacts_Contacts res = (TLRPC.contacts_Contacts) response; - if (res instanceof TLRPC.TL_contacts_contactsNotModified) { + if (hash != 0 && res instanceof TLRPC.TL_contacts_contactsNotModified) { contactsLoaded = true; if (!delayedContactsUpdate.isEmpty() && contactsBookLoaded) { applyContactsUpdates(delayedContactsUpdate, null, null, null); delayedContactsUpdate.clear(); } + UserConfig.lastContactsSyncTime = (int) (System.currentTimeMillis() / 1000); + UserConfig.saveConfig(false); AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { @@ -956,6 +1140,9 @@ public class ContactsController { }); FileLog.e("load contacts don't change"); return; + } else { + UserConfig.contactsSavedCount = res.saved_count; + UserConfig.saveConfig(false); } processLoadedContacts(res.contacts, res.users, 0); } @@ -1001,17 +1188,20 @@ public class ContactsController { public void run() { FileLog.e("done loading contacts"); if (from == 1 && (contactsArr.isEmpty() || Math.abs(System.currentTimeMillis() / 1000 - UserConfig.lastContactsSyncTime) >= 24 * 60 * 60)) { - loadContacts(false, true); - return; + loadContacts(false, getContactsHash(contactsArr)); + if (contactsArr.isEmpty()) { + return; + } } if (from == 0) { UserConfig.lastContactsSyncTime = (int) (System.currentTimeMillis() / 1000); UserConfig.saveConfig(false); } - for (TLRPC.TL_contact contact : contactsArr) { + for (int a = 0; a < contactsArr.size(); a++) { + TLRPC.TL_contact contact = contactsArr.get(a); if (usersDict.get(contact.user_id) == null && contact.user_id != UserConfig.getClientUserId()) { - loadContacts(false, true); + loadContacts(false, 0); FileLog.e("contacts are broken, load from server"); return; } @@ -1020,26 +1210,6 @@ public class ContactsController { if (from != 1) { MessagesStorage.getInstance().putUsersAndChats(usersArr, null, true, true); MessagesStorage.getInstance().putContacts(contactsArr, from != 2); - Collections.sort(contactsArr, new Comparator() { - @Override - public int compare(TLRPC.TL_contact tl_contact, TLRPC.TL_contact tl_contact2) { - if (tl_contact.user_id > tl_contact2.user_id) { - return 1; - } else if (tl_contact.user_id < tl_contact2.user_id) { - return -1; - } - return 0; - } - }); - StringBuilder ids = new StringBuilder(); - for (TLRPC.TL_contact aContactsArr : contactsArr) { - if (ids.length() != 0) { - ids.append(","); - } - ids.append(aContactsArr.user_id); - } - UserConfig.contactsHash = Utilities.MD5(ids.toString()); - UserConfig.saveConfig(false); } Collections.sort(contactsArr, new Comparator() { @@ -1053,18 +1223,21 @@ public class ContactsController { } }); - final SparseArray contactsDictionary = new SparseArray<>(); + final ConcurrentHashMap contactsDictionary = new ConcurrentHashMap<>(20, 1.0f, 2); final HashMap> sectionsDict = new HashMap<>(); final HashMap> sectionsDictMutual = new HashMap<>(); final ArrayList sortedSectionsArray = new ArrayList<>(); final ArrayList sortedSectionsArrayMutual = new ArrayList<>(); HashMap contactsByPhonesDict = null; + HashMap contactsByPhonesShortDict = null; if (!contactsBookLoaded) { contactsByPhonesDict = new HashMap<>(); + contactsByPhonesShortDict = new HashMap<>(); } final HashMap contactsByPhonesDictFinal = contactsByPhonesDict; + final HashMap contactsByPhonesShortDictFinal = contactsByPhonesShortDict; for (int a = 0; a < contactsArr.size(); a++) { TLRPC.TL_contact value = contactsArr.get(a); @@ -1075,6 +1248,7 @@ public class ContactsController { contactsDictionary.put(value.user_id, value); if (contactsByPhonesDict != null && !TextUtils.isEmpty(user.phone)) { contactsByPhonesDict.put(user.phone, value); + contactsByPhonesShortDict.put(user.phone.substring(Math.max(0, user.phone.length() - 7)), value); } String key = UserObject.getFirstName(user); @@ -1176,13 +1350,14 @@ public class ContactsController { @Override public void run() { contactsByPhone = contactsByPhonesDictFinal; + contactsByShortPhone = contactsByPhonesShortDictFinal; } }); if (contactsSyncInProgress) { return; } contactsSyncInProgress = true; - MessagesStorage.getInstance().getCachedPhoneBook(); + MessagesStorage.getInstance().getCachedPhoneBook(false); } }); } else { @@ -1228,9 +1403,8 @@ public class ContactsController { } final ArrayList sortedPhoneBookContacts = new ArrayList<>(); - for (HashMap.Entry pair : contactsBook.entrySet()) { + for (HashMap.Entry pair : contactsBook.entrySet()) { Contact value = pair.getValue(); - int id = pair.getKey(); boolean skip = false; for (int a = 0; a < value.phones.size(); a++) { @@ -1278,11 +1452,11 @@ public class ContactsController { }); } - StringBuilder ids = new StringBuilder(); final HashMap> sectionsDict = new HashMap<>(); final ArrayList sortedSectionsArray = new ArrayList<>(); - for (TLRPC.TL_contact value : contacts) { + for (int a = 0; a < contacts.size(); a++) { + TLRPC.TL_contact value = contacts.get(a); TLRPC.User user = MessagesController.getInstance().getUser(value.user_id); if (user == null) { continue; @@ -1308,13 +1482,7 @@ public class ContactsController { sortedSectionsArray.add(key); } arr.add(value); - if (ids.length() != 0) { - ids.append(","); - } - ids.append(value.user_id); } - UserConfig.contactsHash = Utilities.MD5(ids.toString()); - UserConfig.saveConfig(false); Collections.sort(sortedSectionsArray, new Comparator() { @Override @@ -1493,7 +1661,7 @@ public class ContactsController { Utilities.stageQueue.postRunnable(new Runnable() { @Override public void run() { - loadContacts(false, true); + loadContacts(false, 0); } }); } else { @@ -1521,7 +1689,7 @@ public class ContactsController { updateUnregisteredContacts(contacts); performWriteContactsToPhoneBook(); } - performSyncPhoneBook(getContactsCopy(contactsBook), false, false, false, false); + performSyncPhoneBook(getContactsCopy(contactsBook), false, false, false, false, true, false); buildContactsSectionsArrays(!newContacts.isEmpty()); NotificationCenter.getInstance().postNotificationName(NotificationCenter.contactsDidLoaded); } @@ -1685,7 +1853,6 @@ public class ContactsController { c.client_id = 0; contactsParams.add(c); req.contacts = contactsParams; - req.replace = false; /*if (BuildVars.DEBUG_VERSION) { FileLog.e("add contact " + user.first_name + " " + user.last_name + " " + user.phone); }*/ @@ -1839,7 +2006,7 @@ public class ContactsController { if (!vector.objects.isEmpty()) { ArrayList dbUsersStatus = new ArrayList<>(); for (Object object : vector.objects) { - TLRPC.User toDbUser = new TLRPC.User(); + TLRPC.User toDbUser = new TLRPC.TL_user(); TLRPC.TL_contactStatus status = (TLRPC.TL_contactStatus) object; if (status == null) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/CustomTabsCopyReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/CustomTabsCopyReceiver.java new file mode 100644 index 000000000..0002c675c --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/CustomTabsCopyReceiver.java @@ -0,0 +1,26 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.messenger; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.widget.Toast; + +public class CustomTabsCopyReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + String url = intent.getDataString(); + if (url != null) { + AndroidUtilities.addToClipboard(url); + Toast.makeText(context, LocaleController.getString("LinkCopied", R.string.LinkCopied), Toast.LENGTH_SHORT).show(); + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/Emoji.java b/TMessagesProj/src/main/java/org/telegram/messenger/Emoji.java index 888563acb..bf1cdf388 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/Emoji.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/Emoji.java @@ -10,9 +10,14 @@ package org.telegram.messenger; import java.io.File; import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.Locale; +import android.app.Activity; +import android.content.SharedPreferences; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; @@ -32,6 +37,7 @@ import android.view.ViewGroup; import android.widget.TextView; public class Emoji { + private static HashMap rects = new HashMap<>(); private static int drawImgSize; private static int bigImgSize; @@ -41,10 +47,15 @@ public class Emoji { private static Bitmap emojiBmp[][] = new Bitmap[5][splitCount]; private static boolean loadingEmoji[][] = new boolean[5][splitCount]; + public static HashMap emojiUseHistory = new HashMap<>(); + public static ArrayList recentEmoji = new ArrayList<>(); + public static HashMap emojiColor = new HashMap<>(); + private static boolean recentEmojiLoaded; + private static final int[][] cols = { - {15, 15, 15, 15}, + {16, 16, 16, 16}, {6, 6, 6, 6}, - {8, 8, 8, 8}, + {9, 9, 9, 9}, {9, 9, 9, 9}, {10, 10, 10, 10} }; @@ -129,7 +140,7 @@ public class Emoji { imageFile.delete(); } } - for (int a = 8; a < 11; a++) { + for (int a = 8; a < 12; a++) { imageName = String.format(Locale.US, "v%d_emoji%.01fx_%d.png", a, scale, page); imageFile = ApplicationLoader.applicationContext.getFileStreamPath(imageName); if (imageFile.exists()) { @@ -141,7 +152,7 @@ public class Emoji { } Bitmap bitmap = null; try { - InputStream is = ApplicationLoader.applicationContext.getAssets().open("emoji/" + String.format(Locale.US, "v11_emoji%.01fx_%d_%d.png", scale, page, page2)); + InputStream is = ApplicationLoader.applicationContext.getAssets().open("emoji/" + String.format(Locale.US, "v12_emoji%.01fx_%d_%d.png", scale, page, page2)); BitmapFactory.Options opts = new BitmapFactory.Options(); opts.inJustDecodeBounds = false; opts.inSampleSize = imageResize; @@ -208,6 +219,12 @@ public class Emoji { public static EmojiDrawable getEmojiDrawable(CharSequence code) { DrawableInfo info = rects.get(code); + if (info == null) { + CharSequence newCode = EmojiData.emojiAliasMap.get(code); + if (newCode != null) { + info = Emoji.rects.get(newCode); + } + } if (info == null) { FileLog.e("No drawable for emoji " + code); return null; @@ -219,6 +236,12 @@ public class Emoji { public static Drawable getEmojiBigDrawable(String code) { EmojiDrawable ed = getEmojiDrawable(code); + if (ed == null) { + CharSequence newCode = EmojiData.emojiAliasMap.get(code); + if (newCode != null) { + ed = Emoji.getEmojiDrawable(newCode); + } + } if (ed == null) { return null; } @@ -410,12 +433,27 @@ public class Emoji { emojiOnly = null; } } - if (doneEmoji && i + 2 < length && cs.charAt(i + 1) == 0xD83C) { - char next = cs.charAt(i + 2); - if (next >= 0xDFFB && next <= 0xDFFF) { - emojiCode.append(cs.subSequence(i + 1, i + 3)); - startLength += 2; - i += 2; + if (doneEmoji && i + 2 < length) { + char next = cs.charAt(i + 1); + if (next == 0xD83C){ + next = cs.charAt(i + 2); + if (next >= 0xDFFB && next <= 0xDFFF) { + emojiCode.append(cs.subSequence(i + 1, i + 3)); + startLength += 2; + i += 2; + } + } else if (emojiCode.length() >= 2 && emojiCode.charAt(0) == 0xD83C && emojiCode.charAt(1) == 0xDFF4 && next == 0xDB40) { + i++; + while (true) { + emojiCode.append(cs.subSequence(i, i + 2)); + startLength += 2; + i += 2; + if (i >= cs.length() || cs.charAt(i) != 0xDB40) { + i--; + break; + } + } + } } previousGoodIndex = i; @@ -449,7 +487,8 @@ public class Emoji { if (emojiOnly != null) { emojiOnly[0]++; } - drawable = Emoji.getEmojiDrawable(emojiCode.subSequence(0, emojiCode.length())); + CharSequence code = emojiCode.subSequence(0, emojiCode.length()); + drawable = Emoji.getEmojiDrawable(code); if (drawable != null) { span = new EmojiSpan(drawable, DynamicDrawableSpan.ALIGN_BOTTOM, size, fontMetrics); s.setSpan(span, startIndex, startIndex + startLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); @@ -524,4 +563,168 @@ public class Emoji { } } } + + public static void addRecentEmoji(String code) { + Integer count = emojiUseHistory.get(code); + if (count == null) { + count = 0; + } + if (count == 0 && emojiUseHistory.size() > 50) { + for (int a = recentEmoji.size() - 1; a >= 0; a--) { + String emoji = recentEmoji.get(a); + emojiUseHistory.remove(emoji); + recentEmoji.remove(a); + if (emojiUseHistory.size() <= 50) { + break; + } + } + } + emojiUseHistory.put(code, ++count); + } + + public static void sortEmoji() { + recentEmoji.clear(); + for (HashMap.Entry entry : emojiUseHistory.entrySet()) { + recentEmoji.add(entry.getKey()); + } + Collections.sort(recentEmoji, new Comparator() { + @Override + public int compare(String lhs, String rhs) { + Integer count1 = emojiUseHistory.get(lhs); + Integer count2 = emojiUseHistory.get(rhs); + if (count1 == null) { + count1 = 0; + } + if (count2 == null) { + count2 = 0; + } + if (count1 > count2) { + return -1; + } else if (count1 < count2) { + return 1; + } + return 0; + } + }); + while (recentEmoji.size() > 50) { + recentEmoji.remove(recentEmoji.size() - 1); + } + } + + public static void saveRecentEmoji() { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("emoji", Activity.MODE_PRIVATE); + StringBuilder stringBuilder = new StringBuilder(); + for (HashMap.Entry entry : emojiUseHistory.entrySet()) { + if (stringBuilder.length() != 0) { + stringBuilder.append(","); + } + stringBuilder.append(entry.getKey()); + stringBuilder.append("="); + stringBuilder.append(entry.getValue()); + } + preferences.edit().putString("emojis2", stringBuilder.toString()).commit(); + } + + public static void clearRecentEmoji() { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("emoji", Activity.MODE_PRIVATE); + preferences.edit().putBoolean("filled_default", true).commit(); + emojiUseHistory.clear(); + recentEmoji.clear(); + saveRecentEmoji(); + } + + public static void loadRecentEmoji() { + if (recentEmojiLoaded) { + return; + } + recentEmojiLoaded = true; + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("emoji", Activity.MODE_PRIVATE); + + String str; + try { + emojiUseHistory.clear(); + if (preferences.contains("emojis")) { + str = preferences.getString("emojis", ""); + if (str != null && str.length() > 0) { + String[] args = str.split(","); + for (String arg : args) { + String[] args2 = arg.split("="); + long value = Utilities.parseLong(args2[0]); + String string = ""; + for (int a = 0; a < 4; a++) { + char ch = (char) value; + string = String.valueOf(ch) + string; + value >>= 16; + if (value == 0) { + break; + } + } + if (string.length() > 0) { + emojiUseHistory.put(string, Utilities.parseInt(args2[1])); + } + } + } + preferences.edit().remove("emojis").commit(); + saveRecentEmoji(); + } else { + str = preferences.getString("emojis2", ""); + if (str != null && str.length() > 0) { + String[] args = str.split(","); + for (String arg : args) { + String[] args2 = arg.split("="); + emojiUseHistory.put(args2[0], Utilities.parseInt(args2[1])); + } + } + } + if (emojiUseHistory.isEmpty()) { + if (!preferences.getBoolean("filled_default", false)) { + String[] newRecent = new String[]{ + "\uD83D\uDE02", "\uD83D\uDE18", "\u2764", "\uD83D\uDE0D", "\uD83D\uDE0A", "\uD83D\uDE01", + "\uD83D\uDC4D", "\u263A", "\uD83D\uDE14", "\uD83D\uDE04", "\uD83D\uDE2D", "\uD83D\uDC8B", + "\uD83D\uDE12", "\uD83D\uDE33", "\uD83D\uDE1C", "\uD83D\uDE48", "\uD83D\uDE09", "\uD83D\uDE03", + "\uD83D\uDE22", "\uD83D\uDE1D", "\uD83D\uDE31", "\uD83D\uDE21", "\uD83D\uDE0F", "\uD83D\uDE1E", + "\uD83D\uDE05", "\uD83D\uDE1A", "\uD83D\uDE4A", "\uD83D\uDE0C", "\uD83D\uDE00", "\uD83D\uDE0B", + "\uD83D\uDE06", "\uD83D\uDC4C", "\uD83D\uDE10", "\uD83D\uDE15"}; + for (int i = 0; i < newRecent.length; i++) { + emojiUseHistory.put(newRecent[i], newRecent.length - i); + } + preferences.edit().putBoolean("filled_default", true).commit(); + saveRecentEmoji(); + } + } + sortEmoji(); + } catch (Exception e) { + FileLog.e(e); + } + + try { + str = preferences.getString("color", ""); + if (str != null && str.length() > 0) { + String[] args = str.split(","); + for (int a = 0; a < args.length; a++) { + String arg = args[a]; + String[] args2 = arg.split("="); + emojiColor.put(args2[0], args2[1]); + } + } + } catch (Exception e) { + FileLog.e(e); + } + } + + public static void saveEmojiColors() { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("emoji", Activity.MODE_PRIVATE); + StringBuilder stringBuilder = new StringBuilder(); + for (HashMap.Entry entry : emojiColor.entrySet()) { + if (stringBuilder.length() != 0) { + stringBuilder.append(","); + } + stringBuilder.append(entry.getKey()); + stringBuilder.append("="); + stringBuilder.append(entry.getValue()); + } + preferences.edit().putString("color", stringBuilder.toString()).commit(); + } + + public static native Object[] getSuggestion(String query); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/EmojiData.java b/TMessagesProj/src/main/java/org/telegram/messenger/EmojiData.java index da7139d4f..8c06e0537 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/EmojiData.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/EmojiData.java @@ -60,16 +60,16 @@ public class EmojiData { }; public static final String[] emojiColored = { - "👐","🙌","👏","🙏","👍","👎","👊","✊","🤛","🤜","🤞","✌","🤘","👌","👈","👉","👆","👇","☝","✋","🤚","🖐","🖖","👋","🤙","💪","🖕","✍","🤳","💅","👂","👃","👶","👦","👧","👨","👩","👱‍♀","👱","👴","👵","👲","👳‍♀","👳","👮‍♀","👮","👷‍♀","👷","💂‍♀","💂","🕵‍♀","🕵","👩‍⚕","👨‍⚕","👩‍🌾","👨‍🌾","👩‍🍳","👨‍🍳","👩‍🎓","👨‍🎓","👩‍🎤","👨‍🎤","👩‍🏫","👨‍🏫","👩‍🏭","👨‍🏭","👩‍💻","👨‍💻","👩‍💼","👨‍💼","👩‍🔧","👨‍🔧","👩‍🔬","👨‍🔬","👩‍🎨","👨‍🎨","👩‍🚒","👨‍🚒","👩‍✈","👨‍✈","👩‍🚀","👨‍🚀","👩‍⚖","👨‍⚖","🤶","🎅","👸","🤴","👰","🤵","👼","🤰","🙇‍♀","🙇","💁","💁‍♂","🙅","🙅‍♂","🙆","🙆‍♂","🙋","🙋‍♂","🤦‍♀","🤦‍♂","🤷‍♀","🤷‍♂","🙎","🙎‍♂","🙍","🙍‍♂","💇","💇‍♂","💆","💆‍♂","🕴","💃","🕺","🚶‍♀","🚶","🏃‍♀","🏃","🏋‍♀","🏋","🤸‍♀","🤸‍♂","⛹‍♀","⛹","🤾‍♀","🤾‍♂","🏌‍♀","🏌","🏄‍♀","🏄","🏊‍♀","🏊","🤽‍♀","🤽‍♂","🏄","🏊‍♀","🏊","🤽‍♀","🤽‍♂","🚣‍♀","🚣","🏇","🚴‍♀","🚴","🚵‍♀","🚵","🤹‍♀","🤹‍♂","🛀" + "🤲", "👐", "🙌", "👏", "👍", "👎", "👊", "✊", "🤛", "🤜", "🤞", "✌", "🤟", "🤘", "👌", "👈", "👉", "👆", "👇", "☝", "✋", "🤚", "🖐", "🖖", "👋", "🤙", "💪", "🖕", "✍", "🙏", "👂", "👃", "👶", "👧", "🧒", "👦", "👩", "🧑", "👨", "👱‍♀", "👱‍♂", "🧔", "👵", "🧓", "👴", "👲", "👳‍♀", "👳‍♂", "🧕", "👮‍♀", "👮‍♂", "👷‍♀", "👷‍♂", "💂‍♀", "💂‍♂", "🕵‍♀", "🕵‍♂", "👩‍⚕", "👨‍⚕", "👩‍🌾", "👨‍🌾", "👩‍🍳", "👨‍🍳", "👩‍🎓", "👨‍🎓", "👩‍🎤", "👨‍🎤", "👩‍🏫", "👨‍🏫", "👩‍🏭", "👨‍🏭", "👩‍💻", "👨‍💻", "👩‍💼", "👨‍💼", "👩‍🔧", "👨‍🔧", "👩‍🔬", "👨‍🔬", "👩‍🎨", "👨‍🎨", "👩‍🚒", "👨‍🚒", "👩‍✈", "👨‍✈", "👩‍🚀", "👨‍🚀", "👩‍⚖", "👨‍⚖", "👰", "🤵", "👸", "🤴", "🤶", "🎅", "🧙‍♀", "🧙‍♂", "🧝‍♀", "🧝‍♂", "🧛‍♀", "🧛‍♂", "🧜‍♀", "🧜‍♂", "🧚‍♀", "🧚‍♂", "👼", "🤰", "🤱", "🙇‍♀", "🙇‍♂", "💁‍♀", "💁‍♂", "🙅‍♀", "🙅‍♂", "🙆‍♀", "🙆‍♂", "🙋‍♀", "🙋‍♂", "🤦‍♀", "🤦‍♂", "🤷‍♀", "🤷‍♂", "🙎‍♀", "🙎‍♂", "🙍‍♀", "🙍‍♂", "💇‍♀", "💇‍♂", "💆‍♀", "💆‍♂", "🧖‍♀", "🧖‍♂", "💅", "🤳", "💃", "🕺", "🕴", "🚶‍♀", "🚶‍♂", "🏃‍♀", "🏃‍♂", "🏋‍♀", "🏋‍♂", "🤸‍♀", "🤸‍♂", "⛹‍♀", "⛹‍♂", "🤾‍♀", "🤾‍♂", "🏌‍♀", "🏌‍♂", "🏇", "🧘‍♀", "🧘‍♂", "🏄‍♀", "🏄‍♂", "🏊‍♀", "🏊‍♂", "🤽‍♀", "🤽‍♂", "🚣‍♀", "🚣‍♂", "🧗‍♀", "🧗‍♂", "🚵‍♀", "🚵‍♂", "🚴‍♀", "🚴‍♂", "🤹‍♀", "🤹‍♂", "🛀" }; public static final String[][] dataColored = { new String[]{ - "😀","😃","😄","😁","😆","😅","😂","🤣","☺","😊","😇","🙂","🙃","😉","😌","😍","😘","😗","😙","😚","😋","😜","😝","😛","🤑","🤗","🤓","😎","🤡","🤠","😏","😒","😞","😔","😟","😕","🙁","☹","😣","😖","😫","😩","😤","😠","😡","😶","😐","😑","😯","😦","😧","😮","😲","😵","😳","😱","😨","😰","😢","😥","🤤","😭","😓","😪","😴","🙄","🤔","🤥","😬","🤐","🤢","🤧","😷","🤒","🤕","😈","👿","👹","👺","💩","👻","💀","☠","👽","👾","🤖","🎃","😺","😸","😹","😻","😼","😽","🙀","😿","😾", + "😀", "😃", "😄", "😁", "😆", "😅", "😂", "🤣", "☺", "😊", "😇", "🙂", "🙃", "😉", "😌", "😍", "😘", "😗", "😙", "😚", "😋", "😛", "😝", "😜", "🤪", "🤨", "🧐", "🤓", "😎", "🤩", "😏", "😒", "😞", "😔", "😟", "😕", "🙁", "☹", "😣", "😖", "😫", "😩", "😢", "😭", "😤", "😠", "😡", "🤬", "🤯", "😳", "😱", "😨", "😰", "😥", "😓", "🤗", "🤔", "🤭", "🤫", "🤥", "😶", "😐", "😑", "😬", "🙄", "😯", "😦", "😧", "😮", "😲", "😴", "🤤", "😪", "😵", "🤐", "🤢", "🤮", "🤧", "😷", "🤒", "🤕", "🤑", "🤠", "😈", "👿", "👹", "👺", "🤡", "💩", "👻", "💀", "☠", "👽", "👾", "🤖", "🎃", "😺", "😸", "😹", "😻", "😼", "😽", "🙀", "😿", "😾", + "🤲", "👐", "🙌", "👏", - "🙏", "🤝", "👍", "👎", @@ -79,6 +79,7 @@ public class EmojiData { "🤜", "🤞", "✌", + "🤟", "🤘", "👌", "👈", @@ -95,32 +96,36 @@ public class EmojiData { "💪", "🖕", "✍", - "🤳", - "💅", - "💍","💄","💋","👄","👅", + "🙏", + "💍", "💄", "💋", "👄", "👅", "👂", "👃", - "👣","👁","👀","🗣","👤","👥", + "👣", "👁", "👀", "🧠", "🗣", "👤", "👥", "👶", - "👦", "👧", - "👨", + "🧒", + "👦", "👩", + "🧑", + "👨", "👱‍♀", - "👱", - "👴", + "👱‍♂", + "🧔", "👵", + "🧓", + "👴", "👲", "👳‍♀", - "👳", + "👳‍♂", + "🧕", "👮‍♀", - "👮", + "👮‍♂", "👷‍♀", - "👷", + "👷‍♂", "💂‍♀", - "💂", + "💂‍♂", "🕵‍♀", - "🕵", + "🕵‍♂", "👩‍⚕", "👨‍⚕", "👩‍🌾", @@ -153,107 +158,132 @@ public class EmojiData { "👨‍🚀", "👩‍⚖", "👨‍⚖", - "🤶", - "🎅", - "👸", - "🤴", "👰", "🤵", + "👸", + "🤴", + "🤶", + "🎅", + "🧙‍♀", + "🧙‍♂", + "🧝‍♀", + "🧝‍♂", + "🧛‍♀", + "🧛‍♂", + "🧟‍♀", "🧟‍♂", "🧞‍♀", "🧞‍♂", + "🧜‍♀", + "🧜‍♂", + "🧚‍♀", + "🧚‍♂", "👼", "🤰", + "🤱", "🙇‍♀", - "🙇", - "💁", + "🙇‍♂", + "💁‍♀", "💁‍♂", - "🙅", + "🙅‍♀", "🙅‍♂", - "🙆", + "🙆‍♀", "🙆‍♂", - "🙋", + "🙋‍♀", "🙋‍♂", "🤦‍♀", "🤦‍♂", "🤷‍♀", "🤷‍♂", - "🙎", + "🙎‍♀", "🙎‍♂", - "🙍", + "🙍‍♀", "🙍‍♂", - "💇", + "💇‍♀", "💇‍♂", - "💆", + "💆‍♀", "💆‍♂", - "🕴", + "🧖‍♀", + "🧖‍♂", + "💅", + "🤳", "💃", "🕺", - "👯","👯‍♂", + "👯‍♀", "👯‍♂", + "🕴", "🚶‍♀", - "🚶", + "🚶‍♂", "🏃‍♀", - "🏃", - "👫","👭","👬","💑","👩‍❤‍👩","👨‍❤‍👨","💏","👩‍❤‍💋‍👩","👨‍❤‍💋‍👨","👪","👨‍👩‍👧","👨‍👩‍👧‍👦","👨‍👩‍👦‍👦","👨‍👩‍👧‍👧","👩‍👩‍👦","👩‍👩‍👧","👩‍👩‍👧‍👦","👩‍👩‍👦‍👦","👩‍👩‍👧‍👧","👨‍👨‍👦","👨‍👨‍👧","👨‍👨‍👧‍👦","👨‍👨‍👦‍👦","👨‍👨‍👧‍👧","👩‍👦","👩‍👧","👩‍👧‍👦","👩‍👦‍👦","👩‍👧‍👧","👨‍👦","👨‍👧","👨‍👧‍👦","👨‍👦‍👦","👨‍👧‍👧","👚","👕","👖","👔","👗","👙","👘","👠","👡","👢","👞","👟","👒","🎩","🎓","👑","⛑","🎒","👝","👛","👜","💼","👓","🕶","🌂","☂","❤","💛","💚","💙","💜","🖤","💔","❣","💕","💞","💓","💗","💖","💘","💝" + "🏃‍♂", + "👫", "👭", "👬", "💑", "👩‍❤‍👩", "👨‍❤‍👨", "💏", "👩‍❤‍💋‍👩", "👨‍❤‍💋‍👨", "👪", "👨‍👩‍👧", "👨‍👩‍👧‍👦", "👨‍👩‍👦‍👦", "👨‍👩‍👧‍👧", "👩‍👩‍👦", "👩‍👩‍👧", "👩‍👩‍👧‍👦", "👩‍👩‍👦‍👦", "👩‍👩‍👧‍👧", "👨‍👨‍👦", "👨‍👨‍👧", "👨‍👨‍👧‍👦", "👨‍👨‍👦‍👦", "👨‍👨‍👧‍👧", "👩‍👦", "👩‍👧", "👩‍👧‍👦", "👩‍👦‍👦", "👩‍👧‍👧", "👨‍👦", "👨‍👧", "👨‍👧‍👦", "👨‍👦‍👦", "👨‍👧‍👧", "🧥", "👚", "👕", "👖", "👔", "👗", "👙", "👘", "👠", "👡", "👢", "👞", "👟", "🧦", "🧤", "🧣", "🎩", "🧢", "👒", "🎓", "⛑", "👑", "👝", "👛", "👜", "💼", "🎒", "👓", "🕶", "🌂", "❤", "🧡", "💛", "💚", "💙", "💜", "🖤", "💔", "❣", "💕", "💞", "💓", "💗", "💖", "💘", "💝" }, null, new String[]{ - "🍏","🍎","🍐","🍊","🍋","🍌","🍉","🍇","🍓","🍈","🍒","🍑","🍍","🥝","🥑","🍅","🍆","🥒","🥕","🌽","🌶","🥔","🍠","🌰","🥜","🍯","🥐","🍞","🥖","🧀","🥚","🍳","🥓","🥞","🍤","🍗","🍖","🍕","🌭","🍔","🍟","🥙","🌮","🌯","🥗","🥘","🍝","🍜","🍲","🍥","🍣","🍱","🍛","🍙","🍚","🍘","🍢","🍡","🍧","🍨","🍦","🍰","🎂","🍮","🍭","🍬","🍫","🍿","🍩","🍪","🥛","🍼","☕","🍵","🍶","🍺","🍻","🥂","🍷","🥃","🍸","🍹","🍾","🥄","🍴","🍽","⚽","🏀","🏈","⚾","🎾","🏐","🏉","🎱","🏓","🏸","🥅","🏒","🏑","🏏","⛳","🏹","🎣","🥊","🥋","⛸","🎿","⛷","🏂", + "🍏", "🍎", "🍐", "🍊", "🍋", "🍌", "🍉", "🍇", "🍓", "🍈", "🍒", "🍑", "🍍", "🥥", "🥝", "🍅", "🍆", "🥑", "🥦", "🥒", "🌶", "🌽", "🥕", "🥔", "🍠", "🥐", "🍞", "🥖", "🥨", "🧀", "🥚", "🍳", "🥞", "🥓", "🥩", "🍗", "🍖", "🌭", "🍔", "🍟", "🍕", "🥪", "🥙", "🌮", "🌯", "🥗", "🥘", "🥫", "🍝", "🍜", "🍲", "🍛", "🍣", "🍱", "🍤", "🍙", "🍚", "🍘", "🍥", "🥠", "🍢", "🍡", "🍧", "🍨", "🍦", "🥧", "🍰", "🎂", "🍮", "🍭", "🍬", "🍫", "🍿", "🍩", "🥟", "🍪", "🌰", "🥜", "🍯", "🥛", "🍼", "☕", "🍵", "🥤", "🍶", "🍺", "🍻", "🥂", "🍷", "🥃", "🍸", "🍹", "🍾", "🥄", "🍴", "🍽", "🥣", "🥡", "🥢", "⚽", "🏀", "🏈", "⚾", "🎾", "🏐", "🏉", "🎱", "🏓", "🏸", "🥅", "🏒", "🏑", "🏏", "⛳", "🏹", "🎣", "🥊", "🥋", "🎽", "⛸", "🥌", "🛷", "🎿", "⛷", "🏂", "🏋‍♀", - "🏋", - "🤺","🤼‍♀","🤼‍♂", + "🏋‍♂", + "🤼‍♀", "🤼‍♂", "🤸‍♀", "🤸‍♂", "⛹‍♀", - "⛹", + "⛹‍♂", + "🤺", "🤾‍♀", "🤾‍♂", "🏌‍♀", - "🏌", + "🏌‍♂", + "🏇", + "🧘‍♀", + "🧘‍♂", "🏄‍♀", - "🏄", + "🏄‍♂", "🏊‍♀", - "🏊", + "🏊‍♂", "🤽‍♀", "🤽‍♂", "🚣‍♀", - "🚣", - "🏇", - "🚴‍♀", - "🚴", + "🚣‍♂", + "🧗‍♀", + "🧗‍♂", "🚵‍♀", - "🚵", - "🎽","🏅","🎖","🥇","🥈","🥉","🏆","🏵","🎗","🎫","🎟","🎪", + "🚵‍♂", + "🚴‍♀", + "🚴‍♂", + "🏆", "🥇", "🥈", "🥉", "🏅", "🎖", "🏵", "🎗", "🎫", "🎟", "🎪", "🤹‍♀", "🤹‍♂", - "🎭","🎨","🎬","🎤","🎧","🎼","🎹","🥁","🎷","🎺","🎸","🎻","🎲","🎯","🎳","🎮","🎰" + "🎭", "🎨", "🎬", "🎤", "🎧", "🎼", "🎹", "🥁", "🎷", "🎺", "🎸", "🎻", "🎲", "🎯", "🎳", "🎮", "🎰" }, null, new String[]{ - "💟","☮","✝","☪","🕉","☸","✡","🔯","🕎","☯","☦","🛐","⛎","♈","♉","♊","♋","♌","♍","♎","♏","♐","♑","♒","♓","🆔","⚛","🉑","☢","☣","📴","📳","🈶","🈚","🈸","🈺","🈷","✴","🆚","💮","🉐","㊙","㊗","🈴","🈵","🈹","🈲","🅰","🅱","🆎","🆑","🅾","🆘","❌","⭕","🛑","⛔","📛","🚫","💯","💢","♨","🚷","🚯","🚳","🚱","🔞","📵","🚭","❗","❕","❓","❔","‼","⁉","🔅","🔆","〽","⚠","🚸","🔱","⚜","🔰","♻","✅","🈯","💹","❇","✳","❎","🌐","💠","Ⓜ","🌀","💤","🏧","🚾","♿","🅿","🈳","🈂","🛂","🛃","🛄","🛅","🚹","🚺","🚼","🚻","🚮","🎦","📶","🈁","🔣","ℹ","🔤","🔡","🔠","🆖","🆗","🆙","🆒","🆕","🆓","0⃣","1⃣","2⃣","3⃣","4⃣","5⃣","6⃣","7⃣","8⃣","9⃣","🔟","🔢","#⃣","*⃣","▶","⏸","⏯","⏹","⏺","⏭","⏮","⏩","⏪","⏫","⏬","◀","🔼","🔽","➡","⬅","⬆","⬇","↗","↘","↙","↖","↕","↔","↪","↩","⤴","⤵","🔀","🔁","🔂","🔄","🔃","🎵","🎶","➕","➖","➗","✖","💲","💱","™","©","®","〰","➰","➿","🔚","🔙","🔛","🔝","🔜","✔","☑","🔘","⚪","⚫","🔴","🔵","🔺","🔻","🔸","🔹","🔶","🔷","🔳","🔲","▪","▫","◾","◽","◼","◻","⬛","⬜","🔈","🔇","🔉","🔊","🔔","🔕","📣","📢","👁‍🗨","💬","💭","🗯","♠","♣","♥","♦","🃏","🎴","🀄","🕐","🕑","🕒","🕓","🕔","🕕","🕖","🕗","🕘","🕙","🕚","🕛","🕜","🕝","🕞","🕟","🕠","🕡","🕢","🕣","🕤","🕥","🕦","🕧","⌚","📱","📲","💻","⌨","🖥","🖨","🖱","🖲","🕹","🗜","💽","💾","💿","📀","📼","📷","📸","📹","🎥","📽","🎞","📞","☎","📟","📠","📺","📻","🎙","🎚","🎛","⏱","⏲","⏰","🕰","⌛","⏳","📡","🔋","🔌","💡","🔦","🕯","🗑","🛢","💸","💵","💴","💶","💷","💰","💳","💎","⚖","🔧","🔨","⚒","🛠","⛏","🔩","⚙","⛓","🔫","💣","🔪","🗡","⚔","🛡","🚬","⚰","⚱","🏺","🔮","📿","💈","⚗","🔭","🔬","🕳","💊","💉","🌡","🚽","🚰","🚿","🛁", - "🛀", - "🛎","🔑","🗝","🚪","🛋","🛏","🛌","🖼","🛍","🛒","🎁","🎈","🎏","🎀","🎊","🎉","🎎","🏮","🎐","✉","📩","📨","📧","💌","📥","📤","📦","🏷","📪","📫","📬","📭","📮","📯","📜","📃","📄","📑","📊","📈","📉","🗒","🗓","📆","📅","📇","🗃","🗳","🗄","📋","📁","📂","🗂","🗞","📰","📓","📔","📒","📕","📗","📘","📙","📚","📖","🔖","🔗","📎","🖇","📐","📏","📌","📍","✂","🖊","🖋","✒","🖌","🖍","📝","✏","🔍","🔎","🔏","🔐","🔒","🔓" + "💟", "☮", "✝", "☪", "🕉", "☸", "✡", "🔯", "🕎", "☯", "☦", "🛐", "⛎", "♈", "♉", "♊", "♋", "♌", "♍", "♎", "♏", "♐", "♑", "♒", "♓", "🆔", "⚛", "🉑", "☢", "☣", "📴", "📳", "🈶", "🈚", "🈸", "🈺", "🈷", "✴", "🆚", "💮", "🉐", "㊙", "㊗", "🈴", "🈵", "🈹", "🈲", "🅰", "🅱", "🆎", "🆑", "🅾", "🆘", "❌", "⭕", "🛑", "⛔", "📛", "🚫", "💯", "💢", "♨", "🚷", "🚯", "🚳", "🚱", "🔞", "📵", "🚭", "❗", "❕", "❓", "❔", "‼", "⁉", "🔅", "🔆", "〽", "⚠", "🚸", "🔱", "⚜", "🔰", "♻", "✅", "🈯", "💹", "❇", "✳", "❎", "🌐", "💠", "Ⓜ", "🌀", "💤", "🏧", "🚾", "♿", "🅿", "🈳", "🈂", "🛂", "🛃", "🛄", "🛅", "🚹", "🚺", "🚼", "🚻", "🚮", "🎦", "📶", "🈁", "🔣", "ℹ", "🔤", "🔡", "🔠", "🆖", "🆗", "🆙", "🆒", "🆕", "🆓", "0⃣", "1⃣", "2⃣", "3⃣", "4⃣", "5⃣", "6⃣", "7⃣", "8⃣", "9⃣", "🔟", "🔢", "#⃣", "*⃣", "⏏", "▶", "⏸", "⏯", "⏹", "⏺", "⏭", "⏮", "⏩", "⏪", "⏫", "⏬", "◀", "🔼", "🔽", "➡", "⬅", "⬆", "⬇", "↗", "↘", "↙", "↖", "↕", "↔", "↪", "↩", "⤴", "⤵", "🔀", "🔁", "🔂", "🔄", "🔃", "🎵", "🎶", "➕", "➖", "➗", "✖", "💲", "💱", "™", "©", "®", "〰", "➰", "➿", "🔚", "🔙", "🔛", "🔝", "🔜", "✔", "☑", "🔘", "⚪", "⚫", "🔴", "🔵", "🔺", "🔻", "🔸", "🔹", "🔶", "🔷", "🔳", "🔲", "▪", "▫", "◾", "◽", "◼", "◻", "⬛", "⬜", "🔈", "🔇", "🔉", "🔊", "🔔", "🔕", "📣", "📢", "👁‍🗨", "💬", "💭", "🗯", "♠", "♣", "♥", "♦", "🃏", "🎴", "🀄", "🕐", "🕑", "🕒", "🕓", "🕔", "🕕", "🕖", "🕗", "🕘", "🕙", "🕚", "🕛", "🕜", "🕝", "🕞", "🕟", "🕠", "🕡", "🕢", "🕣", "🕤", "🕥", "🕦", "🕧", "⌚", "📱", "📲", "💻", "⌨", "🖥", "🖨", "🖱", "🖲", "🕹", "🗜", "💽", "💾", "💿", "📀", "📼", "📷", "📸", "📹", "🎥", "📽", "🎞", "📞", "☎", "📟", "📠", "📺", "📻", "🎙", "🎚", "🎛", "⏱", "⏲", "⏰", "🕰", "⌛", "⏳", "📡", "🔋", "🔌", "💡", "🔦", "🕯", "🗑", "🛢", "💸", "💵", "💴", "💶", "💷", "💰", "💳", "💎", "⚖", "🔧", "🔨", "⚒", "🛠", "⛏", "🔩", "⚙", "⛓", "🔫", "💣", "🔪", "🗡", "⚔", "🛡", "🚬", "⚰", "⚱", "🏺", "🔮", "📿", "💈", "⚗", "🔭", "🔬", "🕳", "💊", "💉", "🌡", "🚽", "🚰", "🚿", + "🛁", + "🛎", "🔑", "🗝", "🚪", "🛋", "🛏", "🛌", "🖼", "🛍", "🛒", "🎁", "🎈", "🎏", "🎀", "🎊", "🎉", "🎎", "🏮", "🎐", "✉", "📩", "📨", "📧", "💌", "📥", "📤", "📦", "🏷", "📪", "📫", "📬", "📭", "📮", "📯", "📜", "📃", "📄", "📑", "📊", "📈", "📉", "🗒", "🗓", "📆", "📅", "📇", "🗃", "🗳", "🗄", "📋", "📁", "📂", "🗂", "🗞", "📰", "📓", "📔", "📒", "📕", "📗", "📘", "📙", "📚", "📖", "🔖", "🔗", "📎", "🖇", "📐", "📏", "📌", "📍", "✂", "🖊", "🖋", "✒", "🖌", "🖍", "📝", "✏", "🔍", "🔎", "🔏", "🔐", "🔒", "🔓" } }; + public static final String[] aliasOld = new String[] {"👱", "👱🏻", "👱🏼", "👱🏽", "👱🏾", "👱🏿", "👳", "👳🏻", "👳🏼", "👳🏽", "👳🏾", "👳🏿", "👷", "👷🏻", "👷🏼", "👷🏽", "👷🏾", "👷🏿", "👮", "👮🏻", "👮🏼", "👮🏽", "👮🏾", "👮🏿", "💂", "💂🏻", "💂🏼", "💂🏽", "💂🏾", "💂🏿", "🕵", "🕵🏻", "🕵🏼", "🕵🏽", "🕵🏾", "🕵🏿", "🙇", "🙇🏻", "🙇🏼", "🙇🏽", "🙇🏾", "🙇🏿", "💁", "💁🏻", "💁🏼", "💁🏽", "💁🏾", "💁🏿", "🙅", "🙅🏻", "🙅🏼", "🙅🏽", "🙅🏾", "🙅🏿", "🙆", "🙆🏻", "🙆🏼", "🙆🏽", "🙆🏾", "🙆🏿", "🙋", "🙋🏻", "🙋🏼", "🙋🏽", "🙋🏾", "🙋🏿", "🙎", "🙎🏻", "🙎🏼", "🙎🏽", "🙎🏾", "🙎🏿", "🙍", "🙍🏻", "🙍🏼", "🙍🏽", "🙍🏾", "🙍🏿", "💇", "💇🏻", "💇🏼", "💇🏽", "💇🏾", "💇🏿", "💆", "💆🏻", "💆🏼", "💆🏽", "💆🏾", "💆🏿", "🏃", "🏃🏻", "🏃🏼", "🏃🏽", "🏃🏾", "🏃🏿", "🏋", "🏋🏻", "🏋🏼", "🏋🏽", "🏋🏾", "🏋🏿", "⛹", "⛹🏻", "⛹🏼", "⛹🏽", "⛹🏾", "⛹🏿", "🏌", "🏌🏻", "🏌🏼", "🏌🏽", "🏌🏾", "🏌🏿", "🏄", "🏄🏻", "🏄🏼", "🏄🏽", "🏄🏾", "🏄🏿", "🏊", "🏊🏻", "🏊🏼", "🏊🏽", "🏊🏾", "🏊🏿", "🚣", "🚣🏻", "🚣🏼", "🚣🏽", "🚣🏾", "🚣🏿", "🚴", "🚴🏻", "🚴🏼", "🚴🏽", "🚴🏾", "🚴🏿", "🚵", "🚵🏻", "🚵🏼", "🚵🏽", "🚵🏾", "🚵🏿"}; + public static final String[] aliasNew = new String[] {"👱‍♂", "👱🏻‍♂", "👱🏼‍♂", "👱🏽‍♂", "👱🏾‍♂", "👱🏿‍♂", "👳‍♂", "👳🏻‍♂", "👳🏼‍♂", "👳🏽‍♂", "👳🏾‍♂", "👳🏿‍♂", "👷‍♂", "👷🏻‍♂", "👷🏼‍♂", "👷🏽‍♂", "👷🏾‍♂", "👷🏿‍♂", "👮‍♂", "👮🏻‍♂", "👮🏼‍♂", "👮🏽‍♂", "👮🏾‍♂", "👮🏿‍♂", "💂‍♂", "💂🏻‍♂", "💂🏼‍♂", "💂🏽‍♂", "💂🏾‍♂", "💂🏿‍♂", "🕵‍♂", "🕵🏻‍♂", "🕵🏼‍♂", "🕵🏽‍♂", "🕵🏾‍♂", "🕵🏿‍♂","🙇‍♂", "🙇🏻‍♂", "🙇🏼‍♂", "🙇🏽‍♂", "🙇🏾‍♂", "🙇🏿‍♂", "💁‍♀", "💁🏻‍♀", "💁🏼‍♀", "💁🏽‍♀", "💁🏾‍♀", "💁🏿‍♀", "🙅‍♀", "🙅🏻‍♀", "🙅🏼‍♀", "🙅🏽‍♀", "🙅🏾‍♀", "🙅🏿‍♀", "🙆‍♀", "🙆🏻‍♀", "🙆🏼‍♀", "🙆🏽‍♀", "🙆🏾‍♀", "🙆🏿‍♀", "🙋‍♀", "🙋🏻‍♀", "🙋🏼‍♀", "🙋🏽‍♀", "🙋🏾‍♀", "🙋🏿‍♀", "🙎‍♀", "🙎🏻‍♀", "🙎🏼‍♀", "🙎🏽‍♀", "🙎🏾‍♀", "🙎🏿‍♀", "🙍‍♀", "🙍🏻‍♀", "🙍🏼‍♀", "🙍🏽‍♀", "🙍🏾‍♀", "🙍🏿‍♀", "💇‍♀", "💇🏻‍♀", "💇🏼‍♀", "💇🏽‍♀", "💇🏾‍♀", "💇🏿‍♀", "💆‍♀", "💆🏻‍♀", "💆🏼‍♀", "💆🏽‍♀", "💆🏾‍♀", "💆🏿‍♀", "🏃‍♂", "🏃🏻‍♂", "🏃🏼‍♂", "🏃🏽‍♂", "🏃🏾‍♂", "🏃🏿‍♂", "🏋‍♂", "🏋🏻‍♂", "🏋🏼‍♂", "🏋🏽‍♂", "🏋🏾‍♂", "🏋🏿‍♂", "⛹‍♂", "⛹🏻‍♂", "⛹🏼‍♂", "⛹🏽‍♂", "⛹🏾‍♂", "⛹🏿‍♂", "🏌‍♂", "🏌🏻‍♂", "🏌🏼‍♂", "🏌🏽‍♂", "🏌🏾‍♂", "🏌🏿‍♂", "🏄‍♂", "🏄🏻‍♂", "🏄🏼‍♂", "🏄🏽‍♂", "🏄🏾‍♂", "🏄🏿‍♂", "🏊‍♂", "🏊🏻‍♂", "🏊🏼‍♂", "🏊🏽‍♂", "🏊🏾‍♂", "🏊🏿‍♂", "🚣‍♂", "🚣🏻‍♂", "🚣🏼‍♂", "🚣🏽‍♂", "🚣🏾‍♂", "🚣🏿‍♂", "🚴‍♂", "🚴🏻‍♂", "🚴🏼‍♂", "🚴🏽‍♂", "🚴🏾‍♂", "🚴🏿‍♂", "🚵‍♂", "🚵🏻‍♂", "🚵🏼‍♂", "🚵🏽‍♂", "🚵🏾‍♂", "🚵🏿‍♂"}; + public static final String[][] data = { new String[]{ - "😀","😃","😄","😁","😆","😅","😂","🤣","☺","😊","😇","🙂","🙃","😉","😌","😍","😘","😗","😙","😚","😋","😜","😝","😛","🤑","🤗","🤓","😎","🤡","🤠","😏","😒","😞","😔","😟","😕","🙁","☹","😣","😖","😫","😩","😤","😠","😡","😶","😐","😑","😯","😦","😧","😮","😲","😵","😳","😱","😨","😰","😢","😥","🤤","😭","😓","😪","😴","🙄","🤔","🤥","😬","🤐","🤢","🤧","😷","🤒","🤕","😈","👿","👹","👺","💩","👻","💀","☠","👽","👾","🤖","🎃","😺","😸","😹","😻","😼","😽","🙀","😿","😾","👐","👐🏻","👐🏼","👐🏽","👐🏾","👐🏿","🙌","🙌🏻","🙌🏼","🙌🏽","🙌🏾","🙌🏿","👏","👏🏻","👏🏼","👏🏽","👏🏾","👏🏿","🙏","🙏🏻","🙏🏼","🙏🏽","🙏🏾","🙏🏿","🤝","👍","👍🏻","👍🏼","👍🏽","👍🏾","👍🏿","👎","👎🏻","👎🏼","👎🏽","👎🏾","👎🏿","👊","👊🏻","👊🏼","👊🏽","👊🏾","👊🏿","✊","✊🏻","✊🏼","✊🏽","✊🏾","✊🏿","🤛","🤛🏻","🤛🏼","🤛🏽","🤛🏾","🤛🏿","🤜","🤜🏻","🤜🏼","🤜🏽","🤜🏾","🤜🏿","🤞","🤞🏻","🤞🏼","🤞🏽","🤞🏾","🤞🏿","✌","✌🏻","✌🏼","✌🏽","✌🏾","✌🏿","🤘","🤘🏻","🤘🏼","🤘🏽","🤘🏾","🤘🏿","👌","👌🏻","👌🏼","👌🏽","👌🏾","👌🏿","👈","👈🏻","👈🏼","👈🏽","👈🏾","👈🏿","👉","👉🏻","👉🏼","👉🏽","👉🏾","👉🏿","👆","👆🏻","👆🏼","👆🏽","👆🏾","👆🏿","👇","👇🏻","👇🏼","👇🏽","👇🏾","👇🏿","☝","☝🏻","☝🏼","☝🏽","☝🏾","☝🏿","✋","✋🏻","✋🏼","✋🏽","✋🏾","✋🏿","🤚","🤚🏻","🤚🏼","🤚🏽","🤚🏾","🤚🏿","🖐","🖐🏻","🖐🏼","🖐🏽","🖐🏾","🖐🏿","🖖","🖖🏻","🖖🏼","🖖🏽","🖖🏾","🖖🏿","👋","👋🏻","👋🏼","👋🏽","👋🏾","👋🏿","🤙","🤙🏻","🤙🏼","🤙🏽","🤙🏾","🤙🏿","💪","💪🏻","💪🏼","💪🏽","💪🏾","💪🏿","🖕","🖕🏻","🖕🏼","🖕🏽","🖕🏾","🖕🏿","✍","✍🏻","✍🏼","✍🏽","✍🏾","✍🏿","🤳","🤳🏻","🤳🏼","🤳🏽","🤳🏾","🤳🏿","💅","💅🏻","💅🏼","💅🏽","💅🏾","💅🏿","💍","💄","💋","👄","👅","👂","👂🏻","👂🏼","👂🏽","👂🏾","👂🏿","👃","👃🏻","👃🏼","👃🏽","👃🏾","👃🏿","👣","👁","👀","🗣","👤","👥","👶","👶🏻","👶🏼","👶🏽","👶🏾","👶🏿","👦","👦🏻","👦🏼","👦🏽","👦🏾","👦🏿","👧","👧🏻","👧🏼","👧🏽","👧🏾","👧🏿","👨","👨🏻","👨🏼","👨🏽","👨🏾","👨🏿","👩","👩🏻","👩🏼","👩🏽","👩🏾","👩🏿","👱‍♀","👱🏻‍♀","👱🏼‍♀","👱🏽‍♀","👱🏾‍♀","👱🏿‍♀","👱","👱🏻","👱🏼","👱🏽","👱🏾","👱🏿","👴","👴🏻","👴🏼","👴🏽","👴🏾","👴🏿","👵","👵🏻","👵🏼","👵🏽","👵🏾","👵🏿","👲","👲🏻","👲🏼","👲🏽","👲🏾","👲🏿","👳‍♀","👳🏻‍♀","👳🏼‍♀","👳🏽‍♀","👳🏾‍♀","👳🏿‍♀","👳","👳🏻","👳🏼","👳🏽","👳🏾","👳🏿","👮‍♀","👮🏻‍♀","👮🏼‍♀","👮🏽‍♀","👮🏾‍♀","👮🏿‍♀","👮","👮🏻","👮🏼","👮🏽","👮🏾","👮🏿","👷‍♀","👷🏻‍♀","👷🏼‍♀","👷🏽‍♀","👷🏾‍♀","👷🏿‍♀","👷","👷🏻","👷🏼","👷🏽","👷🏾","👷🏿","💂‍♀","💂🏻‍♀","💂🏼‍♀","💂🏽‍♀","💂🏾‍♀","💂🏿‍♀","💂","💂🏻","💂🏼","💂🏽","💂🏾","💂🏿","🕵‍♀","🕵🏻‍♀","🕵🏼‍♀","🕵🏽‍♀","🕵🏾‍♀","🕵🏿‍♀","🕵","🕵🏻","🕵🏼","🕵🏽","🕵🏾","🕵🏿","👩‍⚕","👩🏻‍⚕","👩🏼‍⚕","👩🏽‍⚕","👩🏾‍⚕","👩🏿‍⚕","👨‍⚕","👨🏻‍⚕","👨🏼‍⚕","👨🏽‍⚕","👨🏾‍⚕","👨🏿‍⚕","👩‍🌾","👩🏻‍🌾","👩🏼‍🌾","👩🏽‍🌾","👩🏾‍🌾","👩🏿‍🌾","👨‍🌾","👨🏻‍🌾","👨🏼‍🌾","👨🏽‍🌾","👨🏾‍🌾","👨🏿‍🌾","👩‍🍳","👩🏻‍🍳","👩🏼‍🍳","👩🏽‍🍳","👩🏾‍🍳","👩🏿‍🍳","👨‍🍳","👨🏻‍🍳","👨🏼‍🍳","👨🏽‍🍳","👨🏾‍🍳","👨🏿‍🍳","👩‍🎓","👩🏻‍🎓","👩🏼‍🎓","👩🏽‍🎓","👩🏾‍🎓","👩🏿‍🎓","👨‍🎓","👨🏻‍🎓","👨🏼‍🎓","👨🏽‍🎓","👨🏾‍🎓","👨🏿‍🎓","👩‍🎤","👩🏻‍🎤","👩🏼‍🎤","👩🏽‍🎤","👩🏾‍🎤","👩🏿‍🎤","👨‍🎤","👨🏻‍🎤","👨🏼‍🎤","👨🏽‍🎤","👨🏾‍🎤","👨🏿‍🎤","👩‍🏫","👩🏻‍🏫","👩🏼‍🏫","👩🏽‍🏫","👩🏾‍🏫","👩🏿‍🏫","👨‍🏫","👨🏻‍🏫","👨🏼‍🏫","👨🏽‍🏫","👨🏾‍🏫","👨🏿‍🏫","👩‍🏭","👩🏻‍🏭","👩🏼‍🏭","👩🏽‍🏭","👩🏾‍🏭","👩🏿‍🏭","👨‍🏭","👨🏻‍🏭","👨🏼‍🏭","👨🏽‍🏭","👨🏾‍🏭","👨🏿‍🏭","👩‍💻","👩🏻‍💻","👩🏼‍💻","👩🏽‍💻","👩🏾‍💻","👩🏿‍💻","👨‍💻","👨🏻‍💻","👨🏼‍💻","👨🏽‍💻","👨🏾‍💻","👨🏿‍💻","👩‍💼","👩🏻‍💼","👩🏼‍💼","👩🏽‍💼","👩🏾‍💼","👩🏿‍💼","👨‍💼","👨🏻‍💼","👨🏼‍💼","👨🏽‍💼","👨🏾‍💼","👨🏿‍💼","👩‍🔧","👩🏻‍🔧","👩🏼‍🔧","👩🏽‍🔧","👩🏾‍🔧","👩🏿‍🔧","👨‍🔧","👨🏻‍🔧","👨🏼‍🔧","👨🏽‍🔧","👨🏾‍🔧","👨🏿‍🔧","👩‍🔬","👩🏻‍🔬","👩🏼‍🔬","👩🏽‍🔬","👩🏾‍🔬","👩🏿‍🔬","👨‍🔬","👨🏻‍🔬","👨🏼‍🔬","👨🏽‍🔬","👨🏾‍🔬","👨🏿‍🔬","👩‍🎨","👩🏻‍🎨","👩🏼‍🎨","👩🏽‍🎨","👩🏾‍🎨","👩🏿‍🎨","👨‍🎨","👨🏻‍🎨","👨🏼‍🎨","👨🏽‍🎨","👨🏾‍🎨","👨🏿‍🎨","👩‍🚒","👩🏻‍🚒","👩🏼‍🚒","👩🏽‍🚒","👩🏾‍🚒","👩🏿‍🚒","👨‍🚒","👨🏻‍🚒","👨🏼‍🚒","👨🏽‍🚒","👨🏾‍🚒","👨🏿‍🚒","👩‍✈","👩🏻‍✈","👩🏼‍✈","👩🏽‍✈","👩🏾‍✈","👩🏿‍✈","👨‍✈","👨🏻‍✈","👨🏼‍✈","👨🏽‍✈","👨🏾‍✈","👨🏿‍✈","👩‍🚀","👩🏻‍🚀","👩🏼‍🚀","👩🏽‍🚀","👩🏾‍🚀","👩🏿‍🚀","👨‍🚀","👨🏻‍🚀","👨🏼‍🚀","👨🏽‍🚀","👨🏾‍🚀","👨🏿‍🚀","👩‍⚖","👩🏻‍⚖","👩🏼‍⚖","👩🏽‍⚖","👩🏾‍⚖","👩🏿‍⚖","👨‍⚖","👨🏻‍⚖","👨🏼‍⚖","👨🏽‍⚖","👨🏾‍⚖","👨🏿‍⚖","🤶","🤶🏻","🤶🏼","🤶🏽","🤶🏾","🤶🏿","🎅","🎅🏻","🎅🏼","🎅🏽","🎅🏾","🎅🏿","👸","👸🏻","👸🏼","👸🏽","👸🏾","👸🏿","🤴","🤴🏻","🤴🏼","🤴🏽","🤴🏾","🤴🏿","👰","👰🏻","👰🏼","👰🏽","👰🏾","👰🏿","🤵","🤵🏻","🤵🏼","🤵🏽","🤵🏾","🤵🏿","👼","👼🏻","👼🏼","👼🏽","👼🏾","👼🏿","🤰","🤰🏻","🤰🏼","🤰🏽","🤰🏾","🤰🏿","🙇‍♀","🙇🏻‍♀","🙇🏼‍♀","🙇🏽‍♀","🙇🏾‍♀","🙇🏿‍♀","🙇","🙇🏻","🙇🏼","🙇🏽","🙇🏾","🙇🏿","💁","💁🏻","💁🏼","💁🏽","💁🏾","💁🏿","💁‍♂","💁🏻‍♂","💁🏼‍♂","💁🏽‍♂","💁🏾‍♂","💁🏿‍♂","🙅","🙅🏻","🙅🏼","🙅🏽","🙅🏾","🙅🏿","🙅‍♂","🙅🏻‍♂","🙅🏼‍♂","🙅🏽‍♂","🙅🏾‍♂","🙅🏿‍♂","🙆","🙆🏻","🙆🏼","🙆🏽","🙆🏾","🙆🏿","🙆‍♂","🙆🏻‍♂","🙆🏼‍♂","🙆🏽‍♂","🙆🏾‍♂","🙆🏿‍♂","🙋","🙋🏻","🙋🏼","🙋🏽","🙋🏾","🙋🏿","🙋‍♂","🙋🏻‍♂","🙋🏼‍♂","🙋🏽‍♂","🙋🏾‍♂","🙋🏿‍♂","🤦‍♀","🤦🏻‍♀","🤦🏼‍♀","🤦🏽‍♀","🤦🏾‍♀","🤦🏿‍♀","🤦‍♂","🤦🏻‍♂","🤦🏼‍♂","🤦🏽‍♂","🤦🏾‍♂","🤦🏿‍♂","🤷‍♀","🤷🏻‍♀","🤷🏼‍♀","🤷🏽‍♀","🤷🏾‍♀","🤷🏿‍♀","🤷‍♂","🤷🏻‍♂","🤷🏼‍♂","🤷🏽‍♂","🤷🏾‍♂","🤷🏿‍♂","🙎","🙎🏻","🙎🏼","🙎🏽","🙎🏾","🙎🏿","🙎‍♂","🙎🏻‍♂","🙎🏼‍♂","🙎🏽‍♂","🙎🏾‍♂","🙎🏿‍♂","🙍","🙍🏻","🙍🏼","🙍🏽","🙍🏾","🙍🏿","🙍‍♂","🙍🏻‍♂","🙍🏼‍♂","🙍🏽‍♂","🙍🏾‍♂","🙍🏿‍♂","💇","💇🏻","💇🏼","💇🏽","💇🏾","💇🏿","💇‍♂","💇🏻‍♂","💇🏼‍♂","💇🏽‍♂","💇🏾‍♂","💇🏿‍♂","💆","💆🏻","💆🏼","💆🏽","💆🏾","💆🏿","💆‍♂","💆🏻‍♂","💆🏼‍♂","💆🏽‍♂","💆🏾‍♂","💆🏿‍♂","🕴","🕴🏻","🕴🏼","🕴🏽","🕴🏾","🕴🏿","💃","💃🏻","💃🏼","💃🏽","💃🏾","💃🏿","🕺","🕺🏻","🕺🏼","🕺🏽","🕺🏾","🕺🏿","👯","👯‍♂","🚶‍♀","🚶🏻‍♀","🚶🏼‍♀","🚶🏽‍♀","🚶🏾‍♀","🚶🏿‍♀","🚶","🚶🏻","🚶🏼","🚶🏽","🚶🏾","🚶🏿","🏃‍♀","🏃🏻‍♀","🏃🏼‍♀","🏃🏽‍♀","🏃🏾‍♀","🏃🏿‍♀","🏃","🏃🏻","🏃🏼","🏃🏽","🏃🏾","🏃🏿","👫","👭","👬","💑","👩‍❤‍👩","👨‍❤‍👨","💏","👩‍❤‍💋‍👩","👨‍❤‍💋‍👨","👪","👨‍👩‍👧","👨‍👩‍👧‍👦","👨‍👩‍👦‍👦","👨‍👩‍👧‍👧","👩‍👩‍👦","👩‍👩‍👧","👩‍👩‍👧‍👦","👩‍👩‍👦‍👦","👩‍👩‍👧‍👧","👨‍👨‍👦","👨‍👨‍👧","👨‍👨‍👧‍👦","👨‍👨‍👦‍👦","👨‍👨‍👧‍👧","👩‍👦","👩‍👧","👩‍👧‍👦","👩‍👦‍👦","👩‍👧‍👧","👨‍👦","👨‍👧","👨‍👧‍👦","👨‍👦‍👦","👨‍👧‍👧","👚","👕","👖","👔","👗","👙","👘","👠","👡","👢","👞","👟","👒","🎩","🎓","👑","⛑","🎒","👝","👛","👜","💼","👓","🕶","🌂","☂","❤","💛","💚","💙","💜","🖤","💔","❣","💕","💞","💓","💗","💖","💘","💝" + "😀", "😃", "😄", "😁", "😆", "😅", "😂", "🤣", "☺", "😊", "😇", "🙂", "🙃", "😉", "😌", "😍", "😘", "😗", "😙", "😚", "😋", "😛", "😝", "😜", "🤪", "🤨", "🧐", "🤓", "😎", "🤩", "😏", "😒", "😞", "😔", "😟", "😕", "🙁", "☹", "😣", "😖", "😫", "😩", "😢", "😭", "😤", "😠", "😡", "🤬", "🤯", "😳", "😱", "😨", "😰", "😥", "😓", "🤗", "🤔", "🤭", "🤫", "🤥", "😶", "😐", "😑", "😬", "🙄", "😯", "😦", "😧", "😮", "😲", "😴", "🤤", "😪", "😵", "🤐", "🤢", "🤮", "🤧", "😷", "🤒", "🤕", "🤑", "🤠", "😈", "👿", "👹", "👺", "🤡", "💩", "👻", "💀", "☠", "👽", "👾", "🤖", "🎃", "😺", "😸", "😹", "😻", "😼", "😽", "🙀", "😿", "😾", "🤲", "🤲🏻", "🤲🏼", "🤲🏽", "🤲🏾", "🤲🏿", "👐", "👐🏻", "👐🏼", "👐🏽", "👐🏾", "👐🏿", "🙌", "🙌🏻", "🙌🏼", "🙌🏽", "🙌🏾", "🙌🏿", "👏", "👏🏻", "👏🏼", "👏🏽", "👏🏾", "👏🏿", "🤝", "👍", "👍🏻", "👍🏼", "👍🏽", "👍🏾", "👍🏿", "👎", "👎🏻", "👎🏼", "👎🏽", "👎🏾", "👎🏿", "👊", "👊🏻", "👊🏼", "👊🏽", "👊🏾", "👊🏿", "✊", "✊🏻", "✊🏼", "✊🏽", "✊🏾", "✊🏿", "🤛", "🤛🏻", "🤛🏼", "🤛🏽", "🤛🏾", "🤛🏿", "🤜", "🤜🏻", "🤜🏼", "🤜🏽", "🤜🏾", "🤜🏿", "🤞", "🤞🏻", "🤞🏼", "🤞🏽", "🤞🏾", "🤞🏿", "✌", "✌🏻", "✌🏼", "✌🏽", "✌🏾", "✌🏿", "🤟", "🤟🏻", "🤟🏼", "🤟🏽", "🤟🏾", "🤟🏿", "🤘", "🤘🏻", "🤘🏼", "🤘🏽", "🤘🏾", "🤘🏿", "👌", "👌🏻", "👌🏼", "👌🏽", "👌🏾", "👌🏿", "👈", "👈🏻", "👈🏼", "👈🏽", "👈🏾", "👈🏿", "👉", "👉🏻", "👉🏼", "👉🏽", "👉🏾", "👉🏿", "👆", "👆🏻", "👆🏼", "👆🏽", "👆🏾", "👆🏿", "👇", "👇🏻", "👇🏼", "👇🏽", "👇🏾", "👇🏿", "☝", "☝🏻", "☝🏼", "☝🏽", "☝🏾", "☝🏿", "✋", "✋🏻", "✋🏼", "✋🏽", "✋🏾", "✋🏿", "🤚", "🤚🏻", "🤚🏼", "🤚🏽", "🤚🏾", "🤚🏿", "🖐", "🖐🏻", "🖐🏼", "🖐🏽", "🖐🏾", "🖐🏿", "🖖", "🖖🏻", "🖖🏼", "🖖🏽", "🖖🏾", "🖖🏿", "👋", "👋🏻", "👋🏼", "👋🏽", "👋🏾", "👋🏿", "🤙", "🤙🏻", "🤙🏼", "🤙🏽", "🤙🏾", "🤙🏿", "💪", "💪🏻", "💪🏼", "💪🏽", "💪🏾", "💪🏿", "🖕", "🖕🏻", "🖕🏼", "🖕🏽", "🖕🏾", "🖕🏿", "✍", "✍🏻", "✍🏼", "✍🏽", "✍🏾", "✍🏿", "🙏", "🙏🏻", "🙏🏼", "🙏🏽", "🙏🏾", "🙏🏿", "💍", "💄", "💋", "👄", "👅", "👂", "👂🏻", "👂🏼", "👂🏽", "👂🏾", "👂🏿", "👃", "👃🏻", "👃🏼", "👃🏽", "👃🏾", "👃🏿", "👣", "👁", "👀", "🧠", "🗣", "👤", "👥", "👶", "👶🏻", "👶🏼", "👶🏽", "👶🏾", "👶🏿", "👧", "👧🏻", "👧🏼", "👧🏽", "👧🏾", "👧🏿", "🧒", "🧒🏻", "🧒🏼", "🧒🏽", "🧒🏾", "🧒🏿", "👦", "👦🏻", "👦🏼", "👦🏽", "👦🏾", "👦🏿", "👩", "👩🏻", "👩🏼", "👩🏽", "👩🏾", "👩🏿", "🧑", "🧑🏻", "🧑🏼", "🧑🏽", "🧑🏾", "🧑🏿", "👨", "👨🏻", "👨🏼", "👨🏽", "👨🏾", "👨🏿", "👱‍♀", "👱🏻‍♀", "👱🏼‍♀", "👱🏽‍♀", "👱🏾‍♀", "👱🏿‍♀", "👱‍♂", "👱🏻‍♂", "👱🏼‍♂", "👱🏽‍♂", "👱🏾‍♂", "👱🏿‍♂", "🧔", "🧔🏻", "🧔🏼", "🧔🏽", "🧔🏾", "🧔🏿", "👵", "👵🏻", "👵🏼", "👵🏽", "👵🏾", "👵🏿", "🧓", "🧓🏻", "🧓🏼", "🧓🏽", "🧓🏾", "🧓🏿", "👴", "👴🏻", "👴🏼", "👴🏽", "👴🏾", "👴🏿", "👲", "👲🏻", "👲🏼", "👲🏽", "👲🏾", "👲🏿", "👳‍♀", "👳🏻‍♀", "👳🏼‍♀", "👳🏽‍♀", "👳🏾‍♀", "👳🏿‍♀", "👳‍♂", "👳🏻‍♂", "👳🏼‍♂", "👳🏽‍♂", "👳🏾‍♂", "👳🏿‍♂", "🧕", "🧕🏻", "🧕🏼", "🧕🏽", "🧕🏾", "🧕🏿", "👮‍♀", "👮🏻‍♀", "👮🏼‍♀", "👮🏽‍♀", "👮🏾‍♀", "👮🏿‍♀", "👮‍♂", "👮🏻‍♂", "👮🏼‍♂", "👮🏽‍♂", "👮🏾‍♂", "👮🏿‍♂", "👷‍♀", "👷🏻‍♀", "👷🏼‍♀", "👷🏽‍♀", "👷🏾‍♀", "👷🏿‍♀", "👷‍♂", "👷🏻‍♂", "👷🏼‍♂", "👷🏽‍♂", "👷🏾‍♂", "👷🏿‍♂", "💂‍♀", "💂🏻‍♀", "💂🏼‍♀", "💂🏽‍♀", "💂🏾‍♀", "💂🏿‍♀", "💂‍♂", "💂🏻‍♂", "💂🏼‍♂", "💂🏽‍♂", "💂🏾‍♂", "💂🏿‍♂", "🕵‍♀", "🕵🏻‍♀", "🕵🏼‍♀", "🕵🏽‍♀", "🕵🏾‍♀", "🕵🏿‍♀", "🕵‍♂", "🕵🏻‍♂", "🕵🏼‍♂", "🕵🏽‍♂", "🕵🏾‍♂", "🕵🏿‍♂", "👩‍⚕", "👩🏻‍⚕", "👩🏼‍⚕", "👩🏽‍⚕", "👩🏾‍⚕", "👩🏿‍⚕", "👨‍⚕", "👨🏻‍⚕", "👨🏼‍⚕", "👨🏽‍⚕", "👨🏾‍⚕", "👨🏿‍⚕", "👩‍🌾", "👩🏻‍🌾", "👩🏼‍🌾", "👩🏽‍🌾", "👩🏾‍🌾", "👩🏿‍🌾", "👨‍🌾", "👨🏻‍🌾", "👨🏼‍🌾", "👨🏽‍🌾", "👨🏾‍🌾", "👨🏿‍🌾", "👩‍🍳", "👩🏻‍🍳", "👩🏼‍🍳", "👩🏽‍🍳", "👩🏾‍🍳", "👩🏿‍🍳", "👨‍🍳", "👨🏻‍🍳", "👨🏼‍🍳", "👨🏽‍🍳", "👨🏾‍🍳", "👨🏿‍🍳", "👩‍🎓", "👩🏻‍🎓", "👩🏼‍🎓", "👩🏽‍🎓", "👩🏾‍🎓", "👩🏿‍🎓", "👨‍🎓", "👨🏻‍🎓", "👨🏼‍🎓", "👨🏽‍🎓", "👨🏾‍🎓", "👨🏿‍🎓", "👩‍🎤", "👩🏻‍🎤", "👩🏼‍🎤", "👩🏽‍🎤", "👩🏾‍🎤", "👩🏿‍🎤", "👨‍🎤", "👨🏻‍🎤", "👨🏼‍🎤", "👨🏽‍🎤", "👨🏾‍🎤", "👨🏿‍🎤", "👩‍🏫", "👩🏻‍🏫", "👩🏼‍🏫", "👩🏽‍🏫", "👩🏾‍🏫", "👩🏿‍🏫", "👨‍🏫", "👨🏻‍🏫", "👨🏼‍🏫", "👨🏽‍🏫", "👨🏾‍🏫", "👨🏿‍🏫", "👩‍🏭", "👩🏻‍🏭", "👩🏼‍🏭", "👩🏽‍🏭", "👩🏾‍🏭", "👩🏿‍🏭", "👨‍🏭", "👨🏻‍🏭", "👨🏼‍🏭", "👨🏽‍🏭", "👨🏾‍🏭", "👨🏿‍🏭", "👩‍💻", "👩🏻‍💻", "👩🏼‍💻", "👩🏽‍💻", "👩🏾‍💻", "👩🏿‍💻", "👨‍💻", "👨🏻‍💻", "👨🏼‍💻", "👨🏽‍💻", "👨🏾‍💻", "👨🏿‍💻", "👩‍💼", "👩🏻‍💼", "👩🏼‍💼", "👩🏽‍💼", "👩🏾‍💼", "👩🏿‍💼", "👨‍💼", "👨🏻‍💼", "👨🏼‍💼", "👨🏽‍💼", "👨🏾‍💼", "👨🏿‍💼", "👩‍🔧", "👩🏻‍🔧", "👩🏼‍🔧", "👩🏽‍🔧", "👩🏾‍🔧", "👩🏿‍🔧", "👨‍🔧", "👨🏻‍🔧", "👨🏼‍🔧", "👨🏽‍🔧", "👨🏾‍🔧", "👨🏿‍🔧", "👩‍🔬", "👩🏻‍🔬", "👩🏼‍🔬", "👩🏽‍🔬", "👩🏾‍🔬", "👩🏿‍🔬", "👨‍🔬", "👨🏻‍🔬", "👨🏼‍🔬", "👨🏽‍🔬", "👨🏾‍🔬", "👨🏿‍🔬", "👩‍🎨", "👩🏻‍🎨", "👩🏼‍🎨", "👩🏽‍🎨", "👩🏾‍🎨", "👩🏿‍🎨", "👨‍🎨", "👨🏻‍🎨", "👨🏼‍🎨", "👨🏽‍🎨", "👨🏾‍🎨", "👨🏿‍🎨", "👩‍🚒", "👩🏻‍🚒", "👩🏼‍🚒", "👩🏽‍🚒", "👩🏾‍🚒", "👩🏿‍🚒", "👨‍🚒", "👨🏻‍🚒", "👨🏼‍🚒", "👨🏽‍🚒", "👨🏾‍🚒", "👨🏿‍🚒", "👩‍✈", "👩🏻‍✈", "👩🏼‍✈", "👩🏽‍✈", "👩🏾‍✈", "👩🏿‍✈", "👨‍✈", "👨🏻‍✈", "👨🏼‍✈", "👨🏽‍✈", "👨🏾‍✈", "👨🏿‍✈", "👩‍🚀", "👩🏻‍🚀", "👩🏼‍🚀", "👩🏽‍🚀", "👩🏾‍🚀", "👩🏿‍🚀", "👨‍🚀", "👨🏻‍🚀", "👨🏼‍🚀", "👨🏽‍🚀", "👨🏾‍🚀", "👨🏿‍🚀", "👩‍⚖", "👩🏻‍⚖", "👩🏼‍⚖", "👩🏽‍⚖", "👩🏾‍⚖", "👩🏿‍⚖", "👨‍⚖", "👨🏻‍⚖", "👨🏼‍⚖", "👨🏽‍⚖", "👨🏾‍⚖", "👨🏿‍⚖", "👰", "👰🏻", "👰🏼", "👰🏽", "👰🏾", "👰🏿", "🤵", "🤵🏻", "🤵🏼", "🤵🏽", "🤵🏾", "🤵🏿", "👸", "👸🏻", "👸🏼", "👸🏽", "👸🏾", "👸🏿", "🤴", "🤴🏻", "🤴🏼", "🤴🏽", "🤴🏾", "🤴🏿", "🤶", "🤶🏻", "🤶🏼", "🤶🏽", "🤶🏾", "🤶🏿", "🎅", "🎅🏻", "🎅🏼", "🎅🏽", "🎅🏾", "🎅🏿", "🧙‍♀", "🧙🏻‍♀", "🧙🏼‍♀", "🧙🏽‍♀", "🧙🏾‍♀", "🧙🏿‍♀", "🧙‍♂", "🧙🏻‍♂", "🧙🏼‍♂", "🧙🏽‍♂", "🧙🏾‍♂", "🧙🏿‍♂", "🧝‍♀", "🧝🏻‍♀", "🧝🏼‍♀", "🧝🏽‍♀", "🧝🏾‍♀", "🧝🏿‍♀", "🧝‍♂", "🧝🏻‍♂", "🧝🏼‍♂", "🧝🏽‍♂", "🧝🏾‍♂", "🧝🏿‍♂", "🧛‍♀", "🧛🏻‍♀", "🧛🏼‍♀", "🧛🏽‍♀", "🧛🏾‍♀", "🧛🏿‍♀", "🧛‍♂", "🧛🏻‍♂", "🧛🏼‍♂", "🧛🏽‍♂", "🧛🏾‍♂", "🧛🏿‍♂", "🧟‍♀", "🧟‍♂", "🧞‍♀", "🧞‍♂", "🧜‍♀", "🧜🏻‍♀", "🧜🏼‍♀", "🧜🏽‍♀", "🧜🏾‍♀", "🧜🏿‍♀", "🧜‍♂", "🧜🏻‍♂", "🧜🏼‍♂", "🧜🏽‍♂", "🧜🏾‍♂", "🧜🏿‍♂", "🧚‍♀", "🧚🏻‍♀", "🧚🏼‍♀", "🧚🏽‍♀", "🧚🏾‍♀", "🧚🏿‍♀", "🧚‍♂", "🧚🏻‍♂", "🧚🏼‍♂", "🧚🏽‍♂", "🧚🏾‍♂", "🧚🏿‍♂", "👼", "👼🏻", "👼🏼", "👼🏽", "👼🏾", "👼🏿", "🤰", "🤰🏻", "🤰🏼", "🤰🏽", "🤰🏾", "🤰🏿", "🤱", "🤱🏻", "🤱🏼", "🤱🏽", "🤱🏾", "🤱🏿", "🙇‍♀", "🙇🏻‍♀", "🙇🏼‍♀", "🙇🏽‍♀", "🙇🏾‍♀", "🙇🏿‍♀", "🙇‍♂", "🙇🏻‍♂", "🙇🏼‍♂", "🙇🏽‍♂", "🙇🏾‍♂", "🙇🏿‍♂", "💁‍♀", "💁🏻‍♀", "💁🏼‍♀", "💁🏽‍♀", "💁🏾‍♀", "💁🏿‍♀", "💁‍♂", "💁🏻‍♂", "💁🏼‍♂", "💁🏽‍♂", "💁🏾‍♂", "💁🏿‍♂", "🙅‍♀", "🙅🏻‍♀", "🙅🏼‍♀", "🙅🏽‍♀", "🙅🏾‍♀", "🙅🏿‍♀", "🙅‍♂", "🙅🏻‍♂", "🙅🏼‍♂", "🙅🏽‍♂", "🙅🏾‍♂", "🙅🏿‍♂", "🙆‍♀", "🙆🏻‍♀", "🙆🏼‍♀", "🙆🏽‍♀", "🙆🏾‍♀", "🙆🏿‍♀", "🙆‍♂", "🙆🏻‍♂", "🙆🏼‍♂", "🙆🏽‍♂", "🙆🏾‍♂", "🙆🏿‍♂", "🙋‍♀", "🙋🏻‍♀", "🙋🏼‍♀", "🙋🏽‍♀", "🙋🏾‍♀", "🙋🏿‍♀", "🙋‍♂", "🙋🏻‍♂", "🙋🏼‍♂", "🙋🏽‍♂", "🙋🏾‍♂", "🙋🏿‍♂", "🤦‍♀", "🤦🏻‍♀", "🤦🏼‍♀", "🤦🏽‍♀", "🤦🏾‍♀", "🤦🏿‍♀", "🤦‍♂", "🤦🏻‍♂", "🤦🏼‍♂", "🤦🏽‍♂", "🤦🏾‍♂", "🤦🏿‍♂", "🤷‍♀", "🤷🏻‍♀", "🤷🏼‍♀", "🤷🏽‍♀", "🤷🏾‍♀", "🤷🏿‍♀", "🤷‍♂", "🤷🏻‍♂", "🤷🏼‍♂", "🤷🏽‍♂", "🤷🏾‍♂", "🤷🏿‍♂", "🙎‍♀", "🙎🏻‍♀", "🙎🏼‍♀", "🙎🏽‍♀", "🙎🏾‍♀", "🙎🏿‍♀", "🙎‍♂", "🙎🏻‍♂", "🙎🏼‍♂", "🙎🏽‍♂", "🙎🏾‍♂", "🙎🏿‍♂", "🙍‍♀", "🙍🏻‍♀", "🙍🏼‍♀", "🙍🏽‍♀", "🙍🏾‍♀", "🙍🏿‍♀", "🙍‍♂", "🙍🏻‍♂", "🙍🏼‍♂", "🙍🏽‍♂", "🙍🏾‍♂", "🙍🏿‍♂", "💇‍♀", "💇🏻‍♀", "💇🏼‍♀", "💇🏽‍♀", "💇🏾‍♀", "💇🏿‍♀", "💇‍♂", "💇🏻‍♂", "💇🏼‍♂", "💇🏽‍♂", "💇🏾‍♂", "💇🏿‍♂", "💆‍♀", "💆🏻‍♀", "💆🏼‍♀", "💆🏽‍♀", "💆🏾‍♀", "💆🏿‍♀", "💆‍♂", "💆🏻‍♂", "💆🏼‍♂", "💆🏽‍♂", "💆🏾‍♂", "💆🏿‍♂", "🧖‍♀", "🧖🏻‍♀", "🧖🏼‍♀", "🧖🏽‍♀", "🧖🏾‍♀", "🧖🏿‍♀", "🧖‍♂", "🧖🏻‍♂", "🧖🏼‍♂", "🧖🏽‍♂", "🧖🏾‍♂", "🧖🏿‍♂", "💅", "💅🏻", "💅🏼", "💅🏽", "💅🏾", "💅🏿", "🤳", "🤳🏻", "🤳🏼", "🤳🏽", "🤳🏾", "🤳🏿", "💃", "💃🏻", "💃🏼", "💃🏽", "💃🏾", "💃🏿", "🕺", "🕺🏻", "🕺🏼", "🕺🏽", "🕺🏾", "🕺🏿", "👯‍♀", "👯‍♂", "🕴", "🕴🏻", "🕴🏼", "🕴🏽", "🕴🏾", "🕴🏿", "🚶‍♀", "🚶🏻‍♀", "🚶🏼‍♀", "🚶🏽‍♀", "🚶🏾‍♀", "🚶🏿‍♀", "🚶‍♂", "🚶🏻‍♂", "🚶🏼‍♂", "🚶🏽‍♂", "🚶🏾‍♂", "🚶🏿‍♂", "🏃‍♀", "🏃🏻‍♀", "🏃🏼‍♀", "🏃🏽‍♀", "🏃🏾‍♀", "🏃🏿‍♀", "🏃‍♂", "🏃🏻‍♂", "🏃🏼‍♂", "🏃🏽‍♂", "🏃🏾‍♂", "🏃🏿‍♂", "👫", "👭", "👬", "💑", "👩‍❤‍👩", "👨‍❤‍👨", "💏", "👩‍❤‍💋‍👩", "👨‍❤‍💋‍👨", "👪", "👨‍👩‍👧", "👨‍👩‍👧‍👦", "👨‍👩‍👦‍👦", "👨‍👩‍👧‍👧", "👩‍👩‍👦", "👩‍👩‍👧", "👩‍👩‍👧‍👦", "👩‍👩‍👦‍👦", "👩‍👩‍👧‍👧", "👨‍👨‍👦", "👨‍👨‍👧", "👨‍👨‍👧‍👦", "👨‍👨‍👦‍👦", "👨‍👨‍👧‍👧", "👩‍👦", "👩‍👧", "👩‍👧‍👦", "👩‍👦‍👦", "👩‍👧‍👧", "👨‍👦", "👨‍👧", "👨‍👧‍👦", "👨‍👦‍👦", "👨‍👧‍👧", "🧥", "👚", "👕", "👖", "👔", "👗", "👙", "👘", "👠", "👡", "👢", "👞", "👟", "🧦", "🧤", "🧣", "🎩", "🧢", "👒", "🎓", "⛑", "👑", "👝", "👛", "👜", "💼", "🎒", "👓", "🕶", "🌂", "❤", "🧡", "💛", "💚", "💙", "💜", "🖤", "💔", "❣", "💕", "💞", "💓", "💗", "💖", "💘", "💝" }, new String[]{ - "🐶","🐱","🐭","🐹","🐰","🦊","🐻","🐼","🐨","🐯","🦁","🐮","🐷","🐽","🐸","🐵","🙈","🙉","🙊","🐒","🐔","🐧","🐦","🐤","🐣","🐥","🦆","🦅","🦉","🦇","🐺","🐗","🐴","🦄","🐝","🐛","🦋","🐌","🐚","🐞","🐜","🕷","🕸","🐢","🐍","🦎","🦂","🦀","🦑","🐙","🦐","🐠","🐟","🐡","🐬","🦈","🐳","🐋","🐊","🐆","🐅","🐃","🐂","🐄","🦌","🐪","🐫","🐘","🦏","🦍","🐎","🐖","🐐","🐏","🐑","🐕","🐩","🐈","🐓","🦃","🕊","🐇","🐁","🐀","🐿","🐾","🐉","🐲","🌵","🎄","🌲","🌳","🌴","🌱","🌿","☘","🍀","🎍","🎋","🍃","🍂","🍁","🍄","🌾","💐","🌷","🌹","🥀","🌻","🌼","🌸","🌺","🌎","🌍","🌏","🌕","🌖","🌗","🌘","🌑","🌒","🌓","🌔","🌚","🌝","🌞","🌛","🌜","🌙","💫","⭐","🌟","✨","⚡","🔥","💥","☄","☀","🌤","⛅","🌥","🌦","🌈","☁","🌧","⛈","🌩","🌨","☃","⛄","❄","🌬","💨","🌪","🌫","🌊","💧","💦","☔" + "🐶", "🐱", "🐭", "🐹", "🐰", "🦊", "🐻", "🐼", "🐨", "🐯", "🦁", "🐮", "🐷", "🐽", "🐸", "🐵", "🙈", "🙉", "🙊", "🐒", "🐔", "🐧", "🐦", "🐤", "🐣", "🐥", "🦆", "🦅", "🦉", "🦇", "🐺", "🐗", "🐴", "🦄", "🐝", "🐛", "🦋", "🐌", "🐚", "🐞", "🐜", "🦗", "🕷", "🕸", "🦂", "🐢", "🐍", "🦎", "🦖", "🦕", "🐙", "🦑", "🦐", "🦀", "🐡", "🐠", "🐟", "🐬", "🐳", "🐋", "🦈", "🐊", "🐅", "🐆", "🦓", "🦍", "🐘", "🦏", "🐪", "🐫", "🦒", "🐃", "🐂", "🐄", "🐎", "🐖", "🐏", "🐑", "🐐", "🦌", "🐕", "🐩", "🐈", "🐓", "🦃", "🕊", "🐇", "🐁", "🐀", "🐿", "🦔", "🐾", "🐉", "🐲", "🌵", "🎄", "🌲", "🌳", "🌴", "🌱", "🌿", "☘", "🍀", "🎍", "🎋", "🍃", "🍂", "🍁", "🍄", "🌾", "💐", "🌷", "🌹", "🥀", "🌺", "🌸", "🌼", "🌻", "🌞", "🌝", "🌛", "🌜", "🌚", "🌕", "🌖", "🌗", "🌘", "🌑", "🌒", "🌓", "🌔", "🌙", "🌎", "🌍", "🌏", "💫", "⭐", "🌟", "✨", "⚡", "☄", "💥", "🔥", "🌪", "🌈", "☀", "🌤", "⛅", "🌥", "☁", "🌦", "🌧", "⛈", "🌩", "🌨", "❄", "☃", "⛄", "🌬", "💨", "💧", "💦", "☔", "☂", "🌊", "🌫" }, new String[]{ - "🍏","🍎","🍐","🍊","🍋","🍌","🍉","🍇","🍓","🍈","🍒","🍑","🍍","🥝","🥑","🍅","🍆","🥒","🥕","🌽","🌶","🥔","🍠","🌰","🥜","🍯","🥐","🍞","🥖","🧀","🥚","🍳","🥓","🥞","🍤","🍗","🍖","🍕","🌭","🍔","🍟","🥙","🌮","🌯","🥗","🥘","🍝","🍜","🍲","🍥","🍣","🍱","🍛","🍙","🍚","🍘","🍢","🍡","🍧","🍨","🍦","🍰","🎂","🍮","🍭","🍬","🍫","🍿","🍩","🍪","🥛","🍼","☕","🍵","🍶","🍺","🍻","🥂","🍷","🥃","🍸","🍹","🍾","🥄","🍴","🍽","⚽","🏀","🏈","⚾","🎾","🏐","🏉","🎱","🏓","🏸","🥅","🏒","🏑","🏏","⛳","🏹","🎣","🥊","🥋","⛸","🎿","⛷","🏂","🏋‍♀","🏋🏻‍♀","🏋🏼‍♀","🏋🏽‍♀","🏋🏾‍♀","🏋🏿‍♀","🏋","🏋🏻","🏋🏼","🏋🏽","🏋🏾","🏋🏿","🤺","🤼‍♀","🤼‍♂","🤸‍♀","🤸🏻‍♀","🤸🏼‍♀","🤸🏽‍♀","🤸🏾‍♀","🤸🏿‍♀","🤸‍♂","🤸🏻‍♂","🤸🏼‍♂","🤸🏽‍♂","🤸🏾‍♂","🤸🏿‍♂","⛹‍♀","⛹🏻‍♀","⛹🏼‍♀","⛹🏽‍♀","⛹🏾‍♀","⛹🏿‍♀","⛹","⛹🏻","⛹🏼","⛹🏽","⛹🏾","⛹🏿","🤾‍♀","🤾🏻‍♀","🤾🏼‍♀","🤾🏽‍♀","🤾🏾‍♀","🤾🏿‍♀","🤾‍♂","🤾🏻‍♂","🤾🏼‍♂","🤾🏽‍♂","🤾🏾‍♂","🤾🏿‍♂","🏌‍♀","🏌🏻‍♀","🏌🏼‍♀","🏌🏽‍♀","🏌🏾‍♀","🏌🏿‍♀","🏌","🏌🏻","🏌🏼","🏌🏽","🏌🏾","🏌🏿","🏄‍♀","🏄🏻‍♀","🏄🏼‍♀","🏄🏽‍♀","🏄🏾‍♀","🏄🏿‍♀","🏄","🏄🏻","🏄🏼","🏄🏽","🏄🏾","🏄🏿","🏊‍♀","🏊🏻‍♀","🏊🏼‍♀","🏊🏽‍♀","🏊🏾‍♀","🏊🏿‍♀","🏊","🏊🏻","🏊🏼","🏊🏽","🏊🏾","🏊🏿","🤽‍♀","🤽🏻‍♀","🤽🏼‍♀","🤽🏽‍♀","🤽🏾‍♀","🤽🏿‍♀","🤽‍♂","🤽🏻‍♂","🤽🏼‍♂","🤽🏽‍♂","🤽🏾‍♂","🤽🏿‍♂","🚣‍♀","🚣🏻‍♀","🚣🏼‍♀","🚣🏽‍♀","🚣🏾‍♀","🚣🏿‍♀","🚣","🚣🏻","🚣🏼","🚣🏽","🚣🏾","🚣🏿","🏇","🏇🏻","🏇🏼","🏇🏽","🏇🏾","🏇🏿","🚴‍♀","🚴🏻‍♀","🚴🏼‍♀","🚴🏽‍♀","🚴🏾‍♀","🚴🏿‍♀","🚴","🚴🏻","🚴🏼","🚴🏽","🚴🏾","🚴🏿","🚵‍♀","🚵🏻‍♀","🚵🏼‍♀","🚵🏽‍♀","🚵🏾‍♀","🚵🏿‍♀","🚵","🚵🏻","🚵🏼","🚵🏽","🚵🏾","🚵🏿","🎽","🏅","🎖","🥇","🥈","🥉","🏆","🏵","🎗","🎫","🎟","🎪","🤹‍♀","🤹🏻‍♀","🤹🏼‍♀","🤹🏽‍♀","🤹🏾‍♀","🤹🏿‍♀","🤹‍♂","🤹🏻‍♂","🤹🏼‍♂","🤹🏽‍♂","🤹🏾‍♂","🤹🏿‍♂","🎭","🎨","🎬","🎤","🎧","🎼","🎹","🥁","🎷","🎺","🎸","🎻","🎲","🎯","🎳","🎮","🎰" + "🍏", "🍎", "🍐", "🍊", "🍋", "🍌", "🍉", "🍇", "🍓", "🍈", "🍒", "🍑", "🍍", "🥥", "🥝", "🍅", "🍆", "🥑", "🥦", "🥒", "🌶", "🌽", "🥕", "🥔", "🍠", "🥐", "🍞", "🥖", "🥨", "🧀", "🥚", "🍳", "🥞", "🥓", "🥩", "🍗", "🍖", "🌭", "🍔", "🍟", "🍕", "🥪", "🥙", "🌮", "🌯", "🥗", "🥘", "🥫", "🍝", "🍜", "🍲", "🍛", "🍣", "🍱", "🍤", "🍙", "🍚", "🍘", "🍥", "🥠", "🍢", "🍡", "🍧", "🍨", "🍦", "🥧", "🍰", "🎂", "🍮", "🍭", "🍬", "🍫", "🍿", "🍩", "🥟", "🍪", "🌰", "🥜", "🍯", "🥛", "🍼", "☕", "🍵", "🥤", "🍶", "🍺", "🍻", "🥂", "🍷", "🥃", "🍸", "🍹", "🍾", "🥄", "🍴", "🍽", "🥣", "🥡", "🥢", "⚽", "🏀", "🏈", "⚾", "🎾", "🏐", "🏉", "🎱", "🏓", "🏸", "🥅", "🏒", "🏑", "🏏", "⛳", "🏹", "🎣", "🥊", "🥋", "🎽", "⛸", "🥌", "🛷", "🎿", "⛷", "🏂", "🏋‍♀", "🏋🏻‍♀", "🏋🏼‍♀", "🏋🏽‍♀", "🏋🏾‍♀", "🏋🏿‍♀", "🏋‍♂", "🏋🏻‍♂", "🏋🏼‍♂", "🏋🏽‍♂", "🏋🏾‍♂", "🏋🏿‍♂", "🤼‍♀", "🤼‍♂", "🤸‍♀", "🤸🏻‍♀", "🤸🏼‍♀", "🤸🏽‍♀", "🤸🏾‍♀", "🤸🏿‍♀", "🤸‍♂", "🤸🏻‍♂", "🤸🏼‍♂", "🤸🏽‍♂", "🤸🏾‍♂", "🤸🏿‍♂", "⛹‍♀", "⛹🏻‍♀", "⛹🏼‍♀", "⛹🏽‍♀", "⛹🏾‍♀", "⛹🏿‍♀", "⛹‍♂", "⛹🏻‍♂", "⛹🏼‍♂", "⛹🏽‍♂", "⛹🏾‍♂", "⛹🏿‍♂", "🤺", "🤾‍♀", "🤾🏻‍♀", "🤾🏼‍♀", "🤾🏽‍♀", "🤾🏾‍♀", "🤾🏿‍♀", "🤾‍♂", "🤾🏻‍♂", "🤾🏼‍♂", "🤾🏽‍♂", "🤾🏾‍♂", "🤾🏿‍♂", "🏌‍♀", "🏌🏻‍♀", "🏌🏼‍♀", "🏌🏽‍♀", "🏌🏾‍♀", "🏌🏿‍♀", "🏌‍♂", "🏌🏻‍♂", "🏌🏼‍♂", "🏌🏽‍♂", "🏌🏾‍♂", "🏌🏿‍♂", "🏇", "🏇🏻", "🏇🏼", "🏇🏽", "🏇🏾", "🏇🏿", "🧘‍♀", "🧘🏻‍♀", "🧘🏼‍♀", "🧘🏽‍♀", "🧘🏾‍♀", "🧘🏿‍♀", "🧘‍♂", "🧘🏻‍♂", "🧘🏼‍♂", "🧘🏽‍♂", "🧘🏾‍♂", "🧘🏿‍♂", "🏄‍♀", "🏄🏻‍♀", "🏄🏼‍♀", "🏄🏽‍♀", "🏄🏾‍♀", "🏄🏿‍♀", "🏄‍♂", "🏄🏻‍♂", "🏄🏼‍♂", "🏄🏽‍♂", "🏄🏾‍♂", "🏄🏿‍♂", "🏊‍♀", "🏊🏻‍♀", "🏊🏼‍♀", "🏊🏽‍♀", "🏊🏾‍♀", "🏊🏿‍♀", "🏊‍♂", "🏊🏻‍♂", "🏊🏼‍♂", "🏊🏽‍♂", "🏊🏾‍♂", "🏊🏿‍♂", "🤽‍♀", "🤽🏻‍♀", "🤽🏼‍♀", "🤽🏽‍♀", "🤽🏾‍♀", "🤽🏿‍♀", "🤽‍♂", "🤽🏻‍♂", "🤽🏼‍♂", "🤽🏽‍♂", "🤽🏾‍♂", "🤽🏿‍♂", "🚣‍♀", "🚣🏻‍♀", "🚣🏼‍♀", "🚣🏽‍♀", "🚣🏾‍♀", "🚣🏿‍♀", "🚣‍♂", "🚣🏻‍♂", "🚣🏼‍♂", "🚣🏽‍♂", "🚣🏾‍♂", "🚣🏿‍♂", "🧗‍♀", "🧗🏻‍♀", "🧗🏼‍♀", "🧗🏽‍♀", "🧗🏾‍♀", "🧗🏿‍♀", "🧗‍♂", "🧗🏻‍♂", "🧗🏼‍♂", "🧗🏽‍♂", "🧗🏾‍♂", "🧗🏿‍♂", "🚵‍♀", "🚵🏻‍♀", "🚵🏼‍♀", "🚵🏽‍♀", "🚵🏾‍♀", "🚵🏿‍♀", "🚵‍♂", "🚵🏻‍♂", "🚵🏼‍♂", "🚵🏽‍♂", "🚵🏾‍♂", "🚵🏿‍♂", "🚴‍♀", "🚴🏻‍♀", "🚴🏼‍♀", "🚴🏽‍♀", "🚴🏾‍♀", "🚴🏿‍♀", "🚴‍♂", "🚴🏻‍♂", "🚴🏼‍♂", "🚴🏽‍♂", "🚴🏾‍♂", "🚴🏿‍♂", "🏆", "🥇", "🥈", "🥉", "🏅", "🎖", "🏵", "🎗", "🎫", "🎟", "🎪", "🤹‍♀", "🤹🏻‍♀", "🤹🏼‍♀", "🤹🏽‍♀", "🤹🏾‍♀", "🤹🏿‍♀", "🤹‍♂", "🤹🏻‍♂", "🤹🏼‍♂", "🤹🏽‍♂", "🤹🏾‍♂", "🤹🏿‍♂", "🎭", "🎨", "🎬", "🎤", "🎧", "🎼", "🎹", "🥁", "🎷", "🎺", "🎸", "🎻", "🎲", "🎯", "🎳", "🎮", "🎰" }, new String[]{ - "🚗","🚕","🚙","🚌","🚎","🏎","🚓","🚑","🚒","🚐","🚚","🚛","🚜","🛴","🚲","🛵","🏍","🚨","🚔","🚍","🚘","🚖","🚡","🚠","🚟","🚃","🚋","🚞","🚝","🚄","🚅","🚈","🚂","🚆","🚇","🚊","🚉","🚁","🛩","✈","🛫","🛬","🚀","🛰","💺","🛶","⛵","🛥","🚤","🛳","⛴","🚢","⚓","🚧","⛽","🚏","🚦","🚥","🗺","🗿","🗽","⛲","🗼","🏰","🏯","🏟","🎡","🎢","🎠","⛱","🏖","🏝","⛰","🏔","🗻","🌋","🏜","🏕","⛺","🛤","🛣","🏗","🏭","🏠","🏡","🏘","🏚","🏢","🏬","🏣","🏤","🏥","🏦","🏨","🏪","🏫","🏩","💒","🏛","⛪","🕌","🕍","🕋","⛩","🗾","🎑","🏞","🌅","🌄","🌠","🎇","🎆","🌇","🌆","🏙","🌃","🌌","🌉","🌁","🏳","🏴","🏁","🚩","🏳‍🌈","🇦🇺","🇦🇹","🇦🇿","🇦🇽","🇦🇱","🇩🇿","🇦🇸","🇦🇮","🇦🇴","🇦🇩","🇦🇶","🇦🇬","🇦🇷","🇦🇲","🇦🇼","🇦🇫","🇧🇸","🇧🇩","🇧🇧","🇧🇭","🇧🇾","🇧🇿","🇧🇪","🇧🇯","🇧🇲","🇧🇬","🇧🇴","🇧🇶","🇧🇦","🇧🇼","🇧🇷","🇮🇴","🇧🇳","🇧🇫","🇧🇮","🇧🇹","🇻🇺","🇻🇦","🇬🇧","🇭🇺","🇻🇪","🇻🇬","🇻🇮","🇹🇱","🇻🇳","🇬🇦","🇭🇹","🇬🇾","🇬🇲","🇬🇭","🇬🇵","🇬🇹","🇬🇳","🇬🇼","🇩🇪","🇬🇬","🇬🇮","🇭🇳","🇭🇰","🇬🇩","🇬🇱","🇬🇷","🇬🇪","🇬🇺","🇩🇰","🇯🇪","🇩🇯","🇩🇲","🇩🇴","🇪🇺","🇪🇬","🇿🇲","🇪🇭","🇿🇼","🇮🇱","🇮🇳","🇮🇩","🇯🇴","🇮🇶","🇮🇷","🇮🇪","🇮🇸","🇪🇸","🇮🇹","🇾🇪","🇨🇻","🇰🇿","🇰🇾","🇰🇭","🇨🇲","🇨🇦","🇮🇨","🇶🇦","🇰🇪","🇨🇾","🇰🇬","🇰🇮","🇨🇳","🇰🇵","🇨🇨","🇨🇴","🇰🇲","🇨🇬","🇨🇩","🇽🇰","🇨🇷","🇨🇮","🇨🇺","🇰🇼","🇨🇼","🇱🇦","🇱🇻","🇱🇸","🇱🇷","🇱🇧","🇱🇾","🇱🇹","🇱🇮","🇱🇺","🇲🇺","🇲🇷","🇲🇬","🇾🇹","🇲🇴","🇲🇰","🇲🇼","🇲🇾","🇲🇱","🇲🇻","🇲🇹","🇲🇦","🇲🇶","🇲🇭","🇲🇽","🇫🇲","🇲🇿","🇲🇩","🇲🇨","🇲🇳","🇲🇸","🇲🇲","🇳🇦","🇳🇷","🇳🇵","🇳🇪","🇳🇬","🇳🇱","🇳🇮","🇳🇺","🇳🇿","🇳🇨","🇳🇴","🇮🇲","🇳🇫","🇨🇽","🇸🇭","🇨🇰","🇹🇨","🇦🇪","🇴🇲","🇵🇳","🇵🇰","🇵🇼","🇵🇸","🇵🇦","🇵🇬","🇵🇾","🇵🇪","🇵🇱","🇵🇹","🇵🇷","🇰🇷","🇷🇪","🇷🇺","🇷🇼","🇷🇴","🇸🇻","🇼🇸","🇸🇲","🇸🇹","🇸🇦","🇸🇿","🇲🇵","🇸🇨","🇧🇱","🇵🇲","🇸🇳","🇻🇨","🇰🇳","🇱🇨","🇷🇸","🇸🇬","🇸🇽","🇸🇾","🇸🇰","🇸🇮","🇺🇸","🇸🇧","🇸🇴","🇸🇩","🇸🇷","🇸🇱","🇹🇯","🇹🇭","🇹🇼","🇹🇿","🇹🇬","🇹🇰","🇹🇴","🇹🇹","🇹🇻","🇹🇳","🇹🇲","🇹🇷","🇺🇬","🇺🇿","🇺🇦","🇼🇫","🇺🇾","🇫🇴","🇫🇯","🇵🇭","🇫🇮","🇫🇰","🇫🇷","🇬🇫","🇵🇫","🇹🇫","🇭🇷","🇨🇫","🇹🇩","🇲🇪","🇨🇿","🇨🇱","🇨🇭","🇸🇪","🇱🇰","🇪🇨","🇬🇶","🇪🇷","🇪🇪","🇪🇹","🇿🇦","🇬🇸","🇸🇸","🇯🇲","🇯🇵","🎌" + "🚗", "🚕", "🚙", "🚌", "🚎", "🏎", "🚓", "🚑", "🚒", "🚐", "🚚", "🚛", "🚜", "🛴", "🚲", "🛵", "🏍", "🚨", "🚔", "🚍", "🚘", "🚖", "🚡", "🚠", "🚟", "🚃", "🚋", "🚞", "🚝", "🚄", "🚅", "🚈", "🚂", "🚆", "🚇", "🚊", "🚉", "✈", "🛫", "🛬", "🛩", "💺", "🛰", "🚀", "🛸", "🚁", "🛶", "⛵", "🚤", "🛥", "🛳", "⛴", "🚢", "⚓", "⛽", "🚧", "🚦", "🚥", "🚏", "🗺", "🗿", "🗽", "🗼", "🏰", "🏯", "🏟", "🎡", "🎢", "🎠", "⛲", "⛱", "🏖", "🏝", "🏜", "🌋", "⛰", "🏔", "🗻", "🏕", "⛺", "🏠", "🏡", "🏘", "🏚", "🏗", "🏭", "🏢", "🏬", "🏣", "🏤", "🏥", "🏦", "🏨", "🏪", "🏫", "🏩", "💒", "🏛", "⛪", "🕌", "🕍", "🕋", "⛩", "🛤", "🛣", "🗾", "🎑", "🏞", "🌅", "🌄", "🌠", "🎇", "🎆", "🌇", "🌆", "🏙", "🌃", "🌌", "🌉", "🌁", "🏳", "🏴", "🏁", "🚩", "🏳‍🌈", "🇦🇫", "🇦🇽", "🇦🇱", "🇩🇿", "🇦🇸", "🇦🇩", "🇦🇴", "🇦🇮", "🇦🇶", "🇦🇬", "🇦🇷", "🇦🇲", "🇦🇼", "🇦🇺", "🇦🇹", "🇦🇿", "🇧🇸", "🇧🇭", "🇧🇩", "🇧🇧", "🇧🇾", "🇧🇪", "🇧🇿", "🇧🇯", "🇧🇲", "🇧🇹", "🇧🇴", "🇧🇦", "🇧🇼", "🇧🇷", "🇮🇴", "🇻🇬", "🇧🇳", "🇧🇬", "🇧🇫", "🇧🇮", "🇰🇭", "🇨🇲", "🇨🇦", "🇮🇨", "🇨🇻", "🇧🇶", "🇰🇾", "🇨🇫", "🇹🇩", "🇨🇱", "🇨🇳", "🇨🇽", "🇨🇨", "🇨🇴", "🇰🇲", "🇨🇬", "🇨🇩", "🇨🇰", "🇨🇷", "🇨🇮", "🇭🇷", "🇨🇺", "🇨🇼", "🇨🇾", "🇨🇿", "🇩🇰", "🇩🇯", "🇩🇲", "🇩🇴", "🇪🇨", "🇪🇬", "🇸🇻", "🇬🇶", "🇪🇷", "🇪🇪", "🇪🇹", "🇪🇺", "🇫🇰", "🇫🇴", "🇫🇯", "🇫🇮", "🇫🇷", "🇬🇫", "🇵🇫", "🇹🇫", "🇬🇦", "🇬🇲", "🇬🇪", "🇩🇪", "🇬🇭", "🇬🇮", "🇬🇷", "🇬🇱", "🇬🇩", "🇬🇵", "🇬🇺", "🇬🇹", "🇬🇬", "🇬🇳", "🇬🇼", "🇬🇾", "🇭🇹", "🇭🇳", "🇭🇰", "🇭🇺", "🇮🇸", "🇮🇳", "🇮🇩", "🇮🇷", "🇮🇶", "🇮🇪", "🇮🇲", "🇮🇱", "🇮🇹", "🇯🇲", "🇯🇵", "🎌", "🇯🇪", "🇯🇴", "🇰🇿", "🇰🇪", "🇰🇮", "🇽🇰", "🇰🇼", "🇰🇬", "🇱🇦", "🇱🇻", "🇱🇧", "🇱🇸", "🇱🇷", "🇱🇾", "🇱🇮", "🇱🇹", "🇱🇺", "🇲🇴", "🇲🇰", "🇲🇬", "🇲🇼", "🇲🇾", "🇲🇻", "🇲🇱", "🇲🇹", "🇲🇭", "🇲🇶", "🇲🇷", "🇲🇺", "🇾🇹", "🇲🇽", "🇫🇲", "🇲🇩", "🇲🇨", "🇲🇳", "🇲🇪", "🇲🇸", "🇲🇦", "🇲🇿", "🇲🇲", "🇳🇦", "🇳🇷", "🇳🇵", "🇳🇱", "🇳🇨", "🇳🇿", "🇳🇮", "🇳🇪", "🇳🇬", "🇳🇺", "🇳🇫", "🇰🇵", "🇲🇵", "🇳🇴", "🇴🇲", "🇵🇰", "🇵🇼", "🇵🇸", "🇵🇦", "🇵🇬", "🇵🇾", "🇵🇪", "🇵🇭", "🇵🇳", "🇵🇱", "🇵🇹", "🇵🇷", "🇶🇦", "🇷🇪", "🇷🇴", "🇷🇺", "🇷🇼", "🇼🇸", "🇸🇲", "🇸🇹", "🇸🇦", "🇸🇳", "🇷🇸", "🇸🇨", "🇸🇱", "🇸🇬", "🇸🇽", "🇸🇰", "🇸🇮", "🇬🇸", "🇸🇧", "🇸🇴", "🇿🇦", "🇰🇷", "🇸🇸", "🇪🇸", "🇱🇰", "🇧🇱", "🇸🇭", "🇰🇳", "🇱🇨", "🇵🇲", "🇻🇨", "🇸🇩", "🇸🇷", "🇸🇿", "🇸🇪", "🇨🇭", "🇸🇾", "🇹🇼", "🇹🇯", "🇹🇿", "🇹🇭", "🇹🇱", "🇹🇬", "🇹🇰", "🇹🇴", "🇹🇹", "🇹🇳", "🇹🇷", "🇹🇲", "🇹🇨", "🇹🇻", "🇻🇮", "🇺🇬", "🇺🇦", "🇦🇪", "🇬🇧", "🏴󠁧󠁢󠁥󠁮󠁧󠁿", "🏴󠁧󠁢󠁳󠁣󠁴󠁿", "🏴󠁧󠁢󠁷󠁬󠁳󠁿", "🇺🇸", "🇺🇾", "🇺🇿", "🇻🇺", "🇻🇦", "🇻🇪", "🇻🇳", "🇼🇫", "🇪🇭", "🇾🇪", "🇿🇲", "🇿🇼" }, new String[]{ - "💟","☮","✝","☪","🕉","☸","✡","🔯","🕎","☯","☦","🛐","⛎","♈","♉","♊","♋","♌","♍","♎","♏","♐","♑","♒","♓","🆔","⚛","🉑","☢","☣","📴","📳","🈶","🈚","🈸","🈺","🈷","✴","🆚","💮","🉐","㊙","㊗","🈴","🈵","🈹","🈲","🅰","🅱","🆎","🆑","🅾","🆘","❌","⭕","🛑","⛔","📛","🚫","💯","💢","♨","🚷","🚯","🚳","🚱","🔞","📵","🚭","❗","❕","❓","❔","‼","⁉","🔅","🔆","〽","⚠","🚸","🔱","⚜","🔰","♻","✅","🈯","💹","❇","✳","❎","🌐","💠","Ⓜ","🌀","💤","🏧","🚾","♿","🅿","🈳","🈂","🛂","🛃","🛄","🛅","🚹","🚺","🚼","🚻","🚮","🎦","📶","🈁","🔣","ℹ","🔤","🔡","🔠","🆖","🆗","🆙","🆒","🆕","🆓","0⃣","1⃣","2⃣","3⃣","4⃣","5⃣","6⃣","7⃣","8⃣","9⃣","🔟","🔢","#⃣","*⃣","▶","⏸","⏯","⏹","⏺","⏭","⏮","⏩","⏪","⏫","⏬","◀","🔼","🔽","➡","⬅","⬆","⬇","↗","↘","↙","↖","↕","↔","↪","↩","⤴","⤵","🔀","🔁","🔂","🔄","🔃","🎵","🎶","➕","➖","➗","✖","💲","💱","™","©","®","〰","➰","➿","🔚","🔙","🔛","🔝","🔜","✔","☑","🔘","⚪","⚫","🔴","🔵","🔺","🔻","🔸","🔹","🔶","🔷","🔳","🔲","▪","▫","◾","◽","◼","◻","⬛","⬜","🔈","🔇","🔉","🔊","🔔","🔕","📣","📢","👁‍🗨","💬","💭","🗯","♠","♣","♥","♦","🃏","🎴","🀄","🕐","🕑","🕒","🕓","🕔","🕕","🕖","🕗","🕘","🕙","🕚","🕛","🕜","🕝","🕞","🕟","🕠","🕡","🕢","🕣","🕤","🕥","🕦","🕧","⌚","📱","📲","💻","⌨","🖥","🖨","🖱","🖲","🕹","🗜","💽","💾","💿","📀","📼","📷","📸","📹","🎥","📽","🎞","📞","☎","📟","📠","📺","📻","🎙","🎚","🎛","⏱","⏲","⏰","🕰","⌛","⏳","📡","🔋","🔌","💡","🔦","🕯","🗑","🛢","💸","💵","💴","💶","💷","💰","💳","💎","⚖","🔧","🔨","⚒","🛠","⛏","🔩","⚙","⛓","🔫","💣","🔪","🗡","⚔","🛡","🚬","⚰","⚱","🏺","🔮","📿","💈","⚗","🔭","🔬","🕳","💊","💉","🌡","🚽","🚰","🚿","🛁","🛀","🛀🏻","🛀🏼","🛀🏽","🛀🏾","🛀🏿","🛎","🔑","🗝","🚪","🛋","🛏","🛌","🖼","🛍","🛒","🎁","🎈","🎏","🎀","🎊","🎉","🎎","🏮","🎐","✉","📩","📨","📧","💌","📥","📤","📦","🏷","📪","📫","📬","📭","📮","📯","📜","📃","📄","📑","📊","📈","📉","🗒","🗓","📆","📅","📇","🗃","🗳","🗄","📋","📁","📂","🗂","🗞","📰","📓","📔","📒","📕","📗","📘","📙","📚","📖","🔖","🔗","📎","🖇","📐","📏","📌","📍","✂","🖊","🖋","✒","🖌","🖍","📝","✏","🔍","🔎","🔏","🔐","🔒","🔓" + "💟", "☮", "✝", "☪", "🕉", "☸", "✡", "🔯", "🕎", "☯", "☦", "🛐", "⛎", "♈", "♉", "♊", "♋", "♌", "♍", "♎", "♏", "♐", "♑", "♒", "♓", "🆔", "⚛", "🉑", "☢", "☣", "📴", "📳", "🈶", "🈚", "🈸", "🈺", "🈷", "✴", "🆚", "💮", "🉐", "㊙", "㊗", "🈴", "🈵", "🈹", "🈲", "🅰", "🅱", "🆎", "🆑", "🅾", "🆘", "❌", "⭕", "🛑", "⛔", "📛", "🚫", "💯", "💢", "♨", "🚷", "🚯", "🚳", "🚱", "🔞", "📵", "🚭", "❗", "❕", "❓", "❔", "‼", "⁉", "🔅", "🔆", "〽", "⚠", "🚸", "🔱", "⚜", "🔰", "♻", "✅", "🈯", "💹", "❇", "✳", "❎", "🌐", "💠", "Ⓜ", "🌀", "💤", "🏧", "🚾", "♿", "🅿", "🈳", "🈂", "🛂", "🛃", "🛄", "🛅", "🚹", "🚺", "🚼", "🚻", "🚮", "🎦", "📶", "🈁", "🔣", "ℹ", "🔤", "🔡", "🔠", "🆖", "🆗", "🆙", "🆒", "🆕", "🆓", "0⃣", "1⃣", "2⃣", "3⃣", "4⃣", "5⃣", "6⃣", "7⃣", "8⃣", "9⃣", "🔟", "🔢", "#⃣", "*⃣", "⏏", "▶", "⏸", "⏯", "⏹", "⏺", "⏭", "⏮", "⏩", "⏪", "⏫", "⏬", "◀", "🔼", "🔽", "➡", "⬅", "⬆", "⬇", "↗", "↘", "↙", "↖", "↕", "↔", "↪", "↩", "⤴", "⤵", "🔀", "🔁", "🔂", "🔄", "🔃", "🎵", "🎶", "➕", "➖", "➗", "✖", "💲", "💱", "™", "©", "®", "〰", "➰", "➿", "🔚", "🔙", "🔛", "🔝", "🔜", "✔", "☑", "🔘", "⚪", "⚫", "🔴", "🔵", "🔺", "🔻", "🔸", "🔹", "🔶", "🔷", "🔳", "🔲", "▪", "▫", "◾", "◽", "◼", "◻", "⬛", "⬜", "🔈", "🔇", "🔉", "🔊", "🔔", "🔕", "📣", "📢", "👁‍🗨", "💬", "💭", "🗯", "♠", "♣", "♥", "♦", "🃏", "🎴", "🀄", "🕐", "🕑", "🕒", "🕓", "🕔", "🕕", "🕖", "🕗", "🕘", "🕙", "🕚", "🕛", "🕜", "🕝", "🕞", "🕟", "🕠", "🕡", "🕢", "🕣", "🕤", "🕥", "🕦", "🕧", "⌚", "📱", "📲", "💻", "⌨", "🖥", "🖨", "🖱", "🖲", "🕹", "🗜", "💽", "💾", "💿", "📀", "📼", "📷", "📸", "📹", "🎥", "📽", "🎞", "📞", "☎", "📟", "📠", "📺", "📻", "🎙", "🎚", "🎛", "⏱", "⏲", "⏰", "🕰", "⌛", "⏳", "📡", "🔋", "🔌", "💡", "🔦", "🕯", "🗑", "🛢", "💸", "💵", "💴", "💶", "💷", "💰", "💳", "💎", "⚖", "🔧", "🔨", "⚒", "🛠", "⛏", "🔩", "⚙", "⛓", "🔫", "💣", "🔪", "🗡", "⚔", "🛡", "🚬", "⚰", "⚱", "🏺", "🔮", "📿", "💈", "⚗", "🔭", "🔬", "🕳", "💊", "💉", "🌡", "🚽", "🚰", "🚿", "🛁", "🛀", "🛀🏻", "🛀🏼", "🛀🏽", "🛀🏾", "🛀🏿", "🛎", "🔑", "🗝", "🚪", "🛋", "🛏", "🛌", "🖼", "🛍", "🛒", "🎁", "🎈", "🎏", "🎀", "🎊", "🎉", "🎎", "🏮", "🎐", "✉", "📩", "📨", "📧", "💌", "📥", "📤", "📦", "🏷", "📪", "📫", "📬", "📭", "📮", "📯", "📜", "📃", "📄", "📑", "📊", "📈", "📉", "🗒", "🗓", "📆", "📅", "📇", "🗃", "🗳", "🗄", "📋", "📁", "📂", "🗂", "🗞", "📰", "📓", "📔", "📒", "📕", "📗", "📘", "📙", "📚", "📖", "🔖", "🔗", "📎", "🖇", "📐", "📏", "📌", "📍", "✂", "🖊", "🖋", "✒", "🖌", "🖍", "📝", "✏", "🔍", "🔎", "🔏", "🔐", "🔒", "🔓" } }; public static final HashMap emojiToFE0FMap = new HashMap<>(emojiToFE0F.length); public static final HashMap dataCharsMap = new HashMap<>(dataChars.length); public static final HashMap emojiColoredMap = new HashMap<>(emojiColored.length); + public static final HashMap emojiAliasMap = new HashMap<>(aliasNew.length); static { for (int a = 0; a < emojiToFE0F.length; a++) { @@ -265,6 +295,9 @@ public class EmojiData { for (int a = 0; a < emojiColored.length; a++) { emojiColoredMap.put(emojiColored[a], true); } + for (int a = 0; a < aliasNew.length; a++) { + emojiAliasMap.put(aliasOld[a], aliasNew[a]); + } dataColored[1] = data[1]; dataColored[3] = data[3]; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/EmojiSuggestion.java b/TMessagesProj/src/main/java/org/telegram/messenger/EmojiSuggestion.java new file mode 100644 index 000000000..5e46bca02 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/EmojiSuggestion.java @@ -0,0 +1,20 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.messenger; + +public class EmojiSuggestion { + + public EmojiSuggestion(String arg1, String arg2) { + emoji = arg1; + label = arg2; + } + + public String emoji; + public String label; +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/GcmPushListenerService.java b/TMessagesProj/src/main/java/org/telegram/messenger/GcmPushListenerService.java index c657ea4fe..466aca38b 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/GcmPushListenerService.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/GcmPushListenerService.java @@ -20,7 +20,7 @@ import org.json.JSONObject; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLRPC; -import static android.support.v4.net.ConnectivityManagerCompat.RESTRICT_BACKGROUND_STATUS_ENABLED; +import android.net.ConnectivityManager; public class GcmPushListenerService extends GcmListenerService { @@ -93,7 +93,10 @@ public class GcmPushListenerService extends GcmListenerService { if (time == -1 || UserConfig.lastAppPauseTime < time) { ConnectivityManager connectivityManager = (ConnectivityManager) ApplicationLoader.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo netInfo = connectivityManager.getActiveNetworkInfo(); - if (connectivityManager.getRestrictBackgroundStatus() == RESTRICT_BACKGROUND_STATUS_ENABLED && netInfo.getType() == ConnectivityManager.TYPE_MOBILE) { + if (BuildVars.DEBUG_VERSION) { + FileLog.d("try show notification in background with time " + time + " with nework info " + netInfo + " and status " + connectivityManager.getRestrictBackgroundStatus()); + } + if (connectivityManager.getRestrictBackgroundStatus() == ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED && netInfo.getType() == ConnectivityManager.TYPE_MOBILE) { NotificationsController.getInstance().showSingleBackgroundNotification(); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ImageLoader.java b/TMessagesProj/src/main/java/org/telegram/messenger/ImageLoader.java index 74353d45b..4d1070f90 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ImageLoader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ImageLoader.java @@ -24,6 +24,7 @@ import android.os.AsyncTask; import android.os.Build; import android.os.Environment; import android.provider.MediaStore; +import android.text.TextUtils; import org.telegram.messenger.secretmedia.EncryptedFileInputStream; import org.telegram.tgnet.ConnectionsManager; @@ -69,6 +70,7 @@ public class ImageLoader { private DispatchQueue imageLoadQueue = new DispatchQueue("imageLoadQueue"); private ConcurrentHashMap fileProgresses = new ConcurrentHashMap<>(); private HashMap thumbGenerateTasks = new HashMap<>(); + private HashMap forceLoadingImages = new HashMap<>(); private static byte[] bytes; private static byte[] bytesThumb; private static byte[] header = new byte[12]; @@ -278,7 +280,7 @@ public class ImageLoader { private int imageSize; private long lastProgressTime; private boolean canRetry = true; - private URLConnection httpConnection = null; + private HttpURLConnection httpConnection = null; public HttpImageTask(CacheImage cacheImage, int size) { this.cacheImage = cacheImage; @@ -311,14 +313,12 @@ public class ImageLoader { if (!isCancelled()) { try { URL downloadUrl = new URL(cacheImage.httpUrl); - httpConnection = downloadUrl.openConnection(); + httpConnection = (HttpURLConnection) downloadUrl.openConnection(); httpConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 10_0 like Mac OS X) AppleWebKit/602.1.38 (KHTML, like Gecko) Version/10.0 Mobile/14A5297c Safari/602.1"); //httpConnection.addRequestProperty("Referer", "google.com"); httpConnection.setConnectTimeout(5000); httpConnection.setReadTimeout(5000); - if (httpConnection instanceof HttpURLConnection) { - ((HttpURLConnection) httpConnection).setInstanceFollowRedirects(true); - } + httpConnection.setInstanceFollowRedirects(true); if (!isCancelled()) { httpConnection.connect(); httpConnectionStream = httpConnection.getInputStream(); @@ -345,7 +345,7 @@ public class ImageLoader { if (!isCancelled()) { try { if (httpConnection != null && httpConnection instanceof HttpURLConnection) { - int code = ((HttpURLConnection) httpConnection).getResponseCode(); + int code = httpConnection.getResponseCode(); if (code != HttpURLConnection.HTTP_OK && code != HttpURLConnection.HTTP_ACCEPTED && code != HttpURLConnection.HTTP_NOT_MODIFIED) { canRetry = false; } @@ -414,6 +414,13 @@ public class ImageLoader { } catch (Throwable e) { FileLog.e(e); } + try { + if (httpConnection != null) { + httpConnection.disconnect(); + } + } catch (Throwable ignore) { + + } try { if (httpConnectionStream != null) { @@ -641,7 +648,7 @@ public class ImageLoader { try { randomAccessFile = new RandomAccessFile(cacheFileFinal, "r"); byte[] bytes; - if (cacheImage.thumb) { + if (cacheImage.selfThumb) { bytes = headerThumb; } else { bytes = header; @@ -666,7 +673,7 @@ public class ImageLoader { } } - if (cacheImage.thumb) { + if (cacheImage.selfThumb) { int blurType = 0; if (cacheImage.filter != null) { if (cacheImage.filter.contains("b2")) { @@ -1003,11 +1010,10 @@ public class ImageLoader { protected String ext; protected TLObject location; protected boolean animatedFile; + protected boolean selfThumb; protected File finalFilePath; protected File tempFilePath; - protected boolean thumb; - protected File encryptionKeyPath; protected String httpUrl; @@ -1017,24 +1023,43 @@ public class ImageLoader { protected ArrayList imageReceiverArray = new ArrayList<>(); protected ArrayList keys = new ArrayList<>(); protected ArrayList filters = new ArrayList<>(); + protected ArrayList thumbs = new ArrayList<>(); - public void addImageReceiver(ImageReceiver imageReceiver, String key, String filter) { + public void addImageReceiver(ImageReceiver imageReceiver, String key, String filter, boolean thumb) { if (imageReceiverArray.contains(imageReceiver)) { return; } imageReceiverArray.add(imageReceiver); keys.add(key); filters.add(filter); + thumbs.add(thumb); imageLoadingByTag.put(imageReceiver.getTag(thumb), this); } + public void replaceImageReceiver(ImageReceiver imageReceiver, String key, String filter, boolean thumb) { + int index = imageReceiverArray.indexOf(imageReceiver); + if (index == -1) { + return; + } + if (thumbs.get(index) != thumb) { + index = imageReceiverArray.subList(index + 1, imageReceiverArray.size()).indexOf(imageReceiver); + if (index == -1) { + return; + } + } + keys.set(index, key); + filters.set(index, filter); + } + public void removeImageReceiver(ImageReceiver imageReceiver) { + Boolean thumb = selfThumb; for (int a = 0; a < imageReceiverArray.size(); a++) { ImageReceiver obj = imageReceiverArray.get(a); if (obj == null || obj == imageReceiver) { imageReceiverArray.remove(a); keys.remove(a); filters.remove(a); + thumb = thumbs.remove(a); if (obj != null) { imageLoadingByTag.remove(obj.getTag(thumb)); } @@ -1047,16 +1072,18 @@ public class ImageLoader { } imageReceiverArray.clear(); if (location != null) { - if (location instanceof TLRPC.FileLocation) { - FileLoader.getInstance().cancelLoadFile((TLRPC.FileLocation) location, ext); - } else if (location instanceof TLRPC.Document) { - FileLoader.getInstance().cancelLoadFile((TLRPC.Document) location); - } else if (location instanceof TLRPC.TL_webDocument) { - FileLoader.getInstance().cancelLoadFile((TLRPC.TL_webDocument) location); + if (!forceLoadingImages.containsKey(key)) { + if (location instanceof TLRPC.FileLocation) { + FileLoader.getInstance().cancelLoadFile((TLRPC.FileLocation) location, ext); + } else if (location instanceof TLRPC.Document) { + FileLoader.getInstance().cancelLoadFile((TLRPC.Document) location); + } else if (location instanceof TLRPC.TL_webDocument) { + FileLoader.getInstance().cancelLoadFile((TLRPC.TL_webDocument) location); + } } } if (cacheTask != null) { - if (thumb) { + if (selfThumb) { cacheThumbOutQueue.cancelRunnable(cacheTask); } else { cacheOutQueue.cancelRunnable(cacheTask); @@ -1089,7 +1116,7 @@ public class ImageLoader { AnimatedFileDrawable fileDrawable = (AnimatedFileDrawable) image; for (int a = 0; a < finalImageReceiverArray.size(); a++) { ImageReceiver imgView = finalImageReceiverArray.get(a); - if (imgView.setImageBitmapByKey(a == 0 ? fileDrawable : fileDrawable.makeCopy(), key, thumb, false)) { + if (imgView.setImageBitmapByKey(a == 0 ? fileDrawable : fileDrawable.makeCopy(), key, selfThumb, false)) { imageSet = true; } } @@ -1099,7 +1126,7 @@ public class ImageLoader { } else { for (int a = 0; a < finalImageReceiverArray.size(); a++) { ImageReceiver imgView = finalImageReceiverArray.get(a); - imgView.setImageBitmapByKey(image, key, thumb, false); + imgView.setImageBitmapByKey(image, key, selfThumb, false); } } } @@ -1107,7 +1134,7 @@ public class ImageLoader { } for (int a = 0; a < imageReceiverArray.size(); a++) { ImageReceiver imageReceiver = imageReceiverArray.get(a); - imageLoadingByTag.remove(imageReceiver.getTag(thumb)); + imageLoadingByTag.remove(imageReceiver.getTag(selfThumb)); } imageReceiverArray.clear(); if (url != null) { @@ -1642,6 +1669,22 @@ public class ImageLoader { } } + public void cancelForceLoadingForImageReceiver(final ImageReceiver imageReceiver) { + if (imageReceiver == null) { + return; + } + final String key = imageReceiver.getKey(); + if (key == null) { + return; + } + imageLoadQueue.postRunnable(new Runnable() { + @Override + public void run() { + forceLoadingImages.remove(key); + } + }); + } + private void createLoadOperationForImageReceiver(final ImageReceiver imageReceiver, final String key, final String url, final String ext, final TLObject imageLocation, final String httpLocation, final String filter, final int size, final int cacheType, final int thumb) { if (imageReceiver == null || url == null || key == null) { return; @@ -1668,7 +1711,12 @@ public class ImageLoader { CacheImage alreadyLoadingCache = imageLoadingByKeys.get(key); CacheImage alreadyLoadingImage = imageLoadingByTag.get(finalTag); if (alreadyLoadingImage != null) { - if (alreadyLoadingImage == alreadyLoadingUrl || alreadyLoadingImage == alreadyLoadingCache) { + if (alreadyLoadingImage == alreadyLoadingCache) { + added = true; + } else if (alreadyLoadingImage == alreadyLoadingUrl) { + if (alreadyLoadingCache == null) { + alreadyLoadingImage.replaceImageReceiver(imageReceiver, key, filter, thumb != 0); + } added = true; } else { alreadyLoadingImage.removeImageReceiver(imageReceiver); @@ -1676,11 +1724,11 @@ public class ImageLoader { } if (!added && alreadyLoadingCache != null) { - alreadyLoadingCache.addImageReceiver(imageReceiver, key, filter); + alreadyLoadingCache.addImageReceiver(imageReceiver, key, filter, thumb != 0); added = true; } if (!added && alreadyLoadingUrl != null) { - alreadyLoadingUrl.addImageReceiver(imageReceiver, key, filter); + alreadyLoadingUrl.addImageReceiver(imageReceiver, key, filter, thumb != 0); added = true; } } @@ -1750,14 +1798,18 @@ public class ImageLoader { if (thumb != 2) { boolean isEncrypted = imageLocation instanceof TLRPC.TL_documentEncrypted || imageLocation instanceof TLRPC.TL_fileEncryptedLocation; CacheImage img = new CacheImage(); - if (httpLocation != null && !httpLocation.startsWith("vthumb") && !httpLocation.startsWith("thumb") && (httpLocation.endsWith("mp4") || httpLocation.endsWith("gif")) || - imageLocation instanceof TLRPC.TL_webDocument && ((TLRPC.TL_webDocument) imageLocation).mime_type.equals("image/gif") || + if (httpLocation != null && !httpLocation.startsWith("vthumb") && !httpLocation.startsWith("thumb")) { + String trueExt = getHttpUrlExtension(httpLocation, "jpg"); + if (trueExt.equals("mp4") || trueExt.equals("gif")) { + img.animatedFile = true; + } + } else if (imageLocation instanceof TLRPC.TL_webDocument && ((TLRPC.TL_webDocument) imageLocation).mime_type.equals("image/gif") || imageLocation instanceof TLRPC.Document && (MessageObject.isGifDocument((TLRPC.Document) imageLocation) || MessageObject.isRoundVideoDocument((TLRPC.Document) imageLocation))) { img.animatedFile = true; } if (cacheFile == null) { - if (cacheType != 0 || size == 0 || httpLocation != null || isEncrypted) { + if (cacheType != 0 || size <= 0 || httpLocation != null || isEncrypted) { cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), url); if (cacheFile.exists()) { cacheFileExists = true; @@ -1777,7 +1829,7 @@ public class ImageLoader { } } - img.thumb = thumb != 0; + img.selfThumb = thumb != 0; img.key = key; img.filter = filter; img.httpUrl = httpLocation; @@ -1785,7 +1837,7 @@ public class ImageLoader { if (cacheType == 2) { img.encryptionKeyPath = new File(FileLoader.getInternalCacheDir(), url + ".enc.key"); } - img.addImageReceiver(imageReceiver, key, filter); + img.addImageReceiver(imageReceiver, key, filter, thumb != 0); if (onlyCache || cacheFileExists || cacheFile.exists()) { img.finalFilePath = cacheFile; img.cacheTask = new CacheOutTask(img); @@ -1803,7 +1855,7 @@ public class ImageLoader { if (imageLocation instanceof TLRPC.FileLocation) { TLRPC.FileLocation location = (TLRPC.FileLocation) imageLocation; int localCacheType = cacheType; - if (localCacheType == 0 && (size == 0 || location.key != null)) { + if (localCacheType == 0 && (size <= 0 || location.key != null)) { localCacheType = 1; } FileLoader.getInstance().loadFile(location, ext, size, localCacheType); @@ -1812,6 +1864,9 @@ public class ImageLoader { } else if (imageLocation instanceof TLRPC.TL_webDocument) { FileLoader.getInstance().loadFile((TLRPC.TL_webDocument) imageLocation, true, cacheType); } + if (imageReceiver.isForceLoding()) { + forceLoadingImages.put(img.key, 0); + } } else { String file = Utilities.MD5(httpLocation); File cacheDir = FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE); @@ -1989,6 +2044,7 @@ public class ImageLoader { for (int a = 0; a < img.imageReceiverArray.size(); a++) { String key = img.keys.get(a); String filter = img.filters.get(a); + Boolean thumb = img.thumbs.get(a); ImageReceiver imageReceiver = img.imageReceiverArray.get(a); CacheImage cacheImage = imageLoadingByKeys.get(key); if (cacheImage == null) { @@ -1996,7 +2052,7 @@ public class ImageLoader { cacheImage.finalFilePath = finalFile; cacheImage.key = key; cacheImage.httpUrl = img.httpUrl; - cacheImage.thumb = img.thumb; + cacheImage.selfThumb = thumb; cacheImage.ext = img.ext; cacheImage.encryptionKeyPath = img.encryptionKeyPath; cacheImage.cacheTask = new CacheOutTask(cacheImage); @@ -2005,13 +2061,14 @@ public class ImageLoader { imageLoadingByKeys.put(key, cacheImage); tasks.add(cacheImage.cacheTask); } - cacheImage.addImageReceiver(imageReceiver, key, filter); + cacheImage.addImageReceiver(imageReceiver, key, filter, thumb); } for (int a = 0; a < tasks.size(); a++) { - if (img.thumb) { - cacheThumbOutQueue.postRunnable(tasks.get(a)); + CacheOutTask task = tasks.get(a); + if (task.cacheImage.selfThumb) { + cacheThumbOutQueue.postRunnable(task); } else { - cacheOutQueue.postRunnable(tasks.get(a)); + cacheOutQueue.postRunnable(task); } } } @@ -2373,6 +2430,10 @@ public class ImageLoader { public static String getHttpUrlExtension(String url, String defaultExt) { String ext = null; + String last = Uri.parse(url).getLastPathSegment(); + if (!TextUtils.isEmpty(last) && last.length() > 1) { + url = last; + } int idx = url.lastIndexOf('.'); if (idx != -1) { ext = url.substring(idx + 1); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ImageReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/ImageReceiver.java index e2ce9c5f0..d83c661d4 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ImageReceiver.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ImageReceiver.java @@ -48,9 +48,12 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg private View parentView; private Integer tag; private Integer thumbTag; + private int param; private MessageObject parentMessageObject; private boolean canceledLoading; private static PorterDuffColorFilter selectedColorFilter = new PorterDuffColorFilter(0xffdddddd, PorterDuff.Mode.MULTIPLY); + private static PorterDuffColorFilter selectedGroupColorFilter = new PorterDuffColorFilter(0xffbbbbbb, PorterDuff.Mode.MULTIPLY); + private boolean forceLoding; private SetImageBackup setImageBackup; @@ -70,6 +73,11 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg private boolean allowStartAnimation = true; private boolean allowDecodeSingleFrame; + private boolean crossfadeWithOldImage; + private Drawable crossfadeImage; + private String crossfadeKey; + private BitmapShader crossfadeShader; + private boolean needsQualityThumb; private boolean shouldGenerateQualityThumb; private boolean invalidateAll; @@ -83,12 +91,12 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg private int roundRadius; private BitmapShader bitmapShader; private BitmapShader bitmapShaderThumb; - private static Paint roundPaint; + private Paint roundPaint; private RectF roundRect = new RectF(); private RectF bitmapRect = new RectF(); private Matrix shaderMatrix = new Matrix(); private float overrideAlpha = 1.0f; - private boolean isPressed; + private int isPressed; private int orientation; private boolean centerRotation; private ImageReceiverDelegate delegate; @@ -105,16 +113,23 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg public ImageReceiver(View view) { parentView = view; - if (roundPaint == null) { - roundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - } + roundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); } public void cancelLoadImage() { + forceLoding = false; ImageLoader.getInstance().cancelLoadingForImageReceiver(this, 0); canceledLoading = true; } + public void setForceLoading(boolean value) { + forceLoding = value; + } + + public boolean isForceLoding() { + return forceLoding; + } + public void setImage(TLObject path, String filter, Drawable thumb, String ext, int cacheType) { setImage(path, null, filter, thumb, null, null, 0, ext, cacheType); } @@ -149,8 +164,9 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg && !(fileLocation instanceof TLRPC.TL_document) && !(fileLocation instanceof TLRPC.TL_webDocument) && !(fileLocation instanceof TLRPC.TL_documentEncrypted))) { - recycleBitmap(null, false); - recycleBitmap(null, true); + for (int a = 0; a < 3; a++) { + recycleBitmap(null, a); + } currentKey = null; currentExt = ext; currentThumbKey = null; @@ -166,6 +182,7 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg currentImage = null; bitmapShader = null; bitmapShaderThumb = null; + crossfadeShader = null; ImageLoader.getInstance().cancelLoadingForImageReceiver(this, 0); if (parentView != null) { if (invalidateAll) { @@ -180,7 +197,7 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg return; } - if (!(thumbLocation instanceof TLRPC.TL_fileLocation)) { + if (!(thumbLocation instanceof TLRPC.TL_fileLocation) && !(thumbLocation instanceof TLRPC.TL_fileEncryptedLocation)) { thumbLocation = null; } @@ -230,8 +247,35 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg } } - recycleBitmap(key, false); - recycleBitmap(thumbKey, true); + if (crossfadeWithOldImage) { + if (currentImage != null) { + recycleBitmap(thumbKey, 1); + recycleBitmap(null, 2); + crossfadeShader = bitmapShader; + crossfadeImage = currentImage; + crossfadeKey = currentKey; + currentImage = null; + currentKey = null; + } else if (currentThumb != null) { + recycleBitmap(key, 0); + recycleBitmap(null, 2); + crossfadeShader = bitmapShaderThumb; + crossfadeImage = currentThumb; + crossfadeKey = currentThumbKey; + currentThumb = null; + currentThumbKey = null; + } else { + recycleBitmap(key, 0); + recycleBitmap(thumbKey, 1); + recycleBitmap(null, 2); + crossfadeShader = null; + } + } else { + recycleBitmap(key, 0); + recycleBitmap(thumbKey, 1); + recycleBitmap(null, 2); + crossfadeShader = null; + } currentThumbKey = thumbKey; currentKey = key; @@ -270,12 +314,12 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg this.delegate = delegate; } - public void setPressed(boolean value) { + public void setPressed(int value) { isPressed = value; } public boolean getPressed() { - return isPressed; + return isPressed != 0; } public void setOrientation(int angle, boolean center) { @@ -316,9 +360,16 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg public void setImageBitmap(Drawable bitmap) { ImageLoader.getInstance().cancelLoadingForImageReceiver(this, 0); - recycleBitmap(null, false); - recycleBitmap(null, true); + for (int a = 0; a < 3; a++) { + recycleBitmap(null, a); + } staticThumb = bitmap; + if (roundRadius != 0 && bitmap instanceof BitmapDrawable) { + Bitmap object = ((BitmapDrawable) bitmap).getBitmap(); + bitmapShaderThumb = new BitmapShader(object, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); + } else { + bitmapShaderThumb = null; + } currentThumbLocation = null; currentKey = null; currentExt = null; @@ -331,7 +382,7 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg currentSize = 0; currentCacheType = 0; bitmapShader = null; - bitmapShaderThumb = null; + crossfadeShader = null; if (setImageBackup != null) { setImageBackup.fileLocation = null; setImageBackup.httpUrl = null; @@ -352,12 +403,13 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg } public void clearImage() { - recycleBitmap(null, false); - recycleBitmap(null, true); + for (int a = 0; a < 3; a++) { + recycleBitmap(null, a); + } if (needsQualityThumb) { NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messageThumbGenerated); - ImageLoader.getInstance().cancelLoadingForImageReceiver(this, 0); } + ImageLoader.getInstance().cancelLoadingForImageReceiver(this, 0); } public void onDetachedFromWindow() { @@ -402,17 +454,25 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg paint = bitmapDrawable.getPaint(); } boolean hasFilter = paint != null && paint.getColorFilter() != null; - if (hasFilter && !isPressed) { + if (hasFilter && isPressed == 0) { if (shader != null) { roundPaint.setColorFilter(null); } else if (staticThumb != drawable) { bitmapDrawable.setColorFilter(null); } - } else if (!hasFilter && isPressed) { - if (shader != null) { - roundPaint.setColorFilter(selectedColorFilter); + } else if (!hasFilter && isPressed != 0) { + if (isPressed == 1) { + if (shader != null) { + roundPaint.setColorFilter(selectedColorFilter); + } else { + bitmapDrawable.setColorFilter(selectedColorFilter); + } } else { - bitmapDrawable.setColorFilter(selectedColorFilter); + if (shader != null) { + roundPaint.setColorFilter(selectedGroupColorFilter); + } else { + bitmapDrawable.setColorFilter(selectedGroupColorFilter); + } } } if (colorFilter != null) { @@ -615,6 +675,10 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg currentAlpha += dt / 150.0f; if (currentAlpha > 1) { currentAlpha = 1; + if (crossfadeImage != null) { + recycleBitmap(null, 2); + crossfadeShader = null; + } } } lastUpdateAlphaTime = System.currentTimeMillis(); @@ -633,8 +697,12 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg Drawable drawable = null; boolean animationNotReady = currentImage instanceof AnimatedFileDrawable && !((AnimatedFileDrawable) currentImage).hasBitmap(); boolean isThumb = false; + BitmapShader customShader = null; if (!forcePreview && currentImage != null && !animationNotReady) { drawable = currentImage; + } else if (crossfadeImage != null) { + drawable = crossfadeImage; + customShader = crossfadeShader; } else if (staticThumb instanceof BitmapDrawable) { drawable = staticThumb; isThumb = true; @@ -649,8 +717,12 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg } else { if (crossfadeWithThumb && currentAlpha != 1.0f) { Drawable thumbDrawable = null; + BitmapShader customThumbShader = null; if (drawable == currentImage) { - if (staticThumb != null) { + if (crossfadeImage != null) { + thumbDrawable = crossfadeImage; + customThumbShader = crossfadeShader; + } else if (staticThumb != null) { thumbDrawable = staticThumb; } else if (currentThumb != null) { thumbDrawable = currentThumb; @@ -661,13 +733,13 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg } } if (thumbDrawable != null) { - drawDrawable(canvas, thumbDrawable, (int) (overrideAlpha * 255), bitmapShaderThumb); + drawDrawable(canvas, thumbDrawable, (int) (overrideAlpha * 255), customThumbShader != null ? customThumbShader : bitmapShaderThumb); } } - drawDrawable(canvas, drawable, (int) (overrideAlpha * currentAlpha * 255), isThumb ? bitmapShaderThumb : bitmapShader); + drawDrawable(canvas, drawable, (int) (overrideAlpha * currentAlpha * 255), customShader != null ? customShader : (isThumb ? bitmapShaderThumb : bitmapShader)); } } else { - drawDrawable(canvas, drawable, (int) (overrideAlpha * 255), isThumb ? bitmapShaderThumb : bitmapShader); + drawDrawable(canvas, drawable, (int) (overrideAlpha * 255), customShader != null ? customShader : (isThumb ? bitmapShaderThumb : bitmapShader)); } checkAlphaAnimation(animationNotReady && crossfadeWithThumb); @@ -799,10 +871,18 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg } } + public void setImageX(int x) { + imageX = x; + } + public void setImageY(int y) { imageY = y; } + public void setImageWidth(int width) { + imageW = width; + } + public void setImageCoords(int x, int y, int width, int height) { imageX = x; imageY = y; @@ -927,6 +1007,10 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg } } + public void setCrossfadeWithOldImage(boolean value) { + crossfadeWithOldImage = value; + } + public boolean isNeedsQualityThumb() { return needsQualityThumb; } @@ -987,6 +1071,14 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg } } + public void setParam(int value) { + param = value; + } + + public int getParam() { + return param; + } + protected boolean setImageBitmapByKey(BitmapDrawable bitmap, String key, boolean thumb, boolean memCache) { if (bitmap == null || key == null) { return false; @@ -1082,10 +1174,13 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg return true; } - private void recycleBitmap(String newKey, boolean thumb) { + private void recycleBitmap(String newKey, int type) { String key; Drawable image; - if (thumb) { + if (type == 2) { + key = crossfadeKey; + image = crossfadeImage; + } else if (type == 1) { key = currentThumbKey; image = currentThumb; } else { @@ -1106,7 +1201,10 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg } } } - if (thumb) { + if (type == 2) { + crossfadeKey = null; + crossfadeImage = null; + } else if (type == 1) { currentThumb = null; currentThumbKey = null; } else { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/LocaleController.java b/TMessagesProj/src/main/java/org/telegram/messenger/LocaleController.java index 5603ea5eb..e967c5f99 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/LocaleController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/LocaleController.java @@ -114,7 +114,7 @@ public class LocaleController { localeInfo = new LocaleInfo(); localeInfo.name = args[0]; localeInfo.nameEnglish = args[1]; - localeInfo.shortName = args[2]; + localeInfo.shortName = args[2].toLowerCase(); localeInfo.pathToFile = args[3]; if (args.length >= 5) { localeInfo.version = Utilities.parseInt(args[4]); @@ -195,7 +195,7 @@ public class LocaleController { addRules(new String[]{"ga", "se", "sma", "smi", "smj", "smn", "sms"}, new PluralRules_Two()); addRules(new String[]{"ak", "am", "bh", "fil", "tl", "guw", "hi", "ln", "mg", "nso", "ti", "wa"}, new PluralRules_Zero()); addRules(new String[]{"az", "bm", "fa", "ig", "hu", "ja", "kde", "kea", "ko", "my", "ses", "sg", "to", - "tr", "vi", "wo", "yo", "zh", "bo", "dz", "id", "jv", "ka", "km", "kn", "ms", "th"}, new PluralRules_None()); + "tr", "vi", "wo", "yo", "zh", "bo", "dz", "id", "jv", "jw", "ka", "km", "kn", "ms", "th", "in"}, new PluralRules_None()); LocaleInfo localeInfo = new LocaleInfo(); localeInfo.name = "English"; @@ -270,7 +270,12 @@ public class LocaleController { loadOtherLanguages(); if (remoteLanguages.isEmpty()) { - loadRemoteLanguages(); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + loadRemoteLanguages(); + } + }); } for (int a = 0; a < otherLanguages.size(); a++) { @@ -316,7 +321,7 @@ public class LocaleController { } } - applyLanguage(currentInfo, override); + applyLanguage(currentInfo, override, true); } catch (Exception e) { FileLog.e(e); } @@ -442,6 +447,40 @@ public class LocaleController { return result.toString(); } + public static String getLocaleAlias(String code) { + if (code == null) { + return null; + } + switch (code) { + case "in": + return "id"; + case "iw": + return "he"; + case "jw": + return "jv"; + case "no": + return "nb"; + case "tl": + return "fil"; + case "ji": + return "yi"; + case "id": + return "in"; + case "he": + return "iw"; + case "jv": + return "jw"; + case "nb": + return "no"; + case "fil": + return "tl"; + case "yi": + return "ji"; + } + + return null; + } + public boolean applyLanguageFile(File file) { try { HashMap stringMap = getLocaleFileStrings(file); @@ -474,17 +513,17 @@ public class LocaleController { localeInfo = new LocaleInfo(); localeInfo.name = languageName; localeInfo.nameEnglish = languageNameInEnglish; - localeInfo.shortName = languageCode; + localeInfo.shortName = languageCode.toLowerCase(); localeInfo.pathToFile = finalFile.getAbsolutePath(); languages.add(localeInfo); - languagesDict.put(localeInfo.shortName, localeInfo); + languagesDict.put(localeInfo.getKey(), localeInfo); otherLanguages.add(localeInfo); saveOtherLanguages(); } localeValues = stringMap; - applyLanguage(localeInfo, true, true); + applyLanguage(localeInfo, true, false, true, false); return true; } } catch (Exception e) { @@ -538,7 +577,7 @@ public class LocaleController { if (info == null) { info = getLanguageFromDict("en"); } - applyLanguage(info, true); + applyLanguage(info, true, false); } otherLanguages.remove(localeInfo); @@ -648,19 +687,31 @@ public class LocaleController { return new HashMap<>(); } - public void applyLanguage(LocaleInfo localeInfo, boolean override) { - applyLanguage(localeInfo, override, false); + public void applyLanguage(LocaleInfo localeInfo, boolean override, boolean init) { + applyLanguage(localeInfo, override, init, false, false); } - public void applyLanguage(LocaleInfo localeInfo, boolean override, boolean fromFile) { + public void applyLanguage(final LocaleInfo localeInfo, boolean override, boolean init, boolean fromFile, boolean force) { if (localeInfo == null) { return; } File pathToFile = localeInfo.getPathToFile(); String shortName = localeInfo.shortName; - ConnectionsManager.getInstance().setLangCode(shortName.replace("_", "-")); - if (localeInfo.isRemote() && !pathToFile.exists()) { - applyRemoteLanguage(localeInfo, null, false); + if (!init) { + ConnectionsManager.getInstance().setLangCode(shortName.replace("_", "-")); + } + if (localeInfo.isRemote() && (force || !pathToFile.exists())) { + FileLog.d("reload locale because file doesn't exist " + pathToFile); + if (init) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + applyRemoteLanguage(localeInfo, null, true); + } + }); + } else { + applyRemoteLanguage(localeInfo, null, true); + } } try { Locale newLocale; @@ -685,9 +736,12 @@ public class LocaleController { } currentLocale = newLocale; currentLocaleInfo = localeInfo; - currentPluralRules = allRules.get(currentLocale.getLanguage()); + currentPluralRules = allRules.get(args[0]); if (currentPluralRules == null) { - currentPluralRules = allRules.get("en"); + currentPluralRules = allRules.get(currentLocale.getLanguage()); + } + if (currentPluralRules == null) { + currentPluralRules = new PluralRules_None(); } changingConfiguration = true; Locale.setDefault(currentLocale); @@ -696,7 +750,16 @@ public class LocaleController { ApplicationLoader.applicationContext.getResources().updateConfiguration(config, ApplicationLoader.applicationContext.getResources().getDisplayMetrics()); changingConfiguration = false; if (reloadLastFile) { - reloadCurrentRemoteLocale(); + if (init) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + reloadCurrentRemoteLocale(); + } + }); + } else { + reloadCurrentRemoteLocale(); + } reloadLastFile = false; } } catch (Exception e) { @@ -733,6 +796,16 @@ public class LocaleController { return getInstance().getStringInternal(key, res); } + public static String getPluralString(String key, int plural) { + if (key == null || key.length() == 0 || getInstance().currentPluralRules == null) { + return "LOC_ERR:" + key; + } + String param = getInstance().stringForQuantity(getInstance().currentPluralRules.quantityForNumber(plural)); + param = key + "_" + param; + int resourceId = ApplicationLoader.applicationContext.getResources().getIdentifier(param, "string", ApplicationLoader.applicationContext.getPackageName()); + return getString(param, resourceId); + } + public static String formatPluralString(String key, int plural) { if (key == null || key.length() == 0 || getInstance().currentPluralRules == null) { return "LOC_ERR:" + key; @@ -843,7 +916,7 @@ public class LocaleController { format.setCurrency(сurrency); return (discount ? "-" : "") + format.format(doubleAmount); } - return (discount ? "-" : "") + String.format(type + customFormat, doubleAmount); + return (discount ? "-" : "") + String.format(Locale.US, type + customFormat, doubleAmount); } public String formatCurrencyDecimalString(long amount, String type, boolean inludeType) { @@ -902,7 +975,7 @@ public class LocaleController { doubleAmount = amount / 100.0; break; } - return String.format(inludeType ? type : "" + customFormat, doubleAmount).trim(); + return String.format(Locale.US, inludeType ? type : "" + customFormat, doubleAmount).trim(); } public static String formatStringSimple(String string, Object... args) { @@ -942,7 +1015,7 @@ public class LocaleController { if (languageOverride != null) { LocaleInfo toSet = currentLocaleInfo; currentLocaleInfo = null; - applyLanguage(toSet, false); + applyLanguage(toSet, false, false); } else { Locale newLocale = newConfig.locale; if (newLocale != null) { @@ -1052,6 +1125,55 @@ public class LocaleController { return "LOC_ERR"; } + public static String formatLocationUpdateDate(long date) { + try { + date *= 1000; + Calendar rightNow = Calendar.getInstance(); + int day = rightNow.get(Calendar.DAY_OF_YEAR); + int year = rightNow.get(Calendar.YEAR); + rightNow.setTimeInMillis(date); + int dateDay = rightNow.get(Calendar.DAY_OF_YEAR); + int dateYear = rightNow.get(Calendar.YEAR); + + if (dateDay == day && year == dateYear) { + int diff = (int) (ConnectionsManager.getInstance().getCurrentTime() - date / 1000) / 60; + if (diff < 1) { + return LocaleController.getString("LocationUpdatedJustNow", R.string.LocationUpdatedJustNow); + } else if (diff < 60) { + return LocaleController.formatPluralString("UpdatedMinutes", diff); + } + return String.format("%s %s %s", LocaleController.getString("LocationUpdated", R.string.LocationUpdated), LocaleController.getString("TodayAt", R.string.TodayAt), getInstance().formatterDay.format(new Date(date))); + } else if (dateDay + 1 == day && year == dateYear) { + return String.format("%s %s %s", LocaleController.getString("LocationUpdated", R.string.LocationUpdated), LocaleController.getString("YesterdayAt", R.string.YesterdayAt), getInstance().formatterDay.format(new Date(date))); + } else if (Math.abs(System.currentTimeMillis() - date) < 31536000000L) { + String format = LocaleController.formatString("formatDateAtTime", R.string.formatDateAtTime, getInstance().formatterMonth.format(new Date(date)), getInstance().formatterDay.format(new Date(date))); + return String.format("%s %s", LocaleController.getString("LocationUpdated", R.string.LocationUpdated), format); + } else { + String format = LocaleController.formatString("formatDateAtTime", R.string.formatDateAtTime, getInstance().formatterYear.format(new Date(date)), getInstance().formatterDay.format(new Date(date))); + return String.format("%s %s", LocaleController.getString("LocationUpdated", R.string.LocationUpdated), format); + } + } catch (Exception e) { + FileLog.e(e); + } + return "LOC_ERR"; + } + + public static String formatLocationLeftTime(int time) { + String text; + int hours = time / 60 / 60; + time -= hours * 60 * 60; + int minutes = time / 60; + time -= minutes * 60; + if (hours != 0) { + text = String.format("%dh", hours + (minutes > 30 ? 1 : 0)); + } else if (minutes != 0) { + text = String.format("%d", minutes + (time > 30 ? 1 : 0)); + } else { + text = String.format("%d", time); + } + return text; + } + public static String formatDateOnline(long date) { try { date *= 1000; @@ -1110,8 +1232,9 @@ public class LocaleController { if (lang == null) { lang = "en"; } - isRTL = lang.toLowerCase().equals("ar"); - nameDisplayOrder = lang.toLowerCase().equals("ko") ? 2 : 1; + lang = lang.toLowerCase(); + isRTL = lang.startsWith("ar") || BuildVars.DEBUG_VERSION && (lang.startsWith("he") || lang.startsWith("iw") || lang.startsWith("fa")); + nameDisplayOrder = lang.equals("ko") ? 2 : 1; formatterMonth = createFormatter(locale, getStringInternal("formatterMonth", R.string.formatterMonth), "dd MMM"); formatterYear = createFormatter(locale, getStringInternal("formatterYear", R.string.formatterYear), "dd.MM.yy"); @@ -1176,11 +1299,11 @@ public class LocaleController { } public static String formatShortNumber(int number, int[] rounded) { - String K = ""; + StringBuilder K = new StringBuilder(); int lastDec = 0; int KCount = 0; while (number / 1000 > 0) { - K += "K"; + K.append("K"); lastDec = (number % 1000) / 100; number /= 1000; } @@ -1195,13 +1318,13 @@ public class LocaleController { if (K.length() == 2) { return String.format(Locale.US, "%d.%dM", number, lastDec); } else { - return String.format(Locale.US, "%d.%d%s", number, lastDec, K); + return String.format(Locale.US, "%d.%d%s", number, lastDec, K.toString()); } } if (K.length() == 2) { return String.format(Locale.US, "%dM", number); } else { - return String.format(Locale.US, "%d%s", number, K); + return String.format(Locale.US, "%d%s", number, K.toString()); } } @@ -1242,8 +1365,19 @@ public class LocaleController { } } + private String escapeString(String str) { + if (str.contains("[CDATA")) { + return str; + } + return str.replace("<", "<").replace(">", ">").replace("&", "&"); + } + public void saveRemoteLocaleStrings(final TLRPC.TL_langPackDifference difference) { - File finalFile = new File(ApplicationLoader.getFilesDirFixed(), "remote_" + difference.lang_code + ".xml"); + if (difference == null || difference.strings.isEmpty()) { + return; + } + final String langCode = difference.lang_code.replace('-', '_').toLowerCase(); + File finalFile = new File(ApplicationLoader.getFilesDirFixed(), "remote_" + langCode + ".xml"); try { final HashMap values; if (difference.from_version == 0) { @@ -1254,18 +1388,19 @@ public class LocaleController { for (int a = 0; a < difference.strings.size(); a++) { TLRPC.LangPackString string = difference.strings.get(a); if (string instanceof TLRPC.TL_langPackString) { - values.put(string.key, string.value); + values.put(string.key, escapeString(string.value)); } else if (string instanceof TLRPC.TL_langPackStringPluralized) { - values.put(string.key + "_zero", string.zero_value != null ? string.zero_value : ""); - values.put(string.key + "_one", string.one_value != null ? string.one_value : ""); - values.put(string.key + "_two", string.two_value != null ? string.two_value : ""); - values.put(string.key + "_few", string.few_value != null ? string.few_value : ""); - values.put(string.key + "_many", string.many_value != null ? string.many_value : ""); - values.put(string.key + "_other", string.other_value != null ? string.other_value : ""); + values.put(string.key + "_zero", string.zero_value != null ? escapeString(string.zero_value) : ""); + values.put(string.key + "_one", string.one_value != null ? escapeString(string.one_value) : ""); + values.put(string.key + "_two", string.two_value != null ? escapeString(string.two_value) : ""); + values.put(string.key + "_few", string.few_value != null ? escapeString(string.few_value) : ""); + values.put(string.key + "_many", string.many_value != null ? escapeString(string.many_value) : ""); + values.put(string.key + "_other", string.other_value != null ? escapeString(string.other_value) : ""); } else if (string instanceof TLRPC.TL_langPackStringDeleted) { values.remove(string.key); } } + FileLog.d("save locale file to " + finalFile); BufferedWriter writer = new BufferedWriter(new FileWriter(finalFile)); writer.write("\n"); writer.write("\n"); @@ -1278,7 +1413,7 @@ public class LocaleController { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - LocaleInfo localeInfo = getLanguageFromDict(difference.lang_code); + LocaleInfo localeInfo = getLanguageFromDict(langCode); if (localeInfo != null) { localeInfo.version = difference.version; } @@ -1349,6 +1484,7 @@ public class LocaleController { remoteLanguages.clear(); for (int a = 0; a < res.objects.size(); a++) { TLRPC.TL_langPackLanguage language = (TLRPC.TL_langPackLanguage) res.objects.get(a); + FileLog.d("loaded lang " + language.name); LocaleInfo localeInfo = new LocaleInfo(); localeInfo.nameEnglish = language.name; localeInfo.name = language.native_name; @@ -1359,6 +1495,7 @@ public class LocaleController { if (existing == null) { languages.add(localeInfo); languagesDict.put(localeInfo.getKey(), localeInfo); + existing = localeInfo; } else { existing.nameEnglish = localeInfo.nameEnglish; existing.name = localeInfo.name; @@ -1374,6 +1511,7 @@ public class LocaleController { } LocaleInfo existing = remoteLoaded.get(info.getKey()); if (existing == null) { + FileLog.d("remove lang " + info.getKey()); languages.remove(a); languagesDict.remove(info.getKey()); a--; @@ -1387,14 +1525,14 @@ public class LocaleController { if (info == null) { info = getLanguageFromDict("en"); } - applyLanguage(info, true); + applyLanguage(info, true, false); NotificationCenter.getInstance().postNotificationName(NotificationCenter.reloadInterface); } } } saveOtherLanguages(); NotificationCenter.getInstance().postNotificationName(NotificationCenter.suggestedLangpack); - applyLanguage(currentLocaleInfo, true); + applyLanguage(currentLocaleInfo, true, false); } }); } @@ -1402,11 +1540,11 @@ public class LocaleController { }, ConnectionsManager.RequestFlagWithoutLogin); } - public void applyRemoteLanguage(LocaleInfo localeInfo, TLRPC.TL_langPackLanguage language, boolean force) { + private void applyRemoteLanguage(LocaleInfo localeInfo, TLRPC.TL_langPackLanguage language, boolean force) { if (localeInfo == null && language == null || localeInfo != null && !localeInfo.isRemote()) { return; } - if (localeInfo.version != 0 && !BuildVars.DEBUG_VERSION && !force) { + if (localeInfo.version != 0 && !force) { TLRPC.TL_langpack_getDifference req = new TLRPC.TL_langpack_getDifference(); req.from_version = localeInfo.version; ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/LocationController.java b/TMessagesProj/src/main/java/org/telegram/messenger/LocationController.java new file mode 100644 index 000000000..487c0d4da --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/LocationController.java @@ -0,0 +1,690 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.messenger; + +import android.content.Context; +import android.content.Intent; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.LongSparseArray; +import android.util.SparseIntArray; + +import org.telegram.SQLite.SQLiteCursor; +import org.telegram.SQLite.SQLitePreparedStatement; +import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.NativeByteBuffer; +import org.telegram.tgnet.RequestDelegate; +import org.telegram.tgnet.TLObject; +import org.telegram.tgnet.TLRPC; + +import java.util.ArrayList; +import java.util.HashMap; + +public class LocationController implements NotificationCenter.NotificationCenterDelegate { + + private HashMap sharingLocationsMap = new HashMap<>(); + private ArrayList sharingLocations = new ArrayList<>(); + public HashMap> locationsCache = new HashMap<>(); + private LocationManager locationManager; + private GpsLocationListener gpsLocationListener = new GpsLocationListener(); + private GpsLocationListener networkLocationListener = new GpsLocationListener(); + private GpsLocationListener passiveLocationListener = new GpsLocationListener(); + private Location lastKnownLocation; + private long lastLocationSendTime; + private boolean locationSentSinceLastGoogleMapUpdate = true; + private long lastLocationStartTime; + private boolean started; + private boolean lastLocationByGoogleMaps; + private SparseIntArray requests = new SparseIntArray(); + private LongSparseArray cacheRequests = new LongSparseArray<>(); + + public ArrayList sharingLocationsUI = new ArrayList<>(); + private HashMap sharingLocationsMapUI = new HashMap<>(); + + private final static int BACKGROUD_UPDATE_TIME = 90 * 1000; + private final static int LOCATION_ACQUIRE_TIME = 10 * 1000; + private final static int FOREGROUND_UPDATE_TIME = 20 * 1000; + private final static double eps = 0.0001; + + private static volatile LocationController Instance = null; + + public static LocationController getInstance() { + LocationController localInstance = Instance; + if (localInstance == null) { + synchronized (LocationController.class) { + localInstance = Instance; + if (localInstance == null) { + Instance = localInstance = new LocationController(); + } + } + } + return localInstance; + } + + public static class SharingLocationInfo { + public long did; + public int mid; + public int stopTime; + public int period; + public MessageObject messageObject; + } + + private class GpsLocationListener implements LocationListener { + + @Override + public void onLocationChanged(Location location) { + if (location == null) { + return; + } + if (lastKnownLocation != null && (this == networkLocationListener || this == passiveLocationListener)) { + if (!started && location.distanceTo(lastKnownLocation) > 20) { + lastKnownLocation = location; + lastLocationSendTime = System.currentTimeMillis() - BACKGROUD_UPDATE_TIME + 5000; + } + } else { + lastKnownLocation = location; + } + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + + } + + @Override + public void onProviderEnabled(String provider) { + + } + + @Override + public void onProviderDisabled(String provider) { + + } + } + + public LocationController() { + locationManager = (LocationManager) ApplicationLoader.applicationContext.getSystemService(Context.LOCATION_SERVICE); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + LocationController locationController = getInstance(); + NotificationCenter.getInstance().addObserver(locationController, NotificationCenter.didReceivedNewMessages); + NotificationCenter.getInstance().addObserver(locationController, NotificationCenter.messagesDeleted); + NotificationCenter.getInstance().addObserver(locationController, NotificationCenter.replaceMessagesObjects); + } + }); + loadSharingLocations(); + } + + @SuppressWarnings("unchecked") + @Override + public void didReceivedNotification(int id, Object... args) { + if (id == NotificationCenter.didReceivedNewMessages) { + long did = (Long) args[0]; + if (!isSharingLocation(did)) { + return; + } + ArrayList messages = locationsCache.get(did); + if (messages == null) { + return; + } + ArrayList arr = (ArrayList) args[1]; + boolean added = false; + for (int a = 0; a < arr.size(); a++) { + MessageObject messageObject = arr.get(a); + if (messageObject.isLiveLocation()) { + added = true; + boolean replaced = false; + for (int b = 0; b < messages.size(); b++) { + if (messages.get(b).from_id == messageObject.messageOwner.from_id) { + replaced = true; + messages.set(b, messageObject.messageOwner); + break; + } + } + if (!replaced) { + messages.add(messageObject.messageOwner); + } + } + } + if (added) { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.liveLocationsCacheChanged, did); + } + } else if (id == NotificationCenter.messagesDeleted) { + if (!sharingLocationsUI.isEmpty()) { + ArrayList markAsDeletedMessages = (ArrayList) args[0]; + int channelId = (Integer) args[1]; + ArrayList toRemove = null; + for (int a = 0; a < sharingLocationsUI.size(); a++) { + SharingLocationInfo info = sharingLocationsUI.get(a); + int messageChannelId = info.messageObject != null ? info.messageObject.getChannelId() : 0; + if (channelId != messageChannelId) { + continue; + } + if (markAsDeletedMessages.contains(info.mid)) { + if (toRemove == null) { + toRemove = new ArrayList<>(); + } + toRemove.add(info.did); + } + } + if (toRemove != null) { + for (int a = 0; a < toRemove.size(); a++) { + removeSharingLocation(toRemove.get(a)); + } + } + } + } else if (id == NotificationCenter.replaceMessagesObjects) { + long did = (long) args[0]; + if (!isSharingLocation(did)) { + return; + } + ArrayList messages = locationsCache.get(did); + if (messages == null) { + return; + } + boolean updated = false; + ArrayList messageObjects = (ArrayList) args[1]; + for (int a = 0; a < messageObjects.size(); a++) { + MessageObject messageObject = messageObjects.get(a); + for (int b = 0; b < messages.size(); b++) { + if (messages.get(b).from_id == messageObject.messageOwner.from_id) { + if (!messageObject.isLiveLocation()) { + messages.remove(b); + } else { + messages.set(b, messageObject.messageOwner); + } + updated = true; + break; + } + } + } + if (updated) { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.liveLocationsCacheChanged, did); + } + } + } + + private void broadcastLastKnownLocation() { + if (lastKnownLocation == null) { + return; + } + if (requests.size() != 0) { + for (int a = 0; a < requests.size(); a++) { + ConnectionsManager.getInstance().cancelRequest(requests.keyAt(a), false); + } + requests.clear(); + } + int date = ConnectionsManager.getInstance().getCurrentTime(); + for (int a = 0; a < sharingLocations.size(); a++) { + final SharingLocationInfo info = sharingLocations.get(a); + if (info.messageObject.messageOwner.media != null && info.messageObject.messageOwner.media.geo != null) { + int messageDate = info.messageObject.messageOwner.edit_date != 0 ? info.messageObject.messageOwner.edit_date : info.messageObject.messageOwner.date; + TLRPC.GeoPoint point = info.messageObject.messageOwner.media.geo; + if (Math.abs(date - messageDate) < 30 && Math.abs(point.lat - lastKnownLocation.getLatitude()) <= eps && Math.abs(point._long - lastKnownLocation.getLongitude()) <= eps) { + continue; + } + } + TLRPC.TL_messages_editMessage req = new TLRPC.TL_messages_editMessage(); + req.peer = MessagesController.getInputPeer((int) info.did); + req.id = info.mid; + req.stop_geo_live = false; + req.flags |= 8192; + req.geo_point = new TLRPC.TL_inputGeoPoint(); + req.geo_point.lat = lastKnownLocation.getLatitude(); + req.geo_point._long = lastKnownLocation.getLongitude(); + final int[] reqId = new int[1]; + reqId[0] = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + if (error != null) { + if (error.text.equals("MESSAGE_ID_INVALID")) { + sharingLocations.remove(info); + sharingLocationsMap.remove(info.did); + saveSharingLocation(info, 1); + requests.delete(reqId[0]); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + sharingLocationsUI.remove(info); + sharingLocationsMapUI.remove(info.did); + if (sharingLocationsUI.isEmpty()) { + stopService(); + } + NotificationCenter.getInstance().postNotificationName(NotificationCenter.liveLocationsChanged); + } + }); + } + return; + } + TLRPC.Updates updates = (TLRPC.Updates) response; + boolean updated = false; + for (int a = 0; a < updates.updates.size(); a++) { + TLRPC.Update update = updates.updates.get(a); + if (update instanceof TLRPC.TL_updateEditMessage) { + updated = true; + info.messageObject.messageOwner = ((TLRPC.TL_updateEditMessage) update).message; + } else if (update instanceof TLRPC.TL_updateEditChannelMessage) { + updated = true; + info.messageObject.messageOwner = ((TLRPC.TL_updateEditChannelMessage) update).message; + } + } + if (updated) { + saveSharingLocation(info, 0); + } + MessagesController.getInstance().processUpdates(updates, false); + } + }); + requests.put(reqId[0], 0); + } + ConnectionsManager.getInstance().resumeNetworkMaybe(); + stop(false); + } + + protected void update() { + if (!sharingLocations.isEmpty()) { + for (int a = 0; a < sharingLocations.size(); a++) { + final SharingLocationInfo info = sharingLocations.get(a); + int currentTime = ConnectionsManager.getInstance().getCurrentTime(); + if (info.stopTime <= currentTime) { + sharingLocations.remove(a); + sharingLocationsMap.remove(info.did); + saveSharingLocation(info, 1); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + sharingLocationsUI.remove(info); + sharingLocationsMapUI.remove(info.did); + if (sharingLocationsUI.isEmpty()) { + stopService(); + } + NotificationCenter.getInstance().postNotificationName(NotificationCenter.liveLocationsChanged); + } + }); + a--; + } + } + if (!started) { + if (Math.abs(lastLocationSendTime - System.currentTimeMillis()) > BACKGROUD_UPDATE_TIME) { + lastLocationStartTime = System.currentTimeMillis(); + start(); + } + } else { + if (lastLocationByGoogleMaps || Math.abs(lastLocationStartTime - System.currentTimeMillis()) > LOCATION_ACQUIRE_TIME) { + lastLocationByGoogleMaps = false; + locationSentSinceLastGoogleMapUpdate = true; + lastLocationSendTime = System.currentTimeMillis(); + broadcastLastKnownLocation(); + } + } + } + } + + public void cleanup() { + sharingLocationsUI.clear(); + sharingLocationsMapUI.clear(); + locationsCache.clear(); + cacheRequests.clear(); + stopService(); + Utilities.stageQueue.postRunnable(new Runnable() { + @Override + public void run() { + requests.clear(); + sharingLocationsMap.clear(); + sharingLocations.clear(); + lastKnownLocation = null; + stop(true); + } + }); + } + + protected void addSharingLocation(long did, int mid, int period, TLRPC.Message message) { + final SharingLocationInfo info = new SharingLocationInfo(); + info.did = did; + info.mid = mid; + info.period = period; + info.messageObject = new MessageObject(message, null, null, false); + info.stopTime = ConnectionsManager.getInstance().getCurrentTime() + period; + final SharingLocationInfo old = sharingLocationsMap.put(did, info); + if (old != null) { + sharingLocations.remove(old); + } + sharingLocations.add(info); + saveSharingLocation(info, 0); + lastLocationSendTime = System.currentTimeMillis() - BACKGROUD_UPDATE_TIME + 5000; + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (old != null) { + sharingLocationsUI.remove(old); + } + sharingLocationsUI.add(info); + sharingLocationsMapUI.put(info.did, info); + startService(); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.liveLocationsChanged); + } + }); + } + + public boolean isSharingLocation(long did) { + return sharingLocationsMapUI.containsKey(did); + } + + public SharingLocationInfo getSharingLocationInfo(long did) { + return sharingLocationsMapUI.get(did); + } + + private void loadSharingLocations() { + MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { + @Override + public void run() { + final ArrayList result = new ArrayList<>(); + final ArrayList users = new ArrayList<>(); + final ArrayList chats = new ArrayList<>(); + try { + ArrayList usersToLoad = new ArrayList<>(); + ArrayList chatsToLoad = new ArrayList<>(); + SQLiteCursor cursor = MessagesStorage.getInstance().getDatabase().queryFinalized("SELECT uid, mid, date, period, message FROM sharing_locations WHERE 1"); + while (cursor.next()) { + SharingLocationInfo info = new SharingLocationInfo(); + info.did = cursor.longValue(0); + info.mid = cursor.intValue(1); + info.stopTime = cursor.intValue(2); + info.period = cursor.intValue(3); + NativeByteBuffer data = cursor.byteBufferValue(4); + if (data != null) { + info.messageObject = new MessageObject(TLRPC.Message.TLdeserialize(data, data.readInt32(false), false), null, false); + MessagesStorage.addUsersAndChatsFromMessage(info.messageObject.messageOwner, usersToLoad, chatsToLoad); + data.reuse(); + } + result.add(info); + int lower_id = (int) info.did; + int high_id = (int) (info.did >> 32); + if (lower_id != 0) { + if (lower_id < 0) { + if (!chatsToLoad.contains(-lower_id)) { + chatsToLoad.add(-lower_id); + } + } else { + if (!usersToLoad.contains(lower_id)) { + usersToLoad.add(lower_id); + } + } + } else { + /*if (!encryptedChatIds.contains(high_id)) { + encryptedChatIds.add(high_id); + }*/ + } + } + cursor.dispose(); + if (!chatsToLoad.isEmpty()) { + MessagesStorage.getInstance().getChatsInternal(TextUtils.join(",", chatsToLoad), chats); + } + if (!usersToLoad.isEmpty()) { + MessagesStorage.getInstance().getUsersInternal(TextUtils.join(",", usersToLoad), users); + } + } catch (Exception e) { + FileLog.e(e); + } + if (!result.isEmpty()) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + MessagesController.getInstance().putUsers(users, true); + MessagesController.getInstance().putChats(chats, true); + Utilities.stageQueue.postRunnable(new Runnable() { + @Override + public void run() { + sharingLocations.addAll(result); + for (int a = 0; a < sharingLocations.size(); a++) { + SharingLocationInfo info = sharingLocations.get(a); + sharingLocationsMap.put(info.did, info); + } + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + sharingLocationsUI.addAll(result); + for (int a = 0; a < result.size(); a++) { + SharingLocationInfo info = result.get(a); + sharingLocationsMapUI.put(info.did, info); + } + startService(); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.liveLocationsChanged); + } + }); + } + }); + } + }); + } + } + }); + } + + private void saveSharingLocation(final SharingLocationInfo info, final int remove) { + MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { + @Override + public void run() { + try { + if (remove == 2) { + MessagesStorage.getInstance().getDatabase().executeFast("DELETE FROM sharing_locations WHERE 1").stepThis().dispose(); + } else if (remove == 1) { + if (info == null) { + return; + } + MessagesStorage.getInstance().getDatabase().executeFast("DELETE FROM sharing_locations WHERE uid = " + info.did).stepThis().dispose(); + } else { + if (info == null) { + return; + } + SQLitePreparedStatement state = MessagesStorage.getInstance().getDatabase().executeFast("REPLACE INTO sharing_locations VALUES(?, ?, ?, ?, ?)"); + state.requery(); + + NativeByteBuffer data = new NativeByteBuffer(info.messageObject.messageOwner.getObjectSize()); + info.messageObject.messageOwner.serializeToStream(data); + + state.bindLong(1, info.did); + state.bindInteger(2, info.mid); + state.bindInteger(3, info.stopTime); + state.bindInteger(4, info.period); + state.bindByteBuffer(5, data); + + state.step(); + state.dispose(); + data.reuse(); + } + } catch (Exception e) { + FileLog.e(e); + } + } + }); + } + + public void removeSharingLocation(final long did) { + Utilities.stageQueue.postRunnable(new Runnable() { + @Override + public void run() { + final SharingLocationInfo info = sharingLocationsMap.remove(did); + if (info != null) { + TLRPC.TL_messages_editMessage req = new TLRPC.TL_messages_editMessage(); + req.peer = MessagesController.getInputPeer((int) info.did); + req.id = info.mid; + req.stop_geo_live = true; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + if (error != null) { + return; + } + MessagesController.getInstance().processUpdates((TLRPC.Updates) response, false); + } + }); + sharingLocations.remove(info); + saveSharingLocation(info, 1); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + sharingLocationsUI.remove(info); + sharingLocationsMapUI.remove(info.did); + if (sharingLocationsUI.isEmpty()) { + stopService(); + } + NotificationCenter.getInstance().postNotificationName(NotificationCenter.liveLocationsChanged); + } + }); + if (sharingLocations.isEmpty()) { + stop(true); + } + } + + } + }); + } + + private void startService() { + ApplicationLoader.applicationContext.startService(new Intent(ApplicationLoader.applicationContext, LocationSharingService.class)); + } + + private void stopService() { + ApplicationLoader.applicationContext.stopService(new Intent(ApplicationLoader.applicationContext, LocationSharingService.class)); + } + + public void removeAllLocationSharings() { + Utilities.stageQueue.postRunnable(new Runnable() { + @Override + public void run() { + for (int a = 0; a < sharingLocations.size(); a++) { + SharingLocationInfo info = sharingLocations.get(a); + TLRPC.TL_messages_editMessage req = new TLRPC.TL_messages_editMessage(); + req.peer = MessagesController.getInputPeer((int) info.did); + req.id = info.mid; + req.stop_geo_live = true; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + if (error != null) { + return; + } + MessagesController.getInstance().processUpdates((TLRPC.Updates) response, false); + } + }); + } + sharingLocations.clear(); + sharingLocationsMap.clear(); + saveSharingLocation(null, 2); + stop(true); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + sharingLocationsUI.clear(); + sharingLocationsMapUI.clear(); + stopService(); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.liveLocationsChanged); + } + }); + } + }); + } + + public void setGoogleMapLocation(Location location, boolean first) { + if (location == null) { + return; + } + lastLocationByGoogleMaps = true; + if (first || lastKnownLocation != null && lastKnownLocation.distanceTo(location) >= 20) { + lastLocationSendTime = System.currentTimeMillis() - BACKGROUD_UPDATE_TIME; + locationSentSinceLastGoogleMapUpdate = false; + } else if (locationSentSinceLastGoogleMapUpdate) { + lastLocationSendTime = System.currentTimeMillis() - BACKGROUD_UPDATE_TIME + FOREGROUND_UPDATE_TIME; + locationSentSinceLastGoogleMapUpdate = false; + } + lastKnownLocation = location; + } + + private void start() { + if (started) { + return; + } + lastLocationStartTime = System.currentTimeMillis(); + started = true; + try { + locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1, 0, gpsLocationListener); + } catch (Exception e) { + FileLog.e(e); + } + try { + locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 1, 0, networkLocationListener); + } catch (Exception e) { + FileLog.e(e); + } + try { + locationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, 1, 0, passiveLocationListener); + } catch (Exception e) { + FileLog.e(e); + } + if (lastKnownLocation == null) { + try { + lastKnownLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); + if (lastKnownLocation == null) { + lastKnownLocation = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); + } + } catch (Exception e) { + FileLog.e(e); + } + } + } + + private void stop(boolean empty) { + started = false; + locationManager.removeUpdates(gpsLocationListener); + if (empty) { + locationManager.removeUpdates(networkLocationListener); + locationManager.removeUpdates(passiveLocationListener); + } + } + + public void loadLiveLocations(final long did) { + if (cacheRequests.indexOfKey(did) >= 0) { + return; + } + cacheRequests.put(did, true); + TLRPC.TL_messages_getRecentLocations req = new TLRPC.TL_messages_getRecentLocations(); + req.peer = MessagesController.getInputPeer((int) did); + req.limit = 100; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, TLRPC.TL_error error) { + if (error != null) { + return; + } + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + cacheRequests.delete(did); + TLRPC.messages_Messages res = (TLRPC.messages_Messages) response; + for (int a = 0; a < res.messages.size(); a++) { + if (!(res.messages.get(a).media instanceof TLRPC.TL_messageMediaGeoLive)) { + res.messages.remove(a); + a--; + } + } + MessagesStorage.getInstance().putUsersAndChats(res.users, res.chats, true, true); + MessagesController.getInstance().putUsers(res.users, false); + MessagesController.getInstance().putChats(res.chats, false); + locationsCache.put(did, res.messages); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.liveLocationsCacheChanged, did); + } + }); + } + }); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/LocationSharingService.java b/TMessagesProj/src/main/java/org/telegram/messenger/LocationSharingService.java new file mode 100644 index 000000000..a129fb749 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/LocationSharingService.java @@ -0,0 +1,135 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.messenger; + +import android.app.PendingIntent; +import android.app.Service; +import android.content.Intent; +import android.os.Handler; +import android.os.IBinder; +import android.support.v4.app.NotificationCompat; +import android.support.v4.app.NotificationManagerCompat; + +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.LaunchActivity; + +import java.util.ArrayList; + +public class LocationSharingService extends Service implements NotificationCenter.NotificationCenterDelegate { + + private NotificationCompat.Builder builder; + private Handler handler; + private Runnable runnable; + + public LocationSharingService() { + super(); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.liveLocationsChanged); + } + + @Override + public void onCreate() { + super.onCreate(); + handler = new Handler(); + runnable = new Runnable() { + public void run() { + handler.postDelayed(runnable, 60000); + Utilities.stageQueue.postRunnable(new Runnable() { + @Override + public void run() { + LocationController.getInstance().update(); + } + }); + } + }; + handler.postDelayed(runnable, 60000); + } + + public IBinder onBind(Intent arg2) { + return null; + } + + public void onDestroy() { + if (handler != null) { + handler.removeCallbacks(runnable); + } + stopForeground(true); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.liveLocationsChanged); + } + + @Override + public void didReceivedNotification(int id, Object... args) { + if (id == NotificationCenter.liveLocationsChanged) { + if (handler != null) { + handler.post(new Runnable() { + @Override + public void run() { + if (LocationController.getInstance().sharingLocationsUI.isEmpty()) { + stopSelf(); + } else { + updateNotification(); + } + } + }); + } + } + } + + private void updateNotification() { + if (builder == null) { + return; + } + String param; + ArrayList infos = LocationController.getInstance().sharingLocationsUI; + if (infos.size() == 1) { + LocationController.SharingLocationInfo info = infos.get(0); + int lower_id = (int) info.messageObject.getDialogId(); + if (lower_id > 0) { + TLRPC.User user = MessagesController.getInstance().getUser(lower_id); + param = UserObject.getFirstName(user); + } else { + TLRPC.Chat chat = MessagesController.getInstance().getChat(-lower_id); + if (chat != null) { + param = chat.title; + } else { + param = ""; + } + } + } else { + param = LocaleController.formatPluralString("Chats", LocationController.getInstance().sharingLocationsUI.size()); + } + String str = String.format(LocaleController.getString("AttachLiveLocationIsSharing", R.string.AttachLiveLocationIsSharing), LocaleController.getString("AttachLiveLocation", R.string.AttachLiveLocation), param); + builder.setTicker(str); + builder.setContentText(str); + NotificationManagerCompat.from(ApplicationLoader.applicationContext).notify(6, builder.build()); + } + + public int onStartCommand(Intent intent, int flags, int startId) { + if (LocationController.getInstance().sharingLocationsUI.isEmpty()) { + stopSelf(); + } + if (builder == null) { + Intent intent2 = new Intent(ApplicationLoader.applicationContext, LaunchActivity.class); + intent2.setAction("org.tmessages.openlocations"); + intent2.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + PendingIntent contentIntent = PendingIntent.getActivity(ApplicationLoader.applicationContext, 0, intent2, 0); + + builder = new NotificationCompat.Builder(ApplicationLoader.applicationContext); + builder.setWhen(System.currentTimeMillis()); + builder.setSmallIcon(R.drawable.notification); + builder.setContentIntent(contentIntent); + builder.setContentTitle(LocaleController.getString("AppName", R.string.AppName)); + Intent stopIntent = new Intent(ApplicationLoader.applicationContext, StopLiveLocationReceiver.class); + builder.addAction(0, LocaleController.getString("StopLiveLocation", R.string.StopLiveLocation), PendingIntent.getBroadcast(ApplicationLoader.applicationContext, 2, stopIntent, PendingIntent.FLAG_UPDATE_CURRENT)); + } + + startForeground(6, builder.build()); + updateNotification(); + return Service.START_NOT_STICKY; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java b/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java index 9b0bd0636..f2928c9f0 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java @@ -10,7 +10,6 @@ package org.telegram.messenger; import android.Manifest; import android.annotation.SuppressLint; -import android.annotation.TargetApi; import android.app.Activity; import android.app.DownloadManager; import android.content.BroadcastReceiver; @@ -50,6 +49,7 @@ import android.provider.MediaStore; import android.provider.OpenableColumns; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; +import android.text.TextUtils; import android.view.TextureView; import android.view.View; import android.view.WindowManager; @@ -69,6 +69,7 @@ import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ChatActivity; import org.telegram.ui.Components.EmbedBottomSheet; +import org.telegram.ui.Components.PhotoFilterView; import org.telegram.ui.Components.PipRoundVideoView; import org.telegram.ui.Components.VideoPlayer; import org.telegram.ui.PhotoViewer; @@ -174,6 +175,28 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, } } + public static class SavedFilterState { + public float enhanceValue; + public float exposureValue; + public float contrastValue; + public float warmthValue; + public float saturationValue; + public float fadeValue; + public int tintShadowsColor; + public int tintHighlightsColor; + public float highlightsValue; + public float shadowsValue; + public float vignetteValue; + public float grainValue; + public int blurType; + public float sharpenValue; + public PhotoFilterView.CurvesToolValue curvesToolValue = new PhotoFilterView.CurvesToolValue(); + public float blurExcludeSize; + public org.telegram.ui.Components.Point blurExcludePoint; + public float blurExcludeBlurSize; + public float blurAngle; + } + public static class PhotoEntry { public int bucketId; public int imageId; @@ -189,7 +212,9 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, public boolean isFiltered; public boolean isPainted; public boolean isCropped; + public boolean isMuted; public int ttl; + public SavedFilterState savedFilterState; public ArrayList stickers = new ArrayList<>(); public PhotoEntry(int bucketId, int imageId, long dateTaken, String path, int orientation, boolean isVideo) { @@ -213,6 +238,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, imagePath = null; thumbPath = null; caption = null; + savedFilterState = null; stickers.clear(); } } @@ -235,7 +261,20 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, public boolean isPainted; public boolean isCropped; public int ttl; + public SavedFilterState savedFilterState; public ArrayList stickers = new ArrayList<>(); + + public void reset() { + isFiltered = false; + isPainted = false; + isCropped = false; + ttl = 0; + imagePath = null; + thumbPath = null; + caption = null; + savedFilterState = null; + stickers.clear(); + } } public final static String MIME_TYPE = "video/avc"; @@ -305,9 +344,13 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, public static final int AUTODOWNLOAD_MASK_MUSIC = 16; public static final int AUTODOWNLOAD_MASK_GIF = 32; public static final int AUTODOWNLOAD_MASK_VIDEOMESSAGE = 64; - public int mobileDataDownloadMask = 0; - public int wifiDownloadMask = 0; - public int roamingDownloadMask = 0; + public boolean globalAutodownloadEnabled; + public int mobileDataDownloadMask[] = new int[4]; + public int wifiDownloadMask[] = new int[4]; + public int roamingDownloadMask[] = new int[4]; + public int mobileMaxFileSize[] = new int[7]; + public int wifiMaxFileSize[] = new int[7]; + public int roamingMaxFileSize[] = new int[7]; private int lastCheckMask = 0; private ArrayList photoDownloadQueue = new ArrayList<>(); private ArrayList audioDownloadQueue = new ArrayList<>(); @@ -323,10 +366,14 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, private boolean raiseToSpeak = true; private boolean customTabs = true; private boolean directShare = true; + private boolean inappCamera = true; + private boolean roundCamera16to9 = true; + private boolean groupPhotosEnabled = true; private boolean shuffleMusic; + private boolean playOrderReversed; private int repeatMode; - private Runnable refreshGalleryRunnable; + private static Runnable refreshGalleryRunnable; public static AlbumEntry allMediaAlbumEntry; public static AlbumEntry allPhotosAlbumEntry; private static Runnable broadcastPhotosRunnable; @@ -494,6 +541,36 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, } }; + private class SmsObserver extends ContentObserver { + public SmsObserver() { + super(null); + } + + @Override + public void onChange(boolean selfChange) { + readSms(); + } + } + + private void readSms() { + /*Cursor cursor = null; + try { + cursor = ApplicationLoader.applicationContext.getContentResolver().query(Uri.parse("content://sms/sent"), null, null, null, null); + while (cursor.moveToNext()) { + String address = cursor.getString(cursor.getColumnIndexOrThrow("address")); + long data = cursor.getLong(cursor.getColumnIndexOrThrow("date")); + String smsBody = cursor.getString(cursor.getColumnIndexOrThrow("body")); + FileLog.d(address + " body = " + smsBody); + } + } catch (Exception e) { + FileLog.e(e); + } finally { + if (cursor != null) { + cursor.close(); + } + }*/ + } + private class InternalObserver extends ContentObserver { public InternalObserver() { super(null); @@ -547,6 +624,25 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, } } + public static int maskToIndex(int mask) { + if (mask == AUTODOWNLOAD_MASK_PHOTO) { + return 0; + } else if (mask == AUTODOWNLOAD_MASK_AUDIO) { + return 1; + } else if (mask == AUTODOWNLOAD_MASK_VIDEO) { + return 2; + } else if (mask == AUTODOWNLOAD_MASK_DOCUMENT) { + return 3; + } else if (mask == AUTODOWNLOAD_MASK_MUSIC) { + return 4; + } else if (mask == AUTODOWNLOAD_MASK_GIF) { + return 5; + } else if (mask == AUTODOWNLOAD_MASK_VIDEOMESSAGE) { + return 6; + } + return 0; + } + private class GalleryObserverExternal extends ContentObserver { public GalleryObserverExternal() { super(null); @@ -568,8 +664,64 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, } } + public static void checkGallery() { + if (Build.VERSION.SDK_INT < 24 || allPhotosAlbumEntry == null) { + return; + } + final int prevSize = allPhotosAlbumEntry.photos.size(); + Utilities.globalQueue.postRunnable(new Runnable() { + @SuppressLint("NewApi") + @Override + public void run() { + int count = 0; + Cursor cursor = null; + try { + if (ApplicationLoader.applicationContext.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + cursor = MediaStore.Images.Media.query(ApplicationLoader.applicationContext.getContentResolver(), MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new String[] {"COUNT(_id)"}, null, null, null); + if (cursor != null) { + if (cursor.moveToNext()) { + count += cursor.getInt(0); + } + } + } + } catch (Throwable e) { + FileLog.e(e); + } finally { + if (cursor != null) { + cursor.close(); + } + } + try { + if (ApplicationLoader.applicationContext.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + cursor = MediaStore.Images.Media.query(ApplicationLoader.applicationContext.getContentResolver(), MediaStore.Video.Media.EXTERNAL_CONTENT_URI, new String[] {"COUNT(_id)"}, null, null, null); + if (cursor != null) { + if (cursor.moveToNext()) { + count += cursor.getInt(0); + } + } + } + } catch (Throwable e) { + FileLog.e(e); + } finally { + if (cursor != null) { + cursor.close(); + } + } + if (prevSize != count) { + if (refreshGalleryRunnable != null) { + AndroidUtilities.cancelRunOnUIThread(refreshGalleryRunnable); + refreshGalleryRunnable = null; + } + loadGalleryPhotosAlbums(0); + } + } + }, 2000); + } + + private ExternalObserver externalObserver; private InternalObserver internalObserver; + private SmsObserver smsObserver; private long lastChatEnterTime; private long lastChatLeaveTime; private long lastMediaCheckTime; @@ -669,15 +821,42 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, fileDecodingQueue = new DispatchQueue("fileDecodingQueue"); SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); - mobileDataDownloadMask = preferences.getInt("mobileDataDownloadMask", AUTODOWNLOAD_MASK_PHOTO | AUTODOWNLOAD_MASK_AUDIO | AUTODOWNLOAD_MASK_MUSIC | AUTODOWNLOAD_MASK_GIF | AUTODOWNLOAD_MASK_VIDEOMESSAGE); - wifiDownloadMask = preferences.getInt("wifiDownloadMask", AUTODOWNLOAD_MASK_PHOTO | AUTODOWNLOAD_MASK_AUDIO | AUTODOWNLOAD_MASK_MUSIC | AUTODOWNLOAD_MASK_GIF | AUTODOWNLOAD_MASK_VIDEOMESSAGE); - roamingDownloadMask = preferences.getInt("roamingDownloadMask", 0); + for (int a = 0; a < 4; a++) { + String key = "mobileDataDownloadMask" + (a == 0 ? "" : a); + if (a == 0 || preferences.contains(key)) { + mobileDataDownloadMask[a] = preferences.getInt(key, AUTODOWNLOAD_MASK_PHOTO | AUTODOWNLOAD_MASK_AUDIO | AUTODOWNLOAD_MASK_MUSIC | AUTODOWNLOAD_MASK_GIF | AUTODOWNLOAD_MASK_VIDEOMESSAGE); + wifiDownloadMask[a] = preferences.getInt("wifiDownloadMask" + (a == 0 ? "" : a), AUTODOWNLOAD_MASK_PHOTO | AUTODOWNLOAD_MASK_AUDIO | AUTODOWNLOAD_MASK_MUSIC | AUTODOWNLOAD_MASK_GIF | AUTODOWNLOAD_MASK_VIDEOMESSAGE); + roamingDownloadMask[a] = preferences.getInt("roamingDownloadMask" + (a == 0 ? "" : a), 0); + } else { + mobileDataDownloadMask[a] = mobileDataDownloadMask[0]; + wifiDownloadMask[a] = wifiDownloadMask[0]; + roamingDownloadMask[a] = roamingDownloadMask[0]; + } + } + for (int a = 0; a < 7; a++) { + int sdefault; + if (a == 1) { + sdefault = 2 * 1024 * 1024; + } else if (a == 6) { + sdefault = 5 * 1024 * 1024; + } else { + sdefault = 10 * 1024 * 1024; + } + mobileMaxFileSize[a] = preferences.getInt("mobileMaxDownloadSize" + a, sdefault); + wifiMaxFileSize[a] = preferences.getInt("wifiMaxDownloadSize" + a, sdefault); + roamingMaxFileSize[a] = preferences.getInt("roamingMaxDownloadSize" + a, sdefault); + } + globalAutodownloadEnabled = preferences.getBoolean("globalAutodownloadEnabled", true); saveToGallery = preferences.getBoolean("save_gallery", false); autoplayGifs = preferences.getBoolean("autoplay_gif", true); raiseToSpeak = preferences.getBoolean("raise_to_speak", true); customTabs = preferences.getBoolean("custom_tabs", true); directShare = preferences.getBoolean("direct_share", true); shuffleMusic = preferences.getBoolean("shuffleMusic", false); + playOrderReversed = preferences.getBoolean("playOrderReversed", false); + inappCamera = preferences.getBoolean("inappCamera", true); + roundCamera16to9 = preferences.getBoolean("roundCamera16to9", true); + groupPhotosEnabled = preferences.getBoolean("groupPhotosEnabled", true); repeatMode = preferences.getInt("repeatMode", 0); AndroidUtilities.runOnUIThread(new Runnable() { @@ -720,12 +899,22 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, }; try { - ApplicationLoader.applicationContext.getContentResolver().registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, false, new GalleryObserverExternal()); + ApplicationLoader.applicationContext.getContentResolver().registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, new GalleryObserverExternal()); } catch (Exception e) { FileLog.e(e); } try { - ApplicationLoader.applicationContext.getContentResolver().registerContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI, false, new GalleryObserverInternal()); + ApplicationLoader.applicationContext.getContentResolver().registerContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI, true, new GalleryObserverInternal()); + } catch (Exception e) { + FileLog.e(e); + } + try { + ApplicationLoader.applicationContext.getContentResolver().registerContentObserver(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true, new GalleryObserverExternal()); + } catch (Exception e) { + FileLog.e(e); + } + try { + ApplicationLoader.applicationContext.getContentResolver().registerContentObserver(MediaStore.Video.Media.INTERNAL_CONTENT_URI, true, new GalleryObserverInternal()); } catch (Exception e) { FileLog.e(e); } @@ -918,27 +1107,73 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, } protected int getAutodownloadMask() { + if (!globalAutodownloadEnabled) { + return 0; + } + int result = 0; + int masksArray[]; + if (ConnectionsManager.isConnectedToWiFi()) { + masksArray = wifiDownloadMask; + } else if (ConnectionsManager.isRoaming()) { + masksArray = roamingDownloadMask; + } else { + masksArray = mobileDataDownloadMask; + } + for (int a = 0; a < 4; a++) { + int mask = 0; + if ((masksArray[a] & AUTODOWNLOAD_MASK_PHOTO) != 0) { + mask |= AUTODOWNLOAD_MASK_PHOTO; + } + if ((masksArray[a] & AUTODOWNLOAD_MASK_AUDIO) != 0) { + mask |= AUTODOWNLOAD_MASK_AUDIO; + } + if ((masksArray[a] & AUTODOWNLOAD_MASK_VIDEOMESSAGE) != 0) { + mask |= AUTODOWNLOAD_MASK_VIDEOMESSAGE; + } + if ((masksArray[a] & AUTODOWNLOAD_MASK_VIDEO) != 0) { + mask |= AUTODOWNLOAD_MASK_VIDEO; + } + if ((masksArray[a] & AUTODOWNLOAD_MASK_DOCUMENT) != 0) { + mask |= AUTODOWNLOAD_MASK_DOCUMENT; + } + if ((masksArray[a] & AUTODOWNLOAD_MASK_MUSIC) != 0) { + mask |= AUTODOWNLOAD_MASK_MUSIC; + } + if ((masksArray[a] & AUTODOWNLOAD_MASK_GIF) != 0) { + mask |= AUTODOWNLOAD_MASK_GIF; + } + result |= mask << (a * 8); + } + return result; + } + + protected int getAutodownloadMaskAll() { + if (!globalAutodownloadEnabled) { + return 0; + } int mask = 0; - if ((mobileDataDownloadMask & AUTODOWNLOAD_MASK_PHOTO) != 0 || (wifiDownloadMask & AUTODOWNLOAD_MASK_PHOTO) != 0 || (roamingDownloadMask & AUTODOWNLOAD_MASK_PHOTO) != 0) { - mask |= AUTODOWNLOAD_MASK_PHOTO; - } - if ((mobileDataDownloadMask & AUTODOWNLOAD_MASK_AUDIO) != 0 || (wifiDownloadMask & AUTODOWNLOAD_MASK_AUDIO) != 0 || (roamingDownloadMask & AUTODOWNLOAD_MASK_AUDIO) != 0) { - mask |= AUTODOWNLOAD_MASK_AUDIO; - } - if ((mobileDataDownloadMask & AUTODOWNLOAD_MASK_VIDEOMESSAGE) != 0 || (wifiDownloadMask & AUTODOWNLOAD_MASK_VIDEOMESSAGE) != 0 || (roamingDownloadMask & AUTODOWNLOAD_MASK_VIDEOMESSAGE) != 0) { - mask |= AUTODOWNLOAD_MASK_VIDEOMESSAGE; - } - if ((mobileDataDownloadMask & AUTODOWNLOAD_MASK_VIDEO) != 0 || (wifiDownloadMask & AUTODOWNLOAD_MASK_VIDEO) != 0 || (roamingDownloadMask & AUTODOWNLOAD_MASK_VIDEO) != 0) { - mask |= AUTODOWNLOAD_MASK_VIDEO; - } - if ((mobileDataDownloadMask & AUTODOWNLOAD_MASK_DOCUMENT) != 0 || (wifiDownloadMask & AUTODOWNLOAD_MASK_DOCUMENT) != 0 || (roamingDownloadMask & AUTODOWNLOAD_MASK_DOCUMENT) != 0) { - mask |= AUTODOWNLOAD_MASK_DOCUMENT; - } - if ((mobileDataDownloadMask & AUTODOWNLOAD_MASK_MUSIC) != 0 || (wifiDownloadMask & AUTODOWNLOAD_MASK_MUSIC) != 0 || (roamingDownloadMask & AUTODOWNLOAD_MASK_MUSIC) != 0) { - mask |= AUTODOWNLOAD_MASK_MUSIC; - } - if ((mobileDataDownloadMask & AUTODOWNLOAD_MASK_GIF) != 0 || (wifiDownloadMask & AUTODOWNLOAD_MASK_GIF) != 0 || (roamingDownloadMask & AUTODOWNLOAD_MASK_GIF) != 0) { - mask |= AUTODOWNLOAD_MASK_GIF; + for (int a = 0; a < 4; a++) { + if ((mobileDataDownloadMask[a] & AUTODOWNLOAD_MASK_PHOTO) != 0 || (wifiDownloadMask[a] & AUTODOWNLOAD_MASK_PHOTO) != 0 || (roamingDownloadMask[a] & AUTODOWNLOAD_MASK_PHOTO) != 0) { + mask |= AUTODOWNLOAD_MASK_PHOTO; + } + if ((mobileDataDownloadMask[a] & AUTODOWNLOAD_MASK_AUDIO) != 0 || (wifiDownloadMask[a] & AUTODOWNLOAD_MASK_AUDIO) != 0 || (roamingDownloadMask[a] & AUTODOWNLOAD_MASK_AUDIO) != 0) { + mask |= AUTODOWNLOAD_MASK_AUDIO; + } + if ((mobileDataDownloadMask[a] & AUTODOWNLOAD_MASK_VIDEOMESSAGE) != 0 || (wifiDownloadMask[a] & AUTODOWNLOAD_MASK_VIDEOMESSAGE) != 0 || (roamingDownloadMask[a] & AUTODOWNLOAD_MASK_VIDEOMESSAGE) != 0) { + mask |= AUTODOWNLOAD_MASK_VIDEOMESSAGE; + } + if ((mobileDataDownloadMask[a] & AUTODOWNLOAD_MASK_VIDEO) != 0 || (wifiDownloadMask[a] & AUTODOWNLOAD_MASK_VIDEO) != 0 || (roamingDownloadMask[a] & AUTODOWNLOAD_MASK_VIDEO) != 0) { + mask |= AUTODOWNLOAD_MASK_VIDEO; + } + if ((mobileDataDownloadMask[a] & AUTODOWNLOAD_MASK_DOCUMENT) != 0 || (wifiDownloadMask[a] & AUTODOWNLOAD_MASK_DOCUMENT) != 0 || (roamingDownloadMask[a] & AUTODOWNLOAD_MASK_DOCUMENT) != 0) { + mask |= AUTODOWNLOAD_MASK_DOCUMENT; + } + if ((mobileDataDownloadMask[a] & AUTODOWNLOAD_MASK_MUSIC) != 0 || (wifiDownloadMask[a] & AUTODOWNLOAD_MASK_MUSIC) != 0 || (roamingDownloadMask[a] & AUTODOWNLOAD_MASK_MUSIC) != 0) { + mask |= AUTODOWNLOAD_MASK_MUSIC; + } + if ((mobileDataDownloadMask[a] & AUTODOWNLOAD_MASK_GIF) != 0 || (wifiDownloadMask[a] & AUTODOWNLOAD_MASK_GIF) != 0 || (roamingDownloadMask[a] & AUTODOWNLOAD_MASK_GIF) != 0) { + mask |= AUTODOWNLOAD_MASK_GIF; + } } return mask; } @@ -1030,7 +1265,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, gifDownloadQueue.clear(); } - int mask = getAutodownloadMask(); + int mask = getAutodownloadMaskAll(); if (mask == 0) { MessagesStorage.getInstance().clearDownloadQueue(0); } else { @@ -1058,17 +1293,88 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, } } - public boolean canDownloadMedia(int type) { - return (getCurrentDownloadMask() & type) != 0; + public boolean canDownloadMedia(MessageObject messageObject) { + return canDownloadMedia(messageObject.messageOwner); + } + + public boolean canDownloadMedia(TLRPC.Message message) { + if (!globalAutodownloadEnabled) { + return false; + } + int type; + if (MessageObject.isPhoto(message)) { + type = MediaController.AUTODOWNLOAD_MASK_PHOTO; + } else if (MessageObject.isVoiceMessage(message)) { + type = MediaController.AUTODOWNLOAD_MASK_AUDIO; + } else if (MessageObject.isRoundVideoMessage(message)) { + type = MediaController.AUTODOWNLOAD_MASK_VIDEOMESSAGE; + } else if (MessageObject.isVideoMessage(message)) { + type = MediaController.AUTODOWNLOAD_MASK_VIDEO; + } else if (MessageObject.isMusicMessage(message)) { + type = MediaController.AUTODOWNLOAD_MASK_MUSIC; + } else if (MessageObject.isGifMessage(message)) { + type = MediaController.AUTODOWNLOAD_MASK_GIF; + } else { + type = MediaController.AUTODOWNLOAD_MASK_DOCUMENT; + } + int mask; + int index; + int maxSize; + TLRPC.Peer peer = message.to_id; + if (peer != null) { + if (peer.user_id != 0) { + if (ContactsController.getInstance().contactsDict.containsKey(peer.user_id)) { + index = 0; + } else { + index = 1; + } + } else if (peer.chat_id != 0) { + index = 2; + } else { + if (MessageObject.isMegagroup(message)) { + index = 2; + } else { + index = 3; + } + } + } else { + index = 1; + } + if (ConnectionsManager.isConnectedToWiFi()) { + mask = wifiDownloadMask[index]; + maxSize = wifiMaxFileSize[maskToIndex(type)]; + } else if (ConnectionsManager.isRoaming()) { + mask = roamingDownloadMask[index]; + maxSize = roamingMaxFileSize[maskToIndex(type)]; + } else { + mask = mobileDataDownloadMask[index]; + maxSize = mobileMaxFileSize[maskToIndex(type)]; + } + return (type == MediaController.AUTODOWNLOAD_MASK_PHOTO || MessageObject.getMessageSize(message) <= maxSize) && (mask & type) != 0; } private int getCurrentDownloadMask() { + if (!globalAutodownloadEnabled) { + return 0; + } if (ConnectionsManager.isConnectedToWiFi()) { - return wifiDownloadMask; + int mask = 0; + for (int a = 0; a < 4; a++) { + mask |= wifiDownloadMask[a]; + } + return mask; } else if (ConnectionsManager.isRoaming()) { - return roamingDownloadMask; + int mask = 0; + for (int a = 0; a < 4; a++) { + mask |= roamingDownloadMask[a]; + } + return mask; } else { - return mobileDataDownloadMask; + int mask = 0; + for (int a = 0; a < 4; a++) { + mask |= mobileDataDownloadMask[a]; + } + return mask; } } @@ -1211,6 +1517,29 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, } } + public void startSmsObserver() { + try { + if (smsObserver == null) { + ApplicationLoader.applicationContext.getContentResolver().registerContentObserver(Uri.parse("content://sms"), false, smsObserver = new SmsObserver()); + } + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + try { + if (smsObserver != null) { + ApplicationLoader.applicationContext.getContentResolver().unregisterContentObserver(smsObserver); + smsObserver = null; + } + } catch (Exception e) { + FileLog.e(e); + } + } + }, 5 * 60 * 1000); + } catch (Exception e) { + FileLog.e(e); + } + } + public void stopMediaObserver() { if (stopMediaObserverRunnable == null) { stopMediaObserverRunnable = new StopMediaObserverRunnable(); @@ -1367,7 +1696,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, private void processLaterArrays() { for (HashMap.Entry listener : addLaterArray.entrySet()) { - addLoadingFileObserver(listener.getKey(), listener.getValue()); //TODO + addLoadingFileObserver(listener.getKey(), listener.getValue()); } addLaterArray.clear(); for (FileDownloadProgressListener listener : deleteLaterArray) { @@ -1463,19 +1792,28 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, for (int a = 0; a < delayedMessages.size(); a++) { SendMessagesHelper.DelayedMessage delayedMessage = delayedMessages.get(a); if (delayedMessage.encryptedChat == null) { - long dialog_id = delayedMessage.obj.getDialogId(); - Long lastTime = typingTimes.get(dialog_id); - if (lastTime == null || lastTime + 4000 < System.currentTimeMillis()) { - if (MessageObject.isRoundVideoDocument(delayedMessage.documentLocation)) { - MessagesController.getInstance().sendTyping(dialog_id, 8, 0); - } else if (MessageObject.isVideoDocument(delayedMessage.documentLocation)) { - MessagesController.getInstance().sendTyping(dialog_id, 5, 0); - } else if (delayedMessage.documentLocation != null) { - MessagesController.getInstance().sendTyping(dialog_id, 3, 0); - } else if (delayedMessage.location != null) { + long dialog_id = delayedMessage.peer; + if (delayedMessage.type == 4) { + Long lastTime = typingTimes.get(dialog_id); + if (lastTime == null || lastTime + 4000 < System.currentTimeMillis()) { MessagesController.getInstance().sendTyping(dialog_id, 4, 0); + typingTimes.put(dialog_id, System.currentTimeMillis()); + } + } else { + Long lastTime = typingTimes.get(dialog_id); + TLRPC.Document document = delayedMessage.obj.getDocument(); + if (lastTime == null || lastTime + 4000 < System.currentTimeMillis()) { + if (delayedMessage.obj.isRoundVideo()) { + MessagesController.getInstance().sendTyping(dialog_id, 8, 0); + } else if (delayedMessage.obj.isVideo()) { + MessagesController.getInstance().sendTyping(dialog_id, 5, 0); + } else if (delayedMessage.obj.getDocument() != null) { + MessagesController.getInstance().sendTyping(dialog_id, 3, 0); + } else if (delayedMessage.location != null) { + MessagesController.getInstance().sendTyping(dialog_id, 4, 0); + } + typingTimes.put(dialog_id, System.currentTimeMillis()); } - typingTimes.put(dialog_id, System.currentTimeMillis()); } } } @@ -2189,14 +2527,24 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, currentPlaylistNum = 0; } if (loadMusic) { - SharedMediaQuery.loadMusic(current.getDialogId(), playlist.get(0).getId()); + SharedMediaQuery.loadMusic(current.getDialogId(), playlist.get(0).getIdWithChannel()); } } return playMessage(current); } public void playNextMessage() { - playNextMessage(false); + playNextMessageWithoutOrder(false); + } + + public boolean findMessageInPlaylistAndPlay(MessageObject messageObject) { + int index = playlist.indexOf(messageObject); + if (index == -1) { + return playMessage(messageObject); + } else { + playMessageAtIndex(index); + } + return true; } public void playMessageAtIndex(int index) { @@ -2208,7 +2556,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, playMessage(playlist.get(currentPlaylistNum)); } - private void playNextMessage(boolean byStop) { + private void playNextMessageWithoutOrder(boolean byStop) { ArrayList currentPlayList = shuffleMusic ? shuffledPlaylist : playlist; if (byStop && repeatMode == 2 && !forceLoopCurrentPlaylist) { @@ -2216,67 +2564,78 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, playMessage(currentPlayList.get(currentPlaylistNum)); return; } - currentPlaylistNum++; - if (currentPlaylistNum >= currentPlayList.size()) { - currentPlaylistNum = 0; - if (byStop && repeatMode == 0 && !forceLoopCurrentPlaylist) { - if (audioPlayer != null || audioTrackPlayer != null || videoPlayer != null) { - if (audioPlayer != null) { - try { - audioPlayer.reset(); - } catch (Exception e) { - FileLog.e(e); - } - try { - audioPlayer.stop(); - } catch (Exception e) { - FileLog.e(e); - } - try { - audioPlayer.release(); - } catch (Exception e) { - FileLog.e(e); - } - audioPlayer = null; - } else if (audioTrackPlayer != null) { - synchronized (playerObjectSync) { - try { - audioTrackPlayer.pause(); - audioTrackPlayer.flush(); - } catch (Exception e) { - FileLog.e(e); - } - try { - audioTrackPlayer.release(); - } catch (Exception e) { - FileLog.e(e); - } - audioTrackPlayer = null; - } - } else if (videoPlayer != null) { - currentAspectRatioFrameLayout = null; - currentTextureViewContainer = null; - currentAspectRatioFrameLayoutReady = false; - currentTextureView = null; - videoPlayer.releasePlayer(); - videoPlayer = null; - try { - baseActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } catch (Exception e) { - FileLog.e(e); - } - } - stopProgressTimer(); - lastProgress = 0; - buffersWrited = 0; - isPaused = true; - playingMessageObject.audioProgress = 0.0f; - playingMessageObject.audioProgressSec = 0; - NotificationCenter.getInstance().postNotificationName(NotificationCenter.messagePlayingProgressDidChanged, playingMessageObject.getId(), 0); - NotificationCenter.getInstance().postNotificationName(NotificationCenter.messagePlayingPlayStateChanged, playingMessageObject.getId()); - } - return; + + boolean last = false; + if (playOrderReversed) { + currentPlaylistNum++; + if (currentPlaylistNum >= currentPlayList.size()) { + currentPlaylistNum = 0; + last = true; } + } else { + currentPlaylistNum--; + if (currentPlaylistNum < 0) { + currentPlaylistNum = currentPlayList.size() - 1; + last = true; + } + } + if (last && byStop && repeatMode == 0 && !forceLoopCurrentPlaylist) { + if (audioPlayer != null || audioTrackPlayer != null || videoPlayer != null) { + if (audioPlayer != null) { + try { + audioPlayer.reset(); + } catch (Exception e) { + FileLog.e(e); + } + try { + audioPlayer.stop(); + } catch (Exception e) { + FileLog.e(e); + } + try { + audioPlayer.release(); + } catch (Exception e) { + FileLog.e(e); + } + audioPlayer = null; + } else if (audioTrackPlayer != null) { + synchronized (playerObjectSync) { + try { + audioTrackPlayer.pause(); + audioTrackPlayer.flush(); + } catch (Exception e) { + FileLog.e(e); + } + try { + audioTrackPlayer.release(); + } catch (Exception e) { + FileLog.e(e); + } + audioTrackPlayer = null; + } + } else if (videoPlayer != null) { + currentAspectRatioFrameLayout = null; + currentTextureViewContainer = null; + currentAspectRatioFrameLayoutReady = false; + currentTextureView = null; + videoPlayer.releasePlayer(); + videoPlayer = null; + try { + baseActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } catch (Exception e) { + FileLog.e(e); + } + } + stopProgressTimer(); + lastProgress = 0; + buffersWrited = 0; + isPaused = true; + playingMessageObject.audioProgress = 0.0f; + playingMessageObject.audioProgressSec = 0; + NotificationCenter.getInstance().postNotificationName(NotificationCenter.messagePlayingProgressDidChanged, playingMessageObject.getId(), 0); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.messagePlayingPlayStateChanged, playingMessageObject.getId()); + } + return; } if (currentPlaylistNum < 0 || currentPlaylistNum >= currentPlayList.size()) { return; @@ -2296,9 +2655,16 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, return; } - currentPlaylistNum--; - if (currentPlaylistNum < 0) { - currentPlaylistNum = currentPlayList.size() - 1; + if (playOrderReversed) { + currentPlaylistNum--; + if (currentPlaylistNum < 0) { + currentPlaylistNum = currentPlayList.size() - 1; + } + } else { + currentPlaylistNum++; + if (currentPlaylistNum >= currentPlayList.size()) { + currentPlaylistNum = 0; + } } if (currentPlaylistNum < 0 || currentPlaylistNum >= currentPlayList.size()) { return; @@ -2334,13 +2700,25 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, if (currentPlayList == null || currentPlayList.size() < 2) { return; } - int nextIndex = currentPlaylistNum + 1; - if (nextIndex >= currentPlayList.size()) { - nextIndex = 0; + int nextIndex; + if (playOrderReversed) { + nextIndex = currentPlaylistNum + 1; + if (nextIndex >= currentPlayList.size()) { + nextIndex = 0; + } + } else { + nextIndex = currentPlaylistNum - 1; + if (nextIndex < 0) { + nextIndex = currentPlayList.size() - 1; + } } + MessageObject nextAudio = currentPlayList.get(nextIndex); + if (!canDownloadMedia(nextAudio)) { + return; + } File file = null; - if (nextAudio.messageOwner.attachPath != null && nextAudio.messageOwner.attachPath.length() > 0) { + if (!TextUtils.isEmpty(nextAudio.messageOwner.attachPath)) { file = new File(nextAudio.messageOwner.attachPath); if (!file.exists()) { file = null; @@ -2481,7 +2859,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, } return true; } - if (!messageObject.isOut() && messageObject.isContentUnread() && messageObject.messageOwner.to_id.channel_id == 0) { + if (!messageObject.isOut() && messageObject.isContentUnread()) { MessagesController.getInstance().markMessageContentAsRead(messageObject); } boolean notify = !playMusicAgain; @@ -2592,7 +2970,6 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, } } - @TargetApi(16) @Override public boolean onSurfaceDestroyed(SurfaceTexture surfaceTexture) { if (videoPlayer == null) { @@ -2727,7 +3104,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, @Override public void onCompletion(MediaPlayer mediaPlayer) { if (!playlist.isEmpty() && playlist.size() > 1) { - playNextMessage(true); + playNextMessageWithoutOrder(true); } else { cleanupPlayer(true, true, messageObject != null && messageObject.isVoice()); } @@ -2895,26 +3272,38 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, return shuffleMusic; } + public boolean isPlayOrderReversed() { + return playOrderReversed; + } + public int getRepeatMode() { return repeatMode; } - public void toggleShuffleMusic() { - shuffleMusic = !shuffleMusic; + public void toggleShuffleMusic(int type) { + boolean oldShuffle = shuffleMusic; + if (type == 2) { + shuffleMusic = !shuffleMusic; + } else { + playOrderReversed = !playOrderReversed; + } SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); editor.putBoolean("shuffleMusic", shuffleMusic); + editor.putBoolean("playOrderReversed", playOrderReversed); editor.commit(); - if (shuffleMusic) { - buildShuffledPlayList(); - currentPlaylistNum = 0; - } else { - if (playingMessageObject != null) { - currentPlaylistNum = playlist.indexOf(playingMessageObject); - if (currentPlaylistNum == -1) { - playlist.clear(); - shuffledPlaylist.clear(); - cleanupPlayer(true, true); + if (oldShuffle != shuffleMusic) { + if (shuffleMusic) { + buildShuffledPlayList(); + currentPlaylistNum = 0; + } else { + if (playingMessageObject != null) { + currentPlaylistNum = playlist.indexOf(playingMessageObject); + if (currentPlaylistNum == -1) { + playlist.clear(); + shuffledPlaylist.clear(); + cleanupPlayer(true, true); + } } } } @@ -2983,8 +3372,22 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, return currentAspectRatioFrameLayout != null && currentAspectRatioFrameLayout.isDrawingReady(); } + public ArrayList getPlaylist() { + return playlist; + } + public boolean isPlayingMessage(MessageObject messageObject) { - return !(audioTrackPlayer == null && audioPlayer == null && videoPlayer == null || messageObject == null || playingMessageObject == null || playingMessageObject != null && (playingMessageObject.eventId != messageObject.eventId || playingMessageObject.getId() != messageObject.getId() || downloadingCurrentMessage)); + if (audioTrackPlayer == null && audioPlayer == null && videoPlayer == null || messageObject == null || playingMessageObject == null) { + return false; + } + if (playingMessageObject.eventId != 0 && playingMessageObject.eventId == playingMessageObject.eventId) { + return !downloadingCurrentMessage; + } + if (playingMessageObject.getDialogId() == messageObject.getDialogId() && playingMessageObject.getId() == messageObject.getId()) { + return !downloadingCurrentMessage; + } + // + return false; } public boolean isMessagePaused() { @@ -3004,7 +3407,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, try { Vibrator v = (Vibrator) ApplicationLoader.applicationContext.getSystemService(Context.VIBRATOR_SERVICE); - v.vibrate(50); + v.vibrate(10); //NotificationsController.getInstance().playRecordSound(); } catch (Exception e) { FileLog.e(e); @@ -3204,7 +3607,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, } try { Vibrator v = (Vibrator) ApplicationLoader.applicationContext.getSystemService(Context.VIBRATOR_SERVICE); - v.vibrate(50); + v.vibrate(10); } catch (Exception e) { FileLog.e(e); } @@ -3236,10 +3639,10 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, } final File sourceFile = file; - final boolean[] cancelled = new boolean[1]; + final boolean[] cancelled = new boolean[] {false}; if (sourceFile.exists()) { AlertDialog progressDialog = null; - if (context != null) { + if (context != null && type != 0) { try { progressDialog = new AlertDialog(context, 2); progressDialog.setMessage(LocaleController.getString("Loading", R.string.Loading)); @@ -3470,7 +3873,9 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, name = String.format(Locale.US, "%d.%s", id, ext); } inputStream = ApplicationLoader.applicationContext.getContentResolver().openInputStream(uri); - File f = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), name); + File f = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), "sharing/"); + f.mkdirs(); + f = new File(f, name); output = new FileOutputStream(f); byte[] buffer = new byte[1024 * 20]; int len; @@ -3540,6 +3945,30 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, editor.commit(); } + public void toggleInappCamera() { + inappCamera = !inappCamera; + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.putBoolean("direct_share", inappCamera); + editor.commit(); + } + + public void toggleRoundCamera16to9() { + roundCamera16to9 = !roundCamera16to9; + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.putBoolean("roundCamera16to9", roundCamera16to9); + editor.commit(); + } + + public void toggleGroupPhotosEnabled() { + groupPhotosEnabled = !groupPhotosEnabled; + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.putBoolean("groupPhotosEnabled", groupPhotosEnabled); + editor.commit(); + } + public void checkSaveToGalleryFiles() { try { File telegramPath = new File(Environment.getExternalStorageDirectory(), "Telegram"); @@ -3588,6 +4017,18 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, return directShare; } + public boolean canInAppCamera() { + return inappCamera; + } + + public boolean canRoundCamera16to9() { + return roundCamera16to9; + } + + public boolean isGroupPhotosEnabled() { + return groupPhotosEnabled; + } + public static void loadGalleryPhotosAlbums(final int guid) { Thread thread = new Thread(new Runnable() { @Override @@ -3896,7 +4337,6 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, return lastColorFormat; } - @TargetApi(16) private int selectTrack(MediaExtractor extractor, boolean audio) { int numTracks = extractor.getTrackCount(); for (int i = 0; i < numTracks; i++) { @@ -3942,7 +4382,6 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, }); } - @TargetApi(16) private long readAndWriteTrack(final MessageObject messageObject, MediaExtractor extractor, MP4Builder mediaMuxer, MediaCodec.BufferInfo info, long start, long end, File file, boolean isAudio) throws Exception { int trackIndex = selectTrack(extractor, isAudio); if (trackIndex >= 0) { @@ -4074,7 +4513,6 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, } } - @TargetApi(16) private boolean convertVideo(final MessageObject messageObject) { String videoPath = messageObject.videoEditedInfo.originalPath; long startTime = messageObject.videoEditedInfo.startTime; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessageKeyData.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessageKeyData.java index f6ce59246..143ada8f6 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessageKeyData.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessageKeyData.java @@ -15,7 +15,7 @@ public class MessageKeyData { public byte[] aesKey; public byte[] aesIv; - public static MessageKeyData generateMessageKeyData(byte[] authKey, byte[] messageKey, boolean incoming) { + public static MessageKeyData generateMessageKeyData(byte[] authKey, byte[] messageKey, boolean incoming, int version) { MessageKeyData keyData = new MessageKeyData(); if (authKey == null || authKey.length == 0) { keyData.aesIv = null; @@ -25,46 +25,78 @@ public class MessageKeyData { int x = incoming ? 8 : 0; - SerializedData data = new SerializedData(); - data.writeBytes(messageKey); - data.writeBytes(authKey, x, 32); - byte[] sha1_a = Utilities.computeSHA1(data.toByteArray()); - data.cleanup(); + switch (version) { + case 2: { + SerializedData data = new SerializedData(); + data.writeBytes(messageKey, 0, 16); + data.writeBytes(authKey, x, 36); + byte[] sha256_a = Utilities.computeSHA256(data.toByteArray()); + data.cleanup(); - data = new SerializedData(); - data.writeBytes(authKey, 32 + x, 16); - data.writeBytes(messageKey); - data.writeBytes(authKey, 48 + x, 16); - byte[] sha1_b = Utilities.computeSHA1(data.toByteArray()); - data.cleanup(); + data = new SerializedData(); + data.writeBytes(authKey, 40 + x, 36); + data.writeBytes(messageKey, 0, 16); + byte[] sha256_b = Utilities.computeSHA256(data.toByteArray()); + data.cleanup(); - data = new SerializedData(); - data.writeBytes(authKey, 64 + x, 32); - data.writeBytes(messageKey); - byte[] sha1_c = Utilities.computeSHA1(data.toByteArray()); - data.cleanup(); + data = new SerializedData(); + data.writeBytes(sha256_a, 0, 8); + data.writeBytes(sha256_b, 8, 16); + data.writeBytes(sha256_a, 24, 8); + keyData.aesKey = data.toByteArray(); + data.cleanup(); - data = new SerializedData(); - data.writeBytes(messageKey); - data.writeBytes(authKey, 96 + x, 32); - byte[] sha1_d = Utilities.computeSHA1(data.toByteArray()); - data.cleanup(); + data = new SerializedData(); + data.writeBytes(sha256_b, 0, 8); + data.writeBytes(sha256_a, 8, 16); + data.writeBytes(sha256_b, 24, 8); + keyData.aesIv = data.toByteArray(); + data.cleanup(); + break; + } + case 1: { + SerializedData data = new SerializedData(); + data.writeBytes(messageKey); + data.writeBytes(authKey, x, 32); + byte[] sha1_a = Utilities.computeSHA1(data.toByteArray()); + data.cleanup(); - data = new SerializedData(); - data.writeBytes(sha1_a, 0, 8); - data.writeBytes(sha1_b, 8, 12); - data.writeBytes(sha1_c, 4, 12); - keyData.aesKey = data.toByteArray(); - data.cleanup(); + data = new SerializedData(); + data.writeBytes(authKey, 32 + x, 16); + data.writeBytes(messageKey); + data.writeBytes(authKey, 48 + x, 16); + byte[] sha1_b = Utilities.computeSHA1(data.toByteArray()); + data.cleanup(); - data = new SerializedData(); - data.writeBytes(sha1_a, 8, 12); - data.writeBytes(sha1_b, 0, 8); - data.writeBytes(sha1_c, 16, 4); - data.writeBytes(sha1_d, 0, 8); - keyData.aesIv = data.toByteArray(); - data.cleanup(); + data = new SerializedData(); + data.writeBytes(authKey, 64 + x, 32); + data.writeBytes(messageKey); + byte[] sha1_c = Utilities.computeSHA1(data.toByteArray()); + data.cleanup(); + data = new SerializedData(); + data.writeBytes(messageKey); + data.writeBytes(authKey, 96 + x, 32); + byte[] sha1_d = Utilities.computeSHA1(data.toByteArray()); + data.cleanup(); + + data = new SerializedData(); + data.writeBytes(sha1_a, 0, 8); + data.writeBytes(sha1_b, 8, 12); + data.writeBytes(sha1_c, 4, 12); + keyData.aesKey = data.toByteArray(); + data.cleanup(); + + data = new SerializedData(); + data.writeBytes(sha1_a, 8, 12); + data.writeBytes(sha1_b, 0, 8); + data.writeBytes(sha1_c, 16, 4); + data.writeBytes(sha1_d, 0, 8); + keyData.aesIv = data.toByteArray(); + data.cleanup(); + break; + } + } return keyData; } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java index b87cb7ed3..c97ef3bd7 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java @@ -48,6 +48,7 @@ public class MessageObject { public static final int MESSAGE_SEND_STATE_SENT = 0; public static final int MESSAGE_SEND_STATE_SEND_ERROR = 2; + public long localGroupId; public TLRPC.Message messageOwner; public CharSequence messageText; public CharSequence linkDescription; @@ -76,6 +77,8 @@ public class MessageObject { public boolean useCustomPhoto; public StringBuilder botButtonsLayout; + public TLRPC.TL_channelAdminLogEvent currentEvent; + public boolean forceUpdate; public int lastLineWidth; @@ -95,6 +98,7 @@ public class MessageObject { public int charactersOffset; public int charactersEnd; public int height; + public int heightByOffset; public byte directionFlags; public boolean isRtl() { @@ -102,6 +106,411 @@ public class MessageObject { } } + public static final int POSITION_FLAG_LEFT = 1; + public static final int POSITION_FLAG_RIGHT = 2; + public static final int POSITION_FLAG_TOP = 4; + public static final int POSITION_FLAG_BOTTOM = 8; + + public static class GroupedMessagePosition { + public byte minX; + public byte maxX; + public byte minY; + public byte maxY; + public int pw; + public float ph; + public float aspectRatio; + public boolean last; + public int spanSize; + public int leftSpanOffset; + public boolean edge; + public int flags; + public float siblingHeights[]; + + public void set(int minX, int maxX, int minY, int maxY, int w, float h, int flags) { + this.minX = (byte) minX; + this.maxX = (byte) maxX; + this.minY = (byte) minY; + this.maxY = (byte) maxY; + this.pw = w; + this.spanSize = w; + this.ph = h; + this.flags = (byte) flags; + } + } + + public static class GroupedMessages { + public long groupId; + public boolean hasSibling; + public ArrayList messages = new ArrayList<>(); + public ArrayList posArray = new ArrayList<>(); + public HashMap positions = new HashMap<>(); + + private int maxSizeWidth = 800; + private int firstSpanAdditionalSize = 200; + + private class MessageGroupedLayoutAttempt { + + public int[] lineCounts; + public float[] heights; + + public MessageGroupedLayoutAttempt(int i1, int i2, float f1, float f2) { + lineCounts = new int[] {i1, i2}; + heights = new float[] {f1, f2}; + } + + public MessageGroupedLayoutAttempt(int i1, int i2, int i3, float f1, float f2, float f3) { + lineCounts = new int[] {i1, i2, i3}; + heights = new float[] {f1, f2, f3}; + } + + public MessageGroupedLayoutAttempt(int i1, int i2, int i3, int i4, float f1, float f2, float f3, float f4) { + lineCounts = new int[] {i1, i2, i3, i4}; + heights = new float[] {f1, f2, f3, f4}; + } + } + + private float multiHeight(float array[], int start, int end) { + float sum = 0; + for (int a = start; a < end; a++) { + sum += array[a]; + } + return maxSizeWidth / sum; + } + + public void calculate() { + posArray.clear(); + positions.clear(); + int count = messages.size(); + if (count <= 1) { + return; + } + + float maxSizeHeight = 814.0f; + StringBuilder proportions = new StringBuilder(); + float averageAspectRatio = 1.0f; + boolean isOut = false; + int maxX = 0; + boolean forceCalc = false; + boolean needShare = false; + hasSibling = false; + + for (int a = 0; a < count; a++) { + MessageObject messageObject = messages.get(a); + if (a == 0) { + isOut = messageObject.isOutOwner(); + needShare = !isOut && ( + messageObject.messageOwner.fwd_from != null && messageObject.messageOwner.fwd_from.saved_from_peer != null || + messageObject.messageOwner.from_id > 0 && (messageObject.messageOwner.to_id.channel_id != 0 || messageObject.messageOwner.to_id.chat_id != 0 || + messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGame || messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaInvoice) + ); + } + TLRPC.PhotoSize photoSize = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, AndroidUtilities.getPhotoSize()); + GroupedMessagePosition position = new GroupedMessagePosition(); + position.last = a == count - 1; + position.aspectRatio = photoSize == null ? 1.0f : photoSize.w / (float) photoSize.h; + + if (position.aspectRatio > 1.2f) { + proportions.append("w"); + } else if (position.aspectRatio < 0.8f) { + proportions.append("n"); + } else { + proportions.append("q"); + } + + averageAspectRatio += position.aspectRatio; + + if (position.aspectRatio > 2.0f) { + forceCalc = true; + } + + positions.put(messageObject, position); + posArray.add(position); + } + + if (needShare) { + maxSizeWidth -= 50; + firstSpanAdditionalSize += 50; + } + + int minHeight = AndroidUtilities.dp(120); + int minWidth = (int) (AndroidUtilities.dp(120) / (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) / (float) maxSizeWidth)); + int paddingsWidth = (int) (AndroidUtilities.dp(40) / (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) / (float) maxSizeWidth)); + + float maxAspectRatio = maxSizeWidth / maxSizeHeight; + averageAspectRatio = averageAspectRatio / count; + + if (!forceCalc && (count == 2 || count == 3 || count == 4)) { + if (count == 2) { + GroupedMessagePosition position1 = posArray.get(0); + GroupedMessagePosition position2 = posArray.get(1); + String pString = proportions.toString(); + if (pString.equals("ww") && averageAspectRatio > 1.4 * maxAspectRatio && position1.aspectRatio - position2.aspectRatio < 0.2) { + float height = Math.round(Math.min(maxSizeWidth / position1.aspectRatio, Math.min(maxSizeWidth / position2.aspectRatio, maxSizeHeight / 2.0f))) / maxSizeHeight; + position1.set(0, 0, 0, 0, maxSizeWidth, height, POSITION_FLAG_LEFT | POSITION_FLAG_RIGHT | POSITION_FLAG_TOP); + position2.set(0, 0, 1, 1, maxSizeWidth, height, POSITION_FLAG_LEFT | POSITION_FLAG_RIGHT | POSITION_FLAG_BOTTOM); + } else if (pString.equals("ww") || pString.equals("qq")) { + int width = maxSizeWidth / 2; + float height = Math.round(Math.min(width / position1.aspectRatio, Math.min(width / position2.aspectRatio, maxSizeHeight))) / maxSizeHeight; + position1.set(0, 0, 0, 0, width, height, POSITION_FLAG_LEFT | POSITION_FLAG_BOTTOM | POSITION_FLAG_TOP); + position2.set(1, 1, 0, 0, width, height, POSITION_FLAG_RIGHT | POSITION_FLAG_BOTTOM | POSITION_FLAG_TOP); + maxX = 1; + } else { + int secondWidth = (int) Math.max(0.4f * maxSizeWidth, Math.round((maxSizeWidth / position1.aspectRatio / (1.0f / position1.aspectRatio + 1.0f / position2.aspectRatio)))); + int firstWidth = maxSizeWidth - secondWidth; + if (firstWidth < minWidth) { + int diff = minWidth - firstWidth; + firstWidth = minWidth; + secondWidth -= diff; + } + + float height = Math.min(maxSizeHeight, Math.round(Math.min(firstWidth / position1.aspectRatio, secondWidth / position2.aspectRatio))) / maxSizeHeight; + position1.set(0, 0, 0, 0, firstWidth, height, POSITION_FLAG_LEFT | POSITION_FLAG_BOTTOM | POSITION_FLAG_TOP); + position2.set(1, 1, 0, 0, secondWidth, height, POSITION_FLAG_RIGHT | POSITION_FLAG_BOTTOM | POSITION_FLAG_TOP); + maxX = 1; + } + } else if (count == 3) { + GroupedMessagePosition position1 = posArray.get(0); + GroupedMessagePosition position2 = posArray.get(1); + GroupedMessagePosition position3 = posArray.get(2); + if (proportions.charAt(0) == 'n') { + float thirdHeight = Math.min(maxSizeHeight * 0.5f, Math.round(position2.aspectRatio * maxSizeWidth / (position3.aspectRatio + position2.aspectRatio))); + float secondHeight = maxSizeHeight - thirdHeight; + int rightWidth = (int) Math.max(minWidth, Math.min(maxSizeWidth * 0.5f, Math.round(Math.min(thirdHeight * position3.aspectRatio, secondHeight * position2.aspectRatio)))); + + int leftWidth = Math.round(Math.min(maxSizeHeight * position1.aspectRatio + paddingsWidth, maxSizeWidth - rightWidth)); + position1.set(0, 0, 0, 1, leftWidth, 1.0f, POSITION_FLAG_LEFT | POSITION_FLAG_BOTTOM | POSITION_FLAG_TOP); + + position2.set(1, 1, 0, 0, rightWidth, secondHeight / maxSizeHeight, POSITION_FLAG_RIGHT | POSITION_FLAG_TOP); + + position3.set(0, 1, 1, 1, rightWidth, thirdHeight / maxSizeHeight, POSITION_FLAG_RIGHT | POSITION_FLAG_BOTTOM); + position3.spanSize = maxSizeWidth; + + position1.siblingHeights = new float[] {thirdHeight / maxSizeHeight, secondHeight / maxSizeHeight}; + + if (isOut) { + position1.spanSize = maxSizeWidth - rightWidth; + } else { + position2.spanSize = maxSizeWidth - leftWidth; + position3.leftSpanOffset = leftWidth; + } + hasSibling = true; + maxX = 1; + } else { + float firstHeight = Math.round(Math.min(maxSizeWidth / position1.aspectRatio, (maxSizeHeight) * 0.66f)) / maxSizeHeight; + position1.set(0, 1, 0, 0, maxSizeWidth, firstHeight, POSITION_FLAG_LEFT | POSITION_FLAG_RIGHT | POSITION_FLAG_TOP); + + int width = maxSizeWidth / 2; + float secondHeight = Math.min(maxSizeHeight - firstHeight, Math.round(Math.min(width / position2.aspectRatio, width / position3.aspectRatio))) / maxSizeHeight; + position2.set(0, 0, 1, 1, width, secondHeight, POSITION_FLAG_LEFT | POSITION_FLAG_BOTTOM); + position3.set(1, 1, 1, 1, width, secondHeight, POSITION_FLAG_RIGHT | POSITION_FLAG_BOTTOM); + maxX = 1; + } + } else if (count == 4) { + GroupedMessagePosition position1 = posArray.get(0); + GroupedMessagePosition position2 = posArray.get(1); + GroupedMessagePosition position3 = posArray.get(2); + GroupedMessagePosition position4 = posArray.get(3); + if (proportions.charAt(0) == 'w') { + float h0 = Math.round(Math.min(maxSizeWidth / position1.aspectRatio, maxSizeHeight * 0.66f)) / maxSizeHeight; + position1.set(0, 2, 0, 0, maxSizeWidth, h0, POSITION_FLAG_LEFT | POSITION_FLAG_RIGHT | POSITION_FLAG_TOP); + + float h = Math.round(maxSizeWidth / (position2.aspectRatio + position3.aspectRatio + position4.aspectRatio)); + int w0 = (int) Math.max(minWidth, Math.min(maxSizeWidth * 0.4f, h * position2.aspectRatio)); + int w2 = (int) Math.max(Math.max(minWidth, maxSizeWidth * 0.33f), h * position4.aspectRatio); + int w1 = maxSizeWidth - w0 - w2; + h = Math.min(maxSizeHeight - h0, h); + h /= maxSizeHeight; + position2.set(0, 0, 1, 1, w0, h, POSITION_FLAG_LEFT | POSITION_FLAG_BOTTOM); + position3.set(1, 1, 1, 1, w1, h, POSITION_FLAG_BOTTOM); + position4.set(2, 2, 1, 1, w2, h, POSITION_FLAG_RIGHT | POSITION_FLAG_BOTTOM); + maxX = 2; + } else { + int w = Math.max(minWidth, Math.round(maxSizeHeight / (1.0f / position2.aspectRatio + 1.0f / position3.aspectRatio + 1.0f / posArray.get(3).aspectRatio))); + float h0 = Math.min(0.33f, Math.max(minHeight, w / position2.aspectRatio) / maxSizeHeight); + float h1 = Math.min(0.33f, Math.max(minHeight, w / position3.aspectRatio) / maxSizeHeight); + float h2 = 1.0f - h0 - h1; + int w0 = Math.round(Math.min(maxSizeHeight * position1.aspectRatio + paddingsWidth, maxSizeWidth - w)); + + position1.set(0, 0, 0, 2, w0, h0 + h1 + h2, POSITION_FLAG_LEFT | POSITION_FLAG_TOP | POSITION_FLAG_BOTTOM); + + position2.set(1, 1, 0, 0, w, h0, POSITION_FLAG_RIGHT | POSITION_FLAG_TOP); + + position3.set(0, 1, 1, 1, w, h1, POSITION_FLAG_RIGHT); + position3.spanSize = maxSizeWidth; + + position4.set(0, 1, 2, 2, w, h2, POSITION_FLAG_RIGHT | POSITION_FLAG_BOTTOM); + position4.spanSize = maxSizeWidth; + + if (isOut) { + position1.spanSize = maxSizeWidth - w; + } else { + position2.spanSize = maxSizeWidth - w0; + position3.leftSpanOffset = w0; + position4.leftSpanOffset = w0; + } + position1.siblingHeights = new float[] {h0, h1, h2}; + hasSibling = true; + maxX = 1; + } + } + } else { + float croppedRatios[] = new float[posArray.size()]; + for (int a = 0; a < count; a++) { + if (averageAspectRatio > 1.1f) { + croppedRatios[a] = Math.max(1.0f, posArray.get(a).aspectRatio); + } else { + croppedRatios[a] = Math.min(1.0f, posArray.get(a).aspectRatio); + } + croppedRatios[a] = Math.max(0.66667f, Math.min(1.7f, croppedRatios[a])); + } + + int firstLine; + int secondLine; + int thirdLine; + int fourthLine; + ArrayList attempts = new ArrayList<>(); + for (firstLine = 1; firstLine < croppedRatios.length; firstLine++) { + secondLine = croppedRatios.length - firstLine; + if (firstLine > 3 || secondLine > 3) { + continue; + } + attempts.add(new MessageGroupedLayoutAttempt(firstLine, secondLine, multiHeight(croppedRatios, 0, firstLine), multiHeight(croppedRatios, firstLine, croppedRatios.length))); + } + + for (firstLine = 1; firstLine < croppedRatios.length - 1; firstLine++) { + for (secondLine = 1; secondLine < croppedRatios.length - firstLine; secondLine++) { + thirdLine = croppedRatios.length - firstLine - secondLine; + if (firstLine > 3 || secondLine > (averageAspectRatio < 0.85f ? 4 : 3) || thirdLine > 3) { + continue; + } + attempts.add(new MessageGroupedLayoutAttempt(firstLine, secondLine, thirdLine, multiHeight(croppedRatios, 0, firstLine), multiHeight(croppedRatios, firstLine, firstLine + secondLine), multiHeight(croppedRatios, firstLine + secondLine, croppedRatios.length))); + } + } + + for (firstLine = 1; firstLine < croppedRatios.length - 2; firstLine++) { + for (secondLine = 1; secondLine < croppedRatios.length - firstLine; secondLine++) { + for (thirdLine = 1; thirdLine < croppedRatios.length - firstLine - secondLine; thirdLine++) { + fourthLine = croppedRatios.length - firstLine - secondLine - thirdLine; + if (firstLine > 3 || secondLine > 3 || thirdLine > 3 || fourthLine > 3) { + continue; + } + attempts.add(new MessageGroupedLayoutAttempt(firstLine, secondLine, thirdLine, fourthLine, multiHeight(croppedRatios, 0, firstLine), multiHeight(croppedRatios, firstLine, firstLine + secondLine), multiHeight(croppedRatios, firstLine + secondLine, firstLine + secondLine + thirdLine), multiHeight(croppedRatios, firstLine + secondLine + thirdLine, croppedRatios.length))); + } + } + } + + MessageGroupedLayoutAttempt optimal = null; + float optimalDiff = 0.0f; + float maxHeight = maxSizeWidth / 3 * 4; + for (int a = 0; a < attempts.size(); a++) { + MessageGroupedLayoutAttempt attempt = attempts.get(a); + float height = 0; + float minLineHeight = Float.MAX_VALUE; + for (int b = 0; b < attempt.heights.length; b++){ + height += attempt.heights[b]; + if (attempt.heights[b] < minLineHeight) { + minLineHeight = attempt.heights[b]; + } + } + + float diff = Math.abs(height - maxHeight); + if (attempt.lineCounts.length > 1) { + if (attempt.lineCounts[0] > attempt.lineCounts[1] || (attempt.lineCounts.length > 2 && attempt.lineCounts[1] > attempt.lineCounts[2]) || (attempt.lineCounts.length > 3 && attempt.lineCounts[2] > attempt.lineCounts[3])) { + diff *= 1.2f; + } + } + + if (minLineHeight < minWidth) { + diff *= 1.5f; + } + + if (optimal == null || diff < optimalDiff) { + optimal = attempt; + optimalDiff = diff; + } + } + if (optimal == null) { + return; + } + + int index = 0; + float y = 0.0f; + + for (int i = 0; i < optimal.lineCounts.length; i++) { + int c = optimal.lineCounts[i]; + float lineHeight = optimal.heights[i]; + int spanLeft = maxSizeWidth; + GroupedMessagePosition posToFix = null; + maxX = Math.max(maxX, c - 1); + for (int k = 0; k < c; k++) { + float ratio = croppedRatios[index]; + int width = (int) (ratio * lineHeight); + spanLeft -= width; + GroupedMessagePosition pos = posArray.get(index); + int flags = 0; + if (i == 0) { + flags |= POSITION_FLAG_TOP; + } + if (i == optimal.lineCounts.length - 1) { + flags |= POSITION_FLAG_BOTTOM; + } + if (k == 0) { + flags |= POSITION_FLAG_LEFT; + if (isOut) { + posToFix = pos; + } + } + if (k == c - 1) { + flags |= POSITION_FLAG_RIGHT; + if (!isOut) { + posToFix = pos; + } + } + pos.set(k, k, i, i, width, lineHeight / maxSizeHeight, flags); + index++; + } + posToFix.pw += spanLeft; + posToFix.spanSize += spanLeft; + y += lineHeight; + } + } + int avatarOffset = 108; + for (int a = 0; a < count; a++) { + GroupedMessagePosition pos = posArray.get(a); + if (isOut) { + if (pos.minX == 0) { + pos.spanSize += firstSpanAdditionalSize; + } + if ((pos.flags & POSITION_FLAG_RIGHT) != 0) { + pos.edge = true; + } + } else { + if (pos.maxX == maxX || (pos.flags & POSITION_FLAG_RIGHT) != 0) { + pos.spanSize += firstSpanAdditionalSize; + } + if ((pos.flags & POSITION_FLAG_LEFT) != 0) { + pos.edge = true; + } + } + MessageObject messageObject = messages.get(a); + if (!isOut && messageObject.needDrawAvatar()) { + if (pos.edge) { + if (pos.spanSize != 1000) { + pos.spanSize += avatarOffset; + } + pos.pw += avatarOffset; + } else if ((pos.flags & POSITION_FLAG_RIGHT) != 0) { + if (pos.spanSize != 1000) { + pos.spanSize -= avatarOffset; + } else if (pos.leftSpanOffset != 0) { + pos.leftSpanOffset += avatarOffset; + } + } + } + } + } + } + private static final int LINES_PER_BLOCK = 10; public ArrayList textLayoutBlocks; @@ -136,7 +545,9 @@ public class MessageObject { if (message instanceof TLRPC.TL_messageService) { if (message.action != null) { - if (message.action instanceof TLRPC.TL_messageActionChatCreate) { + if (message.action instanceof TLRPC.TL_messageActionCustomAction) { + messageText = message.action.message; + } else if (message.action instanceof TLRPC.TL_messageActionChatCreate) { if (isOut()) { messageText = LocaleController.getString("ActionYouCreateGroup", R.string.ActionYouCreateGroup); } else { @@ -400,6 +811,8 @@ public class MessageObject { messageText = LocaleController.getString("AttachRound", R.string.AttachRound); } else if (message.media instanceof TLRPC.TL_messageMediaGeo || message.media instanceof TLRPC.TL_messageMediaVenue) { messageText = LocaleController.getString("AttachLocation", R.string.AttachLocation); + } else if (message.media instanceof TLRPC.TL_messageMediaGeoLive) { + messageText = LocaleController.getString("AttachLiveLocation", R.string.AttachLiveLocation); } else if (message.media instanceof TLRPC.TL_messageMediaContact) { messageText = LocaleController.getString("AttachContact", R.string.AttachContact); } else if (message.media instanceof TLRPC.TL_messageMediaGame) { @@ -498,31 +911,12 @@ public class MessageObject { checkMediaExistance(); } - public MessageObject(TLRPC.TL_channelAdminLogEvent event, ArrayList messageObjects, HashMap> messagesByDays, TLRPC.Chat chat, int[] mid) { - TLRPC.User fromUser = null; - if (event.user_id > 0) { - if (fromUser == null) { - fromUser = MessagesController.getInstance().getUser(event.user_id); - } - } - - Calendar rightNow = new GregorianCalendar(); - rightNow.setTimeInMillis((long) (event.date) * 1000); - int dateDay = rightNow.get(Calendar.DAY_OF_YEAR); - int dateYear = rightNow.get(Calendar.YEAR); - int dateMonth = rightNow.get(Calendar.MONTH); - dateKey = String.format("%d_%02d_%02d", dateYear, dateMonth, dateDay); - monthKey = String.format("%d_%02d", dateYear, dateMonth); - + private void createDateArray(TLRPC.TL_channelAdminLogEvent event, ArrayList messageObjects, HashMap> messagesByDays) { ArrayList dayArray = messagesByDays.get(dateKey); - - TLRPC.Peer to_id = new TLRPC.TL_peerChannel(); - to_id.channel_id = chat.id; - if (dayArray == null) { dayArray = new ArrayList<>(); messagesByDays.put(dateKey, dayArray); - TLRPC.Message dateMsg = new TLRPC.Message(); + TLRPC.TL_message dateMsg = new TLRPC.TL_message(); dateMsg.message = LocaleController.formatDateChat(event.date); dateMsg.id = 0; dateMsg.date = event.date; @@ -532,6 +926,27 @@ public class MessageObject { dateObj.isDateObject = true; messageObjects.add(dateObj); } + } + + public MessageObject(TLRPC.TL_channelAdminLogEvent event, ArrayList messageObjects, HashMap> messagesByDays, TLRPC.Chat chat, int[] mid) { + TLRPC.User fromUser = null; + if (event.user_id > 0) { + if (fromUser == null) { + fromUser = MessagesController.getInstance().getUser(event.user_id); + } + } + currentEvent = event; + + Calendar rightNow = new GregorianCalendar(); + rightNow.setTimeInMillis((long) (event.date) * 1000); + int dateDay = rightNow.get(Calendar.DAY_OF_YEAR); + int dateYear = rightNow.get(Calendar.YEAR); + int dateMonth = rightNow.get(Calendar.MONTH); + dateKey = String.format("%d_%02d_%02d", dateYear, dateMonth, dateDay); + monthKey = String.format("%d_%02d", dateYear, dateMonth); + + TLRPC.Peer to_id = new TLRPC.TL_peerChannel(); + to_id.channel_id = chat.id; TLRPC.Message message = null; if (event.action instanceof TLRPC.TL_channelAdminLogEventActionChangeTitle) { @@ -662,9 +1077,9 @@ public class MessageObject { TLRPC.TL_channelBannedRights n = event.action.new_participant.banned_rights; if (chat.megagroup && (n == null || !n.view_messages || n != null && o != null && n.until_date != o.until_date)) { StringBuilder rights; - String bannedDuration; + StringBuilder bannedDuration; if (n != null && !AndroidUtilities.isBannedForever(n.until_date)) { - bannedDuration = ""; + bannedDuration = new StringBuilder(); int duration = n.until_date - event.date; int days = duration / 60 / 60 / 24; duration -= days * 60 * 60 * 24; @@ -692,20 +1107,20 @@ public class MessageObject { } if (addStr != null) { if (bannedDuration.length() > 0) { - bannedDuration += ", "; + bannedDuration.append(", "); } - bannedDuration += addStr; + bannedDuration.append(addStr); } if (count == 2) { break; } } } else { - bannedDuration = LocaleController.getString("UserRestrictionsUntilForever", R.string.UserRestrictionsUntilForever); + bannedDuration = new StringBuilder(LocaleController.getString("UserRestrictionsUntilForever", R.string.UserRestrictionsUntilForever)); } String str = LocaleController.getString("EventLogRestrictedUntil", R.string.EventLogRestrictedUntil); int offset = str.indexOf("%1$s"); - rights = new StringBuilder(String.format(str, getUserName(whoUser, messageOwner.entities, offset), bannedDuration)); + rights = new StringBuilder(String.format(str, getUserName(whoUser, messageOwner.entities, offset), bannedDuration.toString())); boolean added = false; if (o == null) { o = new TLRPC.TL_channelBannedRights(); @@ -783,6 +1198,12 @@ public class MessageObject { } } else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionDeleteMessage) { messageText = replaceWithLink(LocaleController.getString("EventLogDeletedMessages", R.string.EventLogDeletedMessages), "un1", fromUser); + } else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionTogglePreHistoryHidden) { + if (((TLRPC.TL_channelAdminLogEventActionTogglePreHistoryHidden) event.action).new_value) { + messageText = replaceWithLink(LocaleController.getString("EventLogToggledInvitesHistoryOff", R.string.EventLogToggledInvitesHistoryOff), "un1", fromUser); + } else { + messageText = replaceWithLink(LocaleController.getString("EventLogToggledInvitesHistoryOn", R.string.EventLogToggledInvitesHistoryOn), "un1", fromUser); + } } else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionChangeAbout) { messageText = replaceWithLink(chat.megagroup ? LocaleController.getString("EventLogEditedGroupDescription", R.string.EventLogEditedGroupDescription) : LocaleController.getString("EventLogEditedChannelDescription", R.string.EventLogEditedChannelDescription), "un1", fromUser); message = new TLRPC.TL_message(); @@ -867,6 +1288,14 @@ public class MessageObject { message.media.webpage.flags = 10; message.media.webpage.display_url = ""; message.media.webpage.url = ""; + } else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionChangeStickerSet) { + TLRPC.InputStickerSet newStickerset = ((TLRPC.TL_channelAdminLogEventActionChangeStickerSet) event.action).new_stickerset; + TLRPC.InputStickerSet oldStickerset = ((TLRPC.TL_channelAdminLogEventActionChangeStickerSet) event.action).new_stickerset; + if (newStickerset == null || newStickerset instanceof TLRPC.TL_inputStickerSetEmpty) { + messageText = replaceWithLink(LocaleController.getString("EventLogRemovedStickersSet", R.string.EventLogRemovedStickersSet), "un1", fromUser); + } else { + messageText = replaceWithLink(LocaleController.getString("EventLogChangedStickersSet", R.string.EventLogChangedStickersSet), "un1", fromUser); + } } else { messageText = "unsupported " + event.action; } @@ -900,9 +1329,19 @@ public class MessageObject { message.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; } MessageObject messageObject = new MessageObject(message, null, null, true, eventId); - messageObjects.add(messageObjects.size() - 1, messageObject); + if (messageObject.contentType >= 0) { + createDateArray(event, messageObjects, messagesByDays); + messageObjects.add(messageObjects.size() - 1, messageObject); + } else { + contentType = -1; + } + } + if (contentType >= 0) { + createDateArray(event, messageObjects, messagesByDays); + messageObjects.add(messageObjects.size() - 1, this); + } else { + return; } - messageObjects.add(messageObjects.size() - 1, this); if (messageText == null) { messageText = ""; @@ -1033,6 +1472,10 @@ public class MessageObject { } } + public boolean hasValidReplyMessageObject() { + return !(replyMessageObject == null || replyMessageObject.messageOwner instanceof TLRPC.TL_messageEmpty || replyMessageObject.messageOwner.action instanceof TLRPC.TL_messageActionHistoryClear); + } + public void generatePaymentSentMessageText(TLRPC.User fromUser) { if (fromUser == null) { fromUser = MessagesController.getInstance().getUser((int) getDialogId()); @@ -1059,7 +1502,7 @@ public class MessageObject { chat = MessagesController.getInstance().getChat(messageOwner.to_id.channel_id); } } - if (replyMessageObject == null) { + if (replyMessageObject == null || replyMessageObject.messageOwner instanceof TLRPC.TL_messageEmpty || replyMessageObject.messageOwner.action instanceof TLRPC.TL_messageActionHistoryClear) { messageText = replaceWithLink(LocaleController.getString("ActionPinnedNoText", R.string.ActionPinnedNoText), "un1", fromUser != null ? fromUser : chat); } else { if (replyMessageObject.isMusic()) { @@ -1078,6 +1521,8 @@ public class MessageObject { messageText = replaceWithLink(LocaleController.getString("ActionPinnedFile", R.string.ActionPinnedFile), "un1", fromUser != null ? fromUser : chat); } else if (replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeo) { messageText = replaceWithLink(LocaleController.getString("ActionPinnedGeo", R.string.ActionPinnedGeo), "un1", fromUser != null ? fromUser : chat); + } else if (replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeoLive) { + messageText = replaceWithLink(LocaleController.getString("ActionPinnedGeoLive", R.string.ActionPinnedGeoLive), "un1", fromUser != null ? fromUser : chat); } else if (replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaContact) { messageText = replaceWithLink(LocaleController.getString("ActionPinnedContact", R.string.ActionPinnedContact), "un1", fromUser != null ? fromUser : chat); } else if (replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaPhoto) { @@ -1098,6 +1543,90 @@ public class MessageObject { } } + private TLRPC.Photo getPhotoWithId(TLRPC.WebPage webPage, long id) { + if (webPage == null || webPage.cached_page == null) { + return null; + } + if (webPage.photo != null && webPage.photo.id == id) { + return webPage.photo; + } + for (int a = 0; a < webPage.cached_page.photos.size(); a++) { + TLRPC.Photo photo = webPage.cached_page.photos.get(a); + if (photo.id == id) { + return photo; + } + } + return null; + } + + private TLRPC.Document getDocumentWithId(TLRPC.WebPage webPage, long id) { + if (webPage == null || webPage.cached_page == null) { + return null; + } + if (webPage.document != null && webPage.document.id == id) { + return webPage.document; + } + for (int a = 0; a < webPage.cached_page.documents.size(); a++) { + TLRPC.Document document = webPage.cached_page.documents.get(a); + if (document.id == id) { + return document; + } + } + return null; + } + + private MessageObject getMessageObjectForBlock(TLRPC.WebPage webPage, TLRPC.PageBlock pageBlock) { + TLRPC.TL_message message = null; + if (pageBlock instanceof TLRPC.TL_pageBlockPhoto) { + TLRPC.Photo photo = getPhotoWithId(webPage, pageBlock.photo_id); + if (photo == webPage.photo) { + return this; + } + message = new TLRPC.TL_message(); + message.media = new TLRPC.TL_messageMediaPhoto(); + message.media.photo = photo; + } else if (pageBlock instanceof TLRPC.TL_pageBlockVideo) { + TLRPC.Document document = getDocumentWithId(webPage, pageBlock.video_id); + if (document == webPage.document) { + return this; + } + message = new TLRPC.TL_message(); + message.media = new TLRPC.TL_messageMediaDocument(); + message.media.document = getDocumentWithId(webPage, pageBlock.video_id); + } + message.message = ""; + message.id = Utilities.random.nextInt(); + message.date = messageOwner.date; + message.to_id = messageOwner.to_id; + message.out = messageOwner.out; + message.from_id = messageOwner.from_id; + return new MessageObject(message, null, false); + } + + public ArrayList getWebPagePhotos(ArrayList array, ArrayList blocksToSearch) { + TLRPC.WebPage webPage = messageOwner.media.webpage; + ArrayList messageObjects = array == null ? new ArrayList() : array; + if (webPage.cached_page == null) { + return messageObjects; + } + ArrayList blocks = blocksToSearch == null ? webPage.cached_page.blocks : blocksToSearch; + for (int a = 0; a < blocks.size(); a++) { + TLRPC.PageBlock block = blocks.get(a); + if (block instanceof TLRPC.TL_pageBlockSlideshow) { + TLRPC.TL_pageBlockSlideshow slideshow = (TLRPC.TL_pageBlockSlideshow) block; + for (int b = 0; b < slideshow.items.size(); b++) { + messageObjects.add(getMessageObjectForBlock(webPage, slideshow.items.get(b))); + } + } else if (block instanceof TLRPC.TL_pageBlockCollage) { + TLRPC.TL_pageBlockCollage slideshow = (TLRPC.TL_pageBlockCollage) block; + for (int b = 0; b < slideshow.items.size(); b++) { + messageObjects.add(getMessageObjectForBlock(webPage, slideshow.items.get(b))); + } + } + } + return messageObjects; + } + public void measureInlineBotButtons() { wantedBotKeyboardWidth = 0; if (!(messageOwner.reply_markup instanceof TLRPC.TL_replyInlineMarkup)) { @@ -1149,7 +1678,7 @@ public class MessageObject { type = 10; } else if (messageOwner.media instanceof TLRPC.TL_messageMediaPhoto) { type = 1; - } else if (messageOwner.media instanceof TLRPC.TL_messageMediaGeo || messageOwner.media instanceof TLRPC.TL_messageMediaVenue) { + } else if (messageOwner.media instanceof TLRPC.TL_messageMediaGeo || messageOwner.media instanceof TLRPC.TL_messageMediaVenue || messageOwner.media instanceof TLRPC.TL_messageMediaGeoLive) { type = 4; } else if (isRoundVideo()) { type = 5; @@ -1609,10 +2138,10 @@ public class MessageObject { } public void generateCaption() { - if (caption != null) { + if (caption != null || isRoundVideo()) { return; } - if (messageOwner.media != null && messageOwner.media.caption != null && messageOwner.media.caption.length() > 0) { + if (messageOwner.media != null && !TextUtils.isEmpty(messageOwner.media.caption)) { caption = Emoji.replaceEmoji(messageOwner.media.caption, Theme.chat_msgTextPaint.getFontMetricsInt(), AndroidUtilities.dp(20), false); if (containsUrls(caption)) { try { @@ -1654,6 +2183,13 @@ public class MessageObject { } } + public boolean hasValidGroupId() { + return getGroupId() != 0 && photoThumbs != null && !photoThumbs.isEmpty(); + } + + public long getGroupId() { + return localGroupId != 0 ? localGroupId : messageOwner.grouped_id; + } public static void addLinks(boolean isOut, CharSequence messageText) { addLinks(isOut, messageText, true); @@ -1679,7 +2215,7 @@ public class MessageObject { } public void generateLayout(TLRPC.User fromUser) { - if (type != 0 || messageOwner.to_id == null || messageText == null || messageText.length() == 0) { + if (type != 0 || messageOwner.to_id == null || TextUtils.isEmpty(messageText)) { return; } @@ -1725,10 +2261,14 @@ public class MessageObject { } } + boolean hasUrls = false; if (messageText instanceof Spannable) { Spannable spannable = (Spannable) messageText; int count = messageOwner.entities.size(); URLSpan[] spans = spannable.getSpans(0, messageText.length(), URLSpan.class); + if (spans != null && spans.length > 0) { + hasUrls = true; + } for (int a = 0; a < count; a++) { TLRPC.MessageEntity entity = messageOwner.entities.get(a); if (entity.length <= 0 || entity.offset < 0 || entity.offset >= messageOwner.message.length()) { @@ -1775,6 +2315,7 @@ public class MessageObject { } else if (entity instanceof TLRPC.TL_messageEntityEmail) { spannable.setSpan(new URLSpanReplacement("mailto:" + url), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else if (entity instanceof TLRPC.TL_messageEntityUrl) { + hasUrls = true; if (!url.toLowerCase().startsWith("http") && !url.toLowerCase().startsWith("tg://")) { spannable.setSpan(new URLSpan("http://" + url), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else { @@ -1788,9 +2329,12 @@ public class MessageObject { } int maxWidth; - boolean needShare = eventId == 0 && messageOwner.from_id > 0 && (messageOwner.to_id.channel_id != 0 || messageOwner.to_id.chat_id != 0 || messageOwner.media instanceof TLRPC.TL_messageMediaGame || messageOwner.media instanceof TLRPC.TL_messageMediaInvoice) && !isOut(); + boolean needShare = eventId == 0 && !isOutOwner() && ( + messageOwner.fwd_from != null && (messageOwner.fwd_from.saved_from_peer != null || messageOwner.fwd_from.from_id != 0 || messageOwner.fwd_from.channel_id != 0) || + messageOwner.from_id > 0 && (messageOwner.to_id.channel_id != 0 || messageOwner.to_id.chat_id != 0 || messageOwner.media instanceof TLRPC.TL_messageMediaGame || messageOwner.media instanceof TLRPC.TL_messageMediaInvoice) + ); generatedWithMinSize = AndroidUtilities.isTablet() ? AndroidUtilities.getMinTabletSide() : AndroidUtilities.displaySize.x; - maxWidth = generatedWithMinSize - AndroidUtilities.dp(needShare || eventId != 0 ? 122 : 80); + maxWidth = generatedWithMinSize - AndroidUtilities.dp(needShare || eventId != 0 ? 132 : 80); if (fromUser != null && fromUser.bot || (isMegagroup() || messageOwner.fwd_from != null && messageOwner.fwd_from.channel_id != 0) && !isOut()) { maxWidth -= AndroidUtilities.dp(20); } @@ -1808,7 +2352,7 @@ public class MessageObject { } try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + if (hasUrls && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { textLayout = StaticLayout.Builder.obtain(messageText, 0, messageText.length(), paint, maxWidth) .setBreakStrategy(StaticLayout.BREAK_STRATEGY_HIGH_QUALITY) .setHyphenationFrequency(StaticLayout.HYPHENATION_FREQUENCY_NONE) @@ -1847,8 +2391,8 @@ public class MessageObject { block.charactersOffset = startCharacter; block.charactersEnd = endCharacter; try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - block.textLayout = StaticLayout.Builder.obtain(messageText, startCharacter, endCharacter, paint, maxWidth) + if (hasUrls && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + block.textLayout = StaticLayout.Builder.obtain(messageText, startCharacter, endCharacter, paint, maxWidth + AndroidUtilities.dp(2)) .setBreakStrategy(StaticLayout.BREAK_STRATEGY_HIGH_QUALITY) .setHyphenationFrequency(StaticLayout.HYPHENATION_FREQUENCY_NONE) .setAlignment(Layout.Alignment.ALIGN_NORMAL) @@ -1983,11 +2527,21 @@ public class MessageObject { } public boolean isOutOwner() { - return messageOwner.out && messageOwner.from_id > 0 && !messageOwner.post; + if (!messageOwner.out || messageOwner.from_id <= 0 || messageOwner.post) { + return false; + } + if (messageOwner.fwd_from == null) { + return true; + } + int selfUserId = UserConfig.getClientUserId(); + if (getDialogId() == selfUserId) { + return messageOwner.fwd_from.from_id == selfUserId || messageOwner.fwd_from.saved_from_peer != null && messageOwner.fwd_from.saved_from_peer.user_id == selfUserId; + } + return messageOwner.fwd_from.saved_from_peer == null || messageOwner.fwd_from.saved_from_peer.user_id == selfUserId; } public boolean needDrawAvatar() { - return isFromUser() || eventId != 0; + return isFromUser() || eventId != 0 || messageOwner.fwd_from != null && messageOwner.fwd_from.saved_from_peer != null; } public boolean isFromUser() { @@ -2029,6 +2583,32 @@ public class MessageObject { return messageOwner.id; } + public static int getMessageSize(TLRPC.Message message) { + if (message.media != null && message.media.document != null) { + return message.media.document.size; + } + return 0; + } + + public int getSize() { + return getMessageSize(messageOwner); + } + + public long getIdWithChannel() { + long id = messageOwner.id; + if (messageOwner.to_id != null && messageOwner.to_id.channel_id != 0) { + id |= ((long) messageOwner.to_id.channel_id) << 32; + } + return id; + } + + public int getChannelId() { + if (messageOwner.to_id != null) { + return messageOwner.to_id.channel_id; + } + return 0; + } + public static boolean shouldEncryptPhotoOrVideo(TLRPC.Message message) { return message instanceof TLRPC.TL_message && (message.media instanceof TLRPC.TL_messageMediaPhoto || message.media instanceof TLRPC.TL_messageMediaDocument) && message.media.ttl_seconds != 0 || message instanceof TLRPC.TL_message_secret && (message.media instanceof TLRPC.TL_messageMediaPhoto || isVideoMessage(message)) && message.ttl > 0 && message.ttl <= 60; @@ -2045,12 +2625,12 @@ public class MessageObject { public boolean isSecretPhoto() { return messageOwner instanceof TLRPC.TL_message && (messageOwner.media instanceof TLRPC.TL_messageMediaPhoto || messageOwner.media instanceof TLRPC.TL_messageMediaDocument) && messageOwner.media.ttl_seconds != 0 || - messageOwner instanceof TLRPC.TL_message_secret && (messageOwner.media instanceof TLRPC.TL_messageMediaPhoto || isRoundVideo() || isVideo()) && messageOwner.ttl > 0 && messageOwner.ttl <= 60; + messageOwner instanceof TLRPC.TL_message_secret && ((messageOwner.media instanceof TLRPC.TL_messageMediaPhoto || isVideo()) && messageOwner.ttl > 0 && messageOwner.ttl <= 60 || isRoundVideo()); } public boolean isSecretMedia() { return messageOwner instanceof TLRPC.TL_message && (messageOwner.media instanceof TLRPC.TL_messageMediaPhoto || messageOwner.media instanceof TLRPC.TL_messageMediaDocument) && messageOwner.media.ttl_seconds != 0 || - messageOwner instanceof TLRPC.TL_message_secret && ((messageOwner.media instanceof TLRPC.TL_messageMediaPhoto || isRoundVideo()) && messageOwner.ttl > 0 && messageOwner.ttl <= 60 || isVoice() || isVideo()); + messageOwner instanceof TLRPC.TL_message_secret && ((messageOwner.media instanceof TLRPC.TL_messageMediaPhoto) && messageOwner.ttl > 0 && messageOwner.ttl <= 60 || isVoice() || isRoundVideo() || isVideo()); } public static void setUnreadFlags(TLRPC.Message message, int flag) { @@ -2070,6 +2650,14 @@ public class MessageObject { return isMegagroup(messageOwner); } + public boolean isSavedFromMegagroup() { + if (messageOwner.fwd_from != null && messageOwner.fwd_from.saved_from_peer != null && messageOwner.fwd_from.saved_from_peer.channel_id != 0) { + TLRPC.Chat chat = MessagesController.getInstance().getChat(messageOwner.fwd_from.saved_from_peer.channel_id); + return ChatObject.isMegagroup(chat); + } + return false; + } + public static boolean isMegagroup(TLRPC.Message message) { return (message.flags & TLRPC.MESSAGE_FLAG_MEGAGROUP) != 0; } @@ -2254,6 +2842,10 @@ public class MessageObject { return message.media != null && message.media.document != null && isMusicDocument(message.media.document); } + public static boolean isGifMessage(TLRPC.Message message) { + return message.media != null && message.media.document != null && isGifDocument(message.media.document); + } + public static boolean isRoundVideoMessage(TLRPC.Message message) { if (message.media instanceof TLRPC.TL_messageMediaWebPage) { return isRoundVideoDocument(message.media.webpage.document); @@ -2261,6 +2853,13 @@ public class MessageObject { return message.media != null && message.media.document != null && isRoundVideoDocument(message.media.document); } + public static boolean isPhoto(TLRPC.Message message) { + if (message.media instanceof TLRPC.TL_messageMediaWebPage) { + return message.media.webpage.photo instanceof TLRPC.TL_photo; + } + return message.media instanceof TLRPC.TL_messageMediaPhoto; + } + public static boolean isVoiceMessage(TLRPC.Message message) { if (message.media instanceof TLRPC.TL_messageMediaWebPage) { return isVoiceDocument(message.media.webpage.document); @@ -2275,6 +2874,10 @@ public class MessageObject { return message.media != null && message.media.document != null && isNewGifDocument(message.media.document); } + public static boolean isLiveLocationMessage(TLRPC.Message message) { + return message.media instanceof TLRPC.TL_messageMediaGeoLive; + } + public static boolean isVideoMessage(TLRPC.Message message) { if (message.media instanceof TLRPC.TL_messageMediaWebPage) { return isVideoDocument(message.media.webpage.document); @@ -2442,6 +3045,10 @@ public class MessageObject { return isVideoMessage(messageOwner); } + public boolean isLiveLocation() { + return isLiveLocationMessage(messageOwner); + } + public boolean isGame() { return isGameMessage(messageOwner); } @@ -2462,7 +3069,7 @@ public class MessageObject { } public boolean isGif() { - return messageOwner.media instanceof TLRPC.TL_messageMediaDocument && isGifDocument(messageOwner.media.document); + return isGifMessage(messageOwner); } public boolean isWebpageDocument() { @@ -2593,6 +3200,10 @@ public class MessageObject { return isForwardedMessage(messageOwner); } + public boolean needDrawForwarded() { + return (messageOwner.flags & TLRPC.MESSAGE_FLAG_FWD) != 0 && messageOwner.fwd_from != null && messageOwner.fwd_from.saved_from_peer == null && UserConfig.getClientUserId() != getDialogId(); + } + public static boolean isForwardedMessage(TLRPC.Message message) { return (message.flags & TLRPC.MESSAGE_FLAG_FWD) != 0 && message.fwd_from != null; } @@ -2613,11 +3224,44 @@ public class MessageObject { return canEditMessage(messageOwner, chat); } - public static boolean canEditMessage(TLRPC.Message message, TLRPC.Chat chat) { - if (message == null || message.to_id == null || message.media != null && isRoundVideoDocument(message.media.document) || message.action != null && !(message.action instanceof TLRPC.TL_messageActionEmpty) || isForwardedMessage(message) || message.via_bot_id != 0 || message.id < 0) { + public boolean canEditMessageAnytime(TLRPC.Chat chat) { + return canEditMessageAnytime(messageOwner, chat); + } + + public static boolean canEditMessageAnytime(TLRPC.Message message, TLRPC.Chat chat) { + if (message == null || message.to_id == null || message.media != null && (isRoundVideoDocument(message.media.document) || isStickerDocument(message.media.document)) || message.action != null && !(message.action instanceof TLRPC.TL_messageActionEmpty) || isForwardedMessage(message) || message.via_bot_id != 0 || message.id < 0) { return false; } - if (message.from_id == message.to_id.user_id && message.from_id == UserConfig.getClientUserId()) { + if (message.from_id == message.to_id.user_id && message.from_id == UserConfig.getClientUserId() && !isLiveLocationMessage(message)) { + return true; + } + if (chat == null && message.to_id.channel_id != 0) { + chat = MessagesController.getInstance().getChat(message.to_id.channel_id); + if (chat == null) { + return false; + } + } + if (message.out && chat != null && chat.megagroup && (chat.creator || chat.admin_rights != null && chat.admin_rights.pin_messages)) { + return true; + } + // + return false; + } + + public static boolean canEditMessage(TLRPC.Message message, TLRPC.Chat chat) { + if (message == null || message.to_id == null || message.media != null && (isRoundVideoDocument(message.media.document) || isStickerDocument(message.media.document)) || message.action != null && !(message.action instanceof TLRPC.TL_messageActionEmpty) || isForwardedMessage(message) || message.via_bot_id != 0 || message.id < 0) { + return false; + } + if (message.from_id == message.to_id.user_id && message.from_id == UserConfig.getClientUserId() && !isLiveLocationMessage(message)) { + return true; + } + if (chat == null && message.to_id.channel_id != 0) { + chat = MessagesController.getInstance().getChat(message.to_id.channel_id); + if (chat == null) { + return false; + } + } + if (message.out && chat != null && chat.megagroup && (chat.creator || chat.admin_rights != null && chat.admin_rights.pin_messages)) { return true; } if (Math.abs(message.date - ConnectionsManager.getInstance().getCurrentTime()) > MessagesController.getInstance().maxEditTime) { @@ -2630,13 +3274,7 @@ public class MessageObject { message.media instanceof TLRPC.TL_messageMediaWebPage || message.media == null); } - if (chat == null && message.to_id.channel_id != 0) { - chat = MessagesController.getInstance().getChat(message.to_id.channel_id); - if (chat == null) { - return false; - } - } - if (chat.megagroup && message.out || !chat.megagroup && (chat.creator || chat.admin_rights != null && chat.admin_rights.edit_messages) && message.post) { + if (chat.megagroup && message.out || !chat.megagroup && (chat.creator || chat.admin_rights != null && (chat.admin_rights.edit_messages || message.out)) && message.post) { if (message.media instanceof TLRPC.TL_messageMediaPhoto || message.media instanceof TLRPC.TL_messageMediaDocument && !isStickerMessage(message) || message.media instanceof TLRPC.TL_messageMediaEmpty || @@ -2660,7 +3298,7 @@ public class MessageObject { chat = MessagesController.getInstance().getChat(message.to_id.channel_id); } if (ChatObject.isChannel(chat)) { - return message.id != 1 && (chat.creator || chat.admin_rights != null && chat.admin_rights.delete_messages || chat.megagroup && isOut(message) && message.from_id > 0); + return message.id != 1 && (chat.creator || chat.admin_rights != null && (chat.admin_rights.delete_messages || message.out) || chat.megagroup && message.out && message.from_id > 0); } return isOut(message) || !ChatObject.isChannel(chat); } @@ -2682,6 +3320,35 @@ public class MessageObject { return null; } + public int getFromId() { + if (messageOwner.fwd_from != null && messageOwner.fwd_from.saved_from_peer != null) { + if (messageOwner.fwd_from.saved_from_peer.user_id != 0) { + if (messageOwner.fwd_from.from_id != 0) { + return messageOwner.fwd_from.from_id; + } else { + return messageOwner.fwd_from.saved_from_peer.user_id; + } + } else if (messageOwner.fwd_from.saved_from_peer.channel_id != 0) { + if (isSavedFromMegagroup() && messageOwner.fwd_from.from_id != 0) { + return messageOwner.fwd_from.from_id; + } else { + return -messageOwner.fwd_from.saved_from_peer.channel_id; + } + } else if (messageOwner.fwd_from.saved_from_peer.chat_id != 0) { + if (messageOwner.fwd_from.from_id != 0) { + return messageOwner.fwd_from.from_id; + } else { + return -messageOwner.fwd_from.saved_from_peer.chat_id; + } + } + } else if (messageOwner.from_id != 0) { + return messageOwner.from_id; + } else if (messageOwner.post) { + return messageOwner.to_id.channel_id; + } + return 0; + } + public void checkMediaExistance() { File cacheFile = null; attachPathExists = false; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java index 05558a64d..d5cb4bb16 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java @@ -59,7 +59,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter private HashMap exportedChats = new HashMap<>(); + public ArrayList hintDialogs = new ArrayList<>(); public ArrayList dialogs = new ArrayList<>(); + public ArrayList dialogsForward = new ArrayList<>(); public ArrayList dialogsServerOnly = new ArrayList<>(); public ArrayList dialogsGroupsOnly = new ArrayList<>(); public int nextDialogsCacheOffset; @@ -88,7 +90,6 @@ public class MessagesController implements NotificationCenter.NotificationCenter public ArrayList blockedUsers = new ArrayList<>(); private SparseArray> channelViewsToSend = new SparseArray<>(); - private SparseArray> channelViewsToReload = new SparseArray<>(); private long lastViewsCheckTime; private HashMap> updatesQueueChannels = new HashMap<>(); @@ -112,6 +113,8 @@ public class MessagesController implements NotificationCenter.NotificationCenter private ArrayList loadingFullParticipants = new ArrayList<>(); private ArrayList loadedFullParticipants = new ArrayList<>(); private ArrayList loadedFullChats = new ArrayList<>(); + private HashMap> channelAdmins = new HashMap<>(); + private SparseIntArray loadingChannelAdmins = new SparseIntArray(); private HashMap> reloadingWebpages = new HashMap<>(); private HashMap> reloadingWebpagesPending = new HashMap<>(); @@ -129,9 +132,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter public boolean dialogsEndReached; public boolean serverDialogsEndReached; public boolean gettingDifference; + private boolean getDifferenceFirstSync = true; public boolean updatingState; public boolean firstGettingTask; public boolean registeringForPush; + private boolean resetingDialogs; + private TLRPC.TL_messages_peerDialogs resetDialogsPinned; + private TLRPC.messages_Dialogs resetDialogsAll; public int secretWebpagePreview = 2; @@ -144,7 +151,6 @@ public class MessagesController implements NotificationCenter.NotificationCenter public boolean enableJoined = true; public boolean allowBigEmoji; public boolean useSystemEmoji; - public boolean callsEnabled; public String linkPrefix = "t.me"; public int fontSize = AndroidUtilities.dp(16); public int maxGroupCount = 200; @@ -155,12 +161,15 @@ public class MessagesController implements NotificationCenter.NotificationCenter public int groupBigSize; public int ratingDecay; public int maxRecentStickersCount = 30; + public int maxFaveStickersCount = 5; public int maxRecentGifsCount = 200; public int callReceiveTimeout = 20000; public int callRingTimeout = 90000; public int callConnectTimeout = 30000; public int callPacketTimeout = 10000; public int maxPinnedDialogsCount = 5; + public boolean defaultP2pContacts = false; + private String installReferer; private ArrayList disabledFeatures = new ArrayList<>(); @@ -266,12 +275,19 @@ public class MessagesController implements NotificationCenter.NotificationCenter public MessagesController() { ImageLoader.getInstance(); MessagesStorage.getInstance(); - NotificationCenter.getInstance().addObserver(this, NotificationCenter.FileDidUpload); - NotificationCenter.getInstance().addObserver(this, NotificationCenter.FileDidFailUpload); - NotificationCenter.getInstance().addObserver(this, NotificationCenter.FileDidLoaded); - NotificationCenter.getInstance().addObserver(this, NotificationCenter.FileDidFailedLoad); - NotificationCenter.getInstance().addObserver(this, NotificationCenter.messageReceivedByServer); - NotificationCenter.getInstance().addObserver(this, NotificationCenter.updateMessageMedia); + LocationController.getInstance(); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + MessagesController messagesController = getInstance(); + NotificationCenter.getInstance().addObserver(messagesController, NotificationCenter.FileDidUpload); + NotificationCenter.getInstance().addObserver(messagesController, NotificationCenter.FileDidFailUpload); + NotificationCenter.getInstance().addObserver(messagesController, NotificationCenter.FileDidLoaded); + NotificationCenter.getInstance().addObserver(messagesController, NotificationCenter.FileDidFailedLoad); + NotificationCenter.getInstance().addObserver(messagesController, NotificationCenter.messageReceivedByServer); + NotificationCenter.getInstance().addObserver(messagesController, NotificationCenter.updateMessageMedia); + } + }); addSupportUser(); SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); enableJoined = preferences.getBoolean("EnableContactJoined", true); @@ -282,19 +298,22 @@ public class MessagesController implements NotificationCenter.NotificationCenter maxMegagroupCount = preferences.getInt("maxMegagroupCount", 10000); maxRecentGifsCount = preferences.getInt("maxRecentGifsCount", 200); maxRecentStickersCount = preferences.getInt("maxRecentStickersCount", 30); + maxFaveStickersCount = preferences.getInt("maxFaveStickersCount", 5); maxEditTime = preferences.getInt("maxEditTime", 3600); groupBigSize = preferences.getInt("groupBigSize", 10); ratingDecay = preferences.getInt("ratingDecay", 2419200); fontSize = preferences.getInt("fons_size", AndroidUtilities.isTablet() ? 18 : 16); allowBigEmoji = preferences.getBoolean("allowBigEmoji", false); useSystemEmoji = preferences.getBoolean("useSystemEmoji", false); - callsEnabled = preferences.getBoolean("callsEnabled", false); linkPrefix = preferences.getString("linkPrefix", "t.me"); callReceiveTimeout = preferences.getInt("callReceiveTimeout", 20000); callRingTimeout = preferences.getInt("callRingTimeout", 90000); callConnectTimeout = preferences.getInt("callConnectTimeout", 30000); callPacketTimeout = preferences.getInt("callPacketTimeout", 10000); maxPinnedDialogsCount = preferences.getInt("maxPinnedDialogsCount", 5); + installReferer = preferences.getString("installReferer", null); + defaultP2pContacts = preferences.getBoolean("defaultP2pContacts", false); + String disabledFeaturesString = preferences.getString("disabledFeatures", null); if (disabledFeaturesString != null && disabledFeaturesString.length() != 0) { @@ -330,8 +349,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter ratingDecay = config.rating_e_decay; maxRecentGifsCount = config.saved_gifs_limit; maxRecentStickersCount = config.stickers_recent_limit; - boolean callsOld = callsEnabled; - callsEnabled = config.phonecalls_enabled; + maxFaveStickersCount = config.stickers_faved_limit; linkPrefix = config.me_url_prefix; if (linkPrefix.endsWith("/")) { linkPrefix = linkPrefix.substring(0, linkPrefix.length() - 1); @@ -346,6 +364,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter callConnectTimeout = config.call_connect_timeout_ms; callPacketTimeout = config.call_packet_timeout_ms; maxPinnedDialogsCount = config.pinned_dialogs_count_max; + defaultP2pContacts = config.default_p2p_contacts; SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); @@ -357,13 +376,14 @@ public class MessagesController implements NotificationCenter.NotificationCenter editor.putInt("ratingDecay", ratingDecay); editor.putInt("maxRecentGifsCount", maxRecentGifsCount); editor.putInt("maxRecentStickersCount", maxRecentStickersCount); + editor.putInt("maxFaveStickersCount", maxFaveStickersCount); editor.putInt("callReceiveTimeout", callReceiveTimeout); editor.putInt("callRingTimeout", callRingTimeout); editor.putInt("callConnectTimeout", callConnectTimeout); editor.putInt("callPacketTimeout", callPacketTimeout); - editor.putBoolean("callsEnabled", callsEnabled); editor.putString("linkPrefix", linkPrefix); editor.putInt("maxPinnedDialogsCount", maxPinnedDialogsCount); + editor.putBoolean("defaultP2pContacts", defaultP2pContacts); try { SerializedData data = new SerializedData(); data.writeInt32(disabledFeatures.size()); @@ -379,10 +399,6 @@ public class MessagesController implements NotificationCenter.NotificationCenter FileLog.e(e); } editor.commit(); - - if (callsEnabled != callsOld) { - NotificationCenter.getInstance().postNotificationName(NotificationCenter.mainUserInfoChanged); - } } }); } @@ -565,15 +581,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter Integer newMsgId = (Integer) args[1]; Long did = (Long) args[3]; MessageObject obj = dialogMessage.get(did); - if (obj != null && obj.getId() == msgId) { + if (obj != null && (obj.getId() == msgId || obj.messageOwner.local_id == msgId)) { obj.messageOwner.id = newMsgId; obj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SENT; - TLRPC.TL_dialog dialog = dialogs_dict.get(did); - if (dialog != null) { - if (dialog.top_message == msgId) { - dialog.top_message = newMsgId; - } - } + } + TLRPC.TL_dialog dialog = dialogs_dict.get(did); + if (dialog != null && dialog.top_message == msgId) { + dialog.top_message = newMsgId; NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload); } obj = dialogMessagesByIds.remove(msgId); @@ -599,6 +613,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter NotificationsController.getInstance().cleanup(); SendMessagesHelper.getInstance().cleanup(); SecretChatHelper.getInstance().cleanup(); + LocationController.getInstance().cleanup(); StickersQuery.cleanup(); SearchQuery.cleanup(); DraftQuery.cleanup(); @@ -613,11 +628,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter dialogs.clear(); joiningToChannels.clear(); channelViewsToSend.clear(); - channelViewsToReload.clear(); dialogsServerOnly.clear(); + dialogsForward.clear(); dialogsGroupsOnly.clear(); dialogMessagesByIds.clear(); dialogMessagesByRandomIds.clear(); + channelAdmins.clear(); + loadingChannelAdmins.clear(); users.clear(); objectsByUsernames.clear(); chats.clear(); @@ -641,6 +658,8 @@ public class MessagesController implements NotificationCenter.NotificationCenter updatesStartWaitTimeQts = 0; createdDialogIds.clear(); gettingDifference = false; + resetDialogsPinned = null; + resetDialogsAll = null; } }); createdDialogMainThreadIds.clear(); @@ -664,9 +683,11 @@ public class MessagesController implements NotificationCenter.NotificationCenter loadingBlockedUsers = false; firstGettingTask = false; updatingState = false; + resetingDialogs = false; lastStatusUpdateTime = 0; offlineSent = false; registeringForPush = false; + getDifferenceFirstSync = true; uploadingAvatar = null; statusRequest = 0; statusSettingState = 0; @@ -769,6 +790,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter } fromCache = fromCache && user.id / 1000 != 333 && user.id != 777000; TLRPC.User oldUser = users.get(user.id); + if (oldUser == user) { + return false; + } if (oldUser != null && !TextUtils.isEmpty(oldUser.username)) { objectsByUsernames.remove(oldUser.username.toLowerCase()); } @@ -778,19 +802,21 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (user.min) { if (oldUser != null) { if (!fromCache) { - if (user.username != null) { - oldUser.username = user.username; - oldUser.flags |= 8; - } else { - oldUser.username = null; - oldUser.flags = oldUser.flags &~ 8; + if (user.bot) { + if (user.username != null) { + oldUser.username = user.username; + oldUser.flags |= 8; + } else { + oldUser.flags = oldUser.flags & ~8; + oldUser.username = null; + } } if (user.photo != null) { oldUser.photo = user.photo; oldUser.flags |= 32; } else { - oldUser.photo = null; oldUser.flags = oldUser.flags &~ 32; + oldUser.photo = null; } } } else { @@ -810,19 +836,21 @@ public class MessagesController implements NotificationCenter.NotificationCenter users.put(user.id, user); } else if (oldUser.min) { user.min = false; - if (oldUser.username != null) { - user.username = oldUser.username; - user.flags |= 8; - } else { - user.username = null; - user.flags = user.flags &~ 8; + if (oldUser.bot) { + if (oldUser.username != null) { + user.username = oldUser.username; + user.flags |= 8; + } else { + user.flags = user.flags & ~8; + user.username = null; + } } if (oldUser.photo != null) { user.photo = oldUser.photo; user.flags |= 32; } else { - user.photo = null; user.flags = user.flags &~ 32; + user.photo = null; } users.put(user.id, user); } @@ -857,6 +885,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter return; } TLRPC.Chat oldChat = chats.get(chat.id); + if (oldChat == chat) { + return; + } if (oldChat != null && !TextUtils.isEmpty(oldChat.username)) { objectsByUsernames.remove(oldChat.username.toLowerCase()); } @@ -876,8 +907,11 @@ public class MessagesController implements NotificationCenter.NotificationCenter oldChat.username = chat.username; oldChat.flags |= 64; } else { - oldChat.username = null; oldChat.flags = oldChat.flags &~ 64; + oldChat.username = null; + } + if (chat.participants_count != 0) { + oldChat.participants_count = chat.participants_count; } } } else { @@ -889,6 +923,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (chat.version != oldChat.version) { loadedFullChats.remove((Integer) chat.id); } + if (oldChat.participants_count != 0 && chat.participants_count == 0) { + chat.participants_count = oldChat.participants_count; + chat.flags |= 131072; + } int oldFlags = oldChat.banned_rights != null ? oldChat.banned_rights.flags : 0; int newFlags = chat.banned_rights != null ? chat.banned_rights.flags : 0; if (oldFlags != newFlags) { @@ -915,8 +953,12 @@ public class MessagesController implements NotificationCenter.NotificationCenter chat.username = oldChat.username; chat.flags |= 64; } else { - chat.username = null; chat.flags = chat.flags &~ 64; + chat.username = null; + } + if (oldChat.participants_count != 0 && chat.participants_count == 0) { + chat.participants_count = oldChat.participants_count; + chat.flags |= 131072; } chats.put(chat.id, chat); } @@ -934,6 +976,15 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } + public void setReferer(String referer) { + if (referer == null) { + return; + } + installReferer = referer; + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + preferences.edit().putString("installReferer", referer).commit(); + } + public void putEncryptedChat(TLRPC.EncryptedChat encryptedChat, boolean fromCache) { if (encryptedChat == null) { return; @@ -1066,18 +1117,80 @@ public class MessagesController implements NotificationCenter.NotificationCenter }); } + public boolean isChannelAdmin(int chatId, int uid) { + ArrayList array = channelAdmins.get(chatId); + return array != null && array.indexOf(uid) >= 0; + } + + public void loadChannelAdmins(final int chatId, final boolean cache) { + if (loadingChannelAdmins.indexOfKey(chatId) >= 0) { + return; + } + loadingChannelAdmins.put(chatId, 0); + if (cache) { + MessagesStorage.getInstance().loadChannelAdmins(chatId); + } else { + TLRPC.TL_channels_getParticipants req = new TLRPC.TL_channels_getParticipants(); + ArrayList array = channelAdmins.get(chatId); + if (array != null) { + long acc = 0; + for (int a = 0; a < array.size(); a++) { + acc = ((acc * 20261) + 0x80000000L + array.get(a)) % 0x80000000L; + } + req.hash = (int) acc; + } + req.channel = getInputChannel(chatId); + req.limit = 100; + req.filter = new TLRPC.TL_channelParticipantsAdmins(); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + if (response instanceof TLRPC.TL_channels_channelParticipants) { + TLRPC.TL_channels_channelParticipants participants = (TLRPC.TL_channels_channelParticipants) response; + final ArrayList array = new ArrayList<>(participants.participants.size()); + for (int a = 0; a < participants.participants.size(); a++) { + array.add(participants.participants.get(a).user_id); + } + processLoadedChannelAdmins(array, chatId, false); + } + } + }); + } + } + + public void processLoadedChannelAdmins(final ArrayList array, final int chatId, final boolean cache) { + Collections.sort(array); + if (!cache) { + MessagesStorage.getInstance().putChannelAdmins(chatId, array); + } + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + loadingChannelAdmins.delete(chatId); + channelAdmins.put(chatId, array); + if (cache) { + loadChannelAdmins(chatId, false); + } + } + }); + } + public void loadFullChat(final int chat_id, final int classGuid, boolean force) { - if (loadingFullChats.contains(chat_id) || !force && loadedFullChats.contains(chat_id)) { + boolean loaded = loadedFullChats.contains(chat_id); + if (loadingFullChats.contains(chat_id) || !force && loaded) { return; } loadingFullChats.add(chat_id); TLObject request; final long dialog_id = -chat_id; final TLRPC.Chat chat = getChat(chat_id); - if (ChatObject.isChannel(chat_id)) { + if (ChatObject.isChannel(chat)) { TLRPC.TL_channels_getFullChannel req = new TLRPC.TL_channels_getFullChannel(); - req.channel = getInputChannel(chat_id); + req.channel = getInputChannel(chat); request = req; + if (chat.megagroup) { + loadChannelAdmins(chat_id, !loaded); + } } else { TLRPC.TL_messages_getFullChat req = new TLRPC.TL_messages_getFullChat(); req.chat_id = chat_id; @@ -1143,6 +1256,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter putUsers(res.users, false); putChats(res.chats, false); + if (res.full_chat.stickerset != null) { + StickersQuery.getGroupStickerSetById(res.full_chat.stickerset); + } NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatInfoDidLoaded, res.full_chat, classGuid, false, null); } }); @@ -1651,9 +1767,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter }); } - public void loadDialogPhotos(final int did, final int offset, final int count, final long max_id, final boolean fromCache, final int classGuid) { + public void loadDialogPhotos(final int did, final int count, final long max_id, final boolean fromCache, final int classGuid) { if (fromCache) { - MessagesStorage.getInstance().getDialogPhotos(did, offset, count, max_id, classGuid); + MessagesStorage.getInstance().getDialogPhotos(did, count, max_id, classGuid); } else { if (did > 0) { TLRPC.User user = getUser(did); @@ -1662,7 +1778,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } TLRPC.TL_photos_getUserPhotos req = new TLRPC.TL_photos_getUserPhotos(); req.limit = count; - req.offset = offset; + req.offset = 0; req.max_id = (int) max_id; req.user_id = getInputUser(user); int reqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @@ -1670,7 +1786,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter public void run(TLObject response, TLRPC.TL_error error) { if (error == null) { TLRPC.photos_Photos res = (TLRPC.photos_Photos) response; - processLoadedUserPhotos(res, did, offset, count, max_id, false, classGuid); + processLoadedUserPhotos(res, did, count, max_id, false, classGuid); } } }); @@ -1679,8 +1795,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter TLRPC.TL_messages_search req = new TLRPC.TL_messages_search(); req.filter = new TLRPC.TL_inputMessagesFilterChatPhotos(); req.limit = count; - req.offset = offset; - req.max_id = (int) max_id; + req.offset_id = (int) max_id; req.q = ""; req.peer = MessagesController.getInputPeer(did); int reqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @@ -1698,7 +1813,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } res.photos.add(message.action.photo); } - processLoadedUserPhotos(res, did, offset, count, max_id, false, classGuid); + processLoadedUserPhotos(res, did, count, max_id, false, classGuid); } } }); @@ -1921,19 +2036,19 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } - public void processLoadedUserPhotos(final TLRPC.photos_Photos res, final int did, final int offset, final int count, final long max_id, final boolean fromCache, final int classGuid) { + public void processLoadedUserPhotos(final TLRPC.photos_Photos res, final int did, final int count, final long max_id, final boolean fromCache, final int classGuid) { if (!fromCache) { MessagesStorage.getInstance().putUsersAndChats(res.users, null, true, true); MessagesStorage.getInstance().putDialogPhotos(did, res); } else if (res == null || res.photos.isEmpty()) { - loadDialogPhotos(did, offset, count, max_id, false, classGuid); + loadDialogPhotos(did, count, max_id, false, classGuid); return; } AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { putUsers(res.users, fromCache); - NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogPhotosLoaded, did, offset, count, fromCache, classGuid, res.photos); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogPhotosLoaded, did, count, fromCache, classGuid, res.photos); } }); } @@ -2162,7 +2277,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (onlyHistory == 1 && lower_part != 0 && lastMessageId > 0) { TLRPC.TL_messageService message = new TLRPC.TL_messageService(); message.id = dialog.top_message; - message.out = false; + message.out = UserConfig.getClientUserId() == did; message.from_id = UserConfig.getClientUserId(); message.flags |= 256; message.action = new TLRPC.TL_messageActionHistoryClear(); @@ -2212,26 +2327,43 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (lower_part != 0) { TLRPC.InputPeer peer = getInputPeer(lower_part); - if (peer == null || peer instanceof TLRPC.TL_inputPeerChannel) { + if (peer == null) { return; } - TLRPC.TL_messages_deleteHistory req = new TLRPC.TL_messages_deleteHistory(); - req.peer = peer; - req.max_id = (onlyHistory == 0 ? Integer.MAX_VALUE : max_id_delete); - req.just_clear = onlyHistory != 0; - final int max_id_delete_final = max_id_delete; - ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { - @Override - public void run(TLObject response, TLRPC.TL_error error) { - if (error == null) { - TLRPC.TL_messages_affectedHistory res = (TLRPC.TL_messages_affectedHistory) response; - if (res.offset > 0) { - deleteDialog(did, false, onlyHistory, max_id_delete_final); - } - processNewDifferenceParams(-1, res.pts, -1, res.pts_count); - } + if (peer instanceof TLRPC.TL_inputPeerChannel) { + if (onlyHistory == 0) { + return; } - }, ConnectionsManager.RequestFlagInvokeAfter); + TLRPC.TL_channels_deleteHistory req = new TLRPC.TL_channels_deleteHistory(); + req.channel = new TLRPC.TL_inputChannel(); + req.channel.channel_id = peer.channel_id; + req.channel.access_hash = peer.access_hash; + req.max_id = max_id_delete > 0 ? max_id_delete : Integer.MAX_VALUE; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + + } + }, ConnectionsManager.RequestFlagInvokeAfter); + } else { + TLRPC.TL_messages_deleteHistory req = new TLRPC.TL_messages_deleteHistory(); + req.peer = peer; + req.max_id = (onlyHistory == 0 ? Integer.MAX_VALUE : max_id_delete); + req.just_clear = onlyHistory != 0; + final int max_id_delete_final = max_id_delete; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + if (error == null) { + TLRPC.TL_messages_affectedHistory res = (TLRPC.TL_messages_affectedHistory) response; + if (res.offset > 0) { + deleteDialog(did, false, onlyHistory, max_id_delete_final); + } + processNewDifferenceParams(-1, res.pts, -1, res.pts_count); + } + } + }, ConnectionsManager.RequestFlagInvokeAfter); + } } else { if (onlyHistory == 1) { SecretChatHelper.getInstance().sendClearHistoryMessage(getEncryptedChat(high_id), null); @@ -2314,6 +2446,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter @Override public void run() { putUsers(usersArr, fromCache); + if (info.stickerset != null) { + StickersQuery.getGroupStickerSetById(info.stickerset); + } NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatInfoDidLoaded, info, 0, byChannelUsers, pinnedMessageObject); } }); @@ -2395,49 +2530,43 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } } - if ((channelViewsToSend.size() != 0 || channelViewsToReload.size() != 0) && Math.abs(System.currentTimeMillis() - lastViewsCheckTime) >= 5000) { + if (channelViewsToSend.size() != 0 && Math.abs(System.currentTimeMillis() - lastViewsCheckTime) >= 5000) { lastViewsCheckTime = System.currentTimeMillis(); - for (int b = 0; b < 2; b++) { - SparseArray> array = b == 0 ? channelViewsToSend : channelViewsToReload; - if (array.size() == 0) { - continue; - } - for (int a = 0; a < array.size(); a++) { - final int key = array.keyAt(a); - final TLRPC.TL_messages_getMessagesViews req = new TLRPC.TL_messages_getMessagesViews(); - req.peer = getInputPeer(key); - req.id = array.get(key); - req.increment = a == 0; - ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { - @Override - public void run(TLObject response, TLRPC.TL_error error) { - if (error == null) { - TLRPC.Vector vector = (TLRPC.Vector) response; - final SparseArray channelViews = new SparseArray<>(); - SparseIntArray array = channelViews.get(key); - if (array == null) { - array = new SparseIntArray(); - channelViews.put(key, array); - } - for (int a = 0; a < req.id.size(); a++) { - if (a >= vector.objects.size()) { - break; - } - array.put(req.id.get(a), (Integer) vector.objects.get(a)); - } - MessagesStorage.getInstance().putChannelViews(channelViews, req.peer instanceof TLRPC.TL_inputPeerChannel); - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - NotificationCenter.getInstance().postNotificationName(NotificationCenter.didUpdatedMessagesViews, channelViews); - } - }); + for (int a = 0; a < channelViewsToSend.size(); a++) { + final int key = channelViewsToSend.keyAt(a); + final TLRPC.TL_messages_getMessagesViews req = new TLRPC.TL_messages_getMessagesViews(); + req.peer = getInputPeer(key); + req.id = channelViewsToSend.get(key); + req.increment = a == 0; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + if (error == null) { + TLRPC.Vector vector = (TLRPC.Vector) response; + final SparseArray channelViews = new SparseArray<>(); + SparseIntArray array = channelViews.get(key); + if (array == null) { + array = new SparseIntArray(); + channelViews.put(key, array); } + for (int a = 0; a < req.id.size(); a++) { + if (a >= vector.objects.size()) { + break; + } + array.put(req.id.get(a), (Integer) vector.objects.get(a)); + } + MessagesStorage.getInstance().putChannelViews(channelViews, req.peer instanceof TLRPC.TL_inputPeerChannel); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.didUpdatedMessagesViews, channelViews); + } + }); } - }); - } - array.clear(); + } + }); } + channelViewsToSend.clear(); } if (!onlinePrivacy.isEmpty()) { ArrayList toRemove = null; @@ -2512,6 +2641,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter }); } } + LocationController.getInstance().update(); } private String getUserNameForTyping(TLRPC.User user) { @@ -2541,7 +2671,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter PrintingUser pu = arr.get(0); TLRPC.User user = getUser(pu.userId); if (user == null) { - return; + continue; } if (pu.action instanceof TLRPC.TL_sendMessageRecordAudioAction) { if (lower_id < 0) { @@ -2594,7 +2724,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter newPrintingStringsTypes.put(key, 3); } else { if (lower_id < 0) { - newPrintingStrings.put(key, String.format("%s %s", getUserNameForTyping(user), LocaleController.getString("IsTyping", R.string.IsTyping))); + newPrintingStrings.put(key, LocaleController.formatString("IsTypingGroup", R.string.IsTypingGroup, getUserNameForTyping(user))); } else { newPrintingStrings.put(key, LocaleController.getString("Typing", R.string.Typing)); } @@ -2602,14 +2732,14 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } else { int count = 0; - String label = ""; + StringBuilder label = new StringBuilder(); for (PrintingUser pu : arr) { TLRPC.User user = getUser(pu.userId); if (user != null) { if (label.length() != 0) { - label += ", "; + label.append(", "); } - label += getUserNameForTyping(user); + label.append(getUserNameForTyping(user)); count++; } if (count == 2) { @@ -2618,12 +2748,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter } if (label.length() != 0) { if (count == 1) { - newPrintingStrings.put(key, String.format("%s %s", label, LocaleController.getString("IsTyping", R.string.IsTyping))); + newPrintingStrings.put(key, LocaleController.formatString("IsTypingGroup", R.string.IsTypingGroup, label.toString())); } else { if (arr.size() > 2) { - newPrintingStrings.put(key, String.format("%s %s", label, LocaleController.formatPluralString("AndMoreTyping", arr.size() - 2))); + String plural = LocaleController.getPluralString("AndMoreTypingGroup", arr.size() - 2); + newPrintingStrings.put(key, String.format(plural, label.toString(), arr.size() - 2)); } else { - newPrintingStrings.put(key, String.format("%s %s", label, LocaleController.getString("AreTyping", R.string.AreTyping))); + newPrintingStrings.put(key, LocaleController.formatString("AreTypingGroup", R.string.AreTypingGroup, label.toString())); } } newPrintingStringsTypes.put(key, 0); @@ -2750,11 +2881,11 @@ public class MessagesController implements NotificationCenter.NotificationCenter } public void loadMessages(final long dialog_id, final int count, final int max_id, final int offset_date, boolean fromCache, int midDate, final int classGuid, final int load_type, final int last_message_id, final boolean isChannel, final int loadIndex) { - loadMessages(dialog_id, count, max_id, offset_date, fromCache, midDate, classGuid, load_type, last_message_id, isChannel, loadIndex, 0, 0, 0, false); + loadMessages(dialog_id, count, max_id, offset_date, fromCache, midDate, classGuid, load_type, last_message_id, isChannel, loadIndex, 0, 0, 0, false, 0); } - public void loadMessages(final long dialog_id, final int count, final int max_id, final int offset_date, boolean fromCache, int midDate, final int classGuid, final int load_type, final int last_message_id, final boolean isChannel, final int loadIndex, final int first_unread, final int unread_count, final int last_date, final boolean queryFromServer) { - FileLog.e("load messages in chat " + dialog_id + " count " + count + " max_id " + max_id + " cache " + fromCache + " mindate = " + midDate + " guid " + classGuid + " load_type " + load_type + " last_message_id " + last_message_id + " index " + loadIndex + " firstUnread " + first_unread + " underad count " + unread_count + " last_date " + last_date + " queryFromServer " + queryFromServer); + public void loadMessages(final long dialog_id, final int count, final int max_id, final int offset_date, boolean fromCache, int midDate, final int classGuid, final int load_type, final int last_message_id, final boolean isChannel, final int loadIndex, final int first_unread, final int unread_count, final int last_date, final boolean queryFromServer, final int mentionsCount) { + FileLog.e("load messages in chat " + dialog_id + " count " + count + " max_id " + max_id + " cache " + fromCache + " mindate = " + midDate + " guid " + classGuid + " load_type " + load_type + " last_message_id " + last_message_id + " index " + loadIndex + " firstUnread " + first_unread + " unread_count " + unread_count + " last_date " + last_date + " queryFromServer " + queryFromServer); int lower_part = (int) dialog_id; if (fromCache || lower_part == 0) { MessagesStorage.getInstance().getMessages(dialog_id, count, max_id, offset_date, midDate, classGuid, load_type, isChannel, loadIndex); @@ -2800,7 +2931,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } } - processLoadedMessages(res, dialog_id, count, mid, offset_date, false, classGuid, first_unread, last_message_id, unread_count, last_date, load_type, isChannel, false, loadIndex, queryFromServer); + processLoadedMessages(res, dialog_id, count, mid, offset_date, false, classGuid, first_unread, last_message_id, unread_count, last_date, load_type, isChannel, false, loadIndex, queryFromServer, mentionsCount); } } }); @@ -2862,8 +2993,8 @@ public class MessagesController implements NotificationCenter.NotificationCenter } public void processLoadedMessages(final TLRPC.messages_Messages messagesRes, final long dialog_id, final int count, final int max_id, final int offset_date, final boolean isCache, final int classGuid, - final int first_unread, final int last_message_id, final int unread_count, final int last_date, final int load_type, final boolean isChannel, final boolean isEnd, final int loadIndex, final boolean queryFromServer) { - FileLog.e("processLoadedMessages size " + messagesRes.messages.size() + " in chat " + dialog_id + " count " + count + " max_id " + max_id + " cache " + isCache + " guid " + classGuid + " load_type " + load_type + " last_message_id " + last_message_id + " isChannel " + isChannel + " index " + loadIndex + " firstUnread " + first_unread + " underad count " + unread_count + " last_date " + last_date + " queryFromServer " + queryFromServer); + final int first_unread, final int last_message_id, final int unread_count, final int last_date, final int load_type, final boolean isChannel, final boolean isEnd, final int loadIndex, final boolean queryFromServer, final int mentionsCount) { + FileLog.e("processLoadedMessages size " + messagesRes.messages.size() + " in chat " + dialog_id + " count " + count + " max_id " + max_id + " cache " + isCache + " guid " + classGuid + " load_type " + load_type + " last_message_id " + last_message_id + " isChannel " + isChannel + " index " + loadIndex + " firstUnread " + first_unread + " unread_count " + unread_count + " last_date " + last_date + " queryFromServer " + queryFromServer); Utilities.stageQueue.postRunnable(new Runnable() { @Override public void run() { @@ -2901,7 +3032,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - loadMessages(dialog_id, count, load_type == 2 && queryFromServer ? first_unread : max_id, offset_date, false, 0, classGuid, load_type, last_message_id, isChannel, loadIndex, first_unread, unread_count, last_date, queryFromServer); + loadMessages(dialog_id, count, load_type == 2 && queryFromServer ? first_unread : max_id, offset_date, false, 0, classGuid, load_type, last_message_id, isChannel, loadIndex, first_unread, unread_count, last_date, queryFromServer, mentionsCount); } }); return; @@ -2932,9 +3063,6 @@ public class MessagesController implements NotificationCenter.NotificationCenter for (int a = 0; a < size; a++) { TLRPC.Message message = messagesRes.messages.get(a); - if (!isCache && message.post && !message.out) { - message.media_unread = true; - } if (isMegagroup) { message.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; } @@ -3000,7 +3128,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (first_unread_final == Integer.MAX_VALUE) { first_unread_final = first_unread; } - NotificationCenter.getInstance().postNotificationName(NotificationCenter.messagesDidLoaded, dialog_id, count, objects, isCache, first_unread_final, last_message_id, unread_count, last_date, load_type, isEnd, classGuid, loadIndex, max_id); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.messagesDidLoaded, dialog_id, count, objects, isCache, first_unread_final, last_message_id, unread_count, last_date, load_type, isEnd, classGuid, loadIndex, max_id, mentionsCount); if (!messagesToReload.isEmpty()) { reloadMessages(messagesToReload, dialog_id); } @@ -3013,8 +3141,39 @@ public class MessagesController implements NotificationCenter.NotificationCenter }); } + public void loadHintDialogs() { + if (!hintDialogs.isEmpty() || TextUtils.isEmpty(installReferer)) { + return; + } + TLRPC.TL_help_getRecentMeUrls req = new TLRPC.TL_help_getRecentMeUrls(); + req.referer = installReferer; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, TLRPC.TL_error error) { + if (error == null) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + /*installReferer = null; + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + preferences.edit().remove("installReferer").commit();*/ + + TLRPC.TL_help_recentMeUrls res = (TLRPC.TL_help_recentMeUrls) response; + putUsers(res.users, false); + putChats(res.chats, false); + hintDialogs.clear(); + hintDialogs.addAll(res.urls); + + NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload); + } + }); + } + } + }); + } + public void loadDialogs(final int offset, final int count, boolean fromCache) { - if (loadingDialogs) { + if (loadingDialogs || resetingDialogs) { return; } loadingDialogs = true; @@ -3095,6 +3254,234 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } + public void forceResetDialogs() { + resetDialogs(true, MessagesStorage.lastSeqValue, MessagesStorage.lastPtsValue, MessagesStorage.lastDateValue, MessagesStorage.lastQtsValue); + } + + private void resetDialogs(boolean query, final int seq, final int newPts, final int date, final int qts) { + if (query) { + if (resetingDialogs) { + return; + } + resetingDialogs = true; + TLRPC.TL_messages_getPinnedDialogs req = new TLRPC.TL_messages_getPinnedDialogs(); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, TLRPC.TL_error error) { + if (response != null) { + resetDialogsPinned = (TLRPC.TL_messages_peerDialogs) response; + resetDialogs(false, seq, newPts, date, qts); + } + } + }); + TLRPC.TL_messages_getDialogs req2 = new TLRPC.TL_messages_getDialogs(); + req2.limit = 100; + req2.exclude_pinned = true; + req2.offset_peer = new TLRPC.TL_inputPeerEmpty(); + ConnectionsManager.getInstance().sendRequest(req2, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + if (error == null) { + resetDialogsAll = (TLRPC.messages_Dialogs) response; + resetDialogs(false, seq, newPts, date, qts); + } + } + }); + } else if (resetDialogsPinned != null && resetDialogsAll != null) { + int messagesCount = resetDialogsAll.messages.size(); + int dialogsCount = resetDialogsAll.dialogs.size(); + resetDialogsAll.dialogs.addAll(resetDialogsPinned.dialogs); + resetDialogsAll.messages.addAll(resetDialogsPinned.messages); + resetDialogsAll.users.addAll(resetDialogsPinned.users); + resetDialogsAll.chats.addAll(resetDialogsPinned.chats); + + final HashMap new_dialogs_dict = new HashMap<>(); + final HashMap new_dialogMessage = new HashMap<>(); + final HashMap usersDict = new HashMap<>(); + final HashMap chatsDict = new HashMap<>(); + + for (int a = 0; a < resetDialogsAll.users.size(); a++) { + TLRPC.User u = resetDialogsAll.users.get(a); + usersDict.put(u.id, u); + } + for (int a = 0; a < resetDialogsAll.chats.size(); a++) { + TLRPC.Chat c = resetDialogsAll.chats.get(a); + chatsDict.put(c.id, c); + } + + TLRPC.Message lastMessage = null; + for (int a = 0; a < resetDialogsAll.messages.size(); a++) { + TLRPC.Message message = resetDialogsAll.messages.get(a); + if (a < messagesCount) { + if (lastMessage == null || message.date < lastMessage.date) { + lastMessage = message; + } + } + if (message.to_id.channel_id != 0) { + TLRPC.Chat chat = chatsDict.get(message.to_id.channel_id); + if (chat != null && chat.left) { + continue; + } + if (chat != null && chat.megagroup) { + message.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; + } + } else if (message.to_id.chat_id != 0) { + TLRPC.Chat chat = chatsDict.get(message.to_id.chat_id); + if (chat != null && chat.migrated_to != null) { + continue; + } + } + MessageObject messageObject = new MessageObject(message, usersDict, chatsDict, false); + new_dialogMessage.put(messageObject.getDialogId(), messageObject); + } + + for (int a = 0; a < resetDialogsAll.dialogs.size(); a++) { + TLRPC.TL_dialog d = resetDialogsAll.dialogs.get(a); + if (d.id == 0 && d.peer != null) { + if (d.peer.user_id != 0) { + d.id = d.peer.user_id; + } else if (d.peer.chat_id != 0) { + d.id = -d.peer.chat_id; + } else if (d.peer.channel_id != 0) { + d.id = -d.peer.channel_id; + } + } + if (d.id == 0) { + continue; + } + if (d.last_message_date == 0) { + MessageObject mess = new_dialogMessage.get(d.id); + if (mess != null) { + d.last_message_date = mess.messageOwner.date; + } + } + if (DialogObject.isChannel(d)) { + TLRPC.Chat chat = chatsDict.get(-(int) d.id); + if (chat != null && chat.left) { + continue; + } + channelsPts.put(-(int) d.id, d.pts); + } else if ((int) d.id < 0) { + TLRPC.Chat chat = chatsDict.get(-(int) d.id); + if (chat != null && chat.migrated_to != null) { + continue; + } + } + new_dialogs_dict.put(d.id, d); + + Integer value = dialogs_read_inbox_max.get(d.id); + if (value == null) { + value = 0; + } + dialogs_read_inbox_max.put(d.id, Math.max(value, d.read_inbox_max_id)); + + value = dialogs_read_outbox_max.get(d.id); + if (value == null) { + value = 0; + } + dialogs_read_outbox_max.put(d.id, Math.max(value, d.read_outbox_max_id)); + } + + ImageLoader.saveMessagesThumbs(resetDialogsAll.messages); + for (int a = 0; a < resetDialogsAll.messages.size(); a++) { + TLRPC.Message message = resetDialogsAll.messages.get(a); + if (message.action instanceof TLRPC.TL_messageActionChatDeleteUser) { + TLRPC.User user = usersDict.get(message.action.user_id); + if (user != null && user.bot) { + message.reply_markup = new TLRPC.TL_replyKeyboardHide(); + message.flags |= 64; + } + } + + if (message.action instanceof TLRPC.TL_messageActionChatMigrateTo || message.action instanceof TLRPC.TL_messageActionChannelCreate) { + message.unread = false; + message.media_unread = false; + } else { + ConcurrentHashMap read_max = message.out ? dialogs_read_outbox_max : dialogs_read_inbox_max; + Integer value = read_max.get(message.dialog_id); + if (value == null) { + value = MessagesStorage.getInstance().getDialogReadMax(message.out, message.dialog_id); + read_max.put(message.dialog_id, value); + } + message.unread = value < message.id; + } + } + + MessagesStorage.getInstance().resetDialogs(resetDialogsAll, messagesCount, seq, newPts, date, qts, new_dialogs_dict, new_dialogMessage, lastMessage, dialogsCount); + resetDialogsPinned = null; + resetDialogsAll = null; + } + } + + protected void completeDialogsReset(final TLRPC.messages_Dialogs dialogsRes, final int messagesCount, final int seq, final int newPts, final int date, final int qts, final HashMap new_dialogs_dict, final HashMap new_dialogMessage, final TLRPC.Message lastMessage) { + Utilities.stageQueue.postRunnable(new Runnable() { + @Override + public void run() { + gettingDifference = false; + MessagesStorage.lastPtsValue = newPts; + MessagesStorage.lastDateValue = date; + MessagesStorage.lastQtsValue = qts; + getDifference(); + + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + resetingDialogs = false; + applyDialogsNotificationsSettings(dialogsRes.dialogs); + if (!UserConfig.draftsLoaded) { + DraftQuery.loadDrafts(); + } + + putUsers(dialogsRes.users, false); + putChats(dialogsRes.chats, false); + + for (int a = 0; a < dialogs.size(); a++) { + TLRPC.TL_dialog oldDialog = dialogs.get(a); + if ((int) oldDialog.id != 0) { + dialogs_dict.remove(oldDialog.id); + MessageObject messageObject = dialogMessage.remove(oldDialog.id); + if (messageObject != null) { + dialogMessagesByIds.remove(messageObject.getId()); + if (messageObject.messageOwner.random_id != 0) { + dialogMessagesByRandomIds.remove(messageObject.messageOwner.random_id); + } + } + } + } + + for (HashMap.Entry pair : new_dialogs_dict.entrySet()) { + Long key = pair.getKey(); + TLRPC.TL_dialog value = pair.getValue(); + if (value.draft instanceof TLRPC.TL_draftMessage) { + DraftQuery.saveDraft(value.id, value.draft, null, false); + } + dialogs_dict.put(key, value); + MessageObject messageObject = new_dialogMessage.get(value.id); + dialogMessage.put(key, messageObject); + if (messageObject != null && messageObject.messageOwner.to_id.channel_id == 0) { + dialogMessagesByIds.put(messageObject.getId(), messageObject); + if (messageObject.messageOwner.random_id != 0) { + dialogMessagesByRandomIds.put(messageObject.messageOwner.random_id, messageObject); + } + } + } + + dialogs.clear(); + dialogs.addAll(dialogs_dict.values()); + sortDialogs(null); + dialogsEndReached = true; + serverDialogsEndReached = false; + + if (UserConfig.totalDialogsLoadCount < 400 && UserConfig.dialogsLoadOffsetId != -1 && UserConfig.dialogsLoadOffsetId != Integer.MAX_VALUE) { + loadDialogs(0, 100, false); + } + NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload); + } + }); + } + }); + } + private void migrateDialogs(final int offset, final int offsetDate, final int offsetUser, final int offsetChat, final int offsetChannel, final long accessPeer) { if (migratingDialogs || offset == -1) { return; @@ -3359,9 +3746,6 @@ public class MessagesController implements NotificationCenter.NotificationCenter continue; } } - if (loadType != 1 && message.post && !message.out) { - message.media_unread = true; - } MessageObject messageObject = new MessageObject(message, usersDict, chatsDict, false); new_dialogMessage.put(messageObject.getDialogId(), messageObject); } @@ -3719,18 +4103,69 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } - public void processDialogsUpdateRead(final HashMap dialogsToUpdate) { + public void reloadMentionsCountForChannels(final ArrayList arrayList) { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - for (HashMap.Entry entry : dialogsToUpdate.entrySet()) { - TLRPC.TL_dialog currentDialog = dialogs_dict.get(entry.getKey()); - if (currentDialog != null) { - currentDialog.unread_count = entry.getValue(); + for (int a = 0; a < arrayList.size(); a++) { + final long dialog_id = -arrayList.get(a); + TLRPC.TL_messages_getUnreadMentions req = new TLRPC.TL_messages_getUnreadMentions(); + req.peer = MessagesController.getInputPeer((int) dialog_id); + req.limit = 1; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, final TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + TLRPC.messages_Messages res = (TLRPC.messages_Messages) response; + if (res != null) { + int newCount; + if (res.count != 0) { + newCount = res.count; + } else { + newCount = res.messages.size(); + } + MessagesStorage.getInstance().resetMentionsCount(dialog_id, newCount); + } + } + }); + } + }); + } + } + }); + } + + public void processDialogsUpdateRead(final HashMap dialogsToUpdate, final HashMap dialogsMentionsToUpdate) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (dialogsToUpdate != null) { + for (HashMap.Entry entry : dialogsToUpdate.entrySet()) { + Long dialogId = entry.getKey(); + TLRPC.TL_dialog currentDialog = dialogs_dict.get(dialogId); + if (currentDialog != null) { + currentDialog.unread_count = entry.getValue(); + } + } + } + if (dialogsMentionsToUpdate != null) { + for (HashMap.Entry entry : dialogsMentionsToUpdate.entrySet()) { + Long dialogId = entry.getKey(); + TLRPC.TL_dialog currentDialog = dialogs_dict.get(dialogId); + if (currentDialog != null) { + currentDialog.unread_mentions_count = entry.getValue(); + if (createdDialogMainThreadIds.contains(currentDialog.id)) { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateMentionsCount, currentDialog.id, currentDialog.unread_mentions_count); + } + } } } NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_READ_DIALOG_MESSAGE); - NotificationsController.getInstance().processDialogsUpdateRead(dialogsToUpdate); + if (dialogsToUpdate != null) { + NotificationsController.getInstance().processDialogsUpdateRead(dialogsToUpdate); + } } }); } @@ -3742,7 +4177,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } TLRPC.TL_messages_getHistory req = new TLRPC.TL_messages_getHistory(); req.peer = peer == null ? getInputPeer(lower_id) : peer; - if (req.peer == null) { + if (req.peer == null || req.peer instanceof TLRPC.TL_inputPeerChannel) { return; } req.limit = 1; @@ -3753,7 +4188,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter NativeByteBuffer data = null; try { data = new NativeByteBuffer(48 + req.peer.getObjectSize()); - data.writeInt32(5); + data.writeInt32(8); data.writeInt64(dialog.id); data.writeInt32(dialog.top_message); data.writeInt32(dialog.read_inbox_max_id); @@ -3764,6 +4199,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter data.writeInt32(dialog.flags); data.writeBool(dialog.pinned); data.writeInt32(dialog.pinnedNum); + data.writeInt32(dialog.unread_mentions_count); peer.serializeToStream(data); } catch (Exception e) { FileLog.e(e); @@ -3788,6 +4224,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter newDialog.notify_settings = dialog.notify_settings; newDialog.pts = dialog.pts; newDialog.unread_count = dialog.unread_count; + newDialog.unread_mentions_count = dialog.unread_mentions_count; newDialog.read_inbox_max_id = dialog.read_inbox_max_id; newDialog.read_outbox_max_id = dialog.read_outbox_max_id; newDialog.pinned = dialog.pinned; @@ -3927,6 +4364,12 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } else { currentDialog.unread_count = value.unread_count; + if (currentDialog.unread_mentions_count != value.unread_mentions_count) { + currentDialog.unread_mentions_count = value.unread_mentions_count; + if (createdDialogMainThreadIds.contains(currentDialog.id)) { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateMentionsCount, currentDialog.id, currentDialog.unread_mentions_count); + } + } MessageObject oldMsg = dialogMessage.get(key); if (oldMsg == null || currentDialog.top_message > 0) { if (oldMsg != null && oldMsg.deleted || value.top_message > currentDialog.top_message) { @@ -3980,18 +4423,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter }); } - public void addToViewsQueue(final TLRPC.Message message, final boolean reload) { - ArrayList arrayList = new ArrayList<>(); - long messageId = message.id; - if (message.to_id.channel_id != 0) { - messageId |= ((long) message.to_id.channel_id) << 32; - } - arrayList.add(messageId); - MessagesStorage.getInstance().markMessagesContentAsRead(arrayList, 0); + public void addToViewsQueue(final TLRPC.Message message) { Utilities.stageQueue.postRunnable(new Runnable() { @Override public void run() { - SparseArray> array = channelViewsToSend;//reload ? channelViewsToReload : channelViewsToSend; int peer; if (message.to_id.channel_id != 0) { peer = -message.to_id.channel_id; @@ -4000,10 +4435,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter } else { peer = message.to_id.user_id; } - ArrayList ids = array.get(peer); + ArrayList ids = channelViewsToSend.get(peer); if (ids == null) { ids = new ArrayList<>(); - array.put(peer, ids); + channelViewsToSend.put(peer, ids); } if (!ids.contains(message.id)) { ids.add(message.id); @@ -4018,14 +4453,62 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (messageObject.messageOwner.to_id.channel_id != 0) { messageId |= ((long) messageObject.messageOwner.to_id.channel_id) << 32; } + if (messageObject.messageOwner.mentioned) { + MessagesStorage.getInstance().markMentionMessageAsRead(messageObject.getId(), messageObject.messageOwner.to_id.channel_id, messageObject.getDialogId()); + } arrayList.add(messageId); MessagesStorage.getInstance().markMessagesContentAsRead(arrayList, 0); NotificationCenter.getInstance().postNotificationName(NotificationCenter.messagesReadContent, arrayList); if (messageObject.getId() < 0) { markMessageAsRead(messageObject.getDialogId(), messageObject.messageOwner.random_id, Integer.MIN_VALUE); + } else { + if (messageObject.messageOwner.to_id.channel_id != 0) { + TLRPC.TL_channels_readMessageContents req = new TLRPC.TL_channels_readMessageContents(); + req.channel = getInputChannel(messageObject.messageOwner.to_id.channel_id); + if (req.channel == null) { + return; + } + req.id.add(messageObject.getId()); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + + } + }); + } else { + TLRPC.TL_messages_readMessageContents req = new TLRPC.TL_messages_readMessageContents(); + req.id.add(messageObject.getId()); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + if (error == null) { + TLRPC.TL_messages_affectedMessages res = (TLRPC.TL_messages_affectedMessages) response; + processNewDifferenceParams(-1, res.pts, -1, res.pts_count); + } + } + }); + } + } + } + + public void markMentionMessageAsRead(final int mid, final int channelId, final long did) { + MessagesStorage.getInstance().markMentionMessageAsRead(mid, channelId, did); + if (channelId != 0) { + TLRPC.TL_channels_readMessageContents req = new TLRPC.TL_channels_readMessageContents(); + req.channel = getInputChannel(channelId); + if (req.channel == null) { + return; + } + req.id.add(mid); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + + } + }); } else { TLRPC.TL_messages_readMessageContents req = new TLRPC.TL_messages_readMessageContents(); - req.id.add(messageObject.getId()); + req.id.add(mid); ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override public void run(TLObject response, TLRPC.TL_error error) { @@ -4044,17 +4527,29 @@ public class MessagesController implements NotificationCenter.NotificationCenter } int time = ConnectionsManager.getInstance().getCurrentTime(); MessagesStorage.getInstance().createTaskForMid(mid, channelId, time, time, ttl, false); - TLRPC.TL_messages_readMessageContents req = new TLRPC.TL_messages_readMessageContents(); - req.id.add(mid); - ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { - @Override - public void run(TLObject response, TLRPC.TL_error error) { - if (error == null) { - TLRPC.TL_messages_affectedMessages res = (TLRPC.TL_messages_affectedMessages) response; - processNewDifferenceParams(-1, res.pts, -1, res.pts_count); + if (channelId != 0) { + TLRPC.TL_channels_readMessageContents req = new TLRPC.TL_channels_readMessageContents(); + req.channel = getInputChannel(channelId); + req.id.add(mid); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + } - } - }); + }); + } else { + TLRPC.TL_messages_readMessageContents req = new TLRPC.TL_messages_readMessageContents(); + req.id.add(mid); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + if (error == null) { + TLRPC.TL_messages_affectedMessages res = (TLRPC.TL_messages_affectedMessages) response; + processNewDifferenceParams(-1, res.pts, -1, res.pts_count); + } + } + }); + } } public void markMessageAsRead(final long dialog_id, final long random_id, int ttl) { @@ -4457,6 +4952,26 @@ public class MessagesController implements NotificationCenter.NotificationCenter }, ConnectionsManager.RequestFlagInvokeAfter); } + public void toogleChannelInvitesHistory(int chat_id, boolean enabled) { + TLRPC.TL_channels_togglePreHistoryHidden req = new TLRPC.TL_channels_togglePreHistoryHidden(); + req.channel = getInputChannel(chat_id); + req.enabled = enabled; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + if (response != null) { + processUpdates((TLRPC.Updates) response, false); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_CHANNEL); + } + }); + } + } + }, ConnectionsManager.RequestFlagInvokeAfter); + } + public void updateChannelAbout(int chat_id, final String about, final TLRPC.ChatFull info) { if (info == null) { return; @@ -4580,27 +5095,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter request = req; joiningToChannels.add(chat_id); } else { - /*if (user.bot && !isMegagroup) { TODO - TLRPC.TL_channels_editAdmin req = new TLRPC.TL_channels_editAdmin(); - req.channel = getInputChannel(chat_id); - req.user_id = getInputUser(user); - req.rights = new TLRPC.TL_channelAdminRights(); - req.rights.change_info = true; - req.rights.post_messages = true; - req.rights.delete_messages = true; - req.rights.ban_users = true; - req.rights.invite_users = true; - req.rights.invite_link = true; - req.rights.pin_messages = true; - req.rights.start_calls = true; - req.rights.add_admins = true; - request = req; - } else {*/ - TLRPC.TL_channels_inviteToChannel req = new TLRPC.TL_channels_inviteToChannel(); - req.channel = getInputChannel(chat_id); - req.users.add(inputUser); - request = req; - //} + TLRPC.TL_channels_inviteToChannel req = new TLRPC.TL_channels_inviteToChannel(); + req.channel = getInputChannel(chat_id); + req.users.add(inputUser); + request = req; } } else { TLRPC.TL_messages_addChatUser req = new TLRPC.TL_messages_addChatUser(); @@ -4698,6 +5196,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter } public void deleteUserFromChat(final int chat_id, final TLRPC.User user, final TLRPC.ChatFull info) { + deleteUserFromChat(chat_id, user, info, false); + } + + public void deleteUserFromChat(final int chat_id, final TLRPC.User user, final TLRPC.ChatFull info, boolean forceDelete) { if (user == null) { return; } @@ -4708,7 +5210,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter final boolean isChannel = ChatObject.isChannel(chat); if (isChannel) { if (inputUser instanceof TLRPC.TL_inputUserSelf) { - if (chat.creator) { + if (chat.creator && forceDelete) { TLRPC.TL_channels_deleteChannel req = new TLRPC.TL_channels_deleteChannel(); req.channel = getInputChannel(chat); request = req; @@ -4876,7 +5378,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter SharedPreferences.Editor editor = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE).edit(); editor.clear().commit(); editor = ApplicationLoader.applicationContext.getSharedPreferences("emoji", Activity.MODE_PRIVATE).edit(); - editor.putLong("lastGifLoadTime", 0).putLong("lastStickersLoadTime", 0).commit(); + editor.putLong("lastGifLoadTime", 0).putLong("lastStickersLoadTime", 0).putLong("lastStickersLoadTimeMask", 0).putLong("lastStickersLoadTimeFavs", 0).commit(); editor = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE).edit(); editor.remove("gifhint").commit(); @@ -4899,20 +5401,6 @@ public class MessagesController implements NotificationCenter.NotificationCenter ContactsController.getInstance().deleteAllAppAccounts(); } -// public void generateJoinedMessage(final int uid) { -// Utilities.stageQueue.postRunnable(new Runnable() { -// @Override -// public void run() { -// TLRPC.TL_updateContactRegistered update = new TLRPC.TL_updateContactRegistered(); -// update.user_id = uid; -// update.date = (int) (System.currentTimeMillis() / 1000); -// ArrayList updates = new ArrayList<>(); -// updates.add(update); -// processUpdateArray(updates, null, null, false); -// } -// }); -// } - public void generateUpdateMessage() { if (BuildVars.DEBUG_VERSION || UserConfig.lastUpdateVersion == null || UserConfig.lastUpdateVersion.equals(BuildVars.BUILD_VERSION_STRING)) { return; @@ -5257,6 +5745,20 @@ public class MessagesController implements NotificationCenter.NotificationCenter getChannelDifference(channelId, 0, 0, null); } + public static boolean isSupportId(int id) { + return id / 1000 == 777 || id == 333000 || + id == 4240000 || id == 4240000 || id == 4244000 || + id == 4245000 || id == 4246000 || id == 410000 || + id == 420000 || id == 431000 || id == 431415000 || + id == 434000 || id == 4243000 || id == 439000 || + id == 449000 || id == 450000 || id == 452000 || + id == 454000 || id == 4254000 || id == 455000 || + id == 460000 || id == 470000 || id == 479000 || + id == 796000 || id == 482000 || id == 490000 || + id == 496000 || id == 497000 || id == 498000 || + id == 4298000; + } + protected void getChannelDifference(final int channelId, final int newDialogType, final long taskId, TLRPC.InputChannel inputChannel) { Boolean gettingDifferenceChannel = gettingDifferenceChannels.get(channelId); if (gettingDifferenceChannel == null) { @@ -5291,7 +5793,14 @@ public class MessagesController implements NotificationCenter.NotificationCenter } if (inputChannel == null) { - inputChannel = getInputChannel(channelId); + TLRPC.Chat chat = getChat(channelId); + if (chat == null) { + chat = MessagesStorage.getInstance().getChatSync(channelId); + if (chat != null) { + putChat(chat, true); + } + } + inputChannel = getInputChannel(chat); } if (inputChannel == null || inputChannel.access_hash == 0) { if (taskId != 0) { @@ -5547,7 +6056,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter getDifference(MessagesStorage.lastPtsValue, MessagesStorage.lastDateValue, MessagesStorage.lastQtsValue, false); } - public void getDifference(int pts, int date, int qts, boolean slice) { + public void getDifference(int pts, final int date, final int qts, boolean slice) { registerForPush(UserConfig.pushString); if (MessagesStorage.lastPtsValue == 0) { loadCurrentState(); @@ -5561,218 +6070,249 @@ public class MessagesController implements NotificationCenter.NotificationCenter req.pts = pts; req.date = date; req.qts = qts; + if (getDifferenceFirstSync) { + req.flags |= 1; + if (ConnectionsManager.isConnectedOrConnectingToWiFi()) { + req.pts_total_limit = 5000; + } else { + req.pts_total_limit = 1000; + } + getDifferenceFirstSync = false; + } if (req.date == 0) { req.date = ConnectionsManager.getInstance().getCurrentTime(); } - FileLog.e("start getDifference with date = " + MessagesStorage.lastDateValue + " pts = " + MessagesStorage.lastPtsValue + " seq = " + MessagesStorage.lastSeqValue); + FileLog.e("start getDifference with date = " + date + " pts = " + pts + " qts = " + qts); ConnectionsManager.getInstance().setIsUpdating(true); ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override public void run(TLObject response, TLRPC.TL_error error) { if (error == null) { final TLRPC.updates_Difference res = (TLRPC.updates_Difference) response; - - if (res instanceof TLRPC.TL_updates_differenceSlice) { - getDifference(res.intermediate_state.pts, res.intermediate_state.date, res.intermediate_state.qts, true); - } - - final HashMap usersDict = new HashMap<>(); - final HashMap chatsDict = new HashMap<>(); - for (int a = 0; a < res.users.size(); a++) { - TLRPC.User user = res.users.get(a); - usersDict.put(user.id, user); - } - for (int a = 0; a < res.chats.size(); a++) { - TLRPC.Chat chat = res.chats.get(a); - chatsDict.put(chat.id, chat); - } - - final ArrayList msgUpdates = new ArrayList<>(); - if (!res.other_updates.isEmpty()) { - for (int a = 0; a < res.other_updates.size(); a++) { - TLRPC.Update upd = res.other_updates.get(a); - if (upd instanceof TLRPC.TL_updateMessageID) { - msgUpdates.add((TLRPC.TL_updateMessageID) upd); - res.other_updates.remove(a); - a--; + if (res instanceof TLRPC.TL_updates_differenceTooLong) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + loadedFullUsers.clear(); + loadedFullChats.clear(); + resetDialogs(true, MessagesStorage.lastSeqValue, res.pts, date, qts); } + }); + } else { + if (res instanceof TLRPC.TL_updates_differenceSlice) { + getDifference(res.intermediate_state.pts, res.intermediate_state.date, res.intermediate_state.qts, true); } - } - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - putUsers(res.users, false); - putChats(res.chats, false); + final HashMap usersDict = new HashMap<>(); + final HashMap chatsDict = new HashMap<>(); + for (int a = 0; a < res.users.size(); a++) { + TLRPC.User user = res.users.get(a); + usersDict.put(user.id, user); + } + for (int a = 0; a < res.chats.size(); a++) { + TLRPC.Chat chat = res.chats.get(a); + chatsDict.put(chat.id, chat); } - }); - MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { - @Override - public void run() { - MessagesStorage.getInstance().putUsersAndChats(res.users, res.chats, true, false); - if (!msgUpdates.isEmpty()) { - final HashMap corrected = new HashMap<>(); - for (int a = 0; a < msgUpdates.size(); a++) { - TLRPC.TL_updateMessageID update = msgUpdates.get(a); - long[] ids = MessagesStorage.getInstance().updateMessageStateAndId(update.random_id, null, update.id, 0, false, 0); - if (ids != null) { - corrected.put(update.id, ids); + final ArrayList msgUpdates = new ArrayList<>(); + if (!res.other_updates.isEmpty()) { + for (int a = 0; a < res.other_updates.size(); a++) { + TLRPC.Update upd = res.other_updates.get(a); + if (upd instanceof TLRPC.TL_updateMessageID) { + msgUpdates.add((TLRPC.TL_updateMessageID) upd); + res.other_updates.remove(a); + a--; + } else if (getUpdateType(upd) == 2) { + int channelId = getUpdateChannelId(upd); + Integer channelPts = channelsPts.get(channelId); + if (channelPts == null) { + channelPts = MessagesStorage.getInstance().getChannelPtsSync(channelId); + if (channelPts != 0) { + channelsPts.put(channelId, channelPts); + } + } + if (channelPts != 0 && upd.pts <= channelPts) { + res.other_updates.remove(a); + a--; } } - - if (!corrected.isEmpty()) { - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - for (HashMap.Entry entry : corrected.entrySet()) { - Integer newId = entry.getKey(); - long[] ids = entry.getValue(); - Integer oldId = (int) ids[1]; - SendMessagesHelper.getInstance().processSentMessage(oldId); - NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageReceivedByServer, oldId, newId, null, ids[0]); - } - } - }); - } } + } - Utilities.stageQueue.postRunnable(new Runnable() { - @Override - public void run() { - if (!res.new_messages.isEmpty() || !res.new_encrypted_messages.isEmpty()) { - final HashMap> messages = new HashMap<>(); - for (int b = 0; b < res.new_encrypted_messages.size(); b++) { - TLRPC.EncryptedMessage encryptedMessage = res.new_encrypted_messages.get(b); - ArrayList decryptedMessages = SecretChatHelper.getInstance().decryptMessage(encryptedMessage); - if (decryptedMessages != null && !decryptedMessages.isEmpty()) { - for (int a = 0; a < decryptedMessages.size(); a++) { - TLRPC.Message message = decryptedMessages.get(a); - res.new_messages.add(message); - } - } - } - - ImageLoader.saveMessagesThumbs(res.new_messages); - - final ArrayList pushMessages = new ArrayList<>(); - int clientUserId = UserConfig.getClientUserId(); - for (int a = 0; a < res.new_messages.size(); a++) { - TLRPC.Message message = res.new_messages.get(a); - if (message.dialog_id == 0) { - if (message.to_id.chat_id != 0) { - message.dialog_id = -message.to_id.chat_id; - } else { - if (message.to_id.user_id == UserConfig.getClientUserId()) { - message.to_id.user_id = message.from_id; - } - message.dialog_id = message.to_id.user_id; - } - } - - if ((int) message.dialog_id != 0) { - if (message.action instanceof TLRPC.TL_messageActionChatDeleteUser) { - TLRPC.User user = usersDict.get(message.action.user_id); - if (user != null && user.bot) { - message.reply_markup = new TLRPC.TL_replyKeyboardHide(); - message.flags |= 64; - } - } - if (message.action instanceof TLRPC.TL_messageActionChatMigrateTo || message.action instanceof TLRPC.TL_messageActionChannelCreate) { - message.unread = false; - message.media_unread = false; - } else { - ConcurrentHashMap read_max = message.out ? dialogs_read_outbox_max : dialogs_read_inbox_max; - Integer value = read_max.get(message.dialog_id); - if (value == null) { - value = MessagesStorage.getInstance().getDialogReadMax(message.out, message.dialog_id); - read_max.put(message.dialog_id, value); - } - message.unread = value < message.id; - } - } - if (message.dialog_id == clientUserId) { - message.unread = false; - message.media_unread = false; - message.out = true; - } - - MessageObject obj = new MessageObject(message, usersDict, chatsDict, createdDialogIds.contains(message.dialog_id)); - - if (!obj.isOut() && obj.isUnread()) { - pushMessages.add(obj); - } - - ArrayList arr = messages.get(message.dialog_id); - if (arr == null) { - arr = new ArrayList<>(); - messages.put(message.dialog_id, arr); - } - arr.add(obj); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + loadedFullUsers.clear(); + loadedFullChats.clear(); + putUsers(res.users, false); + putChats(res.chats, false); + } + }); + + MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { + @Override + public void run() { + MessagesStorage.getInstance().putUsersAndChats(res.users, res.chats, true, false); + if (!msgUpdates.isEmpty()) { + final HashMap corrected = new HashMap<>(); + for (int a = 0; a < msgUpdates.size(); a++) { + TLRPC.TL_updateMessageID update = msgUpdates.get(a); + long[] ids = MessagesStorage.getInstance().updateMessageStateAndId(update.random_id, null, update.id, 0, false, 0); + if (ids != null) { + corrected.put(update.id, ids); } + } + if (!corrected.isEmpty()) { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - for (HashMap.Entry> pair : messages.entrySet()) { - Long key = pair.getKey(); - ArrayList value = pair.getValue(); - updateInterfaceWithMessages(key, value); + for (HashMap.Entry entry : corrected.entrySet()) { + Integer newId = entry.getKey(); + long[] ids = entry.getValue(); + Integer oldId = (int) ids[1]; + SendMessagesHelper.getInstance().processSentMessage(oldId); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageReceivedByServer, oldId, newId, null, ids[0]); } - NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload); } }); - MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { - @Override - public void run() { - if (!pushMessages.isEmpty()) { - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - NotificationsController.getInstance().processNewMessages(pushMessages, !(res instanceof TLRPC.TL_updates_differenceSlice)); - } - }); - } - MessagesStorage.getInstance().putMessages(res.new_messages, true, false, false, MediaController.getInstance().getAutodownloadMask()); - } - }); - - SecretChatHelper.getInstance().processPendingEncMessages(); } - - if (!res.other_updates.isEmpty()) { - processUpdateArray(res.other_updates, res.users, res.chats, true); - } - - if (res instanceof TLRPC.TL_updates_difference) { - gettingDifference = false; - MessagesStorage.lastSeqValue = res.state.seq; - MessagesStorage.lastDateValue = res.state.date; - MessagesStorage.lastPtsValue = res.state.pts; - MessagesStorage.lastQtsValue = res.state.qts; - ConnectionsManager.getInstance().setIsUpdating(false); - for (int a = 0; a < 3; a++) { - processUpdatesQueue(a, 1); - } - } else if (res instanceof TLRPC.TL_updates_differenceSlice) { - MessagesStorage.lastDateValue = res.intermediate_state.date; - MessagesStorage.lastPtsValue = res.intermediate_state.pts; - MessagesStorage.lastQtsValue = res.intermediate_state.qts; - } else if (res instanceof TLRPC.TL_updates_differenceEmpty) { - gettingDifference = false; - MessagesStorage.lastSeqValue = res.seq; - MessagesStorage.lastDateValue = res.date; - ConnectionsManager.getInstance().setIsUpdating(false); - for (int a = 0; a < 3; a++) { - processUpdatesQueue(a, 1); - } - } - MessagesStorage.getInstance().saveDiffParams(MessagesStorage.lastSeqValue, MessagesStorage.lastPtsValue, MessagesStorage.lastDateValue, MessagesStorage.lastQtsValue); - FileLog.e("received difference with date = " + MessagesStorage.lastDateValue + " pts = " + MessagesStorage.lastPtsValue + " seq = " + MessagesStorage.lastSeqValue + " messages = " + res.new_messages.size() + " users = " + res.users.size() + " chats = " + res.chats.size() + " other updates = " + res.other_updates.size()); } - }); - } - }); + + Utilities.stageQueue.postRunnable(new Runnable() { + @Override + public void run() { + if (!res.new_messages.isEmpty() || !res.new_encrypted_messages.isEmpty()) { + final HashMap> messages = new HashMap<>(); + for (int b = 0; b < res.new_encrypted_messages.size(); b++) { + TLRPC.EncryptedMessage encryptedMessage = res.new_encrypted_messages.get(b); + ArrayList decryptedMessages = SecretChatHelper.getInstance().decryptMessage(encryptedMessage); + if (decryptedMessages != null && !decryptedMessages.isEmpty()) { + res.new_messages.addAll(decryptedMessages); + } + } + + ImageLoader.saveMessagesThumbs(res.new_messages); + + final ArrayList pushMessages = new ArrayList<>(); + int clientUserId = UserConfig.getClientUserId(); + for (int a = 0; a < res.new_messages.size(); a++) { + TLRPC.Message message = res.new_messages.get(a); + if (message.dialog_id == 0) { + if (message.to_id.chat_id != 0) { + message.dialog_id = -message.to_id.chat_id; + } else { + if (message.to_id.user_id == UserConfig.getClientUserId()) { + message.to_id.user_id = message.from_id; + } + message.dialog_id = message.to_id.user_id; + } + } + + if ((int) message.dialog_id != 0) { + if (message.action instanceof TLRPC.TL_messageActionChatDeleteUser) { + TLRPC.User user = usersDict.get(message.action.user_id); + if (user != null && user.bot) { + message.reply_markup = new TLRPC.TL_replyKeyboardHide(); + message.flags |= 64; + } + } + if (message.action instanceof TLRPC.TL_messageActionChatMigrateTo || message.action instanceof TLRPC.TL_messageActionChannelCreate) { + message.unread = false; + message.media_unread = false; + } else { + ConcurrentHashMap read_max = message.out ? dialogs_read_outbox_max : dialogs_read_inbox_max; + Integer value = read_max.get(message.dialog_id); + if (value == null) { + value = MessagesStorage.getInstance().getDialogReadMax(message.out, message.dialog_id); + read_max.put(message.dialog_id, value); + } + message.unread = value < message.id; + } + } + if (message.dialog_id == clientUserId) { + message.unread = false; + message.media_unread = false; + message.out = true; + } + + MessageObject obj = new MessageObject(message, usersDict, chatsDict, createdDialogIds.contains(message.dialog_id)); + + if (!obj.isOut() && obj.isUnread()) { + pushMessages.add(obj); + } + + ArrayList arr = messages.get(message.dialog_id); + if (arr == null) { + arr = new ArrayList<>(); + messages.put(message.dialog_id, arr); + } + arr.add(obj); + } + + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + for (HashMap.Entry> pair : messages.entrySet()) { + Long key = pair.getKey(); + ArrayList value = pair.getValue(); + updateInterfaceWithMessages(key, value); + } + NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload); + } + }); + MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { + @Override + public void run() { + if (!pushMessages.isEmpty()) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + NotificationsController.getInstance().processNewMessages(pushMessages, !(res instanceof TLRPC.TL_updates_differenceSlice)); + } + }); + } + MessagesStorage.getInstance().putMessages(res.new_messages, true, false, false, MediaController.getInstance().getAutodownloadMask()); + } + }); + + SecretChatHelper.getInstance().processPendingEncMessages(); + } + + if (!res.other_updates.isEmpty()) { + processUpdateArray(res.other_updates, res.users, res.chats, true); + } + + if (res instanceof TLRPC.TL_updates_difference) { + gettingDifference = false; + MessagesStorage.lastSeqValue = res.state.seq; + MessagesStorage.lastDateValue = res.state.date; + MessagesStorage.lastPtsValue = res.state.pts; + MessagesStorage.lastQtsValue = res.state.qts; + ConnectionsManager.getInstance().setIsUpdating(false); + for (int a = 0; a < 3; a++) { + processUpdatesQueue(a, 1); + } + } else if (res instanceof TLRPC.TL_updates_differenceSlice) { + MessagesStorage.lastDateValue = res.intermediate_state.date; + MessagesStorage.lastPtsValue = res.intermediate_state.pts; + MessagesStorage.lastQtsValue = res.intermediate_state.qts; + } else if (res instanceof TLRPC.TL_updates_differenceEmpty) { + gettingDifference = false; + MessagesStorage.lastSeqValue = res.seq; + MessagesStorage.lastDateValue = res.date; + ConnectionsManager.getInstance().setIsUpdating(false); + for (int a = 0; a < 3; a++) { + processUpdatesQueue(a, 1); + } + } + MessagesStorage.getInstance().saveDiffParams(MessagesStorage.lastSeqValue, MessagesStorage.lastPtsValue, MessagesStorage.lastDateValue, MessagesStorage.lastQtsValue); + FileLog.e("received difference with date = " + MessagesStorage.lastDateValue + " pts = " + MessagesStorage.lastPtsValue + " seq = " + MessagesStorage.lastSeqValue + " messages = " + res.new_messages.size() + " users = " + res.users.size() + " chats = " + res.chats.size() + " other updates = " + res.other_updates.size()); + } + }); + } + }); + } } else { gettingDifference = false; ConnectionsManager.getInstance().setIsUpdating(false); @@ -6104,7 +6644,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter }); } }); - MessagesStorage.getInstance().putMessages(messagesArr, true, true, false, MediaController.getInstance().getAutodownloadMask()); + MessagesStorage.getInstance().putMessages(messagesArr, true, true, false, 0); AndroidUtilities.runOnUIThread(new Runnable() { @Override @@ -6115,10 +6655,6 @@ public class MessagesController implements NotificationCenter.NotificationCenter }); } - public void convertGroup() { - - } - public void checkChannelInviter(final int chat_id) { AndroidUtilities.runOnUIThread(new Runnable() { @Override @@ -6188,7 +6724,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter }); } }); - MessagesStorage.getInstance().putMessages(messagesArr, true, true, false, MediaController.getInstance().getAutodownloadMask()); + MessagesStorage.getInstance().putMessages(messagesArr, true, true, false, 0); AndroidUtilities.runOnUIThread(new Runnable() { @Override @@ -6459,12 +6995,21 @@ public class MessagesController implements NotificationCenter.NotificationCenter for (int a = 0; a < updates.updates.size(); a++) { TLRPC.Update update = updates.updates.get(a); if (update instanceof TLRPC.TL_updateNewChannelMessage) { - int channelId = ((TLRPC.TL_updateNewChannelMessage) update).message.to_id.channel_id; + TLRPC.Message message = ((TLRPC.TL_updateNewChannelMessage) update).message; + int channelId = message.to_id.channel_id; if (minChannels.containsKey(channelId)) { FileLog.e("need get diff because of min channel " + channelId); needGetDiff = true; break; } + /*if (message.fwd_from != null && message.fwd_from.channel_id != 0) { + channelId = message.fwd_from.channel_id; + if (minChannels.containsKey(channelId)) { + FileLog.e("need get diff because of min forward channel " + channelId); + needGetDiff = true; + break; + } + }*/ } } } @@ -6735,6 +7280,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter final ArrayList markAsReadMessages = new ArrayList<>(); final HashMap markAsReadEncrypted = new HashMap<>(); final SparseArray> deletedMessages = new SparseArray<>(); + final SparseArray clearHistoryMessages = new SparseArray<>(); boolean printChanged = false; final ArrayList chatInfoToUpdate = new ArrayList<>(); final ArrayList updatesOnMainThread = new ArrayList<>(); @@ -6924,6 +7470,12 @@ public class MessagesController implements NotificationCenter.NotificationCenter long id = update.messages.get(a); markAsReadMessages.add(id); } + } else if (update instanceof TLRPC.TL_updateChannelReadMessagesContents) { + for (int a = 0; a < update.messages.size(); a++) { + long id = update.messages.get(a); + id |= ((long) update.channel_id) << 32; + markAsReadMessages.add(id); + } } else if (update instanceof TLRPC.TL_updateReadHistoryInbox || update instanceof TLRPC.TL_updateReadHistoryOutbox) { long dialog_id; ConcurrentHashMap read_max; @@ -7404,6 +7956,15 @@ public class MessagesController implements NotificationCenter.NotificationCenter LocaleController.getInstance().saveRemoteLocaleStrings(update.difference); } else if (update instanceof TLRPC.TL_updateLangPackTooLong) { LocaleController.getInstance().reloadCurrentRemoteLocale(); + } else if (update instanceof TLRPC.TL_updateFavedStickers) { + updatesOnMainThread.add(update); + } else if (update instanceof TLRPC.TL_updateContactsReset) { + updatesOnMainThread.add(update); + } else if (update instanceof TLRPC.TL_updateChannelAvailableMessages) { + Integer currentValue = clearHistoryMessages.get(update.channel_id); + if (currentValue == null || currentValue < update.available_min_id) { + clearHistoryMessages.put(update.channel_id, update.available_min_id); + } } } if (!messages.isEmpty()) { @@ -7472,7 +8033,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter SharedPreferences.Editor editor = null; for (int a = 0; a < updatesOnMainThread.size(); a++) { final TLRPC.Update update = updatesOnMainThread.get(a); - final TLRPC.User toDbUser = new TLRPC.User(); + final TLRPC.User toDbUser = new TLRPC.TL_user(); toDbUser.id = update.user_id; final TLRPC.User currentUser = getUser(update.user_id); if (update instanceof TLRPC.TL_updatePrivacy) { @@ -7644,6 +8205,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter StickersQuery.loadStickers(update.masks ? StickersQuery.TYPE_MASK : StickersQuery.TYPE_IMAGE, false, true); } else if (update instanceof TLRPC.TL_updateStickerSetsOrder) { StickersQuery.reorderStickers(update.masks ? StickersQuery.TYPE_MASK : StickersQuery.TYPE_IMAGE, ((TLRPC.TL_updateStickerSetsOrder) update).order); + } else if (update instanceof TLRPC.TL_updateFavedStickers) { + StickersQuery.loadRecents(StickersQuery.TYPE_FAVE, false, false, true); + } else if (update instanceof TLRPC.TL_updateContactsReset) { + ContactsController.getInstance().forceImportContacts(); } else if (update instanceof TLRPC.TL_updateNewStickerSet) { StickersQuery.addNewStickerSet(update.stickerset); } else if (update instanceof TLRPC.TL_updateSavedGifs) { @@ -7719,6 +8284,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } } + } else if (update instanceof TLRPC.TL_updateGroupCall) { + + } else if (update instanceof TLRPC.TL_updateGroupCallParticipant) { + } } if (editor != null) { @@ -7833,15 +8402,17 @@ public class MessagesController implements NotificationCenter.NotificationCenter int key = markAsReadMessagesInbox.keyAt(b); int messageId = (int) ((long) markAsReadMessagesInbox.get(key)); TLRPC.TL_dialog dialog = dialogs_dict.get((long) key); - if (dialog != null && dialog.top_message <= messageId) { + if (dialog != null && dialog.top_message > 0 && dialog.top_message <= messageId) { MessageObject obj = dialogMessage.get(dialog.id); if (obj != null && !obj.isOut()) { obj.setIsRead(); updateMask |= UPDATE_MASK_READ_DIALOG_MESSAGE; } } - editor.remove("diditem" + key); - editor.remove("diditemo" + key); + if (key != UserConfig.getClientUserId()) { + editor.remove("diditem" + key); + editor.remove("diditemo" + key); + } } editor.commit(); } @@ -7849,7 +8420,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter int key = markAsReadMessagesOutbox.keyAt(b); int messageId = (int) ((long) markAsReadMessagesOutbox.get(key)); TLRPC.TL_dialog dialog = dialogs_dict.get((long) key); - if (dialog != null && dialog.top_message <= messageId) { + if (dialog != null && dialog.top_message > 0 && dialog.top_message <= messageId) { MessageObject obj = dialogMessage.get(dialog.id); if (obj != null && obj.isOut()) { obj.setIsRead(); @@ -7905,6 +8476,25 @@ public class MessagesController implements NotificationCenter.NotificationCenter } NotificationsController.getInstance().removeDeletedMessagesFromNotifications(deletedMessages); } + if (clearHistoryMessages.size() != 0) { + for (int a = 0; a < clearHistoryMessages.size(); a++) { + int key = clearHistoryMessages.keyAt(a); + Integer id = clearHistoryMessages.get(key); + if (id == null) { + continue; + } + long did = (long) -key; + NotificationCenter.getInstance().postNotificationName(NotificationCenter.historyCleared, did, id); + MessageObject obj = dialogMessage.get(did); + if (obj != null) { + if (obj.getId() <= id) { + obj.deleted = true; + break; + } + } + } + NotificationsController.getInstance().removeDeletedHisoryFromNotifications(clearHistoryMessages); + } if (updateMask != 0) { NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateInterfaces, updateMask); } @@ -7916,9 +8506,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (!webPages.isEmpty()) { MessagesStorage.getInstance().putWebPages(webPages); } - if (markAsReadMessagesInbox.size() != 0 || markAsReadMessagesOutbox.size() != 0 || !markAsReadEncrypted.isEmpty()) { - if (markAsReadMessagesInbox.size() != 0) { - MessagesStorage.getInstance().updateDialogsWithReadMessages(markAsReadMessagesInbox, markAsReadMessagesOutbox, true); + if (markAsReadMessagesInbox.size() != 0 || markAsReadMessagesOutbox.size() != 0 || !markAsReadEncrypted.isEmpty() || !markAsReadMessages.isEmpty()) { + if (markAsReadMessagesInbox.size() != 0 || !markAsReadMessages.isEmpty()) { + MessagesStorage.getInstance().updateDialogsWithReadMessages(markAsReadMessagesInbox, markAsReadMessagesOutbox, markAsReadMessages, true); } MessagesStorage.getInstance().markMessagesAsRead(markAsReadMessagesInbox, markAsReadMessagesOutbox, markAsReadEncrypted, true); } @@ -7938,6 +8528,19 @@ public class MessagesController implements NotificationCenter.NotificationCenter }); } } + if (clearHistoryMessages.size() != 0) { + for (int a = 0; a < clearHistoryMessages.size(); a++) { + final int key = clearHistoryMessages.keyAt(a); + final Integer id = clearHistoryMessages.get(key); + MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { + @Override + public void run() { + ArrayList dialogIds = MessagesStorage.getInstance().markMessagesAsDeleted(key, id, false); + MessagesStorage.getInstance().updateDialogsWithDeletedMessages(new ArrayList(), dialogIds, false, key); + } + }); + } + } if (!tasks.isEmpty()) { for (int a = 0; a < tasks.size(); a++) { TLRPC.TL_updateEncryptedMessagesRead update = tasks.get(a); @@ -8028,7 +8631,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (message.isNewGif()) { StickersQuery.addRecentGif(message.messageOwner.media.document, message.messageOwner.date); } else if (message.isSticker()) { - StickersQuery.addRecentSticker(StickersQuery.TYPE_IMAGE, message.messageOwner.media.document, message.messageOwner.date); + StickersQuery.addRecentSticker(StickersQuery.TYPE_IMAGE, message.messageOwner.media.document, message.messageOwner.date, false); } } if (message.isOut() && message.isSent()) { @@ -8124,11 +8727,20 @@ public class MessagesController implements NotificationCenter.NotificationCenter public void sortDialogs(HashMap chatsDict) { dialogsServerOnly.clear(); dialogsGroupsOnly.clear(); + dialogsForward.clear(); + boolean selfAdded = false; + int selfId = UserConfig.getClientUserId(); Collections.sort(dialogs, dialogComparator); for (int a = 0; a < dialogs.size(); a++) { TLRPC.TL_dialog d = dialogs.get(a); int high_id = (int) (d.id >> 32); int lower_id = (int) d.id; + if (lower_id == selfId) { + dialogsForward.add(0, d); + selfAdded = true; + } else { + dialogsForward.add(d); + } if (lower_id != 0 && high_id != 1) { dialogsServerOnly.add(d); if (DialogObject.isChannel(d)) { @@ -8149,6 +8761,17 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } } + if (!selfAdded) { + TLRPC.User user = UserConfig.getCurrentUser(); + if (user != null) { + TLRPC.TL_dialog dialog = new TLRPC.TL_dialog(); + dialog.id = user.id; + dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); + dialog.peer = new TLRPC.TL_peerUser(); + dialog.peer.user_id = user.id; + dialogsForward.add(0, dialog); + } + } } private static String getRestrictionReason(String reason) { @@ -8177,6 +8800,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter } public static boolean checkCanOpenChat(Bundle bundle, BaseFragment fragment) { + return checkCanOpenChat(bundle, fragment, null); + } + + public static boolean checkCanOpenChat(final Bundle bundle, final BaseFragment fragment, MessageObject originalMessage) { if (bundle == null || fragment == null) { return true; } @@ -8184,6 +8811,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter TLRPC.Chat chat = null; int user_id = bundle.getInt("user_id", 0); int chat_id = bundle.getInt("chat_id", 0); + int messageId = bundle.getInt("message_id", 0); if (user_id != 0) { user = MessagesController.getInstance().getUser(user_id); } else if (chat_id != 0) { @@ -8202,6 +8830,69 @@ public class MessagesController implements NotificationCenter.NotificationCenter showCantOpenAlert(fragment, reason); return false; } + if (messageId != 0 && originalMessage != null && chat != null && chat.access_hash == 0) { + int did = (int) originalMessage.getDialogId(); + if (did != 0) { + final AlertDialog progressDialog = new AlertDialog(fragment.getParentActivity(), 1); + progressDialog.setMessage(LocaleController.getString("Loading", R.string.Loading)); + progressDialog.setCanceledOnTouchOutside(false); + progressDialog.setCancelable(false); + TLObject req; + if (did < 0) { + chat = getInstance().getChat(-did); + } + if (did > 0 || !ChatObject.isChannel(chat)) { + TLRPC.TL_messages_getMessages request = new TLRPC.TL_messages_getMessages(); + request.id.add(originalMessage.getId()); + req = request; + } else { + chat = getInstance().getChat(-did); + TLRPC.TL_channels_getMessages request = new TLRPC.TL_channels_getMessages(); + request.channel = getInputChannel(chat); + request.id.add(originalMessage.getId()); + req = request; + } + final int reqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, TLRPC.TL_error error) { + if (response != null) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + try { + progressDialog.dismiss(); + } catch (Exception e) { + FileLog.e(e); + } + TLRPC.messages_Messages res = (TLRPC.messages_Messages) response; + getInstance().putUsers(res.users, false); + getInstance().putChats(res.chats, false); + MessagesStorage.getInstance().putUsersAndChats(res.users, res.chats, true, true); + fragment.presentFragment(new ChatActivity(bundle), true); + } + }); + } + } + }); + progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, LocaleController.getString("Cancel", R.string.Cancel), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + ConnectionsManager.getInstance().cancelRequest(reqId, true); + try { + dialog.dismiss(); + } catch (Exception e) { + FileLog.e(e); + } + if (fragment != null) { + fragment.setVisibleDialog(null); + } + } + }); + fragment.setVisibleDialog(progressDialog); + progressDialog.show(); + return false; + } + } return true; } @@ -8264,10 +8955,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (fragment.getParentActivity() == null) { return; } - final AlertDialog progressDialog = new AlertDialog(fragment.getParentActivity(), 1); - progressDialog.setMessage(LocaleController.getString("Loading", R.string.Loading)); - progressDialog.setCanceledOnTouchOutside(false); - progressDialog.setCancelable(false); + final AlertDialog progressDialog[] = new AlertDialog[] {new AlertDialog(fragment.getParentActivity(), 1)}; TLRPC.TL_contacts_resolveUsername req = new TLRPC.TL_contacts_resolveUsername(); req.username = username; @@ -8278,10 +8966,11 @@ public class MessagesController implements NotificationCenter.NotificationCenter @Override public void run() { try { - progressDialog.dismiss(); - } catch (Exception e) { - FileLog.e(e); + progressDialog[0].dismiss(); + } catch (Exception ignored) { + } + progressDialog[0] = null; fragment.setVisibleDialog(null); if (error == null) { TLRPC.TL_contacts_resolvedPeer res = (TLRPC.TL_contacts_resolvedPeer) response; @@ -8306,22 +8995,29 @@ public class MessagesController implements NotificationCenter.NotificationCenter }); } }); - progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, LocaleController.getString("Cancel", R.string.Cancel), new DialogInterface.OnClickListener() { + AndroidUtilities.runOnUIThread(new Runnable() { @Override - public void onClick(DialogInterface dialog, int which) { - ConnectionsManager.getInstance().cancelRequest(reqId, true); - try { - dialog.dismiss(); - } catch (Exception e) { - FileLog.e(e); - } - if (fragment != null) { - fragment.setVisibleDialog(null); + public void run() { + if (progressDialog[0] == null) { + return; } + progressDialog[0].setMessage(LocaleController.getString("Loading", R.string.Loading)); + progressDialog[0].setCanceledOnTouchOutside(false); + progressDialog[0].setCancelable(false); + progressDialog[0].setButton(DialogInterface.BUTTON_NEGATIVE, LocaleController.getString("Cancel", R.string.Cancel), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + ConnectionsManager.getInstance().cancelRequest(reqId, true); + try { + dialog.dismiss(); + } catch (Exception e) { + FileLog.e(e); + } + } + }); + fragment.showDialog(progressDialog[0]); } - }); - fragment.setVisibleDialog(progressDialog); - progressDialog.show(); + }, 500); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java index 6ea959591..793912fae 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java @@ -38,6 +38,11 @@ import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicLong; public class MessagesStorage { + + public interface IntCallback { + void run(int param); + } + private DispatchQueue storageQueue = new DispatchQueue("storageQueue"); private SQLiteDatabase database; private File cacheFile; @@ -104,18 +109,20 @@ public class MessagesStorage { database.executeFast("CREATE TABLE media_holes_v2(uid INTEGER, type INTEGER, start INTEGER, end INTEGER, PRIMARY KEY(uid, type, start));").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS uid_end_media_holes_v2 ON media_holes_v2(uid, type, end);").stepThis().dispose(); - database.executeFast("CREATE TABLE messages(mid INTEGER PRIMARY KEY, uid INTEGER, read_state INTEGER, send_state INTEGER, date INTEGER, data BLOB, out INTEGER, ttl INTEGER, media INTEGER, replydata BLOB, imp INTEGER)").stepThis().dispose(); + database.executeFast("CREATE TABLE messages(mid INTEGER PRIMARY KEY, uid INTEGER, read_state INTEGER, send_state INTEGER, date INTEGER, data BLOB, out INTEGER, ttl INTEGER, media INTEGER, replydata BLOB, imp INTEGER, mention INTEGER)").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS uid_mid_idx_messages ON messages(uid, mid);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS uid_date_mid_idx_messages ON messages(uid, date, mid);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS mid_out_idx_messages ON messages(mid, out);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS task_idx_messages ON messages(uid, out, read_state, ttl, date, send_state);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS send_state_idx_messages ON messages(mid, send_state, date) WHERE mid < 0 AND send_state = 1;").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS uid_mention_idx_messages ON messages(uid, mention, read_state);").stepThis().dispose(); database.executeFast("CREATE TABLE download_queue(uid INTEGER, type INTEGER, date INTEGER, data BLOB, PRIMARY KEY (uid, type));").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS type_date_idx_download_queue ON download_queue(type, date);").stepThis().dispose(); - database.executeFast("CREATE TABLE user_phones_v6(uid INTEGER, phone TEXT, sphone TEXT, deleted INTEGER, PRIMARY KEY (uid, phone))").stepThis().dispose(); - database.executeFast("CREATE INDEX IF NOT EXISTS sphone_deleted_idx_user_phones ON user_phones_v6(sphone, deleted);").stepThis().dispose(); + database.executeFast("CREATE TABLE user_contacts_v7(key TEXT PRIMARY KEY, uid INTEGER, fname TEXT, sname TEXT, imported INTEGER)").stepThis().dispose(); + database.executeFast("CREATE TABLE user_phones_v7(key TEXT, phone TEXT, sphone TEXT, deleted INTEGER, PRIMARY KEY (key, phone))").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS sphone_deleted_idx_user_phones ON user_phones_v7(sphone, deleted);").stepThis().dispose(); database.executeFast("CREATE TABLE dialogs(did INTEGER PRIMARY KEY, date INTEGER, unread_count INTEGER, last_mid INTEGER, inbox_max INTEGER, outbox_max INTEGER, last_mid_i INTEGER, unread_count_i INTEGER, pts INTEGER, date_i INTEGER, pinned INTEGER)").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS date_idx_dialogs ON dialogs(date);").stepThis().dispose(); @@ -157,8 +164,9 @@ public class MessagesStorage { database.executeFast("CREATE TABLE users_data(uid INTEGER PRIMARY KEY, about TEXT)").stepThis().dispose(); database.executeFast("CREATE TABLE users(uid INTEGER PRIMARY KEY, name TEXT, status INTEGER, data BLOB)").stepThis().dispose(); database.executeFast("CREATE TABLE chats(uid INTEGER PRIMARY KEY, name TEXT, data BLOB)").stepThis().dispose(); - database.executeFast("CREATE TABLE enc_chats(uid INTEGER PRIMARY KEY, user INTEGER, name TEXT, data BLOB, g BLOB, authkey BLOB, ttl INTEGER, layer INTEGER, seq_in INTEGER, seq_out INTEGER, use_count INTEGER, exchange_id INTEGER, key_date INTEGER, fprint INTEGER, fauthkey BLOB, khash BLOB, in_seq_no INTEGER, admin_id INTEGER)").stepThis().dispose(); + database.executeFast("CREATE TABLE enc_chats(uid INTEGER PRIMARY KEY, user INTEGER, name TEXT, data BLOB, g BLOB, authkey BLOB, ttl INTEGER, layer INTEGER, seq_in INTEGER, seq_out INTEGER, use_count INTEGER, exchange_id INTEGER, key_date INTEGER, fprint INTEGER, fauthkey BLOB, khash BLOB, in_seq_no INTEGER, admin_id INTEGER, mtproto_seq INTEGER)").stepThis().dispose(); database.executeFast("CREATE TABLE channel_users_v2(did INTEGER, uid INTEGER, date INTEGER, data BLOB, PRIMARY KEY(did, uid))").stepThis().dispose(); + database.executeFast("CREATE TABLE channel_admins(did INTEGER, uid INTEGER, PRIMARY KEY(did, uid))").stepThis().dispose(); database.executeFast("CREATE TABLE contacts(uid INTEGER PRIMARY KEY, mutual INTEGER)").stepThis().dispose(); database.executeFast("CREATE TABLE wallpapers(uid INTEGER PRIMARY KEY, data BLOB)").stepThis().dispose(); database.executeFast("CREATE TABLE user_photos(uid INTEGER, id INTEGER, data BLOB, PRIMARY KEY (uid, id))").stepThis().dispose(); @@ -169,7 +177,6 @@ public class MessagesStorage { database.executeFast("CREATE TABLE stickers_featured(id INTEGER PRIMARY KEY, data BLOB, unread BLOB, date INTEGER, hash TEXT);").stepThis().dispose(); database.executeFast("CREATE TABLE hashtag_recent_v2(id TEXT PRIMARY KEY, date INTEGER);").stepThis().dispose(); database.executeFast("CREATE TABLE webpage_pending(id INTEGER, mid INTEGER, PRIMARY KEY (id, mid));").stepThis().dispose(); - database.executeFast("CREATE TABLE user_contacts_v6(uid INTEGER PRIMARY KEY, fname TEXT, sname TEXT)").stepThis().dispose(); database.executeFast("CREATE TABLE sent_files_v2(uid TEXT, type INTEGER, data BLOB, PRIMARY KEY (uid, type))").stepThis().dispose(); database.executeFast("CREATE TABLE search_recent(did INTEGER PRIMARY KEY, date INTEGER);").stepThis().dispose(); database.executeFast("CREATE TABLE media_counts_v2(uid INTEGER, type INTEGER, count INTEGER, PRIMARY KEY(uid, type))").stepThis().dispose(); @@ -177,9 +184,10 @@ public class MessagesStorage { database.executeFast("CREATE TABLE bot_info(uid INTEGER PRIMARY KEY, info BLOB)").stepThis().dispose(); database.executeFast("CREATE TABLE pending_tasks(id INTEGER PRIMARY KEY, data BLOB);").stepThis().dispose(); database.executeFast("CREATE TABLE requested_holes(uid INTEGER, seq_out_start INTEGER, seq_out_end INTEGER, PRIMARY KEY (uid, seq_out_start, seq_out_end));").stepThis().dispose(); + database.executeFast("CREATE TABLE sharing_locations(uid INTEGER PRIMARY KEY, mid INTEGER, date INTEGER, period INTEGER, message BLOB);").stepThis().dispose(); //version - database.executeFast("PRAGMA user_version = 41").stepThis().dispose(); + database.executeFast("PRAGMA user_version = 46").stepThis().dispose(); //database.executeFast("CREATE TABLE secret_holes(uid INTEGER, seq_in INTEGER, seq_out INTEGER, data BLOB, PRIMARY KEY (uid, seq_in, seq_out));").stepThis().dispose(); //database.executeFast("CREATE TABLE attach_data(uid INTEGER, id INTEGER, data BLOB, PRIMARY KEY (uid, id))").stepThis().dispose(); @@ -217,7 +225,7 @@ public class MessagesStorage { FileLog.e(e2); } } - if (version < 41) { + if (version < 46) { updateDbToLastVersion(version); } } @@ -225,6 +233,14 @@ public class MessagesStorage { FileLog.e(e); if (first && e.getMessage().contains("malformed")) { cleanupInternal(); + UserConfig.dialogsLoadOffsetId = 0; + UserConfig.totalDialogsLoadCount = 0; + UserConfig.dialogsLoadOffsetDate = 0; + UserConfig.dialogsLoadOffsetUserId = 0; + UserConfig.dialogsLoadOffsetChatId = 0; + UserConfig.dialogsLoadOffsetChannelId = 0; + UserConfig.dialogsLoadOffsetAccess = 0; + UserConfig.saveConfig(false); openDatabase(false); } } @@ -525,7 +541,36 @@ public class MessagesStorage { if (version == 40) { fixNotificationSettings(); database.executeFast("PRAGMA user_version = 41").stepThis().dispose(); - //version = 41; + version = 41; + } + if (version == 41) { + database.executeFast("ALTER TABLE messages ADD COLUMN mention INTEGER default 0").stepThis().dispose(); + database.executeFast("ALTER TABLE user_contacts_v6 ADD COLUMN imported INTEGER default 0").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS uid_mention_idx_messages ON messages(uid, mention, read_state);").stepThis().dispose(); + database.executeFast("PRAGMA user_version = 42").stepThis().dispose(); + version = 42; + } + if (version == 42) { + database.executeFast("CREATE TABLE IF NOT EXISTS sharing_locations(uid INTEGER PRIMARY KEY, mid INTEGER, date INTEGER, period INTEGER, message BLOB);").stepThis().dispose(); + database.executeFast("PRAGMA user_version = 43").stepThis().dispose(); + version = 43; + } + if (version == 43) { + database.executeFast("CREATE TABLE IF NOT EXISTS channel_admins(did INTEGER, uid INTEGER, PRIMARY KEY(did, uid))").stepThis().dispose(); + database.executeFast("PRAGMA user_version = 44").stepThis().dispose(); + version = 44; + } + if (version == 44) { + database.executeFast("CREATE TABLE IF NOT EXISTS user_contacts_v7(key TEXT PRIMARY KEY, uid INTEGER, fname TEXT, sname TEXT, imported INTEGER)").stepThis().dispose(); + database.executeFast("CREATE TABLE IF NOT EXISTS user_phones_v7(key TEXT, phone TEXT, sphone TEXT, deleted INTEGER, PRIMARY KEY (key, phone))").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS sphone_deleted_idx_user_phones ON user_phones_v7(sphone, deleted);").stepThis().dispose(); + database.executeFast("PRAGMA user_version = 45").stepThis().dispose(); + version = 45; + } + if (version == 45) { + database.executeFast("ALTER TABLE enc_chats ADD COLUMN mtproto_seq INTEGER default 0").stepThis().dispose(); + database.executeFast("PRAGMA user_version = 46").stepThis().dispose(); + //version = 46; } } catch (Exception e) { FileLog.e(e); @@ -729,7 +774,8 @@ public class MessagesStorage { break; } case 2: - case 5: { + case 5: + case 8: { final TLRPC.TL_dialog dialog = new TLRPC.TL_dialog(); dialog.id = data.readInt64(false); dialog.top_message = data.readInt32(false); @@ -739,10 +785,13 @@ public class MessagesStorage { dialog.last_message_date = data.readInt32(false); dialog.pts = data.readInt32(false); dialog.flags = data.readInt32(false); - if (type == 5) { + if (type >= 5) { dialog.pinned = data.readBool(false); dialog.pinnedNum = data.readInt32(false); } + if (type >= 8) { + dialog.unread_mentions_count = data.readInt32(false); + } final TLRPC.InputPeer peer = TLRPC.InputPeer.TLdeserialize(data, data.readInt32(false), false); AndroidUtilities.runOnUIThread(new Runnable() { @Override @@ -832,28 +881,32 @@ public class MessagesStorage { }); } + private void saveDiffParamsInternal(final int seq, final int pts, final int date, final int qts) { + try { + if (lastSavedSeq == seq && lastSavedPts == pts && lastSavedDate == date && lastQtsValue == qts) { + return; + } + SQLitePreparedStatement state = database.executeFast("UPDATE params SET seq = ?, pts = ?, date = ?, qts = ? WHERE id = 1"); + state.bindInteger(1, seq); + state.bindInteger(2, pts); + state.bindInteger(3, date); + state.bindInteger(4, qts); + state.step(); + state.dispose(); + lastSavedSeq = seq; + lastSavedPts = pts; + lastSavedDate = date; + lastSavedQts = qts; + } catch (Exception e) { + FileLog.e(e); + } + } + public void saveDiffParams(final int seq, final int pts, final int date, final int qts) { storageQueue.postRunnable(new Runnable() { @Override public void run() { - try { - if (lastSavedSeq == seq && lastSavedPts == pts && lastSavedDate == date && lastQtsValue == qts) { - return; - } - SQLitePreparedStatement state = database.executeFast("UPDATE params SET seq = ?, pts = ?, date = ?, qts = ? WHERE id = 1"); - state.bindInteger(1, seq); - state.bindInteger(2, pts); - state.bindInteger(3, date); - state.bindInteger(4, qts); - state.step(); - state.dispose(); - lastSavedSeq = seq; - lastSavedPts = pts; - lastSavedDate = date; - lastSavedQts = qts; - } catch (Exception e) { - FileLog.e(e); - } + saveDiffParamsInternal(seq, pts, date, qts); } }); } @@ -939,7 +992,7 @@ public class MessagesStorage { int lower_id = (int) message.dialog_id; addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad); message.send_state = cursor.intValue(2); - if (message.to_id.channel_id == 0 && !MessageObject.isUnread(message) && lower_id != 0 || message.id > 0) { //TODO check + if (message.to_id.channel_id == 0 && !MessageObject.isUnread(message) && lower_id != 0 || message.id > 0) { message.send_state = 0; } if (lower_id == 0 && !cursor.isNull(5)) { @@ -949,14 +1002,17 @@ public class MessagesStorage { try { if (message.reply_to_msg_id != 0 && ( message.action instanceof TLRPC.TL_messageActionPinMessage || - message.action instanceof TLRPC.TL_messageActionPaymentSent || - message.action instanceof TLRPC.TL_messageActionGameScore)) { + message.action instanceof TLRPC.TL_messageActionPaymentSent || + message.action instanceof TLRPC.TL_messageActionGameScore)) { if (!cursor.isNull(6)) { data = cursor.byteBufferValue(6); if (data != null) { message.replyMessage = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); data.reuse(); if (message.replyMessage != null) { + if (MessageObject.isMegagroup(message)) { + message.replyMessage.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; + } addUsersAndChatsFromMessage(message.replyMessage, usersToLoad, chatsToLoad); } } @@ -1000,7 +1056,11 @@ public class MessagesStorage { ArrayList arrayList = replyMessageOwners.get(message.id); if (arrayList != null) { for (int a = 0; a < arrayList.size(); a++) { - arrayList.get(a).replyMessage = message; + TLRPC.Message m = arrayList.get(a); + m.replyMessage = message; + if (MessageObject.isMegagroup(m)) { + m.replyMessage.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; + } } } } @@ -1022,7 +1082,7 @@ public class MessagesStorage { TLRPC.Chat chat = chats.get(a); if (chat != null && (chat.left || chat.migrated_to != null)) { long did = -chat.id; - database.executeFast("UPDATE dialogs SET unread_count = 0, unread_count_i = 0 WHERE did = " + did).stepThis().dispose(); + database.executeFast("UPDATE dialogs SET unread_count = 0 WHERE did = " + did).stepThis().dispose(); database.executeFast(String.format(Locale.US, "UPDATE messages SET read_state = 3 WHERE uid = %d AND mid > 0 AND read_state IN(0,2) AND out = 0", did)).stepThis().dispose(); chats.remove(a); a--; @@ -1497,7 +1557,7 @@ public class MessagesStorage { return; } - database.executeFast("UPDATE dialogs SET unread_count = 0, unread_count_i = 0 WHERE did = " + did).stepThis().dispose(); + database.executeFast("UPDATE dialogs SET unread_count = 0 WHERE did = " + did).stepThis().dispose(); database.executeFast("DELETE FROM messages WHERE uid = " + did).stepThis().dispose(); database.executeFast("DELETE FROM bot_keyboard WHERE uid = " + did).stepThis().dispose(); database.executeFast("DELETE FROM media_counts_v2 WHERE uid = " + did).stepThis().dispose(); @@ -1518,7 +1578,7 @@ public class MessagesStorage { }); } - public void getDialogPhotos(final int did, final int offset, final int count, final long max_id, final int classGuid) { + public void getDialogPhotos(final int did, final int count, final long max_id, final int classGuid) { storageQueue.postRunnable(new Runnable() { @Override public void run() { @@ -1528,10 +1588,10 @@ public class MessagesStorage { if (max_id != 0) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT data FROM user_photos WHERE uid = %d AND id < %d ORDER BY id DESC LIMIT %d", did, max_id, count)); } else { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT data FROM user_photos WHERE uid = %d ORDER BY id DESC LIMIT %d,%d", did, offset, count)); + cursor = database.queryFinalized(String.format(Locale.US, "SELECT data FROM user_photos WHERE uid = %d ORDER BY id DESC LIMIT %d", did, count)); } - final TLRPC.photos_Photos res = new TLRPC.photos_Photos(); + final TLRPC.photos_Photos res = new TLRPC.TL_photos_photos(); while (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); @@ -1546,7 +1606,7 @@ public class MessagesStorage { Utilities.stageQueue.postRunnable(new Runnable() { @Override public void run() { - MessagesController.getInstance().processLoadedUserPhotos(res, did, offset, count, max_id, true, classGuid); + MessagesController.getInstance().processLoadedUserPhotos(res, did, count, max_id, true, classGuid); } }); } catch (Exception e) { @@ -1582,6 +1642,144 @@ public class MessagesStorage { }); } + public void resetDialogs(final TLRPC.messages_Dialogs dialogsRes, final int messagesCount, final int seq, final int newPts, final int date, final int qts, final HashMap new_dialogs_dict, final HashMap new_dialogMessage, final TLRPC.Message lastMessage, final int dialogsCount) { + storageQueue.postRunnable(new Runnable() { + @Override + public void run() { + try { + int maxPinnedNum = 0; + + ArrayList dids = new ArrayList<>(); + + int totalPinnedCount = dialogsRes.dialogs.size() - dialogsCount; + final HashMap oldPinnedDialogNums = new HashMap<>(); + ArrayList oldPinnedOrder = new ArrayList<>(); + ArrayList orderArrayList = new ArrayList<>(); + + for (int a = dialogsCount; a < dialogsRes.dialogs.size(); a++) { + TLRPC.TL_dialog dialog = dialogsRes.dialogs.get(a); + orderArrayList.add(dialog.id); + } + + SQLiteCursor cursor = database.queryFinalized("SELECT did, pinned FROM dialogs WHERE 1"); + while (cursor.next()) { + long did = cursor.longValue(0); + int pinnedNum = cursor.intValue(1); + int lower_id = (int) did; + if (lower_id != 0) { + dids.add(lower_id); + if (pinnedNum > 0) { + maxPinnedNum = Math.max(pinnedNum, maxPinnedNum); + oldPinnedDialogNums.put(did, pinnedNum); + oldPinnedOrder.add(did); + } + } + } + Collections.sort(oldPinnedOrder, new Comparator() { + @Override + public int compare(Long o1, Long o2) { + Integer val1 = oldPinnedDialogNums.get(o1); + Integer val2 = oldPinnedDialogNums.get(o2); + if (val1 < val2) { + return 1; + } else if (val1 > val2) { + return -1; + } + return 0; + } + }); + while (oldPinnedOrder.size() < totalPinnedCount) { + oldPinnedOrder.add(0, 0L); + } + cursor.dispose(); + String ids = "(" + TextUtils.join(",", dids) + ")"; + + database.beginTransaction(); + database.executeFast("DELETE FROM dialogs WHERE did IN " + ids).stepThis().dispose(); + database.executeFast("DELETE FROM messages WHERE uid IN " + ids).stepThis().dispose(); + database.executeFast("DELETE FROM bot_keyboard WHERE uid IN " + ids).stepThis().dispose(); + database.executeFast("DELETE FROM media_counts_v2 WHERE uid IN " + ids).stepThis().dispose(); + database.executeFast("DELETE FROM media_v2 WHERE uid IN " + ids).stepThis().dispose(); + database.executeFast("DELETE FROM messages_holes WHERE uid IN " + ids).stepThis().dispose(); + database.executeFast("DELETE FROM media_holes_v2 WHERE uid IN " + ids).stepThis().dispose(); + database.commitTransaction(); + + for (int a = 0; a < totalPinnedCount; a++) { + TLRPC.TL_dialog dialog = dialogsRes.dialogs.get(dialogsCount + a); + int oldIdx = oldPinnedOrder.indexOf(dialog.id); + int newIdx = orderArrayList.indexOf(dialog.id); + if (oldIdx != -1 && newIdx != -1) { + if (oldIdx == newIdx) { + Integer oldNum = oldPinnedDialogNums.get(dialog.id); + if (oldNum != null) { + dialog.pinnedNum = oldNum; + } + } else { + long oldDid = oldPinnedOrder.get(newIdx); + Integer oldNum = oldPinnedDialogNums.get(oldDid); + if (oldNum != null) { + dialog.pinnedNum = oldNum; + } + } + } + if (dialog.pinnedNum == 0) { + dialog.pinnedNum = (totalPinnedCount - a) + maxPinnedNum; + } + } + + putDialogsInternal(dialogsRes, false); + saveDiffParamsInternal(seq, newPts, date, qts); + + if (lastMessage != null && lastMessage.id != UserConfig.dialogsLoadOffsetId) { + UserConfig.totalDialogsLoadCount = dialogsRes.dialogs.size(); + UserConfig.dialogsLoadOffsetId = lastMessage.id; + UserConfig.dialogsLoadOffsetDate = lastMessage.date; + if (lastMessage.to_id.channel_id != 0) { + UserConfig.dialogsLoadOffsetChannelId = lastMessage.to_id.channel_id; + UserConfig.dialogsLoadOffsetChatId = 0; + UserConfig.dialogsLoadOffsetUserId = 0; + for (int a = 0; a < dialogsRes.chats.size(); a++) { + TLRPC.Chat chat = dialogsRes.chats.get(a); + if (chat.id == UserConfig.dialogsLoadOffsetChannelId) { + UserConfig.dialogsLoadOffsetAccess = chat.access_hash; + break; + } + } + } else if (lastMessage.to_id.chat_id != 0) { + UserConfig.dialogsLoadOffsetChatId = lastMessage.to_id.chat_id; + UserConfig.dialogsLoadOffsetChannelId = 0; + UserConfig.dialogsLoadOffsetUserId = 0; + for (int a = 0; a < dialogsRes.chats.size(); a++) { + TLRPC.Chat chat = dialogsRes.chats.get(a); + if (chat.id == UserConfig.dialogsLoadOffsetChatId) { + UserConfig.dialogsLoadOffsetAccess = chat.access_hash; + break; + } + } + } else if (lastMessage.to_id.user_id != 0) { + UserConfig.dialogsLoadOffsetUserId = lastMessage.to_id.user_id; + UserConfig.dialogsLoadOffsetChatId = 0; + UserConfig.dialogsLoadOffsetChannelId = 0; + for (int a = 0; a < dialogsRes.users.size(); a++) { + TLRPC.User user = dialogsRes.users.get(a); + if (user.id == UserConfig.dialogsLoadOffsetUserId) { + UserConfig.dialogsLoadOffsetAccess = user.access_hash; + break; + } + } + } + } else { + UserConfig.dialogsLoadOffsetId = Integer.MAX_VALUE; + } + UserConfig.saveConfig(false); + MessagesController.getInstance().completeDialogsReset(dialogsRes, messagesCount, seq, newPts, date, qts, new_dialogs_dict, new_dialogMessage, lastMessage); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } + }); + } + public void putDialogPhotos(final int did, final TLRPC.photos_Photos photos) { if (photos == null || photos.photos.isEmpty()) { return; @@ -1657,7 +1855,7 @@ public class MessagesStorage { } cursor.dispose(); if (!messages.isEmpty()) { - SQLitePreparedStatement state = database.executeFast("REPLACE INTO messages VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?)"); + SQLitePreparedStatement state = database.executeFast("REPLACE INTO messages VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?)"); for (int a = 0; a < messages.size(); a++) { TLRPC.Message message = messages.get(a); @@ -1679,6 +1877,7 @@ public class MessagesStorage { state.bindInteger(9, getMessageMediaType(message)); } state.bindInteger(10, 0); + state.bindInteger(11, message.mentioned ? 1 : 0); state.step(); data.reuse(); @@ -1737,6 +1936,67 @@ public class MessagesStorage { }); } + public void markMentionMessageAsRead(final int messageId, final int channelId, final long did) { + storageQueue.postRunnable(new Runnable() { + @Override + public void run() { + try { + long mid = messageId; + if (channelId != 0) { + mid |= ((long) channelId) << 32; + } + + database.executeFast(String.format(Locale.US, "UPDATE messages SET read_state = read_state | 2 WHERE mid = %d", mid)).stepThis().dispose(); + + SQLiteCursor cursor = database.queryFinalized("SELECT unread_count_i FROM dialogs WHERE did = " + did); + int old_mentions_count = 0; + if (cursor.next()) { + old_mentions_count = Math.max(0, cursor.intValue(0) - 1); + } + cursor.dispose(); + database.executeFast(String.format(Locale.US, "UPDATE dialogs SET unread_count_i = %d WHERE did = %d", old_mentions_count, did)).stepThis().dispose(); + HashMap hashMap = new HashMap<>(); + hashMap.put(did, old_mentions_count); + MessagesController.getInstance().processDialogsUpdateRead(null, hashMap); + } catch (Exception e) { + FileLog.e(e); + } + } + }); + } + + public void markMessageAsMention(final long mid) { + storageQueue.postRunnable(new Runnable() { + @Override + public void run() { + try { + database.executeFast(String.format(Locale.US, "UPDATE messages SET mention = 1, read_state = read_state & ~2 WHERE mid = %d", mid)).stepThis().dispose(); + } catch (Exception e) { + FileLog.e(e); + } + } + }); + } + + public void resetMentionsCount(final long did, final int count) { + storageQueue.postRunnable(new Runnable() { + @Override + public void run() { + try { + if (count == 0) { + database.executeFast(String.format(Locale.US, "UPDATE messages SET read_state = read_state | 2 WHERE uid = %d AND mention = 1 AND read_state IN(0, 1)", did)).stepThis().dispose(); + } + database.executeFast(String.format(Locale.US, "UPDATE dialogs SET unread_count_i = %d WHERE did = %d", count, did)).stepThis().dispose(); + HashMap hashMap = new HashMap<>(); + hashMap.put(did, count); + MessagesController.getInstance().processDialogsUpdateRead(null, hashMap); + } catch (Exception e) { + FileLog.e(e); + } + } + }); + } + public void createTaskForMid(final int messageId, final int channelId, final int time, final int readTime, final int ttl, final boolean inner) { storageQueue.postRunnable(new Runnable() { @Override @@ -1859,9 +2119,11 @@ public class MessagesStorage { }); } - private void updateDialogsWithReadMessagesInternal(final ArrayList messages, final SparseArray inbox, final SparseArray outbox) { + private void updateDialogsWithReadMessagesInternal(final ArrayList messages, final SparseArray inbox, final SparseArray outbox, final ArrayList mentions) { try { HashMap dialogsToUpdate = new HashMap<>(); + HashMap dialogsToUpdateMentions = new HashMap<>(); + ArrayList channelMentionsToReload = new ArrayList<>(); if (messages != null && !messages.isEmpty()) { String ids = TextUtils.join(",", messages); @@ -1891,8 +2153,7 @@ public class MessagesStorage { long messageId = inbox.get(key); SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(mid) FROM messages WHERE uid = %d AND mid > %d AND read_state IN(0,2) AND out = 0", key, messageId)); if (cursor.next()) { - int count = cursor.intValue(0); - dialogsToUpdate.put((long) key, count); + dialogsToUpdate.put((long) key, cursor.intValue(0)); } cursor.dispose(); @@ -1905,6 +2166,36 @@ public class MessagesStorage { state.dispose(); } } + if (mentions != null && mentions.size() != 0) { + ArrayList notFoundMentions = new ArrayList<>(mentions); + String ids = TextUtils.join(",", mentions); + SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT uid, read_state, out, mention, mid FROM messages WHERE mid IN(%s)", ids)); + while (cursor.next()) { + long did = cursor.longValue(0); + notFoundMentions.remove(cursor.longValue(4)); + if (cursor.intValue(1) < 2 && cursor.intValue(2) == 0 && cursor.intValue(3) == 1) { + Integer unread_count = dialogsToUpdateMentions.get(did); + if (unread_count == null) { + SQLiteCursor cursor2 = database.queryFinalized("SELECT unread_count_i FROM dialogs WHERE did = " + did); + int old_mentions_count = 0; + if (cursor2.next()) { + old_mentions_count = cursor2.intValue(0); + } + cursor2.dispose(); + dialogsToUpdateMentions.put(did, Math.max(0, old_mentions_count - 1)); + } else { + dialogsToUpdateMentions.put(did, Math.max(0, unread_count - 1)); + } + } + } + cursor.dispose(); + for (int a = 0; a < notFoundMentions.size(); a++) { + int channelId = (int) (notFoundMentions.get(a) >> 32); + if (channelId > 0 && !channelMentionsToReload.contains(channelId)) { + channelMentionsToReload.add(channelId); + } + } + } if (outbox != null && outbox.size() != 0) { for (int b = 0; b < outbox.size(); b++) { int key = outbox.keyAt(b); @@ -1920,40 +2211,53 @@ public class MessagesStorage { } } - if (!dialogsToUpdate.isEmpty()) { + if (!dialogsToUpdate.isEmpty() || !dialogsToUpdateMentions.isEmpty()) { database.beginTransaction(); - SQLitePreparedStatement state = database.executeFast("UPDATE dialogs SET unread_count = ? WHERE did = ?"); - for (HashMap.Entry entry : dialogsToUpdate.entrySet()) { - state.requery(); - state.bindInteger(1, entry.getValue()); - state.bindLong(2, entry.getKey()); - state.step(); + if (!dialogsToUpdate.isEmpty()) { + SQLitePreparedStatement state = database.executeFast("UPDATE dialogs SET unread_count = ? WHERE did = ?"); + for (HashMap.Entry entry : dialogsToUpdate.entrySet()) { + state.requery(); + state.bindInteger(1, entry.getValue()); + state.bindLong(2, entry.getKey()); + state.step(); + } + state.dispose(); + } + if (!dialogsToUpdateMentions.isEmpty()) { + SQLitePreparedStatement state = database.executeFast("UPDATE dialogs SET unread_count_i = ? WHERE did = ?"); + for (HashMap.Entry entry : dialogsToUpdateMentions.entrySet()) { + state.requery(); + state.bindInteger(1, entry.getValue()); + state.bindLong(2, entry.getKey()); + state.step(); + } + state.dispose(); } - state.dispose(); database.commitTransaction(); } - if (!dialogsToUpdate.isEmpty()) { - MessagesController.getInstance().processDialogsUpdateRead(dialogsToUpdate); + MessagesController.getInstance().processDialogsUpdateRead(dialogsToUpdate, dialogsToUpdateMentions); + if (!channelMentionsToReload.isEmpty()) { + MessagesController.getInstance().reloadMentionsCountForChannels(channelMentionsToReload); } } catch (Exception e) { FileLog.e(e); } } - public void updateDialogsWithReadMessages(final SparseArray inbox, final SparseArray outbox, boolean useQueue) { - if (inbox.size() == 0) { + public void updateDialogsWithReadMessages(final SparseArray inbox, final SparseArray outbox, final ArrayList mentions, boolean useQueue) { + if (inbox.size() == 0 && mentions.isEmpty()) { return; } if (useQueue) { storageQueue.postRunnable(new Runnable() { @Override public void run() { - updateDialogsWithReadMessagesInternal(null, inbox, outbox); + updateDialogsWithReadMessagesInternal(null, inbox, outbox, mentions); } }); } else { - updateDialogsWithReadMessagesInternal(null, inbox, outbox); + updateDialogsWithReadMessagesInternal(null, inbox, outbox, mentions); } } @@ -2004,6 +2308,49 @@ public class MessagesStorage { }); } + public void loadChannelAdmins(final int chatId) { + storageQueue.postRunnable(new Runnable() { + @Override + public void run() { + try { + SQLiteCursor cursor = database.queryFinalized("SELECT uid FROM channel_admins WHERE did = " + chatId); + ArrayList ids = new ArrayList<>(); + while (cursor.next()) { + ids.add(cursor.intValue(0)); + } + cursor.dispose(); + MessagesController.getInstance().processLoadedChannelAdmins(ids, chatId, true); + } catch (Exception e) { + FileLog.e(e); + } + } + }); + } + + public void putChannelAdmins(final int chatId, final ArrayList ids) { + storageQueue.postRunnable(new Runnable() { + @Override + public void run() { + try { + database.executeFast("DELETE FROM channel_admins WHERE did = " + chatId).stepThis().dispose(); + database.beginTransaction(); + SQLitePreparedStatement state = database.executeFast("REPLACE INTO channel_admins VALUES(?, ?)"); + int date = (int) (System.currentTimeMillis() / 1000); + for (int a = 0; a < ids.size(); a++) { + state.requery(); + state.bindInteger(1, chatId); + state.bindInteger(2, ids.get(a)); + state.step(); + } + state.dispose(); + database.commitTransaction(); + } catch (Exception e) { + FileLog.e(e); + } + } + }); + } + public void updateChannelUsers(final int channel_id, final ArrayList participants) { storageQueue.postRunnable(new Runnable() { @Override @@ -2088,7 +2435,7 @@ public class MessagesStorage { if (constructor == TLRPC.TL_messages_botCallbackAnswer.constructor) { result = TLRPC.TL_messages_botCallbackAnswer.TLdeserialize(data, constructor, false); } else { - result = TLRPC.TL_messages_botResults.TLdeserialize(data, constructor, false); + result = TLRPC.messages_BotResults.TLdeserialize(data, constructor, false); } data.reuse(); } @@ -2131,7 +2478,7 @@ public class MessagesStorage { data.reuse(); if (info instanceof TLRPC.TL_channelFull) { - SQLiteCursor cursor = database.queryFinalized("SELECT date, pts, last_mid, inbox_max, outbox_max, pinned FROM dialogs WHERE did = " + (-info.id)); + SQLiteCursor cursor = database.queryFinalized("SELECT date, pts, last_mid, inbox_max, outbox_max, pinned, unread_count_i FROM dialogs WHERE did = " + (-info.id)); if (cursor.next()) { int inbox_max = cursor.intValue(3); if (inbox_max <= info.read_inbox_max_id) { @@ -2144,6 +2491,7 @@ public class MessagesStorage { long last_mid = cursor.longValue(2); int outbox_max = cursor.intValue(4); int pinned = cursor.intValue(5); + int mentions = cursor.intValue(6); state = database.executeFast("REPLACE INTO dialogs VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); state.bindLong(1, -info.id); @@ -2153,7 +2501,7 @@ public class MessagesStorage { state.bindInteger(5, info.read_inbox_max_id); state.bindInteger(6, Math.max(outbox_max, info.read_outbox_max_id)); state.bindLong(7, 0); - state.bindInteger(8, 0); + state.bindInteger(8, mentions); state.bindInteger(9, pts); state.bindInteger(10, 0); state.bindInteger(11, pinned); @@ -2470,7 +2818,7 @@ public class MessagesStorage { cursor.dispose(); currentMaxId = Math.max(currentMaxId, (int) max_id); - state = database.executeFast("UPDATE dialogs SET unread_count = 0, unread_count_i = 0, inbox_max = ? WHERE did = ?"); + state = database.executeFast("UPDATE dialogs SET unread_count = 0, inbox_max = ? WHERE did = ?"); state.requery(); state.bindInteger(1, currentMaxId); state.bindLong(2, dialog_id); @@ -2541,10 +2889,10 @@ public class MessagesStorage { public void run() { try { if (adds.length() != 0) { - database.executeFast(String.format(Locale.US, "UPDATE user_phones_v6 SET deleted = 0 WHERE sphone IN(%s)", adds)).stepThis().dispose(); + database.executeFast(String.format(Locale.US, "UPDATE user_phones_v7 SET deleted = 0 WHERE sphone IN(%s)", adds)).stepThis().dispose(); } if (deletes.length() != 0) { - database.executeFast(String.format(Locale.US, "UPDATE user_phones_v6 SET deleted = 1 WHERE sphone IN(%s)", deletes)).stepThis().dispose(); + database.executeFast(String.format(Locale.US, "UPDATE user_phones_v7 SET deleted = 1 WHERE sphone IN(%s)", deletes)).stepThis().dispose(); } } catch (Exception e) { FileLog.e(e); @@ -2553,27 +2901,29 @@ public class MessagesStorage { }); } - public void putCachedPhoneBook(final HashMap contactHashMap) { + public void putCachedPhoneBook(final HashMap contactHashMap, final boolean migrate) { storageQueue.postRunnable(new Runnable() { @Override public void run() { try { database.beginTransaction(); - SQLitePreparedStatement state = database.executeFast("REPLACE INTO user_contacts_v6 VALUES(?, ?, ?)"); - SQLitePreparedStatement state2 = database.executeFast("REPLACE INTO user_phones_v6 VALUES(?, ?, ?, ?)"); - for (HashMap.Entry entry : contactHashMap.entrySet()) { + SQLitePreparedStatement state = database.executeFast("REPLACE INTO user_contacts_v7 VALUES(?, ?, ?, ?, ?)"); + SQLitePreparedStatement state2 = database.executeFast("REPLACE INTO user_phones_v7 VALUES(?, ?, ?, ?)"); + for (HashMap.Entry entry : contactHashMap.entrySet()) { ContactsController.Contact contact = entry.getValue(); if (contact.phones.isEmpty() || contact.shortPhones.isEmpty()) { continue; } state.requery(); - state.bindInteger(1, contact.id); - state.bindString(2, contact.first_name); - state.bindString(3, contact.last_name); + state.bindString(1, contact.key); + state.bindInteger(2, contact.contact_id); + state.bindString(3, contact.first_name); + state.bindString(4, contact.last_name); + state.bindInteger(5, contact.imported); state.step(); for (int a = 0; a < contact.phones.size(); a++) { state2.requery(); - state2.bindInteger(1, contact.id); + state2.bindString(1, contact.key); state2.bindString(2, contact.phones.get(a)); state2.bindString(3, contact.shortPhones.get(a)); state2.bindInteger(4, contact.phoneDeleted.get(a)); @@ -2583,6 +2933,11 @@ public class MessagesStorage { state.dispose(); state2.dispose(); database.commitTransaction(); + if (migrate) { + database.executeFast("DROP TABLE IF EXISTS user_contacts_v6;").stepThis().dispose(); + database.executeFast("DROP TABLE IF EXISTS user_phones_v6;").stepThis().dispose(); + getCachedPhoneBook(false); + } } catch (Exception e) { FileLog.e(e); } @@ -2590,35 +2945,84 @@ public class MessagesStorage { }); } - public void getCachedPhoneBook() { + public void getCachedPhoneBook(final boolean byError) { storageQueue.postRunnable(new Runnable() { @Override public void run() { - HashMap contactHashMap = new HashMap<>(); try { - SQLiteCursor cursor = database.queryFinalized("SELECT us.uid, us.fname, us.sname, up.phone, up.sphone, up.deleted FROM user_contacts_v6 as us LEFT JOIN user_phones_v6 as up ON us.uid = up.uid WHERE 1"); + SQLiteCursor cursor = database.queryFinalized("SELECT name FROM sqlite_master WHERE type='table' AND name='user_contacts_v6'"); + boolean migrate = cursor.next(); + cursor.dispose(); + if (migrate) { + HashMap contactHashMap = new HashMap<>(); + cursor = database.queryFinalized("SELECT us.uid, us.fname, us.sname, up.phone, up.sphone, up.deleted, us.imported FROM user_contacts_v6 as us LEFT JOIN user_phones_v6 as up ON us.uid = up.uid WHERE 1"); + while (cursor.next()) { + int uid = cursor.intValue(0); + ContactsController.Contact contact = contactHashMap.get(uid); + if (contact == null) { + contact = new ContactsController.Contact(); + contact.first_name = cursor.stringValue(1); + contact.last_name = cursor.stringValue(2); + contact.imported = cursor.intValue(6); + if (contact.first_name == null) { + contact.first_name = ""; + } + if (contact.last_name == null) { + contact.last_name = ""; + } + contact.contact_id = uid; + contactHashMap.put(uid, contact); + } + String phone = cursor.stringValue(3); + if (phone == null) { + continue; + } + contact.phones.add(phone); + String sphone = cursor.stringValue(4); + if (sphone == null) { + continue; + } + if (sphone.length() == 8 && phone.length() != 8) { + sphone = PhoneFormat.stripExceptNumbers(phone); + } + contact.shortPhones.add(sphone); + contact.phoneDeleted.add(cursor.intValue(5)); + contact.phoneTypes.add(""); + } + cursor.dispose(); + ContactsController.getInstance().migratePhoneBookToV7(contactHashMap); + return; + } + } catch (Exception e) { + FileLog.e(e); + } + + HashMap contactHashMap = new HashMap<>(); + try { + SQLiteCursor cursor = database.queryFinalized("SELECT us.key, us.uid, us.fname, us.sname, up.phone, up.sphone, up.deleted, us.imported FROM user_contacts_v7 as us LEFT JOIN user_phones_v7 as up ON us.key = up.key WHERE 1"); while (cursor.next()) { - int uid = cursor.intValue(0); - ContactsController.Contact contact = contactHashMap.get(uid); + String key = cursor.stringValue(0); + ContactsController.Contact contact = contactHashMap.get(key); if (contact == null) { contact = new ContactsController.Contact(); - contact.first_name = cursor.stringValue(1); - contact.last_name = cursor.stringValue(2); + contact.contact_id = cursor.intValue(1); + contact.first_name = cursor.stringValue(2); + contact.last_name = cursor.stringValue(3); + contact.imported = cursor.intValue(7); if (contact.first_name == null) { contact.first_name = ""; } if (contact.last_name == null) { contact.last_name = ""; } - contact.id = uid; - contactHashMap.put(uid, contact); + contactHashMap.put(key, contact); } - String phone = cursor.stringValue(3); + String phone = cursor.stringValue(4); if (phone == null) { continue; } contact.phones.add(phone); - String sphone = cursor.stringValue(4); + String sphone = cursor.stringValue(5); if (sphone == null) { continue; } @@ -2626,7 +3030,7 @@ public class MessagesStorage { sphone = PhoneFormat.stripExceptNumbers(phone); } contact.shortPhones.add(sphone); - contact.phoneDeleted.add(cursor.intValue(5)); + contact.phoneDeleted.add(cursor.intValue(6)); contact.phoneTypes.add(""); } cursor.dispose(); @@ -2634,7 +3038,7 @@ public class MessagesStorage { contactHashMap.clear(); FileLog.e(e); } - ContactsController.getInstance().performSyncPhoneBook(contactHashMap, true, true, false, false); + ContactsController.getInstance().performSyncPhoneBook(contactHashMap, true, true, false, false, !byError, false); } }); } @@ -2737,7 +3141,7 @@ public class MessagesStorage { addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad); message.send_state = cursor.intValue(2); - if (message.to_id.channel_id == 0 && !MessageObject.isUnread(message) && lower_id != 0 || message.id > 0) { //TODO check + if (message.to_id.channel_id == 0 && !MessageObject.isUnread(message) && lower_id != 0 || message.id > 0) { message.send_state = 0; } if (lower_id == 0 && !cursor.isNull(5)) { @@ -2814,12 +3218,39 @@ public class MessagesStorage { return result[0]; } + public void getUnreadMention(final long dialog_id, final IntCallback callback) { + storageQueue.postRunnable(new Runnable() { + @Override + public void run() { + try { + final int result; + SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT MIN(mid) FROM messages WHERE uid = %d AND mention = 1 AND read_state IN(0, 1)", dialog_id)); + if (cursor.next()) { + result = cursor.intValue(0); + } else { + result = 0; + } + cursor.dispose(); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + callback.run(result); + } + }); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } + }); + } + public void getMessages(final long dialog_id, final int count, final int max_id, final int offset_date, final int minDate, final int classGuid, final int load_type, final boolean isChannel, final int loadIndex) { storageQueue.postRunnable(new Runnable() { @Override public void run() { TLRPC.TL_messages_messages res = new TLRPC.TL_messages_messages(); int count_unread = 0; + int mentions_unread = 0; int count_query = count; int offset_query = 0; int min_unread_id = 0; @@ -2828,6 +3259,7 @@ public class MessagesStorage { int max_unread_date = 0; long messageMaxId = max_id; int max_id_query = max_id; + boolean unreadCountIsLocal = false; int max_id_override = max_id; int channelId = 0; if (isChannel) { @@ -2849,20 +3281,22 @@ public class MessagesStorage { int lower_id = (int) dialog_id; if (lower_id != 0) { if (load_type == 3 && minDate == 0) { - cursor = database.queryFinalized("SELECT inbox_max, unread_count, date FROM dialogs WHERE did = " + dialog_id); + cursor = database.queryFinalized("SELECT inbox_max, unread_count, date, unread_count_i FROM dialogs WHERE did = " + dialog_id); if (cursor.next()) { min_unread_id = cursor.intValue(0) + 1; count_unread = cursor.intValue(1); max_unread_date = cursor.intValue(2); + mentions_unread = cursor.intValue(3); } cursor.dispose(); } else if (load_type != 1 && load_type != 3 && load_type != 4 && minDate == 0) { if (load_type == 2) { - cursor = database.queryFinalized("SELECT inbox_max, unread_count, date FROM dialogs WHERE did = " + dialog_id); + cursor = database.queryFinalized("SELECT inbox_max, unread_count, date, unread_count_i FROM dialogs WHERE did = " + dialog_id); if (cursor.next()) { messageMaxId = max_id_query = min_unread_id = cursor.intValue(0); count_unread = cursor.intValue(1); max_unread_date = cursor.intValue(2); + mentions_unread = cursor.intValue(3); queryFromServer = true; if (messageMaxId != 0 && channelId != 0) { messageMaxId |= ((long) channelId) << 32; @@ -3046,14 +3480,30 @@ public class MessagesStorage { holeMessageMaxId |= ((long) channelId) << 32; } } - cursor = database.queryFinalized(String.format(Locale.US, "SELECT * FROM (SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid <= %d AND m.mid >= %d ORDER BY m.date DESC, m.mid DESC LIMIT %d) UNION " + - "SELECT * FROM (SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid > %d AND m.mid <= %d ORDER BY m.date ASC, m.mid ASC LIMIT %d)", dialog_id, messageMaxId, holeMessageMinId, count_query / 2, dialog_id, messageMaxId, holeMessageMaxId, count_query / 2)); + cursor = database.queryFinalized(String.format(Locale.US, "SELECT * FROM (SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl, m.mention FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid <= %d AND m.mid >= %d ORDER BY m.date DESC, m.mid DESC LIMIT %d) UNION " + + "SELECT * FROM (SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl, m.mention FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid > %d AND m.mid <= %d ORDER BY m.date ASC, m.mid ASC LIMIT %d)", dialog_id, messageMaxId, holeMessageMinId, count_query / 2, dialog_id, messageMaxId, holeMessageMaxId, count_query / 2)); } else { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT * FROM (SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid <= %d ORDER BY m.date DESC, m.mid DESC LIMIT %d) UNION " + - "SELECT * FROM (SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid > %d ORDER BY m.date ASC, m.mid ASC LIMIT %d)", dialog_id, messageMaxId, count_query / 2, dialog_id, messageMaxId, count_query / 2)); + cursor = database.queryFinalized(String.format(Locale.US, "SELECT * FROM (SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl, m.mention FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid <= %d ORDER BY m.date DESC, m.mid DESC LIMIT %d) UNION " + + "SELECT * FROM (SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl, m.mention FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid > %d ORDER BY m.date ASC, m.mid ASC LIMIT %d)", dialog_id, messageMaxId, count_query / 2, dialog_id, messageMaxId, count_query / 2)); } } else { - cursor = null; + if (load_type == 2) { + int existingUnreadCount = 0; + cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(*) FROM messages WHERE uid = %d AND mid != 0 AND out = 0 AND read_state IN(0,2)", dialog_id)); + if (cursor.next()) { + existingUnreadCount = cursor.intValue(0); + } + cursor.dispose(); + if (existingUnreadCount == count_unread) { + unreadCountIsLocal = true; + cursor = database.queryFinalized(String.format(Locale.US, "SELECT * FROM (SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl, m.mention FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid <= %d ORDER BY m.date DESC, m.mid DESC LIMIT %d) UNION " + + "SELECT * FROM (SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl, m.mention FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid > %d ORDER BY m.date ASC, m.mid ASC LIMIT %d)", dialog_id, messageMaxId, count_query / 2, dialog_id, messageMaxId, count_query / 2)); + } else { + cursor = null; + } + } else { + cursor = null; + } } } else if (load_type == 1) { long holeMessageId = 0; @@ -3066,9 +3516,9 @@ public class MessagesStorage { } cursor.dispose(); if (holeMessageId != 0) { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.date >= %d AND m.mid > %d AND m.mid <= %d ORDER BY m.date ASC, m.mid ASC LIMIT %d", dialog_id, minDate, messageMaxId, holeMessageId, count_query)); + cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl, m.mention FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.date >= %d AND m.mid > %d AND m.mid <= %d ORDER BY m.date ASC, m.mid ASC LIMIT %d", dialog_id, minDate, messageMaxId, holeMessageId, count_query)); } else { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.date >= %d AND m.mid > %d ORDER BY m.date ASC, m.mid ASC LIMIT %d", dialog_id, minDate, messageMaxId, count_query)); + cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl, m.mention FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.date >= %d AND m.mid > %d ORDER BY m.date ASC, m.mid ASC LIMIT %d", dialog_id, minDate, messageMaxId, count_query)); } } else if (minDate != 0) { if (messageMaxId != 0) { @@ -3082,12 +3532,12 @@ public class MessagesStorage { } cursor.dispose(); if (holeMessageId != 0) { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.date <= %d AND m.mid < %d AND (m.mid >= %d OR m.mid < 0) ORDER BY m.date DESC, m.mid DESC LIMIT %d", dialog_id, minDate, messageMaxId, holeMessageId, count_query)); + cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl, m.mention FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.date <= %d AND m.mid < %d AND (m.mid >= %d OR m.mid < 0) ORDER BY m.date DESC, m.mid DESC LIMIT %d", dialog_id, minDate, messageMaxId, holeMessageId, count_query)); } else { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.date <= %d AND m.mid < %d ORDER BY m.date DESC, m.mid DESC LIMIT %d", dialog_id, minDate, messageMaxId, count_query)); + cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl, m.mention FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.date <= %d AND m.mid < %d ORDER BY m.date DESC, m.mid DESC LIMIT %d", dialog_id, minDate, messageMaxId, count_query)); } } else { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.date <= %d ORDER BY m.date DESC, m.mid DESC LIMIT %d,%d", dialog_id, minDate, offset_query, count_query)); + cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl, m.mention FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.date <= %d ORDER BY m.date DESC, m.mid DESC LIMIT %d,%d", dialog_id, minDate, offset_query, count_query)); } } else { cursor = database.queryFinalized(String.format(Locale.US, "SELECT max(mid) FROM messages WHERE uid = %d AND mid > 0", dialog_id)); @@ -3106,9 +3556,9 @@ public class MessagesStorage { } cursor.dispose(); if (holeMessageId != 0) { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND (m.mid >= %d OR m.mid < 0) ORDER BY m.date DESC, m.mid DESC LIMIT %d,%d", dialog_id, holeMessageId, offset_query, count_query)); + cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl, m.mention FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND (m.mid >= %d OR m.mid < 0) ORDER BY m.date DESC, m.mid DESC LIMIT %d,%d", dialog_id, holeMessageId, offset_query, count_query)); } else { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d ORDER BY m.date DESC, m.mid DESC LIMIT %d,%d", dialog_id, offset_query, count_query)); + cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl, m.mention FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d ORDER BY m.date DESC, m.mid DESC LIMIT %d,%d", dialog_id, offset_query, count_query)); } } } else { @@ -3145,15 +3595,15 @@ public class MessagesStorage { } cursor.dispose(); - cursor = database.queryFinalized(String.format(Locale.US, "SELECT * FROM (SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid <= %d ORDER BY m.mid DESC LIMIT %d) UNION " + - "SELECT * FROM (SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid > %d ORDER BY m.mid ASC LIMIT %d)", dialog_id, messageMaxId, count_query / 2, dialog_id, messageMaxId, count_query / 2)); + cursor = database.queryFinalized(String.format(Locale.US, "SELECT * FROM (SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl, m.mention FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid <= %d ORDER BY m.mid DESC LIMIT %d) UNION " + + "SELECT * FROM (SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl, m.mention FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid > %d ORDER BY m.mid ASC LIMIT %d)", dialog_id, messageMaxId, count_query / 2, dialog_id, messageMaxId, count_query / 2)); } else if (load_type == 1) { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid < %d ORDER BY m.mid DESC LIMIT %d", dialog_id, max_id, count_query)); + cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl, m.mention FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid < %d ORDER BY m.mid DESC LIMIT %d", dialog_id, max_id, count_query)); } else if (minDate != 0) { if (max_id != 0) { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid > %d ORDER BY m.mid ASC LIMIT %d", dialog_id, max_id, count_query)); + cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl, m.mention FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid > %d ORDER BY m.mid ASC LIMIT %d", dialog_id, max_id, count_query)); } else { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.date <= %d ORDER BY m.mid ASC LIMIT %d,%d", dialog_id, minDate, offset_query, count_query)); + cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl, m.mention FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.date <= %d ORDER BY m.mid ASC LIMIT %d,%d", dialog_id, minDate, offset_query, count_query)); } } else { if (load_type == 2) { @@ -3189,7 +3639,7 @@ public class MessagesStorage { offset_query = count_unread - count_query; count_query += 10; } - cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d ORDER BY m.mid ASC LIMIT %d,%d", dialog_id, offset_query, count_query)); + cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl, m.mention FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d ORDER BY m.mid ASC LIMIT %d,%d", dialog_id, offset_query, count_query)); } } if (cursor != null) { @@ -3208,6 +3658,9 @@ public class MessagesStorage { if (lower_id != 0 && message.ttl == 0) { message.ttl = cursor.intValue(8); } + if (cursor.intValue(9) != 0) { + message.mentioned = true; + } res.messages.add(message); addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad); @@ -3219,6 +3672,9 @@ public class MessagesStorage { message.replyMessage = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); data.reuse(); if (message.replyMessage != null) { + if (MessageObject.isMegagroup(message)) { + message.replyMessage.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; + } addUsersAndChatsFromMessage(message.replyMessage, usersToLoad, chatsToLoad); } } @@ -3301,7 +3757,7 @@ public class MessagesStorage { }); if (lower_id != 0) { - if ((load_type == 3 || load_type == 4 || load_type == 2 && queryFromServer) && !res.messages.isEmpty()) { + if ((load_type == 3 || load_type == 4 || load_type == 2 && queryFromServer && !unreadCountIsLocal) && !res.messages.isEmpty()) { int minId = res.messages.get(res.messages.size() - 1).id; int maxId = res.messages.get(0).id; if (!(minId <= max_id_query && maxId >= max_id_query)) { @@ -3336,7 +3792,11 @@ public class MessagesStorage { ArrayList arrayList = replyMessageOwners.get(message.id); if (arrayList != null) { for (int a = 0; a < arrayList.size(); a++) { - arrayList.get(a).replyMessage = message; + TLRPC.Message object = arrayList.get(a); + object.replyMessage = message; + if (MessageObject.isMegagroup(object)) { + object.replyMessage.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; + } } } } else { @@ -3346,6 +3806,9 @@ public class MessagesStorage { TLRPC.Message object = arrayList.get(a); object.replyMessage = message; object.reply_to_msg_id = message.id; + if (MessageObject.isMegagroup(object)) { + object.replyMessage.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; + } } } } @@ -3362,6 +3825,18 @@ public class MessagesStorage { } } + if (mentions_unread != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(mid) FROM messages WHERE uid = %d AND mention = 1 AND read_state IN(0, 1)", dialog_id)); + if (cursor.next()) { + if (mentions_unread != cursor.intValue(0)) { + mentions_unread *= -1; + } + } else { + mentions_unread *= -1; + } + cursor.dispose(); + } + if (!usersToLoad.isEmpty()) { getUsersInternal(TextUtils.join(",", usersToLoad), res.users); } @@ -3374,54 +3849,12 @@ public class MessagesStorage { res.users.clear(); FileLog.e(e); } finally { - MessagesController.getInstance().processLoadedMessages(res, dialog_id, count_query, max_id_override, offset_date, true, classGuid, min_unread_id, last_message_id, count_unread, max_unread_date, load_type, isChannel, isEnd, loadIndex, queryFromServer); + MessagesController.getInstance().processLoadedMessages(res, dialog_id, count_query, max_id_override, offset_date, true, classGuid, min_unread_id, last_message_id, count_unread, max_unread_date, load_type, isChannel, isEnd, loadIndex, queryFromServer, mentions_unread); } } }); } - public void startTransaction(boolean useQueue) { - if (useQueue) { - storageQueue.postRunnable(new Runnable() { - @Override - public void run() { - try { - database.beginTransaction(); - } catch (Exception e) { - FileLog.e(e); - } - } - }); - } else { - try { - database.beginTransaction(); - } catch (Exception e) { - FileLog.e(e); - } - } - } - - public void commitTransaction(boolean useQueue) { - if (useQueue) { - storageQueue.postRunnable(new Runnable() { - @Override - public void run() { - try { - database.commitTransaction(); - } catch (Exception e) { - FileLog.e(e); - } - } - }); - } else { - try { - database.commitTransaction(); - } catch (Exception e) { - FileLog.e(e); - } - } - } - public TLObject getSentFile(final String path, final int type) { if (path == null || path.endsWith("attheme")) { return null; @@ -3509,7 +3942,7 @@ public class MessagesStorage { }); } - public void updateEncryptedChatSeq(final TLRPC.EncryptedChat chat) { + public void updateEncryptedChatSeq(final TLRPC.EncryptedChat chat, final boolean cleanup) { if (chat == null) { return; } @@ -3518,13 +3951,18 @@ public class MessagesStorage { public void run() { SQLitePreparedStatement state = null; try { - state = database.executeFast("UPDATE enc_chats SET seq_in = ?, seq_out = ?, use_count = ?, in_seq_no = ? WHERE uid = ?"); + state = database.executeFast("UPDATE enc_chats SET seq_in = ?, seq_out = ?, use_count = ?, in_seq_no = ?, mtproto_seq = ? WHERE uid = ?"); state.bindInteger(1, chat.seq_in); state.bindInteger(2, chat.seq_out); state.bindInteger(3, (int) chat.key_use_count_in << 16 | chat.key_use_count_out); state.bindInteger(4, chat.in_seq_no); - state.bindInteger(5, chat.id); + state.bindInteger(5, chat.mtproto_seq); + state.bindInteger(6, chat.id); state.step(); + if (cleanup) { + long did = ((long) chat.id) << 32; + database.executeFast(String.format(Locale.US, "DELETE FROM messages WHERE mid IN (SELECT m.mid FROM messages as m LEFT JOIN messages_seq as s ON m.mid = s.mid WHERE m.uid = %d AND m.date = 0 AND m.mid < 0 AND s.seq_out <= %d)", did, chat.in_seq_no)).stepThis().dispose(); + } } catch (Exception e) { FileLog.e(e); } finally { @@ -3597,7 +4035,7 @@ public class MessagesStorage { chat.key_hash = AndroidUtilities.calcAuthKeyHash(chat.auth_key); } - state = database.executeFast("UPDATE enc_chats SET data = ?, g = ?, authkey = ?, ttl = ?, layer = ?, seq_in = ?, seq_out = ?, use_count = ?, exchange_id = ?, key_date = ?, fprint = ?, fauthkey = ?, khash = ?, in_seq_no = ?, admin_id = ? WHERE uid = ?"); + state = database.executeFast("UPDATE enc_chats SET data = ?, g = ?, authkey = ?, ttl = ?, layer = ?, seq_in = ?, seq_out = ?, use_count = ?, exchange_id = ?, key_date = ?, fprint = ?, fauthkey = ?, khash = ?, in_seq_no = ?, admin_id = ?, mtproto_seq = ? WHERE uid = ?"); NativeByteBuffer data = new NativeByteBuffer(chat.getObjectSize()); NativeByteBuffer data2 = new NativeByteBuffer(chat.a_or_b != null ? chat.a_or_b.length : 1); NativeByteBuffer data3 = new NativeByteBuffer(chat.auth_key != null ? chat.auth_key.length : 1); @@ -3631,7 +4069,8 @@ public class MessagesStorage { state.bindByteBuffer(13, data5); state.bindInteger(14, chat.in_seq_no); state.bindInteger(15, chat.admin_id); - state.bindInteger(16, chat.id); + state.bindInteger(16, chat.mtproto_seq); + state.bindInteger(17, chat.id); state.step(); data.reuse(); @@ -3739,7 +4178,7 @@ public class MessagesStorage { if ((chat.key_hash == null || chat.key_hash.length < 16) && chat.auth_key != null) { chat.key_hash = AndroidUtilities.calcAuthKeyHash(chat.auth_key); } - SQLitePreparedStatement state = database.executeFast("REPLACE INTO enc_chats VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + SQLitePreparedStatement state = database.executeFast("REPLACE INTO enc_chats VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); NativeByteBuffer data = new NativeByteBuffer(chat.getObjectSize()); NativeByteBuffer data2 = new NativeByteBuffer(chat.a_or_b != null ? chat.a_or_b.length : 1); NativeByteBuffer data3 = new NativeByteBuffer(chat.auth_key != null ? chat.auth_key.length : 1); @@ -3777,6 +4216,7 @@ public class MessagesStorage { state.bindByteBuffer(16, data5); state.bindInteger(17, chat.in_seq_no); state.bindInteger(18, chat.admin_id); + state.bindInteger(19, chat.mtproto_seq); state.step(); state.dispose(); @@ -3794,7 +4234,7 @@ public class MessagesStorage { state.bindInteger(5, dialog.read_inbox_max_id); state.bindInteger(6, dialog.read_outbox_max_id); state.bindInteger(7, 0); - state.bindInteger(8, 0); + state.bindInteger(8, dialog.unread_mentions_count); state.bindInteger(9, dialog.pts); state.bindInteger(10, 0); state.bindInteger(11, dialog.pinnedNum); @@ -3995,7 +4435,7 @@ public class MessagesStorage { if (chatsToLoad == null || chatsToLoad.length() == 0 || result == null) { return; } - SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, user, g, authkey, ttl, layer, seq_in, seq_out, use_count, exchange_id, key_date, fprint, fauthkey, khash, in_seq_no, admin_id FROM enc_chats WHERE uid IN(%s)", chatsToLoad)); + SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, user, g, authkey, ttl, layer, seq_in, seq_out, use_count, exchange_id, key_date, fprint, fauthkey, khash, in_seq_no, admin_id, mtproto_seq FROM enc_chats WHERE uid IN(%s)", chatsToLoad)); while (cursor.next()) { try { NativeByteBuffer data = cursor.byteBufferValue(0); @@ -4026,6 +4466,7 @@ public class MessagesStorage { if (admin_id != 0) { chat.admin_id = admin_id; } + chat.mtproto_seq = cursor.intValue(16); result.add(chat); } } @@ -4291,6 +4732,7 @@ public class MessagesStorage { dialog.read_inbox_max_id = difference.read_inbox_max_id; dialog.read_outbox_max_id = difference.read_outbox_max_id; dialog.unread_count = difference.unread_count; + dialog.unread_mentions_count = difference.unread_mentions_count; dialog.notify_settings = null; dialog.pinned = pinned != 0; dialog.pinnedNum = pinned; @@ -4383,6 +4825,7 @@ public class MessagesStorage { } HashMap messagesMap = new HashMap<>(); HashMap messagesCounts = new HashMap<>(); + HashMap mentionCounts = new HashMap<>(); HashMap> mediaCounts = null; HashMap botKeyboards = new HashMap<>(); @@ -4392,8 +4835,9 @@ public class MessagesStorage { StringBuilder messageIds = new StringBuilder(); HashMap dialogsReadMax = new HashMap<>(); HashMap messagesIdsMap = new HashMap<>(); + HashMap mentionsIdsMap = new HashMap<>(); - SQLitePreparedStatement state = database.executeFast("REPLACE INTO messages VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?)"); + SQLitePreparedStatement state = database.executeFast("REPLACE INTO messages VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?)"); SQLitePreparedStatement state2 = null; SQLitePreparedStatement state3 = database.executeFast("REPLACE INTO randoms VALUES(?, ?)"); SQLitePreparedStatement state4 = database.executeFast("REPLACE INTO download_queue VALUES(?, ?, ?, ?)"); @@ -4415,8 +4859,11 @@ public class MessagesStorage { if (message.to_id.channel_id != 0) { messageId |= ((long) message.to_id.channel_id) << 32; } + if (message.mentioned && message.media_unread) { + mentionsIdsMap.put(messageId, message.dialog_id); + } - if (MessageObject.isUnread(message) && !MessageObject.isOut(message)) { + if (!MessageObject.isOut(message) && (message.id > 0 || MessageObject.isUnread(message))) { Integer currentMaxId = dialogsReadMax.get(message.dialog_id); if (currentMaxId == null) { SQLiteCursor cursor = database.queryFinalized("SELECT inbox_max FROM dialogs WHERE did = " + message.dialog_id); @@ -4491,7 +4938,9 @@ public class MessagesStorage { if (messageIds.length() > 0) { SQLiteCursor cursor = database.queryFinalized("SELECT mid FROM messages WHERE mid IN(" + messageIds.toString() + ")"); while (cursor.next()) { - messagesIdsMap.remove(cursor.longValue(0)); + long mid = cursor.longValue(0); + messagesIdsMap.remove(mid); + mentionsIdsMap.remove(mid); } cursor.dispose(); for (Long dialog_id : messagesIdsMap.values()) { @@ -4502,6 +4951,14 @@ public class MessagesStorage { count++; messagesCounts.put(dialog_id, count); } + for (Long dialog_id : mentionsIdsMap.values()) { + Integer count = mentionCounts.get(dialog_id); + if (count == null) { + count = 0; + } + count++; + mentionCounts.put(dialog_id, count); + } } int downloadMediaMask = 0; @@ -4547,6 +5004,7 @@ public class MessagesStorage { state.bindInteger(9, getMessageMediaType(message)); } state.bindInteger(10, 0); + state.bindInteger(11, message.mentioned ? 1 : 0); state.step(); if (message.random_id != 0) { @@ -4578,54 +5036,44 @@ public class MessagesStorage { data.reuse(); - if ((message.to_id.channel_id == 0 || message.post) && message.date >= ConnectionsManager.getInstance().getCurrentTime() - 60 * 60 && downloadMask != 0) { + if (downloadMask != 0 && (message.to_id.channel_id == 0 || message.post) && message.date >= ConnectionsManager.getInstance().getCurrentTime() - 60 * 60 && MediaController.getInstance().canDownloadMedia(message)) { if (message.media instanceof TLRPC.TL_messageMediaPhoto || message.media instanceof TLRPC.TL_messageMediaDocument) { int type = 0; long id = 0; TLRPC.MessageMedia object = null; if (MessageObject.isVoiceMessage(message)) { - if ((downloadMask & MediaController.AUTODOWNLOAD_MASK_AUDIO) != 0 && message.media.document.size < 1024 * 1024 * 2) { - id = message.media.document.id; - type = MediaController.AUTODOWNLOAD_MASK_AUDIO; - object = new TLRPC.TL_messageMediaDocument(); - object.document = message.media.document; - object.flags |= 1; - } + id = message.media.document.id; + type = MediaController.AUTODOWNLOAD_MASK_AUDIO; + object = new TLRPC.TL_messageMediaDocument(); + object.document = message.media.document; + object.flags |= 1; } else if (MessageObject.isRoundVideoMessage(message)) { - if ((downloadMask & MediaController.AUTODOWNLOAD_MASK_VIDEOMESSAGE) != 0 && message.media.document.size < 1024 * 1024 * 5) { - id = message.media.document.id; - type = MediaController.AUTODOWNLOAD_MASK_VIDEOMESSAGE; - object = new TLRPC.TL_messageMediaDocument(); - object.document = message.media.document; - object.flags |= 1; - } + id = message.media.document.id; + type = MediaController.AUTODOWNLOAD_MASK_VIDEOMESSAGE; + object = new TLRPC.TL_messageMediaDocument(); + object.document = message.media.document; + object.flags |= 1; } else if (message.media instanceof TLRPC.TL_messageMediaPhoto) { - if ((downloadMask & MediaController.AUTODOWNLOAD_MASK_PHOTO) != 0) { - TLRPC.PhotoSize photoSize = FileLoader.getClosestPhotoSizeWithSize(message.media.photo.sizes, AndroidUtilities.getPhotoSize()); - if (photoSize != null) { - id = message.media.photo.id; - type = MediaController.AUTODOWNLOAD_MASK_PHOTO; - object = new TLRPC.TL_messageMediaPhoto(); - object.photo = message.media.photo; - object.flags |= 1; - } + TLRPC.PhotoSize photoSize = FileLoader.getClosestPhotoSizeWithSize(message.media.photo.sizes, AndroidUtilities.getPhotoSize()); + if (photoSize != null) { + id = message.media.photo.id; + type = MediaController.AUTODOWNLOAD_MASK_PHOTO; + object = new TLRPC.TL_messageMediaPhoto(); + object.photo = message.media.photo; + object.flags |= 1; } } else if (MessageObject.isVideoMessage(message)) { - if ((downloadMask & MediaController.AUTODOWNLOAD_MASK_VIDEO) != 0) { - id = message.media.document.id; - type = MediaController.AUTODOWNLOAD_MASK_VIDEO; - object = new TLRPC.TL_messageMediaDocument(); - object.document = message.media.document; - object.flags |= 1; - } + id = message.media.document.id; + type = MediaController.AUTODOWNLOAD_MASK_VIDEO; + object = new TLRPC.TL_messageMediaDocument(); + object.document = message.media.document; + object.flags |= 1; } else if (message.media instanceof TLRPC.TL_messageMediaDocument && !MessageObject.isMusicMessage(message) && !MessageObject.isGifDocument(message.media.document)) { - if ((downloadMask & MediaController.AUTODOWNLOAD_MASK_DOCUMENT) != 0) { - id = message.media.document.id; - type = MediaController.AUTODOWNLOAD_MASK_DOCUMENT; - object = new TLRPC.TL_messageMediaDocument(); - object.document = message.media.document; - object.flags |= 1; - } + id = message.media.document.id; + type = MediaController.AUTODOWNLOAD_MASK_DOCUMENT; + object = new TLRPC.TL_messageMediaDocument(); + object.document = message.media.document; + object.flags |= 1; } if (object != null) { if (message.media.ttl_seconds != 0) { @@ -4670,7 +5118,7 @@ public class MessagesStorage { channelId = message.to_id.channel_id; } - SQLiteCursor cursor = database.queryFinalized("SELECT date, unread_count, pts, last_mid, inbox_max, outbox_max, pinned FROM dialogs WHERE did = " + key); + SQLiteCursor cursor = database.queryFinalized("SELECT date, unread_count, pts, last_mid, inbox_max, outbox_max, pinned, unread_count_i FROM dialogs WHERE did = " + key); int dialog_date = 0; int last_mid = 0; int old_unread_count = 0; @@ -4678,6 +5126,7 @@ public class MessagesStorage { int inbox_max = 0; int outbox_max = 0; int pinned = 0; + int old_mentions_count = 0; if (cursor.next()) { dialog_date = cursor.intValue(0); old_unread_count = cursor.intValue(1); @@ -4686,17 +5135,24 @@ public class MessagesStorage { inbox_max = cursor.intValue(4); outbox_max = cursor.intValue(5); pinned = cursor.intValue(6); + old_mentions_count = cursor.intValue(7); } else if (channelId != 0) { MessagesController.getInstance().checkChannelInviter(channelId); } cursor.dispose(); + Integer mentions_count = mentionCounts.get(key); Integer unread_count = messagesCounts.get(key); if (unread_count == null) { unread_count = 0; } else { messagesCounts.put(key, unread_count + old_unread_count); } + if (mentions_count == null) { + mentions_count = 0; + } else { + mentionCounts.put(key, mentions_count + old_mentions_count); + } long messageId = message != null ? message.id : last_mid; if (message != null) { if (message.local_id != 0) { @@ -4720,7 +5176,7 @@ public class MessagesStorage { state.bindInteger(5, inbox_max); state.bindInteger(6, outbox_max); state.bindLong(7, 0); - state.bindInteger(8, 0); + state.bindInteger(8, old_mentions_count + mentions_count); state.bindInteger(9, pts); state.bindInteger(10, 0); state.bindInteger(11, pinned); @@ -4756,7 +5212,7 @@ public class MessagesStorage { if (withTransaction) { database.commitTransaction(); } - MessagesController.getInstance().processDialogsUpdateRead(messagesCounts); + MessagesController.getInstance().processDialogsUpdateRead(messagesCounts, mentionCounts); if (downloadMediaMask != 0) { final int downloadMediaMaskFinal = downloadMediaMask; @@ -5196,7 +5652,7 @@ public class MessagesStorage { NotificationCenter.getInstance().postNotificationName(NotificationCenter.messagesDeleted, mids, 0); } }); - MessagesStorage.getInstance().updateDialogsWithReadMessagesInternal(mids, null, null); + MessagesStorage.getInstance().updateDialogsWithReadMessagesInternal(mids, null, null, null); MessagesStorage.getInstance().markMessagesAsDeletedInternal(mids, 0); MessagesStorage.getInstance().updateDialogsWithDeletedMessagesInternal(mids, null, 0); } @@ -5211,7 +5667,7 @@ public class MessagesStorage { try { String ids; ArrayList dialogsIds = new ArrayList<>(); - HashMap dialogsToUpdate = new HashMap<>(); + HashMap dialogsToUpdate = new HashMap<>(); if (channelId != 0) { StringBuilder builder = new StringBuilder(messages.size()); for (int a = 0; a < messages.size(); a++) { @@ -5226,9 +5682,10 @@ public class MessagesStorage { } else { ids = TextUtils.join(",", messages); } - SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT uid, data, read_state, out FROM messages WHERE mid IN(%s)", ids)); ArrayList filesToDelete = new ArrayList<>(); int currentUser = UserConfig.getClientUserId(); + SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT uid, data, read_state, out, mention FROM messages WHERE mid IN(%s)", ids)); + try { while (cursor.next()) { long did = cursor.longValue(0); @@ -5236,13 +5693,18 @@ public class MessagesStorage { continue; } int read_state = cursor.intValue(2); - if ((read_state == 0 || read_state == 2) && cursor.intValue(3) == 0) { - Integer unread_count = dialogsToUpdate.get(did); + if (cursor.intValue(3) == 0) { + Integer[] unread_count = dialogsToUpdate.get(did); if (unread_count == null) { - unread_count = 0; + unread_count = new Integer[]{0, 0}; + dialogsToUpdate.put(did, unread_count); + } + if (read_state < 2) { + unread_count[1]++; + } + if (read_state == 0 || read_state == 2) { + unread_count[0]++; } - unread_count++; - dialogsToUpdate.put(did, unread_count); } if ((int) did != 0) { continue; @@ -5276,15 +5738,27 @@ public class MessagesStorage { FileLog.e(e); } cursor.dispose(); + FileLoader.getInstance().deleteFiles(filesToDelete, 0); - for (HashMap.Entry entry : dialogsToUpdate.entrySet()) { + for (HashMap.Entry entry : dialogsToUpdate.entrySet()) { Long did = entry.getKey(); + Integer[] counts = entry.getValue(); + + cursor = database.queryFinalized("SELECT unread_count, unread_count_i FROM dialogs WHERE did = " + did); + int old_unread_count = 0; + int old_mentions_count = 0; + if (cursor.next()) { + old_unread_count = cursor.intValue(0); + old_mentions_count = cursor.intValue(1); + } + cursor.dispose(); + dialogsIds.add(did); - SQLitePreparedStatement state = database.executeFast("UPDATE dialogs SET unread_count = max(0, ((SELECT unread_count FROM dialogs WHERE did = ?) - ?)) WHERE did = ?"); + SQLitePreparedStatement state = database.executeFast("UPDATE dialogs SET unread_count = ?, unread_count_i = ? WHERE did = ?"); state.requery(); - state.bindLong(1, did); - state.bindInteger(2, entry.getValue()); + state.bindInteger(1, Math.max(0, old_unread_count - counts[0])); + state.bindInteger(2, Math.max(0, old_mentions_count - counts[1])); state.bindLong(3, did); state.step(); state.dispose(); @@ -5336,7 +5810,7 @@ public class MessagesStorage { state.dispose(); database.commitTransaction(); } else { - dialogsToUpdate.add((long)-channelId); + dialogsToUpdate.add((long) -channelId); } if (additionalDialogsToUpdate != null) { for (int a = 0; a < additionalDialogsToUpdate.size(); a++) { @@ -5348,12 +5822,12 @@ public class MessagesStorage { } ids = TextUtils.join(",", dialogsToUpdate); - TLRPC.messages_Dialogs dialogs = new TLRPC.messages_Dialogs(); + TLRPC.messages_Dialogs dialogs = new TLRPC.TL_messages_dialogs(); ArrayList encryptedChats = new ArrayList<>(); ArrayList usersToLoad = new ArrayList<>(); ArrayList chatsToLoad = new ArrayList<>(); ArrayList encryptedToLoad = new ArrayList<>(); - SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT d.did, d.last_mid, d.unread_count, d.date, m.data, m.read_state, m.mid, m.send_state, m.date, d.pts, d.inbox_max, d.outbox_max, d.pinned FROM dialogs as d LEFT JOIN messages as m ON d.last_mid = m.mid WHERE d.did IN(%s)", ids)); + SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT d.did, d.last_mid, d.unread_count, d.date, m.data, m.read_state, m.mid, m.send_state, m.date, d.pts, d.inbox_max, d.outbox_max, d.pinned, d.unread_count_i FROM dialogs as d LEFT JOIN messages as m ON d.last_mid = m.mid WHERE d.did IN(%s)", ids)); while (cursor.next()) { TLRPC.TL_dialog dialog = new TLRPC.TL_dialog(); dialog.id = cursor.longValue(0); @@ -5361,6 +5835,7 @@ public class MessagesStorage { dialog.read_inbox_max_id = cursor.intValue(10); dialog.read_outbox_max_id = cursor.intValue(11); dialog.unread_count = cursor.intValue(2); + dialog.unread_mentions_count = cursor.intValue(13); dialog.last_message_date = cursor.intValue(3); dialog.pts = cursor.intValue(9); dialog.flags = channelId == 0 ? 0 : 1; @@ -5465,6 +5940,121 @@ public class MessagesStorage { return null; } + private ArrayList markMessagesAsDeletedInternal(final int channelId, final int mid) { + try { + String ids; + ArrayList dialogsIds = new ArrayList<>(); + HashMap dialogsToUpdate = new HashMap<>(); + long maxMessageId = mid; + maxMessageId |= ((long) channelId) << 32; + + ArrayList filesToDelete = new ArrayList<>(); + int currentUser = UserConfig.getClientUserId(); + + SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT uid, data, read_state, out, mention FROM messages WHERE uid = %d AND mid <= %d", -channelId, maxMessageId)); + + try { + while (cursor.next()) { + long did = cursor.longValue(0); + if (did == currentUser) { + continue; + } + int read_state = cursor.intValue(2); + if (cursor.intValue(3) == 0) { + Integer[] unread_count = dialogsToUpdate.get(did); + if (unread_count == null) { + unread_count = new Integer[]{0, 0}; + dialogsToUpdate.put(did, unread_count); + } + if (read_state < 2) { + unread_count[1]++; + } + if (read_state == 0 || read_state == 2) { + unread_count[0]++; + } + } + if ((int) did != 0) { + continue; + } + NativeByteBuffer data = cursor.byteBufferValue(1); + if (data != null) { + TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); + if (message != null) { + if (message.media instanceof TLRPC.TL_messageMediaPhoto) { + for (TLRPC.PhotoSize photoSize : message.media.photo.sizes) { + File file = FileLoader.getPathToAttach(photoSize); + if (file != null && file.toString().length() > 0) { + filesToDelete.add(file); + } + } + } else if (message.media instanceof TLRPC.TL_messageMediaDocument) { + File file = FileLoader.getPathToAttach(message.media.document); + if (file != null && file.toString().length() > 0) { + filesToDelete.add(file); + } + file = FileLoader.getPathToAttach(message.media.document.thumb); + if (file != null && file.toString().length() > 0) { + filesToDelete.add(file); + } + } + } + } + } + } catch (Exception e) { + FileLog.e(e); + } + cursor.dispose(); + + FileLoader.getInstance().deleteFiles(filesToDelete, 0); + + for (HashMap.Entry entry : dialogsToUpdate.entrySet()) { + Long did = entry.getKey(); + Integer[] counts = entry.getValue(); + + cursor = database.queryFinalized("SELECT unread_count, unread_count_i FROM dialogs WHERE did = " + did); + int old_unread_count = 0; + int old_mentions_count = 0; + if (cursor.next()) { + old_unread_count = cursor.intValue(0); + old_mentions_count = cursor.intValue(1); + } + cursor.dispose(); + + dialogsIds.add(did); + SQLitePreparedStatement state = database.executeFast("UPDATE dialogs SET unread_count = ?, unread_count_i = ? WHERE did = ?"); + state.requery(); + state.bindInteger(1, Math.max(0, old_unread_count - counts[0])); + state.bindInteger(2, Math.max(0, old_mentions_count - counts[1])); + state.bindLong(3, did); + state.step(); + state.dispose(); + } + + database.executeFast(String.format(Locale.US, "DELETE FROM messages WHERE uid = %d AND mid <= %d", -channelId, maxMessageId)).stepThis().dispose(); + database.executeFast(String.format(Locale.US, "DELETE FROM media_v2 WHERE uid = %d AND mid <= %d", -channelId, maxMessageId)).stepThis().dispose(); + database.executeFast("DELETE FROM media_counts_v2 WHERE 1").stepThis().dispose(); + return dialogsIds; + } catch (Exception e) { + FileLog.e(e); + } + return null; + } + + public ArrayList markMessagesAsDeleted(final int channelId, final int mid, boolean useQueue) { + if (useQueue) { + storageQueue.postRunnable(new Runnable() { + @Override + public void run() { + markMessagesAsDeletedInternal(channelId, mid); + } + }); + } else { + return markMessagesAsDeletedInternal(channelId, mid); + } + return null; + } + private void fixUnsupportedMedia(TLRPC.Message message) { if (message == null) { return; @@ -5683,6 +6273,7 @@ public class MessagesStorage { @Override public void run() { try { + int mentionCountUpdate = Integer.MAX_VALUE; if (messages.messages.isEmpty()) { if (load_type == 0) { doneHolesInTable("messages_holes", dialog_id, max_id); @@ -5714,7 +6305,7 @@ public class MessagesStorage { //load_type == 3 ? load around message //load_type == 4 ? load around date - SQLitePreparedStatement state = database.executeFast("REPLACE INTO messages VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?)"); + SQLitePreparedStatement state = database.executeFast("REPLACE INTO messages VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?)"); SQLitePreparedStatement state2 = database.executeFast("REPLACE INTO media_v2 VALUES(?, ?, ?, ?, ?)"); SQLitePreparedStatement state5 = null; TLRPC.Message botKeyboard = null; @@ -5733,7 +6324,7 @@ public class MessagesStorage { } if (load_type == -2) { - SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT mid, data, ttl FROM messages WHERE mid = %d", messageId)); + SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT mid, data, ttl, mention, read_state FROM messages WHERE mid = %d", messageId)); boolean exist; if (exist = cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(1); @@ -5745,6 +6336,26 @@ public class MessagesStorage { message.ttl = cursor.intValue(2); } } + boolean oldMention = cursor.intValue(3) != 0; + int readState = cursor.intValue(4); + if (oldMention != message.mentioned) { + if (mentionCountUpdate == Integer.MAX_VALUE) { + SQLiteCursor cursor2 = database.queryFinalized("SELECT unread_count_i FROM dialogs WHERE did = " + dialog_id); + if (cursor2.next()) { + mentionCountUpdate = cursor2.intValue(0); + } + cursor2.dispose(); + } + if (oldMention) { + if (readState <= 1) { + mentionCountUpdate--; + } + } else { + if (message.media_unread) { + mentionCountUpdate++; + } + } + } } cursor.dispose(); if (!exist) { @@ -5754,9 +6365,11 @@ public class MessagesStorage { if (a == 0 && createDialog) { int pinned = 0; - SQLiteCursor cursor = database.queryFinalized("SELECT pinned FROM dialogs WHERE did = " + dialog_id); + int mentions = 0; + SQLiteCursor cursor = database.queryFinalized("SELECT pinned, unread_count_i FROM dialogs WHERE did = " + dialog_id); if (cursor.next()) { pinned = cursor.intValue(0); + mentions = cursor.intValue(1); } cursor.dispose(); @@ -5768,7 +6381,7 @@ public class MessagesStorage { state3.bindInteger(5, message.id); state3.bindInteger(6, 0); state3.bindLong(7, messageId); - state3.bindInteger(8, message.ttl); + state3.bindInteger(8, mentions); state3.bindInteger(9, messages.pts); state3.bindInteger(10, message.date); state3.bindInteger(11, pinned); @@ -5794,6 +6407,7 @@ public class MessagesStorage { state.bindInteger(9, getMessageMediaType(message)); } state.bindInteger(10, 0); + state.bindInteger(11, message.mentioned ? 1 : 0); state.step(); if (SharedMediaQuery.canAddMessageToMedia(message)) { @@ -5834,6 +6448,13 @@ public class MessagesStorage { putUsersInternal(messages.users); putChatsInternal(messages.chats); + if (mentionCountUpdate != Integer.MAX_VALUE) { + database.executeFast(String.format(Locale.US, "UPDATE dialogs SET unread_count_i = %d WHERE did = %d", mentionCountUpdate, dialog_id)).stepThis().dispose(); + HashMap hashMap = new HashMap<>(); + hashMap.put(dialog_id, mentionCountUpdate); + MessagesController.getInstance().processDialogsUpdateRead(null, hashMap); + } + database.commitTransaction(); if (createDialog) { @@ -5906,6 +6527,21 @@ public class MessagesStorage { chatsToLoad.add(message.fwd_from.channel_id); } } + if (message.fwd_from.saved_from_peer != null) { + if (message.fwd_from.saved_from_peer.user_id != 0) { + if (!chatsToLoad.contains(message.fwd_from.saved_from_peer.user_id)) { + usersToLoad.add(message.fwd_from.saved_from_peer.user_id); + } + } else if (message.fwd_from.saved_from_peer.channel_id != 0) { + if (!chatsToLoad.contains(message.fwd_from.saved_from_peer.channel_id)) { + chatsToLoad.add(message.fwd_from.saved_from_peer.channel_id); + } + } else if (message.fwd_from.saved_from_peer.chat_id != 0) { + if (!chatsToLoad.contains(message.fwd_from.saved_from_peer.chat_id)) { + chatsToLoad.add(message.fwd_from.saved_from_peer.chat_id); + } + } + } } if (message.ttl < 0) { if (!chatsToLoad.contains(-message.ttl)) { @@ -5918,7 +6554,7 @@ public class MessagesStorage { storageQueue.postRunnable(new Runnable() { @Override public void run() { - TLRPC.messages_Dialogs dialogs = new TLRPC.messages_Dialogs(); + TLRPC.messages_Dialogs dialogs = new TLRPC.TL_messages_dialogs(); ArrayList encryptedChats = new ArrayList<>(); try { ArrayList usersToLoad = new ArrayList<>(); @@ -5927,7 +6563,7 @@ public class MessagesStorage { ArrayList encryptedToLoad = new ArrayList<>(); ArrayList replyMessages = new ArrayList<>(); HashMap replyMessageOwners = new HashMap<>(); - SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT d.did, d.last_mid, d.unread_count, d.date, m.data, m.read_state, m.mid, m.send_state, s.flags, m.date, d.pts, d.inbox_max, d.outbox_max, m.replydata, d.pinned FROM dialogs as d LEFT JOIN messages as m ON d.last_mid = m.mid LEFT JOIN dialog_settings as s ON d.did = s.did ORDER BY d.pinned DESC, d.date DESC LIMIT %d,%d", offset, count)); + SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT d.did, d.last_mid, d.unread_count, d.date, m.data, m.read_state, m.mid, m.send_state, s.flags, m.date, d.pts, d.inbox_max, d.outbox_max, m.replydata, d.pinned, d.unread_count_i FROM dialogs as d LEFT JOIN messages as m ON d.last_mid = m.mid LEFT JOIN dialog_settings as s ON d.did = s.did ORDER BY d.pinned DESC, d.date DESC LIMIT %d,%d", offset, count)); while (cursor.next()) { TLRPC.TL_dialog dialog = new TLRPC.TL_dialog(); dialog.id = cursor.longValue(0); @@ -5940,6 +6576,7 @@ public class MessagesStorage { dialog.read_outbox_max_id = cursor.intValue(12); dialog.pinnedNum = cursor.intValue(14); dialog.pinned = dialog.pinnedNum != 0; + dialog.unread_mentions_count = cursor.intValue(15); long flags = cursor.longValue(8); int low_flags = (int) flags; dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); @@ -5979,6 +6616,9 @@ public class MessagesStorage { message.replyMessage = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); data.reuse(); if (message.replyMessage != null) { + if (MessageObject.isMegagroup(message)) { + message.replyMessage.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; + } addUsersAndChatsFromMessage(message.replyMessage, usersToLoad, chatsToLoad); } } @@ -6043,6 +6683,9 @@ public class MessagesStorage { if (owner != null) { owner.replyMessage = message; message.dialog_id = owner.dialog_id; + if (MessageObject.isMegagroup(owner)) { + owner.replyMessage.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; + } } } } @@ -6099,7 +6742,7 @@ public class MessagesStorage { } if (!dialogs.dialogs.isEmpty()) { - SQLitePreparedStatement state = database.executeFast("REPLACE INTO messages VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?)"); + SQLitePreparedStatement state = database.executeFast("REPLACE INTO messages VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?)"); SQLitePreparedStatement state2 = database.executeFast("REPLACE INTO dialogs VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); SQLitePreparedStatement state3 = database.executeFast("REPLACE INTO media_v2 VALUES(?, ?, ?, ?, ?)"); SQLitePreparedStatement state4 = database.executeFast("REPLACE INTO dialog_settings VALUES(?, ?)"); @@ -6156,6 +6799,7 @@ public class MessagesStorage { state.bindInteger(8, 0); state.bindInteger(9, (message.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0 ? message.views : 0); state.bindInteger(10, 0); + state.bindInteger(11, message.mentioned ? 1 : 0); state.step(); if (SharedMediaQuery.canAddMessageToMedia(message)) { @@ -6185,7 +6829,7 @@ public class MessagesStorage { state2.bindInteger(5, dialog.read_inbox_max_id); state2.bindInteger(6, dialog.read_outbox_max_id); state2.bindLong(7, 0); - state2.bindInteger(8, 0); + state2.bindInteger(8, dialog.unread_mentions_count); state2.bindInteger(9, dialog.pts); state2.bindInteger(10, 0); state2.bindInteger(11, dialog.pinnedNum); @@ -6272,7 +6916,11 @@ public class MessagesStorage { @Override public void run() { putDialogsInternal(dialogs, check); - loadUnreadMessages(); + try { + loadUnreadMessages(); + } catch (Exception e) { + FileLog.e(e); + } } }); } @@ -6365,13 +7013,13 @@ public class MessagesStorage { return user[0]; } - public TLRPC.Chat getChatSync(final int user_id) { + public TLRPC.Chat getChatSync(final int chat_id) { final Semaphore semaphore = new Semaphore(0); final TLRPC.Chat[] chat = new TLRPC.Chat[1]; MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { @Override public void run() { - chat[0] = getChat(user_id); + chat[0] = getChat(chat_id); semaphore.release(); } }); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MusicPlayerService.java b/TMessagesProj/src/main/java/org/telegram/messenger/MusicPlayerService.java index 6b273bc4a..f2521fd6d 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MusicPlayerService.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MusicPlayerService.java @@ -10,17 +10,24 @@ package org.telegram.messenger; import android.annotation.SuppressLint; import android.app.Notification; +import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.ComponentName; import android.content.Intent; import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; import android.media.AudioManager; +import android.media.MediaMetadata; import android.media.MediaMetadataRetriever; import android.media.RemoteControlClient; +import android.media.session.MediaSession; +import android.media.session.PlaybackState; import android.os.Build; import android.os.IBinder; import android.support.v4.app.NotificationCompat; +import android.text.TextUtils; import android.view.View; import android.widget.RemoteViews; @@ -35,11 +42,17 @@ public class MusicPlayerService extends Service implements NotificationCenter.No public static final String NOTIFY_PLAY = "org.telegram.android.musicplayer.play"; public static final String NOTIFY_NEXT = "org.telegram.android.musicplayer.next"; + private static final int ID_NOTIFICATION = 5; + private RemoteControlClient remoteControlClient; private AudioManager audioManager; private static boolean supportBigNotifications = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; - private static boolean supportLockScreenControls = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH; + private static boolean supportLockScreenControls = Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP; + + private MediaSession mediaSession; + private PlaybackState.Builder playbackState; + private Bitmap albumArtPlaceholder; @Override public IBinder onBind(Intent intent) { @@ -51,6 +64,43 @@ public class MusicPlayerService extends Service implements NotificationCenter.No audioManager = (AudioManager) getSystemService(AUDIO_SERVICE); NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingProgressDidChanged); NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingPlayStateChanged); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + mediaSession = new MediaSession(this, "telegramAudioPlayer"); + playbackState = new PlaybackState.Builder(); + albumArtPlaceholder = Bitmap.createBitmap(AndroidUtilities.dp(102), AndroidUtilities.dp(102), Bitmap.Config.ARGB_8888); + Drawable placeholder = getResources().getDrawable(R.drawable.nocover_big); + placeholder.setBounds(0, 0, albumArtPlaceholder.getWidth(), albumArtPlaceholder.getHeight()); + placeholder.draw(new Canvas(albumArtPlaceholder)); + mediaSession.setCallback(new MediaSession.Callback() { + @Override + public void onPlay() { + MediaController.getInstance().playMessage(MediaController.getInstance().getPlayingMessageObject()); + } + + @Override + public void onPause() { + MediaController.getInstance().pauseMessage(MediaController.getInstance().getPlayingMessageObject()); + } + + @Override + public void onSkipToNext() { + MediaController.getInstance().playNextMessage(); + } + + @Override + public void onSkipToPrevious() { + MediaController.getInstance().playPreviousMessage(); + } + + @Override + public void onStop() { + //stopSelf(); + } + }); + mediaSession.setActive(true); + } + super.onCreate(); } @@ -58,6 +108,10 @@ public class MusicPlayerService extends Service implements NotificationCenter.No @Override public int onStartCommand(Intent intent, int flags, int startId) { try { + if (intent != null && (getPackageName() + ".STOP_PLAYER").equals(intent.getAction())) { + MediaController.getInstance().cleanupPlayer(true, true); + return START_NOT_STICKY; + } MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject(); if (messageObject == null) { AndroidUtilities.runOnUIThread(new Runnable() { @@ -98,92 +152,164 @@ public class MusicPlayerService extends Service implements NotificationCenter.No String authorName = messageObject.getMusicAuthor(); AudioInfo audioInfo = MediaController.getInstance().getAudioInfo(); - RemoteViews simpleContentView = new RemoteViews(getApplicationContext().getPackageName(), R.layout.player_small_notification); - RemoteViews expandedView = null; - if (supportBigNotifications) { - expandedView = new RemoteViews(getApplicationContext().getPackageName(), R.layout.player_big_notification); - } - Intent intent = new Intent(ApplicationLoader.applicationContext, LaunchActivity.class); intent.setAction("com.tmessages.openplayer"); - intent.setFlags(32768); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); PendingIntent contentIntent = PendingIntent.getActivity(ApplicationLoader.applicationContext, 0, intent, 0); - Notification notification = new NotificationCompat.Builder(getApplicationContext()) - .setSmallIcon(R.drawable.player) - .setContentIntent(contentIntent) - .setContentTitle(songName).build(); + Notification notification; - notification.contentView = simpleContentView; - if (supportBigNotifications) { - notification.bigContentView = expandedView; - } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - setListeners(simpleContentView); - if (supportBigNotifications) { - setListeners(expandedView); - } + Bitmap albumArt = audioInfo != null ? audioInfo.getSmallCover() : null; + Bitmap fullAlbumArt = audioInfo != null ? audioInfo.getCover() : null; + boolean isPlaying = !MediaController.getInstance().isMessagePaused(); - Bitmap albumArt = audioInfo != null ? audioInfo.getSmallCover() : null; - if (albumArt != null) { - notification.contentView.setImageViewBitmap(R.id.player_album_art, albumArt); - if (supportBigNotifications) { - notification.bigContentView.setImageViewBitmap(R.id.player_album_art, albumArt); + PendingIntent pendingPrev = PendingIntent.getBroadcast(getApplicationContext(), 0, new Intent(NOTIFY_PREVIOUS).setComponent(new ComponentName(this, MusicPlayerReceiver.class)), PendingIntent.FLAG_CANCEL_CURRENT); + //PendingIntent pendingStop = PendingIntent.getBroadcast(getApplicationContext(), 0, new Intent(NOTIFY_CLOSE).setComponent(new ComponentName(this, MusicPlayerReceiver.class)), PendingIntent.FLAG_CANCEL_CURRENT); + PendingIntent pendingStop = PendingIntent.getService(getApplicationContext(), 0, new Intent(this, getClass()).setAction(getPackageName() + ".STOP_PLAYER"), PendingIntent.FLAG_CANCEL_CURRENT); + PendingIntent pendingPlaypause = PendingIntent.getBroadcast(getApplicationContext(), 0, new Intent(isPlaying ? NOTIFY_PAUSE : NOTIFY_PLAY).setComponent(new ComponentName(this, MusicPlayerReceiver.class)), PendingIntent.FLAG_CANCEL_CURRENT); + PendingIntent pendingNext = PendingIntent.getBroadcast(getApplicationContext(), 0, new Intent(NOTIFY_NEXT).setComponent(new ComponentName(this, MusicPlayerReceiver.class)), PendingIntent.FLAG_CANCEL_CURRENT); + + Notification.Builder bldr = new Notification.Builder(this); + bldr.setSmallIcon(R.drawable.player) + .setOngoing(isPlaying) + .setContentTitle(songName) + .setContentText(authorName) + .setSubText(audioInfo != null ? audioInfo.getAlbum() : null) + .setContentIntent(contentIntent) + .setDeleteIntent(pendingStop) + .setShowWhen(false) + .setCategory(Notification.CATEGORY_TRANSPORT) + .setPriority(Notification.PRIORITY_MAX) + .setStyle(new Notification.MediaStyle() + .setMediaSession(mediaSession.getSessionToken()) + .setShowActionsInCompactView(0, 1, 2)); + if (albumArt != null) { + bldr.setLargeIcon(albumArt); + } else { + bldr.setLargeIcon(albumArtPlaceholder); } + + if (MediaController.getInstance().isDownloadingCurrentMessage()) { + playbackState.setState(PlaybackState.STATE_BUFFERING, 0, 1).setActions(0); + bldr.addAction(new Notification.Action.Builder(R.drawable.ic_action_previous, null, pendingPrev).build()) + .addAction(new Notification.Action.Builder(R.drawable.loading_animation2, null, null).build()) + .addAction(new Notification.Action.Builder(R.drawable.ic_action_next, null, pendingNext).build()); + } else { + playbackState.setState(isPlaying ? PlaybackState.STATE_PLAYING : PlaybackState.STATE_PAUSED, + MediaController.getInstance().getPlayingMessageObject().audioProgressSec * 1000L, + isPlaying ? 1 : 0) + .setActions(PlaybackState.ACTION_PLAY_PAUSE | PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_SKIP_TO_PREVIOUS | PlaybackState.ACTION_SKIP_TO_NEXT); + bldr.addAction(new Notification.Action.Builder(R.drawable.ic_action_previous, null, pendingPrev).build()) + .addAction(new Notification.Action.Builder(isPlaying ? R.drawable.ic_action_pause : R.drawable.ic_action_play, null, pendingPlaypause).build()) + .addAction(new Notification.Action.Builder(R.drawable.ic_action_next, null, pendingNext).build()); + } + + mediaSession.setPlaybackState(playbackState.build()); + MediaMetadata.Builder meta = new MediaMetadata.Builder() + .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, fullAlbumArt) + .putString(MediaMetadata.METADATA_KEY_ALBUM_ARTIST, authorName) + .putString(MediaMetadata.METADATA_KEY_TITLE, songName) + .putString(MediaMetadata.METADATA_KEY_ALBUM, audioInfo != null ? audioInfo.getAlbum() : null); + + mediaSession.setMetadata(meta.build()); + + notification = bldr.build(); + + if (isPlaying) { + startForeground(ID_NOTIFICATION, notification); + } else { + stopForeground(false); + NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + nm.notify(ID_NOTIFICATION, notification); + } + } else { - notification.contentView.setImageViewResource(R.id.player_album_art, R.drawable.nocover_small); + RemoteViews simpleContentView = new RemoteViews(getApplicationContext().getPackageName(), R.layout.player_small_notification); + RemoteViews expandedView = null; if (supportBigNotifications) { - notification.bigContentView.setImageViewResource(R.id.player_album_art, R.drawable.nocover_big); - } - } - if (MediaController.getInstance().isDownloadingCurrentMessage()) { - notification.contentView.setViewVisibility(R.id.player_pause, View.GONE); - notification.contentView.setViewVisibility(R.id.player_play, View.GONE); - notification.contentView.setViewVisibility(R.id.player_next, View.GONE); - notification.contentView.setViewVisibility(R.id.player_previous, View.GONE); - notification.contentView.setViewVisibility(R.id.player_progress_bar, View.VISIBLE); - if (supportBigNotifications) { - notification.bigContentView.setViewVisibility(R.id.player_pause, View.GONE); - notification.bigContentView.setViewVisibility(R.id.player_play, View.GONE); - notification.bigContentView.setViewVisibility(R.id.player_next, View.GONE); - notification.bigContentView.setViewVisibility(R.id.player_previous, View.GONE); - notification.bigContentView.setViewVisibility(R.id.player_progress_bar, View.VISIBLE); - } - } else { - notification.contentView.setViewVisibility(R.id.player_progress_bar, View.GONE); - notification.contentView.setViewVisibility(R.id.player_next, View.VISIBLE); - notification.contentView.setViewVisibility(R.id.player_previous, View.VISIBLE); - if (supportBigNotifications) { - notification.bigContentView.setViewVisibility(R.id.player_next, View.VISIBLE); - notification.bigContentView.setViewVisibility(R.id.player_previous, View.VISIBLE); - notification.bigContentView.setViewVisibility(R.id.player_progress_bar, View.GONE); + expandedView = new RemoteViews(getApplicationContext().getPackageName(), R.layout.player_big_notification); } - if (MediaController.getInstance().isMessagePaused()) { - notification.contentView.setViewVisibility(R.id.player_pause, View.GONE); - notification.contentView.setViewVisibility(R.id.player_play, View.VISIBLE); + notification = new NotificationCompat.Builder(getApplicationContext()) + .setSmallIcon(R.drawable.player) + .setContentIntent(contentIntent) + .setContentTitle(songName).build(); + + notification.contentView = simpleContentView; + if (supportBigNotifications) { + notification.bigContentView = expandedView; + } + + setListeners(simpleContentView); + if (supportBigNotifications) { + setListeners(expandedView); + } + + Bitmap albumArt = audioInfo != null ? audioInfo.getSmallCover() : null; + if (albumArt != null) { + notification.contentView.setImageViewBitmap(R.id.player_album_art, albumArt); if (supportBigNotifications) { - notification.bigContentView.setViewVisibility(R.id.player_pause, View.GONE); - notification.bigContentView.setViewVisibility(R.id.player_play, View.VISIBLE); + notification.bigContentView.setImageViewBitmap(R.id.player_album_art, albumArt); } } else { - notification.contentView.setViewVisibility(R.id.player_pause, View.VISIBLE); - notification.contentView.setViewVisibility(R.id.player_play, View.GONE); + notification.contentView.setImageViewResource(R.id.player_album_art, R.drawable.nocover_small); if (supportBigNotifications) { - notification.bigContentView.setViewVisibility(R.id.player_pause, View.VISIBLE); - notification.bigContentView.setViewVisibility(R.id.player_play, View.GONE); + notification.bigContentView.setImageViewResource(R.id.player_album_art, R.drawable.nocover_big); } } - } + if (MediaController.getInstance().isDownloadingCurrentMessage()) { + notification.contentView.setViewVisibility(R.id.player_pause, View.GONE); + notification.contentView.setViewVisibility(R.id.player_play, View.GONE); + notification.contentView.setViewVisibility(R.id.player_next, View.GONE); + notification.contentView.setViewVisibility(R.id.player_previous, View.GONE); + notification.contentView.setViewVisibility(R.id.player_progress_bar, View.VISIBLE); + if (supportBigNotifications) { + notification.bigContentView.setViewVisibility(R.id.player_pause, View.GONE); + notification.bigContentView.setViewVisibility(R.id.player_play, View.GONE); + notification.bigContentView.setViewVisibility(R.id.player_next, View.GONE); + notification.bigContentView.setViewVisibility(R.id.player_previous, View.GONE); + notification.bigContentView.setViewVisibility(R.id.player_progress_bar, View.VISIBLE); + } + } else { + notification.contentView.setViewVisibility(R.id.player_progress_bar, View.GONE); + notification.contentView.setViewVisibility(R.id.player_next, View.VISIBLE); + notification.contentView.setViewVisibility(R.id.player_previous, View.VISIBLE); + if (supportBigNotifications) { + notification.bigContentView.setViewVisibility(R.id.player_next, View.VISIBLE); + notification.bigContentView.setViewVisibility(R.id.player_previous, View.VISIBLE); + notification.bigContentView.setViewVisibility(R.id.player_progress_bar, View.GONE); + } + + if (MediaController.getInstance().isMessagePaused()) { + notification.contentView.setViewVisibility(R.id.player_pause, View.GONE); + notification.contentView.setViewVisibility(R.id.player_play, View.VISIBLE); + if (supportBigNotifications) { + notification.bigContentView.setViewVisibility(R.id.player_pause, View.GONE); + notification.bigContentView.setViewVisibility(R.id.player_play, View.VISIBLE); + } + } else { + notification.contentView.setViewVisibility(R.id.player_pause, View.VISIBLE); + notification.contentView.setViewVisibility(R.id.player_play, View.GONE); + if (supportBigNotifications) { + notification.bigContentView.setViewVisibility(R.id.player_pause, View.VISIBLE); + notification.bigContentView.setViewVisibility(R.id.player_play, View.GONE); + } + } + } + + notification.contentView.setTextViewText(R.id.player_song_name, songName); + notification.contentView.setTextViewText(R.id.player_author_name, authorName); + if (supportBigNotifications) { + notification.bigContentView.setTextViewText(R.id.player_song_name, songName); + notification.bigContentView.setTextViewText(R.id.player_author_name, authorName); + notification.bigContentView.setTextViewText(R.id.player_album_title, audioInfo != null && !TextUtils.isEmpty(audioInfo.getAlbum()) ? audioInfo.getAlbum() : ""); + } + notification.flags |= Notification.FLAG_ONGOING_EVENT; + startForeground(ID_NOTIFICATION, notification); - notification.contentView.setTextViewText(R.id.player_song_name, songName); - notification.contentView.setTextViewText(R.id.player_author_name, authorName); - if (supportBigNotifications) { - notification.bigContentView.setTextViewText(R.id.player_song_name, songName); - notification.bigContentView.setTextViewText(R.id.player_author_name, authorName); } - notification.flags |= Notification.FLAG_ONGOING_EVENT; - startForeground(5, notification); if (remoteControlClient != null) { RemoteControlClient.MetadataEditor metadataEditor = remoteControlClient.editMetadata(true); @@ -223,6 +349,9 @@ public class MusicPlayerService extends Service implements NotificationCenter.No metadataEditor.apply(); audioManager.unregisterRemoteControlClient(remoteControlClient); } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + mediaSession.release(); + } NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagePlayingProgressDidChanged); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagePlayingPlayStateChanged); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java index be1288cb6..0434e20c7 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java @@ -21,12 +21,15 @@ public class NotificationCenter { public static final int dialogsNeedReload = totalEvents++; public static final int closeChats = totalEvents++; public static final int messagesDeleted = totalEvents++; + public static final int historyCleared = 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 contactsImported = totalEvents++; + public static final int hasNewContactsToImport = totalEvents++; public static final int chatDidCreated = totalEvents++; public static final int chatDidFailCreate = totalEvents++; public static final int chatInfoDidLoaded = totalEvents++; @@ -60,12 +63,14 @@ public class NotificationCenter { public static final int didReceivedWebpagesInUpdates = totalEvents++; public static final int stickersDidLoaded = totalEvents++; public static final int featuredStickersDidLoaded = totalEvents++; + public static final int groupStickersDidLoaded = totalEvents++; public static final int didReplacedPhotoInMemCache = totalEvents++; public static final int messagesReadContent = totalEvents++; public static final int botInfoDidLoaded = totalEvents++; public static final int userInfoDidLoaded = totalEvents++; public static final int botKeyboardDidLoaded = totalEvents++; public static final int chatSearchResultsAvailable = totalEvents++; + public static final int chatSearchResultsLoading = totalEvents++; public static final int musicDidLoaded = totalEvents++; public static final int needShowAlert = totalEvents++; public static final int didUpdatedMessagesViews = totalEvents++; @@ -86,12 +91,18 @@ public class NotificationCenter { public static final int suggestedLangpack = totalEvents++; public static final int channelRightsUpdated = totalEvents++; public static final int proxySettingsChanged = totalEvents++; + public static final int openArticle = totalEvents++; + public static final int updateMentionsCount = totalEvents++; + public static final int liveLocationsChanged = totalEvents++; + public static final int liveLocationsCacheChanged = totalEvents++; public static final int httpFileDidLoaded = totalEvents++; public static final int httpFileDidFailedLoad = totalEvents++; public static final int messageThumbGenerated = totalEvents++; + public static final int didSetNewTheme = totalEvents++; + public static final int wallpapersDidLoaded = totalEvents++; public static final int closeOtherAppActivities = totalEvents++; public static final int didUpdatedConnectionState = totalEvents++; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java index 3a95dee61..aa716cd4f 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java @@ -443,6 +443,82 @@ public class NotificationsController { }); } + public void removeDeletedHisoryFromNotifications(final SparseArray deletedMessages) { + final ArrayList popupArray = popupMessages.isEmpty() ? null : new ArrayList<>(popupMessages); + notificationsQueue.postRunnable(new Runnable() { + @Override + public void run() { + int old_unread_count = total_unread_count; + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Context.MODE_PRIVATE); + + for (int a = 0; a < deletedMessages.size(); a++) { + int key = deletedMessages.keyAt(a); + long dialog_id = -key; + Integer id = deletedMessages.get(key); + Integer currentCount = pushDialogs.get(dialog_id); + if (currentCount == null) { + currentCount = 0; + } + Integer newCount = currentCount; + + for (int c = 0; c < pushMessages.size(); c++) { + MessageObject messageObject = pushMessages.get(c); + if (messageObject.getDialogId() == dialog_id && messageObject.getId() <= id) { + pushMessagesDict.remove(messageObject.getIdWithChannel()); + delayedPushMessages.remove(messageObject); + pushMessages.remove(messageObject); + c--; + if (isPersonalMessage(messageObject)) { + personal_count--; + } + if (popupArray != null) { + popupArray.remove(messageObject); + } + newCount--; + } + } + + if (newCount <= 0) { + newCount = 0; + smartNotificationsDialogs.remove(dialog_id); + } + if (!newCount.equals(currentCount)) { + total_unread_count -= currentCount; + total_unread_count += newCount; + pushDialogs.put(dialog_id, newCount); + } + if (newCount == 0) { + pushDialogs.remove(dialog_id); + pushDialogsOverrideMention.remove(dialog_id); + if (popupArray != null && pushMessages.isEmpty() && !popupArray.isEmpty()) { + popupArray.clear(); + } + } + } + if (popupArray != null) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + popupMessages = popupArray; + } + }); + } + if (old_unread_count != total_unread_count) { + if (!notifyCheck) { + delayedPushMessages.clear(); + showOrUpdateNotification(notifyCheck); + } else { + scheduleNotificationDelay(lastOnlineFromOtherDevice > ConnectionsManager.getInstance().getCurrentTime()); + } + } + notifyCheck = false; + if (preferences.getBoolean("badgeNumber", true)) { + setBadge(total_unread_count); + } + } + }); + } + public void processReadMessages(final SparseArray inbox, final long dialog_id, final int max_date, final int max_id, final boolean isPopup) { final ArrayList popupArray = popupMessages.isEmpty() ? null : new ArrayList<>(popupMessages); notificationsQueue.postRunnable(new Runnable() { @@ -833,7 +909,7 @@ public class NotificationsController { }); } - private String getStringForMessage(MessageObject messageObject, boolean shortMessage) { + private String getStringForMessage(MessageObject messageObject, boolean shortMessage, boolean text[]) { long dialog_id = messageObject.messageOwner.dialog_id; int chat_id = messageObject.messageOwner.to_id.chat_id != 0 ? messageObject.messageOwner.to_id.chat_id : messageObject.messageOwner.to_id.channel_id; int from_id = messageObject.messageOwner.to_id.user_id; @@ -907,6 +983,7 @@ public class NotificationsController { if (!shortMessage) { if (messageObject.messageOwner.message != null && messageObject.messageOwner.message.length() != 0) { msg = LocaleController.formatString("NotificationMessageText", R.string.NotificationMessageText, name, messageObject.messageOwner.message); + text[0] = true; } else { msg = LocaleController.formatString("NotificationMessageNoText", R.string.NotificationMessageNoText, name); } @@ -916,6 +993,7 @@ public class NotificationsController { } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaPhoto) { if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { msg = LocaleController.formatString("NotificationMessageText", R.string.NotificationMessageText, name, "\uD83D\uDDBC " + messageObject.messageOwner.media.caption); + text[0] = true; } else { if (messageObject.messageOwner.media.ttl_seconds != 0) { msg = LocaleController.formatString("NotificationMessageSDPhoto", R.string.NotificationMessageSDPhoto, name); @@ -926,6 +1004,7 @@ public class NotificationsController { } else if (messageObject.isVideo()) { if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { msg = LocaleController.formatString("NotificationMessageText", R.string.NotificationMessageText, name, "\uD83D\uDCF9 " + messageObject.messageOwner.media.caption); + text[0] = true; } else { if (messageObject.messageOwner.media.ttl_seconds != 0) { msg = LocaleController.formatString("NotificationMessageSDVideo", R.string.NotificationMessageSDVideo, name); @@ -956,12 +1035,14 @@ public class NotificationsController { } else if (messageObject.isGif()) { if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { msg = LocaleController.formatString("NotificationMessageText", R.string.NotificationMessageText, name, "\uD83C\uDFAC " + messageObject.messageOwner.media.caption); + text[0] = true; } else { msg = LocaleController.formatString("NotificationMessageGif", R.string.NotificationMessageGif, name); } } else { if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { msg = LocaleController.formatString("NotificationMessageText", R.string.NotificationMessageText, name, "\uD83D\uDCCE " + messageObject.messageOwner.media.caption); + text[0] = true; } else { msg = LocaleController.formatString("NotificationMessageDocument", R.string.NotificationMessageDocument, name); } @@ -1049,138 +1130,130 @@ public class NotificationsController { } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionScreenshotTaken) { msg = messageObject.messageText.toString(); } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionPinMessage) { - if (messageObject.replyMessageObject == null) { - if (!ChatObject.isChannel(chat) || chat.megagroup) { + if (chat != null && chat.megagroup) { + if (messageObject.replyMessageObject == null) { msg = LocaleController.formatString("NotificationActionPinnedNoText", R.string.NotificationActionPinnedNoText, name, chat.title); } else { - msg = LocaleController.formatString("NotificationActionPinnedNoTextChannel", R.string.NotificationActionPinnedNoTextChannel, name, chat.title); - } - } else { - MessageObject object = messageObject.replyMessageObject; - if (object.isMusic()) { - if (!ChatObject.isChannel(chat) || chat.megagroup) { + MessageObject object = messageObject.replyMessageObject; + if (object.isMusic()) { msg = LocaleController.formatString("NotificationActionPinnedMusic", R.string.NotificationActionPinnedMusic, name, chat.title); - } else { - msg = LocaleController.formatString("NotificationActionPinnedMusicChannel", R.string.NotificationActionPinnedMusicChannel, chat.title); - } - } else if (object.isVideo()) { - if (Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(object.messageOwner.media.caption)) { - String message = "\uD83D\uDCF9 " + object.messageOwner.media.caption; - if (!ChatObject.isChannel(chat) || chat.megagroup) { + } else if (object.isVideo()) { + if (Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(object.messageOwner.media.caption)) { + String message = "\uD83D\uDCF9 " + object.messageOwner.media.caption; msg = LocaleController.formatString("NotificationActionPinnedText", R.string.NotificationActionPinnedText, name, message, chat.title); } else { - msg = LocaleController.formatString("NotificationActionPinnedTextChannel", R.string.NotificationActionPinnedTextChannel, chat.title, message); - } - } else { - if (!ChatObject.isChannel(chat) || chat.megagroup) { msg = LocaleController.formatString("NotificationActionPinnedVideo", R.string.NotificationActionPinnedVideo, name, chat.title); + } + } else if (object.isGif()) { + if (Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(object.messageOwner.media.caption)) { + String message = "\uD83C\uDFAC " + object.messageOwner.media.caption; + msg = LocaleController.formatString("NotificationActionPinnedText", R.string.NotificationActionPinnedText, name, message, chat.title); + } else { + msg = LocaleController.formatString("NotificationActionPinnedGif", R.string.NotificationActionPinnedGif, name, chat.title); + } + } else if (object.isVoice()) { + msg = LocaleController.formatString("NotificationActionPinnedVoice", R.string.NotificationActionPinnedVoice, name, chat.title); + } else if (object.isRoundVideo()) { + msg = LocaleController.formatString("NotificationActionPinnedRound", R.string.NotificationActionPinnedRound, name, chat.title); + } else if (object.isSticker()) { + String emoji = object.getStickerEmoji(); + if (emoji != null) { + msg = LocaleController.formatString("NotificationActionPinnedStickerEmoji", R.string.NotificationActionPinnedStickerEmoji, name, chat.title, emoji); + } else { + msg = LocaleController.formatString("NotificationActionPinnedSticker", R.string.NotificationActionPinnedSticker, name, chat.title); + } + } else if (object.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { + if (Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(object.messageOwner.media.caption)) { + String message = "\uD83D\uDCCE " + object.messageOwner.media.caption; + msg = LocaleController.formatString("NotificationActionPinnedText", R.string.NotificationActionPinnedText, name, message, chat.title); + } else { + msg = LocaleController.formatString("NotificationActionPinnedFile", R.string.NotificationActionPinnedFile, name, chat.title); + } + } else if (object.messageOwner.media instanceof TLRPC.TL_messageMediaGeo) { + msg = LocaleController.formatString("NotificationActionPinnedGeo", R.string.NotificationActionPinnedGeo, name, chat.title); + } else if (object.messageOwner.media instanceof TLRPC.TL_messageMediaGeoLive) { + msg = LocaleController.formatString("NotificationActionPinnedGeoLive", R.string.NotificationActionPinnedGeoLive, name, chat.title); + } else if (object.messageOwner.media instanceof TLRPC.TL_messageMediaContact) { + msg = LocaleController.formatString("NotificationActionPinnedContact", R.string.NotificationActionPinnedContact, name, chat.title); + } else if (object.messageOwner.media instanceof TLRPC.TL_messageMediaPhoto) { + if (Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(object.messageOwner.media.caption)) { + String message = "\uD83D\uDDBC " + object.messageOwner.media.caption; + msg = LocaleController.formatString("NotificationActionPinnedText", R.string.NotificationActionPinnedText, name, message, chat.title); + } else { + msg = LocaleController.formatString("NotificationActionPinnedPhoto", R.string.NotificationActionPinnedPhoto, name, chat.title); + } + } else if (object.messageOwner.media instanceof TLRPC.TL_messageMediaGame) { + msg = LocaleController.formatString("NotificationActionPinnedGame", R.string.NotificationActionPinnedGame, name, chat.title); + } else if (object.messageText != null && object.messageText.length() > 0) { + CharSequence message = object.messageText; + if (message.length() > 20) { + message = message.subSequence(0, 20) + "..."; + } + msg = LocaleController.formatString("NotificationActionPinnedText", R.string.NotificationActionPinnedText, name, message, chat.title); + } else { + msg = LocaleController.formatString("NotificationActionPinnedNoText", R.string.NotificationActionPinnedNoText, name, chat.title); + } + } + } else { + if (messageObject.replyMessageObject == null) { + msg = LocaleController.formatString("NotificationActionPinnedNoTextChannel", R.string.NotificationActionPinnedNoTextChannel, chat.title); + } else { + MessageObject object = messageObject.replyMessageObject; + if (object.isMusic()) { + msg = LocaleController.formatString("NotificationActionPinnedMusicChannel", R.string.NotificationActionPinnedMusicChannel, chat.title); + } else if (object.isVideo()) { + if (Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(object.messageOwner.media.caption)) { + String message = "\uD83D\uDCF9 " + object.messageOwner.media.caption; + msg = LocaleController.formatString("NotificationActionPinnedTextChannel", R.string.NotificationActionPinnedTextChannel, chat.title, message); } else { msg = LocaleController.formatString("NotificationActionPinnedVideoChannel", R.string.NotificationActionPinnedVideoChannel, chat.title); } - } - } else if (object.isGif()) { - if (Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(object.messageOwner.media.caption)) { - String message = "\uD83C\uDFAC " + object.messageOwner.media.caption; - if (!ChatObject.isChannel(chat) || chat.megagroup) { - msg = LocaleController.formatString("NotificationActionPinnedText", R.string.NotificationActionPinnedText, name, message, chat.title); - } else { + } else if (object.isGif()) { + if (Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(object.messageOwner.media.caption)) { + String message = "\uD83C\uDFAC " + object.messageOwner.media.caption; msg = LocaleController.formatString("NotificationActionPinnedTextChannel", R.string.NotificationActionPinnedTextChannel, chat.title, message); - } - } else { - if (!ChatObject.isChannel(chat) || chat.megagroup) { - msg = LocaleController.formatString("NotificationActionPinnedGif", R.string.NotificationActionPinnedGif, name, chat.title); } else { msg = LocaleController.formatString("NotificationActionPinnedGifChannel", R.string.NotificationActionPinnedGifChannel, chat.title); } - } - } else if (object.isVoice()) { - if (!ChatObject.isChannel(chat) || chat.megagroup) { - msg = LocaleController.formatString("NotificationActionPinnedVoice", R.string.NotificationActionPinnedVoice, name, chat.title); - } else { + } else if (object.isVoice()) { msg = LocaleController.formatString("NotificationActionPinnedVoiceChannel", R.string.NotificationActionPinnedVoiceChannel, chat.title); - } - } else if (object.isRoundVideo()) { - if (!ChatObject.isChannel(chat) || chat.megagroup) { - msg = LocaleController.formatString("NotificationActionPinnedRound", R.string.NotificationActionPinnedRound, name, chat.title); - } else { + } else if (object.isRoundVideo()) { msg = LocaleController.formatString("NotificationActionPinnedRoundChannel", R.string.NotificationActionPinnedRoundChannel, chat.title); - } - } else if (object.isSticker()) { - String emoji = messageObject.getStickerEmoji(); - if (emoji != null) { - if (!ChatObject.isChannel(chat) || chat.megagroup) { - msg = LocaleController.formatString("NotificationActionPinnedStickerEmoji", R.string.NotificationActionPinnedStickerEmoji, name, chat.title, emoji); - } else { + } else if (object.isSticker()) { + String emoji = object.getStickerEmoji(); + if (emoji != null) { msg = LocaleController.formatString("NotificationActionPinnedStickerEmojiChannel", R.string.NotificationActionPinnedStickerEmojiChannel, chat.title, emoji); - } - } else { - if (!ChatObject.isChannel(chat) || chat.megagroup) { - msg = LocaleController.formatString("NotificationActionPinnedSticker", R.string.NotificationActionPinnedSticker, name, chat.title); } else { msg = LocaleController.formatString("NotificationActionPinnedStickerChannel", R.string.NotificationActionPinnedStickerChannel, chat.title); } - } - } else if (object.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { - if (Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(object.messageOwner.media.caption)) { - String message = "\uD83D\uDCCE " + object.messageOwner.media.caption; - if (!ChatObject.isChannel(chat) || chat.megagroup) { - msg = LocaleController.formatString("NotificationActionPinnedText", R.string.NotificationActionPinnedText, name, message, chat.title); - } else { + } else if (object.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { + if (Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(object.messageOwner.media.caption)) { + String message = "\uD83D\uDCCE " + object.messageOwner.media.caption; msg = LocaleController.formatString("NotificationActionPinnedTextChannel", R.string.NotificationActionPinnedTextChannel, chat.title, message); - } - } else { - if (!ChatObject.isChannel(chat) || chat.megagroup) { - msg = LocaleController.formatString("NotificationActionPinnedFile", R.string.NotificationActionPinnedFile, name, chat.title); } else { msg = LocaleController.formatString("NotificationActionPinnedFileChannel", R.string.NotificationActionPinnedFileChannel, chat.title); } - } - } else if (object.messageOwner.media instanceof TLRPC.TL_messageMediaGeo) { - if (!ChatObject.isChannel(chat) || chat.megagroup) { - msg = LocaleController.formatString("NotificationActionPinnedGeo", R.string.NotificationActionPinnedGeo, name, chat.title); - } else { + } else if (object.messageOwner.media instanceof TLRPC.TL_messageMediaGeo) { msg = LocaleController.formatString("NotificationActionPinnedGeoChannel", R.string.NotificationActionPinnedGeoChannel, chat.title); - } - } else if (object.messageOwner.media instanceof TLRPC.TL_messageMediaContact) { - if (!ChatObject.isChannel(chat) || chat.megagroup) { - msg = LocaleController.formatString("NotificationActionPinnedContact", R.string.NotificationActionPinnedContact, name, chat.title); - } else { + } else if (object.messageOwner.media instanceof TLRPC.TL_messageMediaGeoLive) { + msg = LocaleController.formatString("NotificationActionPinnedGeoLiveChannel", R.string.NotificationActionPinnedGeoLiveChannel, chat.title); + } else if (object.messageOwner.media instanceof TLRPC.TL_messageMediaContact) { msg = LocaleController.formatString("NotificationActionPinnedContactChannel", R.string.NotificationActionPinnedContactChannel, chat.title); - } - } else if (object.messageOwner.media instanceof TLRPC.TL_messageMediaPhoto) { - if (Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(object.messageOwner.media.caption)) { - String message = "\uD83D\uDDBC " + object.messageOwner.media.caption; - if (!ChatObject.isChannel(chat) || chat.megagroup) { - msg = LocaleController.formatString("NotificationActionPinnedText", R.string.NotificationActionPinnedText, name, message, chat.title); - } else { + } else if (object.messageOwner.media instanceof TLRPC.TL_messageMediaPhoto) { + if (Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(object.messageOwner.media.caption)) { + String message = "\uD83D\uDDBC " + object.messageOwner.media.caption; msg = LocaleController.formatString("NotificationActionPinnedTextChannel", R.string.NotificationActionPinnedTextChannel, chat.title, message); - } - } else { - if (!ChatObject.isChannel(chat) || chat.megagroup) { - msg = LocaleController.formatString("NotificationActionPinnedPhoto", R.string.NotificationActionPinnedPhoto, name, chat.title); } else { msg = LocaleController.formatString("NotificationActionPinnedPhotoChannel", R.string.NotificationActionPinnedPhotoChannel, chat.title); } - } - } else if (object.messageOwner.media instanceof TLRPC.TL_messageMediaGame) { - if (!ChatObject.isChannel(chat) || chat.megagroup) { - msg = LocaleController.formatString("NotificationActionPinnedGame", R.string.NotificationActionPinnedGame, name, chat.title); - } else { + } else if (object.messageOwner.media instanceof TLRPC.TL_messageMediaGame) { msg = LocaleController.formatString("NotificationActionPinnedGameChannel", R.string.NotificationActionPinnedGameChannel, chat.title); - } - } else if (object.messageText != null && object.messageText.length() > 0) { - CharSequence message = object.messageText; - if (message.length() > 20) { - message = message.subSequence(0, 20) + "..."; - } - if (!ChatObject.isChannel(chat) || chat.megagroup) { - msg = LocaleController.formatString("NotificationActionPinnedText", R.string.NotificationActionPinnedText, name, message, chat.title); - } else { + } else if (object.messageText != null && object.messageText.length() > 0) { + CharSequence message = object.messageText; + if (message.length() > 20) { + message = message.subSequence(0, 20) + "..."; + } msg = LocaleController.formatString("NotificationActionPinnedTextChannel", R.string.NotificationActionPinnedTextChannel, chat.title, message); - } - } else { - if (!ChatObject.isChannel(chat) || chat.megagroup) { - msg = LocaleController.formatString("NotificationActionPinnedNoText", R.string.NotificationActionPinnedNoText, name, chat.title); } else { msg = LocaleController.formatString("NotificationActionPinnedNoTextChannel", R.string.NotificationActionPinnedNoTextChannel, chat.title); } @@ -1190,106 +1263,58 @@ public class NotificationsController { msg = messageObject.messageText.toString(); } } else if (ChatObject.isChannel(chat) && !chat.megagroup) { - if (messageObject.messageOwner.post) { - if (messageObject.isMediaEmpty()) { - if (!shortMessage && messageObject.messageOwner.message != null && messageObject.messageOwner.message.length() != 0) { - msg = LocaleController.formatString("NotificationMessageText", R.string.NotificationMessageText, name, messageObject.messageOwner.message); - } else { - msg = LocaleController.formatString("ChannelMessageNoText", R.string.ChannelMessageNoText, name); - } - } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaPhoto) { - if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { - msg = LocaleController.formatString("NotificationMessageText", R.string.NotificationMessageText, name, "\uD83D\uDDBC " + messageObject.messageOwner.media.caption); - } else { - msg = LocaleController.formatString("ChannelMessagePhoto", R.string.ChannelMessagePhoto, name); - } - } else if (messageObject.isVideo()) { - if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { - msg = LocaleController.formatString("NotificationMessageText", R.string.NotificationMessageText, name, "\uD83D\uDCF9 " + messageObject.messageOwner.media.caption); - } else { - msg = LocaleController.formatString("ChannelMessageVideo", R.string.ChannelMessageVideo, name); - } - } else if (messageObject.isVoice()) { - msg = LocaleController.formatString("ChannelMessageAudio", R.string.ChannelMessageAudio, name); - } else if (messageObject.isRoundVideo()) { - msg = LocaleController.formatString("ChannelMessageRound", R.string.ChannelMessageRound, name); - } else if (messageObject.isMusic()) { - msg = LocaleController.formatString("ChannelMessageMusic", R.string.ChannelMessageMusic, name); - } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaContact) { - msg = LocaleController.formatString("ChannelMessageContact", R.string.ChannelMessageContact, name); - } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeo || messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaVenue) { - msg = LocaleController.formatString("ChannelMessageMap", R.string.ChannelMessageMap, name); - } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { - if (messageObject.isSticker()) { - String emoji = messageObject.getStickerEmoji(); - if (emoji != null) { - msg = LocaleController.formatString("ChannelMessageStickerEmoji", R.string.ChannelMessageStickerEmoji, name, emoji); - } else { - msg = LocaleController.formatString("ChannelMessageSticker", R.string.ChannelMessageSticker, name); - } - } else if (messageObject.isGif()) { - if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { - msg = LocaleController.formatString("NotificationMessageText", R.string.NotificationMessageText, name, "\uD83C\uDFAC " + messageObject.messageOwner.media.caption); - } else { - msg = LocaleController.formatString("ChannelMessageGIF", R.string.ChannelMessageGIF, name); - } - } else { - if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { - msg = LocaleController.formatString("NotificationMessageText", R.string.NotificationMessageText, name, "\uD83D\uDCCE " + messageObject.messageOwner.media.caption); - } else { - msg = LocaleController.formatString("ChannelMessageDocument", R.string.ChannelMessageDocument, name); - } - } + if (messageObject.isMediaEmpty()) { + if (!shortMessage && messageObject.messageOwner.message != null && messageObject.messageOwner.message.length() != 0) { + msg = LocaleController.formatString("NotificationMessageText", R.string.NotificationMessageText, name, messageObject.messageOwner.message); + text[0] = true; + } else { + msg = LocaleController.formatString("ChannelMessageNoText", R.string.ChannelMessageNoText, name); } - } else { - if (messageObject.isMediaEmpty()) { - if (!shortMessage && messageObject.messageOwner.message != null && messageObject.messageOwner.message.length() != 0) { - msg = LocaleController.formatString("NotificationMessageGroupText", R.string.NotificationMessageGroupText, name, chat.title, messageObject.messageOwner.message); + } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaPhoto) { + if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { + msg = LocaleController.formatString("NotificationMessageText", R.string.NotificationMessageText, name, "\uD83D\uDDBC " + messageObject.messageOwner.media.caption); + text[0] = true; + } else { + msg = LocaleController.formatString("ChannelMessagePhoto", R.string.ChannelMessagePhoto, name); + } + } else if (messageObject.isVideo()) { + if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { + msg = LocaleController.formatString("NotificationMessageText", R.string.NotificationMessageText, name, "\uD83D\uDCF9 " + messageObject.messageOwner.media.caption); + text[0] = true; + } else { + msg = LocaleController.formatString("ChannelMessageVideo", R.string.ChannelMessageVideo, name); + } + } else if (messageObject.isVoice()) { + msg = LocaleController.formatString("ChannelMessageAudio", R.string.ChannelMessageAudio, name); + } else if (messageObject.isRoundVideo()) { + msg = LocaleController.formatString("ChannelMessageRound", R.string.ChannelMessageRound, name); + } else if (messageObject.isMusic()) { + msg = LocaleController.formatString("ChannelMessageMusic", R.string.ChannelMessageMusic, name); + } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaContact) { + msg = LocaleController.formatString("ChannelMessageContact", R.string.ChannelMessageContact, name); + } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeo || messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaVenue) { + msg = LocaleController.formatString("ChannelMessageMap", R.string.ChannelMessageMap, name); + } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { + if (messageObject.isSticker()) { + String emoji = messageObject.getStickerEmoji(); + if (emoji != null) { + msg = LocaleController.formatString("ChannelMessageStickerEmoji", R.string.ChannelMessageStickerEmoji, name, emoji); } else { - msg = LocaleController.formatString("ChannelMessageGroupNoText", R.string.ChannelMessageGroupNoText, name, chat.title); + msg = LocaleController.formatString("ChannelMessageSticker", R.string.ChannelMessageSticker, name); } - } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaPhoto) { + } else if (messageObject.isGif()) { if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { - msg = LocaleController.formatString("NotificationMessageGroupText", R.string.NotificationMessageGroupText, name, chat.title, "\uD83D\uDDBC " + messageObject.messageOwner.media.caption); + msg = LocaleController.formatString("NotificationMessageText", R.string.NotificationMessageText, name, "\uD83C\uDFAC " + messageObject.messageOwner.media.caption); + text[0] = true; } else { - msg = LocaleController.formatString("ChannelMessageGroupPhoto", R.string.ChannelMessageGroupPhoto, name, chat.title); + msg = LocaleController.formatString("ChannelMessageGIF", R.string.ChannelMessageGIF, name); } - } else if (messageObject.isVideo()) { + } else { if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { - msg = LocaleController.formatString("NotificationMessageGroupText", R.string.NotificationMessageGroupText, name, chat.title, "\uD83D\uDCF9 " + messageObject.messageOwner.media.caption); + msg = LocaleController.formatString("NotificationMessageText", R.string.NotificationMessageText, name, "\uD83D\uDCCE " + messageObject.messageOwner.media.caption); + text[0] = true; } else { - msg = LocaleController.formatString("ChannelMessageGroupVideo", R.string.ChannelMessageGroupVideo, name, chat.title); - } - } else if (messageObject.isVoice()) { - msg = LocaleController.formatString("ChannelMessageGroupAudio", R.string.ChannelMessageGroupAudio, name, chat.title); - } else if (messageObject.isRoundVideo()) { - msg = LocaleController.formatString("ChannelMessageGroupRound", R.string.ChannelMessageGroupRound, name, chat.title); - } else if (messageObject.isMusic()) { - msg = LocaleController.formatString("ChannelMessageGroupMusic", R.string.ChannelMessageGroupMusic, name, chat.title); - } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaContact) { - msg = LocaleController.formatString("ChannelMessageGroupContact", R.string.ChannelMessageGroupContact, name, chat.title); - } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeo || messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaVenue) { - msg = LocaleController.formatString("ChannelMessageGroupMap", R.string.ChannelMessageGroupMap, name, chat.title); - } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { - if (messageObject.isSticker()) { - String emoji = messageObject.getStickerEmoji(); - if (emoji != null) { - msg = LocaleController.formatString("ChannelMessageGroupStickerEmoji", R.string.ChannelMessageGroupStickerEmoji, name, chat.title, emoji); - } else { - msg = LocaleController.formatString("ChannelMessageGroupSticker", R.string.ChannelMessageGroupSticker, name, chat.title); - } - } else if (messageObject.isGif()) { - if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { - msg = LocaleController.formatString("NotificationMessageGroupText", R.string.NotificationMessageGroupText, name, chat.title, "\uD83C\uDFAC " + messageObject.messageOwner.media.caption); - } else { - msg = LocaleController.formatString("ChannelMessageGroupGif", R.string.ChannelMessageGroupGif, name, chat.title); - } - } else { - if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { - msg = LocaleController.formatString("NotificationMessageGroupText", R.string.NotificationMessageGroupText, name, chat.title, "\uD83D\uDCCE " + messageObject.messageOwner.media.caption); - } else { - msg = LocaleController.formatString("ChannelMessageGroupDocument", R.string.ChannelMessageGroupDocument, name, chat.title); - } + msg = LocaleController.formatString("ChannelMessageDocument", R.string.ChannelMessageDocument, name); } } } @@ -1773,7 +1798,8 @@ public class NotificationsController { boolean hasNewMessages = false; if (pushMessages.size() == 1) { MessageObject messageObject = pushMessages.get(0); - String message = lastMessage = getStringForMessage(messageObject, false); + boolean text[] = new boolean[1]; + String message = lastMessage = getStringForMessage(messageObject, false, text); silent = messageObject.messageOwner.silent ? 1 : 0; if (message == null) { return; @@ -1782,7 +1808,11 @@ public class NotificationsController { if (chat != null) { message = message.replace(" @ " + name, ""); } else { - message = message.replace(name + ": ", "").replace(name + " ", ""); + if (text[0]) { + message = message.replace(name + ": ", ""); + } else { + message = message.replace(name + " ", ""); + } } } mBuilder.setContentText(message); @@ -1792,9 +1822,10 @@ public class NotificationsController { NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle(); inboxStyle.setBigContentTitle(name); int count = Math.min(10, pushMessages.size()); + boolean text[] = new boolean[1]; for (int i = 0; i < count; i++) { MessageObject messageObject = pushMessages.get(i); - String message = getStringForMessage(messageObject, false); + String message = getStringForMessage(messageObject, false, text); if (message == null || messageObject.messageOwner.date <= dismissDate) { continue; } @@ -1807,7 +1838,11 @@ public class NotificationsController { if (chat != null) { message = message.replace(" @ " + name, ""); } else { - message = message.replace(name + ": ", "").replace(name + " ", ""); + if (text[0]) { + message = message.replace(name + ": ", ""); + } else { + message = message.replace(name + " ", ""); + } } } } @@ -1980,7 +2015,7 @@ public class NotificationsController { NotificationCompat.CarExtender.UnreadConversation.Builder unreadConvBuilder = new NotificationCompat.CarExtender.UnreadConversation.Builder(name).setLatestTimestamp((long) max_date * 1000); - Intent msgHeardIntent = new Intent(); + Intent msgHeardIntent = new Intent(ApplicationLoader.applicationContext, AutoMessageHeardReceiver.class); msgHeardIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); msgHeardIntent.setAction("org.telegram.messenger.ACTION_MESSAGE_HEARD"); msgHeardIntent.putExtra("dialog_id", dialog_id); @@ -1991,13 +2026,13 @@ public class NotificationsController { NotificationCompat.Action wearReplyAction = null; if ((!ChatObject.isChannel(chat) || chat != null && chat.megagroup) && !AndroidUtilities.needShowPasscode(false) && !UserConfig.isWaitingForPasscodeEnter) { - Intent msgReplyIntent = new Intent(); + Intent msgReplyIntent = new Intent(ApplicationLoader.applicationContext, AutoMessageReplyReceiver.class); msgReplyIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); msgReplyIntent.setAction("org.telegram.messenger.ACTION_MESSAGE_REPLY"); msgReplyIntent.putExtra("dialog_id", dialog_id); msgReplyIntent.putExtra("max_id", max_id); PendingIntent msgReplyPendingIntent = PendingIntent.getBroadcast(ApplicationLoader.applicationContext, notificationId, msgReplyIntent, PendingIntent.FLAG_UPDATE_CURRENT); - RemoteInput remoteInputAuto = new RemoteInput.Builder(NotificationsController.EXTRA_VOICE_REPLY).setLabel(LocaleController.getString("Reply", R.string.Reply)).build(); + RemoteInput remoteInputAuto = new RemoteInput.Builder(EXTRA_VOICE_REPLY).setLabel(LocaleController.getString("Reply", R.string.Reply)).build(); unreadConvBuilder.setReplyAction(msgReplyPendingIntent, remoteInputAuto); Intent replyIntent = new Intent(ApplicationLoader.applicationContext, WearReplyReceiver.class); @@ -2020,22 +2055,27 @@ public class NotificationsController { } NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle(null).setConversationTitle(String.format("%1$s (%2$s)", name, LocaleController.formatPluralString("NewMessages", Math.max(count, messageObjects.size())))); - String text = ""; + StringBuilder text = new StringBuilder(); + boolean isText[] = new boolean[1]; for (int a = messageObjects.size() - 1; a >= 0; a--) { MessageObject messageObject = messageObjects.get(a); - String message = getStringForMessage(messageObject, false); + String message = getStringForMessage(messageObject, false, isText); if (message == null) { continue; } if (chat != null) { message = message.replace(" @ " + name, ""); } else { - message = message.replace(name + ": ", "").replace(name + " ", ""); + if (isText[0]) { + message = message.replace(name + ": ", ""); + } else { + message = message.replace(name + " ", ""); + } } if (text.length() > 0) { - text += "\n\n"; + text.append("\n\n"); } - text += message; + text.append(message); unreadConvBuilder.addMessage(message); messagingStyle.addMessage(message, ((long) messageObject.messageOwner.date) * 1000, null); @@ -2072,7 +2112,7 @@ public class NotificationsController { .setContentTitle(name) .setSmallIcon(R.drawable.notification) .setGroup("messages") - .setContentText(text) + .setContentText(text.toString()) .setAutoCancel(true) .setNumber(messageObjects.size()) .setColor(0xff2ca5e0) diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/RefererReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/RefererReceiver.java new file mode 100644 index 000000000..451a766df --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/RefererReceiver.java @@ -0,0 +1,23 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.messenger; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +public class RefererReceiver extends BroadcastReceiver { + public void onReceive(Context context, Intent intent) { + try { + MessagesController.getInstance().setReferer(intent.getExtras().getString("referrer")); + } catch (Exception ignore) { + + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/SecretChatHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/SecretChatHelper.java index e8d6f050d..6e5799ba8 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/SecretChatHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/SecretChatHelper.java @@ -36,14 +36,14 @@ public class SecretChatHelper { public static class TL_decryptedMessageHolder extends TLObject { public static int constructor = 0x555555F9; - public long random_id; public int date; public TLRPC.TL_decryptedMessageLayer layer; public TLRPC.EncryptedFile file; public boolean new_key_used; + public int decryptedWithVersion; public void readParams(AbstractSerializedData stream, boolean exception) { - random_id = stream.readInt64(exception); + stream.readInt64(exception); date = stream.readInt32(exception); layer = TLRPC.TL_decryptedMessageLayer.TLdeserialize(stream, stream.readInt32(exception), exception); if (stream.readBool(exception)) { @@ -54,7 +54,7 @@ public class SecretChatHelper { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeInt64(random_id); + stream.writeInt64(0); stream.writeInt32(date); layer.serializeToStream(stream); stream.writeBool(file != null); @@ -65,7 +65,7 @@ public class SecretChatHelper { } } - public static final int CURRENT_SECRET_CHAT_LAYER = 66; + public static final int CURRENT_SECRET_CHAT_LAYER = 73; private ArrayList sendingNotifyLayer = new ArrayList<>(); private HashMap> secretHolesQueue = new HashMap<>(); @@ -224,6 +224,7 @@ public class SecretChatHelper { newChat.seq_in = exist.seq_in; newChat.seq_out = exist.seq_out; newChat.admin_id = exist.admin_id; + newChat.mtproto_seq = exist.mtproto_seq; } AndroidUtilities.runOnUIThread(new Runnable() { @Override @@ -533,6 +534,12 @@ public class SecretChatHelper { return message.action instanceof TLRPC.TL_messageEncryptedAction && !(message.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionScreenshotMessages || message.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionSetMessageTTL); } + protected void performSendEncryptedRequest(final TLRPC.TL_messages_sendEncryptedMultiMedia req, final SendMessagesHelper.DelayedMessage message) { + for (int a = 0; a < req.files.size(); a++) { + performSendEncryptedRequest(req.messages.get(a), message.messages.get(a), message.encryptedChat, req.files.get(a), message.originalPaths.get(a), message.messageObjects.get(a)); + } + } + protected void performSendEncryptedRequest(final TLRPC.DecryptedMessage req, final TLRPC.Message newMsgObj, final TLRPC.EncryptedChat chat, final TLRPC.InputEncryptedFile encryptedFile, final String originalPath, final MessageObject newMsg) { if (req == null || chat.auth_key == null || chat instanceof TLRPC.TL_encryptedChatRequested || chat instanceof TLRPC.TL_encryptedChatWaiting) { return; @@ -552,6 +559,8 @@ public class SecretChatHelper { Utilities.random.nextBytes(layer.random_bytes); toEncryptObject = layer; + int mtprotoVersion = AndroidUtilities.getPeerLayerVersion(chat.layer) >= 73 ? 2 : 1; + if (chat.seq_in == 0 && chat.seq_out == 0) { if (chat.admin_id == UserConfig.getClientUserId()) { chat.seq_out = 1; @@ -574,7 +583,7 @@ public class SecretChatHelper { requestNewSecretChatKey(chat); } } - MessagesStorage.getInstance().updateEncryptedChatSeq(chat); + MessagesStorage.getInstance().updateEncryptedChatSeq(chat, false); if (newMsgObj != null) { newMsgObj.seq_in = layer.in_seq_no; newMsgObj.seq_out = layer.out_seq_no; @@ -591,16 +600,12 @@ public class SecretChatHelper { toEncrypt.writeInt32(len); toEncryptObject.serializeToStream(toEncrypt); - byte[] messageKeyFull = Utilities.computeSHA1(toEncrypt.buffer); - byte[] messageKey = new byte[16]; - if (messageKeyFull.length != 0) { - System.arraycopy(messageKeyFull, messageKeyFull.length - 16, messageKey, 0, 16); - } - - MessageKeyData keyData = MessageKeyData.generateMessageKeyData(chat.auth_key, messageKey, false); - len = toEncrypt.length(); int extraLen = len % 16 != 0 ? 16 - len % 16 : 0; + if (mtprotoVersion == 2) { + extraLen += (2 + Utilities.random.nextInt(3)) * 16; + } + NativeByteBuffer dataForEncryption = new NativeByteBuffer(len + extraLen); toEncrypt.position(0); dataForEncryption.writeBytes(toEncrypt); @@ -609,8 +614,21 @@ public class SecretChatHelper { Utilities.random.nextBytes(b); dataForEncryption.writeBytes(b); } + + byte[] messageKey = new byte[16]; + byte[] messageKeyFull; + boolean incoming = mtprotoVersion == 2 && chat.admin_id != UserConfig.getClientUserId(); + if (mtprotoVersion == 2) { + messageKeyFull = Utilities.computeSHA256(chat.auth_key, 88 + (incoming ? 8 : 0), 32, dataForEncryption.buffer, 0, dataForEncryption.buffer.limit()); + System.arraycopy(messageKeyFull, 8, messageKey, 0, 16); + } else { + messageKeyFull = Utilities.computeSHA1(toEncrypt.buffer); + System.arraycopy(messageKeyFull, messageKeyFull.length - 16, messageKey, 0, 16); + } toEncrypt.reuse(); + MessageKeyData keyData = MessageKeyData.generateMessageKeyData(chat.auth_key, messageKey, incoming, mtprotoVersion); + Utilities.aesIgeEncryption(dataForEncryption.buffer, keyData.aesKey, keyData.aesIv, true, false, 0, dataForEncryption.limit()); NativeByteBuffer data = new NativeByteBuffer(8 + messageKey.length + dataForEncryption.length()); @@ -769,7 +787,7 @@ public class SecretChatHelper { }); } - public TLRPC.Message processDecryptedObject(final TLRPC.EncryptedChat chat, final TLRPC.EncryptedFile file, int date, long random_id, TLObject object, boolean new_key_used) { + public TLRPC.Message processDecryptedObject(final TLRPC.EncryptedChat chat, final TLRPC.EncryptedFile file, int date, TLObject object, boolean new_key_used) { if (object != null) { int from_id = chat.admin_id; if (from_id == UserConfig.getClientUserId()) { @@ -814,7 +832,7 @@ public class SecretChatHelper { UserConfig.saveConfig(false); newMessage.from_id = from_id; newMessage.to_id = new TLRPC.TL_peerUser(); - newMessage.random_id = random_id; + newMessage.random_id = decryptedMessage.random_id; newMessage.to_id.user_id = UserConfig.getClientUserId(); newMessage.unread = true; newMessage.flags = TLRPC.MESSAGE_FLAG_HAS_MEDIA | TLRPC.MESSAGE_FLAG_HAS_FROM_ID; @@ -822,6 +840,10 @@ public class SecretChatHelper { newMessage.via_bot_name = decryptedMessage.via_bot_name; newMessage.flags |= TLRPC.MESSAGE_FLAG_HAS_BOT_ID; } + if (decryptedMessage.grouped_id != 0) { + newMessage.grouped_id = decryptedMessage.grouped_id; + newMessage.flags |= 131072; + } newMessage.dialog_id = ((long) chat.id) << 32; if (decryptedMessage.reply_to_random_id != 0) { newMessage.reply_to_random_id = decryptedMessage.reply_to_random_id; @@ -863,6 +885,10 @@ public class SecretChatHelper { small.location = new TLRPC.TL_fileLocationUnavailable(); newMessage.media.photo.sizes.add(small); } + if (newMessage.ttl != 0) { + newMessage.media.ttl_seconds = newMessage.ttl; + newMessage.flags |= 4; + } TLRPC.TL_photoSize big = new TLRPC.TL_photoSize(); big.w = decryptedMessage.media.w; @@ -908,6 +934,7 @@ public class SecretChatHelper { newMessage.media.document.thumb = new TLRPC.TL_photoSizeEmpty(); newMessage.media.document.thumb.type = "s"; } + TLRPC.TL_documentAttributeVideo attributeVideo = new TLRPC.TL_documentAttributeVideo(); attributeVideo.w = decryptedMessage.media.w; attributeVideo.h = decryptedMessage.media.h; @@ -915,6 +942,8 @@ public class SecretChatHelper { newMessage.media.document.attributes.add(attributeVideo); if (newMessage.ttl != 0) { newMessage.ttl = Math.max(decryptedMessage.media.duration + 2, newMessage.ttl); + newMessage.media.ttl_seconds = newMessage.ttl; + newMessage.flags |= 4; } } else if (decryptedMessage.media instanceof TLRPC.TL_decryptedMessageMediaDocument) { if (decryptedMessage.media.key == null || decryptedMessage.media.key.length != 32 || decryptedMessage.media.iv == null || decryptedMessage.media.iv.length != 32) { @@ -1010,6 +1039,7 @@ public class SecretChatHelper { newMessage.media.address = decryptedMessage.media.address; newMessage.media.provider = decryptedMessage.media.provider; newMessage.media.venue_id = decryptedMessage.media.venue_id; + newMessage.media.venue_type = ""; } else { return null; } @@ -1386,7 +1416,11 @@ public class SecretChatHelper { a--; update = true; - TLRPC.Message message = processDecryptedObject(chat, holder.file, holder.date, holder.random_id, holder.layer.message, holder.new_key_used); + if (holder.decryptedWithVersion == 2) { + chat.mtproto_seq = Math.min(chat.mtproto_seq, chat.seq_in); + } + + TLRPC.Message message = processDecryptedObject(chat, holder.file, holder.date, holder.layer.message, holder.new_key_used); if (message != null) { messages.add(message); } @@ -1398,10 +1432,53 @@ public class SecretChatHelper { secretHolesQueue.remove(chat.id); } if (update) { - MessagesStorage.getInstance().updateEncryptedChatSeq(chat); + MessagesStorage.getInstance().updateEncryptedChatSeq(chat, true); } } + private boolean decryptWithMtProtoVersion(NativeByteBuffer is, byte[] keyToDecrypt, byte[] messageKey, int version, boolean incoming, boolean encryptOnError) { + if (version == 1) { + incoming = false; + } + MessageKeyData keyData = MessageKeyData.generateMessageKeyData(keyToDecrypt, messageKey, incoming, version); + Utilities.aesIgeEncryption(is.buffer, keyData.aesKey, keyData.aesIv, false, false, 24, is.limit() - 24); + + int len = is.readInt32(false); + byte[] messageKeyFull; + if (version == 2) { + messageKeyFull = Utilities.computeSHA256(keyToDecrypt, 88 + (incoming ? 8 : 0), 32, is.buffer, 24, is.buffer.limit()); + if (!Utilities.arraysEquals(messageKey, 0, messageKeyFull, 8)) { + if (encryptOnError) { + Utilities.aesIgeEncryption(is.buffer, keyData.aesKey, keyData.aesIv, true, false, 24, is.limit() - 24); + is.position(24); + } + return false; + } + } else { + int l = len + 28; + if (l < is.buffer.limit() - 15 || l > is.buffer.limit()) { + l = is.buffer.limit(); + } + messageKeyFull = Utilities.computeSHA1(is.buffer, 24, l); + if (!Utilities.arraysEquals(messageKey, 0, messageKeyFull, messageKeyFull.length - 16)) { + if (encryptOnError) { + Utilities.aesIgeEncryption(is.buffer, keyData.aesKey, keyData.aesIv, true, false, 24, is.limit() - 24); + is.position(24); + } + return false; + } + } + if (len <= 0 || len > is.limit() - 28) { + return false; + } + int padding = is.limit() - 28 - len; + if (version == 2 && (padding < 12 || padding > 1024) || version == 1 && padding > 15) { + return false; + } + // + return true; + } + protected ArrayList decryptMessage(TLRPC.EncryptedMessage message) { final TLRPC.EncryptedChat chat = MessagesController.getInstance().getEncryptedChatDB(message.chat_id, true); if (chat == null || chat instanceof TLRPC.TL_encryptedChatDiscarded) { @@ -1421,20 +1498,30 @@ public class SecretChatHelper { keyToDecrypt = chat.future_auth_key; new_key_used = true; } + int mtprotoVersion = AndroidUtilities.getPeerLayerVersion(chat.layer) >= 73 ? 2 : 1; + int decryptedWithVersion = mtprotoVersion; if (keyToDecrypt != null) { byte[] messageKey = is.readData(16, false); - MessageKeyData keyData = MessageKeyData.generateMessageKeyData(keyToDecrypt, messageKey, false); - Utilities.aesIgeEncryption(is.buffer, keyData.aesKey, keyData.aesIv, false, false, 24, is.limit() - 24); - - int len = is.readInt32(false); - if (len < 0 || len > is.limit() - 28) { - return null; + boolean incoming = chat.admin_id == UserConfig.getClientUserId(); + boolean tryAnotherDecrypt = true; + if (decryptedWithVersion == 2 && chat.mtproto_seq != 0) { + tryAnotherDecrypt = false; } - byte[] messageKeyFull = Utilities.computeSHA1(is.buffer, 24, Math.min(len + 4 + 24, is.buffer.limit())); - if (!Utilities.arraysEquals(messageKey, 0, messageKeyFull, messageKeyFull.length - 16)) { - return null; + + if (!decryptWithMtProtoVersion(is, keyToDecrypt, messageKey, mtprotoVersion, incoming, tryAnotherDecrypt)) { + if (mtprotoVersion == 2) { + decryptedWithVersion = 1; + if (!tryAnotherDecrypt || !decryptWithMtProtoVersion(is, keyToDecrypt, messageKey, 1, incoming, false)) { + return null; + } + } else { + decryptedWithVersion = 2; + if (!decryptWithMtProtoVersion(is, keyToDecrypt, messageKey, 2, incoming, tryAnotherDecrypt)) { + return null; + } + } } TLObject object = TLClassStore.Instance().TLdeserialize(is, is.readInt32(false), false); @@ -1462,7 +1549,10 @@ public class SecretChatHelper { if (layer.out_seq_no <= chat.seq_in) { return null; } - if (chat.seq_in != layer.out_seq_no && chat.seq_in != layer.out_seq_no - 2) { + if (decryptedWithVersion == 1 && chat.mtproto_seq != 0 && layer.out_seq_no >= chat.mtproto_seq) { + return null; + } + if (chat.seq_in != layer.out_seq_no - 2) { FileLog.e("got hole"); ArrayList arr = secretHolesQueue.get(chat.id); if (arr == null) { @@ -1495,22 +1585,25 @@ public class SecretChatHelper { TL_decryptedMessageHolder holder = new TL_decryptedMessageHolder(); holder.layer = layer; holder.file = message.file; - holder.random_id = message.random_id; holder.date = message.date; holder.new_key_used = new_key_used; + holder.decryptedWithVersion = decryptedWithVersion; arr.add(holder); return null; } + if (decryptedWithVersion == 2) { + chat.mtproto_seq = Math.min(chat.mtproto_seq, chat.seq_in); + } applyPeerLayer(chat, layer.layer); chat.seq_in = layer.out_seq_no; chat.in_seq_no = layer.in_seq_no; - MessagesStorage.getInstance().updateEncryptedChatSeq(chat); + MessagesStorage.getInstance().updateEncryptedChatSeq(chat, true); object = layer.message; } else if (!(object instanceof TLRPC.TL_decryptedMessageService && ((TLRPC.TL_decryptedMessageService) object).action instanceof TLRPC.TL_decryptedMessageActionNotifyLayer)) { return null; } ArrayList messages = new ArrayList<>(); - TLRPC.Message decryptedMessage = processDecryptedObject(chat, message.file, message.date, message.random_id, object, new_key_used); + TLRPC.Message decryptedMessage = processDecryptedObject(chat, message.file, message.date, object, new_key_used); if (decryptedMessage != null) { messages.add(decryptedMessage); } @@ -1605,6 +1698,7 @@ public class SecretChatHelper { newChat.seq_in = encryptedChat.seq_in; newChat.seq_out = encryptedChat.seq_out; newChat.admin_id = encryptedChat.admin_id; + newChat.mtproto_seq = encryptedChat.mtproto_seq; MessagesStorage.getInstance().updateEncryptedChat(newChat); AndroidUtilities.runOnUIThread(new Runnable() { @Override diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/SendMessagesHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/SendMessagesHelper.java index 9d226a874..0cdbd8c6c 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/SendMessagesHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/SendMessagesHelper.java @@ -51,6 +51,10 @@ import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.HashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; public class SendMessagesHelper implements NotificationCenter.NotificationCenterDelegate { @@ -61,6 +65,24 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter private HashMap waitingForLocation = new HashMap<>(); private HashMap waitingForCallback = new HashMap<>(); + private static DispatchQueue mediaSendQueue = new DispatchQueue("mediaSendQueue"); + private static ThreadPoolExecutor mediaSendThreadPool; + + static { + int cores; + if (Build.VERSION.SDK_INT >= 17) { + cores = Runtime.getRuntime().availableProcessors(); + } else { + cores = 2; + } + mediaSendThreadPool = new ThreadPoolExecutor(cores, cores, 60, TimeUnit.SECONDS, new LinkedBlockingQueue()); + } + + private static class MediaSendPrepareWorker { + public volatile TLRPC.TL_photo photo; + public CountDownLatch sync; + } + private LocationProvider locationProvider = new LocationProvider(new LocationProvider.LocationProviderDelegate() { @Override public void onLocationAcquired(Location location) { @@ -76,6 +98,17 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } }); + public static class SendingMediaInfo { + public Uri uri; + public String path; + public String caption; + public int ttl; + public ArrayList masks; + public VideoEditedInfo videoEditedInfo; + public MediaController.SearchImage searchImage; + public boolean isVideo; + } + public static class LocationProvider { public interface LocationProviderDelegate { @@ -201,17 +234,99 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } } + protected class DelayedMessageSendAfterRequest { + public TLObject request; + public MessageObject msgObj; + public ArrayList msgObjs; + public String originalPath; + public ArrayList originalPaths; + } + protected class DelayedMessage { + + public long peer; + ArrayList requests; + public TLObject sendRequest; - public TLRPC.TL_decryptedMessage sendEncryptedRequest; + public TLObject sendEncryptedRequest; public int type; public String originalPath; public TLRPC.FileLocation location; - public TLRPC.TL_document documentLocation; public String httpLocation; public MessageObject obj; public TLRPC.EncryptedChat encryptedChat; public VideoEditedInfo videoEditedInfo; + + public ArrayList messageObjects; + public ArrayList messages; + public ArrayList originalPaths; + public HashMap extraHashMap; + public long groupId; + public int finalGroupMessage; + public boolean upload; + + public DelayedMessage(long peer) { + this.peer = peer; + } + + public void addDelayedRequest(final TLObject req, final MessageObject msgObj, final String originalPath) { + DelayedMessageSendAfterRequest request = new DelayedMessageSendAfterRequest(); + request.request = req; + request.msgObj = msgObj; + request.originalPath = originalPath; + if (requests == null) { + requests = new ArrayList<>(); + } + requests.add(request); + } + + public void addDelayedRequest(final TLObject req, final ArrayList msgObjs, final ArrayList originalPaths) { + DelayedMessageSendAfterRequest request = new DelayedMessageSendAfterRequest(); + request.request = req; + request.msgObjs = msgObjs; + request.originalPaths = originalPaths; + if (requests == null) { + requests = new ArrayList<>(); + } + requests.add(request); + } + + public void sendDelayedRequests() { + if (requests == null || type != 4 && type != 0) { + return; + } + int size = requests.size(); + for (int a = 0; a < size; a++) { + DelayedMessageSendAfterRequest request = requests.get(a); + if (request.request instanceof TLRPC.TL_messages_sendEncryptedMultiMedia) { + SecretChatHelper.getInstance().performSendEncryptedRequest((TLRPC.TL_messages_sendEncryptedMultiMedia) request.request, this); + } else if (request.request instanceof TLRPC.TL_messages_sendMultiMedia) { + performSendMessageRequestMulti((TLRPC.TL_messages_sendMultiMedia) request.request, request.msgObjs, request.originalPaths); + } else { + performSendMessageRequest(request.request, request.msgObj, request.originalPath); + } + } + requests = null; + } + + public void markAsError() { + if (type == 4) { + for (int a = 0; a < messageObjects.size(); a++) { + MessageObject obj = messageObjects.get(a); + MessagesStorage.getInstance().markMessageAsSendError(obj.messageOwner); + obj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SEND_ERROR; + NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageSendError, obj.getId()); + processSentMessage(obj.getId()); + } + delayedMessages.remove( "group_" + groupId); + } else { + MessagesStorage.getInstance().markMessageAsSendError(obj.messageOwner); + obj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SEND_ERROR; + NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageSendError, obj.getId()); + processSentMessage(obj.getId()); + } + sendDelayedRequests(); + } } private static volatile SendMessagesHelper Instance = null; @@ -270,12 +385,14 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter media = ((TLRPC.TL_messages_sendMedia) message.sendRequest).media; } else if (message.sendRequest instanceof TLRPC.TL_messages_sendBroadcast) { media = ((TLRPC.TL_messages_sendBroadcast) message.sendRequest).media; + } else if (message.sendRequest instanceof TLRPC.TL_messages_sendMultiMedia) { + media = (TLRPC.InputMedia) message.extraHashMap.get(location); } if (file != null && media != null) { if (message.type == 0) { media.file = file; - performSendMessageRequest(message.sendRequest, message.obj, message.originalPath); + performSendMessageRequest(message.sendRequest, message.obj, message.originalPath, message, true); } else if (message.type == 1) { if (media.file == null) { media.file = file; @@ -305,19 +422,60 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } else if (message.type == 3) { media.file = file; performSendMessageRequest(message.sendRequest, message.obj, message.originalPath); + } else if (message.type == 4) { + if (media instanceof TLRPC.TL_inputMediaUploadedDocument) { + if (media.file == null) { + media.file = file; + MessageObject messageObject = (MessageObject) message.extraHashMap.get(location + "_i"); + int index = message.messageObjects.indexOf(messageObject); + message.location = (TLRPC.FileLocation) message.extraHashMap.get(location + "_t"); + stopVideoService(message.messageObjects.get(index).messageOwner.attachPath); + if (media.thumb == null && message.location != null) { + performSendDelayedMessage(message, index); + } else { + uploadMultiMedia(message, media, null, location); + } + } else { + media.thumb = file; + media.flags |= 4; + uploadMultiMedia(message, media, null, (String) message.extraHashMap.get(location + "_o")); + } + } else { + media.file = file; + uploadMultiMedia(message, media, null, location); + } } arr.remove(a); a--; } else if (encryptedFile != null && message.sendEncryptedRequest != null) { - if (message.sendEncryptedRequest.media instanceof TLRPC.TL_decryptedMessageMediaVideo || - message.sendEncryptedRequest.media instanceof TLRPC.TL_decryptedMessageMediaPhoto || - message.sendEncryptedRequest.media instanceof TLRPC.TL_decryptedMessageMediaDocument) { - long size = (Long) args[5]; - message.sendEncryptedRequest.media.size = (int) size; + TLRPC.TL_decryptedMessage decryptedMessage; + if (message.type == 4) { + TLRPC.TL_messages_sendEncryptedMultiMedia req = (TLRPC.TL_messages_sendEncryptedMultiMedia) message.sendEncryptedRequest; + TLRPC.InputEncryptedFile inputEncryptedFile = (TLRPC.InputEncryptedFile) message.extraHashMap.get(location); + int index = req.files.indexOf(inputEncryptedFile); + req.files.set(index, encryptedFile); + if (inputEncryptedFile.id == 1) { + MessageObject messageObject = (MessageObject) message.extraHashMap.get(location + "_i"); + message.location = (TLRPC.FileLocation) message.extraHashMap.get(location + "_t"); + stopVideoService(message.messageObjects.get(index).messageOwner.attachPath); + } + decryptedMessage = req.messages.get(index); + } else { + decryptedMessage = (TLRPC.TL_decryptedMessage) message.sendEncryptedRequest; + } + if (decryptedMessage.media instanceof TLRPC.TL_decryptedMessageMediaVideo || + decryptedMessage.media instanceof TLRPC.TL_decryptedMessageMediaPhoto || + decryptedMessage.media instanceof TLRPC.TL_decryptedMessageMediaDocument) { + long size = (Long) args[5]; + decryptedMessage.media.size = (int) size; + } + decryptedMessage.media.key = (byte[]) args[3]; + decryptedMessage.media.iv = (byte[]) args[4]; + if (message.type == 4) { + uploadMultiMedia(message, null, encryptedFile, location); + } else { + SecretChatHelper.getInstance().performSendEncryptedRequest(decryptedMessage, message.obj.messageOwner, message.encryptedChat, encryptedFile, message.originalPath, message.obj); } - message.sendEncryptedRequest.media.key = (byte[]) args[3]; - message.sendEncryptedRequest.media.iv = (byte[]) args[4]; - SecretChatHelper.getInstance().performSendEncryptedRequest(message.sendEncryptedRequest, message.obj.messageOwner, message.encryptedChat, encryptedFile, message.originalPath, message.obj); arr.remove(a); a--; } @@ -334,12 +492,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter for (int a = 0; a < arr.size(); a++) { DelayedMessage obj = arr.get(a); if (enc && obj.sendEncryptedRequest != null || !enc && obj.sendRequest != null) { - MessagesStorage.getInstance().markMessageAsSendError(obj.obj.messageOwner); - obj.obj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SEND_ERROR; + obj.markAsError(); arr.remove(a); a--; - NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageSendError, obj.obj.getId()); - processSentMessage(obj.obj.getId()); } } if (arr.isEmpty()) { @@ -357,7 +512,13 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (arr != null) { for (int a = 0; a < arr.size(); a++) { DelayedMessage message = arr.get(a); - if (message.obj == messageObject) { + if (message.type == 4) { + int index = message.messageObjects.indexOf(messageObject); + message.location = (TLRPC.FileLocation) message.extraHashMap.get(messageObject.messageOwner.attachPath + "_t"); + performSendDelayedMessage(message, index); + arr.remove(a); + break; + } else if (message.obj == messageObject) { message.videoEditedInfo = null; performSendDelayedMessage(message); arr.remove(a); @@ -382,7 +543,21 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (arr != null) { for (int a = 0; a < arr.size(); a++) { DelayedMessage message = arr.get(a); - if (message.obj == messageObject) { + if (message.type == 4) { + for (int b = 0; b < message.messageObjects.size(); a++) { + MessageObject obj = message.messageObjects.get(a); + if (obj == messageObject) { + obj.videoEditedInfo = null; + obj.messageOwner.message = "-1"; + obj.messageOwner.media.document.size = (int) finalSize; + + ArrayList messages = new ArrayList<>(); + messages.add(obj.messageOwner); + MessagesStorage.getInstance().putMessages(messages, false, true, false, 0); + break; + } + } + } else if (message.obj == messageObject) { message.obj.videoEditedInfo = null; message.obj.messageOwner.message = "-1"; message.obj.messageOwner.media.document.size = (int) finalSize; @@ -393,9 +568,6 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter break; } } - if (arr.isEmpty()) { - delayedMessages.remove(messageObject.messageOwner.attachPath); - } } } } else if (id == NotificationCenter.FilePreparingFailed) { @@ -410,13 +582,19 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (arr != null) { for (int a = 0; a < arr.size(); a++) { DelayedMessage message = arr.get(a); - if (message.obj == messageObject) { - MessagesStorage.getInstance().markMessageAsSendError(message.obj.messageOwner); - message.obj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SEND_ERROR; + if (message.type == 4) { + for (int b = 0; b < message.messages.size(); b++) { + if (message.messageObjects.get(b) == messageObject) { + message.markAsError(); + arr.remove(a); + a--; + break; + } + } + } else if (message.obj == messageObject) { + message.markAsError(); arr.remove(a); a--; - NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageSendError, message.obj.getId()); - processSentMessage(message.obj.getId()); } } if (arr.isEmpty()) { @@ -424,13 +602,31 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } } } else if (id == NotificationCenter.httpFileDidLoaded) { - String path = (String) args[0]; + final String path = (String) args[0]; ArrayList arr = delayedMessages.get(path); if (arr != null) { for (int a = 0; a < arr.size(); a++) { final DelayedMessage message = arr.get(a); + final MessageObject messageObject; + int fileType = -1; if (message.type == 0) { - String md5 = Utilities.MD5(message.httpLocation) + "." + ImageLoader.getHttpUrlExtension(message.httpLocation, "file"); + fileType = 0; + messageObject = message.obj; + } else if (message.type == 2) { + fileType = 1; + messageObject = message.obj; + } else if (message.type == 4) { + messageObject = (MessageObject) message.extraHashMap.get(path); + if (messageObject.getDocument() != null) { + fileType = 1; + } else { + fileType = 0; + } + } else { + messageObject = null; + } + if (fileType == 0) { + String md5 = Utilities.MD5(path) + "." + ImageLoader.getHttpUrlExtension(path, "file"); final File cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), md5); Utilities.globalQueue.postRunnable(new Runnable() { @Override @@ -440,46 +636,48 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter @Override public void run() { if (photo != null) { - message.httpLocation = null; - message.obj.messageOwner.media.photo = photo; - message.obj.messageOwner.attachPath = cacheFile.toString(); - message.location = photo.sizes.get(photo.sizes.size() - 1).location; + messageObject.messageOwner.media.photo = photo; + messageObject.messageOwner.attachPath = cacheFile.toString(); ArrayList messages = new ArrayList<>(); - messages.add(message.obj.messageOwner); + messages.add(messageObject.messageOwner); MessagesStorage.getInstance().putMessages(messages, false, true, false, 0); - performSendDelayedMessage(message); - NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateMessageMedia, message.obj.messageOwner); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateMessageMedia, messageObject.messageOwner); + message.location = photo.sizes.get(photo.sizes.size() - 1).location; + message.httpLocation = null; + if (message.type == 4) { + performSendDelayedMessage(message, message.messageObjects.indexOf(messageObject)); + } else { + performSendDelayedMessage(message); + } } else { - FileLog.e("can't load image " + message.httpLocation + " to file " + cacheFile.toString()); - MessagesStorage.getInstance().markMessageAsSendError(message.obj.messageOwner); - message.obj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SEND_ERROR; - NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageSendError, message.obj.getId()); - processSentMessage(message.obj.getId()); + FileLog.e("can't load image " + path + " to file " + cacheFile.toString()); + message.markAsError(); } } }); } }); - } else if (message.type == 2) { - String md5 = Utilities.MD5(message.httpLocation) + ".gif"; + } else if (fileType == 1) { + String md5 = Utilities.MD5(path) + ".gif"; final File cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), md5); Utilities.globalQueue.postRunnable(new Runnable() { @Override public void run() { - if (message.documentLocation.thumb.location instanceof TLRPC.TL_fileLocationUnavailable) { + final TLRPC.Document document = message.obj.getDocument(); + if (document.thumb.location instanceof TLRPC.TL_fileLocationUnavailable) { try { Bitmap bitmap = ImageLoader.loadBitmap(cacheFile.getAbsolutePath(), null, 90, 90, true); if (bitmap != null) { - message.documentLocation.thumb = ImageLoader.scaleAndSaveImage(bitmap, 90, 90, 55, message.sendEncryptedRequest != null); + document.thumb = ImageLoader.scaleAndSaveImage(bitmap, 90, 90, 55, message.sendEncryptedRequest != null); bitmap.recycle(); } } catch (Exception e) { - message.documentLocation.thumb = null; + document.thumb = null; FileLog.e(e); } - if (message.documentLocation.thumb == null) { - message.documentLocation.thumb = new TLRPC.TL_photoSizeEmpty(); - message.documentLocation.thumb.type = "s"; + if (document.thumb == null) { + document.thumb = new TLRPC.TL_photoSizeEmpty(); + document.thumb.type = "s"; } } AndroidUtilities.runOnUIThread(new Runnable() { @@ -487,9 +685,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter public void run() { message.httpLocation = null; message.obj.messageOwner.attachPath = cacheFile.toString(); - message.location = message.documentLocation.thumb.location; + message.location = document.thumb.location; ArrayList messages = new ArrayList<>(); - messages.add(message.obj.messageOwner); + messages.add(messageObject.messageOwner); MessagesStorage.getInstance().putMessages(messages, false, true, false, 0); performSendDelayedMessage(message); NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateMessageMedia, message.obj.messageOwner); @@ -515,11 +713,8 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter ArrayList arr = delayedMessages.get(path); if (arr != null) { - for (DelayedMessage message : arr) { - MessagesStorage.getInstance().markMessageAsSendError(message.obj.messageOwner); - message.obj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SEND_ERROR; - NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageSendError, message.obj.getId()); - processSentMessage(message.obj.getId()); + for (int a = 0; a < arr.size(); a++) { + arr.get(a).markAsError(); } delayedMessages.remove(path); } @@ -527,17 +722,63 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } public void cancelSendingMessage(MessageObject object) { - String keyToRemvoe = null; + ArrayList keysToRemove = new ArrayList<>(); boolean enc = false; for (HashMap.Entry> entry : delayedMessages.entrySet()) { ArrayList messages = entry.getValue(); for (int a = 0; a < messages.size(); a++) { DelayedMessage message = messages.get(a); - if (message.obj.getId() == object.getId()) { + if (message.type == 4) { + int index = -1; + MessageObject messageObject = null; + for (int b = 0; b < message.messageObjects.size(); b++) { + messageObject = message.messageObjects.get(b); + if (messageObject.getId() == object.getId()) { + index = b; + break; + } + } + if (index >= 0) { + message.messageObjects.remove(index); + message.messages.remove(index); + message.originalPaths.remove(index); + if (message.sendRequest != null) { + TLRPC.TL_messages_sendMultiMedia request = (TLRPC.TL_messages_sendMultiMedia) message.sendRequest; + request.multi_media.remove(index); + } else { + TLRPC.TL_messages_sendEncryptedMultiMedia request = (TLRPC.TL_messages_sendEncryptedMultiMedia) message.sendEncryptedRequest; + request.messages.remove(index); + request.files.remove(index); + } + MediaController.getInstance().cancelVideoConvert(object); + + String keyToRemove = (String) message.extraHashMap.get(messageObject); + if (keyToRemove != null) { + keysToRemove.add(keyToRemove); + } + if (message.messageObjects.isEmpty()) { + message.sendDelayedRequests(); + } else { + if (message.finalGroupMessage == object.getId()) { + MessageObject prevMessage = message.messageObjects.get(message.messageObjects.size() - 1); + message.finalGroupMessage = prevMessage.getId(); + prevMessage.messageOwner.params.put("final", "1"); + + TLRPC.TL_messages_messages messagesRes = new TLRPC.TL_messages_messages(); + messagesRes.messages.add(prevMessage.messageOwner); + MessagesStorage.getInstance().putMessages(messagesRes, message.peer, -2, 0, false); + + } + sendReadyToSendGroup(message, false, true); + } + } + break; + } else if (message.obj.getId() == object.getId()) { messages.remove(a); + message.sendDelayedRequests(); MediaController.getInstance().cancelVideoConvert(message.obj); if (messages.size() == 0) { - keyToRemvoe = entry.getKey(); + keysToRemove.add(entry.getKey()); if (message.sendEncryptedRequest != null) { enc = true; } @@ -546,13 +787,15 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } } } - if (keyToRemvoe != null) { - if (keyToRemvoe.startsWith("http")) { - ImageLoader.getInstance().cancelLoadHttpFile(keyToRemvoe); + for (int a = 0; a < keysToRemove.size(); a++) { + String key = keysToRemove.get(a); + if (key.startsWith("http")) { + ImageLoader.getInstance().cancelLoadHttpFile(key); } else { - FileLoader.getInstance().cancelUploadFile(keyToRemvoe, enc); + FileLoader.getInstance().cancelUploadFile(key, enc); } - stopVideoService(keyToRemvoe); + stopVideoService(key); + delayedMessages.remove(key); } ArrayList messages = new ArrayList<>(); messages.add(object.getId()); @@ -803,6 +1046,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } } + HashMap groupsMap = new HashMap<>(); ArrayList objArr = new ArrayList<>(); ArrayList arr = new ArrayList<>(); ArrayList randomIds = new ArrayList<>(); @@ -812,6 +1056,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter long lastDialogId = 0; int myId = UserConfig.getClientUserId(); final boolean toMyself = peer == myId; + long lastGroupedId; for (int a = 0; a < messages.size(); a++) { MessageObject msgObj = messages.get(a); if (msgObj.getId() <= 0 || msgObj.isSecretPhoto()) { @@ -829,11 +1074,18 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter continue; } + boolean groupedIdChanged = false; final TLRPC.Message newMsg = new TLRPC.TL_message(); if (msgObj.isForwarded()) { - newMsg.fwd_from = msgObj.messageOwner.fwd_from; + newMsg.fwd_from = new TLRPC.TL_messageFwdHeader(); + newMsg.fwd_from.flags = msgObj.messageOwner.fwd_from.flags; + newMsg.fwd_from.from_id = msgObj.messageOwner.fwd_from.from_id; + newMsg.fwd_from.date = msgObj.messageOwner.fwd_from.date; + newMsg.fwd_from.channel_id = msgObj.messageOwner.fwd_from.channel_id; + newMsg.fwd_from.channel_post = msgObj.messageOwner.fwd_from.channel_post; + newMsg.fwd_from.post_author = msgObj.messageOwner.fwd_from.post_author; newMsg.flags = TLRPC.MESSAGE_FLAG_FWD; - } else if (msgObj.getDialogId() != myId) { + } else { //if (!toMyself || !msgObj.isOutOwner()) newMsg.fwd_from = new TLRPC.TL_messageFwdHeader(); newMsg.fwd_from.channel_post = msgObj.getId(); newMsg.fwd_from.flags |= 4; @@ -861,6 +1113,11 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter newMsg.date = msgObj.messageOwner.date; newMsg.flags = TLRPC.MESSAGE_FLAG_FWD; } + if (peer == myId && newMsg.fwd_from != null) { + newMsg.fwd_from.flags |= 16; + newMsg.fwd_from.saved_from_msg_id = msgObj.getId(); + newMsg.fwd_from.saved_from_peer = msgObj.messageOwner.to_id; + } if (!canSendPreview && msgObj.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage) { newMsg.media = new TLRPC.TL_messageMediaEmpty(); } else { @@ -888,6 +1145,21 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } newMsg.local_id = newMsg.id = UserConfig.getNewMessageId(); newMsg.out = true; + if ((lastGroupedId = msgObj.messageOwner.grouped_id) != 0) { + Long gId = groupsMap.get(msgObj.messageOwner.grouped_id); + if (gId == null) { + gId = Utilities.random.nextLong(); + groupsMap.put(msgObj.messageOwner.grouped_id, gId); + } + newMsg.grouped_id = gId; + newMsg.flags |= 131072; + } + if (a != messages.size() - 1) { + MessageObject next = messages.get(a + 1); + if (next.messageOwner.grouped_id != msgObj.messageOwner.grouped_id) { + groupedIdChanged = true; + } + } if (to_id.channel_id != 0 && !isMegagroup) { newMsg.from_id = isSignature ? UserConfig.getClientUserId() : -to_id.channel_id; newMsg.post = true; @@ -918,7 +1190,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } newMsg.dialog_id = peer; newMsg.to_id = to_id; - if ((MessageObject.isVoiceMessage(newMsg) || MessageObject.isRoundVideoMessage(newMsg)) && newMsg.to_id.channel_id == 0) { + if (MessageObject.isVoiceMessage(newMsg) || MessageObject.isRoundVideoMessage(newMsg)) { newMsg.media_unread = true; } if (msgObj.messageOwner.to_id instanceof TLRPC.TL_peerChannel) { @@ -936,7 +1208,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter FileLog.e("forward message user_id = " + inputPeer.user_id + " chat_id = " + inputPeer.chat_id + " channel_id = " + inputPeer.channel_id + " access_hash = " + inputPeer.access_hash); } - if (arr.size() == 100 || a == messages.size() - 1 || a != messages.size() - 1 && messages.get(a + 1).getDialogId() != msgObj.getDialogId()) { + if (groupedIdChanged && arr.size() > 0 || arr.size() == 100 || a == messages.size() - 1 || a != messages.size() - 1 && messages.get(a + 1).getDialogId() != msgObj.getDialogId()) { MessagesStorage.getInstance().putMessages(new ArrayList<>(arr), false, true, false, 0); MessagesController.getInstance().updateInterfaceWithMessages(peer, objArr); NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload); @@ -944,6 +1216,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter final TLRPC.TL_messages_forwardMessages req = new TLRPC.TL_messages_forwardMessages(); req.to_peer = inputPeer; + req.grouped = lastGroupedId != 0; if (req.to_peer instanceof TLRPC.TL_inputPeerChannel) { req.silent = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE).getBoolean("silent_" + peer, false); } @@ -990,6 +1263,8 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter for (int a = 0; a < updates.updates.size(); a++) { TLRPC.Update update = updates.updates.get(a); if (update instanceof TLRPC.TL_updateNewMessage || update instanceof TLRPC.TL_updateNewChannelMessage) { + updates.updates.remove(a); + a--; final TLRPC.Message message; if (update instanceof TLRPC.TL_updateNewMessage) { message = ((TLRPC.TL_updateNewMessage) update).message; @@ -1005,6 +1280,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (toMyself) { message.out = true; message.unread = false; + message.media_unread = false; } Long random_id = newMessagesByIds.get(message.id); @@ -1041,14 +1317,14 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter removeFromSendingMessages(oldId); } }); - if (MessageObject.isVideoMessage(newMsgObj) || MessageObject.isRoundVideoMessage(newMsgObj) || MessageObject.isNewGifMessage(newMsgObj)) { - stopVideoService(newMsgObj.attachPath); - } } }); } } } + if (!updates.updates.isEmpty()) { + MessagesController.getInstance().processUpdates(updates, false); + } StatsController.getInstance().incrementSentItemsCount(ConnectionsManager.getCurrentNetworkType(), StatsController.TYPE_MESSAGES, sentCount); } else { AndroidUtilities.runOnUIThread(new Runnable() { @@ -1067,9 +1343,6 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter newMsgObj.send_state = MessageObject.MESSAGE_SEND_STATE_SEND_ERROR; NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageSendError, newMsgObj.id); processSentMessage(newMsgObj.id); - if (MessageObject.isVideoMessage(newMsgObj) || MessageObject.isRoundVideoMessage(newMsgObj) || MessageObject.isNewGifMessage(newMsgObj)) { - stopVideoService(newMsgObj.attachPath); - } removeFromSendingMessages(newMsgObj.id); } }); @@ -1384,6 +1657,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter TLRPC.Message newMsg = null; MessageObject newMsgObj = null; + DelayedMessage delayedMessage = null; int type = -1; int lower_id = (int) peer; int high_id = (int) (peer >> 32); @@ -1744,7 +2018,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } } } - if (high_id != 1 && (MessageObject.isVoiceMessage(newMsg) || MessageObject.isRoundVideoMessage(newMsg)) && newMsg.to_id.channel_id == 0) { + if (high_id != 1 && (MessageObject.isVoiceMessage(newMsg) || MessageObject.isRoundVideoMessage(newMsg))) { newMsg.media_unread = true; } @@ -1755,13 +2029,46 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter newMsgObj.attachPathExists = true; } - ArrayList objArr = new ArrayList<>(); - objArr.add(newMsgObj); - ArrayList arr = new ArrayList<>(); - arr.add(newMsg); - MessagesStorage.getInstance().putMessages(arr, false, true, false, 0); - MessagesController.getInstance().updateInterfaceWithMessages(peer, objArr); - NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload); + long groupId = 0; + boolean isFinalGroupMedia = false; + if (params != null) { + String groupIdStr = params.get("groupId"); + if (groupIdStr != null) { + groupId = Utilities.parseLong(groupIdStr); + newMsg.grouped_id = groupId; + newMsg.flags |= 131072; + } + isFinalGroupMedia = params.get("final") != null; + } + + if (groupId == 0) { + ArrayList objArr = new ArrayList<>(); + objArr.add(newMsgObj); + ArrayList arr = new ArrayList<>(); + arr.add(newMsg); + MessagesStorage.getInstance().putMessages(arr, false, true, false, 0); + MessagesController.getInstance().updateInterfaceWithMessages(peer, objArr); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload); + } else { + String key = "group_" + groupId; + ArrayList arrayList = delayedMessages.get(key); + if (arrayList != null) { + delayedMessage = arrayList.get(0); + } + if (delayedMessage == null) { + delayedMessage = new DelayedMessage(peer); + delayedMessage.type = 4; + delayedMessage.groupId = groupId; + delayedMessage.messageObjects = new ArrayList<>(); + delayedMessage.messages = new ArrayList<>(); + delayedMessage.originalPaths = new ArrayList<>(); + delayedMessage.extraHashMap = new HashMap<>(); + delayedMessage.encryptedChat = encryptedChat; + } + if (isFinalGroupMedia) { + delayedMessage.finalGroupMessage = newMsg.id; + } + } if (BuildVars.DEBUG_VERSION) { if (sendToPeer != null) { @@ -1809,7 +2116,11 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } } else { TLRPC.TL_decryptedMessage reqSend; - reqSend = new TLRPC.TL_decryptedMessage(); + if (AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 73) { + reqSend = new TLRPC.TL_decryptedMessage(); + } else { + reqSend = new TLRPC.TL_decryptedMessage_layer45(); + } reqSend.ttl = newMsg.ttl; if (entities != null && !entities.isEmpty()) { reqSend.entities = entities; @@ -1840,7 +2151,6 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } else if (type >= 1 && type <= 3 || type >= 5 && type <= 8 || type == 9 && encryptedChat != null) { if (encryptedChat == null) { TLRPC.InputMedia inputMedia = null; - DelayedMessage delayedMessage = null; if (type == 1) { if (location instanceof TLRPC.TL_messageMediaVenue) { inputMedia = new TLRPC.TL_inputMediaVenue(); @@ -1848,6 +2158,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter inputMedia.title = location.title; inputMedia.provider = location.provider; inputMedia.venue_id = location.venue_id; + inputMedia.venue_type = ""; + } else if (location instanceof TLRPC.TL_messageMediaGeoLive) { + inputMedia = new TLRPC.TL_inputMediaGeoLive(); + inputMedia.period = location.period; } else { inputMedia = new TLRPC.TL_inputMediaGeoPoint(); } @@ -1873,10 +2187,12 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter inputMedia.flags |= 1; } } - delayedMessage = new DelayedMessage(); - delayedMessage.originalPath = originalPath; - delayedMessage.type = 0; - delayedMessage.obj = newMsgObj; + if (delayedMessage == null) { + delayedMessage = new DelayedMessage(peer); + delayedMessage.type = 0; + delayedMessage.obj = newMsgObj; + delayedMessage.originalPath = originalPath; + } if (path != null && path.length() > 0 && path.startsWith("http")) { delayedMessage.httpLocation = path; } else { @@ -1896,16 +2212,20 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter inputMedia.caption = document.caption != null ? document.caption : ""; inputMedia.mime_type = document.mime_type; inputMedia.attributes = document.attributes; + if (!MessageObject.isRoundVideoDocument(document) && (videoEditedInfo == null || !videoEditedInfo.muted && !videoEditedInfo.roundVideo)) { + inputMedia.nosound_video = true; + } if (ttl != 0) { newMsg.ttl = inputMedia.ttl_seconds = ttl; inputMedia.flags |= 2; } - delayedMessage = new DelayedMessage(); - delayedMessage.originalPath = originalPath; - delayedMessage.type = 1; - delayedMessage.obj = newMsgObj; + if (delayedMessage == null) { + delayedMessage = new DelayedMessage(peer); + delayedMessage.type = 1; + delayedMessage.obj = newMsgObj; + delayedMessage.originalPath = originalPath; + } delayedMessage.location = document.thumb.location; - delayedMessage.documentLocation = document; delayedMessage.videoEditedInfo = videoEditedInfo; } else { TLRPC.TL_inputMediaDocument media = new TLRPC.TL_inputMediaDocument(); @@ -1935,11 +2255,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter newMsg.ttl = inputMedia.ttl_seconds = ttl; inputMedia.flags |= 2; } - delayedMessage = new DelayedMessage(); + delayedMessage = new DelayedMessage(peer); delayedMessage.originalPath = originalPath; delayedMessage.type = 2; delayedMessage.obj = newMsgObj; - delayedMessage.documentLocation = document; delayedMessage.location = document.thumb.location; } inputMedia.mime_type = document.mime_type; @@ -1963,10 +2282,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter newMsg.ttl = inputMedia.ttl_seconds = ttl; inputMedia.flags |= 2; } - delayedMessage = new DelayedMessage(); + delayedMessage = new DelayedMessage(peer); delayedMessage.type = 3; delayedMessage.obj = newMsgObj; - delayedMessage.documentLocation = document; } else { TLRPC.TL_inputMediaDocument media = new TLRPC.TL_inputMediaDocument(); media.id = new TLRPC.TL_inputDocument(); @@ -1996,30 +2314,57 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (retryMessageObject == null) { DraftQuery.cleanDraft(peer, false); } + } else if (groupId != 0) { + TLRPC.TL_messages_sendMultiMedia request; + if (delayedMessage.sendRequest != null) { + request = (TLRPC.TL_messages_sendMultiMedia) delayedMessage.sendRequest; + } else { + request = new TLRPC.TL_messages_sendMultiMedia(); + request.peer = sendToPeer; + if (newMsg.to_id instanceof TLRPC.TL_peerChannel) { + request.silent = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE).getBoolean("silent_" + peer, false); + } + if (reply_to_msg != null) { + request.flags |= 1; + request.reply_to_msg_id = reply_to_msg.getId(); + } + delayedMessage.sendRequest = request; + } + delayedMessage.messageObjects.add(newMsgObj); + delayedMessage.messages.add(newMsg); + delayedMessage.originalPaths.add(originalPath); + TLRPC.TL_inputSingleMedia inputSingleMedia = new TLRPC.TL_inputSingleMedia(); + inputSingleMedia.random_id = newMsg.random_id; + inputSingleMedia.media = inputMedia; + request.multi_media.add(inputSingleMedia); + reqSend = request; } else { TLRPC.TL_messages_sendMedia request = new TLRPC.TL_messages_sendMedia(); request.peer = sendToPeer; if (newMsg.to_id instanceof TLRPC.TL_peerChannel) { request.silent = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE).getBoolean("silent_" + peer, false); } - request.random_id = newMsg.random_id; - request.media = inputMedia; if (reply_to_msg != null) { request.flags |= 1; request.reply_to_msg_id = reply_to_msg.getId(); } + request.random_id = newMsg.random_id; + request.media = inputMedia; + if (delayedMessage != null) { delayedMessage.sendRequest = request; } reqSend = request; } - if (type == 1) { + if (groupId != 0) { + performSendDelayedMessage(delayedMessage); + } else if (type == 1) { performSendMessageRequest(reqSend, newMsgObj, null); } else if (type == 2) { if (photo.access_hash == 0) { performSendDelayedMessage(delayedMessage); } else { - performSendMessageRequest(reqSend, newMsgObj, null); + performSendMessageRequest(reqSend, newMsgObj, null, null, true); } } else if (type == 3) { if (document.access_hash == 0) { @@ -2044,7 +2389,15 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } } else { TLRPC.TL_decryptedMessage reqSend; - reqSend = new TLRPC.TL_decryptedMessage(); + if (AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 73) { + reqSend = new TLRPC.TL_decryptedMessage(); + if (groupId != 0) { + reqSend.grouped_id = groupId; + reqSend.flags |= 131072; + } + } else { + reqSend = new TLRPC.TL_decryptedMessage_layer45(); + } reqSend.ttl = newMsg.ttl; if (entities != null && !entities.isEmpty()) { reqSend.entities = entities; @@ -2090,19 +2443,23 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter reqSend.media.w = big.w; reqSend.media.h = big.h; reqSend.media.size = big.size; - if (big.location.key == null) { - DelayedMessage delayedMessage = new DelayedMessage(); - delayedMessage.originalPath = originalPath; - delayedMessage.sendEncryptedRequest = reqSend; - delayedMessage.type = 0; - delayedMessage.obj = newMsgObj; - delayedMessage.encryptedChat = encryptedChat; - if (path != null && path.length() > 0 && path.startsWith("http")) { + if (big.location.key == null || groupId != 0) { + if (delayedMessage == null) { + delayedMessage = new DelayedMessage(peer); + delayedMessage.encryptedChat = encryptedChat; + delayedMessage.type = 0; + delayedMessage.originalPath = originalPath; + delayedMessage.sendEncryptedRequest = reqSend; + delayedMessage.obj = newMsgObj; + } + if (!TextUtils.isEmpty(path) && path.startsWith("http")) { delayedMessage.httpLocation = path; } else { delayedMessage.location = photo.sizes.get(photo.sizes.size() - 1).location; } - performSendDelayedMessage(delayedMessage); + if (groupId == 0) { + performSendDelayedMessage(delayedMessage); + } } else { TLRPC.TL_inputEncryptedFile encryptedFile = new TLRPC.TL_inputEncryptedFile(); encryptedFile.id = big.location.volume_id; @@ -2143,16 +2500,19 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } reqSend.media.thumb_h = document.thumb.h; reqSend.media.thumb_w = document.thumb.w; - if (document.key == null) { - DelayedMessage delayedMessage = new DelayedMessage(); - delayedMessage.originalPath = originalPath; - delayedMessage.sendEncryptedRequest = reqSend; - delayedMessage.type = 1; - delayedMessage.obj = newMsgObj; - delayedMessage.encryptedChat = encryptedChat; - delayedMessage.documentLocation = document; + if (document.key == null || groupId != 0) { + if (delayedMessage == null) { + delayedMessage = new DelayedMessage(peer); + delayedMessage.encryptedChat = encryptedChat; + delayedMessage.type = 1; + delayedMessage.sendEncryptedRequest = reqSend; + delayedMessage.originalPath = originalPath; + delayedMessage.obj = newMsgObj; + } delayedMessage.videoEditedInfo = videoEditedInfo; - performSendDelayedMessage(delayedMessage); + if (groupId == 0) { + performSendDelayedMessage(delayedMessage); + } } else { TLRPC.TL_inputEncryptedFile encryptedFile = new TLRPC.TL_inputEncryptedFile(); encryptedFile.id = document.id; @@ -2203,7 +2563,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter reqSend.media.mime_type = document.mime_type; if (document.key == null) { - DelayedMessage delayedMessage = new DelayedMessage(); + delayedMessage = new DelayedMessage(peer); delayedMessage.originalPath = originalPath; delayedMessage.sendEncryptedRequest = reqSend; delayedMessage.type = 2; @@ -2212,7 +2572,6 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (path != null && path.length() > 0 && path.startsWith("http")) { delayedMessage.httpLocation = path; } - delayedMessage.documentLocation = document; performSendDelayedMessage(delayedMessage); } else { TLRPC.TL_inputEncryptedFile encryptedFile = new TLRPC.TL_inputEncryptedFile(); @@ -2224,11 +2583,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } } } else if (type == 8) { - DelayedMessage delayedMessage = new DelayedMessage(); + delayedMessage = new DelayedMessage(peer); delayedMessage.encryptedChat = encryptedChat; delayedMessage.sendEncryptedRequest = reqSend; delayedMessage.obj = newMsgObj; - delayedMessage.documentLocation = document; delayedMessage.type = 3; reqSend.media = new TLRPC.TL_decryptedMessageMediaDocument(); @@ -2248,6 +2606,24 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter delayedMessage.originalPath = originalPath; performSendDelayedMessage(delayedMessage); } + if (groupId != 0) { + TLRPC.TL_messages_sendEncryptedMultiMedia request; + if (delayedMessage.sendEncryptedRequest != null) { + request = (TLRPC.TL_messages_sendEncryptedMultiMedia) delayedMessage.sendEncryptedRequest; + } else { + request = new TLRPC.TL_messages_sendEncryptedMultiMedia(); + delayedMessage.sendEncryptedRequest = request; + } + delayedMessage.messageObjects.add(newMsgObj); + delayedMessage.messages.add(newMsg); + delayedMessage.originalPaths.add(originalPath); + delayedMessage.upload = true; + request.messages.add(reqSend); + TLRPC.TL_inputEncryptedFile encryptedFile = new TLRPC.TL_inputEncryptedFile(); + encryptedFile.id = type == 3 ? 1 : 0; + request.files.add(encryptedFile); + performSendDelayedMessage(delayedMessage); + } if (retryMessageObject == null) { DraftQuery.cleanDraft(peer, false); } @@ -2273,10 +2649,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (retryMessageObject.getId() >= 0) { reqSend.id.add(retryMessageObject.getId()); } else { - if (retryMessageObject.messageOwner.fwd_from != null) { - reqSend.id.add(retryMessageObject.messageOwner.fwd_from.channel_post); - } else { + if (retryMessageObject.messageOwner.fwd_msg_id != 0) { reqSend.id.add(retryMessageObject.messageOwner.fwd_msg_id); + } else if (retryMessageObject.messageOwner.fwd_from != null) { + reqSend.id.add(retryMessageObject.messageOwner.fwd_from.channel_post); } } performSendMessageRequest(reqSend, newMsgObj, null); @@ -2311,6 +2687,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } private void performSendDelayedMessage(final DelayedMessage message) { + performSendDelayedMessage(message, -1); + } + + private void performSendDelayedMessage(final DelayedMessage message, int index) { if (message.type == 0) { if (message.httpLocation != null) { putToDelayedMessages(message.httpLocation, message); @@ -2341,8 +2721,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } else if (message.type == 1) { if (message.videoEditedInfo != null && message.videoEditedInfo.needConvert()) { String location = message.obj.messageOwner.attachPath; + TLRPC.Document document = message.obj.getDocument(); if (location == null) { - location = FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE) + "/" + message.documentLocation.id + ".mp4"; + location = FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE) + "/" + document.id + ".mp4"; } putToDelayedMessages(location, message); MediaController.getInstance().scheduleVideoConvert(message.obj); @@ -2358,10 +2739,11 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter media.file = message.videoEditedInfo.file; message.videoEditedInfo.file = null; } else if (message.videoEditedInfo.encryptedFile != null) { - message.sendEncryptedRequest.media.size = (int) message.videoEditedInfo.estimatedSize; - message.sendEncryptedRequest.media.key = message.videoEditedInfo.key; - message.sendEncryptedRequest.media.iv = message.videoEditedInfo.iv; - SecretChatHelper.getInstance().performSendEncryptedRequest(message.sendEncryptedRequest, message.obj.messageOwner, message.encryptedChat, message.videoEditedInfo.encryptedFile, message.originalPath, message.obj); + TLRPC.TL_decryptedMessage decryptedMessage = (TLRPC.TL_decryptedMessage) message.sendEncryptedRequest; + decryptedMessage.media.size = (int) message.videoEditedInfo.estimatedSize; + decryptedMessage.media.key = message.videoEditedInfo.key; + decryptedMessage.media.iv = message.videoEditedInfo.iv; + SecretChatHelper.getInstance().performSendEncryptedRequest(decryptedMessage, message.obj.messageOwner, message.encryptedChat, message.videoEditedInfo.encryptedFile, message.originalPath, message.obj); message.videoEditedInfo.encryptedFile = null; return; } @@ -2375,12 +2757,13 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } if (media.file == null) { String location = message.obj.messageOwner.attachPath; + TLRPC.Document document = message.obj.getDocument(); if (location == null) { - location = FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE) + "/" + message.documentLocation.id + ".mp4"; + location = FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE) + "/" + document.id + ".mp4"; } putToDelayedMessages(location, message); if (message.obj.videoEditedInfo != null && message.obj.videoEditedInfo.needConvert()) { - FileLoader.getInstance().uploadFile(location, false, false, message.documentLocation.size, ConnectionsManager.FileTypeVideo); + FileLoader.getInstance().uploadFile(location, false, false, document.size, ConnectionsManager.FileTypeVideo); } else { FileLoader.getInstance().uploadFile(location, false, false, ConnectionsManager.FileTypeVideo); } @@ -2391,20 +2774,21 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } } else { String location = message.obj.messageOwner.attachPath; + TLRPC.Document document = message.obj.getDocument(); if (location == null) { - location = FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE) + "/" + message.documentLocation.id + ".mp4"; + location = FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE) + "/" + document.id + ".mp4"; } - if (message.sendEncryptedRequest != null && message.documentLocation.dc_id != 0) { + if (message.sendEncryptedRequest != null && document.dc_id != 0) { File file = new File(location); if (!file.exists()) { - putToDelayedMessages(FileLoader.getAttachFileName(message.documentLocation), message); - FileLoader.getInstance().loadFile(message.documentLocation, true, 0); + putToDelayedMessages(FileLoader.getAttachFileName(document), message); + FileLoader.getInstance().loadFile(document, true, 0); return; } } putToDelayedMessages(location, message); if (message.obj.videoEditedInfo != null && message.obj.videoEditedInfo.needConvert()) { - FileLoader.getInstance().uploadFile(location, true, false, message.documentLocation.size, ConnectionsManager.FileTypeVideo); + FileLoader.getInstance().uploadFile(location, true, false, document.size, ConnectionsManager.FileTypeVideo); } else { FileLoader.getInstance().uploadFile(location, true, false, ConnectionsManager.FileTypeVideo); } @@ -2433,11 +2817,12 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } } else { String location = message.obj.messageOwner.attachPath; - if (message.sendEncryptedRequest != null && message.documentLocation.dc_id != 0) { + TLRPC.Document document = message.obj.getDocument(); + if (message.sendEncryptedRequest != null && document.dc_id != 0) { File file = new File(location); if (!file.exists()) { - putToDelayedMessages(FileLoader.getAttachFileName(message.documentLocation), message); - FileLoader.getInstance().loadFile(message.documentLocation, true, 0); + putToDelayedMessages(FileLoader.getAttachFileName(document), message); + FileLoader.getInstance().loadFile(document, true, 0); return; } } @@ -2449,9 +2834,235 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter String location = message.obj.messageOwner.attachPath; putToDelayedMessages(location, message); FileLoader.getInstance().uploadFile(location, message.sendRequest == null, true, ConnectionsManager.FileTypeAudio); + } else if (message.type == 4) { + boolean add = index < 0; + if (message.location != null || message.httpLocation != null || message.upload || index >= 0) { + if (index < 0) { + index = message.messageObjects.size() - 1; + } + MessageObject messageObject = message.messageObjects.get(index); + if (messageObject.getDocument() != null) { + if (message.videoEditedInfo != null) { + String location = messageObject.messageOwner.attachPath; + TLRPC.Document document = messageObject.getDocument(); + if (location == null) { + location = FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE) + "/" + document.id + ".mp4"; + } + putToDelayedMessages(location, message); + message.extraHashMap.put(messageObject, location); + message.extraHashMap.put(location + "_i", messageObject); + if (message.location != null) { + message.extraHashMap.put(location + "_t", message.location); + } + MediaController.getInstance().scheduleVideoConvert(messageObject); + } else { + TLRPC.Document document = messageObject.getDocument(); + String documentLocation = messageObject.messageOwner.attachPath; + if (documentLocation == null) { + documentLocation = FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE) + "/" + document.id + ".mp4"; + } + if (message.sendRequest != null) { + TLRPC.TL_messages_sendMultiMedia request = (TLRPC.TL_messages_sendMultiMedia) message.sendRequest; + TLRPC.InputMedia media = request.multi_media.get(index).media; + if (media.file == null) { + putToDelayedMessages(documentLocation, message); + message.extraHashMap.put(messageObject, documentLocation); + message.extraHashMap.put(documentLocation, media); + message.extraHashMap.put(documentLocation + "_i", messageObject); + if (message.location != null) { + message.extraHashMap.put(documentLocation + "_t", message.location); + } + if (messageObject.videoEditedInfo != null && messageObject.videoEditedInfo.needConvert()) { + FileLoader.getInstance().uploadFile(documentLocation, false, false, document.size, ConnectionsManager.FileTypeVideo); + } else { + FileLoader.getInstance().uploadFile(documentLocation, false, false, ConnectionsManager.FileTypeVideo); + } + } else { + String location = FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE) + "/" + message.location.volume_id + "_" + message.location.local_id + ".jpg"; + putToDelayedMessages(location, message); + message.extraHashMap.put(location + "_o", documentLocation); + message.extraHashMap.put(messageObject, location); + message.extraHashMap.put(location, media); + FileLoader.getInstance().uploadFile(location, false, true, ConnectionsManager.FileTypePhoto); + } + } else { + TLRPC.TL_messages_sendEncryptedMultiMedia request = (TLRPC.TL_messages_sendEncryptedMultiMedia) message.sendEncryptedRequest; + putToDelayedMessages(documentLocation, message); + message.extraHashMap.put(messageObject, documentLocation); + message.extraHashMap.put(documentLocation, request.files.get(index)); + message.extraHashMap.put(documentLocation + "_i", messageObject); + if (message.location != null) { + message.extraHashMap.put(documentLocation + "_t", message.location); + } + if (messageObject.videoEditedInfo != null && messageObject.videoEditedInfo.needConvert()) { + FileLoader.getInstance().uploadFile(documentLocation, true, false, document.size, ConnectionsManager.FileTypeVideo); + } else { + FileLoader.getInstance().uploadFile(documentLocation, true, false, ConnectionsManager.FileTypeVideo); + } + } + } + message.videoEditedInfo = null; + message.location = null; + } else { + if (message.httpLocation != null) { + putToDelayedMessages(message.httpLocation, message); + message.extraHashMap.put(messageObject, message.httpLocation); + message.extraHashMap.put(message.httpLocation, messageObject); + ImageLoader.getInstance().loadHttpFile(message.httpLocation, "file"); + message.httpLocation = null; + } else { + TLObject inputMedia; + if (message.sendRequest != null) { + TLRPC.TL_messages_sendMultiMedia request = (TLRPC.TL_messages_sendMultiMedia) message.sendRequest; + inputMedia = request.multi_media.get(index).media; + } else { + TLRPC.TL_messages_sendEncryptedMultiMedia request = (TLRPC.TL_messages_sendEncryptedMultiMedia) message.sendEncryptedRequest; + inputMedia = request.files.get(index); + } + String location = FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE) + "/" + message.location.volume_id + "_" + message.location.local_id + ".jpg"; + putToDelayedMessages(location, message); + message.extraHashMap.put(location, inputMedia); + message.extraHashMap.put(messageObject, location); + FileLoader.getInstance().uploadFile(location, message.sendEncryptedRequest != null, true, ConnectionsManager.FileTypePhoto); + message.location = null; + } + } + message.upload = false; + } else { + if (!message.messageObjects.isEmpty()) { + putToSendingMessages(message.messageObjects.get(message.messageObjects.size() - 1).messageOwner); + } + } + sendReadyToSendGroup(message, add, true); } } + private void uploadMultiMedia(final DelayedMessage message, final TLRPC.InputMedia inputMedia, final TLRPC.InputEncryptedFile inputEncryptedFile, String key) { + if (inputMedia != null) { + TLRPC.TL_messages_sendMultiMedia multiMedia = (TLRPC.TL_messages_sendMultiMedia) message.sendRequest; + for (int a = 0; a < multiMedia.multi_media.size(); a++) { + if (multiMedia.multi_media.get(a).media == inputMedia) { + putToSendingMessages(message.messages.get(a)); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileUploadProgressChanged, key, 1.0f, false); + break; + } + } + + TLRPC.TL_messages_uploadMedia req = new TLRPC.TL_messages_uploadMedia(); + req.media = inputMedia; + req.peer = ((TLRPC.TL_messages_sendMultiMedia) message.sendRequest).peer; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, final TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + TLRPC.InputMedia newInputMedia = null; + if (response != null) { + TLRPC.MessageMedia messageMedia = (TLRPC.MessageMedia) response; + if (inputMedia instanceof TLRPC.TL_inputMediaUploadedPhoto && messageMedia instanceof TLRPC.TL_messageMediaPhoto) { + TLRPC.TL_inputMediaPhoto inputMediaPhoto = new TLRPC.TL_inputMediaPhoto(); + inputMediaPhoto.id = new TLRPC.TL_inputPhoto(); + inputMediaPhoto.id.id = messageMedia.photo.id; + inputMediaPhoto.id.access_hash = messageMedia.photo.access_hash; + newInputMedia = inputMediaPhoto; + } else if (inputMedia instanceof TLRPC.TL_inputMediaUploadedDocument && messageMedia instanceof TLRPC.TL_messageMediaDocument) { + TLRPC.TL_inputMediaDocument inputMediaDocument = new TLRPC.TL_inputMediaDocument(); + inputMediaDocument.id = new TLRPC.TL_inputDocument(); + inputMediaDocument.id.id = messageMedia.document.id; + inputMediaDocument.id.access_hash = messageMedia.document.access_hash; + newInputMedia = inputMediaDocument; + } + } + if (newInputMedia != null) { + newInputMedia.caption = inputMedia.caption; + if (inputMedia.ttl_seconds != 0) { + newInputMedia.ttl_seconds = inputMedia.ttl_seconds; + newInputMedia.flags |= 1; + } + TLRPC.TL_messages_sendMultiMedia req = (TLRPC.TL_messages_sendMultiMedia) message.sendRequest; + for (int a = 0; a < req.multi_media.size(); a++) { + if (req.multi_media.get(a).media == inputMedia) { + req.multi_media.get(a).media = newInputMedia; + break; + } + } + sendReadyToSendGroup(message, false, true); + } else { + message.markAsError(); + } + } + }); + } + }); + } else if (inputEncryptedFile != null) { + TLRPC.TL_messages_sendEncryptedMultiMedia multiMedia = (TLRPC.TL_messages_sendEncryptedMultiMedia) message.sendEncryptedRequest; + for (int a = 0; a < multiMedia.files.size(); a++) { + if (multiMedia.files.get(a) == inputEncryptedFile) { + putToSendingMessages(message.messages.get(a)); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileUploadProgressChanged, key, 1.0f, false); + break; + } + } + sendReadyToSendGroup(message, false, true); + } + } + + private void sendReadyToSendGroup(DelayedMessage message, boolean add, boolean check) { + if (message.messageObjects.isEmpty()) { + message.markAsError(); + return; + } + String key = "group_" + message.groupId; + if (message.finalGroupMessage != message.messageObjects.get(message.messageObjects.size() - 1).getId()) { + if (add) { + putToDelayedMessages(key, message); + } + return; + } else if (add) { + delayedMessages.remove(key); + MessagesStorage.getInstance().putMessages(message.messages, false, true, false, 0); + MessagesController.getInstance().updateInterfaceWithMessages(message.peer, message.messageObjects); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload); + } + if (message.sendRequest instanceof TLRPC.TL_messages_sendMultiMedia) { + TLRPC.TL_messages_sendMultiMedia request = (TLRPC.TL_messages_sendMultiMedia) message.sendRequest; + for (int a = 0; a < request.multi_media.size(); a++) { + TLRPC.InputMedia inputMedia = request.multi_media.get(a).media; + if (inputMedia instanceof TLRPC.TL_inputMediaUploadedPhoto || inputMedia instanceof TLRPC.TL_inputMediaUploadedDocument) { + return; + } + } + + if (check) { + DelayedMessage maxDelayedMessage = findMaxDelayedMessageForMessageId(message.finalGroupMessage, message.peer); + if (maxDelayedMessage != null) { + maxDelayedMessage.addDelayedRequest(message.sendRequest, message.messageObjects, message.originalPaths); + if (message.requests != null) { + maxDelayedMessage.requests.addAll(message.requests); + } + return; + } + } + } else { + TLRPC.TL_messages_sendEncryptedMultiMedia request = (TLRPC.TL_messages_sendEncryptedMultiMedia) message.sendEncryptedRequest; + for (int a = 0; a < request.files.size(); a++) { + TLRPC.InputEncryptedFile inputMedia = request.files.get(a); + if (inputMedia instanceof TLRPC.TL_inputEncryptedFile) { + return; + } + } + } + + if (message.sendRequest instanceof TLRPC.TL_messages_sendMultiMedia) { + performSendMessageRequestMulti((TLRPC.TL_messages_sendMultiMedia) message.sendRequest, message.messageObjects, message.originalPaths); + } else { + SecretChatHelper.getInstance().performSendEncryptedRequest((TLRPC.TL_messages_sendEncryptedMultiMedia) message.sendEncryptedRequest, message); + } + + message.sendDelayedRequests(); + } + protected void stopVideoService(final String path) { MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { @Override @@ -2478,7 +3089,177 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter return sendingMessages.containsKey(mid); } + private void performSendMessageRequestMulti(final TLRPC.TL_messages_sendMultiMedia req, final ArrayList msgObjs, final ArrayList originalPaths) { + for (int a = 0; a < msgObjs.size(); a++) { + putToSendingMessages(msgObjs.get(a).messageOwner); + } + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, final TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + boolean isSentError = false; + if (error == null) { + HashMap newMessages = new HashMap<>(); + HashMap newIds = new HashMap<>(); + final TLRPC.Updates updates = (TLRPC.Updates) response; + ArrayList updatesArr = ((TLRPC.Updates) response).updates; + for (int a = 0; a < updatesArr.size(); a++) { + TLRPC.Update update = updatesArr.get(a); + if (update instanceof TLRPC.TL_updateMessageID) { + newIds.put(update.random_id, ((TLRPC.TL_updateMessageID) update).id); + updatesArr.remove(a); + a--; + } else if (update instanceof TLRPC.TL_updateNewMessage) { + final TLRPC.TL_updateNewMessage newMessage = (TLRPC.TL_updateNewMessage) update; + newMessages.put(newMessage.message.id, newMessage.message); + Utilities.stageQueue.postRunnable(new Runnable() { + @Override + public void run() { + MessagesController.getInstance().processNewDifferenceParams(-1, newMessage.pts, -1, newMessage.pts_count); + } + }); + updatesArr.remove(a); + a--; + } else if (update instanceof TLRPC.TL_updateNewChannelMessage) { + final TLRPC.TL_updateNewChannelMessage newMessage = (TLRPC.TL_updateNewChannelMessage) update; + newMessages.put(newMessage.message.id, newMessage.message); + Utilities.stageQueue.postRunnable(new Runnable() { + @Override + public void run() { + MessagesController.getInstance().processNewChannelDifferenceParams(newMessage.pts, newMessage.pts_count, newMessage.message.to_id.channel_id); + } + }); + updatesArr.remove(a); + a--; + } + } + + for (int i = 0; i < msgObjs.size(); i++) { + final MessageObject msgObj = msgObjs.get(i); + final String originalPath = originalPaths.get(i); + final TLRPC.Message newMsgObj = msgObj.messageOwner; + final int oldId = newMsgObj.id; + final ArrayList sentMessages = new ArrayList<>(); + final String attachPath = newMsgObj.attachPath; + + Integer id = newIds.get(newMsgObj.random_id); + if (id != null) { + TLRPC.Message message = newMessages.get(id); + if (message != null) { + sentMessages.add(message); + newMsgObj.id = message.id; + if ((newMsgObj.flags & TLRPC.MESSAGE_FLAG_MEGAGROUP) != 0) { + message.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; + } + + Integer value = MessagesController.getInstance().dialogs_read_outbox_max.get(message.dialog_id); + if (value == null) { + value = MessagesStorage.getInstance().getDialogReadMax(message.out, message.dialog_id); + MessagesController.getInstance().dialogs_read_outbox_max.put(message.dialog_id, value); + } + message.unread = value < message.id; + updateMediaPaths(msgObj, message, originalPath, false); + } else { + isSentError = true; + break; + } + } else { + isSentError = true; + break; + } + + if (!isSentError) { + StatsController.getInstance().incrementSentItemsCount(ConnectionsManager.getCurrentNetworkType(), StatsController.TYPE_MESSAGES, 1); + newMsgObj.send_state = MessageObject.MESSAGE_SEND_STATE_SENT; + NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageReceivedByServer, oldId, newMsgObj.id, newMsgObj, newMsgObj.dialog_id); + MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { + @Override + public void run() { + MessagesStorage.getInstance().updateMessageStateAndId(newMsgObj.random_id, oldId, newMsgObj.id, 0, false, newMsgObj.to_id.channel_id); + MessagesStorage.getInstance().putMessages(sentMessages, true, false, false, 0); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + SearchQuery.increasePeerRaiting(newMsgObj.dialog_id); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageReceivedByServer, oldId, newMsgObj.id, newMsgObj, newMsgObj.dialog_id); + processSentMessage(oldId); + removeFromSendingMessages(oldId); + } + }); + } + }); + } + } + Utilities.stageQueue.postRunnable(new Runnable() { + @Override + public void run() { + MessagesController.getInstance().processUpdates(updates, false); + } + }); + } else { + AlertsCreator.processError(error, null, req); + isSentError = true; + } + if (isSentError) { + for (int i = 0; i < msgObjs.size(); i++) { + TLRPC.Message newMsgObj = msgObjs.get(i).messageOwner; + MessagesStorage.getInstance().markMessageAsSendError(newMsgObj); + newMsgObj.send_state = MessageObject.MESSAGE_SEND_STATE_SEND_ERROR; + NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageSendError, newMsgObj.id); + processSentMessage(newMsgObj.id); + removeFromSendingMessages(newMsgObj.id); + } + } + } + }); + } + }, null, ConnectionsManager.RequestFlagCanCompress | ConnectionsManager.RequestFlagInvokeAfter); + } + private void performSendMessageRequest(final TLObject req, final MessageObject msgObj, final String originalPath) { + performSendMessageRequest(req, msgObj, originalPath, null, false); + } + + private DelayedMessage findMaxDelayedMessageForMessageId(int messageId, long dialogId) { + DelayedMessage maxDelayedMessage = null; + int maxDalyedMessageId = Integer.MIN_VALUE; + for (HashMap.Entry> entry : delayedMessages.entrySet()) { + ArrayList messages = entry.getValue(); + int size = messages.size(); + for (int a = 0; a < size; a++) { + DelayedMessage delayedMessage = messages.get(a); + if ((delayedMessage.type == 4 || delayedMessage.type == 0) && delayedMessage.peer == dialogId) { + int mid = 0; + if (delayedMessage.obj != null) { + mid = delayedMessage.obj.getId(); + } else if (delayedMessage.messageObjects != null && !delayedMessage.messageObjects.isEmpty()) { + mid = delayedMessage.messageObjects.get(delayedMessage.messageObjects.size() - 1).getId(); + } + if (mid != 0 && mid > messageId) { + if (maxDelayedMessage == null && maxDalyedMessageId < mid) { + maxDelayedMessage = delayedMessage; + maxDalyedMessageId = mid; + } + } + } + } + } + return maxDelayedMessage; + } + + private void performSendMessageRequest(final TLObject req, final MessageObject msgObj, final String originalPath, DelayedMessage parentMessage, boolean check) { + if (check) { + DelayedMessage maxDelayedMessage = findMaxDelayedMessageForMessageId(msgObj.getId(), msgObj.getDialogId()); + if (maxDelayedMessage != null) { + maxDelayedMessage.addDelayedRequest(req, msgObj, originalPath); + if (parentMessage != null && parentMessage.requests != null) { + maxDelayedMessage.requests.addAll(parentMessage.requests); + } + return; + } + } final TLRPC.Message newMsgObj = msgObj.messageOwner; putToSendingMessages(newMsgObj); ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @@ -2525,7 +3306,6 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (update instanceof TLRPC.TL_updateNewMessage) { final TLRPC.TL_updateNewMessage newMessage = (TLRPC.TL_updateNewMessage) update; sentMessages.add(message = newMessage.message); - newMsgObj.id = newMessage.message.id; Utilities.stageQueue.postRunnable(new Runnable() { @Override public void run() { @@ -2571,6 +3351,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter }); } + if (MessageObject.isLiveLocationMessage(newMsgObj)) { + LocationController.getInstance().addSharingLocation(newMsgObj.dialog_id, newMsgObj.id, newMsgObj.media.period, newMsgObj); + } + if (!isSentError) { StatsController.getInstance().incrementSentItemsCount(ConnectionsManager.getCurrentNetworkType(), StatsController.TYPE_MESSAGES, 1); newMsgObj.send_state = MessageObject.MESSAGE_SEND_STATE_SENT; @@ -2640,6 +3424,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter }); } }, ConnectionsManager.RequestFlagCanCompress | ConnectionsManager.RequestFlagInvokeAfter | (req instanceof TLRPC.TL_messages_sendMessage ? ConnectionsManager.RequestFlagNeedQuickAck : 0)); + + if (parentMessage != null) { + parentMessage.sendDelayedRequests(); + } } private void updateMediaPaths(MessageObject newMsgObj, TLRPC.Message sentMessage, String originalPath, boolean post) { @@ -2750,7 +3538,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (MessageObject.isNewGifDocument(sentMessage.media.document)) { StickersQuery.addRecentGif(sentMessage.media.document, sentMessage.date); } else if (MessageObject.isStickerDocument(sentMessage.media.document)) { - StickersQuery.addRecentSticker(StickersQuery.TYPE_IMAGE, sentMessage.media.document, sentMessage.date); + StickersQuery.addRecentSticker(StickersQuery.TYPE_IMAGE, sentMessage.media.document, sentMessage.date, false); } } @@ -2872,16 +3660,22 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter TLRPC.TL_documentAttributeAudio attributeAudio = null; String extension = null; if (uri != null) { + boolean hasExt = false; if (mime != null) { extension = myMime.getExtensionFromMimeType(mime); } if (extension == null) { extension = "txt"; + } else { + hasExt = true; } path = MediaController.copyFileToCache(uri, extension); if (path == null) { return false; } + if (!hasExt) { + extension = null; + } } final File f = new File(path); if (!f.exists() || f.length() == 0) { @@ -3132,32 +3926,19 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } public static void prepareSendingPhoto(String imageFilePath, Uri imageUri, long dialog_id, MessageObject reply_to_msg, CharSequence caption, ArrayList stickers, InputContentInfoCompat inputContent, int ttl) { - ArrayList paths = null; - ArrayList uris = null; - ArrayList captions = null; - ArrayList ttls = null; - ArrayList> masks = null; - if (imageFilePath != null && imageFilePath.length() != 0) { - paths = new ArrayList<>(); - paths.add(imageFilePath); - } - if (imageUri != null) { - uris = new ArrayList<>(); - uris.add(imageUri); - } - if (ttl != 0) { - ttls = new ArrayList<>(); - ttls.add(ttl); - } + SendingMediaInfo info = new SendingMediaInfo(); + info.path = imageFilePath; + info.uri = imageUri; if (caption != null) { - captions = new ArrayList<>(); - captions.add(caption.toString()); + info.caption = caption.toString(); } + info.ttl = ttl; if (stickers != null && !stickers.isEmpty()) { - masks = new ArrayList<>(); - masks.add(new ArrayList<>(stickers)); + info.masks = new ArrayList<>(stickers); } - prepareSendingPhotos(paths, uris, dialog_id, reply_to_msg, captions, masks, inputContent, false, ttls); + ArrayList infos = new ArrayList<>(); + infos.add(info); + prepareSendingMedia(infos, dialog_id, reply_to_msg, inputContent, false, false); } public static void prepareSendingBotContextResult(final TLRPC.BotInlineResult result, final HashMap params, final long dialog_id, final MessageObject reply_to_msg) { @@ -3403,11 +4184,19 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter venue.title = result.send_message.title; venue.provider = result.send_message.provider; venue.venue_id = result.send_message.venue_id; + venue.venue_type = ""; SendMessagesHelper.getInstance().sendMessage(venue, dialog_id, reply_to_msg, result.send_message.reply_markup, params); } else if (result.send_message instanceof TLRPC.TL_botInlineMessageMediaGeo) { - TLRPC.TL_messageMediaGeo location = new TLRPC.TL_messageMediaGeo(); - location.geo = result.send_message.geo; - SendMessagesHelper.getInstance().sendMessage(location, dialog_id, reply_to_msg, result.send_message.reply_markup, params); + if (result.send_message.period != 0) { + TLRPC.TL_messageMediaGeoLive location = new TLRPC.TL_messageMediaGeoLive(); + location.period = result.send_message.period; + location.geo = result.send_message.geo; + SendMessagesHelper.getInstance().sendMessage(location, dialog_id, reply_to_msg, result.send_message.reply_markup, params); + } else { + TLRPC.TL_messageMediaGeo location = new TLRPC.TL_messageMediaGeo(); + location.geo = result.send_message.geo; + SendMessagesHelper.getInstance().sendMessage(location, dialog_id, reply_to_msg, result.send_message.reply_markup, params); + } } else if (result.send_message instanceof TLRPC.TL_botInlineMessageMediaContact) { TLRPC.User user = new TLRPC.TL_user(); user.phone = result.send_message.phone_number; @@ -3417,163 +4206,6 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } } - public static void prepareSendingPhotosSearch(final ArrayList photos, final long dialog_id, final MessageObject reply_to_msg) { - if (photos == null || photos.isEmpty()) { - return; - } - new Thread(new Runnable() { - @Override - public void run() { - boolean isEncrypted = (int) dialog_id == 0; - for (int a = 0; a < photos.size(); a++) { - final MediaController.SearchImage searchImage = photos.get(a); - final int ttl = searchImage.ttl; - if (searchImage.type == 1) { - final HashMap params = new HashMap<>(); - TLRPC.TL_document document = null; - File cacheFile; - if (searchImage.document instanceof TLRPC.TL_document) { - document = (TLRPC.TL_document) searchImage.document; - cacheFile = FileLoader.getPathToAttach(document, true); - } else { - if (!isEncrypted) { - TLRPC.Document doc = (TLRPC.Document) MessagesStorage.getInstance().getSentFile(searchImage.imageUrl, !isEncrypted ? 1 : 4); - if (doc instanceof TLRPC.TL_document) { - document = (TLRPC.TL_document) doc; - } - } - String md5 = Utilities.MD5(searchImage.imageUrl) + "." + ImageLoader.getHttpUrlExtension(searchImage.imageUrl, "jpg"); - cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), md5); - } - if (document == null) { - if (searchImage.localUrl != null) { - params.put("url", searchImage.localUrl); - } - File thumbFile = null; - document = new TLRPC.TL_document(); - document.id = 0; - document.date = ConnectionsManager.getInstance().getCurrentTime(); - TLRPC.TL_documentAttributeFilename fileName = new TLRPC.TL_documentAttributeFilename(); - fileName.file_name = "animation.gif"; - document.attributes.add(fileName); - document.size = searchImage.size; - document.dc_id = 0; - if (cacheFile.toString().endsWith("mp4")) { - document.mime_type = "video/mp4"; - document.attributes.add(new TLRPC.TL_documentAttributeAnimated()); - } else { - document.mime_type = "image/gif"; - } - if (cacheFile.exists()) { - thumbFile = cacheFile; - } else { - cacheFile = null; - } - if (thumbFile == null) { - String thumb = Utilities.MD5(searchImage.thumbUrl) + "." + ImageLoader.getHttpUrlExtension(searchImage.thumbUrl, "jpg"); - thumbFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), thumb); - if (!thumbFile.exists()) { - thumbFile = null; - } - } - if (thumbFile != null) { - try { - Bitmap bitmap; - if (thumbFile.getAbsolutePath().endsWith("mp4")) { - bitmap = ThumbnailUtils.createVideoThumbnail(thumbFile.getAbsolutePath(), MediaStore.Video.Thumbnails.MINI_KIND); - } else { - bitmap = ImageLoader.loadBitmap(thumbFile.getAbsolutePath(), null, 90, 90, true); - } - if (bitmap != null) { - document.thumb = ImageLoader.scaleAndSaveImage(bitmap, 90, 90, 55, isEncrypted); - bitmap.recycle(); - } - } catch (Exception e) { - FileLog.e(e); - } - } - if (document.thumb == null) { - document.thumb = new TLRPC.TL_photoSize(); - document.thumb.w = searchImage.width; - document.thumb.h = searchImage.height; - document.thumb.size = 0; - document.thumb.location = new TLRPC.TL_fileLocationUnavailable(); - document.thumb.type = "x"; - } - } - - if (searchImage.caption != null) { - document.caption = searchImage.caption.toString(); - } - final TLRPC.TL_document documentFinal = document; - final String originalPathFinal = searchImage.imageUrl; - final String pathFinal = cacheFile == null ? searchImage.imageUrl : cacheFile.toString(); - if (params != null && searchImage.imageUrl != null) { - params.put("originalPath", searchImage.imageUrl); - } - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - SendMessagesHelper.getInstance().sendMessage(documentFinal, null, pathFinal, dialog_id, reply_to_msg, null, params, 0); - } - }); - } else { - boolean needDownloadHttp = true; - TLRPC.TL_photo photo = null; - if (!isEncrypted && ttl == 0) { - photo = (TLRPC.TL_photo) MessagesStorage.getInstance().getSentFile(searchImage.imageUrl, !isEncrypted ? 0 : 3); - } - if (photo == null) { - String md5 = Utilities.MD5(searchImage.imageUrl) + "." + ImageLoader.getHttpUrlExtension(searchImage.imageUrl, "jpg"); - File cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), md5); - if (cacheFile.exists() && cacheFile.length() != 0) { - photo = SendMessagesHelper.getInstance().generatePhotoSizes(cacheFile.toString(), null); - if (photo != null) { - needDownloadHttp = false; - } - } - if (photo == null) { - md5 = Utilities.MD5(searchImage.thumbUrl) + "." + ImageLoader.getHttpUrlExtension(searchImage.thumbUrl, "jpg"); - cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), md5); - if (cacheFile.exists()) { - photo = SendMessagesHelper.getInstance().generatePhotoSizes(cacheFile.toString(), null); - } - if (photo == null) { - photo = new TLRPC.TL_photo(); - photo.date = ConnectionsManager.getInstance().getCurrentTime(); - TLRPC.TL_photoSize photoSize = new TLRPC.TL_photoSize(); - photoSize.w = searchImage.width; - photoSize.h = searchImage.height; - photoSize.size = 0; - photoSize.location = new TLRPC.TL_fileLocationUnavailable(); - photoSize.type = "x"; - photo.sizes.add(photoSize); - } - } - } - if (photo != null) { - if (searchImage.caption != null) { - photo.caption = searchImage.caption.toString(); - } - final TLRPC.TL_photo photoFinal = photo; - final boolean needDownloadHttpFinal = needDownloadHttp; - final HashMap params = new HashMap<>(); - if (searchImage.imageUrl != null) { - params.put("originalPath", searchImage.imageUrl); - } - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - SendMessagesHelper.getInstance().sendMessage(photoFinal, needDownloadHttpFinal ? searchImage.imageUrl : null, dialog_id, reply_to_msg, null, params, ttl); - } - }); - } - } - } - } - }).start(); - } - private static String getTrimmedString(String src) { String result = src.trim(); if (result.length() == 0) { @@ -3614,135 +4246,492 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter }); } - public static void prepareSendingPhotos(ArrayList paths, ArrayList uris, final long dialog_id, final MessageObject reply_to_msg, final ArrayList captions, final ArrayList> masks, final InputContentInfoCompat inputContent, final boolean forceDocument, final ArrayList ttls) { - if (paths == null && uris == null || paths != null && paths.isEmpty() || uris != null && uris.isEmpty()) { + public static void prepareSendingMedia(final ArrayList media, final long dialog_id, final MessageObject reply_to_msg, final InputContentInfoCompat inputContent, final boolean forceDocument, final boolean groupPhotos) { + if (media.isEmpty()) { return; } - final ArrayList pathsCopy = new ArrayList<>(); - final ArrayList urisCopy = new ArrayList<>(); - if (paths != null) { - pathsCopy.addAll(paths); - } - if (uris != null) { - urisCopy.addAll(uris); - } - new Thread(new Runnable() { + mediaSendQueue.postRunnable(new Runnable() { @Override public void run() { + long beginTime = System.currentTimeMillis(); + HashMap workers; + int count = media.size(); boolean isEncrypted = (int) dialog_id == 0; + int enryptedLayer = 0; + if (isEncrypted) { + int high_id = (int) (dialog_id >> 32); + TLRPC.EncryptedChat encryptedChat = MessagesController.getInstance().getEncryptedChat(high_id); + if (encryptedChat != null) { + enryptedLayer = AndroidUtilities.getPeerLayerVersion(encryptedChat.layer); + } + } + if ((!isEncrypted || enryptedLayer >= 73) && !forceDocument && groupPhotos) { + workers = new HashMap<>(); + for (int a = 0; a < count; a++) { + final SendingMediaInfo info = media.get(a); + if (info.searchImage == null && !info.isVideo) { + String originalPath = info.path; + String tempPath = info.path; + if (tempPath == null && info.uri != null) { + tempPath = AndroidUtilities.getPath(info.uri); + originalPath = info.uri.toString(); + } + + if (tempPath != null && (tempPath.endsWith(".gif") || tempPath.endsWith(".webp"))) { + continue; + } else if (tempPath == null && info.uri != null) { + if (MediaController.isGif(info.uri) || MediaController.isWebp(info.uri)) { + continue; + } + } + + if (tempPath != null) { + File temp = new File(tempPath); + originalPath += temp.length() + "_" + temp.lastModified(); + } else { + originalPath = null; + } + TLRPC.TL_photo photo = null; + if (!isEncrypted && info.ttl == 0) { + photo = (TLRPC.TL_photo) MessagesStorage.getInstance().getSentFile(originalPath, !isEncrypted ? 0 : 3); + if (photo == null && info.uri != null) { + photo = (TLRPC.TL_photo) MessagesStorage.getInstance().getSentFile(AndroidUtilities.getPath(info.uri), !isEncrypted ? 0 : 3); + } + } + final MediaSendPrepareWorker worker = new MediaSendPrepareWorker(); + workers.put(info, worker); + if (photo != null) { + worker.photo = photo; + } else { + worker.sync = new CountDownLatch(1); + mediaSendThreadPool.execute(new Runnable() { + @Override + public void run() { + worker.photo = SendMessagesHelper.getInstance().generatePhotoSizes(info.path, info.uri); + worker.sync.countDown(); + } + }); + } + } + } + } else { + workers = null; + } + + long groupId = 0; + long lastGroupId = 0; ArrayList sendAsDocuments = null; ArrayList sendAsDocumentsOriginal = null; ArrayList sendAsDocumentsCaptions = null; - int count = !pathsCopy.isEmpty() ? pathsCopy.size() : urisCopy.size(); - String path = null; - Uri uri = null; + String extension = null; + int photosCount = 0; for (int a = 0; a < count; a++) { - if (!pathsCopy.isEmpty()) { - path = pathsCopy.get(a); - } else if (!urisCopy.isEmpty()) { - uri = urisCopy.get(a); + final SendingMediaInfo info = media.get(a); + if (groupPhotos && (!isEncrypted || enryptedLayer >= 73) && count > 1 && photosCount % 10 == 0) { + lastGroupId = groupId = Utilities.random.nextLong(); + photosCount = 0; } - - String originalPath = path; - String tempPath = path; - if (tempPath == null && uri != null) { - tempPath = AndroidUtilities.getPath(uri); - originalPath = uri.toString(); - } - final int ttl = ttls != null ? ttls.get(a) : 0; - - boolean isDocument = false; - if (forceDocument) { - isDocument = true; - extension = FileLoader.getFileExtension(new File(tempPath)); - } else if (tempPath != null && (tempPath.endsWith(".gif") || tempPath.endsWith(".webp"))) { - if (tempPath.endsWith(".gif")) { - extension = "gif"; - } else { - extension = "webp"; - } - isDocument = true; - } else if (tempPath == null && uri != null) { - if (MediaController.isGif(uri)) { - isDocument = true; - originalPath = uri.toString(); - tempPath = MediaController.copyFileToCache(uri, "gif"); - extension = "gif"; - } else if (MediaController.isWebp(uri)) { - isDocument = true; - originalPath = uri.toString(); - tempPath = MediaController.copyFileToCache(uri, "webp"); - extension = "webp"; - } - } - - - if (isDocument) { - if (sendAsDocuments == null) { - sendAsDocuments = new ArrayList<>(); - sendAsDocumentsOriginal = new ArrayList<>(); - sendAsDocumentsCaptions = new ArrayList<>(); - } - sendAsDocuments.add(tempPath); - sendAsDocumentsOriginal.add(originalPath); - sendAsDocumentsCaptions.add(captions != null ? captions.get(a) : null); - } else { - if (tempPath != null) { - File temp = new File(tempPath); - originalPath += temp.length() + "_" + temp.lastModified(); - } else { - originalPath = null; - } - TLRPC.TL_photo photo = null; - if (!isEncrypted && ttl == 0) { - photo = (TLRPC.TL_photo) MessagesStorage.getInstance().getSentFile(originalPath, !isEncrypted ? 0 : 3); - if (photo == null && uri != null) { - photo = (TLRPC.TL_photo) MessagesStorage.getInstance().getSentFile(AndroidUtilities.getPath(uri), !isEncrypted ? 0 : 3); - } - } - if (photo == null) { - photo = SendMessagesHelper.getInstance().generatePhotoSizes(path, uri); - } - if (photo != null) { - final TLRPC.TL_photo photoFinal = photo; + if (info.searchImage != null) { + if (info.searchImage.type == 1) { final HashMap params = new HashMap<>(); - if (captions != null) { - photo.caption = captions.get(a); - } - if (masks != null) { - ArrayList arrayList = masks.get(a); - if (photo.has_stickers = arrayList != null && !arrayList.isEmpty()) { - SerializedData serializedData = new SerializedData(4 + arrayList.size() * 20); - serializedData.writeInt32(arrayList.size()); - for (int b = 0; b < arrayList.size(); b++) { - arrayList.get(b).serializeToStream(serializedData); + TLRPC.TL_document document = null; + File cacheFile; + if (info.searchImage.document instanceof TLRPC.TL_document) { + document = (TLRPC.TL_document) info.searchImage.document; + cacheFile = FileLoader.getPathToAttach(document, true); + } else { + if (!isEncrypted) { + TLRPC.Document doc = (TLRPC.Document) MessagesStorage.getInstance().getSentFile(info.searchImage.imageUrl, !isEncrypted ? 1 : 4); + if (doc instanceof TLRPC.TL_document) { + document = (TLRPC.TL_document) doc; } - params.put("masks", Utilities.bytesToHex(serializedData.toByteArray())); + } + String md5 = Utilities.MD5(info.searchImage.imageUrl) + "." + ImageLoader.getHttpUrlExtension(info.searchImage.imageUrl, "jpg"); + cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), md5); + } + if (document == null) { + if (info.searchImage.localUrl != null) { + params.put("url", info.searchImage.localUrl); + } + File thumbFile = null; + document = new TLRPC.TL_document(); + document.id = 0; + document.date = ConnectionsManager.getInstance().getCurrentTime(); + TLRPC.TL_documentAttributeFilename fileName = new TLRPC.TL_documentAttributeFilename(); + fileName.file_name = "animation.gif"; + document.attributes.add(fileName); + document.size = info.searchImage.size; + document.dc_id = 0; + if (cacheFile.toString().endsWith("mp4")) { + document.mime_type = "video/mp4"; + document.attributes.add(new TLRPC.TL_documentAttributeAnimated()); + } else { + document.mime_type = "image/gif"; + } + if (cacheFile.exists()) { + thumbFile = cacheFile; + } else { + cacheFile = null; + } + if (thumbFile == null) { + String thumb = Utilities.MD5(info.searchImage.thumbUrl) + "." + ImageLoader.getHttpUrlExtension(info.searchImage.thumbUrl, "jpg"); + thumbFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), thumb); + if (!thumbFile.exists()) { + thumbFile = null; + } + } + if (thumbFile != null) { + try { + Bitmap bitmap; + if (thumbFile.getAbsolutePath().endsWith("mp4")) { + bitmap = ThumbnailUtils.createVideoThumbnail(thumbFile.getAbsolutePath(), MediaStore.Video.Thumbnails.MINI_KIND); + } else { + bitmap = ImageLoader.loadBitmap(thumbFile.getAbsolutePath(), null, 90, 90, true); + } + if (bitmap != null) { + document.thumb = ImageLoader.scaleAndSaveImage(bitmap, 90, 90, 55, isEncrypted); + bitmap.recycle(); + } + } catch (Exception e) { + FileLog.e(e); + } + } + if (document.thumb == null) { + document.thumb = new TLRPC.TL_photoSize(); + document.thumb.w = info.searchImage.width; + document.thumb.h = info.searchImage.height; + document.thumb.size = 0; + document.thumb.location = new TLRPC.TL_fileLocationUnavailable(); + document.thumb.type = "x"; } } - if (originalPath != null) { - params.put("originalPath", originalPath); + document.caption = info.caption; + final TLRPC.TL_document documentFinal = document; + final String originalPathFinal = info.searchImage.imageUrl; + final String pathFinal = cacheFile == null ? info.searchImage.imageUrl : cacheFile.toString(); + if (params != null && info.searchImage.imageUrl != null) { + params.put("originalPath", info.searchImage.imageUrl); } AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - SendMessagesHelper.getInstance().sendMessage(photoFinal, null, dialog_id, reply_to_msg, null, params, ttl); + SendMessagesHelper.getInstance().sendMessage(documentFinal, null, pathFinal, dialog_id, reply_to_msg, null, params, 0); } }); } else { - if (sendAsDocuments == null) { - sendAsDocuments = new ArrayList<>(); - sendAsDocumentsOriginal = new ArrayList<>(); - sendAsDocumentsCaptions = new ArrayList<>(); + boolean needDownloadHttp = true; + TLRPC.TL_photo photo = null; + if (!isEncrypted && info.ttl == 0) { + photo = (TLRPC.TL_photo) MessagesStorage.getInstance().getSentFile(info.searchImage.imageUrl, !isEncrypted ? 0 : 3); + } + if (photo == null) { + String md5 = Utilities.MD5(info.searchImage.imageUrl) + "." + ImageLoader.getHttpUrlExtension(info.searchImage.imageUrl, "jpg"); + File cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), md5); + if (cacheFile.exists() && cacheFile.length() != 0) { + photo = SendMessagesHelper.getInstance().generatePhotoSizes(cacheFile.toString(), null); + if (photo != null) { + needDownloadHttp = false; + } + } + if (photo == null) { + md5 = Utilities.MD5(info.searchImage.thumbUrl) + "." + ImageLoader.getHttpUrlExtension(info.searchImage.thumbUrl, "jpg"); + cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), md5); + if (cacheFile.exists()) { + photo = SendMessagesHelper.getInstance().generatePhotoSizes(cacheFile.toString(), null); + } + if (photo == null) { + photo = new TLRPC.TL_photo(); + photo.date = ConnectionsManager.getInstance().getCurrentTime(); + TLRPC.TL_photoSize photoSize = new TLRPC.TL_photoSize(); + photoSize.w = info.searchImage.width; + photoSize.h = info.searchImage.height; + photoSize.size = 0; + photoSize.location = new TLRPC.TL_fileLocationUnavailable(); + photoSize.type = "x"; + photo.sizes.add(photoSize); + } + } + } + if (photo != null) { + photo.caption = info.caption; + final TLRPC.TL_photo photoFinal = photo; + final boolean needDownloadHttpFinal = needDownloadHttp; + final HashMap params = new HashMap<>(); + if (info.searchImage.imageUrl != null) { + params.put("originalPath", info.searchImage.imageUrl); + } + if (groupPhotos) { + photosCount++; + params.put("groupId", "" + groupId); + if (photosCount == 10 || a == count -1) { + params.put("final", "1"); + lastGroupId = 0; + } + } + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + SendMessagesHelper.getInstance().sendMessage(photoFinal, needDownloadHttpFinal ? info.searchImage.imageUrl : null, dialog_id, reply_to_msg, null, params, info.ttl); + } + }); + } + } + } else { + if (info.isVideo) { + Bitmap thumb = null; + String thumbKey = null; + + if (info.videoEditedInfo != null || info.path.endsWith("mp4")) { + String path = info.path; + String originalPath = info.path; + File temp = new File(originalPath); + long startTime = 0; + boolean muted = false; + + originalPath += temp.length() + "_" + temp.lastModified(); + if (info.videoEditedInfo != null) { + muted = info.videoEditedInfo.muted; + originalPath += info.videoEditedInfo.estimatedDuration + "_" + info.videoEditedInfo.startTime + "_" + info.videoEditedInfo.endTime + (info.videoEditedInfo.muted ? "_m" : ""); + if (info.videoEditedInfo.resultWidth == info.videoEditedInfo.originalWidth) { + originalPath += "_" + info.videoEditedInfo.resultWidth; + } + startTime = info.videoEditedInfo.startTime >= 0 ? info.videoEditedInfo.startTime : 0; + } + TLRPC.TL_document document = null; + if (!isEncrypted && info.ttl == 0) { + document = (TLRPC.TL_document) MessagesStorage.getInstance().getSentFile(originalPath, !isEncrypted ? 2 : 5); + } + if (document == null) { + thumb = createVideoThumbnail(info.path, startTime); + if (thumb == null) { + thumb = ThumbnailUtils.createVideoThumbnail(info.path, MediaStore.Video.Thumbnails.MINI_KIND); + } + TLRPC.PhotoSize size = ImageLoader.scaleAndSaveImage(thumb, 90, 90, 55, isEncrypted); + if (thumb != null && size != null) { + thumb = null; + } + document = new TLRPC.TL_document(); + document.thumb = size; + if (document.thumb == null) { + document.thumb = new TLRPC.TL_photoSizeEmpty(); + document.thumb.type = "s"; + } else { + document.thumb.type = "s"; + } + document.mime_type = "video/mp4"; + UserConfig.saveConfig(false); + TLRPC.TL_documentAttributeVideo attributeVideo; + if (isEncrypted) { + if (enryptedLayer >= 66) { + attributeVideo = new TLRPC.TL_documentAttributeVideo(); + } else { + attributeVideo = new TLRPC.TL_documentAttributeVideo_layer65(); + } + } else { + attributeVideo = new TLRPC.TL_documentAttributeVideo(); + } + document.attributes.add(attributeVideo); + if (info.videoEditedInfo != null && info.videoEditedInfo.needConvert()) { + if (info.videoEditedInfo.muted) { + document.attributes.add(new TLRPC.TL_documentAttributeAnimated()); + fillVideoAttribute(info.path, attributeVideo, info.videoEditedInfo); + info.videoEditedInfo.originalWidth = attributeVideo.w; + info.videoEditedInfo.originalHeight = attributeVideo.h; + attributeVideo.w = info.videoEditedInfo.resultWidth; + attributeVideo.h = info.videoEditedInfo.resultHeight; + } else { + attributeVideo.duration = (int) (info.videoEditedInfo.estimatedDuration / 1000); + if (info.videoEditedInfo.rotationValue == 90 || info.videoEditedInfo.rotationValue == 270) { + attributeVideo.w = info.videoEditedInfo.resultHeight; + attributeVideo.h = info.videoEditedInfo.resultWidth; + } else { + attributeVideo.w = info.videoEditedInfo.resultWidth; + attributeVideo.h = info.videoEditedInfo.resultHeight; + } + } + document.size = (int) info.videoEditedInfo.estimatedSize; + String fileName = Integer.MIN_VALUE + "_" + UserConfig.lastLocalId + ".mp4"; + UserConfig.lastLocalId--; + File cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), fileName); + UserConfig.saveConfig(false); + path = cacheFile.getAbsolutePath(); + } else { + if (temp.exists()) { + document.size = (int) temp.length(); + } + fillVideoAttribute(info.path, attributeVideo, null); + } + } + final TLRPC.TL_document videoFinal = document; + final String originalPathFinal = originalPath; + final String finalPath = path; + final HashMap params = new HashMap<>(); + final Bitmap thumbFinal = thumb; + final String thumbKeyFinal = thumbKey; + videoFinal.caption = info.caption != null ? info.caption : ""; + if (originalPath != null) { + params.put("originalPath", originalPath); + } + if (!muted && groupPhotos) { + photosCount++; + params.put("groupId", "" + groupId); + if (photosCount == 10 || a == count -1) { + params.put("final", "1"); + lastGroupId = 0; + } + } + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (thumbFinal != null && thumbKeyFinal != null) { + ImageLoader.getInstance().putImageToCache(new BitmapDrawable(thumbFinal), thumbKeyFinal); + } + SendMessagesHelper.getInstance().sendMessage(videoFinal, info.videoEditedInfo, finalPath, dialog_id, reply_to_msg, null, params, info.ttl); + } + }); + } else { + prepareSendingDocumentInternal(info.path, info.path, null, null, dialog_id, reply_to_msg, info.caption); + } + } else { + String originalPath = info.path; + String tempPath = info.path; + if (tempPath == null && info.uri != null) { + tempPath = AndroidUtilities.getPath(info.uri); + originalPath = info.uri.toString(); + } + + boolean isDocument = false; + if (forceDocument) { + isDocument = true; + extension = FileLoader.getFileExtension(new File(tempPath)); + } else if (tempPath != null && (tempPath.endsWith(".gif") || tempPath.endsWith(".webp"))) { + if (tempPath.endsWith(".gif")) { + extension = "gif"; + } else { + extension = "webp"; + } + isDocument = true; + } else if (tempPath == null && info.uri != null) { + if (MediaController.isGif(info.uri)) { + isDocument = true; + originalPath = info.uri.toString(); + tempPath = MediaController.copyFileToCache(info.uri, "gif"); + extension = "gif"; + } else if (MediaController.isWebp(info.uri)) { + isDocument = true; + originalPath = info.uri.toString(); + tempPath = MediaController.copyFileToCache(info.uri, "webp"); + extension = "webp"; + } + } + + if (isDocument) { + if (sendAsDocuments == null) { + sendAsDocuments = new ArrayList<>(); + sendAsDocumentsOriginal = new ArrayList<>(); + sendAsDocumentsCaptions = new ArrayList<>(); + } + sendAsDocuments.add(tempPath); + sendAsDocumentsOriginal.add(originalPath); + sendAsDocumentsCaptions.add(info.caption); + } else { + if (tempPath != null) { + File temp = new File(tempPath); + originalPath += temp.length() + "_" + temp.lastModified(); + } else { + originalPath = null; + } + TLRPC.TL_photo photo = null; + if (workers != null) { + MediaSendPrepareWorker worker = workers.get(info); + photo = worker.photo; + if (photo == null) { + try { + worker.sync.await(); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + photo = worker.photo; + } + } else { + if (!isEncrypted && info.ttl == 0) { + photo = (TLRPC.TL_photo) MessagesStorage.getInstance().getSentFile(originalPath, !isEncrypted ? 0 : 3); + if (photo == null && info.uri != null) { + photo = (TLRPC.TL_photo) MessagesStorage.getInstance().getSentFile(AndroidUtilities.getPath(info.uri), !isEncrypted ? 0 : 3); + } + } + if (photo == null) { + photo = SendMessagesHelper.getInstance().generatePhotoSizes(info.path, info.uri); + } + } + if (photo != null) { + final TLRPC.TL_photo photoFinal = photo; + final HashMap params = new HashMap<>(); + photo.caption = info.caption; + if (photo.has_stickers = info.masks != null && !info.masks.isEmpty()) { + SerializedData serializedData = new SerializedData(4 + info.masks.size() * 20); + serializedData.writeInt32(info.masks.size()); + for (int b = 0; b < info.masks.size(); b++) { + info.masks.get(b).serializeToStream(serializedData); + } + params.put("masks", Utilities.bytesToHex(serializedData.toByteArray())); + } + if (originalPath != null) { + params.put("originalPath", originalPath); + } + if (groupPhotos) { + photosCount++; + params.put("groupId", "" + groupId); + if (photosCount == 10 || a == count - 1) { + params.put("final", "1"); + lastGroupId = 0; + } + } + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + SendMessagesHelper.getInstance().sendMessage(photoFinal, null, dialog_id, reply_to_msg, null, params, info.ttl); + } + }); + } else { + if (sendAsDocuments == null) { + sendAsDocuments = new ArrayList<>(); + sendAsDocumentsOriginal = new ArrayList<>(); + sendAsDocumentsCaptions = new ArrayList<>(); + } + sendAsDocuments.add(tempPath); + sendAsDocumentsOriginal.add(originalPath); + sendAsDocumentsCaptions.add(info.caption); + } } - sendAsDocuments.add(tempPath); - sendAsDocumentsOriginal.add(originalPath); - sendAsDocumentsCaptions.add(captions != null ? captions.get(a) : null); } } } + if (lastGroupId != 0) { + final long lastGroupIdFinal = lastGroupId; + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + SendMessagesHelper instance = getInstance(); + ArrayList arrayList = instance.delayedMessages.get("group_" + lastGroupIdFinal); + if (arrayList != null && !arrayList.isEmpty()) { + + DelayedMessage message = arrayList.get(0); + + MessageObject prevMessage = message.messageObjects.get(message.messageObjects.size() - 1); + message.finalGroupMessage = prevMessage.getId(); + prevMessage.messageOwner.params.put("final", "1"); + + TLRPC.TL_messages_messages messagesRes = new TLRPC.TL_messages_messages(); + messagesRes.messages.add(prevMessage.messageOwner); + MessagesStorage.getInstance().putMessages(messagesRes, message.peer, -2, 0, false); + instance.sendReadyToSendGroup(message, true, true); + } + } + }); + } if (inputContent != null) { inputContent.releasePermission(); } @@ -3751,8 +4740,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter prepareSendingDocumentInternal(sendAsDocuments.get(a), sendAsDocumentsOriginal.get(a), null, extension, dialog_id, reply_to_msg, sendAsDocumentsCaptions.get(a)); } } + FileLog.d("total send time = " + (System.currentTimeMillis() - beginTime)); } - }).start(); + }); } private static void fillVideoAttribute(String videoPath, TLRPC.TL_documentAttributeVideo attributeVideo, VideoEditedInfo videoEditedInfo) { @@ -3868,7 +4858,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter originalPath += temp.length() + "_" + temp.lastModified(); if (videoEditedInfo != null) { if (!isRound) { - originalPath += duration + "_" + videoEditedInfo.startTime + "_" + videoEditedInfo.endTime; + originalPath += duration + "_" + videoEditedInfo.startTime + "_" + videoEditedInfo.endTime + (videoEditedInfo.muted ? "_m" : ""); if (videoEditedInfo.resultWidth == videoEditedInfo.originalWidth) { originalPath += "_" + videoEditedInfo.resultWidth; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/SmsListener.java b/TMessagesProj/src/main/java/org/telegram/messenger/SmsListener.java index 4a54faa9e..73446d2b1 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/SmsListener.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/SmsListener.java @@ -11,7 +11,6 @@ package org.telegram.messenger; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; import android.os.Bundle; import android.telephony.SmsMessage; @@ -20,11 +19,10 @@ 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")) { + boolean outgoing = false; + if (intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED") || (outgoing = intent.getAction().equals("android.provider.Telephony.NEW_OUTGOING_SMS"))) { if (!AndroidUtilities.isWaitingForSms()) { return; } @@ -35,30 +33,33 @@ public class SmsListener extends BroadcastReceiver { 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]); + 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(e); - } + if (outgoing) { - } catch(Throwable e) { + } else { + 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(e); + } + } + } catch (Throwable e) { FileLog.e(e); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/StatsController.java b/TMessagesProj/src/main/java/org/telegram/messenger/StatsController.java index f247f290c..f01ca0632 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/StatsController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/StatsController.java @@ -165,7 +165,7 @@ public class StatsController { editor.putLong("resetStatsDate" + networkType, resetStatsDate[networkType]); } try { - editor.commit(); + editor.apply(); } catch (Exception e) { FileLog.e(e); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/StopLiveLocationReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/StopLiveLocationReceiver.java new file mode 100644 index 000000000..8b3384562 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/StopLiveLocationReceiver.java @@ -0,0 +1,21 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.messenger; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +public class StopLiveLocationReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + LocationController.getInstance().removeAllLocationSharings(); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/UserConfig.java b/TMessagesProj/src/main/java/org/telegram/messenger/UserConfig.java index d91d2a6cf..8707eb452 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/UserConfig.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/UserConfig.java @@ -27,7 +27,7 @@ public class UserConfig { public static int lastSendMessageId = -210000; public static int lastLocalId = -210000; public static int lastBroadcastId = -1; - public static String contactsHash = ""; + public static int contactsSavedCount; public static boolean blockedUsersLoaded; private final static Object sync = new Object(); public static boolean saveIncomingPhotos; @@ -48,6 +48,11 @@ public class UserConfig { public static boolean notificationsConverted = true; public static boolean pinnedDialogsLoaded = true; public static TLRPC.TL_account_tmpPassword tmpPassword; + public static int ratingLoadTime; + public static int botRatingLoadTime; + public static boolean contactsReimported; + + private static boolean configLoaded; public static int migrateOffsetId = -1; public static int migrateOffsetDate = -1; @@ -86,7 +91,7 @@ public class UserConfig { editor.putString("pushString2", pushString); editor.putInt("lastSendMessageId", lastSendMessageId); editor.putInt("lastLocalId", lastLocalId); - editor.putString("contactsHash", contactsHash); + editor.putInt("contactsSavedCount", contactsSavedCount); editor.putBoolean("saveIncomingPhotos", saveIncomingPhotos); editor.putInt("lastBroadcastId", lastBroadcastId); editor.putBoolean("blockedUsersLoaded", blockedUsersLoaded); @@ -105,6 +110,9 @@ public class UserConfig { editor.putBoolean("notificationsConverted", notificationsConverted); editor.putBoolean("allowScreenCapture", allowScreenCapture); editor.putBoolean("pinnedDialogsLoaded", pinnedDialogsLoaded); + editor.putInt("ratingLoadTime", ratingLoadTime); + editor.putInt("botRatingLoadTime", botRatingLoadTime); + editor.putBoolean("contactsReimported", contactsReimported); editor.putInt("3migrateOffsetId", migrateOffsetId); if (migrateOffsetId != -1) { @@ -182,6 +190,9 @@ public class UserConfig { public static void loadConfig() { synchronized (sync) { + if (configLoaded) { + return; + } final File configFile = new File(ApplicationLoader.getFilesDirFixed(), "user.dat"); if (configFile.exists()) { try { @@ -197,7 +208,7 @@ public class UserConfig { pushString = data.readString(false); lastSendMessageId = data.readInt32(false); lastLocalId = data.readInt32(false); - contactsHash = data.readString(false); + data.readString(false); data.readString(false); saveIncomingPhotos = data.readBool(false); MessagesStorage.lastQtsValue = data.readInt32(false); @@ -222,7 +233,7 @@ public class UserConfig { pushString = preferences.getString("pushString2", ""); lastSendMessageId = preferences.getInt("lastSendMessageId", -210000); lastLocalId = preferences.getInt("lastLocalId", -210000); - contactsHash = preferences.getString("contactsHash", ""); + contactsSavedCount = preferences.getInt("contactsHash", 0); saveIncomingPhotos = preferences.getBoolean("saveIncomingPhotos", false); } if (lastLocalId > -210000) { @@ -247,7 +258,7 @@ public class UserConfig { pushString = preferences.getString("pushString2", ""); lastSendMessageId = preferences.getInt("lastSendMessageId", -210000); lastLocalId = preferences.getInt("lastLocalId", -210000); - contactsHash = preferences.getString("contactsHash", ""); + contactsSavedCount = preferences.getInt("contactsSavedCount", 0); saveIncomingPhotos = preferences.getBoolean("saveIncomingPhotos", false); lastBroadcastId = preferences.getInt("lastBroadcastId", -1); blockedUsersLoaded = preferences.getBoolean("blockedUsersLoaded", false); @@ -265,6 +276,9 @@ public class UserConfig { notificationsConverted = preferences.getBoolean("notificationsConverted", false); allowScreenCapture = preferences.getBoolean("allowScreenCapture", false); pinnedDialogsLoaded = preferences.getBoolean("pinnedDialogsLoaded", false); + contactsReimported = preferences.getBoolean("contactsReimported", false); + ratingLoadTime = preferences.getInt("ratingLoadTime", 0); + botRatingLoadTime = preferences.getInt("botRatingLoadTime", 0); if (UserConfig.passcodeHash.length() > 0 && lastPauseTime == 0) { lastPauseTime = (int) (System.currentTimeMillis() / 1000 - 60 * 10); @@ -278,12 +292,6 @@ public class UserConfig { migrateOffsetChannelId = preferences.getInt("3migrateOffsetChannelId", 0); migrateOffsetAccess = preferences.getLong("3migrateOffsetAccess", 0); } -// migrateOffsetId = 0; -// migrateOffsetDate = 0; -// migrateOffsetUserId = 0; -// migrateOffsetChatId = 0; -// migrateOffsetChannelId = 0; -// migrateOffsetAccess = 0; dialogsLoadOffsetId = preferences.getInt("2dialogsLoadOffsetId", -1); totalDialogsLoadCount = preferences.getInt("2totalDialogsLoadCount", 0); @@ -293,14 +301,6 @@ public class UserConfig { dialogsLoadOffsetChannelId = preferences.getInt("2dialogsLoadOffsetChannelId", -1); dialogsLoadOffsetAccess = preferences.getLong("2dialogsLoadOffsetAccess", -1); -// dialogsLoadOffsetId = -1; -// totalDialogsLoadCount = 0; -// dialogsLoadOffsetDate = -1; -// dialogsLoadOffsetUserId = -1; -// dialogsLoadOffsetChatId = -1; -// dialogsLoadOffsetChannelId = -1; -// dialogsLoadOffsetAccess = -1; - String string = preferences.getString("tmpPassword", null); if (string != null) { byte[] bytes = Base64.decode(string, Base64.DEFAULT); @@ -404,6 +404,7 @@ public class UserConfig { saveConfig(false); } } + configLoaded = true; } } @@ -445,7 +446,7 @@ public class UserConfig { public static void clearConfig() { currentUser = null; registeredForPush = false; - contactsHash = ""; + contactsSavedCount = 0; lastSendMessageId = -210000; lastBroadcastId = -1; saveIncomingPhotos = false; @@ -463,6 +464,8 @@ public class UserConfig { dialogsLoadOffsetChatId = 0; dialogsLoadOffsetChannelId = 0; dialogsLoadOffsetAccess = 0; + ratingLoadTime = 0; + botRatingLoadTime = 0; appLocked = false; passcodeType = 0; passcodeHash = ""; @@ -472,6 +475,7 @@ public class UserConfig { useFingerprint = true; draftsLoaded = true; notificationsConverted = true; + contactsReimported = true; isWaitingForPasscodeEnter = false; allowScreenCapture = false; pinnedDialogsLoaded = false; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/UserObject.java b/TMessagesProj/src/main/java/org/telegram/messenger/UserObject.java index 5f5afda25..07e689e6e 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/UserObject.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/UserObject.java @@ -20,11 +20,11 @@ public class UserObject { } public static boolean isContact(TLRPC.User user) { - return user instanceof TLRPC.TL_userContact_old2 || user.contact || user.mutual_contact; + return user != null && (user instanceof TLRPC.TL_userContact_old2 || user.contact || user.mutual_contact); } public static boolean isUserSelf(TLRPC.User user) { - return user instanceof TLRPC.TL_userSelf_old3 || user.self; + return user != null && (user instanceof TLRPC.TL_userSelf_old3 || user.self); } public static String getUserName(TLRPC.User user) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/Utilities.java b/TMessagesProj/src/main/java/org/telegram/messenger/Utilities.java index 628792253..54fc07fb1 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/Utilities.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/Utilities.java @@ -183,7 +183,7 @@ public class Utilities { } public static boolean arraysEquals(byte[] arr1, int offset1, byte[] arr2, int offset2) { - if (arr1 == null || arr2 == null || offset1 < 0 || offset2 < 0 || arr1.length - offset1 != arr2.length - offset2 || arr1.length - offset1 < 0 || arr2.length - offset2 < 0) { + if (arr1 == null || arr2 == null || offset1 < 0 || offset2 < 0 || arr1.length - offset1 > arr2.length - offset2 || arr1.length - offset1 < 0 || arr2.length - offset2 < 0) { return false; } boolean result = true; @@ -232,6 +232,10 @@ public class Utilities { return computeSHA1(convertme, 0, convertme.length); } + public static byte[] computeSHA256(byte[] convertme) { + return computeSHA256(convertme, 0, convertme.length); + } + public static byte[] computeSHA256(byte[] convertme, int offset, int len) { try { MessageDigest md = MessageDigest.getInstance("SHA-256"); @@ -240,7 +244,26 @@ public class Utilities { } catch (Exception e) { FileLog.e(e); } - return null; + return new byte[32]; + } + + public static byte[] computeSHA256(byte[] b1, int o1, int l1, ByteBuffer b2, int o2, int l2) { + int oldp = b2.position(); + int oldl = b2.limit(); + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(b1, o1, l1); + b2.position(o2); + b2.limit(l2); + md.update(b2); + return md.digest(); + } catch (Exception e) { + FileLog.e(e); + } finally { + b2.limit(oldl); + b2.position(oldp); + } + return new byte[32]; } public static long bytesToLong(byte[] bytes) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/WearDataLayerListenerService.java b/TMessagesProj/src/main/java/org/telegram/messenger/WearDataLayerListenerService.java index 95efabb48..0b7e0bbc2 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/WearDataLayerListenerService.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/WearDataLayerListenerService.java @@ -7,6 +7,7 @@ import com.google.android.gms.wearable.Channel; import com.google.android.gms.wearable.Wearable; import com.google.android.gms.wearable.WearableListenerService; +import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLRPC; import java.io.BufferedOutputStream; @@ -112,6 +113,7 @@ public class WearDataLayerListenerService extends WearableListenerService { out.flush(); out.close(); } else if ("/waitForAuthCode".equals(path)) { + ConnectionsManager.getInstance().setAppPaused(false, false); final String[] code = {null}; final CyclicBarrier barrier = new CyclicBarrier(2); final NotificationCenter.NotificationCenterDelegate listener = new NotificationCenter.NotificationCenterDelegate() { @@ -161,6 +163,7 @@ public class WearDataLayerListenerService extends WearableListenerService { out.writeUTF(""); out.flush(); out.close(); + ConnectionsManager.getInstance().setAppPaused(true, false); } } catch (Exception x) { FileLog.e("error processing wear request", x); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/audioinfo/mp3/ID3v2TagBody.java b/TMessagesProj/src/main/java/org/telegram/messenger/audioinfo/mp3/ID3v2TagBody.java index c6c52f2d6..e02662f34 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/audioinfo/mp3/ID3v2TagBody.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/audioinfo/mp3/ID3v2TagBody.java @@ -55,12 +55,13 @@ public class ID3v2TagBody { if (frameHeader.isUnsynchronization()) { byte[] bytes = data.readFully(frameHeader.getBodySize()); boolean ff = false; + byte ffByte = (byte) 0xFF; int len = 0; for (byte b : bytes) { if (!ff || b != 0) { bytes[len++] = b; } - ff = (b == 0xFF); + ff = (b == ffByte); } dataLength = len; input = new ByteArrayInputStream(bytes, 0, len); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/audioinfo/mp3/ID3v2TagHeader.java b/TMessagesProj/src/main/java/org/telegram/messenger/audioinfo/mp3/ID3v2TagHeader.java index 79adf8d3f..7b589df64 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/audioinfo/mp3/ID3v2TagHeader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/audioinfo/mp3/ID3v2TagHeader.java @@ -139,12 +139,13 @@ public class ID3v2TagHeader { if (version < 4 && unsynchronization) { byte[] bytes = new ID3v2DataInput(input).readFully(totalTagSize - headerSize); boolean ff = false; + byte ffByte = (byte) 0xFF; int len = 0; for (byte b : bytes) { if (!ff || b != 0) { bytes[len++] = b; } - ff = (b == 0xFF); + ff = (b == ffByte); } return new ID3v2TagBody(new ByteArrayInputStream(bytes, 0, len), headerSize, len, this); } else { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/browser/Browser.java b/TMessagesProj/src/main/java/org/telegram/messenger/browser/Browser.java index 296ae360c..8c80a8912 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/browser/Browser.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/browser/Browser.java @@ -12,17 +12,22 @@ import android.app.Activity; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; +import android.content.pm.ResolveInfo; import android.graphics.BitmapFactory; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import android.text.TextUtils; +import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.BuildVars; +import org.telegram.messenger.CustomTabsCopyReceiver; import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MediaController; +import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; import org.telegram.messenger.ShareBroadcastReceiver; import org.telegram.messenger.support.customtabs.CustomTabsCallback; @@ -33,10 +38,16 @@ import org.telegram.messenger.support.customtabs.CustomTabsSession; import org.telegram.messenger.support.customtabsclient.shared.CustomTabsHelper; import org.telegram.messenger.support.customtabsclient.shared.ServiceConnection; import org.telegram.messenger.support.customtabsclient.shared.ServiceConnectionCallback; +import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.RequestDelegate; +import org.telegram.tgnet.TLObject; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.LaunchActivity; import java.lang.ref.WeakReference; +import java.util.List; public class Browser { @@ -66,9 +77,6 @@ public class Browser { } public static void bindCustomTabsService(Activity activity) { - if (Build.VERSION.SDK_INT < 15) { - return; - } Activity currentActivity = currentCustomTabsActivity == null ? null : currentCustomTabsActivity.get(); if (currentActivity != null && currentActivity != activity) { unbindCustomTabsService(currentActivity); @@ -113,7 +121,7 @@ public class Browser { } public static void unbindCustomTabsService(Activity activity) { - if (Build.VERSION.SDK_INT < 15 || customTabsServiceConnection == null) { + if (customTabsServiceConnection == null) { return; } Activity currentActivity = currentCustomTabsActivity == null ? null : currentCustomTabsActivity.get(); @@ -122,8 +130,8 @@ public class Browser { } try { activity.unbindService(customTabsServiceConnection); - } catch (Exception e) { - FileLog.e(e); + } catch (Exception ignore) { + } customTabsClient = null; customTabsSession = null; @@ -155,23 +163,148 @@ public class Browser { } public static void openUrl(Context context, Uri uri, boolean allowCustom) { + openUrl(context, uri, allowCustom, true); + } + + public static void openUrl(final Context context, final Uri uri, final boolean allowCustom, boolean tryTelegraph) { if (context == null || uri == null) { return; } - boolean internalUri = isInternalUri(uri); + boolean forceBrowser[] = new boolean[] {false}; + boolean internalUri = isInternalUri(uri, forceBrowser); + if (tryTelegraph) { + try { + String host = uri.getHost().toLowerCase(); + if (host.equals("telegra.ph") || uri.toString().contains("telegram.org/faq")) { + final AlertDialog progressDialog[] = new AlertDialog[] {new AlertDialog(context, 1)}; + + TLRPC.TL_messages_getWebPagePreview req = new TLRPC.TL_messages_getWebPagePreview(); + req.message = uri.toString(); + final int reqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + try { + progressDialog[0].dismiss(); + } catch (Throwable ignore) { + + } + progressDialog[0] = null; + + boolean ok = false; + if (response instanceof TLRPC.TL_messageMediaWebPage) { + TLRPC.TL_messageMediaWebPage webPage = (TLRPC.TL_messageMediaWebPage) response; + if (webPage.webpage instanceof TLRPC.TL_webPage && webPage.webpage.cached_page != null) { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.openArticle, webPage.webpage, uri.toString()); + ok = true; + } + } + if (!ok) { + openUrl(context, uri, allowCustom, false); + } + } + }); + } + }); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (progressDialog[0] == null) { + return; + } + try { + progressDialog[0].setMessage(LocaleController.getString("Loading", R.string.Loading)); + progressDialog[0].setCanceledOnTouchOutside(false); + progressDialog[0].setCancelable(false); + progressDialog[0].setButton(DialogInterface.BUTTON_NEGATIVE, LocaleController.getString("Cancel", R.string.Cancel), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + ConnectionsManager.getInstance().cancelRequest(reqId, true); + try { + dialog.dismiss(); + } catch (Exception e) { + FileLog.e(e); + } + } + }); + progressDialog[0].show(); + } catch (Exception ignore) { + + } + } + }, 1000); + return; + } + } catch (Exception ignore) { + + } + } try { String scheme = uri.getScheme() != null ? uri.getScheme().toLowerCase() : ""; - if (Build.VERSION.SDK_INT >= 15 && allowCustom && MediaController.getInstance().canCustomTabs() && !internalUri && !scheme.equals("tel")) { - Intent share = new Intent(ApplicationLoader.applicationContext, ShareBroadcastReceiver.class); - share.setAction(Intent.ACTION_SEND); + if (allowCustom && MediaController.getInstance().canCustomTabs() && !internalUri && !scheme.equals("tel")) { + String browserPackageNames[] = null; + try { + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.google.com")); + List list = context.getPackageManager().queryIntentActivities(browserIntent, 0); + if (list != null && !list.isEmpty()) { + browserPackageNames = new String[list.size()]; + for (int a = 0; a < list.size(); a++) { + browserPackageNames[a] = list.get(a).activityInfo.packageName; + FileLog.d("default browser name = " + browserPackageNames[a]); + } + } + } catch (Exception ignore) { - CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(getSession()); - builder.setToolbarColor(Theme.getColor(Theme.key_actionBarDefault)); - builder.setShowTitle(true); - builder.setActionButton(BitmapFactory.decodeResource(context.getResources(), R.drawable.abc_ic_menu_share_mtrl_alpha), LocaleController.getString("ShareFile", R.string.ShareFile), PendingIntent.getBroadcast(ApplicationLoader.applicationContext, 0, share, 0), false); - CustomTabsIntent intent = builder.build(); - intent.launchUrl((Activity) context, uri); - return; + } + + List allActivities = null; + try { + Intent viewIntent = new Intent(Intent.ACTION_VIEW, uri); + allActivities = context.getPackageManager().queryIntentActivities(viewIntent, 0); + if (browserPackageNames != null) { + for (int a = 0; a < allActivities.size(); a++) { + for (int b = 0; b < browserPackageNames.length; b++) { + if (browserPackageNames[b].equals(allActivities.get(a).activityInfo.packageName)) { + allActivities.remove(a); + a--; + break; + } + } + } + } else { + for (int a = 0; a < allActivities.size(); a++) { + if (allActivities.get(a).activityInfo.packageName.toLowerCase().contains("browser") || allActivities.get(a).activityInfo.packageName.toLowerCase().contains("chrome")) { + allActivities.remove(a); + a--; + } + } + } + if (BuildVars.DEBUG_VERSION) { + for (int a = 0; a < allActivities.size(); a++) { + FileLog.d("device has " + allActivities.get(a).activityInfo.packageName + " to open " + uri.toString()); + } + } + } catch (Exception ignore) { + + } + + if (forceBrowser[0] || allActivities == null || allActivities.isEmpty()) { + Intent share = new Intent(ApplicationLoader.applicationContext, ShareBroadcastReceiver.class); + share.setAction(Intent.ACTION_SEND); + + PendingIntent copy = PendingIntent.getBroadcast(ApplicationLoader.applicationContext, 0, new Intent(ApplicationLoader.applicationContext, CustomTabsCopyReceiver.class), PendingIntent.FLAG_UPDATE_CURRENT); + + CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(getSession()); + builder.addMenuItem(LocaleController.getString("CopyLink", R.string.CopyLink), copy); + builder.setToolbarColor(Theme.getColor(Theme.key_actionBarDefault)); + builder.setShowTitle(true); + builder.setActionButton(BitmapFactory.decodeResource(context.getResources(), R.drawable.abc_ic_menu_share_mtrl_alpha), LocaleController.getString("ShareFile", R.string.ShareFile), PendingIntent.getBroadcast(ApplicationLoader.applicationContext, 0, share, 0), false); + CustomTabsIntent intent = builder.build(); + intent.launchUrl(context, uri); + return; + } } } catch (Exception e) { FileLog.e(e); @@ -182,6 +315,7 @@ public class Browser { ComponentName componentName = new ComponentName(context.getPackageName(), LaunchActivity.class.getName()); intent.setComponent(componentName); } + intent.putExtra(android.provider.Browser.EXTRA_CREATE_NEW_TAB, true); intent.putExtra(android.provider.Browser.EXTRA_APPLICATION_ID, context.getPackageName()); context.startActivity(intent); } catch (Exception e) { @@ -189,19 +323,38 @@ public class Browser { } } - public static boolean isInternalUrl(String url) { - return isInternalUri(Uri.parse(url)); + public static boolean isInternalUrl(String url, boolean forceBrowser[]) { + return isInternalUri(Uri.parse(url), forceBrowser); } - public static boolean isInternalUri(Uri uri) { + public static boolean isInternalUri(Uri uri, boolean forceBrowser[]) { String host = uri.getHost(); host = host != null ? host.toLowerCase() : ""; if ("tg".equals(uri.getScheme())) { return true; - } else if ("telegram.me".equals(host) || "t.me".equals(host) || "telegram.dog".equals(host) || "telesco.pe".equals(host)) { + } else if ("telegram.dog".equals(host)) { String path = uri.getPath(); if (path != null && path.length() > 1) { - return !path.toLowerCase().equals("/iv"); + path = path.substring(1).toLowerCase(); + if (path.startsWith("blog") || path.equals("iv") || path.startsWith("faq") || path.equals("apps")) { + if (forceBrowser != null) { + forceBrowser[0] = true; + } + return false; + } + return true; + } + } else if ("telegram.me".equals(host) || "t.me".equals(host) || "telesco.pe".equals(host)) { + String path = uri.getPath(); + if (path != null && path.length() > 1) { + path = path.substring(1).toLowerCase(); + if (path.equals("iv")) { + if (forceBrowser != null) { + forceBrowser[0] = true; + } + return false; + } + return true; } } return false; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/BaseRenderer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/BaseRenderer.java index 7d8aeed57..41f194c0c 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/BaseRenderer.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/BaseRenderer.java @@ -96,7 +96,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { this.stream = stream; readEndOfStream = false; streamOffsetUs = offsetUs; - onStreamChanged(formats); + onStreamChanged(formats, offsetUs); } @Override @@ -142,9 +142,9 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { public final void disable() { Assertions.checkState(state == STATE_ENABLED); state = STATE_DISABLED; - onDisabled(); stream = null; streamIsFinal = false; + onDisabled(); } // RendererCapabilities implementation. @@ -183,16 +183,19 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { * The default implementation is a no-op. * * @param formats The enabled formats. + * @param offsetUs The offset that will be added to the timestamps of buffers read via + * {@link #readSource(FormatHolder, DecoderInputBuffer, boolean)} so that decoder input + * buffers have monotonically increasing timestamps. * @throws ExoPlaybackException If an error occurs. */ - protected void onStreamChanged(Format[] formats) throws ExoPlaybackException { + protected void onStreamChanged(Format[] formats, long offsetUs) throws ExoPlaybackException { // Do nothing. } /** * Called when the position is reset. This occurs when the renderer is enabled after - * {@link #onStreamChanged(Format[])} has been called, and also when a position discontinuity - * is encountered. + * {@link #onStreamChanged(Format[], long)} has been called, and also when a position + * discontinuity is encountered. *

* After a position reset, the renderer's {@link SampleStream} is guaranteed to provide samples * starting from a key frame. @@ -300,8 +303,6 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { /** * Returns whether the upstream source is ready. - * - * @return Whether the source is ready. */ protected final boolean isSourceReady() { return readEndOfStream ? streamIsFinal : stream.isReady(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/C.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/C.java index 91236b59c..089d739b5 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/C.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/C.java @@ -31,6 +31,7 @@ import java.util.UUID; /** * Defines constants used by the library. */ +@SuppressWarnings("InlinedApi") public final class C { private C() {} @@ -83,12 +84,12 @@ public final class C { public static final String UTF16_NAME = "UTF-16"; /** - * * The name of the serif font family. + * The name of the serif font family. */ public static final String SERIF_NAME = "serif"; /** - * * The name of the sans-serif font family. + * The name of the sans-serif font family. */ public static final String SANS_SERIF_NAME = "sans-serif"; @@ -101,24 +102,20 @@ public final class C { /** * @see MediaCodec#CRYPTO_MODE_UNENCRYPTED */ - @SuppressWarnings("InlinedApi") public static final int CRYPTO_MODE_UNENCRYPTED = MediaCodec.CRYPTO_MODE_UNENCRYPTED; /** * @see MediaCodec#CRYPTO_MODE_AES_CTR */ - @SuppressWarnings("InlinedApi") public static final int CRYPTO_MODE_AES_CTR = MediaCodec.CRYPTO_MODE_AES_CTR; /** * @see MediaCodec#CRYPTO_MODE_AES_CBC */ - @SuppressWarnings("InlinedApi") public static final int CRYPTO_MODE_AES_CBC = MediaCodec.CRYPTO_MODE_AES_CBC; /** * Represents an unset {@link android.media.AudioTrack} session identifier. Equal to * {@link AudioManager#AUDIO_SESSION_ID_GENERATE}. */ - @SuppressWarnings("InlinedApi") public static final int AUDIO_SESSION_ID_UNSET = AudioManager.AUDIO_SESSION_ID_GENERATE; /** @@ -160,28 +157,24 @@ public final class C { /** * @see AudioFormat#ENCODING_AC3 */ - @SuppressWarnings("InlinedApi") public static final int ENCODING_AC3 = AudioFormat.ENCODING_AC3; /** * @see AudioFormat#ENCODING_E_AC3 */ - @SuppressWarnings("InlinedApi") public static final int ENCODING_E_AC3 = AudioFormat.ENCODING_E_AC3; /** * @see AudioFormat#ENCODING_DTS */ - @SuppressWarnings("InlinedApi") public static final int ENCODING_DTS = AudioFormat.ENCODING_DTS; /** * @see AudioFormat#ENCODING_DTS_HD */ - @SuppressWarnings("InlinedApi") public static final int ENCODING_DTS_HD = AudioFormat.ENCODING_DTS_HD; /** * @see AudioFormat#CHANNEL_OUT_7POINT1_SURROUND */ - @SuppressWarnings({"InlinedApi", "deprecation"}) + @SuppressWarnings("deprecation") public static final int CHANNEL_OUT_7POINT1_SURROUND = Util.SDK_INT < 23 ? AudioFormat.CHANNEL_OUT_7POINT1 : AudioFormat.CHANNEL_OUT_7POINT1_SURROUND; @@ -189,13 +182,17 @@ public final class C { * Stream types for an {@link android.media.AudioTrack}. */ @Retention(RetentionPolicy.SOURCE) - @IntDef({STREAM_TYPE_ALARM, STREAM_TYPE_MUSIC, STREAM_TYPE_NOTIFICATION, STREAM_TYPE_RING, - STREAM_TYPE_SYSTEM, STREAM_TYPE_VOICE_CALL}) + @IntDef({STREAM_TYPE_ALARM, STREAM_TYPE_DTMF, STREAM_TYPE_MUSIC, STREAM_TYPE_NOTIFICATION, + STREAM_TYPE_RING, STREAM_TYPE_SYSTEM, STREAM_TYPE_VOICE_CALL, STREAM_TYPE_USE_DEFAULT}) public @interface StreamType {} /** * @see AudioManager#STREAM_ALARM */ public static final int STREAM_TYPE_ALARM = AudioManager.STREAM_ALARM; + /** + * @see AudioManager#STREAM_DTMF + */ + public static final int STREAM_TYPE_DTMF = AudioManager.STREAM_DTMF; /** * @see AudioManager#STREAM_MUSIC */ @@ -216,11 +213,143 @@ public final class C { * @see AudioManager#STREAM_VOICE_CALL */ public static final int STREAM_TYPE_VOICE_CALL = AudioManager.STREAM_VOICE_CALL; + /** + * @see AudioManager#USE_DEFAULT_STREAM_TYPE + */ + public static final int STREAM_TYPE_USE_DEFAULT = AudioManager.USE_DEFAULT_STREAM_TYPE; /** * The default stream type used by audio renderers. */ public static final int STREAM_TYPE_DEFAULT = STREAM_TYPE_MUSIC; + /** + * Content types for {@link org.telegram.messenger.exoplayer2.audio.AudioAttributes}. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({CONTENT_TYPE_MOVIE, CONTENT_TYPE_MUSIC, CONTENT_TYPE_SONIFICATION, CONTENT_TYPE_SPEECH, + CONTENT_TYPE_UNKNOWN}) + public @interface AudioContentType {} + /** + * @see android.media.AudioAttributes#CONTENT_TYPE_MOVIE + */ + public static final int CONTENT_TYPE_MOVIE = android.media.AudioAttributes.CONTENT_TYPE_MOVIE; + /** + * @see android.media.AudioAttributes#CONTENT_TYPE_MUSIC + */ + public static final int CONTENT_TYPE_MUSIC = android.media.AudioAttributes.CONTENT_TYPE_MUSIC; + /** + * @see android.media.AudioAttributes#CONTENT_TYPE_SONIFICATION + */ + public static final int CONTENT_TYPE_SONIFICATION = + android.media.AudioAttributes.CONTENT_TYPE_SONIFICATION; + /** + * @see android.media.AudioAttributes#CONTENT_TYPE_SPEECH + */ + public static final int CONTENT_TYPE_SPEECH = + android.media.AudioAttributes.CONTENT_TYPE_SPEECH; + /** + * @see android.media.AudioAttributes#CONTENT_TYPE_UNKNOWN + */ + public static final int CONTENT_TYPE_UNKNOWN = + android.media.AudioAttributes.CONTENT_TYPE_UNKNOWN; + + /** + * Flags for {@link org.telegram.messenger.exoplayer2.audio.AudioAttributes}. + *

+ * Note that {@code FLAG_HW_AV_SYNC} is not available because the player takes care of setting the + * flag when tunneling is enabled via a track selector. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {FLAG_AUDIBILITY_ENFORCED}) + public @interface AudioFlags {} + /** + * @see android.media.AudioAttributes#FLAG_AUDIBILITY_ENFORCED + */ + public static final int FLAG_AUDIBILITY_ENFORCED = + android.media.AudioAttributes.FLAG_AUDIBILITY_ENFORCED; + + /** + * Usage types for {@link org.telegram.messenger.exoplayer2.audio.AudioAttributes}. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({USAGE_ALARM, USAGE_ASSISTANCE_ACCESSIBILITY, USAGE_ASSISTANCE_NAVIGATION_GUIDANCE, + USAGE_ASSISTANCE_SONIFICATION, USAGE_GAME, USAGE_MEDIA, USAGE_NOTIFICATION, + USAGE_NOTIFICATION_COMMUNICATION_DELAYED, USAGE_NOTIFICATION_COMMUNICATION_INSTANT, + USAGE_NOTIFICATION_COMMUNICATION_REQUEST, USAGE_NOTIFICATION_EVENT, + USAGE_NOTIFICATION_RINGTONE, USAGE_UNKNOWN, USAGE_VOICE_COMMUNICATION, + USAGE_VOICE_COMMUNICATION_SIGNALLING}) + public @interface AudioUsage {} + /** + * @see android.media.AudioAttributes#USAGE_ALARM + */ + public static final int USAGE_ALARM = android.media.AudioAttributes.USAGE_ALARM; + /** + * @see android.media.AudioAttributes#USAGE_ASSISTANCE_ACCESSIBILITY + */ + public static final int USAGE_ASSISTANCE_ACCESSIBILITY = + android.media.AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY; + /** + * @see android.media.AudioAttributes#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE + */ + public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = + android.media.AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE; + /** + * @see android.media.AudioAttributes#USAGE_ASSISTANCE_SONIFICATION + */ + public static final int USAGE_ASSISTANCE_SONIFICATION = + android.media.AudioAttributes.USAGE_ASSISTANCE_SONIFICATION; + /** + * @see android.media.AudioAttributes#USAGE_GAME + */ + public static final int USAGE_GAME = android.media.AudioAttributes.USAGE_GAME; + /** + * @see android.media.AudioAttributes#USAGE_MEDIA + */ + public static final int USAGE_MEDIA = android.media.AudioAttributes.USAGE_MEDIA; + /** + * @see android.media.AudioAttributes#USAGE_NOTIFICATION + */ + public static final int USAGE_NOTIFICATION = android.media.AudioAttributes.USAGE_NOTIFICATION; + /** + * @see android.media.AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_DELAYED + */ + public static final int USAGE_NOTIFICATION_COMMUNICATION_DELAYED = + android.media.AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED; + /** + * @see android.media.AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_INSTANT + */ + public static final int USAGE_NOTIFICATION_COMMUNICATION_INSTANT = + android.media.AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT; + /** + * @see android.media.AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_REQUEST + */ + public static final int USAGE_NOTIFICATION_COMMUNICATION_REQUEST = + android.media.AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST; + /** + * @see android.media.AudioAttributes#USAGE_NOTIFICATION_EVENT + */ + public static final int USAGE_NOTIFICATION_EVENT = + android.media.AudioAttributes.USAGE_NOTIFICATION_EVENT; + /** + * @see android.media.AudioAttributes#USAGE_NOTIFICATION_RINGTONE + */ + public static final int USAGE_NOTIFICATION_RINGTONE = + android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; + /** + * @see android.media.AudioAttributes#USAGE_UNKNOWN + */ + public static final int USAGE_UNKNOWN = android.media.AudioAttributes.USAGE_UNKNOWN; + /** + * @see android.media.AudioAttributes#USAGE_VOICE_COMMUNICATION + */ + public static final int USAGE_VOICE_COMMUNICATION = + android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION; + /** + * @see android.media.AudioAttributes#USAGE_VOICE_COMMUNICATION_SIGNALLING + */ + public static final int USAGE_VOICE_COMMUNICATION_SIGNALLING = + android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING; + /** * Flags which can apply to a buffer containing a media sample. */ @@ -231,12 +360,10 @@ public final class C { /** * Indicates that a buffer holds a synchronization sample. */ - @SuppressWarnings("InlinedApi") public static final int BUFFER_FLAG_KEY_FRAME = MediaCodec.BUFFER_FLAG_KEY_FRAME; /** * Flag for empty buffers that signal that the end of the stream was reached. */ - @SuppressWarnings("InlinedApi") public static final int BUFFER_FLAG_END_OF_STREAM = MediaCodec.BUFFER_FLAG_END_OF_STREAM; /** * Indicates that a buffer is (at least partially) encrypted. @@ -256,13 +383,11 @@ public final class C { /** * @see MediaCodec#VIDEO_SCALING_MODE_SCALE_TO_FIT */ - @SuppressWarnings("InlinedApi") public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT; /** * @see MediaCodec#VIDEO_SCALING_MODE_SCALE_TO_FIT */ - @SuppressWarnings("InlinedApi") public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING; /** @@ -453,6 +578,26 @@ public final class C { public static final int DEFAULT_MUXED_BUFFER_SIZE = DEFAULT_VIDEO_BUFFER_SIZE + DEFAULT_AUDIO_BUFFER_SIZE + DEFAULT_TEXT_BUFFER_SIZE; + /** + * "cenc" scheme type name as defined in ISO/IEC 23001-7:2016. + */ + public static final String CENC_TYPE_cenc = "cenc"; + + /** + * "cbc1" scheme type name as defined in ISO/IEC 23001-7:2016. + */ + public static final String CENC_TYPE_cbc1 = "cbc1"; + + /** + * "cens" scheme type name as defined in ISO/IEC 23001-7:2016. + */ + public static final String CENC_TYPE_cens = "cens"; + + /** + * "cbcs" scheme type name as defined in ISO/IEC 23001-7:2016. + */ + public static final String CENC_TYPE_cbcs = "cbcs"; + /** * The Nil UUID as defined by * RFC4122. @@ -498,16 +643,25 @@ public final class C { /** * A type of a message that can be passed to an audio {@link Renderer} via * {@link ExoPlayer#sendMessages} or {@link ExoPlayer#blockingSendMessages}. The message object - * should be one of the integer stream types in {@link C.StreamType}, and will specify the stream - * type of the underlying {@link android.media.AudioTrack}. See also - * {@link android.media.AudioTrack#AudioTrack(int, int, int, int, int, int)}. If the stream type - * is not set, audio renderers use {@link #STREAM_TYPE_DEFAULT}. + * should be an {@link org.telegram.messenger.exoplayer2.audio.AudioAttributes} instance that will + * configure the underlying audio track. If not set, the default audio attributes will be used. + * They are suitable for general media playback. *

- * Note that when the stream type changes, the AudioTrack must be reinitialized, which can - * introduce a brief gap in audio output. Note also that tracks in the same audio session must - * share the same routing, so a new audio session id will be generated. + * Setting the audio attributes during playback may introduce a short gap in audio output as the + * audio track is recreated. A new audio session id will also be generated. + *

+ * If tunneling is enabled by the track selector, the specified audio attributes will be ignored, + * but they will take effect if audio is later played without tunneling. + *

+ * If the device is running a build before platform API version 21, audio attributes cannot be set + * directly on the underlying audio track. In this case, the usage will be mapped onto an + * equivalent stream type using {@link Util#getStreamTypeForAudioUsage(int)}. + *

+ * To get audio attributes that are equivalent to a legacy stream type, pass the stream type to + * {@link Util#getAudioUsageForStreamType(int)} and use the returned {@link C.AudioUsage} to build + * an audio attributes instance. */ - public static final int MSG_SET_STREAM_TYPE = 3; + public static final int MSG_SET_AUDIO_ATTRIBUTES = 3; /** * The type of a message that can be passed to a {@link MediaCodec}-based video {@link Renderer} @@ -564,17 +718,14 @@ public final class C { /** * @see MediaFormat#COLOR_STANDARD_BT709 */ - @SuppressWarnings("InlinedApi") public static final int COLOR_SPACE_BT709 = MediaFormat.COLOR_STANDARD_BT709; /** * @see MediaFormat#COLOR_STANDARD_BT601_PAL */ - @SuppressWarnings("InlinedApi") public static final int COLOR_SPACE_BT601 = MediaFormat.COLOR_STANDARD_BT601_PAL; /** * @see MediaFormat#COLOR_STANDARD_BT2020 */ - @SuppressWarnings("InlinedApi") public static final int COLOR_SPACE_BT2020 = MediaFormat.COLOR_STANDARD_BT2020; /** @@ -586,17 +737,14 @@ public final class C { /** * @see MediaFormat#COLOR_TRANSFER_SDR_VIDEO */ - @SuppressWarnings("InlinedApi") public static final int COLOR_TRANSFER_SDR = MediaFormat.COLOR_TRANSFER_SDR_VIDEO; /** * @see MediaFormat#COLOR_TRANSFER_ST2084 */ - @SuppressWarnings("InlinedApi") public static final int COLOR_TRANSFER_ST2084 = MediaFormat.COLOR_TRANSFER_ST2084; /** * @see MediaFormat#COLOR_TRANSFER_HLG */ - @SuppressWarnings("InlinedApi") public static final int COLOR_TRANSFER_HLG = MediaFormat.COLOR_TRANSFER_HLG; /** @@ -608,12 +756,10 @@ public final class C { /** * @see MediaFormat#COLOR_RANGE_LIMITED */ - @SuppressWarnings("InlinedApi") public static final int COLOR_RANGE_LIMITED = MediaFormat.COLOR_RANGE_LIMITED; /** * @see MediaFormat#COLOR_RANGE_FULL */ - @SuppressWarnings("InlinedApi") public static final int COLOR_RANGE_FULL = MediaFormat.COLOR_RANGE_FULL; /** @@ -632,24 +778,24 @@ public final class C { /** * Converts a time in microseconds to the corresponding time in milliseconds, preserving - * {@link #TIME_UNSET} values. + * {@link #TIME_UNSET} and {@link #TIME_END_OF_SOURCE} values. * * @param timeUs The time in microseconds. * @return The corresponding time in milliseconds. */ public static long usToMs(long timeUs) { - return timeUs == TIME_UNSET ? TIME_UNSET : (timeUs / 1000); + return (timeUs == TIME_UNSET || timeUs == TIME_END_OF_SOURCE) ? timeUs : (timeUs / 1000); } /** * Converts a time in milliseconds to the corresponding time in microseconds, preserving - * {@link #TIME_UNSET} values. + * {@link #TIME_UNSET} values and {@link #TIME_END_OF_SOURCE} values. * * @param timeMs The time in milliseconds. * @return The corresponding time in microseconds. */ public static long msToUs(long timeMs) { - return timeMs == TIME_UNSET ? TIME_UNSET : (timeMs * 1000); + return (timeMs == TIME_UNSET || timeMs == TIME_END_OF_SOURCE) ? timeMs : (timeMs * 1000); } /** diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayer.java index 2b3795e21..aa96ede0a 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayer.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayer.java @@ -15,28 +15,28 @@ */ package org.telegram.messenger.exoplayer2; -import android.support.annotation.Nullable; +import android.os.Looper; import org.telegram.messenger.exoplayer2.audio.MediaCodecAudioRenderer; import org.telegram.messenger.exoplayer2.metadata.MetadataRenderer; +import org.telegram.messenger.exoplayer2.source.ClippingMediaSource; import org.telegram.messenger.exoplayer2.source.ConcatenatingMediaSource; +import org.telegram.messenger.exoplayer2.source.DynamicConcatenatingMediaSource; import org.telegram.messenger.exoplayer2.source.ExtractorMediaSource; +import org.telegram.messenger.exoplayer2.source.LoopingMediaSource; import org.telegram.messenger.exoplayer2.source.MediaSource; import org.telegram.messenger.exoplayer2.source.MergingMediaSource; import org.telegram.messenger.exoplayer2.source.SingleSampleMediaSource; -import org.telegram.messenger.exoplayer2.source.TrackGroupArray; import org.telegram.messenger.exoplayer2.text.TextRenderer; import org.telegram.messenger.exoplayer2.trackselection.DefaultTrackSelector; -import org.telegram.messenger.exoplayer2.trackselection.TrackSelectionArray; import org.telegram.messenger.exoplayer2.trackselection.TrackSelector; import org.telegram.messenger.exoplayer2.upstream.DataSource; import org.telegram.messenger.exoplayer2.video.MediaCodecVideoRenderer; /** - * An extensible media player exposing traditional high-level media player functionality, such as - * the ability to buffer media, play, pause and seek. Instances can be obtained from + * An extensible media player that plays {@link MediaSource}s. Instances can be obtained from * {@link ExoPlayerFactory}. * - *

Player composition

+ *

Player components

*

ExoPlayer is designed to make few assumptions about (and hence impose few restrictions on) the * type of the media being played, how and where it is stored, and how it is rendered. Rather than * implementing the loading and rendering of media directly, ExoPlayer implementations delegate this @@ -44,18 +44,20 @@ import org.telegram.messenger.exoplayer2.video.MediaCodecVideoRenderer; * Components common to all ExoPlayer implementations are: *

    *
  • A {@link MediaSource} that defines the media to be played, loads the media, and from - * which the loaded media can be read. A MediaSource is injected via {@link #prepare} at the start - * of playback. The library modules provide default implementations for regular media files - * ({@link ExtractorMediaSource}), DASH (DashMediaSource), SmoothStreaming (SsMediaSource) and HLS - * (HlsMediaSource), implementations for merging ({@link MergingMediaSource}) and concatenating - * ({@link ConcatenatingMediaSource}) other MediaSources, and an implementation for loading single - * samples ({@link SingleSampleMediaSource}) most often used for side-loaded subtitle and closed - * caption files.
  • + * which the loaded media can be read. A MediaSource is injected via {@link #prepare(MediaSource)} + * at the start of playback. The library modules provide default implementations for regular media + * files ({@link ExtractorMediaSource}), DASH (DashMediaSource), SmoothStreaming (SsMediaSource) + * and HLS (HlsMediaSource), an implementation for loading single media samples + * ({@link SingleSampleMediaSource}) that's most often used for side-loaded subtitle files, and + * implementations for building more complex MediaSources from simpler ones + * ({@link MergingMediaSource}, {@link ConcatenatingMediaSource}, + * {@link DynamicConcatenatingMediaSource}, {@link LoopingMediaSource} and + * {@link ClippingMediaSource}). *
  • {@link Renderer}s that render individual components of the media. The library * provides default implementations for common media types ({@link MediaCodecVideoRenderer}, * {@link MediaCodecAudioRenderer}, {@link TextRenderer} and {@link MetadataRenderer}). A Renderer - * consumes media of its corresponding type from the MediaSource being played. Renderers are - * injected when the player is created.
  • + * consumes media from the MediaSource being played. Renderers are injected when the player is + * created. *
  • A {@link TrackSelector} that selects tracks provided by the MediaSource to be * consumed by each of the available Renderers. The library provides a default implementation * ({@link DefaultTrackSelector}) suitable for most use cases. A TrackSelector is injected when @@ -68,14 +70,14 @@ import org.telegram.messenger.exoplayer2.video.MediaCodecVideoRenderer; *

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

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

    Threading model

    *

    The figure below shows ExoPlayer's threading model.

    @@ -88,7 +90,9 @@ import org.telegram.messenger.exoplayer2.video.MediaCodecVideoRenderer; * thread. The application's main thread is ideal. Accessing an instance from multiple threads is * discouraged, however if an application does wish to do this then it may do so provided that it * ensures accesses are synchronized.
  • - *
  • Registered listeners are called on the thread that created the ExoPlayer instance.
  • + *
  • Registered listeners are called on the thread that created the ExoPlayer instance, unless + * the thread that created the ExoPlayer instance does not have a {@link Looper}. In that case, + * registered listeners will be called on the application's main thread.
  • *
  • An internal playback thread is responsible for playback. Injected player components such as * Renderers, MediaSources, TrackSelectors and LoadControls are called by the player on this * thread.
  • @@ -99,87 +103,16 @@ import org.telegram.messenger.exoplayer2.video.MediaCodecVideoRenderer; * thread via a second message queue. The application thread consumes messages from the queue, * updating the application visible state and calling corresponding listener methods. *
  • Injected player components may use additional background threads. For example a MediaSource - * may use a background thread to load data. These are implementation specific.
  • + * may use background threads to load data. These are implementation specific. *
*/ -public interface ExoPlayer { +public interface ExoPlayer extends Player { /** - * Listener of changes in player state. + * @deprecated Use {@link Player.EventListener} instead. */ - interface EventListener { - - /** - * Called when the timeline and/or manifest has been refreshed. - *

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

- * When a position discontinuity occurs as a result of a change to the timeline this method is - * not called. {@link #onTimelineChanged(Timeline, Object)} is called in this case. - */ - void onPositionDiscontinuity(); - - /** - * Called when the current playback parameters change. The playback parameters may change due to - * a call to {@link ExoPlayer#setPlaybackParameters(PlaybackParameters)}, or the player itself - * may change them (for example, if audio playback switches to passthrough mode, where speed - * adjustment is no longer possible). - * - * @param playbackParameters The playback parameters. - */ - void onPlaybackParametersChanged(PlaybackParameters playbackParameters); - - } + @Deprecated + interface EventListener extends Player.EventListener {} /** * A component of an {@link ExoPlayer} that can receive messages on the playback thread. @@ -232,46 +165,48 @@ public interface ExoPlayer { } /** - * The player does not have a source to play, so it is neither buffering nor ready to play. + * @deprecated Use {@link Player#STATE_IDLE} instead. */ - int STATE_IDLE = 1; + @Deprecated + int STATE_IDLE = Player.STATE_IDLE; /** - * The player not able to immediately play from the current position. The cause is - * {@link Renderer} specific, but this state typically occurs when more data needs to be - * loaded to be ready to play, or more data needs to be buffered for playback to resume. + * @deprecated Use {@link Player#STATE_BUFFERING} instead. */ - int STATE_BUFFERING = 2; + @Deprecated + int STATE_BUFFERING = Player.STATE_BUFFERING; /** - * The player is able to immediately play from the current position. The player will be playing if - * {@link #getPlayWhenReady()} returns true, and paused otherwise. + * @deprecated Use {@link Player#STATE_READY} instead. */ - int STATE_READY = 3; + @Deprecated + int STATE_READY = Player.STATE_READY; /** - * The player has finished playing the media. + * @deprecated Use {@link Player#STATE_ENDED} instead. */ - int STATE_ENDED = 4; + @Deprecated + int STATE_ENDED = Player.STATE_ENDED; /** - * Register a listener to receive events from the player. The listener's methods will be called on - * the thread that was used to construct the player. - * - * @param listener The listener to register. + * @deprecated Use {@link Player#REPEAT_MODE_OFF} instead. */ - void addListener(EventListener listener); + @Deprecated + @RepeatMode int REPEAT_MODE_OFF = Player.REPEAT_MODE_OFF; + /** + * @deprecated Use {@link Player#REPEAT_MODE_ONE} instead. + */ + @Deprecated + @RepeatMode int REPEAT_MODE_ONE = Player.REPEAT_MODE_ONE; + /** + * @deprecated Use {@link Player#REPEAT_MODE_ALL} instead. + */ + @Deprecated + @RepeatMode int REPEAT_MODE_ALL = Player.REPEAT_MODE_ALL; /** - * Unregister a listener. The listener will no longer receive events from the player. + * Gets the {@link Looper} associated with the playback thread. * - * @param listener The listener to unregister. + * @return The {@link Looper} associated with the playback thread. */ - void removeListener(EventListener listener); - - /** - * Returns the current state of the player. - * - * @return One of the {@code STATE} constants defined in this interface. - */ - int getPlaybackState(); + Looper getPlaybackLooper(); /** * Prepares the player to play the provided {@link MediaSource}. Equivalent to @@ -293,104 +228,6 @@ public interface ExoPlayer { */ void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState); - /** - * Sets whether playback should proceed when {@link #getPlaybackState()} == {@link #STATE_READY}. - *

- * If the player is already in the ready state then this method can be used to pause and resume - * playback. - * - * @param playWhenReady Whether playback should proceed when ready. - */ - void setPlayWhenReady(boolean playWhenReady); - - /** - * Whether playback will proceed when {@link #getPlaybackState()} == {@link #STATE_READY}. - * - * @return Whether playback will proceed when ready. - */ - boolean getPlayWhenReady(); - - /** - * Whether the player is currently loading the source. - * - * @return Whether the player is currently loading the source. - */ - boolean isLoading(); - - /** - * Seeks to the default position associated with the current window. The position can depend on - * the type of source passed to {@link #prepare(MediaSource)}. For live streams it will typically - * be the live edge of the window. For other streams it will typically be the start of the window. - */ - void seekToDefaultPosition(); - - /** - * Seeks to the default position associated with the specified window. The position can depend on - * the type of source passed to {@link #prepare(MediaSource)}. For live streams it will typically - * be the live edge of the window. For other streams it will typically be the start of the window. - * - * @param windowIndex The index of the window whose associated default position should be seeked - * to. - */ - void seekToDefaultPosition(int windowIndex); - - /** - * Seeks to a position specified in milliseconds in the current window. - * - * @param positionMs The seek position in the current window, or {@link C#TIME_UNSET} to seek to - * the window's default position. - */ - void seekTo(long positionMs); - - /** - * Seeks to a position specified in milliseconds in the specified window. - * - * @param windowIndex The index of the window. - * @param positionMs The seek position in the specified window, or {@link C#TIME_UNSET} to seek to - * the window's default position. - */ - void seekTo(int windowIndex, long positionMs); - - /** - * Attempts to set the playback parameters. Passing {@code null} sets the parameters to the - * default, {@link PlaybackParameters#DEFAULT}, which means there is no speed or pitch adjustment. - *

- * Playback parameters changes may cause the player to buffer. - * {@link EventListener#onPlaybackParametersChanged(PlaybackParameters)} will be called whenever - * the currently active playback parameters change. When that listener is called, the parameters - * passed to it may not match {@code playbackParameters}. For example, the chosen speed or pitch - * may be out of range, in which case they are constrained to a set of permitted values. If it is - * not possible to change the playback parameters, the listener will not be invoked. - * - * @param playbackParameters The playback parameters, or {@code null} to use the defaults. - */ - void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters); - - /** - * Returns the currently active playback parameters. - * - * @see EventListener#onPlaybackParametersChanged(PlaybackParameters) - */ - PlaybackParameters getPlaybackParameters(); - - /** - * Stops playback. Use {@code setPlayWhenReady(false)} rather than this method if the intention - * is to pause playback. - *

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

- * Calling this method does not reset the playback position. - */ - void stop(); - - /** - * Releases the player. This method must be called when the player is no longer required. The - * player must not be used after calling this method. - */ - void release(); - /** * Sends messages to their target components. The messages are delivered on the playback thread. * If a component throws an {@link ExoPlaybackException} then it is propagated out of the player @@ -408,88 +245,4 @@ public interface ExoPlayer { */ void blockingSendMessages(ExoPlayerMessage... messages); - /** - * Returns the number of renderers. - */ - int getRendererCount(); - - /** - * Returns the track type that the renderer at a given index handles. - * - * @see Renderer#getTrackType() - * @param index The index of the renderer. - * @return One of the {@code TRACK_TYPE_*} constants defined in {@link C}. - */ - int getRendererType(int index); - - /** - * Returns the available track groups. - */ - TrackGroupArray getCurrentTrackGroups(); - - /** - * Returns the current track selections for each renderer. - */ - TrackSelectionArray getCurrentTrackSelections(); - - /** - * Returns the current manifest. The type depends on the {@link MediaSource} passed to - * {@link #prepare}. May be null. - */ - Object getCurrentManifest(); - - /** - * Returns the current {@link Timeline}. Never null, but may be empty. - */ - Timeline getCurrentTimeline(); - - /** - * Returns the index of the period currently being played. - */ - int getCurrentPeriodIndex(); - - /** - * Returns the index of the window currently being played. - */ - int getCurrentWindowIndex(); - - /** - * Returns the duration of the current window in milliseconds, or {@link C#TIME_UNSET} if the - * duration is not known. - */ - long getDuration(); - - /** - * Returns the playback position in the current window, in milliseconds. - */ - long getCurrentPosition(); - - /** - * Returns an estimate of the position in the current window up to which data is buffered, in - * milliseconds. - */ - long getBufferedPosition(); - - /** - * Returns an estimate of the percentage in the current window up to which data is buffered, or 0 - * if no estimate is available. - */ - int getBufferedPercentage(); - - /** - * Returns whether the current window is dynamic, or {@code false} if the {@link Timeline} is - * empty. - * - * @see Timeline.Window#isDynamic - */ - boolean isCurrentWindowDynamic(); - - /** - * Returns whether the current window is seekable, or {@code false} if the {@link Timeline} is - * empty. - * - * @see Timeline.Window#isSeekable - */ - boolean isCurrentWindowSeekable(); - } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayerFactory.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayerFactory.java index 7d4c1de19..d3bfad542 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayerFactory.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayerFactory.java @@ -16,7 +16,6 @@ package org.telegram.messenger.exoplayer2; import android.content.Context; -import android.os.Looper; import org.telegram.messenger.exoplayer2.drm.DrmSessionManager; import org.telegram.messenger.exoplayer2.drm.FrameworkMediaCrypto; import org.telegram.messenger.exoplayer2.trackselection.TrackSelector; @@ -29,8 +28,7 @@ public final class ExoPlayerFactory { private ExoPlayerFactory() {} /** - * Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated - * {@link Looper}. + * Creates a {@link SimpleExoPlayer} instance. * * @param context A {@link Context}. * @param trackSelector The {@link TrackSelector} that will be used by the instance. @@ -45,8 +43,7 @@ public final class ExoPlayerFactory { } /** - * Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated - * {@link Looper}. Available extension renderers are not used. + * Creates a {@link SimpleExoPlayer} instance. Available extension renderers are not used. * * @param context A {@link Context}. * @param trackSelector The {@link TrackSelector} that will be used by the instance. @@ -63,8 +60,7 @@ public final class ExoPlayerFactory { } /** - * Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated - * {@link Looper}. + * Creates a {@link SimpleExoPlayer} instance. * * @param context A {@link Context}. * @param trackSelector The {@link TrackSelector} that will be used by the instance. @@ -86,8 +82,7 @@ public final class ExoPlayerFactory { } /** - * Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated - * {@link Looper}. + * Creates a {@link SimpleExoPlayer} instance. * * @param context A {@link Context}. * @param trackSelector The {@link TrackSelector} that will be used by the instance. @@ -112,8 +107,7 @@ public final class ExoPlayerFactory { } /** - * Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated - * {@link Looper}. + * Creates a {@link SimpleExoPlayer} instance. * * @param context A {@link Context}. * @param trackSelector The {@link TrackSelector} that will be used by the instance. @@ -123,8 +117,7 @@ public final class ExoPlayerFactory { } /** - * Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated - * {@link Looper}. + * Creates a {@link SimpleExoPlayer} instance. * * @param renderersFactory A factory for creating {@link Renderer}s to be used by the instance. * @param trackSelector The {@link TrackSelector} that will be used by the instance. @@ -135,8 +128,7 @@ public final class ExoPlayerFactory { } /** - * Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated - * {@link Looper}. + * Creates a {@link SimpleExoPlayer} instance. * * @param renderersFactory A factory for creating {@link Renderer}s to be used by the instance. * @param trackSelector The {@link TrackSelector} that will be used by the instance. @@ -148,8 +140,7 @@ public final class ExoPlayerFactory { } /** - * Creates an {@link ExoPlayer} instance. Must be called from a thread that has an associated - * {@link Looper}. + * Creates an {@link ExoPlayer} instance. * * @param renderers The {@link Renderer}s that will be used by the instance. * @param trackSelector The {@link TrackSelector} that will be used by the instance. @@ -159,8 +150,7 @@ public final class ExoPlayerFactory { } /** - * Creates an {@link ExoPlayer} instance. Must be called from a thread that has an associated - * {@link Looper}. + * Creates an {@link ExoPlayer} instance. * * @param renderers The {@link Renderer}s that will be used by the instance. * @param trackSelector The {@link TrackSelector} that will be used by the instance. diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayerImpl.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayerImpl.java index ed7afd4c7..03592a578 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayerImpl.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayerImpl.java @@ -24,6 +24,7 @@ import android.util.Log; import org.telegram.messenger.exoplayer2.ExoPlayerImplInternal.PlaybackInfo; import org.telegram.messenger.exoplayer2.ExoPlayerImplInternal.SourceInfo; import org.telegram.messenger.exoplayer2.source.MediaSource; +import org.telegram.messenger.exoplayer2.source.MediaSource.MediaPeriodId; import org.telegram.messenger.exoplayer2.source.TrackGroupArray; import org.telegram.messenger.exoplayer2.trackselection.TrackSelection; import org.telegram.messenger.exoplayer2.trackselection.TrackSelectionArray; @@ -45,12 +46,13 @@ import java.util.concurrent.CopyOnWriteArraySet; private final TrackSelectionArray emptyTrackSelections; private final Handler eventHandler; private final ExoPlayerImplInternal internalPlayer; - private final CopyOnWriteArraySet listeners; + private final CopyOnWriteArraySet listeners; private final Timeline.Window window; private final Timeline.Period period; private boolean tracksSelected; private boolean playWhenReady; + private @RepeatMode int repeatMode; private int playbackState; private int pendingSeekAcks; private int pendingPrepareAcks; @@ -78,12 +80,14 @@ import java.util.concurrent.CopyOnWriteArraySet; */ @SuppressLint("HandlerLeak") public ExoPlayerImpl(Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl) { - Log.i(TAG, "Init " + ExoPlayerLibraryInfo.VERSION_SLASHY + " [" + Util.DEVICE_DEBUG_INFO + "]"); + Log.i(TAG, "Init " + Integer.toHexString(System.identityHashCode(this)) + " [" + + ExoPlayerLibraryInfo.VERSION_SLASHY + "] [" + Util.DEVICE_DEBUG_INFO + "]"); Assertions.checkState(renderers.length > 0); this.renderers = Assertions.checkNotNull(renderers); this.trackSelector = Assertions.checkNotNull(trackSelector); this.playWhenReady = false; - this.playbackState = STATE_IDLE; + this.repeatMode = Player.REPEAT_MODE_OFF; + this.playbackState = Player.STATE_IDLE; this.listeners = new CopyOnWriteArraySet<>(); emptyTrackSelections = new TrackSelectionArray(new TrackSelection[renderers.length]); timeline = Timeline.EMPTY; @@ -92,7 +96,8 @@ import java.util.concurrent.CopyOnWriteArraySet; trackGroups = TrackGroupArray.EMPTY; trackSelections = emptyTrackSelections; playbackParameters = PlaybackParameters.DEFAULT; - eventHandler = new Handler() { + Looper eventLooper = Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper(); + eventHandler = new Handler(eventLooper) { @Override public void handleMessage(Message msg) { ExoPlayerImpl.this.handleEvent(msg); @@ -100,16 +105,21 @@ import java.util.concurrent.CopyOnWriteArraySet; }; playbackInfo = new ExoPlayerImplInternal.PlaybackInfo(0, 0); internalPlayer = new ExoPlayerImplInternal(renderers, trackSelector, loadControl, playWhenReady, - eventHandler, playbackInfo, this); + repeatMode, eventHandler, playbackInfo, this); } @Override - public void addListener(EventListener listener) { + public Looper getPlaybackLooper() { + return internalPlayer.getPlaybackLooper(); + } + + @Override + public void addListener(Player.EventListener listener) { listeners.add(listener); } @Override - public void removeListener(EventListener listener) { + public void removeListener(Player.EventListener listener) { listeners.remove(listener); } @@ -129,7 +139,7 @@ import java.util.concurrent.CopyOnWriteArraySet; if (!timeline.isEmpty() || manifest != null) { timeline = Timeline.EMPTY; manifest = null; - for (EventListener listener : listeners) { + for (Player.EventListener listener : listeners) { listener.onTimelineChanged(timeline, manifest); } } @@ -138,7 +148,7 @@ import java.util.concurrent.CopyOnWriteArraySet; trackGroups = TrackGroupArray.EMPTY; trackSelections = emptyTrackSelections; trackSelector.onSelectionActivated(null); - for (EventListener listener : listeners) { + for (Player.EventListener listener : listeners) { listener.onTracksChanged(trackGroups, trackSelections); } } @@ -152,7 +162,7 @@ import java.util.concurrent.CopyOnWriteArraySet; if (this.playWhenReady != playWhenReady) { this.playWhenReady = playWhenReady; internalPlayer.setPlayWhenReady(playWhenReady); - for (EventListener listener : listeners) { + for (Player.EventListener listener : listeners) { listener.onPlayerStateChanged(playWhenReady, playbackState); } } @@ -163,6 +173,22 @@ import java.util.concurrent.CopyOnWriteArraySet; return playWhenReady; } + @Override + public void setRepeatMode(@RepeatMode int repeatMode) { + if (this.repeatMode != repeatMode) { + this.repeatMode = repeatMode; + internalPlayer.setRepeatMode(repeatMode); + for (Player.EventListener listener : listeners) { + listener.onRepeatModeChanged(repeatMode); + } + } + } + + @Override + public @RepeatMode int getRepeatMode() { + return repeatMode; + } + @Override public boolean isLoading() { return isLoading; @@ -194,10 +220,10 @@ import java.util.concurrent.CopyOnWriteArraySet; maskingPeriodIndex = 0; } else { timeline.getWindow(windowIndex, window); - long resolvedPositionMs = - positionMs == C.TIME_UNSET ? window.getDefaultPositionUs() : positionMs; + long resolvedPositionUs = + positionMs == C.TIME_UNSET ? window.getDefaultPositionUs() : C.msToUs(positionMs); int periodIndex = window.firstPeriodIndex; - long periodPositionUs = window.getPositionInFirstPeriodUs() + C.msToUs(resolvedPositionMs); + long periodPositionUs = window.getPositionInFirstPeriodUs() + resolvedPositionUs; long periodDurationUs = timeline.getPeriod(periodIndex, period).getDurationUs(); while (periodDurationUs != C.TIME_UNSET && periodPositionUs >= periodDurationUs && periodIndex < window.lastPeriodIndex) { @@ -212,7 +238,7 @@ import java.util.concurrent.CopyOnWriteArraySet; } else { maskingWindowPositionMs = positionMs; internalPlayer.seekTo(timeline, windowIndex, C.msToUs(positionMs)); - for (EventListener listener : listeners) { + for (Player.EventListener listener : listeners) { listener.onPositionDiscontinuity(); } } @@ -238,6 +264,9 @@ import java.util.concurrent.CopyOnWriteArraySet; @Override public void release() { + Log.i(TAG, "Release " + Integer.toHexString(System.identityHashCode(this)) + " [" + + ExoPlayerLibraryInfo.VERSION_SLASHY + "] [" + Util.DEVICE_DEBUG_INFO + "] [" + + ExoPlayerLibraryInfo.registeredModules() + "]"); internalPlayer.release(); eventHandler.removeCallbacksAndMessages(null); } @@ -257,7 +286,7 @@ import java.util.concurrent.CopyOnWriteArraySet; if (timeline.isEmpty() || pendingSeekAcks > 0) { return maskingPeriodIndex; } else { - return playbackInfo.periodIndex; + return playbackInfo.periodId.periodIndex; } } @@ -266,7 +295,7 @@ import java.util.concurrent.CopyOnWriteArraySet; if (timeline.isEmpty() || pendingSeekAcks > 0) { return maskingWindowIndex; } else { - return timeline.getPeriod(playbackInfo.periodIndex, period).windowIndex; + return timeline.getPeriod(playbackInfo.periodId.periodIndex, period).windowIndex; } } @@ -275,7 +304,14 @@ import java.util.concurrent.CopyOnWriteArraySet; if (timeline.isEmpty()) { return C.TIME_UNSET; } - return timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs(); + if (isPlayingAd()) { + MediaPeriodId periodId = playbackInfo.periodId; + timeline.getPeriod(periodId.periodIndex, period); + long adDurationUs = period.getAdDurationUs(periodId.adGroupIndex, periodId.adIndexInAdGroup); + return C.usToMs(adDurationUs); + } else { + return timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs(); + } } @Override @@ -283,8 +319,7 @@ import java.util.concurrent.CopyOnWriteArraySet; if (timeline.isEmpty() || pendingSeekAcks > 0) { return maskingWindowPositionMs; } else { - timeline.getPeriod(playbackInfo.periodIndex, period); - return period.getPositionInWindowMs() + C.usToMs(playbackInfo.positionUs); + return playbackInfoPositionUsToWindowPositionMs(playbackInfo.positionUs); } } @@ -294,8 +329,7 @@ import java.util.concurrent.CopyOnWriteArraySet; if (timeline.isEmpty() || pendingSeekAcks > 0) { return maskingWindowPositionMs; } else { - timeline.getPeriod(playbackInfo.periodIndex, period); - return period.getPositionInWindowMs() + C.usToMs(playbackInfo.bufferedPositionUs); + return playbackInfoPositionUsToWindowPositionMs(playbackInfo.bufferedPositionUs); } } @@ -304,10 +338,10 @@ import java.util.concurrent.CopyOnWriteArraySet; if (timeline.isEmpty()) { return 0; } - long bufferedPosition = getBufferedPosition(); + long position = getBufferedPosition(); long duration = getDuration(); - return (bufferedPosition == C.TIME_UNSET || duration == C.TIME_UNSET) ? 0 - : (int) (duration == 0 ? 100 : (bufferedPosition * 100) / duration); + return position == C.TIME_UNSET || duration == C.TIME_UNSET ? 0 + : (duration == 0 ? 100 : Util.constrainValue((int) ((position * 100) / duration), 0, 100)); } @Override @@ -320,6 +354,31 @@ import java.util.concurrent.CopyOnWriteArraySet; return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isSeekable; } + @Override + public boolean isPlayingAd() { + return !timeline.isEmpty() && pendingSeekAcks == 0 && playbackInfo.periodId.isAd(); + } + + @Override + public int getCurrentAdGroupIndex() { + return isPlayingAd() ? playbackInfo.periodId.adGroupIndex : C.INDEX_UNSET; + } + + @Override + public int getCurrentAdIndexInAdGroup() { + return isPlayingAd() ? playbackInfo.periodId.adIndexInAdGroup : C.INDEX_UNSET; + } + + @Override + public long getContentPosition() { + if (isPlayingAd()) { + timeline.getPeriod(playbackInfo.periodId.periodIndex, period); + return period.getPositionInWindowMs() + C.usToMs(playbackInfo.contentPositionUs); + } else { + return getCurrentPosition(); + } + } + @Override public int getRendererCount() { return renderers.length; @@ -359,14 +418,14 @@ import java.util.concurrent.CopyOnWriteArraySet; } case ExoPlayerImplInternal.MSG_STATE_CHANGED: { playbackState = msg.arg1; - for (EventListener listener : listeners) { + for (Player.EventListener listener : listeners) { listener.onPlayerStateChanged(playWhenReady, playbackState); } break; } case ExoPlayerImplInternal.MSG_LOADING_CHANGED: { isLoading = msg.arg1 != 0; - for (EventListener listener : listeners) { + for (Player.EventListener listener : listeners) { listener.onLoadingChanged(isLoading); } break; @@ -378,7 +437,7 @@ import java.util.concurrent.CopyOnWriteArraySet; trackGroups = trackSelectorResult.groups; trackSelections = trackSelectorResult.selections; trackSelector.onSelectionActivated(trackSelectorResult.info); - for (EventListener listener : listeners) { + for (Player.EventListener listener : listeners) { listener.onTracksChanged(trackGroups, trackSelections); } } @@ -387,8 +446,14 @@ import java.util.concurrent.CopyOnWriteArraySet; case ExoPlayerImplInternal.MSG_SEEK_ACK: { if (--pendingSeekAcks == 0) { playbackInfo = (ExoPlayerImplInternal.PlaybackInfo) msg.obj; + if (timeline.isEmpty()) { + // Update the masking variables, which are used when the timeline is empty. + maskingPeriodIndex = 0; + maskingWindowIndex = 0; + maskingWindowPositionMs = 0; + } if (msg.arg1 != 0) { - for (EventListener listener : listeners) { + for (Player.EventListener listener : listeners) { listener.onPositionDiscontinuity(); } } @@ -398,7 +463,7 @@ import java.util.concurrent.CopyOnWriteArraySet; case ExoPlayerImplInternal.MSG_POSITION_DISCONTINUITY: { if (pendingSeekAcks == 0) { playbackInfo = (ExoPlayerImplInternal.PlaybackInfo) msg.obj; - for (EventListener listener : listeners) { + for (Player.EventListener listener : listeners) { listener.onPositionDiscontinuity(); } } @@ -411,7 +476,13 @@ import java.util.concurrent.CopyOnWriteArraySet; timeline = sourceInfo.timeline; manifest = sourceInfo.manifest; playbackInfo = sourceInfo.playbackInfo; - for (EventListener listener : listeners) { + if (pendingSeekAcks == 0 && timeline.isEmpty()) { + // Update the masking variables, which are used when the timeline is empty. + maskingPeriodIndex = 0; + maskingWindowIndex = 0; + maskingWindowPositionMs = 0; + } + for (Player.EventListener listener : listeners) { listener.onTimelineChanged(timeline, manifest); } } @@ -421,7 +492,7 @@ import java.util.concurrent.CopyOnWriteArraySet; PlaybackParameters playbackParameters = (PlaybackParameters) msg.obj; if (!this.playbackParameters.equals(playbackParameters)) { this.playbackParameters = playbackParameters; - for (EventListener listener : listeners) { + for (Player.EventListener listener : listeners) { listener.onPlaybackParametersChanged(playbackParameters); } } @@ -429,7 +500,7 @@ import java.util.concurrent.CopyOnWriteArraySet; } case ExoPlayerImplInternal.MSG_ERROR: { ExoPlaybackException exception = (ExoPlaybackException) msg.obj; - for (EventListener listener : listeners) { + for (Player.EventListener listener : listeners) { listener.onPlayerError(exception); } break; @@ -439,4 +510,13 @@ import java.util.concurrent.CopyOnWriteArraySet; } } + private long playbackInfoPositionUsToWindowPositionMs(long positionUs) { + long positionMs = C.usToMs(positionUs); + if (!playbackInfo.periodId.isAd()) { + timeline.getPeriod(playbackInfo.periodId.periodIndex, period); + positionMs += period.getPositionInWindowMs(); + } + return positionMs; + } + } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayerImplInternal.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayerImplInternal.java index db0c13557..1673d5462 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayerImplInternal.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayerImplInternal.java @@ -17,14 +17,18 @@ package org.telegram.messenger.exoplayer2; import android.os.Handler; import android.os.HandlerThread; +import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.SystemClock; import android.util.Log; import android.util.Pair; import org.telegram.messenger.exoplayer2.ExoPlayer.ExoPlayerMessage; +import org.telegram.messenger.exoplayer2.MediaPeriodInfoSequence.MediaPeriodInfo; +import org.telegram.messenger.exoplayer2.source.ClippingMediaPeriod; import org.telegram.messenger.exoplayer2.source.MediaPeriod; import org.telegram.messenger.exoplayer2.source.MediaSource; +import org.telegram.messenger.exoplayer2.source.MediaSource.MediaPeriodId; import org.telegram.messenger.exoplayer2.source.SampleStream; import org.telegram.messenger.exoplayer2.trackselection.TrackSelection; import org.telegram.messenger.exoplayer2.trackselection.TrackSelectionArray; @@ -48,21 +52,32 @@ import java.io.IOException; */ public static final class PlaybackInfo { - public final int periodIndex; + public final MediaPeriodId periodId; public final long startPositionUs; + public final long contentPositionUs; public volatile long positionUs; public volatile long bufferedPositionUs; public PlaybackInfo(int periodIndex, long startPositionUs) { - this.periodIndex = periodIndex; + this(new MediaPeriodId(periodIndex), startPositionUs); + } + + public PlaybackInfo(MediaPeriodId periodId, long startPositionUs) { + this(periodId, startPositionUs, C.TIME_UNSET); + } + + public PlaybackInfo(MediaPeriodId periodId, long startPositionUs, long contentPositionUs) { + this.periodId = periodId; this.startPositionUs = startPositionUs; + this.contentPositionUs = contentPositionUs; positionUs = startPositionUs; bufferedPositionUs = startPositionUs; } public PlaybackInfo copyWithPeriodIndex(int periodIndex) { - PlaybackInfo playbackInfo = new PlaybackInfo(periodIndex, startPositionUs); + PlaybackInfo playbackInfo = new PlaybackInfo(periodId.copyWithPeriodIndex(periodIndex), + startPositionUs, contentPositionUs); playbackInfo.positionUs = positionUs; playbackInfo.bufferedPositionUs = bufferedPositionUs; return playbackInfo; @@ -112,6 +127,7 @@ import java.io.IOException; private static final int MSG_SOURCE_CONTINUE_LOADING_REQUESTED = 9; private static final int MSG_TRACK_SELECTION_INVALIDATED = 10; private static final int MSG_CUSTOM = 11; + private static final int MSG_SET_REPEAT_MODE = 12; private static final int PREPARING_SOURCE_INTERVAL_MS = 10; private static final int RENDERING_INTERVAL_MS = 10; @@ -143,6 +159,7 @@ import java.io.IOException; private final ExoPlayer player; private final Timeline.Window window; private final Timeline.Period period; + private final MediaPeriodInfoSequence mediaPeriodInfoSequence; private PlaybackInfo playbackInfo; private PlaybackParameters playbackParameters; @@ -155,6 +172,7 @@ import java.io.IOException; private boolean rebuffering; private boolean isLoading; private int state; + private @Player.RepeatMode int repeatMode; private int customMessagesSent; private int customMessagesProcessed; private long elapsedRealtimeUs; @@ -170,14 +188,15 @@ import java.io.IOException; private Timeline timeline; public ExoPlayerImplInternal(Renderer[] renderers, TrackSelector trackSelector, - LoadControl loadControl, boolean playWhenReady, Handler eventHandler, - PlaybackInfo playbackInfo, ExoPlayer player) { + LoadControl loadControl, boolean playWhenReady, @Player.RepeatMode int repeatMode, + Handler eventHandler, PlaybackInfo playbackInfo, ExoPlayer player) { this.renderers = renderers; this.trackSelector = trackSelector; this.loadControl = loadControl; this.playWhenReady = playWhenReady; + this.repeatMode = repeatMode; this.eventHandler = eventHandler; - this.state = ExoPlayer.STATE_IDLE; + this.state = Player.STATE_IDLE; this.playbackInfo = playbackInfo; this.player = player; @@ -190,6 +209,7 @@ import java.io.IOException; enabledRenderers = new Renderer[0]; window = new Timeline.Window(); period = new Timeline.Period(); + mediaPeriodInfoSequence = new MediaPeriodInfoSequence(); trackSelector.init(this); playbackParameters = PlaybackParameters.DEFAULT; @@ -210,6 +230,10 @@ import java.io.IOException; handler.obtainMessage(MSG_SET_PLAY_WHEN_READY, playWhenReady ? 1 : 0, 0).sendToTarget(); } + public void setRepeatMode(@Player.RepeatMode int repeatMode) { + handler.obtainMessage(MSG_SET_REPEAT_MODE, repeatMode, 0).sendToTarget(); + } + public void seekTo(Timeline timeline, int windowIndex, long positionUs) { handler.obtainMessage(MSG_SEEK_TO, new SeekPosition(timeline, windowIndex, positionUs)) .sendToTarget(); @@ -239,13 +263,18 @@ import java.io.IOException; } int messageNumber = customMessagesSent++; handler.obtainMessage(MSG_CUSTOM, messages).sendToTarget(); + boolean wasInterrupted = false; while (customMessagesProcessed <= messageNumber) { try { wait(); } catch (InterruptedException e) { - Thread.currentThread().interrupt(); + wasInterrupted = true; } } + if (wasInterrupted) { + // Restore the interrupted status. + Thread.currentThread().interrupt(); + } } public synchronized void release() { @@ -253,16 +282,25 @@ import java.io.IOException; return; } handler.sendEmptyMessage(MSG_RELEASE); + boolean wasInterrupted = false; while (!released) { try { wait(); } catch (InterruptedException e) { - Thread.currentThread().interrupt(); + wasInterrupted = true; } } + if (wasInterrupted) { + // Restore the interrupted status. + Thread.currentThread().interrupt(); + } internalPlaybackThread.quit(); } + public Looper getPlaybackLooper() { + return internalPlaybackThread.getLooper(); + } + // MediaSource.Listener implementation. @Override @@ -304,6 +342,10 @@ import java.io.IOException; setPlayWhenReadyInternal(msg.arg1 != 0); return true; } + case MSG_SET_REPEAT_MODE: { + setRepeatModeInternal(msg.arg1); + return true; + } case MSG_DO_SOME_WORK: { doSomeWork(); return true; @@ -388,10 +430,14 @@ import java.io.IOException; loadControl.onPrepared(); if (resetPosition) { playbackInfo = new PlaybackInfo(0, C.TIME_UNSET); + } else { + // The new start position is the current playback position. + playbackInfo = new PlaybackInfo(playbackInfo.periodId, playbackInfo.positionUs, + playbackInfo.contentPositionUs); } this.mediaSource = mediaSource; mediaSource.prepareSource(player, true, this); - setState(ExoPlayer.STATE_BUFFERING); + setState(Player.STATE_BUFFERING); handler.sendEmptyMessage(MSG_DO_SOME_WORK); } @@ -402,15 +448,69 @@ import java.io.IOException; stopRenderers(); updatePlaybackPositions(); } else { - if (state == ExoPlayer.STATE_READY) { + if (state == Player.STATE_READY) { startRenderers(); handler.sendEmptyMessage(MSG_DO_SOME_WORK); - } else if (state == ExoPlayer.STATE_BUFFERING) { + } else if (state == Player.STATE_BUFFERING) { handler.sendEmptyMessage(MSG_DO_SOME_WORK); } } } + private void setRepeatModeInternal(@Player.RepeatMode int repeatMode) + throws ExoPlaybackException { + this.repeatMode = repeatMode; + mediaPeriodInfoSequence.setRepeatMode(repeatMode); + + // Find the last existing period holder that matches the new period order. + MediaPeriodHolder lastValidPeriodHolder = playingPeriodHolder != null + ? playingPeriodHolder : loadingPeriodHolder; + if (lastValidPeriodHolder == null) { + return; + } + while (true) { + int nextPeriodIndex = timeline.getNextPeriodIndex(lastValidPeriodHolder.info.id.periodIndex, + period, window, repeatMode); + while (lastValidPeriodHolder.next != null + && !lastValidPeriodHolder.info.isLastInTimelinePeriod) { + lastValidPeriodHolder = lastValidPeriodHolder.next; + } + if (nextPeriodIndex == C.INDEX_UNSET || lastValidPeriodHolder.next == null + || lastValidPeriodHolder.next.info.id.periodIndex != nextPeriodIndex) { + break; + } + lastValidPeriodHolder = lastValidPeriodHolder.next; + } + + // Release any period holders that don't match the new period order. + int loadingPeriodHolderIndex = loadingPeriodHolder.index; + int readingPeriodHolderIndex = + readingPeriodHolder != null ? readingPeriodHolder.index : C.INDEX_UNSET; + if (lastValidPeriodHolder.next != null) { + releasePeriodHoldersFrom(lastValidPeriodHolder.next); + lastValidPeriodHolder.next = null; + } + + // Update the period info for the last holder, as it may now be the last period in the timeline. + lastValidPeriodHolder.info = + mediaPeriodInfoSequence.getUpdatedMediaPeriodInfo(lastValidPeriodHolder.info); + + // Handle cases where loadingPeriodHolder or readingPeriodHolder have been removed. + boolean seenLoadingPeriodHolder = loadingPeriodHolderIndex <= lastValidPeriodHolder.index; + if (!seenLoadingPeriodHolder) { + loadingPeriodHolder = lastValidPeriodHolder; + } + boolean seenReadingPeriodHolder = readingPeriodHolderIndex != C.INDEX_UNSET + && readingPeriodHolderIndex <= lastValidPeriodHolder.index; + if (!seenReadingPeriodHolder && playingPeriodHolder != null) { + // Renderers may have read from a period that's been removed. Seek back to the current + // position of the playing period to make sure none of the removed period is played. + MediaPeriodId periodId = playingPeriodHolder.info.id; + long newPositionUs = seekToPeriodPosition(periodId, playbackInfo.positionUs); + playbackInfo = new PlaybackInfo(periodId, newPositionUs, playbackInfo.contentPositionUs); + } + } + private void startRenderers() throws ExoPlaybackException { rebuffering = false; standaloneMediaClock.start(); @@ -451,8 +551,7 @@ import java.io.IOException; long bufferedPositionUs = enabledRenderers.length == 0 ? C.TIME_END_OF_SOURCE : playingPeriodHolder.mediaPeriod.getBufferedPositionUs(); playbackInfo.bufferedPositionUs = bufferedPositionUs == C.TIME_END_OF_SOURCE - ? timeline.getPeriod(playingPeriodHolder.index, period).getDurationUs() - : bufferedPositionUs; + ? playingPeriodHolder.info.durationUs : bufferedPositionUs; } private void doSomeWork() throws ExoPlaybackException, IOException { @@ -504,43 +603,43 @@ import java.io.IOException; } } - long playingPeriodDurationUs = timeline.getPeriod(playingPeriodHolder.index, period) - .getDurationUs(); + long playingPeriodDurationUs = playingPeriodHolder.info.durationUs; if (allRenderersEnded && (playingPeriodDurationUs == C.TIME_UNSET || playingPeriodDurationUs <= playbackInfo.positionUs) - && playingPeriodHolder.isLast) { - setState(ExoPlayer.STATE_ENDED); + && playingPeriodHolder.info.isFinal) { + setState(Player.STATE_ENDED); stopRenderers(); - } else if (state == ExoPlayer.STATE_BUFFERING) { + } else if (state == Player.STATE_BUFFERING) { boolean isNewlyReady = enabledRenderers.length > 0 - ? (allRenderersReadyOrEnded && haveSufficientBuffer(rebuffering)) + ? (allRenderersReadyOrEnded + && loadingPeriodHolder.haveSufficientBuffer(rebuffering, rendererPositionUs)) : isTimelineReady(playingPeriodDurationUs); if (isNewlyReady) { - setState(ExoPlayer.STATE_READY); + setState(Player.STATE_READY); if (playWhenReady) { startRenderers(); } } - } else if (state == ExoPlayer.STATE_READY) { + } else if (state == Player.STATE_READY) { boolean isStillReady = enabledRenderers.length > 0 ? allRenderersReadyOrEnded : isTimelineReady(playingPeriodDurationUs); if (!isStillReady) { rebuffering = playWhenReady; - setState(ExoPlayer.STATE_BUFFERING); + setState(Player.STATE_BUFFERING); stopRenderers(); } } - if (state == ExoPlayer.STATE_BUFFERING) { + if (state == Player.STATE_BUFFERING) { for (Renderer renderer : enabledRenderers) { renderer.maybeThrowStreamError(); } } - if ((playWhenReady && state == ExoPlayer.STATE_READY) || state == ExoPlayer.STATE_BUFFERING) { + if ((playWhenReady && state == Player.STATE_READY) || state == Player.STATE_BUFFERING) { scheduleNextWork(operationStartTimeMs, RENDERING_INTERVAL_MS); - } else if (enabledRenderers.length != 0) { + } else if (enabledRenderers.length != 0 && state != Player.STATE_ENDED) { scheduleNextWork(operationStartTimeMs, IDLE_INTERVAL_MS); } else { handler.removeMessages(MSG_DO_SOME_WORK); @@ -576,7 +675,7 @@ import java.io.IOException; // Set the internal position to (0,TIME_UNSET) so that a subsequent seek to (0,0) isn't // ignored. playbackInfo = new PlaybackInfo(0, C.TIME_UNSET); - setState(ExoPlayer.STATE_ENDED); + setState(Player.STATE_ENDED); // Reset, but retain the source so that it can still be used should a seek occur. resetInternal(false); return; @@ -585,28 +684,34 @@ import java.io.IOException; boolean seekPositionAdjusted = seekPosition.windowPositionUs == C.TIME_UNSET; int periodIndex = periodPosition.first; long periodPositionUs = periodPosition.second; - + long contentPositionUs = periodPositionUs; + MediaPeriodId periodId = + mediaPeriodInfoSequence.resolvePeriodPositionForAds(periodIndex, periodPositionUs); + if (periodId.isAd()) { + seekPositionAdjusted = true; + periodPositionUs = 0; + } try { - if (periodIndex == playbackInfo.periodIndex + if (periodId.equals(playbackInfo.periodId) && ((periodPositionUs / 1000) == (playbackInfo.positionUs / 1000))) { // Seek position equals the current position. Do nothing. return; } - long newPeriodPositionUs = seekToPeriodPosition(periodIndex, periodPositionUs); + long newPeriodPositionUs = seekToPeriodPosition(periodId, periodPositionUs); seekPositionAdjusted |= periodPositionUs != newPeriodPositionUs; periodPositionUs = newPeriodPositionUs; } finally { - playbackInfo = new PlaybackInfo(periodIndex, periodPositionUs); + playbackInfo = new PlaybackInfo(periodId, periodPositionUs, contentPositionUs); eventHandler.obtainMessage(MSG_SEEK_ACK, seekPositionAdjusted ? 1 : 0, 0, playbackInfo) .sendToTarget(); } } - private long seekToPeriodPosition(int periodIndex, long periodPositionUs) + private long seekToPeriodPosition(MediaPeriodId periodId, long periodPositionUs) throws ExoPlaybackException { stopRenderers(); rebuffering = false; - setState(ExoPlayer.STATE_BUFFERING); + setState(Player.STATE_BUFFERING); MediaPeriodHolder newPlayingPeriodHolder = null; if (playingPeriodHolder == null) { @@ -618,7 +723,8 @@ import java.io.IOException; // Clear the timeline, but keep the requested period if it is already prepared. MediaPeriodHolder periodHolder = playingPeriodHolder; while (periodHolder != null) { - if (periodHolder.index == periodIndex && periodHolder.prepared) { + if (newPlayingPeriodHolder == null + && shouldKeepPeriodHolder(periodId, periodPositionUs, periodHolder)) { newPlayingPeriodHolder = periodHolder; } else { periodHolder.release(); @@ -662,6 +768,19 @@ import java.io.IOException; return periodPositionUs; } + private boolean shouldKeepPeriodHolder(MediaPeriodId seekPeriodId, long positionUs, + MediaPeriodHolder holder) { + if (seekPeriodId.equals(holder.info.id) && holder.prepared) { + timeline.getPeriod(holder.info.id.periodIndex, period); + int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(positionUs); + if (nextAdGroupIndex == C.INDEX_UNSET + || period.getAdGroupTimeUs(nextAdGroupIndex) == holder.info.endPositionUs) { + return true; + } + } + return false; + } + private void resetRendererPosition(long periodPositionUs) throws ExoPlaybackException { rendererPositionUs = playingPeriodHolder == null ? periodPositionUs + RENDERER_TIMESTAMP_OFFSET_US @@ -683,13 +802,13 @@ import java.io.IOException; private void stopInternal() { resetInternal(true); loadControl.onStopped(); - setState(ExoPlayer.STATE_IDLE); + setState(Player.STATE_IDLE); } private void releaseInternal() { resetInternal(true); loadControl.onReleased(); - setState(ExoPlayer.STATE_IDLE); + setState(Player.STATE_IDLE); synchronized (this) { released = true; notifyAll(); @@ -724,6 +843,7 @@ import java.io.IOException; mediaSource.releaseSource(); mediaSource = null; } + mediaPeriodInfoSequence.setTimeline(null); timeline = null; } } @@ -733,7 +853,7 @@ import java.io.IOException; for (ExoPlayerMessage message : messages) { message.target.handleMessage(message.messageType, message.message); } - if (mediaSource != null) { + if (state == Player.STATE_READY || state == Player.STATE_BUFFERING) { // The message may have caused something to change that now requires us to do work. handler.sendEmptyMessage(MSG_DO_SOME_WORK); } @@ -834,7 +954,7 @@ import java.io.IOException; } loadingPeriodHolder.next = null; if (loadingPeriodHolder.prepared) { - long loadingPeriodPositionUs = Math.max(loadingPeriodHolder.startPositionUs, + long loadingPeriodPositionUs = Math.max(loadingPeriodHolder.info.startPositionUs, loadingPeriodHolder.toPeriodTime(rendererPositionUs)); loadingPeriodHolder.updatePeriodTrackSelection(loadingPeriodPositionUs, false); } @@ -847,23 +967,8 @@ import java.io.IOException; private boolean isTimelineReady(long playingPeriodDurationUs) { return playingPeriodDurationUs == C.TIME_UNSET || playbackInfo.positionUs < playingPeriodDurationUs - || (playingPeriodHolder.next != null && playingPeriodHolder.next.prepared); - } - - private boolean haveSufficientBuffer(boolean rebuffering) { - long loadingPeriodBufferedPositionUs = !loadingPeriodHolder.prepared - ? loadingPeriodHolder.startPositionUs - : loadingPeriodHolder.mediaPeriod.getBufferedPositionUs(); - if (loadingPeriodBufferedPositionUs == C.TIME_END_OF_SOURCE) { - if (loadingPeriodHolder.isLast) { - return true; - } - loadingPeriodBufferedPositionUs = timeline.getPeriod(loadingPeriodHolder.index, period) - .getDurationUs(); - } - return loadControl.shouldStartPlayback( - loadingPeriodBufferedPositionUs - loadingPeriodHolder.toPeriodTime(rendererPositionUs), - rebuffering); + || (playingPeriodHolder.next != null + && (playingPeriodHolder.next.prepared || playingPeriodHolder.next.info.id.isAd())); } private void maybeThrowPeriodPrepareError() throws IOException { @@ -882,48 +987,63 @@ import java.io.IOException; throws ExoPlaybackException { Timeline oldTimeline = timeline; timeline = timelineAndManifest.first; + mediaPeriodInfoSequence.setTimeline(timeline); Object manifest = timelineAndManifest.second; - int processedInitialSeekCount = 0; if (oldTimeline == null) { if (pendingInitialSeekCount > 0) { Pair periodPosition = resolveSeekPosition(pendingSeekPosition); - processedInitialSeekCount = pendingInitialSeekCount; + int processedInitialSeekCount = pendingInitialSeekCount; pendingInitialSeekCount = 0; pendingSeekPosition = null; if (periodPosition == null) { // The seek position was valid for the timeline that it was performed into, but the // timeline has changed and a suitable seek position could not be resolved in the new one. handleSourceInfoRefreshEndedPlayback(manifest, processedInitialSeekCount); - return; + } else { + int periodIndex = periodPosition.first; + long positionUs = periodPosition.second; + MediaPeriodId periodId = + mediaPeriodInfoSequence.resolvePeriodPositionForAds(periodIndex, positionUs); + playbackInfo = new PlaybackInfo(periodId, periodId.isAd() ? 0 : positionUs, positionUs); + notifySourceInfoRefresh(manifest, processedInitialSeekCount); } - playbackInfo = new PlaybackInfo(periodPosition.first, periodPosition.second); } else if (playbackInfo.startPositionUs == C.TIME_UNSET) { if (timeline.isEmpty()) { - handleSourceInfoRefreshEndedPlayback(manifest, processedInitialSeekCount); - return; + handleSourceInfoRefreshEndedPlayback(manifest); + } else { + Pair defaultPosition = getPeriodPosition(0, C.TIME_UNSET); + int periodIndex = defaultPosition.first; + long startPositionUs = defaultPosition.second; + MediaPeriodId periodId = mediaPeriodInfoSequence.resolvePeriodPositionForAds(periodIndex, + startPositionUs); + playbackInfo = new PlaybackInfo(periodId, periodId.isAd() ? 0 : startPositionUs, + startPositionUs); + notifySourceInfoRefresh(manifest); } - Pair defaultPosition = getPeriodPosition(0, C.TIME_UNSET); - playbackInfo = new PlaybackInfo(defaultPosition.first, defaultPosition.second); + } else { + notifySourceInfoRefresh(manifest); } - } - - MediaPeriodHolder periodHolder = playingPeriodHolder != null ? playingPeriodHolder - : loadingPeriodHolder; - if (periodHolder == null) { - // We don't have any period holders, so we're done. - notifySourceInfoRefresh(manifest, processedInitialSeekCount); return; } - int periodIndex = timeline.getIndexOfPeriod(periodHolder.uid); + int playingPeriodIndex = playbackInfo.periodId.periodIndex; + MediaPeriodHolder periodHolder = playingPeriodHolder != null ? playingPeriodHolder + : loadingPeriodHolder; + if (periodHolder == null && playingPeriodIndex >= oldTimeline.getPeriodCount()) { + notifySourceInfoRefresh(manifest); + return; + } + Object playingPeriodUid = periodHolder == null + ? oldTimeline.getPeriod(playingPeriodIndex, period, true).uid : periodHolder.uid; + int periodIndex = timeline.getIndexOfPeriod(playingPeriodUid); if (periodIndex == C.INDEX_UNSET) { // We didn't find the current period in the new timeline. Attempt to resolve a subsequent // period whose window we can restart from. - int newPeriodIndex = resolveSubsequentPeriod(periodHolder.index, oldTimeline, timeline); + int newPeriodIndex = resolveSubsequentPeriod(playingPeriodIndex, oldTimeline, timeline); if (newPeriodIndex == C.INDEX_UNSET) { // We failed to resolve a suitable restart position. - handleSourceInfoRefreshEndedPlayback(manifest, processedInitialSeekCount); + handleSourceInfoRefreshEndedPlayback(manifest); return; } // We resolved a subsequent period. Seek to the default position in the corresponding window. @@ -932,52 +1052,75 @@ import java.io.IOException; newPeriodIndex = defaultPosition.first; long newPositionUs = defaultPosition.second; timeline.getPeriod(newPeriodIndex, period, true); - // Clear the index of each holder that doesn't contain the default position. If a holder - // contains the default position then update its index so it can be re-used when seeking. - Object newPeriodUid = period.uid; - periodHolder.index = C.INDEX_UNSET; - while (periodHolder.next != null) { - periodHolder = periodHolder.next; - periodHolder.index = periodHolder.uid.equals(newPeriodUid) ? newPeriodIndex : C.INDEX_UNSET; + if (periodHolder != null) { + // Clear the index of each holder that doesn't contain the default position. If a holder + // contains the default position then update its index so it can be re-used when seeking. + Object newPeriodUid = period.uid; + periodHolder.info = periodHolder.info.copyWithPeriodIndex(C.INDEX_UNSET); + while (periodHolder.next != null) { + periodHolder = periodHolder.next; + if (periodHolder.uid.equals(newPeriodUid)) { + periodHolder.info = mediaPeriodInfoSequence.getUpdatedMediaPeriodInfo(periodHolder.info, + newPeriodIndex); + } else { + periodHolder.info = periodHolder.info.copyWithPeriodIndex(C.INDEX_UNSET); + } + } } // Actually do the seek. - newPositionUs = seekToPeriodPosition(newPeriodIndex, newPositionUs); - playbackInfo = new PlaybackInfo(newPeriodIndex, newPositionUs); - notifySourceInfoRefresh(manifest, processedInitialSeekCount); + MediaPeriodId periodId = new MediaPeriodId(newPeriodIndex); + newPositionUs = seekToPeriodPosition(periodId, newPositionUs); + playbackInfo = new PlaybackInfo(periodId, newPositionUs); + notifySourceInfoRefresh(manifest); return; } - // The current period is in the new timeline. Update the holder and playbackInfo. - timeline.getPeriod(periodIndex, period); - boolean isLastPeriod = periodIndex == timeline.getPeriodCount() - 1 - && !timeline.getWindow(period.windowIndex, window).isDynamic; - periodHolder.setIndex(periodIndex, isLastPeriod); - boolean seenReadingPeriod = periodHolder == readingPeriodHolder; - if (periodIndex != playbackInfo.periodIndex) { + // The current period is in the new timeline. Update the playback info. + if (periodIndex != playingPeriodIndex) { playbackInfo = playbackInfo.copyWithPeriodIndex(periodIndex); } - // If there are subsequent holders, update the index for each of them. If we find a holder - // that's inconsistent with the new timeline then take appropriate action. + if (playbackInfo.periodId.isAd()) { + // Check that the playing ad hasn't been marked as played. If it has, skip forward. + MediaPeriodId periodId = mediaPeriodInfoSequence.resolvePeriodPositionForAds(periodIndex, + playbackInfo.contentPositionUs); + if (!periodId.isAd() || periodId.adIndexInAdGroup != playbackInfo.periodId.adIndexInAdGroup) { + long newPositionUs = seekToPeriodPosition(periodId, playbackInfo.contentPositionUs); + long contentPositionUs = periodId.isAd() ? playbackInfo.contentPositionUs : C.TIME_UNSET; + playbackInfo = new PlaybackInfo(periodId, newPositionUs, contentPositionUs); + notifySourceInfoRefresh(manifest); + return; + } + } + + if (periodHolder == null) { + // We don't have any period holders, so we're done. + notifySourceInfoRefresh(manifest); + return; + } + + // Update the holder indices. If we find a subsequent holder that's inconsistent with the new + // timeline then take appropriate action. + periodHolder = updatePeriodInfo(periodHolder, periodIndex); while (periodHolder.next != null) { MediaPeriodHolder previousPeriodHolder = periodHolder; periodHolder = periodHolder.next; - periodIndex++; - timeline.getPeriod(periodIndex, period, true); - isLastPeriod = periodIndex == timeline.getPeriodCount() - 1 - && !timeline.getWindow(period.windowIndex, window).isDynamic; - if (periodHolder.uid.equals(period.uid)) { + periodIndex = timeline.getNextPeriodIndex(periodIndex, period, window, repeatMode); + if (periodIndex != C.INDEX_UNSET + && periodHolder.uid.equals(timeline.getPeriod(periodIndex, period, true).uid)) { // The holder is consistent with the new timeline. Update its index and continue. - periodHolder.setIndex(periodIndex, isLastPeriod); - seenReadingPeriod |= (periodHolder == readingPeriodHolder); + periodHolder = updatePeriodInfo(periodHolder, periodIndex); } else { // The holder is inconsistent with the new timeline. - if (!seenReadingPeriod) { + boolean seenReadingPeriodHolder = + readingPeriodHolder != null && readingPeriodHolder.index < periodHolder.index; + if (!seenReadingPeriodHolder) { // Renderers may have read from a period that's been removed. Seek back to the current // position of the playing period to make sure none of the removed period is played. - periodIndex = playingPeriodHolder.index; - long newPositionUs = seekToPeriodPosition(periodIndex, playbackInfo.positionUs); - playbackInfo = new PlaybackInfo(periodIndex, newPositionUs); + long newPositionUs = + seekToPeriodPosition(playingPeriodHolder.info.id, playbackInfo.positionUs); + playbackInfo = new PlaybackInfo(playingPeriodHolder.info.id, newPositionUs, + playbackInfo.contentPositionUs); } else { // Update the loading period to be the last period that's still valid, and release all // subsequent periods. @@ -990,7 +1133,22 @@ import java.io.IOException; } } - notifySourceInfoRefresh(manifest, processedInitialSeekCount); + notifySourceInfoRefresh(manifest); + } + + private MediaPeriodHolder updatePeriodInfo(MediaPeriodHolder periodHolder, int periodIndex) { + while (true) { + periodHolder.info = + mediaPeriodInfoSequence.getUpdatedMediaPeriodInfo(periodHolder.info, periodIndex); + if (periodHolder.info.isLastInTimelinePeriod || periodHolder.next == null) { + return periodHolder; + } + periodHolder = periodHolder.next; + } + } + + private void handleSourceInfoRefreshEndedPlayback(Object manifest) { + handleSourceInfoRefreshEndedPlayback(manifest, 0); } private void handleSourceInfoRefreshEndedPlayback(Object manifest, @@ -1000,11 +1158,15 @@ import java.io.IOException; notifySourceInfoRefresh(manifest, processedInitialSeekCount); // Set the internal position to (0,TIME_UNSET) so that a subsequent seek to (0,0) isn't ignored. playbackInfo = new PlaybackInfo(0, C.TIME_UNSET); - setState(ExoPlayer.STATE_ENDED); + setState(Player.STATE_ENDED); // Reset, but retain the source so that it can still be used should a seek occur. resetInternal(false); } + private void notifySourceInfoRefresh(Object manifest) { + notifySourceInfoRefresh(manifest, 0); + } + private void notifySourceInfoRefresh(Object manifest, int processedInitialSeekCount) { eventHandler.obtainMessage(MSG_SOURCE_INFO_REFRESHED, new SourceInfo(timeline, manifest, playbackInfo, processedInitialSeekCount)).sendToTarget(); @@ -1023,9 +1185,15 @@ import java.io.IOException; private int resolveSubsequentPeriod(int oldPeriodIndex, Timeline oldTimeline, Timeline newTimeline) { int newPeriodIndex = C.INDEX_UNSET; - while (newPeriodIndex == C.INDEX_UNSET && oldPeriodIndex < oldTimeline.getPeriodCount() - 1) { + int maxIterations = oldTimeline.getPeriodCount(); + for (int i = 0; i < maxIterations && newPeriodIndex == C.INDEX_UNSET; i++) { + oldPeriodIndex = oldTimeline.getNextPeriodIndex(oldPeriodIndex, period, window, repeatMode); + if (oldPeriodIndex == C.INDEX_UNSET) { + // We've reached the end of the old timeline. + break; + } newPeriodIndex = newTimeline.getIndexOfPeriod( - oldTimeline.getPeriod(++oldPeriodIndex, period, true).uid); + oldTimeline.getPeriod(oldPeriodIndex, period, true).uid); } return newPeriodIndex; } @@ -1049,7 +1217,7 @@ import java.io.IOException; // Map the SeekPosition to a position in the corresponding timeline. Pair periodPosition; try { - periodPosition = getPeriodPosition(seekTimeline, seekPosition.windowIndex, + periodPosition = seekTimeline.getPeriodPosition(window, period, seekPosition.windowIndex, seekPosition.windowPositionUs); } catch (IndexOutOfBoundsException e) { // The window index of the seek position was outside the bounds of the timeline. @@ -1078,53 +1246,11 @@ import java.io.IOException; } /** - * Calls {@link #getPeriodPosition(Timeline, int, long)} using the current timeline. + * Calls {@link Timeline#getPeriodPosition(Timeline.Window, Timeline.Period, int, long)} using the + * current timeline. */ private Pair getPeriodPosition(int windowIndex, long windowPositionUs) { - return getPeriodPosition(timeline, windowIndex, windowPositionUs); - } - - /** - * Calls {@link #getPeriodPosition(Timeline, int, long, long)} with a zero default position - * projection. - */ - private Pair getPeriodPosition(Timeline timeline, int windowIndex, - long windowPositionUs) { - return getPeriodPosition(timeline, windowIndex, windowPositionUs, 0); - } - - /** - * Converts (windowIndex, windowPositionUs) to the corresponding (periodIndex, periodPositionUs). - * - * @param timeline The timeline containing the window. - * @param windowIndex The window index. - * @param windowPositionUs The window time, or {@link C#TIME_UNSET} to use the window's default - * start position. - * @param defaultPositionProjectionUs If {@code windowPositionUs} is {@link C#TIME_UNSET}, the - * duration into the future by which the window's position should be projected. - * @return The corresponding (periodIndex, periodPositionUs), or null if {@code #windowPositionUs} - * is {@link C#TIME_UNSET}, {@code defaultPositionProjectionUs} is non-zero, and the window's - * position could not be projected by {@code defaultPositionProjectionUs}. - */ - private Pair getPeriodPosition(Timeline timeline, int windowIndex, - long windowPositionUs, long defaultPositionProjectionUs) { - Assertions.checkIndex(windowIndex, 0, timeline.getWindowCount()); - timeline.getWindow(windowIndex, window, false, defaultPositionProjectionUs); - if (windowPositionUs == C.TIME_UNSET) { - windowPositionUs = window.getDefaultPositionUs(); - if (windowPositionUs == C.TIME_UNSET) { - return null; - } - } - int periodIndex = window.firstPeriodIndex; - long periodPositionUs = window.getPositionInFirstPeriodUs() + windowPositionUs; - long periodDurationUs = timeline.getPeriod(periodIndex, period).getDurationUs(); - while (periodDurationUs != C.TIME_UNSET && periodPositionUs >= periodDurationUs - && periodIndex < window.lastPeriodIndex) { - periodPositionUs -= periodDurationUs; - periodDurationUs = timeline.getPeriod(++periodIndex, period).getDurationUs(); - } - return Pair.create(periodIndex, periodPositionUs); + return timeline.getPeriodPosition(window, period, windowIndex, windowPositionUs); } private void updatePeriods() throws ExoPlaybackException, IOException { @@ -1138,7 +1264,7 @@ import java.io.IOException; maybeUpdateLoadingPeriod(); if (loadingPeriodHolder == null || loadingPeriodHolder.isFullyBuffered()) { setIsLoading(false); - } else if (loadingPeriodHolder != null && loadingPeriodHolder.needsContinueLoading) { + } else if (loadingPeriodHolder != null && !isLoading) { maybeContinueLoading(); } @@ -1154,13 +1280,13 @@ import java.io.IOException; // the end of the playing period, so advance playback to the next period. playingPeriodHolder.release(); setPlayingPeriodHolder(playingPeriodHolder.next); - playbackInfo = new PlaybackInfo(playingPeriodHolder.index, - playingPeriodHolder.startPositionUs); + playbackInfo = new PlaybackInfo(playingPeriodHolder.info.id, + playingPeriodHolder.info.startPositionUs, playingPeriodHolder.info.contentPositionUs); updatePlaybackPositions(); eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget(); } - if (readingPeriodHolder.isLast) { + if (readingPeriodHolder.info.isFinal) { for (int i = 0; i < renderers.length; i++) { Renderer renderer = renderers[i]; SampleStream sampleStream = readingPeriodHolder.sampleStreams[i]; @@ -1224,76 +1350,41 @@ import java.io.IOException; } private void maybeUpdateLoadingPeriod() throws IOException { - int newLoadingPeriodIndex; + MediaPeriodInfo info; if (loadingPeriodHolder == null) { - newLoadingPeriodIndex = playbackInfo.periodIndex; + info = mediaPeriodInfoSequence.getFirstMediaPeriodInfo(playbackInfo); } else { - int loadingPeriodIndex = loadingPeriodHolder.index; - if (loadingPeriodHolder.isLast || !loadingPeriodHolder.isFullyBuffered() - || timeline.getPeriod(loadingPeriodIndex, period).getDurationUs() == C.TIME_UNSET) { - // Either the existing loading period is the last period, or we are not ready to advance to - // loading the next period because it hasn't been fully buffered or its duration is unknown. + if (loadingPeriodHolder.info.isFinal || !loadingPeriodHolder.isFullyBuffered() + || loadingPeriodHolder.info.durationUs == C.TIME_UNSET) { return; } - if (playingPeriodHolder != null - && loadingPeriodIndex - playingPeriodHolder.index == MAXIMUM_BUFFER_AHEAD_PERIODS) { - // We are already buffering the maximum number of periods ahead. - return; + if (playingPeriodHolder != null) { + int bufferAheadPeriodCount = loadingPeriodHolder.index - playingPeriodHolder.index; + if (bufferAheadPeriodCount == MAXIMUM_BUFFER_AHEAD_PERIODS) { + // We are already buffering the maximum number of periods ahead. + return; + } } - newLoadingPeriodIndex = loadingPeriodHolder.index + 1; + info = mediaPeriodInfoSequence.getNextMediaPeriodInfo(loadingPeriodHolder.info, + loadingPeriodHolder.getRendererOffset(), rendererPositionUs); } - - if (newLoadingPeriodIndex >= timeline.getPeriodCount()) { - // The next period is not available yet. + if (info == null) { mediaSource.maybeThrowSourceInfoRefreshError(); return; } - long newLoadingPeriodStartPositionUs; - if (loadingPeriodHolder == null) { - newLoadingPeriodStartPositionUs = playbackInfo.positionUs; - } else { - int newLoadingWindowIndex = timeline.getPeriod(newLoadingPeriodIndex, period).windowIndex; - if (newLoadingPeriodIndex - != timeline.getWindow(newLoadingWindowIndex, window).firstPeriodIndex) { - // We're starting to buffer a new period in the current window. Always start from the - // beginning of the period. - newLoadingPeriodStartPositionUs = 0; - } else { - // We're starting to buffer a new window. When playback transitions to this window we'll - // want it to be from its default start position. The expected delay until playback - // transitions is equal the duration of media that's currently buffered (assuming no - // interruptions). Hence we project the default start position forward by the duration of - // the buffer, and start buffering from this point. - long defaultPositionProjectionUs = loadingPeriodHolder.getRendererOffset() - + timeline.getPeriod(loadingPeriodHolder.index, period).getDurationUs() - - rendererPositionUs; - Pair defaultPosition = getPeriodPosition(timeline, newLoadingWindowIndex, - C.TIME_UNSET, Math.max(0, defaultPositionProjectionUs)); - if (defaultPosition == null) { - return; - } - - newLoadingPeriodIndex = defaultPosition.first; - newLoadingPeriodStartPositionUs = defaultPosition.second; - } - } - long rendererPositionOffsetUs = loadingPeriodHolder == null - ? newLoadingPeriodStartPositionUs + RENDERER_TIMESTAMP_OFFSET_US - : (loadingPeriodHolder.getRendererOffset() - + timeline.getPeriod(loadingPeriodHolder.index, period).getDurationUs()); - timeline.getPeriod(newLoadingPeriodIndex, period, true); - boolean isLastPeriod = newLoadingPeriodIndex == timeline.getPeriodCount() - 1 - && !timeline.getWindow(period.windowIndex, window).isDynamic; + ? RENDERER_TIMESTAMP_OFFSET_US + : (loadingPeriodHolder.getRendererOffset() + loadingPeriodHolder.info.durationUs); + int holderIndex = loadingPeriodHolder == null ? 0 : loadingPeriodHolder.index + 1; + Object uid = timeline.getPeriod(info.id.periodIndex, period, true).uid; MediaPeriodHolder newPeriodHolder = new MediaPeriodHolder(renderers, rendererCapabilities, - rendererPositionOffsetUs, trackSelector, loadControl, mediaSource, period.uid, - newLoadingPeriodIndex, isLastPeriod, newLoadingPeriodStartPositionUs); + rendererPositionOffsetUs, trackSelector, loadControl, mediaSource, uid, holderIndex, info); if (loadingPeriodHolder != null) { loadingPeriodHolder.next = newPeriodHolder; } loadingPeriodHolder = newPeriodHolder; - loadingPeriodHolder.mediaPeriod.prepare(this); + loadingPeriodHolder.mediaPeriod.prepare(this, info.startPositionUs); setIsLoading(true); } @@ -1306,7 +1397,7 @@ import java.io.IOException; if (playingPeriodHolder == null) { // This is the first prepared period, so start playing it. readingPeriodHolder = loadingPeriodHolder; - resetRendererPosition(readingPeriodHolder.startPositionUs); + resetRendererPosition(readingPeriodHolder.info.startPositionUs); setPlayingPeriodHolder(readingPeriodHolder); } maybeContinueLoading(); @@ -1321,21 +1412,10 @@ import java.io.IOException; } private void maybeContinueLoading() { - long nextLoadPositionUs = !loadingPeriodHolder.prepared ? 0 - : loadingPeriodHolder.mediaPeriod.getNextLoadPositionUs(); - if (nextLoadPositionUs == C.TIME_END_OF_SOURCE) { - setIsLoading(false); - } else { - long loadingPeriodPositionUs = loadingPeriodHolder.toPeriodTime(rendererPositionUs); - long bufferedDurationUs = nextLoadPositionUs - loadingPeriodPositionUs; - boolean continueLoading = loadControl.shouldContinueLoading(bufferedDurationUs); - setIsLoading(continueLoading); - if (continueLoading) { - loadingPeriodHolder.needsContinueLoading = false; - loadingPeriodHolder.mediaPeriod.continueLoading(loadingPeriodPositionUs); - } else { - loadingPeriodHolder.needsContinueLoading = true; - } + boolean continueLoading = loadingPeriodHolder.shouldContinueLoading(rendererPositionUs); + setIsLoading(continueLoading); + if (continueLoading) { + loadingPeriodHolder.continueLoading(rendererPositionUs); } } @@ -1395,7 +1475,7 @@ import java.io.IOException; RendererConfiguration rendererConfiguration = playingPeriodHolder.trackSelectorResult.rendererConfigurations[i]; // The renderer needs enabling with its new track selection. - boolean playing = playWhenReady && state == ExoPlayer.STATE_READY; + boolean playing = playWhenReady && state == Player.STATE_READY; // Consider as joining only if the renderer was previously disabled. boolean joining = !rendererWasEnabledFlags[i] && playing; // Build an array of formats contained by the selection. @@ -1432,17 +1512,15 @@ import java.io.IOException; public final MediaPeriod mediaPeriod; public final Object uid; + public final int index; public final SampleStream[] sampleStreams; public final boolean[] mayRetainStreamFlags; public final long rendererPositionOffsetUs; - public int index; - public long startPositionUs; - public boolean isLast; + public MediaPeriodInfo info; public boolean prepared; public boolean hasEnabledTracks; public MediaPeriodHolder next; - public boolean needsContinueLoading; public TrackSelectorResult trackSelectorResult; private final Renderer[] renderers; @@ -1455,8 +1533,7 @@ import java.io.IOException; public MediaPeriodHolder(Renderer[] renderers, RendererCapabilities[] rendererCapabilities, long rendererPositionOffsetUs, TrackSelector trackSelector, LoadControl loadControl, - MediaSource mediaSource, Object periodUid, int periodIndex, boolean isLastPeriod, - long startPositionUs) { + MediaSource mediaSource, Object periodUid, int index, MediaPeriodInfo info) { this.renderers = renderers; this.rendererCapabilities = rendererCapabilities; this.rendererPositionOffsetUs = rendererPositionOffsetUs; @@ -1464,13 +1541,17 @@ import java.io.IOException; this.loadControl = loadControl; this.mediaSource = mediaSource; this.uid = Assertions.checkNotNull(periodUid); - this.index = periodIndex; - this.isLast = isLastPeriod; - this.startPositionUs = startPositionUs; + this.index = index; + this.info = info; sampleStreams = new SampleStream[renderers.length]; mayRetainStreamFlags = new boolean[renderers.length]; - mediaPeriod = mediaSource.createPeriod(periodIndex, loadControl.getAllocator(), - startPositionUs); + MediaPeriod mediaPeriod = mediaSource.createPeriod(info.id, loadControl.getAllocator()); + if (info.endPositionUs != C.TIME_END_OF_SOURCE) { + ClippingMediaPeriod clippingMediaPeriod = new ClippingMediaPeriod(mediaPeriod, true); + clippingMediaPeriod.setClipping(0, info.endPositionUs); + mediaPeriod = clippingMediaPeriod; + } + this.mediaPeriod = mediaPeriod; } public long toRendererTime(long periodTimeUs) { @@ -1482,12 +1563,8 @@ import java.io.IOException; } public long getRendererOffset() { - return rendererPositionOffsetUs - startPositionUs; - } - - public void setIndex(int index, boolean isLast) { - this.index = index; - this.isLast = isLast; + return index == 0 ? rendererPositionOffsetUs + : (rendererPositionOffsetUs - info.startPositionUs); } public boolean isFullyBuffered() { @@ -1495,10 +1572,40 @@ import java.io.IOException; && (!hasEnabledTracks || mediaPeriod.getBufferedPositionUs() == C.TIME_END_OF_SOURCE); } + public boolean haveSufficientBuffer(boolean rebuffering, long rendererPositionUs) { + long bufferedPositionUs = !prepared ? info.startPositionUs + : mediaPeriod.getBufferedPositionUs(); + if (bufferedPositionUs == C.TIME_END_OF_SOURCE) { + if (info.isFinal) { + return true; + } + bufferedPositionUs = info.durationUs; + } + return loadControl.shouldStartPlayback(bufferedPositionUs - toPeriodTime(rendererPositionUs), + rebuffering); + } + public void handlePrepared() throws ExoPlaybackException { prepared = true; selectTracks(); - startPositionUs = updatePeriodTrackSelection(startPositionUs, false); + long newStartPositionUs = updatePeriodTrackSelection(info.startPositionUs, false); + info = info.copyWithStartPositionUs(newStartPositionUs); + } + + public boolean shouldContinueLoading(long rendererPositionUs) { + long nextLoadPositionUs = !prepared ? 0 : mediaPeriod.getNextLoadPositionUs(); + if (nextLoadPositionUs == C.TIME_END_OF_SOURCE) { + return false; + } else { + long loadingPeriodPositionUs = toPeriodTime(rendererPositionUs); + long bufferedDurationUs = nextLoadPositionUs - loadingPeriodPositionUs; + return loadControl.shouldContinueLoading(bufferedDurationUs); + } + } + + public void continueLoading(long rendererPositionUs) { + long loadingPeriodPositionUs = toPeriodTime(rendererPositionUs); + mediaPeriod.continueLoading(loadingPeriodPositionUs); } public boolean selectTracks() throws ExoPlaybackException { @@ -1547,7 +1654,11 @@ import java.io.IOException; public void release() { try { - mediaSource.releasePeriod(mediaPeriod); + if (info.endPositionUs != C.TIME_END_OF_SOURCE) { + mediaSource.releasePeriod(((ClippingMediaPeriod) mediaPeriod).mediaPeriod); + } else { + mediaSource.releasePeriod(mediaPeriod); + } } catch (RuntimeException e) { // There's nothing we can do. Log.e(TAG, "Period release failed.", e); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayerLibraryInfo.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayerLibraryInfo.java index 0e25366cb..2f3f8278a 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayerLibraryInfo.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayerLibraryInfo.java @@ -15,22 +15,29 @@ */ package org.telegram.messenger.exoplayer2; +import java.util.HashSet; + /** * Information about the ExoPlayer library. */ -public interface ExoPlayerLibraryInfo { +public final class ExoPlayerLibraryInfo { + + /** + * A tag to use when logging library information. + */ + public static final String TAG = "ExoPlayer"; /** * The version of the library expressed as a string, for example "1.2.3". */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa. - String VERSION = "2.4.0"; + public static final String VERSION = "2.5.4"; /** * The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. - String VERSION_SLASHY = "ExoPlayerLib/2.4.0"; + public static final String VERSION_SLASHY = "ExoPlayerLib/2.5.4"; /** * The version of the library expressed as an integer, for example 1002003. @@ -40,18 +47,41 @@ public interface ExoPlayerLibraryInfo { * integer version 123045006 (123-045-006). */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. - int VERSION_INT = 2004000; + public static final int VERSION_INT = 2005004; /** * Whether the library was compiled with {@link org.telegram.messenger.exoplayer2.util.Assertions} * checks enabled. */ - boolean ASSERTIONS_ENABLED = true; + public static final boolean ASSERTIONS_ENABLED = true; /** * Whether the library was compiled with {@link org.telegram.messenger.exoplayer2.util.TraceUtil} * trace enabled. */ - boolean TRACE_ENABLED = true; + public static final boolean TRACE_ENABLED = true; + + private static final HashSet registeredModules = new HashSet<>(); + private static String registeredModulesString = "goog.exo.core"; + + private ExoPlayerLibraryInfo() {} // Prevents instantiation. + + /** + * Returns a string consisting of registered module names separated by ", ". + */ + public static synchronized String registeredModules() { + return registeredModulesString; + } + + /** + * Registers a module to be returned in the {@link #registeredModules()} string. + * + * @param name The name of the module being registered. + */ + public static synchronized void registerModule(String name) { + if (registeredModules.add(name)) { + registeredModulesString = registeredModulesString + ", " + name; + } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/Format.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/Format.java index dda02f3d9..56b31387c 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/Format.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/Format.java @@ -286,9 +286,14 @@ public final class Format implements Parcelable { OFFSET_SAMPLE_RELATIVE, null, null, null); } - public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs, - int bitrate, @C.SelectionFlags int selectionFlags, String language, DrmInitData drmInitData) { - return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language, + public static Format createTextSampleFormat(String id, String sampleMimeType, + @C.SelectionFlags int selectionFlags, String language) { + return createTextSampleFormat(id, sampleMimeType, selectionFlags, language, null); + } + + public static Format createTextSampleFormat(String id, String sampleMimeType, + @C.SelectionFlags int selectionFlags, String language, DrmInitData drmInitData) { + return createTextSampleFormat(id, sampleMimeType, null, NO_VALUE, selectionFlags, language, NO_VALUE, drmInitData, OFFSET_SAMPLE_RELATIVE, Collections.emptyList()); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/MediaPeriodInfoSequence.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/MediaPeriodInfoSequence.java new file mode 100755 index 000000000..ac4470103 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/MediaPeriodInfoSequence.java @@ -0,0 +1,352 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2; + +import android.util.Pair; +import org.telegram.messenger.exoplayer2.ExoPlayerImplInternal.PlaybackInfo; +import org.telegram.messenger.exoplayer2.Player.RepeatMode; +import org.telegram.messenger.exoplayer2.source.MediaPeriod; +import org.telegram.messenger.exoplayer2.source.MediaSource.MediaPeriodId; + +/** + * Provides a sequence of {@link MediaPeriodInfo}s to the player, determining the order and + * start/end positions for {@link MediaPeriod}s to load and play. + */ +/* package */ final class MediaPeriodInfoSequence { + + // TODO: Consider merging this class with the MediaPeriodHolder queue in ExoPlayerImplInternal. + + /** + * Stores the information required to load and play a {@link MediaPeriod}. + */ + public static final class MediaPeriodInfo { + + /** + * The media period's identifier. + */ + public final MediaPeriodId id; + /** + * The start position of the media to play within the media period, in microseconds. + */ + public final long startPositionUs; + /** + * The end position of the media to play within the media period, in microseconds, or + * {@link C#TIME_END_OF_SOURCE} if the end position is the end of the media period. + */ + public final long endPositionUs; + /** + * If this is an ad, the position to play in the next content media period. {@link C#TIME_UNSET} + * otherwise. + */ + public final long contentPositionUs; + /** + * The duration of the media to play within the media period, in microseconds, or + * {@link C#TIME_UNSET} if not known. + */ + public final long durationUs; + /** + * Whether this is the last media period in its timeline period (e.g., a postroll ad, or a media + * period corresponding to a timeline period without ads). + */ + public final boolean isLastInTimelinePeriod; + /** + * Whether this is the last media period in the entire timeline. If true, + * {@link #isLastInTimelinePeriod} will also be true. + */ + public final boolean isFinal; + + private MediaPeriodInfo(MediaPeriodId id, long startPositionUs, long endPositionUs, + long contentPositionUs, long durationUs, boolean isLastInTimelinePeriod, boolean isFinal) { + this.id = id; + this.startPositionUs = startPositionUs; + this.endPositionUs = endPositionUs; + this.contentPositionUs = contentPositionUs; + this.durationUs = durationUs; + this.isLastInTimelinePeriod = isLastInTimelinePeriod; + this.isFinal = isFinal; + } + + /** + * Returns a copy of this instance with the period identifier's period index set to the + * specified value. + */ + public MediaPeriodInfo copyWithPeriodIndex(int periodIndex) { + return new MediaPeriodInfo(id.copyWithPeriodIndex(periodIndex), startPositionUs, + endPositionUs, contentPositionUs, durationUs, isLastInTimelinePeriod, isFinal); + } + + /** + * Returns a copy of this instance with the start position set to the specified value. + */ + public MediaPeriodInfo copyWithStartPositionUs(long startPositionUs) { + return new MediaPeriodInfo(id, startPositionUs, endPositionUs, contentPositionUs, durationUs, + isLastInTimelinePeriod, isFinal); + } + + } + + private final Timeline.Period period; + private final Timeline.Window window; + + private Timeline timeline; + @RepeatMode + private int repeatMode; + + /** + * Creates a new media period info sequence. + */ + public MediaPeriodInfoSequence() { + period = new Timeline.Period(); + window = new Timeline.Window(); + } + + /** + * Sets the {@link Timeline}. Call {@link #getUpdatedMediaPeriodInfo} to update period information + * taking into account the new timeline. + */ + public void setTimeline(Timeline timeline) { + this.timeline = timeline; + } + + /** + * Sets the {@link RepeatMode}. Call {@link #getUpdatedMediaPeriodInfo} to update period + * information taking into account the new repeat mode. + */ + public void setRepeatMode(@RepeatMode int repeatMode) { + this.repeatMode = repeatMode; + } + + /** + * Returns the first {@link MediaPeriodInfo} to play, based on the specified playback position. + */ + public MediaPeriodInfo getFirstMediaPeriodInfo(PlaybackInfo playbackInfo) { + return getMediaPeriodInfo(playbackInfo.periodId, playbackInfo.contentPositionUs, + playbackInfo.startPositionUs); + } + + /** + * Returns the {@link MediaPeriodInfo} following {@code currentMediaPeriodInfo}. + * + * @param currentMediaPeriodInfo The current media period info. + * @param rendererOffsetUs The current renderer offset in microseconds. + * @param rendererPositionUs The current renderer position in microseconds. + * @return The following media period info, or {@code null} if it is not yet possible to get the + * next media period info. + */ + public MediaPeriodInfo getNextMediaPeriodInfo(MediaPeriodInfo currentMediaPeriodInfo, + long rendererOffsetUs, long rendererPositionUs) { + // TODO: This method is called repeatedly from ExoPlayerImplInternal.maybeUpdateLoadingPeriod + // but if the timeline is not ready to provide the next period it can't return a non-null value + // until the timeline is updated. Store whether the next timeline period is ready when the + // timeline is updated, to avoid repeatedly checking the same timeline. + if (currentMediaPeriodInfo.isLastInTimelinePeriod) { + int nextPeriodIndex = timeline.getNextPeriodIndex(currentMediaPeriodInfo.id.periodIndex, + period, window, repeatMode); + if (nextPeriodIndex == C.INDEX_UNSET) { + // We can't create a next period yet. + return null; + } + + long startPositionUs; + int nextWindowIndex = timeline.getPeriod(nextPeriodIndex, period).windowIndex; + if (timeline.getWindow(nextWindowIndex, window).firstPeriodIndex == nextPeriodIndex) { + // We're starting to buffer a new window. When playback transitions to this window we'll + // want it to be from its default start position. The expected delay until playback + // transitions is equal the duration of media that's currently buffered (assuming no + // interruptions). Hence we project the default start position forward by the duration of + // the buffer, and start buffering from this point. + long defaultPositionProjectionUs = + rendererOffsetUs + currentMediaPeriodInfo.durationUs - rendererPositionUs; + Pair defaultPosition = timeline.getPeriodPosition(window, period, + nextWindowIndex, C.TIME_UNSET, Math.max(0, defaultPositionProjectionUs)); + if (defaultPosition == null) { + return null; + } + nextPeriodIndex = defaultPosition.first; + startPositionUs = defaultPosition.second; + } else { + startPositionUs = 0; + } + MediaPeriodId periodId = resolvePeriodPositionForAds(nextPeriodIndex, startPositionUs); + return getMediaPeriodInfo(periodId, startPositionUs, startPositionUs); + } + + MediaPeriodId currentPeriodId = currentMediaPeriodInfo.id; + if (currentPeriodId.isAd()) { + int currentAdGroupIndex = currentPeriodId.adGroupIndex; + timeline.getPeriod(currentPeriodId.periodIndex, period); + int adCountInCurrentAdGroup = period.getAdCountInAdGroup(currentAdGroupIndex); + if (adCountInCurrentAdGroup == C.LENGTH_UNSET) { + return null; + } + int nextAdIndexInAdGroup = currentPeriodId.adIndexInAdGroup + 1; + if (nextAdIndexInAdGroup < adCountInCurrentAdGroup) { + // Play the next ad in the ad group if it's available. + return !period.isAdAvailable(currentAdGroupIndex, nextAdIndexInAdGroup) ? null + : getMediaPeriodInfoForAd(currentPeriodId.periodIndex, currentAdGroupIndex, + nextAdIndexInAdGroup, currentMediaPeriodInfo.contentPositionUs); + } else { + // Play content from the ad group position. + int nextAdGroupIndex = + period.getAdGroupIndexAfterPositionUs(currentMediaPeriodInfo.contentPositionUs); + long endUs = nextAdGroupIndex == C.INDEX_UNSET ? C.TIME_END_OF_SOURCE + : period.getAdGroupTimeUs(nextAdGroupIndex); + return getMediaPeriodInfoForContent(currentPeriodId.periodIndex, + currentMediaPeriodInfo.contentPositionUs, endUs); + } + } else if (currentMediaPeriodInfo.endPositionUs != C.TIME_END_OF_SOURCE) { + // Play the next ad group if it's available. + int nextAdGroupIndex = + period.getAdGroupIndexForPositionUs(currentMediaPeriodInfo.endPositionUs); + return !period.isAdAvailable(nextAdGroupIndex, 0) ? null + : getMediaPeriodInfoForAd(currentPeriodId.periodIndex, nextAdGroupIndex, 0, + currentMediaPeriodInfo.endPositionUs); + } else { + // Check if the postroll ad should be played. + int adGroupCount = period.getAdGroupCount(); + if (adGroupCount == 0 + || period.getAdGroupTimeUs(adGroupCount - 1) != C.TIME_END_OF_SOURCE + || period.hasPlayedAdGroup(adGroupCount - 1) + || !period.isAdAvailable(adGroupCount - 1, 0)) { + return null; + } + long contentDurationUs = period.getDurationUs(); + return getMediaPeriodInfoForAd(currentPeriodId.periodIndex, adGroupCount - 1, 0, + contentDurationUs); + } + } + + /** + * Resolves the specified timeline period and position to a {@link MediaPeriodId} that should be + * played, returning an identifier for an ad group if one needs to be played before the specified + * position, or an identifier for a content media period if not. + */ + public MediaPeriodId resolvePeriodPositionForAds(int periodIndex, long positionUs) { + timeline.getPeriod(periodIndex, period); + int adGroupIndex = period.getAdGroupIndexForPositionUs(positionUs); + if (adGroupIndex == C.INDEX_UNSET) { + return new MediaPeriodId(periodIndex); + } else { + int adIndexInAdGroup = period.getPlayedAdCount(adGroupIndex); + return new MediaPeriodId(periodIndex, adGroupIndex, adIndexInAdGroup); + } + } + + /** + * Returns the {@code mediaPeriodInfo} updated to take into account the current timeline. + */ + public MediaPeriodInfo getUpdatedMediaPeriodInfo(MediaPeriodInfo mediaPeriodInfo) { + return getUpdatedMediaPeriodInfo(mediaPeriodInfo, mediaPeriodInfo.id); + } + + /** + * Returns the {@code mediaPeriodInfo} updated to take into account the current timeline, + * resetting the identifier of the media period to the specified {@code newPeriodIndex}. + */ + public MediaPeriodInfo getUpdatedMediaPeriodInfo(MediaPeriodInfo mediaPeriodInfo, + int newPeriodIndex) { + return getUpdatedMediaPeriodInfo(mediaPeriodInfo, + mediaPeriodInfo.id.copyWithPeriodIndex(newPeriodIndex)); + } + + // Internal methods. + + private MediaPeriodInfo getUpdatedMediaPeriodInfo(MediaPeriodInfo info, MediaPeriodId newId) { + long startPositionUs = info.startPositionUs; + long endPositionUs = info.endPositionUs; + boolean isLastInPeriod = isLastInPeriod(newId, endPositionUs); + boolean isLastInTimeline = isLastInTimeline(newId, isLastInPeriod); + timeline.getPeriod(newId.periodIndex, period); + long durationUs = newId.isAd() + ? period.getAdDurationUs(newId.adGroupIndex, newId.adIndexInAdGroup) + : (endPositionUs == C.TIME_END_OF_SOURCE ? period.getDurationUs() : endPositionUs); + return new MediaPeriodInfo(newId, startPositionUs, endPositionUs, info.contentPositionUs, + durationUs, isLastInPeriod, isLastInTimeline); + } + + private MediaPeriodInfo getMediaPeriodInfo(MediaPeriodId id, long contentPositionUs, + long startPositionUs) { + timeline.getPeriod(id.periodIndex, period); + if (id.isAd()) { + if (!period.isAdAvailable(id.adGroupIndex, id.adIndexInAdGroup)) { + return null; + } + return getMediaPeriodInfoForAd(id.periodIndex, id.adGroupIndex, id.adIndexInAdGroup, + contentPositionUs); + } else { + int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(startPositionUs); + long endUs = nextAdGroupIndex == C.INDEX_UNSET ? C.TIME_END_OF_SOURCE + : period.getAdGroupTimeUs(nextAdGroupIndex); + return getMediaPeriodInfoForContent(id.periodIndex, startPositionUs, endUs); + } + } + + private MediaPeriodInfo getMediaPeriodInfoForAd(int periodIndex, int adGroupIndex, + int adIndexInAdGroup, long contentPositionUs) { + MediaPeriodId id = new MediaPeriodId(periodIndex, adGroupIndex, adIndexInAdGroup); + boolean isLastInPeriod = isLastInPeriod(id, C.TIME_END_OF_SOURCE); + boolean isLastInTimeline = isLastInTimeline(id, isLastInPeriod); + long durationUs = timeline.getPeriod(id.periodIndex, period) + .getAdDurationUs(id.adGroupIndex, id.adIndexInAdGroup); + long startPositionUs = adIndexInAdGroup == period.getPlayedAdCount(adGroupIndex) + ? period.getAdResumePositionUs() : 0; + return new MediaPeriodInfo(id, startPositionUs, C.TIME_END_OF_SOURCE, contentPositionUs, + durationUs, isLastInPeriod, isLastInTimeline); + } + + private MediaPeriodInfo getMediaPeriodInfoForContent(int periodIndex, long startPositionUs, + long endUs) { + MediaPeriodId id = new MediaPeriodId(periodIndex); + boolean isLastInPeriod = isLastInPeriod(id, endUs); + boolean isLastInTimeline = isLastInTimeline(id, isLastInPeriod); + timeline.getPeriod(id.periodIndex, period); + long durationUs = endUs == C.TIME_END_OF_SOURCE ? period.getDurationUs() : endUs; + return new MediaPeriodInfo(id, startPositionUs, endUs, C.TIME_UNSET, durationUs, isLastInPeriod, + isLastInTimeline); + } + + private boolean isLastInPeriod(MediaPeriodId id, long endPositionUs) { + int adGroupCount = timeline.getPeriod(id.periodIndex, period).getAdGroupCount(); + if (adGroupCount == 0) { + return true; + } + + int lastAdGroupIndex = adGroupCount - 1; + boolean isAd = id.isAd(); + if (period.getAdGroupTimeUs(lastAdGroupIndex) != C.TIME_END_OF_SOURCE) { + // There's no postroll ad. + return !isAd && endPositionUs == C.TIME_END_OF_SOURCE; + } + + int postrollAdCount = period.getAdCountInAdGroup(lastAdGroupIndex); + if (postrollAdCount == C.LENGTH_UNSET) { + // We won't know if this is the last ad until we know how many postroll ads there are. + return false; + } + + boolean isLastAd = isAd && id.adGroupIndex == lastAdGroupIndex + && id.adIndexInAdGroup == postrollAdCount - 1; + return isLastAd || (!isAd && period.getPlayedAdCount(lastAdGroupIndex) == postrollAdCount); + } + + private boolean isLastInTimeline(MediaPeriodId id, boolean isLastMediaPeriodInPeriod) { + int windowIndex = timeline.getPeriod(id.periodIndex, period).windowIndex; + return !timeline.getWindow(windowIndex, window).isDynamic + && timeline.isLastPeriod(id.periodIndex, period, window, repeatMode) + && isLastMediaPeriodInPeriod; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/Player.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/Player.java new file mode 100755 index 000000000..881e6eb1b --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/Player.java @@ -0,0 +1,410 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2; + +import android.os.Looper; +import android.support.annotation.IntDef; +import android.support.annotation.Nullable; +import org.telegram.messenger.exoplayer2.source.TrackGroupArray; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelectionArray; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A media player interface defining traditional high-level functionality, such as the ability to + * play, pause, seek and query properties of the currently playing media. + *

+ * Some important properties of media players that implement this interface are: + *

    + *
  • They can provide a {@link Timeline} representing the structure of the media being played, + * which can be obtained by calling {@link #getCurrentTimeline()}.
  • + *
  • They can provide a {@link TrackGroupArray} defining the currently available tracks, + * which can be obtained by calling {@link #getCurrentTrackGroups()}.
  • + *
  • They contain a number of renderers, each of which is able to render tracks of a single + * type (e.g. audio, video or text). The number of renderers and their respective track types + * can be obtained by calling {@link #getRendererCount()} and {@link #getRendererType(int)}. + *
  • + *
  • They can provide a {@link TrackSelectionArray} defining which of the currently available + * tracks are selected to be rendered by each renderer. This can be obtained by calling + * {@link #getCurrentTrackSelections()}}.
  • + *
+ */ +public interface Player { + + /** + * Listener of changes in player state. + */ + interface EventListener { + + /** + * Called when the timeline and/or manifest has been refreshed. + *

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

+ * When a position discontinuity occurs as a result of a change to the timeline this method is + * not called. {@link #onTimelineChanged(Timeline, Object)} is called in this case. + */ + void onPositionDiscontinuity(); + + /** + * Called when the current playback parameters change. The playback parameters may change due to + * a call to {@link #setPlaybackParameters(PlaybackParameters)}, or the player itself may change + * them (for example, if audio playback switches to passthrough mode, where speed adjustment is + * no longer possible). + * + * @param playbackParameters The playback parameters. + */ + void onPlaybackParametersChanged(PlaybackParameters playbackParameters); + + } + + /** + * The player does not have any media to play. + */ + int STATE_IDLE = 1; + /** + * The player is not able to immediately play from its current position. This state typically + * occurs when more data needs to be loaded. + */ + int STATE_BUFFERING = 2; + /** + * The player is able to immediately play from its current position. The player will be playing if + * {@link #getPlayWhenReady()} is true, and paused otherwise. + */ + int STATE_READY = 3; + /** + * The player has finished playing the media. + */ + int STATE_ENDED = 4; + + /** + * Repeat modes for playback. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({REPEAT_MODE_OFF, REPEAT_MODE_ONE, REPEAT_MODE_ALL}) + public @interface RepeatMode {} + /** + * Normal playback without repetition. + */ + int REPEAT_MODE_OFF = 0; + /** + * "Repeat One" mode to repeat the currently playing window infinitely. + */ + int REPEAT_MODE_ONE = 1; + /** + * "Repeat All" mode to repeat the entire timeline infinitely. + */ + int REPEAT_MODE_ALL = 2; + + /** + * Register a listener to receive events from the player. The listener's methods will be called on + * the thread that was used to construct the player. However, if the thread used to construct the + * player does not have a {@link Looper}, then the listener will be called on the main thread. + * + * @param listener The listener to register. + */ + void addListener(EventListener listener); + + /** + * Unregister a listener. The listener will no longer receive events from the player. + * + * @param listener The listener to unregister. + */ + void removeListener(EventListener listener); + + /** + * Returns the current state of the player. + * + * @return One of the {@code STATE} constants defined in this interface. + */ + int getPlaybackState(); + + /** + * Sets whether playback should proceed when {@link #getPlaybackState()} == {@link #STATE_READY}. + *

+ * If the player is already in the ready state then this method can be used to pause and resume + * playback. + * + * @param playWhenReady Whether playback should proceed when ready. + */ + void setPlayWhenReady(boolean playWhenReady); + + /** + * Whether playback will proceed when {@link #getPlaybackState()} == {@link #STATE_READY}. + * + * @return Whether playback will proceed when ready. + */ + boolean getPlayWhenReady(); + + /** + * Sets the {@link RepeatMode} to be used for playback. + * + * @param repeatMode A repeat mode. + */ + void setRepeatMode(@RepeatMode int repeatMode); + + /** + * Returns the current {@link RepeatMode} used for playback. + * + * @return The current repeat mode. + */ + @RepeatMode int getRepeatMode(); + + /** + * Whether the player is currently loading the source. + * + * @return Whether the player is currently loading the source. + */ + boolean isLoading(); + + /** + * Seeks to the default position associated with the current window. The position can depend on + * the type of media being played. For live streams it will typically be the live edge of the + * window. For other streams it will typically be the start of the window. + */ + void seekToDefaultPosition(); + + /** + * Seeks to the default position associated with the specified window. The position can depend on + * the type of media being played. For live streams it will typically be the live edge of the + * window. For other streams it will typically be the start of the window. + * + * @param windowIndex The index of the window whose associated default position should be seeked + * to. + */ + void seekToDefaultPosition(int windowIndex); + + /** + * Seeks to a position specified in milliseconds in the current window. + * + * @param positionMs The seek position in the current window, or {@link C#TIME_UNSET} to seek to + * the window's default position. + */ + void seekTo(long positionMs); + + /** + * Seeks to a position specified in milliseconds in the specified window. + * + * @param windowIndex The index of the window. + * @param positionMs The seek position in the specified window, or {@link C#TIME_UNSET} to seek to + * the window's default position. + */ + void seekTo(int windowIndex, long positionMs); + + /** + * Attempts to set the playback parameters. Passing {@code null} sets the parameters to the + * default, {@link PlaybackParameters#DEFAULT}, which means there is no speed or pitch adjustment. + *

+ * Playback parameters changes may cause the player to buffer. + * {@link EventListener#onPlaybackParametersChanged(PlaybackParameters)} will be called whenever + * the currently active playback parameters change. When that listener is called, the parameters + * passed to it may not match {@code playbackParameters}. For example, the chosen speed or pitch + * may be out of range, in which case they are constrained to a set of permitted values. If it is + * not possible to change the playback parameters, the listener will not be invoked. + * + * @param playbackParameters The playback parameters, or {@code null} to use the defaults. + */ + void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters); + + /** + * Returns the currently active playback parameters. + * + * @see EventListener#onPlaybackParametersChanged(PlaybackParameters) + */ + PlaybackParameters getPlaybackParameters(); + + /** + * Stops playback. Use {@code setPlayWhenReady(false)} rather than this method if the intention + * is to pause playback. + *

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

+ * Calling this method does not reset the playback position. + */ + void stop(); + + /** + * Releases the player. This method must be called when the player is no longer required. The + * player must not be used after calling this method. + */ + void release(); + + /** + * Returns the number of renderers. + */ + int getRendererCount(); + + /** + * Returns the track type that the renderer at a given index handles. + * + * @see Renderer#getTrackType() + * @param index The index of the renderer. + * @return One of the {@code TRACK_TYPE_*} constants defined in {@link C}. + */ + int getRendererType(int index); + + /** + * Returns the available track groups. + */ + TrackGroupArray getCurrentTrackGroups(); + + /** + * Returns the current track selections for each renderer. + */ + TrackSelectionArray getCurrentTrackSelections(); + + /** + * Returns the current manifest. The type depends on the type of media being played. May be null. + */ + @Nullable Object getCurrentManifest(); + + /** + * Returns the current {@link Timeline}. Never null, but may be empty. + */ + Timeline getCurrentTimeline(); + + /** + * Returns the index of the period currently being played. + */ + int getCurrentPeriodIndex(); + + /** + * Returns the index of the window currently being played. + */ + int getCurrentWindowIndex(); + + /** + * Returns the duration of the current window in milliseconds, or {@link C#TIME_UNSET} if the + * duration is not known. + */ + long getDuration(); + + /** + * Returns the playback position in the current window, in milliseconds. + */ + long getCurrentPosition(); + + /** + * Returns an estimate of the position in the current window up to which data is buffered, in + * milliseconds. + */ + long getBufferedPosition(); + + /** + * Returns an estimate of the percentage in the current window up to which data is buffered, or 0 + * if no estimate is available. + */ + int getBufferedPercentage(); + + /** + * Returns whether the current window is dynamic, or {@code false} if the {@link Timeline} is + * empty. + * + * @see Timeline.Window#isDynamic + */ + boolean isCurrentWindowDynamic(); + + /** + * Returns whether the current window is seekable, or {@code false} if the {@link Timeline} is + * empty. + * + * @see Timeline.Window#isSeekable + */ + boolean isCurrentWindowSeekable(); + + /** + * Returns whether the player is currently playing an ad. + */ + boolean isPlayingAd(); + + /** + * If {@link #isPlayingAd()} returns true, returns the index of the ad group in the period + * currently being played. Returns {@link C#INDEX_UNSET} otherwise. + */ + int getCurrentAdGroupIndex(); + + /** + * If {@link #isPlayingAd()} returns true, returns the index of the ad in its ad group. Returns + * {@link C#INDEX_UNSET} otherwise. + */ + int getCurrentAdIndexInAdGroup(); + + /** + * If {@link #isPlayingAd()} returns {@code true}, returns the content position that will be + * played once all ads in the ad group have finished playing, in milliseconds. If there is no ad + * playing, the returned position is the same as that returned by {@link #getCurrentPosition()}. + */ + long getContentPosition(); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/RendererCapabilities.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/RendererCapabilities.java index b4f25651d..30f40963d 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/RendererCapabilities.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/RendererCapabilities.java @@ -24,14 +24,14 @@ public interface RendererCapabilities { /** * A mask to apply to the result of {@link #supportsFormat(Format)} to obtain one of - * {@link #FORMAT_HANDLED}, {@link #FORMAT_EXCEEDS_CAPABILITIES}, + * {@link #FORMAT_HANDLED}, {@link #FORMAT_EXCEEDS_CAPABILITIES}, {@link #FORMAT_UNSUPPORTED_DRM}, * {@link #FORMAT_UNSUPPORTED_SUBTYPE} and {@link #FORMAT_UNSUPPORTED_TYPE}. */ - int FORMAT_SUPPORT_MASK = 0b11; + int FORMAT_SUPPORT_MASK = 0b111; /** * The {@link Renderer} is capable of rendering the format. */ - int FORMAT_HANDLED = 0b11; + int FORMAT_HANDLED = 0b100; /** * The {@link Renderer} is capable of rendering formats with the same mime type, but the * properties of the format exceed the renderer's capability. @@ -40,7 +40,16 @@ public interface RendererCapabilities { * {@link MimeTypes#VIDEO_H264}, but the format's resolution exceeds the maximum limit supported * by the underlying H264 decoder. */ - int FORMAT_EXCEEDS_CAPABILITIES = 0b10; + int FORMAT_EXCEEDS_CAPABILITIES = 0b011; + /** + * The {@link Renderer} is capable of rendering formats with the same mime type, but the + * drm scheme used is not supported. + *

+ * Example: The {@link Renderer} is capable of rendering H264 and the format's mime type is + * {@link MimeTypes#VIDEO_H264}, but the format indicates cbcs encryption, which is not supported + * by the underlying content decryption module. + */ + int FORMAT_UNSUPPORTED_DRM = 0b010; /** * The {@link Renderer} is a general purpose renderer for formats of the same top-level type, * but is not capable of rendering the format or any other format with the same mime type because @@ -49,7 +58,7 @@ public interface RendererCapabilities { * Example: The {@link Renderer} is a general purpose audio renderer and the format's * mime type matches audio/[subtype], but there does not exist a suitable decoder for [subtype]. */ - int FORMAT_UNSUPPORTED_SUBTYPE = 0b01; + int FORMAT_UNSUPPORTED_SUBTYPE = 0b001; /** * The {@link Renderer} is not capable of rendering the format, either because it does not * support the format's top-level type, or because it's a specialized renderer for a different @@ -58,40 +67,40 @@ public interface RendererCapabilities { * Example: The {@link Renderer} is a general purpose video renderer, but the format has an * audio mime type. */ - int FORMAT_UNSUPPORTED_TYPE = 0b00; + int FORMAT_UNSUPPORTED_TYPE = 0b000; /** * A mask to apply to the result of {@link #supportsFormat(Format)} to obtain one of * {@link #ADAPTIVE_SEAMLESS}, {@link #ADAPTIVE_NOT_SEAMLESS} and {@link #ADAPTIVE_NOT_SUPPORTED}. */ - int ADAPTIVE_SUPPORT_MASK = 0b1100; + int ADAPTIVE_SUPPORT_MASK = 0b11000; /** * The {@link Renderer} can seamlessly adapt between formats. */ - int ADAPTIVE_SEAMLESS = 0b1000; + int ADAPTIVE_SEAMLESS = 0b10000; /** * The {@link Renderer} can adapt between formats, but may suffer a brief discontinuity * (~50-100ms) when adaptation occurs. */ - int ADAPTIVE_NOT_SEAMLESS = 0b0100; + int ADAPTIVE_NOT_SEAMLESS = 0b01000; /** * The {@link Renderer} does not support adaptation between formats. */ - int ADAPTIVE_NOT_SUPPORTED = 0b0000; + int ADAPTIVE_NOT_SUPPORTED = 0b00000; /** * A mask to apply to the result of {@link #supportsFormat(Format)} to obtain one of * {@link #TUNNELING_SUPPORTED} and {@link #TUNNELING_NOT_SUPPORTED}. */ - int TUNNELING_SUPPORT_MASK = 0b10000; + int TUNNELING_SUPPORT_MASK = 0b100000; /** * The {@link Renderer} supports tunneled output. */ - int TUNNELING_SUPPORTED = 0b10000; + int TUNNELING_SUPPORTED = 0b100000; /** * The {@link Renderer} does not support tunneled output. */ - int TUNNELING_NOT_SUPPORTED = 0b00000; + int TUNNELING_NOT_SUPPORTED = 0b000000; /** * Returns the track type that the {@link Renderer} handles. For example, a video renderer will @@ -108,8 +117,8 @@ public interface RendererCapabilities { * the bitwise OR of three properties: *

    *
  • The level of support for the format itself. One of {@link #FORMAT_HANDLED}, - * {@link #FORMAT_EXCEEDS_CAPABILITIES}, {@link #FORMAT_UNSUPPORTED_SUBTYPE} and - * {@link #FORMAT_UNSUPPORTED_TYPE}.
  • + * {@link #FORMAT_EXCEEDS_CAPABILITIES}, {@link #FORMAT_UNSUPPORTED_DRM}, + * {@link #FORMAT_UNSUPPORTED_SUBTYPE} and {@link #FORMAT_UNSUPPORTED_TYPE}. *
  • The level of support for adapting from the format to another format of the same mime type. * One of {@link #ADAPTIVE_SEAMLESS}, {@link #ADAPTIVE_NOT_SEAMLESS} and * {@link #ADAPTIVE_NOT_SUPPORTED}.
  • diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/SimpleExoPlayer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/SimpleExoPlayer.java index 7755a54c3..c6e4cf9e1 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/SimpleExoPlayer.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/SimpleExoPlayer.java @@ -20,12 +20,14 @@ import android.graphics.SurfaceTexture; import android.media.MediaCodec; import android.media.PlaybackParams; import android.os.Handler; +import android.os.Looper; import android.support.annotation.Nullable; import android.util.Log; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.TextureView; +import org.telegram.messenger.exoplayer2.audio.AudioAttributes; import org.telegram.messenger.exoplayer2.audio.AudioRendererEventListener; import org.telegram.messenger.exoplayer2.decoder.DecoderCounters; import org.telegram.messenger.exoplayer2.metadata.Metadata; @@ -36,8 +38,10 @@ import org.telegram.messenger.exoplayer2.text.Cue; import org.telegram.messenger.exoplayer2.text.TextRenderer; import org.telegram.messenger.exoplayer2.trackselection.TrackSelectionArray; import org.telegram.messenger.exoplayer2.trackselection.TrackSelector; +import org.telegram.messenger.exoplayer2.util.Util; import org.telegram.messenger.exoplayer2.video.VideoRendererEventListener; import java.util.List; +import java.util.concurrent.CopyOnWriteArraySet; /** * An {@link ExoPlayer} implementation that uses default {@link Renderer} components. Instances can @@ -86,6 +90,9 @@ public class SimpleExoPlayer implements ExoPlayer { private final ExoPlayer player; private final ComponentListener componentListener; + private final CopyOnWriteArraySet videoListeners; + private final CopyOnWriteArraySet textOutputs; + private final CopyOnWriteArraySet metadataOutputs; private final int videoRendererCount; private final int audioRendererCount; @@ -100,23 +107,24 @@ public class SimpleExoPlayer implements ExoPlayer { private int videoScalingMode; private SurfaceHolder surfaceHolder; private TextureView textureView; - private TextRenderer.Output textOutput; - private MetadataRenderer.Output metadataOutput; - private VideoListener videoListener; private AudioRendererEventListener audioDebugListener; private VideoRendererEventListener videoDebugListener; private DecoderCounters videoDecoderCounters; private DecoderCounters audioDecoderCounters; private int audioSessionId; - @C.StreamType - private int audioStreamType; + private AudioAttributes audioAttributes; private float audioVolume; protected SimpleExoPlayer(RenderersFactory renderersFactory, TrackSelector trackSelector, LoadControl loadControl) { componentListener = new ComponentListener(); - renderers = renderersFactory.createRenderers(new Handler(), componentListener, - componentListener, componentListener, componentListener); + videoListeners = new CopyOnWriteArraySet<>(); + textOutputs = new CopyOnWriteArraySet<>(); + metadataOutputs = new CopyOnWriteArraySet<>(); + Looper eventLooper = Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper(); + Handler eventHandler = new Handler(eventLooper); + renderers = renderersFactory.createRenderers(eventHandler, componentListener, componentListener, + componentListener, componentListener); // Obtain counts of video and audio renderers. int videoRendererCount = 0; @@ -137,7 +145,7 @@ public class SimpleExoPlayer implements ExoPlayer { // Set initial values. audioVolume = 1; audioSessionId = C.AUDIO_SESSION_ID_UNSET; - audioStreamType = C.STREAM_TYPE_DEFAULT; + audioAttributes = AudioAttributes.DEFAULT; videoScalingMode = C.VIDEO_SCALING_MODE_DEFAULT; // Build the player and associated objects. @@ -222,8 +230,9 @@ public class SimpleExoPlayer implements ExoPlayer { if (surfaceHolder == null) { setVideoSurfaceInternal(null, false); } else { - setVideoSurfaceInternal(surfaceHolder.getSurface(), false); surfaceHolder.addCallback(componentListener); + Surface surface = surfaceHolder.getSurface(); + setVideoSurfaceInternal(surface != null && surface.isValid() ? surface : null, false); } } @@ -278,9 +287,10 @@ public class SimpleExoPlayer implements ExoPlayer { if (textureView.getSurfaceTextureListener() != null) { Log.w(TAG, "Replacing existing SurfaceTextureListener."); } - SurfaceTexture surfaceTexture = textureView.getSurfaceTexture(); - setVideoSurfaceInternal(surfaceTexture == null ? null : new Surface(surfaceTexture), true); textureView.setSurfaceTextureListener(componentListener); + SurfaceTexture surfaceTexture = textureView.isAvailable() ? textureView.getSurfaceTexture() + : null; + setVideoSurfaceInternal(surfaceTexture == null ? null : new Surface(surfaceTexture), true); } } @@ -297,33 +307,70 @@ public class SimpleExoPlayer implements ExoPlayer { } /** - * Sets the stream type for audio playback (see {@link C.StreamType} and - * {@link android.media.AudioTrack#AudioTrack(int, int, int, int, int, int)}). If the stream type - * is not set, audio renderers use {@link C#STREAM_TYPE_DEFAULT}. + * Sets the stream type for audio playback, used by the underlying audio track. *

    - * Note that when the stream type changes, the AudioTrack must be reinitialized, which can - * introduce a brief gap in audio output. Note also that tracks in the same audio session must - * share the same routing, so a new audio session id will be generated. + * Setting the stream type during playback may introduce a short gap in audio output as the audio + * track is recreated. A new audio session id will also be generated. + *

    + * Calling this method overwrites any attributes set previously by calling + * {@link #setAudioAttributes(AudioAttributes)}. * - * @param audioStreamType The stream type for audio playback. + * @deprecated Use {@link #setAudioAttributes(AudioAttributes)}. + * @param streamType The stream type for audio playback. */ - public void setAudioStreamType(@C.StreamType int audioStreamType) { - this.audioStreamType = audioStreamType; + @Deprecated + public void setAudioStreamType(@C.StreamType int streamType) { + @C.AudioUsage int usage = Util.getAudioUsageForStreamType(streamType); + @C.AudioContentType int contentType = Util.getAudioContentTypeForStreamType(streamType); + AudioAttributes audioAttributes = + new AudioAttributes.Builder().setUsage(usage).setContentType(contentType).build(); + setAudioAttributes(audioAttributes); + } + + /** + * Returns the stream type for audio playback. + * + * @deprecated Use {@link #getAudioAttributes()}. + */ + @Deprecated + public @C.StreamType int getAudioStreamType() { + return Util.getStreamTypeForAudioUsage(audioAttributes.usage); + } + + /** + * Sets the attributes for audio playback, used by the underlying audio track. If not set, the + * default audio attributes will be used. They are suitable for general media playback. + *

    + * Setting the audio attributes during playback may introduce a short gap in audio output as the + * audio track is recreated. A new audio session id will also be generated. + *

    + * If tunneling is enabled by the track selector, the specified audio attributes will be ignored, + * but they will take effect if audio is later played without tunneling. + *

    + * If the device is running a build before platform API version 21, audio attributes cannot be set + * directly on the underlying audio track. In this case, the usage will be mapped onto an + * equivalent stream type using {@link Util#getStreamTypeForAudioUsage(int)}. + * + * @param audioAttributes The attributes to use for audio playback. + */ + public void setAudioAttributes(AudioAttributes audioAttributes) { + this.audioAttributes = audioAttributes; ExoPlayerMessage[] messages = new ExoPlayerMessage[audioRendererCount]; int count = 0; for (Renderer renderer : renderers) { if (renderer.getTrackType() == C.TRACK_TYPE_AUDIO) { - messages[count++] = new ExoPlayerMessage(renderer, C.MSG_SET_STREAM_TYPE, audioStreamType); + messages[count++] = new ExoPlayerMessage(renderer, C.MSG_SET_AUDIO_ATTRIBUTES, + audioAttributes); } } player.sendMessages(messages); } /** - * Returns the stream type for audio playback. + * Returns the attributes for audio playback. */ - public @C.StreamType int getAudioStreamType() { - return audioStreamType; + public AudioAttributes getAudioAttributes() { + return audioAttributes; } /** @@ -405,63 +452,132 @@ public class SimpleExoPlayer implements ExoPlayer { } /** - * Sets a listener to receive video events. + * Adds a listener to receive video events. + * + * @param listener The listener to register. + */ + public void addVideoListener(VideoListener listener) { + videoListeners.add(listener); + } + + /** + * Removes a listener of video events. + * + * @param listener The listener to unregister. + */ + public void removeVideoListener(VideoListener listener) { + videoListeners.remove(listener); + } + + /** + * Sets a listener to receive video events, removing all existing listeners. * * @param listener The listener. + * @deprecated Use {@link #addVideoListener(VideoListener)}. */ + @Deprecated public void setVideoListener(VideoListener listener) { - videoListener = listener; + videoListeners.clear(); + if (listener != null) { + addVideoListener(listener); + } } /** - * Clears the listener receiving video events if it matches the one passed. Else does nothing. + * Equivalent to {@link #removeVideoListener(VideoListener)}. * * @param listener The listener to clear. + * @deprecated Use {@link #removeVideoListener(VideoListener)}. */ + @Deprecated public void clearVideoListener(VideoListener listener) { - if (videoListener == listener) { - videoListener = null; - } + removeVideoListener(listener); } /** - * Sets an output to receive text events. + * Registers an output to receive text events. + * + * @param listener The output to register. + */ + public void addTextOutput(TextRenderer.Output listener) { + textOutputs.add(listener); + } + + /** + * Removes a text output. + * + * @param listener The output to remove. + */ + public void removeTextOutput(TextRenderer.Output listener) { + textOutputs.remove(listener); + } + + /** + * Sets an output to receive text events, removing all existing outputs. * * @param output The output. + * @deprecated Use {@link #addTextOutput(TextRenderer.Output)}. */ + @Deprecated public void setTextOutput(TextRenderer.Output output) { - textOutput = output; - } - - /** - * Clears the output receiving text events if it matches the one passed. Else does nothing. - * - * @param output The output to clear. - */ - public void clearTextOutput(TextRenderer.Output output) { - if (textOutput == output) { - textOutput = null; + textOutputs.clear(); + if (output != null) { + addTextOutput(output); } } /** - * Sets a listener to receive metadata events. + * Equivalent to {@link #removeTextOutput(TextRenderer.Output)}. + * + * @param output The output to clear. + * @deprecated Use {@link #removeTextOutput(TextRenderer.Output)}. + */ + @Deprecated + public void clearTextOutput(TextRenderer.Output output) { + removeTextOutput(output); + } + + /** + * Registers an output to receive metadata events. + * + * @param listener The output to register. + */ + public void addMetadataOutput(MetadataRenderer.Output listener) { + metadataOutputs.add(listener); + } + + /** + * Removes a metadata output. + * + * @param listener The output to remove. + */ + public void removeMetadataOutput(MetadataRenderer.Output listener) { + metadataOutputs.remove(listener); + } + + /** + * Sets an output to receive metadata events, removing all existing outputs. * * @param output The output. + * @deprecated Use {@link #addMetadataOutput(MetadataRenderer.Output)}. */ + @Deprecated public void setMetadataOutput(MetadataRenderer.Output output) { - metadataOutput = output; + metadataOutputs.clear(); + if (output != null) { + addMetadataOutput(output); + } } /** - * Clears the output receiving metadata events if it matches the one passed. Else does nothing. + * Equivalent to {@link #removeMetadataOutput(MetadataRenderer.Output)}. * * @param output The output to clear. + * @deprecated Use {@link #removeMetadataOutput(MetadataRenderer.Output)}. */ + @Deprecated public void clearMetadataOutput(MetadataRenderer.Output output) { - if (metadataOutput == output) { - metadataOutput = null; - } + removeMetadataOutput(output); } /** @@ -485,12 +601,17 @@ public class SimpleExoPlayer implements ExoPlayer { // ExoPlayer implementation @Override - public void addListener(EventListener listener) { + public Looper getPlaybackLooper() { + return player.getPlaybackLooper(); + } + + @Override + public void addListener(Player.EventListener listener) { player.addListener(listener); } @Override - public void removeListener(EventListener listener) { + public void removeListener(Player.EventListener listener) { player.removeListener(listener); } @@ -519,6 +640,16 @@ public class SimpleExoPlayer implements ExoPlayer { return player.getPlayWhenReady(); } + @Override + public @RepeatMode int getRepeatMode() { + return player.getRepeatMode(); + } + + @Override + public void setRepeatMode(@RepeatMode int repeatMode) { + player.setRepeatMode(repeatMode); + } + @Override public boolean isLoading() { return player.isLoading(); @@ -651,6 +782,26 @@ public class SimpleExoPlayer implements ExoPlayer { return player.isCurrentWindowSeekable(); } + @Override + public boolean isPlayingAd() { + return player.isPlayingAd(); + } + + @Override + public int getCurrentAdGroupIndex() { + return player.getCurrentAdGroupIndex(); + } + + @Override + public int getCurrentAdIndexInAdGroup() { + return player.getCurrentAdIndexInAdGroup(); + } + + @Override + public long getContentPosition() { + return player.getContentPosition(); + } + // Internal methods. private void removeSurfaceCallbacks() { @@ -679,12 +830,12 @@ public class SimpleExoPlayer implements ExoPlayer { } } if (this.surface != null && this.surface != surface) { - // If we created this surface, we are responsible for releasing it. + // We're replacing a surface. Block to ensure that it's not accessed after the method returns. + player.blockingSendMessages(messages); + // If we created the previous surface, we are responsible for releasing it. if (this.ownsSurface) { this.surface.release(); } - // We're replacing a surface. Block to ensure that it's not accessed after the method returns. - player.blockingSendMessages(messages); } else { player.sendMessages(messages); } @@ -733,7 +884,7 @@ public class SimpleExoPlayer implements ExoPlayer { @Override public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { - if (videoListener != null) { + for (VideoListener videoListener : videoListeners) { videoListener.onVideoSizeChanged(width, height, unappliedRotationDegrees, pixelWidthHeightRatio); } @@ -745,8 +896,10 @@ public class SimpleExoPlayer implements ExoPlayer { @Override public void onRenderedFirstFrame(Surface surface) { - if (videoListener != null && SimpleExoPlayer.this.surface == surface) { - videoListener.onRenderedFirstFrame(); + if (SimpleExoPlayer.this.surface == surface) { + for (VideoListener videoListener : videoListeners) { + videoListener.onRenderedFirstFrame(); + } } if (videoDebugListener != null) { videoDebugListener.onRenderedFirstFrame(surface); @@ -819,7 +972,7 @@ public class SimpleExoPlayer implements ExoPlayer { @Override public void onCues(List cues) { - if (textOutput != null) { + for (TextRenderer.Output textOutput : textOutputs) { textOutput.onCues(cues); } } @@ -828,7 +981,7 @@ public class SimpleExoPlayer implements ExoPlayer { @Override public void onMetadata(Metadata metadata) { - if (metadataOutput != null) { + for (MetadataRenderer.Output metadataOutput : metadataOutputs) { metadataOutput.onMetadata(metadata); } } @@ -867,17 +1020,22 @@ public class SimpleExoPlayer implements ExoPlayer { @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { - if (videoListener.onSurfaceDestroyed(surfaceTexture)) { - return false; + for (VideoListener videoListener : videoListeners) { + if (videoListener.onSurfaceDestroyed(surfaceTexture)) { + return false; + } } setVideoSurfaceInternal(null, true); needSetSurface = true; + return true; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { - videoListener.onSurfaceTextureUpdated(surfaceTexture); + for (VideoListener videoListener : videoListeners) { + videoListener.onSurfaceTextureUpdated(surfaceTexture); + } } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/Timeline.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/Timeline.java index 3adbaaf55..344d9d9b2 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/Timeline.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/Timeline.java @@ -15,18 +15,24 @@ */ package org.telegram.messenger.exoplayer2; +import android.util.Pair; +import org.telegram.messenger.exoplayer2.util.Assertions; + /** - * A representation of media currently available for playback. - *

    - * Timeline instances are immutable. For cases where the available media is changing dynamically - * (e.g. live streams) a timeline provides a snapshot of the media currently available. + * A flexible representation of the structure of media. A timeline is able to represent the + * structure of a wide variety of media, from simple cases like a single media file through to + * complex compositions of media such as playlists and streams with inserted ads. Instances are + * immutable. For cases where media is changing dynamically (e.g. live streams), a timeline provides + * a snapshot of the current state. *

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

    Single media file or on-demand stream

    *

    @@ -75,150 +81,36 @@ package org.telegram.messenger.exoplayer2; * with multiple periods"> *

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

    On-demand pre-roll followed by live stream

    + *

    On-demand stream followed by live stream

    *

    - * Example timeline for an on-demand pre-roll
+ *   <img src= *

    * This case is the concatenation of the Single media file or on-demand * stream and Live stream with multiple periods cases. When playback - * of the pre-roll ends, playback of the live stream will start from its default position near the - * live edge. + * of the on-demand stream ends, playback of the live stream will start from its default position + * near the live edge. + * + *

    On-demand stream with mid-roll ads

    + *

    + * Example timeline for an on-demand
+ *       stream with mid-roll ad groups + *

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

    * Information defined by a timeline window *

    @@ -354,9 +246,11 @@ public abstract class Timeline { /** * Holds information about a period in a {@link Timeline}. A period defines a single logical piece - * of media, for example a a media file. See {@link Timeline} for more details. The figure below - * shows some of the information defined by a period, as well as how this information relates to a - * corresponding {@link Window} in the timeline. + * of media, for example a media file. It may also define groups of ads inserted into the media, + * along with information about whether those ads have been loaded and played. + *

    + * The figure below shows some of the information defined by a period, as well as how this + * information relates to a corresponding {@link Window} in the timeline. *

    * Information defined by a period *

    @@ -383,24 +277,71 @@ public abstract class Timeline { */ public long durationUs; - /** - * Whether this period contains an ad. - */ - public boolean isAd; - private long positionInWindowUs; + private long[] adGroupTimesUs; + private int[] adCounts; + private int[] adsLoadedCounts; + private int[] adsPlayedCounts; + private long[][] adDurationsUs; + private long adResumePositionUs; /** * Sets the data held by this period. + * + * @param id An identifier for the period. Not necessarily unique. + * @param uid A unique identifier for the period. + * @param windowIndex The index of the window to which this period belongs. + * @param durationUs The duration of this period in microseconds, or {@link C#TIME_UNSET} if + * unknown. + * @param positionInWindowUs The position of the start of this period relative to the start of + * the window to which it belongs, in milliseconds. May be negative if the start of the + * period is not within the window. + * @return This period, for convenience. */ public Period set(Object id, Object uid, int windowIndex, long durationUs, - long positionInWindowUs, boolean isAd) { + long positionInWindowUs) { + return set(id, uid, windowIndex, durationUs, positionInWindowUs, null, null, null, null, + null, C.TIME_UNSET); + } + + /** + * Sets the data held by this period. + * + * @param id An identifier for the period. Not necessarily unique. + * @param uid A unique identifier for the period. + * @param windowIndex The index of the window to which this period belongs. + * @param durationUs The duration of this period in microseconds, or {@link C#TIME_UNSET} if + * unknown. + * @param positionInWindowUs The position of the start of this period relative to the start of + * the window to which it belongs, in milliseconds. May be negative if the start of the + * period is not within the window. + * @param adGroupTimesUs The times of ad groups relative to the start of the period, in + * microseconds. A final element with the value {@link C#TIME_END_OF_SOURCE} indicates that + * the period has a postroll ad. + * @param adCounts The number of ads in each ad group. An element may be {@link C#LENGTH_UNSET} + * if the number of ads is not yet known. + * @param adsLoadedCounts The number of ads loaded so far in each ad group. + * @param adsPlayedCounts The number of ads played so far in each ad group. + * @param adDurationsUs The duration of each ad in each ad group, in microseconds. An element + * may be {@link C#TIME_UNSET} if the duration is not yet known. + * @param adResumePositionUs The position offset in the first unplayed ad at which to begin + * playback, in microseconds. + * @return This period, for convenience. + */ + public Period set(Object id, Object uid, int windowIndex, long durationUs, + long positionInWindowUs, long[] adGroupTimesUs, int[] adCounts, int[] adsLoadedCounts, + int[] adsPlayedCounts, long[][] adDurationsUs, long adResumePositionUs) { this.id = id; this.uid = uid; this.windowIndex = windowIndex; this.durationUs = durationUs; this.positionInWindowUs = positionInWindowUs; - this.isAd = isAd; + this.adGroupTimesUs = adGroupTimesUs; + this.adCounts = adCounts; + this.adsLoadedCounts = adsLoadedCounts; + this.adsPlayedCounts = adsPlayedCounts; + this.adDurationsUs = adDurationsUs; + this.adResumePositionUs = adResumePositionUs; return this; } @@ -436,6 +377,379 @@ public abstract class Timeline { return positionInWindowUs; } + /** + * Returns the number of ad groups in the period. + */ + public int getAdGroupCount() { + return adGroupTimesUs == null ? 0 : adGroupTimesUs.length; + } + + /** + * Returns the time of the ad group at index {@code adGroupIndex} in the period, in + * microseconds. + * + * @param adGroupIndex The ad group index. + * @return The time of the ad group at the index, in microseconds. + */ + public long getAdGroupTimeUs(int adGroupIndex) { + return adGroupTimesUs[adGroupIndex]; + } + + /** + * Returns the number of ads that have been played in the specified ad group in the period. + * + * @param adGroupIndex The ad group index. + * @return The number of ads that have been played. + */ + public int getPlayedAdCount(int adGroupIndex) { + return adsPlayedCounts[adGroupIndex]; + } + + /** + * Returns whether the ad group at index {@code adGroupIndex} has been played. + * + * @param adGroupIndex The ad group index. + * @return Whether the ad group at index {@code adGroupIndex} has been played. + */ + public boolean hasPlayedAdGroup(int adGroupIndex) { + return adCounts[adGroupIndex] != C.INDEX_UNSET + && adsPlayedCounts[adGroupIndex] == adCounts[adGroupIndex]; + } + + /** + * Returns the index of the ad group at or before {@code positionUs}, if that ad group is + * unplayed. Returns {@link C#INDEX_UNSET} if the ad group before {@code positionUs} has been + * played, or if there is no such ad group. + * + * @param positionUs The position at or before which to find an ad group, in microseconds. + * @return The index of the ad group, or {@link C#INDEX_UNSET}. + */ + public int getAdGroupIndexForPositionUs(long positionUs) { + if (adGroupTimesUs == null) { + return C.INDEX_UNSET; + } + // Use a linear search as the array elements may not be increasing due to TIME_END_OF_SOURCE. + // In practice we expect there to be few ad groups so the search shouldn't be expensive. + int index = adGroupTimesUs.length - 1; + while (index >= 0 && (adGroupTimesUs[index] == C.TIME_END_OF_SOURCE + || adGroupTimesUs[index] > positionUs)) { + index--; + } + return index >= 0 && !hasPlayedAdGroup(index) ? index : C.INDEX_UNSET; + } + + /** + * Returns the index of the next unplayed ad group after {@code positionUs}. Returns + * {@link C#INDEX_UNSET} if there is no such ad group. + * + * @param positionUs The position after which to find an ad group, in microseconds. + * @return The index of the ad group, or {@link C#INDEX_UNSET}. + */ + public int getAdGroupIndexAfterPositionUs(long positionUs) { + if (adGroupTimesUs == null) { + return C.INDEX_UNSET; + } + // Use a linear search as the array elements may not be increasing due to TIME_END_OF_SOURCE. + // In practice we expect there to be few ad groups so the search shouldn't be expensive. + int index = 0; + while (index < adGroupTimesUs.length && adGroupTimesUs[index] != C.TIME_END_OF_SOURCE + && (positionUs >= adGroupTimesUs[index] || hasPlayedAdGroup(index))) { + index++; + } + return index < adGroupTimesUs.length ? index : C.INDEX_UNSET; + } + + /** + * Returns the number of ads in the ad group at index {@code adGroupIndex}, or + * {@link C#LENGTH_UNSET} if not yet known. + * + * @param adGroupIndex The ad group index. + * @return The number of ads in the ad group, or {@link C#LENGTH_UNSET} if not yet known. + */ + public int getAdCountInAdGroup(int adGroupIndex) { + return adCounts[adGroupIndex]; + } + + /** + * Returns whether the URL for the specified ad is known. + * + * @param adGroupIndex The ad group index. + * @param adIndexInAdGroup The ad index in the ad group. + * @return Whether the URL for the specified ad is known. + */ + public boolean isAdAvailable(int adGroupIndex, int adIndexInAdGroup) { + return adIndexInAdGroup < adsLoadedCounts[adGroupIndex]; + } + + /** + * Returns the duration of the ad at index {@code adIndexInAdGroup} in the ad group at + * {@code adGroupIndex}, in microseconds, or {@link C#TIME_UNSET} if not yet known. + * + * @param adGroupIndex The ad group index. + * @param adIndexInAdGroup The ad index in the ad group. + * @return The duration of the ad, or {@link C#TIME_UNSET} if not yet known. + */ + public long getAdDurationUs(int adGroupIndex, int adIndexInAdGroup) { + if (adIndexInAdGroup >= adDurationsUs[adGroupIndex].length) { + return C.TIME_UNSET; + } + return adDurationsUs[adGroupIndex][adIndexInAdGroup]; + } + + /** + * Returns the position offset in the first unplayed ad at which to begin playback, in + * microseconds. + */ + public long getAdResumePositionUs() { + return adResumePositionUs; + } + } + /** + * An empty timeline. + */ + public static final Timeline EMPTY = new Timeline() { + + @Override + public int getWindowCount() { + return 0; + } + + @Override + public Window getWindow(int windowIndex, Window window, boolean setIds, + long defaultPositionProjectionUs) { + throw new IndexOutOfBoundsException(); + } + + @Override + public int getPeriodCount() { + return 0; + } + + @Override + public Period getPeriod(int periodIndex, Period period, boolean setIds) { + throw new IndexOutOfBoundsException(); + } + + @Override + public int getIndexOfPeriod(Object uid) { + return C.INDEX_UNSET; + } + + }; + + /** + * Returns whether the timeline is empty. + */ + public final boolean isEmpty() { + return getWindowCount() == 0; + } + + /** + * Returns the number of windows in the timeline. + */ + public abstract int getWindowCount(); + + /** + * Returns the index of the window after the window at index {@code windowIndex} depending on the + * {@code repeatMode}. + * + * @param windowIndex Index of a window in the timeline. + * @param repeatMode A repeat mode. + * @return The index of the next window, or {@link C#INDEX_UNSET} if this is the last window. + */ + public int getNextWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode) { + switch (repeatMode) { + case Player.REPEAT_MODE_OFF: + return windowIndex == getWindowCount() - 1 ? C.INDEX_UNSET : windowIndex + 1; + case Player.REPEAT_MODE_ONE: + return windowIndex; + case Player.REPEAT_MODE_ALL: + return windowIndex == getWindowCount() - 1 ? 0 : windowIndex + 1; + default: + throw new IllegalStateException(); + } + } + + /** + * Returns the index of the window before the window at index {@code windowIndex} depending on the + * {@code repeatMode}. + * + * @param windowIndex Index of a window in the timeline. + * @param repeatMode A repeat mode. + * @return The index of the previous window, or {@link C#INDEX_UNSET} if this is the first window. + */ + public int getPreviousWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode) { + switch (repeatMode) { + case Player.REPEAT_MODE_OFF: + return windowIndex == 0 ? C.INDEX_UNSET : windowIndex - 1; + case Player.REPEAT_MODE_ONE: + return windowIndex; + case Player.REPEAT_MODE_ALL: + return windowIndex == 0 ? getWindowCount() - 1 : windowIndex - 1; + default: + throw new IllegalStateException(); + } + } + + /** + * Populates a {@link Window} with data for the window at the specified index. Does not populate + * {@link Window#id}. + * + * @param windowIndex The index of the window. + * @param window The {@link Window} to populate. Must not be null. + * @return The populated {@link Window}, for convenience. + */ + public final Window getWindow(int windowIndex, Window window) { + return getWindow(windowIndex, window, false); + } + + /** + * Populates a {@link Window} with data for the window at the specified index. + * + * @param windowIndex The index of the window. + * @param window The {@link Window} to populate. Must not be null. + * @param setIds Whether {@link Window#id} should be populated. If false, the field will be set to + * null. The caller should pass false for efficiency reasons unless the field is required. + * @return The populated {@link Window}, for convenience. + */ + public Window getWindow(int windowIndex, Window window, boolean setIds) { + return getWindow(windowIndex, window, setIds, 0); + } + + /** + * Populates a {@link Window} with data for the window at the specified index. + * + * @param windowIndex The index of the window. + * @param window The {@link Window} to populate. Must not be null. + * @param setIds Whether {@link Window#id} should be populated. If false, the field will be set to + * null. The caller should pass false for efficiency reasons unless the field is required. + * @param defaultPositionProjectionUs A duration into the future that the populated window's + * default start position should be projected. + * @return The populated {@link Window}, for convenience. + */ + public abstract Window getWindow(int windowIndex, Window window, boolean setIds, + long defaultPositionProjectionUs); + + /** + * Returns the number of periods in the timeline. + */ + public abstract int getPeriodCount(); + + /** + * Returns the index of the period after the period at index {@code periodIndex} depending on the + * {@code repeatMode}. + * + * @param periodIndex Index of a period in the timeline. + * @param period A {@link Period} to be used internally. Must not be null. + * @param window A {@link Window} to be used internally. Must not be null. + * @param repeatMode A repeat mode. + * @return The index of the next period, or {@link C#INDEX_UNSET} if this is the last period. + */ + public final int getNextPeriodIndex(int periodIndex, Period period, Window window, + @Player.RepeatMode int repeatMode) { + int windowIndex = getPeriod(periodIndex, period).windowIndex; + if (getWindow(windowIndex, window).lastPeriodIndex == periodIndex) { + int nextWindowIndex = getNextWindowIndex(windowIndex, repeatMode); + if (nextWindowIndex == C.INDEX_UNSET) { + return C.INDEX_UNSET; + } + return getWindow(nextWindowIndex, window).firstPeriodIndex; + } + return periodIndex + 1; + } + + /** + * Returns whether the given period is the last period of the timeline depending on the + * {@code repeatMode}. + * + * @param periodIndex A period index. + * @param period A {@link Period} to be used internally. Must not be null. + * @param window A {@link Window} to be used internally. Must not be null. + * @param repeatMode A repeat mode. + * @return Whether the period of the given index is the last period of the timeline. + */ + public final boolean isLastPeriod(int periodIndex, Period period, Window window, + @Player.RepeatMode int repeatMode) { + return getNextPeriodIndex(periodIndex, period, window, repeatMode) == C.INDEX_UNSET; + } + + /** + * Populates a {@link Period} with data for the period at the specified index. Does not populate + * {@link Period#id} and {@link Period#uid}. + * + * @param periodIndex The index of the period. + * @param period The {@link Period} to populate. Must not be null. + * @return The populated {@link Period}, for convenience. + */ + public final Period getPeriod(int periodIndex, Period period) { + return getPeriod(periodIndex, period, false); + } + + /** + * Calls {@link #getPeriodPosition(Window, Period, int, long, long)} with a zero default position + * projection. + */ + public final Pair getPeriodPosition(Window window, Period period, int windowIndex, + long windowPositionUs) { + return getPeriodPosition(window, period, windowIndex, windowPositionUs, 0); + } + + /** + * Converts (windowIndex, windowPositionUs) to the corresponding (periodIndex, periodPositionUs). + * + * @param window A {@link Window} that may be overwritten. + * @param period A {@link Period} that may be overwritten. + * @param windowIndex The window index. + * @param windowPositionUs The window time, or {@link C#TIME_UNSET} to use the window's default + * start position. + * @param defaultPositionProjectionUs If {@code windowPositionUs} is {@link C#TIME_UNSET}, the + * duration into the future by which the window's position should be projected. + * @return The corresponding (periodIndex, periodPositionUs), or null if {@code #windowPositionUs} + * is {@link C#TIME_UNSET}, {@code defaultPositionProjectionUs} is non-zero, and the window's + * position could not be projected by {@code defaultPositionProjectionUs}. + */ + public final Pair getPeriodPosition(Window window, Period period, int windowIndex, + long windowPositionUs, long defaultPositionProjectionUs) { + Assertions.checkIndex(windowIndex, 0, getWindowCount()); + getWindow(windowIndex, window, false, defaultPositionProjectionUs); + if (windowPositionUs == C.TIME_UNSET) { + windowPositionUs = window.getDefaultPositionUs(); + if (windowPositionUs == C.TIME_UNSET) { + return null; + } + } + int periodIndex = window.firstPeriodIndex; + long periodPositionUs = window.getPositionInFirstPeriodUs() + windowPositionUs; + long periodDurationUs = getPeriod(periodIndex, period).getDurationUs(); + while (periodDurationUs != C.TIME_UNSET && periodPositionUs >= periodDurationUs + && periodIndex < window.lastPeriodIndex) { + periodPositionUs -= periodDurationUs; + periodDurationUs = getPeriod(++periodIndex, period).getDurationUs(); + } + return Pair.create(periodIndex, periodPositionUs); + } + + /** + * Populates a {@link Period} with data for the period at the specified index. + * + * @param periodIndex The index of the period. + * @param period The {@link Period} to populate. Must not be null. + * @param setIds Whether {@link Period#id} and {@link Period#uid} should be populated. If false, + * the fields will be set to null. The caller should pass false for efficiency reasons unless + * the fields are required. + * @return The populated {@link Period}, for convenience. + */ + public abstract Period getPeriod(int periodIndex, Period period, boolean setIds); + + /** + * Returns the index of the period identified by its unique {@code id}, or {@link C#INDEX_UNSET} + * if the period is not in the timeline. + * + * @param uid A unique identifier for a period. + * @return The index of the period, or {@link C#INDEX_UNSET} if the period was not found. + */ + public abstract int getIndexOfPeriod(Object uid); + } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/AudioAttributes.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/AudioAttributes.java new file mode 100755 index 000000000..f7b25389d --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/AudioAttributes.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.audio; + +import android.annotation.TargetApi; +import org.telegram.messenger.exoplayer2.C; + +/** + * Attributes for audio playback, which configure the underlying platform + * {@link android.media.AudioTrack}. + *

    + * To set the audio attributes, create an instance using the {@link Builder} and either pass it to + * {@link org.telegram.messenger.exoplayer2.SimpleExoPlayer#setAudioAttributes(AudioAttributes)} or + * send a message of type {@link C#MSG_SET_AUDIO_ATTRIBUTES} to the audio renderers. + *

    + * This class is based on {@link android.media.AudioAttributes}, but can be used on all supported + * API versions. + */ +public final class AudioAttributes { + + public static final AudioAttributes DEFAULT = new Builder().build(); + + /** + * Builder for {@link AudioAttributes}. + */ + public static final class Builder { + + @C.AudioContentType + private int contentType; + @C.AudioFlags + private int flags; + @C.AudioUsage + private int usage; + + /** + * Creates a new builder for {@link AudioAttributes}. + *

    + * By default the content type is {@link C#CONTENT_TYPE_UNKNOWN}, usage is + * {@link C#USAGE_MEDIA}, and no flags are set. + */ + public Builder() { + contentType = C.CONTENT_TYPE_UNKNOWN; + flags = 0; + usage = C.USAGE_MEDIA; + } + + /** + * @see android.media.AudioAttributes.Builder#setContentType(int) + */ + public Builder setContentType(@C.AudioContentType int contentType) { + this.contentType = contentType; + return this; + } + + /** + * @see android.media.AudioAttributes.Builder#setFlags(int) + */ + public Builder setFlags(@C.AudioFlags int flags) { + this.flags = flags; + return this; + } + + /** + * @see android.media.AudioAttributes.Builder#setUsage(int) + */ + public Builder setUsage(@C.AudioUsage int usage) { + this.usage = usage; + return this; + } + + /** + * Creates an {@link AudioAttributes} instance from this builder. + */ + public AudioAttributes build() { + return new AudioAttributes(contentType, flags, usage); + } + + } + + @C.AudioContentType + public final int contentType; + @C.AudioFlags + public final int flags; + @C.AudioUsage + public final int usage; + + private android.media.AudioAttributes audioAttributesV21; + + private AudioAttributes(@C.AudioContentType int contentType, @C.AudioFlags int flags, + @C.AudioUsage int usage) { + this.contentType = contentType; + this.flags = flags; + this.usage = usage; + } + + @TargetApi(21) + /* package */ android.media.AudioAttributes getAudioAttributesV21() { + if (audioAttributesV21 == null) { + audioAttributesV21 = new android.media.AudioAttributes.Builder() + .setContentType(contentType) + .setFlags(flags) + .setUsage(usage) + .build(); + } + return audioAttributesV21; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + AudioAttributes other = (AudioAttributes) obj; + return this.contentType == other.contentType && this.flags == other.flags + && this.usage == other.usage; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + contentType; + result = 31 * result + flags; + result = 31 * result + usage; + return result; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/AudioTrack.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/AudioTrack.java index b08aa001e..1c7027363 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/AudioTrack.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/AudioTrack.java @@ -17,8 +17,8 @@ package org.telegram.messenger.exoplayer2.audio; import android.annotation.SuppressLint; import android.annotation.TargetApi; -import android.media.AudioAttributes; import android.media.AudioFormat; +import android.media.AudioManager; import android.media.AudioTimestamp; import android.os.ConditionVariable; import android.os.SystemClock; @@ -40,9 +40,9 @@ import java.util.LinkedList; *

    * Before starting playback, specify the input format by calling * {@link #configure(String, int, int, int, int)}. Optionally call {@link #setAudioSessionId(int)}, - * {@link #setStreamType(int)}, {@link #enableTunnelingV21(int)} and {@link #disableTunneling()} - * to configure audio playback. These methods may be called after writing data to the track, in - * which case it will be reinitialized as required. + * {@link #setAudioAttributes(AudioAttributes)}, {@link #enableTunnelingV21(int)} and + * {@link #disableTunneling()} to configure audio playback. These methods may be called after + * writing data to the track, in which case it will be reinitialized as required. *

    * Call {@link #handleBuffer(ByteBuffer, long)} to write data, and {@link #handleDiscontinuity()} * when the data being fed is discontinuous. Call {@link #play()} to start playing the written data. @@ -299,8 +299,7 @@ public final class AudioTrack { private int encoding; @C.Encoding private int outputEncoding; - @C.StreamType - private int streamType; + private AudioAttributes audioAttributes; private boolean passthrough; private int bufferSize; private long bufferSizeUs; @@ -384,7 +383,7 @@ public final class AudioTrack { playheadOffsets = new long[MAX_PLAYHEAD_OFFSET_COUNT]; volume = 1.0f; startMediaTimeState = START_NOT_SET; - streamType = C.STREAM_TYPE_DEFAULT; + audioAttributes = AudioAttributes.DEFAULT; audioSessionId = C.AUDIO_SESSION_ID_UNSET; playbackParameters = PlaybackParameters.DEFAULT; drainingAudioProcessorIndex = C.INDEX_UNSET; @@ -634,19 +633,7 @@ public final class AudioTrack { // initialization of the audio track to fail. releasingConditionVariable.block(); - if (tunneling) { - audioTrack = createHwAvSyncAudioTrackV21(sampleRate, channelConfig, outputEncoding, - bufferSize, audioSessionId); - } else if (audioSessionId == C.AUDIO_SESSION_ID_UNSET) { - audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig, - outputEncoding, bufferSize, MODE_STREAM); - } else { - // Re-attach to the same audio session. - audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig, - outputEncoding, bufferSize, MODE_STREAM, audioSessionId); - } - checkAudioTrackInitialized(); - + audioTrack = initializeAudioTrack(); int audioSessionId = audioTrack.getAudioSessionId(); if (enablePreV21AudioSessionWorkaround) { if (Util.SDK_INT < 21) { @@ -657,12 +644,7 @@ public final class AudioTrack { releaseKeepSessionIdAudioTrack(); } if (keepSessionIdAudioTrack == null) { - int sampleRate = 4000; // Equal to private android.media.AudioTrack.MIN_SAMPLE_RATE. - int channelConfig = AudioFormat.CHANNEL_OUT_MONO; - @C.PcmEncoding int encoding = C.ENCODING_PCM_16BIT; - int bufferSize = 2; // Use a two byte buffer, as it is not actually used for playback. - keepSessionIdAudioTrack = new android.media.AudioTrack(streamType, sampleRate, - channelConfig, encoding, bufferSize, MODE_STATIC, audioSessionId); + keepSessionIdAudioTrack = initializeKeepSessionIdAudioTrack(audioSessionId); } } } @@ -1021,23 +1003,23 @@ public final class AudioTrack { } /** - * Sets the stream type for audio track. If the stream type has changed and if the audio track + * Sets the attributes for audio playback. If the attributes have changed and if the audio track * is not configured for use with tunneling, then the audio track is reset and the audio session * id is cleared. *

    - * If the audio track is configured for use with tunneling then the stream type is ignored, the - * audio track is not reset and the audio session id is not cleared. The passed stream type will - * be used if the audio track is later re-configured into non-tunneled mode. + * If the audio track is configured for use with tunneling then the audio attributes are ignored. + * The audio track is not reset and the audio session id is not cleared. The passed attributes + * will be used if the audio track is later re-configured into non-tunneled mode. * - * @param streamType The {@link C.StreamType} to use for audio output. + * @param audioAttributes The attributes for audio playback. */ - public void setStreamType(@C.StreamType int streamType) { - if (this.streamType == streamType) { + public void setAudioAttributes(AudioAttributes audioAttributes) { + if (this.audioAttributes.equals(audioAttributes)) { return; } - this.streamType = streamType; + this.audioAttributes = audioAttributes; if (tunneling) { - // The stream type is ignored in tunneling mode, so no need to reset. + // The audio attributes are ignored in tunneling mode, so no need to reset. return; } reset(); @@ -1292,7 +1274,7 @@ public final class AudioTrack { // The timestamp time base is probably wrong. String message = "Spurious audio timestamp (system clock mismatch): " + audioTimestampFramePosition + ", " + audioTimestampUs + ", " + systemClockUs + ", " - + playbackPositionUs; + + playbackPositionUs + ", " + getSubmittedFrames() + ", " + getWrittenFrames(); if (failOnSpuriousAudioTimestamp) { throw new InvalidAudioTrackTimestampException(message); } @@ -1303,7 +1285,7 @@ public final class AudioTrack { // The timestamp frame position is probably wrong. String message = "Spurious audio timestamp (frame position mismatch): " + audioTimestampFramePosition + ", " + audioTimestampUs + ", " + systemClockUs + ", " - + playbackPositionUs; + + playbackPositionUs + ", " + getSubmittedFrames() + ", " + getWrittenFrames(); if (failOnSpuriousAudioTimestamp) { throw new InvalidAudioTrackTimestampException(message); } @@ -1333,31 +1315,6 @@ public final class AudioTrack { } } - /** - * Checks that {@link #audioTrack} has been successfully initialized. If it has then calling this - * method is a no-op. If it hasn't then {@link #audioTrack} is released and set to null, and an - * exception is thrown. - * - * @throws InitializationException If {@link #audioTrack} has not been successfully initialized. - */ - private void checkAudioTrackInitialized() throws InitializationException { - int state = audioTrack.getState(); - if (state == STATE_INITIALIZED) { - return; - } - // The track is not successfully initialized. Release and null the track. - try { - audioTrack.release(); - } catch (Exception e) { - // The track has already failed to initialize, so it wouldn't be that surprising if release - // were to fail too. Swallow the exception. - } finally { - audioTrack = null; - } - - throw new InitializationException(state, sampleRate, channelConfig, bufferSize); - } - private boolean isInitialized() { return audioTrack != null; } @@ -1408,24 +1365,65 @@ public final class AudioTrack { && audioTrack.getPlaybackHeadPosition() == 0; } - /** - * Instantiates an {@link android.media.AudioTrack} to be used with tunneling video playback. - */ + private android.media.AudioTrack initializeAudioTrack() throws InitializationException { + android.media.AudioTrack audioTrack; + if (Util.SDK_INT >= 21) { + audioTrack = createAudioTrackV21(); + } else { + int streamType = Util.getStreamTypeForAudioUsage(audioAttributes.usage); + if (audioSessionId == C.AUDIO_SESSION_ID_UNSET) { + audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig, + outputEncoding, bufferSize, MODE_STREAM); + } else { + // Re-attach to the same audio session. + audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig, + outputEncoding, bufferSize, MODE_STREAM, audioSessionId); + } + } + + int state = audioTrack.getState(); + if (state != STATE_INITIALIZED) { + try { + audioTrack.release(); + } catch (Exception e) { + // The track has already failed to initialize, so it wouldn't be that surprising if release + // were to fail too. Swallow the exception. + } + throw new InitializationException(state, sampleRate, channelConfig, bufferSize); + } + return audioTrack; + } + @TargetApi(21) - private static android.media.AudioTrack createHwAvSyncAudioTrackV21(int sampleRate, - int channelConfig, int encoding, int bufferSize, int sessionId) { - AudioAttributes attributesBuilder = new AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_MEDIA) - .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE) - .setFlags(AudioAttributes.FLAG_HW_AV_SYNC) - .build(); + private android.media.AudioTrack createAudioTrackV21() { + android.media.AudioAttributes attributes; + if (tunneling) { + attributes = new android.media.AudioAttributes.Builder() + .setContentType(android.media.AudioAttributes.CONTENT_TYPE_MOVIE) + .setFlags(android.media.AudioAttributes.FLAG_HW_AV_SYNC) + .setUsage(android.media.AudioAttributes.USAGE_MEDIA) + .build(); + } else { + attributes = audioAttributes.getAudioAttributesV21(); + } AudioFormat format = new AudioFormat.Builder() .setChannelMask(channelConfig) - .setEncoding(encoding) + .setEncoding(outputEncoding) .setSampleRate(sampleRate) .build(); - return new android.media.AudioTrack(attributesBuilder, format, bufferSize, MODE_STREAM, - sessionId); + int audioSessionId = this.audioSessionId != C.AUDIO_SESSION_ID_UNSET ? this.audioSessionId + : AudioManager.AUDIO_SESSION_ID_GENERATE; + return new android.media.AudioTrack(attributes, format, bufferSize, MODE_STREAM, + audioSessionId); + } + + private android.media.AudioTrack initializeKeepSessionIdAudioTrack(int audioSessionId) { + int sampleRate = 4000; // Equal to private android.media.AudioTrack.MIN_SAMPLE_RATE. + int channelConfig = AudioFormat.CHANNEL_OUT_MONO; + @C.PcmEncoding int encoding = C.ENCODING_PCM_16BIT; + int bufferSize = 2; // Use a two byte buffer, as it is not actually used for playback. + return new android.media.AudioTrack(C.STREAM_TYPE_DEFAULT, sampleRate, channelConfig, encoding, + bufferSize, MODE_STATIC, audioSessionId); } @C.Encoding @@ -1465,7 +1463,7 @@ public final class AudioTrack { @TargetApi(21) private int writeNonBlockingWithAvSyncV21(android.media.AudioTrack audioTrack, ByteBuffer buffer, int size, long presentationTimeUs) { - // TODO: Uncomment this when [Internal ref b/33627517] is clarified or fixed. + // TODO: Uncomment this when [Internal ref: b/33627517] is clarified or fixed. // if (Util.SDK_INT >= 23) { // // The underlying platform AudioTrack writes AV sync headers directly. // return audioTrack.write(buffer, size, WRITE_NON_BLOCKING, presentationTimeUs * 1000); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/MediaCodecAudioRenderer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/MediaCodecAudioRenderer.java index ffc84a918..4fe8c97fa 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -399,9 +399,9 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media case C.MSG_SET_VOLUME: audioTrack.setVolume((Float) message); break; - case C.MSG_SET_STREAM_TYPE: - @C.StreamType int streamType = (Integer) message; - audioTrack.setStreamType(streamType); + case C.MSG_SET_AUDIO_ATTRIBUTES: + AudioAttributes audioAttributes = (AudioAttributes) message; + audioTrack.setAudioAttributes(audioAttributes); break; default: super.handleMessage(messageType, message); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/SimpleDecoderAudioRenderer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/SimpleDecoderAudioRenderer.java index e4fe25306..b2268e20a 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/SimpleDecoderAudioRenderer.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/SimpleDecoderAudioRenderer.java @@ -32,6 +32,7 @@ import org.telegram.messenger.exoplayer2.decoder.DecoderInputBuffer; import org.telegram.messenger.exoplayer2.decoder.SimpleDecoder; import org.telegram.messenger.exoplayer2.decoder.SimpleOutputBuffer; import org.telegram.messenger.exoplayer2.drm.DrmSession; +import org.telegram.messenger.exoplayer2.drm.DrmSession.DrmSessionException; import org.telegram.messenger.exoplayer2.drm.DrmSessionManager; import org.telegram.messenger.exoplayer2.drm.ExoMediaCrypto; import org.telegram.messenger.exoplayer2.util.Assertions; @@ -376,15 +377,14 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements } private boolean shouldWaitForKeys(boolean bufferEncrypted) throws ExoPlaybackException { - if (drmSession == null) { + if (drmSession == null || (!bufferEncrypted && playClearSamplesWithoutKeys)) { return false; } @DrmSession.State int drmSessionState = drmSession.getState(); if (drmSessionState == DrmSession.STATE_ERROR) { throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex()); } - return drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS - && (bufferEncrypted || !playClearSamplesWithoutKeys); + return drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS; } private void processEndOfStream() throws ExoPlaybackException { @@ -514,13 +514,12 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements drmSession = pendingDrmSession; ExoMediaCrypto mediaCrypto = null; if (drmSession != null) { - @DrmSession.State int drmSessionState = drmSession.getState(); - if (drmSessionState == DrmSession.STATE_ERROR) { - throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex()); - } else if (drmSessionState == DrmSession.STATE_OPENED - || drmSessionState == DrmSession.STATE_OPENED_WITH_KEYS) { - mediaCrypto = drmSession.getMediaCrypto(); - } else { + mediaCrypto = drmSession.getMediaCrypto(); + if (mediaCrypto == null) { + DrmSessionException drmError = drmSession.getError(); + if (drmError != null) { + throw ExoPlaybackException.createForRenderer(drmError, getIndex()); + } // The drm session isn't open yet. return; } @@ -595,9 +594,9 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements case C.MSG_SET_VOLUME: audioTrack.setVolume((Float) message); break; - case C.MSG_SET_STREAM_TYPE: - @C.StreamType int streamType = (Integer) message; - audioTrack.setStreamType(streamType); + case C.MSG_SET_AUDIO_ATTRIBUTES: + AudioAttributes audioAttributes = (AudioAttributes) message; + audioTrack.setAudioAttributes(audioAttributes); break; default: super.handleMessage(messageType, message); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/Sonic.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/Sonic.java index 8182d95bc..89269fac3 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/Sonic.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/Sonic.java @@ -241,7 +241,7 @@ import java.util.Arrays; for (int i = 0; i < period; i++) { short sVal = samples[position + i]; short pVal = samples[position + period + i]; - diff += sVal >= pVal ? sVal - pVal : pVal - sVal; + diff += Math.abs(sVal - pVal); } // Note that the highest number of samples we add into diff will be less than 256, since we // skip samples. Thus, diff is a 24 bit number, and we can safely multiply by numSamples @@ -374,8 +374,8 @@ import java.util.Arrays; } private short interpolate(short[] in, int inPos, int oldSampleRate, int newSampleRate) { - short left = in[inPos * numChannels]; - short right = in[inPos * numChannels + numChannels]; + short left = in[inPos]; + short right = in[inPos + numChannels]; int position = newRatePosition * oldSampleRate; int leftPosition = oldRatePosition * newSampleRate; int rightPosition = (oldRatePosition + 1) * newSampleRate; @@ -402,7 +402,7 @@ import java.util.Arrays; enlargeOutputBufferIfNeeded(1); for (int i = 0; i < numChannels; i++) { outputBuffer[numOutputSamples * numChannels + i] = - interpolate(pitchBuffer, position + i, oldSampleRate, newSampleRate); + interpolate(pitchBuffer, position * numChannels + i, oldSampleRate, newSampleRate); } newRatePosition++; numOutputSamples++; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/CryptoInfo.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/CryptoInfo.java index eeb357a3e..743f5629f 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/CryptoInfo.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/CryptoInfo.java @@ -52,11 +52,11 @@ public final class CryptoInfo { /** * @see android.media.MediaCodec.CryptoInfo.Pattern */ - public int patternBlocksToEncrypt; + public int encryptedBlocks; /** * @see android.media.MediaCodec.CryptoInfo.Pattern */ - public int patternBlocksToSkip; + public int clearBlocks; private final android.media.MediaCodec.CryptoInfo frameworkCryptoInfo; private final PatternHolderV24 patternHolder; @@ -70,28 +70,20 @@ public final class CryptoInfo { * @see android.media.MediaCodec.CryptoInfo#set(int, int[], int[], byte[], byte[], int) */ public void set(int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData, - byte[] key, byte[] iv, @C.CryptoMode int mode) { + byte[] key, byte[] iv, @C.CryptoMode int mode, int encryptedBlocks, int clearBlocks) { this.numSubSamples = numSubSamples; this.numBytesOfClearData = numBytesOfClearData; this.numBytesOfEncryptedData = numBytesOfEncryptedData; this.key = key; this.iv = iv; this.mode = mode; - patternBlocksToEncrypt = 0; - patternBlocksToSkip = 0; + this.encryptedBlocks = encryptedBlocks; + this.clearBlocks = clearBlocks; if (Util.SDK_INT >= 16) { updateFrameworkCryptoInfoV16(); } } - public void setPattern(int patternBlocksToEncrypt, int patternBlocksToSkip) { - this.patternBlocksToEncrypt = patternBlocksToEncrypt; - this.patternBlocksToSkip = patternBlocksToSkip; - if (Util.SDK_INT >= 24) { - patternHolder.set(patternBlocksToEncrypt, patternBlocksToSkip); - } - } - /** * Returns an equivalent {@link android.media.MediaCodec.CryptoInfo} instance. *

    @@ -122,7 +114,7 @@ public final class CryptoInfo { frameworkCryptoInfo.iv = iv; frameworkCryptoInfo.mode = mode; if (Util.SDK_INT >= 24) { - patternHolder.set(patternBlocksToEncrypt, patternBlocksToSkip); + patternHolder.set(encryptedBlocks, clearBlocks); } } @@ -137,8 +129,8 @@ public final class CryptoInfo { pattern = new android.media.MediaCodec.CryptoInfo.Pattern(0, 0); } - private void set(int blocksToEncrypt, int blocksToSkip) { - pattern.set(blocksToEncrypt, blocksToSkip); + private void set(int encryptedBlocks, int clearBlocks) { + pattern.set(encryptedBlocks, clearBlocks); frameworkCryptoInfo.setPattern(pattern); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/SimpleOutputBuffer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/SimpleOutputBuffer.java index ac51e95a3..70144ee84 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/SimpleOutputBuffer.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/SimpleOutputBuffer.java @@ -16,6 +16,7 @@ package org.telegram.messenger.exoplayer2.decoder; import java.nio.ByteBuffer; +import java.nio.ByteOrder; /** * Buffer for {@link SimpleDecoder} output. @@ -40,7 +41,7 @@ public class SimpleOutputBuffer extends OutputBuffer { public ByteBuffer init(long timeUs, int size) { this.timeUs = timeUs; if (data == null || data.capacity() < size) { - data = ByteBuffer.allocateDirect(size); + data = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()); } data.position(0); data.limit(size); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DecryptionException.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DecryptionException.java index 646c8a3b8..bcf374fda 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DecryptionException.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DecryptionException.java @@ -1,20 +1,37 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.telegram.messenger.exoplayer2.drm; /** - * An exception when doing drm decryption using the In-App Drm + * Thrown when a non-platform component fails to decrypt data. */ public class DecryptionException extends Exception { - private final int errorCode; + /** + * A component specific error code. + */ + public final int errorCode; + + /** + * @param errorCode A component specific error code. + * @param message The detail message. + */ public DecryptionException(int errorCode, String message) { super(message); this.errorCode = errorCode; } - /** - * Get error code - */ - public int getErrorCode() { - return errorCode; - } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DefaultDrmSessionManager.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DefaultDrmSessionManager.java index 5fad98182..4334c7957 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DefaultDrmSessionManager.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DefaultDrmSessionManager.java @@ -25,6 +25,7 @@ import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.support.annotation.IntDef; +import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -222,7 +223,6 @@ public class DefaultDrmSessionManager implements DrmSe this.eventHandler = eventHandler; this.eventListener = eventListener; mediaDrm.setOnEventListener(new MediaDrmEventListener()); - state = STATE_CLOSED; mode = MODE_PLAYBACK; } @@ -307,6 +307,26 @@ public class DefaultDrmSessionManager implements DrmSe // DrmSessionManager implementation. + @Override + public boolean canAcquireSession(@NonNull DrmInitData drmInitData) { + SchemeData schemeData = drmInitData.get(uuid); + if (schemeData == null) { + // No data for this manager's scheme. + return false; + } + String schemeType = schemeData.type; + if (schemeType == null || C.CENC_TYPE_cenc.equals(schemeType)) { + // If there is no scheme information, assume patternless AES-CTR. + return true; + } else if (C.CENC_TYPE_cbc1.equals(schemeType) || C.CENC_TYPE_cbcs.equals(schemeType) + || C.CENC_TYPE_cens.equals(schemeType)) { + // AES-CBC and pattern encryption are supported on API 24 onwards. + return Util.SDK_INT >= 24; + } + // Unknown schemes, assume one of them is supported. + return true; + } + @Override public DrmSession acquireSession(Looper playbackLooper, DrmInitData drmInitData) { Assertions.checkState(this.playbackLooper == null || this.playbackLooper == playbackLooper); @@ -358,7 +378,7 @@ public class DefaultDrmSessionManager implements DrmSe if (--openCount != 0) { return; } - state = STATE_CLOSED; + state = STATE_RELEASED; provisioningInProgress = false; mediaDrmHandler.removeCallbacksAndMessages(null); postResponseHandler.removeCallbacksAndMessages(null); @@ -384,35 +404,19 @@ public class DefaultDrmSessionManager implements DrmSe return state; } - @Override - public final T getMediaCrypto() { - if (state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS) { - throw new IllegalStateException(); - } - return mediaCrypto; - } - - @Override - public boolean requiresSecureDecoderComponent(String mimeType) { - if (state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS) { - throw new IllegalStateException(); - } - return mediaCrypto.requiresSecureDecoderComponent(mimeType); - } - @Override public final DrmSessionException getError() { return state == STATE_ERROR ? lastException : null; } + @Override + public final T getMediaCrypto() { + return mediaCrypto; + } + @Override public Map queryKeyStatus() { - // User may call this method rightfully even if state == STATE_ERROR. So only check if there is - // a sessionId - if (sessionId == null) { - throw new IllegalStateException(); - } - return mediaDrm.queryKeyStatus(sessionId); + return sessionId == null ? null : mediaDrm.queryKeyStatus(sessionId); } @Override @@ -513,6 +517,8 @@ public class DefaultDrmSessionManager implements DrmSe } break; case MODE_RELEASE: + // It's not necessary to restore the key (and open a session to do that) before releasing it + // but this serves as a good sanity/fast-failure check. if (restoreKeys()) { postKeyRequest(offlineLicenseKeySetId, MediaDrm.KEY_TYPE_RELEASE); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DrmInitData.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DrmInitData.java index 7cf53522a..cea99c1a5 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DrmInitData.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DrmInitData.java @@ -17,6 +17,7 @@ package org.telegram.messenger.exoplayer2.drm; import android.os.Parcel; import android.os.Parcelable; +import android.support.annotation.Nullable; import org.telegram.messenger.exoplayer2.C; import org.telegram.messenger.exoplayer2.drm.DrmInitData.SchemeData; import org.telegram.messenger.exoplayer2.util.Assertions; @@ -102,6 +103,33 @@ public final class DrmInitData implements Comparator, Parcelable { return schemeDatas[index]; } + /** + * Returns a copy of the {@link DrmInitData} instance whose {@link SchemeData}s have been updated + * to have the specified scheme type. + * + * @param schemeType A protection scheme type. May be null. + * @return A copy of the {@link DrmInitData} instance whose {@link SchemeData}s have been updated + * to have the specified scheme type. + */ + public DrmInitData copyWithSchemeType(@Nullable String schemeType) { + boolean isCopyRequired = false; + for (SchemeData schemeData : schemeDatas) { + if (!Util.areEqual(schemeData.type, schemeType)) { + isCopyRequired = true; + break; + } + } + if (isCopyRequired) { + SchemeData[] schemeDatas = new SchemeData[this.schemeDatas.length]; + for (int i = 0; i < schemeDatas.length; i++) { + schemeDatas[i] = this.schemeDatas[i].copyWithSchemeType(schemeType); + } + return new DrmInitData(schemeDatas); + } else { + return this; + } + } + @Override public int hashCode() { if (hashCode == 0) { @@ -167,6 +195,10 @@ public final class DrmInitData implements Comparator, Parcelable { * applies to all schemes). */ private final UUID uuid; + /** + * The protection scheme type, or null if not applicable or unknown. + */ + @Nullable public final String type; /** * The mimeType of {@link #data}. */ @@ -183,22 +215,26 @@ public final class DrmInitData implements Comparator, Parcelable { /** * @param uuid The {@link UUID} of the DRM scheme, or {@link C#UUID_NIL} if the data is * universal (i.e. applies to all schemes). + * @param type The type of the protection scheme, or null if not applicable or unknown. * @param mimeType The mimeType of the initialization data. * @param data The initialization data. */ - public SchemeData(UUID uuid, String mimeType, byte[] data) { - this(uuid, mimeType, data, false); + public SchemeData(UUID uuid, @Nullable String type, String mimeType, byte[] data) { + this(uuid, type, mimeType, data, false); } /** * @param uuid The {@link UUID} of the DRM scheme, or {@link C#UUID_NIL} if the data is * universal (i.e. applies to all schemes). + * @param type The type of the protection scheme, or null if not applicable or unknown. * @param mimeType The mimeType of the initialization data. * @param data The initialization data. * @param requiresSecureDecryption Whether secure decryption is required. */ - public SchemeData(UUID uuid, String mimeType, byte[] data, boolean requiresSecureDecryption) { + public SchemeData(UUID uuid, @Nullable String type, String mimeType, byte[] data, + boolean requiresSecureDecryption) { this.uuid = Assertions.checkNotNull(uuid); + this.type = type; this.mimeType = Assertions.checkNotNull(mimeType); this.data = Assertions.checkNotNull(data); this.requiresSecureDecryption = requiresSecureDecryption; @@ -206,6 +242,7 @@ public final class DrmInitData implements Comparator, Parcelable { /* package */ SchemeData(Parcel in) { uuid = new UUID(in.readLong(), in.readLong()); + type = in.readString(); mimeType = in.readString(); data = in.createByteArray(); requiresSecureDecryption = in.readByte() != 0; @@ -221,6 +258,19 @@ public final class DrmInitData implements Comparator, Parcelable { return C.UUID_NIL.equals(uuid) || schemeUuid.equals(uuid); } + /** + * Returns a copy of the {@link SchemeData} instance with the given scheme type. + * + * @param type A protection scheme type. + * @return A copy of the {@link SchemeData} instance with the given scheme type. + */ + public SchemeData copyWithSchemeType(String type) { + if (Util.areEqual(this.type, type)) { + return this; + } + return new SchemeData(uuid, type, mimeType, data, requiresSecureDecryption); + } + @Override public boolean equals(Object obj) { if (!(obj instanceof SchemeData)) { @@ -231,13 +281,14 @@ public final class DrmInitData implements Comparator, Parcelable { } SchemeData other = (SchemeData) obj; return mimeType.equals(other.mimeType) && Util.areEqual(uuid, other.uuid) - && Arrays.equals(data, other.data); + && Util.areEqual(type, other.type) && Arrays.equals(data, other.data); } @Override public int hashCode() { if (hashCode == 0) { int result = uuid.hashCode(); + result = 31 * result + (type == null ? 0 : type.hashCode()); result = 31 * result + mimeType.hashCode(); result = 31 * result + Arrays.hashCode(data); hashCode = result; @@ -256,6 +307,7 @@ public final class DrmInitData implements Comparator, Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeLong(uuid.getMostSignificantBits()); dest.writeLong(uuid.getLeastSignificantBits()); + dest.writeString(type); dest.writeString(mimeType); dest.writeByteArray(data); dest.writeByte((byte) (requiresSecureDecryption ? 1 : 0)); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DrmSession.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DrmSession.java index 1064ef553..b8fef9d67 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DrmSession.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DrmSession.java @@ -28,11 +28,13 @@ import java.util.Map; @TargetApi(16) public interface DrmSession { - /** Wraps the exception which is the cause of the error state. */ + /** + * Wraps the throwable which is the cause of the error state. + */ class DrmSessionException extends Exception { - public DrmSessionException(Exception e) { - super(e); + public DrmSessionException(Throwable cause) { + super(cause); } } @@ -41,16 +43,16 @@ public interface DrmSession { * The state of the DRM session. */ @Retention(RetentionPolicy.SOURCE) - @IntDef({STATE_ERROR, STATE_CLOSED, STATE_OPENING, STATE_OPENED, STATE_OPENED_WITH_KEYS}) - @interface State {} + @IntDef({STATE_RELEASED, STATE_ERROR, STATE_OPENING, STATE_OPENED, STATE_OPENED_WITH_KEYS}) + public @interface State {} + /** + * The session has been released. + */ + int STATE_RELEASED = 0; /** * The session has encountered an error. {@link #getError()} can be used to retrieve the cause. */ - int STATE_ERROR = 0; - /** - * The session is closed. - */ - int STATE_CLOSED = 1; + int STATE_ERROR = 1; /** * The session is being opened. */ @@ -65,66 +67,40 @@ public interface DrmSession { int STATE_OPENED_WITH_KEYS = 4; /** - * Returns the current state of the session. - * - * @return One of {@link #STATE_ERROR}, {@link #STATE_CLOSED}, {@link #STATE_OPENING}, - * {@link #STATE_OPENED} and {@link #STATE_OPENED_WITH_KEYS}. + * Returns the current state of the session, which is one of {@link #STATE_ERROR}, + * {@link #STATE_RELEASED}, {@link #STATE_OPENING}, {@link #STATE_OPENED} and + * {@link #STATE_OPENED_WITH_KEYS}. */ @State int getState(); - /** - * Returns a {@link ExoMediaCrypto} for the open session. - *

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

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

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

    - * This method may be called when the session is in any state. - * - * @return An exception if the state is {@link #STATE_ERROR}. Null otherwise. */ DrmSessionException getError(); /** - * Returns an informative description of the key status for the session. The status is in the form - * of {name, value} pairs. - * - *

    Since DRM license policies vary by vendor, the specific status field names are determined by + * Returns a {@link ExoMediaCrypto} for the open session, or null if called before the session has + * been opened or after it's been released. + */ + T getMediaCrypto(); + + /** + * Returns a map describing the key status for the session, or null if called before the session + * has been opened or after it's been released. + *

    + * Since DRM license policies vary by vendor, the specific status field names are determined by * each DRM vendor. Refer to your DRM provider documentation for definitions of the field names * for a particular DRM engine plugin. * - * @return A map of key status. - * @throws IllegalStateException If called when the session isn't opened. + * @return A map describing the key status for the session, or null if called before the session + * has been opened or after it's been released. * @see MediaDrm#queryKeyStatus(byte[]) */ Map queryKeyStatus(); /** - * Returns the key set id of the offline license loaded into this session, if there is one. Null - * otherwise. + * Returns the key set id of the offline license loaded into this session, or null if there isn't + * one. */ byte[] getOfflineLicenseKeySetId(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DrmSessionManager.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DrmSessionManager.java index 2bd992416..054d21cda 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DrmSessionManager.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DrmSessionManager.java @@ -24,6 +24,16 @@ import android.os.Looper; @TargetApi(16) public interface DrmSessionManager { + /** + * Returns whether the manager is capable of acquiring a session for the given + * {@link DrmInitData}. + * + * @param drmInitData DRM initialization data. + * @return Whether the manager is capable of acquiring a session for the given + * {@link DrmInitData}. + */ + boolean canAcquireSession(DrmInitData drmInitData); + /** * Acquires a {@link DrmSession} for the specified {@link DrmInitData}. The {@link DrmSession} * must be returned to {@link #releaseSession(DrmSession)} when it is no longer required. diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/FrameworkMediaCrypto.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/FrameworkMediaCrypto.java index 5e551d0de..6be560669 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/FrameworkMediaCrypto.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/FrameworkMediaCrypto.java @@ -26,18 +26,39 @@ import org.telegram.messenger.exoplayer2.util.Assertions; public final class FrameworkMediaCrypto implements ExoMediaCrypto { private final MediaCrypto mediaCrypto; + private final boolean forceAllowInsecureDecoderComponents; - /* package */ FrameworkMediaCrypto(MediaCrypto mediaCrypto) { - this.mediaCrypto = Assertions.checkNotNull(mediaCrypto); + /** + * @param mediaCrypto The {@link MediaCrypto} to wrap. + */ + public FrameworkMediaCrypto(MediaCrypto mediaCrypto) { + this(mediaCrypto, false); } + /** + * @param mediaCrypto The {@link MediaCrypto} to wrap. + * @param forceAllowInsecureDecoderComponents Whether to force + * {@link #requiresSecureDecoderComponent(String)} to return {@code false}, rather than + * {@link MediaCrypto#requiresSecureDecoderComponent(String)} of the wrapped + * {@link MediaCrypto}. + */ + public FrameworkMediaCrypto(MediaCrypto mediaCrypto, + boolean forceAllowInsecureDecoderComponents) { + this.mediaCrypto = Assertions.checkNotNull(mediaCrypto); + this.forceAllowInsecureDecoderComponents = forceAllowInsecureDecoderComponents; + } + + /** + * Returns the wrapped {@link MediaCrypto}. + */ public MediaCrypto getWrappedMediaCrypto() { return mediaCrypto; } @Override public boolean requiresSecureDecoderComponent(String mimeType) { - return mediaCrypto.requiresSecureDecoderComponent(mimeType); + return !forceAllowInsecureDecoderComponents + && mediaCrypto.requiresSecureDecoderComponent(mimeType); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/FrameworkMediaDrm.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/FrameworkMediaDrm.java index 82827819f..d0ef325e1 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/FrameworkMediaDrm.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/FrameworkMediaDrm.java @@ -24,7 +24,9 @@ import android.media.NotProvisionedException; import android.media.ResourceBusyException; import android.media.UnsupportedSchemeException; import android.support.annotation.NonNull; +import org.telegram.messenger.exoplayer2.C; import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.Util; import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -163,7 +165,12 @@ public final class FrameworkMediaDrm implements ExoMediaDrm PLAYREADY_KEY_REQUEST_PROPERTIES; - static { - PLAYREADY_KEY_REQUEST_PROPERTIES = new HashMap<>(); - PLAYREADY_KEY_REQUEST_PROPERTIES.put("Content-Type", "text/xml"); - PLAYREADY_KEY_REQUEST_PROPERTIES.put("SOAPAction", - "http://schemas.microsoft.com/DRM/2007/03/protocols/AcquireLicense"); - } - private final HttpDataSource.Factory dataSourceFactory; - private final String defaultUrl; + private final String defaultLicenseUrl; + private final boolean forceDefaultLicenseUrl; private final Map keyRequestProperties; /** - * @param defaultUrl The default license URL. + * @param defaultLicenseUrl The default license URL. Used for key requests that do not specify + * their own license URL. * @param dataSourceFactory A factory from which to obtain {@link HttpDataSource} instances. */ - public HttpMediaDrmCallback(String defaultUrl, HttpDataSource.Factory dataSourceFactory) { - this(defaultUrl, dataSourceFactory, null); + public HttpMediaDrmCallback(String defaultLicenseUrl, HttpDataSource.Factory dataSourceFactory) { + this(defaultLicenseUrl, false, dataSourceFactory); } /** - * @deprecated Use {@link HttpMediaDrmCallback#HttpMediaDrmCallback(String, Factory)}. Request - * properties can be set by calling {@link #setKeyRequestProperty(String, String)}. - * @param defaultUrl The default license URL. + * @param defaultLicenseUrl The default license URL. Used for key requests that do not specify + * their own license URL, or for all key requests if {@code forceDefaultLicenseUrl} is + * set to true. + * @param forceDefaultLicenseUrl Whether to use {@code defaultLicenseUrl} for key requests that + * include their own license URL. * @param dataSourceFactory A factory from which to obtain {@link HttpDataSource} instances. - * @param keyRequestProperties Request properties to set when making key requests, or null. */ - @Deprecated - public HttpMediaDrmCallback(String defaultUrl, HttpDataSource.Factory dataSourceFactory, - Map keyRequestProperties) { + public HttpMediaDrmCallback(String defaultLicenseUrl, boolean forceDefaultLicenseUrl, + HttpDataSource.Factory dataSourceFactory) { this.dataSourceFactory = dataSourceFactory; - this.defaultUrl = defaultUrl; + this.defaultLicenseUrl = defaultLicenseUrl; + this.forceDefaultLicenseUrl = forceDefaultLicenseUrl; this.keyRequestProperties = new HashMap<>(); - if (keyRequestProperties != null) { - this.keyRequestProperties.putAll(keyRequestProperties); - } } /** @@ -120,14 +111,19 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback { @Override public byte[] executeKeyRequest(UUID uuid, KeyRequest request) throws Exception { String url = request.getDefaultUrl(); - if (TextUtils.isEmpty(url)) { - url = defaultUrl; + if (forceDefaultLicenseUrl || TextUtils.isEmpty(url)) { + url = defaultLicenseUrl; } Map requestProperties = new HashMap<>(); - requestProperties.put("Content-Type", "application/octet-stream"); + // Add standard request properties for supported schemes. + String contentType = C.PLAYREADY_UUID.equals(uuid) ? "text/xml" + : (C.CLEARKEY_UUID.equals(uuid) ? "application/json" : "application/octet-stream"); + requestProperties.put("Content-Type", contentType); if (C.PLAYREADY_UUID.equals(uuid)) { - requestProperties.putAll(PLAYREADY_KEY_REQUEST_PROPERTIES); + requestProperties.put("SOAPAction", + "http://schemas.microsoft.com/DRM/2007/03/protocols/AcquireLicense"); } + // Add additional request properties. synchronized (keyRequestProperties) { requestProperties.putAll(keyRequestProperties); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/OfflineLicenseHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/OfflineLicenseHelper.java index 1ed3d7c72..748d164b8 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/OfflineLicenseHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/OfflineLicenseHelper.java @@ -44,23 +44,47 @@ public final class OfflineLicenseHelper { * Instantiates a new instance which uses Widevine CDM. Call {@link #release()} when the instance * is no longer required. * - * @param licenseUrl The default license URL. + * @param defaultLicenseUrl The default license URL. Used for key requests that do not specify + * their own license URL. * @param httpDataSourceFactory A factory from which to obtain {@link HttpDataSource} instances. * @return A new instance which uses Widevine CDM. * @throws UnsupportedDrmException If the Widevine DRM scheme is unsupported or cannot be * instantiated. */ public static OfflineLicenseHelper newWidevineInstance( - String licenseUrl, Factory httpDataSourceFactory) throws UnsupportedDrmException { - return newWidevineInstance( - new HttpMediaDrmCallback(licenseUrl, httpDataSourceFactory), null); + String defaultLicenseUrl, Factory httpDataSourceFactory) + throws UnsupportedDrmException { + return newWidevineInstance(defaultLicenseUrl, false, httpDataSourceFactory, null); } /** * Instantiates a new instance which uses Widevine CDM. Call {@link #release()} when the instance * is no longer required. * - * @param callback Performs key and provisioning requests. + * @param defaultLicenseUrl The default license URL. Used for key requests that do not specify + * their own license URL. + * @param forceDefaultLicenseUrl Whether to use {@code defaultLicenseUrl} for key requests that + * include their own license URL. + * @param httpDataSourceFactory A factory from which to obtain {@link HttpDataSource} instances. + * @return A new instance which uses Widevine CDM. + * @throws UnsupportedDrmException If the Widevine DRM scheme is unsupported or cannot be + * instantiated. + */ + public static OfflineLicenseHelper newWidevineInstance( + String defaultLicenseUrl, boolean forceDefaultLicenseUrl, Factory httpDataSourceFactory) + throws UnsupportedDrmException { + return newWidevineInstance(defaultLicenseUrl, forceDefaultLicenseUrl, httpDataSourceFactory, + null); + } + + /** + * Instantiates a new instance which uses Widevine CDM. Call {@link #release()} when the instance + * is no longer required. + * + * @param defaultLicenseUrl The default license URL. Used for key requests that do not specify + * their own license URL. + * @param forceDefaultLicenseUrl Whether to use {@code defaultLicenseUrl} for key requests that + * include their own license URL. * @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument * to {@link MediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap)}. May be null. * @return A new instance which uses Widevine CDM. @@ -70,9 +94,11 @@ public final class OfflineLicenseHelper { * MediaDrmCallback, HashMap, Handler, EventListener) */ public static OfflineLicenseHelper newWidevineInstance( - MediaDrmCallback callback, HashMap optionalKeyRequestParameters) + String defaultLicenseUrl, boolean forceDefaultLicenseUrl, Factory httpDataSourceFactory, + HashMap optionalKeyRequestParameters) throws UnsupportedDrmException { - return new OfflineLicenseHelper<>(FrameworkMediaDrm.newInstance(C.WIDEVINE_UUID), callback, + return new OfflineLicenseHelper<>(FrameworkMediaDrm.newInstance(C.WIDEVINE_UUID), + new HttpMediaDrmCallback(defaultLicenseUrl, forceDefaultLicenseUrl, httpDataSourceFactory), optionalKeyRequestParameters); } @@ -116,9 +142,32 @@ public final class OfflineLicenseHelper { optionalKeyRequestParameters, new Handler(handlerThread.getLooper()), eventListener); } - /** Releases the helper. Should be called when the helper is no longer required. */ - public void release() { - handlerThread.quit(); + /** + * @see DefaultDrmSessionManager#getPropertyByteArray + */ + public synchronized byte[] getPropertyByteArray(String key) { + return drmSessionManager.getPropertyByteArray(key); + } + + /** + * @see DefaultDrmSessionManager#setPropertyByteArray + */ + public synchronized void setPropertyByteArray(String key, byte[] value) { + drmSessionManager.setPropertyByteArray(key, value); + } + + /** + * @see DefaultDrmSessionManager#getPropertyString + */ + public synchronized String getPropertyString(String key) { + return drmSessionManager.getPropertyString(key); + } + + /** + * @see DefaultDrmSessionManager#setPropertyString + */ + public synchronized void setPropertyString(String key, String value) { + drmSessionManager.setPropertyString(key, value); } /** @@ -186,6 +235,13 @@ public final class OfflineLicenseHelper { return licenseDurationRemainingSec; } + /** + * Releases the helper. Should be called when the helper is no longer required. + */ + public void release() { + handlerThread.quit(); + } + private byte[] blockingKeyRequest(@Mode int licenseMode, byte[] offlineLicenseKeySetId, DrmInitData drmInitData) throws DrmSessionException { DrmSession drmSession = openBlockingKeyRequest(licenseMode, offlineLicenseKeySetId, diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/WidevineUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/WidevineUtil.java index b1e11dbb0..21a9e159f 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/WidevineUtil.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/WidevineUtil.java @@ -34,14 +34,16 @@ public final class WidevineUtil { /** * Returns license and playback durations remaining in seconds. * - * @return A {@link Pair} consisting of the remaining license and playback durations in seconds. - * @throws IllegalStateException If called when a session isn't opened. - * @param drmSession + * @param drmSession The drm session to query. + * @return A {@link Pair} consisting of the remaining license and playback durations in seconds, + * or null if called before the session has been opened or after it's been released. */ public static Pair getLicenseDurationRemainingSec(DrmSession drmSession) { Map keyStatus = drmSession.queryKeyStatus(); - return new Pair<>( - getDurationRemainingSec(keyStatus, PROPERTY_LICENSE_DURATION_REMAINING), + if (keyStatus == null) { + return null; + } + return new Pair<>(getDurationRemainingSec(keyStatus, PROPERTY_LICENSE_DURATION_REMAINING), getDurationRemainingSec(keyStatus, PROPERTY_PLAYBACK_DURATION_REMAINING)); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/DefaultExtractorsFactory.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/DefaultExtractorsFactory.java index 162c2d57e..34d2acb60 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/DefaultExtractorsFactory.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/DefaultExtractorsFactory.java @@ -26,7 +26,9 @@ import org.telegram.messenger.exoplayer2.extractor.ts.AdtsExtractor; import org.telegram.messenger.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory; import org.telegram.messenger.exoplayer2.extractor.ts.PsExtractor; import org.telegram.messenger.exoplayer2.extractor.ts.TsExtractor; +import org.telegram.messenger.exoplayer2.extractor.ts.TsPayloadReader; import org.telegram.messenger.exoplayer2.extractor.wav.WavExtractor; +import org.telegram.messenger.exoplayer2.util.TimestampAdjuster; import java.lang.reflect.Constructor; /** @@ -65,10 +67,16 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory { } private @MatroskaExtractor.Flags int matroskaFlags; + private @Mp4Extractor.Flags int mp4Flags; private @FragmentedMp4Extractor.Flags int fragmentedMp4Flags; private @Mp3Extractor.Flags int mp3Flags; + private @TsExtractor.Mode int tsMode; private @DefaultTsPayloadReaderFactory.Flags int tsFlags; + public DefaultExtractorsFactory() { + tsMode = TsExtractor.MODE_SINGLE_PMT; + } + /** * Sets flags for {@link MatroskaExtractor} instances created by the factory. * @@ -82,6 +90,18 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory { return this; } + /** + * Sets flags for {@link Mp4Extractor} instances created by the factory. + * + * @see Mp4Extractor#Mp4Extractor(int) + * @param flags The flags to use. + * @return The factory, for convenience. + */ + public synchronized DefaultExtractorsFactory setMp4ExtractorFlags(@Mp4Extractor.Flags int flags) { + this.mp4Flags = flags; + return this; + } + /** * Sets flags for {@link FragmentedMp4Extractor} instances created by the factory. * @@ -107,6 +127,18 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory { return this; } + /** + * Sets the mode for {@link TsExtractor} instances created by the factory. + * + * @see TsExtractor#TsExtractor(int, TimestampAdjuster, TsPayloadReader.Factory) + * @param mode The mode to use. + * @return The factory, for convenience. + */ + public synchronized DefaultExtractorsFactory setTsExtractorMode(@TsExtractor.Mode int mode) { + tsMode = mode; + return this; + } + /** * Sets flags for {@link DefaultTsPayloadReaderFactory}s used by {@link TsExtractor} instances * created by the factory. @@ -126,11 +158,11 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory { Extractor[] extractors = new Extractor[FLAC_EXTRACTOR_CONSTRUCTOR == null ? 11 : 12]; extractors[0] = new MatroskaExtractor(matroskaFlags); extractors[1] = new FragmentedMp4Extractor(fragmentedMp4Flags); - extractors[2] = new Mp4Extractor(); + extractors[2] = new Mp4Extractor(mp4Flags); extractors[3] = new Mp3Extractor(mp3Flags); extractors[4] = new AdtsExtractor(); extractors[5] = new Ac3Extractor(); - extractors[6] = new TsExtractor(tsFlags); + extractors[6] = new TsExtractor(tsMode, tsFlags); extractors[7] = new FlvExtractor(); extractors[8] = new OggExtractor(); extractors[9] = new PsExtractor(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/DefaultTrackOutput.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/DefaultTrackOutput.java deleted file mode 100755 index 1359ce4da..000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/DefaultTrackOutput.java +++ /dev/null @@ -1,997 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer2.extractor; - -import org.telegram.messenger.exoplayer2.C; -import org.telegram.messenger.exoplayer2.Format; -import org.telegram.messenger.exoplayer2.FormatHolder; -import org.telegram.messenger.exoplayer2.decoder.DecoderInputBuffer; -import org.telegram.messenger.exoplayer2.upstream.Allocation; -import org.telegram.messenger.exoplayer2.upstream.Allocator; -import org.telegram.messenger.exoplayer2.util.Assertions; -import org.telegram.messenger.exoplayer2.util.ParsableByteArray; -import org.telegram.messenger.exoplayer2.util.Util; -import java.io.EOFException; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.concurrent.LinkedBlockingDeque; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * A {@link TrackOutput} that buffers extracted samples in a queue and allows for consumption from - * that queue. - */ -public final class DefaultTrackOutput implements TrackOutput { - - /** - * A listener for changes to the upstream format. - */ - public interface UpstreamFormatChangedListener { - - /** - * Called on the loading thread when an upstream format change occurs. - * - * @param format The new upstream format. - */ - void onUpstreamFormatChanged(Format format); - - } - - private static final int INITIAL_SCRATCH_SIZE = 32; - - private static final int STATE_ENABLED = 0; - private static final int STATE_ENABLED_WRITING = 1; - private static final int STATE_DISABLED = 2; - - private final Allocator allocator; - private final int allocationLength; - - private final InfoQueue infoQueue; - private final LinkedBlockingDeque dataQueue; - private final BufferExtrasHolder extrasHolder; - private final ParsableByteArray scratch; - private final AtomicInteger state; - - // Accessed only by the consuming thread. - private long totalBytesDropped; - private Format downstreamFormat; - - // Accessed only by the loading thread (or the consuming thread when there is no loading thread). - private boolean pendingFormatAdjustment; - private Format lastUnadjustedFormat; - private long sampleOffsetUs; - private long totalBytesWritten; - private Allocation lastAllocation; - private int lastAllocationOffset; - private boolean pendingSplice; - private UpstreamFormatChangedListener upstreamFormatChangeListener; - - /** - * @param allocator An {@link Allocator} from which allocations for sample data can be obtained. - */ - public DefaultTrackOutput(Allocator allocator) { - this.allocator = allocator; - allocationLength = allocator.getIndividualAllocationLength(); - infoQueue = new InfoQueue(); - dataQueue = new LinkedBlockingDeque<>(); - extrasHolder = new BufferExtrasHolder(); - scratch = new ParsableByteArray(INITIAL_SCRATCH_SIZE); - state = new AtomicInteger(); - lastAllocationOffset = allocationLength; - } - - // Called by the consuming thread, but only when there is no loading thread. - - /** - * Resets the output. - * - * @param enable Whether the output should be enabled. False if it should be disabled. - */ - public void reset(boolean enable) { - int previousState = state.getAndSet(enable ? STATE_ENABLED : STATE_DISABLED); - clearSampleData(); - infoQueue.resetLargestParsedTimestamps(); - if (previousState == STATE_DISABLED) { - downstreamFormat = null; - } - } - - /** - * Sets a source identifier for subsequent samples. - * - * @param sourceId The source identifier. - */ - public void sourceId(int sourceId) { - infoQueue.sourceId(sourceId); - } - - /** - * Indicates that samples subsequently queued to the buffer should be spliced into those already - * queued. - */ - public void splice() { - pendingSplice = true; - } - - /** - * Returns the current absolute write index. - */ - public int getWriteIndex() { - return infoQueue.getWriteIndex(); - } - - /** - * Discards samples from the write side of the buffer. - * - * @param discardFromIndex The absolute index of the first sample to be discarded. - */ - public void discardUpstreamSamples(int discardFromIndex) { - totalBytesWritten = infoQueue.discardUpstreamSamples(discardFromIndex); - dropUpstreamFrom(totalBytesWritten); - } - - /** - * Discards data from the write side of the buffer. Data is discarded from the specified absolute - * position. Any allocations that are fully discarded are returned to the allocator. - * - * @param absolutePosition The absolute position (inclusive) from which to discard data. - */ - private void dropUpstreamFrom(long absolutePosition) { - int relativePosition = (int) (absolutePosition - totalBytesDropped); - // Calculate the index of the allocation containing the position, and the offset within it. - int allocationIndex = relativePosition / allocationLength; - int allocationOffset = relativePosition % allocationLength; - // We want to discard any allocations after the one at allocationIdnex. - int allocationDiscardCount = dataQueue.size() - allocationIndex - 1; - if (allocationOffset == 0) { - // If the allocation at allocationIndex is empty, we should discard that one too. - allocationDiscardCount++; - } - // Discard the allocations. - for (int i = 0; i < allocationDiscardCount; i++) { - allocator.release(dataQueue.removeLast()); - } - // Update lastAllocation and lastAllocationOffset to reflect the new position. - lastAllocation = dataQueue.peekLast(); - lastAllocationOffset = allocationOffset == 0 ? allocationLength : allocationOffset; - } - - // Called by the consuming thread. - - /** - * Disables buffering of sample data and metadata. - */ - public void disable() { - if (state.getAndSet(STATE_DISABLED) == STATE_ENABLED) { - clearSampleData(); - } - } - - /** - * Returns whether the buffer is empty. - */ - public boolean isEmpty() { - return infoQueue.isEmpty(); - } - - /** - * Returns the current absolute read index. - */ - public int getReadIndex() { - return infoQueue.getReadIndex(); - } - - /** - * Peeks the source id of the next sample, or the current upstream source id if the buffer is - * empty. - * - * @return The source id. - */ - public int peekSourceId() { - return infoQueue.peekSourceId(); - } - - /** - * Returns the upstream {@link Format} in which samples are being queued. - */ - public Format getUpstreamFormat() { - return infoQueue.getUpstreamFormat(); - } - - /** - * Returns the largest sample timestamp that has been queued since the last {@link #reset}. - *

    - * Samples that were discarded by calling {@link #discardUpstreamSamples(int)} are not - * considered as having been queued. Samples that were dequeued from the front of the queue are - * considered as having been queued. - * - * @return The largest sample timestamp that has been queued, or {@link Long#MIN_VALUE} if no - * samples have been queued. - */ - public long getLargestQueuedTimestampUs() { - return infoQueue.getLargestQueuedTimestampUs(); - } - - /** - * Skips all samples currently in the buffer. - */ - public void skipAll() { - long nextOffset = infoQueue.skipAll(); - if (nextOffset != C.POSITION_UNSET) { - dropDownstreamTo(nextOffset); - } - } - - /** - * Attempts to skip to the keyframe before or at the specified time. Succeeds only if the buffer - * contains a keyframe with a timestamp of {@code timeUs} or earlier. If - * {@code allowTimeBeyondBuffer} is {@code false} then it is also required that {@code timeUs} - * falls within the buffer. - * - * @param timeUs The seek time. - * @param allowTimeBeyondBuffer Whether the skip can succeed if {@code timeUs} is beyond the end - * of the buffer. - * @return Whether the skip was successful. - */ - public boolean skipToKeyframeBefore(long timeUs, boolean allowTimeBeyondBuffer) { - long nextOffset = infoQueue.skipToKeyframeBefore(timeUs, allowTimeBeyondBuffer); - if (nextOffset == C.POSITION_UNSET) { - return false; - } - dropDownstreamTo(nextOffset); - return true; - } - - /** - * Attempts to read from the queue. - * - * @param formatHolder A {@link FormatHolder} to populate in the case of reading a format. - * @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the - * end of the stream. If the end of the stream has been reached, the - * {@link C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer. - * @param formatRequired Whether the caller requires that the format of the stream be read even if - * it's not changing. A sample will never be read if set to true, however it is still possible - * for the end of stream or nothing to be read. - * @param loadingFinished True if an empty queue should be considered the end of the stream. - * @param decodeOnlyUntilUs If a buffer is read, the {@link C#BUFFER_FLAG_DECODE_ONLY} flag will - * be set if the buffer's timestamp is less than this value. - * @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} or - * {@link C#RESULT_BUFFER_READ}. - */ - public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired, - boolean loadingFinished, long decodeOnlyUntilUs) { - int result = infoQueue.readData(formatHolder, buffer, formatRequired, loadingFinished, - downstreamFormat, extrasHolder); - switch (result) { - case C.RESULT_FORMAT_READ: - downstreamFormat = formatHolder.format; - return C.RESULT_FORMAT_READ; - case C.RESULT_BUFFER_READ: - if (!buffer.isEndOfStream()) { - if (buffer.timeUs < decodeOnlyUntilUs) { - buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY); - } - // Read encryption data if the sample is encrypted. - if (buffer.isEncrypted()) { - readEncryptionData(buffer, extrasHolder); - } - // Write the sample data into the holder. - buffer.ensureSpaceForWrite(extrasHolder.size); - readData(extrasHolder.offset, buffer.data, extrasHolder.size); - // Advance the read head. - dropDownstreamTo(extrasHolder.nextOffset); - } - return C.RESULT_BUFFER_READ; - case C.RESULT_NOTHING_READ: - return C.RESULT_NOTHING_READ; - default: - throw new IllegalStateException(); - } - } - - /** - * Reads encryption data for the current sample. - *

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

    - * Samples that were discarded by calling {@link #discardUpstreamSamples(int)} are not - * considered as having been queued. Samples that were dequeued from the front of the queue are - * considered as having been queued. - * - * @return The largest sample timestamp that has been queued, or {@link Long#MIN_VALUE} if no - * samples have been queued. - */ - public synchronized long getLargestQueuedTimestampUs() { - return Math.max(largestDequeuedTimestampUs, largestQueuedTimestampUs); - } - - /** - * Attempts to read from the queue. - * - * @param formatHolder A {@link FormatHolder} to populate in the case of reading a format. - * @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the - * end of the stream. If a sample is read then the buffer is populated with information - * about the sample, but not its data. The size and absolute position of the data in the - * rolling buffer is stored in {@code extrasHolder}, along with an encryption id if present - * and the absolute position of the first byte that may still be required after the current - * sample has been read. May be null if the caller requires that the format of the stream be - * read even if it's not changing. - * @param formatRequired Whether the caller requires that the format of the stream be read even - * if it's not changing. A sample will never be read if set to true, however it is still - * possible for the end of stream or nothing to be read. - * @param loadingFinished True if an empty queue should be considered the end of the stream. - * @param downstreamFormat The current downstream {@link Format}. If the format of the next - * sample is different to the current downstream format then a format will be read. - * @param extrasHolder The holder into which extra sample information should be written. - * @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} - * or {@link C#RESULT_BUFFER_READ}. - */ - @SuppressWarnings("ReferenceEquality") - public synchronized int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, - boolean formatRequired, boolean loadingFinished, Format downstreamFormat, - BufferExtrasHolder extrasHolder) { - if (queueSize == 0) { - if (loadingFinished) { - buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM); - return C.RESULT_BUFFER_READ; - } else if (upstreamFormat != null - && (formatRequired || upstreamFormat != downstreamFormat)) { - formatHolder.format = upstreamFormat; - return C.RESULT_FORMAT_READ; - } else { - return C.RESULT_NOTHING_READ; - } - } - - if (formatRequired || formats[relativeReadIndex] != downstreamFormat) { - formatHolder.format = formats[relativeReadIndex]; - return C.RESULT_FORMAT_READ; - } - - if (buffer.isFlagsOnly()) { - return C.RESULT_NOTHING_READ; - } - - buffer.timeUs = timesUs[relativeReadIndex]; - buffer.setFlags(flags[relativeReadIndex]); - extrasHolder.size = sizes[relativeReadIndex]; - extrasHolder.offset = offsets[relativeReadIndex]; - extrasHolder.encryptionKeyId = encryptionKeys[relativeReadIndex]; - - largestDequeuedTimestampUs = Math.max(largestDequeuedTimestampUs, buffer.timeUs); - queueSize--; - relativeReadIndex++; - absoluteReadIndex++; - if (relativeReadIndex == capacity) { - // Wrap around. - relativeReadIndex = 0; - } - - extrasHolder.nextOffset = queueSize > 0 ? offsets[relativeReadIndex] - : extrasHolder.offset + extrasHolder.size; - return C.RESULT_BUFFER_READ; - } - - /** - * Skips all samples in the buffer. - * - * @return The offset up to which data should be dropped, or {@link C#POSITION_UNSET} if no - * dropping of data is required. - */ - public synchronized long skipAll() { - if (queueSize == 0) { - return C.POSITION_UNSET; - } - - int lastSampleIndex = (relativeReadIndex + queueSize - 1) % capacity; - relativeReadIndex = (relativeReadIndex + queueSize) % capacity; - absoluteReadIndex += queueSize; - queueSize = 0; - return offsets[lastSampleIndex] + sizes[lastSampleIndex]; - } - - /** - * Attempts to locate the keyframe before or at the specified time. If - * {@code allowTimeBeyondBuffer} is {@code false} then it is also required that {@code timeUs} - * falls within the buffer. - * - * @param timeUs The seek time. - * @param allowTimeBeyondBuffer Whether the skip can succeed if {@code timeUs} is beyond the end - * of the buffer. - * @return The offset of the keyframe's data if the keyframe was present. - * {@link C#POSITION_UNSET} otherwise. - */ - public synchronized long skipToKeyframeBefore(long timeUs, boolean allowTimeBeyondBuffer) { - if (queueSize == 0 || timeUs < timesUs[relativeReadIndex]) { - return C.POSITION_UNSET; - } - - if (timeUs > largestQueuedTimestampUs && !allowTimeBeyondBuffer) { - return C.POSITION_UNSET; - } - - // This could be optimized to use a binary search, however in practice callers to this method - // often pass times near to the start of the buffer. Hence it's unclear whether switching to - // a binary search would yield any real benefit. - int sampleCount = 0; - int sampleCountToKeyframe = -1; - int searchIndex = relativeReadIndex; - while (searchIndex != relativeWriteIndex) { - if (timesUs[searchIndex] > timeUs) { - // We've gone too far. - break; - } else if ((flags[searchIndex] & C.BUFFER_FLAG_KEY_FRAME) != 0) { - // We've found a keyframe, and we're still before the seek position. - sampleCountToKeyframe = sampleCount; - } - searchIndex = (searchIndex + 1) % capacity; - sampleCount++; - } - - if (sampleCountToKeyframe == -1) { - return C.POSITION_UNSET; - } - - relativeReadIndex = (relativeReadIndex + sampleCountToKeyframe) % capacity; - absoluteReadIndex += sampleCountToKeyframe; - queueSize -= sampleCountToKeyframe; - return offsets[relativeReadIndex]; - } - - // Called by the loading thread. - - public synchronized boolean format(Format format) { - if (format == null) { - upstreamFormatRequired = true; - return false; - } - upstreamFormatRequired = false; - if (Util.areEqual(format, upstreamFormat)) { - // Suppress changes between equal formats so we can use referential equality in readData. - return false; - } else { - upstreamFormat = format; - return true; - } - } - - public synchronized void commitSample(long timeUs, @C.BufferFlags int sampleFlags, long offset, - int size, byte[] encryptionKey) { - if (upstreamKeyframeRequired) { - if ((sampleFlags & C.BUFFER_FLAG_KEY_FRAME) == 0) { - return; - } - upstreamKeyframeRequired = false; - } - Assertions.checkState(!upstreamFormatRequired); - commitSampleTimestamp(timeUs); - timesUs[relativeWriteIndex] = timeUs; - offsets[relativeWriteIndex] = offset; - sizes[relativeWriteIndex] = size; - flags[relativeWriteIndex] = sampleFlags; - encryptionKeys[relativeWriteIndex] = encryptionKey; - formats[relativeWriteIndex] = upstreamFormat; - sourceIds[relativeWriteIndex] = upstreamSourceId; - // Increment the write index. - queueSize++; - if (queueSize == capacity) { - // Increase the capacity. - int newCapacity = capacity + SAMPLE_CAPACITY_INCREMENT; - int[] newSourceIds = new int[newCapacity]; - long[] newOffsets = new long[newCapacity]; - long[] newTimesUs = new long[newCapacity]; - int[] newFlags = new int[newCapacity]; - int[] newSizes = new int[newCapacity]; - byte[][] newEncryptionKeys = new byte[newCapacity][]; - Format[] newFormats = new Format[newCapacity]; - int beforeWrap = capacity - relativeReadIndex; - System.arraycopy(offsets, relativeReadIndex, newOffsets, 0, beforeWrap); - System.arraycopy(timesUs, relativeReadIndex, newTimesUs, 0, beforeWrap); - System.arraycopy(flags, relativeReadIndex, newFlags, 0, beforeWrap); - System.arraycopy(sizes, relativeReadIndex, newSizes, 0, beforeWrap); - System.arraycopy(encryptionKeys, relativeReadIndex, newEncryptionKeys, 0, beforeWrap); - System.arraycopy(formats, relativeReadIndex, newFormats, 0, beforeWrap); - System.arraycopy(sourceIds, relativeReadIndex, newSourceIds, 0, beforeWrap); - int afterWrap = relativeReadIndex; - System.arraycopy(offsets, 0, newOffsets, beforeWrap, afterWrap); - System.arraycopy(timesUs, 0, newTimesUs, beforeWrap, afterWrap); - System.arraycopy(flags, 0, newFlags, beforeWrap, afterWrap); - System.arraycopy(sizes, 0, newSizes, beforeWrap, afterWrap); - System.arraycopy(encryptionKeys, 0, newEncryptionKeys, beforeWrap, afterWrap); - System.arraycopy(formats, 0, newFormats, beforeWrap, afterWrap); - System.arraycopy(sourceIds, 0, newSourceIds, beforeWrap, afterWrap); - offsets = newOffsets; - timesUs = newTimesUs; - flags = newFlags; - sizes = newSizes; - encryptionKeys = newEncryptionKeys; - formats = newFormats; - sourceIds = newSourceIds; - relativeReadIndex = 0; - relativeWriteIndex = capacity; - queueSize = capacity; - capacity = newCapacity; - } else { - relativeWriteIndex++; - if (relativeWriteIndex == capacity) { - // Wrap around. - relativeWriteIndex = 0; - } - } - } - - public synchronized void commitSampleTimestamp(long timeUs) { - largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs, timeUs); - } - - /** - * Attempts to discard samples from the tail of the queue to allow samples starting from the - * specified timestamp to be spliced in. - * - * @param timeUs The timestamp at which the splice occurs. - * @return Whether the splice was successful. - */ - public synchronized boolean attemptSplice(long timeUs) { - if (largestDequeuedTimestampUs >= timeUs) { - return false; - } - int retainCount = queueSize; - while (retainCount > 0 - && timesUs[(relativeReadIndex + retainCount - 1) % capacity] >= timeUs) { - retainCount--; - } - discardUpstreamSamples(absoluteReadIndex + retainCount); - return true; - } - - } - - /** - * Holds additional buffer information not held by {@link DecoderInputBuffer}. - */ - private static final class BufferExtrasHolder { - - public int size; - public long offset; - public long nextOffset; - public byte[] encryptionKeyId; - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/DummyTrackOutput.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/DummyTrackOutput.java index 2630ef387..587af494b 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/DummyTrackOutput.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/DummyTrackOutput.java @@ -51,7 +51,7 @@ public final class DummyTrackOutput implements TrackOutput { @Override public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset, - byte[] encryptionKey) { + CryptoData cryptoData) { // Do nothing. } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/Extractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/Extractor.java index 8e5c6bd06..ae7c986e5 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/Extractor.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/Extractor.java @@ -63,7 +63,8 @@ public interface Extractor { void init(ExtractorOutput output); /** - * Extracts data read from a provided {@link ExtractorInput}. + * Extracts data read from a provided {@link ExtractorInput}. Must not be called before + * {@link #init(ExtractorOutput)}. *

    * A single call to this method will block until some progress has been made, but will not block * for longer than this. Hence each call will consume only a small amount of input data. diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/TrackOutput.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/TrackOutput.java index f286682dd..3a407bd5f 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/TrackOutput.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/TrackOutput.java @@ -20,12 +20,78 @@ import org.telegram.messenger.exoplayer2.Format; import org.telegram.messenger.exoplayer2.util.ParsableByteArray; import java.io.EOFException; import java.io.IOException; +import java.util.Arrays; /** * Receives track level data extracted by an {@link Extractor}. */ public interface TrackOutput { + /** + * Holds data required to decrypt a sample. + */ + final class CryptoData { + + /** + * The encryption mode used for the sample. + */ + @C.CryptoMode public final int cryptoMode; + + /** + * The encryption key associated with the sample. Its contents must not be modified. + */ + public final byte[] encryptionKey; + + /** + * The number of encrypted blocks in the encryption pattern, 0 if pattern encryption does not + * apply. + */ + public final int encryptedBlocks; + + /** + * The number of clear blocks in the encryption pattern, 0 if pattern encryption does not + * apply. + */ + public final int clearBlocks; + + /** + * @param cryptoMode See {@link #cryptoMode}. + * @param encryptionKey See {@link #encryptionKey}. + * @param encryptedBlocks See {@link #encryptedBlocks}. + * @param clearBlocks See {@link #clearBlocks}. + */ + public CryptoData(@C.CryptoMode int cryptoMode, byte[] encryptionKey, int encryptedBlocks, + int clearBlocks) { + this.cryptoMode = cryptoMode; + this.encryptionKey = encryptionKey; + this.encryptedBlocks = encryptedBlocks; + this.clearBlocks = clearBlocks; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + CryptoData other = (CryptoData) obj; + return cryptoMode == other.cryptoMode && encryptedBlocks == other.encryptedBlocks + && clearBlocks == other.clearBlocks && Arrays.equals(encryptionKey, other.encryptionKey); + } + + @Override + public int hashCode() { + int result = cryptoMode; + result = 31 * result + Arrays.hashCode(encryptionKey); + result = 31 * result + encryptedBlocks; + result = 31 * result + clearBlocks; + return result; + } + + } + /** * Called when the {@link Format} of the track has been extracted from the stream. * @@ -70,9 +136,9 @@ public interface TrackOutput { * {@link #sampleData(ExtractorInput, int, boolean)} or * {@link #sampleData(ParsableByteArray, int)} since the last byte belonging to the sample * whose metadata is being passed. - * @param encryptionKey The encryption key associated with the sample. May be null. + * @param encryptionData The encryption data required to decrypt the sample. May be null. */ void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset, - byte[] encryptionKey); + CryptoData encryptionData); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/flv/AudioTagPayloadReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/flv/AudioTagPayloadReader.java index 0637c9f24..7727ee220 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/flv/AudioTagPayloadReader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/flv/AudioTagPayloadReader.java @@ -67,7 +67,7 @@ import java.util.Collections; hasOutputFormat = true; } else if (audioFormat == AUDIO_FORMAT_ALAW || audioFormat == AUDIO_FORMAT_ULAW) { String type = audioFormat == AUDIO_FORMAT_ALAW ? MimeTypes.AUDIO_ALAW - : MimeTypes.AUDIO_ULAW; + : MimeTypes.AUDIO_MLAW; int pcmEncoding = (header & 0x01) == 1 ? C.ENCODING_PCM_16BIT : C.ENCODING_PCM_8BIT; Format format = Format.createAudioSampleFormat(null, type, null, Format.NO_VALUE, Format.NO_VALUE, 1, 8000, pcmEncoding, null, null, 0, null); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mkv/MatroskaExtractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mkv/MatroskaExtractor.java index 7ee1194b3..6ba9401b0 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mkv/MatroskaExtractor.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -16,6 +16,7 @@ package org.telegram.messenger.exoplayer2.extractor.mkv; import android.support.annotation.IntDef; +import android.util.Log; import android.util.SparseArray; import org.telegram.messenger.exoplayer2.C; import org.telegram.messenger.exoplayer2.Format; @@ -84,6 +85,8 @@ public final class MatroskaExtractor implements Extractor { */ public static final int FLAG_DISABLE_SEEK_FOR_CUES = 1; + private static final String TAG = "MatroskaExtractor"; + private static final int UNSET_ENTRY_ID = -1; private static final int BLOCK_STATE_START = 0; @@ -117,6 +120,7 @@ public final class MatroskaExtractor implements Extractor { private static final String CODEC_ID_ACM = "A_MS/ACM"; private static final String CODEC_ID_PCM_INT_LIT = "A_PCM/INT/LIT"; private static final String CODEC_ID_SUBRIP = "S_TEXT/UTF8"; + private static final String CODEC_ID_ASS = "S_TEXT/ASS"; private static final String CODEC_ID_VOBSUB = "S_VOBSUB"; private static final String CODEC_ID_PGS = "S_HDMV/PGS"; private static final String CODEC_ID_DVBSUB = "S_DVBSUB"; @@ -223,21 +227,62 @@ public final class MatroskaExtractor implements Extractor { private static final byte[] SUBRIP_PREFIX = new byte[] {49, 10, 48, 48, 58, 48, 48, 58, 48, 48, 44, 48, 48, 48, 32, 45, 45, 62, 32, 48, 48, 58, 48, 48, 58, 48, 48, 44, 48, 48, 48, 10}; /** - * A special end timecode indicating that a subtitle should be displayed until the next subtitle, - * or until the end of the media in the case of the last subtitle. + * The byte offset of the end timecode in {@link #SUBRIP_PREFIX}. + */ + private static final int SUBRIP_PREFIX_END_TIMECODE_OFFSET = 19; + /** + * A special end timecode indicating that a subrip subtitle should be displayed until the next + * subtitle, or until the end of the media in the case of the last subtitle. *

    * Equivalent to the UTF-8 string: " ". */ private static final byte[] SUBRIP_TIMECODE_EMPTY = new byte[] {32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32}; /** - * The byte offset of the end timecode in {@link #SUBRIP_PREFIX}. + * The value by which to divide a time in microseconds to convert it to the unit of the last value + * in a subrip timecode (milliseconds). */ - private static final int SUBRIP_PREFIX_END_TIMECODE_OFFSET = 19; + private static long SUBRIP_TIMECODE_LAST_VALUE_SCALING_FACTOR = 1000; /** - * The length in bytes of a timecode in a subrip prefix. + * The format of a subrip timecode. */ - private static final int SUBRIP_TIMECODE_LENGTH = 12; + private static final String SUBRIP_TIMECODE_FORMAT = "%02d:%02d:%02d,%03d"; + + /** + * Matroska specific format line for SSA subtitles. + */ + private static final byte[] SSA_DIALOGUE_FORMAT = Util.getUtf8Bytes("Format: Start, End, " + + "ReadOrder, Layer, Style, Name, MarginL, MarginR, MarginV, Effect, Text"); + /** + * A template for the prefix that must be added to each SSA sample. The 10 byte end timecode + * starting at {@link #SSA_PREFIX_END_TIMECODE_OFFSET} is set to a dummy value, and must be + * replaced with the duration of the subtitle. + *

    + * Equivalent to the UTF-8 string: "Dialogue: 0:00:00:00,0:00:00:00,". + */ + private static final byte[] SSA_PREFIX = new byte[] {68, 105, 97, 108, 111, 103, 117, 101, 58, 32, + 48, 58, 48, 48, 58, 48, 48, 58, 48, 48, 44, 48, 58, 48, 48, 58, 48, 48, 58, 48, 48, 44}; + /** + * The byte offset of the end timecode in {@link #SSA_PREFIX}. + */ + private static final int SSA_PREFIX_END_TIMECODE_OFFSET = 21; + /** + * The value by which to divide a time in microseconds to convert it to the unit of the last value + * in an SSA timecode (1/100ths of a second). + */ + private static long SSA_TIMECODE_LAST_VALUE_SCALING_FACTOR = 10000; + /** + * A special end timecode indicating that an SSA subtitle should be displayed until the next + * subtitle, or until the end of the media in the case of the last subtitle. + *

    + * Equivalent to the UTF-8 string: " ". + */ + private static final byte[] SSA_TIMECODE_EMPTY = + new byte[] {32, 32, 32, 32, 32, 32, 32, 32, 32, 32}; + /** + * The format of an SSA timecode. + */ + private static final String SSA_TIMECODE_FORMAT = "%01d:%02d:%02d:%02d"; /** * The length in bytes of a WAVEFORMATEX structure. @@ -268,7 +313,7 @@ public final class MatroskaExtractor implements Extractor { private final ParsableByteArray vorbisNumPageSamples; private final ParsableByteArray seekEntryIdBytes; private final ParsableByteArray sampleStrippedBytes; - private final ParsableByteArray subripSample; + private final ParsableByteArray subtitleSample; private final ParsableByteArray encryptionInitializationVector; private final ParsableByteArray encryptionSubsampleData; private ByteBuffer encryptionSubsampleDataBuffer; @@ -346,7 +391,7 @@ public final class MatroskaExtractor implements Extractor { nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); nalLength = new ParsableByteArray(4); sampleStrippedBytes = new ParsableByteArray(); - subripSample = new ParsableByteArray(); + subtitleSample = new ParsableByteArray(); encryptionInitializationVector = new ParsableByteArray(ENCRYPTION_IV_SIZE); encryptionSubsampleData = new ParsableByteArray(); } @@ -580,11 +625,11 @@ public final class MatroskaExtractor implements Extractor { break; case ID_CONTENT_ENCODING: if (currentTrack.hasContentEncryption) { - if (currentTrack.encryptionKeyId == null) { + if (currentTrack.cryptoData == null) { throw new ParserException("Encrypted Track found but ContentEncKeyID was not found"); } - currentTrack.drmInitData = new DrmInitData( - new SchemeData(C.UUID_NIL, MimeTypes.VIDEO_WEBM, currentTrack.encryptionKeyId)); + currentTrack.drmInitData = new DrmInitData(new SchemeData(C.UUID_NIL, null, + MimeTypes.VIDEO_WEBM, currentTrack.cryptoData.encryptionKey)); } break; case ID_CONTENT_ENCODINGS: @@ -888,8 +933,10 @@ public final class MatroskaExtractor implements Extractor { input.readFully(currentTrack.sampleStrippedBytes, 0, contentSize); break; case ID_CONTENT_ENCRYPTION_KEY_ID: - currentTrack.encryptionKeyId = new byte[contentSize]; - input.readFully(currentTrack.encryptionKeyId, 0, contentSize); + byte[] encryptionKey = new byte[contentSize]; + input.readFully(encryptionKey, 0, contentSize); + currentTrack.cryptoData = new TrackOutput.CryptoData(C.CRYPTO_MODE_AES_CTR, encryptionKey, + 0, 0); // We assume patternless AES-CTR. break; case ID_SIMPLE_BLOCK: case ID_BLOCK: @@ -1011,7 +1058,7 @@ public final class MatroskaExtractor implements Extractor { // For SimpleBlock, we have metadata for each sample here. while (blockLacingSampleIndex < blockLacingSampleCount) { writeSampleData(input, track, blockLacingSampleSizes[blockLacingSampleIndex]); - long sampleTimeUs = this.blockTimeUs + long sampleTimeUs = blockTimeUs + (blockLacingSampleIndex * track.defaultSampleDurationNs) / 1000; commitSampleToOutput(track, sampleTimeUs); blockLacingSampleIndex++; @@ -1031,9 +1078,13 @@ public final class MatroskaExtractor implements Extractor { private void commitSampleToOutput(Track track, long timeUs) { if (CODEC_ID_SUBRIP.equals(track.codecId)) { - writeSubripSample(track); + commitSubtitleSample(track, SUBRIP_TIMECODE_FORMAT, SUBRIP_PREFIX_END_TIMECODE_OFFSET, + SUBRIP_TIMECODE_LAST_VALUE_SCALING_FACTOR, SUBRIP_TIMECODE_EMPTY); + } else if (CODEC_ID_ASS.equals(track.codecId)) { + commitSubtitleSample(track, SSA_TIMECODE_FORMAT, SSA_PREFIX_END_TIMECODE_OFFSET, + SSA_TIMECODE_LAST_VALUE_SCALING_FACTOR, SSA_TIMECODE_EMPTY); } - track.output.sampleMetadata(timeUs, blockFlags, sampleBytesWritten, 0, track.encryptionKeyId); + track.output.sampleMetadata(timeUs, blockFlags, sampleBytesWritten, 0, track.cryptoData); sampleRead = true; resetSample(); } @@ -1071,17 +1122,10 @@ public final class MatroskaExtractor implements Extractor { private void writeSampleData(ExtractorInput input, Track track, int size) throws IOException, InterruptedException { if (CODEC_ID_SUBRIP.equals(track.codecId)) { - int sizeWithPrefix = SUBRIP_PREFIX.length + size; - if (subripSample.capacity() < sizeWithPrefix) { - // Initialize subripSample to contain the required prefix and have space to hold a subtitle - // twice as long as this one. - subripSample.data = Arrays.copyOf(SUBRIP_PREFIX, sizeWithPrefix + size); - } - input.readFully(subripSample.data, SUBRIP_PREFIX.length, size); - subripSample.setPosition(0); - subripSample.setLimit(sizeWithPrefix); - // Defer writing the data to the track output. We need to modify the sample data by setting - // the correct end timecode, which we might not have yet. + writeSubtitleSampleData(input, SUBRIP_PREFIX, size); + return; + } else if (CODEC_ID_ASS.equals(track.codecId)) { + writeSubtitleSampleData(input, SSA_PREFIX, size); return; } @@ -1225,31 +1269,50 @@ public final class MatroskaExtractor implements Extractor { } } - private void writeSubripSample(Track track) { - setSubripSampleEndTimecode(subripSample.data, blockDurationUs); - // Note: If we ever want to support DRM protected subtitles then we'll need to output the - // appropriate encryption data here. - track.output.sampleData(subripSample, subripSample.limit()); - sampleBytesWritten += subripSample.limit(); + private void writeSubtitleSampleData(ExtractorInput input, byte[] samplePrefix, int size) + throws IOException, InterruptedException { + int sizeWithPrefix = samplePrefix.length + size; + if (subtitleSample.capacity() < sizeWithPrefix) { + // Initialize subripSample to contain the required prefix and have space to hold a subtitle + // twice as long as this one. + subtitleSample.data = Arrays.copyOf(samplePrefix, sizeWithPrefix + size); + } else { + System.arraycopy(samplePrefix, 0, subtitleSample.data, 0, samplePrefix.length); + } + input.readFully(subtitleSample.data, samplePrefix.length, size); + subtitleSample.reset(sizeWithPrefix); + // Defer writing the data to the track output. We need to modify the sample data by setting + // the correct end timecode, which we might not have yet. } - private static void setSubripSampleEndTimecode(byte[] subripSampleData, long timeUs) { + private void commitSubtitleSample(Track track, String timecodeFormat, int endTimecodeOffset, + long lastTimecodeValueScalingFactor, byte[] emptyTimecode) { + setSampleDuration(subtitleSample.data, blockDurationUs, timecodeFormat, endTimecodeOffset, + lastTimecodeValueScalingFactor, emptyTimecode); + // Note: If we ever want to support DRM protected subtitles then we'll need to output the + // appropriate encryption data here. + track.output.sampleData(subtitleSample, subtitleSample.limit()); + sampleBytesWritten += subtitleSample.limit(); + } + + private static void setSampleDuration(byte[] subripSampleData, long durationUs, + String timecodeFormat, int endTimecodeOffset, long lastTimecodeValueScalingFactor, + byte[] emptyTimecode) { byte[] timeCodeData; - if (timeUs == C.TIME_UNSET) { - timeCodeData = SUBRIP_TIMECODE_EMPTY; + if (durationUs == C.TIME_UNSET) { + timeCodeData = emptyTimecode; } else { - int hours = (int) (timeUs / 3600000000L); - timeUs -= (hours * 3600000000L); - int minutes = (int) (timeUs / 60000000); - timeUs -= (minutes * 60000000); - int seconds = (int) (timeUs / 1000000); - timeUs -= (seconds * 1000000); - int milliseconds = (int) (timeUs / 1000); - timeCodeData = Util.getUtf8Bytes(String.format(Locale.US, "%02d:%02d:%02d,%03d", hours, - minutes, seconds, milliseconds)); + int hours = (int) (durationUs / (3600 * C.MICROS_PER_SECOND)); + durationUs -= (hours * 3600 * C.MICROS_PER_SECOND); + int minutes = (int) (durationUs / (60 * C.MICROS_PER_SECOND)); + durationUs -= (minutes * 60 * C.MICROS_PER_SECOND); + int seconds = (int) (durationUs / C.MICROS_PER_SECOND); + durationUs -= (seconds * C.MICROS_PER_SECOND); + int lastValue = (int) (durationUs / lastTimecodeValueScalingFactor); + timeCodeData = Util.getUtf8Bytes(String.format(Locale.US, timecodeFormat, hours, minutes, + seconds, lastValue)); } - System.arraycopy(timeCodeData, 0, subripSampleData, SUBRIP_PREFIX_END_TIMECODE_OFFSET, - SUBRIP_TIMECODE_LENGTH); + System.arraycopy(timeCodeData, 0, subripSampleData, endTimecodeOffset, emptyTimecode.length); } /** @@ -1380,6 +1443,7 @@ public final class MatroskaExtractor implements Extractor { || CODEC_ID_ACM.equals(codecId) || CODEC_ID_PCM_INT_LIT.equals(codecId) || CODEC_ID_SUBRIP.equals(codecId) + || CODEC_ID_ASS.equals(codecId) || CODEC_ID_VOBSUB.equals(codecId) || CODEC_ID_PGS.equals(codecId) || CODEC_ID_DVBSUB.equals(codecId); @@ -1470,7 +1534,7 @@ public final class MatroskaExtractor implements Extractor { public int defaultSampleDurationNs; public boolean hasContentEncryption; public byte[] sampleStrippedBytes; - public byte[] encryptionKeyId; + public TrackOutput.CryptoData cryptoData; public byte[] codecPrivate; public DrmInitData drmInitData; @@ -1558,7 +1622,12 @@ public final class MatroskaExtractor implements Extractor { break; case CODEC_ID_FOURCC: initializationData = parseFourCcVc1Private(new ParsableByteArray(codecPrivate)); - mimeType = initializationData == null ? MimeTypes.VIDEO_UNKNOWN : MimeTypes.VIDEO_VC1; + if (initializationData != null) { + mimeType = MimeTypes.VIDEO_VC1; + } else { + Log.w(TAG, "Unsupported FourCC. Setting mimeType to " + MimeTypes.VIDEO_UNKNOWN); + mimeType = MimeTypes.VIDEO_UNKNOWN; + } break; case CODEC_ID_THEORA: // TODO: This can be set to the real mimeType if/when we work out what initializationData @@ -1614,24 +1683,35 @@ public final class MatroskaExtractor implements Extractor { break; case CODEC_ID_ACM: mimeType = MimeTypes.AUDIO_RAW; - if (!parseMsAcmCodecPrivate(new ParsableByteArray(codecPrivate))) { - throw new ParserException("Non-PCM MS/ACM is unsupported"); - } - pcmEncoding = Util.getPcmEncoding(audioBitDepth); - if (pcmEncoding == C.ENCODING_INVALID) { - throw new ParserException("Unsupported PCM bit depth: " + audioBitDepth); + if (parseMsAcmCodecPrivate(new ParsableByteArray(codecPrivate))) { + pcmEncoding = Util.getPcmEncoding(audioBitDepth); + if (pcmEncoding == C.ENCODING_INVALID) { + pcmEncoding = Format.NO_VALUE; + mimeType = MimeTypes.AUDIO_UNKNOWN; + Log.w(TAG, "Unsupported PCM bit depth: " + audioBitDepth + ". Setting mimeType to " + + mimeType); + } + } else { + mimeType = MimeTypes.AUDIO_UNKNOWN; + Log.w(TAG, "Non-PCM MS/ACM is unsupported. Setting mimeType to " + mimeType); } break; case CODEC_ID_PCM_INT_LIT: mimeType = MimeTypes.AUDIO_RAW; pcmEncoding = Util.getPcmEncoding(audioBitDepth); if (pcmEncoding == C.ENCODING_INVALID) { - throw new ParserException("Unsupported PCM bit depth: " + audioBitDepth); + pcmEncoding = Format.NO_VALUE; + mimeType = MimeTypes.AUDIO_UNKNOWN; + Log.w(TAG, "Unsupported PCM bit depth: " + audioBitDepth + ". Setting mimeType to " + + mimeType); } break; case CODEC_ID_SUBRIP: mimeType = MimeTypes.APPLICATION_SUBRIP; break; + case CODEC_ID_ASS: + mimeType = MimeTypes.TEXT_SSA; + break; case CODEC_ID_VOBSUB: mimeType = MimeTypes.APPLICATION_VOBSUB; initializationData = Collections.singletonList(codecPrivate); @@ -1682,8 +1762,16 @@ public final class MatroskaExtractor implements Extractor { drmInitData); } else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) { type = C.TRACK_TYPE_TEXT; + format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, selectionFlags, + language, drmInitData); + } else if (MimeTypes.TEXT_SSA.equals(mimeType)) { + type = C.TRACK_TYPE_TEXT; + initializationData = new ArrayList<>(2); + initializationData.add(SSA_DIALOGUE_FORMAT); + initializationData.add(codecPrivate); format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null, - Format.NO_VALUE, selectionFlags, language, drmInitData); + Format.NO_VALUE, selectionFlags, language, Format.NO_VALUE, drmInitData, + Format.OFFSET_SAMPLE_RELATIVE, initializationData); } else if (MimeTypes.APPLICATION_VOBSUB.equals(mimeType) || MimeTypes.APPLICATION_PGS.equals(mimeType) || MimeTypes.APPLICATION_DVBSUBS.equals(mimeType)) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java index dd0f0f52a..84f200c0b 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java @@ -16,6 +16,7 @@ package org.telegram.messenger.exoplayer2.extractor.mp3; import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.util.Util; /** * MP3 seeker that doesn't rely on metadata and seeks assuming the source has a constant bitrate. @@ -41,8 +42,11 @@ import org.telegram.messenger.exoplayer2.C; @Override public long getPosition(long timeUs) { - return durationUs == C.TIME_UNSET ? 0 - : firstFramePosition + (timeUs * bitrate) / (C.MICROS_PER_SECOND * BITS_PER_BYTE); + if (durationUs == C.TIME_UNSET) { + return 0; + } + timeUs = Util.constrainValue(timeUs, 0, durationUs); + return firstFramePosition + (timeUs * bitrate) / (C.MICROS_PER_SECOND * BITS_PER_BYTE); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp3/Mp3Extractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp3/Mp3Extractor.java index 0f86b612b..ef730a632 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp3/Mp3Extractor.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp3/Mp3Extractor.java @@ -78,7 +78,7 @@ public final class Mp3Extractor implements Extractor { /** * The maximum number of bytes to peek when sniffing, excluding the ID3 header, before giving up. */ - private static final int MAX_SNIFF_BYTES = MpegAudioHeader.MAX_FRAME_SIZE_BYTES; + private static final int MAX_SNIFF_BYTES = 16 * 1024; /** * Maximum length of data read into {@link #scratch}. */ @@ -87,10 +87,12 @@ public final class Mp3Extractor implements Extractor { /** * Mask that includes the audio header values that must match between frames. */ - private static final int HEADER_MASK = 0xFFFE0C00; - private static final int XING_HEADER = Util.getIntegerCodeForString("Xing"); - private static final int INFO_HEADER = Util.getIntegerCodeForString("Info"); - private static final int VBRI_HEADER = Util.getIntegerCodeForString("VBRI"); + private static final int MPEG_AUDIO_HEADER_MASK = 0xFFFE0C00; + + private static final int SEEK_HEADER_XING = Util.getIntegerCodeForString("Xing"); + private static final int SEEK_HEADER_INFO = Util.getIntegerCodeForString("Info"); + private static final int SEEK_HEADER_VBRI = Util.getIntegerCodeForString("VBRI"); + private static final int SEEK_HEADER_UNSET = 0; @Flags private final int flags; private final long forcedFirstSampleTimestampUs; @@ -178,7 +180,11 @@ public final class Mp3Extractor implements Extractor { } } if (seeker == null) { - seeker = setupSeeker(input); + seeker = maybeReadSeekFrame(input); + if (seeker == null + || (!seeker.isSeekable() && (flags & FLAG_ENABLE_CONSTANT_BITRATE_SEEKING) != 0)) { + seeker = getConstantBitrateSeeker(input); + } extractorOutput.seekMap(seeker); trackOutput.format(Format.createAudioSampleFormat(null, synchronizedHeader.mimeType, null, Format.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, synchronizedHeader.channels, @@ -197,7 +203,7 @@ public final class Mp3Extractor implements Extractor { } scratch.setPosition(0); int sampleHeaderData = scratch.readInt(); - if ((sampleHeaderData & HEADER_MASK) != (synchronizedHeaderData & HEADER_MASK) + if (!headersMatch(sampleHeaderData, synchronizedHeaderData) || MpegAudioHeader.getFrameSize(sampleHeaderData) == C.LENGTH_UNSET) { // We have lost synchronization, so attempt to resynchronize starting at the next byte. extractorInput.skipFully(1); @@ -254,7 +260,7 @@ public final class Mp3Extractor implements Extractor { int headerData = scratch.readInt(); int frameSize; if ((candidateSynchronizedHeaderData != 0 - && (headerData & HEADER_MASK) != (candidateSynchronizedHeaderData & HEADER_MASK)) + && !headersMatch(headerData, candidateSynchronizedHeaderData)) || (frameSize = MpegAudioHeader.getFrameSize(headerData)) == C.LENGTH_UNSET) { // The header doesn't match the candidate header or is invalid. Try the next byte offset. if (searchedBytes++ == searchLimitBytes) { @@ -337,37 +343,27 @@ public final class Mp3Extractor implements Extractor { } /** - * Returns a {@link Seeker} to seek using metadata read from {@code input}, which should provide - * data from the start of the first frame in the stream. On returning, the input's position will - * be set to the start of the first frame of audio. + * Consumes the next frame from the {@code input} if it contains VBRI or Xing seeking metadata, + * returning a {@link Seeker} if the metadata was present and valid, or {@code null} otherwise. + * After this method returns, the input position is the start of the first frame of audio. * * @param input The {@link ExtractorInput} from which to read. + * @return A {@link Seeker} if seeking metadata was present and valid, or {@code null} otherwise. * @throws IOException Thrown if there was an error reading from the stream. Not expected if the * next two frames were already peeked during synchronization. * @throws InterruptedException Thrown if reading from the stream was interrupted. Not expected if * the next two frames were already peeked during synchronization. - * @return a {@link Seeker}. */ - private Seeker setupSeeker(ExtractorInput input) throws IOException, InterruptedException { - // Read the first frame which may contain a Xing or VBRI header with seeking metadata. + private Seeker maybeReadSeekFrame(ExtractorInput input) throws IOException, InterruptedException { ParsableByteArray frame = new ParsableByteArray(synchronizedHeader.frameSize); input.peekFully(frame.data, 0, synchronizedHeader.frameSize); - - long position = input.getPosition(); - long length = input.getLength(); - int headerData = 0; - Seeker seeker = null; - - // Check if there is a Xing header. int xingBase = (synchronizedHeader.version & 1) != 0 ? (synchronizedHeader.channels != 1 ? 36 : 21) // MPEG 1 : (synchronizedHeader.channels != 1 ? 21 : 13); // MPEG 2 or 2.5 - if (frame.limit() >= xingBase + 4) { - frame.setPosition(xingBase); - headerData = frame.readInt(); - } - if (headerData == XING_HEADER || headerData == INFO_HEADER) { - seeker = XingSeeker.create(synchronizedHeader, frame, position, length); + int seekHeader = getSeekFrameHeader(frame, xingBase); + Seeker seeker; + if (seekHeader == SEEK_HEADER_XING || seekHeader == SEEK_HEADER_INFO) { + seeker = XingSeeker.create(synchronizedHeader, frame, input.getPosition(), input.getLength()); if (seeker != null && !gaplessInfoHolder.hasGaplessInfo()) { // If there is a Xing header, read gapless playback metadata at a fixed offset. input.resetPeekPosition(); @@ -377,28 +373,60 @@ public final class Mp3Extractor implements Extractor { gaplessInfoHolder.setFromXingHeaderValue(scratch.readUnsignedInt24()); } input.skipFully(synchronizedHeader.frameSize); - } else if (frame.limit() >= 40) { - // Check if there is a VBRI header. - frame.setPosition(36); // MPEG audio header (4 bytes) + 32 bytes. - headerData = frame.readInt(); - if (headerData == VBRI_HEADER) { - seeker = VbriSeeker.create(synchronizedHeader, frame, position, length); - input.skipFully(synchronizedHeader.frameSize); + if (seeker != null && !seeker.isSeekable() && seekHeader == SEEK_HEADER_INFO) { + // Fall back to constant bitrate seeking for Info headers missing a table of contents. + return getConstantBitrateSeeker(input); + } + } else if (seekHeader == SEEK_HEADER_VBRI) { + seeker = VbriSeeker.create(synchronizedHeader, frame, input.getPosition(), input.getLength()); + input.skipFully(synchronizedHeader.frameSize); + } else { // seekerHeader == SEEK_HEADER_UNSET + // This frame doesn't contain seeking information, so reset the peek position. + seeker = null; + input.resetPeekPosition(); + } + return seeker; + } + + /** + * Peeks the next frame and returns a {@link ConstantBitrateSeeker} based on its bitrate. + */ + private Seeker getConstantBitrateSeeker(ExtractorInput input) + throws IOException, InterruptedException { + input.peekFully(scratch.data, 0, 4); + scratch.setPosition(0); + MpegAudioHeader.populateHeader(scratch.readInt(), synchronizedHeader); + return new ConstantBitrateSeeker(input.getPosition(), synchronizedHeader.bitrate, + input.getLength()); + } + + /** + * Returns whether the headers match in those bits masked by {@link #MPEG_AUDIO_HEADER_MASK}. + */ + private static boolean headersMatch(int headerA, long headerB) { + return (headerA & MPEG_AUDIO_HEADER_MASK) == (headerB & MPEG_AUDIO_HEADER_MASK); + } + + /** + * Returns {@link #SEEK_HEADER_XING}, {@link #SEEK_HEADER_INFO} or {@link #SEEK_HEADER_VBRI} if + * the provided {@code frame} may have seeking metadata, or {@link #SEEK_HEADER_UNSET} otherwise. + * If seeking metadata is present, {@code frame}'s position is advanced past the header. + */ + private static int getSeekFrameHeader(ParsableByteArray frame, int xingBase) { + if (frame.limit() >= xingBase + 4) { + frame.setPosition(xingBase); + int headerData = frame.readInt(); + if (headerData == SEEK_HEADER_XING || headerData == SEEK_HEADER_INFO) { + return headerData; } } - - if (seeker == null || (!seeker.isSeekable() - && (flags & FLAG_ENABLE_CONSTANT_BITRATE_SEEKING) != 0)) { - // Repopulate the synchronized header in case we had to skip an invalid seeking header, which - // would give an invalid CBR bitrate. - input.resetPeekPosition(); - input.peekFully(scratch.data, 0, 4); - scratch.setPosition(0); - MpegAudioHeader.populateHeader(scratch.readInt(), synchronizedHeader); - seeker = new ConstantBitrateSeeker(input.getPosition(), synchronizedHeader.bitrate, length); + if (frame.limit() >= 40) { + frame.setPosition(36); // MPEG audio header (4 bytes) + 32 bytes. + if (frame.readInt() == SEEK_HEADER_VBRI) { + return SEEK_HEADER_VBRI; + } } - - return seeker; + return SEEK_HEADER_UNSET; } /** diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/Atom.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/Atom.java index 34d679bea..6de850ed9 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/Atom.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/Atom.java @@ -39,9 +39,14 @@ import java.util.List; public static final int LONG_HEADER_SIZE = 16; /** - * Value for the first 32 bits of atomSize when the atom size is actually a long value. + * Value for the size field in an atom that defines its size in the largesize field. */ - public static final int LONG_SIZE_PREFIX = 1; + public static final int DEFINES_LARGE_SIZE = 1; + + /** + * Value for the size field in an atom that extends to the end of the file. + */ + public static final int EXTENDS_TO_END_SIZE = 0; public static final int TYPE_ftyp = Util.getIntegerCodeForString("ftyp"); public static final int TYPE_avc1 = Util.getIntegerCodeForString("avc1"); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/AtomParsers.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/AtomParsers.java index 8d08c6895..4c11f223a 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/AtomParsers.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/AtomParsers.java @@ -60,11 +60,13 @@ import java.util.List; * @param duration The duration in units of the timescale declared in the mvhd atom, or * {@link C#TIME_UNSET} if the duration should be parsed from the tkhd atom. * @param drmInitData {@link DrmInitData} to be included in the format. + * @param ignoreEditLists Whether to ignore any edit lists in the trak box. * @param isQuickTime True for QuickTime media. False otherwise. * @return A {@link Track} instance, or {@code null} if the track's type isn't supported. */ public static Track parseTrak(Atom.ContainerAtom trak, Atom.LeafAtom mvhd, long duration, - DrmInitData drmInitData, boolean isQuickTime) throws ParserException { + DrmInitData drmInitData, boolean ignoreEditLists, boolean isQuickTime) + throws ParserException { Atom.ContainerAtom mdia = trak.getContainerAtomOfType(Atom.TYPE_mdia); int trackType = parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).data); if (trackType == C.TRACK_TYPE_UNKNOWN) { @@ -88,11 +90,17 @@ import java.util.List; Pair mdhdData = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data); StsdData stsdData = parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data, tkhdData.id, tkhdData.rotationDegrees, mdhdData.second, drmInitData, isQuickTime); - Pair edtsData = parseEdts(trak.getContainerAtomOfType(Atom.TYPE_edts)); + long[] editListDurations = null; + long[] editListMediaTimes = null; + if (!ignoreEditLists) { + Pair edtsData = parseEdts(trak.getContainerAtomOfType(Atom.TYPE_edts)); + editListDurations = edtsData.first; + editListMediaTimes = edtsData.second; + } return stsdData.format == null ? null : new Track(tkhdData.id, trackType, mdhdData.first, movieTimescale, durationUs, stsdData.format, stsdData.requiredSampleTransformation, stsdData.trackEncryptionBoxes, - stsdData.nalUnitLengthFieldLength, edtsData.first, edtsData.second); + stsdData.nalUnitLengthFieldLength, editListDurations, editListMediaTimes); } /** @@ -395,7 +403,11 @@ import java.util.List; hasSyncSample |= (editedFlags[i] & C.BUFFER_FLAG_KEY_FRAME) != 0; } if (!hasSyncSample) { - throw new ParserException("The edited sample sequence does not contain a sync sample."); + // We don't support edit lists where the edited sample sequence doesn't contain a sync sample. + // Such edit lists are often (although not always) broken, so we ignore it and continue. + Log.w(TAG, "Ignoring edit list: Edited sample sequence does not contain a sync sample."); + Util.scaleLargeTimestampsInPlace(timestamps, C.MICROS_PER_SECOND, track.timescale); + return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags); } return new TrackSampleTable(editedOffsets, editedSizes, editedMaximumSize, editedTimestamps, @@ -615,10 +627,10 @@ import java.util.List; || childAtomType == Atom.TYPE_wvtt || childAtomType == Atom.TYPE_stpp || childAtomType == Atom.TYPE_c608) { parseTextSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId, - language, drmInitData, out); + language, out); } else if (childAtomType == Atom.TYPE_camm) { out.format = Format.createSampleFormat(Integer.toString(trackId), - MimeTypes.APPLICATION_CAMERA_MOTION, null, Format.NO_VALUE, drmInitData); + MimeTypes.APPLICATION_CAMERA_MOTION, null, Format.NO_VALUE, null); } stsd.setPosition(childStartPosition + childAtomSize); } @@ -626,8 +638,7 @@ import java.util.List; } private static void parseTextSampleEntry(ParsableByteArray parent, int atomType, int position, - int atomSize, int trackId, String language, DrmInitData drmInitData, StsdData out) - throws ParserException { + int atomSize, int trackId, String language, StsdData out) throws ParserException { parent.setPosition(position + Atom.HEADER_SIZE + StsdData.STSD_HEADER_SIZE); // Default values. @@ -658,8 +669,7 @@ import java.util.List; } out.format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null, - Format.NO_VALUE, 0, language, Format.NO_VALUE, drmInitData, subsampleOffsetUs, - initializationData); + Format.NO_VALUE, 0, language, Format.NO_VALUE, null, subsampleOffsetUs, initializationData); } private static void parseVideoSampleEntry(ParsableByteArray parent, int atomType, int position, @@ -676,9 +686,20 @@ import java.util.List; int childPosition = parent.getPosition(); if (atomType == Atom.TYPE_encv) { - atomType = parseSampleEntryEncryptionData(parent, position, size, out, entryIndex); + Pair sampleEntryEncryptionData = parseSampleEntryEncryptionData( + parent, position, size); + if (sampleEntryEncryptionData != null) { + atomType = sampleEntryEncryptionData.first; + drmInitData = drmInitData == null ? null + : drmInitData.copyWithSchemeType(sampleEntryEncryptionData.second.schemeType); + out.trackEncryptionBoxes[entryIndex] = sampleEntryEncryptionData.second; + } parent.setPosition(childPosition); } + // TODO: Uncomment when [Internal: b/63092960] is fixed. + // else { + // drmInitData = null; + // } List initializationData = null; String mimeType = null; @@ -770,7 +791,7 @@ import java.util.List; * * @param edtsAtom edts (edit box) atom to decode. * @return Pair of edit list durations and edit list media times, or a pair of nulls if they are - * not present. + * not present. */ private static Pair parseEdts(Atom.ContainerAtom edtsAtom) { Atom.LeafAtom elst; @@ -845,9 +866,20 @@ import java.util.List; int childPosition = parent.getPosition(); if (atomType == Atom.TYPE_enca) { - atomType = parseSampleEntryEncryptionData(parent, position, size, out, entryIndex); + Pair sampleEntryEncryptionData = parseSampleEntryEncryptionData( + parent, position, size); + if (sampleEntryEncryptionData != null) { + atomType = sampleEntryEncryptionData.first; + drmInitData = drmInitData == null ? null + : drmInitData.copyWithSchemeType(sampleEntryEncryptionData.second.schemeType); + out.trackEncryptionBoxes[entryIndex] = sampleEntryEncryptionData.second; + } parent.setPosition(childPosition); } + // TODO: Uncomment when [Internal: b/63092960] is fixed. + // else { + // drmInitData = null; + // } // If the atom type determines a MIME type, set it immediately. String mimeType = null; @@ -975,9 +1007,10 @@ import java.util.List; int objectTypeIndication = parent.readUnsignedByte(); String mimeType; switch (objectTypeIndication) { - case 0x6B: - mimeType = MimeTypes.AUDIO_MPEG; - return Pair.create(mimeType, null); + case 0x60: + case 0x61: + mimeType = MimeTypes.VIDEO_MPEG2; + break; case 0x20: mimeType = MimeTypes.VIDEO_MP4V; break; @@ -987,6 +1020,9 @@ import java.util.List; case 0x23: mimeType = MimeTypes.VIDEO_H265; break; + case 0x6B: + mimeType = MimeTypes.AUDIO_MPEG; + return Pair.create(mimeType, null); case 0x40: case 0x66: case 0x67: @@ -1014,8 +1050,8 @@ import java.util.List; parent.skipBytes(12); - // Start of the AudioSpecificConfig. - parent.skipBytes(1); // AudioSpecificConfig tag + // Start of the DecoderSpecificInfo. + parent.skipBytes(1); // DecoderSpecificInfo tag int initializationDataSize = parseExpandableClassSize(parent); byte[] initializationData = new byte[initializationDataSize]; parent.readBytes(initializationData, 0, initializationDataSize); @@ -1023,11 +1059,12 @@ import java.util.List; } /** - * Parses encryption data from an audio/video sample entry, populating {@code out} and returning - * the unencrypted atom type, or 0 if no common encryption sinf atom was present. + * Parses encryption data from an audio/video sample entry, returning a pair consisting of the + * unencrypted atom type and a {@link TrackEncryptionBox}. Null is returned if no common + * encryption sinf atom was present. */ - private static int parseSampleEntryEncryptionData(ParsableByteArray parent, int position, - int size, StsdData out, int entryIndex) { + private static Pair parseSampleEntryEncryptionData( + ParsableByteArray parent, int position, int size) { int childPosition = parent.getPosition(); while (childPosition - position < size) { parent.setPosition(childPosition); @@ -1035,25 +1072,23 @@ import java.util.List; Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); int childAtomType = parent.readInt(); if (childAtomType == Atom.TYPE_sinf) { - Pair result = parseSinfFromParent(parent, childPosition, - childAtomSize); + Pair result = parseCommonEncryptionSinfFromParent(parent, + childPosition, childAtomSize); if (result != null) { - out.trackEncryptionBoxes[entryIndex] = result.second; - return result.first; + return result; } } childPosition += childAtomSize; } - // This enca/encv box does not have a data format so return an invalid atom type. - return 0; + return null; } - private static Pair parseSinfFromParent(ParsableByteArray parent, - int position, int size) { + /* package */ static Pair parseCommonEncryptionSinfFromParent( + ParsableByteArray parent, int position, int size) { int childPosition = position + Atom.HEADER_SIZE; - - boolean isCencScheme = false; - TrackEncryptionBox trackEncryptionBox = null; + int schemeInformationBoxPosition = C.POSITION_UNSET; + int schemeInformationBoxSize = 0; + String schemeType = null; Integer dataFormat = null; while (childPosition - position < size) { parent.setPosition(childPosition); @@ -1063,36 +1098,61 @@ import java.util.List; dataFormat = parent.readInt(); } else if (childAtomType == Atom.TYPE_schm) { parent.skipBytes(4); - isCencScheme = parent.readInt() == TYPE_cenc; + // Common encryption scheme_type values are defined in ISO/IEC 23001-7:2016, section 4.1. + schemeType = parent.readString(4); } else if (childAtomType == Atom.TYPE_schi) { - trackEncryptionBox = parseSchiFromParent(parent, childPosition, childAtomSize); + schemeInformationBoxPosition = childPosition; + schemeInformationBoxSize = childAtomSize; } childPosition += childAtomSize; } - if (isCencScheme) { + if (C.CENC_TYPE_cenc.equals(schemeType) || C.CENC_TYPE_cbc1.equals(schemeType) + || C.CENC_TYPE_cens.equals(schemeType) || C.CENC_TYPE_cbcs.equals(schemeType)) { Assertions.checkArgument(dataFormat != null, "frma atom is mandatory"); - Assertions.checkArgument(trackEncryptionBox != null, "schi->tenc atom is mandatory"); - return Pair.create(dataFormat, trackEncryptionBox); + Assertions.checkArgument(schemeInformationBoxPosition != C.POSITION_UNSET, + "schi atom is mandatory"); + TrackEncryptionBox encryptionBox = parseSchiFromParent(parent, schemeInformationBoxPosition, + schemeInformationBoxSize, schemeType); + Assertions.checkArgument(encryptionBox != null, "tenc atom is mandatory"); + return Pair.create(dataFormat, encryptionBox); } else { return null; } } private static TrackEncryptionBox parseSchiFromParent(ParsableByteArray parent, int position, - int size) { + int size, String schemeType) { int childPosition = position + Atom.HEADER_SIZE; while (childPosition - position < size) { parent.setPosition(childPosition); int childAtomSize = parent.readInt(); int childAtomType = parent.readInt(); if (childAtomType == Atom.TYPE_tenc) { - parent.skipBytes(6); - boolean defaultIsEncrypted = parent.readUnsignedByte() == 1; - int defaultInitVectorSize = parent.readUnsignedByte(); + int fullAtom = parent.readInt(); + int version = Atom.parseFullAtomVersion(fullAtom); + parent.skipBytes(1); // reserved = 0. + int defaultCryptByteBlock = 0; + int defaultSkipByteBlock = 0; + if (version == 0) { + parent.skipBytes(1); // reserved = 0. + } else /* version 1 or greater */ { + int patternByte = parent.readUnsignedByte(); + defaultCryptByteBlock = (patternByte & 0xF0) >> 4; + defaultSkipByteBlock = patternByte & 0x0F; + } + boolean defaultIsProtected = parent.readUnsignedByte() == 1; + int defaultPerSampleIvSize = parent.readUnsignedByte(); byte[] defaultKeyId = new byte[16]; parent.readBytes(defaultKeyId, 0, defaultKeyId.length); - return new TrackEncryptionBox(defaultIsEncrypted, defaultInitVectorSize, defaultKeyId); + byte[] constantIv = null; + if (defaultIsProtected && defaultPerSampleIvSize == 0) { + int constantIvSize = parent.readUnsignedByte(); + constantIv = new byte[constantIvSize]; + parent.readBytes(constantIv, 0, constantIvSize); + } + return new TrackEncryptionBox(defaultIsProtected, schemeType, defaultPerSampleIvSize, + defaultKeyId, defaultCryptByteBlock, defaultSkipByteBlock, constantIv); } childPosition += childAtomSize; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java index ff0b671fc..113b7ef4c 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -74,7 +74,7 @@ public final class FragmentedMp4Extractor implements Extractor { @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, value = {FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME, FLAG_WORKAROUND_IGNORE_TFDT_BOX, FLAG_ENABLE_EMSG_TRACK, FLAG_ENABLE_CEA608_TRACK, - FLAG_SIDELOADED}) + FLAG_SIDELOADED, FLAG_WORKAROUND_IGNORE_EDIT_LISTS}) public @interface Flags {} /** * Flag to work around an issue in some video streams where every frame is marked as a sync frame. @@ -103,6 +103,10 @@ public final class FragmentedMp4Extractor implements Extractor { * container. */ private static final int FLAG_SIDELOADED = 16; + /** + * Flag to ignore any edit lists in the stream. + */ + public static final int FLAG_WORKAROUND_IGNORE_EDIT_LISTS = 32; private static final String TAG = "FragmentedMp4Extractor"; private static final int SAMPLE_GROUP_TYPE_seig = Util.getIntegerCodeForString("seig"); @@ -128,6 +132,7 @@ public final class FragmentedMp4Extractor implements Extractor { private final ParsableByteArray nalPrefix; private final ParsableByteArray nalBuffer; private final ParsableByteArray encryptionSignalByte; + private final ParsableByteArray defaultInitializationVector; // Adjusts sample timestamps. private final TimestampAdjuster timestampAdjuster; @@ -197,6 +202,7 @@ public final class FragmentedMp4Extractor implements Extractor { nalPrefix = new ParsableByteArray(5); nalBuffer = new ParsableByteArray(); encryptionSignalByte = new ParsableByteArray(1); + defaultInitializationVector = new ParsableByteArray(); extendedTypeScratch = new byte[16]; containerAtoms = new Stack<>(); pendingMetadataSampleInfos = new LinkedList<>(); @@ -281,12 +287,22 @@ public final class FragmentedMp4Extractor implements Extractor { atomType = atomHeader.readInt(); } - if (atomSize == Atom.LONG_SIZE_PREFIX) { - // Read the extended atom size. + if (atomSize == Atom.DEFINES_LARGE_SIZE) { + // Read the large size. int headerBytesRemaining = Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE; input.readFully(atomHeader.data, Atom.HEADER_SIZE, headerBytesRemaining); atomHeaderBytesRead += headerBytesRemaining; atomSize = atomHeader.readUnsignedLongToLong(); + } else if (atomSize == Atom.EXTENDS_TO_END_SIZE) { + // The atom extends to the end of the file. Note that if the atom is within a container we can + // work out its size even if the input length is unknown. + long endPosition = input.getLength(); + if (endPosition == C.LENGTH_UNSET && !containerAtoms.isEmpty()) { + endPosition = containerAtoms.peek().endPosition; + } + if (endPosition != C.LENGTH_UNSET) { + atomSize = endPosition - input.getPosition() + atomHeaderBytesRead; + } } if (atomSize < atomHeaderBytesRead) { @@ -414,7 +430,7 @@ public final class FragmentedMp4Extractor implements Extractor { Atom.ContainerAtom atom = moov.containerChildren.get(i); if (atom.type == Atom.TYPE_trak) { Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd), duration, - drmInitData, false); + drmInitData, (flags & FLAG_WORKAROUND_IGNORE_EDIT_LISTS) != 0, false); if (track != null) { tracks.put(track.id, track); } @@ -462,8 +478,8 @@ public final class FragmentedMp4Extractor implements Extractor { if ((flags & FLAG_ENABLE_CEA608_TRACK) != 0 && cea608TrackOutputs == null) { TrackOutput cea608TrackOutput = extractorOutput.track(trackBundles.size() + 1, C.TRACK_TYPE_TEXT); - cea608TrackOutput.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608, - null, Format.NO_VALUE, 0, null, null)); + cea608TrackOutput.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608, 0, + null)); cea608TrackOutputs = new TrackOutput[] {cea608TrackOutput}; } } @@ -559,11 +575,12 @@ public final class FragmentedMp4Extractor implements Extractor { parseTruns(traf, trackBundle, decodeTime, flags); + TrackEncryptionBox encryptionBox = trackBundle.track + .getSampleDescriptionEncryptionBox(fragment.header.sampleDescriptionIndex); + LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz); if (saiz != null) { - TrackEncryptionBox trackEncryptionBox = trackBundle.track - .sampleDescriptionEncryptionBoxes[fragment.header.sampleDescriptionIndex]; - parseSaiz(trackEncryptionBox, saiz.data, fragment); + parseSaiz(encryptionBox, saiz.data, fragment); } LeafAtom saio = traf.getLeafAtomOfType(Atom.TYPE_saio); @@ -579,7 +596,8 @@ public final class FragmentedMp4Extractor implements Extractor { LeafAtom sbgp = traf.getLeafAtomOfType(Atom.TYPE_sbgp); LeafAtom sgpd = traf.getLeafAtomOfType(Atom.TYPE_sgpd); if (sbgp != null && sgpd != null) { - parseSgpd(sbgp.data, sgpd.data, fragment); + parseSgpd(sbgp.data, sgpd.data, encryptionBox != null ? encryptionBox.schemeType : null, + fragment); } int leafChildrenSize = traf.leafChildren.size(); @@ -811,7 +829,7 @@ public final class FragmentedMp4Extractor implements Extractor { // here, because unsigned integers will still be parsed correctly (unless their top bit is // set, which is never true in practice because sample offsets are always small). int sampleOffset = trun.readInt(); - sampleCompositionTimeOffsetTable[i] = (int) ((sampleOffset * 1000) / timescale); + sampleCompositionTimeOffsetTable[i] = (int) ((sampleOffset * 1000L) / timescale); } else { sampleCompositionTimeOffsetTable[i] = 0; } @@ -868,8 +886,8 @@ public final class FragmentedMp4Extractor implements Extractor { out.fillEncryptionData(senc); } - private static void parseSgpd(ParsableByteArray sbgp, ParsableByteArray sgpd, TrackFragment out) - throws ParserException { + private static void parseSgpd(ParsableByteArray sbgp, ParsableByteArray sgpd, String schemeType, + TrackFragment out) throws ParserException { sbgp.setPosition(Atom.HEADER_SIZE); int sbgpFullAtom = sbgp.readInt(); if (sbgp.readInt() != SAMPLE_GROUP_TYPE_seig) { @@ -877,9 +895,9 @@ public final class FragmentedMp4Extractor implements Extractor { return; } if (Atom.parseFullAtomVersion(sbgpFullAtom) == 1) { - sbgp.skipBytes(4); + sbgp.skipBytes(4); // default_length. } - if (sbgp.readInt() != 1) { + if (sbgp.readInt() != 1) { // entry_count. throw new ParserException("Entry count in sbgp != 1 (unsupported)."); } @@ -892,25 +910,35 @@ public final class FragmentedMp4Extractor implements Extractor { int sgpdVersion = Atom.parseFullAtomVersion(sgpdFullAtom); if (sgpdVersion == 1) { if (sgpd.readUnsignedInt() == 0) { - throw new ParserException("Variable length decription in sgpd found (unsupported)"); + throw new ParserException("Variable length description in sgpd found (unsupported)"); } } else if (sgpdVersion >= 2) { - sgpd.skipBytes(4); + sgpd.skipBytes(4); // default_sample_description_index. } - if (sgpd.readUnsignedInt() != 1) { + if (sgpd.readUnsignedInt() != 1) { // entry_count. throw new ParserException("Entry count in sgpd != 1 (unsupported)."); } // CencSampleEncryptionInformationGroupEntry - sgpd.skipBytes(2); + sgpd.skipBytes(1); // reserved = 0. + int patternByte = sgpd.readUnsignedByte(); + int cryptByteBlock = (patternByte & 0xF0) >> 4; + int skipByteBlock = patternByte & 0x0F; boolean isProtected = sgpd.readUnsignedByte() == 1; if (!isProtected) { return; } - int initVectorSize = sgpd.readUnsignedByte(); + int perSampleIvSize = sgpd.readUnsignedByte(); byte[] keyId = new byte[16]; sgpd.readBytes(keyId, 0, keyId.length); + byte[] constantIv = null; + if (isProtected && perSampleIvSize == 0) { + int constantIvSize = sgpd.readUnsignedByte(); + constantIv = new byte[constantIvSize]; + sgpd.readBytes(constantIv, 0, constantIvSize); + } out.definesEncryptionData = true; - out.trackEncryptionBox = new TrackEncryptionBox(isProtected, initVectorSize, keyId); + out.trackEncryptionBox = new TrackEncryptionBox(isProtected, schemeType, perSampleIvSize, keyId, + cryptByteBlock, skipByteBlock, constantIv); } /** @@ -1122,19 +1150,24 @@ public final class FragmentedMp4Extractor implements Extractor { } long sampleTimeUs = fragment.getSamplePresentationTime(sampleIndex) * 1000L; - @C.BufferFlags int sampleFlags = (fragment.definesEncryptionData ? C.BUFFER_FLAG_ENCRYPTED : 0) - | (fragment.sampleIsSyncFrameTable[sampleIndex] ? C.BUFFER_FLAG_KEY_FRAME : 0); - int sampleDescriptionIndex = fragment.header.sampleDescriptionIndex; - byte[] encryptionKey = null; - if (fragment.definesEncryptionData) { - encryptionKey = fragment.trackEncryptionBox != null - ? fragment.trackEncryptionBox.keyId - : track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex].keyId; - } if (timestampAdjuster != null) { sampleTimeUs = timestampAdjuster.adjustSampleTimestamp(sampleTimeUs); } - output.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, encryptionKey); + + @C.BufferFlags int sampleFlags = fragment.sampleIsSyncFrameTable[sampleIndex] + ? C.BUFFER_FLAG_KEY_FRAME : 0; + + // Encryption data. + TrackOutput.CryptoData cryptoData = null; + if (fragment.definesEncryptionData) { + sampleFlags |= C.BUFFER_FLAG_ENCRYPTED; + TrackEncryptionBox encryptionBox = fragment.trackEncryptionBox != null + ? fragment.trackEncryptionBox + : track.getSampleDescriptionEncryptionBox(fragment.header.sampleDescriptionIndex); + cryptoData = encryptionBox.cryptoData; + } + + output.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, cryptoData); while (!pendingMetadataSampleInfos.isEmpty()) { MetadataSampleInfo sampleInfo = pendingMetadataSampleInfos.removeFirst(); @@ -1190,12 +1223,24 @@ public final class FragmentedMp4Extractor implements Extractor { */ private int appendSampleEncryptionData(TrackBundle trackBundle) { TrackFragment trackFragment = trackBundle.fragment; - ParsableByteArray sampleEncryptionData = trackFragment.sampleEncryptionData; int sampleDescriptionIndex = trackFragment.header.sampleDescriptionIndex; TrackEncryptionBox encryptionBox = trackFragment.trackEncryptionBox != null ? trackFragment.trackEncryptionBox - : trackBundle.track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex]; - int vectorSize = encryptionBox.initializationVectorSize; + : trackBundle.track.getSampleDescriptionEncryptionBox(sampleDescriptionIndex); + + ParsableByteArray initializationVectorData; + int vectorSize; + if (encryptionBox.initializationVectorSize != 0) { + initializationVectorData = trackFragment.sampleEncryptionData; + vectorSize = encryptionBox.initializationVectorSize; + } else { + // The default initialization vector should be used. + byte[] initVectorData = encryptionBox.defaultInitializationVector; + defaultInitializationVector.reset(initVectorData, initVectorData.length); + initializationVectorData = defaultInitializationVector; + vectorSize = initVectorData.length; + } + boolean subsampleEncryption = trackFragment .sampleHasSubsampleEncryptionTable[trackBundle.currentSampleIndex]; @@ -1205,20 +1250,20 @@ public final class FragmentedMp4Extractor implements Extractor { TrackOutput output = trackBundle.output; output.sampleData(encryptionSignalByte, 1); // Write the vector. - output.sampleData(sampleEncryptionData, vectorSize); + output.sampleData(initializationVectorData, vectorSize); // If we don't have subsample encryption data, we're done. if (!subsampleEncryption) { return 1 + vectorSize; } // Write the subsample encryption data. - int subsampleCount = sampleEncryptionData.readUnsignedShort(); - sampleEncryptionData.skipBytes(-2); + ParsableByteArray subsampleEncryptionData = trackFragment.sampleEncryptionData; + int subsampleCount = subsampleEncryptionData.readUnsignedShort(); + subsampleEncryptionData.skipBytes(-2); int subsampleDataLength = 2 + 6 * subsampleCount; - output.sampleData(sampleEncryptionData, subsampleDataLength); + output.sampleData(subsampleEncryptionData, subsampleDataLength); return 1 + vectorSize + subsampleDataLength; } - /** Returns DrmInitData from leaf atoms. */ private static DrmInitData getDrmInitDataFromAtoms(List leafChildren) { ArrayList schemeDatas = null; @@ -1234,7 +1279,7 @@ public final class FragmentedMp4Extractor implements Extractor { if (uuid == null) { Log.w(TAG, "Skipped pssh atom (failed to extract uuid)"); } else { - schemeDatas.add(new SchemeData(uuid, MimeTypes.VIDEO_MP4, psshData)); + schemeDatas.add(new SchemeData(uuid, null, MimeTypes.VIDEO_MP4, psshData)); } } } @@ -1308,8 +1353,12 @@ public final class FragmentedMp4Extractor implements Extractor { } public void updateDrmInitData(DrmInitData drmInitData) { - output.format(track.format.copyWithDrmInitData(drmInitData)); + TrackEncryptionBox encryptionBox = + track.getSampleDescriptionEncryptionBox(fragment.header.sampleDescriptionIndex); + String schemeType = encryptionBox != null ? encryptionBox.schemeType : null; + output.format(track.format.copyWithDrmInitData(drmInitData.copyWithSchemeType(schemeType))); } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/Mp4Extractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/Mp4Extractor.java index 9593479c8..823f88a2e 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/Mp4Extractor.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/Mp4Extractor.java @@ -28,6 +28,7 @@ import org.telegram.messenger.exoplayer2.extractor.PositionHolder; import org.telegram.messenger.exoplayer2.extractor.SeekMap; import org.telegram.messenger.exoplayer2.extractor.TrackOutput; import org.telegram.messenger.exoplayer2.extractor.mp4.Atom.ContainerAtom; +import org.telegram.messenger.exoplayer2.extractor.mp4.FragmentedMp4Extractor.Flags; import org.telegram.messenger.exoplayer2.metadata.Metadata; import org.telegram.messenger.exoplayer2.util.Assertions; import org.telegram.messenger.exoplayer2.util.NalUnitUtil; @@ -57,6 +58,17 @@ public final class Mp4Extractor implements Extractor, SeekMap { }; + /** + * Flags controlling the behavior of the extractor. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {FLAG_WORKAROUND_IGNORE_EDIT_LISTS}) + public @interface Flags {} + /** + * Flag to ignore any edit lists in the stream. + */ + public static final int FLAG_WORKAROUND_IGNORE_EDIT_LISTS = 1; + /** * Parser states. */ @@ -76,6 +88,8 @@ public final class Mp4Extractor implements Extractor, SeekMap { */ private static final long RELOAD_MINIMUM_SEEK_DISTANCE = 256 * 1024; + private final @Flags int flags; + // Temporary arrays. private final ParsableByteArray nalStartCode; private final ParsableByteArray nalLength; @@ -98,7 +112,21 @@ public final class Mp4Extractor implements Extractor, SeekMap { private long durationUs; private boolean isQuickTime; + /** + * Creates a new extractor for unfragmented MP4 streams. + */ public Mp4Extractor() { + this(0); + } + + /** + * Creates a new extractor for unfragmented MP4 streams, using the specified flags to control the + * extractor's behavior. + * + * @param flags Flags that control the extractor's behavior. + */ + public Mp4Extractor(@Flags int flags) { + this.flags = flags; atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE); containerAtoms = new Stack<>(); nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); @@ -205,12 +233,26 @@ public final class Mp4Extractor implements Extractor, SeekMap { atomType = atomHeader.readInt(); } - if (atomSize == Atom.LONG_SIZE_PREFIX) { - // Read the extended atom size. + if (atomSize == Atom.DEFINES_LARGE_SIZE) { + // Read the large size. int headerBytesRemaining = Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE; input.readFully(atomHeader.data, Atom.HEADER_SIZE, headerBytesRemaining); atomHeaderBytesRead += headerBytesRemaining; atomSize = atomHeader.readUnsignedLongToLong(); + } else if (atomSize == Atom.EXTENDS_TO_END_SIZE) { + // The atom extends to the end of the file. Note that if the atom is within a container we can + // work out its size even if the input length is unknown. + long endPosition = input.getLength(); + if (endPosition == C.LENGTH_UNSET && !containerAtoms.isEmpty()) { + endPosition = containerAtoms.peek().endPosition; + } + if (endPosition != C.LENGTH_UNSET) { + atomSize = endPosition - input.getPosition() + atomHeaderBytesRead; + } + } + + if (atomSize < atomHeaderBytesRead) { + throw new ParserException("Atom size less than header length (unsupported)."); } if (shouldParseContainerAtom(atomType)) { @@ -331,7 +373,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { } Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd), - C.TIME_UNSET, null, isQuickTime); + C.TIME_UNSET, null, (flags & FLAG_WORKAROUND_IGNORE_EDIT_LISTS) != 0, isQuickTime); if (track == null) { continue; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/Sniffer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/Sniffer.java index 1fea1b524..c9f60b2b3 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/Sniffer.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/Sniffer.java @@ -104,11 +104,18 @@ import java.io.IOException; input.peekFully(buffer.data, 0, headerSize); long atomSize = buffer.readUnsignedInt(); int atomType = buffer.readInt(); - if (atomSize == Atom.LONG_SIZE_PREFIX) { + if (atomSize == Atom.DEFINES_LARGE_SIZE) { + // Read the large atom size. headerSize = Atom.LONG_HEADER_SIZE; input.peekFully(buffer.data, Atom.HEADER_SIZE, Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE); buffer.setLimit(Atom.LONG_HEADER_SIZE); atomSize = buffer.readUnsignedLongToLong(); + } else if (atomSize == Atom.EXTENDS_TO_END_SIZE) { + // The atom extends to the end of the file. + long endPosition = input.getLength(); + if (endPosition != C.LENGTH_UNSET) { + atomSize = endPosition - input.getPosition() + headerSize; + } } if (atomSize < headerSize) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/Track.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/Track.java index fcf9ab3c6..3dd856be8 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/Track.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/Track.java @@ -16,6 +16,7 @@ package org.telegram.messenger.exoplayer2.extractor.mp4; import android.support.annotation.IntDef; +import android.support.annotation.Nullable; import org.telegram.messenger.exoplayer2.C; import org.telegram.messenger.exoplayer2.Format; import java.lang.annotation.Retention; @@ -77,20 +78,15 @@ public final class Track { */ @Transformation public final int sampleTransformation; - /** - * Track encryption boxes for the different track sample descriptions. Entries may be null. - */ - public final TrackEncryptionBox[] sampleDescriptionEncryptionBoxes; - /** * Durations of edit list segments in the movie timescale. Null if there is no edit list. */ - public final long[] editListDurations; + @Nullable public final long[] editListDurations; /** * Media times for edit list segments in the track timescale. Null if there is no edit list. */ - public final long[] editListMediaTimes; + @Nullable public final long[] editListMediaTimes; /** * For H264 video tracks, the length in bytes of the NALUnitLength field in each sample. 0 for @@ -98,10 +94,12 @@ public final class Track { */ public final int nalUnitLengthFieldLength; + @Nullable private final TrackEncryptionBox[] sampleDescriptionEncryptionBoxes; + public Track(int id, int type, long timescale, long movieTimescale, long durationUs, Format format, @Transformation int sampleTransformation, - TrackEncryptionBox[] sampleDescriptionEncryptionBoxes, int nalUnitLengthFieldLength, - long[] editListDurations, long[] editListMediaTimes) { + @Nullable TrackEncryptionBox[] sampleDescriptionEncryptionBoxes, int nalUnitLengthFieldLength, + @Nullable long[] editListDurations, @Nullable long[] editListMediaTimes) { this.id = id; this.type = type; this.timescale = timescale; @@ -115,4 +113,16 @@ public final class Track { this.editListMediaTimes = editListMediaTimes; } + /** + * Returns the {@link TrackEncryptionBox} for the given sample description index. + * + * @param sampleDescriptionIndex The given sample description index + * @return The {@link TrackEncryptionBox} for the given sample description index. Maybe null if no + * such entry exists. + */ + public TrackEncryptionBox getSampleDescriptionEncryptionBox(int sampleDescriptionIndex) { + return sampleDescriptionEncryptionBoxes == null ? null + : sampleDescriptionEncryptionBoxes[sampleDescriptionIndex]; + } + } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/TrackEncryptionBox.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/TrackEncryptionBox.java index 92e801c9c..2c2fd9ec9 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/TrackEncryptionBox.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/TrackEncryptionBox.java @@ -15,37 +15,86 @@ */ package org.telegram.messenger.exoplayer2.extractor.mp4; +import android.support.annotation.Nullable; +import android.util.Log; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.util.Assertions; + /** * Encapsulates information parsed from a track encryption (tenc) box or sample group description * (sgpd) box in an MP4 stream. */ public final class TrackEncryptionBox { + private static final String TAG = "TrackEncryptionBox"; + /** * Indicates the encryption state of the samples in the sample group. */ public final boolean isEncrypted; + /** + * The protection scheme type, as defined by the 'schm' box, or null if unknown. + */ + @Nullable public final String schemeType; + + /** + * A {@link TrackOutput.CryptoData} instance containing the encryption information from this + * {@link TrackEncryptionBox}. + */ + public final TrackOutput.CryptoData cryptoData; + /** * The initialization vector size in bytes for the samples in the corresponding sample group. */ public final int initializationVectorSize; /** - * The key identifier for the samples in the corresponding sample group. + * If {@link #initializationVectorSize} is 0, holds the default initialization vector as defined + * in the track encryption box or sample group description box. Null otherwise. */ - public final byte[] keyId; + public final byte[] defaultInitializationVector; /** - * @param isEncrypted Indicates the encryption state of the samples in the sample group. - * @param initializationVectorSize The initialization vector size in bytes for the samples in the - * corresponding sample group. - * @param keyId The key identifier for the samples in the corresponding sample group. + * @param isEncrypted See {@link #isEncrypted}. + * @param schemeType See {@link #schemeType}. + * @param initializationVectorSize See {@link #initializationVectorSize}. + * @param keyId See {@link TrackOutput.CryptoData#encryptionKey}. + * @param defaultEncryptedBlocks See {@link TrackOutput.CryptoData#encryptedBlocks}. + * @param defaultClearBlocks See {@link TrackOutput.CryptoData#clearBlocks}. + * @param defaultInitializationVector See {@link #defaultInitializationVector}. */ - public TrackEncryptionBox(boolean isEncrypted, int initializationVectorSize, byte[] keyId) { + public TrackEncryptionBox(boolean isEncrypted, @Nullable String schemeType, + int initializationVectorSize, byte[] keyId, int defaultEncryptedBlocks, + int defaultClearBlocks, @Nullable byte[] defaultInitializationVector) { + Assertions.checkArgument(initializationVectorSize == 0 ^ defaultInitializationVector == null); this.isEncrypted = isEncrypted; + this.schemeType = schemeType; this.initializationVectorSize = initializationVectorSize; - this.keyId = keyId; + this.defaultInitializationVector = defaultInitializationVector; + cryptoData = new TrackOutput.CryptoData(schemeToCryptoMode(schemeType), keyId, + defaultEncryptedBlocks, defaultClearBlocks); + } + + @C.CryptoMode + private static int schemeToCryptoMode(@Nullable String schemeType) { + if (schemeType == null) { + // If unknown, assume cenc. + return C.CRYPTO_MODE_AES_CTR; + } + switch (schemeType) { + case C.CENC_TYPE_cenc: + case C.CENC_TYPE_cens: + return C.CRYPTO_MODE_AES_CTR; + case C.CENC_TYPE_cbc1: + case C.CENC_TYPE_cbcs: + return C.CRYPTO_MODE_AES_CBC; + default: + Log.w(TAG, "Unsupported protection scheme type '" + schemeType + "'. Assuming AES-CTR " + + "crypto mode."); + return C.CRYPTO_MODE_AES_CTR; + } } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/OggExtractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/OggExtractor.java index 3693f453e..b849b8c4d 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/OggExtractor.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/OggExtractor.java @@ -45,30 +45,14 @@ public class OggExtractor implements Extractor { private static final int MAX_VERIFICATION_BYTES = 8; + private ExtractorOutput output; private StreamReader streamReader; + private boolean streamReaderInitialized; @Override public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { try { - OggPageHeader header = new OggPageHeader(); - if (!header.populate(input, true) || (header.type & 0x02) != 0x02) { - return false; - } - - int length = Math.min(header.bodySize, MAX_VERIFICATION_BYTES); - ParsableByteArray scratch = new ParsableByteArray(length); - input.peekFully(scratch.data, 0, length); - - if (FlacReader.verifyBitstreamType(resetPosition(scratch))) { - streamReader = new FlacReader(); - } else if (VorbisReader.verifyBitstreamType(resetPosition(scratch))) { - streamReader = new VorbisReader(); - } else if (OpusReader.verifyBitstreamType(resetPosition(scratch))) { - streamReader = new OpusReader(); - } else { - return false; - } - return true; + return sniffInternal(input); } catch (ParserException e) { return false; } @@ -76,15 +60,14 @@ public class OggExtractor implements Extractor { @Override public void init(ExtractorOutput output) { - TrackOutput trackOutput = output.track(0, C.TRACK_TYPE_AUDIO); - output.endTracks(); - // TODO: fix the case if sniff() isn't called - streamReader.init(output, trackOutput); + this.output = output; } @Override public void seek(long position, long timeUs) { - streamReader.seek(position, timeUs); + if (streamReader != null) { + streamReader.seek(position, timeUs); + } } @Override @@ -95,12 +78,41 @@ public class OggExtractor implements Extractor { @Override public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException, InterruptedException { + if (streamReader == null) { + if (!sniffInternal(input)) { + throw new ParserException("Failed to determine bitstream type"); + } + input.resetPeekPosition(); + } + if (!streamReaderInitialized) { + TrackOutput trackOutput = output.track(0, C.TRACK_TYPE_AUDIO); + output.endTracks(); + streamReader.init(output, trackOutput); + streamReaderInitialized = true; + } return streamReader.read(input, seekPosition); } - //@VisibleForTesting - /* package */ StreamReader getStreamReader() { - return streamReader; + private boolean sniffInternal(ExtractorInput input) throws IOException, InterruptedException { + OggPageHeader header = new OggPageHeader(); + if (!header.populate(input, true) || (header.type & 0x02) != 0x02) { + return false; + } + + int length = Math.min(header.bodySize, MAX_VERIFICATION_BYTES); + ParsableByteArray scratch = new ParsableByteArray(length); + input.peekFully(scratch.data, 0, length); + + if (FlacReader.verifyBitstreamType(resetPosition(scratch))) { + streamReader = new FlacReader(); + } else if (VorbisReader.verifyBitstreamType(resetPosition(scratch))) { + streamReader = new VorbisReader(); + } else if (OpusReader.verifyBitstreamType(resetPosition(scratch))) { + streamReader = new OpusReader(); + } else { + return false; + } + return true; } private static ParsableByteArray resetPosition(ParsableByteArray scratch) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/OggPacket.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/OggPacket.java index 53424bc81..bcf8fa344 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/OggPacket.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/OggPacket.java @@ -20,6 +20,7 @@ import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; import org.telegram.messenger.exoplayer2.util.Assertions; import org.telegram.messenger.exoplayer2.util.ParsableByteArray; import java.io.IOException; +import java.util.Arrays; /** * OGG packet class. @@ -27,8 +28,8 @@ import java.io.IOException; /* package */ final class OggPacket { private final OggPageHeader pageHeader = new OggPageHeader(); - private final ParsableByteArray packetArray = - new ParsableByteArray(new byte[OggPageHeader.MAX_PAGE_PAYLOAD], 0); + private final ParsableByteArray packetArray = new ParsableByteArray( + new byte[OggPageHeader.MAX_PAGE_PAYLOAD], 0); private int currentSegmentIndex = C.INDEX_UNSET; private int segmentCount; @@ -85,6 +86,9 @@ import java.io.IOException; int size = calculatePacketSize(currentSegmentIndex); int segmentIndex = currentSegmentIndex + segmentCount; if (size > 0) { + if (packetArray.capacity() < packetArray.limit() + size) { + packetArray.data = Arrays.copyOf(packetArray.data, packetArray.limit() + size); + } input.readFully(packetArray.data, packetArray.limit(), size); packetArray.setLimit(packetArray.limit() + size); populated = pageHeader.laces[segmentIndex - 1] != 255; @@ -118,6 +122,17 @@ import java.io.IOException; return packetArray; } + /** + * Trims the packet data array. + */ + public void trimPayload() { + if (packetArray.data.length == OggPageHeader.MAX_PAGE_PAYLOAD) { + return; + } + packetArray.data = Arrays.copyOf(packetArray.data, Math.max(OggPageHeader.MAX_PAGE_PAYLOAD, + packetArray.limit())); + } + /** * Calculates the size of the packet starting from {@code startSegmentIndex}. * diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/StreamReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/StreamReader.java index c541a94f3..2bfc38b57 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/StreamReader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/StreamReader.java @@ -41,7 +41,8 @@ import java.io.IOException; OggSeeker oggSeeker; } - private OggPacket oggPacket; + private final OggPacket oggPacket; + private TrackOutput trackOutput; private ExtractorOutput extractorOutput; private OggSeeker oggSeeker; @@ -55,11 +56,13 @@ import java.io.IOException; private boolean seekMapSet; private boolean formatSet; + public StreamReader() { + oggPacket = new OggPacket(); + } + void init(ExtractorOutput output, TrackOutput trackOutput) { this.extractorOutput = output; this.trackOutput = trackOutput; - this.oggPacket = new OggPacket(); - reset(true); } @@ -103,15 +106,12 @@ import java.io.IOException; switch (state) { case STATE_READ_HEADERS: return readHeaders(input); - case STATE_SKIP_HEADERS: input.skipFully((int) payloadStartPosition); state = STATE_READ_PAYLOAD; return Extractor.RESULT_CONTINUE; - case STATE_READ_PAYLOAD: return readPayload(input, seekPosition); - default: // Never happens. throw new IllegalStateException(); @@ -152,6 +152,8 @@ import java.io.IOException; setupData = null; state = STATE_READ_PAYLOAD; + // First payload packet. Trim the payload array of the ogg packet after headers have been read. + oggPacket.trimPayload(); return Extractor.RESULT_CONTINUE; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/VorbisBitArray.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/VorbisBitArray.java index 30a1bfea1..016ade8f5 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/VorbisBitArray.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/VorbisBitArray.java @@ -25,8 +25,9 @@ import org.telegram.messenger.exoplayer2.util.Assertions; */ /* package */ final class VorbisBitArray { - public final byte[] data; - private final int limit; + private final byte[] data; + private final int byteLimit; + private int byteOffset; private int bitOffset; @@ -36,18 +37,8 @@ import org.telegram.messenger.exoplayer2.util.Assertions; * @param data the array to wrap. */ public VorbisBitArray(byte[] data) { - this(data, data.length); - } - - /** - * Creates a new instance that wraps an existing array. - * - * @param data the array to wrap. - * @param limit the limit in bytes. - */ - public VorbisBitArray(byte[] data, int limit) { this.data = data; - this.limit = limit * 8; + byteLimit = data.length; } /** @@ -64,7 +55,9 @@ import org.telegram.messenger.exoplayer2.util.Assertions; * @return {@code true} if the bit is set, {@code false} otherwise. */ public boolean readBit() { - return readBits(1) == 1; + boolean returnValue = (((data[byteOffset] & 0xFF) >> bitOffset) & 0x01) == 1; + skipBits(1); + return returnValue; } /** @@ -74,53 +67,32 @@ import org.telegram.messenger.exoplayer2.util.Assertions; * @return An integer whose bottom {@code numBits} bits hold the read data. */ public int readBits(int numBits) { - Assertions.checkState(getPosition() + numBits <= limit); - if (numBits == 0) { - return 0; + int tempByteOffset = byteOffset; + int bitsRead = Math.min(numBits, 8 - bitOffset); + int returnValue = ((data[tempByteOffset++] & 0xFF) >> bitOffset) & (0xFF >> (8 - bitsRead)); + while (bitsRead < numBits) { + returnValue |= (data[tempByteOffset++] & 0xFF) << bitsRead; + bitsRead += 8; } - int result = 0; - int bitCount = 0; - if (bitOffset != 0) { - bitCount = Math.min(numBits, 8 - bitOffset); - int mask = 0xFF >>> (8 - bitCount); - result = (data[byteOffset] >>> bitOffset) & mask; - bitOffset += bitCount; - if (bitOffset == 8) { - byteOffset++; - bitOffset = 0; - } - } - - if (numBits - bitCount > 7) { - int numBytes = (numBits - bitCount) / 8; - for (int i = 0; i < numBytes; i++) { - result |= (data[byteOffset++] & 0xFFL) << bitCount; - bitCount += 8; - } - } - - if (numBits > bitCount) { - int bitsOnNextByte = numBits - bitCount; - int mask = 0xFF >>> (8 - bitsOnNextByte); - result |= (data[byteOffset] & mask) << bitCount; - bitOffset += bitsOnNextByte; - } - return result; + returnValue &= 0xFFFFFFFF >>> (32 - numBits); + skipBits(numBits); + return returnValue; } /** * Skips {@code numberOfBits} bits. * - * @param numberOfBits The number of bits to skip. + * @param numBits The number of bits to skip. */ - public void skipBits(int numberOfBits) { - Assertions.checkState(getPosition() + numberOfBits <= limit); - byteOffset += numberOfBits / 8; - bitOffset += numberOfBits % 8; + public void skipBits(int numBits) { + int numBytes = numBits / 8; + byteOffset += numBytes; + bitOffset += numBits - (numBytes * 8); if (bitOffset > 7) { byteOffset++; bitOffset -= 8; } + assertValidOffset(); } /** @@ -136,23 +108,22 @@ import org.telegram.messenger.exoplayer2.util.Assertions; * @param position The new reading position in bits. */ public void setPosition(int position) { - Assertions.checkArgument(position < limit && position >= 0); byteOffset = position / 8; bitOffset = position - (byteOffset * 8); + assertValidOffset(); } /** * Returns the number of remaining bits. */ public int bitsLeft() { - return limit - getPosition(); + return (byteLimit - byteOffset) * 8 - bitOffset; } - /** - * Returns the limit in bits. - **/ - public int limit() { - return limit; + private void assertValidOffset() { + // It is fine for position to be at the end of the array, but no further. + Assertions.checkState(byteOffset >= 0 + && (byteOffset < byteLimit || (byteOffset == byteLimit && bitOffset == 0))); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/VorbisReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/VorbisReader.java index 8ee884643..3590843de 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/VorbisReader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/VorbisReader.java @@ -101,7 +101,7 @@ import java.util.ArrayList; codecInitialisationData.add(vorbisSetup.setupHeaderData); setupData.format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_VORBIS, null, - this.vorbisSetup.idHeader.bitrateNominal, OggPageHeader.MAX_PAGE_PAYLOAD, + this.vorbisSetup.idHeader.bitrateNominal, Format.NO_VALUE, this.vorbisSetup.idHeader.channels, (int) this.vorbisSetup.idHeader.sampleRate, codecInitialisationData, null, 0, null); return true; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/Ac3Extractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/Ac3Extractor.java index 5bb00984d..17aa644fe 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/Ac3Extractor.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/Ac3Extractor.java @@ -56,9 +56,9 @@ public final class Ac3Extractor implements Extractor { private static final int ID3_TAG = Util.getIntegerCodeForString("ID3"); private final long firstSampleTimestampUs; + private final Ac3Reader reader; private final ParsableByteArray sampleData; - private Ac3Reader reader; private boolean startedPacket; public Ac3Extractor() { @@ -67,6 +67,7 @@ public final class Ac3Extractor implements Extractor { public Ac3Extractor(long firstSampleTimestampUs) { this.firstSampleTimestampUs = firstSampleTimestampUs; + reader = new Ac3Reader(); sampleData = new ParsableByteArray(MAX_SYNC_FRAME_SIZE); } @@ -117,7 +118,6 @@ public final class Ac3Extractor implements Extractor { @Override public void init(ExtractorOutput output) { - reader = new Ac3Reader(); // TODO: Add support for embedded ID3. reader.createTracks(output, new TrackIdGenerator(0, 1)); output.endTracks(); output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/AdtsExtractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/AdtsExtractor.java index d74248b8d..b4cf66927 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/AdtsExtractor.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/AdtsExtractor.java @@ -55,10 +55,9 @@ public final class AdtsExtractor implements Extractor { private static final int MAX_SNIFF_BYTES = 8 * 1024; private final long firstSampleTimestampUs; + private final AdtsReader reader; private final ParsableByteArray packetBuffer; - // Accessed only by the loading thread. - private AdtsReader reader; private boolean startedPacket; public AdtsExtractor() { @@ -67,6 +66,7 @@ public final class AdtsExtractor implements Extractor { public AdtsExtractor(long firstSampleTimestampUs) { this.firstSampleTimestampUs = firstSampleTimestampUs; + reader = new AdtsReader(true); packetBuffer = new ParsableByteArray(MAX_PACKET_SIZE); } @@ -127,7 +127,6 @@ public final class AdtsExtractor implements Extractor { @Override public void init(ExtractorOutput output) { - reader = new AdtsReader(true); reader.createTracks(output, new TrackIdGenerator(0, 1)); output.endTracks(); output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java index 00a30609c..57f75efd6 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java @@ -78,7 +78,7 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact this.flags = flags; if (!isSet(FLAG_OVERRIDE_CAPTION_DESCRIPTORS) && closedCaptionFormats.isEmpty()) { closedCaptionFormats = Collections.singletonList(Format.createTextSampleFormat(null, - MimeTypes.APPLICATION_CEA608, null, Format.NO_VALUE, 0, null, null)); + MimeTypes.APPLICATION_CEA608, 0, null)); } this.closedCaptionFormats = closedCaptionFormats; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/H262Reader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/H262Reader.java index 749783f88..4b8854c6b 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/H262Reader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/H262Reader.java @@ -51,17 +51,17 @@ public final class H262Reader implements ElementaryStreamReader { // State that should be reset on seek. private final boolean[] prefixFlags; private final CsdBuffer csdBuffer; - private boolean foundFirstFrameInGroup; private long totalBytesWritten; + private boolean startedFirstSample; // Per packet state that gets reset at the start of each packet. private long pesTimeUs; - private boolean pesPtsUsAvailable; - // Per sample state that gets reset at the start of each frame. - private boolean isKeyframe; - private long framePosition; - private long frameTimeUs; + // Per sample state that gets reset at the start of each sample. + private long samplePosition; + private long sampleTimeUs; + private boolean sampleIsKeyframe; + private boolean sampleHasPicture; public H262Reader() { prefixFlags = new boolean[4]; @@ -72,9 +72,8 @@ public final class H262Reader implements ElementaryStreamReader { public void seek() { NalUnitUtil.clearPrefixFlags(prefixFlags); csdBuffer.reset(); - pesPtsUsAvailable = false; - foundFirstFrameInGroup = false; totalBytesWritten = 0; + startedFirstSample = false; } @Override @@ -86,10 +85,7 @@ public final class H262Reader implements ElementaryStreamReader { @Override public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { - pesPtsUsAvailable = pesTimeUs != C.TIME_UNSET; - if (pesPtsUsAvailable) { - this.pesTimeUs = pesTimeUs; - } + this.pesTimeUs = pesTimeUs; } @Override @@ -102,9 +98,8 @@ public final class H262Reader implements ElementaryStreamReader { totalBytesWritten += data.bytesLeft(); output.sampleData(data, data.bytesLeft()); - int searchOffset = offset; while (true) { - int startCodeOffset = NalUnitUtil.findNalUnit(dataArray, searchOffset, limit, prefixFlags); + int startCodeOffset = NalUnitUtil.findNalUnit(dataArray, offset, limit, prefixFlags); if (startCodeOffset == limit) { // We've scanned to the end of the data without finding another start code. @@ -125,7 +120,7 @@ public final class H262Reader implements ElementaryStreamReader { csdBuffer.onData(dataArray, offset, startCodeOffset); } // This is the number of bytes belonging to the next start code that have already been - // passed to csdDataTargetBuffer. + // passed to csdBuffer. int bytesAlreadyPassed = lengthToStartCode < 0 ? -lengthToStartCode : 0; if (csdBuffer.onStartCode(startCodeValue, bytesAlreadyPassed)) { // The csd data is complete, so we can decode and output the media format. @@ -136,27 +131,29 @@ public final class H262Reader implements ElementaryStreamReader { } } - if (hasOutputFormat && (startCodeValue == START_GROUP || startCodeValue == START_PICTURE)) { + if (startCodeValue == START_PICTURE || startCodeValue == START_SEQUENCE_HEADER) { int bytesWrittenPastStartCode = limit - startCodeOffset; - if (foundFirstFrameInGroup) { - @C.BufferFlags int flags = isKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0; - int size = (int) (totalBytesWritten - framePosition) - bytesWrittenPastStartCode; - output.sampleMetadata(frameTimeUs, flags, size, bytesWrittenPastStartCode, null); - isKeyframe = false; + if (startedFirstSample && sampleHasPicture && hasOutputFormat) { + // Output the sample. + @C.BufferFlags int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0; + int size = (int) (totalBytesWritten - samplePosition) - bytesWrittenPastStartCode; + output.sampleMetadata(sampleTimeUs, flags, size, bytesWrittenPastStartCode, null); } - if (startCodeValue == START_GROUP) { - foundFirstFrameInGroup = false; - isKeyframe = true; - } else /* startCodeValue == START_PICTURE */ { - frameTimeUs = pesPtsUsAvailable ? pesTimeUs : (frameTimeUs + frameDurationUs); - framePosition = totalBytesWritten - bytesWrittenPastStartCode; - pesPtsUsAvailable = false; - foundFirstFrameInGroup = true; + if (!startedFirstSample || sampleHasPicture) { + // Start the next sample. + samplePosition = totalBytesWritten - bytesWrittenPastStartCode; + sampleTimeUs = pesTimeUs != C.TIME_UNSET ? pesTimeUs + : (startedFirstSample ? (sampleTimeUs + frameDurationUs) : 0); + sampleIsKeyframe = false; + pesTimeUs = C.TIME_UNSET; + startedFirstSample = true; } + sampleHasPicture = startCodeValue == START_PICTURE; + } else if (startCodeValue == START_GROUP) { + sampleIsKeyframe = true; } - offset = startCodeOffset; - searchOffset = offset + 3; + offset = startCodeOffset + 3; } } @@ -221,6 +218,8 @@ public final class H262Reader implements ElementaryStreamReader { private static final class CsdBuffer { + private static final byte[] START_CODE = new byte[] {0, 0, 1}; + private boolean isFilling; public int length; @@ -244,24 +243,25 @@ public final class H262Reader implements ElementaryStreamReader { * Called when a start code is encountered in the stream. * * @param startCodeValue The start code value. - * @param bytesAlreadyPassed The number of bytes of the start code that have already been - * passed to {@link #onData(byte[], int, int)}, or 0. + * @param bytesAlreadyPassed The number of bytes of the start code that have been passed to + * {@link #onData(byte[], int, int)}, or 0. * @return Whether the csd data is now complete. If true is returned, neither - * this method or {@link #onData(byte[], int, int)} should be called again without an + * this method nor {@link #onData(byte[], int, int)} should be called again without an * interleaving call to {@link #reset()}. */ public boolean onStartCode(int startCodeValue, int bytesAlreadyPassed) { if (isFilling) { + length -= bytesAlreadyPassed; if (sequenceExtensionPosition == 0 && startCodeValue == START_EXTENSION) { sequenceExtensionPosition = length; } else { - length -= bytesAlreadyPassed; isFilling = false; return true; } } else if (startCodeValue == START_SEQUENCE_HEADER) { isFilling = true; } + onData(START_CODE, 0, START_CODE.length); return false; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/H264Reader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/H264Reader.java index 6797446fc..525b9366d 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/H264Reader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/H264Reader.java @@ -316,7 +316,7 @@ public final class H264Reader implements ElementaryStreamReader { if (!bitArray.canReadBits(8)) { return; } - bitArray.skipBits(1); // forbidden_zero_bit + bitArray.skipBit(); // forbidden_zero_bit int nalRefIdc = bitArray.readBits(2); bitArray.skipBits(5); // nal_unit_type diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/H265Reader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/H265Reader.java index d921fd39d..d1ae0fbcb 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/H265Reader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/H265Reader.java @@ -225,7 +225,7 @@ public final class H265Reader implements ElementaryStreamReader { ParsableNalUnitBitArray bitArray = new ParsableNalUnitBitArray(sps.nalData, 0, sps.nalLength); bitArray.skipBits(40 + 4); // NAL header, sps_video_parameter_set_id int maxSubLayersMinus1 = bitArray.readBits(3); - bitArray.skipBits(1); // sps_temporal_id_nesting_flag + bitArray.skipBit(); // sps_temporal_id_nesting_flag // profile_tier_level(1, sps_max_sub_layers_minus1) bitArray.skipBits(88); // if (profilePresentFlag) {...} @@ -247,7 +247,7 @@ public final class H265Reader implements ElementaryStreamReader { bitArray.readUnsignedExpGolombCodedInt(); // sps_seq_parameter_set_id int chromaFormatIdc = bitArray.readUnsignedExpGolombCodedInt(); if (chromaFormatIdc == 3) { - bitArray.skipBits(1); // separate_colour_plane_flag + bitArray.skipBit(); // separate_colour_plane_flag } int picWidthInLumaSamples = bitArray.readUnsignedExpGolombCodedInt(); int picHeightInLumaSamples = bitArray.readUnsignedExpGolombCodedInt(); @@ -288,7 +288,7 @@ public final class H265Reader implements ElementaryStreamReader { bitArray.skipBits(8); bitArray.readUnsignedExpGolombCodedInt(); // log2_min_pcm_luma_coding_block_size_minus3 bitArray.readUnsignedExpGolombCodedInt(); // log2_diff_max_min_pcm_luma_coding_block_size - bitArray.skipBits(1); // pcm_loop_filter_disabled_flag + bitArray.skipBit(); // pcm_loop_filter_disabled_flag } // Skips all short term reference picture sets. skipShortTermRefPicSets(bitArray); @@ -365,11 +365,11 @@ public final class H265Reader implements ElementaryStreamReader { interRefPicSetPredictionFlag = bitArray.readBit(); } if (interRefPicSetPredictionFlag) { - bitArray.skipBits(1); // delta_rps_sign + bitArray.skipBit(); // delta_rps_sign bitArray.readUnsignedExpGolombCodedInt(); // abs_delta_rps_minus1 for (int j = 0; j <= previousNumDeltaPocs; j++) { if (bitArray.readBit()) { // used_by_curr_pic_flag[j] - bitArray.skipBits(1); // use_delta_flag[j] + bitArray.skipBit(); // use_delta_flag[j] } } } else { @@ -378,11 +378,11 @@ public final class H265Reader implements ElementaryStreamReader { previousNumDeltaPocs = numNegativePics + numPositivePics; for (int i = 0; i < numNegativePics; i++) { bitArray.readUnsignedExpGolombCodedInt(); // delta_poc_s0_minus1[i] - bitArray.skipBits(1); // used_by_curr_pic_s0_flag[i] + bitArray.skipBit(); // used_by_curr_pic_s0_flag[i] } for (int i = 0; i < numPositivePics; i++) { bitArray.readUnsignedExpGolombCodedInt(); // delta_poc_s1_minus1[i] - bitArray.skipBits(1); // used_by_curr_pic_s1_flag[i] + bitArray.skipBit(); // used_by_curr_pic_s1_flag[i] } } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/SeiReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/SeiReader.java index d8fe37255..9e884a14e 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/SeiReader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/SeiReader.java @@ -51,9 +51,10 @@ import java.util.List; Assertions.checkArgument(MimeTypes.APPLICATION_CEA608.equals(channelMimeType) || MimeTypes.APPLICATION_CEA708.equals(channelMimeType), "Invalid closed caption mime type provided: " + channelMimeType); - output.format(Format.createTextSampleFormat(idGenerator.getFormatId(), channelMimeType, null, - Format.NO_VALUE, channelFormat.selectionFlags, channelFormat.language, - channelFormat.accessibilityChannel, null)); + String formatId = channelFormat.id != null ? channelFormat.id : idGenerator.getFormatId(); + output.format(Format.createTextSampleFormat(formatId, channelMimeType, null, Format.NO_VALUE, + channelFormat.selectionFlags, channelFormat.language, channelFormat.accessibilityChannel, + null)); outputs[i] = output; } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/TsExtractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/TsExtractor.java index 211036275..19c7978a7 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/TsExtractor.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/TsExtractor.java @@ -65,13 +65,13 @@ public final class TsExtractor implements Extractor { * Modes for the extractor. */ @Retention(RetentionPolicy.SOURCE) - @IntDef({MODE_NORMAL, MODE_SINGLE_PMT, MODE_HLS}) + @IntDef({MODE_MULTI_PMT, MODE_SINGLE_PMT, MODE_HLS}) public @interface Mode {} /** * Behave as defined in ISO/IEC 13818-1. */ - public static final int MODE_NORMAL = 0; + public static final int MODE_MULTI_PMT = 0; /** * Assume only one PMT will be contained in the stream, even if more are declared by the PAT. */ @@ -105,13 +105,12 @@ public final class TsExtractor implements Extractor { private static final long E_AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("EAC3"); private static final long HEVC_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("HEVC"); - private static final int BUFFER_PACKET_COUNT = 5; // Should be at least 2 - private static final int BUFFER_SIZE = TS_PACKET_SIZE * BUFFER_PACKET_COUNT; + private static final int BUFFER_SIZE = TS_PACKET_SIZE * 50; + private static final int SNIFF_TS_PACKET_COUNT = 5; @Mode private final int mode; private final List timestampAdjusters; private final ParsableByteArray tsPacketBuffer; - private final ParsableBitArray tsScratch; private final SparseIntArray continuityCounters; private final TsPayloadReader.Factory payloadReaderFactory; private final SparseArray tsPayloadReaders; // Indexed by pid @@ -132,12 +131,23 @@ public final class TsExtractor implements Extractor { * {@code FLAG_*} values that control the behavior of the payload readers. */ public TsExtractor(@Flags int defaultTsPayloadReaderFlags) { - this(MODE_NORMAL, new TimestampAdjuster(0), - new DefaultTsPayloadReaderFactory(defaultTsPayloadReaderFlags)); + this(MODE_SINGLE_PMT, defaultTsPayloadReaderFlags); } /** - * @param mode Mode for the extractor. One of {@link #MODE_NORMAL}, {@link #MODE_SINGLE_PMT} + * @param mode Mode for the extractor. One of {@link #MODE_MULTI_PMT}, {@link #MODE_SINGLE_PMT} + * and {@link #MODE_HLS}. + * @param defaultTsPayloadReaderFlags A combination of {@link DefaultTsPayloadReaderFactory} + * {@code FLAG_*} values that control the behavior of the payload readers. + */ + public TsExtractor(@Mode int mode, @Flags int defaultTsPayloadReaderFlags) { + this(mode, new TimestampAdjuster(0), + new DefaultTsPayloadReaderFactory(defaultTsPayloadReaderFlags)); + } + + + /** + * @param mode Mode for the extractor. One of {@link #MODE_MULTI_PMT}, {@link #MODE_SINGLE_PMT} * and {@link #MODE_HLS}. * @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps. * @param payloadReaderFactory Factory for injecting a custom set of payload readers. @@ -153,7 +163,6 @@ public final class TsExtractor implements Extractor { timestampAdjusters.add(timestampAdjuster); } tsPacketBuffer = new ParsableByteArray(BUFFER_SIZE); - tsScratch = new ParsableBitArray(new byte[3]); trackIds = new SparseBooleanArray(); tsPayloadReaders = new SparseArray<>(); continuityCounters = new SparseIntArray(); @@ -165,10 +174,10 @@ public final class TsExtractor implements Extractor { @Override public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { byte[] buffer = tsPacketBuffer.data; - input.peekFully(buffer, 0, BUFFER_SIZE); + input.peekFully(buffer, 0, TS_PACKET_SIZE * SNIFF_TS_PACKET_COUNT); for (int j = 0; j < TS_PACKET_SIZE; j++) { for (int i = 0; true; i++) { - if (i == BUFFER_PACKET_COUNT) { + if (i == SNIFF_TS_PACKET_COUNT) { input.skipFully(j); return true; } @@ -207,7 +216,8 @@ public final class TsExtractor implements Extractor { public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException, InterruptedException { byte[] data = tsPacketBuffer.data; - // Shift bytes to the start of the buffer if there isn't enough space left at the end + + // Shift bytes to the start of the buffer if there isn't enough space left at the end. if (BUFFER_SIZE - tsPacketBuffer.getPosition() < TS_PACKET_SIZE) { int bytesLeft = tsPacketBuffer.bytesLeft(); if (bytesLeft > 0) { @@ -215,7 +225,8 @@ public final class TsExtractor implements Extractor { } tsPacketBuffer.reset(data, bytesLeft); } - // Read more bytes until there is at least one packet size + + // Read more bytes until we have at least one packet. while (tsPacketBuffer.bytesLeft() < TS_PACKET_SIZE) { int limit = tsPacketBuffer.limit(); int read = input.read(data, limit, BUFFER_SIZE - limit); @@ -225,8 +236,7 @@ public final class TsExtractor implements Extractor { tsPacketBuffer.setLimit(limit + read); } - // Note: see ISO/IEC 13818-1, section 2.4.3.2 for detailed information on the format of - // the header. + // Note: See ISO/IEC 13818-1, section 2.4.3.2 for details of the header format. final int limit = tsPacketBuffer.limit(); int position = tsPacketBuffer.getPosition(); while (position < limit && data[position] != TS_SYNC_BYTE) { @@ -239,24 +249,23 @@ public final class TsExtractor implements Extractor { return RESULT_CONTINUE; } - tsPacketBuffer.skipBytes(1); - tsPacketBuffer.readBytes(tsScratch, 3); - if (tsScratch.readBit()) { // transport_error_indicator + int tsPacketHeader = tsPacketBuffer.readInt(); + if ((tsPacketHeader & 0x800000) != 0) { // transport_error_indicator // There are uncorrectable errors in this packet. tsPacketBuffer.setPosition(endOfPacket); return RESULT_CONTINUE; } - boolean payloadUnitStartIndicator = tsScratch.readBit(); - tsScratch.skipBits(1); // transport_priority - int pid = tsScratch.readBits(13); - tsScratch.skipBits(2); // transport_scrambling_control - boolean adaptationFieldExists = tsScratch.readBit(); - boolean payloadExists = tsScratch.readBit(); + boolean payloadUnitStartIndicator = (tsPacketHeader & 0x400000) != 0; + // Ignoring transport_priority (tsPacketHeader & 0x200000) + int pid = (tsPacketHeader & 0x1FFF00) >> 8; + // Ignoring transport_scrambling_control (tsPacketHeader & 0xC0) + boolean adaptationFieldExists = (tsPacketHeader & 0x20) != 0; + boolean payloadExists = (tsPacketHeader & 0x10) != 0; // Discontinuity check. boolean discontinuityFound = false; - int continuityCounter = tsScratch.readBits(4); if (mode != MODE_HLS) { + int continuityCounter = tsPacketHeader & 0xF; int previousCounter = continuityCounters.get(pid, continuityCounter - 1); continuityCounters.put(pid, continuityCounter); if (previousCounter == continuityCounter) { @@ -265,7 +274,7 @@ public final class TsExtractor implements Extractor { tsPacketBuffer.setPosition(endOfPacket); return RESULT_CONTINUE; } - } else if (continuityCounter != (previousCounter + 1) % 16) { + } else if (continuityCounter != ((previousCounter + 1) & 0xF)) { discontinuityFound = true; } } @@ -285,7 +294,6 @@ public final class TsExtractor implements Extractor { } tsPacketBuffer.setLimit(endOfPacket); payloadReader.consume(tsPacketBuffer, payloadUnitStartIndicator); - Assertions.checkState(tsPacketBuffer.getPosition() <= endOfPacket); tsPacketBuffer.setLimit(limit); } } @@ -371,10 +379,14 @@ public final class TsExtractor implements Extractor { private static final int TS_PMT_DESC_DVBSUBS = 0x59; private final ParsableBitArray pmtScratch; + private final SparseArray trackIdToReaderScratch; + private final SparseIntArray trackIdToPidScratch; private final int pid; public PmtReader(int pid) { pmtScratch = new ParsableBitArray(new byte[5]); + trackIdToReaderScratch = new SparseArray<>(); + trackIdToPidScratch = new SparseIntArray(); this.pid = pid; } @@ -425,6 +437,8 @@ public final class TsExtractor implements Extractor { new TrackIdGenerator(programNumber, TS_STREAM_TYPE_ID3, MAX_PID_PLUS_ONE)); } + trackIdToReaderScratch.clear(); + trackIdToPidScratch.clear(); int remainingEntriesLength = sectionData.bytesLeft(); while (remainingEntriesLength > 0) { sectionData.readBytes(pmtScratch, 5); @@ -443,23 +457,30 @@ public final class TsExtractor implements Extractor { if (trackIds.get(trackId)) { continue; } - trackIds.put(trackId, true); - TsPayloadReader reader; - if (mode == MODE_HLS && streamType == TS_STREAM_TYPE_ID3) { - reader = id3Reader; - } else { - reader = payloadReaderFactory.createPayloadReader(streamType, esInfo); - if (reader != null) { + TsPayloadReader reader = mode == MODE_HLS && streamType == TS_STREAM_TYPE_ID3 ? id3Reader + : payloadReaderFactory.createPayloadReader(streamType, esInfo); + if (mode != MODE_HLS + || elementaryPid < trackIdToPidScratch.get(trackId, MAX_PID_PLUS_ONE)) { + trackIdToPidScratch.put(trackId, elementaryPid); + trackIdToReaderScratch.put(trackId, reader); + } + } + + int trackIdCount = trackIdToPidScratch.size(); + for (int i = 0; i < trackIdCount; i++) { + int trackId = trackIdToPidScratch.keyAt(i); + trackIds.put(trackId, true); + TsPayloadReader reader = trackIdToReaderScratch.valueAt(i); + if (reader != null) { + if (reader != id3Reader) { reader.init(timestampAdjuster, output, new TrackIdGenerator(programNumber, trackId, MAX_PID_PLUS_ONE)); } - } - - if (reader != null) { - tsPayloadReaders.put(elementaryPid, reader); + tsPayloadReaders.put(trackIdToPidScratch.valueAt(i), reader); } } + if (mode == MODE_HLS) { if (!tracksEnded) { output.endTracks(); @@ -534,5 +555,4 @@ public final class TsExtractor implements Extractor { } - } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/mediacodec/MediaCodecInfo.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/mediacodec/MediaCodecInfo.java index 95aff2f01..4c93916b7 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/mediacodec/MediaCodecInfo.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/mediacodec/MediaCodecInfo.java @@ -61,6 +61,14 @@ public final class MediaCodecInfo { */ public final boolean tunneling; + /** + * Whether the decoder is secure. + * + * @see CodecCapabilities#isFeatureSupported(String) + * @see CodecCapabilities#FEATURE_SecurePlayback + */ + public final boolean secure; + private final String mimeType; private final CodecCapabilities capabilities; @@ -71,7 +79,7 @@ public final class MediaCodecInfo { * @return The created instance. */ public static MediaCodecInfo newPassthroughInstance(String name) { - return new MediaCodecInfo(name, null, null); + return new MediaCodecInfo(name, null, null, false, false); } /** @@ -84,19 +92,32 @@ public final class MediaCodecInfo { */ public static MediaCodecInfo newInstance(String name, String mimeType, CodecCapabilities capabilities) { - return new MediaCodecInfo(name, mimeType, capabilities); + return new MediaCodecInfo(name, mimeType, capabilities, false, false); } /** - * @param name The name of the decoder. - * @param capabilities The capabilities of the decoder. + * Creates an instance. + * + * @param name The name of the {@link MediaCodec}. + * @param mimeType A mime type supported by the {@link MediaCodec}. + * @param capabilities The capabilities of the {@link MediaCodec} for the specified mime type. + * @param forceDisableAdaptive Whether {@link #adaptive} should be forced to {@code false}. + * @param forceSecure Whether {@link #secure} should be forced to {@code true}. + * @return The created instance. */ - private MediaCodecInfo(String name, String mimeType, CodecCapabilities capabilities) { + public static MediaCodecInfo newInstance(String name, String mimeType, + CodecCapabilities capabilities, boolean forceDisableAdaptive, boolean forceSecure) { + return new MediaCodecInfo(name, mimeType, capabilities, forceDisableAdaptive, forceSecure); + } + + private MediaCodecInfo(String name, String mimeType, CodecCapabilities capabilities, + boolean forceDisableAdaptive, boolean forceSecure) { this.name = Assertions.checkNotNull(name); this.mimeType = mimeType; this.capabilities = capabilities; - adaptive = capabilities != null && isAdaptive(capabilities); + adaptive = !forceDisableAdaptive && capabilities != null && isAdaptive(capabilities); tunneling = capabilities != null && isTunneling(capabilities); + secure = forceSecure || (capabilities != null && isSecure(capabilities)); } /** @@ -165,12 +186,12 @@ public final class MediaCodecInfo { logNoSupport("sizeAndRate.vCaps"); return false; } - if (!areSizeAndRateSupported(videoCapabilities, width, height, frameRate)) { + if (!areSizeAndRateSupportedV21(videoCapabilities, width, height, frameRate)) { // Capabilities are known to be inaccurately reported for vertical resolutions on some devices // (b/31387661). If the video is vertical and the capabilities indicate support if the width // and height are swapped, we assume that the vertical resolution is also supported. if (width >= height - || !areSizeAndRateSupported(videoCapabilities, height, width, frameRate)) { + || !areSizeAndRateSupportedV21(videoCapabilities, height, width, frameRate)) { logNoSupport("sizeAndRate.support, " + width + "x" + height + "x" + frameRate); return false; } @@ -253,7 +274,9 @@ public final class MediaCodecInfo { logNoSupport("channelCount.aCaps"); return false; } - if (audioCapabilities.getMaxInputChannelCount() < channelCount) { + int maxInputChannelCount = adjustMaxInputChannelCount(name, mimeType, + audioCapabilities.getMaxInputChannelCount()); + if (maxInputChannelCount < channelCount) { logNoSupport("channelCount.support, " + channelCount); return false; } @@ -270,6 +293,40 @@ public final class MediaCodecInfo { + Util.DEVICE_DEBUG_INFO + "]"); } + private static int adjustMaxInputChannelCount(String name, String mimeType, int maxChannelCount) { + if (maxChannelCount > 1 || (Util.SDK_INT >= 26 && maxChannelCount > 0)) { + // The maximum channel count looks like it's been set correctly. + return maxChannelCount; + } + if (MimeTypes.AUDIO_MPEG.equals(mimeType) + || MimeTypes.AUDIO_AMR_NB.equals(mimeType) + || MimeTypes.AUDIO_AMR_WB.equals(mimeType) + || MimeTypes.AUDIO_AAC.equals(mimeType) + || MimeTypes.AUDIO_VORBIS.equals(mimeType) + || MimeTypes.AUDIO_OPUS.equals(mimeType) + || MimeTypes.AUDIO_RAW.equals(mimeType) + || MimeTypes.AUDIO_FLAC.equals(mimeType) + || MimeTypes.AUDIO_ALAW.equals(mimeType) + || MimeTypes.AUDIO_MLAW.equals(mimeType) + || MimeTypes.AUDIO_MSGSM.equals(mimeType)) { + // Platform code should have set a default. + return maxChannelCount; + } + // The maximum channel count looks incorrect. Adjust it to an assumed default. + int assumedMaxChannelCount; + if (MimeTypes.AUDIO_AC3.equals(mimeType)) { + assumedMaxChannelCount = 6; + } else if (MimeTypes.AUDIO_E_AC3.equals(mimeType)) { + assumedMaxChannelCount = 16; + } else { + // Default to the platform limit, which is 30. + assumedMaxChannelCount = 30; + } + Log.w(TAG, "AssumedMaxChannelAdjustment: " + name + ", [" + maxChannelCount + " to " + + assumedMaxChannelCount + "]"); + return assumedMaxChannelCount; + } + private static boolean isAdaptive(CodecCapabilities capabilities) { return Util.SDK_INT >= 19 && isAdaptiveV19(capabilities); } @@ -279,14 +336,6 @@ public final class MediaCodecInfo { return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback); } - @TargetApi(21) - private static boolean areSizeAndRateSupported(VideoCapabilities capabilities, int width, - int height, double frameRate) { - return frameRate == Format.NO_VALUE || frameRate <= 0 - ? capabilities.isSizeSupported(width, height) - : capabilities.areSizeAndRateSupported(width, height, frameRate); - } - private static boolean isTunneling(CodecCapabilities capabilities) { return Util.SDK_INT >= 21 && isTunnelingV21(capabilities); } @@ -296,4 +345,21 @@ public final class MediaCodecInfo { return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_TunneledPlayback); } + private static boolean isSecure(CodecCapabilities capabilities) { + return Util.SDK_INT >= 21 && isSecureV21(capabilities); + } + + @TargetApi(21) + private static boolean isSecureV21(CodecCapabilities capabilities) { + return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback); + } + + @TargetApi(21) + private static boolean areSizeAndRateSupportedV21(VideoCapabilities capabilities, int width, + int height, double frameRate) { + return frameRate == Format.NO_VALUE || frameRate <= 0 + ? capabilities.isSizeSupported(width, height) + : capabilities.areSizeAndRateSupported(width, height, frameRate); + } + } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/mediacodec/MediaCodecRenderer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/mediacodec/MediaCodecRenderer.java index 2fa2bdac2..0ade43be9 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -23,6 +23,8 @@ import android.media.MediaCrypto; import android.media.MediaFormat; import android.os.Looper; import android.os.SystemClock; +import android.support.annotation.IntDef; +import android.support.annotation.Nullable; import android.util.Log; import org.telegram.messenger.exoplayer2.BaseRenderer; import org.telegram.messenger.exoplayer2.C; @@ -31,7 +33,9 @@ import org.telegram.messenger.exoplayer2.Format; import org.telegram.messenger.exoplayer2.FormatHolder; import org.telegram.messenger.exoplayer2.decoder.DecoderCounters; import org.telegram.messenger.exoplayer2.decoder.DecoderInputBuffer; +import org.telegram.messenger.exoplayer2.drm.DrmInitData; import org.telegram.messenger.exoplayer2.drm.DrmSession; +import org.telegram.messenger.exoplayer2.drm.DrmSession.DrmSessionException; import org.telegram.messenger.exoplayer2.drm.DrmSessionManager; import org.telegram.messenger.exoplayer2.drm.FrameworkMediaCrypto; import org.telegram.messenger.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; @@ -40,6 +44,9 @@ import org.telegram.messenger.exoplayer2.util.Assertions; import org.telegram.messenger.exoplayer2.util.NalUnitUtil; import org.telegram.messenger.exoplayer2.util.TraceUtil; import org.telegram.messenger.exoplayer2.util.Util; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; @@ -155,9 +162,26 @@ public abstract class MediaCodecRenderer extends BaseRenderer { */ private static final int REINITIALIZATION_STATE_WAIT_END_OF_STREAM = 2; + @Retention(RetentionPolicy.SOURCE) + @IntDef({ADAPTATION_WORKAROUND_MODE_NEVER, ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION, + ADAPTATION_WORKAROUND_MODE_ALWAYS}) + private @interface AdaptationWorkaroundMode {} + /** + * The adaptation workaround is never used. + */ + private static final int ADAPTATION_WORKAROUND_MODE_NEVER = 0; + /** + * The adaptation workaround is used when adapting between formats of the same resolution only. + */ + private static final int ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION = 1; + /** + * The adaptation workaround is always used when adapting between formats. + */ + private static final int ADAPTATION_WORKAROUND_MODE_ALWAYS = 2; + /** * H.264/AVC buffer to queue when using the adaptation workaround (see - * {@link #codecNeedsAdaptationWorkaround(String)}. Consists of three NAL units with start codes: + * {@link #codecAdaptationWorkaroundMode(String)}. Consists of three NAL units with start codes: * Baseline sequence/picture parameter sets and a 32 * 32 pixel IDR slice. This stream can be * queued to force a resolution change when adapting to a new format. */ @@ -175,13 +199,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer { private final MediaCodec.BufferInfo outputBufferInfo; private Format format; - private MediaCodec codec; private DrmSession drmSession; private DrmSession pendingDrmSession; - private boolean codecIsAdaptive; + private MediaCodec codec; + private MediaCodecInfo codecInfo; + private @AdaptationWorkaroundMode int codecAdaptationWorkaroundMode; private boolean codecNeedsDiscardToSpsWorkaround; private boolean codecNeedsFlushWorkaround; - private boolean codecNeedsAdaptationWorkaround; private boolean codecNeedsEosPropagationWorkaround; private boolean codecNeedsEosFlushWorkaround; private boolean codecNeedsEosOutputExceptionWorkaround; @@ -237,14 +261,21 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } @Override - public final int supportsMixedMimeTypeAdaptation() throws ExoPlaybackException { + public final int supportsMixedMimeTypeAdaptation() { return ADAPTIVE_NOT_SEAMLESS; } @Override public final int supportsFormat(Format format) throws ExoPlaybackException { try { - return supportsFormat(mediaCodecSelector, format); + int formatSupport = supportsFormat(mediaCodecSelector, format); + if ((formatSupport & FORMAT_SUPPORT_MASK) > FORMAT_UNSUPPORTED_DRM + && !isDrmSchemeSupported(drmSessionManager, format.drmInitData)) { + // The renderer advertises higher support than FORMAT_UNSUPPORTED_DRM but the DRM scheme is + // not supported. The format support is truncated to reflect this. + formatSupport = (formatSupport & ~FORMAT_SUPPORT_MASK) | FORMAT_UNSUPPORTED_DRM; + } + return formatSupport; } catch (DecoderQueryException e) { throw ExoPlaybackException.createForRenderer(e, getIndex()); } @@ -291,58 +322,63 @@ public abstract class MediaCodecRenderer extends BaseRenderer { @SuppressWarnings("deprecation") protected final void maybeInitCodec() throws ExoPlaybackException { - if (!shouldInitCodec()) { + if (codec != null || format == null) { + // We have a codec already, or we don't have a format with which to instantiate one. return; } drmSession = pendingDrmSession; String mimeType = format.sampleMimeType; - MediaCrypto mediaCrypto = null; + MediaCrypto wrappedMediaCrypto = null; boolean drmSessionRequiresSecureDecoder = false; if (drmSession != null) { - @DrmSession.State int drmSessionState = drmSession.getState(); - if (drmSessionState == DrmSession.STATE_ERROR) { - throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex()); - } else if (drmSessionState == DrmSession.STATE_OPENED - || drmSessionState == DrmSession.STATE_OPENED_WITH_KEYS) { - mediaCrypto = drmSession.getMediaCrypto().getWrappedMediaCrypto(); - drmSessionRequiresSecureDecoder = drmSession.requiresSecureDecoderComponent(mimeType); - } else { + FrameworkMediaCrypto mediaCrypto = drmSession.getMediaCrypto(); + if (mediaCrypto == null) { + DrmSessionException drmError = drmSession.getError(); + if (drmError != null) { + throw ExoPlaybackException.createForRenderer(drmError, getIndex()); + } // The drm session isn't open yet. return; } + wrappedMediaCrypto = mediaCrypto.getWrappedMediaCrypto(); + drmSessionRequiresSecureDecoder = mediaCrypto.requiresSecureDecoderComponent(mimeType); } - MediaCodecInfo decoderInfo = null; - try { - decoderInfo = getDecoderInfo(mediaCodecSelector, format, drmSessionRequiresSecureDecoder); - if (decoderInfo == null && drmSessionRequiresSecureDecoder) { - // The drm session indicates that a secure decoder is required, but the device does not have - // one. Assuming that supportsFormat indicated support for the media being played, we know - // that it does not require a secure output path. Most CDM implementations allow playback to - // proceed with a non-secure decoder in this case, so we try our luck. - decoderInfo = getDecoderInfo(mediaCodecSelector, format, false); - if (decoderInfo != null) { - Log.w(TAG, "Drm session requires secure decoder for " + mimeType + ", but " - + "no secure decoder available. Trying to proceed with " + decoderInfo.name + "."); + if (codecInfo == null) { + try { + codecInfo = getDecoderInfo(mediaCodecSelector, format, drmSessionRequiresSecureDecoder); + if (codecInfo == null && drmSessionRequiresSecureDecoder) { + // The drm session indicates that a secure decoder is required, but the device does not + // have one. Assuming that supportsFormat indicated support for the media being played, we + // know that it does not require a secure output path. Most CDM implementations allow + // playback to proceed with a non-secure decoder in this case, so we try our luck. + codecInfo = getDecoderInfo(mediaCodecSelector, format, false); + if (codecInfo != null) { + Log.w(TAG, "Drm session requires secure decoder for " + mimeType + ", but " + + "no secure decoder available. Trying to proceed with " + codecInfo.name + "."); + } } + } catch (DecoderQueryException e) { + throwDecoderInitError(new DecoderInitializationException(format, e, + drmSessionRequiresSecureDecoder, DecoderInitializationException.DECODER_QUERY_ERROR)); + } + + if (codecInfo == null) { + throwDecoderInitError(new DecoderInitializationException(format, null, + drmSessionRequiresSecureDecoder, + DecoderInitializationException.NO_SUITABLE_DECODER_ERROR)); } - } catch (DecoderQueryException e) { - throwDecoderInitError(new DecoderInitializationException(format, e, - drmSessionRequiresSecureDecoder, DecoderInitializationException.DECODER_QUERY_ERROR)); } - if (decoderInfo == null) { - throwDecoderInitError(new DecoderInitializationException(format, null, - drmSessionRequiresSecureDecoder, - DecoderInitializationException.NO_SUITABLE_DECODER_ERROR)); + if (!shouldInitCodec(codecInfo)) { + return; } - String codecName = decoderInfo.name; - codecIsAdaptive = decoderInfo.adaptive && !codecNeedsDisableAdaptationWorkaround(codecName); + String codecName = codecInfo.name; + codecAdaptationWorkaroundMode = codecAdaptationWorkaroundMode(codecName); codecNeedsDiscardToSpsWorkaround = codecNeedsDiscardToSpsWorkaround(codecName, format); codecNeedsFlushWorkaround = codecNeedsFlushWorkaround(codecName); - codecNeedsAdaptationWorkaround = codecNeedsAdaptationWorkaround(codecName); codecNeedsEosPropagationWorkaround = codecNeedsEosPropagationWorkaround(codecName); codecNeedsEosFlushWorkaround = codecNeedsEosFlushWorkaround(codecName); codecNeedsEosOutputExceptionWorkaround = codecNeedsEosOutputExceptionWorkaround(codecName); @@ -353,7 +389,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { codec = MediaCodec.createByCodecName(codecName); TraceUtil.endSection(); TraceUtil.beginSection("configureCodec"); - configureCodec(decoderInfo, codec, format, mediaCrypto); + configureCodec(codecInfo, codec, format, wrappedMediaCrypto); TraceUtil.endSection(); TraceUtil.beginSection("startCodec"); codec.start(); @@ -380,14 +416,18 @@ public abstract class MediaCodecRenderer extends BaseRenderer { throw ExoPlaybackException.createForRenderer(e, getIndex()); } - protected boolean shouldInitCodec() { - return codec == null && format != null; + protected boolean shouldInitCodec(MediaCodecInfo codecInfo) { + return true; } protected final MediaCodec getCodec() { return codec; } + protected final MediaCodecInfo getCodecInfo() { + return codecInfo; + } + @Override protected void onEnabled(boolean joining) throws ExoPlaybackException { decoderCounters = new DecoderCounters(); @@ -426,31 +466,31 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } protected void releaseCodec() { + codecHotswapDeadlineMs = C.TIME_UNSET; + inputIndex = C.INDEX_UNSET; + outputIndex = C.INDEX_UNSET; + waitingForKeys = false; + shouldSkipOutputBuffer = false; + decodeOnlyPresentationTimestamps.clear(); + inputBuffers = null; + outputBuffers = null; + codecInfo = null; + codecReconfigured = false; + codecReceivedBuffers = false; + codecNeedsDiscardToSpsWorkaround = false; + codecNeedsFlushWorkaround = false; + codecAdaptationWorkaroundMode = ADAPTATION_WORKAROUND_MODE_NEVER; + codecNeedsEosPropagationWorkaround = false; + codecNeedsEosFlushWorkaround = false; + codecNeedsMonoChannelCountWorkaround = false; + codecNeedsAdaptationWorkaroundBuffer = false; + shouldSkipAdaptationWorkaroundOutputBuffer = false; + codecReceivedEos = false; + codecReconfigurationState = RECONFIGURATION_STATE_NONE; + codecReinitializationState = REINITIALIZATION_STATE_NONE; + buffer.data = null; if (codec != null) { - codecHotswapDeadlineMs = C.TIME_UNSET; - inputIndex = C.INDEX_UNSET; - outputIndex = C.INDEX_UNSET; - waitingForKeys = false; - shouldSkipOutputBuffer = false; - decodeOnlyPresentationTimestamps.clear(); - inputBuffers = null; - outputBuffers = null; - codecReconfigured = false; - codecReceivedBuffers = false; - codecIsAdaptive = false; - codecNeedsDiscardToSpsWorkaround = false; - codecNeedsFlushWorkaround = false; - codecNeedsAdaptationWorkaround = false; - codecNeedsEosPropagationWorkaround = false; - codecNeedsEosFlushWorkaround = false; - codecNeedsMonoChannelCountWorkaround = false; - codecNeedsAdaptationWorkaroundBuffer = false; - shouldSkipAdaptationWorkaroundOutputBuffer = false; - codecReceivedEos = false; - codecReconfigurationState = RECONFIGURATION_STATE_NONE; - codecReinitializationState = REINITIALIZATION_STATE_NONE; decoderCounters.decoderReleaseCount++; - buffer.data = null; try { codec.stop(); } finally { @@ -488,7 +528,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } if (format == null) { // We don't have a format yet, so try and read one. - buffer.clear(); + flagsOnlyBuffer.clear(); int result = readSource(formatHolder, flagsOnlyBuffer, true); if (result == C.RESULT_FORMAT_READ) { onInputFormatChanged(formatHolder.format); @@ -727,15 +767,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } private boolean shouldWaitForKeys(boolean bufferEncrypted) throws ExoPlaybackException { - if (drmSession == null) { + if (drmSession == null || (!bufferEncrypted && playClearSamplesWithoutKeys)) { return false; } @DrmSession.State int drmSessionState = drmSession.getState(); if (drmSessionState == DrmSession.STATE_ERROR) { throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex()); } - return drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS - && (bufferEncrypted || !playClearSamplesWithoutKeys); + return drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS; } /** @@ -781,11 +820,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } if (pendingDrmSession == drmSession && codec != null - && canReconfigureCodec(codec, codecIsAdaptive, oldFormat, format)) { + && canReconfigureCodec(codec, codecInfo.adaptive, oldFormat, format)) { codecReconfigured = true; codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; - codecNeedsAdaptationWorkaroundBuffer = codecNeedsAdaptationWorkaround - && format.width == oldFormat.width && format.height == oldFormat.height; + codecNeedsAdaptationWorkaroundBuffer = + codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_ALWAYS + || (codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION + && format.width == oldFormat.width && format.height == oldFormat.height); } else { if (codecReceivedBuffers) { // Signal end of stream and wait for any final output buffers before re-initialization. @@ -971,7 +1012,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { */ private void processOutputFormat() throws ExoPlaybackException { MediaFormat format = codec.getOutputFormat(); - if (codecNeedsAdaptationWorkaround + if (codecAdaptationWorkaroundMode != ADAPTATION_WORKAROUND_MODE_NEVER && format.getInteger(MediaFormat.KEY_WIDTH) == ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT && format.getInteger(MediaFormat.KEY_HEIGHT) == ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT) { // We assume this format changed event was caused by the adaptation workaround. @@ -1065,6 +1106,25 @@ public abstract class MediaCodecRenderer extends BaseRenderer { return false; } + /** + * Returns whether the encryption scheme is supported, or true if {@code drmInitData} is null. + * + * @param drmSessionManager The drm session manager associated with the renderer. + * @param drmInitData {@link DrmInitData} of the format to check for support. + * @return Whether the encryption scheme is supported, or true if {@code drmInitData} is null. + */ + private static boolean isDrmSchemeSupported(DrmSessionManager drmSessionManager, + @Nullable DrmInitData drmInitData) { + if (drmInitData == null) { + // Content is unencrypted. + return true; + } else if (drmSessionManager == null) { + // Content is encrypted, but no drm session manager is available. + return false; + } + return drmSessionManager.canAcquireSession(drmInitData); + } + /** * Returns whether the decoder is known to fail when flushed. *

    @@ -1085,22 +1145,30 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } /** - * Returns whether the decoder is known to get stuck during some adaptations where the resolution - * does not change. + * Returns a mode that specifies when the adaptation workaround should be enabled. *

    - * If true is returned, the renderer will work around the issue by queueing and discarding a blank - * frame at a different resolution, which resets the codec's internal state. + * When enabled, the workaround queues and discards a blank frame with a resolution whose width + * and height both equal {@link #ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT}, to reset the codec's + * internal state when a format change occurs. *

    * See [Internal: b/27807182]. + * See GitHub issue #3257. * * @param name The name of the decoder. - * @return True if the decoder is known to get stuck during some adaptations. + * @return The mode specifying when the adaptation workaround should be enabled. */ - private static boolean codecNeedsAdaptationWorkaround(String name) { - return Util.SDK_INT < 24 + private @AdaptationWorkaroundMode int codecAdaptationWorkaroundMode(String name) { + if (Util.SDK_INT <= 24 && "OMX.Exynos.avc.dec.secure".equals(name) + && (Util.MODEL.startsWith("SM-T585") || Util.MODEL.startsWith("SM-A520"))) { + return ADAPTATION_WORKAROUND_MODE_ALWAYS; + } else if (Util.SDK_INT < 24 && ("OMX.Nvidia.h264.decode".equals(name) || "OMX.Nvidia.h264.decode.secure".equals(name)) && ("flounder".equals(Util.DEVICE) || "flounder_lte".equals(Util.DEVICE) - || "grouper".equals(Util.DEVICE) || "tilapia".equals(Util.DEVICE)); + || "grouper".equals(Util.DEVICE) || "tilapia".equals(Util.DEVICE))) { + return ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION; + } else { + return ADAPTATION_WORKAROUND_MODE_NEVER; + } } /** @@ -1188,18 +1256,4 @@ public abstract class MediaCodecRenderer extends BaseRenderer { && "OMX.MTK.AUDIO.DECODER.MP3".equals(name); } - /** - * Returns whether the decoder is known to fail when adapting, despite advertising itself as an - * adaptive decoder. - *

    - * If true is returned then we explicitly disable adaptation for the decoder. - * - * @param name The decoder name. - * @return True if the decoder is known to fail when adapting. - */ - private static boolean codecNeedsDisableAdaptationWorkaround(String name) { - return Util.SDK_INT <= 19 && Util.MODEL.equals("ODROID-XU3") - && ("OMX.Exynos.AVC.Decoder".equals(name) || "OMX.Exynos.AVC.Decoder.secure".equals(name)); - } - } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/mediacodec/MediaCodecSelector.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/mediacodec/MediaCodecSelector.java index 3d495b9d4..87a03fe1b 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/mediacodec/MediaCodecSelector.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/mediacodec/MediaCodecSelector.java @@ -55,8 +55,7 @@ public interface MediaCodecSelector { /** * Selects a decoder to instantiate for audio passthrough. * - * @return A {@link MediaCodecInfo} describing the decoder, or null if no suitable decoder - * exists. + * @return A {@link MediaCodecInfo} describing the decoder, or null if no suitable decoder exists. * @throws DecoderQueryException Thrown if there was an error querying decoders. */ MediaCodecInfo getPassthroughDecoderInfo() throws DecoderQueryException; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/mediacodec/MediaCodecUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/mediacodec/MediaCodecUtil.java index a1e8c71d9..9445972d1 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/mediacodec/MediaCodecUtil.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/mediacodec/MediaCodecUtil.java @@ -56,8 +56,10 @@ public final class MediaCodecUtil { } private static final String TAG = "MediaCodecUtil"; + private static final String GOOGLE_RAW_DECODER_NAME = "OMX.google.raw.decoder"; + private static final String MTK_RAW_DECODER_NAME = "OMX.MTK.AUDIO.DECODER.RAW"; private static final MediaCodecInfo PASSTHROUGH_DECODER_INFO = - MediaCodecInfo.newPassthroughInstance("OMX.google.raw.decoder"); + MediaCodecInfo.newPassthroughInstance(GOOGLE_RAW_DECODER_NAME); private static final Pattern PROFILE_PATTERN = Pattern.compile("^\\D?(\\d+)$"); private static final HashMap> decoderInfosCache = new HashMap<>(); @@ -99,7 +101,7 @@ public final class MediaCodecUtil { /** * Returns information about a decoder suitable for audio passthrough. - ** + * * @return A {@link MediaCodecInfo} describing the decoder, or null if no suitable decoder * exists. */ @@ -155,11 +157,61 @@ public final class MediaCodecUtil { + ". Assuming: " + decoderInfos.get(0).name); } } + applyWorkarounds(decoderInfos); decoderInfos = Collections.unmodifiableList(decoderInfos); decoderInfosCache.put(key, decoderInfos); return decoderInfos; } + /** + * Returns the maximum frame size supported by the default H264 decoder. + * + * @return The maximum frame size for an H264 stream that can be decoded on the device. + */ + public static int maxH264DecodableFrameSize() throws DecoderQueryException { + if (maxH264DecodableFrameSize == -1) { + int result = 0; + MediaCodecInfo decoderInfo = getDecoderInfo(MimeTypes.VIDEO_H264, false); + if (decoderInfo != null) { + for (CodecProfileLevel profileLevel : decoderInfo.getProfileLevels()) { + result = Math.max(avcLevelToMaxFrameSize(profileLevel.level), result); + } + // We assume support for at least 480p (SDK_INT >= 21) or 360p (SDK_INT < 21), which are + // the levels mandated by the Android CDD. + result = Math.max(result, Util.SDK_INT >= 21 ? (720 * 480) : (480 * 360)); + } + maxH264DecodableFrameSize = result; + } + return maxH264DecodableFrameSize; + } + + /** + * Returns profile and level (as defined by {@link CodecProfileLevel}) corresponding to the given + * codec description string (as defined by RFC 6381). + * + * @param codec A codec description string, as defined by RFC 6381. + * @return A pair (profile constant, level constant) if {@code codec} is well-formed and + * recognized, or null otherwise + */ + public static Pair getCodecProfileAndLevel(String codec) { + if (codec == null) { + return null; + } + String[] parts = codec.split("\\."); + switch (parts[0]) { + case CODEC_ID_HEV1: + case CODEC_ID_HVC1: + return getHevcProfileAndLevel(codec, parts); + case CODEC_ID_AVC1: + case CODEC_ID_AVC2: + return getAvcProfileAndLevel(codec, parts); + default: + return null; + } + } + + // Internal methods. + private static List getDecoderInfosInternal( CodecKey key, MediaCodecListCompat mediaCodecList) throws DecoderQueryException { try { @@ -177,12 +229,14 @@ public final class MediaCodecUtil { try { CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(supportedType); boolean secure = mediaCodecList.isSecurePlaybackSupported(mimeType, capabilities); + boolean forceDisableAdaptive = codecNeedsDisableAdaptationWorkaround(codecName); if ((secureDecodersExplicit && key.secure == secure) || (!secureDecodersExplicit && !key.secure)) { - decoderInfos.add(MediaCodecInfo.newInstance(codecName, mimeType, capabilities)); + decoderInfos.add(MediaCodecInfo.newInstance(codecName, mimeType, capabilities, + forceDisableAdaptive, false)); } else if (!secureDecodersExplicit && secure) { decoderInfos.add(MediaCodecInfo.newInstance(codecName + ".secure", mimeType, - capabilities)); + capabilities, forceDisableAdaptive, true)); // It only makes sense to have one synthesized secure decoder, return immediately. return decoderInfos; } @@ -234,9 +288,11 @@ public final class MediaCodecUtil { return false; } - // Work around https://github.com/google/ExoPlayer/issues/1528 + // Work around https://github.com/google/ExoPlayer/issues/1528 and + // https://github.com/google/ExoPlayer/issues/3171 if (Util.SDK_INT < 18 && "OMX.MTK.AUDIO.DECODER.AAC".equals(name) - && "a70".equals(Util.DEVICE)) { + && ("a70".equals(Util.DEVICE) + || ("Xiaomi".equals(Util.MANUFACTURER) && Util.DEVICE.startsWith("HM")))) { return false; } @@ -269,7 +325,22 @@ public final class MediaCodecUtil { return false; } - // Work around https://github.com/google/ExoPlayer/issues/548 + // Work around https://github.com/google/ExoPlayer/issues/3249. + if (Util.SDK_INT < 24 + && ("OMX.SEC.aac.dec".equals(name) || "OMX.Exynos.AAC.Decoder".equals(name)) + && Util.MANUFACTURER.equals("samsung") + && (Util.DEVICE.startsWith("zeroflte") // Galaxy S6 + || Util.DEVICE.startsWith("zerolte") // Galaxy S6 Edge + || Util.DEVICE.startsWith("zenlte") // Galaxy S6 Edge+ + || Util.DEVICE.equals("SC-05G") // Galaxy S6 + || Util.DEVICE.equals("marinelteatt") // Galaxy S6 Active + || Util.DEVICE.equals("404SC") // Galaxy S6 Edge + || Util.DEVICE.equals("SC-04G") + || Util.DEVICE.equals("SCV31"))) { + return false; + } + + // Work around https://github.com/google/ExoPlayer/issues/548. // VP8 decoder on Samsung Galaxy S3/S4/S4 Mini/Tab 3/Note 2 does not render video. if (Util.SDK_INT <= 19 && "OMX.SEC.vp8.dec".equals(name) && "samsung".equals(Util.MANUFACTURER) @@ -289,50 +360,37 @@ public final class MediaCodecUtil { } /** - * Returns the maximum frame size supported by the default H264 decoder. + * Modifies a list of {@link MediaCodecInfo}s to apply workarounds where we know better than the + * platform. * - * @return The maximum frame size for an H264 stream that can be decoded on the device. + * @param decoderInfos The list to modify. */ - public static int maxH264DecodableFrameSize() throws DecoderQueryException { - if (maxH264DecodableFrameSize == -1) { - int result = 0; - MediaCodecInfo decoderInfo = getDecoderInfo(MimeTypes.VIDEO_H264, false); - if (decoderInfo != null) { - for (CodecProfileLevel profileLevel : decoderInfo.getProfileLevels()) { - result = Math.max(avcLevelToMaxFrameSize(profileLevel.level), result); + private static void applyWorkarounds(List decoderInfos) { + if (Util.SDK_INT < 26 && decoderInfos.size() > 1 + && MTK_RAW_DECODER_NAME.equals(decoderInfos.get(0).name)) { + // Prefer the Google raw decoder over the MediaTek one [Internal: b/62337687]. + for (int i = 1; i < decoderInfos.size(); i++) { + MediaCodecInfo decoderInfo = decoderInfos.get(i); + if (GOOGLE_RAW_DECODER_NAME.equals(decoderInfo.name)) { + decoderInfos.remove(i); + decoderInfos.add(0, decoderInfo); + break; } - // We assume support for at least 480p (SDK_INT >= 21) or 360p (SDK_INT < 21), which are - // the levels mandated by the Android CDD. - result = Math.max(result, Util.SDK_INT >= 21 ? (720 * 480) : (480 * 360)); } - maxH264DecodableFrameSize = result; } - return maxH264DecodableFrameSize; } /** - * Returns profile and level (as defined by {@link CodecProfileLevel}) corresponding to the given - * codec description string (as defined by RFC 6381). + * Returns whether the decoder is known to fail when adapting, despite advertising itself as an + * adaptive decoder. * - * @param codec A codec description string, as defined by RFC 6381. - * @return A pair (profile constant, level constant) if {@code codec} is well-formed and - * recognized, or null otherwise + * @param name The decoder name. + * @return True if the decoder is known to fail when adapting. */ - public static Pair getCodecProfileAndLevel(String codec) { - if (codec == null) { - return null; - } - String[] parts = codec.split("\\."); - switch (parts[0]) { - case CODEC_ID_HEV1: - case CODEC_ID_HVC1: - return getHevcProfileAndLevel(codec, parts); - case CODEC_ID_AVC1: - case CODEC_ID_AVC2: - return getAvcProfileAndLevel(codec, parts); - default: - return null; - } + private static boolean codecNeedsDisableAdaptationWorkaround(String name) { + return Util.SDK_INT <= 22 + && (Util.MODEL.equals("ODROID-XU3") || Util.MODEL.equals("Nexus 10")) + && ("OMX.Exynos.AVC.Decoder".equals(name) || "OMX.Exynos.AVC.Decoder.secure".equals(name)); } private static Pair getHevcProfileAndLevel(String codec, String[] parts) { @@ -429,6 +487,7 @@ public final class MediaCodecUtil { case CodecProfileLevel.AVCLevel42: return 8704 * 16 * 16; case CodecProfileLevel.AVCLevel5: return 22080 * 16 * 16; case CodecProfileLevel.AVCLevel51: return 36864 * 16 * 16; + case CodecProfileLevel.AVCLevel52: return 36864 * 16 * 16; default: return -1; } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/MetadataRenderer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/MetadataRenderer.java index e2530d397..c1f74e92f 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/MetadataRenderer.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/MetadataRenderer.java @@ -104,7 +104,7 @@ public final class MetadataRenderer extends BaseRenderer implements Callback { } @Override - protected void onStreamChanged(Format[] formats) throws ExoPlaybackException { + protected void onStreamChanged(Format[] formats, long offsetUs) throws ExoPlaybackException { decoder = decoderFactory.createDecoder(formats[0]); } @@ -153,7 +153,6 @@ public final class MetadataRenderer extends BaseRenderer implements Callback { protected void onDisabled() { flushPendingMetadata(); decoder = null; - super.onDisabled(); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/Id3Decoder.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/Id3Decoder.java index b3bb3e276..6db8f0641 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/Id3Decoder.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/Id3Decoder.java @@ -483,13 +483,8 @@ public final class Id3Decoder implements MetadataDecoder { int ownerEndIndex = indexOfZeroByte(data, 0); String owner = new String(data, 0, ownerEndIndex, "ISO-8859-1"); - byte[] privateData; int privateDataStartIndex = ownerEndIndex + 1; - if (privateDataStartIndex < data.length) { - privateData = Arrays.copyOfRange(data, privateDataStartIndex, data.length); - } else { - privateData = new byte[0]; - } + byte[] privateData = copyOfRangeIfValid(data, privateDataStartIndex, data.length); return new PrivFrame(owner, privateData); } @@ -516,7 +511,7 @@ public final class Id3Decoder implements MetadataDecoder { descriptionEndIndex - descriptionStartIndex, charset); int objectDataStartIndex = descriptionEndIndex + delimiterLength(encoding); - byte[] objectData = Arrays.copyOfRange(data, objectDataStartIndex, data.length); + byte[] objectData = copyOfRangeIfValid(data, objectDataStartIndex, data.length); return new GeobFrame(mimeType, filename, description, objectData); } @@ -553,7 +548,7 @@ public final class Id3Decoder implements MetadataDecoder { descriptionEndIndex - descriptionStartIndex, charset); int pictureDataStartIndex = descriptionEndIndex + delimiterLength(encoding); - byte[] pictureData = Arrays.copyOfRange(data, pictureDataStartIndex, data.length); + byte[] pictureData = copyOfRangeIfValid(data, pictureDataStartIndex, data.length); return new ApicFrame(mimeType, description, pictureType, pictureData); } @@ -749,6 +744,22 @@ public final class Id3Decoder implements MetadataDecoder { ? 1 : 2; } + /** + * Copies the specified range of an array, or returns a zero length array if the range is invalid. + * + * @param data The array from which to copy. + * @param from The start of the range to copy (inclusive). + * @param to The end of the range to copy (exclusive). + * @return The copied data, or a zero length array if the range is invalid. + */ + private static byte[] copyOfRangeIfValid(byte[] data, int from, int to) { + if (to <= from) { + // Invalid or zero length range. + return new byte[0]; + } + return Arrays.copyOfRange(data, from, to); + } + private static final class Id3Header { private final int majorVersion; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/AbstractConcatenatedTimeline.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/AbstractConcatenatedTimeline.java new file mode 100755 index 000000000..12e32ec32 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/AbstractConcatenatedTimeline.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source; + +import android.util.Pair; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Player; +import org.telegram.messenger.exoplayer2.Timeline; + +/** + * Abstract base class for the concatenation of one or more {@link Timeline}s. + */ +/* package */ abstract class AbstractConcatenatedTimeline extends Timeline { + + private final int childCount; + + public AbstractConcatenatedTimeline(int childCount) { + this.childCount = childCount; + } + + @Override + public int getNextWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode) { + int childIndex = getChildIndexByWindowIndex(windowIndex); + int firstWindowIndexInChild = getFirstWindowIndexByChildIndex(childIndex); + int nextWindowIndexInChild = getTimelineByChildIndex(childIndex).getNextWindowIndex( + windowIndex - firstWindowIndexInChild, + repeatMode == Player.REPEAT_MODE_ALL ? Player.REPEAT_MODE_OFF : repeatMode); + if (nextWindowIndexInChild != C.INDEX_UNSET) { + return firstWindowIndexInChild + nextWindowIndexInChild; + } else { + int nextChildIndex = childIndex + 1; + if (nextChildIndex < childCount) { + return getFirstWindowIndexByChildIndex(nextChildIndex); + } else if (repeatMode == Player.REPEAT_MODE_ALL) { + return 0; + } else { + return C.INDEX_UNSET; + } + } + } + + @Override + public int getPreviousWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode) { + int childIndex = getChildIndexByWindowIndex(windowIndex); + int firstWindowIndexInChild = getFirstWindowIndexByChildIndex(childIndex); + int previousWindowIndexInChild = getTimelineByChildIndex(childIndex).getPreviousWindowIndex( + windowIndex - firstWindowIndexInChild, + repeatMode == Player.REPEAT_MODE_ALL ? Player.REPEAT_MODE_OFF : repeatMode); + if (previousWindowIndexInChild != C.INDEX_UNSET) { + return firstWindowIndexInChild + previousWindowIndexInChild; + } else { + if (firstWindowIndexInChild > 0) { + return firstWindowIndexInChild - 1; + } else if (repeatMode == Player.REPEAT_MODE_ALL) { + return getWindowCount() - 1; + } else { + return C.INDEX_UNSET; + } + } + } + + @Override + public final Window getWindow(int windowIndex, Window window, boolean setIds, + long defaultPositionProjectionUs) { + int childIndex = getChildIndexByWindowIndex(windowIndex); + int firstWindowIndexInChild = getFirstWindowIndexByChildIndex(childIndex); + int firstPeriodIndexInChild = getFirstPeriodIndexByChildIndex(childIndex); + getTimelineByChildIndex(childIndex).getWindow(windowIndex - firstWindowIndexInChild, window, + setIds, defaultPositionProjectionUs); + window.firstPeriodIndex += firstPeriodIndexInChild; + window.lastPeriodIndex += firstPeriodIndexInChild; + return window; + } + + @Override + public final Period getPeriod(int periodIndex, Period period, boolean setIds) { + int childIndex = getChildIndexByPeriodIndex(periodIndex); + int firstWindowIndexInChild = getFirstWindowIndexByChildIndex(childIndex); + int firstPeriodIndexInChild = getFirstPeriodIndexByChildIndex(childIndex); + getTimelineByChildIndex(childIndex).getPeriod(periodIndex - firstPeriodIndexInChild, period, + setIds); + period.windowIndex += firstWindowIndexInChild; + if (setIds) { + period.uid = Pair.create(getChildUidByChildIndex(childIndex), period.uid); + } + return period; + } + + @Override + public final int getIndexOfPeriod(Object uid) { + if (!(uid instanceof Pair)) { + return C.INDEX_UNSET; + } + Pair childUidAndPeriodUid = (Pair) uid; + Object childUid = childUidAndPeriodUid.first; + Object periodUid = childUidAndPeriodUid.second; + int childIndex = getChildIndexByChildUid(childUid); + if (childIndex == C.INDEX_UNSET) { + return C.INDEX_UNSET; + } + int periodIndexInChild = getTimelineByChildIndex(childIndex).getIndexOfPeriod(periodUid); + return periodIndexInChild == C.INDEX_UNSET ? C.INDEX_UNSET + : getFirstPeriodIndexByChildIndex(childIndex) + periodIndexInChild; + } + + /** + * Returns the index of the child timeline containing the given period index. + * + * @param periodIndex A valid period index within the bounds of the timeline. + */ + protected abstract int getChildIndexByPeriodIndex(int periodIndex); + + /** + * Returns the index of the child timeline containing the given window index. + * + * @param windowIndex A valid window index within the bounds of the timeline. + */ + protected abstract int getChildIndexByWindowIndex(int windowIndex); + + /** + * Returns the index of the child timeline with the given UID or {@link C#INDEX_UNSET} if not + * found. + * + * @param childUid A child UID. + * @return Index of child timeline or {@link C#INDEX_UNSET} if UID was not found. + */ + protected abstract int getChildIndexByChildUid(Object childUid); + + /** + * Returns the child timeline for the child with the given index. + * + * @param childIndex A valid child index within the bounds of the timeline. + */ + protected abstract Timeline getTimelineByChildIndex(int childIndex); + + /** + * Returns the first period index belonging to the child timeline with the given index. + * + * @param childIndex A valid child index within the bounds of the timeline. + */ + protected abstract int getFirstPeriodIndexByChildIndex(int childIndex); + + /** + * Returns the first window index belonging to the child timeline with the given index. + * + * @param childIndex A valid child index within the bounds of the timeline. + */ + protected abstract int getFirstWindowIndexByChildIndex(int childIndex); + + /** + * Returns the UID of the child timeline with the given index. + * + * @param childIndex A valid child index within the bounds of the timeline. + */ + protected abstract Object getChildUidByChildIndex(int childIndex); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ClippingMediaPeriod.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ClippingMediaPeriod.java index a15c40c21..692ab6ff7 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ClippingMediaPeriod.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ClippingMediaPeriod.java @@ -16,10 +16,12 @@ package org.telegram.messenger.exoplayer2.source; import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; import org.telegram.messenger.exoplayer2.FormatHolder; import org.telegram.messenger.exoplayer2.decoder.DecoderInputBuffer; import org.telegram.messenger.exoplayer2.trackselection.TrackSelection; import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.MimeTypes; import java.io.IOException; /** @@ -45,14 +47,20 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb *

    * The clipping start/end positions must be specified by calling {@link #setClipping(long, long)} * on the playback thread before preparation completes. + *

    + * If the start point is guaranteed to be a key frame, pass {@code false} to {@code + * enableInitialPositionDiscontinuity} to suppress an initial discontinuity when the period is + * first read from. * * @param mediaPeriod The media period to clip. + * @param enableInitialDiscontinuity Whether the initial discontinuity should be enabled. */ - public ClippingMediaPeriod(MediaPeriod mediaPeriod) { + public ClippingMediaPeriod(MediaPeriod mediaPeriod, boolean enableInitialDiscontinuity) { this.mediaPeriod = mediaPeriod; startUs = C.TIME_UNSET; endUs = C.TIME_UNSET; sampleStreams = new ClippingSampleStream[0]; + pendingInitialDiscontinuity = enableInitialDiscontinuity; } /** @@ -68,9 +76,9 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb } @Override - public void prepare(MediaPeriod.Callback callback) { + public void prepare(MediaPeriod.Callback callback, long positionUs) { this.callback = callback; - mediaPeriod.prepare(this); + mediaPeriod.prepare(this, startUs + positionUs); } @Override @@ -94,6 +102,9 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb } long enablePositionUs = mediaPeriod.selectTracks(selections, mayRetainStreamFlags, internalStreams, streamResetFlags, positionUs + startUs); + if (pendingInitialDiscontinuity) { + pendingInitialDiscontinuity = startUs != 0 && shouldKeepInitialDiscontinuity(selections); + } Assertions.checkState(enablePositionUs == positionUs + startUs || (enablePositionUs >= startUs && (endUs == C.TIME_END_OF_SOURCE || enablePositionUs <= endUs))); @@ -179,6 +190,15 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb @Override public void onPrepared(MediaPeriod mediaPeriod) { Assertions.checkState(startUs != C.TIME_UNSET && endUs != C.TIME_UNSET); + callback.onPrepared(this); + } + + @Override + public void onContinueLoadingRequested(MediaPeriod source) { + callback.onContinueLoadingRequested(this); + } + + private static boolean shouldKeepInitialDiscontinuity(TrackSelection[] selections) { // If the clipping start position is non-zero, the clipping sample streams will adjust // timestamps on buffers they read from the unclipped sample streams. These adjusted buffer // timestamps can be negative, because sample streams provide buffers starting at a key-frame, @@ -186,13 +206,17 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb // negative timestamp, its offset timestamp can jump backwards compared to the last timestamp // read in the previous period. Renderer implementations may not allow this, so we signal a // discontinuity which resets the renderers before they read the clipping sample stream. - pendingInitialDiscontinuity = startUs != 0; - callback.onPrepared(this); - } - - @Override - public void onContinueLoadingRequested(MediaPeriod source) { - callback.onContinueLoadingRequested(this); + // However, for audio-only track selections we assume to have random access seek behaviour and + // do not need an initial discontinuity to reset the renderer. + for (TrackSelection trackSelection : selections) { + if (trackSelection != null) { + Format selectedFormat = trackSelection.getSelectedFormat(); + if (!MimeTypes.isAudio(selectedFormat.sampleMimeType)) { + return true; + } + } + } + return false; } /** diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ClippingMediaSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ClippingMediaSource.java index 108754f1c..d3bfcab43 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ClippingMediaSource.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ClippingMediaSource.java @@ -17,6 +17,7 @@ package org.telegram.messenger.exoplayer2.source; import org.telegram.messenger.exoplayer2.C; import org.telegram.messenger.exoplayer2.ExoPlayer; +import org.telegram.messenger.exoplayer2.Player.RepeatMode; import org.telegram.messenger.exoplayer2.Timeline; import org.telegram.messenger.exoplayer2.upstream.Allocator; import org.telegram.messenger.exoplayer2.util.Assertions; @@ -33,6 +34,7 @@ public final class ClippingMediaSource implements MediaSource, MediaSource.Liste private final MediaSource mediaSource; private final long startUs; private final long endUs; + private final boolean enableInitialDiscontinuity; private final ArrayList mediaPeriods; private MediaSource.Listener sourceListener; @@ -46,13 +48,38 @@ public final class ClippingMediaSource implements MediaSource, MediaSource.Liste * start providing samples, in microseconds. * @param endPositionUs The end position within {@code mediaSource}'s timeline at which to stop * providing samples, in microseconds. Specify {@link C#TIME_END_OF_SOURCE} to provide samples - * from the specified start point up to the end of the source. + * from the specified start point up to the end of the source. Specifying a position that + * exceeds the {@code mediaSource}'s duration will also result in the end of the source not + * being clipped. */ public ClippingMediaSource(MediaSource mediaSource, long startPositionUs, long endPositionUs) { + this(mediaSource, startPositionUs, endPositionUs, true); + } + + /** + * Creates a new clipping source that wraps the specified source. + *

    + * If the start point is guaranteed to be a key frame, pass {@code false} to + * {@code enableInitialPositionDiscontinuity} to suppress an initial discontinuity when a period + * is first read from. + * + * @param mediaSource The single-period, non-dynamic source to wrap. + * @param startPositionUs The start position within {@code mediaSource}'s timeline at which to + * start providing samples, in microseconds. + * @param endPositionUs The end position within {@code mediaSource}'s timeline at which to stop + * providing samples, in microseconds. Specify {@link C#TIME_END_OF_SOURCE} to provide samples + * from the specified start point up to the end of the source. Specifying a position that + * exceeds the {@code mediaSource}'s duration will also result in the end of the source not + * being clipped. + * @param enableInitialDiscontinuity Whether the initial discontinuity should be enabled. + */ + public ClippingMediaSource(MediaSource mediaSource, long startPositionUs, long endPositionUs, + boolean enableInitialDiscontinuity) { Assertions.checkArgument(startPositionUs >= 0); this.mediaSource = Assertions.checkNotNull(mediaSource); startUs = startPositionUs; endUs = endPositionUs; + this.enableInitialDiscontinuity = enableInitialDiscontinuity; mediaPeriods = new ArrayList<>(); } @@ -68,9 +95,9 @@ public final class ClippingMediaSource implements MediaSource, MediaSource.Liste } @Override - public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { + public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { ClippingMediaPeriod mediaPeriod = new ClippingMediaPeriod( - mediaSource.createPeriod(index, allocator, startUs + positionUs)); + mediaSource.createPeriod(id, allocator), enableInitialDiscontinuity); mediaPeriods.add(mediaPeriod); mediaPeriod.setClipping(clippingTimeline.startUs, clippingTimeline.endUs); return mediaPeriod; @@ -126,8 +153,10 @@ public final class ClippingMediaSource implements MediaSource, MediaSource.Liste Assertions.checkArgument(!window.isDynamic); long resolvedEndUs = endUs == C.TIME_END_OF_SOURCE ? window.durationUs : endUs; if (window.durationUs != C.TIME_UNSET) { + if (resolvedEndUs > window.durationUs) { + resolvedEndUs = window.durationUs; + } Assertions.checkArgument(startUs == 0 || window.isSeekable); - Assertions.checkArgument(resolvedEndUs <= window.durationUs); Assertions.checkArgument(startUs <= resolvedEndUs); } Period period = timeline.getPeriod(0, new Period()); @@ -142,6 +171,16 @@ public final class ClippingMediaSource implements MediaSource, MediaSource.Liste return 1; } + @Override + public int getNextWindowIndex(int windowIndex, @RepeatMode int repeatMode) { + return timeline.getNextWindowIndex(windowIndex, repeatMode); + } + + @Override + public int getPreviousWindowIndex(int windowIndex, @RepeatMode int repeatMode) { + return timeline.getPreviousWindowIndex(windowIndex, repeatMode); + } + @Override public Window getWindow(int windowIndex, Window window, boolean setIds, long defaultPositionProjectionUs) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/CompositeSequenceableLoader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/CompositeSequenceableLoader.java index 8e53581ca..1221ee690 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/CompositeSequenceableLoader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/CompositeSequenceableLoader.java @@ -29,7 +29,19 @@ public final class CompositeSequenceableLoader implements SequenceableLoader { } @Override - public long getNextLoadPositionUs() { + public final long getBufferedPositionUs() { + long bufferedPositionUs = Long.MAX_VALUE; + for (SequenceableLoader loader : loaders) { + long loaderBufferedPositionUs = loader.getBufferedPositionUs(); + if (loaderBufferedPositionUs != C.TIME_END_OF_SOURCE) { + bufferedPositionUs = Math.min(bufferedPositionUs, loaderBufferedPositionUs); + } + } + return bufferedPositionUs == Long.MAX_VALUE ? C.TIME_END_OF_SOURCE : bufferedPositionUs; + } + + @Override + public final long getNextLoadPositionUs() { long nextLoadPositionUs = Long.MAX_VALUE; for (SequenceableLoader loader : loaders) { long loaderNextLoadPositionUs = loader.getNextLoadPositionUs(); @@ -41,7 +53,7 @@ public final class CompositeSequenceableLoader implements SequenceableLoader { } @Override - public boolean continueLoading(long positionUs) { + public final boolean continueLoading(long positionUs) { boolean madeProgress = false; boolean madeProgressThisIteration; do { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ConcatenatingMediaSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ConcatenatingMediaSource.java index 90b2aadc9..48b2ba426 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ConcatenatingMediaSource.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ConcatenatingMediaSource.java @@ -15,9 +15,9 @@ */ package org.telegram.messenger.exoplayer2.source; -import android.util.Pair; import org.telegram.messenger.exoplayer2.C; import org.telegram.messenger.exoplayer2.ExoPlayer; +import org.telegram.messenger.exoplayer2.Player; import org.telegram.messenger.exoplayer2.Timeline; import org.telegram.messenger.exoplayer2.upstream.Allocator; import org.telegram.messenger.exoplayer2.util.Assertions; @@ -38,6 +38,7 @@ public final class ConcatenatingMediaSource implements MediaSource { private final Object[] manifests; private final Map sourceIndexByMediaPeriod; private final boolean[] duplicateFlags; + private final boolean isRepeatOneAtomic; private Listener listener; private ConcatenatedTimeline timeline; @@ -47,7 +48,21 @@ public final class ConcatenatingMediaSource implements MediaSource { * {@link MediaSource} instance to be present more than once in the array. */ public ConcatenatingMediaSource(MediaSource... mediaSources) { + this(false, mediaSources); + } + + /** + * @param isRepeatOneAtomic Whether the concatenated media source shall be treated as atomic + * (i.e., repeated in its entirety) when repeat mode is set to {@code Player.REPEAT_MODE_ONE}. + * @param mediaSources The {@link MediaSource}s to concatenate. It is valid for the same + * {@link MediaSource} instance to be present more than once in the array. + */ + public ConcatenatingMediaSource(boolean isRepeatOneAtomic, MediaSource... mediaSources) { + for (MediaSource mediaSource : mediaSources) { + Assertions.checkNotNull(mediaSource); + } this.mediaSources = mediaSources; + this.isRepeatOneAtomic = isRepeatOneAtomic; timelines = new Timeline[mediaSources.length]; manifests = new Object[mediaSources.length]; sourceIndexByMediaPeriod = new HashMap<>(); @@ -80,11 +95,11 @@ public final class ConcatenatingMediaSource implements MediaSource { } @Override - public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { - int sourceIndex = timeline.getSourceIndexForPeriod(index); - int periodIndexInSource = index - timeline.getFirstPeriodIndexInSource(sourceIndex); - MediaPeriod mediaPeriod = mediaSources[sourceIndex].createPeriod(periodIndexInSource, allocator, - positionUs); + public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { + int sourceIndex = timeline.getChildIndexByPeriodIndex(id.periodIndex); + MediaPeriodId periodIdInSource = + new MediaPeriodId(id.periodIndex - timeline.getFirstPeriodIndexByChildIndex(sourceIndex)); + MediaPeriod mediaPeriod = mediaSources[sourceIndex].createPeriod(periodIdInSource, allocator); sourceIndexByMediaPeriod.put(mediaPeriod, sourceIndex); return mediaPeriod; } @@ -123,7 +138,7 @@ public final class ConcatenatingMediaSource implements MediaSource { return; } } - timeline = new ConcatenatedTimeline(timelines.clone()); + timeline = new ConcatenatedTimeline(timelines.clone(), isRepeatOneAtomic); listener.onSourceInfoRefreshed(timeline, manifests.clone()); } @@ -144,13 +159,15 @@ public final class ConcatenatingMediaSource implements MediaSource { /** * A {@link Timeline} that is the concatenation of one or more {@link Timeline}s. */ - private static final class ConcatenatedTimeline extends Timeline { + private static final class ConcatenatedTimeline extends AbstractConcatenatedTimeline { private final Timeline[] timelines; private final int[] sourcePeriodOffsets; private final int[] sourceWindowOffsets; + private final boolean isRepeatOneAtomic; - public ConcatenatedTimeline(Timeline[] timelines) { + public ConcatenatedTimeline(Timeline[] timelines, boolean isRepeatOneAtomic) { + super(timelines.length); int[] sourcePeriodOffsets = new int[timelines.length]; int[] sourceWindowOffsets = new int[timelines.length]; long periodCount = 0; @@ -167,6 +184,7 @@ public final class ConcatenatingMediaSource implements MediaSource { this.timelines = timelines; this.sourcePeriodOffsets = sourcePeriodOffsets; this.sourceWindowOffsets = sourceWindowOffsets; + this.isRepeatOneAtomic = isRepeatOneAtomic; } @Override @@ -174,70 +192,63 @@ public final class ConcatenatingMediaSource implements MediaSource { return sourceWindowOffsets[sourceWindowOffsets.length - 1]; } - @Override - public Window getWindow(int windowIndex, Window window, boolean setIds, - long defaultPositionProjectionUs) { - int sourceIndex = getSourceIndexForWindow(windowIndex); - int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex); - int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex); - timelines[sourceIndex].getWindow(windowIndex - firstWindowIndexInSource, window, setIds, - defaultPositionProjectionUs); - window.firstPeriodIndex += firstPeriodIndexInSource; - window.lastPeriodIndex += firstPeriodIndexInSource; - return window; - } - @Override public int getPeriodCount() { return sourcePeriodOffsets[sourcePeriodOffsets.length - 1]; } @Override - public Period getPeriod(int periodIndex, Period period, boolean setIds) { - int sourceIndex = getSourceIndexForPeriod(periodIndex); - int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex); - int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex); - timelines[sourceIndex].getPeriod(periodIndex - firstPeriodIndexInSource, period, setIds); - period.windowIndex += firstWindowIndexInSource; - if (setIds) { - period.uid = Pair.create(sourceIndex, period.uid); + public int getNextWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode) { + if (isRepeatOneAtomic && repeatMode == Player.REPEAT_MODE_ONE) { + repeatMode = Player.REPEAT_MODE_ALL; } - return period; + return super.getNextWindowIndex(windowIndex, repeatMode); } @Override - public int getIndexOfPeriod(Object uid) { - if (!(uid instanceof Pair)) { - return C.INDEX_UNSET; + public int getPreviousWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode) { + if (isRepeatOneAtomic && repeatMode == Player.REPEAT_MODE_ONE) { + repeatMode = Player.REPEAT_MODE_ALL; } - Pair sourceIndexAndPeriodId = (Pair) uid; - if (!(sourceIndexAndPeriodId.first instanceof Integer)) { - return C.INDEX_UNSET; - } - int sourceIndex = (Integer) sourceIndexAndPeriodId.first; - Object periodId = sourceIndexAndPeriodId.second; - if (sourceIndex < 0 || sourceIndex >= timelines.length) { - return C.INDEX_UNSET; - } - int periodIndexInSource = timelines[sourceIndex].getIndexOfPeriod(periodId); - return periodIndexInSource == C.INDEX_UNSET ? C.INDEX_UNSET - : getFirstPeriodIndexInSource(sourceIndex) + periodIndexInSource; + return super.getPreviousWindowIndex(windowIndex, repeatMode); } - private int getSourceIndexForPeriod(int periodIndex) { + @Override + protected int getChildIndexByPeriodIndex(int periodIndex) { return Util.binarySearchFloor(sourcePeriodOffsets, periodIndex, true, false) + 1; } - private int getFirstPeriodIndexInSource(int sourceIndex) { - return sourceIndex == 0 ? 0 : sourcePeriodOffsets[sourceIndex - 1]; - } - - private int getSourceIndexForWindow(int windowIndex) { + @Override + protected int getChildIndexByWindowIndex(int windowIndex) { return Util.binarySearchFloor(sourceWindowOffsets, windowIndex, true, false) + 1; } - private int getFirstWindowIndexInSource(int sourceIndex) { - return sourceIndex == 0 ? 0 : sourceWindowOffsets[sourceIndex - 1]; + @Override + protected int getChildIndexByChildUid(Object childUid) { + if (!(childUid instanceof Integer)) { + return C.INDEX_UNSET; + } + return (Integer) childUid; + } + + @Override + protected Timeline getTimelineByChildIndex(int childIndex) { + return timelines[childIndex]; + } + + @Override + protected int getFirstPeriodIndexByChildIndex(int childIndex) { + return childIndex == 0 ? 0 : sourcePeriodOffsets[childIndex - 1]; + } + + @Override + protected int getFirstWindowIndexByChildIndex(int childIndex) { + return childIndex == 0 ? 0 : sourceWindowOffsets[childIndex - 1]; + } + + @Override + protected Object getChildUidByChildIndex(int childIndex) { + return childIndex; } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/DynamicConcatenatingMediaSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/DynamicConcatenatingMediaSource.java new file mode 100755 index 000000000..e7423134b --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/DynamicConcatenatingMediaSource.java @@ -0,0 +1,639 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source; + +import android.util.Pair; +import android.util.SparseIntArray; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.ExoPlaybackException; +import org.telegram.messenger.exoplayer2.ExoPlayer; +import org.telegram.messenger.exoplayer2.ExoPlayer.ExoPlayerComponent; +import org.telegram.messenger.exoplayer2.ExoPlayer.ExoPlayerMessage; +import org.telegram.messenger.exoplayer2.Timeline; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelection; +import org.telegram.messenger.exoplayer2.upstream.Allocator; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.Util; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; + +/** + * Concatenates multiple {@link MediaSource}s. The list of {@link MediaSource}s can be modified + * during playback. Access to this class is thread-safe. + */ +public final class DynamicConcatenatingMediaSource implements MediaSource, ExoPlayerComponent { + + private static final int MSG_ADD = 0; + private static final int MSG_ADD_MULTIPLE = 1; + private static final int MSG_REMOVE = 2; + private static final int MSG_MOVE = 3; + + // Accessed on the app thread. + private final List mediaSourcesPublic; + + // Accessed on the playback thread. + private final List mediaSourceHolders; + private final MediaSourceHolder query; + private final Map mediaSourceByMediaPeriod; + private final List deferredMediaPeriods; + + private ExoPlayer player; + private Listener listener; + private boolean preventListenerNotification; + private int windowCount; + private int periodCount; + + public DynamicConcatenatingMediaSource() { + this.mediaSourceByMediaPeriod = new IdentityHashMap<>(); + this.mediaSourcesPublic = new ArrayList<>(); + this.mediaSourceHolders = new ArrayList<>(); + this.deferredMediaPeriods = new ArrayList<>(1); + this.query = new MediaSourceHolder(null, null, -1, -1, -1); + } + + /** + * Appends a {@link MediaSource} to the playlist. + * + * @param mediaSource The {@link MediaSource} to be added to the list. + */ + public synchronized void addMediaSource(MediaSource mediaSource) { + addMediaSource(mediaSourcesPublic.size(), mediaSource); + } + + /** + * Adds a {@link MediaSource} to the playlist. + * + * @param index The index at which the new {@link MediaSource} will be inserted. This index must + * be in the range of 0 <= index <= {@link #getSize()}. + * @param mediaSource The {@link MediaSource} to be added to the list. + */ + public synchronized void addMediaSource(int index, MediaSource mediaSource) { + Assertions.checkNotNull(mediaSource); + Assertions.checkArgument(!mediaSourcesPublic.contains(mediaSource)); + mediaSourcesPublic.add(index, mediaSource); + if (player != null) { + player.sendMessages(new ExoPlayerMessage(this, MSG_ADD, Pair.create(index, mediaSource))); + } + } + + /** + * Appends multiple {@link MediaSource}s to the playlist. + * + * @param mediaSources A collection of {@link MediaSource}s to be added to the list. The media + * sources are added in the order in which they appear in this collection. + */ + public synchronized void addMediaSources(Collection mediaSources) { + addMediaSources(mediaSourcesPublic.size(), mediaSources); + } + + /** + * Adds multiple {@link MediaSource}s to the playlist. + * + * @param index The index at which the new {@link MediaSource}s will be inserted. This index must + * be in the range of 0 <= index <= {@link #getSize()}. + * @param mediaSources A collection of {@link MediaSource}s to be added to the list. The media + * sources are added in the order in which they appear in this collection. + */ + public synchronized void addMediaSources(int index, Collection mediaSources) { + for (MediaSource mediaSource : mediaSources) { + Assertions.checkNotNull(mediaSource); + Assertions.checkArgument(!mediaSourcesPublic.contains(mediaSource)); + } + mediaSourcesPublic.addAll(index, mediaSources); + if (player != null && !mediaSources.isEmpty()) { + player.sendMessages(new ExoPlayerMessage(this, MSG_ADD_MULTIPLE, + Pair.create(index, mediaSources))); + } + } + + /** + * Removes a {@link MediaSource} from the playlist. + * + * @param index The index at which the media source will be removed. This index must be in the + * range of 0 <= index < {@link #getSize()}. + */ + public synchronized void removeMediaSource(int index) { + mediaSourcesPublic.remove(index); + if (player != null) { + player.sendMessages(new ExoPlayerMessage(this, MSG_REMOVE, index)); + } + } + + /** + * Moves an existing {@link MediaSource} within the playlist. + * + * @param currentIndex The current index of the media source in the playlist. This index must be + * in the range of 0 <= index < {@link #getSize()}. + * @param newIndex The target index of the media source in the playlist. This index must be in the + * range of 0 <= index < {@link #getSize()}. + */ + public synchronized void moveMediaSource(int currentIndex, int newIndex) { + if (currentIndex == newIndex) { + return; + } + mediaSourcesPublic.add(newIndex, mediaSourcesPublic.remove(currentIndex)); + if (player != null) { + player.sendMessages(new ExoPlayerMessage(this, MSG_MOVE, + Pair.create(currentIndex, newIndex))); + } + } + + /** + * Returns the number of media sources in the playlist. + */ + public synchronized int getSize() { + return mediaSourcesPublic.size(); + } + + /** + * Returns the {@link MediaSource} at a specified index. + * + * @param index A index in the range of 0 <= index <= {@link #getSize()}. + * @return The {@link MediaSource} at this index. + */ + public synchronized MediaSource getMediaSource(int index) { + return mediaSourcesPublic.get(index); + } + + @Override + public synchronized void prepareSource(ExoPlayer player, boolean isTopLevelSource, + Listener listener) { + this.player = player; + this.listener = listener; + preventListenerNotification = true; + addMediaSourcesInternal(0, mediaSourcesPublic); + preventListenerNotification = false; + maybeNotifyListener(); + } + + @Override + public void maybeThrowSourceInfoRefreshError() throws IOException { + for (MediaSourceHolder mediaSourceHolder : mediaSourceHolders) { + mediaSourceHolder.mediaSource.maybeThrowSourceInfoRefreshError(); + } + } + + @Override + public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { + int mediaSourceHolderIndex = findMediaSourceHolderByPeriodIndex(id.periodIndex); + MediaSourceHolder holder = mediaSourceHolders.get(mediaSourceHolderIndex); + MediaPeriodId idInSource = new MediaPeriodId(id.periodIndex - holder.firstPeriodIndexInChild); + MediaPeriod mediaPeriod; + if (!holder.isPrepared) { + mediaPeriod = new DeferredMediaPeriod(holder.mediaSource, idInSource, allocator); + deferredMediaPeriods.add((DeferredMediaPeriod) mediaPeriod); + } else { + mediaPeriod = holder.mediaSource.createPeriod(idInSource, allocator); + } + mediaSourceByMediaPeriod.put(mediaPeriod, holder.mediaSource); + return mediaPeriod; + } + + @Override + public void releasePeriod(MediaPeriod mediaPeriod) { + MediaSource mediaSource = mediaSourceByMediaPeriod.get(mediaPeriod); + mediaSourceByMediaPeriod.remove(mediaPeriod); + if (mediaPeriod instanceof DeferredMediaPeriod) { + deferredMediaPeriods.remove(mediaPeriod); + ((DeferredMediaPeriod) mediaPeriod).releasePeriod(); + } else { + mediaSource.releasePeriod(mediaPeriod); + } + } + + @Override + public void releaseSource() { + for (MediaSourceHolder mediaSourceHolder : mediaSourceHolders) { + mediaSourceHolder.mediaSource.releaseSource(); + } + } + + @Override + @SuppressWarnings("unchecked") + public void handleMessage(int messageType, Object message) throws ExoPlaybackException { + preventListenerNotification = true; + switch (messageType) { + case MSG_ADD: { + Pair messageData = (Pair) message; + addMediaSourceInternal(messageData.first, messageData.second); + break; + } + case MSG_ADD_MULTIPLE: { + Pair> messageData = + (Pair>) message; + addMediaSourcesInternal(messageData.first, messageData.second); + break; + } + case MSG_REMOVE: { + removeMediaSourceInternal((Integer) message); + break; + } + case MSG_MOVE: { + Pair messageData = (Pair) message; + moveMediaSourceInternal(messageData.first, messageData.second); + break; + } + default: { + throw new IllegalStateException(); + } + } + preventListenerNotification = false; + maybeNotifyListener(); + } + + private void maybeNotifyListener() { + if (!preventListenerNotification) { + listener.onSourceInfoRefreshed( + new ConcatenatedTimeline(mediaSourceHolders, windowCount, periodCount), null); + } + } + + private void addMediaSourceInternal(int newIndex, MediaSource newMediaSource) { + final MediaSourceHolder newMediaSourceHolder; + Object newUid = System.identityHashCode(newMediaSource); + DeferredTimeline newTimeline = new DeferredTimeline(); + if (newIndex > 0) { + MediaSourceHolder previousHolder = mediaSourceHolders.get(newIndex - 1); + newMediaSourceHolder = new MediaSourceHolder(newMediaSource, newTimeline, + previousHolder.firstWindowIndexInChild + previousHolder.timeline.getWindowCount(), + previousHolder.firstPeriodIndexInChild + previousHolder.timeline.getPeriodCount(), + newUid); + } else { + newMediaSourceHolder = new MediaSourceHolder(newMediaSource, newTimeline, 0, 0, newUid); + } + correctOffsets(newIndex, newTimeline.getWindowCount(), newTimeline.getPeriodCount()); + mediaSourceHolders.add(newIndex, newMediaSourceHolder); + newMediaSourceHolder.mediaSource.prepareSource(player, false, new Listener() { + @Override + public void onSourceInfoRefreshed(Timeline newTimeline, Object manifest) { + updateMediaSourceInternal(newMediaSourceHolder, newTimeline); + } + }); + } + + private void addMediaSourcesInternal(int index, Collection mediaSources) { + for (MediaSource mediaSource : mediaSources) { + addMediaSourceInternal(index++, mediaSource); + } + } + + private void updateMediaSourceInternal(MediaSourceHolder mediaSourceHolder, Timeline timeline) { + if (mediaSourceHolder == null) { + throw new IllegalArgumentException(); + } + DeferredTimeline deferredTimeline = mediaSourceHolder.timeline; + if (deferredTimeline.getTimeline() == timeline) { + return; + } + int windowOffsetUpdate = timeline.getWindowCount() - deferredTimeline.getWindowCount(); + int periodOffsetUpdate = timeline.getPeriodCount() - deferredTimeline.getPeriodCount(); + if (windowOffsetUpdate != 0 || periodOffsetUpdate != 0) { + int index = findMediaSourceHolderByPeriodIndex(mediaSourceHolder.firstPeriodIndexInChild); + correctOffsets(index + 1, windowOffsetUpdate, periodOffsetUpdate); + } + mediaSourceHolder.timeline = deferredTimeline.cloneWithNewTimeline(timeline); + if (!mediaSourceHolder.isPrepared) { + for (int i = deferredMediaPeriods.size() - 1; i >= 0; i--) { + if (deferredMediaPeriods.get(i).mediaSource == mediaSourceHolder.mediaSource) { + deferredMediaPeriods.get(i).createPeriod(); + deferredMediaPeriods.remove(i); + } + } + } + mediaSourceHolder.isPrepared = true; + maybeNotifyListener(); + } + + private void removeMediaSourceInternal(int index) { + MediaSourceHolder holder = mediaSourceHolders.get(index); + mediaSourceHolders.remove(index); + Timeline oldTimeline = holder.timeline; + correctOffsets(index, -oldTimeline.getWindowCount(), -oldTimeline.getPeriodCount()); + holder.mediaSource.releaseSource(); + } + + private void moveMediaSourceInternal(int currentIndex, int newIndex) { + int startIndex = Math.min(currentIndex, newIndex); + int endIndex = Math.max(currentIndex, newIndex); + int windowOffset = mediaSourceHolders.get(startIndex).firstWindowIndexInChild; + int periodOffset = mediaSourceHolders.get(startIndex).firstPeriodIndexInChild; + mediaSourceHolders.add(newIndex, mediaSourceHolders.remove(currentIndex)); + for (int i = startIndex; i <= endIndex; i++) { + MediaSourceHolder holder = mediaSourceHolders.get(i); + holder.firstWindowIndexInChild = windowOffset; + holder.firstPeriodIndexInChild = periodOffset; + windowOffset += holder.timeline.getWindowCount(); + periodOffset += holder.timeline.getPeriodCount(); + } + } + + private void correctOffsets(int startIndex, int windowOffsetUpdate, int periodOffsetUpdate) { + windowCount += windowOffsetUpdate; + periodCount += periodOffsetUpdate; + for (int i = startIndex; i < mediaSourceHolders.size(); i++) { + mediaSourceHolders.get(i).firstWindowIndexInChild += windowOffsetUpdate; + mediaSourceHolders.get(i).firstPeriodIndexInChild += periodOffsetUpdate; + } + } + + private int findMediaSourceHolderByPeriodIndex(int periodIndex) { + query.firstPeriodIndexInChild = periodIndex; + int index = Collections.binarySearch(mediaSourceHolders, query); + return index >= 0 ? index : -index - 2; + } + + private static final class MediaSourceHolder implements Comparable { + + public final MediaSource mediaSource; + public final Object uid; + + public DeferredTimeline timeline; + public int firstWindowIndexInChild; + public int firstPeriodIndexInChild; + public boolean isPrepared; + + public MediaSourceHolder(MediaSource mediaSource, DeferredTimeline timeline, int window, + int period, Object uid) { + this.mediaSource = mediaSource; + this.timeline = timeline; + this.firstWindowIndexInChild = window; + this.firstPeriodIndexInChild = period; + this.uid = uid; + } + + @Override + public int compareTo(MediaSourceHolder other) { + return this.firstPeriodIndexInChild - other.firstPeriodIndexInChild; + } + } + + private static final class ConcatenatedTimeline extends AbstractConcatenatedTimeline { + + private final int windowCount; + private final int periodCount; + private final int[] firstPeriodInChildIndices; + private final int[] firstWindowInChildIndices; + private final Timeline[] timelines; + private final int[] uids; + private final SparseIntArray childIndexByUid; + + public ConcatenatedTimeline(Collection mediaSourceHolders, int windowCount, + int periodCount) { + super(mediaSourceHolders.size()); + this.windowCount = windowCount; + this.periodCount = periodCount; + int childCount = mediaSourceHolders.size(); + firstPeriodInChildIndices = new int[childCount]; + firstWindowInChildIndices = new int[childCount]; + timelines = new Timeline[childCount]; + uids = new int[childCount]; + childIndexByUid = new SparseIntArray(); + int index = 0; + for (MediaSourceHolder mediaSourceHolder : mediaSourceHolders) { + timelines[index] = mediaSourceHolder.timeline; + firstPeriodInChildIndices[index] = mediaSourceHolder.firstPeriodIndexInChild; + firstWindowInChildIndices[index] = mediaSourceHolder.firstWindowIndexInChild; + uids[index] = (int) mediaSourceHolder.uid; + childIndexByUid.put(uids[index], index++); + } + } + + @Override + protected int getChildIndexByPeriodIndex(int periodIndex) { + return Util.binarySearchFloor(firstPeriodInChildIndices, periodIndex, true, false); + } + + @Override + protected int getChildIndexByWindowIndex(int windowIndex) { + return Util.binarySearchFloor(firstWindowInChildIndices, windowIndex, true, false); + } + + @Override + protected int getChildIndexByChildUid(Object childUid) { + if (!(childUid instanceof Integer)) { + return C.INDEX_UNSET; + } + int index = childIndexByUid.get((int) childUid, -1); + return index == -1 ? C.INDEX_UNSET : index; + } + + @Override + protected Timeline getTimelineByChildIndex(int childIndex) { + return timelines[childIndex]; + } + + @Override + protected int getFirstPeriodIndexByChildIndex(int childIndex) { + return firstPeriodInChildIndices[childIndex]; + } + + @Override + protected int getFirstWindowIndexByChildIndex(int childIndex) { + return firstWindowInChildIndices[childIndex]; + } + + @Override + protected Object getChildUidByChildIndex(int childIndex) { + return uids[childIndex]; + } + + @Override + public int getWindowCount() { + return windowCount; + } + + @Override + public int getPeriodCount() { + return periodCount; + } + + } + + private static final class DeferredTimeline extends Timeline { + + private static final Object DUMMY_ID = new Object(); + private static final Period period = new Period(); + + private final Timeline timeline; + private final Object replacedID; + + public DeferredTimeline() { + timeline = null; + replacedID = null; + } + + private DeferredTimeline(Timeline timeline, Object replacedID) { + this.timeline = timeline; + this.replacedID = replacedID; + } + + public DeferredTimeline cloneWithNewTimeline(Timeline timeline) { + return new DeferredTimeline(timeline, replacedID == null && timeline.getPeriodCount() > 0 + ? timeline.getPeriod(0, period, true).uid : replacedID); + } + + public Timeline getTimeline() { + return timeline; + } + + @Override + public int getWindowCount() { + return timeline == null ? 1 : timeline.getWindowCount(); + } + + @Override + public Window getWindow(int windowIndex, Window window, boolean setIds, + long defaultPositionProjectionUs) { + return timeline == null + // Dynamic window to indicate pending timeline updates. + ? window.set(setIds ? DUMMY_ID : null, C.TIME_UNSET, C.TIME_UNSET, false, true, 0, + C.TIME_UNSET, 0, 0, 0) + : timeline.getWindow(windowIndex, window, setIds, defaultPositionProjectionUs); + } + + @Override + public int getPeriodCount() { + return timeline == null ? 1 : timeline.getPeriodCount(); + } + + @Override + public Period getPeriod(int periodIndex, Period period, boolean setIds) { + if (timeline == null) { + return period.set(setIds ? DUMMY_ID : null, setIds ? DUMMY_ID : null, 0, C.TIME_UNSET, + C.TIME_UNSET); + } + timeline.getPeriod(periodIndex, period, setIds); + if (period.uid == replacedID) { + period.uid = DUMMY_ID; + } + return period; + } + + @Override + public int getIndexOfPeriod(Object uid) { + return timeline == null ? (uid == DUMMY_ID ? 0 : C.INDEX_UNSET) + : timeline.getIndexOfPeriod(uid == DUMMY_ID ? replacedID : uid); + } + + } + + private static final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callback { + + public final MediaSource mediaSource; + + private final MediaPeriodId id; + private final Allocator allocator; + + private MediaPeriod mediaPeriod; + private Callback callback; + private long preparePositionUs; + + public DeferredMediaPeriod(MediaSource mediaSource, MediaPeriodId id, Allocator allocator) { + this.id = id; + this.allocator = allocator; + this.mediaSource = mediaSource; + } + + public void createPeriod() { + mediaPeriod = mediaSource.createPeriod(id, allocator); + if (callback != null) { + mediaPeriod.prepare(this, preparePositionUs); + } + } + + public void releasePeriod() { + if (mediaPeriod != null) { + mediaSource.releasePeriod(mediaPeriod); + } + } + + @Override + public void prepare(Callback callback, long preparePositionUs) { + this.callback = callback; + this.preparePositionUs = preparePositionUs; + if (mediaPeriod != null) { + mediaPeriod.prepare(this, preparePositionUs); + } + } + + @Override + public void maybeThrowPrepareError() throws IOException { + if (mediaPeriod != null) { + mediaPeriod.maybeThrowPrepareError(); + } else { + mediaSource.maybeThrowSourceInfoRefreshError(); + } + } + + @Override + public TrackGroupArray getTrackGroups() { + return mediaPeriod.getTrackGroups(); + } + + @Override + public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, + SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { + return mediaPeriod.selectTracks(selections, mayRetainStreamFlags, streams, streamResetFlags, + positionUs); + } + + @Override + public void discardBuffer(long positionUs) { + mediaPeriod.discardBuffer(positionUs); + } + + @Override + public long readDiscontinuity() { + return mediaPeriod.readDiscontinuity(); + } + + @Override + public long getBufferedPositionUs() { + return mediaPeriod.getBufferedPositionUs(); + } + + @Override + public long seekToUs(long positionUs) { + return mediaPeriod.seekToUs(positionUs); + } + + @Override + public long getNextLoadPositionUs() { + return mediaPeriod.getNextLoadPositionUs(); + } + + @Override + public boolean continueLoading(long positionUs) { + return mediaPeriod != null && mediaPeriod.continueLoading(positionUs); + } + + @Override + public void onContinueLoadingRequested(MediaPeriod source) { + callback.onContinueLoadingRequested(this); + } + + @Override + public void onPrepared(MediaPeriod mediaPeriod) { + callback.onPrepared(this); + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ExtractorMediaPeriod.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ExtractorMediaPeriod.java index f08d05606..8ca4f71a7 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ExtractorMediaPeriod.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ExtractorMediaPeriod.java @@ -17,20 +17,18 @@ package org.telegram.messenger.exoplayer2.source; import android.net.Uri; import android.os.Handler; -import android.util.SparseArray; import org.telegram.messenger.exoplayer2.C; import org.telegram.messenger.exoplayer2.Format; import org.telegram.messenger.exoplayer2.FormatHolder; import org.telegram.messenger.exoplayer2.decoder.DecoderInputBuffer; import org.telegram.messenger.exoplayer2.extractor.DefaultExtractorInput; -import org.telegram.messenger.exoplayer2.extractor.DefaultTrackOutput; -import org.telegram.messenger.exoplayer2.extractor.DefaultTrackOutput.UpstreamFormatChangedListener; import org.telegram.messenger.exoplayer2.extractor.Extractor; import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; import org.telegram.messenger.exoplayer2.extractor.PositionHolder; import org.telegram.messenger.exoplayer2.extractor.SeekMap; import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.source.SampleQueue.UpstreamFormatChangedListener; import org.telegram.messenger.exoplayer2.trackselection.TrackSelection; import org.telegram.messenger.exoplayer2.upstream.Allocator; import org.telegram.messenger.exoplayer2.upstream.DataSource; @@ -43,12 +41,29 @@ import org.telegram.messenger.exoplayer2.util.MimeTypes; import org.telegram.messenger.exoplayer2.util.Util; import java.io.EOFException; import java.io.IOException; +import java.util.Arrays; /** * A {@link MediaPeriod} that extracts data using an {@link Extractor}. */ /* package */ final class ExtractorMediaPeriod implements MediaPeriod, ExtractorOutput, - Loader.Callback, UpstreamFormatChangedListener { + Loader.Callback, Loader.ReleaseCallback, + UpstreamFormatChangedListener { + + /** + * Listener for information about the period. + */ + interface Listener { + + /** + * Called when the duration or ability to seek within the period changes. + * + * @param durationUs The duration of the period, or {@link C#TIME_UNSET}. + * @param isSeekable Whether the period is seekable. + */ + void onSourceInfoRefreshed(long durationUs, boolean isSeekable); + + } /** * When the source's duration is unknown, it is calculated by adding this value to the largest @@ -61,24 +76,26 @@ import java.io.IOException; private final int minLoadableRetryCount; private final Handler eventHandler; private final ExtractorMediaSource.EventListener eventListener; - private final MediaSource.Listener sourceListener; + private final Listener listener; private final Allocator allocator; private final String customCacheKey; + private final long continueLoadingCheckIntervalBytes; private final Loader loader; private final ExtractorHolder extractorHolder; private final ConditionVariable loadCondition; private final Runnable maybeFinishPrepareRunnable; private final Runnable onContinueLoadingRequestedRunnable; private final Handler handler; - private final SparseArray sampleQueues; private Callback callback; private SeekMap seekMap; - private boolean tracksBuilt; + private SampleQueue[] sampleQueues; + private int[] sampleQueueTrackIds; + private boolean sampleQueuesBuilt; private boolean prepared; private boolean seenFirstTrackSelection; - private boolean notifyReset; + private boolean notifyDiscontinuity; private int enabledTrackCount; private TrackGroupArray tracks; private long durationUs; @@ -101,23 +118,26 @@ import java.io.IOException; * @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs. * @param eventHandler A handler for events. May be null if delivery of events is not required. * @param eventListener A listener of events. May be null if delivery of events is not required. - * @param sourceListener A listener to notify when the timeline has been loaded. + * @param listener A listener to notify when information about the period changes. * @param allocator An {@link Allocator} from which to obtain media buffer allocations. * @param customCacheKey A custom key that uniquely identifies the original stream. Used for cache * indexing. May be null. + * @param continueLoadingCheckIntervalBytes The number of bytes that should be loaded between each + * invocation of {@link Callback#onContinueLoadingRequested(SequenceableLoader)}. */ public ExtractorMediaPeriod(Uri uri, DataSource dataSource, Extractor[] extractors, int minLoadableRetryCount, Handler eventHandler, - ExtractorMediaSource.EventListener eventListener, MediaSource.Listener sourceListener, - Allocator allocator, String customCacheKey) { + ExtractorMediaSource.EventListener eventListener, Listener listener, + Allocator allocator, String customCacheKey, int continueLoadingCheckIntervalBytes) { this.uri = uri; this.dataSource = dataSource; this.minLoadableRetryCount = minLoadableRetryCount; this.eventHandler = eventHandler; this.eventListener = eventListener; - this.sourceListener = sourceListener; + this.listener = listener; this.allocator = allocator; this.customCacheKey = customCacheKey; + this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; loader = new Loader("Loader:ExtractorMediaPeriod"); extractorHolder = new ExtractorHolder(extractors, this); loadCondition = new ConditionVariable(); @@ -136,30 +156,35 @@ import java.io.IOException; } }; handler = new Handler(); - + sampleQueueTrackIds = new int[0]; + sampleQueues = new SampleQueue[0]; pendingResetPositionUs = C.TIME_UNSET; - sampleQueues = new SparseArray<>(); length = C.LENGTH_UNSET; } public void release() { - final ExtractorHolder extractorHolder = this.extractorHolder; - loader.release(new Runnable() { - @Override - public void run() { - extractorHolder.release(); - int trackCount = sampleQueues.size(); - for (int i = 0; i < trackCount; i++) { - sampleQueues.valueAt(i).disable(); - } + boolean releasedSynchronously = loader.release(this); + if (prepared && !releasedSynchronously) { + // Discard as much as we can synchronously. We only do this if we're prepared, since otherwise + // sampleQueues may still be being modified by the loading thread. + for (SampleQueue sampleQueue : sampleQueues) { + sampleQueue.discardToEnd(); } - }); + } handler.removeCallbacksAndMessages(null); released = true; } @Override - public void prepare(Callback callback) { + public void onLoaderReleased() { + extractorHolder.release(); + for (SampleQueue sampleQueue : sampleQueues) { + sampleQueue.reset(); + } + } + + @Override + public void prepare(Callback callback, long positionUs) { this.callback = callback; loadCondition.open(); startLoading(); @@ -179,19 +204,21 @@ import java.io.IOException; public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { Assertions.checkState(prepared); - // Disable old tracks. + int oldEnabledTrackCount = enabledTrackCount; + // Deselect old tracks. for (int i = 0; i < selections.length; i++) { if (streams[i] != null && (selections[i] == null || !mayRetainStreamFlags[i])) { int track = ((SampleStreamImpl) streams[i]).track; Assertions.checkState(trackEnabledStates[track]); enabledTrackCount--; trackEnabledStates[track] = false; - sampleQueues.valueAt(track).disable(); streams[i] = null; } } - // Enable new tracks. - boolean selectedNewTracks = false; + // We'll always need to seek if this is a first selection to a non-zero position, or if we're + // making a selection having previously disabled all tracks. + boolean seekRequired = seenFirstTrackSelection ? oldEnabledTrackCount == 0 : positionUs != 0; + // Select new tracks. for (int i = 0; i < selections.length; i++) { if (streams[i] == null && selections[i] != null) { TrackSelection selection = selections[i]; @@ -203,25 +230,33 @@ import java.io.IOException; trackEnabledStates[track] = true; streams[i] = new SampleStreamImpl(track); streamResetFlags[i] = true; - selectedNewTracks = true; - } - } - if (!seenFirstTrackSelection) { - // At the time of the first track selection all queues will be enabled, so we need to disable - // any that are no longer required. - int trackCount = sampleQueues.size(); - for (int i = 0; i < trackCount; i++) { - if (!trackEnabledStates[i]) { - sampleQueues.valueAt(i).disable(); + // If there's still a chance of avoiding a seek, try and seek within the sample queue. + if (!seekRequired) { + SampleQueue sampleQueue = sampleQueues[track]; + sampleQueue.rewind(); + // A seek can be avoided if we're able to advance to the current playback position in the + // sample queue, or if we haven't read anything from the queue since the previous seek + // (this case is common for sparse tracks such as metadata tracks). In all other cases a + // seek is required. + seekRequired = !sampleQueue.advanceTo(positionUs, true, true) + && sampleQueue.getReadIndex() != 0; } } } if (enabledTrackCount == 0) { - notifyReset = false; + notifyDiscontinuity = false; if (loader.isLoading()) { + // Discard as much as we can synchronously. + for (SampleQueue sampleQueue : sampleQueues) { + sampleQueue.discardToEnd(); + } loader.cancelLoading(); + } else { + for (SampleQueue sampleQueue : sampleQueues) { + sampleQueue.reset(); + } } - } else if (seenFirstTrackSelection ? selectedNewTracks : positionUs != 0) { + } else if (seekRequired) { positionUs = seekToUs(positionUs); // We'll need to reset renderers consuming from all streams due to the seek. for (int i = 0; i < streams.length; i++) { @@ -236,7 +271,10 @@ import java.io.IOException; @Override public void discardBuffer(long positionUs) { - // Do nothing. + int trackCount = sampleQueues.length; + for (int i = 0; i < trackCount; i++) { + sampleQueues[i].discardTo(positionUs, false, trackEnabledStates[i]); + } } @Override @@ -259,8 +297,8 @@ import java.io.IOException; @Override public long readDiscontinuity() { - if (notifyReset) { - notifyReset = false; + if (notifyDiscontinuity) { + notifyDiscontinuity = false; return lastSeekPositionUs; } return C.TIME_UNSET; @@ -277,11 +315,11 @@ import java.io.IOException; if (haveAudioVideoTracks) { // Ignore non-AV tracks, which may be sparse or poorly interleaved. largestQueuedTimestampUs = Long.MAX_VALUE; - int trackCount = sampleQueues.size(); + int trackCount = sampleQueues.length; for (int i = 0; i < trackCount; i++) { if (trackIsAudioVideoFlags[i]) { largestQueuedTimestampUs = Math.min(largestQueuedTimestampUs, - sampleQueues.valueAt(i).getLargestQueuedTimestampUs()); + sampleQueues[i].getLargestQueuedTimestampUs()); } } } else { @@ -296,34 +334,28 @@ import java.io.IOException; // Treat all seeks into non-seekable media as being to t=0. positionUs = seekMap.isSeekable() ? positionUs : 0; lastSeekPositionUs = positionUs; - int trackCount = sampleQueues.size(); - // If we're not pending a reset, see if we can seek within the sample queues. - boolean seekInsideBuffer = !isPendingReset(); - for (int i = 0; seekInsideBuffer && i < trackCount; i++) { - if (trackEnabledStates[i]) { - seekInsideBuffer = sampleQueues.valueAt(i).skipToKeyframeBefore(positionUs, false); + notifyDiscontinuity = false; + // If we're not pending a reset, see if we can seek within the buffer. + if (!isPendingReset() && seekInsideBufferUs(positionUs)) { + return positionUs; + } + // We were unable to seek within the buffer, so need to reset. + pendingResetPositionUs = positionUs; + loadingFinished = false; + if (loader.isLoading()) { + loader.cancelLoading(); + } else { + for (SampleQueue sampleQueue : sampleQueues) { + sampleQueue.reset(); } } - // If we failed to seek within the sample queues, we need to restart. - if (!seekInsideBuffer) { - pendingResetPositionUs = positionUs; - loadingFinished = false; - if (loader.isLoading()) { - loader.cancelLoading(); - } else { - for (int i = 0; i < trackCount; i++) { - sampleQueues.valueAt(i).reset(trackEnabledStates[i]); - } - } - } - notifyReset = false; return positionUs; } // SampleStream methods. /* package */ boolean isReady(int track) { - return loadingFinished || (!isPendingReset() && !sampleQueues.valueAt(track).isEmpty()); + return loadingFinished || (!isPendingReset() && sampleQueues[track].hasNextSample()); } /* package */ void maybeThrowError() throws IOException { @@ -332,20 +364,19 @@ import java.io.IOException; /* package */ int readData(int track, FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired) { - if (notifyReset || isPendingReset()) { + if (notifyDiscontinuity || isPendingReset()) { return C.RESULT_NOTHING_READ; } - - return sampleQueues.valueAt(track).readData(formatHolder, buffer, formatRequired, - loadingFinished, lastSeekPositionUs); + return sampleQueues[track].read(formatHolder, buffer, formatRequired, loadingFinished, + lastSeekPositionUs); } /* package */ void skipData(int track, long positionUs) { - DefaultTrackOutput sampleQueue = sampleQueues.valueAt(track); + SampleQueue sampleQueue = sampleQueues[track]; if (loadingFinished && positionUs > sampleQueue.getLargestQueuedTimestampUs()) { - sampleQueue.skipAll(); + sampleQueue.advanceToEnd(); } else { - sampleQueue.skipToKeyframeBefore(positionUs, true); + sampleQueue.advanceTo(positionUs, true, true); } } @@ -360,8 +391,7 @@ import java.io.IOException; long largestQueuedTimestampUs = getLargestQueuedTimestampUs(); durationUs = largestQueuedTimestampUs == Long.MIN_VALUE ? 0 : largestQueuedTimestampUs + DEFAULT_LAST_SAMPLE_DURATION_US; - sourceListener.onSourceInfoRefreshed( - new SinglePeriodTimeline(durationUs, seekMap.isSeekable()), null); + listener.onSourceInfoRefreshed(durationUs, seekMap.isSeekable()); } callback.onContinueLoadingRequested(this); } @@ -369,12 +399,14 @@ import java.io.IOException; @Override public void onLoadCanceled(ExtractingLoadable loadable, long elapsedRealtimeMs, long loadDurationMs, boolean released) { + if (released) { + return; + } copyLengthFromLoader(loadable); - if (!released && enabledTrackCount > 0) { - int trackCount = sampleQueues.size(); - for (int i = 0; i < trackCount; i++) { - sampleQueues.valueAt(i).reset(trackEnabledStates[i]); - } + for (SampleQueue sampleQueue : sampleQueues) { + sampleQueue.reset(); + } + if (enabledTrackCount > 0) { callback.onContinueLoadingRequested(this); } } @@ -398,18 +430,24 @@ import java.io.IOException; @Override public TrackOutput track(int id, int type) { - DefaultTrackOutput trackOutput = sampleQueues.get(id); - if (trackOutput == null) { - trackOutput = new DefaultTrackOutput(allocator); - trackOutput.setUpstreamFormatChangeListener(this); - sampleQueues.put(id, trackOutput); + int trackCount = sampleQueues.length; + for (int i = 0; i < trackCount; i++) { + if (sampleQueueTrackIds[i] == id) { + return sampleQueues[i]; + } } + SampleQueue trackOutput = new SampleQueue(allocator); + trackOutput.setUpstreamFormatChangeListener(this); + sampleQueueTrackIds = Arrays.copyOf(sampleQueueTrackIds, trackCount + 1); + sampleQueueTrackIds[trackCount] = id; + sampleQueues = Arrays.copyOf(sampleQueues, trackCount + 1); + sampleQueues[trackCount] = trackOutput; return trackOutput; } @Override public void endTracks() { - tracksBuilt = true; + sampleQueuesBuilt = true; handler.post(maybeFinishPrepareRunnable); } @@ -429,22 +467,22 @@ import java.io.IOException; // Internal methods. private void maybeFinishPrepare() { - if (released || prepared || seekMap == null || !tracksBuilt) { + if (released || prepared || seekMap == null || !sampleQueuesBuilt) { return; } - int trackCount = sampleQueues.size(); - for (int i = 0; i < trackCount; i++) { - if (sampleQueues.valueAt(i).getUpstreamFormat() == null) { + for (SampleQueue sampleQueue : sampleQueues) { + if (sampleQueue.getUpstreamFormat() == null) { return; } } loadCondition.close(); + int trackCount = sampleQueues.length; TrackGroup[] trackArray = new TrackGroup[trackCount]; trackIsAudioVideoFlags = new boolean[trackCount]; trackEnabledStates = new boolean[trackCount]; durationUs = seekMap.getDurationUs(); for (int i = 0; i < trackCount; i++) { - Format trackFormat = sampleQueues.valueAt(i).getUpstreamFormat(); + Format trackFormat = sampleQueues[i].getUpstreamFormat(); trackArray[i] = new TrackGroup(trackFormat); String mimeType = trackFormat.sampleMimeType; boolean isAudioVideo = MimeTypes.isVideo(mimeType) || MimeTypes.isAudio(mimeType); @@ -453,8 +491,7 @@ import java.io.IOException; } tracks = new TrackGroupArray(trackArray); prepared = true; - sourceListener.onSourceInfoRefreshed( - new SinglePeriodTimeline(durationUs, seekMap.isSeekable()), null); + listener.onSourceInfoRefreshed(durationUs, seekMap.isSeekable()); callback.onPrepared(this); } @@ -502,30 +539,51 @@ import java.io.IOException; // previous load finished, so it's necessary to load from the start whenever commencing // a new load. lastSeekPositionUs = 0; - notifyReset = prepared; - int trackCount = sampleQueues.size(); - for (int i = 0; i < trackCount; i++) { - sampleQueues.valueAt(i).reset(!prepared || trackEnabledStates[i]); + notifyDiscontinuity = prepared; + for (SampleQueue sampleQueue : sampleQueues) { + sampleQueue.reset(); } loadable.setLoadPosition(0, 0); } } + /** + * Attempts to seek to the specified position within the sample queues. + * + * @param positionUs The seek position in microseconds. + * @return Whether the in-buffer seek was successful. + */ + private boolean seekInsideBufferUs(long positionUs) { + int trackCount = sampleQueues.length; + for (int i = 0; i < trackCount; i++) { + SampleQueue sampleQueue = sampleQueues[i]; + sampleQueue.rewind(); + boolean seekInsideQueue = sampleQueue.advanceTo(positionUs, true, false); + // If we have AV tracks then an in-buffer seek is successful if the seek into every AV queue + // is successful. We ignore whether seeks within non-AV queues are successful in this case, as + // they may be sparse or poorly interleaved. If we only have non-AV tracks then a seek is + // successful only if the seek into every queue succeeds. + if (!seekInsideQueue && (trackIsAudioVideoFlags[i] || !haveAudioVideoTracks)) { + return false; + } + sampleQueue.discardToRead(); + } + return true; + } + private int getExtractedSamplesCount() { int extractedSamplesCount = 0; - int trackCount = sampleQueues.size(); - for (int i = 0; i < trackCount; i++) { - extractedSamplesCount += sampleQueues.valueAt(i).getWriteIndex(); + for (SampleQueue sampleQueue : sampleQueues) { + extractedSamplesCount += sampleQueue.getWriteIndex(); } return extractedSamplesCount; } private long getLargestQueuedTimestampUs() { long largestQueuedTimestampUs = Long.MIN_VALUE; - int trackCount = sampleQueues.size(); - for (int i = 0; i < trackCount; i++) { + for (SampleQueue sampleQueue : sampleQueues) { largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs, - sampleQueues.valueAt(i).getLargestQueuedTimestampUs()); + sampleQueue.getLargestQueuedTimestampUs()); } return largestQueuedTimestampUs; } @@ -585,12 +643,6 @@ import java.io.IOException; */ /* package */ final class ExtractingLoadable implements Loadable { - /** - * The number of bytes that should be loaded between each each invocation of - * {@link Callback#onContinueLoadingRequested(SequenceableLoader)}. - */ - private static final int CONTINUE_LOADING_CHECK_INTERVAL_BYTES = 1024 * 1024; - private final Uri uri; private final DataSource dataSource; private final ExtractorHolder extractorHolder; @@ -650,7 +702,7 @@ import java.io.IOException; while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { loadCondition.block(); result = extractor.read(input, positionHolder); - if (input.getPosition() > position + CONTINUE_LOADING_CHECK_INTERVAL_BYTES) { + if (input.getPosition() > position + continueLoadingCheckIntervalBytes) { position = input.getPosition(); loadCondition.close(); handler.post(onContinueLoadingRequestedRunnable); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ExtractorMediaSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ExtractorMediaSource.java index 51c5a4d83..eefebca47 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ExtractorMediaSource.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ExtractorMediaSource.java @@ -39,7 +39,7 @@ import java.io.IOException; *

    * Note that the built-in extractors for AAC, MPEG PS/TS and FLV streams do not support seeking. */ -public final class ExtractorMediaSource implements MediaSource, MediaSource.Listener { +public final class ExtractorMediaSource implements MediaSource, ExtractorMediaPeriod.Listener { /** * Listener of {@link ExtractorMediaSource} events. @@ -72,6 +72,12 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List */ public static final int MIN_RETRY_COUNT_DEFAULT_FOR_MEDIA = -1; + /** + * The default number of bytes that should be loaded between each each invocation of + * {@link MediaPeriod.Callback#onContinueLoadingRequested(SequenceableLoader)}. + */ + public static final int DEFAULT_LOADING_CHECK_INTERVAL_BYTES = 1024 * 1024; + private final Uri uri; private final DataSource.Factory dataSourceFactory; private final ExtractorsFactory extractorsFactory; @@ -80,10 +86,11 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List private final EventListener eventListener; private final Timeline.Period period; private final String customCacheKey; + private final int continueLoadingCheckIntervalBytes; private MediaSource.Listener sourceListener; - private Timeline timeline; - private boolean timelineHasDuration; + private long timelineDurationUs; + private boolean timelineIsSeekable; /** * @param uri The {@link Uri} of the media stream. @@ -96,8 +103,7 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List */ public ExtractorMediaSource(Uri uri, DataSource.Factory dataSourceFactory, ExtractorsFactory extractorsFactory, Handler eventHandler, EventListener eventListener) { - this(uri, dataSourceFactory, extractorsFactory, MIN_RETRY_COUNT_DEFAULT_FOR_MEDIA, eventHandler, - eventListener, null); + this(uri, dataSourceFactory, extractorsFactory, eventHandler, eventListener, null); } /** @@ -115,7 +121,7 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List ExtractorsFactory extractorsFactory, Handler eventHandler, EventListener eventListener, String customCacheKey) { this(uri, dataSourceFactory, extractorsFactory, MIN_RETRY_COUNT_DEFAULT_FOR_MEDIA, eventHandler, - eventListener, customCacheKey); + eventListener, customCacheKey, DEFAULT_LOADING_CHECK_INTERVAL_BYTES); } /** @@ -129,10 +135,12 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List * @param eventListener A listener of events. May be null if delivery of events is not required. * @param customCacheKey A custom key that uniquely identifies the original stream. Used for cache * indexing. May be null. + * @param continueLoadingCheckIntervalBytes The number of bytes that should be loaded between each + * invocation of {@link MediaPeriod.Callback#onContinueLoadingRequested(SequenceableLoader)}. */ public ExtractorMediaSource(Uri uri, DataSource.Factory dataSourceFactory, ExtractorsFactory extractorsFactory, int minLoadableRetryCount, Handler eventHandler, - EventListener eventListener, String customCacheKey) { + EventListener eventListener, String customCacheKey, int continueLoadingCheckIntervalBytes) { this.uri = uri; this.dataSourceFactory = dataSourceFactory; this.extractorsFactory = extractorsFactory; @@ -140,14 +148,14 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List this.eventHandler = eventHandler; this.eventListener = eventListener; this.customCacheKey = customCacheKey; + this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; period = new Timeline.Period(); } @Override public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) { sourceListener = listener; - timeline = new SinglePeriodTimeline(C.TIME_UNSET, false); - listener.onSourceInfoRefreshed(timeline, null); + notifySourceInfoRefreshed(C.TIME_UNSET, false); } @Override @@ -156,11 +164,11 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List } @Override - public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { - Assertions.checkArgument(index == 0); + public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { + Assertions.checkArgument(id.periodIndex == 0); return new ExtractorMediaPeriod(uri, dataSourceFactory.createDataSource(), extractorsFactory.createExtractors(), minLoadableRetryCount, eventHandler, eventListener, - this, allocator, customCacheKey); + this, allocator, customCacheKey, continueLoadingCheckIntervalBytes); } @Override @@ -173,19 +181,27 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List sourceListener = null; } - // MediaSource.Listener implementation. + // ExtractorMediaPeriod.Listener implementation. @Override - public void onSourceInfoRefreshed(Timeline newTimeline, Object manifest) { - long newTimelineDurationUs = newTimeline.getPeriod(0, period).getDurationUs(); - boolean newTimelineHasDuration = newTimelineDurationUs != C.TIME_UNSET; - if (timelineHasDuration && !newTimelineHasDuration) { - // Suppress source info changes that would make the duration unknown when it is already known. + public void onSourceInfoRefreshed(long durationUs, boolean isSeekable) { + // If we already have the duration from a previous source info refresh, use it. + durationUs = durationUs == C.TIME_UNSET ? timelineDurationUs : durationUs; + if (timelineDurationUs == durationUs && timelineIsSeekable == isSeekable + || (timelineDurationUs != C.TIME_UNSET && durationUs == C.TIME_UNSET)) { + // Suppress no-op source info changes. return; } - timeline = newTimeline; - timelineHasDuration = newTimelineHasDuration; - sourceListener.onSourceInfoRefreshed(timeline, null); + notifySourceInfoRefreshed(durationUs, isSeekable); + } + + // Internal methods. + + private void notifySourceInfoRefreshed(long durationUs, boolean isSeekable) { + timelineDurationUs = durationUs; + timelineIsSeekable = isSeekable; + sourceListener.onSourceInfoRefreshed( + new SinglePeriodTimeline(timelineDurationUs, timelineIsSeekable), null); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/LoopingMediaSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/LoopingMediaSource.java index 9ceefe7c3..19c467a1d 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/LoopingMediaSource.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/LoopingMediaSource.java @@ -15,36 +15,30 @@ */ package org.telegram.messenger.exoplayer2.source; -import android.util.Log; -import android.util.Pair; import org.telegram.messenger.exoplayer2.C; import org.telegram.messenger.exoplayer2.ExoPlayer; +import org.telegram.messenger.exoplayer2.Player; import org.telegram.messenger.exoplayer2.Timeline; import org.telegram.messenger.exoplayer2.upstream.Allocator; import org.telegram.messenger.exoplayer2.util.Assertions; import java.io.IOException; /** - * Loops a {@link MediaSource}. + * Loops a {@link MediaSource} a specified number of times. + *

    + * Note: To loop a {@link MediaSource} indefinitely, it is usually better to use + * {@link ExoPlayer#setRepeatMode(int)}. */ public final class LoopingMediaSource implements MediaSource { - /** - * The maximum number of periods that can be exposed by the source. The value of this constant is - * large enough to cause indefinite looping in practice (the total duration of the looping source - * will be approximately five years if the duration of each period is one second). - */ - public static final int MAX_EXPOSED_PERIODS = 157680000; - - private static final String TAG = "LoopingMediaSource"; - private final MediaSource childSource; private final int loopCount; private int childPeriodCount; /** - * Loops the provided source indefinitely. + * Loops the provided source indefinitely. Note that it is usually better to use + * {@link ExoPlayer#setRepeatMode(int)}. * * @param childSource The {@link MediaSource} to loop. */ @@ -56,9 +50,7 @@ public final class LoopingMediaSource implements MediaSource { * Loops the provided source a specified number of times. * * @param childSource The {@link MediaSource} to loop. - * @param loopCount The desired number of loops. Must be strictly positive. The actual number of - * loops will be capped at the maximum that can achieved without causing the number of - * periods exposed by the source to exceed {@link #MAX_EXPOSED_PERIODS}. + * @param loopCount The desired number of loops. Must be strictly positive. */ public LoopingMediaSource(MediaSource childSource, int loopCount) { Assertions.checkArgument(loopCount > 0); @@ -72,7 +64,9 @@ public final class LoopingMediaSource implements MediaSource { @Override public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { childPeriodCount = timeline.getPeriodCount(); - listener.onSourceInfoRefreshed(new LoopingTimeline(timeline, loopCount), manifest); + Timeline loopingTimeline = loopCount != Integer.MAX_VALUE + ? new LoopingTimeline(timeline, loopCount) : new InfinitelyLoopingTimeline(timeline); + listener.onSourceInfoRefreshed(loopingTimeline, manifest); } }); } @@ -83,8 +77,10 @@ public final class LoopingMediaSource implements MediaSource { } @Override - public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { - return childSource.createPeriod(index % childPeriodCount, allocator, positionUs); + public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { + return loopCount != Integer.MAX_VALUE + ? childSource.createPeriod(new MediaPeriodId(id.periodIndex % childPeriodCount), allocator) + : childSource.createPeriod(id, allocator); } @Override @@ -97,7 +93,7 @@ public final class LoopingMediaSource implements MediaSource { childSource.releaseSource(); } - private static final class LoopingTimeline extends Timeline { + private static final class LoopingTimeline extends AbstractConcatenatedTimeline { private final Timeline childTimeline; private final int childPeriodCount; @@ -105,20 +101,13 @@ public final class LoopingMediaSource implements MediaSource { private final int loopCount; public LoopingTimeline(Timeline childTimeline, int loopCount) { + super(loopCount); this.childTimeline = childTimeline; childPeriodCount = childTimeline.getPeriodCount(); childWindowCount = childTimeline.getWindowCount(); - // This is the maximum number of loops that can be performed without exceeding - // MAX_EXPOSED_PERIODS periods. - int maxLoopCount = MAX_EXPOSED_PERIODS / childPeriodCount; - if (loopCount > maxLoopCount) { - if (loopCount != Integer.MAX_VALUE) { - Log.w(TAG, "Capped loops to avoid overflow: " + loopCount + " -> " + maxLoopCount); - } - this.loopCount = maxLoopCount; - } else { - this.loopCount = loopCount; - } + this.loopCount = loopCount; + Assertions.checkState(loopCount <= Integer.MAX_VALUE / childPeriodCount, + "LoopingMediaSource contains too many periods"); } @Override @@ -126,45 +115,96 @@ public final class LoopingMediaSource implements MediaSource { return childWindowCount * loopCount; } - @Override - public Window getWindow(int windowIndex, Window window, boolean setIds, - long defaultPositionProjectionUs) { - childTimeline.getWindow(windowIndex % childWindowCount, window, setIds, - defaultPositionProjectionUs); - int periodIndexOffset = (windowIndex / childWindowCount) * childPeriodCount; - window.firstPeriodIndex += periodIndexOffset; - window.lastPeriodIndex += periodIndexOffset; - return window; - } - @Override public int getPeriodCount() { return childPeriodCount * loopCount; } @Override - public Period getPeriod(int periodIndex, Period period, boolean setIds) { - childTimeline.getPeriod(periodIndex % childPeriodCount, period, setIds); - int loopCount = (periodIndex / childPeriodCount); - period.windowIndex += loopCount * childWindowCount; - if (setIds) { - period.uid = Pair.create(loopCount, period.uid); + protected int getChildIndexByPeriodIndex(int periodIndex) { + return periodIndex / childPeriodCount; + } + + @Override + protected int getChildIndexByWindowIndex(int windowIndex) { + return windowIndex / childWindowCount; + } + + @Override + protected int getChildIndexByChildUid(Object childUid) { + if (!(childUid instanceof Integer)) { + return C.INDEX_UNSET; } - return period; + return (Integer) childUid; + } + + @Override + protected Timeline getTimelineByChildIndex(int childIndex) { + return childTimeline; + } + + @Override + protected int getFirstPeriodIndexByChildIndex(int childIndex) { + return childIndex * childPeriodCount; + } + + @Override + protected int getFirstWindowIndexByChildIndex(int childIndex) { + return childIndex * childWindowCount; + } + + @Override + protected Object getChildUidByChildIndex(int childIndex) { + return childIndex; + } + + } + + private static final class InfinitelyLoopingTimeline extends Timeline { + + private final Timeline childTimeline; + + public InfinitelyLoopingTimeline(Timeline childTimeline) { + this.childTimeline = childTimeline; + } + + @Override + public int getWindowCount() { + return childTimeline.getWindowCount(); + } + + @Override + public int getNextWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode) { + int childNextWindowIndex = childTimeline.getNextWindowIndex(windowIndex, repeatMode); + return childNextWindowIndex == C.INDEX_UNSET ? 0 : childNextWindowIndex; + } + + @Override + public int getPreviousWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode) { + int childPreviousWindowIndex = childTimeline.getPreviousWindowIndex(windowIndex, repeatMode); + return childPreviousWindowIndex == C.INDEX_UNSET ? getWindowCount() - 1 + : childPreviousWindowIndex; + } + + @Override + public Window getWindow(int windowIndex, Window window, boolean setIds, + long defaultPositionProjectionUs) { + return childTimeline.getWindow(windowIndex, window, setIds, defaultPositionProjectionUs); + } + + @Override + public int getPeriodCount() { + return childTimeline.getPeriodCount(); + } + + @Override + public Period getPeriod(int periodIndex, Period period, boolean setIds) { + return childTimeline.getPeriod(periodIndex, period, setIds); } @Override public int getIndexOfPeriod(Object uid) { - if (!(uid instanceof Pair)) { - return C.INDEX_UNSET; - } - Pair loopCountAndChildUid = (Pair) uid; - if (!(loopCountAndChildUid.first instanceof Integer)) { - return C.INDEX_UNSET; - } - int loopCount = (Integer) loopCountAndChildUid.first; - int periodIndexOffset = loopCount * childPeriodCount; - return childTimeline.getIndexOfPeriod(loopCountAndChildUid.second) + periodIndexOffset; + return childTimeline.getIndexOfPeriod(uid); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/MediaPeriod.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/MediaPeriod.java index 89558cc85..e2b473362 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/MediaPeriod.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/MediaPeriod.java @@ -16,12 +16,15 @@ package org.telegram.messenger.exoplayer2.source; import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.ExoPlayer; import org.telegram.messenger.exoplayer2.Timeline; import org.telegram.messenger.exoplayer2.trackselection.TrackSelection; import java.io.IOException; /** - * A source of a single period of media. + * Loads media corresponding to a {@link Timeline.Period}, and allows that media to be read. All + * methods are called on the player's internal playback thread, as described in the + * {@link ExoPlayer} Javadoc. */ public interface MediaPeriod extends SequenceableLoader { @@ -55,8 +58,10 @@ public interface MediaPeriod extends SequenceableLoader { * * @param callback Callback to receive updates from this period, including being notified when * preparation completes. + * @param positionUs The position in microseconds relative to the start of the period at which to + * start loading data. */ - void prepare(Callback callback); + void prepare(Callback callback, long positionUs); /** * Throws an error that's preventing the period from becoming prepared. Does nothing if no such @@ -106,6 +111,8 @@ public interface MediaPeriod extends SequenceableLoader { /** * Discards buffered media up to the specified position. + *

    + * This method should only be called after the period has been prepared. * * @param positionUs The position in microseconds. */ @@ -116,22 +123,14 @@ public interface MediaPeriod extends SequenceableLoader { *

    * After this method has returned a value other than {@link C#TIME_UNSET}, all * {@link SampleStream}s provided by the period are guaranteed to start from a key frame. + *

    + * This method should only be called after the period has been prepared. * * @return If a discontinuity was read then the playback position in microseconds after the * discontinuity. Else {@link C#TIME_UNSET}. */ long readDiscontinuity(); - /** - * Returns an estimate of the position up to which data is buffered for the enabled tracks. - *

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

    @@ -147,6 +146,17 @@ public interface MediaPeriod extends SequenceableLoader { // SequenceableLoader interface. Overridden to provide more specific documentation. + /** + * Returns an estimate of the position up to which data is buffered for the enabled tracks. + *

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

    @@ -162,9 +172,9 @@ public interface MediaPeriod extends SequenceableLoader { * This method may be called both during and after the period has been prepared. *

    * A period may call {@link Callback#onContinueLoadingRequested(SequenceableLoader)} on the - * {@link Callback} passed to {@link #prepare(Callback)} to request that this method be called - * when the period is permitted to continue loading data. A period may do this both during and - * after preparation. + * {@link Callback} passed to {@link #prepare(Callback, long)} to request that this method be + * called when the period is permitted to continue loading data. A period may do this both during + * and after preparation. * * @param positionUs The current playback position. * @return True if progress was made, meaning that {@link #getNextLoadPositionUs()} will return diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/MediaSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/MediaSource.java index fa7d7b9e9..c8d882d1f 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/MediaSource.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/MediaSource.java @@ -15,13 +15,27 @@ */ package org.telegram.messenger.exoplayer2.source; +import android.support.annotation.Nullable; +import org.telegram.messenger.exoplayer2.C; import org.telegram.messenger.exoplayer2.ExoPlayer; import org.telegram.messenger.exoplayer2.Timeline; import org.telegram.messenger.exoplayer2.upstream.Allocator; import java.io.IOException; /** - * A source of media consisting of one or more {@link MediaPeriod}s. + * Defines and provides media to be played by an {@link ExoPlayer}. A MediaSource has two main + * responsibilities: + *

      + *
    • To provide the player with a {@link Timeline} defining the structure of its media, and to + * provide a new timeline whenever the structure of the media changes. The MediaSource provides + * these timelines by calling {@link Listener#onSourceInfoRefreshed} on the {@link Listener} + * passed to {@link #prepareSource(ExoPlayer, boolean, Listener)}.
    • + *
    • To provide {@link MediaPeriod} instances for the periods in its timeline. MediaPeriods are + * obtained by calling {@link #createPeriod(MediaPeriodId, Allocator)}, and provide a way for the + * player to load and read the media.
    • + *
    + * All methods are called on the player's internal playback thread, as described in the + * {@link ExoPlayer} Javadoc. */ public interface MediaSource { @@ -34,9 +48,100 @@ public interface MediaSource { * Called when manifest and/or timeline has been refreshed. * * @param timeline The source's timeline. - * @param manifest The loaded manifest. + * @param manifest The loaded manifest. May be null. */ - void onSourceInfoRefreshed(Timeline timeline, Object manifest); + void onSourceInfoRefreshed(Timeline timeline, @Nullable Object manifest); + + } + + /** + * Identifier for a {@link MediaPeriod}. + */ + final class MediaPeriodId { + + /** + * Value for unset media period identifiers. + */ + public static final MediaPeriodId UNSET = + new MediaPeriodId(C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET); + + /** + * The timeline period index. + */ + public final int periodIndex; + + /** + * If the media period is in an ad group, the index of the ad group in the period. + * {@link C#INDEX_UNSET} otherwise. + */ + public final int adGroupIndex; + + /** + * If the media period is in an ad group, the index of the ad in its ad group in the period. + * {@link C#INDEX_UNSET} otherwise. + */ + public final int adIndexInAdGroup; + + /** + * Creates a media period identifier for the specified period in the timeline. + * + * @param periodIndex The timeline period index. + */ + public MediaPeriodId(int periodIndex) { + this(periodIndex, C.INDEX_UNSET, C.INDEX_UNSET); + } + + /** + * Creates a media period identifier that identifies an ad within an ad group at the specified + * timeline period. + * + * @param periodIndex The index of the timeline period that contains the ad group. + * @param adGroupIndex The index of the ad group. + * @param adIndexInAdGroup The index of the ad in the ad group. + */ + public MediaPeriodId(int periodIndex, int adGroupIndex, int adIndexInAdGroup) { + this.periodIndex = periodIndex; + this.adGroupIndex = adGroupIndex; + this.adIndexInAdGroup = adIndexInAdGroup; + } + + /** + * Returns a copy of this period identifier but with {@code newPeriodIndex} as its period index. + */ + public MediaPeriodId copyWithPeriodIndex(int newPeriodIndex) { + return periodIndex == newPeriodIndex ? this + : new MediaPeriodId(newPeriodIndex, adGroupIndex, adIndexInAdGroup); + } + + /** + * Returns whether this period identifier identifies an ad in an ad group in a period. + */ + public boolean isAd() { + return adGroupIndex != C.INDEX_UNSET; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + MediaPeriodId periodId = (MediaPeriodId) obj; + return periodIndex == periodId.periodIndex && adGroupIndex == periodId.adGroupIndex + && adIndexInAdGroup == periodId.adIndexInAdGroup; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + periodIndex; + result = 31 * result + adGroupIndex; + result = 31 * result + adIndexInAdGroup; + return result; + } } @@ -58,16 +163,15 @@ public interface MediaSource { void maybeThrowSourceInfoRefreshError() throws IOException; /** - * Returns a new {@link MediaPeriod} corresponding to the period at the specified {@code index}. - * This method may be called multiple times with the same index without an intervening call to + * Returns a new {@link MediaPeriod} identified by {@code periodId}. This method may be called + * multiple times with the same period identifier without an intervening call to * {@link #releasePeriod(MediaPeriod)}. * - * @param index The index of the period. + * @param id The identifier of the period. * @param allocator An {@link Allocator} from which to obtain media buffer allocations. - * @param positionUs The player's current playback position. * @return A new {@link MediaPeriod}. */ - MediaPeriod createPeriod(int index, Allocator allocator, long positionUs); + MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator); /** * Releases the period. diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/MergingMediaPeriod.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/MergingMediaPeriod.java index 524b0336f..9db1d3b77 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/MergingMediaPeriod.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/MergingMediaPeriod.java @@ -44,11 +44,11 @@ import java.util.IdentityHashMap; } @Override - public void prepare(Callback callback) { + public void prepare(Callback callback, long positionUs) { this.callback = callback; pendingChildPrepareCount = periods.length; for (MediaPeriod period : periods) { - period.prepare(this); + period.prepare(this, positionUs); } } @@ -168,14 +168,7 @@ import java.util.IdentityHashMap; @Override public long getBufferedPositionUs() { - long bufferedPositionUs = Long.MAX_VALUE; - for (MediaPeriod period : enabledPeriods) { - long rendererBufferedPositionUs = period.getBufferedPositionUs(); - if (rendererBufferedPositionUs != C.TIME_END_OF_SOURCE) { - bufferedPositionUs = Math.min(bufferedPositionUs, rendererBufferedPositionUs); - } - } - return bufferedPositionUs == Long.MAX_VALUE ? C.TIME_END_OF_SOURCE : bufferedPositionUs; + return sequenceableLoader.getBufferedPositionUs(); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/MergingMediaSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/MergingMediaSource.java index 00e9dd2a0..e07eb6db3 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/MergingMediaSource.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/MergingMediaSource.java @@ -116,10 +116,10 @@ public final class MergingMediaSource implements MediaSource { } @Override - public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { + public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { MediaPeriod[] periods = new MediaPeriod[mediaSources.length]; for (int i = 0; i < periods.length; i++) { - periods[i] = mediaSources[i].createPeriod(index, allocator, positionUs); + periods[i] = mediaSources[i].createPeriod(id, allocator); } return new MergingMediaPeriod(periods); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SampleMetadataQueue.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SampleMetadataQueue.java new file mode 100755 index 000000000..538fdd88a --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SampleMetadataQueue.java @@ -0,0 +1,541 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.FormatHolder; +import org.telegram.messenger.exoplayer2.decoder.DecoderInputBuffer; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput.CryptoData; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.Util; + +/** + * A queue of metadata describing the contents of a media buffer. + */ +/* package */ final class SampleMetadataQueue { + + /** + * A holder for sample metadata not held by {@link DecoderInputBuffer}. + */ + public static final class SampleExtrasHolder { + + public int size; + public long offset; + public CryptoData cryptoData; + + } + + private static final int SAMPLE_CAPACITY_INCREMENT = 1000; + + private int capacity; + private int[] sourceIds; + private long[] offsets; + private int[] sizes; + private int[] flags; + private long[] timesUs; + private CryptoData[] cryptoDatas; + private Format[] formats; + + private int length; + private int absoluteStartIndex; + private int relativeStartIndex; + private int readPosition; + + private long largestDiscardedTimestampUs; + private long largestQueuedTimestampUs; + private boolean upstreamKeyframeRequired; + private boolean upstreamFormatRequired; + private Format upstreamFormat; + private int upstreamSourceId; + + public SampleMetadataQueue() { + capacity = SAMPLE_CAPACITY_INCREMENT; + sourceIds = new int[capacity]; + offsets = new long[capacity]; + timesUs = new long[capacity]; + flags = new int[capacity]; + sizes = new int[capacity]; + cryptoDatas = new CryptoData[capacity]; + formats = new Format[capacity]; + largestDiscardedTimestampUs = Long.MIN_VALUE; + largestQueuedTimestampUs = Long.MIN_VALUE; + upstreamFormatRequired = true; + upstreamKeyframeRequired = true; + } + + /** + * Clears all sample metadata from the queue. + * + * @param resetUpstreamFormat Whether the upstream format should be cleared. If set to false, + * samples queued after the reset (and before a subsequent call to {@link #format(Format)}) + * are assumed to have the current upstream format. If set to true, {@link #format(Format)} + * must be called after the reset before any more samples can be queued. + */ + public void reset(boolean resetUpstreamFormat) { + length = 0; + absoluteStartIndex = 0; + relativeStartIndex = 0; + readPosition = 0; + upstreamKeyframeRequired = true; + largestDiscardedTimestampUs = Long.MIN_VALUE; + largestQueuedTimestampUs = Long.MIN_VALUE; + if (resetUpstreamFormat) { + upstreamFormat = null; + upstreamFormatRequired = true; + } + } + + /** + * Returns the current absolute write index. + */ + public int getWriteIndex() { + return absoluteStartIndex + length; + } + + /** + * Discards samples from the write side of the queue. + * + * @param discardFromIndex The absolute index of the first sample to be discarded. + * @return The reduced total number of bytes written after the samples have been discarded, or 0 + * if the queue is now empty. + */ + public long discardUpstreamSamples(int discardFromIndex) { + int discardCount = getWriteIndex() - discardFromIndex; + Assertions.checkArgument(0 <= discardCount && discardCount <= (length - readPosition)); + length -= discardCount; + largestQueuedTimestampUs = Math.max(largestDiscardedTimestampUs, getLargestTimestamp(length)); + if (length == 0) { + return 0; + } else { + int relativeLastWriteIndex = getRelativeIndex(length - 1); + return offsets[relativeLastWriteIndex] + sizes[relativeLastWriteIndex]; + } + } + + public void sourceId(int sourceId) { + upstreamSourceId = sourceId; + } + + // Called by the consuming thread. + + /** + * Returns the current absolute read index. + */ + public int getReadIndex() { + return absoluteStartIndex + readPosition; + } + + /** + * Peeks the source id of the next sample to be read, or the current upstream source id if the + * queue is empty or if the read position is at the end of the queue. + * + * @return The source id. + */ + public int peekSourceId() { + int relativeReadIndex = getRelativeIndex(readPosition); + return hasNextSample() ? sourceIds[relativeReadIndex] : upstreamSourceId; + } + + /** + * Returns whether a sample is available to be read. + */ + public synchronized boolean hasNextSample() { + return readPosition != length; + } + + /** + * Returns the upstream {@link Format} in which samples are being queued. + */ + public synchronized Format getUpstreamFormat() { + return upstreamFormatRequired ? null : upstreamFormat; + } + + /** + * Returns the largest sample timestamp that has been queued since the last call to + * {@link #reset(boolean)}. + *

    + * Samples that were discarded by calling {@link #discardUpstreamSamples(int)} are not + * considered as having been queued. Samples that were dequeued from the front of the queue are + * considered as having been queued. + * + * @return The largest sample timestamp that has been queued, or {@link Long#MIN_VALUE} if no + * samples have been queued. + */ + public synchronized long getLargestQueuedTimestampUs() { + return largestQueuedTimestampUs; + } + + /** + * Rewinds the read position to the first sample retained in the queue. + */ + public synchronized void rewind() { + readPosition = 0; + } + + /** + * Attempts to read from the queue. + * + * @param formatHolder A {@link FormatHolder} to populate in the case of reading a format. + * @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the + * end of the stream. If a sample is read then the buffer is populated with information + * about the sample, but not its data. The size and absolute position of the data in the + * rolling buffer is stored in {@code extrasHolder}, along with an encryption id if present + * and the absolute position of the first byte that may still be required after the current + * sample has been read. May be null if the caller requires that the format of the stream be + * read even if it's not changing. + * @param formatRequired Whether the caller requires that the format of the stream be read even + * if it's not changing. A sample will never be read if set to true, however it is still + * possible for the end of stream or nothing to be read. + * @param loadingFinished True if an empty queue should be considered the end of the stream. + * @param downstreamFormat The current downstream {@link Format}. If the format of the next + * sample is different to the current downstream format then a format will be read. + * @param extrasHolder The holder into which extra sample information should be written. + * @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} + * or {@link C#RESULT_BUFFER_READ}. + */ + @SuppressWarnings("ReferenceEquality") + public synchronized int read(FormatHolder formatHolder, DecoderInputBuffer buffer, + boolean formatRequired, boolean loadingFinished, Format downstreamFormat, + SampleExtrasHolder extrasHolder) { + if (!hasNextSample()) { + if (loadingFinished) { + buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM); + return C.RESULT_BUFFER_READ; + } else if (upstreamFormat != null + && (formatRequired || upstreamFormat != downstreamFormat)) { + formatHolder.format = upstreamFormat; + return C.RESULT_FORMAT_READ; + } else { + return C.RESULT_NOTHING_READ; + } + } + + int relativeReadIndex = getRelativeIndex(readPosition); + if (formatRequired || formats[relativeReadIndex] != downstreamFormat) { + formatHolder.format = formats[relativeReadIndex]; + return C.RESULT_FORMAT_READ; + } + + if (buffer.isFlagsOnly()) { + return C.RESULT_NOTHING_READ; + } + + buffer.timeUs = timesUs[relativeReadIndex]; + buffer.setFlags(flags[relativeReadIndex]); + extrasHolder.size = sizes[relativeReadIndex]; + extrasHolder.offset = offsets[relativeReadIndex]; + extrasHolder.cryptoData = cryptoDatas[relativeReadIndex]; + + readPosition++; + return C.RESULT_BUFFER_READ; + } + + /** + * Attempts to advance the read position to the sample before or at the specified time. + * + * @param timeUs The time to advance to. + * @param toKeyframe If true then attempts to advance to the keyframe before or at the specified + * time, rather than to any sample before or at that time. + * @param allowTimeBeyondBuffer Whether the operation can succeed if {@code timeUs} is beyond the + * end of the queue, by advancing the read position to the last sample (or keyframe) in the + * queue. + * @return Whether the operation was a success. A successful advance is one in which the read + * position was unchanged or advanced, and is now at a sample meeting the specified criteria. + */ + public synchronized boolean advanceTo(long timeUs, boolean toKeyframe, + boolean allowTimeBeyondBuffer) { + int relativeReadIndex = getRelativeIndex(readPosition); + if (!hasNextSample() || timeUs < timesUs[relativeReadIndex] + || (timeUs > largestQueuedTimestampUs && !allowTimeBeyondBuffer)) { + return false; + } + int offset = findSampleBefore(relativeReadIndex, length - readPosition, timeUs, toKeyframe); + if (offset == -1) { + return false; + } + readPosition += offset; + return true; + } + + /** + * Advances the read position to the end of the queue. + */ + public synchronized void advanceToEnd() { + if (!hasNextSample()) { + return; + } + readPosition = length; + } + + /** + * Discards up to but not including the sample immediately before or at the specified time. + * + * @param timeUs The time to discard up to. + * @param toKeyframe If true then discards samples up to the keyframe before or at the specified + * time, rather than just any sample before or at that time. + * @param stopAtReadPosition If true then samples are only discarded if they're before the read + * position. If false then samples at and beyond the read position may be discarded, in which + * case the read position is advanced to the first remaining sample. + * @return The corresponding offset up to which data should be discarded, or + * {@link C#POSITION_UNSET} if no discarding of data is necessary. + */ + public synchronized long discardTo(long timeUs, boolean toKeyframe, boolean stopAtReadPosition) { + if (length == 0 || timeUs < timesUs[relativeStartIndex]) { + return C.POSITION_UNSET; + } + int searchLength = stopAtReadPosition && readPosition != length ? readPosition + 1 : length; + int discardCount = findSampleBefore(relativeStartIndex, searchLength, timeUs, toKeyframe); + if (discardCount == -1) { + return C.POSITION_UNSET; + } + return discardSamples(discardCount); + } + + /** + * Discards samples up to but not including the read position. + * + * @return The corresponding offset up to which data should be discarded, or + * {@link C#POSITION_UNSET} if no discarding of data is necessary. + */ + public synchronized long discardToRead() { + if (readPosition == 0) { + return C.POSITION_UNSET; + } + return discardSamples(readPosition); + } + + /** + * Discards all samples in the queue. The read position is also advanced. + * + * @return The corresponding offset up to which data should be discarded, or + * {@link C#POSITION_UNSET} if no discarding of data is necessary. + */ + public synchronized long discardToEnd() { + if (length == 0) { + return C.POSITION_UNSET; + } + return discardSamples(length); + } + + // Called by the loading thread. + + public synchronized boolean format(Format format) { + if (format == null) { + upstreamFormatRequired = true; + return false; + } + upstreamFormatRequired = false; + if (Util.areEqual(format, upstreamFormat)) { + // Suppress changes between equal formats so we can use referential equality in readData. + return false; + } else { + upstreamFormat = format; + return true; + } + } + + public synchronized void commitSample(long timeUs, @C.BufferFlags int sampleFlags, long offset, + int size, CryptoData cryptoData) { + if (upstreamKeyframeRequired) { + if ((sampleFlags & C.BUFFER_FLAG_KEY_FRAME) == 0) { + return; + } + upstreamKeyframeRequired = false; + } + Assertions.checkState(!upstreamFormatRequired); + commitSampleTimestamp(timeUs); + + int relativeEndIndex = getRelativeIndex(length); + timesUs[relativeEndIndex] = timeUs; + offsets[relativeEndIndex] = offset; + sizes[relativeEndIndex] = size; + flags[relativeEndIndex] = sampleFlags; + cryptoDatas[relativeEndIndex] = cryptoData; + formats[relativeEndIndex] = upstreamFormat; + sourceIds[relativeEndIndex] = upstreamSourceId; + + length++; + if (length == capacity) { + // Increase the capacity. + int newCapacity = capacity + SAMPLE_CAPACITY_INCREMENT; + int[] newSourceIds = new int[newCapacity]; + long[] newOffsets = new long[newCapacity]; + long[] newTimesUs = new long[newCapacity]; + int[] newFlags = new int[newCapacity]; + int[] newSizes = new int[newCapacity]; + CryptoData[] newCryptoDatas = new CryptoData[newCapacity]; + Format[] newFormats = new Format[newCapacity]; + int beforeWrap = capacity - relativeStartIndex; + System.arraycopy(offsets, relativeStartIndex, newOffsets, 0, beforeWrap); + System.arraycopy(timesUs, relativeStartIndex, newTimesUs, 0, beforeWrap); + System.arraycopy(flags, relativeStartIndex, newFlags, 0, beforeWrap); + System.arraycopy(sizes, relativeStartIndex, newSizes, 0, beforeWrap); + System.arraycopy(cryptoDatas, relativeStartIndex, newCryptoDatas, 0, beforeWrap); + System.arraycopy(formats, relativeStartIndex, newFormats, 0, beforeWrap); + System.arraycopy(sourceIds, relativeStartIndex, newSourceIds, 0, beforeWrap); + int afterWrap = relativeStartIndex; + System.arraycopy(offsets, 0, newOffsets, beforeWrap, afterWrap); + System.arraycopy(timesUs, 0, newTimesUs, beforeWrap, afterWrap); + System.arraycopy(flags, 0, newFlags, beforeWrap, afterWrap); + System.arraycopy(sizes, 0, newSizes, beforeWrap, afterWrap); + System.arraycopy(cryptoDatas, 0, newCryptoDatas, beforeWrap, afterWrap); + System.arraycopy(formats, 0, newFormats, beforeWrap, afterWrap); + System.arraycopy(sourceIds, 0, newSourceIds, beforeWrap, afterWrap); + offsets = newOffsets; + timesUs = newTimesUs; + flags = newFlags; + sizes = newSizes; + cryptoDatas = newCryptoDatas; + formats = newFormats; + sourceIds = newSourceIds; + relativeStartIndex = 0; + length = capacity; + capacity = newCapacity; + } + } + + public synchronized void commitSampleTimestamp(long timeUs) { + largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs, timeUs); + } + + /** + * Attempts to discard samples from the end of the queue to allow samples starting from the + * specified timestamp to be spliced in. Samples will not be discarded prior to the read position. + * + * @param timeUs The timestamp at which the splice occurs. + * @return Whether the splice was successful. + */ + public synchronized boolean attemptSplice(long timeUs) { + if (length == 0) { + return timeUs > largestDiscardedTimestampUs; + } + long largestReadTimestampUs = Math.max(largestDiscardedTimestampUs, + getLargestTimestamp(readPosition)); + if (largestReadTimestampUs >= timeUs) { + return false; + } + int retainCount = length; + int relativeSampleIndex = getRelativeIndex(length - 1); + while (retainCount > readPosition && timesUs[relativeSampleIndex] >= timeUs) { + retainCount--; + relativeSampleIndex--; + if (relativeSampleIndex == -1) { + relativeSampleIndex = capacity - 1; + } + } + discardUpstreamSamples(absoluteStartIndex + retainCount); + return true; + } + + // Internal methods. + + /** + * Finds the sample in the specified range that's before or at the specified time. If + * {@code keyframe} is {@code true} then the sample is additionally required to be a keyframe. + * + * @param relativeStartIndex The relative index from which to start searching. + * @param length The length of the range being searched. + * @param timeUs The specified time. + * @param keyframe Whether only keyframes should be considered. + * @return The offset from {@code relativeStartIndex} to the found sample, or -1 if no matching + * sample was found. + */ + private int findSampleBefore(int relativeStartIndex, int length, long timeUs, boolean keyframe) { + // This could be optimized to use a binary search, however in practice callers to this method + // normally pass times near to the start of the search region. Hence it's unclear whether + // switching to a binary search would yield any real benefit. + int sampleCountToTarget = -1; + int searchIndex = relativeStartIndex; + for (int i = 0; i < length && timesUs[searchIndex] <= timeUs; i++) { + if (!keyframe || (flags[searchIndex] & C.BUFFER_FLAG_KEY_FRAME) != 0) { + // We've found a suitable sample. + sampleCountToTarget = i; + } + searchIndex++; + if (searchIndex == capacity) { + searchIndex = 0; + } + } + return sampleCountToTarget; + } + + /** + * Discards the specified number of samples. + * + * @param discardCount The number of samples to discard. + * @return The corresponding offset up to which data should be discarded, or + * {@link C#POSITION_UNSET} if no discarding of data is necessary. + */ + private long discardSamples(int discardCount) { + largestDiscardedTimestampUs = Math.max(largestDiscardedTimestampUs, + getLargestTimestamp(discardCount)); + length -= discardCount; + absoluteStartIndex += discardCount; + relativeStartIndex += discardCount; + if (relativeStartIndex >= capacity) { + relativeStartIndex -= capacity; + } + readPosition -= discardCount; + if (readPosition < 0) { + readPosition = 0; + } + if (length == 0) { + int relativeLastDiscardIndex = (relativeStartIndex == 0 ? capacity : relativeStartIndex) - 1; + return offsets[relativeLastDiscardIndex] + sizes[relativeLastDiscardIndex]; + } else { + return offsets[relativeStartIndex]; + } + } + + /** + * Finds the largest timestamp of any sample from the start of the queue up to the specified + * length, assuming that the timestamps prior to a keyframe are always less than the timestamp of + * the keyframe itself, and of subsequent frames. + * + * @param length The length of the range being searched. + * @return The largest timestamp, or {@link Long#MIN_VALUE} if {@code length == 0}. + */ + private long getLargestTimestamp(int length) { + if (length == 0) { + return Long.MIN_VALUE; + } + long largestTimestampUs = Long.MIN_VALUE; + int relativeSampleIndex = getRelativeIndex(length - 1); + for (int i = 0; i < length; i++) { + largestTimestampUs = Math.max(largestTimestampUs, timesUs[relativeSampleIndex]); + if ((flags[relativeSampleIndex] & C.BUFFER_FLAG_KEY_FRAME) != 0) { + break; + } + relativeSampleIndex--; + if (relativeSampleIndex == -1) { + relativeSampleIndex = capacity - 1; + } + } + return largestTimestampUs; + } + + /** + * Returns the relative index for a given offset from the start of the queue. + * + * @param offset The offset, which must be in the range [0, length]. + */ + private int getRelativeIndex(int offset) { + int relativeIndex = relativeStartIndex + offset; + return relativeIndex < capacity ? relativeIndex : relativeIndex - capacity; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SampleQueue.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SampleQueue.java new file mode 100755 index 000000000..6131a7e4a --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SampleQueue.java @@ -0,0 +1,701 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source; + +import android.support.annotation.Nullable; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.FormatHolder; +import org.telegram.messenger.exoplayer2.decoder.DecoderInputBuffer; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.source.SampleMetadataQueue.SampleExtrasHolder; +import org.telegram.messenger.exoplayer2.upstream.Allocation; +import org.telegram.messenger.exoplayer2.upstream.Allocator; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import java.io.EOFException; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * A queue of media samples. + */ +public final class SampleQueue implements TrackOutput { + + /** + * A listener for changes to the upstream format. + */ + public interface UpstreamFormatChangedListener { + + /** + * Called on the loading thread when an upstream format change occurs. + * + * @param format The new upstream format. + */ + void onUpstreamFormatChanged(Format format); + + } + + private static final int INITIAL_SCRATCH_SIZE = 32; + + private final Allocator allocator; + private final int allocationLength; + private final SampleMetadataQueue metadataQueue; + private final SampleExtrasHolder extrasHolder; + private final ParsableByteArray scratch; + + // References into the linked list of allocations. + private AllocationNode firstAllocationNode; + private AllocationNode readAllocationNode; + private AllocationNode writeAllocationNode; + + // Accessed only by the consuming thread. + private Format downstreamFormat; + + // Accessed only by the loading thread (or the consuming thread when there is no loading thread). + private boolean pendingFormatAdjustment; + private Format lastUnadjustedFormat; + private long sampleOffsetUs; + private long totalBytesWritten; + private boolean pendingSplice; + private UpstreamFormatChangedListener upstreamFormatChangeListener; + + /** + * @param allocator An {@link Allocator} from which allocations for sample data can be obtained. + */ + public SampleQueue(Allocator allocator) { + this.allocator = allocator; + allocationLength = allocator.getIndividualAllocationLength(); + metadataQueue = new SampleMetadataQueue(); + extrasHolder = new SampleExtrasHolder(); + scratch = new ParsableByteArray(INITIAL_SCRATCH_SIZE); + firstAllocationNode = new AllocationNode(0, allocationLength); + readAllocationNode = firstAllocationNode; + writeAllocationNode = firstAllocationNode; + } + + // Called by the consuming thread, but only when there is no loading thread. + + /** + * Resets the output without clearing the upstream format. Equivalent to {@code reset(false)}. + */ + public void reset() { + reset(false); + } + + /** + * Resets the output. + * + * @param resetUpstreamFormat Whether the upstream format should be cleared. If set to false, + * samples queued after the reset (and before a subsequent call to {@link #format(Format)}) + * are assumed to have the current upstream format. If set to true, {@link #format(Format)} + * must be called after the reset before any more samples can be queued. + */ + public void reset(boolean resetUpstreamFormat) { + metadataQueue.reset(resetUpstreamFormat); + clearAllocationNodes(firstAllocationNode); + firstAllocationNode = new AllocationNode(0, allocationLength); + readAllocationNode = firstAllocationNode; + writeAllocationNode = firstAllocationNode; + totalBytesWritten = 0; + allocator.trim(); + } + + /** + * Sets a source identifier for subsequent samples. + * + * @param sourceId The source identifier. + */ + public void sourceId(int sourceId) { + metadataQueue.sourceId(sourceId); + } + + /** + * Indicates samples that are subsequently queued should be spliced into those already queued. + */ + public void splice() { + pendingSplice = true; + } + + /** + * Returns the current absolute write index. + */ + public int getWriteIndex() { + return metadataQueue.getWriteIndex(); + } + + /** + * Discards samples from the write side of the queue. + * + * @param discardFromIndex The absolute index of the first sample to be discarded. Must be in the + * range [{@link #getReadIndex()}, {@link #getWriteIndex()}]. + */ + public void discardUpstreamSamples(int discardFromIndex) { + totalBytesWritten = metadataQueue.discardUpstreamSamples(discardFromIndex); + if (totalBytesWritten == 0 || totalBytesWritten == firstAllocationNode.startPosition) { + clearAllocationNodes(firstAllocationNode); + firstAllocationNode = new AllocationNode(totalBytesWritten, allocationLength); + readAllocationNode = firstAllocationNode; + writeAllocationNode = firstAllocationNode; + } else { + // Find the last node containing at least 1 byte of data that we need to keep. + AllocationNode lastNodeToKeep = firstAllocationNode; + while (totalBytesWritten > lastNodeToKeep.endPosition) { + lastNodeToKeep = lastNodeToKeep.next; + } + // Discard all subsequent nodes. + AllocationNode firstNodeToDiscard = lastNodeToKeep.next; + clearAllocationNodes(firstNodeToDiscard); + // Reset the successor of the last node to be an uninitialized node. + lastNodeToKeep.next = new AllocationNode(lastNodeToKeep.endPosition, allocationLength); + // Update writeAllocationNode and readAllocationNode as necessary. + writeAllocationNode = totalBytesWritten == lastNodeToKeep.endPosition ? lastNodeToKeep.next + : lastNodeToKeep; + if (readAllocationNode == firstNodeToDiscard) { + readAllocationNode = lastNodeToKeep.next; + } + } + } + + // Called by the consuming thread. + + /** + * Returns whether a sample is available to be read. + */ + public boolean hasNextSample() { + return metadataQueue.hasNextSample(); + } + + /** + * Returns the current absolute read index. + */ + public int getReadIndex() { + return metadataQueue.getReadIndex(); + } + + /** + * Peeks the source id of the next sample to be read, or the current upstream source id if the + * queue is empty or if the read position is at the end of the queue. + * + * @return The source id. + */ + public int peekSourceId() { + return metadataQueue.peekSourceId(); + } + + /** + * Returns the upstream {@link Format} in which samples are being queued. + */ + public Format getUpstreamFormat() { + return metadataQueue.getUpstreamFormat(); + } + + /** + * Returns the largest sample timestamp that has been queued since the last {@link #reset}. + *

    + * Samples that were discarded by calling {@link #discardUpstreamSamples(int)} are not + * considered as having been queued. Samples that were dequeued from the front of the queue are + * considered as having been queued. + * + * @return The largest sample timestamp that has been queued, or {@link Long#MIN_VALUE} if no + * samples have been queued. + */ + public long getLargestQueuedTimestampUs() { + return metadataQueue.getLargestQueuedTimestampUs(); + } + + /** + * Rewinds the read position to the first sample in the queue. + */ + public void rewind() { + metadataQueue.rewind(); + readAllocationNode = firstAllocationNode; + } + + /** + * Discards up to but not including the sample immediately before or at the specified time. + * + * @param timeUs The time to discard to. + * @param toKeyframe If true then discards samples up to the keyframe before or at the specified + * time, rather than any sample before or at that time. + * @param stopAtReadPosition If true then samples are only discarded if they're before the + * read position. If false then samples at and beyond the read position may be discarded, in + * which case the read position is advanced to the first remaining sample. + */ + public void discardTo(long timeUs, boolean toKeyframe, boolean stopAtReadPosition) { + discardDownstreamTo(metadataQueue.discardTo(timeUs, toKeyframe, stopAtReadPosition)); + } + + /** + * Discards up to but not including the read position. + */ + public void discardToRead() { + discardDownstreamTo(metadataQueue.discardToRead()); + } + + /** + * Discards to the end of the queue. The read position is also advanced. + */ + public void discardToEnd() { + discardDownstreamTo(metadataQueue.discardToEnd()); + } + + /** + * Advances the read position to the end of the queue. + */ + public void advanceToEnd() { + metadataQueue.advanceToEnd(); + } + + /** + * Attempts to advance the read position to the sample before or at the specified time. + * + * @param timeUs The time to advance to. + * @param toKeyframe If true then attempts to advance to the keyframe before or at the specified + * time, rather than to any sample before or at that time. + * @param allowTimeBeyondBuffer Whether the operation can succeed if {@code timeUs} is beyond the + * end of the queue, by advancing the read position to the last sample (or keyframe). + * @return Whether the operation was a success. A successful advance is one in which the read + * position was unchanged or advanced, and is now at a sample meeting the specified criteria. + */ + public boolean advanceTo(long timeUs, boolean toKeyframe, boolean allowTimeBeyondBuffer) { + return metadataQueue.advanceTo(timeUs, toKeyframe, allowTimeBeyondBuffer); + } + + /** + * Attempts to read from the queue. + * + * @param formatHolder A {@link FormatHolder} to populate in the case of reading a format. + * @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the + * end of the stream. If the end of the stream has been reached, the + * {@link C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer. + * @param formatRequired Whether the caller requires that the format of the stream be read even if + * it's not changing. A sample will never be read if set to true, however it is still possible + * for the end of stream or nothing to be read. + * @param loadingFinished True if an empty queue should be considered the end of the stream. + * @param decodeOnlyUntilUs If a buffer is read, the {@link C#BUFFER_FLAG_DECODE_ONLY} flag will + * be set if the buffer's timestamp is less than this value. + * @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} or + * {@link C#RESULT_BUFFER_READ}. + */ + public int read(FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired, + boolean loadingFinished, long decodeOnlyUntilUs) { + int result = metadataQueue.read(formatHolder, buffer, formatRequired, loadingFinished, + downstreamFormat, extrasHolder); + switch (result) { + case C.RESULT_FORMAT_READ: + downstreamFormat = formatHolder.format; + return C.RESULT_FORMAT_READ; + case C.RESULT_BUFFER_READ: + if (!buffer.isEndOfStream()) { + if (buffer.timeUs < decodeOnlyUntilUs) { + buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY); + } + // Read encryption data if the sample is encrypted. + if (buffer.isEncrypted()) { + readEncryptionData(buffer, extrasHolder); + } + // Write the sample data into the holder. + buffer.ensureSpaceForWrite(extrasHolder.size); + readData(extrasHolder.offset, buffer.data, extrasHolder.size); + } + return C.RESULT_BUFFER_READ; + case C.RESULT_NOTHING_READ: + return C.RESULT_NOTHING_READ; + default: + throw new IllegalStateException(); + } + } + + /** + * Reads encryption data for the current sample. + *

    + * The encryption data is written into {@link DecoderInputBuffer#cryptoInfo}, and + * {@link SampleExtrasHolder#size} is adjusted to subtract the number of bytes that were read. The + * same value is added to {@link SampleExtrasHolder#offset}. + * + * @param buffer The buffer into which the encryption data should be written. + * @param extrasHolder The extras holder whose offset should be read and subsequently adjusted. + */ + private void readEncryptionData(DecoderInputBuffer buffer, SampleExtrasHolder extrasHolder) { + long offset = extrasHolder.offset; + + // Read the signal byte. + scratch.reset(1); + readData(offset, scratch.data, 1); + offset++; + byte signalByte = scratch.data[0]; + boolean subsampleEncryption = (signalByte & 0x80) != 0; + int ivSize = signalByte & 0x7F; + + // Read the initialization vector. + if (buffer.cryptoInfo.iv == null) { + buffer.cryptoInfo.iv = new byte[16]; + } + readData(offset, buffer.cryptoInfo.iv, ivSize); + offset += ivSize; + + // Read the subsample count, if present. + int subsampleCount; + if (subsampleEncryption) { + scratch.reset(2); + readData(offset, scratch.data, 2); + offset += 2; + subsampleCount = scratch.readUnsignedShort(); + } else { + subsampleCount = 1; + } + + // Write the clear and encrypted subsample sizes. + int[] clearDataSizes = buffer.cryptoInfo.numBytesOfClearData; + if (clearDataSizes == null || clearDataSizes.length < subsampleCount) { + clearDataSizes = new int[subsampleCount]; + } + int[] encryptedDataSizes = buffer.cryptoInfo.numBytesOfEncryptedData; + if (encryptedDataSizes == null || encryptedDataSizes.length < subsampleCount) { + encryptedDataSizes = new int[subsampleCount]; + } + if (subsampleEncryption) { + int subsampleDataLength = 6 * subsampleCount; + scratch.reset(subsampleDataLength); + readData(offset, scratch.data, subsampleDataLength); + offset += subsampleDataLength; + scratch.setPosition(0); + for (int i = 0; i < subsampleCount; i++) { + clearDataSizes[i] = scratch.readUnsignedShort(); + encryptedDataSizes[i] = scratch.readUnsignedIntToInt(); + } + } else { + clearDataSizes[0] = 0; + encryptedDataSizes[0] = extrasHolder.size - (int) (offset - extrasHolder.offset); + } + + // Populate the cryptoInfo. + CryptoData cryptoData = extrasHolder.cryptoData; + buffer.cryptoInfo.set(subsampleCount, clearDataSizes, encryptedDataSizes, + cryptoData.encryptionKey, buffer.cryptoInfo.iv, cryptoData.cryptoMode, + cryptoData.encryptedBlocks, cryptoData.clearBlocks); + + // Adjust the offset and size to take into account the bytes read. + int bytesRead = (int) (offset - extrasHolder.offset); + extrasHolder.offset += bytesRead; + extrasHolder.size -= bytesRead; + } + + /** + * Reads data from the front of the rolling buffer. + * + * @param absolutePosition The absolute position from which data should be read. + * @param target The buffer into which data should be written. + * @param length The number of bytes to read. + */ + private void readData(long absolutePosition, ByteBuffer target, int length) { + advanceReadTo(absolutePosition); + int remaining = length; + while (remaining > 0) { + int toCopy = Math.min(remaining, (int) (readAllocationNode.endPosition - absolutePosition)); + Allocation allocation = readAllocationNode.allocation; + target.put(allocation.data, readAllocationNode.translateOffset(absolutePosition), toCopy); + remaining -= toCopy; + absolutePosition += toCopy; + if (absolutePosition == readAllocationNode.endPosition) { + readAllocationNode = readAllocationNode.next; + } + } + } + + /** + * Reads data from the front of the rolling buffer. + * + * @param absolutePosition The absolute position from which data should be read. + * @param target The array into which data should be written. + * @param length The number of bytes to read. + */ + private void readData(long absolutePosition, byte[] target, int length) { + advanceReadTo(absolutePosition); + int remaining = length; + while (remaining > 0) { + int toCopy = Math.min(remaining, (int) (readAllocationNode.endPosition - absolutePosition)); + Allocation allocation = readAllocationNode.allocation; + System.arraycopy(allocation.data, readAllocationNode.translateOffset(absolutePosition), + target, length - remaining, toCopy); + remaining -= toCopy; + absolutePosition += toCopy; + if (absolutePosition == readAllocationNode.endPosition) { + readAllocationNode = readAllocationNode.next; + } + } + } + + /** + * Advances {@link #readAllocationNode} to the specified absolute position. + * + * @param absolutePosition The position to which {@link #readAllocationNode} should be advanced. + */ + private void advanceReadTo(long absolutePosition) { + while (absolutePosition >= readAllocationNode.endPosition) { + readAllocationNode = readAllocationNode.next; + } + } + + /** + * Advances {@link #firstAllocationNode} to the specified absolute position. + * {@link #readAllocationNode} is also advanced if necessary to avoid it falling behind + * {@link #firstAllocationNode}. Nodes that have been advanced past are cleared, and their + * underlying allocations are returned to the allocator. + * + * @param absolutePosition The position to which {@link #firstAllocationNode} should be advanced. + * May be {@link C#POSITION_UNSET}, in which case calling this method is a no-op. + */ + private void discardDownstreamTo(long absolutePosition) { + if (absolutePosition == C.POSITION_UNSET) { + return; + } + while (absolutePosition >= firstAllocationNode.endPosition) { + allocator.release(firstAllocationNode.allocation); + firstAllocationNode = firstAllocationNode.clear(); + } + // If we discarded the node referenced by readAllocationNode then we need to advance it to the + // first remaining node. + if (readAllocationNode.startPosition < firstAllocationNode.startPosition) { + readAllocationNode = firstAllocationNode; + } + } + + // Called by the loading thread. + + /** + * Sets a listener to be notified of changes to the upstream format. + * + * @param listener The listener. + */ + public void setUpstreamFormatChangeListener(UpstreamFormatChangedListener listener) { + upstreamFormatChangeListener = listener; + } + + /** + * Sets an offset that will be added to the timestamps (and sub-sample timestamps) of samples + * that are subsequently queued. + * + * @param sampleOffsetUs The timestamp offset in microseconds. + */ + public void setSampleOffsetUs(long sampleOffsetUs) { + if (this.sampleOffsetUs != sampleOffsetUs) { + this.sampleOffsetUs = sampleOffsetUs; + pendingFormatAdjustment = true; + } + } + + @Override + public void format(Format format) { + Format adjustedFormat = getAdjustedSampleFormat(format, sampleOffsetUs); + boolean formatChanged = metadataQueue.format(adjustedFormat); + lastUnadjustedFormat = format; + pendingFormatAdjustment = false; + if (upstreamFormatChangeListener != null && formatChanged) { + upstreamFormatChangeListener.onUpstreamFormatChanged(adjustedFormat); + } + } + + @Override + public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) + throws IOException, InterruptedException { + length = preAppend(length); + int bytesAppended = input.read(writeAllocationNode.allocation.data, + writeAllocationNode.translateOffset(totalBytesWritten), length); + if (bytesAppended == C.RESULT_END_OF_INPUT) { + if (allowEndOfInput) { + return C.RESULT_END_OF_INPUT; + } + throw new EOFException(); + } + postAppend(bytesAppended); + return bytesAppended; + } + + @Override + public void sampleData(ParsableByteArray buffer, int length) { + while (length > 0) { + int bytesAppended = preAppend(length); + buffer.readBytes(writeAllocationNode.allocation.data, + writeAllocationNode.translateOffset(totalBytesWritten), bytesAppended); + length -= bytesAppended; + postAppend(bytesAppended); + } + } + + @Override + public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset, + CryptoData cryptoData) { + if (pendingFormatAdjustment) { + format(lastUnadjustedFormat); + } + if (pendingSplice) { + if ((flags & C.BUFFER_FLAG_KEY_FRAME) == 0 || !metadataQueue.attemptSplice(timeUs)) { + return; + } + pendingSplice = false; + } + timeUs += sampleOffsetUs; + long absoluteOffset = totalBytesWritten - size - offset; + metadataQueue.commitSample(timeUs, flags, absoluteOffset, size, cryptoData); + } + + // Private methods. + + /** + * Clears allocation nodes starting from {@code fromNode}. + * + * @param fromNode The node from which to clear. + */ + private void clearAllocationNodes(AllocationNode fromNode) { + if (!fromNode.wasInitialized) { + return; + } + // Bulk release allocations for performance (it's significantly faster when using + // DefaultAllocator because the allocator's lock only needs to be acquired and released once) + // [Internal: See b/29542039]. + int allocationCount = (writeAllocationNode.wasInitialized ? 1 : 0) + + ((int) (writeAllocationNode.startPosition - fromNode.startPosition) / allocationLength); + Allocation[] allocationsToRelease = new Allocation[allocationCount]; + AllocationNode currentNode = fromNode; + for (int i = 0; i < allocationsToRelease.length; i++) { + allocationsToRelease[i] = currentNode.allocation; + currentNode = currentNode.clear(); + } + allocator.release(allocationsToRelease); + } + + /** + * Called before writing sample data to {@link #writeAllocationNode}. May cause + * {@link #writeAllocationNode} to be initialized. + * + * @param length The number of bytes that the caller wishes to write. + * @return The number of bytes that the caller is permitted to write, which may be less than + * {@code length}. + */ + private int preAppend(int length) { + if (!writeAllocationNode.wasInitialized) { + writeAllocationNode.initialize(allocator.allocate(), + new AllocationNode(writeAllocationNode.endPosition, allocationLength)); + } + return Math.min(length, (int) (writeAllocationNode.endPosition - totalBytesWritten)); + } + + /** + * Called after writing sample data. May cause {@link #writeAllocationNode} to be advanced. + * + * @param length The number of bytes that were written. + */ + private void postAppend(int length) { + totalBytesWritten += length; + if (totalBytesWritten == writeAllocationNode.endPosition) { + writeAllocationNode = writeAllocationNode.next; + } + } + + /** + * Adjusts a {@link Format} to incorporate a sample offset into {@link Format#subsampleOffsetUs}. + * + * @param format The {@link Format} to adjust. + * @param sampleOffsetUs The offset to apply. + * @return The adjusted {@link Format}. + */ + private static Format getAdjustedSampleFormat(Format format, long sampleOffsetUs) { + if (format == null) { + return null; + } + if (sampleOffsetUs != 0 && format.subsampleOffsetUs != Format.OFFSET_SAMPLE_RELATIVE) { + format = format.copyWithSubsampleOffsetUs(format.subsampleOffsetUs + sampleOffsetUs); + } + return format; + } + + /** + * A node in a linked list of {@link Allocation}s held by the output. + */ + private static final class AllocationNode { + + /** + * The absolute position of the start of the data (inclusive). + */ + public final long startPosition; + /** + * The absolute position of the end of the data (exclusive). + */ + public final long endPosition; + /** + * Whether the node has been initialized. Remains true after {@link #clear()}. + */ + public boolean wasInitialized; + /** + * The {@link Allocation}, or {@code null} if the node is not initialized. + */ + @Nullable public Allocation allocation; + /** + * The next {@link AllocationNode} in the list, or {@code null} if the node has not been + * initialized. Remains set after {@link #clear()}. + */ + @Nullable public AllocationNode next; + + /** + * @param startPosition See {@link #startPosition}. + * @param allocationLength The length of the {@link Allocation} with which this node will be + * initialized. + */ + public AllocationNode(long startPosition, int allocationLength) { + this.startPosition = startPosition; + this.endPosition = startPosition + allocationLength; + } + + /** + * Initializes the node. + * + * @param allocation The node's {@link Allocation}. + * @param next The next {@link AllocationNode}. + */ + public void initialize(Allocation allocation, AllocationNode next) { + this.allocation = allocation; + this.next = next; + wasInitialized = true; + } + + /** + * Gets the offset into the {@link #allocation}'s {@link Allocation#data} that corresponds to + * the specified absolute position. + * + * @param absolutePosition The absolute position. + * @return The corresponding offset into the allocation's data. + */ + public int translateOffset(long absolutePosition) { + return (int) (absolutePosition - startPosition) + allocation.offset; + } + + /** + * Clears {@link #allocation} and {@link #next}. + * + * @return The cleared next {@link AllocationNode}. + */ + public AllocationNode clear() { + allocation = null; + AllocationNode temp = next; + next = null; + return temp; + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SequenceableLoader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SequenceableLoader.java index 47267b1ed..3f80161c7 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SequenceableLoader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SequenceableLoader.java @@ -36,6 +36,14 @@ public interface SequenceableLoader { } + /** + * Returns an estimate of the position up to which data is buffered. + * + * @return An estimate of the absolute position in microseconds up to which data is buffered, or + * {@link C#TIME_END_OF_SOURCE} if the data is fully buffered. + */ + long getBufferedPositionUs(); + /** * Returns the next load time, or {@link C#TIME_END_OF_SOURCE} if loading has finished. */ diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SinglePeriodTimeline.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SinglePeriodTimeline.java index e1f5479f3..a77d22ce3 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SinglePeriodTimeline.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SinglePeriodTimeline.java @@ -26,6 +26,8 @@ public final class SinglePeriodTimeline extends Timeline { private static final Object ID = new Object(); + private final long presentationStartTimeMs; + private final long windowStartTimeMs; private final long periodDurationUs; private final long windowDurationUs; private final long windowPositionInPeriodUs; @@ -45,8 +47,8 @@ public final class SinglePeriodTimeline extends Timeline { } /** - * Creates a timeline with one period of known duration, and a window of known duration starting - * at a specified position in the period. + * Creates a timeline with one period, and a window of known duration starting at a specified + * position in the period. * * @param periodDurationUs The duration of the period in microseconds. * @param windowDurationUs The duration of the window in microseconds. @@ -60,6 +62,31 @@ public final class SinglePeriodTimeline extends Timeline { public SinglePeriodTimeline(long periodDurationUs, long windowDurationUs, long windowPositionInPeriodUs, long windowDefaultStartPositionUs, boolean isSeekable, boolean isDynamic) { + this(C.TIME_UNSET, C.TIME_UNSET, periodDurationUs, windowDurationUs, windowPositionInPeriodUs, + windowDefaultStartPositionUs, isSeekable, isDynamic); + } + + /** + * Creates a timeline with one period, and a window of known duration starting at a specified + * position in the period. + * + * @param presentationStartTimeMs The start time of the presentation in milliseconds since the + * epoch. + * @param windowStartTimeMs The window's start time in milliseconds since the epoch. + * @param periodDurationUs The duration of the period in microseconds. + * @param windowDurationUs The duration of the window in microseconds. + * @param windowPositionInPeriodUs The position of the start of the window in the period, in + * microseconds. + * @param windowDefaultStartPositionUs The default position relative to the start of the window at + * which to begin playback, in microseconds. + * @param isSeekable Whether seeking is supported within the window. + * @param isDynamic Whether the window may change when the timeline is updated. + */ + public SinglePeriodTimeline(long presentationStartTimeMs, long windowStartTimeMs, + long periodDurationUs, long windowDurationUs, long windowPositionInPeriodUs, + long windowDefaultStartPositionUs, boolean isSeekable, boolean isDynamic) { + this.presentationStartTimeMs = presentationStartTimeMs; + this.windowStartTimeMs = windowStartTimeMs; this.periodDurationUs = periodDurationUs; this.windowDurationUs = windowDurationUs; this.windowPositionInPeriodUs = windowPositionInPeriodUs; @@ -86,7 +113,7 @@ public final class SinglePeriodTimeline extends Timeline { windowDefaultStartPositionUs = C.TIME_UNSET; } } - return window.set(id, C.TIME_UNSET, C.TIME_UNSET, isSeekable, isDynamic, + return window.set(id, presentationStartTimeMs, windowStartTimeMs, isSeekable, isDynamic, windowDefaultStartPositionUs, windowDurationUs, 0, 0, windowPositionInPeriodUs); } @@ -99,7 +126,7 @@ public final class SinglePeriodTimeline extends Timeline { public Period getPeriod(int periodIndex, Period period, boolean setIds) { Assertions.checkIndex(periodIndex, 0, 1); Object id = setIds ? ID : null; - return period.set(id, id, 0, periodDurationUs, -windowPositionInPeriodUs, false); + return period.set(id, id, 0, periodDurationUs, -windowPositionInPeriodUs); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SingleSampleMediaPeriod.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SingleSampleMediaPeriod.java index d641b013a..1d1944858 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SingleSampleMediaPeriod.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SingleSampleMediaPeriod.java @@ -79,7 +79,7 @@ import java.util.Arrays; } @Override - public void prepare(Callback callback) { + public void prepare(Callback callback, long positionUs) { callback.onPrepared(this); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SingleSampleMediaSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SingleSampleMediaSource.java index 27859df7d..096481686 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SingleSampleMediaSource.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SingleSampleMediaSource.java @@ -95,8 +95,8 @@ public final class SingleSampleMediaSource implements MediaSource { } @Override - public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { - Assertions.checkArgument(index == 0); + public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { + Assertions.checkArgument(id.periodIndex == 0); return new SingleSampleMediaPeriod(uri, dataSourceFactory, format, minLoadableRetryCount, eventHandler, eventListener, eventSourceId); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/TrackGroup.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/TrackGroup.java index bca7c05a7..accea2493 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/TrackGroup.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/TrackGroup.java @@ -42,7 +42,7 @@ public final class TrackGroup { private int hashCode; /** - * @param formats The track formats. Must not be null or contain null elements. + * @param formats The track formats. Must not be null, contain null elements or be of length 0. */ public TrackGroup(Format... formats) { Assertions.checkState(formats.length > 0); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/BaseMediaChunkOutput.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/BaseMediaChunkOutput.java index ea6cae6b8..9cb74dd0c 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/BaseMediaChunkOutput.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/BaseMediaChunkOutput.java @@ -16,9 +16,9 @@ package org.telegram.messenger.exoplayer2.source.chunk; import android.util.Log; -import org.telegram.messenger.exoplayer2.extractor.DefaultTrackOutput; import org.telegram.messenger.exoplayer2.extractor.DummyTrackOutput; import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.source.SampleQueue; import org.telegram.messenger.exoplayer2.source.chunk.ChunkExtractorWrapper.TrackOutputProvider; /** @@ -29,22 +29,22 @@ import org.telegram.messenger.exoplayer2.source.chunk.ChunkExtractorWrapper.Trac private static final String TAG = "BaseMediaChunkOutput"; private final int[] trackTypes; - private final DefaultTrackOutput[] trackOutputs; + private final SampleQueue[] sampleQueues; /** * @param trackTypes The track types of the individual track outputs. - * @param trackOutputs The individual track outputs. + * @param sampleQueues The individual sample queues. */ - public BaseMediaChunkOutput(int[] trackTypes, DefaultTrackOutput[] trackOutputs) { + public BaseMediaChunkOutput(int[] trackTypes, SampleQueue[] sampleQueues) { this.trackTypes = trackTypes; - this.trackOutputs = trackOutputs; + this.sampleQueues = sampleQueues; } @Override public TrackOutput track(int id, int type) { for (int i = 0; i < trackTypes.length; i++) { if (type == trackTypes[i]) { - return trackOutputs[i]; + return sampleQueues[i]; } } Log.e(TAG, "Unmatched track of type: " + type); @@ -52,13 +52,13 @@ import org.telegram.messenger.exoplayer2.source.chunk.ChunkExtractorWrapper.Trac } /** - * Returns the current absolute write indices of the individual track outputs. + * Returns the current absolute write indices of the individual sample queues. */ public int[] getWriteIndices() { - int[] writeIndices = new int[trackOutputs.length]; - for (int i = 0; i < trackOutputs.length; i++) { - if (trackOutputs[i] != null) { - writeIndices[i] = trackOutputs[i].getWriteIndex(); + int[] writeIndices = new int[sampleQueues.length]; + for (int i = 0; i < sampleQueues.length; i++) { + if (sampleQueues[i] != null) { + writeIndices[i] = sampleQueues[i].getWriteIndex(); } } return writeIndices; @@ -66,12 +66,12 @@ import org.telegram.messenger.exoplayer2.source.chunk.ChunkExtractorWrapper.Trac /** * Sets an offset that will be added to the timestamps (and sub-sample timestamps) of samples - * subsequently written to the track outputs. + * subsequently written to the sample queues. */ public void setSampleOffsetUs(long sampleOffsetUs) { - for (DefaultTrackOutput trackOutput : trackOutputs) { - if (trackOutput != null) { - trackOutput.setSampleOffsetUs(sampleOffsetUs); + for (SampleQueue sampleQueue : sampleQueues) { + if (sampleQueue != null) { + sampleQueue.setSampleOffsetUs(sampleOffsetUs); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ChunkExtractorWrapper.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ChunkExtractorWrapper.java index e6d041b61..b38b76513 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ChunkExtractorWrapper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ChunkExtractorWrapper.java @@ -186,8 +186,8 @@ public final class ChunkExtractorWrapper implements ExtractorOutput { @Override public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset, - byte[] encryptionKey) { - trackOutput.sampleMetadata(timeUs, flags, size, offset, encryptionKey); + CryptoData cryptoData) { + trackOutput.sampleMetadata(timeUs, flags, size, offset, cryptoData); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ChunkSampleStream.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ChunkSampleStream.java index 6cb657b35..9e5f63ab1 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ChunkSampleStream.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ChunkSampleStream.java @@ -19,8 +19,8 @@ import org.telegram.messenger.exoplayer2.C; import org.telegram.messenger.exoplayer2.Format; import org.telegram.messenger.exoplayer2.FormatHolder; import org.telegram.messenger.exoplayer2.decoder.DecoderInputBuffer; -import org.telegram.messenger.exoplayer2.extractor.DefaultTrackOutput; import org.telegram.messenger.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; +import org.telegram.messenger.exoplayer2.source.SampleQueue; import org.telegram.messenger.exoplayer2.source.SampleStream; import org.telegram.messenger.exoplayer2.source.SequenceableLoader; import org.telegram.messenger.exoplayer2.upstream.Allocator; @@ -36,7 +36,7 @@ import java.util.List; * May also be configured to expose additional embedded {@link SampleStream}s. */ public class ChunkSampleStream implements SampleStream, SequenceableLoader, - Loader.Callback { + Loader.Callback, Loader.ReleaseCallback { private final int primaryTrackType; private final int[] embeddedTrackTypes; @@ -49,8 +49,8 @@ public class ChunkSampleStream implements SampleStream, S private final ChunkHolder nextChunkHolder; private final LinkedList mediaChunks; private final List readOnlyMediaChunks; - private final DefaultTrackOutput primarySampleQueue; - private final DefaultTrackOutput[] embeddedSampleQueues; + private final SampleQueue primarySampleQueue; + private final SampleQueue[] embeddedSampleQueues; private final BaseMediaChunkOutput mediaChunkOutput; private Format primaryDownstreamTrackFormat; @@ -85,19 +85,19 @@ public class ChunkSampleStream implements SampleStream, S readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks); int embeddedTrackCount = embeddedTrackTypes == null ? 0 : embeddedTrackTypes.length; - embeddedSampleQueues = new DefaultTrackOutput[embeddedTrackCount]; + embeddedSampleQueues = new SampleQueue[embeddedTrackCount]; embeddedTracksSelected = new boolean[embeddedTrackCount]; int[] trackTypes = new int[1 + embeddedTrackCount]; - DefaultTrackOutput[] sampleQueues = new DefaultTrackOutput[1 + embeddedTrackCount]; + SampleQueue[] sampleQueues = new SampleQueue[1 + embeddedTrackCount]; - primarySampleQueue = new DefaultTrackOutput(allocator); + primarySampleQueue = new SampleQueue(allocator); trackTypes[0] = primaryTrackType; sampleQueues[0] = primarySampleQueue; for (int i = 0; i < embeddedTrackCount; i++) { - DefaultTrackOutput trackOutput = new DefaultTrackOutput(allocator); - embeddedSampleQueues[i] = trackOutput; - sampleQueues[i + 1] = trackOutput; + SampleQueue sampleQueue = new SampleQueue(allocator); + embeddedSampleQueues[i] = sampleQueue; + sampleQueues[i + 1] = sampleQueue; trackTypes[i + 1] = embeddedTrackTypes[i]; } @@ -106,17 +106,20 @@ public class ChunkSampleStream implements SampleStream, S lastSeekPositionUs = positionUs; } + // TODO: Generalize this method to also discard from the primary sample queue and stop discarding + // from this queue in readData and skipData. This will cause samples to be kept in the queue until + // they've been rendered, rather than being discarded as soon as they're read by the renderer. + // This will make in-buffer seeks more likely when seeking slightly forward from the current + // position. This change will need handling with care, in particular when considering removal of + // chunks from the front of the mediaChunks list. /** - * Discards buffered media for embedded tracks that are not currently selected, up to the - * specified position. + * Discards buffered media for embedded tracks, up to the specified position. * * @param positionUs The position to discard up to, in microseconds. */ - public void discardUnselectedEmbeddedTracksTo(long positionUs) { + public void discardEmbeddedTracksTo(long positionUs) { for (int i = 0; i < embeddedSampleQueues.length; i++) { - if (!embeddedTracksSelected[i]) { - embeddedSampleQueues[i].skipToKeyframeBefore(positionUs, true); - } + embeddedSampleQueues[i].discardTo(positionUs, true, embeddedTracksSelected[i]); } } @@ -135,7 +138,8 @@ public class ChunkSampleStream implements SampleStream, S if (embeddedTrackTypes[i] == trackType) { Assertions.checkState(!embeddedTracksSelected[i]); embeddedTracksSelected[i] = true; - embeddedSampleQueues[i].skipToKeyframeBefore(positionUs, true); + embeddedSampleQueues[i].rewind(); + embeddedSampleQueues[i].advanceTo(positionUs, true, true); return new EmbeddedSampleStream(this, embeddedSampleQueues[i], i); } } @@ -181,19 +185,15 @@ public class ChunkSampleStream implements SampleStream, S public void seekToUs(long positionUs) { lastSeekPositionUs = positionUs; // If we're not pending a reset, see if we can seek within the primary sample queue. - boolean seekInsideBuffer = !isPendingReset() && primarySampleQueue.skipToKeyframeBefore( - positionUs, positionUs < getNextLoadPositionUs()); + boolean seekInsideBuffer = !isPendingReset() && primarySampleQueue.advanceTo(positionUs, true, + positionUs < getNextLoadPositionUs()); if (seekInsideBuffer) { - // We succeeded. We need to discard any chunks that we've moved past and perform the seek for - // any embedded streams as well. - while (mediaChunks.size() > 1 - && mediaChunks.get(1).getFirstSampleIndex(0) <= primarySampleQueue.getReadIndex()) { - mediaChunks.removeFirst(); - } - // TODO: For this to work correctly, the embedded streams must not discard anything from their - // sample queues beyond the current read position of the primary stream. - for (DefaultTrackOutput embeddedSampleQueue : embeddedSampleQueues) { - embeddedSampleQueue.skipToKeyframeBefore(positionUs, true); + // We succeeded. Discard samples and corresponding chunks prior to the seek position. + discardDownstreamMediaChunks(primarySampleQueue.getReadIndex()); + primarySampleQueue.discardToRead(); + for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) { + embeddedSampleQueue.rewind(); + embeddedSampleQueue.discardTo(positionUs, true, false); } } else { // We failed, and need to restart. @@ -203,9 +203,9 @@ public class ChunkSampleStream implements SampleStream, S if (loader.isLoading()) { loader.cancelLoading(); } else { - primarySampleQueue.reset(true); - for (DefaultTrackOutput embeddedSampleQueue : embeddedSampleQueues) { - embeddedSampleQueue.reset(true); + primarySampleQueue.reset(); + for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) { + embeddedSampleQueue.reset(); } } } @@ -217,18 +217,29 @@ public class ChunkSampleStream implements SampleStream, S * This method should be called when the stream is no longer required. */ public void release() { - primarySampleQueue.disable(); - for (DefaultTrackOutput embeddedSampleQueue : embeddedSampleQueues) { - embeddedSampleQueue.disable(); + boolean releasedSynchronously = loader.release(this); + if (!releasedSynchronously) { + // Discard as much as we can synchronously. + primarySampleQueue.discardToEnd(); + for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) { + embeddedSampleQueue.discardToEnd(); + } + } + } + + @Override + public void onLoaderReleased() { + primarySampleQueue.reset(); + for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) { + embeddedSampleQueue.reset(); } - loader.release(); } // SampleStream implementation. @Override public boolean isReady() { - return loadingFinished || (!isPendingReset() && !primarySampleQueue.isEmpty()); + return loadingFinished || (!isPendingReset() && primarySampleQueue.hasNextSample()); } @Override @@ -246,17 +257,22 @@ public class ChunkSampleStream implements SampleStream, S return C.RESULT_NOTHING_READ; } discardDownstreamMediaChunks(primarySampleQueue.getReadIndex()); - return primarySampleQueue.readData(formatHolder, buffer, formatRequired, loadingFinished, + int result = primarySampleQueue.read(formatHolder, buffer, formatRequired, loadingFinished, lastSeekPositionUs); + if (result == C.RESULT_BUFFER_READ) { + primarySampleQueue.discardToRead(); + } + return result; } @Override public void skipData(long positionUs) { if (loadingFinished && positionUs > primarySampleQueue.getLargestQueuedTimestampUs()) { - primarySampleQueue.skipAll(); + primarySampleQueue.advanceToEnd(); } else { - primarySampleQueue.skipToKeyframeBefore(positionUs, true); + primarySampleQueue.advanceTo(positionUs, true, true); } + primarySampleQueue.discardToRead(); } // Loader.Callback implementation. @@ -279,9 +295,9 @@ public class ChunkSampleStream implements SampleStream, S loadable.startTimeUs, loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded()); if (!released) { - primarySampleQueue.reset(true); - for (DefaultTrackOutput embeddedSampleQueue : embeddedSampleQueues) { - embeddedSampleQueue.reset(true); + primarySampleQueue.reset(); + for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) { + embeddedSampleQueue.reset(); } callback.onContinueLoadingRequested(this); } @@ -336,6 +352,7 @@ public class ChunkSampleStream implements SampleStream, S nextChunkHolder.clear(); if (endOfStream) { + pendingResetPositionUs = C.TIME_UNSET; loadingFinished = true; return true; } @@ -389,18 +406,20 @@ public class ChunkSampleStream implements SampleStream, S } private void discardDownstreamMediaChunks(int primaryStreamReadIndex) { - while (mediaChunks.size() > 1 - && mediaChunks.get(1).getFirstSampleIndex(0) <= primaryStreamReadIndex) { - mediaChunks.removeFirst(); + if (!mediaChunks.isEmpty()) { + while (mediaChunks.size() > 1 + && mediaChunks.get(1).getFirstSampleIndex(0) <= primaryStreamReadIndex) { + mediaChunks.removeFirst(); + } + BaseMediaChunk currentChunk = mediaChunks.getFirst(); + Format trackFormat = currentChunk.trackFormat; + if (!trackFormat.equals(primaryDownstreamTrackFormat)) { + eventDispatcher.downstreamFormatChanged(primaryTrackType, trackFormat, + currentChunk.trackSelectionReason, currentChunk.trackSelectionData, + currentChunk.startTimeUs); + } + primaryDownstreamTrackFormat = trackFormat; } - BaseMediaChunk currentChunk = mediaChunks.getFirst(); - Format trackFormat = currentChunk.trackFormat; - if (!trackFormat.equals(primaryDownstreamTrackFormat)) { - eventDispatcher.downstreamFormatChanged(primaryTrackType, trackFormat, - currentChunk.trackSelectionReason, currentChunk.trackSelectionData, - currentChunk.startTimeUs); - } - primaryDownstreamTrackFormat = trackFormat; } /** @@ -413,18 +432,18 @@ public class ChunkSampleStream implements SampleStream, S if (mediaChunks.size() <= queueLength) { return false; } - long startTimeUs = 0; + BaseMediaChunk removed; + long startTimeUs; long endTimeUs = mediaChunks.getLast().endTimeUs; - BaseMediaChunk removed = null; - while (mediaChunks.size() > queueLength) { + do { removed = mediaChunks.removeLast(); startTimeUs = removed.startTimeUs; - loadingFinished = false; - } + } while (mediaChunks.size() > queueLength); primarySampleQueue.discardUpstreamSamples(removed.getFirstSampleIndex(0)); for (int i = 0; i < embeddedSampleQueues.length; i++) { embeddedSampleQueues[i].discardUpstreamSamples(removed.getFirstSampleIndex(i + 1)); } + loadingFinished = false; eventDispatcher.upstreamDiscarded(primaryTrackType, startTimeUs, endTimeUs); return true; } @@ -436,11 +455,10 @@ public class ChunkSampleStream implements SampleStream, S public final ChunkSampleStream parent; - private final DefaultTrackOutput sampleQueue; + private final SampleQueue sampleQueue; private final int index; - public EmbeddedSampleStream(ChunkSampleStream parent, DefaultTrackOutput sampleQueue, - int index) { + public EmbeddedSampleStream(ChunkSampleStream parent, SampleQueue sampleQueue, int index) { this.parent = parent; this.sampleQueue = sampleQueue; this.index = index; @@ -448,15 +466,15 @@ public class ChunkSampleStream implements SampleStream, S @Override public boolean isReady() { - return loadingFinished || (!isPendingReset() && !sampleQueue.isEmpty()); + return loadingFinished || (!isPendingReset() && sampleQueue.hasNextSample()); } @Override public void skipData(long positionUs) { if (loadingFinished && positionUs > sampleQueue.getLargestQueuedTimestampUs()) { - sampleQueue.skipAll(); + sampleQueue.advanceToEnd(); } else { - sampleQueue.skipToKeyframeBefore(positionUs, true); + sampleQueue.advanceTo(positionUs, true, true); } } @@ -471,7 +489,7 @@ public class ChunkSampleStream implements SampleStream, S if (isPendingReset()) { return C.RESULT_NOTHING_READ; } - return sampleQueue.readData(formatHolder, buffer, formatRequired, loadingFinished, + return sampleQueue.read(formatHolder, buffer, formatRequired, loadingFinished, lastSeekPositionUs); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ContainerMediaChunk.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ContainerMediaChunk.java index d55a34abf..5525409c0 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ContainerMediaChunk.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ContainerMediaChunk.java @@ -93,7 +93,7 @@ public class ContainerMediaChunk extends BaseMediaChunk { @SuppressWarnings("NonAtomicVolatileUpdate") @Override public final void load() throws IOException, InterruptedException { - DataSpec loadDataSpec = Util.getRemainderDataSpec(dataSpec, bytesLoaded); + DataSpec loadDataSpec = dataSpec.subrange(bytesLoaded); try { // Create and open the input. ExtractorInput input = new DefaultExtractorInput(dataSource, diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/InitializationChunk.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/InitializationChunk.java index 750b1f284..8dd9b42d1 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/InitializationChunk.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/InitializationChunk.java @@ -72,7 +72,7 @@ public final class InitializationChunk extends Chunk { @SuppressWarnings("NonAtomicVolatileUpdate") @Override public void load() throws IOException, InterruptedException { - DataSpec loadDataSpec = Util.getRemainderDataSpec(dataSpec, bytesLoaded); + DataSpec loadDataSpec = dataSpec.subrange(bytesLoaded); try { // Create and open the input. ExtractorInput input = new DefaultExtractorInput(dataSource, diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/SingleSampleMediaChunk.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/SingleSampleMediaChunk.java index f51d5c16d..d2325eacf 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/SingleSampleMediaChunk.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/SingleSampleMediaChunk.java @@ -85,7 +85,7 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk { @SuppressWarnings("NonAtomicVolatileUpdate") @Override public void load() throws IOException, InterruptedException { - DataSpec loadDataSpec = Util.getRemainderDataSpec(dataSpec, bytesLoaded); + DataSpec loadDataSpec = dataSpec.subrange(bytesLoaded); try { // Create and open the input. long length = dataSource.open(loadDataSpec); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DashChunkSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DashChunkSource.java index 6e63d66d6..dd6ca2f64 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DashChunkSource.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DashChunkSource.java @@ -28,8 +28,8 @@ public interface DashChunkSource extends ChunkSource { interface Factory { DashChunkSource createDashChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, - DashManifest manifest, int periodIndex, int adaptationSetIndex, - TrackSelection trackSelection, long elapsedRealtimeOffsetMs, + DashManifest manifest, int periodIndex, int[] adaptationSetIndices, + TrackSelection trackSelection, int type, long elapsedRealtimeOffsetMs, boolean enableEventMessageTrack, boolean enableCea608Track); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DashMediaPeriod.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DashMediaPeriod.java index 134a6c2b4..610f59207 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DashMediaPeriod.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DashMediaPeriod.java @@ -16,6 +16,7 @@ package org.telegram.messenger.exoplayer2.source.dash; import android.util.Pair; +import android.util.SparseIntArray; import org.telegram.messenger.exoplayer2.C; import org.telegram.messenger.exoplayer2.Format; import org.telegram.messenger.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; @@ -30,13 +31,14 @@ import org.telegram.messenger.exoplayer2.source.chunk.ChunkSampleStream; import org.telegram.messenger.exoplayer2.source.chunk.ChunkSampleStream.EmbeddedSampleStream; import org.telegram.messenger.exoplayer2.source.dash.manifest.AdaptationSet; import org.telegram.messenger.exoplayer2.source.dash.manifest.DashManifest; +import org.telegram.messenger.exoplayer2.source.dash.manifest.Descriptor; import org.telegram.messenger.exoplayer2.source.dash.manifest.Representation; -import org.telegram.messenger.exoplayer2.source.dash.manifest.SchemeValuePair; import org.telegram.messenger.exoplayer2.trackselection.TrackSelection; import org.telegram.messenger.exoplayer2.upstream.Allocator; import org.telegram.messenger.exoplayer2.upstream.LoaderErrorThrower; import org.telegram.messenger.exoplayer2.util.MimeTypes; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -55,7 +57,7 @@ import java.util.List; private final LoaderErrorThrower manifestLoaderErrorThrower; private final Allocator allocator; private final TrackGroupArray trackGroups; - private final EmbeddedTrackInfo[] embeddedTrackInfos; + private final TrackGroupInfo[] trackGroupInfos; private Callback callback; private ChunkSampleStream[] sampleStreams; @@ -80,9 +82,9 @@ import java.util.List; sampleStreams = newSampleStreamArray(0); sequenceableLoader = new CompositeSequenceableLoader(sampleStreams); adaptationSets = manifest.getPeriod(periodIndex).adaptationSets; - Pair result = buildTrackGroups(adaptationSets); + Pair result = buildTrackGroups(adaptationSets); trackGroups = result.first; - embeddedTrackInfos = result.second; + trackGroupInfos = result.second; } public void updateManifest(DashManifest manifest, int periodIndex) { @@ -104,7 +106,7 @@ import java.util.List; } @Override - public void prepare(Callback callback) { + public void prepare(Callback callback, long positionUs) { this.callback = callback; callback.onPrepared(this); } @@ -122,7 +124,6 @@ import java.util.List; @Override public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { - int adaptationSetCount = adaptationSets.size(); HashMap> primarySampleStreams = new HashMap<>(); // First pass for primary tracks. for (int i = 0; i < selections.length; i++) { @@ -133,14 +134,15 @@ import java.util.List; stream.release(); streams[i] = null; } else { - int adaptationSetIndex = trackGroups.indexOf(selections[i].getTrackGroup()); - primarySampleStreams.put(adaptationSetIndex, stream); + int trackGroupIndex = trackGroups.indexOf(selections[i].getTrackGroup()); + primarySampleStreams.put(trackGroupIndex, stream); } } if (streams[i] == null && selections[i] != null) { int trackGroupIndex = trackGroups.indexOf(selections[i].getTrackGroup()); - if (trackGroupIndex < adaptationSetCount) { - ChunkSampleStream stream = buildSampleStream(trackGroupIndex, + TrackGroupInfo trackGroupInfo = trackGroupInfos[trackGroupIndex]; + if (trackGroupInfo.isPrimary) { + ChunkSampleStream stream = buildSampleStream(trackGroupInfo, selections[i], positionUs); primarySampleStreams.put(trackGroupIndex, stream); streams[i] = stream; @@ -160,11 +162,10 @@ import java.util.List; // may have been replaced, selected or deselected. if (selections[i] != null) { int trackGroupIndex = trackGroups.indexOf(selections[i].getTrackGroup()); - if (trackGroupIndex >= adaptationSetCount) { - int embeddedTrackIndex = trackGroupIndex - adaptationSetCount; - EmbeddedTrackInfo embeddedTrackInfo = embeddedTrackInfos[embeddedTrackIndex]; - int adaptationSetIndex = embeddedTrackInfo.adaptationSetIndex; - ChunkSampleStream primaryStream = primarySampleStreams.get(adaptationSetIndex); + TrackGroupInfo trackGroupInfo = trackGroupInfos[trackGroupIndex]; + if (!trackGroupInfo.isPrimary) { + ChunkSampleStream primaryStream = primarySampleStreams.get( + trackGroupInfo.primaryTrackGroupIndex); SampleStream stream = streams[i]; boolean mayRetainStream = primaryStream == null ? stream instanceof EmptySampleStream : (stream instanceof EmbeddedSampleStream @@ -172,7 +173,7 @@ import java.util.List; if (!mayRetainStream) { releaseIfEmbeddedSampleStream(stream); streams[i] = primaryStream == null ? new EmptySampleStream() - : primaryStream.selectEmbeddedTrack(positionUs, embeddedTrackInfo.trackType); + : primaryStream.selectEmbeddedTrack(positionUs, trackGroupInfo.trackType); streamResetFlags[i] = true; } } @@ -187,7 +188,7 @@ import java.util.List; @Override public void discardBuffer(long positionUs) { for (ChunkSampleStream sampleStream : sampleStreams) { - sampleStream.discardUnselectedEmbeddedTracksTo(positionUs); + sampleStream.discardEmbeddedTracksTo(positionUs); } } @@ -208,14 +209,7 @@ import java.util.List; @Override public long getBufferedPositionUs() { - long bufferedPositionUs = Long.MAX_VALUE; - for (ChunkSampleStream sampleStream : sampleStreams) { - long rendererBufferedPositionUs = sampleStream.getBufferedPositionUs(); - if (rendererBufferedPositionUs != C.TIME_END_OF_SOURCE) { - bufferedPositionUs = Math.min(bufferedPositionUs, rendererBufferedPositionUs); - } - } - return bufferedPositionUs == Long.MAX_VALUE ? C.TIME_END_OF_SOURCE : bufferedPositionUs; + return sequenceableLoader.getBufferedPositionUs(); } @Override @@ -235,49 +229,114 @@ import java.util.List; // Internal methods. - private static Pair buildTrackGroups( + private static Pair buildTrackGroups( List adaptationSets) { - int adaptationSetCount = adaptationSets.size(); - int embeddedTrackCount = getEmbeddedTrackCount(adaptationSets); - TrackGroup[] trackGroupArray = new TrackGroup[adaptationSetCount + embeddedTrackCount]; - EmbeddedTrackInfo[] embeddedTrackInfos = new EmbeddedTrackInfo[embeddedTrackCount]; + int[][] groupedAdaptationSetIndices = getGroupedAdaptationSetIndices(adaptationSets); - int embeddedTrackIndex = 0; - for (int i = 0; i < adaptationSetCount; i++) { - AdaptationSet adaptationSet = adaptationSets.get(i); - List representations = adaptationSet.representations; + int primaryGroupCount = groupedAdaptationSetIndices.length; + boolean[] primaryGroupHasEventMessageTrackFlags = new boolean[primaryGroupCount]; + boolean[] primaryGroupHasCea608TrackFlags = new boolean[primaryGroupCount]; + int totalGroupCount = primaryGroupCount; + for (int i = 0; i < primaryGroupCount; i++) { + if (hasEventMessageTrack(adaptationSets, groupedAdaptationSetIndices[i])) { + primaryGroupHasEventMessageTrackFlags[i] = true; + totalGroupCount++; + } + if (hasCea608Track(adaptationSets, groupedAdaptationSetIndices[i])) { + primaryGroupHasCea608TrackFlags[i] = true; + totalGroupCount++; + } + } + + TrackGroup[] trackGroups = new TrackGroup[totalGroupCount]; + TrackGroupInfo[] trackGroupInfos = new TrackGroupInfo[totalGroupCount]; + + int trackGroupCount = 0; + for (int i = 0; i < primaryGroupCount; i++) { + int[] adaptationSetIndices = groupedAdaptationSetIndices[i]; + List representations = new ArrayList<>(); + for (int adaptationSetIndex : adaptationSetIndices) { + representations.addAll(adaptationSets.get(adaptationSetIndex).representations); + } Format[] formats = new Format[representations.size()]; for (int j = 0; j < formats.length; j++) { formats[j] = representations.get(j).format; } - trackGroupArray[i] = new TrackGroup(formats); - if (hasEventMessageTrack(adaptationSet)) { - Format format = Format.createSampleFormat(adaptationSet.id + ":emsg", + + AdaptationSet firstAdaptationSet = adaptationSets.get(adaptationSetIndices[0]); + int primaryTrackGroupIndex = trackGroupCount; + boolean hasEventMessageTrack = primaryGroupHasEventMessageTrackFlags[i]; + boolean hasCea608Track = primaryGroupHasCea608TrackFlags[i]; + + trackGroups[trackGroupCount] = new TrackGroup(formats); + trackGroupInfos[trackGroupCount++] = new TrackGroupInfo(firstAdaptationSet.type, + adaptationSetIndices, primaryTrackGroupIndex, true, hasEventMessageTrack, hasCea608Track); + if (hasEventMessageTrack) { + Format format = Format.createSampleFormat(firstAdaptationSet.id + ":emsg", MimeTypes.APPLICATION_EMSG, null, Format.NO_VALUE, null); - trackGroupArray[adaptationSetCount + embeddedTrackIndex] = new TrackGroup(format); - embeddedTrackInfos[embeddedTrackIndex++] = new EmbeddedTrackInfo(i, C.TRACK_TYPE_METADATA); + trackGroups[trackGroupCount] = new TrackGroup(format); + trackGroupInfos[trackGroupCount++] = new TrackGroupInfo(C.TRACK_TYPE_METADATA, + adaptationSetIndices, primaryTrackGroupIndex, false, false, false); } - if (hasCea608Track(adaptationSet)) { - Format format = Format.createTextSampleFormat(adaptationSet.id + ":cea608", - MimeTypes.APPLICATION_CEA608, null, Format.NO_VALUE, 0, null, null); - trackGroupArray[adaptationSetCount + embeddedTrackIndex] = new TrackGroup(format); - embeddedTrackInfos[embeddedTrackIndex++] = new EmbeddedTrackInfo(i, C.TRACK_TYPE_TEXT); + if (hasCea608Track) { + Format format = Format.createTextSampleFormat(firstAdaptationSet.id + ":cea608", + MimeTypes.APPLICATION_CEA608, 0, null); + trackGroups[trackGroupCount] = new TrackGroup(format); + trackGroupInfos[trackGroupCount++] = new TrackGroupInfo(C.TRACK_TYPE_TEXT, + adaptationSetIndices, primaryTrackGroupIndex, false, false, false); } } - return Pair.create(new TrackGroupArray(trackGroupArray), embeddedTrackInfos); + return Pair.create(new TrackGroupArray(trackGroups), trackGroupInfos); } - private ChunkSampleStream buildSampleStream(int adaptationSetIndex, + private static int[][] getGroupedAdaptationSetIndices(List adaptationSets) { + int adaptationSetCount = adaptationSets.size(); + SparseIntArray idToIndexMap = new SparseIntArray(adaptationSetCount); + for (int i = 0; i < adaptationSetCount; i++) { + idToIndexMap.put(adaptationSets.get(i).id, i); + } + + int[][] groupedAdaptationSetIndices = new int[adaptationSetCount][]; + boolean[] adaptationSetUsedFlags = new boolean[adaptationSetCount]; + + int groupCount = 0; + for (int i = 0; i < adaptationSetCount; i++) { + if (adaptationSetUsedFlags[i]) { + // This adaptation set has already been included in a group. + continue; + } + adaptationSetUsedFlags[i] = true; + Descriptor adaptationSetSwitchingProperty = findAdaptationSetSwitchingProperty( + adaptationSets.get(i).supplementalProperties); + if (adaptationSetSwitchingProperty == null) { + groupedAdaptationSetIndices[groupCount++] = new int[] {i}; + } else { + String[] extraAdaptationSetIds = adaptationSetSwitchingProperty.value.split(","); + int[] adaptationSetIndices = new int[1 + extraAdaptationSetIds.length]; + adaptationSetIndices[0] = i; + for (int j = 0; j < extraAdaptationSetIds.length; j++) { + int extraIndex = idToIndexMap.get(Integer.parseInt(extraAdaptationSetIds[j])); + adaptationSetUsedFlags[extraIndex] = true; + adaptationSetIndices[1 + j] = extraIndex; + } + groupedAdaptationSetIndices[groupCount++] = adaptationSetIndices; + } + } + + return groupCount < adaptationSetCount + ? Arrays.copyOf(groupedAdaptationSetIndices, groupCount) : groupedAdaptationSetIndices; + } + + private ChunkSampleStream buildSampleStream(TrackGroupInfo trackGroupInfo, TrackSelection selection, long positionUs) { - AdaptationSet adaptationSet = adaptationSets.get(adaptationSetIndex); int embeddedTrackCount = 0; int[] embeddedTrackTypes = new int[2]; - boolean enableEventMessageTrack = hasEventMessageTrack(adaptationSet); + boolean enableEventMessageTrack = trackGroupInfo.hasEmbeddedEventMessageTrack; if (enableEventMessageTrack) { embeddedTrackTypes[embeddedTrackCount++] = C.TRACK_TYPE_METADATA; } - boolean enableCea608Track = hasCea608Track(adaptationSet); + boolean enableCea608Track = trackGroupInfo.hasEmbeddedCea608Track; if (enableCea608Track) { embeddedTrackTypes[embeddedTrackCount++] = C.TRACK_TYPE_TEXT; } @@ -285,45 +344,48 @@ import java.util.List; embeddedTrackTypes = Arrays.copyOf(embeddedTrackTypes, embeddedTrackCount); } DashChunkSource chunkSource = chunkSourceFactory.createDashChunkSource( - manifestLoaderErrorThrower, manifest, periodIndex, adaptationSetIndex, selection, - elapsedRealtimeOffset, enableEventMessageTrack, enableCea608Track); - ChunkSampleStream stream = new ChunkSampleStream<>(adaptationSet.type, + manifestLoaderErrorThrower, manifest, periodIndex, trackGroupInfo.adaptationSetIndices, + selection, trackGroupInfo.trackType, elapsedRealtimeOffset, enableEventMessageTrack, + enableCea608Track); + ChunkSampleStream stream = new ChunkSampleStream<>(trackGroupInfo.trackType, embeddedTrackTypes, chunkSource, this, allocator, positionUs, minLoadableRetryCount, eventDispatcher); return stream; } - private static int getEmbeddedTrackCount(List adaptationSets) { - int embeddedTrackCount = 0; - for (int i = 0; i < adaptationSets.size(); i++) { - AdaptationSet adaptationSet = adaptationSets.get(i); - if (hasEventMessageTrack(adaptationSet)) { - embeddedTrackCount++; - } - if (hasCea608Track(adaptationSet)) { - embeddedTrackCount++; + private static Descriptor findAdaptationSetSwitchingProperty(List descriptors) { + for (int i = 0; i < descriptors.size(); i++) { + Descriptor descriptor = descriptors.get(i); + if ("urn:mpeg:dash:adaptation-set-switching:2016".equals(descriptor.schemeIdUri)) { + return descriptor; } } - return embeddedTrackCount; + return null; } - private static boolean hasEventMessageTrack(AdaptationSet adaptationSet) { - List representations = adaptationSet.representations; - for (int i = 0; i < representations.size(); i++) { - Representation representation = representations.get(i); - if (!representation.inbandEventStreams.isEmpty()) { - return true; + private static boolean hasEventMessageTrack(List adaptationSets, + int[] adaptationSetIndices) { + for (int i : adaptationSetIndices) { + List representations = adaptationSets.get(i).representations; + for (int j = 0; j < representations.size(); j++) { + Representation representation = representations.get(j); + if (!representation.inbandEventStreams.isEmpty()) { + return true; + } } } return false; } - private static boolean hasCea608Track(AdaptationSet adaptationSet) { - List descriptors = adaptationSet.accessibilityDescriptors; - for (int i = 0; i < descriptors.size(); i++) { - SchemeValuePair descriptor = descriptors.get(i); - if ("urn:scte:dash:cc:cea-608:2015".equals(descriptor.schemeIdUri)) { - return true; + private static boolean hasCea608Track(List adaptationSets, + int[] adaptationSetIndices) { + for (int i : adaptationSetIndices) { + List descriptors = adaptationSets.get(i).accessibilityDescriptors; + for (int j = 0; j < descriptors.size(); j++) { + Descriptor descriptor = descriptors.get(j); + if ("urn:scte:dash:cc:cea-608:2015".equals(descriptor.schemeIdUri)) { + return true; + } } } return false; @@ -340,14 +402,24 @@ import java.util.List; } } - private static final class EmbeddedTrackInfo { + private static final class TrackGroupInfo { - public final int adaptationSetIndex; + public final int[] adaptationSetIndices; public final int trackType; + public final boolean isPrimary; - public EmbeddedTrackInfo(int adaptationSetIndex, int trackType) { - this.adaptationSetIndex = adaptationSetIndex; + public final int primaryTrackGroupIndex; + public final boolean hasEmbeddedEventMessageTrack; + public final boolean hasEmbeddedCea608Track; + + public TrackGroupInfo(int trackType, int[] adaptationSetIndices, int primaryTrackGroupIndex, + boolean isPrimary, boolean hasEmbeddedEventMessageTrack, boolean hasEmbeddedCea608Track) { this.trackType = trackType; + this.adaptationSetIndices = adaptationSetIndices; + this.primaryTrackGroupIndex = primaryTrackGroupIndex; + this.isPrimary = isPrimary; + this.hasEmbeddedEventMessageTrack = hasEmbeddedEventMessageTrack; + this.hasEmbeddedCea608Track = hasEmbeddedCea608Track; } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DashMediaSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DashMediaSource.java index 334d1ce01..6515a5df4 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DashMediaSource.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DashMediaSource.java @@ -22,6 +22,7 @@ import android.util.Log; import android.util.SparseArray; import org.telegram.messenger.exoplayer2.C; import org.telegram.messenger.exoplayer2.ExoPlayer; +import org.telegram.messenger.exoplayer2.ExoPlayerLibraryInfo; import org.telegram.messenger.exoplayer2.ParserException; import org.telegram.messenger.exoplayer2.Timeline; import org.telegram.messenger.exoplayer2.source.AdaptiveMediaSourceEventListener; @@ -52,6 +53,10 @@ import java.util.TimeZone; */ public final class DashMediaSource implements MediaSource { + static { + ExoPlayerLibraryInfo.registerModule("goog.exo.dash"); + } + /** * The default minimum number of times to retry loading data prior to failing. */ @@ -281,7 +286,8 @@ public final class DashMediaSource implements MediaSource { } @Override - public MediaPeriod createPeriod(int periodIndex, Allocator allocator, long positionUs) { + public MediaPeriod createPeriod(MediaPeriodId periodId, Allocator allocator) { + int periodIndex = periodId.periodIndex; EventDispatcher periodEventDispatcher = eventDispatcher.copyWithMediaTimeOffsetMs( manifest.getPeriod(periodIndex).startMs); DashMediaPeriod mediaPeriod = new DashMediaPeriod(firstPeriodId + periodIndex, manifest, @@ -410,12 +416,14 @@ public final class DashMediaSource implements MediaSource { private void resolveUtcTimingElement(UtcTimingElement timingElement) { String scheme = timingElement.schemeIdUri; - if (Util.areEqual(scheme, "urn:mpeg:dash:utc:direct:2012")) { + if (Util.areEqual(scheme, "urn:mpeg:dash:utc:direct:2014") + || Util.areEqual(scheme, "urn:mpeg:dash:utc:direct:2012")) { resolveUtcTimingElementDirect(timingElement); - } else if (Util.areEqual(scheme, "urn:mpeg:dash:utc:http-iso:2014")) { + } else if (Util.areEqual(scheme, "urn:mpeg:dash:utc:http-iso:2014") + || Util.areEqual(scheme, "urn:mpeg:dash:utc:http-iso:2012")) { resolveUtcTimingElementHttp(timingElement, new Iso8601Parser()); - } else if (Util.areEqual(scheme, "urn:mpeg:dash:utc:http-xsdate:2012") - || Util.areEqual(scheme, "urn:mpeg:dash:utc:http-xsdate:2014")) { + } else if (Util.areEqual(scheme, "urn:mpeg:dash:utc:http-xsdate:2014") + || Util.areEqual(scheme, "urn:mpeg:dash:utc:http-xsdate:2012")) { resolveUtcTimingElementHttp(timingElement, new XsDateTimeParser()); } else { // Unsupported scheme. @@ -625,9 +633,9 @@ public final class DashMediaSource implements MediaSource { private final long windowDefaultStartPositionUs; private final DashManifest manifest; - public DashTimeline(long presentationStartTimeMs, long windowStartTimeMs, - int firstPeriodId, long offsetInFirstPeriodUs, long windowDurationUs, - long windowDefaultStartPositionUs, DashManifest manifest) { + public DashTimeline(long presentationStartTimeMs, long windowStartTimeMs, int firstPeriodId, + long offsetInFirstPeriodUs, long windowDurationUs, long windowDefaultStartPositionUs, + DashManifest manifest) { this.presentationStartTimeMs = presentationStartTimeMs; this.windowStartTimeMs = windowStartTimeMs; this.firstPeriodId = firstPeriodId; @@ -650,7 +658,7 @@ public final class DashMediaSource implements MediaSource { + Assertions.checkIndex(periodIndex, 0, manifest.getPeriodCount()) : null; return period.set(id, uid, 0, manifest.getPeriodDurationUs(periodIndex), C.msToUs(manifest.getPeriod(periodIndex).startMs - manifest.getPeriod(0).startMs) - - offsetInFirstPeriodUs, false); + - offsetInFirstPeriodUs); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DefaultDashChunkSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DefaultDashChunkSource.java index 460c23af4..2ae430a16 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DefaultDashChunkSource.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DefaultDashChunkSource.java @@ -46,6 +46,7 @@ import org.telegram.messenger.exoplayer2.upstream.LoaderErrorThrower; import org.telegram.messenger.exoplayer2.util.MimeTypes; import org.telegram.messenger.exoplayer2.util.Util; import java.io.IOException; +import java.util.ArrayList; import java.util.List; /** @@ -69,28 +70,29 @@ public class DefaultDashChunkSource implements DashChunkSource { @Override public DashChunkSource createDashChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, - DashManifest manifest, int periodIndex, int adaptationSetIndex, - TrackSelection trackSelection, long elapsedRealtimeOffsetMs, + DashManifest manifest, int periodIndex, int[] adaptationSetIndices, + TrackSelection trackSelection, int trackType, long elapsedRealtimeOffsetMs, boolean enableEventMessageTrack, boolean enableCea608Track) { DataSource dataSource = dataSourceFactory.createDataSource(); return new DefaultDashChunkSource(manifestLoaderErrorThrower, manifest, periodIndex, - adaptationSetIndex, trackSelection, dataSource, elapsedRealtimeOffsetMs, + adaptationSetIndices, trackSelection, trackType, dataSource, elapsedRealtimeOffsetMs, maxSegmentsPerLoad, enableEventMessageTrack, enableCea608Track); } } private final LoaderErrorThrower manifestLoaderErrorThrower; - private final int adaptationSetIndex; + private final int[] adaptationSetIndices; private final TrackSelection trackSelection; - private final RepresentationHolder[] representationHolders; + private final int trackType; private final DataSource dataSource; private final long elapsedRealtimeOffsetMs; private final int maxSegmentsPerLoad; + protected final RepresentationHolder[] representationHolders; + private DashManifest manifest; private int periodIndex; - private IOException fatalError; private boolean missingLastSegment; @@ -98,8 +100,9 @@ public class DefaultDashChunkSource implements DashChunkSource { * @param manifestLoaderErrorThrower Throws errors affecting loading of manifests. * @param manifest The initial manifest. * @param periodIndex The index of the period in the manifest. - * @param adaptationSetIndex The index of the adaptation set in the period. + * @param adaptationSetIndices The indices of the adaptation sets in the period. * @param trackSelection The track selection. + * @param trackType The type of the tracks in the selection. * @param dataSource A {@link DataSource} suitable for loading the media data. * @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between * server-side unix time and {@link SystemClock#elapsedRealtime()} in milliseconds, specified @@ -112,26 +115,27 @@ public class DefaultDashChunkSource implements DashChunkSource { * @param enableCea608Track Whether the chunks generated by the source may output a CEA-608 track. */ public DefaultDashChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, - DashManifest manifest, int periodIndex, int adaptationSetIndex, TrackSelection trackSelection, - DataSource dataSource, long elapsedRealtimeOffsetMs, int maxSegmentsPerLoad, - boolean enableEventMessageTrack, boolean enableCea608Track) { + DashManifest manifest, int periodIndex, int[] adaptationSetIndices, + TrackSelection trackSelection, int trackType, DataSource dataSource, + long elapsedRealtimeOffsetMs, int maxSegmentsPerLoad, boolean enableEventMessageTrack, + boolean enableCea608Track) { this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; this.manifest = manifest; - this.adaptationSetIndex = adaptationSetIndex; + this.adaptationSetIndices = adaptationSetIndices; this.trackSelection = trackSelection; + this.trackType = trackType; this.dataSource = dataSource; this.periodIndex = periodIndex; this.elapsedRealtimeOffsetMs = elapsedRealtimeOffsetMs; this.maxSegmentsPerLoad = maxSegmentsPerLoad; long periodDurationUs = manifest.getPeriodDurationUs(periodIndex); - AdaptationSet adaptationSet = getAdaptationSet(); - List representations = adaptationSet.representations; + List representations = getRepresentations(); representationHolders = new RepresentationHolder[trackSelection.length()]; for (int i = 0; i < representationHolders.length; i++) { Representation representation = representations.get(trackSelection.getIndexInTrackGroup(i)); representationHolders[i] = new RepresentationHolder(periodDurationUs, representation, - enableEventMessageTrack, enableCea608Track, adaptationSet.type); + enableEventMessageTrack, enableCea608Track); } } @@ -141,7 +145,7 @@ public class DefaultDashChunkSource implements DashChunkSource { manifest = newManifest; periodIndex = newPeriodIndex; long periodDurationUs = manifest.getPeriodDurationUs(periodIndex); - List representations = getAdaptationSet().representations; + List representations = getRepresentations(); for (int i = 0; i < representationHolders.length; i++) { Representation representation = representations.get(trackSelection.getIndexInTrackGroup(i)); representationHolders[i].updateRepresentation(periodDurationUs, representation); @@ -169,7 +173,7 @@ public class DefaultDashChunkSource implements DashChunkSource { } @Override - public final void getNextChunk(MediaChunk previous, long playbackPositionUs, ChunkHolder out) { + public void getNextChunk(MediaChunk previous, long playbackPositionUs, ChunkHolder out) { if (fatalError != null) { return; } @@ -248,9 +252,9 @@ public class DefaultDashChunkSource implements DashChunkSource { } int maxSegmentCount = Math.min(maxSegmentsPerLoad, lastAvailableSegmentNum - segmentNum + 1); - out.chunk = newMediaChunk(representationHolder, dataSource, trackSelection.getSelectedFormat(), - trackSelection.getSelectionReason(), trackSelection.getSelectionData(), segmentNum, - maxSegmentCount); + out.chunk = newMediaChunk(representationHolder, dataSource, trackType, + trackSelection.getSelectedFormat(), trackSelection.getSelectionReason(), + trackSelection.getSelectionData(), segmentNum, maxSegmentCount); } @Override @@ -296,10 +300,15 @@ public class DefaultDashChunkSource implements DashChunkSource { trackSelection.indexOf(chunk.trackFormat), e); } - // Private methods. + // Internal methods. - private AdaptationSet getAdaptationSet() { - return manifest.getPeriod(periodIndex).adaptationSets.get(adaptationSetIndex); + private ArrayList getRepresentations() { + List manifestAdapationSets = manifest.getPeriod(periodIndex).adaptationSets; + ArrayList representations = new ArrayList<>(); + for (int adaptationSetIndex : adaptationSetIndices) { + representations.addAll(manifestAdapationSets.get(adaptationSetIndex).representations); + } + return representations; } private long getNowUnixTimeUs() { @@ -310,7 +319,7 @@ public class DefaultDashChunkSource implements DashChunkSource { } } - private static Chunk newInitializationChunk(RepresentationHolder representationHolder, + protected static Chunk newInitializationChunk(RepresentationHolder representationHolder, DataSource dataSource, Format trackFormat, int trackSelectionReason, Object trackSelectionData, RangedUri initializationUri, RangedUri indexUri) { RangedUri requestUri; @@ -331,8 +340,8 @@ public class DefaultDashChunkSource implements DashChunkSource { trackSelectionReason, trackSelectionData, representationHolder.extractorWrapper); } - private static Chunk newMediaChunk(RepresentationHolder representationHolder, - DataSource dataSource, Format trackFormat, int trackSelectionReason, + protected static Chunk newMediaChunk(RepresentationHolder representationHolder, + DataSource dataSource, int trackType, Format trackFormat, int trackSelectionReason, Object trackSelectionData, int firstSegmentNum, int maxSegmentCount) { Representation representation = representationHolder.representation; long startTimeUs = representationHolder.getSegmentStartTimeUs(firstSegmentNum); @@ -343,8 +352,7 @@ public class DefaultDashChunkSource implements DashChunkSource { DataSpec dataSpec = new DataSpec(segmentUri.resolveUri(baseUrl), segmentUri.start, segmentUri.length, representation.getCacheKey()); return new SingleSampleMediaChunk(dataSource, dataSpec, trackFormat, trackSelectionReason, - trackSelectionData, startTimeUs, endTimeUs, firstSegmentNum, - representationHolder.trackType, trackFormat); + trackSelectionData, startTimeUs, endTimeUs, firstSegmentNum, trackType, trackFormat); } else { int segmentCount = 1; for (int i = 1; i < maxSegmentCount; i++) { @@ -369,10 +377,12 @@ public class DefaultDashChunkSource implements DashChunkSource { // Protected classes. + /** + * Holds information about a single {@link Representation}. + */ protected static final class RepresentationHolder { - public final int trackType; - public final ChunkExtractorWrapper extractorWrapper; + /* package */ final ChunkExtractorWrapper extractorWrapper; public Representation representation; public DashSegmentIndex segmentIndex; @@ -380,11 +390,10 @@ public class DefaultDashChunkSource implements DashChunkSource { private long periodDurationUs; private int segmentNumShift; - public RepresentationHolder(long periodDurationUs, Representation representation, - boolean enableEventMessageTrack, boolean enableCea608Track, int trackType) { + /* package */ RepresentationHolder(long periodDurationUs, Representation representation, + boolean enableEventMessageTrack, boolean enableCea608Track) { this.periodDurationUs = periodDurationUs; this.representation = representation; - this.trackType = trackType; String containerMimeType = representation.format.containerMimeType; if (mimeTypeIsRawText(containerMimeType)) { extractorWrapper = null; @@ -411,8 +420,8 @@ public class DefaultDashChunkSource implements DashChunkSource { segmentIndex = representation.getIndex(); } - public void updateRepresentation(long newPeriodDurationUs, Representation newRepresentation) - throws BehindLiveWindowException{ + /* package */ void updateRepresentation(long newPeriodDurationUs, + Representation newRepresentation) throws BehindLiveWindowException { DashSegmentIndex oldIndex = representation.getIndex(); DashSegmentIndex newIndex = newRepresentation.getIndex(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/AdaptationSet.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/AdaptationSet.java index 0b23205b2..556e37a59 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/AdaptationSet.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/AdaptationSet.java @@ -41,31 +41,40 @@ public class AdaptationSet { public final int type; /** - * The {@link Representation}s in the adaptation set. + * {@link Representation}s in the adaptation set. */ public final List representations; /** - * The accessibility descriptors in the adaptation set. + * Accessibility descriptors in the adaptation set. */ - public final List accessibilityDescriptors; + public final List accessibilityDescriptors; + + /** + * Supplemental properties in the adaptation set. + */ + public final List supplementalProperties; /** * @param id A non-negative identifier for the adaptation set that's unique in the scope of its * containing period, or {@link #ID_UNSET} if not specified. * @param type The type of the adaptation set. One of the {@link org.telegram.messenger.exoplayer2.C} * {@code TRACK_TYPE_*} constants. - * @param representations The {@link Representation}s in the adaptation set. - * @param accessibilityDescriptors The accessibility descriptors in the adaptation set. + * @param representations {@link Representation}s in the adaptation set. + * @param accessibilityDescriptors Accessibility descriptors in the adaptation set. + * @param supplementalProperties Supplemental properties in the adaptation set. */ public AdaptationSet(int id, int type, List representations, - List accessibilityDescriptors) { + List accessibilityDescriptors, List supplementalProperties) { this.id = id; this.type = type; this.representations = Collections.unmodifiableList(representations); this.accessibilityDescriptors = accessibilityDescriptors == null - ? Collections.emptyList() + ? Collections.emptyList() : Collections.unmodifiableList(accessibilityDescriptors); + this.supplementalProperties = supplementalProperties == null + ? Collections.emptyList() + : Collections.unmodifiableList(supplementalProperties); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/DashManifest.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/DashManifest.java index e95881a1f..ff67773f3 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/DashManifest.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/DashManifest.java @@ -134,7 +134,8 @@ public class DashManifest { } while(key.periodIndex == periodIndex && key.adaptationSetIndex == adaptationSetIndex); copyAdaptationSets.add(new AdaptationSet(adaptationSet.id, adaptationSet.type, - copyRepresentations, adaptationSet.accessibilityDescriptors)); + copyRepresentations, adaptationSet.accessibilityDescriptors, + adaptationSet.supplementalProperties)); } while(key.periodIndex == periodIndex); // Add back the last key which doesn't belong to the period being processed keys.addFirst(key); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/DashManifestParser.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/DashManifestParser.java index 9ea7e1c21..5b1a202ff 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/DashManifestParser.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/DashManifestParser.java @@ -239,8 +239,9 @@ public class DashManifestParser extends DefaultHandler int audioSamplingRate = parseInt(xpp, "audioSamplingRate", Format.NO_VALUE); String language = xpp.getAttributeValue(null, "lang"); ArrayList drmSchemeDatas = new ArrayList<>(); - ArrayList inbandEventStreams = new ArrayList<>(); - ArrayList accessibilityDescriptors = new ArrayList<>(); + ArrayList inbandEventStreams = new ArrayList<>(); + ArrayList accessibilityDescriptors = new ArrayList<>(); + ArrayList supplementalProperties = new ArrayList<>(); List representationInfos = new ArrayList<>(); @C.SelectionFlags int selectionFlags = 0; @@ -265,7 +266,9 @@ public class DashManifestParser extends DefaultHandler } else if (XmlPullParserUtil.isStartTag(xpp, "AudioChannelConfiguration")) { audioChannels = parseAudioChannelConfiguration(xpp); } else if (XmlPullParserUtil.isStartTag(xpp, "Accessibility")) { - accessibilityDescriptors.add(parseAccessibility(xpp)); + accessibilityDescriptors.add(parseDescriptor(xpp, "Accessibility")); + } else if (XmlPullParserUtil.isStartTag(xpp, "SupplementalProperty")) { + supplementalProperties.add(parseDescriptor(xpp, "SupplementalProperty")); } else if (XmlPullParserUtil.isStartTag(xpp, "Representation")) { RepresentationInfo representationInfo = parseRepresentation(xpp, baseUrl, mimeType, codecs, width, height, frameRate, audioChannels, audioSamplingRate, language, @@ -280,7 +283,7 @@ public class DashManifestParser extends DefaultHandler } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) { segmentBase = parseSegmentTemplate(xpp, (SegmentTemplate) segmentBase); } else if (XmlPullParserUtil.isStartTag(xpp, "InbandEventStream")) { - inbandEventStreams.add(parseInbandEventStream(xpp)); + inbandEventStreams.add(parseDescriptor(xpp, "InbandEventStream")); } else if (XmlPullParserUtil.isStartTag(xpp)) { parseAdaptationSetChild(xpp); } @@ -293,12 +296,15 @@ public class DashManifestParser extends DefaultHandler drmSchemeDatas, inbandEventStreams)); } - return buildAdaptationSet(id, contentType, representations, accessibilityDescriptors); + return buildAdaptationSet(id, contentType, representations, accessibilityDescriptors, + supplementalProperties); } protected AdaptationSet buildAdaptationSet(int id, int contentType, - List representations, List accessibilityDescriptors) { - return new AdaptationSet(id, contentType, representations, accessibilityDescriptors); + List representations, List accessibilityDescriptors, + List supplementalProperties) { + return new AdaptationSet(id, contentType, representations, accessibilityDescriptors, + supplementalProperties); } protected int parseContentType(XmlPullParser xpp) { @@ -337,6 +343,7 @@ public class DashManifestParser extends DefaultHandler IOException { String schemeIdUri = xpp.getAttributeValue(null, "schemeIdUri"); boolean isPlayReady = "urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95".equals(schemeIdUri); + String schemeType = xpp.getAttributeValue(null, "value"); byte[] data = null; UUID uuid = null; boolean requiresSecureDecoder = false; @@ -362,34 +369,8 @@ public class DashManifestParser extends DefaultHandler requiresSecureDecoder = robustnessLevel != null && robustnessLevel.startsWith("HW"); } } while (!XmlPullParserUtil.isEndTag(xpp, "ContentProtection")); - return data != null ? new SchemeData(uuid, MimeTypes.VIDEO_MP4, data, requiresSecureDecoder) - : null; - } - - /** - * Parses an InbandEventStream element. - * - * @param xpp The parser from which to read. - * @throws XmlPullParserException If an error occurs parsing the element. - * @throws IOException If an error occurs reading the element. - * @return A {@link SchemeValuePair} parsed from the element. - */ - protected SchemeValuePair parseInbandEventStream(XmlPullParser xpp) - throws XmlPullParserException, IOException { - return parseSchemeValuePair(xpp, "InbandEventStream"); - } - - /** - * Parses an Accessibility element. - * - * @param xpp The parser from which to read. - * @throws XmlPullParserException If an error occurs parsing the element. - * @throws IOException If an error occurs reading the element. - * @return A {@link SchemeValuePair} parsed from the element. - */ - protected SchemeValuePair parseAccessibility(XmlPullParser xpp) - throws XmlPullParserException, IOException { - return parseSchemeValuePair(xpp, "Accessibility"); + return data != null + ? new SchemeData(uuid, schemeType, MimeTypes.VIDEO_MP4, data, requiresSecureDecoder) : null; } /** @@ -429,7 +410,7 @@ public class DashManifestParser extends DefaultHandler int adaptationSetHeight, float adaptationSetFrameRate, int adaptationSetAudioChannels, int adaptationSetAudioSamplingRate, String adaptationSetLanguage, @C.SelectionFlags int adaptationSetSelectionFlags, - List adaptationSetAccessibilityDescriptors, SegmentBase segmentBase) + List adaptationSetAccessibilityDescriptors, SegmentBase segmentBase) throws XmlPullParserException, IOException { String id = xpp.getAttributeValue(null, "id"); int bandwidth = parseInt(xpp, "bandwidth", Format.NO_VALUE); @@ -442,7 +423,7 @@ public class DashManifestParser extends DefaultHandler int audioChannels = adaptationSetAudioChannels; int audioSamplingRate = parseInt(xpp, "audioSamplingRate", adaptationSetAudioSamplingRate); ArrayList drmSchemeDatas = new ArrayList<>(); - ArrayList inbandEventStreams = new ArrayList<>(); + ArrayList inbandEventStreams = new ArrayList<>(); boolean seenFirstBaseUrl = false; do { @@ -466,7 +447,7 @@ public class DashManifestParser extends DefaultHandler drmSchemeDatas.add(contentProtection); } } else if (XmlPullParserUtil.isStartTag(xpp, "InbandEventStream")) { - inbandEventStreams.add(parseInbandEventStream(xpp)); + inbandEventStreams.add(parseDescriptor(xpp, "InbandEventStream")); } } while (!XmlPullParserUtil.isEndTag(xpp, "Representation")); @@ -480,7 +461,7 @@ public class DashManifestParser extends DefaultHandler protected Format buildFormat(String id, String containerMimeType, int width, int height, float frameRate, int audioChannels, int audioSamplingRate, int bitrate, String language, - @C.SelectionFlags int selectionFlags, List accessibilityDescriptors, + @C.SelectionFlags int selectionFlags, List accessibilityDescriptors, String codecs) { String sampleMimeType = getSampleMimeType(containerMimeType, codecs); if (sampleMimeType != null) { @@ -509,14 +490,14 @@ public class DashManifestParser extends DefaultHandler protected Representation buildRepresentation(RepresentationInfo representationInfo, String contentId, ArrayList extraDrmSchemeDatas, - ArrayList extraInbandEventStreams) { + ArrayList extraInbandEventStreams) { Format format = representationInfo.format; ArrayList drmSchemeDatas = representationInfo.drmSchemeDatas; drmSchemeDatas.addAll(extraDrmSchemeDatas); if (!drmSchemeDatas.isEmpty()) { format = format.copyWithDrmInitData(new DrmInitData(drmSchemeDatas)); } - ArrayList inbandEventStremas = representationInfo.inbandEventStreams; + ArrayList inbandEventStremas = representationInfo.inbandEventStreams; inbandEventStremas.addAll(extraInbandEventStreams); return Representation.newInstance(contentId, Representation.REVISION_ID_DEFAULT, format, representationInfo.baseUrl, representationInfo.segmentBase, inbandEventStremas); @@ -809,28 +790,29 @@ public class DashManifestParser extends DefaultHandler } /** - * Parses a {@link SchemeValuePair} from an element. + * Parses a {@link Descriptor} from an element. * * @param xpp The parser from which to read. * @param tag The tag of the element being parsed. * @throws XmlPullParserException If an error occurs parsing the element. * @throws IOException If an error occurs reading the element. - * @return The parsed {@link SchemeValuePair}. + * @return The parsed {@link Descriptor}. */ - protected static SchemeValuePair parseSchemeValuePair(XmlPullParser xpp, String tag) + protected static Descriptor parseDescriptor(XmlPullParser xpp, String tag) throws XmlPullParserException, IOException { - String schemeIdUri = parseString(xpp, "schemeIdUri", null); + String schemeIdUri = parseString(xpp, "schemeIdUri", ""); String value = parseString(xpp, "value", null); + String id = parseString(xpp, "id", null); do { xpp.next(); } while (!XmlPullParserUtil.isEndTag(xpp, tag)); - return new SchemeValuePair(schemeIdUri, value); + return new Descriptor(schemeIdUri, value, id); } protected static int parseCea608AccessibilityChannel( - List accessibilityDescriptors) { + List accessibilityDescriptors) { for (int i = 0; i < accessibilityDescriptors.size(); i++) { - SchemeValuePair descriptor = accessibilityDescriptors.get(i); + Descriptor descriptor = accessibilityDescriptors.get(i); if ("urn:scte:dash:cc:cea-608:2015".equals(descriptor.schemeIdUri) && descriptor.value != null) { Matcher accessibilityValueMatcher = CEA_608_ACCESSIBILITY_PATTERN.matcher(descriptor.value); @@ -845,9 +827,9 @@ public class DashManifestParser extends DefaultHandler } protected static int parseCea708AccessibilityChannel( - List accessibilityDescriptors) { + List accessibilityDescriptors) { for (int i = 0; i < accessibilityDescriptors.size(); i++) { - SchemeValuePair descriptor = accessibilityDescriptors.get(i); + Descriptor descriptor = accessibilityDescriptors.get(i); if ("urn:scte:dash:cc:cea-708:2015".equals(descriptor.schemeIdUri) && descriptor.value != null) { Matcher accessibilityValueMatcher = CEA_708_ACCESSIBILITY_PATTERN.matcher(descriptor.value); @@ -925,10 +907,10 @@ public class DashManifestParser extends DefaultHandler public final String baseUrl; public final SegmentBase segmentBase; public final ArrayList drmSchemeDatas; - public final ArrayList inbandEventStreams; + public final ArrayList inbandEventStreams; public RepresentationInfo(Format format, String baseUrl, SegmentBase segmentBase, - ArrayList drmSchemeDatas, ArrayList inbandEventStreams) { + ArrayList drmSchemeDatas, ArrayList inbandEventStreams) { this.format = format; this.baseUrl = baseUrl; this.segmentBase = segmentBase; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/SchemeValuePair.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/Descriptor.java similarity index 52% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/SchemeValuePair.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/Descriptor.java index b63bced78..2a9638379 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/SchemeValuePair.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/Descriptor.java @@ -15,19 +15,37 @@ */ package org.telegram.messenger.exoplayer2.source.dash.manifest; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import org.telegram.messenger.exoplayer2.util.Util; /** - * A pair consisting of a scheme ID and value. + * A descriptor, as defined by ISO 23009-1, 2nd edition, 5.8.2. */ -public class SchemeValuePair { +public final class Descriptor { - public final String schemeIdUri; - public final String value; + /** + * The scheme URI. + */ + @NonNull public final String schemeIdUri; + /** + * The value, or null. + */ + @Nullable public final String value; + /** + * The identifier, or null. + */ + @Nullable public final String id; - public SchemeValuePair(String schemeIdUri, String value) { + /** + * @param schemeIdUri The scheme URI. + * @param value The value, or null. + * @param id The identifier, or null. + */ + public Descriptor(@NonNull String schemeIdUri, @Nullable String value, @Nullable String id) { this.schemeIdUri = schemeIdUri; this.value = value; + this.id = id; } @Override @@ -38,14 +56,17 @@ public class SchemeValuePair { if (obj == null || getClass() != obj.getClass()) { return false; } - SchemeValuePair other = (SchemeValuePair) obj; - return Util.areEqual(schemeIdUri, other.schemeIdUri) && Util.areEqual(value, other.value); + Descriptor other = (Descriptor) obj; + return Util.areEqual(schemeIdUri, other.schemeIdUri) && Util.areEqual(value, other.value) + && Util.areEqual(id, other.id); } @Override public int hashCode() { - return 31 * (schemeIdUri != null ? schemeIdUri.hashCode() : 0) - + (value != null ? value.hashCode() : 0); + int result = (schemeIdUri != null ? schemeIdUri.hashCode() : 0); + result = 31 * result + (value != null ? value.hashCode() : 0); + result = 31 * result + (id != null ? id.hashCode() : 0); + return result; } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/Representation.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/Representation.java index c47ccf2d5..d3316e33a 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/Representation.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/Representation.java @@ -65,7 +65,7 @@ public abstract class Representation { /** * The in-band event streams in the representation. Never null, but may be empty. */ - public final List inbandEventStreams; + public final List inbandEventStreams; private final RangedUri initializationUri; @@ -96,7 +96,7 @@ public abstract class Representation { * @return The constructed instance. */ public static Representation newInstance(String contentId, long revisionId, Format format, - String baseUrl, SegmentBase segmentBase, List inbandEventStreams) { + String baseUrl, SegmentBase segmentBase, List inbandEventStreams) { return newInstance(contentId, revisionId, format, baseUrl, segmentBase, inbandEventStreams, null); } @@ -115,7 +115,7 @@ public abstract class Representation { * @return The constructed instance. */ public static Representation newInstance(String contentId, long revisionId, Format format, - String baseUrl, SegmentBase segmentBase, List inbandEventStreams, + String baseUrl, SegmentBase segmentBase, List inbandEventStreams, String customCacheKey) { if (segmentBase instanceof SingleSegmentBase) { return new SingleSegmentRepresentation(contentId, revisionId, format, baseUrl, @@ -130,13 +130,12 @@ public abstract class Representation { } private Representation(String contentId, long revisionId, Format format, String baseUrl, - SegmentBase segmentBase, List inbandEventStreams) { + SegmentBase segmentBase, List inbandEventStreams) { this.contentId = contentId; this.revisionId = revisionId; this.format = format; this.baseUrl = baseUrl; - this.inbandEventStreams = inbandEventStreams == null - ? Collections.emptyList() + this.inbandEventStreams = inbandEventStreams == null ? Collections.emptyList() : Collections.unmodifiableList(inbandEventStreams); initializationUri = segmentBase.getInitialization(this); presentationTimeOffsetUs = segmentBase.getPresentationTimeOffsetUs(); @@ -201,8 +200,8 @@ public abstract class Representation { */ public static SingleSegmentRepresentation newInstance(String contentId, long revisionId, Format format, String uri, long initializationStart, long initializationEnd, - long indexStart, long indexEnd, List inbandEventStreams, - String customCacheKey, long contentLength) { + long indexStart, long indexEnd, List inbandEventStreams, String customCacheKey, + long contentLength) { RangedUri rangedUri = new RangedUri(null, initializationStart, initializationEnd - initializationStart + 1); SingleSegmentBase segmentBase = new SingleSegmentBase(rangedUri, 1, 0, indexStart, @@ -222,7 +221,7 @@ public abstract class Representation { * @param contentLength The content length, or {@link C#LENGTH_UNSET} if unknown. */ public SingleSegmentRepresentation(String contentId, long revisionId, Format format, - String baseUrl, SingleSegmentBase segmentBase, List inbandEventStreams, + String baseUrl, SingleSegmentBase segmentBase, List inbandEventStreams, String customCacheKey, long contentLength) { super(contentId, revisionId, format, baseUrl, segmentBase, inbandEventStreams); this.uri = Uri.parse(baseUrl); @@ -270,7 +269,7 @@ public abstract class Representation { * @param inbandEventStreams The in-band event streams in the representation. May be null. */ public MultiSegmentRepresentation(String contentId, long revisionId, Format format, - String baseUrl, MultiSegmentBase segmentBase, List inbandEventStreams) { + String baseUrl, MultiSegmentBase segmentBase, List inbandEventStreams) { super(contentId, revisionId, format, baseUrl, segmentBase, inbandEventStreams); this.segmentBase = segmentBase; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsChunkSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsChunkSource.java index 2202d022a..73c647104 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsChunkSource.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsChunkSource.java @@ -39,7 +39,6 @@ import java.io.IOException; import java.math.BigInteger; import java.util.Arrays; import java.util.List; -import java.util.Locale; /** * Source of Hls (possibly adaptive) chunks. @@ -92,6 +91,8 @@ import java.util.Locale; private boolean isTimestampMaster; private byte[] scratchSpace; private IOException fatalError; + private HlsUrl expectedPlaylistUrl; + private boolean independentSegments; private Uri encryptionKeyUri; private byte[] encryptionKey; @@ -111,7 +112,8 @@ import java.util.Locale; * @param timestampAdjusterProvider A provider of {@link TimestampAdjuster} instances. If * multiple {@link HlsChunkSource}s are used for a single playback, they should all share the * same provider. - * @param muxedCaptionFormats List of muxed caption {@link Format}s. + * @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption + * information is available in the master playlist. */ public HlsChunkSource(HlsPlaylistTracker playlistTracker, HlsUrl[] variants, HlsDataSourceFactory dataSourceFactory, TimestampAdjusterProvider timestampAdjusterProvider, @@ -142,6 +144,9 @@ import java.util.Locale; if (fatalError != null) { throw fatalError; } + if (expectedPlaylistUrl != null) { + playlistTracker.maybeThrowPlaylistRefreshError(expectedPlaylistUrl); + } } /** @@ -160,6 +165,13 @@ import java.util.Locale; this.trackSelection = trackSelection; } + /** + * Returns the current track selection. + */ + public TrackSelection getTrackSelection() { + return trackSelection; + } + /** * Resets the source. */ @@ -194,10 +206,12 @@ import java.util.Locale; public void getNextChunk(HlsMediaChunk previous, long playbackPositionUs, HlsChunkHolder out) { int oldVariantIndex = previous == null ? C.INDEX_UNSET : trackGroup.indexOf(previous.trackFormat); - // Use start time of the previous chunk rather than its end time because switching format will - // require downloading overlapping segments. - long bufferedDurationUs = previous == null ? 0 - : Math.max(0, previous.startTimeUs - playbackPositionUs); + expectedPlaylistUrl = null; + // Unless segments are known to be independent, switching variant will require downloading + // overlapping segments. Hence we use the start time of the previous chunk rather than its end + // time for this case. + long bufferedDurationUs = previous == null ? 0 : Math.max(0, + (independentSegments ? previous.endTimeUs : previous.startTimeUs) - playbackPositionUs); // Select the variant. trackSelection.updateSelectedTrack(bufferedDurationUs); @@ -207,16 +221,19 @@ import java.util.Locale; HlsUrl selectedUrl = variants[selectedVariantIndex]; if (!playlistTracker.isSnapshotValid(selectedUrl)) { out.playlist = selectedUrl; + expectedPlaylistUrl = selectedUrl; // Retry when playlist is refreshed. return; } HlsMediaPlaylist mediaPlaylist = playlistTracker.getPlaylistSnapshot(selectedUrl); + independentSegments = mediaPlaylist.hasIndependentSegmentsTag; // Select the chunk. int chunkMediaSequence; if (previous == null || switchingVariant) { - long targetPositionUs = previous == null ? playbackPositionUs : previous.startTimeUs; - if (!mediaPlaylist.hasEndTag && targetPositionUs > mediaPlaylist.getEndTimeUs()) { + long targetPositionUs = previous == null ? playbackPositionUs + : independentSegments ? previous.endTimeUs : previous.startTimeUs; + if (!mediaPlaylist.hasEndTag && targetPositionUs >= mediaPlaylist.getEndTimeUs()) { // If the playlist is too old to contain the chunk, we need to refresh it. chunkMediaSequence = mediaPlaylist.mediaSequence + mediaPlaylist.segments.size(); } else { @@ -246,6 +263,7 @@ import java.util.Locale; out.endOfStream = true; } else /* Live */ { out.playlist = selectedUrl; + expectedPlaylistUrl = selectedUrl; } return; } @@ -350,7 +368,7 @@ import java.util.Locale; private void setEncryptionData(Uri keyUri, String iv, byte[] secretKey) { String trimmedIv; - if (iv.toLowerCase(Locale.getDefault()).startsWith("0x")) { + if (Util.toLowerInvariant(iv).startsWith("0x")) { trimmedIv = iv.substring(2); } else { trimmedIv = iv; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsMediaChunk.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsMediaChunk.java index 416b675ae..ac429ac01 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsMediaChunk.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsMediaChunk.java @@ -39,6 +39,7 @@ import org.telegram.messenger.exoplayer2.util.ParsableByteArray; import org.telegram.messenger.exoplayer2.util.TimestampAdjuster; import org.telegram.messenger.exoplayer2.util.Util; import java.io.IOException; +import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -104,7 +105,8 @@ import java.util.concurrent.atomic.AtomicInteger; * @param dataSpec Defines the data to be loaded. * @param initDataSpec Defines the initialization data to be fed to new extractors. May be null. * @param hlsUrl The url of the playlist from which this chunk was obtained. - * @param muxedCaptionFormats List of muxed caption {@link Format}s. + * @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption + * information is available in the master playlist. * @param trackSelectionReason See {@link #trackSelectionReason}. * @param trackSelectionData See {@link #trackSelectionData}. * @param startTimeUs The start time of the chunk in microseconds. @@ -208,7 +210,7 @@ import java.util.concurrent.atomic.AtomicInteger; // According to spec, for packed audio, initDataSpec is expected to be null. return; } - DataSpec initSegmentDataSpec = Util.getRemainderDataSpec(initDataSpec, initSegmentBytesLoaded); + DataSpec initSegmentDataSpec = initDataSpec.subrange(initSegmentBytesLoaded); try { ExtractorInput input = new DefaultExtractorInput(initDataSource, initSegmentDataSpec.absoluteStreamPosition, initDataSource.open(initSegmentDataSpec)); @@ -237,7 +239,7 @@ import java.util.concurrent.atomic.AtomicInteger; loadDataSpec = dataSpec; skipLoadedBytes = bytesLoaded != 0; } else { - loadDataSpec = Util.getRemainderDataSpec(dataSpec, bytesLoaded); + loadDataSpec = dataSpec.subrange(bytesLoaded); skipLoadedBytes = false; } if (!isMasterTimestampSource) { @@ -356,9 +358,12 @@ import java.util.concurrent.atomic.AtomicInteger; // This flag ensures the change of pid between streams does not affect the sample queues. @DefaultTsPayloadReaderFactory.Flags int esReaderFactoryFlags = DefaultTsPayloadReaderFactory.FLAG_IGNORE_SPLICE_INFO_STREAM; - if (!muxedCaptionFormats.isEmpty()) { + List closedCaptionFormats = muxedCaptionFormats; + if (closedCaptionFormats != null) { // The playlist declares closed caption renditions, we should ignore descriptors. esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_OVERRIDE_CAPTION_DESCRIPTORS; + } else { + closedCaptionFormats = Collections.emptyList(); } String codecs = trackFormat.codecs; if (!TextUtils.isEmpty(codecs)) { @@ -373,7 +378,7 @@ import java.util.concurrent.atomic.AtomicInteger; } } extractor = new TsExtractor(TsExtractor.MODE_HLS, timestampAdjuster, - new DefaultTsPayloadReaderFactory(esReaderFactoryFlags, muxedCaptionFormats)); + new DefaultTsPayloadReaderFactory(esReaderFactoryFlags, closedCaptionFormats)); } if (usingNewExtractor) { extractor.init(extractorOutput); @@ -391,7 +396,7 @@ import java.util.concurrent.atomic.AtomicInteger; } else if (lastPathSegment.endsWith(MP3_FILE_EXTENSION)) { extractor = new Mp3Extractor(0, startTimeUs); } else { - throw new IllegalArgumentException("Unkown extension for audio file: " + lastPathSegment); + throw new IllegalArgumentException("Unknown extension for audio file: " + lastPathSegment); } extractor.init(extractorOutput); return extractor; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsMediaPeriod.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsMediaPeriod.java index 5b502dde2..d8d8c281e 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsMediaPeriod.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsMediaPeriod.java @@ -33,6 +33,7 @@ import org.telegram.messenger.exoplayer2.upstream.Allocator; import org.telegram.messenger.exoplayer2.util.Assertions; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.IdentityHashMap; import java.util.List; @@ -51,19 +52,16 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper private final IdentityHashMap streamWrapperIndices; private final TimestampAdjusterProvider timestampAdjusterProvider; private final Handler continueLoadingHandler; - private final long preparePositionUs; private Callback callback; private int pendingPrepareCount; - private boolean seenFirstTrackSelection; private TrackGroupArray trackGroups; private HlsSampleStreamWrapper[] sampleStreamWrappers; private HlsSampleStreamWrapper[] enabledSampleStreamWrappers; private CompositeSequenceableLoader sequenceableLoader; public HlsMediaPeriod(HlsPlaylistTracker playlistTracker, HlsDataSourceFactory dataSourceFactory, - int minLoadableRetryCount, EventDispatcher eventDispatcher, Allocator allocator, - long positionUs) { + int minLoadableRetryCount, EventDispatcher eventDispatcher, Allocator allocator) { this.playlistTracker = playlistTracker; this.dataSourceFactory = dataSourceFactory; this.minLoadableRetryCount = minLoadableRetryCount; @@ -72,32 +70,29 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper streamWrapperIndices = new IdentityHashMap<>(); timestampAdjusterProvider = new TimestampAdjusterProvider(); continueLoadingHandler = new Handler(); - preparePositionUs = positionUs; + sampleStreamWrappers = new HlsSampleStreamWrapper[0]; + enabledSampleStreamWrappers = new HlsSampleStreamWrapper[0]; } public void release() { playlistTracker.removeListener(this); continueLoadingHandler.removeCallbacksAndMessages(null); - if (sampleStreamWrappers != null) { - for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) { - sampleStreamWrapper.release(); - } + for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) { + sampleStreamWrapper.release(); } } @Override - public void prepare(Callback callback) { - playlistTracker.addListener(this); + public void prepare(Callback callback, long positionUs) { this.callback = callback; - buildAndPrepareSampleStreamWrappers(); + playlistTracker.addListener(this); + buildAndPrepareSampleStreamWrappers(positionUs); } @Override public void maybeThrowPrepareError() throws IOException { - if (sampleStreamWrappers != null) { - for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) { - sampleStreamWrapper.maybeThrowPrepareError(); - } + for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) { + sampleStreamWrapper.maybeThrowPrepareError(); } } @@ -126,21 +121,24 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper } } } - boolean selectedNewTracks = false; + + boolean forceReset = false; streamWrapperIndices.clear(); // Select tracks for each child, copying the resulting streams back into a new streams array. SampleStream[] newStreams = new SampleStream[selections.length]; SampleStream[] childStreams = new SampleStream[selections.length]; TrackSelection[] childSelections = new TrackSelection[selections.length]; - ArrayList enabledSampleStreamWrapperList = new ArrayList<>( - sampleStreamWrappers.length); + int newEnabledSampleStreamWrapperCount = 0; + HlsSampleStreamWrapper[] newEnabledSampleStreamWrappers = + new HlsSampleStreamWrapper[sampleStreamWrappers.length]; for (int i = 0; i < sampleStreamWrappers.length; i++) { for (int j = 0; j < selections.length; j++) { childStreams[j] = streamChildIndices[j] == i ? streams[j] : null; childSelections[j] = selectionChildIndices[j] == i ? selections[j] : null; } - selectedNewTracks |= sampleStreamWrappers[i].selectTracks(childSelections, - mayRetainStreamFlags, childStreams, streamResetFlags, !seenFirstTrackSelection); + HlsSampleStreamWrapper sampleStreamWrapper = sampleStreamWrappers[i]; + boolean wasReset = sampleStreamWrapper.selectTracks(childSelections, mayRetainStreamFlags, + childStreams, streamResetFlags, positionUs, forceReset); boolean wrapperEnabled = false; for (int j = 0; j < selections.length; j++) { if (selectionChildIndices[j] == i) { @@ -155,43 +153,37 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper } } if (wrapperEnabled) { - enabledSampleStreamWrapperList.add(sampleStreamWrappers[i]); + newEnabledSampleStreamWrappers[newEnabledSampleStreamWrapperCount] = sampleStreamWrapper; + if (newEnabledSampleStreamWrapperCount++ == 0) { + // The first enabled wrapper is responsible for initializing timestamp adjusters. This + // way, if enabled, variants are responsible. Else audio renditions. Else text renditions. + sampleStreamWrapper.setIsTimestampMaster(true); + if (wasReset || enabledSampleStreamWrappers.length == 0 + || sampleStreamWrapper != enabledSampleStreamWrappers[0]) { + // The wrapper responsible for initializing the timestamp adjusters was reset or + // changed. We need to reset the timestamp adjuster provider and all other wrappers. + timestampAdjusterProvider.reset(); + forceReset = true; + } + } else { + sampleStreamWrapper.setIsTimestampMaster(false); + } } } // Copy the new streams back into the streams array. System.arraycopy(newStreams, 0, streams, 0, newStreams.length); // Update the local state. - enabledSampleStreamWrappers = new HlsSampleStreamWrapper[enabledSampleStreamWrapperList.size()]; - enabledSampleStreamWrapperList.toArray(enabledSampleStreamWrappers); - - // The first enabled sample stream wrapper is responsible for intializing the timestamp - // adjuster. This way, if present, variants are responsible. Otherwise, audio renditions are. - // If only subtitles are present, then text renditions are used for timestamp adjustment - // initialization. - if (enabledSampleStreamWrappers.length > 0) { - enabledSampleStreamWrappers[0].setIsTimestampMaster(true); - for (int i = 1; i < enabledSampleStreamWrappers.length; i++) { - enabledSampleStreamWrappers[i].setIsTimestampMaster(false); - } - } - + enabledSampleStreamWrappers = Arrays.copyOf(newEnabledSampleStreamWrappers, + newEnabledSampleStreamWrapperCount); sequenceableLoader = new CompositeSequenceableLoader(enabledSampleStreamWrappers); - if (seenFirstTrackSelection && selectedNewTracks) { - seekToUs(positionUs); - // We'll need to reset renderers consuming from all streams due to the seek. - for (int i = 0; i < selections.length; i++) { - if (streams[i] != null) { - streamResetFlags[i] = true; - } - } - } - seenFirstTrackSelection = true; return positionUs; } @Override public void discardBuffer(long positionUs) { - // Do nothing. + for (HlsSampleStreamWrapper sampleStreamWrapper : enabledSampleStreamWrappers) { + sampleStreamWrapper.discardBuffer(positionUs); + } } @Override @@ -211,21 +203,21 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper @Override public long getBufferedPositionUs() { - long bufferedPositionUs = Long.MAX_VALUE; - for (HlsSampleStreamWrapper sampleStreamWrapper : enabledSampleStreamWrappers) { - long rendererBufferedPositionUs = sampleStreamWrapper.getBufferedPositionUs(); - if (rendererBufferedPositionUs != C.TIME_END_OF_SOURCE) { - bufferedPositionUs = Math.min(bufferedPositionUs, rendererBufferedPositionUs); - } - } - return bufferedPositionUs == Long.MAX_VALUE ? C.TIME_END_OF_SOURCE : bufferedPositionUs; + return sequenceableLoader.getBufferedPositionUs(); } @Override public long seekToUs(long positionUs) { - timestampAdjusterProvider.reset(); - for (HlsSampleStreamWrapper sampleStreamWrapper : enabledSampleStreamWrappers) { - sampleStreamWrapper.seekTo(positionUs); + if (enabledSampleStreamWrappers.length > 0) { + // We need to reset all wrappers if the one responsible for initializing timestamp adjusters + // is reset. Else each wrapper can decide whether to reset independently. + boolean forceReset = enabledSampleStreamWrappers[0].seekToUs(positionUs, false); + for (int i = 1; i < enabledSampleStreamWrappers.length; i++) { + enabledSampleStreamWrappers[i].seekToUs(positionUs, forceReset); + } + if (forceReset) { + timestampAdjusterProvider.reset(); + } } return positionUs; } @@ -285,7 +277,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper // Internal methods. - private void buildAndPrepareSampleStreamWrappers() { + private void buildAndPrepareSampleStreamWrappers(long positionUs) { HlsMasterPlaylist masterPlaylist = playlistTracker.getMasterPlaylist(); // Build the default stream wrapper. List selectedVariants = new ArrayList<>(masterPlaylist.variants); @@ -322,7 +314,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[selectedVariants.size()]; selectedVariants.toArray(variants); HlsSampleStreamWrapper sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, - variants, masterPlaylist.muxedAudioFormat, masterPlaylist.muxedCaptionFormats); + variants, masterPlaylist.muxedAudioFormat, masterPlaylist.muxedCaptionFormats, positionUs); sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper; sampleStreamWrapper.setIsTimestampMaster(true); sampleStreamWrapper.continuePreparing(); @@ -332,7 +324,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper // Build audio stream wrappers. for (int i = 0; i < audioRenditions.size(); i++) { sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_AUDIO, - new HlsUrl[] {audioRenditions.get(i)}, null, Collections.emptyList()); + new HlsUrl[] {audioRenditions.get(i)}, null, Collections.emptyList(), positionUs); sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper; sampleStreamWrapper.continuePreparing(); } @@ -341,18 +333,21 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper for (int i = 0; i < subtitleRenditions.size(); i++) { HlsUrl url = subtitleRenditions.get(i); sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_TEXT, new HlsUrl[] {url}, null, - Collections.emptyList()); + Collections.emptyList(), positionUs); sampleStreamWrapper.prepareSingleTrack(url.format); sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper; } + + // All wrappers are enabled during preparation. + enabledSampleStreamWrappers = sampleStreamWrappers; } private HlsSampleStreamWrapper buildSampleStreamWrapper(int trackType, HlsUrl[] variants, - Format muxedAudioFormat, List muxedCaptionFormats) { + Format muxedAudioFormat, List muxedCaptionFormats, long positionUs) { HlsChunkSource defaultChunkSource = new HlsChunkSource(playlistTracker, variants, dataSourceFactory, timestampAdjusterProvider, muxedCaptionFormats); - return new HlsSampleStreamWrapper(trackType, this, defaultChunkSource, allocator, - preparePositionUs, muxedAudioFormat, minLoadableRetryCount, eventDispatcher); + return new HlsSampleStreamWrapper(trackType, this, defaultChunkSource, allocator, positionUs, + muxedAudioFormat, minLoadableRetryCount, eventDispatcher); } private void continuePreparingOrLoading() { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsMediaSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsMediaSource.java index 601d6048e..93b113ef8 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsMediaSource.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsMediaSource.java @@ -19,15 +19,19 @@ import android.net.Uri; import android.os.Handler; import org.telegram.messenger.exoplayer2.C; import org.telegram.messenger.exoplayer2.ExoPlayer; +import org.telegram.messenger.exoplayer2.ExoPlayerLibraryInfo; import org.telegram.messenger.exoplayer2.source.AdaptiveMediaSourceEventListener; import org.telegram.messenger.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; import org.telegram.messenger.exoplayer2.source.MediaPeriod; import org.telegram.messenger.exoplayer2.source.MediaSource; import org.telegram.messenger.exoplayer2.source.SinglePeriodTimeline; import org.telegram.messenger.exoplayer2.source.hls.playlist.HlsMediaPlaylist; +import org.telegram.messenger.exoplayer2.source.hls.playlist.HlsPlaylist; +import org.telegram.messenger.exoplayer2.source.hls.playlist.HlsPlaylistParser; import org.telegram.messenger.exoplayer2.source.hls.playlist.HlsPlaylistTracker; import org.telegram.messenger.exoplayer2.upstream.Allocator; import org.telegram.messenger.exoplayer2.upstream.DataSource; +import org.telegram.messenger.exoplayer2.upstream.ParsingLoadable; import org.telegram.messenger.exoplayer2.util.Assertions; import java.io.IOException; import java.util.List; @@ -38,6 +42,10 @@ import java.util.List; public final class HlsMediaSource implements MediaSource, HlsPlaylistTracker.PrimaryPlaylistListener { + static { + ExoPlayerLibraryInfo.registerModule("goog.exo.hls"); + } + /** * The default minimum number of times to retry loading data prior to failing. */ @@ -47,6 +55,7 @@ public final class HlsMediaSource implements MediaSource, private final HlsDataSourceFactory dataSourceFactory; private final int minLoadableRetryCount; private final EventDispatcher eventDispatcher; + private final ParsingLoadable.Parser playlistParser; private HlsPlaylistTracker playlistTracker; private Listener sourceListener; @@ -67,9 +76,18 @@ public final class HlsMediaSource implements MediaSource, public HlsMediaSource(Uri manifestUri, HlsDataSourceFactory dataSourceFactory, int minLoadableRetryCount, Handler eventHandler, AdaptiveMediaSourceEventListener eventListener) { + this(manifestUri, dataSourceFactory, minLoadableRetryCount, eventHandler, eventListener, + new HlsPlaylistParser()); + } + + public HlsMediaSource(Uri manifestUri, HlsDataSourceFactory dataSourceFactory, + int minLoadableRetryCount, Handler eventHandler, + AdaptiveMediaSourceEventListener eventListener, + ParsingLoadable.Parser playlistParser) { this.manifestUri = manifestUri; this.dataSourceFactory = dataSourceFactory; this.minLoadableRetryCount = minLoadableRetryCount; + this.playlistParser = playlistParser; eventDispatcher = new EventDispatcher(eventHandler, eventListener); } @@ -77,21 +95,21 @@ public final class HlsMediaSource implements MediaSource, public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) { Assertions.checkState(playlistTracker == null); playlistTracker = new HlsPlaylistTracker(manifestUri, dataSourceFactory, eventDispatcher, - minLoadableRetryCount, this); + minLoadableRetryCount, this, playlistParser); sourceListener = listener; playlistTracker.start(); } @Override public void maybeThrowSourceInfoRefreshError() throws IOException { - playlistTracker.maybeThrowPlaylistRefreshError(); + playlistTracker.maybeThrowPrimaryPlaylistRefreshError(); } @Override - public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { - Assertions.checkArgument(index == 0); + public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { + Assertions.checkArgument(id.periodIndex == 0); return new HlsMediaPeriod(playlistTracker, dataSourceFactory, minLoadableRetryCount, - eventDispatcher, allocator, positionUs); + eventDispatcher, allocator); } @Override @@ -111,6 +129,9 @@ public final class HlsMediaSource implements MediaSource, @Override public void onPrimaryPlaylistRefreshed(HlsMediaPlaylist playlist) { SinglePeriodTimeline timeline; + long presentationStartTimeMs = playlist.hasProgramDateTime ? 0 : C.TIME_UNSET; + long windowStartTimeMs = playlist.hasProgramDateTime ? C.usToMs(playlist.startTimeUs) + : C.TIME_UNSET; long windowDefaultStartPositionUs = playlist.startOffsetUs; if (playlistTracker.isLive()) { long periodDurationUs = playlist.hasEndTag ? (playlist.startTimeUs + playlist.durationUs) @@ -120,14 +141,16 @@ public final class HlsMediaSource implements MediaSource, windowDefaultStartPositionUs = segments.isEmpty() ? 0 : segments.get(Math.max(0, segments.size() - 3)).relativeStartTimeUs; } - timeline = new SinglePeriodTimeline(periodDurationUs, playlist.durationUs, - playlist.startTimeUs, windowDefaultStartPositionUs, true, !playlist.hasEndTag); + timeline = new SinglePeriodTimeline(presentationStartTimeMs, windowStartTimeMs, + periodDurationUs, playlist.durationUs, playlist.startTimeUs, windowDefaultStartPositionUs, + true, !playlist.hasEndTag); } else /* not live */ { if (windowDefaultStartPositionUs == C.TIME_UNSET) { windowDefaultStartPositionUs = 0; } - timeline = new SinglePeriodTimeline(playlist.startTimeUs + playlist.durationUs, - playlist.durationUs, playlist.startTimeUs, windowDefaultStartPositionUs, true, false); + timeline = new SinglePeriodTimeline(presentationStartTimeMs, windowStartTimeMs, + playlist.startTimeUs + playlist.durationUs, playlist.durationUs, playlist.startTimeUs, + windowDefaultStartPositionUs, true, false); } sourceListener.onSourceInfoRefreshed(timeline, new HlsManifest(playlistTracker.getMasterPlaylist(), playlist)); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsSampleStreamWrapper.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsSampleStreamWrapper.java index 9fe156fd8..94aaee6ae 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsSampleStreamWrapper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsSampleStreamWrapper.java @@ -17,16 +17,15 @@ package org.telegram.messenger.exoplayer2.source.hls; import android.os.Handler; import android.text.TextUtils; -import android.util.SparseArray; import org.telegram.messenger.exoplayer2.C; import org.telegram.messenger.exoplayer2.Format; import org.telegram.messenger.exoplayer2.FormatHolder; import org.telegram.messenger.exoplayer2.decoder.DecoderInputBuffer; -import org.telegram.messenger.exoplayer2.extractor.DefaultTrackOutput; -import org.telegram.messenger.exoplayer2.extractor.DefaultTrackOutput.UpstreamFormatChangedListener; import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; import org.telegram.messenger.exoplayer2.extractor.SeekMap; import org.telegram.messenger.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; +import org.telegram.messenger.exoplayer2.source.SampleQueue; +import org.telegram.messenger.exoplayer2.source.SampleQueue.UpstreamFormatChangedListener; import org.telegram.messenger.exoplayer2.source.SampleStream; import org.telegram.messenger.exoplayer2.source.SequenceableLoader; import org.telegram.messenger.exoplayer2.source.TrackGroup; @@ -39,7 +38,9 @@ import org.telegram.messenger.exoplayer2.upstream.Allocator; import org.telegram.messenger.exoplayer2.upstream.Loader; import org.telegram.messenger.exoplayer2.util.Assertions; import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.Util; import java.io.IOException; +import java.util.Arrays; import java.util.LinkedList; /** @@ -47,7 +48,7 @@ import java.util.LinkedList; * {@link SampleStream}s from which the loaded media can be consumed. */ /* package */ final class HlsSampleStreamWrapper implements Loader.Callback, - SequenceableLoader, ExtractorOutput, UpstreamFormatChangedListener { + Loader.ReleaseCallback, SequenceableLoader, ExtractorOutput, UpstreamFormatChangedListener { /** * A callback to be notified of events. @@ -81,11 +82,12 @@ import java.util.LinkedList; private final Loader loader; private final EventDispatcher eventDispatcher; private final HlsChunkSource.HlsChunkHolder nextChunkHolder; - private final SparseArray sampleQueues; private final LinkedList mediaChunks; private final Runnable maybeFinishPrepareRunnable; private final Handler handler; + private SampleQueue[] sampleQueues; + private int[] sampleQueueTrackIds; private boolean sampleQueuesBuilt; private boolean prepared; private int enabledTrackCount; @@ -97,12 +99,15 @@ import java.util.LinkedList; // Indexed by track (as exposed by this source). private TrackGroupArray trackGroups; private int primaryTrackGroupIndex; - // Indexed by group. - private boolean[] groupEnabledStates; + private boolean haveAudioVideoTrackGroups; + // Indexed by track group. + private boolean[] trackGroupEnabledStates; + private boolean[] trackGroupIsAudioVideoFlags; private long lastSeekPositionUs; private long pendingResetPositionUs; - + private boolean pendingResetUpstreamFormats; + private boolean seenFirstTrackSelection; private boolean loadingFinished; /** @@ -128,7 +133,8 @@ import java.util.LinkedList; this.eventDispatcher = eventDispatcher; loader = new Loader("Loader:HlsSampleStreamWrapper"); nextChunkHolder = new HlsChunkSource.HlsChunkHolder(); - sampleQueues = new SparseArray<>(); + sampleQueueTrackIds = new int[0]; + sampleQueues = new SampleQueue[0]; mediaChunks = new LinkedList<>(); maybeFinishPrepareRunnable = new Runnable() { @Override @@ -165,78 +171,153 @@ import java.util.LinkedList; return trackGroups; } + /** + * Called by the parent {@link HlsMediaPeriod} when a track selection occurs. + * + * @param selections The renderer track selections. + * @param mayRetainStreamFlags Flags indicating whether the existing sample stream can be retained + * for each selection. A {@code true} value indicates that the selection is unchanged, and + * that the caller does not require that the sample stream be recreated. + * @param streams The existing sample streams, which will be updated to reflect the provided + * selections. + * @param streamResetFlags Will be updated to indicate new sample streams, and sample streams that + * have been retained but with the requirement that the consuming renderer be reset. + * @param positionUs The current playback position in microseconds. + * @param forceReset If true then a reset is forced (i.e. a seek will be performed with in-buffer + * seeking disabled). + * @return Whether this wrapper requires the parent {@link HlsMediaPeriod} to perform a seek as + * part of the track selection. + */ public boolean selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, - SampleStream[] streams, boolean[] streamResetFlags, boolean isFirstTrackSelection) { + SampleStream[] streams, boolean[] streamResetFlags, long positionUs, boolean forceReset) { Assertions.checkState(prepared); - // Disable old tracks. + int oldEnabledTrackCount = enabledTrackCount; + // Deselect old tracks. for (int i = 0; i < selections.length; i++) { if (streams[i] != null && (selections[i] == null || !mayRetainStreamFlags[i])) { int group = ((HlsSampleStream) streams[i]).group; setTrackGroupEnabledState(group, false); - sampleQueues.valueAt(group).disable(); streams[i] = null; } } - // Enable new tracks. - TrackSelection primaryTrackSelection = null; - boolean selectedNewTracks = false; + // We'll always need to seek if we're being forced to reset, or if this is a first selection to + // a position other than the one we started preparing with, or if we're making a selection + // having previously disabled all tracks. + boolean seekRequired = forceReset + || (seenFirstTrackSelection ? oldEnabledTrackCount == 0 : positionUs != lastSeekPositionUs); + // Get the old (i.e. current before the loop below executes) primary track selection. The new + // primary selection will equal the old one unless it's changed in the loop. + TrackSelection oldPrimaryTrackSelection = chunkSource.getTrackSelection(); + TrackSelection primaryTrackSelection = oldPrimaryTrackSelection; + // Select new tracks. for (int i = 0; i < selections.length; i++) { if (streams[i] == null && selections[i] != null) { TrackSelection selection = selections[i]; - int group = trackGroups.indexOf(selection.getTrackGroup()); - setTrackGroupEnabledState(group, true); - if (group == primaryTrackGroupIndex) { + int trackGroupIndex = trackGroups.indexOf(selection.getTrackGroup()); + setTrackGroupEnabledState(trackGroupIndex, true); + if (trackGroupIndex == primaryTrackGroupIndex) { primaryTrackSelection = selection; chunkSource.selectTracks(selection); } - streams[i] = new HlsSampleStream(this, group); + streams[i] = new HlsSampleStream(this, trackGroupIndex); streamResetFlags[i] = true; - selectedNewTracks = true; - } - } - if (isFirstTrackSelection) { - // At the time of the first track selection all queues will be enabled, so we need to disable - // any that are no longer required. - int sampleQueueCount = sampleQueues.size(); - for (int i = 0; i < sampleQueueCount; i++) { - if (!groupEnabledStates[i]) { - sampleQueues.valueAt(i).disable(); - } - } - if (primaryTrackSelection != null && !mediaChunks.isEmpty()) { - primaryTrackSelection.updateSelectedTrack(0); - int chunkIndex = chunkSource.getTrackGroup().indexOf(mediaChunks.getLast().trackFormat); - if (primaryTrackSelection.getSelectedIndexInTrackGroup() != chunkIndex) { - // The loaded preparation chunk does match the selection. We discard it. - seekTo(lastSeekPositionUs); + // If there's still a chance of avoiding a seek, try and seek within the sample queue. + if (!seekRequired) { + SampleQueue sampleQueue = sampleQueues[trackGroupIndex]; + sampleQueue.rewind(); + // A seek can be avoided if we're able to advance to the current playback position in the + // sample queue, or if we haven't read anything from the queue since the previous seek + // (this case is common for sparse tracks such as metadata tracks). In all other cases a + // seek is required. + seekRequired = !sampleQueue.advanceTo(positionUs, true, true) + && sampleQueue.getReadIndex() != 0; } } } - // Cancel requests if necessary. + if (enabledTrackCount == 0) { chunkSource.reset(); downstreamTrackFormat = null; mediaChunks.clear(); if (loader.isLoading()) { + // Discard as much as we can synchronously. + for (SampleQueue sampleQueue : sampleQueues) { + sampleQueue.discardToEnd(); + } loader.cancelLoading(); + } else { + resetSampleQueues(); + } + } else { + if (!mediaChunks.isEmpty() + && !Util.areEqual(primaryTrackSelection, oldPrimaryTrackSelection)) { + // The primary track selection has changed and we have buffered media. The buffered media + // may need to be discarded. + boolean primarySampleQueueDirty = false; + if (!seenFirstTrackSelection) { + primaryTrackSelection.updateSelectedTrack(0); + int chunkIndex = chunkSource.getTrackGroup().indexOf(mediaChunks.getLast().trackFormat); + if (primaryTrackSelection.getSelectedIndexInTrackGroup() != chunkIndex) { + // This is the first selection and the chunk loaded during preparation does not match + // the initially selected format. + primarySampleQueueDirty = true; + } + } else { + // The primary sample queue contains media buffered for the old primary track selection. + primarySampleQueueDirty = true; + } + if (primarySampleQueueDirty) { + forceReset = true; + seekRequired = true; + pendingResetUpstreamFormats = true; + } + } + if (seekRequired) { + seekToUs(positionUs, forceReset); + // We'll need to reset renderers consuming from all streams due to the seek. + for (int i = 0; i < streams.length; i++) { + if (streams[i] != null) { + streamResetFlags[i] = true; + } + } } } - return selectedNewTracks; + + seenFirstTrackSelection = true; + return seekRequired; } - public void seekTo(long positionUs) { + public void discardBuffer(long positionUs) { + int sampleQueueCount = sampleQueues.length; + for (int i = 0; i < sampleQueueCount; i++) { + sampleQueues[i].discardTo(positionUs, false, trackGroupEnabledStates[i]); + } + } + + /** + * Attempts to seek to the specified position in microseconds. + * + * @param positionUs The seek position in microseconds. + * @param forceReset If true then a reset is forced (i.e. in-buffer seeking is disabled). + * @return Whether the wrapper was reset, meaning the wrapped sample queues were reset. If false, + * an in-buffer seek was performed. + */ + public boolean seekToUs(long positionUs, boolean forceReset) { lastSeekPositionUs = positionUs; + // If we're not forced to reset nor have a pending reset, see if we can seek within the buffer. + if (!forceReset && !isPendingReset() && seekInsideBufferUs(positionUs)) { + return false; + } + // We were unable to seek within the buffer, so need to reset. pendingResetPositionUs = positionUs; loadingFinished = false; mediaChunks.clear(); if (loader.isLoading()) { loader.cancelLoading(); } else { - int sampleQueueCount = sampleQueues.size(); - for (int i = 0; i < sampleQueueCount; i++) { - sampleQueues.valueAt(i).reset(groupEnabledStates[i]); - } + resetSampleQueues(); } + return true; } public long getBufferedPositionUs() { @@ -252,25 +333,32 @@ import java.util.LinkedList; if (lastCompletedMediaChunk != null) { bufferedPositionUs = Math.max(bufferedPositionUs, lastCompletedMediaChunk.endTimeUs); } - int sampleQueueCount = sampleQueues.size(); - for (int i = 0; i < sampleQueueCount; i++) { + for (SampleQueue sampleQueue : sampleQueues) { bufferedPositionUs = Math.max(bufferedPositionUs, - sampleQueues.valueAt(i).getLargestQueuedTimestampUs()); + sampleQueue.getLargestQueuedTimestampUs()); } return bufferedPositionUs; } } public void release() { - int sampleQueueCount = sampleQueues.size(); - for (int i = 0; i < sampleQueueCount; i++) { - sampleQueues.valueAt(i).disable(); + boolean releasedSynchronously = loader.release(this); + if (prepared && !releasedSynchronously) { + // Discard as much as we can synchronously. We only do this if we're prepared, since otherwise + // sampleQueues may still be being modified by the loading thread. + for (SampleQueue sampleQueue : sampleQueues) { + sampleQueue.discardToEnd(); + } } - loader.release(); handler.removeCallbacksAndMessages(null); released = true; } + @Override + public void onLoaderReleased() { + resetSampleQueues(); + } + public void setIsTimestampMaster(boolean isTimestampMaster) { chunkSource.setIsTimestampMaster(isTimestampMaster); } @@ -281,8 +369,8 @@ import java.util.LinkedList; // SampleStream implementation. - /* package */ boolean isReady(int group) { - return loadingFinished || (!isPendingReset() && !sampleQueues.valueAt(group).isEmpty()); + /* package */ boolean isReady(int trackGroupIndex) { + return loadingFinished || (!isPendingReset() && sampleQueues[trackGroupIndex].hasNextSample()); } /* package */ void maybeThrowError() throws IOException { @@ -290,47 +378,56 @@ import java.util.LinkedList; chunkSource.maybeThrowError(); } - /* package */ int readData(int group, FormatHolder formatHolder, DecoderInputBuffer buffer, - boolean requireFormat) { + /* package */ int readData(int trackGroupIndex, FormatHolder formatHolder, + DecoderInputBuffer buffer, boolean requireFormat) { if (isPendingReset()) { return C.RESULT_NOTHING_READ; } - while (mediaChunks.size() > 1 && finishedReadingChunk(mediaChunks.getFirst())) { - mediaChunks.removeFirst(); + if (!mediaChunks.isEmpty()) { + while (mediaChunks.size() > 1 && finishedReadingChunk(mediaChunks.getFirst())) { + mediaChunks.removeFirst(); + } + HlsMediaChunk currentChunk = mediaChunks.getFirst(); + Format trackFormat = currentChunk.trackFormat; + if (!trackFormat.equals(downstreamTrackFormat)) { + eventDispatcher.downstreamFormatChanged(trackType, trackFormat, + currentChunk.trackSelectionReason, currentChunk.trackSelectionData, + currentChunk.startTimeUs); + } + downstreamTrackFormat = trackFormat; } - HlsMediaChunk currentChunk = mediaChunks.getFirst(); - Format trackFormat = currentChunk.trackFormat; - if (!trackFormat.equals(downstreamTrackFormat)) { - eventDispatcher.downstreamFormatChanged(trackType, trackFormat, - currentChunk.trackSelectionReason, currentChunk.trackSelectionData, - currentChunk.startTimeUs); - } - downstreamTrackFormat = trackFormat; - return sampleQueues.valueAt(group).readData(formatHolder, buffer, requireFormat, - loadingFinished, lastSeekPositionUs); + return sampleQueues[trackGroupIndex].read(formatHolder, buffer, requireFormat, loadingFinished, + lastSeekPositionUs); } - /* package */ void skipData(int group, long positionUs) { - DefaultTrackOutput sampleQueue = sampleQueues.valueAt(group); + /* package */ void skipData(int trackGroupIndex, long positionUs) { + SampleQueue sampleQueue = sampleQueues[trackGroupIndex]; if (loadingFinished && positionUs > sampleQueue.getLargestQueuedTimestampUs()) { - sampleQueue.skipAll(); + sampleQueue.advanceToEnd(); } else { - sampleQueue.skipToKeyframeBefore(positionUs, true); + sampleQueue.advanceTo(positionUs, true, true); } } private boolean finishedReadingChunk(HlsMediaChunk chunk) { int chunkUid = chunk.uid; - for (int i = 0; i < sampleQueues.size(); i++) { - if (groupEnabledStates[i] && sampleQueues.valueAt(i).peekSourceId() == chunkUid) { + for (int i = 0; i < sampleQueues.length; i++) { + if (trackGroupEnabledStates[i] && sampleQueues[i].peekSourceId() == chunkUid) { return false; } } return true; } + private void resetSampleQueues() { + for (SampleQueue sampleQueue : sampleQueues) { + sampleQueue.reset(pendingResetUpstreamFormats); + } + pendingResetUpstreamFormats = false; + } + // SequenceableLoader implementation @Override @@ -348,6 +445,7 @@ import java.util.LinkedList; nextChunkHolder.clear(); if (endOfStream) { + pendingResetPositionUs = C.TIME_UNSET; loadingFinished = true; return true; } @@ -403,11 +501,10 @@ import java.util.LinkedList; loadable.trackSelectionReason, loadable.trackSelectionData, loadable.startTimeUs, loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded()); if (!released) { - int sampleQueueCount = sampleQueues.size(); - for (int i = 0; i < sampleQueueCount; i++) { - sampleQueues.valueAt(i).reset(groupEnabledStates[i]); + resetSampleQueues(); + if (enabledTrackCount > 0) { + callback.onContinueLoadingRequested(this); } - callback.onContinueLoadingRequested(this); } } @@ -455,12 +552,12 @@ import java.util.LinkedList; */ public void init(int chunkUid, boolean shouldSpliceIn) { upstreamChunkUid = chunkUid; - for (int i = 0; i < sampleQueues.size(); i++) { - sampleQueues.valueAt(i).sourceId(chunkUid); + for (SampleQueue sampleQueue : sampleQueues) { + sampleQueue.sourceId(chunkUid); } if (shouldSpliceIn) { - for (int i = 0; i < sampleQueues.size(); i++) { - sampleQueues.valueAt(i).splice(); + for (SampleQueue sampleQueue : sampleQueues) { + sampleQueue.splice(); } } } @@ -468,14 +565,19 @@ import java.util.LinkedList; // ExtractorOutput implementation. Called by the loading thread. @Override - public DefaultTrackOutput track(int id, int type) { - if (sampleQueues.indexOfKey(id) >= 0) { - return sampleQueues.get(id); + public SampleQueue track(int id, int type) { + int trackCount = sampleQueues.length; + for (int i = 0; i < trackCount; i++) { + if (sampleQueueTrackIds[i] == id) { + return sampleQueues[i]; + } } - DefaultTrackOutput trackOutput = new DefaultTrackOutput(allocator); + SampleQueue trackOutput = new SampleQueue(allocator); trackOutput.setUpstreamFormatChangeListener(this); - trackOutput.sourceId(upstreamChunkUid); - sampleQueues.put(id, trackOutput); + sampleQueueTrackIds = Arrays.copyOf(sampleQueueTrackIds, trackCount + 1); + sampleQueueTrackIds[trackCount] = id; + sampleQueues = Arrays.copyOf(sampleQueues, trackCount + 1); + sampleQueues[trackCount] = trackOutput; return trackOutput; } @@ -503,9 +605,8 @@ import java.util.LinkedList; if (released || prepared || !sampleQueuesBuilt) { return; } - int sampleQueueCount = sampleQueues.size(); - for (int i = 0; i < sampleQueueCount; i++) { - if (sampleQueues.valueAt(i).getUpstreamFormat() == null) { + for (SampleQueue sampleQueue : sampleQueues) { + if (sampleQueue.getUpstreamFormat() == null) { return; } } @@ -548,9 +649,9 @@ import java.util.LinkedList; // of the single track of this type. int primaryExtractorTrackType = PRIMARY_TYPE_NONE; int primaryExtractorTrackIndex = C.INDEX_UNSET; - int extractorTrackCount = sampleQueues.size(); + int extractorTrackCount = sampleQueues.length; for (int i = 0; i < extractorTrackCount; i++) { - String sampleMimeType = sampleQueues.valueAt(i).getUpstreamFormat().sampleMimeType; + String sampleMimeType = sampleQueues[i].getUpstreamFormat().sampleMimeType; int trackType; if (MimeTypes.isVideo(sampleMimeType)) { trackType = PRIMARY_TYPE_VIDEO; @@ -577,12 +678,17 @@ import java.util.LinkedList; // Instantiate the necessary internal data-structures. primaryTrackGroupIndex = C.INDEX_UNSET; - groupEnabledStates = new boolean[extractorTrackCount]; + trackGroupEnabledStates = new boolean[extractorTrackCount]; + trackGroupIsAudioVideoFlags = new boolean[extractorTrackCount]; // Construct the set of exposed track groups. TrackGroup[] trackGroups = new TrackGroup[extractorTrackCount]; for (int i = 0; i < extractorTrackCount; i++) { - Format sampleFormat = sampleQueues.valueAt(i).getUpstreamFormat(); + Format sampleFormat = sampleQueues[i].getUpstreamFormat(); + String mimeType = sampleFormat.sampleMimeType; + boolean isAudioVideo = MimeTypes.isVideo(mimeType) || MimeTypes.isAudio(mimeType); + trackGroupIsAudioVideoFlags[i] = isAudioVideo; + haveAudioVideoTrackGroups |= isAudioVideo; if (i == primaryExtractorTrackIndex) { Format[] formats = new Format[chunkSourceTrackCount]; for (int j = 0; j < chunkSourceTrackCount; j++) { @@ -602,12 +708,12 @@ import java.util.LinkedList; /** * Enables or disables a specified track group. * - * @param group The index of the track group. + * @param trackGroupIndex The index of the track group. * @param enabledState True if the group is being enabled, or false if it's being disabled. */ - private void setTrackGroupEnabledState(int group, boolean enabledState) { - Assertions.checkState(groupEnabledStates[group] != enabledState); - groupEnabledStates[group] = enabledState; + private void setTrackGroupEnabledState(int trackGroupIndex, boolean enabledState) { + Assertions.checkState(trackGroupEnabledStates[trackGroupIndex] != enabledState); + trackGroupEnabledStates[trackGroupIndex] = enabledState; enabledTrackCount = enabledTrackCount + (enabledState ? 1 : -1); } @@ -643,6 +749,30 @@ import java.util.LinkedList; return pendingResetPositionUs != C.TIME_UNSET; } + /** + * Attempts to seek to the specified position within the sample queues. + * + * @param positionUs The seek position in microseconds. + * @return Whether the in-buffer seek was successful. + */ + private boolean seekInsideBufferUs(long positionUs) { + int trackCount = sampleQueues.length; + for (int i = 0; i < trackCount; i++) { + SampleQueue sampleQueue = sampleQueues[i]; + sampleQueue.rewind(); + boolean seekInsideQueue = sampleQueue.advanceTo(positionUs, true, false); + // If we have AV tracks then an in-queue seek is successful if the seek into every AV queue + // is successful. We ignore whether seeks within non-AV queues are successful in this case, as + // they may be sparse or poorly interleaved. If we only have non-AV tracks then a seek is + // successful only if the seek into every queue succeeds. + if (!seekInsideQueue && (trackGroupIsAudioVideoFlags[i] || !haveAudioVideoTrackGroups)) { + return false; + } + sampleQueue.discardToRead(); + } + return true; + } + private static String getAudioCodecs(String codecs) { return getCodecsOfType(codecs, C.TRACK_TYPE_AUDIO); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/WebvttExtractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/WebvttExtractor.java index d3c2644ca..69b55f592 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/WebvttExtractor.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/WebvttExtractor.java @@ -141,8 +141,7 @@ import java.util.regex.Pattern; throw new ParserException("X-TIMESTAMP-MAP doesn't contain media timestamp: " + line); } vttTimestampUs = WebvttParserUtil.parseTimestampUs(localTimestampMatcher.group(1)); - tsTimestampUs = TimestampAdjuster.ptsToUs( - Long.parseLong(mediaTimestampMatcher.group(1))); + tsTimestampUs = TimestampAdjuster.ptsToUs(Long.parseLong(mediaTimestampMatcher.group(1))); } } @@ -155,8 +154,8 @@ import java.util.regex.Pattern; } long firstCueTimeUs = WebvttParserUtil.parseTimestampUs(cueHeaderMatcher.group(1)); - long sampleTimeUs = timestampAdjuster.adjustSampleTimestamp( - firstCueTimeUs + tsTimestampUs - vttTimestampUs); + long sampleTimeUs = timestampAdjuster.adjustTsTimestamp( + TimestampAdjuster.usToPts(firstCueTimeUs + tsTimestampUs - vttTimestampUs)); long subsampleOffsetUs = sampleTimeUs - firstCueTimeUs; // Output the track. TrackOutput trackOutput = buildTrackOutput(subsampleOffsetUs); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java index 0a0b314a3..f03e0e9ef 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java @@ -17,6 +17,7 @@ package org.telegram.messenger.exoplayer2.source.hls.playlist; import org.telegram.messenger.exoplayer2.Format; import org.telegram.messenger.exoplayer2.util.MimeTypes; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -30,15 +31,31 @@ public final class HlsMasterPlaylist extends HlsPlaylist { */ public static final class HlsUrl { + /** + * The http url from which the media playlist can be obtained. + */ public final String url; + /** + * Format information associated with the HLS url. + */ public final Format format; - public static HlsUrl createMediaPlaylistHlsUrl(String baseUri) { + /** + * Creates an HLS url from a given http url. + * + * @param url The url. + * @return An HLS url. + */ + public static HlsUrl createMediaPlaylistHlsUrl(String url) { Format format = Format.createContainerFormat("0", MimeTypes.APPLICATION_M3U8, null, null, Format.NO_VALUE, 0, null); - return new HlsUrl(baseUri, format); + return new HlsUrl(url, format); } + /** + * @param url See {@link #url}. + * @param format See {@link #format}. + */ public HlsUrl(String url, Format format) { this.url = url; this.format = format; @@ -46,28 +63,88 @@ public final class HlsMasterPlaylist extends HlsPlaylist { } + /** + * The list of variants declared by the playlist. + */ public final List variants; + /** + * The list of demuxed audios declared by the playlist. + */ public final List audios; + /** + * The list of subtitles declared by the playlist. + */ public final List subtitles; + /** + * The format of the audio muxed in the variants. May be null if the playlist does not declare any + * muxed audio. + */ public final Format muxedAudioFormat; + /** + * The format of the closed captions declared by the playlist. May be empty if the playlist + * explicitly declares no captions are available, or null if the playlist does not declare any + * captions information. + */ public final List muxedCaptionFormats; - public HlsMasterPlaylist(String baseUri, List variants, List audios, - List subtitles, Format muxedAudioFormat, List muxedCaptionFormats) { - super(baseUri); + /** + * @param baseUri See {@link #baseUri}. + * @param tags See {@link #tags}. + * @param variants See {@link #variants}. + * @param audios See {@link #audios}. + * @param subtitles See {@link #subtitles}. + * @param muxedAudioFormat See {@link #muxedAudioFormat}. + * @param muxedCaptionFormats See {@link #muxedCaptionFormats}. + */ + public HlsMasterPlaylist(String baseUri, List tags, List variants, + List audios, List subtitles, Format muxedAudioFormat, + List muxedCaptionFormats) { + super(baseUri, tags); this.variants = Collections.unmodifiableList(variants); this.audios = Collections.unmodifiableList(audios); this.subtitles = Collections.unmodifiableList(subtitles); this.muxedAudioFormat = muxedAudioFormat; - this.muxedCaptionFormats = Collections.unmodifiableList(muxedCaptionFormats); + this.muxedCaptionFormats = muxedCaptionFormats != null + ? Collections.unmodifiableList(muxedCaptionFormats) : null; } - public static HlsMasterPlaylist createSingleVariantMasterPlaylist(String variantUri) { - List variant = Collections.singletonList(HlsUrl.createMediaPlaylistHlsUrl(variantUri)); + /** + * Returns a copy of this playlist which includes only the renditions identified by the given + * urls. + * + * @param renditionUrls List of rendition urls. + * @return A copy of this playlist which includes only the renditions identified by the given + * urls. + */ + public HlsMasterPlaylist copy(List renditionUrls) { + return new HlsMasterPlaylist(baseUri, tags, copyRenditionsList(variants, renditionUrls), + copyRenditionsList(audios, renditionUrls), copyRenditionsList(subtitles, renditionUrls), + muxedAudioFormat, muxedCaptionFormats); + } + + /** + * Creates a playlist with a single variant. + * + * @param variantUrl The url of the single variant. + * @return A master playlist with a single variant for the provided url. + */ + public static HlsMasterPlaylist createSingleVariantMasterPlaylist(String variantUrl) { + List variant = Collections.singletonList(HlsUrl.createMediaPlaylistHlsUrl(variantUrl)); List emptyList = Collections.emptyList(); - return new HlsMasterPlaylist(null, variant, emptyList, emptyList, null, - Collections.emptyList()); + return new HlsMasterPlaylist(null, Collections.emptyList(), variant, emptyList, + emptyList, null, null); + } + + private static List copyRenditionsList(List renditions, List urls) { + List copiedRenditions = new ArrayList<>(urls.size()); + for (int i = 0; i < renditions.size(); i++) { + HlsUrl rendition = renditions.get(i); + if (urls.contains(rendition.url)) { + copiedRenditions.add(rendition); + } + } + return copiedRenditions; } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java index 8bafc464a..4767468da 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java @@ -33,24 +33,64 @@ public final class HlsMediaPlaylist extends HlsPlaylist { */ public static final class Segment implements Comparable { + /** + * The url of the segment. + */ public final String url; + /** + * The duration of the segment in microseconds, as defined by #EXTINF. + */ public final long durationUs; + /** + * The number of #EXT-X-DISCONTINUITY tags in the playlist before the segment. + */ public final int relativeDiscontinuitySequence; + /** + * The start time of the segment in microseconds, relative to the start of the playlist. + */ public final long relativeStartTimeUs; + /** + * Whether the segment is encrypted, as defined by #EXT-X-KEY. + */ public final boolean isEncrypted; + /** + * The encryption key uri as defined by #EXT-X-KEY, or null if the segment is not encrypted. + */ public final String encryptionKeyUri; + /** + * The encryption initialization vector as defined by #EXT-X-KEY, or null if the segment is not + * encrypted. + */ public final String encryptionIV; + /** + * The segment's byte range offset, as defined by #EXT-X-BYTERANGE. + */ public final long byterangeOffset; + /** + * The segment's byte range length, as defined by #EXT-X-BYTERANGE, or {@link C#LENGTH_UNSET} if + * no byte range is specified. + */ public final long byterangeLength; public Segment(String uri, long byterangeOffset, long byterangeLength) { this(uri, 0, -1, C.TIME_UNSET, false, null, null, byterangeOffset, byterangeLength); } - public Segment(String uri, long durationUs, int relativeDiscontinuitySequence, + /** + * @param url See {@link #url}. + * @param durationUs See {@link #durationUs}. + * @param relativeDiscontinuitySequence See {@link #relativeDiscontinuitySequence}. + * @param relativeStartTimeUs See {@link #relativeStartTimeUs}. + * @param isEncrypted See {@link #isEncrypted}. + * @param encryptionKeyUri See {@link #encryptionKeyUri}. + * @param encryptionIV See {@link #encryptionIV}. + * @param byterangeOffset See {@link #byterangeOffset}. + * @param byterangeLength See {@link #byterangeLength}. + */ + public Segment(String url, long durationUs, int relativeDiscontinuitySequence, long relativeStartTimeUs, boolean isEncrypted, String encryptionKeyUri, String encryptionIV, long byterangeOffset, long byterangeLength) { - this.url = uri; + this.url = url; this.durationUs = durationUs; this.relativeDiscontinuitySequence = relativeDiscontinuitySequence; this.relativeStartTimeUs = relativeStartTimeUs; @@ -70,7 +110,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist { } /** - * Type of the playlist as specified by #EXT-X-PLAYLIST-TYPE. + * Type of the playlist as defined by #EXT-X-PLAYLIST-TYPE. */ @Retention(RetentionPolicy.SOURCE) @IntDef({PLAYLIST_TYPE_UNKNOWN, PLAYLIST_TYPE_VOD, PLAYLIST_TYPE_EVENT}) @@ -79,25 +119,88 @@ public final class HlsMediaPlaylist extends HlsPlaylist { public static final int PLAYLIST_TYPE_VOD = 1; public static final int PLAYLIST_TYPE_EVENT = 2; + /** + * The type of the playlist. See {@link PlaylistType}. + */ @PlaylistType public final int playlistType; + /** + * The start offset in microseconds, as defined by #EXT-X-START. + */ public final long startOffsetUs; + /** + * The start time of the playlist in playback timebase in microseconds. + */ public final long startTimeUs; + /** + * Whether the playlist contains the #EXT-X-DISCONTINUITY-SEQUENCE tag. + */ public final boolean hasDiscontinuitySequence; + /** + * The discontinuity sequence number of the first media segment in the playlist, as defined by + * #EXT-X-DISCONTINUITY-SEQUENCE. + */ public final int discontinuitySequence; + /** + * The media sequence number of the first media segment in the playlist, as defined by + * #EXT-X-MEDIA-SEQUENCE. + */ public final int mediaSequence; + /** + * The compatibility version, as defined by #EXT-X-VERSION. + */ public final int version; + /** + * The target duration in microseconds, as defined by #EXT-X-TARGETDURATION. + */ public final long targetDurationUs; + /** + * Whether the playlist contains the #EXT-X-INDEPENDENT-SEGMENTS tag. + */ + public final boolean hasIndependentSegmentsTag; + /** + * Whether the playlist contains the #EXT-X-ENDLIST tag. + */ public final boolean hasEndTag; + /** + * Whether the playlist contains a #EXT-X-PROGRAM-DATE-TIME tag. + */ public final boolean hasProgramDateTime; + /** + * The initialization segment, as defined by #EXT-X-MAP. + */ public final Segment initializationSegment; + /** + * The list of segments in the playlist. + */ public final List segments; + /** + * The total duration of the playlist in microseconds. + */ public final long durationUs; - public HlsMediaPlaylist(@PlaylistType int playlistType, String baseUri, long startOffsetUs, - long startTimeUs, boolean hasDiscontinuitySequence, int discontinuitySequence, - int mediaSequence, int version, long targetDurationUs, boolean hasEndTag, - boolean hasProgramDateTime, Segment initializationSegment, List segments) { - super(baseUri); + /** + * @param playlistType See {@link #playlistType}. + * @param baseUri See {@link #baseUri}. + * @param tags See {@link #tags}. + * @param startOffsetUs See {@link #startOffsetUs}. + * @param startTimeUs See {@link #startTimeUs}. + * @param hasDiscontinuitySequence See {@link #hasDiscontinuitySequence}. + * @param discontinuitySequence See {@link #discontinuitySequence}. + * @param mediaSequence See {@link #mediaSequence}. + * @param version See {@link #version}. + * @param targetDurationUs See {@link #targetDurationUs}. + * @param hasIndependentSegmentsTag See {@link #hasIndependentSegmentsTag}. + * @param hasEndTag See {@link #hasEndTag}. + * @param hasProgramDateTime See {@link #hasProgramDateTime}. + * @param initializationSegment See {@link #initializationSegment}. + * @param segments See {@link #segments}. + */ + public HlsMediaPlaylist(@PlaylistType int playlistType, String baseUri, List tags, + long startOffsetUs, long startTimeUs, boolean hasDiscontinuitySequence, + int discontinuitySequence, int mediaSequence, int version, long targetDurationUs, + boolean hasIndependentSegmentsTag, boolean hasEndTag, boolean hasProgramDateTime, + Segment initializationSegment, List segments) { + super(baseUri, tags); this.playlistType = playlistType; this.startTimeUs = startTimeUs; this.hasDiscontinuitySequence = hasDiscontinuitySequence; @@ -105,6 +208,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist { this.mediaSequence = mediaSequence; this.version = version; this.targetDurationUs = targetDurationUs; + this.hasIndependentSegmentsTag = hasIndependentSegmentsTag; this.hasEndTag = hasEndTag; this.hasProgramDateTime = hasProgramDateTime; this.initializationSegment = initializationSegment; @@ -139,6 +243,9 @@ public final class HlsMediaPlaylist extends HlsPlaylist { || (segmentCount == otherSegmentCount && hasEndTag && !other.hasEndTag); } + /** + * Returns the result of adding the duration of the playlist to its start time. + */ public long getEndTimeUs() { return startTimeUs + durationUs; } @@ -153,9 +260,9 @@ public final class HlsMediaPlaylist extends HlsPlaylist { * @return The playlist. */ public HlsMediaPlaylist copyWith(long startTimeUs, int discontinuitySequence) { - return new HlsMediaPlaylist(playlistType, baseUri, startOffsetUs, startTimeUs, true, - discontinuitySequence, mediaSequence, version, targetDurationUs, hasEndTag, - hasProgramDateTime, initializationSegment, segments); + return new HlsMediaPlaylist(playlistType, baseUri, tags, startOffsetUs, startTimeUs, true, + discontinuitySequence, mediaSequence, version, targetDurationUs, hasIndependentSegmentsTag, + hasEndTag, hasProgramDateTime, initializationSegment, segments); } /** @@ -168,9 +275,9 @@ public final class HlsMediaPlaylist extends HlsPlaylist { if (this.hasEndTag) { return this; } - return new HlsMediaPlaylist(playlistType, baseUri, startOffsetUs, startTimeUs, + return new HlsMediaPlaylist(playlistType, baseUri, tags, startOffsetUs, startTimeUs, hasDiscontinuitySequence, discontinuitySequence, mediaSequence, version, targetDurationUs, - true, hasProgramDateTime, initializationSegment, segments); + hasIndependentSegmentsTag, true, hasProgramDateTime, initializationSegment, segments); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsPlaylist.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsPlaylist.java index 967a42a83..51df0b042 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsPlaylist.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsPlaylist.java @@ -15,15 +15,30 @@ */ package org.telegram.messenger.exoplayer2.source.hls.playlist; +import java.util.Collections; +import java.util.List; + /** * Represents an HLS playlist. */ public abstract class HlsPlaylist { + /** + * The base uri. Used to resolve relative paths. + */ public final String baseUri; + /** + * The list of tags in the playlist. + */ + public final List tags; - protected HlsPlaylist(String baseUri) { + /** + * @param baseUri See {@link #baseUri}. + * @param tags See {@link #tags}. + */ + protected HlsPlaylist(String baseUri, List tags) { this.baseUri = baseUri; + this.tags = Collections.unmodifiableList(tags); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsPlaylistParser.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsPlaylistParser.java index 2325e1eca..4d2020543 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsPlaylistParser.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsPlaylistParser.java @@ -29,6 +29,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Queue; @@ -42,6 +44,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser variantUrls = new HashSet<>(); ArrayList variants = new ArrayList<>(); ArrayList audios = new ArrayList<>(); ArrayList subtitles = new ArrayList<>(); + ArrayList tags = new ArrayList<>(); Format muxedAudioFormat = null; - ArrayList muxedCaptionFormats = new ArrayList<>(); + List muxedCaptionFormats = null; + boolean noClosedCaptions = false; String line; while (iterator.hasNext()) { line = iterator.next(); + + if (line.startsWith(TAG_PREFIX)) { + // We expose all tags through the playlist. + tags.add(line); + } + if (line.startsWith(TAG_MEDIA)) { @C.SelectionFlags int selectionFlags = parseSelectionFlags(line); String uri = parseOptionalStringAttr(line, REGEX_URI); @@ -209,6 +227,9 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser(); + } muxedCaptionFormats.add(Format.createTextContainerFormat(id, null, mimeType, null, Format.NO_VALUE, selectionFlags, language, accessibilityChannel)); break; @@ -218,8 +239,14 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser segments = new ArrayList<>(); + List tags = new ArrayList<>(); long segmentDurationUs = 0; boolean hasDiscontinuitySequence = false; @@ -281,14 +315,18 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser> { + /** + * Thrown when a playlist is considered to be stuck due to a server side error. + */ + public static final class PlaylistStuckException extends IOException { + + /** + * The url of the stuck playlist. + */ + public final String url; + + private PlaylistStuckException(String url) { + this.url = url; + } + + } + + /** + * Thrown when the media sequence of a new snapshot indicates the server has reset. + */ + public static final class PlaylistResetException extends IOException { + + /** + * The url of the reset playlist. + */ + public final String url; + + private PlaylistResetException(String url) { + this.url = url; + } + + } + /** * Listener for primary playlist changes. */ @@ -76,14 +108,14 @@ public final class HlsPlaylistTracker implements Loader.Callback playlistParser; private final int minRetryCount; private final IdentityHashMap playlistBundles; private final Handler playlistRefreshHandler; @@ -108,15 +140,16 @@ public final class HlsPlaylistTracker implements Loader.Callback playlistParser) { this.initialPlaylistUri = initialPlaylistUri; this.dataSourceFactory = dataSourceFactory; this.eventDispatcher = eventDispatcher; this.minRetryCount = minRetryCount; this.primaryPlaylistListener = primaryPlaylistListener; + this.playlistParser = playlistParser; listeners = new ArrayList<>(); initialPlaylistLoader = new Loader("HlsPlaylistTracker:MasterPlaylist"); - playlistParser = new HlsPlaylistParser(); playlistBundles = new IdentityHashMap<>(); playlistRefreshHandler = new Handler(); } @@ -200,18 +233,29 @@ public final class HlsPlaylistTracker implements Loader.Callback PRIMARY_URL_KEEPALIVE_MS) { - primaryHlsUrl = url; - playlistBundles.get(primaryHlsUrl).loadPlaylist(); - } + primaryHlsUrl = url; + playlistBundles.get(primaryHlsUrl).loadPlaylist(); } private void createBundles(List urls) { int listSize = urls.size(); - long currentTimeMs = SystemClock.elapsedRealtime(); for (int i = 0; i < listSize; i++) { HlsUrl url = urls.get(i); - MediaPlaylistBundle bundle = new MediaPlaylistBundle(url, currentTimeMs); + MediaPlaylistBundle bundle = new MediaPlaylistBundle(url); playlistBundles.put(url, bundle); } } @@ -325,9 +364,8 @@ public final class HlsPlaylistTracker implements Loader.Callback( dataSourceFactory.createDataSource(C.DATA_TYPE_MANIFEST), @@ -445,7 +482,6 @@ public final class HlsPlaylistTracker implements Loader.Callback C.usToMs(playlistSnapshot.targetDurationUs) + * PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT) { + // The playlist seems to be stuck. Blacklist it. + playlistError = new PlaylistStuckException(playlistUrl.url); + blacklistPlaylist(); + } } - if (refreshDelayUs != C.TIME_UNSET) { - // See HLS spec v20, section 6.3.4 for more information on media playlist refreshing. - pendingRefresh = playlistRefreshHandler.postDelayed(this, C.usToMs(refreshDelayUs)); + // Do not allow the playlist to load again within the target duration if we obtained a new + // snapshot, or half the target duration otherwise. + earliestNextLoadTimeMs = currentTimeMs + C.usToMs(playlistSnapshot != oldPlaylist + ? playlistSnapshot.targetDurationUs : (playlistSnapshot.targetDurationUs / 2)); + // Schedule a load if this is the primary playlist and it doesn't have an end tag. Else the + // next load will be scheduled when refreshPlaylist is called, or when this playlist becomes + // the primary. + if (playlistUrl == primaryHlsUrl && !playlistSnapshot.hasEndTag) { + loadPlaylist(); } } + /** + * Blacklists the playlist. + * + * @return Whether the playlist is the primary, despite being blacklisted. + */ + private boolean blacklistPlaylist() { + blacklistUntilMs = SystemClock.elapsedRealtime() + + ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS; + notifyPlaylistBlacklisting(playlistUrl, ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS); + return primaryHlsUrl == playlistUrl && !maybeSelectNewPrimaryUrl(); + } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/SsMediaPeriod.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/SsMediaPeriod.java index 6cc4549af..813b90c37 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/SsMediaPeriod.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/SsMediaPeriod.java @@ -68,8 +68,9 @@ import java.util.ArrayList; ProtectionElement protectionElement = manifest.protectionElement; if (protectionElement != null) { byte[] keyId = getProtectionElementKeyId(protectionElement.data); + // We assume pattern encryption does not apply. trackEncryptionBoxes = new TrackEncryptionBox[] { - new TrackEncryptionBox(true, INITIALIZATION_VECTOR_SIZE, keyId)}; + new TrackEncryptionBox(true, null, INITIALIZATION_VECTOR_SIZE, keyId, 0, 0, null)}; } else { trackEncryptionBoxes = null; } @@ -93,7 +94,7 @@ import java.util.ArrayList; } @Override - public void prepare(Callback callback) { + public void prepare(Callback callback, long positionUs) { this.callback = callback; callback.onPrepared(this); } @@ -158,14 +159,7 @@ import java.util.ArrayList; @Override public long getBufferedPositionUs() { - long bufferedPositionUs = Long.MAX_VALUE; - for (ChunkSampleStream sampleStream : sampleStreams) { - long rendererBufferedPositionUs = sampleStream.getBufferedPositionUs(); - if (rendererBufferedPositionUs != C.TIME_END_OF_SOURCE) { - bufferedPositionUs = Math.min(bufferedPositionUs, rendererBufferedPositionUs); - } - } - return bufferedPositionUs == Long.MAX_VALUE ? C.TIME_END_OF_SOURCE : bufferedPositionUs; + return sequenceableLoader.getBufferedPositionUs(); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/SsMediaSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/SsMediaSource.java index 0e3544180..c0d493b71 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/SsMediaSource.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/SsMediaSource.java @@ -20,6 +20,7 @@ import android.os.Handler; import android.os.SystemClock; import org.telegram.messenger.exoplayer2.C; import org.telegram.messenger.exoplayer2.ExoPlayer; +import org.telegram.messenger.exoplayer2.ExoPlayerLibraryInfo; import org.telegram.messenger.exoplayer2.ParserException; import org.telegram.messenger.exoplayer2.Timeline; import org.telegram.messenger.exoplayer2.source.AdaptiveMediaSourceEventListener; @@ -46,6 +47,10 @@ import java.util.ArrayList; public final class SsMediaSource implements MediaSource, Loader.Callback> { + static { + ExoPlayerLibraryInfo.registerModule("goog.exo.smoothstreaming"); + } + /** * The default minimum number of times to retry loading data prior to failing. */ @@ -222,8 +227,8 @@ public final class SsMediaSource implements MediaSource, } @Override - public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { - Assertions.checkArgument(index == 0); + public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { + Assertions.checkArgument(id.periodIndex == 0); SsMediaPeriod period = new SsMediaPeriod(manifest, chunkSourceFactory, minLoadableRetryCount, eventDispatcher, manifestLoaderErrorThrower, allocator); mediaPeriods.add(period); @@ -287,39 +292,41 @@ public final class SsMediaSource implements MediaSource, for (int i = 0; i < mediaPeriods.size(); i++) { mediaPeriods.get(i).updateManifest(manifest); } + + long startTimeUs = Long.MAX_VALUE; + long endTimeUs = Long.MIN_VALUE; + for (StreamElement element : manifest.streamElements) { + if (element.chunkCount > 0) { + startTimeUs = Math.min(startTimeUs, element.getStartTimeUs(0)); + endTimeUs = Math.max(endTimeUs, element.getStartTimeUs(element.chunkCount - 1) + + element.getChunkDurationUs(element.chunkCount - 1)); + } + } + Timeline timeline; - if (manifest.isLive) { - long startTimeUs = Long.MAX_VALUE; - long endTimeUs = Long.MIN_VALUE; - for (int i = 0; i < manifest.streamElements.length; i++) { - StreamElement element = manifest.streamElements[i]; - if (element.chunkCount > 0) { - startTimeUs = Math.min(startTimeUs, element.getStartTimeUs(0)); - endTimeUs = Math.max(endTimeUs, element.getStartTimeUs(element.chunkCount - 1) - + element.getChunkDurationUs(element.chunkCount - 1)); - } + if (startTimeUs == Long.MAX_VALUE) { + long periodDurationUs = manifest.isLive ? C.TIME_UNSET : 0; + timeline = new SinglePeriodTimeline(periodDurationUs, 0, 0, 0, true /* isSeekable */, + manifest.isLive /* isDynamic */); + } else if (manifest.isLive) { + if (manifest.dvrWindowLengthUs != C.TIME_UNSET && manifest.dvrWindowLengthUs > 0) { + startTimeUs = Math.max(startTimeUs, endTimeUs - manifest.dvrWindowLengthUs); } - if (startTimeUs == Long.MAX_VALUE) { - timeline = new SinglePeriodTimeline(C.TIME_UNSET, false); - } else { - if (manifest.dvrWindowLengthUs != C.TIME_UNSET - && manifest.dvrWindowLengthUs > 0) { - startTimeUs = Math.max(startTimeUs, endTimeUs - manifest.dvrWindowLengthUs); - } - long durationUs = endTimeUs - startTimeUs; - long defaultStartPositionUs = durationUs - C.msToUs(livePresentationDelayMs); - if (defaultStartPositionUs < MIN_LIVE_DEFAULT_START_POSITION_US) { - // The default start position is too close to the start of the live window. Set it to the - // minimum default start position provided the window is at least twice as big. Else set - // it to the middle of the window. - defaultStartPositionUs = Math.min(MIN_LIVE_DEFAULT_START_POSITION_US, durationUs / 2); - } - timeline = new SinglePeriodTimeline(C.TIME_UNSET, durationUs, startTimeUs, - defaultStartPositionUs, true /* isSeekable */, true /* isDynamic */); + long durationUs = endTimeUs - startTimeUs; + long defaultStartPositionUs = durationUs - C.msToUs(livePresentationDelayMs); + if (defaultStartPositionUs < MIN_LIVE_DEFAULT_START_POSITION_US) { + // The default start position is too close to the start of the live window. Set it to the + // minimum default start position provided the window is at least twice as big. Else set + // it to the middle of the window. + defaultStartPositionUs = Math.min(MIN_LIVE_DEFAULT_START_POSITION_US, durationUs / 2); } + timeline = new SinglePeriodTimeline(C.TIME_UNSET, durationUs, startTimeUs, + defaultStartPositionUs, true /* isSeekable */, true /* isDynamic */); } else { - boolean isSeekable = manifest.durationUs != C.TIME_UNSET; - timeline = new SinglePeriodTimeline(manifest.durationUs, isSeekable); + long durationUs = manifest.durationUs != C.TIME_UNSET ? manifest.durationUs + : endTimeUs - startTimeUs; + timeline = new SinglePeriodTimeline(startTimeUs + durationUs, durationUs, startTimeUs, 0, + true /* isSeekable */, false /* isDynamic */); } sourceListener.onSourceInfoRefreshed(timeline, manifest); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java index 08f693a7e..71c00fdcb 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java @@ -375,7 +375,7 @@ public class SsManifestParser implements ParsingLoadable.Parser { StreamElement[] streamElementArray = new StreamElement[streamElements.size()]; streamElements.toArray(streamElementArray); if (protectionElement != null) { - DrmInitData drmInitData = new DrmInitData(new SchemeData(protectionElement.uuid, + DrmInitData drmInitData = new DrmInitData(new SchemeData(protectionElement.uuid, null, MimeTypes.VIDEO_MP4, protectionElement.data)); for (StreamElement streamElement : streamElementArray) { for (int i = 0; i < streamElement.formats.length; i++) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SubtitleDecoderFactory.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SubtitleDecoderFactory.java index 76791bb7f..ec5fa8c24 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SubtitleDecoderFactory.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SubtitleDecoderFactory.java @@ -19,6 +19,7 @@ import org.telegram.messenger.exoplayer2.Format; import org.telegram.messenger.exoplayer2.text.cea.Cea608Decoder; import org.telegram.messenger.exoplayer2.text.cea.Cea708Decoder; import org.telegram.messenger.exoplayer2.text.dvb.DvbDecoder; +import org.telegram.messenger.exoplayer2.text.ssa.SsaDecoder; import org.telegram.messenger.exoplayer2.text.subrip.SubripDecoder; import org.telegram.messenger.exoplayer2.text.ttml.TtmlDecoder; import org.telegram.messenger.exoplayer2.text.tx3g.Tx3gDecoder; @@ -58,6 +59,7 @@ public interface SubtitleDecoderFactory { *

  • WebVTT (MP4) ({@link Mp4WebvttDecoder})
  • *
  • TTML ({@link TtmlDecoder})
  • *
  • SubRip ({@link SubripDecoder})
  • + *
  • SSA/ASS ({@link SsaDecoder})
  • *
  • TX3G ({@link Tx3gDecoder})
  • *
  • Cea608 ({@link Cea608Decoder})
  • *
  • Cea708 ({@link Cea708Decoder})
  • @@ -70,6 +72,7 @@ public interface SubtitleDecoderFactory { public boolean supportsFormat(Format format) { String mimeType = format.sampleMimeType; return MimeTypes.TEXT_VTT.equals(mimeType) + || MimeTypes.TEXT_SSA.equals(mimeType) || MimeTypes.APPLICATION_TTML.equals(mimeType) || MimeTypes.APPLICATION_MP4VTT.equals(mimeType) || MimeTypes.APPLICATION_SUBRIP.equals(mimeType) @@ -85,6 +88,8 @@ public interface SubtitleDecoderFactory { switch (format.sampleMimeType) { case MimeTypes.TEXT_VTT: return new WebvttDecoder(); + case MimeTypes.TEXT_SSA: + return new SsaDecoder(format.initializationData); case MimeTypes.APPLICATION_MP4VTT: return new Mp4WebvttDecoder(); case MimeTypes.APPLICATION_TTML: diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SubtitleInputBuffer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SubtitleInputBuffer.java index eeaf5f489..ca1336cc1 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SubtitleInputBuffer.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SubtitleInputBuffer.java @@ -37,6 +37,9 @@ public final class SubtitleInputBuffer extends DecoderInputBuffer @Override public int compareTo(@NonNull SubtitleInputBuffer other) { + if (isEndOfStream() != other.isEndOfStream()) { + return isEndOfStream() ? 1 : -1; + } long delta = timeUs - other.timeUs; if (delta == 0) { return 0; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/TextRenderer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/TextRenderer.java index 028c155c0..e812e3f74 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/TextRenderer.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/TextRenderer.java @@ -130,7 +130,7 @@ public final class TextRenderer extends BaseRenderer implements Callback { } @Override - protected void onStreamChanged(Format[] formats) throws ExoPlaybackException { + protected void onStreamChanged(Format[] formats, long offsetUs) throws ExoPlaybackException { streamFormat = formats[0]; if (decoder != null) { decoderReplacementState = REPLACEMENT_STATE_SIGNAL_END_OF_STREAM; @@ -254,7 +254,6 @@ public final class TextRenderer extends BaseRenderer implements Callback { streamFormat = null; clearOutput(); releaseDecoder(); - super.onDisabled(); } @Override @@ -295,9 +294,9 @@ public final class TextRenderer extends BaseRenderer implements Callback { } private long getNextEventTime() { - return ((nextSubtitleEventIndex == C.INDEX_UNSET) - || (nextSubtitleEventIndex >= subtitle.getEventTimeCount())) ? Long.MAX_VALUE - : (subtitle.getEventTime(nextSubtitleEventIndex)); + return nextSubtitleEventIndex == C.INDEX_UNSET + || nextSubtitleEventIndex >= subtitle.getEventTimeCount() + ? Long.MAX_VALUE : subtitle.getEventTime(nextSubtitleEventIndex); } private void updateOutput(List cues) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/cea/CeaDecoder.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/cea/CeaDecoder.java index 2329f7f66..568fa582b 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/cea/CeaDecoder.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/cea/CeaDecoder.java @@ -24,7 +24,7 @@ import org.telegram.messenger.exoplayer2.text.SubtitleInputBuffer; import org.telegram.messenger.exoplayer2.text.SubtitleOutputBuffer; import org.telegram.messenger.exoplayer2.util.Assertions; import java.util.LinkedList; -import java.util.TreeSet; +import java.util.PriorityQueue; /** * Base class for subtitle parsers for CEA captions. @@ -36,7 +36,7 @@ import java.util.TreeSet; private final LinkedList availableInputBuffers; private final LinkedList availableOutputBuffers; - private final TreeSet queuedInputBuffers; + private final PriorityQueue queuedInputBuffers; private SubtitleInputBuffer dequeuedInputBuffer; private long playbackPositionUs; @@ -50,7 +50,7 @@ import java.util.TreeSet; for (int i = 0; i < NUM_OUTPUT_BUFFERS; i++) { availableOutputBuffers.add(new CeaOutputBuffer(this)); } - queuedInputBuffers = new TreeSet<>(); + queuedInputBuffers = new PriorityQueue<>(); } @Override @@ -73,7 +73,6 @@ import java.util.TreeSet; @Override public void queueInputBuffer(SubtitleInputBuffer inputBuffer) throws SubtitleDecoderException { - Assertions.checkArgument(inputBuffer != null); Assertions.checkArgument(inputBuffer == dequeuedInputBuffer); if (inputBuffer.isDecodeOnly()) { // We can drop this buffer early (i.e. before it would be decoded) as the CEA formats allow @@ -90,13 +89,12 @@ import java.util.TreeSet; if (availableOutputBuffers.isEmpty()) { return null; } - // iterate through all available input buffers whose timestamps are less than or equal // to the current playback position; processing input buffers for future content should // be deferred until they would be applicable while (!queuedInputBuffers.isEmpty() - && queuedInputBuffers.first().timeUs <= playbackPositionUs) { - SubtitleInputBuffer inputBuffer = queuedInputBuffers.pollFirst(); + && queuedInputBuffers.peek().timeUs <= playbackPositionUs) { + SubtitleInputBuffer inputBuffer = queuedInputBuffers.poll(); // If the input buffer indicates we've reached the end of the stream, we can // return immediately with an output buffer propagating that @@ -142,7 +140,7 @@ import java.util.TreeSet; public void flush() { playbackPositionUs = 0; while (!queuedInputBuffers.isEmpty()) { - releaseInputBuffer(queuedInputBuffers.pollFirst()); + releaseInputBuffer(queuedInputBuffers.poll()); } if (dequeuedInputBuffer != null) { releaseInputBuffer(dequeuedInputBuffer); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/dvb/DvbParser.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/dvb/DvbParser.java index 5cd0cfeb2..1b8a6e2a9 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/dvb/DvbParser.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/dvb/DvbParser.java @@ -667,13 +667,15 @@ import java.util.List; int runLength = 0; int clutIndex = 0; int peek = data.readBits(2); - if (!data.readBit()) { + if (peek != 0x00) { runLength = 1; clutIndex = peek; } else if (data.readBit()) { runLength = 3 + data.readBits(3); clutIndex = data.readBits(2); - } else if (!data.readBit()) { + } else if (data.readBit()) { + runLength = 1; + } else { switch (data.readBits(2)) { case 0x00: endOfPixelCodeString = true; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ssa/SsaDecoder.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ssa/SsaDecoder.java new file mode 100755 index 000000000..99418e093 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ssa/SsaDecoder.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.text.ssa; + +import android.text.TextUtils; +import android.util.Log; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.text.Cue; +import org.telegram.messenger.exoplayer2.text.SimpleSubtitleDecoder; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.LongArray; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.util.Util; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A {@link SimpleSubtitleDecoder} for SSA/ASS. + */ +public final class SsaDecoder extends SimpleSubtitleDecoder { + + private static final String TAG = "SsaDecoder"; + + private static final Pattern SSA_TIMECODE_PATTERN = Pattern.compile( + "(?:(\\d+):)?(\\d+):(\\d+)(?::|\\.)(\\d+)"); + private static final String FORMAT_LINE_PREFIX = "Format: "; + private static final String DIALOGUE_LINE_PREFIX = "Dialogue: "; + + private final boolean haveInitializationData; + + private int formatKeyCount; + private int formatStartIndex; + private int formatEndIndex; + private int formatTextIndex; + + public SsaDecoder() { + this(null); + } + + /** + * @param initializationData Optional initialization data for the decoder. If not null, the + * initialization data must consist of two byte arrays. The first must contain an SSA format + * line. The second must contain an SSA header that will be assumed common to all samples. + */ + public SsaDecoder(List initializationData) { + super("SsaDecoder"); + if (initializationData != null) { + haveInitializationData = true; + String formatLine = new String(initializationData.get(0)); + Assertions.checkArgument(formatLine.startsWith(FORMAT_LINE_PREFIX)); + parseFormatLine(formatLine); + parseHeader(new ParsableByteArray(initializationData.get(1))); + } else { + haveInitializationData = false; + } + } + + @Override + protected SsaSubtitle decode(byte[] bytes, int length, boolean reset) { + ArrayList cues = new ArrayList<>(); + LongArray cueTimesUs = new LongArray(); + + ParsableByteArray data = new ParsableByteArray(bytes, length); + if (!haveInitializationData) { + parseHeader(data); + } + parseEventBody(data, cues, cueTimesUs); + + Cue[] cuesArray = new Cue[cues.size()]; + cues.toArray(cuesArray); + long[] cueTimesUsArray = cueTimesUs.toArray(); + return new SsaSubtitle(cuesArray, cueTimesUsArray); + } + + /** + * Parses the header of the subtitle. + * + * @param data A {@link ParsableByteArray} from which the header should be read. + */ + private void parseHeader(ParsableByteArray data) { + String currentLine; + while ((currentLine = data.readLine()) != null) { + // TODO: Parse useful data from the header. + if (currentLine.startsWith("[Events]")) { + // We've reached the event body. + return; + } + } + } + + /** + * Parses the event body of the subtitle. + * + * @param data A {@link ParsableByteArray} from which the body should be read. + * @param cues A list to which parsed cues will be added. + * @param cueTimesUs An array to which parsed cue timestamps will be added. + */ + private void parseEventBody(ParsableByteArray data, List cues, LongArray cueTimesUs) { + String currentLine; + while ((currentLine = data.readLine()) != null) { + if (!haveInitializationData && currentLine.startsWith(FORMAT_LINE_PREFIX)) { + parseFormatLine(currentLine); + } else if (currentLine.startsWith(DIALOGUE_LINE_PREFIX)) { + parseDialogueLine(currentLine, cues, cueTimesUs); + } + } + } + + /** + * Parses a format line. + * + * @param formatLine The line to parse. + */ + private void parseFormatLine(String formatLine) { + String[] values = TextUtils.split(formatLine.substring(FORMAT_LINE_PREFIX.length()), ","); + formatKeyCount = values.length; + formatStartIndex = C.INDEX_UNSET; + formatEndIndex = C.INDEX_UNSET; + formatTextIndex = C.INDEX_UNSET; + for (int i = 0; i < formatKeyCount; i++) { + String key = Util.toLowerInvariant(values[i].trim()); + switch (key) { + case "start": + formatStartIndex = i; + break; + case "end": + formatEndIndex = i; + break; + case "text": + formatTextIndex = i; + break; + default: + // Do nothing. + break; + } + } + } + + /** + * Parses a dialogue line. + * + * @param dialogueLine The line to parse. + * @param cues A list to which parsed cues will be added. + * @param cueTimesUs An array to which parsed cue timestamps will be added. + */ + private void parseDialogueLine(String dialogueLine, List cues, LongArray cueTimesUs) { + if (formatKeyCount == 0) { + Log.w(TAG, "Skipping dialogue line before format: " + dialogueLine); + return; + } + + String[] lineValues = dialogueLine.substring(DIALOGUE_LINE_PREFIX.length()) + .split(",", formatKeyCount); + long startTimeUs = SsaDecoder.parseTimecodeUs(lineValues[formatStartIndex]); + if (startTimeUs == C.TIME_UNSET) { + Log.w(TAG, "Skipping invalid timing: " + dialogueLine); + return; + } + + long endTimeUs = C.TIME_UNSET; + String endTimeString = lineValues[formatEndIndex]; + if (!endTimeString.trim().isEmpty()) { + endTimeUs = SsaDecoder.parseTimecodeUs(endTimeString); + if (endTimeUs == C.TIME_UNSET) { + Log.w(TAG, "Skipping invalid timing: " + dialogueLine); + return; + } + } + + String text = lineValues[formatTextIndex] + .replaceAll("\\{.*?\\}", "") + .replaceAll("\\\\N", "\n") + .replaceAll("\\\\n", "\n"); + cues.add(new Cue(text)); + cueTimesUs.add(startTimeUs); + if (endTimeUs != C.TIME_UNSET) { + cues.add(null); + cueTimesUs.add(endTimeUs); + } + } + + /** + * Parses an SSA timecode string. + * + * @param timeString The string to parse. + * @return The parsed timestamp in microseconds. + */ + public static long parseTimecodeUs(String timeString) { + Matcher matcher = SSA_TIMECODE_PATTERN.matcher(timeString); + if (!matcher.matches()) { + return C.TIME_UNSET; + } + long timestampUs = Long.parseLong(matcher.group(1)) * 60 * 60 * C.MICROS_PER_SECOND; + timestampUs += Long.parseLong(matcher.group(2)) * 60 * C.MICROS_PER_SECOND; + timestampUs += Long.parseLong(matcher.group(3)) * C.MICROS_PER_SECOND; + timestampUs += Long.parseLong(matcher.group(4)) * 10000; // 100ths of a second. + return timestampUs; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ssa/SsaSubtitle.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ssa/SsaSubtitle.java new file mode 100755 index 000000000..db7f7c604 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ssa/SsaSubtitle.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.text.ssa; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.text.Cue; +import org.telegram.messenger.exoplayer2.text.Subtitle; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.Util; +import java.util.Collections; +import java.util.List; + +/** + * A representation of an SSA/ASS subtitle. + */ +/* package */ final class SsaSubtitle implements Subtitle { + + private final Cue[] cues; + private final long[] cueTimesUs; + + /** + * @param cues The cues in the subtitle. Null entries may be used to represent empty cues. + * @param cueTimesUs The cue times, in microseconds. + */ + public SsaSubtitle(Cue[] cues, long[] cueTimesUs) { + this.cues = cues; + this.cueTimesUs = cueTimesUs; + } + + @Override + public int getNextEventTimeIndex(long timeUs) { + int index = Util.binarySearchCeil(cueTimesUs, timeUs, false, false); + return index < cueTimesUs.length ? index : C.INDEX_UNSET; + } + + @Override + public int getEventTimeCount() { + return cueTimesUs.length; + } + + @Override + public long getEventTime(int index) { + Assertions.checkArgument(index >= 0); + Assertions.checkArgument(index < cueTimesUs.length); + return cueTimesUs[index]; + } + + @Override + public List getCues(long timeUs) { + int index = Util.binarySearchFloor(cueTimesUs, timeUs, true, false); + if (index == -1 || cues[index] == null) { + // timeUs is earlier than the start of the first cue, or we have an empty cue. + return Collections.emptyList(); + } else { + return Collections.singletonList(cues[index]); + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/subrip/SubripDecoder.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/subrip/SubripDecoder.java index 1bcc892c8..ce99145e6 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/subrip/SubripDecoder.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/subrip/SubripDecoder.java @@ -69,6 +69,11 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { // Read and parse the timing line. boolean haveEndTimecode = false; currentLine = subripData.readLine(); + if (currentLine == null) { + Log.w(TAG, "Unexpected end"); + break; + } + Matcher matcher = SUBRIP_TIMING_LINE.matcher(currentLine); if (matcher.matches()) { cueTimesUs.add(parseTimecode(matcher, 1)); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlDecoder.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlDecoder.java index 7b71093aa..3ff285bf5 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlDecoder.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlDecoder.java @@ -17,7 +17,6 @@ package org.telegram.messenger.exoplayer2.text.ttml; import android.text.Layout; import android.util.Log; -import android.util.Pair; import org.telegram.messenger.exoplayer2.C; import org.telegram.messenger.exoplayer2.text.Cue; import org.telegram.messenger.exoplayer2.text.SimpleSubtitleDecoder; @@ -100,7 +99,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { XmlPullParser xmlParser = xmlParserFactory.newPullParser(); Map globalStyles = new HashMap<>(); Map regionMap = new HashMap<>(); - regionMap.put(TtmlNode.ANONYMOUS_REGION_ID, new TtmlRegion()); + regionMap.put(TtmlNode.ANONYMOUS_REGION_ID, new TtmlRegion(null)); ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes, 0, length); xmlParser.setInput(inputStream, null); TtmlSubtitle ttmlSubtitle = null; @@ -211,9 +210,9 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { globalStyles.put(style.getId(), style); } } else if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_REGION)) { - Pair ttmlRegionInfo = parseRegionAttributes(xmlParser); - if (ttmlRegionInfo != null) { - globalRegions.put(ttmlRegionInfo.first, ttmlRegionInfo.second); + TtmlRegion ttmlRegion = parseRegionAttributes(xmlParser); + if (ttmlRegion != null) { + globalRegions.put(ttmlRegion.id, ttmlRegion); } } } while (!XmlPullParserUtil.isEndTag(xmlParser, TtmlNode.TAG_HEAD)); @@ -221,41 +220,92 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { } /** - * Parses a region declaration. Supports origin and extent definition but only when defined in - * terms of percentage of the viewport. Regions that do not correctly declare origin are ignored. + * Parses a region declaration. + *

    + * If the region defines an origin and extent, it is required that they're defined as percentages + * of the viewport. Region declarations that define origin and extent in other formats are + * unsupported, and null is returned. */ - private Pair parseRegionAttributes(XmlPullParser xmlParser) { + private TtmlRegion parseRegionAttributes(XmlPullParser xmlParser) { String regionId = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_ID); - String regionOrigin = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_ORIGIN); - String regionExtent = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_EXTENT); - if (regionOrigin == null || regionId == null) { + if (regionId == null) { return null; } - float position = Cue.DIMEN_UNSET; - float line = Cue.DIMEN_UNSET; - Matcher originMatcher = PERCENTAGE_COORDINATES.matcher(regionOrigin); - if (originMatcher.matches()) { - try { - position = Float.parseFloat(originMatcher.group(1)) / 100.f; - line = Float.parseFloat(originMatcher.group(2)) / 100.f; - } catch (NumberFormatException e) { - Log.w(TAG, "Ignoring region with malformed origin: '" + regionOrigin + "'", e); - position = Cue.DIMEN_UNSET; + + float position; + float line; + String regionOrigin = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_ORIGIN); + if (regionOrigin != null) { + Matcher originMatcher = PERCENTAGE_COORDINATES.matcher(regionOrigin); + if (originMatcher.matches()) { + try { + position = Float.parseFloat(originMatcher.group(1)) / 100f; + line = Float.parseFloat(originMatcher.group(2)) / 100f; + } catch (NumberFormatException e) { + Log.w(TAG, "Ignoring region with malformed origin: " + regionOrigin); + return null; + } + } else { + Log.w(TAG, "Ignoring region with unsupported origin: " + regionOrigin); + return null; } + } else { + Log.w(TAG, "Ignoring region without an origin"); + return null; + // TODO: Should default to top left as below in this case, but need to fix + // https://github.com/google/ExoPlayer/issues/2953 first. + // Origin is omitted. Default to top left. + // position = 0; + // line = 0; } - float width = Cue.DIMEN_UNSET; + + float width; + float height; + String regionExtent = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_EXTENT); if (regionExtent != null) { Matcher extentMatcher = PERCENTAGE_COORDINATES.matcher(regionExtent); if (extentMatcher.matches()) { try { - width = Float.parseFloat(extentMatcher.group(1)) / 100.f; + width = Float.parseFloat(extentMatcher.group(1)) / 100f; + height = Float.parseFloat(extentMatcher.group(2)) / 100f; } catch (NumberFormatException e) { - Log.w(TAG, "Ignoring malformed region extent: '" + regionExtent + "'", e); + Log.w(TAG, "Ignoring region with malformed extent: " + regionOrigin); + return null; } + } else { + Log.w(TAG, "Ignoring region with unsupported extent: " + regionOrigin); + return null; + } + } else { + Log.w(TAG, "Ignoring region without an extent"); + return null; + // TODO: Should default to extent of parent as below in this case, but need to fix + // https://github.com/google/ExoPlayer/issues/2953 first. + // Extent is omitted. Default to extent of parent. + // width = 1; + // height = 1; + } + + @Cue.AnchorType int lineAnchor = Cue.ANCHOR_TYPE_START; + String displayAlign = XmlPullParserUtil.getAttributeValue(xmlParser, + TtmlNode.ATTR_TTS_DISPLAY_ALIGN); + if (displayAlign != null) { + switch (Util.toLowerInvariant(displayAlign)) { + case "center": + lineAnchor = Cue.ANCHOR_TYPE_MIDDLE; + line += height / 2; + break; + case "after": + lineAnchor = Cue.ANCHOR_TYPE_END; + line += height; + break; + default: + // Default "before" case. Do nothing. + break; } } - return position != Cue.DIMEN_UNSET ? new Pair<>(regionId, new TtmlRegion(position, line, - Cue.LINE_TYPE_FRACTION, width)) : null; + + return new TtmlRegion(regionId, position, line, Cue.LINE_TYPE_FRACTION, lineAnchor, width); } private String[] parseStyleIds(String parentStyleIds) { @@ -277,7 +327,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { try { style.setBackgroundColor(ColorParser.parseTtmlColor(attributeValue)); } catch (IllegalArgumentException e) { - Log.w(TAG, "failed parsing background value: '" + attributeValue + "'"); + Log.w(TAG, "Failed parsing background value: " + attributeValue); } break; case TtmlNode.ATTR_TTS_COLOR: @@ -285,7 +335,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { try { style.setFontColor(ColorParser.parseTtmlColor(attributeValue)); } catch (IllegalArgumentException e) { - Log.w(TAG, "failed parsing color value: '" + attributeValue + "'"); + Log.w(TAG, "Failed parsing color value: " + attributeValue); } break; case TtmlNode.ATTR_TTS_FONT_FAMILY: @@ -296,7 +346,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { style = createIfNull(style); parseFontSize(attributeValue, style); } catch (SubtitleDecoderException e) { - Log.w(TAG, "failed parsing fontSize value: '" + attributeValue + "'"); + Log.w(TAG, "Failed parsing fontSize value: " + attributeValue); } break; case TtmlNode.ATTR_TTS_FONT_WEIGHT: diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlNode.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlNode.java index fd33b0ff5..2988e017d 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlNode.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlNode.java @@ -50,14 +50,15 @@ import java.util.TreeSet; public static final String ANONYMOUS_REGION_ID = ""; public static final String ATTR_ID = "id"; - public static final String ATTR_TTS_BACKGROUND_COLOR = "backgroundColor"; + public static final String ATTR_TTS_ORIGIN = "origin"; public static final String ATTR_TTS_EXTENT = "extent"; + public static final String ATTR_TTS_DISPLAY_ALIGN = "displayAlign"; + public static final String ATTR_TTS_BACKGROUND_COLOR = "backgroundColor"; public static final String ATTR_TTS_FONT_STYLE = "fontStyle"; public static final String ATTR_TTS_FONT_SIZE = "fontSize"; public static final String ATTR_TTS_FONT_FAMILY = "fontFamily"; public static final String ATTR_TTS_FONT_WEIGHT = "fontWeight"; public static final String ATTR_TTS_COLOR = "color"; - public static final String ATTR_TTS_ORIGIN = "origin"; public static final String ATTR_TTS_TEXT_DECORATION = "textDecoration"; public static final String ATTR_TTS_TEXT_ALIGN = "textAlign"; @@ -179,7 +180,7 @@ import java.util.TreeSet; for (Entry entry : regionOutputs.entrySet()) { TtmlRegion region = regionMap.get(entry.getKey()); cues.add(new Cue(cleanUpText(entry.getValue()), null, region.line, region.lineType, - Cue.TYPE_UNSET, region.position, Cue.TYPE_UNSET, region.width)); + region.lineAnchor, region.position, Cue.TYPE_UNSET, region.width)); } return cues; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlRegion.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlRegion.java index cc7f6067f..3139b223b 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlRegion.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlRegion.java @@ -22,20 +22,24 @@ import org.telegram.messenger.exoplayer2.text.Cue; */ /* package */ final class TtmlRegion { + public final String id; public final float position; public final float line; - @Cue.LineType - public final int lineType; + @Cue.LineType public final int lineType; + @Cue.AnchorType public final int lineAnchor; public final float width; - public TtmlRegion() { - this(Cue.DIMEN_UNSET, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET); + public TtmlRegion(String id) { + this(id, Cue.DIMEN_UNSET, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET); } - public TtmlRegion(float position, float line, @Cue.LineType int lineType, float width) { + public TtmlRegion(String id, float position, float line, @Cue.LineType int lineType, + @Cue.AnchorType int lineAnchor, float width) { + this.id = id; this.position = position; this.line = line; this.lineType = lineType; + this.lineAnchor = lineAnchor; this.width = width; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/WebvttCueParser.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/WebvttCueParser.java index 7c9903237..7e32627d6 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/WebvttCueParser.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/WebvttCueParser.java @@ -21,6 +21,7 @@ import android.text.Layout.Alignment; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.Spanned; +import android.text.TextUtils; import android.text.style.AbsoluteSizeSpan; import android.text.style.AlignmentSpan; import android.text.style.BackgroundColorSpan; @@ -92,19 +93,24 @@ import java.util.regex.Pattern; /* package */ boolean parseCue(ParsableByteArray webvttData, WebvttCue.Builder builder, List styles) { String firstLine = webvttData.readLine(); + if (firstLine == null) { + return false; + } Matcher cueHeaderMatcher = WebvttCueParser.CUE_HEADER_PATTERN.matcher(firstLine); if (cueHeaderMatcher.matches()) { // We have found the timestamps in the first line. No id present. return parseCue(null, cueHeaderMatcher, webvttData, builder, textBuilder, styles); - } else { - // The first line is not the timestamps, but could be the cue id. - String secondLine = webvttData.readLine(); - cueHeaderMatcher = WebvttCueParser.CUE_HEADER_PATTERN.matcher(secondLine); - if (cueHeaderMatcher.matches()) { - // We can do the rest of the parsing, including the id. - return parseCue(firstLine.trim(), cueHeaderMatcher, webvttData, builder, textBuilder, - styles); - } + } + // The first line is not the timestamps, but could be the cue id. + String secondLine = webvttData.readLine(); + if (secondLine == null) { + return false; + } + cueHeaderMatcher = WebvttCueParser.CUE_HEADER_PATTERN.matcher(secondLine); + if (cueHeaderMatcher.matches()) { + // We can do the rest of the parsing, including the id. + return parseCue(firstLine.trim(), cueHeaderMatcher, webvttData, builder, textBuilder, + styles); } return false; } @@ -233,7 +239,7 @@ import java.util.regex.Pattern; // Parse the cue text. textBuilder.setLength(0); String line; - while ((line = webvttData.readLine()) != null && !line.isEmpty()) { + while (!TextUtils.isEmpty(line = webvttData.readLine())) { if (textBuilder.length() > 0) { textBuilder.append("\n"); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/AdaptiveTrackSelection.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/AdaptiveTrackSelection.java index fd2a80965..db8e5bc63 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/AdaptiveTrackSelection.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/AdaptiveTrackSelection.java @@ -154,23 +154,24 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { @Override public void updateSelectedTrack(long bufferedDurationUs) { long nowMs = SystemClock.elapsedRealtime(); - // Get the current and ideal selections. + // Stash the current selection, then make a new one. int currentSelectedIndex = selectedIndex; - Format currentFormat = getSelectedFormat(); - int idealSelectedIndex = determineIdealSelectedIndex(nowMs); - Format idealFormat = getFormat(idealSelectedIndex); - // Assume we can switch to the ideal selection. - selectedIndex = idealSelectedIndex; - // Revert back to the current selection if conditions are not suitable for switching. - if (currentFormat != null && !isBlacklisted(selectedIndex, nowMs)) { - if (idealFormat.bitrate > currentFormat.bitrate + selectedIndex = determineIdealSelectedIndex(nowMs); + if (selectedIndex == currentSelectedIndex) { + return; + } + if (!isBlacklisted(currentSelectedIndex, nowMs)) { + // Revert back to the current selection if conditions are not suitable for switching. + Format currentFormat = getFormat(currentSelectedIndex); + Format selectedFormat = getFormat(selectedIndex); + if (selectedFormat.bitrate > currentFormat.bitrate && bufferedDurationUs < minDurationForQualityIncreaseUs) { - // The ideal track is a higher quality, but we have insufficient buffer to safely switch + // The selected track is a higher quality, but we have insufficient buffer to safely switch // up. Defer switching up for now. selectedIndex = currentSelectedIndex; - } else if (idealFormat.bitrate < currentFormat.bitrate + } else if (selectedFormat.bitrate < currentFormat.bitrate && bufferedDurationUs >= maxDurationForQualityDecreaseUs) { - // The ideal track is a lower quality, but we have sufficient buffer to defer switching + // The selected track is a lower quality, but we have sufficient buffer to defer switching // down for now. selectedIndex = currentSelectedIndex; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/DefaultTrackSelector.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/DefaultTrackSelector.java index 77e771f96..753b56714 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/DefaultTrackSelector.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/DefaultTrackSelector.java @@ -24,6 +24,7 @@ import org.telegram.messenger.exoplayer2.Format; import org.telegram.messenger.exoplayer2.RendererCapabilities; import org.telegram.messenger.exoplayer2.source.TrackGroup; import org.telegram.messenger.exoplayer2.source.TrackGroupArray; +import org.telegram.messenger.exoplayer2.upstream.BandwidthMeter; import org.telegram.messenger.exoplayer2.util.Assertions; import org.telegram.messenger.exoplayer2.util.Util; import java.util.ArrayList; @@ -32,37 +33,115 @@ import java.util.List; import java.util.concurrent.atomic.AtomicReference; /** - * A {@link MappingTrackSelector} that allows configuration of common parameters. It is safe to call - * the methods of this class from the application thread. See {@link Parameters#Parameters()} for - * default selection parameters. + * A default {@link TrackSelector} suitable for most use cases. + * + *

    Constraint based track selection

    + * Whilst this selector supports setting specific track overrides, the recommended way of + * changing which tracks are selected is by setting {@link Parameters} that constrain the track + * selection process. For example an instance can specify a preferred language for + * the audio track, and impose constraints on the maximum video resolution that should be selected + * for adaptive playbacks. Modifying the parameters is simple: + *
    + * {@code
    + * Parameters currentParameters = trackSelector.getParameters();
    + * // Generate new parameters to prefer German audio and impose a maximum video size constraint.
    + * Parameters newParameters = currentParameters
    + *     .withPreferredAudioLanguage("de")
    + *     .withMaxVideoSize(1024, 768);
    + * // Set the new parameters on the selector.
    + * trackSelector.setParameters(newParameters);}
    + * 
    + * There are several benefits to using constraint based track selection instead of specific track + * overrides: + *
      + *
    • You can specify constraints before knowing what tracks the media provides. This can + * simplify track selection code (e.g. you don't have to listen for changes in the available + * tracks before configuring the selector).
    • + *
    • Constraints can be applied consistently across all periods in a complex piece of media, + * even if those periods contain different tracks. In contrast, a specific track override is only + * applied to periods whose tracks match those for which the override was set.
    • + *
    + * + *

    Track overrides, disabling renderers and tunneling

    + * This selector extends {@link MappingTrackSelector}, and so inherits its support for setting + * specific track overrides, disabling renderers and configuring tunneled media playback. See + * {@link MappingTrackSelector} for details. + * + *

    Extending this class

    + * This class is designed to be extensible by developers who wish to customize its behavior but do + * not wish to implement their own {@link MappingTrackSelector} or {@link TrackSelector} from + * scratch. */ public class DefaultTrackSelector extends MappingTrackSelector { /** - * Holder for available configurations for the {@link DefaultTrackSelector}. + * Constraint parameters for {@link DefaultTrackSelector}. */ public static final class Parameters { - // Audio. + // Audio + /** + * The preferred language for audio, as well as for forced text tracks as defined by RFC 5646. + * {@code null} selects the default track, or the first track if there's no default. + */ public final String preferredAudioLanguage; - // Text. + // Text + /** + * The preferred language for text tracks as defined by RFC 5646. {@code null} selects the + * default track if there is one, or no track otherwise. + */ public final String preferredTextLanguage; - // Video. - public final boolean allowMixedMimeAdaptiveness; - public final boolean allowNonSeamlessAdaptiveness; + // Video + /** + * Maximum allowed video width. + */ public final int maxVideoWidth; + /** + * Maximum allowed video height. + */ public final int maxVideoHeight; + /** + * Maximum video bitrate. + */ public final int maxVideoBitrate; + /** + * Whether to exceed video constraints when no selection can be made otherwise. + */ public final boolean exceedVideoConstraintsIfNecessary; - public final boolean exceedRendererCapabilitiesIfNecessary; + /** + * Viewport width in pixels. Constrains video tracks selections for adaptive playbacks so that + * only tracks suitable for the viewport are selected. + */ public final int viewportWidth; + /** + * Viewport height in pixels. Constrains video tracks selections for adaptive playbacks so that + * only tracks suitable for the viewport are selected. + */ public final int viewportHeight; - public final boolean orientationMayChange; + /** + * Whether the viewport orientation may change during playback. Constrains video tracks + * selections for adaptive playbacks so that only tracks suitable for the viewport are selected. + */ + public final boolean viewportOrientationMayChange; + + // General + /** + * Whether to allow adaptive selections containing mixed mime types. + */ + public final boolean allowMixedMimeAdaptiveness; + /** + * Whether to allow adaptive selections where adaptation may not be completely seamless. + */ + public final boolean allowNonSeamlessAdaptiveness; + /** + * Whether to exceed renderer capabilities when no selection can be made otherwise. + */ + public final boolean exceedRendererCapabilitiesIfNecessary; /** - * Constructor with default selection parameters: + * Default parameters. The default values are: *
      *
    • No preferred audio language is set.
    • *
    • No preferred text language is set.
    • @@ -72,7 +151,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { *
    • No max video bitrate.
    • *
    • Video constraints are exceeded if no supported selection can be made otherwise.
    • *
    • Renderer capabilities are exceeded if no supported selection can be made.
    • - *
    • No viewport width/height constraints are set.
    • + *
    • No viewport constraints are set.
    • *
    */ public Parameters() { @@ -81,29 +160,24 @@ public class DefaultTrackSelector extends MappingTrackSelector { } /** - * @param preferredAudioLanguage The preferred language for audio, as well as for forced text - * tracks as defined by RFC 5646. {@code null} to select the default track, or first track - * if there's no default. - * @param preferredTextLanguage The preferred language for text tracks as defined by RFC 5646. - * {@code null} to select the default track, or first track if there's no default. - * @param allowMixedMimeAdaptiveness Whether to allow selections to contain mixed mime types. - * @param allowNonSeamlessAdaptiveness Whether non-seamless adaptation is allowed. - * @param maxVideoWidth Maximum allowed video width. - * @param maxVideoHeight Maximum allowed video height. - * @param maxVideoBitrate Maximum allowed video bitrate. - * @param exceedVideoConstraintsIfNecessary Whether to exceed video constraints when no - * selection can be made otherwise. - * @param exceedRendererCapabilitiesIfNecessary Whether to exceed renderer capabilities when no - * selection can be made otherwise. - * @param viewportWidth Viewport width in pixels. - * @param viewportHeight Viewport height in pixels. - * @param orientationMayChange Whether orientation may change during playback. + * @param preferredAudioLanguage See {@link #preferredAudioLanguage} + * @param preferredTextLanguage See {@link #preferredTextLanguage} + * @param allowMixedMimeAdaptiveness See {@link #allowMixedMimeAdaptiveness} + * @param allowNonSeamlessAdaptiveness See {@link #allowNonSeamlessAdaptiveness} + * @param maxVideoWidth See {@link #maxVideoWidth} + * @param maxVideoHeight See {@link #maxVideoHeight} + * @param maxVideoBitrate See {@link #maxVideoBitrate} + * @param exceedVideoConstraintsIfNecessary See {@link #exceedVideoConstraintsIfNecessary} + * @param exceedRendererCapabilitiesIfNecessary See {@link #preferredTextLanguage} + * @param viewportWidth See {@link #viewportWidth} + * @param viewportHeight See {@link #viewportHeight} + * @param viewportOrientationMayChange See {@link #viewportOrientationMayChange} */ public Parameters(String preferredAudioLanguage, String preferredTextLanguage, boolean allowMixedMimeAdaptiveness, boolean allowNonSeamlessAdaptiveness, int maxVideoWidth, int maxVideoHeight, int maxVideoBitrate, boolean exceedVideoConstraintsIfNecessary, boolean exceedRendererCapabilitiesIfNecessary, - int viewportWidth, int viewportHeight, boolean orientationMayChange) { + int viewportWidth, int viewportHeight, boolean viewportOrientationMayChange) { this.preferredAudioLanguage = preferredAudioLanguage; this.preferredTextLanguage = preferredTextLanguage; this.allowMixedMimeAdaptiveness = allowMixedMimeAdaptiveness; @@ -115,17 +189,15 @@ public class DefaultTrackSelector extends MappingTrackSelector { this.exceedRendererCapabilitiesIfNecessary = exceedRendererCapabilitiesIfNecessary; this.viewportWidth = viewportWidth; this.viewportHeight = viewportHeight; - this.orientationMayChange = orientationMayChange; + this.viewportOrientationMayChange = viewportOrientationMayChange; } /** - * Returns a {@link Parameters} instance with the provided preferred language for audio and - * forced text tracks. + * Returns an instance with the provided preferred language for audio and forced text tracks. * * @param preferredAudioLanguage The preferred language as defined by RFC 5646. {@code null} to * select the default track, or first track if there's no default. - * @return A {@link Parameters} instance with the provided preferred language for audio and - * forced text tracks. + * @return An instance with the provided preferred language for audio and forced text tracks. */ public Parameters withPreferredAudioLanguage(String preferredAudioLanguage) { preferredAudioLanguage = Util.normalizeLanguageCode(preferredAudioLanguage); @@ -135,15 +207,15 @@ public class DefaultTrackSelector extends MappingTrackSelector { return new Parameters(preferredAudioLanguage, preferredTextLanguage, allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, maxVideoHeight, maxVideoBitrate, exceedVideoConstraintsIfNecessary, exceedRendererCapabilitiesIfNecessary, - viewportWidth, viewportHeight, orientationMayChange); + viewportWidth, viewportHeight, viewportOrientationMayChange); } /** - * Returns a {@link Parameters} instance with the provided preferred language for text tracks. + * Returns an instance with the provided preferred language for text tracks. * * @param preferredTextLanguage The preferred language as defined by RFC 5646. {@code null} to * select the default track, or no track if there's no default. - * @return A {@link Parameters} instance with the provided preferred language for text tracks. + * @return An instance with the provided preferred language for text tracks. */ public Parameters withPreferredTextLanguage(String preferredTextLanguage) { preferredTextLanguage = Util.normalizeLanguageCode(preferredTextLanguage); @@ -153,14 +225,14 @@ public class DefaultTrackSelector extends MappingTrackSelector { return new Parameters(preferredAudioLanguage, preferredTextLanguage, allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, maxVideoHeight, maxVideoBitrate, exceedVideoConstraintsIfNecessary, exceedRendererCapabilitiesIfNecessary, - viewportWidth, viewportHeight, orientationMayChange); + viewportWidth, viewportHeight, viewportOrientationMayChange); } /** - * Returns a {@link Parameters} instance with the provided mixed mime adaptiveness allowance. + * Returns an instance with the provided mixed mime adaptiveness allowance. * * @param allowMixedMimeAdaptiveness Whether to allow selections to contain mixed mime types. - * @return A {@link Parameters} instance with the provided mixed mime adaptiveness allowance. + * @return An instance with the provided mixed mime adaptiveness allowance. */ public Parameters withAllowMixedMimeAdaptiveness(boolean allowMixedMimeAdaptiveness) { if (allowMixedMimeAdaptiveness == this.allowMixedMimeAdaptiveness) { @@ -169,14 +241,14 @@ public class DefaultTrackSelector extends MappingTrackSelector { return new Parameters(preferredAudioLanguage, preferredTextLanguage, allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, maxVideoHeight, maxVideoBitrate, exceedVideoConstraintsIfNecessary, exceedRendererCapabilitiesIfNecessary, - viewportWidth, viewportHeight, orientationMayChange); + viewportWidth, viewportHeight, viewportOrientationMayChange); } /** - * Returns a {@link Parameters} instance with the provided seamless adaptiveness allowance. + * Returns an instance with the provided seamless adaptiveness allowance. * * @param allowNonSeamlessAdaptiveness Whether non-seamless adaptation is allowed. - * @return A {@link Parameters} instance with the provided seamless adaptiveness allowance. + * @return An instance with the provided seamless adaptiveness allowance. */ public Parameters withAllowNonSeamlessAdaptiveness(boolean allowNonSeamlessAdaptiveness) { if (allowNonSeamlessAdaptiveness == this.allowNonSeamlessAdaptiveness) { @@ -185,15 +257,15 @@ public class DefaultTrackSelector extends MappingTrackSelector { return new Parameters(preferredAudioLanguage, preferredTextLanguage, allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, maxVideoHeight, maxVideoBitrate, exceedVideoConstraintsIfNecessary, exceedRendererCapabilitiesIfNecessary, - viewportWidth, viewportHeight, orientationMayChange); + viewportWidth, viewportHeight, viewportOrientationMayChange); } /** - * Returns a {@link Parameters} instance with the provided max video size. + * Returns an instance with the provided max video size. * * @param maxVideoWidth The max video width. * @param maxVideoHeight The max video width. - * @return A {@link Parameters} instance with the provided max video size. + * @return An instance with the provided max video size. */ public Parameters withMaxVideoSize(int maxVideoWidth, int maxVideoHeight) { if (maxVideoWidth == this.maxVideoWidth && maxVideoHeight == this.maxVideoHeight) { @@ -202,14 +274,14 @@ public class DefaultTrackSelector extends MappingTrackSelector { return new Parameters(preferredAudioLanguage, preferredTextLanguage, allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, maxVideoHeight, maxVideoBitrate, exceedVideoConstraintsIfNecessary, exceedRendererCapabilitiesIfNecessary, - viewportWidth, viewportHeight, orientationMayChange); + viewportWidth, viewportHeight, viewportOrientationMayChange); } /** - * Returns a {@link Parameters} instance with the provided max video bitrate. + * Returns an instance with the provided max video bitrate. * * @param maxVideoBitrate The max video bitrate. - * @return A {@link Parameters} instance with the provided max video bitrate. + * @return An instance with the provided max video bitrate. */ public Parameters withMaxVideoBitrate(int maxVideoBitrate) { if (maxVideoBitrate == this.maxVideoBitrate) { @@ -218,13 +290,13 @@ public class DefaultTrackSelector extends MappingTrackSelector { return new Parameters(preferredAudioLanguage, preferredTextLanguage, allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, maxVideoHeight, maxVideoBitrate, exceedVideoConstraintsIfNecessary, exceedRendererCapabilitiesIfNecessary, - viewportWidth, viewportHeight, orientationMayChange); + viewportWidth, viewportHeight, viewportOrientationMayChange); } /** * Equivalent to {@code withMaxVideoSize(1279, 719)}. * - * @return A {@link Parameters} instance with maximum standard definition as maximum video size. + * @return An instance with maximum standard definition as maximum video size. */ public Parameters withMaxVideoSizeSd() { return withMaxVideoSize(1279, 719); @@ -233,20 +305,18 @@ public class DefaultTrackSelector extends MappingTrackSelector { /** * Equivalent to {@code withMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE)}. * - * @return A {@link Parameters} instance without video size constraints. + * @return An instance without video size constraints. */ public Parameters withoutVideoSizeConstraints() { return withMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE); } /** - * Returns a {@link Parameters} instance with the provided - * {@code exceedVideoConstraintsIfNecessary} value. + * Returns an instance with the provided {@code exceedVideoConstraintsIfNecessary} value. * * @param exceedVideoConstraintsIfNecessary Whether to exceed video constraints when no * selection can be made otherwise. - * @return A {@link Parameters} instance with the provided - * {@code exceedVideoConstraintsIfNecessary} value. + * @return An instance with the provided {@code exceedVideoConstraintsIfNecessary} value. */ public Parameters withExceedVideoConstraintsIfNecessary( boolean exceedVideoConstraintsIfNecessary) { @@ -256,17 +326,15 @@ public class DefaultTrackSelector extends MappingTrackSelector { return new Parameters(preferredAudioLanguage, preferredTextLanguage, allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, maxVideoHeight, maxVideoBitrate, exceedVideoConstraintsIfNecessary, exceedRendererCapabilitiesIfNecessary, - viewportWidth, viewportHeight, orientationMayChange); + viewportWidth, viewportHeight, viewportOrientationMayChange); } /** - * Returns a {@link Parameters} instance with the provided - * {@code exceedRendererCapabilitiesIfNecessary} value. + * Returns an instance with the provided {@code exceedRendererCapabilitiesIfNecessary} value. * * @param exceedRendererCapabilitiesIfNecessary Whether to exceed renderer capabilities when no * selection can be made otherwise. - * @return A {@link Parameters} instance with the provided - * {@code exceedRendererCapabilitiesIfNecessary} value. + * @return An instance with the provided {@code exceedRendererCapabilitiesIfNecessary} value. */ public Parameters withExceedRendererCapabilitiesIfNecessary( boolean exceedRendererCapabilitiesIfNecessary) { @@ -276,48 +344,47 @@ public class DefaultTrackSelector extends MappingTrackSelector { return new Parameters(preferredAudioLanguage, preferredTextLanguage, allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, maxVideoHeight, maxVideoBitrate, exceedVideoConstraintsIfNecessary, exceedRendererCapabilitiesIfNecessary, - viewportWidth, viewportHeight, orientationMayChange); + viewportWidth, viewportHeight, viewportOrientationMayChange); } /** - * Returns a {@link Parameters} instance with the provided viewport size. + * Returns an instance with the provided viewport size. * * @param viewportWidth Viewport width in pixels. * @param viewportHeight Viewport height in pixels. - * @param orientationMayChange Whether orientation may change during playback. - * @return A {@link Parameters} instance with the provided viewport size. + * @param viewportOrientationMayChange Whether orientation may change during playback. + * @return An instance with the provided viewport size. */ public Parameters withViewportSize(int viewportWidth, int viewportHeight, - boolean orientationMayChange) { + boolean viewportOrientationMayChange) { if (viewportWidth == this.viewportWidth && viewportHeight == this.viewportHeight - && orientationMayChange == this.orientationMayChange) { + && viewportOrientationMayChange == this.viewportOrientationMayChange) { return this; } return new Parameters(preferredAudioLanguage, preferredTextLanguage, allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, maxVideoHeight, maxVideoBitrate, exceedVideoConstraintsIfNecessary, exceedRendererCapabilitiesIfNecessary, - viewportWidth, viewportHeight, orientationMayChange); + viewportWidth, viewportHeight, viewportOrientationMayChange); } /** - * Returns a {@link Parameters} instance where the viewport size is obtained from the provided - * {@link Context}. + * Returns an instance where the viewport size is obtained from the provided {@link Context}. * * @param context The context to obtain the viewport size from. - * @param orientationMayChange Whether orientation may change during playback. - * @return A {@link Parameters} instance where the viewport size is obtained from the provided - * {@link Context}. + * @param viewportOrientationMayChange Whether orientation may change during playback. + * @return An instance where the viewport size is obtained from the provided {@link Context}. */ - public Parameters withViewportSizeFromContext(Context context, boolean orientationMayChange) { + public Parameters withViewportSizeFromContext(Context context, + boolean viewportOrientationMayChange) { // Assume the viewport is fullscreen. Point viewportSize = Util.getPhysicalDisplaySize(context); - return withViewportSize(viewportSize.x, viewportSize.y, orientationMayChange); + return withViewportSize(viewportSize.x, viewportSize.y, viewportOrientationMayChange); } /** * Equivalent to {@code withViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true)}. * - * @return A {@link Parameters} instance without viewport size constraints. + * @return An instance without viewport size constraints. */ public Parameters withoutViewportSizeConstraints() { return withViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true); @@ -337,7 +404,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { && maxVideoWidth == other.maxVideoWidth && maxVideoHeight == other.maxVideoHeight && exceedVideoConstraintsIfNecessary == other.exceedVideoConstraintsIfNecessary && exceedRendererCapabilitiesIfNecessary == other.exceedRendererCapabilitiesIfNecessary - && orientationMayChange == other.orientationMayChange + && viewportOrientationMayChange == other.viewportOrientationMayChange && viewportWidth == other.viewportWidth && viewportHeight == other.viewportHeight && maxVideoBitrate == other.maxVideoBitrate && TextUtils.equals(preferredAudioLanguage, other.preferredAudioLanguage) @@ -355,7 +422,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { result = 31 * result + maxVideoBitrate; result = 31 * result + (exceedVideoConstraintsIfNecessary ? 1 : 0); result = 31 * result + (exceedRendererCapabilitiesIfNecessary ? 1 : 0); - result = 31 * result + (orientationMayChange ? 1 : 0); + result = 31 * result + (viewportOrientationMayChange ? 1 : 0); result = 31 * result + viewportWidth; result = 31 * result + viewportHeight; return result; @@ -376,10 +443,21 @@ public class DefaultTrackSelector extends MappingTrackSelector { private final AtomicReference paramsReference; /** - * Constructs an instance that does not support adaptive tracks. + * Constructs an instance that does not support adaptive track selection. */ public DefaultTrackSelector() { - this(null); + this((TrackSelection.Factory) null); + } + + /** + * Constructs an instance that supports adaptive track selection. Adaptive track selections use + * the provided {@link BandwidthMeter} to determine which individual track should be used during + * playback. + * + * @param bandwidthMeter The {@link BandwidthMeter}. + */ + public DefaultTrackSelector(BandwidthMeter bandwidthMeter) { + this(new AdaptiveTrackSelection.Factory(bandwidthMeter)); } /** @@ -424,40 +502,46 @@ public class DefaultTrackSelector extends MappingTrackSelector { int rendererCount = rendererCapabilities.length; TrackSelection[] rendererTrackSelections = new TrackSelection[rendererCount]; Parameters params = paramsReference.get(); - boolean videoTrackAndRendererPresent = false; + boolean seenVideoRendererWithMappedTracks = false; + boolean selectedVideoTracks = false; for (int i = 0; i < rendererCount; i++) { if (C.TRACK_TYPE_VIDEO == rendererCapabilities[i].getTrackType()) { - rendererTrackSelections[i] = selectVideoTrack(rendererCapabilities[i], - rendererTrackGroupArrays[i], rendererFormatSupports[i], params.maxVideoWidth, - params.maxVideoHeight, params.maxVideoBitrate, params.allowNonSeamlessAdaptiveness, - params.allowMixedMimeAdaptiveness, params.viewportWidth, params.viewportHeight, - params.orientationMayChange, adaptiveTrackSelectionFactory, - params.exceedVideoConstraintsIfNecessary, params.exceedRendererCapabilitiesIfNecessary); - videoTrackAndRendererPresent |= rendererTrackGroupArrays[i].length > 0; + if (!selectedVideoTracks) { + rendererTrackSelections[i] = selectVideoTrack(rendererCapabilities[i], + rendererTrackGroupArrays[i], rendererFormatSupports[i], params, + adaptiveTrackSelectionFactory); + selectedVideoTracks = rendererTrackSelections[i] != null; + } + seenVideoRendererWithMappedTracks |= rendererTrackGroupArrays[i].length > 0; } } + boolean selectedAudioTracks = false; + boolean selectedTextTracks = false; for (int i = 0; i < rendererCount; i++) { switch (rendererCapabilities[i].getTrackType()) { case C.TRACK_TYPE_VIDEO: // Already done. Do nothing. break; case C.TRACK_TYPE_AUDIO: - rendererTrackSelections[i] = selectAudioTrack(rendererTrackGroupArrays[i], - rendererFormatSupports[i], params.preferredAudioLanguage, - params.exceedRendererCapabilitiesIfNecessary, params.allowMixedMimeAdaptiveness, - videoTrackAndRendererPresent ? null : adaptiveTrackSelectionFactory); + if (!selectedAudioTracks) { + rendererTrackSelections[i] = selectAudioTrack(rendererTrackGroupArrays[i], + rendererFormatSupports[i], params, + seenVideoRendererWithMappedTracks ? null : adaptiveTrackSelectionFactory); + selectedAudioTracks = rendererTrackSelections[i] != null; + } break; case C.TRACK_TYPE_TEXT: - rendererTrackSelections[i] = selectTextTrack(rendererTrackGroupArrays[i], - rendererFormatSupports[i], params.preferredTextLanguage, - params.preferredAudioLanguage, params.exceedRendererCapabilitiesIfNecessary); + if (!selectedTextTracks) { + rendererTrackSelections[i] = selectTextTrack(rendererTrackGroupArrays[i], + rendererFormatSupports[i], params); + selectedTextTracks = rendererTrackSelections[i] != null; + } break; default: rendererTrackSelections[i] = selectOtherTrack(rendererCapabilities[i].getTrackType(), - rendererTrackGroupArrays[i], rendererFormatSupports[i], - params.exceedRendererCapabilitiesIfNecessary); + rendererTrackGroupArrays[i], rendererFormatSupports[i], params); break; } } @@ -466,42 +550,48 @@ public class DefaultTrackSelector extends MappingTrackSelector { // Video track selection implementation. + /** + * Called by {@link #selectTracks(RendererCapabilities[], TrackGroupArray[], int[][][])} to + * create a {@link TrackSelection} for a video renderer. + * + * @param rendererCapabilities The {@link RendererCapabilities} for the renderer. + * @param groups The {@link TrackGroupArray} mapped to the renderer. + * @param formatSupport The result of {@link RendererCapabilities#supportsFormat} for each mapped + * track, indexed by track group index and track index (in that order). + * @param params The selector's current constraint parameters. + * @param adaptiveTrackSelectionFactory A factory for generating adaptive track selections, or + * null if a fixed track selection is required. + * @return The {@link TrackSelection} for the renderer, or null if no selection was made. + * @throws ExoPlaybackException If an error occurs while selecting the tracks. + */ protected TrackSelection selectVideoTrack(RendererCapabilities rendererCapabilities, - TrackGroupArray groups, int[][] formatSupport, int maxVideoWidth, int maxVideoHeight, - int maxVideoBitrate, boolean allowNonSeamlessAdaptiveness, boolean allowMixedMimeAdaptiveness, - int viewportWidth, int viewportHeight, boolean orientationMayChange, - TrackSelection.Factory adaptiveTrackSelectionFactory, boolean exceedConstraintsIfNecessary, - boolean exceedRendererCapabilitiesIfNecessary) throws ExoPlaybackException { + TrackGroupArray groups, int[][] formatSupport, Parameters params, + TrackSelection.Factory adaptiveTrackSelectionFactory) throws ExoPlaybackException { TrackSelection selection = null; if (adaptiveTrackSelectionFactory != null) { selection = selectAdaptiveVideoTrack(rendererCapabilities, groups, formatSupport, - maxVideoWidth, maxVideoHeight, maxVideoBitrate, allowNonSeamlessAdaptiveness, - allowMixedMimeAdaptiveness, viewportWidth, viewportHeight, - orientationMayChange, adaptiveTrackSelectionFactory); + params, adaptiveTrackSelectionFactory); } if (selection == null) { - selection = selectFixedVideoTrack(groups, formatSupport, maxVideoWidth, maxVideoHeight, - maxVideoBitrate, viewportWidth, viewportHeight, orientationMayChange, - exceedConstraintsIfNecessary, exceedRendererCapabilitiesIfNecessary); + selection = selectFixedVideoTrack(groups, formatSupport, params); } return selection; } private static TrackSelection selectAdaptiveVideoTrack(RendererCapabilities rendererCapabilities, - TrackGroupArray groups, int[][] formatSupport, int maxVideoWidth, int maxVideoHeight, - int maxVideoBitrate, boolean allowNonSeamlessAdaptiveness, boolean allowMixedMimeAdaptiveness, - int viewportWidth, int viewportHeight, boolean orientationMayChange, + TrackGroupArray groups, int[][] formatSupport, Parameters params, TrackSelection.Factory adaptiveTrackSelectionFactory) throws ExoPlaybackException { - int requiredAdaptiveSupport = allowNonSeamlessAdaptiveness + int requiredAdaptiveSupport = params.allowNonSeamlessAdaptiveness ? (RendererCapabilities.ADAPTIVE_NOT_SEAMLESS | RendererCapabilities.ADAPTIVE_SEAMLESS) : RendererCapabilities.ADAPTIVE_SEAMLESS; - boolean allowMixedMimeTypes = allowMixedMimeAdaptiveness + boolean allowMixedMimeTypes = params.allowMixedMimeAdaptiveness && (rendererCapabilities.supportsMixedMimeTypeAdaptation() & requiredAdaptiveSupport) != 0; for (int i = 0; i < groups.length; i++) { TrackGroup group = groups.get(i); int[] adaptiveTracks = getAdaptiveVideoTracksForGroup(group, formatSupport[i], - allowMixedMimeTypes, requiredAdaptiveSupport, maxVideoWidth, maxVideoHeight, - maxVideoBitrate, viewportWidth, viewportHeight, orientationMayChange); + allowMixedMimeTypes, requiredAdaptiveSupport, params.maxVideoWidth, params.maxVideoHeight, + params.maxVideoBitrate, params.viewportWidth, params.viewportHeight, + params.viewportOrientationMayChange); if (adaptiveTracks.length > 0) { return adaptiveTrackSelectionFactory.createTrackSelection(group, adaptiveTracks); } @@ -512,13 +602,13 @@ public class DefaultTrackSelector extends MappingTrackSelector { private static int[] getAdaptiveVideoTracksForGroup(TrackGroup group, int[] formatSupport, boolean allowMixedMimeTypes, int requiredAdaptiveSupport, int maxVideoWidth, int maxVideoHeight, int maxVideoBitrate, int viewportWidth, int viewportHeight, - boolean orientationMayChange) { + boolean viewportOrientationMayChange) { if (group.length < 2) { return NO_TRACKS; } List selectedTrackIndices = getViewportFilteredTrackIndices(group, viewportWidth, - viewportHeight, orientationMayChange); + viewportHeight, viewportOrientationMayChange); if (selectedTrackIndices.size() < 2) { return NO_TRACKS; } @@ -589,9 +679,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { } private static TrackSelection selectFixedVideoTrack(TrackGroupArray groups, - int[][] formatSupport, int maxVideoWidth, int maxVideoHeight, int maxVideoBitrate, - int viewportWidth, int viewportHeight, boolean orientationMayChange, - boolean exceedConstraintsIfNecessary, boolean exceedRendererCapabilitiesIfNecessary) { + int[][] formatSupport, Parameters params) { TrackGroup selectedGroup = null; int selectedTrackIndex = 0; int selectedTrackScore = 0; @@ -600,21 +688,23 @@ public class DefaultTrackSelector extends MappingTrackSelector { for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) { TrackGroup trackGroup = groups.get(groupIndex); List selectedTrackIndices = getViewportFilteredTrackIndices(trackGroup, - viewportWidth, viewportHeight, orientationMayChange); + params.viewportWidth, params.viewportHeight, params.viewportOrientationMayChange); int[] trackFormatSupport = formatSupport[groupIndex]; for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { - if (isSupported(trackFormatSupport[trackIndex], exceedRendererCapabilitiesIfNecessary)) { + if (isSupported(trackFormatSupport[trackIndex], + params.exceedRendererCapabilitiesIfNecessary)) { Format format = trackGroup.getFormat(trackIndex); boolean isWithinConstraints = selectedTrackIndices.contains(trackIndex) - && (format.width == Format.NO_VALUE || format.width <= maxVideoWidth) - && (format.height == Format.NO_VALUE || format.height <= maxVideoHeight) - && (format.bitrate == Format.NO_VALUE || format.bitrate <= maxVideoBitrate); - if (!isWithinConstraints && !exceedConstraintsIfNecessary) { + && (format.width == Format.NO_VALUE || format.width <= params.maxVideoWidth) + && (format.height == Format.NO_VALUE || format.height <= params.maxVideoHeight) + && (format.bitrate == Format.NO_VALUE || format.bitrate <= params.maxVideoBitrate); + if (!isWithinConstraints && !params.exceedVideoConstraintsIfNecessary) { // Track should not be selected. continue; } int trackScore = isWithinConstraints ? 2 : 1; - if (isSupported(trackFormatSupport[trackIndex], false)) { + boolean isWithinCapabilities = isSupported(trackFormatSupport[trackIndex], false); + if (isWithinCapabilities) { trackScore += WITHIN_RENDERER_CAPABILITIES_BONUS; } boolean selectTrack = trackScore > selectedTrackScore; @@ -630,7 +720,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { } else { comparisonResult = compareFormatValues(format.bitrate, selectedBitrate); } - selectTrack = isWithinConstraints ? comparisonResult > 0 : comparisonResult < 0; + selectTrack = isWithinCapabilities && isWithinConstraints + ? comparisonResult > 0 : comparisonResult < 0; } if (selectTrack) { selectedGroup = trackGroup; @@ -662,9 +753,22 @@ public class DefaultTrackSelector extends MappingTrackSelector { // Audio track selection implementation. + /** + * Called by {@link #selectTracks(RendererCapabilities[], TrackGroupArray[], int[][][])} to + * create a {@link TrackSelection} for an audio renderer. + * + * @param groups The {@link TrackGroupArray} mapped to the renderer. + * @param formatSupport The result of {@link RendererCapabilities#supportsFormat} for each mapped + * track, indexed by track group index and track index (in that order). + * @param params The selector's current constraint parameters. + * @param adaptiveTrackSelectionFactory A factory for generating adaptive track selections, or + * null if a fixed track selection is required. + * @return The {@link TrackSelection} for the renderer, or null if no selection was made. + * @throws ExoPlaybackException If an error occurs while selecting the tracks. + */ protected TrackSelection selectAudioTrack(TrackGroupArray groups, int[][] formatSupport, - String preferredAudioLanguage, boolean exceedRendererCapabilitiesIfNecessary, - boolean allowMixedMimeAdaptiveness, TrackSelection.Factory adaptiveTrackSelectionFactory) { + Parameters params, TrackSelection.Factory adaptiveTrackSelectionFactory) + throws ExoPlaybackException { int selectedGroupIndex = C.INDEX_UNSET; int selectedTrackIndex = C.INDEX_UNSET; int selectedTrackScore = 0; @@ -672,10 +776,11 @@ public class DefaultTrackSelector extends MappingTrackSelector { TrackGroup trackGroup = groups.get(groupIndex); int[] trackFormatSupport = formatSupport[groupIndex]; for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { - if (isSupported(trackFormatSupport[trackIndex], exceedRendererCapabilitiesIfNecessary)) { + if (isSupported(trackFormatSupport[trackIndex], + params.exceedRendererCapabilitiesIfNecessary)) { Format format = trackGroup.getFormat(trackIndex); int trackScore = getAudioTrackScore(trackFormatSupport[trackIndex], - preferredAudioLanguage, format); + params.preferredAudioLanguage, format); if (trackScore > selectedTrackScore) { selectedGroupIndex = groupIndex; selectedTrackIndex = trackIndex; @@ -693,7 +798,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { if (adaptiveTrackSelectionFactory != null) { // If the group of the track with the highest score allows it, try to enable adaptation. int[] adaptiveTracks = getAdaptiveAudioTracks(selectedGroup, - formatSupport[selectedGroupIndex], allowMixedMimeAdaptiveness); + formatSupport[selectedGroupIndex], params.allowMixedMimeAdaptiveness); if (adaptiveTracks.length > 0) { return adaptiveTrackSelectionFactory.createTrackSelection(selectedGroup, adaptiveTracks); @@ -777,9 +882,19 @@ public class DefaultTrackSelector extends MappingTrackSelector { // Text track selection implementation. + /** + * Called by {@link #selectTracks(RendererCapabilities[], TrackGroupArray[], int[][][])} to + * create a {@link TrackSelection} for a text renderer. + * + * @param groups The {@link TrackGroupArray} mapped to the renderer. + * @param formatSupport The result of {@link RendererCapabilities#supportsFormat} for each mapped + * track, indexed by track group index and track index (in that order). + * @param params The selector's current constraint parameters. + * @return The {@link TrackSelection} for the renderer, or null if no selection was made. + * @throws ExoPlaybackException If an error occurs while selecting the tracks. + */ protected TrackSelection selectTextTrack(TrackGroupArray groups, int[][] formatSupport, - String preferredTextLanguage, String preferredAudioLanguage, - boolean exceedRendererCapabilitiesIfNecessary) { + Parameters params) throws ExoPlaybackException { TrackGroup selectedGroup = null; int selectedTrackIndex = 0; int selectedTrackScore = 0; @@ -787,12 +902,13 @@ public class DefaultTrackSelector extends MappingTrackSelector { TrackGroup trackGroup = groups.get(groupIndex); int[] trackFormatSupport = formatSupport[groupIndex]; for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { - if (isSupported(trackFormatSupport[trackIndex], exceedRendererCapabilitiesIfNecessary)) { + if (isSupported(trackFormatSupport[trackIndex], + params.exceedRendererCapabilitiesIfNecessary)) { Format format = trackGroup.getFormat(trackIndex); boolean isDefault = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0; boolean isForced = (format.selectionFlags & C.SELECTION_FLAG_FORCED) != 0; int trackScore; - if (formatHasLanguage(format, preferredTextLanguage)) { + if (formatHasLanguage(format, params.preferredTextLanguage)) { if (isDefault) { trackScore = 6; } else if (!isForced) { @@ -806,7 +922,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { } else if (isDefault) { trackScore = 3; } else if (isForced) { - if (formatHasLanguage(format, preferredAudioLanguage)) { + if (formatHasLanguage(format, params.preferredAudioLanguage)) { trackScore = 2; } else { trackScore = 1; @@ -832,8 +948,20 @@ public class DefaultTrackSelector extends MappingTrackSelector { // General track selection methods. + /** + * Called by {@link #selectTracks(RendererCapabilities[], TrackGroupArray[], int[][][])} to + * create a {@link TrackSelection} for a renderer whose type is neither video, audio or text. + * + * @param trackType The type of the renderer. + * @param groups The {@link TrackGroupArray} mapped to the renderer. + * @param formatSupport The result of {@link RendererCapabilities#supportsFormat} for each mapped + * track, indexed by track group index and track index (in that order). + * @param params The selector's current constraint parameters. + * @return The {@link TrackSelection} for the renderer, or null if no selection was made. + * @throws ExoPlaybackException If an error occurs while selecting the tracks. + */ protected TrackSelection selectOtherTrack(int trackType, TrackGroupArray groups, - int[][] formatSupport, boolean exceedRendererCapabilitiesIfNecessary) { + int[][] formatSupport, Parameters params) throws ExoPlaybackException { TrackGroup selectedGroup = null; int selectedTrackIndex = 0; int selectedTrackScore = 0; @@ -841,7 +969,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { TrackGroup trackGroup = groups.get(groupIndex); int[] trackFormatSupport = formatSupport[groupIndex]; for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { - if (isSupported(trackFormatSupport[trackIndex], exceedRendererCapabilitiesIfNecessary)) { + if (isSupported(trackFormatSupport[trackIndex], + params.exceedRendererCapabilitiesIfNecessary)) { Format format = trackGroup.getFormat(trackIndex); boolean isDefault = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0; int trackScore = isDefault ? 2 : 1; @@ -860,14 +989,37 @@ public class DefaultTrackSelector extends MappingTrackSelector { : new FixedTrackSelection(selectedGroup, selectedTrackIndex); } + /** + * Applies the {@link RendererCapabilities#FORMAT_SUPPORT_MASK} to a value obtained from + * {@link RendererCapabilities#supportsFormat(Format)}, returning true if the result is + * {@link RendererCapabilities#FORMAT_HANDLED} or if {@code allowExceedsCapabilities} is set + * and the result is {@link RendererCapabilities#FORMAT_EXCEEDS_CAPABILITIES}. + * + * @param formatSupport A value obtained from {@link RendererCapabilities#supportsFormat(Format)}. + * @param allowExceedsCapabilities Whether to return true if the format support component of the + * value is {@link RendererCapabilities#FORMAT_EXCEEDS_CAPABILITIES}. + * @return True if the format support component is {@link RendererCapabilities#FORMAT_HANDLED}, or + * if {@code allowExceedsCapabilities} is set and the format support component is + * {@link RendererCapabilities#FORMAT_EXCEEDS_CAPABILITIES}. + */ protected static boolean isSupported(int formatSupport, boolean allowExceedsCapabilities) { int maskedSupport = formatSupport & RendererCapabilities.FORMAT_SUPPORT_MASK; return maskedSupport == RendererCapabilities.FORMAT_HANDLED || (allowExceedsCapabilities && maskedSupport == RendererCapabilities.FORMAT_EXCEEDS_CAPABILITIES); } + /** + * Returns whether a {@link Format} specifies a particular language, or {@code false} if + * {@code language} is null. + * + * @param format The {@link Format}. + * @param language The language. + * @return Whether the format specifies the language, or {@code false} if {@code language} is + * null. + */ protected static boolean formatHasLanguage(Format format, String language) { - return TextUtils.equals(language, Util.normalizeLanguageCode(format.language)); + return language != null + && TextUtils.equals(language, Util.normalizeLanguageCode(format.language)); } // Viewport size util methods. diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/MappingTrackSelector.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/MappingTrackSelector.java index 4ad1790ee..d19092077 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/MappingTrackSelector.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/MappingTrackSelector.java @@ -20,6 +20,8 @@ import android.util.SparseArray; import android.util.SparseBooleanArray; import org.telegram.messenger.exoplayer2.C; import org.telegram.messenger.exoplayer2.ExoPlaybackException; +import org.telegram.messenger.exoplayer2.Player; +import org.telegram.messenger.exoplayer2.Renderer; import org.telegram.messenger.exoplayer2.RendererCapabilities; import org.telegram.messenger.exoplayer2.RendererConfiguration; import org.telegram.messenger.exoplayer2.source.TrackGroup; @@ -31,10 +33,262 @@ import java.util.Map; /** * Base class for {@link TrackSelector}s that first establish a mapping between {@link TrackGroup}s - * and renderers, and then from that mapping create a {@link TrackSelection} for each renderer. + * and {@link Renderer}s, and then from that mapping create a {@link TrackSelection} for each + * renderer. + * + *

    Track overrides

    + * Mapping track selectors support overriding of track selections for each renderer. To specify an + * override for a renderer it's first necessary to obtain the tracks that have been mapped to it: + *
    + * {@code
    + * MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo();
    + * TrackGroupArray rendererTrackGroups = mappedTrackInfo == null ? null
    + *     : mappedTrackInfo.getTrackGroups(rendererIndex);}
    + * 
    + * If {@code rendererTrackGroups} is null then there aren't any currently mapped tracks, and so + * setting an override isn't possible. Note that a {@link Player.EventListener} registered on the + * player can be used to determine when the current tracks (and therefore the mapping) changes. If + * {@code rendererTrackGroups} is non-null then an override can be set. The next step is to query + * the properties of the available tracks to determine the {@code groupIndex} of the track group you + * want to select and the {@code trackIndices} within it. You can then create and set the override: + *
    + * {@code
    + * trackSelector.setSelectionOverride(rendererIndex, rendererTrackGroups,
    + *     new SelectionOverride(trackSelectionFactory, groupIndex, trackIndices));}
    + * 
    + * where {@code trackSelectionFactory} is a {@link TrackSelection.Factory} for generating concrete + * {@link TrackSelection} instances for the override. It's also possible to pass {@code null} as the + * selection override if you don't want any tracks to be selected. + *

    + * Note that an override applies only when the track groups available to the renderer match the + * {@link TrackGroupArray} for which the override was specified. Overrides can be cleared using + * the {@code clearSelectionOverride} methods. + * + *

    Disabling renderers

    + * Renderers can be disabled using {@link #setRendererDisabled(int, boolean)}. Disabling a renderer + * differs from setting a {@code null} override because the renderer is disabled unconditionally, + * whereas a {@code null} override is applied only when the track groups available to the renderer + * match the {@link TrackGroupArray} for which it was specified. + * + *

    Tunneling

    + * Tunneled playback can be enabled in cases where the combination of renderers and selected tracks + * support it. See {@link #setTunnelingAudioSessionId(int)} for more details. */ public abstract class MappingTrackSelector extends TrackSelector { + /** + * Provides mapped track information for each renderer. + */ + public static final class MappedTrackInfo { + + /** + * The renderer does not have any associated tracks. + */ + public static final int RENDERER_SUPPORT_NO_TRACKS = 0; + /** + * The renderer has associated tracks, but all are of unsupported types. + */ + public static final int RENDERER_SUPPORT_UNSUPPORTED_TRACKS = 1; + /** + * The renderer has associated tracks and at least one is of a supported type, but all of the + * tracks whose types are supported exceed the renderer's capabilities. + */ + public static final int RENDERER_SUPPORT_EXCEEDS_CAPABILITIES_TRACKS = 2; + /** + * The renderer has associated tracks and can play at least one of them. + */ + public static final int RENDERER_SUPPORT_PLAYABLE_TRACKS = 3; + + /** + * The number of renderers to which tracks are mapped. + */ + public final int length; + + private final int[] rendererTrackTypes; + private final TrackGroupArray[] trackGroups; + private final int[] mixedMimeTypeAdaptiveSupport; + private final int[][][] formatSupport; + private final TrackGroupArray unassociatedTrackGroups; + + /** + * @param rendererTrackTypes The track type supported by each renderer. + * @param trackGroups The {@link TrackGroup}s mapped to each renderer. + * @param mixedMimeTypeAdaptiveSupport The result of + * {@link RendererCapabilities#supportsMixedMimeTypeAdaptation()} for each renderer. + * @param formatSupport The result of {@link RendererCapabilities#supportsFormat} for each + * mapped track, indexed by renderer index, track group index and track index (in that + * order). + * @param unassociatedTrackGroups Any {@link TrackGroup}s not mapped to any renderer. + */ + /* package */ MappedTrackInfo(int[] rendererTrackTypes, + TrackGroupArray[] trackGroups, int[] mixedMimeTypeAdaptiveSupport, + int[][][] formatSupport, TrackGroupArray unassociatedTrackGroups) { + this.rendererTrackTypes = rendererTrackTypes; + this.trackGroups = trackGroups; + this.formatSupport = formatSupport; + this.mixedMimeTypeAdaptiveSupport = mixedMimeTypeAdaptiveSupport; + this.unassociatedTrackGroups = unassociatedTrackGroups; + this.length = trackGroups.length; + } + + /** + * Returns the {@link TrackGroup}s mapped to the renderer at the specified index. + * + * @param rendererIndex The renderer index. + * @return The corresponding {@link TrackGroup}s. + */ + public TrackGroupArray getTrackGroups(int rendererIndex) { + return trackGroups[rendererIndex]; + } + + /** + * Returns the extent to which a renderer can play the tracks in the track groups mapped to it. + * + * @param rendererIndex The renderer index. + * @return One of {@link #RENDERER_SUPPORT_PLAYABLE_TRACKS}, + * {@link #RENDERER_SUPPORT_EXCEEDS_CAPABILITIES_TRACKS}, + * {@link #RENDERER_SUPPORT_UNSUPPORTED_TRACKS} and {@link #RENDERER_SUPPORT_NO_TRACKS}. + */ + public int getRendererSupport(int rendererIndex) { + int bestRendererSupport = RENDERER_SUPPORT_NO_TRACKS; + int[][] rendererFormatSupport = formatSupport[rendererIndex]; + for (int i = 0; i < rendererFormatSupport.length; i++) { + for (int j = 0; j < rendererFormatSupport[i].length; j++) { + int trackRendererSupport; + switch (rendererFormatSupport[i][j] & RendererCapabilities.FORMAT_SUPPORT_MASK) { + case RendererCapabilities.FORMAT_HANDLED: + return RENDERER_SUPPORT_PLAYABLE_TRACKS; + case RendererCapabilities.FORMAT_EXCEEDS_CAPABILITIES: + trackRendererSupport = RENDERER_SUPPORT_EXCEEDS_CAPABILITIES_TRACKS; + break; + default: + trackRendererSupport = RENDERER_SUPPORT_UNSUPPORTED_TRACKS; + break; + } + bestRendererSupport = Math.max(bestRendererSupport, trackRendererSupport); + } + } + return bestRendererSupport; + } + + /** + * Returns the best level of support obtained from {@link #getRendererSupport(int)} for all + * renderers of the specified track type. If no renderers exist for the specified type then + * {@link #RENDERER_SUPPORT_NO_TRACKS} is returned. + * + * @param trackType The track type. One of the {@link C} {@code TRACK_TYPE_*} constants. + * @return One of {@link #RENDERER_SUPPORT_PLAYABLE_TRACKS}, + * {@link #RENDERER_SUPPORT_EXCEEDS_CAPABILITIES_TRACKS}, + * {@link #RENDERER_SUPPORT_UNSUPPORTED_TRACKS} and {@link #RENDERER_SUPPORT_NO_TRACKS}. + */ + public int getTrackTypeRendererSupport(int trackType) { + int bestRendererSupport = RENDERER_SUPPORT_NO_TRACKS; + for (int i = 0; i < length; i++) { + if (rendererTrackTypes[i] == trackType) { + bestRendererSupport = Math.max(bestRendererSupport, getRendererSupport(i)); + } + } + return bestRendererSupport; + } + + /** + * Returns the extent to which an individual track is supported by the renderer. + * + * @param rendererIndex The renderer index. + * @param groupIndex The index of the track group to which the track belongs. + * @param trackIndex The index of the track within the track group. + * @return One of {@link RendererCapabilities#FORMAT_HANDLED}, + * {@link RendererCapabilities#FORMAT_EXCEEDS_CAPABILITIES}, + * {@link RendererCapabilities#FORMAT_UNSUPPORTED_DRM}, + * {@link RendererCapabilities#FORMAT_UNSUPPORTED_SUBTYPE} and + * {@link RendererCapabilities#FORMAT_UNSUPPORTED_TYPE}. + */ + public int getTrackFormatSupport(int rendererIndex, int groupIndex, int trackIndex) { + return formatSupport[rendererIndex][groupIndex][trackIndex] + & RendererCapabilities.FORMAT_SUPPORT_MASK; + } + + /** + * Returns the extent to which a renderer supports adaptation between supported tracks in a + * specified {@link TrackGroup}. + *

    + * Tracks for which {@link #getTrackFormatSupport(int, int, int)} returns + * {@link RendererCapabilities#FORMAT_HANDLED} are always considered. + * Tracks for which {@link #getTrackFormatSupport(int, int, int)} returns + * {@link RendererCapabilities#FORMAT_UNSUPPORTED_DRM}, + * {@link RendererCapabilities#FORMAT_UNSUPPORTED_TYPE} or + * {@link RendererCapabilities#FORMAT_UNSUPPORTED_SUBTYPE} are never considered. + * Tracks for which {@link #getTrackFormatSupport(int, int, int)} returns + * {@link RendererCapabilities#FORMAT_EXCEEDS_CAPABILITIES} are considered only if + * {@code includeCapabilitiesExceededTracks} is set to {@code true}. + * + * @param rendererIndex The renderer index. + * @param groupIndex The index of the track group. + * @param includeCapabilitiesExceededTracks True if formats that exceed the capabilities of the + * renderer should be included when determining support. False otherwise. + * @return One of {@link RendererCapabilities#ADAPTIVE_SEAMLESS}, + * {@link RendererCapabilities#ADAPTIVE_NOT_SEAMLESS} and + * {@link RendererCapabilities#ADAPTIVE_NOT_SUPPORTED}. + */ + public int getAdaptiveSupport(int rendererIndex, int groupIndex, + boolean includeCapabilitiesExceededTracks) { + int trackCount = trackGroups[rendererIndex].get(groupIndex).length; + // Iterate over the tracks in the group, recording the indices of those to consider. + int[] trackIndices = new int[trackCount]; + int trackIndexCount = 0; + for (int i = 0; i < trackCount; i++) { + int fixedSupport = getTrackFormatSupport(rendererIndex, groupIndex, i); + if (fixedSupport == RendererCapabilities.FORMAT_HANDLED + || (includeCapabilitiesExceededTracks + && fixedSupport == RendererCapabilities.FORMAT_EXCEEDS_CAPABILITIES)) { + trackIndices[trackIndexCount++] = i; + } + } + trackIndices = Arrays.copyOf(trackIndices, trackIndexCount); + return getAdaptiveSupport(rendererIndex, groupIndex, trackIndices); + } + + /** + * Returns the extent to which a renderer supports adaptation between specified tracks within + * a {@link TrackGroup}. + * + * @param rendererIndex The renderer index. + * @param groupIndex The index of the track group. + * @return One of {@link RendererCapabilities#ADAPTIVE_SEAMLESS}, + * {@link RendererCapabilities#ADAPTIVE_NOT_SEAMLESS} and + * {@link RendererCapabilities#ADAPTIVE_NOT_SUPPORTED}. + */ + public int getAdaptiveSupport(int rendererIndex, int groupIndex, int[] trackIndices) { + int handledTrackCount = 0; + int adaptiveSupport = RendererCapabilities.ADAPTIVE_SEAMLESS; + boolean multipleMimeTypes = false; + String firstSampleMimeType = null; + for (int i = 0; i < trackIndices.length; i++) { + int trackIndex = trackIndices[i]; + String sampleMimeType = trackGroups[rendererIndex].get(groupIndex).getFormat(trackIndex) + .sampleMimeType; + if (handledTrackCount++ == 0) { + firstSampleMimeType = sampleMimeType; + } else { + multipleMimeTypes |= !Util.areEqual(firstSampleMimeType, sampleMimeType); + } + adaptiveSupport = Math.min(adaptiveSupport, formatSupport[rendererIndex][groupIndex][i] + & RendererCapabilities.ADAPTIVE_SUPPORT_MASK); + } + return multipleMimeTypes + ? Math.min(adaptiveSupport, mixedMimeTypeAdaptiveSupport[rendererIndex]) + : adaptiveSupport; + } + + /** + * Returns {@link TrackGroup}s not mapped to any renderer. + */ + public TrackGroupArray getUnassociatedTrackGroups() { + return unassociatedTrackGroups; + } + + } + /** * A track selection override. */ @@ -47,8 +301,8 @@ public abstract class MappingTrackSelector extends TrackSelector { /** * @param factory A factory for creating selections from this override. - * @param groupIndex The overriding group index. - * @param tracks The overriding track indices within the group. + * @param groupIndex The overriding track group index. + * @param tracks The overriding track indices within the track group. */ public SelectionOverride(TrackSelection.Factory factory, int groupIndex, int... tracks) { this.factory = factory; @@ -60,7 +314,7 @@ public abstract class MappingTrackSelector extends TrackSelector { /** * Creates an selection from this override. * - * @param groups The groups whose selection is being overridden. + * @param groups The track groups whose selection is being overridden. * @return The selection. */ public TrackSelection createTrackSelection(TrackGroupArray groups) { @@ -94,7 +348,7 @@ public abstract class MappingTrackSelector extends TrackSelector { } /** - * Returns the mapping information associated with the current track selections, or null if no + * Returns the mapping information for the currently active track selection, or null if no * selection is currently active. */ public final MappedTrackInfo getCurrentMappedTrackInfo() { @@ -102,7 +356,8 @@ public abstract class MappingTrackSelector extends TrackSelector { } /** - * Sets whether the renderer at the specified index is disabled. + * Sets whether the renderer at the specified index is disabled. Disabling a renderer prevents the + * selector from selecting any tracks for it. * * @param rendererIndex The renderer index. * @param disabled Whether the renderer is disabled. @@ -127,16 +382,22 @@ public abstract class MappingTrackSelector extends TrackSelector { } /** - * Overrides the track selection for the renderer at a specified index. + * Overrides the track selection for the renderer at the specified index. *

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

    - * Passing a {@code null} override will explicitly disable the renderer. To remove overrides use - * {@link #clearSelectionOverride(int, TrackGroupArray)}, {@link #clearSelectionOverrides(int)} - * or {@link #clearSelectionOverrides()}. + * Passing a {@code null} override will cause the renderer to be disabled when the + * {@link TrackGroupArray} mapped to it matches the one provided. When the {@link TrackGroupArray} + * does not match a {@code null} override has no effect. Hence a {@code null} override differs + * from disabling the renderer using {@link #setRendererDisabled(int, boolean)} because the + * renderer is disabled conditionally on the {@link TrackGroupArray} mapped to it, where-as + * {@link #setRendererDisabled(int, boolean)} disables the renderer unconditionally. + *

    + * To remove overrides use {@link #clearSelectionOverride(int, TrackGroupArray)}, + * {@link #clearSelectionOverrides(int)} or {@link #clearSelectionOverrides()}. * * @param rendererIndex The renderer index. * @param groups The {@link TrackGroupArray} for which the override should be applied. @@ -201,7 +462,7 @@ public abstract class MappingTrackSelector extends TrackSelector { } /** - * Clears all track selection override for the specified renderer. + * Clears all track selection overrides for the specified renderer. * * @param rendererIndex The renderer index. */ @@ -216,7 +477,7 @@ public abstract class MappingTrackSelector extends TrackSelector { } /** - * Clears all track selection overrides. + * Clears all track selection overrides for all renderers. */ public final void clearSelectionOverrides() { if (selectionOverrides.size() == 0) { @@ -304,10 +565,10 @@ public abstract class MappingTrackSelector extends TrackSelector { trackSelections[i] = null; } else { TrackGroupArray rendererTrackGroup = rendererTrackGroupArrays[i]; - Map overrides = selectionOverrides.get(i); - SelectionOverride override = overrides == null ? null : overrides.get(rendererTrackGroup); - if (override != null) { - trackSelections[i] = override.createTrackSelection(rendererTrackGroup); + if (hasSelectionOverride(i, rendererTrackGroup)) { + SelectionOverride override = selectionOverrides.get(i).get(rendererTrackGroup); + trackSelections[i] = override == null ? null + : override.createTrackSelection(rendererTrackGroup); } } } @@ -338,15 +599,15 @@ public abstract class MappingTrackSelector extends TrackSelector { } /** - * Given an array of renderers and a set of {@link TrackGroup}s mapped to each of them, provides a - * {@link TrackSelection} per renderer. + * Given an array of renderer capabilities and the {@link TrackGroupArray}s mapped to each of + * them, provides a {@link TrackSelection} per renderer. * * @param rendererCapabilities The {@link RendererCapabilities} of the renderers for which * {@link TrackSelection}s are to be generated. - * @param rendererTrackGroupArrays An array of {@link TrackGroupArray}s where each entry - * corresponds to the renderer of equal index in {@code renderers}. - * @param rendererFormatSupports Maps every available track to a specific level of support as - * defined by the renderer {@code FORMAT_*} constants. + * @param rendererTrackGroupArrays The {@link TrackGroupArray}s mapped to each of the renderers. + * @param rendererFormatSupports The result of {@link RendererCapabilities#supportsFormat} for + * each mapped track, indexed by renderer index, track group index and track index (in that + * order). * @throws ExoPlaybackException If an error occurs while selecting the tracks. */ protected abstract TrackSelection[] selectTracks(RendererCapabilities[] rendererCapabilities, @@ -354,23 +615,23 @@ public abstract class MappingTrackSelector extends TrackSelector { throws ExoPlaybackException; /** - * Finds the renderer to which the provided {@link TrackGroup} should be associated. + * Finds the renderer to which the provided {@link TrackGroup} should be mapped. *

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

    * If all renderers report {@link RendererCapabilities#FORMAT_UNSUPPORTED_TYPE} for all of the - * tracks in the group, then {@code renderers.length} is returned to indicate that no association - * was made. + * tracks in the group, then {@code renderers.length} is returned to indicate that the group was + * not mapped to any renderer. * * @param rendererCapabilities The {@link RendererCapabilities} of the renderers. - * @param group The {@link TrackGroup} whose associated renderer is to be found. - * @return The index of the associated renderer, or {@code renderers.length} if no - * association was made. + * @param group The track group to map to a renderer. + * @return The index of the renderer to which the track group was mapped, or + * {@code renderers.length} if it was not mapped to any renderer. * @throws ExoPlaybackException If an error occurs finding a renderer. */ private static int findRenderer(RendererCapabilities[] rendererCapabilities, TrackGroup group) @@ -400,7 +661,7 @@ public abstract class MappingTrackSelector extends TrackSelector { * {@link TrackGroup}, returning the results in an array. * * @param rendererCapabilities The {@link RendererCapabilities} of the renderer. - * @param group The {@link TrackGroup} to evaluate. + * @param group The track group to evaluate. * @return An array containing the result of calling * {@link RendererCapabilities#supportsFormat} for each track in the group. * @throws ExoPlaybackException If an error occurs determining the format support. @@ -520,214 +781,4 @@ public abstract class MappingTrackSelector extends TrackSelector { return true; } - /** - * Provides track information for each renderer. - */ - public static final class MappedTrackInfo { - - /** - * The renderer does not have any associated tracks. - */ - public static final int RENDERER_SUPPORT_NO_TRACKS = 0; - /** - * The renderer has associated tracks, but all are of unsupported types. - */ - public static final int RENDERER_SUPPORT_UNSUPPORTED_TRACKS = 1; - /** - * The renderer has associated tracks and at least one is of a supported type, but all of the - * tracks whose types are supported exceed the renderer's capabilities. - */ - public static final int RENDERER_SUPPORT_EXCEEDS_CAPABILITIES_TRACKS = 2; - /** - * The renderer has associated tracks and can play at least one of them. - */ - public static final int RENDERER_SUPPORT_PLAYABLE_TRACKS = 3; - - /** - * The number of renderers to which tracks are mapped. - */ - public final int length; - - private final int[] rendererTrackTypes; - private final TrackGroupArray[] trackGroups; - private final int[] mixedMimeTypeAdaptiveSupport; - private final int[][][] formatSupport; - private final TrackGroupArray unassociatedTrackGroups; - - /** - * @param rendererTrackTypes The track type supported by each renderer. - * @param trackGroups The {@link TrackGroupArray}s for each renderer. - * @param mixedMimeTypeAdaptiveSupport The result of - * {@link RendererCapabilities#supportsMixedMimeTypeAdaptation()} for each renderer. - * @param formatSupport The result of {@link RendererCapabilities#supportsFormat} for each - * track, indexed by renderer index, group index and track index (in that order). - * @param unassociatedTrackGroups Contains {@link TrackGroup}s not associated with any renderer. - */ - /* package */ MappedTrackInfo(int[] rendererTrackTypes, - TrackGroupArray[] trackGroups, int[] mixedMimeTypeAdaptiveSupport, - int[][][] formatSupport, TrackGroupArray unassociatedTrackGroups) { - this.rendererTrackTypes = rendererTrackTypes; - this.trackGroups = trackGroups; - this.formatSupport = formatSupport; - this.mixedMimeTypeAdaptiveSupport = mixedMimeTypeAdaptiveSupport; - this.unassociatedTrackGroups = unassociatedTrackGroups; - this.length = trackGroups.length; - } - - /** - * Returns the array of {@link TrackGroup}s associated to the renderer at a specified index. - * - * @param rendererIndex The renderer index. - * @return The corresponding {@link TrackGroup}s. - */ - public TrackGroupArray getTrackGroups(int rendererIndex) { - return trackGroups[rendererIndex]; - } - - /** - * Returns the extent to which a renderer can support playback of the tracks associated to it. - * - * @param rendererIndex The renderer index. - * @return One of {@link #RENDERER_SUPPORT_PLAYABLE_TRACKS}, - * {@link #RENDERER_SUPPORT_EXCEEDS_CAPABILITIES_TRACKS}, - * {@link #RENDERER_SUPPORT_UNSUPPORTED_TRACKS} and {@link #RENDERER_SUPPORT_NO_TRACKS}. - */ - public int getRendererSupport(int rendererIndex) { - int bestRendererSupport = RENDERER_SUPPORT_NO_TRACKS; - int[][] rendererFormatSupport = formatSupport[rendererIndex]; - for (int i = 0; i < rendererFormatSupport.length; i++) { - for (int j = 0; j < rendererFormatSupport[i].length; j++) { - int trackRendererSupport; - switch (rendererFormatSupport[i][j] & RendererCapabilities.FORMAT_SUPPORT_MASK) { - case RendererCapabilities.FORMAT_HANDLED: - return RENDERER_SUPPORT_PLAYABLE_TRACKS; - case RendererCapabilities.FORMAT_EXCEEDS_CAPABILITIES: - trackRendererSupport = RENDERER_SUPPORT_EXCEEDS_CAPABILITIES_TRACKS; - break; - default: - trackRendererSupport = RENDERER_SUPPORT_UNSUPPORTED_TRACKS; - break; - } - bestRendererSupport = Math.max(bestRendererSupport, trackRendererSupport); - } - } - return bestRendererSupport; - } - - /** - * Returns the best level of support obtained from {@link #getRendererSupport(int)} for all - * renderers of the specified track type. If no renderers exist for the specified type then - * {@link #RENDERER_SUPPORT_NO_TRACKS} is returned. - * - * @param trackType The track type. One of the {@link C} {@code TRACK_TYPE_*} constants. - * @return One of {@link #RENDERER_SUPPORT_PLAYABLE_TRACKS}, - * {@link #RENDERER_SUPPORT_EXCEEDS_CAPABILITIES_TRACKS}, - * {@link #RENDERER_SUPPORT_UNSUPPORTED_TRACKS} and {@link #RENDERER_SUPPORT_NO_TRACKS}. - */ - public int getTrackTypeRendererSupport(int trackType) { - int bestRendererSupport = RENDERER_SUPPORT_NO_TRACKS; - for (int i = 0; i < length; i++) { - if (rendererTrackTypes[i] == trackType) { - bestRendererSupport = Math.max(bestRendererSupport, getRendererSupport(i)); - } - } - return bestRendererSupport; - } - - /** - * Returns the extent to which the format of an individual track is supported by the renderer. - * - * @param rendererIndex The renderer index. - * @param groupIndex The index of the group to which the track belongs. - * @param trackIndex The index of the track within the group. - * @return One of {@link RendererCapabilities#FORMAT_HANDLED}, - * {@link RendererCapabilities#FORMAT_EXCEEDS_CAPABILITIES}, - * {@link RendererCapabilities#FORMAT_UNSUPPORTED_SUBTYPE} and - * {@link RendererCapabilities#FORMAT_UNSUPPORTED_TYPE}. - */ - public int getTrackFormatSupport(int rendererIndex, int groupIndex, int trackIndex) { - return formatSupport[rendererIndex][groupIndex][trackIndex] - & RendererCapabilities.FORMAT_SUPPORT_MASK; - } - - /** - * Returns the extent to which the renderer supports adaptation between supported tracks in a - * specified {@link TrackGroup}. - *

    - * Tracks for which {@link #getTrackFormatSupport(int, int, int)} returns - * {@link RendererCapabilities#FORMAT_HANDLED} are always considered. - * Tracks for which {@link #getTrackFormatSupport(int, int, int)} returns - * {@link RendererCapabilities#FORMAT_UNSUPPORTED_TYPE} or - * {@link RendererCapabilities#FORMAT_UNSUPPORTED_SUBTYPE} are never considered. - * Tracks for which {@link #getTrackFormatSupport(int, int, int)} returns - * {@link RendererCapabilities#FORMAT_EXCEEDS_CAPABILITIES} are considered only if - * {@code includeCapabilitiesExceededTracks} is set to {@code true}. - * - * @param rendererIndex The renderer index. - * @param groupIndex The index of the group. - * @param includeCapabilitiesExceededTracks True if formats that exceed the capabilities of the - * renderer should be included when determining support. False otherwise. - * @return One of {@link RendererCapabilities#ADAPTIVE_SEAMLESS}, - * {@link RendererCapabilities#ADAPTIVE_NOT_SEAMLESS} and - * {@link RendererCapabilities#ADAPTIVE_NOT_SUPPORTED}. - */ - public int getAdaptiveSupport(int rendererIndex, int groupIndex, - boolean includeCapabilitiesExceededTracks) { - int trackCount = trackGroups[rendererIndex].get(groupIndex).length; - // Iterate over the tracks in the group, recording the indices of those to consider. - int[] trackIndices = new int[trackCount]; - int trackIndexCount = 0; - for (int i = 0; i < trackCount; i++) { - int fixedSupport = getTrackFormatSupport(rendererIndex, groupIndex, i); - if (fixedSupport == RendererCapabilities.FORMAT_HANDLED - || (includeCapabilitiesExceededTracks - && fixedSupport == RendererCapabilities.FORMAT_EXCEEDS_CAPABILITIES)) { - trackIndices[trackIndexCount++] = i; - } - } - trackIndices = Arrays.copyOf(trackIndices, trackIndexCount); - return getAdaptiveSupport(rendererIndex, groupIndex, trackIndices); - } - - /** - * Returns the extent to which the renderer supports adaptation between specified tracks within - * a {@link TrackGroup}. - * - * @param rendererIndex The renderer index. - * @param groupIndex The index of the group. - * @return One of {@link RendererCapabilities#ADAPTIVE_SEAMLESS}, - * {@link RendererCapabilities#ADAPTIVE_NOT_SEAMLESS} and - * {@link RendererCapabilities#ADAPTIVE_NOT_SUPPORTED}. - */ - public int getAdaptiveSupport(int rendererIndex, int groupIndex, int[] trackIndices) { - int handledTrackCount = 0; - int adaptiveSupport = RendererCapabilities.ADAPTIVE_SEAMLESS; - boolean multipleMimeTypes = false; - String firstSampleMimeType = null; - for (int i = 0; i < trackIndices.length; i++) { - int trackIndex = trackIndices[i]; - String sampleMimeType = trackGroups[rendererIndex].get(groupIndex).getFormat(trackIndex) - .sampleMimeType; - if (handledTrackCount++ == 0) { - firstSampleMimeType = sampleMimeType; - } else { - multipleMimeTypes |= !Util.areEqual(firstSampleMimeType, sampleMimeType); - } - adaptiveSupport = Math.min(adaptiveSupport, formatSupport[rendererIndex][groupIndex][i] - & RendererCapabilities.ADAPTIVE_SUPPORT_MASK); - } - return multipleMimeTypes - ? Math.min(adaptiveSupport, mixedMimeTypeAdaptiveSupport[rendererIndex]) - : adaptiveSupport; - } - - /** - * Returns the {@link TrackGroup}s not associated with any renderer. - */ - public TrackGroupArray getUnassociatedTrackGroups() { - return unassociatedTrackGroups; - } - - } - } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/TrackSelector.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/TrackSelector.java index 7bb92ad64..fce6ce519 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/TrackSelector.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/TrackSelector.java @@ -16,19 +16,74 @@ package org.telegram.messenger.exoplayer2.trackselection; import org.telegram.messenger.exoplayer2.ExoPlaybackException; +import org.telegram.messenger.exoplayer2.ExoPlayer; +import org.telegram.messenger.exoplayer2.Renderer; import org.telegram.messenger.exoplayer2.RendererCapabilities; +import org.telegram.messenger.exoplayer2.RendererConfiguration; import org.telegram.messenger.exoplayer2.source.TrackGroupArray; -/** Selects tracks to be consumed by available renderers. */ +/** + * The component of an {@link ExoPlayer} responsible for selecting tracks to be consumed by each of + * the player's {@link Renderer}s. The {@link DefaultTrackSelector} implementation should be + * suitable for most use cases. + * + *

    Interactions with the player

    + * The following interactions occur between the player and its track selector during playback. + *

    + *

      + *
    • When the player is created it will initialize the track selector by calling + * {@link #init(InvalidationListener)}.
    • + *
    • When the player needs to make a track selection it will call + * {@link #selectTracks(RendererCapabilities[], TrackGroupArray)}. This typically occurs at the + * start of playback, when the player starts to buffer a new period of the media being played, + * and when the track selector invalidates its previous selections.
    • + *
    • The player may perform a track selection well in advance of the selected tracks becoming + * active, where active is defined to mean that the renderers are actually consuming media + * corresponding to the selection that was made. For example when playing media containing + * multiple periods, the track selection for a period is made when the player starts to buffer + * that period. Hence if the player's buffering policy is to maintain a 30 second buffer, the + * selection will occur approximately 30 seconds in advance of it becoming active. In fact the + * selection may never become active, for example if the user seeks to some other period of the + * media during the 30 second gap. The player indicates to the track selector when a selection + * it has previously made becomes active by calling {@link #onSelectionActivated(Object)}.
    • + *
    • If the track selector wishes to indicate to the player that selections it has previously + * made are invalid, it can do so by calling + * {@link InvalidationListener#onTrackSelectionsInvalidated()} on the + * {@link InvalidationListener} that was passed to {@link #init(InvalidationListener)}. A + * track selector may wish to do this if its configuration has changed, for example if it now + * wishes to prefer audio tracks in a particular language. This will trigger the player to make + * new track selections. Note that the player will have to re-buffer in the case that the new + * track selection for the currently playing period differs from the one that was invalidated. + *
    • + *
    + * + *

    Renderer configuration

    + * The {@link TrackSelectorResult} returned by + * {@link #selectTracks(RendererCapabilities[], TrackGroupArray)} contains not only + * {@link TrackSelection}s for each renderer, but also {@link RendererConfiguration}s defining + * configuration parameters that the renderers should apply when consuming the corresponding media. + * Whilst it may seem counter-intuitive for a track selector to also specify renderer configuration + * information, in practice the two are tightly bound together. It may only be possible to play a + * certain combination tracks if the renderers are configured in a particular way. Equally, it may + * only be possible to configure renderers in a particular way if certain tracks are selected. Hence + * it makes sense to determined the track selection and corresponding renderer configurations in a + * single step. + * + *

    Threading model

    + * All calls made by the player into the track selector are on the player's internal playback + * thread. The track selector may call {@link InvalidationListener#onTrackSelectionsInvalidated()} + * from any thread. + */ public abstract class TrackSelector { /** - * Notified when previous selections by a {@link TrackSelector} are no longer valid. + * Notified when selections previously made by a {@link TrackSelector} are no longer valid. */ public interface InvalidationListener { /** - * Called by a {@link TrackSelector} when previous selections are no longer valid. + * Called by a {@link TrackSelector} to indicate that selections it has previously made are no + * longer valid. May be called from any thread. */ void onTrackSelectionsInvalidated(); @@ -37,16 +92,17 @@ public abstract class TrackSelector { private InvalidationListener listener; /** - * Initializes the selector. + * Called by the player to initialize the selector. * - * @param listener A listener for the selector. + * @param listener An invalidation listener that the selector can call to indicate that selections + * it has previously made are no longer valid. */ public final void init(InvalidationListener listener) { this.listener = listener; } /** - * Performs a track selection for renderers. + * Called by the player to perform a track selection. * * @param rendererCapabilities The {@link RendererCapabilities} of the renderers for which tracks * are to be selected. @@ -58,15 +114,16 @@ public abstract class TrackSelector { TrackGroupArray trackGroups) throws ExoPlaybackException; /** - * Called when a {@link TrackSelectorResult} previously generated by + * Called by the player when a {@link TrackSelectorResult} previously generated by * {@link #selectTracks(RendererCapabilities[], TrackGroupArray)} is activated. * - * @param info The value of {@link TrackSelectorResult#info} in the activated result. + * @param info The value of {@link TrackSelectorResult#info} in the activated selection. */ public abstract void onSelectionActivated(Object info); /** - * Invalidates all previously generated track selections. + * Calls {@link InvalidationListener#onTrackSelectionsInvalidated()} to invalidate all previously + * generated track selections. */ protected final void invalidate() { if (listener != null) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/TrackSelectorResult.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/TrackSelectorResult.java index 1f2c38cf6..4114260a3 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/TrackSelectorResult.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/TrackSelectorResult.java @@ -25,11 +25,11 @@ import org.telegram.messenger.exoplayer2.util.Util; public final class TrackSelectorResult { /** - * The groups provided to the {@link TrackSelector}. + * The track groups that were provided to the {@link TrackSelector}. */ public final TrackGroupArray groups; /** - * A {@link TrackSelectionArray} containing the selection for each renderer. + * A {@link TrackSelectionArray} containing the track selection for each renderer. */ public final TrackSelectionArray selections; /** @@ -43,10 +43,10 @@ public final class TrackSelectorResult { public final RendererConfiguration[] rendererConfigurations; /** - * @param groups The groups provided to the {@link TrackSelector}. + * @param groups The track groups provided to the {@link TrackSelector}. * @param selections A {@link TrackSelectionArray} containing the selection for each renderer. * @param info An opaque object that will be returned to - * {@link TrackSelector#onSelectionActivated(Object)} should the selections be activated. + * {@link TrackSelector#onSelectionActivated(Object)} should the selection be activated. * @param rendererConfigurations A {@link RendererConfiguration} for each renderer, to be used * with the selections. */ @@ -62,7 +62,7 @@ public final class TrackSelectorResult { * Returns whether this result is equivalent to {@code other} for all renderers. * * @param other The other {@link TrackSelectorResult}. May be null, in which case {@code false} - * will be returned in all cases. + * will be returned. * @return Whether this result is equivalent to {@code other} for all renderers. */ public boolean isEquivalent(TrackSelectorResult other) { @@ -83,9 +83,10 @@ public final class TrackSelectorResult { * renderer. * * @param other The other {@link TrackSelectorResult}. May be null, in which case {@code false} - * will be returned in all cases. + * will be returned. * @param index The renderer index to check for equivalence. - * @return Whether this result is equivalent to {@code other} for all renderers. + * @return Whether this result is equivalent to {@code other} for the renderer at the specified + * index. */ public boolean isEquivalent(TrackSelectorResult other, int index) { if (other == null) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ui/AspectRatioFrameLayout.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ui/AspectRatioFrameLayout.java index 719fe519a..a43d82008 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ui/AspectRatioFrameLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ui/AspectRatioFrameLayout.java @@ -22,7 +22,6 @@ import android.util.AttributeSet; import android.view.TextureView; import android.view.View; import android.widget.FrameLayout; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -35,7 +34,8 @@ public class AspectRatioFrameLayout extends FrameLayout { * Resize modes for {@link AspectRatioFrameLayout}. */ @Retention(RetentionPolicy.SOURCE) - @IntDef({RESIZE_MODE_FIT, RESIZE_MODE_FIXED_WIDTH, RESIZE_MODE_FIXED_HEIGHT, RESIZE_MODE_FILL}) + @IntDef({RESIZE_MODE_FIT, RESIZE_MODE_FIXED_WIDTH, RESIZE_MODE_FIXED_HEIGHT, RESIZE_MODE_FILL, + RESIZE_MODE_ZOOM}) public @interface ResizeMode {} /** @@ -54,6 +54,10 @@ public class AspectRatioFrameLayout extends FrameLayout { * The specified aspect ratio is ignored. */ public static final int RESIZE_MODE_FILL = 3; + /** + * Either the width or height is increased to obtain the desired aspect ratio. + */ + public static final int RESIZE_MODE_ZOOM = 4; /** * The {@link FrameLayout} will not resize itself if the fractional difference between its natural @@ -81,13 +85,11 @@ public class AspectRatioFrameLayout extends FrameLayout { resizeMode = RESIZE_MODE_FIT; } - public void setDrawingReady(boolean value) { - if (drawingReady == value) { - return; - } - drawingReady = value; - } - + /** + * Sets the aspect ratio that this view should satisfy. + * + * @param widthHeightRatio The width to height ratio. + */ public boolean isDrawingReady() { return drawingReady; } @@ -105,12 +107,11 @@ public class AspectRatioFrameLayout extends FrameLayout { } } - public float getAspectRatio() { - return videoAspectRatio; - } - - public int getVideoRotation() { - return rotation; + /** + * Returns the resize mode. + */ + public @ResizeMode int getResizeMode() { + return resizeMode; } /** @@ -125,6 +126,21 @@ public class AspectRatioFrameLayout extends FrameLayout { } } + public void setDrawingReady(boolean value) { + if (drawingReady == value) { + return; + } + drawingReady = value; + } + + public float getAspectRatio() { + return videoAspectRatio; + } + + public int getVideoRotation() { + return rotation; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); @@ -149,6 +165,13 @@ public class AspectRatioFrameLayout extends FrameLayout { case RESIZE_MODE_FIXED_HEIGHT: width = (int) (height * videoAspectRatio); break; + case RESIZE_MODE_ZOOM: + if (aspectDeformation > 0) { + width = (int) (height * videoAspectRatio); + } else { + height = (int) (width / videoAspectRatio); + } + break; default: if (aspectDeformation > 0) { height = (int) (width / videoAspectRatio); @@ -175,4 +198,5 @@ public class AspectRatioFrameLayout extends FrameLayout { } } } -} \ No newline at end of file + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ui/DebugTextViewHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ui/DebugTextViewHelper.java deleted file mode 100755 index 95530722b..000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ui/DebugTextViewHelper.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer2.ui; - -import android.widget.TextView; -import org.telegram.messenger.exoplayer2.ExoPlaybackException; -import org.telegram.messenger.exoplayer2.ExoPlayer; -import org.telegram.messenger.exoplayer2.Format; -import org.telegram.messenger.exoplayer2.PlaybackParameters; -import org.telegram.messenger.exoplayer2.SimpleExoPlayer; -import org.telegram.messenger.exoplayer2.Timeline; -import org.telegram.messenger.exoplayer2.decoder.DecoderCounters; -import org.telegram.messenger.exoplayer2.source.TrackGroupArray; -import org.telegram.messenger.exoplayer2.trackselection.TrackSelectionArray; - -/** - * A helper class for periodically updating a {@link TextView} with debug information obtained from - * a {@link SimpleExoPlayer}. - */ -public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListener { - - private static final int REFRESH_INTERVAL_MS = 1000; - - private final SimpleExoPlayer player; - private final TextView textView; - - private boolean started; - - /** - * @param player The {@link SimpleExoPlayer} from which debug information should be obtained. - * @param textView The {@link TextView} that should be updated to display the information. - */ - public DebugTextViewHelper(SimpleExoPlayer player, TextView textView) { - this.player = player; - this.textView = textView; - } - - /** - * Starts periodic updates of the {@link TextView}. Must be called from the application's main - * thread. - */ - public void start() { - if (started) { - return; - } - started = true; - player.addListener(this); - updateAndPost(); - } - - /** - * Stops periodic updates of the {@link TextView}. Must be called from the application's main - * thread. - */ - public void stop() { - if (!started) { - return; - } - started = false; - player.removeListener(this); - textView.removeCallbacks(this); - } - - // ExoPlayer.EventListener implementation. - - @Override - public void onLoadingChanged(boolean isLoading) { - // Do nothing. - } - - @Override - public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { - updateAndPost(); - } - - @Override - public void onPositionDiscontinuity() { - updateAndPost(); - } - - @Override - public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { - // Do nothing. - } - - @Override - public void onTimelineChanged(Timeline timeline, Object manifest) { - // Do nothing. - } - - @Override - public void onPlayerError(ExoPlaybackException error) { - // Do nothing. - } - - @Override - public void onTracksChanged(TrackGroupArray tracks, TrackSelectionArray selections) { - // Do nothing. - } - - // Runnable implementation. - - @Override - public void run() { - updateAndPost(); - } - - // Private methods. - - private void updateAndPost() { - textView.setText(getPlayerStateString() + getPlayerWindowIndexString() + getVideoString() - + getAudioString()); - textView.removeCallbacks(this); - textView.postDelayed(this, REFRESH_INTERVAL_MS); - } - - private String getPlayerStateString() { - String text = "playWhenReady:" + player.getPlayWhenReady() + " playbackState:"; - switch (player.getPlaybackState()) { - case ExoPlayer.STATE_BUFFERING: - text += "buffering"; - break; - case ExoPlayer.STATE_ENDED: - text += "ended"; - break; - case ExoPlayer.STATE_IDLE: - text += "idle"; - break; - case ExoPlayer.STATE_READY: - text += "ready"; - break; - default: - text += "unknown"; - break; - } - return text; - } - - private String getPlayerWindowIndexString() { - return " window:" + player.getCurrentWindowIndex(); - } - - private String getVideoString() { - Format format = player.getVideoFormat(); - if (format == null) { - return ""; - } - return "\n" + format.sampleMimeType + "(id:" + format.id + " r:" + format.width + "x" - + format.height + getDecoderCountersBufferCountString(player.getVideoDecoderCounters()) - + ")"; - } - - private String getAudioString() { - Format format = player.getAudioFormat(); - if (format == null) { - return ""; - } - return "\n" + format.sampleMimeType + "(id:" + format.id + " hz:" + format.sampleRate + " ch:" - + format.channelCount - + getDecoderCountersBufferCountString(player.getAudioDecoderCounters()) + ")"; - } - - private static String getDecoderCountersBufferCountString(DecoderCounters counters) { - if (counters == null) { - return ""; - } - counters.ensureUpdated(); - return " rb:" + counters.renderedOutputBufferCount - + " sb:" + counters.skippedOutputBufferCount - + " db:" + counters.droppedOutputBufferCount - + " mcdb:" + counters.maxConsecutiveDroppedOutputBufferCount; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ui/SubtitlePainter.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ui/SubtitlePainter.java deleted file mode 100755 index 45522d768..000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ui/SubtitlePainter.java +++ /dev/null @@ -1,428 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer2.ui; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Paint.Join; -import android.graphics.Paint.Style; -import android.graphics.Rect; -import android.graphics.RectF; -import android.text.Layout.Alignment; -import android.text.SpannableStringBuilder; -import android.text.StaticLayout; -import android.text.TextPaint; -import android.text.TextUtils; -import android.text.style.AbsoluteSizeSpan; -import android.text.style.RelativeSizeSpan; -import android.util.DisplayMetrics; -import android.util.Log; -import org.telegram.messenger.exoplayer2.text.CaptionStyleCompat; -import org.telegram.messenger.exoplayer2.text.Cue; -import org.telegram.messenger.exoplayer2.util.Util; - -/** - * Paints subtitle {@link Cue}s. - */ -/* package */ final class SubtitlePainter { - - private static final String TAG = "SubtitlePainter"; - - /** - * Ratio of inner padding to font size. - */ - private static final float INNER_PADDING_RATIO = 0.125f; - - /** - * Temporary rectangle used for computing line bounds. - */ - private final RectF lineBounds = new RectF(); - - // Styled dimensions. - private final float cornerRadius; - private final float outlineWidth; - private final float shadowRadius; - private final float shadowOffset; - private final float spacingMult; - private final float spacingAdd; - - private final TextPaint textPaint; - private final Paint paint; - - // Previous input variables. - private CharSequence cueText; - private Alignment cueTextAlignment; - private Bitmap cueBitmap; - private float cueLine; - @Cue.LineType - private int cueLineType; - @Cue.AnchorType - private int cueLineAnchor; - private float cuePosition; - @Cue.AnchorType - private int cuePositionAnchor; - private float cueSize; - private float cueBitmapHeight; - private boolean applyEmbeddedStyles; - private boolean applyEmbeddedFontSizes; - private int foregroundColor; - private int backgroundColor; - private int windowColor; - private int edgeColor; - @CaptionStyleCompat.EdgeType - private int edgeType; - private float textSizePx; - private float bottomPaddingFraction; - private int parentLeft; - private int parentTop; - private int parentRight; - private int parentBottom; - - // Derived drawing variables. - private StaticLayout textLayout; - private int textLeft; - private int textTop; - private int textPaddingX; - private Rect bitmapRect; - - @SuppressWarnings("ResourceType") - public SubtitlePainter(Context context) { - int[] viewAttr = {android.R.attr.lineSpacingExtra, android.R.attr.lineSpacingMultiplier}; - TypedArray styledAttributes = context.obtainStyledAttributes(null, viewAttr, 0, 0); - spacingAdd = styledAttributes.getDimensionPixelSize(0, 0); - spacingMult = styledAttributes.getFloat(1, 1); - styledAttributes.recycle(); - - Resources resources = context.getResources(); - DisplayMetrics displayMetrics = resources.getDisplayMetrics(); - int twoDpInPx = Math.round((2f * displayMetrics.densityDpi) / DisplayMetrics.DENSITY_DEFAULT); - cornerRadius = twoDpInPx; - outlineWidth = twoDpInPx; - shadowRadius = twoDpInPx; - shadowOffset = twoDpInPx; - - textPaint = new TextPaint(); - textPaint.setAntiAlias(true); - textPaint.setSubpixelText(true); - - paint = new Paint(); - paint.setAntiAlias(true); - paint.setStyle(Style.FILL); - } - - /** - * Draws the provided {@link Cue} into a canvas with the specified styling. - *

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

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

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

    - * Note that this padding is applied in addition to any standard view padding. - * - * @param bottomPaddingFraction The bottom padding fraction. - */ - public void setBottomPaddingFraction(float bottomPaddingFraction) { - if (this.bottomPaddingFraction == bottomPaddingFraction) { - return; - } - this.bottomPaddingFraction = bottomPaddingFraction; - // Invalidate to trigger drawing. - invalidate(); - } - - @Override - public void dispatchDraw(Canvas canvas) { - int cueCount = (cues == null) ? 0 : cues.size(); - int rawTop = getTop(); - int rawBottom = getBottom(); - - // Calculate the bounds after padding is taken into account. - int left = getLeft() + getPaddingLeft(); - int top = rawTop + getPaddingTop(); - int right = getRight() + getPaddingRight(); - int bottom = rawBottom - getPaddingBottom(); - if (bottom <= top || right <= left) { - // No space to draw subtitles. - return; - } - - float textSizePx = textSizeType == ABSOLUTE ? textSize - : textSize * (textSizeType == FRACTIONAL ? (bottom - top) : (rawBottom - rawTop)); - if (textSizePx <= 0) { - // Text has no height. - return; - } - - for (int i = 0; i < cueCount; i++) { - painters.get(i).draw(cues.get(i), applyEmbeddedStyles, applyEmbeddedFontSizes, style, - textSizePx, bottomPaddingFraction, canvas, left, top, right, bottom); - } - } - - @TargetApi(19) - private float getUserCaptionFontScaleV19() { - CaptioningManager captioningManager = - (CaptioningManager) getContext().getSystemService(Context.CAPTIONING_SERVICE); - return captioningManager.getFontScale(); - } - - @TargetApi(19) - private CaptionStyleCompat getUserCaptionStyleV19() { - CaptioningManager captioningManager = - (CaptioningManager) getContext().getSystemService(Context.CAPTIONING_SERVICE); - return CaptionStyleCompat.createFromCaptionStyle(captioningManager.getUserStyle()); - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ui/TimeBar.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ui/TimeBar.java deleted file mode 100755 index 099c41280..000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ui/TimeBar.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer2.ui; - -import android.support.annotation.Nullable; -import android.view.View; - -/** - * Interface for time bar views that can display a playback position, buffered position, duration - * and ad markers, and that have a listener for scrubbing (seeking) events. - */ -public interface TimeBar { - - /** - * @see View#isEnabled() - */ - void setEnabled(boolean enabled); - - /** - * Sets the listener for the scrubbing events. - * - * @param listener The listener for scrubbing events. - */ - void setListener(OnScrubListener listener); - - /** - * Sets the position increment for key presses and accessibility actions, in milliseconds. - *

    - * Clears any increment specified in a preceding call to {@link #setKeyCountIncrement(int)}. - * - * @param time The time increment, in milliseconds. - */ - void setKeyTimeIncrement(long time); - - /** - * Sets the position increment for key presses and accessibility actions, as a number of - * increments that divide the duration of the media. For example, passing 20 will cause key - * presses to increment/decrement the position by 1/20th of the duration (if known). - *

    - * Clears any increment specified in a preceding call to {@link #setKeyTimeIncrement(long)}. - * - * @param count The number of increments that divide the duration of the media. - */ - void setKeyCountIncrement(int count); - - /** - * Sets the current position. - * - * @param position The current position to show, in milliseconds. - */ - void setPosition(long position); - - /** - * Sets the buffered position. - * - * @param bufferedPosition The current buffered position to show, in milliseconds. - */ - void setBufferedPosition(long bufferedPosition); - - /** - * Sets the duration. - * - * @param duration The duration to show, in milliseconds. - */ - void setDuration(long duration); - - /** - * Sets the times of ad breaks. - * - * @param adBreakTimesMs An array where the first {@code adBreakCount} elements are the times of - * ad breaks in milliseconds. May be {@code null} if there are no ad breaks. - * @param adBreakCount The number of ad breaks. - */ - void setAdBreakTimesMs(@Nullable long[] adBreakTimesMs, int adBreakCount); - - /** - * Listener for scrubbing events. - */ - interface OnScrubListener { - - /** - * Called when the user starts moving the scrubber. - * - * @param timeBar The time bar. - */ - void onScrubStart(TimeBar timeBar); - - /** - * Called when the user moves the scrubber. - * - * @param timeBar The time bar. - * @param position The position of the scrubber, in milliseconds. - */ - void onScrubMove(TimeBar timeBar, long position); - - /** - * Called when the user stops moving the scrubber. - * - * @param timeBar The time bar. - * @param position The position of the scrubber, in milliseconds. - * @param canceled Whether scrubbing was canceled. - */ - void onScrubStop(TimeBar timeBar, long position, boolean canceled); - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/Allocation.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/Allocation.java index 4f44cb896..ea21f31f9 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/Allocation.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/Allocation.java @@ -25,29 +25,22 @@ public final class Allocation { /** * The array containing the allocated space. The allocated space might not be at the start of the - * array, and so {@link #translateOffset(int)} method must be used when indexing into it. + * array, and so {@link #offset} must be used when indexing into it. */ public final byte[] data; - private final int offset; + /** + * The offset of the allocated space in {@link #data}. + */ + public final int offset; /** * @param data The array containing the allocated space. - * @param offset The offset of the allocated space within the array. + * @param offset The offset of the allocated space in {@code data}. */ public Allocation(byte[] data, int offset) { this.data = data; this.offset = offset; } - /** - * Translates a zero-based offset into the allocation to the corresponding {@link #data} offset. - * - * @param offset The zero-based offset to translate. - * @return The corresponding offset in {@link #data}. - */ - public int translateOffset(int offset) { - return this.offset + offset; - } - } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/ContentDataSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/ContentDataSource.java index ab663a8d3..e96ebc753 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/ContentDataSource.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/ContentDataSource.java @@ -22,6 +22,7 @@ import android.net.Uri; import org.telegram.messenger.exoplayer2.C; import java.io.EOFException; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -71,9 +72,13 @@ public final class ContentDataSource implements DataSource { try { uri = dataSpec.uri; assetFileDescriptor = resolver.openAssetFileDescriptor(uri, "r"); + if (assetFileDescriptor == null) { + throw new FileNotFoundException("Could not open file descriptor for: " + uri); + } inputStream = new FileInputStream(assetFileDescriptor.getFileDescriptor()); - long skipped = inputStream.skip(dataSpec.position); - if (skipped < dataSpec.position) { + long assetStartOffset = assetFileDescriptor.getStartOffset(); + long skipped = inputStream.skip(assetStartOffset + dataSpec.position) - assetStartOffset; + if (skipped != dataSpec.position) { // We expect the skip to be satisfied in full. If it isn't then we're probably trying to // skip beyond the end of the data. throw new EOFException(); @@ -81,12 +86,18 @@ public final class ContentDataSource implements DataSource { if (dataSpec.length != C.LENGTH_UNSET) { bytesRemaining = dataSpec.length; } else { - bytesRemaining = inputStream.available(); - if (bytesRemaining == 0) { - // FileInputStream.available() returns 0 if the remaining length cannot be determined, or - // if it's greater than Integer.MAX_VALUE. We don't know the true length in either case, - // so treat as unbounded. - bytesRemaining = C.LENGTH_UNSET; + long assetFileDescriptorLength = assetFileDescriptor.getLength(); + if (assetFileDescriptorLength == AssetFileDescriptor.UNKNOWN_LENGTH) { + // The asset must extend to the end of the file. + bytesRemaining = inputStream.available(); + if (bytesRemaining == 0) { + // FileInputStream.available() returns 0 if the remaining length cannot be determined, + // or if it's greater than Integer.MAX_VALUE. We don't know the true length in either + // case, so treat as unbounded. + bytesRemaining = C.LENGTH_UNSET; + } + } else { + bytesRemaining = assetFileDescriptorLength - skipped; } } } catch (IOException e) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DataSpec.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DataSpec.java index ebc88d6da..84954f144 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DataSpec.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DataSpec.java @@ -68,7 +68,7 @@ public final class DataSpec { * The position of the data when read from {@link #uri}. *

    * Always equal to {@link #absoluteStreamPosition} unless the {@link #uri} defines the location - * of a subset of the underyling data. + * of a subset of the underlying data. */ public final long position; /** @@ -187,4 +187,31 @@ public final class DataSpec { + ", " + position + ", " + length + ", " + key + ", " + flags + "]"; } + /** + * Returns a {@link DataSpec} that represents a subrange of the data defined by this DataSpec. The + * subrange includes data from the offset up to the end of this DataSpec. + * + * @param offset The offset of the subrange. + * @return A {@link DataSpec} that represents a subrange of the data defined by this DataSpec. + */ + public DataSpec subrange(long offset) { + return subrange(offset, length == C.LENGTH_UNSET ? C.LENGTH_UNSET : length - offset); + } + + /** + * Returns a {@link DataSpec} that represents a subrange of the data defined by this DataSpec. + * + * @param offset The offset of the subrange. + * @param length The length of the subrange. + * @return A {@link DataSpec} that represents a subrange of the data defined by this DataSpec. + */ + public DataSpec subrange(long offset, long length) { + if (offset == 0 && this.length == length) { + return this; + } else { + return new DataSpec(uri, postBody, absoluteStreamPosition + offset, position + offset, length, + key, flags); + } + } + } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DefaultBandwidthMeter.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DefaultBandwidthMeter.java index a35f4a308..b24c4e161 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DefaultBandwidthMeter.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DefaultBandwidthMeter.java @@ -16,8 +16,8 @@ package org.telegram.messenger.exoplayer2.upstream; import android.os.Handler; -import android.os.SystemClock; import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.Clock; import org.telegram.messenger.exoplayer2.util.SlidingPercentile; /** @@ -37,6 +37,7 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList private final Handler eventHandler; private final EventListener eventListener; private final SlidingPercentile slidingPercentile; + private final Clock clock; private int streamCount; private long sampleStartTimeMs; @@ -55,9 +56,15 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList } public DefaultBandwidthMeter(Handler eventHandler, EventListener eventListener, int maxWeight) { + this(eventHandler, eventListener, maxWeight, Clock.DEFAULT); + } + + public DefaultBandwidthMeter(Handler eventHandler, EventListener eventListener, int maxWeight, + Clock clock) { this.eventHandler = eventHandler; this.eventListener = eventListener; this.slidingPercentile = new SlidingPercentile(maxWeight); + this.clock = clock; bitrateEstimate = NO_ESTIMATE; } @@ -69,7 +76,7 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList @Override public synchronized void onTransferStart(Object source, DataSpec dataSpec) { if (streamCount == 0) { - sampleStartTimeMs = SystemClock.elapsedRealtime(); + sampleStartTimeMs = clock.elapsedRealtime(); } streamCount++; } @@ -82,7 +89,7 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList @Override public synchronized void onTransferEnd(Object source) { Assertions.checkState(streamCount > 0); - long nowMs = SystemClock.elapsedRealtime(); + long nowMs = clock.elapsedRealtime(); int sampleElapsedTimeMs = (int) (nowMs - sampleStartTimeMs); totalElapsedTimeMs += sampleElapsedTimeMs; totalBytesTransferred += sampleBytesTransferred; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DefaultDataSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DefaultDataSource.java index 2a13b5421..3988f3344 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DefaultDataSource.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DefaultDataSource.java @@ -17,9 +17,11 @@ package org.telegram.messenger.exoplayer2.upstream; import android.content.Context; import android.net.Uri; +import android.util.Log; import org.telegram.messenger.exoplayer2.util.Assertions; import org.telegram.messenger.exoplayer2.util.Util; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; /** * A {@link DataSource} that supports multiple URI schemes. The supported schemes are: @@ -30,6 +32,8 @@ import java.io.IOException; * local file URI). *

  • asset: For fetching data from an asset in the application's apk (e.g. asset:///media.mp4). *
  • content: For fetching data from a content URI (e.g. content://authority/path/123). + *
  • rtmp: For fetching data over RTMP. Only supported if the project using ExoPlayer has an + * explicit dependency on ExoPlayer's RTMP extension.
  • *
  • http(s): For fetching data over HTTP and HTTPS (e.g. https://www.something.com/media.mp4), if * constructed using {@link #DefaultDataSource(Context, TransferListener, String, boolean)}, or * any other schemes supported by a base data source if constructed using @@ -38,13 +42,22 @@ import java.io.IOException; */ public final class DefaultDataSource implements DataSource { + private static final String TAG = "DefaultDataSource"; + private static final String SCHEME_ASSET = "asset"; private static final String SCHEME_CONTENT = "content"; + private static final String SCHEME_RTMP = "rtmp"; + + private final Context context; + private final TransferListener listener; private final DataSource baseDataSource; - private final DataSource fileDataSource; - private final DataSource assetDataSource; - private final DataSource contentDataSource; + + // Lazily initialized. + private DataSource fileDataSource; + private DataSource assetDataSource; + private DataSource contentDataSource; + private DataSource rtmpDataSource; private DataSource dataSource; @@ -95,10 +108,9 @@ public final class DefaultDataSource implements DataSource { */ public DefaultDataSource(Context context, TransferListener listener, DataSource baseDataSource) { + this.context = context.getApplicationContext(); + this.listener = listener; this.baseDataSource = Assertions.checkNotNull(baseDataSource); - this.fileDataSource = new FileDataSource(listener); - this.assetDataSource = new AssetDataSource(context, listener); - this.contentDataSource = new ContentDataSource(context, listener); } @Override @@ -108,14 +120,16 @@ public final class DefaultDataSource implements DataSource { String scheme = dataSpec.uri.getScheme(); if (Util.isLocalFileUri(dataSpec.uri)) { if (dataSpec.uri.getPath().startsWith("/android_asset/")) { - dataSource = assetDataSource; + dataSource = getAssetDataSource(); } else { - dataSource = fileDataSource; + dataSource = getFileDataSource(); } } else if (SCHEME_ASSET.equals(scheme)) { - dataSource = assetDataSource; + dataSource = getAssetDataSource(); } else if (SCHEME_CONTENT.equals(scheme)) { - dataSource = contentDataSource; + dataSource = getContentDataSource(); + } else if (SCHEME_RTMP.equals(scheme)) { + dataSource = getRtmpDataSource(); } else { dataSource = baseDataSource; } @@ -144,4 +158,48 @@ public final class DefaultDataSource implements DataSource { } } + private DataSource getFileDataSource() { + if (fileDataSource == null) { + fileDataSource = new FileDataSource(listener); + } + return fileDataSource; + } + + private DataSource getAssetDataSource() { + if (assetDataSource == null) { + assetDataSource = new AssetDataSource(context, listener); + } + return assetDataSource; + } + + private DataSource getContentDataSource() { + if (contentDataSource == null) { + contentDataSource = new ContentDataSource(context, listener); + } + return contentDataSource; + } + + private DataSource getRtmpDataSource() { + if (rtmpDataSource == null) { + try { + Class clazz = Class.forName("org.telegram.messenger.exoplayer2.ext.rtmp.RtmpDataSource"); + rtmpDataSource = (DataSource) clazz.getDeclaredConstructor().newInstance(); + } catch (ClassNotFoundException e) { + Log.w(TAG, "Attempting to play RTMP stream without depending on the RTMP extension"); + } catch (InstantiationException e) { + Log.e(TAG, "Error instantiating RtmpDataSource", e); + } catch (IllegalAccessException e) { + Log.e(TAG, "Error instantiating RtmpDataSource", e); + } catch (NoSuchMethodException e) { + Log.e(TAG, "Error instantiating RtmpDataSource", e); + } catch (InvocationTargetException e) { + Log.e(TAG, "Error instantiating RtmpDataSource", e); + } + if (rtmpDataSource == null) { + rtmpDataSource = baseDataSource; + } + } + return rtmpDataSource; + } + } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/Loader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/Loader.java index 3ed8b84f9..247b9e8d7 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/Loader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/Loader.java @@ -33,11 +33,11 @@ import java.util.concurrent.ExecutorService; public final class Loader implements LoaderErrorThrower { /** - * Thrown when an unexpected exception is encountered during loading. + * Thrown when an unexpected exception or error is encountered during loading. */ public static final class UnexpectedLoaderException extends IOException { - public UnexpectedLoaderException(Exception cause) { + public UnexpectedLoaderException(Throwable cause) { super("Unexpected " + cause.getClass().getSimpleName() + ": " + cause.getMessage(), cause); } @@ -119,17 +119,23 @@ public final class Loader implements LoaderErrorThrower { } + /** + * A callback to be notified when a {@link Loader} has finished being released. + */ + public interface ReleaseCallback { + + /** + * Called when the {@link Loader} has finished being released. + */ + void onLoaderReleased(); + + } + public static final int RETRY = 0; public static final int RETRY_RESET_ERROR_COUNT = 1; public static final int DONT_RETRY = 2; public static final int DONT_RETRY_FATAL = 3; - private static final int MSG_START = 0; - private static final int MSG_CANCEL = 1; - private static final int MSG_END_OF_SOURCE = 2; - private static final int MSG_IO_EXCEPTION = 3; - private static final int MSG_FATAL_ERROR = 4; - private final ExecutorService downloadExecutorService; private LoadTask currentTask; @@ -150,7 +156,7 @@ public final class Loader implements LoaderErrorThrower { * * @param The type of the loadable. * @param loadable The {@link Loadable} to load. - * @param callback A callback to called when the load ends. + * @param callback A callback to be called when the load ends. * @param defaultMinRetryCount The minimum number of times the load must be retried before * {@link #maybeThrowError()} will propagate an error. * @throws IllegalStateException If the calling thread does not have an associated {@link Looper}. @@ -188,20 +194,28 @@ public final class Loader implements LoaderErrorThrower { } /** - * Releases the {@link Loader}, running {@code postLoadAction} on its thread. This method should - * be called when the {@link Loader} is no longer required. + * Releases the {@link Loader}. This method should be called when the {@link Loader} is no longer + * required. * - * @param postLoadAction A {@link Runnable} to run on the loader's thread when - * {@link Loadable#load()} is no longer running. + * @param callback A callback to be called when the release ends. Will be called synchronously + * from this method if no load is in progress, or asynchronously once the load has been + * canceled otherwise. May be null. + * @return True if {@code callback} was called synchronously. False if it will be called + * asynchronously or if {@code callback} is null. */ - public void release(Runnable postLoadAction) { + public boolean release(ReleaseCallback callback) { + boolean callbackInvoked = false; if (currentTask != null) { currentTask.cancel(true); - } - if (postLoadAction != null) { - downloadExecutorService.execute(postLoadAction); + if (callback != null) { + downloadExecutorService.execute(new ReleaseTask(callback)); + } + } else if (callback != null) { + callback.onLoaderReleased(); + callbackInvoked = true; } downloadExecutorService.shutdown(); + return callbackInvoked; } // LoaderErrorThrower implementation. @@ -228,6 +242,12 @@ public final class Loader implements LoaderErrorThrower { private static final String TAG = "LoadTask"; + private static final int MSG_START = 0; + private static final int MSG_CANCEL = 1; + private static final int MSG_END_OF_SOURCE = 2; + private static final int MSG_IO_EXCEPTION = 3; + private static final int MSG_FATAL_ERROR = 4; + private final T loadable; private final Loader.Callback callback; public final int defaultMinRetryCount; @@ -316,6 +336,14 @@ public final class Loader implements LoaderErrorThrower { if (!released) { obtainMessage(MSG_IO_EXCEPTION, new UnexpectedLoaderException(e)).sendToTarget(); } + } catch (OutOfMemoryError e) { + // This can occur if a stream is malformed in a way that causes an extractor to think it + // needs to allocate a large amount of memory. We don't want the process to die in this + // case, but we do want the playback to fail. + Log.e(TAG, "OutOfMemory error loading stream", e); + if (!released) { + obtainMessage(MSG_IO_EXCEPTION, new UnexpectedLoaderException(e)).sendToTarget(); + } } catch (Error e) { // We'd hope that the platform would kill the process if an Error is thrown here, but the // executor may catch the error (b/20616433). Throw it here, but also pass and throw it from @@ -382,4 +410,24 @@ public final class Loader implements LoaderErrorThrower { } + private static final class ReleaseTask extends Handler implements Runnable { + + private final ReleaseCallback callback; + + public ReleaseTask(ReleaseCallback callback) { + this.callback = callback; + } + + @Override + public void run() { + sendEmptyMessage(0); + } + + @Override + public void handleMessage(Message msg) { + callback.onLoaderReleased(); + } + + } + } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheDataSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheDataSource.java index f55aba193..70869e9b3 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheDataSource.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheDataSource.java @@ -54,8 +54,8 @@ public final class CacheDataSource implements DataSource { FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}) public @interface Flags {} /** - * A flag indicating whether we will block reads if the cache key is locked. If this flag is - * set, then we will read from upstream if the cache key is locked. + * A flag indicating whether we will block reads if the cache key is locked. If unset then data is + * read from upstream if the cache key is locked, regardless of whether the data is cached. */ public static final int FLAG_BLOCK_ON_CACHE = 1 << 0; @@ -110,7 +110,23 @@ public final class CacheDataSource implements DataSource { /** * Constructs an instance with default {@link DataSource} and {@link DataSink} instances for - * reading and writing the cache and with {@link #DEFAULT_MAX_CACHE_FILE_SIZE}. + * reading and writing the cache. + * + * @param cache The cache. + * @param upstream A {@link DataSource} for reading data not in the cache. + */ + public CacheDataSource(Cache cache, DataSource upstream) { + this(cache, upstream, 0, DEFAULT_MAX_CACHE_FILE_SIZE); + } + + /** + * Constructs an instance with default {@link DataSource} and {@link DataSink} instances for + * reading and writing the cache. + * + * @param cache The cache. + * @param upstream A {@link DataSource} for reading data not in the cache. + * @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE}, {@link #FLAG_IGNORE_CACHE_ON_ERROR} + * and {@link #FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}, or 0. */ public CacheDataSource(Cache cache, DataSource upstream, @Flags int flags) { this(cache, upstream, flags, DEFAULT_MAX_CACHE_FILE_SIZE); @@ -123,8 +139,8 @@ public final class CacheDataSource implements DataSource { * * @param cache The cache. * @param upstream A {@link DataSource} for reading data not in the cache. - * @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE} and {@link - * #FLAG_IGNORE_CACHE_ON_ERROR} or 0. + * @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE}, {@link #FLAG_IGNORE_CACHE_ON_ERROR} + * and {@link #FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}, or 0. * @param maxCacheFileSize The maximum size of a cache file, in bytes. If the cached data size * exceeds this value, then the data will be fragmented into multiple cache files. The * finer-grained this is the finer-grained the eviction policy can be. @@ -145,8 +161,8 @@ public final class CacheDataSource implements DataSource { * @param cacheReadDataSource A {@link DataSource} for reading data from the cache. * @param cacheWriteDataSink A {@link DataSink} for writing data to the cache. If null, cache is * accessed read-only. - * @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE} and {@link - * #FLAG_IGNORE_CACHE_ON_ERROR} or 0. + * @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE}, {@link #FLAG_IGNORE_CACHE_ON_ERROR} + * and {@link #FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}, or 0. * @param eventListener An optional {@link EventListener} to receive events. */ public CacheDataSource(Cache cache, DataSource upstream, DataSource cacheReadDataSource, diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheDataSourceFactory.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheDataSourceFactory.java index 173099e21..beba2714c 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheDataSourceFactory.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheDataSourceFactory.java @@ -33,18 +33,26 @@ public final class CacheDataSourceFactory implements DataSource.Factory { private final int flags; private final EventListener eventListener; + /** + * @see CacheDataSource#CacheDataSource(Cache, DataSource) + */ + public CacheDataSourceFactory(Cache cache, DataSource.Factory upstreamFactory) { + this(cache, upstreamFactory, 0); + } + /** * @see CacheDataSource#CacheDataSource(Cache, DataSource, int) */ - public CacheDataSourceFactory(Cache cache, DataSource.Factory upstreamFactory, int flags) { + public CacheDataSourceFactory(Cache cache, DataSource.Factory upstreamFactory, + @CacheDataSource.Flags int flags) { this(cache, upstreamFactory, flags, CacheDataSource.DEFAULT_MAX_CACHE_FILE_SIZE); } /** * @see CacheDataSource#CacheDataSource(Cache, DataSource, int, long) */ - public CacheDataSourceFactory(Cache cache, DataSource.Factory upstreamFactory, int flags, - long maxCacheFileSize) { + public CacheDataSourceFactory(Cache cache, DataSource.Factory upstreamFactory, + @CacheDataSource.Flags int flags, long maxCacheFileSize) { this(cache, upstreamFactory, new FileDataSourceFactory(), new CacheDataSinkFactory(cache, maxCacheFileSize), flags, null); } @@ -54,8 +62,8 @@ public final class CacheDataSourceFactory implements DataSource.Factory { * EventListener) */ public CacheDataSourceFactory(Cache cache, Factory upstreamFactory, - Factory cacheReadDataSourceFactory, - DataSink.Factory cacheWriteDataSinkFactory, int flags, EventListener eventListener) { + Factory cacheReadDataSourceFactory, DataSink.Factory cacheWriteDataSinkFactory, + @CacheDataSource.Flags int flags, EventListener eventListener) { this.cache = cache; this.upstreamFactory = upstreamFactory; this.cacheReadDataSourceFactory = cacheReadDataSourceFactory; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheUtil.java index 91e36336e..00ff44785 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheUtil.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheUtil.java @@ -1,16 +1,16 @@ /* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and + * See the License for the specific language governing permissions and * limitations under the License. */ package org.telegram.messenger.exoplayer2.upstream.cache; @@ -22,28 +22,36 @@ import org.telegram.messenger.exoplayer2.upstream.DataSpec; import org.telegram.messenger.exoplayer2.util.Assertions; import org.telegram.messenger.exoplayer2.util.PriorityTaskManager; import org.telegram.messenger.exoplayer2.util.Util; +import java.io.EOFException; import java.io.IOException; import java.util.NavigableSet; /** * Caching related utility methods. */ +@SuppressWarnings({"NonAtomicVolatileUpdate", "NonAtomicOperationOnVolatileField"}) public final class CacheUtil { - /** Holds the counters used during caching. */ + /** Counters used during caching. */ public static class CachingCounters { - /** Total number of already cached bytes. */ - public long alreadyCachedBytes; + /** The number of bytes already in the cache. */ + public volatile long alreadyCachedBytes; + /** The number of newly cached bytes. */ + public volatile long newlyCachedBytes; + /** The length of the content being cached in bytes, or {@link C#LENGTH_UNSET} if unknown. */ + public volatile long contentLength = C.LENGTH_UNSET; + /** - * Total number of downloaded bytes. - * - *

    {@link #getCached(DataSpec, Cache, CachingCounters)} sets it to the count of the missing - * bytes or to {@link C#LENGTH_UNSET} if {@code dataSpec} is unbounded and content length isn't - * available in the {@code cache}. + * Returns the sum of {@link #alreadyCachedBytes} and {@link #newlyCachedBytes}. */ - public long downloadedBytes; + public long totalCachedBytes() { + return alreadyCachedBytes + newlyCachedBytes; + } } + /** Default buffer size to be used while caching. */ + public static final int DEFAULT_BUFFER_SIZE_BYTES = 128 * 1024; + /** * Generates a cache key out of the given {@link Uri}. * @@ -64,26 +72,57 @@ public final class CacheUtil { } /** - * Returns already cached and missing bytes in the {@cache} for the data defined by {@code - * dataSpec}. + * Sets a {@link CachingCounters} to contain the number of bytes already downloaded and the + * length for the content defined by a {@code dataSpec}. {@link CachingCounters#newlyCachedBytes} + * is reset to 0. * * @param dataSpec Defines the data to be checked. * @param cache A {@link Cache} which has the data. - * @param counters The counters to be set. If null a new {@link CachingCounters} is created and - * used. - * @return The used {@link CachingCounters} instance. + * @param counters The {@link CachingCounters} to update. */ - public static CachingCounters getCached(DataSpec dataSpec, Cache cache, - CachingCounters counters) { - try { - return internalCache(dataSpec, cache, null, null, null, 0, counters); - } catch (IOException | InterruptedException e) { - throw new IllegalStateException(e); + public static void getCached(DataSpec dataSpec, Cache cache, CachingCounters counters) { + String key = getKey(dataSpec); + long start = dataSpec.absoluteStreamPosition; + long left = dataSpec.length != C.LENGTH_UNSET ? dataSpec.length : cache.getContentLength(key); + counters.contentLength = left; + counters.alreadyCachedBytes = 0; + counters.newlyCachedBytes = 0; + while (left != 0) { + long blockLength = cache.getCachedBytes(key, start, + left != C.LENGTH_UNSET ? left : Long.MAX_VALUE); + if (blockLength > 0) { + counters.alreadyCachedBytes += blockLength; + } else { + blockLength = -blockLength; + if (blockLength == Long.MAX_VALUE) { + return; + } + } + start += blockLength; + left -= left == C.LENGTH_UNSET ? 0 : blockLength; } } /** - * Caches the data defined by {@code dataSpec} while skipping already cached data. + * Caches the data defined by {@code dataSpec}, skipping already cached data. Caching stops early + * if the end of the input is reached. + * + * @param dataSpec Defines the data to be cached. + * @param cache A {@link Cache} to store the data. + * @param upstream A {@link DataSource} for reading data not in the cache. + * @param counters Counters to update during caching. + * @throws IOException If an error occurs reading from the source. + * @throws InterruptedException If the thread was interrupted. + */ + public static void cache(DataSpec dataSpec, Cache cache, DataSource upstream, + CachingCounters counters) throws IOException, InterruptedException { + cache(dataSpec, cache, new CacheDataSource(cache, upstream), + new byte[DEFAULT_BUFFER_SIZE_BYTES], null, 0, counters, false); + } + + /** + * Caches the data defined by {@code dataSpec} while skipping already cached data. Caching stops + * early if end of input is reached and {@code enableEOFException} is false. * * @param dataSpec Defines the data to be cached. * @param cache A {@link Cache} to store the data. @@ -92,123 +131,109 @@ public final class CacheUtil { * @param priorityTaskManager If not null it's used to check whether it is allowed to proceed with * caching. * @param priority The priority of this task. Used with {@code priorityTaskManager}. - * @param counters The counters to be set during caching. If not null its values reset to - * zero before using. If null a new {@link CachingCounters} is created and used. - * @return The used {@link CachingCounters} instance. + * @param counters Counters to update during caching. + * @param enableEOFException Whether to throw an {@link EOFException} if end of input has been + * reached unexpectedly. * @throws IOException If an error occurs reading from the source. * @throws InterruptedException If the thread was interrupted. */ - public static CachingCounters cache(DataSpec dataSpec, Cache cache, CacheDataSource dataSource, + public static void cache(DataSpec dataSpec, Cache cache, CacheDataSource dataSource, byte[] buffer, PriorityTaskManager priorityTaskManager, int priority, - CachingCounters counters) throws IOException, InterruptedException { + CachingCounters counters, boolean enableEOFException) + throws IOException, InterruptedException { Assertions.checkNotNull(dataSource); Assertions.checkNotNull(buffer); - return internalCache(dataSpec, cache, dataSource, buffer, priorityTaskManager, priority, - counters); - } - /** - * Caches the data defined by {@code dataSpec} while skipping already cached data. If {@code - * dataSource} or {@code buffer} is null performs a dry run. - * - * @param dataSpec Defines the data to be cached. - * @param cache A {@link Cache} to store the data. - * @param dataSource A {@link CacheDataSource} that works on the {@code cache}. If null a dry run - * is performed. - * @param buffer The buffer to be used while caching. If null a dry run is performed. - * @param priorityTaskManager If not null it's used to check whether it is allowed to proceed with - * caching. - * @param priority The priority of this task. Used with {@code priorityTaskManager}. - * @param counters The counters to be set during caching. If not null its values reset to - * zero before using. If null a new {@link CachingCounters} is created and used. - * @return The used {@link CachingCounters} instance. - * @throws IOException If not dry run and an error occurs reading from the source. - * @throws InterruptedException If not dry run and the thread was interrupted. - */ - private static CachingCounters internalCache(DataSpec dataSpec, Cache cache, - CacheDataSource dataSource, byte[] buffer, PriorityTaskManager priorityTaskManager, - int priority, CachingCounters counters) throws IOException, InterruptedException { - long start = dataSpec.position; - long left = dataSpec.length; - String key = getKey(dataSpec); - if (left == C.LENGTH_UNSET) { - left = cache.getContentLength(key); - if (left == C.LENGTH_UNSET) { - left = Long.MAX_VALUE; - } - } - if (counters == null) { - counters = new CachingCounters(); + if (counters != null) { + // Initialize the CachingCounter values. + getCached(dataSpec, cache, counters); } else { - counters.alreadyCachedBytes = 0; - counters.downloadedBytes = 0; + // Dummy CachingCounters. No need to initialize as they will not be visible to the caller. + counters = new CachingCounters(); } - while (left > 0) { - long blockLength = cache.getCachedBytes(key, start, left); - // Skip already cached data + + String key = getKey(dataSpec); + long start = dataSpec.absoluteStreamPosition; + long left = dataSpec.length != C.LENGTH_UNSET ? dataSpec.length : cache.getContentLength(key); + while (left != 0) { + long blockLength = cache.getCachedBytes(key, start, + left != C.LENGTH_UNSET ? left : Long.MAX_VALUE); if (blockLength > 0) { - counters.alreadyCachedBytes += blockLength; + // Skip already cached data. } else { // There is a hole in the cache which is at least "-blockLength" long. blockLength = -blockLength; - if (dataSource != null && buffer != null) { - DataSpec subDataSpec = new DataSpec(dataSpec.uri, start, - blockLength == Long.MAX_VALUE ? C.LENGTH_UNSET : blockLength, key); - long read = readAndDiscard(subDataSpec, dataSource, buffer, priorityTaskManager, - priority); - counters.downloadedBytes += read; - if (read < blockLength) { - // Reached end of data. - break; + long read = readAndDiscard(dataSpec, start, blockLength, dataSource, buffer, + priorityTaskManager, priority, counters); + if (read < blockLength) { + // Reached to the end of the data. + if (enableEOFException && left != C.LENGTH_UNSET) { + throw new EOFException(); } - } else if (blockLength == Long.MAX_VALUE) { - counters.downloadedBytes = C.LENGTH_UNSET; break; - } else { - counters.downloadedBytes += blockLength; } } start += blockLength; - if (left != Long.MAX_VALUE) { - left -= blockLength; - } + left -= left == C.LENGTH_UNSET ? 0 : blockLength; } - return counters; } /** * Reads and discards all data specified by the {@code dataSpec}. * - * @param dataSpec Defines the data to be read. + * @param dataSpec Defines the data to be read. {@code absoluteStreamPosition} and {@code length} + * fields are overwritten by the following parameters. + * @param absoluteStreamPosition The absolute position of the data to be read. + * @param length Length of the data to be read, or {@link C#LENGTH_UNSET} if it is unknown. * @param dataSource The {@link DataSource} to read the data from. * @param buffer The buffer to be used while downloading. * @param priorityTaskManager If not null it's used to check whether it is allowed to proceed with * caching. * @param priority The priority of this task. + * @param counters Counters to be set during reading. * @return Number of read bytes, or 0 if no data is available because the end of the opened range - * has been reached. + * has been reached. */ - private static long readAndDiscard(DataSpec dataSpec, DataSource dataSource, byte[] buffer, - PriorityTaskManager priorityTaskManager, int priority) - throws IOException, InterruptedException { + private static long readAndDiscard(DataSpec dataSpec, long absoluteStreamPosition, long length, + DataSource dataSource, byte[] buffer, PriorityTaskManager priorityTaskManager, int priority, + CachingCounters counters) throws IOException, InterruptedException { while (true) { if (priorityTaskManager != null) { // Wait for any other thread with higher priority to finish its job. priorityTaskManager.proceed(priority); } try { - dataSource.open(dataSpec); + if (Thread.interrupted()) { + throw new InterruptedException(); + } + // Create a new dataSpec setting length to C.LENGTH_UNSET to prevent getting an error in + // case the given length exceeds the end of input. + dataSpec = new DataSpec(dataSpec.uri, dataSpec.postBody, absoluteStreamPosition, + dataSpec.position + absoluteStreamPosition - dataSpec.absoluteStreamPosition, + C.LENGTH_UNSET, dataSpec.key, + dataSpec.flags | DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH); + long resolvedLength = dataSource.open(dataSpec); + if (counters.contentLength == C.LENGTH_UNSET && resolvedLength != C.LENGTH_UNSET) { + counters.contentLength = dataSpec.absoluteStreamPosition + resolvedLength; + } long totalRead = 0; - while (true) { + while (totalRead != length) { if (Thread.interrupted()) { throw new InterruptedException(); } - int read = dataSource.read(buffer, 0, buffer.length); + int read = dataSource.read(buffer, 0, + length != C.LENGTH_UNSET ? (int) Math.min(buffer.length, length - totalRead) + : buffer.length); if (read == C.RESULT_END_OF_INPUT) { - return totalRead; + if (counters.contentLength == C.LENGTH_UNSET) { + counters.contentLength = dataSpec.absoluteStreamPosition + totalRead; + } + break; } totalRead += read; + counters.newlyCachedBytes += read; } + return totalRead; } catch (PriorityTaskManager.PriorityTooLowException exception) { // catch and try again } finally { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CachedContentIndex.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CachedContentIndex.java index 256d5bc5a..5f32822be 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CachedContentIndex.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CachedContentIndex.java @@ -49,7 +49,7 @@ import javax.crypto.spec.SecretKeySpec; /** * This class maintains the index of cached content. */ -/*package*/ final class CachedContentIndex { +/*package*/ class CachedContentIndex { public static final String FILE_NAME = "cached_content_index.exi"; @@ -64,6 +64,7 @@ import javax.crypto.spec.SecretKeySpec; private final AtomicFile atomicFile; private final Cipher cipher; private final SecretKeySpec secretKeySpec; + private final boolean encrypt; private boolean changed; private ReusableBufferedOutputStream bufferedOutputStream; @@ -80,14 +81,25 @@ import javax.crypto.spec.SecretKeySpec; * Creates a CachedContentIndex which works on the index file in the given cacheDir. * * @param cacheDir Directory where the index file is kept. - * @param secretKey If not null, cache keys will be stored encrypted on filesystem using AES/CBC. - * The key must be 16 bytes long. + * @param secretKey 16 byte AES key for reading and writing the cache index. */ public CachedContentIndex(File cacheDir, byte[] secretKey) { + this(cacheDir, secretKey, secretKey != null); + } + + /** + * Creates a CachedContentIndex which works on the index file in the given cacheDir. + * + * @param cacheDir Directory where the index file is kept. + * @param secretKey 16 byte AES key for reading, and optionally writing, the cache index. + * @param encrypt When false, a plaintext index will be written. + */ + public CachedContentIndex(File cacheDir, byte[] secretKey, boolean encrypt) { + this.encrypt = encrypt; if (secretKey != null) { Assertions.checkArgument(secretKey.length == 16); try { - cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING"); + cipher = getCipher(); secretKeySpec = new SecretKeySpec(secretKey, "AES"); } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { throw new IllegalStateException(e); // Should never happen. @@ -288,10 +300,11 @@ import javax.crypto.spec.SecretKeySpec; output = new DataOutputStream(bufferedOutputStream); output.writeInt(VERSION); - int flags = cipher != null ? FLAG_ENCRYPTED_INDEX : 0; + boolean writeEncrypted = encrypt && cipher != null; + int flags = writeEncrypted ? FLAG_ENCRYPTED_INDEX : 0; output.writeInt(flags); - if (cipher != null) { + if (writeEncrypted) { byte[] initializationVector = new byte[16]; new Random().nextBytes(initializationVector); output.write(initializationVector); @@ -341,6 +354,18 @@ import javax.crypto.spec.SecretKeySpec; return cachedContent; } + private static Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException { + // Workaround for https://issuetracker.google.com/issues/36976726 + if (Util.SDK_INT == 18) { + try { + return Cipher.getInstance("AES/CBC/PKCS5PADDING", "BC"); + } catch (Throwable ignored) { + // ignored + } + } + return Cipher.getInstance("AES/CBC/PKCS5PADDING"); + } + /** * Returns an id which isn't used in the given array. If the maximum id in the array is smaller * than {@link java.lang.Integer#MAX_VALUE} it just returns the next bigger integer. Otherwise it diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictor.java index 3a9204fc9..9b0809286 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictor.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictor.java @@ -74,7 +74,7 @@ public final class LeastRecentlyUsedCacheEvictor implements CacheEvictor, Compar } private void evictCache(Cache cache, long requiredSpace) { - while (currentSize + requiredSpace > maxBytes) { + while (currentSize + requiredSpace > maxBytes && !leastRecentlyUsed.isEmpty()) { try { cache.removeSpan(leastRecentlyUsed.first()); } catch (CacheException e) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/SimpleCache.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/SimpleCache.java index c24d5d580..67ea3cfaf 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/SimpleCache.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/SimpleCache.java @@ -48,7 +48,7 @@ public final class SimpleCache implements Cache { * @param evictor The evictor to be used. */ public SimpleCache(File cacheDir, CacheEvictor evictor) { - this(cacheDir, evictor, null); + this(cacheDir, evictor, null, false); } /** @@ -61,10 +61,36 @@ public final class SimpleCache implements Cache { * The key must be 16 bytes long. */ public SimpleCache(File cacheDir, CacheEvictor evictor, byte[] secretKey) { + this(cacheDir, evictor, secretKey, secretKey != null); + } + + /** + * Constructs the cache. The cache will delete any unrecognized files from the directory. Hence + * the directory cannot be used to store other files. + * + * @param cacheDir A dedicated cache directory. + * @param evictor The evictor to be used. + * @param secretKey If not null, cache keys will be stored encrypted on filesystem using AES/CBC. + * The key must be 16 bytes long. + * @param encrypt When false, a plaintext index will be written. + */ + public SimpleCache(File cacheDir, CacheEvictor evictor, byte[] secretKey, boolean encrypt) { + this(cacheDir, evictor, new CachedContentIndex(cacheDir, secretKey, encrypt)); + } + + /** + * Constructs the cache. The cache will delete any unrecognized files from the directory. Hence + * the directory cannot be used to store other files. + * + * @param cacheDir A dedicated cache directory. + * @param evictor The evictor to be used. + * @param index The CachedContentIndex to be used. + */ + /*package*/ SimpleCache(File cacheDir, CacheEvictor evictor, CachedContentIndex index) { this.cacheDir = cacheDir; this.evictor = evictor; this.lockedSpans = new HashMap<>(); - this.index = new CachedContentIndex(cacheDir, secretKey); + this.index = index; this.listeners = new HashMap<>(); // Start cache initialization. final ConditionVariable conditionVariable = new ConditionVariable(); @@ -110,7 +136,8 @@ public final class SimpleCache implements Cache { @Override public synchronized NavigableSet getCachedSpans(String key) { CachedContent cachedContent = index.get(key); - return cachedContent == null ? null : new TreeSet(cachedContent.getSpans()); + return cachedContent == null || cachedContent.isEmpty() ? null + : new TreeSet(cachedContent.getSpans()); } @Override @@ -286,13 +313,18 @@ public final class SimpleCache implements Cache { private void removeSpan(CacheSpan span, boolean removeEmptyCachedContent) throws CacheException { CachedContent cachedContent = index.get(span.key); - Assertions.checkState(cachedContent.removeSpan(span)); - totalSpace -= span.length; - if (removeEmptyCachedContent && cachedContent.isEmpty()) { - index.removeEmpty(cachedContent.key); - index.store(); + if (cachedContent == null || !cachedContent.removeSpan(span)) { + return; + } + totalSpace -= span.length; + try { + if (removeEmptyCachedContent && cachedContent.isEmpty()) { + index.removeEmpty(cachedContent.key); + index.store(); + } + } finally { + notifySpanRemoved(span); } - notifySpanRemoved(span); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/Clock.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/Clock.java index 451a4e66c..7a38eb065 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/Clock.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/Clock.java @@ -16,16 +16,24 @@ package org.telegram.messenger.exoplayer2.util; /** - * An interface through which system clocks can be read. The {@link SystemClock} implementation + * An interface through which system clocks can be read. The {@link #DEFAULT} implementation * must be used for all non-test cases. */ public interface Clock { /** - * Returns {@link android.os.SystemClock#elapsedRealtime}. - * - * @return Elapsed milliseconds since boot. + * Default {@link Clock} to use for all non-test cases. + */ + Clock DEFAULT = new SystemClock(); + + /** + * @see android.os.SystemClock#elapsedRealtime() */ long elapsedRealtime(); + /** + * @see android.os.SystemClock#sleep(long) + */ + void sleep(long sleepTimeMs); + } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/FlacStreamInfo.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/FlacStreamInfo.java index 36a812ff6..ed4cf2434 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/FlacStreamInfo.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/FlacStreamInfo.java @@ -47,7 +47,8 @@ public final class FlacStreamInfo { this.sampleRate = scratch.readBits(20); this.channels = scratch.readBits(3) + 1; this.bitsPerSample = scratch.readBits(5) + 1; - this.totalSamples = scratch.readBits(36); + this.totalSamples = ((scratch.readBits(4) & 0xFL) << 32) + | (scratch.readBits(32) & 0xFFFFFFFFL); // Remaining 16 bytes is md5 value } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/MimeTypes.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/MimeTypes.java index 7e5e114d0..33a75a250 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/MimeTypes.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/MimeTypes.java @@ -48,7 +48,7 @@ public final class MimeTypes { public static final String AUDIO_MPEG_L2 = BASE_TYPE_AUDIO + "/mpeg-L2"; public static final String AUDIO_RAW = BASE_TYPE_AUDIO + "/raw"; public static final String AUDIO_ALAW = BASE_TYPE_AUDIO + "/g711-alaw"; - public static final String AUDIO_ULAW = BASE_TYPE_AUDIO + "/g711-mlaw"; + public static final String AUDIO_MLAW = BASE_TYPE_AUDIO + "/g711-mlaw"; public static final String AUDIO_AC3 = BASE_TYPE_AUDIO + "/ac3"; public static final String AUDIO_E_AC3 = BASE_TYPE_AUDIO + "/eac3"; public static final String AUDIO_TRUEHD = BASE_TYPE_AUDIO + "/true-hd"; @@ -59,10 +59,13 @@ public final class MimeTypes { public static final String AUDIO_OPUS = BASE_TYPE_AUDIO + "/opus"; public static final String AUDIO_AMR_NB = BASE_TYPE_AUDIO + "/3gpp"; public static final String AUDIO_AMR_WB = BASE_TYPE_AUDIO + "/amr-wb"; - public static final String AUDIO_FLAC = BASE_TYPE_AUDIO + "/x-flac"; + public static final String AUDIO_FLAC = BASE_TYPE_AUDIO + "/flac"; public static final String AUDIO_ALAC = BASE_TYPE_AUDIO + "/alac"; + public static final String AUDIO_MSGSM = BASE_TYPE_AUDIO + "/gsm"; + public static final String AUDIO_UNKNOWN = BASE_TYPE_AUDIO + "/x-unknown"; public static final String TEXT_VTT = BASE_TYPE_TEXT + "/vtt"; + public static final String TEXT_SSA = BASE_TYPE_TEXT + "/x-ssa"; public static final String APPLICATION_MP4 = BASE_TYPE_APPLICATION + "/mp4"; public static final String APPLICATION_WEBM = BASE_TYPE_APPLICATION + "/webm"; @@ -82,6 +85,7 @@ public final class MimeTypes { public static final String APPLICATION_CAMERA_MOTION = BASE_TYPE_APPLICATION + "/x-camera-motion"; public static final String APPLICATION_EMSG = BASE_TYPE_APPLICATION + "/x-emsg"; public static final String APPLICATION_DVBSUBS = BASE_TYPE_APPLICATION + "/dvbsubs"; + public static final String APPLICATION_EXIF = BASE_TYPE_APPLICATION + "/x-exif"; private MimeTypes() {} @@ -181,9 +185,9 @@ public final class MimeTypes { return MimeTypes.VIDEO_H264; } else if (codec.startsWith("hev1") || codec.startsWith("hvc1")) { return MimeTypes.VIDEO_H265; - } else if (codec.startsWith("vp9")) { + } else if (codec.startsWith("vp9") || codec.startsWith("vp09")) { return MimeTypes.VIDEO_VP9; - } else if (codec.startsWith("vp8")) { + } else if (codec.startsWith("vp8") || codec.startsWith("vp08")) { return MimeTypes.VIDEO_VP8; } else if (codec.startsWith("mp4a")) { return MimeTypes.AUDIO_AAC; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/NalUnitUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/NalUnitUtil.java index 097b6bda6..9ae76592a 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/NalUnitUtil.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/NalUnitUtil.java @@ -265,7 +265,7 @@ public final class NalUnitUtil { } data.readUnsignedExpGolombCodedInt(); // bit_depth_luma_minus8 data.readUnsignedExpGolombCodedInt(); // bit_depth_chroma_minus8 - data.skipBits(1); // qpprime_y_zero_transform_bypass_flag + data.skipBit(); // qpprime_y_zero_transform_bypass_flag boolean seqScalingMatrixPresentFlag = data.readBit(); if (seqScalingMatrixPresentFlag) { int limit = (chromaFormatIdc != 3) ? 8 : 12; @@ -295,17 +295,17 @@ public final class NalUnitUtil { } } data.readUnsignedExpGolombCodedInt(); // max_num_ref_frames - data.skipBits(1); // gaps_in_frame_num_value_allowed_flag + data.skipBit(); // gaps_in_frame_num_value_allowed_flag int picWidthInMbs = data.readUnsignedExpGolombCodedInt() + 1; int picHeightInMapUnits = data.readUnsignedExpGolombCodedInt() + 1; boolean frameMbsOnlyFlag = data.readBit(); int frameHeightInMbs = (2 - (frameMbsOnlyFlag ? 1 : 0)) * picHeightInMapUnits; if (!frameMbsOnlyFlag) { - data.skipBits(1); // mb_adaptive_frame_field_flag + data.skipBit(); // mb_adaptive_frame_field_flag } - data.skipBits(1); // direct_8x8_inference_flag + data.skipBit(); // direct_8x8_inference_flag int frameWidth = picWidthInMbs * 16; int frameHeight = frameHeightInMbs * 16; boolean frameCroppingFlag = data.readBit(); @@ -368,7 +368,7 @@ public final class NalUnitUtil { data.skipBits(8); // nal_unit int picParameterSetId = data.readUnsignedExpGolombCodedInt(); int seqParameterSetId = data.readUnsignedExpGolombCodedInt(); - data.skipBits(1); // entropy_coding_mode_flag + data.skipBit(); // entropy_coding_mode_flag boolean bottomFieldPicOrderInFramePresentFlag = data.readBit(); return new PpsData(picParameterSetId, seqParameterSetId, bottomFieldPicOrderInFramePresentFlag); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ParsableBitArray.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ParsableBitArray.java index ae3a87476..e4784d33f 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ParsableBitArray.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ParsableBitArray.java @@ -110,14 +110,26 @@ public final class ParsableBitArray { assertValidOffset(); } + /** + * Skips a single bit. + */ + public void skipBit() { + if (++bitOffset == 8) { + bitOffset = 0; + byteOffset++; + } + assertValidOffset(); + } + /** * Skips bits and moves current reading position forward. * - * @param n The number of bits to skip. + * @param numBits The number of bits to skip. */ - public void skipBits(int n) { - byteOffset += (n / 8); - bitOffset += (n % 8); + public void skipBits(int numBits) { + int numBytes = numBits / 8; + byteOffset += numBytes; + bitOffset += numBits - (numBytes * 8); if (bitOffset > 7) { byteOffset++; bitOffset -= 8; @@ -131,7 +143,9 @@ public final class ParsableBitArray { * @return Whether the bit is set. */ public boolean readBit() { - return readBits(1) == 1; + boolean returnValue = (data[byteOffset] & (0x80 >> bitOffset)) != 0; + skipBit(); + return returnValue; } /** @@ -144,45 +158,18 @@ public final class ParsableBitArray { if (numBits == 0) { return 0; } - int returnValue = 0; - - // Read as many whole bytes as we can. - int wholeBytes = (numBits / 8); - for (int i = 0; i < wholeBytes; i++) { - int byteValue; - if (bitOffset != 0) { - byteValue = ((data[byteOffset] & 0xFF) << bitOffset) - | ((data[byteOffset + 1] & 0xFF) >>> (8 - bitOffset)); - } else { - byteValue = data[byteOffset]; - } - numBits -= 8; - returnValue |= (byteValue & 0xFF) << numBits; + bitOffset += numBits; + while (bitOffset > 8) { + bitOffset -= 8; + returnValue |= (data[byteOffset++] & 0xFF) << bitOffset; + } + returnValue |= (data[byteOffset] & 0xFF) >> 8 - bitOffset; + returnValue &= 0xFFFFFFFF >>> (32 - numBits); + if (bitOffset == 8) { + bitOffset = 0; byteOffset++; } - - // Read any remaining bits. - if (numBits > 0) { - int nextBit = bitOffset + numBits; - byte writeMask = (byte) (0xFF >> (8 - numBits)); - - if (nextBit > 8) { - // Combine bits from current byte and next byte. - returnValue |= ((((data[byteOffset] & 0xFF) << (nextBit - 8) - | ((data[byteOffset + 1] & 0xFF) >> (16 - nextBit))) & writeMask)); - byteOffset++; - } else { - // Bits to be read only within current byte. - returnValue |= (((data[byteOffset] & 0xFF) >> (8 - nextBit)) & writeMask); - if (nextBit == 8) { - byteOffset++; - } - } - - bitOffset = nextBit % 8; - } - assertValidOffset(); return returnValue; } @@ -231,7 +218,6 @@ public final class ParsableBitArray { private void assertValidOffset() { // It is fine for position to be at the end of the array, but no further. Assertions.checkState(byteOffset >= 0 - && (bitOffset >= 0 && bitOffset < 8) && (byteOffset < byteLimit || (byteOffset == byteLimit && bitOffset == 0))); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ParsableByteArray.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ParsableByteArray.java index 6d93c4d8b..086c91a20 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ParsableByteArray.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ParsableByteArray.java @@ -15,6 +15,7 @@ */ package org.telegram.messenger.exoplayer2.util; +import org.telegram.messenger.exoplayer2.C; import java.nio.ByteBuffer; import java.nio.charset.Charset; @@ -428,7 +429,7 @@ public final class ParsableByteArray { * @return The string encoded by the bytes. */ public String readString(int length) { - return readString(length, Charset.defaultCharset()); + return readString(length, Charset.forName(C.UTF8_NAME)); } /** diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ParsableNalUnitBitArray.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ParsableNalUnitBitArray.java index 9d961aa7c..f232b8bf8 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ParsableNalUnitBitArray.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ParsableNalUnitBitArray.java @@ -54,15 +54,27 @@ public final class ParsableNalUnitBitArray { assertValidOffset(); } + /** + * Skips a single bit. + */ + public void skipBit() { + if (++bitOffset == 8) { + bitOffset = 0; + byteOffset += shouldSkipByte(byteOffset + 1) ? 2 : 1; + } + assertValidOffset(); + } + /** * Skips bits and moves current reading position forward. * - * @param n The number of bits to skip. + * @param numBits The number of bits to skip. */ - public void skipBits(int n) { + public void skipBits(int numBits) { int oldByteOffset = byteOffset; - byteOffset += (n / 8); - bitOffset += (n % 8); + int numBytes = numBits / 8; + byteOffset += numBytes; + bitOffset += numBits - (numBytes * 8); if (bitOffset > 7) { byteOffset++; bitOffset -= 8; @@ -81,13 +93,14 @@ public final class ParsableNalUnitBitArray { * Returns whether it's possible to read {@code n} bits starting from the current offset. The * offset is not modified. * - * @param n The number of bits. + * @param numBits The number of bits. * @return Whether it is possible to read {@code n} bits. */ - public boolean canReadBits(int n) { + public boolean canReadBits(int numBits) { int oldByteOffset = byteOffset; - int newByteOffset = byteOffset + (n / 8); - int newBitOffset = bitOffset + (n % 8); + int numBytes = numBits / 8; + int newByteOffset = byteOffset + numBytes; + int newBitOffset = bitOffset + numBits - (numBytes * 8); if (newBitOffset > 7) { newByteOffset++; newBitOffset -= 8; @@ -108,7 +121,9 @@ public final class ParsableNalUnitBitArray { * @return Whether the bit is set. */ public boolean readBit() { - return readBits(1) == 1; + boolean returnValue = (data[byteOffset] & (0x80 >> bitOffset)) != 0; + skipBit(); + return returnValue; } /** @@ -118,50 +133,19 @@ public final class ParsableNalUnitBitArray { * @return An integer whose bottom n bits hold the read data. */ public int readBits(int numBits) { - if (numBits == 0) { - return 0; - } - int returnValue = 0; - - // Read as many whole bytes as we can. - int wholeBytes = (numBits / 8); - for (int i = 0; i < wholeBytes; i++) { - int nextByteOffset = shouldSkipByte(byteOffset + 1) ? byteOffset + 2 : byteOffset + 1; - int byteValue; - if (bitOffset != 0) { - byteValue = ((data[byteOffset] & 0xFF) << bitOffset) - | ((data[nextByteOffset] & 0xFF) >>> (8 - bitOffset)); - } else { - byteValue = data[byteOffset]; - } - numBits -= 8; - returnValue |= (byteValue & 0xFF) << numBits; - byteOffset = nextByteOffset; + bitOffset += numBits; + while (bitOffset > 8) { + bitOffset -= 8; + returnValue |= (data[byteOffset] & 0xFF) << bitOffset; + byteOffset += shouldSkipByte(byteOffset + 1) ? 2 : 1; } - - // Read any remaining bits. - if (numBits > 0) { - int nextBit = bitOffset + numBits; - byte writeMask = (byte) (0xFF >> (8 - numBits)); - int nextByteOffset = shouldSkipByte(byteOffset + 1) ? byteOffset + 2 : byteOffset + 1; - - if (nextBit > 8) { - // Combine bits from current byte and next byte. - returnValue |= ((((data[byteOffset] & 0xFF) << (nextBit - 8) - | ((data[nextByteOffset] & 0xFF) >> (16 - nextBit))) & writeMask)); - byteOffset = nextByteOffset; - } else { - // Bits to be read only within current byte. - returnValue |= (((data[byteOffset] & 0xFF) >> (8 - nextBit)) & writeMask); - if (nextBit == 8) { - byteOffset = nextByteOffset; - } - } - - bitOffset = nextBit % 8; + returnValue |= (data[byteOffset] & 0xFF) >> 8 - bitOffset; + returnValue &= 0xFFFFFFFF >>> (32 - numBits); + if (bitOffset == 8) { + bitOffset = 0; + byteOffset += shouldSkipByte(byteOffset + 1) ? 2 : 1; } - assertValidOffset(); return returnValue; } @@ -220,7 +204,6 @@ public final class ParsableNalUnitBitArray { private void assertValidOffset() { // It is fine for position to be at the end of the array, but no further. Assertions.checkState(byteOffset >= 0 - && (bitOffset >= 0 && bitOffset < 8) && (byteOffset < byteLimit || (byteOffset == byteLimit && bitOffset == 0))); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/RepeatModeUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/RepeatModeUtil.java new file mode 100755 index 000000000..bb00420fd --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/RepeatModeUtil.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.util; + +import android.support.annotation.IntDef; +import org.telegram.messenger.exoplayer2.Player; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Util class for repeat mode handling. + */ +public final class RepeatModeUtil { + + /** + * Set of repeat toggle modes. Can be combined using bit-wise operations. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {REPEAT_TOGGLE_MODE_NONE, REPEAT_TOGGLE_MODE_ONE, + REPEAT_TOGGLE_MODE_ALL}) + public @interface RepeatToggleModes {} + /** + * All repeat mode buttons disabled. + */ + public static final int REPEAT_TOGGLE_MODE_NONE = 0; + /** + * "Repeat One" button enabled. + */ + public static final int REPEAT_TOGGLE_MODE_ONE = 1; + /** + * "Repeat All" button enabled. + */ + public static final int REPEAT_TOGGLE_MODE_ALL = 2; + + private RepeatModeUtil() { + // Prevent instantiation. + } + + /** + * Gets the next repeat mode out of {@code enabledModes} starting from {@code currentMode}. + * + * @param currentMode The current repeat mode. + * @param enabledModes Bitmask of enabled modes. + * @return The next repeat mode. + */ + public static @Player.RepeatMode int getNextRepeatMode(@Player.RepeatMode int currentMode, + int enabledModes) { + for (int offset = 1; offset <= 2; offset++) { + @Player.RepeatMode int proposedMode = (currentMode + offset) % 3; + if (isRepeatModeEnabled(proposedMode, enabledModes)) { + return proposedMode; + } + } + return currentMode; + } + + /** + * Verifies whether a given {@code repeatMode} is enabled in the bitmask {@code enabledModes}. + * + * @param repeatMode The mode to check. + * @param enabledModes The bitmask representing the enabled modes. + * @return {@code true} if enabled. + */ + public static boolean isRepeatModeEnabled(@Player.RepeatMode int repeatMode, int enabledModes) { + switch (repeatMode) { + case Player.REPEAT_MODE_OFF: + return true; + case Player.REPEAT_MODE_ONE: + return (enabledModes & REPEAT_TOGGLE_MODE_ONE) != 0; + case Player.REPEAT_MODE_ALL: + return (enabledModes & REPEAT_TOGGLE_MODE_ALL) != 0; + default: + return false; + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/SystemClock.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/SystemClock.java index ecb5e4df1..8aa4aeff2 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/SystemClock.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/SystemClock.java @@ -18,11 +18,16 @@ package org.telegram.messenger.exoplayer2.util; /** * The standard implementation of {@link Clock}. */ -public final class SystemClock implements Clock { +/* package */ final class SystemClock implements Clock { @Override public long elapsedRealtime() { return android.os.SystemClock.elapsedRealtime(); } + @Override + public void sleep(long sleepTimeMs) { + android.os.SystemClock.sleep(sleepTimeMs); + } + } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/Util.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/Util.java index 387ccb9e4..09446137d 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/Util.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/Util.java @@ -34,7 +34,6 @@ import org.telegram.messenger.exoplayer2.C; import org.telegram.messenger.exoplayer2.ExoPlayerLibraryInfo; import org.telegram.messenger.exoplayer2.ParserException; import org.telegram.messenger.exoplayer2.upstream.DataSource; -import org.telegram.messenger.exoplayer2.upstream.DataSpec; import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.File; @@ -98,7 +97,7 @@ public final class Util { private static final Pattern XS_DATE_TIME_PATTERN = Pattern.compile( "(\\d\\d\\d\\d)\\-(\\d\\d)\\-(\\d\\d)[Tt]" + "(\\d\\d):(\\d\\d):(\\d\\d)([\\.,](\\d+))?" - + "([Zz]|((\\+|\\-)(\\d\\d):?(\\d\\d)))?"); + + "([Zz]|((\\+|\\-)(\\d?\\d):?(\\d\\d)))?"); private static final Pattern XS_DURATION_PATTERN = Pattern.compile("^(-)?P(([0-9]*)Y)?(([0-9]*)M)?(([0-9]*)D)?" + "(T(([0-9]*)H)?(([0-9]*)M)?(([0-9.]*)S)?)?$"); @@ -254,7 +253,7 @@ public final class Util { * @return The code points encoding using UTF-8. */ public static byte[] getUtf8Bytes(String value) { - return value.getBytes(Charset.defaultCharset()); // UTF-8 is the default on Android. + return value.getBytes(Charset.forName(C.UTF8_NAME)); } /** @@ -680,25 +679,6 @@ public final class Util { return intArray; } - /** - * Given a {@link DataSpec} and a number of bytes already loaded, returns a {@link DataSpec} - * that represents the remainder of the data. - * - * @param dataSpec The original {@link DataSpec}. - * @param bytesLoaded The number of bytes already loaded. - * @return A {@link DataSpec} that represents the remainder of the data. - */ - public static DataSpec getRemainderDataSpec(DataSpec dataSpec, int bytesLoaded) { - if (bytesLoaded == 0) { - return dataSpec; - } else { - long remainingLength = dataSpec.length == C.LENGTH_UNSET ? C.LENGTH_UNSET - : dataSpec.length - bytesLoaded; - return new DataSpec(dataSpec.uri, dataSpec.position + bytesLoaded, remainingLength, - dataSpec.key, dataSpec.flags); - } - } - /** * Returns the integer equal to the big-endian concatenation of the characters in {@code string} * as bytes. The string must be no more than four characters long. @@ -816,6 +796,85 @@ public final class Util { } } + /** + * Returns the {@link C.AudioUsage} corresponding to the specified {@link C.StreamType}. + */ + @C.AudioUsage + public static int getAudioUsageForStreamType(@C.StreamType int streamType) { + switch (streamType) { + case C.STREAM_TYPE_ALARM: + return C.USAGE_ALARM; + case C.STREAM_TYPE_DTMF: + return C.USAGE_VOICE_COMMUNICATION_SIGNALLING; + case C.STREAM_TYPE_NOTIFICATION: + return C.USAGE_NOTIFICATION; + case C.STREAM_TYPE_RING: + return C.USAGE_NOTIFICATION_RINGTONE; + case C.STREAM_TYPE_SYSTEM: + return C.USAGE_ASSISTANCE_SONIFICATION; + case C.STREAM_TYPE_VOICE_CALL: + return C.USAGE_VOICE_COMMUNICATION; + case C.STREAM_TYPE_USE_DEFAULT: + case C.STREAM_TYPE_MUSIC: + default: + return C.USAGE_MEDIA; + } + } + + /** + * Returns the {@link C.AudioContentType} corresponding to the specified {@link C.StreamType}. + */ + @C.AudioContentType + public static int getAudioContentTypeForStreamType(@C.StreamType int streamType) { + switch (streamType) { + case C.STREAM_TYPE_ALARM: + case C.STREAM_TYPE_DTMF: + case C.STREAM_TYPE_NOTIFICATION: + case C.STREAM_TYPE_RING: + case C.STREAM_TYPE_SYSTEM: + return C.CONTENT_TYPE_SONIFICATION; + case C.STREAM_TYPE_VOICE_CALL: + return C.CONTENT_TYPE_SPEECH; + case C.STREAM_TYPE_USE_DEFAULT: + case C.STREAM_TYPE_MUSIC: + default: + return C.CONTENT_TYPE_MUSIC; + } + } + + /** + * Returns the {@link C.StreamType} corresponding to the specified {@link C.AudioUsage}. + */ + @C.StreamType + public static int getStreamTypeForAudioUsage(@C.AudioUsage int usage) { + switch (usage) { + case C.USAGE_MEDIA: + case C.USAGE_GAME: + case C.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE: + return C.STREAM_TYPE_MUSIC; + case C.USAGE_ASSISTANCE_SONIFICATION: + return C.STREAM_TYPE_SYSTEM; + case C.USAGE_VOICE_COMMUNICATION: + return C.STREAM_TYPE_VOICE_CALL; + case C.USAGE_VOICE_COMMUNICATION_SIGNALLING: + return C.STREAM_TYPE_DTMF; + case C.USAGE_ALARM: + return C.STREAM_TYPE_ALARM; + case C.USAGE_NOTIFICATION_RINGTONE: + return C.STREAM_TYPE_RING; + case C.USAGE_NOTIFICATION: + case C.USAGE_NOTIFICATION_COMMUNICATION_REQUEST: + case C.USAGE_NOTIFICATION_COMMUNICATION_INSTANT: + case C.USAGE_NOTIFICATION_COMMUNICATION_DELAYED: + case C.USAGE_NOTIFICATION_EVENT: + return C.STREAM_TYPE_NOTIFICATION; + case C.USAGE_ASSISTANCE_ACCESSIBILITY: + case C.USAGE_UNKNOWN: + default: + return C.STREAM_TYPE_DEFAULT; + } + } + /** * Makes a best guess to infer the type from a {@link Uri}. * @@ -836,7 +895,7 @@ public final class Util { */ @C.ContentType public static int inferContentType(String fileName) { - fileName = fileName.toLowerCase(); + fileName = Util.toLowerInvariant(fileName); if (fileName.endsWith(".mpd")) { return C.TYPE_DASH; } else if (fileName.endsWith(".m3u8")) { @@ -977,15 +1036,15 @@ public final class Util { int expectedLength = length - percentCharacterCount * 2; StringBuilder builder = new StringBuilder(expectedLength); Matcher matcher = ESCAPED_CHARACTER_PATTERN.matcher(fileName); - int endOfLastMatch = 0; + int startOfNotEscaped = 0; while (percentCharacterCount > 0 && matcher.find()) { char unescapedCharacter = (char) Integer.parseInt(matcher.group(1), 16); - builder.append(fileName, endOfLastMatch, matcher.start()).append(unescapedCharacter); - endOfLastMatch = matcher.end(); + builder.append(fileName, startOfNotEscaped, matcher.start()).append(unescapedCharacter); + startOfNotEscaped = matcher.end(); percentCharacterCount--; } - if (endOfLastMatch < length) { - builder.append(fileName, endOfLastMatch, length); + if (startOfNotEscaped < length) { + builder.append(fileName, startOfNotEscaped, length); } if (builder.length() != expectedLength) { return null; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/video/DummySurface.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/video/DummySurface.java new file mode 100755 index 000000000..b6b20990b --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/video/DummySurface.java @@ -0,0 +1,355 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.video; + +import static android.opengl.EGL14.EGL_ALPHA_SIZE; +import static android.opengl.EGL14.EGL_BLUE_SIZE; +import static android.opengl.EGL14.EGL_CONFIG_CAVEAT; +import static android.opengl.EGL14.EGL_CONTEXT_CLIENT_VERSION; +import static android.opengl.EGL14.EGL_DEFAULT_DISPLAY; +import static android.opengl.EGL14.EGL_DEPTH_SIZE; +import static android.opengl.EGL14.EGL_GREEN_SIZE; +import static android.opengl.EGL14.EGL_HEIGHT; +import static android.opengl.EGL14.EGL_NONE; +import static android.opengl.EGL14.EGL_OPENGL_ES2_BIT; +import static android.opengl.EGL14.EGL_RED_SIZE; +import static android.opengl.EGL14.EGL_RENDERABLE_TYPE; +import static android.opengl.EGL14.EGL_SURFACE_TYPE; +import static android.opengl.EGL14.EGL_TRUE; +import static android.opengl.EGL14.EGL_WIDTH; +import static android.opengl.EGL14.EGL_WINDOW_BIT; +import static android.opengl.EGL14.eglChooseConfig; +import static android.opengl.EGL14.eglCreateContext; +import static android.opengl.EGL14.eglCreatePbufferSurface; +import static android.opengl.EGL14.eglDestroyContext; +import static android.opengl.EGL14.eglDestroySurface; +import static android.opengl.EGL14.eglGetDisplay; +import static android.opengl.EGL14.eglInitialize; +import static android.opengl.EGL14.eglMakeCurrent; +import static android.opengl.GLES20.glDeleteTextures; +import static android.opengl.GLES20.glGenTextures; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.SurfaceTexture; +import android.graphics.SurfaceTexture.OnFrameAvailableListener; +import android.opengl.EGL14; +import android.opengl.EGLConfig; +import android.opengl.EGLContext; +import android.opengl.EGLDisplay; +import android.opengl.EGLSurface; +import android.os.Handler; +import android.os.Handler.Callback; +import android.os.HandlerThread; +import android.os.Message; +import android.util.Log; +import android.view.Surface; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.Util; +import javax.microedition.khronos.egl.EGL10; + +/** + * A dummy {@link Surface}. + */ +@TargetApi(17) +public final class DummySurface extends Surface { + + private static final String TAG = "DummySurface"; + + private static final int EGL_PROTECTED_CONTENT_EXT = 0x32C0; + + private static boolean secureSupported; + private static boolean secureSupportedInitialized; + + /** + * Whether the surface is secure. + */ + public final boolean secure; + + private final DummySurfaceThread thread; + private boolean threadReleased; + + /** + * Returns whether the device supports secure dummy surfaces. + * + * @param context Any {@link Context}. + * @return Whether the device supports secure dummy surfaces. + */ + public static synchronized boolean isSecureSupported(Context context) { + if (!secureSupportedInitialized) { + secureSupported = Util.SDK_INT >= 24 && enableSecureDummySurfaceV24(context); + secureSupportedInitialized = true; + } + return secureSupported; + } + + /** + * Returns a newly created dummy surface. The surface must be released by calling {@link #release} + * when it's no longer required. + *

    + * Must only be called if {@link Util#SDK_INT} is 17 or higher. + * + * @param context Any {@link Context}. + * @param secure Whether a secure surface is required. Must only be requested if + * {@link #isSecureSupported(Context)} returns {@code true}. + * @throws IllegalStateException If a secure surface is requested on a device for which + * {@link #isSecureSupported(Context)} returns {@code false}. + */ + public static DummySurface newInstanceV17(Context context, boolean secure) { + assertApiLevel17OrHigher(); + Assertions.checkState(!secure || isSecureSupported(context)); + DummySurfaceThread thread = new DummySurfaceThread(); + return thread.init(secure); + } + + private DummySurface(DummySurfaceThread thread, SurfaceTexture surfaceTexture, boolean secure) { + super(surfaceTexture); + this.thread = thread; + this.secure = secure; + } + + @Override + public void release() { + super.release(); + // The Surface may be released multiple times (explicitly and by Surface.finalize()). The + // implementation of super.release() has its own deduplication logic. Below we need to + // deduplicate ourselves. Synchronization is required as we don't control the thread on which + // Surface.finalize() is called. + synchronized (thread) { + if (!threadReleased) { + thread.release(); + threadReleased = true; + } + } + } + + private static void assertApiLevel17OrHigher() { + if (Util.SDK_INT < 17) { + throw new UnsupportedOperationException("Unsupported prior to API level 17"); + } + } + + /** + * Returns whether use of secure dummy surfaces should be enabled. + * + * @param context Any {@link Context}. + */ + @TargetApi(24) + private static boolean enableSecureDummySurfaceV24(Context context) { + EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + String eglExtensions = EGL14.eglQueryString(display, EGL10.EGL_EXTENSIONS); + if (eglExtensions == null || !eglExtensions.contains("EGL_EXT_protected_content")) { + // EGL_EXT_protected_content is required to enable secure dummy surfaces. + return false; + } + if (Util.SDK_INT == 24 && "samsung".equals(Util.MANUFACTURER)) { + // Samsung devices running API level 24 are known to be broken [Internal: b/37197802]. + return false; + } + if (Util.SDK_INT < 26 && !context.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) { + // Pre API level 26 devices were not well tested unless they supported VR mode. + return false; + } + return true; + } + + private static class DummySurfaceThread extends HandlerThread implements OnFrameAvailableListener, + Callback { + + private static final int MSG_INIT = 1; + private static final int MSG_UPDATE_TEXTURE = 2; + private static final int MSG_RELEASE = 3; + + private final int[] textureIdHolder; + private EGLDisplay display; + private EGLContext context; + private EGLSurface pbuffer; + private Handler handler; + private SurfaceTexture surfaceTexture; + + private Error initError; + private RuntimeException initException; + private DummySurface surface; + + public DummySurfaceThread() { + super("dummySurface"); + textureIdHolder = new int[1]; + } + + public DummySurface init(boolean secure) { + start(); + handler = new Handler(getLooper(), this); + boolean wasInterrupted = false; + synchronized (this) { + handler.obtainMessage(MSG_INIT, secure ? 1 : 0, 0).sendToTarget(); + while (surface == null && initException == null && initError == null) { + try { + wait(); + } catch (InterruptedException e) { + wasInterrupted = true; + } + } + } + if (wasInterrupted) { + // Restore the interrupted status. + Thread.currentThread().interrupt(); + } + if (initException != null) { + throw initException; + } else if (initError != null) { + throw initError; + } else { + return surface; + } + } + + public void release() { + handler.sendEmptyMessage(MSG_RELEASE); + } + + @Override + public void onFrameAvailable(SurfaceTexture surfaceTexture) { + handler.sendEmptyMessage(MSG_UPDATE_TEXTURE); + } + + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case MSG_INIT: + try { + initInternal(msg.arg1 != 0); + } catch (RuntimeException e) { + Log.e(TAG, "Failed to initialize dummy surface", e); + initException = e; + } catch (Error e) { + Log.e(TAG, "Failed to initialize dummy surface", e); + initError = e; + } finally { + synchronized (this) { + notify(); + } + } + return true; + case MSG_UPDATE_TEXTURE: + surfaceTexture.updateTexImage(); + return true; + case MSG_RELEASE: + try { + releaseInternal(); + } catch (Throwable e) { + Log.e(TAG, "Failed to release dummy surface", e); + } finally { + quit(); + } + return true; + default: + return true; + } + } + + private void initInternal(boolean secure) { + display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + Assertions.checkState(display != null, "eglGetDisplay failed"); + + int[] version = new int[2]; + boolean eglInitialized = eglInitialize(display, version, 0, version, 1); + Assertions.checkState(eglInitialized, "eglInitialize failed"); + + int[] eglAttributes = new int[] { + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, 0, + EGL_CONFIG_CAVEAT, EGL_NONE, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_NONE + }; + EGLConfig[] configs = new EGLConfig[1]; + int[] numConfigs = new int[1]; + boolean eglChooseConfigSuccess = eglChooseConfig(display, eglAttributes, 0, configs, 0, 1, + numConfigs, 0); + Assertions.checkState(eglChooseConfigSuccess && numConfigs[0] > 0 && configs[0] != null, + "eglChooseConfig failed"); + + EGLConfig config = configs[0]; + int[] glAttributes; + if (secure) { + glAttributes = new int[] { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_PROTECTED_CONTENT_EXT, EGL_TRUE, + EGL_NONE}; + } else { + glAttributes = new int[] { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE}; + } + context = eglCreateContext(display, config, android.opengl.EGL14.EGL_NO_CONTEXT, glAttributes, + 0); + Assertions.checkState(context != null, "eglCreateContext failed"); + + int[] pbufferAttributes; + if (secure) { + pbufferAttributes = new int[] { + EGL_WIDTH, 1, + EGL_HEIGHT, 1, + EGL_PROTECTED_CONTENT_EXT, EGL_TRUE, + EGL_NONE}; + } else { + pbufferAttributes = new int[] { + EGL_WIDTH, 1, + EGL_HEIGHT, 1, + EGL_NONE}; + } + pbuffer = eglCreatePbufferSurface(display, config, pbufferAttributes, 0); + Assertions.checkState(pbuffer != null, "eglCreatePbufferSurface failed"); + + boolean eglMadeCurrent = eglMakeCurrent(display, pbuffer, pbuffer, context); + Assertions.checkState(eglMadeCurrent, "eglMakeCurrent failed"); + + glGenTextures(1, textureIdHolder, 0); + surfaceTexture = new SurfaceTexture(textureIdHolder[0]); + surfaceTexture.setOnFrameAvailableListener(this); + surface = new DummySurface(this, surfaceTexture, secure); + } + + private void releaseInternal() { + try { + if (surfaceTexture != null) { + surfaceTexture.release(); + glDeleteTextures(1, textureIdHolder, 0); + } + } finally { + if (pbuffer != null) { + eglDestroySurface(display, pbuffer); + } + if (context != null) { + eglDestroyContext(display, context); + } + pbuffer = null; + context = null; + display = null; + surface = null; + surfaceTexture = null; + } + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/video/MediaCodecVideoRenderer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/video/MediaCodecVideoRenderer.java index 833a607a9..b5f029c95 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/video/MediaCodecVideoRenderer.java @@ -40,6 +40,7 @@ import org.telegram.messenger.exoplayer2.mediacodec.MediaCodecRenderer; import org.telegram.messenger.exoplayer2.mediacodec.MediaCodecSelector; import org.telegram.messenger.exoplayer2.mediacodec.MediaCodecUtil; import org.telegram.messenger.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; +import org.telegram.messenger.exoplayer2.util.Assertions; import org.telegram.messenger.exoplayer2.util.MimeTypes; import org.telegram.messenger.exoplayer2.util.TraceUtil; import org.telegram.messenger.exoplayer2.util.Util; @@ -62,16 +63,24 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { private static final int[] STANDARD_LONG_EDGE_VIDEO_PX = new int[] { 1920, 1600, 1440, 1280, 960, 854, 640, 540, 480}; + // Generally there is zero or one pending output stream offset. We track more offsets to allow for + // pending output streams that have fewer frames than the codec latency. + private static final int MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT = 10; + + private final Context context; private final VideoFrameReleaseTimeHelper frameReleaseTimeHelper; private final EventDispatcher eventDispatcher; private final long allowedJoiningTimeMs; private final int maxDroppedFramesToNotify; private final boolean deviceNeedsAutoFrcWorkaround; + private final long[] pendingOutputStreamOffsetsUs; private Format[] streamFormats; private CodecMaxValues codecMaxValues; + private boolean codecNeedsSetOutputSurfaceWorkaround; private Surface surface; + private Surface dummySurface; @C.VideoScalingMode private int scalingMode; private boolean renderedFirstFrame; @@ -95,6 +104,9 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { private int tunnelingAudioSessionId; /* package */ OnFrameRenderedListenerV23 tunnelingOnFrameRenderedListener; + private long outputStreamOffsetUs; + private int pendingOutputStreamOffsetCount; + /** * @param context A context. * @param mediaCodecSelector A decoder selector. @@ -157,9 +169,12 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { super(C.TRACK_TYPE_VIDEO, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys); this.allowedJoiningTimeMs = allowedJoiningTimeMs; this.maxDroppedFramesToNotify = maxDroppedFramesToNotify; + this.context = context.getApplicationContext(); frameReleaseTimeHelper = new VideoFrameReleaseTimeHelper(context); eventDispatcher = new EventDispatcher(eventHandler, eventListener); deviceNeedsAutoFrcWorkaround = deviceNeedsAutoFrcWorkaround(); + pendingOutputStreamOffsetsUs = new long[MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT]; + outputStreamOffsetUs = C.TIME_UNSET; joiningDeadlineMs = C.TIME_UNSET; currentWidth = Format.NO_VALUE; currentHeight = Format.NO_VALUE; @@ -219,9 +234,20 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } @Override - protected void onStreamChanged(Format[] formats) throws ExoPlaybackException { + protected void onStreamChanged(Format[] formats, long offsetUs) throws ExoPlaybackException { streamFormats = formats; - super.onStreamChanged(formats); + if (outputStreamOffsetUs == C.TIME_UNSET) { + outputStreamOffsetUs = offsetUs; + } else { + if (pendingOutputStreamOffsetCount == pendingOutputStreamOffsetsUs.length) { + Log.w(TAG, "Too many stream changes, so dropping offset: " + + pendingOutputStreamOffsetsUs[pendingOutputStreamOffsetCount - 1]); + } else { + pendingOutputStreamOffsetCount++; + } + pendingOutputStreamOffsetsUs[pendingOutputStreamOffsetCount - 1] = offsetUs; + } + super.onStreamChanged(formats, offsetUs); } @Override @@ -229,6 +255,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { super.onPositionReset(positionUs, joining); clearRenderedFirstFrame(); consecutiveDroppedFrameCount = 0; + if (pendingOutputStreamOffsetCount != 0) { + outputStreamOffsetUs = pendingOutputStreamOffsetsUs[pendingOutputStreamOffsetCount - 1]; + pendingOutputStreamOffsetCount = 0; + } if (joining) { setJoiningDeadlineMs(); } else { @@ -238,7 +268,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { @Override public boolean isReady() { - if ((renderedFirstFrame || super.shouldInitCodec()) && super.isReady()) { + if (super.isReady() && (renderedFirstFrame || (dummySurface != null && surface == dummySurface) + || getCodec() == null || tunneling)) { // Ready. If we were joining then we've now joined, so clear the joining deadline. joiningDeadlineMs = C.TIME_UNSET; return true; @@ -260,11 +291,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { super.onStarted(); droppedFrames = 0; droppedFrameAccumulationStartTimeMs = SystemClock.elapsedRealtime(); - joiningDeadlineMs = C.TIME_UNSET; } @Override protected void onStopped() { + joiningDeadlineMs = C.TIME_UNSET; maybeNotifyDroppedFrames(); super.onStopped(); } @@ -275,10 +306,13 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { currentHeight = Format.NO_VALUE; currentPixelWidthHeightRatio = Format.NO_VALUE; pendingPixelWidthHeightRatio = Format.NO_VALUE; + outputStreamOffsetUs = C.TIME_UNSET; + pendingOutputStreamOffsetCount = 0; clearReportedVideoSize(); clearRenderedFirstFrame(); frameReleaseTimeHelper.disable(); tunnelingOnFrameRenderedListener = null; + tunneling = false; try { super.onDisabled(); } finally { @@ -303,20 +337,33 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } private void setSurface(Surface surface) throws ExoPlaybackException { + if (surface == null) { + // Use a dummy surface if possible. + if (dummySurface != null) { + surface = dummySurface; + } else { + MediaCodecInfo codecInfo = getCodecInfo(); + if (codecInfo != null && shouldUseDummySurface(codecInfo.secure)) { + dummySurface = DummySurface.newInstanceV17(context, codecInfo.secure); + surface = dummySurface; + } + } + } // We only need to update the codec if the surface has changed. if (this.surface != surface) { this.surface = surface; int state = getState(); if (state == STATE_ENABLED || state == STATE_STARTED) { MediaCodec codec = getCodec(); - if (Util.SDK_INT >= 23 && codec != null && surface != null) { + if (Util.SDK_INT >= 23 && codec != null && surface != null + && !codecNeedsSetOutputSurfaceWorkaround) { setOutputSurfaceV23(codec, surface); } else { releaseCodec(); maybeInitCodec(); } } - if (surface != null) { + if (surface != null && surface != dummySurface) { // If we know the video size, report it again immediately. maybeRenotifyVideoSizeChanged(); // We haven't rendered to the new surface yet. @@ -329,17 +376,17 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { clearReportedVideoSize(); clearRenderedFirstFrame(); } - } else if (surface != null) { - // The surface is unchanged and non-null. If we know the video size and/or have already - // rendered to the surface, report these again immediately. + } else if (surface != null && surface != dummySurface) { + // The surface is set and unchanged. If we know the video size and/or have already rendered to + // the surface, report these again immediately. maybeRenotifyVideoSizeChanged(); maybeRenotifyRenderedFirstFrame(); } } @Override - protected boolean shouldInitCodec() { - return super.shouldInitCodec() && surface != null && surface.isValid(); + protected boolean shouldInitCodec(MediaCodecInfo codecInfo) { + return surface != null || shouldUseDummySurface(codecInfo.secure); } @Override @@ -348,16 +395,39 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { codecMaxValues = getCodecMaxValues(codecInfo, format, streamFormats); MediaFormat mediaFormat = getMediaFormat(format, codecMaxValues, deviceNeedsAutoFrcWorkaround, tunnelingAudioSessionId); + if (surface == null) { + Assertions.checkState(shouldUseDummySurface(codecInfo.secure)); + if (dummySurface == null) { + dummySurface = DummySurface.newInstanceV17(context, codecInfo.secure); + } + surface = dummySurface; + } codec.configure(mediaFormat, surface, crypto, 0); if (Util.SDK_INT >= 23 && tunneling) { tunnelingOnFrameRenderedListener = new OnFrameRenderedListenerV23(codec); } } + @Override + protected void releaseCodec() { + try { + super.releaseCodec(); + } finally { + if (dummySurface != null) { + if (surface == dummySurface) { + surface = null; + } + dummySurface.release(); + dummySurface = null; + } + } + } + @Override protected void onCodecInitialized(String name, long initializedTimestampMs, long initializationDurationMs) { eventDispatcher.decoderInitialized(name, initializedTimestampMs, initializationDurationMs); + codecNeedsSetOutputSurfaceWorkaround = codecNeedsSetOutputSurfaceWorkaround(name); } @Override @@ -376,7 +446,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } @Override - protected void onOutputFormatChanged(MediaCodec codec, android.media.MediaFormat outputFormat) { + protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat) { boolean hasCrop = outputFormat.containsKey(KEY_CROP_RIGHT) && outputFormat.containsKey(KEY_CROP_LEFT) && outputFormat.containsKey(KEY_CROP_BOTTOM) && outputFormat.containsKey(KEY_CROP_TOP); @@ -408,27 +478,44 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { @Override protected boolean canReconfigureCodec(MediaCodec codec, boolean codecIsAdaptive, Format oldFormat, Format newFormat) { - return areAdaptationCompatible(oldFormat, newFormat) + return areAdaptationCompatible(codecIsAdaptive, oldFormat, newFormat) && newFormat.width <= codecMaxValues.width && newFormat.height <= codecMaxValues.height - && newFormat.maxInputSize <= codecMaxValues.inputSize - && (codecIsAdaptive - || (oldFormat.width == newFormat.width && oldFormat.height == newFormat.height)); + && getMaxInputSize(newFormat) <= codecMaxValues.inputSize; } @Override protected boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, MediaCodec codec, ByteBuffer buffer, int bufferIndex, int bufferFlags, long bufferPresentationTimeUs, boolean shouldSkip) { + while (pendingOutputStreamOffsetCount != 0 + && bufferPresentationTimeUs >= pendingOutputStreamOffsetsUs[0]) { + outputStreamOffsetUs = pendingOutputStreamOffsetsUs[0]; + pendingOutputStreamOffsetCount--; + System.arraycopy(pendingOutputStreamOffsetsUs, 1, pendingOutputStreamOffsetsUs, 0, + pendingOutputStreamOffsetCount); + } + long presentationTimeUs = bufferPresentationTimeUs - outputStreamOffsetUs; + if (shouldSkip) { - skipOutputBuffer(codec, bufferIndex); + skipOutputBuffer(codec, bufferIndex, presentationTimeUs); return true; } + long earlyUs = bufferPresentationTimeUs - positionUs; + if (surface == dummySurface) { + // Skip frames in sync with playback, so we'll be at the right frame if the mode changes. + if (isBufferLate(earlyUs)) { + skipOutputBuffer(codec, bufferIndex, presentationTimeUs); + return true; + } + return false; + } + if (!renderedFirstFrame) { if (Util.SDK_INT >= 21) { - renderOutputBufferV21(codec, bufferIndex, System.nanoTime()); + renderOutputBufferV21(codec, bufferIndex, presentationTimeUs, System.nanoTime()); } else { - renderOutputBuffer(codec, bufferIndex); + renderOutputBuffer(codec, bufferIndex, presentationTimeUs); } return true; } @@ -437,9 +524,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { return false; } - // Compute how many microseconds it is until the buffer's presentation time. + // Fine-grained adjustment of earlyUs based on the elapsed time since the start of the current + // iteration of the rendering loop. long elapsedSinceStartOfLoopUs = (SystemClock.elapsedRealtime() * 1000) - elapsedRealtimeUs; - long earlyUs = bufferPresentationTimeUs - positionUs - elapsedSinceStartOfLoopUs; + earlyUs -= elapsedSinceStartOfLoopUs; // Compute the buffer's desired release time in nanoseconds. long systemTimeNs = System.nanoTime(); @@ -451,15 +539,14 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { earlyUs = (adjustedReleaseTimeNs - systemTimeNs) / 1000; if (shouldDropOutputBuffer(earlyUs, elapsedRealtimeUs)) { - // We're more than 30ms late rendering the frame. - dropOutputBuffer(codec, bufferIndex); + dropOutputBuffer(codec, bufferIndex, presentationTimeUs); return true; } if (Util.SDK_INT >= 21) { // Let the underlying framework time the release. if (earlyUs < 50000) { - renderOutputBufferV21(codec, bufferIndex, adjustedReleaseTimeNs); + renderOutputBufferV21(codec, bufferIndex, presentationTimeUs, adjustedReleaseTimeNs); return true; } } else { @@ -475,7 +562,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { Thread.currentThread().interrupt(); } } - renderOutputBuffer(codec, bufferIndex); + renderOutputBuffer(codec, bufferIndex, presentationTimeUs); return true; } } @@ -493,20 +580,33 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { * measured at the start of the current iteration of the rendering loop. */ protected boolean shouldDropOutputBuffer(long earlyUs, long elapsedRealtimeUs) { - // Drop the frame if we're more than 30ms late rendering the frame. - return earlyUs < -30000; + return isBufferLate(earlyUs); } - private void skipOutputBuffer(MediaCodec codec, int bufferIndex) { + /** + * Skips the output buffer with the specified index. + * + * @param codec The codec that owns the output buffer. + * @param index The index of the output buffer to skip. + * @param presentationTimeUs The presentation time of the output buffer, in microseconds. + */ + protected void skipOutputBuffer(MediaCodec codec, int index, long presentationTimeUs) { TraceUtil.beginSection("skipVideoBuffer"); - codec.releaseOutputBuffer(bufferIndex, false); + codec.releaseOutputBuffer(index, false); TraceUtil.endSection(); decoderCounters.skippedOutputBufferCount++; } - private void dropOutputBuffer(MediaCodec codec, int bufferIndex) { + /** + * Drops the output buffer with the specified index. + * + * @param codec The codec that owns the output buffer. + * @param index The index of the output buffer to drop. + * @param presentationTimeUs The presentation time of the output buffer, in microseconds. + */ + protected void dropOutputBuffer(MediaCodec codec, int index, long presentationTimeUs) { TraceUtil.beginSection("dropVideoBuffer"); - codec.releaseOutputBuffer(bufferIndex, false); + codec.releaseOutputBuffer(index, false); TraceUtil.endSection(); decoderCounters.droppedOutputBufferCount++; droppedFrames++; @@ -518,27 +618,50 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } } - private void renderOutputBuffer(MediaCodec codec, int bufferIndex) { + /** + * Renders the output buffer with the specified index. This method is only called if the platform + * API version of the device is less than 21. + * + * @param codec The codec that owns the output buffer. + * @param index The index of the output buffer to drop. + * @param presentationTimeUs The presentation time of the output buffer, in microseconds. + */ + protected void renderOutputBuffer(MediaCodec codec, int index, long presentationTimeUs) { maybeNotifyVideoSizeChanged(); TraceUtil.beginSection("releaseOutputBuffer"); - codec.releaseOutputBuffer(bufferIndex, true); + codec.releaseOutputBuffer(index, true); TraceUtil.endSection(); decoderCounters.renderedOutputBufferCount++; consecutiveDroppedFrameCount = 0; maybeNotifyRenderedFirstFrame(); } + /** + * Renders the output buffer with the specified index. This method is only called if the platform + * API version of the device is 21 or later. + * + * @param codec The codec that owns the output buffer. + * @param index The index of the output buffer to drop. + * @param presentationTimeUs The presentation time of the output buffer, in microseconds. + * @param releaseTimeNs The wallclock time at which the frame should be displayed, in nanoseconds. + */ @TargetApi(21) - private void renderOutputBufferV21(MediaCodec codec, int bufferIndex, long releaseTimeNs) { + protected void renderOutputBufferV21(MediaCodec codec, int index, long presentationTimeUs, + long releaseTimeNs) { maybeNotifyVideoSizeChanged(); TraceUtil.beginSection("releaseOutputBuffer"); - codec.releaseOutputBuffer(bufferIndex, releaseTimeNs); + codec.releaseOutputBuffer(index, releaseTimeNs); TraceUtil.endSection(); decoderCounters.renderedOutputBufferCount++; consecutiveDroppedFrameCount = 0; maybeNotifyRenderedFirstFrame(); } + private boolean shouldUseDummySurface(boolean codecIsSecure) { + return Util.SDK_INT >= 23 && !tunneling + && (!codecIsSecure || DummySurface.isSecureSupported(context)); + } + private void setJoiningDeadlineMs() { joiningDeadlineMs = allowedJoiningTimeMs > 0 ? (SystemClock.elapsedRealtime() + allowedJoiningTimeMs) : C.TIME_UNSET; @@ -580,9 +703,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } private void maybeNotifyVideoSizeChanged() { - if (reportedWidth != currentWidth || reportedHeight != currentHeight + if ((currentWidth != Format.NO_VALUE || currentHeight != Format.NO_VALUE) + && (reportedWidth != currentWidth || reportedHeight != currentHeight || reportedUnappliedRotationDegrees != currentUnappliedRotationDegrees - || reportedPixelWidthHeightRatio != currentPixelWidthHeightRatio) { + || reportedPixelWidthHeightRatio != currentPixelWidthHeightRatio)) { eventDispatcher.videoSizeChanged(currentWidth, currentHeight, currentUnappliedRotationDegrees, currentPixelWidthHeightRatio); reportedWidth = currentWidth; @@ -594,8 +718,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { private void maybeRenotifyVideoSizeChanged() { if (reportedWidth != Format.NO_VALUE || reportedHeight != Format.NO_VALUE) { - eventDispatcher.videoSizeChanged(currentWidth, currentHeight, currentUnappliedRotationDegrees, - currentPixelWidthHeightRatio); + eventDispatcher.videoSizeChanged(reportedWidth, reportedHeight, + reportedUnappliedRotationDegrees, reportedPixelWidthHeightRatio); } } @@ -609,26 +733,9 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } } - @SuppressLint("InlinedApi") - private static MediaFormat getMediaFormat(Format format, CodecMaxValues codecMaxValues, - boolean deviceNeedsAutoFrcWorkaround, int tunnelingAudioSessionId) { - MediaFormat frameworkMediaFormat = format.getFrameworkMediaFormatV16(); - // Set the maximum adaptive video dimensions. - frameworkMediaFormat.setInteger(MediaFormat.KEY_MAX_WIDTH, codecMaxValues.width); - frameworkMediaFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, codecMaxValues.height); - // Set the maximum input size. - if (codecMaxValues.inputSize != Format.NO_VALUE) { - frameworkMediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, codecMaxValues.inputSize); - } - // Set FRC workaround. - if (deviceNeedsAutoFrcWorkaround) { - frameworkMediaFormat.setInteger("auto-frc", 0); - } - // Configure tunneling if enabled. - if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) { - configureTunnelingV21(frameworkMediaFormat, tunnelingAudioSessionId); - } - return frameworkMediaFormat; + private static boolean isBufferLate(long earlyUs) { + // Class a buffer as late if it should have been presented more than 30ms ago. + return earlyUs < -30000; } @TargetApi(23) @@ -652,7 +759,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { * @return Suitable {@link CodecMaxValues}. * @throws DecoderQueryException If an error occurs querying {@code codecInfo}. */ - private static CodecMaxValues getCodecMaxValues(MediaCodecInfo codecInfo, Format format, + protected CodecMaxValues getCodecMaxValues(MediaCodecInfo codecInfo, Format format, Format[] streamFormats) throws DecoderQueryException { int maxWidth = format.width; int maxHeight = format.height; @@ -664,7 +771,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } boolean haveUnknownDimensions = false; for (Format streamFormat : streamFormats) { - if (areAdaptationCompatible(format, streamFormat)) { + if (areAdaptationCompatible(codecInfo.adaptive, format, streamFormat)) { haveUnknownDimensions |= (streamFormat.width == Format.NO_VALUE || streamFormat.height == Format.NO_VALUE); maxWidth = Math.max(maxWidth, streamFormat.width); @@ -686,6 +793,40 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { return new CodecMaxValues(maxWidth, maxHeight, maxInputSize); } + /** + * Returns the framework {@link MediaFormat} that should be used to configure the decoder when + * playing media in the specified input format. + * + * @param format The format of input media. + * @param codecMaxValues The codec's maximum supported values. + * @param deviceNeedsAutoFrcWorkaround Whether the device is known to enable frame-rate conversion + * logic that negatively impacts ExoPlayer. + * @param tunnelingAudioSessionId The audio session id to use for tunneling, or + * {@link C#AUDIO_SESSION_ID_UNSET} if tunneling should not be enabled. + * @return The framework {@link MediaFormat} that should be used to configure the decoder. + */ + @SuppressLint("InlinedApi") + protected MediaFormat getMediaFormat(Format format, CodecMaxValues codecMaxValues, + boolean deviceNeedsAutoFrcWorkaround, int tunnelingAudioSessionId) { + MediaFormat frameworkMediaFormat = format.getFrameworkMediaFormatV16(); + // Set the maximum adaptive video dimensions. + frameworkMediaFormat.setInteger(MediaFormat.KEY_MAX_WIDTH, codecMaxValues.width); + frameworkMediaFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, codecMaxValues.height); + // Set the maximum input size. + if (codecMaxValues.inputSize != Format.NO_VALUE) { + frameworkMediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, codecMaxValues.inputSize); + } + // Set FRC workaround. + if (deviceNeedsAutoFrcWorkaround) { + frameworkMediaFormat.setInteger("auto-frc", 0); + } + // Configure tunneling if enabled. + if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) { + configureTunnelingV21(frameworkMediaFormat, tunnelingAudioSessionId); + } + return frameworkMediaFormat; + } + /** * Returns a maximum video size to use when configuring a codec for {@code format} in a way * that will allow possible adaptation to other compatible formats that are expected to have the @@ -728,18 +869,27 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } /** - * Returns a maximum input size for a given format. + * Returns a maximum input buffer size for a given format. * * @param format The format. - * @return A maximum input size in bytes, or {@link Format#NO_VALUE} if a maximum could not be - * determined. + * @return A maximum input buffer size in bytes, or {@link Format#NO_VALUE} if a maximum could not + * be determined. */ private static int getMaxInputSize(Format format) { if (format.maxInputSize != Format.NO_VALUE) { - // The format defines an explicit maximum input size. - return format.maxInputSize; + // The format defines an explicit maximum input size. Add the total size of initialization + // data buffers, as they may need to be queued in the same input buffer as the largest sample. + int totalInitializationDataSize = 0; + int initializationDataCount = format.initializationData.size(); + for (int i = 0; i < initializationDataCount; i++) { + totalInitializationDataSize += format.initializationData.get(i).length; + } + return format.maxInputSize + totalInitializationDataSize; + } else { + // Calculated maximum input sizes are overestimates, so it's not necessary to add the size of + // initialization data. + return getMaxInputSize(format.sampleMimeType, format.width, format.height); } - return getMaxInputSize(format.sampleMimeType, format.width, format.height); } /** @@ -817,17 +967,33 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } /** - * Returns whether an adaptive codec with suitable {@link CodecMaxValues} will support adaptation - * between two {@link Format}s. + * Returns whether the device is known to implement {@link MediaCodec#setOutputSurface(Surface)} + * incorrectly. + *

    + * If true is returned then we fall back to releasing and re-instantiating the codec instead. + */ + private static boolean codecNeedsSetOutputSurfaceWorkaround(String name) { + // Work around https://github.com/google/ExoPlayer/issues/3236 and + // https://github.com/google/ExoPlayer/issues/3355. + return (("deb".equals(Util.DEVICE) || "flo".equals(Util.DEVICE)) + && "OMX.qcom.video.decoder.avc".equals(name)) + || ("tcl_eu".equals(Util.DEVICE) && "OMX.MTK.VIDEO.DECODER.AVC".equals(name)); + } + + /** + * Returns whether a codec with suitable {@link CodecMaxValues} will support adaptation between + * two {@link Format}s. * + * @param codecIsAdaptive Whether the codec supports seamless resolution switches. * @param first The first format. * @param second The second format. - * @return Whether an adaptive codec with suitable {@link CodecMaxValues} will support adaptation - * between two {@link Format}s. + * @return Whether the codec will support adaptation between the two {@link Format}s. */ - private static boolean areAdaptationCompatible(Format first, Format second) { + private static boolean areAdaptationCompatible(boolean codecIsAdaptive, Format first, + Format second) { return first.sampleMimeType.equals(second.sampleMimeType) - && getRotationDegrees(first) == getRotationDegrees(second); + && getRotationDegrees(first) == getRotationDegrees(second) + && (codecIsAdaptive || (first.width == second.width && first.height == second.height)); } private static float getPixelWidthHeightRatio(Format format) { @@ -838,7 +1004,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { return format.rotationDegrees == Format.NO_VALUE ? 0 : format.rotationDegrees; } - private static final class CodecMaxValues { + protected static final class CodecMaxValues { public final int width; public final int height; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/video/VideoFrameReleaseTimeHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/video/VideoFrameReleaseTimeHelper.java index bc08b4520..49e0e466e 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/video/VideoFrameReleaseTimeHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/video/VideoFrameReleaseTimeHelper.java @@ -31,6 +31,7 @@ import org.telegram.messenger.exoplayer2.C; @TargetApi(16) public final class VideoFrameReleaseTimeHelper { + private static final double DISPLAY_REFRESH_RATE_UNKNOWN = -1; private static final long CHOREOGRAPHER_SAMPLE_DELAY_MILLIS = 500; private static final long MAX_ALLOWED_DRIFT_NS = 20000000; @@ -52,26 +53,25 @@ public final class VideoFrameReleaseTimeHelper { private long frameCount; /** - * Constructs an instance that smoothes frame release timestamps but does not align them with + * Constructs an instance that smooths frame release timestamps but does not align them with * the default display's vsync signal. */ public VideoFrameReleaseTimeHelper() { - this(-1 /* Value unused */, false); + this(DISPLAY_REFRESH_RATE_UNKNOWN); } /** - * Constructs an instance that smoothes frame release timestamps and aligns them with the default + * Constructs an instance that smooths frame release timestamps and aligns them with the default * display's vsync signal. * * @param context A context from which information about the default display can be retrieved. */ public VideoFrameReleaseTimeHelper(Context context) { - this(getDefaultDisplayRefreshRate(context), true); + this(getDefaultDisplayRefreshRate(context)); } - private VideoFrameReleaseTimeHelper(double defaultDisplayRefreshRate, - boolean useDefaultDisplayVsync) { - this.useDefaultDisplayVsync = useDefaultDisplayVsync; + private VideoFrameReleaseTimeHelper(double defaultDisplayRefreshRate) { + useDefaultDisplayVsync = defaultDisplayRefreshRate != DISPLAY_REFRESH_RATE_UNKNOWN; if (useDefaultDisplayVsync) { vsyncSampler = VSyncSampler.getInstance(); vsyncDurationNs = (long) (C.NANOS_PER_SECOND / defaultDisplayRefreshRate); @@ -200,9 +200,10 @@ public final class VideoFrameReleaseTimeHelper { return snappedAfterDiff < snappedBeforeDiff ? snappedAfterNs : snappedBeforeNs; } - private static float getDefaultDisplayRefreshRate(Context context) { + private static double getDefaultDisplayRefreshRate(Context context) { WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - return manager.getDefaultDisplay().getRefreshRate(); + return manager.getDefaultDisplay() != null ? manager.getDefaultDisplay().getRefreshRate() + : DISPLAY_REFRESH_RATE_UNKNOWN; } /** diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/query/MessagesQuery.java b/TMessagesProj/src/main/java/org/telegram/messenger/query/MessagesQuery.java index 70bb371ae..3b992ccda 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/query/MessagesQuery.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/query/MessagesQuery.java @@ -79,10 +79,14 @@ public class MessagesQuery { if (data != null) { result = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); data.reuse(); - result.id = cursor.intValue(1); - result.date = cursor.intValue(2); - result.dialog_id = -channelId; - MessagesStorage.addUsersAndChatsFromMessage(result, usersToLoad, chatsToLoad); + if (result.action instanceof TLRPC.TL_messageActionHistoryClear) { + result = null; + } else { + result.id = cursor.intValue(1); + result.date = cursor.intValue(2); + result.dialog_id = -channelId; + MessagesStorage.addUsersAndChatsFromMessage(result, usersToLoad, chatsToLoad); + } } } cursor.dispose(); @@ -94,7 +98,7 @@ public class MessagesQuery { if (data != null) { result = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); data.reuse(); - if (result.id != mid) { + if (result.id != mid || result.action instanceof TLRPC.TL_messageActionHistoryClear) { result = null; } else { result.dialog_id = -channelId; @@ -115,6 +119,7 @@ public class MessagesQuery { boolean ok = false; if (error == null) { TLRPC.messages_Messages messagesRes = (TLRPC.messages_Messages) response; + removeEmptyMessages(messagesRes.messages); if (!messagesRes.messages.isEmpty()) { ImageLoader.saveMessagesThumbs(messagesRes.messages); broadcastPinnedMessage(messagesRes.messages.get(0), messagesRes.users, messagesRes.chats, false, false); @@ -197,6 +202,16 @@ public class MessagesQuery { return null; } + private static void removeEmptyMessages(ArrayList messages) { + for (int a = 0; a < messages.size(); a++) { + TLRPC.Message message = messages.get(a); + if (message == null || message instanceof TLRPC.TL_messageEmpty || message.action instanceof TLRPC.TL_messageActionHistoryClear) { + messages.remove(a); + a--; + } + } + } + public static void loadReplyMessagesForMessages(final ArrayList messages, final long dialogId) { if ((int) dialogId == 0) { final ArrayList replyMessages = new ArrayList<>(); @@ -246,6 +261,9 @@ public class MessagesQuery { MessageObject object = arrayList.get(b); object.replyMessageObject = messageObject; object.messageOwner.reply_to_msg_id = messageObject.getId(); + if (object.isMegagroup()) { + object.replyMessageObject.messageOwner.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; + } } } } @@ -348,6 +366,7 @@ public class MessagesQuery { public void run(TLObject response, TLRPC.TL_error error) { if (error == null) { TLRPC.messages_Messages messagesRes = (TLRPC.messages_Messages) response; + removeEmptyMessages(messagesRes.messages); ImageLoader.saveMessagesThumbs(messagesRes.messages); broadcastReplyMessages(messagesRes.messages, replyMessageOwners, messagesRes.users, messagesRes.chats, dialogId, false); MessagesStorage.getInstance().putUsersAndChats(messagesRes.users, messagesRes.chats, true, true); @@ -363,6 +382,7 @@ public class MessagesQuery { public void run(TLObject response, TLRPC.TL_error error) { if (error == null) { TLRPC.messages_Messages messagesRes = (TLRPC.messages_Messages) response; + removeEmptyMessages(messagesRes.messages); ImageLoader.saveMessagesThumbs(messagesRes.messages); broadcastReplyMessages(messagesRes.messages, replyMessageOwners, messagesRes.users, messagesRes.chats, dialogId, false); MessagesStorage.getInstance().putUsersAndChats(messagesRes.users, messagesRes.chats, true, true); @@ -448,6 +468,9 @@ public class MessagesQuery { } else if (m.messageOwner.action instanceof TLRPC.TL_messageActionPaymentSent) { m.generatePaymentSentMessageText(null); } + if (m.isMegagroup()) { + m.replyMessageObject.messageOwner.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; + } } changed = true; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/query/MessagesSearchQuery.java b/TMessagesProj/src/main/java/org/telegram/messenger/query/MessagesSearchQuery.java index 87f33786b..a44ba3e53 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/query/MessagesSearchQuery.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/query/MessagesSearchQuery.java @@ -8,8 +8,6 @@ package org.telegram.messenger.query; -import android.text.TextUtils; - import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.MessageObject; import org.telegram.messenger.MessagesController; @@ -21,6 +19,7 @@ import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import java.util.ArrayList; +import java.util.HashMap; @SuppressWarnings("unchecked") public class MessagesSearchQuery { @@ -32,6 +31,7 @@ public class MessagesSearchQuery { private static int messagesSearchCount[] = new int[] {0, 0}; private static boolean messagesSearchEndReached[] = new boolean[] {false, false}; private static ArrayList searchResultMessages = new ArrayList<>(); + private static HashMap searchResultMessagesMap[] = new HashMap[] {new HashMap<>(), new HashMap<>()}; private static String lastSearchQuery; private static int lastReturnedNum; @@ -46,11 +46,15 @@ public class MessagesSearchQuery { return mask; } - public static void searchMessagesInChat(String query, final long dialog_id, final long mergeDialogId, final int guid, final int direction) { - searchMessagesInChat(query, dialog_id, mergeDialogId, guid, direction, false); + public static boolean isMessageFound(final int messageId, boolean mergeDialog) { + return searchResultMessagesMap[mergeDialog ? 1 : 0].containsKey(messageId); } - private static void searchMessagesInChat(String query, final long dialog_id, final long mergeDialogId, final int guid, final int direction, final boolean internal) { + public static void searchMessagesInChat(String query, final long dialog_id, final long mergeDialogId, final int guid, final int direction, TLRPC.User user) { + searchMessagesInChat(query, dialog_id, mergeDialogId, guid, direction, false, user); + } + + private static void searchMessagesInChat(String query, final long dialog_id, final long mergeDialogId, final int guid, final int direction, final boolean internal, final TLRPC.User user) { int max_id = 0; long queryWithDialog = dialog_id; boolean firstQuery = !internal; @@ -62,7 +66,7 @@ public class MessagesSearchQuery { ConnectionsManager.getInstance().cancelRequest(mergeReqId, true); mergeReqId = 0; } - if (TextUtils.isEmpty(query)) { + if (query == null) { if (searchResultMessages.isEmpty()) { return; } @@ -107,9 +111,12 @@ public class MessagesSearchQuery { return; } } else if (firstQuery) { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatSearchResultsLoading, guid); messagesSearchEndReached[0] = messagesSearchEndReached[1] = false; messagesSearchCount[0] = messagesSearchCount[1] = 0; searchResultMessages.clear(); + searchResultMessagesMap[0].clear(); + searchResultMessagesMap[1].clear(); } if (messagesSearchEndReached[0] && !messagesSearchEndReached[1] && mergeDialogId != 0) { queryWithDialog = mergeDialogId; @@ -124,7 +131,11 @@ public class MessagesSearchQuery { req.peer = inputPeer; lastMergeDialogId = mergeDialogId; req.limit = 1; - req.q = query; + req.q = query != null ? query : ""; + if (user != null) { + req.from_id = MessagesController.getInputUser(user); + req.flags |= 1; + } req.filter = new TLRPC.TL_inputMessagesFilterEmpty(); mergeReqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override @@ -138,7 +149,7 @@ public class MessagesSearchQuery { TLRPC.messages_Messages res = (TLRPC.messages_Messages) response; messagesSearchEndReached[1] = res.messages.isEmpty(); messagesSearchCount[1] = res instanceof TLRPC.TL_messages_messagesSlice ? res.count : res.messages.size(); - searchMessagesInChat(req.q, dialog_id, mergeDialogId, guid, direction, true); + searchMessagesInChat(req.q, dialog_id, mergeDialogId, guid, direction, true, user); } } } @@ -158,8 +169,12 @@ public class MessagesSearchQuery { return; } req.limit = 21; - req.q = query; - req.max_id = max_id; + req.q = query != null ? query : ""; + req.offset_id = max_id; + if (user != null) { + req.from_id = MessagesController.getInputUser(user); + req.flags |= 1; + } req.filter = new TLRPC.TL_inputMessagesFilterEmpty(); final int currentReqId = ++lastReqId; lastSearchQuery = query; @@ -174,22 +189,33 @@ public class MessagesSearchQuery { reqId = 0; if (response != null) { TLRPC.messages_Messages res = (TLRPC.messages_Messages) response; + for (int a = 0; a < res.messages.size(); a++) { + TLRPC.Message message = res.messages.get(a); + if (message instanceof TLRPC.TL_messageEmpty || message.action instanceof TLRPC.TL_messageActionHistoryClear) { + res.messages.remove(a); + a--; + } + } MessagesStorage.getInstance().putUsersAndChats(res.users, res.chats, true, true); MessagesController.getInstance().putUsers(res.users, false); MessagesController.getInstance().putChats(res.chats, false); - if (req.max_id == 0 && queryWithDialogFinal == dialog_id) { + if (req.offset_id == 0 && queryWithDialogFinal == dialog_id) { lastReturnedNum = 0; searchResultMessages.clear(); + searchResultMessagesMap[0].clear(); + searchResultMessagesMap[1].clear(); messagesSearchCount[0] = 0; } boolean added = false; for (int a = 0; a < Math.min(res.messages.size(), 20); a++) { TLRPC.Message message = res.messages.get(a); added = true; - searchResultMessages.add(new MessageObject(message, null, false)); + MessageObject messageObject = new MessageObject(message, null, false); + searchResultMessages.add(messageObject); + searchResultMessagesMap[queryWithDialogFinal == dialog_id ? 0 : 1].put(messageObject.getId(), messageObject); } messagesSearchEndReached[queryWithDialogFinal == dialog_id ? 0 : 1] = res.messages.size() != 21; - messagesSearchCount[queryWithDialogFinal == dialog_id ? 0 : 1] = res instanceof TLRPC.TL_messages_messagesSlice ? res.count : res.messages.size(); + messagesSearchCount[queryWithDialogFinal == dialog_id ? 0 : 1] = res instanceof TLRPC.TL_messages_messagesSlice || res instanceof TLRPC.TL_messages_channelMessages ? res.count : res.messages.size(); if (searchResultMessages.isEmpty()) { NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatSearchResultsAvailable, guid, 0, getMask(), (long) 0, 0, 0); } else { @@ -202,7 +228,7 @@ public class MessagesSearchQuery { } } if (queryWithDialogFinal == dialog_id && messagesSearchEndReached[0] && mergeDialogId != 0 && !messagesSearchEndReached[1]) { - searchMessagesInChat(lastSearchQuery, dialog_id, mergeDialogId, guid, 0, true); + searchMessagesInChat(lastSearchQuery, dialog_id, mergeDialogId, guid, 0, true, user); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/query/SearchQuery.java b/TMessagesProj/src/main/java/org/telegram/messenger/query/SearchQuery.java index ebf9cbb44..2a2441a9b 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/query/SearchQuery.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/query/SearchQuery.java @@ -49,7 +49,6 @@ import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; -import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -57,7 +56,6 @@ public class SearchQuery { public static ArrayList hints = new ArrayList<>(); public static ArrayList inlineBots = new ArrayList<>(); - private static HashMap inlineDates = new HashMap<>(); private static boolean loaded; private static boolean loading; @@ -69,7 +67,6 @@ public class SearchQuery { loaded = false; hints.clear(); inlineBots.clear(); - inlineDates.clear(); NotificationCenter.getInstance().postNotificationName(NotificationCenter.reloadHints); NotificationCenter.getInstance().postNotificationName(NotificationCenter.reloadInlineHints); } @@ -89,153 +86,157 @@ public class SearchQuery { @SuppressLint("NewApi") @Override public void run() { - ShortcutManager shortcutManager = ApplicationLoader.applicationContext.getSystemService(ShortcutManager.class); - List currentShortcuts = shortcutManager.getDynamicShortcuts(); - ArrayList shortcutsToUpdate = new ArrayList<>(); - ArrayList newShortcutsIds = new ArrayList<>(); - ArrayList shortcutsToDelete = new ArrayList<>(); + try { + ShortcutManager shortcutManager = ApplicationLoader.applicationContext.getSystemService(ShortcutManager.class); + List currentShortcuts = shortcutManager.getDynamicShortcuts(); + ArrayList shortcutsToUpdate = new ArrayList<>(); + ArrayList newShortcutsIds = new ArrayList<>(); + ArrayList shortcutsToDelete = new ArrayList<>(); - if (currentShortcuts != null && !currentShortcuts.isEmpty()) { - newShortcutsIds.add("compose"); - for (int a = 0; a < hintsFinal.size(); a++) { - TLRPC.TL_topPeer hint = hintsFinal.get(a); - long did; - if (hint.peer.user_id != 0) { - did = hint.peer.user_id; - } else { - did = -hint.peer.chat_id; - if (did == 0) { - did = -hint.peer.channel_id; - } - } - newShortcutsIds.add("did" + did); - } - for (int a = 0; a < currentShortcuts.size(); a++) { - String id = currentShortcuts.get(a).getId(); - if (!newShortcutsIds.remove(id)) { - shortcutsToDelete.add(id); - } - shortcutsToUpdate.add(id); - } - if (newShortcutsIds.isEmpty() && shortcutsToDelete.isEmpty()) { - return; - } - } - - Intent intent = new Intent(ApplicationLoader.applicationContext, LaunchActivity.class); - intent.setAction("new_dialog"); - ArrayList arrayList = new ArrayList<>(); - arrayList.add(new ShortcutInfo.Builder(ApplicationLoader.applicationContext, "compose") - .setShortLabel(LocaleController.getString("NewConversationShortcut", R.string.NewConversationShortcut)) - .setLongLabel(LocaleController.getString("NewConversationShortcut", R.string.NewConversationShortcut)) - .setIcon(Icon.createWithResource(ApplicationLoader.applicationContext, R.drawable.shortcut_compose)) - .setIntent(intent) - .build()); - if (shortcutsToUpdate.contains("compose")) { - shortcutManager.updateShortcuts(arrayList); - } else { - shortcutManager.addDynamicShortcuts(arrayList); - } - arrayList.clear(); - - if (!shortcutsToDelete.isEmpty()) { - shortcutManager.removeDynamicShortcuts(shortcutsToDelete); - } - - for (int a = 0; a < hintsFinal.size(); a++) { - Intent shortcutIntent = new Intent(ApplicationLoader.applicationContext, OpenChatReceiver.class); - TLRPC.TL_topPeer hint = hintsFinal.get(a); - - TLRPC.User user = null; - TLRPC.Chat chat = null; - long did; - if (hint.peer.user_id != 0) { - shortcutIntent.putExtra("userId", hint.peer.user_id); - user = MessagesController.getInstance().getUser(hint.peer.user_id); - did = hint.peer.user_id; - } else { - int chat_id = hint.peer.chat_id; - if (chat_id == 0) { - chat_id = hint.peer.channel_id; - } - chat = MessagesController.getInstance().getChat(chat_id); - shortcutIntent.putExtra("chatId", chat_id); - did = -chat_id; - } - if (user == null && chat == null) { - continue; - } - - String name; - TLRPC.FileLocation photo = null; - - if (user != null) { - name = ContactsController.formatName(user.first_name, user.last_name); - if (user.photo != null) { - photo = user.photo.photo_small; - } - } else { - name = chat.title; - if (chat.photo != null) { - photo = chat.photo.photo_small; - } - } - - shortcutIntent.setAction("com.tmessages.openchat" + did); - shortcutIntent.addFlags(0x4000000); - - Bitmap bitmap = null; - if (photo != null) { - try { - File path = FileLoader.getPathToAttach(photo, true); - bitmap = BitmapFactory.decodeFile(path.toString()); - if (bitmap != null) { - int size = AndroidUtilities.dp(48); - Bitmap result = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); - result.eraseColor(Color.TRANSPARENT); - Canvas canvas = new Canvas(result); - BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); - if (roundPaint == null) { - roundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - bitmapRect = new RectF(); + if (currentShortcuts != null && !currentShortcuts.isEmpty()) { + newShortcutsIds.add("compose"); + for (int a = 0; a < hintsFinal.size(); a++) { + TLRPC.TL_topPeer hint = hintsFinal.get(a); + long did; + if (hint.peer.user_id != 0) { + did = hint.peer.user_id; + } else { + did = -hint.peer.chat_id; + if (did == 0) { + did = -hint.peer.channel_id; } - float scale = size / (float) bitmap.getWidth(); - canvas.scale(scale, scale); - roundPaint.setShader(shader); - bitmapRect.set(AndroidUtilities.dp(2), AndroidUtilities.dp(2), AndroidUtilities.dp(46), AndroidUtilities.dp(46)); - canvas.drawRoundRect(bitmapRect, bitmap.getWidth(), bitmap.getHeight(), roundPaint); - try { - canvas.setBitmap(null); - } catch (Exception e) { - //don't promt, this will crash on 2.x - } - bitmap = result; } - } catch (Throwable e) { - FileLog.e(e); + newShortcutsIds.add("did" + did); + } + for (int a = 0; a < currentShortcuts.size(); a++) { + String id = currentShortcuts.get(a).getId(); + if (!newShortcutsIds.remove(id)) { + shortcutsToDelete.add(id); + } + shortcutsToUpdate.add(id); + } + if (newShortcutsIds.isEmpty() && shortcutsToDelete.isEmpty()) { + return; } } - String id = "did" + did; - if (TextUtils.isEmpty(name)) { - name = " "; - } - ShortcutInfo.Builder builder = new ShortcutInfo.Builder(ApplicationLoader.applicationContext, id) - .setShortLabel(name) - .setLongLabel(name) - .setIntent(shortcutIntent); - if (bitmap != null) { - builder.setIcon(Icon.createWithBitmap(bitmap)); - } else { - builder.setIcon(Icon.createWithResource(ApplicationLoader.applicationContext, R.drawable.shortcut_user)); - } - arrayList.add(builder.build()); - if (shortcutsToUpdate.contains(id)) { + Intent intent = new Intent(ApplicationLoader.applicationContext, LaunchActivity.class); + intent.setAction("new_dialog"); + ArrayList arrayList = new ArrayList<>(); + arrayList.add(new ShortcutInfo.Builder(ApplicationLoader.applicationContext, "compose") + .setShortLabel(LocaleController.getString("NewConversationShortcut", R.string.NewConversationShortcut)) + .setLongLabel(LocaleController.getString("NewConversationShortcut", R.string.NewConversationShortcut)) + .setIcon(Icon.createWithResource(ApplicationLoader.applicationContext, R.drawable.shortcut_compose)) + .setIntent(intent) + .build()); + if (shortcutsToUpdate.contains("compose")) { shortcutManager.updateShortcuts(arrayList); } else { shortcutManager.addDynamicShortcuts(arrayList); } arrayList.clear(); + + if (!shortcutsToDelete.isEmpty()) { + shortcutManager.removeDynamicShortcuts(shortcutsToDelete); + } + + for (int a = 0; a < hintsFinal.size(); a++) { + Intent shortcutIntent = new Intent(ApplicationLoader.applicationContext, OpenChatReceiver.class); + TLRPC.TL_topPeer hint = hintsFinal.get(a); + + TLRPC.User user = null; + TLRPC.Chat chat = null; + long did; + if (hint.peer.user_id != 0) { + shortcutIntent.putExtra("userId", hint.peer.user_id); + user = MessagesController.getInstance().getUser(hint.peer.user_id); + did = hint.peer.user_id; + } else { + int chat_id = hint.peer.chat_id; + if (chat_id == 0) { + chat_id = hint.peer.channel_id; + } + chat = MessagesController.getInstance().getChat(chat_id); + shortcutIntent.putExtra("chatId", chat_id); + did = -chat_id; + } + if (user == null && chat == null) { + continue; + } + + String name; + TLRPC.FileLocation photo = null; + + if (user != null) { + name = ContactsController.formatName(user.first_name, user.last_name); + if (user.photo != null) { + photo = user.photo.photo_small; + } + } else { + name = chat.title; + if (chat.photo != null) { + photo = chat.photo.photo_small; + } + } + + shortcutIntent.setAction("com.tmessages.openchat" + did); + shortcutIntent.addFlags(0x4000000); + + Bitmap bitmap = null; + if (photo != null) { + try { + File path = FileLoader.getPathToAttach(photo, true); + bitmap = BitmapFactory.decodeFile(path.toString()); + if (bitmap != null) { + int size = AndroidUtilities.dp(48); + Bitmap result = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + result.eraseColor(Color.TRANSPARENT); + Canvas canvas = new Canvas(result); + BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); + if (roundPaint == null) { + roundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + bitmapRect = new RectF(); + } + float scale = size / (float) bitmap.getWidth(); + canvas.scale(scale, scale); + roundPaint.setShader(shader); + bitmapRect.set(AndroidUtilities.dp(2), AndroidUtilities.dp(2), AndroidUtilities.dp(46), AndroidUtilities.dp(46)); + canvas.drawRoundRect(bitmapRect, bitmap.getWidth(), bitmap.getHeight(), roundPaint); + try { + canvas.setBitmap(null); + } catch (Exception e) { + //don't promt, this will crash on 2.x + } + bitmap = result; + } + } catch (Throwable e) { + FileLog.e(e); + } + } + + String id = "did" + did; + if (TextUtils.isEmpty(name)) { + name = " "; + } + ShortcutInfo.Builder builder = new ShortcutInfo.Builder(ApplicationLoader.applicationContext, id) + .setShortLabel(name) + .setLongLabel(name) + .setIntent(shortcutIntent); + if (bitmap != null) { + builder.setIcon(Icon.createWithBitmap(bitmap)); + } else { + builder.setIcon(Icon.createWithResource(ApplicationLoader.applicationContext, R.drawable.shortcut_user)); + } + arrayList.add(builder.build()); + if (shortcutsToUpdate.contains(id)) { + shortcutManager.updateShortcuts(arrayList); + } else { + shortcutManager.addDynamicShortcuts(arrayList); + } + arrayList.clear(); + } + } catch (Throwable ignore) { + } } }); @@ -255,15 +256,18 @@ public class SearchQuery { public void run() { final ArrayList hintsNew = new ArrayList<>(); final ArrayList inlineBotsNew = new ArrayList<>(); - final HashMap inlineDatesNew = new HashMap<>(); final ArrayList users = new ArrayList<>(); final ArrayList chats = new ArrayList<>(); + int selfUserId = UserConfig.getClientUserId(); try { ArrayList usersToLoad = new ArrayList<>(); ArrayList chatsToLoad = new ArrayList<>(); - SQLiteCursor cursor = MessagesStorage.getInstance().getDatabase().queryFinalized("SELECT did, type, rating, date FROM chat_hints WHERE 1 ORDER BY rating DESC"); + SQLiteCursor cursor = MessagesStorage.getInstance().getDatabase().queryFinalized("SELECT did, type, rating FROM chat_hints WHERE 1 ORDER BY rating DESC"); while (cursor.next()) { int did = cursor.intValue(0); + if (did == selfUserId) { + continue; + } int type = cursor.intValue(1); TLRPC.TL_topPeer peer = new TLRPC.TL_topPeer(); peer.rating = cursor.doubleValue(2); @@ -280,7 +284,6 @@ public class SearchQuery { hintsNew.add(peer); } else if (type == 1) { inlineBotsNew.add(peer); - inlineDatesNew.put(did, cursor.intValue(3)); } } cursor.dispose(); @@ -300,7 +303,6 @@ public class SearchQuery { loaded = true; hints = hintsNew; inlineBots = inlineBotsNew; - inlineDates = inlineDatesNew; buildShortcuts(); NotificationCenter.getInstance().postNotificationName(NotificationCenter.reloadHints); NotificationCenter.getInstance().postNotificationName(NotificationCenter.reloadInlineHints); @@ -340,14 +342,24 @@ public class SearchQuery { TLRPC.TL_topPeerCategoryPeers category = topPeers.categories.get(a); if (category.category instanceof TLRPC.TL_topPeerCategoryBotsInline) { inlineBots = category.peers; + UserConfig.botRatingLoadTime = (int) (System.currentTimeMillis() / 1000); } else { hints = category.peers; + int selfUserId = UserConfig.getClientUserId(); + for (int b = 0; b < hints.size(); b++) { + TLRPC.TL_topPeer topPeer = hints.get(b); + if (topPeer.peer.user_id == selfUserId) { + hints.remove(b); + break; + } + } + UserConfig.ratingLoadTime = (int) (System.currentTimeMillis() / 1000); } } + UserConfig.saveConfig(false); buildShortcuts(); NotificationCenter.getInstance().postNotificationName(NotificationCenter.reloadHints); NotificationCenter.getInstance().postNotificationName(NotificationCenter.reloadInlineHints); - final HashMap inlineDatesCopy = new HashMap<>(inlineDates); MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { @Override public void run() { @@ -375,12 +387,11 @@ public class SearchQuery { } else { did = -peer.peer.channel_id; } - Integer date = inlineDatesCopy.get(did); state.requery(); state.bindInteger(1, did); state.bindInteger(2, type); state.bindDouble(3, peer.rating); - state.bindInteger(4, date != null ? date : 0); + state.bindInteger(4, 0); state.step(); } } @@ -409,10 +420,9 @@ public class SearchQuery { } public static void increaseInlineRaiting(final int uid) { - Integer time = inlineDates.get(uid); int dt; - if (time != null) { - dt = Math.max(1, ((int) (System.currentTimeMillis() / 1000)) - time); + if (UserConfig.botRatingLoadTime != 0) { + dt = Math.max(1, ((int) (System.currentTimeMillis() / 1000)) - UserConfig.botRatingLoadTime); } else { dt = 60; } @@ -516,12 +526,8 @@ public class SearchQuery { lastTime = cursor.intValue(1); } cursor.dispose(); - if (lastMid > 0) { - cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT date FROM messages WHERE uid = %d AND mid < %d AND out = 1 ORDER BY date DESC", did, lastMid)); - if (cursor.next()) { - dt = (lastTime - cursor.intValue(0)); - } - cursor.dispose(); + if (lastMid > 0 && UserConfig.ratingLoadTime != 0) { + dt = (lastTime - UserConfig.ratingLoadTime); } } catch (Exception e) { FileLog.e(e); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/query/SharedMediaQuery.java b/TMessagesProj/src/main/java/org/telegram/messenger/query/SharedMediaQuery.java index 1a0afac43..a0e69c821 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/query/SharedMediaQuery.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/query/SharedMediaQuery.java @@ -40,17 +40,16 @@ public class SharedMediaQuery { public final static int MEDIA_MUSIC = 4; public final static int MEDIA_TYPES_COUNT = 5; - 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) { + public static void loadMedia(final long uid, final int count, final int max_id, final int type, final boolean fromCache, final int classGuid) { final boolean isChannel = (int) uid < 0 && ChatObject.isChannel(-(int) uid); int lower_part = (int)uid; if (fromCache || lower_part == 0) { - loadMediaDatabase(uid, offset, count, max_id, type, classGuid, isChannel); + loadMediaDatabase(uid, count, max_id, type, classGuid, isChannel); } else { TLRPC.TL_messages_search req = new TLRPC.TL_messages_search(); - req.offset = offset; req.limit = count + 1; - req.max_id = max_id; + req.offset_id = max_id; if (type == MEDIA_PHOTOVIDEO) { req.filter = new TLRPC.TL_inputMessagesFilterPhotoVideo(); } else if (type == MEDIA_FILE) { @@ -79,7 +78,7 @@ public class SharedMediaQuery { } else { topReached = true; } - processLoadedMedia(res, uid, offset, count, max_id, type, false, classGuid, isChannel, topReached); + processLoadedMedia(res, uid, count, max_id, type, false, classGuid, isChannel, topReached); } } }); @@ -93,9 +92,8 @@ public class SharedMediaQuery { 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; + req.offset_id = 0; if (type == MEDIA_PHOTOVIDEO) { req.filter = new TLRPC.TL_inputMessagesFilterPhotoVideo(); } else if (type == MEDIA_FILE) { @@ -188,10 +186,10 @@ public class SharedMediaQuery { 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, final boolean isChannel, final boolean topReached) { + private static void processLoadedMedia(final TLRPC.messages_Messages res, final long uid, int count, int max_id, final int type, final boolean fromCache, final int classGuid, final boolean isChannel, final boolean topReached) { int lower_part = (int)uid; if (fromCache && res.messages.isEmpty() && lower_part != 0) { - loadMedia(uid, offset, count, max_id, type, false, classGuid); + loadMedia(uid, count, max_id, type, false, classGuid); } else { if (!fromCache) { ImageLoader.saveMessagesThumbs(res.messages); @@ -289,7 +287,7 @@ public class SharedMediaQuery { }); } - private static void loadMediaDatabase(final long uid, final int offset, final int count, final int max_id, final int type, final int classGuid, final boolean isChannel) { + private static void loadMediaDatabase(final long uid, final int count, final int max_id, final int type, final int classGuid, final boolean isChannel) { MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { @Override public void run() { @@ -362,9 +360,9 @@ public class SharedMediaQuery { } cursor.dispose(); if (holeMessageId > 1) { - cursor = database.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,%d", uid, holeMessageId, type, offset, countToLoad)); + cursor = database.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, holeMessageId, type, countToLoad)); } else { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v2 WHERE uid = %d AND mid > 0 AND type = %d ORDER BY date DESC, mid DESC LIMIT %d,%d", uid, type, offset, countToLoad)); + cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v2 WHERE uid = %d AND mid > 0 AND type = %d ORDER BY date DESC, mid DESC LIMIT %d", uid, type, countToLoad)); } } } else { @@ -372,7 +370,7 @@ public class SharedMediaQuery { if (max_id != 0) { cursor = database.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, countToLoad)); } else { - cursor = database.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, countToLoad)); + cursor = database.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", uid, type, countToLoad)); } } @@ -418,7 +416,7 @@ public class SharedMediaQuery { res.users.clear(); FileLog.e(e); } finally { - processLoadedMedia(res, uid, offset, count, max_id, type, true, classGuid, isChannel, topReached); + processLoadedMedia(res, uid, count, max_id, type, true, classGuid, isChannel, topReached); } } }); @@ -474,13 +472,19 @@ public class SharedMediaQuery { }); } - public static void loadMusic(final long uid, final int max_id) { + public static void loadMusic(final long uid, final long max_id) { MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { @Override public void run() { final ArrayList 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_MUSIC)); + int lower_id = (int) uid; + SQLiteCursor cursor; + if (lower_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 1000", uid, max_id, MEDIA_MUSIC)); + } else { + 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_MUSIC)); + } while (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/query/StickersQuery.java b/TMessagesProj/src/main/java/org/telegram/messenger/query/StickersQuery.java index 0eadd22c4..556532a23 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/query/StickersQuery.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/query/StickersQuery.java @@ -43,9 +43,11 @@ public class StickersQuery { public static final int TYPE_IMAGE = 0; public static final int TYPE_MASK = 1; + public static final int TYPE_FAVE = 2; private static ArrayList stickerSets[] = new ArrayList[] {new ArrayList<>(), new ArrayList<>()}; private static HashMap stickerSetsById = new HashMap<>(); + private static HashMap groupStickerSets = new HashMap<>(); private static HashMap stickerSetsByName = new HashMap<>(); private static boolean loadingStickers[] = new boolean[2]; private static boolean stickersLoaded[] = new boolean[2]; @@ -57,9 +59,9 @@ public class StickersQuery { private static HashMap stickersByEmoji = new HashMap<>(); private static HashMap> allStickers = new HashMap<>(); - private static ArrayList recentStickers[] = new ArrayList[] {new ArrayList<>(), new ArrayList<>()}; - private static boolean loadingRecentStickers[] = new boolean[2]; - private static boolean recentStickersLoaded[] = new boolean[2]; + private static ArrayList recentStickers[] = new ArrayList[] {new ArrayList<>(), new ArrayList<>(), new ArrayList<>()}; + private static boolean loadingRecentStickers[] = new boolean[3]; + private static boolean recentStickersLoaded[] = new boolean[3]; private static ArrayList recentGifs = new ArrayList<>(); private static boolean loadingRecentGifs; @@ -75,15 +77,17 @@ public class StickersQuery { private static boolean featuredStickersLoaded; public static void cleanup() { + for (int a = 0; a < 3; a++) { + recentStickers[a].clear(); + loadingRecentStickers[a] = false; + recentStickersLoaded[a] = false; + } for (int a = 0; a < 2; a++) { loadHash[a] = 0; loadDate[a] = 0; stickerSets[a].clear(); - recentStickers[a].clear(); loadingStickers[a] = false; stickersLoaded[a] = false; - loadingRecentStickers[a] = false; - recentStickersLoaded[a] = false; } loadFeaturedDate = 0; loadFeaturedHash = 0; @@ -121,35 +125,82 @@ public class StickersQuery { return recentStickers[type]; } - public static void addRecentSticker(int type, TLRPC.Document document, int date) { + public static boolean isStickerInFavorites(TLRPC.Document document) { + for (int a = 0; a < recentStickers[TYPE_FAVE].size(); a++) { + TLRPC.Document d = recentStickers[TYPE_FAVE].get(a); + if (d.id == document.id && d.dc_id == document.dc_id) { + return true; + } + } + return false; + } + + public static void addRecentSticker(final int type, TLRPC.Document document, int date, boolean remove) { boolean found = false; for (int a = 0; a < recentStickers[type].size(); a++) { TLRPC.Document image = recentStickers[type].get(a); if (image.id == document.id) { recentStickers[type].remove(a); - recentStickers[type].add(0, image); + if (!remove) { + recentStickers[type].add(0, image); + } found = true; } } - if (!found) { + if (!found && !remove) { recentStickers[type].add(0, document); } - if (recentStickers[type].size() > MessagesController.getInstance().maxRecentStickersCount) { - final TLRPC.Document old = recentStickers[type].remove(recentStickers[type].size() - 1); + int maxCount; + if (type == TYPE_FAVE) { + if (remove) { + Toast.makeText(ApplicationLoader.applicationContext, LocaleController.getString("RemovedFromFavorites", R.string.RemovedFromFavorites), Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(ApplicationLoader.applicationContext, LocaleController.getString("AddedToFavorites", R.string.AddedToFavorites), Toast.LENGTH_SHORT).show(); + } + TLRPC.TL_messages_faveSticker req = new TLRPC.TL_messages_faveSticker(); + req.id = new TLRPC.TL_inputDocument(); + req.id.id = document.id; + req.id.access_hash = document.access_hash; + req.unfave = remove; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + + } + }); + maxCount = MessagesController.getInstance().maxFaveStickersCount; + } else { + maxCount = MessagesController.getInstance().maxRecentStickersCount; + } + if (recentStickers[type].size() > maxCount || remove) { + final TLRPC.Document old = remove ? document : recentStickers[type].remove(recentStickers[type].size() - 1); MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { @Override public void run() { + int cacheType; + if (type == TYPE_IMAGE) { + cacheType = 3; + } else if (type == TYPE_MASK) { + cacheType = 4; + } else { + cacheType = 5; + } try { - MessagesStorage.getInstance().getDatabase().executeFast("DELETE FROM web_recent_v3 WHERE id = '" + old.id + "'").stepThis().dispose(); + MessagesStorage.getInstance().getDatabase().executeFast("DELETE FROM web_recent_v3 WHERE id = '" + old.id + "' AND type = " + cacheType).stepThis().dispose(); } catch (Exception e) { FileLog.e(e); } } }); } - ArrayList arrayList = new ArrayList<>(); - arrayList.add(document); - processLoadedRecentDocuments(type, arrayList, false, date); + if (!remove) { + ArrayList arrayList = new ArrayList<>(); + arrayList.add(document); + processLoadedRecentDocuments(type, arrayList, false, date); + } + if (type == TYPE_FAVE) { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.recentDocumentsDidLoaded, false, type); + } } public static ArrayList getRecentGifs() { @@ -173,7 +224,7 @@ public class StickersQuery { @Override public void run() { try { - MessagesStorage.getInstance().getDatabase().executeFast("DELETE FROM web_recent_v3 WHERE id = '" + document.id + "'").stepThis().dispose(); + MessagesStorage.getInstance().getDatabase().executeFast("DELETE FROM web_recent_v3 WHERE id = '" + document.id + "' AND type = 2").stepThis().dispose(); } catch (Exception e) { FileLog.e(e); } @@ -200,7 +251,7 @@ public class StickersQuery { @Override public void run() { try { - MessagesStorage.getInstance().getDatabase().executeFast("DELETE FROM web_recent_v3 WHERE id = '" + old.id + "'").stepThis().dispose(); + MessagesStorage.getInstance().getDatabase().executeFast("DELETE FROM web_recent_v3 WHERE id = '" + old.id + "' AND type = 2").stepThis().dispose(); } catch (Exception e) { FileLog.e(e); } @@ -224,10 +275,118 @@ public class StickersQuery { return stickerSetsById.get(id); } + public static TLRPC.TL_messages_stickerSet getGroupStickerSetById(TLRPC.StickerSet stickerSet) { + TLRPC.TL_messages_stickerSet set = stickerSetsById.get(stickerSet.id); + if (set == null) { + set = groupStickerSets.get(stickerSet.id); + if (set == null || set.set == null) { + loadGroupStickerSet(stickerSet, true); + } else if (set.set.hash != stickerSet.hash) { + loadGroupStickerSet(stickerSet, false); + } + } + return set; + } + + public static void putGroupStickerSet(TLRPC.TL_messages_stickerSet stickerSet) { + groupStickerSets.put(stickerSet.set.id, stickerSet); + } + + private static void loadGroupStickerSet(final TLRPC.StickerSet stickerSet, boolean cache) { + if (cache) { + MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { + @Override + public void run() { + try { + final TLRPC.TL_messages_stickerSet set; + SQLiteCursor cursor = MessagesStorage.getInstance().getDatabase().queryFinalized("SELECT document FROM web_recent_v3 WHERE id = 's_" + stickerSet.id + "'"); + if (cursor.next() && !cursor.isNull(0)) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { + set = TLRPC.TL_messages_stickerSet.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); + } else { + set = null; + } + } else { + set = null; + } + cursor.dispose(); + if (set == null || set.set == null || set.set.hash != stickerSet.hash) { + loadGroupStickerSet(stickerSet, false); + } + if (set != null && set.set != null) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + groupStickerSets.put(set.set.id, set); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.groupStickersDidLoaded, set.set.id); + } + }); + } + } catch (Throwable e) { + FileLog.e(e); + } + } + }); + } else { + 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().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + if (response != null) { + final TLRPC.TL_messages_stickerSet set = (TLRPC.TL_messages_stickerSet) response; + MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { + @Override + public void run() { + try { + SQLiteDatabase database = MessagesStorage.getInstance().getDatabase(); + SQLitePreparedStatement state = database.executeFast("REPLACE INTO web_recent_v3 VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + state.requery(); + state.bindString(1, "s_" + set.set.id); + state.bindInteger(2, 6); + state.bindString(3, ""); + state.bindString(4, ""); + state.bindString(5, ""); + state.bindInteger(6, 0); + state.bindInteger(7, 0); + state.bindInteger(8, 0); + state.bindInteger(9, 0); + NativeByteBuffer data = new NativeByteBuffer(set.getObjectSize()); + set.serializeToStream(data); + state.bindByteBuffer(10, data); + state.step(); + data.reuse(); + state.dispose(); + } catch (Exception e) { + FileLog.e(e); + } + } + }); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + groupStickerSets.put(set.set.id, set); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.groupStickersDidLoaded, set.set.id); + } + }); + } + } + }); + } + } + public static HashMap> getAllStickers() { return allStickers; } + public static boolean canAddStickerToFavorites() { + return !stickersLoaded[0] || stickerSets[0].size() >= 5 || !recentStickers[TYPE_FAVE].isEmpty(); + } + public static ArrayList getStickerSets(int type) { return stickerSets[type]; } @@ -275,7 +434,7 @@ public class StickersQuery { return (int) acc; } - public static void loadRecents(final int type, final boolean gif, boolean cache) { + public static void loadRecents(final int type, final boolean gif, boolean cache, boolean force) { if (gif) { if (loadingRecentGifs) { return; @@ -298,7 +457,17 @@ public class StickersQuery { @Override public void run() { try { - SQLiteCursor cursor = MessagesStorage.getInstance().getDatabase().queryFinalized("SELECT document FROM web_recent_v3 WHERE type = " + (gif ? 2 : (type == TYPE_IMAGE ? 3 : 4)) + " ORDER BY date DESC"); + final int cacheType; + if (gif) { + cacheType = 2; + } else if (type == TYPE_IMAGE) { + cacheType = 3; + } else if (type == TYPE_MASK) { + cacheType = 4; + } else { + cacheType = 5; + } + SQLiteCursor cursor = MessagesStorage.getInstance().getDatabase().queryFinalized("SELECT document FROM web_recent_v3 WHERE type = " + cacheType + " ORDER BY date DESC"); final ArrayList arrayList = new ArrayList<>(); while (cursor.next()) { if (!cursor.isNull(0)) { @@ -326,7 +495,7 @@ public class StickersQuery { recentStickersLoaded[type] = true; } NotificationCenter.getInstance().postNotificationName(NotificationCenter.recentDocumentsDidLoaded, gif, type); - loadRecents(type, gif, false); + loadRecents(type, gif, false, false); } }); } catch (Throwable e) { @@ -336,14 +505,25 @@ public class StickersQuery { }); } else { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("emoji", Activity.MODE_PRIVATE); - long lastLoadTime; - if (gif) { - lastLoadTime = preferences.getLong("lastGifLoadTime", 0); - } else { - lastLoadTime = preferences.getLong("lastStickersLoadTime", 0); - } - if (Math.abs(System.currentTimeMillis() - lastLoadTime) < 60 * 60 * 1000) { - return; + if (!force) { + long lastLoadTime; + if (gif) { + lastLoadTime = preferences.getLong("lastGifLoadTime", 0); + } else if (type == TYPE_IMAGE) { + lastLoadTime = preferences.getLong("lastStickersLoadTime", 0); + } else if (type == TYPE_MASK) { + lastLoadTime = preferences.getLong("lastStickersLoadTimeMask", 0); + } else { + lastLoadTime = preferences.getLong("lastStickersLoadTimeFavs", 0); + } + if (Math.abs(System.currentTimeMillis() - lastLoadTime) < 60 * 60 * 1000) { + if (gif) { + loadingRecentGifs = false; + } else { + loadingRecentStickers[type] = false; + } + return; + } } if (gif) { TLRPC.TL_messages_getSavedGifs req = new TLRPC.TL_messages_getSavedGifs(); @@ -360,16 +540,31 @@ public class StickersQuery { } }); } else { - TLRPC.TL_messages_getRecentStickers req = new TLRPC.TL_messages_getRecentStickers(); - req.hash = calcDocumentsHash(recentStickers[type]); - req.attached = type == TYPE_MASK; - ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + TLObject request; + if (type == TYPE_FAVE) { + TLRPC.TL_messages_getFavedStickers req = new TLRPC.TL_messages_getFavedStickers(); + req.hash = calcDocumentsHash(recentStickers[type]); + request = req; + } else { + TLRPC.TL_messages_getRecentStickers req = new TLRPC.TL_messages_getRecentStickers(); + req.hash = calcDocumentsHash(recentStickers[type]); + req.attached = type == TYPE_MASK; + request = req; + } + ConnectionsManager.getInstance().sendRequest(request, new RequestDelegate() { @Override public void run(TLObject response, TLRPC.TL_error error) { ArrayList arrayList = null; - if (response instanceof TLRPC.TL_messages_recentStickers) { - TLRPC.TL_messages_recentStickers res = (TLRPC.TL_messages_recentStickers) response; - arrayList = res.stickers; + if (type == TYPE_FAVE) { + if (response instanceof TLRPC.TL_messages_favedStickers) { + TLRPC.TL_messages_favedStickers res = (TLRPC.TL_messages_favedStickers) response; + arrayList = res.stickers; + } + } else { + if (response instanceof TLRPC.TL_messages_recentStickers) { + TLRPC.TL_messages_recentStickers res = (TLRPC.TL_messages_recentStickers) response; + arrayList = res.stickers; + } } processLoadedRecentDocuments(type, arrayList, gif, 0); } @@ -385,10 +580,29 @@ public class StickersQuery { public void run() { try { SQLiteDatabase database = MessagesStorage.getInstance().getDatabase(); - int maxCount = gif ? MessagesController.getInstance().maxRecentGifsCount : MessagesController.getInstance().maxRecentStickersCount; + int maxCount; + if (gif) { + maxCount = MessagesController.getInstance().maxRecentGifsCount; + } else { + if (type == TYPE_FAVE) { + maxCount = MessagesController.getInstance().maxFaveStickersCount; + } else { + maxCount = MessagesController.getInstance().maxRecentStickersCount; + } + } database.beginTransaction(); SQLitePreparedStatement state = database.executeFast("REPLACE INTO web_recent_v3 VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); int count = documents.size(); + int cacheType; + if (gif) { + cacheType = 2; + } else if (type == TYPE_IMAGE) { + cacheType = 3; + } else if (type == TYPE_MASK) { + cacheType = 4; + } else { + cacheType = 5; + } for (int a = 0; a < count; a++) { if (a == maxCount) { break; @@ -396,7 +610,7 @@ public class StickersQuery { TLRPC.Document document = documents.get(a); state.requery(); state.bindString(1, "" + document.id); - state.bindInteger(2, gif ? 2 : (type == TYPE_IMAGE ? 3 : 4)); + state.bindInteger(2, cacheType); state.bindString(3, ""); state.bindString(4, ""); state.bindString(5, ""); @@ -417,7 +631,7 @@ public class StickersQuery { if (documents.size() >= maxCount) { database.beginTransaction(); for (int a = maxCount; a < documents.size(); a++) { - database.executeFast("DELETE FROM web_recent_v3 WHERE id = '" + documents.get(a).id + "'").stepThis().dispose(); + database.executeFast("DELETE FROM web_recent_v3 WHERE id = '" + documents.get(a).id + "' AND type = " + cacheType).stepThis().dispose(); } database.commitTransaction(); } @@ -439,7 +653,13 @@ public class StickersQuery { } else { loadingRecentStickers[type] = false; recentStickersLoaded[type] = true; - editor.putLong("lastStickersLoadTime", System.currentTimeMillis()).commit(); + if (type == TYPE_IMAGE) { + editor.putLong("lastStickersLoadTime", System.currentTimeMillis()).commit(); + } else if (type == TYPE_MASK) { + editor.putLong("lastStickersLoadTimeMask", System.currentTimeMillis()).commit(); + } else { + editor.putLong("lastStickersLoadTimeFavs", System.currentTimeMillis()).commit(); + } } if (documents != null) { if (gif) { @@ -448,6 +668,8 @@ public class StickersQuery { recentStickers[type] = documents; } NotificationCenter.getInstance().postNotificationName(NotificationCenter.recentDocumentsDidLoaded, gif, type); + } else { + } } }); @@ -749,7 +971,7 @@ public class StickersQuery { } public static void markFaturedStickersByIdAsRead(final long id) { - if (!unreadStickerSets.contains(id) || readingStickerSets.contains(id)) { //TODO + if (!unreadStickerSets.contains(id) || readingStickerSets.contains(id)) { return; } readingStickerSets.add(id); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/CustomTabsCallback.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/CustomTabsCallback.java index f5cb4a320..80cc01eb8 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/CustomTabsCallback.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/CustomTabsCallback.java @@ -22,19 +22,79 @@ import android.os.Bundle; * A callback class for custom tabs client to get messages regarding events in their custom tabs. */ public class CustomTabsCallback { + /** + * Sent when the tab has started loading a page. + */ public static final int NAVIGATION_STARTED = 1; + + /** + * Sent when the tab has finished loading a page. + */ public static final int NAVIGATION_FINISHED = 2; + + /** + * Sent when the tab couldn't finish loading due to a failure. + */ public static final int NAVIGATION_FAILED = 3; + + /** + * Sent when loading was aborted by a user action before it finishes like clicking on a link + * or refreshing the page. + */ public static final int NAVIGATION_ABORTED = 4; + + /** + * Sent when the tab becomes visible. + */ public static final int TAB_SHOWN = 5; + + /** + * Sent when the tab becomes hidden. + */ public static final int TAB_HIDDEN = 6; - public CustomTabsCallback() { - } + /** + * To be called when a navigation event happens. + * + * @param navigationEvent The code corresponding to the navigation event. + * @param extras Reserved for future use. + */ + public void onNavigationEvent(int navigationEvent, Bundle extras) {} - public void onNavigationEvent(int navigationEvent, Bundle extras) { - } + /** + * Unsupported callbacks that may be provided by the implementation. + * + *

    + * Note:Clients should never rely on this callback to be + * called and/or to have a defined behavior, as it is entirely implementation-defined and not + * supported. + * + *

    This can be used by implementations to add extra callbacks, for testing or experimental + * purposes. + * + * @param callbackName Name of the extra callback. + * @param args Arguments for the calback + */ + public void extraCallback(String callbackName, Bundle args) {} - public void extraCallback(String callbackName, Bundle args) { - } + /** + * Called when {@link CustomTabsSession} has requested a postMessage channel through + * {@link CustomTabsService#requestPostMessageChannel( + * CustomTabsSessionToken, android.net.Uri)} and the channel + * is ready for sending and receiving messages on both ends. + * + * @param extras Reserved for future use. + */ + public void onMessageChannelReady(Bundle extras) {} + + /** + * Called when a tab controlled by this {@link CustomTabsSession} has sent a postMessage. + * If postMessage() is called from a single thread, then the messages will be posted in the + * same order. When received on the client side, it is the client's responsibility to preserve + * the ordering further. + * + * @param message The message sent. + * @param extras Reserved for future use. + */ + public void onPostMessage(String message, Bundle extras) {} } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/CustomTabsClient.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/CustomTabsClient.java index d80d30b54..8c568e548 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/CustomTabsClient.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/CustomTabsClient.java @@ -19,68 +19,226 @@ package org.telegram.messenger.support.customtabs; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.os.RemoteException; +import android.support.annotation.Nullable; import android.text.TextUtils; +import java.util.ArrayList; +import java.util.List; + public class CustomTabsClient { private final ICustomTabsService mService; private final ComponentName mServiceComponentName; CustomTabsClient(ICustomTabsService service, ComponentName componentName) { - this.mService = service; - this.mServiceComponentName = componentName; + mService = service; + mServiceComponentName = componentName; } - public static boolean bindCustomTabsService(Context context, String packageName, CustomTabsServiceConnection connection) { - Intent intent = new Intent("android.support.customtabs.action.CustomTabsService"); - if (!TextUtils.isEmpty(packageName)) { - intent.setPackage(packageName); + /** + * Bind to a {@link CustomTabsService} using the given package name and + * {@link ServiceConnection}. + * @param context {@link Context} to use while calling + * {@link Context#bindService(Intent, ServiceConnection, int)} + * @param packageName Package name to set on the {@link Intent} for binding. + * @param connection {@link CustomTabsServiceConnection} to use when binding. This will + * return a {@link CustomTabsClient} on + * {@link CustomTabsServiceConnection + * #onCustomTabsServiceConnected(ComponentName, CustomTabsClient)} + * @return Whether the binding was successful. + */ + public static boolean bindCustomTabsService(Context context, + String packageName, CustomTabsServiceConnection connection) { + Intent intent = new Intent(CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION); + if (!TextUtils.isEmpty(packageName)) intent.setPackage(packageName); + return context.bindService(intent, connection, + Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY); + } + + /** + * Returns the preferred package to use for Custom Tabs, preferring the default VIEW handler. + * + * @see #getPackageName(Context, List, boolean) + */ + public static String getPackageName(Context context, @Nullable List packages) { + return getPackageName(context, packages, false); + } + + /** + * Returns the preferred package to use for Custom Tabs. + * + * The preferred package name is the default VIEW intent handler as long as it supports Custom + * Tabs. To modify this preferred behavior, set ignoreDefault to true and give a + * non empty list of package names in packages. + * + * @param context {@link Context} to use for querying the packages. + * @param packages Ordered list of packages to test for Custom Tabs support, in + * decreasing order of priority. + * @param ignoreDefault If set, the default VIEW handler won't get priority over other browsers. + * @return The preferred package name for handling Custom Tabs, or null. + */ + public static String getPackageName( + Context context, @Nullable List packages, boolean ignoreDefault) { + PackageManager pm = context.getPackageManager(); + + List packageNames = packages == null ? new ArrayList() : packages; + Intent activityIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://")); + + if (!ignoreDefault) { + ResolveInfo defaultViewHandlerInfo = pm.resolveActivity(activityIntent, 0); + if (defaultViewHandlerInfo != null) { + String packageName = defaultViewHandlerInfo.activityInfo.packageName; + packageNames = new ArrayList<>(packageNames.size() + 1); + packageNames.add(packageName); + if (packages != null) packageNames.addAll(packages); + } } - return context.bindService(intent, connection, 33); + Intent serviceIntent = new Intent(CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION); + for (String packageName : packageNames) { + serviceIntent.setPackage(packageName); + if (pm.resolveService(serviceIntent, 0) != null) return packageName; + } + return null; } - public boolean warmup(long flags) { + /** + * Connects to the Custom Tabs warmup service, and initializes the browser. + * + * This convenience method connects to the service, and immediately warms up the Custom Tabs + * implementation. Since service connection is asynchronous, the return code is not the return + * code of warmup. + * This call is optional, and clients are encouraged to connect to the service, call + * warmup() and create a session. In this case, calling this method is not + * necessary. + * + * @param context {@link Context} to use to connect to the remote service. + * @param packageName Package name of the target implementation. + * @return Whether the binding was successful. + */ + public static boolean connectAndInitialize(Context context, String packageName) { + if (packageName == null) return false; + final Context applicationContext = context.getApplicationContext(); + CustomTabsServiceConnection connection = new CustomTabsServiceConnection() { + @Override + public final void onCustomTabsServiceConnected( + ComponentName name, CustomTabsClient client) { + client.warmup(0); + // Unbinding immediately makes the target process "Empty", provided that it is + // not used by anyone else, and doesn't contain any Activity. This makes it + // likely to get killed, but is preferable to keeping the connection around. + applicationContext.unbindService(this); + } + + @Override + public final void onServiceDisconnected(ComponentName componentName) { } + }; try { - return this.mService.warmup(flags); - } catch (RemoteException var4) { + return bindCustomTabsService(applicationContext, packageName, connection); + } catch (SecurityException e) { return false; } } + /** + * Warm up the browser process. + * + * Allows the browser application to pre-initialize itself in the background. Significantly + * speeds up URL opening in the browser. This is asynchronous and can be called several times. + * + * @param flags Reserved for future use. + * @return Whether the warmup was successful. + */ + public boolean warmup(long flags) { + try { + return mService.warmup(flags); + } catch (RemoteException e) { + return false; + } + } + + /** + * Creates a new session through an ICustomTabsService with the optional callback. This session + * can be used to associate any related communication through the service with an intent and + * then later with a Custom Tab. The client can then send later service calls or intents to + * through same session-intent-Custom Tab association. + * @param callback The callback through which the client will receive updates about the created + * session. Can be null. All the callbacks will be received on the UI thread. + * @return The session object that was created as a result of the transaction. The client can + * use this to relay session specific calls. + * Null on error. + */ public CustomTabsSession newSession(final CustomTabsCallback callback) { ICustomTabsCallback.Stub wrapper = new ICustomTabsCallback.Stub() { - public void onNavigationEvent(int navigationEvent, Bundle extras) { - if (callback != null) { - callback.onNavigationEvent(navigationEvent, extras); - } + private Handler mHandler = new Handler(Looper.getMainLooper()); + @Override + public void onNavigationEvent(final int navigationEvent, final Bundle extras) { + if (callback == null) return; + mHandler.post(new Runnable() { + @Override + public void run() { + callback.onNavigationEvent(navigationEvent, extras); + } + }); } - public void extraCallback(String callbackName, Bundle args) throws RemoteException { - if (callback != null) { - callback.extraCallback(callbackName, args); - } + @Override + public void extraCallback(final String callbackName, final Bundle args) + throws RemoteException { + if (callback == null) return; + mHandler.post(new Runnable() { + @Override + public void run() { + callback.extraCallback(callbackName, args); + } + }); + } + @Override + public void onMessageChannelReady(final Bundle extras) + throws RemoteException { + if (callback == null) return; + mHandler.post(new Runnable() { + @Override + public void run() { + callback.onMessageChannelReady(extras); + } + }); + } + + @Override + public void onPostMessage(final String message, final Bundle extras) + throws RemoteException { + if (callback == null) return; + mHandler.post(new Runnable() { + @Override + public void run() { + callback.onPostMessage(message, extras); + } + }); } }; try { - if (!this.mService.newSession(wrapper)) { - return null; - } - } catch (RemoteException var4) { + if (!mService.newSession(wrapper)) return null; + } catch (RemoteException e) { return null; } - - return new CustomTabsSession(this.mService, wrapper, this.mServiceComponentName); + return new CustomTabsSession(mService, wrapper, mServiceComponentName); } public Bundle extraCommand(String commandName, Bundle args) { try { - return this.mService.extraCommand(commandName, args); - } catch (RemoteException var4) { + return mService.extraCommand(commandName, args); + } catch (RemoteException e) { return null; } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/CustomTabsIntent.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/CustomTabsIntent.java index 16bf9526f..9536ca933 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/CustomTabsIntent.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/CustomTabsIntent.java @@ -21,15 +21,18 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; +import android.graphics.Color; import android.net.Uri; import android.os.Bundle; import android.support.annotation.AnimRes; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityOptionsCompat; import android.support.v4.app.BundleCompat; +import android.support.v4.content.ContextCompat; +import android.view.View; +import android.widget.RemoteViews; import java.util.ArrayList; @@ -41,35 +44,222 @@ import java.util.ArrayList; * You are strongly encouraged to use {@link CustomTabsIntent.Builder}.

    */ public final class CustomTabsIntent { - public static final String EXTRA_SESSION = "android.support.customtabs.extra.SESSION"; - public static final String EXTRA_TOOLBAR_COLOR = "android.support.customtabs.extra.TOOLBAR_COLOR"; - public static final String EXTRA_ENABLE_URLBAR_HIDING = "android.support.customtabs.extra.ENABLE_URLBAR_HIDING"; - public static final String EXTRA_CLOSE_BUTTON_ICON = "android.support.customtabs.extra.CLOSE_BUTTON_ICON"; - public static final String EXTRA_TITLE_VISIBILITY_STATE = "android.support.customtabs.extra.TITLE_VISIBILITY"; - public static final int NO_TITLE = 0; - public static final int SHOW_PAGE_TITLE = 1; - public static final String EXTRA_ACTION_BUTTON_BUNDLE = "android.support.customtabs.extra.ACTION_BUTTON_BUNDLE"; - public static final String EXTRA_TOOLBAR_ITEMS = "android.support.customtabs.extra.TOOLBAR_ITEMS"; - public static final String EXTRA_SECONDARY_TOOLBAR_COLOR = "android.support.customtabs.extra.SECONDARY_TOOLBAR_COLOR"; - public static final String KEY_ICON = "android.support.customtabs.customaction.ICON"; - public static final String KEY_DESCRIPTION = "android.support.customtabs.customaction.DESCRIPTION"; - public static final String KEY_PENDING_INTENT = "android.support.customtabs.customaction.PENDING_INTENT"; - public static final String EXTRA_TINT_ACTION_BUTTON = "android.support.customtabs.extra.TINT_ACTION_BUTTON"; - public static final String EXTRA_MENU_ITEMS = "android.support.customtabs.extra.MENU_ITEMS"; - public static final String KEY_MENU_ITEM_TITLE = "android.support.customtabs.customaction.MENU_ITEM_TITLE"; - public static final String EXTRA_EXIT_ANIMATION_BUNDLE = "android.support.customtabs.extra.EXIT_ANIMATION_BUNDLE"; - public static final String EXTRA_DEFAULT_SHARE_MENU_ITEM = "android.support.customtabs.extra.SHARE_MENU_ITEM"; - public static final String KEY_ID = "android.support.customtabs.customaction.ID"; - public static final int TOOLBAR_ACTION_BUTTON_ID = 0; - private static final int MAX_TOOLBAR_ITEMS = 5; - @NonNull - public final Intent intent; - @Nullable - public final Bundle startAnimationBundle; - public void launchUrl(Activity context, Uri url) { - this.intent.setData(url); - ActivityCompat.startActivity(context, this.intent, this.startAnimationBundle); + /** + * Indicates that the user explicitly opted out of Custom Tabs in the calling application. + *

    + * If an application provides a mechanism for users to opt out of Custom Tabs, this extra should + * be provided with {@link Intent#FLAG_ACTIVITY_NEW_TASK} to ensure the browser does not attempt + * to trigger any Custom Tab-like experiences as a result of the VIEW intent. + *

    + * If this extra is present with {@link Intent#FLAG_ACTIVITY_NEW_TASK}, all Custom Tabs + * customizations will be ignored. + */ + private static final String EXTRA_USER_OPT_OUT_FROM_CUSTOM_TABS = + "android.support.customtabs.extra.user_opt_out"; + + /** + * Extra used to match the session. This has to be included in the intent to open in + * a custom tab. This is the same IBinder that gets passed to ICustomTabsService#newSession. + * Null if there is no need to match any service side sessions with the intent. + */ + public static final String EXTRA_SESSION = "android.support.customtabs.extra.SESSION"; + + /** + * Extra that changes the background color for the toolbar. colorRes is an int that specifies a + * {@link Color}, not a resource id. + */ + public static final String EXTRA_TOOLBAR_COLOR = + "android.support.customtabs.extra.TOOLBAR_COLOR"; + + /** + * Boolean extra that enables the url bar to hide as the user scrolls down the page + */ + public static final String EXTRA_ENABLE_URLBAR_HIDING = + "android.support.customtabs.extra.ENABLE_URLBAR_HIDING"; + + /** + * Extra bitmap that specifies the icon of the back button on the toolbar. If the client chooses + * not to customize it, a default close button will be used. + */ + public static final String EXTRA_CLOSE_BUTTON_ICON = + "android.support.customtabs.extra.CLOSE_BUTTON_ICON"; + + /** + * Extra (int) that specifies state for showing the page title. Default is {@link #NO_TITLE}. + */ + public static final String EXTRA_TITLE_VISIBILITY_STATE = + "android.support.customtabs.extra.TITLE_VISIBILITY"; + + /** + * Don't show any title. Shows only the domain. + */ + public static final int NO_TITLE = 0; + + /** + * Shows the page title and the domain. + */ + public static final int SHOW_PAGE_TITLE = 1; + + /** + * Bundle used for adding a custom action button to the custom tab toolbar. The client should + * provide a description, an icon {@link Bitmap} and a {@link PendingIntent} for the button. + * All three keys must be present. + */ + public static final String EXTRA_ACTION_BUTTON_BUNDLE = + "android.support.customtabs.extra.ACTION_BUTTON_BUNDLE"; + + /** + * List used for adding items to the top and bottom toolbars. The client should + * provide an ID, a description, an icon {@link Bitmap} for each item. They may also provide a + * {@link PendingIntent} if the item is a button. + */ + public static final String EXTRA_TOOLBAR_ITEMS = + "android.support.customtabs.extra.TOOLBAR_ITEMS"; + + /** + * Extra that changes the background color for the secondary toolbar. The value should be an + * int that specifies a {@link Color}, not a resource id. + */ + public static final String EXTRA_SECONDARY_TOOLBAR_COLOR = + "android.support.customtabs.extra.SECONDARY_TOOLBAR_COLOR"; + + /** + * Key that specifies the {@link Bitmap} to be used as the image source for the action button. + * The icon should't be more than 24dp in height (No padding needed. The button itself will be + * 48dp in height) and have a width/height ratio of less than 2. + */ + public static final String KEY_ICON = "android.support.customtabs.customaction.ICON"; + + /** + * Key that specifies the content description for the custom action button. + */ + public static final String KEY_DESCRIPTION = + "android.support.customtabs.customaction.DESCRIPTION"; + + /** + * Key that specifies the PendingIntent to launch when the action button or menu item was + * clicked. The custom tab will be calling {@link PendingIntent#send()} on clicks after adding + * the url as data. The client app can call {@link Intent#getDataString()} to get the url. + */ + public static final String KEY_PENDING_INTENT = + "android.support.customtabs.customaction.PENDING_INTENT"; + + /** + * Extra boolean that specifies whether the custom action button should be tinted. Default is + * false and the action button will not be tinted. + */ + public static final String EXTRA_TINT_ACTION_BUTTON = + "android.support.customtabs.extra.TINT_ACTION_BUTTON"; + + /** + * Use an {@code ArrayList} for specifying menu related params. There should be a + * separate {@link Bundle} for each custom menu item. + */ + public static final String EXTRA_MENU_ITEMS = "android.support.customtabs.extra.MENU_ITEMS"; + + /** + * Key for specifying the title of a menu item. + */ + public static final String KEY_MENU_ITEM_TITLE = + "android.support.customtabs.customaction.MENU_ITEM_TITLE"; + + /** + * Bundle constructed out of {@link ActivityOptionsCompat} that will be running when the + * {@link Activity} that holds the custom tab gets finished. A similar ActivityOptions + * for creation should be constructed and given to the startActivity() call that + * launches the custom tab. + */ + public static final String EXTRA_EXIT_ANIMATION_BUNDLE = + "android.support.customtabs.extra.EXIT_ANIMATION_BUNDLE"; + + /** + * Boolean extra that specifies whether a default share button will be shown in the menu. + */ + public static final String EXTRA_DEFAULT_SHARE_MENU_ITEM = + "android.support.customtabs.extra.SHARE_MENU_ITEM"; + + /** + * Extra that specifies the {@link RemoteViews} showing on the secondary toolbar. If this extra + * is set, the other secondary toolbar configurations will be overriden. The height of the + * {@link RemoteViews} should not exceed 56dp. + * @see CustomTabsIntent.Builder#setSecondaryToolbarViews(RemoteViews, int[], PendingIntent). + */ + public static final String EXTRA_REMOTEVIEWS = + "android.support.customtabs.extra.EXTRA_REMOTEVIEWS"; + + /** + * Extra that specifies an array of {@link View} ids. When these {@link View}s are clicked, a + * {@link PendingIntent} will be sent, carrying the current url of the custom tab as data. + *

    + * Note that Custom Tabs will override the default onClick behavior of the listed {@link View}s. + * If you do not care about the current url, you can safely ignore this extra and use + * {@link RemoteViews#setOnClickPendingIntent(int, PendingIntent)} instead. + * @see CustomTabsIntent.Builder#setSecondaryToolbarViews(RemoteViews, int[], PendingIntent). + */ + public static final String EXTRA_REMOTEVIEWS_VIEW_IDS = + "android.support.customtabs.extra.EXTRA_REMOTEVIEWS_VIEW_IDS"; + + /** + * Extra that specifies the {@link PendingIntent} to be sent when the user clicks on the + * {@link View}s that is listed by {@link #EXTRA_REMOTEVIEWS_VIEW_IDS}. + *

    + * Note when this {@link PendingIntent} is triggered, it will have the current url as data + * field, also the id of the clicked {@link View}, specified by + * {@link #EXTRA_REMOTEVIEWS_CLICKED_ID}. + * @see CustomTabsIntent.Builder#setSecondaryToolbarViews(RemoteViews, int[], PendingIntent). + */ + public static final String EXTRA_REMOTEVIEWS_PENDINGINTENT = + "android.support.customtabs.extra.EXTRA_REMOTEVIEWS_PENDINGINTENT"; + + /** + * Extra that specifies which {@link View} has been clicked. This extra will be put to the + * {@link PendingIntent} sent from Custom Tabs when a view in the {@link RemoteViews} is clicked + * @see CustomTabsIntent.Builder#setSecondaryToolbarViews(RemoteViews, int[], PendingIntent). + */ + public static final String EXTRA_REMOTEVIEWS_CLICKED_ID = + "android.support.customtabs.extra.EXTRA_REMOTEVIEWS_CLICKED_ID"; + + /** + * Extra that specifies whether Instant Apps is enabled. + */ + public static final String EXTRA_ENABLE_INSTANT_APPS = + "android.support.customtabs.extra.EXTRA_ENABLE_INSTANT_APPS"; + + /** + * Key that specifies the unique ID for an action button. To make a button to show on the + * toolbar, use {@link #TOOLBAR_ACTION_BUTTON_ID} as its ID. + */ + public static final String KEY_ID = "android.support.customtabs.customaction.ID"; + + /** + * The ID allocated to the custom action button that is shown on the toolbar. + */ + public static final int TOOLBAR_ACTION_BUTTON_ID = 0; + + /** + * The maximum allowed number of toolbar items. + */ + private static final int MAX_TOOLBAR_ITEMS = 5; + + /** + * An {@link Intent} used to start the Custom Tabs Activity. + */ + @NonNull public final Intent intent; + + /** + * A {@link Bundle} containing the start animation for the Custom Tabs Activity. + */ + @Nullable public final Bundle startAnimationBundle; + + /** + * Convenience method to launch a Custom Tabs Activity. + * @param context The source Context. + * @param url The URL to load in the Custom Tab. + */ + public void launchUrl(Context context, Uri url) { + intent.setData(url); + ContextCompat.startActivity(context, intent, startAnimationBundle); } private CustomTabsIntent(Intent intent, Bundle startAnimationBundle) { @@ -77,130 +267,296 @@ public final class CustomTabsIntent { this.startAnimationBundle = startAnimationBundle; } - public static int getMaxToolbarItems() { - return 5; - } - + /** + * Builder class for {@link CustomTabsIntent} objects. + */ public static final class Builder { - private final Intent mIntent; - private ArrayList mMenuItems; - private Bundle mStartAnimationBundle; - private ArrayList mActionButtons; + private final Intent mIntent = new Intent(Intent.ACTION_VIEW); + private ArrayList mMenuItems = null; + private Bundle mStartAnimationBundle = null; + private ArrayList mActionButtons = null; + private boolean mInstantAppsEnabled = true; + /** + * Creates a {@link CustomTabsIntent.Builder} object associated with no + * {@link CustomTabsSession}. + */ public Builder() { this(null); } + /** + * Creates a {@link CustomTabsIntent.Builder} object associated with a given + * {@link CustomTabsSession}. + * + * Guarantees that the {@link Intent} will be sent to the same component as the one the + * session is associated with. + * + * @param session The session to associate this Builder with. + */ public Builder(@Nullable CustomTabsSession session) { - this.mIntent = new Intent("android.intent.action.VIEW"); - this.mMenuItems = null; - this.mStartAnimationBundle = null; - this.mActionButtons = null; - if (session != null) { - this.mIntent.setPackage(session.getComponentName().getPackageName()); - } - + if (session != null) mIntent.setPackage(session.getComponentName().getPackageName()); Bundle bundle = new Bundle(); - BundleCompat.putBinder(bundle, "android.support.customtabs.extra.SESSION", session == null ? null : session.getBinder()); - this.mIntent.putExtras(bundle); + BundleCompat.putBinder( + bundle, EXTRA_SESSION, session == null ? null : session.getBinder()); + mIntent.putExtras(bundle); } - public CustomTabsIntent.Builder setToolbarColor(@ColorInt int color) { - this.mIntent.putExtra("android.support.customtabs.extra.TOOLBAR_COLOR", color); + /** + * Sets the toolbar color. + * + * @param color {@link Color} + */ + public Builder setToolbarColor(@ColorInt int color) { + mIntent.putExtra(EXTRA_TOOLBAR_COLOR, color); return this; } - public CustomTabsIntent.Builder enableUrlBarHiding() { - this.mIntent.putExtra("android.support.customtabs.extra.ENABLE_URLBAR_HIDING", true); + /** + * Enables the url bar to hide as the user scrolls down on the page. + */ + public Builder enableUrlBarHiding() { + mIntent.putExtra(EXTRA_ENABLE_URLBAR_HIDING, true); return this; } - public CustomTabsIntent.Builder setCloseButtonIcon(@NonNull Bitmap icon) { - this.mIntent.putExtra("android.support.customtabs.extra.CLOSE_BUTTON_ICON", icon); + /** + * Sets the Close button icon for the custom tab. + * + * @param icon The icon {@link Bitmap} + */ + public Builder setCloseButtonIcon(@NonNull Bitmap icon) { + mIntent.putExtra(EXTRA_CLOSE_BUTTON_ICON, icon); return this; } - public CustomTabsIntent.Builder setShowTitle(boolean showTitle) { - this.mIntent.putExtra("android.support.customtabs.extra.TITLE_VISIBILITY", showTitle ? 1 : 0); + /** + * Sets whether the title should be shown in the custom tab. + * + * @param showTitle Whether the title should be shown. + */ + public Builder setShowTitle(boolean showTitle) { + mIntent.putExtra(EXTRA_TITLE_VISIBILITY_STATE, + showTitle ? SHOW_PAGE_TITLE : NO_TITLE); return this; } - public CustomTabsIntent.Builder addMenuItem(@NonNull String label, @NonNull PendingIntent pendingIntent) { - if (this.mMenuItems == null) { - this.mMenuItems = new ArrayList(); - } - + /** + * Adds a menu item. + * + * @param label Menu label. + * @param pendingIntent Pending intent delivered when the menu item is clicked. + */ + public Builder addMenuItem(@NonNull String label, @NonNull PendingIntent pendingIntent) { + if (mMenuItems == null) mMenuItems = new ArrayList<>(); Bundle bundle = new Bundle(); - bundle.putString("android.support.customtabs.customaction.MENU_ITEM_TITLE", label); - bundle.putParcelable("android.support.customtabs.customaction.PENDING_INTENT", pendingIntent); - this.mMenuItems.add(bundle); + bundle.putString(KEY_MENU_ITEM_TITLE, label); + bundle.putParcelable(KEY_PENDING_INTENT, pendingIntent); + mMenuItems.add(bundle); return this; } - public CustomTabsIntent.Builder addDefaultShareMenuItem() { - this.mIntent.putExtra("android.support.customtabs.extra.SHARE_MENU_ITEM", true); + /** + * Adds a default share item to the menu. + */ + public Builder addDefaultShareMenuItem() { + mIntent.putExtra(EXTRA_DEFAULT_SHARE_MENU_ITEM, true); return this; } - public CustomTabsIntent.Builder setActionButton(@NonNull Bitmap icon, @NonNull String description, @NonNull PendingIntent pendingIntent, boolean shouldTint) { + /** + * Sets the action button that is displayed in the Toolbar. + *

    + * This is equivalent to calling + * {@link CustomTabsIntent.Builder#addToolbarItem(int, Bitmap, String, PendingIntent)} + * with {@link #TOOLBAR_ACTION_BUTTON_ID} as id. + * + * @param icon The icon. + * @param description The description for the button. To be used for accessibility. + * @param pendingIntent pending intent delivered when the button is clicked. + * @param shouldTint Whether the action button should be tinted. + * + * @see CustomTabsIntent.Builder#addToolbarItem(int, Bitmap, String, PendingIntent) + */ + public Builder setActionButton(@NonNull Bitmap icon, @NonNull String description, + @NonNull PendingIntent pendingIntent, boolean shouldTint) { Bundle bundle = new Bundle(); - bundle.putInt("android.support.customtabs.customaction.ID", 0); - bundle.putParcelable("android.support.customtabs.customaction.ICON", icon); - bundle.putString("android.support.customtabs.customaction.DESCRIPTION", description); - bundle.putParcelable("android.support.customtabs.customaction.PENDING_INTENT", pendingIntent); - this.mIntent.putExtra("android.support.customtabs.extra.ACTION_BUTTON_BUNDLE", bundle); - this.mIntent.putExtra("android.support.customtabs.extra.TINT_ACTION_BUTTON", shouldTint); + bundle.putInt(KEY_ID, TOOLBAR_ACTION_BUTTON_ID); + bundle.putParcelable(KEY_ICON, icon); + bundle.putString(KEY_DESCRIPTION, description); + bundle.putParcelable(KEY_PENDING_INTENT, pendingIntent); + mIntent.putExtra(EXTRA_ACTION_BUTTON_BUNDLE, bundle); + mIntent.putExtra(EXTRA_TINT_ACTION_BUTTON, shouldTint); return this; } - public CustomTabsIntent.Builder setActionButton(@NonNull Bitmap icon, @NonNull String description, @NonNull PendingIntent pendingIntent) { - return this.setActionButton(icon, description, pendingIntent, false); + /** + * Sets the action button that is displayed in the Toolbar with default tinting behavior. + * + * @see CustomTabsIntent.Builder#setActionButton( + * Bitmap, String, PendingIntent, boolean) + */ + public Builder setActionButton(@NonNull Bitmap icon, @NonNull String description, + @NonNull PendingIntent pendingIntent) { + return setActionButton(icon, description, pendingIntent, false); } - public CustomTabsIntent.Builder addToolbarItem(int id, @NonNull Bitmap icon, @NonNull String description, PendingIntent pendingIntent) throws IllegalStateException { - if (this.mActionButtons == null) { - this.mActionButtons = new ArrayList(); + /** + * Adds an action button to the custom tab. Multiple buttons can be added via this method. + * If the given id equals {@link #TOOLBAR_ACTION_BUTTON_ID}, the button will be placed on + * the toolbar; if the bitmap is too wide, it will be put to the bottom bar instead. If + * the id is not {@link #TOOLBAR_ACTION_BUTTON_ID}, it will be directly put on secondary + * toolbar. The maximum number of allowed toolbar items in a single intent is + * {@link CustomTabsIntent#getMaxToolbarItems()}. Throws an + * {@link IllegalStateException} when that number is exceeded per intent. + * + * @param id The unique id of the action button. This should be non-negative. + * @param icon The icon. + * @param description The description for the button. To be used for accessibility. + * @param pendingIntent The pending intent delivered when the button is clicked. + * + * @see CustomTabsIntent#getMaxToolbarItems() + * @deprecated Use + * CustomTabsIntent.Builder#setSecondaryToolbarViews(RemoteViews, int[], PendingIntent). + */ + @Deprecated + public Builder addToolbarItem(int id, @NonNull Bitmap icon, @NonNull String description, + PendingIntent pendingIntent) throws IllegalStateException { + if (mActionButtons == null) { + mActionButtons = new ArrayList<>(); } - - if (this.mActionButtons.size() >= 5) { - throw new IllegalStateException("Exceeded maximum toolbar item count of 5"); - } else { - Bundle bundle = new Bundle(); - bundle.putInt("android.support.customtabs.customaction.ID", id); - bundle.putParcelable("android.support.customtabs.customaction.ICON", icon); - bundle.putString("android.support.customtabs.customaction.DESCRIPTION", description); - bundle.putParcelable("android.support.customtabs.customaction.PENDING_INTENT", pendingIntent); - this.mActionButtons.add(bundle); - return this; + if (mActionButtons.size() >= MAX_TOOLBAR_ITEMS) { + throw new IllegalStateException( + "Exceeded maximum toolbar item count of " + MAX_TOOLBAR_ITEMS); } - } - - public CustomTabsIntent.Builder setSecondaryToolbarColor(@ColorInt int color) { - this.mIntent.putExtra("android.support.customtabs.extra.SECONDARY_TOOLBAR_COLOR", color); + Bundle bundle = new Bundle(); + bundle.putInt(KEY_ID, id); + bundle.putParcelable(KEY_ICON, icon); + bundle.putString(KEY_DESCRIPTION, description); + bundle.putParcelable(KEY_PENDING_INTENT, pendingIntent); + mActionButtons.add(bundle); return this; } - public CustomTabsIntent.Builder setStartAnimations(@NonNull Context context, @AnimRes int enterResId, @AnimRes int exitResId) { - this.mStartAnimationBundle = ActivityOptionsCompat.makeCustomAnimation(context, enterResId, exitResId).toBundle(); + /** + * Sets the color of the secondary toolbar. + * @param color The color for the secondary toolbar. + */ + public Builder setSecondaryToolbarColor(@ColorInt int color) { + mIntent.putExtra(EXTRA_SECONDARY_TOOLBAR_COLOR, color); return this; } - public CustomTabsIntent.Builder setExitAnimations(@NonNull Context context, @AnimRes int enterResId, @AnimRes int exitResId) { - Bundle bundle = ActivityOptionsCompat.makeCustomAnimation(context, enterResId, exitResId).toBundle(); - this.mIntent.putExtra("android.support.customtabs.extra.EXIT_ANIMATION_BUNDLE", bundle); + /** + * Sets the remote views displayed in the secondary toolbar in a custom tab. + * + * @param remoteViews The {@link RemoteViews} that will be shown on the secondary toolbar. + * @param clickableIDs The IDs of clickable views. The onClick event of these views will be + * handled by custom tabs. + * @param pendingIntent The {@link PendingIntent} that will be sent when the user clicks on + * one of the {@link View}s in clickableIDs. When the + * {@link PendingIntent} is sent, it will have the current URL as its + * intent data. + * @see CustomTabsIntent#EXTRA_REMOTEVIEWS + * @see CustomTabsIntent#EXTRA_REMOTEVIEWS_VIEW_IDS + * @see CustomTabsIntent#EXTRA_REMOTEVIEWS_PENDINGINTENT + * @see CustomTabsIntent#EXTRA_REMOTEVIEWS_CLICKED_ID + */ + public Builder setSecondaryToolbarViews(@NonNull RemoteViews remoteViews, + @Nullable int[] clickableIDs, @Nullable PendingIntent pendingIntent) { + mIntent.putExtra(EXTRA_REMOTEVIEWS, remoteViews); + mIntent.putExtra(EXTRA_REMOTEVIEWS_VIEW_IDS, clickableIDs); + mIntent.putExtra(EXTRA_REMOTEVIEWS_PENDINGINTENT, pendingIntent); return this; } + /** + * Sets whether Instant Apps is enabled for this Custom Tab. + + * @param enabled Whether Instant Apps should be enabled. + */ + public Builder setInstantAppsEnabled(boolean enabled) { + mInstantAppsEnabled = enabled; + return this; + } + + /** + * Sets the start animations. + * + * @param context Application context. + * @param enterResId Resource ID of the "enter" animation for the browser. + * @param exitResId Resource ID of the "exit" animation for the application. + */ + public Builder setStartAnimations( + @NonNull Context context, @AnimRes int enterResId, @AnimRes int exitResId) { + mStartAnimationBundle = ActivityOptionsCompat.makeCustomAnimation( + context, enterResId, exitResId).toBundle(); + return this; + } + + /** + * Sets the exit animations. + * + * @param context Application context. + * @param enterResId Resource ID of the "enter" animation for the application. + * @param exitResId Resource ID of the "exit" animation for the browser. + */ + public Builder setExitAnimations( + @NonNull Context context, @AnimRes int enterResId, @AnimRes int exitResId) { + Bundle bundle = ActivityOptionsCompat.makeCustomAnimation( + context, enterResId, exitResId).toBundle(); + mIntent.putExtra(EXTRA_EXIT_ANIMATION_BUNDLE, bundle); + return this; + } + + /** + * Combines all the options that have been set and returns a new {@link CustomTabsIntent} + * object. + */ public CustomTabsIntent build() { - if (this.mMenuItems != null) { - this.mIntent.putParcelableArrayListExtra("android.support.customtabs.extra.MENU_ITEMS", this.mMenuItems); + if (mMenuItems != null) { + mIntent.putParcelableArrayListExtra(CustomTabsIntent.EXTRA_MENU_ITEMS, mMenuItems); } - - if (this.mActionButtons != null) { - this.mIntent.putParcelableArrayListExtra("android.support.customtabs.extra.TOOLBAR_ITEMS", this.mActionButtons); + if (mActionButtons != null) { + mIntent.putParcelableArrayListExtra(EXTRA_TOOLBAR_ITEMS, mActionButtons); } - - return new CustomTabsIntent(this.mIntent, this.mStartAnimationBundle); + mIntent.putExtra(EXTRA_ENABLE_INSTANT_APPS, mInstantAppsEnabled); + return new CustomTabsIntent(mIntent, mStartAnimationBundle); } } + + /** + * @return The maximum number of allowed toolbar items for + * {@link CustomTabsIntent.Builder#addToolbarItem(int, Bitmap, String, PendingIntent)} and + * {@link CustomTabsIntent#EXTRA_TOOLBAR_ITEMS}. + */ + public static int getMaxToolbarItems() { + return MAX_TOOLBAR_ITEMS; + } + + /** + * Adds the necessary flags and extras to signal any browser supporting custom tabs to use the + * browser UI at all times and avoid showing custom tab like UI. Calling this with an intent + * will override any custom tabs related customizations. + * @param intent The intent to modify for always showing browser UI. + * @return The same intent with the necessary flags and extras added. + */ + public static Intent setAlwaysUseBrowserUI(Intent intent) { + if (intent == null) intent = new Intent(Intent.ACTION_VIEW); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(EXTRA_USER_OPT_OUT_FROM_CUSTOM_TABS, true); + return intent; + } + + /** + * Whether a browser receiving the given intent should always use browser UI and avoid using any + * custom tabs UI. + * + * @param intent The intent to check for the required flags and extras. + * @return Whether the browser UI should be used exclusively. + */ + public static boolean shouldAlwaysUseBrowserUI(Intent intent) { + return intent.getBooleanExtra(EXTRA_USER_OPT_OUT_FROM_CUSTOM_TABS, false) + && (intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) != 0; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/CustomTabsService.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/CustomTabsService.java index aa60199f9..e3d4afb6f 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/CustomTabsService.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/CustomTabsService.java @@ -23,83 +23,249 @@ import android.os.Bundle; import android.os.IBinder; import android.os.IBinder.DeathRecipient; import android.os.RemoteException; +import android.support.annotation.IntDef; import android.support.v4.util.ArrayMap; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; +/** + * Abstract service class for implementing Custom Tabs related functionality. The service should + * be responding to the action ACTION_CUSTOM_TABS_CONNECTION. This class should be used by + * implementers that want to provide Custom Tabs functionality, not by clients that want to launch + * Custom Tabs. + */ public abstract class CustomTabsService extends Service { - public static final String ACTION_CUSTOM_TABS_CONNECTION = "android.support.customtabs.action.CustomTabsService"; - public static final String KEY_URL = "android.support.customtabs.otherurls.URL"; - private final Map mDeathRecipientMap = new ArrayMap(); + /** + * The Intent action that a CustomTabsService must respond to. + */ + public static final String ACTION_CUSTOM_TABS_CONNECTION = + "android.support.customtabs.action.CustomTabsService"; + + /** + * For {@link CustomTabsService#mayLaunchUrl} calls that wants to specify more than one url, + * this key can be used with {@link Bundle#putParcelable(String, android.os.Parcelable)} + * to insert a new url to each bundle inside list of bundles. + */ + public static final String KEY_URL = + "android.support.customtabs.otherurls.URL"; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({RESULT_SUCCESS, RESULT_FAILURE_DISALLOWED, + RESULT_FAILURE_REMOTE_ERROR, RESULT_FAILURE_MESSAGING_ERROR}) + public @interface Result { + } + + /** + * Indicates that the postMessage request was accepted. + */ + public static final int RESULT_SUCCESS = 0; + /** + * Indicates that the postMessage request was not allowed due to a bad argument or requesting + * at a disallowed time like when in background. + */ + public static final int RESULT_FAILURE_DISALLOWED = -1; + /** + * Indicates that the postMessage request has failed due to a {@link RemoteException} . + */ + public static final int RESULT_FAILURE_REMOTE_ERROR = -2; + /** + * Indicates that the postMessage request has failed due to an internal error on the browser + * message channel. + */ + public static final int RESULT_FAILURE_MESSAGING_ERROR = -3; + + private final Map mDeathRecipientMap = new ArrayMap<>(); + private ICustomTabsService.Stub mBinder = new ICustomTabsService.Stub() { + + @Override public boolean warmup(long flags) { return CustomTabsService.this.warmup(flags); } + @Override public boolean newSession(ICustomTabsCallback callback) { final CustomTabsSessionToken sessionToken = new CustomTabsSessionToken(callback); - try { - DeathRecipient e = new DeathRecipient() { + DeathRecipient deathRecipient = new IBinder.DeathRecipient() { + @Override public void binderDied() { - CustomTabsService.this.cleanUpSession(sessionToken); + cleanUpSession(sessionToken); } }; - synchronized (CustomTabsService.this.mDeathRecipientMap) { - callback.asBinder().linkToDeath(e, 0); - CustomTabsService.this.mDeathRecipientMap.put(callback.asBinder(), e); + synchronized (mDeathRecipientMap) { + callback.asBinder().linkToDeath(deathRecipient, 0); + mDeathRecipientMap.put(callback.asBinder(), deathRecipient); } - return CustomTabsService.this.newSession(sessionToken); - } catch (RemoteException var7) { + } catch (RemoteException e) { return false; } } - public boolean mayLaunchUrl(ICustomTabsCallback callback, Uri url, Bundle extras, List otherLikelyBundles) { - return CustomTabsService.this.mayLaunchUrl(new CustomTabsSessionToken(callback), url, extras, otherLikelyBundles); + @Override + public boolean mayLaunchUrl(ICustomTabsCallback callback, Uri url, + Bundle extras, List otherLikelyBundles) { + return CustomTabsService.this.mayLaunchUrl( + new CustomTabsSessionToken(callback), url, extras, otherLikelyBundles); } + @Override public Bundle extraCommand(String commandName, Bundle args) { return CustomTabsService.this.extraCommand(commandName, args); } + @Override public boolean updateVisuals(ICustomTabsCallback callback, Bundle bundle) { - return CustomTabsService.this.updateVisuals(new CustomTabsSessionToken(callback), bundle); + return CustomTabsService.this.updateVisuals( + new CustomTabsSessionToken(callback), bundle); + } + + @Override + public boolean requestPostMessageChannel(ICustomTabsCallback callback, + Uri postMessageOrigin) { + return CustomTabsService.this.requestPostMessageChannel( + new CustomTabsSessionToken(callback), postMessageOrigin); + } + + @Override + public int postMessage(ICustomTabsCallback callback, String message, Bundle extras) { + return CustomTabsService.this.postMessage( + new CustomTabsSessionToken(callback), message, extras); } }; - public CustomTabsService() { - } - + @Override public IBinder onBind(Intent intent) { - return this.mBinder; + return mBinder; } + /** + * Called when the client side {@link IBinder} for this {@link CustomTabsSessionToken} is dead. + * Can also be used to clean up {@link DeathRecipient} instances allocated for the given token. + * + * @param sessionToken The session token for which the {@link DeathRecipient} call has been + * received. + * @return Whether the clean up was successful. Multiple calls with two tokens holdings the + * same binder will return false. + */ protected boolean cleanUpSession(CustomTabsSessionToken sessionToken) { try { - Map e = this.mDeathRecipientMap; - synchronized (this.mDeathRecipientMap) { + synchronized (mDeathRecipientMap) { IBinder binder = sessionToken.getCallbackBinder(); - DeathRecipient deathRecipient = this.mDeathRecipientMap.get(binder); + DeathRecipient deathRecipient = + mDeathRecipientMap.get(binder); binder.unlinkToDeath(deathRecipient, 0); - this.mDeathRecipientMap.remove(binder); - return true; + mDeathRecipientMap.remove(binder); } - } catch (NoSuchElementException var7) { + } catch (NoSuchElementException e) { return false; } + return true; } - protected abstract boolean warmup(long var1); + /** + * Warms up the browser process asynchronously. + * + * @param flags Reserved for future use. + * @return Whether warmup was/had been completed successfully. Multiple successful + * calls will return true. + */ + protected abstract boolean warmup(long flags); - protected abstract boolean newSession(CustomTabsSessionToken var1); + /** + * Creates a new session through an ICustomTabsService with the optional callback. This session + * can be used to associate any related communication through the service with an intent and + * then later with a Custom Tab. The client can then send later service calls or intents to + * through same session-intent-Custom Tab association. + * + * @param sessionToken Session token to be used as a unique identifier. This also has access + * to the {@link CustomTabsCallback} passed from the client side through + * {@link CustomTabsSessionToken#getCallback()}. + * @return Whether a new session was successfully created. + */ + protected abstract boolean newSession(CustomTabsSessionToken sessionToken); - protected abstract boolean mayLaunchUrl(CustomTabsSessionToken var1, Uri var2, Bundle var3, List var4); + /** + * Tells the browser of a likely future navigation to a URL. + *

    + * The method {@link CustomTabsService#warmup(long)} has to be called beforehand. + * The most likely URL has to be specified explicitly. Optionally, a list of + * other likely URLs can be provided. They are treated as less likely than + * the first one, and have to be sorted in decreasing priority order. These + * additional URLs may be ignored. + * All previous calls to this method will be deprioritized. + * + * @param sessionToken The unique identifier for the session. Can not be null. + * @param url Most likely URL. + * @param extras Reserved for future use. + * @param otherLikelyBundles Other likely destinations, sorted in decreasing + * likelihood order. Each Bundle has to provide a url. + * @return Whether the call was successful. + */ + protected abstract boolean mayLaunchUrl(CustomTabsSessionToken sessionToken, Uri url, + Bundle extras, List otherLikelyBundles); - protected abstract Bundle extraCommand(String var1, Bundle var2); + /** + * Unsupported commands that may be provided by the implementation. + *

    + *

    + * Note:Clients should never rely on this method to have a + * defined behavior, as it is entirely implementation-defined and not supported. + *

    + *

    This call can be used by implementations to add extra commands, for testing or + * experimental purposes. + * + * @param commandName Name of the extra command to execute. + * @param args Arguments for the command + * @return The result {@link Bundle}, or null. + */ + protected abstract Bundle extraCommand(String commandName, Bundle args); - protected abstract boolean updateVisuals(CustomTabsSessionToken var1, Bundle var2); + /** + * Updates the visuals of custom tabs for the given session. Will only succeed if the given + * session matches the currently active one. + * + * @param sessionToken The currently active session that the custom tab belongs to. + * @param bundle The action button configuration bundle. This bundle should be constructed + * with the same structure in {@link CustomTabsIntent.Builder}. + * @return Whether the operation was successful. + */ + protected abstract boolean updateVisuals(CustomTabsSessionToken sessionToken, + Bundle bundle); + + /** + * Sends a request to create a two way postMessage channel between the client and the browser + * linked with the given {@link CustomTabsSession}. + * + * @param sessionToken The unique identifier for the session. Can not be null. + * @param postMessageOrigin A origin that the client is requesting to be identified as + * during the postMessage communication. + * @return Whether the implementation accepted the request. Note that returning true + * here doesn't mean an origin has already been assigned as the validation is + * asynchronous. + */ + protected abstract boolean requestPostMessageChannel(CustomTabsSessionToken sessionToken, + Uri postMessageOrigin); + + /** + * Sends a postMessage request using the origin communicated via + * {@link CustomTabsService#requestPostMessageChannel( + *CustomTabsSessionToken, Uri)}. Fails when called before + * {@link PostMessageServiceConnection#notifyMessageChannelReady(Bundle)} is received on the + * client side. + * + * @param sessionToken The unique identifier for the session. Can not be null. + * @param message The message that is being sent. + * @param extras Reserved for future use. + * @return An integer constant about the postMessage request result. Will return + * {@link CustomTabsService#RESULT_SUCCESS} if successful. + */ + @Result + protected abstract int postMessage( + CustomTabsSessionToken sessionToken, String message, Bundle extras); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/CustomTabsServiceConnection.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/CustomTabsServiceConnection.java index 7998b02dd..8b20e3a51 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/CustomTabsServiceConnection.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/CustomTabsServiceConnection.java @@ -20,14 +20,26 @@ import android.content.ComponentName; import android.content.ServiceConnection; import android.os.IBinder; +/** + * Abstract {@link ServiceConnection} to use while binding to a {@link CustomTabsService}. Any + * client implementing this is responsible for handling changes related with the lifetime of the + * connection like rebinding on disconnect. + */ public abstract class CustomTabsServiceConnection implements ServiceConnection { - public CustomTabsServiceConnection() { - } - public final void onServiceConnected(final ComponentName name, IBinder service) { - this.onCustomTabsServiceConnected(name, new CustomTabsClient(ICustomTabsService.Stub.asInterface(service), name) { + @Override + public final void onServiceConnected(ComponentName name, IBinder service) { + onCustomTabsServiceConnected(name, new CustomTabsClient( + ICustomTabsService.Stub.asInterface(service), name) { }); } - public abstract void onCustomTabsServiceConnected(ComponentName var1, CustomTabsClient var2); + /** + * Called when a connection to the {@link CustomTabsService} has been established. + * @param name The concrete component name of the service that has been connected. + * @param client {@link CustomTabsClient} that contains the {@link IBinder} with which the + * connection have been established. All further communication should be initiated + * using this client. + */ + public abstract void onCustomTabsServiceConnected(ComponentName name, CustomTabsClient client); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/CustomTabsSession.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/CustomTabsSession.java index a84755a4f..c0de1c0cf 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/CustomTabsSession.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/CustomTabsSession.java @@ -16,6 +16,7 @@ package org.telegram.messenger.support.customtabs; +import android.app.PendingIntent; import android.content.ComponentName; import android.graphics.Bitmap; import android.net.Uri; @@ -23,6 +24,9 @@ import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; +import android.widget.RemoteViews; import java.util.List; @@ -32,48 +36,169 @@ import java.util.List; */ public final class CustomTabsSession { private static final String TAG = "CustomTabsSession"; + private final Object mLock = new Object(); private final ICustomTabsService mService; private final ICustomTabsCallback mCallback; private final ComponentName mComponentName; - CustomTabsSession(ICustomTabsService service, ICustomTabsCallback callback, ComponentName componentName) { - this.mService = service; - this.mCallback = callback; - this.mComponentName = componentName; + /** + * Provides browsers a way to generate a dummy {@link CustomTabsSession} for testing + * purposes. + * + * @param componentName The component the session should be created for. + * @return A dummy session with no functionality. + */ + public static CustomTabsSession createDummySessionForTesting(ComponentName componentName) { + return new CustomTabsSession(null, new CustomTabsSessionToken.DummyCallback(), componentName); } + /* package */ CustomTabsSession( + ICustomTabsService service, ICustomTabsCallback callback, ComponentName componentName) { + mService = service; + mCallback = callback; + mComponentName = componentName; + } + + /** + * Tells the browser of a likely future navigation to a URL. + * The most likely URL has to be specified first. Optionally, a list of + * other likely URLs can be provided. They are treated as less likely than + * the first one, and have to be sorted in decreasing priority order. These + * additional URLs may be ignored. + * All previous calls to this method will be deprioritized. + * + * @param url Most likely URL. + * @param extras Reserved for future use. + * @param otherLikelyBundles Other likely destinations, sorted in decreasing + * likelihood order. Inside each Bundle, the client should provide a + * {@link Uri} using {@link CustomTabsService#KEY_URL} with + * {@link Bundle#putParcelable(String, android.os.Parcelable)}. + * @return true for success. + */ public boolean mayLaunchUrl(Uri url, Bundle extras, List otherLikelyBundles) { try { - return this.mService.mayLaunchUrl(this.mCallback, url, extras, otherLikelyBundles); - } catch (RemoteException var5) { + return mService.mayLaunchUrl(mCallback, url, extras, otherLikelyBundles); + } catch (RemoteException e) { return false; } } + /** + * This sets the action button on the toolbar with ID + * {@link CustomTabsIntent#TOOLBAR_ACTION_BUTTON_ID}. + * + * @param icon The new icon of the action button. + * @param description Content description of the action button. + * + * @see CustomTabsSession#setToolbarItem(int, Bitmap, String) + */ public boolean setActionButton(@NonNull Bitmap icon, @NonNull String description) { - return this.setToolbarItem(0, icon, description); + Bundle bundle = new Bundle(); + bundle.putParcelable(CustomTabsIntent.KEY_ICON, icon); + bundle.putString(CustomTabsIntent.KEY_DESCRIPTION, description); + + Bundle metaBundle = new Bundle(); + metaBundle.putBundle(CustomTabsIntent.EXTRA_ACTION_BUTTON_BUNDLE, bundle); + try { + return mService.updateVisuals(mCallback, metaBundle); + } catch (RemoteException e) { + return false; + } } + /** + * Updates the {@link RemoteViews} of the secondary toolbar in an existing custom tab session. + * @param remoteViews The updated {@link RemoteViews} that will be shown in secondary toolbar. + * If null, the current secondary toolbar will be dismissed. + * @param clickableIDs The ids of clickable views. The onClick event of these views will be + * handled by custom tabs. + * @param pendingIntent The {@link PendingIntent} that will be sent when the user clicks on one + * of the {@link View}s in clickableIDs. + */ + public boolean setSecondaryToolbarViews(@Nullable RemoteViews remoteViews, + @Nullable int[] clickableIDs, @Nullable PendingIntent pendingIntent) { + Bundle bundle = new Bundle(); + bundle.putParcelable(CustomTabsIntent.EXTRA_REMOTEVIEWS, remoteViews); + bundle.putIntArray(CustomTabsIntent.EXTRA_REMOTEVIEWS_VIEW_IDS, clickableIDs); + bundle.putParcelable(CustomTabsIntent.EXTRA_REMOTEVIEWS_PENDINGINTENT, pendingIntent); + try { + return mService.updateVisuals(mCallback, bundle); + } catch (RemoteException e) { + return false; + } + } + + /** + * Updates the visuals for toolbar items. Will only succeed if a custom tab created using this + * session is in the foreground in browser and the given id is valid. + * @param id The id for the item to update. + * @param icon The new icon of the toolbar item. + * @param description Content description of the toolbar item. + * @return Whether the update succeeded. + * @deprecated Use + * CustomTabsSession#setSecondaryToolbarViews(RemoteViews, int[], PendingIntent) + */ + @Deprecated public boolean setToolbarItem(int id, @NonNull Bitmap icon, @NonNull String description) { Bundle bundle = new Bundle(); - bundle.putInt("android.support.customtabs.customaction.ID", id); - bundle.putParcelable("android.support.customtabs.customaction.ICON", icon); - bundle.putString("android.support.customtabs.customaction.DESCRIPTION", description); - Bundle metaBundle = new Bundle(); - metaBundle.putBundle("android.support.customtabs.extra.ACTION_BUTTON_BUNDLE", bundle); + bundle.putInt(CustomTabsIntent.KEY_ID, id); + bundle.putParcelable(CustomTabsIntent.KEY_ICON, icon); + bundle.putString(CustomTabsIntent.KEY_DESCRIPTION, description); + Bundle metaBundle = new Bundle(); + metaBundle.putBundle(CustomTabsIntent.EXTRA_ACTION_BUTTON_BUNDLE, bundle); try { - return this.mService.updateVisuals(this.mCallback, metaBundle); - } catch (RemoteException var7) { + return mService.updateVisuals(mCallback, metaBundle); + } catch (RemoteException e) { return false; } } - IBinder getBinder() { - return this.mCallback.asBinder(); + /** + * Sends a request to create a two way postMessage channel between the client and the browser. + * + * @param postMessageOrigin A origin that the client is requesting to be identified as + * during the postMessage communication. + * @return Whether the implementation accepted the request. Note that returning true + * here doesn't mean an origin has already been assigned as the validation is + * asynchronous. + */ + public boolean requestPostMessageChannel(Uri postMessageOrigin) { + try { + return mService.requestPostMessageChannel( + mCallback, postMessageOrigin); + } catch (RemoteException e) { + return false; + } } - ComponentName getComponentName() { - return this.mComponentName; + /** + * Sends a postMessage request using the origin communicated via + * {@link CustomTabsService#requestPostMessageChannel( + * CustomTabsSessionToken, Uri)}. Fails when called before + * {@link PostMessageServiceConnection#notifyMessageChannelReady(Bundle)} is received on + * the client side. + * + * @param message The message that is being sent. + * @param extras Reserved for future use. + * @return An integer constant about the postMessage request result. Will return + * {@link CustomTabsService#RESULT_SUCCESS} if successful. + */ + public int postMessage(String message, Bundle extras) { + synchronized (mLock) { + try { + return mService.postMessage(mCallback, message, extras); + } catch (RemoteException e) { + return CustomTabsService.RESULT_FAILURE_REMOTE_ERROR; + } + } + } + + /* package */ IBinder getBinder() { + return mCallback.asBinder(); + } + + /* package */ ComponentName getComponentName() { + return mComponentName; } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/CustomTabsSessionToken.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/CustomTabsSessionToken.java index 007081773..668a01320 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/CustomTabsSessionToken.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/CustomTabsSessionToken.java @@ -32,44 +32,119 @@ public class CustomTabsSessionToken { private final ICustomTabsCallback mCallbackBinder; private final CustomTabsCallback mCallback; + /* package */ static class DummyCallback extends ICustomTabsCallback.Stub { + @Override + public void onNavigationEvent(int navigationEvent, Bundle extras) {} + + @Override + public void extraCallback(String callbackName, Bundle args) {} + + @Override + public void onMessageChannelReady(Bundle extras) {} + + @Override + public void onPostMessage(String message, Bundle extras) {} + + @Override + public IBinder asBinder() { + return this; + } + } + + /** + * Obtain a {@link CustomTabsSessionToken} from an intent. See {@link CustomTabsIntent.Builder} + * for ways to generate an intent for custom tabs. + * @param intent The intent to generate the token from. This has to include an extra for + * {@link CustomTabsIntent#EXTRA_SESSION}. + * @return The token that was generated. + */ public static CustomTabsSessionToken getSessionTokenFromIntent(Intent intent) { Bundle b = intent.getExtras(); - IBinder binder = BundleCompat.getBinder(b, "android.support.customtabs.extra.SESSION"); - return binder == null ? null : new CustomTabsSessionToken(ICustomTabsCallback.Stub.asInterface(binder)); + IBinder binder = BundleCompat.getBinder(b, CustomTabsIntent.EXTRA_SESSION); + if (binder == null) return null; + return new CustomTabsSessionToken(ICustomTabsCallback.Stub.asInterface(binder)); + } + + /** + * Provides browsers a way to generate a dummy {@link CustomTabsSessionToken} for testing + * purposes. + * + * @return A dummy token with no functionality. + */ + public static CustomTabsSessionToken createDummySessionTokenForTesting() { + return new CustomTabsSessionToken(new DummyCallback()); } CustomTabsSessionToken(ICustomTabsCallback callbackBinder) { - this.mCallbackBinder = callbackBinder; - this.mCallback = new CustomTabsCallback() { + mCallbackBinder = callbackBinder; + mCallback = new CustomTabsCallback() { + + @Override public void onNavigationEvent(int navigationEvent, Bundle extras) { try { - CustomTabsSessionToken.this.mCallbackBinder.onNavigationEvent(navigationEvent, extras); - } catch (RemoteException var4) { - Log.e("CustomTabsSessionToken", "RemoteException during ICustomTabsCallback transaction"); + mCallbackBinder.onNavigationEvent(navigationEvent, extras); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException during ICustomTabsCallback transaction"); } + } + @Override + public void extraCallback(String callbackName, Bundle args) { + try { + mCallbackBinder.extraCallback(callbackName, args); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException during ICustomTabsCallback transaction"); + } + } + + @Override + public void onMessageChannelReady(Bundle extras) { + try { + mCallbackBinder.onMessageChannelReady(extras); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException during ICustomTabsCallback transaction"); + } + } + + @Override + public void onPostMessage(String message, Bundle extras) { + try { + mCallbackBinder.onPostMessage(message, extras); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException during ICustomTabsCallback transaction"); + } } }; } IBinder getCallbackBinder() { - return this.mCallbackBinder.asBinder(); + return mCallbackBinder.asBinder(); } + @Override public int hashCode() { - return this.getCallbackBinder().hashCode(); + return getCallbackBinder().hashCode(); } + @Override public boolean equals(Object o) { - if (!(o instanceof CustomTabsSessionToken)) { - return false; - } else { - CustomTabsSessionToken token = (CustomTabsSessionToken) o; - return token.getCallbackBinder().equals(this.mCallbackBinder.asBinder()); - } + if (!(o instanceof CustomTabsSessionToken)) return false; + CustomTabsSessionToken token = (CustomTabsSessionToken) o; + return token.getCallbackBinder().equals(mCallbackBinder.asBinder()); } + /** + * @return {@link CustomTabsCallback} corresponding to this session if there was any non-null + * callbacks passed by the client. + */ public CustomTabsCallback getCallback() { - return this.mCallback; + return mCallback; + } + + /** + * @return Whether this token is associated with the given session. + */ + public boolean isAssociatedWith(CustomTabsSession session) { + return session.getBinder().equals(mCallbackBinder); } } \ No newline at end of file diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/ICustomTabsCallback.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/ICustomTabsCallback.java index 2d311a4d5..d03e6a704 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/ICustomTabsCallback.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/ICustomTabsCallback.java @@ -28,21 +28,27 @@ public interface ICustomTabsCallback extends IInterface { void extraCallback(String var1, Bundle var2) throws RemoteException; + void onMessageChannelReady(Bundle var1) throws RemoteException; + + void onPostMessage(String var1, Bundle var2) throws RemoteException; + abstract class Stub extends Binder implements ICustomTabsCallback { private static final String DESCRIPTOR = "android.support.customtabs.ICustomTabsCallback"; static final int TRANSACTION_onNavigationEvent = 2; static final int TRANSACTION_extraCallback = 3; + static final int TRANSACTION_onMessageChannelReady = 4; + static final int TRANSACTION_onPostMessage = 5; public Stub() { this.attachInterface(this, "android.support.customtabs.ICustomTabsCallback"); } public static ICustomTabsCallback asInterface(IBinder obj) { - if (obj == null) { + if(obj == null) { return null; } else { IInterface iin = obj.queryLocalInterface("android.support.customtabs.ICustomTabsCallback"); - return (iin != null && iin instanceof ICustomTabsCallback ? (ICustomTabsCallback) iin : new ICustomTabsCallback.Stub.Proxy(obj)); + return (iin != null && iin instanceof ICustomTabsCallback?(ICustomTabsCallback)iin:new ICustomTabsCallback.Stub.Proxy(obj)); } } @@ -51,29 +57,56 @@ public interface ICustomTabsCallback extends IInterface { } public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { + String _arg0; Bundle _arg1; - switch (code) { + switch(code) { case 2: data.enforceInterface("android.support.customtabs.ICustomTabsCallback"); - int _arg01 = data.readInt(); - if (0 != data.readInt()) { + int _arg02 = data.readInt(); + if(0 != data.readInt()) { _arg1 = Bundle.CREATOR.createFromParcel(data); } else { _arg1 = null; } - this.onNavigationEvent(_arg01, _arg1); + this.onNavigationEvent(_arg02, _arg1); + reply.writeNoException(); return true; case 3: data.enforceInterface("android.support.customtabs.ICustomTabsCallback"); - String _arg0 = data.readString(); - if (0 != data.readInt()) { + _arg0 = data.readString(); + if(0 != data.readInt()) { _arg1 = Bundle.CREATOR.createFromParcel(data); } else { _arg1 = null; } this.extraCallback(_arg0, _arg1); + reply.writeNoException(); + return true; + case 4: + data.enforceInterface("android.support.customtabs.ICustomTabsCallback"); + Bundle _arg01; + if(0 != data.readInt()) { + _arg01 = Bundle.CREATOR.createFromParcel(data); + } else { + _arg01 = null; + } + + this.onMessageChannelReady(_arg01); + reply.writeNoException(); + return true; + case 5: + data.enforceInterface("android.support.customtabs.ICustomTabsCallback"); + _arg0 = data.readString(); + if(0 != data.readInt()) { + _arg1 = Bundle.CREATOR.createFromParcel(data); + } else { + _arg1 = null; + } + + this.onPostMessage(_arg0, _arg1); + reply.writeNoException(); return true; case 1598968902: reply.writeString("android.support.customtabs.ICustomTabsCallback"); @@ -100,19 +133,22 @@ public interface ICustomTabsCallback extends IInterface { public void onNavigationEvent(int navigationEvent, Bundle extras) throws RemoteException { Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); try { _data.writeInterfaceToken("android.support.customtabs.ICustomTabsCallback"); _data.writeInt(navigationEvent); - if (extras != null) { + if(extras != null) { _data.writeInt(1); extras.writeToParcel(_data, 0); } else { _data.writeInt(0); } - this.mRemote.transact(2, _data, null, 1); + this.mRemote.transact(2, _data, _reply, 0); + _reply.readException(); } finally { + _reply.recycle(); _data.recycle(); } @@ -120,19 +156,67 @@ public interface ICustomTabsCallback extends IInterface { public void extraCallback(String callbackName, Bundle args) throws RemoteException { Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); try { _data.writeInterfaceToken("android.support.customtabs.ICustomTabsCallback"); _data.writeString(callbackName); - if (args != null) { + if(args != null) { _data.writeInt(1); args.writeToParcel(_data, 0); } else { _data.writeInt(0); } - this.mRemote.transact(3, _data, null, 1); + this.mRemote.transact(3, _data, _reply, 0); + _reply.readException(); } finally { + _reply.recycle(); + _data.recycle(); + } + + } + + public void onMessageChannelReady(Bundle extras) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + + try { + _data.writeInterfaceToken("android.support.customtabs.ICustomTabsCallback"); + if(extras != null) { + _data.writeInt(1); + extras.writeToParcel(_data, 0); + } else { + _data.writeInt(0); + } + + this.mRemote.transact(4, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + + } + + public void onPostMessage(String message, Bundle extras) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + + try { + _data.writeInterfaceToken("android.support.customtabs.ICustomTabsCallback"); + _data.writeString(message); + if(extras != null) { + _data.writeInt(1); + extras.writeToParcel(_data, 0); + } else { + _data.writeInt(0); + } + + this.mRemote.transact(5, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); _data.recycle(); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/ICustomTabsService.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/ICustomTabsService.java index fefde3666..427a2c9c6 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/ICustomTabsService.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/ICustomTabsService.java @@ -38,6 +38,10 @@ public interface ICustomTabsService extends IInterface { boolean updateVisuals(ICustomTabsCallback var1, Bundle var2) throws RemoteException; + boolean requestPostMessageChannel(ICustomTabsCallback var1, Uri var2) throws RemoteException; + + int postMessage(ICustomTabsCallback var1, String var2, Bundle var3) throws RemoteException; + abstract class Stub extends Binder implements ICustomTabsService { private static final String DESCRIPTOR = "android.support.customtabs.ICustomTabsService"; static final int TRANSACTION_warmup = 2; @@ -45,17 +49,19 @@ public interface ICustomTabsService extends IInterface { static final int TRANSACTION_mayLaunchUrl = 4; static final int TRANSACTION_extraCommand = 5; static final int TRANSACTION_updateVisuals = 6; + static final int TRANSACTION_requestPostMessageChannel = 7; + static final int TRANSACTION_postMessage = 8; public Stub() { this.attachInterface(this, "android.support.customtabs.ICustomTabsService"); } public static ICustomTabsService asInterface(IBinder obj) { - if (obj == null) { + if(obj == null) { return null; } else { IInterface iin = obj.queryLocalInterface("android.support.customtabs.ICustomTabsService"); - return (iin != null && iin instanceof ICustomTabsService ? (ICustomTabsService) iin : new ICustomTabsService.Stub.Proxy(obj)); + return (iin != null && iin instanceof ICustomTabsService?(ICustomTabsService)iin:new ICustomTabsService.Stub.Proxy(obj)); } } @@ -65,59 +71,59 @@ public interface ICustomTabsService extends IInterface { public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { ICustomTabsCallback _arg0; - Bundle _arg1; - boolean _result; - Bundle _result2; - switch (code) { + Bundle _arg2; + Uri _arg11; + Bundle _arg12; + boolean _arg21; + switch(code) { case 2: data.enforceInterface("android.support.customtabs.ICustomTabsService"); long _arg02 = data.readLong(); - _result = this.warmup(_arg02); + _arg21 = this.warmup(_arg02); reply.writeNoException(); - reply.writeInt(_result ? 1 : 0); + reply.writeInt(_arg21?1:0); return true; case 3: data.enforceInterface("android.support.customtabs.ICustomTabsService"); _arg0 = ICustomTabsCallback.Stub.asInterface(data.readStrongBinder()); - boolean _arg12 = this.newSession(_arg0); + boolean _arg13 = this.newSession(_arg0); reply.writeNoException(); - reply.writeInt(_arg12 ? 1 : 0); + reply.writeInt(_arg13?1:0); return true; case 4: data.enforceInterface("android.support.customtabs.ICustomTabsService"); _arg0 = ICustomTabsCallback.Stub.asInterface(data.readStrongBinder()); - Uri _arg11; - if (0 != data.readInt()) { + if(0 != data.readInt()) { _arg11 = Uri.CREATOR.createFromParcel(data); } else { _arg11 = null; } - if (0 != data.readInt()) { - _result2 = Bundle.CREATOR.createFromParcel(data); + if(0 != data.readInt()) { + _arg2 = Bundle.CREATOR.createFromParcel(data); } else { - _result2 = null; + _arg2 = null; } - ArrayList _arg3 = data.createTypedArrayList(Bundle.CREATOR); - boolean _result1 = this.mayLaunchUrl(_arg0, _arg11, _result2, _arg3); + ArrayList _result2 = data.createTypedArrayList(Bundle.CREATOR); + boolean _result1 = this.mayLaunchUrl(_arg0, _arg11, _arg2, _result2); reply.writeNoException(); - reply.writeInt(_result1 ? 1 : 0); + reply.writeInt(_result1?1:0); return true; case 5: data.enforceInterface("android.support.customtabs.ICustomTabsService"); String _arg01 = data.readString(); - if (0 != data.readInt()) { - _arg1 = Bundle.CREATOR.createFromParcel(data); + if(0 != data.readInt()) { + _arg12 = Bundle.CREATOR.createFromParcel(data); } else { - _arg1 = null; + _arg12 = null; } - _result2 = this.extraCommand(_arg01, _arg1); + _arg2 = this.extraCommand(_arg01, _arg12); reply.writeNoException(); - if (_result2 != null) { + if(_arg2 != null) { reply.writeInt(1); - _result2.writeToParcel(reply, 1); + _arg2.writeToParcel(reply, 1); } else { reply.writeInt(0); } @@ -126,15 +132,42 @@ public interface ICustomTabsService extends IInterface { case 6: data.enforceInterface("android.support.customtabs.ICustomTabsService"); _arg0 = ICustomTabsCallback.Stub.asInterface(data.readStrongBinder()); - if (0 != data.readInt()) { - _arg1 = Bundle.CREATOR.createFromParcel(data); + if(0 != data.readInt()) { + _arg12 = Bundle.CREATOR.createFromParcel(data); } else { - _arg1 = null; + _arg12 = null; } - _result = this.updateVisuals(_arg0, _arg1); + _arg21 = this.updateVisuals(_arg0, _arg12); reply.writeNoException(); - reply.writeInt(_result ? 1 : 0); + reply.writeInt(_arg21?1:0); + return true; + case 7: + data.enforceInterface("android.support.customtabs.ICustomTabsService"); + _arg0 = ICustomTabsCallback.Stub.asInterface(data.readStrongBinder()); + if(0 != data.readInt()) { + _arg11 = Uri.CREATOR.createFromParcel(data); + } else { + _arg11 = null; + } + + _arg21 = this.requestPostMessageChannel(_arg0, _arg11); + reply.writeNoException(); + reply.writeInt(_arg21?1:0); + return true; + case 8: + data.enforceInterface("android.support.customtabs.ICustomTabsService"); + _arg0 = ICustomTabsCallback.Stub.asInterface(data.readStrongBinder()); + String _arg1 = data.readString(); + if(0 != data.readInt()) { + _arg2 = Bundle.CREATOR.createFromParcel(data); + } else { + _arg2 = null; + } + + int _result = this.postMessage(_arg0, _arg1, _arg2); + reply.writeNoException(); + reply.writeInt(_result); return true; case 1598968902: reply.writeString("android.support.customtabs.ICustomTabsService"); @@ -185,7 +218,7 @@ public interface ICustomTabsService extends IInterface { boolean _result; try { _data.writeInterfaceToken("android.support.customtabs.ICustomTabsService"); - _data.writeStrongBinder(callback != null ? callback.asBinder() : null); + _data.writeStrongBinder(callback != null?callback.asBinder():null); this.mRemote.transact(3, _data, _reply, 0); _reply.readException(); _result = 0 != _reply.readInt(); @@ -204,15 +237,15 @@ public interface ICustomTabsService extends IInterface { boolean _result; try { _data.writeInterfaceToken("android.support.customtabs.ICustomTabsService"); - _data.writeStrongBinder(callback != null ? callback.asBinder() : null); - if (url != null) { + _data.writeStrongBinder(callback != null?callback.asBinder():null); + if(url != null) { _data.writeInt(1); url.writeToParcel(_data, 0); } else { _data.writeInt(0); } - if (extras != null) { + if(extras != null) { _data.writeInt(1); extras.writeToParcel(_data, 0); } else { @@ -239,7 +272,7 @@ public interface ICustomTabsService extends IInterface { try { _data.writeInterfaceToken("android.support.customtabs.ICustomTabsService"); _data.writeString(commandName); - if (args != null) { + if(args != null) { _data.writeInt(1); args.writeToParcel(_data, 0); } else { @@ -248,7 +281,7 @@ public interface ICustomTabsService extends IInterface { this.mRemote.transact(5, _data, _reply, 0); _reply.readException(); - if (0 != _reply.readInt()) { + if(0 != _reply.readInt()) { _result = Bundle.CREATOR.createFromParcel(_reply); } else { _result = null; @@ -268,8 +301,8 @@ public interface ICustomTabsService extends IInterface { boolean _result; try { _data.writeInterfaceToken("android.support.customtabs.ICustomTabsService"); - _data.writeStrongBinder(callback != null ? callback.asBinder() : null); - if (bundle != null) { + _data.writeStrongBinder(callback != null?callback.asBinder():null); + if(bundle != null) { _data.writeInt(1); bundle.writeToParcel(_data, 0); } else { @@ -286,6 +319,59 @@ public interface ICustomTabsService extends IInterface { return _result; } + + public boolean requestPostMessageChannel(ICustomTabsCallback callback, Uri postMessageOrigin) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + + boolean _result; + try { + _data.writeInterfaceToken("android.support.customtabs.ICustomTabsService"); + _data.writeStrongBinder(callback != null?callback.asBinder():null); + if(postMessageOrigin != null) { + _data.writeInt(1); + postMessageOrigin.writeToParcel(_data, 0); + } else { + _data.writeInt(0); + } + + this.mRemote.transact(7, _data, _reply, 0); + _reply.readException(); + _result = 0 != _reply.readInt(); + } finally { + _reply.recycle(); + _data.recycle(); + } + + return _result; + } + + public int postMessage(ICustomTabsCallback callback, String message, Bundle extras) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + + int _result; + try { + _data.writeInterfaceToken("android.support.customtabs.ICustomTabsService"); + _data.writeStrongBinder(callback != null?callback.asBinder():null); + _data.writeString(message); + if(extras != null) { + _data.writeInt(1); + extras.writeToParcel(_data, 0); + } else { + _data.writeInt(0); + } + + this.mRemote.transact(8, _data, _reply, 0); + _reply.readException(); + _result = _reply.readInt(); + } finally { + _reply.recycle(); + _data.recycle(); + } + + return _result; + } } } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/IPostMessageService.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/IPostMessageService.java new file mode 100644 index 000000000..8eef1e5f6 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/IPostMessageService.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.telegram.messenger.support.customtabs; + +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.IInterface; +import android.os.Parcel; +import android.os.RemoteException; + +public interface IPostMessageService extends IInterface { + + void onMessageChannelReady(ICustomTabsCallback var1, Bundle var2) throws RemoteException; + + void onPostMessage(ICustomTabsCallback var1, String var2, Bundle var3) throws RemoteException; + + abstract class Stub extends Binder implements IPostMessageService { + private static final String DESCRIPTOR = "android.support.customtabs.IPostMessageService"; + static final int TRANSACTION_onMessageChannelReady = 2; + static final int TRANSACTION_onPostMessage = 3; + + public Stub() { + this.attachInterface(this, "android.support.customtabs.IPostMessageService"); + } + + public static IPostMessageService asInterface(IBinder obj) { + if(obj == null) { + return null; + } else { + IInterface iin = obj.queryLocalInterface("android.support.customtabs.IPostMessageService"); + return (iin != null && iin instanceof IPostMessageService?(IPostMessageService)iin:new IPostMessageService.Stub.Proxy(obj)); + } + } + + public IBinder asBinder() { + return this; + } + + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { + ICustomTabsCallback _arg0; + switch(code) { + case 2: + data.enforceInterface("android.support.customtabs.IPostMessageService"); + _arg0 = ICustomTabsCallback.Stub.asInterface(data.readStrongBinder()); + Bundle _arg11; + if(0 != data.readInt()) { + _arg11 = Bundle.CREATOR.createFromParcel(data); + } else { + _arg11 = null; + } + + this.onMessageChannelReady(_arg0, _arg11); + reply.writeNoException(); + return true; + case 3: + data.enforceInterface("android.support.customtabs.IPostMessageService"); + _arg0 = ICustomTabsCallback.Stub.asInterface(data.readStrongBinder()); + String _arg1 = data.readString(); + Bundle _arg2; + if(0 != data.readInt()) { + _arg2 = Bundle.CREATOR.createFromParcel(data); + } else { + _arg2 = null; + } + + this.onPostMessage(_arg0, _arg1, _arg2); + reply.writeNoException(); + return true; + case 1598968902: + reply.writeString("android.support.customtabs.IPostMessageService"); + return true; + default: + return super.onTransact(code, data, reply, flags); + } + } + + private static class Proxy implements IPostMessageService { + private IBinder mRemote; + + Proxy(IBinder remote) { + this.mRemote = remote; + } + + public IBinder asBinder() { + return this.mRemote; + } + + public String getInterfaceDescriptor() { + return "android.support.customtabs.IPostMessageService"; + } + + public void onMessageChannelReady(ICustomTabsCallback callback, Bundle extras) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + + try { + _data.writeInterfaceToken("android.support.customtabs.IPostMessageService"); + _data.writeStrongBinder(callback != null?callback.asBinder():null); + if(extras != null) { + _data.writeInt(1); + extras.writeToParcel(_data, 0); + } else { + _data.writeInt(0); + } + + this.mRemote.transact(2, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + + } + + public void onPostMessage(ICustomTabsCallback callback, String message, Bundle extras) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + + try { + _data.writeInterfaceToken("android.support.customtabs.IPostMessageService"); + _data.writeStrongBinder(callback != null?callback.asBinder():null); + _data.writeString(message); + if(extras != null) { + _data.writeInt(1); + extras.writeToParcel(_data, 0); + } else { + _data.writeInt(0); + } + + this.mRemote.transact(3, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + + } + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/PostMessageService.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/PostMessageService.java new file mode 100755 index 000000000..35b1341c4 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/PostMessageService.java @@ -0,0 +1,40 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.messenger.support.customtabs; + +import android.app.Service; +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; + +/** + * A service to receive postMessage related communication from a Custom Tabs provider. + */ +public class PostMessageService extends Service { + private IPostMessageService.Stub mBinder = new IPostMessageService.Stub() { + + @Override + public void onMessageChannelReady( + ICustomTabsCallback callback, Bundle extras) throws RemoteException { + callback.onMessageChannelReady(extras); + } + + @Override + public void onPostMessage(ICustomTabsCallback callback, + String message, Bundle extras) throws RemoteException { + callback.onPostMessage(message, extras); + } + }; + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/PostMessageServiceConnection.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/PostMessageServiceConnection.java new file mode 100755 index 000000000..d5ddf9132 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabs/PostMessageServiceConnection.java @@ -0,0 +1,118 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.messenger.support.customtabs; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; + +/** + * A {@link ServiceConnection} for Custom Tabs providers to use while connecting to a + * {@link PostMessageService} on the client side. + */ +public abstract class PostMessageServiceConnection implements ServiceConnection { + private final Object mLock = new Object(); + private final ICustomTabsCallback mSessionBinder; + private IPostMessageService mService; + + public PostMessageServiceConnection(CustomTabsSessionToken session) { + mSessionBinder = ICustomTabsCallback.Stub.asInterface(session.getCallbackBinder()); + } + + /** + * Binds the browser side to the client app through the given {@link PostMessageService} name. + * After this, this {@link PostMessageServiceConnection} can be used for sending postMessage + * related communication back to the client. + * @param context A context to bind to the service. + * @param packageName The name of the package to be bound to. + * @return Whether the binding was successful. + */ + public boolean bindSessionToPostMessageService(Context context, String packageName) { + Intent intent = new Intent(); + intent.setClassName(packageName, PostMessageService.class.getName()); + return context.bindService(intent, this, Context.BIND_AUTO_CREATE); + } + + /** + * Unbinds this service connection from the given context. + * @param context The context to be unbound from. + */ + public void unbindFromContext(Context context) { + context.unbindService(this); + } + + @Override + public final void onServiceConnected(ComponentName name, IBinder service) { + mService = IPostMessageService.Stub.asInterface(service); + onPostMessageServiceConnected(); + } + + @Override + public final void onServiceDisconnected(ComponentName name) { + mService = null; + onPostMessageServiceDisconnected(); + } + + /** + * Notifies the client that the postMessage channel requested with + * {@link CustomTabsService#requestPostMessageChannel( + * CustomTabsSessionToken, android.net.Uri)} is ready. This method should be + * called when the browser binds to the client side {@link PostMessageService} and also readies + * a connection to the web frame. + * + * @param extras Reserved for future use. + * @return Whether the notification was sent to the remote successfully. + */ + public final boolean notifyMessageChannelReady(Bundle extras) { + if (mService == null) return false; + synchronized (mLock) { + try { + mService.onMessageChannelReady(mSessionBinder, extras); + } catch (RemoteException e) { + return false; + } + } + return true; + } + + /** + * Posts a message to the client. This should be called when a tab controlled by related + * {@link CustomTabsSession} has sent a postMessage. If postMessage() is called from a single + * thread, then the messages will be posted in the same order. + * + * @param message The message sent. + * @param extras Reserved for future use. + * @return Whether the postMessage was sent to the remote successfully. + */ + public final boolean postMessage(String message, Bundle extras) { + if (mService == null) return false; + synchronized (mLock) { + try { + mService.onPostMessage(mSessionBinder, message, extras); + } catch (RemoteException e) { + return false; + } + } + return true; + } + + /** + * Called when the {@link PostMessageService} connection is established. + */ + public void onPostMessageServiceConnected() {} + + /** + * Called when the connection is lost with the {@link PostMessageService}. + */ + public void onPostMessageServiceDisconnected() {} +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabsclient/shared/ServiceConnection.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabsclient/shared/ServiceConnection.java index 86f1b8797..d9c7690e7 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabsclient/shared/ServiceConnection.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/customtabsclient/shared/ServiceConnection.java @@ -21,7 +21,12 @@ import org.telegram.messenger.support.customtabs.CustomTabsServiceConnection; import java.lang.ref.WeakReference; +/** + * Implementation for the CustomTabsServiceConnection that avoids leaking the + * ServiceConnectionCallback + */ public class ServiceConnection extends CustomTabsServiceConnection { + // A weak reference to the ServiceConnectionCallback to avoid leaking it. private WeakReference mConnectionCallback; public ServiceConnection(ServiceConnectionCallback connectionCallback) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/GridLayoutManager.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/GridLayoutManager.java index 8639be76f..428f9f56e 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/GridLayoutManager.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/GridLayoutManager.java @@ -255,7 +255,7 @@ public class GridLayoutManager extends LinearLayoutManager { return mSpanSizeLookup; } - private void updateMeasurements() { + protected void updateMeasurements() { int totalSpace; if (getOrientation() == VERTICAL) { totalSpace = getWidth() - getPaddingRight() - getPaddingLeft(); @@ -301,7 +301,7 @@ public class GridLayoutManager extends LinearLayoutManager { * @return The updated array. Might be the same instance as the provided array if its size * has not changed. */ - static int[] calculateItemBorders(int[] cachedBorders, int spanCount, int totalSpace) { + protected int[] calculateItemBorders(int[] cachedBorders, int spanCount, int totalSpace) { if (cachedBorders == null || cachedBorders.length != spanCount + 1 || cachedBorders[cachedBorders.length - 1] != totalSpace) { cachedBorders = new int[spanCount + 1]; @@ -447,7 +447,7 @@ public class GridLayoutManager extends LinearLayoutManager { return mSpanSizeLookup.getSpanGroupIndex(adapterPosition, mSpanCount); } - private int getSpanIndex(RecyclerView.Recycler recycler, RecyclerView.State state, int pos) { + protected int getSpanIndex(RecyclerView.Recycler recycler, RecyclerView.State state, int pos) { if (!state.isPreLayout()) { return mSpanSizeLookup.getCachedSpanIndex(pos, mSpanCount); } @@ -468,7 +468,7 @@ public class GridLayoutManager extends LinearLayoutManager { return mSpanSizeLookup.getCachedSpanIndex(adapterPosition, mSpanCount); } - private int getSpanSize(RecyclerView.Recycler recycler, RecyclerView.State state, int pos) { + protected int getSpanSize(RecyclerView.Recycler recycler, RecyclerView.State state, int pos) { if (!state.isPreLayout()) { return mSpanSizeLookup.getSpanSize(pos); } @@ -692,7 +692,7 @@ public class GridLayoutManager extends LinearLayoutManager { * orientation * @param alreadyMeasured True if we've already measured this view once */ - private void measureChild(View view, int otherDirParentSpecMode, boolean alreadyMeasured) { + protected void measureChild(View view, int otherDirParentSpecMode, boolean alreadyMeasured) { final LayoutParams lp = (LayoutParams) view.getLayoutParams(); final Rect decorInsets = lp.mDecorInsets; final int verticalInsets = decorInsets.top + decorInsets.bottom @@ -726,13 +726,13 @@ public class GridLayoutManager extends LinearLayoutManager { * @param maxSizeInOther The maximum size per span ratio from the measurement of the children. * @param currentOtherDirSize The size before this layout chunk. There is no reason to go below. */ - private void guessMeasurement(float maxSizeInOther, int currentOtherDirSize) { + protected void guessMeasurement(float maxSizeInOther, int currentOtherDirSize) { final int contentSize = Math.round(maxSizeInOther * mSpanCount); // always re-calculate because borders were stretched during the fill calculateItemBorders(Math.max(contentSize, currentOtherDirSize)); } - private void measureChildWithDecorationsAndMargin(View child, int widthSpec, int heightSpec, + protected void measureChildWithDecorationsAndMargin(View child, int widthSpec, int heightSpec, boolean alreadyMeasured) { RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams(); final boolean measure; @@ -746,7 +746,7 @@ public class GridLayoutManager extends LinearLayoutManager { } } - private void assignSpans(RecyclerView.Recycler recycler, RecyclerView.State state, int count, + protected void assignSpans(RecyclerView.Recycler recycler, RecyclerView.State state, int count, int consumedSpanCount, boolean layingOutInPrimaryDirection) { // spans are always assigned from 0 to N no matter if it is RTL or not. // RTL is used only when positioning the view. diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/GridLayoutManagerFixed.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/GridLayoutManagerFixed.java new file mode 100644 index 000000000..3b0d2d78d --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/GridLayoutManagerFixed.java @@ -0,0 +1,286 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ +package org.telegram.messenger.support.widget; + +import android.content.Context; +import android.graphics.Rect; +import android.view.View; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * A {@link RecyclerView.LayoutManager} implementations that lays out items in a grid. + *

    + * By default, each item occupies 1 span. You can change it by providing a custom + * {@link SpanSizeLookup} instance via {@link #setSpanSizeLookup(SpanSizeLookup)}. + */ +public class GridLayoutManagerFixed extends GridLayoutManager { + + ArrayList additionalViews = new ArrayList<>(4); + + public GridLayoutManagerFixed(Context context, int spanCount) { + super(context, spanCount); + } + + public GridLayoutManagerFixed(Context context, int spanCount, int orientation, boolean reverseLayout) { + super(context, spanCount, orientation, reverseLayout); + } + + protected boolean hasSiblingChild(int position) { + return false; + } + + @Override + protected void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) { + if (dt < 0) { + return; + } + // ignore padding, ViewGroup may not clip children. + final int childCount = getChildCount(); + if (mShouldReverseLayout) { + for (int i = childCount - 1; i >= 0; i--) { + View child = getChildAt(i); + final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); + if (child.getBottom() + params.bottomMargin > dt + || child.getTop() + child.getHeight() > dt) { + // stop here + recycleChildren(recycler, childCount - 1, i); + return; + } + } + } else { + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + if (mOrientationHelper.getDecoratedEnd(child) > dt + || mOrientationHelper.getTransformedEndWithDecoration(child) > dt) { + // stop here + recycleChildren(recycler, 0, i); + return; + } + } + } + } + + @Override + protected int[] calculateItemBorders(int[] cachedBorders, int spanCount, int totalSpace) { + if (cachedBorders == null || cachedBorders.length != spanCount + 1 || cachedBorders[cachedBorders.length - 1] != totalSpace) { + cachedBorders = new int[spanCount + 1]; + } + cachedBorders[0] = 0; + for (int i = 1; i <= spanCount; i++) { + cachedBorders[i] = (int) Math.ceil(i / (float) spanCount * totalSpace); + } + return cachedBorders; + } + + public boolean shouldLayoutChildFromOpositeSide(View child) { + return false; + } + + @Override + protected void measureChild(View view, int otherDirParentSpecMode, boolean alreadyMeasured) { + final LayoutParams lp = (LayoutParams) view.getLayoutParams(); + final Rect decorInsets = lp.mDecorInsets; + final int verticalInsets = decorInsets.top + decorInsets.bottom + + lp.topMargin + lp.bottomMargin; + final int horizontalInsets = decorInsets.left + decorInsets.right + + lp.leftMargin + lp.rightMargin; + final int availableSpaceInOther = mCachedBorders[lp.mSpanSize]; + final int wSpec = getChildMeasureSpec(availableSpaceInOther, otherDirParentSpecMode, + horizontalInsets, lp.width, false); + final int hSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(), getHeightMode(), + verticalInsets, lp.height, true); + measureChildWithDecorationsAndMargin(view, wSpec, hSpec, alreadyMeasured); + } + + @Override + void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) { + final int otherDirSpecMode = mOrientationHelper.getModeInOther(); + + final boolean layingOutInPrimaryDirection = layoutState.mItemDirection == LayoutState.ITEM_DIRECTION_TAIL; + boolean working = true; + result.mConsumed = 0; + int yOffset = 0; + + int startPosition = layoutState.mCurrentPosition; + if (layoutState.mLayoutDirection != LayoutState.LAYOUT_START && hasSiblingChild(layoutState.mCurrentPosition) && findViewByPosition(layoutState.mCurrentPosition + 1) == null) { + if (hasSiblingChild(layoutState.mCurrentPosition + 1)) { + layoutState.mCurrentPosition += 3; + } else { + layoutState.mCurrentPosition += 2; + } + int backupPosition = layoutState.mCurrentPosition; + for (int a = layoutState.mCurrentPosition; a > startPosition; a--) { + View view = layoutState.next(recycler); + additionalViews.add(view); + if (a != backupPosition) { + calculateItemDecorationsForChild(view, mDecorInsets); + measureChild(view, otherDirSpecMode, false); + int size = mOrientationHelper.getDecoratedMeasurement(view); + layoutState.mOffset -= size; + layoutState.mAvailable += size; + } + } + layoutState.mCurrentPosition = backupPosition; + } + + while (working) { + int count = 0; + int consumedSpanCount = 0; + int remainingSpan = mSpanCount; + + working = !additionalViews.isEmpty(); + int firstPositionStart = layoutState.mCurrentPosition; + + while (count < mSpanCount && layoutState.hasMore(state) && remainingSpan > 0) { + int pos = layoutState.mCurrentPosition; + final int spanSize = getSpanSize(recycler, state, pos); + + remainingSpan -= spanSize; + if (remainingSpan < 0) { + break; + } + View view; + if (!additionalViews.isEmpty()) { + view = additionalViews.get(0); + additionalViews.remove(0); + layoutState.mCurrentPosition--; + } else { + view = layoutState.next(recycler); + } + if (view == null) { + break; + } + consumedSpanCount += spanSize; + mSet[count] = view; + count++; + if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START && remainingSpan <= 0 && hasSiblingChild(pos)) { + working = true; + } + } + + if (count == 0) { + result.mFinished = true; + return; + } + + int maxSize = 0; + float maxSizeInOther = 0; + + 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); + } + } + calculateItemDecorationsForChild(view, mDecorInsets); + + measureChild(view, otherDirSpecMode, false); + final int size = mOrientationHelper.getDecoratedMeasurement(view); + if (size > maxSize) { + maxSize = size; + } + final LayoutParams lp = (LayoutParams) view.getLayoutParams(); + final float otherSize = 1f * mOrientationHelper.getDecoratedMeasurementInOther(view) / lp.mSpanSize; + if (otherSize > maxSizeInOther) { + maxSizeInOther = otherSize; + } + } + + // Views that did not measure the maxSize has to be re-measured + // We will stop doing this once we introduce Gravity in the GLM layout params + for (int i = 0; i < count; i++) { + final View view = mSet[i]; + if (mOrientationHelper.getDecoratedMeasurement(view) != maxSize) { + final LayoutParams lp = (LayoutParams) view.getLayoutParams(); + final Rect decorInsets = lp.mDecorInsets; + final int verticalInsets = decorInsets.top + decorInsets.bottom + lp.topMargin + lp.bottomMargin; + final int horizontalInsets = decorInsets.left + decorInsets.right + lp.leftMargin + lp.rightMargin; + final int totalSpaceInOther = mCachedBorders[lp.mSpanSize]; + + final int wSpec = getChildMeasureSpec(totalSpaceInOther, View.MeasureSpec.EXACTLY, horizontalInsets, lp.width, false); + final int hSpec = View.MeasureSpec.makeMeasureSpec(maxSize - verticalInsets, View.MeasureSpec.EXACTLY); + + measureChildWithDecorationsAndMargin(view, wSpec, hSpec, true); + } + } + + int left, right, top, bottom; + + boolean fromOpositeSide = shouldLayoutChildFromOpositeSide(mSet[0]); + if (fromOpositeSide && layoutState.mLayoutDirection == LayoutState.LAYOUT_START || !fromOpositeSide && layoutState.mLayoutDirection == LayoutState.LAYOUT_END) { + if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { + bottom = layoutState.mOffset - result.mConsumed; + top = bottom - maxSize; + left = 0; + } else { + top = layoutState.mOffset + result.mConsumed; + bottom = top + maxSize; + left = getWidth(); + } + for (int i = count - 1; i >= 0; i--) { + View view = mSet[i]; + LayoutParams params = (LayoutParams) view.getLayoutParams(); + + right = mOrientationHelper.getDecoratedMeasurementInOther(view); + if (layoutState.mLayoutDirection == LayoutState.LAYOUT_END) { + left -= right; + } + layoutDecoratedWithMargins(view, left, top, left + right, bottom); + if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { + left += right; + } + if (params.isItemRemoved() || params.isItemChanged()) { + result.mIgnoreConsumed = true; + } + result.mFocusable |= view.hasFocusable(); + } + } else { + if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { + bottom = layoutState.mOffset - result.mConsumed; + top = bottom - maxSize; + left = getWidth(); + } else { + top = layoutState.mOffset + result.mConsumed; + bottom = top + maxSize; + left = 0; + } + for (int i = 0; i < count; i++) { + View view = mSet[i]; + LayoutParams params = (LayoutParams) view.getLayoutParams(); + + right = mOrientationHelper.getDecoratedMeasurementInOther(view); + if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { + left -= right; + } + layoutDecoratedWithMargins(view, left, top, left + right, bottom); + if (layoutState.mLayoutDirection != LayoutState.LAYOUT_START) { + left += right; + } + if (params.isItemRemoved() || params.isItemChanged()) { + result.mIgnoreConsumed = true; + } + result.mFocusable |= view.hasFocusable(); + } + } + result.mConsumed += maxSize; + Arrays.fill(mSet, null); + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LinearLayoutManager.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LinearLayoutManager.java index edf0a3ee2..df892c8bb 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LinearLayoutManager.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LinearLayoutManager.java @@ -124,6 +124,8 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager implements */ int mPendingScrollPosition = NO_POSITION; + boolean mPendingScrollPositionBottom = true; + /** * Used to keep the offset value when {@link #scrollToPositionWithOffset(int, int)} is * called. @@ -515,7 +517,7 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager implements if (existing != null) { final int current; final int upcomingOffset; - if (mShouldReverseLayout) { + if (mPendingScrollPositionBottom) { current = mOrientationHelper.getEndAfterPadding() - mOrientationHelper.getDecoratedEnd(existing); upcomingOffset = current - mPendingScrollPositionOffset; @@ -847,16 +849,16 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager implements // get position of any child, does not matter int pos = getPosition(getChildAt(0)); anchorInfo.mLayoutFromEnd = mPendingScrollPosition < pos - == mShouldReverseLayout; + == mPendingScrollPositionBottom; } anchorInfo.assignCoordinateFromPadding(); } return true; } // override layout from end values for consistency - anchorInfo.mLayoutFromEnd = mShouldReverseLayout; + anchorInfo.mLayoutFromEnd = mPendingScrollPositionBottom; // if this changes, we should update prepareForDrop as well - if (mShouldReverseLayout) { + if (mPendingScrollPositionBottom) { anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding() - mPendingScrollPositionOffset; } else { @@ -1010,9 +1012,15 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager implements * @see #setReverseLayout(boolean) * @see #scrollToPosition(int) */ + public void scrollToPositionWithOffset(int position, int offset) { + scrollToPositionWithOffset(position, offset, mShouldReverseLayout); + } + + public void scrollToPositionWithOffset(int position, int offset, boolean bottom) { mPendingScrollPosition = position; mPendingScrollPositionOffset = offset; + mPendingScrollPositionBottom = bottom; if (mPendingSavedState != null) { mPendingSavedState.invalidateAnchor(); } @@ -1334,7 +1342,7 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager implements * @param startIndex inclusive * @param endIndex exclusive */ - private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) { + protected void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) { if (startIndex == endIndex) { return; } @@ -1362,7 +1370,7 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager implements * to detect children that will go out of bounds after scrolling, without * actually moving them. */ - private void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) { + protected void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) { if (dt < 0) { if (DEBUG) { Log.d(TAG, "Called recycle from start with a negative value. This might happen" @@ -1407,7 +1415,7 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager implements * to detect children that will go out of bounds after scrolling, without * actually moving them. */ - private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int dt) { + protected void recycleViewsFromEnd(RecyclerView.Recycler recycler, int dt) { final int childCount = getChildCount(); if (dt < 0) { if (DEBUG) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/video/MP4Builder.java b/TMessagesProj/src/main/java/org/telegram/messenger/video/MP4Builder.java index 5c12d894f..db5d98744 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/video/MP4Builder.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/video/MP4Builder.java @@ -8,7 +8,6 @@ package org.telegram.messenger.video; -import android.annotation.TargetApi; import android.media.MediaCodec; import android.media.MediaFormat; @@ -50,7 +49,6 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.List; -@TargetApi(16) public class MP4Builder { private InterleaveChunkMdat mdat = null; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/video/Mp4Movie.java b/TMessagesProj/src/main/java/org/telegram/messenger/video/Mp4Movie.java index e7e47442b..4709024d0 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/video/Mp4Movie.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/video/Mp4Movie.java @@ -8,7 +8,6 @@ package org.telegram.messenger.video; -import android.annotation.TargetApi; import android.media.MediaCodec; import android.media.MediaFormat; @@ -17,7 +16,6 @@ import com.googlecode.mp4parser.util.Matrix; import java.io.File; import java.util.ArrayList; -@TargetApi(16) public class Mp4Movie { private Matrix matrix = Matrix.ROTATE_0; private ArrayList tracks = new ArrayList<>(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/video/OutputSurface.java b/TMessagesProj/src/main/java/org/telegram/messenger/video/OutputSurface.java index 3354e1628..3a2fa7a8c 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/video/OutputSurface.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/video/OutputSurface.java @@ -16,7 +16,6 @@ package org.telegram.messenger.video; -import android.annotation.TargetApi; import android.graphics.SurfaceTexture; import android.opengl.GLES20; import android.view.Surface; @@ -30,7 +29,6 @@ import javax.microedition.khronos.egl.EGLContext; import javax.microedition.khronos.egl.EGLDisplay; import javax.microedition.khronos.egl.EGLSurface; -@TargetApi(16) public class OutputSurface implements SurfaceTexture.OnFrameAvailableListener { private static final int EGL_OPENGL_ES2_BIT = 4; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/video/TextureRenderer.java b/TMessagesProj/src/main/java/org/telegram/messenger/video/TextureRenderer.java index accdeefbd..3f093d8c7 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/video/TextureRenderer.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/video/TextureRenderer.java @@ -20,13 +20,11 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; -import android.annotation.TargetApi; import android.graphics.SurfaceTexture; import android.opengl.GLES11Ext; import android.opengl.GLES20; import android.opengl.Matrix; -@TargetApi(16) public class TextureRenderer { private static final int FLOAT_SIZE_BYTES = 4; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/video/Track.java b/TMessagesProj/src/main/java/org/telegram/messenger/video/Track.java index 47d85025a..b9e6abf97 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/video/Track.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/video/Track.java @@ -8,7 +8,6 @@ package org.telegram.messenger.video; -import android.annotation.TargetApi; import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaFormat; @@ -35,7 +34,6 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.Map; -@TargetApi(16) public class Track { private class SamplePresentationTime { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/voip/VoIPActionsReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/voip/VoIPActionsReceiver.java new file mode 100644 index 000000000..193e34f2d --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/voip/VoIPActionsReceiver.java @@ -0,0 +1,17 @@ +package org.telegram.messenger.voip; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +/** + * Created by grishka on 28.07.17. + */ + +public class VoIPActionsReceiver extends BroadcastReceiver{ + @Override + public void onReceive(Context context, Intent intent){ + if(VoIPBaseService.getSharedInstance()!=null) + VoIPBaseService.getSharedInstance().handleNotificationAction(intent); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/voip/VoIPBaseService.java b/TMessagesProj/src/main/java/org/telegram/messenger/voip/VoIPBaseService.java new file mode 100644 index 000000000..0f2ca5839 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/voip/VoIPBaseService.java @@ -0,0 +1,699 @@ +package org.telegram.messenger.voip; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.Notification; +import android.app.PendingIntent; +import android.app.Service; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothProfile; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.media.AudioFormat; +import android.media.AudioManager; +import android.media.AudioTrack; +import android.media.MediaPlayer; +import android.media.SoundPool; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.Build; +import android.os.PowerManager; +import android.os.Vibrator; +import android.telephony.TelephonyManager; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.FileLoader; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.ImageLoader; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.R; +import org.telegram.messenger.StatsController; +import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.Components.voip.VoIPHelper; +import org.telegram.ui.VoIPActivity; +import org.telegram.ui.VoIPPermissionActivity; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; + +/** + * Created by grishka on 21.07.17. + */ + +public abstract class VoIPBaseService extends Service implements SensorEventListener, AudioManager.OnAudioFocusChangeListener, VoIPController.ConnectionStateListener{ + + public static final int STATE_WAIT_INIT = 1; + public static final int STATE_WAIT_INIT_ACK = 2; + public static final int STATE_ESTABLISHED = 3; + public static final int STATE_FAILED = 4; + public static final int STATE_RECONNECTING = 5; + public static final int STATE_ENDED = 11; + public static final String ACTION_HEADSET_PLUG = "android.intent.action.HEADSET_PLUG"; + + protected static final int ID_ONGOING_CALL_NOTIFICATION = 201; + protected static final int ID_INCOMING_CALL_NOTIFICATION = 202; + + public static final int DISCARD_REASON_HANGUP = 1; + public static final int DISCARD_REASON_DISCONNECT = 2; + public static final int DISCARD_REASON_MISSED = 3; + public static final int DISCARD_REASON_LINE_BUSY = 4; + + + protected static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK = 32; + protected static VoIPBaseService sharedInstance; + protected NetworkInfo lastNetInfo; + protected int currentState = 0; + protected Notification ongoingCallNotification; + protected VoIPController controller; + protected int lastError; + protected PowerManager.WakeLock proximityWakelock; + protected PowerManager.WakeLock cpuWakelock; + protected boolean isProximityNear; + protected boolean isHeadsetPlugged; + protected ArrayList stateListeners = new ArrayList<>(); + protected MediaPlayer ringtonePlayer; + protected Vibrator vibrator; + protected SoundPool soundPool; + protected int spRingbackID; + protected int spFailedID; + protected int spEndId; + protected int spBusyId; + protected int spConnectingId; + protected int spPlayID; + protected boolean needPlayEndSound; + protected boolean haveAudioFocus; + protected boolean micMute; + protected boolean controllerStarted; + protected BluetoothAdapter btAdapter; + protected VoIPController.Stats stats = new VoIPController.Stats(); + protected VoIPController.Stats prevStats = new VoIPController.Stats(); + protected boolean isBtHeadsetConnected; + protected Runnable afterSoundRunnable=new Runnable(){ + @Override + public void run(){ + soundPool.release(); + if(isBtHeadsetConnected) + ((AudioManager) ApplicationLoader.applicationContext.getSystemService(AUDIO_SERVICE)).stopBluetoothSco(); + ((AudioManager)ApplicationLoader.applicationContext.getSystemService(AUDIO_SERVICE)).setSpeakerphoneOn(false); + } + }; + protected long lastKnownDuration = 0; + protected boolean playingSound; + protected boolean isOutgoing; + protected Runnable timeoutRunnable; + protected BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (ACTION_HEADSET_PLUG.equals(intent.getAction())) { + isHeadsetPlugged = intent.getIntExtra("state", 0) == 1; + if (isHeadsetPlugged && proximityWakelock != null && proximityWakelock.isHeld()) { + proximityWakelock.release(); + } + isProximityNear = false; + updateOutputGainControlState(); + } else if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { + updateNetworkType(); + } else if(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())){ + //FileLog.e("bt headset state = "+intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0)); + updateBluetoothHeadsetState(intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0)==BluetoothProfile.STATE_CONNECTED); + }else if(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED.equals(intent.getAction())){ + for (StateListener l : stateListeners) + l.onAudioSettingsChanged(); + }else if(TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(intent.getAction())){ + String state=intent.getStringExtra(TelephonyManager.EXTRA_STATE); + if(TelephonyManager.EXTRA_STATE_OFFHOOK.equals(state)){ + hangUp(); + } + } + } + }; + private Boolean mHasEarpiece = null; + private boolean wasEstablished; + protected int signalBarCount; + + public boolean hasEarpiece() { + if(((TelephonyManager)getSystemService(TELEPHONY_SERVICE)).getPhoneType()!=TelephonyManager.PHONE_TYPE_NONE) + return true; + if (mHasEarpiece != null) { + return mHasEarpiece; + } + + // not calculated yet, do it now + try { + AudioManager am=(AudioManager)getSystemService(AUDIO_SERVICE); + Method method = AudioManager.class.getMethod("getDevicesForStream", Integer.TYPE); + Field field = AudioManager.class.getField("DEVICE_OUT_EARPIECE"); + int earpieceFlag = field.getInt(null); + int bitmaskResult = (int) method.invoke(am, AudioManager.STREAM_VOICE_CALL); + + // check if masked by the earpiece flag + if ((bitmaskResult & earpieceFlag) == earpieceFlag) { + mHasEarpiece = Boolean.TRUE; + } else { + mHasEarpiece = Boolean.FALSE; + } + } catch (Throwable error) { + FileLog.e("Error while checking earpiece! ", error); + mHasEarpiece = Boolean.TRUE; + } + + return mHasEarpiece; + } + + protected int getStatsNetworkType() { + int netType = StatsController.TYPE_WIFI; + if (lastNetInfo != null) { + if (lastNetInfo.getType() == ConnectivityManager.TYPE_MOBILE) + netType = lastNetInfo.isRoaming() ? StatsController.TYPE_ROAMING : StatsController.TYPE_MOBILE; + } + return netType; + } + + public void registerStateListener(StateListener l) { + stateListeners.add(l); + if (currentState != 0) + l.onStateChanged(currentState); + if(signalBarCount!=0) + l.onSignalBarsCountChanged(signalBarCount); + } + + public void unregisterStateListener(StateListener l) { + stateListeners.remove(l); + } + + public void setMicMute(boolean mute) { + controller.setMicMute(micMute = mute); + } + + public boolean isMicMute() { + return micMute; + } + + public String getDebugString() { + return controller.getDebugString(); + } + + public long getCallDuration() { + if (!controllerStarted || controller == null) + return lastKnownDuration; + return lastKnownDuration = controller.getCallDuration(); + } + + public static VoIPBaseService getSharedInstance(){ + return sharedInstance; + } + + public void stopRinging() { + if (ringtonePlayer != null) { + ringtonePlayer.stop(); + ringtonePlayer.release(); + ringtonePlayer = null; + } + if (vibrator != null) { + vibrator.cancel(); + vibrator = null; + } + } + + protected void showNotification(String name, TLRPC.FileLocation photo, Class activity) { + Intent intent = new Intent(this, activity); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); + Notification.Builder builder = new Notification.Builder(this) + .setContentTitle(LocaleController.getString("VoipOutgoingCall", R.string.VoipOutgoingCall)) + .setContentText(name) + .setSmallIcon(R.drawable.notification) + .setContentIntent(PendingIntent.getActivity(this, 0, intent, 0)); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + Intent endIntent = new Intent(this, VoIPActionsReceiver.class); + endIntent.setAction(getPackageName() + ".END_CALL"); + builder.addAction(R.drawable.ic_call_end_white_24dp, LocaleController.getString("VoipEndCall", R.string.VoipEndCall), PendingIntent.getBroadcast(this, 0, endIntent, PendingIntent.FLAG_UPDATE_CURRENT)); + builder.setPriority(Notification.PRIORITY_MAX); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + builder.setShowWhen(false); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + builder.setColor(0xff2ca5e0); + } + if (photo!= null) { + BitmapDrawable img = ImageLoader.getInstance().getImageFromMemory(photo, null, "50_50"); + if (img != null) { + builder.setLargeIcon(img.getBitmap()); + } else { + try { + float scaleFactor = 160.0f / AndroidUtilities.dp(50); + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inSampleSize = scaleFactor < 1 ? 1 : (int) scaleFactor; + Bitmap bitmap = BitmapFactory.decodeFile(FileLoader.getPathToAttach(photo, true).toString(), options); + if (bitmap != null) { + builder.setLargeIcon(bitmap); + } + } catch (Throwable e) { + FileLog.e(e); + } + } + } + ongoingCallNotification = builder.getNotification(); + startForeground(ID_ONGOING_CALL_NOTIFICATION, ongoingCallNotification); + } + + protected abstract long getCallID(); + public abstract void hangUp(); + public abstract void acceptIncomingCall(); + public abstract void declineIncomingCall(int reason, Runnable onDone); + public abstract void declineIncomingCall(); + + @Override + public void onDestroy() { + FileLog.d("=============== VoIPService STOPPING ==============="); + stopForeground(true); + stopRinging(); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.appDidLogout); + SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE); + Sensor proximity = sm.getDefaultSensor(Sensor.TYPE_PROXIMITY); + if (proximity != null) { + sm.unregisterListener(this); + } + if (proximityWakelock != null && proximityWakelock.isHeld()) { + proximityWakelock.release(); + } + unregisterReceiver(receiver); + if(timeoutRunnable!=null){ + AndroidUtilities.cancelRunOnUIThread(timeoutRunnable); + timeoutRunnable=null; + } + super.onDestroy(); + sharedInstance = null; + AndroidUtilities.runOnUIThread(new Runnable(){ + @Override + public void run(){ + NotificationCenter.getInstance().postNotificationName(NotificationCenter.didEndedCall); + } + }); + if (controller != null && controllerStarted) { + lastKnownDuration = controller.getCallDuration(); + updateStats(); + StatsController.getInstance().incrementTotalCallsTime(getStatsNetworkType(), (int) (lastKnownDuration / 1000) % 5); + onControllerPreRelease(); + controller.release(); + controller = null; + } + cpuWakelock.release(); + AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE); + if(isBtHeadsetConnected && !playingSound){ + am.stopBluetoothSco(); + am.setSpeakerphoneOn(false); + } + try{ + am.setMode(AudioManager.MODE_NORMAL); + }catch(SecurityException x){ + FileLog.e("Error setting audio more to normal", x); + } + am.unregisterMediaButtonEventReceiver(new ComponentName(this, VoIPMediaButtonReceiver.class)); + if (haveAudioFocus) + am.abandonAudioFocus(this); + + if (!playingSound) + soundPool.release(); + + ConnectionsManager.getInstance().setAppPaused(true, false); + VoIPHelper.lastCallTime=System.currentTimeMillis(); + } + + protected void onControllerPreRelease(){ + + } + + protected VoIPController createController(){ + return new VoIPController(); + } + + @Override + public void onCreate() { + super.onCreate(); + FileLog.d("=============== VoIPService STARTING ==============="); + AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER)!=null) { + int outFramesPerBuffer = Integer.parseInt(am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER)); + VoIPController.setNativeBufferSize(outFramesPerBuffer); + } else { + VoIPController.setNativeBufferSize(AudioTrack.getMinBufferSize(48000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT) / 2); + } + updateServerConfig(); + try { + controller = createController(); + controller.setConnectionStateListener(this); + + cpuWakelock = ((PowerManager) getSystemService(POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "telegram-voip"); + cpuWakelock.acquire(); + + btAdapter=am.isBluetoothScoAvailableOffCall() ? BluetoothAdapter.getDefaultAdapter() : null; + + IntentFilter filter = new IntentFilter(); + filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + filter.addAction(ACTION_HEADSET_PLUG); + if(btAdapter!=null){ + filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); + filter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED); + } + filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); + registerReceiver(receiver, filter); + + ConnectionsManager.getInstance().setAppPaused(false, false); + + soundPool = new SoundPool(1, AudioManager.STREAM_VOICE_CALL, 0); + spConnectingId = soundPool.load(this, R.raw.voip_connecting, 1); + spRingbackID = soundPool.load(this, R.raw.voip_ringback, 1); + spFailedID = soundPool.load(this, R.raw.voip_failed, 1); + spEndId = soundPool.load(this, R.raw.voip_end, 1); + spBusyId = soundPool.load(this, R.raw.voip_busy, 1); + + am.registerMediaButtonEventReceiver(new ComponentName(this, VoIPMediaButtonReceiver.class)); + + if(btAdapter!=null && btAdapter.isEnabled()){ + int headsetState=btAdapter.getProfileConnectionState(BluetoothProfile.HEADSET); + updateBluetoothHeadsetState(headsetState==BluetoothProfile.STATE_CONNECTED); + if(headsetState==BluetoothProfile.STATE_CONNECTED) + am.setBluetoothScoOn(true); + for (StateListener l : stateListeners) + l.onAudioSettingsChanged(); + } + + NotificationCenter.getInstance().addObserver(this, NotificationCenter.appDidLogout); + } catch (Exception x) { + FileLog.e("error initializing voip controller", x); + callFailed(); + } + } + + protected abstract void updateServerConfig(); + protected abstract void showNotification(); + + protected void dispatchStateChanged(int state) { + FileLog.d("== Call "+getCallID()+" state changed to "+state+" =="); + currentState = state; + for (int a = 0; a < stateListeners.size(); a++) { + StateListener l = stateListeners.get(a); + l.onStateChanged(state); + } + } + + protected void updateStats() { + controller.getStats(stats); + long wifiSentDiff = stats.bytesSentWifi - prevStats.bytesSentWifi; + long wifiRecvdDiff = stats.bytesRecvdWifi - prevStats.bytesRecvdWifi; + long mobileSentDiff = stats.bytesSentMobile - prevStats.bytesSentMobile; + long mobileRecvdDiff = stats.bytesRecvdMobile - prevStats.bytesRecvdMobile; + VoIPController.Stats tmp = stats; + stats = prevStats; + prevStats = tmp; + if (wifiSentDiff > 0) + StatsController.getInstance().incrementSentBytesCount(StatsController.TYPE_WIFI, StatsController.TYPE_CALLS, wifiSentDiff); + if (wifiRecvdDiff > 0) + StatsController.getInstance().incrementReceivedBytesCount(StatsController.TYPE_WIFI, StatsController.TYPE_CALLS, wifiRecvdDiff); + if (mobileSentDiff > 0) + StatsController.getInstance().incrementSentBytesCount(lastNetInfo != null && lastNetInfo.isRoaming() ? StatsController.TYPE_ROAMING : StatsController.TYPE_MOBILE, + StatsController.TYPE_CALLS, mobileSentDiff); + if (mobileRecvdDiff > 0) + StatsController.getInstance().incrementReceivedBytesCount(lastNetInfo != null && lastNetInfo.isRoaming() ? StatsController.TYPE_ROAMING : StatsController.TYPE_MOBILE, + StatsController.TYPE_CALLS, mobileRecvdDiff); + } + + protected void configureDeviceForCall() { + needPlayEndSound = true; + AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE); + am.setMode(AudioManager.MODE_IN_COMMUNICATION); + am.setSpeakerphoneOn(false); + am.requestAudioFocus(this, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN); + updateOutputGainControlState(); + + SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE); + Sensor proximity = sm.getDefaultSensor(Sensor.TYPE_PROXIMITY); + try{ + if(proximity!=null){ + proximityWakelock=((PowerManager) getSystemService(Context.POWER_SERVICE)).newWakeLock(PROXIMITY_SCREEN_OFF_WAKE_LOCK, "telegram-voip-prx"); + sm.registerListener(this, proximity, SensorManager.SENSOR_DELAY_NORMAL); + } + }catch(Exception x){ + FileLog.e("Error initializing proximity sensor", x); + } + } + + @SuppressLint("NewApi") + @Override + public void onSensorChanged(SensorEvent event) { + if (event.sensor.getType() == Sensor.TYPE_PROXIMITY) { + AudioManager am=(AudioManager) getSystemService(AUDIO_SERVICE); + if (isHeadsetPlugged || am.isSpeakerphoneOn() || (isBluetoothHeadsetConnected() && am.isBluetoothScoOn())) { + return; + } + boolean newIsNear = event.values[0] < Math.min(event.sensor.getMaximumRange(), 3); + if (newIsNear != isProximityNear) { + FileLog.d("proximity " + newIsNear); + isProximityNear = newIsNear; + try{ + if(isProximityNear){ + proximityWakelock.acquire(); + }else{ + proximityWakelock.release(1); // this is non-public API before L + } + }catch(Exception x){ + FileLog.e(x); + } + } + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + + } + + public boolean isBluetoothHeadsetConnected(){ + return isBtHeadsetConnected; + } + + public void onAudioFocusChange(int focusChange) { + if (focusChange == AudioManager.AUDIOFOCUS_GAIN) { + haveAudioFocus = true; + } else { + haveAudioFocus = false; + } + } + + protected void updateBluetoothHeadsetState(boolean connected){ + if(connected==isBtHeadsetConnected) + return; + isBtHeadsetConnected=connected; + AudioManager am=(AudioManager)getSystemService(AUDIO_SERVICE); + if(connected){ + am.startBluetoothSco(); + am.setSpeakerphoneOn(false); + am.setBluetoothScoOn(true); + }else + am.stopBluetoothSco(); + for (StateListener l : stateListeners) + l.onAudioSettingsChanged(); + } + + public int getLastError() { + return lastError; + } + + public int getCallState(){ + return currentState; + } + + protected void updateNetworkType() { + ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); + NetworkInfo info = cm.getActiveNetworkInfo(); + lastNetInfo = info; + int type = VoIPController.NET_TYPE_UNKNOWN; + if (info != null) { + switch (info.getType()) { + case ConnectivityManager.TYPE_MOBILE: + switch (info.getSubtype()) { + case TelephonyManager.NETWORK_TYPE_GPRS: + type = VoIPController.NET_TYPE_GPRS; + break; + case TelephonyManager.NETWORK_TYPE_EDGE: + case TelephonyManager.NETWORK_TYPE_1xRTT: + type = VoIPController.NET_TYPE_EDGE; + break; + case TelephonyManager.NETWORK_TYPE_UMTS: + case TelephonyManager.NETWORK_TYPE_EVDO_0: + type = VoIPController.NET_TYPE_3G; + break; + case TelephonyManager.NETWORK_TYPE_HSDPA: + case TelephonyManager.NETWORK_TYPE_HSPA: + case TelephonyManager.NETWORK_TYPE_HSPAP: + case TelephonyManager.NETWORK_TYPE_HSUPA: + case TelephonyManager.NETWORK_TYPE_EVDO_A: + case TelephonyManager.NETWORK_TYPE_EVDO_B: + type = VoIPController.NET_TYPE_HSPA; + break; + case TelephonyManager.NETWORK_TYPE_LTE: + type = VoIPController.NET_TYPE_LTE; + break; + default: + type = VoIPController.NET_TYPE_OTHER_MOBILE; + break; + } + break; + case ConnectivityManager.TYPE_WIFI: + type = VoIPController.NET_TYPE_WIFI; + break; + case ConnectivityManager.TYPE_ETHERNET: + type = VoIPController.NET_TYPE_ETHERNET; + break; + } + } + if (controller != null) { + controller.setNetworkType(type); + } + } + + protected void callFailed() { + callFailed(controller != null && controllerStarted ? controller.getLastError() : VoIPController.ERROR_UNKNOWN); + } + + protected void callFailed(int errorCode){ + try{ + throw new Exception("Call "+getCallID()+" failed with error code "+errorCode); + }catch(Exception x){ + FileLog.e(x); + } + lastError = errorCode; + dispatchStateChanged(STATE_FAILED); + if(errorCode!=VoIPController.ERROR_LOCALIZED && soundPool!=null){ + playingSound=true; + soundPool.play(spFailedID, 1, 1, 0, 0, 1); + AndroidUtilities.runOnUIThread(afterSoundRunnable, 1000); + } + stopSelf(); + } + + @Override + public void onConnectionStateChanged(int newState) { + if (newState == STATE_FAILED) { + callFailed(); + return; + } + if (newState == STATE_ESTABLISHED) { + if (spPlayID != 0) { + soundPool.stop(spPlayID); + spPlayID = 0; + } + if(!wasEstablished){ + wasEstablished=true; + if(!isProximityNear){ + Vibrator vibrator=(Vibrator) getSystemService(VIBRATOR_SERVICE); + if(vibrator.hasVibrator()) + vibrator.vibrate(100); + } + AndroidUtilities.runOnUIThread(new Runnable(){ + @Override + public void run(){ + if(controller==null) + return; + int netType=getStatsNetworkType(); + StatsController.getInstance().incrementTotalCallsTime(netType, 5); + AndroidUtilities.runOnUIThread(this, 5000); + } + }, 5000); + if(isOutgoing) + StatsController.getInstance().incrementSentItemsCount(getStatsNetworkType(), StatsController.TYPE_CALLS, 1); + else + StatsController.getInstance().incrementReceivedItemsCount(getStatsNetworkType(), StatsController.TYPE_CALLS, 1); + } + } + if(newState==STATE_RECONNECTING){ + if(spPlayID!=0) + soundPool.stop(spPlayID); + spPlayID=soundPool.play(spConnectingId, 1, 1, 0, -1, 1); + } + dispatchStateChanged(newState); + } + + @Override + public void onSignalBarCountChanged(int newCount){ + signalBarCount=newCount; + for (int a = 0; a < stateListeners.size(); a++) { + StateListener l = stateListeners.get(a); + l.onSignalBarsCountChanged(newCount); + } + } + + protected void callEnded() { + FileLog.d("Call "+getCallID()+" ended"); + dispatchStateChanged(STATE_ENDED); + if (needPlayEndSound) { + playingSound = true; + soundPool.play(spEndId, 1, 1, 0, 0, 1); + AndroidUtilities.runOnUIThread(afterSoundRunnable, 700); + } + if(timeoutRunnable!=null){ + AndroidUtilities.cancelRunOnUIThread(timeoutRunnable); + timeoutRunnable=null; + } + stopSelf(); + } + + public boolean isOutgoing(){ + return isOutgoing; + } + + public void handleNotificationAction(Intent intent){ + if ((getPackageName() + ".END_CALL").equals(intent.getAction())) { + stopForeground(true); + hangUp(); + } else if ((getPackageName() + ".DECLINE_CALL").equals(intent.getAction())) { + stopForeground(true); + declineIncomingCall(DISCARD_REASON_LINE_BUSY, null); + } else if ((getPackageName() + ".ANSWER_CALL").equals(intent.getAction())) { + showNotification(); + if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.M && checkSelfPermission(Manifest.permission.RECORD_AUDIO)!=PackageManager.PERMISSION_GRANTED){ + try{ + PendingIntent.getActivity(VoIPBaseService.this, 0, new Intent(VoIPBaseService.this, VoIPPermissionActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0).send(); + }catch(Exception x){ + FileLog.e("Error starting permission activity", x); + } + return; + } + acceptIncomingCall(); + try{ + PendingIntent.getActivity(VoIPBaseService.this, 0, new Intent(VoIPBaseService.this, VoIPActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_SINGLE_TOP), 0).send(); + }catch(Exception x){ + FileLog.e("Error starting incall activity", x); + } + } + } + + public void updateOutputGainControlState(){ + AudioManager am=(AudioManager) getSystemService(AUDIO_SERVICE); + controller.setAudioOutputGainControlEnabled(hasEarpiece() && !am.isSpeakerphoneOn() && !am.isBluetoothScoOn() && !isHeadsetPlugged); + } + + public interface StateListener { + void onStateChanged(int state); + void onSignalBarsCountChanged(int count); + void onAudioSettingsChanged(); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/voip/VoIPController.java b/TMessagesProj/src/main/java/org/telegram/messenger/voip/VoIPController.java index 5fb8033fc..fb8defa5e 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/voip/VoIPController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/voip/VoIPController.java @@ -11,7 +11,6 @@ package org.telegram.messenger.voip; import android.app.Activity; import android.content.SharedPreferences; import android.media.audiofx.AcousticEchoCanceler; -import android.media.audiofx.NoiseSuppressor; import android.os.Build; import android.os.SystemClock; @@ -24,7 +23,6 @@ import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; -import java.util.Collections; import java.util.Locale; public class VoIPController { @@ -60,12 +58,12 @@ public class VoIPController { public static final int ERROR_TIMEOUT=2; public static final int ERROR_AUDIO_IO=3; - private long nativeInst = 0; - private long callStartTime; - private ConnectionStateListener listener; + protected long nativeInst = 0; + protected long callStartTime; + protected ConnectionStateListener listener; public VoIPController() { - nativeInst = nativeInit(Build.VERSION.SDK_INT); + nativeInst = nativeInit(); } public void start() { @@ -118,7 +116,7 @@ public class VoIPController { return nativeGetDebugString(nativeInst); } - private void ensureNativeInstance() { + protected void ensureNativeInstance() { if (nativeInst == 0) { throw new IllegalStateException("Native instance is not valid"); } @@ -128,6 +126,7 @@ public class VoIPController { listener = connectionStateListener; } + // called from native code private void handleStateChange(int state) { if(state==STATE_ESTABLISHED && callStartTime==0) callStartTime = SystemClock.elapsedRealtime(); @@ -136,6 +135,12 @@ public class VoIPController { } } + // called from native code + private void handleSignalBarsChange(int count){ + if(listener!=null) + listener.onSignalBarCountChanged(count); + } + public void setNetworkType(int type) { ensureNativeInstance(); nativeSetNetworkType(nativeInst, type); @@ -234,7 +239,12 @@ public class VoIPController { nativeSetProxy(nativeInst, address, port, username, password); } - private native long nativeInit(int systemVersion); + public void setAudioOutputGainControlEnabled(boolean enabled){ + ensureNativeInstance(); + nativeSetAudioOutputGainControlEnabled(nativeInst, enabled); + } + + private native long nativeInit(); private native void nativeStart(long inst); private native void nativeConnect(long inst); private static native void nativeSetNativeBufferSize(int size); @@ -251,10 +261,12 @@ public class VoIPController { private native int nativeGetLastError(long inst); private native String nativeGetDebugString(long inst); private static native String nativeGetVersion(); + private native void nativeSetAudioOutputGainControlEnabled(long inst, boolean enabled); private native String nativeGetDebugLog(long inst); public interface ConnectionStateListener { void onConnectionStateChanged(int newState); + void onSignalBarCountChanged(int newCount); } public static class Stats{ diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/voip/VoIPService.java b/TMessagesProj/src/main/java/org/telegram/messenger/voip/VoIPService.java index 68d9362a3..529d91940 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/voip/VoIPService.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/voip/VoIPService.java @@ -8,51 +8,26 @@ package org.telegram.messenger.voip; -import android.Manifest; -import android.annotation.SuppressLint; import android.app.Activity; import android.app.KeyguardManager; import android.app.Notification; import android.app.PendingIntent; -import android.app.Service; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothHeadset; -import android.bluetooth.BluetoothProfile; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.content.SharedPreferences; -import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.drawable.BitmapDrawable; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; -import android.media.AudioFormat; import android.media.AudioManager; -import android.media.AudioRecord; -import android.media.AudioTrack; import android.media.MediaPlayer; import android.media.RingtoneManager; -import android.media.SoundPool; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; import android.net.Uri; import android.os.Build; import android.os.IBinder; -import android.os.PowerManager; import android.os.Vibrator; import android.support.annotation.Nullable; import android.support.v4.app.NotificationManagerCompat; -import android.telephony.TelephonyManager; -import android.text.Html; import android.text.SpannableString; import android.text.style.ForegroundColorSpan; -import android.util.Log; import android.view.KeyEvent; import org.telegram.messenger.AndroidUtilities; @@ -68,18 +43,17 @@ import org.telegram.messenger.MessagesController; import org.telegram.messenger.MessagesStorage; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; -import org.telegram.messenger.StatsController; +import org.telegram.messenger.UserObject; import org.telegram.messenger.Utilities; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.RequestDelegate; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.Components.voip.VoIPHelper; import org.telegram.ui.VoIPActivity; import org.telegram.ui.VoIPFeedbackActivity; -import org.telegram.ui.VoIPPermissionActivity; import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -87,22 +61,12 @@ import java.util.Collections; import java.util.HashSet; import java.util.Iterator; -public class VoIPService extends Service implements VoIPController.ConnectionStateListener, SensorEventListener, AudioManager.OnAudioFocusChangeListener, NotificationCenter.NotificationCenterDelegate{ - - private static final int ID_ONGOING_CALL_NOTIFICATION = 201; - private static final int ID_INCOMING_CALL_NOTIFICATION = 202; +public class VoIPService extends VoIPBaseService implements NotificationCenter.NotificationCenterDelegate{ private static final int CALL_MIN_LAYER = 65; private static final int CALL_MAX_LAYER = 65; - public static final int STATE_WAIT_INIT = 1; - public static final int STATE_WAIT_INIT_ACK = 2; - public static final int STATE_ESTABLISHED = 3; - public static final int STATE_FAILED = 4; - public static final int STATE_RECONNECTING = 5; - public static final int STATE_HANGING_UP = 10; - public static final int STATE_ENDED = 11; public static final int STATE_EXCHANGING_KEYS = 12; public static final int STATE_WAITING = 13; public static final int STATE_REQUESTING = 14; @@ -110,126 +74,25 @@ public class VoIPService extends Service implements VoIPController.ConnectionSta public static final int STATE_RINGING = 16; public static final int STATE_BUSY = 17; - public static final int DISCARD_REASON_HANGUP = 1; - public static final int DISCARD_REASON_DISCONNECT = 2; - public static final int DISCARD_REASON_MISSED = 3; - public static final int DISCARD_REASON_LINE_BUSY = 4; - private static final String TAG = "tg-voip-service"; - private static VoIPService sharedInstance; - - private int userID; private TLRPC.User user; - private boolean isOutgoing; private TLRPC.PhoneCall call; - private Notification ongoingCallNotification; - private VoIPController controller; - private int currentState = 0; - private int endHash; private int callReqId; - private int lastError; private byte[] g_a; private byte[] a_or_b; private byte[] g_a_hash; private byte[] authKey; private long keyFingerprint; + private boolean forceRating; public static TLRPC.PhoneCall callIShouldHavePutIntoIntent; - private static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK = 32; - - private PowerManager.WakeLock proximityWakelock; - private PowerManager.WakeLock cpuWakelock; - private boolean isProximityNear, isHeadsetPlugged; - private ArrayList stateListeners = new ArrayList<>(); - private MediaPlayer ringtonePlayer; - private Vibrator vibrator; - private SoundPool soundPool; - private int spRingbackID, spFailedID, spEndId, spBusyId, spConnectingId; - private int spPlayID; - private boolean needPlayEndSound; - private Runnable timeoutRunnable; - private boolean haveAudioFocus; - private boolean playingSound; - private boolean micMute; - private boolean controllerStarted; - private long lastKnownDuration = 0; - private VoIPController.Stats stats = new VoIPController.Stats(), prevStats = new VoIPController.Stats(); - private NetworkInfo lastNetInfo; - private Boolean mHasEarpiece = null; - private BluetoothAdapter btAdapter; - private boolean isBtHeadsetConnected; private boolean needSendDebugLog=false; private boolean endCallAfterRequest=false; - private boolean wasEstablished; private ArrayList pendingUpdates=new ArrayList<>(); - private Runnable afterSoundRunnable=new Runnable(){ - @Override - public void run(){ - soundPool.release(); - if(isBtHeadsetConnected) - ((AudioManager)ApplicationLoader.applicationContext.getSystemService(AUDIO_SERVICE)).stopBluetoothSco(); - ((AudioManager)ApplicationLoader.applicationContext.getSystemService(AUDIO_SERVICE)).setSpeakerphoneOn(false); - } - }; - - public static final String ACTION_HEADSET_PLUG = "android.intent.action.HEADSET_PLUG"; - - private BroadcastReceiver receiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (ACTION_HEADSET_PLUG.equals(intent.getAction())) { - isHeadsetPlugged = intent.getIntExtra("state", 0) == 1; - if (isHeadsetPlugged && proximityWakelock != null && proximityWakelock.isHeld()) { - proximityWakelock.release(); - } - isProximityNear = false; - } else if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { - updateNetworkType(); - } else if ((getPackageName() + ".END_CALL").equals(intent.getAction())) { - if (intent.getIntExtra("end_hash", 0) == endHash) { - stopForeground(true); - hangUp(); - } - } else if ((getPackageName() + ".DECLINE_CALL").equals(intent.getAction())) { - if (intent.getIntExtra("end_hash", 0) == endHash) { - stopForeground(true); - declineIncomingCall(DISCARD_REASON_LINE_BUSY, null); - } - } else if ((getPackageName() + ".ANSWER_CALL").equals(intent.getAction())) { - if (intent.getIntExtra("end_hash", 0) == endHash) { - showNotification(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { - try { - PendingIntent.getActivity(VoIPService.this, 0, new Intent(VoIPService.this, VoIPPermissionActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0).send(); - } catch (Exception x) { - FileLog.e("Error starting permission activity", x); - } - return; - } - acceptIncomingCall(); - try { - PendingIntent.getActivity(VoIPService.this, 0, new Intent(VoIPService.this, VoIPActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP), 0).send(); - } catch (Exception x) { - FileLog.e("Error starting incall activity", x); - } - } - }else if(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())){ - //FileLog.e("bt headset state = "+intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0)); - updateBluetoothHeadsetState(intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0)==BluetoothProfile.STATE_CONNECTED); - }else if(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED.equals(intent.getAction())){ - for (StateListener l : stateListeners) - l.onAudioSettingsChanged(); - }else if(TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(intent.getAction())){ - String state=intent.getStringExtra(TelephonyManager.EXTRA_STATE); - if(TelephonyManager.EXTRA_STATE_OFFHOOK.equals(state)){ - hangUp(); - } - } - } - }; + private Runnable delayedStartOutgoingCall; @Nullable @Override @@ -244,7 +107,8 @@ public class VoIPService extends Service implements VoIPController.ConnectionSta FileLog.e("Tried to start the VoIP service when it's already started"); return START_NOT_STICKY; } - userID = intent.getIntExtra("user_id", 0); + + int userID=intent.getIntExtra("user_id", 0); isOutgoing = intent.getBooleanExtra("is_outgoing", false); user = MessagesController.getInstance().getUser(userID); @@ -255,7 +119,15 @@ public class VoIPService extends Service implements VoIPController.ConnectionSta } if (isOutgoing) { - startOutgoingCall(); + dispatchStateChanged(STATE_REQUESTING); + delayedStartOutgoingCall=new Runnable(){ + @Override + public void run(){ + delayedStartOutgoingCall=null; + startOutgoingCall(); + } + }; + AndroidUtilities.runOnUIThread(delayedStartOutgoingCall, 2000); if (intent.getBooleanExtra("start_incall_activity", false)) { startActivity(new Intent(this, VoIPActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } @@ -272,16 +144,7 @@ public class VoIPService extends Service implements VoIPController.ConnectionSta } @Override - public void onCreate() { - super.onCreate(); - FileLog.d("=============== VoIPService STARTING ==============="); - AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE); - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1 && am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER)!=null) { - int outFramesPerBuffer = Integer.parseInt(am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER)); - VoIPController.setNativeBufferSize(outFramesPerBuffer); - } else { - VoIPController.setNativeBufferSize(AudioTrack.getMinBufferSize(48000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT) / 2); - } + protected void updateServerConfig(){ final SharedPreferences preferences = getSharedPreferences("mainconfig", MODE_PRIVATE); VoIPServerConfig.setConfig(preferences.getString("voip_server_config", "{}")); if(System.currentTimeMillis()-preferences.getLong("voip_server_config_updated", 0)>24*3600000){ @@ -296,151 +159,35 @@ public class VoIPService extends Service implements VoIPController.ConnectionSta } }); } - try { - controller = new VoIPController(); - controller.setConnectionStateListener(this); - - cpuWakelock = ((PowerManager) getSystemService(POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "telegram-voip"); - cpuWakelock.acquire(); - - btAdapter=am.isBluetoothScoAvailableOffCall() ? BluetoothAdapter.getDefaultAdapter() : null; - - IntentFilter filter = new IntentFilter(); - filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); - filter.addAction(ACTION_HEADSET_PLUG); - if(btAdapter!=null){ - filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); - filter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED); - } - filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); - filter.addAction(getPackageName() + ".END_CALL"); - filter.addAction(getPackageName() + ".DECLINE_CALL"); - filter.addAction(getPackageName() + ".ANSWER_CALL"); - registerReceiver(receiver, filter); - - ConnectionsManager.getInstance().setAppPaused(false, false); - - soundPool = new SoundPool(1, AudioManager.STREAM_VOICE_CALL, 0); - spConnectingId = soundPool.load(this, R.raw.voip_connecting, 1); - spRingbackID = soundPool.load(this, R.raw.voip_ringback, 1); - spFailedID = soundPool.load(this, R.raw.voip_failed, 1); - spEndId = soundPool.load(this, R.raw.voip_end, 1); - spBusyId = soundPool.load(this, R.raw.voip_busy, 1); - - am.registerMediaButtonEventReceiver(new ComponentName(this, VoIPMediaButtonReceiver.class)); - - if(btAdapter!=null && btAdapter.isEnabled()){ - int headsetState=btAdapter.getProfileConnectionState(BluetoothProfile.HEADSET); - updateBluetoothHeadsetState(headsetState==BluetoothProfile.STATE_CONNECTED); - if(headsetState==BluetoothProfile.STATE_CONNECTED) - am.setBluetoothScoOn(true); - for (StateListener l : stateListeners) - l.onAudioSettingsChanged(); - } - - NotificationCenter.getInstance().addObserver(this, NotificationCenter.appDidLogout); - } catch (Exception x) { - FileLog.e("error initializing voip controller", x); - callFailed(); - } } @Override - public void onDestroy() { - FileLog.d("=============== VoIPService STOPPING ==============="); - stopForeground(true); - stopRinging(); - NotificationCenter.getInstance().removeObserver(this, NotificationCenter.appDidLogout); - SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE); - Sensor proximity = sm.getDefaultSensor(Sensor.TYPE_PROXIMITY); - if (proximity != null) { - sm.unregisterListener(this); + protected void onControllerPreRelease(){ + if(needSendDebugLog){ + String debugLog=controller.getDebugLog(); + TLRPC.TL_phone_saveCallDebug req=new TLRPC.TL_phone_saveCallDebug(); + req.debug=new TLRPC.TL_dataJSON(); + req.debug.data=debugLog; + req.peer=new TLRPC.TL_inputPhoneCall(); + req.peer.access_hash=call.access_hash; + req.peer.id=call.id; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate(){ + @Override + public void run(TLObject response, TLRPC.TL_error error){ + FileLog.d("Sent debug logs, response="+response); + } + }); } - if (proximityWakelock != null && proximityWakelock.isHeld()) { - proximityWakelock.release(); - } - unregisterReceiver(receiver); - if(timeoutRunnable!=null){ - AndroidUtilities.cancelRunOnUIThread(timeoutRunnable); - timeoutRunnable=null; - } - super.onDestroy(); - sharedInstance = null; - AndroidUtilities.runOnUIThread(new Runnable(){ - @Override - public void run(){ - NotificationCenter.getInstance().postNotificationName(NotificationCenter.didEndedCall); - } - }); - if (controller != null && controllerStarted) { - lastKnownDuration = controller.getCallDuration(); - updateStats(); - StatsController.getInstance().incrementTotalCallsTime(getStatsNetworkType(), (int) (lastKnownDuration / 1000) % 5); - if(needSendDebugLog){ - String debugLog=controller.getDebugLog(); - TLRPC.TL_phone_saveCallDebug req=new TLRPC.TL_phone_saveCallDebug(); - req.debug=new TLRPC.TL_dataJSON(); - req.debug.data=debugLog; - req.peer=new TLRPC.TL_inputPhoneCall(); - req.peer.access_hash=call.access_hash; - req.peer.id=call.id; - ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate(){ - @Override - public void run(TLObject response, TLRPC.TL_error error){ - FileLog.d("Sent debug logs, response="+response); - } - }); - } - controller.release(); - controller = null; - } - cpuWakelock.release(); - AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE); - if(isBtHeadsetConnected && !playingSound){ - am.stopBluetoothSco(); - am.setSpeakerphoneOn(false); - } - try{ - am.setMode(AudioManager.MODE_NORMAL); - }catch(SecurityException x){ - FileLog.e("Error setting audio more to normal", x); - } - am.unregisterMediaButtonEventReceiver(new ComponentName(this, VoIPMediaButtonReceiver.class)); - if (haveAudioFocus) - am.abandonAudioFocus(this); - - if (!playingSound) - soundPool.release(); - - ConnectionsManager.getInstance().setAppPaused(true, false); } public static VoIPService getSharedInstance() { - return sharedInstance; + return sharedInstance instanceof VoIPService ? ((VoIPService)sharedInstance) : null; } public TLRPC.User getUser() { return user; } - public void setMicMute(boolean mute) { - controller.setMicMute(micMute = mute); - } - - public boolean isMicMute() { - return micMute; - } - - public String getDebugString() { - return controller.getDebugString(); - } - - public long getCallDuration() { - if (!controllerStarted || controller == null) - return lastKnownDuration; - return lastKnownDuration = controller.getCallDuration(); - } - public void hangUp() { declineIncomingCall(currentState == STATE_RINGING || (currentState==STATE_WAITING && isOutgoing) ? DISCARD_REASON_MISSED : DISCARD_REASON_HANGUP, null); } @@ -449,16 +196,6 @@ public class VoIPService extends Service implements VoIPController.ConnectionSta declineIncomingCall(currentState == STATE_RINGING || (currentState==STATE_WAITING && isOutgoing) ? DISCARD_REASON_MISSED : DISCARD_REASON_HANGUP, onDone); } - public void registerStateListener(StateListener l) { - stateListeners.add(l); - if (currentState != 0) - l.onStateChanged(currentState); - } - - public void unregisterStateListener(StateListener l) { - stateListeners.remove(l); - } - private void startOutgoingCall() { configureDeviceForCall(); showNotification(); @@ -778,8 +515,13 @@ public class VoIPService extends Service implements VoIPController.ConnectionSta public void declineIncomingCall(int reason, final Runnable onDone) { if(currentState==STATE_REQUESTING){ - dispatchStateChanged(STATE_HANGING_UP); - endCallAfterRequest=true; + if(delayedStartOutgoingCall!=null){ + AndroidUtilities.cancelRunOnUIThread(delayedStartOutgoingCall); + callEnded(); + }else{ + dispatchStateChanged(STATE_HANGING_UP); + endCallAfterRequest=true; + } return; } if (currentState == STATE_HANGING_UP || currentState == STATE_ENDED) @@ -902,7 +644,7 @@ public class VoIPService extends Service implements VoIPController.ConnectionSta } else { callEnded(); } - if (call.need_rating) { + if (call.need_rating || forceRating) { startRatingActivity(); } } else if (call instanceof TLRPC.TL_phoneCall && authKey == null){ @@ -989,28 +731,11 @@ public class VoIPService extends Service implements VoIPController.ConnectionSta } } - public void stopRinging() { - if (ringtonePlayer != null) { - ringtonePlayer.stop(); - ringtonePlayer.release(); - ringtonePlayer = null; - } - if (vibrator != null) { - vibrator.cancel(); - vibrator = null; - } - } - public byte[] getEncryptionKey() { return authKey; } private void processAcceptedCall() { - if(!isProximityNear){ - Vibrator vibrator=(Vibrator) getSystemService(VIBRATOR_SERVICE); - if(vibrator.hasVibrator()) - vibrator.vibrate(100); - } dispatchStateChanged(STATE_EXCHANGING_KEYS); BigInteger p = new BigInteger(1, MessagesStorage.secretPBytes); @@ -1115,7 +840,20 @@ public class VoIPService extends Service implements VoIPController.ConnectionSta for (int i = 0; i < call.alternative_connections.size(); i++) endpoints[i + 1] = call.alternative_connections.get(i); - controller.setRemoteEndpoints(endpoints, call.protocol.udp_p2p && getSharedPreferences("mainconfig", MODE_PRIVATE).getBoolean("calls_p2p", true)); + VoIPHelper.upgradeP2pSetting(); + boolean allowP2p=true; + switch(getSharedPreferences("mainconfig", MODE_PRIVATE).getInt("calls_p2p_new", MessagesController.getInstance().defaultP2pContacts ? 1 : 0)){ + case 0: + allowP2p=true; + break; + case 2: + allowP2p=false; + break; + case 1: + allowP2p=ContactsController.getInstance().contactsDict.get(user.id)!=null; + break; + } + controller.setRemoteEndpoints(endpoints, call.protocol.udp_p2p && allowP2p); SharedPreferences prefs=ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); if(prefs.getBoolean("proxy_enabled", false) && prefs.getBoolean("proxy_enabled_calls", false)){ String server=prefs.getString("proxy_ip", null); @@ -1142,50 +880,8 @@ public class VoIPService extends Service implements VoIPController.ConnectionSta } } - private void showNotification() { - Intent intent = new Intent(this, VoIPActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); - Notification.Builder builder = new Notification.Builder(this) - .setContentTitle(LocaleController.getString("VoipOutgoingCall", R.string.VoipOutgoingCall)) - .setContentText(ContactsController.formatName(user.first_name, user.last_name)) - .setSmallIcon(R.drawable.notification) - .setContentIntent(PendingIntent.getActivity(this, 0, intent, 0)); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - Intent endIntent = new Intent(); - endIntent.setAction(getPackageName() + ".END_CALL"); - endIntent.putExtra("end_hash", endHash = Utilities.random.nextInt()); - builder.addAction(R.drawable.ic_call_end_white_24dp, LocaleController.getString("VoipEndCall", R.string.VoipEndCall), PendingIntent.getBroadcast(this, 0, endIntent, PendingIntent.FLAG_UPDATE_CURRENT)); - builder.setPriority(Notification.PRIORITY_MAX); - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - builder.setShowWhen(false); - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - builder.setColor(0xff2ca5e0); - } - if (user.photo != null) { - TLRPC.FileLocation photoPath = user.photo.photo_small; - if (photoPath != null) { - BitmapDrawable img = ImageLoader.getInstance().getImageFromMemory(photoPath, null, "50_50"); - if (img != null) { - builder.setLargeIcon(img.getBitmap()); - } else { - try { - float scaleFactor = 160.0f / AndroidUtilities.dp(50); - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inSampleSize = scaleFactor < 1 ? 1 : (int) scaleFactor; - Bitmap bitmap = BitmapFactory.decodeFile(FileLoader.getPathToAttach(photoPath, true).toString(), options); - if (bitmap != null) { - builder.setLargeIcon(bitmap); - } - } catch (Throwable e) { - FileLog.e(e); - } - } - } - } - ongoingCallNotification = builder.getNotification(); - startForeground(ID_ONGOING_CALL_NOTIFICATION, ongoingCallNotification); + protected void showNotification(){ + showNotification(ContactsController.formatName(user.first_name, user.last_name), user.photo!=null ? user.photo.photo_small : null, VoIPActivity.class); } private void showIncomingNotification() { @@ -1197,19 +893,18 @@ public class VoIPService extends Service implements VoIPController.ConnectionSta .setSmallIcon(R.drawable.notification) .setContentIntent(PendingIntent.getActivity(this, 0, intent, 0)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - endHash = Utilities.random.nextInt(); - Intent endIntent = new Intent(); + Intent endIntent = new Intent(this, VoIPActionsReceiver.class); endIntent.setAction(getPackageName() + ".DECLINE_CALL"); - endIntent.putExtra("end_hash", endHash); + endIntent.putExtra("call_id", getCallID()); CharSequence endTitle=LocaleController.getString("VoipDeclineCall", R.string.VoipDeclineCall); if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){ endTitle=new SpannableString(endTitle); ((SpannableString)endTitle).setSpan(new ForegroundColorSpan(0xFFF44336), 0, endTitle.length(), 0); } builder.addAction(R.drawable.ic_call_end_white_24dp, endTitle, PendingIntent.getBroadcast(this, 0, endIntent, PendingIntent.FLAG_UPDATE_CURRENT)); - Intent answerIntent = new Intent(); + Intent answerIntent = new Intent(this, VoIPActionsReceiver.class); answerIntent.setAction(getPackageName() + ".ANSWER_CALL"); - answerIntent.putExtra("end_hash", endHash); + answerIntent.putExtra("call_id", getCallID()); CharSequence answerTitle=LocaleController.getString("VoipAnswerCall", R.string.VoipAnswerCall); if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){ answerTitle=new SpannableString(answerTitle); @@ -1271,17 +966,7 @@ public class VoIPService extends Service implements VoIPController.ConnectionSta } } - private void callFailed() { - callFailed(controller != null && controllerStarted ? controller.getLastError() : VoIPController.ERROR_UNKNOWN); - } - - private void callFailed(int errorCode) { - try{ - throw new Exception("Call "+(call!=null ? call.id : 0)+" failed with error code "+errorCode); - }catch(Exception x){ - FileLog.e(x); - } - lastError = errorCode; + protected void callFailed(int errorCode) { if (call != null) { FileLog.d("Discarding failed call"); TLRPC.TL_phone_discardCall req = new TLRPC.TL_phone_discardCall(); @@ -1302,206 +987,12 @@ public class VoIPService extends Service implements VoIPController.ConnectionSta } }); } - dispatchStateChanged(STATE_FAILED); - if(errorCode!=VoIPController.ERROR_LOCALIZED && soundPool!=null){ - playingSound=true; - soundPool.play(spFailedID, 1, 1, 0, 0, 1); - AndroidUtilities.runOnUIThread(afterSoundRunnable, 1000); - } - stopSelf(); - } - - private void callEnded() { - FileLog.d("Call "+(call!=null ? call.id : 0)+" ended"); - dispatchStateChanged(STATE_ENDED); - if (needPlayEndSound) { - playingSound = true; - soundPool.play(spEndId, 1, 1, 0, 0, 1); - AndroidUtilities.runOnUIThread(afterSoundRunnable, 700); - } - if(timeoutRunnable!=null){ - AndroidUtilities.cancelRunOnUIThread(timeoutRunnable); - timeoutRunnable=null; - } - stopSelf(); + super.callFailed(errorCode); } @Override - public void onConnectionStateChanged(int newState) { - if (newState == STATE_FAILED) { - callFailed(); - return; - } - if (newState == STATE_ESTABLISHED) { - if (spPlayID != 0) { - soundPool.stop(spPlayID); - spPlayID = 0; - } - if(!wasEstablished){ - wasEstablished=true; - AndroidUtilities.runOnUIThread(new Runnable(){ - @Override - public void run(){ - if(controller==null) - return; - int netType=StatsController.TYPE_WIFI; - if(lastNetInfo!=null){ - if(lastNetInfo.getType()==ConnectivityManager.TYPE_MOBILE) - netType=lastNetInfo.isRoaming() ? StatsController.TYPE_ROAMING : StatsController.TYPE_MOBILE; - } - StatsController.getInstance().incrementTotalCallsTime(netType, 5); - AndroidUtilities.runOnUIThread(this, 5000); - } - }, 5000); - if(isOutgoing) - StatsController.getInstance().incrementSentItemsCount(getStatsNetworkType(), StatsController.TYPE_CALLS, 1); - else - StatsController.getInstance().incrementReceivedItemsCount(getStatsNetworkType(), StatsController.TYPE_CALLS, 1); - } - } - if(newState==STATE_RECONNECTING){ - if(spPlayID!=0) - soundPool.stop(spPlayID); - spPlayID=soundPool.play(spConnectingId, 1, 1, 0, -1, 1); - } - dispatchStateChanged(newState); - } - - public boolean isOutgoing(){ - return isOutgoing; - } - - @SuppressLint("NewApi") - @Override - public void onSensorChanged(SensorEvent event) { - if (event.sensor.getType() == Sensor.TYPE_PROXIMITY) { - AudioManager am=(AudioManager) getSystemService(AUDIO_SERVICE); - if (isHeadsetPlugged || am.isSpeakerphoneOn() || (isBluetoothHeadsetConnected() && am.isBluetoothScoOn())) { - return; - } - boolean newIsNear = event.values[0] < Math.min(event.sensor.getMaximumRange(), 3); - if (newIsNear != isProximityNear) { - FileLog.d("proximity " + newIsNear); - isProximityNear = newIsNear; - try{ - if(isProximityNear){ - proximityWakelock.acquire(); - }else{ - proximityWakelock.release(1); // this is non-public API before L - } - }catch(Exception x){ - FileLog.e(x); - } - } - } - } - - @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) { - - } - - private void updateNetworkType() { - ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); - NetworkInfo info = cm.getActiveNetworkInfo(); - lastNetInfo = info; - int type = VoIPController.NET_TYPE_UNKNOWN; - if (info != null) { - switch (info.getType()) { - case ConnectivityManager.TYPE_MOBILE: - switch (info.getSubtype()) { - case TelephonyManager.NETWORK_TYPE_GPRS: - type = VoIPController.NET_TYPE_GPRS; - break; - case TelephonyManager.NETWORK_TYPE_EDGE: - case TelephonyManager.NETWORK_TYPE_1xRTT: - type = VoIPController.NET_TYPE_EDGE; - break; - case TelephonyManager.NETWORK_TYPE_UMTS: - case TelephonyManager.NETWORK_TYPE_EVDO_0: - type = VoIPController.NET_TYPE_3G; - break; - case TelephonyManager.NETWORK_TYPE_HSDPA: - case TelephonyManager.NETWORK_TYPE_HSPA: - case TelephonyManager.NETWORK_TYPE_HSPAP: - case TelephonyManager.NETWORK_TYPE_HSUPA: - case TelephonyManager.NETWORK_TYPE_EVDO_A: - case TelephonyManager.NETWORK_TYPE_EVDO_B: - type = VoIPController.NET_TYPE_HSPA; - break; - case TelephonyManager.NETWORK_TYPE_LTE: - type = VoIPController.NET_TYPE_LTE; - break; - default: - type = VoIPController.NET_TYPE_OTHER_MOBILE; - break; - } - break; - case ConnectivityManager.TYPE_WIFI: - type = VoIPController.NET_TYPE_WIFI; - break; - case ConnectivityManager.TYPE_ETHERNET: - type = VoIPController.NET_TYPE_ETHERNET; - break; - } - } - if (controller != null) { - controller.setNetworkType(type); - } - } - - private void updateBluetoothHeadsetState(boolean connected){ - if(connected==isBtHeadsetConnected) - return; - isBtHeadsetConnected=connected; - AudioManager am=(AudioManager)getSystemService(AUDIO_SERVICE); - if(connected) - am.startBluetoothSco(); - else - am.stopBluetoothSco(); - for (StateListener l : stateListeners) - l.onAudioSettingsChanged(); - } - - public boolean isBluetoothHeadsetConnected(){ - return isBtHeadsetConnected; - } - - private void configureDeviceForCall() { - needPlayEndSound = true; - AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE); - am.setMode(AudioManager.MODE_IN_COMMUNICATION); - am.setSpeakerphoneOn(false); - am.requestAudioFocus(this, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN); - - SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE); - Sensor proximity = sm.getDefaultSensor(Sensor.TYPE_PROXIMITY); - try{ - if(proximity!=null){ - proximityWakelock=((PowerManager) getSystemService(Context.POWER_SERVICE)).newWakeLock(PROXIMITY_SCREEN_OFF_WAKE_LOCK, "telegram-voip-prx"); - sm.registerListener(this, proximity, SensorManager.SENSOR_DELAY_NORMAL); - } - }catch(Exception x){ - FileLog.e("Error initializing proximity sensor", x); - } - } - - private void dispatchStateChanged(int state) { - FileLog.d("== Call "+(call!=null ? call.id : 0)+" state changed to "+state+" =="); - currentState = state; - for (int a = 0; a < stateListeners.size(); a++) { - StateListener l = stateListeners.get(a); - l.onStateChanged(state); - } - } - - @Override - public void onAudioFocusChange(int focusChange) { - if (focusChange == AudioManager.AUDIOFOCUS_GAIN) { - haveAudioFocus = true; - } else { - haveAudioFocus = false; - } + protected long getCallID(){ + return call!=null ? call.id : 0; } public void onUIForegroundStateChanged(boolean isForeground) { @@ -1548,73 +1039,6 @@ public class VoIPService extends Service implements VoIPController.ConnectionSta controller.debugCtl(request, param); } - public int getLastError() { - return lastError; - } - - private void updateStats() { - controller.getStats(stats); - long wifiSentDiff = stats.bytesSentWifi - prevStats.bytesSentWifi; - long wifiRecvdDiff = stats.bytesRecvdWifi - prevStats.bytesRecvdWifi; - long mobileSentDiff = stats.bytesSentMobile - prevStats.bytesSentMobile; - long mobileRecvdDiff = stats.bytesRecvdMobile - prevStats.bytesRecvdMobile; - VoIPController.Stats tmp = stats; - stats = prevStats; - prevStats = tmp; - if (wifiSentDiff > 0) - StatsController.getInstance().incrementSentBytesCount(StatsController.TYPE_WIFI, StatsController.TYPE_CALLS, wifiSentDiff); - if (wifiRecvdDiff > 0) - StatsController.getInstance().incrementReceivedBytesCount(StatsController.TYPE_WIFI, StatsController.TYPE_CALLS, wifiRecvdDiff); - if (mobileSentDiff > 0) - StatsController.getInstance().incrementSentBytesCount(lastNetInfo != null && lastNetInfo.isRoaming() ? StatsController.TYPE_ROAMING : StatsController.TYPE_MOBILE, - StatsController.TYPE_CALLS, mobileSentDiff); - if (mobileRecvdDiff > 0) - StatsController.getInstance().incrementReceivedBytesCount(lastNetInfo != null && lastNetInfo.isRoaming() ? StatsController.TYPE_ROAMING : StatsController.TYPE_MOBILE, - StatsController.TYPE_CALLS, mobileRecvdDiff); - } - - private int getStatsNetworkType() { - int netType = StatsController.TYPE_WIFI; - if (lastNetInfo != null) { - if (lastNetInfo.getType() == ConnectivityManager.TYPE_MOBILE) - netType = lastNetInfo.isRoaming() ? StatsController.TYPE_ROAMING : StatsController.TYPE_MOBILE; - } - return netType; - } - - public boolean hasEarpiece() { - if(((TelephonyManager)getSystemService(TELEPHONY_SERVICE)).getPhoneType()!=TelephonyManager.PHONE_TYPE_NONE) - return true; - if (mHasEarpiece != null) { - return mHasEarpiece; - } - - // not calculated yet, do it now - try { - AudioManager am=(AudioManager)getSystemService(AUDIO_SERVICE); - Method method = AudioManager.class.getMethod("getDevicesForStream", Integer.TYPE); - Field field = AudioManager.class.getField("DEVICE_OUT_EARPIECE"); - int earpieceFlag = field.getInt(null); - int bitmaskResult = (int) method.invoke(am, AudioManager.STREAM_VOICE_CALL); - - // check if masked by the earpiece flag - if ((bitmaskResult & earpieceFlag) == earpieceFlag) { - mHasEarpiece = Boolean.TRUE; - } else { - mHasEarpiece = Boolean.FALSE; - } - } catch (Throwable error) { - FileLog.e("Error while checking earpiece! ", error); - mHasEarpiece = Boolean.TRUE; - } - - return mHasEarpiece; - } - - public int getCallState(){ - return currentState; - } - public byte[] getGA(){ return g_a; } @@ -1626,9 +1050,8 @@ public class VoIPService extends Service implements VoIPController.ConnectionSta } } - public interface StateListener { - void onStateChanged(int state); - - void onAudioSettingsChanged(); + public void forceRating(){ + forceRating=true; } + } diff --git a/TMessagesProj/src/main/java/org/telegram/tgnet/ConnectionsManager.java b/TMessagesProj/src/main/java/org/telegram/tgnet/ConnectionsManager.java index a51ca804e..f87952ca3 100644 --- a/TMessagesProj/src/main/java/org/telegram/tgnet/ConnectionsManager.java +++ b/TMessagesProj/src/main/java/org/telegram/tgnet/ConnectionsManager.java @@ -7,6 +7,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; +import android.content.pm.PackageInfo; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.AsyncTask; @@ -22,6 +23,7 @@ import org.telegram.messenger.BuildVars; import org.telegram.messenger.ContactsController; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.FileLog; +import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesController; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.StatsController; @@ -106,6 +108,42 @@ public class ConnectionsManager { } public ConnectionsManager() { + String deviceModel; + String systemLangCode; + String langCode; + String appVersion; + String systemVersion; + String configPath = ApplicationLoader.getFilesDirFixed().toString(); + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + boolean enablePushConnection = preferences.getBoolean("pushConnection", true); + try { + systemLangCode = LocaleController.getSystemLocaleStringIso639(); + langCode = LocaleController.getLocaleStringIso639(); + deviceModel = Build.MANUFACTURER + Build.MODEL; + PackageInfo pInfo = ApplicationLoader.applicationContext.getPackageManager().getPackageInfo(ApplicationLoader.applicationContext.getPackageName(), 0); + appVersion = pInfo.versionName + " (" + pInfo.versionCode + ")"; + systemVersion = "SDK " + Build.VERSION.SDK_INT; + } catch (Exception e) { + systemLangCode = "en"; + langCode = ""; + deviceModel = "Android unknown"; + appVersion = "App version unknown"; + systemVersion = "SDK " + Build.VERSION.SDK_INT; + } + if (systemLangCode.trim().length() == 0) { + langCode = "en"; + } + if (deviceModel.trim().length() == 0) { + deviceModel = "Android unknown"; + } + if (appVersion.trim().length() == 0) { + appVersion = "App version unknown"; + } + if (systemVersion.trim().length() == 0) { + systemVersion = "SDK Unknown"; + } + UserConfig.loadConfig(); + init(BuildVars.BUILD_VERSION, TLRPC.LAYER, BuildVars.APP_ID, deviceModel, systemVersion, appVersion, langCode, systemLangCode, configPath, FileLog.getNetworkLogPath(), UserConfig.getClientUserId(), enablePushConnection); try { PowerManager pm = (PowerManager) ApplicationLoader.applicationContext.getSystemService(Context.POWER_SERVICE); wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "lock"); @@ -265,6 +303,8 @@ public class ConnectionsManager { } public void switchBackend() { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + preferences.edit().remove("language_showed2").commit(); native_switchBackend(); } @@ -399,7 +439,7 @@ public class ConnectionsManager { } public static void onRequestNewServerIpAndPort(int second) { - if (currentTask != null || second != 1 && Math.abs(lastDnsRequestTime - System.currentTimeMillis()) < 10000) { + if (currentTask != null || second != 1 && Math.abs(lastDnsRequestTime - System.currentTimeMillis()) < 10000 || !isNetworkOnline()) { return; } lastDnsRequestTime = System.currentTimeMillis(); @@ -525,10 +565,6 @@ public class ConnectionsManager { return false; } - public void applyCountryPortNumber(String number) { - - } - public void setIsUpdating(final boolean value) { AndroidUtilities.runOnUIThread(new Runnable() { @Override diff --git a/TMessagesProj/src/main/java/org/telegram/tgnet/NativeByteBuffer.java b/TMessagesProj/src/main/java/org/telegram/tgnet/NativeByteBuffer.java index 315f3f355..62b67e4c9 100644 --- a/TMessagesProj/src/main/java/org/telegram/tgnet/NativeByteBuffer.java +++ b/TMessagesProj/src/main/java/org/telegram/tgnet/NativeByteBuffer.java @@ -108,7 +108,7 @@ public class NativeByteBuffer extends AbstractSerializedData { } else { len += 4; } - } catch(Exception e) { + } catch (Exception e) { FileLog.e("write int32 error"); } } @@ -120,7 +120,7 @@ public class NativeByteBuffer extends AbstractSerializedData { } else { len += 8; } - } catch(Exception e) { + } catch (Exception e) { FileLog.e("write int64 error"); } } @@ -180,25 +180,25 @@ public class NativeByteBuffer extends AbstractSerializedData { public void writeString(String s) { try { writeByteArray(s.getBytes("UTF-8")); - } catch(Exception e) { + } catch (Exception e) { FileLog.e("write string error"); } } public void writeByteArray(byte[] b, int offset, int count) { try { - if(count <= 253) { + if (count <= 253) { if (!justCalc) { - buffer.put((byte)count); + buffer.put((byte) count); } else { len += 1; } } else { if (!justCalc) { - buffer.put((byte)254); - buffer.put((byte)count); - buffer.put((byte)(count >> 8)); - buffer.put((byte)(count >> 16)); + buffer.put((byte) 254); + buffer.put((byte) count); + buffer.put((byte) (count >> 8)); + buffer.put((byte) (count >> 16)); } else { len += 4; } @@ -211,7 +211,7 @@ public class NativeByteBuffer extends AbstractSerializedData { int i = count <= 253 ? 1 : 4; while ((count + i) % 4 != 0) { if (!justCalc) { - buffer.put((byte)0); + buffer.put((byte) 0); } else { len += 1; } @@ -246,7 +246,7 @@ public class NativeByteBuffer extends AbstractSerializedData { len += b.length; } int i = b.length <= 253 ? 1 : 4; - while((b.length + i) % 4 != 0) { + while ((b.length + i) % 4 != 0) { if (!justCalc) { buffer.put((byte) 0); } else { @@ -262,7 +262,7 @@ public class NativeByteBuffer extends AbstractSerializedData { public void writeDouble(double d) { try { writeInt64(Double.doubleToRawLongBits(d)); - } catch(Exception e) { + } catch (Exception e) { FileLog.e("write double error"); } } @@ -293,7 +293,7 @@ public class NativeByteBuffer extends AbstractSerializedData { len += l; } int i = l <= 253 ? 1 : 4; - while((l + i) % 4 != 0) { + while ((l + i) % 4 != 0) { if (!justCalc) { buffer.put((byte) 0); } else { @@ -316,7 +316,7 @@ public class NativeByteBuffer extends AbstractSerializedData { } public int getIntFromByte(byte b) { - return b >= 0 ? b : ((int)b) + 256; + return b >= 0 ? b : ((int) b) + 256; } public int length() { @@ -401,17 +401,18 @@ public class NativeByteBuffer extends AbstractSerializedData { } public String readString(boolean exception) { + int startReadPosition = getPosition(); try { int sl = 1; int l = getIntFromByte(buffer.get()); - if(l >= 254) { + if (l >= 254) { l = getIntFromByte(buffer.get()) | (getIntFromByte(buffer.get()) << 8) | (getIntFromByte(buffer.get()) << 16); sl = 4; } byte[] b = new byte[l]; buffer.get(b); int i = sl; - while((l + i) % 4 != 0) { + while ((l + i) % 4 != 0) { buffer.get(); i++; } @@ -422,6 +423,7 @@ public class NativeByteBuffer extends AbstractSerializedData { } else { FileLog.e("read string error"); } + position(startReadPosition); } return ""; } @@ -437,7 +439,7 @@ public class NativeByteBuffer extends AbstractSerializedData { byte[] b = new byte[l]; buffer.get(b); int i = sl; - while((l + i) % 4 != 0) { + while ((l + i) % 4 != 0) { buffer.get(); i++; } @@ -467,7 +469,7 @@ public class NativeByteBuffer extends AbstractSerializedData { buffer.limit(old); b.buffer.position(0); int i = sl; - while((l + i) % 4 != 0) { + while ((l + i) % 4 != 0) { buffer.get(); i++; } @@ -485,7 +487,7 @@ public class NativeByteBuffer extends AbstractSerializedData { public double readDouble(boolean exception) { try { return Double.longBitsToDouble(readInt64(exception)); - } catch(Exception e) { + } catch (Exception e) { if (exception) { throw new RuntimeException("read double error", e); } else { diff --git a/TMessagesProj/src/main/java/org/telegram/tgnet/TLClassStore.java b/TMessagesProj/src/main/java/org/telegram/tgnet/TLClassStore.java index 81b56fb3e..ca4c899f9 100644 --- a/TMessagesProj/src/main/java/org/telegram/tgnet/TLClassStore.java +++ b/TMessagesProj/src/main/java/org/telegram/tgnet/TLClassStore.java @@ -24,9 +24,11 @@ public class TLClassStore { classStore.put(TLRPC.TL_config.constructor, TLRPC.TL_config.class); classStore.put(TLRPC.TL_decryptedMessageLayer.constructor, TLRPC.TL_decryptedMessageLayer.class); classStore.put(TLRPC.TL_decryptedMessage_layer17.constructor, TLRPC.TL_decryptedMessage.class); + classStore.put(TLRPC.TL_decryptedMessage_layer45.constructor, TLRPC.TL_decryptedMessage_layer45.class); classStore.put(TLRPC.TL_decryptedMessageService_layer8.constructor, TLRPC.TL_decryptedMessageService_layer8.class); classStore.put(TLRPC.TL_decryptedMessage_layer8.constructor, TLRPC.TL_decryptedMessage_layer8.class); classStore.put(TLRPC.TL_message_secret.constructor, TLRPC.TL_message_secret.class); + classStore.put(TLRPC.TL_message_secret_layer72.constructor, TLRPC.TL_message_secret_layer72.class); classStore.put(TLRPC.TL_message_secret_old.constructor, TLRPC.TL_message_secret_old.class); classStore.put(TLRPC.TL_messageEncryptedAction.constructor, TLRPC.TL_messageEncryptedAction.class); classStore.put(TLRPC.TL_null.constructor, TLRPC.TL_null.class); diff --git a/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java b/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java index b3188aa83..cbe63d2ae 100644 --- a/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java +++ b/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java @@ -57,9 +57,9 @@ public class TLRPC { public static final int MESSAGE_FLAG_EDITED = 0x00008000; public static final int MESSAGE_FLAG_MEGAGROUP = 0x80000000; - public static final int LAYER = 70; + public static final int LAYER = 73; - public static class DraftMessage extends TLObject { + public static abstract class DraftMessage extends TLObject { public int flags; public boolean no_webpage; public int reply_to_msg_id; @@ -69,7 +69,7 @@ public class TLRPC { public static DraftMessage TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { DraftMessage result = null; - switch(constructor) { + switch (constructor) { case 0xba4baec5: result = new TL_draftMessageEmpty(); break; @@ -147,13 +147,13 @@ public class TLRPC { } } - public static class ChatPhoto extends TLObject { + public static abstract class ChatPhoto extends TLObject { public FileLocation photo_small; public FileLocation photo_big; public static ChatPhoto TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { ChatPhoto result = null; - switch(constructor) { + switch (constructor) { case 0x37c1011c: result = new TL_chatPhotoEmpty(); break; @@ -309,12 +309,12 @@ public class TLRPC { } } - public static class NotifyPeer extends TLObject { + public static abstract class NotifyPeer extends TLObject { public Peer peer; public static NotifyPeer TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { NotifyPeer result = null; - switch(constructor) { + switch (constructor) { case 0x74d07c60: result = new TL_notifyAll(); break; @@ -379,13 +379,13 @@ public class TLRPC { } } - public static class messages_SentEncryptedMessage extends TLObject { + public static abstract class messages_SentEncryptedMessage extends TLObject { public int date; public EncryptedFile file; public static messages_SentEncryptedMessage TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { messages_SentEncryptedMessage result = null; - switch(constructor) { + switch (constructor) { case 0x560f8935: result = new TL_messages_sentEncryptedMessage(); break; @@ -592,7 +592,7 @@ public class TLRPC { } } - public static class DocumentAttribute extends TLObject { + public static abstract class DocumentAttribute extends TLObject { public String alt; public InputStickerSet stickerset; public int duration; @@ -610,7 +610,7 @@ public class TLRPC { public static DocumentAttribute TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { DocumentAttribute result = null; - switch(constructor) { + switch (constructor) { case 0x3a556302: result = new TL_documentAttributeSticker_layer55(); break; @@ -1084,12 +1084,13 @@ public class TLRPC { } } - public static class TL_messages_botCallbackAnswer extends TLObject { + public static class TL_messages_botCallbackAnswer extends TLObject { public static int constructor = 0x36585ea4; public int flags; public boolean alert; public boolean has_url; + public boolean native_ui; public String message; public String url; public int cache_time; @@ -1111,6 +1112,7 @@ public class TLRPC { flags = stream.readInt32(exception); alert = (flags & 2) != 0; has_url = (flags & 8) != 0; + native_ui = (flags & 16) != 0; if ((flags & 1) != 0) { message = stream.readString(exception); } @@ -1124,6 +1126,7 @@ public class TLRPC { stream.writeInt32(constructor); flags = alert ? (flags | 2) : (flags &~ 2); flags = has_url ? (flags | 8) : (flags &~ 8); + flags = native_ui ? (flags | 16) : (flags &~ 16); stream.writeInt32(flags); if ((flags & 1) != 0) { stream.writeString(message); @@ -1194,6 +1197,135 @@ public class TLRPC { } } + public static abstract class GroupCall extends TLObject { + public long id; + public long access_hash; + public int duration; + public int flags; + public int channel_id; + public int admin_id; + public byte[] encryption_key; + public long key_fingerprint; + public TL_phoneCallProtocol protocol; + public TL_groupCallConnection connection; + public byte[] reflector_group_tag; + public byte[] reflector_self_tag; + public byte[] reflector_self_secret; + public int participants_count; + + public static GroupCall TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + GroupCall result = null; + switch (constructor) { + case 0x7780bcb4: + result = new TL_groupCallDiscarded(); + break; + case 0xa8f1624: + result = new TL_groupCall(); + break; + case 0x6d0b1604: + result = new TL_groupCallPrivate(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in GroupCall", constructor)); + } + if (result != null) { + result.readParams(stream, exception); + } + return result; + } + } + + public static class TL_groupCallDiscarded extends GroupCall { + public static int constructor = 0x7780bcb4; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + id = stream.readInt64(exception); + access_hash = stream.readInt64(exception); + duration = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt64(id); + stream.writeInt64(access_hash); + stream.writeInt32(duration); + } + } + + public static class TL_groupCall extends GroupCall { + public static int constructor = 0xa8f1624; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + id = stream.readInt64(exception); + access_hash = stream.readInt64(exception); + if ((flags & 1) != 0) { + channel_id = stream.readInt32(exception); + } + admin_id = stream.readInt32(exception); + if ((flags & 2) != 0) { + encryption_key = stream.readByteArray(exception); + } + key_fingerprint = stream.readInt64(exception); + protocol = TL_phoneCallProtocol.TLdeserialize(stream, stream.readInt32(exception), exception); + connection = TL_groupCallConnection.TLdeserialize(stream, stream.readInt32(exception), exception); + reflector_group_tag = stream.readByteArray(exception); + reflector_self_tag = stream.readByteArray(exception); + reflector_self_secret = stream.readByteArray(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + stream.writeInt64(id); + stream.writeInt64(access_hash); + if ((flags & 1) != 0) { + stream.writeInt32(channel_id); + } + stream.writeInt32(admin_id); + if ((flags & 2) != 0) { + stream.writeByteArray(encryption_key); + } + stream.writeInt64(key_fingerprint); + protocol.serializeToStream(stream); + connection.serializeToStream(stream); + stream.writeByteArray(reflector_group_tag); + stream.writeByteArray(reflector_self_tag); + stream.writeByteArray(reflector_self_secret); + } + } + + public static class TL_groupCallPrivate extends GroupCall { + public static int constructor = 0x6d0b1604; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + id = stream.readInt64(exception); + access_hash = stream.readInt64(exception); + if ((flags & 1) != 0) { + channel_id = stream.readInt32(exception); + } + participants_count = stream.readInt32(exception); + admin_id = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + stream.writeInt64(id); + stream.writeInt64(access_hash); + if ((flags & 1) != 0) { + stream.writeInt32(channel_id); + } + stream.writeInt32(participants_count); + stream.writeInt32(admin_id); + } + } + public static class TL_channelBannedRights extends TLObject { public static int constructor = 0x58cf4249; @@ -1287,7 +1419,7 @@ public class TLRPC { } } - public static class messages_Messages extends TLObject { + public static abstract class messages_Messages extends TLObject { public ArrayList messages = new ArrayList<>(); public ArrayList chats = new ArrayList<>(); public ArrayList users = new ArrayList<>(); @@ -1297,7 +1429,7 @@ public class TLRPC { public static messages_Messages TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { messages_Messages result = null; - switch(constructor) { + switch (constructor) { case 0x8c718e87: result = new TL_messages_messages(); break; @@ -1681,7 +1813,7 @@ public class TLRPC { } } - public static class EncryptedFile extends TLObject { + public static abstract class EncryptedFile extends TLObject { public long id; public long access_hash; public int size; @@ -1690,7 +1822,7 @@ public class TLRPC { public static EncryptedFile TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { EncryptedFile result = null; - switch(constructor) { + switch (constructor) { case 0x4a70994c: result = new TL_encryptedFile(); break; @@ -1739,14 +1871,14 @@ public class TLRPC { } } - public static class Peer extends TLObject { + public static abstract class Peer extends TLObject { public int channel_id; public int user_id; public int chat_id; public static Peer TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { Peer result = null; - switch(constructor) { + switch (constructor) { case 0xbddde532: result = new TL_peerChannel(); break; @@ -2041,7 +2173,7 @@ public class TLRPC { } } - public static class updates_Difference extends TLObject { + public static abstract class updates_Difference extends TLObject { public ArrayList new_messages = new ArrayList<>(); public ArrayList new_encrypted_messages = new ArrayList<>(); public ArrayList other_updates = new ArrayList<>(); @@ -2055,7 +2187,7 @@ public class TLRPC { public static updates_Difference TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { updates_Difference result = null; - switch(constructor) { + switch (constructor) { case 0xf49ca0: result = new TL_updates_difference(); break; @@ -2347,11 +2479,11 @@ public class TLRPC { } } - public static class PrivacyKey extends TLObject { + public static abstract class PrivacyKey extends TLObject { public static PrivacyKey TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { PrivacyKey result = null; - switch(constructor) { + switch (constructor) { case 0xbc2eab30: result = new TL_privacyKeyStatusTimestamp(); break; @@ -2399,13 +2531,13 @@ public class TLRPC { } } - public static class GeoPoint extends TLObject { + public static abstract class GeoPoint extends TLObject { public double _long; public double lat; public static GeoPoint TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { GeoPoint result = null; - switch(constructor) { + switch (constructor) { case 0x1117dd5f: result = new TL_geoPointEmpty(); break; @@ -2517,7 +2649,7 @@ public class TLRPC { } } - public static class ChatInvite extends TLObject { + public static abstract class ChatInvite extends TLObject { public int flags; public boolean channel; public boolean broadcast; @@ -2531,7 +2663,7 @@ public class TLRPC { public static ChatInvite TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { ChatInvite result = null; - switch(constructor) { + switch (constructor) { case 0xdb74f558: result = new TL_chatInvite(); break; @@ -2616,7 +2748,38 @@ public class TLRPC { } } - public static class help_AppUpdate extends TLObject { + public static class TL_inputGroupCall extends TLObject { + public static int constructor = 0xd8aa840f; + + public long id; + public long access_hash; + + public static TL_inputGroupCall TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_inputGroupCall.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_inputGroupCall", constructor)); + } else { + return null; + } + } + TL_inputGroupCall result = new TL_inputGroupCall(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + id = stream.readInt64(exception); + access_hash = stream.readInt64(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt64(id); + stream.writeInt64(access_hash); + } + } + + public static abstract class help_AppUpdate extends TLObject { public int id; public boolean critical; public String url; @@ -2624,7 +2787,7 @@ public class TLRPC { public static help_AppUpdate TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { help_AppUpdate result = null; - switch(constructor) { + switch (constructor) { case 0x8987f311: result = new TL_help_appUpdate(); break; @@ -2708,6 +2871,96 @@ public class TLRPC { } } + public static abstract class messages_FavedStickers extends TLObject { + public int hash; + public ArrayList packs = new ArrayList<>(); + public ArrayList stickers = new ArrayList<>(); + + public static messages_FavedStickers TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + messages_FavedStickers result = null; + switch (constructor) { + case 0x9e8fa6d3: + result = new TL_messages_favedStickersNotModified(); + break; + case 0xf37f2f16: + result = new TL_messages_favedStickers(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in messages_FavedStickers", constructor)); + } + if (result != null) { + result.readParams(stream, exception); + } + return result; + } + } + + public static class TL_messages_favedStickersNotModified extends messages_FavedStickers { + public static int constructor = 0x9e8fa6d3; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + public static class TL_messages_favedStickers extends messages_FavedStickers { + public static int constructor = 0xf37f2f16; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + hash = stream.readInt32(exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + TL_stickerPack object = TL_stickerPack.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + packs.add(object); + } + magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + Document object = Document.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + stickers.add(object); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(hash); + stream.writeInt32(0x1cb5c415); + int count = packs.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + packs.get(a).serializeToStream(stream); + } + stream.writeInt32(0x1cb5c415); + count = stickers.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + stickers.get(a).serializeToStream(stream); + } + } + } + public static class TL_langPackLanguage extends TLObject { public static int constructor = 0x117698f1; @@ -2742,12 +2995,12 @@ public class TLRPC { } } - public static class SendMessageAction extends TLObject { + public static abstract class SendMessageAction extends TLObject { public int progress; public static SendMessageAction TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { SendMessageAction result = null; - switch(constructor) { + switch (constructor) { case 0xdd6a8f48: result = new TL_sendMessageGamePlayAction(); break; @@ -3000,13 +3253,13 @@ public class TLRPC { } } - public static class auth_SentCodeType extends TLObject { + public static abstract class auth_SentCodeType extends TLObject { public int length; public String pattern; public static auth_SentCodeType TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { auth_SentCodeType result = null; - switch(constructor) { + switch (constructor) { case 0x3dbb5986: result = new TL_auth_sentCodeTypeApp(); break; @@ -3086,12 +3339,12 @@ public class TLRPC { } } - public static class messages_StickerSetInstallResult extends TLObject { + public static abstract class messages_StickerSetInstallResult extends TLObject { public ArrayList sets = new ArrayList<>(); public static messages_StickerSetInstallResult TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { messages_StickerSetInstallResult result = null; - switch(constructor) { + switch (constructor) { case 0x38641628: result = new TL_messages_stickerSetInstallResultSuccess(); break; @@ -3182,7 +3435,7 @@ public class TLRPC { } } - public static class FoundGif extends TLObject { + public static abstract class FoundGif extends TLObject { public String url; public Photo photo; public Document document; @@ -3194,7 +3447,7 @@ public class TLRPC { public static FoundGif TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { FoundGif result = null; - switch(constructor) { + switch (constructor) { case 0x9c750409: result = new TL_foundGifCached(); break; @@ -3254,13 +3507,13 @@ public class TLRPC { } } - public static class payments_PaymentResult extends TLObject { + public static abstract class payments_PaymentResult extends TLObject { public Updates updates; public String url; public static payments_PaymentResult TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { payments_PaymentResult result = null; - switch(constructor) { + switch (constructor) { case 0x4e5f810d: result = new TL_payments_paymentResult(); break; @@ -3434,12 +3687,12 @@ public class TLRPC { } } - public static class PrivacyRule extends TLObject { + public static abstract class PrivacyRule extends TLObject { public ArrayList users = new ArrayList<>(); public static PrivacyRule TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { PrivacyRule result = null; - switch(constructor) { + switch (constructor) { case 0x4d5bbe0c: result = new TL_privacyValueAllowUsers(); break; @@ -3665,6 +3918,30 @@ public class TLRPC { } public static class TL_messageMediaVenue extends MessageMedia { + public static int constructor = 0x2ec0533f; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + geo = GeoPoint.TLdeserialize(stream, stream.readInt32(exception), exception); + title = stream.readString(exception); + address = stream.readString(exception); + provider = stream.readString(exception); + venue_id = stream.readString(exception); + venue_type = stream.readString(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + geo.serializeToStream(stream); + stream.writeString(title); + stream.writeString(address); + stream.writeString(provider); + stream.writeString(venue_id); + stream.writeString(venue_type); + } + } + + public static class TL_messageMediaVenue_layer71 extends MessageMedia { public static int constructor = 0x7912b71f; @@ -3797,6 +4074,22 @@ public class TLRPC { } } + public static class TL_messageMediaGeoLive extends MessageMedia { + public static int constructor = 0x7c3c2609; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + geo = GeoPoint.TLdeserialize(stream, stream.readInt32(exception), exception); + period = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + geo.serializeToStream(stream); + stream.writeInt32(period); + } + } + public static class TL_messageMediaGame extends MessageMedia { public static int constructor = 0xfdb19008; @@ -3891,7 +4184,7 @@ public class TLRPC { } } - public static class LangPackString extends TLObject { + public static abstract class LangPackString extends TLObject { public int flags; public String key; public String zero_value; @@ -3904,7 +4197,7 @@ public class TLRPC { public static LangPackString TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { LangPackString result = null; - switch(constructor) { + switch (constructor) { case 0x6c47ac9f: result = new TL_langPackStringPluralized(); break; @@ -4054,7 +4347,7 @@ public class TLRPC { } } - public static class BotInlineResult extends TLObject { + public static abstract class BotInlineResult extends TLObject { public int flags; public String id; public String type; @@ -4074,7 +4367,7 @@ public class TLRPC { public static BotInlineResult TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { BotInlineResult result = null; - switch(constructor) { + switch (constructor) { case 0x9bebaeb9: result = new TL_botInlineResult(); break; @@ -4210,7 +4503,7 @@ public class TLRPC { } } - public static class PeerNotifySettings extends TLObject { + public static abstract class PeerNotifySettings extends TLObject { public int flags; public boolean silent; public int mute_until; @@ -4219,7 +4512,7 @@ public class TLRPC { public static PeerNotifySettings TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { PeerNotifySettings result = null; - switch(constructor) { + switch (constructor) { case 0x9acda4c0: result = new TL_peerNotifySettings(); break; @@ -4293,14 +4586,14 @@ public class TLRPC { } } - public static class contacts_Blocked extends TLObject { + public static abstract class contacts_Blocked extends TLObject { public ArrayList blocked = new ArrayList<>(); public ArrayList users = new ArrayList<>(); public int count; public static contacts_Blocked TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { contacts_Blocked result = null; - switch(constructor) { + switch (constructor) { case 0x1c138d15: result = new TL_contacts_blocked(); break; @@ -4428,7 +4721,7 @@ public class TLRPC { } } - public static class messages_DhConfig extends TLObject { + public static abstract class messages_DhConfig extends TLObject { public byte[] random; public int g; public byte[] p; @@ -4436,7 +4729,7 @@ public class TLRPC { public static messages_DhConfig TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { messages_DhConfig result = null; - switch(constructor) { + switch (constructor) { case 0xc0e24635: result = new TL_messages_dhConfigNotModified(); break; @@ -4560,13 +4853,13 @@ public class TLRPC { } } - public static class InputGeoPoint extends TLObject { + public static abstract class InputGeoPoint extends TLObject { public double lat; public double _long; public static InputGeoPoint TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { InputGeoPoint result = null; - switch(constructor) { + switch (constructor) { case 0xf3b7acc9: result = new TL_inputGeoPoint(); break; @@ -4637,7 +4930,7 @@ public class TLRPC { } } - public static class Audio extends TLObject { + public static abstract class Audio extends TLObject { public long id; public long access_hash; public int date; @@ -4651,7 +4944,7 @@ public class TLRPC { public static Audio TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { Audio result = null; - switch(constructor) { + switch (constructor) { case 0x586988d8: result = new TL_audioEmpty_layer45(); break; @@ -4802,7 +5095,7 @@ public class TLRPC { } } - public static class BotInfo extends TLObject { + public static abstract class BotInfo extends TLObject { public int user_id; public String description; public ArrayList commands = new ArrayList<>(); @@ -4810,7 +5103,7 @@ public class TLRPC { public static BotInfo TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { BotInfo result = null; - switch(constructor) { + switch (constructor) { case 0xbb2e37ce: result = new TL_botInfoEmpty_layer48(); break; @@ -4918,7 +5211,7 @@ public class TLRPC { } } - public static class InputGame extends TLObject { + public static abstract class InputGame extends TLObject { public InputUser bot_id; public String short_name; public long id; @@ -4926,7 +5219,7 @@ public class TLRPC { public static InputGame TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { InputGame result = null; - switch(constructor) { + switch (constructor) { case 0xc331e80a: result = new TL_inputGameShortName(); break; @@ -4976,7 +5269,7 @@ public class TLRPC { } } - public static class ReplyMarkup extends TLObject { + public static abstract class ReplyMarkup extends TLObject { public ArrayList rows = new ArrayList<>(); public int flags; public boolean selective; @@ -4985,7 +5278,7 @@ public class TLRPC { public static ReplyMarkup TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { ReplyMarkup result = null; - switch(constructor) { + switch (constructor) { case 0x48a30254: result = new TL_replyInlineMarkup(); break; @@ -5117,17 +5410,18 @@ public class TLRPC { } } - public static class contacts_Contacts extends TLObject { + public static abstract class contacts_Contacts extends TLObject { public ArrayList contacts = new ArrayList<>(); + public int saved_count; public ArrayList users = new ArrayList<>(); public static contacts_Contacts TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { contacts_Contacts result = null; - switch(constructor) { + switch (constructor) { case 0xb74ba9d2: result = new TL_contacts_contactsNotModified(); break; - case 0x6f8b8cb2: + case 0xeae87e42: result = new TL_contacts_contacts(); break; } @@ -5151,7 +5445,7 @@ public class TLRPC { } public static class TL_contacts_contacts extends contacts_Contacts { - public static int constructor = 0x6f8b8cb2; + public static int constructor = 0xeae87e42; public void readParams(AbstractSerializedData stream, boolean exception) { @@ -5170,6 +5464,7 @@ public class TLRPC { } contacts.add(object); } + saved_count = stream.readInt32(exception); magic = stream.readInt32(exception); if (magic != 0x1cb5c415) { if (exception) { @@ -5195,6 +5490,7 @@ public class TLRPC { for (int a = 0; a < count; a++) { contacts.get(a).serializeToStream(stream); } + stream.writeInt32(saved_count); stream.writeInt32(0x1cb5c415); count = users.size(); stream.writeInt32(count); @@ -5204,11 +5500,11 @@ public class TLRPC { } } - public static class InputPrivacyKey extends TLObject { + public static abstract class InputPrivacyKey extends TLObject { public static InputPrivacyKey TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { InputPrivacyKey result = null; - switch(constructor) { + switch (constructor) { case 0xbdfb0426: result = new TL_inputPrivacyKeyChatInvite(); break; @@ -5256,14 +5552,14 @@ public class TLRPC { } } - public static class photos_Photos extends TLObject { + public static abstract class photos_Photos extends TLObject { public ArrayList photos = new ArrayList<>(); public ArrayList users = new ArrayList<>(); public int count; public static photos_Photos TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { photos_Photos result = null; - switch(constructor) { + switch (constructor) { case 0x8dca6aa5: result = new TL_photos_photos(); break; @@ -5391,7 +5687,7 @@ public class TLRPC { } } - public static class ChatFull extends TLObject { + public static abstract class ChatFull extends TLObject { public int id; public ChatParticipants participants; public Photo chat_photo; @@ -5404,7 +5700,6 @@ public class TLRPC { public String about; public int participants_count; public int admins_count; - public int banned_count; public int read_inbox_max_id; public int read_outbox_max_id; public int unread_count; @@ -5413,15 +5708,24 @@ public class TLRPC { public int pinned_msg_id; public int kicked_count; public int unread_important_count; + public boolean can_set_stickers; + public boolean hidden_prehistory; + public int banned_count; + public StickerSet stickerset; + public int available_min_id; + public int call_msg_id; public static ChatFull TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { ChatFull result = null; - switch(constructor) { + switch (constructor) { case 0x2e02a614: result = new TL_chatFull(); break; - case 0x95cb5f57: - result = new TL_channelFull(); + case 0x17f45fcf: + result = new TL_channelFull_layer71(); + break; + case 0x76af5481: + result = new TL_channelFull_layer72(); break; case 0x97bee562: result = new TL_channelFull_layer52(); @@ -5435,6 +5739,12 @@ public class TLRPC { case 0xfab31aa3: result = new TL_channelFull_old(); break; + case 0x95cb5f57: + result = new TL_channelFull_layer70(); + break; + case 0xcbb62890: + result = new TL_channelFull(); + break; } if (result == null && exception) { throw new RuntimeException(String.format("can't parse magic %x in ChatFull", constructor)); @@ -5490,6 +5800,343 @@ public class TLRPC { } public static class TL_channelFull extends ChatFull { + public static int constructor = 0xcbb62890; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + can_view_participants = (flags & 8) != 0; + can_set_username = (flags & 64) != 0; + can_set_stickers = (flags & 128) != 0; + hidden_prehistory = (flags & 1024) != 0; + id = stream.readInt32(exception); + about = stream.readString(exception); + if ((flags & 1) != 0) { + participants_count = stream.readInt32(exception); + } + if ((flags & 2) != 0) { + admins_count = stream.readInt32(exception); + } + if ((flags & 4) != 0) { + kicked_count = stream.readInt32(exception); + } + if ((flags & 4) != 0) { + banned_count = stream.readInt32(exception); + } + read_inbox_max_id = stream.readInt32(exception); + read_outbox_max_id = stream.readInt32(exception); + unread_count = stream.readInt32(exception); + chat_photo = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); + notify_settings = PeerNotifySettings.TLdeserialize(stream, stream.readInt32(exception), exception); + exported_invite = ExportedChatInvite.TLdeserialize(stream, stream.readInt32(exception), exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + BotInfo object = BotInfo.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + bot_info.add(object); + } + if ((flags & 16) != 0) { + migrated_from_chat_id = stream.readInt32(exception); + } + if ((flags & 16) != 0) { + migrated_from_max_id = stream.readInt32(exception); + } + if ((flags & 32) != 0) { + pinned_msg_id = stream.readInt32(exception); + } + if ((flags & 256) != 0) { + stickerset = StickerSet.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 512) != 0) { + available_min_id = stream.readInt32(exception); + } + if ((flags & 2048) != 0) { + call_msg_id = stream.readInt32(exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = can_view_participants ? (flags | 8) : (flags &~ 8); + flags = can_set_username ? (flags | 64) : (flags &~ 64); + flags = can_set_stickers ? (flags | 128) : (flags &~ 128); + flags = hidden_prehistory ? (flags | 1024) : (flags &~ 1024); + stream.writeInt32(flags); + stream.writeInt32(id); + stream.writeString(about); + if ((flags & 1) != 0) { + stream.writeInt32(participants_count); + } + if ((flags & 2) != 0) { + stream.writeInt32(admins_count); + } + if ((flags & 4) != 0) { + stream.writeInt32(kicked_count); + } + if ((flags & 4) != 0) { + stream.writeInt32(banned_count); + } + stream.writeInt32(read_inbox_max_id); + stream.writeInt32(read_outbox_max_id); + stream.writeInt32(unread_count); + chat_photo.serializeToStream(stream); + notify_settings.serializeToStream(stream); + exported_invite.serializeToStream(stream); + stream.writeInt32(0x1cb5c415); + int count = bot_info.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + bot_info.get(a).serializeToStream(stream); + } + if ((flags & 16) != 0) { + stream.writeInt32(migrated_from_chat_id); + } + if ((flags & 16) != 0) { + stream.writeInt32(migrated_from_max_id); + } + if ((flags & 32) != 0) { + stream.writeInt32(pinned_msg_id); + } + if ((flags & 256) != 0) { + stickerset.serializeToStream(stream); + } + if ((flags & 512) != 0) { + stream.writeInt32(available_min_id); + } + if ((flags & 2048) != 0) { + stream.writeInt32(call_msg_id); + } + } + } + + public static class TL_channelFull_layer72 extends TL_channelFull { + public static int constructor = 0x76af5481; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + can_view_participants = (flags & 8) != 0; + can_set_username = (flags & 64) != 0; + can_set_stickers = (flags & 128) != 0; + hidden_prehistory = (flags & 1024) != 0; + id = stream.readInt32(exception); + about = stream.readString(exception); + if ((flags & 1) != 0) { + participants_count = stream.readInt32(exception); + } + if ((flags & 2) != 0) { + admins_count = stream.readInt32(exception); + } + if ((flags & 4) != 0) { + kicked_count = stream.readInt32(exception); + } + if ((flags & 4) != 0) { + banned_count = stream.readInt32(exception); + } + read_inbox_max_id = stream.readInt32(exception); + read_outbox_max_id = stream.readInt32(exception); + unread_count = stream.readInt32(exception); + chat_photo = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); + notify_settings = PeerNotifySettings.TLdeserialize(stream, stream.readInt32(exception), exception); + exported_invite = ExportedChatInvite.TLdeserialize(stream, stream.readInt32(exception), exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + BotInfo object = BotInfo.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + bot_info.add(object); + } + if ((flags & 16) != 0) { + migrated_from_chat_id = stream.readInt32(exception); + } + if ((flags & 16) != 0) { + migrated_from_max_id = stream.readInt32(exception); + } + if ((flags & 32) != 0) { + pinned_msg_id = stream.readInt32(exception); + } + if ((flags & 256) != 0) { + stickerset = StickerSet.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 512) != 0) { + available_min_id = stream.readInt32(exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = can_view_participants ? (flags | 8) : (flags &~ 8); + flags = can_set_username ? (flags | 64) : (flags &~ 64); + flags = can_set_stickers ? (flags | 128) : (flags &~ 128); + flags = hidden_prehistory ? (flags | 1024) : (flags &~ 1024); + stream.writeInt32(flags); + stream.writeInt32(id); + stream.writeString(about); + if ((flags & 1) != 0) { + stream.writeInt32(participants_count); + } + if ((flags & 2) != 0) { + stream.writeInt32(admins_count); + } + if ((flags & 4) != 0) { + stream.writeInt32(kicked_count); + } + if ((flags & 4) != 0) { + stream.writeInt32(banned_count); + } + stream.writeInt32(read_inbox_max_id); + stream.writeInt32(read_outbox_max_id); + stream.writeInt32(unread_count); + chat_photo.serializeToStream(stream); + notify_settings.serializeToStream(stream); + exported_invite.serializeToStream(stream); + stream.writeInt32(0x1cb5c415); + int count = bot_info.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + bot_info.get(a).serializeToStream(stream); + } + if ((flags & 16) != 0) { + stream.writeInt32(migrated_from_chat_id); + } + if ((flags & 16) != 0) { + stream.writeInt32(migrated_from_max_id); + } + if ((flags & 32) != 0) { + stream.writeInt32(pinned_msg_id); + } + if ((flags & 256) != 0) { + stickerset.serializeToStream(stream); + } + if ((flags & 512) != 0) { + stream.writeInt32(available_min_id); + } + } + } + + public static class TL_channelFull_layer71 extends TL_channelFull { + public static int constructor = 0x17f45fcf; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + can_view_participants = (flags & 8) != 0; + can_set_username = (flags & 64) != 0; + can_set_stickers = (flags & 128) != 0; + id = stream.readInt32(exception); + about = stream.readString(exception); + if ((flags & 1) != 0) { + participants_count = stream.readInt32(exception); + } + if ((flags & 2) != 0) { + admins_count = stream.readInt32(exception); + } + if ((flags & 4) != 0) { + kicked_count = stream.readInt32(exception); + } + if ((flags & 4) != 0) { + banned_count = stream.readInt32(exception); + } + read_inbox_max_id = stream.readInt32(exception); + read_outbox_max_id = stream.readInt32(exception); + unread_count = stream.readInt32(exception); + chat_photo = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); + notify_settings = PeerNotifySettings.TLdeserialize(stream, stream.readInt32(exception), exception); + exported_invite = ExportedChatInvite.TLdeserialize(stream, stream.readInt32(exception), exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + BotInfo object = BotInfo.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + bot_info.add(object); + } + if ((flags & 16) != 0) { + migrated_from_chat_id = stream.readInt32(exception); + } + if ((flags & 16) != 0) { + migrated_from_max_id = stream.readInt32(exception); + } + if ((flags & 32) != 0) { + pinned_msg_id = stream.readInt32(exception); + } + if ((flags & 256) != 0) { + stickerset = StickerSet.TLdeserialize(stream, stream.readInt32(exception), exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = can_view_participants ? (flags | 8) : (flags &~ 8); + flags = can_set_username ? (flags | 64) : (flags &~ 64); + flags = can_set_stickers ? (flags | 128) : (flags &~ 128); + stream.writeInt32(flags); + stream.writeInt32(id); + stream.writeString(about); + if ((flags & 1) != 0) { + stream.writeInt32(participants_count); + } + if ((flags & 2) != 0) { + stream.writeInt32(admins_count); + } + if ((flags & 4) != 0) { + stream.writeInt32(kicked_count); + } + if ((flags & 4) != 0) { + stream.writeInt32(banned_count); + } + stream.writeInt32(read_inbox_max_id); + stream.writeInt32(read_outbox_max_id); + stream.writeInt32(unread_count); + chat_photo.serializeToStream(stream); + notify_settings.serializeToStream(stream); + exported_invite.serializeToStream(stream); + stream.writeInt32(0x1cb5c415); + int count = bot_info.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + bot_info.get(a).serializeToStream(stream); + } + if ((flags & 16) != 0) { + stream.writeInt32(migrated_from_chat_id); + } + if ((flags & 16) != 0) { + stream.writeInt32(migrated_from_max_id); + } + if ((flags & 32) != 0) { + stream.writeInt32(pinned_msg_id); + } + if ((flags & 256) != 0) { + stickerset.serializeToStream(stream); + } + } + } + + public static class TL_channelFull_layer70 extends TL_channelFull { public static int constructor = 0x95cb5f57; @@ -5963,14 +6610,14 @@ public class TLRPC { } } - public static class Page extends TLObject { + public static abstract class Page extends TLObject { public ArrayList blocks = new ArrayList<>(); public ArrayList photos = new ArrayList<>(); public ArrayList documents = new ArrayList<>(); public static Page TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { Page result = null; - switch(constructor) { + switch (constructor) { case 0x556ec7aa: result = new TL_pageFull(); break; @@ -6347,13 +6994,13 @@ public class TLRPC { } } - public static class InputUser extends TLObject { + public static abstract class InputUser extends TLObject { public int user_id; public long access_hash; public static InputUser TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { InputUser result = null; - switch(constructor) { + switch (constructor) { case 0xb98886cf: result = new TL_inputUserEmpty(); break; @@ -6408,7 +7055,7 @@ public class TLRPC { } } - public static class KeyboardButton extends TLObject { + public static abstract class KeyboardButton extends TLObject { public String text; public String url; public int flags; @@ -6418,7 +7065,7 @@ public class TLRPC { public static KeyboardButton TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { KeyboardButton result = null; - switch(constructor) { + switch (constructor) { case 0xb16a6c29: result = new TL_keyboardButtonRequestPhone(); break; @@ -6576,9 +7223,10 @@ public class TLRPC { } } - public static class BotInlineMessage extends TLObject { + public static abstract class BotInlineMessage extends TLObject { public int flags; public GeoPoint geo; + public int period; public ReplyMarkup reply_markup; public String caption; public boolean no_webpage; @@ -6594,9 +7242,9 @@ public class TLRPC { public static BotInlineMessage TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { BotInlineMessage result = null; - switch(constructor) { + switch (constructor) { case 0x3a8fd8b8: - result = new TL_botInlineMessageMediaGeo(); + result = new TL_botInlineMessageMediaGeo_layer71(); break; case 0xa74b15b: result = new TL_botInlineMessageMediaAuto(); @@ -6604,6 +7252,9 @@ public class TLRPC { case 0x8c7f65e2: result = new TL_botInlineMessageText(); break; + case 0xb722de65: + result = new TL_botInlineMessageMediaGeo(); + break; case 0x35edb4d4: result = new TL_botInlineMessageMediaContact(); break; @@ -6621,7 +7272,7 @@ public class TLRPC { } } - public static class TL_botInlineMessageMediaGeo extends BotInlineMessage { + public static class TL_botInlineMessageMediaGeo_layer71 extends TL_botInlineMessageMediaGeo { public static int constructor = 0x3a8fd8b8; @@ -6643,6 +7294,30 @@ public class TLRPC { } } + public static class TL_botInlineMessageMediaGeo extends BotInlineMessage { + public static int constructor = 0xb722de65; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + geo = GeoPoint.TLdeserialize(stream, stream.readInt32(exception), exception); + period = stream.readInt32(exception); + if ((flags & 4) != 0) { + reply_markup = ReplyMarkup.TLdeserialize(stream, stream.readInt32(exception), exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + geo.serializeToStream(stream); + stream.writeInt32(period); + if ((flags & 4) != 0) { + reply_markup.serializeToStream(stream); + } + } + } + public static class TL_botInlineMessageMediaAuto extends BotInlineMessage { public static int constructor = 0xa74b15b; @@ -6817,11 +7492,11 @@ public class TLRPC { } } - public static class Bool extends TLObject { + public static abstract class Bool extends TLObject { public static Bool TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { Bool result = null; - switch(constructor) { + switch (constructor) { case 0x997275b5: result = new TL_boolTrue(); break; @@ -6888,7 +7563,7 @@ public class TLRPC { } } - public static class WebPage extends TLObject { + public static abstract class WebPage extends TLObject { public int flags; public long id; public String url; @@ -6911,7 +7586,7 @@ public class TLRPC { public static WebPage TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { WebPage result = null; - switch(constructor) { + switch (constructor) { case 0x5f07b4bc: result = new TL_webPage(); break; @@ -7275,14 +7950,14 @@ public class TLRPC { } } - public static class messages_FeaturedStickers extends TLObject { + public static abstract class messages_FeaturedStickers extends TLObject { public int hash; public ArrayList sets = new ArrayList<>(); public ArrayList unread = new ArrayList<>(); public static messages_FeaturedStickers TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { messages_FeaturedStickers result = null; - switch(constructor) { + switch (constructor) { case 0xf89d88e5: result = new TL_messages_featuredStickers(); break; @@ -7361,17 +8036,115 @@ public class TLRPC { } } - public static class PhoneCallDiscardReason extends TLObject { + public static class TL_phone_groupCall extends TLObject { + public static int constructor = 0x6737ffb7; + + public GroupCall call; + public ArrayList participants = new ArrayList<>(); + public ArrayList chats = new ArrayList<>(); + public ArrayList users = new ArrayList<>(); + + public static TL_phone_groupCall TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_phone_groupCall.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_phone_groupCall", constructor)); + } else { + return null; + } + } + TL_phone_groupCall result = new TL_phone_groupCall(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + call = GroupCall.TLdeserialize(stream, stream.readInt32(exception), exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + GroupCallParticipant object = GroupCallParticipant.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + participants.add(object); + } + magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + Chat object = Chat.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + chats.add(object); + } + magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + User object = User.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + users.add(object); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + call.serializeToStream(stream); + stream.writeInt32(0x1cb5c415); + int count = participants.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + participants.get(a).serializeToStream(stream); + } + stream.writeInt32(0x1cb5c415); + count = chats.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + chats.get(a).serializeToStream(stream); + } + stream.writeInt32(0x1cb5c415); + count = users.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + users.get(a).serializeToStream(stream); + } + } + } + + public static abstract class PhoneCallDiscardReason extends TLObject { + public byte[] encrypted_key; public static PhoneCallDiscardReason TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { PhoneCallDiscardReason result = null; - switch(constructor) { + switch (constructor) { case 0x57adc690: result = new TL_phoneCallDiscardReasonHangup(); break; case 0xfaf7e8c9: result = new TL_phoneCallDiscardReasonBusy(); break; + case 0xafe2b839: + result = new TL_phoneCallDiscardReasonAllowGroupCall(); + break; case 0x85e42301: result = new TL_phoneCallDiscardReasonMissed(); break; @@ -7407,6 +8180,20 @@ public class TLRPC { } } + public static class TL_phoneCallDiscardReasonAllowGroupCall extends PhoneCallDiscardReason { + public static int constructor = 0xafe2b839; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + encrypted_key = stream.readByteArray(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeByteArray(encrypted_key); + } + } + public static class TL_phoneCallDiscardReasonMissed extends PhoneCallDiscardReason { public static int constructor = 0x85e42301; @@ -7484,11 +8271,11 @@ public class TLRPC { } } - public static class InputNotifyPeer extends TLObject { + public static abstract class InputNotifyPeer extends TLObject { public static InputNotifyPeer TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { InputNotifyPeer result = null; - switch(constructor) { + switch (constructor) { case 0x4a95e84e: result = new TL_inputNotifyChats(); break; @@ -7572,7 +8359,7 @@ public class TLRPC { } } - public static class InputFileLocation extends TLObject { + public static abstract class InputFileLocation extends TLObject { public long id; public long access_hash; public long volume_id; @@ -7581,7 +8368,7 @@ public class TLRPC { public static InputFileLocation TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { InputFileLocation result = null; - switch(constructor) { + switch (constructor) { case 0xf5235d55: result = new TL_inputEncryptedFileLocation(); break; @@ -7702,7 +8489,7 @@ public class TLRPC { } } - public static class PhoneCall extends TLObject { + public static abstract class PhoneCall extends TLObject { public long id; public long access_hash; public int date; @@ -7725,7 +8512,7 @@ public class TLRPC { public static PhoneCall TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { PhoneCall result = null; - switch(constructor) { + switch (constructor) { case 0x83761ce4: result = new TL_phoneCallRequested(); break; @@ -7938,7 +8725,7 @@ public class TLRPC { } } - public static class User extends TLObject { + public static abstract class User extends TLObject { public int id; public String first_name; public String last_name; @@ -7968,7 +8755,7 @@ public class TLRPC { public static User TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { User result = null; - switch(constructor) { + switch (constructor) { case 0xcab35e18: result = new TL_userContact_old2(); break; @@ -8599,6 +9386,120 @@ public class TLRPC { } } + public static abstract class RecentMeUrl extends TLObject { + public String url; + public int chat_id; + public StickerSetCovered set; + public ChatInvite chat_invite; + public int user_id; + + public static RecentMeUrl TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + RecentMeUrl result = null; + switch (constructor) { + case 0xa01b22f9: + result = new TL_recentMeUrlChat(); + break; + case 0xbc0a57dc: + result = new TL_recentMeUrlStickerSet(); + break; + case 0x46e1d13d: + result = new TL_recentMeUrlUnknown(); + break; + case 0xeb49081d: + result = new TL_recentMeUrlChatInvite(); + break; + case 0x8dbc3336: + result = new TL_recentMeUrlUser(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in RecentMeUrl", constructor)); + } + if (result != null) { + result.readParams(stream, exception); + } + return result; + } + } + + public static class TL_recentMeUrlChat extends RecentMeUrl { + public static int constructor = 0xa01b22f9; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + url = stream.readString(exception); + chat_id = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(url); + stream.writeInt32(chat_id); + } + } + + public static class TL_recentMeUrlStickerSet extends RecentMeUrl { + public static int constructor = 0xbc0a57dc; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + url = stream.readString(exception); + set = StickerSetCovered.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(url); + set.serializeToStream(stream); + } + } + + public static class TL_recentMeUrlUnknown extends RecentMeUrl { + public static int constructor = 0x46e1d13d; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + url = stream.readString(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(url); + } + } + + public static class TL_recentMeUrlChatInvite extends RecentMeUrl { + public static int constructor = 0xeb49081d; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + url = stream.readString(exception); + chat_invite = ChatInvite.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(url); + chat_invite.serializeToStream(stream); + } + } + + public static class TL_recentMeUrlUser extends RecentMeUrl { + public static int constructor = 0x8dbc3336; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + url = stream.readString(exception); + user_id = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(url); + stream.writeInt32(user_id); + } + } + public static class TL_messages_highScores extends TLObject { public static int constructor = 0x9a3bfd99; @@ -8718,12 +9619,12 @@ public class TLRPC { } } - public static class ChannelParticipantsFilter extends TLObject { + public static abstract class ChannelParticipantsFilter extends TLObject { public String q; public static ChannelParticipantsFilter TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { ChannelParticipantsFilter result = null; - switch(constructor) { + switch (constructor) { case 0xb4608969: result = new TL_channelParticipantsAdmins(); break; @@ -8822,7 +9723,7 @@ public class TLRPC { } } - public static class GeoChatMessage extends TLObject { + public static abstract class GeoChatMessage extends TLObject { public int chat_id; public int id; public int from_id; @@ -8833,7 +9734,7 @@ public class TLRPC { public static GeoChatMessage TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { GeoChatMessage result = null; - switch(constructor) { + switch (constructor) { case 0x4505f8e1: result = new TL_geoChatMessage(); break; @@ -8916,11 +9817,13 @@ public class TLRPC { } } - public static class MessageAction extends TLObject { + public static abstract class MessageAction extends TLObject { public String title; public String address; public DecryptedMessageAction encryptedAction; + public String message; public ArrayList users = new ArrayList<>(); + public TL_inputGroupCall call; public int channel_id; public Photo photo; public int chat_id; @@ -8939,19 +9842,25 @@ public class TLRPC { public static MessageAction TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { MessageAction result = null; - switch(constructor) { + switch (constructor) { case 0x555555F5: result = new TL_messageActionLoginUnknownLocation(); break; case 0x555555F7: result = new TL_messageEncryptedAction(); break; + case 0xfae69f56: + result = new TL_messageActionCustomAction(); + break; case 0xa6638b9a: result = new TL_messageActionChatCreate(); break; case 0x51bdb021: result = new TL_messageActionChatMigrateTo(); break; + case 0x7a0d7f42: + result = new TL_messageActionGroupCall(); + break; case 0x4792929b: result = new TL_messageActionScreenshotTaken(); break; @@ -9059,6 +9968,20 @@ public class TLRPC { } } + public static class TL_messageActionCustomAction extends MessageAction { + public static int constructor = 0xfae69f56; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + message = stream.readString(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(message); + } + } + public static class TL_messageActionChatCreate extends MessageAction { public static int constructor = 0xa6638b9a; @@ -9090,6 +10013,28 @@ public class TLRPC { } } + public static class TL_messageActionGroupCall extends MessageAction { + public static int constructor = 0x7a0d7f42; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + call = TL_inputGroupCall.TLdeserialize(stream, stream.readInt32(exception), exception); + if ((flags & 1) != 0) { + duration = stream.readInt32(exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + call.serializeToStream(stream); + if ((flags & 1) != 0) { + stream.writeInt32(duration); + } + } + } + public static class TL_messageActionChatMigrateTo extends MessageAction { public static int constructor = 0x51bdb021; @@ -9409,12 +10354,12 @@ public class TLRPC { } } - public static class ReportReason extends TLObject { + public static abstract class ReportReason extends TLObject { public String text; public static ReportReason TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { ReportReason result = null; - switch(constructor) { + switch (constructor) { case 0x58dbcab8: result = new TL_inputReportReasonSpam(); break; @@ -9479,11 +10424,11 @@ public class TLRPC { } } - public static class PeerNotifyEvents extends TLObject { + public static abstract class PeerNotifyEvents extends TLObject { public static PeerNotifyEvents TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { PeerNotifyEvents result = null; - switch(constructor) { + switch (constructor) { case 0xadd53cb3: result = new TL_peerNotifyEventsEmpty(); break; @@ -9600,7 +10545,7 @@ public class TLRPC { } } - public static class DecryptedMessage extends TLObject { + public static abstract class DecryptedMessage extends TLObject { public long random_id; public int ttl; public String message; @@ -9611,10 +10556,11 @@ public class TLRPC { public ArrayList entities = new ArrayList<>(); public String via_bot_name; public long reply_to_random_id; + public long grouped_id; public static DecryptedMessage TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { DecryptedMessage result = null; - switch(constructor) { + switch (constructor) { case 0x204d3878: result = new TL_decryptedMessage_layer17(); break; @@ -9627,9 +10573,12 @@ public class TLRPC { case 0x1f814f1f: result = new TL_decryptedMessage_layer8(); break; - case 0x36b091de: + case 0x91cc4674: result = new TL_decryptedMessage(); break; + case 0x36b091de: + result = new TL_decryptedMessage_layer45(); + break; } if (result == null && exception) { throw new RuntimeException(String.format("can't parse magic %x in DecryptedMessage", constructor)); @@ -9716,6 +10665,75 @@ public class TLRPC { } public static class TL_decryptedMessage extends DecryptedMessage { + public static int constructor = 0x91cc4674; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + random_id = stream.readInt64(exception); + ttl = stream.readInt32(exception); + message = stream.readString(exception); + if ((flags & 512) != 0) { + media = DecryptedMessageMedia.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 128) != 0) { + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + MessageEntity object = MessageEntity.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + entities.add(object); + } + } + if ((flags & 2048) != 0) { + via_bot_name = stream.readString(exception); + } + if ((flags & 8) != 0) { + reply_to_random_id = stream.readInt64(exception); + } + if ((flags & 131072) != 0) { + grouped_id = stream.readInt64(exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + stream.writeInt64(random_id); + stream.writeInt32(ttl); + stream.writeString(message); + if ((flags & 512) != 0) { + media.serializeToStream(stream); + } + if ((flags & 128) != 0) { + stream.writeInt32(0x1cb5c415); + int count = entities.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + entities.get(a).serializeToStream(stream); + } + } + if ((flags & 2048) != 0) { + stream.writeString(via_bot_name); + } + if ((flags & 8) != 0) { + stream.writeInt64(reply_to_random_id); + } + if ((flags & 131072) != 0) { + stream.writeInt64(grouped_id); + } + } + } + + public static class TL_decryptedMessage_layer45 extends TL_decryptedMessage { public static int constructor = 0x36b091de; @@ -9778,11 +10796,11 @@ public class TLRPC { } } - public static class InputPeerNotifyEvents extends TLObject { + public static abstract class InputPeerNotifyEvents extends TLObject { public static InputPeerNotifyEvents TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { InputPeerNotifyEvents result = null; - switch(constructor) { + switch (constructor) { case 0xe86a2c74: result = new TL_inputPeerNotifyEventsAll(); break; @@ -9828,6 +10846,8 @@ public class TLRPC { public boolean email_requested; public boolean shipping_address_requested; public boolean flexible; + public boolean phone_to_provider; + public boolean email_to_provider; public String currency; public ArrayList prices = new ArrayList<>(); @@ -9852,6 +10872,8 @@ public class TLRPC { email_requested = (flags & 8) != 0; shipping_address_requested = (flags & 16) != 0; flexible = (flags & 32) != 0; + phone_to_provider = (flags & 64) != 0; + email_to_provider = (flags & 128) != 0; currency = stream.readString(exception); int magic = stream.readInt32(exception); if (magic != 0x1cb5c415) { @@ -9878,6 +10900,8 @@ public class TLRPC { flags = email_requested ? (flags | 8) : (flags &~ 8); flags = shipping_address_requested ? (flags | 16) : (flags &~ 16); flags = flexible ? (flags | 32) : (flags &~ 32); + flags = phone_to_provider ? (flags | 64) : (flags &~ 64); + flags = email_to_provider ? (flags | 128) : (flags &~ 128); stream.writeInt32(flags); stream.writeString(currency); stream.writeInt32(0x1cb5c415); @@ -9945,7 +10969,7 @@ public class TLRPC { } } - public static class Video extends TLObject { + public static abstract class Video extends TLObject { public long id; public long access_hash; public int user_id; @@ -9963,7 +10987,7 @@ public class TLRPC { public static Video TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { Video result = null; - switch(constructor) { + switch (constructor) { case 0xee9f4a4d: result = new TL_video_old3(); break; @@ -10179,19 +11203,23 @@ public class TLRPC { } } - public static class InputPaymentCredentials extends TLObject { + public static abstract class InputPaymentCredentials extends TLObject { public int flags; public boolean save; public TL_dataJSON data; + public TL_dataJSON payment_token; public String id; public byte[] tmp_password; public static InputPaymentCredentials TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { InputPaymentCredentials result = null; - switch(constructor) { + switch (constructor) { case 0x3417d728: result = new TL_inputPaymentCredentials(); break; + case 0x795667a6: + result = new TL_inputPaymentCredentialsAndroidPay(); + break; case 0xc10eb2cf: result = new TL_inputPaymentCredentialsSaved(); break; @@ -10224,6 +11252,20 @@ public class TLRPC { } } + public static class TL_inputPaymentCredentialsAndroidPay extends InputPaymentCredentials { + public static int constructor = 0x795667a6; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + payment_token = TL_dataJSON.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + payment_token.serializeToStream(stream); + } + } + public static class TL_inputPaymentCredentialsSaved extends InputPaymentCredentials { public static int constructor = 0xc10eb2cf; @@ -10268,11 +11310,11 @@ public class TLRPC { } } - public static class TopPeerCategory extends TLObject { + public static abstract class TopPeerCategory extends TLObject { public static TopPeerCategory TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { TopPeerCategory result = null; - switch(constructor) { + switch (constructor) { case 0x637b7ed: result = new TL_topPeerCategoryCorrespondents(); break; @@ -10501,13 +11543,13 @@ public class TLRPC { } } - public static class InputDocument extends TLObject { + public static abstract class InputDocument extends TLObject { public long id; public long access_hash; public static InputDocument TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { InputDocument result = null; - switch(constructor) { + switch (constructor) { case 0x72f0eaae: result = new TL_inputDocumentEmpty(); break; @@ -10621,7 +11663,7 @@ public class TLRPC { } } - public static class Document extends TLObject { + public static abstract class Document extends TLObject { public long id; public long access_hash; public int user_id; @@ -10639,7 +11681,7 @@ public class TLRPC { public static Document TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { Document result = null; - switch(constructor) { + switch (constructor) { case 0x87232bc7: result = new TL_document(); break; @@ -10804,15 +11846,7 @@ public class TLRPC { id = stream.readInt64(exception); access_hash = stream.readInt64(exception); date = stream.readInt32(exception); - int startReadPosiition = stream.getPosition(); //TODO remove this hack after some time - try { - mime_type = stream.readString(true); - } catch (Exception e) { - mime_type = "audio/ogg"; - if (stream instanceof NativeByteBuffer) { - ((NativeByteBuffer) stream).position(startReadPosiition); - } - } + mime_type = stream.readString(exception); size = stream.readInt32(exception); thumb = PhotoSize.TLdeserialize(stream, stream.readInt32(exception), exception); dc_id = stream.readInt32(exception); @@ -10902,11 +11936,11 @@ public class TLRPC { } } - public static class ContactLink extends TLObject { + public static abstract class ContactLink extends TLObject { public static ContactLink TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { ContactLink result = null; - switch(constructor) { + switch (constructor) { case 0xfeedd3ad: result = new TL_contactLinkNone(); break; @@ -10966,7 +12000,7 @@ public class TLRPC { } } - public static class PageBlock extends TLObject { + public static abstract class PageBlock extends TLObject { public String author; public int published_date; public RichText text; @@ -11000,7 +12034,7 @@ public class TLRPC { public static PageBlock TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { PageBlock result = null; - switch(constructor) { + switch (constructor) { case 0xdb20b188: result = new TL_pageBlockDivider(); break; @@ -11582,12 +12616,12 @@ public class TLRPC { } } - public static class InputPrivacyRule extends TLObject { + public static abstract class InputPrivacyRule extends TLObject { public ArrayList users = new ArrayList<>(); public static InputPrivacyRule TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { InputPrivacyRule result = null; - switch(constructor) { + switch (constructor) { case 0x90110467: result = new TL_inputPrivacyValueDisallowUsers(); break; @@ -11790,7 +12824,7 @@ public class TLRPC { } } - public static class InputMedia extends TLObject { + public static abstract class InputMedia extends TLObject { public String phone_number; public String first_name; public String last_name; @@ -11806,13 +12840,16 @@ public class TLRPC { public String address; public String provider; public String venue_id; + public String venue_type; + public int period; public InputFile thumb; public String mime_type; + public boolean nosound_video; public ArrayList attributes = new ArrayList<>(); public static InputMedia TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { InputMedia result = null; - switch(constructor) { + switch (constructor) { case 0xa6e45987: result = new TL_inputMediaContact(); break; @@ -11837,9 +12874,12 @@ public class TLRPC { case 0x2f37e231: result = new TL_inputMediaUploadedPhoto(); break; - case 0x2827a81a: + case 0xc13d1c11: result = new TL_inputMediaVenue(); break; + case 0x7b1a118f: + result = new TL_inputMediaGeoLive(); + break; case 0xe39621fd: result = new TL_inputMediaUploadedDocument(); break; @@ -12031,7 +13071,7 @@ public class TLRPC { } public static class TL_inputMediaVenue extends InputMedia { - public static int constructor = 0x2827a81a; + public static int constructor = 0xc13d1c11; public void readParams(AbstractSerializedData stream, boolean exception) { @@ -12040,6 +13080,7 @@ public class TLRPC { address = stream.readString(exception); provider = stream.readString(exception); venue_id = stream.readString(exception); + venue_type = stream.readString(exception); } public void serializeToStream(AbstractSerializedData stream) { @@ -12049,6 +13090,23 @@ public class TLRPC { stream.writeString(address); stream.writeString(provider); stream.writeString(venue_id); + stream.writeString(venue_type); + } + } + + public static class TL_inputMediaGeoLive extends InputMedia { + public static int constructor = 0x7b1a118f; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + geo_point = InputGeoPoint.TLdeserialize(stream, stream.readInt32(exception), exception); + period = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + geo_point.serializeToStream(stream); + stream.writeInt32(period); } } @@ -12058,6 +13116,7 @@ public class TLRPC { public void readParams(AbstractSerializedData stream, boolean exception) { flags = stream.readInt32(exception); + nosound_video = (flags & 8) != 0; file = InputFile.TLdeserialize(stream, stream.readInt32(exception), exception); if ((flags & 4) != 0) { thumb = InputFile.TLdeserialize(stream, stream.readInt32(exception), exception); @@ -12103,6 +13162,7 @@ public class TLRPC { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + flags = nosound_video ? (flags | 8) : (flags &~ 8); stream.writeInt32(flags); file.serializeToStream(stream); if ((flags & 4) != 0) { @@ -12179,14 +13239,14 @@ public class TLRPC { } } - public static class StickerSetCovered extends TLObject { + public static abstract class StickerSetCovered extends TLObject { public StickerSet set; public ArrayList covers = new ArrayList<>(); public Document cover; public static StickerSetCovered TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { StickerSetCovered result = null; - switch(constructor) { + switch (constructor) { case 0x3407e51b: result = new TL_stickerSetMultiCovered(); break; @@ -12255,7 +13315,7 @@ public class TLRPC { } } - public static class geochats_Messages extends TLObject { + public static abstract class geochats_Messages extends TLObject { public int count; public ArrayList messages = new ArrayList<>(); public ArrayList chats = new ArrayList<>(); @@ -12263,7 +13323,7 @@ public class TLRPC { public static geochats_Messages TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { geochats_Messages result = null; - switch(constructor) { + switch (constructor) { case 0xbc5863e8: result = new TL_geochats_messagesSlice(); break; @@ -12433,7 +13493,7 @@ public class TLRPC { } } - public static class EncryptedMessage extends TLObject { + public static abstract class EncryptedMessage extends TLObject { public long random_id; public int chat_id; public int date; @@ -12442,7 +13502,7 @@ public class TLRPC { public static EncryptedMessage TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { EncryptedMessage result = null; - switch(constructor) { + switch (constructor) { case 0x23734b06: result = new TL_encryptedMessageService(); break; @@ -12502,14 +13562,14 @@ public class TLRPC { } } - public static class InputStickerSet extends TLObject { + public static abstract class InputStickerSet extends TLObject { public long id; public long access_hash; public String short_name; public static InputStickerSet TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { InputStickerSet result = null; - switch(constructor) { + switch (constructor) { case 0xffb62b95: result = new TL_inputStickerSetEmpty(); break; @@ -12639,12 +13699,12 @@ public class TLRPC { } } - public static class UserStatus extends TLObject { + public static abstract class UserStatus extends TLObject { public int expires; public static UserStatus TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { UserStatus result = null; - switch(constructor) { + switch (constructor) { case 0x8c703f: result = new TL_userStatusOffline(); break; @@ -12940,7 +14000,7 @@ public class TLRPC { } } - public static class Update extends TLObject { + public static abstract class Update extends TLObject { public ArrayList messages = new ArrayList<>(); public int pts; public int pts_count; @@ -12956,6 +14016,7 @@ public class TLRPC { public int max_id; public boolean pinned; public String phone; + public GroupCallParticipant participant; public long random_id; public int channel_id; public int qts; @@ -12983,6 +14044,7 @@ public class TLRPC { public String first_name; public String last_name; public String username; + public int available_min_id; public PhoneCall phone_call; public ContactLink my_link; public ContactLink foreign_link; @@ -12997,7 +14059,7 @@ public class TLRPC { public static Update TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { Update result = null; - switch(constructor) { + switch (constructor) { case 0xa20db0e5: result = new TL_updateDeleteMessages(); break; @@ -13028,6 +14090,9 @@ public class TLRPC { case 0x12b9417b: result = new TL_updateUserPhone(); break; + case 0x57eaec8: + result = new TL_updateGroupCallParticipant(); + break; case 0x4e90bfd6: result = new TL_updateMessageID(); break; @@ -13067,6 +14132,9 @@ public class TLRPC { case 0xe48f964: result = new TL_updateBotInlineSend(); break; + case 0xe511996d: + result = new TL_updateFavedStickers(); + break; case 0x7f891213: result = new TL_updateWebPage(); break; @@ -13076,6 +14144,9 @@ public class TLRPC { case 0x9a65ea1f: result = new TL_updateChatUserTyping(); break; + case 0x85fe86ed: + result = new TL_updateGroupCall(); + break; case 0xb4a2e88d: result = new TL_updateEncryption(); break; @@ -13094,6 +14165,9 @@ public class TLRPC { case 0x56022f4d: result = new TL_updateLangPack(); break; + case 0x89893b45: + result = new TL_updateChannelReadMessagesContents(); + break; case 0xb6901959: result = new TL_updateChatParticipantAdmin(); break; @@ -13112,6 +14186,9 @@ public class TLRPC { case 0xa7332b73: result = new TL_updateUserName(); break; + case 0x70db6837: + result = new TL_updateChannelAvailableMessages(); + break; case 0xab0f6b1e: result = new TL_updatePhoneCall(); break; @@ -13133,6 +14210,9 @@ public class TLRPC { case 0x9375341e: result = new TL_updateSavedGifs(); break; + case 0x7084a7be: + result = new TL_updateContactsReset(); + break; case 0xb6d45656: result = new TL_updateChannel(); break; @@ -13425,6 +14505,32 @@ public class TLRPC { } } + public static class TL_updateFavedStickers extends Update { + public static int constructor = 0xe511996d; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + public static class TL_updateGroupCallParticipant extends Update { + public static int constructor = 0x57eaec8; + + public TL_inputGroupCall call; + + public void readParams(AbstractSerializedData stream, boolean exception) { + call = TL_inputGroupCall.TLdeserialize(stream, stream.readInt32(exception), exception); + participant = GroupCallParticipant.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + call.serializeToStream(stream); + participant.serializeToStream(stream); + } + } + public static class TL_updateReadChannelOutbox extends Update { public static int constructor = 0x25d6c9c7; @@ -13708,6 +14814,21 @@ public class TLRPC { } } + public static class TL_updateGroupCall extends Update { + public static int constructor = 0x85fe86ed; + + public GroupCall call; + + public void readParams(AbstractSerializedData stream, boolean exception) { + call = GroupCall.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + call.serializeToStream(stream); + } + } + public static class TL_updateChannelTooLong extends Update { public static int constructor = 0xeb0467fb; @@ -13827,6 +14948,37 @@ public class TLRPC { } } + public static class TL_updateChannelReadMessagesContents extends Update { + public static int constructor = 0x89893b45; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + channel_id = stream.readInt32(exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + messages.add(stream.readInt32(exception)); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(channel_id); + stream.writeInt32(0x1cb5c415); + int count = messages.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + stream.writeInt32(messages.get(a)); + } + } + } + public static class TL_updateChatParticipantAdmin extends Update { public static int constructor = 0xb6901959; @@ -13956,6 +15108,22 @@ public class TLRPC { } } + public static class TL_updateChannelAvailableMessages extends Update { + public static int constructor = 0x70db6837; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + channel_id = stream.readInt32(exception); + available_min_id = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(channel_id); + stream.writeInt32(available_min_id); + } + } + public static class TL_updatePhoneCall extends Update { public static int constructor = 0xab0f6b1e; @@ -14082,6 +15250,15 @@ public class TLRPC { } } + public static class TL_updateContactsReset extends Update { + public static int constructor = 0x7084a7be; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + public static class TL_updateChannel extends Update { public static int constructor = 0xb6d45656; @@ -14408,7 +15585,7 @@ public class TLRPC { } } - public static class InputEncryptedFile extends TLObject { + public static abstract class InputEncryptedFile extends TLObject { public long id; public long access_hash; public int parts; @@ -14417,7 +15594,7 @@ public class TLRPC { public static InputEncryptedFile TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { InputEncryptedFile result = null; - switch(constructor) { + switch (constructor) { case 0x5a17b5e5: result = new TL_inputEncryptedFile(); break; @@ -14504,7 +15681,7 @@ public class TLRPC { } } - public static class messages_AllStickers extends TLObject { + public static abstract class messages_AllStickers extends TLObject { public String hash; public ArrayList sets = new ArrayList<>(); public ArrayList packs = new ArrayList<>(); @@ -14512,7 +15689,7 @@ public class TLRPC { public static messages_AllStickers TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { messages_AllStickers result = null; - switch(constructor) { + switch (constructor) { case 0xedfd405f: result = new TL_messages_allStickers(); break; @@ -14575,7 +15752,7 @@ public class TLRPC { } } - public static class DecryptedMessageAction extends TLObject { + public static abstract class DecryptedMessageAction extends TLObject { public int ttl_seconds; public int layer; public ArrayList random_ids = new ArrayList<>(); @@ -14589,7 +15766,7 @@ public class TLRPC { public static DecryptedMessageAction TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { DecryptedMessageAction result = null; - switch(constructor) { + switch (constructor) { case 0xa1733aec: result = new TL_decryptedMessageActionSetMessageTTL(); break; @@ -14867,7 +16044,7 @@ public class TLRPC { } } - public static class account_Password extends TLObject { + public static abstract class account_Password extends TLObject { public byte[] current_salt; public byte[] new_salt; public String hint; @@ -14876,7 +16053,7 @@ public class TLRPC { public static account_Password TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { account_Password result = null; - switch(constructor) { + switch (constructor) { case 0x7c18141c: result = new TL_account_password(); break; @@ -14932,14 +16109,14 @@ public class TLRPC { } } - public static class UserProfilePhoto extends TLObject { + public static abstract class UserProfilePhoto extends TLObject { public long photo_id; public FileLocation photo_small; public FileLocation photo_big; public static UserProfilePhoto TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { UserProfilePhoto result = null; - switch(constructor) { + switch (constructor) { case 0x4f11bae1: result = new TL_userProfilePhotoEmpty(); break; @@ -15003,7 +16180,7 @@ public class TLRPC { } } - public static class MessageEntity extends TLObject { + public static abstract class MessageEntity extends TLObject { public int offset; public int length; public String url; @@ -15011,7 +16188,7 @@ public class TLRPC { public static MessageEntity TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { MessageEntity result = null; - switch(constructor) { + switch (constructor) { case 0x76a6d327: result = new TL_messageEntityTextUrl(); break; @@ -15280,7 +16457,7 @@ public class TLRPC { } } - public static class Photo extends TLObject { + public static abstract class Photo extends TLObject { public long id; public long access_hash; public int user_id; @@ -15293,7 +16470,7 @@ public class TLRPC { public static Photo TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { Photo result = null; - switch(constructor) { + switch (constructor) { case 0x22b56751: result = new TL_photo_old(); break; @@ -15764,11 +16941,49 @@ public class TLRPC { } } + public static class TL_groupCallConnection extends TLObject { + public static int constructor = 0x40732163; + + public long id; + public String ip; + public String ipv6; + public int port; + + public static TL_groupCallConnection TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_groupCallConnection.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_groupCallConnection", constructor)); + } else { + return null; + } + } + TL_groupCallConnection result = new TL_groupCallConnection(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + id = stream.readInt64(exception); + ip = stream.readString(exception); + ipv6 = stream.readString(exception); + port = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt64(id); + stream.writeString(ip); + stream.writeString(ipv6); + stream.writeInt32(port); + } + } + public static class TL_config extends TLObject { - public static int constructor = 0x7feec888; + public static int constructor = 0x9c840964; public int flags; public boolean phonecalls_enabled; + public boolean default_p2p_contacts; public int date; public int expires; public boolean test_mode; @@ -15790,6 +17005,8 @@ public class TLRPC { public int edit_time_limit; public int rating_e_decay; public int stickers_recent_limit; + public int stickers_faved_limit; + public int channels_read_media_period; public int tmp_sessions; public int pinned_dialogs_count_max; public int call_receive_timeout_ms; @@ -15817,6 +17034,7 @@ public class TLRPC { public void readParams(AbstractSerializedData stream, boolean exception) { flags = stream.readInt32(exception); phonecalls_enabled = (flags & 2) != 0; + default_p2p_contacts = (flags & 8) != 0; date = stream.readInt32(exception); expires = stream.readInt32(exception); test_mode = stream.readBool(exception); @@ -15852,6 +17070,8 @@ public class TLRPC { edit_time_limit = stream.readInt32(exception); rating_e_decay = stream.readInt32(exception); stickers_recent_limit = stream.readInt32(exception); + stickers_faved_limit = stream.readInt32(exception); + channels_read_media_period = stream.readInt32(exception); if ((flags & 1) != 0) { tmp_sessions = stream.readInt32(exception); } @@ -15887,6 +17107,7 @@ public class TLRPC { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); flags = phonecalls_enabled ? (flags | 2) : (flags &~ 2); + flags = default_p2p_contacts ? (flags | 8) : (flags &~ 8); stream.writeInt32(flags); stream.writeInt32(date); stream.writeInt32(expires); @@ -15914,6 +17135,8 @@ public class TLRPC { stream.writeInt32(edit_time_limit); stream.writeInt32(rating_e_decay); stream.writeInt32(stickers_recent_limit); + stream.writeInt32(stickers_faved_limit); + stream.writeInt32(channels_read_media_period); if ((flags & 1) != 0) { stream.writeInt32(tmp_sessions); } @@ -15938,14 +17161,14 @@ public class TLRPC { } } - public static class contacts_TopPeers extends TLObject { + public static abstract class contacts_TopPeers extends TLObject { public ArrayList categories = new ArrayList<>(); public ArrayList chats = new ArrayList<>(); public ArrayList users = new ArrayList<>(); public static contacts_TopPeers TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { contacts_TopPeers result = null; - switch(constructor) { + switch (constructor) { case 0x70b772a8: result = new TL_contacts_topPeers(); break; @@ -16110,13 +17333,13 @@ public class TLRPC { } - public static class messages_Chats extends TLObject { + public static abstract class messages_Chats extends TLObject { public ArrayList chats = new ArrayList<>(); public int count; public static messages_Chats TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { messages_Chats result = null; - switch(constructor) { + switch (constructor) { case 0x64ff9fd5: result = new TL_messages_chats(); break; @@ -16202,13 +17425,13 @@ public class TLRPC { } } - public static class InputChannel extends TLObject { + public static abstract class InputChannel extends TLObject { public int channel_id; public long access_hash; public static InputChannel TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { InputChannel result = null; - switch(constructor) { + switch (constructor) { case 0xee8c1e86: result = new TL_inputChannelEmpty(); break; @@ -16282,9 +17505,7 @@ public class TLRPC { } } - public static class TL_messages_botResults extends TLObject { - public static int constructor = 0xccd3563d; - + public static abstract class messages_BotResults extends TLObject { public int flags; public boolean gallery; public long query_id; @@ -16292,19 +17513,31 @@ public class TLRPC { public TL_inlineBotSwitchPM switch_pm; public ArrayList results = new ArrayList<>(); public int cache_time; + public ArrayList users = new ArrayList<>(); - public static TL_messages_botResults TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { - if (TL_messages_botResults.constructor != constructor) { - if (exception) { - throw new RuntimeException(String.format("can't parse magic %x in TL_messages_botResults", constructor)); - } else { - return null; - } + public static messages_BotResults TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + messages_BotResults result = null; + switch (constructor) { + case 0xccd3563d: + result = new TL_messages_botResults_layer71(); + break; + case 0x947ca848: + result = new TL_messages_botResults(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in messages_BotResults", constructor)); + } + if (result != null) { + result.readParams(stream, exception); } - TL_messages_botResults result = new TL_messages_botResults(); - result.readParams(stream, exception); return result; } + } + + public static class TL_messages_botResults_layer71 extends TL_messages_botResults { + public static int constructor = 0xccd3563d; + public void readParams(AbstractSerializedData stream, boolean exception) { flags = stream.readInt32(exception); @@ -16355,6 +17588,80 @@ public class TLRPC { } } + public static class TL_messages_botResults extends messages_BotResults { + public static int constructor = 0x947ca848; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + gallery = (flags & 1) != 0; + query_id = stream.readInt64(exception); + if ((flags & 2) != 0) { + next_offset = stream.readString(exception); + } + if ((flags & 4) != 0) { + switch_pm = TL_inlineBotSwitchPM.TLdeserialize(stream, stream.readInt32(exception), exception); + } + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + BotInlineResult object = BotInlineResult.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + results.add(object); + } + cache_time = stream.readInt32(exception); + magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + User object = User.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + users.add(object); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = gallery ? (flags | 1) : (flags &~ 1); + stream.writeInt32(flags); + stream.writeInt64(query_id); + if ((flags & 2) != 0) { + stream.writeString(next_offset); + } + if ((flags & 4) != 0) { + switch_pm.serializeToStream(stream); + } + stream.writeInt32(0x1cb5c415); + int count = results.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + results.get(a).serializeToStream(stream); + } + stream.writeInt32(cache_time); + stream.writeInt32(0x1cb5c415); + count = users.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + users.get(a).serializeToStream(stream); + } + } + } + public static class TL_phoneConnection extends TLObject { public static int constructor = 0x9d4c17c0; @@ -16479,7 +17786,7 @@ public class TLRPC { } } - public static class updates_ChannelDifference extends TLObject { + public static abstract class updates_ChannelDifference extends TLObject { public int flags; public boolean isFinal; public int pts; @@ -16492,18 +17799,19 @@ public class TLRPC { public int read_inbox_max_id; public int read_outbox_max_id; public int unread_count; + public int unread_mentions_count; public ArrayList messages = new ArrayList<>(); public static updates_ChannelDifference TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { updates_ChannelDifference result = null; - switch(constructor) { + switch (constructor) { case 0x3e11affb: result = new TL_updates_channelDifferenceEmpty(); break; case 0x2064674e: result = new TL_updates_channelDifference(); break; - case 0x410dee07: + case 0x6a9d7b35: result = new TL_updates_channelDifferenceTooLong(); break; } @@ -16650,7 +17958,7 @@ public class TLRPC { } public static class TL_updates_channelDifferenceTooLong extends updates_ChannelDifference { - public static int constructor = 0x410dee07; + public static int constructor = 0x6a9d7b35; public void readParams(AbstractSerializedData stream, boolean exception) { @@ -16664,6 +17972,7 @@ public class TLRPC { read_inbox_max_id = stream.readInt32(exception); read_outbox_max_id = stream.readInt32(exception); unread_count = stream.readInt32(exception); + unread_mentions_count = stream.readInt32(exception); int magic = stream.readInt32(exception); if (magic != 0x1cb5c415) { if (exception) { @@ -16723,6 +18032,7 @@ public class TLRPC { stream.writeInt32(read_inbox_max_id); stream.writeInt32(read_outbox_max_id); stream.writeInt32(unread_count); + stream.writeInt32(unread_mentions_count); stream.writeInt32(0x1cb5c415); int count = messages.size(); stream.writeInt32(count); @@ -16744,14 +18054,14 @@ public class TLRPC { } } - public static class ChannelMessagesFilter extends TLObject { + public static abstract class ChannelMessagesFilter extends TLObject { public int flags; public boolean exclude_new_messages; public ArrayList ranges = new ArrayList<>(); public static ChannelMessagesFilter TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { ChannelMessagesFilter result = null; - switch(constructor) { + switch (constructor) { case 0x94d42ee7: result = new TL_channelMessagesFilterEmpty(); break; @@ -16769,6 +18079,97 @@ public class TLRPC { } } + public static class TL_help_recentMeUrls extends TLObject { + public static int constructor = 0xe0310d7; + + public ArrayList urls = new ArrayList<>(); + public ArrayList chats = new ArrayList<>(); + public ArrayList users = new ArrayList<>(); + + public static TL_help_recentMeUrls TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_help_recentMeUrls.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_help_recentMeUrls", constructor)); + } else { + return null; + } + } + TL_help_recentMeUrls result = new TL_help_recentMeUrls(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + RecentMeUrl object = RecentMeUrl.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + urls.add(object); + } + magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + Chat object = Chat.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + chats.add(object); + } + magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + User object = User.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + users.add(object); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(0x1cb5c415); + int count = urls.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + urls.get(a).serializeToStream(stream); + } + stream.writeInt32(0x1cb5c415); + count = chats.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + chats.get(a).serializeToStream(stream); + } + stream.writeInt32(0x1cb5c415); + count = users.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + users.get(a).serializeToStream(stream); + } + } + } + public static class TL_channelMessagesFilterEmpty extends ChannelMessagesFilter { public static int constructor = 0x94d42ee7; @@ -16887,6 +18288,37 @@ public class TLRPC { } } + public static class TL_inputSingleMedia extends TLObject { + public static int constructor = 0x5eaa7809; + + public InputMedia media; + public long random_id; + + public static TL_inputSingleMedia TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_inputSingleMedia.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_inputSingleMedia", constructor)); + } else { + return null; + } + } + TL_inputSingleMedia result = new TL_inputSingleMedia(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + media = InputMedia.TLdeserialize(stream, stream.readInt32(exception), exception); + random_id = stream.readInt64(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + media.serializeToStream(stream); + stream.writeInt64(random_id); + } + } + public static class TL_inputPhoneCall extends TLObject { public static int constructor = 0x1e36fded; @@ -16980,7 +18412,7 @@ public class TLRPC { } } - public static class ChannelParticipant extends TLObject { + public static abstract class ChannelParticipant extends TLObject { public int user_id; public int kicked_by; public int date; @@ -16994,7 +18426,7 @@ public class TLRPC { public static ChannelParticipant TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { ChannelParticipant result = null; - switch(constructor) { + switch (constructor) { case 0x222c1886: result = new TL_channelParticipantBanned(); break; @@ -17182,11 +18614,11 @@ public class TLRPC { } } - public static class InputStickeredMedia extends TLObject { + public static abstract class InputStickeredMedia extends TLObject { public static InputStickeredMedia TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { InputStickeredMedia result = null; - switch(constructor) { + switch (constructor) { case 0x438865b: result = new TL_inputStickeredMediaDocument(); break; @@ -17234,77 +18666,95 @@ public class TLRPC { } } - public static class TL_channels_channelParticipants extends TLObject { - public static int constructor = 0xf56ee2a8; + public static abstract class channels_ChannelParticipants extends TLObject { + public int count; + public ArrayList participants = new ArrayList<>(); + public ArrayList users = new ArrayList<>(); - public int count; - public ArrayList participants = new ArrayList<>(); - public ArrayList users = new ArrayList<>(); + public static channels_ChannelParticipants TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + channels_ChannelParticipants result = null; + switch (constructor) { + case 0xf56ee2a8: + result = new TL_channels_channelParticipants(); + break; + case 0xf0173fe9: + result = new TL_channels_channelParticipantsNotModified(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in channels_ChannelParticipants", constructor)); + } + if (result != null) { + result.readParams(stream, exception); + } + return result; + } + } - public static TL_channels_channelParticipants TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { - if (TL_channels_channelParticipants.constructor != constructor) { - if (exception) { - throw new RuntimeException(String.format("can't parse magic %x in TL_channels_channelParticipants", constructor)); - } else { - return null; - } - } - TL_channels_channelParticipants result = new TL_channels_channelParticipants(); - result.readParams(stream, exception); - return result; - } + public static class TL_channels_channelParticipants extends channels_ChannelParticipants { + public static int constructor = 0xf56ee2a8; - public void readParams(AbstractSerializedData stream, boolean exception) { - count = stream.readInt32(exception); - int magic = stream.readInt32(exception); - if (magic != 0x1cb5c415) { - if (exception) { - throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); - } - return; - } - int count = stream.readInt32(exception); - for (int a = 0; a < count; a++) { - ChannelParticipant object = ChannelParticipant.TLdeserialize(stream, stream.readInt32(exception), exception); - if (object == null) { - return; - } - participants.add(object); - } - magic = stream.readInt32(exception); - if (magic != 0x1cb5c415) { - if (exception) { - throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); - } - return; - } - count = stream.readInt32(exception); - for (int a = 0; a < count; a++) { - User object = User.TLdeserialize(stream, stream.readInt32(exception), exception); - if (object == null) { - return; - } - users.add(object); - } - } - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - stream.writeInt32(count); - stream.writeInt32(0x1cb5c415); - int count = participants.size(); - stream.writeInt32(count); - for (int a = 0; a < count; a++) { - participants.get(a).serializeToStream(stream); - } - stream.writeInt32(0x1cb5c415); - count = users.size(); - stream.writeInt32(count); - for (int a = 0; a < count; a++) { - users.get(a).serializeToStream(stream); - } - } - } + public void readParams(AbstractSerializedData stream, boolean exception) { + count = stream.readInt32(exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + ChannelParticipant object = ChannelParticipant.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + participants.add(object); + } + magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + User object = User.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + users.add(object); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(count); + stream.writeInt32(0x1cb5c415); + int count = participants.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + participants.get(a).serializeToStream(stream); + } + stream.writeInt32(0x1cb5c415); + count = users.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + users.get(a).serializeToStream(stream); + } + } + } + + public static class TL_channels_channelParticipantsNotModified extends channels_ChannelParticipants { + public static int constructor = 0xf0173fe9; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } public static class TL_contacts_found extends TLObject { public static int constructor = 0x1aa1f784; @@ -17397,7 +18847,7 @@ public class TLRPC { } } - public static class ChatParticipants extends TLObject { + public static abstract class ChatParticipants extends TLObject { public int flags; public int chat_id; public ChatParticipant self_participant; @@ -17407,7 +18857,7 @@ public class TLRPC { public static ChatParticipants TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { ChatParticipants result = null; - switch(constructor) { + switch (constructor) { case 0xfc900c2b: result = new TL_chatParticipantsForbidden(); break; @@ -17596,7 +19046,7 @@ public class TLRPC { } } - public static class DecryptedMessageMedia extends TLObject { + public static abstract class DecryptedMessageMedia extends TLObject { public int duration; public String mime_type; public int size; @@ -17627,7 +19077,7 @@ public class TLRPC { public static DecryptedMessageMedia TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { DecryptedMessageMedia result = null; - switch(constructor) { + switch (constructor) { case 0x57e0a9cb: result = new TL_decryptedMessageMediaAudio(); break; @@ -18095,14 +19545,14 @@ public class TLRPC { } } - public static class ChatParticipant extends TLObject { + public static abstract class ChatParticipant extends TLObject { public int user_id; public int inviter_id; public int date; public static ChatParticipant TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { ChatParticipant result = null; - switch(constructor) { + switch (constructor) { case 0xc8d7493e: result = new TL_chatParticipant(); break; @@ -18216,20 +19666,22 @@ public class TLRPC { } } - public static class ChannelAdminLogEventAction extends TLObject { + public static abstract class ChannelAdminLogEventAction extends TLObject { public Message message; public String prev_value; public Message prev_message; public Message new_message; public ChannelParticipant prev_participant; public ChannelParticipant new_participant; + public InputStickerSet prev_stickerset; + public InputStickerSet new_stickerset; public ChannelParticipant participant; public ChatPhoto prev_photo; public ChatPhoto new_photo; public static ChannelAdminLogEventAction TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { ChannelAdminLogEventAction result = null; - switch(constructor) { + switch (constructor) { case 0x1b7907ae: result = new TL_channelAdminLogEventActionToggleInvites(); break; @@ -18248,9 +19700,15 @@ public class TLRPC { case 0xd5676710: result = new TL_channelAdminLogEventActionParticipantToggleAdmin(); break; + case 0xb1c3caa7: + result = new TL_channelAdminLogEventActionChangeStickerSet(); + break; case 0xe6dfb825: result = new TL_channelAdminLogEventActionChangeTitle(); break; + case 0x5f5c95f1: + result = new TL_channelAdminLogEventActionTogglePreHistoryHidden(); + break; case 0x42e047bb: result = new TL_channelAdminLogEventActionDeleteMessage(); break; @@ -18376,6 +19834,22 @@ public class TLRPC { } } + public static class TL_channelAdminLogEventActionChangeStickerSet extends ChannelAdminLogEventAction { + public static int constructor = 0xb1c3caa7; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + prev_stickerset = InputStickerSet.TLdeserialize(stream, stream.readInt32(exception), exception); + new_stickerset = InputStickerSet.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + prev_stickerset.serializeToStream(stream); + new_stickerset.serializeToStream(stream); + } + } + public static class TL_channelAdminLogEventActionChangeTitle extends ChannelAdminLogEventAction { public static int constructor = 0xe6dfb825; @@ -18393,6 +19867,21 @@ public class TLRPC { } } + public static class TL_channelAdminLogEventActionTogglePreHistoryHidden extends ChannelAdminLogEventAction { + public static int constructor = 0x5f5c95f1; + + public boolean new_value; + + public void readParams(AbstractSerializedData stream, boolean exception) { + new_value = stream.readBool(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeBool(new_value); + } + } + public static class TL_channelAdminLogEventActionDeleteMessage extends ChannelAdminLogEventAction { public static int constructor = 0x42e047bb; @@ -18574,7 +20063,7 @@ public class TLRPC { } } - public static class Chat extends TLObject { + public static abstract class Chat extends TLObject { public int id; public String title; public int date; @@ -18611,7 +20100,7 @@ public class TLRPC { public static Chat TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { Chat result = null; - switch(constructor) { + switch (constructor) { case 0xfb0ccc41: result = new TL_chatForbidden_old(); break; @@ -18649,6 +20138,9 @@ public class TLRPC { result = new TL_chatEmpty(); break; case 0xcb44b1c: + result = new TL_channel_layer72(); + break; + case 0x450b7115: result = new TL_channel(); break; case 0xd91cdd54: @@ -19035,6 +20527,83 @@ public class TLRPC { } public static class TL_channel extends Chat { + public static int constructor = 0x450b7115; + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + creator = (flags & 1) != 0; + left = (flags & 4) != 0; + broadcast = (flags & 32) != 0; + verified = (flags & 128) != 0; + megagroup = (flags & 256) != 0; + restricted = (flags & 512) != 0; + democracy = (flags & 1024) != 0; + signatures = (flags & 2048) != 0; + min = (flags & 4096) != 0; + id = stream.readInt32(exception); + if ((flags & 8192) != 0) { + access_hash = stream.readInt64(exception); + } + title = stream.readString(exception); + if ((flags & 64) != 0) { + username = stream.readString(exception); + } + photo = ChatPhoto.TLdeserialize(stream, stream.readInt32(exception), exception); + date = stream.readInt32(exception); + version = stream.readInt32(exception); + if ((flags & 512) != 0) { + restriction_reason = stream.readString(exception); + } + if ((flags & 16384) != 0) { + admin_rights = TL_channelAdminRights.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 32768) != 0) { + banned_rights = TL_channelBannedRights.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 131072) != 0) { + participants_count = stream.readInt32(exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = creator ? (flags | 1) : (flags &~ 1); + flags = left ? (flags | 4) : (flags &~ 4); + flags = broadcast ? (flags | 32) : (flags &~ 32); + flags = verified ? (flags | 128) : (flags &~ 128); + flags = megagroup ? (flags | 256) : (flags &~ 256); + flags = restricted ? (flags | 512) : (flags &~ 512); + flags = democracy ? (flags | 1024) : (flags &~ 1024); + flags = signatures ? (flags | 2048) : (flags &~ 2048); + flags = min ? (flags | 4096) : (flags &~ 4096); + stream.writeInt32(flags); + stream.writeInt32(id); + if ((flags & 8192) != 0) { + stream.writeInt64(access_hash); + } + stream.writeString(title); + if ((flags & 64) != 0) { + stream.writeString(username); + } + photo.serializeToStream(stream); + stream.writeInt32(date); + stream.writeInt32(version); + if ((flags & 512) != 0) { + stream.writeString(restriction_reason); + } + if ((flags & 16384) != 0) { + admin_rights.serializeToStream(stream); + } + if ((flags & 32768) != 0) { + banned_rights.serializeToStream(stream); + } + if ((flags & 131072) != 0) { + stream.writeInt32(participants_count); + } + } + } + + public static class TL_channel_layer72 extends TL_channel { public static int constructor = 0xcb44b1c; public void readParams(AbstractSerializedData stream, boolean exception) { @@ -19149,7 +20718,7 @@ public class TLRPC { } } - public static class StickerSet extends TLObject { + public static abstract class StickerSet extends TLObject { public long id; public long access_hash; public String title; @@ -19164,7 +20733,7 @@ public class TLRPC { public static StickerSet TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { StickerSet result = null; - switch(constructor) { + switch (constructor) { case 0xa7a43b17: result = new TL_stickerSet_old(); break; @@ -19270,11 +20839,11 @@ public class TLRPC { } } - public static class storage_FileType extends TLObject { + public static abstract class storage_FileType extends TLObject { public static storage_FileType TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { storage_FileType result = null; - switch(constructor) { + switch (constructor) { case 0xaa963b05: result = new TL_storage_fileUnknown(); break; @@ -19406,11 +20975,11 @@ public class TLRPC { } } - public static class auth_CodeType extends TLObject { + public static abstract class auth_CodeType extends TLObject { public static auth_CodeType TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { auth_CodeType result = null; - switch(constructor) { + switch (constructor) { case 0x72a3158c: result = new TL_auth_codeTypeSms(); break; @@ -19458,13 +21027,13 @@ public class TLRPC { } } - public static class MessagesFilter extends TLObject { + public static abstract class MessagesFilter extends TLObject { public int flags; public boolean missed; public static MessagesFilter TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { MessagesFilter result = null; - switch(constructor) { + switch (constructor) { case 0xffc86587: result = new TL_inputMessagesFilterGif(); break; @@ -19489,9 +21058,18 @@ public class TLRPC { case 0xd95e73bb: result = new TL_inputMessagesFilterPhotoVideoDocuments(); break; + case 0xe7026d0d: + result = new TL_inputMessagesFilterGeo(); + break; + case 0xc1f8e69a: + result = new TL_inputMessagesFilterMyMentions(); + break; case 0x7a7c17a4: result = new TL_inputMessagesFilterRoundVoice(); break; + case 0xe062db83: + result = new TL_inputMessagesFilterContacts(); + break; case 0x50f5c392: result = new TL_inputMessagesFilterVoice(); break; @@ -19590,6 +21168,24 @@ public class TLRPC { } } + public static class TL_inputMessagesFilterGeo extends MessagesFilter { + public static int constructor = 0xe7026d0d; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + public static class TL_inputMessagesFilterMyMentions extends MessagesFilter { + public static int constructor = 0xc1f8e69a; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + public static class TL_inputMessagesFilterRoundVoice extends MessagesFilter { public static int constructor = 0x7a7c17a4; @@ -19608,6 +21204,15 @@ public class TLRPC { } } + public static class TL_inputMessagesFilterContacts extends MessagesFilter { + public static int constructor = 0xe062db83; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + public static class TL_inputMessagesFilterVideo extends MessagesFilter { public static int constructor = 0x9fc00e65; @@ -19786,109 +21391,166 @@ public class TLRPC { } } - public static class MessageFwdHeader extends TLObject { - public int flags; - public int from_id; - public int date; - public int channel_id; - public int channel_post; - public String post_author; + public static abstract class MessageFwdHeader extends TLObject { + public int flags; + public int from_id; + public int date; + public int channel_id; + public int channel_post; + public String post_author; + public Peer saved_from_peer; + public int saved_from_msg_id; - public static MessageFwdHeader TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { - MessageFwdHeader result = null; - switch(constructor) { - case 0xfadff4ac: - result = new TL_messageFwdHeader(); - break; - case 0xc786ddcb: - result = new TL_messageFwdHeader_layer68(); - break; - } - if (result == null && exception) { - throw new RuntimeException(String.format("can't parse magic %x in MessageFwdHeader", constructor)); - } - if (result != null) { - result.readParams(stream, exception); - } - return result; - } - } + public static MessageFwdHeader TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + MessageFwdHeader result = null; + switch (constructor) { + case 0xfadff4ac: + result = new TL_messageFwdHeader_layer72(); + break; + case 0x559ebe6d: + result = new TL_messageFwdHeader(); + break; + case 0xc786ddcb: + result = new TL_messageFwdHeader_layer68(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in MessageFwdHeader", constructor)); + } + if (result != null) { + result.readParams(stream, exception); + } + return result; + } + } - public static class TL_messageFwdHeader extends MessageFwdHeader { - public static int constructor = 0xfadff4ac; + public static class TL_messageFwdHeader_layer72 extends TL_messageFwdHeader { + public static int constructor = 0xfadff4ac; - public void readParams(AbstractSerializedData stream, boolean exception) { - flags = stream.readInt32(exception); - if ((flags & 1) != 0) { - from_id = stream.readInt32(exception); - } - date = stream.readInt32(exception); - if ((flags & 2) != 0) { - channel_id = stream.readInt32(exception); - } - if ((flags & 4) != 0) { - channel_post = stream.readInt32(exception); - } - if ((flags & 8) != 0) { - post_author = stream.readString(exception); - } - } + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + if ((flags & 1) != 0) { + from_id = stream.readInt32(exception); + } + date = stream.readInt32(exception); + if ((flags & 2) != 0) { + channel_id = stream.readInt32(exception); + } + if ((flags & 4) != 0) { + channel_post = stream.readInt32(exception); + } + if ((flags & 8) != 0) { + post_author = stream.readString(exception); + } + } - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - stream.writeInt32(flags); - if ((flags & 1) != 0) { - stream.writeInt32(from_id); - } - stream.writeInt32(date); - if ((flags & 2) != 0) { - stream.writeInt32(channel_id); - } - if ((flags & 4) != 0) { - stream.writeInt32(channel_post); - } - if ((flags & 8) != 0) { - stream.writeString(post_author); - } - } - } + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + if ((flags & 1) != 0) { + stream.writeInt32(from_id); + } + stream.writeInt32(date); + if ((flags & 2) != 0) { + stream.writeInt32(channel_id); + } + if ((flags & 4) != 0) { + stream.writeInt32(channel_post); + } + if ((flags & 8) != 0) { + stream.writeString(post_author); + } + } + } - public static class TL_messageFwdHeader_layer68 extends TL_messageFwdHeader { - public static int constructor = 0xc786ddcb; + public static class TL_messageFwdHeader extends MessageFwdHeader { + public static int constructor = 0x559ebe6d; - public void readParams(AbstractSerializedData stream, boolean exception) { - flags = stream.readInt32(exception); - if ((flags & 1) != 0) { - from_id = stream.readInt32(exception); - } - date = stream.readInt32(exception); - if ((flags & 2) != 0) { - channel_id = stream.readInt32(exception); - } - if ((flags & 4) != 0) { - channel_post = stream.readInt32(exception); - } - } + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + if ((flags & 1) != 0) { + from_id = stream.readInt32(exception); + } + date = stream.readInt32(exception); + if ((flags & 2) != 0) { + channel_id = stream.readInt32(exception); + } + if ((flags & 4) != 0) { + channel_post = stream.readInt32(exception); + } + if ((flags & 8) != 0) { + post_author = stream.readString(exception); + } + if ((flags & 16) != 0) { + saved_from_peer = Peer.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 16) != 0) { + saved_from_msg_id = stream.readInt32(exception); + } + } - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - stream.writeInt32(flags); - if ((flags & 1) != 0) { - stream.writeInt32(from_id); - } - stream.writeInt32(date); - if ((flags & 2) != 0) { - stream.writeInt32(channel_id); - } - if ((flags & 4) != 0) { - stream.writeInt32(channel_post); - } - } - } + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + if ((flags & 1) != 0) { + stream.writeInt32(from_id); + } + stream.writeInt32(date); + if ((flags & 2) != 0) { + stream.writeInt32(channel_id); + } + if ((flags & 4) != 0) { + stream.writeInt32(channel_post); + } + if ((flags & 8) != 0) { + stream.writeString(post_author); + } + if ((flags & 16) != 0) { + saved_from_peer.serializeToStream(stream); + } + if ((flags & 16) != 0) { + stream.writeInt32(saved_from_msg_id); + } + } + } - public static class FileLocation extends TLObject { + public static class TL_messageFwdHeader_layer68 extends TL_messageFwdHeader { + public static int constructor = 0xc786ddcb; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + if ((flags & 1) != 0) { + from_id = stream.readInt32(exception); + } + date = stream.readInt32(exception); + if ((flags & 2) != 0) { + channel_id = stream.readInt32(exception); + } + if ((flags & 4) != 0) { + channel_post = stream.readInt32(exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + if ((flags & 1) != 0) { + stream.writeInt32(from_id); + } + stream.writeInt32(date); + if ((flags & 2) != 0) { + stream.writeInt32(channel_id); + } + if ((flags & 4) != 0) { + stream.writeInt32(channel_post); + } + } + } + + public static abstract class FileLocation extends TLObject { public int dc_id; public long volume_id; public int local_id; @@ -20012,13 +21674,13 @@ public class TLRPC { } } - public static class messages_SavedGifs extends TLObject { + public static abstract class messages_SavedGifs extends TLObject { public int hash; public ArrayList gifs = new ArrayList<>(); public static messages_SavedGifs TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { messages_SavedGifs result = null; - switch(constructor) { + switch (constructor) { case 0xe8025ca2: result = new TL_messages_savedGifsNotModified(); break; @@ -20080,7 +21742,7 @@ public class TLRPC { } } - public static class PhotoSize extends TLObject { + public static abstract class PhotoSize extends TLObject { public String type; public FileLocation location; public int w; @@ -20090,7 +21752,7 @@ public class TLRPC { public static PhotoSize TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { PhotoSize result = null; - switch(constructor) { + switch (constructor) { case 0x77bfb61b: result = new TL_photoSize(); break; @@ -20138,21 +21800,7 @@ public class TLRPC { public void readParams(AbstractSerializedData stream, boolean exception) { - int startReadPosiition = stream.getPosition(); //TODO remove this hack after some time - try { - type = stream.readString(true); - if (type.length() > 1 || !type.equals("") && !type.equals("s") && !type.equals("x") && !type.equals("m") && !type.equals("y") && !type.equals("w")) { - type = "s"; - if (stream instanceof NativeByteBuffer) { - ((NativeByteBuffer) stream).position(startReadPosiition); - } - } - } catch (Exception e) { - type = "s"; - if (stream instanceof NativeByteBuffer) { - ((NativeByteBuffer) stream).position(startReadPosiition); - } - } + type = stream.readString(exception); } public void serializeToStream(AbstractSerializedData stream) { @@ -20211,12 +21859,12 @@ public class TLRPC { } } - public static class ExportedChatInvite extends TLObject { + public static abstract class ExportedChatInvite extends TLObject { public String link; public static ExportedChatInvite TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { ExportedChatInvite result = null; - switch(constructor) { + switch (constructor) { case 0xfc2e05bc: result = new TL_chatInviteExported(); break; @@ -20257,7 +21905,7 @@ public class TLRPC { } } - public static class InputFile extends TLObject { + public static abstract class InputFile extends TLObject { public long id; public int parts; public String name; @@ -20265,7 +21913,7 @@ public class TLRPC { public static InputFile TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { InputFile result = null; - switch(constructor) { + switch (constructor) { case 0xfa4f0bb5: result = new TL_inputFileBig(); break; @@ -20431,7 +22079,7 @@ public class TLRPC { } } - public static class Updates extends TLObject { + public static abstract class Updates extends TLObject { public ArrayList updates = new ArrayList<>(); public ArrayList users = new ArrayList<>(); public ArrayList chats = new ArrayList<>(); @@ -20459,7 +22107,7 @@ public class TLRPC { public static Updates TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { Updates result = null; - switch(constructor) { + switch (constructor) { case 0x74ae4240: result = new TL_updates(); break; @@ -20742,7 +22390,7 @@ public class TLRPC { public static int constructor = 0xe317af7e; } - public static class WallPaper extends TLObject { + public static abstract class WallPaper extends TLObject { public int id; public String title; public ArrayList sizes = new ArrayList<>(); @@ -20751,7 +22399,7 @@ public class TLRPC { public static WallPaper TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { WallPaper result = null; - switch(constructor) { + switch (constructor) { case 0xccb03657: result = new TL_wallPaper(); break; @@ -20936,13 +22584,13 @@ public class TLRPC { } } - public static class InputChatPhoto extends TLObject { + public static abstract class InputChatPhoto extends TLObject { public InputPhoto id; public InputFile file; public static InputChatPhoto TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { InputChatPhoto result = null; - switch(constructor) { + switch (constructor) { case 0x8953ad37: result = new TL_inputChatPhoto(); break; @@ -21072,13 +22720,13 @@ public class TLRPC { } } - public static class InputPhoto extends TLObject { + public static abstract class InputPhoto extends TLObject { public long id; public long access_hash; public static InputPhoto TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { InputPhoto result = null; - switch(constructor) { + switch (constructor) { case 0x1cd7bf0d: result = new TL_inputPhotoEmpty(); break; @@ -21152,13 +22800,13 @@ public class TLRPC { } } - public static class messages_RecentStickers extends TLObject { + public static abstract class messages_RecentStickers extends TLObject { public int hash; public ArrayList stickers = new ArrayList<>(); public static messages_RecentStickers TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { messages_RecentStickers result = null; - switch(constructor) { + switch (constructor) { case 0x5ce20970: result = new TL_messages_recentStickers(); break; @@ -21248,13 +22896,13 @@ public class TLRPC { } } - public static class messages_Stickers extends TLObject { + public static abstract class messages_Stickers extends TLObject { public String hash; public ArrayList stickers = new ArrayList<>(); public static messages_Stickers TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { messages_Stickers result = null; - switch(constructor) { + switch (constructor) { case 0xf1749a22: result = new TL_messages_stickersNotModified(); break; @@ -21316,7 +22964,7 @@ public class TLRPC { } } - public static class InputPeer extends TLObject { + public static abstract class InputPeer extends TLObject { public int user_id; public long access_hash; public int chat_id; @@ -21324,7 +22972,7 @@ public class TLRPC { public static InputPeer TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { InputPeer result = null; - switch(constructor) { + switch (constructor) { case 0x7b8e7de6: result = new TL_inputPeerUser(); break; @@ -21563,6 +23211,124 @@ public class TLRPC { } } + public static abstract class GroupCallParticipant extends TLObject { + public int user_id; + public byte[] member_tag_hash; + public byte[] streams; + public int flags; + public boolean readonly; + public int date; + public int inviter_id; + public TL_inputPhoneCall phone_call; + + public static GroupCallParticipant TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + GroupCallParticipant result = null; + switch (constructor) { + case 0x419b0df2: + result = new TL_groupCallParticipantLeft(); + break; + case 0x4f0b39b8: + result = new TL_groupCallParticipantAdmin(); + break; + case 0x589db397: + result = new TL_groupCallParticipant(); + break; + case 0x377496f0: + result = new TL_groupCallParticipantInvited(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in GroupCallParticipant", constructor)); + } + if (result != null) { + result.readParams(stream, exception); + } + return result; + } + } + + public static class TL_groupCallParticipantLeft extends GroupCallParticipant { + public static int constructor = 0x419b0df2; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + user_id = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(user_id); + } + } + + public static class TL_groupCallParticipantAdmin extends GroupCallParticipant { + public static int constructor = 0x4f0b39b8; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + user_id = stream.readInt32(exception); + member_tag_hash = stream.readByteArray(exception); + streams = stream.readByteArray(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(user_id); + stream.writeByteArray(member_tag_hash); + stream.writeByteArray(streams); + } + } + + public static class TL_groupCallParticipant extends GroupCallParticipant { + public static int constructor = 0x589db397; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + readonly = (flags & 1) != 0; + user_id = stream.readInt32(exception); + date = stream.readInt32(exception); + member_tag_hash = stream.readByteArray(exception); + streams = stream.readByteArray(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = readonly ? (flags | 1) : (flags &~ 1); + stream.writeInt32(flags); + stream.writeInt32(user_id); + stream.writeInt32(date); + stream.writeByteArray(member_tag_hash); + stream.writeByteArray(streams); + } + } + + public static class TL_groupCallParticipantInvited extends GroupCallParticipant { + public static int constructor = 0x377496f0; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + user_id = stream.readInt32(exception); + inviter_id = stream.readInt32(exception); + date = stream.readInt32(exception); + if ((flags & 1) != 0) { + phone_call = TL_inputPhoneCall.TLdeserialize(stream, stream.readInt32(exception), exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + stream.writeInt32(user_id); + stream.writeInt32(inviter_id); + stream.writeInt32(date); + if ((flags & 1) != 0) { + phone_call.serializeToStream(stream); + } + } + } + public static class TL_messages_peerDialogs extends TLObject { public static int constructor = 0x3371c354; @@ -21710,7 +23476,7 @@ public class TLRPC { } } - public static class messages_Dialogs extends TLObject { + public static abstract class messages_Dialogs extends TLObject { public ArrayList dialogs = new ArrayList<>(); public ArrayList messages = new ArrayList<>(); public ArrayList chats = new ArrayList<>(); @@ -21719,7 +23485,7 @@ public class TLRPC { public static messages_Dialogs TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { messages_Dialogs result = null; - switch(constructor) { + switch (constructor) { case 0x15ba6c40: result = new TL_messages_dialogs(); break; @@ -22198,27 +23964,6 @@ public class TLRPC { } } - public static class TL_auth_bindTempAuthKey extends TLObject { - public static int constructor = 0xcdd42a05; - - public long perm_auth_key_id; - public long nonce; - public int expires_at; - public byte[] encrypted_message; - - public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return Bool.TLdeserialize(stream, constructor, exception); - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - stream.writeInt64(perm_auth_key_id); - stream.writeInt64(nonce); - stream.writeInt32(expires_at); - stream.writeByteArray(encrypted_message); - } - } - public static class TL_account_registerDevice extends TLObject { public static int constructor = 0x637ea878; @@ -22446,25 +24191,24 @@ public class TLRPC { } public static class TL_contacts_getContacts extends TLObject { - public static int constructor = 0x22c6aa08; + public static int constructor = 0xc023849f; - public String hash; + public int hash; public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { return contacts_Contacts.TLdeserialize(stream, constructor, exception); } - public void serializeToStream(AbstractSerializedData stream) { + public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeString(hash); + stream.writeInt32(hash); } } public static class TL_contacts_importContacts extends TLObject { - public static int constructor = 0xda30b32d; + public static int constructor = 0x2c800be5; public ArrayList contacts = new ArrayList<>(); - public boolean replace; public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { return TL_contacts_importedContacts.TLdeserialize(stream, constructor, exception); @@ -22478,7 +24222,6 @@ public class TLRPC { for (int a = 0; a < count; a++) { contacts.get(a).serializeToStream(stream); } - stream.writeBool(replace); } } @@ -22602,6 +24345,19 @@ public class TLRPC { } } + public static class TL_contacts_resetSaved extends TLObject { + public static int constructor = 0x879537f1; + + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Bool.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + public static class TL_messages_getMessages extends TLObject { public static int constructor = 0x4222fa74; @@ -22675,7 +24431,7 @@ public class TLRPC { } public static class TL_messages_search extends TLObject { - public static int constructor = 0xf288a275; + public static int constructor = 0x39e9ea0; public int flags; public InputPeer peer; @@ -22684,9 +24440,11 @@ public class TLRPC { public MessagesFilter filter; public int min_date; public int max_date; - public int offset; - public int max_id; + public int offset_id; + public int add_offset; public int limit; + public int max_id; + public int min_id; public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { return messages_Messages.TLdeserialize(stream, constructor, exception); @@ -22703,9 +24461,11 @@ public class TLRPC { filter.serializeToStream(stream); stream.writeInt32(min_date); stream.writeInt32(max_date); - stream.writeInt32(offset); - stream.writeInt32(max_id); + stream.writeInt32(offset_id); + stream.writeInt32(add_offset); stream.writeInt32(limit); + stream.writeInt32(max_id); + stream.writeInt32(min_id); } } @@ -22747,6 +24507,23 @@ public class TLRPC { } } + public static class TL_channels_togglePreHistoryHidden extends TLObject { + public static int constructor = 0xeabbb94c; + + public InputChannel channel; + public boolean enabled; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Updates.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + channel.serializeToStream(stream); + stream.writeBool(enabled); + } + } + public static class TL_messages_toggleChatAdmins extends TLObject { public static int constructor = 0xec8bd9e1; @@ -23003,6 +24780,7 @@ public class TLRPC { public boolean silent; public boolean background; public boolean with_my_score; + public boolean grouped; public InputPeer from_peer; public ArrayList id = new ArrayList<>(); public ArrayList random_id = new ArrayList<>(); @@ -23017,6 +24795,7 @@ public class TLRPC { flags = silent ? (flags | 32) : (flags &~ 32); flags = background ? (flags | 64) : (flags &~ 64); flags = with_my_score ? (flags | 256) : (flags &~ 256); + flags = grouped ? (flags | 512) : (flags &~ 512); stream.writeInt32(flags); from_peer.serializeToStream(stream); stream.writeInt32(0x1cb5c415); @@ -24373,27 +26152,7 @@ public class TLRPC { } } - public static class TL_auth_dropTempAuthKeys extends TLObject { - public static int constructor = 0x8e48a188; - - public ArrayList except_auth_keys = new ArrayList<>(); - - public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return Bool.TLdeserialize(stream, constructor, exception); - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - stream.writeInt32(0x1cb5c415); - int count = except_auth_keys.size(); - stream.writeInt32(count); - for (int a = 0; a < count; a++) { - stream.writeInt64(except_auth_keys.get(a)); - } - } - } - - public static class TL_messages_exportChatInvite extends TLObject { + public static class TL_messages_exportChatInvite extends TLObject { public static int constructor = 0x7d885289; public int chat_id; @@ -24595,7 +26354,7 @@ public class TLRPC { public String offset; public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return TL_messages_botResults.TLdeserialize(stream, constructor, exception); + return messages_BotResults.TLdeserialize(stream, constructor, exception); } public void serializeToStream(AbstractSerializedData stream) { @@ -24662,15 +26421,17 @@ public class TLRPC { } public static class TL_messages_editMessage extends TLObject { - public static int constructor = 0xce91e4ca; + public static int constructor = 0x5d1b8dd; public int flags; public boolean no_webpage; + public boolean stop_geo_live; public InputPeer peer; public int id; public String message; public ReplyMarkup reply_markup; public ArrayList entities = new ArrayList<>(); + public InputGeoPoint geo_point; public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { return Updates.TLdeserialize(stream, constructor, exception); @@ -24679,6 +26440,7 @@ public class TLRPC { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); flags = no_webpage ? (flags | 2) : (flags &~ 2); + flags = stop_geo_live ? (flags | 4096) : (flags &~ 4096); stream.writeInt32(flags); peer.serializeToStream(stream); stream.writeInt32(id); @@ -24696,6 +26458,9 @@ public class TLRPC { entities.get(a).serializeToStream(stream); } } + if ((flags & 8192) != 0) { + geo_point.serializeToStream(stream); + } } } @@ -24773,42 +26538,6 @@ public class TLRPC { } } - public static class TL_messages_editInlineBotMessage extends TLObject { - public static int constructor = 0x130c2c85; - - public int flags; - public boolean no_webpage; - public TL_inputBotInlineMessageID id; - public String message; - public ReplyMarkup reply_markup; - public ArrayList entities = new ArrayList<>(); - - public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return Bool.TLdeserialize(stream, constructor, exception); - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - flags = no_webpage ? (flags | 2) : (flags &~ 2); - stream.writeInt32(flags); - id.serializeToStream(stream); - if ((flags & 2048) != 0) { - stream.writeString(message); - } - if ((flags & 4) != 0) { - reply_markup.serializeToStream(stream); - } - if ((flags & 8) != 0) { - stream.writeInt32(0x1cb5c415); - int count = entities.size(); - stream.writeInt32(count); - for (int a = 0; a < count; a++) { - entities.get(a).serializeToStream(stream); - } - } - } - } - public static class TL_messages_saveDraft extends TLObject { public static int constructor = 0xbc39e14b; @@ -25208,6 +26937,23 @@ public class TLRPC { } } + public static class TL_messages_uploadMedia extends TLObject { + public static int constructor = 0x519bc2b1; + + public InputPeer peer; + public InputMedia media; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return MessageMedia.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + peer.serializeToStream(stream); + media.serializeToStream(stream); + } + } + public static class TL_messages_sendScreenshotNotification extends TLObject { public static int constructor = 0xc97df020; @@ -25227,6 +26973,129 @@ public class TLRPC { } } + public static class TL_messages_getFavedStickers extends TLObject { + public static int constructor = 0x21ce0b0e; + + public int hash; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return messages_FavedStickers.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(hash); + } + } + + public static class TL_messages_faveSticker extends TLObject { + public static int constructor = 0xb9ffc55b; + + public InputDocument id; + public boolean unfave; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Bool.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + id.serializeToStream(stream); + stream.writeBool(unfave); + } + } + + public static class TL_messages_getUnreadMentions extends TLObject { + public static int constructor = 0x46578472; + + public InputPeer peer; + public int offset_id; + public int add_offset; + public int limit; + public int max_id; + public int min_id; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return messages_Messages.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + peer.serializeToStream(stream); + stream.writeInt32(offset_id); + stream.writeInt32(add_offset); + stream.writeInt32(limit); + stream.writeInt32(max_id); + stream.writeInt32(min_id); + } + } + + public static class TL_messages_readMentions extends TLObject { + public static int constructor = 0xf0189d3; + + public InputPeer peer; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return TL_messages_affectedHistory.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + peer.serializeToStream(stream); + } + } + + public static class TL_messages_getRecentLocations extends TLObject { + public static int constructor = 0x249431e2; + + public InputPeer peer; + public int limit; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return messages_Messages.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + peer.serializeToStream(stream); + stream.writeInt32(limit); + } + } + + public static class TL_messages_sendMultiMedia extends TLObject { + public static int constructor = 0x2095512f; + + public int flags; + public boolean silent; + public boolean background; + public boolean clear_draft; + public InputPeer peer; + public int reply_to_msg_id; + public ArrayList multi_media = new ArrayList<>(); + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Updates.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = silent ? (flags | 32) : (flags &~ 32); + flags = background ? (flags | 64) : (flags &~ 64); + flags = clear_draft ? (flags | 128) : (flags &~ 128); + stream.writeInt32(flags); + peer.serializeToStream(stream); + if ((flags & 1) != 0) { + stream.writeInt32(reply_to_msg_id); + } + stream.writeInt32(0x1cb5c415); + int count = multi_media.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + multi_media.get(a).serializeToStream(stream); + } + } + } + public static class TL_help_getAppChangelog extends TLObject { public static int constructor = 0x9010ef6f; @@ -25242,6 +27111,23 @@ public class TLRPC { } } + public static class TL_messages_uploadEncryptedFile extends TLObject { + public static int constructor = 0x5057c497; + + public TL_inputEncryptedChat peer; + public InputEncryptedFile file; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return EncryptedFile.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + peer.serializeToStream(stream); + file.serializeToStream(stream); + } + } + public static class TL_help_getTermsOfService extends TLObject { public static int constructor = 0x350170f3; @@ -25315,6 +27201,21 @@ public class TLRPC { } } + public static class TL_help_getRecentMeUrls extends TLObject { + public static int constructor = 0x3dc0f114; + + public String referer; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return TL_help_recentMeUrls.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(referer); + } + } + public static class TL_channels_readHistory extends TLObject { public static int constructor = 0xcc104937; @@ -25445,26 +27346,28 @@ public class TLRPC { } } - public static class TL_channels_getParticipants extends TLObject { - public static int constructor = 0x24d98f92; + public static class TL_channels_getParticipants extends TLObject { + public static int constructor = 0x123e05e9; - public InputChannel channel; - public ChannelParticipantsFilter filter; - public int offset; - public int limit; + public InputChannel channel; + public ChannelParticipantsFilter filter; + public int offset; + public int limit; + public int hash; - public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return TL_channels_channelParticipants.TLdeserialize(stream, constructor, exception); - } + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return channels_ChannelParticipants.TLdeserialize(stream, constructor, exception); + } - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - channel.serializeToStream(stream); - filter.serializeToStream(stream); - stream.writeInt32(offset); - stream.writeInt32(limit); - } - } + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + channel.serializeToStream(stream); + filter.serializeToStream(stream); + stream.writeInt32(offset); + stream.writeInt32(limit); + stream.writeInt32(hash); + } + } public static class TL_channels_getParticipant extends TLObject { public static int constructor = 0x546dd7a6; @@ -25812,19 +27715,6 @@ public class TLRPC { } } - public static class TL_phone_getCallConfig extends TLObject { - public static int constructor = 0x55451fa9; - - - public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return TL_dataJSON.TLdeserialize(stream, constructor, exception); - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - } - } - public static class TL_channels_editBanned extends TLObject { public static int constructor = 0xbfd915cd; @@ -25882,6 +27772,75 @@ public class TLRPC { } } + public static class TL_channels_setStickers extends TLObject { + public static int constructor = 0xea8ca4f9; + + public InputChannel channel; + public InputStickerSet stickerset; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Bool.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + channel.serializeToStream(stream); + stickerset.serializeToStream(stream); + } + } + + public static class TL_channels_readMessageContents extends TLObject { + public static int constructor = 0xeab5dc38; + + public InputChannel channel; + public ArrayList id = new ArrayList<>(); + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Bool.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + channel.serializeToStream(stream); + stream.writeInt32(0x1cb5c415); + int count = id.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + stream.writeInt32(id.get(a)); + } + } + } + + public static class TL_channels_deleteHistory extends TLObject { + public static int constructor = 0xaf369d42; + + public InputChannel channel; + public int max_id; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Bool.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + channel.serializeToStream(stream); + stream.writeInt32(max_id); + } + } + + public static class TL_phone_getCallConfig extends TLObject { + public static int constructor = 0x55451fa9; + + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return TL_dataJSON.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + public static class TL_phone_requestCall extends TLObject { public static int constructor = 0x5b95b3d4; @@ -26015,6 +27974,192 @@ public class TLRPC { } } + public static class TL_phone_createGroupCall extends TLObject { + public static int constructor = 0x8504e5b6; + + public int flags; + public InputChannel channel; + public int random_id; + public TL_phoneCallProtocol protocol; + public byte[] encryption_key; + public long key_fingerprint; + public byte[] streams; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Updates.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + channel.serializeToStream(stream); + stream.writeInt32(random_id); + protocol.serializeToStream(stream); + if ((flags & 1) != 0) { + stream.writeByteArray(encryption_key); + } + stream.writeInt64(key_fingerprint); + stream.writeByteArray(streams); + } + } + + public static class TL_phone_joinGroupCall extends TLObject { + public static int constructor = 0x9db32d7; + + public TL_inputGroupCall call; + public byte[] streams; + public long key_fingerprint; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Updates.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + call.serializeToStream(stream); + stream.writeByteArray(streams); + stream.writeInt64(key_fingerprint); + } + } + + public static class TL_phone_leaveGroupCall extends TLObject { + public static int constructor = 0x60e98e5f; + + public TL_inputGroupCall call; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Updates.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + call.serializeToStream(stream); + } + } + + public static class TL_phone_editGroupCallMember extends TLObject { + public static int constructor = 0x46659be4; + + public int flags; + public boolean readonly; + public boolean kicked; + public TL_inputGroupCall call; + public InputUser user_id; + public byte[] streams; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Updates.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = readonly ? (flags | 1) : (flags &~ 1); + flags = kicked ? (flags | 2) : (flags &~ 2); + stream.writeInt32(flags); + call.serializeToStream(stream); + user_id.serializeToStream(stream); + if ((flags & 4) != 0) { + stream.writeByteArray(streams); + } + } + } + + public static class TL_phone_inviteGroupCallMembers extends TLObject { + public static int constructor = 0xcc92a6dc; + + public int flags; + public boolean uninvite; + public TL_inputGroupCall call; + public ArrayList users = new ArrayList<>(); + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Bool.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = uninvite ? (flags | 1) : (flags &~ 1); + stream.writeInt32(flags); + call.serializeToStream(stream); + stream.writeInt32(0x1cb5c415); + int count = users.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + users.get(a).serializeToStream(stream); + } + } + } + + public static class TL_phone_discardGroupCall extends TLObject { + public static int constructor = 0x7a777135; + + public TL_inputGroupCall call; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Updates.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + call.serializeToStream(stream); + } + } + + public static class TL_phone_getGroupCall extends TLObject { + public static int constructor = 0xc7cb017; + + public TL_inputGroupCall call; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return TL_phone_groupCall.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + call.serializeToStream(stream); + } + } + + public static class TL_phone_upgradePhoneCall extends TLObject { + public static int constructor = 0x98e3cdba; + + public int flags; + public TL_inputPhoneCall peer; + public byte[] encryption_key; + public long key_fingerprint; + public byte[] streams; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return TL_phone_groupCall.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + peer.serializeToStream(stream); + if ((flags & 1) != 0) { + stream.writeByteArray(encryption_key); + } + stream.writeInt64(key_fingerprint); + stream.writeByteArray(streams); + } + } + + public static class TL_phone_getCall extends TLObject { + public static int constructor = 0x8adb4f79; + + public TL_inputPhoneCall peer; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return TL_phone_phoneCall.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + peer.serializeToStream(stream); + } + } + public static class TL_payments_getPaymentForm extends TLObject { public static int constructor = 0x99f09745; @@ -26211,7 +28356,7 @@ public class TLRPC { //manually created //RichText start - public static class RichText extends TLObject { + public static abstract class RichText extends TLObject { public String url; public long webpage_id; public String email; @@ -26220,7 +28365,7 @@ public class TLRPC { public static RichText TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { RichText result = null; - switch(constructor) { + switch (constructor) { case 0xdc3d824f: result = new TL_textEmpty(); break; @@ -26264,7 +28409,7 @@ public class TLRPC { //RichText end //MessageMedia start - public static class MessageMedia extends TLObject { + public static abstract class MessageMedia extends TLObject { public byte[] bytes; public Audio audio_unused; public int flags; @@ -26289,12 +28434,14 @@ public class TLRPC { public String last_name; public int user_id; public WebPage webpage; + public String venue_type; public boolean test; + public int period; public int ttl_seconds; public static MessageMedia TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { MessageMedia result = null; - switch(constructor) { + switch (constructor) { case 0x29632a36: result = new TL_messageMediaUnsupported_old(); break; @@ -26314,6 +28461,12 @@ public class TLRPC { result = new TL_messageMediaEmpty(); break; case 0x7912b71f: + result = new TL_messageMediaVenue_layer71(); + break; + case 0x7c3c2609: + result = new TL_messageMediaGeoLive(); + break; + case 0x2ec0533f: result = new TL_messageMediaVenue(); break; case 0xa2d24290: @@ -26444,7 +28597,7 @@ public class TLRPC { //PageBlock end //EncryptedChat start - public static class EncryptedChat extends TLObject { + public static abstract class EncryptedChat extends TLObject { public int id; public long access_hash; public int date; @@ -26462,6 +28615,7 @@ public class TLRPC { public int seq_in; //custom public int seq_out; //custom public int in_seq_no; //custom + public int mtproto_seq; //custom public byte[] key_hash; //custom public short key_use_count_in; //custom public short key_use_count_out; //custom @@ -26472,7 +28626,7 @@ public class TLRPC { public static EncryptedChat TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { EncryptedChat result = null; - switch(constructor) { + switch (constructor) { case 0xfda9a7b7: result = new TL_encryptedChatRequested_old(); break; @@ -26507,7 +28661,7 @@ public class TLRPC { //EncryptedChat end //Message start - public static class Message extends TLObject { + public static abstract class Message extends TLObject { public int id; public int from_id; public Peer to_id; @@ -26532,6 +28686,7 @@ public class TLRPC { public MessageFwdHeader fwd_from; public int via_bot_id; public String post_author; + public long grouped_id; public int send_state = 0; //custom public int fwd_msg_id = 0; //custom public String attachPath = ""; //custom @@ -26549,7 +28704,7 @@ public class TLRPC { public static Message TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { Message result = null; - switch(constructor) { + switch (constructor) { case 0x1d86f70e: result = new TL_messageService_old2(); break; @@ -26559,11 +28714,14 @@ public class TLRPC { case 0xc3060325: result = new TL_message_old4(); break; - case 0x555555f9: + case 0x555555fa: result = new TL_message_secret(); break; + case 0x555555f9: + result = new TL_message_secret_layer72(); + break; case 0x90dddc11: - result = new TL_message(); + result = new TL_message_layer72(); break; case 0xc09be45f: result = new TL_message_layer68(); @@ -26583,6 +28741,9 @@ public class TLRPC { case 0x2bebfa86: result = new TL_message_old6(); break; + case 0x44f9b43d: + result = new TL_message(); + break; case 0xa367e716: result = new TL_messageForwarded_old2(); //custom break; @@ -26666,7 +28827,7 @@ public class TLRPC { } } - public static class TL_message extends Message { + public static class TL_message_layer72 extends TL_message { public static int constructor = 0x90dddc11; public void readParams(AbstractSerializedData stream, boolean exception) { @@ -27287,6 +29448,155 @@ public class TLRPC { } } + public static class TL_message extends Message { + public static int constructor = 0x44f9b43d; + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + out = (flags & 2) != 0; + mentioned = (flags & 16) != 0; + media_unread = (flags & 32) != 0; + silent = (flags & 8192) != 0; + post = (flags & 16384) != 0; + id = stream.readInt32(exception); + if ((flags & 256) != 0) { + from_id = stream.readInt32(exception); + } + to_id = Peer.TLdeserialize(stream, stream.readInt32(exception), exception); + if ((flags & 4) != 0) { + fwd_from = MessageFwdHeader.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 2048) != 0) { + via_bot_id = stream.readInt32(exception); + } + if ((flags & 8) != 0) { + reply_to_msg_id = stream.readInt32(exception); + } + date = stream.readInt32(exception); + message = stream.readString(exception); + if ((flags & 512) != 0) { + media = MessageMedia.TLdeserialize(stream, stream.readInt32(exception), exception); + if (media != null) { + ttl = media.ttl_seconds; //custom + } + } + if ((flags & 64) != 0) { + reply_markup = ReplyMarkup.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 128) != 0) { + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + MessageEntity object = MessageEntity.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + entities.add(object); + } + } + if ((flags & 1024) != 0) { + views = stream.readInt32(exception); + } + if ((flags & 32768) != 0) { + edit_date = stream.readInt32(exception); + } + if ((flags & 65536) != 0) { + post_author = stream.readString(exception); + } + if ((flags & 131072) != 0) { + grouped_id = stream.readInt64(exception); + } + if (id < 0 || (media != null && !(media instanceof TL_messageMediaEmpty) && !(media instanceof TL_messageMediaWebPage) && message != null && message.length() != 0 && message.startsWith("-1"))) { //custom + attachPath = stream.readString(exception); + if (id < 0 && attachPath.startsWith("||")) { + String args[] = attachPath.split("\\|\\|"); + if (args.length > 0) { + params = new HashMap<>(); + for (int a = 1; a < args.length - 1; a++) { + String args2[] = args[a].split("\\|=\\|"); + if (args2.length == 2) { + params.put(args2[0], args2[1]); + } + } + attachPath = args[args.length - 1]; + } + } + } + if ((flags & MESSAGE_FLAG_FWD) != 0 && id < 0) { + fwd_msg_id = stream.readInt32(exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = out ? (flags | 2) : (flags &~ 2); + flags = mentioned ? (flags | 16) : (flags &~ 16); + flags = media_unread ? (flags | 32) : (flags &~ 32); + flags = silent ? (flags | 8192) : (flags &~ 8192); + flags = post ? (flags | 16384) : (flags &~ 16384); + stream.writeInt32(flags); + stream.writeInt32(id); + if ((flags & 256) != 0) { + stream.writeInt32(from_id); + } + to_id.serializeToStream(stream); + if ((flags & 4) != 0) { + fwd_from.serializeToStream(stream); + } + if ((flags & 2048) != 0) { + stream.writeInt32(via_bot_id); + } + if ((flags & 8) != 0) { + stream.writeInt32(reply_to_msg_id); + } + stream.writeInt32(date); + stream.writeString(message); + if ((flags & 512) != 0) { + media.serializeToStream(stream); + } + if ((flags & 64) != 0) { + reply_markup.serializeToStream(stream); + } + if ((flags & 128) != 0) { + stream.writeInt32(0x1cb5c415); + int count = entities.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + entities.get(a).serializeToStream(stream); + } + } + if ((flags & 1024) != 0) { + stream.writeInt32(views); + } + if ((flags & 32768) != 0) { + stream.writeInt32(edit_date); + } + if ((flags & 65536) != 0) { + stream.writeString(post_author); + } + if ((flags & 131072) != 0) { + stream.writeInt64(grouped_id); + } + String path = attachPath; //custom + if (id < 0 && params != null && params.size() > 0) { + for (HashMap.Entry entry : params.entrySet()) { + path = entry.getKey() + "|=|" + entry.getValue() + "||" + path; + } + path = "||" + path; + } + stream.writeString(path); + if ((flags & MESSAGE_FLAG_FWD) != 0 && id < 0) { + stream.writeInt32(fwd_msg_id); + } + } + } + public static class TL_message_old6 extends TL_message { public static int constructor = 0x2bebfa86; @@ -27790,6 +30100,84 @@ public class TLRPC { } public static class TL_message_secret extends TL_message { + public static int constructor = 0x555555fa; + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + unread = (flags & 1) != 0; + out = (flags & 2) != 0; + mentioned = (flags & 16) != 0; + media_unread = (flags & 32) != 0; + id = stream.readInt32(exception); + ttl = stream.readInt32(exception); + from_id = stream.readInt32(exception); + to_id = Peer.TLdeserialize(stream, stream.readInt32(exception), exception); + date = stream.readInt32(exception); + message = stream.readString(exception); + media = MessageMedia.TLdeserialize(stream, stream.readInt32(exception), exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + MessageEntity object = MessageEntity.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + entities.add(object); + } + if ((flags & 2048) != 0) { + via_bot_name = stream.readString(exception); + } + if ((flags & 8) != 0) { + reply_to_random_id = stream.readInt64(exception); + } + if ((flags & 131072) != 0) { + grouped_id = stream.readInt64(exception); + } + if (id < 0 || (media != null && !(media instanceof TL_messageMediaEmpty) && !(media instanceof TL_messageMediaWebPage) && message != null && message.length() != 0 && message.startsWith("-1"))) { + attachPath = stream.readString(exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = unread ? (flags | 1) : (flags &~ 1); + flags = out ? (flags | 2) : (flags &~ 2); + flags = mentioned ? (flags | 16) : (flags &~ 16); + flags = media_unread ? (flags | 32) : (flags &~ 32); + stream.writeInt32(flags); + stream.writeInt32(id); + stream.writeInt32(ttl); + stream.writeInt32(from_id); + to_id.serializeToStream(stream); + stream.writeInt32(date); + stream.writeString(message); + media.serializeToStream(stream); + stream.writeInt32(0x1cb5c415); + int count = entities.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + entities.get(a).serializeToStream(stream); + } + if ((flags & 2048) != 0) { + stream.writeString(via_bot_name); + } + if ((flags & 8) != 0) { + stream.writeInt64(reply_to_random_id); + } + if ((flags & 131072) != 0) { + stream.writeInt64(grouped_id); + } + stream.writeString(attachPath); + } + } + + public static class TL_message_secret_layer72 extends TL_message { public static int constructor = 0x555555f9; public void readParams(AbstractSerializedData stream, boolean exception) { @@ -27948,7 +30336,7 @@ public class TLRPC { //TL_dialog start public static class TL_dialog extends TLObject { - public static int constructor = 0x66ffba14; + public static int constructor = 0xe4def5db; public int flags; public boolean pinned; @@ -27957,6 +30345,7 @@ public class TLRPC { public int read_inbox_max_id; public int read_outbox_max_id; public int unread_count; + public int unread_mentions_count; public PeerNotifySettings notify_settings; public int pts; public DraftMessage draft; @@ -27985,6 +30374,7 @@ public class TLRPC { read_inbox_max_id = stream.readInt32(exception); read_outbox_max_id = stream.readInt32(exception); unread_count = stream.readInt32(exception); + unread_mentions_count = stream.readInt32(exception); notify_settings = PeerNotifySettings.TLdeserialize(stream, stream.readInt32(exception), exception); if ((flags & 1) != 0) { pts = stream.readInt32(exception); @@ -28003,6 +30393,7 @@ public class TLRPC { stream.writeInt32(read_inbox_max_id); stream.writeInt32(read_outbox_max_id); stream.writeInt32(unread_count); + stream.writeInt32(unread_mentions_count); notify_settings.serializeToStream(stream); if ((flags & 1) != 0) { stream.writeInt32(pts); @@ -28244,7 +30635,7 @@ public class TLRPC { } } - public static class upload_File extends TLObject { + public static abstract class upload_File extends TLObject { public storage_FileType type; public int mtime; public NativeByteBuffer bytes; @@ -28256,7 +30647,7 @@ public class TLRPC { public static upload_File TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { upload_File result = null; - switch(constructor) { + switch (constructor) { case 0x96a18d5: result = new TL_upload_file(); break; @@ -28274,13 +30665,13 @@ public class TLRPC { } } - public static class upload_CdnFile extends TLObject { + public static abstract class upload_CdnFile extends TLObject { public NativeByteBuffer bytes; public byte[] request_token; public static upload_CdnFile TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { upload_CdnFile result = null; - switch(constructor) { + switch (constructor) { case 0xa99fca4f: result = new TL_upload_cdnFile(); break; @@ -28476,6 +30867,26 @@ public class TLRPC { } } + public static class TL_messages_sendEncryptedMultiMedia extends TLObject { + public static int constructor = 0xcacacaca; + + public ArrayList messages = new ArrayList<>(); + public ArrayList files = new ArrayList<>(); + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return messages_SentEncryptedMessage.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + + } + + @Override + public void freeResources() { + + } + } + public static class TL_messages_sendEncrypted extends TLObject { public static int constructor = 0xa9776773; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBar.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBar.java index 06fe6c8e9..fa4d4631b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBar.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBar.java @@ -100,7 +100,7 @@ public class ActionBar extends FrameLayout { backButtonImageView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - if (isSearchFieldVisible) { + if (!actionModeVisible && isSearchFieldVisible) { closeSearchField(); return; } @@ -250,6 +250,10 @@ public class ActionBar extends FrameLayout { actionBarMenuOnItemClick = listener; } + public ActionBarMenuOnItemClick getActionBarMenuOnItemClick() { + return actionBarMenuOnItemClick; + } + public View getBackButton() { return backButtonImageView; } @@ -455,10 +459,14 @@ public class ActionBar extends FrameLayout { } public void closeSearchField() { + closeSearchField(true); + } + + public void closeSearchField(boolean closeKeyboard) { if (!isSearchFieldVisible || menu == null) { return; } - menu.closeSearchField(); + menu.closeSearchField(closeKeyboard); } public void openSearchField(String text) { @@ -501,7 +509,6 @@ public class ActionBar extends FrameLayout { if (titleTextView != null && titleTextView.getVisibility() != GONE) { titleTextView.setTextSize(!AndroidUtilities.isTablet() && getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE ? 18 : 20); titleTextView.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(24), MeasureSpec.AT_MOST)); - } if (subtitleTextView != null && subtitleTextView.getVisibility() != GONE) { subtitleTextView.setTextSize(!AndroidUtilities.isTablet() && getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE ? 14 : 16); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenu.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenu.java index 07ad493e5..8be13af08 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenu.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenu.java @@ -9,8 +9,6 @@ package org.telegram.ui.ActionBar; import android.content.Context; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.view.View; import android.view.ViewGroup; @@ -74,7 +72,7 @@ public class ActionBarMenu extends LinearLayout { menuItem.setTag(id); if (drawable != null) { menuItem.iconView.setImageDrawable(drawable); - } else { + } else if (icon != 0) { menuItem.iconView.setImageResource(icon); } addView(menuItem, new LinearLayout.LayoutParams(width, ViewGroup.LayoutParams.MATCH_PARENT)); @@ -158,14 +156,15 @@ public class ActionBarMenu extends LinearLayout { } } - public void closeSearchField() { + public void closeSearchField(boolean closeKeyboard) { int count = getChildCount(); for (int a = 0; a < count; a++) { View view = getChildAt(a); if (view instanceof ActionBarMenuItem) { ActionBarMenuItem item = (ActionBarMenuItem) view; if (item.isSearchField()) { - parentActionBar.onSearchFieldVisibilityChanged(item.toggleSearch(false)); + parentActionBar.onSearchFieldVisibilityChanged(false); + item.toggleSearch(closeKeyboard); break; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java index 4bc3edf83..83ff113dd 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java @@ -12,8 +12,10 @@ import android.content.Context; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; +import android.graphics.drawable.Drawable; import android.os.Build; import android.text.Editable; +import android.text.TextUtils; import android.text.TextWatcher; import android.util.TypedValue; import android.view.ActionMode; @@ -36,9 +38,10 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; import org.telegram.messenger.R; +import org.telegram.ui.Components.CloseProgressDrawable2; +import org.telegram.ui.Components.EditTextBoldCursor; import org.telegram.ui.Components.LayoutHelper; -import java.lang.reflect.Field; import java.lang.reflect.Method; public class ActionBarMenuItem extends FrameLayout { @@ -60,6 +63,9 @@ public class ActionBarMenuItem extends FrameLayout { public void onSearchPressed(EditText editText) { } + + public void onCaptionCleared() { + } } public interface ActionBarMenuItemDelegate { @@ -69,7 +75,8 @@ public class ActionBarMenuItem extends FrameLayout { private ActionBarPopupWindow.ActionBarPopupWindowLayout popupLayout; private ActionBarMenu parentMenu; private ActionBarPopupWindow popupWindow; - private EditText searchField; + private EditTextBoldCursor searchField; + private TextView searchFieldCaption; private ImageView clearButton; protected ImageView iconView; private FrameLayout searchContainer; @@ -88,6 +95,9 @@ public class ActionBarMenuItem extends FrameLayout { private boolean layoutInScreen; private boolean animationEnabled = true; private static Method layoutInScreenMethod; + private boolean ignoreOnTextChange; + private CloseProgressDrawable2 progressDrawable; + private int additionalOffset; public ActionBarMenuItem(Context context, ActionBarMenu menu, int backgroundColor, int iconColor) { super(context); @@ -247,7 +257,7 @@ public class ActionBarMenuItem extends FrameLayout { textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT); } textView.setPadding(AndroidUtilities.dp(16), 0, AndroidUtilities.dp(16), 0); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); textView.setMinWidth(AndroidUtilities.dp(196)); textView.setTag(id); textView.setText(text); @@ -381,7 +391,9 @@ public class ActionBarMenuItem extends FrameLayout { searchContainer.setVisibility(GONE); searchField.clearFocus(); setVisibility(VISIBLE); - AndroidUtilities.hideKeyboard(searchField); + if (openKeyboard) { + AndroidUtilities.hideKeyboard(searchField); + } if (listener != null) { listener.onSearchCollapse(); } @@ -412,11 +424,15 @@ public class ActionBarMenuItem extends FrameLayout { iconView.setImageResource(resId); } + public void setIcon(Drawable drawable) { + iconView.setImageDrawable(drawable); + } + public ImageView getImageView() { return iconView; } - public EditText getSearchField() { + public EditTextBoldCursor getSearchField() { return searchField; } @@ -430,11 +446,67 @@ public class ActionBarMenuItem extends FrameLayout { return this; } if (value && searchContainer == null) { - searchContainer = new FrameLayout(getContext()); + searchContainer = new FrameLayout(getContext()) { + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + measureChildWithMargins(clearButton, widthMeasureSpec, 0, heightMeasureSpec, 0); + int width; + if (searchFieldCaption.getVisibility() == VISIBLE) { + measureChildWithMargins(searchFieldCaption, widthMeasureSpec, MeasureSpec.getSize(widthMeasureSpec) / 2, heightMeasureSpec, 0); + width = searchFieldCaption.getMeasuredWidth() + AndroidUtilities.dp(4); + } else { + width = 0; + } + measureChildWithMargins(searchField, widthMeasureSpec, width, heightMeasureSpec, 0); + int w = MeasureSpec.getSize(widthMeasureSpec); + int h = MeasureSpec.getSize(heightMeasureSpec); + setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec)); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + int x; + if (LocaleController.isRTL) { + x = 0; + } else { + if (searchFieldCaption.getVisibility() == VISIBLE) { + x = searchFieldCaption.getMeasuredWidth() + AndroidUtilities.dp(4); + } else { + x = 0; + } + } + searchField.layout(x, searchField.getTop(), x + searchField.getMeasuredWidth(), searchField.getBottom()); + } + }; parentMenu.addView(searchContainer, 0, LayoutHelper.createLinear(0, LayoutHelper.MATCH_PARENT, 1.0f, 6, 0, 0, 0)); searchContainer.setVisibility(GONE); - searchField = new EditText(getContext()); + searchFieldCaption = new TextView(getContext()); + searchFieldCaption.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); + searchFieldCaption.setTextColor(Theme.getColor(Theme.key_actionBarDefaultSearch)); + searchFieldCaption.setSingleLine(true); + searchFieldCaption.setEllipsize(TextUtils.TruncateAt.END); + searchFieldCaption.setVisibility(GONE); + searchFieldCaption.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); + + searchField = new EditTextBoldCursor(getContext()) { + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_DEL && searchField.length() == 0 && searchFieldCaption.getVisibility() == VISIBLE && searchFieldCaption.length() > 0) { + clearButton.callOnClick(); + return true; + } + return super.onKeyDown(keyCode, event); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + return super.dispatchKeyEvent(event); + } + }; + searchField.setCursorWidth(1.5f); + searchField.setCursorColor(0xffffffff); searchField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); searchField.setHintTextColor(Theme.getColor(Theme.key_actionBarDefaultSearchPlaceholder)); searchField.setTextColor(Theme.getColor(Theme.key_actionBarDefaultSearch)); @@ -443,27 +515,29 @@ public class ActionBarMenuItem extends FrameLayout { searchField.setPadding(0, 0, 0, 0); int inputType = searchField.getInputType() | EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS; searchField.setInputType(inputType); - searchField.setCustomSelectionActionModeCallback(new ActionMode.Callback() { - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - return false; - } + if (Build.VERSION.SDK_INT < 23) { + searchField.setCustomSelectionActionModeCallback(new ActionMode.Callback() { + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } - public void onDestroyActionMode(ActionMode mode) { + public void onDestroyActionMode(ActionMode mode) { - } + } - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - return false; - } + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + return false; + } - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - return false; - } - }); + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + return false; + } + }); + } searchField.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if (/*actionId == EditorInfo.IME_ACTION_SEARCH || */event != null && (event.getAction() == KeyEvent.ACTION_UP && event.getKeyCode() == KeyEvent.KEYCODE_SEARCH || event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) { + if (event != null && (event.getAction() == KeyEvent.ACTION_UP && event.getKeyCode() == KeyEvent.KEYCODE_SEARCH || event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) { AndroidUtilities.hideKeyboard(searchField); if (listener != null) { listener.onSearchPressed(searchField); @@ -480,11 +554,15 @@ public class ActionBarMenuItem extends FrameLayout { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { + if (ignoreOnTextChange) { + ignoreOnTextChange = false; + return; + } if (listener != null) { listener.onTextChanged(searchField); } if (clearButton != null) { - clearButton.setAlpha(s == null || s.length() == 0 ? 0.6f : 1.0f); + //clearButton.setAlpha(TextUtils.isEmpty(s) && searchFieldCaption.getVisibility() != VISIBLE ? 0.6f : 1.0f); } } @@ -494,25 +572,32 @@ public class ActionBarMenuItem extends FrameLayout { } }); - try { - Field mCursorDrawableRes = TextView.class.getDeclaredField("mCursorDrawableRes"); - mCursorDrawableRes.setAccessible(true); - mCursorDrawableRes.set(searchField, R.drawable.search_carret); - } catch (Exception e) { - //nothing to do - } searchField.setImeOptions(EditorInfo.IME_FLAG_NO_FULLSCREEN | EditorInfo.IME_ACTION_SEARCH); searchField.setTextIsSelectable(false); - searchContainer.addView(searchField, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 36, Gravity.CENTER_VERTICAL, 0, 0, 48, 0)); + if (!LocaleController.isRTL) { + searchContainer.addView(searchFieldCaption, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 36, Gravity.CENTER_VERTICAL | Gravity.LEFT, 0, 5.5f, 0, 0)); + searchContainer.addView(searchField, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 36, Gravity.CENTER_VERTICAL, 0, 0, 48, 0)); + } else { + searchContainer.addView(searchField, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 36, Gravity.CENTER_VERTICAL, 0, 0, 48, 0)); + searchContainer.addView(searchFieldCaption, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 36, Gravity.CENTER_VERTICAL | Gravity.RIGHT, 0, 5.5f, 48, 0)); + } clearButton = new ImageView(getContext()); - clearButton.setImageResource(R.drawable.ic_close_white); + clearButton.setImageDrawable(progressDrawable = new CloseProgressDrawable2()); clearButton.setColorFilter(new PorterDuffColorFilter(parentMenu.parentActionBar.itemsColor, PorterDuff.Mode.MULTIPLY)); clearButton.setScaleType(ImageView.ScaleType.CENTER); clearButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - searchField.setText(""); + if (searchField.length() != 0) { + searchField.setText(""); + } else if (searchFieldCaption != null && searchFieldCaption.getVisibility() == VISIBLE) { + searchFieldCaption.setVisibility(GONE); + //clearButton.setAlpha(searchField.length() == 0 && searchFieldCaption.getVisibility() != VISIBLE ? 0.6f : 1.0f); + if (listener != null) { + listener.onCaptionCleared(); + } + } searchField.requestFocus(); AndroidUtilities.showKeyboard(searchField); } @@ -523,10 +608,44 @@ public class ActionBarMenuItem extends FrameLayout { return this; } + public void setShowSearchProgress(boolean show) { + if (progressDrawable == null) { + return; + } + if (show) { + progressDrawable.startAnimation(); + } else { + progressDrawable.stopAnimation(); + } + } + + public void setSearchFieldCaption(CharSequence caption) { + if (TextUtils.isEmpty(caption)) { + searchFieldCaption.setVisibility(GONE); + } else { + searchFieldCaption.setVisibility(VISIBLE); + searchFieldCaption.setText(caption); + } + if (clearButton != null) { + //clearButton.setAlpha(searchField.length() == 0 && searchFieldCaption.getVisibility() != VISIBLE ? 0.6f : 1.0f); + } + } + + public void setIgnoreOnTextChange() { + ignoreOnTextChange = true; + } + public boolean isSearchField() { return isSearchField; } + public void clearSearchText() { + if (searchField == null) { + return; + } + searchField.setText(""); + } + public ActionBarMenuItem setActionBarMenuItemSearchListener(ActionBarMenuItemSearchListener listener) { this.listener = listener; return this; @@ -552,6 +671,10 @@ public class ActionBarMenuItem extends FrameLayout { } } + public void setAdditionalOffset(int value) { + additionalOffset = value; + } + private void updateOrShowPopup(boolean show, boolean update) { int offsetY; @@ -559,7 +682,7 @@ public class ActionBarMenuItem extends FrameLayout { offsetY = -parentMenu.parentActionBar.getMeasuredHeight() + parentMenu.getTop(); } else { float scaleY = getScaleY(); - offsetY = -(int) (getMeasuredHeight() * scaleY - getTranslationY() / scaleY); + offsetY = -(int) (getMeasuredHeight() * scaleY - getTranslationY() / scaleY) + additionalOffset; } if (show) { @@ -570,17 +693,17 @@ public class ActionBarMenuItem extends FrameLayout { View parent = parentMenu.parentActionBar; if (subMenuOpenSide == 0) { if (show) { - popupWindow.showAsDropDown(parent, getLeft() + parentMenu.getLeft() + getMeasuredWidth() - popupLayout.getMeasuredWidth(), offsetY); + popupWindow.showAsDropDown(parent, getLeft() + parentMenu.getLeft() + getMeasuredWidth() - popupLayout.getMeasuredWidth() + (int) getTranslationX(), offsetY); } if (update) { - popupWindow.update(parent, getLeft() + parentMenu.getLeft() + getMeasuredWidth() - popupLayout.getMeasuredWidth(), offsetY, -1, -1); + popupWindow.update(parent, getLeft() + parentMenu.getLeft() + getMeasuredWidth() - popupLayout.getMeasuredWidth() + (int) getTranslationX(), offsetY, -1, -1); } } else { if (show) { - popupWindow.showAsDropDown(parent, getLeft() - AndroidUtilities.dp(8), offsetY); + popupWindow.showAsDropDown(parent, getLeft() - AndroidUtilities.dp(8) + (int) getTranslationX(), offsetY); } if (update) { - popupWindow.update(parent, getLeft() - AndroidUtilities.dp(8), offsetY, -1, -1); + popupWindow.update(parent, getLeft() - AndroidUtilities.dp(8) + (int) getTranslationX(), offsetY, -1, -1); } } } else { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/AlertDialog.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/AlertDialog.java index 3dd454f33..bad52e8fc 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/AlertDialog.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/AlertDialog.java @@ -70,6 +70,7 @@ public class AlertDialog extends Dialog implements Drawable.Callback { private CharSequence subtitle; private CharSequence message; private int topResId; + private Drawable topDrawable; private int topBackgroundColor; private int progressViewStyle; private int currentProgress; @@ -84,6 +85,7 @@ public class AlertDialog extends Dialog implements Drawable.Callback { private FrameLayout buttonsLayout; private LineProgressView lineProgressView; private TextView lineProgressViewPercent; + private OnClickListener onBackButtonListener; private Drawable shadowDrawable; private Rect backgroundPaddings; @@ -305,9 +307,13 @@ public class AlertDialog extends Dialog implements Drawable.Callback { final boolean hasButtons = positiveButtonText != null || negativeButtonText != null || neutralButtonText != null; - if (topResId != 0) { + if (topResId != 0 || topDrawable != null) { topImageView = new ImageView(getContext()); - topImageView.setImageResource(topResId); + if (topDrawable != null) { + topImageView.setImageDrawable(topDrawable); + } else { + topImageView.setImageResource(topResId); + } topImageView.setScaleType(ImageView.ScaleType.CENTER); topImageView.setBackgroundDrawable(getContext().getResources().getDrawable(R.drawable.popup_fixed_top)); topImageView.getBackground().setColorFilter(new PorterDuffColorFilter(topBackgroundColor, PorterDuff.Mode.MULTIPLY)); @@ -583,6 +589,14 @@ public class AlertDialog extends Dialog implements Drawable.Callback { window.setAttributes(params); } + @Override + public void onBackPressed() { + super.onBackPressed(); + if (onBackButtonListener != null) { + onBackButtonListener.onClick(AlertDialog.this, AlertDialog.BUTTON_NEGATIVE); + } + } + private void runShadowAnimation(final int num, final boolean show) { if (show && !shadowVisibility[num] || !show && shadowVisibility[num]) { shadowVisibility[num] = show; @@ -766,6 +780,12 @@ public class AlertDialog extends Dialog implements Drawable.Callback { return this; } + public Builder setTopImage(Drawable drawable, int backgroundColor) { + alertDialog.topDrawable = drawable; + alertDialog.topBackgroundColor = backgroundColor; + return this; + } + public Builder setMessage(CharSequence message) { alertDialog.message = message; return this; @@ -789,6 +809,11 @@ public class AlertDialog extends Dialog implements Drawable.Callback { return this; } + public Builder setOnBackButtonListener(final OnClickListener listener) { + alertDialog.onBackButtonListener = listener; + return this; + } + public AlertDialog create() { return alertDialog; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java index 091a5970a..bcb49b7fc 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java @@ -85,6 +85,8 @@ public class BottomSheet extends Dialog { private boolean focusable; + private boolean allowNestedScroll = true; + private Drawable shadowDrawable; protected static int backgroundPaddingTop; protected static int backgroundPaddingLeft; @@ -119,13 +121,13 @@ public class BottomSheet extends Dialog { @Override public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { - return !dismissed && nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL && !canDismissWithSwipe(); + return !dismissed && allowNestedScroll && nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL && !canDismissWithSwipe(); } @Override public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) { nestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes); - if (dismissed) { + if (dismissed || !allowNestedScroll) { return; } cancelCurrentAnimation(); @@ -134,7 +136,7 @@ public class BottomSheet extends Dialog { @Override public void onStopNestedScroll(View target) { nestedScrollingParentHelper.onStopNestedScroll(target); - if (dismissed) { + if (dismissed || !allowNestedScroll) { return; } float currentTranslation = containerView.getTranslationY(); @@ -143,7 +145,7 @@ public class BottomSheet extends Dialog { @Override public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { - if (dismissed) { + if (dismissed || !allowNestedScroll) { return; } cancelCurrentAnimation(); @@ -159,7 +161,7 @@ public class BottomSheet extends Dialog { @Override public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { - if (dismissed) { + if (dismissed || !allowNestedScroll) { return; } cancelCurrentAnimation(); @@ -231,7 +233,7 @@ public class BottomSheet extends Dialog { if (onContainerTouchEvent(ev)) { return true; } - if (canDismissWithTouchOutside() && ev != null && (ev.getAction() == MotionEvent.ACTION_DOWN || ev.getAction() == MotionEvent.ACTION_MOVE) && !startedTracking && !maybeStartTracking) { + if (canDismissWithTouchOutside() && ev != null && (ev.getAction() == MotionEvent.ACTION_DOWN || ev.getAction() == MotionEvent.ACTION_MOVE) && (!startedTracking && !maybeStartTracking || ev.getPointerCount() == 1)) { startedTrackingX = (int) ev.getX(); startedTrackingY = (int) ev.getY(); if (startedTrackingY < containerView.getTop() || startedTrackingX < containerView.getLeft() || startedTrackingX > containerView.getRight()) { @@ -519,6 +521,13 @@ public class BottomSheet extends Dialog { super.onAttachedToWindow(); } + public void setAllowNestedScroll(boolean value) { + allowNestedScroll = value; + if (!allowNestedScroll) { + containerView.setTranslationY(0); + } + } + public BottomSheet(Context context, boolean needFocus) { super(context, R.style.TransparentDialog); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/SimpleTextView.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/SimpleTextView.java index c4a3faf72..3d57090ac 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/SimpleTextView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/SimpleTextView.java @@ -107,6 +107,7 @@ public class SimpleTextView extends View implements Drawable.Callback { } else { offsetX = -AndroidUtilities.dp(8); } + offsetX += getPaddingLeft(); } } @@ -121,7 +122,6 @@ public class SimpleTextView extends View implements Drawable.Callback { width -= rightDrawable.getIntrinsicWidth(); width -= drawablePadding; } - width -= getPaddingLeft() + getPaddingRight(); CharSequence string = TextUtils.ellipsize(text, textPaint, width, TextUtils.TruncateAt.END); /*if (layout != null && TextUtils.equals(layout.getText(), string)) { calcOffset(width); @@ -129,8 +129,8 @@ public class SimpleTextView extends View implements Drawable.Callback { }*/ layout = new StaticLayout(string, 0, string.length(), textPaint, width + AndroidUtilities.dp(8), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); calcOffset(width); - } catch (Exception e) { - //ignore + } catch (Exception ignore) { + } } else { layout = null; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/Theme.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/Theme.java index 804195141..af3560d18 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/Theme.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/Theme.java @@ -19,6 +19,7 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Paint; +import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.PorterDuffXfermode; @@ -163,6 +164,7 @@ public class Theme { public static Drawable listSelector; public static Drawable avatar_broadcastDrawable; + public static Drawable avatar_savedDrawable; public static Drawable avatar_photoDrawable; public static Paint dialogs_tabletSeletedPaint; @@ -190,7 +192,7 @@ public class Theme { public static Drawable dialogs_verifiedDrawable; public static Drawable dialogs_verifiedCheckDrawable; public static Drawable dialogs_pinnedDrawable; - public static Drawable dialogs_errorIconDrawable; + public static Drawable dialogs_mentionDrawable; public static TextPaint profile_aboutTextPaint; public static Drawable profile_verifiedDrawable; @@ -210,6 +212,7 @@ public class Theme { public static Paint chat_timeBackgroundPaint; public static Paint chat_composeBackgroundPaint; public static Paint chat_radialProgressPaint; + public static Paint chat_radialProgress2Paint; public static TextPaint chat_msgTextPaint; public static TextPaint chat_actionTextPaint; public static TextPaint chat_msgBotButtonPaint; @@ -218,6 +221,7 @@ public class Theme { public static TextPaint chat_msgTextPaintTwoEmoji; public static TextPaint chat_msgTextPaintThreeEmoji; public static TextPaint chat_infoPaint; + public static TextPaint chat_livePaint; public static TextPaint chat_docNamePaint; public static TextPaint chat_locationTitlePaint; public static TextPaint chat_locationAddressPaint; @@ -232,6 +236,7 @@ public class Theme { public static TextPaint chat_contactNamePaint; public static TextPaint chat_contactPhonePaint; public static TextPaint chat_timePaint; + public static TextPaint chat_adminPaint; public static TextPaint chat_namePaint; public static TextPaint chat_forwardNamePaint; public static TextPaint chat_replyNamePaint; @@ -294,6 +299,7 @@ public class Theme { public static Drawable chat_contextResult_shadowUnderSwitchDrawable; public static Drawable chat_shareDrawable; public static Drawable chat_shareIconDrawable; + public static Drawable chat_goIconDrawable; public static Drawable chat_botLinkDrawalbe; public static Drawable chat_botInlineDrawable; public static Drawable chat_systemDrawable; @@ -305,6 +311,7 @@ public class Theme { public static Drawable chat_msgCallUpGreenDrawable; public static Drawable chat_msgCallDownRedDrawable; public static Drawable chat_msgCallDownGreenDrawable; + public static Drawable chat_msgAvatarLiveLocationDrawable; public static Drawable[] chat_attachButtonDrawables = new Drawable[8]; public static Drawable[] chat_locationDrawable = new Drawable[2]; public static Drawable[] chat_contactDrawable = new Drawable[2]; @@ -349,6 +356,7 @@ public class Theme { public static final String key_dialogButtonSelector = "dialogButtonSelector"; public static final String key_dialogIcon = "dialogIcon"; public static final String key_dialogGrayLine = "dialogGrayLine"; + public static final String key_dialogTopBackground = "dialogTopBackground"; public static final String key_windowBackgroundWhite = "windowBackgroundWhite"; public static final String key_progressCircle = "progressCircle"; @@ -424,6 +432,7 @@ public class Theme { public static final String key_contextProgressOuter3 = "contextProgressOuter3"; public static final String key_avatar_text = "avatar_text"; + public static final String key_avatar_backgroundSaved = "avatar_backgroundSaved"; public static final String key_avatar_backgroundRed = "avatar_backgroundRed"; public static final String key_avatar_backgroundOrange = "avatar_backgroundOrange"; public static final String key_avatar_backgroundViolet = "avatar_backgroundViolet"; @@ -618,6 +627,8 @@ public class Theme { public static final String key_chat_inAudioSelectedProgress = "chat_inAudioSelectedProgress"; public static final String key_chat_outAudioSelectedProgress = "chat_outAudioSelectedProgress"; public static final String key_chat_mediaTimeText = "chat_mediaTimeText"; + public static final String key_chat_adminText = "chat_adminText"; + public static final String key_chat_adminSelectedText = "chat_adminSelectedText"; public static final String key_chat_inTimeText = "chat_inTimeText"; public static final String key_chat_outTimeText = "chat_outTimeText"; public static final String key_chat_inTimeSelectedText = "chat_inTimeSelectedText"; @@ -736,6 +747,8 @@ public class Theme { public static final String key_chat_emojiPanelMasksIcon = "chat_emojiPanelMasksIcon"; public static final String key_chat_emojiPanelMasksIconSelected = "chat_emojiPanelMasksIconSelected"; public static final String key_chat_emojiPanelTrendingTitle = "chat_emojiPanelTrendingTitle"; + public static final String key_chat_emojiPanelStickerSetName = "chat_emojiPanelStickerSetName"; + public static final String key_chat_emojiPanelStickerSetNameIcon = "chat_emojiPanelStickerSetNameIcon"; public static final String key_chat_emojiPanelTrendingDescription = "chat_emojiPanelTrendingDescription"; public static final String key_chat_botKeyboardButtonText = "chat_botKeyboardButtonText"; public static final String key_chat_botKeyboardButtonBackground = "chat_botKeyboardButtonBackground"; @@ -804,6 +817,9 @@ public class Theme { public static final String key_groupcreate_spanText = "groupcreate_spanText"; public static final String key_groupcreate_spanBackground = "groupcreate_spanBackground"; + public static final String key_contacts_inviteBackground = "contacts_inviteBackground"; + public static final String key_contacts_inviteText = "contacts_inviteText"; + public static final String key_login_progressInner = "login_progressInner"; public static final String key_login_progressOuter = "login_progressOuter"; @@ -819,7 +835,11 @@ public class Theme { public static final String key_location_markerX = "location_markerX"; public static final String key_location_sendLocationBackground = "location_sendLocationBackground"; + public static final String key_location_sendLiveLocationBackground = "location_sendLiveLocationBackground"; public static final String key_location_sendLocationIcon = "location_sendLocationIcon"; + public static final String key_location_liveLocationProgress = "location_liveLocationProgress"; + public static final String key_location_placeLocationBackground = "location_placeLocationBackground"; + public static final String key_dialog_liveLocationProgress = "location_liveLocationProgress"; public static final String key_files_folderIcon = "files_folderIcon"; public static final String key_files_folderIconBackground = "files_folderIconBackground"; @@ -843,16 +863,17 @@ public class Theme { public static final String key_player_actionBarTop = "player_actionBarTop"; public static final String key_player_actionBarSubtitle = "player_actionBarSubtitle"; public static final String key_player_actionBarItems = "player_actionBarItems"; - public static final String key_player_seekBarBackground = "player_seekBarBackground"; + public static final String key_player_background = "player_background"; public static final String key_player_time = "player_time"; - public static final String key_player_duration = "player_duration"; public static final String key_player_progressBackground = "player_progressBackground"; public static final String key_player_progress = "player_progress"; public static final String key_player_placeholder = "player_placeholder"; + public static final String key_player_placeholderBackground = "player_placeholderBackground"; public static final String key_player_button = "player_button"; public static final String key_player_buttonActive = "player_buttonActive"; private static HashMap defaultColors = new HashMap<>(); + private static HashMap fallbackKeys = new HashMap<>(); private static HashMap currentColors; static { @@ -873,6 +894,7 @@ public class Theme { defaultColors.put(key_dialogTextHint, 0xff979797); defaultColors.put(key_dialogIcon, 0xff8a8a8a); defaultColors.put(key_dialogGrayLine, 0xffd2d2d2); + defaultColors.put(key_dialogTopBackground, 0xff6fb2e5); defaultColors.put(key_dialogInputField, 0xffdbdbdb); defaultColors.put(key_dialogInputFieldActivated, 0xff37a9f0); defaultColors.put(key_dialogCheckboxSquareBackground, 0xff43a0df); @@ -955,6 +977,7 @@ public class Theme { defaultColors.put(key_avatar_text, 0xffffffff); + defaultColors.put(key_avatar_backgroundSaved, 0xff66bffa); defaultColors.put(key_avatar_backgroundRed, 0xffe56555); defaultColors.put(key_avatar_backgroundOrange, 0xfff28c48); defaultColors.put(key_avatar_backgroundViolet, 0xff8e85ee); @@ -1141,6 +1164,8 @@ public class Theme { defaultColors.put(key_chat_mediaTimeText, 0xffffffff); defaultColors.put(key_chat_inTimeText, 0xffa1aab3); defaultColors.put(key_chat_outTimeText, 0xff70b15c); + defaultColors.put(key_chat_adminText, 0xffc0c6cb); + defaultColors.put(key_chat_adminSelectedText, 0xff89b4c1); defaultColors.put(key_chat_inTimeSelectedText, 0xff89b4c1); defaultColors.put(key_chat_outTimeSelectedText, 0xff70b15c); defaultColors.put(key_chat_inAudioPerfomerText, 0xff2f3438); @@ -1197,6 +1222,8 @@ public class Theme { defaultColors.put(key_chat_emojiPanelMasksIcon, 0xffffffff); defaultColors.put(key_chat_emojiPanelMasksIconSelected, 0xff62bfe8); defaultColors.put(key_chat_emojiPanelTrendingTitle, 0xff212121); + defaultColors.put(key_chat_emojiPanelStickerSetName, 0xff838c96); + defaultColors.put(key_chat_emojiPanelStickerSetNameIcon, 0xffb1b6bc); defaultColors.put(key_chat_emojiPanelTrendingDescription, 0xff8a8a8a); defaultColors.put(key_chat_botKeyboardButtonText, 0xff36474f); defaultColors.put(key_chat_botKeyboardButtonBackground, 0xffe4e7e9); @@ -1298,18 +1325,18 @@ public class Theme { defaultColors.put(key_player_actionBar, 0xffffffff); defaultColors.put(key_player_actionBarSelector, 0x2f000000); - defaultColors.put(key_player_actionBarTitle, 0xff212121); + defaultColors.put(key_player_actionBarTitle, 0xff2f3438); defaultColors.put(key_player_actionBarTop, 0x99000000); defaultColors.put(key_player_actionBarSubtitle, 0xff8a8a8a); defaultColors.put(key_player_actionBarItems, 0xff8a8a8a); - defaultColors.put(key_player_seekBarBackground, 0xe5ffffff); - defaultColors.put(key_player_time, 0xff19a7e8); - defaultColors.put(key_player_duration, 0xff8a8a8a); + defaultColors.put(key_player_background, 0xffffffff); + defaultColors.put(key_player_time, 0xff8c9296); defaultColors.put(key_player_progressBackground, 0x19000000); defaultColors.put(key_player_progress, 0xff23afef); - defaultColors.put(key_player_placeholder, 0xffd9d9d9); - defaultColors.put(key_player_button, 0xff8a8a8a); - defaultColors.put(key_player_buttonActive, 0xff23afef); + defaultColors.put(key_player_placeholder, 0xffa8a8a8); + defaultColors.put(key_player_placeholderBackground, 0xfff0f0f0); + defaultColors.put(key_player_button, 0xff333333); + defaultColors.put(key_player_buttonActive, 0xff4ca8ea); defaultColors.put(key_files_folderIcon, 0xff999999); defaultColors.put(key_files_folderIconBackground, 0xfff0f0f0); @@ -1319,7 +1346,11 @@ public class Theme { defaultColors.put(key_location_markerX, 0xff808080); defaultColors.put(key_location_sendLocationBackground, 0xff6da0d4); + defaultColors.put(key_location_sendLiveLocationBackground, 0xffff6464); defaultColors.put(key_location_sendLocationIcon, 0xffffffff); + defaultColors.put(key_location_liveLocationProgress, 0xff359fe5); + defaultColors.put(key_location_placeLocationBackground, 0xff4ca8ea); + defaultColors.put(key_dialog_liveLocationProgress, 0xff359fe5); defaultColors.put(key_calls_callReceivedGreenIcon, 0xff00c853); defaultColors.put(key_calls_callReceivedRedIcon, 0xffff4848); @@ -1364,6 +1395,9 @@ public class Theme { defaultColors.put(key_groupcreate_spanText, 0xff212121); defaultColors.put(key_groupcreate_spanBackground, 0xfff2f2f2); + defaultColors.put(key_contacts_inviteBackground, 0xff55be61); + defaultColors.put(key_contacts_inviteText, 0xffffffff); + defaultColors.put(key_login_progressInner, 0xffe1eaf2); defaultColors.put(key_login_progressOuter, 0xff62a0d0); @@ -1381,6 +1415,9 @@ public class Theme { defaultColors.put(key_calls_ratingStar, 0x80000000); defaultColors.put(key_calls_ratingStarSelected, 0xFF4a97d6); + fallbackKeys.put(key_chat_adminText, key_chat_inTimeText); + fallbackKeys.put(key_chat_adminSelectedText, key_chat_inTimeSelectedText); + themes = new ArrayList<>(); otherThemes = new ArrayList<>(); themesDict = new HashMap<>(); @@ -1594,6 +1631,11 @@ public class Theme { } public static Drawable createCircleDrawableWithIcon(int size, int iconRes, int stroke) { + Drawable drawable = ApplicationLoader.applicationContext.getResources().getDrawable(iconRes).mutate(); + return createCircleDrawableWithIcon(size, drawable, stroke); + } + + public static Drawable createCircleDrawableWithIcon(int size, Drawable drawable, int stroke) { OvalShape ovalShape = new OvalShape(); ovalShape.resize(size, size); ShapeDrawable defaultDrawable = new ShapeDrawable(ovalShape); @@ -1605,7 +1647,6 @@ public class Theme { } else if (stroke == 2) { paint.setAlpha(0); } - Drawable drawable = ApplicationLoader.applicationContext.getResources().getDrawable(iconRes).mutate(); CombinedDrawable combinedDrawable = new CombinedDrawable(defaultDrawable, drawable); combinedDrawable.setCustomSize(size, size); return combinedDrawable; @@ -1740,7 +1781,7 @@ public class Theme { @Override public int getOpacity() { - return 0; + return PixelFormat.UNKNOWN; } }; } else if (maskType == 2) { @@ -1868,6 +1909,12 @@ public class Theme { applyDialogsTheme(); applyProfileTheme(); applyChatTheme(false); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.didSetNewTheme); + } + }); } catch (Exception e) { FileLog.e(e); } @@ -1975,7 +2022,16 @@ public class Theme { public static File getAssetFile(String assetName) { File file = new File(ApplicationLoader.getFilesDirFixed(), assetName); - if (!file.exists()) { + long size; + try { + InputStream stream = ApplicationLoader.applicationContext.getAssets().open(assetName); + size = stream.available(); + stream.close(); + } catch (Exception e) { + size = 0; + FileLog.e(e); + } + if (!file.exists() || size != 0 && file.length() != size) { InputStream in = null; try { in = ApplicationLoader.applicationContext.getAssets().open(assetName); @@ -2083,6 +2139,7 @@ public class Theme { Resources resources = context.getResources(); avatar_broadcastDrawable = resources.getDrawable(R.drawable.broadcast_w); + avatar_savedDrawable = resources.getDrawable(R.drawable.bookmark_large); avatar_photoDrawable = resources.getDrawable(R.drawable.photo_w); applyCommonTheme(); @@ -2097,6 +2154,7 @@ public class Theme { linkSelectionPaint.setColor(getColor(key_windowBackgroundWhiteLinkSelection)); setDrawableColorByKey(avatar_broadcastDrawable, key_avatar_text); + setDrawableColorByKey(avatar_savedDrawable, key_avatar_text); setDrawableColorByKey(avatar_photoDrawable, key_avatar_text); } @@ -2133,6 +2191,7 @@ public class Theme { dialogs_muteDrawable = resources.getDrawable(R.drawable.list_mute).mutate(); dialogs_verifiedDrawable = resources.getDrawable(R.drawable.verified_area); dialogs_verifiedCheckDrawable = resources.getDrawable(R.drawable.verified_check); + dialogs_mentionDrawable = resources.getDrawable(R.drawable.mentionchatslist); dialogs_botDrawable = resources.getDrawable(R.drawable.list_bot); dialogs_pinnedDrawable = resources.getDrawable(R.drawable.list_pin); @@ -2220,7 +2279,12 @@ public class Theme { chat_radialProgressPaint.setStrokeCap(Paint.Cap.ROUND); chat_radialProgressPaint.setStyle(Paint.Style.STROKE); chat_radialProgressPaint.setColor(0x9fffffff); + chat_radialProgress2Paint = new Paint(Paint.ANTI_ALIAS_FLAG); + chat_radialProgress2Paint.setStrokeCap(Paint.Cap.ROUND); + chat_radialProgress2Paint.setStyle(Paint.Style.STROKE); chat_audioTimePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + chat_livePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + chat_livePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); chat_audioTitlePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); chat_audioTitlePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); chat_audioPerformerPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); @@ -2234,6 +2298,7 @@ public class Theme { chat_gamePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); chat_shipmentPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); chat_timePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + chat_adminPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); chat_namePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); chat_namePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); chat_forwardNamePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); @@ -2312,6 +2377,7 @@ public class Theme { chat_msgCallUpGreenDrawable = resources.getDrawable(R.drawable.ic_call_made_green_18dp).mutate(); chat_msgCallDownRedDrawable = resources.getDrawable(R.drawable.ic_call_received_green_18dp).mutate(); chat_msgCallDownGreenDrawable = resources.getDrawable(R.drawable.ic_call_received_green_18dp).mutate(); + chat_msgAvatarLiveLocationDrawable = resources.getDrawable(R.drawable.livepin).mutate(); chat_inlineResultFile = resources.getDrawable(R.drawable.bot_file); chat_inlineResultAudio = resources.getDrawable(R.drawable.bot_music); @@ -2350,6 +2416,7 @@ public class Theme { chat_shareDrawable = resources.getDrawable(R.drawable.share_round); chat_shareIconDrawable = resources.getDrawable(R.drawable.share_arrow); + chat_goIconDrawable = resources.getDrawable(R.drawable.message_arrow); chat_ivStatesDrawable[0][0] = createCircleDrawableWithIcon(AndroidUtilities.dp(40), R.drawable.msg_round_play_m, 1); chat_ivStatesDrawable[0][1] = createCircleDrawableWithIcon(AndroidUtilities.dp(40), R.drawable.msg_round_play_m, 1); @@ -2449,6 +2516,7 @@ public class Theme { chat_locationTitlePaint.setTextSize(AndroidUtilities.dp(15)); chat_locationAddressPaint.setTextSize(AndroidUtilities.dp(13)); chat_audioTimePaint.setTextSize(AndroidUtilities.dp(12)); + chat_livePaint.setTextSize(AndroidUtilities.dp(12)); chat_audioTitlePaint.setTextSize(AndroidUtilities.dp(16)); chat_audioPerformerPaint.setTextSize(AndroidUtilities.dp(15)); chat_botButtonPaint.setTextSize(AndroidUtilities.dp(15)); @@ -2456,6 +2524,7 @@ public class Theme { chat_contactPhonePaint.setTextSize(AndroidUtilities.dp(13)); chat_durationPaint.setTextSize(AndroidUtilities.dp(12)); chat_timePaint.setTextSize(AndroidUtilities.dp(12)); + chat_adminPaint.setTextSize(AndroidUtilities.dp(13)); chat_namePaint.setTextSize(AndroidUtilities.dp(14)); chat_forwardNamePaint.setTextSize(AndroidUtilities.dp(14)); chat_replyNamePaint.setTextSize(AndroidUtilities.dp(14)); @@ -2469,7 +2538,7 @@ public class Theme { chat_contextResult_titleTextPaint.setTextSize(AndroidUtilities.dp(15)); chat_contextResult_descriptionTextPaint.setTextSize(AndroidUtilities.dp(13)); chat_radialProgressPaint.setStrokeWidth(AndroidUtilities.dp(3)); - + chat_radialProgress2Paint.setStrokeWidth(AndroidUtilities.dp(2)); } } @@ -2523,6 +2592,7 @@ public class Theme { setDrawableColorByKey(chat_msgStickerClockDrawable, key_chat_serviceText); setDrawableColorByKey(chat_msgStickerViewsDrawable, key_chat_serviceText); setDrawableColorByKey(chat_shareIconDrawable, key_chat_serviceIcon); + setDrawableColorByKey(chat_goIconDrawable, key_chat_serviceIcon); setDrawableColorByKey(chat_botInlineDrawable, key_chat_serviceIcon); setDrawableColorByKey(chat_botLinkDrawalbe, key_chat_serviceIcon); setDrawableColorByKey(chat_msgInViewsDrawable, key_chat_inViews); @@ -2683,7 +2753,13 @@ public class Theme { public static Integer getColorOrNull(String key) { Integer color = currentColors.get(key); if (color == null) { - color = defaultColors.get(key); + String fallbackKey = fallbackKeys.get(key); + if (fallbackKey != null) { + color = currentColors.get(key); + } + if (color == null) { + color = defaultColors.get(key); + } } return color; } @@ -2695,15 +2771,21 @@ public class Theme { public static int getColor(String key, boolean[] isDefault) { Integer color = currentColors.get(key); if (color == null) { - if (isDefault != null) { - isDefault[0] = true; + String fallbackKey = fallbackKeys.get(key); + if (fallbackKey != null) { + color = currentColors.get(key); } - if (key.equals(key_chat_serviceBackground)) { - return serviceMessageColor; - } else if (key.equals(key_chat_serviceBackgroundSelected)) { - return serviceSelectedMessageColor; + if (color == null) { + if (isDefault != null) { + isDefault[0] = true; + } + if (key.equals(key_chat_serviceBackground)) { + return serviceMessageColor; + } else if (key.equals(key_chat_serviceBackgroundSelected)) { + return serviceSelectedMessageColor; + } + return getDefaultColor(key); } - return getDefaultColor(key); } return color; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/BaseLocationAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/BaseLocationAdapter.java index 6b0f9fdaf..7ef315ab0 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/BaseLocationAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/BaseLocationAdapter.java @@ -300,6 +300,7 @@ public abstract class BaseLocationAdapter extends RecyclerListView.SelectionAdap if (object.has("name")) { venue.title = object.getString("name"); } + venue.venue_type = ""; venue.venue_id = object.getString("id"); venue.provider = "foursquare"; places.add(venue); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java index c456bddf0..17949066d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java @@ -8,16 +8,31 @@ package org.telegram.ui.Adapters; +import android.app.Activity; import android.content.Context; +import android.content.SharedPreferences; +import android.util.TypedValue; +import android.view.Gravity; import android.view.View; import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesController; +import org.telegram.messenger.R; import org.telegram.messenger.support.widget.RecyclerView; +import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Cells.DialogCell; +import org.telegram.ui.Cells.DialogMeUrlCell; +import org.telegram.ui.Cells.DialogsEmptyCell; +import org.telegram.ui.Cells.HeaderCell; import org.telegram.ui.Cells.LoadingCell; +import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.RecyclerListView; import java.util.ArrayList; @@ -28,16 +43,46 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter { private int dialogsType; private long openedDialogId; private int currentCount; + private boolean isOnlySelect; + private ArrayList selectedDialogs; + private boolean hasHints; - public DialogsAdapter(Context context, int type) { + public DialogsAdapter(Context context, int type, boolean onlySelect) { mContext = context; dialogsType = type; + isOnlySelect = onlySelect; + hasHints = type == 0 && !onlySelect; + if (onlySelect) { + selectedDialogs = new ArrayList<>(); + } } public void setOpenedDialogId(long id) { openedDialogId = id; } + public boolean hasSelectedDialogs() { + return selectedDialogs != null && !selectedDialogs.isEmpty(); + } + + public void addOrRemoveSelectedDialog(long did, View cell) { + if (selectedDialogs.contains(did)) { + selectedDialogs.remove(did); + if (cell instanceof DialogCell) { + ((DialogCell) cell).setChecked(false, true); + } + } else { + selectedDialogs.add(did); + if (cell instanceof DialogCell) { + ((DialogCell) cell).setChecked(true, true); + } + } + } + + public ArrayList getSelectedDialogs() { + return selectedDialogs; + } + public boolean isDataSetChanged() { int current = currentCount; return current != getItemCount() || current == 1; @@ -50,6 +95,8 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter { return MessagesController.getInstance().dialogsServerOnly; } else if (dialogsType == 2) { return MessagesController.getInstance().dialogsGroupsOnly; + } else if (dialogsType == 3) { + return MessagesController.getInstance().dialogsForward; } return null; } @@ -60,21 +107,38 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter { if (count == 0 && MessagesController.getInstance().loadingDialogs) { return 0; } - if (!MessagesController.getInstance().dialogsEndReached) { + if (!MessagesController.getInstance().dialogsEndReached || count == 0) { count++; } + if (hasHints) { + count += 2 + MessagesController.getInstance().hintDialogs.size(); + } currentCount = count; return count; } - public TLRPC.TL_dialog getItem(int i) { + public TLObject getItem(int i) { ArrayList arrayList = getDialogsArray(); + if (hasHints) { + int count = MessagesController.getInstance().hintDialogs.size(); + if (i < 2 + count) { + return MessagesController.getInstance().hintDialogs.get(i - 1); + } else { + i -= count + 2; + } + } if (i < 0 || i >= arrayList.size()) { return null; } return arrayList.get(i); } + @Override + public void notifyDataSetChanged() { + hasHints = dialogsType == 0 && !isOnlySelect && !MessagesController.getInstance().hintDialogs.isEmpty(); + super.notifyDataSetChanged(); + } + @Override public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { if (holder.itemView instanceof DialogCell) { @@ -84,40 +148,118 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter { @Override public boolean isEnabled(RecyclerView.ViewHolder holder) { - return holder.getItemViewType() != 1; + int viewType = holder.getItemViewType(); + return viewType != 1 && viewType != 5 && viewType != 3; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { - View view = null; - if (viewType == 0) { - view = new DialogCell(mContext); - } else if (viewType == 1) { - view = new LoadingCell(mContext); + View view; + switch (viewType) { + case 0: + view = new DialogCell(mContext, isOnlySelect); + break; + case 1: + view = new LoadingCell(mContext); + break; + case 2: + HeaderCell headerCell = new HeaderCell(mContext); + headerCell.setText(LocaleController.getString("RecentlyViewed", R.string.RecentlyViewed)); + + TextView textView = new TextView(mContext); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueHeader)); + textView.setText(LocaleController.getString("RecentlyViewedHide", R.string.RecentlyViewedHide)); + textView.setGravity((LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.CENTER_VERTICAL); + headerCell.addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.TOP, 17, 15, 17, 0)); + textView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + MessagesController.getInstance().hintDialogs.clear(); + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + preferences.edit().remove("installReferer").commit(); + notifyDataSetChanged(); + } + }); + + view = headerCell; + break; + case 3: + FrameLayout frameLayout = new FrameLayout(mContext) { + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(12), MeasureSpec.EXACTLY)); + } + }; + frameLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); + View v = new View(mContext); + v.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + frameLayout.addView(v, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + view = frameLayout; + break; + case 4: + view = new DialogMeUrlCell(mContext); + break; + case 5: + default: + view = new DialogsEmptyCell(mContext); + break; } - view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); + view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, viewType == 5 ? RecyclerView.LayoutParams.MATCH_PARENT : RecyclerView.LayoutParams.WRAP_CONTENT)); return new RecyclerListView.Holder(view); } @Override - public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) { - if (viewHolder.getItemViewType() == 0) { - DialogCell cell = (DialogCell) viewHolder.itemView; - cell.useSeparator = (i != getItemCount() - 1); - TLRPC.TL_dialog dialog = getItem(i); - if (dialogsType == 0) { - if (AndroidUtilities.isTablet()) { - cell.setDialogSelected(dialog.id == openedDialogId); + public void onBindViewHolder(RecyclerView.ViewHolder holder, int i) { + switch (holder.getItemViewType()) { + case 0: { + DialogCell cell = (DialogCell) holder.itemView; + TLRPC.TL_dialog dialog = (TLRPC.TL_dialog) getItem(i); + if (hasHints) { + i -= 2 + MessagesController.getInstance().hintDialogs.size(); } + cell.useSeparator = (i != getItemCount() - 1); + if (dialogsType == 0) { + if (AndroidUtilities.isTablet()) { + cell.setDialogSelected(dialog.id == openedDialogId); + } + } + if (selectedDialogs != null) { + cell.setChecked(selectedDialogs.contains(dialog.id), false); + } + cell.setDialog(dialog, i, dialogsType); + break; + } + case 4: { + DialogMeUrlCell cell = (DialogMeUrlCell) holder.itemView; + cell.setRecentMeUrl((TLRPC.RecentMeUrl) getItem(i)); + break; } - cell.setDialog(dialog, i, dialogsType); } } @Override public int getItemViewType(int i) { + if (hasHints) { + int count = MessagesController.getInstance().hintDialogs.size(); + if (i < 2 + count) { + if (i == 0) { + return 2; + } else if (i == 1 + count) { + return 3; + } + return 4; + } else { + i -= 2 + count; + } + } if (i == getDialogsArray().size()) { - return 1; + if (!MessagesController.getInstance().dialogsEndReached) { + return 1; + } else { + return 5; + } } return 0; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java index ad100855e..8c27f4b2b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java @@ -26,6 +26,7 @@ import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessageObject; import org.telegram.messenger.MessagesController; import org.telegram.messenger.MessagesStorage; +import org.telegram.messenger.UserConfig; import org.telegram.messenger.query.SearchQuery; import org.telegram.messenger.support.widget.LinearLayoutManager; import org.telegram.messenger.support.widget.RecyclerView; @@ -73,6 +74,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { private int dialogsType; private SearchAdapterHelper searchAdapterHelper; private RecyclerListView innerListView; + private int selfUserId; private ArrayList recentSearchObjects = new ArrayList<>(); private HashMap recentSearchObjectsById = new HashMap<>(); @@ -91,7 +93,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { public interface DialogsSearchAdapterDelegate { void searchStateChanged(boolean searching); - void didPressedOnSubDialog(int did); + void didPressedOnSubDialog(long did); void needRemoveHint(int did); } @@ -170,6 +172,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { mContext = context; needMessagesSearch = messagesSearch; dialogsType = type; + selfUserId = UserConfig.getClientUserId(); loadRecentSearch(); SearchQuery.loadHints(true); } @@ -486,6 +489,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { @Override public void run() { try { + String savedMessages = LocaleController.getString("SavedMessages", R.string.SavedMessages).toLowerCase(); String search1 = query.trim().toLowerCase(); if (search1.length() == 0) { lastSearchId = -1; @@ -542,6 +546,16 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { } cursor.dispose(); + if (savedMessages.startsWith(search1)) { + TLRPC.User user = UserConfig.getCurrentUser(); + DialogSearchResult dialogSearchResult = new DialogSearchResult(); + dialogSearchResult.date = Integer.MAX_VALUE; + dialogSearchResult.name = savedMessages; + dialogSearchResult.object = user; + dialogsResult.put((long) user.id, dialogSearchResult); + resultCount++; + } + if (!usersToLoad.isEmpty()) { cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, status, name FROM users WHERE uid IN(%s)", TextUtils.join(",", usersToLoad))); while (cursor.next()) { @@ -621,7 +635,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { } if (!encryptedToLoad.isEmpty()) { - cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT q.data, u.name, q.user, q.g, q.authkey, q.ttl, u.data, u.status, q.layer, q.seq_in, q.seq_out, q.use_count, q.exchange_id, q.key_date, q.fprint, q.fauthkey, q.khash, q.in_seq_no FROM enc_chats as q INNER JOIN users as u ON q.user = u.uid WHERE q.uid IN(%s)", TextUtils.join(",", encryptedToLoad))); + cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT q.data, u.name, q.user, q.g, q.authkey, q.ttl, u.data, u.status, q.layer, q.seq_in, q.seq_out, q.use_count, q.exchange_id, q.key_date, q.fprint, q.fauthkey, q.khash, q.in_seq_no, q.admin_id, q.mtproto_seq FROM enc_chats as q INNER JOIN users as u ON q.user = u.uid WHERE q.uid IN(%s)", TextUtils.join(",", encryptedToLoad))); while (cursor.next()) { String name = cursor.stringValue(1); String tName = LocaleController.getInstance().getTranslitString(name); @@ -674,6 +688,11 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { chat.future_auth_key = cursor.byteArrayValue(15); chat.key_hash = cursor.byteArrayValue(16); chat.in_seq_no = cursor.intValue(17); + int admin_id = cursor.intValue(18); + if (admin_id != 0) { + chat.admin_id = admin_id; + } + chat.mtproto_seq = cursor.intValue(19); if (user.status != null) { user.status.expires = cursor.intValue(7); @@ -800,6 +819,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { MessagesController.getInstance().putUsers(encUsers, true); searchResult = result; searchResultNames = names; + searchAdapterHelper.mergeResults(searchResult); notifyDataSetChanged(); } }); @@ -833,6 +853,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { searchResult.clear(); searchResultNames.clear(); searchResultHashtags.clear(); + searchAdapterHelper.mergeResults(null); if (needMessagesSearch != 2) { searchAdapterHelper.queryServerSearch(null, true, true, true, true, 0, false); } @@ -972,7 +993,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { view = new GraySectionCell(mContext); break; case 2: - view = new DialogCell(mContext); + view = new DialogCell(mContext, false); break; case 3: view = new LoadingCell(mContext); @@ -1083,15 +1104,41 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { foundUserName = foundUserName.substring(1); } try { - username = new SpannableStringBuilder(un); - ((SpannableStringBuilder) username).setSpan(new ForegroundColorSpan(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText4)), 0, foundUserName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(); + spannableStringBuilder.append("@"); + spannableStringBuilder.append(un); + if (un.startsWith(foundUserName)) { + spannableStringBuilder.setSpan(new ForegroundColorSpan(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText4)), 0, Math.min(spannableStringBuilder.length(), foundUserName.length() + 1), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + username = spannableStringBuilder; } catch (Exception e) { username = un; FileLog.e(e); } } } - cell.setData(user != null ? user : chat, encryptedChat, name, username, isRecent); + boolean savedMessages = false; + if (user != null && user.id == selfUserId) { + name = LocaleController.getString("SavedMessages", R.string.SavedMessages); + username = null; + savedMessages = true; + } + if (chat != null && chat.participants_count != 0) { + String membersString; + if (ChatObject.isChannel(chat) && !chat.megagroup) { + membersString = LocaleController.formatPluralString("Subscribers", chat.participants_count); + } else { + membersString = LocaleController.formatPluralString("Members", chat.participants_count); + } + if (username instanceof SpannableStringBuilder) { + ((SpannableStringBuilder) username).append(", ").append(membersString); + } else if (!TextUtils.isEmpty(username)) { + username = TextUtils.concat(username, ", ", membersString); + } else { + username = membersString; + } + } + cell.setData(user != null ? user : chat, encryptedChat, name, username, isRecent, savedMessages); break; } case 1: { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DrawerLayoutAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DrawerLayoutAdapter.java index b2c064fc6..51760e929 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DrawerLayoutAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DrawerLayoutAdapter.java @@ -113,12 +113,11 @@ public class DrawerLayoutAdapter extends RecyclerListView.SelectionAdapter { items.add(new Item(4, LocaleController.getString("NewChannel", R.string.NewChannel), R.drawable.menu_broadcast)); items.add(null); // divider items.add(new Item(6, LocaleController.getString("Contacts", R.string.Contacts), R.drawable.menu_contacts)); - if (MessagesController.getInstance().callsEnabled) { - items.add(new Item(10, LocaleController.getString("Calls", R.string.Calls), R.drawable.menu_calls)); - } + items.add(new Item(11, LocaleController.getString("SavedMessages", R.string.SavedMessages), R.drawable.menu_saved)); + items.add(new Item(10, LocaleController.getString("Calls", R.string.Calls), R.drawable.menu_calls)); items.add(new Item(7, LocaleController.getString("InviteFriends", R.string.InviteFriends), R.drawable.menu_invite)); items.add(new Item(8, LocaleController.getString("Settings", R.string.Settings), R.drawable.menu_settings)); - items.add(new Item(9, LocaleController.getString("TelegramFaq", R.string.TelegramFaq), R.drawable.menu_help)); + items.add(new Item(9, LocaleController.getString("TelegramFAQ", R.string.TelegramFAQ), R.drawable.menu_help)); } public int getId(int position) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/LocationActivityAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/LocationActivityAdapter.java index 102591a63..f6e442528 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/LocationActivityAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/LocationActivityAdapter.java @@ -13,18 +13,24 @@ import android.location.Location; import android.view.View; import android.view.ViewGroup; +import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; +import org.telegram.messenger.LocationController; +import org.telegram.messenger.MessageObject; import org.telegram.messenger.R; +import org.telegram.messenger.UserConfig; import org.telegram.messenger.support.widget.RecyclerView; -import org.telegram.tgnet.TLRPC; import org.telegram.ui.Cells.EmptyCell; import org.telegram.ui.Cells.GraySectionCell; import org.telegram.ui.Cells.LocationCell; import org.telegram.ui.Cells.LocationLoadingCell; import org.telegram.ui.Cells.LocationPoweredCell; import org.telegram.ui.Cells.SendLocationCell; +import org.telegram.ui.Cells.SharingLiveLocationCell; import org.telegram.ui.Components.RecyclerListView; +import org.telegram.ui.LocationActivity; +import java.util.ArrayList; import java.util.Locale; public class LocationActivityAdapter extends BaseLocationAdapter { @@ -34,10 +40,18 @@ public class LocationActivityAdapter extends BaseLocationAdapter { private SendLocationCell sendLocationCell; private Location gpsLocation; private Location customLocation; + private int liveLocationType; + private long dialogId; + private boolean pulledUp; + private int shareLiveLocationPotistion = -1; + private MessageObject currentMessageObject; + private ArrayList currentLiveLocations = new ArrayList<>(); - public LocationActivityAdapter(Context context) { + public LocationActivityAdapter(Context context, int live, long did) { super(); mContext = context; + liveLocationType = live; + dialogId = did; } public void setOverScrollHeight(int value) { @@ -45,8 +59,25 @@ public class LocationActivityAdapter extends BaseLocationAdapter { } public void setGpsLocation(Location location) { + boolean notSet = gpsLocation == null; gpsLocation = location; - updateCell(); + if (notSet && shareLiveLocationPotistion > 0) { + notifyItemChanged(shareLiveLocationPotistion); + } + if (currentMessageObject != null) { + notifyItemChanged(1); + updateLiveLocations(); + } else if (liveLocationType != 2) { + updateCell(); + } else { + updateLiveLocations(); + } + } + + public void updateLiveLocations() { + if (!currentLiveLocations.isEmpty()) { + notifyItemRangeChanged(2, currentLiveLocations.size()); + } } public void setCustomLocation(Location location) { @@ -54,6 +85,23 @@ public class LocationActivityAdapter extends BaseLocationAdapter { updateCell(); } + public void setLiveLocations(ArrayList liveLocations) { + currentLiveLocations = new ArrayList<>(liveLocations); + int uid = UserConfig.getClientUserId(); + for (int a = 0; a < currentLiveLocations.size(); a++) { + if (currentLiveLocations.get(a).id == uid) { + currentLiveLocations.remove(a); + break; + } + } + notifyDataSetChanged(); + } + + public void setMessageObject(MessageObject messageObject) { + currentMessageObject = messageObject; + notifyDataSetChanged(); + } + private void updateCell() { if (sendLocationCell != null) { if (customLocation != null) { @@ -70,10 +118,20 @@ public class LocationActivityAdapter extends BaseLocationAdapter { @Override public int getItemCount() { - if (searching || !searching && places.isEmpty()) { - return 4; + if (currentMessageObject != null) { + return 2 + (currentLiveLocations.isEmpty() ? 0 : currentLiveLocations.size() + 2); + } else if (liveLocationType == 2) { + return 2 + currentLiveLocations.size(); + } else { + if (searching || !searching && places.isEmpty()) { + return liveLocationType != 0 ? 5 : 4; + } + if (liveLocationType == 1) { + return 4 + places.size() + (places.isEmpty() ? 0 : 1); + } else { + return 3 + places.size() + (places.isEmpty() ? 0 : 1); + } } - return 3 + places.size() + (places.isEmpty() ? 0 : 1); } @Override @@ -84,7 +142,7 @@ public class LocationActivityAdapter extends BaseLocationAdapter { view = new EmptyCell(mContext); break; case 1: - view = new SendLocationCell(mContext); + view = new SendLocationCell(mContext, false); break; case 2: view = new GraySectionCell(mContext); @@ -96,13 +154,38 @@ public class LocationActivityAdapter extends BaseLocationAdapter { view = new LocationLoadingCell(mContext); break; case 5: - default: view = new LocationPoweredCell(mContext); break; + case 6: + SendLocationCell cell = new SendLocationCell(mContext, true); + cell.setDialogId(dialogId); + view = cell; + break; + case 7: + default: + view = new SharingLiveLocationCell(mContext, true); + break; } return new RecyclerListView.Holder(view); } + public void setPulledUp() { + if (pulledUp) { + return; + } + pulledUp = true; + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + notifyItemChanged(liveLocationType == 0 ? 2 : 3); + } + }); + } + + public boolean isPulledUp() { + return pulledUp; + } + @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (holder.getItemViewType()) { @@ -114,20 +197,57 @@ public class LocationActivityAdapter extends BaseLocationAdapter { updateCell(); break; case 2: - ((GraySectionCell) holder.itemView).setText(LocaleController.getString("NearbyPlaces", R.string.NearbyPlaces)); + if (currentMessageObject != null) { + ((GraySectionCell) holder.itemView).setText(LocaleController.getString("LiveLocations", R.string.LiveLocations)); + } else if (pulledUp) { + ((GraySectionCell) holder.itemView).setText(LocaleController.getString("NearbyPlaces", R.string.NearbyPlaces)); + } else { + ((GraySectionCell) holder.itemView).setText(LocaleController.getString("ShowNearbyPlaces", R.string.ShowNearbyPlaces)); + } break; case 3: - ((LocationCell) holder.itemView).setLocation(places.get(position - 3), iconUrls.get(position - 3), true); + if (liveLocationType == 0) { + ((LocationCell) holder.itemView).setLocation(places.get(position - 3), iconUrls.get(position - 3), true); + } else { + ((LocationCell) holder.itemView).setLocation(places.get(position - 4), iconUrls.get(position - 4), true); + } break; case 4: ((LocationLoadingCell) holder.itemView).setLoading(searching); break; + case 6: + ((SendLocationCell) holder.itemView).setHasLocation(gpsLocation != null); + break; + case 7: + if (currentMessageObject != null && position == 1) { + ((SharingLiveLocationCell) holder.itemView).setDialog(currentMessageObject, gpsLocation); + } else { + ((SharingLiveLocationCell) holder.itemView).setDialog(currentLiveLocations.get(position - (currentMessageObject != null ? 4 : 2)), gpsLocation); + } + break; } } - public TLRPC.TL_messageMediaVenue getItem(int i) { - if (i > 2 && i < places.size() + 3) { - return places.get(i - 3); + public Object getItem(int i) { + if (currentMessageObject != null) { + if (i == 1) { + return currentMessageObject; + } else if (i > 3 && i < places.size() + 3) { + return currentLiveLocations.get(i - 4); + } + } else if (liveLocationType == 2) { + if (i >= 2) { + return currentLiveLocations.get(i - 2); + } + return null; + } else if (liveLocationType == 1) { + if (i > 3 && i < places.size() + 3) { + return places.get(i - 4); + } + } else { + if (i > 2 && i < places.size() + 2) { + return places.get(i - 3); + } } return null; } @@ -136,21 +256,56 @@ public class LocationActivityAdapter extends BaseLocationAdapter { public int getItemViewType(int position) { if (position == 0) { return 0; - } else if (position == 1) { - return 1; - } else if (position == 2) { - return 2; - } else if (searching || !searching && places.isEmpty()) { - return 4; - } else if (position == places.size() + 3) { - return 5; + } + if (currentMessageObject != null) { + if (position == 2) { + return 2; + } else if (position == 3) { + shareLiveLocationPotistion = position; + return 6; + } else { + return 7; + } + } else if (liveLocationType == 2) { + if (position == 1) { + shareLiveLocationPotistion = position; + return 6; + } else { + return 7; + } + } else if (liveLocationType == 1) { + if (position == 1) { + return 1; + } else if (position == 2) { + shareLiveLocationPotistion = position; + return 6; + } else if (position == 3) { + return 2; + } else if (searching || !searching && places.isEmpty()) { + return 4; + } else if (position == places.size() + 4) { + return 5; + } + } else { + if (position == 1) { + return 1; + } else if (position == 2) { + return 2; + } else if (searching || !searching && places.isEmpty()) { + return 4; + } else if (position == places.size() + 3) { + return 5; + } } return 3; } @Override public boolean isEnabled(RecyclerView.ViewHolder holder) { - int position = holder.getAdapterPosition(); - return !(position == 2 || position == 0 || position == 3 && (searching || !searching && places.isEmpty()) || position == places.size() + 3); + int viewType = holder.getItemViewType(); + if (viewType == 6) { + return !(LocationController.getInstance().getSharingLocationInfo(dialogId) == null && gpsLocation == null); + } + return viewType == 1 || viewType == 3 || viewType == 7; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/MentionsAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/MentionsAdapter.java index 1eb3ebb84..4dc4acc28 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/MentionsAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/MentionsAdapter.java @@ -25,12 +25,16 @@ import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.ChatObject; +import org.telegram.messenger.ContactsController; +import org.telegram.messenger.Emoji; +import org.telegram.messenger.EmojiSuggestion; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessageObject; import org.telegram.messenger.MessagesController; import org.telegram.messenger.MessagesStorage; import org.telegram.messenger.R; import org.telegram.messenger.SendMessagesHelper; +import org.telegram.messenger.UserConfig; import org.telegram.messenger.UserObject; import org.telegram.messenger.query.SearchQuery; import org.telegram.messenger.support.widget.RecyclerView; @@ -64,9 +68,12 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter { private TLRPC.ChatFull info; private SearchAdapterHelper searchAdapterHelper; private ArrayList searchResultUsernames; + private HashMap searchResultUsernamesMap; + private Runnable searchGlobalRunnable; private ArrayList searchResultHashtags; private ArrayList searchResultCommands; private ArrayList searchResultCommandsHelp; + private ArrayList searchResultSuggestions; private ArrayList searchResultCommandsUsers; private ArrayList searchResultBotContext; private TLRPC.TL_inlineBotSwitchPM searchResultBotContextSwitch; @@ -77,6 +84,7 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter { private boolean allowNewMentions = true; private int resultLength; private String lastText; + private boolean lastUsernameOnly; private int lastPosition; private ArrayList messages; private boolean needUsernames = true; @@ -84,6 +92,9 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter { private boolean isDarkTheme; private int botsCount; private boolean inlineMediaEnabled = true; + private int channelLastReqId; + private int channelReqId; + private boolean isSearchingMentions; private String searchingContextUsername; private String searchingContextQuery; @@ -134,7 +145,7 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter { @Override public void onSetHashtags(ArrayList arrayList, HashMap hashMap) { if (lastText != null) { - searchUsernameOrHashtag(lastText, lastPosition, messages); + searchUsernameOrHashtag(lastText, lastPosition, messages, lastUsernameOnly); } } }); @@ -171,10 +182,22 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter { parentFragment = fragment; } - public void setChatInfo(TLRPC.ChatFull chatParticipants) { - info = chatParticipants; + public void setChatInfo(TLRPC.ChatFull chatInfo) { + info = chatInfo; + if (!inlineMediaEnabled && foundContextBot != null && parentFragment != null) { + TLRPC.Chat chat = parentFragment.getCurrentChat(); + if (chat != null) { + inlineMediaEnabled = ChatObject.canSendStickers(chat); + if (inlineMediaEnabled) { + searchResultUsernames = null; + notifyDataSetChanged(); + delegate.needChangePanelVisibility(false); + processFoundUser(foundContextBot); + } + } + } if (lastText != null) { - searchUsernameOrHashtag(lastText, lastPosition, messages); + searchUsernameOrHashtag(lastText, lastPosition, messages, lastUsernameOnly); } } @@ -384,7 +407,6 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter { user = res.users.get(0); MessagesController.getInstance().putUser(user, false); MessagesStorage.getInstance().putUsersAndChats(res.users, null, true, true); - } } processFoundUser(user); @@ -421,6 +443,10 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter { } } + public void setSearchingMentions(boolean value) { + isSearchingMentions = value; + } + public String getBotCaption() { if (foundContextBot != null) { return foundContextBot.bot_inline_placeholder; @@ -465,12 +491,11 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter { if (searchingContextQuery == null || !query.equals(searchingContextQuery)) { return; } - if (delegate != null) { - delegate.onContextSearch(false); - } contextQueryReqid = 0; if (cache && response == null) { searchForContextBotResults(false, user, query, offset); + } else if (delegate != null) { + delegate.onContextSearch(false); } if (response != null) { TLRPC.TL_messages_botResults res = (TLRPC.TL_messages_botResults) response; @@ -504,7 +529,9 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter { } searchResultHashtags = null; searchResultUsernames = null; + searchResultUsernamesMap = null; searchResultCommands = null; + searchResultSuggestions = null; searchResultCommandsHelp = null; searchResultCommandsUsers = null; if (added) { @@ -545,7 +572,15 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter { } } - public void searchUsernameOrHashtag(String text, int position, ArrayList messageObjects) { + public void searchUsernameOrHashtag(String text, int position, ArrayList messageObjects, boolean usernameOnly) { + if (channelReqId != 0) { + ConnectionsManager.getInstance().cancelRequest(channelReqId, true); + channelReqId = 0; + } + if (searchGlobalRunnable != null) { + AndroidUtilities.cancelRunOnUIThread(searchGlobalRunnable); + searchGlobalRunnable = null; + } if (TextUtils.isEmpty(text)) { searchForContextBot(null, null); delegate.needChangePanelVisibility(false); @@ -557,10 +592,11 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter { searchPostion--; } lastText = null; + lastUsernameOnly = usernameOnly; StringBuilder result = new StringBuilder(); int foundType = -1; boolean hasIllegalUsernameCharacters = false; - if (needBotContext && text.charAt(0) == '@') { + if (!usernameOnly && needBotContext && text.charAt(0) == '@') { int index = text.indexOf(' '); int len = text.length(); String username = null; @@ -593,56 +629,64 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter { return; } int dogPostion = -1; - for (int a = searchPostion; a >= 0; a--) { - if (a >= text.length()) { - continue; - } - char ch = text.charAt(a); - if (a == 0 || text.charAt(a - 1) == ' ' || text.charAt(a - 1) == '\n') { - if (ch == '@') { - if (needUsernames || needBotContext && a == 0) { - if (hasIllegalUsernameCharacters) { - delegate.needChangePanelVisibility(false); - return; + if (usernameOnly) { + result.append(text.substring(1)); + resultStartPosition = 0; + resultLength = result.length(); + foundType = 0; + } else { + for (int a = searchPostion; a >= 0; a--) { + if (a >= text.length()) { + continue; + } + char ch = text.charAt(a); + if (a == 0 || text.charAt(a - 1) == ' ' || text.charAt(a - 1) == '\n') { + if (ch == '@') { + if (needUsernames || needBotContext && a == 0) { + if (info == null && a != 0) { + lastText = text; + lastPosition = position; + messages = messageObjects; + delegate.needChangePanelVisibility(false); + return; + } + dogPostion = a; + foundType = 0; + resultStartPosition = a; + resultLength = result.length() + 1; + break; } - if (info == null && a != 0) { + } else if (ch == '#') { + if (searchAdapterHelper.loadRecentHashtags()) { + foundType = 1; + resultStartPosition = a; + resultLength = result.length() + 1; + result.insert(0, ch); + break; + } else { lastText = text; lastPosition = position; messages = messageObjects; delegate.needChangePanelVisibility(false); return; } - dogPostion = a; - foundType = 0; + } else if (a == 0 && botInfo != null && ch == '/') { + foundType = 2; + resultStartPosition = a; + resultLength = result.length() + 1; + break; + } else if (ch == ':' && result.length() > 0) { + foundType = 3; resultStartPosition = a; resultLength = result.length() + 1; break; } - } else if (ch == '#') { - if (searchAdapterHelper.loadRecentHashtags()) { - foundType = 1; - resultStartPosition = a; - resultLength = result.length() + 1; - result.insert(0, ch); - break; - } else { - lastText = text; - lastPosition = position; - messages = messageObjects; - delegate.needChangePanelVisibility(false); - return; - } - } else if (a == 0 && botInfo != null && ch == '/') { - foundType = 2; - resultStartPosition = a; - resultLength = result.length() + 1; - break; } + if (!(ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch == '_')) { + hasIllegalUsernameCharacters = true; + } + result.insert(0, ch); } - if (!(ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch == '_')) { - hasIllegalUsernameCharacters = true; - } - result.insert(0, ch); } if (foundType == -1) { delegate.needChangePanelVisibility(false); @@ -656,10 +700,12 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter { users.add(from_id); } } - String usernameString = result.toString().toLowerCase(); + final String usernameString = result.toString().toLowerCase(); + boolean hasSpace = usernameString.indexOf(' ') >= 0; ArrayList newResult = new ArrayList<>(); final HashMap newResultsHashMap = new HashMap<>(); - if (needBotContext && dogPostion == 0 && !SearchQuery.inlineBots.isEmpty()) { + final HashMap newMap = new HashMap<>(); + if (!usernameOnly && needBotContext && dogPostion == 0 && !SearchQuery.inlineBots.isEmpty()) { int count = 0; for (int a = 0; a < SearchQuery.inlineBots.size(); a++) { TLRPC.User user = MessagesController.getInstance().getUser(SearchQuery.inlineBots.get(a).peer.user_id); @@ -676,7 +722,7 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter { } } } - TLRPC.Chat chat; + final TLRPC.Chat chat; if (parentFragment != null) { chat = parentFragment.getCurrentChat(); } else if (info != null) { @@ -688,7 +734,7 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter { for (int a = 0; a < info.participants.participants.size(); a++) { TLRPC.ChatParticipant chatParticipant = info.participants.participants.get(a); TLRPC.User user = MessagesController.getInstance().getUser(chatParticipant.user_id); - if (user == null || UserObject.isUserSelf(user) || newResultsHashMap.containsKey(user.id)) { + if (user == null || !usernameOnly && UserObject.isUserSelf(user) || newResultsHashMap.containsKey(user.id)) { continue; } if (usernameString.length() == 0) { @@ -698,14 +744,20 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter { } else { if (user.username != null && user.username.length() > 0 && user.username.toLowerCase().startsWith(usernameString)) { newResult.add(user); + newMap.put(user.id, user); } else { if (!allowNewMentions && (user.username == null || user.username.length() == 0)) { continue; } if (user.first_name != null && user.first_name.length() > 0 && user.first_name.toLowerCase().startsWith(usernameString)) { newResult.add(user); + newMap.put(user.id, user); } else if (user.last_name != null && user.last_name.length() > 0 && user.last_name.toLowerCase().startsWith(usernameString)) { newResult.add(user); + newMap.put(user.id, user); + } else if (hasSpace && ContactsController.formatName(user.first_name, user.last_name).toLowerCase().startsWith(usernameString)) { + newResult.add(user); + newMap.put(user.id, user); } } } @@ -715,7 +767,59 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter { searchResultCommands = null; searchResultCommandsHelp = null; searchResultCommandsUsers = null; + searchResultSuggestions = null; searchResultUsernames = newResult; + searchResultUsernamesMap = newMap; + if (chat != null && chat.megagroup && usernameString.length() > 0) { + AndroidUtilities.runOnUIThread(searchGlobalRunnable = new Runnable() { + @Override + public void run() { + if (searchGlobalRunnable != this) { + return; + } + TLRPC.TL_channels_getParticipants req = new TLRPC.TL_channels_getParticipants(); + req.channel = MessagesController.getInputChannel(chat); + req.limit = 20; + req.offset = 0; + req.filter = new TLRPC.TL_channelParticipantsSearch(); + req.filter.q = usernameString; + final int currentReqId = ++channelLastReqId; + channelReqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, final TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (channelReqId != 0 && currentReqId == channelLastReqId && searchResultUsernamesMap != null && searchResultUsernames != null) { + if (error == null) { + TLRPC.TL_channels_channelParticipants res = (TLRPC.TL_channels_channelParticipants) response; + MessagesController.getInstance().putUsers(res.users, false); + if (!res.participants.isEmpty()) { + int currentUserId = UserConfig.getClientUserId(); + for (int a = 0; a < res.participants.size(); a++) { + TLRPC.ChannelParticipant participant = res.participants.get(a); + if (searchResultUsernamesMap.containsKey(participant.user_id) || !isSearchingMentions && participant.user_id == currentUserId) { + continue; + } + TLRPC.User user = MessagesController.getInstance().getUser(participant.user_id); + if (user == null) { + return; + } + searchResultUsernames.add(user); + } + notifyDataSetChanged(); + } + } + } + channelReqId = 0; + } + }); + } + }); + } + }, 200); + } + Collections.sort(searchResultUsernames, new Comparator() { @Override public int compare(TLRPC.User lhs, TLRPC.User rhs) { @@ -752,9 +856,11 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter { } searchResultHashtags = newResult; searchResultUsernames = null; + searchResultUsernamesMap = null; searchResultCommands = null; searchResultCommandsHelp = null; searchResultCommandsUsers = null; + searchResultSuggestions = null; notifyDataSetChanged(); delegate.needChangePanelVisibility(!newResult.isEmpty()); } else if (foundType == 2) { @@ -775,11 +881,50 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter { } searchResultHashtags = null; searchResultUsernames = null; + searchResultUsernamesMap = null; + searchResultSuggestions = null; searchResultCommands = newResult; searchResultCommandsHelp = newResultHelp; searchResultCommandsUsers = newResultUsers; notifyDataSetChanged(); delegate.needChangePanelVisibility(!newResult.isEmpty()); + } else if (foundType == 3) { + if (!hasIllegalUsernameCharacters) { + Object[] suggestions = Emoji.getSuggestion(result.toString()); + if (suggestions != null) { + searchResultSuggestions = new ArrayList<>(); + for (int a = 0; a < suggestions.length; a++) { + EmojiSuggestion suggestion = (EmojiSuggestion) suggestions[a]; + suggestion.emoji = suggestion.emoji.replace("\ufe0f", ""); + searchResultSuggestions.add(suggestion); + } + Emoji.loadRecentEmoji(); + Collections.sort(searchResultSuggestions, new Comparator() { + @Override + public int compare(EmojiSuggestion o1, EmojiSuggestion o2) { + Integer n1 = Emoji.emojiUseHistory.get(o1.emoji); + if (n1 == null) { + n1 = 0; + } + Integer n2 = Emoji.emojiUseHistory.get(o2.emoji); + if (n2 == null) { + n2 = 0; + } + return n2.compareTo(n1); + } + }); + } + searchResultHashtags = null; + searchResultUsernames = null; + searchResultUsernamesMap = null; + searchResultCommands = null; + searchResultCommandsHelp = null; + searchResultCommandsUsers = null; + notifyDataSetChanged(); + delegate.needChangePanelVisibility(searchResultSuggestions != null); + } else { + delegate.needChangePanelVisibility(false); + } } } @@ -808,6 +953,8 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter { return searchResultHashtags.size(); } else if (searchResultCommands != null) { return searchResultCommands.size(); + } else if (searchResultSuggestions != null) { + return searchResultSuggestions.size(); } return 0; } @@ -860,6 +1007,11 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter { return null; } return searchResultHashtags.get(i); + } else if (searchResultSuggestions != null) { + if (i < 0 || i >= searchResultSuggestions.size()) { + return null; + } + return searchResultSuggestions.get(i); } else if (searchResultCommands != null) { if (i < 0 || i >= searchResultCommands.size()) { return null; @@ -962,6 +1114,8 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter { ((MentionCell) holder.itemView).setUser(searchResultUsernames.get(position)); } else if (searchResultHashtags != null) { ((MentionCell) holder.itemView).setText(searchResultHashtags.get(position)); + } else if (searchResultSuggestions != null) { + ((MentionCell) holder.itemView).setEmojiSuggestion(searchResultSuggestions.get(position)); } else if (searchResultCommands != null) { ((MentionCell) holder.itemView).setBotCommand(searchResultCommands.get(position), searchResultCommandsHelp.get(position), searchResultCommandsUsers != null ? searchResultCommandsUsers.get(position) : null); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapter.java index 495b7a5a6..ee1268c3b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapter.java @@ -303,7 +303,7 @@ public class SearchAdapter extends RecyclerListView.SelectionAdapter { } } else { ProfileSearchCell profileSearchCell = (ProfileSearchCell) holder.itemView; - profileSearchCell.setData(object, null, name, username, false); + profileSearchCell.setData(object, null, name, username, false, false); profileSearchCell.useSeparator = (position != getItemCount() - 1 && position != searchResult.size() - 1); /*if (ignoreUsers != null) { if (ignoreUsers.containsKey(id)) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapterHelper.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapterHelper.java index fe8fd75b4..428e49ef5 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapterHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapterHelper.java @@ -44,8 +44,10 @@ public class SearchAdapterHelper { private int lastReqId; private String lastFoundUsername = null; private ArrayList globalSearch = new ArrayList<>(); + private HashMap globalSearchMap = new HashMap<>(); private ArrayList groupSearch = new ArrayList<>(); private ArrayList groupSearch2 = new ArrayList<>(); + private ArrayList localSearchResults; private int channelReqId = 0; private int channelLastReqId; @@ -59,6 +61,12 @@ public class SearchAdapterHelper { private HashMap hashtagsByText; private boolean hashtagsLoadedFromDb = false; + protected static final class DialogSearchResult { + public TLObject object; + public int date; + public CharSequence name; + } + public void queryServerSearch(final String query, final boolean allowUsername, final boolean allowChats, final boolean allowBots, final boolean allowSelf, final int channelId, final boolean kicked) { if (reqId != 0) { ConnectionsManager.getInstance().cancelRequest(reqId, true); @@ -76,6 +84,7 @@ public class SearchAdapterHelper { groupSearch.clear(); groupSearch2.clear(); globalSearch.clear(); + globalSearchMap.clear(); lastReqId = 0; channelLastReqId = 0; channelLastReqId2 = 0; @@ -165,9 +174,15 @@ public class SearchAdapterHelper { if (error == null) { TLRPC.TL_contacts_found res = (TLRPC.TL_contacts_found) response; globalSearch.clear(); + globalSearchMap.clear(); + MessagesController.getInstance().putChats(res.chats, false); + MessagesController.getInstance().putUsers(res.users, false); + MessagesStorage.getInstance().putUsersAndChats(res.users, res.chats, true, true); if (allowChats) { for (int a = 0; a < res.chats.size(); a++) { - globalSearch.add(res.chats.get(a)); + TLRPC.Chat chat = res.chats.get(a); + globalSearch.add(chat); + globalSearchMap.put(-chat.id, chat); } } for (int a = 0; a < res.users.size(); a++) { @@ -175,9 +190,13 @@ public class SearchAdapterHelper { if (!allowBots && user.bot || !allowSelf && user.self) { continue; } - globalSearch.add(res.users.get(a)); + globalSearch.add(user); + globalSearchMap.put(user.id, user); } lastFoundUsername = query.toLowerCase(); + if (localSearchResults != null) { + mergeResults(localSearchResults); + } delegate.onDataSetChanged(); } } @@ -188,6 +207,7 @@ public class SearchAdapterHelper { }, ConnectionsManager.RequestFlagFailOnServerErrors); } else { globalSearch.clear(); + globalSearchMap.clear(); lastReqId = 0; delegate.onDataSetChanged(); } @@ -243,6 +263,32 @@ public class SearchAdapterHelper { return false; } + public void mergeResults(ArrayList localResults) { + localSearchResults = localResults; + if (globalSearch.isEmpty() || localResults == null) { + return; + } + int count = localResults.size(); + for (int a = 0; a < count; a++) { + TLObject obj = localResults.get(a); + if (obj instanceof TLRPC.User) { + TLRPC.User user = (TLRPC.User) obj; + TLRPC.User u = (TLRPC.User) globalSearchMap.get(user.id); + if (u != null) { + globalSearch.remove(u); + globalSearchMap.remove(u.id); + } + } else if (obj instanceof TLRPC.Chat) { + TLRPC.Chat chat = (TLRPC.Chat) obj; + TLRPC.Chat c = (TLRPC.Chat) globalSearchMap.get(-chat.id); + if (c != null) { + globalSearch.remove(c); + globalSearchMap.remove(-c.id); + } + } + } + } + public void setDelegate(SearchAdapterHelperDelegate searchAdapterHelperDelegate) { delegate = searchAdapterHelperDelegate; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/StickersAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/StickersAdapter.java index 99f13e465..f4f49a0a7 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/StickersAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/StickersAdapter.java @@ -111,9 +111,15 @@ public class StickersAdapter extends RecyclerListView.SelectionAdapter implement stickers = newStickers != null && !newStickers.isEmpty() ? new ArrayList<>(newStickers) : null; if (stickers != null) { final ArrayList recentStickers = StickersQuery.getRecentStickersNoCopy(StickersQuery.TYPE_IMAGE); + final ArrayList favsStickers = StickersQuery.getRecentStickersNoCopy(StickersQuery.TYPE_FAVE); if (!recentStickers.isEmpty()) { Collections.sort(stickers, new Comparator() { private int getIndex(long id) { + for (int a = 0; a < favsStickers.size(); a++) { + if (favsStickers.get(a).id == id) { + return a + 1000; + } + } for (int a = 0; a < recentStickers.size(); a++) { if (recentStickers.get(a).id == id) { return a; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ArticleViewer.java b/TMessagesProj/src/main/java/org/telegram/ui/ArticleViewer.java index d9f22b8bd..6d5f61baf 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ArticleViewer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ArticleViewer.java @@ -14,7 +14,6 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; -import android.annotation.TargetApi; import android.app.Activity; import android.app.Dialog; import android.content.Context; @@ -39,6 +38,7 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.support.v4.content.FileProvider; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; import android.text.Layout; @@ -78,6 +78,7 @@ import android.widget.Toast; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.BuildConfig; import org.telegram.messenger.Emoji; import org.telegram.messenger.FileLoader; import org.telegram.messenger.FileLog; @@ -135,7 +136,6 @@ import java.util.Calendar; import java.util.HashMap; import java.util.Locale; -@TargetApi(16) public class ArticleViewer implements NotificationCenter.NotificationCenterDelegate, GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener { private Activity parentActivity; @@ -218,9 +218,11 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg private boolean drawBlockSelection; private LinkPath urlPath = new LinkPath(); - public ArrayList blocks = new ArrayList<>(); - public ArrayList photoBlocks = new ArrayList<>(); - public HashMap anchors = new HashMap<>(); + private ArrayList blocks = new ArrayList<>(); + private ArrayList photoBlocks = new ArrayList<>(); + private HashMap anchors = new HashMap<>(); + private HashMap audioBlocks = new HashMap<>(); + private ArrayList audioMessages = new ArrayList<>(); @SuppressLint("StaticFieldLeak") private static volatile ArticleViewer Instance = null; @@ -953,6 +955,8 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg channelBlock = null; blocks.clear(); photoBlocks.clear(); + audioBlocks.clear(); + audioMessages.clear(); int numBlocks = 0; int count = currentPage.cached_page.blocks.size(); for (int a = 0; a < count; a++) { @@ -962,6 +966,22 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg } else if (block instanceof TLRPC.TL_pageBlockAnchor) { anchors.put(block.name.toLowerCase(), blocks.size()); continue; + } else if (block instanceof TLRPC.TL_pageBlockAudio) { + + TLRPC.TL_message message = new TLRPC.TL_message(); + message.out = true; + message.id = block.mid = ((int) currentPage.id) + a; + message.to_id = new TLRPC.TL_peerUser(); + message.to_id.user_id = message.from_id = UserConfig.getClientUserId(); + message.date = (int) (System.currentTimeMillis() / 1000); + message.message = "-1"; + message.media = new TLRPC.TL_messageMediaDocument(); + message.media.flags |= 3; + message.media.document = getDocumentWithId(block.audio_id); + message.flags |= TLRPC.MESSAGE_FLAG_HAS_MEDIA | TLRPC.MESSAGE_FLAG_HAS_FROM_ID; + MessageObject messageObject = new MessageObject(message, null, false); + audioMessages.add(messageObject); + audioBlocks.put((TLRPC.TL_pageBlockAudio) block, messageObject); } setRichTextParents(null, block.text); setRichTextParents(null, block.caption); @@ -984,8 +1004,6 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg setRichTextParents(null, innerBlock.items.get(i).text); setRichTextParents(null, innerBlock.items.get(i).caption); } - } else if (block instanceof TLRPC.TL_pageBlockAudio) { - block.mid = ((int) currentPage.id) + a; } if (a == 0) { block.first = true; @@ -1485,7 +1503,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg } else { paint = getTextPaint(richText, richText, parentBlock); } - if (parentBlock instanceof TLRPC.TL_pageBlockPullquote || richText != null && parentBlock != null && richText == parentBlock.caption) { + if (parentBlock instanceof TLRPC.TL_pageBlockPullquote || richText != null && parentBlock != null && !(parentBlock instanceof TLRPC.TL_pageBlockBlockquote) && richText == parentBlock.caption) { return new StaticLayout(text, paint, width, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); } else { return new StaticLayout(text, paint, width, Layout.Alignment.ALIGN_NORMAL, 1.0f, AndroidUtilities.dp(4), false); @@ -1701,7 +1719,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg if (captionTextView != null) { captionTextView.invalidate(); } - } else if (id == NotificationCenter.messagePlayingDidStarted) { + } else if (id == NotificationCenter.messagePlayingDidStarted) { MessageObject messageObject = (MessageObject) args[0]; if (listView != null) { @@ -1854,6 +1872,9 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg if (quoteLinePaint != null) { quoteLinePaint.setColor(getTextColor()); } + if (listTextPointerPaint != null) { + listTextPointerPaint.setColor(getTextColor()); + } if (preformattedBackgroundPaint != null) { if (currentColor == 0) { preformattedBackgroundPaint.setColor(0xfff5f8fc); @@ -1957,6 +1978,10 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg public void setParentActivity(Activity activity, BaseFragment fragment) { parentFragment = fragment; + NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingProgressDidChanged); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingDidReset); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingPlayStateChanged); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingDidStarted); if (parentActivity == activity) { updatePaintColors(); return; @@ -2558,29 +2583,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg rightImage.setCrossfadeAlpha((byte) 2); rightImage.setInvalidateAll(true); - NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingProgressDidChanged); - NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingDidReset); - NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingPlayStateChanged); - NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingDidStarted); - - int color = getSelectedColor(); - if (color == 0) { - backgroundPaint.setColor(0xffffffff); - listView.setGlowColor(0xfff5f6f7); - } else if (color == 1) { - backgroundPaint.setColor(0xfff5efdc); - listView.setGlowColor(0xfff5efdc); - } else if (color == 2) { - backgroundPaint.setColor(0xff141414); - listView.setGlowColor(0xff141414); - } - - for (int a = 0; a < Theme.chat_ivStatesDrawable.length; a++) { - Theme.setCombinedDrawableColor(Theme.chat_ivStatesDrawable[a][0], getTextColor(), false); - Theme.setCombinedDrawableColor(Theme.chat_ivStatesDrawable[a][0], getTextColor(), true); - Theme.setCombinedDrawableColor(Theme.chat_ivStatesDrawable[a][1], getTextColor(), false); - Theme.setCombinedDrawableColor(Theme.chat_ivStatesDrawable[a][1], getTextColor(), true); - } + updatePaintColors(); } private void showNightModeHint() { @@ -2694,22 +2697,30 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg } public boolean open(MessageObject messageObject) { - return open(messageObject, true); + return open(messageObject, null, null, true); } - private boolean open(final MessageObject messageObject, boolean first) { - if (parentActivity == null || isVisible && !collapsed || messageObject == null) { + public boolean open(TLRPC.TL_webPage webpage, String url) { + return open(null, webpage, url, true); + } + + private boolean open(final MessageObject messageObject, TLRPC.WebPage webpage, String url, boolean first) { + if (parentActivity == null || isVisible && !collapsed || messageObject == null && webpage == null) { return false; } + if (messageObject != null) { + webpage = messageObject.messageOwner.media.webpage; + } if (first) { TLRPC.TL_messages_getWebPage req = new TLRPC.TL_messages_getWebPage(); - req.url = messageObject.messageOwner.media.webpage.url; - if (messageObject.messageOwner.media.webpage.cached_page instanceof TLRPC.TL_pagePart) { + req.url = webpage.url; + if (webpage.cached_page instanceof TLRPC.TL_pagePart) { req.hash = 0; } else { - req.hash = messageObject.messageOwner.media.webpage.hash; + req.hash = webpage.hash; } + final TLRPC.WebPage webPageFinal = webpage; ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override public void run(TLObject response, TLRPC.TL_error error) { @@ -2721,8 +2732,10 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - if (!pagesStack.isEmpty() && pagesStack.get(0) == messageObject.messageOwner.media.webpage && webPage.cached_page != null) { - messageObject.messageOwner.media.webpage = webPage; + if (!pagesStack.isEmpty() && pagesStack.get(0) == webPageFinal && webPage.cached_page != null) { + if (messageObject != null) { + messageObject.messageOwner.media.webpage = webPage; + } pagesStack.set(0, webPage); if (pagesStack.size() == 1) { currentPage = webPage; @@ -2759,27 +2772,34 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg layoutManager.scrollToPositionWithOffset(0, 0); checkScroll(-AndroidUtilities.dp(56)); - TLRPC.WebPage webPage = messageObject.messageOwner.media.webpage; - String webPageUrl = webPage.url.toLowerCase(); - int index; String anchor = null; - for (int a = 0; a < messageObject.messageOwner.entities.size(); a++) { - TLRPC.MessageEntity entity = messageObject.messageOwner.entities.get(a); - if (entity instanceof TLRPC.TL_messageEntityUrl) { - try { - String url = messageObject.messageOwner.message.substring(entity.offset, entity.offset + entity.length).toLowerCase(); - if (url.contains(webPageUrl) || webPageUrl.contains(url)) { - if ((index = url.lastIndexOf('#')) != -1) { - anchor = url.substring(index + 1); + if (messageObject != null) { + webpage = messageObject.messageOwner.media.webpage; + String webPageUrl = webpage.url.toLowerCase(); + int index; + for (int a = 0; a < messageObject.messageOwner.entities.size(); a++) { + TLRPC.MessageEntity entity = messageObject.messageOwner.entities.get(a); + if (entity instanceof TLRPC.TL_messageEntityUrl) { + try { + url = messageObject.messageOwner.message.substring(entity.offset, entity.offset + entity.length).toLowerCase(); + if (url.contains(webPageUrl) || webPageUrl.contains(url)) { + if ((index = url.lastIndexOf('#')) != -1) { + anchor = url.substring(index + 1); + } + break; } - break; + } catch (Exception e) { + FileLog.e(e); } - } catch (Exception e) { - FileLog.e(e); } } + } else if (url != null) { + int index; + if ((index = url.lastIndexOf('#')) != -1) { + anchor = url.substring(index + 1); + } } - addPageToStack(webPage, anchor); + addPageToStack(webpage, anchor); lastInsets = null; if (!isVisible) { @@ -2832,6 +2852,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg containerView.setLayerType(View.LAYER_TYPE_NONE, null); } animationInProgress = 0; + AndroidUtilities.hideKeyboard(parentActivity.getCurrentFocus()); } }; @@ -2856,7 +2877,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - NotificationCenter.getInstance().setAllowedNotificationsDutingAnimation(new int[]{NotificationCenter.dialogsNeedReload, NotificationCenter.closeChats, NotificationCenter.mediaCountDidLoaded, NotificationCenter.mediaDidLoaded, NotificationCenter.dialogPhotosLoaded}); + NotificationCenter.getInstance().setAllowedNotificationsDutingAnimation(new int[]{NotificationCenter.dialogsNeedReload, NotificationCenter.closeChats}); NotificationCenter.getInstance().setAnimationInProgress(true); animatorSet.start(); } @@ -4068,20 +4089,10 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg public void setBlock(TLRPC.TL_pageBlockAudio block, boolean first, boolean last) { currentBlock = block; - currentDocument = getDocumentWithId(currentBlock.audio_id); - TLRPC.TL_message message = new TLRPC.TL_message(); - message.out = true; - message.id = currentBlock.mid; - message.to_id = new TLRPC.TL_peerUser(); - message.to_id.user_id = message.from_id = UserConfig.getClientUserId(); - message.date = (int) (System.currentTimeMillis() / 1000); - message.message = "-1"; - message.media = new TLRPC.TL_messageMediaDocument(); - message.media.flags |= 3; - message.media.document = currentDocument; - message.flags |= TLRPC.MESSAGE_FLAG_HAS_MEDIA | TLRPC.MESSAGE_FLAG_HAS_FROM_ID; - currentMessageObject = new MessageObject(message, null, false); + + currentMessageObject = audioBlocks.get(currentBlock); + currentDocument = currentMessageObject.getDocument(); lastCreatedWidth = 0; isFirst = first; @@ -4303,7 +4314,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg private void didPressedButton(boolean animated) { if (buttonState == 0) { - if (MediaController.getInstance().playMessage(currentMessageObject)) { + if (MediaController.getInstance().setPlaylist(audioMessages, currentMessageObject, false)) { buttonState = 1; radialProgress.setBackground(getDrawableForCurrentState(), false, false); invalidate(); @@ -4573,10 +4584,12 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg @Override public boolean onTouchEvent(MotionEvent event) { - if (currentBlock.allow_scrolling) { - requestDisallowInterceptTouchEvent(true); - } else { - windowView.requestDisallowInterceptTouchEvent(true); + if (currentBlock != null) { + if (currentBlock.allow_scrolling) { + requestDisallowInterceptTouchEvent(true); + } else { + windowView.requestDisallowInterceptTouchEvent(true); + } } return super.onTouchEvent(event); } @@ -4710,9 +4723,16 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg return; } customView = view; - fullscreenVideoContainer.setVisibility(VISIBLE); - fullscreenVideoContainer.addView(view, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); customViewCallback = callback; + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (customView != null) { + fullscreenVideoContainer.addView(customView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + fullscreenVideoContainer.setVisibility(VISIBLE); + } + } + }, 100); } @Override @@ -4776,13 +4796,18 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg try { if (currentBlock.html != null) { - webView.loadData(currentBlock.html, "text/html", "UTF-8"); + webView.loadDataWithBaseURL("https://telegram.org/embed", currentBlock.html, "text/html", "UTF-8", null); + videoView.setVisibility(INVISIBLE); + videoView.loadVideo(null, null, null, false); + webView.setVisibility(VISIBLE); } else { TLRPC.Photo thumb = currentBlock.poster_photo_id != 0 ? getPhotoWithId(currentBlock.poster_photo_id) : null; boolean handled = videoView.loadVideo(block.url, thumb, null, currentBlock.autoplay); if (handled) { webView.setVisibility(INVISIBLE); videoView.setVisibility(VISIBLE); + webView.stopLoading(); + webView.loadUrl("about:blank"); } else { webView.setVisibility(VISIBLE); videoView.setVisibility(INVISIBLE); @@ -5327,12 +5352,14 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg TLRPC.RichText item = currentBlock.items.get(a); if (a == 0) { StaticLayout textLayout = createLayoutForText(null, item, width - AndroidUtilities.dp(6 + 18) - maxLetterWidth, currentBlock); - int lCount = textLayout.getLineCount(); - for (int b = 0; b < lCount; b++) { - if (textLayout.getLineLeft(b) > 0) { - hasRtl = true; - isRtl = 1; - break; + if (textLayout != null) { + int lCount = textLayout.getLineCount(); + for (int b = 0; b < lCount; b++) { + if (textLayout.getLineLeft(b) > 0) { + hasRtl = true; + isRtl = 1; + break; + } } } } @@ -5673,7 +5700,11 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg if (currentBlock != null) { if (lastCreatedWidth != width) { - textLayout = createLayoutForText(null, currentBlock.text, width - AndroidUtilities.dp(36 + 14), currentBlock); + int textWidth = width - AndroidUtilities.dp(36 + 14); + if (currentBlock.level > 0) { + textWidth -= AndroidUtilities.dp(14 * currentBlock.level); + } + textLayout = createLayoutForText(null, currentBlock.text, textWidth, currentBlock); hasRtl = false; if (textLayout != null) { height += AndroidUtilities.dp(8) + textLayout.getHeight(); @@ -5686,14 +5717,22 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg } } } - if (hasRtl) { - textX = AndroidUtilities.dp(14); + if (currentBlock.level > 0) { + if (hasRtl) { + textX = AndroidUtilities.dp(14 + currentBlock.level * 14); + } else { + textX = AndroidUtilities.dp(14 * currentBlock.level) + AndroidUtilities.dp(18 + 14); + } } else { - textX = AndroidUtilities.dp(18 + 14); + if (hasRtl) { + textX = AndroidUtilities.dp(14); + } else { + textX = AndroidUtilities.dp(18 + 14); + } } - textLayout2 = createLayoutForText(null, currentBlock.caption, width - AndroidUtilities.dp(36 + 14), currentBlock); + textLayout2 = createLayoutForText(null, currentBlock.caption, textWidth, currentBlock); if (textLayout2 != null) { - textY2 = height + AndroidUtilities.dp(2); + textY2 = height + AndroidUtilities.dp(8); height += AndroidUtilities.dp(8) + textLayout2.getHeight(); } if (height != 0) { @@ -5731,7 +5770,10 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg int x = getMeasuredWidth() - AndroidUtilities.dp(20); canvas.drawRect(x, AndroidUtilities.dp(6), x + AndroidUtilities.dp(2), getMeasuredHeight() - AndroidUtilities.dp(6), quoteLinePaint); } else { - canvas.drawRect(AndroidUtilities.dp(18), AndroidUtilities.dp(6), AndroidUtilities.dp(20), getMeasuredHeight() - AndroidUtilities.dp(6), quoteLinePaint); + canvas.drawRect(AndroidUtilities.dp(18 + currentBlock.level * 14), AndroidUtilities.dp(6), AndroidUtilities.dp(20 + currentBlock.level * 14), getMeasuredHeight() - AndroidUtilities.dp(6), quoteLinePaint); + } + if (currentBlock.level > 0) { + canvas.drawRect(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(20), getMeasuredHeight() - (currentBlock.bottom ? AndroidUtilities.dp(6) : 0), quoteLinePaint); } } } @@ -6130,7 +6172,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg if (spans != null && spans.length > 0) { int idx = TextUtils.indexOf(text, author); if (idx != -1) { - Spannable spannable = Spannable.Factory.getInstance().newSpannable(author); + Spannable spannable = Spannable.Factory.getInstance().newSpannable(text); text = spannable; for (int a = 0; a < spans.length; a++) { spannable.setSpan(spans[a], idx + spannableAuthor.getSpanStart(spans[a]), idx + spannableAuthor.getSpanEnd(spans[a]), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); @@ -6749,7 +6791,16 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg } else { intent.setType("image/jpeg"); } - intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(f)); + if (Build.VERSION.SDK_INT >= 24) { + try { + intent.putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(parentActivity, BuildConfig.APPLICATION_ID + ".provider", f)); + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } catch (Exception ignore) { + intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(f)); + } + } else { + intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(f)); + } parentActivity.startActivityForResult(Intent.createChooser(intent, LocaleController.getString("ShareFile", R.string.ShareFile)), 500); } else { AlertDialog.Builder builder = new AlertDialog.Builder(parentActivity); @@ -6778,7 +6829,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg private void updateVideoPlayerTime() { String newText; if (videoPlayer == null) { - newText = "00:00 / 00:00"; + newText = String.format("%02d:%02d / %02d:%02d", 0, 0, 0, 0); } else { long current = videoPlayer.getCurrentPosition() / 1000; long total = videoPlayer.getDuration(); @@ -6786,7 +6837,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg if (total != C.TIME_UNSET && current != C.TIME_UNSET) { newText = String.format("%02d:%02d / %02d:%02d", current / 60, current % 60, total / 60, total % 60); } else { - newText = "00:00 / 00:00"; + newText = String.format("%02d:%02d / %02d:%02d", 0, 0, 0, 0); } } if (!TextUtils.equals(videoPlayerTime.getText(), newText)) { @@ -7561,7 +7612,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - NotificationCenter.getInstance().setAllowedNotificationsDutingAnimation(new int[]{NotificationCenter.dialogsNeedReload, NotificationCenter.closeChats, NotificationCenter.mediaCountDidLoaded, NotificationCenter.mediaDidLoaded, NotificationCenter.dialogPhotosLoaded}); + NotificationCenter.getInstance().setAllowedNotificationsDutingAnimation(new int[]{NotificationCenter.dialogsNeedReload, NotificationCenter.closeChats}); NotificationCenter.getInstance().setAnimationInProgress(true); animatorSet.start(); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/AudioPlayerActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/AudioPlayerActivity.java deleted file mode 100644 index 336de6919..000000000 --- a/TMessagesProj/src/main/java/org/telegram/ui/AudioPlayerActivity.java +++ /dev/null @@ -1,530 +0,0 @@ -/* - * This is the source code of Telegram for Android v. 3.x.x. - * It is licensed under GNU GPL v. 2 or later. - * You should have received a copy of the license in this archive (see LICENSE). - * - * Copyright Nikolai Kudashov, 2013-2017. - */ - -package org.telegram.ui; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.util.TypedValue; -import android.view.Gravity; -import android.view.MotionEvent; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.TextView; - -import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.ImageLoader; -import org.telegram.messenger.MediaController; -import org.telegram.messenger.MessageObject; -import org.telegram.messenger.NotificationCenter; -import org.telegram.messenger.audioinfo.AudioInfo; -import org.telegram.messenger.FileLoader; -import org.telegram.messenger.R; -import org.telegram.tgnet.TLRPC; -import org.telegram.ui.ActionBar.ActionBar; -import org.telegram.ui.ActionBar.BaseFragment; -import org.telegram.ui.ActionBar.Theme; -import org.telegram.ui.ActionBar.ThemeDescription; -import org.telegram.ui.Components.LayoutHelper; -import org.telegram.ui.Components.LineProgressView; - -import java.io.File; - -public class AudioPlayerActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, MediaController.FileDownloadProgressListener { - - private MessageObject lastMessageObject; - private ImageView placeholder; - private ImageView playButton; - private ImageView shuffleButton; - private LineProgressView progressView; - private ImageView repeatButton; - private ImageView[] buttons = new ImageView[5]; - private TextView durationTextView; - private TextView timeTextView; - private SeekBarView seekBarView; - private FrameLayout seekBarContainer; - private FrameLayout bottomView; - private ImageView prevButton; - private ImageView nextButton; - - private int TAG; - - private String lastTimeString; - - private class SeekBarView extends FrameLayout { - - private Paint innerPaint1; - private Paint outerPaint1; - private int thumbWidth; - private int thumbHeight; - public int thumbX = 0; - public int thumbDX = 0; - private boolean pressed = false; - - public SeekBarView(Context context) { - super(context); - setWillNotDraw(false); - innerPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG); - innerPaint1.setColor(Theme.getColor(Theme.key_player_progressBackground)); - - outerPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG); - outerPaint1.setColor(Theme.getColor(Theme.key_player_progress)); - - thumbWidth = AndroidUtilities.dp(24); - thumbHeight = AndroidUtilities.dp(24); - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - return onTouch(ev); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - return onTouch(event); - } - - boolean onTouch(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - getParent().requestDisallowInterceptTouchEvent(true); - int additionWidth = (getMeasuredHeight() - thumbWidth) / 2; - if (thumbX - additionWidth <= ev.getX() && ev.getX() <= thumbX + thumbWidth + additionWidth && ev.getY() >= 0 && ev.getY() <= getMeasuredHeight()) { - pressed = true; - thumbDX = (int) (ev.getX() - thumbX); - invalidate(); - return true; - } - } else if (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL) { - if (pressed) { - if (ev.getAction() == MotionEvent.ACTION_UP) { - onSeekBarDrag((float) thumbX / (float) (getMeasuredWidth() - thumbWidth)); - } - pressed = false; - invalidate(); - return true; - } - } else if (ev.getAction() == MotionEvent.ACTION_MOVE) { - if (pressed) { - thumbX = (int) (ev.getX() - thumbDX); - if (thumbX < 0) { - thumbX = 0; - } else if (thumbX > getMeasuredWidth() - thumbWidth) { - thumbX = getMeasuredWidth() - thumbWidth; - } - invalidate(); - return true; - } - } - return false; - } - - public void setProgress(float progress) { - int newThumbX = (int) Math.ceil((getMeasuredWidth() - thumbWidth) * progress); - if (thumbX != newThumbX) { - thumbX = newThumbX; - if (thumbX < 0) { - thumbX = 0; - } else if (thumbX > getMeasuredWidth() - thumbWidth) { - thumbX = getMeasuredWidth() - thumbWidth; - } - invalidate(); - } - } - - public boolean isDragging() { - return pressed; - } - - @Override - protected void onDraw(Canvas canvas) { - int y = (getMeasuredHeight() - thumbHeight) / 2; - canvas.drawRect(thumbWidth / 2, getMeasuredHeight() / 2 - AndroidUtilities.dp(1), getMeasuredWidth() - thumbWidth / 2, getMeasuredHeight() / 2 + AndroidUtilities.dp(1), innerPaint1); - canvas.drawRect(thumbWidth / 2, getMeasuredHeight() / 2 - AndroidUtilities.dp(1), thumbWidth / 2 + thumbX, getMeasuredHeight() / 2 + AndroidUtilities.dp(1), outerPaint1); - canvas.drawCircle(thumbX + thumbWidth / 2, y + thumbHeight / 2, AndroidUtilities.dp(pressed ? 8 : 6), outerPaint1); - } - } - - @Override - public boolean onFragmentCreate() { - TAG = MediaController.getInstance().generateObserverTag(); - NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingDidReset); - NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingPlayStateChanged); - NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingDidStarted); - NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingProgressDidChanged); - return super.onFragmentCreate(); - } - - @Override - public void onFragmentDestroy() { - NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagePlayingDidReset); - NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagePlayingPlayStateChanged); - NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagePlayingDidStarted); - NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagePlayingProgressDidChanged); - MediaController.getInstance().removeLoadingFileObserver(this); - super.onFragmentDestroy(); - } - - @Override - public View createView(Context context) { - FrameLayout frameLayout = new FrameLayout(context); - frameLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); - frameLayout.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - return true; - } - }); - fragmentView = frameLayout; - - actionBar.setBackgroundColor(Theme.getColor(Theme.key_player_actionBar)); - actionBar.setBackButtonImage(R.drawable.ic_ab_back); - actionBar.setItemsColor(Theme.getColor(Theme.key_player_actionBarItems), false); - actionBar.setItemsBackgroundColor(Theme.getColor(Theme.key_player_actionBarSelector), false); - actionBar.setTitleColor(Theme.getColor(Theme.key_player_actionBarTitle)); - actionBar.setSubtitleColor(Theme.getColor(Theme.key_player_actionBarSubtitle)); - - if (!AndroidUtilities.isTablet()) { - actionBar.showActionModeTop(); - actionBar.setActionModeTopColor(Theme.getColor(Theme.key_player_actionBarTop)); - } - actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { - @Override - public void onItemClick(int id) { - if (id == -1) { - finishFragment(); - } - } - }); - - placeholder = new ImageView(context); - frameLayout.addView(placeholder, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 0, 0, 0, 66)); - - View shadow = new View(context); - shadow.setBackgroundResource(R.drawable.header_shadow_reverse); - frameLayout.addView(shadow, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 3, Gravity.BOTTOM | Gravity.LEFT, 0, 0, 0, 96)); - - seekBarContainer = new FrameLayout(context); - seekBarContainer.setBackgroundColor(Theme.getColor(Theme.key_player_seekBarBackground)); - frameLayout.addView(seekBarContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 30, Gravity.BOTTOM | Gravity.LEFT, 0, 0, 0, 66)); - - timeTextView = new TextView(context); - timeTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); - timeTextView.setTextColor(Theme.getColor(Theme.key_player_time)); - timeTextView.setGravity(Gravity.CENTER); - timeTextView.setText("0:00"); - seekBarContainer.addView(timeTextView, LayoutHelper.createFrame(44, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); - - durationTextView = new TextView(context); - durationTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); - durationTextView.setTextColor(Theme.getColor(Theme.key_player_duration)); - durationTextView.setGravity(Gravity.CENTER); - durationTextView.setText("3:00"); - seekBarContainer.addView(durationTextView, LayoutHelper.createFrame(44, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.RIGHT)); - - seekBarView = new SeekBarView(context); - seekBarContainer.addView(seekBarView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 32, 0, 32, 0)); - - progressView = new LineProgressView(context); - progressView.setVisibility(View.INVISIBLE); - progressView.setBackgroundColor(Theme.getColor(Theme.key_player_progressBackground)); - progressView.setProgressColor(Theme.getColor(Theme.key_player_progress)); - seekBarContainer.addView(progressView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 2, Gravity.CENTER_VERTICAL | Gravity.LEFT, 44, 0, 44, 0)); - - bottomView = new FrameLayout(context) { - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - int dist = ((right - left) - AndroidUtilities.dp(30 + 48 * 5)) / 4; - for (int a = 0; a < 5; a++) { - int l = AndroidUtilities.dp(15 + 48 * a) + dist * a; - int t = AndroidUtilities.dp(9); - buttons[a].layout(l, t, l + buttons[a].getMeasuredWidth(), t + buttons[a].getMeasuredHeight()); - } - } - }; - bottomView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); - frameLayout.addView(bottomView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 66, Gravity.BOTTOM | Gravity.LEFT)); - - buttons[0] = repeatButton = new ImageView(context); - repeatButton.setScaleType(ImageView.ScaleType.CENTER); - bottomView.addView(repeatButton, LayoutHelper.createFrame(48, 48, Gravity.LEFT | Gravity.TOP)); - repeatButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - MediaController.getInstance().toggleRepeatMode(); - updateRepeatButton(); - } - }); - - buttons[1] = prevButton = new ImageView(context); - prevButton.setScaleType(ImageView.ScaleType.CENTER); - prevButton.setImageDrawable(Theme.createSimpleSelectorDrawable(context, R.drawable.pl_previous, Theme.getColor(Theme.key_player_button), Theme.getColor(Theme.key_player_buttonActive))); - bottomView.addView(prevButton, LayoutHelper.createFrame(48, 48, Gravity.LEFT | Gravity.TOP)); - prevButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - MediaController.getInstance().playPreviousMessage(); - } - }); - - buttons[2] = playButton = new ImageView(context); - playButton.setScaleType(ImageView.ScaleType.CENTER); - playButton.setImageDrawable(Theme.createSimpleSelectorDrawable(context, R.drawable.pl_play, Theme.getColor(Theme.key_player_button), Theme.getColor(Theme.key_player_buttonActive))); - bottomView.addView(playButton, LayoutHelper.createFrame(48, 48, Gravity.LEFT | Gravity.TOP)); - playButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (MediaController.getInstance().isDownloadingCurrentMessage()) { - return; - } - if (MediaController.getInstance().isMessagePaused()) { - MediaController.getInstance().playMessage(MediaController.getInstance().getPlayingMessageObject()); - } else { - MediaController.getInstance().pauseMessage(MediaController.getInstance().getPlayingMessageObject()); - } - } - }); - - buttons[3] = nextButton = new ImageView(context); - nextButton.setScaleType(ImageView.ScaleType.CENTER); - nextButton.setImageDrawable(Theme.createSimpleSelectorDrawable(context, R.drawable.pl_next, Theme.getColor(Theme.key_player_button), Theme.getColor(Theme.key_player_buttonActive))); - bottomView.addView(nextButton, LayoutHelper.createFrame(48, 48, Gravity.LEFT | Gravity.TOP)); - nextButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - MediaController.getInstance().playNextMessage(); - } - }); - - buttons[4] = shuffleButton = new ImageView(context); - shuffleButton.setImageResource(R.drawable.pl_shuffle); - shuffleButton.setScaleType(ImageView.ScaleType.CENTER); - bottomView.addView(shuffleButton, LayoutHelper.createFrame(48, 48, Gravity.LEFT | Gravity.TOP)); - shuffleButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - MediaController.getInstance().toggleShuffleMusic(); - updateShuffleButton(); - } - }); - - updateTitle(false); - updateRepeatButton(); - updateShuffleButton(); - - return frameLayout; - } - - @Override - public void didReceivedNotification(int id, Object... args) { - if (id == NotificationCenter.messagePlayingDidStarted || id == NotificationCenter.messagePlayingPlayStateChanged || id == NotificationCenter.messagePlayingDidReset) { - updateTitle(id == NotificationCenter.messagePlayingDidReset && (Boolean) args[1]); - } else if (id == NotificationCenter.messagePlayingProgressDidChanged) { - MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject(); - if (messageObject != null && messageObject.isMusic()) { - updateProgress(messageObject); - } - } - } - - @Override - public void onFailedDownload(String fileName) { - - } - - @Override - public void onSuccessDownload(String fileName) { - - } - - @Override - public void onProgressDownload(String fileName, float progress) { - progressView.setProgress(progress, true); - } - - @Override - public void onProgressUpload(String fileName, float progress, boolean isEncrypted) { - - } - - @Override - public int getObserverTag() { - return TAG; - } - - private void onSeekBarDrag(float progress) { - MediaController.getInstance().seekToProgress(MediaController.getInstance().getPlayingMessageObject(), progress); - } - - private void updateShuffleButton() { - if (MediaController.getInstance().isShuffleMusic()) { - shuffleButton.setTag(Theme.key_player_buttonActive); - shuffleButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_player_buttonActive), PorterDuff.Mode.MULTIPLY)); - } else { - shuffleButton.setTag(Theme.key_player_button); - shuffleButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_player_button), PorterDuff.Mode.MULTIPLY)); - } - } - - private void updateRepeatButton() { - int mode = MediaController.getInstance().getRepeatMode(); - if (mode == 0) { - repeatButton.setImageResource(R.drawable.pl_repeat); - repeatButton.setTag(Theme.key_player_button); - repeatButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_player_button), PorterDuff.Mode.MULTIPLY)); - } else if (mode == 1) { - repeatButton.setImageResource(R.drawable.pl_repeat); - repeatButton.setTag(Theme.key_player_buttonActive); - repeatButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_player_buttonActive), PorterDuff.Mode.MULTIPLY)); - } else if (mode == 2) { - repeatButton.setImageResource(R.drawable.pl_repeat1); - repeatButton.setTag(Theme.key_player_buttonActive); - repeatButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_player_buttonActive), PorterDuff.Mode.MULTIPLY)); - } - } - - private void updateProgress(MessageObject messageObject) { - if (seekBarView != null) { - if (!seekBarView.isDragging()) { - seekBarView.setProgress(messageObject.audioProgress); - } - String timeString = String.format("%d:%02d", messageObject.audioProgressSec / 60, messageObject.audioProgressSec % 60); - if (lastTimeString == null || lastTimeString != null && !lastTimeString.equals(timeString)) { - lastTimeString = timeString; - timeTextView.setText(timeString); - } - } - } - - private void checkIfMusicDownloaded(MessageObject messageObject) { - File cacheFile = null; - if (messageObject.messageOwner.attachPath != null && messageObject.messageOwner.attachPath.length() > 0) { - cacheFile = new File(messageObject.messageOwner.attachPath); - if (!cacheFile.exists()) { - cacheFile = null; - } - } - if (cacheFile == null) { - cacheFile = FileLoader.getPathToMessage(messageObject.messageOwner); - } - if (!cacheFile.exists()) { - String fileName = messageObject.getFileName(); - MediaController.getInstance().addLoadingFileObserver(fileName, this); - Float progress = ImageLoader.getInstance().getFileProgress(fileName); - progressView.setProgress(progress != null ? progress : 0, false); - progressView.setVisibility(View.VISIBLE); - seekBarView.setVisibility(View.INVISIBLE); - playButton.setEnabled(false); - } else { - MediaController.getInstance().removeLoadingFileObserver(this); - progressView.setVisibility(View.INVISIBLE); - seekBarView.setVisibility(View.VISIBLE); - playButton.setEnabled(true); - } - } - - private void updateTitle(boolean shutdown) { - MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject(); - if (messageObject == null && shutdown || messageObject != null && !messageObject.isMusic()) { - if (parentLayout != null && !parentLayout.fragmentsStack.isEmpty() && parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 1) == this) { - finishFragment(); - } else { - removeSelfFromStack(); - } - } else { - if (messageObject == null) { - return; - } - checkIfMusicDownloaded(messageObject); - updateProgress(messageObject); - - if (MediaController.getInstance().isMessagePaused()) { - playButton.setImageDrawable(Theme.createSimpleSelectorDrawable(playButton.getContext(), R.drawable.pl_play, Theme.getColor(Theme.key_player_button), Theme.getColor(Theme.key_player_buttonActive))); - } else { - playButton.setImageDrawable(Theme.createSimpleSelectorDrawable(playButton.getContext(), R.drawable.pl_pause, Theme.getColor(Theme.key_player_button), Theme.getColor(Theme.key_player_buttonActive))); - } - if (actionBar != null) { - actionBar.setTitle(messageObject.getMusicTitle()); - actionBar.setSubtitle(messageObject.getMusicAuthor()); - } - AudioInfo audioInfo = MediaController.getInstance().getAudioInfo(); - if (audioInfo != null && audioInfo.getCover() != null) { - placeholder.setImageBitmap(audioInfo.getCover()); - placeholder.setPadding(0, 0, 0, 0); - placeholder.setScaleType(ImageView.ScaleType.CENTER_CROP); - placeholder.setTag(null); - placeholder.setColorFilter(null); - } else { - placeholder.setImageResource(R.drawable.nocover); - placeholder.setPadding(0, 0, 0, AndroidUtilities.dp(30)); - placeholder.setScaleType(ImageView.ScaleType.CENTER); - placeholder.setTag(Theme.key_player_placeholder); - placeholder.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_player_placeholder), PorterDuff.Mode.MULTIPLY)); - } - - if (durationTextView != null) { - int duration = 0; - TLRPC.Document document = messageObject.getDocument(); - if (document != null) { - for (int a = 0; a < document.attributes.size(); a++) { - TLRPC.DocumentAttribute attribute = document.attributes.get(a); - if (attribute instanceof TLRPC.TL_documentAttributeAudio) { - duration = attribute.duration; - break; - } - } - } - durationTextView.setText(duration != 0 ? String.format("%d:%02d", duration / 60, duration % 60) : "-:--"); - } - } - } - - @Override - public ThemeDescription[] getThemeDescriptions() { - return new ThemeDescription[]{ - new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), - new ThemeDescription(bottomView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), - - new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_player_actionBar), - new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_player_actionBarItems), - new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_player_actionBarTitle), - new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SUBTITLECOLOR, null, null, null, null, Theme.key_player_actionBarSubtitle), - new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_player_actionBarSelector), - new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_AM_TOPBACKGROUND, null, null, null, null, Theme.key_player_actionBarTop), - - new ThemeDescription(seekBarContainer, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_player_seekBarBackground), - - new ThemeDescription(timeTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_player_time), - new ThemeDescription(durationTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_player_time), - - new ThemeDescription(progressView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_player_progressBackground), - new ThemeDescription(progressView, ThemeDescription.FLAG_PROGRESSBAR, null, null, null, null, Theme.key_player_progress), - - new ThemeDescription(seekBarView, 0, null, seekBarView.innerPaint1, null, null, Theme.key_player_progressBackground), - new ThemeDescription(seekBarView, 0, null, seekBarView.outerPaint1, null, null, Theme.key_player_progress), - - new ThemeDescription(repeatButton, ThemeDescription.FLAG_CHECKTAG | ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_player_buttonActive), - new ThemeDescription(repeatButton, ThemeDescription.FLAG_CHECKTAG | ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_player_button), - - new ThemeDescription(shuffleButton, ThemeDescription.FLAG_CHECKTAG | ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_player_buttonActive), - new ThemeDescription(shuffleButton, ThemeDescription.FLAG_CHECKTAG | ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_player_button), - - new ThemeDescription(placeholder, ThemeDescription.FLAG_CHECKTAG | ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_player_placeholder), - - new ThemeDescription(prevButton, ThemeDescription.FLAG_IMAGECOLOR | ThemeDescription.FLAG_USEBACKGROUNDDRAWABLE, null, null, null, null, Theme.key_player_button), - new ThemeDescription(prevButton, ThemeDescription.FLAG_IMAGECOLOR | ThemeDescription.FLAG_USEBACKGROUNDDRAWABLE | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_player_buttonActive), - new ThemeDescription(playButton, ThemeDescription.FLAG_IMAGECOLOR | ThemeDescription.FLAG_USEBACKGROUNDDRAWABLE, null, null, null, null, Theme.key_player_button), - new ThemeDescription(playButton, ThemeDescription.FLAG_IMAGECOLOR | ThemeDescription.FLAG_USEBACKGROUNDDRAWABLE | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_player_buttonActive), - new ThemeDescription(nextButton, ThemeDescription.FLAG_IMAGECOLOR | ThemeDescription.FLAG_USEBACKGROUNDDRAWABLE, null, null, null, null, Theme.key_player_button), - new ThemeDescription(nextButton, ThemeDescription.FLAG_IMAGECOLOR | ThemeDescription.FLAG_USEBACKGROUNDDRAWABLE | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_player_buttonActive), - }; - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/BlockedUsersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/BlockedUsersActivity.java index f5576d7c5..7cb72b935 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/BlockedUsersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/BlockedUsersActivity.java @@ -286,7 +286,7 @@ public class BlockedUsersActivity extends BaseFragment implements NotificationCe new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"statusColor"}, null, null, сellDelegate, Theme.key_windowBackgroundWhiteGrayText), - new ThemeDescription(listView, 0, new Class[]{UserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, null, Theme.key_avatar_text), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/CallLogActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/CallLogActivity.java index 0e8adc7cf..f20130e30 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/CallLogActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/CallLogActivity.java @@ -232,16 +232,17 @@ public class CallLogActivity extends BaseFragment implements NotificationCenter. return false; } final CallLogRow row = calls.get(position); - ArrayList items=new ArrayList(); + ArrayList items = new ArrayList<>(); items.add(LocaleController.getString("Delete", R.string.Delete)); - if(VoIPHelper.canRateCall((TLRPC.TL_messageActionPhoneCall) row.calls.get(0).action)) + if (VoIPHelper.canRateCall((TLRPC.TL_messageActionPhoneCall) row.calls.get(0).action)) { items.add(LocaleController.getString("CallMessageReportProblem", R.string.CallMessageReportProblem)); + } new AlertDialog.Builder(getParentActivity()) .setTitle(LocaleController.getString("Calls", R.string.Calls)) .setItems(items.toArray(new String[items.size()]), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - switch(which){ + switch (which) { case 0: confirmAndDelete(row); break; @@ -377,7 +378,7 @@ public class CallLogActivity extends BaseFragment implements NotificationCenter. req.peer = new TLRPC.TL_inputPeerEmpty(); req.filter = new TLRPC.TL_inputMessagesFilterPhoneCalls(); req.q = ""; - req.max_id = max_id; + req.offset_id = max_id; int reqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override public void run(final TLObject response, final TLRPC.TL_error error) { @@ -395,7 +396,7 @@ public class CallLogActivity extends BaseFragment implements NotificationCenter. CallLogRow currentRow = calls.size() > 0 ? calls.get(calls.size() - 1) : null; for (int a = 0; a < msgs.messages.size(); a++) { TLRPC.Message msg = msgs.messages.get(a); - if (msg.action == null) { + if (msg.action == null || msg.action instanceof TLRPC.TL_messageActionHistoryClear) { continue; } int callType = msg.from_id == UserConfig.getClientUserId() ? TYPE_OUT : TYPE_IN; @@ -563,7 +564,7 @@ public class CallLogActivity extends BaseFragment implements NotificationCenter. subtitle.setSpan(iconMissed, ldir.length(), ldir.length() + 1, 0); break; } - cell.setData(row.user, null, null, subtitle, false); + cell.setData(row.user, null, null, subtitle, false, false); cell.useSeparator = position != calls.size() - 1 || !endReached; viewItem.button.setTag(row); } @@ -642,7 +643,7 @@ public class CallLogActivity extends BaseFragment implements NotificationCenter. new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, Theme.dialogs_offlinePaint, null, null, Theme.key_windowBackgroundWhiteGrayText3), new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, Theme.dialogs_onlinePaint, null, null, Theme.key_windowBackgroundWhiteBlueText3), new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, Theme.dialogs_namePaint, null, null, Theme.key_chats_name), - new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, null, Theme.key_avatar_text), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/CancelAccountDeletionActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/CancelAccountDeletionActivity.java index aabec9485..5899a8195 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/CancelAccountDeletionActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/CancelAccountDeletionActivity.java @@ -33,7 +33,6 @@ import android.view.KeyEvent; import android.view.View; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.inputmethod.EditorInfo; -import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ScrollView; @@ -57,6 +56,7 @@ import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Components.AlertsCreator; +import org.telegram.ui.Components.EditTextBoldCursor; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.RadialProgressView; import org.telegram.ui.Components.SlideView; @@ -428,7 +428,7 @@ public class CancelAccountDeletionActivity extends BaseFragment { private String phone; private String phoneHash; - private EditText codeField; + private EditTextBoldCursor codeField; private TextView confirmTextView; private TextView timeText; private TextView problemText; @@ -482,10 +482,12 @@ public class CancelAccountDeletionActivity extends BaseFragment { addView(confirmTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT)); } - codeField = new EditText(context); + codeField = new EditTextBoldCursor(context); codeField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); codeField.setHint(LocaleController.getString("Code", R.string.Code)); - AndroidUtilities.clearCursorDrawable(codeField); + codeField.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + codeField.setCursorWidth(1.5f); + codeField.setCursorSize(AndroidUtilities.dp(20)); codeField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); codeField.setImeOptions(EditorInfo.IME_ACTION_NEXT | EditorInfo.IME_FLAG_NO_EXTRACT_UI); codeField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/AboutLinkCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/AboutLinkCell.java index 7baf9813d..7813e35f9 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/AboutLinkCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/AboutLinkCell.java @@ -13,6 +13,7 @@ import android.content.Context; import android.graphics.Canvas; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; +import android.os.Build; import android.text.Layout; import android.text.Spannable; import android.text.SpannableStringBuilder; @@ -172,7 +173,16 @@ public class AboutLinkCell extends FrameLayout { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (stringBuilder != null) { - textLayout = new StaticLayout(stringBuilder, Theme.profile_aboutTextPaint, MeasureSpec.getSize(widthMeasureSpec) - AndroidUtilities.dp(71 + 16), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + int maxWidth = MeasureSpec.getSize(widthMeasureSpec) - AndroidUtilities.dp(71 + 16); + if (Build.VERSION.SDK_INT >= 24) { + textLayout = StaticLayout.Builder.obtain(stringBuilder, 0, stringBuilder.length(), Theme.profile_aboutTextPaint, maxWidth) + .setBreakStrategy(StaticLayout.BREAK_STRATEGY_HIGH_QUALITY) + .setHyphenationFrequency(StaticLayout.HYPHENATION_FREQUENCY_NONE) + .setAlignment(Layout.Alignment.ALIGN_NORMAL) + .build(); + } else { + textLayout = new StaticLayout(stringBuilder, Theme.profile_aboutTextPaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + } } super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec((textLayout != null ? textLayout.getHeight() : AndroidUtilities.dp(20)) + AndroidUtilities.dp(16), MeasureSpec.EXACTLY)); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/AudioPlayerCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/AudioPlayerCell.java new file mode 100644 index 000000000..5f16c5cdd --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/AudioPlayerCell.java @@ -0,0 +1,240 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Cells; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.text.Layout; +import android.text.StaticLayout; +import android.text.TextUtils; +import android.view.View; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.FileLoader; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.ImageLoader; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MediaController; +import org.telegram.messenger.MessageObject; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.RadialProgress; + +import java.io.File; + +public class AudioPlayerCell extends View implements MediaController.FileDownloadProgressListener { + + private boolean buttonPressed; + + private int titleY = AndroidUtilities.dp(9); + private StaticLayout titleLayout; + + private int descriptionY = AndroidUtilities.dp(29); + private StaticLayout descriptionLayout; + + private MessageObject currentMessageObject; + + private int TAG; + private int buttonState; + private RadialProgress radialProgress; + + public AudioPlayerCell(Context context) { + super(context); + + radialProgress = new RadialProgress(this); + TAG = MediaController.getInstance().generateObserverTag(); + } + + @SuppressLint("DrawAllocation") + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + descriptionLayout = null; + titleLayout = null; + + int viewWidth = MeasureSpec.getSize(widthMeasureSpec); + int maxWidth = viewWidth - AndroidUtilities.dp(AndroidUtilities.leftBaseline) - AndroidUtilities.dp(8 + 20); + + try { + String title = currentMessageObject.getMusicTitle(); + int width = (int) Math.ceil(Theme.chat_contextResult_titleTextPaint.measureText(title)); + CharSequence titleFinal = TextUtils.ellipsize(title.replace('\n', ' '), Theme.chat_contextResult_titleTextPaint, Math.min(width, maxWidth), TextUtils.TruncateAt.END); + titleLayout = new StaticLayout(titleFinal, Theme.chat_contextResult_titleTextPaint, maxWidth + AndroidUtilities.dp(4), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + } catch (Exception e) { + FileLog.e(e); + } + + try { + String author = currentMessageObject.getMusicAuthor(); + int width = (int) Math.ceil(Theme.chat_contextResult_descriptionTextPaint.measureText(author)); + CharSequence authorFinal = TextUtils.ellipsize(author.replace('\n', ' '), Theme.chat_contextResult_descriptionTextPaint, Math.min(width, maxWidth), TextUtils.TruncateAt.END); + descriptionLayout = new StaticLayout(authorFinal, Theme.chat_contextResult_descriptionTextPaint, maxWidth + AndroidUtilities.dp(4), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + } catch (Exception e) { + FileLog.e(e); + } + + setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), AndroidUtilities.dp(56)); + + int maxPhotoWidth = AndroidUtilities.dp(52); + int x = LocaleController.isRTL ? MeasureSpec.getSize(widthMeasureSpec) - AndroidUtilities.dp(8) - maxPhotoWidth : AndroidUtilities.dp(8); + radialProgress.setProgressRect(x + AndroidUtilities.dp(4), AndroidUtilities.dp(6), x + AndroidUtilities.dp(48), AndroidUtilities.dp(50)); + } + + public void setMessageObject(MessageObject messageObject) { + currentMessageObject = messageObject; + requestLayout(); + updateButtonState(false); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + MediaController.getInstance().removeLoadingFileObserver(this); + } + + public MessageObject getMessageObject() { + return currentMessageObject; + } + + public void didPressedButton() { + if (buttonState == 0) { + if (MediaController.getInstance().findMessageInPlaylistAndPlay(currentMessageObject)) { + buttonState = 1; + radialProgress.setBackground(getDrawableForCurrentState(), false, false); + invalidate(); + } + } else if (buttonState == 1) { + boolean result = MediaController.getInstance().pauseMessage(currentMessageObject); + if (result) { + buttonState = 0; + radialProgress.setBackground(getDrawableForCurrentState(), false, false); + invalidate(); + } + } else if (buttonState == 2) { + radialProgress.setProgress(0, false); + FileLoader.getInstance().loadFile(currentMessageObject.getDocument(), true, 0); + buttonState = 4; + radialProgress.setBackground(getDrawableForCurrentState(), true, false); + invalidate(); + } else if (buttonState == 4) { + FileLoader.getInstance().cancelLoadFile(currentMessageObject.getDocument()); + buttonState = 2; + radialProgress.setBackground(getDrawableForCurrentState(), false, false); + invalidate(); + } + } + + @Override + protected void onDraw(Canvas canvas) { + if (titleLayout != null) { + canvas.save(); + canvas.translate(AndroidUtilities.dp(LocaleController.isRTL ? 8 : AndroidUtilities.leftBaseline), titleY); + titleLayout.draw(canvas); + canvas.restore(); + } + + if (descriptionLayout != null) { + Theme.chat_contextResult_descriptionTextPaint.setColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2)); + canvas.save(); + canvas.translate(AndroidUtilities.dp(LocaleController.isRTL ? 8 : AndroidUtilities.leftBaseline), descriptionY); + descriptionLayout.draw(canvas); + canvas.restore(); + } + + radialProgress.setProgressColor(Theme.getColor(buttonPressed ? Theme.key_chat_inAudioSelectedProgress : Theme.key_chat_inAudioProgress)); + radialProgress.draw(canvas); + } + + private Drawable getDrawableForCurrentState() { + if (buttonState == -1) { + return null; + } + radialProgress.setAlphaForPrevious(false); + return Theme.chat_fileStatesDrawable[buttonState + 5][buttonPressed ? 1 : 0]; + } + + public void updateButtonState(boolean animated) { + String fileName = currentMessageObject.getFileName(); + File cacheFile = null; + if (!TextUtils.isEmpty(currentMessageObject.messageOwner.attachPath)) { + cacheFile = new File(currentMessageObject.messageOwner.attachPath); + if (!cacheFile.exists()) { + cacheFile = null; + } + } + if (cacheFile == null) { + cacheFile = FileLoader.getPathToAttach(currentMessageObject.getDocument()); + } + if (TextUtils.isEmpty(fileName)) { + radialProgress.setBackground(null, false, false); + return; + } + if (cacheFile.exists() && cacheFile.length() == 0) { + cacheFile.delete(); + } + if (!cacheFile.exists()) { + MediaController.getInstance().addLoadingFileObserver(fileName, this); + boolean isLoading = FileLoader.getInstance().isLoadingFile(fileName); + if (!isLoading) { + buttonState = 2; + radialProgress.setProgress(0, animated); + radialProgress.setBackground(getDrawableForCurrentState(), false, animated); + } else { + buttonState = 4; + Float progress = ImageLoader.getInstance().getFileProgress(fileName); + if (progress != null) { + radialProgress.setProgress(progress, animated); + } else { + radialProgress.setProgress(0, animated); + } + radialProgress.setBackground(getDrawableForCurrentState(), true, animated); + } + invalidate(); + } else { + MediaController.getInstance().removeLoadingFileObserver(this); + boolean playing = MediaController.getInstance().isPlayingMessage(currentMessageObject); + if (!playing || playing && MediaController.getInstance().isMessagePaused()) { + buttonState = 0; + } else { + buttonState = 1; + } + radialProgress.setBackground(getDrawableForCurrentState(), false, animated); + invalidate(); + } + } + + @Override + public void onFailedDownload(String fileName) { + updateButtonState(false); + } + + @Override + public void onSuccessDownload(String fileName) { + radialProgress.setProgress(1, true); + updateButtonState(true); + } + + @Override + public void onProgressDownload(String fileName, float progress) { + radialProgress.setProgress(progress, true); + if (buttonState != 4) { + updateButtonState(false); + } + } + + @Override + public void onProgressUpload(String fileName, float progress, boolean isEncrypted) { + + } + + @Override + public int getObserverTag() { + return TAG; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/BaseCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/BaseCell.java index 2066ac4d5..389d77e61 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/BaseCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/BaseCell.java @@ -12,10 +12,10 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.view.HapticFeedbackConstants; import android.view.MotionEvent; -import android.view.View; import android.view.ViewConfiguration; +import android.view.ViewGroup; -public class BaseCell extends View { +public abstract class BaseCell extends ViewGroup { private final class CheckForTap implements Runnable { public void run() { @@ -49,6 +49,7 @@ public class BaseCell extends View { public BaseCell(Context context) { super(context); + setWillNotDraw(false); } protected void setDrawableBounds(Drawable drawable, int x, int y) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatActionCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatActionCell.java index e4108d294..4a0ac7a8d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatActionCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatActionCell.java @@ -151,6 +151,11 @@ public class ChatActionCell extends BaseCell { } } + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + + } + @Override public boolean onTouchEvent(MotionEvent event) { if (currentMessageObject == null) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java index 09cbb2491..187639122 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java @@ -11,9 +11,11 @@ package org.telegram.ui.Cells; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.ColorStateList; +import android.content.res.Configuration; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; +import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Typeface; @@ -35,6 +37,7 @@ import android.util.StateSet; import android.view.MotionEvent; import android.view.SoundEffectConstants; import android.view.View; +import android.view.ViewGroup; import android.view.ViewStructure; import org.telegram.PhoneFormat.PhoneFormat; @@ -52,6 +55,7 @@ import org.telegram.messenger.MessageObject; import org.telegram.messenger.MessagesController; import org.telegram.messenger.R; import org.telegram.messenger.SendMessagesHelper; +import org.telegram.messenger.UserConfig; import org.telegram.messenger.UserObject; import org.telegram.messenger.Utilities; import org.telegram.messenger.browser.Browser; @@ -93,6 +97,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate void didPressedOther(ChatMessageCell cell); void didPressedBotButton(ChatMessageCell cell, TLRPC.KeyboardButton button); void didPressedInstantButton(ChatMessageCell cell, int type); + boolean isChatAdminCell(int uid); boolean needPlayMessage(MessageObject messageObject); boolean canPerformActions(); } @@ -120,6 +125,11 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate private boolean pinnedTop; private boolean pinnedBottom; + private boolean drawPinnedTop; + private boolean drawPinnedBottom; + private MessageObject.GroupedMessages currentMessagesGroup; + private MessageObject.GroupedMessagePosition currentPosition; + private boolean groupPhotoInvisible; private int textX; private int textY; @@ -139,6 +149,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate private boolean fullyDraw; private RadialProgress radialProgress; + private boolean drawRadialCheckBackground; private ImageReceiver photoImage; private AvatarDrawable contactAvatarDrawable; @@ -157,6 +168,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate private int mediaOffsetY; private int descriptionY; private int durationWidth; + private int photosCountWidth; private int descriptionX; private int titleX; private int authorX; @@ -164,6 +176,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate private StaticLayout titleLayout; private StaticLayout descriptionLayout; private StaticLayout videoInfoLayout; + private StaticLayout photosCountLayout; private StaticLayout authorLayout; private StaticLayout instantViewLayout; private boolean drawInstantView; @@ -181,6 +194,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate private StaticLayout docTitleLayout; private int docTitleOffsetX; + private boolean locationExpired; private StaticLayout captionLayout; private int captionX; @@ -198,6 +212,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate private int buttonPressed; private int otherX; private int otherY; + private int lastHeight; private boolean imagePressed; private boolean otherPressed; private boolean photoNotSet; @@ -209,10 +224,12 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate private String currentPhotoFilterThumb; private boolean cancelLoading; + private float timeAlpha = 1.0f; private float controlsAlpha = 1.0f; private long lastControlsAlphaChangeTime; private long totalChangeTime; private boolean mediaWasInvisible; + private boolean timeWasInvisible; private CharacterStyle pressedLink; private int pressedLinkType; @@ -229,7 +246,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate private int seekBarY; private StaticLayout durationLayout; - private String lastTimeString; + private int lastTime; private int timeWidthAudio; private int timeAudioX; @@ -253,6 +270,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate private boolean isPressed; private boolean forwardName; private boolean isHighlighted; + private boolean isHighlightedAnimated; + private int highlightProgress; + private long lastHighlightProgressTime; private boolean mediaBackground; private boolean isCheckPressed = true; private boolean wasLayout; @@ -262,6 +282,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate private boolean allowAssistant; private Drawable currentBackgroundDrawable; private int backgroundDrawableLeft; + private int backgroundDrawableRight; private MessageObject currentMessageObject; private int viaWidth; private int viaNameWidth; @@ -279,6 +300,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate private boolean forwardNamePressed; private boolean forwardBotPressed; + private ImageReceiver locationImageReceiver; + private StaticLayout replyNameLayout; private StaticLayout replyTextLayout; private ImageReceiver replyImageReceiver; @@ -292,12 +315,14 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate private boolean replyPressed; private TLRPC.FileLocation currentReplyPhoto; + private boolean drwaShareGoIcon; private boolean drawShareButton; private boolean sharePressed; private int shareStartX; private int shareStartY; private StaticLayout nameLayout; + private StaticLayout adminLayout; private int nameWidth; private float nameOffsetX; private float nameX; @@ -342,6 +367,23 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate private int lastDeleteDate; private int lastViewsCount; + private boolean scheduledInvalidate; + private Runnable invalidateRunnable = new Runnable() { + @Override + public void run() { + checkLocationExpired(); + if (locationExpired) { + invalidate(); + scheduledInvalidate = false; + } else { + invalidate((int) rect.left - 5, (int) rect.top - 5, (int) rect.right + 5, (int) rect.bottom + 5); + if (scheduledInvalidate) { + AndroidUtilities.runOnUIThread(invalidateRunnable, 1000); + } + } + } + }; + public ChatMessageCell(Context context) { super(context); @@ -349,6 +391,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate avatarImage.setRoundRadius(AndroidUtilities.dp(21)); avatarDrawable = new AvatarDrawable(); replyImageReceiver = new ImageReceiver(this); + locationImageReceiver = new ImageReceiver(this); + locationImageReceiver.setRoundRadius(AndroidUtilities.dp(26.1f)); TAG = MediaController.getInstance().generateObserverTag(); contactAvatarDrawable = new AvatarDrawable(); @@ -765,7 +809,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate TLRPC.WebPage webPage = currentMessageObject.messageOwner.media.webpage; if (webPage != null && !TextUtils.isEmpty(webPage.embed_url)) { delegate.needOpenWebView(webPage.embed_url, webPage.site_name, webPage.title, webPage.url, webPage.embed_width, webPage.embed_height); - } else if (buttonState == -1) { + } else if (buttonState == -1 || buttonState == 3) { delegate.didPressedImage(this); playSoundEffect(SoundEffectConstants.CLICK); } else if (webPage != null) { @@ -880,7 +924,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate buttonPressed = 0; playSoundEffect(SoundEffectConstants.CLICK); didPressedButton(false); - radialProgress.swapBackground(getDrawableForCurrentState()); + updateRadialProgressBackground(true); invalidate(); } else if (imagePressed) { imagePressed = false; @@ -923,7 +967,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate int side = AndroidUtilities.dp(36); boolean area; if (buttonState == 0 || buttonState == 1 || buttonState == 2) { - area = x >= buttonX - AndroidUtilities.dp(12) && x <= buttonX - AndroidUtilities.dp(12) + backgroundWidth && y >= namesOffset + mediaOffsetY && y <= layoutHeight; + area = x >= buttonX - AndroidUtilities.dp(12) && x <= buttonX - AndroidUtilities.dp(12) + backgroundWidth && y >= (drawInstantView ? buttonY : namesOffset + mediaOffsetY) && y <= (drawInstantView ? buttonY + side : namesOffset + mediaOffsetY + AndroidUtilities.dp(82)); } else { area = x >= buttonX && x <= buttonX + side && y >= buttonY && y <= buttonY + side; } @@ -932,7 +976,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate buttonPressed = 1; invalidate(); result = true; - radialProgress.swapBackground(getDrawableForCurrentState()); + updateRadialProgressBackground(true); } } else if (buttonPressed != 0) { if (event.getAction() == MotionEvent.ACTION_UP) { @@ -949,7 +993,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate invalidate(); } } - radialProgress.swapBackground(getDrawableForCurrentState()); + updateRadialProgressBackground(true); } } return result; @@ -1006,18 +1050,18 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (!result) { result = checkOtherButtonMotionEvent(event); } - if (!result) { - result = checkLinkPreviewMotionEvent(event); - } - if (!result) { - result = checkGameMotionEvent(event); - } if (!result) { result = checkCaptionMotionEvent(event); } if (!result) { result = checkAudioMotionEvent(event); } + if (!result) { + result = checkLinkPreviewMotionEvent(event); + } + if (!result) { + result = checkGameMotionEvent(event); + } if (!result) { result = checkPhotoImageMotionEvent(event); } @@ -1063,13 +1107,21 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else if (drawNameLayout && nameLayout != null && viaWidth != 0 && x >= nameX + viaNameWidth && x <= nameX + viaNameWidth + viaWidth && y >= nameY - AndroidUtilities.dp(4) && y <= nameY + AndroidUtilities.dp(20)) { forwardBotPressed = true; result = true; - } else if (currentMessageObject.isReply() && x >= replyStartX && x <= replyStartX + Math.max(replyNameWidth, replyTextWidth) && y >= replyStartY && y <= replyStartY + AndroidUtilities.dp(35)) { - replyPressed = true; - result = true; } else if (drawShareButton && x >= shareStartX && x <= shareStartX + AndroidUtilities.dp(40) && y >= shareStartY && y <= shareStartY + AndroidUtilities.dp(32)) { sharePressed = true; result = true; invalidate(); + } else if (replyNameLayout != null) { + int replyEnd; + if (currentMessageObject.type == 13 || currentMessageObject.type == 5) { + replyEnd = replyStartX + Math.max(replyNameWidth, replyTextWidth); + } else { + replyEnd = replyStartX + backgroundDrawableRight; + } + if (x >= replyStartX && x <= replyEnd && y >= replyStartY && y <= replyStartY + AndroidUtilities.dp(35)){ + replyPressed = true; + result = true; + } } if (result) { startCheckLongPress(); @@ -1145,7 +1197,13 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { replyPressed = false; } else if (event.getAction() == MotionEvent.ACTION_MOVE) { - if (!(x >= replyStartX && x <= replyStartX + Math.max(replyNameWidth, replyTextWidth) && y >= replyStartY && y <= replyStartY + AndroidUtilities.dp(35))) { + int replyEnd; + if (currentMessageObject.type == 13 || currentMessageObject.type == 5) { + replyEnd = replyStartX + Math.max(replyNameWidth, replyTextWidth); + } else { + replyEnd = replyStartX + backgroundDrawableRight; + } + if (!(x >= replyStartX && x <= replyEnd && y >= replyStartY && y <= replyStartY + AndroidUtilities.dp(35))){ replyPressed = false; } } @@ -1188,9 +1246,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (MediaController.getInstance().isPlayingMessage(currentMessageObject)) { duration = Math.max(0, duration - currentMessageObject.audioProgressSec); } - String timeString = String.format("%02d:%02d", duration / 60, duration % 60); - if (lastTimeString == null || lastTimeString != null && !lastTimeString.equals(timeString)) { - lastTimeString = timeString; + if (lastTime != duration) { + lastTime = duration; + String timeString = String.format("%02d:%02d", duration / 60, duration % 60); timeWidthAudio = (int) Math.ceil(Theme.chat_timePaint.measureText(timeString)); durationLayout = new StaticLayout(timeString, Theme.chat_timePaint, timeWidthAudio, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); invalidate(); @@ -1219,9 +1277,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else { duration = currentMessageObject.audioProgressSec; } - String timeString = String.format("%02d:%02d", duration / 60, duration % 60); - if (lastTimeString == null || lastTimeString != null && !lastTimeString.equals(timeString)) { - lastTimeString = timeString; + + if (lastTime != duration) { + lastTime = duration; + String timeString = String.format("%02d:%02d", duration / 60, duration % 60); timeWidthAudio = (int) Math.ceil(Theme.chat_audioTimePaint.measureText(timeString)); durationLayout = new StaticLayout(timeString, Theme.chat_audioTimePaint, timeWidthAudio, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } @@ -1237,9 +1296,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (MediaController.getInstance().isPlayingMessage(currentMessageObject)) { currentProgress = currentMessageObject.audioProgressSec; } - String timeString = String.format("%d:%02d / %d:%02d", currentProgress / 60, currentProgress % 60, duration / 60, duration % 60); - if (lastTimeString == null || lastTimeString != null && !lastTimeString.equals(timeString)) { - lastTimeString = timeString; + if (lastTime != currentProgress) { + lastTime = currentProgress; + String timeString = String.format("%d:%02d / %d:%02d", currentProgress / 60, currentProgress % 60, duration / 60, duration % 60); int timeWidth = (int) Math.ceil(Theme.chat_audioTimePaint.measureText(timeString)); durationLayout = new StaticLayout(timeString, Theme.chat_audioTimePaint, timeWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } @@ -1249,7 +1308,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } public void downloadAudioIfNeed() { - if (documentAttachType != DOCUMENT_ATTACH_TYPE_AUDIO || documentAttach.size >= 1024 * 1024) { + if (documentAttachType != DOCUMENT_ATTACH_TYPE_AUDIO) { return; } if (buttonState == 2) { @@ -1397,7 +1456,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } private void updateSecretTimeText(MessageObject messageObject) { - if (messageObject == null) { + if (messageObject == null || !messageObject.isSecretPhoto()) { return; } String str = messageObject.getSecretTimeString(); @@ -1420,7 +1479,21 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } double lat = object.messageOwner.media.geo.lat; double lon = object.messageOwner.media.geo._long; - String url = String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=15&size=100x100&maptype=roadmap&scale=%d&markers=color:red|size:mid|%f,%f&sensor=false", lat, lon, Math.min(2, (int) Math.ceil(AndroidUtilities.density)), lat, lon); + String url; + if (object.messageOwner.media instanceof TLRPC.TL_messageMediaGeoLive) { + int photoWidth = backgroundWidth - AndroidUtilities.dp(21); + int photoHeight = AndroidUtilities.dp(195); + + int offset = 268435456; + double rad = offset / Math.PI; + double y = Math.round(offset - rad * Math.log((1 + Math.sin(lat * Math.PI / 180.0)) / (1 - Math.sin(lat * Math.PI / 180.0))) / 2) - (AndroidUtilities.dp(10.3f) << (21 - 15)); + lat = (Math.PI / 2.0 - 2 * Math.atan(Math.exp((y - offset) / rad))) * 180.0 / Math.PI; + url = String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=15&size=%dx%d&maptype=roadmap&scale=%d&sensor=false", lat, lon, (int) (photoWidth / AndroidUtilities.density), (int) (photoHeight / AndroidUtilities.density), Math.min(2, (int) Math.ceil(AndroidUtilities.density))); + } else if (!TextUtils.isEmpty(object.messageOwner.media.title)) { + url = String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=15&size=72x72&maptype=roadmap&scale=%d&markers=color:red|size:mid|%f,%f&sensor=false", lat, lon, Math.min(2, (int) Math.ceil(AndroidUtilities.density)), lat, lon); + } else { + url = String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=15&size=200x100&maptype=roadmap&scale=%d&markers=color:red|size:mid|%f,%f&sensor=false", lat, lon, Math.min(2, (int) Math.ceil(AndroidUtilities.density)), lat, lon); + } if (!url.equals(currentUrl)) { return true; } @@ -1452,22 +1525,14 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate return true; } - TLRPC.User newUser = null; - TLRPC.Chat newChat = null; - if (currentMessageObject.isFromUser()) { - newUser = MessagesController.getInstance().getUser(currentMessageObject.messageOwner.from_id); - } else if (currentMessageObject.messageOwner.from_id < 0) { - newChat = MessagesController.getInstance().getChat(-currentMessageObject.messageOwner.from_id); - } else if (currentMessageObject.messageOwner.post) { - newChat = MessagesController.getInstance().getChat(currentMessageObject.messageOwner.to_id.channel_id); - } + updateCurrentUserAndChat(); TLRPC.FileLocation newPhoto = null; if (isAvatarVisible) { - if (newUser != null && newUser.photo != null){ - newPhoto = newUser.photo.photo_small; - } else if (newChat != null && newChat.photo != null) { - newPhoto = newChat.photo.photo_small; + if (currentUser != null && currentUser.photo != null){ + newPhoto = currentUser.photo.photo_small; + } else if (currentChat != null && currentChat.photo != null) { + newPhoto = currentChat.photo.photo_small; } } @@ -1481,7 +1546,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate TLRPC.FileLocation newReplyPhoto = null; - if (currentMessageObject.replyMessageObject != null) { + if (replyNameLayout != null) { TLRPC.PhotoSize photoSize = FileLoader.getClosestPhotoSizeWithSize(currentMessageObject.replyMessageObject.photoThumbs, 80); if (photoSize != null && currentMessageObject.replyMessageObject.type != 13) { newReplyPhoto = photoSize.location; @@ -1494,10 +1559,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate String newNameString = null; if (drawName && isChat && !currentMessageObject.isOutOwner()) { - if (newUser != null) { - newNameString = UserObject.getUserName(newUser); - } else if (newChat != null) { - newNameString = newChat.title; + if (currentUser != null) { + newNameString = UserObject.getUserName(currentUser); + } else if (currentChat != null) { + newNameString = currentChat.title; } } @@ -1521,6 +1586,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate super.onDetachedFromWindow(); avatarImage.onDetachedFromWindow(); replyImageReceiver.onDetachedFromWindow(); + locationImageReceiver.onDetachedFromWindow(); photoImage.onDetachedFromWindow(); MediaController.getInstance().removeLoadingFileObserver(this); } @@ -1531,6 +1597,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate avatarImage.onAttachedToWindow(); avatarImage.setParentView((View) getParent()); replyImageReceiver.onAttachedToWindow(); + locationImageReceiver.onAttachedToWindow(); if (drawPhotoImage) { if (photoImage.onAttachedToWindow()) { updateButtonState(false); @@ -1559,6 +1626,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate protected void onLongPress() { if (pressedLink instanceof URLSpanMono) { delegate.didPressedUrl(currentMessageObject, pressedLink, true); + return; } else if (pressedLink instanceof URLSpanNoUnderline) { URLSpanNoUnderline url = (URLSpanNoUnderline) pressedLink; if (url.getURL().startsWith("/")) { @@ -1590,7 +1658,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate public void setCheckPressed(boolean value, boolean pressed) { isCheckPressed = value; isPressed = pressed; - radialProgress.swapBackground(getDrawableForCurrentState()); + updateRadialProgressBackground(true); if (useSeekBarWaweform) { seekBarWaveform.setSelected(isDrawSelectedBackground()); } else { @@ -1599,12 +1667,24 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate invalidate(); } + public void setHighlightedAnimated() { + isHighlightedAnimated = true; + highlightProgress = 1000; + lastHighlightProgressTime = System.currentTimeMillis(); + invalidate(); + } + public void setHighlighted(boolean value) { if (isHighlighted == value) { return; } isHighlighted = value; - radialProgress.swapBackground(getDrawableForCurrentState()); + if (!isHighlighted) { + lastHighlightProgressTime = System.currentTimeMillis(); + isHighlightedAnimated = true; + highlightProgress = 300; + } + updateRadialProgressBackground(true); if (useSeekBarWaweform) { seekBarWaveform.setSelected(isDrawSelectedBackground()); } else { @@ -1616,7 +1696,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate @Override public void setPressed(boolean pressed) { super.setPressed(pressed); - radialProgress.swapBackground(getDrawableForCurrentState()); + updateRadialProgressBackground(true); if (useSeekBarWaweform) { seekBarWaveform.setSelected(isDrawSelectedBackground()); } else { @@ -1625,6 +1705,15 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate invalidate(); } + private void updateRadialProgressBackground(boolean swap) { + if (drawRadialCheckBackground) { + return; + } + if (swap) { + radialProgress.swapBackground(getDrawableForCurrentState()); + } + } + @Override public void onSeekBarDrag(float progress) { if (currentMessageObject == null) { @@ -1705,8 +1794,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } int durationWidth = (int) Math.ceil(Theme.chat_audioTimePaint.measureText(String.format("%d:%02d / %d:%02d", duration / 60, duration % 60, duration / 60, duration % 60))); - widthBeforeNewTimeLine = backgroundWidth - AndroidUtilities.dp(18 + 76) - durationWidth; - availableTimeWidth = backgroundWidth - AndroidUtilities.dp(18); + widthBeforeNewTimeLine = backgroundWidth - AndroidUtilities.dp(10 + 76) - durationWidth; + availableTimeWidth = backgroundWidth - AndroidUtilities.dp(28); return durationWidth; } else if (MessageObject.isVideoDocument(documentAttach)) { documentAttachType = DOCUMENT_ATTACH_TYPE_VIDEO; @@ -1849,28 +1938,83 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate return super.verifyDrawable(who) || who == instantViewSelectorDrawable; } - public void setMessageObject(MessageObject messageObject, boolean bottomNear, boolean topNear) { - if (messageObject.checkLayout()) { + private boolean isCurrentLocationTimeExpired(MessageObject messageObject) { + if (currentMessageObject.messageOwner.media.period % 60 == 0) { + return Math.abs(ConnectionsManager.getInstance().getCurrentTime() - messageObject.messageOwner.date) > messageObject.messageOwner.media.period; + } else { + return Math.abs(ConnectionsManager.getInstance().getCurrentTime() - messageObject.messageOwner.date) > messageObject.messageOwner.media.period - 5; + } + } + + private void checkLocationExpired() { + if (currentMessageObject == null) { + return; + } + boolean newExpired = isCurrentLocationTimeExpired(currentMessageObject); + if (newExpired != locationExpired) { + locationExpired = newExpired; + if (!locationExpired) { + AndroidUtilities.runOnUIThread(invalidateRunnable, 1000); + scheduledInvalidate = true; + int maxWidth = backgroundWidth - AndroidUtilities.dp(37 + 54); + docTitleLayout = new StaticLayout(LocaleController.getString("AttachLiveLocation", R.string.AttachLiveLocation), Theme.chat_locationTitlePaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + } else { + MessageObject messageObject = currentMessageObject; + currentMessageObject = null; + setMessageObject(messageObject, currentMessagesGroup, pinnedBottom, pinnedTop); + } + } + } + + public void setMessageObject(MessageObject messageObject, MessageObject.GroupedMessages groupedMessages, boolean bottomNear, boolean topNear) { + if (messageObject.checkLayout() || currentPosition != null && lastHeight != AndroidUtilities.displaySize.y) { currentMessageObject = null; } boolean messageIdChanged = currentMessageObject == null || currentMessageObject.getId() != messageObject.getId(); boolean messageChanged = currentMessageObject != messageObject || messageObject.forceUpdate; boolean dataChanged = currentMessageObject == messageObject && (isUserDataChanged() || photoNotSet); - if (messageChanged || dataChanged || isPhotoDataChanged(messageObject) || pinnedBottom != bottomNear || pinnedTop != topNear) { + boolean groupChanged = groupedMessages != currentMessagesGroup; + if (!groupChanged && groupedMessages != null) { + MessageObject.GroupedMessagePosition newPosition; + if (groupedMessages.messages.size() > 1) { + newPosition = currentMessagesGroup.positions.get(currentMessageObject); + } else { + newPosition = null; + } + groupChanged = newPosition != currentPosition; + } + if (messageChanged || dataChanged || groupChanged || isPhotoDataChanged(messageObject) || pinnedBottom != bottomNear || pinnedTop != topNear) { pinnedBottom = bottomNear; pinnedTop = topNear; - lastTimeString = null; + lastTime = -2; + isHighlightedAnimated = false; widthBeforeNewTimeLine = -1; currentMessageObject = messageObject; + currentMessagesGroup = groupedMessages; + if (currentMessagesGroup != null && currentMessagesGroup.posArray.size() > 1) { + currentPosition = currentMessagesGroup.positions.get(currentMessageObject); + if (currentPosition == null) { + currentMessagesGroup = null; + } + } else { + currentMessagesGroup = null; + currentPosition = null; + } + drawPinnedTop = pinnedTop && (currentPosition == null || (currentPosition.flags & MessageObject.POSITION_FLAG_TOP) != 0); + drawPinnedBottom = pinnedBottom && (currentPosition == null || (currentPosition.flags & MessageObject.POSITION_FLAG_BOTTOM) != 0); + photoImage.setCrossfadeWithOldImage(false); lastSendState = messageObject.messageOwner.send_state; lastDeleteDate = messageObject.messageOwner.destroyTime; lastViewsCount = messageObject.messageOwner.views; isPressed = false; isCheckPressed = true; - isAvatarVisible = false; + isAvatarVisible = isChat && !messageObject.isOutOwner() && messageObject.needDrawAvatar() && (currentPosition == null || currentPosition.edge); wasLayout = false; + drwaShareGoIcon = false; + groupPhotoInvisible = false; drawShareButton = checkNeedDrawShareButton(messageObject); replyNameLayout = null; + adminLayout = null; replyTextLayout = null; replyNameWidth = 0; replyTextWidth = 0; @@ -1881,6 +2025,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate currentChat = null; currentViaBotUser = null; drawNameLayout = false; + if (scheduledInvalidate) { + AndroidUtilities.cancelRunOnUIThread(invalidateRunnable); + scheduledInvalidate = false; + } resetPressedLink(-1); messageObject.forceUpdate = false; @@ -1904,6 +2052,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate descriptionLayout = null; titleLayout = null; videoInfoLayout = null; + photosCountLayout = null; siteNameLayout = null; authorLayout = null; captionLayout = null; @@ -1926,6 +2075,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate mediaBackground = false; int captionNewLine = 0; availableTimeWidth = 0; + photoImage.setForceLoading(false); photoImage.setNeedsQualityThumb(false); photoImage.setShouldGenerateQualityThumb(false); photoImage.setAllowDecodeSingleFrame(false); @@ -1970,15 +2120,38 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate hasInvoicePreview = messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaInvoice; hasLinkPreview = messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage && messageObject.messageOwner.media.webpage instanceof TLRPC.TL_webPage; drawInstantView = hasLinkPreview && messageObject.messageOwner.media.webpage.cached_page != null; + boolean slideshow = false; + String siteName = hasLinkPreview ? messageObject.messageOwner.media.webpage.site_name : null; String webpageType = hasLinkPreview ? messageObject.messageOwner.media.webpage.type : null; if (!drawInstantView) { if ("telegram_channel".equals(webpageType)) { drawInstantView = true; drawInstantViewType = 1; - } else if ("telegram_group".equals(webpageType)) { + } else if ("telegram_megagroup".equals(webpageType)) { drawInstantView = true; drawInstantViewType = 2; } + } else if (siteName != null) { + siteName = siteName.toLowerCase(); + if ((siteName.equals("instagram") || siteName.equals("twitter")) && messageObject.messageOwner.media.webpage.cached_page instanceof TLRPC.TL_pageFull && messageObject.messageOwner.media.webpage.photo instanceof TLRPC.TL_photo) { + drawInstantView = false; + slideshow = true; + ArrayList blocks = messageObject.messageOwner.media.webpage.cached_page.blocks; + int count = 1; + for (int a = 0; a < blocks.size(); a++) { + TLRPC.PageBlock block = blocks.get(a); + if (block instanceof TLRPC.TL_pageBlockSlideshow) { + TLRPC.TL_pageBlockSlideshow b = (TLRPC.TL_pageBlockSlideshow) block; + count = b.items.size(); + } else if (block instanceof TLRPC.TL_pageBlockCollage) { + TLRPC.TL_pageBlockCollage b = (TLRPC.TL_pageBlockCollage) block; + count = b.items.size(); + } + } + String str = LocaleController.formatString("Of", R.string.Of, 1, count); + photosCountWidth = (int) Math.ceil(Theme.chat_durationPaint.measureText(str)); + photosCountLayout = new StaticLayout(str, Theme.chat_durationPaint, photosCountWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + } } if (Build.VERSION.SDK_INT >= 21 && drawInstantView) { if (instantViewSelectorDrawable == null) { @@ -2007,7 +2180,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate @Override public int getOpacity() { - return 255; + return PixelFormat.OPAQUE; } }; ColorStateList colorStateList = new ColorStateList( @@ -2039,7 +2212,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate backgroundWidth = messageObject.textWidth + ((hasGamePreview || hasInvoicePreview) ? AndroidUtilities.dp(10) : 0); totalHeight = messageObject.textHeight + AndroidUtilities.dp(19.5f) + namesOffset; - if (pinnedTop) { + if (drawPinnedTop) { namesOffset -= AndroidUtilities.dp(1); } @@ -2052,14 +2225,14 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (hasLinkPreview || hasGamePreview || hasInvoicePreview) { int linkPreviewMaxWidth; if (AndroidUtilities.isTablet()) { - if (messageObject.needDrawAvatar() && (currentMessageObject.messageOwner.to_id.channel_id != 0 || currentMessageObject.messageOwner.to_id.chat_id != 0) && !currentMessageObject.isOut()) { - linkPreviewMaxWidth = AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(122); + if (isChat && messageObject.needDrawAvatar() && !currentMessageObject.isOut()) { + linkPreviewMaxWidth = AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(132); } else { linkPreviewMaxWidth = AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(80); } } else { - if (messageObject.needDrawAvatar() && (currentMessageObject.messageOwner.to_id.channel_id != 0 || currentMessageObject.messageOwner.to_id.chat_id != 0) && !currentMessageObject.isOutOwner()) { - linkPreviewMaxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(122); + if (isChat && messageObject.needDrawAvatar() && !currentMessageObject.isOutOwner()) { + linkPreviewMaxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(132); } else { linkPreviewMaxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(80); } @@ -2091,8 +2264,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (site_name != null && photo != null && site_name.toLowerCase().equals("instagram")) { linkPreviewMaxWidth = Math.max(AndroidUtilities.displaySize.y / 3, currentMessageObject.textWidth); } - smallImage = !drawInstantView && document == null && type != null && (type.equals("app") || type.equals("profile") || type.equals("article")); - isSmallImage = !drawInstantView && document == null && description != null && type != null && (type.equals("app") || type.equals("profile") || type.equals("article")) && currentMessageObject.photoThumbs != null; + smallImage = !slideshow && !drawInstantView && document == null && type != null && (type.equals("app") || type.equals("profile") || type.equals("article")); + isSmallImage = !slideshow && !drawInstantView && document == null && description != null && type != null && (type.equals("app") || type.equals("profile") || type.equals("article")) && currentMessageObject.photoThumbs != null; } else if (hasInvoicePreview) { site_name = messageObject.messageOwner.media.title; title = null; @@ -2464,6 +2637,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (height > AndroidUtilities.displaySize.y / 3) { height = AndroidUtilities.displaySize.y / 3; } + } else { + if (height > AndroidUtilities.displaySize.y / 2) { + height = AndroidUtilities.displaySize.y / 2; + } } } } @@ -2494,10 +2671,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate String fileName = FileLoader.getAttachFileName(document); boolean autoDownload = false; if (MessageObject.isNewGifDocument(document)) { - autoDownload = MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_GIF); + autoDownload = MediaController.getInstance().canDownloadMedia(currentMessageObject); } else if (MessageObject.isRoundVideoDocument(document)) { photoImage.setRoundRadius(AndroidUtilities.roundMessageSize / 2); - autoDownload = MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_VIDEOMESSAGE); + autoDownload = MediaController.getInstance().canDownloadMedia(currentMessageObject); } if (!messageObject.isSending() && (messageObject.mediaExists || FileLoader.getInstance().isLoadingFile(fileName) || autoDownload)) { photoNotSet = false; @@ -2509,7 +2686,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else { boolean photoExist = messageObject.mediaExists; String fileName = FileLoader.getAttachFileName(currentPhotoObject); - if (hasGamePreview || photoExist || MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_PHOTO) || FileLoader.getInstance().isLoadingFile(fileName)) { + if (hasGamePreview || photoExist || MediaController.getInstance().canDownloadMedia(currentMessageObject) || FileLoader.getInstance().isLoadingFile(fileName)) { photoNotSet = false; photoImage.setImage(currentPhotoObject.location, currentPhotoFilter, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, currentPhotoFilterThumb, 0, null, 0); } else { @@ -2640,7 +2817,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate setMessageObjectInternal(messageObject); totalHeight = AndroidUtilities.dp(65) + namesOffset; - if (pinnedTop) { + if (drawPinnedTop) { namesOffset -= AndroidUtilities.dp(1); } } else if (messageObject.type == 12) { @@ -2688,14 +2865,14 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate setMessageObjectInternal(messageObject); - if (drawForwardedName && messageObject.isForwarded()) { + if (drawForwardedName && messageObject.needDrawForwarded() && (currentPosition == null || currentPosition.minY == 0)) { namesOffset += AndroidUtilities.dp(5); } else if (drawNameLayout && messageObject.messageOwner.reply_to_msg_id == 0) { namesOffset += AndroidUtilities.dp(7); } totalHeight = AndroidUtilities.dp(70) + namesOffset; - if (pinnedTop) { + if (drawPinnedTop) { namesOffset -= AndroidUtilities.dp(1); } if (docTitleLayout.getLineCount() > 0) { @@ -2716,7 +2893,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate setMessageObjectInternal(messageObject); totalHeight = AndroidUtilities.dp(70) + namesOffset; - if (pinnedTop) { + if (drawPinnedTop) { namesOffset -= AndroidUtilities.dp(1); } } else if (messageObject.type == 14) { @@ -2731,7 +2908,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate setMessageObjectInternal(messageObject); totalHeight = AndroidUtilities.dp(82) + namesOffset; - if (pinnedTop) { + if (drawPinnedTop) { namesOffset -= AndroidUtilities.dp(1); } } else { @@ -2792,7 +2969,56 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate double lat = messageObject.messageOwner.media.geo.lat; double lon = messageObject.messageOwner.media.geo._long; - if (messageObject.messageOwner.media.title != null && messageObject.messageOwner.media.title.length() > 0) { + if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeoLive) { + if (AndroidUtilities.isTablet()) { + backgroundWidth = Math.min(AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(isChat && messageObject.needDrawAvatar() && !messageObject.isOutOwner() ? 102 : 50), AndroidUtilities.dp(252 + 37)); + } else { + backgroundWidth = Math.min(AndroidUtilities.displaySize.x - AndroidUtilities.dp(isChat && messageObject.needDrawAvatar() && !messageObject.isOutOwner() ? 102 : 50), AndroidUtilities.dp(252 + 37)); + } + if (checkNeedDrawShareButton(messageObject)) { + backgroundWidth -= AndroidUtilities.dp(20); + } + int maxWidth = backgroundWidth - AndroidUtilities.dp(37); + availableTimeWidth = maxWidth; + maxWidth -= AndroidUtilities.dp(54); + + photoWidth = backgroundWidth - AndroidUtilities.dp(21); + photoHeight = AndroidUtilities.dp(195); + + int offset = 268435456; + double rad = offset / Math.PI; + double y = Math.round(offset - rad * Math.log((1 + Math.sin(lat * Math.PI / 180.0)) / (1 - Math.sin(lat * Math.PI / 180.0))) / 2) - (AndroidUtilities.dp(10.3f) << (21 - 15)); + lat = (Math.PI / 2.0 - 2 * Math.atan(Math.exp((y - offset) / rad))) * 180.0 / Math.PI; + currentUrl = String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=15&size=%dx%d&maptype=roadmap&scale=%d&sensor=false", lat, lon, (int) (photoWidth / AndroidUtilities.density), (int) (photoHeight / AndroidUtilities.density), Math.min(2, (int) Math.ceil(AndroidUtilities.density))); + + if (!(locationExpired = isCurrentLocationTimeExpired(messageObject))) { + photoImage.setCrossfadeWithOldImage(true); + mediaBackground = false; + additionHeight = AndroidUtilities.dp(56); + AndroidUtilities.runOnUIThread(invalidateRunnable, 1000); + scheduledInvalidate = true; + } else { + backgroundWidth -= AndroidUtilities.dp(9); + } + docTitleLayout = new StaticLayout(LocaleController.getString("AttachLiveLocation", R.string.AttachLiveLocation), Theme.chat_locationTitlePaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + + TLRPC.FileLocation currentPhoto = null; + updateCurrentUserAndChat(); + if (currentUser != null) { + if (currentUser.photo != null) { + currentPhoto = currentUser.photo.photo_small; + } + contactAvatarDrawable.setInfo(currentUser); + } else if (currentChat != null) { + if (currentChat.photo != null) { + currentPhoto = currentChat.photo.photo_small; + } + contactAvatarDrawable.setInfo(currentChat); + } + locationImageReceiver.setImage(currentPhoto, "50_50", contactAvatarDrawable, null, 0); + + infoLayout = new StaticLayout(LocaleController.formatLocationUpdateDate(messageObject.messageOwner.edit_date != 0 ? messageObject.messageOwner.edit_date : messageObject.messageOwner.date), Theme.chat_locationAddressPaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + } else if (!TextUtils.isEmpty(messageObject.messageOwner.media.title)) { if (AndroidUtilities.isTablet()) { backgroundWidth = Math.min(AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(isChat && messageObject.needDrawAvatar() && !messageObject.isOutOwner() ? 102 : 50), AndroidUtilities.dp(270)); } else { @@ -2823,7 +3049,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate backgroundWidth = photoWidth + AndroidUtilities.dp(12); currentUrl = String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=15&size=200x100&maptype=roadmap&scale=%d&markers=color:red|size:mid|%f,%f&sensor=false", lat, lon, Math.min(2, (int) Math.ceil(AndroidUtilities.density)), lat, lon); } - photoImage.setImage(currentUrl, null, Theme.chat_locationDrawable[messageObject.isOutOwner() ? 1 : 0], null, 0); + if (currentUrl != null) { + photoImage.setImage(currentUrl, null, Theme.chat_locationDrawable[messageObject.isOutOwner() ? 1 : 0], null, 0); + } } else if (messageObject.type == 13) { //webp drawBackground = false; for (int a = 0; a < messageObject.messageOwner.media.document.attributes.size(); a++) { @@ -2922,7 +3150,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate photoImage.setParentMessageObject(messageObject); } - if (messageObject.caption != null) { + if (currentMessagesGroup == null && messageObject.caption != null) { mediaBackground = false; } @@ -2986,7 +3214,6 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } - if (w == 0 || h == 0) { w = h = AndroidUtilities.dp(150); } @@ -2996,7 +3223,21 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } - availableTimeWidth = maxPhotoWidth - AndroidUtilities.dp(14); + if (currentMessagesGroup != null) { + int firstLineWidth = 0; + int dWidth = getGroupPhotosWidth(); + for (int a = 0; a < currentMessagesGroup.posArray.size(); a++) { + MessageObject.GroupedMessagePosition position = currentMessagesGroup.posArray.get(a); + if (position.minY == 0) { + firstLineWidth += Math.ceil((position.pw + position.leftSpanOffset) / 1000.0f * dWidth); + } else { + break; + } + } + availableTimeWidth = firstLineWidth - AndroidUtilities.dp(35); + } else { + availableTimeWidth = maxPhotoWidth - AndroidUtilities.dp(14); + } if (messageObject.type == 5) { availableTimeWidth -= Math.ceil(Theme.chat_audioTimePaint.measureText("00:00")) + AndroidUtilities.dp(26); } @@ -3018,20 +3259,98 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } - photoWidth = w; - photoHeight = h; - backgroundWidth = w + AndroidUtilities.dp(12); - if (!mediaBackground) { - backgroundWidth += AndroidUtilities.dp(9); + CharSequence caption = null; + int widthForCaption = 0; + if (currentMessagesGroup != null) { + float maxHeight = Math.max(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.5f; + int dWidth = getGroupPhotosWidth(); + w = (int) Math.ceil(currentPosition.pw / 1000.0f * dWidth); + if (currentPosition.minY != 0 && (messageObject.isOutOwner() && (currentPosition.flags & MessageObject.POSITION_FLAG_LEFT) != 0 || !messageObject.isOutOwner() && (currentPosition.flags & MessageObject.POSITION_FLAG_RIGHT) != 0)) { + int firstLineWidth = 0; + int currentLineWidth = 0; + for (int a = 0; a < currentMessagesGroup.posArray.size(); a++) { + MessageObject.GroupedMessagePosition position = currentMessagesGroup.posArray.get(a); + if (position.minY == 0) { + firstLineWidth += Math.ceil(position.pw / 1000.0f * dWidth) + (position.leftSpanOffset != 0 ? Math.ceil(position.leftSpanOffset / 1000.0f * dWidth) : 0); + } else if (position.minY == currentPosition.minY) { + currentLineWidth += Math.ceil((position.pw) / 1000.0f * dWidth) + (position.leftSpanOffset != 0 ? Math.ceil(position.leftSpanOffset / 1000.0f * dWidth) : 0); + } else if (position.minY > currentPosition.minY) { + break; + } + } + w += firstLineWidth - currentLineWidth; + } + w -= AndroidUtilities.dp(9); + if (isAvatarVisible) { + w -= AndroidUtilities.dp(48); + } + if (currentPosition.siblingHeights != null) { + h = 0; + for (int a = 0; a < currentPosition.siblingHeights.length; a++) { + h += (int) Math.ceil(maxHeight * currentPosition.siblingHeights[a]); + } + h += (currentPosition.maxY - currentPosition.minY) * AndroidUtilities.dp(11); + } else { + h = (int) Math.ceil(maxHeight * currentPosition.ph); + } + backgroundWidth = w; + w -= AndroidUtilities.dp(12); + photoWidth = w; + if (!currentPosition.edge) { + photoWidth += AndroidUtilities.dp(10); + } + photoHeight = h; + /*widthForCaption += photoWidth - AndroidUtilities.dp(10); + if ((currentPosition.flags & MessageObject.POSITION_FLAG_BOTTOM) != 0) { + int count = currentMessagesGroup.messages.size(); + for (int a = 0; a < count; a++) { + MessageObject m = currentMessagesGroup.messages.get(a); + MessageObject.GroupedMessagePosition position = currentMessagesGroup.posArray.get(a); + if (position != currentPosition && (position.flags & MessageObject.POSITION_FLAG_BOTTOM) != 0) { + w = (int) Math.ceil(currentPosition.pw / 1000.0f * dWidth); + w -= AndroidUtilities.dp(9); + //if (isAvatarVisible) { + // w -= AndroidUtilities.dp(48); + //} + widthForCaption += w; + } + if (m.caption != null) { + if (caption != null) { + caption = null; + break; + } else { + caption = m.caption; + } + } + } + }*/ + } else { + photoWidth = w; + photoHeight = h; + backgroundWidth = w + AndroidUtilities.dp(12); + if (!mediaBackground) { + backgroundWidth += AndroidUtilities.dp(9); + } + caption = messageObject.caption; + widthForCaption = photoWidth - AndroidUtilities.dp(10); } - if (messageObject.caption != null) { + + if (caption != null) { try { - captionLayout = new StaticLayout(messageObject.caption, Theme.chat_msgTextPaint, photoWidth - AndroidUtilities.dp(10), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + captionLayout = StaticLayout.Builder.obtain(caption, 0, caption.length(), Theme.chat_msgTextPaint, widthForCaption) + .setBreakStrategy(StaticLayout.BREAK_STRATEGY_HIGH_QUALITY) + .setHyphenationFrequency(StaticLayout.HYPHENATION_FREQUENCY_NONE) + .setAlignment(Layout.Alignment.ALIGN_NORMAL) + .build(); + } else { + captionLayout = new StaticLayout(caption, Theme.chat_msgTextPaint, widthForCaption, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + } if (captionLayout.getLineCount() > 0) { captionHeight = captionLayout.getHeight(); additionHeight += captionHeight + AndroidUtilities.dp(9); float lastLineWidth = captionLayout.getLineWidth(captionLayout.getLineCount() - 1) + captionLayout.getLineLeft(captionLayout.getLineCount() - 1); - if (photoWidth - AndroidUtilities.dp(8) - lastLineWidth < timeWidthTotal) { + if (widthForCaption + AndroidUtilities.dp(2) - lastLineWidth < timeWidthTotal) { additionHeight += AndroidUtilities.dp(14); captionNewLine = 1; } @@ -3071,7 +3390,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else { photoExist = false; } - if (photoExist || MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_PHOTO) || FileLoader.getInstance().isLoadingFile(fileName)) { + if (photoExist || MediaController.getInstance().canDownloadMedia(currentMessageObject) || FileLoader.getInstance().isLoadingFile(fileName)) { photoImage.setImage(currentPhotoObject.location, currentPhotoFilter, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, currentPhotoFilterThumb, noSize ? 0 : currentPhotoObject.size, null, currentMessageObject.shouldEncryptPhotoOrVideo() ? 2 : 0); } else { photoNotSet = true; @@ -3096,9 +3415,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } boolean autoDownload = false; if (MessageObject.isNewGifDocument(messageObject.messageOwner.media.document)) { - autoDownload = MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_GIF); + autoDownload = MediaController.getInstance().canDownloadMedia(currentMessageObject); } else if (messageObject.type == 5) { - autoDownload = MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_VIDEOMESSAGE); + autoDownload = MediaController.getInstance().canDownloadMedia(currentMessageObject); } if (!messageObject.isSending() && (localFile != 0 || FileLoader.getInstance().isLoadingFile(fileName) || autoDownload)) { if (localFile == 1) { @@ -3116,7 +3435,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } setMessageObjectInternal(messageObject); - if (drawForwardedName) { + if (drawForwardedName && messageObject.needDrawForwarded() && (currentPosition == null || currentPosition.minY == 0)) { if (messageObject.type != 5) { namesOffset += AndroidUtilities.dp(5); } @@ -3124,19 +3443,47 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate namesOffset += AndroidUtilities.dp(7); } totalHeight = photoHeight + AndroidUtilities.dp(14) + namesOffset + additionHeight; + if (currentPosition != null && (currentPosition.flags & MessageObject.POSITION_FLAG_BOTTOM) == 0) { + totalHeight -= AndroidUtilities.dp(3); + } - if (pinnedTop) { + int additionalTop = 0; + if (currentPosition != null) { + if ((currentPosition.flags & MessageObject.POSITION_FLAG_RIGHT) == 0) { + photoWidth += AndroidUtilities.dp(4); + } + if ((currentPosition.flags & MessageObject.POSITION_FLAG_LEFT) == 0) { + photoWidth += AndroidUtilities.dp(4); + } + if ((currentPosition.flags & MessageObject.POSITION_FLAG_TOP) == 0) { + photoHeight += AndroidUtilities.dp(4); + additionalTop -= AndroidUtilities.dp(4); + } + if ((currentPosition.flags & MessageObject.POSITION_FLAG_BOTTOM) == 0) { + photoHeight += AndroidUtilities.dp(4); + } + } + + if (drawPinnedTop) { namesOffset -= AndroidUtilities.dp(1); } - photoImage.setImageCoords(0, AndroidUtilities.dp(7) + namesOffset, photoWidth, photoHeight); + photoImage.setImageCoords(0, AndroidUtilities.dp(7) + namesOffset + additionalTop, photoWidth, photoHeight); invalidate(); } - if (captionLayout == null && messageObject.caption != null && messageObject.type != 13) { + if (currentPosition == null && captionLayout == null && messageObject.caption != null && messageObject.type != 13) { try { int width = backgroundWidth - AndroidUtilities.dp(31); - captionLayout = new StaticLayout(messageObject.caption, Theme.chat_msgTextPaint, width - AndroidUtilities.dp(10), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + captionLayout = StaticLayout.Builder.obtain(messageObject.caption, 0, messageObject.caption.length(), Theme.chat_msgTextPaint, width - AndroidUtilities.dp(10)) + .setBreakStrategy(StaticLayout.BREAK_STRATEGY_HIGH_QUALITY) + .setHyphenationFrequency(StaticLayout.HYPHENATION_FREQUENCY_NONE) + .setAlignment(Layout.Alignment.ALIGN_NORMAL) + .build(); + } else { + captionLayout = new StaticLayout(messageObject.caption, Theme.chat_msgTextPaint, width - AndroidUtilities.dp(10), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + } if (captionLayout.getLineCount() > 0) { int timeWidthTotal = timeWidth + (messageObject.isOutOwner() ? AndroidUtilities.dp(20) : 0); captionHeight = captionLayout.getHeight(); @@ -3211,7 +3558,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate botButtonsByPosition.clear(); botButtonsLayout = null; } - if (messageObject.messageOwner.reply_markup instanceof TLRPC.TL_replyInlineMarkup) { + if (currentPosition == null && messageObject.messageOwner.reply_markup instanceof TLRPC.TL_replyInlineMarkup) { int rows = messageObject.messageOwner.reply_markup.rows.size(); substractBackgroundHeight = keyboardHeight = AndroidUtilities.dp(44 + 4) * rows + AndroidUtilities.dp(1); @@ -3290,9 +3637,11 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate substractBackgroundHeight = 0; keyboardHeight = 0; } - if (pinnedBottom && pinnedTop) { + if (drawPinnedBottom && drawPinnedTop) { totalHeight -= AndroidUtilities.dp(2); - } else if (pinnedBottom) { + } else if (drawPinnedBottom) { + totalHeight -= AndroidUtilities.dp(1); + } else if (drawPinnedTop && pinnedBottom && currentPosition != null && currentPosition.siblingHeights == null) { totalHeight -= AndroidUtilities.dp(1); } if (messageObject.type == 13 && totalHeight < AndroidUtilities.dp(70)) { @@ -3313,21 +3662,33 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (currentMessageObject != null && currentMessageObject.checkLayout()) { + if (currentMessageObject != null && (currentMessageObject.checkLayout() || currentPosition != null && lastHeight != AndroidUtilities.displaySize.y)) { inLayout = true; MessageObject messageObject = currentMessageObject; currentMessageObject = null; - setMessageObject(messageObject, pinnedBottom, pinnedTop); + setMessageObject(messageObject, currentMessagesGroup, pinnedBottom, pinnedTop); inLayout = false; } setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), totalHeight + keyboardHeight); + lastHeight = AndroidUtilities.displaySize.y; + } + + private int getGroupPhotosWidth() { + if (!AndroidUtilities.isInMultiwindow && AndroidUtilities.isTablet() && (!AndroidUtilities.isSmallTablet() || getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE)) { + int leftWidth = AndroidUtilities.displaySize.x / 100 * 35; + if (leftWidth < AndroidUtilities.dp(320)) { + leftWidth = AndroidUtilities.dp(320); + } + return AndroidUtilities.displaySize.x - leftWidth; + } else { + return AndroidUtilities.displaySize.x; + } } @SuppressLint("DrawAllocation") @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { if (currentMessageObject == null) { - super.onLayout(changed, left, top, right, bottom); return; } @@ -3340,13 +3701,17 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate timeLayout = new StaticLayout(currentTimeString, Theme.chat_timePaint, timeTextWidth + AndroidUtilities.dp(100), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); if (!mediaBackground) { if (!currentMessageObject.isOutOwner()) { - timeX = backgroundWidth - AndroidUtilities.dp(9) - timeWidth + (isChat && currentMessageObject.needDrawAvatar() ? AndroidUtilities.dp(48) : 0); + timeX = backgroundWidth - AndroidUtilities.dp(9) - timeWidth + (isAvatarVisible ? AndroidUtilities.dp(48) : 0); } else { timeX = layoutWidth - timeWidth - AndroidUtilities.dp(38.5f); } } else { if (!currentMessageObject.isOutOwner()) { - timeX = backgroundWidth - AndroidUtilities.dp(4) - timeWidth + (isChat && currentMessageObject.needDrawAvatar() ? AndroidUtilities.dp(48) : 0); + timeX = backgroundWidth - AndroidUtilities.dp(4) - timeWidth + (isAvatarVisible ? AndroidUtilities.dp(48) : 0); + + if (currentPosition != null && currentPosition.leftSpanOffset != 0) { + timeX += (int) Math.ceil(currentPosition.leftSpanOffset / 1000.0f * getGroupPhotosWidth()); + } } else { timeX = layoutWidth - timeWidth - AndroidUtilities.dp(42.0f); } @@ -3479,11 +3844,22 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate x = layoutWidth - backgroundWidth + AndroidUtilities.dp(6); } } else { - if (isChat && currentMessageObject.needDrawAvatar()) { + if (isChat && isAvatarVisible) { x = AndroidUtilities.dp(63); } else { x = AndroidUtilities.dp(15); } + if (currentPosition != null && !currentPosition.edge) { + x -= AndroidUtilities.dp(10); + } + } + } + if (currentPosition != null) { + if ((currentPosition.flags & MessageObject.POSITION_FLAG_LEFT) == 0) { + x -= AndroidUtilities.dp(4); + } + if (currentPosition.leftSpanOffset != 0) { + x += (int) Math.ceil(currentPosition.leftSpanOffset / 1000.0f * getGroupPhotosWidth()); } } photoImage.setImageCoords(x, photoImage.getImageY(), photoImage.getImageWidth(), photoImage.getImageHeight()); @@ -3500,17 +3876,25 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate setVisiblePart(scrollRect.top, scrollRect.bottom - scrollRect.top); needNewVisiblePart = false; } - - forceNotDrawTime = false; - photoImage.setPressed(isDrawSelectedBackground()); + forceNotDrawTime = currentMessagesGroup != null; + photoImage.setPressed(isDrawSelectedBackground() ? (currentPosition != null ? 2 : 1) : 0); photoImage.setVisible(!PhotoViewer.getInstance().isShowingImage(currentMessageObject) && !SecretMediaViewer.getInstance().isShowingImage(currentMessageObject), false); if (!photoImage.getVisible()) { mediaWasInvisible = true; - } else if (mediaWasInvisible) { + timeWasInvisible = true; + } else if (groupPhotoInvisible) { + timeWasInvisible = true; + } else if (mediaWasInvisible || timeWasInvisible) { + if (mediaWasInvisible) { + controlsAlpha = 0.0f; + mediaWasInvisible = false; + } + if (timeWasInvisible) { + timeAlpha = 0.0f; + timeWasInvisible = false; + } lastControlsAlphaChangeTime = System.currentTimeMillis(); totalChangeTime = 0; - controlsAlpha = 0.0f; - mediaWasInvisible = false; } radialProgress.setHideCurrentDrawable(false); radialProgress.setProgressColor(Theme.getColor(Theme.key_chat_mediaProgress)); @@ -3520,7 +3904,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (currentMessageObject.isOutOwner()) { textX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(11); } else { - textX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(!mediaBackground && pinnedBottom ? 11 : 17); + textX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(!mediaBackground && drawPinnedBottom ? 11 : 17); } if (hasGamePreview) { textX += AndroidUtilities.dp(11); @@ -3695,6 +4079,21 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate imageDrawn = photoImage.draw(canvas); } } + if (photosCountLayout != null && photoImage.getVisible()) { + int x = photoImage.getImageX() + photoImage.getImageWidth() - AndroidUtilities.dp(8) - photosCountWidth; + int y = photoImage.getImageY() + photoImage.getImageHeight() - AndroidUtilities.dp(19); + rect.set(x - AndroidUtilities.dp(4), y - AndroidUtilities.dp(1.5f), x + photosCountWidth + AndroidUtilities.dp(4), y + AndroidUtilities.dp(14.5f)); + int oldAlpha = Theme.chat_timeBackgroundPaint.getAlpha(); + Theme.chat_timeBackgroundPaint.setAlpha((int) (oldAlpha * controlsAlpha)); + Theme.chat_durationPaint.setAlpha((int) (255 * controlsAlpha)); + canvas.drawRoundRect(rect, AndroidUtilities.dp(4), AndroidUtilities.dp(4), Theme.chat_timeBackgroundPaint); + Theme.chat_timeBackgroundPaint.setAlpha(oldAlpha); + canvas.save(); + canvas.translate(x, y); + photosCountLayout.draw(canvas); + canvas.restore(); + Theme.chat_durationPaint.setAlpha(255); + } if (videoInfoLayout != null && (!drawPhotoImage || photoImage.getVisible())) { int x; int y; @@ -3734,7 +4133,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (drawInstantView) { Drawable instantDrawable; - int instantY = linkPreviewY + AndroidUtilities.dp(4); + int instantY = startY + linkPreviewHeight + AndroidUtilities.dp(10); Paint backPaint = Theme.chat_instantViewRectPaint; if (currentMessageObject.isOutOwner()) { instantDrawable = Theme.chat_msgOutInstantDrawable; @@ -3778,7 +4177,34 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate Theme.chat_roundVideoShadow.draw(canvas); } imageDrawn = photoImage.draw(canvas); + boolean drawTimeOld = drawTime; drawTime = photoImage.getVisible(); + if (currentPosition != null && drawTimeOld != drawTime) { + ViewGroup viewGroup = (ViewGroup) getParent(); + if (viewGroup != null) { + if (!currentPosition.last) { + int count = viewGroup.getChildCount(); + for (int a = 0; a < count; a++) { + View child = viewGroup.getChildAt(a); + if (child == this || !(child instanceof ChatMessageCell)) { + continue; + } + ChatMessageCell cell = (ChatMessageCell) child; + + if (cell.getCurrentMessagesGroup() == currentMessagesGroup) { + MessageObject.GroupedMessagePosition position = cell.getCurrentPosition(); + if (position.last && position.maxY == currentPosition.maxY && cell.timeX - AndroidUtilities.dp(4) + cell.getLeft() < getRight()) { + cell.groupPhotoInvisible = !drawTime; + cell.invalidate(); + viewGroup.invalidate(); + } + } + } + } else { + viewGroup.invalidate(); + } + } + } } } @@ -3829,14 +4255,14 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } if (documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND) { x1 = backgroundDrawableLeft + AndroidUtilities.dp(currentMessageObject.isOutOwner() ? 12 : 18); - y1 = layoutHeight - AndroidUtilities.dp(6.3f - (pinnedBottom ? 2 : 0)) - timeLayout.getHeight(); + y1 = layoutHeight - AndroidUtilities.dp(6.3f - (drawPinnedBottom ? 2 : 0)) - timeLayout.getHeight(); } else { x1 = backgroundDrawableLeft + AndroidUtilities.dp(8); y1 = layoutHeight - AndroidUtilities.dp(28); rect.set(x1, y1, x1 + timeWidthAudio + AndroidUtilities.dp(8 + 12 + 2), y1 + AndroidUtilities.dp(17)); canvas.drawRoundRect(rect, AndroidUtilities.dp(4), AndroidUtilities.dp(4), Theme.chat_actionBackgroundPaint); - if (!playing && currentMessageObject.messageOwner.to_id.channel_id == 0 && currentMessageObject.isContentUnread()) { + if (!playing && currentMessageObject.isContentUnread()) { Theme.chat_docBackPaint.setColor(Theme.getColor(Theme.key_chat_mediaTimeText)); canvas.drawCircle(x1 + timeWidthAudio + AndroidUtilities.dp(12), y1 + AndroidUtilities.dp(8.3f), AndroidUtilities.dp(3), Theme.chat_docBackPaint); } else { @@ -3924,7 +4350,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate durationLayout.draw(canvas); canvas.restore(); - if (currentMessageObject.type != 0 && currentMessageObject.messageOwner.to_id.channel_id == 0 && currentMessageObject.isContentUnread()) { + if (currentMessageObject.type != 0 && currentMessageObject.isContentUnread()) { Theme.chat_docBackPaint.setColor(Theme.getColor(currentMessageObject.isOutOwner() ? Theme.key_chat_outVoiceSeekbarFill : Theme.key_chat_inVoiceSeekbarFill)); canvas.drawCircle(timeAudioX + timeWidthAudio + AndroidUtilities.dp(6), AndroidUtilities.dp(51) + namesOffset + mediaOffsetY, AndroidUtilities.dp(3), Theme.chat_docBackPaint); } @@ -3942,7 +4368,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } - if (infoLayout != null && (buttonState == 1 || buttonState == 0 || buttonState == 3 || currentMessageObject.isSecretPhoto())) { + if (!forceNotDrawTime && infoLayout != null && (buttonState == 1 || buttonState == 0 || buttonState == 3 || currentMessageObject.isSecretPhoto())) { Theme.chat_infoPaint.setColor(Theme.getColor(Theme.key_chat_mediaInfoText)); int x1 = photoImage.getImageX() + AndroidUtilities.dp(4); int y1 = photoImage.getImageY() + AndroidUtilities.dp(4); @@ -3964,23 +4390,72 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (currentMessageObject.type == 4) { if (docTitleLayout != null) { if (currentMessageObject.isOutOwner()) { - Theme.chat_locationTitlePaint.setColor(Theme.getColor(Theme.key_chat_outVenueNameText)); + if (currentMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeoLive) { + Theme.chat_locationTitlePaint.setColor(Theme.getColor(Theme.key_chat_messageTextOut)); + } else { + Theme.chat_locationTitlePaint.setColor(Theme.getColor(Theme.key_chat_outVenueNameText)); + } Theme.chat_locationAddressPaint.setColor(Theme.getColor(isDrawSelectedBackground() ? Theme.key_chat_outVenueInfoSelectedText : Theme.key_chat_outVenueInfoText)); } else { - Theme.chat_locationTitlePaint.setColor(Theme.getColor(Theme.key_chat_inVenueNameText)); + if (currentMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeoLive) { + Theme.chat_locationTitlePaint.setColor(Theme.getColor(Theme.key_chat_messageTextIn)); + } else { + Theme.chat_locationTitlePaint.setColor(Theme.getColor(Theme.key_chat_inVenueNameText)); + } Theme.chat_locationAddressPaint.setColor(Theme.getColor(isDrawSelectedBackground() ? Theme.key_chat_inVenueInfoSelectedText : Theme.key_chat_inVenueInfoText)); } - canvas.save(); - canvas.translate(docTitleOffsetX + photoImage.getImageX() + photoImage.getImageWidth() + AndroidUtilities.dp(10), photoImage.getImageY() + AndroidUtilities.dp(8)); - docTitleLayout.draw(canvas); - canvas.restore(); + if (currentMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeoLive) { + int cy = photoImage.getImageY2() + AndroidUtilities.dp(30); + if (!locationExpired) { + forceNotDrawTime = true; + float progress = 1.0f - Math.abs(ConnectionsManager.getInstance().getCurrentTime() - currentMessageObject.messageOwner.date) / (float) currentMessageObject.messageOwner.media.period; + rect.set(photoImage.getImageX2() - AndroidUtilities.dp(43), cy - AndroidUtilities.dp(15), photoImage.getImageX2() - AndroidUtilities.dp(13), cy + AndroidUtilities.dp(15)); + if (currentMessageObject.isOutOwner()) { + Theme.chat_radialProgress2Paint.setColor(Theme.getColor(Theme.key_chat_outInstant)); + Theme.chat_livePaint.setColor(Theme.getColor(Theme.key_chat_outInstant)); + } else { + Theme.chat_radialProgress2Paint.setColor(Theme.getColor(Theme.key_chat_inInstant)); + Theme.chat_livePaint.setColor(Theme.getColor(Theme.key_chat_inInstant)); + } - if (infoLayout != null) { + Theme.chat_radialProgress2Paint.setAlpha(50); + canvas.drawCircle(rect.centerX(), rect.centerY(), AndroidUtilities.dp(15), Theme.chat_radialProgress2Paint); + Theme.chat_radialProgress2Paint.setAlpha(255); + canvas.drawArc(rect, -90, -360 * progress, false, Theme.chat_radialProgress2Paint); + + String text = LocaleController.formatLocationLeftTime(Math.abs(currentMessageObject.messageOwner.media.period - (ConnectionsManager.getInstance().getCurrentTime() - currentMessageObject.messageOwner.date))); + float w = Theme.chat_livePaint.measureText(text); + + canvas.drawText(text, rect.centerX() - w / 2, cy + AndroidUtilities.dp(4), Theme.chat_livePaint); + + canvas.save(); + canvas.translate(photoImage.getImageX() + AndroidUtilities.dp(10), photoImage.getImageY2() + AndroidUtilities.dp(10)); + docTitleLayout.draw(canvas); + canvas.translate(0, AndroidUtilities.dp(23)); + infoLayout.draw(canvas); + canvas.restore(); + } + + int cx = photoImage.getImageX() + photoImage.getImageWidth() / 2 - AndroidUtilities.dp(31); + cy = photoImage.getImageY() + photoImage.getImageHeight() / 2 - AndroidUtilities.dp(38); + setDrawableBounds(Theme.chat_msgAvatarLiveLocationDrawable, cx, cy); + Theme.chat_msgAvatarLiveLocationDrawable.draw(canvas); + + locationImageReceiver.setImageCoords(cx + AndroidUtilities.dp(5.0f), cy + AndroidUtilities.dp(5.0f), AndroidUtilities.dp(52), AndroidUtilities.dp(52)); + locationImageReceiver.draw(canvas); + } else { canvas.save(); - canvas.translate(photoImage.getImageX() + photoImage.getImageWidth() + AndroidUtilities.dp(10), photoImage.getImageY() + docTitleLayout.getLineBottom(docTitleLayout.getLineCount() - 1) + AndroidUtilities.dp(13)); - infoLayout.draw(canvas); + canvas.translate(docTitleOffsetX + photoImage.getImageX() + photoImage.getImageWidth() + AndroidUtilities.dp(10), photoImage.getImageY() + AndroidUtilities.dp(8)); + docTitleLayout.draw(canvas); canvas.restore(); + + if (infoLayout != null) { + canvas.save(); + canvas.translate(photoImage.getImageX() + photoImage.getImageWidth() + AndroidUtilities.dp(10), photoImage.getImageY() + docTitleLayout.getLineBottom(docTitleLayout.getLineCount() - 1) + AndroidUtilities.dp(13)); + infoLayout.draw(canvas); + canvas.restore(); + } } } } else if (currentMessageObject.type == 16) { @@ -4061,26 +4536,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } - if (captionLayout != null) { - canvas.save(); - if (currentMessageObject.type == 1 || documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO || currentMessageObject.type == 8) { - canvas.translate(captionX = photoImage.getImageX() + AndroidUtilities.dp(5), captionY = photoImage.getImageY() + photoImage.getImageHeight() + AndroidUtilities.dp(6)); - } else if (hasOldCaptionPreview) { - canvas.translate(captionX = backgroundDrawableLeft + AndroidUtilities.dp(currentMessageObject.isOutOwner() ? 11 : 17), captionY = totalHeight - captionHeight - AndroidUtilities.dp(pinnedTop ? 9 : 10) - linkPreviewHeight - AndroidUtilities.dp(17)); - } else { - canvas.translate(captionX = backgroundDrawableLeft + AndroidUtilities.dp(currentMessageObject.isOutOwner() ? 11 : 17), captionY = totalHeight - captionHeight - AndroidUtilities.dp(pinnedTop ? 9 : 10)); - } - if (pressedLink != null) { - for (int b = 0; b < urlPath.size(); b++) { - canvas.drawPath(urlPath.get(b), Theme.chat_urlPaint); - } - } - try { - captionLayout.draw(canvas); - } catch (Exception e) { - FileLog.e(e); - } - canvas.restore(); + if (currentPosition == null) { + drawCaptionLayout(canvas); } if (hasOldCaptionPreview) { @@ -4090,7 +4547,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else { linkX = backgroundDrawableLeft + AndroidUtilities.dp(currentMessageObject.isOutOwner() ? 11 : 17); } - int startY = totalHeight - AndroidUtilities.dp(pinnedTop ? 9 : 10) - linkPreviewHeight - AndroidUtilities.dp(8); + int startY = totalHeight - AndroidUtilities.dp(drawPinnedTop ? 9 : 10) - linkPreviewHeight - AndroidUtilities.dp(8); int linkPreviewY = startY; Theme.chat_replyLinePaint.setColor(Theme.getColor(currentMessageObject.isOutOwner() ? Theme.key_chat_outPreviewLine : Theme.key_chat_inPreviewLine)); @@ -4215,6 +4672,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } if (drawImageButton && photoImage.getVisible()) { + if (controlsAlpha != 1.0f) { + radialProgress.setOverrideAlpha(controlsAlpha); + } radialProgress.draw(canvas); } @@ -4338,10 +4798,30 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } return maxWidth - backgroundWidth - AndroidUtilities.dp(57); } - return backgroundWidth - AndroidUtilities.dp(mediaBackground ? 22 : 31); + if (currentMessagesGroup != null) { + int dWidth; + if (AndroidUtilities.isTablet()) { + dWidth = AndroidUtilities.getMinTabletSide(); + } else { + dWidth = AndroidUtilities.displaySize.x; + } + int firstLineWidth = 0; + for (int a = 0; a < currentMessagesGroup.posArray.size(); a++) { + MessageObject.GroupedMessagePosition position = currentMessagesGroup.posArray.get(a); + if (position.minY == 0) { + firstLineWidth += Math.ceil((position.pw + position.leftSpanOffset) / 1000.0f * dWidth); + } else { + break; + } + } + return firstLineWidth - AndroidUtilities.dp(31 + (isAvatarVisible ? 48 : 0)); + } else { + return backgroundWidth - AndroidUtilities.dp(mediaBackground ? 22 : 31); + } } public void updateButtonState(boolean animated) { + drawRadialCheckBackground = false; String fileName = null; boolean fileExists = false; if (currentMessageObject.type == 1) { @@ -4429,8 +4909,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate boolean progressVisible = false; if (!FileLoader.getInstance().isLoadingFile(fileName)) { if (!cancelLoading && - (documentAttachType == 0 && MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_PHOTO) || - documentAttachType == DOCUMENT_ATTACH_TYPE_GIF && MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_GIF))) { + (documentAttachType == 0 && MediaController.getInstance().canDownloadMedia(currentMessageObject) || + documentAttachType == DOCUMENT_ATTACH_TYPE_GIF && MediaController.getInstance().canDownloadMedia(currentMessageObject))) { progressVisible = true; buttonState = 1; } else { @@ -4467,10 +4947,16 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else { buttonState = 1; } - radialProgress.setBackground(getDrawableForCurrentState(), needProgress, animated); + boolean sending = SendMessagesHelper.getInstance().isSendingMessage(currentMessageObject.getId()); + if (currentPosition != null && sending && buttonState == 1) { + drawRadialCheckBackground = true; + radialProgress.setCheckBackground(false, animated); + } else { + radialProgress.setBackground(getDrawableForCurrentState(), needProgress, animated); + } if (needProgress) { Float progress = ImageLoader.getInstance().getFileProgress(currentMessageObject.messageOwner.attachPath); - if (progress == null && SendMessagesHelper.getInstance().isSendingMessage(currentMessageObject.getId())) { + if (progress == null && sending) { progress = 1.0f; } radialProgress.setProgress(progress != null ? progress : 0, false); @@ -4490,11 +4976,11 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (!FileLoader.getInstance().isLoadingFile(fileName)) { boolean autoDownload = false; if (currentMessageObject.type == 1) { - autoDownload = MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_PHOTO); + autoDownload = MediaController.getInstance().canDownloadMedia(currentMessageObject); } else if (currentMessageObject.type == 8 && MessageObject.isNewGifDocument(currentMessageObject.messageOwner.media.document)) { - autoDownload = MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_GIF); + autoDownload = MediaController.getInstance().canDownloadMedia(currentMessageObject); } else if (currentMessageObject.type == 5) { - autoDownload = MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_VIDEOMESSAGE); + autoDownload = MediaController.getInstance().canDownloadMedia(currentMessageObject); } if (!cancelLoading && autoDownload) { progressVisible = true; @@ -4526,7 +5012,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } radialProgress.setBackground(getDrawableForCurrentState(), false, animated); if (photoNotSet) { - setMessageObject(currentMessageObject, pinnedBottom, pinnedTop); + setMessageObject(currentMessageObject, currentMessagesGroup, pinnedBottom, pinnedTop); } invalidate(); } @@ -4546,9 +5032,11 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate cancelLoading = false; radialProgress.setProgress(0, false); if (currentMessageObject.type == 1) { + photoImage.setForceLoading(true); photoImage.setImage(currentPhotoObject.location, currentPhotoFilter, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, currentPhotoFilterThumb, currentPhotoObject.size, null, currentMessageObject.shouldEncryptPhotoOrVideo() ? 2 : 0); } else if (currentMessageObject.type == 8) { currentMessageObject.gifState = 2; + photoImage.setForceLoading(true); photoImage.setImage(currentMessageObject.messageOwner.media.document, null, currentPhotoObject != null ? currentPhotoObject.location : null, currentPhotoFilterThumb, currentMessageObject.messageOwner.media.document.size, null, 0); } else if (currentMessageObject.isRoundVideo()) { if (currentMessageObject.isSecretMedia()) { @@ -4556,6 +5044,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else { currentMessageObject.gifState = 2; TLRPC.Document document = currentMessageObject.getDocument(); + photoImage.setForceLoading(true); photoImage.setImage(document, null, currentPhotoObject != null ? currentPhotoObject.location : null, currentPhotoFilterThumb, document.size, null, 0); } } else if (currentMessageObject.type == 9) { @@ -4564,12 +5053,14 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate FileLoader.getInstance().loadFile(documentAttach, true, currentMessageObject.shouldEncryptPhotoOrVideo() ? 2 : 0); } else if (currentMessageObject.type == 0 && documentAttachType != DOCUMENT_ATTACH_TYPE_NONE) { if (documentAttachType == DOCUMENT_ATTACH_TYPE_GIF) { + photoImage.setForceLoading(true); photoImage.setImage(currentMessageObject.messageOwner.media.webpage.document, null, currentPhotoObject.location, currentPhotoFilterThumb, currentMessageObject.messageOwner.media.webpage.document.size, null, 0); currentMessageObject.gifState = 2; } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_DOCUMENT) { FileLoader.getInstance().loadFile(currentMessageObject.messageOwner.media.webpage.document, false, 0); } } else { + photoImage.setForceLoading(true); photoImage.setImage(currentPhotoObject.location, currentPhotoFilter, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, currentPhotoFilterThumb, 0, null, 0); } buttonState = 1; @@ -4592,6 +5083,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO || documentAttachType == DOCUMENT_ATTACH_TYPE_DOCUMENT) { FileLoader.getInstance().cancelLoadFile(documentAttach); } else if (currentMessageObject.type == 0 || currentMessageObject.type == 1 || currentMessageObject.type == 8 || currentMessageObject.type == 5) { + ImageLoader.getInstance().cancelForceLoadingForImageReceiver(photoImage); photoImage.cancelLoadImage(); } else if (currentMessageObject.type == 9) { FileLoader.getInstance().cancelLoadFile(currentMessageObject.messageOwner.media.document); @@ -4652,7 +5144,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else if (!photoNotSet) { updateButtonState(true); } else { - setMessageObject(currentMessageObject, pinnedBottom, pinnedTop); + setMessageObject(currentMessageObject, currentMessagesGroup, pinnedBottom, pinnedTop); } } else { if (!photoNotSet || (currentMessageObject.type == 8 || currentMessageObject.type == 5) && currentMessageObject.gifState != 1) { @@ -4665,7 +5157,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } if (photoNotSet) { - setMessageObject(currentMessageObject, pinnedBottom, pinnedTop); + setMessageObject(currentMessageObject, currentMessagesGroup, pinnedBottom, pinnedTop); } } } @@ -4696,6 +5188,13 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate @Override public void onProgressUpload(String fileName, float progress, boolean isEncrypted) { radialProgress.setProgress(progress, true); + if (progress == 1.0f && currentPosition != null) { + boolean sending = SendMessagesHelper.getInstance().isSendingMessage(currentMessageObject.getId()); + if (sending && buttonState == 1) { + drawRadialCheckBackground = true; + radialProgress.setCheckBackground(false, true); + } + } } @Override @@ -4739,7 +5238,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (currentMessageObject.isFromUser()) { author = MessagesController.getInstance().getUser(messageObject.messageOwner.from_id); } - if (messageObject.messageOwner.via_bot_id == 0 && messageObject.messageOwner.via_bot_name == null && (author == null || !author.bot) && (messageObject.messageOwner.flags & TLRPC.MESSAGE_FLAG_EDITED) != 0) { + if (!messageObject.isLiveLocation() && messageObject.messageOwner.via_bot_id == 0 && messageObject.messageOwner.via_bot_name == null && (author == null || !author.bot) && (messageObject.messageOwner.flags & TLRPC.MESSAGE_FLAG_EDITED) != 0 && currentPosition == null) { timeString = LocaleController.getString("EditedMessage", R.string.EditedMessage) + " " + LocaleController.getInstance().formatterDay.format((long) (messageObject.messageOwner.date) * 1000); } else { timeString = LocaleController.getInstance().formatterDay.format((long) (messageObject.messageOwner.date) * 1000); @@ -4760,10 +5259,22 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate availableTimeWidth = AndroidUtilities.dp(1000); } int widthForSign = availableTimeWidth - timeWidth; + if (messageObject.isOutOwner()) { + if (messageObject.type == 5) { + widthForSign -= AndroidUtilities.dp(20); + } else { + widthForSign -= AndroidUtilities.dp(96); + } + } int width = (int) Math.ceil(Theme.chat_timePaint.measureText(signString, 0, signString.length())); if (width > widthForSign) { - signString = TextUtils.ellipsize(signString, Theme.chat_timePaint, widthForSign, TextUtils.TruncateAt.END); - width = widthForSign; + if (widthForSign <= 0) { + signString = ""; + width = 0; + } else { + signString = TextUtils.ellipsize(signString, Theme.chat_timePaint, widthForSign, TextUtils.TruncateAt.END); + width = widthForSign; + } } currentTimeString = signString + currentTimeString; timeTextWidth += width; @@ -4775,12 +5286,21 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate return isPressed() && isCheckPressed || !isCheckPressed && isPressed || isHighlighted; } + private boolean isOpenChatByShare(MessageObject messageObject) { + return messageObject.messageOwner.fwd_from != null && messageObject.messageOwner.fwd_from.saved_from_peer != null; + } + private boolean checkNeedDrawShareButton(MessageObject messageObject) { - if (messageObject.eventId != 0) { + if (currentPosition != null && !currentPosition.last) { return false; + } else if (messageObject.eventId != 0) { + return false; + } else if (messageObject.messageOwner.fwd_from != null && !messageObject.isOutOwner() && messageObject.messageOwner.fwd_from.saved_from_peer != null && messageObject.getDialogId() == UserConfig.getClientUserId()) { + drwaShareGoIcon = true; + return true; } else if (messageObject.type == 13) { return false; - } else if (messageObject.messageOwner.fwd_from != null && messageObject.messageOwner.fwd_from.channel_id != 0 && !messageObject.isOut()) { + } else if (messageObject.messageOwner.fwd_from != null && messageObject.messageOwner.fwd_from.channel_id != 0 && !messageObject.isOutOwner()) { return true; } else if (messageObject.isFromUser()) { if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaEmpty || messageObject.messageOwner.media == null || messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage && !(messageObject.messageOwner.media.webpage instanceof TLRPC.TL_webPage)) { @@ -4807,27 +5327,57 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate return false; } - private void setMessageObjectInternal(MessageObject messageObject) { - if ((messageObject.messageOwner.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { - if (currentMessageObject.isContentUnread() && !currentMessageObject.isOut()) { - MessagesController.getInstance().addToViewsQueue(currentMessageObject.messageOwner, false); - currentMessageObject.setContentIsRead(); - } else if (!currentMessageObject.viewsReloaded) { - MessagesController.getInstance().addToViewsQueue(currentMessageObject.messageOwner, true); - currentMessageObject.viewsReloaded = true; - } - } + public boolean isInsideBackground(float x, float y) { + return currentBackgroundDrawable != null && x >= getLeft() + backgroundDrawableLeft && x <= getLeft() + backgroundDrawableLeft + backgroundDrawableRight; + } - if (currentMessageObject.isFromUser()) { + private void updateCurrentUserAndChat() { + TLRPC.MessageFwdHeader fwd_from = currentMessageObject.messageOwner.fwd_from; + int currentUserId = UserConfig.getClientUserId(); + if (fwd_from != null && fwd_from.channel_id != 0 && currentMessageObject.getDialogId() == currentUserId) { + currentChat = MessagesController.getInstance().getChat(fwd_from.channel_id); + } else if (fwd_from != null && fwd_from.saved_from_peer != null) { + if (fwd_from.saved_from_peer.user_id != 0) { + if (fwd_from.from_id != 0) { + currentUser = MessagesController.getInstance().getUser(fwd_from.from_id); + } else { + currentUser = MessagesController.getInstance().getUser(fwd_from.saved_from_peer.user_id); + } + } else if (fwd_from.saved_from_peer.channel_id != 0) { + if (currentMessageObject.isSavedFromMegagroup() && fwd_from.from_id != 0) { + currentUser = MessagesController.getInstance().getUser(fwd_from.from_id); + } else { + currentChat = MessagesController.getInstance().getChat(fwd_from.saved_from_peer.channel_id); + } + } else if (fwd_from.saved_from_peer.chat_id != 0) { + if (fwd_from.from_id != 0) { + currentUser = MessagesController.getInstance().getUser(fwd_from.from_id); + } else { + currentChat = MessagesController.getInstance().getChat(fwd_from.saved_from_peer.chat_id); + } + } + } else if (fwd_from != null && fwd_from.from_id != 0 && fwd_from.channel_id == 0 && currentMessageObject.getDialogId() == currentUserId) { + currentUser = MessagesController.getInstance().getUser(fwd_from.from_id); + } else if (currentMessageObject.isFromUser()) { currentUser = MessagesController.getInstance().getUser(currentMessageObject.messageOwner.from_id); } else if (currentMessageObject.messageOwner.from_id < 0) { currentChat = MessagesController.getInstance().getChat(-currentMessageObject.messageOwner.from_id); } else if (currentMessageObject.messageOwner.post) { currentChat = MessagesController.getInstance().getChat(currentMessageObject.messageOwner.to_id.channel_id); } + } - if (isChat && !messageObject.isOutOwner() && messageObject.needDrawAvatar()) { - isAvatarVisible = true; + private void setMessageObjectInternal(MessageObject messageObject) { + if ((messageObject.messageOwner.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { + if (!currentMessageObject.viewsReloaded) { + MessagesController.getInstance().addToViewsQueue(currentMessageObject.messageOwner); + currentMessageObject.viewsReloaded = true; + } + } + + updateCurrentUserAndChat(); + + if (isAvatarVisible) { if (currentUser != null) { if (currentUser.photo != null) { currentPhoto = currentUser.photo.photo_small; @@ -4878,6 +5428,16 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (nameWidth < 0) { nameWidth = AndroidUtilities.dp(100); } + int adminWidth; + String adminString; + if (currentUser != null && !currentMessageObject.isOutOwner() && currentMessageObject.type != 13 && currentMessageObject.type != 5 && delegate.isChatAdminCell(currentUser.id)) { + adminString = LocaleController.getString("ChatAdmin", R.string.ChatAdmin); + adminWidth = (int) Math.ceil(Theme.chat_adminPaint.measureText(adminString)); + nameWidth -= adminWidth; + } else { + adminString = null; + adminWidth = 0; + } if (authorName) { if (currentUser != null) { @@ -4926,6 +5486,12 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else { nameWidth = 0; } + if (adminString != null) { + adminLayout = new StaticLayout(adminString, Theme.chat_adminPaint, adminWidth + AndroidUtilities.dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + nameWidth += adminLayout.getLineWidth(0) + AndroidUtilities.dp(8); + } else { + adminLayout = null; + } } catch (Exception e) { FileLog.e(e); } @@ -4944,7 +5510,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate forwardedNameLayout[0] = null; forwardedNameLayout[1] = null; forwardedNameWidth = 0; - if (drawForwardedName && messageObject.isForwarded()) { + if (drawForwardedName && messageObject.needDrawForwarded() && (currentPosition == null || currentPosition.minY == 0)) { if (messageObject.messageOwner.fwd_from.channel_id != 0) { currentForwardChannel = MessagesController.getInstance().getChat(messageObject.messageOwner.fwd_from.channel_id); } @@ -4995,22 +5561,22 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } - if (messageObject.isReply()) { - if (messageObject.type != 13 && messageObject.type != 5) { - namesOffset += AndroidUtilities.dp(42); - if (messageObject.type != 0) { - namesOffset += AndroidUtilities.dp(5); + if (messageObject.hasValidReplyMessageObject()) { + if (currentPosition == null || currentPosition.minY == 0) { + if (messageObject.type != 13 && messageObject.type != 5) { + namesOffset += AndroidUtilities.dp(42); + if (messageObject.type != 0) { + namesOffset += AndroidUtilities.dp(5); + } } - } - int maxWidth = getMaxNameWidth(); - if (messageObject.type != 13 && messageObject.type != 5) { - maxWidth -= AndroidUtilities.dp(10); - } + int maxWidth = getMaxNameWidth(); + if (messageObject.type != 13 && messageObject.type != 5) { + maxWidth -= AndroidUtilities.dp(10); + } + + CharSequence stringFinalText = null; - CharSequence stringFinalName = null; - CharSequence stringFinalText = null; - if (messageObject.replyMessageObject != null) { TLRPC.PhotoSize photoSize = FileLoader.getClosestPhotoSizeWithSize(messageObject.replyMessageObject.photoThumbs2, 80); if (photoSize == null) { photoSize = FileLoader.getClosestPhotoSizeWithSize(messageObject.replyMessageObject.photoThumbs, 80); @@ -5054,7 +5620,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (name == null) { name = LocaleController.getString("Loading", R.string.Loading); } - stringFinalName = TextUtils.ellipsize(name.replace('\n', ' '), Theme.chat_replyNamePaint, maxWidth, TextUtils.TruncateAt.END); + CharSequence stringFinalName = TextUtils.ellipsize(name.replace('\n', ' '), Theme.chat_replyNamePaint, maxWidth, TextUtils.TruncateAt.END); if (messageObject.replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGame) { stringFinalText = Emoji.replaceEmoji(messageObject.replyMessageObject.messageOwner.media.game.title, Theme.chat_replyTextPaint.getFontMetricsInt(), AndroidUtilities.dp(14), false); stringFinalText = TextUtils.ellipsize(stringFinalText, Theme.chat_replyTextPaint, maxWidth, TextUtils.TruncateAt.END); @@ -5070,30 +5636,31 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate stringFinalText = Emoji.replaceEmoji(mess, Theme.chat_replyTextPaint.getFontMetricsInt(), AndroidUtilities.dp(14), false); stringFinalText = TextUtils.ellipsize(stringFinalText, Theme.chat_replyTextPaint, maxWidth, TextUtils.TruncateAt.END); } - } - try { - replyNameWidth = AndroidUtilities.dp(4 + (needReplyImage ? 44 : 0)); - if (stringFinalName != null) { - replyNameLayout = new StaticLayout(stringFinalName, Theme.chat_replyNamePaint, maxWidth + AndroidUtilities.dp(6), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - if (replyNameLayout.getLineCount() > 0) { - replyNameWidth += (int) Math.ceil(replyNameLayout.getLineWidth(0)) + AndroidUtilities.dp(8); - replyNameOffset = replyNameLayout.getLineLeft(0); + + try { + replyNameWidth = AndroidUtilities.dp(4 + (needReplyImage ? 44 : 0)); + if (stringFinalName != null) { + replyNameLayout = new StaticLayout(stringFinalName, Theme.chat_replyNamePaint, maxWidth + AndroidUtilities.dp(6), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + if (replyNameLayout.getLineCount() > 0) { + replyNameWidth += (int) Math.ceil(replyNameLayout.getLineWidth(0)) + AndroidUtilities.dp(8); + replyNameOffset = replyNameLayout.getLineLeft(0); + } } + } catch (Exception e) { + FileLog.e(e); } - } catch (Exception e) { - FileLog.e(e); - } - try { - replyTextWidth = AndroidUtilities.dp(4 + (needReplyImage ? 44 : 0)); - if (stringFinalText != null) { - replyTextLayout = new StaticLayout(stringFinalText, Theme.chat_replyTextPaint, maxWidth + AndroidUtilities.dp(6), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - if (replyTextLayout.getLineCount() > 0) { - replyTextWidth += (int) Math.ceil(replyTextLayout.getLineWidth(0)) + AndroidUtilities.dp(8); - replyTextOffset = replyTextLayout.getLineLeft(0); + try { + replyTextWidth = AndroidUtilities.dp(4 + (needReplyImage ? 44 : 0)); + if (stringFinalText != null) { + replyTextLayout = new StaticLayout(stringFinalText, Theme.chat_replyTextPaint, maxWidth + AndroidUtilities.dp(6), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + if (replyTextLayout.getLineCount() > 0) { + replyTextWidth += (int) Math.ceil(replyTextLayout.getLineWidth(0)) + AndroidUtilities.dp(8); + replyTextOffset = replyTextLayout.getLineLeft(0); + } } + } catch (Exception e) { + FileLog.e(e); } - } catch (Exception e) { - FileLog.e(e); } } @@ -5166,79 +5733,144 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } Drawable currentBackgroundShadowDrawable; + Drawable currentBackgroundSelectedDrawable; + int additionalTop = 0; + int additionalBottom = 0; if (currentMessageObject.isOutOwner()) { - if (isDrawSelectedBackground()) { - if (!mediaBackground && !pinnedBottom) { - currentBackgroundDrawable = Theme.chat_msgOutSelectedDrawable; - currentBackgroundShadowDrawable = Theme.chat_msgOutShadowDrawable; - } else { - currentBackgroundDrawable = Theme.chat_msgOutMediaSelectedDrawable; - currentBackgroundShadowDrawable = Theme.chat_msgOutMediaShadowDrawable; - } + if (!mediaBackground && !drawPinnedBottom) { + currentBackgroundDrawable = Theme.chat_msgOutDrawable; + currentBackgroundSelectedDrawable = Theme.chat_msgOutSelectedDrawable; + currentBackgroundShadowDrawable = Theme.chat_msgOutShadowDrawable; } else { - if (!mediaBackground && !pinnedBottom) { - currentBackgroundDrawable = Theme.chat_msgOutDrawable; - currentBackgroundShadowDrawable = Theme.chat_msgOutShadowDrawable; - } else { - currentBackgroundDrawable = Theme.chat_msgOutMediaDrawable; - currentBackgroundShadowDrawable = Theme.chat_msgOutMediaShadowDrawable; - } + currentBackgroundDrawable = Theme.chat_msgOutMediaDrawable; + currentBackgroundSelectedDrawable = Theme.chat_msgOutMediaSelectedDrawable; + currentBackgroundShadowDrawable = Theme.chat_msgOutMediaShadowDrawable; } backgroundDrawableLeft = layoutWidth - backgroundWidth - (!mediaBackground ? 0 : AndroidUtilities.dp(9)); - int backgroundRight = backgroundWidth - (mediaBackground ? 0 : AndroidUtilities.dp(3)); + backgroundDrawableRight = backgroundWidth - (mediaBackground ? 0 : AndroidUtilities.dp(3)); + if (currentMessagesGroup != null) { + if (!currentPosition.edge) { + backgroundDrawableRight += AndroidUtilities.dp(10); + } + } int backgroundLeft = backgroundDrawableLeft; - if (!mediaBackground && pinnedBottom) { - backgroundRight -= AndroidUtilities.dp(6); + if (!mediaBackground && drawPinnedBottom) { + backgroundDrawableRight -= AndroidUtilities.dp(6); + } + + if (currentPosition != null) { + if ((currentPosition.flags & MessageObject.POSITION_FLAG_RIGHT) == 0) { + backgroundDrawableRight += AndroidUtilities.dp(8); + } + if ((currentPosition.flags & MessageObject.POSITION_FLAG_LEFT) == 0) { + backgroundLeft -= AndroidUtilities.dp(8); + backgroundDrawableRight += AndroidUtilities.dp(8); + } + if ((currentPosition.flags & MessageObject.POSITION_FLAG_TOP) == 0) { + additionalTop -= AndroidUtilities.dp(9); + additionalBottom += AndroidUtilities.dp(9); + } + if ((currentPosition.flags & MessageObject.POSITION_FLAG_BOTTOM) == 0) { + additionalBottom += AndroidUtilities.dp(9); + } } int offsetBottom; - if (pinnedBottom && pinnedTop) { + if (drawPinnedBottom && drawPinnedTop) { offsetBottom = 0; - } else if (pinnedBottom) { + } else if (drawPinnedBottom) { offsetBottom = AndroidUtilities.dp(1); } else { offsetBottom = AndroidUtilities.dp(2); } - setDrawableBounds(currentBackgroundDrawable, backgroundLeft, pinnedTop || pinnedTop && pinnedBottom ? 0 : AndroidUtilities.dp(1), backgroundRight, layoutHeight - offsetBottom); - setDrawableBounds(currentBackgroundShadowDrawable, backgroundLeft, pinnedTop || pinnedTop && pinnedBottom ? 0 : AndroidUtilities.dp(1), backgroundRight, layoutHeight - offsetBottom); + int backgroundTop = additionalTop + (drawPinnedTop || drawPinnedTop && drawPinnedBottom ? 0 : AndroidUtilities.dp(1)); + setDrawableBounds(currentBackgroundDrawable, backgroundLeft, backgroundTop, backgroundDrawableRight, layoutHeight - offsetBottom + additionalBottom); + setDrawableBounds(currentBackgroundSelectedDrawable, backgroundLeft, backgroundTop, backgroundDrawableRight, layoutHeight - offsetBottom + additionalBottom); + setDrawableBounds(currentBackgroundShadowDrawable, backgroundLeft, backgroundTop, backgroundDrawableRight, layoutHeight - offsetBottom + additionalBottom); } else { - if (isDrawSelectedBackground()) { - if (!mediaBackground && !pinnedBottom) { - currentBackgroundDrawable = Theme.chat_msgInSelectedDrawable; - currentBackgroundShadowDrawable = Theme.chat_msgInShadowDrawable; - } else { - currentBackgroundDrawable = Theme.chat_msgInMediaSelectedDrawable; - currentBackgroundShadowDrawable = Theme.chat_msgInMediaShadowDrawable; - } + if (!mediaBackground && !drawPinnedBottom) { + currentBackgroundDrawable = Theme.chat_msgInDrawable; + currentBackgroundSelectedDrawable = Theme.chat_msgInSelectedDrawable; + currentBackgroundShadowDrawable = Theme.chat_msgInShadowDrawable; } else { - if (!mediaBackground && !pinnedBottom) { - currentBackgroundDrawable = Theme.chat_msgInDrawable; - currentBackgroundShadowDrawable = Theme.chat_msgInShadowDrawable; - } else { - currentBackgroundDrawable = Theme.chat_msgInMediaDrawable; - currentBackgroundShadowDrawable = Theme.chat_msgInMediaShadowDrawable; - } + currentBackgroundDrawable = Theme.chat_msgInMediaDrawable; + currentBackgroundSelectedDrawable = Theme.chat_msgInMediaSelectedDrawable; + currentBackgroundShadowDrawable = Theme.chat_msgInMediaShadowDrawable; } backgroundDrawableLeft = AndroidUtilities.dp((isChat && isAvatarVisible ? 48 : 0) + (!mediaBackground ? 3 : 9)); - int backgroundRight = backgroundWidth - (mediaBackground ? 0 : AndroidUtilities.dp(3)); - int backgroundLeft = backgroundDrawableLeft; - if (!mediaBackground && pinnedBottom) { - backgroundRight -= AndroidUtilities.dp(6); - backgroundLeft += AndroidUtilities.dp(6); + backgroundDrawableRight = backgroundWidth - (mediaBackground ? 0 : AndroidUtilities.dp(3)); + if (currentMessagesGroup != null) { + if (!currentPosition.edge) { + backgroundDrawableLeft -= AndroidUtilities.dp(10); + backgroundDrawableRight += AndroidUtilities.dp(10); + } + if (currentPosition.leftSpanOffset != 0) { + backgroundDrawableLeft += (int) Math.ceil(currentPosition.leftSpanOffset / 1000.0f * getGroupPhotosWidth()); + } + } + if (!mediaBackground && drawPinnedBottom) { + backgroundDrawableRight -= AndroidUtilities.dp(6); + backgroundDrawableLeft += AndroidUtilities.dp(6); + } + if (currentPosition != null) { + if ((currentPosition.flags & MessageObject.POSITION_FLAG_RIGHT) == 0) { + backgroundDrawableRight += AndroidUtilities.dp(8); + } + if ((currentPosition.flags & MessageObject.POSITION_FLAG_LEFT) == 0) { + backgroundDrawableLeft -= AndroidUtilities.dp(8); + backgroundDrawableRight += AndroidUtilities.dp(8); + } + if ((currentPosition.flags & MessageObject.POSITION_FLAG_TOP) == 0) { + additionalTop -= AndroidUtilities.dp(9); + additionalBottom += AndroidUtilities.dp(9); + } + if ((currentPosition.flags & MessageObject.POSITION_FLAG_BOTTOM) == 0) { + additionalBottom += AndroidUtilities.dp(10); + } } int offsetBottom; - if (pinnedBottom && pinnedTop) { + if (drawPinnedBottom && drawPinnedTop) { offsetBottom = 0; - } else if (pinnedBottom) { + } else if (drawPinnedBottom) { offsetBottom = AndroidUtilities.dp(1); } else { offsetBottom = AndroidUtilities.dp(2); } - setDrawableBounds(currentBackgroundDrawable, backgroundLeft, pinnedTop || pinnedTop && pinnedBottom ? 0 : AndroidUtilities.dp(1), backgroundRight, layoutHeight - offsetBottom); - setDrawableBounds(currentBackgroundShadowDrawable, backgroundLeft, pinnedTop || pinnedTop && pinnedBottom ? 0 : AndroidUtilities.dp(1), backgroundRight, layoutHeight - offsetBottom); + int backgroundTop = additionalTop + (drawPinnedTop || drawPinnedTop && drawPinnedBottom ? 0 : AndroidUtilities.dp(1)); + setDrawableBounds(currentBackgroundDrawable, backgroundDrawableLeft, backgroundTop, backgroundDrawableRight, layoutHeight - offsetBottom + additionalBottom); + setDrawableBounds(currentBackgroundSelectedDrawable, backgroundDrawableLeft, backgroundTop, backgroundDrawableRight, layoutHeight - offsetBottom + additionalBottom); + setDrawableBounds(currentBackgroundShadowDrawable, backgroundDrawableLeft, backgroundTop, backgroundDrawableRight, layoutHeight - offsetBottom + additionalBottom); } if (drawBackground && currentBackgroundDrawable != null) { - currentBackgroundDrawable.draw(canvas); - currentBackgroundShadowDrawable.draw(canvas); + if (isHighlightedAnimated) { + currentBackgroundDrawable.draw(canvas); + float alpha = highlightProgress >= 300 ? 1.0f : highlightProgress / 300.0f; + if (currentPosition == null) { + currentBackgroundSelectedDrawable.setAlpha((int) (alpha * 255)); + currentBackgroundSelectedDrawable.draw(canvas); + } + long newTime = System.currentTimeMillis(); + long dt = Math.abs(newTime - lastHighlightProgressTime); + if (dt > 17) { + dt = 17; + } + highlightProgress -= dt; + lastHighlightProgressTime = newTime; + if (highlightProgress <= 0) { + highlightProgress = 0; + isHighlightedAnimated = false; + } + invalidate(); + } else { + if (isDrawSelectedBackground() && (currentPosition == null || getBackground() != null)) { + currentBackgroundSelectedDrawable.setAlpha(255); + currentBackgroundSelectedDrawable.draw(canvas); + } else { + currentBackgroundDrawable.draw(canvas); + } + } + if (currentPosition == null || currentPosition.flags != 0) { + currentBackgroundShadowDrawable.draw(canvas); + } } drawContent(canvas); @@ -5252,10 +5884,63 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } setDrawableBounds(Theme.chat_shareDrawable, shareStartX, shareStartY = layoutHeight - AndroidUtilities.dp(41)); Theme.chat_shareDrawable.draw(canvas); - setDrawableBounds(Theme.chat_shareIconDrawable, shareStartX + AndroidUtilities.dp(9), shareStartY + AndroidUtilities.dp(9)); - Theme.chat_shareIconDrawable.draw(canvas); + if (drwaShareGoIcon) { + setDrawableBounds(Theme.chat_goIconDrawable, shareStartX + AndroidUtilities.dp(12), shareStartY + AndroidUtilities.dp(9)); + Theme.chat_goIconDrawable.draw(canvas); + } else { + setDrawableBounds(Theme.chat_shareIconDrawable, shareStartX + AndroidUtilities.dp(9), shareStartY + AndroidUtilities.dp(9)); + Theme.chat_shareIconDrawable.draw(canvas); + } } + if (currentPosition == null) { + drawNamesLayout(canvas); + } + + if ((drawTime || !mediaBackground) && !forceNotDrawTime) { + drawTimeLayout(canvas); + } + + if (controlsAlpha != 1.0f || timeAlpha != 1.0f) { + long newTime = System.currentTimeMillis(); + long dt = Math.abs(lastControlsAlphaChangeTime - newTime); + if (dt > 17) { + dt = 17; + } + totalChangeTime += dt; + if (totalChangeTime > 100) { + totalChangeTime = 100; + } + lastControlsAlphaChangeTime = newTime; + if (controlsAlpha != 1.0f) { + controlsAlpha = AndroidUtilities.decelerateInterpolator.getInterpolation(totalChangeTime / 100.0f); + } + if (timeAlpha != 1.0f) { + timeAlpha = AndroidUtilities.decelerateInterpolator.getInterpolation(totalChangeTime / 100.0f); + } + invalidate(); + if (forceNotDrawTime && currentPosition != null && currentPosition.last && getParent() != null) { + View parent = (View) getParent(); + parent.invalidate(); + } + } + } + + public int getBackgroundDrawableLeft() { + if (currentMessageObject.isOutOwner()) { + return layoutWidth - backgroundWidth - (!mediaBackground ? 0 : AndroidUtilities.dp(9)); + } else { + return AndroidUtilities.dp((isChat && isAvatarVisible ? 48 : 0) + (!mediaBackground ? 3 : 9)); + } + } + + public boolean hasNameLayout() { + return drawNameLayout && nameLayout != null || + drawForwardedName && forwardedNameLayout[0] != null && forwardedNameLayout[1] != null && (currentPosition == null || currentPosition.minY == 0 && currentPosition.minX == 0) || + replyNameLayout != null; + } + + public void drawNamesLayout(Canvas canvas) { if (drawNameLayout && nameLayout != null) { canvas.save(); @@ -5265,7 +5950,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (currentMessageObject.isOutOwner()) { nameX = AndroidUtilities.dp(28); } else { - nameX = currentBackgroundDrawable.getBounds().right + AndroidUtilities.dp(22); + nameX = backgroundDrawableLeft + backgroundDrawableRight + AndroidUtilities.dp(22); } nameY = layoutHeight - AndroidUtilities.dp(38); Theme.chat_systemDrawable.setColorFilter(Theme.colorFilter); @@ -5273,9 +5958,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate Theme.chat_systemDrawable.draw(canvas); } else { if (mediaBackground || currentMessageObject.isOutOwner()) { - nameX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(11) - nameOffsetX; + nameX = backgroundDrawableLeft + AndroidUtilities.dp(11) - nameOffsetX; } else { - nameX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(!mediaBackground && pinnedBottom ? 11 : 17) - nameOffsetX; + nameX = backgroundDrawableLeft + AndroidUtilities.dp(!mediaBackground && drawPinnedBottom ? 11 : 17) - nameOffsetX; } if (currentUser != null) { Theme.chat_namePaint.setColor(AvatarDrawable.getNameColorForId(currentUser.id)); @@ -5288,20 +5973,27 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else { Theme.chat_namePaint.setColor(AvatarDrawable.getNameColorForId(0)); } - nameY = AndroidUtilities.dp(pinnedTop ? 9 : 10); + nameY = AndroidUtilities.dp(drawPinnedTop ? 9 : 10); } canvas.translate(nameX, nameY); nameLayout.draw(canvas); canvas.restore(); + if (adminLayout != null) { + Theme.chat_adminPaint.setColor(Theme.getColor(isDrawSelectedBackground() ? Theme.key_chat_adminSelectedText : Theme.key_chat_adminText)); + canvas.save(); + canvas.translate(backgroundDrawableLeft + backgroundDrawableRight - AndroidUtilities.dp(11) - adminLayout.getLineWidth(0), nameY + AndroidUtilities.dp(0.5f)); + adminLayout.draw(canvas); + canvas.restore(); + } } - if (drawForwardedName && forwardedNameLayout[0] != null && forwardedNameLayout[1] != null) { + if (drawForwardedName && forwardedNameLayout[0] != null && forwardedNameLayout[1] != null && (currentPosition == null || currentPosition.minY == 0 && currentPosition.minX == 0)) { if (currentMessageObject.type == 5) { Theme.chat_forwardNamePaint.setColor(Theme.getColor(Theme.key_chat_stickerReplyNameText)); if (currentMessageObject.isOutOwner()) { forwardNameX = AndroidUtilities.dp(23); } else { - forwardNameX = currentBackgroundDrawable.getBounds().right + AndroidUtilities.dp(17); + forwardNameX = backgroundDrawableLeft + backgroundDrawableRight + AndroidUtilities.dp(17); } forwardNameY = AndroidUtilities.dp(12); @@ -5313,13 +6005,13 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate forwardNameY = AndroidUtilities.dp(10 + (drawNameLayout ? 19 : 0)); if (currentMessageObject.isOutOwner()) { Theme.chat_forwardNamePaint.setColor(Theme.getColor(Theme.key_chat_outForwardedNameText)); - forwardNameX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(11); + forwardNameX = backgroundDrawableLeft + AndroidUtilities.dp(11); } else { Theme.chat_forwardNamePaint.setColor(Theme.getColor(Theme.key_chat_inForwardedNameText)); if (mediaBackground) { - forwardNameX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(11); + forwardNameX = backgroundDrawableLeft + AndroidUtilities.dp(11); } else { - forwardNameX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(!mediaBackground && pinnedBottom ? 11 : 17); + forwardNameX = backgroundDrawableLeft + AndroidUtilities.dp(!mediaBackground && drawPinnedBottom ? 11 : 17); } } } @@ -5331,7 +6023,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } - if (currentMessageObject.isReply()) { + if (replyNameLayout != null) { if (currentMessageObject.type == 13 || currentMessageObject.type == 5) { Theme.chat_replyLinePaint.setColor(Theme.getColor(Theme.key_chat_stickerReplyLine)); Theme.chat_replyNamePaint.setColor(Theme.getColor(Theme.key_chat_stickerReplyNameText)); @@ -5339,7 +6031,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (currentMessageObject.isOutOwner()) { replyStartX = AndroidUtilities.dp(23); } else { - replyStartX = currentBackgroundDrawable.getBounds().right + AndroidUtilities.dp(17); + replyStartX = backgroundDrawableLeft + backgroundDrawableRight + AndroidUtilities.dp(17); } replyStartY = AndroidUtilities.dp(12); if (nameLayout != null) { @@ -5353,292 +6045,327 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (currentMessageObject.isOutOwner()) { Theme.chat_replyLinePaint.setColor(Theme.getColor(Theme.key_chat_outReplyLine)); Theme.chat_replyNamePaint.setColor(Theme.getColor(Theme.key_chat_outReplyNameText)); - if (currentMessageObject.replyMessageObject != null && currentMessageObject.replyMessageObject.type == 0 && !(currentMessageObject.replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGame || currentMessageObject.replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaInvoice)) { + if (currentMessageObject.hasValidReplyMessageObject() && currentMessageObject.replyMessageObject.type == 0 && !(currentMessageObject.replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGame || currentMessageObject.replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaInvoice)) { Theme.chat_replyTextPaint.setColor(Theme.getColor(Theme.key_chat_outReplyMessageText)); } else { Theme.chat_replyTextPaint.setColor(Theme.getColor(isDrawSelectedBackground() ? Theme.key_chat_outReplyMediaMessageSelectedText : Theme.key_chat_outReplyMediaMessageText)); } - replyStartX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(12); + replyStartX = backgroundDrawableLeft + AndroidUtilities.dp(12); } else { Theme.chat_replyLinePaint.setColor(Theme.getColor(Theme.key_chat_inReplyLine)); Theme.chat_replyNamePaint.setColor(Theme.getColor(Theme.key_chat_inReplyNameText)); - if (currentMessageObject.replyMessageObject != null && currentMessageObject.replyMessageObject.type == 0 && !(currentMessageObject.replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGame || currentMessageObject.replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaInvoice)) { + if (currentMessageObject.hasValidReplyMessageObject() && currentMessageObject.replyMessageObject.type == 0 && !(currentMessageObject.replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGame || currentMessageObject.replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaInvoice)) { Theme.chat_replyTextPaint.setColor(Theme.getColor(Theme.key_chat_inReplyMessageText)); } else { Theme.chat_replyTextPaint.setColor(Theme.getColor(isDrawSelectedBackground() ? Theme.key_chat_inReplyMediaMessageSelectedText : Theme.key_chat_inReplyMediaMessageText)); } if (mediaBackground) { - replyStartX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(12); + replyStartX = backgroundDrawableLeft + AndroidUtilities.dp(12); } else { - replyStartX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(!mediaBackground && pinnedBottom ? 12 : 18); + replyStartX = backgroundDrawableLeft + AndroidUtilities.dp(!mediaBackground && drawPinnedBottom ? 12 : 18); } } replyStartY = AndroidUtilities.dp(12 + (drawForwardedName && forwardedNameLayout[0] != null ? 36 : 0) + (drawNameLayout && nameLayout != null ? 20 : 0)); } - canvas.drawRect(replyStartX, replyStartY, replyStartX + AndroidUtilities.dp(2), replyStartY + AndroidUtilities.dp(35), Theme.chat_replyLinePaint); - if (needReplyImage) { - replyImageReceiver.setImageCoords(replyStartX + AndroidUtilities.dp(10), replyStartY, AndroidUtilities.dp(35), AndroidUtilities.dp(35)); - replyImageReceiver.draw(canvas); - } + if (currentPosition == null || currentPosition.minY == 0 && currentPosition.minX == 0) { + canvas.drawRect(replyStartX, replyStartY, replyStartX + AndroidUtilities.dp(2), replyStartY + AndroidUtilities.dp(35), Theme.chat_replyLinePaint); + if (needReplyImage) { + replyImageReceiver.setImageCoords(replyStartX + AndroidUtilities.dp(10), replyStartY, AndroidUtilities.dp(35), AndroidUtilities.dp(35)); + replyImageReceiver.draw(canvas); + } - if (replyNameLayout != null) { - canvas.save(); - canvas.translate(replyStartX - replyNameOffset + AndroidUtilities.dp(10 + (needReplyImage ? 44 : 0)), replyStartY); - replyNameLayout.draw(canvas); - canvas.restore(); - } - if (replyTextLayout != null) { - canvas.save(); - canvas.translate(replyStartX - replyTextOffset + AndroidUtilities.dp(10 + (needReplyImage ? 44 : 0)), replyStartY + AndroidUtilities.dp(19)); - replyTextLayout.draw(canvas); - canvas.restore(); + if (replyNameLayout != null) { + canvas.save(); + canvas.translate(replyStartX - replyNameOffset + AndroidUtilities.dp(10 + (needReplyImage ? 44 : 0)), replyStartY); + replyNameLayout.draw(canvas); + canvas.restore(); + } + if (replyTextLayout != null) { + canvas.save(); + canvas.translate(replyStartX - replyTextOffset + AndroidUtilities.dp(10 + (needReplyImage ? 44 : 0)), replyStartY + AndroidUtilities.dp(19)); + replyTextLayout.draw(canvas); + canvas.restore(); + } } } + } - if ((drawTime || !mediaBackground) && !forceNotDrawTime) { - if (pinnedBottom) { - canvas.translate(0, AndroidUtilities.dp(2)); - } - if (mediaBackground) { - Paint paint; - if (currentMessageObject.type == 13 || currentMessageObject.type == 5) { - paint = Theme.chat_actionBackgroundPaint; - } else { - paint = Theme.chat_timeBackgroundPaint; - } - int oldAlpha = paint.getAlpha(); - paint.setAlpha((int) (oldAlpha * controlsAlpha)); - Theme.chat_timePaint.setAlpha((int) (255 * controlsAlpha)); - int x1 = timeX - AndroidUtilities.dp(4); - int y1 = layoutHeight - AndroidUtilities.dp(28); - rect.set(x1, y1, x1 + timeWidth + AndroidUtilities.dp(8 + (currentMessageObject.isOutOwner() ? 20 : 0)), y1 + AndroidUtilities.dp(17)); - canvas.drawRoundRect(rect, AndroidUtilities.dp(4), AndroidUtilities.dp(4), paint); - paint.setAlpha(oldAlpha); + public boolean hasCaptionLayout() { + return captionLayout != null; + } - int additionalX = (int) (-timeLayout.getLineLeft(0)); - if ((currentMessageObject.messageOwner.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { - additionalX += (int) (timeWidth - timeLayout.getLineWidth(0)); - - if (currentMessageObject.isSending()) { - if (!currentMessageObject.isOutOwner()) { - setDrawableBounds(Theme.chat_msgMediaClockDrawable, timeX + AndroidUtilities.dp(11), layoutHeight - AndroidUtilities.dp(14.0f) - Theme.chat_msgMediaClockDrawable.getIntrinsicHeight()); - Theme.chat_msgMediaClockDrawable.draw(canvas); - } - } else if (currentMessageObject.isSendError()) { - if (!currentMessageObject.isOutOwner()) { - int x = timeX + AndroidUtilities.dp(11); - int y = layoutHeight - AndroidUtilities.dp(27.5f); - rect.set(x, y, x + AndroidUtilities.dp(14), y + AndroidUtilities.dp(14)); - canvas.drawRoundRect(rect, AndroidUtilities.dp(1), AndroidUtilities.dp(1), Theme.chat_msgErrorPaint); - setDrawableBounds(Theme.chat_msgErrorDrawable, x + AndroidUtilities.dp(6), y + AndroidUtilities.dp(2)); - Theme.chat_msgErrorDrawable.draw(canvas); - } - } else { - Drawable viewsDrawable; - if (currentMessageObject.type == 13 || currentMessageObject.type == 5) { - viewsDrawable = Theme.chat_msgStickerViewsDrawable; - } else { - viewsDrawable = Theme.chat_msgMediaViewsDrawable; - } - oldAlpha = ((BitmapDrawable) viewsDrawable).getPaint().getAlpha(); - viewsDrawable.setAlpha((int) (controlsAlpha * oldAlpha)); - setDrawableBounds(viewsDrawable, timeX, layoutHeight - AndroidUtilities.dp(10.5f) - timeLayout.getHeight()); - viewsDrawable.draw(canvas); - viewsDrawable.setAlpha(oldAlpha); - - if (viewsLayout != null) { - canvas.save(); - canvas.translate(timeX + viewsDrawable.getIntrinsicWidth() + AndroidUtilities.dp(3), layoutHeight - AndroidUtilities.dp(12.3f) - timeLayout.getHeight()); - viewsLayout.draw(canvas); - canvas.restore(); - } - } - } - - canvas.save(); - canvas.translate(timeX + additionalX, layoutHeight - AndroidUtilities.dp(12.3f) - timeLayout.getHeight()); - timeLayout.draw(canvas); - canvas.restore(); - Theme.chat_timePaint.setAlpha(255); + public void drawCaptionLayout(Canvas canvas) { + if (captionLayout != null) { + canvas.save(); + if (currentMessageObject.type == 1 || documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO || currentMessageObject.type == 8) { + canvas.translate(captionX = photoImage.getImageX() + AndroidUtilities.dp(5), captionY = photoImage.getImageY() + photoImage.getImageHeight() + AndroidUtilities.dp(6)); + } else if (hasOldCaptionPreview) { + canvas.translate(captionX = backgroundDrawableLeft + AndroidUtilities.dp(currentMessageObject.isOutOwner() ? 11 : 17), captionY = totalHeight - captionHeight - AndroidUtilities.dp(drawPinnedTop ? 9 : 10) - linkPreviewHeight - AndroidUtilities.dp(17)); } else { - int additionalX = (int) (-timeLayout.getLineLeft(0)); - if ((currentMessageObject.messageOwner.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { - additionalX += (int) (timeWidth - timeLayout.getLineWidth(0)); - - if (currentMessageObject.isSending()) { - if (!currentMessageObject.isOutOwner()) { - Drawable clockDrawable = isDrawSelectedBackground() ? Theme.chat_msgInSelectedClockDrawable : Theme.chat_msgInClockDrawable; - setDrawableBounds(clockDrawable, timeX + AndroidUtilities.dp(11), layoutHeight - AndroidUtilities.dp(8.5f) - clockDrawable.getIntrinsicHeight()); - clockDrawable.draw(canvas); - } - } else if (currentMessageObject.isSendError()) { - if (!currentMessageObject.isOutOwner()) { - int x = timeX + AndroidUtilities.dp(11); - int y = layoutHeight - AndroidUtilities.dp(20.5f); - rect.set(x, y, x + AndroidUtilities.dp(14), y + AndroidUtilities.dp(14)); - canvas.drawRoundRect(rect, AndroidUtilities.dp(1), AndroidUtilities.dp(1), Theme.chat_msgErrorPaint); - setDrawableBounds(Theme.chat_msgErrorDrawable, x + AndroidUtilities.dp(6), y + AndroidUtilities.dp(2)); - Theme.chat_msgErrorDrawable.draw(canvas); - } - } else { - if (!currentMessageObject.isOutOwner()) { - Drawable viewsDrawable = isDrawSelectedBackground() ? Theme.chat_msgInViewsSelectedDrawable : Theme.chat_msgInViewsDrawable; - setDrawableBounds(viewsDrawable, timeX, layoutHeight - AndroidUtilities.dp(4.5f) - timeLayout.getHeight()); - viewsDrawable.draw(canvas); - } else { - Drawable viewsDrawable = isDrawSelectedBackground() ? Theme.chat_msgOutViewsSelectedDrawable : Theme.chat_msgOutViewsDrawable; - setDrawableBounds(viewsDrawable, timeX, layoutHeight - AndroidUtilities.dp(4.5f) - timeLayout.getHeight()); - viewsDrawable.draw(canvas); - } - - if (viewsLayout != null) { - canvas.save(); - canvas.translate(timeX + Theme.chat_msgInViewsDrawable.getIntrinsicWidth() + AndroidUtilities.dp(3), layoutHeight - AndroidUtilities.dp(6.5f) - timeLayout.getHeight()); - viewsLayout.draw(canvas); - canvas.restore(); - } - } - } - - canvas.save(); - canvas.translate(timeX + additionalX, layoutHeight - AndroidUtilities.dp(6.5f) - timeLayout.getHeight()); - timeLayout.draw(canvas); - canvas.restore(); + canvas.translate(captionX = backgroundDrawableLeft + AndroidUtilities.dp(currentMessageObject.isOutOwner() ? 11 : 17), captionY = totalHeight - captionHeight - AndroidUtilities.dp(drawPinnedTop ? 9 : 10)); } + if (pressedLink != null) { + for (int b = 0; b < urlPath.size(); b++) { + canvas.drawPath(urlPath.get(b), Theme.chat_urlPaint); + } + } + try { + captionLayout.draw(canvas); + } catch (Exception e) { + FileLog.e(e); + } + canvas.restore(); + } + } - if (currentMessageObject.isOutOwner()) { - boolean drawCheck1 = false; - boolean drawCheck2 = false; - boolean drawClock = false; - boolean drawError = false; - boolean isBroadcast = (int) (currentMessageObject.getDialogId() >> 32) == 1; + public void drawTimeLayout(Canvas canvas) { + if ((!drawTime || groupPhotoInvisible) && mediaBackground) { + return; + } + if (currentMessageObject.type == 5) { + Theme.chat_timePaint.setColor(Theme.getColor(Theme.key_chat_mediaTimeText)); + } else { + if (mediaBackground && captionLayout == null) { + if (currentMessageObject.type == 13 || currentMessageObject.type == 5) { + Theme.chat_timePaint.setColor(Theme.getColor(Theme.key_chat_serviceText)); + } else { + Theme.chat_timePaint.setColor(Theme.getColor(Theme.key_chat_mediaTimeText)); + } + } else { + if (currentMessageObject.isOutOwner()) { + Theme.chat_timePaint.setColor(Theme.getColor(isDrawSelectedBackground() ? Theme.key_chat_outTimeSelectedText : Theme.key_chat_outTimeText)); + } else { + Theme.chat_timePaint.setColor(Theme.getColor(isDrawSelectedBackground() ? Theme.key_chat_inTimeSelectedText : Theme.key_chat_inTimeText)); + } + } + } + if (drawPinnedBottom) { + canvas.translate(0, AndroidUtilities.dp(2)); + } + if (mediaBackground && captionLayout == null) { + Paint paint; + if (currentMessageObject.type == 13 || currentMessageObject.type == 5) { + paint = Theme.chat_actionBackgroundPaint; + } else { + paint = Theme.chat_timeBackgroundPaint; + } + int oldAlpha = paint.getAlpha(); + paint.setAlpha((int) (oldAlpha * timeAlpha)); + Theme.chat_timePaint.setAlpha((int) (255 * timeAlpha)); + int x1 = timeX - AndroidUtilities.dp(4); + int y1 = layoutHeight - AndroidUtilities.dp(28); + rect.set(x1, y1, x1 + timeWidth + AndroidUtilities.dp(8 + (currentMessageObject.isOutOwner() ? 20 : 0)), y1 + AndroidUtilities.dp(17)); + canvas.drawRoundRect(rect, AndroidUtilities.dp(4), AndroidUtilities.dp(4), paint); + paint.setAlpha(oldAlpha); + + int additionalX = (int) (-timeLayout.getLineLeft(0)); + if ((currentMessageObject.messageOwner.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { + additionalX += (int) (timeWidth - timeLayout.getLineWidth(0)); if (currentMessageObject.isSending()) { - drawCheck1 = false; - drawCheck2 = false; - drawClock = true; - drawError = false; + if (!currentMessageObject.isOutOwner()) { + setDrawableBounds(Theme.chat_msgMediaClockDrawable, timeX + AndroidUtilities.dp(11), layoutHeight - AndroidUtilities.dp(14.0f) - Theme.chat_msgMediaClockDrawable.getIntrinsicHeight()); + Theme.chat_msgMediaClockDrawable.draw(canvas); + } } else if (currentMessageObject.isSendError()) { - drawCheck1 = false; - drawCheck2 = false; - drawClock = false; - drawError = true; - } else if (currentMessageObject.isSent()) { - if (!currentMessageObject.isUnread()) { - drawCheck1 = true; - drawCheck2 = true; - } else { - drawCheck1 = false; - drawCheck2 = true; - } - drawClock = false; - drawError = false; - } - - if (drawClock) { - if (!mediaBackground) { - setDrawableBounds(Theme.chat_msgOutClockDrawable, layoutWidth - AndroidUtilities.dp(18.5f) - Theme.chat_msgOutClockDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(8.5f) - Theme.chat_msgOutClockDrawable.getIntrinsicHeight()); - Theme.chat_msgOutClockDrawable.draw(canvas); - } else { - if (currentMessageObject.type == 13 || currentMessageObject.type == 5) { - setDrawableBounds(Theme.chat_msgStickerClockDrawable, layoutWidth - AndroidUtilities.dp(22.0f) - Theme.chat_msgStickerClockDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(13.5f) - Theme.chat_msgStickerClockDrawable.getIntrinsicHeight()); - Theme.chat_msgStickerClockDrawable.draw(canvas); - } else { - setDrawableBounds(Theme.chat_msgMediaClockDrawable, layoutWidth - AndroidUtilities.dp(22.0f) - Theme.chat_msgMediaClockDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(13.5f) - Theme.chat_msgMediaClockDrawable.getIntrinsicHeight()); - Theme.chat_msgMediaClockDrawable.draw(canvas); - } - } - } - - if (isBroadcast) { - if (drawCheck1 || drawCheck2) { - if (!mediaBackground) { - setDrawableBounds(Theme.chat_msgBroadcastDrawable, layoutWidth - AndroidUtilities.dp(20.5f) - Theme.chat_msgBroadcastDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(8.0f) - Theme.chat_msgBroadcastDrawable.getIntrinsicHeight()); - Theme.chat_msgBroadcastDrawable.draw(canvas); - } else { - setDrawableBounds(Theme.chat_msgBroadcastMediaDrawable, layoutWidth - AndroidUtilities.dp(24.0f) - Theme.chat_msgBroadcastMediaDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(14.0f) - Theme.chat_msgBroadcastMediaDrawable.getIntrinsicHeight()); - Theme.chat_msgBroadcastMediaDrawable.draw(canvas); - } + if (!currentMessageObject.isOutOwner()) { + int x = timeX + AndroidUtilities.dp(11); + int y = layoutHeight - AndroidUtilities.dp(27.5f); + rect.set(x, y, x + AndroidUtilities.dp(14), y + AndroidUtilities.dp(14)); + canvas.drawRoundRect(rect, AndroidUtilities.dp(1), AndroidUtilities.dp(1), Theme.chat_msgErrorPaint); + setDrawableBounds(Theme.chat_msgErrorDrawable, x + AndroidUtilities.dp(6), y + AndroidUtilities.dp(2)); + Theme.chat_msgErrorDrawable.draw(canvas); } } else { - if (drawCheck2) { - if (!mediaBackground) { - Drawable drawable = isDrawSelectedBackground() ? Theme.chat_msgOutCheckSelectedDrawable : Theme.chat_msgOutCheckDrawable; - if (drawCheck1) { - setDrawableBounds(drawable, layoutWidth - AndroidUtilities.dp(22.5f) - drawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(8.0f) - drawable.getIntrinsicHeight()); - } else { - setDrawableBounds(drawable, layoutWidth - AndroidUtilities.dp(18.5f) - drawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(8.0f) - drawable.getIntrinsicHeight()); - } - drawable.draw(canvas); - } else { - if (currentMessageObject.type == 13 || currentMessageObject.type == 5) { - if (drawCheck1) { - setDrawableBounds(Theme.chat_msgStickerCheckDrawable, layoutWidth - AndroidUtilities.dp(26.3f) - Theme.chat_msgStickerCheckDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(13.5f) - Theme.chat_msgStickerCheckDrawable.getIntrinsicHeight()); - } else { - setDrawableBounds(Theme.chat_msgStickerCheckDrawable, layoutWidth - AndroidUtilities.dp(21.5f) - Theme.chat_msgStickerCheckDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(13.5f) - Theme.chat_msgStickerCheckDrawable.getIntrinsicHeight()); - } - Theme.chat_msgStickerCheckDrawable.draw(canvas); - } else { - if (drawCheck1) { - setDrawableBounds(Theme.chat_msgMediaCheckDrawable, layoutWidth - AndroidUtilities.dp(26.3f) - Theme.chat_msgMediaCheckDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(13.5f) - Theme.chat_msgMediaCheckDrawable.getIntrinsicHeight()); - } else { - setDrawableBounds(Theme.chat_msgMediaCheckDrawable, layoutWidth - AndroidUtilities.dp(21.5f) - Theme.chat_msgMediaCheckDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(13.5f) - Theme.chat_msgMediaCheckDrawable.getIntrinsicHeight()); - } - Theme.chat_msgMediaCheckDrawable.setAlpha((int) (255 * controlsAlpha)); - Theme.chat_msgMediaCheckDrawable.draw(canvas); - Theme.chat_msgMediaCheckDrawable.setAlpha(255); - } - } - } - if (drawCheck1) { - if (!mediaBackground) { - Drawable drawable = isDrawSelectedBackground() ? Theme.chat_msgOutHalfCheckSelectedDrawable : Theme.chat_msgOutHalfCheckDrawable; - setDrawableBounds(drawable, layoutWidth - AndroidUtilities.dp(18) - drawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(8.0f) - drawable.getIntrinsicHeight()); - drawable.draw(canvas); - } else { - if (currentMessageObject.type == 13 || currentMessageObject.type == 5) { - setDrawableBounds(Theme.chat_msgStickerHalfCheckDrawable, layoutWidth - AndroidUtilities.dp(21.5f) - Theme.chat_msgStickerHalfCheckDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(13.5f) - Theme.chat_msgStickerHalfCheckDrawable.getIntrinsicHeight()); - Theme.chat_msgStickerHalfCheckDrawable.draw(canvas); - } else { - setDrawableBounds(Theme.chat_msgMediaHalfCheckDrawable, layoutWidth - AndroidUtilities.dp(21.5f) - Theme.chat_msgMediaHalfCheckDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(13.5f) - Theme.chat_msgMediaHalfCheckDrawable.getIntrinsicHeight()); - Theme.chat_msgMediaHalfCheckDrawable.setAlpha((int) (255 * controlsAlpha)); - Theme.chat_msgMediaHalfCheckDrawable.draw(canvas); - Theme.chat_msgMediaHalfCheckDrawable.setAlpha(255); - } - } - } - } - if (drawError) { - int x; - int y; - if (!mediaBackground) { - x = layoutWidth - AndroidUtilities.dp(32); - y = layoutHeight - AndroidUtilities.dp(21); + Drawable viewsDrawable; + if (currentMessageObject.type == 13 || currentMessageObject.type == 5) { + viewsDrawable = Theme.chat_msgStickerViewsDrawable; } else { - x = layoutWidth - AndroidUtilities.dp(34.5f); - y = layoutHeight - AndroidUtilities.dp(26.5f); + viewsDrawable = Theme.chat_msgMediaViewsDrawable; + } + oldAlpha = ((BitmapDrawable) viewsDrawable).getPaint().getAlpha(); + viewsDrawable.setAlpha((int) (timeAlpha * oldAlpha)); + setDrawableBounds(viewsDrawable, timeX, layoutHeight - AndroidUtilities.dp(10.5f) - timeLayout.getHeight()); + viewsDrawable.draw(canvas); + viewsDrawable.setAlpha(oldAlpha); + + if (viewsLayout != null) { + canvas.save(); + canvas.translate(timeX + viewsDrawable.getIntrinsicWidth() + AndroidUtilities.dp(3), layoutHeight - AndroidUtilities.dp(12.3f) - timeLayout.getHeight()); + viewsLayout.draw(canvas); + canvas.restore(); } - rect.set(x, y, x + AndroidUtilities.dp(14), y + AndroidUtilities.dp(14)); - canvas.drawRoundRect(rect, AndroidUtilities.dp(1), AndroidUtilities.dp(1), Theme.chat_msgErrorPaint); - setDrawableBounds(Theme.chat_msgErrorDrawable, x + AndroidUtilities.dp(6), y + AndroidUtilities.dp(2)); - Theme.chat_msgErrorDrawable.draw(canvas); } } + + canvas.save(); + canvas.translate(timeX + additionalX, layoutHeight - AndroidUtilities.dp(12.3f) - timeLayout.getHeight()); + timeLayout.draw(canvas); + canvas.restore(); + Theme.chat_timePaint.setAlpha(255); + } else { + int additionalX = (int) (-timeLayout.getLineLeft(0)); + if ((currentMessageObject.messageOwner.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { + additionalX += (int) (timeWidth - timeLayout.getLineWidth(0)); + + if (currentMessageObject.isSending()) { + if (!currentMessageObject.isOutOwner()) { + Drawable clockDrawable = isDrawSelectedBackground() ? Theme.chat_msgInSelectedClockDrawable : Theme.chat_msgInClockDrawable; + setDrawableBounds(clockDrawable, timeX + AndroidUtilities.dp(11), layoutHeight - AndroidUtilities.dp(8.5f) - clockDrawable.getIntrinsicHeight()); + clockDrawable.draw(canvas); + } + } else if (currentMessageObject.isSendError()) { + if (!currentMessageObject.isOutOwner()) { + int x = timeX + AndroidUtilities.dp(11); + int y = layoutHeight - AndroidUtilities.dp(20.5f); + rect.set(x, y, x + AndroidUtilities.dp(14), y + AndroidUtilities.dp(14)); + canvas.drawRoundRect(rect, AndroidUtilities.dp(1), AndroidUtilities.dp(1), Theme.chat_msgErrorPaint); + setDrawableBounds(Theme.chat_msgErrorDrawable, x + AndroidUtilities.dp(6), y + AndroidUtilities.dp(2)); + Theme.chat_msgErrorDrawable.draw(canvas); + } + } else { + if (!currentMessageObject.isOutOwner()) { + Drawable viewsDrawable = isDrawSelectedBackground() ? Theme.chat_msgInViewsSelectedDrawable : Theme.chat_msgInViewsDrawable; + setDrawableBounds(viewsDrawable, timeX, layoutHeight - AndroidUtilities.dp(4.5f) - timeLayout.getHeight()); + viewsDrawable.draw(canvas); + } else { + Drawable viewsDrawable = isDrawSelectedBackground() ? Theme.chat_msgOutViewsSelectedDrawable : Theme.chat_msgOutViewsDrawable; + setDrawableBounds(viewsDrawable, timeX, layoutHeight - AndroidUtilities.dp(4.5f) - timeLayout.getHeight()); + viewsDrawable.draw(canvas); + } + + if (viewsLayout != null) { + canvas.save(); + canvas.translate(timeX + Theme.chat_msgInViewsDrawable.getIntrinsicWidth() + AndroidUtilities.dp(3), layoutHeight - AndroidUtilities.dp(6.5f) - timeLayout.getHeight()); + viewsLayout.draw(canvas); + canvas.restore(); + } + } + } + + canvas.save(); + canvas.translate(timeX + additionalX, layoutHeight - AndroidUtilities.dp(6.5f) - timeLayout.getHeight()); + timeLayout.draw(canvas); + canvas.restore(); } - if (controlsAlpha != 1.0f) { - long newTime = System.currentTimeMillis(); - long dt = Math.abs(lastControlsAlphaChangeTime - newTime); - if (dt > 17) { - dt = 17; + if (currentMessageObject.isOutOwner()) { + boolean drawCheck1 = false; + boolean drawCheck2 = false; + boolean drawClock = false; + boolean drawError = false; + boolean isBroadcast = (int) (currentMessageObject.getDialogId() >> 32) == 1; + + if (currentMessageObject.isSending()) { + drawCheck1 = false; + drawCheck2 = false; + drawClock = true; + drawError = false; + } else if (currentMessageObject.isSendError()) { + drawCheck1 = false; + drawCheck2 = false; + drawClock = false; + drawError = true; + } else if (currentMessageObject.isSent()) { + if (!currentMessageObject.isUnread()) { + drawCheck1 = true; + drawCheck2 = true; + } else { + drawCheck1 = false; + drawCheck2 = true; + } + drawClock = false; + drawError = false; } - totalChangeTime += dt; - if (totalChangeTime > 100) { - totalChangeTime = 100; + + if (drawClock) { + if (mediaBackground && captionLayout == null) { + if (currentMessageObject.type == 13 || currentMessageObject.type == 5) { + setDrawableBounds(Theme.chat_msgStickerClockDrawable, layoutWidth - AndroidUtilities.dp(22.0f) - Theme.chat_msgStickerClockDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(13.5f) - Theme.chat_msgStickerClockDrawable.getIntrinsicHeight()); + Theme.chat_msgStickerClockDrawable.draw(canvas); + } else { + setDrawableBounds(Theme.chat_msgMediaClockDrawable, layoutWidth - AndroidUtilities.dp(22.0f) - Theme.chat_msgMediaClockDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(13.5f) - Theme.chat_msgMediaClockDrawable.getIntrinsicHeight()); + Theme.chat_msgMediaClockDrawable.draw(canvas); + } + } else { + setDrawableBounds(Theme.chat_msgOutClockDrawable, layoutWidth - AndroidUtilities.dp(18.5f) - Theme.chat_msgOutClockDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(8.5f) - Theme.chat_msgOutClockDrawable.getIntrinsicHeight()); + Theme.chat_msgOutClockDrawable.draw(canvas); + } + } + + if (isBroadcast) { + if (drawCheck1 || drawCheck2) { + if (mediaBackground && captionLayout == null) { + setDrawableBounds(Theme.chat_msgBroadcastMediaDrawable, layoutWidth - AndroidUtilities.dp(24.0f) - Theme.chat_msgBroadcastMediaDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(14.0f) - Theme.chat_msgBroadcastMediaDrawable.getIntrinsicHeight()); + Theme.chat_msgBroadcastMediaDrawable.draw(canvas); + } else { + setDrawableBounds(Theme.chat_msgBroadcastDrawable, layoutWidth - AndroidUtilities.dp(20.5f) - Theme.chat_msgBroadcastDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(8.0f) - Theme.chat_msgBroadcastDrawable.getIntrinsicHeight()); + Theme.chat_msgBroadcastDrawable.draw(canvas); + } + } + } else { + if (drawCheck2) { + if (mediaBackground && captionLayout == null) { + if (currentMessageObject.type == 13 || currentMessageObject.type == 5) { + if (drawCheck1) { + setDrawableBounds(Theme.chat_msgStickerCheckDrawable, layoutWidth - AndroidUtilities.dp(26.3f) - Theme.chat_msgStickerCheckDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(13.5f) - Theme.chat_msgStickerCheckDrawable.getIntrinsicHeight()); + } else { + setDrawableBounds(Theme.chat_msgStickerCheckDrawable, layoutWidth - AndroidUtilities.dp(21.5f) - Theme.chat_msgStickerCheckDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(13.5f) - Theme.chat_msgStickerCheckDrawable.getIntrinsicHeight()); + } + Theme.chat_msgStickerCheckDrawable.draw(canvas); + } else { + if (drawCheck1) { + setDrawableBounds(Theme.chat_msgMediaCheckDrawable, layoutWidth - AndroidUtilities.dp(26.3f) - Theme.chat_msgMediaCheckDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(13.5f) - Theme.chat_msgMediaCheckDrawable.getIntrinsicHeight()); + } else { + setDrawableBounds(Theme.chat_msgMediaCheckDrawable, layoutWidth - AndroidUtilities.dp(21.5f) - Theme.chat_msgMediaCheckDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(13.5f) - Theme.chat_msgMediaCheckDrawable.getIntrinsicHeight()); + } + Theme.chat_msgMediaCheckDrawable.setAlpha((int) (255 * timeAlpha)); + Theme.chat_msgMediaCheckDrawable.draw(canvas); + Theme.chat_msgMediaCheckDrawable.setAlpha(255); + } + } else { + Drawable drawable = isDrawSelectedBackground() ? Theme.chat_msgOutCheckSelectedDrawable : Theme.chat_msgOutCheckDrawable; + if (drawCheck1) { + setDrawableBounds(drawable, layoutWidth - AndroidUtilities.dp(22.5f) - drawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(8.0f) - drawable.getIntrinsicHeight()); + } else { + setDrawableBounds(drawable, layoutWidth - AndroidUtilities.dp(18.5f) - drawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(8.0f) - drawable.getIntrinsicHeight()); + } + drawable.draw(canvas); + } + } + if (drawCheck1) { + if (mediaBackground && captionLayout == null) { + if (currentMessageObject.type == 13 || currentMessageObject.type == 5) { + setDrawableBounds(Theme.chat_msgStickerHalfCheckDrawable, layoutWidth - AndroidUtilities.dp(21.5f) - Theme.chat_msgStickerHalfCheckDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(13.5f) - Theme.chat_msgStickerHalfCheckDrawable.getIntrinsicHeight()); + Theme.chat_msgStickerHalfCheckDrawable.draw(canvas); + } else { + setDrawableBounds(Theme.chat_msgMediaHalfCheckDrawable, layoutWidth - AndroidUtilities.dp(21.5f) - Theme.chat_msgMediaHalfCheckDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(13.5f) - Theme.chat_msgMediaHalfCheckDrawable.getIntrinsicHeight()); + Theme.chat_msgMediaHalfCheckDrawable.setAlpha((int) (255 * timeAlpha)); + Theme.chat_msgMediaHalfCheckDrawable.draw(canvas); + Theme.chat_msgMediaHalfCheckDrawable.setAlpha(255); + } + } else { + Drawable drawable = isDrawSelectedBackground() ? Theme.chat_msgOutHalfCheckSelectedDrawable : Theme.chat_msgOutHalfCheckDrawable; + setDrawableBounds(drawable, layoutWidth - AndroidUtilities.dp(18) - drawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(8.0f) - drawable.getIntrinsicHeight()); + drawable.draw(canvas); + } + } + } + if (drawError) { + int x; + int y; + if (mediaBackground && captionLayout == null) { + x = layoutWidth - AndroidUtilities.dp(34.5f); + y = layoutHeight - AndroidUtilities.dp(26.5f); + } else { + x = layoutWidth - AndroidUtilities.dp(32); + y = layoutHeight - AndroidUtilities.dp(21); + } + rect.set(x, y, x + AndroidUtilities.dp(14), y + AndroidUtilities.dp(14)); + canvas.drawRoundRect(rect, AndroidUtilities.dp(1), AndroidUtilities.dp(1), Theme.chat_msgErrorPaint); + setDrawableBounds(Theme.chat_msgErrorDrawable, x + AndroidUtilities.dp(6), y + AndroidUtilities.dp(2)); + Theme.chat_msgErrorDrawable.draw(canvas); } - lastControlsAlphaChangeTime = newTime; - controlsAlpha = AndroidUtilities.decelerateInterpolator.getInterpolation(totalChangeTime / 100.0f); - invalidate(); } } @@ -5659,6 +6386,14 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate return pinnedTop; } + public MessageObject.GroupedMessages getCurrentMessagesGroup() { + return currentMessagesGroup; + } + + public MessageObject.GroupedMessagePosition getCurrentPosition() { + return currentPosition; + } + public int getLayoutHeight() { return layoutHeight; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java index 3749c46fb..2492fe22a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java @@ -11,6 +11,7 @@ package org.telegram.ui.Cells; import android.content.Context; import android.graphics.Canvas; import android.graphics.RectF; +import android.os.Build; import android.text.Layout; import android.text.SpannableStringBuilder; import android.text.Spanned; @@ -37,6 +38,7 @@ import org.telegram.messenger.UserConfig; import org.telegram.messenger.ImageReceiver; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.AvatarDrawable; +import org.telegram.ui.Components.GroupCreateCheckBox; import java.util.ArrayList; @@ -62,6 +64,7 @@ public class DialogCell extends BaseCell { private boolean isDialogCell; private int lastMessageDate; private int unreadCount; + private int mentionCount; private boolean lastUnreadState; private int lastSendState; private boolean dialogMuted; @@ -78,6 +81,8 @@ public class DialogCell extends BaseCell { private CharSequence lastPrintString = null; private TLRPC.DraftMessage draftMessage; + private GroupCreateCheckBox checkBox; + public boolean useSeparator = false; private int nameLeft; @@ -109,6 +114,7 @@ public class DialogCell extends BaseCell { private int errorTop = AndroidUtilities.dp(39); private int errorLeft; + private boolean drawPinBackground; private boolean drawPin; private int pinTop = AndroidUtilities.dp(39); private int pinLeft; @@ -119,6 +125,10 @@ public class DialogCell extends BaseCell { private int countWidth; private StaticLayout countLayout; + private boolean drawMention; + private int mentionLeft; + private int mentionWidth; + private boolean drawVerified; private int avatarTop = AndroidUtilities.dp(10); @@ -127,11 +137,17 @@ public class DialogCell extends BaseCell { private RectF rect = new RectF(); - public DialogCell(Context context) { + public DialogCell(Context context, boolean needCheck) { super(context); Theme.createDialogsResources(context); avatarImage.setRoundRadius(AndroidUtilities.dp(26)); + + if (needCheck) { + checkBox = new GroupCreateCheckBox(context); + checkBox.setVisibility(VISIBLE); + addView(checkBox); + } } public void setDialog(TLRPC.TL_dialog dialog, int i, int type) { @@ -154,6 +170,7 @@ public class DialogCell extends BaseCell { lastMessageDate = date; currentEditDate = messageObject != null ? messageObject.messageOwner.edit_date : 0; unreadCount = 0; + mentionCount = 0; lastUnreadState = messageObject != null && messageObject.isUnread(); if (message != null) { lastSendState = message.messageOwner.send_state; @@ -179,15 +196,22 @@ public class DialogCell extends BaseCell { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (checkBox != null) { + checkBox.measure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(24), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(24), MeasureSpec.EXACTLY)); + } setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), AndroidUtilities.dp(72) + (useSeparator ? 1 : 0)); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { if (currentDialogId == 0 && customDialog == null) { - super.onLayout(changed, left, top, right, bottom); return; } + if (checkBox != null) { + int x = LocaleController.isRTL ? (right - left) - AndroidUtilities.dp(42) : AndroidUtilities.dp(42); + int y = AndroidUtilities.dp(43); + checkBox.layout(x, y, x + checkBox.getMeasuredWidth(), y + checkBox.getMeasuredHeight()); + } if (changed) { buildLayout(); } @@ -197,6 +221,7 @@ public class DialogCell extends BaseCell { String nameString = ""; String timeString = ""; String countString = null; + String mentionString = null; CharSequence messageString = ""; CharSequence printingString = null; if (isDialogCell) { @@ -211,6 +236,16 @@ public class DialogCell extends BaseCell { drawNameLock = false; drawNameBot = false; drawVerified = false; + drawPinBackground = false; + boolean showChecks = !UserObject.isUserSelf(user); + boolean drawTime = true; + + String messageFormat; + if (Build.VERSION.SDK_INT >= 18) { + messageFormat = "%s: \u2068%s\u2069"; + } else { + messageFormat = "%s: %s"; + } if (customDialog != null) { if (customDialog.type == 2) { @@ -250,14 +285,14 @@ public class DialogCell extends BaseCell { SpannableStringBuilder stringBuilder; if (customDialog.isMedia) { currentMessagePaint = Theme.dialogs_messagePrintingPaint; - stringBuilder = SpannableStringBuilder.valueOf(String.format("%s: %s", name, message.messageText)); + stringBuilder = SpannableStringBuilder.valueOf(String.format(messageFormat, name, message.messageText)); stringBuilder.setSpan(new ForegroundColorSpan(Theme.getColor(Theme.key_chats_attachMessage)), name.length() + 2, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else { String mess = customDialog.message; if (mess.length() > 150) { mess = mess.substring(0, 150); } - stringBuilder = SpannableStringBuilder.valueOf(String.format("%s: %s", name, mess.replace('\n', ' '))); + stringBuilder = SpannableStringBuilder.valueOf(String.format(messageFormat, name, mess.replace('\n', ' '))); } if (stringBuilder.length() > 0) { stringBuilder.setSpan(new ForegroundColorSpan(Theme.getColor(Theme.key_chats_nameMessage)), 0, name.length() + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); @@ -381,7 +416,7 @@ public class DialogCell extends BaseCell { mess = mess.substring(0, 150); } String draftString = LocaleController.getString("Draft", R.string.Draft); - SpannableStringBuilder stringBuilder = SpannableStringBuilder.valueOf(String.format("%s: %s", draftString, mess.replace('\n', ' '))); + SpannableStringBuilder stringBuilder = SpannableStringBuilder.valueOf(String.format(messageFormat, draftString, mess.replace('\n', ' '))); stringBuilder.setSpan(new ForegroundColorSpan(Theme.getColor(Theme.key_chats_draft)), 0, draftString.length() + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); messageString = Emoji.replaceEmoji(stringBuilder, Theme.dialogs_messagePaint.getFontMetricsInt(), AndroidUtilities.dp(20), false); } @@ -419,8 +454,17 @@ public class DialogCell extends BaseCell { } else { fromChat = MessagesController.getInstance().getChat(message.messageOwner.to_id.channel_id); } - if (message.messageOwner instanceof TLRPC.TL_messageService) { - messageString = message.messageText; + if (dialogsType == 3 && UserObject.isUserSelf(user)) { + messageString = LocaleController.getString("SavedMessagesInfo", R.string.SavedMessagesInfo); + showChecks = false; + drawTime = false; + } else if (message.messageOwner instanceof TLRPC.TL_messageService) { + if (ChatObject.isChannel(chat) && message.messageOwner.action instanceof TLRPC.TL_messageActionHistoryClear) { + messageString = ""; + showChecks = false; + } else { + messageString = message.messageText; + } currentMessagePaint = Theme.dialogs_messagePrintingPaint; } else { if (chat != null && chat.id > 0 && fromChat == null) { @@ -441,15 +485,23 @@ public class DialogCell extends BaseCell { if (mess.length() > 150) { mess = mess.substring(0, 150); } - stringBuilder = SpannableStringBuilder.valueOf(String.format("%s: %s", name, mess.replace('\n', ' '))); + stringBuilder = SpannableStringBuilder.valueOf(String.format(messageFormat, name, mess.replace('\n', ' '))); } else if (message.messageOwner.media != null && !message.isMediaEmpty()) { currentMessagePaint = Theme.dialogs_messagePrintingPaint; if (message.messageOwner.media instanceof TLRPC.TL_messageMediaGame) { - stringBuilder = SpannableStringBuilder.valueOf(String.format("%s: \uD83C\uDFAE %s", name, message.messageOwner.media.game.title)); + if (Build.VERSION.SDK_INT >= 18) { + stringBuilder = SpannableStringBuilder.valueOf(String.format("%s: \uD83C\uDFAE \u2068%s\u2069", name, message.messageOwner.media.game.title)); + } else { + stringBuilder = SpannableStringBuilder.valueOf(String.format("%s: \uD83C\uDFAE %s", name, message.messageOwner.media.game.title)); + } } else if (message.type == 14) { - stringBuilder = SpannableStringBuilder.valueOf(String.format("%s: \uD83C\uDFA7 %s - %s", name, message.getMusicAuthor(), message.getMusicTitle())); + if (Build.VERSION.SDK_INT >= 18) { + stringBuilder = SpannableStringBuilder.valueOf(String.format("%s: \uD83C\uDFA7 \u2068%s - %s\u2069", name, message.getMusicAuthor(), message.getMusicTitle())); + } else { + stringBuilder = SpannableStringBuilder.valueOf(String.format("%s: \uD83C\uDFA7 %s - %s", name, message.getMusicAuthor(), message.getMusicTitle())); + } } else { - stringBuilder = SpannableStringBuilder.valueOf(String.format("%s: %s", name, message.messageText)); + stringBuilder = SpannableStringBuilder.valueOf(String.format(messageFormat, name, message.messageText)); } stringBuilder.setSpan(new ForegroundColorSpan(Theme.getColor(Theme.key_chats_attachMessage)), name.length() + 2, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else if (message.messageOwner.message != null) { @@ -457,7 +509,7 @@ public class DialogCell extends BaseCell { if (mess.length() > 150) { mess = mess.substring(0, 150); } - stringBuilder = SpannableStringBuilder.valueOf(String.format("%s: %s", name, mess.replace('\n', ' '))); + stringBuilder = SpannableStringBuilder.valueOf(String.format(messageFormat, name, mess.replace('\n', ' '))); } else { stringBuilder = SpannableStringBuilder.valueOf(""); } @@ -503,16 +555,23 @@ public class DialogCell extends BaseCell { drawCheck2 = false; drawClock = false; drawCount = false; + drawMention = false; drawError = false; } else { - if (unreadCount != 0) { + if (unreadCount != 0 && (unreadCount != 1 || unreadCount != mentionCount || message == null || !message.messageOwner.mentioned)) { drawCount = true; countString = String.format("%d", unreadCount); } else { drawCount = false; } + if (mentionCount != 0) { + drawMention = true; + mentionString = "@"; + } else { + drawMention = false; + } - if (message.isOut() && draftMessage == null) { + if (message.isOut() && draftMessage == null && showChecks) { if (message.isSending()) { drawCheck1 = false; drawCheck2 = false; @@ -524,6 +583,7 @@ public class DialogCell extends BaseCell { drawClock = false; drawError = true; drawCount = false; + drawMention = false; } else if (message.isSent()) { drawCheck1 = !message.isUnread() || ChatObject.isChannel(chat) && !chat.megagroup; drawCheck2 = true; @@ -541,8 +601,11 @@ public class DialogCell extends BaseCell { if (chat != null) { nameString = chat.title; } else if (user != null) { - if (user.id == UserConfig.getClientUserId()) { - nameString = LocaleController.getString("ChatYourSelfName", R.string.ChatYourSelfName); + if (UserObject.isUserSelf(user)) { + if (dialogsType == 3) { + drawPinBackground = true; + } + nameString = LocaleController.getString("SavedMessages", R.string.SavedMessages); } else if (user.id / 1000 != 777 && user.id / 1000 != 333 && ContactsController.getInstance().contactsDict.get(user.id) == null) { if (ContactsController.getInstance().contactsDict.size() == 0 && (!ContactsController.getInstance().contactsLoaded || ContactsController.getInstance().isLoadingContacts())) { nameString = UserObject.getUserName(user); @@ -565,12 +628,19 @@ public class DialogCell extends BaseCell { } } - int timeWidth = (int) Math.ceil(Theme.dialogs_timePaint.measureText(timeString)); - timeLayout = new StaticLayout(timeString, Theme.dialogs_timePaint, timeWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - if (!LocaleController.isRTL) { - timeLeft = getMeasuredWidth() - AndroidUtilities.dp(15) - timeWidth; + int timeWidth; + if (drawTime) { + timeWidth = (int) Math.ceil(Theme.dialogs_timePaint.measureText(timeString)); + timeLayout = new StaticLayout(timeString, Theme.dialogs_timePaint, timeWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + if (!LocaleController.isRTL) { + timeLeft = getMeasuredWidth() - AndroidUtilities.dp(15) - timeWidth; + } else { + timeLeft = AndroidUtilities.dp(15); + } } else { - timeLeft = AndroidUtilities.dp(15); + timeWidth = 0; + timeLayout = null; + timeLeft = 0; } int nameWidth; @@ -663,18 +733,34 @@ public class DialogCell extends BaseCell { errorLeft = AndroidUtilities.dp(11); messageLeft += w; } - } else if (countString != null) { - countWidth = Math.max(AndroidUtilities.dp(12), (int)Math.ceil(Theme.dialogs_countTextPaint.measureText(countString))); - countLayout = new StaticLayout(countString, Theme.dialogs_countTextPaint, countWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); - int w = countWidth + AndroidUtilities.dp(18); - messageWidth -= w; - if (!LocaleController.isRTL) { - countLeft = getMeasuredWidth() - countWidth - AndroidUtilities.dp(19); + } else if (countString != null || mentionString != null) { + if (countString != null) { + countWidth = Math.max(AndroidUtilities.dp(12), (int) Math.ceil(Theme.dialogs_countTextPaint.measureText(countString))); + countLayout = new StaticLayout(countString, Theme.dialogs_countTextPaint, countWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); + int w = countWidth + AndroidUtilities.dp(18); + messageWidth -= w; + if (!LocaleController.isRTL) { + countLeft = getMeasuredWidth() - countWidth - AndroidUtilities.dp(19); + } else { + countLeft = AndroidUtilities.dp(19); + messageLeft += w; + } + drawCount = true; } else { - countLeft = AndroidUtilities.dp(19); - messageLeft += w; + countWidth = 0; + } + if (mentionString != null) { + mentionWidth = AndroidUtilities.dp(12); + int w = mentionWidth + AndroidUtilities.dp(18); + messageWidth -= w; + if (!LocaleController.isRTL) { + mentionLeft = getMeasuredWidth() - mentionWidth - AndroidUtilities.dp(19) - (countWidth != 0 ? countWidth + AndroidUtilities.dp(18) : 0); + } else { + mentionLeft = AndroidUtilities.dp(19) + (countWidth != 0 ? countWidth + AndroidUtilities.dp(18) : 0); + messageLeft += w; + } + drawMention = true; } - drawCount = true; } else { if (drawPin) { int w = Theme.dialogs_pinnedDrawable.getIntrinsicWidth() + AndroidUtilities.dp(8); @@ -687,6 +773,7 @@ public class DialogCell extends BaseCell { } } drawCount = false; + drawMention = false; } if (checkMessage) { @@ -773,6 +860,8 @@ public class DialogCell extends BaseCell { return MessagesController.getInstance().dialogsServerOnly; } else if (dialogsType == 2) { return MessagesController.getInstance().dialogsGroupsOnly; + } else if (dialogsType == 3) { + return MessagesController.getInstance().dialogsForward; } return null; } @@ -786,6 +875,7 @@ public class DialogCell extends BaseCell { message != null && message.getId() != dialog.top_message || newMessageObject != null && newMessageObject.messageOwner.edit_date != currentEditDate || unreadCount != dialog.unread_count || + mentionCount != dialog.unread_mentions_count || message != newMessageObject || message == null && newMessageObject != null || newDraftMessage != draftMessage || drawPin != dialog.pinned) { currentDialogId = dialog.id; @@ -794,6 +884,13 @@ public class DialogCell extends BaseCell { } } + public void setChecked(boolean checked, boolean animated) { + if (checkBox == null) { + return; + } + checkBox.setChecked(checked, animated); + } + public void update(int mask) { if (customDialog != null) { lastMessageDate = customDialog.date; @@ -810,6 +907,7 @@ public class DialogCell extends BaseCell { message = MessagesController.getInstance().dialogMessage.get(dialog.id); lastUnreadState = message != null && message.isUnread(); unreadCount = dialog.unread_count; + mentionCount = dialog.unread_mentions_count; currentEditDate = message != null ? message.messageOwner.edit_date : 0; lastMessageDate = dialog.last_message_date; drawPin = dialog.pinned; @@ -857,8 +955,9 @@ public class DialogCell extends BaseCell { continueUpdate = true; } else if (isDialogCell) { TLRPC.TL_dialog dialog = MessagesController.getInstance().dialogs_dict.get(currentDialogId); - if (dialog != null && unreadCount != dialog.unread_count) { + if (dialog != null && (unreadCount != dialog.unread_count || mentionCount != dialog.unread_mentions_count)) { unreadCount = dialog.unread_count; + mentionCount = dialog.unread_mentions_count; continueUpdate = true; } } @@ -907,10 +1006,12 @@ public class DialogCell extends BaseCell { TLRPC.FileLocation photo = null; if (user != null) { - if (user.photo != null) { + avatarDrawable.setInfo(user); + if (UserObject.isUserSelf(user)) { + avatarDrawable.setSavedMessages(1); + } else if (user.photo != null) { photo = user.photo.photo_small; } - avatarDrawable.setInfo(user); } else if (chat != null) { if (chat.photo != null) { photo = chat.photo.photo_small; @@ -937,7 +1038,7 @@ public class DialogCell extends BaseCell { if (isSelected) { canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), Theme.dialogs_tabletSeletedPaint); } - if (drawPin) { + if (drawPin || drawPinBackground) { canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), Theme.dialogs_pinnedPaint); } @@ -962,10 +1063,12 @@ public class DialogCell extends BaseCell { canvas.restore(); } - canvas.save(); - canvas.translate(timeLeft, timeTop); - timeLayout.draw(canvas); - canvas.restore(); + if (timeLayout != null) { + canvas.save(); + canvas.translate(timeLeft, timeTop); + timeLayout.draw(canvas); + canvas.restore(); + } if (messageLayout != null) { canvas.save(); @@ -1008,16 +1111,25 @@ public class DialogCell extends BaseCell { canvas.drawRoundRect(rect, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, Theme.dialogs_errorPaint); setDrawableBounds(Theme.dialogs_errorDrawable, errorLeft + AndroidUtilities.dp(5.5f), errorTop + AndroidUtilities.dp(5)); Theme.dialogs_errorDrawable.draw(canvas); - } else if (drawCount) { - int x = countLeft - AndroidUtilities.dp(5.5f); - rect.set(x, countTop, x + countWidth + AndroidUtilities.dp(11), countTop + AndroidUtilities.dp(23)); - canvas.drawRoundRect(rect, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, dialogMuted ? Theme.dialogs_countGrayPaint : Theme.dialogs_countPaint); - canvas.save(); - canvas.translate(countLeft, countTop + AndroidUtilities.dp(4)); - if (countLayout != null) { - countLayout.draw(canvas); + } else if (drawCount || drawMention) { + if (drawCount) { + int x = countLeft - AndroidUtilities.dp(5.5f); + rect.set(x, countTop, x + countWidth + AndroidUtilities.dp(11), countTop + AndroidUtilities.dp(23)); + canvas.drawRoundRect(rect, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, dialogMuted ? Theme.dialogs_countGrayPaint : Theme.dialogs_countPaint); + canvas.save(); + canvas.translate(countLeft, countTop + AndroidUtilities.dp(4)); + if (countLayout != null) { + countLayout.draw(canvas); + } + canvas.restore(); + } + if (drawMention) { + int x = mentionLeft - AndroidUtilities.dp(5.5f); + rect.set(x, countTop, x + mentionWidth + AndroidUtilities.dp(11), countTop + AndroidUtilities.dp(23)); + canvas.drawRoundRect(rect, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, Theme.dialogs_countPaint); + setDrawableBounds(Theme.dialogs_mentionDrawable, mentionLeft - AndroidUtilities.dp(2), countTop + AndroidUtilities.dp(3.2f), AndroidUtilities.dp(16), AndroidUtilities.dp(16)); + Theme.dialogs_mentionDrawable.draw(canvas); } - canvas.restore(); } else if (drawPin) { setDrawableBounds(Theme.dialogs_pinnedDrawable, pinLeft, pinTop); Theme.dialogs_pinnedDrawable.draw(canvas); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogMeUrlCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogMeUrlCell.java new file mode 100644 index 000000000..de2ac71d5 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogMeUrlCell.java @@ -0,0 +1,399 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Cells; + +import android.content.Context; +import android.graphics.Canvas; +import android.text.Layout; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.text.TextUtils; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ChatObject; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.ImageReceiver; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.R; +import org.telegram.messenger.UserObject; +import org.telegram.tgnet.TLObject; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.AvatarDrawable; + +public class DialogMeUrlCell extends BaseCell { + + private TLRPC.RecentMeUrl recentMeUrl; + + private ImageReceiver avatarImage = new ImageReceiver(this); + private AvatarDrawable avatarDrawable = new AvatarDrawable(); + + public boolean useSeparator; + + private int nameLeft; + private StaticLayout nameLayout; + private boolean drawNameLock; + private boolean drawNameGroup; + private boolean drawNameBroadcast; + private boolean drawNameBot; + private int nameMuteLeft; + private int nameLockLeft; + private int nameLockTop; + + private int messageTop = AndroidUtilities.dp(40); + private int messageLeft; + private StaticLayout messageLayout; + + private boolean drawVerified; + + private int avatarTop = AndroidUtilities.dp(10); + + private boolean isSelected; + + public DialogMeUrlCell(Context context) { + super(context); + + Theme.createDialogsResources(context); + avatarImage.setRoundRadius(AndroidUtilities.dp(26)); + } + + public void setRecentMeUrl(TLRPC.RecentMeUrl url) { + recentMeUrl = url; + requestLayout(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + avatarImage.onDetachedFromWindow(); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + avatarImage.onAttachedToWindow(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), AndroidUtilities.dp(72) + (useSeparator ? 1 : 0)); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + if (changed) { + buildLayout(); + } + } + + public void buildLayout() { + String nameString = ""; + CharSequence messageString; + TextPaint currentNamePaint = Theme.dialogs_namePaint; + TextPaint currentMessagePaint = Theme.dialogs_messagePaint; + + drawNameGroup = false; + drawNameBroadcast = false; + drawNameLock = false; + drawNameBot = false; + drawVerified = false; + + TLObject image; + + if (recentMeUrl instanceof TLRPC.TL_recentMeUrlChat) { + TLRPC.Chat chat = MessagesController.getInstance().getChat(recentMeUrl.chat_id); + if (chat.id < 0 || ChatObject.isChannel(chat) && !chat.megagroup) { + drawNameBroadcast = true; + nameLockTop = AndroidUtilities.dp(16.5f); + } else { + drawNameGroup = true; + nameLockTop = AndroidUtilities.dp(17.5f); + } + drawVerified = chat.verified; + + if (!LocaleController.isRTL) { + nameLockLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline); + nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline + 4) + (drawNameGroup ? Theme.dialogs_groupDrawable.getIntrinsicWidth() : Theme.dialogs_broadcastDrawable.getIntrinsicWidth()); + } else { + nameLockLeft = getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline) - (drawNameGroup ? Theme.dialogs_groupDrawable.getIntrinsicWidth() : Theme.dialogs_broadcastDrawable.getIntrinsicWidth()); + nameLeft = AndroidUtilities.dp(14); + } + nameString = chat.title; + if (chat.photo != null) { + image = chat.photo.photo_small; + } else { + image = null; + } + avatarDrawable.setInfo(chat); + } else if (recentMeUrl instanceof TLRPC.TL_recentMeUrlUser) { + TLRPC.User user = MessagesController.getInstance().getUser(recentMeUrl.user_id); + if (!LocaleController.isRTL) { + nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline); + } else { + nameLeft = AndroidUtilities.dp(14); + } + if (user != null) { + if (user.bot) { + drawNameBot = true; + nameLockTop = AndroidUtilities.dp(16.5f); + if (!LocaleController.isRTL) { + nameLockLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline); + nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline + 4) + Theme.dialogs_botDrawable.getIntrinsicWidth(); + } else { + nameLockLeft = getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline) - Theme.dialogs_botDrawable.getIntrinsicWidth(); + nameLeft = AndroidUtilities.dp(14); + } + } + drawVerified = user.verified; + } + nameString = UserObject.getUserName(user); + if (user.photo != null) { + image = user.photo.photo_small; + } else { + image = null; + } + avatarDrawable.setInfo(user); + } else if (recentMeUrl instanceof TLRPC.TL_recentMeUrlStickerSet) { + if (!LocaleController.isRTL) { + nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline); + } else { + nameLeft = AndroidUtilities.dp(14); + } + nameString = recentMeUrl.set.set.title; + image = recentMeUrl.set.cover; + avatarDrawable.setInfo(5, recentMeUrl.set.set.title, null, false); + } else if (recentMeUrl instanceof TLRPC.TL_recentMeUrlChatInvite) { + if (!LocaleController.isRTL) { + nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline); + } else { + nameLeft = AndroidUtilities.dp(14); + } + if (recentMeUrl.chat_invite.chat != null) { + avatarDrawable.setInfo(recentMeUrl.chat_invite.chat); + nameString = recentMeUrl.chat_invite.chat.title; + if (recentMeUrl.chat_invite.chat.photo != null) { + image = recentMeUrl.chat_invite.chat.photo.photo_small; + } else { + image = null; + } + if (recentMeUrl.chat_invite.chat.id < 0 || ChatObject.isChannel(recentMeUrl.chat_invite.chat) && !recentMeUrl.chat_invite.chat.megagroup) { + drawNameBroadcast = true; + nameLockTop = AndroidUtilities.dp(16.5f); + } else { + drawNameGroup = true; + nameLockTop = AndroidUtilities.dp(17.5f); + } + drawVerified = recentMeUrl.chat_invite.chat.verified; + } else { + nameString = recentMeUrl.chat_invite.title; + image = recentMeUrl.chat_invite.photo.photo_small; + avatarDrawable.setInfo(5, recentMeUrl.chat_invite.title, null, false); + if (recentMeUrl.chat_invite.broadcast || recentMeUrl.chat_invite.channel) { + drawNameBroadcast = true; + nameLockTop = AndroidUtilities.dp(16.5f); + } else { + drawNameGroup = true; + nameLockTop = AndroidUtilities.dp(17.5f); + } + } + if (!LocaleController.isRTL) { + nameLockLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline); + nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline + 4) + (drawNameGroup ? Theme.dialogs_groupDrawable.getIntrinsicWidth() : Theme.dialogs_broadcastDrawable.getIntrinsicWidth()); + } else { + nameLockLeft = getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline) - (drawNameGroup ? Theme.dialogs_groupDrawable.getIntrinsicWidth() : Theme.dialogs_broadcastDrawable.getIntrinsicWidth()); + nameLeft = AndroidUtilities.dp(14); + } + } else if (recentMeUrl instanceof TLRPC.TL_recentMeUrlUnknown) { + if (!LocaleController.isRTL) { + nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline); + } else { + nameLeft = AndroidUtilities.dp(14); + } + nameString = "Url"; + image = null; + } else { + image = null; + } + messageString = MessagesController.getInstance().linkPrefix + "/" + recentMeUrl.url; + avatarImage.setImage(image, "50_50", avatarDrawable, null, 0); + + if (TextUtils.isEmpty(nameString)) { + nameString = LocaleController.getString("HiddenName", R.string.HiddenName); + } + + int nameWidth; + + if (!LocaleController.isRTL) { + nameWidth = getMeasuredWidth() - nameLeft - AndroidUtilities.dp(14); + } else { + nameWidth = getMeasuredWidth() - nameLeft - AndroidUtilities.dp(AndroidUtilities.leftBaseline); + } + if (drawNameLock) { + nameWidth -= AndroidUtilities.dp(4) + Theme.dialogs_lockDrawable.getIntrinsicWidth(); + } else if (drawNameGroup) { + nameWidth -= AndroidUtilities.dp(4) + Theme.dialogs_groupDrawable.getIntrinsicWidth(); + } else if (drawNameBroadcast) { + nameWidth -= AndroidUtilities.dp(4) + Theme.dialogs_broadcastDrawable.getIntrinsicWidth(); + } else if (drawNameBot) { + nameWidth -= AndroidUtilities.dp(4) + Theme.dialogs_botDrawable.getIntrinsicWidth(); + } + + if (drawVerified) { + int w = AndroidUtilities.dp(6) + Theme.dialogs_verifiedDrawable.getIntrinsicWidth(); + nameWidth -= w; + if (LocaleController.isRTL) { + nameLeft += w; + } + } + + nameWidth = Math.max(AndroidUtilities.dp(12), nameWidth); + try { + CharSequence nameStringFinal = TextUtils.ellipsize(nameString.replace('\n', ' '), currentNamePaint, nameWidth - AndroidUtilities.dp(12), TextUtils.TruncateAt.END); + nameLayout = new StaticLayout(nameStringFinal, currentNamePaint, nameWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + } catch (Exception e) { + FileLog.e(e); + } + + int messageWidth = getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline + 16); + int avatarLeft; + if (!LocaleController.isRTL) { + messageLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline); + avatarLeft = AndroidUtilities.dp(AndroidUtilities.isTablet() ? 13 : 9); + } else { + messageLeft = AndroidUtilities.dp(16); + avatarLeft = getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.isTablet() ? 65 : 61); + } + avatarImage.setImageCoords(avatarLeft, avatarTop, AndroidUtilities.dp(52), AndroidUtilities.dp(52)); + + messageWidth = Math.max(AndroidUtilities.dp(12), messageWidth); + CharSequence messageStringFinal = TextUtils.ellipsize(messageString, currentMessagePaint, messageWidth - AndroidUtilities.dp(12), TextUtils.TruncateAt.END); + try { + messageLayout = new StaticLayout(messageStringFinal, currentMessagePaint, messageWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + } catch (Exception e) { + FileLog.e(e); + } + + double widthpx; + float left; + if (LocaleController.isRTL) { + if (nameLayout != null && nameLayout.getLineCount() > 0) { + left = nameLayout.getLineLeft(0); + widthpx = Math.ceil(nameLayout.getLineWidth(0)); + if (drawVerified) { + nameMuteLeft = (int) (nameLeft + (nameWidth - widthpx) - AndroidUtilities.dp(6) - Theme.dialogs_verifiedDrawable.getIntrinsicWidth()); + } + if (left == 0) { + if (widthpx < nameWidth) { + nameLeft += (nameWidth - widthpx); + } + } + } + if (messageLayout != null && messageLayout.getLineCount() > 0) { + left = messageLayout.getLineLeft(0); + if (left == 0) { + widthpx = Math.ceil(messageLayout.getLineWidth(0)); + if (widthpx < messageWidth) { + messageLeft += (messageWidth - widthpx); + } + } + } + } else { + if (nameLayout != null && nameLayout.getLineCount() > 0) { + left = nameLayout.getLineRight(0); + if (left == nameWidth) { + widthpx = Math.ceil(nameLayout.getLineWidth(0)); + if (widthpx < nameWidth) { + nameLeft -= (nameWidth - widthpx); + } + } + if (drawVerified) { + nameMuteLeft = (int) (nameLeft + left + AndroidUtilities.dp(6)); + } + } + if (messageLayout != null && messageLayout.getLineCount() > 0) { + left = messageLayout.getLineRight(0); + if (left == messageWidth) { + widthpx = Math.ceil(messageLayout.getLineWidth(0)); + if (widthpx < messageWidth) { + messageLeft -= (messageWidth - widthpx); + } + } + } + } + } + + public void setDialogSelected(boolean value) { + if (isSelected != value) { + invalidate(); + } + isSelected = value; + } + + @Override + protected void onDraw(Canvas canvas) { + if (isSelected) { + canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), Theme.dialogs_tabletSeletedPaint); + } + + if (drawNameLock) { + setDrawableBounds(Theme.dialogs_lockDrawable, nameLockLeft, nameLockTop); + Theme.dialogs_lockDrawable.draw(canvas); + } else if (drawNameGroup) { + setDrawableBounds(Theme.dialogs_groupDrawable, nameLockLeft, nameLockTop); + Theme.dialogs_groupDrawable.draw(canvas); + } else if (drawNameBroadcast) { + setDrawableBounds(Theme.dialogs_broadcastDrawable, nameLockLeft, nameLockTop); + Theme.dialogs_broadcastDrawable.draw(canvas); + } else if (drawNameBot) { + setDrawableBounds(Theme.dialogs_botDrawable, nameLockLeft, nameLockTop); + Theme.dialogs_botDrawable.draw(canvas); + } + + if (nameLayout != null) { + canvas.save(); + canvas.translate(nameLeft, AndroidUtilities.dp(13)); + nameLayout.draw(canvas); + canvas.restore(); + } + + if (messageLayout != null) { + canvas.save(); + canvas.translate(messageLeft, messageTop); + try { + messageLayout.draw(canvas); + } catch (Exception e) { + FileLog.e(e); + } + canvas.restore(); + } + + if (drawVerified) { + setDrawableBounds(Theme.dialogs_verifiedDrawable, nameMuteLeft, AndroidUtilities.dp(16.5f)); + setDrawableBounds(Theme.dialogs_verifiedCheckDrawable, nameMuteLeft, AndroidUtilities.dp(16.5f)); + Theme.dialogs_verifiedDrawable.draw(canvas); + Theme.dialogs_verifiedCheckDrawable.draw(canvas); + } + + if (useSeparator) { + if (LocaleController.isRTL) { + canvas.drawLine(0, getMeasuredHeight() - 1, getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline), getMeasuredHeight() - 1, Theme.dividerPaint); + } else { + canvas.drawLine(AndroidUtilities.dp(AndroidUtilities.leftBaseline), getMeasuredHeight() - 1, getMeasuredWidth(), getMeasuredHeight() - 1, Theme.dividerPaint); + } + } + + avatarImage.draw(canvas); + } + + @Override + public boolean hasOverlappingRendering() { + return false; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogsEmptyCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogsEmptyCell.java new file mode 100644 index 000000000..5fd3a054e --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogsEmptyCell.java @@ -0,0 +1,82 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Cells; + +import android.content.Context; +import android.os.Build; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.R; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.LayoutHelper; + +import java.util.ArrayList; + +@SuppressWarnings("FieldCanBeLocal") +public class DialogsEmptyCell extends LinearLayout { + + private TextView emptyTextView1; + private TextView emptyTextView2; + + public DialogsEmptyCell(Context context) { + super(context); + + setGravity(Gravity.CENTER); + setOrientation(VERTICAL); + setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + return true; + } + }); + + emptyTextView1 = new TextView(context); + emptyTextView1.setText(LocaleController.getString("NoChats", R.string.NoChats)); + emptyTextView1.setTextColor(Theme.getColor(Theme.key_emptyListPlaceholder)); + emptyTextView1.setGravity(Gravity.CENTER); + emptyTextView1.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); + addView(emptyTextView1, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); + + emptyTextView2 = new TextView(context); + String help = LocaleController.getString("NoChatsHelp", R.string.NoChatsHelp); + if (AndroidUtilities.isTablet() && !AndroidUtilities.isSmallTablet()) { + help = help.replace('\n', ' '); + } + emptyTextView2.setText(help); + emptyTextView2.setTextColor(Theme.getColor(Theme.key_emptyListPlaceholder)); + emptyTextView2.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + emptyTextView2.setGravity(Gravity.CENTER); + emptyTextView2.setPadding(AndroidUtilities.dp(8), AndroidUtilities.dp(6), AndroidUtilities.dp(8), 0); + emptyTextView2.setLineSpacing(AndroidUtilities.dp(2), 1); + addView(emptyTextView2, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int totalHeight = MeasureSpec.getSize(heightMeasureSpec); + if (totalHeight == 0) { + totalHeight = AndroidUtilities.displaySize.y - ActionBar.getCurrentActionBarHeight() - (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0); + } + ArrayList arrayList = MessagesController.getInstance().hintDialogs; + if (!arrayList.isEmpty()) { + totalHeight -= AndroidUtilities.dp(72) * arrayList.size() + arrayList.size() - 1 + AndroidUtilities.dp(12 + 38); + } + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(totalHeight, MeasureSpec.EXACTLY)); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerProfileCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerProfileCell.java index 13c0682c2..8b655f112 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerProfileCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerProfileCell.java @@ -22,7 +22,6 @@ import android.os.Build; import android.text.TextUtils; import android.util.TypedValue; import android.view.Gravity; -import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; @@ -44,15 +43,15 @@ public class DrawerProfileCell extends FrameLayout { private TextView nameTextView; private TextView phoneTextView; private ImageView shadowView; - private CloudView cloudView; + //private CloudView cloudView; private Rect srcRect = new Rect(); private Rect destRect = new Rect(); private Paint paint = new Paint(); private Integer currentColor; - private Drawable cloudDrawable; - private int lastCloudColor; + //private Drawable cloudDrawable; + //private int lastCloudColor; - private class CloudView extends View { + /*private class CloudView extends View { private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); @@ -72,18 +71,18 @@ public class DrawerProfileCell extends FrameLayout { cloudDrawable.setColorFilter(new PorterDuffColorFilter(lastCloudColor = Theme.getColor(Theme.key_chats_menuCloud), PorterDuff.Mode.MULTIPLY)); } canvas.drawCircle(getMeasuredWidth() / 2.0f, getMeasuredHeight() / 2.0f, AndroidUtilities.dp(34) / 2.0f, paint); - int l = (getMeasuredWidth() - AndroidUtilities.dp(33)) / 2; - int t = (getMeasuredHeight() - AndroidUtilities.dp(33)) / 2; - cloudDrawable.setBounds(l, t, l + AndroidUtilities.dp(33), t + AndroidUtilities.dp(33)); + int l = (getMeasuredWidth() - AndroidUtilities.dp(24)) / 2; + int t = (getMeasuredHeight() - AndroidUtilities.dp(24)) / 2 + AndroidUtilities.dp(0.5f); + cloudDrawable.setBounds(l, t, l + AndroidUtilities.dp(24), t + AndroidUtilities.dp(24)); cloudDrawable.draw(canvas); } - } + }*/ public DrawerProfileCell(Context context) { super(context); - cloudDrawable = context.getResources().getDrawable(R.drawable.cloud); - cloudDrawable.setColorFilter(new PorterDuffColorFilter(lastCloudColor = Theme.getColor(Theme.key_chats_menuCloud), PorterDuff.Mode.MULTIPLY)); + //cloudDrawable = context.getResources().getDrawable(R.drawable.bookmark_filled); + //cloudDrawable.setColorFilter(new PorterDuffColorFilter(lastCloudColor = Theme.getColor(Theme.key_chats_menuCloud), PorterDuff.Mode.MULTIPLY)); shadowView = new ImageView(context); shadowView.setVisibility(INVISIBLE); @@ -113,8 +112,8 @@ public class DrawerProfileCell extends FrameLayout { phoneTextView.setGravity(Gravity.LEFT); addView(phoneTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.BOTTOM, 16, 0, 76, 9)); - cloudView = new CloudView(context); - addView(cloudView, LayoutHelper.createFrame(61, 61, Gravity.RIGHT | Gravity.BOTTOM)); + //cloudView = new CloudView(context); + //addView(cloudView, LayoutHelper.createFrame(61, 61, Gravity.RIGHT | Gravity.BOTTOM)); } @Override @@ -193,6 +192,6 @@ public class DrawerProfileCell extends FrameLayout { @Override public void invalidate() { super.invalidate(); - cloudView.invalidate(); + //cloudView.invalidate(); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/FeaturedStickerSetInfoCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/FeaturedStickerSetInfoCell.java index f646022e1..add163936 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/FeaturedStickerSetInfoCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/FeaturedStickerSetInfoCell.java @@ -146,7 +146,6 @@ public class FeaturedStickerSetInfoCell extends FrameLayout { } } }; - addButton.setPadding(AndroidUtilities.dp(17), 0, AndroidUtilities.dp(17), 0); addButton.setGravity(Gravity.CENTER); addButton.setTextColor(Theme.getColor(Theme.key_featuredStickers_buttonText)); addButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); @@ -182,6 +181,7 @@ public class FeaturedStickerSetInfoCell extends FrameLayout { addButton.setBackgroundDrawable(addDrawable); addButton.setText(LocaleController.getString("Add", R.string.Add).toUpperCase()); } + addButton.setPadding(AndroidUtilities.dp(17), 0, AndroidUtilities.dp(17), 0); } else { addButton.setVisibility(GONE); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/InviteTextCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/InviteTextCell.java new file mode 100644 index 000000000..ebea18a3d --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/InviteTextCell.java @@ -0,0 +1,80 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Cells; + +import android.content.Context; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.view.Gravity; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.ui.ActionBar.SimpleTextView; +import org.telegram.ui.ActionBar.Theme; + +public class InviteTextCell extends FrameLayout { + + private SimpleTextView textView; + private ImageView imageView; + + public InviteTextCell(Context context) { + super(context); + + textView = new SimpleTextView(context); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + textView.setTextSize(17); + textView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); + addView(textView); + + imageView = new ImageView(context); + imageView.setScaleType(ImageView.ScaleType.CENTER); + imageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_windowBackgroundWhiteGrayIcon), PorterDuff.Mode.MULTIPLY)); + addView(imageView); + } + + public SimpleTextView getTextView() { + return textView; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = AndroidUtilities.dp(72); + + textView.measure(MeasureSpec.makeMeasureSpec(width - AndroidUtilities.dp(71 + 24), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(20), MeasureSpec.EXACTLY)); + imageView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); + + setMeasuredDimension(width, AndroidUtilities.dp(72)); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + int height = bottom - top; + int width = right - left; + + int viewTop = (height - textView.getTextHeight()) / 2; + int viewLeft = !LocaleController.isRTL ? AndroidUtilities.dp(71) : AndroidUtilities.dp(24); + textView.layout(viewLeft, viewTop, viewLeft + textView.getMeasuredWidth(), viewTop + textView.getMeasuredHeight()); + + viewTop = (height - imageView.getMeasuredHeight()) / 2; + viewLeft = !LocaleController.isRTL ? AndroidUtilities.dp(20) : width - imageView.getMeasuredWidth() - AndroidUtilities.dp(20); + imageView.layout(viewLeft, viewTop, viewLeft + imageView.getMeasuredWidth(), viewTop + imageView.getMeasuredHeight()); + } + + public void setTextColor(int color) { + textView.setTextColor(color); + } + + public void setTextAndIcon(String text, int resId) { + textView.setText(text); + imageView.setImageResource(resId); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/InviteUserCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/InviteUserCell.java new file mode 100644 index 000000000..6dc3bfec0 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/InviteUserCell.java @@ -0,0 +1,114 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Cells; + +import android.content.Context; +import android.view.Gravity; +import android.widget.FrameLayout; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ContactsController; +import org.telegram.messenger.LocaleController; +import org.telegram.ui.ActionBar.SimpleTextView; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.AvatarDrawable; +import org.telegram.ui.Components.BackupImageView; +import org.telegram.ui.Components.GroupCreateCheckBox; +import org.telegram.ui.Components.LayoutHelper; + +public class InviteUserCell extends FrameLayout { + + private BackupImageView avatarImageView; + private SimpleTextView nameTextView; + private SimpleTextView statusTextView; + private GroupCreateCheckBox checkBox; + private AvatarDrawable avatarDrawable; + private ContactsController.Contact currentContact; + private CharSequence currentName; + + public InviteUserCell(Context context, boolean needCheck) { + super(context); + avatarDrawable = new AvatarDrawable(); + + avatarImageView = new BackupImageView(context); + avatarImageView.setRoundRadius(AndroidUtilities.dp(24)); + addView(avatarImageView, LayoutHelper.createFrame(50, 50, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 0 : 11, 11, LocaleController.isRTL ? 11 : 0, 0)); + + nameTextView = new SimpleTextView(context); + nameTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + nameTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + nameTextView.setTextSize(17); + nameTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); + addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 20, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 28 : 72, 14, LocaleController.isRTL ? 72 : 28, 0)); + + statusTextView = new SimpleTextView(context); + statusTextView.setTextSize(16); + statusTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); + addView(statusTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 20, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 28 : 72, 39, LocaleController.isRTL ? 72 : 28, 0)); + + if (needCheck) { + checkBox = new GroupCreateCheckBox(context); + checkBox.setVisibility(VISIBLE); + addView(checkBox, LayoutHelper.createFrame(24, 24, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 0 : 41, 41, LocaleController.isRTL ? 41 : 0, 0)); + } + } + + public void setUser(ContactsController.Contact contact, CharSequence name) { + currentContact = contact; + currentName = name; + update(0); + } + + public void setChecked(boolean checked, boolean animated) { + checkBox.setChecked(checked, animated); + } + + public ContactsController.Contact getContact() { + return currentContact; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(72), MeasureSpec.EXACTLY)); + } + + public void recycle() { + avatarImageView.getImageReceiver().cancelLoadImage(); + } + + public void update(int mask) { + if (currentContact == null) { + return; + } + String newName = null; + + avatarDrawable.setInfo(currentContact.contact_id, currentContact.first_name, currentContact.last_name, false); + + if (currentName != null) { + nameTextView.setText(currentName, true); + } else { + nameTextView.setText(ContactsController.formatName(currentContact.first_name, currentContact.last_name)); + } + + statusTextView.setTag(Theme.key_groupcreate_offlineText); + statusTextView.setTextColor(Theme.getColor(Theme.key_groupcreate_offlineText)); + if (currentContact.imported > 0) { + statusTextView.setText(LocaleController.formatPluralString("TelegramContacts", currentContact.imported)); + } else { + statusTextView.setText(currentContact.phones.get(0)); + } + + avatarImageView.setImageDrawable(avatarDrawable); + } + + @Override + public boolean hasOverlappingRendering() { + return false; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ManageChatUserCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ManageChatUserCell.java index 164da6315..343c71ff7 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ManageChatUserCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ManageChatUserCell.java @@ -46,6 +46,7 @@ public class ManageChatUserCell extends FrameLayout { private String lastName; private int lastStatus; private TLRPC.FileLocation lastAvatar; + private boolean isAdmin; private int statusColor; private int statusOnlineColor; @@ -126,6 +127,10 @@ public class ManageChatUserCell extends FrameLayout { statusOnlineColor = onlineColor; } + public void setIsAdmin(boolean value) { + isAdmin = value; + } + public void update(int mask) { if (currentUser == null) { return; @@ -183,7 +188,7 @@ public class ManageChatUserCell extends FrameLayout { } else if (currentUser != null) { if (currentUser.bot) { statusTextView.setTextColor(statusColor); - if (currentUser.bot_chat_history) { + if (currentUser.bot_chat_history || isAdmin) { statusTextView.setText(LocaleController.getString("BotStatusRead", R.string.BotStatusRead)); } else { statusTextView.setText(LocaleController.getString("BotStatusCantRead", R.string.BotStatusCantRead)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/MaxFileSizeCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/MaxFileSizeCell.java new file mode 100644 index 000000000..bbc6b8261 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/MaxFileSizeCell.java @@ -0,0 +1,111 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Cells; + +import android.content.Context; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.MotionEvent; +import android.widget.FrameLayout; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.R; +import org.telegram.ui.ActionBar.SimpleTextView; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.SeekBarView; + +public class MaxFileSizeCell extends FrameLayout { + + private TextView textView; + private SimpleTextView sizeTextView; + private SeekBarView seekBarView; + + private long maxSize; + + public MaxFileSizeCell(Context context) { + super(context); + + textView = new TextView(context); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + textView.setLines(1); + textView.setMaxLines(1); + textView.setSingleLine(true); + textView.setText(LocaleController.getString("AutodownloadSizeLimit", R.string.AutodownloadSizeLimit)); + textView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); + textView.setEllipsize(TextUtils.TruncateAt.END); + addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 64 : 17, 13, LocaleController.isRTL ? 17 : 64, 0)); + + sizeTextView = new SimpleTextView(context); + sizeTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText6)); + sizeTextView.setTextSize(16); + sizeTextView.setGravity((LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.TOP); + addView(sizeTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 17 : 64, 13, LocaleController.isRTL ? 64 : 17, 0)); + + seekBarView = new SeekBarView(context) { + @Override + public boolean onTouchEvent(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + getParent().requestDisallowInterceptTouchEvent(true); + } + return super.onTouchEvent(event); + } + }; + seekBarView.setReportChanges(true); + seekBarView.setDelegate(new SeekBarView.SeekBarViewDelegate() { + @Override + public void onSeekBarDrag(float progress) { + int size; + if (maxSize > 1024 * 1024 * 10) { + int min = 1024 * 1024 * 100; + if (progress <= 0.8f) { + size = (int) (min * (progress / 0.8f)); + } else { + size = (int) (min + (maxSize - min) * (progress - 0.8f) / 0.2f); + } + } else { + size = (int) (maxSize * progress); + } + sizeTextView.setText(LocaleController.formatString("AutodownloadSizeLimitUpTo", R.string.AutodownloadSizeLimitUpTo, AndroidUtilities.formatFileSize(size))); + didChangedSizeValue(size); + } + }); + addView(seekBarView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 30, Gravity.TOP | Gravity.LEFT, 4, 40, 4, 0)); + } + + protected void didChangedSizeValue(int value) { + + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(80), MeasureSpec.EXACTLY)); + } + + public void setSize(long size, long max) { + maxSize = max; + float progress; + if (maxSize > 1024 * 1024 * 10) { + int min = 1024 * 1024 * 100; + if (size <= min) { + progress = size / (float) min * 0.8f; + } else { + progress = 0.8f + (size - min) / (float) (maxSize - min) * 0.2f; + } + } else { + progress = size / (float) maxSize; + } + seekBarView.setProgress(progress); + sizeTextView.setText(LocaleController.formatString("AutodownloadSizeLimitUpTo", R.string.AutodownloadSizeLimitUpTo, AndroidUtilities.formatFileSize(size))); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/MentionCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/MentionCell.java index e24451e4c..84e17b8c3 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/MentionCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/MentionCell.java @@ -17,6 +17,7 @@ import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.Emoji; +import org.telegram.messenger.EmojiSuggestion; import org.telegram.messenger.UserObject; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.Theme; @@ -94,6 +95,22 @@ public class MentionCell extends LinearLayout { nameTextView.setText(text); } + @Override + public void invalidate() { + super.invalidate(); + nameTextView.invalidate(); + } + + public void setEmojiSuggestion(EmojiSuggestion suggestion) { + imageView.setVisibility(INVISIBLE); + usernameTextView.setVisibility(INVISIBLE); + StringBuilder stringBuilder = new StringBuilder(suggestion.emoji.length() + suggestion.label.length() + 3); + stringBuilder.append(suggestion.emoji); + stringBuilder.append(" "); + stringBuilder.append(suggestion.label); + nameTextView.setText(Emoji.replaceEmoji(stringBuilder, nameTextView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(20), false)); + } + public void setBotCommand(String command, String help, TLRPC.User user) { if (user != null) { imageView.setVisibility(VISIBLE); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoAttachPhotoCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoAttachPhotoCell.java index 966099349..2bd8afd00 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoAttachPhotoCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoAttachPhotoCell.java @@ -131,8 +131,12 @@ public class PhotoAttachPhotoCell extends FrameLayout { requestLayout(); } - public void setChecked(boolean value, boolean animated) { - checkBox.setChecked(value, animated); + public void setChecked(int num, boolean value, boolean animated) { + checkBox.setChecked(num, value, animated); + } + + public void setNum(int num) { + checkBox.setNum(num); } public void setOnCheckClickLisnener(OnClickListener onCheckClickLisnener) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoPickerPhotoCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoPickerPhotoCell.java index a1108dbc1..318aa3d1f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoPickerPhotoCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoPickerPhotoCell.java @@ -36,10 +36,13 @@ public class PhotoPickerPhotoCell extends FrameLayout { private AnimatorSet animator; private AnimatorSet animatorSet; public int itemWidth; + private boolean zoomOnSelect; - public PhotoPickerPhotoCell(Context context) { + public PhotoPickerPhotoCell(Context context, boolean zoom) { super(context); + zoomOnSelect = zoom; + photoImage = new BackupImageView(context); addView(photoImage, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); @@ -61,11 +64,11 @@ public class PhotoPickerPhotoCell extends FrameLayout { videoInfoContainer.addView(videoTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.CENTER_VERTICAL, 18, -0.7f, 0, 0)); checkBox = new CheckBox(context, R.drawable.checkbig); - checkBox.setSize(30); + checkBox.setSize(zoom ? 30 : 26); checkBox.setCheckOffset(AndroidUtilities.dp(1)); checkBox.setDrawBackground(true); - checkBox.setColor(0xff3ccaef, 0xffffffff); - addView(checkBox, LayoutHelper.createFrame(30, 30, Gravity.RIGHT | Gravity.TOP, 0, 4, 4, 0)); + checkBox.setColor(0xff66bffa, 0xffffffff); + addView(checkBox, LayoutHelper.createFrame(zoom ? 30 : 26, zoom ? 30 : 26, Gravity.RIGHT | Gravity.TOP, 0, 4, 4, 0)); } @Override @@ -95,43 +98,49 @@ public class PhotoPickerPhotoCell extends FrameLayout { animatorSet.start(); } - public void setChecked(final boolean checked, final boolean animated) { - checkBox.setChecked(checked, animated); + public void setNum(int num) { + checkBox.setNum(num); + } + + public void setChecked(final int num, final boolean checked, final boolean animated) { + checkBox.setChecked(num, checked, animated); if (animator != null) { animator.cancel(); animator = null; } - if (animated) { - if (checked) { - setBackgroundColor(0xff0A0A0A); - } - animator = new AnimatorSet(); - animator.playTogether(ObjectAnimator.ofFloat(photoImage, "scaleX", checked ? 0.85f : 1.0f), - ObjectAnimator.ofFloat(photoImage, "scaleY", checked ? 0.85f : 1.0f)); - animator.setDuration(200); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (animator != null && animator.equals(animation)) { - animator = null; - if (!checked) { - setBackgroundColor(0); + if (zoomOnSelect) { + if (animated) { + if (checked) { + setBackgroundColor(0xff0a0a0a); + } + animator = new AnimatorSet(); + animator.playTogether(ObjectAnimator.ofFloat(photoImage, "scaleX", checked ? 0.85f : 1.0f), + ObjectAnimator.ofFloat(photoImage, "scaleY", checked ? 0.85f : 1.0f)); + animator.setDuration(200); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animator != null && animator.equals(animation)) { + animator = null; + if (!checked) { + setBackgroundColor(0); + } } } - } - @Override - public void onAnimationCancel(Animator animation) { - if (animator != null && animator.equals(animation)) { - animator = null; + @Override + public void onAnimationCancel(Animator animation) { + if (animator != null && animator.equals(animation)) { + animator = null; + } } - } - }); - animator.start(); - } else { - setBackgroundColor(checked ? 0xff0A0A0A : 0); - photoImage.setScaleX(checked ? 0.85f : 1.0f); - photoImage.setScaleY(checked ? 0.85f : 1.0f); + }); + animator.start(); + } else { + setBackgroundColor(checked ? 0xff0A0A0A : 0); + photoImage.setScaleX(checked ? 0.85f : 1.0f); + photoImage.setScaleY(checked ? 0.85f : 1.0f); + } } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ProfileSearchCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ProfileSearchCell.java index 4dfb2928a..80d648409 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ProfileSearchCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ProfileSearchCell.java @@ -38,16 +38,18 @@ public class ProfileSearchCell extends BaseCell { private AvatarDrawable avatarDrawable; private CharSequence subLabel; - private TLRPC.User user = null; - private TLRPC.Chat chat = null; - private TLRPC.EncryptedChat encryptedChat = null; + private TLRPC.User user; + private TLRPC.Chat chat; + private TLRPC.EncryptedChat encryptedChat; private long dialog_id; - private String lastName = null; - private int lastStatus = 0; - private TLRPC.FileLocation lastAvatar = null; + private String lastName; + private int lastStatus; + private TLRPC.FileLocation lastAvatar; - public boolean useSeparator = false; + private boolean savedMessages; + + public boolean useSeparator; private int nameLeft; private int nameTop; @@ -82,7 +84,7 @@ public class ProfileSearchCell extends BaseCell { avatarDrawable = new AvatarDrawable(); } - public void setData(TLObject object, TLRPC.EncryptedChat ec, CharSequence n, CharSequence s, boolean needCount) { + public void setData(TLObject object, TLRPC.EncryptedChat ec, CharSequence n, CharSequence s, boolean needCount, boolean saved) { currentName = n; if (object instanceof TLRPC.User) { user = (TLRPC.User) object; @@ -94,6 +96,7 @@ public class ProfileSearchCell extends BaseCell { encryptedChat = ec; subLabel = s; drawCount = needCount; + savedMessages = saved; update(0); } @@ -121,7 +124,6 @@ public class ProfileSearchCell extends BaseCell { @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { if (user == null && chat == null && encryptedChat == null) { - super.onLayout(changed, left, top, right, bottom); return; } if (changed) { @@ -293,11 +295,16 @@ public class ProfileSearchCell extends BaseCell { } } - CharSequence onlineStringFinal = TextUtils.ellipsize(onlineString, currentOnlinePaint, onlineWidth - AndroidUtilities.dp(12), TextUtils.TruncateAt.END); - onlineLayout = new StaticLayout(onlineStringFinal, currentOnlinePaint, onlineWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - nameTop = AndroidUtilities.dp(13); - if (subLabel != null && chat != null) { - nameLockTop -= AndroidUtilities.dp(12); + if (savedMessages) { + onlineLayout = null; + nameTop = AndroidUtilities.dp(25); + } else { + CharSequence onlineStringFinal = TextUtils.ellipsize(onlineString, currentOnlinePaint, onlineWidth - AndroidUtilities.dp(12), TextUtils.TruncateAt.END); + onlineLayout = new StaticLayout(onlineStringFinal, currentOnlinePaint, onlineWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + nameTop = AndroidUtilities.dp(13); + if (subLabel != null && chat != null) { + nameLockTop -= AndroidUtilities.dp(12); + } } } else { onlineLayout = null; @@ -364,10 +371,12 @@ public class ProfileSearchCell extends BaseCell { public void update(int mask) { TLRPC.FileLocation photo = null; if (user != null) { - if (user.photo != null) { + avatarDrawable.setInfo(user); + if (savedMessages) { + avatarDrawable.setSavedMessages(1); + } else if (user.photo != null) { photo = user.photo.photo_small; } - avatarDrawable.setInfo(user); } else if (chat != null) { if (chat.photo != null) { photo = chat.photo.photo_small; @@ -427,7 +436,6 @@ public class ProfileSearchCell extends BaseCell { lastName = chat.title; } - lastAvatar = photo; avatarImage.setImage(photo, "50_50", avatarDrawable, null, 0); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SendLocationCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SendLocationCell.java index 98cc2af1e..4b6361d9f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SendLocationCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SendLocationCell.java @@ -9,8 +9,10 @@ package org.telegram.ui.Cells; import android.content.Context; +import android.graphics.Canvas; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; +import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.view.Gravity; import android.widget.FrameLayout; @@ -18,7 +20,9 @@ import android.widget.ImageView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; +import org.telegram.messenger.LocationController; import org.telegram.messenger.R; +import org.telegram.tgnet.ConnectionsManager; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.CombinedDrawable; import org.telegram.ui.Components.LayoutHelper; @@ -29,23 +33,50 @@ public class SendLocationCell extends FrameLayout { private SimpleTextView accurateTextView; private SimpleTextView titleTextView; private ImageView imageView; + private long dialogId; + private RectF rect; - public SendLocationCell(Context context) { + private Runnable invalidateRunnable = new Runnable() { + @Override + public void run() { + checkText(); + invalidate((int) rect.left - 5, (int) rect.top - 5, (int) rect.right + 5, (int) rect.bottom + 5); + AndroidUtilities.runOnUIThread(invalidateRunnable, 1000); + } + }; + + public SendLocationCell(Context context, boolean live) { super(context); imageView = new ImageView(context); - imageView.setImageResource(R.drawable.pin); - Drawable circle = Theme.createSimpleSelectorCircleDrawable(AndroidUtilities.dp(40), Theme.getColor(Theme.key_location_sendLocationBackground), Theme.getColor(Theme.key_location_sendLocationBackground)); - Drawable drawable = getResources().getDrawable(R.drawable.pin); - drawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_location_sendLocationIcon), PorterDuff.Mode.MULTIPLY)); - CombinedDrawable combinedDrawable = new CombinedDrawable(circle, drawable); - combinedDrawable.setCustomSize(AndroidUtilities.dp(40), AndroidUtilities.dp(40)); - imageView.setBackgroundDrawable(combinedDrawable); + + Drawable circle = Theme.createSimpleSelectorCircleDrawable(AndroidUtilities.dp(40), Theme.getColor(live ? Theme.key_location_sendLiveLocationBackground : Theme.key_location_sendLocationBackground), Theme.getColor(live ? Theme.key_location_sendLiveLocationBackground : Theme.key_location_sendLocationBackground)); + if (live) { + rect = new RectF(); + Drawable drawable = getResources().getDrawable(R.drawable.livelocationpin); + drawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_location_sendLocationIcon), PorterDuff.Mode.MULTIPLY)); + CombinedDrawable combinedDrawable = new CombinedDrawable(circle, drawable); + combinedDrawable.setCustomSize(AndroidUtilities.dp(40), AndroidUtilities.dp(40)); + imageView.setBackgroundDrawable(combinedDrawable); + AndroidUtilities.runOnUIThread(invalidateRunnable, 1000); + setWillNotDraw(false); + } else { + Drawable drawable = getResources().getDrawable(R.drawable.pin); + drawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_location_sendLocationIcon), PorterDuff.Mode.MULTIPLY)); + CombinedDrawable combinedDrawable = new CombinedDrawable(circle, drawable); + combinedDrawable.setCustomSize(AndroidUtilities.dp(40), AndroidUtilities.dp(40)); + combinedDrawable.setIconSize(AndroidUtilities.dp(24), AndroidUtilities.dp(24)); + imageView.setBackgroundDrawable(combinedDrawable); + } addView(imageView, LayoutHelper.createFrame(40, 40, Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), LocaleController.isRTL ? 0 : 17, 13, LocaleController.isRTL ? 17 : 0, 0)); titleTextView = new SimpleTextView(context); titleTextView.setTextSize(16); - titleTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText7)); + if (live) { + titleTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText2)); + } else { + titleTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText7)); + } titleTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); titleTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); addView(titleTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 20, Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), LocaleController.isRTL ? 16 : 73, 12, LocaleController.isRTL ? 73 : 16, 0)); @@ -61,13 +92,81 @@ public class SendLocationCell extends FrameLayout { return imageView; } + public void setHasLocation(boolean value) { + LocationController.SharingLocationInfo info = LocationController.getInstance().getSharingLocationInfo(dialogId); + if (info == null) { + titleTextView.setAlpha(value ? 1.0f : 0.5f); + accurateTextView.setAlpha(value ? 1.0f : 0.5f); + imageView.setAlpha(value ? 1.0f : 0.5f); + } + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(66), MeasureSpec.EXACTLY)); } + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + AndroidUtilities.cancelRunOnUIThread(invalidateRunnable); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (rect != null) { + AndroidUtilities.runOnUIThread(invalidateRunnable, 1000); + } + } + public void setText(String title, String text) { titleTextView.setText(title); accurateTextView.setText(text); } + + public void setDialogId(long did) { + dialogId = did; + checkText(); + } + + private void checkText() { + LocationController.SharingLocationInfo info = LocationController.getInstance().getSharingLocationInfo(dialogId); + if (info != null) { + setText(LocaleController.getString("StopLiveLocation", R.string.StopLiveLocation), LocaleController.formatLocationUpdateDate(info.messageObject.messageOwner.edit_date != 0 ? info.messageObject.messageOwner.edit_date : info.messageObject.messageOwner.date)); + } else { + setText(LocaleController.getString("SendLiveLocation", R.string.SendLiveLocation), LocaleController.getString("SendLiveLocationInfo", R.string.SendLiveLocationInfo)); + } + } + + @Override + protected void onDraw(Canvas canvas) { + LocationController.SharingLocationInfo currentInfo = LocationController.getInstance().getSharingLocationInfo(dialogId); + if (currentInfo == null) { + return; + } + int currentTime = ConnectionsManager.getInstance().getCurrentTime(); + if (currentInfo.stopTime < currentTime) { + return; + } + + float progress = Math.abs(currentInfo.stopTime - currentTime) / (float) currentInfo.period; + if (LocaleController.isRTL) { + rect.set(AndroidUtilities.dp(13), AndroidUtilities.dp(18), AndroidUtilities.dp(43), AndroidUtilities.dp(48)); + } else { + rect.set(getMeasuredWidth() - AndroidUtilities.dp(43), AndroidUtilities.dp(18), getMeasuredWidth() - AndroidUtilities.dp(13), AndroidUtilities.dp(48)); + } + + int color = Theme.getColor(Theme.key_location_liveLocationProgress); + Theme.chat_radialProgress2Paint.setColor(color); + Theme.chat_livePaint.setColor(color); + + canvas.drawArc(rect, -90, -360 * progress, false, Theme.chat_radialProgress2Paint); + + String text = LocaleController.formatLocationLeftTime(Math.abs(currentInfo.stopTime - currentTime)); + + float size = Theme.chat_livePaint.measureText(text); + + canvas.drawText(text, rect.centerX() - size / 2, AndroidUtilities.dp(37), Theme.chat_livePaint); + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShareDialogCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShareDialogCell.java index 715a0b2d7..1b609ef12 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShareDialogCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShareDialogCell.java @@ -17,8 +17,10 @@ import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ContactsController; +import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesController; import org.telegram.messenger.R; +import org.telegram.messenger.UserObject; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.AvatarDrawable; @@ -66,16 +68,21 @@ public class ShareDialogCell extends FrameLayout { TLRPC.FileLocation photo = null; if (uid > 0) { TLRPC.User user = MessagesController.getInstance().getUser(uid); - if (name != null) { - nameTextView.setText(name); - } else if (user != null) { - nameTextView.setText(ContactsController.formatName(user.first_name, user.last_name)); - } else { - nameTextView.setText(""); - } avatarDrawable.setInfo(user); - if (user != null && user.photo != null) { - photo = user.photo.photo_small; + if (UserObject.isUserSelf(user)) { + nameTextView.setText(LocaleController.getString("SavedMessages", R.string.SavedMessages)); + avatarDrawable.setSavedMessages(1); + } else { + if (name != null) { + nameTextView.setText(name); + } else if (user != null) { + nameTextView.setText(ContactsController.formatName(user.first_name, user.last_name)); + } else { + nameTextView.setText(""); + } + if (user != null && user.photo != null) { + photo = user.photo.photo_small; + } } } else { TLRPC.Chat chat = MessagesController.getInstance().getChat(-uid); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedDocumentCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedDocumentCell.java index e8c52efb5..8777497b8 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedDocumentCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedDocumentCell.java @@ -192,6 +192,14 @@ public class SharedDocumentCell extends FrameLayout implements MediaController.F MediaController.getInstance().removeLoadingFileObserver(this); } + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (progressView.getVisibility() == VISIBLE) { + updateFileExistIcon(); + } + } + public void setChecked(boolean checked, boolean animated) { if (checkBox.getVisibility() != VISIBLE) { checkBox.setVisibility(VISIBLE); @@ -270,6 +278,7 @@ public class SharedDocumentCell extends FrameLayout implements MediaController.F loaded = false; if (fileName == null) { statusImageView.setVisibility(INVISIBLE); + progressView.setVisibility(INVISIBLE); dateTextView.setPadding(0, 0, 0, 0); loading = false; loaded = true; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedLinkCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedLinkCell.java index 800894ed2..257ab0b61 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedLinkCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedLinkCell.java @@ -12,7 +12,6 @@ import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; -import android.graphics.drawable.Drawable; import android.net.Uri; import android.text.Layout; import android.text.StaticLayout; @@ -25,7 +24,6 @@ import android.widget.FrameLayout; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ImageReceiver; import org.telegram.messenger.LocaleController; -import org.telegram.messenger.MediaController; import org.telegram.messenger.MessageObject; import org.telegram.messenger.FileLoader; import org.telegram.messenger.FileLog; @@ -38,7 +36,6 @@ import org.telegram.ui.Components.LetterDrawable; import org.telegram.ui.Components.LinkPath; import org.telegram.ui.ActionBar.Theme; -import java.io.File; import java.util.ArrayList; import java.util.Locale; @@ -271,21 +268,8 @@ public class SharedLinkCell extends FrameLayout { } linkImageView.setImageCoords(x, AndroidUtilities.dp(10), maxPhotoWidth, maxPhotoWidth); String fileName = FileLoader.getAttachFileName(currentPhotoObject); - boolean photoExist = true; - File cacheFile = FileLoader.getPathToAttach(currentPhotoObject, true); - if (!cacheFile.exists()) { - photoExist = false; - } String filter = String.format(Locale.US, "%d_%d", maxPhotoWidth, maxPhotoWidth); - if (photoExist || MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_PHOTO) || FileLoader.getInstance().isLoadingFile(fileName)) { - linkImageView.setImage(currentPhotoObject.location, filter, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, String.format(Locale.US, "%d_%d_b", maxPhotoWidth, maxPhotoWidth), 0, null, 0); - } else { - if (currentPhotoObjectThumb != null) { - linkImageView.setImage(null, null, currentPhotoObjectThumb.location, String.format(Locale.US, "%d_%d_b", maxPhotoWidth, maxPhotoWidth), 0, null, 0); - } else { - linkImageView.setImageBitmap((Drawable) null); - } - } + linkImageView.setImage(currentPhotoObject.location, filter, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, String.format(Locale.US, "%d_%d_b", maxPhotoWidth, maxPhotoWidth), 0, null, 0); drawLinkImageView = true; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharingLiveLocationCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharingLiveLocationCell.java new file mode 100644 index 000000000..f84228994 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharingLiveLocationCell.java @@ -0,0 +1,302 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Cells; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.location.Location; +import android.text.TextUtils; +import android.view.Gravity; +import android.widget.FrameLayout; + +import com.google.android.gms.maps.model.LatLng; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ContactsController; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.LocationController; +import org.telegram.messenger.MessageObject; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.R; +import org.telegram.messenger.UserObject; +import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.SimpleTextView; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.AvatarDrawable; +import org.telegram.ui.Components.BackupImageView; +import org.telegram.ui.Components.CombinedDrawable; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.LocationActivity; + +public class SharingLiveLocationCell extends FrameLayout { + + private BackupImageView avatarImageView; + private SimpleTextView nameTextView; + private SimpleTextView distanceTextView; + private AvatarDrawable avatarDrawable; + + private RectF rect = new RectF(); + + private LocationController.SharingLocationInfo currentInfo; + private LocationActivity.LiveLocation liveLocation; + private Location location = new Location("network"); + + private Runnable invalidateRunnable = new Runnable() { + @Override + public void run() { + invalidate((int) rect.left - 5, (int) rect.top - 5, (int) rect.right + 5, (int) rect.bottom + 5); + AndroidUtilities.runOnUIThread(invalidateRunnable, 1000); + } + }; + + public SharingLiveLocationCell(Context context, boolean distance) { + super(context); + + avatarImageView = new BackupImageView(context); + avatarImageView.setRoundRadius(AndroidUtilities.dp(20)); + + + avatarDrawable = new AvatarDrawable(); + + nameTextView = new SimpleTextView(context); + nameTextView.setTextSize(16); + nameTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + nameTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + nameTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); + + if (distance) { + addView(avatarImageView, LayoutHelper.createFrame(40, 40, Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), LocaleController.isRTL ? 0 : 17, 13, LocaleController.isRTL ? 17 : 0, 0)); + addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 20, Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), LocaleController.isRTL ? 54 : 73, 12, LocaleController.isRTL ? 73 : 54, 0)); + + distanceTextView = new SimpleTextView(context); + distanceTextView.setTextSize(14); + distanceTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2)); + distanceTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); + + addView(distanceTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 20, Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), LocaleController.isRTL ? 54 : 73, 37, LocaleController.isRTL ? 73 : 54, 0)); + } else { + addView(avatarImageView, LayoutHelper.createFrame(40, 40, Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), LocaleController.isRTL ? 0 : 17, 7, LocaleController.isRTL ? 17 : 0, 0)); + addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), LocaleController.isRTL ? 54 : 74, 17, LocaleController.isRTL ? 74 : 54, 0)); + } + + setWillNotDraw(false); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(distanceTextView != null ? 66 : 54), MeasureSpec.EXACTLY)); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + AndroidUtilities.cancelRunOnUIThread(invalidateRunnable); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + AndroidUtilities.runOnUIThread(invalidateRunnable); + } + + public void setDialog(MessageObject messageObject, Location userLocation) { + int fromId = messageObject.messageOwner.from_id; + if (messageObject.isForwarded()) { + if (messageObject.messageOwner.fwd_from.channel_id != 0) { + fromId = -messageObject.messageOwner.fwd_from.channel_id; + } else { + fromId = messageObject.messageOwner.fwd_from.from_id; + } + } + String address = null; + TLRPC.FileLocation photo = null; + String name; + if (!TextUtils.isEmpty(messageObject.messageOwner.media.address)) { + address = messageObject.messageOwner.media.address; + } + if (!TextUtils.isEmpty(messageObject.messageOwner.media.title)) { + name = messageObject.messageOwner.media.title; + + Drawable drawable = getResources().getDrawable(R.drawable.pin); + drawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_location_sendLocationIcon), PorterDuff.Mode.MULTIPLY)); + int color = Theme.getColor(Theme.key_location_placeLocationBackground); + Drawable circle = Theme.createSimpleSelectorCircleDrawable(AndroidUtilities.dp(40), color, color); + CombinedDrawable combinedDrawable = new CombinedDrawable(circle, drawable); + combinedDrawable.setCustomSize(AndroidUtilities.dp(40), AndroidUtilities.dp(40)); + combinedDrawable.setIconSize(AndroidUtilities.dp(24), AndroidUtilities.dp(24)); + avatarImageView.setImageDrawable(combinedDrawable); + } else { + name = ""; + avatarDrawable = null; + if (fromId > 0) { + TLRPC.User user = MessagesController.getInstance().getUser(fromId); + if (user != null) { + if (user.photo != null) { + photo = user.photo.photo_small; + } + avatarDrawable = new AvatarDrawable(user); + name = UserObject.getUserName(user); + } + } else { + TLRPC.Chat chat = MessagesController.getInstance().getChat(-fromId); + if (chat != null) { + if (chat.photo != null) { + photo = chat.photo.photo_small; + } + avatarDrawable = new AvatarDrawable(chat); + name = chat.title; + } + } + avatarImageView.setImage(photo, null, avatarDrawable); + } + nameTextView.setText(name); + + location.setLatitude(messageObject.messageOwner.media.geo.lat); + location.setLongitude(messageObject.messageOwner.media.geo._long); + if (userLocation != null) { + float distance = location.distanceTo(userLocation); + if (address != null) { + if (distance < 1000) { + distanceTextView.setText(String.format("%s - %d %s", address, (int) (distance), LocaleController.getString("MetersAway", R.string.MetersAway))); + } else { + distanceTextView.setText(String.format("%s - %.2f %s", address, distance / 1000.0f, LocaleController.getString("KMetersAway", R.string.KMetersAway))); + } + } else { + if (distance < 1000) { + distanceTextView.setText(String.format("%d %s", (int) (distance), LocaleController.getString("MetersAway", R.string.MetersAway))); + } else { + distanceTextView.setText(String.format("%.2f %s", distance / 1000.0f, LocaleController.getString("KMetersAway", R.string.KMetersAway))); + } + } + } else { + if (address != null) { + distanceTextView.setText(address); + } else { + distanceTextView.setText(LocaleController.getString("Loading", R.string.Loading)); + } + } + } + + public void setDialog(LocationActivity.LiveLocation info, Location userLocation) { + liveLocation = info; + int lower_id = info.id; + TLRPC.FileLocation photo = null; + if (lower_id > 0) { + TLRPC.User user = MessagesController.getInstance().getUser(lower_id); + avatarDrawable.setInfo(user); + if (user != null) { + nameTextView.setText(ContactsController.formatName(user.first_name, user.last_name)); + if (user.photo != null && user.photo.photo_small != null) { + photo = user.photo.photo_small; + } + } + } else { + TLRPC.Chat chat = MessagesController.getInstance().getChat(-lower_id); + if (chat != null) { + avatarDrawable.setInfo(chat); + nameTextView.setText(chat.title); + if (chat.photo != null && chat.photo.photo_small != null) { + photo = chat.photo.photo_small; + } + } + } + + LatLng position = info.marker.getPosition(); + location.setLatitude(position.latitude); + location.setLongitude(position.longitude); + + String time = LocaleController.formatLocationUpdateDate(info.object.edit_date != 0 ? info.object.edit_date : info.object.date); + if (userLocation != null) { + float distance = location.distanceTo(userLocation); + if (distance < 1000) { + distanceTextView.setText(String.format("%s - %d %s", time, (int) (distance), LocaleController.getString("MetersAway", R.string.MetersAway))); + } else { + distanceTextView.setText(String.format("%s - %.2f %s", time, distance / 1000.0f, LocaleController.getString("KMetersAway", R.string.KMetersAway))); + } + } else { + distanceTextView.setText(time); + } + + avatarImageView.setImage(photo, null, avatarDrawable); + } + + public void setDialog(LocationController.SharingLocationInfo info) { + currentInfo = info; + int lower_id = (int) info.did; + TLRPC.FileLocation photo = null; + if (lower_id > 0) { + TLRPC.User user = MessagesController.getInstance().getUser(lower_id); + if (user != null) { + avatarDrawable.setInfo(user); + nameTextView.setText(ContactsController.formatName(user.first_name, user.last_name)); + if (user.photo != null && user.photo.photo_small != null) { + photo = user.photo.photo_small; + } + } + } else { + TLRPC.Chat chat = MessagesController.getInstance().getChat(-lower_id); + if (chat != null) { + avatarDrawable.setInfo(chat); + nameTextView.setText(chat.title); + if (chat.photo != null && chat.photo.photo_small != null) { + photo = chat.photo.photo_small; + } + } + } + avatarImageView.setImage(photo, null, avatarDrawable); + } + + @Override + protected void onDraw(Canvas canvas) { + if (currentInfo == null && liveLocation == null) { + return; + } + int stopTime; + int period; + if (currentInfo != null) { + stopTime = currentInfo.stopTime; + period = currentInfo.period; + } else { + stopTime = liveLocation.object.date + liveLocation.object.media.period; + period = liveLocation.object.media.period; + } + int currentTime = ConnectionsManager.getInstance().getCurrentTime(); + if (stopTime < currentTime) { + return; + } + float progress = Math.abs(stopTime - currentTime) / (float) period; + if (LocaleController.isRTL) { + rect.set(AndroidUtilities.dp(13), AndroidUtilities.dp(distanceTextView != null ? 18 : 12), AndroidUtilities.dp(43), AndroidUtilities.dp(distanceTextView != null ? 48 : 42)); + } else { + rect.set(getMeasuredWidth() - AndroidUtilities.dp(43), AndroidUtilities.dp(distanceTextView != null ? 18 : 12), getMeasuredWidth() - AndroidUtilities.dp(13), AndroidUtilities.dp(distanceTextView != null ? 48 : 42)); + } + + int color; + if (distanceTextView == null) { + color = Theme.getColor(Theme.key_dialog_liveLocationProgress); + } else { + color = Theme.getColor(Theme.key_location_liveLocationProgress); + } + Theme.chat_radialProgress2Paint.setColor(color); + Theme.chat_livePaint.setColor(color); + + canvas.drawArc(rect, -90, -360 * progress, false, Theme.chat_radialProgress2Paint); + + String text = LocaleController.formatLocationLeftTime(stopTime - currentTime); + + float size = Theme.chat_livePaint.measureText(text); + + canvas.drawText(text, rect.centerX() - size / 2, AndroidUtilities.dp(distanceTextView != null ? 37 : 31), Theme.chat_livePaint); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerCell.java index 8ee143c38..5ea33b4e6 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerCell.java @@ -51,7 +51,7 @@ public class StickerCell extends FrameLayout { @Override public void setPressed(boolean pressed) { if (imageView.getImageReceiver().getPressed() != pressed) { - imageView.getImageReceiver().setPressed(pressed); + imageView.getImageReceiver().setPressed(pressed ? 1 : 0); imageView.invalidate(); } super.setPressed(pressed); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerSetCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerSetCell.java index 553a00936..dde4d6e9a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerSetCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerSetCell.java @@ -29,6 +29,7 @@ import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RadialProgressView; import java.util.ArrayList; @@ -37,12 +38,13 @@ public class StickerSetCell extends FrameLayout { private TextView textView; private TextView valueTextView; private BackupImageView imageView; + private RadialProgressView progressView; private boolean needDivider; private ImageView optionsButton; private TLRPC.TL_messages_stickerSet stickersSet; private Rect rect = new Rect(); - public StickerSetCell(Context context) { + public StickerSetCell(Context context, int option) { super(context); textView = new TextView(context); @@ -68,13 +70,26 @@ public class StickerSetCell extends FrameLayout { imageView.setAspectFit(true); addView(imageView, LayoutHelper.createFrame(48, 48, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 0 : 12, 8, LocaleController.isRTL ? 12 : 0, 0)); - optionsButton = new ImageView(context); - optionsButton.setFocusable(false); - optionsButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.getColor(Theme.key_stickers_menuSelector))); - optionsButton.setImageResource(R.drawable.msg_actions); - optionsButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_stickers_menu), PorterDuff.Mode.MULTIPLY)); - optionsButton.setScaleType(ImageView.ScaleType.CENTER); - addView(optionsButton, LayoutHelper.createFrame(40, 40, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.TOP)); + if (option == 2) { + progressView = new RadialProgressView(getContext()); + progressView.setProgressColor(Theme.getColor(Theme.key_dialogProgressCircle)); + progressView.setSize(AndroidUtilities.dp(30)); + addView(progressView, LayoutHelper.createFrame(48, 48, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 0 : 12, 8, LocaleController.isRTL ? 12 : 0, 0)); + } else if (option != 0) { + optionsButton = new ImageView(context); + optionsButton.setFocusable(false); + optionsButton.setScaleType(ImageView.ScaleType.CENTER); + optionsButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.getColor(Theme.key_stickers_menuSelector))); + if (option == 1) { + optionsButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_stickers_menu), PorterDuff.Mode.MULTIPLY)); + optionsButton.setImageResource(R.drawable.msg_actions); + addView(optionsButton, LayoutHelper.createFrame(40, 40, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.TOP)); + } else if (option == 3) { + optionsButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_featuredStickers_addedIcon), PorterDuff.Mode.MULTIPLY)); + optionsButton.setImageResource(R.drawable.sticker_added); + addView(optionsButton, LayoutHelper.createFrame(40, 40, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.TOP, (LocaleController.isRTL ? 10 : 0), 12, (LocaleController.isRTL ? 0 : 10), 0)); + } + } } @Override @@ -82,10 +97,40 @@ public class StickerSetCell extends FrameLayout { super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(64) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY)); } + public void setText(String title, String subtitle, int icon, boolean divider) { + needDivider = divider; + stickersSet = null; + textView.setText(title); + valueTextView.setText(subtitle); + if (TextUtils.isEmpty(subtitle)) { + textView.setTranslationY(AndroidUtilities.dp(10)); + } else { + textView.setTranslationY(0); + } + if (icon != 0) { + imageView.setImageResource(icon, Theme.getColor(Theme.key_windowBackgroundWhiteGrayIcon)); + imageView.setVisibility(VISIBLE); + if (progressView != null) { + progressView.setVisibility(INVISIBLE); + } + } else { + imageView.setVisibility(INVISIBLE); + if (progressView != null) { + progressView.setVisibility(VISIBLE); + } + } + } + public void setStickersSet(TLRPC.TL_messages_stickerSet set, boolean divider) { needDivider = divider; stickersSet = set; + imageView.setVisibility(VISIBLE); + if (progressView != null) { + progressView.setVisibility(INVISIBLE); + } + + textView.setTranslationY(0); textView.setText(stickersSet.set.title); if (stickersSet.set.archived) { textView.setAlpha(0.5f); @@ -108,7 +153,17 @@ public class StickerSetCell extends FrameLayout { } } + public void setChecked(boolean checked) { + if (optionsButton == null) { + return; + } + optionsButton.setVisibility(checked ? VISIBLE : INVISIBLE); + } + public void setOnOptionsClick(OnClickListener listener) { + if (optionsButton == null) { + return; + } optionsButton.setOnClickListener(listener); } @@ -118,7 +173,7 @@ public class StickerSetCell extends FrameLayout { @Override public boolean onTouchEvent(MotionEvent event) { - if (Build.VERSION.SDK_INT >= 21 && getBackground() != null) { + if (Build.VERSION.SDK_INT >= 21 && getBackground() != null && optionsButton != null) { optionsButton.getHitRect(rect); if (rect.contains((int) event.getX(), (int) event.getY())) { return true; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerSetGroupInfoCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerSetGroupInfoCell.java new file mode 100644 index 000000000..f49bc349e --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerSetGroupInfoCell.java @@ -0,0 +1,72 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Cells; + +import android.content.Context; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.R; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.LayoutHelper; + +public class StickerSetGroupInfoCell extends LinearLayout { + + private TextView addButton; + private boolean isLast; + + public StickerSetGroupInfoCell(Context context) { + super(context); + setOrientation(VERTICAL); + + TextView infoTextView = new TextView(context); + infoTextView.setTextColor(Theme.getColor(Theme.key_chat_emojiPanelTrendingDescription)); + infoTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + infoTextView.setText(LocaleController.getString("GroupStickersInfo", R.string.GroupStickersInfo)); + addView(infoTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.LEFT, 17, 4, 17, 0)); + + addButton = new TextView(context); + addButton.setPadding(AndroidUtilities.dp(17), 0, AndroidUtilities.dp(17), 0); + addButton.setGravity(Gravity.CENTER); + addButton.setTextColor(Theme.getColor(Theme.key_featuredStickers_buttonText)); + addButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + addButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + addButton.setBackgroundDrawable(Theme.createSimpleSelectorRoundRectDrawable(AndroidUtilities.dp(4), Theme.getColor(Theme.key_featuredStickers_addButton), Theme.getColor(Theme.key_featuredStickers_addButtonPressed))); + addButton.setText(LocaleController.getString("ChooseStickerSet", R.string.ChooseStickerSet).toUpperCase()); + addView(addButton, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, 28, Gravity.TOP | Gravity.LEFT, 17, 10, 14, 8)); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), heightMeasureSpec); + if (isLast) { + View parent = (View) getParent(); + if (parent != null) { + int height = parent.getMeasuredHeight() - parent.getPaddingBottom() - parent.getPaddingTop() - AndroidUtilities.dp(24); + if (getMeasuredHeight() < height) { + setMeasuredDimension(getMeasuredWidth(), height); + } + } + } + } + + public void setAddOnClickListener(OnClickListener onClickListener) { + addButton.setOnClickListener(onClickListener); + } + + public void setIsLast(boolean last) { + isLast = last; + requestLayout(); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerSetNameCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerSetNameCell.java new file mode 100644 index 000000000..ed5fc4765 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerSetNameCell.java @@ -0,0 +1,65 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Cells; + +import android.content.Context; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.Gravity; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.LayoutHelper; + +public class StickerSetNameCell extends FrameLayout { + + private TextView textView; + private ImageView buttonView; + + public StickerSetNameCell(Context context) { + super(context); + + textView = new TextView(context); + textView.setTextColor(Theme.getColor(Theme.key_chat_emojiPanelStickerSetName)); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + textView.setEllipsize(TextUtils.TruncateAt.END); + textView.setSingleLine(true); + addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.LEFT, 17, 4, 57, 0)); + + buttonView = new ImageView(context); + buttonView.setScaleType(ImageView.ScaleType.CENTER); + buttonView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_emojiPanelStickerSetNameIcon), PorterDuff.Mode.MULTIPLY)); + addView(buttonView, LayoutHelper.createFrame(24, 24, Gravity.TOP | Gravity.RIGHT, 0, 0, 16, 0)); + } + + public void setText(String text, int resId) { + textView.setText(text); + if (resId != 0) { + buttonView.setImageResource(resId); + buttonView.setVisibility(VISIBLE); + } else { + buttonView.setVisibility(INVISIBLE); + } + } + + public void setOnIconClickListener(OnClickListener onIconClickListener) { + buttonView.setOnClickListener(onIconClickListener); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(24), MeasureSpec.EXACTLY)); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCheckBoxCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCheckBoxCell.java index b51671c68..b4aebb336 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCheckBoxCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCheckBoxCell.java @@ -71,6 +71,10 @@ public class TextCheckBoxCell extends FrameLayout { checkBox.setChecked(checked, true); } + public boolean isChecked() { + return checkBox.isChecked(); + } + @Override protected void onDraw(Canvas canvas) { if (needDivider) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextColorCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextColorCell.java index 8ff8f504c..59c71501b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextColorCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextColorCell.java @@ -35,7 +35,7 @@ public class TextColorCell extends FrameLayout { private static Paint colorPaint; public final static int colors[] = new int[] {0xfff04444, 0xffff8e01, 0xffffce1f, 0xff79d660, 0xff40edf6, 0xff46beff, 0xffd274f9, 0xffff4f96, 0xffbbbbbb}; - public final static int colorsToSave[] = new int[] {0xffff0000, 0xffff8e01, 0xffffce1f, 0xff00ff00, 0xff40edf6, 0xff0000ff, 0xffd274f9, 0xffff4f96, 0xffffffff}; + public final static int colorsToSave[] = new int[] {0xffff0000, 0xffff8e01, 0xffffff00, 0xff00ff00, 0xff00ffff, 0xff0000ff, 0xffd274f9, 0xffff00ff, 0xffffffff}; public TextColorCell(Context context) { super(context); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextColorThemeCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextColorThemeCell.java index 829ae2386..66d5cb6b5 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextColorThemeCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextColorThemeCell.java @@ -75,7 +75,7 @@ public class TextColorThemeCell extends FrameLayout { if (currentColor != 0) { colorPaint.setColor(currentColor); colorPaint.setAlpha((int) (255 * alpha)); - canvas.drawCircle(!LocaleController.isRTL ? AndroidUtilities.dp(28) : getMeasuredWidth() - AndroidUtilities.dp(28 + 20), getMeasuredHeight() / 2, AndroidUtilities.dp(10), colorPaint); + canvas.drawCircle(!LocaleController.isRTL ? AndroidUtilities.dp(28) : getMeasuredWidth() - AndroidUtilities.dp(28), getMeasuredHeight() / 2, AndroidUtilities.dp(10), colorPaint); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextDetailSettingsCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextDetailSettingsCell.java index 7a8481e88..0ce5bc1fc 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextDetailSettingsCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextDetailSettingsCell.java @@ -17,6 +17,7 @@ import android.widget.FrameLayout; import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.Emoji; import org.telegram.messenger.LocaleController; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.LayoutHelper; @@ -76,13 +77,26 @@ public class TextDetailSettingsCell extends FrameLayout { } } - public void setTextAndValue(String text, String value, boolean divider) { + public void setTextAndValue(String text, CharSequence value, boolean divider) { textView.setText(text); valueTextView.setText(value); needDivider = divider; setWillNotDraw(!divider); } + public void setTextWithEmojiAndValue(String text, CharSequence value, boolean divider) { + textView.setText(Emoji.replaceEmoji(text, textView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(14), false)); + valueTextView.setText(value); + needDivider = divider; + setWillNotDraw(!divider); + } + + @Override + public void invalidate() { + super.invalidate(); + textView.invalidate(); + } + @Override protected void onDraw(Canvas canvas) { if (needDivider) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChangeBioActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChangeBioActivity.java index d74d69a64..05f1ed776 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChangeBioActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChangeBioActivity.java @@ -25,7 +25,6 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.inputmethod.EditorInfo; -import android.widget.EditText; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.TextView; @@ -49,11 +48,12 @@ import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Components.AlertsCreator; +import org.telegram.ui.Components.EditTextBoldCursor; import org.telegram.ui.Components.LayoutHelper; public class ChangeBioActivity extends BaseFragment { - private EditText firstNameField; + private EditTextBoldCursor firstNameField; private View doneButton; private TextView checkTextView; private TextView helpTextView; @@ -92,7 +92,7 @@ public class ChangeBioActivity extends BaseFragment { FrameLayout fieldContainer = new FrameLayout(context); linearLayout.addView(fieldContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 24, 24, 20, 0)); - firstNameField = new EditText(context); + firstNameField = new EditTextBoldCursor(context); firstNameField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); firstNameField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); firstNameField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); @@ -125,7 +125,9 @@ public class ChangeBioActivity extends BaseFragment { firstNameField.setFilters(inputFilters); firstNameField.setMinHeight(AndroidUtilities.dp(36)); firstNameField.setHint(LocaleController.getString("UserBio", R.string.UserBio)); - AndroidUtilities.clearCursorDrawable(firstNameField); + firstNameField.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + firstNameField.setCursorSize(AndroidUtilities.dp(20)); + firstNameField.setCursorWidth(1.5f); firstNameField.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChangeChatNameActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChangeChatNameActivity.java index 469a61eb2..9e7a80973 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChangeChatNameActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChangeChatNameActivity.java @@ -25,7 +25,6 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; -import android.widget.EditText; import android.widget.FrameLayout; import android.widget.LinearLayout; @@ -49,11 +48,12 @@ import org.telegram.ui.Cells.TextSettingsCell; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.AvatarUpdater; import org.telegram.ui.Components.BackupImageView; +import org.telegram.ui.Components.EditTextBoldCursor; import org.telegram.ui.Components.LayoutHelper; public class ChangeChatNameActivity extends BaseFragment implements AvatarUpdater.AvatarUpdaterDelegate { - private EditText nameTextView; + private EditTextBoldCursor nameTextView; private BackupImageView avatarImage; private AvatarDrawable avatarDrawable; private AvatarUpdater avatarUpdater; @@ -209,7 +209,7 @@ public class ChangeChatNameActivity extends BaseFragment implements AvatarUpdate } }); - nameTextView = new EditText(context); + nameTextView = new EditTextBoldCursor(context); if (currentChat.megagroup) { nameTextView.setHint(LocaleController.getString("GroupName", R.string.GroupName)); } else { @@ -229,7 +229,9 @@ public class ChangeChatNameActivity extends BaseFragment implements AvatarUpdate InputFilter[] inputFilters = new InputFilter[1]; inputFilters[0] = new InputFilter.LengthFilter(100); nameTextView.setFilters(inputFilters); - AndroidUtilities.clearCursorDrawable(nameTextView); + nameTextView.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + nameTextView.setCursorSize(AndroidUtilities.dp(20)); + nameTextView.setCursorWidth(1.5f); nameTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); frameLayout.addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, LocaleController.isRTL ? 16 : 96, 0, LocaleController.isRTL ? 96 : 16, 0)); nameTextView.addTextChangedListener(new TextWatcher() { @@ -268,7 +270,7 @@ public class ChangeChatNameActivity extends BaseFragment implements AvatarUpdate @Override public void onClick(View v) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setMessage(LocaleController.getString("MegaDeleteAlert", R.string.MegaDeleteAlert)); + builder.setMessage(LocaleController.getString("AreYouSureDeleteAndExit", R.string.AreYouSureDeleteAndExit)); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { @Override @@ -279,7 +281,7 @@ public class ChangeChatNameActivity extends BaseFragment implements AvatarUpdate } else { NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); } - MessagesController.getInstance().deleteUserFromChat(chatId, MessagesController.getInstance().getUser(UserConfig.getClientUserId()), null); + MessagesController.getInstance().deleteUserFromChat(chatId, MessagesController.getInstance().getUser(UserConfig.getClientUserId()), null, true); finishFragment(); } }); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChangeNameActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChangeNameActivity.java index 5ebf2fa01..2e0fc9a87 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChangeNameActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChangeNameActivity.java @@ -19,7 +19,6 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; -import android.widget.EditText; import android.widget.LinearLayout; import android.widget.TextView; @@ -39,12 +38,13 @@ import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Components.EditTextBoldCursor; import org.telegram.ui.Components.LayoutHelper; public class ChangeNameActivity extends BaseFragment { - private EditText firstNameField; - private EditText lastNameField; + private EditTextBoldCursor firstNameField; + private EditTextBoldCursor lastNameField; private View headerLabelView; private View doneButton; @@ -88,7 +88,7 @@ public class ChangeNameActivity extends BaseFragment { } }); - firstNameField = new EditText(context); + firstNameField = new EditTextBoldCursor(context); firstNameField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); firstNameField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); firstNameField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); @@ -100,7 +100,9 @@ public class ChangeNameActivity extends BaseFragment { firstNameField.setInputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT); firstNameField.setImeOptions(EditorInfo.IME_ACTION_NEXT); firstNameField.setHint(LocaleController.getString("FirstName", R.string.FirstName)); - AndroidUtilities.clearCursorDrawable(firstNameField); + firstNameField.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + firstNameField.setCursorSize(AndroidUtilities.dp(20)); + firstNameField.setCursorWidth(1.5f); linearLayout.addView(firstNameField, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36, 24, 24, 24, 0)); firstNameField.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override @@ -114,7 +116,7 @@ public class ChangeNameActivity extends BaseFragment { } }); - lastNameField = new EditText(context); + lastNameField = new EditTextBoldCursor(context); lastNameField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); lastNameField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); lastNameField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); @@ -126,7 +128,9 @@ public class ChangeNameActivity extends BaseFragment { lastNameField.setInputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT); lastNameField.setImeOptions(EditorInfo.IME_ACTION_DONE); lastNameField.setHint(LocaleController.getString("LastName", R.string.LastName)); - AndroidUtilities.clearCursorDrawable(lastNameField); + lastNameField.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + lastNameField.setCursorSize(AndroidUtilities.dp(20)); + lastNameField.setCursorWidth(1.5f); linearLayout.addView(lastNameField, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36, 24, 16, 24, 0)); lastNameField.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChangePhoneActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChangePhoneActivity.java index bba00fce5..0e9593686 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChangePhoneActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChangePhoneActivity.java @@ -38,7 +38,6 @@ import android.view.View; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.inputmethod.EditorInfo; import android.widget.AdapterView; -import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; @@ -67,6 +66,7 @@ import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Components.AlertsCreator; +import org.telegram.ui.Components.EditTextBoldCursor; import org.telegram.ui.Components.HintEditText; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.SlideView; @@ -323,7 +323,7 @@ public class ChangePhoneActivity extends BaseFragment { public class PhoneView extends SlideView implements AdapterView.OnItemSelectedListener { - private EditText codeField; + private EditTextBoldCursor codeField; private HintEditText phoneField; private TextView countryButton; private View view; @@ -394,10 +394,12 @@ public class ChangePhoneActivity extends BaseFragment { textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); linearLayout.addView(textView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); - codeField = new EditText(context); + codeField = new EditTextBoldCursor(context); codeField.setInputType(InputType.TYPE_CLASS_PHONE); codeField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); - AndroidUtilities.clearCursorDrawable(codeField); + codeField.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + codeField.setCursorSize(AndroidUtilities.dp(20)); + codeField.setCursorWidth(1.5f); codeField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); codeField.setPadding(AndroidUtilities.dp(10), 0, 0, 0); codeField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); @@ -502,7 +504,9 @@ public class ChangePhoneActivity extends BaseFragment { phoneField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); phoneField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); phoneField.setPadding(0, 0, 0, 0); - AndroidUtilities.clearCursorDrawable(phoneField); + phoneField.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + phoneField.setCursorSize(AndroidUtilities.dp(20)); + phoneField.setCursorWidth(1.5f); phoneField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); phoneField.setMaxLines(1); phoneField.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL); @@ -690,6 +694,11 @@ public class ChangePhoneActivity extends BaseFragment { ignoreOnTextChange = false; } + @Override + public void onCancelPressed() { + nextPressed = false; + } + @Override public void onNothingSelected(AdapterView adapterView) { @@ -713,6 +722,9 @@ public class ChangePhoneActivity extends BaseFragment { } if (!allowSms) { permissionsItems.add(Manifest.permission.RECEIVE_SMS); + if (Build.VERSION.SDK_INT >= 23) { + permissionsItems.add(Manifest.permission.READ_SMS); + } } if (!permissionsItems.isEmpty()) { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); @@ -826,7 +838,7 @@ public class ChangePhoneActivity extends BaseFragment { private String phoneHash; private String requestPhone; private String emailPhone; - private EditText codeField; + private EditTextBoldCursor codeField; private TextView confirmTextView; private TextView timeText; private TextView problemText; @@ -881,10 +893,12 @@ public class ChangePhoneActivity extends BaseFragment { addView(confirmTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT)); } - codeField = new EditText(context); + codeField = new EditTextBoldCursor(context); codeField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); codeField.setHint(LocaleController.getString("Code", R.string.Code)); - AndroidUtilities.clearCursorDrawable(codeField); + codeField.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + codeField.setCursorSize(AndroidUtilities.dp(20)); + codeField.setCursorWidth(1.5f); codeField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); codeField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); codeField.setImeOptions(EditorInfo.IME_ACTION_NEXT | EditorInfo.IME_FLAG_NO_EXTRACT_UI); @@ -1046,6 +1060,11 @@ public class ChangePhoneActivity extends BaseFragment { return LocaleController.getString("YourCode", R.string.YourCode); } + @Override + public void onCancelPressed() { + nextPressed = false; + } + @Override public void setParams(Bundle params, boolean restore) { if (params == null) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChangeUsernameActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChangeUsernameActivity.java index 22d767d29..8fce70afa 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChangeUsernameActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChangeUsernameActivity.java @@ -29,7 +29,6 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.inputmethod.EditorInfo; -import android.widget.EditText; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; @@ -54,13 +53,14 @@ import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Components.AlertsCreator; +import org.telegram.ui.Components.EditTextBoldCursor; import org.telegram.ui.Components.LayoutHelper; import java.util.ArrayList; public class ChangeUsernameActivity extends BaseFragment { - private EditText firstNameField; + private EditTextBoldCursor firstNameField; private View doneButton; private TextView checkTextView; private TextView helpTextView; @@ -151,7 +151,7 @@ public class ChangeUsernameActivity extends BaseFragment { } }); - firstNameField = new EditText(context); + firstNameField = new EditTextBoldCursor(context); firstNameField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); firstNameField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); firstNameField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); @@ -164,7 +164,9 @@ public class ChangeUsernameActivity extends BaseFragment { firstNameField.setInputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT); firstNameField.setImeOptions(EditorInfo.IME_ACTION_DONE); firstNameField.setHint(LocaleController.getString("UsernamePlaceholder", R.string.UsernamePlaceholder)); - AndroidUtilities.clearCursorDrawable(firstNameField); + firstNameField.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + firstNameField.setCursorSize(AndroidUtilities.dp(20)); + firstNameField.setCursorWidth(1.5f); firstNameField.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { @@ -196,7 +198,9 @@ public class ChangeUsernameActivity extends BaseFragment { String text = LocaleController.formatString("UsernameHelpLink", R.string.UsernameHelpLink, url); int index = text.indexOf(url); SpannableStringBuilder textSpan = new SpannableStringBuilder(text); - textSpan.setSpan(new LinkSpan(url), index, index + url.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + if (index >= 0) { + textSpan.setSpan(new LinkSpan(url), index, index + url.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } helpTextView.setText(TextUtils.concat(infoText, "\n\n", textSpan)); } else { helpTextView.setText(infoText); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChannelAdminLogActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChannelAdminLogActivity.java index c81e194cb..cd11772fa 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChannelAdminLogActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChannelAdminLogActivity.java @@ -19,7 +19,6 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; -import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Outline; import android.graphics.Paint; @@ -67,7 +66,6 @@ import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; import org.telegram.messenger.UserConfig; import org.telegram.messenger.Utilities; -import org.telegram.messenger.VideoEditedInfo; import org.telegram.messenger.browser.Browser; import org.telegram.messenger.exoplayer2.ui.AspectRatioFrameLayout; import org.telegram.messenger.query.StickersQuery; @@ -113,7 +111,7 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; -public class ChannelAdminLogActivity extends BaseFragment implements PhotoViewer.PhotoViewerProvider, NotificationCenter.NotificationCenterDelegate { +public class ChannelAdminLogActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { protected TLRPC.Chat currentChat; @@ -183,6 +181,61 @@ public class ChannelAdminLogActivity extends BaseFragment implements PhotoViewer private MessageObject scrollToMessage; + private PhotoViewer.PhotoViewerProvider provider = new PhotoViewer.EmptyPhotoViewerProvider() { + + @Override + public PhotoViewer.PlaceProviderObject getPlaceForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { + int count = chatListView.getChildCount(); + + for (int a = 0; a < count; a++) { + ImageReceiver imageReceiver = null; + View view = chatListView.getChildAt(a); + if (view instanceof ChatMessageCell) { + if (messageObject != null) { + ChatMessageCell cell = (ChatMessageCell) view; + MessageObject message = cell.getMessageObject(); + if (message != null && message.getId() == messageObject.getId()) { + imageReceiver = cell.getPhotoImage(); + } + } + } else if (view instanceof ChatActionCell) { + ChatActionCell cell = (ChatActionCell) view; + MessageObject message = cell.getMessageObject(); + if (message != null) { + if (messageObject != null) { + if (message.getId() == messageObject.getId()) { + imageReceiver = cell.getPhotoImage(); + } + } else if (fileLocation != null && message.photoThumbs != null) { + for (int b = 0; b < message.photoThumbs.size(); b++) { + TLRPC.PhotoSize photoSize = message.photoThumbs.get(b); + if (photoSize.location.volume_id == fileLocation.volume_id && photoSize.location.local_id == fileLocation.local_id) { + imageReceiver = cell.getPhotoImage(); + break; + } + } + } + } + } + + if (imageReceiver != null) { + int coords[] = new int[2]; + view.getLocationInWindow(coords); + PhotoViewer.PlaceProviderObject object = new PhotoViewer.PlaceProviderObject(); + object.viewX = coords[0]; + object.viewY = coords[1] - (Build.VERSION.SDK_INT >= 21 ? 0 : AndroidUtilities.statusBarHeight); + object.parentView = chatListView; + object.imageReceiver = imageReceiver; + object.thumb = imageReceiver.getBitmap(); + object.radius = imageReceiver.getRoundRadius(); + object.isEvent = true; + return object; + } + } + return null; + } + }; + public ChannelAdminLogActivity(TLRPC.Chat chat) { currentChat = chat; } @@ -288,7 +341,11 @@ public class ChannelAdminLogActivity extends BaseFragment implements PhotoViewer } minEventId = Math.min(minEventId, event.id); added = true; - messagesDict.put(event.id, new MessageObject(event, messages, messagesByDays, currentChat, mid)); + MessageObject messageObject = new MessageObject(event, messages, messagesByDays, currentChat, mid); + if (messageObject.contentType < 0) { + continue; + } + messagesDict.put(event.id, messageObject); } int newRowsCount = messages.size() - oldRowsCount; loading = false; @@ -435,6 +492,7 @@ public class ChannelAdminLogActivity extends BaseFragment implements PhotoViewer Theme.createChatResources(context, false); actionBar.setAddToContainer(false); + actionBar.setOccupyStatusBar(Build.VERSION.SDK_INT >= 21 && !AndroidUtilities.isTablet()); actionBar.setBackButtonDrawable(new BackDrawable(false)); actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { @Override @@ -999,7 +1057,18 @@ public class ChannelAdminLogActivity extends BaseFragment implements PhotoViewer items.add(LocaleController.getString("Copy", R.string.Copy)); options.add(3); } - if (type == 3) { + if (type == 1) { + if (selectedObject.currentEvent != null && selectedObject.currentEvent.action instanceof TLRPC.TL_channelAdminLogEventActionChangeStickerSet) { + TLRPC.InputStickerSet stickerSet = selectedObject.currentEvent.action.new_stickerset; + if (stickerSet == null || stickerSet instanceof TLRPC.TL_inputStickerSetEmpty) { + stickerSet = selectedObject.currentEvent.action.prev_stickerset; + } + if (stickerSet != null) { + showDialog(new StickersAlert(getParentActivity(), ChannelAdminLogActivity.this, stickerSet, null, null)); + return; + } + } + } else if (type == 3) { if (selectedObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage && MessageObject.isNewGifDocument(selectedObject.messageOwner.media.webpage.document)) { items.add(LocaleController.getString("SaveToGIFs", R.string.SaveToGIFs)); options.add(11); @@ -1310,7 +1379,16 @@ public class ChannelAdminLogActivity extends BaseFragment implements PhotoViewer } Intent intent = new Intent(Intent.ACTION_SEND); intent.setType(selectedObject.getDocument().mime_type); - intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(path))); + if (Build.VERSION.SDK_INT >= 24) { + try { + intent.putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(getParentActivity(), BuildConfig.APPLICATION_ID + ".provider", new File(path))); + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } catch (Exception ignore) { + intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(path))); + } + } else { + intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(path))); + } getParentActivity().startActivityForResult(Intent.createChooser(intent, LocaleController.getString("ShareFile", R.string.ShareFile)), 500); break; } @@ -1760,77 +1838,10 @@ public class ChannelAdminLogActivity extends BaseFragment implements PhotoViewer showDialog(builder.create()); } - @Override - public void updatePhotoAtIndex(int index) { - - } - public TLRPC.Chat getCurrentChat() { return currentChat; } - @Override - public boolean allowCaption() { - return true; - } - - @Override - public boolean scaleToFill() { - return false; - } - - @Override - public PhotoViewer.PlaceProviderObject getPlaceForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { - int count = chatListView.getChildCount(); - - for (int a = 0; a < count; a++) { - ImageReceiver imageReceiver = null; - View view = chatListView.getChildAt(a); - if (view instanceof ChatMessageCell) { - if (messageObject != null) { - ChatMessageCell cell = (ChatMessageCell) view; - MessageObject message = cell.getMessageObject(); - if (message != null && message.getId() == messageObject.getId()) { - imageReceiver = cell.getPhotoImage(); - } - } - } else if (view instanceof ChatActionCell) { - ChatActionCell cell = (ChatActionCell) view; - MessageObject message = cell.getMessageObject(); - if (message != null) { - if (messageObject != null) { - if (message.getId() == messageObject.getId()) { - imageReceiver = cell.getPhotoImage(); - } - } else if (fileLocation != null && message.photoThumbs != null) { - for (int b = 0; b < message.photoThumbs.size(); b++) { - TLRPC.PhotoSize photoSize = message.photoThumbs.get(b); - if (photoSize.location.volume_id == fileLocation.volume_id && photoSize.location.local_id == fileLocation.local_id) { - imageReceiver = cell.getPhotoImage(); - break; - } - } - } - } - } - - if (imageReceiver != null) { - int coords[] = new int[2]; - view.getLocationInWindow(coords); - PhotoViewer.PlaceProviderObject object = new PhotoViewer.PlaceProviderObject(); - object.viewX = coords[0]; - object.viewY = coords[1] - (Build.VERSION.SDK_INT >= 21 ? 0 : AndroidUtilities.statusBarHeight); - object.parentView = chatListView; - object.imageReceiver = imageReceiver; - object.thumb = imageReceiver.getBitmap(); - object.radius = imageReceiver.getRoundRadius(); - object.isEvent = true; - return object; - } - } - return null; - } - private void addCanBanUser(Bundle bundle, int uid) { if (!currentChat.megagroup || admins == null || !ChatObject.canBlockUsers(currentChat)) { return; @@ -1848,7 +1859,7 @@ public class ChannelAdminLogActivity extends BaseFragment implements PhotoViewer } public void showOpenUrlAlert(final String url, boolean ask) { - if (Browser.isInternalUrl(url) || !ask) { + if (Browser.isInternalUrl(url, null) || !ask) { Browser.openUrl(getParentActivity(), url, true); } else { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); @@ -1865,42 +1876,6 @@ public class ChannelAdminLogActivity extends BaseFragment implements PhotoViewer } } - @Override - public Bitmap getThumbForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { - return null; - } - - @Override - public void willSwitchFromPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { - } - - @Override - public void willHidePhotoViewer() { - } - - @Override - public boolean isPhotoChecked(int index) { - return false; - } - - @Override - public void setPhotoChecked(int index, VideoEditedInfo videoEditedInfo) { - } - - @Override - public boolean cancelButtonPressed() { - return true; - } - - @Override - public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo) { - } - - @Override - public int getSelectedCount() { - return 0; - } - private void removeMessageObject(MessageObject messageObject) { int index = messages.indexOf(messageObject); if (index == -1) { @@ -1969,7 +1944,7 @@ public class ChannelAdminLogActivity extends BaseFragment implements PhotoViewer if (getParentActivity() == null) { return; } - showDialog(new ShareAlert(mContext, cell.getMessageObject(), null, ChatObject.isChannel(currentChat) && !currentChat.megagroup && currentChat.username != null && currentChat.username.length() > 0, null, false)); + showDialog(ShareAlert.createShareAlert(mContext, cell.getMessageObject(), null, ChatObject.isChannel(currentChat) && !currentChat.megagroup && currentChat.username != null && currentChat.username.length() > 0, null, false)); } @Override @@ -2122,7 +2097,7 @@ public class ChannelAdminLogActivity extends BaseFragment implements PhotoViewer showDialog(new StickersAlert(getParentActivity(), ChannelAdminLogActivity.this, message.getInputStickerSet(), null, null)); } else if (message.isVideo() || message.type == 1 || message.type == 0 && !message.isWebpageDocument() || message.isGif()) { PhotoViewer.getInstance().setParentActivity(getParentActivity()); - PhotoViewer.getInstance().openPhoto(message, message.type != 0 ? dialog_id : 0, 0, ChannelAdminLogActivity.this); + PhotoViewer.getInstance().openPhoto(message, message.type != 0 ? dialog_id : 0, 0, provider); } else if (message.type == 3) { try { File f = null; @@ -2147,7 +2122,7 @@ public class ChannelAdminLogActivity extends BaseFragment implements PhotoViewer if (!AndroidUtilities.isGoogleMapsInstalled(ChannelAdminLogActivity.this)) { return; } - LocationActivity fragment = new LocationActivity(); + LocationActivity fragment = new LocationActivity(0); fragment.setMessageObject(message); presentFragment(fragment); } else if (message.type == 9 || message.type == 0) { @@ -2207,6 +2182,11 @@ public class ChannelAdminLogActivity extends BaseFragment implements PhotoViewer Browser.openUrl(getParentActivity(), messageObject.messageOwner.media.webpage.url); } } + + @Override + public boolean isChatAdminCell(int uid) { + return false; + } }); chatMessageCell.setAllowAssistant(true); } else if (viewType == 1) { @@ -2218,9 +2198,9 @@ public class ChannelAdminLogActivity extends BaseFragment implements PhotoViewer PhotoViewer.getInstance().setParentActivity(getParentActivity()); TLRPC.PhotoSize photoSize = FileLoader.getClosestPhotoSizeWithSize(message.photoThumbs, 640); if (photoSize != null) { - PhotoViewer.getInstance().openPhoto(photoSize.location, ChannelAdminLogActivity.this); + PhotoViewer.getInstance().openPhoto(photoSize.location, provider); } else { - PhotoViewer.getInstance().openPhoto(message, 0, 0, ChannelAdminLogActivity.this); + PhotoViewer.getInstance().openPhoto(message, 0, 0, provider); } } @@ -2308,8 +2288,8 @@ public class ChannelAdminLogActivity extends BaseFragment implements PhotoViewer } else { pinnedTop = false; } - messageCell.setMessageObject(message, pinnedBotton, pinnedTop); - if (view instanceof ChatMessageCell && MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_AUDIO)) { + messageCell.setMessageObject(message, null, pinnedBotton, pinnedTop); + if (view instanceof ChatMessageCell && MediaController.getInstance().canDownloadMedia(message)) { ((ChatMessageCell) view).downloadAudioIfNeed(); } messageCell.setHighlighted(false); @@ -2471,7 +2451,7 @@ public class ChannelAdminLogActivity extends BaseFragment implements PhotoViewer new ThemeDescription(avatarContainer.getSubtitleTextView(), ThemeDescription.FLAG_TEXTCOLOR, null, new Paint[]{Theme.chat_statusPaint, Theme.chat_statusRecordPaint}, null, null, Theme.key_actionBarDefaultSubtitle, null), new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), - new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, null, Theme.key_avatar_text), new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_avatar_backgroundRed), new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_avatar_backgroundOrange), new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_avatar_backgroundViolet), @@ -2495,7 +2475,7 @@ public class ChannelAdminLogActivity extends BaseFragment implements PhotoViewer new ThemeDescription(chatListView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{ChatActionCell.class}, Theme.chat_actionTextPaint, null, null, Theme.key_chat_serviceText), new ThemeDescription(chatListView, ThemeDescription.FLAG_LINKCOLOR, new Class[]{ChatActionCell.class}, Theme.chat_actionTextPaint, null, null, Theme.key_chat_serviceLink), - new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_shareIconDrawable, Theme.chat_botInlineDrawable, Theme.chat_botLinkDrawalbe}, null, Theme.key_chat_serviceIcon), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_shareIconDrawable, Theme.chat_botInlineDrawable, Theme.chat_botLinkDrawalbe, Theme.chat_goIconDrawable}, null, Theme.key_chat_serviceIcon), new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class, ChatActionCell.class}, null, null, null, Theme.key_chat_serviceBackground), new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class, ChatActionCell.class}, null, null, null, Theme.key_chat_serviceBackgroundSelected), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChannelCreateActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChannelCreateActivity.java index fb47619ba..390b360ff 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChannelCreateActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChannelCreateActivity.java @@ -59,6 +59,7 @@ import org.telegram.ui.Cells.TextInfoPrivacyCell; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.AvatarUpdater; import org.telegram.ui.Components.BackupImageView; +import org.telegram.ui.Components.EditTextBoldCursor; import org.telegram.ui.Components.LayoutHelper; import java.util.ArrayList; @@ -66,13 +67,13 @@ import java.util.ArrayList; public class ChannelCreateActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, AvatarUpdater.AvatarUpdaterDelegate { private View doneButton; - private EditText nameTextView; + private EditTextBoldCursor nameTextView; private AlertDialog progressDialog; private ShadowSectionCell sectionCell; private BackupImageView avatarImage; private AvatarDrawable avatarDrawable; private AvatarUpdater avatarUpdater; - private EditText descriptionTextView; + private EditTextBoldCursor descriptionTextView; private TLRPC.FileLocation avatar; private String nameToSet; private LinearLayout linearLayout2; @@ -330,7 +331,7 @@ public class ChannelCreateActivity extends BaseFragment implements NotificationC } }); - nameTextView = new EditText(context); + nameTextView = new EditTextBoldCursor(context); nameTextView.setHint(LocaleController.getString("EnterChannelName", R.string.EnterChannelName)); if (nameToSet != null) { nameTextView.setText(nameToSet); @@ -348,7 +349,9 @@ public class ChannelCreateActivity extends BaseFragment implements NotificationC inputFilters[0] = new InputFilter.LengthFilter(100); nameTextView.setFilters(inputFilters); nameTextView.setPadding(0, 0, 0, AndroidUtilities.dp(8)); - AndroidUtilities.clearCursorDrawable(nameTextView); + nameTextView.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + nameTextView.setCursorSize(AndroidUtilities.dp(20)); + nameTextView.setCursorWidth(1.5f); frameLayout.addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, LocaleController.isRTL ? 16 : 96, 0, LocaleController.isRTL ? 96 : 16, 0)); nameTextView.addTextChangedListener(new TextWatcher() { @Override @@ -368,7 +371,7 @@ public class ChannelCreateActivity extends BaseFragment implements NotificationC } }); - descriptionTextView = new EditText(context); + descriptionTextView = new EditTextBoldCursor(context); descriptionTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); descriptionTextView.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); descriptionTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); @@ -381,7 +384,9 @@ public class ChannelCreateActivity extends BaseFragment implements NotificationC inputFilters[0] = new InputFilter.LengthFilter(120); descriptionTextView.setFilters(inputFilters); descriptionTextView.setHint(LocaleController.getString("DescriptionPlaceholder", R.string.DescriptionPlaceholder)); - AndroidUtilities.clearCursorDrawable(descriptionTextView); + descriptionTextView.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + descriptionTextView.setCursorSize(AndroidUtilities.dp(20)); + descriptionTextView.setCursorWidth(1.5f); linearLayout.addView(descriptionTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 24, 18, 24, 0)); descriptionTextView.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override @@ -486,7 +491,7 @@ public class ChannelCreateActivity extends BaseFragment implements NotificationC editText.setImeOptions(EditorInfo.IME_ACTION_DONE); publicContainer.addView(editText, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, 36)); - nameTextView = new EditText(context); + nameTextView = new EditTextBoldCursor(context); nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); nameTextView.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); nameTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); @@ -498,7 +503,9 @@ public class ChannelCreateActivity extends BaseFragment implements NotificationC nameTextView.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT); nameTextView.setImeOptions(EditorInfo.IME_ACTION_DONE); nameTextView.setHint(LocaleController.getString("ChannelUsernamePlaceholder", R.string.ChannelUsernamePlaceholder)); - AndroidUtilities.clearCursorDrawable(nameTextView); + nameTextView.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + nameTextView.setCursorSize(AndroidUtilities.dp(20)); + nameTextView.setCursorWidth(1.5f); publicContainer.addView(nameTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36)); nameTextView.addTextChangedListener(new TextWatcher() { @Override @@ -919,9 +926,6 @@ public class ChannelCreateActivity extends BaseFragment implements NotificationC case "USERNAME_OCCUPIED": builder.setMessage(LocaleController.getString("LinkInUse", R.string.LinkInUse)); break; - case "USERNAMES_UNAVAILABLE": - builder.setMessage(LocaleController.getString("FeatureUnavailable", R.string.FeatureUnavailable)); - break; default: builder.setMessage(LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred)); break; @@ -1004,7 +1008,7 @@ public class ChannelCreateActivity extends BaseFragment implements NotificationC new ThemeDescription(adminnedChannelsLayout, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{AdminedChannelCell.class}, new String[]{"statusTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText), new ThemeDescription(adminnedChannelsLayout, ThemeDescription.FLAG_LINKCOLOR, new Class[]{AdminedChannelCell.class}, new String[]{"statusTextView"}, null, null, null, Theme.key_windowBackgroundWhiteLinkText), new ThemeDescription(adminnedChannelsLayout, ThemeDescription.FLAG_IMAGECOLOR, new Class[]{AdminedChannelCell.class}, new String[]{"deleteButton"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText), - new ThemeDescription(null, 0, null, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, сellDelegate, Theme.key_avatar_text), + new ThemeDescription(null, 0, null, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, сellDelegate, Theme.key_avatar_text), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChannelEditActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChannelEditActivity.java index 5ebd39ebd..9f4cdb621 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChannelEditActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChannelEditActivity.java @@ -85,6 +85,7 @@ public class ChannelEditActivity extends BaseFragment implements NotificationCen private int eventLogRow; private int blockedUsersRow; private int managementRow; + private int permissionsRow; private int membersSectionRow; private int membersStartRow; private int membersEndRow; @@ -254,6 +255,10 @@ public class ChannelEditActivity extends BaseFragment implements NotificationCen args.putInt("type", 1); } presentFragment(new ChannelUsersActivity(args)); + } else if (position == permissionsRow) { + ChannelPermissionsActivity permissions = new ChannelPermissionsActivity(chat_id); + permissions.setInfo(info); + presentFragment(permissions); } else if (position == eventLogRow) { presentFragment(new ChannelAdminLogActivity(currentChat)); } else if (position == infoRow) { @@ -399,9 +404,13 @@ public class ChannelEditActivity extends BaseFragment implements NotificationCen } else { infoRow = -1; } + permissionsRow = -1; + /*if (currentChat.creator) { + permissionsRow = rowCount++; + }*/ eventLogRow = rowCount++; managementRow = rowCount++; - if (currentChat.megagroup) { + if (currentChat.megagroup || info != null && (info.banned_count != 0 || info.kicked_count != 0)) { blockedUsersRow = rowCount++; } else { blockedUsersRow = -1; @@ -423,14 +432,6 @@ public class ChannelEditActivity extends BaseFragment implements NotificationCen loadMoreMembersRow = -1; membersSection2Row = -1; } - - /* - if (!ChatObject.isNotInChat(currentChat) && !currentChat.megagroup && (currentChat.creator || currentChat.admin_rights != null && currentChat.admin_rights.add_admins)) { - managementRow = rowCount++; - } - if (!ChatObject.isNotInChat(currentChat) && currentChat.megagroup && (currentChat.creator || currentChat.admin_rights != null && currentChat.admin_rights.ban_users)) { - blockedUsersRow = rowCount++; - }*/ } private boolean createMenuForParticipant(TLRPC.TL_chatChannelParticipant user, TLRPC.ChannelParticipant channelParticipant, boolean resultOnly) { @@ -653,15 +654,21 @@ public class ChannelEditActivity extends BaseFragment implements NotificationCen case 0: { TLObject object = getItem(position); TLRPC.User user; + boolean isAdmin; if (object instanceof TLRPC.User) { user = (TLRPC.User) object; + TLRPC.ChatParticipant part = participantsMap.get(user.id); + if (part instanceof TLRPC.TL_chatChannelParticipant) { + TLRPC.ChannelParticipant channelParticipant = ((TLRPC.TL_chatChannelParticipant) part).channelParticipant; + isAdmin = channelParticipant instanceof TLRPC.TL_channelParticipantCreator || channelParticipant instanceof TLRPC.TL_channelParticipantAdmin; + } else { + isAdmin = part instanceof TLRPC.TL_chatParticipantAdmin; + } } else { + isAdmin = object instanceof TLRPC.TL_channelParticipantAdmin || object instanceof TLRPC.TL_channelParticipantCreator; user = MessagesController.getInstance().getUser(((TLRPC.ChannelParticipant) object).user_id); } - String un = user.username; - CharSequence username = null; CharSequence name = null; - String nameSearch = searchAdapterHelper.getLastFoundChannel(); if (nameSearch != null) { @@ -675,7 +682,9 @@ public class ChannelEditActivity extends BaseFragment implements NotificationCen ManageChatUserCell userCell = (ManageChatUserCell) holder.itemView; userCell.setTag(position); - userCell.setData(user, name, username); + userCell.setIsAdmin(isAdmin); + userCell.setData(user, name, null); + break; } } @@ -755,6 +764,8 @@ public class ChannelEditActivity extends BaseFragment implements NotificationCen textCell.setText(LocaleController.getString("EventLog", R.string.EventLog), null, R.drawable.group_log, true); } else if (i == infoRow) { textCell.setText(currentChat.megagroup ? LocaleController.getString("EventLogFilterGroupInfo", R.string.EventLogFilterGroupInfo) : LocaleController.getString("EventLogFilterChannelInfo", R.string.EventLogFilterChannelInfo), null, R.drawable.group_edit, true); + } else if (i == permissionsRow) { + //textCell.setText(LocaleController.getString("ChatPermissions", R.string.ChatPermissions), null, R.drawable.group_log, true); //TODO icon } break; case 1: @@ -767,6 +778,12 @@ public class ChannelEditActivity extends BaseFragment implements NotificationCen part = info.participants.participants.get(i - membersStartRow); } if (part != null) { + if (part instanceof TLRPC.TL_chatChannelParticipant) { + TLRPC.ChannelParticipant channelParticipant = ((TLRPC.TL_chatChannelParticipant) part).channelParticipant; + userCell.setIsAdmin(channelParticipant instanceof TLRPC.TL_channelParticipantCreator || channelParticipant instanceof TLRPC.TL_channelParticipantAdmin); + } else { + userCell.setIsAdmin(part instanceof TLRPC.TL_chatParticipantAdmin); + } userCell.setData(MessagesController.getInstance().getUser(part.user_id), null, null); } break; @@ -793,7 +810,7 @@ public class ChannelEditActivity extends BaseFragment implements NotificationCen @Override public int getItemViewType(int i) { - if (i == managementRow || i == blockedUsersRow || i == infoRow || i == eventLogRow) { + if (i == managementRow || i == blockedUsersRow || i == infoRow || i == eventLogRow || i == permissionsRow) { return 0; } else if (i >= membersStartRow && i < membersEndRow) { return 1; @@ -841,7 +858,7 @@ public class ChannelEditActivity extends BaseFragment implements NotificationCen new ThemeDescription(listView, 0, new Class[]{ManageChatUserCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), new ThemeDescription(listView, 0, new Class[]{ManageChatUserCell.class}, new String[]{"statusColor"}, null, null, сellDelegate, Theme.key_windowBackgroundWhiteGrayText), new ThemeDescription(listView, 0, new Class[]{ManageChatUserCell.class}, new String[]{"statusOnlineColor"}, null, null, сellDelegate, Theme.key_windowBackgroundWhiteBlueText), - new ThemeDescription(listView, 0, new Class[]{ManageChatUserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(listView, 0, new Class[]{ManageChatUserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, null, Theme.key_avatar_text), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChannelEditInfoActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChannelEditInfoActivity.java index 94fcc5ec9..1897a9ae7 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChannelEditInfoActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChannelEditInfoActivity.java @@ -63,6 +63,7 @@ import org.telegram.ui.Cells.TextSettingsCell; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.AvatarUpdater; import org.telegram.ui.Components.BackupImageView; +import org.telegram.ui.Components.EditTextBoldCursor; import org.telegram.ui.Components.LayoutHelper; import java.util.ArrayList; @@ -71,12 +72,13 @@ import java.util.concurrent.Semaphore; public class ChannelEditInfoActivity extends BaseFragment implements AvatarUpdater.AvatarUpdaterDelegate, NotificationCenter.NotificationCenterDelegate { private View doneButton; - private EditText nameTextView; - private EditText usernameTextView; - private EditText descriptionTextView; + private EditTextBoldCursor nameTextView; + private EditTextBoldCursor usernameTextView; + private EditTextBoldCursor descriptionTextView; private EditText editText; private TextInfoPrivacyCell typeInfoCell; private HeaderCell headerCell; + private HeaderCell headerCell2; private TextView checkTextView; private LinearLayout linearLayout; private BackupImageView avatarImage; @@ -86,8 +88,11 @@ public class ChannelEditInfoActivity extends BaseFragment implements AvatarUpdat private LinearLayout linearLayout2; private LinearLayout linearLayout3; private LinearLayout linearLayoutTypeContainer; + private LinearLayout linearLayoutInviteContainer; private RadioButtonCell radioButtonCell1; private RadioButtonCell radioButtonCell2; + private RadioButtonCell radioButtonCell3; + private RadioButtonCell radioButtonCell4; private LinearLayout adminnedChannelsLayout; private LinearLayout linkContainer; private LinearLayout publicContainer; @@ -101,13 +106,18 @@ public class ChannelEditInfoActivity extends BaseFragment implements AvatarUpdat private FrameLayout container4; private ShadowSectionCell sectionCell; private ShadowSectionCell sectionCell2; + private ShadowSectionCell sectionCell3; private TextCheckCell textCheckCell; private TextInfoPrivacyCell infoCell; private TextSettingsCell textCell; private TextInfoPrivacyCell infoCell2; + private TextSettingsCell textCell2; + private TextInfoPrivacyCell infoCell3; private boolean isPrivate; + private boolean historyHidden; + private TLRPC.FileLocation avatar; private TLRPC.Chat currentChat; private TLRPC.ChatFull info; @@ -216,6 +226,13 @@ public class ChannelEditInfoActivity extends BaseFragment implements AvatarUpdat public void onResume() { super.onResume(); AndroidUtilities.requestAdjustResize(getParentActivity(), classGuid); + if (textCell2 != null && info != null) { + if (info.stickerset != null) { + textCell2.setTextAndValue(LocaleController.getString("GroupStickers", R.string.GroupStickers), info.stickerset.title, false); + } else { + textCell2.setText(LocaleController.getString("GroupStickers", R.string.GroupStickers), false); + } + } } @Override @@ -289,6 +306,10 @@ public class ChannelEditInfoActivity extends BaseFragment implements AvatarUpdat if (info != null && !info.about.equals(descriptionTextView.getText().toString())) { MessagesController.getInstance().updateChannelAbout(chatId, descriptionTextView.getText().toString(), info); } + if (headerCell2 != null && headerCell2.getVisibility() == View.VISIBLE && info != null && currentChat.creator && info.hidden_prehistory != historyHidden) { + info.hidden_prehistory = historyHidden; + MessagesController.getInstance().toogleChannelInvitesHistory(chatId, historyHidden); + } if (signMessages != currentChat.signatures) { currentChat.signatures = true; MessagesController.getInstance().toogleChannelSignatures(chatId, signMessages); @@ -364,7 +385,7 @@ public class ChannelEditInfoActivity extends BaseFragment implements AvatarUpdat } }); - nameTextView = new EditText(context); + nameTextView = new EditTextBoldCursor(context); if (currentChat.megagroup) { nameTextView.setHint(LocaleController.getString("GroupName", R.string.GroupName)); } else { @@ -383,8 +404,10 @@ public class ChannelEditInfoActivity extends BaseFragment implements AvatarUpdat InputFilter[] inputFilters = new InputFilter[1]; inputFilters[0] = new InputFilter.LengthFilter(100); nameTextView.setFilters(inputFilters); - AndroidUtilities.clearCursorDrawable(nameTextView); + nameTextView.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + nameTextView.setCursorSize(AndroidUtilities.dp(20)); nameTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + nameTextView.setCursorWidth(1.5f); frameLayout.addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, LocaleController.isRTL ? 16 : 96, 0, LocaleController.isRTL ? 96 : 16, 0)); nameTextView.addTextChangedListener(new TextWatcher() { @Override @@ -413,7 +436,7 @@ public class ChannelEditInfoActivity extends BaseFragment implements AvatarUpdat linearLayout3.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); linearLayout.addView(linearLayout3, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); - descriptionTextView = new EditText(context); + descriptionTextView = new EditTextBoldCursor(context); descriptionTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); descriptionTextView.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); descriptionTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); @@ -428,7 +451,9 @@ public class ChannelEditInfoActivity extends BaseFragment implements AvatarUpdat inputFilters[0] = new InputFilter.LengthFilter(255); descriptionTextView.setFilters(inputFilters); descriptionTextView.setHint(LocaleController.getString("DescriptionOptionalPlaceholder", R.string.DescriptionOptionalPlaceholder)); - AndroidUtilities.clearCursorDrawable(descriptionTextView); + descriptionTextView.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + descriptionTextView.setCursorSize(AndroidUtilities.dp(20)); + descriptionTextView.setCursorWidth(1.5f); linearLayout3.addView(descriptionTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 17, 12, 17, 6)); descriptionTextView.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override @@ -458,7 +483,6 @@ public class ChannelEditInfoActivity extends BaseFragment implements AvatarUpdat }); sectionCell = new ShadowSectionCell(context); - sectionCell.setSize(20); linearLayout.addView(sectionCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); container1 = new FrameLayout(context); @@ -539,7 +563,7 @@ public class ChannelEditInfoActivity extends BaseFragment implements AvatarUpdat editText.setImeOptions(EditorInfo.IME_ACTION_DONE); publicContainer.addView(editText, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, 36)); - usernameTextView = new EditText(context); + usernameTextView = new EditTextBoldCursor(context); usernameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); if (!isPrivate) { usernameTextView.setText(currentChat.username); @@ -554,7 +578,9 @@ public class ChannelEditInfoActivity extends BaseFragment implements AvatarUpdat usernameTextView.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT); usernameTextView.setImeOptions(EditorInfo.IME_ACTION_DONE); usernameTextView.setHint(LocaleController.getString("ChannelUsernamePlaceholder", R.string.ChannelUsernamePlaceholder)); - AndroidUtilities.clearCursorDrawable(usernameTextView); + usernameTextView.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + usernameTextView.setCursorSize(AndroidUtilities.dp(20)); + usernameTextView.setCursorWidth(1.5f); publicContainer.addView(usernameTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36)); usernameTextView.addTextChangedListener(new TextWatcher() { @Override @@ -612,12 +638,54 @@ public class ChannelEditInfoActivity extends BaseFragment implements AvatarUpdat linearLayout.addView(adminnedChannelsLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); adminedInfoCell = new ShadowSectionCell(context); - adminedInfoCell.setSize(20); linearLayout.addView(adminedInfoCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); updatePrivatePublic(); } + if (currentChat.creator && currentChat.megagroup) { + headerCell2 = new HeaderCell(context); + headerCell2.setText(LocaleController.getString("ChatHistory", R.string.ChatHistory)); + headerCell2.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + linearLayout.addView(headerCell2); + + linearLayoutInviteContainer = new LinearLayout(context); + linearLayoutInviteContainer.setOrientation(LinearLayout.VERTICAL); + linearLayoutInviteContainer.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + linearLayout.addView(linearLayoutInviteContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + radioButtonCell3 = new RadioButtonCell(context); + radioButtonCell3.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + radioButtonCell3.setTextAndValue(LocaleController.getString("ChatHistoryVisible", R.string.ChatHistoryVisible), LocaleController.getString("ChatHistoryVisibleInfo", R.string.ChatHistoryVisibleInfo), !historyHidden); + linearLayoutInviteContainer.addView(radioButtonCell3, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + radioButtonCell3.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + radioButtonCell3.setChecked(true, true); + radioButtonCell4.setChecked(false, true); + historyHidden = false; + } + }); + + radioButtonCell4 = new RadioButtonCell(context); + radioButtonCell4.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + radioButtonCell4.setTextAndValue(LocaleController.getString("ChatHistoryHidden", R.string.ChatHistoryHidden), LocaleController.getString("ChatHistoryHiddenInfo", R.string.ChatHistoryHiddenInfo), historyHidden); + linearLayoutInviteContainer.addView(radioButtonCell4, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + radioButtonCell4.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + radioButtonCell3.setChecked(false, true); + radioButtonCell4.setChecked(true, true); + historyHidden = true; + } + }); + + sectionCell3 = new ShadowSectionCell(context); + linearLayout.addView(sectionCell3, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + updatePrivatePublic(); + } + lineView2 = new View(context); lineView2.setBackgroundColor(Theme.getColor(Theme.key_divider)); linearLayout.addView(lineView2, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1)); @@ -652,6 +720,28 @@ public class ChannelEditInfoActivity extends BaseFragment implements AvatarUpdat infoCell.setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); infoCell.setText(LocaleController.getString("ChannelSignMessagesInfo", R.string.ChannelSignMessagesInfo)); linearLayout.addView(infoCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } else if (info != null && info.can_set_stickers) { + textCell2 = new TextSettingsCell(context); + textCell2.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + textCell2.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + if (info.stickerset != null) { + textCell2.setTextAndValue(LocaleController.getString("GroupStickers", R.string.GroupStickers), info.stickerset.title, false); + } else { + textCell2.setText(LocaleController.getString("GroupStickers", R.string.GroupStickers), false); + } + container3.addView(textCell2, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + textCell2.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + GroupStickersActivity groupStickersActivity = new GroupStickersActivity(currentChat.id); + groupStickersActivity.setInfo(info); + presentFragment(groupStickersActivity); + } + }); + + infoCell3 = new TextInfoPrivacyCell(context); + infoCell3.setText(LocaleController.getString("GroupStickersInfo", R.string.GroupStickersInfo)); + linearLayout.addView(infoCell3, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); } if (currentChat.creator) { @@ -687,7 +777,7 @@ public class ChannelEditInfoActivity extends BaseFragment implements AvatarUpdat } else { NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); } - MessagesController.getInstance().deleteUserFromChat(chatId, MessagesController.getInstance().getUser(UserConfig.getClientUserId()), info); + MessagesController.getInstance().deleteUserFromChat(chatId, MessagesController.getInstance().getUser(UserConfig.getClientUserId()), info, true); finishFragment(); } }); @@ -706,7 +796,9 @@ public class ChannelEditInfoActivity extends BaseFragment implements AvatarUpdat linearLayout.addView(infoCell2, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); } else { if (currentChat.megagroup) { - sectionCell.setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + if (infoCell3 == null) { + sectionCell.setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + } } else { infoCell.setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); } @@ -714,6 +806,14 @@ public class ChannelEditInfoActivity extends BaseFragment implements AvatarUpdat lineView2.setVisibility(View.GONE); } + if (infoCell3 != null) { + if (infoCell2 == null) { + infoCell3.setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + } else { + infoCell3.setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + } + } + nameTextView.setText(currentChat.title); nameTextView.setSelection(nameTextView.length()); if (info != null) { @@ -736,6 +836,11 @@ public class ChannelEditInfoActivity extends BaseFragment implements AvatarUpdat if (chatFull.id == chatId) { if (info == null) { descriptionTextView.setText(chatFull.about); + historyHidden = chatFull.hidden_prehistory; + if (radioButtonCell3 != null) { + radioButtonCell3.setChecked(!historyHidden, false); + radioButtonCell4.setChecked(historyHidden, false); + } } info = chatFull; invite = chatFull.exported_invite; @@ -794,6 +899,9 @@ public class ChannelEditInfoActivity extends BaseFragment implements AvatarUpdat } public void setInfo(TLRPC.ChatFull chatFull) { + if (info == null && chatFull != null) { + historyHidden = chatFull.hidden_prehistory; + } info = chatFull; if (chatFull != null) { if (chatFull.exported_invite instanceof TLRPC.TL_chatInviteExported) { @@ -904,12 +1012,17 @@ public class ChannelEditInfoActivity extends BaseFragment implements AvatarUpdat loadingAdminedCell.setVisibility(View.GONE); adminnedChannelsLayout.setVisibility(View.VISIBLE); } + if (headerCell2 != null) { + headerCell2.setVisibility(View.GONE); + linearLayoutInviteContainer.setVisibility(View.GONE); + sectionCell3.setVisibility(View.GONE); + } } else { typeInfoCell.setTag(Theme.key_windowBackgroundWhiteGrayText4); typeInfoCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText4)); sectionCell2.setVisibility(View.VISIBLE); adminedInfoCell.setVisibility(View.GONE); - typeInfoCell.setBackgroundDrawable(Theme.getThemedDrawable(typeInfoCell.getContext(), R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + typeInfoCell.setBackgroundDrawable(Theme.getThemedDrawable(typeInfoCell.getContext(), R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); adminnedChannelsLayout.setVisibility(View.GONE); linkContainer.setVisibility(View.VISIBLE); loadingAdminedCell.setVisibility(View.GONE); @@ -925,6 +1038,11 @@ public class ChannelEditInfoActivity extends BaseFragment implements AvatarUpdat linkContainer.setPadding(0, 0, 0, isPrivate ? 0 : AndroidUtilities.dp(7)); privateContainer.setText(invite != null ? invite.link : LocaleController.getString("Loading", R.string.Loading), false); checkTextView.setVisibility(!isPrivate && checkTextView.length() != 0 ? View.VISIBLE : View.GONE); + if (headerCell2 != null) { + headerCell2.setVisibility(isPrivate ? View.VISIBLE : View.GONE); + linearLayoutInviteContainer.setVisibility(isPrivate ? View.VISIBLE : View.GONE); + sectionCell3.setVisibility(isPrivate ? View.VISIBLE : View.GONE); + } } radioButtonCell1.setChecked(!isPrivate, true); radioButtonCell2.setChecked(isPrivate, true); @@ -1113,7 +1231,7 @@ public class ChannelEditInfoActivity extends BaseFragment implements AvatarUpdat new ThemeDescription(container3, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), new ThemeDescription(container4, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), - new ThemeDescription(null, 0, null, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, сellDelegate, Theme.key_avatar_text), + new ThemeDescription(null, 0, null, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, сellDelegate, Theme.key_avatar_text), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundBlue), new ThemeDescription(lineView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_divider), @@ -1122,6 +1240,7 @@ public class ChannelEditInfoActivity extends BaseFragment implements AvatarUpdat new ThemeDescription(sectionCell, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ShadowSectionCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), new ThemeDescription(sectionCell2, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ShadowSectionCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + new ThemeDescription(sectionCell3, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ShadowSectionCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), new ThemeDescription(textCheckCell, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), new ThemeDescription(textCheckCell, 0, new Class[]{TextCheckCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), @@ -1135,9 +1254,13 @@ public class ChannelEditInfoActivity extends BaseFragment implements AvatarUpdat new ThemeDescription(textCell, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), new ThemeDescription(textCell, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteRedText5), + new ThemeDescription(textCell2, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + new ThemeDescription(textCell2, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), new ThemeDescription(infoCell2, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), new ThemeDescription(infoCell2, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + new ThemeDescription(infoCell3, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + new ThemeDescription(infoCell3, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), new ThemeDescription(usernameTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), new ThemeDescription(usernameTextView, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), @@ -1145,6 +1268,7 @@ public class ChannelEditInfoActivity extends BaseFragment implements AvatarUpdat new ThemeDescription(linearLayoutTypeContainer, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), new ThemeDescription(linkContainer, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), new ThemeDescription(headerCell, 0, new Class[]{HeaderCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader), + new ThemeDescription(headerCell2, 0, new Class[]{HeaderCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader), new ThemeDescription(editText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), new ThemeDescription(editText, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), new ThemeDescription(checkTextView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, null, null, null, null, Theme.key_windowBackgroundWhiteRedText4), @@ -1171,11 +1295,23 @@ public class ChannelEditInfoActivity extends BaseFragment implements AvatarUpdat new ThemeDescription(radioButtonCell2, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{RadioButtonCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), new ThemeDescription(radioButtonCell2, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{RadioButtonCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(linearLayoutInviteContainer, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(radioButtonCell3, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + new ThemeDescription(radioButtonCell3, ThemeDescription.FLAG_CHECKBOX, new Class[]{RadioButtonCell.class}, new String[]{"radioButton"}, null, null, null, Theme.key_radioBackground), + new ThemeDescription(radioButtonCell3, ThemeDescription.FLAG_CHECKBOXCHECK, new Class[]{RadioButtonCell.class}, new String[]{"radioButton"}, null, null, null, Theme.key_radioBackgroundChecked), + new ThemeDescription(radioButtonCell3, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{RadioButtonCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(radioButtonCell3, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{RadioButtonCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(radioButtonCell4, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + new ThemeDescription(radioButtonCell4, ThemeDescription.FLAG_CHECKBOX, new Class[]{RadioButtonCell.class}, new String[]{"radioButton"}, null, null, null, Theme.key_radioBackground), + new ThemeDescription(radioButtonCell4, ThemeDescription.FLAG_CHECKBOXCHECK, new Class[]{RadioButtonCell.class}, new String[]{"radioButton"}, null, null, null, Theme.key_radioBackgroundChecked), + new ThemeDescription(radioButtonCell4, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{RadioButtonCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(radioButtonCell4, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{RadioButtonCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(adminnedChannelsLayout, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{AdminedChannelCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), new ThemeDescription(adminnedChannelsLayout, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{AdminedChannelCell.class}, new String[]{"statusTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText), new ThemeDescription(adminnedChannelsLayout, ThemeDescription.FLAG_LINKCOLOR, new Class[]{AdminedChannelCell.class}, new String[]{"statusTextView"}, null, null, null, Theme.key_windowBackgroundWhiteLinkText), new ThemeDescription(adminnedChannelsLayout, ThemeDescription.FLAG_IMAGECOLOR, new Class[]{AdminedChannelCell.class}, new String[]{"deleteButton"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText), - new ThemeDescription(null, 0, null, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, сellDelegate2, Theme.key_avatar_text), + new ThemeDescription(null, 0, null, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, сellDelegate2, Theme.key_avatar_text), new ThemeDescription(null, 0, null, null, null, сellDelegate2, Theme.key_avatar_backgroundRed), new ThemeDescription(null, 0, null, null, null, сellDelegate2, Theme.key_avatar_backgroundOrange), new ThemeDescription(null, 0, null, null, null, сellDelegate2, Theme.key_avatar_backgroundViolet), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChannelPermissionsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChannelPermissionsActivity.java new file mode 100644 index 000000000..1e7808e70 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChannelPermissionsActivity.java @@ -0,0 +1,380 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui; + +import android.content.Context; +import android.text.TextUtils; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.R; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Cells.HeaderCell; +import org.telegram.ui.Cells.RadioButtonCell; +import org.telegram.ui.Cells.ShadowSectionCell; +import org.telegram.ui.Cells.TextCheckCell2; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; + +public class ChannelPermissionsActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { + + private ListAdapter listViewAdapter; + private RecyclerListView listView; + + private int chatId; + + private TLRPC.TL_channelAdminRights adminRights; + private TLRPC.ChatFull info; + private boolean historyHidden; + + private HeaderCell headerCell2; + private LinearLayout linearLayout; + private RadioButtonCell radioButtonCell3; + private RadioButtonCell radioButtonCell4; + + private int rowCount; + private int permissionsHeaderRow; + private int sendMediaRow; + private int sendStickersRow; + private int embedLinksRow; + private int addUsersRow; + private int changeInfoRow; + private int rightsShadowRow; + private int forwardRow; + private int forwardShadowRow; + + private final static int done_button = 1; + + public ChannelPermissionsActivity(int channelId) { + super(); + chatId = channelId; + adminRights = new TLRPC.TL_channelAdminRights(); + rowCount = 0; + permissionsHeaderRow = rowCount++; + sendMediaRow = rowCount++; + sendStickersRow = rowCount++; + embedLinksRow = rowCount++; + addUsersRow = rowCount++; + changeInfoRow = rowCount++; + rightsShadowRow = rowCount++; + TLRPC.Chat chat = MessagesController.getInstance().getChat(chatId); + if (chat != null && TextUtils.isEmpty(chat.username)) { + forwardRow = rowCount++; + forwardShadowRow = rowCount++; + } else { + forwardRow = -1; + forwardShadowRow = -1; + } + } + + /* + Permissions + Group Permissions + What can group members do? + Invite New Members + Edit Group Info & Photo + */ + + @Override + public boolean onFragmentCreate() { + NotificationCenter.getInstance().addObserver(this, NotificationCenter.chatInfoDidLoaded); + return super.onFragmentCreate(); + } + + @Override + public void onFragmentDestroy() { + super.onFragmentDestroy(); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.chatInfoDidLoaded); + } + + @Override + public View createView(Context context) { + actionBar.setBackButtonImage(R.drawable.ic_ab_back); + actionBar.setAllowOverlayTitle(true); + //actionBar.setTitle(LocaleController.getString("ChatPermissionsTitle", R.string.ChatPermissionsTitle)); + + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { + @Override + public void onItemClick(int id) { + if (id == -1) { + finishFragment(); + } else if (id == done_button) { + if (headerCell2 != null && headerCell2.getVisibility() == View.VISIBLE && info != null && info.hidden_prehistory != historyHidden) { + info.hidden_prehistory = historyHidden; + MessagesController.getInstance().toogleChannelInvitesHistory(chatId, historyHidden); + } + finishFragment(); + } + } + }); + + ActionBarMenu menu = actionBar.createMenu(); + menu.addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56)); + + fragmentView = new FrameLayout(context); + fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); + FrameLayout frameLayout = (FrameLayout) fragmentView; + + listView = new RecyclerListView(context); + LinearLayoutManager linearLayoutManager = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) { + @Override + public boolean supportsPredictiveItemAnimations() { + return false; + } + }; + listView.setItemAnimator(null); + listView.setLayoutAnimation(null); + listView.setLayoutManager(linearLayoutManager); + listView.setAdapter(listViewAdapter = new ListAdapter(context)); + listView.setVerticalScrollbarPosition(LocaleController.isRTL ? RecyclerListView.SCROLLBAR_POSITION_LEFT : RecyclerListView.SCROLLBAR_POSITION_RIGHT); + frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + if (view instanceof TextCheckCell2) { + TextCheckCell2 checkCell = (TextCheckCell2) view; + if (!checkCell.isEnabled()) { + return; + } + checkCell.setChecked(!checkCell.isChecked()); + if (position == changeInfoRow) { + adminRights.change_info = !adminRights.change_info; + } else if (position == addUsersRow) { + adminRights.invite_users = !adminRights.invite_users; + } else if (position == sendMediaRow) { + adminRights.ban_users = !adminRights.ban_users; + } else if (position == sendStickersRow) { + adminRights.add_admins = !adminRights.add_admins; + } else if (position == embedLinksRow) { + adminRights.pin_messages = !adminRights.pin_messages; + } + } + } + }); + + linearLayout = new LinearLayout(context); + linearLayout.setOrientation(LinearLayout.VERTICAL); + linearLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + linearLayout.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + headerCell2 = new HeaderCell(context); + headerCell2.setText(LocaleController.getString("ChatHistory", R.string.ChatHistory)); + headerCell2.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + linearLayout.addView(headerCell2); + + radioButtonCell3 = new RadioButtonCell(context); + radioButtonCell3.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + radioButtonCell3.setTextAndValue(LocaleController.getString("ChatHistoryVisible", R.string.ChatHistoryVisible), LocaleController.getString("ChatHistoryVisibleInfo", R.string.ChatHistoryVisibleInfo), !historyHidden); + linearLayout.addView(radioButtonCell3, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + radioButtonCell3.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + radioButtonCell3.setChecked(true, true); + radioButtonCell4.setChecked(false, true); + historyHidden = false; + } + }); + + radioButtonCell4 = new RadioButtonCell(context); + radioButtonCell4.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + radioButtonCell4.setTextAndValue(LocaleController.getString("ChatHistoryHidden", R.string.ChatHistoryHidden), LocaleController.getString("ChatHistoryHiddenInfo", R.string.ChatHistoryHiddenInfo), historyHidden); + linearLayout.addView(radioButtonCell4, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + radioButtonCell4.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + radioButtonCell3.setChecked(false, true); + radioButtonCell4.setChecked(true, true); + historyHidden = true; + } + }); + + return fragmentView; + } + + @Override + public void onResume() { + super.onResume(); + if (listViewAdapter != null) { + listViewAdapter.notifyDataSetChanged(); + } + } + + @Override + public void didReceivedNotification(int id, Object... args) { + if (id == NotificationCenter.chatInfoDidLoaded) { + TLRPC.ChatFull chatFull = (TLRPC.ChatFull) args[0]; + if (chatFull.id == chatId) { + if (info == null) { + historyHidden = chatFull.hidden_prehistory; + if (radioButtonCell3 != null) { + radioButtonCell3.setChecked(!historyHidden, false); + radioButtonCell4.setChecked(historyHidden, false); + } + } + info = chatFull; + } + } + } + + public void setInfo(TLRPC.ChatFull chatFull) { + if (info == null && chatFull != null) { + historyHidden = chatFull.hidden_prehistory; + } + info = chatFull; + } + + private class ListAdapter extends RecyclerListView.SelectionAdapter { + + private Context mContext; + + public ListAdapter(Context context) { + mContext = context; + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + int type = holder.getItemViewType(); + return type == 1; + } + + @Override + public int getItemCount() { + return rowCount; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + view = new HeaderCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 1: + view = new TextCheckCell2(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 2: + view = new ShadowSectionCell(mContext); + break; + case 3: + default: + view = linearLayout; + break; + } + return new RecyclerListView.Holder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + /*case 0: + HeaderCell headerCell = (HeaderCell) holder.itemView; + headerCell.setText(LocaleController.getString("ChatPermissionsHeader", R.string.ChatPermissionsHeader)); + break; + case 1: + TextCheckCell2 checkCell = (TextCheckCell2) holder.itemView; + if (position == changeInfoRow) { + checkCell.setTextAndCheck(LocaleController.getString("ChatPermissionsEditInfo", R.string.ChatPermissionsEditInfo), adminRights.change_info, true); + } else if (position == addUsersRow) { + checkCell.setTextAndCheck(LocaleController.getString("ChatPermissionsInviteMembers", R.string.ChatPermissionsInviteMembers), adminRights.invite_users, true); + } else if (position == sendMediaRow) { + checkCell.setTextAndCheck(LocaleController.getString("UserRestrictionsSendMedia", R.string.UserRestrictionsSendMedia), adminRights.ban_users, true); + } else if (position == sendStickersRow) { + checkCell.setTextAndCheck(LocaleController.getString("UserRestrictionsSendStickers", R.string.UserRestrictionsSendStickers), adminRights.add_admins, true); + } else if (position == embedLinksRow) { + checkCell.setTextAndCheck(LocaleController.getString("UserRestrictionsEmbedLinks", R.string.UserRestrictionsEmbedLinks), adminRights.pin_messages, false); + } + //if (position == sendMediaRow || position == sendStickersRow || position == embedLinksRow) { + // checkCell.setEnabled(!bannedRights.send_messages && !bannedRights.view_messages); + //} else if (position == sendMessagesRow) { + // checkCell.setEnabled(!bannedRights.view_messages); + //} + break;*/ + case 2: + ShadowSectionCell shadowCell = (ShadowSectionCell) holder.itemView; + if (position == rightsShadowRow) { + shadowCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, forwardShadowRow == -1 ? R.drawable.greydivider_bottom : R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + } else { + shadowCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + } + break; + } + } + + @Override + public int getItemViewType(int position) { + if (position == rightsShadowRow || position == forwardShadowRow) { + return 2; + } else if (position == changeInfoRow || position == addUsersRow || position == sendMediaRow || position == sendStickersRow || position == embedLinksRow) { + return 1; + } else if (position == forwardRow) { + return 3; + } else if (position == permissionsHeaderRow) { + return 0; + } + return 0; + } + } + + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{TextCheckCell2.class, HeaderCell.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(listView, 0, new Class[]{TextCheckCell2.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell2.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell2.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchThumb), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell2.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchTrack), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell2.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchThumbChecked), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell2.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchTrackChecked), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ShadowSectionCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + + new ThemeDescription(listView, 0, new Class[]{HeaderCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader), + + new ThemeDescription(linearLayout, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(radioButtonCell3, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + new ThemeDescription(radioButtonCell3, ThemeDescription.FLAG_CHECKBOX, new Class[]{RadioButtonCell.class}, new String[]{"radioButton"}, null, null, null, Theme.key_radioBackground), + new ThemeDescription(radioButtonCell3, ThemeDescription.FLAG_CHECKBOXCHECK, new Class[]{RadioButtonCell.class}, new String[]{"radioButton"}, null, null, null, Theme.key_radioBackgroundChecked), + new ThemeDescription(radioButtonCell3, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{RadioButtonCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(radioButtonCell3, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{RadioButtonCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(radioButtonCell4, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + new ThemeDescription(radioButtonCell4, ThemeDescription.FLAG_CHECKBOX, new Class[]{RadioButtonCell.class}, new String[]{"radioButton"}, null, null, null, Theme.key_radioBackground), + new ThemeDescription(radioButtonCell4, ThemeDescription.FLAG_CHECKBOXCHECK, new Class[]{RadioButtonCell.class}, new String[]{"radioButton"}, null, null, null, Theme.key_radioBackgroundChecked), + new ThemeDescription(radioButtonCell4, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{RadioButtonCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(radioButtonCell4, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{RadioButtonCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + }; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChannelRightsEditActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChannelRightsEditActivity.java index 871d3618e..33247ccc7 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChannelRightsEditActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChannelRightsEditActivity.java @@ -743,7 +743,7 @@ public class ChannelRightsEditActivity extends BaseFragment { new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"statusColor"}, null, null, сellDelegate, Theme.key_windowBackgroundWhiteGrayText), new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"statusOnlineColor"}, null, null, сellDelegate, Theme.key_windowBackgroundWhiteBlueText), - new ThemeDescription(listView, 0, new Class[]{UserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, null, Theme.key_avatar_text), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChannelUsersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChannelUsersActivity.java index df638231f..8596e97eb 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChannelUsersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChannelUsersActivity.java @@ -257,7 +257,11 @@ public class ChannelUsersActivity extends BaseFragment implements NotificationCe actionBar.setTitle(LocaleController.getString("ChannelAdministrators", R.string.ChannelAdministrators)); } else if (type == 2) { if (selectType == 0) { - actionBar.setTitle(LocaleController.getString("ChannelMembers", R.string.ChannelMembers)); + if (ChatObject.isChannel(currentChat) && !currentChat.megagroup) { + actionBar.setTitle(LocaleController.getString("ChannelSubscribers", R.string.ChannelSubscribers)); + } else { + actionBar.setTitle(LocaleController.getString("ChannelMembers", R.string.ChannelMembers)); + } } else { if (selectType == 1) { actionBar.setTitle(LocaleController.getString("ChannelAddAdmin", R.string.ChannelAddAdmin)); @@ -443,6 +447,11 @@ public class ChannelUsersActivity extends BaseFragment implements NotificationCe if (participant != null) { participant.admin_rights = rightsAdmin; participant.banned_rights = rightsBanned; + TLRPC.ChannelParticipant p = participantsMap.get(participant.user_id); + if (p != null) { + p.admin_rights = rightsAdmin; + p.banned_rights = rightsBanned; + } } removeSelfFromStack(); } @@ -483,6 +492,11 @@ public class ChannelUsersActivity extends BaseFragment implements NotificationCe if (participant != null) { participant.admin_rights = rightsAdmin; participant.banned_rights = rightsBanned; + TLRPC.ChannelParticipant p = participantsMap.get(participant.user_id); + if (p != null) { + p.admin_rights = rightsAdmin; + p.banned_rights = rightsBanned; + } } } }); @@ -1258,7 +1272,11 @@ public class ChannelUsersActivity extends BaseFragment implements NotificationCe if (type == 0) { sectionCell.setText(LocaleController.getString("ChannelRestrictedUsers", R.string.ChannelRestrictedUsers).toUpperCase()); } else { - sectionCell.setText(LocaleController.getString("ChannelMembers", R.string.ChannelMembers).toUpperCase()); + if (ChatObject.isChannel(currentChat) && !currentChat.megagroup) { + actionBar.setTitle(LocaleController.getString("ChannelSubscribers", R.string.ChannelSubscribers)); + } else { + sectionCell.setText(LocaleController.getString("ChannelMembers", R.string.ChannelMembers).toUpperCase()); + } } } else if (position == group2StartRow) { sectionCell.setText(LocaleController.getString("ChannelBlockedUsers", R.string.ChannelBlockedUsers).toUpperCase()); @@ -1471,7 +1489,11 @@ public class ChannelUsersActivity extends BaseFragment implements NotificationCe } else if (type == 1) { actionCell.setText(LocaleController.getString("ChannelAddAdmin", R.string.ChannelAddAdmin), null, R.drawable.group_admin_new, false); } else if (type == 2) { - actionCell.setText(LocaleController.getString("AddMember", R.string.AddMember), null, R.drawable.menu_invite, true); + if (ChatObject.isChannel(currentChat) && !currentChat.megagroup) { + actionCell.setText(LocaleController.getString("AddSubscriber", R.string.AddSubscriber), null, R.drawable.menu_invite, true); + } else { + actionCell.setText(LocaleController.getString("AddMember", R.string.AddMember), null, R.drawable.menu_invite, true); + } } } else if (position == addNew2Row) { actionCell.setText(LocaleController.getString("ChannelInviteViaLink", R.string.ChannelInviteViaLink), null, R.drawable.msg_panel_link, false); @@ -1585,7 +1607,7 @@ public class ChannelUsersActivity extends BaseFragment implements NotificationCe new ThemeDescription(listView, 0, new Class[]{ManageChatUserCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), new ThemeDescription(listView, 0, new Class[]{ManageChatUserCell.class}, new String[]{"statusColor"}, null, null, сellDelegate, Theme.key_windowBackgroundWhiteGrayText), new ThemeDescription(listView, 0, new Class[]{ManageChatUserCell.class}, new String[]{"statusOnlineColor"}, null, null, сellDelegate, Theme.key_windowBackgroundWhiteBlueText), - new ThemeDescription(listView, 0, new Class[]{ManageChatUserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(listView, 0, new Class[]{ManageChatUserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, null, Theme.key_avatar_text), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java index 2dbfa82c5..cf402308d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java @@ -17,6 +17,7 @@ import android.annotation.TargetApi; import android.app.Activity; import android.app.DatePickerDialog; import android.app.Dialog; +import android.content.ClipData; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -35,6 +36,7 @@ import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.media.ExifInterface; +import android.media.ThumbnailUtils; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -48,8 +50,10 @@ import android.text.Spanned; import android.text.TextUtils; import android.text.style.CharacterStyle; import android.text.style.ClickableSpan; +import android.text.style.ForegroundColorSpan; import android.text.style.URLSpan; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.util.TypedValue; import android.view.Gravity; @@ -76,6 +80,7 @@ import org.telegram.messenger.BuildConfig; import org.telegram.messenger.BuildVars; import org.telegram.messenger.ChatObject; import org.telegram.messenger.Emoji; +import org.telegram.messenger.EmojiSuggestion; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MediaController; import org.telegram.messenger.MessagesStorage; @@ -94,6 +99,7 @@ import org.telegram.messenger.query.MessagesQuery; import org.telegram.messenger.query.SearchQuery; import org.telegram.messenger.query.StickersQuery; import org.telegram.messenger.support.widget.GridLayoutManager; +import org.telegram.messenger.support.widget.GridLayoutManagerFixed; import org.telegram.messenger.support.widget.LinearLayoutManager; import org.telegram.messenger.support.widget.LinearSmoothScrollerMiddle; import org.telegram.messenger.support.widget.RecyclerView; @@ -174,8 +180,7 @@ import java.util.concurrent.Semaphore; import java.util.regex.Matcher; @SuppressWarnings("unchecked") -public class ChatActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, DialogsActivity.DialogsActivityDelegate, - PhotoViewer.PhotoViewerProvider { +public class ChatActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, DialogsActivity.DialogsActivityDelegate, LocationActivity.LocationActivityDelegate { protected TLRPC.Chat currentChat; protected TLRPC.User currentUser; @@ -196,7 +201,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private RadialProgressView progressBar; private TextView addContactItem; private RecyclerListView chatListView; - private LinearLayoutManager chatLayoutManager; + private GridLayoutManagerFixed chatLayoutManager; private ChatActivityAdapter chatAdapter; private TextView bottomOverlayChatText; private FrameLayout bottomOverlayChat; @@ -218,8 +223,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private FrameLayout stickersPanel; private TextView muteItem; private FrameLayout pagedownButton; + private ImageView pagedownButtonImage; private boolean pagedownButtonShowedByScroll; private TextView pagedownButtonCounter; + private FrameLayout mentiondownButton; + private TextView mentiondownButtonCounter; + private ImageView mentiondownButtonImage; private BackupImageView replyImageView; private SimpleTextView replyNameTextView; private SimpleTextView replyObjectTextView; @@ -239,9 +248,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private FrameLayout reportSpamContainer; private ImageView closeReportSpam; private FragmentContextView fragmentContextView; + private FragmentContextView fragmentLocationContextView; private View replyLineView; private TextView emptyView; - private ImageView pagedownButtonImage; private TextView gifHintTextView; private TextView mediaBanTooltip; private TextView voiceHintTextView; @@ -262,6 +271,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private AnimatorSet alertViewAnimator; private FrameLayout searchContainer; private ImageView searchCalendarButton; + private ImageView searchUserButton; private ImageView searchUpButton; private ImageView searchDownButton; private SimpleTextView searchCountText; @@ -273,11 +283,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private AnimatorSet floatingDateAnimation; private boolean scrollingFloatingDate; private boolean checkTextureViewPosition; + private boolean searchingForUser; + private TLRPC.User searchingUserMessages; private ArrayList animatingMessageObjects = new ArrayList<>(); private int scrollToPositionOnRecreate = -1; private int scrollToOffsetOnRecreate = 0; + private boolean chatListViewIgnoreLayout; private int topViewWasVisible; @@ -290,7 +303,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private MessageObject pinnedMessageObject; private int loadingPinnedMessage; - private ObjectAnimator pagedownButtonAnimation; + private AnimatorSet pagedownButtonAnimation; + private ObjectAnimator mentiondownButtonAnimation; private AnimatorSet replyButtonAnimation; private boolean openSearchKeyboard; @@ -305,11 +319,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private AnimatorSet runningAnimation; private MessageObject selectedObject; + private MessageObject.GroupedMessages selectedObjectGroup; private ArrayList forwardingMessages; - private MessageObject forwaringMessage; + private MessageObject forwardingMessage; + private MessageObject.GroupedMessages forwardingMessageGroup; private MessageObject replyingMessageObject; private int editingMessageObjectReqId; private boolean paused = true; + private boolean pausedOnLastMessage; private boolean wasPaused; private boolean readWhenResume; private TLRPC.FileLocation replyImageLocation; @@ -333,14 +350,22 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private boolean isBroadcast; private HashMap[] selectedMessagesIds = new HashMap[]{new HashMap<>(), new HashMap<>()}; private HashMap[] selectedMessagesCanCopyIds = new HashMap[]{new HashMap<>(), new HashMap<>()}; + private HashMap[] selectedMessagesCanStarIds = new HashMap[]{new HashMap<>(), new HashMap<>()}; + private boolean hasUnfavedSelected; private int cantDeleteMessagesCount; + private int canEditMessagesCount; private ArrayList waitingForLoad = new ArrayList<>(); private int newUnreadMessageCount; + private int newMentionsCount; + private boolean hasAllMentionsLocal; + + private boolean startReplyOnTextChange; private HashMap[] messagesDict = new HashMap[]{new HashMap<>(), new HashMap<>()}; private HashMap> messagesByDays = new HashMap<>(); protected ArrayList messages = new ArrayList<>(); + private HashMap groupedMessagesMap = new HashMap<>(); private int maxMessageId[] = new int[] {Integer.MAX_VALUE, Integer.MAX_VALUE}; private int minMessageId[] = new int[] {Integer.MIN_VALUE, Integer.MIN_VALUE}; private int maxDate[] = new int[] {Integer.MIN_VALUE, Integer.MIN_VALUE}; @@ -360,6 +385,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private int returnToMessageId; private int returnToLoadIndex; private int createUnreadMessageAfterId; + private boolean createUnreadMessageAfterIdLoading; private boolean loadingFromOldPosition; private boolean first = true; @@ -393,6 +419,66 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private Path aspectPath; private Paint aspectPaint; + private PhotoViewer.PhotoViewerProvider photoViewerProvider = new PhotoViewer.EmptyPhotoViewerProvider() { + + @Override + public PhotoViewer.PlaceProviderObject getPlaceForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { + int count = chatListView.getChildCount(); + + for (int a = 0; a < count; a++) { + ImageReceiver imageReceiver = null; + View view = chatListView.getChildAt(a); + if (view instanceof ChatMessageCell) { + if (messageObject != null) { + ChatMessageCell cell = (ChatMessageCell) view; + MessageObject message = cell.getMessageObject(); + if (message != null && message.getId() == messageObject.getId()) { + imageReceiver = cell.getPhotoImage(); + } + } + } else if (view instanceof ChatActionCell) { + ChatActionCell cell = (ChatActionCell) view; + MessageObject message = cell.getMessageObject(); + if (message != null) { + if (messageObject != null) { + if (message.getId() == messageObject.getId()) { + imageReceiver = cell.getPhotoImage(); + } + } else if (fileLocation != null && message.photoThumbs != null) { + for (int b = 0; b < message.photoThumbs.size(); b++) { + TLRPC.PhotoSize photoSize = message.photoThumbs.get(b); + if (photoSize.location.volume_id == fileLocation.volume_id && photoSize.location.local_id == fileLocation.local_id) { + imageReceiver = cell.getPhotoImage(); + break; + } + } + } + } + } + + if (imageReceiver != null) { + int coords[] = new int[2]; + view.getLocationInWindow(coords); + PhotoViewer.PlaceProviderObject object = new PhotoViewer.PlaceProviderObject(); + object.viewX = coords[0]; + object.viewY = coords[1] - (Build.VERSION.SDK_INT >= 21 ? 0 : AndroidUtilities.statusBarHeight); + object.parentView = chatListView; + object.imageReceiver = imageReceiver; + object.thumb = imageReceiver.getBitmap(); + object.radius = imageReceiver.getRoundRadius(); + if (view instanceof ChatActionCell && currentChat != null) { + object.dialogId = -currentChat.id; + } + if (pinnedMessageView != null && pinnedMessageView.getTag() == null || reportSpamView != null && reportSpamView.getTag() == null) { + object.clipTopAddition = AndroidUtilities.dp(48); + } + return object; + } + } + return null; + } + }; + private Runnable readRunnable = new Runnable() { @Override public void run() { @@ -413,7 +499,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not }; private ArrayList botContextResults; - private PhotoViewer.PhotoViewerProvider botContextProvider = new PhotoViewer.PhotoViewerProvider() { + private PhotoViewer.PhotoViewerProvider botContextProvider = new PhotoViewer.EmptyPhotoViewerProvider() { @Override public PhotoViewer.PlaceProviderObject getPlaceForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { @@ -449,36 +535,6 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return null; } - @Override - public Bitmap getThumbForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { - return null; - } - - @Override - public void willSwitchFromPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { - - } - - @Override - public void willHidePhotoViewer() { - - } - - @Override - public boolean isPhotoChecked(int index) { - return false; - } - - @Override - public void setPhotoChecked(int index, VideoEditedInfo videoEditedInfo) { - - } - - @Override - public boolean cancelButtonPressed() { - return false; - } - @Override public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo) { if (index < 0 || index >= botContextResults.size()) { @@ -486,26 +542,6 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } sendBotInlineResult((TLRPC.BotInlineResult) botContextResults.get(index)); } - - @Override - public int getSelectedCount() { - return 0; - } - - @Override - public void updatePhotoAtIndex(int index) { - - } - - @Override - public boolean scaleToFill() { - return false; - } - - @Override - public boolean allowCaption() { - return true; - } }; private final static int copy = 10; @@ -519,6 +555,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private final static int mute = 18; private final static int reply = 19; private final static int report = 21; + private final static int star = 22; + private final static int edit = 23; + private final static int add_shortcut = 24; private final static int bot_help = 30; private final static int bot_settings = 31; @@ -540,21 +579,25 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public boolean onItemClick(View view, int position) { if (!actionBar.isActionModeShowed()) { - createMenu(view, false); + createMenu(view, false, true); return true; } return false; } }; - RecyclerListView.OnItemClickListener onItemClickListener = new RecyclerListView.OnItemClickListener() { + RecyclerListView.OnItemClickListenerExtended onItemClickListener = new RecyclerListView.OnItemClickListenerExtended() { @Override - public void onItemClick(View view, int position) { + public void onItemClick(View view, int position, float x, float y) { if (actionBar.isActionModeShowed()) { - processRowSelect(view); + boolean outside = false; + if (view instanceof ChatMessageCell) { + outside = !((ChatMessageCell) view).isInsideBackground(x, y); + } + processRowSelect(view, outside); return; } - createMenu(view, true); + createMenu(view, true, false); } }; @@ -692,6 +735,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not NotificationCenter.getInstance().addObserver(this, NotificationCenter.closeChats); NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagesRead); NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagesDeleted); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.historyCleared); NotificationCenter.getInstance().addObserver(this, NotificationCenter.messageReceivedByServer); NotificationCenter.getInstance().addObserver(this, NotificationCenter.messageReceivedByAck); NotificationCenter.getInstance().addObserver(this, NotificationCenter.messageSendError); @@ -718,6 +762,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not NotificationCenter.getInstance().addObserver(this, NotificationCenter.botInfoDidLoaded); NotificationCenter.getInstance().addObserver(this, NotificationCenter.botKeyboardDidLoaded); NotificationCenter.getInstance().addObserver(this, NotificationCenter.chatSearchResultsAvailable); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.chatSearchResultsLoading); NotificationCenter.getInstance().addObserver(this, NotificationCenter.didUpdatedMessagesViews); NotificationCenter.getInstance().addObserver(this, NotificationCenter.chatInfoCantLoad); NotificationCenter.getInstance().addObserver(this, NotificationCenter.didLoadedPinnedMessage); @@ -726,6 +771,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not NotificationCenter.getInstance().addObserver(this, NotificationCenter.userInfoDidLoaded); NotificationCenter.getInstance().addObserver(this, NotificationCenter.didSetNewWallpapper); NotificationCenter.getInstance().addObserver(this, NotificationCenter.channelRightsUpdated); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.updateMentionsCount); super.onFragmentCreate(); @@ -824,6 +870,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not NotificationCenter.getInstance().removeObserver(this, NotificationCenter.closeChats); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagesRead); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagesDeleted); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.historyCleared); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messageReceivedByServer); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messageReceivedByAck); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messageSendError); @@ -849,6 +896,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not NotificationCenter.getInstance().removeObserver(this, NotificationCenter.botInfoDidLoaded); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.botKeyboardDidLoaded); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.chatSearchResultsAvailable); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.chatSearchResultsLoading); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagePlayingPlayStateChanged); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didUpdatedMessagesViews); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.chatInfoCantLoad); @@ -858,6 +906,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not NotificationCenter.getInstance().removeObserver(this, NotificationCenter.userInfoDidLoaded); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didSetNewWallpapper); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.channelRightsUpdated); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.updateMentionsCount); if (AndroidUtilities.isTablet()) { NotificationCenter.getInstance().postNotificationName(NotificationCenter.openedChatChanged, dialog_id, true); @@ -900,8 +949,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not for (int a = 1; a >= 0; a--) { selectedMessagesIds[a].clear(); selectedMessagesCanCopyIds[a].clear(); + selectedMessagesCanStarIds[a].clear(); } cantDeleteMessagesCount = 0; + canEditMessagesCount = 0; hasOwnBackground = true; if (chatAttachAlert != null) { @@ -928,8 +979,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not for (int a = 1; a >= 0; a--) { selectedMessagesIds[a].clear(); selectedMessagesCanCopyIds[a].clear(); + selectedMessagesCanStarIds[a].clear(); } cantDeleteMessagesCount = 0; + canEditMessagesCount = 0; if (chatActivityEnterView.isEditingMessage()) { chatActivityEnterView.setEditingMessageObject(null, false); } else { @@ -966,8 +1019,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not for (int a = 1; a >= 0; a--) { selectedMessagesIds[a].clear(); selectedMessagesCanCopyIds[a].clear(); + selectedMessagesCanStarIds[a].clear(); } cantDeleteMessagesCount = 0; + canEditMessagesCount = 0; actionBar.hideActionMode(); updatePinnedMessageView(true); updateVisibleRows(); @@ -975,10 +1030,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (getParentActivity() == null) { return; } - createDeleteMessagesAlert(null); + createDeleteMessagesAlert(null, null); } else if (id == forward) { Bundle args = new Bundle(); args.putBoolean("onlySelect", true); + args.putInt("dialogsType", 3); DialogsActivity fragment = new DialogsActivity(args); fragment.setDelegate(ChatActivity.this); presentFragment(fragment); @@ -1018,6 +1074,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } finishFragment(); } else { + if (ChatObject.isChannel(currentChat) && info != null && info.pinned_msg_id != 0) { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + preferences.edit().putInt("pin_" + dialog_id, info.pinned_msg_id).commit(); + updatePinnedMessageView(true); + } MessagesController.getInstance().deleteDialog(dialog_id, 1); } } @@ -1038,6 +1099,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } else if (id == mute) { toggleMute(false); + } else if (id == add_shortcut) { + try { + AndroidUtilities.installShortcut(currentUser.id); + } catch (Exception e) { + FileLog.e(e); + } } else if (id == report) { showDialog(AlertsCreator.createReportAlert(getParentActivity(), dialog_id, ChatActivity.this)); } else if (id == reply) { @@ -1049,14 +1116,50 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } selectedMessagesIds[a].clear(); selectedMessagesCanCopyIds[a].clear(); + selectedMessagesCanStarIds[a].clear(); } if (messageObject != null && (messageObject.messageOwner.id > 0 || messageObject.messageOwner.id < 0 && currentEncryptedChat != null)) { showReplyPanel(true, messageObject, null, null, false); } cantDeleteMessagesCount = 0; + canEditMessagesCount = 0; actionBar.hideActionMode(); updatePinnedMessageView(true); updateVisibleRows(); + } else if (id == star) { + for (int a = 0; a < 2; a++) { + for (HashMap.Entry entry : selectedMessagesCanStarIds[a].entrySet()) { + MessageObject msg = entry.getValue(); + StickersQuery.addRecentSticker(StickersQuery.TYPE_FAVE, msg.getDocument(), (int) (System.currentTimeMillis() / 1000), !hasUnfavedSelected); + } + } + for (int a = 1; a >= 0; a--) { + selectedMessagesIds[a].clear(); + selectedMessagesCanCopyIds[a].clear(); + selectedMessagesCanStarIds[a].clear(); + } + cantDeleteMessagesCount = 0; + canEditMessagesCount = 0; + actionBar.hideActionMode(); + updatePinnedMessageView(true); + updateVisibleRows(); + } else if (id == edit) { + MessageObject messageObject = null; + for (int a = 1; a >= 0; a--) { + if (messageObject == null && selectedMessagesIds[a].size() == 1) { + ArrayList ids = new ArrayList<>(selectedMessagesIds[a].keySet()); + messageObject = messagesDict[a].get(ids.get(0)); + } + selectedMessagesIds[a].clear(); + selectedMessagesCanCopyIds[a].clear(); + selectedMessagesCanStarIds[a].clear(); + } + startReplyOnTextChange = false; + startEditingMessageObject(messageObject); + cantDeleteMessagesCount = 0; + canEditMessagesCount = 0; + updatePinnedMessageView(true); + updateVisibleRows(); } else if (id == chat_menu_attach) { openAttachMenu(); } else if (id == bot_help) { @@ -1066,8 +1169,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else if (id == search) { openSearchWithText(null); } else if(id == call) { - if(currentUser!=null) - VoIPHelper.startCall(currentUser, getParentActivity(), MessagesController.getInstance().getUserFull(currentUser.id)); + if (currentUser != null && getParentActivity() != null) { + VoIPHelper.startCall(currentUser, getParentActivity(), MessagesController.getInstance().getUserFull(currentUser.id)); + } } } }); @@ -1092,8 +1196,23 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (currentEncryptedChat == null && !isBroadcast) { searchItem = menu.addItem(0, R.drawable.ic_ab_search).setIsSearchField(true).setActionBarMenuItemSearchListener(new ActionBarMenuItem.ActionBarMenuItemSearchListener() { + boolean searchWas; + @Override public void onSearchCollapse() { + searchCalendarButton.setVisibility(View.VISIBLE); + if (searchUserButton != null) { + searchUserButton.setVisibility(View.VISIBLE); + } + if (searchingForUser) { + mentionsAdapter.searchUsernameOrHashtag(null, 0, null, false); + searchingForUser = false; + } + mentionLayoutManager.setReverseLayout(false); + mentionsAdapter.setSearchingMentions(false); + searchingUserMessages = null; + searchItem.getSearchField().setHint(LocaleController.getString("Search", R.string.Search)); + searchItem.setSearchFieldCaption(null); avatarContainer.setVisibility(View.VISIBLE); if (chatActivityEnterView.hasText()) { if (headerItem != null) { @@ -1113,8 +1232,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not searchItem.setVisibility(View.GONE); highlightMessageId = Integer.MAX_VALUE; updateVisibleRows(); - scrollToLastMessage(false); + if (searchWas) { + scrollToLastMessage(false); + } updateBottomOverlay(); + updatePinnedMessageView(true); } @Override @@ -1125,6 +1247,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { + searchWas = false; searchItem.getSearchField().requestFocus(); AndroidUtilities.showKeyboard(searchItem.getSearchField()); } @@ -1133,8 +1256,34 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public void onSearchPressed(EditText editText) { - updateSearchButtons(0, 0, 0); - MessagesSearchQuery.searchMessagesInChat(editText.getText().toString(), dialog_id, mergeDialogId, classGuid, 0); + searchWas = true; + updateSearchButtons(0, 0, -1); + MessagesSearchQuery.searchMessagesInChat(editText.getText().toString(), dialog_id, mergeDialogId, classGuid, 0, searchingUserMessages); + } + + @Override + public void onTextChanged(EditText editText) { + if (searchingForUser) { + mentionsAdapter.searchUsernameOrHashtag("@" + editText.getText().toString(), 0, messages, true); + } else if (!searchingForUser && searchingUserMessages == null && searchUserButton != null && TextUtils.equals(editText.getText(), LocaleController.getString("SearchFrom", R.string.SearchFrom))) { + searchUserButton.callOnClick(); + } + } + + @Override + public void onCaptionCleared() { + if (searchingUserMessages != null) { + searchUserButton.callOnClick(); + } else { + if (searchingForUser) { + mentionsAdapter.searchUsernameOrHashtag(null, 0, null, false); + searchingForUser = false; + } + searchItem.getSearchField().setHint(LocaleController.getString("Search", R.string.Search)); + searchCalendarButton.setVisibility(View.VISIBLE); + searchUserButton.setVisibility(View.VISIBLE); + searchingUserMessages = null; + } } }); searchItem.getSearchField().setHint(LocaleController.getString("Search", R.string.Search)); @@ -1142,7 +1291,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } headerItem = menu.addItem(0, R.drawable.ic_ab_other); - if (currentUser != null && MessagesController.getInstance().callsEnabled) { + if (currentUser != null) { headerItem.addSubItem(call, LocaleController.getString("Call", R.string.Call)); TLRPC.TL_userFull userFull = MessagesController.getInstance().getUserFull(currentUser.id); if (userFull != null && userFull.phone_calls_available) { @@ -1163,8 +1312,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (currentEncryptedChat != null) { timeItem2 = headerItem.addSubItem(chat_enc_timer, LocaleController.getString("SetTimer", R.string.SetTimer)); } - if (!ChatObject.isChannel(currentChat)) { + if (!ChatObject.isChannel(currentChat) || currentChat != null && currentChat.megagroup && TextUtils.isEmpty(currentChat.username)) { headerItem.addSubItem(clear_history, LocaleController.getString("ClearHistory", R.string.ClearHistory)); + } + if (!ChatObject.isChannel(currentChat)) { if (currentChat != null && !isBroadcast) { headerItem.addSubItem(delete_chat, LocaleController.getString("DeleteAndExit", R.string.DeleteAndExit)); } else { @@ -1173,6 +1324,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } if (currentUser == null || !currentUser.self) { muteItem = headerItem.addSubItem(mute, null); + } else if (currentUser.self) { + headerItem.addSubItem(add_shortcut, LocaleController.getString("AddShortcut", R.string.AddShortcut)); } if (currentUser != null && currentEncryptedChat == null && currentUser.bot) { headerItem.addSubItem(bot_settings, LocaleController.getString("BotSettings", R.string.BotSettings)); @@ -1262,18 +1415,24 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not actionModeTitleContainer.addView(actionModeSubTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); if (currentEncryptedChat == null) { + actionModeViews.add(actionMode.addItemWithWidth(edit, R.drawable.group_edit, AndroidUtilities.dp(54))); if (!isBroadcast) { actionModeViews.add(actionMode.addItemWithWidth(reply, R.drawable.ic_ab_reply, AndroidUtilities.dp(54))); } + actionModeViews.add(actionMode.addItemWithWidth(star, R.drawable.ic_ab_fave, AndroidUtilities.dp(54))); actionModeViews.add(actionMode.addItemWithWidth(copy, R.drawable.ic_ab_copy, AndroidUtilities.dp(54))); actionModeViews.add(actionMode.addItemWithWidth(forward, R.drawable.ic_ab_forward, AndroidUtilities.dp(54))); actionModeViews.add(actionMode.addItemWithWidth(delete, R.drawable.ic_ab_delete, AndroidUtilities.dp(54))); } else { + actionModeViews.add(actionMode.addItemWithWidth(edit, R.drawable.group_edit, AndroidUtilities.dp(54))); actionModeViews.add(actionMode.addItemWithWidth(reply, R.drawable.ic_ab_reply, AndroidUtilities.dp(54))); + actionModeViews.add(actionMode.addItemWithWidth(star, R.drawable.ic_ab_fave, AndroidUtilities.dp(54))); actionModeViews.add(actionMode.addItemWithWidth(copy, R.drawable.ic_ab_copy, AndroidUtilities.dp(54))); actionModeViews.add(actionMode.addItemWithWidth(delete, R.drawable.ic_ab_delete, AndroidUtilities.dp(54))); } + actionMode.getItem(edit).setVisibility(canEditMessagesCount == 1 && selectedMessagesIds[0].size() + selectedMessagesIds[1].size() == 1 ? View.VISIBLE : View.GONE); actionMode.getItem(copy).setVisibility(selectedMessagesCanCopyIds[0].size() + selectedMessagesCanCopyIds[1].size() != 0 ? View.VISIBLE : View.GONE); + actionMode.getItem(star).setVisibility(selectedMessagesCanStarIds[0].size() + selectedMessagesCanStarIds[1].size() != 0 ? View.VISIBLE : View.GONE); actionMode.getItem(delete).setVisibility(cantDeleteMessagesCount == 0 ? View.VISIBLE : View.GONE); checkActionBarMenu(); @@ -1395,7 +1554,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } height = heightSize - chatActivityEnterView.getMeasuredHeight() + (maxHeight != 0 ? AndroidUtilities.dp(2) : 0); - mentionListView.setPadding(0, Math.max(0, height - AndroidUtilities.dp(Math.min(maxHeight, 68 * 1.8f))), 0, 0); + int padding = Math.max(0, height - AndroidUtilities.dp(Math.min(maxHeight, 68 * 1.8f))); + if (mentionLayoutManager.getReverseLayout()) { + mentionListView.setPadding(0, 0, 0, padding); + } else { + mentionListView.setPadding(0, padding, 0, 0); + } } else { int size = mentionsAdapter.getItemCount(); int maxHeight = 0; @@ -1409,7 +1573,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not maxHeight += size * 36; } height = heightSize - chatActivityEnterView.getMeasuredHeight() + (maxHeight != 0 ? AndroidUtilities.dp(2) : 0); - mentionListView.setPadding(0, Math.max(0, height - AndroidUtilities.dp(Math.min(maxHeight, 68 * 1.8f))), 0, 0); + int padding = Math.max(0, height - AndroidUtilities.dp(Math.min(maxHeight, 68 * 1.8f))); + if (mentionLayoutManager.getReverseLayout()) { + mentionListView.setPadding(0, 0, 0, padding); + } else { + mentionListView.setPadding(0, padding, 0, 0); + } } layoutParams.height = height; @@ -1485,6 +1654,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not childTop -= chatActivityEnterView.getMeasuredHeight() - AndroidUtilities.dp(2); } else if (child == pagedownButton) { childTop -= chatActivityEnterView.getMeasuredHeight(); + } else if (child == mentiondownButton) { + childTop -= chatActivityEnterView.getMeasuredHeight(); } else if (child == emptyViewContainer) { childTop -= inputFieldHeight / 2 - (actionBar.getVisibility() == VISIBLE ? actionBar.getMeasuredHeight() / 2 : 0); } else if (chatActivityEnterView.isPopupView(child)) { @@ -1534,7 +1705,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not emptyViewContainer.addView(bigEmptyView, new FrameLayout.LayoutParams(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER)); } else { emptyView = new TextView(context); - if (currentUser != null && currentUser.id != 777000 && currentUser.id != 429000 && currentUser.id != 4244000 && (currentUser.id / 1000 == 333 || currentUser.id % 1000 == 0)) { + if (currentUser != null && currentUser.id != 777000 && currentUser.id != 429000 && currentUser.id != 4244000 && MessagesController.isSupportId(currentUser.id)) { emptyView.setText(LocaleController.getString("GotAQuestion", R.string.GotAQuestion)); } else { emptyView.setText(LocaleController.getString("NoMessages", R.string.NoMessages)); @@ -1558,14 +1729,27 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not emptyViewContainer.addView(bigEmptyView, new FrameLayout.LayoutParams(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER)); } + CharSequence oldMessage; if (chatActivityEnterView != null) { chatActivityEnterView.onDestroy(); + if (!chatActivityEnterView.isEditingMessage()) { + oldMessage = chatActivityEnterView.getFieldText(); + } else { + oldMessage = null; + } + } else { + oldMessage = null; } if (mentionsAdapter != null) { mentionsAdapter.onDestroy(); } chatListView = new RecyclerListView(context) { + + ArrayList drawTimeAfter = new ArrayList<>(); + ArrayList drawNamesAfter = new ArrayList<>(); + ArrayList drawCaptionAfter = new ArrayList<>(); + @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); @@ -1586,18 +1770,174 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } + @Override + protected void onChildPressed(View child, boolean pressed) { + super.onChildPressed(child, pressed); + if (child instanceof ChatMessageCell) { + MessageObject.GroupedMessages groupedMessages = ((ChatMessageCell) child).getCurrentMessagesGroup(); + if (groupedMessages != null) { + int count = getChildCount(); + for (int a = 0; a < count; a++) { + View item = getChildAt(a); + if (item == child || !(item instanceof ChatMessageCell)) { + continue; + } + ChatMessageCell cell = (ChatMessageCell) item; + if (((ChatMessageCell) item).getCurrentMessagesGroup() == groupedMessages) { + cell.setPressed(pressed); + } + } + } + } + } + + @Override + public void requestLayout() { + if (chatListViewIgnoreLayout) { + return; + } + super.requestLayout(); + } + @Override public boolean drawChild(Canvas canvas, View child, long drawingTime) { + int clipLeft = 0; + int clipBottom = 0; + if (child instanceof ChatMessageCell) { + ChatMessageCell cell = (ChatMessageCell) child; + MessageObject.GroupedMessagePosition position = cell.getCurrentPosition(); + MessageObject.GroupedMessages group = cell.getCurrentMessagesGroup(); + if (position != null) { + if (position.pw != position.spanSize && position.spanSize == 1000 && position.siblingHeights == null && group.hasSibling) { + clipLeft = ((ChatMessageCell) child).getBackgroundDrawableLeft(); + } else if (position.siblingHeights != null) { + clipBottom = child.getBottom() - AndroidUtilities.dp(1 + (cell.isPinnedBottom() ? 1 : 0)); + } + } + } + if (clipLeft != 0) { + canvas.save(); + canvas.clipRect(clipLeft, child.getTop(), child.getRight(), child.getBottom()); + } else if (clipBottom != 0) { + canvas.save(); + canvas.clipRect(child.getLeft(), child.getTop(), child.getRight(), clipBottom); + } boolean result = super.drawChild(canvas, child, drawingTime); + if (clipLeft != 0 || clipBottom != 0) { + canvas.restore(); + } + int num = 0; + int count = getChildCount(); + for (int a = 0; a < count; a++) { + if (getChildAt(a) == child) { + num = a; + break; + } + } + if (num == count - 1) { + int size = drawTimeAfter.size(); + if (size > 0) { + for (int a = 0; a < size; a++) { + ChatMessageCell cell = drawTimeAfter.get(a); + canvas.save(); + canvas.translate(cell.getLeft(), cell.getTop()); + cell.drawTimeLayout(canvas); + canvas.restore(); + } + drawTimeAfter.clear(); + } + size = drawNamesAfter.size(); + if (size > 0) { + for (int a = 0; a < size; a++) { + ChatMessageCell cell = drawNamesAfter.get(a); + canvas.save(); + canvas.translate(cell.getLeft(), cell.getTop()); + cell.drawNamesLayout(canvas); + canvas.restore(); + } + drawNamesAfter.clear(); + } + size = drawCaptionAfter.size(); + if (size > 0) { + for (int a = 0; a < size; a++) { + ChatMessageCell cell = drawCaptionAfter.get(a); + canvas.save(); + canvas.translate(cell.getLeft(), cell.getTop()); + cell.drawCaptionLayout(canvas); + canvas.restore(); + } + drawCaptionAfter.clear(); + } + } if (child instanceof ChatMessageCell) { ChatMessageCell chatMessageCell = (ChatMessageCell) child; + + MessageObject.GroupedMessagePosition position = chatMessageCell.getCurrentPosition(); + if (position != null) { + if (position.last || position.minX == 0 && position.minY == 0) { + if (num == count - 1) { + canvas.save(); + canvas.translate(chatMessageCell.getLeft(), chatMessageCell.getTop()); + if (position.last) { + chatMessageCell.drawTimeLayout(canvas); + } + if (position.minX == 0 && position.minY == 0) { + chatMessageCell.drawNamesLayout(canvas); + } + canvas.restore(); + } else { + if (position.last) { + drawTimeAfter.add(chatMessageCell); + } + if (position.minX == 0 && position.minY == 0 && chatMessageCell.hasNameLayout()) { + drawNamesAfter.add(chatMessageCell); + } + } + } + if (num == count - 1) { + canvas.save(); + canvas.translate(chatMessageCell.getLeft(), chatMessageCell.getTop()); + if (chatMessageCell.hasCaptionLayout() && (position.flags & MessageObject.POSITION_FLAG_BOTTOM) != 0 && (position.flags & MessageObject.POSITION_FLAG_LEFT) != 0) { + chatMessageCell.drawCaptionLayout(canvas); + } + canvas.restore(); + } else { + if (chatMessageCell.hasCaptionLayout() && (position.flags & MessageObject.POSITION_FLAG_BOTTOM) != 0 && (position.flags & MessageObject.POSITION_FLAG_LEFT) != 0) { + drawCaptionAfter.add(chatMessageCell); + } + } + } ImageReceiver imageReceiver = chatMessageCell.getAvatarImage(); if (imageReceiver != null) { + MessageObject message = chatMessageCell.getMessageObject(); + + MessageObject.GroupedMessages groupedMessages = getValidGroupedMessage(message); + int top = child.getTop(); if (chatMessageCell.isPinnedBottom()) { ViewHolder holder = chatListView.getChildViewHolder(child); if (holder != null) { - holder = chatListView.findViewHolderForAdapterPosition(holder.getAdapterPosition() + 1); + int p = holder.getAdapterPosition(); + int nextPosition; + if (groupedMessages != null && position != null) { + int idx = groupedMessages.posArray.indexOf(position); + int size = groupedMessages.posArray.size(); + if ((position.flags & MessageObject.POSITION_FLAG_BOTTOM) != 0) { + nextPosition = p - size + idx; + } else { + nextPosition = p - 1; + for (int a = idx + 1; idx < size; a++) { + if (groupedMessages.posArray.get(a).minY > position.maxY) { + break; + } else { + nextPosition--; + } + } + } + } else { + nextPosition = p - 1; + } + holder = chatListView.findViewHolderForAdapterPosition(nextPosition); if (holder != null) { imageReceiver.setImageY(-AndroidUtilities.dp(1000)); imageReceiver.draw(canvas); @@ -1609,7 +1949,27 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not ViewHolder holder = chatListView.getChildViewHolder(child); if (holder != null) { while (true) { - holder = chatListView.findViewHolderForAdapterPosition(holder.getAdapterPosition() - 1); + int p = holder.getAdapterPosition(); + int prevPosition; + if (groupedMessages != null && position != null) { + int idx = groupedMessages.posArray.indexOf(position); + int size = groupedMessages.posArray.size(); + if ((position.flags & MessageObject.POSITION_FLAG_TOP) != 0) { + prevPosition = p + idx + 1; + } else { + prevPosition = p + 1; + for (int a = idx - 1; idx >= 0; a--) { + if (groupedMessages.posArray.get(a).maxY < position.minY) { + break; + } else { + prevPosition++; + } + } + } + } else { + prevPosition = p + 1; + } + holder = chatListView.findViewHolderForAdapterPosition(prevPosition); if (holder != null) { top = holder.itemView.getTop(); if (!(holder.itemView instanceof ChatMessageCell) || !((ChatMessageCell) holder.itemView).isPinnedTop()) { @@ -1622,7 +1982,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } int y = child.getTop() + chatMessageCell.getLayoutHeight(); - int maxY = chatListView.getHeight() - chatListView.getPaddingBottom(); + int maxY = chatListView.getMeasuredHeight() - chatListView.getPaddingBottom(); if (y > maxY) { y = maxY; } @@ -1643,7 +2003,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not chatListView.setPadding(0, AndroidUtilities.dp(4), 0, AndroidUtilities.dp(3)); chatListView.setItemAnimator(null); chatListView.setLayoutAnimation(null); - chatLayoutManager = new LinearLayoutManager(context) { + + chatLayoutManager = new GridLayoutManagerFixed(context, 1000, LinearLayoutManager.VERTICAL, true) { @Override public boolean supportsPredictiveItemAnimations() { return false; @@ -1655,10 +2016,93 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not linearSmoothScroller.setTargetPosition(position); startSmoothScroll(linearSmoothScroller); } + + @Override + public boolean shouldLayoutChildFromOpositeSide(View child) { + if (child instanceof ChatMessageCell) { + return !((ChatMessageCell) child).getMessageObject().isOutOwner(); + } + return false; + } + + @Override + protected boolean hasSiblingChild(int position) { + if (position >= chatAdapter.messagesStartRow && position < chatAdapter.messagesEndRow) { + int index = position - chatAdapter.messagesStartRow; + if (index >= 0 && index < messages.size()) { + MessageObject message = messages.get(index); + MessageObject.GroupedMessages group = getValidGroupedMessage(message); + if (group != null) { + MessageObject.GroupedMessagePosition pos = group.positions.get(message); + if (pos.minX == pos.maxX || pos.minY != pos.maxY || pos.minY == 0) { + return false; + } + int count = group.posArray.size(); + for (int a = 0; a < count; a++) { + MessageObject.GroupedMessagePosition p = group.posArray.get(a); + if (p == pos) { + continue; + } + if (p.minY <= pos.minY && p.maxY >= pos.minY) { + return true; + } + } + } + } + } + return false; + } }; - chatLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); - chatLayoutManager.setStackFromEnd(true); + chatLayoutManager.setSpanSizeLookup(new GridLayoutManagerFixed.SpanSizeLookup() { + @Override + public int getSpanSize(int position) { + if (position >= chatAdapter.messagesStartRow && position < chatAdapter.messagesEndRow) { + int idx = position - chatAdapter.messagesStartRow; + if (idx >= 0 && idx < messages.size()) { + MessageObject message = messages.get(idx); + MessageObject.GroupedMessages groupedMessages = getValidGroupedMessage(message); + if (groupedMessages != null) { + return groupedMessages.positions.get(message).spanSize; + } + } + } + return 1000; + } + }); chatListView.setLayoutManager(chatLayoutManager); + chatListView.addItemDecoration(new RecyclerView.ItemDecoration() { + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + outRect.bottom = 0; + if (view instanceof ChatMessageCell) { + ChatMessageCell cell = (ChatMessageCell) view; + MessageObject.GroupedMessages group = cell.getCurrentMessagesGroup(); + if (group != null) { + MessageObject.GroupedMessagePosition position = cell.getCurrentPosition(); + if (position != null && position.siblingHeights != null) { + float maxHeight = Math.max(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.5f; + int h = 0; + for (int a = 0; a < position.siblingHeights.length; a++) { + h += (int) Math.ceil(maxHeight * position.siblingHeights[a]); + } + h += (position.maxY - position.minY) * AndroidUtilities.dp(11); + int count = group.posArray.size(); + for (int a = 0; a < count; a++) { + MessageObject.GroupedMessagePosition pos = group.posArray.get(a); + if (pos.minY != position.minY || pos.minX == position.minX && pos.maxX == position.maxX && pos.minY == position.minY && pos.maxY == position.maxY) { + continue; + } + if (pos.minY == position.minY) { + h -= (int) Math.ceil(maxHeight * pos.ph) - AndroidUtilities.dp(4); + break; + } + } + outRect.bottom = -h; + } + } + } + } + }); contentView.addView(chatListView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); chatListView.setOnItemLongClickListener(onItemLongClickListener); chatListView.setOnItemClickListener(onItemClickListener); @@ -1708,10 +2152,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } checkScrollForLoad(true); int firstVisibleItem = chatLayoutManager.findFirstVisibleItemPosition(); - int visibleItemCount = firstVisibleItem == RecyclerView.NO_POSITION ? 0 : Math.abs(chatLayoutManager.findLastVisibleItemPosition() - firstVisibleItem) + 1; - if (visibleItemCount > 0) { + if (firstVisibleItem != RecyclerView.NO_POSITION) { int totalItemCount = chatAdapter.getItemCount(); - if (firstVisibleItem + visibleItemCount == totalItemCount && forwardEndReached[0]) { + if (firstVisibleItem == 0 && forwardEndReached[0]) { showPagedownButton(false, true); } else { if (dy > 0) { @@ -1821,7 +2264,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (getParentActivity() == null) { return; } - if (currentChat.creator || currentChat.admin_rights != null && currentChat.admin_rights.pin_messages) { + if (currentChat.creator || currentChat.admin_rights != null && (currentChat.megagroup && currentChat.admin_rights.pin_messages || !currentChat.megagroup && currentChat.admin_rights.edit_messages)) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setMessage(LocaleController.getString("UnpinMessageAlert", R.string.UnpinMessageAlert)); builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { @@ -1983,6 +2426,102 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } }); + mentiondownButton = new FrameLayout(context); + mentiondownButton.setVisibility(View.INVISIBLE); + contentView.addView(mentiondownButton, LayoutHelper.createFrame(46, 59, Gravity.RIGHT | Gravity.BOTTOM, 0, 0, 7, 5)); + mentiondownButton.setOnClickListener(new View.OnClickListener() { + + private void loadLastUnreadMention() { + if (hasAllMentionsLocal) { + MessagesStorage.getInstance().getUnreadMention(dialog_id, new MessagesStorage.IntCallback() { + @Override + public void run(int param) { + if (param == 0) { + hasAllMentionsLocal = false; + loadLastUnreadMention(); + } else { + scrollToMessageId(param, 0, false, 0, false); + } + } + }); + } else { + TLRPC.TL_messages_getUnreadMentions req = new TLRPC.TL_messages_getUnreadMentions(); + req.peer = MessagesController.getInputPeer((int) dialog_id); + req.limit = 1; + req.add_offset = newMentionsCount - 1; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, final TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + TLRPC.messages_Messages res = (TLRPC.messages_Messages) response; + if (error != null || res.messages.isEmpty()) { + if (res != null) { + newMentionsCount = res.count; + } else { + newMentionsCount = 0; + } + MessagesStorage.getInstance().resetMentionsCount(dialog_id, newMentionsCount); + if (newMentionsCount == 0) { + hasAllMentionsLocal = true; + showMentiondownButton(false, true); + } else { + mentiondownButtonCounter.setText(String.format("%d", newMentionsCount)); + loadLastUnreadMention(); + } + } else { + int id = res.messages.get(0).id; + long mid = id; + if (ChatObject.isChannel(currentChat)) { + mid = mid | (((long) currentChat.id) << 32); + } + MessageObject object = messagesDict[0].get(id); + MessagesStorage.getInstance().markMessageAsMention(mid); + if (object != null) { + object.messageOwner.media_unread = true; + object.messageOwner.mentioned = true; + } + scrollToMessageId(id, 0, false, 0, false); + } + } + }); + } + }); + } + } + + @Override + public void onClick(View view) { + loadLastUnreadMention(); + } + }); + + mentiondownButton.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + for (int a = 0; a < messages.size(); a++) { + MessageObject messageObject = messages.get(a); + if (messageObject.messageOwner.mentioned && !messageObject.isContentUnread()) { + messageObject.setContentIsRead(); + } + } + newMentionsCount = 0; + MessagesStorage.getInstance().resetMentionsCount(dialog_id, newMentionsCount); + hasAllMentionsLocal = true; + showMentiondownButton(false, true); + TLRPC.TL_messages_readMentions req = new TLRPC.TL_messages_readMentions(); + req.peer = MessagesController.getInputPeer((int) dialog_id); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + + } + }); + return true; + } + }); + if (!isBroadcast) { mentionContainer = new FrameLayout(context) { @@ -1991,17 +2530,24 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (mentionListView.getChildCount() <= 0) { return; } - int top; - if (mentionsAdapter.isBotContext() && mentionsAdapter.isMediaLayout() && mentionsAdapter.getBotContextSwitch() == null) { - top = mentionListViewScrollOffsetY - AndroidUtilities.dp(4); + if (mentionLayoutManager.getReverseLayout()) { + int top = mentionListViewScrollOffsetY + AndroidUtilities.dp(2); + int bottom = top + Theme.chat_composeShadowDrawable.getIntrinsicHeight(); + Theme.chat_composeShadowDrawable.setBounds(0, bottom, getMeasuredWidth(), top); + Theme.chat_composeShadowDrawable.draw(canvas); + canvas.drawRect(0, 0, getMeasuredWidth(), top, Theme.chat_composeBackgroundPaint); } else { - top = mentionListViewScrollOffsetY - AndroidUtilities.dp(2); + int top; + if (mentionsAdapter.isBotContext() && mentionsAdapter.isMediaLayout() && mentionsAdapter.getBotContextSwitch() == null) { + top = mentionListViewScrollOffsetY - AndroidUtilities.dp(4); + } else { + top = mentionListViewScrollOffsetY - AndroidUtilities.dp(2); + } + int bottom = top + Theme.chat_composeShadowDrawable.getIntrinsicHeight(); + Theme.chat_composeShadowDrawable.setBounds(0, top, getMeasuredWidth(), bottom); + Theme.chat_composeShadowDrawable.draw(canvas); + canvas.drawRect(0, bottom, getMeasuredWidth(), getMeasuredHeight(), Theme.chat_composeBackgroundPaint); } - - int bottom = top + Theme.chat_composeShadowDrawable.getIntrinsicHeight(); - Theme.chat_composeShadowDrawable.setBounds(0, top, getMeasuredWidth(), bottom); - Theme.chat_composeShadowDrawable.draw(canvas); - canvas.drawRect(0, bottom, getMeasuredWidth(), getMeasuredHeight(), Theme.chat_composeBackgroundPaint); } @Override @@ -2023,8 +2569,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public boolean onInterceptTouchEvent(MotionEvent event) { - if (!mentionListViewIsScrolling && mentionListViewScrollOffsetY != 0 && event.getY() < mentionListViewScrollOffsetY) { - return false; + if (mentionLayoutManager.getReverseLayout()) { + if (!mentionListViewIsScrolling && mentionListViewScrollOffsetY != 0 && event.getY() > mentionListViewScrollOffsetY) { + return false; + } + } else { + if (!mentionListViewIsScrolling && mentionListViewScrollOffsetY != 0 && event.getY() < mentionListViewScrollOffsetY) { + return false; + } } boolean result = StickerPreviewViewer.getInstance().onInterceptTouchEvent(event, mentionListView, 0, null); return super.onInterceptTouchEvent(event) || result; @@ -2032,8 +2584,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public boolean onTouchEvent(MotionEvent event) { - if (!mentionListViewIsScrolling && mentionListViewScrollOffsetY != 0 && event.getY() < mentionListViewScrollOffsetY) { - return false; + if (mentionLayoutManager.getReverseLayout()) { + if (!mentionListViewIsScrolling && mentionListViewScrollOffsetY != 0 && event.getY() > mentionListViewScrollOffsetY) { + return false; + } + } else { + if (!mentionListViewIsScrolling && mentionListViewScrollOffsetY != 0 && event.getY() < mentionListViewScrollOffsetY) { + return false; + } } //supress warning return super.onTouchEvent(event); @@ -2054,7 +2612,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not int newPosition = -1; int newTop = 0; - if (mentionListView != null && mentionListViewLastViewPosition >= 0 && width == lastWidth && height - lastHeight != 0) { + if (!mentionLayoutManager.getReverseLayout() && mentionListView != null && mentionListViewLastViewPosition >= 0 && width == lastWidth && height - lastHeight != 0) { newPosition = mentionListViewLastViewPosition; newTop = mentionListViewLastViewTop + height - lastHeight - getPaddingTop(); } @@ -2183,6 +2741,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else { mentionListView.setLayoutManager(mentionLayoutManager); } + if (show && bottomOverlay.getVisibility() == View.VISIBLE) { + show = false; + } if (show) { if (mentionListAnimation != null) { mentionListAnimation.cancel(); @@ -2315,26 +2876,44 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not int start = mentionsAdapter.getResultStartPosition(); int len = mentionsAdapter.getResultLength(); if (object instanceof TLRPC.User) { - TLRPC.User user = (TLRPC.User) object; - if (user != null) { - if (user.username != null) { - chatActivityEnterView.replaceWithText(start, len, "@" + user.username + " "); - } else { - String name = user.first_name; - if (name == null || name.length() == 0) { - name = user.last_name; + if (searchingForUser && searchContainer.getVisibility() == View.VISIBLE) { + searchingUserMessages = (TLRPC.User) object; + if (searchingUserMessages == null) { + return; + } + String name = searchingUserMessages.first_name; + if (TextUtils.isEmpty(name)) { + name = searchingUserMessages.last_name; + } + searchingForUser = false; + String from = LocaleController.getString("SearchFrom", R.string.SearchFrom); + Spannable spannable = new SpannableString(from + " " + name); + spannable.setSpan(new ForegroundColorSpan(Theme.getColor(Theme.key_actionBarDefaultSubtitle)), from.length() + 1, spannable.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + searchItem.setSearchFieldCaption(spannable); + mentionsAdapter.searchUsernameOrHashtag(null, 0, null, false); + searchItem.getSearchField().setHint(null); + searchItem.clearSearchText(); + MessagesSearchQuery.searchMessagesInChat("", dialog_id, mergeDialogId, classGuid, 0, searchingUserMessages); + } else { + TLRPC.User user = (TLRPC.User) object; + if (user != null) { + if (user.username != null) { + chatActivityEnterView.replaceWithText(start, len, "@" + user.username + " ", false); + } else { + String name = UserObject.getFirstName(user); + Spannable spannable = new SpannableString(name + " "); + spannable.setSpan(new URLSpanUserMention("" + user.id, true), 0, spannable.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + chatActivityEnterView.replaceWithText(start, len, spannable, false); } - Spannable spannable = new SpannableString(name + " "); - spannable.setSpan(new URLSpanUserMention("" + user.id, true), 0, spannable.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - chatActivityEnterView.replaceWithText(start, len, spannable); } } } else if (object instanceof String) { if (mentionsAdapter.isBotCommands()) { - SendMessagesHelper.getInstance().sendMessage((String) object, dialog_id, null, null, false, null, null, null); + SendMessagesHelper.getInstance().sendMessage((String) object, dialog_id, replyingMessageObject, null, false, null, null, null); chatActivityEnterView.setFieldText(""); + showReplyPanel(false, null, null, null, false); } else { - chatActivityEnterView.replaceWithText(start, len, object + " "); + chatActivityEnterView.replaceWithText(start, len, object + " ", false); } } else if (object instanceof TLRPC.BotInlineResult) { if (chatActivityEnterView.getFieldText() == null) { @@ -2352,6 +2931,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } else if (object instanceof TLRPC.TL_inlineBotSwitchPM) { processInlineBotContextPM((TLRPC.TL_inlineBotSwitchPM) object); + } else if (object instanceof EmojiSuggestion) { + String code = ((EmojiSuggestion) object).emoji; + chatActivityEnterView.addEmojiToRecent(code); + chatActivityEnterView.replaceWithText(start, len, code, true); } } }); @@ -2440,8 +3023,37 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not pagedownButtonCounter.setPadding(AndroidUtilities.dp(8), 0, AndroidUtilities.dp(8), AndroidUtilities.dp(1)); pagedownButton.addView(pagedownButtonCounter, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 23, Gravity.TOP | Gravity.CENTER_HORIZONTAL)); + mentiondownButtonImage = new ImageView(context); + mentiondownButtonImage.setImageResource(R.drawable.mentionbutton); + mentiondownButtonImage.setScaleType(ImageView.ScaleType.CENTER); + mentiondownButtonImage.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_goDownButtonIcon), PorterDuff.Mode.MULTIPLY)); + mentiondownButtonImage.setPadding(0, AndroidUtilities.dp(2), 0, 0); + drawable = Theme.createCircleDrawable(AndroidUtilities.dp(42), Theme.getColor(Theme.key_chat_goDownButton)); + shadowDrawable = context.getResources().getDrawable(R.drawable.pagedown_shadow).mutate(); + shadowDrawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_goDownButtonShadow), PorterDuff.Mode.MULTIPLY)); + combinedDrawable = new CombinedDrawable(shadowDrawable, drawable, 0, 0); + combinedDrawable.setIconSize(AndroidUtilities.dp(42), AndroidUtilities.dp(42)); + drawable = combinedDrawable; + mentiondownButtonImage.setBackgroundDrawable(drawable); + + mentiondownButton.addView(mentiondownButtonImage, LayoutHelper.createFrame(46, 46, Gravity.LEFT | Gravity.BOTTOM)); + + mentiondownButtonCounter = new TextView(context); + mentiondownButtonCounter.setVisibility(View.INVISIBLE); + mentiondownButtonCounter.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + mentiondownButtonCounter.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); + mentiondownButtonCounter.setTextColor(Theme.getColor(Theme.key_chat_goDownButtonCounter)); + mentiondownButtonCounter.setGravity(Gravity.CENTER); + mentiondownButtonCounter.setBackgroundDrawable(Theme.createRoundRectDrawable(AndroidUtilities.dp(11.5f), Theme.getColor(Theme.key_chat_goDownButtonCounterBackground))); + mentiondownButtonCounter.setMinWidth(AndroidUtilities.dp(23)); + mentiondownButtonCounter.setPadding(AndroidUtilities.dp(8), 0, AndroidUtilities.dp(8), AndroidUtilities.dp(1)); + mentiondownButton.addView(mentiondownButtonCounter, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 23, Gravity.TOP | Gravity.CENTER_HORIZONTAL)); + if (!AndroidUtilities.isTablet() || AndroidUtilities.isSmallTablet()) { - contentView.addView(fragmentContextView = new FragmentContextView(context, this), LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 39, Gravity.TOP | Gravity.LEFT, 0, -36, 0, 0)); + contentView.addView(fragmentLocationContextView = new FragmentContextView(context, this, true), LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 39, Gravity.TOP | Gravity.LEFT, 0, -36, 0, 0)); + contentView.addView(fragmentContextView = new FragmentContextView(context, this, false), LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 39, Gravity.TOP | Gravity.LEFT, 0, -36, 0, 0)); + fragmentContextView.setAdditionalContextView(fragmentLocationContextView); + fragmentLocationContextView.setAdditionalContextView(fragmentContextView); } contentView.addView(actionBar); @@ -2491,12 +3103,16 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public void onTextChanged(final CharSequence text, boolean bigChange) { - MediaController.getInstance().setInputFieldHasText(text != null && text.length() != 0 || chatActivityEnterView.isEditingMessage()); + if (startReplyOnTextChange && text.length() > 0) { + actionBar.getActionBarMenuOnItemClick().onItemClick(reply); + startReplyOnTextChange = false; + } + MediaController.getInstance().setInputFieldHasText(!TextUtils.isEmpty(text) || chatActivityEnterView.isEditingMessage()); if (stickersAdapter != null && !chatActivityEnterView.isEditingMessage() && ChatObject.canSendStickers(currentChat)) { stickersAdapter.loadStikersForEmoji(text); } if (mentionsAdapter != null) { - mentionsAdapter.searchUsernameOrHashtag(text.toString(), chatActivityEnterView.getCursorPosition(), messages); + mentionsAdapter.searchUsernameOrHashtag(text.toString(), chatActivityEnterView.getCursorPosition(), messages, false); } if (waitingForCharaterEnterRunnable != null) { AndroidUtilities.cancelRunOnUIThread(waitingForCharaterEnterRunnable); @@ -2665,6 +3281,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (pagedownButton != null) { pagedownButton.setTranslationY(translationY); } + if (mentiondownButton != null) { + mentiondownButton.setTranslationY(pagedownButton.getVisibility() != VISIBLE ? translationY : translationY - AndroidUtilities.dp(72)); + } } } @@ -2689,6 +3308,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (pagedownButton != null) { pagedownButton.setTranslationY(pagedownButton.getTag() == null ? AndroidUtilities.dp(100) : 0); } + if (mentiondownButton != null) { + mentiondownButton.setTranslationY(mentiondownButton.getTag() == null ? AndroidUtilities.dp(100) : (pagedownButton.getVisibility() == VISIBLE ? -AndroidUtilities.dp(72) : 0)); + } } } }; @@ -2781,6 +3403,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not canvas.drawRect(0, bottom, getMeasuredWidth(), getMeasuredHeight(), Theme.chat_composeBackgroundPaint); } }; + searchContainer.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + return true; + } + }); searchContainer.setWillNotDraw(false); searchContainer.setVisibility(View.INVISIBLE); searchContainer.setFocusable(true); @@ -2792,11 +3420,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not searchUpButton.setScaleType(ImageView.ScaleType.CENTER); searchUpButton.setImageResource(R.drawable.search_up); searchUpButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_searchPanelIcons), PorterDuff.Mode.MULTIPLY)); - searchContainer.addView(searchUpButton, LayoutHelper.createFrame(48, 48)); + searchContainer.addView(searchUpButton, LayoutHelper.createFrame(48, 48, Gravity.RIGHT | Gravity.TOP, 0, 0, 48, 0)); searchUpButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - MessagesSearchQuery.searchMessagesInChat(null, dialog_id, mergeDialogId, classGuid, 1); + MessagesSearchQuery.searchMessagesInChat(null, dialog_id, mergeDialogId, classGuid, 1, searchingUserMessages); } }); @@ -2804,19 +3432,42 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not searchDownButton.setScaleType(ImageView.ScaleType.CENTER); searchDownButton.setImageResource(R.drawable.search_down); searchDownButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_searchPanelIcons), PorterDuff.Mode.MULTIPLY)); - searchContainer.addView(searchDownButton, LayoutHelper.createFrame(48, 48, Gravity.LEFT | Gravity.TOP, 48, 0, 0, 0)); + searchContainer.addView(searchDownButton, LayoutHelper.createFrame(48, 48, Gravity.RIGHT | Gravity.TOP, 0, 0, 0, 0)); searchDownButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - MessagesSearchQuery.searchMessagesInChat(null, dialog_id, mergeDialogId, classGuid, 2); + MessagesSearchQuery.searchMessagesInChat(null, dialog_id, mergeDialogId, classGuid, 2, searchingUserMessages); } }); + if (currentChat != null && (!ChatObject.isChannel(currentChat) || currentChat.megagroup)) { + searchUserButton = new ImageView(context); + searchUserButton.setScaleType(ImageView.ScaleType.CENTER); + searchUserButton.setImageResource(R.drawable.usersearch); + searchUserButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_searchPanelIcons), PorterDuff.Mode.MULTIPLY)); + searchContainer.addView(searchUserButton, LayoutHelper.createFrame(48, 48, Gravity.LEFT | Gravity.TOP, 48, 0, 0, 0)); + searchUserButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mentionLayoutManager.setReverseLayout(true); + mentionsAdapter.setSearchingMentions(true); + searchCalendarButton.setVisibility(View.GONE); + searchUserButton.setVisibility(View.GONE); + searchingForUser = true; + searchingUserMessages = null; + searchItem.getSearchField().setHint(LocaleController.getString("SearchMembers", R.string.SearchMembers)); + searchItem.setSearchFieldCaption(LocaleController.getString("SearchFrom", R.string.SearchFrom)); + AndroidUtilities.showKeyboard(searchItem.getSearchField()); + searchItem.clearSearchText(); + } + }); + } + searchCalendarButton = new ImageView(context); searchCalendarButton.setScaleType(ImageView.ScaleType.CENTER); searchCalendarButton.setImageResource(R.drawable.search_calendar); searchCalendarButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_searchPanelIcons), PorterDuff.Mode.MULTIPLY)); - searchContainer.addView(searchCalendarButton, LayoutHelper.createFrame(48, 48, Gravity.RIGHT | Gravity.TOP)); + searchContainer.addView(searchCalendarButton, LayoutHelper.createFrame(48, 48, Gravity.LEFT | Gravity.TOP)); searchCalendarButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { @@ -2876,7 +3527,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not searchCountText.setTextColor(Theme.getColor(Theme.key_chat_searchPanelText)); searchCountText.setTextSize(15); searchCountText.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - searchContainer.addView(searchCountText, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.CENTER_VERTICAL, 108, 0, 0, 0)); + searchCountText.setGravity(Gravity.RIGHT); + searchContainer.addView(searchCountText, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.RIGHT | Gravity.CENTER_VERTICAL, 0, 0, 108, 0)); bottomOverlay = new FrameLayout(context) { @Override @@ -2898,6 +3550,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not bottomOverlayText = new TextView(context); bottomOverlayText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); bottomOverlayText.setGravity(Gravity.CENTER); + bottomOverlayText.setMaxLines(2); + bottomOverlayText.setEllipsize(TextUtils.TruncateAt.END); bottomOverlayText.setLineSpacing(AndroidUtilities.dp(2), 1); bottomOverlayText.setTextColor(Theme.getColor(Theme.key_chat_secretChatStatusText)); bottomOverlay.addView(bottomOverlayText, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER, 14, 0, 14, 0)); @@ -3009,6 +3663,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } catch (Throwable e) { FileLog.e(e); } + if (oldMessage != null) { + chatActivityEnterView.setFieldText(oldMessage); + } fixLayoutInternal(); return fragmentView; @@ -3124,20 +3781,40 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } View child = mentionListView.getChildAt(mentionListView.getChildCount() - 1); RecyclerListView.Holder holder = (RecyclerListView.Holder) mentionListView.findContainingViewHolder(child); - if (holder != null) { - mentionListViewLastViewPosition = holder.getAdapterPosition(); - mentionListViewLastViewTop = child.getTop(); - } else { - mentionListViewLastViewPosition = -1; - } + if (mentionLayoutManager.getReverseLayout()) { + if (holder != null) { + mentionListViewLastViewPosition = holder.getAdapterPosition(); + mentionListViewLastViewTop = child.getBottom(); + } else { + mentionListViewLastViewPosition = -1; + } - child = mentionListView.getChildAt(0); - holder = (RecyclerListView.Holder) mentionListView.findContainingViewHolder(child); - int newOffset = child.getTop() > 0 && holder != null && holder.getAdapterPosition() == 0 ? child.getTop() : 0; - if (mentionListViewScrollOffsetY != newOffset) { - mentionListView.setTopGlowOffset(mentionListViewScrollOffsetY = newOffset); - mentionListView.invalidate(); - mentionContainer.invalidate(); + child = mentionListView.getChildAt(0); + holder = (RecyclerListView.Holder) mentionListView.findContainingViewHolder(child); + int newOffset = child.getBottom() < mentionListView.getMeasuredHeight() && holder != null && holder.getAdapterPosition() == 0 ? child.getBottom() : mentionListView.getMeasuredHeight(); + if (mentionListViewScrollOffsetY != newOffset) { + mentionListView.setBottomGlowOffset(mentionListViewScrollOffsetY = newOffset); + mentionListView.setTopGlowOffset(0); + mentionListView.invalidate(); + mentionContainer.invalidate(); + } + } else { + if (holder != null) { + mentionListViewLastViewPosition = holder.getAdapterPosition(); + mentionListViewLastViewTop = child.getTop(); + } else { + mentionListViewLastViewPosition = -1; + } + + child = mentionListView.getChildAt(0); + holder = (RecyclerListView.Holder) mentionListView.findContainingViewHolder(child); + int newOffset = child.getTop() > 0 && holder != null && holder.getAdapterPosition() == 0 ? child.getTop() : 0; + if (mentionListViewScrollOffsetY != newOffset) { + mentionListView.setTopGlowOffset(mentionListViewScrollOffsetY = newOffset); + mentionListView.setBottomGlowOffset(0); + mentionListView.invalidate(); + mentionContainer.invalidate(); + } } } @@ -3159,6 +3836,17 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } + private MessageObject.GroupedMessages getValidGroupedMessage(MessageObject message) { + MessageObject.GroupedMessages groupedMessages = null; + if (message.getGroupId() != 0) { + groupedMessages = groupedMessagesMap.get(message.getGroupId()); + if (groupedMessages != null && (groupedMessages.messages.size() <= 1 || groupedMessages.positions.get(message) == null)) { + groupedMessages = null; + } + } + return groupedMessages; + } + private void jumpToDate(int date) { if (messages.isEmpty()) { return; @@ -3220,36 +3908,28 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } if (button == 7 || button == 4 && !chatAttachAlert.getSelectedPhotos().isEmpty()) { chatAttachAlert.dismiss(); - HashMap selectedPhotos = chatAttachAlert.getSelectedPhotos(); + HashMap selectedPhotos = chatAttachAlert.getSelectedPhotos(); + ArrayList selectedPhotosOrder = chatAttachAlert.getSelectedPhotosOrder(); if (!selectedPhotos.isEmpty()) { - ArrayList photos = new ArrayList<>(); - ArrayList captions = new ArrayList<>(); - ArrayList ttls = new ArrayList<>(); - ArrayList> masks = new ArrayList<>(); - for (HashMap.Entry entry : selectedPhotos.entrySet()) { - MediaController.PhotoEntry photoEntry = entry.getValue(); - if (photoEntry.isVideo) { - if (photoEntry.editedInfo != null) { - SendMessagesHelper.prepareSendingVideo(photoEntry.path, photoEntry.editedInfo.estimatedSize, photoEntry.editedInfo.estimatedDuration, photoEntry.editedInfo.resultWidth, photoEntry.editedInfo.resultHeight, photoEntry.editedInfo, dialog_id, replyingMessageObject, photoEntry.caption, photoEntry.ttl); - } else { - SendMessagesHelper.prepareSendingVideo(photoEntry.path, 0, 0, 0, 0, null, dialog_id, replyingMessageObject, photoEntry.caption, photoEntry.ttl); - } - continue; - } + ArrayList photos = new ArrayList<>(); + for (int a = 0; a < selectedPhotosOrder.size(); a++) { + MediaController.PhotoEntry photoEntry = (MediaController.PhotoEntry) selectedPhotos.get(selectedPhotosOrder.get(a)); + + SendMessagesHelper.SendingMediaInfo info = new SendMessagesHelper.SendingMediaInfo(); if (photoEntry.imagePath != null) { - photos.add(photoEntry.imagePath); - captions.add(photoEntry.caption != null ? photoEntry.caption.toString() : null); - masks.add(!photoEntry.stickers.isEmpty() ? new ArrayList<>(photoEntry.stickers) : null); - ttls.add(photoEntry.ttl); + info.path = photoEntry.imagePath; } else if (photoEntry.path != null) { - photos.add(photoEntry.path); - captions.add(photoEntry.caption != null ? photoEntry.caption.toString() : null); - masks.add(!photoEntry.stickers.isEmpty() ? new ArrayList<>(photoEntry.stickers) : null); - ttls.add(photoEntry.ttl); + info.path = photoEntry.path; } + info.isVideo = photoEntry.isVideo; + info.caption = photoEntry.caption != null ? photoEntry.caption.toString() : null; + info.masks = !photoEntry.stickers.isEmpty() ? new ArrayList<>(photoEntry.stickers) : null; + info.ttl = photoEntry.ttl; + info.videoEditedInfo = photoEntry.editedInfo; + photos.add(info); photoEntry.reset(); } - SendMessagesHelper.prepareSendingPhotos(photos, null, dialog_id, replyingMessageObject, captions, masks, null, button == 4, ttls); + SendMessagesHelper.prepareSendingMedia(photos, dialog_id, replyingMessageObject, null, button == 4, MediaController.getInstance().isGroupPhotosEnabled()); showReplyPanel(false, null, null, null, false); DraftQuery.cleanDraft(dialog_id, true); } @@ -3267,7 +3947,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public void didSelectBot(TLRPC.User user) { - if (chatActivityEnterView == null || user.username == null || user.username.length() == 0) { + if (chatActivityEnterView == null || TextUtils.isEmpty(user.username)) { return; } chatActivityEnterView.setFieldText("@" + user.username + " "); @@ -3301,7 +3981,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } for (int a = messages.size() - 1; a >= 0; a--) { MessageObject messageObject = messages.get(a); - if ((messageObject.isVoice() || messageObject.isRoundVideo()) && messageObject.isContentUnread() && !messageObject.isOut() && messageObject.messageOwner.to_id.channel_id == 0) { + if ((messageObject.isVoice() || messageObject.isRoundVideo()) && messageObject.isContentUnread() && !messageObject.isOut()) { MediaController.getInstance().setVoiceMessagesPlaylist(MediaController.getInstance().playMessage(messageObject) ? createVoiceMessagesPlaylist(messageObject, true) : null, true); return true; } @@ -3771,7 +4451,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } int firstVisibleItem = chatLayoutManager.findFirstVisibleItemPosition(); int visibleItemCount = firstVisibleItem == RecyclerView.NO_POSITION ? 0 : Math.abs(chatLayoutManager.findLastVisibleItemPosition() - firstVisibleItem) + 1; - if (visibleItemCount > 0) { + if (visibleItemCount > 0 || currentEncryptedChat != null) { int totalItemCount = chatAdapter.getItemCount(); int checkLoadCount; if (scroll) { @@ -3779,7 +4459,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else { checkLoadCount = 5; } - if (firstVisibleItem <= checkLoadCount && !loading) { + if (totalItemCount - firstVisibleItem - visibleItemCount <= checkLoadCount && !loading) { if (!endReached[0]) { loading = true; waitingForLoad.add(lastLoadIndex); @@ -3794,7 +4474,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not MessagesController.getInstance().loadMessages(mergeDialogId, 50, maxMessageId[1], 0, !cacheEndReached[1], minDate[1], classGuid, 0, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); } } - if (!loadingForward && firstVisibleItem + visibleItemCount >= totalItemCount - 10) { + if (visibleItemCount > 0 && !loadingForward && firstVisibleItem <= 10) { if (mergeDialogId != 0 && !forwardEndReached[1]) { waitingForLoad.add(lastLoadIndex); MessagesController.getInstance().loadMessages(mergeDialogId, 50, minMessageId[1], 0, true, maxDate[1], classGuid, 1, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); @@ -3867,17 +4547,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not PhotoAlbumPickerActivity fragment = new PhotoAlbumPickerActivity(false, currentEncryptedChat == null || AndroidUtilities.getPeerLayerVersion(currentEncryptedChat.layer) >= 46, true, ChatActivity.this); fragment.setDelegate(new PhotoAlbumPickerActivity.PhotoAlbumPickerActivityDelegate() { @Override - public void didSelectPhotos(ArrayList photos, ArrayList captions, ArrayList ttls, ArrayList videos, ArrayList> masks, ArrayList webPhotos) { - for (int a = 0; a < videos.size(); a++) { - MediaController.PhotoEntry video = videos.get(a); - if (video.editedInfo != null) { - SendMessagesHelper.prepareSendingVideo(video.path, video.editedInfo.estimatedSize, video.editedInfo.estimatedDuration, video.editedInfo.resultWidth, video.editedInfo.resultHeight, video.editedInfo, dialog_id, replyingMessageObject, video.caption, video.ttl); - } else { - SendMessagesHelper.prepareSendingVideo(video.path, 0, 0, 0, 0, null, dialog_id, replyingMessageObject, video.caption, video.ttl); - } - } - SendMessagesHelper.prepareSendingPhotos(photos, null, dialog_id, replyingMessageObject, captions, masks, null, false, ttls); - SendMessagesHelper.prepareSendingPhotosSearch(webPhotos, dialog_id, replyingMessageObject); + public void didSelectPhotos(ArrayList photos) { + SendMessagesHelper.prepareSendingMedia(photos, dialog_id, replyingMessageObject, null, false, MediaController.getInstance().isGroupPhotosEnabled()); showReplyPanel(false, null, null, null, false); DraftQuery.cleanDraft(dialog_id, true); } @@ -3929,19 +4600,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (!AndroidUtilities.isGoogleMapsInstalled(ChatActivity.this)) { return; } - LocationActivity fragment = new LocationActivity(); - fragment.setDelegate(new LocationActivity.LocationActivityDelegate() { - @Override - public void didSelectLocation(TLRPC.MessageMedia location) { - SendMessagesHelper.getInstance().sendMessage(location, dialog_id, replyingMessageObject, null, null); - moveScrollToLastMessage(); - showReplyPanel(false, null, null, null, false); - DraftQuery.cleanDraft(dialog_id, true); - if (paused) { - scrollToTopOnResume = true; - } - } - }); + LocationActivity fragment = new LocationActivity(currentEncryptedChat == null ? 1 : 0); + fragment.setDialogId(dialog_id); + fragment.setDelegate(this); presentFragment(fragment); } else if (which == attach_document) { if (Build.VERSION.SDK_INT >= 23 && getParentActivity().checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { @@ -3961,7 +4622,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public void startDocumentSelectActivity() { try { - Intent photoPickerIntent = new Intent(Intent.ACTION_PICK); + Intent photoPickerIntent = new Intent(Intent.ACTION_GET_CONTENT); + if (Build.VERSION.SDK_INT >= 18) { + photoPickerIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); + } photoPickerIntent.setType("*/*"); startActivityForResult(photoPickerIntent, 21); } catch (Exception e) { @@ -4192,8 +4856,16 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return; } if (searchItem != null && actionBar.isSearchFieldVisible()) { - actionBar.closeSearchField(); + actionBar.closeSearchField(false); chatActivityEnterView.setFieldFocused(); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (chatActivityEnterView != null) { + chatActivityEnterView.openKeyboard(); + } + } + }, 100); } boolean openKeyboard = false; if (messageObjectToReply != null && messageObjectToReply.getDialogId() != dialog_id) { @@ -4295,9 +4967,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not userNames.append(", "); } if (user != null) { - if (user.first_name != null && user.first_name.length() > 0) { + if (!TextUtils.isEmpty(user.first_name)) { userNames.append(user.first_name); - } else if (user.last_name != null && user.last_name.length() > 0) { + } else if (!TextUtils.isEmpty(user.last_name)) { userNames.append(user.last_name); } else { userNames.append(" "); @@ -4326,7 +4998,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not replyObjectTextView.setText(Emoji.replaceEmoji(mess, replyObjectTextView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(14), false)); } } else { - replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedMessage", messageObjectsToForward.size())); + replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedMessageCount", messageObjectsToForward.size())); } } else { if (type == 1) { @@ -4450,7 +5122,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private void moveScrollToLastMessage() { if (chatListView != null && !messages.isEmpty()) { - chatLayoutManager.scrollToPositionWithOffset(messages.size() - 1, -999999 - chatListView.getPaddingTop()); + chatLayoutManager.scrollToPositionWithOffset(0, 0); } } @@ -4471,6 +5143,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not messages.clear(); messagesByDays.clear(); waitingForLoad.clear(); + groupedMessagesMap.clear(); progressView.setVisibility(chatAdapter.botInfoRow == -1 ? View.VISIBLE : View.INVISIBLE); chatListView.setEmptyView(null); @@ -4496,19 +5169,21 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not waitingForReplyMessageLoad = false; startLoadFromMessageId = 0; last_message_id = 0; + unreadMessageObject = null; createUnreadMessageAfterId = 0; + createUnreadMessageAfterIdLoading = false; needSelectFromMessageId = false; chatAdapter.notifyDataSetChanged(); } private void scrollToLastMessage(boolean pagedown) { if (forwardEndReached[0] && first_unread_id == 0 && startLoadFromMessageId == 0) { - if (pagedown && chatLayoutManager.findLastCompletelyVisibleItemPosition() == chatAdapter.getItemCount() - 1) { + if (pagedown && chatLayoutManager.findFirstCompletelyVisibleItemPosition() == 0) { showPagedownButton(false, true); highlightMessageId = Integer.MAX_VALUE; updateVisibleRows(); } else { - chatLayoutManager.scrollToPositionWithOffset(messages.size() - 1, -999999 - chatListView.getPaddingTop()); + chatLayoutManager.scrollToPositionWithOffset(0, 0); } } else { clearChatData(); @@ -4518,6 +5193,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } private void updateTextureViewPosition() { + if (fragmentView == null) { + return; + } boolean foundTextureViewMessage = false; int count = chatListView.getChildCount(); int additionalTop = chatActivityEnterView.isTopViewVisible() ? AndroidUtilities.dp(48) : 0; @@ -4707,6 +5385,43 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } + private int getScrollOffsetForMessage(MessageObject object) { + int offset = Integer.MAX_VALUE; + MessageObject.GroupedMessages groupedMessages = getValidGroupedMessage(object); + if (groupedMessages != null) { + MessageObject.GroupedMessagePosition currentPosition = groupedMessages.positions.get(object); + float maxH = Math.max(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.5f; + + float itemHeight; + if (currentPosition.siblingHeights != null) { + itemHeight = currentPosition.siblingHeights[0]; + } else { + itemHeight = currentPosition.ph; + } + float totalHeight = 0.0f; + float moveDiff = 0.0f; + SparseBooleanArray array = new SparseBooleanArray(); + for (int a = 0; a < groupedMessages.posArray.size(); a++) { + MessageObject.GroupedMessagePosition pos = groupedMessages.posArray.get(a); + if (array.indexOfKey(pos.minY) < 0 && pos.siblingHeights == null) { + array.put(pos.minY, true); + if (pos.minY < currentPosition.minY) { + moveDiff -= pos.ph; + } else if (pos.minY > currentPosition.minY) { + moveDiff += pos.ph; + } + totalHeight += pos.ph; + } + } + if (Math.abs(totalHeight - itemHeight) < 0.02f) { + offset = (int) (chatListView.getMeasuredHeight() - totalHeight * maxH) / 2 - chatListView.getPaddingTop() - AndroidUtilities.dp(7); + } else { + offset = (int) (chatListView.getMeasuredHeight() - (itemHeight + moveDiff) * maxH) / 2 - chatListView.getPaddingTop() - AndroidUtilities.dp(7); + } + } + return Math.max(0, offset == Integer.MAX_VALUE ? (chatListView.getMeasuredHeight() - object.getApproximateHeight()) / 2 : offset); + } + public void scrollToMessageId(int id, int fromMessageId, boolean select, int loadIndex, boolean smooth) { MessageObject object = messagesDict[loadIndex].get(id); boolean query = false; @@ -4718,18 +5433,18 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else { highlightMessageId = Integer.MAX_VALUE; } - final int yOffset = Math.max(0, (chatListView.getHeight() - object.getApproximateHeight()) / 2); + int yOffset = getScrollOffsetForMessage(object); if (smooth) { if (messages.get(messages.size() - 1) == object) { - chatListView.smoothScrollToPosition(0); + chatListView.smoothScrollToPosition(chatAdapter.getItemCount() - 1); } else { - chatListView.smoothScrollToPosition(chatAdapter.messagesStartRow + messages.size() - messages.indexOf(object) - 1); + chatListView.smoothScrollToPosition(chatAdapter.messagesStartRow + messages.indexOf(object)); } } else { if (messages.get(messages.size() - 1) == object) { - chatLayoutManager.scrollToPositionWithOffset(0, -chatListView.getPaddingTop() - AndroidUtilities.dp(7) + yOffset); + chatLayoutManager.scrollToPositionWithOffset(chatAdapter.getItemCount() - 1, yOffset, false); } else { - chatLayoutManager.scrollToPositionWithOffset(chatAdapter.messagesStartRow + messages.size() - messages.indexOf(object) - 1, -AndroidUtilities.dp(7) + yOffset); + chatLayoutManager.scrollToPositionWithOffset(chatAdapter.messagesStartRow + messages.indexOf(object), yOffset, false); } } updateVisibleRows(); @@ -4780,6 +5495,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not highlightMessageId = Integer.MAX_VALUE; scrollToMessagePosition = -10000; startLoadFromMessageId = id; + if (id == createUnreadMessageAfterId) { + createUnreadMessageAfterIdLoading = true; + } waitingForLoad.add(lastLoadIndex); MessagesController.getInstance().loadMessages(loadIndex == 0 ? dialog_id : mergeDialogId, AndroidUtilities.isTablet() ? 30 : 20, startLoadFromMessageId, 0, true, 0, classGuid, 3, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); //emptyViewContainer.setVisibility(View.INVISIBLE); @@ -4806,7 +5524,15 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } pagedownButton.setVisibility(View.VISIBLE); pagedownButton.setTag(1); - pagedownButtonAnimation = ObjectAnimator.ofFloat(pagedownButton, "translationY", 0).setDuration(200); + pagedownButtonAnimation = new AnimatorSet(); + if (mentiondownButton.getVisibility() == View.VISIBLE) { + pagedownButtonAnimation.playTogether( + ObjectAnimator.ofFloat(pagedownButton, "translationY", 0), + ObjectAnimator.ofFloat(mentiondownButton, "translationY", -AndroidUtilities.dp(72))); + } else { + pagedownButtonAnimation.playTogether(ObjectAnimator.ofFloat(pagedownButton, "translationY", 0)); + } + pagedownButtonAnimation.setDuration(200); pagedownButtonAnimation.start(); } else { pagedownButton.setVisibility(View.VISIBLE); @@ -4822,7 +5548,15 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not pagedownButtonAnimation = null; } if (animated) { - pagedownButtonAnimation = ObjectAnimator.ofFloat(pagedownButton, "translationY", AndroidUtilities.dp(100)).setDuration(200); + pagedownButtonAnimation = new AnimatorSet(); + if (mentiondownButton.getVisibility() == View.VISIBLE) { + pagedownButtonAnimation.playTogether( + ObjectAnimator.ofFloat(pagedownButton, "translationY", AndroidUtilities.dp(100)), + ObjectAnimator.ofFloat(mentiondownButton, "translationY", 0)); + } else { + pagedownButtonAnimation.playTogether(ObjectAnimator.ofFloat(pagedownButton, "translationY", AndroidUtilities.dp(100))); + } + pagedownButtonAnimation.setDuration(200); pagedownButtonAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -4838,6 +5572,62 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } + private void showMentiondownButton(boolean show, boolean animated) { + if (mentiondownButton == null) { + return; + } + if (show) { + if (mentiondownButton.getTag() == null) { + if (mentiondownButtonAnimation != null) { + mentiondownButtonAnimation.cancel(); + mentiondownButtonAnimation = null; + } + if (animated) { + mentiondownButton.setVisibility(View.VISIBLE); + mentiondownButton.setTag(1); + if (pagedownButton.getVisibility() == View.VISIBLE) { + mentiondownButton.setTranslationY(-AndroidUtilities.dp(72)); + mentiondownButtonAnimation = ObjectAnimator.ofFloat(mentiondownButton, "alpha", 0.0f, 1.0f).setDuration(200); + } else { + if (mentiondownButton.getTranslationY() == 0) { + mentiondownButton.setTranslationY(AndroidUtilities.dp(100)); + } + mentiondownButtonAnimation = ObjectAnimator.ofFloat(mentiondownButton, "translationY", 0).setDuration(200); + } + mentiondownButtonAnimation.start(); + } else { + mentiondownButton.setVisibility(View.VISIBLE); + } + } + } else { + returnToMessageId = 0; + if (mentiondownButton.getTag() != null) { + mentiondownButton.setTag(null); + if (mentiondownButtonAnimation != null) { + mentiondownButtonAnimation.cancel(); + mentiondownButtonAnimation = null; + } + if (animated) { + if (pagedownButton.getVisibility() == View.VISIBLE) { + mentiondownButtonAnimation = ObjectAnimator.ofFloat(mentiondownButton, "alpha", 1.0f, 0.0f).setDuration(200); + } else { + mentiondownButtonAnimation = ObjectAnimator.ofFloat(mentiondownButton, "translationY", AndroidUtilities.dp(100)).setDuration(200); + } + mentiondownButtonAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mentiondownButtonCounter.setVisibility(View.INVISIBLE); + mentiondownButton.setVisibility(View.INVISIBLE); + } + }); + mentiondownButtonAnimation.start(); + } else { + mentiondownButton.setVisibility(View.INVISIBLE); + } + } + } + } + private void updateSecretStatus() { if (bottomOverlay == null) { return; @@ -4850,6 +5640,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not bottomOverlayText.setText(LocaleController.formatString("SendMessageRestricted", R.string.SendMessageRestricted, LocaleController.formatDateForBan(currentChat.banned_rights.until_date))); } bottomOverlay.setVisibility(View.VISIBLE); + if (mentionListAnimation != null) { + mentionListAnimation.cancel(); + mentionListAnimation = null; + } + mentionContainer.setVisibility(View.GONE); + mentionContainer.setTag(null); hideKeyboard = true; } else { if (currentEncryptedChat == null || bigEmptyView == null) { @@ -4993,9 +5789,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return 7; } } + return 9; } else if ((!messageObject.isRoundVideo() || messageObject.isRoundVideo() && BuildVars.DEBUG_VERSION) && (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaPhoto || messageObject.getDocument() != null || messageObject.isMusic() || messageObject.isVideo())) { boolean canSave = false; - if (messageObject.messageOwner.attachPath != null && messageObject.messageOwner.attachPath.length() != 0) { + if (!TextUtils.isEmpty(messageObject.messageOwner.attachPath)) { File f = new File(messageObject.messageOwner.attachPath); if (f.exists()) { canSave = true; @@ -5060,7 +5857,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } else if (!messageObject.isRoundVideo() && (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaPhoto || messageObject.getDocument() != null || messageObject.isMusic() || messageObject.isVideo())) { boolean canSave = false; - if (messageObject.messageOwner.attachPath != null && messageObject.messageOwner.attachPath.length() != 0) { + if (!TextUtils.isEmpty(messageObject.messageOwner.attachPath)) { File f = new File(messageObject.messageOwner.attachPath); if (f.exists()) { canSave = true; @@ -5093,13 +5890,52 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } - private void addToSelectedMessages(MessageObject messageObject) { + private void addToSelectedMessages(MessageObject messageObject, boolean outside) { + addToSelectedMessages(messageObject, outside, true); + } + + private void addToSelectedMessages(MessageObject messageObject, boolean outside, boolean last) { int index = messageObject.getDialogId() == dialog_id ? 0 : 1; + if (outside && messageObject.getGroupId() != 0) { + boolean hasUnselected = false; + MessageObject.GroupedMessages groupedMessages = groupedMessagesMap.get(messageObject.getGroupId()); + if (groupedMessages != null) { + int lastNum = 0; + for (int a = 0; a < groupedMessages.messages.size(); a++) { + MessageObject message = groupedMessages.messages.get(a); + if (!selectedMessagesIds[index].containsKey(message.getId())) { + hasUnselected = true; + lastNum = a; + } + } + + for (int a = 0; a < groupedMessages.messages.size(); a++) { + MessageObject message = groupedMessages.messages.get(a); + if (hasUnselected) { + if (!selectedMessagesIds[index].containsKey(message.getId())) { + addToSelectedMessages(message, false, a == lastNum); + } + } else { + addToSelectedMessages(message, false, a == groupedMessages.messages.size() - 1); + } + } + } + return; + } if (selectedMessagesIds[index].containsKey(messageObject.getId())) { selectedMessagesIds[index].remove(messageObject.getId()); if (messageObject.type == 0 || messageObject.caption != null) { selectedMessagesCanCopyIds[index].remove(messageObject.getId()); } + if (messageObject.isSticker()) { + selectedMessagesCanStarIds[index].remove(messageObject.getId()); + } + if (messageObject.canEditMessage(currentChat) && messageObject.getGroupId() != 0) { + MessageObject.GroupedMessages groupedMessages = groupedMessagesMap.get(messageObject.getGroupId()); + if (groupedMessages != null && groupedMessages.messages.size() > 1) { + canEditMessagesCount--; + } + } if (!messageObject.canDeleteMessage(currentChat)) { cantDeleteMessagesCount--; } @@ -5111,33 +5947,69 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (messageObject.type == 0 || messageObject.caption != null) { selectedMessagesCanCopyIds[index].put(messageObject.getId(), messageObject); } + if (messageObject.isSticker()) { + selectedMessagesCanStarIds[index].put(messageObject.getId(), messageObject); + } + if (messageObject.canEditMessage(currentChat) && messageObject.getGroupId() != 0) { + MessageObject.GroupedMessages groupedMessages = groupedMessagesMap.get(messageObject.getGroupId()); + if (groupedMessages != null && groupedMessages.messages.size() > 1) { + canEditMessagesCount++; + } + } if (!messageObject.canDeleteMessage(currentChat)) { cantDeleteMessagesCount++; } } - if (actionBar.isActionModeShowed()) { - if (selectedMessagesIds[0].isEmpty() && selectedMessagesIds[1].isEmpty()) { + if (last && actionBar.isActionModeShowed()) { + int selectedCount = selectedMessagesIds[0].size() + selectedMessagesIds[1].size(); + if (selectedCount == 0) { actionBar.hideActionMode(); updatePinnedMessageView(true); + startReplyOnTextChange = false; } else { - int copyVisible = actionBar.createActionMode().getItem(copy).getVisibility(); - actionBar.createActionMode().getItem(copy).setVisibility(selectedMessagesCanCopyIds[0].size() + selectedMessagesCanCopyIds[1].size() != 0 ? View.VISIBLE : View.GONE); - int newCopyVisible = actionBar.createActionMode().getItem(copy).getVisibility(); - actionBar.createActionMode().getItem(delete).setVisibility(cantDeleteMessagesCount == 0 ? View.VISIBLE : View.GONE); + ActionBarMenuItem copyItem = actionBar.createActionMode().getItem(copy); + ActionBarMenuItem starItem = actionBar.createActionMode().getItem(star); + ActionBarMenuItem editItem = actionBar.createActionMode().getItem(edit); final ActionBarMenuItem replyItem = actionBar.createActionMode().getItem(reply); + int copyVisible = copyItem.getVisibility(); + int starVisible = starItem.getVisibility(); + copyItem.setVisibility(selectedMessagesCanCopyIds[0].size() + selectedMessagesCanCopyIds[1].size() != 0 ? View.VISIBLE : View.GONE); + starItem.setVisibility(StickersQuery.canAddStickerToFavorites() && (selectedMessagesCanStarIds[0].size() + selectedMessagesCanStarIds[1].size()) == selectedCount ? View.VISIBLE : View.GONE); + int newCopyVisible = copyItem.getVisibility(); + int newStarVisible = starItem.getVisibility(); + actionBar.createActionMode().getItem(delete).setVisibility(cantDeleteMessagesCount == 0 ? View.VISIBLE : View.GONE); + if (editItem != null) { + editItem.setVisibility(canEditMessagesCount == 1 && selectedMessagesIds[0].size() + selectedMessagesIds[1].size() == 1 ? View.VISIBLE : View.GONE); + } + hasUnfavedSelected = false; + for (int a = 0; a < 2; a++) { + for (HashMap.Entry entry : selectedMessagesCanStarIds[a].entrySet()) { + MessageObject msg = entry.getValue(); + if (!StickersQuery.isStickerInFavorites(msg.getDocument())) { + hasUnfavedSelected = true; + break; + } + } + if (hasUnfavedSelected) { + break; + } + } + starItem.setIcon(hasUnfavedSelected ? R.drawable.ic_ab_fave : R.drawable.ic_ab_unfave); if (replyItem != null) { boolean allowChatActions = true; if (currentEncryptedChat != null && AndroidUtilities.getPeerLayerVersion(currentEncryptedChat.layer) < 46 || isBroadcast || + bottomOverlayChat != null && bottomOverlayChat.getVisibility() == View.VISIBLE || currentChat != null && (ChatObject.isNotInChat(currentChat) || ChatObject.isChannel(currentChat) && !ChatObject.canPost(currentChat) && !currentChat.megagroup || !ChatObject.canSendMessages(currentChat))) { allowChatActions = false; } final int newVisibility = allowChatActions && selectedMessagesIds[0].size() + selectedMessagesIds[1].size() == 1 ? View.VISIBLE : View.GONE; + startReplyOnTextChange = newVisibility == View.VISIBLE && !chatActivityEnterView.hasText(); if (replyItem.getVisibility() != newVisibility) { if (replyButtonAnimation != null) { replyButtonAnimation.cancel(); } - if (copyVisible != newCopyVisible) { + if (copyVisible != newCopyVisible || starVisible != newStarVisible) { if (newVisibility == View.VISIBLE) { replyItem.setAlpha(1.0f); replyItem.setScaleX(1.0f); @@ -5149,16 +6021,21 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else { replyButtonAnimation = new AnimatorSet(); replyItem.setPivotX(AndroidUtilities.dp(54)); + editItem.setPivotX(AndroidUtilities.dp(54)); if (newVisibility == View.VISIBLE) { replyItem.setVisibility(newVisibility); replyButtonAnimation.playTogether( ObjectAnimator.ofFloat(replyItem, "alpha", 1.0f), - ObjectAnimator.ofFloat(replyItem, "scaleX", 1.0f) + ObjectAnimator.ofFloat(replyItem, "scaleX", 1.0f), + ObjectAnimator.ofFloat(editItem, "alpha", 1.0f), + ObjectAnimator.ofFloat(editItem, "scaleX", 1.0f) ); } else { replyButtonAnimation.playTogether( ObjectAnimator.ofFloat(replyItem, "alpha", 0.0f), - ObjectAnimator.ofFloat(replyItem, "scaleX", 0.0f) + ObjectAnimator.ofFloat(replyItem, "scaleX", 0.0f), + ObjectAnimator.ofFloat(editItem, "alpha", 0.0f), + ObjectAnimator.ofFloat(editItem, "scaleX", 0.0f) ); } replyButtonAnimation.setDuration(100); @@ -5187,7 +6064,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } - private void processRowSelect(View view) { + private void processRowSelect(View view, boolean outside) { MessageObject message = null; if (view instanceof ChatMessageCell) { message = ((ChatMessageCell) view).getMessageObject(); @@ -5200,7 +6077,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (type < 2 || type == 20) { return; } - addToSelectedMessages(message); + addToSelectedMessages(message, outside); updateActionModeTitle(); updateVisibleRows(); } @@ -5222,9 +6099,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not avatarContainer.setTitle(currentChat.title); } else if (currentUser != null) { if (currentUser.self) { - avatarContainer.setTitle(LocaleController.getString("ChatYourSelfName", R.string.ChatYourSelfName)); - } else if (currentUser.id != 4244000 && currentUser.id / 1000 != 777 && currentUser.id / 1000 != 333 && ContactsController.getInstance().contactsDict.get(currentUser.id) == null && (ContactsController.getInstance().contactsDict.size() != 0 || !ContactsController.getInstance().isLoadingContacts())) { - if (currentUser.phone != null && currentUser.phone.length() != 0) { + avatarContainer.setTitle(LocaleController.getString("SavedMessages", R.string.SavedMessages)); + } else if (!MessagesController.isSupportId(currentUser.id) && ContactsController.getInstance().contactsDict.get(currentUser.id) == null && (ContactsController.getInstance().contactsDict.size() != 0 || !ContactsController.getInstance().isLoadingContacts())) { + if (!TextUtils.isEmpty(currentUser.phone)) { avatarContainer.setTitle(PhoneFormat.getInstance().format("+" + currentUser.phone)); } else { avatarContainer.setTitle(UserObject.getUserName(currentUser)); @@ -5303,40 +6180,35 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } - public boolean openVideoEditor(String videoPath, boolean removeLast, boolean animated) { - /*Bundle args = new Bundle(); TODO - args.putString("videoPath", videoPath); - VideoEditorActivity fragment = new VideoEditorActivity(args); - fragment.setDelegate(new VideoEditorActivity.VideoEditorActivityDelegate() { - @Override - public void didFinishEditVideo(String videoPath, long startTime, long endTime, int resultWidth, int resultHeight, int rotationValue, int originalWidth, int originalHeight, int bitrate, long estimatedSize, long estimatedDuration, String caption) { - VideoEditedInfo videoEditedInfo = new VideoEditedInfo(); - videoEditedInfo.startTime = startTime; - videoEditedInfo.endTime = endTime; - videoEditedInfo.rotationValue = rotationValue; - videoEditedInfo.originalWidth = originalWidth; - videoEditedInfo.originalHeight = originalHeight; - videoEditedInfo.bitrate = bitrate; - videoEditedInfo.resultWidth = resultWidth; - videoEditedInfo.resultHeight = resultHeight; - videoEditedInfo.originalPath = videoPath; - videoEditedInfo.muted = videoEditedInfo.bitrate == -1; - SendMessagesHelper.prepareSendingVideo(videoPath, estimatedSize, estimatedDuration, resultWidth, resultHeight, videoEditedInfo, dialog_id, replyingMessageObject, caption, 0); - showReplyPanel(false, null, null, null, false); - DraftQuery.cleanDraft(dialog_id, true); - } - });*/ + public void openVideoEditor(String videoPath, String caption) { + if (getParentActivity() != null) { + final Bitmap thumb = ThumbnailUtils.createVideoThumbnail(videoPath, MediaStore.Video.Thumbnails.MINI_KIND); + PhotoViewer.getInstance().setParentActivity(getParentActivity()); + final ArrayList cameraPhoto = new ArrayList<>(); + MediaController.PhotoEntry entry = new MediaController.PhotoEntry(0, 0, 0, videoPath, 0, true); + entry.caption = caption; + cameraPhoto.add(entry); + PhotoViewer.getInstance().openPhotoForSelect(cameraPhoto, 0, 2, new PhotoViewer.EmptyPhotoViewerProvider() { + @Override + public Bitmap getThumbForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { + return thumb; + } - //if (parentLayout == null || !fragment.onFragmentCreate()) { + @Override + public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo) { + sendMedia((MediaController.PhotoEntry) cameraPhoto.get(0), videoEditedInfo); + } + + @Override + public boolean canScrollAway() { + return false; + } + }, this); + } else { SendMessagesHelper.prepareSendingVideo(videoPath, 0, 0, 0, 0, null, dialog_id, replyingMessageObject, null, 0); showReplyPanel(false, null, null, null, false); DraftQuery.cleanDraft(dialog_id, true); - return false; - //} - //if (parentLayout.presentFragment(fragment, removeLast, !animated, true)) { - // fragment.setParentChatActivity(this); - //} - //return true; + } } private void showAttachmentError() { @@ -5347,6 +6219,37 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not toast.show(); } + private void sendUriAsDocument(Uri uri) { + if (uri == null) { + return; + } + String extractUriFrom = uri.toString(); + if (extractUriFrom.contains("com.google.android.apps.photos.contentprovider")) { + try { + String firstExtraction = extractUriFrom.split("/1/")[1]; + int index = firstExtraction.indexOf("/ACTUAL"); + if (index != -1) { + firstExtraction = firstExtraction.substring(0, index); + String secondExtraction = URLDecoder.decode(firstExtraction, "UTF-8"); + uri = Uri.parse(secondExtraction); + } + } catch (Exception e) { + FileLog.e(e); + } + } + String tempPath = AndroidUtilities.getPath(uri); + String originalPath = tempPath; + if (tempPath == null) { + originalPath = uri.toString(); + tempPath = MediaController.copyFileToCache(uri, "file"); + } + if (tempPath == null) { + showAttachmentError(); + return; + } + SendMessagesHelper.prepareSendingDocument(tempPath, originalPath, null, null, dialog_id, replyingMessageObject, null); + } + @Override public void onActivityResultFragment(int requestCode, int resultCode, Intent data) { if (resultCode == Activity.RESULT_OK) { @@ -5400,7 +6303,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (paused) { startVideoEdit = videoPath; } else { - openVideoEditor(videoPath, false, false); + openVideoEditor(videoPath, null); } } else { SendMessagesHelper.prepareSendingPhoto(null, uri, dialog_id, replyingMessageObject, null, null, null, 0); @@ -5421,7 +6324,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not FileLog.d("video record uri " + uri.toString()); videoPath = AndroidUtilities.getPath(uri); FileLog.d("resolved path = " + videoPath); - if (!(new File(videoPath).exists())) { + if (videoPath == null || !(new File(videoPath).exists())) { videoPath = currentPicturePath; } } else { @@ -5440,40 +6343,23 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (paused) { startVideoEdit = videoPath; } else { - openVideoEditor(videoPath, false, false); + openVideoEditor(videoPath, null); } } else if (requestCode == 21) { - if (data == null || data.getData() == null) { + if (data == null) { showAttachmentError(); return; } - Uri uri = data.getData(); - - String extractUriFrom = uri.toString(); - if (extractUriFrom.contains("com.google.android.apps.photos.contentprovider")) { - try { - String firstExtraction = extractUriFrom.split("/1/")[1]; - int index = firstExtraction.indexOf("/ACTUAL"); - if (index != -1) { - firstExtraction = firstExtraction.substring(0, index); - String secondExtraction = URLDecoder.decode(firstExtraction, "UTF-8"); - uri = Uri.parse(secondExtraction); - } - } catch (Exception e) { - FileLog.e(e); + if (data.getData() != null) { + sendUriAsDocument(data.getData()); + } else if (data.getClipData() != null) { + ClipData clipData = data.getClipData(); + for (int i = 0; i < clipData.getItemCount(); i++) { + sendUriAsDocument(clipData.getItemAt(i).getUri()); } - } - String tempPath = AndroidUtilities.getPath(uri); - String originalPath = tempPath; - if (tempPath == null) { - originalPath = data.toString(); - tempPath = MediaController.copyFileToCache(data.getData(), "file"); - } - if (tempPath == null) { + } else { showAttachmentError(); - return; } - SendMessagesHelper.prepareSendingDocument(tempPath, originalPath, null, null, dialog_id, replyingMessageObject, null); showReplyPanel(false, null, null, null, false); DraftQuery.cleanDraft(dialog_id, true); } else if (requestCode == 31) { @@ -5491,7 +6377,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not sent = true; String name = c.getString(0); String number = c.getString(1); - TLRPC.User user = new TLRPC.User(); + TLRPC.User user = new TLRPC.TL_user(); user.first_name = name; user.last_name = ""; user.phone = number; @@ -5533,6 +6419,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not first_unread_id = 0; last_message_id = 0; createUnreadMessageAfterId = 0; + createUnreadMessageAfterIdLoading = false; unread_to_load = 0; removeMessageObject(unreadMessageObject); unreadMessageObject = null; @@ -5554,36 +6441,41 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } int queryLoadIndex = (Integer) args[11]; int index = waitingForLoad.indexOf(queryLoadIndex); + int currentUserId = UserConfig.getClientUserId(); if (index == -1) { return; } else { waitingForLoad.remove(index); } ArrayList messArr = (ArrayList) args[2]; + boolean createUnreadLoading = false; if (waitingForReplyMessageLoad) { - boolean found = false; - for (int a = 0; a < messArr.size(); a++) { - MessageObject obj = messArr.get(a); - if (obj.getId() == startLoadFromMessageId) { - found = true; - break; - } - if (a + 1 < messArr.size()) { - MessageObject obj2 = messArr.get(a + 1); - if (obj.getId() >= startLoadFromMessageId && obj2.getId() < startLoadFromMessageId) { - startLoadFromMessageId = obj.getId(); + if (!createUnreadMessageAfterIdLoading) { + boolean found = false; + for (int a = 0; a < messArr.size(); a++) { + MessageObject obj = messArr.get(a); + if (obj.getId() == startLoadFromMessageId) { found = true; break; } + if (a + 1 < messArr.size()) { + MessageObject obj2 = messArr.get(a + 1); + if (obj.getId() >= startLoadFromMessageId && obj2.getId() < startLoadFromMessageId) { + startLoadFromMessageId = obj.getId(); + found = true; + break; + } + } + } + if (!found) { + startLoadFromMessageId = 0; + return; } - } - if (!found) { - startLoadFromMessageId = 0; - return; } int startLoadFrom = startLoadFromMessageId; boolean needSelect = needSelectFromMessageId; int unreadAfterId = createUnreadMessageAfterId; + createUnreadLoading = createUnreadMessageAfterIdLoading; clearChatData(); createUnreadMessageAfterId = unreadAfterId; startLoadFromMessageId = startLoadFrom; @@ -5599,6 +6491,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not int last_unread_date = (Integer) args[7]; int load_type = (Integer) args[8]; int loaded_max_id = (Integer) args[12]; + int loaded_mentions_count = (Integer) args[13]; + if (loaded_mentions_count < 0) { + loaded_mentions_count *= -1; + hasAllMentionsLocal = false; + } else if (first) { + hasAllMentionsLocal = true; + } if (load_type == 4) { startLoadFromMessageId = loaded_max_id; @@ -5611,6 +6510,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } boolean wasUnread = false; + boolean showUnreadCounter = false; if (fnid != 0) { last_message_id = (Integer) args[5]; if (load_type == 3) { @@ -5619,6 +6519,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (unread_to_load != 0) { createUnreadMessageAfterId = fnid; } + showUnreadCounter = true; loadingFromOldPosition = false; } first_unread_id = 0; @@ -5631,7 +6532,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } int newRowsCount = 0; - forwardEndReached[loadIndex] = startLoadFromMessageId == 0 && last_message_id == 0; + if (load_type != 0) { + forwardEndReached[loadIndex] = startLoadFromMessageId == 0 && last_message_id == 0; + } if ((load_type == 1 || load_type == 3) && loadIndex == 1) { endReached[0] = cacheEndReached[0] = true; forwardEndReached[0] = false; @@ -5646,6 +6549,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (!forwardEndReached[loadIndex]) { messages.clear(); messagesByDays.clear(); + groupedMessagesMap.clear(); for (int a = 0; a < 2; a++) { messagesDict[a].clear(); if (currentEncryptedChat == null) { @@ -5677,6 +6581,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not MessagesQuery.loadReplyMessagesForMessages(messArr, dialog_id); } int approximateHeightSum = 0; + if (load_type == 2 && messArr.isEmpty() && !isCache) { + forwardEndReached[0] = true; + } + HashMap newGroups = null; + HashMap changedGroups = null; for (int a = 0; a < messArr.size(); a++) { MessageObject obj = messArr.get(a); approximateHeightSum += obj.getApproximateHeight(); @@ -5684,7 +6593,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (currentUser.self) { obj.messageOwner.out = true; } - if (currentUser.bot && obj.isOut()) { + if (currentUser.bot && obj.isOut() || currentUser.id == currentUserId) { obj.setIsRead(); } } @@ -5712,6 +6621,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } + if (obj.getId() == last_message_id) { + forwardEndReached[loadIndex] = true; + } + if (obj.type < 0 || loadIndex == 1 && obj.messageOwner.action instanceof TLRPC.TL_messageActionChatMigrateTo) { continue; } @@ -5725,7 +6638,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (dayArray == null) { dayArray = new ArrayList<>(); messagesByDays.put(obj.dateKey, dayArray); - TLRPC.Message dateMsg = new TLRPC.Message(); + TLRPC.Message dateMsg = new TLRPC.TL_message(); dateMsg.message = LocaleController.formatDateChat(obj.messageOwner.date); dateMsg.id = 0; dateMsg.date = obj.messageOwner.date; @@ -5741,21 +6654,58 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not newRowsCount++; } + if (obj.hasValidGroupId()) { + MessageObject.GroupedMessages groupedMessages = groupedMessagesMap.get(obj.messageOwner.grouped_id); + if (groupedMessages != null) { + if (messages.size() > 1) { + MessageObject previous; + if (load_type == 1) { + previous = messages.get(0); + } else { + previous = messages.get(messages.size() - 2); + } + if (previous.messageOwner.grouped_id == obj.messageOwner.grouped_id) { + if (previous.localGroupId != 0) { + obj.localGroupId = previous.localGroupId; + groupedMessages = groupedMessagesMap.get(previous.localGroupId); + } + } else if (previous.messageOwner.grouped_id != obj.messageOwner.grouped_id) { + obj.localGroupId = Utilities.random.nextLong(); + groupedMessages = null; + } + } + } + if (groupedMessages == null) { + groupedMessages = new MessageObject.GroupedMessages(); + groupedMessages.groupId = obj.getGroupId(); + groupedMessagesMap.put(groupedMessages.groupId, groupedMessages); + } else if (newGroups == null || !newGroups.containsKey(obj.getGroupId())) { + if (changedGroups == null) { + changedGroups = new HashMap<>(); + } + changedGroups.put(obj.getGroupId(), groupedMessages); + } + if (newGroups == null) { + newGroups = new HashMap<>(); + } + newGroups.put(groupedMessages.groupId, groupedMessages); + if (load_type == 1) { + groupedMessages.messages.add(obj); + } else { + groupedMessages.messages.add(0, obj); + } + } else if (obj.messageOwner.grouped_id != 0) { + obj.messageOwner.grouped_id = 0; + } + newRowsCount++; + dayArray.add(obj); if (load_type == 1) { - dayArray.add(obj); messages.add(0, obj); - } - - if (load_type != 1) { - dayArray.add(obj); + } else { messages.add(messages.size() - 1, obj); } - if (obj.getId() == last_message_id) { - forwardEndReached[loadIndex] = true; - } - MessageObject prevObj; if (currentEncryptedChat == null) { if (createUnreadMessageAfterId != 0 && load_type != 1 && a + 1 < messArr.size()) { @@ -5778,7 +6728,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } if (load_type == 2 && obj.getId() == first_unread_id) { if (approximateHeightSum > AndroidUtilities.displaySize.y / 2 || !forwardEndReached[0]) { - TLRPC.Message dateMsg = new TLRPC.Message(); + TLRPC.Message dateMsg = new TLRPC.TL_message(); dateMsg.message = ""; dateMsg.id = 0; MessageObject dateObj = new MessageObject(dateMsg, null, false); @@ -5804,8 +6754,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } if (load_type != 2 && unreadMessageObject == null && createUnreadMessageAfterId != 0 && (currentEncryptedChat == null && !obj.isOut() && obj.getId() >= createUnreadMessageAfterId || currentEncryptedChat != null && !obj.isOut() && obj.getId() <= createUnreadMessageAfterId) && - (load_type == 1 || prevObj != null)) { - TLRPC.Message dateMsg = new TLRPC.Message(); + (load_type == 1 || prevObj != null || prevObj == null && createUnreadLoading && a == messArr.size() - 1)) { + TLRPC.Message dateMsg = new TLRPC.TL_message(); dateMsg.message = ""; dateMsg.id = 0; MessageObject dateObj = new MessageObject(dateMsg, null, false); @@ -5817,12 +6767,33 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not messages.add(messages.size() - 1, dateObj); } unreadMessageObject = dateObj; + if (load_type == 3) { + scrollToMessage = unreadMessageObject; + startLoadFromMessageId = 0; + scrollToMessagePosition = -9000; + } newRowsCount++; } } + if (createUnreadLoading) { + createUnreadMessageAfterId = 0; + } if (load_type == 0 && newRowsCount == 0) { loadsCount--; } + if (newGroups != null) { + for (HashMap.Entry entry : newGroups.entrySet()) { + MessageObject.GroupedMessages groupedMessages = entry.getValue(); + groupedMessages.calculate(); + if (chatAdapter != null && changedGroups != null && changedGroups.containsKey(entry.getKey())) { + MessageObject messageObject = groupedMessages.messages.get(groupedMessages.messages.size() - 1); + int idx = messages.indexOf(messageObject); + if (idx >= 0) { + chatAdapter.notifyItemRangeChanged(idx + chatAdapter.messagesStartRow, groupedMessages.messages.size()); + } + } + } + } if (forwardEndReached[loadIndex] && loadIndex != 1) { first_unread_id = 0; @@ -5831,31 +6802,29 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } if (load_type == 1) { - if (messArr.size() != count && (!isCache || currentEncryptedChat != null)) { + int rowsRemoved = 0; + if (messArr.size() != count && (!isCache || currentEncryptedChat != null || forwardEndReached[loadIndex])) { forwardEndReached[loadIndex] = true; if (loadIndex != 1) { first_unread_id = 0; last_message_id = 0; createUnreadMessageAfterId = 0; - chatAdapter.notifyItemRemoved(chatAdapter.getItemCount() - 1); - newRowsCount--; + chatAdapter.notifyItemRemoved(chatAdapter.loadingDownRow); + rowsRemoved++; } startLoadFromMessageId = 0; } if (newRowsCount > 0) { - int firstVisPos = chatLayoutManager.findLastVisibleItemPosition(); + int firstVisPos = chatLayoutManager.findFirstVisibleItemPosition(); int top = 0; - if (firstVisPos != chatLayoutManager.getItemCount() - 1) { - firstVisPos = RecyclerView.NO_POSITION; - } else { - View firstVisView = chatLayoutManager.findViewByPosition(firstVisPos); - top = ((firstVisView == null) ? 0 : firstVisView.getTop()) - chatListView.getPaddingTop(); + if (firstVisPos == 0) { + firstVisPos++; } - int insertStart = chatAdapter.getItemCount() - 1; - chatAdapter.notifyItemChanged(insertStart); - chatAdapter.notifyItemRangeInserted(insertStart, newRowsCount); + View firstVisView = chatLayoutManager.findViewByPosition(firstVisPos); + top = ((firstVisView == null) ? 0 : chatListView.getMeasuredHeight() - firstVisView.getBottom() - chatListView.getPaddingBottom()); + chatAdapter.notifyItemRangeInserted(1, newRowsCount); if (firstVisPos != RecyclerView.NO_POSITION) { - chatLayoutManager.scrollToPositionWithOffset(firstVisPos, top); + chatLayoutManager.scrollToPositionWithOffset(firstVisPos + newRowsCount - rowsRemoved, top); } } loadingForward = false; @@ -5869,7 +6838,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not cacheEndReached[loadIndex] = true; } } else if (load_type != 2 || messArr.size() == 0 && messages.isEmpty()) { - endReached[loadIndex] = true;//TODO if < 7 from unread + endReached[loadIndex] = true; } } loading = false; @@ -5880,27 +6849,30 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not chatAdapter.notifyDataSetChanged(); if (scrollToMessage != null) { int yOffset; + boolean bottom = true; if (startLoadFromMessageOffset != Integer.MAX_VALUE) { - yOffset = chatListView.getHeight() - startLoadFromMessageOffset + AndroidUtilities.dp(4); + yOffset = -startLoadFromMessageOffset - chatListView.getPaddingBottom(); startLoadFromMessageOffset = Integer.MAX_VALUE; } else if (scrollToMessagePosition == -9000) { - yOffset = Math.max(0, (chatListView.getHeight() - scrollToMessage.getApproximateHeight()) / 2); + yOffset = getScrollOffsetForMessage(scrollToMessage); + bottom = false; } else if (scrollToMessagePosition == -10000) { - yOffset = 0; + yOffset = -chatListView.getPaddingTop() - AndroidUtilities.dp(7); + bottom = false; } else { yOffset = scrollToMessagePosition; } if (!messages.isEmpty()) { if (messages.get(messages.size() - 1) == scrollToMessage || messages.get(messages.size() - 2) == scrollToMessage) { - chatLayoutManager.scrollToPositionWithOffset((chatAdapter.isBot ? 1 : 0), -chatListView.getPaddingTop() - AndroidUtilities.dp(7) + yOffset); + chatLayoutManager.scrollToPositionWithOffset(chatAdapter.loadingUpRow, yOffset, bottom); } else { - chatLayoutManager.scrollToPositionWithOffset(chatAdapter.messagesStartRow + messages.size() - messages.indexOf(scrollToMessage) - 1, -AndroidUtilities.dp(7) + yOffset - (scrollToMessage == unreadMessageObject ? chatListView.getPaddingTop() : 0)); + chatLayoutManager.scrollToPositionWithOffset(chatAdapter.messagesStartRow + messages.indexOf(scrollToMessage), yOffset, bottom); } } chatListView.invalidate(); if (scrollToMessagePosition == -10000 || scrollToMessagePosition == -9000) { showPagedownButton(true, true); - if (load_type == 3 && unread_to_load != 0) { + if (load_type == 3 && unread_to_load != 0 && showUnreadCounter) { pagedownButtonCounter.setVisibility(View.VISIBLE); pagedownButtonCounter.setText(String.format("%d", newUnreadMessageCount = unread_to_load)); } @@ -5910,26 +6882,32 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else { moveScrollToLastMessage(); } + if (loaded_mentions_count != 0) { + showMentiondownButton(true, true); + mentiondownButtonCounter.setVisibility(View.VISIBLE); + mentiondownButtonCounter.setText(String.format("%d", newMentionsCount = loaded_mentions_count)); + } } else { if (newRowsCount != 0) { boolean end = false; if (endReached[loadIndex] && (loadIndex == 0 && mergeDialogId == 0 || loadIndex == 1)) { end = true; - chatAdapter.notifyItemRangeChanged(chatAdapter.isBot ? 1 : 0, 2); + chatAdapter.notifyItemRangeChanged(chatAdapter.loadingUpRow - 1, 2); + chatAdapter.updateRows(); } - int firstVisPos = chatLayoutManager.findLastVisibleItemPosition(); + int firstVisPos = chatLayoutManager.findFirstVisibleItemPosition(); View firstVisView = chatLayoutManager.findViewByPosition(firstVisPos); - int top = ((firstVisView == null) ? 0 : firstVisView.getTop()) - chatListView.getPaddingTop(); + int top = ((firstVisView == null) ? 0 : chatListView.getMeasuredHeight() - firstVisView.getBottom() - chatListView.getPaddingBottom()); if (newRowsCount - (end ? 1 : 0) > 0) { - int insertStart = (chatAdapter.isBot ? 2 : 1) + (end ? 0 : 1); - chatAdapter.notifyItemChanged(insertStart); + int insertStart = chatAdapter.messagesEndRow;/* (chatAdapter.isBot ? 2 : 1) + (end ? 0 : 1); TODO check with bot*/ + chatAdapter.notifyItemChanged(chatAdapter.loadingUpRow); chatAdapter.notifyItemRangeInserted(insertStart, newRowsCount - (end ? 1 : 0)); } if (firstVisPos != -1) { - chatLayoutManager.scrollToPositionWithOffset(firstVisPos + newRowsCount - (end ? 1 : 0), top); + chatLayoutManager.scrollToPositionWithOffset(firstVisPos, top); } } else if (endReached[loadIndex] && (loadIndex == 0 && mergeDialogId == 0 || loadIndex == 1)) { - chatAdapter.notifyItemRemoved(chatAdapter.isBot ? 1 : 0); + chatAdapter.notifyItemRemoved(chatAdapter.loadingUpRow); } } @@ -6058,7 +7036,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else if (id == NotificationCenter.didReceivedNewMessages) { long did = (Long) args[0]; if (did == dialog_id) { - + int currentUserId = UserConfig.getClientUserId(); boolean updateChat = false; boolean hasFromMe = false; ArrayList arr = (ArrayList) args[1]; @@ -6080,8 +7058,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not for (int a = 0; a < arr.size(); a++) { MessageObject messageObject = arr.get(a); if (currentChat != null) { - if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionChatDeleteUser && messageObject.messageOwner.action.user_id == UserConfig.getClientUserId() || - messageObject.messageOwner.action instanceof TLRPC.TL_messageActionChatAddUser && messageObject.messageOwner.action.users.contains(UserConfig.getClientUserId())) { + if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionChatDeleteUser && messageObject.messageOwner.action.user_id == currentUserId || + messageObject.messageOwner.action instanceof TLRPC.TL_messageActionChatAddUser && messageObject.messageOwner.action.users.contains(currentUserId)) { TLRPC.Chat newChat = MessagesController.getInstance().getChat(currentChat.id); if (newChat != null) { currentChat = newChat; @@ -6100,6 +7078,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionPaymentSent) { messageObject.generatePaymentSentMessageText(null); } + if (messageObject.isMegagroup() && messageObject.replyMessageObject != null && messageObject.replyMessageObject.messageOwner != null) { + messageObject.replyMessageObject.messageOwner.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; + } } } else if (inlineReturn != 0) { if (messageObject.messageOwner.reply_markup != null) { @@ -6129,7 +7110,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not for (int a = 0; a < arr.size(); a++) { MessageObject obj = arr.get(a); - if (currentUser != null && currentUser.bot && obj.isOut()) { + if (currentUser != null && (currentUser.bot && obj.isOut() || currentUser.id == currentUserId)) { obj.setIsRead(); } if (avatarContainer != null && currentEncryptedChat != null && obj.messageOwner.action != null && obj.messageOwner.action instanceof TLRPC.TL_messageEncryptedAction && obj.messageOwner.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionSetMessageTTL) { @@ -6182,6 +7163,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not unread_to_load++; currentMarkAsRead = true; } + if (obj.messageOwner.mentioned && obj.isContentUnread()) { + newMentionsCount++; + } newUnreadMessageCount++; if (obj.type == 10 || obj.type == 11) { updateChat = true; @@ -6191,6 +7175,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not pagedownButtonCounter.setVisibility(View.VISIBLE); pagedownButtonCounter.setText(String.format("%d", newUnreadMessageCount)); } + if (newMentionsCount != 0 && mentiondownButtonCounter != null) { + mentiondownButtonCounter.setVisibility(View.VISIBLE); + mentiondownButtonCounter.setText(String.format("%d", newMentionsCount)); + showMentiondownButton(true, true); + } if (currentMarkAsRead) { if (paused) { @@ -6205,48 +7194,18 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } updateVisibleRows(); } else { + HashMap newGroups = null; boolean markAsRead = false; boolean unreadUpdated = true; - int oldCount = messages.size(); - int addedCount = 0; HashMap> webpagesToReload = null; - int placeToPaste = -1; + if (BuildVars.DEBUG_VERSION) { + FileLog.d("received new messages " + arr.size() + " in dialog " + dialog_id); + } for (int a = 0; a < arr.size(); a++) { + int placeToPaste = -1; MessageObject obj = arr.get(a); - if (a == 0) { - if (obj.messageOwner.id < 0) { - placeToPaste = 0; - if (obj.type == 5) { - animatingMessageObjects.add(obj); - } - } else { - if (!messages.isEmpty()) { - int size = messages.size(); - for (int b = 0; b < size; b++) { - MessageObject lastMessage = messages.get(b); - if (lastMessage.type >= 0 && lastMessage.messageOwner.date > 0) { - if (lastMessage.messageOwner.id > 0 && obj.messageOwner.id > 0) { - if (lastMessage.messageOwner.id < obj.messageOwner.id) { - placeToPaste = b; - break; - } - } else { - if (lastMessage.messageOwner.date < obj.messageOwner.date) { - placeToPaste = b; - break; - } - } - } - } - if (placeToPaste == -1 || placeToPaste > messages.size()) { - placeToPaste = messages.size(); - } - } else { - placeToPaste = 0; - } - } - } - if (currentUser != null && currentUser.bot && obj.isOut()) { + + if (currentUser != null && (currentUser.bot && obj.isOut() || currentUser.id == currentUserId)) { obj.setIsRead(); } if (avatarContainer != null && currentEncryptedChat != null && obj.messageOwner.action != null && obj.messageOwner.action instanceof TLRPC.TL_messageEncryptedAction && obj.messageOwner.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionSetMessageTTL) { @@ -6255,6 +7214,68 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (obj.type < 0 || messagesDict[0].containsKey(obj.getId())) { continue; } + + if (a == 0 && obj.messageOwner.id < 0 && obj.type == 5) { + animatingMessageObjects.add(obj); + } + + MessageObject.GroupedMessages groupedMessages; + if (obj.hasValidGroupId()) { + groupedMessages = groupedMessagesMap.get(obj.getGroupId()); + if (groupedMessages == null) { + groupedMessages = new MessageObject.GroupedMessages(); + groupedMessages.groupId = obj.getGroupId(); + groupedMessagesMap.put(groupedMessages.groupId, groupedMessages); + } + if (newGroups == null) { + newGroups = new HashMap<>(); + } + newGroups.put(groupedMessages.groupId, groupedMessages); + groupedMessages.messages.add(obj); + } else { + groupedMessages = null; + } + + if (groupedMessages != null) { + int size = groupedMessages.messages.size(); + MessageObject messageObject = size > 1 ? groupedMessages.messages.get(groupedMessages.messages.size() - 2) : null; + if (messageObject != null) { + placeToPaste = messages.indexOf(messageObject); + } + } + + if (placeToPaste == -1) { + if (obj.messageOwner.id < 0 || messages.isEmpty()) { + placeToPaste = 0; + } else { + int size = messages.size(); + for (int b = 0; b < size; b++) { + MessageObject lastMessage = messages.get(b); + if (lastMessage.type >= 0 && lastMessage.messageOwner.date > 0) { + if (lastMessage.messageOwner.id > 0 && obj.messageOwner.id > 0 && lastMessage.messageOwner.id < obj.messageOwner.id || lastMessage.messageOwner.date < obj.messageOwner.date) { + MessageObject.GroupedMessages lastGroupedMessages; + if (lastMessage.getGroupId() != 0) { + lastGroupedMessages = groupedMessagesMap.get(lastMessage.getGroupId()); + if (lastGroupedMessages != null && lastGroupedMessages.messages.size() == 0) { + lastGroupedMessages = null; + } + } else { + lastGroupedMessages = null; + } + if (lastGroupedMessages == null) { + placeToPaste = b; + } else { + placeToPaste = messages.indexOf(lastGroupedMessages.messages.get(lastGroupedMessages.messages.size() - 1)); + } + break; + } + } + } + if (placeToPaste == -1 || placeToPaste > messages.size()) { + placeToPaste = messages.size(); + } + } + } if (currentEncryptedChat != null && obj.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage && obj.messageOwner.media.webpage instanceof TLRPC.TL_webPageUrlPending) { if (webpagesToReload == null) { webpagesToReload = new HashMap<>(); @@ -6289,6 +7310,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not }, 1000); } }); + if (newGroups != null) { + for (HashMap.Entry entry : newGroups.entrySet()) { + entry.getValue().calculate(); + } + } return; } else if (currentChat != null && currentChat.megagroup && (obj.messageOwner.action instanceof TLRPC.TL_messageActionChatAddUser || obj.messageOwner.action instanceof TLRPC.TL_messageActionChatDeleteUser)) { reloadMegagroup = true; @@ -6315,7 +7341,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (dayArray == null) { dayArray = new ArrayList<>(); messagesByDays.put(obj.dateKey, dayArray); - TLRPC.Message dateMsg = new TLRPC.Message(); + TLRPC.Message dateMsg = new TLRPC.TL_message(); dateMsg.message = LocaleController.formatDateChat(obj.messageOwner.date); dateMsg.id = 0; dateMsg.date = obj.messageOwner.date; @@ -6324,7 +7350,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not dateObj.contentType = 1; dateObj.isDateObject = true; messages.add(placeToPaste, dateObj); - addedCount++; + if (chatAdapter != null) { + chatAdapter.notifyItemInserted(placeToPaste); + } } if (!obj.isOut()) { if (paused && placeToPaste == 0) { @@ -6336,20 +7364,22 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not unreadMessageObject = null; } if (unreadMessageObject == null) { - TLRPC.Message dateMsg = new TLRPC.Message(); + TLRPC.Message dateMsg = new TLRPC.TL_message(); dateMsg.message = ""; dateMsg.id = 0; MessageObject dateObj = new MessageObject(dateMsg, null, false); dateObj.type = 6; dateObj.contentType = 2; messages.add(0, dateObj); + if (chatAdapter != null) { + chatAdapter.notifyItemInserted(0); + } unreadMessageObject = dateObj; scrollToMessage = unreadMessageObject; scrollToMessagePosition = -10000; unreadUpdated = false; unread_to_load = 0; scrollToTopUnReadOnResume = true; - addedCount++; } } if (unreadMessageObject != null) { @@ -6368,8 +7398,15 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (placeToPaste > messages.size()) { placeToPaste = messages.size(); } + messages.add(placeToPaste, obj); - addedCount++; + if (chatAdapter != null) { + chatAdapter.notifyItemChanged(placeToPaste); + chatAdapter.notifyItemInserted(placeToPaste); + } + if (!obj.isOut() && obj.messageOwner.mentioned && obj.isContentUnread()) { + newMentionsCount++; + } newUnreadMessageCount++; if (obj.type == 10 || obj.type == 11) { updateChat = true; @@ -6378,6 +7415,20 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (webpagesToReload != null) { MessagesController.getInstance().reloadWebPages(dialog_id, webpagesToReload); } + if (newGroups != null) { + for (HashMap.Entry entry : newGroups.entrySet()) { + MessageObject.GroupedMessages groupedMessages = entry.getValue(); + int oldCount = groupedMessages.posArray.size(); + entry.getValue().calculate(); + int newCount = groupedMessages.posArray.size(); + if (newCount - oldCount > 0) { + int index = messages.indexOf(groupedMessages.messages.get(groupedMessages.messages.size() - 1)); + if (index >= 0) { + chatAdapter.notifyItemRangeChanged(index, newCount); + } + } + } + } if (progressView != null) { progressView.setVisibility(View.INVISIBLE); @@ -6386,27 +7437,16 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (unreadUpdated) { chatAdapter.updateRowWithMessageObject(unreadMessageObject); } - if (addedCount != 0) { - int insertStart = chatAdapter.getItemCount() - placeToPaste; - chatAdapter.notifyItemChanged(insertStart - 1); - chatAdapter.notifyItemRangeInserted(insertStart, addedCount); - } } else { scrollToTopOnResume = true; } if (chatListView != null && chatAdapter != null) { - int lastVisible = chatLayoutManager.findLastVisibleItemPosition(); + int lastVisible = chatLayoutManager.findFirstVisibleItemPosition(); if (lastVisible == RecyclerView.NO_POSITION) { lastVisible = 0; } - if (endReached[0]) { - lastVisible++; - } - if (chatAdapter.isBot) { - oldCount++; - } - if (lastVisible >= oldCount || hasFromMe) { + if (lastVisible == 0 || hasFromMe) { newUnreadMessageCount = 0; if (!firstLoading) { if (paused) { @@ -6423,6 +7463,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } showPagedownButton(true, true); } + if (newMentionsCount != 0 && mentiondownButtonCounter != null) { + mentiondownButtonCounter.setVisibility(View.VISIBLE); + mentiondownButtonCounter.setText(String.format("%d", newMentionsCount)); + showMentiondownButton(true, true); + } } else { scrollToTopOnResume = true; } @@ -6504,50 +7549,43 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (updated) { updateVisibleRows(); } - } else if (id == NotificationCenter.messagesDeleted) { - ArrayList markAsDeletedMessages = (ArrayList) args[0]; - int channelId = (Integer) args[1]; - int loadIndex = 0; - if (ChatObject.isChannel(currentChat)) { - if (channelId == 0 && mergeDialogId != 0) { - loadIndex = 1; - } else if (channelId == currentChat.id) { - loadIndex = 0; - } else { - return; - } - } else if (channelId != 0) { + } else if (id == NotificationCenter.historyCleared) { + long did = (Long) args[0]; + if (did != dialog_id) { return; } + int max_id = (Integer) args[1]; boolean updated = false; - for (int a = 0; a < markAsDeletedMessages.size(); a++) { - Integer ids = markAsDeletedMessages.get(a); - MessageObject obj = messagesDict[loadIndex].get(ids); - if (loadIndex == 0 && info != null && info.pinned_msg_id == ids) { + + for (int b = 0; b < messages.size(); b++) { + MessageObject obj = messages.get(b); + int mid = obj.getId(); + if (mid <= 0 || mid > max_id) { + continue; + } + if (info != null && info.pinned_msg_id == mid) { pinnedMessageObject = null; info.pinned_msg_id = 0; - MessagesStorage.getInstance().updateChannelPinnedMessage(channelId, 0); + MessagesStorage.getInstance().updateChannelPinnedMessage(info.id, 0); updatePinnedMessageView(true); } - if (obj != null) { - int index = messages.indexOf(obj); - if (index != -1) { - messages.remove(index); - messagesDict[loadIndex].remove(ids); - ArrayList dayArr = messagesByDays.get(obj.dateKey); - if (dayArr != null) { - dayArr.remove(obj); - if (dayArr.isEmpty()) { - messagesByDays.remove(obj.dateKey); - if (index >= 0 && index < messages.size()) { - messages.remove(index); - } - } + messages.remove(b); + b--; + messagesDict[0].remove(mid); + ArrayList dayArr = messagesByDays.get(obj.dateKey); + if (dayArr != null) { + dayArr.remove(obj); + if (dayArr.isEmpty()) { + messagesByDays.remove(obj.dateKey); + if (b >= 0 && b < messages.size()) { + messages.remove(b); + b--; } - updated = true; } } + updated = true; } + if (messages.isEmpty()) { if (!endReached[0] && !loading) { if (progressView != null) { @@ -6585,6 +7623,127 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not removeUnreadPlane(); chatAdapter.notifyDataSetChanged(); } + } else if (id == NotificationCenter.messagesDeleted) { + ArrayList markAsDeletedMessages = (ArrayList) args[0]; + int channelId = (Integer) args[1]; + int loadIndex = 0; + if (ChatObject.isChannel(currentChat)) { + if (channelId == 0 && mergeDialogId != 0) { + loadIndex = 1; + } else if (channelId == currentChat.id) { + loadIndex = 0; + } else { + return; + } + } else if (channelId != 0) { + return; + } + boolean updated = false; + HashMap newGroups = null; + for (int a = 0; a < markAsDeletedMessages.size(); a++) { + Integer ids = markAsDeletedMessages.get(a); + MessageObject obj = messagesDict[loadIndex].get(ids); + if (loadIndex == 0 && info != null && info.pinned_msg_id == ids) { + pinnedMessageObject = null; + info.pinned_msg_id = 0; + MessagesStorage.getInstance().updateChannelPinnedMessage(channelId, 0); + updatePinnedMessageView(true); + } + if (obj != null) { + int index = messages.indexOf(obj); + if (index != -1) { + MessageObject removed = messages.remove(index); + if (removed.getGroupId() != 0) { + MessageObject.GroupedMessages groupedMessages = groupedMessagesMap.get(removed.getGroupId()); + if (groupedMessages != null) { + if (newGroups == null) { + newGroups = new HashMap<>(); + } + newGroups.put(groupedMessages.groupId, groupedMessages); + groupedMessages.messages.remove(obj); + } + } + messagesDict[loadIndex].remove(ids); + ArrayList dayArr = messagesByDays.get(obj.dateKey); + if (dayArr != null) { + dayArr.remove(obj); + if (dayArr.isEmpty()) { + messagesByDays.remove(obj.dateKey); + if (index >= 0 && index < messages.size()) { + messages.remove(index); + } + } + } + updated = true; + } + } + } + if (newGroups != null) { + for (HashMap.Entry entry : newGroups.entrySet()) { + MessageObject.GroupedMessages groupedMessages = entry.getValue(); + if (groupedMessages.messages.isEmpty()) { + groupedMessagesMap.remove(groupedMessages.groupId); + } else { + groupedMessages.calculate(); + MessageObject messageObject = groupedMessages.messages.get(groupedMessages.messages.size() - 1); + int index = messages.indexOf(messageObject); + if (index >= 0) { + if (chatAdapter != null) { + chatAdapter.notifyItemRangeChanged(index + chatAdapter.messagesStartRow, groupedMessages.messages.size()); + } + } + } + } + } + if (messages.isEmpty()) { + if (!endReached[0] && !loading) { + if (progressView != null) { + progressView.setVisibility(View.INVISIBLE); + } + if (chatListView != null) { + chatListView.setEmptyView(null); + } + if (currentEncryptedChat == null) { + maxMessageId[0] = maxMessageId[1] = Integer.MAX_VALUE; + minMessageId[0] = minMessageId[1] = Integer.MIN_VALUE; + } else { + maxMessageId[0] = maxMessageId[1] = Integer.MIN_VALUE; + minMessageId[0] = minMessageId[1] = Integer.MAX_VALUE; + } + maxDate[0] = maxDate[1] = Integer.MIN_VALUE; + minDate[0] = minDate[1] = 0; + waitingForLoad.add(lastLoadIndex); + MessagesController.getInstance().loadMessages(dialog_id, 30, 0, 0, !cacheEndReached[0], minDate[0], classGuid, 0, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + loading = true; + } else { + if (botButtons != null) { + botButtons = null; + if (chatActivityEnterView != null) { + chatActivityEnterView.setButtons(null, false); + } + } + if (currentEncryptedChat == null && currentUser != null && currentUser.bot && botUser == null) { + botUser = ""; + updateBottomOverlay(); + } + } + } + if (chatAdapter != null) { + if (updated) { + removeUnreadPlane(); + chatAdapter.notifyDataSetChanged(); + } else { + first_unread_id = 0; + last_message_id = 0; + createUnreadMessageAfterId = 0; + unread_to_load = 0; + removeMessageObject(unreadMessageObject); + unreadMessageObject = null; + if (pagedownButtonCounter != null) { + pagedownButtonCounter.setVisibility(View.INVISIBLE); + } + } + } } else if (id == NotificationCenter.messageReceivedByServer) { Integer msgId = (Integer) args[0]; MessageObject obj = messagesDict[0].get(msgId); @@ -6621,9 +7780,16 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } catch (Exception e) { FileLog.e(e); } + if (obj.getGroupId() != 0 && newMsgObj.grouped_id != 0) { + MessageObject.GroupedMessages oldGroup = groupedMessagesMap.get(obj.getGroupId()); + if (oldGroup != null) { + groupedMessagesMap.put(newMsgObj.grouped_id, oldGroup); + } + } obj.messageOwner = newMsgObj; obj.generateThumbs(true); obj.setType(); + if (newMsgObj.media instanceof TLRPC.TL_messageMediaGame) { obj.applyNewText(); } @@ -6645,7 +7811,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not chatAdapter.updateRowWithMessageObject(obj); } if (chatLayoutManager != null) { - if (mediaUpdated && chatLayoutManager.findLastVisibleItemPosition() >= messages.size() - 1) { + if (mediaUpdated && chatLayoutManager.findFirstVisibleItemPosition() == 0) { moveScrollToLastMessage(); } } @@ -6687,6 +7853,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } info = chatFull; + if (chatActivityEnterView != null) { + chatActivityEnterView.setChatInfo(info); + } if (mentionsAdapter != null) { mentionsAdapter.setChatInfo(info); } @@ -6826,6 +7995,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not messages.clear(); waitingForLoad.clear(); messagesByDays.clear(); + groupedMessagesMap.clear(); for (int a = 1; a >= 0; a--) { messagesDict[a].clear(); if (currentEncryptedChat == null) { @@ -6839,8 +8009,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not minDate[a] = 0; selectedMessagesIds[a].clear(); selectedMessagesCanCopyIds[a].clear(); + selectedMessagesCanStarIds[a].clear(); } cantDeleteMessagesCount = 0; + canEditMessagesCount = 0; actionBar.hideActionMode(); updatePinnedMessageView(true); @@ -6850,10 +8022,6 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not chatActivityEnterView.setButtons(null, false); } } - if (currentEncryptedChat == null && currentUser != null && currentUser.bot && botUser == null) { - botUser = ""; - updateBottomOverlay(); - } if ((Boolean) args[1]) { if (chatAdapter != null) { progressView.setVisibility(chatAdapter.botInfoRow == -1 ? View.VISIBLE : View.INVISIBLE); @@ -6881,6 +8049,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (chatAdapter != null) { chatAdapter.notifyDataSetChanged(); } + if (currentEncryptedChat == null && currentUser != null && currentUser.bot && botUser == null) { + botUser = ""; + updateBottomOverlay(); + } } } else if (id == NotificationCenter.screenshotTook) { updateInformationForScreenshotDetector(); @@ -7031,6 +8203,43 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not existMessageObject.messageOwner.media = message.media; existMessageObject.messageOwner.attachPath = message.attachPath; existMessageObject.generateThumbs(false); + if (existMessageObject.getGroupId() != 0 && (existMessageObject.photoThumbs == null || existMessageObject.photoThumbs.isEmpty())) { + MessageObject.GroupedMessages groupedMessages = groupedMessagesMap.get(existMessageObject.getGroupId()); + if (groupedMessages != null) { + int idx = groupedMessages.messages.indexOf(existMessageObject); + if (idx >= 0) { + int updateCount = groupedMessages.messages.size(); + MessageObject messageObject = null; + if (idx > 0 && idx < groupedMessages.messages.size() - 1) { + MessageObject.GroupedMessages slicedGroup = new MessageObject.GroupedMessages(); + slicedGroup.groupId = Utilities.random.nextLong(); + slicedGroup.messages.addAll(groupedMessages.messages.subList(idx + 1, groupedMessages.messages.size())); + for (int b = 0; b < slicedGroup.messages.size(); b++) { + slicedGroup.messages.get(b).localGroupId = slicedGroup.groupId; + groupedMessages.messages.remove(idx + 1); + } + groupedMessagesMap.put(slicedGroup.groupId, slicedGroup); + messageObject = slicedGroup.messages.get(slicedGroup.messages.size() - 1); + slicedGroup.calculate(); + } + groupedMessages.messages.remove(idx); + if (messageObject == null) { + messageObject = groupedMessages.messages.get(groupedMessages.messages.size() - 1); + } + if (groupedMessages.messages.isEmpty()) { + groupedMessagesMap.remove(groupedMessages.groupId); + } else { + groupedMessages.calculate(); + int index = messages.indexOf(messageObject); + if (index >= 0) { + if (chatAdapter != null) { + chatAdapter.notifyItemRangeChanged(index + chatAdapter.messagesStartRow, updateCount); + } + } + } + } + } + } if (message.media.ttl_seconds != 0 && (message.media.photo instanceof TLRPC.TL_photoEmpty || message.media.document instanceof TLRPC.TL_documentEmpty)) { existMessageObject.setType(); chatAdapter.updateRowWithMessageObject(existMessageObject); @@ -7047,6 +8256,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not boolean changed = false; boolean mediaUpdated = false; ArrayList messageObjects = (ArrayList) args[1]; + HashMap newGroups = null; for (int a = 0; a < messageObjects.size(); a++) { MessageObject messageObject = messageObjects.get(a); MessageObject old = messagesDict[loadIndex].get(messageObject.getId()); @@ -7081,10 +8291,45 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (dayArr != null) { index2 = dayArr.indexOf(old); } + if (old.getGroupId() != 0) { + MessageObject.GroupedMessages groupedMessages = groupedMessagesMap.get(old.getGroupId()); + if (groupedMessages != null) { + int idx = groupedMessages.messages.indexOf(old); + if (idx >= 0) { + if (old.getGroupId() != messageObject.getGroupId()) { + groupedMessagesMap.put(messageObject.getGroupId(), groupedMessages); + } + if (messageObject.photoThumbs == null || messageObject.photoThumbs.isEmpty()) { + if (newGroups == null) { + newGroups = new HashMap<>(); + } + newGroups.put(groupedMessages.groupId, groupedMessages); + if (idx > 0 && idx < groupedMessages.messages.size() - 1) { + MessageObject.GroupedMessages slicedGroup = new MessageObject.GroupedMessages(); + slicedGroup.groupId = Utilities.random.nextLong(); + slicedGroup.messages.addAll(groupedMessages.messages.subList(idx + 1, groupedMessages.messages.size())); + for (int b = 0; b < slicedGroup.messages.size(); b++) { + slicedGroup.messages.get(b).localGroupId = slicedGroup.groupId; + groupedMessages.messages.remove(idx + 1); + } + newGroups.put(slicedGroup.groupId, slicedGroup); + groupedMessagesMap.put(slicedGroup.groupId, slicedGroup); + } + groupedMessages.messages.remove(idx); + } else { + groupedMessages.messages.set(idx, messageObject); + MessageObject.GroupedMessagePosition oldPosition = groupedMessages.positions.remove(old); + if (oldPosition != null) { + groupedMessages.positions.put(messageObject, oldPosition); + } + } + } + } + } if (messageObject.type >= 0) { messages.set(index, messageObject); if (chatAdapter != null) { - chatAdapter.notifyItemChanged(chatAdapter.messagesStartRow + messages.size() - index - 1); + chatAdapter.notifyItemChanged(chatAdapter.messagesStartRow + index); } if (index2 >= 0) { dayArr.set(index2, messageObject); @@ -7092,14 +8337,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else { messages.remove(index); if (chatAdapter != null) { - chatAdapter.notifyItemRemoved(chatAdapter.messagesStartRow + messages.size() - index - 1); + chatAdapter.notifyItemRemoved(chatAdapter.messagesStartRow + index); } if (index2 >= 0) { dayArr.remove(index2); if (dayArr.isEmpty()) { messagesByDays.remove(old.dateKey); messages.remove(index); - chatAdapter.notifyItemRemoved(chatAdapter.messagesStartRow + messages.size()); + chatAdapter.notifyItemRemoved(chatAdapter.messagesStartRow); } } } @@ -7107,9 +8352,26 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } } + if (newGroups != null) { + for (HashMap.Entry entry : newGroups.entrySet()) { + MessageObject.GroupedMessages groupedMessages = entry.getValue(); + if (groupedMessages.messages.isEmpty()) { + groupedMessagesMap.remove(groupedMessages.groupId); + } else { + groupedMessages.calculate(); + MessageObject messageObject = groupedMessages.messages.get(groupedMessages.messages.size() - 1); + int index = messages.indexOf(messageObject); + if (index >= 0) { + if (chatAdapter != null) { + chatAdapter.notifyItemRangeChanged(index + chatAdapter.messagesStartRow, groupedMessages.messages.size()); + } + } + } + } + } if (changed && chatLayoutManager != null) { - if (mediaUpdated && chatLayoutManager.findLastVisibleItemPosition() >= messages.size() - (chatAdapter.isBot ? 2 : 1)) { - moveScrollToLastMessage(); + if (mediaUpdated && chatLayoutManager.findFirstVisibleItemPosition() == 0) { + //moveScrollToLastMessage(); } } } else if (id == NotificationCenter.notificationsSettingsUpdated) { @@ -7148,7 +8410,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } if (updated) { updateVisibleRows(); - if (chatLayoutManager != null && chatLayoutManager.findLastVisibleItemPosition() >= messages.size() - 1) { + if (chatLayoutManager != null && chatLayoutManager.findFirstVisibleItemPosition() == 0) { moveScrollToLastMessage(); } } @@ -7165,12 +8427,30 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else if (id == NotificationCenter.messagesReadContent) { ArrayList arrayList = (ArrayList) args[0]; boolean updated = false; + int currentChannelId = ChatObject.isChannel(currentChat) ? currentChat.id : 0; for (int a = 0; a < arrayList.size(); a++) { long mid = arrayList.get(a); - MessageObject currentMessage = messagesDict[mergeDialogId == 0 ? 0 : 1].get((int) mid); + int channelId = (int) (mid >> 32); + if (channelId < 0) { + channelId = 0; + } + if (channelId != currentChannelId) { + continue; + } + MessageObject currentMessage = messagesDict[0].get((int) mid); if (currentMessage != null) { currentMessage.setContentIsRead(); updated = true; + if (currentMessage.messageOwner.mentioned) { + newMentionsCount--; + if (newMentionsCount <= 0) { + newMentionsCount = 0; + hasAllMentionsLocal = true; + showMentiondownButton(false, true); + } else { + mentiondownButtonCounter.setText(String.format("%d", newMentionsCount)); + } + } } } if (updated) { @@ -7186,7 +8466,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } botInfo.put(info.user_id, info); if (chatAdapter != null) { - chatAdapter.notifyItemChanged(0); + chatAdapter.notifyItemChanged(chatAdapter.botInfoRow); } if (mentionsAdapter != null && (!ChatObject.isChannel(currentChat) || currentChat != null && currentChat.megagroup)) { mentionsAdapter.setBotInfo(botInfo); @@ -7237,6 +8517,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not scrollToMessageId(messageId, 0, true, did == dialog_id ? 0 : 1, false); } updateSearchButtons((Integer) args[2], (Integer) args[4], (Integer) args[5]); + if (searchItem != null) { + searchItem.setShowSearchProgress(false); + } + } + } else if (id == NotificationCenter.chatSearchResultsLoading) { + if (classGuid == (Integer) args[0] && searchItem != null) { + searchItem.setShowSearchProgress(true); } } else if (id == NotificationCenter.didUpdatedMessagesViews) { SparseArray channelViews = (SparseArray) args[0]; @@ -7300,6 +8587,20 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not checkRaiseSensors(); updateSecretStatus(); } + } else if (id == NotificationCenter.updateMentionsCount) { + if (dialog_id == (Long) args[0]) { + int count = (int) args[1]; + if (newMentionsCount > count) { + newMentionsCount = count; + if (newMentionsCount <= 0) { + newMentionsCount = 0; + hasAllMentionsLocal = true; + showMentiondownButton(false, true); + } else { + mentiondownButtonCounter.setText(String.format("%d", newMentionsCount)); + } + } + } } } @@ -7348,8 +8649,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not searchDownButton.setEnabled((mask & 2) != 0); searchUpButton.setAlpha(searchUpButton.isEnabled() ? 1.0f : 0.5f); searchDownButton.setAlpha(searchDownButton.isEnabled() ? 1.0f : 0.5f); - if (count == 0) { + if (count < 0) { searchCountText.setText(""); + } else if (count == 0) { + searchCountText.setText(LocaleController.getString("NoResult", R.string.NoResult)); } else { searchCountText.setText(LocaleController.formatString("Of", R.string.Of, num + 1, count)); } @@ -7383,7 +8686,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not createChatAttachView(); } - if (chatActivityEnterView.hasRecordVideo()) { + if (chatActivityEnterView.hasRecordVideo() && !chatActivityEnterView.isSendButtonVisible()) { boolean isChannel = false; if (currentChat != null) { isChannel = ChatObject.isChannel(currentChat) && !currentChat.megagroup; @@ -7657,7 +8960,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - if (info == null || info.pinned_msg_id == 0 || info.pinned_msg_id == preferences.getInt("pin_" + dialog_id, 0) || actionBar != null && actionBar.isActionModeShowed()) { + if (info == null || info.pinned_msg_id == 0 || info.pinned_msg_id == preferences.getInt("pin_" + dialog_id, 0) || actionBar != null && (actionBar.isActionModeShowed() || actionBar.isSearchFieldVisible())) { hidePinnedMessageView(animated); } else { if (pinnedMessageObject != null) { @@ -7831,14 +9134,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not currentUser = user; } if (currentEncryptedChat != null && !(currentEncryptedChat instanceof TLRPC.TL_encryptedChat) - || currentUser.id == 4244000 || currentUser.id / 1000 == 333 || currentUser.id / 1000 == 777 + || MessagesController.isSupportId(currentUser.id) || UserObject.isDeleted(currentUser) || ContactsController.getInstance().isLoadingContacts() - || (currentUser.phone != null && currentUser.phone.length() != 0 && ContactsController.getInstance().contactsDict.get(currentUser.id) != null && (ContactsController.getInstance().contactsDict.size() != 0 || !ContactsController.getInstance().isLoadingContacts()))) { + || (!TextUtils.isEmpty(currentUser.phone) && ContactsController.getInstance().contactsDict.get(currentUser.id) != null && (ContactsController.getInstance().contactsDict.size() != 0 || !ContactsController.getInstance().isLoadingContacts()))) { addContactItem.setVisibility(View.GONE); } else { addContactItem.setVisibility(View.VISIBLE); - if (currentUser.phone != null && currentUser.phone.length() != 0) { + if (!TextUtils.isEmpty(currentUser.phone)) { addContactItem.setText(LocaleController.getString("AddToContacts", R.string.AddToContacts)); reportSpamButton.setPadding(AndroidUtilities.dp(4), 0, AndroidUtilities.dp(50), 0); addToContactsButton.setVisibility(View.VISIBLE); @@ -7859,11 +9162,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public void run() { try { - int firstVisPos = chatLayoutManager.findLastVisibleItemPosition(); + int firstVisPos = chatLayoutManager.findFirstVisibleItemPosition(); int top = 0; if (firstVisPos != RecyclerView.NO_POSITION) { View firstVisView = chatLayoutManager.findViewByPosition(firstVisPos); - top = ((firstVisView == null) ? 0 : firstVisView.getTop()) - chatListView.getPaddingTop(); + top = ((firstVisView == null) ? 0 : chatListView.getMeasuredHeight() - firstVisView.getBottom() - chatListView.getPaddingBottom()); } if (chatListView.getPaddingTop() != AndroidUtilities.dp(52) && (pinnedMessageView != null && pinnedMessageView.getTag() == null || reportSpamView != null && reportSpamView.getTag() == null)) { chatListView.setPadding(0, AndroidUtilities.dp(52), 0, AndroidUtilities.dp(3)); @@ -7871,14 +9174,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not layoutParams.topMargin = AndroidUtilities.dp(52); floatingDateView.setLayoutParams(layoutParams); chatListView.setTopGlowOffset(AndroidUtilities.dp(48)); - top -= AndroidUtilities.dp(48); } else if (chatListView.getPaddingTop() != AndroidUtilities.dp(4) && (pinnedMessageView == null || pinnedMessageView.getTag() != null) && (reportSpamView == null || reportSpamView.getTag() != null)) { chatListView.setPadding(0, AndroidUtilities.dp(4), 0, AndroidUtilities.dp(3)); FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) floatingDateView.getLayoutParams(); layoutParams.topMargin = AndroidUtilities.dp(4); floatingDateView.setLayoutParams(layoutParams); chatListView.setTopGlowOffset(0); - top += AndroidUtilities.dp(48); } else { firstVisPos = RecyclerView.NO_POSITION; } @@ -7937,14 +9238,17 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (scrollToTopUnReadOnResume && scrollToMessage != null) { if (chatListView != null) { int yOffset; + boolean bottom = true; if (scrollToMessagePosition == -9000) { - yOffset = Math.max(0, (chatListView.getHeight() - scrollToMessage.getApproximateHeight()) / 2); + yOffset = getScrollOffsetForMessage(scrollToMessage); + bottom = false; } else if (scrollToMessagePosition == -10000) { - yOffset = 0; + yOffset = -chatListView.getPaddingTop() - AndroidUtilities.dp(7); + bottom = false; } else { yOffset = scrollToMessagePosition; } - chatLayoutManager.scrollToPositionWithOffset(messages.size() - messages.indexOf(scrollToMessage), -AndroidUtilities.dp(7) + yOffset - (scrollToMessage == unreadMessageObject ? chatListView.getPaddingTop() : 0)); + chatLayoutManager.scrollToPositionWithOffset(chatAdapter.messagesStartRow + messages.indexOf(scrollToMessage), yOffset, bottom); } } else { moveScrollToLastMessage(); @@ -7954,6 +9258,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not scrollToMessage = null; } paused = false; + pausedOnLastMessage = false; AndroidUtilities.runOnUIThread(readRunnable, 500); checkScrollForLoad(false); if (wasPaused) { @@ -7980,7 +9285,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - openVideoEditor(startVideoEdit, false, false); + openVideoEditor(startVideoEdit, null); startVideoEdit = null; } }); @@ -8003,9 +9308,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not wasPaused = true; NotificationsController.getInstance().setOpenedDialogId(0); CharSequence draftMessage = null; + MessageObject replyMessage = null; boolean searchWebpage = true; - if (!ignoreAttachOnPause && chatActivityEnterView != null) { + if (!ignoreAttachOnPause && chatActivityEnterView != null && bottomOverlayChat.getVisibility() != View.VISIBLE) { chatActivityEnterView.onPause(); + replyMessage = replyingMessageObject; if (!chatActivityEnterView.isEditingMessage()) { CharSequence text = AndroidUtilities.getTrimmedString(chatActivityEnterView.getFieldText()); if (!TextUtils.isEmpty(text) && !TextUtils.equals(text, "@gif")) { @@ -8024,38 +9331,41 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } CharSequence[] message = new CharSequence[] {draftMessage}; ArrayList entities = MessagesQuery.getEntities(message); - DraftQuery.saveDraft(dialog_id, message[0], entities, replyingMessageObject != null ? replyingMessageObject.messageOwner : null, !searchWebpage); + DraftQuery.saveDraft(dialog_id, message[0], entities, replyMessage != null ? replyMessage.messageOwner : null, !searchWebpage); MessagesController.getInstance().cancelTyping(0, dialog_id); - SharedPreferences.Editor editor = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE).edit(); - int messageId = 0; - int offset = 0; - if (chatLayoutManager != null) { - int position = chatLayoutManager.findLastVisibleItemPosition(); - if (position < chatLayoutManager.getItemCount() - 1) { - RecyclerListView.Holder holder = (RecyclerListView.Holder) chatListView.findViewHolderForAdapterPosition(position); - if (holder != null) { - if (holder.itemView instanceof ChatMessageCell) { - messageId = ((ChatMessageCell) holder.itemView).getMessageObject().getId(); - } else if (holder.itemView instanceof ChatActionCell) { - messageId = ((ChatActionCell) holder.itemView).getMessageObject().getId(); - } - if (messageId != 0) { - offset = holder.itemView.getMeasuredHeight() - (holder.itemView.getBottom() - chatListView.getMeasuredHeight()) + AndroidUtilities.dp2(1); - FileLog.d("save offset = " + offset + " for mid " + messageId); + if (!pausedOnLastMessage) { + SharedPreferences.Editor editor = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE).edit(); + int messageId = 0; + int offset = 0; + if (chatLayoutManager != null) { + int position = chatLayoutManager.findFirstVisibleItemPosition(); + if (position != 0) { + RecyclerListView.Holder holder = (RecyclerListView.Holder) chatListView.findViewHolderForAdapterPosition(position); + if (holder != null) { + if (holder.itemView instanceof ChatMessageCell) { + messageId = ((ChatMessageCell) holder.itemView).getMessageObject().getId(); + } else if (holder.itemView instanceof ChatActionCell) { + messageId = ((ChatActionCell) holder.itemView).getMessageObject().getId(); + } + if (messageId != 0) { + offset = holder.itemView.getBottom() - chatListView.getMeasuredHeight(); + FileLog.d("save offset = " + offset + " for mid " + messageId); + } } } } + if (messageId != 0) { + editor.putInt("diditem" + dialog_id, messageId); + editor.putInt("diditemo" + dialog_id, offset); + } else { + pausedOnLastMessage = true; + editor.remove("diditem" + dialog_id); + editor.remove("diditemo" + dialog_id); + } + editor.commit(); } - if (messageId != 0) { - editor.putInt("diditem" + dialog_id, messageId); - editor.putInt("diditemo" + dialog_id, offset); - } else { - editor.remove("diditem" + dialog_id); - editor.remove("diditemo" + dialog_id); - } - editor.commit(); if (currentUser != null) { chatLeaveTime = System.currentTimeMillis(); @@ -8171,6 +9481,29 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not selectedMessagesCountTextView.setTextSize(20); } + HashMap newGroups = null; + int count = chatListView.getChildCount(); + for (int a = 0; a < count; a++) { + View child = chatListView.getChildAt(a); + if (child instanceof ChatMessageCell) { + MessageObject.GroupedMessages groupedMessages = ((ChatMessageCell) child).getCurrentMessagesGroup(); + if (groupedMessages != null && groupedMessages.hasSibling) { + if (newGroups == null) { + newGroups = new HashMap<>(); + } + if (!newGroups.containsKey(groupedMessages.groupId)) { + newGroups.put(groupedMessages.groupId, groupedMessages); + + MessageObject messageObject = groupedMessages.messages.get(groupedMessages.messages.size() - 1); + int idx = messages.indexOf(messageObject); + if (idx >= 0) { + chatAdapter.notifyItemRangeChanged(idx + chatAdapter.messagesStartRow, groupedMessages.messages.size()); + } + } + } + } + } + if (AndroidUtilities.isTablet()) { if (AndroidUtilities.isSmallTablet() && ApplicationLoader.applicationContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { actionBar.setBackButtonDrawable(new BackDrawable(false)); @@ -8211,16 +9544,24 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } - private void createDeleteMessagesAlert(final MessageObject finalSelectedObject) { - createDeleteMessagesAlert(finalSelectedObject, 1); + private void createDeleteMessagesAlert(final MessageObject finalSelectedObject, final MessageObject.GroupedMessages selectedGroup) { + createDeleteMessagesAlert(finalSelectedObject, selectedGroup, 1); } - private void createDeleteMessagesAlert(final MessageObject finalSelectedObject, int loadParticipant) { + private void createDeleteMessagesAlert(final MessageObject finalSelectedObject, final MessageObject.GroupedMessages finalSelectedGroup, int loadParticipant) { if (getParentActivity() == null) { return; } AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setMessage(LocaleController.formatString("AreYouSureDeleteMessages", R.string.AreYouSureDeleteMessages, LocaleController.formatPluralString("messages", finalSelectedObject != null ? 1 : selectedMessagesIds[0].size() + selectedMessagesIds[1].size()))); + int count; + if (finalSelectedGroup != null) { + count = finalSelectedGroup.messages.size(); + } else if (finalSelectedObject != null) { + count = 1; + } else { + count = selectedMessagesIds[0].size() + selectedMessagesIds[1].size(); + } + builder.setMessage(LocaleController.formatString("AreYouSureDeleteMessages", R.string.AreYouSureDeleteMessages, LocaleController.formatPluralString("messages", count))); builder.setTitle(LocaleController.getString("Message", R.string.Message)); final boolean[] checks = new boolean[3]; @@ -8231,7 +9572,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not boolean canBan = ChatObject.canBlockUsers(currentChat); int currentDate = ConnectionsManager.getInstance().getCurrentTime(); if (finalSelectedObject != null) { - if (finalSelectedObject.messageOwner.action == null || finalSelectedObject.messageOwner.action instanceof TLRPC.TL_messageActionEmpty) { + if (finalSelectedObject.messageOwner.action == null || finalSelectedObject.messageOwner.action instanceof TLRPC.TL_messageActionEmpty || finalSelectedObject.messageOwner.action instanceof TLRPC.TL_messageActionChatDeleteUser) { user = MessagesController.getInstance().getUser(finalSelectedObject.messageOwner.from_id); } hasOutgoing = !finalSelectedObject.isSendError() && finalSelectedObject.getDialogId() == mergeDialogId && (finalSelectedObject.messageOwner.action == null || finalSelectedObject.messageOwner.action instanceof TLRPC.TL_messageActionEmpty) && finalSelectedObject.isOut() && (currentDate - finalSelectedObject.messageOwner.date) <= 2 * 24 * 60 * 60; @@ -8299,7 +9640,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not try { progressDialog[0].dismiss(); } catch (Throwable ignore) { - //ignore + } progressDialog[0] = null; int loadType = 2; @@ -8309,7 +9650,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not loadType = 0; } } - createDeleteMessagesAlert(finalSelectedObject, loadType); + createDeleteMessagesAlert(finalSelectedObject, finalSelectedGroup, loadType); } }); } @@ -8401,7 +9742,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else if (!ChatObject.isChannel(currentChat) && currentEncryptedChat == null) { boolean hasOutgoing = false; int currentDate = ConnectionsManager.getInstance().getCurrentTime(); - if (currentUser != null && currentUser.id != UserConfig.getClientUserId() || currentChat != null) { + if (currentUser != null && currentUser.id != UserConfig.getClientUserId() && !currentUser.bot || currentChat != null) { if (finalSelectedObject != null) { hasOutgoing = !finalSelectedObject.isSendError() && (finalSelectedObject.messageOwner.action == null || finalSelectedObject.messageOwner.action instanceof TLRPC.TL_messageActionEmpty) && (finalSelectedObject.isOut() || currentChat != null && (currentChat.creator || currentChat.admin && currentChat.admins_enabled)) && (currentDate - finalSelectedObject.messageOwner.date) <= 2 * 24 * 60 * 60; } else { @@ -8458,11 +9799,24 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not ArrayList ids = null; if (finalSelectedObject != null) { ids = new ArrayList<>(); - ids.add(finalSelectedObject.getId()); ArrayList random_ids = null; - if (currentEncryptedChat != null && finalSelectedObject.messageOwner.random_id != 0 && finalSelectedObject.type != 10) { - random_ids = new ArrayList<>(); - random_ids.add(finalSelectedObject.messageOwner.random_id); + if (finalSelectedGroup != null) { + for (int a = 0; a < finalSelectedGroup.messages.size(); a++) { + MessageObject messageObject = finalSelectedGroup.messages.get(a); + ids.add(messageObject.getId()); + if (currentEncryptedChat != null && messageObject.messageOwner.random_id != 0 && messageObject.type != 10) { + if (random_ids == null) { + random_ids = new ArrayList<>(); + } + random_ids.add(messageObject.messageOwner.random_id); + } + } + } else { + ids.add(finalSelectedObject.getId()); + if (currentEncryptedChat != null && finalSelectedObject.messageOwner.random_id != 0 && finalSelectedObject.type != 10) { + random_ids = new ArrayList<>(); + random_ids.add(finalSelectedObject.messageOwner.random_id); + } } MessagesController.getInstance().deleteMessages(ids, random_ids, currentEncryptedChat, finalSelectedObject.messageOwner.to_id.channel_id, deleteForAll[0]); } else { @@ -8516,7 +9870,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not showDialog(builder.create()); } - private void createMenu(View v, boolean single) { + private void createMenu(View v, boolean single, boolean listView) { + createMenu(v, single, listView, true); + } + + private void createMenu(View v, boolean single, boolean listView, boolean searchGroup) { if (actionBar.isActionModeShowed()) { return; } @@ -8539,30 +9897,43 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } selectedObject = null; - forwaringMessage = null; + selectedObjectGroup = null; + forwardingMessage = null; + forwardingMessageGroup = null; for (int a = 1; a >= 0; a--) { selectedMessagesCanCopyIds[a].clear(); + selectedMessagesCanStarIds[a].clear(); selectedMessagesIds[a].clear(); } cantDeleteMessagesCount = 0; + canEditMessagesCount = 0; actionBar.hideActionMode(); updatePinnedMessageView(true); + MessageObject.GroupedMessages groupedMessages; + if (searchGroup) { + groupedMessages = getValidGroupedMessage(message); + } else { + groupedMessages = null; + } + boolean allowChatActions = true; - boolean allowPin = message.getDialogId() != mergeDialogId && message.getId() > 0 && ChatObject.isChannel(currentChat) && currentChat.megagroup && (currentChat.creator || currentChat.admin_rights != null && currentChat.admin_rights.pin_messages) && (message.messageOwner.action == null || message.messageOwner.action instanceof TLRPC.TL_messageActionEmpty); - boolean allowUnpin = message.getDialogId() != mergeDialogId && info != null && info.pinned_msg_id == message.getId() && (currentChat.creator || currentChat.admin_rights != null && currentChat.admin_rights.pin_messages); - boolean allowEdit = message.canEditMessage(currentChat) && !chatActivityEnterView.hasAudioToSend() && message.getDialogId() != mergeDialogId; + boolean allowPin = message.getDialogId() != mergeDialogId && message.getId() > 0 && ChatObject.isChannel(currentChat) && (currentChat.creator || currentChat.admin_rights != null && (currentChat.megagroup && currentChat.admin_rights.pin_messages || !currentChat.megagroup && currentChat.admin_rights.edit_messages)) && (message.messageOwner.action == null || message.messageOwner.action instanceof TLRPC.TL_messageActionEmpty); + boolean allowUnpin = message.getDialogId() != mergeDialogId && info != null && info.pinned_msg_id == message.getId() && (currentChat.creator || currentChat.admin_rights != null && (currentChat.megagroup && currentChat.admin_rights.pin_messages || !currentChat.megagroup && currentChat.admin_rights.edit_messages)); + boolean allowEdit = groupedMessages == null && message.canEditMessage(currentChat) && !chatActivityEnterView.hasAudioToSend() && message.getDialogId() != mergeDialogId; if (currentEncryptedChat != null && AndroidUtilities.getPeerLayerVersion(currentEncryptedChat.layer) < 46 || type == 1 && (message.getDialogId() == mergeDialogId || message.isSecretPhoto()) || currentEncryptedChat == null && message.getId() < 0 || + bottomOverlayChat != null && bottomOverlayChat.getVisibility() == View.VISIBLE || isBroadcast || currentChat != null && (ChatObject.isNotInChat(currentChat) || ChatObject.isChannel(currentChat) && !ChatObject.canPost(currentChat) && !currentChat.megagroup || !ChatObject.canSendMessages(currentChat))) { allowChatActions = false; } - if (single || type < 2 || type == 20 || message.isSecretPhoto()) { + if (single || type < 2 || type == 20 || message.isSecretPhoto() || message.isLiveLocation()) { if (type >= 0) { selectedObject = message; + selectedObjectGroup = groupedMessages; if (getParentActivity() == null) { return; } @@ -8633,6 +10004,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not items.add(LocaleController.getString("Copy", R.string.Copy)); options.add(3); } + if (ChatObject.isChannel(currentChat) && currentChat.megagroup && !TextUtils.isEmpty(currentChat.username) && ChatObject.hasAdminRights(currentChat)) { + items.add(LocaleController.getString("CopyLink", R.string.CopyLink)); + options.add(22); + } if (type == 3) { if (selectedObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage && MessageObject.isNewGifDocument(selectedObject.messageOwner.media.webpage.document)) { items.add(LocaleController.getString("SaveToGIFs", R.string.SaveToGIFs)); @@ -8690,24 +10065,42 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else if (type == 7) { if (selectedObject.isMask()) { items.add(LocaleController.getString("AddToMasks", R.string.AddToMasks)); + options.add(9); } else { items.add(LocaleController.getString("AddToStickers", R.string.AddToStickers)); + options.add(9); + if (!StickersQuery.isStickerInFavorites(selectedObject.getDocument())) { + if (StickersQuery.canAddStickerToFavorites()) { + items.add(LocaleController.getString("AddToFavorites", R.string.AddToFavorites)); + options.add(20); + } + } else { + items.add(LocaleController.getString("DeleteFromFavorites", R.string.DeleteFromFavorites)); + options.add(21); + } } - options.add(9); } else if (type == 8) { TLRPC.User user = MessagesController.getInstance().getUser(selectedObject.messageOwner.media.user_id); if (user != null && user.id != UserConfig.getClientUserId() && ContactsController.getInstance().contactsDict.get(user.id) == null) { items.add(LocaleController.getString("AddContactTitle", R.string.AddContactTitle)); options.add(15); } - if (selectedObject.messageOwner.media.phone_number != null || selectedObject.messageOwner.media.phone_number.length() != 0) { + if (!TextUtils.isEmpty(selectedObject.messageOwner.media.phone_number)) { items.add(LocaleController.getString("Copy", R.string.Copy)); options.add(16); items.add(LocaleController.getString("Call", R.string.Call)); options.add(17); } + } else if (type == 9) { + if (!StickersQuery.isStickerInFavorites(selectedObject.getDocument())) { + items.add(LocaleController.getString("AddToFavorites", R.string.AddToFavorites)); + options.add(20); + } else { + items.add(LocaleController.getString("DeleteFromFavorites", R.string.DeleteFromFavorites)); + options.add(21); + } } - if (!selectedObject.isSecretPhoto()) { + if (!selectedObject.isSecretPhoto() && !selectedObject.isLiveLocation()) { items.add(LocaleController.getString("Forward", R.string.Forward)); options.add(2); } @@ -8807,6 +10200,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not ArrayList animators = new ArrayList<>(); for (int a = 0; a < actionModeViews.size(); a++) { View view = actionModeViews.get(a); + view.setPivotY(ActionBar.getCurrentActionBarHeight() / 2); AndroidUtilities.clearDrawableAnimation(view); animators.add(ObjectAnimator.ofFloat(view, "scaleY", 0.1f, 1.0f)); } @@ -8814,11 +10208,78 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not animatorSet.setDuration(250); animatorSet.start(); - addToSelectedMessages(message); - selectedMessagesCountTextView.setNumber(1, false); + addToSelectedMessages(message, listView); + + selectedMessagesCountTextView.setNumber(selectedMessagesIds[0].size() + selectedMessagesIds[1].size(), false); updateVisibleRows(); } + private void startEditingMessageObject(MessageObject messageObject) { + if (messageObject == null || getParentActivity() == null) { + return; + } + if (searchItem != null && actionBar.isSearchFieldVisible()) { + actionBar.closeSearchField(); + chatActivityEnterView.setFieldFocused(); + } + + mentionsAdapter.setNeedBotContext(false); + chatListView.setOnItemLongClickListener(null); + chatListView.setOnItemClickListener((RecyclerListView.OnItemClickListenerExtended) null); + chatListView.setClickable(false); + chatListView.setLongClickable(false); + chatActivityEnterView.setEditingMessageObject(messageObject, !messageObject.isMediaEmpty()); + updateBottomOverlay(); + if (chatActivityEnterView.isEditingCaption()) { + mentionsAdapter.setAllowNewMentions(false); + } + actionModeTitleContainer.setVisibility(View.VISIBLE); + selectedMessagesCountTextView.setVisibility(View.GONE); + checkEditTimer(); + + chatActivityEnterView.setAllowStickersAndGifs(false, false); + final ActionBarMenu actionMode = actionBar.createActionMode(); + actionMode.getItem(reply).setVisibility(View.GONE); + actionMode.getItem(copy).setVisibility(View.GONE); + actionMode.getItem(forward).setVisibility(View.GONE); + actionMode.getItem(delete).setVisibility(View.GONE); + actionMode.getItem(edit).setVisibility(View.GONE); + + actionBar.showActionMode(); + updatePinnedMessageView(true); + updateVisibleRows(); + + TLRPC.TL_messages_getMessageEditData req = new TLRPC.TL_messages_getMessageEditData(); + req.peer = MessagesController.getInputPeer((int) dialog_id); + req.id = messageObject.getId(); + editingMessageObjectReqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + editingMessageObjectReqId = 0; + if (response == null) { + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setMessage(LocaleController.getString("EditMessageError", R.string.EditMessageError)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); + showDialog(builder.create()); + + if (chatActivityEnterView != null) { + chatActivityEnterView.setEditingMessageObject(null, false); + } + } else { + if (chatActivityEnterView != null) { + chatActivityEnterView.showEditDoneProgress(false, true); + } + } + } + }); + } + }); + } + private String getMessageContent(MessageObject messageObject, int previousUid, boolean name) { String str = ""; if (name) { @@ -8846,29 +10307,58 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return str; } + private void saveMessageToGallery(MessageObject messageObject) { + String path = messageObject.messageOwner.attachPath; + if (!TextUtils.isEmpty(path)) { + File temp = new File(path); + if (!temp.exists()) { + path = null; + } + } + if (TextUtils.isEmpty(path)) { + path = FileLoader.getPathToMessage(messageObject.messageOwner).toString(); + } + MediaController.saveFile(path, getParentActivity(), messageObject.type == 3 ? 1 : 0, null, null); + } + private void processSelectedOption(int option) { if (selectedObject == null) { return; } switch (option) { case 0: { - if (SendMessagesHelper.getInstance().retrySendMessage(selectedObject, false)) { - moveScrollToLastMessage(); + if (selectedObjectGroup != null) { + boolean success = true; + for (int a = 0; a < selectedObjectGroup.messages.size(); a++) { + if (!SendMessagesHelper.getInstance().retrySendMessage(selectedObjectGroup.messages.get(a), false)) { + success = false; + } + } + if (success) { + moveScrollToLastMessage(); + } + } else { + if (SendMessagesHelper.getInstance().retrySendMessage(selectedObject, false)) { + moveScrollToLastMessage(); + } } break; } case 1: { if (getParentActivity() == null) { selectedObject = null; + selectedObjectGroup = null; return; } - createDeleteMessagesAlert(selectedObject); + createDeleteMessagesAlert(selectedObject, selectedObjectGroup); break; } case 2: { - forwaringMessage = selectedObject; + forwardingMessage = selectedObject; + forwardingMessageGroup = selectedObjectGroup; Bundle args = new Bundle(); args.putBoolean("onlySelect", true); + args.putInt("dialogsType", 3); DialogsActivity fragment = new DialogsActivity(args); fragment.setDelegate(this); presentFragment(fragment); @@ -8879,29 +10369,24 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not break; } case 4: { - String path = selectedObject.messageOwner.attachPath; - if (path != null && path.length() > 0) { - File temp = new File(path); - if (!temp.exists()) { - path = null; - } + if (Build.VERSION.SDK_INT >= 23 && getParentActivity().checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + getParentActivity().requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 4); + selectedObject = null; + selectedObjectGroup = null; + return; } - if (path == null || path.length() == 0) { - path = FileLoader.getPathToMessage(selectedObject.messageOwner).toString(); - } - if (selectedObject.type == 3 || selectedObject.type == 1) { - if (Build.VERSION.SDK_INT >= 23 && getParentActivity().checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - getParentActivity().requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 4); - selectedObject = null; - return; + if (selectedObjectGroup != null) { + for (int a = 0; a < selectedObjectGroup.messages.size(); a++) { + saveMessageToGallery(selectedObjectGroup.messages.get(a)); } - MediaController.saveFile(path, getParentActivity(), selectedObject.type == 3 ? 1 : 0, null, null); + } else { + saveMessageToGallery(selectedObject); } break; } case 5: { File locFile = null; - if (selectedObject.messageOwner.attachPath != null && selectedObject.messageOwner.attachPath.length() != 0) { + if (!TextUtils.isEmpty(selectedObject.messageOwner.attachPath)) { File f = new File(selectedObject.messageOwner.attachPath); if (f.exists()) { locFile = f; @@ -8916,12 +10401,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (locFile != null) { if (locFile.getName().endsWith("attheme")) { if (chatLayoutManager != null) { - int lastPosition = chatLayoutManager.findLastVisibleItemPosition(); - if (lastPosition < chatLayoutManager.getItemCount() - 1) { - scrollToPositionOnRecreate = chatLayoutManager.findFirstVisibleItemPosition(); + int lastPosition = chatLayoutManager.findFirstVisibleItemPosition(); + if (lastPosition != 0) { + scrollToPositionOnRecreate = lastPosition; RecyclerListView.Holder holder = (RecyclerListView.Holder) chatListView.findViewHolderForAdapterPosition(scrollToPositionOnRecreate); if (holder != null) { - scrollToOffsetOnRecreate = holder.itemView.getTop(); + scrollToOffsetOnRecreate = chatListView.getMeasuredHeight() - holder.itemView.getBottom() - chatListView.getPaddingBottom(); } else { scrollToPositionOnRecreate = -1; } @@ -8929,6 +10414,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not scrollToPositionOnRecreate = -1; } } + Theme.ThemeInfo themeInfo = Theme.applyThemeFile(locFile, selectedObject.getDocumentName(), true); if (themeInfo != null) { presentFragment(new ThemePreviewActivity(locFile, themeInfo)); @@ -8936,6 +10422,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not scrollToPositionOnRecreate = -1; if (getParentActivity() == null) { selectedObject = null; + selectedObjectGroup = null; return; } AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); @@ -8950,6 +10437,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else { if (getParentActivity() == null) { selectedObject = null; + selectedObjectGroup = null; return; } AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); @@ -8975,7 +10463,17 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } Intent intent = new Intent(Intent.ACTION_SEND); intent.setType(selectedObject.getDocument().mime_type); - intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(path))); + File f = new File(path); + if (Build.VERSION.SDK_INT >= 24) { + try { + intent.putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(getParentActivity(), BuildConfig.APPLICATION_ID + ".provider", f)); + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } catch (Exception ignore) { + intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(f)); + } + } else { + intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(f)); + } getParentActivity().startActivityForResult(Intent.createChooser(intent, LocaleController.getString("ShareFile", R.string.ShareFile)), 500); break; } @@ -8993,6 +10491,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (Build.VERSION.SDK_INT >= 23 && getParentActivity().checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { getParentActivity().requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 4); selectedObject = null; + selectedObjectGroup = null; return; } MediaController.saveFile(path, getParentActivity(), 0, null, null); @@ -9010,6 +10509,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (Build.VERSION.SDK_INT >= 23 && getParentActivity().checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { getParentActivity().requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 4); selectedObject = null; + selectedObjectGroup = null; return; } String fileName = FileLoader.getDocumentFileName(selectedObject.getDocument()); @@ -9037,92 +10537,38 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not break; } case 12: { - if (getParentActivity() == null) { - selectedObject = null; - return; - } - if (searchItem != null && actionBar.isSearchFieldVisible()) { - actionBar.closeSearchField(); - chatActivityEnterView.setFieldFocused(); - } - - mentionsAdapter.setNeedBotContext(false); - chatListView.setOnItemLongClickListener(null); - chatListView.setOnItemClickListener(null); - chatListView.setClickable(false); - chatListView.setLongClickable(false); - chatActivityEnterView.setEditingMessageObject(selectedObject, !selectedObject.isMediaEmpty()); - updateBottomOverlay(); - if (chatActivityEnterView.isEditingCaption()) { - mentionsAdapter.setAllowNewMentions(false); - } - actionModeTitleContainer.setVisibility(View.VISIBLE); - selectedMessagesCountTextView.setVisibility(View.GONE); - checkEditTimer(); - - chatActivityEnterView.setAllowStickersAndGifs(false, false); - final ActionBarMenu actionMode = actionBar.createActionMode(); - actionMode.getItem(reply).setVisibility(View.GONE); - actionMode.getItem(copy).setVisibility(View.GONE); - actionMode.getItem(forward).setVisibility(View.GONE); - actionMode.getItem(delete).setVisibility(View.GONE); - - actionBar.showActionMode(); - updatePinnedMessageView(true); - updateVisibleRows(); - - TLRPC.TL_messages_getMessageEditData req = new TLRPC.TL_messages_getMessageEditData(); - req.peer = MessagesController.getInputPeer((int) dialog_id); - req.id = selectedObject.getId(); - editingMessageObjectReqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { - @Override - public void run(final TLObject response, TLRPC.TL_error error) { - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - editingMessageObjectReqId = 0; - if (response == null) { - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); - builder.setMessage(LocaleController.getString("EditMessageError", R.string.EditMessageError)); - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); - showDialog(builder.create()); - - if (chatActivityEnterView != null) { - chatActivityEnterView.setEditingMessageObject(null, false); - } - } else { - if (chatActivityEnterView != null) { - chatActivityEnterView.showEditDoneProgress(false, true); - } - } - } - }); - } - }); + startEditingMessageObject(selectedObject); + selectedObject = null; + selectedObjectGroup = null; break; } case 13: { final int mid = selectedObject.getId(); AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setMessage(LocaleController.getString("PinMessageAlert", R.string.PinMessageAlert)); - final boolean[] checks = new boolean[]{true}; - FrameLayout frameLayout = new FrameLayout(getParentActivity()); - CheckBoxCell cell = new CheckBoxCell(getParentActivity(), true); - cell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); - cell.setText(LocaleController.getString("PinNotify", R.string.PinNotify), "", true, false); - cell.setPadding(LocaleController.isRTL ? AndroidUtilities.dp(8) : 0, 0, LocaleController.isRTL ? 0 : AndroidUtilities.dp(8), 0); - frameLayout.addView(cell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.TOP | Gravity.LEFT, 8, 0, 8, 0)); - cell.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - CheckBoxCell cell = (CheckBoxCell) v; - checks[0] = !checks[0]; - cell.setChecked(checks[0], true); - } - }); - builder.setView(frameLayout); + final boolean[] checks; + if (ChatObject.isChannel(currentChat) && currentChat.megagroup) { + builder.setMessage(LocaleController.getString("PinMessageAlert", R.string.PinMessageAlert)); + checks = new boolean[]{true}; + FrameLayout frameLayout = new FrameLayout(getParentActivity()); + CheckBoxCell cell = new CheckBoxCell(getParentActivity(), true); + cell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + cell.setText(LocaleController.getString("PinNotify", R.string.PinNotify), "", true, false); + cell.setPadding(LocaleController.isRTL ? AndroidUtilities.dp(8) : 0, 0, LocaleController.isRTL ? 0 : AndroidUtilities.dp(8), 0); + frameLayout.addView(cell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.TOP | Gravity.LEFT, 8, 0, 8, 0)); + cell.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + CheckBoxCell cell = (CheckBoxCell) v; + checks[0] = !checks[0]; + cell.setChecked(checks[0], true); + } + }); + builder.setView(frameLayout); + } else { + builder.setMessage(LocaleController.getString("PinMessageAlertChannel", R.string.PinMessageAlertChannel)); + checks = new boolean[]{false}; + } builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { @@ -9171,44 +10617,102 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not break; } case 18: { - if(currentUser!=null) - VoIPHelper.startCall(currentUser, getParentActivity(), MessagesController.getInstance().getUserFull(currentUser.id)); + if (currentUser != null) { + VoIPHelper.startCall(currentUser, getParentActivity(), MessagesController.getInstance().getUserFull(currentUser.id)); + } break; } - case 19:{ - VoIPHelper.showRateAlert(getParentActivity(), (TLRPC.TL_messageActionPhoneCall)selectedObject.messageOwner.action); + case 19: { + VoIPHelper.showRateAlert(getParentActivity(), (TLRPC.TL_messageActionPhoneCall) selectedObject.messageOwner.action); + break; + } + case 20: { + StickersQuery.addRecentSticker(StickersQuery.TYPE_FAVE, selectedObject.getDocument(), (int) (System.currentTimeMillis() / 1000), false); + break; + } + case 21: { + StickersQuery.addRecentSticker(StickersQuery.TYPE_FAVE, selectedObject.getDocument(), (int) (System.currentTimeMillis() / 1000), true); + break; + } + case 22: { + TLRPC.TL_channels_exportMessageLink req = new TLRPC.TL_channels_exportMessageLink(); + req.id = selectedObject.getId(); + req.channel = MessagesController.getInputChannel(currentChat); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (response != null) { + TLRPC.TL_exportedMessageLink exportedMessageLink = (TLRPC.TL_exportedMessageLink) response; + try { + android.content.ClipboardManager clipboard = (android.content.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE); + android.content.ClipData clip = android.content.ClipData.newPlainText("label", exportedMessageLink.link); + clipboard.setPrimaryClip(clip); + Toast.makeText(ApplicationLoader.applicationContext, LocaleController.getString("LinkCopied", R.string.LinkCopied), Toast.LENGTH_SHORT).show(); + } catch (Exception e) { + FileLog.e(e); + } + } + + } + }); + } + }); break; } } selectedObject = null; + selectedObjectGroup = null; } @Override - public void didSelectDialog(DialogsActivity activity, long did, boolean param) { - if (dialog_id != 0 && (forwaringMessage != null || !selectedMessagesIds[0].isEmpty() || !selectedMessagesIds[1].isEmpty())) { - ArrayList fmessages = new ArrayList<>(); - if (forwaringMessage != null) { - fmessages.add(forwaringMessage); - forwaringMessage = null; + public void didSelectDialogs(DialogsActivity fragment, ArrayList dids, CharSequence message, boolean param) { + if (forwardingMessage == null && selectedMessagesIds[0].isEmpty() && selectedMessagesIds[1].isEmpty()) { + return; + } + ArrayList fmessages = new ArrayList<>(); + if (forwardingMessage != null) { + if (forwardingMessageGroup != null) { + fmessages.addAll(forwardingMessageGroup.messages); } else { - for (int a = 1; a >= 0; a--) { - ArrayList ids = new ArrayList<>(selectedMessagesIds[a].keySet()); - Collections.sort(ids); - for (int b = 0; b < ids.size(); b++) { - Integer id = ids.get(b); - MessageObject message = selectedMessagesIds[a].get(id); - if (message != null && id > 0) { - fmessages.add(message); - } - } - selectedMessagesCanCopyIds[a].clear(); - selectedMessagesIds[a].clear(); - } - cantDeleteMessagesCount = 0; - actionBar.hideActionMode(); - updatePinnedMessageView(true); + fmessages.add(forwardingMessage); } + forwardingMessage = null; + forwardingMessageGroup = null; + } else { + for (int a = 1; a >= 0; a--) { + ArrayList ids = new ArrayList<>(selectedMessagesIds[a].keySet()); + Collections.sort(ids); + for (int b = 0; b < ids.size(); b++) { + Integer id = ids.get(b); + MessageObject messageObject = selectedMessagesIds[a].get(id); + if (messageObject != null && id > 0) { + fmessages.add(messageObject); + } + } + selectedMessagesCanCopyIds[a].clear(); + selectedMessagesCanStarIds[a].clear(); + selectedMessagesIds[a].clear(); + } + cantDeleteMessagesCount = 0; + canEditMessagesCount = 0; + actionBar.hideActionMode(); + updatePinnedMessageView(true); + } + if (dids.size() > 1 || dids.get(0) == UserConfig.getClientUserId() || message != null) { + for (int a = 0; a < dids.size(); a++) { + long did = dids.get(a); + if (message != null) { + SendMessagesHelper.getInstance().sendMessage(message.toString(), did, null, null, true, null, null, null); + } + SendMessagesHelper.getInstance().sendMessage(fmessages, did); + } + fragment.finishFragment(); + } else { + long did = dids.get(0); if (did != dialog_id) { int lower_part = (int) did; int high_part = (int) (did >> 32); @@ -9224,7 +10728,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not args.putInt("enc_id", high_part); } if (lower_part != 0) { - if (!MessagesController.checkCanOpenChat(args, activity)) { + if (!MessagesController.checkCanOpenChat(args, fragment)) { return; } } @@ -9235,10 +10739,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not removeSelfFromStack(); } } else { - activity.finishFragment(); + fragment.finishFragment(); } } else { - activity.finishFragment(); + fragment.finishFragment(); moveScrollToLastMessage(); showReplyPanel(true, null, fmessages, null, false); if (AndroidUtilities.isTablet()) { @@ -9283,11 +10787,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not for (int a = 1; a >= 0; a--) { selectedMessagesIds[a].clear(); selectedMessagesCanCopyIds[a].clear(); + selectedMessagesCanStarIds[a].clear(); } chatActivityEnterView.setEditingMessageObject(null, false); actionBar.hideActionMode(); updatePinnedMessageView(true); cantDeleteMessagesCount = 0; + canEditMessagesCount = 0; updateVisibleRows(); return false; } else if (chatActivityEnterView != null && chatActivityEnterView.isPopupShowing()) { @@ -9307,13 +10813,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not View view = chatListView.getChildAt(a); if (view instanceof ChatMessageCell) { ChatMessageCell cell = (ChatMessageCell) view; + MessageObject messageObject = cell.getMessageObject(); boolean disableSelection = false; boolean selected = false; if (actionBar.isActionModeShowed()) { - MessageObject messageObject = cell.getMessageObject(); - if (messageObject == editingMessageObject || selectedMessagesIds[messageObject.getDialogId() == dialog_id ? 0 : 1].containsKey(messageObject.getId())) { - view.setBackgroundColor(Theme.getColor(Theme.key_chat_selectedBackground)); + int idx = messageObject.getDialogId() == dialog_id ? 0 : 1; + if (messageObject == editingMessageObject || selectedMessagesIds[idx].containsKey(messageObject.getId())) { + setCellSelectionBackground(messageObject, cell, idx); selected = true; } else { view.setBackgroundDrawable(null); @@ -9323,10 +10830,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not view.setBackgroundDrawable(null); } - cell.setMessageObject(cell.getMessageObject(), cell.isPinnedBottom(), cell.isPinnedTop()); + cell.setMessageObject(cell.getMessageObject(), cell.getCurrentMessagesGroup(), cell.isPinnedBottom(), cell.isPinnedTop()); cell.setCheckPressed(!disableSelection, disableSelection && selected); - cell.setHighlighted(highlightMessageId != Integer.MAX_VALUE && cell.getMessageObject() != null && cell.getMessageObject().getId() == highlightMessageId); - if (searchContainer != null && searchContainer.getVisibility() == View.VISIBLE && MessagesSearchQuery.getLastSearchQuery() != null) { + cell.setHighlighted(highlightMessageId != Integer.MAX_VALUE && messageObject != null && messageObject.getId() == highlightMessageId); + if (searchContainer != null && searchContainer.getVisibility() == View.VISIBLE && MessagesSearchQuery.isMessageFound(messageObject.getId(), messageObject.getDialogId() == mergeDialogId) && MessagesSearchQuery.getLastSearchQuery() != null) { cell.setHighlightedText(MessagesSearchQuery.getLastSearchQuery()); } else { cell.setHighlightedText(null); @@ -9336,6 +10843,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not cell.setMessageObject(cell.getMessageObject()); } } + chatListView.invalidate(); } private void checkEditTimer() { @@ -9352,7 +10860,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } return; } - int dt = MessagesController.getInstance().maxEditTime + 5 * 60 - Math.abs(ConnectionsManager.getInstance().getCurrentTime() - messageObject.messageOwner.date); + int dt = messageObject.canEditMessageAnytime(currentChat) ? 6 * 60 : MessagesController.getInstance().maxEditTime + 5 * 60 - Math.abs(ConnectionsManager.getInstance().getCurrentTime() - messageObject.messageOwner.date); if (dt > 0) { if (dt > 5 * 60) { if (actionModeSubTextView.getVisibility() != View.GONE) { @@ -9380,10 +10888,17 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not ArrayList messageObjects = new ArrayList<>(); messageObjects.add(startMessageObject); int messageId = startMessageObject.getId(); + long startDialogId = startMessageObject.getDialogId(); if (messageId != 0) { boolean started = false; for (int a = messages.size() - 1; a >= 0; a--) { MessageObject messageObject = messages.get(a); + if (messageObject.getDialogId() == mergeDialogId && startMessageObject.getDialogId() != mergeDialogId) { + continue; + } + /*if (startDialogId == mergeDialogId && messageId != 0 && messageObject.getDialogId() != mergeDialogId) { + messageId = 0; + }*/ if ((currentEncryptedChat == null && messageObject.getId() > messageId || currentEncryptedChat != null && messageObject.getId() < messageId) && (messageObject.isVoice() || messageObject.isRoundVideo()) && (!playingUnreadMedia || messageObject.isContentUnread() && !messageObject.isOut())) { messageObjects.add(messageObject); } @@ -9412,20 +10927,29 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not headerItem.setVisibility(View.GONE); attachItem.setVisibility(View.GONE); searchItem.setVisibility(View.VISIBLE); - updateSearchButtons(0, 0, 0); + updateSearchButtons(0, 0, -1); updateBottomOverlay(); openSearchKeyboard = text == null; searchItem.openSearch(openSearchKeyboard); if (text != null) { searchItem.getSearchField().setText(text); searchItem.getSearchField().setSelection(searchItem.getSearchField().length()); - MessagesSearchQuery.searchMessagesInChat(text, dialog_id, mergeDialogId, classGuid, 0); + MessagesSearchQuery.searchMessagesInChat(text, dialog_id, mergeDialogId, classGuid, 0, searchingUserMessages); } + updatePinnedMessageView(true); } @Override - public void updatePhotoAtIndex(int index) { - + public void didSelectLocation(TLRPC.MessageMedia location, int live) { + SendMessagesHelper.getInstance().sendMessage(location, dialog_id, replyingMessageObject, null, null); + moveScrollToLastMessage(); + if (live == 1) { + showReplyPanel(false, null, null, null, false); + DraftQuery.cleanDraft(dialog_id, true); + } + if (paused) { + scrollToTopOnResume = true; + } } public boolean isSecretChat() { @@ -9440,6 +10964,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return currentChat; } + public boolean allowGroupPhotos() { + return currentEncryptedChat == null || AndroidUtilities.getPeerLayerVersion(currentEncryptedChat.layer) >= 73; + } + public TLRPC.EncryptedChat getCurrentEncryptedChat() { return currentEncryptedChat; } @@ -9448,112 +10976,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return info; } - @Override - public boolean allowCaption() { - return true; - } - - @Override - public boolean scaleToFill() { - return false; - } - - @Override - public PhotoViewer.PlaceProviderObject getPlaceForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { - int count = chatListView.getChildCount(); - - for (int a = 0; a < count; a++) { - ImageReceiver imageReceiver = null; - View view = chatListView.getChildAt(a); - if (view instanceof ChatMessageCell) { - if (messageObject != null) { - ChatMessageCell cell = (ChatMessageCell) view; - MessageObject message = cell.getMessageObject(); - if (message != null && message.getId() == messageObject.getId()) { - imageReceiver = cell.getPhotoImage(); - } - } - } else if (view instanceof ChatActionCell) { - ChatActionCell cell = (ChatActionCell) view; - MessageObject message = cell.getMessageObject(); - if (message != null) { - if (messageObject != null) { - if (message.getId() == messageObject.getId()) { - imageReceiver = cell.getPhotoImage(); - } - } else if (fileLocation != null && message.photoThumbs != null) { - for (int b = 0; b < message.photoThumbs.size(); b++) { - TLRPC.PhotoSize photoSize = message.photoThumbs.get(b); - if (photoSize.location.volume_id == fileLocation.volume_id && photoSize.location.local_id == fileLocation.local_id) { - imageReceiver = cell.getPhotoImage(); - break; - } - } - } - } - } - - if (imageReceiver != null) { - int coords[] = new int[2]; - view.getLocationInWindow(coords); - PhotoViewer.PlaceProviderObject object = new PhotoViewer.PlaceProviderObject(); - object.viewX = coords[0]; - object.viewY = coords[1] - (Build.VERSION.SDK_INT >= 21 ? 0 : AndroidUtilities.statusBarHeight); - object.parentView = chatListView; - object.imageReceiver = imageReceiver; - object.thumb = imageReceiver.getBitmap(); - object.radius = imageReceiver.getRoundRadius(); - if (view instanceof ChatActionCell && currentChat != null) { - object.dialogId = -currentChat.id; - } - if (pinnedMessageView != null && pinnedMessageView.getTag() == null || reportSpamView != null && reportSpamView.getTag() == null) { - object.clipTopAddition = AndroidUtilities.dp(48); - } - return object; - } - } - return null; - } - - @Override - public Bitmap getThumbForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { - return null; - } - - @Override - public void willSwitchFromPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { - } - - @Override - public void willHidePhotoViewer() { - } - - @Override - public boolean isPhotoChecked(int index) { - return false; - } - - @Override - public void setPhotoChecked(int index, VideoEditedInfo videoEditedInfo) { - } - - @Override - public boolean cancelButtonPressed() { - return true; - } - - @Override - public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo) { - } - - @Override - public int getSelectedCount() { - return 0; - } - public void sendMedia(MediaController.PhotoEntry photoEntry, VideoEditedInfo videoEditedInfo) { if (photoEntry.isVideo) { - SendMessagesHelper.prepareSendingVideo(photoEntry.path, videoEditedInfo.estimatedSize, videoEditedInfo.estimatedDuration, videoEditedInfo.resultWidth, videoEditedInfo.resultHeight, videoEditedInfo, dialog_id, replyingMessageObject, photoEntry.caption != null ? photoEntry.caption.toString() : null, photoEntry.ttl); + if (videoEditedInfo != null) { + SendMessagesHelper.prepareSendingVideo(photoEntry.path, videoEditedInfo.estimatedSize, videoEditedInfo.estimatedDuration, videoEditedInfo.resultWidth, videoEditedInfo.resultHeight, videoEditedInfo, dialog_id, replyingMessageObject, photoEntry.caption != null ? photoEntry.caption.toString() : null, photoEntry.ttl); + } else { + SendMessagesHelper.prepareSendingVideo(photoEntry.path, 0, 0, 0, 0, null, dialog_id, replyingMessageObject, photoEntry.caption != null ? photoEntry.caption.toString() : null, photoEntry.ttl); + } showReplyPanel(false, null, null, null, false); DraftQuery.cleanDraft(dialog_id, true); } else { @@ -9602,7 +11031,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } public void showOpenUrlAlert(final String url, boolean ask) { - if (Browser.isInternalUrl(url) || !ask) { + if (Browser.isInternalUrl(url, null) || !ask) { Browser.openUrl(getParentActivity(), url, inlineReturn == 0); } else { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); @@ -9626,7 +11055,28 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } messages.remove(index); if (chatAdapter != null) { - chatAdapter.notifyItemRemoved(chatAdapter.messagesStartRow + messages.size() - index - 1); + chatAdapter.notifyItemRemoved(chatAdapter.messagesStartRow + index); + } + } + + private void setCellSelectionBackground(MessageObject message, ChatMessageCell messageCell, int idx) { + MessageObject.GroupedMessages groupedMessages = getValidGroupedMessage(message); + if (groupedMessages != null) { + boolean hasUnselected = false; + for (int a = 0; a < groupedMessages.messages.size(); a++) { + if (!selectedMessagesIds[idx].containsKey(groupedMessages.messages.get(a).getId())) { + hasUnselected = true; + break; + } + } + if (!hasUnselected) { + groupedMessages = null; + } + } + if (groupedMessages == null) { + messageCell.setBackgroundColor(Theme.getColor(Theme.key_chat_selectedBackground)); + } else { + messageCell.setBackground(null); } } @@ -9648,31 +11098,31 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not public void updateRows() { rowCount = 0; - if (currentUser != null && currentUser.bot) { - botInfoRow = rowCount++; - } else { - botInfoRow = -1; - } if (!messages.isEmpty()) { - if (!endReached[0] || mergeDialogId != 0 && !endReached[1]) { - loadingUpRow = rowCount++; - } else { - loadingUpRow = -1; - } - messagesStartRow = rowCount; - rowCount += messages.size(); - messagesEndRow = rowCount; if (!forwardEndReached[0] || mergeDialogId != 0 && !forwardEndReached[1]) { loadingDownRow = rowCount++; } else { loadingDownRow = -1; } + messagesStartRow = rowCount; + rowCount += messages.size(); + messagesEndRow = rowCount; + if (!endReached[0] || mergeDialogId != 0 && !endReached[1]) { + loadingUpRow = rowCount++; + } else { + loadingUpRow = -1; + } } else { loadingUpRow = -1; loadingDownRow = -1; messagesStartRow = -1; messagesEndRow = -1; } + if (currentUser != null && currentUser.bot) { + botInfoRow = rowCount++; + } else { + botInfoRow = -1; + } } @Override @@ -9705,7 +11155,34 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (chatActivityEnterView != null) { chatActivityEnterView.closeKeyboard(); } - showDialog(new ShareAlert(mContext, cell.getMessageObject(), null, ChatObject.isChannel(currentChat) && !currentChat.megagroup && currentChat.username != null && currentChat.username.length() > 0, null, false)); + MessageObject messageObject = cell.getMessageObject(); + if (UserObject.isUserSelf(currentUser) && messageObject.messageOwner.fwd_from.saved_from_peer != null) { + Bundle args = new Bundle(); + if (messageObject.messageOwner.fwd_from.saved_from_peer.channel_id != 0) { + args.putInt("chat_id", messageObject.messageOwner.fwd_from.saved_from_peer.channel_id); + } else if (messageObject.messageOwner.fwd_from.saved_from_peer.chat_id != 0) { + args.putInt("chat_id", messageObject.messageOwner.fwd_from.saved_from_peer.chat_id); + } else if (messageObject.messageOwner.fwd_from.saved_from_peer.user_id != 0) { + args.putInt("user_id", messageObject.messageOwner.fwd_from.saved_from_peer.user_id); + } + args.putInt("message_id", messageObject.messageOwner.fwd_from.saved_from_msg_id); + if (MessagesController.checkCanOpenChat(args, ChatActivity.this)) { + presentFragment(new ChatActivity(args)); + } + } else { + ArrayList arrayList = null; + if (messageObject.getGroupId() != 0) { + MessageObject.GroupedMessages groupedMessages = groupedMessagesMap.get(messageObject.getGroupId()); + if (groupedMessages != null) { + arrayList = groupedMessages.messages; + } + } + if (arrayList == null) { + arrayList = new ArrayList<>(); + arrayList.add(messageObject); + } + showDialog(new ShareAlert(mContext, arrayList, null, ChatObject.isChannel(currentChat) && !currentChat.megagroup && currentChat.username != null && currentChat.username.length() > 0, null, false)); + } } @Override @@ -9723,7 +11200,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public void didPressedChannelAvatar(ChatMessageCell cell, TLRPC.Chat chat, int postId) { if (actionBar.isActionModeShowed()) { - processRowSelect(cell); + processRowSelect(cell, true); return; } if (chat != null && chat != currentChat) { @@ -9732,7 +11209,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (postId != 0) { args.putInt("message_id", postId); } - if (MessagesController.checkCanOpenChat(args, ChatActivity.this)) { + if (MessagesController.checkCanOpenChat(args, ChatActivity.this, cell.getMessageObject())) { presentFragment(new ChatActivity(args), true); } } @@ -9745,14 +11222,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not VoIPHelper.startCall(currentUser, getParentActivity(), MessagesController.getInstance().getUserFull(currentUser.id)); } } else { - createMenu(cell, true); + createMenu(cell, true, false, false); } } @Override public void didPressedUserAvatar(ChatMessageCell cell, TLRPC.User user) { if (actionBar.isActionModeShowed()) { - processRowSelect(cell); + processRowSelect(cell, true); return; } if (user != null && user.id != UserConfig.getClientUserId()) { @@ -9785,7 +11262,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public void didLongPressed(ChatMessageCell cell) { - createMenu(cell, false); + createMenu(cell, false, false); } @Override @@ -9821,6 +11298,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else if (str.startsWith("/")) { if (URLSpanBotCommand.enabled) { chatActivityEnterView.setCommand(messageObject, str, longPress, currentChat != null && currentChat.megagroup); + if (!longPress && chatActivityEnterView.getFieldText() == null) { + showReplyPanel(false, null, null, null, false); + } } } } else { @@ -9892,7 +11372,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not public void didPressedImage(ChatMessageCell cell) { MessageObject message = cell.getMessageObject(); if (message.isSendError()) { - createMenu(cell, false); + createMenu(cell, false, false); return; } else if (message.isSending()) { return; @@ -9902,7 +11382,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not cell.invalidate(); } SecretMediaViewer.getInstance().setParentActivity(getParentActivity()); - SecretMediaViewer.getInstance().openMedia(message, ChatActivity.this); + SecretMediaViewer.getInstance().openMedia(message, photoViewerProvider); } else if (message.type == 13) { showDialog(new StickersAlert(getParentActivity(), ChatActivity.this, message.getInputStickerSet(), null, bottomOverlayChat.getVisibility() != View.VISIBLE && ChatObject.canSendStickers(currentChat) ? chatActivityEnterView : null)); } else if (message.isVideo() || message.type == 1 || message.type == 0 && !message.isWebpageDocument() || message.isGif()) { @@ -9910,7 +11390,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not sendSecretMessageRead(message); } PhotoViewer.getInstance().setParentActivity(getParentActivity()); - if (PhotoViewer.getInstance().openPhoto(message, message.type != 0 ? dialog_id : 0, message.type != 0 ? mergeDialogId : 0, ChatActivity.this)) { + if (PhotoViewer.getInstance().openPhoto(message, message.type != 0 ? dialog_id : 0, message.type != 0 ? mergeDialogId : 0, photoViewerProvider)) { PhotoViewer.getInstance().setParentChatActivity(ChatActivity.this); } } else if (message.type == 3) { @@ -9932,15 +11412,24 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } getParentActivity().startActivityForResult(intent, 500); } catch (Exception e) { + FileLog.e(e); alertUserOpenError(message); } } else if (message.type == 4) { if (!AndroidUtilities.isGoogleMapsInstalled(ChatActivity.this)) { return; } - LocationActivity fragment = new LocationActivity(); - fragment.setMessageObject(message); - presentFragment(fragment); + if (message.isLiveLocation()) { + LocationActivity fragment = new LocationActivity(2); + fragment.setMessageObject(message); + fragment.setDelegate(ChatActivity.this); + presentFragment(fragment); + } else { + LocationActivity fragment = new LocationActivity(currentEncryptedChat == null ? 3 : 0); + fragment.setMessageObject(message); + fragment.setDelegate(ChatActivity.this); + presentFragment(fragment); + } } else if (message.type == 9 || message.type == 0) { if (message.getDocumentName().endsWith("attheme")) { File locFile = null; @@ -9957,12 +11446,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } if (chatLayoutManager != null) { - int lastPosition = chatLayoutManager.findLastVisibleItemPosition(); - if (lastPosition < chatLayoutManager.getItemCount() - 1) { - scrollToPositionOnRecreate = chatLayoutManager.findFirstVisibleItemPosition(); + int lastPosition = chatLayoutManager.findFirstVisibleItemPosition(); + if (lastPosition != 0) { + scrollToPositionOnRecreate = lastPosition; RecyclerListView.Holder holder = (RecyclerListView.Holder) chatListView.findViewHolderForAdapterPosition(scrollToPositionOnRecreate); if (holder != null) { - scrollToOffsetOnRecreate = holder.itemView.getTop(); + scrollToOffsetOnRecreate = chatListView.getMeasuredHeight() - holder.itemView.getBottom() - chatListView.getPaddingBottom(); } else { scrollToPositionOnRecreate = -1; } @@ -9981,6 +11470,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not try { AndroidUtilities.openForView(message, getParentActivity()); } catch (Exception e) { + FileLog.e(e); alertUserOpenError(message); } } @@ -9995,9 +11485,19 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not ArticleViewer.getInstance().open(messageObject); } } else { - Browser.openUrl(getParentActivity(), messageObject.messageOwner.media.webpage.url); + if (messageObject.messageOwner.media != null && messageObject.messageOwner.media.webpage != null) { + Browser.openUrl(getParentActivity(), messageObject.messageOwner.media.webpage.url); + } } } + + @Override + public boolean isChatAdminCell(int uid) { + if (ChatObject.isChannel(currentChat) && currentChat.megagroup) { + return MessagesController.getInstance().isChannelAdmin(currentChat.id, uid); + } + return false; + } }); if (currentEncryptedChat == null) { chatMessageCell.setAllowAssistant(true); @@ -10011,15 +11511,15 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not PhotoViewer.getInstance().setParentActivity(getParentActivity()); TLRPC.PhotoSize photoSize = FileLoader.getClosestPhotoSizeWithSize(message.photoThumbs, 640); if (photoSize != null) { - PhotoViewer.getInstance().openPhoto(photoSize.location, ChatActivity.this); + PhotoViewer.getInstance().openPhoto(photoSize.location, photoViewerProvider); } else { - PhotoViewer.getInstance().openPhoto(message, 0, 0, ChatActivity.this); + PhotoViewer.getInstance().openPhoto(message, 0, 0, photoViewerProvider); } } @Override public void didLongPressed(ChatActionCell cell) { - createMenu(cell, false); + createMenu(cell, false, false); } @Override @@ -10074,6 +11574,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not presentFragment(fragment); } else if (url.startsWith("/")) { chatActivityEnterView.setCommand(null, url, false, false); + if (chatActivityEnterView.getFieldText() == null) { + showReplyPanel(false, null, null, null, false); + } } } }); @@ -10093,34 +11596,72 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not ChatLoadingCell loadingCell = (ChatLoadingCell) holder.itemView; loadingCell.setProgressVisible(loadsCount > 1); } else if (position >= messagesStartRow && position < messagesEndRow) { - MessageObject message = messages.get(messages.size() - (position - messagesStartRow) - 1); + MessageObject message = messages.get(position - messagesStartRow); View view = holder.itemView; if (view instanceof ChatMessageCell) { final ChatMessageCell messageCell = (ChatMessageCell) view; - messageCell.isChat = currentChat != null; - int nextType = getItemViewType(position + 1); - int prevType = getItemViewType(position - 1); - boolean pinnedBotton; - boolean pinnedTop; - if (!(message.messageOwner.reply_markup instanceof TLRPC.TL_replyInlineMarkup) && nextType == holder.getItemViewType()) { - MessageObject nextMessage = messages.get(messages.size() - (position + 1 - messagesStartRow) - 1); - pinnedBotton = nextMessage.isOutOwner() == message.isOutOwner() && (currentChat != null && nextMessage.messageOwner.from_id == message.messageOwner.from_id || currentChat == null) && Math.abs(nextMessage.messageOwner.date - message.messageOwner.date) <= 5 * 60; + messageCell.isChat = currentChat != null || UserObject.isUserSelf(currentUser); + boolean pinnedBottom = false; + boolean pinnedTop = false; + MessageObject.GroupedMessages groupedMessages = getValidGroupedMessage(message); + + int prevPosition; + int nextPosition; + if (groupedMessages != null) { + MessageObject.GroupedMessagePosition pos = groupedMessages.positions.get(message); + if (pos != null) { + if ((pos.flags & MessageObject.POSITION_FLAG_TOP) != 0) { + prevPosition = position + groupedMessages.posArray.indexOf(pos) + 1; + } else { + pinnedTop = true; + prevPosition = -100; + } + if ((pos.flags & MessageObject.POSITION_FLAG_BOTTOM) != 0) { + nextPosition = position - groupedMessages.posArray.size() + groupedMessages.posArray.indexOf(pos); + } else { + pinnedBottom = true; + nextPosition = -100; + } + } else { + prevPosition = -100; + nextPosition = -100; + } } else { - pinnedBotton = false; + nextPosition = position - 1; + prevPosition = position + 1; + } + int nextType = getItemViewType(nextPosition); + int prevType = getItemViewType(prevPosition); + if (!(message.messageOwner.reply_markup instanceof TLRPC.TL_replyInlineMarkup) && nextType == holder.getItemViewType()) { + MessageObject nextMessage = messages.get(nextPosition - messagesStartRow); + pinnedBottom = nextMessage.isOutOwner() == message.isOutOwner() && Math.abs(nextMessage.messageOwner.date - message.messageOwner.date) <= 5 * 60; + if (pinnedBottom) { + if (currentChat != null) { + pinnedBottom = nextMessage.messageOwner.from_id == message.messageOwner.from_id; + } else if (UserObject.isUserSelf(currentUser)) { + pinnedBottom = nextMessage.getFromId() == message.getFromId(); + } + } } if (prevType == holder.getItemViewType()) { - MessageObject prevMessage = messages.get(messages.size() - (position - messagesStartRow)); - pinnedTop = !(prevMessage.messageOwner.reply_markup instanceof TLRPC.TL_replyInlineMarkup) && prevMessage.isOutOwner() == message.isOutOwner() && (currentChat != null && prevMessage.messageOwner.from_id == message.messageOwner.from_id || currentChat == null) && Math.abs(prevMessage.messageOwner.date - message.messageOwner.date) <= 5 * 60; - } else { - pinnedTop = false; + MessageObject prevMessage = messages.get(prevPosition - messagesStartRow); + pinnedTop = !(prevMessage.messageOwner.reply_markup instanceof TLRPC.TL_replyInlineMarkup) && prevMessage.isOutOwner() == message.isOutOwner() && Math.abs(prevMessage.messageOwner.date - message.messageOwner.date) <= 5 * 60; + if (pinnedTop) { + if (currentChat != null) { + pinnedTop = prevMessage.messageOwner.from_id == message.messageOwner.from_id; + } else if (UserObject.isUserSelf(currentUser)) { + pinnedTop = prevMessage.getFromId() == message.getFromId(); + } + } } - messageCell.setMessageObject(message, pinnedBotton, pinnedTop); - if (view instanceof ChatMessageCell && MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_AUDIO)) { + + messageCell.setMessageObject(message, groupedMessages, pinnedBottom, pinnedTop); + if (view instanceof ChatMessageCell && MediaController.getInstance().canDownloadMedia(message)) { ((ChatMessageCell) view).downloadAudioIfNeed(); } messageCell.setHighlighted(highlightMessageId != Integer.MAX_VALUE && message.getId() == highlightMessageId); - if (searchContainer != null && searchContainer.getVisibility() == View.VISIBLE && MessagesSearchQuery.getLastSearchQuery() != null) { + if (searchContainer != null && searchContainer.getVisibility() == View.VISIBLE && MessagesSearchQuery.isMessageFound(message.getId(), message.getDialogId() == mergeDialogId) && MessagesSearchQuery.getLastSearchQuery() != null) { messageCell.setHighlightedText(MessagesSearchQuery.getLastSearchQuery()); } else { messageCell.setHighlightedText(null); @@ -10136,7 +11677,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not pipRoundVideoView.showTemporary(true); } - messageCell.getViewTreeObserver().removeOnPreDrawListener(this); //TODO + messageCell.getViewTreeObserver().removeOnPreDrawListener(this); ImageReceiver imageReceiver = messageCell.getPhotoImage(); int w = imageReceiver.getImageWidth(); org.telegram.ui.Components.Rect rect = instantCameraView.getCameraRect(); @@ -10198,13 +11739,30 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not createUnreadMessageAfterId = 0; } } + if (message != null && message.messageOwner != null && message.messageOwner.media_unread && message.messageOwner.mentioned) { + if (!message.isVoice() && !message.isRoundVideo()) { + newMentionsCount--; + if (newMentionsCount <= 0) { + newMentionsCount = 0; + hasAllMentionsLocal = true; + showMentiondownButton(false, true); + } else { + mentiondownButtonCounter.setText(String.format("%d", newMentionsCount)); + } + MessagesController.getInstance().markMentionMessageAsRead(message.getId(), ChatObject.isChannel(currentChat) ? currentChat.id : 0, dialog_id); + message.setContentIsRead(); + } + if (view instanceof ChatMessageCell) { + ((ChatMessageCell) view).setHighlightedAnimated(); + } + } } } @Override public int getItemViewType(int position) { if (position >= messagesStartRow && position < messagesEndRow) { - return messages.get(messages.size() - (position - messagesStartRow) - 1).contentType; + return messages.get(position - messagesStartRow).contentType; } else if (position == botInfoRow) { return 3; } @@ -10221,8 +11779,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not boolean disableSelection = false; if (actionBar.isActionModeShowed()) { MessageObject messageObject = chatActivityEnterView != null ? chatActivityEnterView.getEditingMessageObject() : null; - if (messageObject == message || selectedMessagesIds[message.getDialogId() == dialog_id ? 0 : 1].containsKey(message.getId())) { - messageCell.setBackgroundColor(Theme.getColor(Theme.key_chat_selectedBackground)); + int idx = message.getDialogId() == dialog_id ? 0 : 1; + if (messageObject == message || selectedMessagesIds[idx].containsKey(message.getId())) { + setCellSelectionBackground(message, messageCell, idx); selected = true; } else { messageCell.setBackgroundDrawable(null); @@ -10260,7 +11819,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (index == -1) { return; } - notifyItemChanged(messagesStartRow + messages.size() - index - 1); + notifyItemChanged(index + messagesStartRow); } @Override @@ -10275,7 +11834,6 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public void notifyItemChanged(int position) { - updateRows(); try { super.notifyItemChanged(position); } catch (Exception e) { @@ -10285,7 +11843,6 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public void notifyItemRangeChanged(int positionStart, int itemCount) { - updateRows(); try { super.notifyItemRangeChanged(positionStart, itemCount); } catch (Exception e) { @@ -10382,7 +11939,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not new ThemeDescription(avatarContainer.getTitleTextView(), 0, null, null, new Drawable[]{Theme.chat_muteIconDrawable}, null, Theme.key_chat_muteIcon), new ThemeDescription(avatarContainer.getTitleTextView(), 0, null, null, new Drawable[]{Theme.chat_lockIconDrawable}, null, Theme.key_chat_lockIcon), - new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, null, Theme.key_avatar_text), new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_avatar_backgroundRed), new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_avatar_backgroundOrange), new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_avatar_backgroundViolet), @@ -10406,7 +11963,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not new ThemeDescription(chatListView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{ChatActionCell.class}, Theme.chat_actionTextPaint, null, null, Theme.key_chat_serviceText), new ThemeDescription(chatListView, ThemeDescription.FLAG_LINKCOLOR, new Class[]{ChatActionCell.class}, Theme.chat_actionTextPaint, null, null, Theme.key_chat_serviceLink), - new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_shareIconDrawable, Theme.chat_botInlineDrawable, Theme.chat_botLinkDrawalbe}, null, Theme.key_chat_serviceIcon), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_shareIconDrawable, Theme.chat_botInlineDrawable, Theme.chat_botLinkDrawalbe, Theme.chat_goIconDrawable}, null, Theme.key_chat_serviceIcon), new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class, ChatActionCell.class}, null, null, null, Theme.key_chat_serviceBackground), new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class, ChatActionCell.class}, null, null, null, Theme.key_chat_serviceBackgroundSelected), @@ -10487,6 +12044,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inTimeText), new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outTimeText), new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inTimeSelectedText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_adminText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_adminSelectedText), new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outTimeSelectedText), new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inAudioPerfomerText), new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outAudioPerfomerText), @@ -10664,6 +12223,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not new ThemeDescription(searchUpButton, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_chat_searchPanelIcons), new ThemeDescription(searchDownButton, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_chat_searchPanelIcons), new ThemeDescription(searchCalendarButton, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_chat_searchPanelIcons), + new ThemeDescription(searchUserButton, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_chat_searchPanelIcons), new ThemeDescription(searchCountText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_chat_searchPanelText), new ThemeDescription(bottomOverlayText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_chat_secretChatStatusText), @@ -10710,6 +12270,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not new ThemeDescription(pagedownButtonImage, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_chat_goDownButtonShadow), new ThemeDescription(pagedownButtonImage, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_chat_goDownButtonIcon), + new ThemeDescription(mentiondownButtonCounter, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_chat_goDownButtonCounterBackground), + new ThemeDescription(mentiondownButtonCounter, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_chat_goDownButtonCounter), + new ThemeDescription(mentiondownButtonImage, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_chat_goDownButton), + new ThemeDescription(mentiondownButtonImage, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_chat_goDownButtonShadow), + new ThemeDescription(mentiondownButtonImage, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_chat_goDownButtonIcon), + new ThemeDescription(avatarContainer.getTimeItem(), 0, null, null, null, null, Theme.key_chat_secretTimerBackground), new ThemeDescription(avatarContainer.getTimeItem(), 0, null, null, null, null, Theme.key_chat_secretTimerText), }; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatUsersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatUsersActivity.java new file mode 100644 index 000000000..1ffdc5c31 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatUsersActivity.java @@ -0,0 +1,660 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui; + +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.FrameLayout; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ContactsController; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.R; +import org.telegram.messenger.UserConfig; +import org.telegram.messenger.Utilities; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; +import org.telegram.tgnet.TLObject; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.ActionBarMenuItem; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Cells.ManageChatUserCell; +import org.telegram.ui.Cells.TextInfoPrivacyCell; +import org.telegram.ui.Components.EmptyTextProgressView; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; + +import java.util.ArrayList; +import java.util.Timer; +import java.util.TimerTask; + +public class ChatUsersActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { + + private ListAdapter listViewAdapter; + private EmptyTextProgressView emptyView; + private RecyclerListView listView; + private SearchAdapter searchListViewAdapter; + private ActionBarMenuItem searchItem; + + private TLRPC.Chat currentChat; + + private TLRPC.ChatFull info; + + private ArrayList participants = new ArrayList<>(); + private int chatId; + private boolean loadingUsers; + private boolean firstLoaded; + + private int participantsStartRow; + private int participantsEndRow; + private int participantsInfoRow; + private int rowCount; + + private boolean searchWas; + private boolean searching; + + private final static int search_button = 0; + + public ChatUsersActivity(Bundle args) { + super(args); + chatId = arguments.getInt("chat_id"); + currentChat = MessagesController.getInstance().getChat(chatId); + } + + private void updateRows() { + currentChat = MessagesController.getInstance().getChat(chatId); + if (currentChat == null) { + return; + } + participantsStartRow = -1; + participantsEndRow = -1; + participantsInfoRow = -1; + + rowCount = 0; + if (!participants.isEmpty()) { + participantsStartRow = rowCount; + rowCount += participants.size(); + participantsEndRow = rowCount; + } else { + participantsStartRow = -1; + participantsEndRow = -1; + } + if (rowCount != 0) { + participantsInfoRow = rowCount++; + } + } + + @Override + public boolean onFragmentCreate() { + super.onFragmentCreate(); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.chatInfoDidLoaded); + fetchUsers(); + return true; + } + + private void fetchUsers() { + if (info == null) { + loadingUsers = true; + return; + } + loadingUsers = false; + participants = new ArrayList<>(info.participants.participants); + if (listViewAdapter != null) { + listViewAdapter.notifyDataSetChanged(); + } + } + + @Override + public void onFragmentDestroy() { + super.onFragmentDestroy(); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.chatInfoDidLoaded); + } + + @Override + public View createView(Context context) { + searching = false; + searchWas = false; + + actionBar.setBackButtonImage(R.drawable.ic_ab_back); + actionBar.setAllowOverlayTitle(true); + + actionBar.setTitle(LocaleController.getString("GroupMembers", R.string.GroupMembers)); + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { + @Override + public void onItemClick(int id) { + if (id == -1) { + finishFragment(); + } + } + }); + searchListViewAdapter = new SearchAdapter(context); + ActionBarMenu menu = actionBar.createMenu(); + searchItem = menu.addItem(search_button, R.drawable.ic_ab_search).setIsSearchField(true).setActionBarMenuItemSearchListener(new ActionBarMenuItem.ActionBarMenuItemSearchListener() { + @Override + public void onSearchExpand() { + searching = true; + emptyView.setShowAtCenter(true); + } + + @Override + public void onSearchCollapse() { + searchListViewAdapter.searchDialogs(null); + searching = false; + searchWas = false; + listView.setAdapter(listViewAdapter); + listViewAdapter.notifyDataSetChanged(); + listView.setFastScrollVisible(true); + listView.setVerticalScrollBarEnabled(false); + emptyView.setShowAtCenter(false); + } + + @Override + public void onTextChanged(EditText editText) { + if (searchListViewAdapter == null) { + return; + } + String text = editText.getText().toString(); + if (text.length() != 0) { + searchWas = true; + if (listView != null) { + listView.setAdapter(searchListViewAdapter); + searchListViewAdapter.notifyDataSetChanged(); + listView.setFastScrollVisible(false); + listView.setVerticalScrollBarEnabled(true); + } + } + searchListViewAdapter.searchDialogs(text); + } + }); + searchItem.getSearchField().setHint(LocaleController.getString("Search", R.string.Search)); + + fragmentView = new FrameLayout(context); + fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); + FrameLayout frameLayout = (FrameLayout) fragmentView; + + emptyView = new EmptyTextProgressView(context); + emptyView.setText(LocaleController.getString("NoResult", R.string.NoResult)); + frameLayout.addView(emptyView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + + listView = new RecyclerListView(context); + listView.setEmptyView(emptyView); + listView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); + listView.setAdapter(listViewAdapter = new ListAdapter(context)); + listView.setVerticalScrollbarPosition(LocaleController.isRTL ? RecyclerListView.SCROLLBAR_POSITION_LEFT : RecyclerListView.SCROLLBAR_POSITION_RIGHT); + frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + final TLRPC.ChatParticipant participant; + int user_id = 0; + int promoted_by = 0; + boolean canEditAdmin = false; + if (listView.getAdapter() == listViewAdapter) { + participant = listViewAdapter.getItem(position); + if (participant != null) { + user_id = participant.user_id; + } + } else { + TLObject object = searchListViewAdapter.getItem(position); + if (object instanceof TLRPC.ChatParticipant) { + participant = (TLRPC.ChatParticipant) object; + } else { + participant = null; + } + if (participant != null) { + user_id = participant.user_id; + } + } + if (user_id != 0) { + Bundle args = new Bundle(); + args.putInt("user_id", user_id); + presentFragment(new ProfileActivity(args)); + } + } + }); + + listView.setOnItemLongClickListener(new RecyclerListView.OnItemLongClickListener() { + @Override + public boolean onItemClick(View view, int position) { + return !(getParentActivity() == null || listView.getAdapter() != listViewAdapter) && createMenuForParticipant(listViewAdapter.getItem(position), false); + + } + }); + + listView.setOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + if (newState == RecyclerView.SCROLL_STATE_DRAGGING && searching && searchWas) { + AndroidUtilities.hideKeyboard(getParentActivity().getCurrentFocus()); + } + } + + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + super.onScrolled(recyclerView, dx, dy); + } + }); + + if (loadingUsers) { + emptyView.showProgress(); + } else { + emptyView.showTextView(); + } + updateRows(); + return fragmentView; + } + + private boolean createMenuForParticipant(final TLRPC.ChatParticipant participant, boolean resultOnly) { + if (participant == null) { + return false; + } + int currentUserId = UserConfig.getClientUserId(); + if (participant.user_id == currentUserId) { + return false; + } + boolean allowKick = false; + if (currentChat.creator) { + allowKick = true; + } else if (participant instanceof TLRPC.TL_chatParticipant) { + if (currentChat.admin && currentChat.admins_enabled || participant.inviter_id == currentUserId) { + allowKick = true; + } + } + if (!allowKick) { + return false; + } + if (resultOnly) { + return true; + } + ArrayList items = new ArrayList<>(); + final ArrayList actions = new ArrayList<>(); + items.add(LocaleController.getString("KickFromGroup", R.string.KickFromGroup)); + actions.add(0); + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setItems(items.toArray(new CharSequence[actions.size()]), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, final int i) { + if (actions.get(i) == 0) { + MessagesController.getInstance().deleteUserFromChat(chatId, MessagesController.getInstance().getUser(participant.user_id), info); + } + } + }); + showDialog(builder.create()); + return true; + } + + @Override + public void didReceivedNotification(int id, Object... args) { + if (id == NotificationCenter.chatInfoDidLoaded) { + TLRPC.ChatFull chatFull = (TLRPC.ChatFull) args[0]; + boolean byChannelUsers = (Boolean) args[2]; + if (chatFull.id == chatId && !byChannelUsers) { + info = chatFull; + fetchUsers(); + updateRows(); + } + } + } + + public void setInfo(TLRPC.ChatFull chatInfo) { + info = chatInfo; + } + + @Override + public void onResume() { + super.onResume(); + if (listViewAdapter != null) { + listViewAdapter.notifyDataSetChanged(); + } + } + + @Override + protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) { + if (isOpen && !backward) { + searchItem.openSearch(true); + } + } + + private class SearchAdapter extends RecyclerListView.SelectionAdapter { + + private Context mContext; + private ArrayList searchResult = new ArrayList<>(); + private ArrayList searchResultNames = new ArrayList<>(); + private Timer searchTimer; + + public SearchAdapter(Context context) { + mContext = context; + } + + public void searchDialogs(final String query) { + try { + if (searchTimer != null) { + searchTimer.cancel(); + } + } catch (Exception e) { + FileLog.e(e); + } + if (query == null) { + searchResult.clear(); + searchResultNames.clear(); + notifyDataSetChanged(); + } else { + searchTimer = new Timer(); + searchTimer.schedule(new TimerTask() { + @Override + public void run() { + try { + searchTimer.cancel(); + searchTimer = null; + } catch (Exception e) { + FileLog.e(e); + } + processSearch(query); + } + }, 200, 300); + } + } + + private void processSearch(final String query) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + final ArrayList contactsCopy = new ArrayList<>(); + contactsCopy.addAll(participants); + Utilities.searchQueue.postRunnable(new Runnable() { + @Override + public void run() { + String search1 = query.trim().toLowerCase(); + if (search1.length() == 0) { + updateSearchResults(new ArrayList(), new ArrayList()); + return; + } + String search2 = LocaleController.getInstance().getTranslitString(search1); + if (search1.equals(search2) || search2.length() == 0) { + search2 = null; + } + String search[] = new String[1 + (search2 != null ? 1 : 0)]; + search[0] = search1; + if (search2 != null) { + search[1] = search2; + } + + ArrayList resultArray = new ArrayList<>(); + ArrayList resultArrayNames = new ArrayList<>(); + + for (int a = 0; a < contactsCopy.size(); a++) { + TLRPC.ChatParticipant participant = contactsCopy.get(a); + TLRPC.User user = MessagesController.getInstance().getUser(participant.user_id); + + String name = ContactsController.formatName(user.first_name, user.last_name).toLowerCase(); + String tName = LocaleController.getInstance().getTranslitString(name); + if (name.equals(tName)) { + tName = null; + } + + int found = 0; + for (String q : search) { + if (name.startsWith(q) || name.contains(" " + q) || tName != null && (tName.startsWith(q) || tName.contains(" " + q))) { + found = 1; + } else if (user.username != null && user.username.startsWith(q)) { + found = 2; + } + + if (found != 0) { + if (found == 1) { + resultArrayNames.add(AndroidUtilities.generateSearchName(user.first_name, user.last_name, q)); + } else { + resultArrayNames.add(AndroidUtilities.generateSearchName("@" + user.username, null, "@" + q)); + } + resultArray.add(participant); + break; + } + } + } + updateSearchResults(resultArray, resultArrayNames); + } + }); + } + }); + } + + private void updateSearchResults(final ArrayList users, final ArrayList names) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + searchResult = users; + searchResultNames = names; + notifyDataSetChanged(); + } + }); + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return true; + } + + @Override + public int getItemCount() { + return searchResult.size(); + } + + public TLObject getItem(int i) { + return searchResult.get(i); + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = new ManageChatUserCell(mContext, 2, true); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + ((ManageChatUserCell) view).setDelegate(new ManageChatUserCell.ManageChatUserCellDelegate() { + @Override + public boolean onOptionsButtonCheck(ManageChatUserCell cell, boolean click) { + TLObject object = getItem((Integer) cell.getTag()); + if (object instanceof TLRPC.ChatParticipant) { + TLRPC.ChatParticipant participant = (TLRPC.ChatParticipant) getItem((Integer) cell.getTag()); + return createMenuForParticipant(participant, !click); + } else { + return false; + } + } + }); + return new RecyclerListView.Holder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + TLObject object = getItem(position); + TLRPC.User user; + if (object instanceof TLRPC.User) { + user = (TLRPC.User) object; + } else { + user = MessagesController.getInstance().getUser(((TLRPC.ChatParticipant) object).user_id); + } + + String un = user.username; + CharSequence name = searchResultNames.get(position); + CharSequence username = null; + if (name != null && un != null && un.length() > 0) { + if (name.toString().startsWith("@" + un)) { + username = name; + name = null; + } + } + + ManageChatUserCell userCell = (ManageChatUserCell) holder.itemView; + userCell.setTag(position); + userCell.setData(user, name, username); + } + + @Override + public void onViewRecycled(RecyclerView.ViewHolder holder) { + if (holder.itemView instanceof ManageChatUserCell) { + ((ManageChatUserCell) holder.itemView).recycle(); + } + } + + @Override + public int getItemViewType(int i) { + return 0; + } + } + + private class ListAdapter extends RecyclerListView.SelectionAdapter { + + private Context mContext; + + public ListAdapter(Context context) { + mContext = context; + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + int type = holder.getItemViewType(); + return type == 0 || type == 2 || type == 6; + } + + @Override + public int getItemCount() { + if (loadingUsers) { + return 0; + } + return rowCount; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + view = new ManageChatUserCell(mContext, 1, true); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + ((ManageChatUserCell) view).setDelegate(new ManageChatUserCell.ManageChatUserCellDelegate() { + @Override + public boolean onOptionsButtonCheck(ManageChatUserCell cell, boolean click) { + TLRPC.ChatParticipant participant = listViewAdapter.getItem((Integer) cell.getTag()); + return createMenuForParticipant(participant, !click); + } + }); + break; + case 1: + default: + view = new TextInfoPrivacyCell(mContext); + view.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + break; + } + return new RecyclerListView.Holder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case 0: + ManageChatUserCell userCell = (ManageChatUserCell) holder.itemView; + userCell.setTag(position); + TLRPC.ChatParticipant participant = getItem(position); + TLRPC.User user = MessagesController.getInstance().getUser(participant.user_id); + if (user != null) { + userCell.setData(user, null, null); + } + break; + case 1: + TextInfoPrivacyCell privacyCell = (TextInfoPrivacyCell) holder.itemView; + if (position == participantsInfoRow) { + privacyCell.setText(""); + } + break; + } + } + + @Override + public void onViewRecycled(RecyclerView.ViewHolder holder) { + if (holder.itemView instanceof ManageChatUserCell) { + ((ManageChatUserCell) holder.itemView).recycle(); + } + } + + @Override + public int getItemViewType(int position) { + if (position >= participantsStartRow && position < participantsEndRow) { + return 0; + } else if (position == participantsInfoRow) { + return 1; + } + return 0; + } + + public TLRPC.ChatParticipant getItem(int position) { + if (participantsStartRow != -1 && position >= participantsStartRow && position < participantsEndRow) { + return participants.get(position - participantsStartRow); + } + return null; + } + } + + @Override + public ThemeDescription[] getThemeDescriptions() { + ThemeDescription.ThemeDescriptionDelegate сellDelegate = new ThemeDescription.ThemeDescriptionDelegate() { + @Override + public void didSetColor(int color) { + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View child = listView.getChildAt(a); + if (child instanceof ManageChatUserCell) { + ((ManageChatUserCell) child).update(0); + } + } + } + }; + + return new ThemeDescription[]{ + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{ManageChatUserCell.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + new ThemeDescription(listView, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + + new ThemeDescription(listView, 0, new Class[]{ManageChatUserCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{ManageChatUserCell.class}, new String[]{"statusColor"}, null, null, сellDelegate, Theme.key_windowBackgroundWhiteGrayText), + new ThemeDescription(listView, 0, new Class[]{ManageChatUserCell.class}, new String[]{"statusOnlineColor"}, null, null, сellDelegate, Theme.key_windowBackgroundWhiteBlueText), + new ThemeDescription(listView, 0, new Class[]{ManageChatUserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundGreen), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundCyan), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundBlue), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundPink), + }; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/CommonGroupsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/CommonGroupsActivity.java index f2df4738a..df22817f6 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/CommonGroupsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/CommonGroupsActivity.java @@ -236,7 +236,7 @@ public class CommonGroupsActivity extends BaseFragment { if (holder.getItemViewType() == 0) { ProfileSearchCell cell = (ProfileSearchCell) holder.itemView; TLRPC.Chat chat = chats.get(position); - cell.setData(chat, null, null, null, false); + cell.setData(chat, null, null, null, false, false); cell.useSeparator = position != chats.size() - 1 || !endReached; } } @@ -289,7 +289,7 @@ public class CommonGroupsActivity extends BaseFragment { new ThemeDescription(listView, 0, new Class[]{LoadingCell.class}, new String[]{"progressBar"}, null, null, null, Theme.key_progressCircle), new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, Theme.dialogs_namePaint, null, null, Theme.key_chats_name), - new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, null, Theme.key_avatar_text), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java index bc1219239..a28260cb7 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java @@ -14,8 +14,11 @@ import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; import android.os.Bundle; +import android.util.TypedValue; +import android.view.Gravity; import android.view.View; import android.widget.LinearLayout; +import android.widget.TextView; import android.widget.Toast; import org.telegram.messenger.AndroidUtilities; @@ -27,6 +30,7 @@ import org.telegram.messenger.NotificationsController; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.R; import org.telegram.messenger.SecretChatHelper; +import org.telegram.messenger.UserObject; import org.telegram.messenger.Utilities; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.RequestDelegate; @@ -35,8 +39,11 @@ import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.BottomSheet; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.CacheControlActivity; import org.telegram.ui.Cells.RadioColorCell; import org.telegram.ui.Cells.TextColorCell; +import org.telegram.ui.LaunchActivity; import org.telegram.ui.ReportOtherActivity; public class AlertsCreator { @@ -160,9 +167,6 @@ public class AlertsCreator { case "USERNAME_OCCUPIED": showSimpleAlert(fragment, LocaleController.getString("UsernameInUse", R.string.UsernameInUse)); break; - case "USERNAMES_UNAVAILABLE": - showSimpleAlert(fragment, LocaleController.getString("FeatureUnavailable", R.string.FeatureUnavailable)); - break; default: showSimpleAlert(fragment, LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred)); break; @@ -612,7 +616,7 @@ public class AlertsCreator { RadioColorCell cell = new RadioColorCell(parentActivity); cell.setPadding(AndroidUtilities.dp(4), 0, AndroidUtilities.dp(4), 0); cell.setTag(a); - cell.setCheckColor(0xffb3b3b3, 0xff37a9f0); + cell.setCheckColor(Theme.getColor(Theme.key_radioBackground), Theme.getColor(Theme.key_dialogRadioBackgroundChecked)); cell.setTextAndValue(descriptions[a], selected[0] == a); linearLayout.addView(cell); cell.setOnClickListener(new View.OnClickListener() { @@ -662,6 +666,154 @@ public class AlertsCreator { return builder.create(); } + public static Dialog createLocationUpdateDialog(final Activity parentActivity, TLRPC.User user, final MessagesStorage.IntCallback callback) { + final int selected[] = new int[1]; + + String[] descriptions = new String[]{ + LocaleController.getString("SendLiveLocationFor15m", R.string.SendLiveLocationFor15m), + LocaleController.getString("SendLiveLocationFor1h", R.string.SendLiveLocationFor1h), + LocaleController.getString("SendLiveLocationFor8h", R.string.SendLiveLocationFor8h), + }; + + final LinearLayout linearLayout = new LinearLayout(parentActivity); + linearLayout.setOrientation(LinearLayout.VERTICAL); + + TextView titleTextView = new TextView(parentActivity); + if (user != null) { + titleTextView.setText(LocaleController.formatString("LiveLocationAlertPrivate", R.string.LiveLocationAlertPrivate, UserObject.getFirstName(user))); + } else { + titleTextView.setText(LocaleController.getString("LiveLocationAlertGroup", R.string.LiveLocationAlertGroup)); + } + titleTextView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); + titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + titleTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); + linearLayout.addView(titleTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 24, 0, 24, 8)); + + for (int a = 0; a < descriptions.length; a++) { + RadioColorCell cell = new RadioColorCell(parentActivity); + cell.setPadding(AndroidUtilities.dp(4), 0, AndroidUtilities.dp(4), 0); + cell.setTag(a); + cell.setCheckColor(Theme.getColor(Theme.key_radioBackground), Theme.getColor(Theme.key_dialogRadioBackgroundChecked)); + cell.setTextAndValue(descriptions[a], selected[0] == a); + linearLayout.addView(cell); + cell.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + int num = (Integer) v.getTag(); + selected[0] = num; + int count = linearLayout.getChildCount(); + for (int a = 0; a < count; a++) { + View child = linearLayout.getChildAt(a); + if (child instanceof RadioColorCell) { + ((RadioColorCell) child).setChecked(child == v, true); + } + } + } + }); + } + AlertDialog.Builder builder = new AlertDialog.Builder(parentActivity); + builder.setTopImage(new ShareLocationDrawable(parentActivity, false), Theme.getColor(Theme.key_dialogTopBackground)); + builder.setView(linearLayout); + builder.setPositiveButton(LocaleController.getString("ShareFile", R.string.ShareFile), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + int time; + if (selected[0] == 0) { + time = 15 * 60; + } else if (selected[0] == 1) { + time = 60 * 60; + } else { + time = 8 * 60 * 60; + } + callback.run(time); + } + }); + builder.setNeutralButton(LocaleController.getString("Cancel", R.string.Cancel), null); + return builder.create(); + } + + public static Dialog createFreeSpaceDialog(final LaunchActivity parentActivity) { + final int selected[] = new int[1]; + + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + int keepMedia = preferences.getInt("keep_media", 2); + if (keepMedia == 2) { + selected[0] = 3; + } else if (keepMedia == 0) { + selected[0] = 1; + } else if (keepMedia == 1) { + selected[0] = 2; + } else if (keepMedia == 3) { + selected[0] = 0; + } + + String[] descriptions = new String[]{ + LocaleController.formatPluralString("Days", 3), + LocaleController.formatPluralString("Weeks", 1), + LocaleController.formatPluralString("Months", 1), + LocaleController.getString("LowDiskSpaceNeverRemove", R.string.LowDiskSpaceNeverRemove) + }; + + final LinearLayout linearLayout = new LinearLayout(parentActivity); + linearLayout.setOrientation(LinearLayout.VERTICAL); + + TextView titleTextView = new TextView(parentActivity); + titleTextView.setText(LocaleController.getString("LowDiskSpaceTitle2", R.string.LowDiskSpaceTitle2)); + titleTextView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); + titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + titleTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + titleTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); + linearLayout.addView(titleTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 24, 0, 24, 8)); + + for (int a = 0; a < descriptions.length; a++) { + RadioColorCell cell = new RadioColorCell(parentActivity); + cell.setPadding(AndroidUtilities.dp(4), 0, AndroidUtilities.dp(4), 0); + cell.setTag(a); + cell.setCheckColor(Theme.getColor(Theme.key_radioBackground), Theme.getColor(Theme.key_dialogRadioBackgroundChecked)); + cell.setTextAndValue(descriptions[a], selected[0] == a); + linearLayout.addView(cell); + cell.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + int num = (Integer) v.getTag(); + if (num == 0) { + selected[0] = 3; + } else if (num == 1) { + selected[0] = 0; + } else if (num == 2) { + selected[0] = 1; + } else if (num == 3) { + selected[0] = 2; + } + int count = linearLayout.getChildCount(); + for (int a = 0; a < count; a++) { + View child = linearLayout.getChildAt(a); + if (child instanceof RadioColorCell) { + ((RadioColorCell) child).setChecked(child == v, true); + } + } + } + }); + } + AlertDialog.Builder builder = new AlertDialog.Builder(parentActivity); + builder.setTitle(LocaleController.getString("LowDiskSpaceTitle", R.string.LowDiskSpaceTitle)); + builder.setMessage(LocaleController.getString("LowDiskSpaceMessage", R.string.LowDiskSpaceMessage)); + builder.setView(linearLayout); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE).edit().putInt("keep_media", selected[0]).commit(); + } + }); + builder.setNeutralButton(LocaleController.getString("ClearMediaCache", R.string.ClearMediaCache), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + parentActivity.presentFragment(new CacheControlActivity()); + } + }); + return builder.create(); + } + public static Dialog createPrioritySelectDialog(Activity parentActivity, final BaseFragment parentFragment, final long dialog_id, final boolean globalGroup, final boolean globalAll, final Runnable onSelect) { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); final int selected[] = new int[1]; @@ -699,7 +851,7 @@ public class AlertsCreator { RadioColorCell cell = new RadioColorCell(parentActivity); cell.setPadding(AndroidUtilities.dp(4), 0, AndroidUtilities.dp(4), 0); cell.setTag(a); - cell.setCheckColor(0xffb3b3b3, 0xff37a9f0); + cell.setCheckColor(Theme.getColor(Theme.key_radioBackground), Theme.getColor(Theme.key_dialogRadioBackgroundChecked)); cell.setTextAndValue(descriptions[a], selected[0] == a); linearLayout.addView(cell); cell.setOnClickListener(new View.OnClickListener() { @@ -758,7 +910,7 @@ public class AlertsCreator { RadioColorCell cell = new RadioColorCell(parentActivity); cell.setTag(a); cell.setPadding(AndroidUtilities.dp(4), 0, AndroidUtilities.dp(4), 0); - cell.setCheckColor(0xffb3b3b3, 0xff37a9f0); + cell.setCheckColor(Theme.getColor(Theme.key_radioBackground), Theme.getColor(Theme.key_dialogRadioBackgroundChecked)); cell.setTextAndValue(descriptions[a], selected[0] == a); linearLayout.addView(cell); cell.setOnClickListener(new View.OnClickListener() { @@ -794,7 +946,7 @@ public class AlertsCreator { RadioColorCell cell = new RadioColorCell(parentActivity); cell.setPadding(AndroidUtilities.dp(4), 0, AndroidUtilities.dp(4), 0); cell.setTag(a); - cell.setCheckColor(0xffb3b3b3, 0xff37a9f0); + cell.setCheckColor(Theme.getColor(Theme.key_radioBackground), Theme.getColor(Theme.key_dialogRadioBackgroundChecked)); cell.setTextAndValue(options[a], selected == a); linearLayout.addView(cell); cell.setOnClickListener(new View.OnClickListener() { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AudioPlayerAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AudioPlayerAlert.java new file mode 100644 index 000000000..2d37f730f --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AudioPlayerAlert.java @@ -0,0 +1,1387 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Components; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.support.v4.content.FileProvider; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.DecelerateInterpolator; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.BuildConfig; +import org.telegram.messenger.ChatObject; +import org.telegram.messenger.ContactsController; +import org.telegram.messenger.FileLoader; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.ImageLoader; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MediaController; +import org.telegram.messenger.MessageObject; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.R; +import org.telegram.messenger.SendMessagesHelper; +import org.telegram.messenger.UserConfig; +import org.telegram.messenger.UserObject; +import org.telegram.messenger.Utilities; +import org.telegram.messenger.audioinfo.AudioInfo; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; +import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.ActionBarMenuItem; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.BottomSheet; +import org.telegram.ui.ActionBar.SimpleTextView; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Cells.AudioPlayerCell; +import org.telegram.ui.Cells.CheckBoxCell; +import org.telegram.ui.ChatActivity; +import org.telegram.ui.DialogsActivity; +import org.telegram.ui.LaunchActivity; + +import java.io.File; +import java.util.ArrayList; +import java.util.Timer; +import java.util.TimerTask; + +public class AudioPlayerAlert extends BottomSheet implements NotificationCenter.NotificationCenterDelegate, MediaController.FileDownloadProgressListener { + + private ActionBar actionBar; + private View shadow; + private View shadow2; + private ChatAvatarContainer avatarContainer; + private ActionBarMenuItem searchItem; + private ActionBarMenuItem menuItem; + private boolean searchWas; + private boolean searching; + + private RecyclerListView listView; + private LinearLayoutManager layoutManager; + private ListAdapter listAdapter; + + private FrameLayout playerLayout; + private BackupImageView placeholderImageView; + private TextView titleTextView; + private TextView authorTextView; + private ActionBarMenuItem optionsButton; + private LineProgressView progressView; + private SeekBarView seekBarView; + private SimpleTextView timeTextView; + private TextView durationTextView; + private ActionBarMenuItem shuffleButton; + private ImageView playButton; + private ImageView repeatButton; + private View[] buttons = new View[5]; + private Drawable[] playOrderButtons = new Drawable[2]; + private boolean hasOptions = true; + + private boolean scrollToSong = true; + + private boolean isInFullMode; + private AnimatorSet animatorSet; + private float fullAnimationProgress; + private float startTranslation; + private float endTranslation; + private float panelStartTranslation; + private float panelEndTranslation; + + private int searchOpenPosition = -1; + private int searchOpenOffset; + + private boolean hasNoCover; + private Drawable noCoverDrawable; + private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + + private float thumbMaxScale; + private int thumbMaxX; + private int thumbMaxY; + + private ArrayList playlist = new ArrayList<>(); + + private int scrollOffsetY = Integer.MAX_VALUE; + private int topBeforeSwitch; + private Drawable shadowDrawable; + + private boolean inFullSize; + + private AnimatorSet actionBarAnimation; + + private int lastTime; + private int TAG; + + private LaunchActivity parentActivity; + + public AudioPlayerAlert(final Context context) { + super(context, true); + + parentActivity = (LaunchActivity) context; + noCoverDrawable = context.getResources().getDrawable(R.drawable.nocover).mutate(); + noCoverDrawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_player_placeholder), PorterDuff.Mode.MULTIPLY)); + + TAG = MediaController.getInstance().generateObserverTag(); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingDidReset); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingPlayStateChanged); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingDidStarted); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingProgressDidChanged); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.musicDidLoaded); + + shadowDrawable = context.getResources().getDrawable(R.drawable.sheet_shadow).mutate(); + shadowDrawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_player_background), PorterDuff.Mode.MULTIPLY)); + paint.setColor(Theme.getColor(Theme.key_player_placeholderBackground)); + + containerView = new FrameLayout(context) { + + private boolean ignoreLayout = false; + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN && scrollOffsetY != 0 && ev.getY() < scrollOffsetY && placeholderImageView.getTranslationX() == 0) { + dismiss(); + return true; + } + return super.onInterceptTouchEvent(ev); + } + + @Override + public boolean onTouchEvent(MotionEvent e) { + return !isDismissed() && super.onTouchEvent(e); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int height = MeasureSpec.getSize(heightMeasureSpec); + int contentSize = AndroidUtilities.dp(178) + playlist.size() * AndroidUtilities.dp(56) + backgroundPaddingTop + ActionBar.getCurrentActionBarHeight() + AndroidUtilities.statusBarHeight; + int padding; + heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); + if (searching) { + padding = AndroidUtilities.dp(178) + ActionBar.getCurrentActionBarHeight() + (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0); + } else { + if (contentSize < height) { + padding = height - contentSize; + } else { + padding = (contentSize < height ? 0 : height - (height / 5 * 3)); + } + padding += ActionBar.getCurrentActionBarHeight() + (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0); + } + if (listView.getPaddingTop() != padding) { + ignoreLayout = true; + listView.setPadding(0, padding, 0, AndroidUtilities.dp(8)); + ignoreLayout = false; + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + inFullSize = getMeasuredHeight() >= height; + int availableHeight = height - ActionBar.getCurrentActionBarHeight() - (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0) - AndroidUtilities.dp(120); + int maxSize = Math.max(availableHeight, getMeasuredWidth()); + thumbMaxX = (getMeasuredWidth() - maxSize) / 2 - AndroidUtilities.dp(17); + thumbMaxY = AndroidUtilities.dp(19); + panelEndTranslation = getMeasuredHeight() - playerLayout.getMeasuredHeight(); + thumbMaxScale = maxSize / (float) placeholderImageView.getMeasuredWidth() - 1.0f; + + endTranslation = ActionBar.getCurrentActionBarHeight() + AndroidUtilities.dp(5); + int scaledHeight = (int) Math.ceil(placeholderImageView.getMeasuredHeight() * (1.0f + thumbMaxScale)); + if (scaledHeight > availableHeight) { + endTranslation -= (scaledHeight - availableHeight); + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + int y = actionBar.getMeasuredHeight(); + shadow.layout(shadow.getLeft(), y, shadow.getRight(), y + shadow.getMeasuredHeight()); + updateLayout(); + + setFullAnimationProgress(fullAnimationProgress); + } + + @Override + public void requestLayout() { + if (ignoreLayout) { + return; + } + super.requestLayout(); + } + + @Override + protected void onDraw(Canvas canvas) { + shadowDrawable.setBounds(0, Math.max(actionBar.getMeasuredHeight(), scrollOffsetY) - backgroundPaddingTop, getMeasuredWidth(), getMeasuredHeight()); + shadowDrawable.draw(canvas); + } + }; + containerView.setWillNotDraw(false); + containerView.setPadding(backgroundPaddingLeft, 0, backgroundPaddingLeft, 0); + + actionBar = new ActionBar(context); + actionBar.setBackgroundColor(Theme.getColor(Theme.key_player_actionBar)); + actionBar.setBackButtonImage(R.drawable.ic_ab_back); + actionBar.setItemsColor(Theme.getColor(Theme.key_player_actionBarItems), false); + actionBar.setItemsBackgroundColor(Theme.getColor(Theme.key_player_actionBarSelector), false); + actionBar.setTitleColor(Theme.getColor(Theme.key_player_actionBarTitle)); + actionBar.setSubtitleColor(Theme.getColor(Theme.key_player_actionBarSubtitle)); + actionBar.setAlpha(0.0f); + actionBar.setTitle("1"); + actionBar.setSubtitle("1"); + actionBar.getTitleTextView().setAlpha(0.0f); + actionBar.getSubtitleTextView().setAlpha(0.0f); + avatarContainer = new ChatAvatarContainer(context, null, false); + avatarContainer.setEnabled(false); + avatarContainer.setTitleColors(Theme.getColor(Theme.key_player_actionBarTitle), Theme.getColor(Theme.key_player_actionBarSubtitle)); + MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject(); + if (messageObject != null) { + long did = messageObject.getDialogId(); + int lower_id = (int) did; + int high_id = (int) (did >> 32); + if (lower_id != 0) { + if (lower_id > 0) { + TLRPC.User user = MessagesController.getInstance().getUser(lower_id); + avatarContainer.setTitle(ContactsController.formatName(user.first_name, user.last_name)); + avatarContainer.setUserAvatar(user); + } else { + TLRPC.Chat chat = MessagesController.getInstance().getChat(-lower_id); + avatarContainer.setTitle(chat.title); + avatarContainer.setChatAvatar(chat); + } + } else { + TLRPC.EncryptedChat encryptedChat = MessagesController.getInstance().getEncryptedChat(high_id); + TLRPC.User user = MessagesController.getInstance().getUser(encryptedChat.user_id); + avatarContainer.setTitle(ContactsController.formatName(user.first_name, user.last_name)); + avatarContainer.setUserAvatar(user); + } + } + avatarContainer.setSubtitle(LocaleController.getString("AudioTitle", R.string.AudioTitle)); + actionBar.addView(avatarContainer, 0, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 56, 0, 40, 0)); + + ActionBarMenu menu = actionBar.createMenu(); + menuItem = menu.addItem(0, R.drawable.ic_ab_other); + menuItem.addSubItem(1, LocaleController.getString("Forward", R.string.Forward)); + menuItem.addSubItem(2, LocaleController.getString("ShareFile", R.string.ShareFile)); + //menuItem.addSubItem(3, LocaleController.getString("Delete", R.string.Delete)); + menuItem.addSubItem(4, LocaleController.getString("ShowInChat", R.string.ShowInChat)); + menuItem.setTranslationX(AndroidUtilities.dp(48)); + menuItem.setAlpha(0.0f); + + searchItem = menu.addItem(0, R.drawable.ic_ab_search).setIsSearchField(true).setActionBarMenuItemSearchListener(new ActionBarMenuItem.ActionBarMenuItemSearchListener() { + @Override + public void onSearchCollapse() { + avatarContainer.setVisibility(View.VISIBLE); + if (hasOptions) { + menuItem.setVisibility(View.INVISIBLE); + } + if (searching) { + searchWas = false; + searching = false; + setAllowNestedScroll(true); + listAdapter.search(null); + } + } + + @Override + public void onSearchExpand() { + searchOpenPosition = layoutManager.findLastVisibleItemPosition(); + View firstVisView = layoutManager.findViewByPosition(searchOpenPosition); + searchOpenOffset = ((firstVisView == null) ? 0 : firstVisView.getTop()) - listView.getPaddingTop(); + + avatarContainer.setVisibility(View.GONE); + if (hasOptions) { + menuItem.setVisibility(View.GONE); + } + searching = true; + setAllowNestedScroll(false); + listAdapter.notifyDataSetChanged(); + } + + @Override + public void onTextChanged(EditText editText) { + if (editText.length() > 0) { + searchWas = true; + listAdapter.search(editText.getText().toString()); + } else { + searchWas = false; + listAdapter.search(null); + } + } + }); + EditTextBoldCursor editText = searchItem.getSearchField(); + editText.setHint(LocaleController.getString("Search", R.string.Search)); + editText.setTextColor(Theme.getColor(Theme.key_player_actionBarTitle)); + editText.setHintTextColor(Theme.getColor(Theme.key_player_time)); + editText.setCursorColor(Theme.getColor(Theme.key_player_actionBarTitle)); + + if (!AndroidUtilities.isTablet()) { + actionBar.showActionModeTop(); + actionBar.setActionModeTopColor(Theme.getColor(Theme.key_player_actionBarTop)); + } + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { + @Override + public void onItemClick(int id) { + if (id == -1) { + dismiss(); + } else { + onSubItemClick(id); + } + } + }); + + shadow = new View(context); + shadow.setAlpha(0.0f); + shadow.setBackgroundResource(R.drawable.header_shadow); + + shadow2 = new View(context); + shadow2.setAlpha(0.0f); + shadow2.setBackgroundResource(R.drawable.header_shadow); + + playerLayout = new FrameLayout(context); + playerLayout.setBackgroundColor(Theme.getColor(Theme.key_player_background)); + + placeholderImageView = new BackupImageView(context) { + + private RectF rect = new RectF(); + + @Override + protected void onDraw(Canvas canvas) { + if (hasNoCover) { + rect.set(0, 0, getMeasuredWidth(), getMeasuredHeight()); + canvas.drawRoundRect(rect, getRoundRadius(), getRoundRadius(), paint); + float plusScale = thumbMaxScale / getScaleX() / 3; + int s = (int) (AndroidUtilities.dp(63) * Math.max(plusScale / thumbMaxScale, 1.0f / thumbMaxScale)); + int x = (int) (rect.centerX() - s / 2); + int y = (int) (rect.centerY() - s / 2); + noCoverDrawable.setBounds(x, y, x + s, y + s); + noCoverDrawable.draw(canvas); + } else { + super.onDraw(canvas); + } + } + }; + placeholderImageView.setRoundRadius(AndroidUtilities.dp(20)); + placeholderImageView.setPivotX(0); + placeholderImageView.setPivotY(0); + placeholderImageView.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View view) { + if (animatorSet != null) { + animatorSet.cancel(); + animatorSet = null; + } + animatorSet = new AnimatorSet(); + if (scrollOffsetY <= actionBar.getMeasuredHeight()) { + animatorSet.playTogether(ObjectAnimator.ofFloat(AudioPlayerAlert.this, "fullAnimationProgress", isInFullMode ? 0.0f : 1.0f)); + } else { + animatorSet.playTogether(ObjectAnimator.ofFloat(AudioPlayerAlert.this, "fullAnimationProgress", isInFullMode ? 0.0f : 1.0f), + ObjectAnimator.ofFloat(actionBar, "alpha", isInFullMode ? 0.0f : 1.0f), + ObjectAnimator.ofFloat(shadow, "alpha", isInFullMode ? 0.0f : 1.0f), + ObjectAnimator.ofFloat(shadow2, "alpha", isInFullMode ? 0.0f : 1.0f)); + } + + animatorSet.setInterpolator(new DecelerateInterpolator()); + animatorSet.setDuration(250); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animation.equals(animatorSet)) { + if (!isInFullMode) { + listView.setScrollEnabled(true); + if (hasOptions) { + menuItem.setVisibility(View.INVISIBLE); + } + searchItem.setVisibility(View.VISIBLE); + } else { + if (hasOptions) { + menuItem.setVisibility(View.VISIBLE); + } + searchItem.setVisibility(View.INVISIBLE); + } + animatorSet = null; + } + } + }); + animatorSet.start(); + if (hasOptions) { + menuItem.setVisibility(View.VISIBLE); + } + searchItem.setVisibility(View.VISIBLE); + isInFullMode = !isInFullMode; + listView.setScrollEnabled(false); + if (isInFullMode) { + shuffleButton.setAdditionalOffset(-AndroidUtilities.dp(20 + 48)); + } else { + shuffleButton.setAdditionalOffset(-AndroidUtilities.dp(10)); + } + } + }); + + titleTextView = new TextView(context); + titleTextView.setTextColor(Theme.getColor(Theme.key_player_actionBarTitle)); + titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + titleTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + titleTextView.setEllipsize(TextUtils.TruncateAt.END); + titleTextView.setSingleLine(true); + playerLayout.addView(titleTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.LEFT, 72, 18, 60, 0)); + + authorTextView = new TextView(context); + authorTextView.setTextColor(Theme.getColor(Theme.key_player_time)); + authorTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + authorTextView.setEllipsize(TextUtils.TruncateAt.END); + authorTextView.setSingleLine(true); + playerLayout.addView(authorTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.LEFT, 72, 40, 60, 0)); + + optionsButton = new ActionBarMenuItem(context, null, 0, Theme.getColor(Theme.key_player_actionBarItems)); + optionsButton.setIcon(R.drawable.ic_ab_other); + optionsButton.setAdditionalOffset(-AndroidUtilities.dp(120)); + playerLayout.addView(optionsButton, LayoutHelper.createFrame(40, 40, Gravity.TOP | Gravity.RIGHT, 0, 19, 10, 0)); + optionsButton.addSubItem(1, LocaleController.getString("Forward", R.string.Forward)); + optionsButton.addSubItem(2, LocaleController.getString("ShareFile", R.string.ShareFile)); + //optionsButton.addSubItem(3, LocaleController.getString("Delete", R.string.Delete)); + optionsButton.addSubItem(4, LocaleController.getString("ShowInChat", R.string.ShowInChat)); + optionsButton.setDelegate(new ActionBarMenuItem.ActionBarMenuItemDelegate() { + @Override + public void onItemClick(int id) { + onSubItemClick(id); + } + }); + + seekBarView = new SeekBarView(context); + seekBarView.setDelegate(new SeekBarView.SeekBarViewDelegate() { + @Override + public void onSeekBarDrag(float progress) { + MediaController.getInstance().seekToProgress(MediaController.getInstance().getPlayingMessageObject(), progress); + } + }); + playerLayout.addView(seekBarView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 30, Gravity.TOP | Gravity.LEFT, 8, 62, 8, 0)); + + progressView = new LineProgressView(context); + progressView.setVisibility(View.INVISIBLE); + progressView.setBackgroundColor(Theme.getColor(Theme.key_player_progressBackground)); + progressView.setProgressColor(Theme.getColor(Theme.key_player_progress)); + playerLayout.addView(progressView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 2, Gravity.TOP | Gravity.LEFT, 20, 78, 20, 0)); + + timeTextView = new SimpleTextView(context); + timeTextView.setTextSize(12); + timeTextView.setTextColor(Theme.getColor(Theme.key_player_time)); + playerLayout.addView(timeTextView, LayoutHelper.createFrame(100, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.LEFT, 20, 92, 0, 0)); + + durationTextView = new TextView(context); + durationTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); + durationTextView.setTextColor(Theme.getColor(Theme.key_player_time)); + durationTextView.setGravity(Gravity.CENTER); + playerLayout.addView(durationTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.RIGHT, 0, 90, 20, 0)); + + FrameLayout bottomView = new FrameLayout(context) { + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + int dist = ((right - left) - AndroidUtilities.dp(8 + 48 * 5)) / 4; + for (int a = 0; a < 5; a++) { + int l = AndroidUtilities.dp(4 + 48 * a) + dist * a; + int t = AndroidUtilities.dp(9); + buttons[a].layout(l, t, l + buttons[a].getMeasuredWidth(), t + buttons[a].getMeasuredHeight()); + } + } + }; + playerLayout.addView(bottomView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 66, Gravity.TOP | Gravity.LEFT, 0, 106, 0, 0)); + + buttons[0] = shuffleButton = new ActionBarMenuItem(context, null, 0, 0); + shuffleButton.setAdditionalOffset(-AndroidUtilities.dp(10)); + bottomView.addView(shuffleButton, LayoutHelper.createFrame(48, 48, Gravity.LEFT | Gravity.TOP)); + + TextView textView = shuffleButton.addSubItem(1, LocaleController.getString("ReverseOrder", R.string.ReverseOrder)); + textView.setPadding(AndroidUtilities.dp(8), 0, AndroidUtilities.dp(16), 0); + playOrderButtons[0] = context.getResources().getDrawable(R.drawable.music_reverse).mutate(); + textView.setCompoundDrawablePadding(AndroidUtilities.dp(8)); + textView.setCompoundDrawablesWithIntrinsicBounds(playOrderButtons[0], null, null, null); + + textView = shuffleButton.addSubItem(2, LocaleController.getString("Shuffle", R.string.Shuffle)); + textView.setPadding(AndroidUtilities.dp(8), 0, AndroidUtilities.dp(16), 0); + playOrderButtons[1] = context.getResources().getDrawable(R.drawable.pl_shuffle).mutate(); + textView.setCompoundDrawablePadding(AndroidUtilities.dp(8)); + textView.setCompoundDrawablesWithIntrinsicBounds(playOrderButtons[1], null, null, null); + + shuffleButton.setDelegate(new ActionBarMenuItem.ActionBarMenuItemDelegate() { + @Override + public void onItemClick(int id) { + MediaController.getInstance().toggleShuffleMusic(id); + updateShuffleButton(); + } + }); + + ImageView prevButton; + buttons[1] = prevButton = new ImageView(context); + prevButton.setScaleType(ImageView.ScaleType.CENTER); + prevButton.setImageDrawable(Theme.createSimpleSelectorDrawable(context, R.drawable.pl_previous, Theme.getColor(Theme.key_player_button), Theme.getColor(Theme.key_player_buttonActive))); + bottomView.addView(prevButton, LayoutHelper.createFrame(48, 48, Gravity.LEFT | Gravity.TOP)); + prevButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + MediaController.getInstance().playPreviousMessage(); + } + }); + + buttons[2] = playButton = new ImageView(context); + playButton.setScaleType(ImageView.ScaleType.CENTER); + playButton.setImageDrawable(Theme.createSimpleSelectorDrawable(context, R.drawable.pl_play, Theme.getColor(Theme.key_player_button), Theme.getColor(Theme.key_player_buttonActive))); + bottomView.addView(playButton, LayoutHelper.createFrame(48, 48, Gravity.LEFT | Gravity.TOP)); + playButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (MediaController.getInstance().isDownloadingCurrentMessage()) { + return; + } + if (MediaController.getInstance().isMessagePaused()) { + MediaController.getInstance().playMessage(MediaController.getInstance().getPlayingMessageObject()); + } else { + MediaController.getInstance().pauseMessage(MediaController.getInstance().getPlayingMessageObject()); + } + } + }); + + ImageView nextButton; + buttons[3] = nextButton = new ImageView(context); + nextButton.setScaleType(ImageView.ScaleType.CENTER); + nextButton.setImageDrawable(Theme.createSimpleSelectorDrawable(context, R.drawable.pl_next, Theme.getColor(Theme.key_player_button), Theme.getColor(Theme.key_player_buttonActive))); + bottomView.addView(nextButton, LayoutHelper.createFrame(48, 48, Gravity.LEFT | Gravity.TOP)); + nextButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + MediaController.getInstance().playNextMessage(); + } + }); + + buttons[4] = repeatButton = new ImageView(context); + repeatButton.setScaleType(ImageView.ScaleType.CENTER); + repeatButton.setPadding(0, 0, AndroidUtilities.dp(8), 0); + bottomView.addView(repeatButton, LayoutHelper.createFrame(50, 48, Gravity.LEFT | Gravity.TOP)); + repeatButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + MediaController.getInstance().toggleRepeatMode(); + updateRepeatButton(); + } + }); + + listView = new RecyclerListView(context) { + + boolean ignoreLayout; + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + + if (searchOpenPosition != -1 && !actionBar.isSearchFieldVisible()) { + ignoreLayout = true; + layoutManager.scrollToPositionWithOffset(searchOpenPosition, searchOpenOffset); + super.onLayout(false, l, t, r, b); + ignoreLayout = false; + searchOpenPosition = -1; + } else if (scrollToSong) { + scrollToSong = false; + boolean found = false; + MessageObject playingMessageObject = MediaController.getInstance().getPlayingMessageObject(); + if (playingMessageObject != null) { + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View child = listView.getChildAt(a); + if (child instanceof AudioPlayerCell) { + if (((AudioPlayerCell) child).getMessageObject() == playingMessageObject) { + if (child.getBottom() <= getMeasuredHeight()) { + found = true; + } + break; + } + } + } + if (!found) { + int idx = playlist.indexOf(playingMessageObject); + if (idx >= 0) { + ignoreLayout = true; + layoutManager.scrollToPosition(playlist.size() - idx); + super.onLayout(false, l, t, r, b); + ignoreLayout = false; + } + } + } + } + } + + @Override + public void requestLayout() { + if (ignoreLayout) { + return; + } + super.requestLayout(); + } + + @Override + protected boolean allowSelectChildAtPosition(float x, float y) { + float p = playerLayout.getY() + playerLayout.getMeasuredHeight(); + return playerLayout == null || y > playerLayout.getY() + playerLayout.getMeasuredHeight(); + } + + @Override + public boolean drawChild(Canvas canvas, View child, long drawingTime) { + canvas.save(); + canvas.clipRect(0, (actionBar != null ? actionBar.getMeasuredHeight() : 0) + AndroidUtilities.dp(50), getMeasuredWidth(), getMeasuredHeight()); + boolean result = super.drawChild(canvas, child, drawingTime); + canvas.restore(); + return result; + } + }; + listView.setPadding(0, 0, 0, AndroidUtilities.dp(8)); + listView.setClipToPadding(false); + listView.setLayoutManager(layoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false)); + listView.setHorizontalScrollBarEnabled(false); + listView.setVerticalScrollBarEnabled(false); + containerView.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); + listView.setAdapter(listAdapter = new ListAdapter(context)); + listView.setGlowColor(Theme.getColor(Theme.key_dialogScrollGlow)); + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + if (view instanceof AudioPlayerCell) { + ((AudioPlayerCell) view).didPressedButton(); + } + } + }); + listView.setOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + if (newState == RecyclerView.SCROLL_STATE_DRAGGING && searching && searchWas) { + AndroidUtilities.hideKeyboard(getCurrentFocus()); + } + } + + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + updateLayout(); + } + }); + + playlist = MediaController.getInstance().getPlaylist(); + listAdapter.notifyDataSetChanged(); + + containerView.addView(playerLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 178)); + containerView.addView(shadow2, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 3)); + containerView.addView(placeholderImageView, LayoutHelper.createFrame(40, 40, Gravity.TOP | Gravity.LEFT, 17, 19, 0, 0)); + containerView.addView(shadow, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 3)); + containerView.addView(actionBar); + + updateTitle(false); + updateRepeatButton(); + updateShuffleButton(); + } + + public void setFullAnimationProgress(float value) { + fullAnimationProgress = value; + placeholderImageView.setRoundRadius(AndroidUtilities.dp(20 * (1.0f - fullAnimationProgress))); + float scale = 1.0f + thumbMaxScale * fullAnimationProgress; + placeholderImageView.setScaleX(scale); + placeholderImageView.setScaleY(scale); + float translationY = placeholderImageView.getTranslationY(); + placeholderImageView.setTranslationX(thumbMaxX * fullAnimationProgress); + placeholderImageView.setTranslationY(startTranslation + (endTranslation - startTranslation) * fullAnimationProgress); + playerLayout.setTranslationY(panelStartTranslation + (panelEndTranslation - panelStartTranslation) * fullAnimationProgress); + shadow2.setTranslationY(panelStartTranslation + (panelEndTranslation - panelStartTranslation) * fullAnimationProgress + playerLayout.getMeasuredHeight()); + menuItem.setAlpha(fullAnimationProgress); + searchItem.setAlpha(1.0f - fullAnimationProgress); + avatarContainer.setAlpha(1.0f - fullAnimationProgress); + actionBar.getTitleTextView().setAlpha(fullAnimationProgress); + actionBar.getSubtitleTextView().setAlpha(fullAnimationProgress); + } + + public float getFullAnimationProgress() { + return fullAnimationProgress; + } + + private void onSubItemClick(int id) { + final MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject(); + if (messageObject == null) { + return; + } + if (id == 1) { + Bundle args = new Bundle(); + args.putBoolean("onlySelect", true); + args.putInt("dialogsType", 3); + DialogsActivity fragment = new DialogsActivity(args); + final ArrayList fmessages = new ArrayList<>(); + fmessages.add(messageObject); + fragment.setDelegate(new DialogsActivity.DialogsActivityDelegate() { + @Override + public void didSelectDialogs(DialogsActivity fragment, ArrayList dids, CharSequence message, boolean param) { + if (dids.size() > 1 || dids.get(0) == UserConfig.getClientUserId() || message != null) { + for (int a = 0; a < dids.size(); a++) { + long did = dids.get(a); + if (message != null) { + SendMessagesHelper.getInstance().sendMessage(message.toString(), did, null, null, true, null, null, null); + } + SendMessagesHelper.getInstance().sendMessage(fmessages, did); + } + fragment.finishFragment(); + } else { + long did = dids.get(0); + int lower_part = (int) did; + int high_part = (int) (did >> 32); + Bundle args = new Bundle(); + args.putBoolean("scrollToTopOnResume", true); + if (lower_part != 0) { + if (lower_part > 0) { + args.putInt("user_id", lower_part); + } else if (lower_part < 0) { + args.putInt("chat_id", -lower_part); + } + } else { + args.putInt("enc_id", high_part); + } + NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); + ChatActivity chatActivity = new ChatActivity(args); + if (parentActivity.presentFragment(chatActivity, true, false)) { + chatActivity.showReplyPanel(true, null, fmessages, null, false); + } else { + fragment.finishFragment(); + } + } + } + }); + parentActivity.presentFragment(fragment); + dismiss(); + } else if (id == 2) { + try { + File f = null; + boolean isVideo = false; + + if (!TextUtils.isEmpty(messageObject.messageOwner.attachPath)) { + f = new File(messageObject.messageOwner.attachPath); + if (!f.exists()) { + f = null; + } + } + if (f == null) { + f = FileLoader.getPathToMessage(messageObject.messageOwner); + } + + if (f.exists()) { + Intent intent = new Intent(Intent.ACTION_SEND); + if (messageObject != null) { + intent.setType(messageObject.getMimeType()); + } else { + intent.setType("audio/mp3"); + } + if (Build.VERSION.SDK_INT >= 24) { + try { + intent.putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(ApplicationLoader.applicationContext, BuildConfig.APPLICATION_ID + ".provider", f)); + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } catch (Exception ignore) { + intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(f)); + } + } else { + intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(f)); + } + + parentActivity.startActivityForResult(Intent.createChooser(intent, LocaleController.getString("ShareFile", R.string.ShareFile)), 500); + } else { + AlertDialog.Builder builder = new AlertDialog.Builder(parentActivity); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); + builder.setMessage(LocaleController.getString("PleaseDownload", R.string.PleaseDownload)); + builder.show(); + } + } catch (Exception e) { + FileLog.e(e); + } + } else if (id == 3) { + AlertDialog.Builder builder = new AlertDialog.Builder(parentActivity); + //builder.setMessage(LocaleController.formatString("AreYouSureDeleteAudio", R.string.AreYouSureDeleteAudio)); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + + final boolean deleteForAll[] = new boolean[1]; + int lower_id = (int) messageObject.getDialogId(); + if (lower_id != 0) { + TLRPC.Chat currentChat; + TLRPC.User currentUser; + if (lower_id > 0) { + currentUser = MessagesController.getInstance().getUser(lower_id); + currentChat = null; + } else { + currentUser = null; + currentChat = MessagesController.getInstance().getChat(-lower_id); + } + if (currentUser != null || !ChatObject.isChannel(currentChat)) { + boolean hasOutgoing = false; + int currentDate = ConnectionsManager.getInstance().getCurrentTime(); + if (currentUser != null && currentUser.id != UserConfig.getClientUserId() || currentChat != null) { + if ((messageObject.messageOwner.action == null || messageObject.messageOwner.action instanceof TLRPC.TL_messageActionEmpty) && messageObject.isOut() && (currentDate - messageObject.messageOwner.date) <= 2 * 24 * 60 * 60) { + FrameLayout frameLayout = new FrameLayout(parentActivity); + CheckBoxCell cell = new CheckBoxCell(parentActivity, true); + cell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + if (currentChat != null) { + cell.setText(LocaleController.getString("DeleteForAll", R.string.DeleteForAll), "", false, false); + } else { + cell.setText(LocaleController.formatString("DeleteForUser", R.string.DeleteForUser, UserObject.getFirstName(currentUser)), "", false, false); + } + cell.setPadding(LocaleController.isRTL ? AndroidUtilities.dp(16) : AndroidUtilities.dp(8), 0, LocaleController.isRTL ? AndroidUtilities.dp(8) : AndroidUtilities.dp(16), 0); + frameLayout.addView(cell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.TOP | Gravity.LEFT, 0, 0, 0, 0)); + cell.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + CheckBoxCell cell = (CheckBoxCell) v; + deleteForAll[0] = !deleteForAll[0]; + cell.setChecked(deleteForAll[0], true); + } + }); + builder.setView(frameLayout); + } + } + } + } + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + dismiss(); + ArrayList arr = new ArrayList<>(); + arr.add(messageObject.getId()); + ArrayList random_ids = null; + TLRPC.EncryptedChat encryptedChat = null; + if ((int) messageObject.getDialogId() == 0 && messageObject.messageOwner.random_id != 0) { + random_ids = new ArrayList<>(); + random_ids.add(messageObject.messageOwner.random_id); + encryptedChat = MessagesController.getInstance().getEncryptedChat((int) (messageObject.getDialogId() >> 32)); + } + MessagesController.getInstance().deleteMessages(arr, random_ids, encryptedChat, messageObject.messageOwner.to_id.channel_id, deleteForAll[0]); + } + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + builder.show(); + } else if (id == 4) { + Bundle args = new Bundle(); + long did = messageObject.getDialogId(); + int lower_part = (int) did; + int high_id = (int) (did >> 32); + if (lower_part != 0) { + if (high_id == 1) { + args.putInt("chat_id", lower_part); + } else { + if (lower_part > 0) { + args.putInt("user_id", lower_part); + } else if (lower_part < 0) { + TLRPC.Chat chat = MessagesController.getInstance().getChat(-lower_part); + if (chat != null && chat.migrated_to != null) { + args.putInt("migrated_to", lower_part); + lower_part = -chat.migrated_to.channel_id; + } + args.putInt("chat_id", -lower_part); + } + } + } else { + args.putInt("enc_id", high_id); + } + args.putInt("message_id", messageObject.getId()); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); + parentActivity.presentFragment(new ChatActivity(args), false, false); + dismiss(); + } + } + + private int getCurrentTop() { + if (listView.getChildCount() != 0) { + View child = listView.getChildAt(0); + RecyclerListView.Holder holder = (RecyclerListView.Holder) listView.findContainingViewHolder(child); + if (holder != null) { + return listView.getPaddingTop() - (holder.getAdapterPosition() == 0 && child.getTop() >= 0 ? child.getTop() : 0); + } + } + return -1000; + } + + @Override + public void didReceivedNotification(int id, Object... args) { + if (id == NotificationCenter.messagePlayingDidStarted || id == NotificationCenter.messagePlayingPlayStateChanged || id == NotificationCenter.messagePlayingDidReset) { + updateTitle(id == NotificationCenter.messagePlayingDidReset && (Boolean) args[1]); + if (id == NotificationCenter.messagePlayingDidReset || id == NotificationCenter.messagePlayingPlayStateChanged) { + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View view = listView.getChildAt(a); + if (view instanceof AudioPlayerCell) { + AudioPlayerCell cell = (AudioPlayerCell) view; + MessageObject messageObject = cell.getMessageObject(); + if (messageObject != null && (messageObject.isVoice() || messageObject.isMusic())) { + cell.updateButtonState(false); + } + } + } + } else if (id == NotificationCenter.messagePlayingDidStarted) { + MessageObject messageObject = (MessageObject) args[0]; + if (messageObject.eventId != 0) { + return; + } + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View view = listView.getChildAt(a); + if (view instanceof AudioPlayerCell) { + AudioPlayerCell cell = (AudioPlayerCell) view; + MessageObject messageObject1 = cell.getMessageObject(); + if (messageObject1 != null && (messageObject1.isVoice() || messageObject1.isMusic())) { + cell.updateButtonState(false); + } + } + } + } + } else if (id == NotificationCenter.messagePlayingProgressDidChanged) { + MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject(); + if (messageObject != null && messageObject.isMusic()) { + updateProgress(messageObject); + } + } else if (id == NotificationCenter.musicDidLoaded) { + playlist = MediaController.getInstance().getPlaylist(); + listAdapter.notifyDataSetChanged(); + } + } + + @Override + protected boolean canDismissWithSwipe() { + return false; + } + + private void updateLayout() { + if (listView.getChildCount() <= 0) { + return; + } + View child = listView.getChildAt(0); + RecyclerListView.Holder holder = (RecyclerListView.Holder) listView.findContainingViewHolder(child); + int top = child.getTop(); + int newOffset = top > 0 && holder != null && holder.getAdapterPosition() == 0 ? top : 0; + if (searchWas || searching) { + newOffset = 0; + } + if (scrollOffsetY != newOffset) { + listView.setTopGlowOffset(scrollOffsetY = newOffset); + playerLayout.setTranslationY(Math.max(actionBar.getMeasuredHeight(), scrollOffsetY)); + placeholderImageView.setTranslationY(Math.max(actionBar.getMeasuredHeight(), scrollOffsetY)); + shadow2.setTranslationY(Math.max(actionBar.getMeasuredHeight(), scrollOffsetY) + playerLayout.getMeasuredHeight()); + containerView.invalidate(); + + if (inFullSize && scrollOffsetY <= actionBar.getMeasuredHeight() || searchWas) { + if (actionBar.getTag() == null) { + if (actionBarAnimation != null) { + actionBarAnimation.cancel(); + } + actionBar.setTag(1); + actionBarAnimation = new AnimatorSet(); + actionBarAnimation.playTogether( + ObjectAnimator.ofFloat(actionBar, "alpha", 1.0f), + ObjectAnimator.ofFloat(shadow, "alpha", 1.0f), + ObjectAnimator.ofFloat(shadow2, "alpha", 1.0f)); + actionBarAnimation.setDuration(180); + actionBarAnimation.start(); + } + } else { + if (actionBar.getTag() != null) { + if (actionBarAnimation != null) { + actionBarAnimation.cancel(); + } + actionBar.setTag(null); + actionBarAnimation = new AnimatorSet(); + actionBarAnimation.playTogether( + ObjectAnimator.ofFloat(actionBar, "alpha", 0.0f), + ObjectAnimator.ofFloat(shadow, "alpha", 0.0f), + ObjectAnimator.ofFloat(shadow2, "alpha", 0.0f)); + actionBarAnimation.setDuration(180); + actionBarAnimation.start(); + } + } + } + + startTranslation = Math.max(actionBar.getMeasuredHeight(), scrollOffsetY); + panelStartTranslation = Math.max(actionBar.getMeasuredHeight(), scrollOffsetY); + } + + @Override + public void dismiss() { + super.dismiss(); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagePlayingDidReset); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagePlayingPlayStateChanged); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagePlayingDidStarted); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagePlayingProgressDidChanged); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.musicDidLoaded); + MediaController.getInstance().removeLoadingFileObserver(this); + } + + @Override + public void onBackPressed() { + if (actionBar != null && actionBar.isSearchFieldVisible()) { + actionBar.closeSearchField(); + return; + } + super.onBackPressed(); + } + + @Override + public void onFailedDownload(String fileName) { + + } + + @Override + public void onSuccessDownload(String fileName) { + + } + + @Override + public void onProgressDownload(String fileName, float progress) { + progressView.setProgress(progress, true); + } + + @Override + public void onProgressUpload(String fileName, float progress, boolean isEncrypted) { + + } + + @Override + public int getObserverTag() { + return TAG; + } + + private void updateShuffleButton() { + if (MediaController.getInstance().isShuffleMusic()) { + Drawable drawable = getContext().getResources().getDrawable(R.drawable.pl_shuffle).mutate(); + drawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_player_buttonActive), PorterDuff.Mode.MULTIPLY)); + shuffleButton.setIcon(drawable); + } else { + Drawable drawable = getContext().getResources().getDrawable(R.drawable.music_reverse).mutate(); + if (MediaController.getInstance().isPlayOrderReversed()) { + drawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_player_buttonActive), PorterDuff.Mode.MULTIPLY)); + } else { + drawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_player_button), PorterDuff.Mode.MULTIPLY)); + } + shuffleButton.setIcon(drawable); + } + + playOrderButtons[0].setColorFilter(new PorterDuffColorFilter(Theme.getColor(MediaController.getInstance().isPlayOrderReversed() ? Theme.key_player_buttonActive : Theme.key_player_button), PorterDuff.Mode.MULTIPLY)); + playOrderButtons[1].setColorFilter(new PorterDuffColorFilter(Theme.getColor(MediaController.getInstance().isShuffleMusic() ? Theme.key_player_buttonActive : Theme.key_player_button), PorterDuff.Mode.MULTIPLY)); + } + + private void updateRepeatButton() { + int mode = MediaController.getInstance().getRepeatMode(); + if (mode == 0) { + repeatButton.setImageResource(R.drawable.pl_repeat); + repeatButton.setTag(Theme.key_player_button); + repeatButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_player_button), PorterDuff.Mode.MULTIPLY)); + } else if (mode == 1) { + repeatButton.setImageResource(R.drawable.pl_repeat); + repeatButton.setTag(Theme.key_player_buttonActive); + repeatButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_player_buttonActive), PorterDuff.Mode.MULTIPLY)); + } else if (mode == 2) { + repeatButton.setImageResource(R.drawable.pl_repeat1); + repeatButton.setTag(Theme.key_player_buttonActive); + repeatButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_player_buttonActive), PorterDuff.Mode.MULTIPLY)); + } + } + + private void updateProgress(MessageObject messageObject) { + if (seekBarView != null) { + if (!seekBarView.isDragging()) { + seekBarView.setProgress(messageObject.audioProgress); + } + if (lastTime != messageObject.audioProgressSec) { + lastTime = messageObject.audioProgressSec; + timeTextView.setText(String.format("%d:%02d", messageObject.audioProgressSec / 60, messageObject.audioProgressSec % 60)); + } + } + } + + private void checkIfMusicDownloaded(MessageObject messageObject) { + File cacheFile = null; + if (messageObject.messageOwner.attachPath != null && messageObject.messageOwner.attachPath.length() > 0) { + cacheFile = new File(messageObject.messageOwner.attachPath); + if (!cacheFile.exists()) { + cacheFile = null; + } + } + if (cacheFile == null) { + cacheFile = FileLoader.getPathToMessage(messageObject.messageOwner); + } + if (!cacheFile.exists()) { + String fileName = messageObject.getFileName(); + MediaController.getInstance().addLoadingFileObserver(fileName, this); + Float progress = ImageLoader.getInstance().getFileProgress(fileName); + progressView.setProgress(progress != null ? progress : 0, false); + progressView.setVisibility(View.VISIBLE); + seekBarView.setVisibility(View.INVISIBLE); + playButton.setEnabled(false); + } else { + MediaController.getInstance().removeLoadingFileObserver(this); + progressView.setVisibility(View.INVISIBLE); + seekBarView.setVisibility(View.VISIBLE); + playButton.setEnabled(true); + } + } + + private void updateTitle(boolean shutdown) { + MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject(); + if (messageObject == null && shutdown || messageObject != null && !messageObject.isMusic()) { + dismiss(); + } else { + if (messageObject == null) { + return; + } + if (messageObject.eventId != 0) { + hasOptions = false; + menuItem.setVisibility(View.INVISIBLE); + optionsButton.setVisibility(View.INVISIBLE); + } else { + hasOptions = true; + if (!actionBar.isSearchFieldVisible()) { + menuItem.setVisibility(View.VISIBLE); + } + optionsButton.setVisibility(View.VISIBLE); + } + checkIfMusicDownloaded(messageObject); + updateProgress(messageObject); + + if (MediaController.getInstance().isMessagePaused()) { + playButton.setImageDrawable(Theme.createSimpleSelectorDrawable(playButton.getContext(), R.drawable.pl_play, Theme.getColor(Theme.key_player_button), Theme.getColor(Theme.key_player_buttonActive))); + } else { + playButton.setImageDrawable(Theme.createSimpleSelectorDrawable(playButton.getContext(), R.drawable.pl_pause, Theme.getColor(Theme.key_player_button), Theme.getColor(Theme.key_player_buttonActive))); + } + String title = messageObject.getMusicTitle(); + String author = messageObject.getMusicAuthor(); + titleTextView.setText(title); + authorTextView.setText(author); + actionBar.setTitle(title); + actionBar.setSubtitle(author); + + AudioInfo audioInfo = MediaController.getInstance().getAudioInfo(); + if (audioInfo != null && audioInfo.getCover() != null) { + hasNoCover = false; + placeholderImageView.setImageBitmap(audioInfo.getCover()); + } else { + hasNoCover = true; + placeholderImageView.invalidate(); + placeholderImageView.setImageDrawable(null); + } + + if (durationTextView != null) { + int duration = 0; + TLRPC.Document document = messageObject.getDocument(); + if (document != null) { + for (int a = 0; a < document.attributes.size(); a++) { + TLRPC.DocumentAttribute attribute = document.attributes.get(a); + if (attribute instanceof TLRPC.TL_documentAttributeAudio) { + duration = attribute.duration; + break; + } + } + } + durationTextView.setText(duration != 0 ? String.format("%d:%02d", duration / 60, duration % 60) : "-:--"); + } + } + } + + private class ListAdapter extends RecyclerListView.SelectionAdapter { + + private Context context; + private ArrayList searchResult = new ArrayList<>(); + private Timer searchTimer; + + public ListAdapter(Context context) { + this.context = context; + } + + @Override + public int getItemCount() { + if (searchWas) { + return searchResult.size(); + } else if (searching) { + return playlist.size(); + } + return 1 + playlist.size(); + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return searchWas || holder.getAdapterPosition() > 0; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + view = new View(context); + view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, AndroidUtilities.dp(178))); + break; + case 1: + default: + view = new AudioPlayerCell(context); + break; + } + return new RecyclerListView.Holder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if (holder.getItemViewType() == 1) { + AudioPlayerCell cell = (AudioPlayerCell) holder.itemView; + if (searchWas) { + cell.setMessageObject(searchResult.get(position)); + } else if (searching) { + cell.setMessageObject(playlist.get(playlist.size() - position - 1)); + } else if (position > 0) { + cell.setMessageObject(playlist.get(playlist.size() - position)); + } + } + } + + @Override + public int getItemViewType(int i) { + if (searchWas || searching) { + return 1; + } + if (i == 0) { + return 0; + } + return 1; + } + + public void search(final String query) { + try { + if (searchTimer != null) { + searchTimer.cancel(); + } + } catch (Exception e) { + FileLog.e(e); + } + if (query == null) { + searchResult.clear(); + notifyDataSetChanged(); + } else { + searchTimer = new Timer(); + searchTimer.schedule(new TimerTask() { + @Override + public void run() { + try { + searchTimer.cancel(); + searchTimer = null; + } catch (Exception e) { + FileLog.e(e); + } + processSearch(query); + } + }, 200, 300); + } + } + + private void processSearch(final String query) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + final ArrayList copy = new ArrayList<>(); + copy.addAll(playlist); + Utilities.searchQueue.postRunnable(new Runnable() { + @Override + public void run() { + String search1 = query.trim().toLowerCase(); + if (search1.length() == 0) { + updateSearchResults(new ArrayList()); + return; + } + String search2 = LocaleController.getInstance().getTranslitString(search1); + if (search1.equals(search2) || search2.length() == 0) { + search2 = null; + } + String search[] = new String[1 + (search2 != null ? 1 : 0)]; + search[0] = search1; + if (search2 != null) { + search[1] = search2; + } + + ArrayList resultArray = new ArrayList<>(); + + for (int a = 0; a < copy.size(); a++) { + MessageObject messageObject = copy.get(a); + for (int b = 0; b < search.length; b++) { + String q = search[b]; + String name = messageObject.getDocumentName(); + if (name == null || name.length() == 0) { + continue; + } + name = name.toLowerCase(); + if (name.contains(q)) { + resultArray.add(messageObject); + break; + } + TLRPC.Document document; + if (messageObject.type == 0) { + document = messageObject.messageOwner.media.webpage.document; + } else { + document = messageObject.messageOwner.media.document; + } + boolean ok = false; + for (int c = 0; c < document.attributes.size(); c++) { + TLRPC.DocumentAttribute attribute = document.attributes.get(c); + if (attribute instanceof TLRPC.TL_documentAttributeAudio) { + if (attribute.performer != null) { + ok = attribute.performer.toLowerCase().contains(q); + } + if (!ok && attribute.title != null) { + ok = attribute.title.toLowerCase().contains(q); + } + break; + } + } + if (ok) { + resultArray.add(messageObject); + break; + } + } + } + + updateSearchResults(resultArray); + } + }); + } + }); + } + + private void updateSearchResults(final ArrayList documents) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + searchResult = documents; + notifyDataSetChanged(); + layoutManager.scrollToPosition(0); + } + }); + } + } +} \ No newline at end of file diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarDrawable.java index 6e9f96722..dcac2f4d3 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarDrawable.java @@ -34,6 +34,7 @@ public class AvatarDrawable extends Drawable { private float textLeft; private boolean isProfile; private boolean drawBrodcast; + private int savedMessages; private boolean drawPhoto; private StringBuilder stringBuilder = new StringBuilder(5); @@ -74,7 +75,7 @@ public class AvatarDrawable extends Drawable { } public static int getColorIndex(int id) { - if (id >= 0 && id < 8) { + if (id >= 0 && id < 7) { return id; } return Math.abs(id % Theme.keys_avatar_background.length); @@ -114,6 +115,11 @@ public class AvatarDrawable extends Drawable { } } + public void setSavedMessages(int value) { + savedMessages = value; + color = Theme.getColor(Theme.key_avatar_backgroundSaved); + } + public void setInfo(TLRPC.Chat chat) { if (chat != null) { setInfo(chat.id, chat.title, null, chat.id < 0, null); @@ -144,6 +150,7 @@ public class AvatarDrawable extends Drawable { } drawBrodcast = isBroadcast; + savedMessages = 0; if (firstName == null || firstName.length() == 0) { firstName = lastName; @@ -165,13 +172,17 @@ public class AvatarDrawable extends Drawable { } lastch = lastName.codePointAt(a); } - stringBuilder.append("\u200C"); + if (Build.VERSION.SDK_INT >= 17) { + stringBuilder.append("\u200C"); + } stringBuilder.appendCodePoint(lastch); } else if (firstName != null && firstName.length() > 0) { for (int a = firstName.length() - 1; a >= 0; a--) { if (firstName.charAt(a) == ' ') { if (a != firstName.length() - 1 && firstName.charAt(a + 1) != ' ') { - stringBuilder.append("\u200C"); + if (Build.VERSION.SDK_INT >= 17) { + stringBuilder.append("\u200C"); + } stringBuilder.appendCodePoint(firstName.codePointAt(a + 1)); break; } @@ -212,9 +223,20 @@ public class AvatarDrawable extends Drawable { Theme.avatar_backgroundPaint.setColor(color); canvas.save(); canvas.translate(bounds.left, bounds.top); - canvas.drawCircle(size / 2, size / 2, size / 2, Theme.avatar_backgroundPaint); + canvas.drawCircle(size / 2.0f, size / 2.0f, size / 2.0f, Theme.avatar_backgroundPaint); - if (drawBrodcast && Theme.avatar_broadcastDrawable != null) { + if (savedMessages != 0 && Theme.avatar_savedDrawable != null) { + int w = Theme.avatar_savedDrawable.getIntrinsicWidth(); + int h = Theme.avatar_savedDrawable.getIntrinsicHeight(); + if (savedMessages == 2) { + w *= 0.8f; + h *= 0.8f; + } + int x = (size - w) / 2; + int y = (size - h) / 2; + Theme.avatar_savedDrawable.setBounds(x, y, x + w, y + h); + Theme.avatar_savedDrawable.draw(canvas); + } else if (drawBrodcast && Theme.avatar_broadcastDrawable != null) { int x = (size - Theme.avatar_broadcastDrawable.getIntrinsicWidth()) / 2; int y = (size - Theme.avatar_broadcastDrawable.getIntrinsicHeight()) / 2; Theme.avatar_broadcastDrawable.setBounds(x, y, x + Theme.avatar_broadcastDrawable.getIntrinsicWidth(), y + Theme.avatar_broadcastDrawable.getIntrinsicHeight()); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarUpdater.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarUpdater.java index adaa0bf00..d44b70343 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarUpdater.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarUpdater.java @@ -24,6 +24,7 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.BuildConfig; import org.telegram.messenger.ImageLoader; import org.telegram.messenger.MediaController; +import org.telegram.messenger.SendMessagesHelper; import org.telegram.messenger.VideoEditedInfo; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLRPC; @@ -39,7 +40,6 @@ import org.telegram.ui.PhotoViewer; import java.io.File; import java.util.ArrayList; -import java.util.Objects; public class AvatarUpdater implements NotificationCenter.NotificationCenterDelegate, PhotoCropActivity.PhotoEditActivityDelegate { @@ -103,9 +103,9 @@ public class AvatarUpdater implements NotificationCenter.NotificationCenterDeleg PhotoAlbumPickerActivity fragment = new PhotoAlbumPickerActivity(true, false, false, null); fragment.setDelegate(new PhotoAlbumPickerActivity.PhotoAlbumPickerActivityDelegate() { @Override - public void didSelectPhotos(ArrayList photos, ArrayList captions, ArrayList ttls, ArrayList videos, ArrayList> masks, ArrayList webPhotos) { + public void didSelectPhotos(ArrayList photos) { if (!photos.isEmpty()) { - Bitmap bitmap = ImageLoader.loadBitmap(photos.get(0), null, 800, 800, true); + Bitmap bitmap = ImageLoader.loadBitmap(photos.get(0).path, null, 800, 800, true); processBitmap(bitmap); } } @@ -188,6 +188,11 @@ public class AvatarUpdater implements NotificationCenter.NotificationCenterDeleg public boolean allowCaption() { return false; } + + @Override + public boolean canScrollAway() { + return false; + } }, null); AndroidUtilities.addMediaToGallery(currentPicturePath); currentPicturePath = null; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/BackupImageView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/BackupImageView.java index dce281ad6..4c80d04cc 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/BackupImageView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/BackupImageView.java @@ -11,6 +11,8 @@ package org.telegram.ui.Components; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.util.AttributeSet; @@ -94,12 +96,26 @@ public class BackupImageView extends View { invalidate(); } + public void setImageResource(int resId, int color) { + Drawable drawable = getResources().getDrawable(resId); + if (drawable != null) { + drawable.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + } + imageReceiver.setImageBitmap(drawable); + invalidate(); + } + public void setImageDrawable(Drawable drawable) { imageReceiver.setImageBitmap(drawable); } public void setRoundRadius(int value) { imageReceiver.setRoundRadius(value); + invalidate(); + } + + public int getRoundRadius() { + return imageReceiver.getRoundRadius(); } public void setAspectFit(boolean value) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/BotKeyboardView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/BotKeyboardView.java index c6adb276a..2804cb6fe 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/BotKeyboardView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/BotKeyboardView.java @@ -32,6 +32,7 @@ public class BotKeyboardView extends LinearLayout { private boolean isFullSize; private int buttonHeight; private ArrayList buttonViews = new ArrayList<>(); + private ScrollView scrollView; public interface BotKeyboardViewDelegate { void didPressedButton(TLRPC.KeyboardButton button); @@ -42,7 +43,7 @@ public class BotKeyboardView extends LinearLayout { setOrientation(VERTICAL); - ScrollView scrollView = new ScrollView(context); + scrollView = new ScrollView(context); addView(scrollView); container = new LinearLayout(context); container.setOrientation(VERTICAL); @@ -87,6 +88,7 @@ public class BotKeyboardView extends LinearLayout { botButtons = buttons; container.removeAllViews(); buttonViews.clear(); + scrollView.scrollTo(0, 0); if (buttons != null && botButtons.rows.size() != 0) { isFullSize = !buttons.resize; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java index 4177c25c2..7e6d71534 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java @@ -85,6 +85,7 @@ import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.ChatActivity; import org.telegram.ui.DialogsActivity; +import org.telegram.ui.GroupStickersActivity; import org.telegram.ui.StickersActivity; import java.io.File; @@ -218,6 +219,8 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe private int editingMessageReqId; private boolean editingCaption; + private TLRPC.ChatFull info; + private boolean hasRecordVideo; private int currentPopupContentType = -1; @@ -305,22 +308,25 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe lastKnownPage = curPage; boolean prevOpen = stickersTabOpen; stickersTabOpen = curPage == 1 || curPage == 2; - if (prevOpen != stickersTabOpen && curPage != 2) - checkSendButton(false); - if (!stickersTabOpen && stickersExpanded) + if (prevOpen != stickersTabOpen) { + checkSendButton(true); + } + if (!stickersTabOpen && stickersExpanded) { setStickersExpanded(false, true); + } } } } }; - private Property roundedTranslationYProperty=new Property(Integer.class, "translationY"){ + + private Property roundedTranslationYProperty = new Property(Integer.class, "translationY") { @Override - public Integer get(View object){ + public Integer get(View object) { return Math.round(object.getTranslationY()); } @Override - public void set(View object, Integer value){ + public void set(View object, Integer value) { object.setTranslationY(value); } }; @@ -1167,7 +1173,6 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe recordTimeContainer.addView(recordDot, LayoutHelper.createLinear(11, 11, Gravity.CENTER_VERTICAL, 0, 1, 0, 0)); recordTimeText = new TextView(context); - recordTimeText.setText("00:00"); recordTimeText.setTextColor(Theme.getColor(Theme.key_chat_recordTime)); recordTimeText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); recordTimeContainer.addView(recordTimeText, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, 6, 0, 0, 0)); @@ -1369,8 +1374,12 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe expandStickersButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - if (!stickersDragging) + if (expandStickersButton.getVisibility() != VISIBLE || expandStickersButton.getAlpha() != 1.0f) { + return; + } + if (!stickersDragging) { setStickersExpanded(!stickersExpanded, true); + } } }); @@ -1434,6 +1443,10 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe return false; } + public boolean isSendButtonVisible() { + return sendButton.getVisibility() == VISIBLE; + } + private void setRecordVideoButtonVisible(boolean visible, boolean animated) { if (videoSendButton == null) { return; @@ -1546,9 +1559,14 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe setEmojiButtonImage(); } + public void addEmojiToRecent(String code) { + createEmojiView(); + emojiView.addEmojiToRecent(code); + } + public void setOpenGifsTabFirst() { createEmojiView(); - StickersQuery.loadRecents(StickersQuery.TYPE_IMAGE, true, true); + StickersQuery.loadRecents(StickersQuery.TYPE_IMAGE, true, true, false); emojiView.switchToGifRecent(); } @@ -1848,6 +1866,13 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe updateFieldHint(); } + public void setChatInfo(TLRPC.ChatFull chatInfo) { + info = chatInfo; + if (emojiView != null) { + emojiView.setChatInfo(info); + } + } + public void checkRoundVideo() { if (hasRecordVideo) { return; @@ -1875,6 +1900,9 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe hasRecordVideo = false; } } + if (!MediaController.getInstance().canInAppCamera()) { + hasRecordVideo = false; + } if (hasRecordVideo) { CameraController.getInstance().initCamera(); SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); @@ -2444,7 +2472,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe recordPanel.setVisibility(VISIBLE); recordCircle.setVisibility(VISIBLE); recordCircle.setAmplitude(0); - recordTimeText.setText("00:00"); + recordTimeText.setText(String.format("%02d:%02d.%02d", 0, 0, 0)); recordDot.resetAlpha(); lastTimeString = null; lastTypingSendTime = -1; @@ -2553,9 +2581,9 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe } else { TLRPC.User user = messageObject != null && (int) dialog_id < 0 ? MessagesController.getInstance().getUser(messageObject.messageOwner.from_id) : null; if ((botCount != 1 || username) && user != null && user.bot && !command.contains("@")) { - SendMessagesHelper.getInstance().sendMessage(String.format(Locale.US, "%s@%s", command, user.username), dialog_id, null, null, false, null, null, null); + SendMessagesHelper.getInstance().sendMessage(String.format(Locale.US, "%s@%s", command, user.username), dialog_id, replyingMessageObject, null, false, null, null, null); } else { - SendMessagesHelper.getInstance().sendMessage(command, dialog_id, null, null, false, null, null, null); + SendMessagesHelper.getInstance().sendMessage(command, dialog_id, replyingMessageObject, null, false, null, null, null); } } } @@ -2732,10 +2760,13 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe return 0; } - public void replaceWithText(int start, int len, CharSequence text) { + public void replaceWithText(int start, int len, CharSequence text, boolean parseEmoji) { try { SpannableStringBuilder builder = new SpannableStringBuilder(messageEditText.getText()); builder.replace(start, start + len, text); + if (parseEmoji) { + Emoji.replaceEmoji(builder, messageEditText.getPaint().getFontMetricsInt(), AndroidUtilities.dp(20), false); + } messageEditText.setText(builder); messageEditText.setSelection(start + text.length()); } catch (Exception e) { @@ -2861,7 +2892,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe botReplyMarkup = messageObject != null && messageObject.messageOwner.reply_markup instanceof TLRPC.TL_replyKeyboardMarkup ? (TLRPC.TL_replyKeyboardMarkup) messageObject.messageOwner.reply_markup : null; botKeyboardView.setPanelHeight(AndroidUtilities.displaySize.x > AndroidUtilities.displaySize.y ? keyboardHeightLand : keyboardHeight); - botKeyboardView.setButtons(botReplyMarkup != null ? botReplyMarkup : null); + botKeyboardView.setButtons(botReplyMarkup); if (botReplyMarkup != null) { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); boolean keyboardHidden = preferences.getInt("hidekeyboard_" + dialog_id, 0) == messageObject.getId(); @@ -2936,7 +2967,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe DialogsActivity fragment = new DialogsActivity(args); fragment.setDelegate(new DialogsActivity.DialogsActivityDelegate() { @Override - public void didSelectDialog(DialogsActivity fragment, long did, boolean param) { + public void didSelectDialogs(DialogsActivity fragment, ArrayList dids, CharSequence message, boolean param) { int uid = messageObject.messageOwner.from_id; if (messageObject.messageOwner.via_bot_id != 0) { uid = messageObject.messageOwner.via_bot_id; @@ -2946,6 +2977,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe fragment.finishFragment(); return; } + long did = dids.get(0); DraftQuery.saveDraft(did, "@" + user.username + " " + button.query, null, null, true); if (did != dialog_id) { int lower_part = (int) did; @@ -2992,7 +3024,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe if (emojiView != null) { return; } - emojiView = new EmojiView(allowStickers, allowGifs, parentActivity); + emojiView = new EmojiView(allowStickers, allowGifs, parentActivity, info); emojiView.setVisibility(GONE); emojiView.setListener(new EmojiView.Listener() { public boolean onBackspace() { @@ -3022,10 +3054,11 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe } public void onStickerSelected(TLRPC.Document sticker) { - if(stickersExpanded) + if (stickersExpanded) { setStickersExpanded(false, true); + } ChatActivityEnterView.this.onStickerSelected(sticker); - StickersQuery.addRecentSticker(StickersQuery.TYPE_IMAGE, sticker, (int) (System.currentTimeMillis() / 1000)); + StickersQuery.addRecentSticker(StickersQuery.TYPE_IMAGE, sticker, (int) (System.currentTimeMillis() / 1000), false); if ((int) dialog_id == 0) { MessagesController.getInstance().saveGif(sticker); } @@ -3040,8 +3073,9 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe @Override public void onGifSelected(TLRPC.Document gif) { - if(stickersExpanded) - setStickersExpanded(false, true); + if (stickersExpanded) { + setStickersExpanded(false, true); + } SendMessagesHelper.getInstance().sendSticker(gif, dialog_id, replyingMessageObject); StickersQuery.addRecentGif(gif, (int) (System.currentTimeMillis() / 1000)); if ((int) dialog_id == 0) { @@ -3113,6 +3147,18 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe public void onStickerSetRemove(TLRPC.StickerSetCovered stickerSet) { StickersQuery.removeStickersSet(parentActivity, stickerSet.set, 0, parentFragment, false); } + + @Override + public void onStickersGroupClick(int chatId) { + if (parentFragment != null) { + if (AndroidUtilities.isTablet()) { + hidePopup(false); + } + GroupStickersActivity fragment = new GroupStickersActivity(chatId); + fragment.setInfo(info); + parentFragment.presentFragment(fragment); + } + } }); emojiView.setDragListener(new EmojiView.DragListener() { @@ -3274,8 +3320,9 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe if (stickersTabOpen) { checkSendButton(true); } - if (stickersExpanded && show != 1) + if (stickersExpanded && show != 1) { setStickersExpanded(false, false); + } } private void setEmojiButtonImage() { @@ -3292,7 +3339,6 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe } else if (currentPage == 2) { emojiButton.setImageResource(R.drawable.ic_msg_panel_gif); } - stickersTabOpen = currentPage == 1 || currentPage == 2; } public void hidePopup(boolean byBackButton) { @@ -3625,13 +3671,15 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe } private void setStickersExpanded(boolean expanded, boolean animated) { - if(expanded && !emojiView.areThereAnyStickers()) + if (emojiView == null || expanded && !emojiView.areThereAnyStickers()) { return; + } stickersExpanded = expanded; - //expandStickersButton.setRotationX(stickersExpanded ? 0 : 180); final int origHeight = AndroidUtilities.displaySize.x > AndroidUtilities.displaySize.y ? keyboardHeightLand : keyboardHeight; - if (stickersExpansionAnim != null) + if (stickersExpansionAnim != null) { stickersExpansionAnim.cancel(); + stickersExpansionAnim = null; + } if (stickersExpanded) { stickersExpandedHeight = sizeNotifierLayout.getHeight() - (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? AndroidUtilities.statusBarHeight : 0) - ActionBar.getCurrentActionBarHeight() - getHeight() + Theme.chat_composeShadowDrawable.getIntrinsicHeight(); emojiView.getLayoutParams().height = stickersExpandedHeight; @@ -3650,7 +3698,6 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe ((ObjectAnimator) anims.getChildAnimations().get(0)).addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { - //stickersExpansionProgress=animation.getAnimatedFraction(); stickersExpansionProgress = getTranslationY() / (-(stickersExpandedHeight - origHeight)); sizeNotifierLayout.invalidate(); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlert.java index e14dc3910..0ca2b3c69 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlert.java @@ -14,7 +14,6 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; -import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.DialogInterface; @@ -77,7 +76,7 @@ import java.io.File; import java.util.ArrayList; import java.util.HashMap; -public class ChatAttachAlert extends BottomSheet implements NotificationCenter.NotificationCenterDelegate, PhotoViewer.PhotoViewerProvider, BottomSheet.BottomSheetDelegateInterface { +public class ChatAttachAlert extends BottomSheet implements NotificationCenter.NotificationCenterDelegate, BottomSheet.BottomSheetDelegateInterface { public interface ChatAttachViewDelegate { void didPressedButton(int button); @@ -169,6 +168,166 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N private boolean paused; + private PhotoViewer.PhotoViewerProvider photoViewerProvider = new PhotoViewer.EmptyPhotoViewerProvider() { + @Override + public PhotoViewer.PlaceProviderObject getPlaceForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { + PhotoAttachPhotoCell cell = getCellForIndex(index); + if (cell != null) { + int coords[] = new int[2]; + cell.getImageView().getLocationInWindow(coords); + coords[0] -= getLeftInset(); + PhotoViewer.PlaceProviderObject object = new PhotoViewer.PlaceProviderObject(); + object.viewX = coords[0]; + object.viewY = coords[1]; + object.parentView = attachPhotoRecyclerView; + object.imageReceiver = cell.getImageView().getImageReceiver(); + object.thumb = object.imageReceiver.getBitmap(); + object.scale = cell.getImageView().getScaleX(); + cell.showCheck(false); + return object; + } + return null; + } + + @Override + public void updatePhotoAtIndex(int index) { + PhotoAttachPhotoCell cell = getCellForIndex(index); + if (cell != null) { + cell.getImageView().setOrientation(0, true); + MediaController.PhotoEntry photoEntry = MediaController.allMediaAlbumEntry.photos.get(index); + if (photoEntry.thumbPath != null) { + cell.getImageView().setImage(photoEntry.thumbPath, null, cell.getContext().getResources().getDrawable(R.drawable.nophotos)); + } else if (photoEntry.path != null) { + cell.getImageView().setOrientation(photoEntry.orientation, true); + if (photoEntry.isVideo) { + cell.getImageView().setImage("vthumb://" + photoEntry.imageId + ":" + photoEntry.path, null, cell.getContext().getResources().getDrawable(R.drawable.nophotos)); + } else { + cell.getImageView().setImage("thumb://" + photoEntry.imageId + ":" + photoEntry.path, null, cell.getContext().getResources().getDrawable(R.drawable.nophotos)); + } + } else { + cell.getImageView().setImageResource(R.drawable.nophotos); + } + } + } + + @Override + public Bitmap getThumbForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { + PhotoAttachPhotoCell cell = getCellForIndex(index); + if (cell != null) { + return cell.getImageView().getImageReceiver().getBitmap(); + } + return null; + } + + @Override + public void willSwitchFromPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { + PhotoAttachPhotoCell cell = getCellForIndex(index); + if (cell != null) { + cell.showCheck(true); + } + } + + @Override + public void willHidePhotoViewer() { + int count = attachPhotoRecyclerView.getChildCount(); + for (int a = 0; a < count; a++) { + View view = attachPhotoRecyclerView.getChildAt(a); + if (view instanceof PhotoAttachPhotoCell) { + PhotoAttachPhotoCell cell = (PhotoAttachPhotoCell) view; + cell.showCheck(true); + } + } + } + + @Override + public boolean isPhotoChecked(int index) { + return !(index < 0 || index >= MediaController.allMediaAlbumEntry.photos.size()) && photoAttachAdapter.selectedPhotos.containsKey(MediaController.allMediaAlbumEntry.photos.get(index).imageId); + } + + @Override + public int setPhotoChecked(int index, VideoEditedInfo videoEditedInfo) { + boolean add = true; + if (index < 0 || index >= MediaController.allMediaAlbumEntry.photos.size()) { + return -1; + } + int num; + + MediaController.PhotoEntry photoEntry = MediaController.allMediaAlbumEntry.photos.get(index); + if ((num = photoAttachAdapter.addToSelectedPhotos(photoEntry, -1)) == -1) { + num = photoAttachAdapter.selectedPhotosOrder.indexOf(photoEntry.imageId); + } else { + add = false; + photoEntry.editedInfo = null; + } + photoEntry.editedInfo = videoEditedInfo; + + int count = attachPhotoRecyclerView.getChildCount(); + for (int a = 0; a < count; a++) { + View view = attachPhotoRecyclerView.getChildAt(a); + if (view instanceof PhotoAttachPhotoCell) { + int tag = (Integer) view.getTag(); + if (tag == index) { + ((PhotoAttachPhotoCell) view).setChecked(num, add, false); + break; + } + } + } + updatePhotosButton(); + return num; + } + + @Override + public boolean cancelButtonPressed() { + return false; + } + + @Override + public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo) { + if (photoAttachAdapter.selectedPhotos.isEmpty()) { + if (index < 0 || index >= MediaController.allMediaAlbumEntry.photos.size()) { + return; + } + MediaController.PhotoEntry photoEntry = MediaController.allMediaAlbumEntry.photos.get(index); + photoEntry.editedInfo = videoEditedInfo; + photoAttachAdapter.addToSelectedPhotos(photoEntry, -1); + } + delegate.didPressedButton(7); + } + + @Override + public int getSelectedCount() { + return photoAttachAdapter.selectedPhotos.size(); + } + + @Override + public ArrayList getSelectedPhotosOrder() { + return photoAttachAdapter.selectedPhotosOrder; + } + + @Override + public HashMap getSelectedPhotos() { + return photoAttachAdapter.selectedPhotos; + } + + @Override + public boolean allowGroupPhotos() { + return baseFragment != null && baseFragment.allowGroupPhotos(); + } + }; + + private void updateCheckedPhotoIndices() { + int count = attachPhotoRecyclerView.getChildCount(); + for (int a = 0; a < count; a++) { + View view = attachPhotoRecyclerView.getChildAt(a); + if (view instanceof PhotoAttachPhotoCell) { + PhotoAttachPhotoCell cell = (PhotoAttachPhotoCell) view; + Integer index = (Integer) cell.getTag(); + MediaController.PhotoEntry photoEntry = MediaController.allMediaAlbumEntry.photos.get(index); + cell.setNum(photoAttachAdapter.selectedPhotosOrder.indexOf(photoEntry.imageId)); + } + } + } + private class AttachButton extends FrameLayout { private TextView textView; @@ -599,7 +758,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } PhotoViewer.getInstance().setParentActivity(baseFragment.getParentActivity()); PhotoViewer.getInstance().setParentAlert(ChatAttachAlert.this); - PhotoViewer.getInstance().openPhotoForSelect(arrayList, position, 0, ChatAttachAlert.this, baseFragment); + PhotoViewer.getInstance().openPhotoForSelect(arrayList, position, 0, photoViewerProvider, baseFragment); AndroidUtilities.hideKeyboard(baseFragment.getFragmentView().findFocus()); } else { openCamera(); @@ -697,7 +856,6 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N recordTime = new TextView(context); recordTime.setBackgroundResource(R.drawable.system); recordTime.getBackground().setColorFilter(new PorterDuffColorFilter(0x66000000, PorterDuff.Mode.MULTIPLY)); - recordTime.setText("00:00"); recordTime.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); recordTime.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); recordTime.setAlpha(0.0f); @@ -753,7 +911,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N switchCameraButton.setAlpha(0.0f); cameraFile = AndroidUtilities.generateVideoPath(); recordTime.setAlpha(1.0f); - recordTime.setText("00:00"); + recordTime.setText(String.format("%02d:%02d", 0, 0)); videoRecordTime = 0; videoRecordRunnable = new Runnable() { @Override @@ -783,7 +941,6 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N return thumb; } - @TargetApi(16) @Override public boolean cancelButtonPressed() { if (cameraOpened && cameraView != null && cameraFile != null) { @@ -801,6 +958,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } return true; } + @Override public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo) { if (cameraFile == null || baseFragment == null) { @@ -817,6 +975,11 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N public void willHidePhotoViewer() { mediaCaptured = false; } + + @Override + public boolean canScrollAway() { + return false; + } }, baseFragment); } }, new Runnable() { @@ -883,7 +1046,6 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } cameraPhoto.add(new MediaController.PhotoEntry(0, 0, 0, cameraFile.getAbsolutePath(), orientation, false)); PhotoViewer.getInstance().openPhotoForSelect(cameraPhoto, 0, 2, new PhotoViewer.EmptyPhotoViewerProvider() { - @TargetApi(16) @Override public boolean cancelButtonPressed() { if (cameraOpened && cameraView != null && cameraFile != null) { @@ -927,6 +1089,11 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N public void willHidePhotoViewer() { mediaCaptured = false; } + + @Override + public boolean canScrollAway() { + return false; + } }, baseFragment); } }); @@ -1049,7 +1216,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N cameraPanel.setTag(null); } } - } else { + } else if (cameraView != null) { cameraView.getLocationOnScreen(viewPosition); float viewX = event.getRawX() - viewPosition[0]; float viewY = event.getRawY() - viewPosition[1]; @@ -1203,7 +1370,6 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } } - @TargetApi(16) private void openCamera() { if (cameraView == null) { return; @@ -1242,7 +1408,6 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N cameraOpened = true; } - @TargetApi(16) public void closeCamera(boolean animated) { if (takingPhoto || cameraView == null) { return; @@ -1453,7 +1618,6 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } } - @TargetApi(16) public void showCamera() { if (paused || !mediaEnabled) { return; @@ -1626,7 +1790,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } public void updatePhotosButton() { - int count = photoAttachAdapter.getSelectedPhotos().size(); + int count = photoAttachAdapter.selectedPhotos.size(); if (count == 0) { sendPhotosButton.imageView.setPadding(0, AndroidUtilities.dp(4), 0, 0); sendPhotosButton.imageView.setBackgroundResource(R.drawable.attach_hide_states); @@ -1679,8 +1843,12 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N updatePhotosButton(); } - public HashMap getSelectedPhotos() { - return photoAttachAdapter.getSelectedPhotos(); + public HashMap getSelectedPhotos() { + return photoAttachAdapter.selectedPhotos; + } + + public ArrayList getSelectedPhotosOrder() { + return photoAttachAdapter.selectedPhotosOrder; } public void onDestroy() { @@ -1711,139 +1879,6 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N return null; } - @Override - public PhotoViewer.PlaceProviderObject getPlaceForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { - PhotoAttachPhotoCell cell = getCellForIndex(index); - if (cell != null) { - int coords[] = new int[2]; - cell.getImageView().getLocationInWindow(coords); - coords[0] -= getLeftInset(); - PhotoViewer.PlaceProviderObject object = new PhotoViewer.PlaceProviderObject(); - object.viewX = coords[0]; - object.viewY = coords[1]; - object.parentView = attachPhotoRecyclerView; - object.imageReceiver = cell.getImageView().getImageReceiver(); - object.thumb = object.imageReceiver.getBitmap(); - object.scale = cell.getImageView().getScaleX(); - cell.showCheck(false); - return object; - } - return null; - } - - @Override - public boolean scaleToFill() { - return false; - } - - @Override - public boolean allowCaption() { - return true; - } - - @Override - public void updatePhotoAtIndex(int index) { - PhotoAttachPhotoCell cell = getCellForIndex(index); - if (cell != null) { - cell.getImageView().setOrientation(0, true); - MediaController.PhotoEntry photoEntry = MediaController.allMediaAlbumEntry.photos.get(index); - if (photoEntry.thumbPath != null) { - cell.getImageView().setImage(photoEntry.thumbPath, null, cell.getContext().getResources().getDrawable(R.drawable.nophotos)); - } else if (photoEntry.path != null) { - cell.getImageView().setOrientation(photoEntry.orientation, true); - cell.getImageView().setImage("thumb://" + photoEntry.imageId + ":" + photoEntry.path, null, cell.getContext().getResources().getDrawable(R.drawable.nophotos)); - } else { - cell.getImageView().setImageResource(R.drawable.nophotos); - } - } - } - - @Override - public Bitmap getThumbForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { - PhotoAttachPhotoCell cell = getCellForIndex(index); - if (cell != null) { - return cell.getImageView().getImageReceiver().getBitmap(); - } - return null; - } - - @Override - public void willSwitchFromPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { - PhotoAttachPhotoCell cell = getCellForIndex(index); - if (cell != null) { - cell.showCheck(true); - } - } - - @Override - public void willHidePhotoViewer() { - int count = attachPhotoRecyclerView.getChildCount(); - for (int a = 0; a < count; a++) { - View view = attachPhotoRecyclerView.getChildAt(a); - if (view instanceof PhotoAttachPhotoCell) { - PhotoAttachPhotoCell cell = (PhotoAttachPhotoCell) view; - cell.showCheck(true); - } - } - } - - @Override - public boolean isPhotoChecked(int index) { - return !(index < 0 || index >= MediaController.allMediaAlbumEntry.photos.size()) && photoAttachAdapter.getSelectedPhotos().containsKey(MediaController.allMediaAlbumEntry.photos.get(index).imageId); - } - - @Override - public void setPhotoChecked(int index, VideoEditedInfo videoEditedInfo) { - boolean add = true; - if (index < 0 || index >= MediaController.allMediaAlbumEntry.photos.size()) { - return; - } - MediaController.PhotoEntry photoEntry = MediaController.allMediaAlbumEntry.photos.get(index); - if (photoAttachAdapter.getSelectedPhotos().containsKey(photoEntry.imageId)) { - photoAttachAdapter.getSelectedPhotos().remove(photoEntry.imageId); - photoEntry.editedInfo = null; - add = false; - } else { - photoEntry.editedInfo = videoEditedInfo; - photoAttachAdapter.getSelectedPhotos().put(photoEntry.imageId, photoEntry); - } - int count = attachPhotoRecyclerView.getChildCount(); - for (int a = 0; a < count; a++) { - View view = attachPhotoRecyclerView.getChildAt(a); - if (view instanceof PhotoAttachPhotoCell) { - int num = (Integer) view.getTag(); - if (num == index) { - ((PhotoAttachPhotoCell) view).setChecked(add, false); - break; - } - } - } - updatePhotosButton(); - } - - @Override - public boolean cancelButtonPressed() { - return false; - } - - @Override - public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo) { - if (photoAttachAdapter.getSelectedPhotos().isEmpty()) { - if (index < 0 || index >= MediaController.allMediaAlbumEntry.photos.size()) { - return; - } - MediaController.PhotoEntry photoEntry = MediaController.allMediaAlbumEntry.photos.get(index); - photoEntry.editedInfo = videoEditedInfo; - photoAttachAdapter.getSelectedPhotos().put(photoEntry.imageId, photoEntry); - } - delegate.didPressedButton(7); - } - - @Override - public int getSelectedCount() { - return photoAttachAdapter.getSelectedPhotos().size(); - } - private void onRevealAnimationEnd(boolean open) { NotificationCenter.getInstance().setAnimationInProgress(false); revealAnimationInProgress = false; @@ -1861,19 +1896,23 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N return; } boolean old = deviceHasGoodCamera; - if (Build.VERSION.SDK_INT >= 23) { - if (baseFragment.getParentActivity().checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { - if (request) { - baseFragment.getParentActivity().requestPermissions(new String[]{Manifest.permission.CAMERA}, 17); + if (!MediaController.getInstance().canInAppCamera()) { + deviceHasGoodCamera = false; + } else { + if (Build.VERSION.SDK_INT >= 23) { + if (baseFragment.getParentActivity().checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { + if (request) { + baseFragment.getParentActivity().requestPermissions(new String[]{Manifest.permission.CAMERA}, 17); + } + deviceHasGoodCamera = false; + } else { + CameraController.getInstance().initCamera(); + deviceHasGoodCamera = CameraController.getInstance().isCameraInitied(); } - deviceHasGoodCamera = false; } else { CameraController.getInstance().initCamera(); deviceHasGoodCamera = CameraController.getInstance().isCameraInitied(); } - } else { - CameraController.getInstance().initCamera(); - deviceHasGoodCamera = CameraController.getInstance().isCameraInitied(); } if (old != deviceHasGoodCamera && photoAttachAdapter != null) { photoAttachAdapter.notifyDataSetChanged(); @@ -1993,7 +2032,8 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N private class PhotoAttachAdapter extends RecyclerListView.SelectionAdapter { private Context mContext; - private HashMap selectedPhotos = new HashMap<>(); + private HashMap selectedPhotos = new HashMap<>(); + private ArrayList selectedPhotosOrder = new ArrayList<>(); public PhotoAttachAdapter(Context context) { mContext = context; @@ -2001,11 +2041,12 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N public void clearSelectedPhotos() { if (!selectedPhotos.isEmpty()) { - for (HashMap.Entry entry : selectedPhotos.entrySet()) { - MediaController.PhotoEntry photoEntry = entry.getValue(); + for (HashMap.Entry entry : selectedPhotos.entrySet()) { + MediaController.PhotoEntry photoEntry = (MediaController.PhotoEntry) entry.getValue(); photoEntry.reset(); } selectedPhotos.clear(); + selectedPhotosOrder.clear(); updatePhotosButton(); notifyDataSetChanged(); } @@ -2019,24 +2060,40 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N if (!mediaEnabled) { return; } + int index = (Integer) v.getTag(); MediaController.PhotoEntry photoEntry = v.getPhotoEntry(); - if (selectedPhotos.containsKey(photoEntry.imageId)) { - selectedPhotos.remove(photoEntry.imageId); - v.setChecked(false, true); - photoEntry.reset(); - v.setPhotoEntry(photoEntry, (Integer) v.getTag() == MediaController.allMediaAlbumEntry.photos.size() - 1); - } else { - selectedPhotos.put(photoEntry.imageId, photoEntry); - v.setChecked(true, true); - } + boolean added = !selectedPhotos.containsKey(photoEntry.imageId); + int num = added ? selectedPhotosOrder.size() : -1; + v.setChecked(num, added, true); + addToSelectedPhotos(photoEntry, index); updatePhotosButton(); } }); return new RecyclerListView.Holder(cell); } - public HashMap getSelectedPhotos() { - return selectedPhotos; + private int addToSelectedPhotos(MediaController.PhotoEntry photoEntry, int index) { + if (selectedPhotos.containsKey(photoEntry.imageId)) { + selectedPhotos.remove(photoEntry.imageId); + int position = 0; + for (int a = 0; a < selectedPhotosOrder.size(); a++) { + if (selectedPhotosOrder.get(a).equals(photoEntry.imageId)) { + position = a; + selectedPhotosOrder.remove(a); + break; + } + } + updateCheckedPhotoIndices(); + if (index >= 0) { + photoEntry.reset(); + photoViewerProvider.updatePhotoAtIndex(index); + } + return position; + } else { + selectedPhotos.put(photoEntry.imageId, photoEntry); + selectedPhotosOrder.add(photoEntry.imageId); + return -1; + } } @Override @@ -2048,7 +2105,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N PhotoAttachPhotoCell cell = (PhotoAttachPhotoCell) holder.itemView; MediaController.PhotoEntry photoEntry = MediaController.allMediaAlbumEntry.photos.get(position); cell.setPhotoEntry(photoEntry, position == MediaController.allMediaAlbumEntry.photos.size() - 1); - cell.setChecked(selectedPhotos.containsKey(photoEntry.imageId), false); + cell.setChecked(selectedPhotosOrder.indexOf(photoEntry.imageId), selectedPhotos.containsKey(photoEntry.imageId), false); cell.getImageView().setTag(position); cell.setTag(position); } else if (deviceHasGoodCamera && position == 0) { @@ -2107,7 +2164,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } private void setUseRevealAnimation(boolean value) { - if (!value || value && Build.VERSION.SDK_INT >= 18 && !AndroidUtilities.isTablet()) { + if (!value || value && Build.VERSION.SDK_INT >= 18 && !AndroidUtilities.isTablet() && Build.VERSION.SDK_INT < 26) { useRevealAnimation = value; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAvatarContainer.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAvatarContainer.java index 90a191953..505e1f120 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAvatarContainer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAvatarContainer.java @@ -23,17 +23,20 @@ import org.telegram.messenger.ChatObject; import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesController; +import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; import org.telegram.messenger.UserConfig; +import org.telegram.messenger.UserObject; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.SimpleTextView; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.ChatActivity; +import org.telegram.ui.MediaActivity; import org.telegram.ui.ProfileActivity; -public class ChatAvatarContainer extends FrameLayout { +public class ChatAvatarContainer extends FrameLayout implements NotificationCenter.NotificationCenterDelegate { private BackupImageView avatarImageView; private SimpleTextView titleTextView; @@ -45,6 +48,8 @@ public class ChatAvatarContainer extends FrameLayout { private AvatarDrawable avatarDrawable = new AvatarDrawable(); private int onlineCount = -1; + private int currentConnectionState; + private CharSequence lastSubtitle; public ChatAvatarContainer(Context context, ChatActivity chatActivity, boolean needTime) { super(context); @@ -90,13 +95,20 @@ public class ChatAvatarContainer extends FrameLayout { TLRPC.Chat chat = parentFragment.getCurrentChat(); if (user != null) { Bundle args = new Bundle(); - args.putInt("user_id", user.id); - if (timeItem != null) { + if (UserObject.isUserSelf(user)) { args.putLong("dialog_id", parentFragment.getDialogId()); + MediaActivity fragment = new MediaActivity(args); + fragment.setChatInfo(parentFragment.getCurrentChatInfo()); + parentFragment.presentFragment(fragment); + } else { + args.putInt("user_id", user.id); + if (timeItem != null) { + args.putLong("dialog_id", parentFragment.getDialogId()); + } + ProfileActivity fragment = new ProfileActivity(args); + fragment.setPlayProfileAnimation(true); + parentFragment.presentFragment(fragment); } - ProfileActivity fragment = new ProfileActivity(args); - fragment.setPlayProfileAnimation(true); - parentFragment.presentFragment(fragment); } else if (chat != null) { Bundle args = new Bundle(); args.putInt("chat_id", chat.id); @@ -120,6 +132,11 @@ public class ChatAvatarContainer extends FrameLayout { } } + public void setTitleColors(int title, int subtitle) { + titleTextView.setTextColor(title); + subtitleTextView.setTextColor(title); + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); @@ -138,7 +155,11 @@ public class ChatAvatarContainer extends FrameLayout { int actionBarHeight = ActionBar.getCurrentActionBarHeight(); int viewTop = (actionBarHeight - AndroidUtilities.dp(42)) / 2 + (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0); avatarImageView.layout(AndroidUtilities.dp(8), viewTop, AndroidUtilities.dp(42 + 8), viewTop + AndroidUtilities.dp(42)); - titleTextView.layout(AndroidUtilities.dp(8 + 54), viewTop + AndroidUtilities.dp(1.3f), AndroidUtilities.dp(8 + 54) + titleTextView.getMeasuredWidth(), viewTop + titleTextView.getTextHeight() + AndroidUtilities.dp(1.3f)); + if (subtitleTextView.getVisibility() == VISIBLE) { + titleTextView.layout(AndroidUtilities.dp(8 + 54), viewTop + AndroidUtilities.dp(1.3f), AndroidUtilities.dp(8 + 54) + titleTextView.getMeasuredWidth(), viewTop + titleTextView.getTextHeight() + AndroidUtilities.dp(1.3f)); + } else { + titleTextView.layout(AndroidUtilities.dp(8 + 54), viewTop + AndroidUtilities.dp(11), AndroidUtilities.dp(8 + 54) + titleTextView.getMeasuredWidth(), viewTop + titleTextView.getTextHeight() + AndroidUtilities.dp(11)); + } if (timeItem != null) { timeItem.layout(AndroidUtilities.dp(8 + 16), viewTop + AndroidUtilities.dp(15), AndroidUtilities.dp(8 + 16 + 34), viewTop + AndroidUtilities.dp(15 + 34)); } @@ -181,7 +202,11 @@ public class ChatAvatarContainer extends FrameLayout { } public void setSubtitle(CharSequence value) { - subtitleTextView.setText(value); + if (lastSubtitle == null) { + subtitleTextView.setText(value); + } else { + lastSubtitle = value; + } } public ImageView getTimeItem() { @@ -224,11 +249,18 @@ public class ChatAvatarContainer extends FrameLayout { return; } TLRPC.User user = parentFragment.getCurrentUser(); + if (UserObject.isUserSelf(user)) { + if (subtitleTextView.getVisibility() != GONE) { + subtitleTextView.setVisibility(GONE); + } + return; + } TLRPC.Chat chat = parentFragment.getCurrentChat(); CharSequence printString = MessagesController.getInstance().printingStrings.get(parentFragment.getDialogId()); if (printString != null) { printString = TextUtils.replace(printString, new String[]{"..."}, new String[]{""}); } + CharSequence newSubtitle; if (printString == null || printString.length() == 0 || ChatObject.isChannel(chat) && !chat.megagroup) { setTypingAnimation(false); if (chat != null) { @@ -237,41 +269,44 @@ public class ChatAvatarContainer extends FrameLayout { if (info != null && info.participants_count != 0) { if (chat.megagroup && info.participants_count <= 200) { if (onlineCount > 1 && info.participants_count != 0) { - subtitleTextView.setText(String.format("%s, %s", LocaleController.formatPluralString("Members", info.participants_count), LocaleController.formatPluralString("OnlineCount", onlineCount))); + newSubtitle = String.format("%s, %s", LocaleController.formatPluralString("Members", info.participants_count), LocaleController.formatPluralString("OnlineCount", onlineCount)); } else { - subtitleTextView.setText(LocaleController.formatPluralString("Members", info.participants_count)); + newSubtitle = LocaleController.formatPluralString("Members", info.participants_count); } } else { int result[] = new int[1]; String shortNumber = LocaleController.formatShortNumber(info.participants_count, result); - String text = LocaleController.formatPluralString("Members", result[0]).replace(String.format("%d", result[0]), shortNumber); - subtitleTextView.setText(text); + if (chat.megagroup) { + newSubtitle = LocaleController.formatPluralString("Members", result[0]).replace(String.format("%d", result[0]), shortNumber); + } else { + newSubtitle = LocaleController.formatPluralString("Subscribers", result[0]).replace(String.format("%d", result[0]), shortNumber); + } } } else { if (chat.megagroup) { - subtitleTextView.setText(LocaleController.getString("Loading", R.string.Loading).toLowerCase()); + newSubtitle = LocaleController.getString("Loading", R.string.Loading).toLowerCase(); } else { if ((chat.flags & TLRPC.CHAT_FLAG_IS_PUBLIC) != 0) { - subtitleTextView.setText(LocaleController.getString("ChannelPublic", R.string.ChannelPublic).toLowerCase()); + newSubtitle = LocaleController.getString("ChannelPublic", R.string.ChannelPublic).toLowerCase(); } else { - subtitleTextView.setText(LocaleController.getString("ChannelPrivate", R.string.ChannelPrivate).toLowerCase()); + newSubtitle = LocaleController.getString("ChannelPrivate", R.string.ChannelPrivate).toLowerCase(); } } } } else { if (ChatObject.isKickedFromChat(chat)) { - subtitleTextView.setText(LocaleController.getString("YouWereKicked", R.string.YouWereKicked)); + newSubtitle = LocaleController.getString("YouWereKicked", R.string.YouWereKicked); } else if (ChatObject.isLeftFromChat(chat)) { - subtitleTextView.setText(LocaleController.getString("YouLeft", R.string.YouLeft)); + newSubtitle = LocaleController.getString("YouLeft", R.string.YouLeft); } else { int count = chat.participants_count; if (info != null && info.participants != null) { count = info.participants.participants.size(); } if (onlineCount > 1 && count != 0) { - subtitleTextView.setText(String.format("%s, %s", LocaleController.formatPluralString("Members", count), LocaleController.formatPluralString("OnlineCount", onlineCount))); + newSubtitle = String.format("%s, %s", LocaleController.formatPluralString("Members", count), LocaleController.formatPluralString("OnlineCount", onlineCount)); } else { - subtitleTextView.setText(LocaleController.formatPluralString("Members", count)); + newSubtitle = LocaleController.formatPluralString("Members", count); } } } @@ -290,12 +325,19 @@ public class ChatAvatarContainer extends FrameLayout { } else { newStatus = LocaleController.formatUserStatus(user); } - subtitleTextView.setText(newStatus); + newSubtitle = newStatus; + } else { + newSubtitle = ""; } } else { - subtitleTextView.setText(printString); + newSubtitle = printString; setTypingAnimation(true); } + if (lastSubtitle == null) { + subtitleTextView.setText(newSubtitle); + } else { + lastSubtitle = newSubtitle; + } } public void setChatAvatar(TLRPC.Chat chat) { @@ -309,6 +351,20 @@ public class ChatAvatarContainer extends FrameLayout { } } + public void setUserAvatar(TLRPC.User user) { + TLRPC.FileLocation newPhoto = null; + avatarDrawable.setInfo(user); + if (UserObject.isUserSelf(user)) { + avatarDrawable.setSavedMessages(2); + } else if (user.photo != null) { + newPhoto = user.photo.photo_small; + } + + if (avatarImageView != null) { + avatarImageView.setImage(newPhoto, "50_50", avatarDrawable); + } + } + public void checkAndUpdateAvatar() { if (parentFragment == null) { return; @@ -317,10 +373,12 @@ public class ChatAvatarContainer extends FrameLayout { TLRPC.User user = parentFragment.getCurrentUser(); TLRPC.Chat chat = parentFragment.getCurrentChat(); if (user != null) { - if (user.photo != null) { + avatarDrawable.setInfo(user); + if (UserObject.isUserSelf(user)) { + avatarDrawable.setSavedMessages(2); + } else if (user.photo != null) { newPhoto = user.photo.photo_small; } - avatarDrawable.setInfo(user); } else if (chat != null) { if (chat.photo != null) { newPhoto = chat.photo.photo_small; @@ -352,4 +410,55 @@ public class ChatAvatarContainer extends FrameLayout { } } } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (parentFragment != null) { + NotificationCenter.getInstance().addObserver(this, NotificationCenter.didUpdatedConnectionState); + currentConnectionState = ConnectionsManager.getInstance().getConnectionState(); + updateCurrentConnectionState(); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (parentFragment != null) { + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didUpdatedConnectionState); + } + } + + @Override + public void didReceivedNotification(int id, Object... args) { + if (id == NotificationCenter.didUpdatedConnectionState) { + int state = ConnectionsManager.getInstance().getConnectionState(); + if (currentConnectionState != state) { + currentConnectionState = state; + updateCurrentConnectionState(); + } + } + } + + private void updateCurrentConnectionState() { + String title = null; + if (currentConnectionState == ConnectionsManager.ConnectionStateWaitingForNetwork) { + title = LocaleController.getString("WaitingForNetwork", R.string.WaitingForNetwork); + } else if (currentConnectionState == ConnectionsManager.ConnectionStateConnecting) { + title = LocaleController.getString("Connecting", R.string.Connecting); + } else if (currentConnectionState == ConnectionsManager.ConnectionStateUpdating) { + title = LocaleController.getString("Updating", R.string.Updating); + } else if (currentConnectionState == ConnectionsManager.ConnectionStateConnectingToProxy) { + title = LocaleController.getString("ConnectingToProxy", R.string.ConnectingToProxy); + } + if (title == null) { + if (lastSubtitle != null) { + subtitleTextView.setText(lastSubtitle); + lastSubtitle = null; + } + } else { + lastSubtitle = subtitleTextView.getText(); + subtitleTextView.setText(title); + } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/CheckBox.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/CheckBox.java index 14f5ef653..b244bb69a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/CheckBox.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/CheckBox.java @@ -8,6 +8,8 @@ package org.telegram.ui.Components; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Bitmap; @@ -17,6 +19,7 @@ import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.PorterDuffXfermode; import android.graphics.drawable.Drawable; +import android.text.TextPaint; import android.view.View; import org.telegram.messenger.AndroidUtilities; @@ -29,6 +32,7 @@ public class CheckBox extends View { private static Paint eraser2; private static Paint checkPaint; private static Paint backgroundPaint; + private static TextPaint textPaint; private Bitmap drawBitmap; private Bitmap checkBitmap; @@ -47,7 +51,8 @@ public class CheckBox extends View { private int size = 22; private int checkOffset; - private int color; //default 0xff5ec245 + private int color; + private String checkedText; private final static float progressBounceDiff = 0.2f; @@ -67,6 +72,9 @@ public class CheckBox extends View { backgroundPaint.setColor(0xffffffff); backgroundPaint.setStyle(Paint.Style.STROKE); backgroundPaint.setStrokeWidth(AndroidUtilities.dp(2)); + textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + textPaint.setTextSize(AndroidUtilities.dp(18)); + textPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); } checkDrawable = context.getResources().getDrawable(resId).mutate(); @@ -114,6 +122,7 @@ public class CheckBox extends View { public void setColor(int backgroundColor, int checkColor) { color = backgroundColor; checkDrawable.setColorFilter(new PorterDuffColorFilter(checkColor, PorterDuff.Mode.MULTIPLY)); + textPaint.setColor(checkColor); invalidate(); } @@ -124,18 +133,31 @@ public class CheckBox extends View { public void setCheckColor(int checkColor) { checkDrawable.setColorFilter(new PorterDuffColorFilter(checkColor, PorterDuff.Mode.MULTIPLY)); + textPaint.setColor(checkColor); invalidate(); } private void cancelCheckAnimator() { if (checkAnimator != null) { checkAnimator.cancel(); + checkAnimator = null; } } private void animateToCheckedState(boolean newCheckedState) { isCheckAnimation = newCheckedState; checkAnimator = ObjectAnimator.ofFloat(this, "progress", newCheckedState ? 1 : 0); + checkAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animation.equals(checkAnimator)) { + checkAnimator = null; + } + if (!isChecked) { + checkedText = null; + } + } + }); checkAnimator.setDuration(300); checkAnimator.start(); } @@ -158,6 +180,22 @@ public class CheckBox extends View { } public void setChecked(boolean checked, boolean animated) { + setChecked(-1, checked, animated); + } + + public void setNum(int num) { + if (num >= 0) { + checkedText = "" + (num + 1); + } else if (checkAnimator == null) { + checkedText = null; + } + invalidate(); + } + + public void setChecked(int num, boolean checked, boolean animated) { + if (num >= 0) { + checkedText = "" + (num + 1); + } if (checked == isChecked) { return; } @@ -211,13 +249,18 @@ public class CheckBox extends View { canvas.drawBitmap(drawBitmap, 0, 0, null); checkBitmap.eraseColor(0); - int w = checkDrawable.getIntrinsicWidth(); - int h = checkDrawable.getIntrinsicHeight(); - int x = (getMeasuredWidth() - w) / 2; - int y = (getMeasuredHeight() - h) / 2; + if (checkedText != null) { + int w = (int) Math.ceil(textPaint.measureText(checkedText)); + checkCanvas.drawText(checkedText, (getMeasuredWidth() - w) / 2, AndroidUtilities.dp(21), textPaint); + } else { + int w = checkDrawable.getIntrinsicWidth(); + int h = checkDrawable.getIntrinsicHeight(); + int x = (getMeasuredWidth() - w) / 2; + int y = (getMeasuredHeight() - h) / 2; - checkDrawable.setBounds(x, y + checkOffset, x + w, y + h + checkOffset); - checkDrawable.draw(checkCanvas); + checkDrawable.setBounds(x, y + checkOffset, x + w, y + h + checkOffset); + checkDrawable.draw(checkCanvas); + } checkCanvas.drawCircle(getMeasuredWidth() / 2 - AndroidUtilities.dp(2.5f), getMeasuredHeight() / 2 + AndroidUtilities.dp(4), ((getMeasuredWidth() + AndroidUtilities.dp(6)) / 2) * (1 - checkProgress), eraser2); canvas.drawBitmap(checkBitmap, 0, 0, null); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextBoldCursor.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextBoldCursor.java index 8744a2f72..9045fb73e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextBoldCursor.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextBoldCursor.java @@ -51,6 +51,7 @@ public class EditTextBoldCursor extends EditText { private float hintAlpha = 1.0f; private long lastUpdateTime; private boolean allowDrawCursor = true; + private float cursorWidth = 2.0f; public EditTextBoldCursor(Context context) { super(context); @@ -89,6 +90,10 @@ public class EditTextBoldCursor extends EditText { allowDrawCursor = value; } + public void setCursorWidth(float width) { + cursorWidth = width; + } + public void setCursorColor(int color) { gradientDrawable.setColor(color); invalidate(); @@ -192,7 +197,12 @@ public class EditTextBoldCursor extends EditText { getPaint().setColor(hintColor); getPaint().setAlpha((int) (255 * hintAlpha)); canvas.save(); - canvas.translate(0.0f, (getMeasuredHeight() - hintLayout.getHeight()) / 2.0f); + int left = 0; + float lineLeft = hintLayout.getLineLeft(0); + if (lineLeft != 0) { + left -= lineLeft; + } + canvas.translate(left, (getMeasuredHeight() - hintLayout.getHeight()) / 2.0f); hintLayout.draw(canvas); getPaint().setColor(oldColor); canvas.restore(); @@ -200,7 +210,7 @@ public class EditTextBoldCursor extends EditText { try { if (allowDrawCursor && mShowCursorField != null && mCursorDrawable != null && mCursorDrawable[0] != null) { long mShowCursor = mShowCursorField.getLong(editor); - boolean showCursor = (SystemClock.uptimeMillis() - mShowCursor) % (2 * 500) < 500; + boolean showCursor = (SystemClock.uptimeMillis() - mShowCursor) % (2 * 500) < 500 && isFocused(); if (showCursor) { canvas.save(); int voffsetCursor = 0; @@ -213,7 +223,7 @@ public class EditTextBoldCursor extends EditText { int lineCount = layout.getLineCount(); Rect bounds = mCursorDrawable[0].getBounds(); rect.left = bounds.left; - rect.right = bounds.left + AndroidUtilities.dp(2); + rect.right = bounds.left + AndroidUtilities.dp(cursorWidth); rect.bottom = bounds.bottom; rect.top = bounds.top; if (lineSpacingExtra != 0 && line < lineCount - 1) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextCaption.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextCaption.java index ee3598a33..a3c71eabf 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextCaption.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextCaption.java @@ -131,7 +131,12 @@ public class EditTextCaption extends EditTextBoldCursor { mode.finish(); return true; } - return callback.onActionItemClicked(mode, item); + try { + return callback.onActionItemClicked(mode, item); + } catch (Exception ignore) { + + } + return true; } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmbedBottomSheet.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmbedBottomSheet.java index 5d8f4e25b..8f3d7a6ed 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmbedBottomSheet.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmbedBottomSheet.java @@ -21,6 +21,8 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Canvas; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.net.Uri; import android.os.Build; import android.provider.Settings; @@ -37,6 +39,7 @@ import android.view.ViewTreeObserver; import android.view.WindowManager; import android.view.animation.DecelerateInterpolator; import android.webkit.CookieManager; +import android.webkit.JavascriptInterface; import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebView; @@ -53,6 +56,7 @@ import org.telegram.messenger.BringAppForegroundService; import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; import org.telegram.messenger.R; +import org.telegram.messenger.Utilities; import org.telegram.messenger.browser.Browser; import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BottomSheet; @@ -60,7 +64,6 @@ import org.telegram.ui.ActionBar.Theme; import java.util.HashMap; -@TargetApi(16) public class EmbedBottomSheet extends BottomSheet { private WebView webView; @@ -68,9 +71,17 @@ public class EmbedBottomSheet extends BottomSheet { private View customView; private FrameLayout fullscreenVideoContainer; private WebChromeClient.CustomViewCallback customViewCallback; + private View progressBarBlackBackground; private RadialProgressView progressBar; private Activity parentActivity; private PipVideoView pipVideoView; + private LinearLayout imageButtonsContainer; + private TextView copyTextButton; + private TextView openInButton; + private FrameLayout containerLayout; + private ImageView pipButton; + private ImageView youtubeLogoImage; + private boolean isYouTube; private int[] position = new int[2]; @@ -89,6 +100,120 @@ public class EmbedBottomSheet extends BottomSheet { private int waitingForDraw; + private class YoutubeProxy { + @JavascriptInterface + public void postEvent(final String eventName, final String eventData) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + switch (eventName) { + case "loaded": + progressBar.setVisibility(View.INVISIBLE); + progressBarBlackBackground.setVisibility(View.INVISIBLE); + pipButton.setEnabled(true); + pipButton.setAlpha(1.0f); + showOrHideYoutubeLogo(false); + break; + } + } + }); + } + } + + private final String youtubeFrame = "" + + "
    " + + "
    " + + "
    " + + " " + + " " + + "" + + ""; + private OnShowListener onShowListener = new OnShowListener() { @Override public void onShow(DialogInterface dialog) { @@ -156,19 +281,19 @@ public class EmbedBottomSheet extends BottomSheet { } }); - FrameLayout containerLayout = new FrameLayout(context) { + containerLayout = new FrameLayout(context) { @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); try { - if (webView.getParent() != null) { + if ((pipVideoView == null || webView.getVisibility() != VISIBLE) && webView.getParent() != null) { removeView(webView); webView.stopLoading(); webView.loadUrl("about:blank"); webView.destroy(); } - if (!videoView.isInline()) { + if (!videoView.isInline() && pipVideoView == null) { if (instance == EmbedBottomSheet.this) { instance = null; } @@ -196,7 +321,15 @@ public class EmbedBottomSheet extends BottomSheet { }); setCustomView(containerLayout); - webView = new WebView(context); + webView = new WebView(context) { + @Override + public boolean onTouchEvent(MotionEvent event) { + if (isYouTube && event.getAction() == MotionEvent.ACTION_DOWN) { + showOrHideYoutubeLogo(true); + } + return super.onTouchEvent(event); + } + }; webView.getSettings().setJavaScriptEnabled(true); webView.getSettings().setDomStorageEnabled(true); if (Build.VERSION.SDK_INT >= 17) { @@ -218,10 +351,11 @@ public class EmbedBottomSheet extends BottomSheet { @Override public void onShowCustomView(View view, CustomViewCallback callback) { - if (customView != null) { + if (customView != null || pipVideoView != null) { callback.onCustomViewHidden(); return; } + exitFromPip(); customView = view; getSheetContainer().setVisibility(View.INVISIBLE); fullscreenVideoContainer.setVisibility(View.VISIBLE); @@ -257,16 +391,36 @@ public class EmbedBottomSheet extends BottomSheet { @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); - progressBar.setVisibility(View.INVISIBLE); + if (!isYouTube || Build.VERSION.SDK_INT < 17) { + progressBar.setVisibility(View.INVISIBLE); + progressBarBlackBackground.setVisibility(View.INVISIBLE); + pipButton.setEnabled(true); + pipButton.setAlpha(1.0f); + } } }); containerLayout.addView(webView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 0, 0, 0, 48 + 36 + (hasDescription ? 22 : 0))); + youtubeLogoImage = new ImageView(context); + youtubeLogoImage.setVisibility(View.GONE); + containerLayout.addView(youtubeLogoImage, LayoutHelper.createFrame(66, 28, Gravity.RIGHT | Gravity.TOP, 0, 8, 8, 0)); + youtubeLogoImage.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (youtubeLogoImage.getAlpha() == 0) { + return; + } + openInButton.callOnClick(); + } + }); + videoView = new WebPlayerView(context, true, false, new WebPlayerView.WebPlayerViewDelegate() { @Override public void onInitFailed() { webView.setVisibility(View.VISIBLE); + imageButtonsContainer.setVisibility(View.VISIBLE); + copyTextButton.setVisibility(View.INVISIBLE); webView.setKeepScreenOn(true); videoView.setVisibility(View.INVISIBLE); videoView.getControlsView().setVisibility(View.INVISIBLE); @@ -448,7 +602,7 @@ public class EmbedBottomSheet extends BottomSheet { if (inline) { controlsView.setTranslationY(0); pipVideoView = new PipVideoView(); - return pipVideoView.show(parentActivity, EmbedBottomSheet.this, controlsView, aspectRatio, rotation); + return pipVideoView.show(parentActivity, EmbedBottomSheet.this, controlsView, aspectRatio, rotation, null); } if (animated) { @@ -513,25 +667,7 @@ public class EmbedBottomSheet extends BottomSheet { @Override public boolean checkInlinePermissons() { - if (parentActivity == null) { - return false; - } - if (Build.VERSION.SDK_INT < 23 || Settings.canDrawOverlays(parentActivity)) { - return true; - } else { - new AlertDialog.Builder(parentActivity).setTitle(LocaleController.getString("AppName", R.string.AppName)) - .setMessage(LocaleController.getString("PermissionDrawAboveOtherApps", R.string.PermissionDrawAboveOtherApps)) - .setPositiveButton(LocaleController.getString("PermissionOpenSettings", R.string.PermissionOpenSettings), new DialogInterface.OnClickListener() { - @TargetApi(Build.VERSION_CODES.M) - @Override - public void onClick(DialogInterface dialog, int which) { - if (parentActivity != null) { - parentActivity.startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + parentActivity.getPackageName()))); - } - } - }).show(); - } - return false; + return checkInlinePermissions(); } @Override @@ -542,6 +678,11 @@ public class EmbedBottomSheet extends BottomSheet { videoView.setVisibility(View.INVISIBLE); containerLayout.addView(videoView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 0, 0, 0, 48 + 36 + (hasDescription ? 22 : 0) - 10)); + progressBarBlackBackground = new View(context); + progressBarBlackBackground.setBackgroundColor(0xff000000); + progressBarBlackBackground.setVisibility(View.INVISIBLE); + containerLayout.addView(progressBarBlackBackground, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 0, 0, 0, 48 + 36 + (hasDescription ? 22 : 0))); + progressBar = new RadialProgressView(context); progressBar.setVisibility(View.INVISIBLE); containerLayout.addView(progressBar, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER, 0, 0, 0, (48 + 36 + (hasDescription ? 22 : 0)) / 2)); @@ -578,15 +719,22 @@ public class EmbedBottomSheet extends BottomSheet { frameLayout.setBackgroundColor(Theme.getColor(Theme.key_dialogBackground)); containerLayout.addView(frameLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT | Gravity.BOTTOM)); + LinearLayout linearLayout = new LinearLayout(context); + linearLayout.setOrientation(LinearLayout.HORIZONTAL); + linearLayout.setWeightSum(1); + frameLayout.addView(linearLayout, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.RIGHT)); + textView = new TextView(context); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); textView.setTextColor(Theme.getColor(Theme.key_dialogTextBlue4)); textView.setGravity(Gravity.CENTER); + textView.setSingleLine(true); + textView.setEllipsize(TextUtils.TruncateAt.END); textView.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.getColor(Theme.key_dialogButtonSelector), 0)); textView.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); textView.setText(LocaleController.getString("Close", R.string.Close).toUpperCase()); textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - frameLayout.addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); + frameLayout.addView(textView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); textView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -594,20 +742,73 @@ public class EmbedBottomSheet extends BottomSheet { } }); - LinearLayout linearLayout = new LinearLayout(context); - linearLayout.setOrientation(LinearLayout.HORIZONTAL); - frameLayout.addView(linearLayout, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.RIGHT)); + imageButtonsContainer = new LinearLayout(context); + imageButtonsContainer.setVisibility(View.INVISIBLE); + frameLayout.addView(imageButtonsContainer, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.CENTER)); - textView = new TextView(context); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - textView.setTextColor(Theme.getColor(Theme.key_dialogTextBlue4)); - textView.setGravity(Gravity.CENTER); - textView.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.getColor(Theme.key_dialogButtonSelector), 0)); - textView.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); - textView.setText(LocaleController.getString("Copy", R.string.Copy).toUpperCase()); - textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - linearLayout.addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); - textView.setOnClickListener(new View.OnClickListener() { + pipButton = new ImageView(context); + pipButton.setScaleType(ImageView.ScaleType.CENTER); + pipButton.setImageResource(R.drawable.video_pip); + pipButton.setEnabled(false); + pipButton.setAlpha(0.5f); + pipButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_dialogTextBlue4), PorterDuff.Mode.MULTIPLY)); + pipButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.getColor(Theme.key_dialogButtonSelector), 0)); + imageButtonsContainer.addView(pipButton, LayoutHelper.createFrame(48, 48, Gravity.TOP | Gravity.LEFT, 0, 0, 4, 0)); + pipButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (!checkInlinePermissions()) { + return; + } + if (progressBar.getVisibility() == View.VISIBLE) { + return; + } + boolean animated = false; + pipVideoView = new PipVideoView(); + pipVideoView.show(parentActivity, EmbedBottomSheet.this, null, width != 0 && height != 0 ? width / (float) height : 1.0f, 0, webView); + if (isYouTube) { + runJsCode("hideControls();"); + } + if (animated) { + animationInProgress = true; + + View view = videoView.getAspectRatioView(); + view.getLocationInWindow(position); + position[0] -= getLeftInset(); + position[1] -= containerView.getTranslationY(); + + TextureView textureView = videoView.getTextureView(); + ImageView textureImageView = videoView.getTextureImageView(); + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether( + ObjectAnimator.ofFloat(textureImageView, "scaleX", 1.0f), + ObjectAnimator.ofFloat(textureImageView, "scaleY", 1.0f), + ObjectAnimator.ofFloat(textureImageView, "translationX", position[0]), + ObjectAnimator.ofFloat(textureImageView, "translationY", position[1]), + ObjectAnimator.ofFloat(textureView, "scaleX", 1.0f), + ObjectAnimator.ofFloat(textureView, "scaleY", 1.0f), + ObjectAnimator.ofFloat(textureView, "translationX", position[0]), + ObjectAnimator.ofFloat(textureView, "translationY", position[1]), + ObjectAnimator.ofFloat(containerView, "translationY", 0), + ObjectAnimator.ofInt(backDrawable, "alpha", 51) + ); + animatorSet.setInterpolator(new DecelerateInterpolator()); + animatorSet.setDuration(250); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + animationInProgress = false; + } + }); + animatorSet.start(); + } else { + containerView.setTranslationY(0); + } + dismissInternal(); + } + }); + + View.OnClickListener copyClickListener = new View.OnClickListener() { @Override public void onClick(View v) { try { @@ -620,18 +821,41 @@ public class EmbedBottomSheet extends BottomSheet { Toast.makeText(getContext(), LocaleController.getString("LinkCopied", R.string.LinkCopied), Toast.LENGTH_SHORT).show(); dismiss(); } - }); + }; - textView = new TextView(context); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - textView.setTextColor(Theme.getColor(Theme.key_dialogTextBlue4)); - textView.setGravity(Gravity.CENTER); - textView.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.getColor(Theme.key_dialogButtonSelector), 0)); - textView.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); - textView.setText(LocaleController.getString("OpenInBrowser", R.string.OpenInBrowser).toUpperCase()); - textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - linearLayout.addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); - textView.setOnClickListener(new View.OnClickListener() { + ImageView copyButton = new ImageView(context); + copyButton.setScaleType(ImageView.ScaleType.CENTER); + copyButton.setImageResource(R.drawable.video_copy); + copyButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_dialogTextBlue4), PorterDuff.Mode.MULTIPLY)); + copyButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.getColor(Theme.key_dialogButtonSelector), 0)); + imageButtonsContainer.addView(copyButton, LayoutHelper.createFrame(48, 48, Gravity.TOP | Gravity.LEFT)); + copyButton.setOnClickListener(copyClickListener); + + copyTextButton = new TextView(context); + copyTextButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + copyTextButton.setTextColor(Theme.getColor(Theme.key_dialogTextBlue4)); + copyTextButton.setGravity(Gravity.CENTER); + copyTextButton.setSingleLine(true); + copyTextButton.setEllipsize(TextUtils.TruncateAt.END); + copyTextButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.getColor(Theme.key_dialogButtonSelector), 0)); + copyTextButton.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); + copyTextButton.setText(LocaleController.getString("Copy", R.string.Copy).toUpperCase()); + copyTextButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + linearLayout.addView(copyTextButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); + copyTextButton.setOnClickListener(copyClickListener); + + openInButton = new TextView(context); + openInButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + openInButton.setTextColor(Theme.getColor(Theme.key_dialogTextBlue4)); + openInButton.setGravity(Gravity.CENTER); + openInButton.setSingleLine(true); + openInButton.setEllipsize(TextUtils.TruncateAt.END); + openInButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.getColor(Theme.key_dialogButtonSelector), 0)); + openInButton.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); + openInButton.setText(LocaleController.getString("OpenInBrowser", R.string.OpenInBrowser).toUpperCase()); + openInButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + linearLayout.addView(openInButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); + openInButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Browser.openUrl(parentActivity, openUrl); @@ -650,6 +874,8 @@ public class EmbedBottomSheet extends BottomSheet { } else { progressBar.setVisibility(View.VISIBLE); webView.setVisibility(View.VISIBLE); + imageButtonsContainer.setVisibility(View.VISIBLE); + copyTextButton.setVisibility(View.INVISIBLE); webView.setKeepScreenOn(true); videoView.setVisibility(View.INVISIBLE); videoView.getControlsView().setVisibility(View.INVISIBLE); @@ -661,7 +887,36 @@ public class EmbedBottomSheet extends BottomSheet { HashMap args = new HashMap<>(); args.put("Referer", "http://youtube.com"); try { - webView.loadUrl(embedUrl, args); + String currentYoutubeId = videoView.getYoutubeId(); + if (currentYoutubeId != null) { + progressBarBlackBackground.setVisibility(View.VISIBLE); + youtubeLogoImage.setVisibility(View.VISIBLE); + youtubeLogoImage.setImageResource(R.drawable.ytlogo); + isYouTube = true; + if (Build.VERSION.SDK_INT >= 17) { + webView.addJavascriptInterface(new YoutubeProxy(), "YoutubeProxy"); + } + int seekToTime = 0; + if (openUrl != null) { + try { + Uri uri = Uri.parse(openUrl); + String t = uri.getQueryParameter("t"); + if (t != null) { + if (t.contains("m")) { + String arg[] = t.split("m"); + seekToTime = Utilities.parseInt(arg[0]) * 60 + Utilities.parseInt(arg[1]); + } else { + seekToTime = Utilities.parseInt(t); + } + } + } catch (Exception e) { + FileLog.e(e); + } + } + webView.loadDataWithBaseURL("https://www.youtube.com", String.format(youtubeFrame, currentYoutubeId, seekToTime), "text/html", "UTF-8", "http://youtube.com"); + } else { + webView.loadUrl(embedUrl, args); + } } catch (Exception e) { FileLog.e(e); } @@ -710,6 +965,51 @@ public class EmbedBottomSheet extends BottomSheet { instance = this; } + private void runJsCode(String code) { + if (Build.VERSION.SDK_INT >= 21) { + webView.evaluateJavascript(code, null); + } else { + try { + webView.loadUrl("javascript:" + code); + } catch (Exception e) { + FileLog.e(e); + } + } + } + + private void showOrHideYoutubeLogo(final boolean show) { + youtubeLogoImage.animate().alpha(show ? 1.0f : 0.0f).setDuration(200).setStartDelay(show ? 0 : 2900).setInterpolator(new DecelerateInterpolator()).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (show) { + showOrHideYoutubeLogo(false); + } + } + }).start(); + } + + public boolean checkInlinePermissions() { + if (parentActivity == null) { + return false; + } + if (Build.VERSION.SDK_INT < 23 || Settings.canDrawOverlays(parentActivity)) { + return true; + } else { + new AlertDialog.Builder(parentActivity).setTitle(LocaleController.getString("AppName", R.string.AppName)) + .setMessage(LocaleController.getString("PermissionDrawAboveOtherApps", R.string.PermissionDrawAboveOtherApps)) + .setPositiveButton(LocaleController.getString("PermissionOpenSettings", R.string.PermissionOpenSettings), new DialogInterface.OnClickListener() { + @TargetApi(Build.VERSION_CODES.M) + @Override + public void onClick(DialogInterface dialog, int which) { + if (parentActivity != null) { + parentActivity.startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + parentActivity.getPackageName()))); + } + } + }).show(); + } + return false; + } + @Override protected boolean canDismissWithSwipe() { return videoView.getVisibility() != View.VISIBLE || !videoView.isInFullscreen(); @@ -734,6 +1034,12 @@ public class EmbedBottomSheet extends BottomSheet { } public void destroy() { + if (webView != null && webView.getVisibility() == View.VISIBLE) { + containerLayout.removeView(webView); + webView.stopLoading(); + webView.loadUrl("about:blank"); + webView.destroy(); + } if (pipVideoView != null) { pipVideoView.close(); pipVideoView = null; @@ -745,6 +1051,27 @@ public class EmbedBottomSheet extends BottomSheet { dismissInternal(); } + public void exitFromPip() { + if (webView == null || pipVideoView == null) { + return; + } + if (ApplicationLoader.mainInterfacePaused) { + parentActivity.startService(new Intent(ApplicationLoader.applicationContext, BringAppForegroundService.class)); + } + if (isYouTube) { + runJsCode("showControls();"); + } + ViewGroup parent = (ViewGroup) webView.getParent(); + if (parent != null) { + parent.removeView(webView); + } + containerLayout.addView(webView, 0, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 0, 0, 0, 48 + 36 + (hasDescription ? 22 : 0))); + setShowWithoutAnimation(true); + show(); + pipVideoView.close(); + pipVideoView = null; + } + public static EmbedBottomSheet getInstance() { return instance; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiView.java index db5717ec8..c2c2e3fec 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiView.java @@ -16,7 +16,6 @@ import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.DialogInterface; -import android.content.SharedPreferences; import android.database.DataSetObserver; import android.graphics.Canvas; import android.graphics.Outline; @@ -28,6 +27,7 @@ import android.graphics.drawable.Drawable; import android.os.Build; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; +import android.text.SpannableStringBuilder; import android.util.TypedValue; import android.view.Gravity; import android.view.HapticFeedbackConstants; @@ -49,12 +49,12 @@ import android.widget.PopupWindow; import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ChatObject; import org.telegram.messenger.Emoji; import org.telegram.messenger.EmojiData; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesController; import org.telegram.messenger.NotificationCenter; -import org.telegram.messenger.Utilities; import org.telegram.messenger.query.StickersQuery; import org.telegram.messenger.FileLog; import org.telegram.messenger.R; @@ -67,12 +67,12 @@ import org.telegram.ui.Cells.ContextLinkCell; import org.telegram.ui.Cells.EmptyCell; import org.telegram.ui.Cells.FeaturedStickerSetInfoCell; import org.telegram.ui.Cells.StickerEmojiCell; +import org.telegram.ui.Cells.StickerSetGroupInfoCell; +import org.telegram.ui.Cells.StickerSetNameCell; import org.telegram.ui.StickerPreviewViewer; import java.lang.reflect.Field; import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; public class EmojiView extends FrameLayout implements NotificationCenter.NotificationCenterDelegate { @@ -82,6 +82,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific void onEmojiSelected(String emoji); void onStickerSelected(TLRPC.Document sticker); void onStickersSettingsClick(); + void onStickersGroupClick(int chatId); void onGifSelected(TLRPC.Document gif); void onGifTab(boolean opened); void onStickersTab(boolean opened); @@ -109,7 +110,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific if (set == null) { return; } - TLRPC.TL_messages_stickerSet stickerSet; + /*TLRPC.TL_messages_stickerSet stickerSet; if (set.id != 0) { stickerSet = StickersQuery.getStickerSetById(set.id); } else if (set.short_name != null) { @@ -127,7 +128,8 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific stickersLayoutManager.scrollToPositionWithOffset(position, 0); } else { listener.onShowStickerSet(null, set); - } + }*/ + listener.onShowStickerSet(null, set); } }; @@ -166,6 +168,15 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific return code; } + public void addEmojiToRecent(String code) { + Emoji.addRecentEmoji(code); + if (getVisibility() != VISIBLE || pager.getCurrentItem() != 0) { + Emoji.sortEmoji(); + } + Emoji.saveRecentEmoji(); + adapters.get(0).notifyDataSetChanged(); + } + private class ImageViewEmoji extends ImageView { private boolean touched; @@ -191,7 +202,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific touchedX = lastX; touchedY = lastY; - String color = emojiColor.get(code); + String color = Emoji.emojiColor.get(code); if (color != null) { switch (color) { case "\uD83C\uDFFB": @@ -241,33 +252,16 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific private void sendEmoji(String override) { String code = override != null ? override : (String) getTag(); + SpannableStringBuilder builder = new SpannableStringBuilder(); + builder.append(code); if (override == null) { if (pager.getCurrentItem() != 0) { - String color = emojiColor.get(code); + String color = Emoji.emojiColor.get(code); if (color != null) { code = addColorToCode(code, color); } } - Integer count = emojiUseHistory.get(code); - if (count == null) { - count = 0; - } - if (count == 0 && emojiUseHistory.size() > 50) { - for (int a = recentEmoji.size() - 1; a >= 0; a--) { - String emoji = recentEmoji.get(a); - emojiUseHistory.remove(emoji); - recentEmoji.remove(a); - if (emojiUseHistory.size() <= 50) { - break; - } - } - } - emojiUseHistory.put(code, ++count); - if (pager.getCurrentItem() != 0) { - sortEmoji(); - } - saveRecentEmoji(); - adapters.get(0).notifyDataSetChanged(); + addEmojiToRecent(code); if (listener != null) { listener.onEmojiSelected(Emoji.fixEmoji(code)); } @@ -311,16 +305,20 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific String code = (String) getTag(); if (pager.getCurrentItem() != 0) { if (color != null) { - emojiColor.put(code, color); + Emoji.emojiColor.put(code, color); code = addColorToCode(code, color); } else { - emojiColor.remove(code); + Emoji.emojiColor.remove(code); } setImageDrawable(Emoji.getEmojiBigDrawable(code)); sendEmoji(null); - saveEmojiColors(); + Emoji.saveEmojiColors(); } else { - sendEmoji(code + (color != null ? color : "")); + if (color != null) { + sendEmoji(addColorToCode(code, color)); + } else { + sendEmoji(code); + } } } touched = false; @@ -558,13 +556,15 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific } private ArrayList adapters = new ArrayList<>(); - private HashMap emojiUseHistory = new HashMap<>(); - private static HashMap emojiColor = new HashMap<>(); - private ArrayList recentEmoji = new ArrayList<>(); private ArrayList stickerSets = new ArrayList<>(); + private int groupStickerPackNum; + private int groupStickerPackPosition; + private boolean groupStickersHidden; + private TLRPC.TL_messages_stickerSet groupStickerSet; private ArrayList recentGifs = new ArrayList<>(); private ArrayList recentStickers = new ArrayList<>(); + private ArrayList favouriteStickers = new ArrayList<>(); private Paint dotPaint; @@ -610,10 +610,13 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific private int location[] = new int[2]; private int stickersTabOffset; private int recentTabBum = -2; + private int favTabBum = -2; private int gifTabNum = -2; private int trendingTabNum = -2; private boolean switchToGifTab; + private TLRPC.ChatFull info; + private boolean isLayout; private int currentBackgroundType = -1; private Object outlineProvider; @@ -627,7 +630,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific private int minusDy; - public EmojiView(boolean needStickers, boolean needGif, final Context context) { + public EmojiView(boolean needStickers, boolean needGif, final Context context, final TLRPC.ChatFull chatFull) { super(context); Drawable stickersDrawable = context.getResources().getDrawable(R.drawable.ic_smiles2_stickers); @@ -643,6 +646,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific }; showGifs = needGif; + info = chatFull; dotPaint = new Paint(Paint.ANTI_ALIAS_FLAG); dotPaint.setColor(Theme.getColor(Theme.key_chat_emojiPanelNewTrending)); @@ -702,10 +706,13 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific stickersLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { - if (position == stickersGridAdapter.totalItems) { - return stickersGridAdapter.stickersPerRow; + if (position != stickersGridAdapter.totalItems) { + Object object = stickersGridAdapter.cache.get(position); + if (object == null || stickersGridAdapter.cache.get(position) instanceof TLRPC.Document) { + return 1; + } } - return 1; + return stickersGridAdapter.stickersPerRow; } }); stickersGridView.setPadding(0, AndroidUtilities.dp(4 + 48), 0, 0); @@ -858,6 +865,9 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific } if (recentGifs.isEmpty()) { updateStickerTabs(); + if (stickersGridAdapter != null) { + stickersGridAdapter.notifyDataSetChanged(); + } } } }); @@ -883,7 +893,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific float lastX; float lastTranslateX; boolean first = true; - final int touchslop=ViewConfiguration.get(getContext()).getScaledTouchSlop(); + final int touchslop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); float downX, downY; boolean draggingVertically, draggingHorizontally; VelocityTracker vTracker; @@ -893,19 +903,19 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific if (getParent() != null) { getParent().requestDisallowInterceptTouchEvent(true); } - if(ev.getAction()==MotionEvent.ACTION_DOWN){ - draggingVertically=draggingHorizontally=false; - downX=ev.getRawX(); - downY=ev.getRawY(); - }else{ - if(!draggingVertically && !draggingHorizontally && dragListener!=null){ - if(Math.abs(ev.getRawY()-downY)>=touchslop){ - draggingVertically=true; - downY=ev.getRawY(); - dragListener.onDragStart(); - if(startedScroll){ + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + draggingVertically = draggingHorizontally = false; + downX = ev.getRawX(); + downY = ev.getRawY(); + } else { + if (!draggingVertically && !draggingHorizontally && dragListener != null) { + if (Math.abs(ev.getRawY() - downY) >= touchslop) { + draggingVertically = true; + downY = ev.getRawY(); + dragListener.onDragStart(); + if (startedScroll) { pager.endFakeDrag(); - startedScroll=false; + startedScroll = false; } return true; } @@ -920,43 +930,44 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific first = false; lastX = ev.getX(); } - if(ev.getAction()==MotionEvent.ACTION_DOWN){ - draggingVertically=draggingHorizontally=false; - downX=ev.getRawX(); - downY=ev.getRawY(); - }else{ - if(!draggingVertically && !draggingHorizontally && dragListener!=null){ - if(Math.abs(ev.getRawX()-downX)>=touchslop){ - draggingHorizontally=true; - }else if(Math.abs(ev.getRawY()-downY)>=touchslop){ - draggingVertically=true; - downY=ev.getRawY(); + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + draggingVertically = draggingHorizontally = false; + downX = ev.getRawX(); + downY = ev.getRawY(); + } else { + if (!draggingVertically && !draggingHorizontally && dragListener != null) { + if (Math.abs(ev.getRawX() - downX) >= touchslop) { + draggingHorizontally = true; + } else if (Math.abs(ev.getRawY() - downY) >= touchslop) { + draggingVertically = true; + downY = ev.getRawY(); dragListener.onDragStart(); - if(startedScroll){ + if (startedScroll) { pager.endFakeDrag(); - startedScroll=false; + startedScroll = false; } } } } - if(draggingVertically){ - if(vTracker==null) - vTracker=VelocityTracker.obtain(); + if (draggingVertically) { + if (vTracker == null) { + vTracker = VelocityTracker.obtain(); + } vTracker.addMovement(ev); - if(ev.getAction()==MotionEvent.ACTION_UP || ev.getAction()==MotionEvent.ACTION_CANCEL){ + if (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL) { vTracker.computeCurrentVelocity(1000); - float velocity=vTracker.getYVelocity(); + float velocity = vTracker.getYVelocity(); vTracker.recycle(); - vTracker=null; - if(ev.getAction()==MotionEvent.ACTION_UP){ + vTracker = null; + if (ev.getAction() == MotionEvent.ACTION_UP) { dragListener.onDragEnd(velocity); - }else{ + } else { dragListener.onDragCancel(); } - first=true; - draggingVertically=draggingHorizontally=false; - }else{ - dragListener.onDrag(Math.round(ev.getRawY()-downY)); + first = true; + draggingVertically = draggingHorizontally = false; + } else { + dragListener.onDrag(Math.round(ev.getRawY() - downY)); } return true; } @@ -992,7 +1003,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific lastX = ev.getX(); if (ev.getAction() == MotionEvent.ACTION_CANCEL || ev.getAction() == MotionEvent.ACTION_UP) { first = true; - draggingVertically=draggingHorizontally=false; + draggingVertically = draggingHorizontally = false; if (startedScroll) { pager.endFakeDrag(); startedScroll = false; @@ -1027,6 +1038,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific listener.onGifTab(false); gifsGridView.setVisibility(GONE); stickersGridView.setVisibility(VISIBLE); + int vis = stickersGridView.getVisibility(); stickersEmptyView.setVisibility(stickersGridAdapter.getItemCount() != 0 ? GONE : VISIBLE); checkScroll(); saveNewPage(); @@ -1045,10 +1057,15 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific if (page == gifTabNum + 1 || page == trendingTabNum + 1) { return; } else if (page == recentTabBum + 1) { - stickersLayoutManager.scrollToPositionWithOffset(0, 0); + stickersLayoutManager.scrollToPositionWithOffset(stickersGridAdapter.getPositionForPack("recent"), 0); checkStickersTabY(null, 0); stickersTab.onPageScrolled(recentTabBum + 1, (recentTabBum > 0 ? recentTabBum : stickersTabOffset) + 1); return; + } else if (page == favTabBum + 1) { + stickersLayoutManager.scrollToPositionWithOffset(stickersGridAdapter.getPositionForPack("fav"), 0); + checkStickersTabY(null, 0); + stickersTab.onPageScrolled(favTabBum + 1, (favTabBum > 0 ? favTabBum : stickersTabOffset) + 1); + return; } } int index = page - 1 - stickersTabOffset; @@ -1196,7 +1213,8 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific } }); currentPage = getContext().getSharedPreferences("emoji", Activity.MODE_PRIVATE).getInt("selected_page", 0); - loadRecents(); + Emoji.loadRecentEmoji(); + adapters.get(0).notifyDataSetChanged(); } private void checkStickersTabY(View list, int dy) { @@ -1224,6 +1242,14 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific if (stickersGridView == null) { return; } + int firstTab; + if (favTabBum > 0) { + firstTab = favTabBum; + } else if (recentTabBum > 0) { + firstTab = recentTabBum; + } else { + firstTab = stickersTabOffset; + } if (stickersGridView.getVisibility() != VISIBLE) { if (gifsGridView != null && gifsGridView.getVisibility() != VISIBLE) { gifsGridView.setVisibility(VISIBLE); @@ -1231,10 +1257,10 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific if (stickersEmptyView != null && stickersEmptyView.getVisibility() == VISIBLE) { stickersEmptyView.setVisibility(GONE); } - stickersTab.onPageScrolled(gifTabNum + 1, (recentTabBum > 0 ? recentTabBum : stickersTabOffset) + 1); + stickersTab.onPageScrolled(gifTabNum + 1, firstTab + 1); return; } - stickersTab.onPageScrolled(stickersGridAdapter.getTabForPosition(firstVisibleItem) + 1, (recentTabBum > 0 ? recentTabBum : stickersTabOffset) + 1); + stickersTab.onPageScrolled(stickersGridAdapter.getTabForPosition(firstVisibleItem) + 1, firstTab + 1); } private void saveNewPage() { @@ -1255,11 +1281,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific } public void clearRecentEmoji() { - SharedPreferences preferences = getContext().getSharedPreferences("emoji", Activity.MODE_PRIVATE); - preferences.edit().putBoolean("filled_default", true).commit(); - emojiUseHistory.clear(); - recentEmoji.clear(); - saveRecentEmoji(); + Emoji.clearRecentEmoji(); adapters.get(0).notifyDataSetChanged(); } @@ -1330,34 +1352,6 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific }, time); } - private void saveRecentEmoji() { - SharedPreferences preferences = getContext().getSharedPreferences("emoji", Activity.MODE_PRIVATE); - StringBuilder stringBuilder = new StringBuilder(); - for (HashMap.Entry entry : emojiUseHistory.entrySet()) { - if (stringBuilder.length() != 0) { - stringBuilder.append(","); - } - stringBuilder.append(entry.getKey()); - stringBuilder.append("="); - stringBuilder.append(entry.getValue()); - } - preferences.edit().putString("emojis2", stringBuilder.toString()).commit(); - } - - private void saveEmojiColors() { - SharedPreferences preferences = getContext().getSharedPreferences("emoji", Activity.MODE_PRIVATE); - StringBuilder stringBuilder = new StringBuilder(); - for (HashMap.Entry entry : emojiColor.entrySet()) { - if (stringBuilder.length() != 0) { - stringBuilder.append(","); - } - stringBuilder.append(entry.getKey()); - stringBuilder.append("="); - stringBuilder.append(entry.getValue()); - } - preferences.edit().putString("color", stringBuilder.toString()).commit(); - } - public void switchToGifRecent() { if (gifTabNum >= 0 && !recentGifs.isEmpty()) { stickersTab.selectTab(gifTabNum + 1); @@ -1367,40 +1361,12 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific pager.setCurrentItem(6); } - private void sortEmoji() { - recentEmoji.clear(); - for (HashMap.Entry entry : emojiUseHistory.entrySet()) { - recentEmoji.add(entry.getKey()); - } - Collections.sort(recentEmoji, new Comparator() { - @Override - public int compare(String lhs, String rhs) { - Integer count1 = emojiUseHistory.get(lhs); - Integer count2 = emojiUseHistory.get(rhs); - if (count1 == null) { - count1 = 0; - } - if (count2 == null) { - count2 = 0; - } - if (count1 > count2) { - return -1; - } else if (count1 < count2) { - return 1; - } - return 0; - } - }); - while (recentEmoji.size() > 50) { - recentEmoji.remove(recentEmoji.size() - 1); - } - } - private void updateStickerTabs() { if (stickersTab == null) { return; } recentTabBum = -2; + favTabBum = -2; gifTabNum = -2; trendingTabNum = -2; @@ -1431,6 +1397,14 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific stickersCounter.setText(String.format("%d", unread.size())); } + if (!favouriteStickers.isEmpty()) { + favTabBum = stickersTabOffset; + stickersTabOffset++; + drawable = getContext().getResources().getDrawable(R.drawable.staredstickerstab); + Theme.setDrawableColorByKey(drawable, Theme.key_chat_emojiPanelIcon); + stickersTab.addIconTab(drawable); + } + if (!recentStickers.isEmpty()) { recentTabBum = stickersTabOffset; stickersTabOffset++; @@ -1440,6 +1414,9 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific } stickerSets.clear(); + groupStickerSet = null; + groupStickerPackPosition = -1; + groupStickerPackNum = -10; ArrayList packs = StickersQuery.getStickerSets(StickersQuery.TYPE_IMAGE); for (int a = 0; a < packs.size(); a++) { TLRPC.TL_messages_stickerSet pack = packs.get(a); @@ -1448,8 +1425,53 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific } stickerSets.add(pack); } + if (info != null) { + long hiddenStickerSetId = getContext().getSharedPreferences("emoji", Activity.MODE_PRIVATE).getLong("group_hide_stickers_" + info.id, -1); + TLRPC.Chat chat = MessagesController.getInstance().getChat(info.id); + if (chat == null || info.stickerset == null || !ChatObject.hasAdminRights(chat)) { + groupStickersHidden = hiddenStickerSetId != -1; + } else if (info.stickerset != null) { + groupStickersHidden = hiddenStickerSetId == info.stickerset.id; + } + if (info.stickerset != null) { + TLRPC.TL_messages_stickerSet pack = StickersQuery.getGroupStickerSetById(info.stickerset); + if (pack != null && pack.documents != null && !pack.documents.isEmpty() && pack.set != null) { + TLRPC.TL_messages_stickerSet set = new TLRPC.TL_messages_stickerSet(); + set.documents = pack.documents; + set.packs = pack.packs; + set.set = pack.set; + if (groupStickersHidden) { + groupStickerPackNum = stickerSets.size(); + stickerSets.add(set); + } else { + groupStickerPackNum = 0; + stickerSets.add(0, set); + } + groupStickerSet = info.can_set_stickers ? set : null; + } + } else if (info.can_set_stickers) { + TLRPC.TL_messages_stickerSet pack = new TLRPC.TL_messages_stickerSet(); + if (groupStickersHidden) { + groupStickerPackNum = stickerSets.size(); + stickerSets.add(pack); + } else { + groupStickerPackNum = 0; + stickerSets.add(0, pack); + } + } + } for (int a = 0; a < stickerSets.size(); a++) { - stickersTab.addStickerTab(stickerSets.get(a).documents.get(0)); + if (a == groupStickerPackNum) { + TLRPC.Chat chat = MessagesController.getInstance().getChat(info.id); + if (chat == null) { + stickerSets.remove(0); + a--; + } else { + stickersTab.addStickerTab(chat); + } + } else { + stickersTab.addStickerTab(stickerSets.get(a).documents.get(0)); + } } if (trendingGridAdapter != null && trendingGridAdapter.getItemCount() != 0 && unread.isEmpty()) { drawable = getContext().getResources().getDrawable(R.drawable.ic_smiles_trend); @@ -1497,7 +1519,15 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific } else { int position = stickersLayoutManager.findFirstVisibleItemPosition(); if (position != RecyclerView.NO_POSITION) { - stickersTab.onPageScrolled(stickersGridAdapter.getTabForPosition(position) + 1, (recentTabBum > 0 ? recentTabBum : stickersTabOffset) + 1); + int firstTab; + if (favTabBum > 0) { + firstTab = favTabBum; + } else if (recentTabBum > 0) { + firstTab = recentTabBum; + } else { + firstTab = stickersTabOffset; + } + stickersTab.onPageScrolled(stickersGridAdapter.getTabForPosition(position) + 1, firstTab + 1); } } } @@ -1507,7 +1537,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific if (document == null) { return; } - StickersQuery.addRecentSticker(StickersQuery.TYPE_IMAGE, document, (int) (System.currentTimeMillis() / 1000)); + StickersQuery.addRecentSticker(StickersQuery.TYPE_IMAGE, document, (int) (System.currentTimeMillis() / 1000), false); boolean wasEmpty = recentStickers.isEmpty(); recentStickers = StickersQuery.getRecentStickers(StickersQuery.TYPE_IMAGE); if (stickersGridAdapter != null) { @@ -1532,82 +1562,6 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific } } - public void loadRecents() { - SharedPreferences preferences = getContext().getSharedPreferences("emoji", Activity.MODE_PRIVATE); - - String str; - try { - emojiUseHistory.clear(); - if (preferences.contains("emojis")) { - str = preferences.getString("emojis", ""); - if (str != null && str.length() > 0) { - String[] args = str.split(","); - for (String arg : args) { - String[] args2 = arg.split("="); - long value = Utilities.parseLong(args2[0]); - String string = ""; - for (int a = 0; a < 4; a++) { - char ch = (char) value; - string = String.valueOf(ch) + string; - value >>= 16; - if (value == 0) { - break; - } - } - if (string.length() > 0) { - emojiUseHistory.put(string, Utilities.parseInt(args2[1])); - } - } - } - preferences.edit().remove("emojis").commit(); - saveRecentEmoji(); - } else { - str = preferences.getString("emojis2", ""); - if (str != null && str.length() > 0) { - String[] args = str.split(","); - for (String arg : args) { - String[] args2 = arg.split("="); - emojiUseHistory.put(args2[0], Utilities.parseInt(args2[1])); - } - } - } - if (emojiUseHistory.isEmpty()) { - if (!preferences.getBoolean("filled_default", false)) { - String[] newRecent = new String[]{ - "\uD83D\uDE02", "\uD83D\uDE18", "\u2764", "\uD83D\uDE0D", "\uD83D\uDE0A", "\uD83D\uDE01", - "\uD83D\uDC4D", "\u263A", "\uD83D\uDE14", "\uD83D\uDE04", "\uD83D\uDE2D", "\uD83D\uDC8B", - "\uD83D\uDE12", "\uD83D\uDE33", "\uD83D\uDE1C", "\uD83D\uDE48", "\uD83D\uDE09", "\uD83D\uDE03", - "\uD83D\uDE22", "\uD83D\uDE1D", "\uD83D\uDE31", "\uD83D\uDE21", "\uD83D\uDE0F", "\uD83D\uDE1E", - "\uD83D\uDE05", "\uD83D\uDE1A", "\uD83D\uDE4A", "\uD83D\uDE0C", "\uD83D\uDE00", "\uD83D\uDE0B", - "\uD83D\uDE06", "\uD83D\uDC4C", "\uD83D\uDE10", "\uD83D\uDE15"}; - for (int i = 0; i < newRecent.length; i++) { - emojiUseHistory.put(newRecent[i], newRecent.length - i); - } - preferences.edit().putBoolean("filled_default", true).commit(); - saveRecentEmoji(); - } - } - sortEmoji(); - adapters.get(0).notifyDataSetChanged(); - } catch (Exception e) { - FileLog.e(e); - } - - try { - str = preferences.getString("color", ""); - if (str != null && str.length() > 0) { - String[] args = str.split(","); - for (int a = 0; a < args.length; a++) { - String arg = args[a]; - String[] args2 = arg.split("="); - emojiColor.put(args2[0], args2[1]); - } - } - } catch (Exception e) { - FileLog.e(e); - } - } - @Override public void requestLayout() { if (isLayout) { @@ -1691,8 +1645,13 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific listener = value; } - public void setDragListener(DragListener dragListener){ - this.dragListener=dragListener; + public void setDragListener(DragListener dragListener) { + this.dragListener = dragListener; + } + + public void setChatInfo(TLRPC.ChatFull chatInfo) { + info = chatInfo; + updateStickerTabs(); } public void invalidateViews() { @@ -1717,6 +1676,8 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific if (stickersTab.getCurrentPosition() == gifTabNum + 1) { if (recentTabBum >= 0) { stickersTab.selectTab(recentTabBum + 1); + } else if (favTabBum >= 0) { + stickersTab.selectTab(favTabBum + 1); } else if (gifTabNum >= 0) { stickersTab.selectTab(gifTabNum + 2); } else { @@ -1745,6 +1706,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific NotificationCenter.getInstance().addObserver(this, NotificationCenter.stickersDidLoaded); NotificationCenter.getInstance().addObserver(this, NotificationCenter.recentImagesDidLoaded); NotificationCenter.getInstance().addObserver(this, NotificationCenter.featuredStickersDidLoaded); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.groupStickersDidLoaded); AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { @@ -1759,7 +1721,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific public void setVisibility(int visibility) { super.setVisibility(visibility); if (visibility != GONE) { - sortEmoji(); + Emoji.sortEmoji(); adapters.get(0).notifyDataSetChanged(); if (stickersGridAdapter != null) { NotificationCenter.getInstance().addObserver(this, NotificationCenter.stickersDidLoaded); @@ -1776,8 +1738,9 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific } checkDocuments(true); checkDocuments(false); - StickersQuery.loadRecents(StickersQuery.TYPE_IMAGE, true, true); - StickersQuery.loadRecents(StickersQuery.TYPE_IMAGE, false, true); + StickersQuery.loadRecents(StickersQuery.TYPE_IMAGE, true, true, false); + StickersQuery.loadRecents(StickersQuery.TYPE_IMAGE, false, true, false); + StickersQuery.loadRecents(StickersQuery.TYPE_FAVE, false, true, false); } } @@ -1790,6 +1753,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific NotificationCenter.getInstance().removeObserver(this, NotificationCenter.stickersDidLoaded); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.recentDocumentsDidLoaded); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.featuredStickersDidLoaded); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.groupStickersDidLoaded); } } @@ -1811,15 +1775,31 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific if (previousCount != recentGifs.size()) { updateStickerTabs(); } - } else { - int previousCount = recentStickers.size(); - recentStickers = StickersQuery.getRecentStickers(StickersQuery.TYPE_IMAGE); if (stickersGridAdapter != null) { stickersGridAdapter.notifyDataSetChanged(); } - if (previousCount != recentStickers.size()) { + } else { + int previousCount = recentStickers.size(); + int previousCount2 = favouriteStickers.size(); + recentStickers = StickersQuery.getRecentStickers(StickersQuery.TYPE_IMAGE); + favouriteStickers = StickersQuery.getRecentStickers(StickersQuery.TYPE_FAVE); + for (int a = 0; a < favouriteStickers.size(); a++) { + TLRPC.Document favSticker = favouriteStickers.get(a); + for (int b = 0; b < recentStickers.size(); b++) { + TLRPC.Document recSticker = recentStickers.get(b); + if (recSticker.dc_id == favSticker.dc_id && recSticker.id == favSticker.id) { + recentStickers.remove(b); + break; + } + } + } + if (previousCount != recentStickers.size() || previousCount2 != favouriteStickers.size()) { updateStickerTabs(); } + if (stickersGridAdapter != null) { + stickersGridAdapter.notifyDataSetChanged(); + } + checkPanels(); } } @@ -1928,8 +1908,8 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific } } - public boolean areThereAnyStickers(){ - return stickersGridAdapter!=null && stickersGridAdapter.getItemCount()>0; + public boolean areThereAnyStickers() { + return stickersGridAdapter != null && stickersGridAdapter.getItemCount() > 0; } @SuppressWarnings("unchecked") @@ -1950,7 +1930,8 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific } } else if (id == NotificationCenter.recentDocumentsDidLoaded) { boolean isGif = (Boolean) args[0]; - if (isGif || (Integer) args[1] == StickersQuery.TYPE_IMAGE) { + int type = (Integer) args[1]; + if (isGif || type == StickersQuery.TYPE_IMAGE || type == StickersQuery.TYPE_FAVE) { checkDocuments(isGif); } } else if (id == NotificationCenter.featuredStickersDidLoaded) { @@ -1971,6 +1952,10 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific } } updateStickerTabs(); + } else if (id == NotificationCenter.groupStickersDidLoaded) { + if (info != null && info.stickerset != null && info.stickerset.id == (Long) args[0]) { + updateStickerTabs(); + } } } @@ -2103,9 +2088,12 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific } else { width = AndroidUtilities.displaySize.x; } + if (width == 0) { + width = 1080; + } } stickersPerRow = width / AndroidUtilities.dp(72); - trendingLayoutManager.setSpanCount(stickersPerRow); + trendingLayoutManager.setSpanCount(Math.max(1, stickersPerRow)); if (trendingLoaded) { return; } @@ -2153,9 +2141,10 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific private Context context; private int stickersPerRow; - private HashMap rowStartPack = new HashMap<>(); - private HashMap packStartRow = new HashMap<>(); - private HashMap cache = new HashMap<>(); + private HashMap rowStartPack = new HashMap<>(); + private HashMap packStartPosition = new HashMap<>(); + private HashMap cache = new HashMap<>(); + private HashMap positionToRow = new HashMap<>(); private int totalItems; public StickersGridAdapter(Context context) { @@ -2176,18 +2165,25 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific return cache.get(i); } - public int getPositionForPack(TLRPC.TL_messages_stickerSet stickerSet) { - Integer pos = packStartRow.get(stickerSet); + public int getPositionForPack(Object pack) { + Integer pos = packStartPosition.get(pack); if (pos == null) { return -1; } - return pos * stickersPerRow; + return pos; } @Override public int getItemViewType(int position) { - if (cache.get(position) != null) { - return 0; + Object object = cache.get(position); + if (object != null) { + if (object instanceof TLRPC.Document) { + return 0; + } else if (object instanceof String) { + return 3; + } else { + return 2; + } } return 1; } @@ -2200,12 +2196,22 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific } stickersPerRow = width / AndroidUtilities.dp(72); } - int row = position / stickersPerRow; - TLRPC.TL_messages_stickerSet pack = rowStartPack.get(row); - if (pack == null) { - return recentTabBum; + Integer row = positionToRow.get(position); + if (row == null) { + return stickerSets.size() - 1 + stickersTabOffset; + } + Object pack = rowStartPack.get(row); + if (pack instanceof String) { + if ("recent".equals(pack)) { + return recentTabBum; + } else { + return favTabBum; + } + } else { + TLRPC.TL_messages_stickerSet set = (TLRPC.TL_messages_stickerSet) pack; + int idx = stickerSets.indexOf(set); + return idx + stickersTabOffset; } - return stickerSets.indexOf(pack) + stickersTabOffset; } @Override @@ -2222,6 +2228,37 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific case 1: view = new EmptyCell(context); break; + case 2: + view = new StickerSetNameCell(context); + ((StickerSetNameCell) view).setOnIconClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (groupStickerSet != null) { + if (listener != null) { + listener.onStickersGroupClick(info.id); + } + } else { + getContext().getSharedPreferences("emoji", Activity.MODE_PRIVATE).edit().putLong("group_hide_stickers_" + info.id, info.stickerset != null ? info.stickerset.id : 0).commit(); + updateStickerTabs(); + if (stickersGridAdapter != null) { + stickersGridAdapter.notifyDataSetChanged(); + } + } + } + }); + break; + case 3: + view = new StickerSetGroupInfoCell(context); + ((StickerSetGroupInfoCell) view).setAddOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (listener != null) { + listener.onStickersGroupClick(info.id); + } + } + }); + view.setLayoutParams(new RecyclerView.LayoutParams(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + break; } return new RecyclerListView.Holder(view); @@ -2230,25 +2267,80 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (holder.getItemViewType()) { - case 0: - TLRPC.Document sticker = cache.get(position); - ((StickerEmojiCell) holder.itemView).setSticker(sticker, false); - ((StickerEmojiCell) holder.itemView).setRecent(recentStickers.contains(sticker)); + case 0: { + TLRPC.Document sticker = (TLRPC.Document) cache.get(position); + StickerEmojiCell cell = (StickerEmojiCell) holder.itemView; + cell.setSticker(sticker, false); + cell.setRecent(recentStickers.contains(sticker) || favouriteStickers.contains(sticker)); break; - case 1: + } + case 1: { + EmptyCell cell = (EmptyCell) holder.itemView; if (position == totalItems) { - int row = (position - 1) / stickersPerRow; - TLRPC.TL_messages_stickerSet pack = rowStartPack.get(row); - if (pack == null) { - ((EmptyCell) holder.itemView).setHeight(1); + Integer row = positionToRow.get(position - 1); + if (row == null) { + cell.setHeight(1); } else { - int height = pager.getHeight() - (int) Math.ceil(pack.documents.size() / (float) stickersPerRow) * AndroidUtilities.dp(82); - ((EmptyCell) holder.itemView).setHeight(height > 0 ? height : 1); + ArrayList documents; + Object pack = rowStartPack.get(row); + if (pack instanceof TLRPC.TL_messages_stickerSet) { + documents = ((TLRPC.TL_messages_stickerSet) pack).documents; + } else if (pack instanceof String) { + if ("recent".equals(pack)) { + documents = recentStickers; + } else { + documents = favouriteStickers; + } + } else { + documents = null; + } + if (documents == null) { + cell.setHeight(1); + } else { + if (documents.isEmpty()) { + cell.setHeight(AndroidUtilities.dp(8)); + } else { + int height = pager.getHeight() - (int) Math.ceil(documents.size() / (float) stickersPerRow) * AndroidUtilities.dp(82); + cell.setHeight(height > 0 ? height : 1); + } + } } } else { - ((EmptyCell) holder.itemView).setHeight(AndroidUtilities.dp(82)); + cell.setHeight(AndroidUtilities.dp(82)); } break; + } + case 2: { + StickerSetNameCell cell = (StickerSetNameCell) holder.itemView; + if (position == groupStickerPackPosition) { + int icon; + if (groupStickersHidden && groupStickerSet == null) { + icon = 0; + } else { + icon = groupStickerSet != null ? R.drawable.stickersclose : R.drawable.stickerset_close; + } + TLRPC.Chat chat = info != null ? MessagesController.getInstance().getChat(info.id) : null; + cell.setText(LocaleController.formatString("CurrentGroupStickers", R.string.CurrentGroupStickers, chat != null ? chat.title : "Group Stickers"), icon); + } else { + Object object = cache.get(position); + if (object instanceof TLRPC.TL_messages_stickerSet) { + TLRPC.TL_messages_stickerSet set = (TLRPC.TL_messages_stickerSet) object; + if (set.set != null) { + cell.setText(set.set.title, 0); + } + } else if (object == recentStickers) { + cell.setText(LocaleController.getString("RecentStickers", R.string.RecentStickers), 0); + } else if (object == favouriteStickers) { + cell.setText(LocaleController.getString("FavoriteStickers", R.string.FavoriteStickers), 0); + } + } + break; + } + case 3: { + StickerSetGroupInfoCell cell = (StickerSetGroupInfoCell) holder.itemView; + cell.setIsLast(position == totalItems - 1); + break; + } } } @@ -2261,32 +2353,61 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific stickersPerRow = width / AndroidUtilities.dp(72); stickersLayoutManager.setSpanCount(stickersPerRow); rowStartPack.clear(); - packStartRow.clear(); + packStartPosition.clear(); + positionToRow.clear(); cache.clear(); totalItems = 0; ArrayList packs = stickerSets; - for (int a = -1; a < packs.size(); a++) { + int startRow = 0; + for (int a = -2; a < packs.size(); a++) { ArrayList documents; TLRPC.TL_messages_stickerSet pack = null; - int startRow = totalItems / stickersPerRow; - if (a == -1) { + if (a == -2) { + documents = favouriteStickers; + packStartPosition.put("fav", totalItems); + } else if (a == -1) { documents = recentStickers; + packStartPosition.put("recent", totalItems); } else { pack = packs.get(a); documents = pack.documents; - packStartRow.put(pack, startRow); + packStartPosition.put(pack, totalItems); + } + if (a == groupStickerPackNum) { + groupStickerPackPosition = totalItems; + if (documents.isEmpty()) { + rowStartPack.put(startRow, pack); + positionToRow.put(totalItems, startRow++); + rowStartPack.put(startRow, pack); + positionToRow.put(totalItems + 1, startRow++); + cache.put(totalItems++, pack); + cache.put(totalItems++, "group"); + continue; + } } if (documents.isEmpty()) { continue; } int count = (int) Math.ceil(documents.size() / (float) stickersPerRow); + if (pack != null) { + cache.put(totalItems, pack); + } else { + cache.put(totalItems, documents); + } + positionToRow.put(totalItems, startRow); for (int b = 0; b < documents.size(); b++) { - cache.put(b + totalItems, documents.get(b)); + cache.put(1 + b + totalItems, documents.get(b)); + positionToRow.put(1 + b + totalItems, startRow + 1 + b / stickersPerRow); } - totalItems += count * stickersPerRow; - for (int b = 0; b < count; b++) { - rowStartPack.put(startRow + b, pack); + for (int b = 0; b < count + 1; b++) { + if (pack != null) { + rowStartPack.put(startRow + b, pack); + } else { + rowStartPack.put(startRow + b, a == -1 ? "recent" : "fav"); + } } + totalItems += count * stickersPerRow + 1; + startRow += count + 1; } super.notifyDataSetChanged(); } @@ -2308,7 +2429,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific @Override public int getCount() { if (emojiPage == -1) { - return recentEmoji.size(); + return Emoji.recentEmoji.size(); } return EmojiData.dataColored[emojiPage].length; } @@ -2327,10 +2448,10 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific String code; String coloredCode; if (emojiPage == -1) { - coloredCode = code = recentEmoji.get(position); + coloredCode = code = Emoji.recentEmoji.get(position); } else { coloredCode = code = EmojiData.dataColored[emojiPage][position]; - String color = emojiColor.get(code); + String color = Emoji.emojiColor.get(code); if (color != null) { coloredCode = addColorToCode(coloredCode, color); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/FragmentContextView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/FragmentContextView.java index f03937476..38853af32 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/FragmentContextView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/FragmentContextView.java @@ -13,6 +13,7 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.graphics.Canvas; import android.graphics.PorterDuff; @@ -32,17 +33,28 @@ import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; +import org.telegram.messenger.LocationController; import org.telegram.messenger.MediaController; import org.telegram.messenger.MessageObject; +import org.telegram.messenger.MessagesController; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; +import org.telegram.messenger.SendMessagesHelper; +import org.telegram.messenger.UserConfig; +import org.telegram.messenger.UserObject; import org.telegram.messenger.voip.VoIPService; +import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.Theme; -import org.telegram.ui.AudioPlayerActivity; import org.telegram.ui.ChatActivity; +import org.telegram.ui.DialogsActivity; +import org.telegram.ui.LocationActivity; import org.telegram.ui.VoIPActivity; +import java.util.ArrayList; + public class FragmentContextView extends FrameLayout implements NotificationCenter.NotificationCenterDelegate { private ImageView playButton; @@ -56,12 +68,29 @@ public class FragmentContextView extends FrameLayout implements NotificationCent private FrameLayout frameLayout; private ImageView closeButton; private int currentStyle = -1; + private String lastString; - public FragmentContextView(Context context, BaseFragment parentFragment) { + private FragmentContextView additionalContextView; + + private boolean isLocation; + + private boolean firstLocationsLoaded; + private boolean loadingSharingCount; + private int lastLocationSharingCount = -1; + private Runnable checkLocationRunnable = new Runnable() { + @Override + public void run() { + checkLocationString(); + AndroidUtilities.runOnUIThread(checkLocationRunnable, 1000); + } + }; + + public FragmentContextView(Context context, BaseFragment parentFragment, boolean location) { super(context); fragment = parentFragment; visible = true; + isLocation = location; ((ViewGroup) fragment.getFragmentView()).setClipToPadding(false); setTag(1); @@ -80,10 +109,12 @@ public class FragmentContextView extends FrameLayout implements NotificationCent playButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - if (MediaController.getInstance().isMessagePaused()) { - MediaController.getInstance().playMessage(MediaController.getInstance().getPlayingMessageObject()); - } else { - MediaController.getInstance().pauseMessage(MediaController.getInstance().getPlayingMessageObject()); + if (currentStyle == 0) { + if (MediaController.getInstance().isMessagePaused()) { + MediaController.getInstance().playMessage(MediaController.getInstance().getPlayingMessageObject()); + } else { + MediaController.getInstance().pauseMessage(MediaController.getInstance().getPlayingMessageObject()); + } } } }); @@ -105,7 +136,38 @@ public class FragmentContextView extends FrameLayout implements NotificationCent closeButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - MediaController.getInstance().cleanupPlayer(true, true); + if (currentStyle == 2) { + AlertDialog.Builder builder = new AlertDialog.Builder(fragment.getParentActivity()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + if (fragment instanceof DialogsActivity) { + builder.setMessage(LocaleController.getString("StopLiveLocationAlertAll", R.string.StopLiveLocationAlertAll)); + } else { + ChatActivity activity = (ChatActivity) fragment; + TLRPC.Chat chat = activity.getCurrentChat(); + TLRPC.User user = activity.getCurrentUser(); + if (chat != null) { + builder.setMessage(LocaleController.formatString("StopLiveLocationAlertToGroup", R.string.StopLiveLocationAlertToGroup, chat.title)); + } else if (user != null) { + builder.setMessage(LocaleController.formatString("StopLiveLocationAlertToUser", R.string.StopLiveLocationAlertToUser, UserObject.getFirstName(user))); + } else { + builder.setMessage(LocaleController.getString("AreYouSure", R.string.AreYouSure)); + } + } + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + if (fragment instanceof DialogsActivity) { + LocationController.getInstance().removeAllLocationSharings(); + } else { + LocationController.getInstance().removeSharingLocation(((ChatActivity) fragment).getDialogId()); + } + } + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + builder.show(); + } else { + MediaController.getInstance().cleanupPlayer(true, true); + } } }); @@ -116,7 +178,7 @@ public class FragmentContextView extends FrameLayout implements NotificationCent MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject(); if (fragment != null && messageObject != null) { if (messageObject.isMusic()) { - fragment.presentFragment(new AudioPlayerActivity()); + fragment.showDialog(new AudioPlayerAlert(getContext())); } else { long dialog_id = 0; if (fragment instanceof ChatActivity) { @@ -151,21 +213,88 @@ public class FragmentContextView extends FrameLayout implements NotificationCent Intent intent = new Intent(getContext(), VoIPActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); getContext().startActivity(intent); + } else if (currentStyle == 2) { + long did; + if (fragment instanceof ChatActivity) { + did = ((ChatActivity) fragment).getDialogId(); + } else if (LocationController.getInstance().sharingLocationsUI.size() == 1) { + did = LocationController.getInstance().sharingLocationsUI.get(0).did; + } else { + did = 0; + } + if (did != 0) { + openSharingLocation(LocationController.getInstance().getSharingLocationInfo(did)); + } else { + fragment.showDialog(new SharingLocationsAlert(getContext(), new SharingLocationsAlert.SharingLocationsAlertDelegate() { + @Override + public void didSelectLocation(LocationController.SharingLocationInfo info) { + openSharingLocation(info); + } + })); + } } } }); } + public void setAdditionalContextView(FragmentContextView contextView) { + additionalContextView = contextView; + } + + private void openSharingLocation(LocationController.SharingLocationInfo info) { + if (info == null) { + return; + } + LocationActivity locationActivity = new LocationActivity(2); + locationActivity.setMessageObject(info.messageObject); + final long dialog_id = info.messageObject.getDialogId(); + locationActivity.setDelegate(new LocationActivity.LocationActivityDelegate() { + @Override + public void didSelectLocation(TLRPC.MessageMedia location, int live) { + SendMessagesHelper.getInstance().sendMessage(location, dialog_id, null, null, null); + } + }); + fragment.presentFragment(locationActivity); + } + public float getTopPadding() { return topPadding; } + private void checkVisibility() { + boolean show = false; + if (isLocation) { + if (fragment instanceof DialogsActivity) { + show = !LocationController.getInstance().sharingLocationsUI.isEmpty(); + } else { + show = LocationController.getInstance().isSharingLocation(((ChatActivity) fragment).getDialogId()); + } + } else { + if (VoIPService.getSharedInstance() != null && VoIPService.getSharedInstance().getCallState() != VoIPService.STATE_WAITING_INCOMING) { + show = true; + } else { + MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject(); + if (messageObject != null && messageObject.getId() != 0) { + show = true; + } + } + } + setVisibility(show ? VISIBLE : GONE); + } + public void setTopPadding(float value) { topPadding = value; if (fragment != null) { View view = fragment.getFragmentView(); + int additionalPadding = 0; + if (additionalContextView != null && additionalContextView.getVisibility() == VISIBLE) { + additionalPadding = AndroidUtilities.dp(36); + } if (view != null) { - view.setPadding(0, (int) topPadding, 0, 0); + view.setPadding(0, (int) topPadding + additionalPadding, 0, 0); + } + if (isLocation && additionalContextView != null) { + ((LayoutParams) additionalContextView.getLayoutParams()).topMargin = -AndroidUtilities.dp(36) - (int) topPadding; } } } @@ -175,7 +304,7 @@ public class FragmentContextView extends FrameLayout implements NotificationCent return; } currentStyle = style; - if (style == 0) { + if (style == 0 || style == 2) { frameLayout.setBackgroundColor(Theme.getColor(Theme.key_inappPlayerBackground)); titleTextView.setTextColor(Theme.getColor(Theme.key_inappPlayerTitle)); closeButton.setVisibility(VISIBLE); @@ -183,6 +312,13 @@ public class FragmentContextView extends FrameLayout implements NotificationCent titleTextView.setTypeface(Typeface.DEFAULT); titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); titleTextView.setLayoutParams(LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 36, Gravity.LEFT | Gravity.TOP, 35, 0, 36, 0)); + if (style == 0) { + playButton.setLayoutParams(LayoutHelper.createFrame(36, 36, Gravity.TOP | Gravity.LEFT, 0, 0, 0, 0)); + titleTextView.setLayoutParams(LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 36, Gravity.LEFT | Gravity.TOP, 35, 0, 36, 0)); + } else if (style == 2) { + playButton.setLayoutParams(LayoutHelper.createFrame(36, 36, Gravity.TOP | Gravity.LEFT, 8, 0, 0, 0)); + titleTextView.setLayoutParams(LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 36, Gravity.LEFT | Gravity.TOP, 35 + 16, 0, 36, 0)); + } } else if (style == 1) { titleTextView.setText(LocaleController.getString("ReturnToCall", R.string.ReturnToCall)); frameLayout.setBackgroundColor(Theme.getColor(Theme.key_returnToCallBackground)); @@ -199,26 +335,42 @@ public class FragmentContextView extends FrameLayout implements NotificationCent protected void onDetachedFromWindow() { super.onDetachedFromWindow(); topPadding = 0; - NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagePlayingDidReset); - NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagePlayingPlayStateChanged); - NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagePlayingDidStarted); - NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didStartedCall); - NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didEndedCall); + if (isLocation) { + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.liveLocationsChanged); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.liveLocationsCacheChanged); + } else { + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagePlayingDidReset); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagePlayingPlayStateChanged); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagePlayingDidStarted); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didStartedCall); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didEndedCall); + } } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingDidReset); - NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingPlayStateChanged); - NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingDidStarted); - NotificationCenter.getInstance().addObserver(this, NotificationCenter.didStartedCall); - NotificationCenter.getInstance().addObserver(this, NotificationCenter.didEndedCall); - boolean callAvailable = VoIPService.getSharedInstance() != null && VoIPService.getSharedInstance().getCallState() != VoIPService.STATE_WAITING_INCOMING; - if (callAvailable) { - checkCall(true); + if (isLocation) { + NotificationCenter.getInstance().addObserver(this, NotificationCenter.liveLocationsChanged); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.liveLocationsCacheChanged); + if (additionalContextView != null) { + additionalContextView.checkVisibility(); + } + checkLiveLocation(true); } else { - checkPlayer(true); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingDidReset); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingPlayStateChanged); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingDidStarted); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.didStartedCall); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.didEndedCall); + if (additionalContextView != null) { + additionalContextView.checkVisibility(); + } + if (VoIPService.getSharedInstance() != null && VoIPService.getSharedInstance().getCallState() != VoIPService.STATE_WAITING_INCOMING) { + checkCall(true); + } else { + checkPlayer(true); + } } } @@ -229,7 +381,16 @@ public class FragmentContextView extends FrameLayout implements NotificationCent @Override public void didReceivedNotification(int id, Object... args) { - if (id == NotificationCenter.messagePlayingDidStarted || id == NotificationCenter.messagePlayingPlayStateChanged || id == NotificationCenter.messagePlayingDidReset || id == NotificationCenter.didEndedCall) { + if (id == NotificationCenter.liveLocationsChanged) { + checkLiveLocation(false); + } else if (id == NotificationCenter.liveLocationsCacheChanged) { + if (fragment instanceof ChatActivity) { + long did = (Long) args[0]; + if (((ChatActivity) fragment).getDialogId() == did) { + checkLocationString(); + } + } + } else if (id == NotificationCenter.messagePlayingDidStarted || id == NotificationCenter.messagePlayingPlayStateChanged || id == NotificationCenter.messagePlayingDidReset || id == NotificationCenter.didEndedCall) { checkPlayer(false); } else if (id == NotificationCenter.didStartedCall) { checkCall(false); @@ -238,6 +399,190 @@ public class FragmentContextView extends FrameLayout implements NotificationCent } } + private void checkLiveLocation(boolean create) { + View fragmentView = fragment.getFragmentView(); + if (!create && fragmentView != null) { + if (fragmentView.getParent() == null || ((View) fragmentView.getParent()).getVisibility() != VISIBLE) { + create = true; + } + } + boolean show; + if (fragment instanceof DialogsActivity) { + show = !LocationController.getInstance().sharingLocationsUI.isEmpty(); + } else { + show = LocationController.getInstance().isSharingLocation(((ChatActivity) fragment).getDialogId()); + } + if (!show) { + lastLocationSharingCount = -1; + AndroidUtilities.cancelRunOnUIThread(checkLocationRunnable); + if (visible) { + visible = false; + if (create) { + if (getVisibility() != GONE) { + setVisibility(GONE); + } + setTopPadding(0); + } else { + if (animatorSet != null) { + animatorSet.cancel(); + animatorSet = null; + } + animatorSet = new AnimatorSet(); + animatorSet.playTogether(ObjectAnimator.ofFloat(this, "translationY", -AndroidUtilities.dp2(36)), + ObjectAnimator.ofFloat(this, "topPadding", 0)); + animatorSet.setDuration(200); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animatorSet != null && animatorSet.equals(animation)) { + setVisibility(GONE); + animatorSet = null; + } + } + }); + animatorSet.start(); + } + } + } else { + updateStyle(2); + playButton.setImageDrawable(new ShareLocationDrawable(getContext(), true)); + if (create && topPadding == 0) { + setTopPadding(AndroidUtilities.dp2(36)); + setTranslationY(0); + yPosition = 0; + } + if (!visible) { + if (!create) { + if (animatorSet != null) { + animatorSet.cancel(); + animatorSet = null; + } + animatorSet = new AnimatorSet(); + animatorSet.playTogether(ObjectAnimator.ofFloat(this, "translationY", -AndroidUtilities.dp2(36), 0), + ObjectAnimator.ofFloat(this, "topPadding", AndroidUtilities.dp2(36))); + animatorSet.setDuration(200); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animatorSet != null && animatorSet.equals(animation)) { + animatorSet = null; + } + } + }); + animatorSet.start(); + } + visible = true; + setVisibility(VISIBLE); + } + + if (fragment instanceof DialogsActivity) { + String liveLocation = LocaleController.getString("AttachLiveLocation", R.string.AttachLiveLocation); + String param; + ArrayList infos = LocationController.getInstance().sharingLocationsUI; + if (infos.size() == 1) { + LocationController.SharingLocationInfo info = infos.get(0); + int lower_id = (int) info.messageObject.getDialogId(); + if (lower_id > 0) { + TLRPC.User user = MessagesController.getInstance().getUser(lower_id); + param = UserObject.getFirstName(user); + } else { + TLRPC.Chat chat = MessagesController.getInstance().getChat(-lower_id); + if (chat != null) { + param = chat.title; + } else { + param = ""; + } + } + } else { + param = LocaleController.formatPluralString("Chats", LocationController.getInstance().sharingLocationsUI.size()); + } + String fullString = String.format(LocaleController.getString("AttachLiveLocationIsSharing", R.string.AttachLiveLocationIsSharing), liveLocation, param); + int start = fullString.indexOf(liveLocation); + SpannableStringBuilder stringBuilder = new SpannableStringBuilder(fullString); + titleTextView.setEllipsize(TextUtils.TruncateAt.END); + TypefaceSpan span = new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf"), 0, Theme.getColor(Theme.key_inappPlayerPerformer)); + stringBuilder.setSpan(span, start, start + liveLocation.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + titleTextView.setText(stringBuilder); + } else { + checkLocationRunnable.run(); + checkLocationString(); + } + } + } + + private void checkLocationString() { + if (!(fragment instanceof ChatActivity) || titleTextView == null) { + return; + } + ChatActivity chatActivity = (ChatActivity) fragment; + long dialogId = chatActivity.getDialogId(); + ArrayList messages = LocationController.getInstance().locationsCache.get(dialogId); + if (!firstLocationsLoaded) { + LocationController.getInstance().loadLiveLocations(dialogId); + firstLocationsLoaded = true; + } + + int locationSharingCount = 0; + TLRPC.User notYouUser = null; + if (messages != null) { + int currentUserId = UserConfig.getClientUserId(); + int date = ConnectionsManager.getInstance().getCurrentTime(); + for (int a = 0; a < messages.size(); a++) { + TLRPC.Message message = messages.get(a); + if (message.media == null) { + continue; + } + if (message.date + message.media.period > date) { + if (notYouUser == null && message.from_id != currentUserId) { + notYouUser = MessagesController.getInstance().getUser(message.from_id); + } + locationSharingCount++; + } + } + } + if (lastLocationSharingCount == locationSharingCount) { + return; + } + lastLocationSharingCount = locationSharingCount; + + String liveLocation = LocaleController.getString("AttachLiveLocation", R.string.AttachLiveLocation); + String fullString; + if (locationSharingCount == 0) { + fullString = liveLocation; + } else { + int otherSharingCount = locationSharingCount - 1; + if (LocationController.getInstance().isSharingLocation(dialogId)) { + if (otherSharingCount != 0) { + if (otherSharingCount == 1 && notYouUser != null) { + fullString = String.format("%1$s - %2$s", liveLocation, LocaleController.formatString("SharingYouAndOtherName", R.string.SharingYouAndOtherName, UserObject.getFirstName(notYouUser))); + } else { + fullString = String.format("%1$s - %2$s %3$s", liveLocation, LocaleController.getString("ChatYourSelfName", R.string.ChatYourSelfName), LocaleController.formatPluralString("AndOther", otherSharingCount)); + } + } else { + fullString = String.format("%1$s - %2$s", liveLocation, LocaleController.getString("ChatYourSelfName", R.string.ChatYourSelfName)); + } + } else { + if (otherSharingCount != 0) { + fullString = String.format("%1$s - %2$s %3$s", liveLocation, UserObject.getFirstName(notYouUser), LocaleController.formatPluralString("AndOther", otherSharingCount)); + } else { + fullString = String.format("%1$s - %2$s", liveLocation, UserObject.getFirstName(notYouUser)); + } + } + } + if (lastString != null && fullString.equals(lastString)) { + return; + } + lastString = fullString; + int start = fullString.indexOf(liveLocation); + SpannableStringBuilder stringBuilder = new SpannableStringBuilder(fullString); + titleTextView.setEllipsize(TextUtils.TruncateAt.END); + if (start >= 0) { + TypefaceSpan span = new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf"), 0, Theme.getColor(Theme.key_inappPlayerPerformer)); + stringBuilder.setSpan(span, start, start + liveLocation.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } + titleTextView.setText(stringBuilder); + } + private void checkPlayer(boolean create) { MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject(); View fragmentView = fragment.getFragmentView(); @@ -281,6 +626,11 @@ public class FragmentContextView extends FrameLayout implements NotificationCent updateStyle(0); if (create && topPadding == 0) { setTopPadding(AndroidUtilities.dp2(36)); + if (additionalContextView != null && additionalContextView.getVisibility() == VISIBLE) { + ((LayoutParams) getLayoutParams()).topMargin = -AndroidUtilities.dp(72); + } else { + ((LayoutParams) getLayoutParams()).topMargin = -AndroidUtilities.dp(36); + } setTranslationY(0); yPosition = 0; } @@ -291,6 +641,11 @@ public class FragmentContextView extends FrameLayout implements NotificationCent animatorSet = null; } animatorSet = new AnimatorSet(); + if (additionalContextView != null && additionalContextView.getVisibility() == VISIBLE) { + ((LayoutParams) getLayoutParams()).topMargin = -AndroidUtilities.dp(72); + } else { + ((LayoutParams) getLayoutParams()).topMargin = -AndroidUtilities.dp(36); + } animatorSet.playTogether(ObjectAnimator.ofFloat(this, "translationY", -AndroidUtilities.dp2(36), 0), ObjectAnimator.ofFloat(this, "topPadding", AndroidUtilities.dp2(36))); animatorSet.setDuration(200); @@ -370,6 +725,11 @@ public class FragmentContextView extends FrameLayout implements NotificationCent updateStyle(1); if (create && topPadding == 0) { setTopPadding(AndroidUtilities.dp2(36)); + if (additionalContextView != null && additionalContextView.getVisibility() == VISIBLE) { + ((LayoutParams) getLayoutParams()).topMargin = -AndroidUtilities.dp(72); + } else { + ((LayoutParams) getLayoutParams()).topMargin = -AndroidUtilities.dp(36); + } setTranslationY(0); yPosition = 0; } @@ -380,6 +740,11 @@ public class FragmentContextView extends FrameLayout implements NotificationCent animatorSet = null; } animatorSet = new AnimatorSet(); + if (additionalContextView != null && additionalContextView.getVisibility() == VISIBLE) { + ((LayoutParams) getLayoutParams()).topMargin = -AndroidUtilities.dp(72); + } else { + ((LayoutParams) getLayoutParams()).topMargin = -AndroidUtilities.dp(36); + } animatorSet.playTogether(ObjectAnimator.ofFloat(this, "translationY", -AndroidUtilities.dp2(36), 0), ObjectAnimator.ofFloat(this, "topPadding", AndroidUtilities.dp2(36))); animatorSet.setDuration(200); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/GroupCreateDividerItemDecoration.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/GroupCreateDividerItemDecoration.java index c203ea9fa..f389b3493 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/GroupCreateDividerItemDecoration.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/GroupCreateDividerItemDecoration.java @@ -19,22 +19,24 @@ import org.telegram.ui.ActionBar.Theme; public class GroupCreateDividerItemDecoration extends RecyclerView.ItemDecoration { private boolean searching; + private boolean single; public void setSearching(boolean value) { searching = value; } + public void setSingle(boolean value) { + single = value; + } + @Override public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) { int width = parent.getWidth(); int top; - int childCount = parent.getChildCount(); - for (int i = 0; i < childCount - 1; i++) { + int childCount = parent.getChildCount() - (single ? 0 : 1); + for (int i = 0; i < childCount; i++) { View child = parent.getChildAt(i); int position = parent.getChildAdapterPosition(child); - if (position == 0) { - continue; - } top = child.getBottom(); canvas.drawLine(LocaleController.isRTL ? 0 : AndroidUtilities.dp(72), top, width - (LocaleController.isRTL ? AndroidUtilities.dp(72) : 0), top, Theme.dividerPaint); } @@ -43,10 +45,10 @@ public class GroupCreateDividerItemDecoration extends RecyclerView.ItemDecoratio @Override public void getItemOffsets(android.graphics.Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); - int position = parent.getChildAdapterPosition(view); + /*int position = parent.getChildAdapterPosition(view); if (position == 0 || !searching && position == 1) { return; - } + }*/ outRect.top = 1; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/GroupCreateSpan.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/GroupCreateSpan.java index c6a390cc2..6be57bdea 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/GroupCreateSpan.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/GroupCreateSpan.java @@ -23,6 +23,7 @@ import android.text.TextUtils; import android.view.View; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ContactsController; import org.telegram.messenger.ImageReceiver; import org.telegram.messenger.R; import org.telegram.messenger.UserObject; @@ -32,6 +33,7 @@ import org.telegram.ui.ActionBar.Theme; public class GroupCreateSpan extends View { private int uid; + private String key; private static TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); private static Paint backPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private Drawable deleteDrawable; @@ -39,6 +41,7 @@ public class GroupCreateSpan extends View { private ImageReceiver imageReceiver; private StaticLayout nameLayout; private AvatarDrawable avatarDrawable; + private ContactsController.Contact currentContact; private int textWidth; private float textX; private float progress; @@ -47,29 +50,53 @@ public class GroupCreateSpan extends View { private int[] colors = new int[6]; public GroupCreateSpan(Context context, TLRPC.User user) { + this(context, user, null); + } + + public GroupCreateSpan(Context context, ContactsController.Contact contact) { + this(context, null, contact); + } + + public GroupCreateSpan(Context context, TLRPC.User user, ContactsController.Contact contact) { super(context); + currentContact = contact; deleteDrawable = getResources().getDrawable(R.drawable.delete); textPaint.setTextSize(AndroidUtilities.dp(14)); avatarDrawable = new AvatarDrawable(); avatarDrawable.setTextSize(AndroidUtilities.dp(12)); - avatarDrawable.setInfo(user); + if (user != null) { + avatarDrawable.setInfo(user); + uid = user.id; + } else { + avatarDrawable.setInfo(0, contact.first_name, contact.last_name, false); + uid = contact.contact_id; + key = contact.key; + } imageReceiver = new ImageReceiver(); imageReceiver.setRoundRadius(AndroidUtilities.dp(16)); imageReceiver.setParentView(this); imageReceiver.setImageCoords(0, 0, AndroidUtilities.dp(32), AndroidUtilities.dp(32)); - uid = user.id; - int maxNameWidth; if (AndroidUtilities.isTablet()) { maxNameWidth = AndroidUtilities.dp(530 - 32 - 18 - 57 * 2) / 2; } else { maxNameWidth = (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(32 + 18 + 57 * 2)) / 2; } - CharSequence name = TextUtils.ellipsize(UserObject.getFirstName(user).replace('\n', ' '), textPaint, maxNameWidth, TextUtils.TruncateAt.END); + String firstName; + if (user != null) { + firstName = UserObject.getFirstName(user); + } else { + if (!TextUtils.isEmpty(contact.first_name)) { + firstName = contact.first_name; + } else { + firstName = contact.last_name; + } + } + CharSequence name = TextUtils.ellipsize(firstName.replace('\n', ' '), textPaint, maxNameWidth, TextUtils.TruncateAt.END); nameLayout = new StaticLayout(name, textPaint, 1000, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); if (nameLayout.getLineCount() > 0) { textWidth = (int) Math.ceil(nameLayout.getLineWidth(0)); @@ -77,7 +104,7 @@ public class GroupCreateSpan extends View { } TLRPC.FileLocation photo = null; - if (user.photo != null) { + if (user != null && user.photo != null) { photo = user.photo.photo_small; } imageReceiver.setImage(photo, null, "50_50", avatarDrawable, null, null, 0, null, 1); @@ -126,6 +153,14 @@ public class GroupCreateSpan extends View { return uid; } + public String getKey() { + return key; + } + + public ContactsController.Contact getContact() { + return currentContact; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(AndroidUtilities.dp(32 + 25) + textWidth, AndroidUtilities.dp(32)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/HintEditText.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/HintEditText.java index efdcc42db..a533611e7 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/HintEditText.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/HintEditText.java @@ -12,12 +12,11 @@ import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; -import android.widget.EditText; import org.telegram.messenger.AndroidUtilities; import org.telegram.ui.ActionBar.Theme; -public class HintEditText extends EditText { +public class HintEditText extends EditTextBoldCursor { private String hintText; private float textOffset; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/InstantCameraView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/InstantCameraView.java index 498acbada..af1244121 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/InstantCameraView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/InstantCameraView.java @@ -145,7 +145,7 @@ public class InstantCameraView extends FrameLayout implements NotificationCenter private CameraGLThread cameraThread; private Size previewSize; private Size pictureSize; - private Size aspectRatio = new Size(16, 9); + private Size aspectRatio = MediaController.getInstance().canRoundCamera16to9() ? new Size(16, 9) : new Size(4, 3); private TextureView textureView; private CameraSession cameraSession; @@ -1110,7 +1110,7 @@ public class InstantCameraView extends FrameLayout implements NotificationCenter } } - videoEncoder.frameAvailable(cameraSurface, cameraId); + videoEncoder.frameAvailable(cameraSurface, cameraId, System.nanoTime()); cameraSurface.getTransformMatrix(mSTMatrix); @@ -1347,6 +1347,7 @@ public class InstantCameraView extends FrameLayout implements NotificationCenter private int scaleXHandle; private int scaleYHandle; private int alphaHandle; + private int zeroTimeStamps; private Integer lastCameraId = 0; @@ -1447,7 +1448,7 @@ public class InstantCameraView extends FrameLayout implements NotificationCenter handler.sendMessage(handler.obtainMessage(MSG_STOP_RECORDING, send, 0)); } - public void frameAvailable(SurfaceTexture st, Integer cameraId) { + public void frameAvailable(SurfaceTexture st, Integer cameraId, long timestampInternal) { synchronized (sync) { if (!ready) { return; @@ -1456,7 +1457,14 @@ public class InstantCameraView extends FrameLayout implements NotificationCenter long timestamp = st.getTimestamp(); if (timestamp == 0) { - return; + zeroTimeStamps++; + if (zeroTimeStamps > 1) { + timestamp = timestampInternal; + } else { + return; + } + } else { + zeroTimeStamps = 0; } handler.sendMessage(handler.obtainMessage(MSG_VIDEOFRAME_AVAILABLE, (int) (timestamp >> 32), (int) timestamp, cameraId)); @@ -1930,7 +1938,10 @@ public class InstantCameraView extends FrameLayout implements NotificationCenter videoEncoder.signalEndOfInputStream(); } - ByteBuffer[] encoderOutputBuffers = videoEncoder.getOutputBuffers(); + ByteBuffer[] encoderOutputBuffers = null; + if (Build.VERSION.SDK_INT < 21) { + encoderOutputBuffers = videoEncoder.getOutputBuffers(); + } while (true) { int encoderStatus = videoEncoder.dequeueOutputBuffer(videoBufferInfo, 10000); if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { @@ -1997,7 +2008,9 @@ public class InstantCameraView extends FrameLayout implements NotificationCenter } } - encoderOutputBuffers = audioEncoder.getOutputBuffers(); + if (Build.VERSION.SDK_INT < 21) { + encoderOutputBuffers = audioEncoder.getOutputBuffers(); + } boolean encoderOutputAvailable = true; while (true) { int encoderStatus = audioEncoder.dequeueOutputBuffer(audioBufferInfo, 0); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PasscodeView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PasscodeView.java index 6fc98e530..d71236aea 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PasscodeView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PasscodeView.java @@ -40,7 +40,6 @@ import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.inputmethod.EditorInfo; -import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.RelativeLayout; @@ -403,7 +402,7 @@ public class PasscodeView extends FrameLayout { private ArrayList numberFrameLayouts; private FrameLayout passwordFrameLayout; private ImageView eraseView; - private EditText passwordEditText; + private EditTextBoldCursor passwordEditText; private AnimatingTextView passwordEditText2; private FrameLayout backgroundFrameLayout; private TextView passcodeTextView; @@ -483,7 +482,7 @@ public class PasscodeView extends FrameLayout { layoutParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; passwordEditText2.setLayoutParams(layoutParams); - passwordEditText = new EditText(context); + passwordEditText = new EditTextBoldCursor(context); passwordEditText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 36); passwordEditText.setTextColor(0xffffffff); passwordEditText.setMaxLines(1); @@ -493,14 +492,14 @@ public class PasscodeView extends FrameLayout { passwordEditText.setImeOptions(EditorInfo.IME_ACTION_DONE); passwordEditText.setTypeface(Typeface.DEFAULT); passwordEditText.setBackgroundDrawable(null); - AndroidUtilities.clearCursorDrawable(passwordEditText); + passwordEditText.setCursorColor(0xffffffff); + passwordEditText.setCursorSize(AndroidUtilities.dp(32)); passwordFrameLayout.addView(passwordEditText); layoutParams = (FrameLayout.LayoutParams) passwordEditText.getLayoutParams(); layoutParams.height = LayoutHelper.WRAP_CONTENT; layoutParams.width = LayoutHelper.MATCH_PARENT; layoutParams.leftMargin = AndroidUtilities.dp(70); layoutParams.rightMargin = AndroidUtilities.dp(70); - layoutParams.bottomMargin = AndroidUtilities.dp(6); layoutParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; passwordEditText.setLayoutParams(layoutParams); passwordEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { @@ -855,9 +854,9 @@ public class PasscodeView extends FrameLayout { relativeLayout.setPadding(AndroidUtilities.dp(24), 0, AndroidUtilities.dp(24), 0); TextView fingerprintTextView = new TextView(getContext()); - fingerprintTextView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); fingerprintTextView.setId(id_fingerprint_textview); fingerprintTextView.setTextAppearance(android.R.style.TextAppearance_Material_Subhead); + fingerprintTextView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); fingerprintTextView.setText(LocaleController.getString("FingerprintInfo", R.string.FingerprintInfo)); relativeLayout.addView(fingerprintTextView); RelativeLayout.LayoutParams layoutParams = LayoutHelper.createRelative(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT); @@ -970,16 +969,21 @@ public class PasscodeView extends FrameLayout { } setAlpha(1.0f); setTranslationY(0); - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); - int selectedBackground = preferences.getInt("selectedBackground", 1000001); - if (selectedBackground == 1000001) { - backgroundFrameLayout.setBackgroundColor(0xff517c9e); - } else { + if (Theme.isCustomTheme()) { backgroundDrawable = Theme.getCachedWallpaper(); - if (backgroundDrawable != null) { - backgroundFrameLayout.setBackgroundColor(0xbf000000); - } else { + backgroundFrameLayout.setBackgroundColor(0xbf000000); + } else { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + int selectedBackground = preferences.getInt("selectedBackground", 1000001); + if (selectedBackground == 1000001) { backgroundFrameLayout.setBackgroundColor(0xff517c9e); + } else { + backgroundDrawable = Theme.getCachedWallpaper(); + if (backgroundDrawable != null) { + backgroundFrameLayout.setBackgroundColor(0xbf000000); + } else { + backgroundFrameLayout.setBackgroundColor(0xff517c9e); + } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoFilterView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoFilterView.java index 12338a7a7..12d53e4db 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoFilterView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoFilterView.java @@ -33,6 +33,7 @@ import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MediaController; import org.telegram.messenger.support.widget.LinearLayoutManager; import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.messenger.DispatchQueue; @@ -60,6 +61,9 @@ import javax.microedition.khronos.opengles.GL10; @SuppressLint("NewApi") public class PhotoFilterView extends FrameLayout { + private final static int curveGranularity = 100; + private final static int curveDataStep = 2; + private boolean showOriginal; private int enhanceTool = 0; @@ -76,29 +80,27 @@ public class PhotoFilterView extends FrameLayout { private int tintShadowsTool = 11; private int tintHighlightsTool = 12; - private float enhanceValue = 0; //0 100 - private float exposureValue = 0; //-100 100 - private float contrastValue = 0; //-100 100 - private float warmthValue = 0; //-100 100 - private float saturationValue = 0; //-100 100 - private float fadeValue = 0; // 0 100 - private int tintShadowsColor = 0; //0 0xffffffff - private int tintHighlightsColor = 0; //0 0xffffffff - private float highlightsValue = 0; //-100 100 - private float shadowsValue = 0; //-100 100 - private float vignetteValue = 0; //0 100 - private float grainValue = 0; //0 100 - private int blurType = 0; //0 none, 1 radial, 2 linear - private float sharpenValue = 0; //0 100 - private CurvesToolValue curvesToolValue = new CurvesToolValue(); + private float enhanceValue; //0 100 + private float exposureValue; //-100 100 + private float contrastValue; //-100 100 + private float warmthValue; //-100 100 + private float saturationValue; //-100 100 + private float fadeValue; // 0 100 + private int tintShadowsColor; //0 0xffffffff + private int tintHighlightsColor; //0 0xffffffff + private float highlightsValue; //-100 100 + private float shadowsValue; //-100 100 + private float vignetteValue; //0 100 + private float grainValue; //0 100 + private int blurType; //0 none, 1 radial, 2 linear + private float sharpenValue; //0 100 + private CurvesToolValue curvesToolValue; + private float blurExcludeSize; + private Point blurExcludePoint; + private float blurExcludeBlurSize; + private float blurAngle; - private final static int curveGranularity = 100; - private final static int curveDataStep = 2; - - private float blurExcludeSize = 0.35f; - private Point blurExcludePoint = new Point(0.5f, 0.5f); - private float blurExcludeBlurSize = 0.15f; - private float blurAngle = (float) Math.PI / 2.0f; + private MediaController.SavedFilterState lastState; private FrameLayout toolsView; private TextView doneTextView; @@ -1625,9 +1627,37 @@ public class PhotoFilterView extends FrameLayout { } } - public PhotoFilterView(Context context, Bitmap bitmap, int rotation) { + public PhotoFilterView(Context context, Bitmap bitmap, int rotation, MediaController.SavedFilterState state) { super(context); + if (state != null) { + enhanceValue = state.enhanceValue; + exposureValue = state.exposureValue; + contrastValue = state.contrastValue; + warmthValue = state.warmthValue; + saturationValue = state.saturationValue; + fadeValue = state.fadeValue; + tintShadowsColor = state.tintShadowsColor; + tintHighlightsColor = state.tintHighlightsColor; + highlightsValue = state.highlightsValue; + shadowsValue = state.shadowsValue; + vignetteValue = state.vignetteValue; + grainValue = state.grainValue; + blurType = state.blurType; + sharpenValue = state.sharpenValue; + curvesToolValue = state.curvesToolValue; + blurExcludeSize = state.blurExcludeSize; + blurExcludePoint = state.blurExcludePoint; + blurExcludeBlurSize = state.blurExcludeBlurSize; + blurAngle = state.blurAngle; + lastState = state; + } else { + curvesToolValue = new CurvesToolValue(); + blurExcludeSize = 0.35f; + blurExcludePoint = new Point(0.5f, 0.5f); + blurExcludeBlurSize = 0.15f; + blurAngle = (float) Math.PI / 2.0f; + } bitmapToEdit = bitmap; orientation = rotation; @@ -1952,9 +1982,50 @@ public class PhotoFilterView extends FrameLayout { } } + public MediaController.SavedFilterState getSavedFilterState() { + MediaController.SavedFilterState state = new MediaController.SavedFilterState(); + state.enhanceValue = enhanceValue; + state.exposureValue = exposureValue; + state.contrastValue = contrastValue; + state.warmthValue = warmthValue; + state.saturationValue = saturationValue; + state.fadeValue = fadeValue; + state.tintShadowsColor = tintShadowsColor; + state.tintHighlightsColor = tintHighlightsColor; + state.highlightsValue = highlightsValue; + state.shadowsValue = shadowsValue; + state.vignetteValue = vignetteValue; + state.grainValue = grainValue; + state.blurType = blurType; + state.sharpenValue = sharpenValue; + state.curvesToolValue = curvesToolValue; + state.blurExcludeSize = blurExcludeSize; + state.blurExcludePoint = blurExcludePoint; + state.blurExcludeBlurSize = blurExcludeBlurSize; + state.blurAngle = blurAngle; + return state; + } + public boolean hasChanges() { - return enhanceValue != 0 || contrastValue != 0 || highlightsValue != 0 || exposureValue != 0 || warmthValue != 0 || saturationValue != 0 || vignetteValue != 0 || - shadowsValue != 0 || grainValue != 0 || sharpenValue != 0 || fadeValue != 0 || tintHighlightsColor != 0 || tintShadowsColor != 0 || !curvesToolValue.shouldBeSkipped(); + if (lastState != null) { + return enhanceValue != lastState.enhanceValue || + contrastValue != lastState.contrastValue || + highlightsValue != lastState.highlightsValue || + exposureValue != lastState.exposureValue || + warmthValue != lastState.warmthValue || + saturationValue != lastState.saturationValue || + vignetteValue != lastState.vignetteValue || + shadowsValue != lastState.shadowsValue || + grainValue != lastState.grainValue || + sharpenValue != lastState.sharpenValue || + fadeValue != lastState.fadeValue || + tintHighlightsColor != lastState.tintHighlightsColor || + tintShadowsColor != lastState.tintShadowsColor || + !curvesToolValue.shouldBeSkipped(); + } else { + return enhanceValue != 0 || contrastValue != 0 || highlightsValue != 0 || exposureValue != 0 || warmthValue != 0 || saturationValue != 0 || vignetteValue != 0 || + shadowsValue != 0 || grainValue != 0 || sharpenValue != 0 || fadeValue != 0 || tintHighlightsColor != 0 || tintShadowsColor != 0 || !curvesToolValue.shouldBeSkipped(); + } } public void onTouch(MotionEvent event) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoViewerCaptionEnterView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoViewerCaptionEnterView.java index 842764b63..2b6f60703 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoViewerCaptionEnterView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoViewerCaptionEnterView.java @@ -14,6 +14,7 @@ import android.content.Context; import android.os.Build; import android.text.Editable; import android.text.InputFilter; +import android.text.SpannableStringBuilder; import android.text.TextWatcher; import android.text.style.ImageSpan; import android.util.TypedValue; @@ -25,7 +26,6 @@ import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.inputmethod.EditorInfo; -import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; @@ -51,7 +51,7 @@ public class PhotoViewerCaptionEnterView extends FrameLayout implements Notifica void onWindowSizeChanged(int size); } - private EditText messageEditText; + private EditTextBoldCursor messageEditText; private ImageView emojiButton; private EmojiView emojiView; private SizeNotifierFrameLayoutPhoto sizeNotifierLayout; @@ -114,7 +114,7 @@ public class PhotoViewerCaptionEnterView extends FrameLayout implements Notifica } }); - messageEditText = new EditText(context) { + messageEditText = new EditTextBoldCursor(context) { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { try { @@ -191,7 +191,8 @@ public class PhotoViewerCaptionEnterView extends FrameLayout implements Notifica messageEditText.setGravity(Gravity.BOTTOM); messageEditText.setPadding(0, AndroidUtilities.dp(11), 0, AndroidUtilities.dp(12)); messageEditText.setBackgroundDrawable(null); - AndroidUtilities.clearCursorDrawable(messageEditText); + messageEditText.setCursorColor(0xffffffff); + messageEditText.setCursorSize(AndroidUtilities.dp(20)); messageEditText.setTextColor(0xffffffff); messageEditText.setHintTextColor(0xb2ffffff); InputFilter[] inputFilters = new InputFilter[1]; @@ -374,10 +375,105 @@ public class PhotoViewerCaptionEnterView extends FrameLayout implements Notifica return messageEditText.getSelectionStart(); } - public void replaceWithText(int start, int len, String text) { + private void createEmojiView() { + if (emojiView != null) { + return; + } + emojiView = new EmojiView(false, false, getContext(), null); + emojiView.setListener(new EmojiView.Listener() { + public boolean onBackspace() { + if (messageEditText.length() == 0) { + return false; + } + messageEditText.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)); + return true; + } + + public void onEmojiSelected(String symbol) { + if (messageEditText.length() + symbol.length() > captionMaxLength) { + return; + } + int i = messageEditText.getSelectionEnd(); + if (i < 0) { + i = 0; + } + try { + innerTextChange = true; + CharSequence localCharSequence = Emoji.replaceEmoji(symbol, messageEditText.getPaint().getFontMetricsInt(), AndroidUtilities.dp(20), false); + messageEditText.setText(messageEditText.getText().insert(i, localCharSequence)); + int j = i + localCharSequence.length(); + messageEditText.setSelection(j, j); + } catch (Exception e) { + FileLog.e(e); + } finally { + innerTextChange = false; + } + } + + public void onStickerSelected(TLRPC.Document sticker) { + + } + + @Override + public void onStickersSettingsClick() { + + } + + @Override + public void onGifSelected(TLRPC.Document gif) { + + } + + @Override + public void onGifTab(boolean opened) { + + } + + @Override + public void onStickersTab(boolean opened) { + + } + + @Override + public void onClearEmojiRecent() { + + } + + @Override + public void onShowStickerSet(TLRPC.StickerSet stickerSet, TLRPC.InputStickerSet inputStickerSet) { + + } + + @Override + public void onStickerSetAdd(TLRPC.StickerSetCovered stickerSet) { + + } + + @Override + public void onStickerSetRemove(TLRPC.StickerSetCovered stickerSet) { + + } + + @Override + public void onStickersGroupClick(int chatId) { + + } + }); + sizeNotifierLayout.addView(emojiView); + } + + public void addEmojiToRecent(String code) { + createEmojiView(); + emojiView.addEmojiToRecent(code); + } + + public void replaceWithText(int start, int len, CharSequence text, boolean parseEmoji) { try { - StringBuilder builder = new StringBuilder(messageEditText.getText()); + SpannableStringBuilder builder = new SpannableStringBuilder(messageEditText.getText()); builder.replace(start, start + len, text); + if (parseEmoji) { + Emoji.replaceEmoji(builder, messageEditText.getPaint().getFontMetricsInt(), AndroidUtilities.dp(20), false); + } messageEditText.setText(builder); if (start + text.length() <= messageEditText.length()) { messageEditText.setSelection(start + text.length()); @@ -430,82 +526,7 @@ public class PhotoViewerCaptionEnterView extends FrameLayout implements Notifica private void showPopup(int show) { if (show == 1) { if (emojiView == null) { - emojiView = new EmojiView(false, false, getContext()); - emojiView.setListener(new EmojiView.Listener() { - public boolean onBackspace() { - if (messageEditText.length() == 0) { - return false; - } - messageEditText.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)); - return true; - } - - public void onEmojiSelected(String symbol) { - if (messageEditText.length() + symbol.length() > captionMaxLength) { - return; - } - int i = messageEditText.getSelectionEnd(); - if (i < 0) { - i = 0; - } - try { - innerTextChange = true; - CharSequence localCharSequence = Emoji.replaceEmoji(symbol, messageEditText.getPaint().getFontMetricsInt(), AndroidUtilities.dp(20), false); - messageEditText.setText(messageEditText.getText().insert(i, localCharSequence)); - int j = i + localCharSequence.length(); - messageEditText.setSelection(j, j); - } catch (Exception e) { - FileLog.e(e); - } finally { - innerTextChange = false; - } - } - - public void onStickerSelected(TLRPC.Document sticker) { - - } - - @Override - public void onStickersSettingsClick() { - - } - - @Override - public void onGifSelected(TLRPC.Document gif) { - - } - - @Override - public void onGifTab(boolean opened) { - - } - - @Override - public void onStickersTab(boolean opened) { - - } - - @Override - public void onClearEmojiRecent() { - - } - - @Override - public void onShowStickerSet(TLRPC.StickerSet stickerSet, TLRPC.InputStickerSet inputStickerSet) { - - } - - @Override - public void onStickerSetAdd(TLRPC.StickerSetCovered stickerSet) { - - } - - @Override - public void onStickerSetRemove(TLRPC.StickerSetCovered stickerSet) { - - } - }); - sizeNotifierLayout.addView(emojiView); + createEmojiView(); } emojiView.setVisibility(VISIBLE); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PickerBottomLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PickerBottomLayout.java index 4eb7ae605..89679818c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PickerBottomLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PickerBottomLayout.java @@ -64,7 +64,7 @@ public class PickerBottomLayout extends FrameLayout { doneButtonBadgeTextView.setGravity(Gravity.CENTER); Drawable drawable; if (isDarkTheme) { - drawable = getResources().getDrawable(R.drawable.photobadge); + drawable = Theme.createRoundRectDrawable(AndroidUtilities.dp(11), 0xff66bffa); } else { drawable = Theme.createRoundRectDrawable(AndroidUtilities.dp(11), Theme.getColor(Theme.key_picker_badge)); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PipVideoView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PipVideoView.java index e8cd4cbbd..e5a8a81f1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PipVideoView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PipVideoView.java @@ -16,18 +16,23 @@ import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; import android.graphics.PixelFormat; +import android.os.Build; import android.view.Gravity; import android.view.MotionEvent; import android.view.TextureView; import android.view.View; +import android.view.ViewGroup; import android.view.ViewParent; import android.view.WindowManager; import android.view.animation.DecelerateInterpolator; +import android.webkit.WebView; import android.widget.FrameLayout; +import android.widget.ImageView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.FileLog; +import org.telegram.messenger.R; import org.telegram.messenger.exoplayer2.ui.AspectRatioFrameLayout; import org.telegram.ui.ActionBar.ActionBar; @@ -47,7 +52,113 @@ public class PipVideoView { private SharedPreferences preferences; private DecelerateInterpolator decelerateInterpolator; - public TextureView show(Activity activity, EmbedBottomSheet sheet, View controls, float aspectRatio, int rotation) { + private class MiniControlsView extends FrameLayout { + + private boolean isVisible = true; + private AnimatorSet currentAnimation; + private ImageView inlineButton; + private Runnable hideRunnable = new Runnable() { + @Override + public void run() { + show(false, true); + } + }; + + public MiniControlsView(Context context) { + super(context); + setWillNotDraw(false); + + inlineButton = new ImageView(context); + inlineButton.setScaleType(ImageView.ScaleType.CENTER); + inlineButton.setImageResource(R.drawable.ic_outinline); + addView(inlineButton, LayoutHelper.createFrame(56, 48, Gravity.RIGHT | Gravity.TOP)); + inlineButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (parentSheet == null) { + return; + } + parentSheet.exitFromPip(); + } + }); + } + + public void show(boolean value, boolean animated) { + if (isVisible == value) { + return; + } + isVisible = value; + if (currentAnimation != null) { + currentAnimation.cancel(); + } + if (isVisible) { + if (animated) { + currentAnimation = new AnimatorSet(); + currentAnimation.playTogether(ObjectAnimator.ofFloat(this, "alpha", 1.0f)); + currentAnimation.setDuration(150); + currentAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + currentAnimation = null; + } + }); + currentAnimation.start(); + } else { + setAlpha(1.0f); + } + } else { + if (animated) { + currentAnimation = new AnimatorSet(); + currentAnimation.playTogether(ObjectAnimator.ofFloat(this, "alpha", 0.0f)); + currentAnimation.setDuration(150); + currentAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + currentAnimation = null; + } + }); + currentAnimation.start(); + } else { + setAlpha(0.0f); + } + } + checkNeedHide(); + } + + private void checkNeedHide() { + AndroidUtilities.cancelRunOnUIThread(hideRunnable); + if (isVisible) { + AndroidUtilities.runOnUIThread(hideRunnable, 3000); + } + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + if (!isVisible) { + show(true, true); + return true; + } else { + checkNeedHide(); + } + } + return super.onInterceptTouchEvent(ev); + } + + @Override + public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { + super.requestDisallowInterceptTouchEvent(disallowIntercept); + checkNeedHide(); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + checkNeedHide(); + } + } + + public TextureView show(Activity activity, EmbedBottomSheet sheet, View controls, float aspectRatio, int rotation, WebView webview) { windowView = new FrameLayout(activity) { private float startX; @@ -66,7 +177,9 @@ public class PipVideoView { dragging = true; startX = x; startY = y; - ((ViewParent) controlsView).requestDisallowInterceptTouchEvent(true); + if (controlsView != null) { + ((ViewParent) controlsView).requestDisallowInterceptTouchEvent(true); + } return true; } } @@ -134,10 +247,24 @@ public class PipVideoView { aspectRatioFrameLayout.setAspectRatio(aspectRatio, rotation); windowView.addView(aspectRatioFrameLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.CENTER)); - TextureView textureView = new TextureView(activity); - aspectRatioFrameLayout.addView(textureView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + TextureView textureView; + if (webview != null) { + ViewGroup parent = (ViewGroup) webview.getParent(); + if (parent != null) { + parent.removeView(webview); + } + aspectRatioFrameLayout.addView(webview, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + textureView = null; + } else { + textureView = new TextureView(activity); + aspectRatioFrameLayout.addView(textureView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + } - controlsView = controls; + if (controls == null) { + controlsView = new MiniControlsView(activity); + } else { + controlsView = controls; + } windowView.addView(controlsView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); windowManager = (WindowManager) ApplicationLoader.applicationContext.getSystemService(Context.WINDOW_SERVICE); @@ -157,7 +284,11 @@ public class PipVideoView { windowLayoutParams.y = getSideCoord(false, sidey, py, videoHeight); windowLayoutParams.format = PixelFormat.TRANSLUCENT; windowLayoutParams.gravity = Gravity.TOP | Gravity.LEFT; - windowLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; + if (Build.VERSION.SDK_INT >= 26) { + windowLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + } else { + windowLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; + } windowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; windowManager.addView(windowView, windowLayoutParams); } catch (Exception e) { @@ -283,7 +414,9 @@ public class PipVideoView { animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - parentSheet.destroy(); + if (parentSheet != null) { + parentSheet.destroy(); + } } }); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PopupAudioView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PopupAudioView.java index b163aa117..272aa425c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PopupAudioView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PopupAudioView.java @@ -91,7 +91,6 @@ public class PopupAudioView extends BaseCell implements SeekBar.SeekBarDelegate, @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { if (currentMessageObject == null) { - super.onLayout(changed, left, top, right, bottom); return; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/RadialProgress.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/RadialProgress.java index a29c55d24..6fe9090e0 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/RadialProgress.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/RadialProgress.java @@ -9,13 +9,16 @@ package org.telegram.ui.Components; import android.graphics.Canvas; +import android.graphics.ColorFilter; import android.graphics.Paint; +import android.graphics.PixelFormat; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.view.View; import android.view.animation.DecelerateInterpolator; import org.telegram.messenger.AndroidUtilities; +import org.telegram.ui.ActionBar.Theme; public class RadialProgress { @@ -30,6 +33,9 @@ public class RadialProgress { private View parent; private float animatedAlphaValue = 1.0f; + private boolean drawCheckDrawable; + private boolean previousCheckDrawable; + private boolean currentWithRound; private boolean previousWithRound; private Drawable currentDrawable; @@ -38,11 +44,83 @@ public class RadialProgress { private int progressColor = 0xffffffff; private Paint progressPaint; + private CheckDrawable checkDrawable; + private Drawable checkBackgroundDrawable; + private int diff = AndroidUtilities.dp(4); private static DecelerateInterpolator decelerateInterpolator; private boolean alphaForPrevious = true; + private float overrideAlpha = 1.0f; + + private class CheckDrawable extends Drawable { + + private Paint paint; + private float progress; + + public CheckDrawable() { + paint = new Paint(Paint.ANTI_ALIAS_FLAG); + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeWidth(AndroidUtilities.dp(3)); + paint.setStrokeCap(Paint.Cap.ROUND); + paint.setColor(0xffffffff); + } + + public void resetProgress(boolean animated) { + progress = animated ? 0.0f : 1.0f; + } + + public boolean updateAnimation(long dt) { + if (progress < 1.0f) { + progress += dt / 700.0f; + if (progress > 1.0f) { + progress = 1.0f; + } + return true; + } + return false; + } + + @Override + public void draw(Canvas canvas) { + int x = getBounds().centerX() - AndroidUtilities.dp(12); + int y = getBounds().centerY() - AndroidUtilities.dp(6); + float p = progress != 1.0f ? decelerateInterpolator.getInterpolation(progress) : 1.0f; + int endX = (int) (AndroidUtilities.dp(7.0f) - AndroidUtilities.dp(6) * p); + int endY = (int) (AndroidUtilities.dpf2(13.0f) - AndroidUtilities.dp(6) * p); + canvas.drawLine(x + AndroidUtilities.dp(7.0f), y + (int) AndroidUtilities.dpf2(13.0f), x + endX, y + endY, paint); + endX = (int) (AndroidUtilities.dpf2(7.0f) + AndroidUtilities.dp(13) * p); + endY = (int) (AndroidUtilities.dpf2(13.0f) - AndroidUtilities.dp(13) * p); + canvas.drawLine(x + (int) AndroidUtilities.dpf2(7.0f), y + (int) AndroidUtilities.dpf2(13.0f), x + endX, y + endY, paint); + } + + @Override + public void setAlpha(int alpha) { + paint.setAlpha(alpha); + } + + @Override + public void setColorFilter(ColorFilter cf) { + paint.setColorFilter(cf); + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSPARENT; + } + + @Override + public int getIntrinsicWidth() { + return AndroidUtilities.dp(48); + } + + @Override + public int getIntrinsicHeight() { + return AndroidUtilities.dp(48); + } + } + public RadialProgress(View parentView) { if (decelerateInterpolator == null) { decelerateInterpolator = new DecelerateInterpolator(); @@ -62,6 +140,10 @@ public class RadialProgress { progressRect.set(left, top, right, bottom); } + public RectF getProgressRect() { + return progressRect; + } + public void setAlphaForPrevious(boolean value) { alphaForPrevious = value; } @@ -70,6 +152,11 @@ public class RadialProgress { long newTime = System.currentTimeMillis(); long dt = newTime - lastUpdateTime; lastUpdateTime = newTime; + if (checkBackgroundDrawable != null && (currentDrawable == checkBackgroundDrawable || previousDrawable == checkBackgroundDrawable)) { + if (checkDrawable.updateAnimation(dt)) { + invalidateParent(); + } + } if (progress) { if (animatedProgressValue != 1) { @@ -144,6 +231,19 @@ public class RadialProgress { parent.invalidate((int) progressRect.left - offset, (int) progressRect.top - offset, (int) progressRect.right + offset * 2, (int) progressRect.bottom + offset * 2); } + public void setCheckBackground(boolean withRound, boolean animated) { + if (checkDrawable == null) { + checkDrawable = new CheckDrawable(); + checkBackgroundDrawable = Theme.createCircleDrawableWithIcon(AndroidUtilities.dp(48), checkDrawable, 0); + } + Theme.setCombinedDrawableColor(checkBackgroundDrawable, Theme.getColor(Theme.key_chat_mediaLoaderPhoto), false); + Theme.setCombinedDrawableColor(checkBackgroundDrawable, Theme.getColor(Theme.key_chat_mediaLoaderPhotoIcon), true); + if (currentDrawable != checkBackgroundDrawable) { + setBackground(checkBackgroundDrawable, withRound, animated); + checkDrawable.resetProgress(animated); + } + } + public void setBackground(Drawable drawable, boolean withRound, boolean animated) { lastUpdateTime = System.currentTimeMillis(); if (animated && currentDrawable != drawable) { @@ -176,12 +276,16 @@ public class RadialProgress { return previousDrawable != null || currentDrawable != null ? animatedAlphaValue : 0.0f; } + public void setOverrideAlpha(float alpha) { + overrideAlpha = alpha; + } + public void draw(Canvas canvas) { if (previousDrawable != null) { if (alphaForPrevious) { - previousDrawable.setAlpha((int) (255 * animatedAlphaValue)); + previousDrawable.setAlpha((int) (255 * animatedAlphaValue * overrideAlpha)); } else { - previousDrawable.setAlpha(255); + previousDrawable.setAlpha((int) (255 * overrideAlpha)); } previousDrawable.setBounds((int) progressRect.left, (int) progressRect.top, (int) progressRect.right, (int) progressRect.bottom); previousDrawable.draw(canvas); @@ -189,9 +293,9 @@ public class RadialProgress { if (!hideCurrentDrawable && currentDrawable != null) { if (previousDrawable != null) { - currentDrawable.setAlpha((int) (255 * (1.0f - animatedAlphaValue))); + currentDrawable.setAlpha((int) (255 * (1.0f - animatedAlphaValue) * overrideAlpha)); } else { - currentDrawable.setAlpha(255); + currentDrawable.setAlpha((int) (255 * overrideAlpha)); } currentDrawable.setBounds((int) progressRect.left, (int) progressRect.top, (int) progressRect.right, (int) progressRect.bottom); currentDrawable.draw(canvas); @@ -200,9 +304,9 @@ public class RadialProgress { if (currentWithRound || previousWithRound) { progressPaint.setColor(progressColor); if (previousWithRound) { - progressPaint.setAlpha((int) (255 * animatedAlphaValue)); + progressPaint.setAlpha((int) (255 * animatedAlphaValue * overrideAlpha)); } else { - progressPaint.setAlpha(255); + progressPaint.setAlpha((int) (255 * overrideAlpha)); } cicleRect.set(progressRect.left + diff, progressRect.top + diff, progressRect.right - diff, progressRect.bottom - diff); canvas.drawArc(cicleRect, -90 + radOffset, Math.max(4, 360 * animatedProgressValue), false, progressPaint); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/RecyclerListView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/RecyclerListView.java index b0e694d19..a2d9c22e9 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/RecyclerListView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/RecyclerListView.java @@ -45,6 +45,7 @@ import java.util.ArrayList; public class RecyclerListView extends RecyclerView { private OnItemClickListener onItemClickListener; + private OnItemClickListenerExtended onItemClickListenerExtended; private OnItemLongClickListener onItemLongClickListener; private OnScrollListener onScrollListener; private OnInterceptTouchListener onInterceptTouchListener; @@ -79,13 +80,21 @@ public class RecyclerListView extends RecyclerView { private Runnable clickRunnable; private boolean ignoreOnScroll; + private boolean scrollEnabled = true; + private static int[] attributes; private static boolean gotAttributes; + private boolean hiddenByEmptyView; + public interface OnItemClickListener { void onItemClick(View view, int position); } + public interface OnItemClickListenerExtended { + void onItemClick(View view, int position, float x, float y); + } + public interface OnItemLongClickListener { boolean onItemClick(View view, int position); } @@ -459,13 +468,19 @@ public class RecyclerListView extends RecyclerView { gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { @Override public boolean onSingleTapUp(MotionEvent e) { - if (currentChildView != null && onItemClickListener != null) { - currentChildView.setPressed(true); + if (currentChildView != null && (onItemClickListener != null || onItemClickListenerExtended != null)) { + onChildPressed(currentChildView, true); final View view = currentChildView; final int position = currentChildPosition; + final float x = e.getX(); + final float y = e.getY(); if (instantClick && position != -1) { view.playSoundEffect(SoundEffectConstants.CLICK); - onItemClickListener.onItemClick(view, position); + if (onItemClickListener != null) { + onItemClickListener.onItemClick(view, position); + } else if (onItemClickListenerExtended != null) { + onItemClickListenerExtended.onItemClick(view, position, x, y); + } } AndroidUtilities.runOnUIThread(clickRunnable = new Runnable() { @Override @@ -474,11 +489,15 @@ public class RecyclerListView extends RecyclerView { clickRunnable = null; } if (view != null) { - view.setPressed(false); + onChildPressed(view, false); if (!instantClick) { view.playSoundEffect(SoundEffectConstants.CLICK); - if (onItemClickListener != null && position != -1) { - onItemClickListener.onItemClick(view, position); + if (position != -1) { + if (onItemClickListener != null) { + onItemClickListener.onItemClick(view, position); + } else if (onItemClickListenerExtended != null) { + onItemClickListenerExtended.onItemClick(view, position, x, y); + } } } } @@ -517,7 +536,11 @@ public class RecyclerListView extends RecyclerView { boolean isScrollIdle = RecyclerListView.this.getScrollState() == RecyclerListView.SCROLL_STATE_IDLE; if ((action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) && currentChildView == null && isScrollIdle) { - currentChildView = view.findChildViewUnder(event.getX(), event.getY()); + float ex = event.getX(); + float ey = event.getY(); + if (allowSelectChildAtPosition(ex, ey)) { + currentChildView = view.findChildViewUnder(ex, ey); + } if (currentChildView instanceof ViewGroup) { float x = event.getX() - currentChildView.getLeft(); float y = event.getY() - currentChildView.getTop(); @@ -560,7 +583,7 @@ public class RecyclerListView extends RecyclerView { @Override public void run() { if (selectChildRunnable != null && currentChildView != null) { - currentChildView.setPressed(true); + onChildPressed(currentChildView, true); selectChildRunnable = null; } } @@ -593,7 +616,7 @@ public class RecyclerListView extends RecyclerView { selectChildRunnable = null; } View pressedChild = currentChildView; - currentChildView.setPressed(false); + onChildPressed(currentChildView, false); currentChildView = null; interceptedByChild = false; removeSelection(pressedChild, event); @@ -613,6 +636,14 @@ public class RecyclerListView extends RecyclerView { } } + protected void onChildPressed(View child, boolean pressed) { + child.setPressed(pressed); + } + + protected boolean allowSelectChildAtPosition(float x, float y) { + return true; + } + private void removeSelection(View pressedChild, MotionEvent event) { if (pressedChild == null) { return; @@ -642,7 +673,7 @@ public class RecyclerListView extends RecyclerView { if (currentChildView != null) { View child = currentChildView; if (uncheck) { - currentChildView.setPressed(false); + onChildPressed(currentChildView, false); } currentChildView = null; removeSelection(child, null); @@ -724,7 +755,7 @@ public class RecyclerListView extends RecyclerView { currentChildView.onTouchEvent(event); event.recycle(); View child = currentChildView; - currentChildView.setPressed(false); + onChildPressed(currentChildView, false); currentChildView = null; removeSelection(child, null); interceptedByChild = false; @@ -910,6 +941,10 @@ public class RecyclerListView extends RecyclerView { onItemClickListener = listener; } + public void setOnItemClickListener(OnItemClickListenerExtended listener) { + onItemClickListenerExtended = listener; + } + public void setOnItemLongClickListener(OnItemLongClickListener listener) { onItemLongClickListener = listener; } @@ -939,6 +974,15 @@ public class RecyclerListView extends RecyclerView { } } + @Override + public boolean canScrollVertically(int direction) { + return scrollEnabled && super.canScrollVertically(direction); + } + + public void setScrollEnabled(boolean value) { + scrollEnabled = value; + } + @Override public boolean onInterceptTouchEvent(MotionEvent e) { if (!isEnabled()) { @@ -951,12 +995,25 @@ public class RecyclerListView extends RecyclerView { } private void checkIfEmpty() { - if (emptyView == null || getAdapter() == null) { + if (getAdapter() == null || emptyView == null) { + if (hiddenByEmptyView && getVisibility() != VISIBLE) { + setVisibility(VISIBLE); + hiddenByEmptyView = false; + } return; } boolean emptyViewVisible = getAdapter().getItemCount() == 0; emptyView.setVisibility(emptyViewVisible ? VISIBLE : GONE); setVisibility(emptyViewVisible ? INVISIBLE : VISIBLE); + hiddenByEmptyView = true; + } + + @Override + public void setVisibility(int visibility) { + super.setVisibility(visibility); + if (visibility != VISIBLE) { + hiddenByEmptyView = false; + } } @Override @@ -1239,44 +1296,4 @@ public class RecyclerListView extends RecyclerView { public View getPinnedHeader() { return pinnedHeader; } - - /* - void keyPressed() { - if (!isEnabled() || !isClickable()) { - return; - } - - Drawable selector = mSelector; - Rect selectorRect = mSelectorRect; - if (selector != null && (isFocused() || touchModeDrawsInPressedState()) - && !selectorRect.isEmpty()) { - - final View v = getChildAt(mSelectedPosition - mFirstPosition); - - if (v != null) { - if (v.hasFocusable()) return; - v.setPressed(true); - } - setPressed(true); - - final boolean longClickable = isLongClickable(); - Drawable d = selector.getCurrent(); - if (d != null && d instanceof TransitionDrawable) { - if (longClickable) { - ((TransitionDrawable) d).startTransition( - ViewConfiguration.getLongPressTimeout()); - } else { - ((TransitionDrawable) d).resetTransition(); - } - } - if (longClickable && !mDataChanged) { - if (mPendingCheckForKeyLongPress == null) { - mPendingCheckForKeyLongPress = new CheckForKeyLongPress(); - } - mPendingCheckForKeyLongPress.rememberWindowAttachCount(); - postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout()); - } - } - } - */ } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ScrollSlidingTabStrip.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ScrollSlidingTabStrip.java index 161306b5d..a09230858 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ScrollSlidingTabStrip.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ScrollSlidingTabStrip.java @@ -13,7 +13,6 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.drawable.Drawable; -import android.os.Build; import android.util.TypedValue; import android.view.Gravity; import android.view.View; @@ -88,11 +87,7 @@ public class ScrollSlidingTabStrip extends HorizontalScrollView { return; } View tab = tabsContainer.getChildAt(num); - if (Build.VERSION.SDK_INT >= 15) { - tab.callOnClick(); - } else { - tab.performClick(); - } + tab.performClick(); } public TextView addIconTabWithCounter(Drawable drawable) { @@ -143,7 +138,7 @@ public class ScrollSlidingTabStrip extends HorizontalScrollView { tab.setSelected(position == currentPosition); } - public void addStickerTab(TLRPC.Document sticker) { + public void addStickerTab(TLRPC.Chat chat) { final int position = tabCount++; FrameLayout tab = new FrameLayout(getContext()); tab.setFocusable(true); @@ -156,9 +151,35 @@ public class ScrollSlidingTabStrip extends HorizontalScrollView { tabsContainer.addView(tab); tab.setSelected(position == currentPosition); BackupImageView imageView = new BackupImageView(getContext()); - if (sticker != null && sticker.thumb != null) { - imageView.setImage(sticker.thumb.location, null, "webp", null); + imageView.setRoundRadius(AndroidUtilities.dp(15)); + TLRPC.FileLocation photo = null; + + AvatarDrawable avatarDrawable = new AvatarDrawable(); + if (chat.photo != null) { + photo = chat.photo.photo_small; } + avatarDrawable.setTextSize(AndroidUtilities.dp(14)); + avatarDrawable.setInfo(chat); + imageView.setImage(photo, "50_50", avatarDrawable); + + imageView.setAspectFit(true); + tab.addView(imageView, LayoutHelper.createFrame(30, 30, Gravity.CENTER)); + } + + public void addStickerTab(TLRPC.Document sticker) { + final int position = tabCount++; + FrameLayout tab = new FrameLayout(getContext()); + tab.setTag(sticker); + tab.setFocusable(true); + tab.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + delegate.onPageSelected(position); + } + }); + tabsContainer.addView(tab); + tab.setSelected(position == currentPosition); + BackupImageView imageView = new BackupImageView(getContext()); imageView.setAspectFit(true); tab.addView(imageView, LayoutHelper.createFrame(30, 30, Gravity.CENTER)); } @@ -190,9 +211,58 @@ public class ScrollSlidingTabStrip extends HorizontalScrollView { } } + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + setImages(); + } + + public void setImages() { + int tabSize = AndroidUtilities.dp(52); + int start = getScrollX() / tabSize; + int end = Math.min(tabsContainer.getChildCount(), start + (int) Math.ceil(getMeasuredWidth() / (float) tabSize) + 1); + + for (int a = start; a < end; a++) { + View child = tabsContainer.getChildAt(a); + Object object = child.getTag(); + if (!(object instanceof TLRPC.Document)) { + continue; + } + BackupImageView imageView = (BackupImageView) ((FrameLayout) child).getChildAt(0); + TLRPC.Document sticker = (TLRPC.Document) object; + imageView.setImage(sticker.thumb.location, null, "webp", null); + } + } + @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); + + int tabSize = AndroidUtilities.dp(52); + int oldStart = oldl / tabSize; + int newStart = l / tabSize; + + int count = (int) Math.ceil(getMeasuredWidth() / (float) tabSize) + 1; + int start = Math.max(0, Math.min(oldStart, newStart)); + int end = Math.min(tabsContainer.getChildCount(), Math.max(oldStart, newStart) + count); + + for (int a = start; a < end; a++) { + View child = tabsContainer.getChildAt(a); + if (child == null) { + continue; + } + Object object = child.getTag(); + if (!(object instanceof TLRPC.Document)) { + continue; + } + BackupImageView imageView = (BackupImageView) ((FrameLayout) child).getChildAt(0); + if (a < newStart || a >= newStart + count) { + imageView.setImageDrawable(null); + } else { + TLRPC.Document sticker = (TLRPC.Document) object; + imageView.setImage(sticker.thumb.location, null, "webp", null); + } + } } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SeekBarView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SeekBarView.java new file mode 100644 index 000000000..2cd6106d5 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SeekBarView.java @@ -0,0 +1,148 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Components; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.view.MotionEvent; +import android.widget.FrameLayout; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.ui.ActionBar.Theme; + +public class SeekBarView extends FrameLayout { + + private Paint innerPaint1; + private Paint outerPaint1; + private int thumbWidth; + private int thumbHeight; + private int thumbX; + private int thumbDX; + private float progressToSet; + private boolean pressed; + private SeekBarViewDelegate delegate; + private boolean reportChanges; + + public interface SeekBarViewDelegate { + void onSeekBarDrag(float progress); + } + + public SeekBarView(Context context) { + super(context); + setWillNotDraw(false); + innerPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG); + innerPaint1.setColor(Theme.getColor(Theme.key_player_progressBackground)); + + outerPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG); + outerPaint1.setColor(Theme.getColor(Theme.key_player_progress)); + + thumbWidth = AndroidUtilities.dp(24); + thumbHeight = AndroidUtilities.dp(24); + } + + public void setColors(int inner, int outer) { + innerPaint1.setColor(inner); + outerPaint1.setColor(outer); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return onTouch(ev); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return onTouch(event); + } + + public void setReportChanges(boolean value) { + reportChanges = value; + } + + public void setDelegate(SeekBarViewDelegate seekBarViewDelegate) { + delegate = seekBarViewDelegate; + } + + boolean onTouch(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + getParent().requestDisallowInterceptTouchEvent(true); + int additionWidth = (getMeasuredHeight() - thumbWidth) / 2; + if (thumbX - additionWidth <= ev.getX() && ev.getX() <= thumbX + thumbWidth + additionWidth && ev.getY() >= 0 && ev.getY() <= getMeasuredHeight()) { + pressed = true; + thumbDX = (int) (ev.getX() - thumbX); + invalidate(); + return true; + } + } else if (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL) { + if (pressed) { + if (ev.getAction() == MotionEvent.ACTION_UP) { + delegate.onSeekBarDrag((float) thumbX / (float) (getMeasuredWidth() - thumbWidth)); + } + pressed = false; + invalidate(); + return true; + } + } else if (ev.getAction() == MotionEvent.ACTION_MOVE) { + if (pressed) { + thumbX = (int) (ev.getX() - thumbDX); + if (thumbX < 0) { + thumbX = 0; + } else if (thumbX > getMeasuredWidth() - thumbWidth) { + thumbX = getMeasuredWidth() - thumbWidth; + } + if (reportChanges) { + delegate.onSeekBarDrag((float) thumbX / (float) (getMeasuredWidth() - thumbWidth)); + } + invalidate(); + return true; + } + } + return false; + } + + public void setProgress(float progress) { + if (getMeasuredWidth() == 0) { + progressToSet = progress; + return; + } + progressToSet = -1; + int newThumbX = (int) Math.ceil((getMeasuredWidth() - thumbWidth) * progress); + if (thumbX != newThumbX) { + thumbX = newThumbX; + if (thumbX < 0) { + thumbX = 0; + } else if (thumbX > getMeasuredWidth() - thumbWidth) { + thumbX = getMeasuredWidth() - thumbWidth; + } + invalidate(); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (progressToSet >= 0 && getMeasuredWidth() > 0) { + setProgress(progressToSet); + progressToSet = -1; + } + } + + public boolean isDragging() { + return pressed; + } + + @Override + protected void onDraw(Canvas canvas) { + int y = (getMeasuredHeight() - thumbHeight) / 2; + canvas.drawRect(thumbWidth / 2, getMeasuredHeight() / 2 - AndroidUtilities.dp(1), getMeasuredWidth() - thumbWidth / 2, getMeasuredHeight() / 2 + AndroidUtilities.dp(1), innerPaint1); + canvas.drawRect(thumbWidth / 2, getMeasuredHeight() / 2 - AndroidUtilities.dp(1), thumbWidth / 2 + thumbX, getMeasuredHeight() / 2 + AndroidUtilities.dp(1), outerPaint1); + canvas.drawCircle(thumbX + thumbWidth / 2, y + thumbHeight / 2, AndroidUtilities.dp(pressed ? 8 : 6), outerPaint1); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SeekBarWaveform.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SeekBarWaveform.java index 3b4bf7d74..c5a34e16f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SeekBarWaveform.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SeekBarWaveform.java @@ -147,7 +147,7 @@ public class SeekBarWaveform { float barCounter = 0; int nextBarNum = 0; - paintInner.setColor(messageObject != null && !messageObject.isOutOwner() && messageObject.isContentUnread() && messageObject.messageOwner.to_id.channel_id == 0 ? outerColor : (selected ? selectedColor : innerColor)); + paintInner.setColor(messageObject != null && !messageObject.isOutOwner() && messageObject.isContentUnread() ? outerColor : (selected ? selectedColor : innerColor)); paintOuter.setColor(outerColor); int y = (height - AndroidUtilities.dp(14)) / 2; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ShareAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ShareAlert.java index 68d88db32..82afc7597 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ShareAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ShareAlert.java @@ -30,7 +30,6 @@ import android.view.View; import android.view.ViewGroup; import android.view.animation.DecelerateInterpolator; import android.view.inputmethod.EditorInfo; -import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; @@ -77,8 +76,8 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi private TextView doneButtonBadgeTextView; private TextView doneButtonTextView; private LinearLayout doneButton; - private EditText nameTextView; - private EditText commentTextView; + private EditTextBoldCursor nameTextView; + private EditTextBoldCursor commentTextView; private View shadow; private View shadow2; private AnimatorSet animatorSet; @@ -86,7 +85,7 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi private GridLayoutManager layoutManager; private ShareDialogsAdapter listAdapter; private ShareSearchAdapter searchAdapter; - private MessageObject sendingMessageObject; + private ArrayList sendingMessageObjects; private String sendingText; private EmptyTextProgressView searchEmptyView; private Drawable shadowDrawable; @@ -102,14 +101,26 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi private int scrollOffsetY; private int topBeforeSwitch; - public ShareAlert(final Context context, MessageObject messageObject, final String text, boolean publicChannel, final String copyLink, boolean fullScreen) { + + public static ShareAlert createShareAlert(final Context context, MessageObject messageObject, final String text, boolean publicChannel, final String copyLink, boolean fullScreen) { + ArrayList arrayList; + if (messageObject != null) { + arrayList = new ArrayList<>(); + arrayList.add(messageObject); + } else { + arrayList = null; + } + return new ShareAlert(context, arrayList, text, publicChannel, copyLink, fullScreen); + } + + public ShareAlert(final Context context, ArrayList messages, final String text, boolean publicChannel, final String copyLink, boolean fullScreen) { super(context, true); shadowDrawable = context.getResources().getDrawable(R.drawable.sheet_shadow).mutate(); shadowDrawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_dialogBackground), PorterDuff.Mode.MULTIPLY)); linkToCopy = copyLink; - sendingMessageObject = messageObject; + sendingMessageObjects = messages; searchAdapter = new ShareSearchAdapter(context); isPublicChannel = publicChannel; sendingText = text; @@ -117,8 +128,8 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi if (publicChannel) { loadingLink = true; TLRPC.TL_channels_exportMessageLink req = new TLRPC.TL_channels_exportMessageLink(); - req.id = messageObject.getId(); - req.channel = MessagesController.getInputChannel(messageObject.messageOwner.to_id.channel_id); + req.id = messages.get(0).getId(); + req.channel = MessagesController.getInputChannel(messages.get(0).messageOwner.to_id.channel_id); ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override public void run(final TLObject response, TLRPC.TL_error error) { @@ -222,14 +233,12 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi } dismiss(); } else { - if (sendingMessageObject != null) { - ArrayList arrayList = new ArrayList<>(); - arrayList.add(sendingMessageObject); + if (sendingMessageObjects != null) { for (HashMap.Entry entry : selectedDialogs.entrySet()) { if (frameLayout2.getTag() != null && commentTextView.length() > 0) { SendMessagesHelper.getInstance().sendMessage(commentTextView.getText().toString(), entry.getKey(), null, null, true, null, null, null); } - SendMessagesHelper.getInstance().sendMessage(arrayList, entry.getKey()); + SendMessagesHelper.getInstance().sendMessage(sendingMessageObjects, entry.getKey()); } } else if (sendingText != null) { for (HashMap.Entry entry : selectedDialogs.entrySet()) { @@ -268,7 +277,7 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi imageView.setPadding(0, AndroidUtilities.dp(2), 0, 0); frameLayout.addView(imageView, LayoutHelper.createFrame(48, 48, Gravity.LEFT | Gravity.CENTER_VERTICAL)); - nameTextView = new EditText(context); + nameTextView = new EditTextBoldCursor(context); nameTextView.setHint(LocaleController.getString("ShareSendTo", R.string.ShareSendTo)); nameTextView.setMaxLines(1); nameTextView.setSingleLine(true); @@ -278,7 +287,9 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi nameTextView.setHintTextColor(Theme.getColor(Theme.key_dialogTextHint)); nameTextView.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); nameTextView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES); - AndroidUtilities.clearCursorDrawable(nameTextView); + nameTextView.setCursorColor(Theme.getColor(Theme.key_dialogTextBlack)); + nameTextView.setCursorSize(AndroidUtilities.dp(20)); + nameTextView.setCursorWidth(1.5f); nameTextView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); frameLayout.addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 48, 2, 96, 0)); nameTextView.addTextChangedListener(new TextWatcher() { @@ -402,7 +413,7 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi } }); - commentTextView = new EditText(context); + commentTextView = new EditTextBoldCursor(context); commentTextView.setHint(LocaleController.getString("ShareComment", R.string.ShareComment)); commentTextView.setMaxLines(1); commentTextView.setSingleLine(true); @@ -412,7 +423,9 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi commentTextView.setHintTextColor(Theme.getColor(Theme.key_dialogTextHint)); commentTextView.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); commentTextView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES); - AndroidUtilities.clearCursorDrawable(commentTextView); + commentTextView.setCursorColor(Theme.getColor(Theme.key_dialogTextBlack)); + commentTextView.setCursorSize(AndroidUtilities.dp(20)); + commentTextView.setCursorWidth(1.5f); commentTextView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); frameLayout2.addView(commentTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 8, 1, 8, 0)); @@ -569,8 +582,8 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi public void fetchDialogs() { dialogs.clear(); - for (int a = 0; a < MessagesController.getInstance().dialogsServerOnly.size(); a++) { - TLRPC.TL_dialog dialog = MessagesController.getInstance().dialogsServerOnly.get(a); + for (int a = 0; a < MessagesController.getInstance().dialogsForward.size(); a++) { + TLRPC.TL_dialog dialog = MessagesController.getInstance().dialogsForward.get(a); int lower_id = (int) dialog.id; int high_id = (int) (dialog.id >> 32); if (lower_id != 0 && high_id != 1) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ShareLocationDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ShareLocationDrawable.java new file mode 100644 index 000000000..c158b49a3 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ShareLocationDrawable.java @@ -0,0 +1,127 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Components; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.drawable.Drawable; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.R; + +public class ShareLocationDrawable extends Drawable { + + private long lastUpdateTime = 0; + private float progress[] = new float[] {0.0f, -0.5f}; + private Drawable drawable; + private Drawable drawableLeft; + private Drawable drawableRight; + private boolean isSmall; + + public ShareLocationDrawable(Context context, boolean small) { + isSmall = small; + if (small) { + drawable = context.getResources().getDrawable(R.drawable.smallanimationpin); + drawableLeft = context.getResources().getDrawable(R.drawable.smallanimationpinleft); + drawableRight = context.getResources().getDrawable(R.drawable.smallanimationpinright); + } else { + drawable = context.getResources().getDrawable(R.drawable.animationpin); + drawableLeft = context.getResources().getDrawable(R.drawable.animationpinleft); + drawableRight = context.getResources().getDrawable(R.drawable.animationpinright); + } + } + + private void update() { + long newTime = System.currentTimeMillis(); + long dt = newTime - lastUpdateTime; + lastUpdateTime = newTime; + if (dt > 16) { + dt = 16; + } + for (int a = 0; a < 2; a++) { + if (progress[a] >= 1.0f) { + progress[a] = 0.0f; + } + progress[a] += dt / 1300.0f; + if (progress[a] > 1.0f) { + progress[a] = 1.0f; + } + } + invalidateSelf(); + } + + @Override + public void draw(Canvas canvas) { + int size = AndroidUtilities.dp(isSmall ? 30 : 120); + int y = getBounds().top + (getIntrinsicHeight() - size) / 2; + int x = getBounds().left + (getIntrinsicWidth() - size) / 2; + + drawable.setBounds(x, y, x + drawable.getIntrinsicWidth(), y + drawable.getIntrinsicHeight()); + drawable.draw(canvas); + + for (int a = 0; a < 2; a++) { + if (progress[a] < 0) { + continue; + } + float scale = 0.5f + 0.5f * progress[a]; + int w = AndroidUtilities.dp((isSmall ? 2.5f : 5) * scale); + int h = AndroidUtilities.dp((isSmall ? 6.5f : 18) * scale); + int tx = AndroidUtilities.dp((isSmall ? 6.0f : 15) * progress[a]); + float alpha; + if (progress[a] < 0.5f) { + alpha = progress[a] / 0.5f; + } else { + alpha = 1.0f - (progress[a] - 0.5f) / 0.5f; + } + + int cx = x + AndroidUtilities.dp(isSmall ? 7 : 42) - tx; + int cy = y + drawable.getIntrinsicHeight() / 2 - (isSmall ? 0 : AndroidUtilities.dp(7)); + + drawableLeft.setAlpha((int) (alpha * 255)); + drawableLeft.setBounds(cx - w, cy - h, cx + w, cy + h); + drawableLeft.draw(canvas); + + cx = x + drawable.getIntrinsicWidth() - AndroidUtilities.dp(isSmall ? 7 : 42) + tx; + + drawableRight.setAlpha((int) (alpha * 255)); + drawableRight.setBounds(cx - w, cy - h, cx + w, cy + h); + drawableRight.draw(canvas); + } + + update(); + } + + @Override + public void setAlpha(int alpha) { + + } + + @Override + public void setColorFilter(ColorFilter cf) { + drawable.setColorFilter(cf); + drawableLeft.setColorFilter(cf); + drawableRight.setColorFilter(cf); + } + + @Override + public int getOpacity() { + return 0; + } + + @Override + public int getIntrinsicWidth() { + return AndroidUtilities.dp(isSmall ? 40 : 120); + } + + @Override + public int getIntrinsicHeight() { + return AndroidUtilities.dp(isSmall ? 40 : 180); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SharingLocationsAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SharingLocationsAlert.java new file mode 100644 index 000000000..57e4691b2 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SharingLocationsAlert.java @@ -0,0 +1,319 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Components; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.LocationController; +import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.R; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; +import org.telegram.ui.ActionBar.BottomSheet; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Cells.SharingLiveLocationCell; +import org.telegram.ui.StickerPreviewViewer; + +import java.util.regex.Pattern; + +public class SharingLocationsAlert extends BottomSheet implements NotificationCenter.NotificationCenterDelegate { + + private Pattern urlPattern; + private RecyclerListView listView; + private ListAdapter adapter; + private Drawable shadowDrawable; + private TextView textView; + + private int scrollOffsetY; + private int reqId; + private boolean ignoreLayout; + + private SharingLocationsAlertDelegate delegate; + + public interface SharingLocationsAlertDelegate { + void didSelectLocation(LocationController.SharingLocationInfo info); + } + + public SharingLocationsAlert(Context context, SharingLocationsAlertDelegate sharingLocationsAlertDelegate) { + super(context, false); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.liveLocationsChanged); + delegate = sharingLocationsAlertDelegate; + + shadowDrawable = context.getResources().getDrawable(R.drawable.sheet_shadow).mutate(); + shadowDrawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_dialogBackground), PorterDuff.Mode.MULTIPLY)); + + containerView = new FrameLayout(context) { + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN && scrollOffsetY != 0 && ev.getY() < scrollOffsetY) { + dismiss(); + return true; + } + return super.onInterceptTouchEvent(ev); + } + + @Override + public boolean onTouchEvent(MotionEvent e) { + return !isDismissed() && super.onTouchEvent(e); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int height = MeasureSpec.getSize(heightMeasureSpec); + if (Build.VERSION.SDK_INT >= 21) { + height -= AndroidUtilities.statusBarHeight; + } + int measuredWidth = getMeasuredWidth(); + int contentSize = AndroidUtilities.dp(48 + 8) + AndroidUtilities.dp(56) + 1 + LocationController.getInstance().sharingLocationsUI.size() * AndroidUtilities.dp(54); + + int padding; + if (contentSize < (height / 5 * 3)) { + padding = AndroidUtilities.dp(8); + } else { + padding = (height / 5 * 2); + if (contentSize < height) { + padding -= (height - contentSize); + } + } + + if (listView.getPaddingTop() != padding) { + ignoreLayout = true; + listView.setPadding(0, padding, 0, AndroidUtilities.dp(8)); + ignoreLayout = false; + } + super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(Math.min(contentSize, height), MeasureSpec.EXACTLY)); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + updateLayout(); + } + + @Override + public void requestLayout() { + if (ignoreLayout) { + return; + } + super.requestLayout(); + } + + @Override + protected void onDraw(Canvas canvas) { + shadowDrawable.setBounds(0, scrollOffsetY - backgroundPaddingTop, getMeasuredWidth(), getMeasuredHeight()); + shadowDrawable.draw(canvas); + } + }; + containerView.setWillNotDraw(false); + containerView.setPadding(backgroundPaddingLeft, 0, backgroundPaddingLeft, 0); + + listView = new RecyclerListView(context) { + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + boolean result = StickerPreviewViewer.getInstance().onInterceptTouchEvent(event, listView, 0, null); + return super.onInterceptTouchEvent(event) || result; + } + + @Override + public void requestLayout() { + if (ignoreLayout) { + return; + } + super.requestLayout(); + } + }; + listView.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false)); + listView.setAdapter(adapter = new ListAdapter(context)); + listView.setVerticalScrollBarEnabled(false); + listView.setClipToPadding(false); + listView.setEnabled(true); + listView.setGlowColor(Theme.getColor(Theme.key_dialogScrollGlow)); + listView.setOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + updateLayout(); + } + }); + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + position -= 1; + if (position < 0 || position >= LocationController.getInstance().sharingLocationsUI.size()) { + return; + } + delegate.didSelectLocation(LocationController.getInstance().sharingLocationsUI.get(position)); + dismiss(); + } + }); + containerView.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 0, 0, 0, 48)); + + View shadow = new View(context); + shadow.setBackgroundResource(R.drawable.header_shadow_reverse); + containerView.addView(shadow, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 3, Gravity.BOTTOM | Gravity.LEFT, 0, 0, 0, 48)); + + PickerBottomLayout pickerBottomLayout = new PickerBottomLayout(context, false); + pickerBottomLayout.setBackgroundColor(Theme.getColor(Theme.key_dialogBackground)); + containerView.addView(pickerBottomLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT | Gravity.BOTTOM)); + pickerBottomLayout.cancelButton.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); + pickerBottomLayout.cancelButton.setTextColor(Theme.getColor(Theme.key_dialogTextRed)); + pickerBottomLayout.cancelButton.setText(LocaleController.getString("StopAllLocationSharings", R.string.StopAllLocationSharings)); + pickerBottomLayout.cancelButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + LocationController.getInstance().removeAllLocationSharings(); + dismiss(); + } + }); + pickerBottomLayout.doneButtonTextView.setTextColor(Theme.getColor(Theme.key_dialogTextBlue2)); + pickerBottomLayout.doneButtonTextView.setText(LocaleController.getString("Close", R.string.Close).toUpperCase()); + pickerBottomLayout.doneButton.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); + pickerBottomLayout.doneButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + dismiss(); + } + }); + pickerBottomLayout.doneButtonBadgeTextView.setVisibility(View.GONE); + + adapter.notifyDataSetChanged(); + } + + @Override + protected boolean canDismissWithSwipe() { + return false; + } + + @SuppressLint("NewApi") + private void updateLayout() { + if (listView.getChildCount() <= 0) { + listView.setTopGlowOffset(scrollOffsetY = listView.getPaddingTop()); + containerView.invalidate(); + return; + } + View child = listView.getChildAt(0); + RecyclerListView.Holder holder = (RecyclerListView.Holder) listView.findContainingViewHolder(child); + int top = child.getTop() - AndroidUtilities.dp(8); + int newOffset = top > 0 && holder != null && holder.getAdapterPosition() == 0 ? top : 0; + if (scrollOffsetY != newOffset) { + listView.setTopGlowOffset(scrollOffsetY = newOffset); + containerView.invalidate(); + } + } + + @Override + public void didReceivedNotification(int id, Object... args) { + if (id == NotificationCenter.liveLocationsChanged) { + if (LocationController.getInstance().sharingLocationsUI.isEmpty()) { + dismiss(); + } else { + adapter.notifyDataSetChanged(); + } + } + } + + @Override + public void dismiss() { + super.dismiss(); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.liveLocationsChanged); + } + + private class ListAdapter extends RecyclerListView.SelectionAdapter { + + private Context context; + + public ListAdapter(Context context) { + this.context = context; + } + + @Override + public int getItemCount() { + return LocationController.getInstance().sharingLocationsUI.size() + 1; + } + + @Override + public int getItemViewType(int position) { + if (position == 0) { + return 1; + } + return 0; + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return holder.getItemViewType() == 0; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + view = new SharingLiveLocationCell(context, false); + //view.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + break; + case 1: + default: + FrameLayout frameLayout = new FrameLayout(context) { + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(48) + 1, MeasureSpec.EXACTLY)); + } + + @Override + protected void onDraw(Canvas canvas) { + canvas.drawLine(0, AndroidUtilities.dp(40), getMeasuredWidth(), AndroidUtilities.dp(40), Theme.dividerPaint); + } + }; + frameLayout.setWillNotDraw(false); + textView = new TextView(context); + textView.setTextColor(Theme.getColor(Theme.key_dialogIcon)); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + textView.setGravity(Gravity.CENTER); + textView.setPadding(0, 0, 0, AndroidUtilities.dp(8)); + frameLayout.addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 40)); + view = frameLayout; + break; + } + return new RecyclerListView.Holder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case 0: { + SharingLiveLocationCell cell = (SharingLiveLocationCell) holder.itemView; + cell.setDialog(LocationController.getInstance().sharingLocationsUI.get(position - 1)); + break; + } + case 1: { + if (textView != null) { + textView.setText(LocaleController.formatString("SharingLiveLocationTitle", R.string.SharingLiveLocationTitle, LocaleController.formatPluralString("Chats", LocationController.getInstance().sharingLocationsUI.size()))); + } + break; + } + } + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SizeNotifierFrameLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SizeNotifierFrameLayout.java index 76642f211..6ca8d8d53 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SizeNotifierFrameLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SizeNotifierFrameLayout.java @@ -116,15 +116,11 @@ public class SizeNotifierFrameLayout extends FrameLayout { int height = (int) Math.ceil(backgroundDrawable.getIntrinsicHeight() * scale); int x = (getMeasuredWidth() - width) / 2; int y = (viewHeight - height + keyboardHeight) / 2 + actionBarHeight; - if (bottomClip != 0) { - canvas.save(); - canvas.clipRect(0, actionBarHeight, width, getMeasuredHeight() - bottomClip); - } + canvas.save(); + canvas.clipRect(0, actionBarHeight, width, getMeasuredHeight() - bottomClip); backgroundDrawable.setBounds(x, y, x + width, y + height); backgroundDrawable.draw(canvas); - if (bottomClip != 0) { - canvas.restore(); - } + canvas.restore(); } } } else { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SlideView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SlideView.java index 99fe459d2..3235a3242 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SlideView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SlideView.java @@ -42,6 +42,10 @@ public class SlideView extends LinearLayout { } + public void onCancelPressed() { + + } + public void saveStateParams(Bundle bundle) { } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/StickerMasksView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/StickerMasksView.java index d760e8f7c..4ff5c35e6 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/StickerMasksView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/StickerMasksView.java @@ -107,7 +107,7 @@ public class StickerMasksView extends FrameLayout implements NotificationCenter. } TLRPC.Document document = cell.getSticker(); listener.onStickerSelected(document); - StickersQuery.addRecentSticker(StickersQuery.TYPE_MASK, document, (int) (System.currentTimeMillis() / 1000)); + StickersQuery.addRecentSticker(StickersQuery.TYPE_MASK, document, (int) (System.currentTimeMillis() / 1000), false); MessagesController.getInstance().saveRecentSticker(document, true); } }; @@ -249,7 +249,7 @@ public class StickerMasksView extends FrameLayout implements NotificationCenter. if (document == null) { return; } - StickersQuery.addRecentSticker(currentType, document, (int) (System.currentTimeMillis() / 1000)); + StickersQuery.addRecentSticker(currentType, document, (int) (System.currentTimeMillis() / 1000), false); boolean wasEmpty = recentStickers[currentType].isEmpty(); recentStickers[currentType] = StickersQuery.getRecentStickers(currentType); if (stickersGridAdapter != null) { @@ -306,8 +306,9 @@ public class StickerMasksView extends FrameLayout implements NotificationCenter. updateStickerTabs(); reloadStickersAdapter(); checkDocuments(); - StickersQuery.loadRecents(StickersQuery.TYPE_IMAGE, false, true); - StickersQuery.loadRecents(StickersQuery.TYPE_MASK, false, true); + StickersQuery.loadRecents(StickersQuery.TYPE_IMAGE, false, true, false); + StickersQuery.loadRecents(StickersQuery.TYPE_MASK, false, true, false); + StickersQuery.loadRecents(StickersQuery.TYPE_FAVE, false, true, false); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersAlert.java index 5f14f332b..dab7653e0 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersAlert.java @@ -95,6 +95,7 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not private PickerBottomLayout pickerBottomLayout; private FrameLayout stickerPreviewLayout; private TextView previewSendButton; + private ImageView previewFavButton; private View previewSendButtonShadow; private BackupImageView stickerImageView; private TextView stickerEmojiTextView; @@ -398,6 +399,12 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not if (!set) { stickerEmojiTextView.setText(Emoji.replaceEmoji(StickersQuery.getEmojiForSticker(selectedSticker.id), stickerEmojiTextView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(30), false)); } + boolean fav = StickersQuery.isStickerInFavorites(selectedSticker); + previewFavButton.setImageResource(fav ? R.drawable.stickers_unfavorite : R.drawable.stickers_favorite); + previewFavButton.setTag(fav ? 1 : null); + if (previewFavButton.getVisibility() != View.GONE) { + previewFavButton.setVisibility(fav || StickersQuery.canAddStickerToFavorites() ? View.VISIBLE : View.INVISIBLE); + } stickerImageView.getImageReceiver().setImage(selectedSticker, null, selectedSticker.thumb.location, null, "webp", 1); FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) stickerPreviewLayout.getLayoutParams(); @@ -517,6 +524,24 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not } }); + previewFavButton = new ImageView(context); + previewFavButton.setScaleType(ImageView.ScaleType.CENTER); + stickerPreviewLayout.addView(previewFavButton, LayoutHelper.createFrame(48, 48, Gravity.BOTTOM | Gravity.RIGHT, 0, 0, 4, 0)); + previewFavButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_dialogIcon), PorterDuff.Mode.MULTIPLY)); + previewFavButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + StickersQuery.addRecentSticker(StickersQuery.TYPE_FAVE, selectedSticker, (int) (System.currentTimeMillis() / 1000), previewFavButton.getTag() != null); + if (previewFavButton.getTag() == null) { + previewFavButton.setTag(1); + previewFavButton.setImageResource(R.drawable.stickers_unfavorite); + } else { + previewFavButton.setTag(null); + previewFavButton.setImageResource(R.drawable.stickers_favorite); + } + } + }); + previewSendButtonShadow = new View(context); previewSendButtonShadow.setBackgroundResource(R.drawable.header_shadow_reverse); stickerPreviewLayout.addView(previewSendButtonShadow, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 3, Gravity.BOTTOM | Gravity.LEFT, 0, 0, 0, 48)); @@ -534,12 +559,14 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not stickerImageView.setLayoutParams(LayoutHelper.createFrame(size, size, Gravity.CENTER, 0, 0, 0, 30)); stickerEmojiTextView.setLayoutParams(LayoutHelper.createFrame(size, size, Gravity.CENTER, 0, 0, 0, 30)); previewSendButton.setVisibility(View.VISIBLE); + previewFavButton.setVisibility(View.VISIBLE); previewSendButtonShadow.setVisibility(View.VISIBLE); } else { previewSendButton.setText(LocaleController.getString("Close", R.string.Close).toUpperCase()); stickerImageView.setLayoutParams(LayoutHelper.createFrame(size, size, Gravity.CENTER)); stickerEmojiTextView.setLayoutParams(LayoutHelper.createFrame(size, size, Gravity.CENTER)); previewSendButton.setVisibility(View.GONE); + previewFavButton.setVisibility(View.GONE); previewSendButtonShadow.setVisibility(View.GONE); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ThemeEditorView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ThemeEditorView.java index 2010aaffe..3b19a05ae 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ThemeEditorView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ThemeEditorView.java @@ -45,7 +45,6 @@ import android.view.ViewGroup; import android.view.WindowManager; import android.view.animation.DecelerateInterpolator; import android.view.inputmethod.EditorInfo; -import android.widget.EditText; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.TextView; @@ -160,7 +159,7 @@ public class ThemeEditorView { private Bitmap colorWheelBitmap; - private EditText colorEditText[] = new EditText[4]; + private EditTextBoldCursor colorEditText[] = new EditTextBoldCursor[4]; private int colorWheelRadius; @@ -196,10 +195,12 @@ public class ThemeEditorView { linearLayout.setOrientation(LinearLayout.HORIZONTAL); addView(linearLayout, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL | Gravity.TOP)); for (int a = 0; a < 4; a++){ - colorEditText[a] = new EditText(context); + colorEditText[a] = new EditTextBoldCursor(context); colorEditText[a].setInputType(InputType.TYPE_CLASS_NUMBER); colorEditText[a].setTextColor(0xff212121); - AndroidUtilities.clearCursorDrawable(colorEditText[a]); + colorEditText[a].setCursorColor(0xff212121); + colorEditText[a].setCursorSize(AndroidUtilities.dp(20)); + colorEditText[a].setCursorWidth(1.5f); colorEditText[a].setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); colorEditText[a].setBackgroundDrawable(Theme.createEditTextDrawable(context, true)); colorEditText[a].setMaxLines(1); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/VideoPlayer.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/VideoPlayer.java index 32b57770d..1ae9df210 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/VideoPlayer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/VideoPlayer.java @@ -14,6 +14,8 @@ import android.net.Uri; import android.os.Handler; import android.view.TextureView; +import org.telegram.messenger.exoplayer2.Player; +import org.telegram.messenger.exoplayer2.source.LoopingMediaSource; import org.telegram.messenger.secretmedia.ExtendedDefaultDataSourceFactory; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.exoplayer2.DefaultLoadControl; @@ -26,9 +28,7 @@ import org.telegram.messenger.exoplayer2.SimpleExoPlayer; import org.telegram.messenger.exoplayer2.Timeline; import org.telegram.messenger.exoplayer2.extractor.DefaultExtractorsFactory; import org.telegram.messenger.exoplayer2.source.ExtractorMediaSource; -import org.telegram.messenger.exoplayer2.source.LoopingMediaSource; import org.telegram.messenger.exoplayer2.source.MediaSource; -import org.telegram.messenger.exoplayer2.source.MergingMediaSource; import org.telegram.messenger.exoplayer2.source.TrackGroupArray; import org.telegram.messenger.exoplayer2.source.dash.DashMediaSource; import org.telegram.messenger.exoplayer2.source.dash.DefaultDashChunkSource; @@ -62,11 +62,17 @@ public class VideoPlayer implements ExoPlayer.EventListener, SimpleExoPlayer.Vid } private SimpleExoPlayer player; + private SimpleExoPlayer audioPlayer; private MappingTrackSelector trackSelector; private Handler mainHandler; private DataSource.Factory mediaDataSourceFactory; private TextureView textureView; private boolean autoplay; + private boolean mixedAudio; + + private boolean videoPlayerReady; + private boolean audioPlayerReady; + private boolean mixedPlayWhenReady; private VideoPlayerDelegate delegate; private int lastReportedPlaybackState; @@ -97,9 +103,62 @@ public class VideoPlayer implements ExoPlayer.EventListener, SimpleExoPlayer.Vid player.setVideoTextureView(textureView); player.setPlayWhenReady(autoplay); } + if (mixedAudio) { + if (audioPlayer == null) { + audioPlayer = ExoPlayerFactory.newSimpleInstance(ApplicationLoader.applicationContext, trackSelector, new DefaultLoadControl(), null, DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF); + audioPlayer.addListener(new Player.EventListener() { + @Override + public void onTimelineChanged(Timeline timeline, Object manifest) { + + } + + @Override + public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { + + } + + @Override + public void onLoadingChanged(boolean isLoading) { + + } + + @Override + public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { + if (!audioPlayerReady && playbackState == Player.STATE_READY) { + audioPlayerReady = true; + checkPlayersReady(); + } + } + + @Override + public void onRepeatModeChanged(int repeatMode) { + + } + + @Override + public void onPlayerError(ExoPlaybackException error) { + + } + + @Override + public void onPositionDiscontinuity() { + + } + + @Override + public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { + + } + }); + audioPlayer.setPlayWhenReady(autoplay); + } + } } public void preparePlayerLoop(Uri videoUri, String videoType, Uri audioUri, String audioType) { + mixedAudio = true; + audioPlayerReady = false; + videoPlayerReady = false; ensurePleyaerCreated(); MediaSource mediaSource1 = null, mediaSource2 = null; for (int a = 0; a < 2; a++) { @@ -134,11 +193,13 @@ public class VideoPlayer implements ExoPlayer.EventListener, SimpleExoPlayer.Vid mediaSource2 = mediaSource; } } - MediaSource mediaSource = new MergingMediaSource(mediaSource1, mediaSource2); player.prepare(mediaSource1, true, true); + audioPlayer.prepare(mediaSource2, true, true); } public void preparePlayer(Uri uri, String type) { + videoPlayerReady = false; + mixedAudio = false; ensurePleyaerCreated(); MediaSource mediaSource; switch (type) { @@ -167,6 +228,10 @@ public class VideoPlayer implements ExoPlayer.EventListener, SimpleExoPlayer.Vid player.release(); player = null; } + if (audioPlayer != null) { + audioPlayer.release(); + audioPlayer = null; + } } public void setTextureView(TextureView texture) { @@ -181,25 +246,56 @@ public class VideoPlayer implements ExoPlayer.EventListener, SimpleExoPlayer.Vid } public void play() { - if (player == null) { - return; + mixedPlayWhenReady = true; + if (mixedAudio) { + if (!audioPlayerReady || !videoPlayerReady) { + if (player != null) { + player.setPlayWhenReady(false); + } + if (audioPlayer != null) { + audioPlayer.setPlayWhenReady(false); + } + return; + } + } + if (player != null) { + player.setPlayWhenReady(true); + } + if (audioPlayer != null) { + audioPlayer.setPlayWhenReady(true); } - player.setPlayWhenReady(true); } public void pause() { - if (player == null) { - return; + mixedPlayWhenReady = false; + if (player != null) { + player.setPlayWhenReady(false); + } + if (audioPlayer != null) { + audioPlayer.setPlayWhenReady(false); } - player.setPlayWhenReady(false); } public void setPlayWhenReady(boolean playWhenReady) { - autoplay = playWhenReady; - if (player == null) { - return; + mixedPlayWhenReady = playWhenReady; + if (playWhenReady && mixedAudio) { + if (!audioPlayerReady || !videoPlayerReady) { + if (player != null) { + player.setPlayWhenReady(false); + } + if (audioPlayer != null) { + audioPlayer.setPlayWhenReady(false); + } + return; + } + } + autoplay = playWhenReady; + if (player != null) { + player.setPlayWhenReady(playWhenReady); + } + if (audioPlayer != null) { + audioPlayer.setPlayWhenReady(playWhenReady); } - player.setPlayWhenReady(playWhenReady); } public long getDuration() { @@ -215,28 +311,32 @@ public class VideoPlayer implements ExoPlayer.EventListener, SimpleExoPlayer.Vid } public void setMute(boolean value) { - if (player == null) { - return; + if (player != null) { + player.setVolume(value ? 0.0f : 1.0f); } - if (value) { - player.setVolume(0.0f); - } else { - player.setVolume(1.0f); + if (audioPlayer != null) { + audioPlayer.setVolume(value ? 0.0f : 1.0f); } } + @Override + public void onRepeatModeChanged(int repeatMode) { + + } + public void setVolume(float volume) { - if (player == null) { - return; + if (player != null) { + player.setVolume(volume); + } + if (audioPlayer != null) { + audioPlayer.setVolume(volume); } - player.setVolume(volume); } public void seekTo(long positionMs) { - if (player == null) { - return; + if (player != null) { + player.seekTo(positionMs); } - player.seekTo(positionMs); } public void setDelegate(VideoPlayerDelegate videoPlayerDelegate) { @@ -252,7 +352,7 @@ public class VideoPlayer implements ExoPlayer.EventListener, SimpleExoPlayer.Vid } public boolean isPlaying() { - return player != null && player.getPlayWhenReady(); + return mixedAudio && mixedPlayWhenReady || player != null && player.getPlayWhenReady(); } public boolean isBuffering() { @@ -263,6 +363,15 @@ public class VideoPlayer implements ExoPlayer.EventListener, SimpleExoPlayer.Vid if (player != null) { player.setAudioStreamType(type); } + if (audioPlayer != null) { + audioPlayer.setAudioStreamType(type); + } + } + + private void checkPlayersReady() { + if (audioPlayerReady && videoPlayerReady && mixedPlayWhenReady) { + play(); + } } @Override @@ -273,6 +382,10 @@ public class VideoPlayer implements ExoPlayer.EventListener, SimpleExoPlayer.Vid @Override public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { maybeReportPlayerState(); + if (!videoPlayerReady && playbackState == Player.STATE_READY) { + videoPlayerReady = true; + checkPlayersReady(); + } } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/WebPlayerView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/WebPlayerView.java index 4497ee35c..c611e52aa 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/WebPlayerView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/WebPlayerView.java @@ -13,7 +13,6 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; -import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; @@ -49,6 +48,7 @@ import org.json.JSONTokener; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.Bitmaps; +import org.telegram.messenger.BuildVars; import org.telegram.messenger.FileLoader; import org.telegram.messenger.FileLog; import org.telegram.messenger.ImageReceiver; @@ -78,7 +78,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; -@TargetApi(16) public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerDelegate, AudioManager.OnAudioFocusChangeListener { public interface WebPlayerViewDelegate { @@ -94,6 +93,9 @@ public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerD boolean checkInlinePermissons(); } + private static int lastContainerId = 4001; + private int fragment_container_id = lastContainerId++; + private VideoPlayer videoPlayer; private WebView webView; private String interfaceName; @@ -112,6 +114,9 @@ public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerD private String playVideoType; private String playAudioUrl; private String playAudioType; + private String currentYoutubeId; + + private boolean isStream; private boolean allowInlineAnimation = Build.VERSION.SDK_INT >= 21; @@ -167,9 +172,13 @@ public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerD private static final Pattern vimeoIdRegex = Pattern.compile("https?://(?:(?:www|(player))\\.)?vimeo(pro)?\\.com/(?!(?:channels|album)/[^/?#]+/?(?:$|[?#])|[^/]+/review/|ondemand/)(?:.*?/)?(?:(?:play_redirect_hls|moogaloop\\.swf)\\?clip_id=)?(?:videos?/)?([0-9]+)(?:/[\\da-f]+)?/?(?:[?&].*)?(?:[#].*)?$"); private static final Pattern coubIdRegex = Pattern.compile("(?:coub:|https?://(?:coub\\.com/(?:view|embed|coubs)/|c-cdn\\.coub\\.com/fb-player\\.swf\\?.*\\bcoub(?:ID|id)=))([\\da-z]+)"); private static final Pattern aparatIdRegex = Pattern.compile("^https?://(?:www\\.)?aparat\\.com/(?:v/|video/video/embed/videohash/)([a-zA-Z0-9]+)"); + private static final Pattern twitchClipIdRegex = Pattern.compile("https?://clips\\.twitch\\.tv/(?:[^/]+/)*([^/?#&]+)"); + private static final Pattern twitchStreamIdRegex = Pattern.compile("https?://(?:(?:www\\.)?twitch\\.tv/|player\\.twitch\\.tv/\\?.*?\\bchannel=)([^/#?]+)"); private static final Pattern aparatFileListPattern = Pattern.compile("fileList\\s*=\\s*JSON\\.parse\\('([^']+)'\\)"); + private static final Pattern twitchClipFilePattern = Pattern.compile("clipInfo\\s*=\\s*(\\{[^']+\\});"); + private static final Pattern stsPattern = Pattern.compile("\"sts\"\\s*:\\s*(\\d+)"); private static final Pattern jsPattern = Pattern.compile("\"assets\":.+?\"js\":\\s*(\"[^\"]+\")"); private static final Pattern sigPattern = Pattern.compile("\\.sig\\|\\|([a-zA-Z0-9$]+)\\("); @@ -431,6 +440,10 @@ public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerD } protected String downloadUrlContent(AsyncTask parentTask, String url) { + return downloadUrlContent(parentTask, url, null, true); + } + + protected String downloadUrlContent(AsyncTask parentTask, String url, HashMap headers, boolean tryGzip) { boolean canRetry = true; InputStream httpConnectionStream = null; boolean done = false; @@ -440,10 +453,17 @@ public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerD URL downloadUrl = new URL(url); httpConnection = downloadUrl.openConnection(); httpConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20150101 Firefox/47.0 (Chrome)"); - httpConnection.addRequestProperty("Accept-Encoding", "gzip, deflate"); + if (tryGzip) { + httpConnection.addRequestProperty("Accept-Encoding", "gzip, deflate"); + } httpConnection.addRequestProperty("Accept-Language", "en-us,en;q=0.5"); httpConnection.addRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); httpConnection.addRequestProperty("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7"); + if (headers != null) { + for (HashMap.Entry entry : headers.entrySet()) { + httpConnection.addRequestProperty(entry.getKey(), entry.getValue()); + } + } httpConnection.setConnectTimeout(5000); httpConnection.setReadTimeout(5000); if (httpConnection instanceof HttpURLConnection) { @@ -457,14 +477,25 @@ public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerD httpConnection = downloadUrl.openConnection(); httpConnection.setRequestProperty("Cookie", cookies); httpConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20150101 Firefox/47.0 (Chrome)"); - httpConnection.addRequestProperty("Accept-Encoding", "gzip, deflate"); + if (tryGzip) { + httpConnection.addRequestProperty("Accept-Encoding", "gzip, deflate"); + } httpConnection.addRequestProperty("Accept-Language", "en-us,en;q=0.5"); httpConnection.addRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); httpConnection.addRequestProperty("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7"); + if (headers != null) { + for (HashMap.Entry entry : headers.entrySet()) { + httpConnection.addRequestProperty(entry.getKey(), entry.getValue()); + } + } } } httpConnection.connect(); - httpConnectionStream = new GZIPInputStream(httpConnection.getInputStream()); + if (tryGzip) { + httpConnectionStream = new GZIPInputStream(httpConnection.getInputStream()); + } else { + httpConnectionStream = httpConnection.getInputStream(); + } } catch (Throwable e) { if (e instanceof SocketTimeoutException) { if (ConnectionsManager.isNetworkOnline()) { @@ -535,12 +566,12 @@ public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerD return done ? result.toString() : null; } - private class YoutubeVideoTask extends AsyncTask { + private class YoutubeVideoTask extends AsyncTask { private String videoId; private boolean canRetry = true; private Semaphore semaphore = new Semaphore(0); - private String[] result = new String[1]; + private String[] result = new String[2]; private String sig; public YoutubeVideoTask(String vid) { @@ -548,7 +579,7 @@ public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerD } @Override - protected String doInBackground(Void... voids) { + protected String[] doInBackground(Void... voids) { Matcher matcher; String embedCode = downloadUrlContent(this, "https://www.youtube.com/embed/" + videoId); if (isCancelled()) { @@ -568,6 +599,7 @@ public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerD params += "&sts="; } } + result[1] = "dash"; boolean encrypted = false; String extra[] = new String[] {"", "&el=info", "&el=embedded", "&el=detailpage", "&el=vevo"}; @@ -577,6 +609,8 @@ public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerD return null; } boolean exists = false; + String hls = null; + boolean isLive = false; if (videoInfo != null) { String args[] = videoInfo.split("&"); for (int a = 0; a < args.length; a++) { @@ -597,9 +631,33 @@ public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerD encrypted = true; } } + } else if (args[a].startsWith("hlsvp")) { + String args2[] = args[a].split("="); + if (args2.length == 2) { + try { + hls = URLDecoder.decode(args2[1], "UTF-8"); + } catch (Exception e) { + FileLog.e(e); + } + } + } else if (args[a].startsWith("livestream")) { + String args2[] = args[a].split("="); + if (args2.length == 2) { + if (args2[1].toLowerCase().equals("1")) { + isLive = true; + } + } } } } + if (isLive) { + if (hls == null || encrypted || hls.contains("/s/")) { + return null; + } else { + result[0] = hls; + result[1] = "hls"; + } + } if (exists) { break; } @@ -716,7 +774,7 @@ public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerD } } } - return isCancelled() || encrypted ? null : result[0]; + return isCancelled() || encrypted ? null : result; } private void onInterfaceResult(String value) { @@ -725,11 +783,14 @@ public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerD } @Override - protected void onPostExecute(String result) { + protected void onPostExecute(String[] result) { if (result != null) { initied = true; - playVideoType = "dash"; - playVideoUrl = result; + playVideoUrl = result[0]; + playVideoType = result[1]; + if (playVideoType.equals("hls")) { + isStream = true; + } if (isAutoplay) { preparePlayer(); } @@ -773,11 +834,8 @@ public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerD } else if (files.has("progressive")) { results[1] = "other"; JSONArray progressive = files.getJSONArray("progressive"); - for (int i = 0; i < progressive.length(); i++) { - JSONObject format = progressive.getJSONObject(i); - results[0] = format.getString("url"); - break; - } + JSONObject format = progressive.getJSONObject(0); + results[0] = format.getString("url"); } } catch (Exception e) { FileLog.e(e); @@ -858,6 +916,121 @@ public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerD } } + private class TwitchClipVideoTask extends AsyncTask { + + private String videoId; + private String currentUrl; + private boolean canRetry = true; + private String[] results = new String[2]; + + public TwitchClipVideoTask(String url, String vid) { + videoId = vid; + currentUrl = url; + } + + protected String doInBackground(Void... voids) { + String playerCode = downloadUrlContent(this, currentUrl, null, false); + if (isCancelled()) { + return null; + } + try { + Matcher filelist = twitchClipFilePattern.matcher(playerCode); + if (filelist.find()) { + String jsonCode = filelist.group(1); + JSONObject json = new JSONObject(jsonCode); + JSONArray array = json.getJSONArray("quality_options"); + JSONObject obj = array.getJSONObject(0); + results[0] = obj.getString("source"); + results[1] = "other"; + } + } catch (Exception e) { + FileLog.e(e); + } + return isCancelled() ? null : results[0]; + } + + @Override + protected void onPostExecute(String result) { + if (result != null) { + initied = true; + playVideoUrl = result; + playVideoType = results[1]; + if (isAutoplay) { + preparePlayer(); + } + showProgress(false, true); + controlsView.show(true, true); + } else if (!isCancelled()) { + onInitFailed(); + } + } + } + + private class TwitchStreamVideoTask extends AsyncTask { + + private String videoId; + private String currentUrl; + private boolean canRetry = true; + private String[] results = new String[2]; + + public TwitchStreamVideoTask(String url, String vid) { + videoId = vid; + currentUrl = url; + } + + protected String doInBackground(Void... voids) { + HashMap headers = new HashMap<>(); + headers.put("Client-ID", "jzkbprff40iqj646a697cyrvl0zt2m6"); + int idx; + if ((idx = videoId.indexOf('&')) > 0) { + videoId = videoId.substring(0, idx); + } + String streamCode = downloadUrlContent(this, String.format(Locale.US, "https://api.twitch.tv/kraken/streams/%s?stream_type=all", videoId), headers, false); + if (isCancelled()) { + return null; + } + try { + JSONObject obj = new JSONObject(streamCode); + JSONObject stream = obj.getJSONObject("stream"); + String accessTokenCode = downloadUrlContent(this, String.format(Locale.US, "https://api.twitch.tv/api/channels/%s/access_token", videoId), headers, false); + JSONObject accessToken = new JSONObject(accessTokenCode); + String sig = URLEncoder.encode(accessToken.getString("sig"), "UTF-8"); + String token = URLEncoder.encode(accessToken.getString("token"), "UTF-8"); + URLEncoder.encode("https://youtube.googleapis.com/v/" + videoId, "UTF-8"); + String params = "allow_source=true&" + + "allow_audio_only=true&" + + "allow_spectre=true&" + + "player=twitchweb&" + + "segment_preference=4&" + + "p=" + (int) (Math.random() * 10000000) + "&" + + "sig=" + sig + "&" + + "token=" + token; + String m3uUrl = String.format(Locale.US, "https://usher.ttvnw.net/api/channel/hls/%s.m3u8?%s", videoId, params); + results[0] = m3uUrl; + results[1] = "hls"; + } catch (Exception e) { + FileLog.e(e); + } + return isCancelled() ? null : results[0]; + } + + @Override + protected void onPostExecute(String result) { + if (result != null) { + initied = true; + playVideoUrl = result; + playVideoType = results[1]; + if (isAutoplay) { + preparePlayer(); + } + showProgress(false, true); + controlsView.show(true, true); + } else if (!isCancelled()) { + onInitFailed(); + } + } + } + private class CoubVideoTask extends AsyncTask { private String videoId; @@ -868,15 +1041,29 @@ public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerD videoId = vid; } + private String decodeUrl(String input) { + StringBuilder source = new StringBuilder(input); + for (int a = 0; a < source.length(); a++) { + char c = source.charAt(a); + char lower = Character.toLowerCase(c); + source.setCharAt(a, c == lower ? Character.toUpperCase(c) : lower); + } + try { + return new String(Base64.decode(source.toString(), Base64.DEFAULT), "UTF-8"); + } catch (Exception ignore) { + return null; + } + } + protected String doInBackground(Void... voids) { String playerCode = downloadUrlContent(this, String.format(Locale.US, "https://coub.com/api/v2/coubs/%s.json", videoId)); if (isCancelled()) { return null; } try { - JSONObject json = new JSONObject(playerCode); - String video = json.getString("file"); - String audio = json.getString("audio_file_url"); + JSONObject json = new JSONObject(playerCode).getJSONObject("file_versions").getJSONObject("mobile"); + String video = decodeUrl(json.getString("gifv")); + String audio = json.getJSONArray("audio").getString(0); if (video != null && audio != null) { results[0] = video; results[1] = "other"; @@ -1060,7 +1247,7 @@ public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerD } public void setDuration(int value) { - if (duration == value || value < 0) { + if (duration == value || value < 0 || isStream) { return; } duration = value; @@ -1078,7 +1265,7 @@ public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerD } public void setProgress(int value) { - if (progressPressed || value < 0) { + if (progressPressed || value < 0 || isStream) { return; } progress = value; @@ -1172,7 +1359,7 @@ public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerD int progressX = progressLineX + (duration != 0 ? (int) ((progressLineEndX - progressLineX) * (progress / (float) duration)) : 0); if (event.getAction() == MotionEvent.ACTION_DOWN) { - if (isVisible && !isInline) { + if (isVisible && !isInline && !isStream) { if (duration != 0) { int x = (int) event.getX(); int y = (int) event.getY(); @@ -1233,7 +1420,7 @@ public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerD imageReceiver.setAlpha(currentAlpha); imageReceiver.draw(canvas); } - if (videoPlayer.isPlayerPrepared()) { + if (videoPlayer.isPlayerPrepared() && !isStream) { int width = getMeasuredWidth(); int height = getMeasuredHeight(); if (!isInline) { @@ -1526,6 +1713,10 @@ public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerD } } + public String getYoutubeId() { + return currentYoutubeId; + } + @Override public void onStateChanged(boolean playWhenReady, int playbackState) { if (playbackState != ExoPlayer.STATE_BUFFERING) { @@ -1869,8 +2060,10 @@ public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerD String youtubeId = null; String vimeoId = null; String coubId = null; - String aparatId = null; + String twitchClipId = null; + String twitchStreamId = null; String mp4File = null; + String aparatId = null; seekToTime = -1; if (url != null) { if (url.endsWith(".mp4")) { @@ -1918,7 +2111,7 @@ public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerD FileLog.e(e); } } - if (youtubeId == null && vimeoId == null) { + if (vimeoId == null) { try { Matcher matcher = aparatIdRegex.matcher(url); String id = null; @@ -1932,7 +2125,35 @@ public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerD FileLog.e(e); } } - /*if (youtubeId == null && vimeoId == null) { + if (aparatId == null) { + try { + Matcher matcher = twitchClipIdRegex.matcher(url); + String id = null; + if (matcher.find()) { + id = matcher.group(1); + } + if (id != null) { + twitchClipId = id; + } + } catch (Exception e) { + FileLog.e(e); + } + } + if (twitchClipId == null) { + try { + Matcher matcher = twitchStreamIdRegex.matcher(url); + String id = null; + if (matcher.find()) { + id = matcher.group(1); + } + if (id != null) { + twitchStreamId = id; + } + } catch (Exception e) { + FileLog.e(e); + } + } + if (twitchStreamId == null) { try { Matcher matcher = coubIdRegex.matcher(url); String id = null; @@ -1945,7 +2166,7 @@ public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerD } catch (Exception e) { FileLog.e(e); } - }*/ + } } } @@ -1981,6 +2202,10 @@ public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerD } isLoading = true; controlsView.setProgress(0); + if (youtubeId != null && !BuildVars.DEBUG_PRIVATE_VERSION) { + currentYoutubeId = youtubeId; + youtubeId = null; + } if (mp4File != null) { initied = true; playVideoUrl = mp4File; @@ -2003,16 +2228,27 @@ public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerD CoubVideoTask task = new CoubVideoTask(coubId); task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null, null, null); currentTask = task; + isStream = true; } else if (aparatId != null) { AparatVideoTask task = new AparatVideoTask(aparatId); task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null, null, null); currentTask = task; + } else if (twitchClipId != null) { + TwitchClipVideoTask task = new TwitchClipVideoTask(url, twitchClipId); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null, null, null); + currentTask = task; + } else if (twitchStreamId != null) { + TwitchStreamVideoTask task = new TwitchStreamVideoTask(url, twitchStreamId); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null, null, null); + currentTask = task; + isStream = true; } controlsView.show(false, false); showProgress(true, false); } - if (youtubeId != null || vimeoId != null || coubId != null || aparatId != null || mp4File != null) { + if (youtubeId != null || vimeoId != null || coubId != null || aparatId != null || mp4File != null || twitchClipId != null || twitchStreamId != null) { + controlsView.setVisibility(VISIBLE); return true; } controlsView.setVisibility(GONE); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/voip/VoIPHelper.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/voip/VoIPHelper.java index fb5c5e5d0..72950c691 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/voip/VoIPHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/voip/VoIPHelper.java @@ -12,7 +12,6 @@ import android.net.Uri; import android.os.Build; import android.provider.Settings; import android.text.InputType; -import android.text.TextUtils; import android.util.TypedValue; import android.view.Gravity; import android.view.View; @@ -25,9 +24,7 @@ import android.widget.Toast; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.ContactsController; -import org.telegram.messenger.FileLoader; import org.telegram.messenger.LocaleController; -import org.telegram.messenger.MediaController; import org.telegram.messenger.MessagesController; import org.telegram.messenger.R; import org.telegram.messenger.SendMessagesHelper; @@ -49,7 +46,7 @@ import java.util.Set; public class VoIPHelper{ - private static long lastCallRequestTime=0; + public static long lastCallTime=0; private static final int VOIP_SUPPORT_ID=4244000; @@ -131,9 +128,9 @@ public class VoIPHelper{ if (activity == null || user==null) { return; } - if(System.currentTimeMillis()-lastCallRequestTime<1000) + if(System.currentTimeMillis()-lastCallTime<2000) return; - lastCallRequestTime=System.currentTimeMillis(); + lastCallTime=System.currentTimeMillis(); Intent intent = new Intent(activity, VoIPService.class); intent.putExtra("user_id", user.id); intent.putExtra("is_outgoing", true); @@ -325,4 +322,15 @@ public class VoIPHelper{ } }); } + + public static void upgradeP2pSetting(){ + SharedPreferences prefs=ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Context.MODE_PRIVATE); + if(prefs.contains("calls_p2p")){ + SharedPreferences.Editor e=prefs.edit(); + if(!prefs.getBoolean("calls_p2p", true)){ + e.putInt("calls_p2p_new", 2); + } + e.remove("calls_p2p").apply(); + } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ContactAddActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ContactAddActivity.java index 51e68b85e..718c7970d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ContactAddActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ContactAddActivity.java @@ -21,7 +21,6 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.inputmethod.EditorInfo; -import android.widget.EditText; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.ScrollView; @@ -43,13 +42,14 @@ import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.Components.EditTextBoldCursor; import org.telegram.ui.Components.LayoutHelper; public class ContactAddActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { private View doneButton; - private EditText firstNameField; - private EditText lastNameField; + private EditTextBoldCursor firstNameField; + private EditTextBoldCursor lastNameField; private BackupImageView avatarImage; private TextView nameTextView; private TextView onlineTextView; @@ -154,7 +154,7 @@ public class ContactAddActivity extends BaseFragment implements NotificationCent onlineTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT)); frameLayout.addView(onlineTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 0 : 80, 32, LocaleController.isRTL ? 80 : 0, 0)); - firstNameField = new EditText(context); + firstNameField = new EditTextBoldCursor(context); firstNameField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); firstNameField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); firstNameField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); @@ -166,7 +166,9 @@ public class ContactAddActivity extends BaseFragment implements NotificationCent firstNameField.setInputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT); firstNameField.setImeOptions(EditorInfo.IME_ACTION_NEXT); firstNameField.setHint(LocaleController.getString("FirstName", R.string.FirstName)); - AndroidUtilities.clearCursorDrawable(firstNameField); + firstNameField.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + firstNameField.setCursorSize(AndroidUtilities.dp(20)); + firstNameField.setCursorWidth(1.5f); linearLayout.addView(firstNameField, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36, 24, 24, 24, 0)); firstNameField.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override @@ -180,7 +182,7 @@ public class ContactAddActivity extends BaseFragment implements NotificationCent } }); - lastNameField = new EditText(context); + lastNameField = new EditTextBoldCursor(context); lastNameField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); lastNameField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); lastNameField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); @@ -192,7 +194,9 @@ public class ContactAddActivity extends BaseFragment implements NotificationCent lastNameField.setInputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT); lastNameField.setImeOptions(EditorInfo.IME_ACTION_DONE); lastNameField.setHint(LocaleController.getString("LastName", R.string.LastName)); - AndroidUtilities.clearCursorDrawable(lastNameField); + lastNameField.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + lastNameField.setCursorSize(AndroidUtilities.dp(20)); + lastNameField.setCursorWidth(1.5f); linearLayout.addView(lastNameField, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36, 24, 16, 24, 0)); lastNameField.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override @@ -300,7 +304,7 @@ public class ContactAddActivity extends BaseFragment implements NotificationCent new ThemeDescription(lastNameField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), new ThemeDescription(lastNameField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), - new ThemeDescription(null, 0, null, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, сellDelegate, Theme.key_avatar_text), + new ThemeDescription(null, 0, null, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, сellDelegate, Theme.key_avatar_text), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ContactsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ContactsActivity.java index 4d803af38..b1c6ed039 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ContactsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ContactsActivity.java @@ -24,6 +24,7 @@ import android.os.Bundle; import android.text.Editable; import android.text.InputType; import android.text.TextWatcher; +import android.util.TypedValue; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; @@ -297,14 +298,7 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter if ((!onlyUsers || chat_id != 0) && section == 0) { if (needPhonebook) { if (row == 0) { - try { - Intent intent = new Intent(Intent.ACTION_SEND); - intent.setType("text/plain"); - intent.putExtra(Intent.EXTRA_TEXT, ContactsController.getInstance().getInviteText()); - getParentActivity().startActivityForResult(Intent.createChooser(intent, LocaleController.getString("InviteFriends", R.string.InviteFriends)), 500); - } catch (Exception e) { - FileLog.e(e); - } + presentFragment(new InviteContactsActivity()); } } else if (chat_id != 0) { if (row == 0) { @@ -378,7 +372,7 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter public void onClick(DialogInterface dialogInterface, int i) { try { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.fromParts("sms", arg1, null)); - intent.putExtra("sms_body", LocaleController.getString("InviteText", R.string.InviteText)); + intent.putExtra("sms_body", ContactsController.getInstance().getInviteText(1)); getParentActivity().startActivityForResult(intent, 500); } catch (Exception e) { FileLog.e(e); @@ -430,8 +424,9 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter if (!user.bot && needForwardCount) { message = String.format("%s\n\n%s", message, LocaleController.getString("AddToTheGroupForwardCount", R.string.AddToTheGroupForwardCount)); editText = new EditText(getParentActivity()); - editText.setTextSize(18); + editText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); editText.setText("50"); + editText.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); editText.setGravity(Gravity.CENTER); editText.setInputType(InputType.TYPE_CLASS_NUMBER); editText.setImeOptions(EditorInfo.IME_ACTION_DONE); @@ -489,7 +484,8 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter if (layoutParams instanceof FrameLayout.LayoutParams) { ((FrameLayout.LayoutParams) layoutParams).gravity = Gravity.CENTER_HORIZONTAL; } - layoutParams.rightMargin = layoutParams.leftMargin = AndroidUtilities.dp(10); + layoutParams.rightMargin = layoutParams.leftMargin = AndroidUtilities.dp(24); + layoutParams.height = AndroidUtilities.dp(36); editText.setLayoutParams(layoutParams); } editText.setSelection(editText.getText().length()); @@ -541,15 +537,13 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter @TargetApi(Build.VERSION_CODES.M) private void askForPermissons() { Activity activity = getParentActivity(); - if (activity == null) { + if (activity == null || activity.checkSelfPermission(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) { return; } ArrayList permissons = new ArrayList<>(); - if (activity.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { - permissons.add(Manifest.permission.READ_CONTACTS); - permissons.add(Manifest.permission.WRITE_CONTACTS); - permissons.add(Manifest.permission.GET_ACCOUNTS); - } + permissons.add(Manifest.permission.READ_CONTACTS); + permissons.add(Manifest.permission.WRITE_CONTACTS); + permissons.add(Manifest.permission.GET_ACCOUNTS); String[] items = permissons.toArray(new String[permissons.size()]); activity.requestPermissions(items, 1); } @@ -563,7 +557,7 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter } switch (permissions[a]) { case Manifest.permission.READ_CONTACTS: - ContactsController.getInstance().readContacts(); + ContactsController.getInstance().forceImportContacts(); break; } } @@ -667,7 +661,7 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"statusColor"}, null, null, сellDelegate, Theme.key_windowBackgroundWhiteGrayText), new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"statusOnlineColor"}, null, null, сellDelegate, Theme.key_windowBackgroundWhiteBlueText), - new ThemeDescription(listView, 0, new Class[]{UserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, null, Theme.key_avatar_text), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/DataAutoDownloadActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/DataAutoDownloadActivity.java new file mode 100644 index 000000000..a673fee16 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/DataAutoDownloadActivity.java @@ -0,0 +1,474 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui; + +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MediaController; +import org.telegram.messenger.R; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; +import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Cells.HeaderCell; +import org.telegram.ui.Cells.MaxFileSizeCell; +import org.telegram.ui.Cells.ShadowSectionCell; +import org.telegram.ui.Cells.TextCheckBoxCell; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; + +public class DataAutoDownloadActivity extends BaseFragment { + + private ListAdapter listAdapter; + private RecyclerListView listView; + + private int currentType; + + private int mobileDataDownloadMask; + private int wifiDownloadMask; + private int roamingDownloadMask; + private int mobileDataPrivateDownloadMask; + private int wifiPrivateDownloadMask; + private int roamingPrivateDownloadMask; + private int mobileDataGroupDownloadMask; + private int wifiGroupDownloadMask; + private int roamingGroupDownloadMask; + private int mobileDataChannelDownloadMask; + private int wifiChannelDownloadMask; + private int roamingChannelDownloadMask; + private int mobileMaxSize; + private int wifiMaxSize; + private int roamingMaxSize; + + private int mobileSectionRow; + private int mContactsRow; + private int mPrivateRow; + private int mGroupRow; + private int mChannelsRow; + private int mSizeRow; + private int mobileSection2Row; + private int wifiSectionRow; + private int wContactsRow; + private int wPrivateRow; + private int wGroupRow; + private int wChannelsRow; + private int wSizeRow; + private int wifiSection2Row; + private int roamingSectionRow; + private int rContactsRow; + private int rPrivateRow; + private int rGroupRow; + private int rChannelsRow; + private int rSizeRow; + private int roamingSection2Row; + private int rowCount; + + private long maxSize; + + private final static int done_button = 1; + + public DataAutoDownloadActivity(int type) { + super(); + currentType = type; + + if (currentType == MediaController.AUTODOWNLOAD_MASK_VIDEOMESSAGE) { + maxSize = 8 * 1024 * 1024; + } else if (currentType == MediaController.AUTODOWNLOAD_MASK_GIF) { + maxSize = 10 * 1024 * 1024; + } else { + maxSize = 1536 * 1024 * 1024; + } + + mobileDataDownloadMask = MediaController.getInstance().mobileDataDownloadMask[0]; + mobileDataPrivateDownloadMask = MediaController.getInstance().mobileDataDownloadMask[1]; + mobileDataGroupDownloadMask = MediaController.getInstance().mobileDataDownloadMask[2]; + mobileDataChannelDownloadMask = MediaController.getInstance().mobileDataDownloadMask[3]; + wifiDownloadMask = MediaController.getInstance().wifiDownloadMask[0]; + wifiPrivateDownloadMask = MediaController.getInstance().wifiDownloadMask[1]; + wifiGroupDownloadMask = MediaController.getInstance().wifiDownloadMask[2]; + wifiChannelDownloadMask = MediaController.getInstance().wifiDownloadMask[3]; + roamingDownloadMask = MediaController.getInstance().roamingDownloadMask[0]; + roamingPrivateDownloadMask = MediaController.getInstance().roamingDownloadMask[1]; + roamingGroupDownloadMask = MediaController.getInstance().roamingDownloadMask[2]; + roamingChannelDownloadMask = MediaController.getInstance().roamingDownloadMask[2]; + + mobileMaxSize = MediaController.getInstance().mobileMaxFileSize[MediaController.maskToIndex(currentType)]; + wifiMaxSize = MediaController.getInstance().wifiMaxFileSize[MediaController.maskToIndex(currentType)]; + roamingMaxSize = MediaController.getInstance().roamingMaxFileSize[MediaController.maskToIndex(currentType)]; + } + + @Override + public boolean onFragmentCreate() { + super.onFragmentCreate(); + + rowCount = 0; + mobileSectionRow = rowCount++; + mContactsRow = rowCount++; + mPrivateRow = rowCount++; + mGroupRow = rowCount++; + mChannelsRow = rowCount++; + if (currentType != MediaController.AUTODOWNLOAD_MASK_PHOTO) { + mSizeRow = rowCount++; + } else { + mSizeRow = -1; + } + mobileSection2Row = rowCount++; + wifiSectionRow = rowCount++; + wContactsRow = rowCount++; + wPrivateRow = rowCount++; + wGroupRow = rowCount++; + wChannelsRow = rowCount++; + if (currentType != MediaController.AUTODOWNLOAD_MASK_PHOTO) { + wSizeRow = rowCount++; + } else { + wSizeRow = -1; + } + wifiSection2Row = rowCount++; + roamingSectionRow = rowCount++; + rContactsRow = rowCount++; + rPrivateRow = rowCount++; + rGroupRow = rowCount++; + rChannelsRow = rowCount++; + if (currentType != MediaController.AUTODOWNLOAD_MASK_PHOTO) { + rSizeRow = rowCount++; + } else { + rSizeRow = -1; + } + roamingSection2Row = rowCount++; + + return true; + } + + @Override + public View createView(Context context) { + actionBar.setBackButtonImage(R.drawable.ic_ab_back); + if (currentType == MediaController.AUTODOWNLOAD_MASK_PHOTO) { + actionBar.setTitle(LocaleController.getString("LocalPhotoCache", R.string.LocalPhotoCache)); + } else if (currentType == MediaController.AUTODOWNLOAD_MASK_AUDIO) { + actionBar.setTitle(LocaleController.getString("AudioAutodownload", R.string.AudioAutodownload)); + } else if (currentType == MediaController.AUTODOWNLOAD_MASK_VIDEOMESSAGE) { + actionBar.setTitle(LocaleController.getString("VideoMessagesAutodownload", R.string.VideoMessagesAutodownload)); + } else if (currentType == MediaController.AUTODOWNLOAD_MASK_VIDEO) { + actionBar.setTitle(LocaleController.getString("LocalVideoCache", R.string.LocalVideoCache)); + } else if (currentType == MediaController.AUTODOWNLOAD_MASK_DOCUMENT) { + actionBar.setTitle(LocaleController.getString("FilesDataUsage", R.string.FilesDataUsage)); + } else if (currentType == MediaController.AUTODOWNLOAD_MASK_MUSIC) { + actionBar.setTitle(LocaleController.getString("AttachMusic", R.string.AttachMusic)); + } else if (currentType == MediaController.AUTODOWNLOAD_MASK_GIF) { + actionBar.setTitle(LocaleController.getString("LocalGifCache", R.string.LocalGifCache)); + } + if (AndroidUtilities.isTablet()) { + actionBar.setOccupyStatusBar(false); + } + actionBar.setAllowOverlayTitle(true); + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { + @Override + public void onItemClick(int id) { + if (id == -1) { + finishFragment(); + } else if (id == done_button) { + MediaController.getInstance().mobileDataDownloadMask[0] = mobileDataDownloadMask; + MediaController.getInstance().mobileDataDownloadMask[1] = mobileDataPrivateDownloadMask; + MediaController.getInstance().mobileDataDownloadMask[2] = mobileDataGroupDownloadMask; + MediaController.getInstance().mobileDataDownloadMask[3] = mobileDataChannelDownloadMask; + MediaController.getInstance().wifiDownloadMask[0] = wifiDownloadMask; + MediaController.getInstance().wifiDownloadMask[1] = wifiPrivateDownloadMask; + MediaController.getInstance().wifiDownloadMask[2] = wifiGroupDownloadMask; + MediaController.getInstance().wifiDownloadMask[3] = wifiChannelDownloadMask; + MediaController.getInstance().roamingDownloadMask[0] = roamingDownloadMask; + MediaController.getInstance().roamingDownloadMask[1] = roamingPrivateDownloadMask; + MediaController.getInstance().roamingDownloadMask[2] = roamingGroupDownloadMask; + MediaController.getInstance().roamingDownloadMask[3] = roamingChannelDownloadMask; + MediaController.getInstance().mobileMaxFileSize[MediaController.maskToIndex(currentType)] = mobileMaxSize; + MediaController.getInstance().wifiMaxFileSize[MediaController.maskToIndex(currentType)] = wifiMaxSize; + MediaController.getInstance().roamingMaxFileSize[MediaController.maskToIndex(currentType)] = roamingMaxSize; + SharedPreferences.Editor editor = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE).edit(); + for (int a = 0; a < 4; a++) { + editor.putInt("mobileDataDownloadMask" + (a != 0 ? a : ""), MediaController.getInstance().mobileDataDownloadMask[a]); + editor.putInt("wifiDownloadMask" + (a != 0 ? a : ""), MediaController.getInstance().wifiDownloadMask[a]); + editor.putInt("roamingDownloadMask" + (a != 0 ? a : ""), MediaController.getInstance().roamingDownloadMask[a]); + } + editor.putInt("mobileMaxDownloadSize" + MediaController.maskToIndex(currentType), mobileMaxSize); + editor.putInt("wifiMaxDownloadSize" + MediaController.maskToIndex(currentType), wifiMaxSize); + editor.putInt("roamingMaxDownloadSize" + MediaController.maskToIndex(currentType), roamingMaxSize); + editor.commit(); + + MediaController.getInstance().checkAutodownloadSettings(); + finishFragment(); + } + } + }); + + ActionBarMenu menu = actionBar.createMenu(); + menu.addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56)); + + listAdapter = new ListAdapter(context); + + fragmentView = new FrameLayout(context); + fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); + FrameLayout frameLayout = (FrameLayout) fragmentView; + + listView = new RecyclerListView(context); + listView.setVerticalScrollBarEnabled(false); + listView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); + frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); + listView.setAdapter(listAdapter); + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { + @Override + public void onItemClick(View view, final int position) { + if (!(view instanceof TextCheckBoxCell)) { + return; + } + int mask = getMaskForRow(position); + TextCheckBoxCell textCell = (TextCheckBoxCell) view; + boolean isChecked = !textCell.isChecked(); + if (isChecked) { + mask |= currentType; + } else { + mask &=~ currentType; + } + setMaskForRow(position, mask); + textCell.setChecked(isChecked); + } + }); + + frameLayout.addView(actionBar); + + return fragmentView; + } + + @Override + public void onResume() { + super.onResume(); + if (listAdapter != null) { + listAdapter.notifyDataSetChanged(); + } + } + + private int getMaskForRow(int position) { + if (position == mContactsRow) { + return mobileDataDownloadMask; + } else if (position == mPrivateRow) { + return mobileDataPrivateDownloadMask; + } else if (position == mGroupRow) { + return mobileDataGroupDownloadMask; + } else if (position == mChannelsRow) { + return mobileDataChannelDownloadMask; + } else if (position == wContactsRow) { + return wifiDownloadMask; + } else if (position == wPrivateRow) { + return wifiPrivateDownloadMask; + } else if (position == wGroupRow) { + return wifiGroupDownloadMask; + } else if (position == wChannelsRow) { + return wifiChannelDownloadMask; + } else if (position == rContactsRow) { + return roamingDownloadMask; + } else if (position == rPrivateRow) { + return roamingPrivateDownloadMask; + } else if (position == rGroupRow) { + return roamingGroupDownloadMask; + } else if (position == rChannelsRow) { + return roamingChannelDownloadMask; + } + return 0; + } + + private void setMaskForRow(int position, int mask) { + if (position == mContactsRow) { + mobileDataDownloadMask = mask; + } else if (position == mPrivateRow) { + mobileDataPrivateDownloadMask = mask; + } else if (position == mGroupRow) { + mobileDataGroupDownloadMask = mask; + } else if (position == mChannelsRow) { + mobileDataChannelDownloadMask = mask; + } else if (position == wContactsRow) { + wifiDownloadMask = mask; + } else if (position == wPrivateRow) { + wifiPrivateDownloadMask = mask; + } else if (position == wGroupRow) { + wifiGroupDownloadMask = mask; + } else if (position == wChannelsRow) { + wifiChannelDownloadMask = mask; + } else if (position == rContactsRow) { + roamingDownloadMask = mask; + } else if (position == rPrivateRow) { + roamingPrivateDownloadMask = mask; + } else if (position == rGroupRow) { + roamingGroupDownloadMask = mask; + } else if (position == rChannelsRow) { + roamingChannelDownloadMask = mask; + } + } + + private class ListAdapter extends RecyclerListView.SelectionAdapter { + + private Context mContext; + + public ListAdapter(Context context) { + mContext = context; + } + + @Override + public int getItemCount() { + return rowCount; + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case 0: { + if (position == mobileSection2Row || position == wifiSection2Row) { + holder.itemView.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + } else { + holder.itemView.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + } + break; + } + case 1: { + TextCheckBoxCell textCell = (TextCheckBoxCell) holder.itemView; + if (position == mContactsRow || position == wContactsRow || position == rContactsRow) { + textCell.setTextAndCheck(LocaleController.getString("AutodownloadContacts", R.string.AutodownloadContacts), (getMaskForRow(position) & currentType) != 0, true); + } else if (position == mPrivateRow || position == wPrivateRow || position == rPrivateRow) { + textCell.setTextAndCheck(LocaleController.getString("AutodownloadPrivateChats", R.string.AutodownloadPrivateChats), (getMaskForRow(position) & currentType) != 0, true); + } else if (position == mChannelsRow || position == wChannelsRow || position == rChannelsRow) { + textCell.setTextAndCheck(LocaleController.getString("AutodownloadChannels", R.string.AutodownloadChannels), (getMaskForRow(position) & currentType) != 0, mSizeRow != -1); + } else if (position == mGroupRow || position == wGroupRow || position == rGroupRow) { + textCell.setTextAndCheck(LocaleController.getString("AutodownloadGroupChats", R.string.AutodownloadGroupChats), (getMaskForRow(position) & currentType) != 0, true); + } + break; + } + case 2: { + HeaderCell headerCell = (HeaderCell) holder.itemView; + if (position == mobileSectionRow) { + headerCell.setText(LocaleController.getString("WhenUsingMobileData", R.string.WhenUsingMobileData)); + } else if (position == wifiSectionRow) { + headerCell.setText(LocaleController.getString("WhenConnectedOnWiFi", R.string.WhenConnectedOnWiFi)); + } else if (position == roamingSectionRow) { + headerCell.setText(LocaleController.getString("WhenRoaming", R.string.WhenRoaming)); + } + break; + } + case 3: { + MaxFileSizeCell cell = (MaxFileSizeCell) holder.itemView; + if (position == mSizeRow) { + cell.setSize(mobileMaxSize, maxSize); + cell.setTag(0); + } else if (position == wSizeRow) { + cell.setSize(wifiMaxSize, maxSize); + cell.setTag(1); + } else if (position == rSizeRow) { + cell.setSize(roamingMaxSize, maxSize); + cell.setTag(2); + } + break; + } + } + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + int position = holder.getAdapterPosition(); + return position != mSizeRow && position != rSizeRow && position != wSizeRow && position != mobileSectionRow && position != wifiSectionRow && position != roamingSectionRow && position != mobileSection2Row && position != wifiSection2Row && position != roamingSection2Row; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = null; + switch (viewType) { + case 0: + view = new ShadowSectionCell(mContext); + break; + case 1: + view = new TextCheckBoxCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 2: + view = new HeaderCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 3: + view = new MaxFileSizeCell(mContext) { + @Override + protected void didChangedSizeValue(int value) { + Integer tag = (Integer) getTag(); + if (tag == 0) { + mobileMaxSize = value; + } else if (tag == 1) { + wifiMaxSize = value; + } else if (tag == 2) { + roamingMaxSize = value; + } + } + }; + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + } + view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); + return new RecyclerListView.Holder(view); + } + + @Override + public int getItemViewType(int position) { + if (position == mobileSection2Row || position == wifiSection2Row || position == roamingSection2Row) { + return 0; + } else if (position == mobileSectionRow || position == wifiSectionRow || position == roamingSectionRow) { + return 2; + } else if (position == wSizeRow || position == mSizeRow || position == rSizeRow) { + return 3; + } else { + return 1; + } + } + } + + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{TextCheckBoxCell.class, MaxFileSizeCell.class, HeaderCell.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ShadowSectionCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + + new ThemeDescription(listView, 0, new Class[]{MaxFileSizeCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{MaxFileSizeCell.class}, new String[]{"sizeTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + + new ThemeDescription(listView, 0, new Class[]{TextCheckBoxCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{TextCheckBoxCell.class}, null, null, null, Theme.key_checkboxSquareUnchecked), + new ThemeDescription(listView, 0, new Class[]{TextCheckBoxCell.class}, null, null, null, Theme.key_checkboxSquareDisabled), + new ThemeDescription(listView, 0, new Class[]{TextCheckBoxCell.class}, null, null, null, Theme.key_checkboxSquareBackground), + new ThemeDescription(listView, 0, new Class[]{TextCheckBoxCell.class}, null, null, null, Theme.key_checkboxSquareCheck), + + new ThemeDescription(listView, 0, new Class[]{HeaderCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader), + }; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/DataSettingsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/DataSettingsActivity.java index bc62441a0..0aa15f9df 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/DataSettingsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/DataSettingsActivity.java @@ -8,6 +8,9 @@ package org.telegram.ui; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; import android.app.Activity; import android.app.Dialog; import android.content.Context; @@ -17,41 +20,46 @@ import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; -import android.widget.LinearLayout; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; -import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MediaController; -import org.telegram.messenger.MessagesController; import org.telegram.messenger.R; import org.telegram.messenger.support.widget.LinearLayoutManager; import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.messenger.voip.VoIPController; import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; -import org.telegram.ui.ActionBar.BottomSheet; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.ActionBar.ThemeDescription; -import org.telegram.ui.Cells.CheckBoxCell; import org.telegram.ui.Cells.HeaderCell; import org.telegram.ui.Cells.ShadowSectionCell; -import org.telegram.ui.Cells.TextDetailSettingsCell; +import org.telegram.ui.Cells.TextCheckCell; import org.telegram.ui.Cells.TextSettingsCell; import org.telegram.ui.Components.AlertsCreator; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.RecyclerListView; +import java.util.ArrayList; + public class DataSettingsActivity extends BaseFragment { private ListAdapter listAdapter; private RecyclerListView listView; + private AnimatorSet animatorSet; private int mediaDownloadSectionRow; - private int mobileDownloadRow; - private int wifiDownloadRow; - private int roamingDownloadRow; + private int autoDownloadMediaRow; + private int photosRow; + private int voiceMessagesRow; + private int videoMessagesRow; + private int videosRow; + private int filesRow; + private int musicRow; + private int gifsRow; + private int resetDownloadRow; private int mediaDownloadSection2Row; private int usageSectionRow; private int storageUsageRow; @@ -72,26 +80,26 @@ public class DataSettingsActivity extends BaseFragment { super.onFragmentCreate(); rowCount = 0; - mediaDownloadSectionRow = rowCount++; - mobileDownloadRow = rowCount++; - wifiDownloadRow = rowCount++; - roamingDownloadRow = rowCount++; - mediaDownloadSection2Row = rowCount++; usageSectionRow = rowCount++; storageUsageRow = rowCount++; mobileUsageRow = rowCount++; wifiUsageRow = rowCount++; roamingUsageRow = rowCount++; usageSection2Row = rowCount++; - if (MessagesController.getInstance().callsEnabled) { - callsSectionRow = rowCount++; - useLessDataForCallsRow = rowCount++; - callsSection2Row = rowCount++; - } else { - callsSection2Row = -1; - callsSectionRow = -1; - useLessDataForCallsRow = -1; - } + mediaDownloadSectionRow = rowCount++; + autoDownloadMediaRow = rowCount++; + photosRow = rowCount++; + voiceMessagesRow = rowCount++; + videoMessagesRow = rowCount++; + videosRow = rowCount++; + filesRow = rowCount++; + musicRow = rowCount++; + gifsRow = rowCount++; + resetDownloadRow = rowCount++; + mediaDownloadSection2Row = rowCount++; + callsSectionRow = rowCount++; + useLessDataForCallsRow = rowCount++; + callsSection2Row = rowCount++; proxySectionRow = rowCount++; proxyRow = rowCount++; proxySection2Row = rowCount++; @@ -130,120 +138,79 @@ public class DataSettingsActivity extends BaseFragment { listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override public void onItemClick(View view, final int position) { - if (position == wifiDownloadRow || position == mobileDownloadRow || position == roamingDownloadRow) { + if (position == photosRow || position == voiceMessagesRow || position == videoMessagesRow || position == videosRow || position == filesRow || position == musicRow || position == gifsRow) { + if (!MediaController.getInstance().globalAutodownloadEnabled) { + return; + } + if (position == photosRow) { + presentFragment(new DataAutoDownloadActivity(MediaController.AUTODOWNLOAD_MASK_PHOTO)); + } else if (position == voiceMessagesRow) { + presentFragment(new DataAutoDownloadActivity(MediaController.AUTODOWNLOAD_MASK_AUDIO)); + } else if (position == videoMessagesRow) { + presentFragment(new DataAutoDownloadActivity(MediaController.AUTODOWNLOAD_MASK_VIDEOMESSAGE)); + } else if (position == videosRow) { + presentFragment(new DataAutoDownloadActivity(MediaController.AUTODOWNLOAD_MASK_VIDEO)); + } else if (position == filesRow) { + presentFragment(new DataAutoDownloadActivity(MediaController.AUTODOWNLOAD_MASK_DOCUMENT)); + } else if (position == musicRow) { + presentFragment(new DataAutoDownloadActivity(MediaController.AUTODOWNLOAD_MASK_MUSIC)); + } else if (position == gifsRow) { + presentFragment(new DataAutoDownloadActivity(MediaController.AUTODOWNLOAD_MASK_GIF)); + } + } else if (position == resetDownloadRow) { if (getParentActivity() == null) { return; } - final boolean maskValues[] = new boolean[7]; - BottomSheet.Builder builder = new BottomSheet.Builder(getParentActivity()); - - int mask = 0; - if (position == mobileDownloadRow) { - mask = MediaController.getInstance().mobileDataDownloadMask; - } else if (position == wifiDownloadRow) { - mask = MediaController.getInstance().wifiDownloadMask; - } else if (position == roamingDownloadRow) { - mask = MediaController.getInstance().roamingDownloadMask; - } - - builder.setApplyTopPadding(false); - builder.setApplyBottomPadding(false); - LinearLayout linearLayout = new LinearLayout(getParentActivity()); - linearLayout.setOrientation(LinearLayout.VERTICAL); - for (int a = 0; a < 7; a++) { - String name = null; - if (a == 0) { - maskValues[a] = (mask & MediaController.AUTODOWNLOAD_MASK_PHOTO) != 0; - name = LocaleController.getString("LocalPhotoCache", R.string.LocalPhotoCache); - } else if (a == 1) { - maskValues[a] = (mask & MediaController.AUTODOWNLOAD_MASK_AUDIO) != 0; - name = LocaleController.getString("AudioAutodownload", R.string.AudioAutodownload); - } else if (a == 2) { - maskValues[a] = (mask & MediaController.AUTODOWNLOAD_MASK_VIDEOMESSAGE) != 0; - name = LocaleController.getString("VideoMessagesAutodownload", R.string.VideoMessagesAutodownload); - } else if (a == 3) { - maskValues[a] = (mask & MediaController.AUTODOWNLOAD_MASK_VIDEO) != 0; - name = LocaleController.getString("LocalVideoCache", R.string.LocalVideoCache); - } else if (a == 4) { - maskValues[a] = (mask & MediaController.AUTODOWNLOAD_MASK_DOCUMENT) != 0; - name = LocaleController.getString("FilesDataUsage", R.string.FilesDataUsage); - } else if (a == 5) { - maskValues[a] = (mask & MediaController.AUTODOWNLOAD_MASK_MUSIC) != 0; - name = LocaleController.getString("AttachMusic", R.string.AttachMusic); - } else if (a == 6) { - maskValues[a] = (mask & MediaController.AUTODOWNLOAD_MASK_GIF) != 0; - name = LocaleController.getString("LocalGifCache", R.string.LocalGifCache); - } - CheckBoxCell checkBoxCell = new CheckBoxCell(getParentActivity(), true); - checkBoxCell.setTag(a); - checkBoxCell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); - linearLayout.addView(checkBoxCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48)); - checkBoxCell.setText(name, "", maskValues[a], true); - checkBoxCell.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); - checkBoxCell.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - CheckBoxCell cell = (CheckBoxCell) v; - int num = (Integer) cell.getTag(); - maskValues[num] = !maskValues[num]; - cell.setChecked(maskValues[num], true); - } - }); - } - BottomSheet.BottomSheetCell cell = new BottomSheet.BottomSheetCell(getParentActivity(), 1); - cell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); - cell.setTextAndIcon(LocaleController.getString("Save", R.string.Save).toUpperCase(), 0); - cell.setTextColor(Theme.getColor(Theme.key_dialogTextBlue2)); - cell.setOnClickListener(new View.OnClickListener() { + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setMessage(LocaleController.getString("ResetAutomaticMediaDownloadAlert", R.string.ResetAutomaticMediaDownloadAlert)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { @Override - public void onClick(View v) { - try { - if (visibleDialog != null) { - visibleDialog.dismiss(); - } - } catch (Exception e) { - FileLog.e(e); - } - int newMask = 0; - for (int a = 0; a < 7; a++) { - if (maskValues[a]) { - if (a == 0) { - newMask |= MediaController.AUTODOWNLOAD_MASK_PHOTO; - } else if (a == 1) { - newMask |= MediaController.AUTODOWNLOAD_MASK_AUDIO; - } else if (a == 2) { - newMask |= MediaController.AUTODOWNLOAD_MASK_VIDEOMESSAGE; - } else if (a == 3) { - newMask |= MediaController.AUTODOWNLOAD_MASK_VIDEO; - } else if (a == 4) { - newMask |= MediaController.AUTODOWNLOAD_MASK_DOCUMENT; - } else if (a == 5) { - newMask |= MediaController.AUTODOWNLOAD_MASK_MUSIC; - } else if (a == 6) { - newMask |= MediaController.AUTODOWNLOAD_MASK_GIF; - } - } - } + public void onClick(DialogInterface dialogInterface, int i) { SharedPreferences.Editor editor = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE).edit(); - if (position == mobileDownloadRow) { - editor.putInt("mobileDataDownloadMask", newMask); - MediaController.getInstance().mobileDataDownloadMask = newMask; - } else if (position == wifiDownloadRow) { - editor.putInt("wifiDownloadMask", newMask); - MediaController.getInstance().wifiDownloadMask = newMask; - } else if (position == roamingDownloadRow) { - editor.putInt("roamingDownloadMask", newMask); - MediaController.getInstance().roamingDownloadMask = newMask; + MediaController mediaController = MediaController.getInstance(); + for (int a = 0; a < 4; a++) { + mediaController.mobileDataDownloadMask[a] = MediaController.AUTODOWNLOAD_MASK_PHOTO | MediaController.AUTODOWNLOAD_MASK_AUDIO | MediaController.AUTODOWNLOAD_MASK_MUSIC | MediaController.AUTODOWNLOAD_MASK_GIF | MediaController.AUTODOWNLOAD_MASK_VIDEOMESSAGE; + mediaController.wifiDownloadMask[a] = MediaController.AUTODOWNLOAD_MASK_PHOTO | MediaController.AUTODOWNLOAD_MASK_AUDIO | MediaController.AUTODOWNLOAD_MASK_MUSIC | MediaController.AUTODOWNLOAD_MASK_GIF | MediaController.AUTODOWNLOAD_MASK_VIDEOMESSAGE; + mediaController.roamingDownloadMask[a] = 0; + editor.putInt("mobileDataDownloadMask" + (a != 0 ? a : ""), mediaController.mobileDataDownloadMask[a]); + editor.putInt("wifiDownloadMask" + (a != 0 ? a : ""), mediaController.wifiDownloadMask[a]); + editor.putInt("roamingDownloadMask" + (a != 0 ? a : ""), mediaController.roamingDownloadMask[a]); + } + for (int a = 0; a < 7; a++) { + int sdefault; + if (a == 1) { + sdefault = 2 * 1024 * 1024; + } else if (a == 6) { + sdefault = 5 * 1024 * 1024; + } else { + sdefault = 10 * 1024 * 1024; + } + mediaController.mobileMaxFileSize[a] = sdefault; + mediaController.wifiMaxFileSize[a] = sdefault; + mediaController.roamingMaxFileSize[a] = sdefault; + editor.putInt("mobileMaxDownloadSize" + a, sdefault); + editor.putInt("wifiMaxDownloadSize" + a, sdefault); + editor.putInt("roamingMaxDownloadSize" + a, sdefault); + } + if (!MediaController.getInstance().globalAutodownloadEnabled) { + MediaController.getInstance().globalAutodownloadEnabled = true; + editor.putBoolean("globalAutodownloadEnabled", MediaController.getInstance().globalAutodownloadEnabled); + updateAutodownloadRows(true); } editor.commit(); - if (listAdapter != null) { - listAdapter.notifyItemChanged(position); - } + MediaController.getInstance().checkAutodownloadSettings(); } }); - linearLayout.addView(cell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48)); - builder.setCustomView(linearLayout); - showDialog(builder.create()); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + builder.show(); + } else if (position == autoDownloadMediaRow) { + MediaController.getInstance().globalAutodownloadEnabled = !MediaController.getInstance().globalAutodownloadEnabled; + final SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + preferences.edit().putBoolean("globalAutodownloadEnabled", MediaController.getInstance().globalAutodownloadEnabled).commit(); + TextCheckCell textCheckCell = (TextCheckCell) view; + textCheckCell.setChecked(MediaController.getInstance().globalAutodownloadEnabled); + updateAutodownloadRows(false); } else if (position == storageUsageRow) { presentFragment(new CacheControlActivity()); } else if (position == useLessDataForCallsRow) { @@ -307,6 +274,41 @@ public class DataSettingsActivity extends BaseFragment { } } + private void updateAutodownloadRows(boolean check) { + int count = listView.getChildCount(); + ArrayList animators = new ArrayList<>(); + for (int a = 0; a < count; a++) { + View child = listView.getChildAt(a); + RecyclerListView.Holder holder = (RecyclerListView.Holder) listView.getChildViewHolder(child); + int type = holder.getItemViewType(); + int p = holder.getAdapterPosition(); + if (p >= photosRow && p <= gifsRow) { + TextSettingsCell textCell = (TextSettingsCell) holder.itemView; + textCell.setEnabled(MediaController.getInstance().globalAutodownloadEnabled, animators); + } else if (check && p == autoDownloadMediaRow) { + TextCheckCell textCell = (TextCheckCell) holder.itemView; + textCell.setChecked(true); + } + } + if (!animators.isEmpty()) { + if (animatorSet != null) { + animatorSet.cancel(); + } + animatorSet = new AnimatorSet(); + animatorSet.playTogether(animators); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + if (animator.equals(animatorSet)) { + animatorSet = null; + } + } + }); + animatorSet.setDuration(150); + animatorSet.start(); + } + } + private class ListAdapter extends RecyclerListView.SelectionAdapter { private Context mContext; @@ -333,6 +335,7 @@ public class DataSettingsActivity extends BaseFragment { } case 1: { TextSettingsCell textCell = (TextSettingsCell) holder.itemView; + textCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); if (position == storageUsageRow) { textCell.setText(LocaleController.getString("StorageUsage", R.string.StorageUsage), true); } else if (position == useLessDataForCallsRow) { @@ -358,6 +361,23 @@ public class DataSettingsActivity extends BaseFragment { textCell.setText(LocaleController.getString("WiFiUsage", R.string.WiFiUsage), true); } else if (position == proxyRow) { textCell.setText(LocaleController.getString("ProxySettings", R.string.ProxySettings), true); + } else if (position == resetDownloadRow) { + textCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText)); + textCell.setText(LocaleController.getString("ResetAutomaticMediaDownload", R.string.ResetAutomaticMediaDownload), false); + } else if (position == photosRow) { + textCell.setText(LocaleController.getString("LocalPhotoCache", R.string.LocalPhotoCache), true); + } else if (position == voiceMessagesRow) { + textCell.setText(LocaleController.getString("AudioAutodownload", R.string.AudioAutodownload), true); + } else if (position == videoMessagesRow) { + textCell.setText(LocaleController.getString("VideoMessagesAutodownload", R.string.VideoMessagesAutodownload), true); + } else if (position == videosRow) { + textCell.setText(LocaleController.getString("LocalVideoCache", R.string.LocalVideoCache), true); + } else if (position == filesRow) { + textCell.setText(LocaleController.getString("FilesDataUsage", R.string.FilesDataUsage), true); + } else if (position == musicRow) { + textCell.setText(LocaleController.getString("AttachMusic", R.string.AttachMusic), true); + } else if (position == gifsRow) { + textCell.setText(LocaleController.getString("LocalGifCache", R.string.LocalGifCache), true); } break; } @@ -375,77 +395,40 @@ public class DataSettingsActivity extends BaseFragment { break; } case 3: { - TextDetailSettingsCell textCell = (TextDetailSettingsCell) holder.itemView; - - if (position == mobileDownloadRow || position == wifiDownloadRow || position == roamingDownloadRow) { - int mask; - String value; - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); - if (position == mobileDownloadRow) { - value = LocaleController.getString("WhenUsingMobileData", R.string.WhenUsingMobileData); - mask = MediaController.getInstance().mobileDataDownloadMask; - } else if (position == wifiDownloadRow) { - value = LocaleController.getString("WhenConnectedOnWiFi", R.string.WhenConnectedOnWiFi); - mask = MediaController.getInstance().wifiDownloadMask; - } else { - value = LocaleController.getString("WhenRoaming", R.string.WhenRoaming); - mask = MediaController.getInstance().roamingDownloadMask; - } - String text = ""; - if ((mask & MediaController.AUTODOWNLOAD_MASK_PHOTO) != 0) { - text += LocaleController.getString("LocalPhotoCache", R.string.LocalPhotoCache); - } - if ((mask & MediaController.AUTODOWNLOAD_MASK_AUDIO) != 0) { - if (text.length() != 0) { - text += ", "; - } - text += LocaleController.getString("AudioAutodownload", R.string.AudioAutodownload); - } - if ((mask & MediaController.AUTODOWNLOAD_MASK_VIDEOMESSAGE) != 0) { - if (text.length() != 0) { - text += ", "; - } - text += LocaleController.getString("VideoMessagesAutodownload", R.string.VideoMessagesAutodownload); - } - if ((mask & MediaController.AUTODOWNLOAD_MASK_VIDEO) != 0) { - if (text.length() != 0) { - text += ", "; - } - text += LocaleController.getString("LocalVideoCache", R.string.LocalVideoCache); - } - if ((mask & MediaController.AUTODOWNLOAD_MASK_DOCUMENT) != 0) { - if (text.length() != 0) { - text += ", "; - } - text += LocaleController.getString("FilesDataUsage", R.string.FilesDataUsage); - } - if ((mask & MediaController.AUTODOWNLOAD_MASK_MUSIC) != 0) { - if (text.length() != 0) { - text += ", "; - } - text += LocaleController.getString("AttachMusic", R.string.AttachMusic); - } - if ((mask & MediaController.AUTODOWNLOAD_MASK_GIF) != 0) { - if (text.length() != 0) { - text += ", "; - } - text += LocaleController.getString("LocalGifCache", R.string.LocalGifCache); - } - if (text.length() == 0) { - text = LocaleController.getString("NoMediaAutoDownload", R.string.NoMediaAutoDownload); - } - textCell.setTextAndValue(value, text, true); + TextCheckCell checkCell = (TextCheckCell) holder.itemView; + if (position == autoDownloadMediaRow) { + checkCell.setTextAndCheck(LocaleController.getString("AutoDownloadMedia", R.string.AutoDownloadMedia), MediaController.getInstance().globalAutodownloadEnabled, true); } break; } } } + @Override + public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { + int viewType = holder.getItemViewType(); + if (viewType == 1) { + int position = holder.getAdapterPosition(); + TextSettingsCell textCell = (TextSettingsCell) holder.itemView; + if (position >= photosRow && position <= gifsRow) { + textCell.setEnabled(MediaController.getInstance().globalAutodownloadEnabled, null); + } else { + textCell.setEnabled(true, null); + } + } else if (viewType == 3) { + TextCheckCell checkCell = (TextCheckCell) holder.itemView; + checkCell.setChecked(MediaController.getInstance().globalAutodownloadEnabled); + } + } + @Override public boolean isEnabled(RecyclerView.ViewHolder holder) { int position = holder.getAdapterPosition(); - return position == wifiDownloadRow || position == mobileDownloadRow || position == roamingDownloadRow || position == storageUsageRow || - position == useLessDataForCallsRow || position == mobileUsageRow || position == roamingUsageRow || position == wifiUsageRow || position == proxyRow; + if (position == photosRow || position == voiceMessagesRow || position == videoMessagesRow || position == videosRow || position == filesRow || position == musicRow || position == gifsRow) { + return MediaController.getInstance().globalAutodownloadEnabled; + } + return position == storageUsageRow || position == useLessDataForCallsRow || position == mobileUsageRow || position == roamingUsageRow || position == wifiUsageRow || position == proxyRow || + position == resetDownloadRow || position == autoDownloadMediaRow; } @Override @@ -464,7 +447,7 @@ public class DataSettingsActivity extends BaseFragment { view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); break; case 3: - view = new TextDetailSettingsCell(mContext); + view = new TextCheckCell(mContext); view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); break; } @@ -476,12 +459,10 @@ public class DataSettingsActivity extends BaseFragment { public int getItemViewType(int position) { if (position == mediaDownloadSection2Row || position == usageSection2Row || position == callsSection2Row || position == proxySection2Row) { return 0; - } else if (position == storageUsageRow || position == useLessDataForCallsRow || position == roamingUsageRow || position == wifiUsageRow || position == mobileUsageRow || position == proxyRow) { - return 1; - } else if (position == wifiDownloadRow || position == mobileDownloadRow || position == roamingDownloadRow) { - return 3; } else if (position == mediaDownloadSectionRow || position == callsSectionRow || position == usageSectionRow || position == proxySectionRow) { return 2; + } else if (position == autoDownloadMediaRow) { + return 3; } else { return 1; } @@ -491,7 +472,7 @@ public class DataSettingsActivity extends BaseFragment { @Override public ThemeDescription[] getThemeDescriptions() { return new ThemeDescription[]{ - new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{TextSettingsCell.class, TextSettingsCell.class, TextDetailSettingsCell.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{TextSettingsCell.class, TextCheckCell.class}, null, null, null, Theme.key_windowBackgroundWhite), new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), @@ -511,8 +492,12 @@ public class DataSettingsActivity extends BaseFragment { new ThemeDescription(listView, 0, new Class[]{HeaderCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader), - new ThemeDescription(listView, 0, new Class[]{TextDetailSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), - new ThemeDescription(listView, 0, new Class[]{TextDetailSettingsCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchThumb), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchTrack), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchThumbChecked), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchTrackChecked), }; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/DataUsageActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/DataUsageActivity.java index cece9aa48..32820ea72 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/DataUsageActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/DataUsageActivity.java @@ -133,23 +133,13 @@ public class DataUsageActivity extends BaseFragment { filesBytesReceivedRow = rowCount++; filesSection2Row = rowCount++; - if (MessagesController.getInstance().callsEnabled) { - callsSectionRow = rowCount++; - callsSentRow = rowCount++; - callsReceivedRow = rowCount++; - callsBytesSentRow = rowCount++; - callsBytesReceivedRow = rowCount++; - callsTotalTimeRow = rowCount++; - callsSection2Row = rowCount++; - } else { - callsSectionRow = -1; - callsSentRow = -1; - callsReceivedRow = -1; - callsBytesSentRow = -1; - callsBytesReceivedRow = -1; - callsTotalTimeRow = -1; - callsSection2Row = -1; - } + callsSectionRow = rowCount++; + callsSentRow = rowCount++; + callsReceivedRow = rowCount++; + callsBytesSentRow = rowCount++; + callsBytesReceivedRow = rowCount++; + callsTotalTimeRow = rowCount++; + callsSection2Row = rowCount++; messagesSectionRow = rowCount++; /*if (BuildVars.DEBUG_VERSION) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java index 145b9b053..90ddf28db 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java @@ -9,6 +9,9 @@ package org.telegram.ui; import android.Manifest; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.StateListAnimator; import android.annotation.SuppressLint; @@ -25,18 +28,15 @@ import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; -import android.util.TypedValue; +import android.text.TextUtils; import android.view.Gravity; -import android.view.MotionEvent; import android.view.View; import android.view.ViewOutlineProvider; import android.view.ViewTreeObserver; import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.DecelerateInterpolator; import android.widget.EditText; -import android.widget.FrameLayout; import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.BuildVars; @@ -51,6 +51,7 @@ import org.telegram.messenger.query.StickersQuery; import org.telegram.messenger.support.widget.LinearLayoutManager; import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.messenger.FileLog; +import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.messenger.ContactsController; import org.telegram.messenger.MessagesController; @@ -63,6 +64,7 @@ import org.telegram.ui.ActionBar.BottomSheet; import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Adapters.DialogsAdapter; import org.telegram.ui.Adapters.DialogsSearchAdapter; +import org.telegram.ui.Cells.DialogsEmptyCell; import org.telegram.ui.Cells.DividerCell; import org.telegram.ui.Cells.DrawerActionCell; import org.telegram.ui.Cells.DrawerProfileCell; @@ -78,13 +80,17 @@ import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.ActionBarMenuItem; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.MenuDrawable; +import org.telegram.ui.Components.ChatActivityEnterView; import org.telegram.ui.Components.CombinedDrawable; import org.telegram.ui.Components.FragmentContextView; import org.telegram.ui.Components.EmptyTextProgressView; +import org.telegram.ui.Components.JoinGroupAlert; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.RadialProgressView; import org.telegram.ui.Components.RecyclerListView; import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.SizeNotifierFrameLayout; +import org.telegram.ui.Components.StickersAlert; import java.util.ArrayList; @@ -96,14 +102,12 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. private DialogsSearchAdapter dialogsSearchAdapter; private EmptyTextProgressView searchEmptyView; private RadialProgressView progressView; - private LinearLayout emptyView; private ActionBarMenuItem passcodeItem; private ImageView floatingButton; private RecyclerView sideMenu; private FragmentContextView fragmentContextView; - - private TextView emptyTextView1; - private TextView emptyTextView2; + private FragmentContextView fragmentLocationContextView; + private ChatActivityEnterView commentView; private AlertDialog permissionDialog; @@ -132,7 +136,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. private DialogsActivityDelegate delegate; public interface DialogsActivityDelegate { - void didSelectDialog(DialogsActivity fragment, long dialog_id, boolean param); + void didSelectDialogs(DialogsActivity fragment, ArrayList dids, CharSequence message, boolean param); } public DialogsActivity(Bundle args) { @@ -170,11 +174,12 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. NotificationCenter.getInstance().addObserver(this, NotificationCenter.reloadHints); } - if (!dialogsLoaded) { MessagesController.getInstance().loadDialogs(0, 100, true); + MessagesController.getInstance().loadHintDialogs(); ContactsController.getInstance().checkInviteText(); MessagesController.getInstance().loadPinnedDialogs(0, null); + StickersQuery.loadRecents(StickersQuery.TYPE_FAVE, false, true, false); StickersQuery.checkFeaturedStickers(); dialogsLoaded = true; } @@ -201,6 +206,9 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didLoadedReplyMessages); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.reloadHints); } + if (commentView != null) { + commentView.onDestroy(); + } delegate = null; } @@ -229,7 +237,6 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. if (searchString != null) { listView.setEmptyView(searchEmptyView); progressView.setVisibility(View.GONE); - emptyView.setVisibility(View.GONE); } if (!onlySelect) { floatingButton.setVisibility(View.GONE); @@ -252,14 +259,13 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. searching = false; searchWas = false; if (listView != null) { - searchEmptyView.setVisibility(View.GONE); if (MessagesController.getInstance().loadingDialogs && MessagesController.getInstance().dialogs.isEmpty()) { - emptyView.setVisibility(View.GONE); listView.setEmptyView(progressView); } else { progressView.setVisibility(View.GONE); - listView.setEmptyView(emptyView); + listView.setEmptyView(null); } + searchEmptyView.setVisibility(View.GONE); if (!onlySelect) { floatingButton.setVisibility(View.VISIBLE); floatingHidden = true; @@ -287,7 +293,6 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. dialogsSearchAdapter.notifyDataSetChanged(); } if (searchEmptyView != null && listView.getEmptyView() != searchEmptyView) { - emptyView.setVisibility(View.GONE); progressView.setVisibility(View.GONE); searchEmptyView.showTextView(); listView.setEmptyView(searchEmptyView); @@ -301,7 +306,11 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. item.getSearchField().setHint(LocaleController.getString("Search", R.string.Search)); if (onlySelect) { actionBar.setBackButtonImage(R.drawable.ic_ab_back); - actionBar.setTitle(LocaleController.getString("SelectChat", R.string.SelectChat)); + if (dialogsType == 3 && selectAlertString == null) { + actionBar.setTitle(LocaleController.getString("ForwardTo", R.string.ForwardTo)); + } else { + actionBar.setTitle(LocaleController.getString("SelectChat", R.string.SelectChat)); + } } else { if (searchString != null) { actionBar.setBackButtonImage(R.drawable.ic_ab_back); @@ -339,8 +348,135 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. sideMenu.getAdapter().notifyDataSetChanged(); } - FrameLayout frameLayout = new FrameLayout(context); - fragmentView = frameLayout; + SizeNotifierFrameLayout contentView = new SizeNotifierFrameLayout(context) { + + int inputFieldHeight = 0; + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int widthSize = MeasureSpec.getSize(widthMeasureSpec); + int heightSize = MeasureSpec.getSize(heightMeasureSpec); + + setMeasuredDimension(widthSize, heightSize); + heightSize -= getPaddingTop(); + + measureChildWithMargins(actionBar, widthMeasureSpec, 0, heightMeasureSpec, 0); + + int keyboardSize = getKeyboardHeight(); + int childCount = getChildCount(); + + if (commentView != null) { + measureChildWithMargins(commentView, widthMeasureSpec, 0, heightMeasureSpec, 0); + Object tag = commentView.getTag(); + if (tag != null && tag.equals(2)) { + if (keyboardSize <= AndroidUtilities.dp(20) && !AndroidUtilities.isInMultiwindow) { + heightSize -= commentView.getEmojiPadding(); + } + inputFieldHeight = commentView.getMeasuredHeight(); + } else { + inputFieldHeight = 0; + } + } + + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + if (child == null || child.getVisibility() == GONE || child == commentView || child == actionBar) { + continue; + } + if (child == listView || child == progressView || child == searchEmptyView) { + int contentWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY); + int contentHeightSpec = MeasureSpec.makeMeasureSpec(Math.max(AndroidUtilities.dp(10), heightSize - inputFieldHeight + AndroidUtilities.dp(2)), MeasureSpec.EXACTLY); + child.measure(contentWidthSpec, contentHeightSpec); + } else if (commentView != null && commentView.isPopupView(child)) { + if (AndroidUtilities.isInMultiwindow) { + if (AndroidUtilities.isTablet()) { + child.measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(Math.min(AndroidUtilities.dp(320), heightSize - inputFieldHeight - AndroidUtilities.statusBarHeight + getPaddingTop()), MeasureSpec.EXACTLY)); + } else { + child.measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(heightSize - inputFieldHeight - AndroidUtilities.statusBarHeight + getPaddingTop(), MeasureSpec.EXACTLY)); + } + } else { + child.measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(child.getLayoutParams().height, MeasureSpec.EXACTLY)); + } + } else { + measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); + } + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + final int count = getChildCount(); + + int paddingBottom; + Object tag = commentView != null ? commentView.getTag() : null; + if (tag != null && tag.equals(2)) { + paddingBottom = getKeyboardHeight() <= AndroidUtilities.dp(20) && !AndroidUtilities.isInMultiwindow ? commentView.getEmojiPadding() : 0; + } else { + paddingBottom = 0; + } + setBottomClip(paddingBottom); + + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.getVisibility() == GONE) { + continue; + } + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + + final int width = child.getMeasuredWidth(); + final int height = child.getMeasuredHeight(); + + int childLeft; + int childTop; + + int gravity = lp.gravity; + if (gravity == -1) { + gravity = Gravity.TOP | Gravity.LEFT; + } + + final int absoluteGravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK; + final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; + + switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { + case Gravity.CENTER_HORIZONTAL: + childLeft = (r - l - width) / 2 + lp.leftMargin - lp.rightMargin; + break; + case Gravity.RIGHT: + childLeft = r - width - lp.rightMargin; + break; + case Gravity.LEFT: + default: + childLeft = lp.leftMargin; + } + + switch (verticalGravity) { + case Gravity.TOP: + childTop = lp.topMargin + getPaddingTop(); + break; + case Gravity.CENTER_VERTICAL: + childTop = ((b - paddingBottom) - t - height) / 2 + lp.topMargin - lp.bottomMargin; + break; + case Gravity.BOTTOM: + childTop = ((b - paddingBottom) - t) - height - lp.bottomMargin; + break; + default: + childTop = lp.topMargin; + } + + if (commentView != null && commentView.isPopupView(child)) { + if (AndroidUtilities.isInMultiwindow) { + childTop = commentView.getTop() - child.getMeasuredHeight() + AndroidUtilities.dp(1); + } else { + childTop = commentView.getBottom(); + } + } + child.layout(childLeft, childTop, childLeft + width, childTop + height); + } + + notifyHeightChanged(); + } + }; + fragmentView = contentView; listView = new RecyclerListView(context); listView.setVerticalScrollBarEnabled(true); @@ -357,22 +493,54 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. layoutManager.setOrientation(LinearLayoutManager.VERTICAL); listView.setLayoutManager(layoutManager); listView.setVerticalScrollbarPosition(LocaleController.isRTL ? RecyclerListView.SCROLLBAR_POSITION_LEFT : RecyclerListView.SCROLLBAR_POSITION_RIGHT); - frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + contentView.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override public void onItemClick(View view, int position) { - if (listView == null || listView.getAdapter() == null) { + if (listView == null || listView.getAdapter() == null || getParentActivity() == null) { return; } long dialog_id = 0; int message_id = 0; RecyclerView.Adapter adapter = listView.getAdapter(); if (adapter == dialogsAdapter) { - TLRPC.TL_dialog dialog = dialogsAdapter.getItem(position); - if (dialog == null) { + TLObject object = dialogsAdapter.getItem(position); + if (object instanceof TLRPC.TL_dialog) { + dialog_id = ((TLRPC.TL_dialog) object).id; + } else if (object instanceof TLRPC.TL_recentMeUrlChat) { + dialog_id = -((TLRPC.TL_recentMeUrlChat) object).chat_id; + } else if (object instanceof TLRPC.TL_recentMeUrlUser) { + dialog_id = ((TLRPC.TL_recentMeUrlUser) object).user_id; + } else if (object instanceof TLRPC.TL_recentMeUrlChatInvite) { + TLRPC.TL_recentMeUrlChatInvite chatInvite = (TLRPC.TL_recentMeUrlChatInvite) object; + TLRPC.ChatInvite invite = chatInvite.chat_invite; + if (invite.chat == null && (!invite.channel || invite.megagroup) || invite.chat != null && (!ChatObject.isChannel(invite.chat) || invite.chat.megagroup)) { + String hash = chatInvite.url; + int index = hash.indexOf('/'); + if (index > 0) { + hash = hash.substring(index + 1); + } + showDialog(new JoinGroupAlert(getParentActivity(), invite, hash, DialogsActivity.this)); + return; + } else { + if (invite.chat != null) { + dialog_id = -invite.chat.id; + } else { + return; + } + } + } else if (object instanceof TLRPC.TL_recentMeUrlStickerSet) { + TLRPC.StickerSet stickerSet = ((TLRPC.TL_recentMeUrlStickerSet) object).set.set; + TLRPC.TL_inputStickerSetID set = new TLRPC.TL_inputStickerSetID(); + set.id = stickerSet.id; + set.access_hash = stickerSet.access_hash; + showDialog(new StickersAlert(getParentActivity(), DialogsActivity.this, set, null, null)); + return; + } else if (object instanceof TLRPC.TL_recentMeUrlUnknown) { + return; + } else { return; } - dialog_id = dialog.id; } else if (adapter == dialogsSearchAdapter) { Object obj = dialogsSearchAdapter.getItem(position); if (obj instanceof TLRPC.User) { @@ -421,7 +589,12 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } if (onlySelect) { - didSelectResult(dialog_id, true, false); + if (dialogsAdapter.hasSelectedDialogs()) { + dialogsAdapter.addOrRemoveSelectedDialog(dialog_id, view); + updateSelectedCount(); + } else { + didSelectResult(dialog_id, true, false); + } } else { Bundle args = new Bundle(); int lower_part = (int) dialog_id; @@ -478,30 +651,29 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. listView.setOnItemLongClickListener(new RecyclerListView.OnItemLongClickListener() { @Override public boolean onItemClick(View view, int position) { - if (onlySelect || searching && searchWas || getParentActivity() == null) { - if (searchWas && searching || dialogsSearchAdapter.isRecentSearchDisplayed()) { - RecyclerView.Adapter adapter = listView.getAdapter(); - if (adapter == dialogsSearchAdapter) { - Object item = dialogsSearchAdapter.getItem(position); - if (item instanceof String || dialogsSearchAdapter.isRecentSearchDisplayed()) { - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); - builder.setMessage(LocaleController.getString("ClearSearch", R.string.ClearSearch)); - builder.setPositiveButton(LocaleController.getString("ClearButton", R.string.ClearButton).toUpperCase(), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - if (dialogsSearchAdapter.isRecentSearchDisplayed()) { - dialogsSearchAdapter.clearRecentSearch(); - } else { - dialogsSearchAdapter.clearRecentHashtags(); - } - } - }); - builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); - showDialog(builder.create()); - return true; + if (getParentActivity() == null) { + return false; + } + RecyclerView.Adapter adapter = listView.getAdapter(); + if (adapter == dialogsSearchAdapter) { + Object item = dialogsSearchAdapter.getItem(position); + if (item instanceof String || dialogsSearchAdapter.isRecentSearchDisplayed()) { + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setMessage(LocaleController.getString("ClearSearch", R.string.ClearSearch)); + builder.setPositiveButton(LocaleController.getString("ClearButton", R.string.ClearButton).toUpperCase(), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + if (dialogsSearchAdapter.isRecentSearchDisplayed()) { + dialogsSearchAdapter.clearRecentSearch(); + } else { + dialogsSearchAdapter.clearRecentHashtags(); + } } - } + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + showDialog(builder.create()); + return true; } return false; } @@ -511,150 +683,158 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. return false; } dialog = dialogs.get(position); - selectedDialog = dialog.id; - final boolean pinned = dialog.pinned; - - BottomSheet.Builder builder = new BottomSheet.Builder(getParentActivity()); - int lower_id = (int) selectedDialog; - int high_id = (int) (selectedDialog >> 32); - - if (DialogObject.isChannel(dialog)) { - final TLRPC.Chat chat = MessagesController.getInstance().getChat(-lower_id); - CharSequence items[]; - int icons[] = new int[]{ - dialog.pinned ? R.drawable.chats_unpin : R.drawable.chats_pin, - R.drawable.chats_clear, - R.drawable.chats_leave - }; - if (chat != null && chat.megagroup) { - items = new CharSequence[]{ - dialog.pinned || MessagesController.getInstance().canPinDialog(false) ? (dialog.pinned ? LocaleController.getString("UnpinFromTop", R.string.UnpinFromTop) : LocaleController.getString("PinToTop", R.string.PinToTop)) : null, - LocaleController.getString("ClearHistoryCache", R.string.ClearHistoryCache), - chat == null || !chat.creator ? LocaleController.getString("LeaveMegaMenu", R.string.LeaveMegaMenu) : LocaleController.getString("DeleteMegaMenu", R.string.DeleteMegaMenu)}; - } else { - items = new CharSequence[]{ - dialog.pinned || MessagesController.getInstance().canPinDialog(false) ? (dialog.pinned ? LocaleController.getString("UnpinFromTop", R.string.UnpinFromTop) : LocaleController.getString("PinToTop", R.string.PinToTop)) : null, - LocaleController.getString("ClearHistoryCache", R.string.ClearHistoryCache), - chat == null || !chat.creator ? LocaleController.getString("LeaveChannelMenu", R.string.LeaveChannelMenu) : LocaleController.getString("ChannelDeleteMenu", R.string.ChannelDeleteMenu)}; + if (onlySelect) { + if (dialogsType != 3 || selectAlertString != null) { + return false; } - builder.setItems(items, icons, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, final int which) { - if (which == 0) { - if (MessagesController.getInstance().pinDialog(selectedDialog, !pinned, null, 0) && !pinned) { - listView.smoothScrollToPosition(0); - } - } else { - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); - if (which == 1) { - if (chat != null && chat.megagroup) { - builder.setMessage(LocaleController.getString("AreYouSureClearHistorySuper", R.string.AreYouSureClearHistorySuper)); - } else { - builder.setMessage(LocaleController.getString("AreYouSureClearHistoryChannel", R.string.AreYouSureClearHistoryChannel)); + dialogsAdapter.addOrRemoveSelectedDialog(dialog.id, view); + updateSelectedCount(); + } else { + selectedDialog = dialog.id; + final boolean pinned = dialog.pinned; + + BottomSheet.Builder builder = new BottomSheet.Builder(getParentActivity()); + int lower_id = (int) selectedDialog; + int high_id = (int) (selectedDialog >> 32); + + if (DialogObject.isChannel(dialog)) { + final TLRPC.Chat chat = MessagesController.getInstance().getChat(-lower_id); + CharSequence items[]; + int icons[] = new int[]{ + dialog.pinned ? R.drawable.chats_unpin : R.drawable.chats_pin, + R.drawable.chats_clear, + R.drawable.chats_leave + }; + if (chat != null && chat.megagroup) { + items = new CharSequence[]{ + dialog.pinned || MessagesController.getInstance().canPinDialog(false) ? (dialog.pinned ? LocaleController.getString("UnpinFromTop", R.string.UnpinFromTop) : LocaleController.getString("PinToTop", R.string.PinToTop)) : null, + TextUtils.isEmpty(chat.username) ? LocaleController.getString("ClearHistory", R.string.ClearHistory) : LocaleController.getString("ClearHistoryCache", R.string.ClearHistoryCache), + LocaleController.getString("LeaveMegaMenu", R.string.LeaveMegaMenu)}; + } else { + items = new CharSequence[]{ + dialog.pinned || MessagesController.getInstance().canPinDialog(false) ? (dialog.pinned ? LocaleController.getString("UnpinFromTop", R.string.UnpinFromTop) : LocaleController.getString("PinToTop", R.string.PinToTop)) : null, + LocaleController.getString("ClearHistoryCache", R.string.ClearHistoryCache), + LocaleController.getString("LeaveChannelMenu", R.string.LeaveChannelMenu)}; + } + builder.setItems(items, icons, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, final int which) { + if (which == 0) { + if (MessagesController.getInstance().pinDialog(selectedDialog, !pinned, null, 0) && !pinned) { + listView.smoothScrollToPosition(0); } - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - MessagesController.getInstance().deleteDialog(selectedDialog, 2); - } - }); } else { - if (chat != null && chat.megagroup) { - if (!chat.creator) { + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + if (which == 1) { + if (chat != null && chat.megagroup) { + if (TextUtils.isEmpty(chat.username)) { + builder.setMessage(LocaleController.getString("AreYouSureClearHistory", R.string.AreYouSureClearHistory)); + } else { + builder.setMessage(LocaleController.getString("AreYouSureClearHistoryGroup", R.string.AreYouSureClearHistoryGroup)); + } + } else { + builder.setMessage(LocaleController.getString("AreYouSureClearHistoryChannel", R.string.AreYouSureClearHistoryChannel)); + } + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + if (chat != null && chat.megagroup && TextUtils.isEmpty(chat.username)) { + MessagesController.getInstance().deleteDialog(selectedDialog, 1); + } else { + MessagesController.getInstance().deleteDialog(selectedDialog, 2); + } + } + }); + } else { + if (chat != null && chat.megagroup) { builder.setMessage(LocaleController.getString("MegaLeaveAlert", R.string.MegaLeaveAlert)); } else { - builder.setMessage(LocaleController.getString("MegaDeleteAlert", R.string.MegaDeleteAlert)); - } - } else { - if (chat == null || !chat.creator) { builder.setMessage(LocaleController.getString("ChannelLeaveAlert", R.string.ChannelLeaveAlert)); + } + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + MessagesController.getInstance().deleteUserFromChat((int) -selectedDialog, UserConfig.getCurrentUser(), null); + if (AndroidUtilities.isTablet()) { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats, selectedDialog); + } + } + }); + } + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + showDialog(builder.create()); + } + } + }); + showDialog(builder.create()); + } else { + final boolean isChat = lower_id < 0 && high_id != 1; + TLRPC.User user = null; + if (!isChat && lower_id > 0 && high_id != 1) { + user = MessagesController.getInstance().getUser(lower_id); + } + final boolean isBot = user != null && user.bot; + + builder.setItems(new CharSequence[]{ + dialog.pinned || MessagesController.getInstance().canPinDialog(lower_id == 0) ? (dialog.pinned ? LocaleController.getString("UnpinFromTop", R.string.UnpinFromTop) : LocaleController.getString("PinToTop", R.string.PinToTop)) : null, + LocaleController.getString("ClearHistory", R.string.ClearHistory), + isChat ? LocaleController.getString("DeleteChat", R.string.DeleteChat) : isBot ? LocaleController.getString("DeleteAndStop", R.string.DeleteAndStop) : LocaleController.getString("Delete", R.string.Delete) + }, new int[]{ + dialog.pinned ? R.drawable.chats_unpin : R.drawable.chats_pin, + R.drawable.chats_clear, + isChat ? R.drawable.chats_leave : R.drawable.chats_delete + }, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, final int which) { + if (which == 0) { + if (MessagesController.getInstance().pinDialog(selectedDialog, !pinned, null, 0) && !pinned) { + listView.smoothScrollToPosition(0); + } + } else { + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + if (which == 1) { + builder.setMessage(LocaleController.getString("AreYouSureClearHistory", R.string.AreYouSureClearHistory)); + } else { + if (isChat) { + builder.setMessage(LocaleController.getString("AreYouSureDeleteAndExit", R.string.AreYouSureDeleteAndExit)); } else { - builder.setMessage(LocaleController.getString("ChannelDeleteAlert", R.string.ChannelDeleteAlert)); + builder.setMessage(LocaleController.getString("AreYouSureDeleteThisChat", R.string.AreYouSureDeleteThisChat)); } } builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { - MessagesController.getInstance().deleteUserFromChat((int) -selectedDialog, UserConfig.getCurrentUser(), null); - if (AndroidUtilities.isTablet()) { - NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats, selectedDialog); + if (which != 1) { + if (isChat) { + TLRPC.Chat currentChat = MessagesController.getInstance().getChat((int) -selectedDialog); + if (currentChat != null && ChatObject.isNotInChat(currentChat)) { + MessagesController.getInstance().deleteDialog(selectedDialog, 0); + } else { + MessagesController.getInstance().deleteUserFromChat((int) -selectedDialog, MessagesController.getInstance().getUser(UserConfig.getClientUserId()), null); + } + } else { + MessagesController.getInstance().deleteDialog(selectedDialog, 0); + } + if (isBot) { + MessagesController.getInstance().blockUser((int) selectedDialog); + } + if (AndroidUtilities.isTablet()) { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats, selectedDialog); + } + } else { + MessagesController.getInstance().deleteDialog(selectedDialog, 1); } } }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + showDialog(builder.create()); } - builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); - showDialog(builder.create()); } - } - }); - showDialog(builder.create()); - } else { - final boolean isChat = lower_id < 0 && high_id != 1; - TLRPC.User user = null; - if (!isChat && lower_id > 0 && high_id != 1) { - user = MessagesController.getInstance().getUser(lower_id); + }); + showDialog(builder.create()); } - final boolean isBot = user != null && user.bot; - - builder.setItems(new CharSequence[]{ - dialog.pinned || MessagesController.getInstance().canPinDialog(lower_id == 0) ? (dialog.pinned ? LocaleController.getString("UnpinFromTop", R.string.UnpinFromTop) : LocaleController.getString("PinToTop", R.string.PinToTop)) : null, - LocaleController.getString("ClearHistory", R.string.ClearHistory), - isChat ? LocaleController.getString("DeleteChat", R.string.DeleteChat) : isBot ? LocaleController.getString("DeleteAndStop", R.string.DeleteAndStop) : LocaleController.getString("Delete", R.string.Delete) - }, new int[]{ - dialog.pinned ? R.drawable.chats_unpin : R.drawable.chats_pin, - R.drawable.chats_clear, - isChat ? R.drawable.chats_leave : R.drawable.chats_delete - }, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, final int which) { - if (which == 0) { - if (MessagesController.getInstance().pinDialog(selectedDialog, !pinned, null, 0) && !pinned) { - listView.smoothScrollToPosition(0); - } - } else { - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); - if (which == 1) { - builder.setMessage(LocaleController.getString("AreYouSureClearHistory", R.string.AreYouSureClearHistory)); - } else { - if (isChat) { - builder.setMessage(LocaleController.getString("AreYouSureDeleteAndExit", R.string.AreYouSureDeleteAndExit)); - } else { - builder.setMessage(LocaleController.getString("AreYouSureDeleteThisChat", R.string.AreYouSureDeleteThisChat)); - } - } - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - if (which != 1) { - if (isChat) { - TLRPC.Chat currentChat = MessagesController.getInstance().getChat((int) -selectedDialog); - if (currentChat != null && ChatObject.isNotInChat(currentChat)) { - MessagesController.getInstance().deleteDialog(selectedDialog, 0); - } else { - MessagesController.getInstance().deleteUserFromChat((int) -selectedDialog, MessagesController.getInstance().getUser(UserConfig.getClientUserId()), null); - } - } else { - MessagesController.getInstance().deleteDialog(selectedDialog, 0); - } - if (isBot) { - MessagesController.getInstance().blockUser((int) selectedDialog); - } - if (AndroidUtilities.isTablet()) { - NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats, selectedDialog); - } - } else { - MessagesController.getInstance().deleteDialog(selectedDialog, 1); - } - } - }); - builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); - showDialog(builder.create()); - } - } - }); - showDialog(builder.create()); } return true; } @@ -664,43 +844,11 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. searchEmptyView.setVisibility(View.GONE); searchEmptyView.setShowAtCenter(true); searchEmptyView.setText(LocaleController.getString("NoResult", R.string.NoResult)); - frameLayout.addView(searchEmptyView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - - emptyView = new LinearLayout(context); - emptyView.setOrientation(LinearLayout.VERTICAL); - emptyView.setVisibility(View.GONE); - emptyView.setGravity(Gravity.CENTER); - frameLayout.addView(emptyView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - emptyView.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - return true; - } - }); - - emptyTextView1 = new TextView(context); - emptyTextView1.setText(LocaleController.getString("NoChats", R.string.NoChats)); - emptyTextView1.setTextColor(Theme.getColor(Theme.key_emptyListPlaceholder)); - emptyTextView1.setGravity(Gravity.CENTER); - emptyTextView1.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); - emptyView.addView(emptyTextView1, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); - - emptyTextView2 = new TextView(context); - String help = LocaleController.getString("NoChatsHelp", R.string.NoChatsHelp); - if (AndroidUtilities.isTablet() && !AndroidUtilities.isSmallTablet()) { - help = help.replace('\n', ' '); - } - emptyTextView2.setText(help); - emptyTextView2.setTextColor(Theme.getColor(Theme.key_emptyListPlaceholder)); - emptyTextView2.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); - emptyTextView2.setGravity(Gravity.CENTER); - emptyTextView2.setPadding(AndroidUtilities.dp(8), AndroidUtilities.dp(6), AndroidUtilities.dp(8), 0); - emptyTextView2.setLineSpacing(AndroidUtilities.dp(2), 1); - emptyView.addView(emptyTextView2, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); + contentView.addView(searchEmptyView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); progressView = new RadialProgressView(context); progressView.setVisibility(View.GONE); - frameLayout.addView(progressView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER)); + contentView.addView(progressView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER)); floatingButton = new ImageView(context); floatingButton.setVisibility(onlySelect ? View.GONE : View.VISIBLE); @@ -730,7 +878,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } }); } - frameLayout.addView(floatingButton, LayoutHelper.createFrame(Build.VERSION.SDK_INT >= 21 ? 56 : 60, Build.VERSION.SDK_INT >= 21 ? 56 : 60, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.BOTTOM, LocaleController.isRTL ? 14 : 0, 0, LocaleController.isRTL ? 0 : 14, 14)); + contentView.addView(floatingButton, LayoutHelper.createFrame(Build.VERSION.SDK_INT >= 21 ? 56 : 60, Build.VERSION.SDK_INT >= 21 ? 56 : 60, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.BOTTOM, LocaleController.isRTL ? 14 : 0, 0, LocaleController.isRTL ? 0 : 14, 14)); floatingButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -795,7 +943,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. }); if (searchString == null) { - dialogsAdapter = new DialogsAdapter(context, dialogsType); + dialogsAdapter = new DialogsAdapter(context, dialogsType, onlySelect); if (AndroidUtilities.isTablet() && openedDialogId != 0) { dialogsAdapter.setOpenedDialogId(openedDialogId); } @@ -821,15 +969,22 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } @Override - public void didPressedOnSubDialog(int did) { + public void didPressedOnSubDialog(long did) { if (onlySelect) { - didSelectResult(did, true, false); - } else { - Bundle args = new Bundle(); - if (did > 0) { - args.putInt("user_id", did); + if (dialogsAdapter.hasSelectedDialogs()) { + dialogsAdapter.addOrRemoveSelectedDialog(did, null); + updateSelectedCount(); + actionBar.closeSearchField(); } else { - args.putInt("chat_id", -did); + didSelectResult(did, true, false); + } + } else { + int lower_id = (int) did; + Bundle args = new Bundle(); + if (lower_id > 0) { + args.putInt("user_id", lower_id); + } else { + args.putInt("chat_id", -lower_id); } if (actionBar != null) { actionBar.closeSearchField(); @@ -878,19 +1033,113 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. if (MessagesController.getInstance().loadingDialogs && MessagesController.getInstance().dialogs.isEmpty()) { searchEmptyView.setVisibility(View.GONE); - emptyView.setVisibility(View.GONE); listView.setEmptyView(progressView); } else { searchEmptyView.setVisibility(View.GONE); progressView.setVisibility(View.GONE); - listView.setEmptyView(emptyView); + listView.setEmptyView(null); } if (searchString != null) { actionBar.openSearchField(searchString); } if (!onlySelect && dialogsType == 0) { - frameLayout.addView(fragmentContextView = new FragmentContextView(context, this), LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 39, Gravity.TOP | Gravity.LEFT, 0, -36, 0, 0)); + contentView.addView(fragmentLocationContextView = new FragmentContextView(context, this, true), LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 39, Gravity.TOP | Gravity.LEFT, 0, -36, 0, 0)); + contentView.addView(fragmentContextView = new FragmentContextView(context, this, false), LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 39, Gravity.TOP | Gravity.LEFT, 0, -36, 0, 0)); + fragmentContextView.setAdditionalContextView(fragmentLocationContextView); + fragmentLocationContextView.setAdditionalContextView(fragmentContextView); + } else if (dialogsType == 3 && selectAlertString == null) { + if (commentView != null) { + commentView.onDestroy(); + } + commentView = new ChatActivityEnterView(getParentActivity(), contentView, null, false); + commentView.setAllowStickersAndGifs(false, false); + commentView.setForceShowSendButton(true, false); + commentView.setVisibility(View.GONE); + contentView.addView(commentView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.BOTTOM)); + commentView.setDelegate(new ChatActivityEnterView.ChatActivityEnterViewDelegate() { + @Override + public void onMessageSend(CharSequence message) { + if (delegate == null) { + return; + } + ArrayList selectedDialogs = dialogsAdapter.getSelectedDialogs(); + if (selectedDialogs.isEmpty()) { + return; + } + delegate.didSelectDialogs(DialogsActivity.this, selectedDialogs, message, false); + } + + @Override + public void onSwitchRecordMode(boolean video) { + + } + + @Override + public void onPreAudioVideoRecord() { + + } + + @Override + public void onTextChanged(final CharSequence text, boolean bigChange) { + + } + + @Override + public void needSendTyping() { + + } + + @Override + public void onAttachButtonHidden() { + + } + + @Override + public void onAttachButtonShow() { + + } + + @Override + public void onMessageEditEnd(boolean loading) { + + } + + @Override + public void onWindowSizeChanged(int size) { + + } + + @Override + public void onStickersTab(boolean opened) { + + } + + @Override + public void didPressedAttachButton() { + + } + + @Override + public void needStartRecordVideo(int state) { + + } + + @Override + public void needChangeVideoPreviewState(int state, float seekProgress) { + + } + + @Override + public void needStartRecordAudio(int state) { + + } + + @Override + public void needShowMediaBanHint() { + + } + }); } return fragmentView; @@ -902,6 +1151,9 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. if (dialogsAdapter != null) { dialogsAdapter.notifyDataSetChanged(); } + if (commentView != null) { + commentView.onResume(); + } if (dialogsSearchAdapter != null) { dialogsSearchAdapter.notifyDataSetChanged(); } @@ -930,6 +1182,62 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } } + @Override + public void onPause() { + super.onPause(); + if (commentView != null) { + commentView.onResume(); + } + } + + private void updateSelectedCount() { + if (commentView == null) { + return; + } + if (!dialogsAdapter.hasSelectedDialogs()) { + if (dialogsType == 3 && selectAlertString == null) { + actionBar.setTitle(LocaleController.getString("ForwardTo", R.string.ForwardTo)); + } else { + actionBar.setTitle(LocaleController.getString("SelectChat", R.string.SelectChat)); + } + if (commentView.getTag() != null) { + commentView.hidePopup(false); + commentView.closeKeyboard(); + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether(ObjectAnimator.ofFloat(commentView, "translationY", 0, commentView.getMeasuredHeight())); + animatorSet.setDuration(180); + animatorSet.setInterpolator(new DecelerateInterpolator()); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + commentView.setVisibility(View.GONE); + } + }); + animatorSet.start(); + commentView.setTag(null); + listView.requestLayout(); + } + } else { + if (commentView.getTag() == null) { + commentView.setFieldText(""); + commentView.setVisibility(View.VISIBLE); + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether(ObjectAnimator.ofFloat(commentView, "translationY", commentView.getMeasuredHeight(), 0)); + animatorSet.setDuration(180); + animatorSet.setInterpolator(new DecelerateInterpolator()); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + commentView.setTag(2); + } + }); + animatorSet.start(); + commentView.setTag(1); + } + actionBar.setTitle(LocaleController.formatPluralString("Recipient", dialogsAdapter.getSelectedDialogs().size())); + } + } + @TargetApi(Build.VERSION_CODES.M) private void askForPermissons() { Activity activity = getParentActivity(); @@ -987,7 +1295,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } switch (permissions[a]) { case Manifest.permission.READ_CONTACTS: - ContactsController.getInstance().readContacts(); + ContactsController.getInstance().forceImportContacts(); break; case Manifest.permission.WRITE_EXTERNAL_STORAGE: ImageLoader.getInstance().checkMediaPaths(); @@ -1015,20 +1323,18 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. try { if (MessagesController.getInstance().loadingDialogs && MessagesController.getInstance().dialogs.isEmpty()) { searchEmptyView.setVisibility(View.GONE); - emptyView.setVisibility(View.GONE); listView.setEmptyView(progressView); } else { progressView.setVisibility(View.GONE); if (searching && searchWas) { - emptyView.setVisibility(View.GONE); listView.setEmptyView(searchEmptyView); } else { searchEmptyView.setVisibility(View.GONE); - listView.setEmptyView(emptyView); + listView.setEmptyView(null); } } } catch (Exception e) { - FileLog.e(e); //TODO fix it in other way? + FileLog.e(e); } } } else if (id == NotificationCenter.emojiDidLoaded) { @@ -1083,6 +1389,8 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. return MessagesController.getInstance().dialogsServerOnly; } else if (dialogsType == 2) { return MessagesController.getInstance().dialogsGroupsOnly; + } else if (dialogsType == 3) { + return MessagesController.getInstance().dialogsForward; } return null; } @@ -1202,7 +1510,9 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } builder.setMessage(LocaleController.formatStringSimple(selectAlertStringGroup, chat.title)); } else { - if (lower_part > 0) { + if (lower_part == UserConfig.getClientUserId()) { + builder.setMessage(LocaleController.formatStringSimple(selectAlertStringGroup, LocaleController.getString("SavedMessages", R.string.SavedMessages))); + } else if (lower_part > 0) { TLRPC.User user = MessagesController.getInstance().getUser(lower_part); if (user == null) { return; @@ -1239,7 +1549,9 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. showDialog(builder.create()); } else { if (delegate != null) { - delegate.didSelectDialog(DialogsActivity.this, dialog_id, param); + ArrayList dids = new ArrayList<>(); + dids.add(dialog_id); + delegate.didSelectDialogs(DialogsActivity.this, dids, null, param); delegate = null; } else { finishFragment(); @@ -1291,14 +1603,14 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. new ThemeDescription(searchEmptyView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_emptyListPlaceholder), new ThemeDescription(searchEmptyView, ThemeDescription.FLAG_PROGRESSBAR, null, null, null, null, Theme.key_progressCircle), - new ThemeDescription(emptyTextView1, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_emptyListPlaceholder), - new ThemeDescription(emptyTextView2, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_emptyListPlaceholder), + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{DialogsEmptyCell.class}, new String[]{"emptyTextView1"}, null, null, null, Theme.key_emptyListPlaceholder), + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{DialogsEmptyCell.class}, new String[]{"emptyTextView2"}, null, null, null, Theme.key_emptyListPlaceholder), new ThemeDescription(floatingButton, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_chats_actionIcon), new ThemeDescription(floatingButton, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_chats_actionBackground), new ThemeDescription(floatingButton, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_chats_actionPressedBackground), - new ThemeDescription(listView, 0, new Class[]{DialogCell.class, ProfileSearchCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(listView, 0, new Class[]{DialogCell.class, ProfileSearchCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, null, Theme.key_avatar_text), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), @@ -1306,6 +1618,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundCyan), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundBlue), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundPink), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundSaved), new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, Theme.dialogs_countPaint, null, null, Theme.key_chats_unreadCounter), new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, Theme.dialogs_countGrayPaint, null, null, Theme.key_chats_unreadCounterMuted), new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, Theme.dialogs_countTextPaint, null, null, Theme.key_chats_unreadCounterText), @@ -1370,6 +1683,15 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. new ThemeDescription(fragmentContextView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{FragmentContextView.class}, new String[]{"frameLayout"}, null, null, null, Theme.key_returnToCallBackground), new ThemeDescription(fragmentContextView, 0, new Class[]{FragmentContextView.class}, new String[]{"titleTextView"}, null, null, null, Theme.key_returnToCallText), + new ThemeDescription(fragmentLocationContextView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{FragmentContextView.class}, new String[]{"frameLayout"}, null, null, null, Theme.key_inappPlayerBackground), + new ThemeDescription(fragmentLocationContextView, 0, new Class[]{FragmentContextView.class}, new String[]{"playButton"}, null, null, null, Theme.key_inappPlayerPlayPause), + new ThemeDescription(fragmentLocationContextView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{FragmentContextView.class}, new String[]{"titleTextView"}, null, null, null, Theme.key_inappPlayerTitle), + new ThemeDescription(fragmentLocationContextView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{FragmentContextView.class}, new String[]{"frameLayout"}, null, null, null, Theme.key_inappPlayerPerformer), + new ThemeDescription(fragmentLocationContextView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{FragmentContextView.class}, new String[]{"closeButton"}, null, null, null, Theme.key_inappPlayerClose), + + new ThemeDescription(fragmentLocationContextView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{FragmentContextView.class}, new String[]{"frameLayout"}, null, null, null, Theme.key_returnToCallBackground), + new ThemeDescription(fragmentLocationContextView, 0, new Class[]{FragmentContextView.class}, new String[]{"titleTextView"}, null, null, null, Theme.key_returnToCallText), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogBackground), new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogBackgroundGray), new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogTextBlack), @@ -1405,6 +1727,21 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogLineProgress), new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogLineProgressBackground), new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogGrayLine), + + new ThemeDescription(null, 0, null, null, null, null, Theme.key_player_actionBar), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_player_actionBarSelector), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_player_actionBarTitle), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_player_actionBarTop), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_player_actionBarSubtitle), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_player_actionBarItems), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_player_background), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_player_time), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_player_progressBackground), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_player_progress), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_player_placeholder), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_player_placeholderBackground), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_player_button), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_player_buttonActive), }; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/DocumentSelectActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/DocumentSelectActivity.java index b45904f80..a48377ffd 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/DocumentSelectActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/DocumentSelectActivity.java @@ -481,7 +481,6 @@ public class DocumentSelectActivity extends BaseFragment { showErrorBox(LocaleController.getString("AccessError", R.string.AccessError)); return false; } - emptyView.setText(LocaleController.getString("NoFiles", R.string.NoFiles)); File[] files; try { files = dir.listFiles(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateActivity.java index 8a5e126c8..83943edcf 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateActivity.java @@ -452,6 +452,7 @@ public class GroupCreateActivity extends BaseFragment implements NotificationCen editText.setHintColor(Theme.getColor(Theme.key_groupcreate_hintText)); editText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); editText.setCursorColor(Theme.getColor(Theme.key_groupcreate_cursor)); + editText.setCursorWidth(1.5f); editText.setInputType(InputType.TYPE_TEXT_VARIATION_FILTER | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_MULTI_LINE); editText.setSingleLine(true); editText.setBackgroundDrawable(null); @@ -505,13 +506,20 @@ public class GroupCreateActivity extends BaseFragment implements NotificationCen } }); editText.setOnKeyListener(new View.OnKeyListener() { + + private boolean wasEmpty; + @Override public boolean onKey(View v, int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_UP && editText.length() == 0 && !allSpans.isEmpty()) { - spansContainer.removeSpan(allSpans.get(allSpans.size() - 1)); - updateHint(); - checkVisibleRows(); - return true; + if (keyCode == KeyEvent.KEYCODE_DEL) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + wasEmpty = editText.length() == 0; + } else if (event.getAction() == KeyEvent.ACTION_UP && wasEmpty && !allSpans.isEmpty()){ + spansContainer.removeSpan(allSpans.get(allSpans.size() - 1)); + updateHint(); + checkVisibleRows(); + return true; + } } return false; } @@ -1103,7 +1111,7 @@ public class GroupCreateActivity extends BaseFragment implements NotificationCen new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{GroupCreateUserCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_groupcreate_checkboxCheck), new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, new Class[]{GroupCreateUserCell.class}, new String[]{"statusTextView"}, null, null, null, Theme.key_groupcreate_onlineText), new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, new Class[]{GroupCreateUserCell.class}, new String[]{"statusTextView"}, null, null, null, Theme.key_groupcreate_offlineText), - new ThemeDescription(listView, 0, new Class[]{GroupCreateUserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(listView, 0, new Class[]{GroupCreateUserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, null, Theme.key_avatar_text), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateFinalActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateFinalActivity.java index 9f7eabdc3..58a3ee607 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateFinalActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateFinalActivity.java @@ -28,7 +28,6 @@ import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; -import android.widget.EditText; import android.widget.FrameLayout; import android.widget.LinearLayout; @@ -57,6 +56,7 @@ import org.telegram.ui.Components.AvatarUpdater; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.Components.ContextProgressView; +import org.telegram.ui.Components.EditTextBoldCursor; import org.telegram.ui.Components.GroupCreateDividerItemDecoration; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.RecyclerListView; @@ -68,7 +68,7 @@ public class GroupCreateFinalActivity extends BaseFragment implements Notificati private GroupCreateAdapter adapter; private RecyclerView listView; - private EditText editText; + private EditTextBoldCursor editText; private BackupImageView avatarImage; private AvatarDrawable avatarDrawable; private ActionBarMenuItem doneItem; @@ -260,7 +260,7 @@ public class GroupCreateFinalActivity extends BaseFragment implements Notificati } }); - editText = new EditText(context); + editText = new EditTextBoldCursor(context); editText.setHint(chatType == ChatObject.CHAT_TYPE_CHAT ? LocaleController.getString("EnterGroupNamePlaceholder", R.string.EnterGroupNamePlaceholder) : LocaleController.getString("EnterListName", R.string.EnterListName)); if (nameToSet != null) { editText.setText(nameToSet); @@ -278,7 +278,9 @@ public class GroupCreateFinalActivity extends BaseFragment implements Notificati InputFilter[] inputFilters = new InputFilter[1]; inputFilters[0] = new InputFilter.LengthFilter(100); editText.setFilters(inputFilters); - AndroidUtilities.clearCursorDrawable(editText); + editText.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + editText.setCursorSize(AndroidUtilities.dp(20)); + editText.setCursorWidth(1.5f); editTextContainer.addView(editText, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, LocaleController.isRTL ? 16 : 96, 0, LocaleController.isRTL ? 96 : 16, 0)); editText.addTextChangedListener(new TextWatcher() { @Override @@ -578,7 +580,7 @@ public class GroupCreateFinalActivity extends BaseFragment implements Notificati new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{GroupCreateUserCell.class}, new String[]{"textView"}, null, null, null, Theme.key_groupcreate_sectionText), new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, new Class[]{GroupCreateUserCell.class}, new String[]{"statusTextView"}, null, null, null, Theme.key_groupcreate_onlineText), new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, new Class[]{GroupCreateUserCell.class}, new String[]{"statusTextView"}, null, null, null, Theme.key_groupcreate_offlineText), - new ThemeDescription(listView, 0, new Class[]{GroupCreateUserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, сellDelegate, Theme.key_avatar_text), + new ThemeDescription(listView, 0, new Class[]{GroupCreateUserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, сellDelegate, Theme.key_avatar_text), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/GroupInviteActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/GroupInviteActivity.java index c13d35084..a206b5397 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/GroupInviteActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/GroupInviteActivity.java @@ -112,6 +112,7 @@ public class GroupInviteActivity extends BaseFragment implements NotificationCen emptyView = new EmptyTextProgressView(context); emptyView.showProgress(); + frameLayout.addView(emptyView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); listView = new RecyclerListView(context); listView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/GroupStickersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/GroupStickersActivity.java new file mode 100644 index 000000000..38a500e9b --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/GroupStickersActivity.java @@ -0,0 +1,812 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Canvas; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.Rect; +import android.net.Uri; +import android.text.Editable; +import android.text.InputType; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextWatcher; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.Toast; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.MessagesStorage; +import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.R; +import org.telegram.messenger.query.StickersQuery; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; +import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.RequestDelegate; +import org.telegram.tgnet.TLObject; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.ActionBarMenuItem; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Cells.HeaderCell; +import org.telegram.ui.Cells.ShadowSectionCell; +import org.telegram.ui.Cells.StickerSetCell; +import org.telegram.ui.Cells.TextInfoPrivacyCell; +import org.telegram.ui.Cells.TextSettingsCell; +import org.telegram.ui.Components.ContextProgressView; +import org.telegram.ui.Components.EditTextBoldCursor; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; +import org.telegram.ui.Components.StickersAlert; +import org.telegram.ui.Components.URLSpanNoUnderline; + +import java.util.ArrayList; +import java.util.List; + +public class GroupStickersActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { + + private RecyclerListView listView; + private ListAdapter listAdapter; + private ActionBarMenuItem doneItem; + private ContextProgressView progressView; + private AnimatorSet doneItemAnimation; + private LinearLayout nameContainer; + private EditText editText; + private EditTextBoldCursor usernameTextView; + private LinearLayoutManager layoutManager; + private ImageView eraseImageView; + + private Runnable queryRunnable; + + private boolean searchWas; + private boolean searching; + + private boolean ignoreTextChanges; + + private int reqId; + + private TLRPC.TL_messages_stickerSet selectedStickerSet; + + private TLRPC.ChatFull info; + private int chatId; + + private boolean donePressed; + + private int nameRow; + private int infoRow; + private int selectedStickerRow; + private int headerRow; + private int stickersStartRow; + private int stickersEndRow; + private int stickersShadowRow; + private int rowCount; + + private final static int done_button = 1; + + public GroupStickersActivity(int id) { + super(); + chatId = id; + } + + @Override + public boolean onFragmentCreate() { + super.onFragmentCreate(); + StickersQuery.checkStickers(StickersQuery.TYPE_IMAGE); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.stickersDidLoaded); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.chatInfoDidLoaded); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.groupStickersDidLoaded); + updateRows(); + return true; + } + + @Override + public void onFragmentDestroy() { + super.onFragmentDestroy(); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.stickersDidLoaded); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.chatInfoDidLoaded); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.groupStickersDidLoaded); + } + + @Override + public View createView(Context context) { + actionBar.setBackButtonImage(R.drawable.ic_ab_back); + actionBar.setAllowOverlayTitle(true); + actionBar.setTitle(LocaleController.getString("GroupStickers", R.string.GroupStickers)); + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { + @Override + public void onItemClick(int id) { + if (id == -1) { + finishFragment(); + } else if (id == done_button) { + if (donePressed) { + return; + } + donePressed = true; + if (searching) { + showEditDoneProgress(true); + return; + } + saveStickerSet(); + } + } + }); + + ActionBarMenu menu = actionBar.createMenu(); + doneItem = menu.addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56)); + progressView = new ContextProgressView(context, 1); + doneItem.addView(progressView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + progressView.setVisibility(View.INVISIBLE); + + nameContainer = new LinearLayout(context) { + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(42), MeasureSpec.EXACTLY)); + } + + @Override + protected void onDraw(Canvas canvas) { + if (selectedStickerSet != null) { + canvas.drawLine(0, getHeight() - 1, getWidth(), getHeight() - 1, Theme.dividerPaint); + } + } + }; + nameContainer.setWeightSum(1.0f); + nameContainer.setWillNotDraw(false); + nameContainer.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + nameContainer.setOrientation(LinearLayout.HORIZONTAL); + nameContainer.setPadding(AndroidUtilities.dp(17), 0, AndroidUtilities.dp(14), 0); + + editText = new EditText(context); + editText.setText(MessagesController.getInstance().linkPrefix + "/addstickers/"); + editText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 17); + editText.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + editText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + editText.setMaxLines(1); + editText.setLines(1); + editText.setEnabled(false); + editText.setFocusable(false); + editText.setBackgroundDrawable(null); + editText.setPadding(0, 0, 0, 0); + editText.setGravity(Gravity.CENTER_VERTICAL); + editText.setSingleLine(true); + editText.setInputType(InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT); + editText.setImeOptions(EditorInfo.IME_ACTION_DONE); + nameContainer.addView(editText, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, 42)); + + usernameTextView = new EditTextBoldCursor(context); + usernameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 17); + usernameTextView.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + usernameTextView.setCursorSize(AndroidUtilities.dp(20)); + usernameTextView.setCursorWidth(1.5f); + usernameTextView.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + usernameTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + usernameTextView.setMaxLines(1); + usernameTextView.setLines(1); + usernameTextView.setBackgroundDrawable(null); + usernameTextView.setPadding(0, 0, 0, 0); + usernameTextView.setSingleLine(true); + usernameTextView.setGravity(Gravity.CENTER_VERTICAL); + usernameTextView.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT); + usernameTextView.setImeOptions(EditorInfo.IME_ACTION_DONE); + usernameTextView.setHint(LocaleController.getString("ChooseStickerSetPlaceholder", R.string.ChooseStickerSetPlaceholder)); + usernameTextView.addTextChangedListener(new TextWatcher() { + + boolean ignoreTextChange; + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + if (eraseImageView != null) { + eraseImageView.setVisibility(s.length() > 0 ? View.VISIBLE : View.INVISIBLE); + } + if (ignoreTextChange || ignoreTextChanges) { + return; + } + if (s.length() > 5) { + ignoreTextChange = true; + try { + Uri uri = Uri.parse(s.toString()); + if (uri != null) { + List segments = uri.getPathSegments(); + if (segments.size() == 2) { + if (segments.get(0).toLowerCase().equals("addstickers")) { + usernameTextView.setText(segments.get(1)); + usernameTextView.setSelection(usernameTextView.length()); + } + } + } + } catch (Exception ignore) { + + } + ignoreTextChange = false; + } + resolveStickerSet(); + } + }); + + nameContainer.addView(usernameTextView, LayoutHelper.createLinear(0, 42, 1.0f)); + + eraseImageView = new ImageView(context); + eraseImageView.setScaleType(ImageView.ScaleType.CENTER); + eraseImageView.setImageResource(R.drawable.ic_close_white); + eraseImageView.setPadding(AndroidUtilities.dp(16), 0, 0, 0); + eraseImageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText3), PorterDuff.Mode.MULTIPLY)); + eraseImageView.setVisibility(View.INVISIBLE); + eraseImageView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + searchWas = false; + selectedStickerSet = null; + usernameTextView.setText(""); + updateRows(); + } + }); + nameContainer.addView(eraseImageView, LayoutHelper.createLinear(42, 42, 0.0f)); + + if (info != null && info.stickerset != null) { + ignoreTextChanges = true; + usernameTextView.setText(info.stickerset.short_name); + usernameTextView.setSelection(usernameTextView.length()); + ignoreTextChanges = false; + } + + listAdapter = new ListAdapter(context); + + fragmentView = new FrameLayout(context); + FrameLayout frameLayout = (FrameLayout) fragmentView; + frameLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); + + listView = new RecyclerListView(context); + listView.setFocusable(true); + listView.setItemAnimator(null); + listView.setLayoutAnimation(null); + layoutManager = new LinearLayoutManager(context) { + @Override + public boolean requestChildRectangleOnScreen(RecyclerView parent, View child, Rect rect, boolean immediate, boolean focusedChildVisible) { + return false; + } + + @Override + public boolean supportsPredictiveItemAnimations() { + return false; + } + }; + layoutManager.setOrientation(LinearLayoutManager.VERTICAL); + listView.setLayoutManager(layoutManager); + + frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + listView.setAdapter(listAdapter); + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + if (getParentActivity() == null) { + return; + } + if (position == selectedStickerRow) { + if (selectedStickerSet == null) { + return; + } + showDialog(new StickersAlert(getParentActivity(), GroupStickersActivity.this, null, selectedStickerSet, null)); + } else if (position >= stickersStartRow && position < stickersEndRow) { + boolean needScroll = selectedStickerRow == -1; + int row = layoutManager.findFirstVisibleItemPosition(); + int top = Integer.MAX_VALUE; + RecyclerListView.Holder holder = (RecyclerListView.Holder) listView.findViewHolderForAdapterPosition(row); + if (holder != null) { + top = holder.itemView.getTop(); + } + selectedStickerSet = StickersQuery.getStickerSets(StickersQuery.TYPE_IMAGE).get(position - stickersStartRow); + ignoreTextChanges = true; + usernameTextView.setText(selectedStickerSet.set.short_name); + usernameTextView.setSelection(usernameTextView.length()); + ignoreTextChanges = false; + AndroidUtilities.hideKeyboard(usernameTextView); + updateRows(); + if (needScroll && top != Integer.MAX_VALUE) { + layoutManager.scrollToPositionWithOffset(row + 1, top); + } + } + } + }); + listView.setOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { + AndroidUtilities.hideKeyboard(getParentActivity().getCurrentFocus()); + } + } + + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + + } + }); + + return fragmentView; + } + + @Override + public void didReceivedNotification(int id, Object... args) { + if (id == NotificationCenter.stickersDidLoaded) { + if ((Integer) args[0] == StickersQuery.TYPE_IMAGE) { + updateRows(); + } + } else if (id == NotificationCenter.chatInfoDidLoaded) { + TLRPC.ChatFull chatFull = (TLRPC.ChatFull) args[0]; + if (chatFull.id == chatId) { + if (info == null && chatFull.stickerset != null) { + selectedStickerSet = StickersQuery.getGroupStickerSetById(chatFull.stickerset); + } + info = chatFull; + updateRows(); + } + } else if (id == NotificationCenter.groupStickersDidLoaded) { + long setId = (Long) args[0]; + if (info != null && info.stickerset != null && info.stickerset.id == id) { + updateRows(); + } + } + } + + public void setInfo(TLRPC.ChatFull chatFull) { + info = chatFull; + if (info != null && info.stickerset != null) { + selectedStickerSet = StickersQuery.getGroupStickerSetById(info.stickerset); + } + } + + private void resolveStickerSet() { + if (listAdapter == null) { + return; + } + if (reqId != 0) { + ConnectionsManager.getInstance().cancelRequest(reqId, true); + reqId = 0; + } + if (queryRunnable != null) { + AndroidUtilities.cancelRunOnUIThread(queryRunnable); + queryRunnable = null; + } + selectedStickerSet = null; + if (usernameTextView.length() <= 0) { + searching = false; + searchWas = false; + if (selectedStickerRow != -1) { + updateRows(); + } + return; + } + searching = true; + searchWas = true; + final String query = usernameTextView.getText().toString(); + TLRPC.TL_messages_stickerSet existingSet = StickersQuery.getStickerSetByName(query); + if (existingSet != null) { + selectedStickerSet = existingSet; + } + if (selectedStickerRow == -1) { + updateRows(); + } else { + listAdapter.notifyItemChanged(selectedStickerRow); + } + if (existingSet != null) { + searching = false; + return; + } + AndroidUtilities.runOnUIThread(queryRunnable = new Runnable() { + @Override + public void run() { + if (queryRunnable == null) { + return; + } + TLRPC.TL_messages_getStickerSet req = new TLRPC.TL_messages_getStickerSet(); + req.stickerset = new TLRPC.TL_inputStickerSetShortName(); + req.stickerset.short_name = query; + reqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, final TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + searching = false; + if (response instanceof TLRPC.TL_messages_stickerSet) { + selectedStickerSet = (TLRPC.TL_messages_stickerSet) response; + if (donePressed) { + saveStickerSet(); + } else { + if (selectedStickerRow != -1) { + listAdapter.notifyItemChanged(selectedStickerRow); + } else { + updateRows(); + } + } + } else { + if (selectedStickerRow != -1) { + listAdapter.notifyItemChanged(selectedStickerRow); + } + if (donePressed) { + donePressed = false; + showEditDoneProgress(false); + if (getParentActivity() != null) { + Toast.makeText(getParentActivity(), LocaleController.getString("AddStickersNotFound", R.string.AddStickersNotFound), Toast.LENGTH_SHORT).show(); + } + } + } + reqId = 0; + } + }); + } + }); + } + }, 500); + } + + @Override + public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { + if (isOpen) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (usernameTextView != null) { + usernameTextView.requestFocus(); + AndroidUtilities.showKeyboard(usernameTextView); + } + } + }, 100); + } + } + + private void saveStickerSet() { + if (info == null || info.stickerset != null && selectedStickerSet != null && selectedStickerSet.set.id == info.stickerset.id || info.stickerset == null && selectedStickerSet == null) { + finishFragment(); + return; + } + showEditDoneProgress(true); + TLRPC.TL_channels_setStickers req = new TLRPC.TL_channels_setStickers(); + req.channel = MessagesController.getInputChannel(chatId); + if (selectedStickerSet == null) { + req.stickerset = new TLRPC.TL_inputStickerSetEmpty(); + } else { + ApplicationLoader.applicationContext.getSharedPreferences("emoji", Activity.MODE_PRIVATE).edit().remove("group_hide_stickers_" + info.id).commit(); + req.stickerset = new TLRPC.TL_inputStickerSetID(); + req.stickerset.id = selectedStickerSet.set.id; + req.stickerset.access_hash = selectedStickerSet.set.access_hash; + } + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, final TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (error == null) { + if (selectedStickerSet == null) { + info.stickerset = null; + } else { + info.stickerset = selectedStickerSet.set; + StickersQuery.putGroupStickerSet(selectedStickerSet); + } + if (info.stickerset == null) { + info.flags |= 256; + } else { + info.flags = info.flags &~ 256; + } + MessagesStorage.getInstance().updateChatInfo(info, false); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatInfoDidLoaded, info, 0, true, null); + finishFragment(); + } else { + Toast.makeText(getParentActivity(), LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred) + "\n" + error.text, Toast.LENGTH_SHORT).show(); + donePressed = false; + showEditDoneProgress(false); + } + } + }); + } + }); + } + + private void updateRows() { + rowCount = 0; + nameRow = rowCount++; + if (selectedStickerSet != null || searchWas) { + selectedStickerRow = rowCount++; + } else { + selectedStickerRow = -1; + } + infoRow = rowCount++; + ArrayList stickerSets = StickersQuery.getStickerSets(StickersQuery.TYPE_IMAGE); + if (!stickerSets.isEmpty()) { + headerRow = rowCount++; + stickersStartRow = rowCount; + stickersEndRow = rowCount + stickerSets.size(); + rowCount += stickerSets.size(); + stickersShadowRow = rowCount++; + } else { + headerRow = -1; + stickersStartRow = -1; + stickersEndRow = -1; + stickersShadowRow = -1; + } + if (nameContainer != null) { + nameContainer.invalidate(); + } + if (listAdapter != null) { + listAdapter.notifyDataSetChanged(); + } + } + + @Override + public void onResume() { + super.onResume(); + if (listAdapter != null) { + listAdapter.notifyDataSetChanged(); + } + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + boolean animations = preferences.getBoolean("view_animations", true); + if (!animations) { + usernameTextView.requestFocus(); + AndroidUtilities.showKeyboard(usernameTextView); + } + } + + private void showEditDoneProgress(final boolean show) { + if (doneItem == null) { + return; + } + if (doneItemAnimation != null) { + doneItemAnimation.cancel(); + } + doneItemAnimation = new AnimatorSet(); + if (show) { + progressView.setVisibility(View.VISIBLE); + doneItem.setEnabled(false); + doneItemAnimation.playTogether( + ObjectAnimator.ofFloat(doneItem.getImageView(), "scaleX", 0.1f), + ObjectAnimator.ofFloat(doneItem.getImageView(), "scaleY", 0.1f), + ObjectAnimator.ofFloat(doneItem.getImageView(), "alpha", 0.0f), + ObjectAnimator.ofFloat(progressView, "scaleX", 1.0f), + ObjectAnimator.ofFloat(progressView, "scaleY", 1.0f), + ObjectAnimator.ofFloat(progressView, "alpha", 1.0f)); + } else { + doneItem.getImageView().setVisibility(View.VISIBLE); + doneItem.setEnabled(true); + doneItemAnimation.playTogether( + ObjectAnimator.ofFloat(progressView, "scaleX", 0.1f), + ObjectAnimator.ofFloat(progressView, "scaleY", 0.1f), + ObjectAnimator.ofFloat(progressView, "alpha", 0.0f), + ObjectAnimator.ofFloat(doneItem.getImageView(), "scaleX", 1.0f), + ObjectAnimator.ofFloat(doneItem.getImageView(), "scaleY", 1.0f), + ObjectAnimator.ofFloat(doneItem.getImageView(), "alpha", 1.0f)); + + } + doneItemAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (doneItemAnimation != null && doneItemAnimation.equals(animation)) { + if (!show) { + progressView.setVisibility(View.INVISIBLE); + } else { + doneItem.getImageView().setVisibility(View.INVISIBLE); + } + } + } + + @Override + public void onAnimationCancel(Animator animation) { + if (doneItemAnimation != null && doneItemAnimation.equals(animation)) { + doneItemAnimation = null; + } + } + }); + doneItemAnimation.setDuration(150); + doneItemAnimation.start(); + } + + private class ListAdapter extends RecyclerListView.SelectionAdapter { + + private Context mContext; + + public ListAdapter(Context context) { + mContext = context; + } + + @Override + public int getItemCount() { + return rowCount; + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case 0: { + ArrayList arrayList = StickersQuery.getStickerSets(StickersQuery.TYPE_IMAGE); + int row = position - stickersStartRow; + StickerSetCell cell = (StickerSetCell) holder.itemView; + TLRPC.TL_messages_stickerSet set = arrayList.get(row); + cell.setStickersSet(arrayList.get(row), row != arrayList.size() - 1); + long id; + if (selectedStickerSet != null) { + id = selectedStickerSet.set.id; + } else if (info != null && info.stickerset != null) { + id = info.stickerset.id; + } else { + id = 0; + } + cell.setChecked(set.set.id == id); + break; + } + case 1: { + if (position == infoRow) { + String text = LocaleController.getString("ChooseStickerSetMy", R.string.ChooseStickerSetMy); + String botName = "@stickers"; + int index = text.indexOf(botName); + if (index != -1) { + try { + SpannableStringBuilder stringBuilder = new SpannableStringBuilder(text); + URLSpanNoUnderline spanNoUnderline = new URLSpanNoUnderline("@stickers") { + @Override + public void onClick(View widget) { + MessagesController.openByUserName("stickers", GroupStickersActivity.this, 1); + } + }; + stringBuilder.setSpan(spanNoUnderline, index, index + botName.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + ((TextInfoPrivacyCell) holder.itemView).setText(stringBuilder); + } catch (Exception e) { + FileLog.e(e); + ((TextInfoPrivacyCell) holder.itemView).setText(text); + } + } else { + ((TextInfoPrivacyCell) holder.itemView).setText(text); + } + } + break; + } + case 4: { + ((HeaderCell) holder.itemView).setText(LocaleController.getString("ChooseFromYourStickers", R.string.ChooseFromYourStickers)); + break; + } + case 5: { + StickerSetCell cell = (StickerSetCell) holder.itemView; + if (selectedStickerSet != null) { + cell.setStickersSet(selectedStickerSet, false); + } else { + if (searching) { + cell.setText(LocaleController.getString("Loading", R.string.Loading), null, 0, false); + } else { + cell.setText(LocaleController.getString("ChooseStickerSetNotFound", R.string.ChooseStickerSetNotFound), LocaleController.getString("ChooseStickerSetNotFoundInfo", R.string.ChooseStickerSetNotFoundInfo), R.drawable.ic_smiles2_sad, false); + } + } + break; + } + } + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + int type = holder.getItemViewType(); + return type == 0 || type == 2 || type == 5; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = null; + switch (viewType) { + case 0: + case 5: + view = new StickerSetCell(mContext, viewType == 0 ? 3 : 2); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 1: + view = new TextInfoPrivacyCell(mContext); + view.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + break; + case 2: + view = nameContainer; + break; + case 3: + view = new ShadowSectionCell(mContext); + view.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + break; + case 4: + view = new HeaderCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + } + view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); + return new RecyclerListView.Holder(view); + } + + @Override + public int getItemViewType(int i) { + if (i >= stickersStartRow && i < stickersEndRow) { + return 0; + } else if (i == infoRow) { + return 1; + } else if (i == nameRow) { + return 2; + } else if (i == stickersShadowRow) { + return 3; + } else if (i == headerRow) { + return 4; + } else if (i == selectedStickerRow) { + return 5; + } + return 0; + } + } + + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{StickerSetCell.class, TextSettingsCell.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(editText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(editText, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(usernameTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(usernameTextView, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + new ThemeDescription(listView, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + new ThemeDescription(listView, ThemeDescription.FLAG_LINKCOLOR, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteLinkText), + + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteValueText), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ShadowSectionCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + + new ThemeDescription(nameContainer, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(listView, 0, new Class[]{StickerSetCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{StickerSetCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(listView, ThemeDescription.FLAG_USEBACKGROUNDDRAWABLE | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, new Class[]{StickerSetCell.class}, new String[]{"optionsButton"}, null, null, null, Theme.key_stickers_menuSelector), + new ThemeDescription(listView, 0, new Class[]{StickerSetCell.class}, new String[]{"optionsButton"}, null, null, null, Theme.key_stickers_menu), + }; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/IdenticonActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/IdenticonActivity.java index 5e76bc35d..206d8609c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/IdenticonActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/IdenticonActivity.java @@ -58,7 +58,6 @@ public class IdenticonActivity extends BaseFragment implements NotificationCente private TextView emojiTextView; private FrameLayout container; private LinearLayout linearLayout1; - private TextView hintTextView; private LinearLayout linearLayout; private int chat_id; @@ -114,15 +113,7 @@ public class IdenticonActivity extends BaseFragment implements NotificationCente } }); - fragmentView = new FrameLayout(context) { - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); - int x = container.getLeft() + codeTextView.getLeft() + codeTextView.getMeasuredWidth() / 2 - hintTextView.getMeasuredWidth() / 2; - int y = Math.max(AndroidUtilities.dp(5), container.getTop() + codeTextView.getTop() - AndroidUtilities.dp(10)); - hintTextView.layout(x, y, x + hintTextView.getMeasuredWidth(), y + hintTextView.getMeasuredHeight()); - } - }; + fragmentView = new FrameLayout(context); FrameLayout parentFrameLayout = (FrameLayout) fragmentView; fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); fragmentView.setOnTouchListener(new View.OnTouchListener() { @@ -179,16 +170,6 @@ public class IdenticonActivity extends BaseFragment implements NotificationCente });*/ linearLayout1.addView(codeTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL)); - hintTextView = new TextView(getParentActivity()); - hintTextView.setBackgroundDrawable(Theme.createRoundRectDrawable(AndroidUtilities.dp(3), Theme.getColor(Theme.key_chat_gifSaveHintBackground))); - hintTextView.setTextColor(Theme.getColor(Theme.key_chat_gifSaveHintText)); - hintTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - hintTextView.setPadding(AndroidUtilities.dp(10), 0, AndroidUtilities.dp(10), 0); - hintTextView.setText(LocaleController.getString("TapToEmojify", R.string.TapToEmojify)); - hintTextView.setGravity(Gravity.CENTER_VERTICAL); - hintTextView.setAlpha(0.0f); - parentFrameLayout.addView(hintTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 32)); - textView = new TextView(context); textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText4)); textView.setLinkTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteLinkText)); @@ -332,42 +313,10 @@ public class IdenticonActivity extends BaseFragment implements NotificationCente }); } - private void showHint(boolean show) { - /*SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); - if (show) { - if (preferences.getBoolean("secrethint", false)) { - return; - } - } else { - if (hintTextView.getAlpha() == 0.0f) { - return; - } - preferences.edit().putBoolean("secrethint", true).commit(); - } - if (hintAnimatorSet != null) { - hintAnimatorSet.cancel(); - } - hintAnimatorSet = new AnimatorSet(); - hintAnimatorSet.playTogether( - ObjectAnimator.ofFloat(hintTextView, "alpha", show ? 1.0f : 0.0f) - ); - hintAnimatorSet.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (animation.equals(hintAnimatorSet)) { - hintAnimatorSet = null; - } - } - }); - hintAnimatorSet.setDuration(300); - hintAnimatorSet.start();*/ - } - @Override protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) { if (isOpen && !backward && emojiText != null) { emojiTextView.setText(Emoji.replaceEmoji(emojiText, emojiTextView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(32), false)); - showHint(true); } } @@ -385,9 +334,6 @@ public class IdenticonActivity extends BaseFragment implements NotificationCente new ThemeDescription(textView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), new ThemeDescription(codeTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), new ThemeDescription(textView, ThemeDescription.FLAG_LINKCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteLinkText), - - new ThemeDescription(hintTextView, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_chat_gifSaveHintBackground), - new ThemeDescription(hintTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_chat_gifSaveHintText), }; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/IntroActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/IntroActivity.java index f26a6def0..fadf5edc5 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/IntroActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/IntroActivity.java @@ -137,6 +137,7 @@ public class IntroActivity extends Activity implements NotificationCenter.Notifi private boolean justEndDragging; private boolean dragging; private int startDragX; + private boolean destroyed; private LocaleController.LocaleInfo localeInfo; @@ -177,7 +178,7 @@ public class IntroActivity extends Activity implements NotificationCenter.Notifi frameLayout.addView(frameLayout2, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 0, 78, 0, 0)); TextureView textureView = new TextureView(this); - frameLayout2.addView(textureView, LayoutHelper.createFrame(180, 140, Gravity.CENTER)); + frameLayout2.addView(textureView, LayoutHelper.createFrame(200, 150, Gravity.CENTER)); textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { @@ -279,6 +280,7 @@ public class IntroActivity extends Activity implements NotificationCenter.Notifi Intent intent2 = new Intent(IntroActivity.this, LaunchActivity.class); intent2.putExtra("fromIntro", true); startActivity(intent2); + destroyed = true; finish(); } }); @@ -306,11 +308,12 @@ public class IntroActivity extends Activity implements NotificationCenter.Notifi if (startPressed || localeInfo == null) { return; } - LocaleController.getInstance().applyLanguage(localeInfo, true); + LocaleController.getInstance().applyLanguage(localeInfo, true, false); startPressed = true; Intent intent2 = new Intent(IntroActivity.this, LaunchActivity.class); intent2.putExtra("fromIntro", true); startActivity(intent2); + destroyed = true; finish(); } }); @@ -334,6 +337,7 @@ public class IntroActivity extends Activity implements NotificationCenter.Notifi setContentView(scrollView); } + LocaleController.getInstance().loadRemoteLanguages(); checkContinueText(); justCreated = true; NotificationCenter.getInstance().addObserver(this, NotificationCenter.suggestedLangpack); @@ -369,6 +373,7 @@ public class IntroActivity extends Activity implements NotificationCenter.Notifi @Override protected void onDestroy() { super.onDestroy(); + destroyed = true; NotificationCenter.getInstance().removeObserver(this, NotificationCenter.suggestedLangpack); SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); preferences.edit().putLong("intro_crashed_time", 0).commit(); @@ -380,14 +385,13 @@ public class IntroActivity extends Activity implements NotificationCenter.Notifi LocaleController.LocaleInfo currentLocaleInfo = LocaleController.getInstance().getCurrentLocaleInfo(); String systemLang = LocaleController.getSystemLocaleStringIso639().toLowerCase(); String arg = systemLang.contains("-") ? systemLang.split("-")[0] : systemLang; - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); - preferences.edit().putString("language_showed2", LocaleController.getSystemLocaleStringIso639().toLowerCase()).commit(); + String alias = LocaleController.getLocaleAlias(arg); for (int a = 0; a < LocaleController.getInstance().languages.size(); a++) { LocaleController.LocaleInfo info = LocaleController.getInstance().languages.get(a); if (info.shortName.equals("en")) { englishInfo = info; } - if (info.shortName.replace("_", "-").equals(systemLang) || info.shortName.equals(arg)) { + if (info.shortName.replace("_", "-").equals(systemLang) || info.shortName.equals(arg) || alias != null && info.shortName.equals(alias)) { systemInfo = info; } if (englishInfo != null && systemInfo != null) { @@ -419,7 +423,11 @@ public class IntroActivity extends Activity implements NotificationCenter.Notifi AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - textView.setText(string.value); + if (!destroyed) { + textView.setText(string.value); + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + preferences.edit().putString("language_showed2", LocaleController.getSystemLocaleStringIso639().toLowerCase()).commit(); + } } }); } @@ -703,7 +711,7 @@ public class IntroActivity extends Activity implements NotificationCenter.Notifi public void setSurfaceTextureSize(int width, int height) { surfaceWidth = width; surfaceHeight = height; - Intro.onSurfaceChanged(width, height, Math.min(surfaceWidth / 148.0f, surfaceHeight / 148.0f), 0); + Intro.onSurfaceChanged(width, height, Math.min(surfaceWidth / 150.0f, surfaceHeight / 150.0f), 0); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/InviteContactsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/InviteContactsActivity.java new file mode 100644 index 000000000..62e71a8ed --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/InviteContactsActivity.java @@ -0,0 +1,978 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.content.Intent; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.text.Editable; +import android.text.InputType; +import android.text.TextWatcher; +import android.util.TypedValue; +import android.view.ActionMode; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ContactsController; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MediaController; +import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.R; +import org.telegram.messenger.UserConfig; +import org.telegram.messenger.Utilities; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; +import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Cells.GroupCreateSectionCell; +import org.telegram.ui.Cells.InviteTextCell; +import org.telegram.ui.Cells.InviteUserCell; +import org.telegram.ui.Components.EditTextBoldCursor; +import org.telegram.ui.Components.EmptyTextProgressView; +import org.telegram.ui.Components.GroupCreateDividerItemDecoration; +import org.telegram.ui.Components.GroupCreateSpan; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Timer; +import java.util.TimerTask; + +public class InviteContactsActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, View.OnClickListener { + + private ScrollView scrollView; + private SpansContainer spansContainer; + private EditTextBoldCursor editText; + private RecyclerListView listView; + private EmptyTextProgressView emptyView; + private InviteAdapter adapter; + private TextView infoTextView; + private FrameLayout counterView; + private TextView counterTextView; + private TextView textView; + private GroupCreateDividerItemDecoration decoration; + private boolean ignoreScrollEvent; + private ArrayList phoneBookContacts; + + private int containerHeight; + + private boolean searchWas; + private boolean searching; + private HashMap selectedContacts = new HashMap<>(); + private ArrayList allSpans = new ArrayList<>(); + private GroupCreateSpan currentDeletingSpan; + + private int fieldY; + + private class SpansContainer extends ViewGroup { + + private AnimatorSet currentAnimation; + private boolean animationStarted; + private ArrayList animators = new ArrayList<>(); + private View addingSpan; + private View removingSpan; + + public SpansContainer(Context context) { + super(context); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int count = getChildCount(); + int width = MeasureSpec.getSize(widthMeasureSpec); + int maxWidth = width - AndroidUtilities.dp(32); + int currentLineWidth = 0; + int y = AndroidUtilities.dp(12); + int allCurrentLineWidth = 0; + int allY = AndroidUtilities.dp(12); + int x; + for (int a = 0; a < count; a++) { + View child = getChildAt(a); + if (!(child instanceof GroupCreateSpan)) { + continue; + } + child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(32), MeasureSpec.EXACTLY)); + if (child != removingSpan && currentLineWidth + child.getMeasuredWidth() > maxWidth) { + y += child.getMeasuredHeight() + AndroidUtilities.dp(12); + currentLineWidth = 0; + } + if (allCurrentLineWidth + child.getMeasuredWidth() > maxWidth) { + allY += child.getMeasuredHeight() + AndroidUtilities.dp(12); + allCurrentLineWidth = 0; + } + x = AndroidUtilities.dp(16) + currentLineWidth; + if (!animationStarted) { + if (child == removingSpan) { + child.setTranslationX(AndroidUtilities.dp(16) + allCurrentLineWidth); + child.setTranslationY(allY); + } else if (removingSpan != null) { + if (child.getTranslationX() != x) { + animators.add(ObjectAnimator.ofFloat(child, "translationX", x)); + } + if (child.getTranslationY() != y) { + animators.add(ObjectAnimator.ofFloat(child, "translationY", y)); + } + } else { + child.setTranslationX(x); + child.setTranslationY(y); + } + } + if (child != removingSpan) { + currentLineWidth += child.getMeasuredWidth() + AndroidUtilities.dp(9); + } + allCurrentLineWidth += child.getMeasuredWidth() + AndroidUtilities.dp(9); + } + int minWidth; + if (AndroidUtilities.isTablet()) { + minWidth = AndroidUtilities.dp(530 - 32 - 18 - 57 * 2) / 3; + } else { + minWidth = (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(32 + 18 + 57 * 2)) / 3; + } + if (maxWidth - currentLineWidth < minWidth) { + currentLineWidth = 0; + y += AndroidUtilities.dp(32 + 12); + } + if (maxWidth - allCurrentLineWidth < minWidth) { + allY += AndroidUtilities.dp(32 + 12); + } + editText.measure(MeasureSpec.makeMeasureSpec(maxWidth - currentLineWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(32), MeasureSpec.EXACTLY)); + if (!animationStarted) { + int currentHeight = allY + AndroidUtilities.dp(32 + 12); + int fieldX = currentLineWidth + AndroidUtilities.dp(16); + fieldY = y; + if (currentAnimation != null) { + int resultHeight = y + AndroidUtilities.dp(32 + 12); + if (containerHeight != resultHeight) { + animators.add(ObjectAnimator.ofInt(InviteContactsActivity.this, "containerHeight", resultHeight)); + } + if (editText.getTranslationX() != fieldX) { + animators.add(ObjectAnimator.ofFloat(editText, "translationX", fieldX)); + } + if (editText.getTranslationY() != fieldY) { + animators.add(ObjectAnimator.ofFloat(editText, "translationY", fieldY)); + } + editText.setAllowDrawCursor(false); + currentAnimation.playTogether(animators); + currentAnimation.start(); + animationStarted = true; + } else { + containerHeight = currentHeight; + editText.setTranslationX(fieldX); + editText.setTranslationY(fieldY); + } + } else if (currentAnimation != null) { + if (!ignoreScrollEvent && removingSpan == null) { + editText.bringPointIntoView(editText.getSelectionStart()); + } + } + setMeasuredDimension(width, containerHeight); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + int count = getChildCount(); + for (int a = 0; a < count; a++) { + View child = getChildAt(a); + child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight()); + } + } + + public void addSpan(final GroupCreateSpan span) { + allSpans.add(span); + selectedContacts.put(span.getKey(), span); + + editText.setHintVisible(false); + if (currentAnimation != null) { + currentAnimation.setupEndValues(); + currentAnimation.cancel(); + } + animationStarted = false; + currentAnimation = new AnimatorSet(); + currentAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + addingSpan = null; + currentAnimation = null; + animationStarted = false; + editText.setAllowDrawCursor(true); + } + }); + currentAnimation.setDuration(150); + addingSpan = span; + animators.clear(); + animators.add(ObjectAnimator.ofFloat(addingSpan, "scaleX", 0.01f, 1.0f)); + animators.add(ObjectAnimator.ofFloat(addingSpan, "scaleY", 0.01f, 1.0f)); + animators.add(ObjectAnimator.ofFloat(addingSpan, "alpha", 0.0f, 1.0f)); + addView(span); + } + + public void removeSpan(final GroupCreateSpan span) { + ignoreScrollEvent = true; + selectedContacts.remove(span.getKey()); + allSpans.remove(span); + span.setOnClickListener(null); + + if (currentAnimation != null) { + currentAnimation.setupEndValues(); + currentAnimation.cancel(); + } + animationStarted = false; + currentAnimation = new AnimatorSet(); + currentAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + removeView(span); + removingSpan = null; + currentAnimation = null; + animationStarted = false; + editText.setAllowDrawCursor(true); + if (allSpans.isEmpty()) { + editText.setHintVisible(true); + } + } + }); + currentAnimation.setDuration(150); + removingSpan = span; + animators.clear(); + animators.add(ObjectAnimator.ofFloat(removingSpan, "scaleX", 1.0f, 0.01f)); + animators.add(ObjectAnimator.ofFloat(removingSpan, "scaleY", 1.0f, 0.01f)); + animators.add(ObjectAnimator.ofFloat(removingSpan, "alpha", 1.0f, 0.0f)); + requestLayout(); + } + } + + public InviteContactsActivity() { + super(); + } + + @Override + public boolean onFragmentCreate() { + NotificationCenter.getInstance().addObserver(this, NotificationCenter.contactsImported); + fetchContacts(); + if (!UserConfig.contactsReimported) { + ContactsController.getInstance().forceImportContacts(); + UserConfig.contactsReimported = true; + UserConfig.saveConfig(false); + } + return super.onFragmentCreate(); + } + + @Override + public void onFragmentDestroy() { + super.onFragmentDestroy(); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.contactsImported); + } + + @Override + public void onClick(View v) { + GroupCreateSpan span = (GroupCreateSpan) v; + if (span.isDeleting()) { + currentDeletingSpan = null; + spansContainer.removeSpan(span); + updateHint(); + checkVisibleRows(); + } else { + if (currentDeletingSpan != null) { + currentDeletingSpan.cancelDeleteAnimation(); + } + currentDeletingSpan = span; + span.startDeleteAnimation(); + } + } + + @Override + public View createView(Context context) { + searching = false; + searchWas = false; + allSpans.clear(); + selectedContacts.clear(); + currentDeletingSpan = null; + + actionBar.setBackButtonImage(R.drawable.ic_ab_back); + actionBar.setAllowOverlayTitle(true); + actionBar.setTitle(LocaleController.getString("InviteFriends", R.string.InviteFriends)); + + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { + @Override + public void onItemClick(int id) { + if (id == -1) { + finishFragment(); + } + } + }); + fragmentView = new ViewGroup(context) { + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + setMeasuredDimension(width, height); + int maxSize; + if (AndroidUtilities.isTablet() || height > width) { + maxSize = AndroidUtilities.dp(144); + } else { + maxSize = AndroidUtilities.dp(56); + } + + int h; + infoTextView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(maxSize, MeasureSpec.AT_MOST)); + counterView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(48), MeasureSpec.EXACTLY)); + if (infoTextView.getVisibility() == VISIBLE) { + h = infoTextView.getMeasuredHeight(); + } else { + h = counterView.getMeasuredHeight(); + } + scrollView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(maxSize, MeasureSpec.AT_MOST)); + listView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height - scrollView.getMeasuredHeight() - h, MeasureSpec.EXACTLY)); + emptyView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height - scrollView.getMeasuredHeight() - AndroidUtilities.dp(72), MeasureSpec.EXACTLY)); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + scrollView.layout(0, 0, scrollView.getMeasuredWidth(), scrollView.getMeasuredHeight()); + listView.layout(0, scrollView.getMeasuredHeight(), listView.getMeasuredWidth(), scrollView.getMeasuredHeight() + listView.getMeasuredHeight()); + emptyView.layout(0, scrollView.getMeasuredHeight() + AndroidUtilities.dp(72), emptyView.getMeasuredWidth(), scrollView.getMeasuredHeight() + emptyView.getMeasuredHeight()); + int y = bottom - top - infoTextView.getMeasuredHeight(); + infoTextView.layout(0, y, infoTextView.getMeasuredWidth(), y + infoTextView.getMeasuredHeight()); + y = bottom - top - counterView.getMeasuredHeight(); + counterView.layout(0, y, counterView.getMeasuredWidth(), y + counterView.getMeasuredHeight()); + } + + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + boolean result = super.drawChild(canvas, child, drawingTime); + if (child == listView || child == emptyView) { + parentLayout.drawHeaderShadow(canvas, scrollView.getMeasuredHeight()); + } + return result; + } + }; + ViewGroup frameLayout = (ViewGroup) fragmentView; + + scrollView = new ScrollView(context) { + @Override + public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) { + if (ignoreScrollEvent) { + ignoreScrollEvent = false; + return false; + } + rectangle.offset(child.getLeft() - child.getScrollX(), child.getTop() - child.getScrollY()); + rectangle.top += fieldY + AndroidUtilities.dp(20); + rectangle.bottom += fieldY + AndroidUtilities.dp(50); + return super.requestChildRectangleOnScreen(child, rectangle, immediate); + } + }; + scrollView.setVerticalScrollBarEnabled(false); + AndroidUtilities.setScrollViewEdgeEffectColor(scrollView, Theme.getColor(Theme.key_windowBackgroundWhite)); + frameLayout.addView(scrollView); + + spansContainer = new SpansContainer(context); + scrollView.addView(spansContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + editText = new EditTextBoldCursor(context) { + @Override + public boolean onTouchEvent(MotionEvent event) { + if (currentDeletingSpan != null) { + currentDeletingSpan.cancelDeleteAnimation(); + currentDeletingSpan = null; + } + return super.onTouchEvent(event); + } + }; + editText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); + editText.setHintColor(Theme.getColor(Theme.key_groupcreate_hintText)); + editText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + editText.setCursorColor(Theme.getColor(Theme.key_groupcreate_cursor)); + editText.setCursorWidth(1.5f); + editText.setInputType(InputType.TYPE_TEXT_VARIATION_FILTER | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_MULTI_LINE); + editText.setSingleLine(true); + editText.setBackgroundDrawable(null); + editText.setVerticalScrollBarEnabled(false); + editText.setHorizontalScrollBarEnabled(false); + editText.setTextIsSelectable(false); + editText.setPadding(0, 0, 0, 0); + editText.setImeOptions(EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI); + editText.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL); + spansContainer.addView(editText); + editText.setHintText(LocaleController.getString("SearchFriends", R.string.SearchFriends)); + editText.setCustomSelectionActionModeCallback(new ActionMode.Callback() { + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } + + public void onDestroyActionMode(ActionMode mode) { + + } + + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + return false; + } + + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + return false; + } + }); + /*editText.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + return actionId == EditorInfo.IME_ACTION_DONE && onDonePressed(); + } + });*/ + editText.setOnKeyListener(new View.OnKeyListener() { + + private boolean wasEmpty; + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + wasEmpty = editText.length() == 0; + } else if (event.getAction() == KeyEvent.ACTION_UP && wasEmpty && !allSpans.isEmpty()){ + spansContainer.removeSpan(allSpans.get(allSpans.size() - 1)); + updateHint(); + checkVisibleRows(); + return true; + } + return false; + } + }); + editText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { + + } + + @Override + public void afterTextChanged(Editable editable) { + if (editText.length() != 0) { + searching = true; + searchWas = true; + adapter.setSearching(true); + adapter.searchDialogs(editText.getText().toString()); + listView.setFastScrollVisible(false); + listView.setVerticalScrollBarEnabled(true); + emptyView.setText(LocaleController.getString("NoResult", R.string.NoResult)); + } else { + closeSearch(); + } + } + }); + + emptyView = new EmptyTextProgressView(context); + if (ContactsController.getInstance().isLoadingContacts()) { + emptyView.showProgress(); + } else { + emptyView.showTextView(); + } + emptyView.setText(LocaleController.getString("NoContacts", R.string.NoContacts)); + frameLayout.addView(emptyView); + + LinearLayoutManager linearLayoutManager = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false); + + listView = new RecyclerListView(context); + listView.setEmptyView(emptyView); + listView.setAdapter(adapter = new InviteAdapter(context)); + listView.setLayoutManager(linearLayoutManager); + listView.setVerticalScrollBarEnabled(true); + listView.setVerticalScrollbarPosition(LocaleController.isRTL ? View.SCROLLBAR_POSITION_LEFT : View.SCROLLBAR_POSITION_RIGHT); + listView.addItemDecoration(decoration = new GroupCreateDividerItemDecoration()); + frameLayout.addView(listView); + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + if (position == 0 && !searching) { + try { + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("text/plain"); + String text = ContactsController.getInstance().getInviteText(0); + intent.putExtra(Intent.EXTRA_TEXT, text); + getParentActivity().startActivityForResult(Intent.createChooser(intent, text), 500); + } catch (Exception e) { + FileLog.e(e); + } + return; + } + if (!(view instanceof InviteUserCell)) { + return; + } + InviteUserCell cell = (InviteUserCell) view; + ContactsController.Contact contact = cell.getContact(); + if (contact == null) { + return; + } + boolean exists; + if (exists = selectedContacts.containsKey(contact.key)) { + GroupCreateSpan span = selectedContacts.get(contact.key); + spansContainer.removeSpan(span); + } else { + GroupCreateSpan span = new GroupCreateSpan(editText.getContext(), contact); + spansContainer.addSpan(span); + span.setOnClickListener(InviteContactsActivity.this); + } + updateHint(); + if (searching || searchWas) { + AndroidUtilities.showKeyboard(editText); + } else { + cell.setChecked(!exists, true); + } + if (editText.length() > 0) { + editText.setText(null); + } + } + }); + listView.setOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { + AndroidUtilities.hideKeyboard(editText); + } + } + }); + + infoTextView = new TextView(context); + infoTextView.setBackgroundColor(Theme.getColor(Theme.key_contacts_inviteBackground)); + infoTextView.setTextColor(Theme.getColor(Theme.key_contacts_inviteText)); + infoTextView.setGravity(Gravity.CENTER); + infoTextView.setText(LocaleController.getString("InviteFriendsHelp", R.string.InviteFriendsHelp)); + infoTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); + infoTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + infoTextView.setPadding(AndroidUtilities.dp(17), AndroidUtilities.dp(9), AndroidUtilities.dp(17), AndroidUtilities.dp(9)); + frameLayout.addView(infoTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.BOTTOM)); + + counterView = new FrameLayout(context); + counterView.setBackgroundColor(Theme.getColor(Theme.key_contacts_inviteBackground)); + counterView.setVisibility(View.INVISIBLE); + frameLayout.addView(counterView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT | Gravity.BOTTOM)); + counterView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + try { + StringBuilder builder = new StringBuilder(); + int num = 0; + for (int a = 0; a < allSpans.size(); a++) { + ContactsController.Contact contact = allSpans.get(a).getContact(); + if (builder.length() != 0) { + builder.append(';'); + } + builder.append(contact.phones.get(0)); + if (a == 0 && allSpans.size() == 1) { + num = contact.imported; + } + } + Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse("smsto:" + builder.toString())); + intent.putExtra("sms_body", ContactsController.getInstance().getInviteText(num)); + getParentActivity().startActivityForResult(intent, 500); + MediaController.getInstance().startSmsObserver(); + } catch (Exception e) { + FileLog.e(e); + } + finishFragment(); + } + }); + + LinearLayout linearLayout = new LinearLayout(context); + linearLayout.setOrientation(LinearLayout.HORIZONTAL); + counterView.addView(linearLayout, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.CENTER)); + + counterTextView = new TextView(context); + counterTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + counterTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + counterTextView.setTextColor(Theme.getColor(Theme.key_contacts_inviteBackground)); + counterTextView.setGravity(Gravity.CENTER); + counterTextView.setBackgroundDrawable(Theme.createRoundRectDrawable(AndroidUtilities.dp(10), 0xffffffff)); + counterTextView.setMinWidth(AndroidUtilities.dp(20)); + counterTextView.setPadding(AndroidUtilities.dp(6), 0, AndroidUtilities.dp(6), AndroidUtilities.dp(1)); + linearLayout.addView(counterTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, 20, Gravity.CENTER_VERTICAL, 0, 0, 10, 0)); + + textView = new TextView(context); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + textView.setTextColor(Theme.getColor(Theme.key_contacts_inviteText)); + textView.setGravity(Gravity.CENTER); + textView.setCompoundDrawablePadding(AndroidUtilities.dp(8)); + textView.setText(LocaleController.getString("InviteToTelegram", R.string.InviteToTelegram).toUpperCase()); + textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + linearLayout.addView(textView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL)); + + updateHint(); + adapter.notifyDataSetChanged(); + + return fragmentView; + } + + @Override + public void onResume() { + super.onResume(); + if (editText != null) { + editText.requestFocus(); + } + } + + @Override + public void didReceivedNotification(int id, Object... args) { + if (id == NotificationCenter.contactsImported) { + fetchContacts(); + } + } + + public void setContainerHeight(int value) { + containerHeight = value; + if (spansContainer != null) { + spansContainer.requestLayout(); + } + } + + public int getContainerHeight() { + return containerHeight; + } + + private void checkVisibleRows() { + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View child = listView.getChildAt(a); + if (child instanceof InviteUserCell) { + InviteUserCell cell = (InviteUserCell) child; + ContactsController.Contact contact = cell.getContact(); + if (contact != null) { + cell.setChecked(selectedContacts.containsKey(contact.key), true); + } + } + } + } + + private void updateHint() { + if (selectedContacts.isEmpty()) { + infoTextView.setVisibility(View.VISIBLE); + counterView.setVisibility(View.INVISIBLE); + } else { + infoTextView.setVisibility(View.INVISIBLE); + counterView.setVisibility(View.VISIBLE); + counterTextView.setText(String.format("%d", selectedContacts.size())); + } + } + + private void closeSearch() { + searching = false; + searchWas = false; + adapter.setSearching(false); + adapter.searchDialogs(null); + listView.setFastScrollVisible(true); + listView.setVerticalScrollBarEnabled(false); + emptyView.setText(LocaleController.getString("NoContacts", R.string.NoContacts)); + } + + private void fetchContacts() { + phoneBookContacts = new ArrayList<>(ContactsController.getInstance().phoneBookContacts); + Collections.sort(phoneBookContacts, new Comparator() { + @Override + public int compare(ContactsController.Contact o1, ContactsController.Contact o2) { + if (o1.imported > o2.imported) { + return -1; + } else if (o1.imported < o2.imported) { + return 1; + } + return 0; + } + }); + if (emptyView != null) { + emptyView.showTextView(); + } + if (adapter != null) { + adapter.notifyDataSetChanged(); + } + } + + public class InviteAdapter extends RecyclerListView.SelectionAdapter { + + private Context context; + private ArrayList searchResult = new ArrayList<>(); + private ArrayList searchResultNames = new ArrayList<>(); + private Timer searchTimer; + private boolean searching; + + public InviteAdapter(Context ctx) { + context = ctx; + } + + public void setSearching(boolean value) { + if (searching == value) { + return; + } + searching = value; + notifyDataSetChanged(); + } + + @Override + public int getItemCount() { + if (searching) { + return searchResult.size(); + } + return phoneBookContacts.size() + 1; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 1: + view = new InviteTextCell(context); + ((InviteTextCell) view).setTextAndIcon(LocaleController.getString("ShareTelegram", R.string.ShareTelegram), R.drawable.share); + break; + default: + view = new InviteUserCell(context, true); + break; + } + return new RecyclerListView.Holder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case 0: { + InviteUserCell cell = (InviteUserCell) holder.itemView; + ContactsController.Contact contact; + CharSequence name; + if (searching) { + contact = searchResult.get(position); + name = searchResultNames.get(position); + } else { + contact = phoneBookContacts.get(position - 1); + name = null; + } + cell.setUser(contact, name); + cell.setChecked(selectedContacts.containsKey(contact.key), false); + break; + } + } + } + + @Override + public int getItemViewType(int position) { + if (!searching) { + if (position == 0) { + return 1; + } + } + return 0; + } + + @Override + public void onViewRecycled(RecyclerView.ViewHolder holder) { + if (holder.itemView instanceof InviteUserCell) { + ((InviteUserCell) holder.itemView).recycle(); + } + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return true; + } + + public void searchDialogs(final String query) { + try { + if (searchTimer != null) { + searchTimer.cancel(); + } + } catch (Exception e) { + FileLog.e(e); + } + if (query == null) { + searchResult.clear(); + searchResultNames.clear(); + notifyDataSetChanged(); + } else { + searchTimer = new Timer(); + searchTimer.schedule(new TimerTask() { + @Override + public void run() { + try { + searchTimer.cancel(); + searchTimer = null; + } catch (Exception e) { + FileLog.e(e); + } + + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + Utilities.searchQueue.postRunnable(new Runnable() { + @Override + public void run() { + String search1 = query.trim().toLowerCase(); + if (search1.length() == 0) { + updateSearchResults(new ArrayList(), new ArrayList()); + return; + } + String search2 = LocaleController.getInstance().getTranslitString(search1); + if (search1.equals(search2) || search2.length() == 0) { + search2 = null; + } + String search[] = new String[1 + (search2 != null ? 1 : 0)]; + search[0] = search1; + if (search2 != null) { + search[1] = search2; + } + + ArrayList resultArray = new ArrayList<>(); + ArrayList resultArrayNames = new ArrayList<>(); + + for (int a = 0; a < phoneBookContacts.size(); a++) { + ContactsController.Contact contact = phoneBookContacts.get(a); + + String name = ContactsController.formatName(contact.first_name, contact.last_name).toLowerCase(); + String tName = LocaleController.getInstance().getTranslitString(name); + if (name.equals(tName)) { + tName = null; + } + + int found = 0; + for (String q : search) { + if (name.startsWith(q) || name.contains(" " + q) || tName != null && (tName.startsWith(q) || tName.contains(" " + q))) { + found = 1; + } + + if (found != 0) { + resultArrayNames.add(AndroidUtilities.generateSearchName(contact.first_name, contact.last_name, q)); + resultArray.add(contact); + break; + } + } + } + updateSearchResults(resultArray, resultArrayNames); + } + }); + } + }); + + } + }, 200, 300); + } + } + + private void updateSearchResults(final ArrayList users, final ArrayList names) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + searchResult = users; + searchResultNames = names; + notifyDataSetChanged(); + } + }); + } + + @Override + public void notifyDataSetChanged() { + super.notifyDataSetChanged(); + int count = getItemCount(); + emptyView.setVisibility(count == 1 ? View.VISIBLE : View.INVISIBLE); + decoration.setSingle(count == 1); + } + } + + @Override + public ThemeDescription[] getThemeDescriptions() { + ThemeDescription.ThemeDescriptionDelegate сellDelegate = new ThemeDescription.ThemeDescriptionDelegate() { + @Override + public void didSetColor(int color) { + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View child = listView.getChildAt(a); + if (child instanceof InviteUserCell) { + ((InviteUserCell) child).update(0); + } + } + } + }; + + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(scrollView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_FASTSCROLL, null, null, null, null, Theme.key_fastScrollActive), + new ThemeDescription(listView, ThemeDescription.FLAG_FASTSCROLL, null, null, null, null, Theme.key_fastScrollInactive), + new ThemeDescription(listView, ThemeDescription.FLAG_FASTSCROLL, null, null, null, null, Theme.key_fastScrollText), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(emptyView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_emptyListPlaceholder), + new ThemeDescription(emptyView, ThemeDescription.FLAG_PROGRESSBAR, null, null, null, null, Theme.key_progressCircle), + + new ThemeDescription(editText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(editText, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_groupcreate_hintText), + new ThemeDescription(editText, ThemeDescription.FLAG_CURSORCOLOR, null, null, null, null, Theme.key_groupcreate_cursor), + + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{GroupCreateSectionCell.class}, null, null, null, Theme.key_graySection), + new ThemeDescription(listView, 0, new Class[]{GroupCreateSectionCell.class}, new String[]{"drawable"}, null, null, null, Theme.key_groupcreate_sectionShadow), + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{GroupCreateSectionCell.class}, new String[]{"textView"}, null, null, null, Theme.key_groupcreate_sectionText), + + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{InviteUserCell.class}, new String[]{"textView"}, null, null, null, Theme.key_groupcreate_sectionText), + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{InviteUserCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_groupcreate_checkbox), + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{InviteUserCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_groupcreate_checkboxCheck), + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, new Class[]{InviteUserCell.class}, new String[]{"statusTextView"}, null, null, null, Theme.key_groupcreate_onlineText), + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, new Class[]{InviteUserCell.class}, new String[]{"statusTextView"}, null, null, null, Theme.key_groupcreate_offlineText), + new ThemeDescription(listView, 0, new Class[]{InviteUserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundGreen), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundCyan), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundBlue), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundPink), + + new ThemeDescription(listView, 0, new Class[]{InviteTextCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{InviteTextCell.class}, new String[]{"imageView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayIcon), + + new ThemeDescription(spansContainer, 0, new Class[]{GroupCreateSpan.class}, null, null, null, Theme.key_avatar_backgroundGroupCreateSpanBlue), + new ThemeDescription(spansContainer, 0, new Class[]{GroupCreateSpan.class}, null, null, null, Theme.key_groupcreate_spanBackground), + new ThemeDescription(spansContainer, 0, new Class[]{GroupCreateSpan.class}, null, null, null, Theme.key_groupcreate_spanText), + new ThemeDescription(spansContainer, 0, new Class[]{GroupCreateSpan.class}, null, null, null, Theme.key_avatar_backgroundBlue), + + new ThemeDescription(infoTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_contacts_inviteText), + new ThemeDescription(infoTextView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_contacts_inviteBackground), + new ThemeDescription(counterView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_contacts_inviteBackground), + new ThemeDescription(counterTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_contacts_inviteBackground), + new ThemeDescription(textView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_contacts_inviteText), + }; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LanguageSelectActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LanguageSelectActivity.java index 70fdc5950..999e67e4f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LanguageSelectActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LanguageSelectActivity.java @@ -18,6 +18,7 @@ import android.widget.FrameLayout; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; +import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; import org.telegram.messenger.Utilities; import org.telegram.messenger.support.widget.LinearLayoutManager; @@ -40,7 +41,7 @@ import java.util.Comparator; import java.util.Timer; import java.util.TimerTask; -public class LanguageSelectActivity extends BaseFragment { +public class LanguageSelectActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { private ListAdapter listAdapter; private RecyclerListView listView; @@ -57,9 +58,17 @@ public class LanguageSelectActivity extends BaseFragment { @Override public boolean onFragmentCreate() { fillLanguages(); + LocaleController.getInstance().loadRemoteLanguages(); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.suggestedLangpack); return super.onFragmentCreate(); } + @Override + public void onFragmentDestroy() { + super.onFragmentDestroy(); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.suggestedLangpack); + } + @Override public View createView(Context context) { searching = false; @@ -132,6 +141,9 @@ public class LanguageSelectActivity extends BaseFragment { listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override public void onItemClick(View view, int position) { + if (getParentActivity() == null || parentLayout == null) { + return; + } LocaleController.LocaleInfo localeInfo = null; if (searching && searchWas) { if (position >= 0 && position < searchResult.size()) { @@ -143,7 +155,7 @@ public class LanguageSelectActivity extends BaseFragment { } } if (localeInfo != null) { - LocaleController.getInstance().applyLanguage(localeInfo, true); + LocaleController.getInstance().applyLanguage(localeInfo, true, false, false, true); parentLayout.rebuildAllFragmentViews(false, false); } finishFragment(); @@ -205,6 +217,16 @@ public class LanguageSelectActivity extends BaseFragment { return fragmentView; } + @Override + public void didReceivedNotification(int id, Object... args) { + if (id == NotificationCenter.suggestedLangpack) { + if (listAdapter != null) { + fillLanguages(); + listAdapter.notifyDataSetChanged(); + } + } + } + private void fillLanguages() { sortedLanguages = new ArrayList<>(LocaleController.getInstance().languages); final LocaleController.LocaleInfo currentLocale = LocaleController.getInstance().getCurrentLocaleInfo(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java index 4f5506095..504bc7575 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java @@ -10,6 +10,7 @@ package org.telegram.ui; import android.annotation.TargetApi; import android.app.Activity; +import android.app.ActivityManager; import android.content.ContentResolver; import android.content.DialogInterface; import android.content.Intent; @@ -24,6 +25,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Parcelable; +import android.os.StatFs; import android.provider.ContactsContract; import android.text.TextUtils; import android.view.ActionMode; @@ -45,7 +47,9 @@ import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.messenger.BuildVars; import org.telegram.messenger.ChatObject; import org.telegram.messenger.ContactsController; +import org.telegram.messenger.FileLoader; import org.telegram.messenger.ImageLoader; +import org.telegram.messenger.LocationController; import org.telegram.messenger.MediaController; import org.telegram.messenger.MessageObject; import org.telegram.messenger.MessagesController; @@ -74,17 +78,21 @@ import org.telegram.ui.ActionBar.ActionBarLayout; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.DrawerLayoutContainer; import org.telegram.ui.Cells.LanguageCell; +import org.telegram.ui.Components.AudioPlayerAlert; +import org.telegram.ui.Components.AlertsCreator; import org.telegram.ui.Components.EmbedBottomSheet; import org.telegram.ui.Components.JoinGroupAlert; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.PasscodeView; import org.telegram.ui.Components.PipRoundVideoView; import org.telegram.ui.Components.RecyclerListView; +import org.telegram.ui.Components.SharingLocationsAlert; import org.telegram.ui.Components.StickersAlert; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.ThemeEditorView; import java.io.BufferedReader; +import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; @@ -97,7 +105,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa private boolean finished; private String videoPath; private String sendingText; - private ArrayList photoPathsArray; + private ArrayList photoPathsArray; private ArrayList documentsPathsArray; private ArrayList documentsUrisArray; private String documentsMimeType; @@ -169,6 +177,14 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa requestWindowFeature(Window.FEATURE_NO_TITLE); setTheme(R.style.Theme_TMessages); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + try { + setTaskDescription(new ActivityManager.TaskDescription(null, null, Theme.getColor(Theme.key_actionBarDefault) | 0xff000000)); + } catch (Exception e) { + // + } + } + getWindow().setBackgroundDrawableResource(R.drawable.transparent); if (UserConfig.passcodeHash.length() > 0 && !UserConfig.allowScreenCapture) { try { @@ -385,30 +401,8 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa presentFragment(new ContactsActivity(null)); drawerLayoutContainer.closeDrawer(false); } else if (id == 7) { - if (BuildVars.DEBUG_PRIVATE_VERSION) { - /*AlertDialog.Builder builder = new AlertDialog.Builder(LaunchActivity.this); - builder.setTopImage(R.drawable.permissions_contacts, 0xff35a8e0); - builder.setMessage(AndroidUtilities.replaceTags(LocaleController.getString("ContactsPermissionAlert", R.string.ContactsPermissionAlert))); - builder.setPositiveButton(LocaleController.getString("ContactsPermissionAlertContinue", R.string.ContactsPermissionAlertContinue), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - - } - }); - builder.setNegativeButton(LocaleController.getString("ContactsPermissionAlertNotNow", R.string.ContactsPermissionAlertNotNow), null); - showAlertDialog(builder);*/ - showLanguageAlert(true); - } else { - try { - Intent intent = new Intent(Intent.ACTION_SEND); - intent.setType("text/plain"); - intent.putExtra(Intent.EXTRA_TEXT, ContactsController.getInstance().getInviteText()); - startActivityForResult(Intent.createChooser(intent, LocaleController.getString("InviteFriends", R.string.InviteFriends)), 500); - } catch (Exception e) { - FileLog.e(e); - } - drawerLayoutContainer.closeDrawer(false); - } + presentFragment(new InviteContactsActivity()); + drawerLayoutContainer.closeDrawer(false); } else if (id == 8) { presentFragment(new SettingsActivity()); drawerLayoutContainer.closeDrawer(false); @@ -418,6 +412,11 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa } else if (id == 10) { presentFragment(new CallLogActivity()); drawerLayoutContainer.closeDrawer(false); + } else if (id == 11) { + Bundle args = new Bundle(); + args.putInt("user_id", UserConfig.getClientUserId()); + presentFragment(new ChatActivity(args)); + drawerLayoutContainer.closeDrawer(false); } } }); @@ -445,6 +444,9 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa NotificationCenter.getInstance().addObserver(this, NotificationCenter.didSetPasscode); NotificationCenter.getInstance().addObserver(this, NotificationCenter.reloadInterface); NotificationCenter.getInstance().addObserver(this, NotificationCenter.suggestedLangpack); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.openArticle); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.hasNewContactsToImport); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.didSetNewTheme); if (actionBarLayout.fragmentsStack.isEmpty()) { if (!UserConfig.isClientActivated()) { @@ -688,6 +690,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa long dialogId = intent != null && intent.getExtras() != null ? intent.getExtras().getLong("dialogId", 0) : 0; boolean showDialogsList = false; boolean showPlayer = false; + boolean showLocations = false; photoPathsArray = null; videoPath = null; @@ -835,7 +838,9 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa if (photoPathsArray == null) { photoPathsArray = new ArrayList<>(); } - photoPathsArray.add(uri); + SendMessagesHelper.SendingMediaInfo info = new SendMessagesHelper.SendingMediaInfo(); + info.uri = uri; + photoPathsArray.add(info); } else { path = AndroidUtilities.getPath(uri); if (path != null) { @@ -902,7 +907,9 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa if (photoPathsArray == null) { photoPathsArray = new ArrayList<>(); } - photoPathsArray.add(uri); + SendMessagesHelper.SendingMediaInfo info = new SendMessagesHelper.SendingMediaInfo(); + info.uri = uri; + photoPathsArray.add(info); } } else { for (int a = 0; a < uris.size(); a++) { @@ -1137,6 +1144,8 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa } } else if (intent.getAction().equals("com.tmessages.openplayer")) { showPlayer = true; + } else if (intent.getAction().equals("org.tmessages.openlocations")) { + showLocations = true; } } } @@ -1185,29 +1194,31 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa pushOpened = false; isNew = false; } else if (showPlayer) { - if (AndroidUtilities.isTablet()) { - for (int a = 0; a < layersActionBarLayout.fragmentsStack.size(); a++) { - BaseFragment fragment = layersActionBarLayout.fragmentsStack.get(a); - if (fragment instanceof AudioPlayerActivity) { - layersActionBarLayout.removeFragmentFromStack(fragment); - break; - } - } - actionBarLayout.showLastFragment(); - rightActionBarLayout.showLastFragment(); - drawerLayoutContainer.setAllowOpenDrawer(false, false); - } else { - for (int a = 0; a < actionBarLayout.fragmentsStack.size(); a++) { - BaseFragment fragment = actionBarLayout.fragmentsStack.get(a); - if (fragment instanceof AudioPlayerActivity) { - actionBarLayout.removeFragmentFromStack(fragment); - break; - } - } - drawerLayoutContainer.setAllowOpenDrawer(true, false); + if (!actionBarLayout.fragmentsStack.isEmpty()) { + BaseFragment fragment = actionBarLayout.fragmentsStack.get(0); + fragment.showDialog(new AudioPlayerAlert(this)); } - actionBarLayout.presentFragment(new AudioPlayerActivity(), false, true, true); - pushOpened = true; + pushOpened = false; + } else if (showLocations) { + if (!actionBarLayout.fragmentsStack.isEmpty()) { + BaseFragment fragment = actionBarLayout.fragmentsStack.get(0); + fragment.showDialog(new SharingLocationsAlert(this, new SharingLocationsAlert.SharingLocationsAlertDelegate() { + @Override + public void didSelectLocation(LocationController.SharingLocationInfo info) { + LocationActivity locationActivity = new LocationActivity(2); + locationActivity.setMessageObject(info.messageObject); + final long dialog_id = info.messageObject.getDialogId(); + locationActivity.setDelegate(new LocationActivity.LocationActivityDelegate() { + @Override + public void didSelectLocation(TLRPC.MessageMedia location, int live) { + SendMessagesHelper.getInstance().sendMessage(location, dialog_id, null, null, null); + } + }); + presentFragment(locationActivity); + } + })); + } + pushOpened = false; } else if (videoPath != null || photoPathsArray != null || sendingText != null || documentsPathsArray != null || contactsToSend != null || documentsUrisArray != null) { if (!AndroidUtilities.isTablet()) { NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); @@ -1215,6 +1226,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa if (dialogId == 0) { Bundle args = new Bundle(); args.putBoolean("onlySelect", true); + args.putInt("dialogsType", 3); if (contactsToSend != null) { args.putString("selectAlertString", LocaleController.getString("SendContactTo", R.string.SendMessagesTo)); args.putString("selectAlertStringGroup", LocaleController.getString("SendContactToGroup", R.string.SendContactToGroup)); @@ -1248,7 +1260,9 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa drawerLayoutContainer.setAllowOpenDrawer(true, false); } } else { - didSelectDialog(null, dialogId, false); + ArrayList dids = new ArrayList<>(); + dids.add(dialogId); + didSelectDialogs(null, dids, null, false); } } else if (open_settings != 0) { actionBarLayout.presentFragment(new SettingsActivity(), false, true, true); @@ -1352,17 +1366,18 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa DialogsActivity fragment = new DialogsActivity(args); fragment.setDelegate(new DialogsActivity.DialogsActivityDelegate() { @Override - public void didSelectDialog(DialogsActivity fragment, long dialog_id, boolean param) { + public void didSelectDialogs(DialogsActivity fragment, ArrayList dids, CharSequence message, boolean param) { + long did = dids.get(0); TLRPC.TL_inputMediaGame inputMediaGame = new TLRPC.TL_inputMediaGame(); inputMediaGame.id = new TLRPC.TL_inputGameShortName(); inputMediaGame.id.short_name = game; inputMediaGame.id.bot_id = MessagesController.getInputUser(res.users.get(0)); - SendMessagesHelper.getInstance().sendGame(MessagesController.getInputPeer((int) dialog_id), inputMediaGame, 0, 0); + SendMessagesHelper.getInstance().sendGame(MessagesController.getInputPeer((int) did), inputMediaGame, 0, 0); Bundle args = new Bundle(); args.putBoolean("scrollToTopOnResume", true); - int lower_part = (int) dialog_id; - int high_id = (int) (dialog_id >> 32); + int lower_part = (int) did; + int high_id = (int) (did >> 32); if (lower_part != 0) { if (high_id == 1) { args.putInt("chat_id", lower_part); @@ -1420,7 +1435,8 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa DialogsActivity fragment = new DialogsActivity(args); fragment.setDelegate(new DialogsActivity.DialogsActivityDelegate() { @Override - public void didSelectDialog(DialogsActivity fragment, long did, boolean param) { + public void didSelectDialogs(DialogsActivity fragment, ArrayList dids, CharSequence message, boolean param) { + long did = dids.get(0); Bundle args = new Bundle(); args.putBoolean("scrollToTopOnResume", true); args.putInt("chat_id", -(int) did); @@ -1510,11 +1526,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa } else { AlertDialog.Builder builder = new AlertDialog.Builder(LaunchActivity.this); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); - if (!invite.megagroup && invite.channel || ChatObject.isChannel(invite.chat) && !invite.chat.megagroup) { - builder.setMessage(LocaleController.formatString("ChannelJoinTo", R.string.ChannelJoinTo, invite.chat != null ? invite.chat.title : invite.title)); - } else { - builder.setMessage(LocaleController.formatString("JoinToGroup", R.string.JoinToGroup, invite.chat != null ? invite.chat.title : invite.title)); - } + builder.setMessage(LocaleController.formatString("ChannelJoinTo", R.string.ChannelJoinTo, invite.chat != null ? invite.chat.title : invite.title)); builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { @@ -1611,7 +1623,8 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa DialogsActivity fragment = new DialogsActivity(args); fragment.setDelegate(new DialogsActivity.DialogsActivityDelegate() { @Override - public void didSelectDialog(DialogsActivity fragment, long did, boolean param) { + public void didSelectDialogs(DialogsActivity fragment, ArrayList dids, CharSequence m, boolean param) { + long did = dids.get(0); Bundle args = new Bundle(); args.putBoolean("scrollToTopOnResume", true); args.putBoolean("hasUrl", hasUrl); @@ -1655,7 +1668,11 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa } } }); - progressDialog.show(); + try { + progressDialog.show(); + } catch (Exception ignore) { + + } } } @@ -1700,89 +1717,66 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa } @Override - public void didSelectDialog(DialogsActivity dialogsFragment, long dialog_id, boolean param) { - if (dialog_id != 0) { - int lower_part = (int) dialog_id; - int high_id = (int) (dialog_id >> 32); + public void didSelectDialogs(DialogsActivity dialogsFragment, ArrayList dids, CharSequence message, boolean param) { + long did = dids.get(0); + int lower_part = (int) did; + int high_id = (int) (did >> 32); - Bundle args = new Bundle(); - args.putBoolean("scrollToTopOnResume", true); - if (!AndroidUtilities.isTablet()) { - NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); - } - if (lower_part != 0) { - if (high_id == 1) { - args.putInt("chat_id", lower_part); - } else { - if (lower_part > 0) { - args.putInt("user_id", lower_part); - } else if (lower_part < 0) { - args.putInt("chat_id", -lower_part); - } - } - } else { - args.putInt("enc_id", high_id); - } - if (!MessagesController.checkCanOpenChat(args, dialogsFragment)) { - return; - } - ChatActivity fragment = new ChatActivity(args); - - if (videoPath != null) { - if (AndroidUtilities.isTablet()) { - if (tabletFullSize) { - actionBarLayout.presentFragment(fragment, false, true, false); - } else { - rightActionBarLayout.removeAllFragments(); - rightActionBarLayout.addFragmentToStack(fragment); - rightActionBarLayout.setVisibility(View.VISIBLE); - rightActionBarLayout.showLastFragment(); - } - } else { - actionBarLayout.addFragmentToStack(fragment, dialogsFragment != null ? actionBarLayout.fragmentsStack.size() - 1 : actionBarLayout.fragmentsStack.size()); - } - - if (!fragment.openVideoEditor(videoPath, dialogsFragment != null, false) && !AndroidUtilities.isTablet()) { - if (dialogsFragment != null) { - dialogsFragment.finishFragment(true); - } else { - actionBarLayout.showLastFragment(); - } - } - } else { - actionBarLayout.presentFragment(fragment, dialogsFragment != null, dialogsFragment == null, true); - - if (photoPathsArray != null) { - ArrayList captions = null; - if (sendingText != null && sendingText.length() <= 200 && photoPathsArray.size() == 1) { - captions = new ArrayList<>(); - captions.add(sendingText); - sendingText = null; - } - SendMessagesHelper.prepareSendingPhotos(null, photoPathsArray, dialog_id, null, captions, null, null, false, null); - } - - if (sendingText != null) { - SendMessagesHelper.prepareSendingText(sendingText, dialog_id); - } - - if (documentsPathsArray != null || documentsUrisArray != null) { - SendMessagesHelper.prepareSendingDocuments(documentsPathsArray, documentsOriginalPathsArray, documentsUrisArray, documentsMimeType, dialog_id, null, null); - } - if (contactsToSend != null && !contactsToSend.isEmpty()) { - for (TLRPC.User user : contactsToSend) { - SendMessagesHelper.getInstance().sendMessage(user, dialog_id, null, null, null); - } - } - } - - photoPathsArray = null; - videoPath = null; - sendingText = null; - documentsPathsArray = null; - documentsOriginalPathsArray = null; - contactsToSend = null; + Bundle args = new Bundle(); + args.putBoolean("scrollToTopOnResume", true); + if (!AndroidUtilities.isTablet()) { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); } + if (lower_part != 0) { + if (high_id == 1) { + args.putInt("chat_id", lower_part); + } else { + if (lower_part > 0) { + args.putInt("user_id", lower_part); + } else if (lower_part < 0) { + args.putInt("chat_id", -lower_part); + } + } + } else { + args.putInt("enc_id", high_id); + } + if (!MessagesController.checkCanOpenChat(args, dialogsFragment)) { + return; + } + ChatActivity fragment = new ChatActivity(args); + + actionBarLayout.presentFragment(fragment, dialogsFragment != null, dialogsFragment == null, true); + if (videoPath != null) { + fragment.openVideoEditor(videoPath, sendingText); + sendingText = null; + } + + if (photoPathsArray != null) { + if (sendingText != null && sendingText.length() <= 200 && photoPathsArray.size() == 1) { + photoPathsArray.get(0).caption = sendingText; + } + SendMessagesHelper.prepareSendingMedia(photoPathsArray, did, null, null, false, false); + } + + if (sendingText != null) { + SendMessagesHelper.prepareSendingText(sendingText, did); + } + + if (documentsPathsArray != null || documentsUrisArray != null) { + SendMessagesHelper.prepareSendingDocuments(documentsPathsArray, documentsOriginalPathsArray, documentsUrisArray, documentsMimeType, did, null, null); + } + if (contactsToSend != null && !contactsToSend.isEmpty()) { + for (TLRPC.User user : contactsToSend) { + SendMessagesHelper.getInstance().sendMessage(user, did, null, null, null); + } + } + + photoPathsArray = null; + videoPath = null; + sendingText = null; + documentsPathsArray = null; + documentsOriginalPathsArray = null; + contactsToSend = null; } private void onFinish() { @@ -1804,6 +1798,9 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didSetPasscode); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.reloadInterface); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.suggestedLangpack); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.openArticle); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.hasNewContactsToImport); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didSetNewTheme); } public void presentFragment(BaseFragment fragment) { @@ -1863,10 +1860,12 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa ImageLoader.getInstance().checkMediaPaths(); return; } else if (requestCode == 5) { - ContactsController.getInstance().readContacts(); + ContactsController.getInstance().forceImportContacts(); return; } else if (requestCode == 3) { - CameraController.getInstance().initCamera(); + if (MediaController.getInstance().canInAppCamera()) { + CameraController.getInstance().initCamera(); + } return; } else if (requestCode == 19 || requestCode == 20) { showAlert = false; @@ -2014,6 +2013,8 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa ApplicationLoader.mainInterfacePausedStageQueueTime = System.currentTimeMillis(); } }); + checkFreeDiscSpace(); + MediaController.checkGallery(); onPasscodeResume(); if (passcodeView.getVisibility() != View.VISIBLE) { actionBarLayout.onResume(); @@ -2149,10 +2150,10 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa if (!AndroidUtilities.isGoogleMapsInstalled(lastFragment)) { return; } - LocationActivity fragment = new LocationActivity(); + LocationActivity fragment = new LocationActivity(0); fragment.setDelegate(new LocationActivity.LocationActivityDelegate() { @Override - public void didSelectLocation(TLRPC.MessageMedia location) { + public void didSelectLocation(TLRPC.MessageMedia location, int live) { for (HashMap.Entry entry : waitingForLocation.entrySet()) { MessageObject messageObject = entry.getValue(); SendMessagesHelper.getInstance().sendMessage(location, messageObject.getDialogId(), messageObject, null, null); @@ -2191,6 +2192,59 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa rebuildAllFragments(true); } else if (id == NotificationCenter.suggestedLangpack) { showLanguageAlert(false); + } else if (id == NotificationCenter.openArticle) { + if (mainFragmentsStack.isEmpty()) { + return; + } + ArticleViewer.getInstance().setParentActivity(this, mainFragmentsStack.get(mainFragmentsStack.size() - 1)); + ArticleViewer.getInstance().open((TLRPC.TL_webPage) args[0], (String) args[1]); + } else if (id == NotificationCenter.hasNewContactsToImport) { + if (actionBarLayout == null || actionBarLayout.fragmentsStack.isEmpty()) { + return; + } + final int type = (Integer) args[0]; + final HashMap contactHashMap = (HashMap) args[1]; + final boolean first = (Boolean) args[2]; + final boolean schedule = (Boolean) args[3]; + BaseFragment fragment = actionBarLayout.fragmentsStack.get(actionBarLayout.fragmentsStack.size() - 1); + + AlertDialog.Builder builder = new AlertDialog.Builder(LaunchActivity.this); + builder.setTitle(LocaleController.getString("UpdateContactsTitle", R.string.UpdateContactsTitle)); + builder.setMessage(LocaleController.getString("UpdateContactsMessage", R.string.UpdateContactsMessage)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + ContactsController.getInstance().syncPhoneBookByAlert(contactHashMap, first, schedule, false); + } + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + ContactsController.getInstance().syncPhoneBookByAlert(contactHashMap, first, schedule, true); + } + }); + builder.setOnBackButtonListener(new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + ContactsController.getInstance().syncPhoneBookByAlert(contactHashMap, first, schedule, true); + } + }); + AlertDialog dialog = builder.create(); + fragment.showDialog(dialog); + dialog.setCanceledOnTouchOutside(false); + } else if (id == NotificationCenter.didSetNewTheme) { + if (sideMenu != null) { + sideMenu.setBackgroundColor(Theme.getColor(Theme.key_chats_menuBackground)); + sideMenu.setGlowColor(Theme.getColor(Theme.key_chats_menuBackground)); + sideMenu.getAdapter().notifyDataSetChanged(); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + try { + setTaskDescription(new ActivityManager.TaskDescription(null, null, Theme.getColor(Theme.key_actionBarDefault) | 0xff000000)); + } catch (Exception e) { + // + } + } } } @@ -2202,6 +2256,51 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa return value; } + private void checkFreeDiscSpace() { + if (Build.VERSION.SDK_INT >= 26) { + return; + } + Utilities.globalQueue.postRunnable(new Runnable() { + @Override + public void run() { + if (!UserConfig.isClientActivated()) { + return; + } + try { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + if (Math.abs(preferences.getLong("last_space_check", 0) - System.currentTimeMillis()) >= 3 * 24 * 3600 * 1000) { + File path = FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE); + if (path == null) { + return; + } + long freeSpace; + StatFs statFs = new StatFs(path.getAbsolutePath()); + if (android.os.Build.VERSION.SDK_INT < 18) { + freeSpace = Math.abs(statFs.getAvailableBlocks() * statFs.getBlockSize()); + } else { + freeSpace = statFs.getAvailableBlocksLong() * statFs.getBlockSizeLong(); + } + preferences.edit().putLong("last_space_check", System.currentTimeMillis()).commit(); + if (freeSpace < 1024 * 1024 * 100) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + try { + AlertsCreator.createFreeSpaceDialog(LaunchActivity.this).show(); + } catch (Throwable ignore) { + + } + } + }); + } + } + } catch (Throwable ignore) { + + } + } + }, 2000); + } + private void showLanguageAlertInternal(LocaleController.LocaleInfo systemInfo, LocaleController.LocaleInfo englishInfo, String systemLang) { try { loadingLocaleDialog = false; @@ -2256,7 +2355,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa builder.setNegativeButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - LocaleController.getInstance().applyLanguage(selectedLanguage[0], true); + LocaleController.getInstance().applyLanguage(selectedLanguage[0], true, false); rebuildAllFragments(true); } }); @@ -2277,17 +2376,28 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa String showedLang = preferences.getString("language_showed2", ""); final String systemLang = LocaleController.getSystemLocaleStringIso639().toLowerCase(); if (!force && showedLang.equals(systemLang)) { + FileLog.d("alert already showed for " + showedLang); return; } final LocaleController.LocaleInfo infos[] = new LocaleController.LocaleInfo[2]; String arg = systemLang.contains("-") ? systemLang.split("-")[0] : systemLang; + String alias; + if ("in".equals(arg)) { + alias = "id"; + } else if ("iw".equals(arg)) { + alias = "he"; + } else if ("jw".equals(arg)) { + alias = "jv"; + } else { + alias = null; + } for (int a = 0; a < LocaleController.getInstance().languages.size(); a++) { LocaleController.LocaleInfo info = LocaleController.getInstance().languages.get(a); if (info.shortName.equals("en")) { infos[0] = info; } - if (info.shortName.replace("_", "-").equals(systemLang) || info.shortName.equals(arg)) { + if (info.shortName.replace("_", "-").equals(systemLang) || info.shortName.equals(arg) || alias != null && info.shortName.equals(alias)) { infos[1] = info; } if (infos[0] != null && infos[1] != null) { @@ -2297,6 +2407,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa if (infos[0] == null || infos[1] == null || infos[0] == infos[1]) { return; } + FileLog.d("show lang alert for " + infos[0].getKey() + " and " + infos[1].getKey()); systemLocaleStrings = null; englishLocaleStrings = null; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LocationActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LocationActivity.java index 5a4e971cd..97a6c3df4 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LocationActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LocationActivity.java @@ -19,16 +19,23 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; import android.graphics.Outline; +import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; +import android.graphics.RectF; +import android.graphics.Shader; import android.graphics.drawable.Drawable; import android.location.Location; import android.location.LocationManager; import android.net.Uri; import android.os.Build; -import android.text.TextUtils; -import android.util.TypedValue; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; @@ -37,7 +44,6 @@ import android.view.ViewOutlineProvider; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ImageView; -import android.widget.TextView; import com.google.android.gms.maps.CameraUpdate; import com.google.android.gms.maps.CameraUpdateFactory; @@ -49,15 +55,23 @@ import com.google.android.gms.maps.OnMapReadyCallback; import com.google.android.gms.maps.model.BitmapDescriptorFactory; import com.google.android.gms.maps.model.CircleOptions; import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.LatLngBounds; +import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.MarkerOptions; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.UserObject; +import org.telegram.messenger.FileLoader; +import org.telegram.messenger.LocationController; +import org.telegram.messenger.MessagesStorage; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; +import org.telegram.messenger.UserConfig; import org.telegram.messenger.support.widget.LinearLayoutManager; import org.telegram.messenger.support.widget.RecyclerView; +import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.RequestDelegate; +import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.messenger.MessageObject; import org.telegram.messenger.MessagesController; @@ -77,8 +91,8 @@ import org.telegram.ui.Cells.LocationCell; import org.telegram.ui.Cells.LocationLoadingCell; import org.telegram.ui.Cells.LocationPoweredCell; import org.telegram.ui.Cells.SendLocationCell; +import org.telegram.ui.Components.AlertsCreator; import org.telegram.ui.Components.AvatarDrawable; -import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.Components.CombinedDrawable; import org.telegram.ui.Components.EmptyTextProgressView; @@ -86,16 +100,23 @@ import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.MapPlaceholderDrawable; import org.telegram.ui.Components.RecyclerListView; +import java.io.File; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Locale; public class LocationActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { + public class LiveLocation { + public int id; + public TLRPC.Message object; + public TLRPC.User user; + public TLRPC.Chat chat; + public Marker marker; + } + private GoogleMap googleMap; - private TextView distanceTextView; - private BackupImageView avatarImageView; - private TextView nameTextView; private MapView mapView; private EmptyTextProgressView emptyView; private FrameLayout mapViewClip; @@ -107,9 +128,21 @@ public class LocationActivity extends BaseFragment implements NotificationCenter private ImageView markerXImageView; private ImageView locationButton; private ImageView routeButton; - private FrameLayout bottomView; private LinearLayoutManager layoutManager; private AvatarDrawable avatarDrawable; + private ActionBarMenuItem otherItem; + + private boolean checkGpsEnabled = true; + + private boolean isFirstLocation = true; + private long dialogId; + + private boolean firstFocus = true; + + private Runnable updateRunnable; + + private ArrayList markers = new ArrayList<>(); + private HashMap markersMap = new HashMap<>(); private AnimatorSet animatorSet; @@ -132,6 +165,8 @@ public class LocationActivity extends BaseFragment implements NotificationCenter private CircleOptions circleOptions; private LocationActivityDelegate delegate; + private int liveLocationType; + private int overScrollHeight = AndroidUtilities.displaySize.x - ActionBar.getCurrentActionBarHeight() - AndroidUtilities.dp(66); private final static int share = 1; @@ -140,7 +175,12 @@ public class LocationActivity extends BaseFragment implements NotificationCenter private final static int map_list_menu_hybrid = 4; public interface LocationActivityDelegate { - void didSelectLocation(TLRPC.MessageMedia location); + void didSelectLocation(TLRPC.MessageMedia location, int live); + } + + public LocationActivity(int liveLocation) { + super(); + liveLocationType = liveLocation; } @Override @@ -149,8 +189,10 @@ public class LocationActivity extends BaseFragment implements NotificationCenter swipeBackEnabled = false; NotificationCenter.getInstance().addObserver(this, NotificationCenter.closeChats); NotificationCenter.getInstance().addObserver(this, NotificationCenter.locationPermissionGranted); - if (messageObject != null) { - NotificationCenter.getInstance().addObserver(this, NotificationCenter.updateInterfaces); + if (messageObject != null && messageObject.isLiveLocation()) { + NotificationCenter.getInstance().addObserver(this, NotificationCenter.didReceivedNewMessages); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagesDeleted); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.replaceMessagesObjects); } return true; } @@ -158,9 +200,11 @@ public class LocationActivity extends BaseFragment implements NotificationCenter @Override public void onFragmentDestroy() { super.onFragmentDestroy(); - NotificationCenter.getInstance().removeObserver(this, NotificationCenter.updateInterfaces); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.locationPermissionGranted); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.closeChats); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReceivedNewMessages); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagesDeleted); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.replaceMessagesObjects); try { if (mapView != null) { mapView.onDestroy(); @@ -174,6 +218,10 @@ public class LocationActivity extends BaseFragment implements NotificationCenter if (searchAdapter != null) { searchAdapter.destroy(); } + if (updateRunnable != null) { + AndroidUtilities.cancelRunOnUIThread(updateRunnable); + updateRunnable = null; + } } @Override @@ -183,7 +231,7 @@ public class LocationActivity extends BaseFragment implements NotificationCenter if (AndroidUtilities.isTablet()) { actionBar.setOccupyStatusBar(false); } - actionBar.setAddToContainer(messageObject != null); + actionBar.setAddToContainer(false); actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { @Override @@ -216,15 +264,16 @@ public class LocationActivity extends BaseFragment implements NotificationCenter ActionBarMenu menu = actionBar.createMenu(); if (messageObject != null) { - if (messageObject.messageOwner.media.title != null && messageObject.messageOwner.media.title.length() > 0) { - actionBar.setTitle(messageObject.messageOwner.media.title); - if (messageObject.messageOwner.media.address != null && messageObject.messageOwner.media.address.length() > 0) { - actionBar.setSubtitle(messageObject.messageOwner.media.address); - } + if (messageObject.isLiveLocation()) { + actionBar.setTitle(LocaleController.getString("AttachLiveLocation", R.string.AttachLiveLocation)); } else { - actionBar.setTitle(LocaleController.getString("ChatLocation", R.string.ChatLocation)); + if (messageObject.messageOwner.media.title != null && messageObject.messageOwner.media.title.length() > 0) { + actionBar.setTitle(LocaleController.getString("SharedPlace", R.string.SharedPlace)); + } else { + actionBar.setTitle(LocaleController.getString("ChatLocation", R.string.ChatLocation)); + } + menu.addItem(share, R.drawable.share); } - menu.addItem(share, R.drawable.share); } else { actionBar.setTitle(LocaleController.getString("ShareLocation", R.string.ShareLocation)); @@ -232,6 +281,7 @@ public class LocationActivity extends BaseFragment implements NotificationCenter @Override public void onSearchExpand() { searching = true; + otherItem.setVisibility(View.GONE); listView.setVisibility(View.GONE); mapViewClip.setVisibility(View.GONE); searchListView.setVisibility(View.VISIBLE); @@ -242,6 +292,7 @@ public class LocationActivity extends BaseFragment implements NotificationCenter public void onSearchCollapse() { searching = false; searchWas = false; + otherItem.setVisibility(View.VISIBLE); searchListView.setEmptyView(null); listView.setVisibility(View.VISIBLE); mapViewClip.setVisibility(View.VISIBLE); @@ -265,10 +316,11 @@ public class LocationActivity extends BaseFragment implements NotificationCenter item.getSearchField().setHint(LocaleController.getString("Search", R.string.Search)); } - ActionBarMenuItem item = menu.addItem(0, R.drawable.ic_ab_other); - item.addSubItem(map_list_menu_map, LocaleController.getString("Map", R.string.Map)); - item.addSubItem(map_list_menu_satellite, LocaleController.getString("Satellite", R.string.Satellite)); - item.addSubItem(map_list_menu_hybrid, LocaleController.getString("Hybrid", R.string.Hybrid)); + otherItem = menu.addItem(0, R.drawable.ic_ab_other); + otherItem.addSubItem(map_list_menu_map, LocaleController.getString("Map", R.string.Map)); + otherItem.addSubItem(map_list_menu_satellite, LocaleController.getString("Satellite", R.string.Satellite)); + otherItem.addSubItem(map_list_menu_hybrid, LocaleController.getString("Hybrid", R.string.Hybrid)); + fragmentView = new FrameLayout(context) { private boolean first = true; @@ -312,225 +364,133 @@ public class LocationActivity extends BaseFragment implements NotificationCenter } if (messageObject != null) { - mapView = new MapView(context); - frameLayout.setBackgroundDrawable(new MapPlaceholderDrawable()); - final MapView map = mapView; - new Thread(new Runnable() { - @Override - public void run() { - try { - map.onCreate(null); - } catch (Exception e) { - //this will cause exception, but will preload google maps? - } - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - if (mapView != null && getParentActivity() != null) { - try { - map.onCreate(null); - MapsInitializer.initialize(getParentActivity()); - mapView.getMapAsync(new OnMapReadyCallback() { - @Override - public void onMapReady(GoogleMap map) { - googleMap = map; - googleMap.setPadding(0, 0, 0, AndroidUtilities.dp(10)); - onMapInit(); - } - }); - mapsInitialized = true; - if (onResumeCalled) { - mapView.onResume(); - } - } catch (Exception e) { - FileLog.e(e); - } - } - } - }); - } - }).start(); - - bottomView = new FrameLayout(context); - Drawable background = context.getResources().getDrawable(R.drawable.location_panel); - background.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_windowBackgroundWhite), PorterDuff.Mode.MULTIPLY)); - bottomView.setBackgroundDrawable(background); - frameLayout.addView(bottomView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 60, Gravity.LEFT | Gravity.BOTTOM)); - bottomView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - if (userLocation != null) { - LatLng latLng = new LatLng(userLocation.getLatitude(), userLocation.getLongitude()); - if (googleMap != null) { - CameraUpdate position = CameraUpdateFactory.newLatLngZoom(latLng, googleMap.getMaxZoomLevel() - 4); - googleMap.animateCamera(position); - } - } - } - }); - - avatarImageView = new BackupImageView(context); - avatarImageView.setRoundRadius(AndroidUtilities.dp(20)); - bottomView.addView(avatarImageView, LayoutHelper.createFrame(40, 40, Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), LocaleController.isRTL ? 0 : 12, 12, LocaleController.isRTL ? 12 : 0, 0)); - - nameTextView = new TextView(context); - nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); - nameTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); - nameTextView.setMaxLines(1); - nameTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - nameTextView.setEllipsize(TextUtils.TruncateAt.END); - nameTextView.setSingleLine(true); - nameTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); - bottomView.addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), LocaleController.isRTL ? 12 : 72, 10, LocaleController.isRTL ? 72 : 12, 0)); - - distanceTextView = new TextView(context); - distanceTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - distanceTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteValueText)); - distanceTextView.setMaxLines(1); - distanceTextView.setEllipsize(TextUtils.TruncateAt.END); - distanceTextView.setSingleLine(true); - distanceTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); - bottomView.addView(distanceTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), LocaleController.isRTL ? 12 : 72, 33, LocaleController.isRTL ? 72 : 12, 0)); - userLocation = new Location("network"); userLocation.setLatitude(messageObject.messageOwner.media.geo.lat); userLocation.setLongitude(messageObject.messageOwner.media.geo._long); + } - routeButton = new ImageView(context); - drawable = Theme.createSimpleSelectorCircleDrawable(AndroidUtilities.dp(56), Theme.getColor(Theme.key_chats_actionBackground), Theme.getColor(Theme.key_chats_actionPressedBackground)); - if (Build.VERSION.SDK_INT < 21) { - Drawable shadowDrawable = context.getResources().getDrawable(R.drawable.floating_shadow).mutate(); - shadowDrawable.setColorFilter(new PorterDuffColorFilter(0xff000000, PorterDuff.Mode.MULTIPLY)); - CombinedDrawable combinedDrawable = new CombinedDrawable(shadowDrawable, drawable, 0, 0); - combinedDrawable.setIconSize(AndroidUtilities.dp(56), AndroidUtilities.dp(56)); - drawable = combinedDrawable; + searchWas = false; + searching = false; + mapViewClip = new FrameLayout(context); + mapViewClip.setBackgroundDrawable(new MapPlaceholderDrawable()); + if (adapter != null) { + adapter.destroy(); + } + if (searchAdapter != null) { + searchAdapter.destroy(); + } + + listView = new RecyclerListView(context); + listView.setItemAnimator(null); + listView.setLayoutAnimation(null); + listView.setAdapter(adapter = new LocationActivityAdapter(context, liveLocationType, dialogId)); + listView.setVerticalScrollBarEnabled(false); + listView.setLayoutManager(layoutManager = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) { + @Override + public boolean supportsPredictiveItemAnimations() { + return false; } - routeButton.setBackgroundDrawable(drawable); - routeButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chats_actionIcon), PorterDuff.Mode.MULTIPLY)); - routeButton.setImageResource(R.drawable.navigate); - routeButton.setScaleType(ImageView.ScaleType.CENTER); - if (Build.VERSION.SDK_INT >= 21) { - StateListAnimator animator = new StateListAnimator(); - animator.addState(new int[]{android.R.attr.state_pressed}, ObjectAnimator.ofFloat(routeButton, "translationZ", AndroidUtilities.dp(2), AndroidUtilities.dp(4)).setDuration(200)); - animator.addState(new int[]{}, ObjectAnimator.ofFloat(routeButton, "translationZ", AndroidUtilities.dp(4), AndroidUtilities.dp(2)).setDuration(200)); - routeButton.setStateListAnimator(animator); - routeButton.setOutlineProvider(new ViewOutlineProvider() { - @SuppressLint("NewApi") - @Override - public void getOutline(View view, Outline outline) { - outline.setOval(0, 0, AndroidUtilities.dp(56), AndroidUtilities.dp(56)); - } - }); - } - frameLayout.addView(routeButton, LayoutHelper.createFrame(Build.VERSION.SDK_INT >= 21 ? 56 : 60, Build.VERSION.SDK_INT >= 21 ? 56 : 60, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.BOTTOM, LocaleController.isRTL ? 14 : 0, 0, LocaleController.isRTL ? 0 : 14, 28)); - routeButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (Build.VERSION.SDK_INT >= 23) { - Activity activity = getParentActivity(); - if (activity != null) { - if (activity.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { - showPermissionAlert(true); - return; - } - } - } - if (myLocation != null) { - try { - Intent intent = new Intent(android.content.Intent.ACTION_VIEW, Uri.parse(String.format(Locale.US, "http://maps.google.com/maps?saddr=%f,%f&daddr=%f,%f", myLocation.getLatitude(), myLocation.getLongitude(), messageObject.messageOwner.media.geo.lat, messageObject.messageOwner.media.geo._long))); - getParentActivity().startActivity(intent); - } catch (Exception e) { - FileLog.e(e); + }); + frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP)); + + listView.setOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + if (adapter.getItemCount() == 0) { + return; + } + int position = layoutManager.findFirstVisibleItemPosition(); + if (position == RecyclerView.NO_POSITION) { + return; + } + updateClipView(position); + if (dy > 0) { + if (!adapter.isPulledUp()) { + adapter.setPulledUp(); + if (myLocation != null) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + adapter.searchGooglePlacesWithQuery(null, myLocation); + } + }); } } } - }); - - frameLayout.addView(locationButton, LayoutHelper.createFrame(Build.VERSION.SDK_INT >= 21 ? 56 : 60, Build.VERSION.SDK_INT >= 21 ? 56 : 60, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.BOTTOM, LocaleController.isRTL ? 14 : 0, 0, LocaleController.isRTL ? 0 : 14, 100)); - locationButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (Build.VERSION.SDK_INT >= 23) { - Activity activity = getParentActivity(); - if (activity != null) { - if (activity.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { - showPermissionAlert(true); - return; - } - } - } - if (myLocation != null && googleMap != null) { - googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(myLocation.getLatitude(), myLocation.getLongitude()), googleMap.getMaxZoomLevel() - 4)); - } - } - }); - } else { - searchWas = false; - searching = false; - mapViewClip = new FrameLayout(context); - mapViewClip.setBackgroundDrawable(new MapPlaceholderDrawable()); - if (adapter != null) { - adapter.destroy(); } - if (searchAdapter != null) { - searchAdapter.destroy(); - } - - listView = new RecyclerListView(context); - listView.setAdapter(adapter = new LocationActivityAdapter(context)); - listView.setVerticalScrollBarEnabled(false); - listView.setLayoutManager(layoutManager = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); - frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP)); - - listView.setOnScrollListener(new RecyclerView.OnScrollListener() { - @Override - public void onScrolled(RecyclerView recyclerView, int dx, int dy) { - if (adapter.getItemCount() == 0) { - return; + }); + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + if (position == 1 && messageObject != null && !messageObject.isLiveLocation()) { + if (googleMap != null) { + googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(messageObject.messageOwner.media.geo.lat, messageObject.messageOwner.media.geo._long), googleMap.getMaxZoomLevel() - 4)); } - int position = layoutManager.findFirstVisibleItemPosition(); - if (position == RecyclerView.NO_POSITION) { - return; + } else if (position == 1 && liveLocationType != 2) { + if (delegate != null && userLocation != null) { + TLRPC.TL_messageMediaGeo location = new TLRPC.TL_messageMediaGeo(); + location.geo = new TLRPC.TL_geoPoint(); + location.geo.lat = userLocation.getLatitude(); + location.geo._long = userLocation.getLongitude(); + delegate.didSelectLocation(location, liveLocationType); } - updateClipView(position); - } - }); - listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { - @Override - public void onItemClick(View view, int position) { - if (position == 1) { - if (delegate != null && userLocation != null) { - TLRPC.TL_messageMediaGeo location = new TLRPC.TL_messageMediaGeo(); - location.geo = new TLRPC.TL_geoPoint(); - location.geo.lat = userLocation.getLatitude(); - location.geo._long = userLocation.getLongitude(); - delegate.didSelectLocation(location); - } + finishFragment(); + } else if (position == 2 && liveLocationType == 1 || position == 1 && liveLocationType == 2 || position == 3 && liveLocationType == 3) { + if (LocationController.getInstance().isSharingLocation(dialogId)) { + LocationController.getInstance().removeSharingLocation(dialogId); finishFragment(); } else { - TLRPC.TL_messageMediaVenue object = adapter.getItem(position); + if (delegate == null || getParentActivity() == null) { + return; + } + if (myLocation != null) { + TLRPC.User user = null; + if ((int) dialogId > 0) { + user = MessagesController.getInstance().getUser((int) dialogId); + } + showDialog(AlertsCreator.createLocationUpdateDialog(getParentActivity(), user, new MessagesStorage.IntCallback() { + @Override + public void run(int param) { + TLRPC.TL_messageMediaGeoLive location = new TLRPC.TL_messageMediaGeoLive(); + location.geo = new TLRPC.TL_geoPoint(); + location.geo.lat = myLocation.getLatitude(); + location.geo._long = myLocation.getLongitude(); + location.period = param; + delegate.didSelectLocation(location, liveLocationType); + finishFragment(); + } + })); + } + } + } else { + Object object = adapter.getItem(position); + if (object instanceof TLRPC.TL_messageMediaVenue) { if (object != null && delegate != null) { - delegate.didSelectLocation(object); + delegate.didSelectLocation((TLRPC.TL_messageMediaVenue) object, liveLocationType); } finishFragment(); + } else if (object instanceof LiveLocation) { + googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(((LiveLocation) object).marker.getPosition(), googleMap.getMaxZoomLevel() - 4)); } } - }); - adapter.setDelegate(new BaseLocationAdapter.BaseLocationAdapterDelegate() { - @Override - public void didLoadedSearchResult(ArrayList places) { - if (!wasResults && !places.isEmpty()) { - wasResults = true; - } + } + }); + adapter.setDelegate(new BaseLocationAdapter.BaseLocationAdapterDelegate() { + @Override + public void didLoadedSearchResult(ArrayList places) { + if (!wasResults && !places.isEmpty()) { + wasResults = true; } - }); - adapter.setOverScrollHeight(overScrollHeight); + } + }); + adapter.setOverScrollHeight(overScrollHeight); - frameLayout.addView(mapViewClip, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP)); + frameLayout.addView(mapViewClip, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP)); - mapView = new MapView(context) { - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { + mapView = new MapView(context) { + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (messageObject == null) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { if (animatorSet != null) { animatorSet.cancel(); @@ -566,50 +526,52 @@ public class LocationActivity extends BaseFragment implements NotificationCenter } adapter.setCustomLocation(userLocation); } - return super.onInterceptTouchEvent(ev); } - }; - final MapView map = mapView; - new Thread(new Runnable() { - @Override - public void run() { - try { - map.onCreate(null); - } catch (Exception e) { - //this will cause exception, but will preload google maps? - } - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - if (mapView != null && getParentActivity() != null) { - try { - map.onCreate(null); - MapsInitializer.initialize(getParentActivity()); - mapView.getMapAsync(new OnMapReadyCallback() { - @Override - public void onMapReady(GoogleMap map) { - googleMap = map; - googleMap.setPadding(0, 0, 0, AndroidUtilities.dp(10)); - onMapInit(); - } - }); - mapsInitialized = true; - if (onResumeCalled) { - mapView.onResume(); + return super.onInterceptTouchEvent(ev); + } + }; + final MapView map = mapView; + new Thread(new Runnable() { + @Override + public void run() { + try { + map.onCreate(null); + } catch (Exception e) { + //this will cause exception, but will preload google maps? + } + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (mapView != null && getParentActivity() != null) { + try { + map.onCreate(null); + MapsInitializer.initialize(getParentActivity()); + mapView.getMapAsync(new OnMapReadyCallback() { + @Override + public void onMapReady(GoogleMap map) { + googleMap = map; + googleMap.setPadding(0, 0, AndroidUtilities.dp(70), AndroidUtilities.dp(10)); + onMapInit(); } - } catch (Exception e) { - FileLog.e(e); + }); + mapsInitialized = true; + if (onResumeCalled) { + mapView.onResume(); } + } catch (Exception e) { + FileLog.e(e); } } - }); - } - }).start(); + } + }); + } + }).start(); - View shadow = new View(context); - shadow.setBackgroundResource(R.drawable.header_shadow_reverse); - mapViewClip.addView(shadow, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 3, Gravity.LEFT | Gravity.BOTTOM)); + View shadow = new View(context); + shadow.setBackgroundResource(R.drawable.header_shadow_reverse); + mapViewClip.addView(shadow, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 3, Gravity.LEFT | Gravity.BOTTOM)); + if (messageObject == null) { markerImageView = new ImageView(context); markerImageView.setImageResource(R.drawable.map_pin); mapViewClip.addView(markerImageView, LayoutHelper.createFrame(24, 42, Gravity.TOP | Gravity.CENTER_HORIZONTAL)); @@ -620,32 +582,6 @@ public class LocationActivity extends BaseFragment implements NotificationCenter markerXImageView.setImageResource(R.drawable.place_x); mapViewClip.addView(markerXImageView, LayoutHelper.createFrame(14, 14, Gravity.TOP | Gravity.CENTER_HORIZONTAL)); - mapViewClip.addView(locationButton, LayoutHelper.createFrame(Build.VERSION.SDK_INT >= 21 ? 56 : 60, Build.VERSION.SDK_INT >= 21 ? 56 : 60, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.BOTTOM, LocaleController.isRTL ? 14 : 0, 0, LocaleController.isRTL ? 0 : 14, 14)); - locationButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (Build.VERSION.SDK_INT >= 23) { - Activity activity = getParentActivity(); - if (activity != null) { - if (activity.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { - showPermissionAlert(false); - return; - } - } - } - if (myLocation != null && googleMap != null) { - AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.setDuration(200); - animatorSet.play(ObjectAnimator.ofFloat(locationButton, "alpha", 0.0f)); - animatorSet.start(); - adapter.setCustomLocation(null); - userLocationMoved = false; - googleMap.animateCamera(CameraUpdateFactory.newLatLng(new LatLng(myLocation.getLatitude(), myLocation.getLongitude()))); - } - } - }); - locationButton.setAlpha(0.0f); - emptyView = new EmptyTextProgressView(context); emptyView.setText(LocaleController.getString("NoResult", R.string.NoResult)); emptyView.setShowAtCenter(true); @@ -670,32 +606,239 @@ public class LocationActivity extends BaseFragment implements NotificationCenter public void onItemClick(View view, int position) { TLRPC.TL_messageMediaVenue object = searchAdapter.getItem(position); if (object != null && delegate != null) { - delegate.didSelectLocation(object); + delegate.didSelectLocation(object, liveLocationType); } finishFragment(); } }); + } else if (!messageObject.isLiveLocation()) { + routeButton = new ImageView(context); + drawable = Theme.createSimpleSelectorCircleDrawable(AndroidUtilities.dp(56), Theme.getColor(Theme.key_chats_actionBackground), Theme.getColor(Theme.key_chats_actionPressedBackground)); + if (Build.VERSION.SDK_INT < 21) { + Drawable shadowDrawable = context.getResources().getDrawable(R.drawable.floating_shadow).mutate(); + shadowDrawable.setColorFilter(new PorterDuffColorFilter(0xff000000, PorterDuff.Mode.MULTIPLY)); + CombinedDrawable combinedDrawable = new CombinedDrawable(shadowDrawable, drawable, 0, 0); + combinedDrawable.setIconSize(AndroidUtilities.dp(56), AndroidUtilities.dp(56)); + drawable = combinedDrawable; + } + routeButton.setBackgroundDrawable(drawable); + routeButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chats_actionIcon), PorterDuff.Mode.MULTIPLY)); + routeButton.setImageResource(R.drawable.navigate); + routeButton.setScaleType(ImageView.ScaleType.CENTER); + if (Build.VERSION.SDK_INT >= 21) { + StateListAnimator animator = new StateListAnimator(); + animator.addState(new int[]{android.R.attr.state_pressed}, ObjectAnimator.ofFloat(routeButton, "translationZ", AndroidUtilities.dp(2), AndroidUtilities.dp(4)).setDuration(200)); + animator.addState(new int[]{}, ObjectAnimator.ofFloat(routeButton, "translationZ", AndroidUtilities.dp(4), AndroidUtilities.dp(2)).setDuration(200)); + routeButton.setStateListAnimator(animator); + routeButton.setOutlineProvider(new ViewOutlineProvider() { + @SuppressLint("NewApi") + @Override + public void getOutline(View view, Outline outline) { + outline.setOval(0, 0, AndroidUtilities.dp(56), AndroidUtilities.dp(56)); + } + }); + } + frameLayout.addView(routeButton, LayoutHelper.createFrame(Build.VERSION.SDK_INT >= 21 ? 56 : 60, Build.VERSION.SDK_INT >= 21 ? 56 : 60, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.BOTTOM, LocaleController.isRTL ? 14 : 0, 0, LocaleController.isRTL ? 0 : 14, 37)); + routeButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (Build.VERSION.SDK_INT >= 23) { + Activity activity = getParentActivity(); + if (activity != null) { + if (activity.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + showPermissionAlert(true); + return; + } + } + } + if (myLocation != null) { + try { + Intent intent = new Intent(android.content.Intent.ACTION_VIEW, Uri.parse(String.format(Locale.US, "http://maps.google.com/maps?saddr=%f,%f&daddr=%f,%f", myLocation.getLatitude(), myLocation.getLongitude(), messageObject.messageOwner.media.geo.lat, messageObject.messageOwner.media.geo._long))); + getParentActivity().startActivity(intent); + } catch (Exception e) { + FileLog.e(e); + } + } + } + }); - frameLayout.addView(actionBar); + adapter.setMessageObject(messageObject); } + if (messageObject != null && !messageObject.isLiveLocation()) { + mapViewClip.addView(locationButton, LayoutHelper.createFrame(Build.VERSION.SDK_INT >= 21 ? 56 : 60, Build.VERSION.SDK_INT >= 21 ? 56 : 60, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.BOTTOM, LocaleController.isRTL ? 14 : 0, 0, LocaleController.isRTL ? 0 : 14, 43)); + } else { + mapViewClip.addView(locationButton, LayoutHelper.createFrame(Build.VERSION.SDK_INT >= 21 ? 56 : 60, Build.VERSION.SDK_INT >= 21 ? 56 : 60, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.BOTTOM, LocaleController.isRTL ? 14 : 0, 0, LocaleController.isRTL ? 0 : 14, 14)); + } + locationButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (Build.VERSION.SDK_INT >= 23) { + Activity activity = getParentActivity(); + if (activity != null) { + if (activity.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + showPermissionAlert(false); + return; + } + } + } + if (messageObject != null) { + if (myLocation != null && googleMap != null) { + googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(myLocation.getLatitude(), myLocation.getLongitude()), googleMap.getMaxZoomLevel() - 4)); + } + } else { + if (myLocation != null && googleMap != null) { + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.setDuration(200); + animatorSet.play(ObjectAnimator.ofFloat(locationButton, "alpha", 0.0f)); + animatorSet.start(); + adapter.setCustomLocation(null); + userLocationMoved = false; + googleMap.animateCamera(CameraUpdateFactory.newLatLng(new LatLng(myLocation.getLatitude(), myLocation.getLongitude()))); + } + } + } + }); + if (messageObject == null) { + locationButton.setAlpha(0.0f); + } + + frameLayout.addView(actionBar); + return fragmentView; } + private Bitmap createUserBitmap(LiveLocation liveLocation) { + Bitmap result = null; + try { + TLRPC.FileLocation photo = null; + if (liveLocation.user != null && liveLocation.user.photo != null) { + photo = liveLocation.user.photo.photo_small; + } else if (liveLocation.chat != null && liveLocation.chat.photo != null) { + photo = liveLocation.chat.photo.photo_small; + } + result = Bitmap.createBitmap(AndroidUtilities.dp(62), AndroidUtilities.dp(76), Bitmap.Config.ARGB_8888); + result.eraseColor(Color.TRANSPARENT); + Canvas canvas = new Canvas(result); + Drawable drawable = ApplicationLoader.applicationContext.getResources().getDrawable(R.drawable.livepin); + drawable.setBounds(0, 0, AndroidUtilities.dp(62), AndroidUtilities.dp(76)); + drawable.draw(canvas); + + Paint roundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + RectF bitmapRect = new RectF(); + canvas.save(); + if (photo != null) { + File path = FileLoader.getPathToAttach(photo, true); + Bitmap bitmap = BitmapFactory.decodeFile(path.toString()); + if (bitmap != null) { + BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); + Matrix matrix = new Matrix(); + float scale = AndroidUtilities.dp(52) / (float) bitmap.getWidth(); + matrix.postTranslate(AndroidUtilities.dp(5), AndroidUtilities.dp(5)); + matrix.postScale(scale, scale); + roundPaint.setShader(shader); + shader.setLocalMatrix(matrix); + bitmapRect.set(AndroidUtilities.dp(5), AndroidUtilities.dp(5), AndroidUtilities.dp(52 + 5), AndroidUtilities.dp(52 + 5)); + canvas.drawRoundRect(bitmapRect, AndroidUtilities.dp(26), AndroidUtilities.dp(26), roundPaint); + } + } else { + AvatarDrawable avatarDrawable = new AvatarDrawable(); + if (liveLocation.user != null) { + avatarDrawable.setInfo(liveLocation.user); + } else if (liveLocation.chat != null) { + avatarDrawable.setInfo(liveLocation.chat); + } + canvas.translate(AndroidUtilities.dp(5), AndroidUtilities.dp(5)); + avatarDrawable.setBounds(0, 0, AndroidUtilities.dp(52.2f), AndroidUtilities.dp(52.2f)); + avatarDrawable.draw(canvas); + } + canvas.restore(); + try { + canvas.setBitmap(null); + } catch (Exception e) { + //don't promt, this will crash on 2.x + } + } catch (Throwable e) { + FileLog.e(e); + } + return result; + } + + private int getMessageId(TLRPC.Message message) { + if (message.from_id != 0) { + return message.from_id; + } else { + return (int) MessageObject.getDialogId(message); + } + } + + private LiveLocation addUserMarker(TLRPC.Message message) { + LiveLocation liveLocation; + LatLng latLng = new LatLng(message.media.geo.lat, message.media.geo._long); + if ((liveLocation = markersMap.get(message.from_id)) == null) { + liveLocation = new LiveLocation(); + liveLocation.object = message; + if (liveLocation.object.from_id != 0) { + liveLocation.user = MessagesController.getInstance().getUser(liveLocation.object.from_id); + liveLocation.id = liveLocation.object.from_id; + } else { + int did = (int) MessageObject.getDialogId(message); + if (did > 0) { + liveLocation.user = MessagesController.getInstance().getUser(did); + liveLocation.id = did; + } else { + liveLocation.chat = MessagesController.getInstance().getChat(-did); + liveLocation.id = -did; + } + } + + try { + MarkerOptions options = new MarkerOptions().position(latLng); + Bitmap bitmap = createUserBitmap(liveLocation); + if (bitmap != null) { + options.icon(BitmapDescriptorFactory.fromBitmap(bitmap)); + options.anchor(0.5f, 0.907f); + liveLocation.marker = googleMap.addMarker(options); + markers.add(liveLocation); + markersMap.put(liveLocation.id, liveLocation); + LocationController.SharingLocationInfo myInfo = LocationController.getInstance().getSharingLocationInfo(dialogId); + if (liveLocation.id == UserConfig.getClientUserId() && myInfo != null && liveLocation.object.id == myInfo.mid && myLocation != null) { + liveLocation.marker.setPosition(new LatLng(myLocation.getLatitude(), myLocation.getLongitude())); + } + } + } catch (Exception e) { + FileLog.e(e); + } + } else { + liveLocation.object = message; + liveLocation.marker.setPosition(latLng); + } + return liveLocation; + } + private void onMapInit() { if (googleMap == null) { return; } if (messageObject != null) { - LatLng latLng = new LatLng(userLocation.getLatitude(), userLocation.getLongitude()); - try { - googleMap.addMarker(new MarkerOptions().position(latLng).icon(BitmapDescriptorFactory.fromResource(R.drawable.map_pin))); - } catch (Exception e) { - FileLog.e(e); + if (messageObject.isLiveLocation()) { + LiveLocation liveLocation = addUserMarker(messageObject.messageOwner); + if (!getRecentLocations()) { + googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(liveLocation.marker.getPosition(), googleMap.getMaxZoomLevel() - 4)); + } + } else { + LatLng latLng = new LatLng(userLocation.getLatitude(), userLocation.getLongitude()); + try { + googleMap.addMarker(new MarkerOptions().position(latLng).icon(BitmapDescriptorFactory.fromResource(R.drawable.map_pin))); + } catch (Exception e) { + FileLog.e(e); + } + CameraUpdate position = CameraUpdateFactory.newLatLngZoom(latLng, googleMap.getMaxZoomLevel() - 4); + googleMap.moveCamera(position); + firstFocus = false; + getRecentLocations(); } - CameraUpdate position = CameraUpdateFactory.newLatLngZoom(latLng, googleMap.getMaxZoomLevel() - 4); - googleMap.moveCamera(position); } else { userLocation = new Location("network"); userLocation.setLatitude(20.659322); @@ -710,13 +853,44 @@ public class LocationActivity extends BaseFragment implements NotificationCenter googleMap.getUiSettings().setMyLocationButtonEnabled(false); googleMap.getUiSettings().setZoomControlsEnabled(false); googleMap.getUiSettings().setCompassEnabled(false); + //googleMap.getUiSettings().setMapToolbarEnabled(false); googleMap.setOnMyLocationChangeListener(new GoogleMap.OnMyLocationChangeListener() { @Override public void onMyLocationChange(Location location) { positionMarker(location); + LocationController.getInstance().setGoogleMapLocation(location, isFirstLocation); + isFirstLocation = false; } }); positionMarker(myLocation = getLastLocation()); + + if (checkGpsEnabled && getParentActivity() != null) { + checkGpsEnabled = false; + try { + LocationManager lm = (LocationManager) ApplicationLoader.applicationContext.getSystemService(Context.LOCATION_SERVICE); + if (!lm.isProviderEnabled(LocationManager.GPS_PROVIDER)) { + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setMessage(LocaleController.getString("GpsDisabledAlert", R.string.GpsDisabledAlert)); + builder.setPositiveButton(LocaleController.getString("ConnectingToProxyEnable", R.string.ConnectingToProxyEnable), new DialogInterface.OnClickListener() { + public void onClick(final DialogInterface dialog, final int id) { + if (getParentActivity() == null) { + return; + } + try { + getParentActivity().startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS)); + } catch (Exception ignore) { + + } + } + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + showDialog(builder.create()); + } + } catch (Exception e) { + FileLog.e(e); + } + } } private void showPermissionAlert(boolean byButton) { @@ -798,13 +972,18 @@ public class LocationActivity extends BaseFragment implements NotificationCenter mapViewClip.setTranslationY(Math.min(0, top)); mapView.setTranslationY(Math.max(0, -top / 2)); - markerImageView.setTranslationY(markerTop = -top - AndroidUtilities.dp(42) + height / 2); - markerXImageView.setTranslationY(-top - AndroidUtilities.dp(7) + height / 2); + if (markerImageView != null) { + markerImageView.setTranslationY(markerTop = -top - AndroidUtilities.dp(42) + height / 2); + markerXImageView.setTranslationY(-top - AndroidUtilities.dp(7) + height / 2); + } + if (routeButton != null) { + routeButton.setTranslationY(top); + } layoutParams = (FrameLayout.LayoutParams) mapView.getLayoutParams(); if (layoutParams != null && layoutParams.height != overScrollHeight + AndroidUtilities.dp(10)) { layoutParams.height = overScrollHeight + AndroidUtilities.dp(10); if (googleMap != null) { - googleMap.setPadding(0, 0, 0, AndroidUtilities.dp(10)); + googleMap.setPadding(0, 0, AndroidUtilities.dp(70), AndroidUtilities.dp(10)); } mapView.setLayoutParams(layoutParams); } @@ -828,28 +1007,30 @@ public class LocationActivity extends BaseFragment implements NotificationCenter layoutParams.topMargin = height; layoutParams.height = overScrollHeight; mapViewClip.setLayoutParams(layoutParams); - layoutParams = (FrameLayout.LayoutParams) searchListView.getLayoutParams(); - layoutParams.topMargin = height; - searchListView.setLayoutParams(layoutParams); + if (searchListView != null) { + layoutParams = (FrameLayout.LayoutParams) searchListView.getLayoutParams(); + layoutParams.topMargin = height; + searchListView.setLayoutParams(layoutParams); + } adapter.setOverScrollHeight(overScrollHeight); layoutParams = (FrameLayout.LayoutParams) mapView.getLayoutParams(); if (layoutParams != null) { layoutParams.height = overScrollHeight + AndroidUtilities.dp(10); if (googleMap != null) { - googleMap.setPadding(0, 0, 0, AndroidUtilities.dp(10)); + googleMap.setPadding(0, 0, AndroidUtilities.dp(70), AndroidUtilities.dp(10)); } mapView.setLayoutParams(layoutParams); } adapter.notifyDataSetChanged(); if (resume) { - layoutManager.scrollToPositionWithOffset(0, -(int) (AndroidUtilities.dp(56) * 2.5f + AndroidUtilities.dp(36 + 66))); + layoutManager.scrollToPositionWithOffset(0, -AndroidUtilities.dp(32 + (liveLocationType == 1 || liveLocationType == 2 ? 66 : 0))); updateClipView(layoutManager.findFirstVisibleItemPosition()); listView.post(new Runnable() { @Override public void run() { - layoutManager.scrollToPositionWithOffset(0, -(int) (AndroidUtilities.dp(56) * 2.5f + AndroidUtilities.dp(36 + 66))); + layoutManager.scrollToPositionWithOffset(0, -AndroidUtilities.dp(32 + (liveLocationType == 1 || liveLocationType == 2 ? 66 : 0))); updateClipView(layoutManager.findFirstVisibleItemPosition()); } }); @@ -872,65 +1053,22 @@ public class LocationActivity extends BaseFragment implements NotificationCenter return l; } - private void updateUserData() { - if (messageObject != null && avatarImageView != null) { - int fromId = messageObject.messageOwner.from_id; - if (messageObject.isForwarded()) { - if (messageObject.messageOwner.fwd_from.channel_id != 0) { - fromId = -messageObject.messageOwner.fwd_from.channel_id; - } else { - fromId = messageObject.messageOwner.fwd_from.from_id; - } - } - String name = ""; - TLRPC.FileLocation photo = null; - avatarDrawable = null; - if (fromId > 0) { - TLRPC.User user = MessagesController.getInstance().getUser(fromId); - if (user != null) { - if (user.photo != null) { - photo = user.photo.photo_small; - } - avatarDrawable = new AvatarDrawable(user); - name = UserObject.getUserName(user); - } - } else { - TLRPC.Chat chat = MessagesController.getInstance().getChat(-fromId); - if (chat != null) { - if (chat.photo != null) { - photo = chat.photo.photo_small; - } - avatarDrawable = new AvatarDrawable(chat); - name = chat.title; - } - } - if (avatarDrawable != null) { - avatarImageView.setImage(photo, null, avatarDrawable); - nameTextView.setText(name); - } else { - avatarImageView.setImageDrawable(null); - } - } - } - private void positionMarker(Location location) { if (location == null) { return; } myLocation = new Location(location); - if (messageObject != null) { - if (userLocation != null && distanceTextView != null) { - float distance = location.distanceTo(userLocation); - if (distance < 1000) { - distanceTextView.setText(String.format("%d %s", (int) (distance), LocaleController.getString("MetersAway", R.string.MetersAway))); - } else { - distanceTextView.setText(String.format("%.2f %s", distance / 1000.0f, LocaleController.getString("KMetersAway", R.string.KMetersAway))); - } - } - } else if (googleMap != null) { + LiveLocation liveLocation = markersMap.get(UserConfig.getClientUserId()); + LocationController.SharingLocationInfo myInfo = LocationController.getInstance().getSharingLocationInfo(dialogId); + if (liveLocation != null && myInfo != null && liveLocation.object.id == myInfo.mid) { + liveLocation.marker.setPosition(new LatLng(location.getLatitude(), location.getLongitude())); + } + if (messageObject == null && googleMap != null) { LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude()); if (adapter != null) { - adapter.searchGooglePlacesWithQuery(null, myLocation); + if (adapter.isPulledUp()) { + adapter.searchGooglePlacesWithQuery(null, myLocation); + } adapter.setGpsLocation(myLocation); } if (!userLocationMoved) { @@ -944,21 +1082,102 @@ public class LocationActivity extends BaseFragment implements NotificationCenter googleMap.moveCamera(position); } } + } else { + adapter.setGpsLocation(myLocation); } } public void setMessageObject(MessageObject message) { messageObject = message; + dialogId = messageObject.getDialogId(); } + public void setDialogId(long did) { + dialogId = did; + } + + private void fetchRecentLocations(ArrayList messages) { + LatLngBounds.Builder builder = null; + if (firstFocus) { + builder = new LatLngBounds.Builder(); + } + int date = ConnectionsManager.getInstance().getCurrentTime(); + for (int a = 0; a < messages.size(); a++) { + TLRPC.Message message = messages.get(a); + if (message.date + message.media.period > date) { + if (builder != null) { + LatLng latLng = new LatLng(message.media.geo.lat, message.media.geo._long); + builder.include(latLng); + } + addUserMarker(message); + } + } + if (builder != null) { + firstFocus = false; + adapter.setLiveLocations(markers); + if (messageObject.isLiveLocation()) { + try { + final LatLngBounds bounds = builder.build(); + if (messages.size() > 1) { + try { + googleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, AndroidUtilities.dp(60))); + } catch (Exception e) { + FileLog.e(e); + } + } + } catch (Exception ignore) { + + } + } + } + } + + private boolean getRecentLocations() { + ArrayList messages = LocationController.getInstance().locationsCache.get(messageObject.getDialogId()); + if (messages != null && messages.isEmpty()) { + fetchRecentLocations(messages); + } else { + messages = null; + } + TLRPC.TL_messages_getRecentLocations req = new TLRPC.TL_messages_getRecentLocations(); + final long dialog_id = messageObject.getDialogId(); + req.peer = MessagesController.getInputPeer((int) dialog_id); + req.limit = 100; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, TLRPC.TL_error error) { + if (response != null) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (googleMap == null) { + return; + } + TLRPC.messages_Messages res = (TLRPC.messages_Messages) response; + for (int a = 0; a < res.messages.size(); a++) { + if (!(res.messages.get(a).media instanceof TLRPC.TL_messageMediaGeoLive)) { + res.messages.remove(a); + a--; + } + } + MessagesStorage.getInstance().putUsersAndChats(res.users, res.chats, true, true); + MessagesController.getInstance().putUsers(res.users, false); + MessagesController.getInstance().putChats(res.chats, false); + LocationController.getInstance().locationsCache.put(dialog_id, res.messages); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.liveLocationsCacheChanged, dialog_id); + fetchRecentLocations(res.messages); + } + }); + } + } + }); + return messages != null; + } + + @SuppressWarnings("unchecked") @Override public void didReceivedNotification(int id, Object... args) { - if (id == NotificationCenter.updateInterfaces) { - int mask = (Integer) args[0]; - if ((mask & MessagesController.UPDATE_MASK_AVATAR) != 0 || (mask & MessagesController.UPDATE_MASK_NAME) != 0) { - updateUserData(); - } - } else if (id == NotificationCenter.closeChats) { + if (id == NotificationCenter.closeChats) { removeSelfFromStack(); } else if (id == NotificationCenter.locationPermissionGranted) { if (googleMap != null) { @@ -968,6 +1187,49 @@ public class LocationActivity extends BaseFragment implements NotificationCenter FileLog.e(e); } } + } else if (id == NotificationCenter.didReceivedNewMessages) { + long did = (Long) args[0]; + if (did != dialogId || messageObject == null) { + return; + } + ArrayList arr = (ArrayList) args[1]; + boolean added = false; + for (int a = 0; a < arr.size(); a++) { + MessageObject messageObject = arr.get(a); + if (messageObject.isLiveLocation()) { + addUserMarker(messageObject.messageOwner); + added = true; + } + } + if (added && adapter != null) { + adapter.setLiveLocations(markers); + } + } else if (id == NotificationCenter.messagesDeleted) { + + } else if (id == NotificationCenter.replaceMessagesObjects) { + long did = (long) args[0]; + if (did != dialogId || messageObject == null) { + return; + } + boolean updated = false; + ArrayList messageObjects = (ArrayList) args[1]; + for (int a = 0; a < messageObjects.size(); a++) { + MessageObject messageObject = messageObjects.get(a); + if (!messageObject.isLiveLocation()) { + continue; + } + LiveLocation liveLocation = markersMap.get(getMessageId(messageObject.messageOwner)); + if (liveLocation != null) { + LocationController.SharingLocationInfo myInfo = LocationController.getInstance().getSharingLocationInfo(did); + if (myInfo == null || myInfo.mid != messageObject.getId()) { + liveLocation.marker.setPosition(new LatLng(messageObject.messageOwner.media.geo.lat, messageObject.messageOwner.media.geo._long)); + } + updated = true; + } + } + if (updated && adapter != null) { + adapter.updateLiveLocations(); + } } } @@ -1003,7 +1265,6 @@ public class LocationActivity extends BaseFragment implements NotificationCenter FileLog.e(e); } } - updateUserData(); fixLayoutInternal(true); if (checkPermission && Build.VERSION.SDK_INT >= 23) { Activity activity = getParentActivity(); @@ -1039,7 +1300,7 @@ public class LocationActivity extends BaseFragment implements NotificationCenter ThemeDescription.ThemeDescriptionDelegate сellDelegate = new ThemeDescription.ThemeDescriptionDelegate() { @Override public void didSetColor(int color) { - updateUserData(); + } }; return new ThemeDescription[]{ @@ -1066,12 +1327,6 @@ public class LocationActivity extends BaseFragment implements NotificationCenter new ThemeDescription(locationButton, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_profile_actionBackground), new ThemeDescription(locationButton, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_profile_actionPressedBackground), - new ThemeDescription(bottomView, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhite), - - new ThemeDescription(nameTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), - - new ThemeDescription(distanceTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteValueText), - new ThemeDescription(routeButton, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_chats_actionIcon), new ThemeDescription(routeButton, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_chats_actionBackground), new ThemeDescription(routeButton, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_chats_actionPressedBackground), @@ -1081,7 +1336,7 @@ public class LocationActivity extends BaseFragment implements NotificationCenter new ThemeDescription(listView, 0, new Class[]{GraySectionCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{GraySectionCell.class}, null, null, null, Theme.key_graySection), - new ThemeDescription(null, 0, null, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, сellDelegate, Theme.key_avatar_text), + new ThemeDescription(null, 0, null, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, сellDelegate, Theme.key_avatar_text), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), @@ -1090,8 +1345,13 @@ public class LocationActivity extends BaseFragment implements NotificationCenter new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundBlue), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundPink), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_location_liveLocationProgress), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_location_placeLocationBackground), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialog_liveLocationProgress), + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{SendLocationCell.class}, new String[]{"imageView"}, null, null, null, Theme.key_location_sendLocationIcon), new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_USEBACKGROUNDDRAWABLE, new Class[]{SendLocationCell.class}, new String[]{"imageView"}, null, null, null, Theme.key_location_sendLocationBackground), + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_USEBACKGROUNDDRAWABLE, new Class[]{SendLocationCell.class}, new String[]{"imageView"}, null, null, null, Theme.key_location_sendLiveLocationBackground), new ThemeDescription(listView, 0, new Class[]{SendLocationCell.class}, new String[]{"titleTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueText7), new ThemeDescription(listView, 0, new Class[]{SendLocationCell.class}, new String[]{"accurateTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText3), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java index ca54bb02e..f711e80e2 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java @@ -39,7 +39,6 @@ import android.view.View; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.inputmethod.EditorInfo; import android.widget.AdapterView; -import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; @@ -69,6 +68,7 @@ import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Components.EditTextBoldCursor; import org.telegram.ui.Components.HintEditText; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.SlideView; @@ -423,6 +423,7 @@ public class LoginActivity extends BaseFragment { builder.setPositiveButton(LocaleController.getString("Cancel", R.string.Cancel), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { + views[currentViewNum].onCancelPressed(); ConnectionsManager.getInstance().cancelRequest(reqiestId, true); progressDialog = null; } @@ -577,7 +578,7 @@ public class LoginActivity extends BaseFragment { public class PhoneView extends SlideView implements AdapterView.OnItemSelectedListener { - private EditText codeField; + private EditTextBoldCursor codeField; private HintEditText phoneField; private TextView countryButton; private View view; @@ -648,11 +649,13 @@ public class LoginActivity extends BaseFragment { textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); linearLayout.addView(textView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); - codeField = new EditText(context); + codeField = new EditTextBoldCursor(context); codeField.setInputType(InputType.TYPE_CLASS_PHONE); codeField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); codeField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); - AndroidUtilities.clearCursorDrawable(codeField); + codeField.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + codeField.setCursorSize(AndroidUtilities.dp(20)); + codeField.setCursorWidth(1.5f); codeField.setPadding(AndroidUtilities.dp(10), 0, 0, 0); codeField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); codeField.setMaxLines(1); @@ -754,7 +757,9 @@ public class LoginActivity extends BaseFragment { phoneField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); phoneField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); phoneField.setPadding(0, 0, 0, 0); - AndroidUtilities.clearCursorDrawable(phoneField); + phoneField.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + phoneField.setCursorSize(AndroidUtilities.dp(20)); + phoneField.setCursorWidth(1.5f); phoneField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); phoneField.setMaxLines(1); phoneField.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL); @@ -928,6 +933,11 @@ public class LoginActivity extends BaseFragment { } } + @Override + public void onCancelPressed() { + nextPressed = false; + } + @Override public void onItemSelected(AdapterView adapterView, View view, int i, long l) { if (ignoreSelection) { @@ -964,6 +974,9 @@ public class LoginActivity extends BaseFragment { } if (!allowSms) { permissionsItems.add(Manifest.permission.RECEIVE_SMS); + if (Build.VERSION.SDK_INT >= 23) { + permissionsItems.add(Manifest.permission.READ_SMS); + } } if (!allowCancelCall) { permissionsItems.add(Manifest.permission.CALL_PHONE); @@ -1017,7 +1030,6 @@ public class LoginActivity extends BaseFragment { ConnectionsManager.getInstance().cleanup(); final TLRPC.TL_auth_sendCode req = new TLRPC.TL_auth_sendCode(); String phone = PhoneFormat.stripExceptNumbers("" + codeField.getText() + phoneField.getText()); - ConnectionsManager.getInstance().applyCountryPortNumber(phone); req.api_hash = BuildVars.APP_HASH; req.api_id = BuildVars.APP_ID; req.phone_number = phone; @@ -1100,6 +1112,9 @@ public class LoginActivity extends BaseFragment { } if (!allowSms) { permissionsShowItems.add(Manifest.permission.RECEIVE_SMS); + if (Build.VERSION.SDK_INT >= 23) { + permissionsShowItems.add(Manifest.permission.READ_SMS); + } } if (!permissionsShowItems.isEmpty()) { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); @@ -1204,7 +1219,7 @@ public class LoginActivity extends BaseFragment { private String phoneHash; private String requestPhone; private String emailPhone; - private EditText codeField; + private EditTextBoldCursor codeField; private TextView confirmTextView; private TextView timeText; private TextView problemText; @@ -1261,10 +1276,12 @@ public class LoginActivity extends BaseFragment { addView(confirmTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT)); } - codeField = new EditText(context); + codeField = new EditTextBoldCursor(context); codeField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); codeField.setHint(LocaleController.getString("Code", R.string.Code)); - AndroidUtilities.clearCursorDrawable(codeField); + codeField.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + codeField.setCursorSize(AndroidUtilities.dp(20)); + codeField.setCursorWidth(1.5f); codeField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); codeField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); codeField.setImeOptions(EditorInfo.IME_ACTION_NEXT | EditorInfo.IME_FLAG_NO_EXTRACT_UI); @@ -1386,6 +1403,11 @@ public class LoginActivity extends BaseFragment { }); } + @Override + public void onCancelPressed() { + nextPressed = false; + } + private void resendCode() { final Bundle params = new Bundle(); params.putString("phone", phone); @@ -1914,7 +1936,7 @@ public class LoginActivity extends BaseFragment { public class LoginActivityPasswordView extends SlideView { - private EditText codeField; + private EditTextBoldCursor codeField; private TextView confirmTextView; private TextView resetAccountButton; private TextView resetAccountText; @@ -1943,9 +1965,11 @@ public class LoginActivity extends BaseFragment { confirmTextView.setText(LocaleController.getString("LoginPasswordText", R.string.LoginPasswordText)); addView(confirmTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT)); - codeField = new EditText(context); + codeField = new EditTextBoldCursor(context); codeField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); - AndroidUtilities.clearCursorDrawable(codeField); + codeField.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + codeField.setCursorSize(AndroidUtilities.dp(20)); + codeField.setCursorWidth(1.5f); codeField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); codeField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); codeField.setHint(LocaleController.getString("LoginPassword", R.string.LoginPassword)); @@ -2111,6 +2135,11 @@ public class LoginActivity extends BaseFragment { return LocaleController.getString("LoginPassword", R.string.LoginPassword); } + @Override + public void onCancelPressed() { + nextPressed = false; + } + @Override public void setParams(Bundle params, boolean restore) { if (params == null) { @@ -2450,7 +2479,7 @@ public class LoginActivity extends BaseFragment { public class LoginActivityRecoverView extends SlideView { - private EditText codeField; + private EditTextBoldCursor codeField; private TextView confirmTextView; private TextView cancelButton; @@ -2471,9 +2500,11 @@ public class LoginActivity extends BaseFragment { confirmTextView.setText(LocaleController.getString("RestoreEmailSentInfo", R.string.RestoreEmailSentInfo)); addView(confirmTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT))); - codeField = new EditText(context); + codeField = new EditTextBoldCursor(context); codeField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); - AndroidUtilities.clearCursorDrawable(codeField); + codeField.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + codeField.setCursorSize(AndroidUtilities.dp(20)); + codeField.setCursorWidth(1.5f); codeField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); codeField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); codeField.setHint(LocaleController.getString("PasswordCode", R.string.PasswordCode)); @@ -2530,6 +2561,11 @@ public class LoginActivity extends BaseFragment { return true; } + @Override + public void onCancelPressed() { + nextPressed = false; + } + @Override public String getHeaderName() { return LocaleController.getString("LoginPassword", R.string.LoginPassword); @@ -2670,8 +2706,8 @@ public class LoginActivity extends BaseFragment { public class LoginActivityRegisterView extends SlideView { - private EditText firstNameField; - private EditText lastNameField; + private EditTextBoldCursor firstNameField; + private EditTextBoldCursor lastNameField; private TextView textView; private TextView wrongNumber; private String requestPhone; @@ -2692,11 +2728,13 @@ public class LoginActivity extends BaseFragment { textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); addView(textView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 0, 8, 0, 0)); - firstNameField = new EditText(context); + firstNameField = new EditTextBoldCursor(context); firstNameField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); firstNameField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); firstNameField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); - AndroidUtilities.clearCursorDrawable(firstNameField); + firstNameField.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + firstNameField.setCursorSize(AndroidUtilities.dp(20)); + firstNameField.setCursorWidth(1.5f); firstNameField.setHint(LocaleController.getString("FirstName", R.string.FirstName)); firstNameField.setImeOptions(EditorInfo.IME_ACTION_NEXT | EditorInfo.IME_FLAG_NO_EXTRACT_UI); firstNameField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); @@ -2714,17 +2752,29 @@ public class LoginActivity extends BaseFragment { } }); - lastNameField = new EditText(context); + lastNameField = new EditTextBoldCursor(context); lastNameField.setHint(LocaleController.getString("LastName", R.string.LastName)); lastNameField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); lastNameField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); lastNameField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); - AndroidUtilities.clearCursorDrawable(lastNameField); - lastNameField.setImeOptions(EditorInfo.IME_ACTION_NEXT | EditorInfo.IME_FLAG_NO_EXTRACT_UI); + lastNameField.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + lastNameField.setCursorSize(AndroidUtilities.dp(20)); + lastNameField.setCursorWidth(1.5f); + lastNameField.setImeOptions(EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI); lastNameField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); lastNameField.setMaxLines(1); lastNameField.setInputType(InputType.TYPE_TEXT_FLAG_CAP_WORDS); addView(lastNameField, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36, 0, 10, 0, 0)); + lastNameField.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { + if (i == EditorInfo.IME_ACTION_DONE || i == EditorInfo.IME_ACTION_NEXT) { + onNextPressed(); + return true; + } + return false; + } + }); LinearLayout linearLayout = new LinearLayout(context); linearLayout.setGravity(Gravity.BOTTOM | Gravity.CENTER_VERTICAL); @@ -2767,6 +2817,11 @@ public class LoginActivity extends BaseFragment { return LocaleController.getString("YourName", R.string.YourName); } + @Override + public void onCancelPressed() { + nextPressed = false; + } + @Override public void onShow() { super.onShow(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/MediaActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/MediaActivity.java index 35d546259..350e95eb1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/MediaActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/MediaActivity.java @@ -16,7 +16,6 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.res.Configuration; -import android.graphics.Bitmap; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; @@ -46,9 +45,9 @@ import org.telegram.messenger.ChatObject; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MediaController; import org.telegram.messenger.MessagesController; +import org.telegram.messenger.SendMessagesHelper; import org.telegram.messenger.UserConfig; import org.telegram.messenger.UserObject; -import org.telegram.messenger.VideoEditedInfo; import org.telegram.messenger.browser.Browser; import org.telegram.messenger.query.SharedMediaQuery; import org.telegram.messenger.ApplicationLoader; @@ -96,7 +95,7 @@ import java.util.Timer; import java.util.TimerTask; @SuppressWarnings("unchecked") -public class MediaActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, PhotoViewer.PhotoViewerProvider { +public class MediaActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { private SharedPhotoVideoAdapter photoVideoAdapter; private SharedLinksAdapter linksAdapter; @@ -135,6 +134,45 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No private int selectedMode; private int columnsCount = 4; + private PhotoViewer.PhotoViewerProvider provider = new PhotoViewer.EmptyPhotoViewerProvider() { + + @Override + public PhotoViewer.PlaceProviderObject getPlaceForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { + if (messageObject == null || listView == null || selectedMode != 0) { + return null; + } + int count = listView.getChildCount(); + + for (int a = 0; a < count; a++) { + View view = listView.getChildAt(a); + if (view instanceof SharedPhotoVideoCell) { + SharedPhotoVideoCell cell = (SharedPhotoVideoCell) view; + for (int i = 0; i < 6; i++) { + MessageObject message = cell.getMessageObject(i); + if (message == null) { + break; + } + BackupImageView imageView = cell.getImageView(i); + if (message.getId() == messageObject.getId()) { + int coords[] = new int[2]; + imageView.getLocationInWindow(coords); + PhotoViewer.PlaceProviderObject object = new PhotoViewer.PlaceProviderObject(); + object.viewX = coords[0]; + object.viewY = coords[1] - (Build.VERSION.SDK_INT >= 21 ? 0 : AndroidUtilities.statusBarHeight); + object.parentView = listView; + object.imageReceiver = imageView.getImageReceiver(); + object.thumb = object.imageReceiver.getBitmap(); + object.parentView.getLocationInWindow(coords); + object.clipTopAddition = AndroidUtilities.dp(40); + return object; + } + } + } + } + return null; + } + }; + private class SharedMediaData { private ArrayList messages = new ArrayList<>(); private HashMap[] messagesDict = new HashMap[] {new HashMap<>(), new HashMap<>()}; @@ -238,7 +276,7 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No } } sharedMediaData[0].loading = true; - SharedMediaQuery.loadMedia(dialog_id, 0, 50, 0, SharedMediaQuery.MEDIA_PHOTOVIDEO, true, classGuid); + SharedMediaQuery.loadMedia(dialog_id, 50, 0, SharedMediaQuery.MEDIA_PHOTOVIDEO, true, classGuid); return true; } @@ -415,37 +453,54 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No } else if (id == forward) { Bundle args = new Bundle(); args.putBoolean("onlySelect", true); - args.putInt("dialogsType", 1); + args.putInt("dialogsType", 3); DialogsActivity fragment = new DialogsActivity(args); fragment.setDelegate(new DialogsActivity.DialogsActivityDelegate() { @Override - public void didSelectDialog(DialogsActivity fragment, long did, boolean param) { - int lower_part = (int) did; - if (lower_part != 0) { + public void didSelectDialogs(DialogsActivity fragment, ArrayList dids, CharSequence message, boolean param) { + ArrayList fmessages = new ArrayList<>(); + for (int a = 1; a >= 0; a--) { + ArrayList ids = new ArrayList<>(selectedFiles[a].keySet()); + Collections.sort(ids); + for (Integer id : ids) { + if (id > 0) { + fmessages.add(selectedFiles[a].get(id)); + } + } + selectedFiles[a].clear(); + } + cantDeleteMessagesCount = 0; + actionBar.hideActionMode(); + + if (dids.size() > 1 || dids.get(0) == UserConfig.getClientUserId() || message != null) { + for (int a = 0; a < dids.size(); a++) { + long did = dids.get(a); + if (message != null) { + SendMessagesHelper.getInstance().sendMessage(message.toString(), did, null, null, true, null, null, null); + } + SendMessagesHelper.getInstance().sendMessage(fmessages, did); + } + fragment.finishFragment(); + } else { + long did = dids.get(0); + int lower_part = (int) did; + int high_part = (int) (did >> 32); Bundle args = new Bundle(); args.putBoolean("scrollToTopOnResume", true); - if (lower_part > 0) { - args.putInt("user_id", lower_part); - } else if (lower_part < 0) { - args.putInt("chat_id", -lower_part); - } - if (!MessagesController.checkCanOpenChat(args, fragment)) { - return; - } - - ArrayList fmessages = new ArrayList<>(); - for (int a = 1; a >= 0; a--) { - ArrayList ids = new ArrayList<>(selectedFiles[a].keySet()); - Collections.sort(ids); - for (Integer id : ids) { - if (id > 0) { - fmessages.add(selectedFiles[a].get(id)); - } + if (lower_part != 0) { + if (lower_part > 0) { + args.putInt("user_id", lower_part); + } else if (lower_part < 0) { + args.putInt("chat_id", -lower_part); + } + } else { + args.putInt("enc_id", high_part); + } + if (lower_part != 0) { + if (!MessagesController.checkCanOpenChat(args, fragment)) { + return; } - selectedFiles[a].clear(); } - cantDeleteMessagesCount = 0; - actionBar.hideActionMode(); NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); @@ -456,8 +511,6 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No if (!AndroidUtilities.isTablet()) { removeSelfFromStack(); } - } else { - fragment.finishFragment(); } } }); @@ -653,10 +706,10 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No } if (!sharedMediaData[selectedMode].endReached[0]) { sharedMediaData[selectedMode].loading = true; - SharedMediaQuery.loadMedia(dialog_id, 0, 50, sharedMediaData[selectedMode].max_id[0], type, true, classGuid); + SharedMediaQuery.loadMedia(dialog_id, 50, sharedMediaData[selectedMode].max_id[0], type, true, classGuid); } else if (mergeDialogId != 0 && !sharedMediaData[selectedMode].endReached[1]) { sharedMediaData[selectedMode].loading = true; - SharedMediaQuery.loadMedia(mergeDialogId, 0, 50, sharedMediaData[selectedMode].max_id[1], type, true, classGuid); + SharedMediaQuery.loadMedia(mergeDialogId, 50, sharedMediaData[selectedMode].max_id[1], type, true, classGuid); } } } @@ -720,7 +773,7 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No switchToCurrentSelectedMode(); if (!AndroidUtilities.isTablet()) { - frameLayout.addView(fragmentContextView = new FragmentContextView(context, this), LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 39, Gravity.TOP | Gravity.LEFT, 0, -36, 0, 0)); + frameLayout.addView(fragmentContextView = new FragmentContextView(context, this, false), LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 39, Gravity.TOP | Gravity.LEFT, 0, -36, 0, 0)); } return fragmentView; @@ -745,7 +798,7 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No sharedMediaData[type].endReached[loadIndex] = (Boolean) args[5]; if (loadIndex == 0 && sharedMediaData[type].endReached[loadIndex] && mergeDialogId != 0) { sharedMediaData[type].loading = true; - SharedMediaQuery.loadMedia(mergeDialogId, 0, 50, sharedMediaData[type].max_id[1], type, true, classGuid); + SharedMediaQuery.loadMedia(mergeDialogId, 50, sharedMediaData[type].max_id[1], type, true, classGuid); } if (!sharedMediaData[type].loading) { if (progressView != null) { @@ -911,83 +964,6 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No } } - @Override - public void updatePhotoAtIndex(int index) { - - } - - @Override - public boolean scaleToFill() { - return false; - } - - @Override - public PhotoViewer.PlaceProviderObject getPlaceForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { - if (messageObject == null || listView == null || selectedMode != 0) { - return null; - } - int count = listView.getChildCount(); - - for (int a = 0; a < count; a++) { - View view = listView.getChildAt(a); - if (view instanceof SharedPhotoVideoCell) { - SharedPhotoVideoCell cell = (SharedPhotoVideoCell) view; - for (int i = 0; i < 6; i++) { - MessageObject message = cell.getMessageObject(i); - if (message == null) { - break; - } - BackupImageView imageView = cell.getImageView(i); - if (message.getId() == messageObject.getId()) { - int coords[] = new int[2]; - imageView.getLocationInWindow(coords); - PhotoViewer.PlaceProviderObject object = new PhotoViewer.PlaceProviderObject(); - object.viewX = coords[0]; - object.viewY = coords[1] - (Build.VERSION.SDK_INT >= 21 ? 0 : AndroidUtilities.statusBarHeight); - object.parentView = listView; - object.imageReceiver = imageView.getImageReceiver(); - object.thumb = object.imageReceiver.getBitmap(); - object.parentView.getLocationInWindow(coords); - object.clipTopAddition = AndroidUtilities.dp(40); - return object; - } - } - } - } - return null; - } - - @Override - public Bitmap getThumbForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { - return null; - } - - @Override - public boolean allowCaption() { - return true; - } - - @Override - public void willSwitchFromPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { } - - @Override - public void willHidePhotoViewer() { } - - @Override - public boolean isPhotoChecked(int index) { return false; } - - @Override - public void setPhotoChecked(int index, VideoEditedInfo videoEditedInfo) { } - - @Override - public boolean cancelButtonPressed() { return true; } - - @Override - public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo) { } - - @Override - public int getSelectedCount() { return 0; } - public void setChatInfo(TLRPC.ChatFull chatInfo) { info = chatInfo; if (info != null && info.migrated_from_chat_id != 0) { @@ -1064,7 +1040,7 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No searchItem.setVisibility(!sharedMediaData[selectedMode].messages.isEmpty() ? View.VISIBLE : View.GONE); if (!sharedMediaData[selectedMode].loading && !sharedMediaData[selectedMode].endReached[0] && sharedMediaData[selectedMode].messages.isEmpty()) { sharedMediaData[selectedMode].loading = true; - SharedMediaQuery.loadMedia(dialog_id, 0, 50, 0, selectedMode == 1 ? SharedMediaQuery.MEDIA_FILE : SharedMediaQuery.MEDIA_MUSIC, true, classGuid); + SharedMediaQuery.loadMedia(dialog_id, 50, 0, selectedMode == 1 ? SharedMediaQuery.MEDIA_FILE : SharedMediaQuery.MEDIA_MUSIC, true, classGuid); } listView.setVisibility(View.VISIBLE); if (sharedMediaData[selectedMode].loading && sharedMediaData[selectedMode].messages.isEmpty()) { @@ -1088,7 +1064,7 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No searchItem.setVisibility(!sharedMediaData[3].messages.isEmpty() ? View.VISIBLE : View.GONE); if (!sharedMediaData[selectedMode].loading && !sharedMediaData[selectedMode].endReached[0] && sharedMediaData[selectedMode].messages.isEmpty()) { sharedMediaData[selectedMode].loading = true; - SharedMediaQuery.loadMedia(dialog_id, 0, 50, 0, SharedMediaQuery.MEDIA_URL, true, classGuid); + SharedMediaQuery.loadMedia(dialog_id, 50, 0, SharedMediaQuery.MEDIA_URL, true, classGuid); } listView.setVisibility(View.VISIBLE); if (sharedMediaData[selectedMode].loading && sharedMediaData[selectedMode].messages.isEmpty()) { @@ -1174,7 +1150,7 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No } else { if (selectedMode == 0) { PhotoViewer.getInstance().setParentActivity(getParentActivity()); - PhotoViewer.getInstance().openPhoto(sharedMediaData[selectedMode].messages, index, dialog_id, mergeDialogId, this); + PhotoViewer.getInstance().openPhoto(sharedMediaData[selectedMode].messages, index, dialog_id, mergeDialogId, provider); } else if (selectedMode == 1 || selectedMode == 4) { if (view instanceof SharedDocumentCell) { SharedDocumentCell cell = (SharedDocumentCell) view; @@ -1739,9 +1715,8 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No return; } TLRPC.TL_messages_search req = new TLRPC.TL_messages_search(); - req.offset = 0; req.limit = 50; - req.max_id = max_id; + req.offset_id = max_id; if (currentType == 1) { req.filter = new TLRPC.TL_inputMessagesFilterDocument(); } else if (currentType == 3) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/NewContactActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/NewContactActivity.java index ba8f376f2..8c3fc4635 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/NewContactActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/NewContactActivity.java @@ -33,7 +33,6 @@ import android.view.MotionEvent; import android.view.View; import android.view.inputmethod.EditorInfo; import android.widget.AdapterView; -import android.widget.EditText; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.ScrollView; @@ -62,6 +61,7 @@ import org.telegram.ui.Components.AlertsCreator; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.Components.ContextProgressView; +import org.telegram.ui.Components.EditTextBoldCursor; import org.telegram.ui.Components.HintEditText; import org.telegram.ui.Components.LayoutHelper; @@ -78,9 +78,9 @@ public class NewContactActivity extends BaseFragment implements AdapterView.OnIt private ActionBarMenuItem editDoneItem; private ContextProgressView editDoneItemProgress; - private EditText firstNameField; - private EditText lastNameField; - private EditText codeField; + private EditTextBoldCursor firstNameField; + private EditTextBoldCursor lastNameField; + private EditTextBoldCursor codeField; private HintEditText phoneField; private BackupImageView avatarImage; private TextView countryButton; @@ -174,7 +174,7 @@ public class NewContactActivity extends BaseFragment implements AdapterView.OnIt public void onClick(DialogInterface dialog, int which) { try { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.fromParts("sms", inputPhoneContact.phone, null)); - intent.putExtra("sms_body", LocaleController.getString("InviteText", R.string.InviteText)); + intent.putExtra("sms_body", ContactsController.getInstance().getInviteText(1)); getParentActivity().startActivityForResult(intent, 500); } catch (Exception e) { FileLog.e(e); @@ -226,7 +226,7 @@ public class NewContactActivity extends BaseFragment implements AdapterView.OnIt avatarImage.setImageDrawable(avatarDrawable); frameLayout.addView(avatarImage, LayoutHelper.createFrame(60, 60, Gravity.LEFT | Gravity.TOP, 0, 9, 0, 0)); - firstNameField = new EditText(context); + firstNameField = new EditTextBoldCursor(context); firstNameField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); firstNameField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); firstNameField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); @@ -238,7 +238,9 @@ public class NewContactActivity extends BaseFragment implements AdapterView.OnIt firstNameField.setInputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT); firstNameField.setImeOptions(EditorInfo.IME_ACTION_NEXT); firstNameField.setHint(LocaleController.getString("FirstName", R.string.FirstName)); - AndroidUtilities.clearCursorDrawable(firstNameField); + firstNameField.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + firstNameField.setCursorSize(AndroidUtilities.dp(20)); + firstNameField.setCursorWidth(1.5f); frameLayout.addView(firstNameField, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 34, Gravity.LEFT | Gravity.TOP, 84, 0, 0, 0)); firstNameField.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override @@ -269,7 +271,7 @@ public class NewContactActivity extends BaseFragment implements AdapterView.OnIt } }); - lastNameField = new EditText(context); + lastNameField = new EditTextBoldCursor(context); lastNameField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); lastNameField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); lastNameField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); @@ -281,7 +283,9 @@ public class NewContactActivity extends BaseFragment implements AdapterView.OnIt lastNameField.setInputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT); lastNameField.setImeOptions(EditorInfo.IME_ACTION_NEXT); lastNameField.setHint(LocaleController.getString("LastName", R.string.LastName)); - AndroidUtilities.clearCursorDrawable(lastNameField); + lastNameField.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + lastNameField.setCursorSize(AndroidUtilities.dp(20)); + lastNameField.setCursorWidth(1.5f); frameLayout.addView(lastNameField, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 34, Gravity.LEFT | Gravity.TOP, 84, 44, 0, 0)); lastNameField.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override @@ -359,11 +363,13 @@ public class NewContactActivity extends BaseFragment implements AdapterView.OnIt textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); linearLayout2.addView(textView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); - codeField = new EditText(context); + codeField = new EditTextBoldCursor(context); codeField.setInputType(InputType.TYPE_CLASS_PHONE); codeField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); codeField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); - AndroidUtilities.clearCursorDrawable(codeField); + codeField.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + codeField.setCursorSize(AndroidUtilities.dp(20)); + codeField.setCursorWidth(1.5f); codeField.setPadding(AndroidUtilities.dp(10), 0, 0, 0); codeField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); codeField.setMaxLines(1); @@ -467,7 +473,9 @@ public class NewContactActivity extends BaseFragment implements AdapterView.OnIt phoneField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); phoneField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); phoneField.setPadding(0, 0, 0, 0); - AndroidUtilities.clearCursorDrawable(phoneField); + phoneField.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + phoneField.setCursorSize(AndroidUtilities.dp(20)); + phoneField.setCursorWidth(1.5f); phoneField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); phoneField.setMaxLines(1); phoneField.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL); @@ -787,7 +795,7 @@ public class NewContactActivity extends BaseFragment implements AdapterView.OnIt new ThemeDescription(editDoneItemProgress, 0, null, null, null, null, Theme.key_contextProgressInner2), new ThemeDescription(editDoneItemProgress, 0, null, null, null, null, Theme.key_contextProgressOuter2), - new ThemeDescription(null, 0, null, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, сellDelegate, Theme.key_avatar_text), + new ThemeDescription(null, 0, null, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, сellDelegate, Theme.key_avatar_text), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), @@ -795,8 +803,6 @@ public class NewContactActivity extends BaseFragment implements AdapterView.OnIt new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundCyan), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundBlue), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundPink), - - //TODO edittext }; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/NotificationsSettingsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/NotificationsSettingsActivity.java index 829a4f11b..c24676c60 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/NotificationsSettingsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/NotificationsSettingsActivity.java @@ -140,14 +140,10 @@ public class NotificationsSettingsActivity extends BaseFragment implements Notif } else { inappPriorityRow = -1; } - if (MessagesController.getInstance().callsEnabled) { - callsSectionRow2 = rowCount++; - callsSectionRow = rowCount++; - callsVibrateRow = rowCount++; - callsRingtoneRow = rowCount++; - } else { - callsSectionRow2 = callsSectionRow = callsVibrateRow = callsRingtoneRow = -1; - } + callsSectionRow2 = rowCount++; + callsSectionRow = rowCount++; + callsVibrateRow = rowCount++; + callsRingtoneRow = rowCount++; eventsSectionRow2 = rowCount++; eventsSectionRow = rowCount++; contactJoinedRow = rowCount++; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PasscodeActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PasscodeActivity.java index 2ef34a1ba..a511954dc 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PasscodeActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PasscodeActivity.java @@ -34,7 +34,6 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.inputmethod.EditorInfo; -import android.widget.EditText; import android.widget.FrameLayout; import android.widget.TextView; import android.widget.Toast; @@ -60,6 +59,7 @@ import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.TextCheckCell; import org.telegram.ui.Cells.TextInfoPrivacyCell; import org.telegram.ui.Cells.TextSettingsCell; +import org.telegram.ui.Components.EditTextBoldCursor; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.NumberPicker; import org.telegram.ui.Components.RecyclerListView; @@ -69,7 +69,7 @@ public class PasscodeActivity extends BaseFragment implements NotificationCenter private ListAdapter listAdapter; private RecyclerListView listView; private TextView titleTextView; - private EditText passwordEditText; + private EditTextBoldCursor passwordEditText; private TextView dropDown; private ActionBarMenuItem dropDownContainer; private Drawable dropDownDrawable; @@ -165,7 +165,7 @@ public class PasscodeActivity extends BaseFragment implements NotificationCenter titleTextView.setGravity(Gravity.CENTER_HORIZONTAL); frameLayout.addView(titleTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL, 0, 38, 0, 0)); - passwordEditText = new EditText(context); + passwordEditText = new EditTextBoldCursor(context); passwordEditText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); passwordEditText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); passwordEditText.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); @@ -182,7 +182,9 @@ public class PasscodeActivity extends BaseFragment implements NotificationCenter } passwordEditText.setTransformationMethod(PasswordTransformationMethod.getInstance()); passwordEditText.setTypeface(Typeface.DEFAULT); - AndroidUtilities.clearCursorDrawable(passwordEditText); + passwordEditText.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + passwordEditText.setCursorSize(AndroidUtilities.dp(20)); + passwordEditText.setCursorWidth(1.5f); frameLayout.addView(passwordEditText, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 36, Gravity.TOP | Gravity.LEFT, 40, 90, 40, 0)); passwordEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PaymentFormActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PaymentFormActivity.java index 6735335fc..c421f7d7d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PaymentFormActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PaymentFormActivity.java @@ -15,6 +15,7 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.app.Activity; +import android.app.Dialog; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.content.Context; @@ -61,6 +62,7 @@ import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; +import android.widget.Toast; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.BooleanResult; @@ -126,6 +128,7 @@ import org.telegram.ui.Cells.TextPriceCell; import org.telegram.ui.Cells.TextSettingsCell; import org.telegram.ui.Components.AlertsCreator; import org.telegram.ui.Components.ContextProgressView; +import org.telegram.ui.Components.EditTextBoldCursor; import org.telegram.ui.Components.HintEditText; import org.telegram.ui.Components.LayoutHelper; @@ -164,6 +167,11 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen private final static int FIELD_SAVEDPASSWORD = 1; private final static int FIELDS_COUNT_SAVEDCARD = 2; + private final static int FIELD_ENTERPASSWORD = 0; + private final static int FIELD_REENTERPASSWORD = 1; + private final static int FIELD_ENTERPASSWORDEMAIL = 2; + private final static int FIELDS_COUNT_PASSWORD = 3; + private ArrayList countriesArray = new ArrayList<>(); private HashMap countriesMap = new HashMap<>(); private HashMap codesMap = new HashMap<>(); @@ -171,7 +179,7 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen private GoogleApiClient googleApiClient; - private EditText[] inputFields; + private EditTextBoldCursor[] inputFields; private RadioCell[] radioCells; private ActionBarMenuItem doneItem; private ContextProgressView progressView; @@ -185,7 +193,7 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen private ArrayList dividers = new ArrayList<>(); private ShadowSectionCell sectionCell[] = new ShadowSectionCell[3]; private TextCheckCell checkCell1; - private TextInfoPrivacyCell bottomCell[] = new TextInfoPrivacyCell[2]; + private TextInfoPrivacyCell bottomCell[] = new TextInfoPrivacyCell[3]; private TextSettingsCell settingsCell1; private FrameLayout androidPayContainer; private LinearLayout linearLayout2; @@ -197,6 +205,12 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen private PaymentInfoCell paymentInfoCell; private TextDetailSettingsCell detailSettingsCell[] = new TextDetailSettingsCell[7]; + private TLRPC.account_Password currentPassword; + private boolean waitingForEmail; + private Runnable shortPollRunnable; + private boolean loadingPasswordInfo; + private PaymentFormActivity passwordFragment; + private boolean need_card_country; private boolean need_card_postcode; private boolean need_card_name; @@ -222,6 +236,10 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen private TLRPC.TL_payments_validatedRequestedInfo requestedInfo; private TLRPC.TL_shippingOption shippingOption; private TLRPC.TL_payments_validateRequestedInfo validateRequest; + private TLRPC.TL_inputPaymentCredentialsAndroidPay androidPayCredentials; + private String androidPayPublicKey; + private int androidPayBackgroundColor; + private boolean androidPayBlackTheme; private MessageObject messageObject; private boolean donePressed; private boolean canceled; @@ -238,7 +256,9 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen private final static int fragment_container_id = 4000; private interface PaymentFormActivityDelegate { - void didSelectNewCard(String tokenJson, String card, boolean saveCard); + boolean didSelectNewCard(String tokenJson, String card, boolean saveCard, TLRPC.TL_inputPaymentCredentialsAndroidPay androidPay); + void onFragmentDestroyed(); + void currentPasswordUpdated(TLRPC.account_Password password); } private class TelegramWebviewProxy { @@ -338,20 +358,36 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen } else { step = 2; } - init(form, message, step, null, null, null, null, null, false); + init(form, message, step, null, null, null, null, null, false, null); } - private PaymentFormActivity(TLRPC.TL_payments_paymentForm form, MessageObject message, int step, TLRPC.TL_payments_validatedRequestedInfo validatedRequestedInfo, TLRPC.TL_shippingOption shipping, String tokenJson, String card, TLRPC.TL_payments_validateRequestedInfo request, boolean saveCard) { - init(form, message, step, validatedRequestedInfo, shipping, tokenJson, card, request, saveCard); + private PaymentFormActivity(TLRPC.TL_payments_paymentForm form, MessageObject message, int step, TLRPC.TL_payments_validatedRequestedInfo validatedRequestedInfo, TLRPC.TL_shippingOption shipping, String tokenJson, String card, TLRPC.TL_payments_validateRequestedInfo request, boolean saveCard, TLRPC.TL_inputPaymentCredentialsAndroidPay androidPay) { + init(form, message, step, validatedRequestedInfo, shipping, tokenJson, card, request, saveCard, androidPay); + } + + private void setCurrentPassword(TLRPC.account_Password password) { + if (password instanceof TLRPC.TL_account_password) { + if (getParentActivity() == null) { + return; + } + goToNextStep(); + } else { + currentPassword = password; + if (currentPassword != null) { + waitingForEmail = currentPassword.email_unconfirmed_pattern.length() > 0; + } + updatePasswordFields(); + } } private void setDelegate(PaymentFormActivityDelegate paymentFormActivityDelegate) { delegate = paymentFormActivityDelegate; } - private void init(TLRPC.TL_payments_paymentForm form, MessageObject message, int step, TLRPC.TL_payments_validatedRequestedInfo validatedRequestedInfo, TLRPC.TL_shippingOption shipping, String tokenJson, String card, TLRPC.TL_payments_validateRequestedInfo request, boolean saveCard) { + private void init(TLRPC.TL_payments_paymentForm form, MessageObject message, int step, TLRPC.TL_payments_validatedRequestedInfo validatedRequestedInfo, TLRPC.TL_shippingOption shipping, String tokenJson, String card, TLRPC.TL_payments_validateRequestedInfo request, boolean saveCard, TLRPC.TL_inputPaymentCredentialsAndroidPay androidPay) { currentStep = step; paymentJson = tokenJson; + androidPayCredentials = androidPay; requestedInfo = validatedRequestedInfo; paymentForm = form; shippingOption = shipping; @@ -387,7 +423,7 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen AndroidUtilities.requestAdjustResize(getParentActivity(), classGuid); if (Build.VERSION.SDK_INT >= 23) { try { - if (currentStep == 2 && !paymentForm.invoice.test) { + if ((currentStep == 2 || currentStep == 6) && !paymentForm.invoice.test) { getParentActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); } else if (UserConfig.passcodeHash.length() == 0 || UserConfig.allowScreenCapture) { getParentActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE); @@ -431,6 +467,8 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen } else { actionBar.setTitle(LocaleController.getString("PaymentReceipt", R.string.PaymentReceipt)); } + } else if (currentStep == 6) { + actionBar.setTitle(LocaleController.getString("PaymentPassword", R.string.PaymentPassword)); } actionBar.setBackButtonImage(R.drawable.ic_ab_back); @@ -466,6 +504,8 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen sendCardData(); } else if (currentStep == 3) { checkPassword(); + } else if (currentStep == 6) { + sendSavePassword(false); } } } @@ -473,7 +513,7 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen ActionBarMenu menu = actionBar.createMenu(); - if (currentStep == 0 || currentStep == 1 || currentStep == 2 || currentStep == 3 || currentStep == 4) { + if (currentStep == 0 || currentStep == 1 || currentStep == 2 || currentStep == 3 || currentStep == 4 || currentStep == 6) { doneItem = menu.addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56)); progressView = new ContextProgressView(context, 1); doneItem.addView(progressView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); @@ -522,7 +562,7 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen } }); - inputFields = new EditText[FIELDS_COUNT_ADDRESS]; + inputFields = new EditTextBoldCursor[FIELDS_COUNT_ADDRESS]; for (int a = 0; a < FIELDS_COUNT_ADDRESS; a++) { if (a == FIELD_STREET1) { headerCell[0] = new HeaderCell(context); @@ -570,14 +610,16 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen if (a == FIELD_PHONE) { inputFields[a] = new HintEditText(context); } else { - inputFields[a] = new EditText(context); + inputFields[a] = new EditTextBoldCursor(context); } inputFields[a].setTag(a); inputFields[a].setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); inputFields[a].setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); inputFields[a].setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); inputFields[a].setBackgroundDrawable(null); - AndroidUtilities.clearCursorDrawable(inputFields[a]); + inputFields[a].setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + inputFields[a].setCursorSize(AndroidUtilities.dp(20)); + inputFields[a].setCursorWidth(1.5f); if (a == FIELD_COUNTRY) { inputFields[a].setOnTouchListener(new View.OnTouchListener() { @Override @@ -853,8 +895,35 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen } }); if (a == FIELD_PHONE) { - sectionCell[1] = new ShadowSectionCell(context); - linearLayout2.addView(sectionCell[1], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + if (paymentForm.invoice.email_to_provider || paymentForm.invoice.phone_to_provider) { + TLRPC.User providerUser = null; + for (int b = 0; b < paymentForm.users.size(); b++) { + TLRPC.User user = paymentForm.users.get(b); + if (user.id == paymentForm.provider_id) { + providerUser = user; + } + } + final String providerName; + if (providerUser != null) { + providerName = ContactsController.formatName(providerUser.first_name, providerUser.last_name); + } else { + providerName = ""; + } + + bottomCell[1] = new TextInfoPrivacyCell(context); + bottomCell[1].setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + linearLayout2.addView(bottomCell[1], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + if (paymentForm.invoice.email_to_provider && paymentForm.invoice.phone_to_provider) { + bottomCell[1].setText(LocaleController.formatString("PaymentPhoneEmailToProvider", R.string.PaymentPhoneEmailToProvider, providerName)); + } else if (paymentForm.invoice.email_to_provider) { + bottomCell[1].setText(LocaleController.formatString("PaymentEmailToProvider", R.string.PaymentPhoneEmailToProvider, providerName)); + } else { + bottomCell[1].setText(LocaleController.formatString("PaymentPhoneToProvider", R.string.PaymentPhoneEmailToProvider, providerName)); + } + } else { + sectionCell[1] = new ShadowSectionCell(context); + linearLayout2.addView(sectionCell[1], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } checkCell1 = new TextCheckCell(context); checkCell1.setBackgroundDrawable(Theme.getSelectorDrawable(true)); @@ -895,7 +964,11 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen inputFields[FIELD_POSTCODE].setImeOptions(EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI); } - sectionCell[1].setVisibility(paymentForm.invoice.name_requested || paymentForm.invoice.phone_requested || paymentForm.invoice.email_requested ? View.VISIBLE : View.GONE); + if (sectionCell[1] != null) { + sectionCell[1].setVisibility(paymentForm.invoice.name_requested || paymentForm.invoice.phone_requested || paymentForm.invoice.email_requested ? View.VISIBLE : View.GONE); + } else if (bottomCell[1] != null) { + bottomCell[1].setVisibility(paymentForm.invoice.name_requested || paymentForm.invoice.phone_requested || paymentForm.invoice.email_requested ? View.VISIBLE : View.GONE); + } headerCell[1].setVisibility(paymentForm.invoice.name_requested || paymentForm.invoice.phone_requested || paymentForm.invoice.email_requested ? View.VISIBLE : View.GONE); if (!paymentForm.invoice.shipping_address_requested) { headerCell[0].setVisibility(View.GONE); @@ -937,7 +1010,41 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen } } } else if (currentStep == 2) { + if (paymentForm.native_params != null) { + try { + JSONObject jsonObject = new JSONObject(paymentForm.native_params.data); + try { + String androidPayKey = jsonObject.getString("android_pay_public_key"); + if (!TextUtils.isEmpty(androidPayKey)) { + androidPayPublicKey = androidPayKey; + } + } catch (Exception e) { + androidPayPublicKey = null; + } + try { + androidPayBackgroundColor = jsonObject.getInt("android_pay_bgcolor") | 0xff000000; + } catch (Exception e) { + androidPayBackgroundColor = 0xffffffff; + } + try { + androidPayBlackTheme = jsonObject.getBoolean("android_pay_inverse"); + } catch (Exception e) { + androidPayBlackTheme = false; + } + } catch (Exception e) { + FileLog.e(e); + } + } if (isWebView) { + if (androidPayPublicKey != null) { + initAndroidPay(context); + } + androidPayContainer = new FrameLayout(context); + androidPayContainer.setId(fragment_container_id); + androidPayContainer.setBackgroundDrawable(Theme.getSelectorDrawable(true)); + androidPayContainer.setVisibility(View.GONE); + linearLayout2.addView(androidPayContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48)); + webviewLoading = true; showEditDoneProgress(true, true); progressView.setVisibility(View.VISIBLE); @@ -996,75 +1103,37 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen updateSavePaymentField(); linearLayout2.addView(bottomCell[0], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); } else { - try { - JSONObject jsonObject = new JSONObject(paymentForm.native_params.data); + if (paymentForm.native_params != null) { try { - need_card_country = jsonObject.getBoolean("need_country"); + JSONObject jsonObject = new JSONObject(paymentForm.native_params.data); + try { + need_card_country = jsonObject.getBoolean("need_country"); + } catch (Exception e) { + need_card_country = false; + } + try { + need_card_postcode = jsonObject.getBoolean("need_zip"); + } catch (Exception e) { + need_card_postcode = false; + } + try { + need_card_name = jsonObject.getBoolean("need_cardholder_name"); + } catch (Exception e) { + need_card_name = false; + } + try { + stripeApiKey = jsonObject.getString("publishable_key"); + } catch (Exception e) { + stripeApiKey = ""; + } } catch (Exception e) { - need_card_country = false; + FileLog.e(e); } - try { - need_card_postcode = jsonObject.getBoolean("need_zip"); - } catch (Exception e) { - need_card_postcode = false; - } - try { - need_card_name = jsonObject.getBoolean("need_cardholder_name"); - } catch (Exception e) { - need_card_name = false; - } - try { - stripeApiKey = jsonObject.getString("publishable_key"); - } catch (Exception e) { - stripeApiKey = ""; - } - } catch (Exception e) { - FileLog.e(e); } - if (Build.VERSION.SDK_INT >= 19) { - googleApiClient = new GoogleApiClient.Builder(context) - .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() { - @Override - public void onConnected(Bundle bundle) { + initAndroidPay(context); - } - - @Override - public void onConnectionSuspended(int i) { - - } - }) - .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() { - @Override - public void onConnectionFailed(ConnectionResult connectionResult) { - - } - }) - .addApi(Wallet.API, new Wallet.WalletOptions.Builder() - .setEnvironment(paymentForm.invoice.test ? WalletConstants.ENVIRONMENT_TEST : WalletConstants.ENVIRONMENT_PRODUCTION) - .setTheme(WalletConstants.THEME_LIGHT) - .build()) - .build(); - - Wallet.Payments.isReadyToPay(googleApiClient).setResultCallback( - new ResultCallback() { - @Override - public void onResult(BooleanResult booleanResult) { - if (booleanResult.getStatus().isSuccess()) { - if (booleanResult.getValue()) { - showAndroidPay(); - } - } else { - - } - } - } - ); - googleApiClient.connect(); - } - - inputFields = new EditText[FIELDS_COUNT_CARD]; + inputFields = new EditTextBoldCursor[FIELDS_COUNT_CARD]; for (int a = 0; a < FIELDS_COUNT_CARD; a++) { if (a == FIELD_CARD) { headerCell[0] = new HeaderCell(context); @@ -1084,13 +1153,15 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen container.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); View.OnTouchListener onTouchListener = null; - inputFields[a] = new EditText(context); + inputFields[a] = new EditTextBoldCursor(context); inputFields[a].setTag(a); inputFields[a].setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); inputFields[a].setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); inputFields[a].setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); inputFields[a].setBackgroundDrawable(null); - AndroidUtilities.clearCursorDrawable(inputFields[a]); + inputFields[a].setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + inputFields[a].setCursorSize(AndroidUtilities.dp(20)); + inputFields[a].setCursorWidth(1.5f); if (a == FIELD_CVV) { InputFilter[] inputFilters = new InputFilter[1]; inputFilters[0] = new InputFilter.LengthFilter(3); @@ -1524,7 +1595,7 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen bottomCell[0].setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); linearLayout2.addView(bottomCell[0], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); } else if (currentStep == 3) { - inputFields = new EditText[FIELDS_COUNT_SAVEDCARD]; + inputFields = new EditTextBoldCursor[FIELDS_COUNT_SAVEDCARD]; for (int a = 0; a < FIELDS_COUNT_SAVEDCARD; a++) { if (a == FIELD_SAVEDCARD) { headerCell[0] = new HeaderCell(context); @@ -1552,13 +1623,15 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen container.addView(divider, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1, Gravity.LEFT | Gravity.BOTTOM)); } - inputFields[a] = new EditText(context); + inputFields[a] = new EditTextBoldCursor(context); inputFields[a].setTag(a); inputFields[a].setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); inputFields[a].setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); inputFields[a].setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); inputFields[a].setBackgroundDrawable(null); - AndroidUtilities.clearCursorDrawable(inputFields[a]); + inputFields[a].setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + inputFields[a].setCursorSize(AndroidUtilities.dp(20)); + inputFields[a].setCursorWidth(1.5f); if (a == FIELD_SAVEDCARD) { inputFields[a].setOnTouchListener(new View.OnTouchListener() { @Override @@ -1662,14 +1735,27 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen detailSettingsCell[0].setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - PaymentFormActivity activity = new PaymentFormActivity(paymentForm, messageObject, 2, requestedInfo, shippingOption, null, cardName, validateRequest, saveCardInfo); + PaymentFormActivity activity = new PaymentFormActivity(paymentForm, messageObject, 2, requestedInfo, shippingOption, null, cardName, validateRequest, saveCardInfo, null); activity.setDelegate(new PaymentFormActivityDelegate() { @Override - public void didSelectNewCard(String tokenJson, String card, boolean saveCard) { + public boolean didSelectNewCard(String tokenJson, String card, boolean saveCard, TLRPC.TL_inputPaymentCredentialsAndroidPay androidPay) { + paymentForm.saved_credentials = null; paymentJson = tokenJson; saveCardInfo = saveCard; cardName = card; + androidPayCredentials = androidPay; detailSettingsCell[0].setTextAndValue(cardName, LocaleController.getString("PaymentCheckoutMethod", R.string.PaymentCheckoutMethod), true); + return false; + } + + @Override + public void onFragmentDestroyed() { + + } + + @Override + public void currentPasswordUpdated(TLRPC.account_Password password) { + } }); presentFragment(activity); @@ -1799,6 +1885,7 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen } webView.setWebViewClient(new WebViewClient() { + @Override public void onLoadResource(WebView view, String url) { try { @@ -1821,8 +1908,6 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen updateSavePaymentField(); } - - @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { try { @@ -1845,10 +1930,241 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen sectionCell[1] = new ShadowSectionCell(context); sectionCell[1].setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); linearLayout2.addView(sectionCell[1], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } else if (currentStep == 6) { + bottomCell[2] = new TextInfoPrivacyCell(context); + bottomCell[2].setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + linearLayout2.addView(bottomCell[2], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + settingsCell1 = new TextSettingsCell(context); + settingsCell1.setBackgroundDrawable(Theme.getSelectorDrawable(true)); + settingsCell1.setTag(Theme.key_windowBackgroundWhiteRedText3); + settingsCell1.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText3)); + settingsCell1.setText(LocaleController.getString("AbortPassword", R.string.AbortPassword), false); + linearLayout2.addView(settingsCell1, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + settingsCell1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setMessage(LocaleController.getString("TurnPasswordOffQuestion", R.string.TurnPasswordOffQuestion)); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + sendSavePassword(true); + } + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + showDialog(builder.create()); + } + }); + + inputFields = new EditTextBoldCursor[FIELDS_COUNT_PASSWORD]; + for (int a = 0; a < FIELDS_COUNT_PASSWORD; a++) { + if (a == FIELD_ENTERPASSWORD) { + headerCell[0] = new HeaderCell(context); + headerCell[0].setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + headerCell[0].setText(LocaleController.getString("PaymentPasswordTitle", R.string.PaymentPasswordTitle)); + linearLayout2.addView(headerCell[0], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } else if (a == FIELD_ENTERPASSWORDEMAIL) { + headerCell[1] = new HeaderCell(context); + headerCell[1].setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + headerCell[1].setText(LocaleController.getString("PaymentPasswordEmailTitle", R.string.PaymentPasswordEmailTitle)); + linearLayout2.addView(headerCell[1], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } + + ViewGroup container = new FrameLayout(context); + linearLayout2.addView(container, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48)); + container.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + + if (a == FIELD_ENTERPASSWORD) { + View divider = new View(context); + dividers.add(divider); + divider.setBackgroundColor(Theme.getColor(Theme.key_divider)); + container.addView(divider, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1, Gravity.LEFT | Gravity.BOTTOM)); + } + + inputFields[a] = new EditTextBoldCursor(context); + inputFields[a].setTag(a); + inputFields[a].setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + inputFields[a].setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + inputFields[a].setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + inputFields[a].setBackgroundDrawable(null); + inputFields[a].setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + inputFields[a].setCursorSize(AndroidUtilities.dp(20)); + inputFields[a].setCursorWidth(1.5f); + + if (a == FIELD_ENTERPASSWORD || a == FIELD_REENTERPASSWORD) { + inputFields[a].setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + inputFields[a].setTypeface(Typeface.DEFAULT); + inputFields[a].setImeOptions(EditorInfo.IME_ACTION_NEXT | EditorInfo.IME_FLAG_NO_EXTRACT_UI); + } else { + inputFields[a].setImeOptions(EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI); + } + + switch (a) { + case FIELD_ENTERPASSWORD: + inputFields[a].setHint(LocaleController.getString("PaymentPasswordEnter", R.string.PaymentPasswordEnter)); + inputFields[a].requestFocus(); + break; + case FIELD_REENTERPASSWORD: + inputFields[a].setHint(LocaleController.getString("PaymentPasswordReEnter", R.string.PaymentPasswordReEnter)); + break; + case FIELD_ENTERPASSWORDEMAIL: + inputFields[a].setHint(LocaleController.getString("PaymentPasswordEmail", R.string.PaymentPasswordEmail)); + break; + } + + inputFields[a].setPadding(0, 0, 0, AndroidUtilities.dp(6)); + inputFields[a].setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); + container.addView(inputFields[a], LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 17, 12, 17, 6)); + + inputFields[a].setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { + if (i == EditorInfo.IME_ACTION_DONE) { + doneItem.performClick(); + return true; + } else if (i == EditorInfo.IME_ACTION_NEXT) { + int num = (Integer) textView.getTag(); + if (num == FIELD_ENTERPASSWORD) { + inputFields[FIELD_REENTERPASSWORD].requestFocus(); + } else if (num == FIELD_REENTERPASSWORD) { + inputFields[FIELD_ENTERPASSWORDEMAIL].requestFocus(); + } + } + return false; + } + }); + if (a == FIELD_REENTERPASSWORD) { + bottomCell[0] = new TextInfoPrivacyCell(context); + bottomCell[0].setText(LocaleController.getString("PaymentPasswordInfo", R.string.PaymentPasswordInfo)); + bottomCell[0].setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + linearLayout2.addView(bottomCell[0], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } else if (a == FIELD_ENTERPASSWORDEMAIL) { + bottomCell[1] = new TextInfoPrivacyCell(context); + bottomCell[1].setText(LocaleController.getString("PaymentPasswordEmailInfo", R.string.PaymentPasswordEmailInfo)); + bottomCell[1].setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + linearLayout2.addView(bottomCell[1], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } + } + updatePasswordFields(); } return fragmentView; } + private void updatePasswordFields() { + if (currentStep != 6 || bottomCell[2] == null) { + return; + } + if (currentPassword == null) { + doneItem.setVisibility(View.VISIBLE); + showEditDoneProgress(true, true); + bottomCell[2].setVisibility(View.GONE); + settingsCell1.setVisibility(View.GONE); + headerCell[0].setVisibility(View.GONE); + headerCell[1].setVisibility(View.GONE); + bottomCell[0].setVisibility(View.GONE); + for (int a = 0; a < FIELDS_COUNT_PASSWORD; a++) { + ((View) inputFields[a].getParent()).setVisibility(View.GONE); + } + for (int a = 0; a < dividers.size(); a++) { + dividers.get(a).setVisibility(View.GONE); + } + } else { + showEditDoneProgress(true, false); + if (waitingForEmail) { + if (getParentActivity() != null) { + AndroidUtilities.hideKeyboard(getParentActivity().getCurrentFocus()); + } + doneItem.setVisibility(View.GONE); + bottomCell[2].setText(LocaleController.formatString("EmailPasswordConfirmText", R.string.EmailPasswordConfirmText, currentPassword.email_unconfirmed_pattern)); + bottomCell[2].setVisibility(View.VISIBLE); + settingsCell1.setVisibility(View.VISIBLE); + bottomCell[1].setText(""); + + headerCell[0].setVisibility(View.GONE); + headerCell[1].setVisibility(View.GONE); + bottomCell[0].setVisibility(View.GONE); + for (int a = 0; a < FIELDS_COUNT_PASSWORD; a++) { + ((View) inputFields[a].getParent()).setVisibility(View.GONE); + } + for (int a = 0; a < dividers.size(); a++) { + dividers.get(a).setVisibility(View.GONE); + } + } else { + doneItem.setVisibility(View.VISIBLE); + bottomCell[2].setVisibility(View.GONE); + settingsCell1.setVisibility(View.GONE); + bottomCell[1].setText(LocaleController.getString("PaymentPasswordEmailInfo", R.string.PaymentPasswordEmailInfo)); + + headerCell[0].setVisibility(View.VISIBLE); + headerCell[1].setVisibility(View.VISIBLE); + bottomCell[0].setVisibility(View.VISIBLE); + for (int a = 0; a < FIELDS_COUNT_PASSWORD; a++) { + ((View) inputFields[a].getParent()).setVisibility(View.VISIBLE); + } + for (int a = 0; a < dividers.size(); a++) { + dividers.get(a).setVisibility(View.VISIBLE); + } + } + } + } + + private void loadPasswordInfo() { + if (loadingPasswordInfo) { + return; + } + loadingPasswordInfo = true; + TLRPC.TL_account_getPassword req = new TLRPC.TL_account_getPassword(); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, final TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + loadingPasswordInfo = false; + if (error == null) { + currentPassword = (TLRPC.account_Password) response; + if (paymentForm != null && currentPassword instanceof TLRPC.TL_account_password) { + paymentForm.password_missing = false; + paymentForm.can_save_credentials = true; + updateSavePaymentField(); + } + byte[] salt = new byte[currentPassword.new_salt.length + 8]; + Utilities.random.nextBytes(salt); + System.arraycopy(currentPassword.new_salt, 0, salt, 0, currentPassword.new_salt.length); + currentPassword.new_salt = salt; + if (passwordFragment != null) { + passwordFragment.setCurrentPassword(currentPassword); + } + } + if (response instanceof TLRPC.TL_account_noPassword && shortPollRunnable == null) { + shortPollRunnable = new Runnable() { + @Override + public void run() { + if (shortPollRunnable == null) { + return; + } + loadPasswordInfo(); + shortPollRunnable = null; + } + }; + AndroidUtilities.runOnUIThread(shortPollRunnable, 5000); + } + } + }); + } + }, ConnectionsManager.RequestFlagFailOnServerErrors | ConnectionsManager.RequestFlagWithoutLogin); + } + + private void showAlertWithText(String title, String text) { + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); + builder.setTitle(title); + builder.setMessage(text); + showDialog(builder.create()); + } + private void showPayAlert(final String totalPrice) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setTitle(LocaleController.getString("PaymentTransactionReview", R.string.PaymentTransactionReview)); @@ -1864,8 +2180,53 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen showDialog(builder.create()); } + private void initAndroidPay(Context context) { + if (Build.VERSION.SDK_INT < 19) { + return; + } + googleApiClient = new GoogleApiClient.Builder(context) + .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() { + @Override + public void onConnected(Bundle bundle) { + + } + + @Override + public void onConnectionSuspended(int i) { + + } + }) + .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() { + @Override + public void onConnectionFailed(ConnectionResult connectionResult) { + + } + }) + .addApi(Wallet.API, new Wallet.WalletOptions.Builder() + .setEnvironment(paymentForm.invoice.test ? WalletConstants.ENVIRONMENT_TEST : WalletConstants.ENVIRONMENT_PRODUCTION) + .setTheme(WalletConstants.THEME_LIGHT) + .build()) + .build(); + + Wallet.Payments.isReadyToPay(googleApiClient).setResultCallback( + new ResultCallback() { + @Override + public void onResult(BooleanResult booleanResult) { + if (booleanResult.getStatus().isSuccess()) { + if (booleanResult.getValue()) { + showAndroidPay(); + } + } else { + + } + } + } + ); + googleApiClient.connect(); + } + private String getTotalPriceString(ArrayList prices) { - int amount = 0; + long amount = 0; for (int a = 0; a < prices.size(); a++) { amount += prices.get(a).amount; } @@ -1873,7 +2234,7 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen } private String getTotalPriceDecimalString(ArrayList prices) { - int amount = 0; + long amount = 0; for (int a = 0; a < prices.size(); a++) { amount += prices.get(a).amount; } @@ -1892,6 +2253,9 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen @Override public void onFragmentDestroy() { + if (delegate != null) { + delegate.onFragmentDestroyed(); + } NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didSetTwoStepPassword); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didRemovedTwoStepPassword); if (currentStep != 4) { @@ -1912,7 +2276,7 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen } } try { - if (currentStep == 2 && Build.VERSION.SDK_INT >= 23 && (UserConfig.passcodeHash.length() == 0 || UserConfig.allowScreenCapture)) { + if ((currentStep == 2 || currentStep == 6) && Build.VERSION.SDK_INT >= 23 && (UserConfig.passcodeHash.length() == 0 || UserConfig.allowScreenCapture)) { getParentActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE); } } catch (Throwable e) { @@ -1935,6 +2299,11 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen } else if (currentStep == 3) { inputFields[FIELD_SAVEDPASSWORD].requestFocus(); AndroidUtilities.showKeyboard(inputFields[FIELD_SAVEDPASSWORD]); + } else if (currentStep == 6) { + if (!waitingForEmail) { + inputFields[FIELD_ENTERPASSWORD].requestFocus(); + AndroidUtilities.showKeyboard(inputFields[FIELD_ENTERPASSWORD]); + } } } } @@ -1955,7 +2324,7 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen } private void showAndroidPay() { - if (getParentActivity() == null || bottomCell[0] == null) { + if (getParentActivity() == null || androidPayContainer == null) { return; } @@ -1963,10 +2332,19 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen optionsBuilder.setEnvironment(paymentForm.invoice.test ? WalletConstants.ENVIRONMENT_TEST : WalletConstants.ENVIRONMENT_PRODUCTION); optionsBuilder.setMode(WalletFragmentMode.BUY_BUTTON); - WalletFragmentStyle walletFragmentStyle = new WalletFragmentStyle() - .setBuyButtonText(WalletFragmentStyle.BuyButtonText.LOGO_ONLY) - .setBuyButtonAppearance(WalletFragmentStyle.BuyButtonAppearance.ANDROID_PAY_LIGHT_WITH_BORDER) - .setBuyButtonWidth(WalletFragmentStyle.Dimension.WRAP_CONTENT); + WalletFragmentStyle walletFragmentStyle; + if (androidPayPublicKey != null) { + androidPayContainer.setBackgroundColor(androidPayBackgroundColor); + walletFragmentStyle = new WalletFragmentStyle() + .setBuyButtonText(WalletFragmentStyle.BuyButtonText.BUY_WITH) + .setBuyButtonAppearance(androidPayBlackTheme ? WalletFragmentStyle.BuyButtonAppearance.ANDROID_PAY_LIGHT_WITH_BORDER : WalletFragmentStyle.BuyButtonAppearance.ANDROID_PAY_DARK) + .setBuyButtonWidth(WalletFragmentStyle.Dimension.MATCH_PARENT); + } else { + walletFragmentStyle = new WalletFragmentStyle() + .setBuyButtonText(WalletFragmentStyle.BuyButtonText.LOGO_ONLY) + .setBuyButtonAppearance(WalletFragmentStyle.BuyButtonAppearance.ANDROID_PAY_LIGHT_WITH_BORDER) + .setBuyButtonWidth(WalletFragmentStyle.Dimension.WRAP_CONTENT); + } optionsBuilder.setFragmentStyle(walletFragmentStyle); WalletFragment walletFragment = WalletFragment.newInstance(optionsBuilder.build()); @@ -1982,14 +2360,23 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen } totalPriceDecimal = getTotalPriceDecimalString(arrayList); - MaskedWalletRequest maskedWalletRequest = MaskedWalletRequest.newBuilder() - .setPaymentMethodTokenizationParameters(PaymentMethodTokenizationParameters.newBuilder() - .setPaymentMethodTokenizationType(PaymentMethodTokenizationType.PAYMENT_GATEWAY) - .addParameter("gateway", "stripe") - .addParameter("stripe:publishableKey", stripeApiKey) - .addParameter("stripe:version", StripeApiHandler.VERSION) - .build()) + PaymentMethodTokenizationParameters parameters; + if (androidPayPublicKey != null) { + parameters = PaymentMethodTokenizationParameters.newBuilder() + .setPaymentMethodTokenizationType(PaymentMethodTokenizationType.NETWORK_TOKEN) + .addParameter("publicKey", androidPayPublicKey) + .build(); + } else { + parameters = PaymentMethodTokenizationParameters.newBuilder() + .setPaymentMethodTokenizationType(PaymentMethodTokenizationType.PAYMENT_GATEWAY) + .addParameter("gateway", "stripe") + .addParameter("stripe:publishableKey", stripeApiKey) + .addParameter("stripe:version", StripeApiHandler.VERSION) + .build(); + } + MaskedWalletRequest maskedWalletRequest = MaskedWalletRequest.newBuilder() + .setPaymentMethodTokenizationParameters(parameters) .setEstimatedTotalPrice(totalPriceDecimal) .setCurrencyCode(paymentForm.invoice.currency) .build(); @@ -2049,12 +2436,23 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen if (resultCode == Activity.RESULT_OK) { FullWallet fullWallet = data.getParcelableExtra(WalletConstants.EXTRA_FULL_WALLET); String tokenJSON = fullWallet.getPaymentMethodToken().getToken(); - try { - Token token = TokenParser.parseToken(tokenJSON); - paymentJson = String.format(Locale.US, "{\"type\":\"%1$s\", \"id\":\"%2$s\"}", token.getType(), token.getId()); - Card card = token.getCard(); - cardName = card.getType() + " *" + card.getLast4(); + if (androidPayPublicKey != null) { + androidPayCredentials = new TLRPC.TL_inputPaymentCredentialsAndroidPay(); + androidPayCredentials.payment_token = new TLRPC.TL_dataJSON(); + androidPayCredentials.payment_token.data = tokenJSON; + String[] descriptions = fullWallet.getPaymentDescriptions(); + if (descriptions.length > 0) { + cardName = descriptions[0]; + } else { + cardName = "Android Pay"; + } + } else { + Token token = TokenParser.parseToken(tokenJSON); + paymentJson = String.format(Locale.US, "{\"type\":\"%1$s\", \"id\":\"%2$s\"}", token.getType(), token.getId()); + Card card = token.getCard(); + cardName = card.getType() + " *" + card.getLast4(); + } goToNextStep(); showEditDoneProgress(true, false); setDonePressed(false); @@ -2089,7 +2487,7 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen } else { nextStep = 2; } - presentFragment(new PaymentFormActivity(paymentForm, messageObject, nextStep, requestedInfo, null, null, cardName, validateRequest, saveCardInfo), isWebView); + presentFragment(new PaymentFormActivity(paymentForm, messageObject, nextStep, requestedInfo, null, null, cardName, validateRequest, saveCardInfo, androidPayCredentials), isWebView); } else if (currentStep == 1) { int nextStep; if (paymentForm.saved_credentials != null) { @@ -2107,13 +2505,41 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen } else { nextStep = 2; } - presentFragment(new PaymentFormActivity(paymentForm, messageObject, nextStep, requestedInfo, shippingOption, null, cardName, validateRequest, saveCardInfo), isWebView); + presentFragment(new PaymentFormActivity(paymentForm, messageObject, nextStep, requestedInfo, shippingOption, null, cardName, validateRequest, saveCardInfo, androidPayCredentials), isWebView); } else if (currentStep == 2) { - if (delegate != null) { - delegate.didSelectNewCard(paymentJson, cardName, saveCardInfo); - finishFragment(); + if (paymentForm.password_missing && saveCardInfo) { + passwordFragment = new PaymentFormActivity(paymentForm, messageObject, 6, requestedInfo, shippingOption, paymentJson, cardName, validateRequest, saveCardInfo, androidPayCredentials); + passwordFragment.setCurrentPassword(currentPassword); + passwordFragment.setDelegate(new PaymentFormActivityDelegate() { + @Override + public boolean didSelectNewCard(String tokenJson, String card, boolean saveCard, TLRPC.TL_inputPaymentCredentialsAndroidPay androidPay) { + if (delegate != null) { + delegate.didSelectNewCard(tokenJson, card, saveCard, androidPay); + } + if (isWebView) { + removeSelfFromStack(); + } + return delegate != null; + } + + @Override + public void onFragmentDestroyed() { + passwordFragment = null; + } + + @Override + public void currentPasswordUpdated(TLRPC.account_Password password) { + currentPassword = password; + } + }); + presentFragment(passwordFragment, isWebView); } else { - presentFragment(new PaymentFormActivity(paymentForm, messageObject, 4, requestedInfo, shippingOption, paymentJson, cardName, validateRequest, saveCardInfo), isWebView); + if (delegate != null) { + delegate.didSelectNewCard(paymentJson, cardName, saveCardInfo, androidPayCredentials); + finishFragment(); + } else { + presentFragment(new PaymentFormActivity(paymentForm, messageObject, 4, requestedInfo, shippingOption, paymentJson, cardName, validateRequest, saveCardInfo, androidPayCredentials), isWebView); + } } } else if (currentStep == 3) { int nextStep; @@ -2122,10 +2548,16 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen } else { nextStep = 2; } - presentFragment(new PaymentFormActivity(paymentForm, messageObject, nextStep, requestedInfo, shippingOption, paymentJson, cardName, validateRequest, saveCardInfo), !passwordOk); + presentFragment(new PaymentFormActivity(paymentForm, messageObject, nextStep, requestedInfo, shippingOption, paymentJson, cardName, validateRequest, saveCardInfo, androidPayCredentials), !passwordOk); } else if (currentStep == 4) { NotificationCenter.getInstance().postNotificationName(NotificationCenter.paymentFinished); finishFragment(); + } else if (currentStep == 6) { + if (!delegate.didSelectNewCard(paymentJson, cardName, saveCardInfo, androidPayCredentials)) { + presentFragment(new PaymentFormActivity(paymentForm, messageObject, 4, requestedInfo, shippingOption, paymentJson, cardName, validateRequest, saveCardInfo, androidPayCredentials), true); + } else { + finishFragment(); + } } } @@ -2136,6 +2568,7 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen if ((paymentForm.password_missing || paymentForm.can_save_credentials) && (webView == null || webView != null && !webviewLoading)) { SpannableStringBuilder text = new SpannableStringBuilder(LocaleController.getString("PaymentCardSavePaymentInformationInfoLine1", R.string.PaymentCardSavePaymentInformationInfoLine1)); if (paymentForm.password_missing) { + loadPasswordInfo(); text.append("\n"); int len = text.length(); String str2 = LocaleController.getString("PaymentCardSavePaymentInformationInfoLine2", R.string.PaymentCardSavePaymentInformationInfoLine2); @@ -2150,10 +2583,8 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen text.replace(index1, index1 + 1, ""); text.setSpan(new LinkSpan(), index1, index2 - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } - checkCell1.setEnabled(false); - } else { - checkCell1.setEnabled(true); } + checkCell1.setEnabled(true); bottomCell[0].setText(text); checkCell1.setVisibility(View.VISIBLE); bottomCell[0].setVisibility(View.VISIBLE); @@ -2211,20 +2642,129 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen } } - private boolean sendCardData() { - if (paymentForm.saved_credentials != null && !saveCardInfo && paymentForm.can_save_credentials) { - /*TLRPC.TL_payments_clearSavedInfo req = new TLRPC.TL_payments_clearSavedInfo(); - req.credentials = true; - paymentForm.saved_credentials = null; - UserConfig.tmpPassword = null; - UserConfig.saveConfig(false); - ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { - @Override - public void run(TLObject response, TLRPC.TL_error error) { - + private void sendSavePassword(final boolean clear) { + final TLRPC.TL_account_updatePasswordSettings req = new TLRPC.TL_account_updatePasswordSettings(); + final String email; + if (clear) { + doneItem.setVisibility(View.VISIBLE); + email = null; + req.new_settings = new TLRPC.TL_account_passwordInputSettings(); + req.new_settings.flags = 2; + req.new_settings.email = ""; + req.current_password_hash = new byte[0]; + } else { + final String firstPassword = inputFields[FIELD_ENTERPASSWORD].getText().toString(); + if (TextUtils.isEmpty(firstPassword)) { + shakeField(FIELD_ENTERPASSWORD); + return; + } + String secondPassword = inputFields[FIELD_REENTERPASSWORD].getText().toString(); + if (!firstPassword.equals(secondPassword)) { + try { + Toast.makeText(getParentActivity(), LocaleController.getString("PasswordDoNotMatch", R.string.PasswordDoNotMatch), Toast.LENGTH_SHORT).show(); + } catch (Exception e) { + FileLog.e(e); } - });*/ + shakeField(FIELD_REENTERPASSWORD); + return; + } + email = inputFields[FIELD_ENTERPASSWORDEMAIL].getText().toString(); + if (email.length() < 3) { + shakeField(FIELD_ENTERPASSWORDEMAIL); + return; + } + int dot = email.lastIndexOf('.'); + int dog = email.lastIndexOf('@'); + if (dot < 0 || dog < 0 || dot < dog) { + shakeField(FIELD_ENTERPASSWORDEMAIL); + return; + } + + req.current_password_hash = new byte[0]; + req.new_settings = new TLRPC.TL_account_passwordInputSettings(); + byte[] newPasswordBytes = null; + try { + newPasswordBytes = firstPassword.getBytes("UTF-8"); + } catch (Exception e) { + FileLog.e(e); + } + + byte[] new_salt = currentPassword.new_salt; + byte[] hash = new byte[new_salt.length * 2 + newPasswordBytes.length]; + System.arraycopy(new_salt, 0, hash, 0, new_salt.length); + System.arraycopy(newPasswordBytes, 0, hash, new_salt.length, newPasswordBytes.length); + System.arraycopy(new_salt, 0, hash, hash.length - new_salt.length, new_salt.length); + req.new_settings.flags |= 1; + req.new_settings.hint = ""; + req.new_settings.new_password_hash = Utilities.computeSHA256(hash, 0, hash.length); + req.new_settings.new_salt = new_salt; + + if (email.length() > 0) { + req.new_settings.flags |= 2; + req.new_settings.email = email.trim(); + } } + showEditDoneProgress(true, true); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, final TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + showEditDoneProgress(true, false); + if (clear) { + currentPassword = new TLRPC.TL_account_noPassword(); + delegate.currentPasswordUpdated(currentPassword); + finishFragment(); + } else { + if (error == null && response instanceof TLRPC.TL_boolTrue) { + if (getParentActivity() == null) { + return; + } + goToNextStep(); + } else if (error != null) { + if (error.text.equals("EMAIL_UNCONFIRMED")) { + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + waitingForEmail = true; + currentPassword.email_unconfirmed_pattern = email; + updatePasswordFields(); + } + }); + builder.setMessage(LocaleController.getString("YourEmailAlmostThereText", R.string.YourEmailAlmostThereText)); + builder.setTitle(LocaleController.getString("YourEmailAlmostThere", R.string.YourEmailAlmostThere)); + Dialog dialog = showDialog(builder.create()); + if (dialog != null) { + dialog.setCanceledOnTouchOutside(false); + dialog.setCancelable(false); + } + } else { + if (error.text.equals("EMAIL_INVALID")) { + showAlertWithText(LocaleController.getString("AppName", R.string.AppName), LocaleController.getString("PasswordEmailInvalid", R.string.PasswordEmailInvalid)); + } else if (error.text.startsWith("FLOOD_WAIT")) { + int time = Utilities.parseInt(error.text); + String timeString; + if (time < 60) { + timeString = LocaleController.formatPluralString("Seconds", time); + } else { + timeString = LocaleController.formatPluralString("Minutes", time / 60); + } + showAlertWithText(LocaleController.getString("AppName", R.string.AppName), LocaleController.formatString("FloodWaitTime", R.string.FloodWaitTime, timeString)); + } else { + showAlertWithText(LocaleController.getString("AppName", R.string.AppName), error.text); + } + } + } + } + } + }); + } + }, ConnectionsManager.RequestFlagFailOnServerErrors | ConnectionsManager.RequestFlagWithoutLogin); + } + + private boolean sendCardData() { Integer month; Integer year; String date = inputFields[FIELD_EXPIRE_DATE].getText().toString(); @@ -2445,6 +2985,8 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen req.credentials = new TLRPC.TL_inputPaymentCredentialsSaved(); req.credentials.id = paymentForm.saved_credentials.id; req.credentials.tmp_password = UserConfig.tmpPassword.tmp_password; + } else if (androidPayCredentials != null) { + req.credentials = androidPayCredentials; } else { req.credentials = new TLRPC.TL_inputPaymentCredentials(); req.credentials.save = saveCardInfo; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PhotoAlbumPickerActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PhotoAlbumPickerActivity.java index ea2613016..4f7463220 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PhotoAlbumPickerActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PhotoAlbumPickerActivity.java @@ -27,9 +27,9 @@ import org.telegram.messenger.MessagesStorage; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.R; +import org.telegram.messenger.SendMessagesHelper; import org.telegram.messenger.support.widget.LinearLayoutManager; import org.telegram.messenger.support.widget.RecyclerView; -import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.BaseFragment; @@ -47,17 +47,19 @@ import java.util.HashMap; public class PhotoAlbumPickerActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { public interface PhotoAlbumPickerActivityDelegate { - void didSelectPhotos(ArrayList photos, ArrayList captions, ArrayList ttls, ArrayList videos, ArrayList> masks, ArrayList webPhotos); + void didSelectPhotos(ArrayList photos); void startPhotoSelectActivity(); } + private HashMap selectedPhotos = new HashMap<>(); + private ArrayList selectedPhotosOrder = new ArrayList<>(); + private ArrayList albumsSorted = null; - private HashMap selectedPhotos = new HashMap<>(); - private HashMap selectedWebPhotos = new HashMap<>(); private HashMap recentImagesWebKeys = new HashMap<>(); private HashMap recentImagesGifKeys = new HashMap<>(); private ArrayList recentWebImages = new ArrayList<>(); private ArrayList recentGifImages = new ArrayList<>(); + private boolean loading = false; private int columnsCount = 2; @@ -199,7 +201,7 @@ public class PhotoAlbumPickerActivity extends BaseFragment implements Notificati pickerBottomLayout.doneButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - sendSelectedPhotos(); + sendSelectedPhotos(selectedPhotos, selectedPhotosOrder); finishFragment(); } }); @@ -211,7 +213,7 @@ public class PhotoAlbumPickerActivity extends BaseFragment implements Notificati progressView.setVisibility(View.GONE); listView.setEmptyView(emptyView); } - pickerBottomLayout.updateSelectedCount(selectedPhotos.size() + selectedWebPhotos.size(), true); + pickerBottomLayout.updateSelectedCount(selectedPhotos.size(), true); return fragmentView; } @@ -277,67 +279,68 @@ public class PhotoAlbumPickerActivity extends BaseFragment implements Notificati this.delegate = delegate; } - private void sendSelectedPhotos() { - if (selectedPhotos.isEmpty() && selectedWebPhotos.isEmpty() || delegate == null || sendPressed) { + private void sendSelectedPhotos(HashMap photos, ArrayList order) { + if (photos.isEmpty() || delegate == null || sendPressed) { return; } sendPressed = true; - ArrayList photos = new ArrayList<>(); - ArrayList videos = new ArrayList<>(); - ArrayList captions = new ArrayList<>(); - ArrayList ttls = new ArrayList<>(); - ArrayList> masks = new ArrayList<>(); - for (HashMap.Entry entry : selectedPhotos.entrySet()) { - MediaController.PhotoEntry photoEntry = entry.getValue(); - if (photoEntry.isVideo) { - videos.add(photoEntry); - } else if (photoEntry.imagePath != null) { - photos.add(photoEntry.imagePath); - captions.add(photoEntry.caption != null ? photoEntry.caption.toString() : null); - masks.add(!photoEntry.stickers.isEmpty() ? new ArrayList<>(photoEntry.stickers) : null); - ttls.add(photoEntry.ttl); - } else if (photoEntry.path != null) { - photos.add(photoEntry.path); - captions.add(photoEntry.caption != null ? photoEntry.caption.toString() : null); - masks.add(!photoEntry.stickers.isEmpty() ? new ArrayList<>(photoEntry.stickers) : null); - ttls.add(photoEntry.ttl); - } - } - ArrayList webPhotos = new ArrayList<>(); boolean gifChanged = false; boolean webChange = false; - for (HashMap.Entry entry : selectedWebPhotos.entrySet()) { - MediaController.SearchImage searchImage = entry.getValue(); - if (searchImage.imagePath != null) { - photos.add(searchImage.imagePath); - captions.add(searchImage.caption != null ? searchImage.caption.toString() : null); - masks.add(!searchImage.stickers.isEmpty() ? new ArrayList<>(searchImage.stickers) : null); - ttls.add(searchImage.ttl); - } else { - webPhotos.add(searchImage); - } - searchImage.date = (int) (System.currentTimeMillis() / 1000); - if (searchImage.type == 0) { - webChange = true; - MediaController.SearchImage recentImage = recentImagesWebKeys.get(searchImage.id); - if (recentImage != null) { - recentWebImages.remove(recentImage); - recentWebImages.add(0, recentImage); - } else { - recentWebImages.add(0, searchImage); + ArrayList media = new ArrayList<>(); + for (int a = 0; a < order.size(); a++) { + Object object = photos.get(order.get(a)); + SendMessagesHelper.SendingMediaInfo info = new SendMessagesHelper.SendingMediaInfo(); + media.add(info); + if (object instanceof MediaController.PhotoEntry) { + MediaController.PhotoEntry photoEntry = (MediaController.PhotoEntry) object; + if (photoEntry.isVideo) { + info.path = photoEntry.path; + info.videoEditedInfo = photoEntry.editedInfo; + } else if (photoEntry.imagePath != null) { + info.path = photoEntry.imagePath; + } else if (photoEntry.path != null) { + info.path = photoEntry.path; } - } else if (searchImage.type == 1) { - gifChanged = true; - MediaController.SearchImage recentImage = recentImagesGifKeys.get(searchImage.id); - if (recentImage != null) { - recentGifImages.remove(recentImage); - recentGifImages.add(0, recentImage); + info.isVideo = photoEntry.isVideo; + info.caption = photoEntry.caption != null ? photoEntry.caption.toString() : null; + info.masks = !photoEntry.stickers.isEmpty() ? new ArrayList<>(photoEntry.stickers) : null; + info.ttl = photoEntry.ttl; + } else if (object instanceof MediaController.SearchImage) { + MediaController.SearchImage searchImage = (MediaController.SearchImage) object; + if (searchImage.imagePath != null) { + info.path = searchImage.imagePath; } else { - recentGifImages.add(0, searchImage); + info.searchImage = searchImage; + } + + info.caption = searchImage.caption != null ? searchImage.caption.toString() : null; + info.masks = !searchImage.stickers.isEmpty() ? new ArrayList<>(searchImage.stickers) : null; + info.ttl = searchImage.ttl; + + searchImage.date = (int) (System.currentTimeMillis() / 1000); + if (searchImage.type == 0) { + webChange = true; + MediaController.SearchImage recentImage = recentImagesWebKeys.get(searchImage.id); + if (recentImage != null) { + recentWebImages.remove(recentImage); + recentWebImages.add(0, recentImage); + } else { + recentWebImages.add(0, searchImage); + } + } else if (searchImage.type == 1) { + gifChanged = true; + MediaController.SearchImage recentImage = recentImagesGifKeys.get(searchImage.id); + if (recentImage != null) { + recentGifImages.remove(recentImage); + recentGifImages.add(0, recentImage); + } else { + recentGifImages.add(0, searchImage); + } } } } + if (webChange) { MessagesStorage.getInstance().putWebRecent(recentWebImages); } @@ -345,7 +348,7 @@ public class PhotoAlbumPickerActivity extends BaseFragment implements Notificati MessagesStorage.getInstance().putWebRecent(recentGifImages); } - delegate.didSelectPhotos(photos, captions, ttls, videos, masks, webPhotos); + delegate.didSelectPhotos(media); } private void fixLayout() { @@ -387,23 +390,45 @@ public class PhotoAlbumPickerActivity extends BaseFragment implements Notificati recentImages = recentGifImages; } } - PhotoPickerActivity fragment = new PhotoPickerActivity(type, albumEntry, selectedPhotos, selectedWebPhotos, recentImages, singlePhoto, allowCaption, chatActivity); - fragment.setDelegate(new PhotoPickerActivity.PhotoPickerActivityDelegate() { - @Override - public void selectedPhotosChanged() { - if (pickerBottomLayout != null) { - pickerBottomLayout.updateSelectedCount(selectedPhotos.size() + selectedWebPhotos.size(), true); - } - } - @Override - public void actionButtonPressed(boolean canceled) { - removeSelfFromStack(); - if (!canceled) { - sendSelectedPhotos(); + PhotoPickerActivity fragment; + if (albumEntry != null) { + fragment = new PhotoPickerActivity(type, albumEntry, selectedPhotos, selectedPhotosOrder, recentImages, singlePhoto, allowCaption, chatActivity); + fragment.setDelegate(new PhotoPickerActivity.PhotoPickerActivityDelegate() { + @Override + public void selectedPhotosChanged() { + if (pickerBottomLayout != null) { + pickerBottomLayout.updateSelectedCount(selectedPhotos.size(), true); + } } - } - }); + + @Override + public void actionButtonPressed(boolean canceled) { + removeSelfFromStack(); + if (!canceled) { + sendSelectedPhotos(selectedPhotos, selectedPhotosOrder); + } + } + }); + } else { + final HashMap photos = new HashMap<>(); + final ArrayList order = new ArrayList<>(); + fragment = new PhotoPickerActivity(type, albumEntry, photos, order, recentImages, singlePhoto, allowCaption, chatActivity); + fragment.setDelegate(new PhotoPickerActivity.PhotoPickerActivityDelegate() { + @Override + public void selectedPhotosChanged() { + + } + + @Override + public void actionButtonPressed(boolean canceled) { + removeSelfFromStack(); + if (!canceled) { + sendSelectedPhotos(photos, order); + } + } + }); + } presentFragment(fragment); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PhotoPickerActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PhotoPickerActivity.java index d01413bc8..705d33af0 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PhotoPickerActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PhotoPickerActivity.java @@ -8,13 +8,20 @@ package org.telegram.ui; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; import android.app.Activity; import android.content.Context; import android.content.DialogInterface; import android.graphics.Bitmap; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; import android.os.AsyncTask; import android.os.Build; +import android.util.TypedValue; import android.view.Gravity; import android.view.Surface; import android.view.View; @@ -23,6 +30,8 @@ import android.view.ViewTreeObserver; import android.view.WindowManager; import android.widget.EditText; import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; import org.json.JSONArray; import org.json.JSONObject; @@ -73,7 +82,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Locale; -public class PhotoPickerActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, PhotoViewer.PhotoViewerProvider { +public class PhotoPickerActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { public interface PhotoPickerActivityDelegate { void selectedPhotosChanged(); @@ -81,14 +90,19 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen } private int type; - private HashMap selectedWebPhotos; - private HashMap selectedPhotos; - private ArrayList recentImages; + private HashMap selectedPhotos; + private ArrayList selectedPhotosOrder; + private boolean allowIndices; + private ArrayList recentImages; private ArrayList searchResult = new ArrayList<>(); private HashMap searchResultKeys = new HashMap<>(); private HashMap searchResultUrls = new HashMap<>(); + private TextView hintTextView; + private Runnable hintHideRunnable; + private AnimatorSet hintAnimation; + private boolean searching; private boolean bingSearchEndReached = true; private boolean giphySearchEndReached = true; @@ -106,8 +120,10 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen private ListAdapter listAdapter; private GridLayoutManager layoutManager; private PickerBottomLayout pickerBottomLayout; + private ImageView imageOrderToggleButton; private EmptyTextProgressView emptyView; private ActionBarMenuItem searchItem; + private FrameLayout frameLayout; private int itemWidth = 100; private boolean sendPressed; private boolean singlePhoto; @@ -115,11 +131,257 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen private PhotoPickerActivityDelegate delegate; - public PhotoPickerActivity(int type, MediaController.AlbumEntry selectedAlbum, HashMap selectedPhotos, HashMap selectedWebPhotos, ArrayList recentImages, boolean onlyOnePhoto, boolean allowCaption, ChatActivity chatActivity) { + private PhotoViewer.PhotoViewerProvider provider = new PhotoViewer.EmptyPhotoViewerProvider() { + @Override + public boolean scaleToFill() { + return false; + } + + @Override + public PhotoViewer.PlaceProviderObject getPlaceForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { + PhotoPickerPhotoCell cell = getCellForIndex(index); + if (cell != null) { + int coords[] = new int[2]; + cell.photoImage.getLocationInWindow(coords); + PhotoViewer.PlaceProviderObject object = new PhotoViewer.PlaceProviderObject(); + object.viewX = coords[0]; + object.viewY = coords[1] - (Build.VERSION.SDK_INT >= 21 ? 0 : AndroidUtilities.statusBarHeight); + object.parentView = listView; + object.imageReceiver = cell.photoImage.getImageReceiver(); + object.thumb = object.imageReceiver.getBitmap(); + object.scale = cell.photoImage.getScaleX(); + cell.showCheck(false); + return object; + } + return null; + } + + @Override + public void updatePhotoAtIndex(int index) { + PhotoPickerPhotoCell cell = getCellForIndex(index); + if (cell != null) { + if (selectedAlbum != null) { + cell.photoImage.setOrientation(0, true); + MediaController.PhotoEntry photoEntry = selectedAlbum.photos.get(index); + if (photoEntry.thumbPath != null) { + cell.photoImage.setImage(photoEntry.thumbPath, null, cell.getContext().getResources().getDrawable(R.drawable.nophotos)); + } else if (photoEntry.path != null) { + cell.photoImage.setOrientation(photoEntry.orientation, true); + if (photoEntry.isVideo) { + cell.photoImage.setImage("vthumb://" + photoEntry.imageId + ":" + photoEntry.path, null, cell.getContext().getResources().getDrawable(R.drawable.nophotos)); + } else { + cell.photoImage.setImage("thumb://" + photoEntry.imageId + ":" + photoEntry.path, null, cell.getContext().getResources().getDrawable(R.drawable.nophotos)); + } + } else { + cell.photoImage.setImageResource(R.drawable.nophotos); + } + } else { + ArrayList array; + if (searchResult.isEmpty() && lastSearchString == null) { + array = recentImages; + } else { + array = searchResult; + } + MediaController.SearchImage photoEntry = array.get(index); + if (photoEntry.document != null && photoEntry.document.thumb != null) { + cell.photoImage.setImage(photoEntry.document.thumb.location, null, cell.getContext().getResources().getDrawable(R.drawable.nophotos)); + } else if (photoEntry.thumbPath != null) { + cell.photoImage.setImage(photoEntry.thumbPath, null, cell.getContext().getResources().getDrawable(R.drawable.nophotos)); + } else if (photoEntry.thumbUrl != null && photoEntry.thumbUrl.length() > 0) { + cell.photoImage.setImage(photoEntry.thumbUrl, null, cell.getContext().getResources().getDrawable(R.drawable.nophotos)); + } else { + cell.photoImage.setImageResource(R.drawable.nophotos); + } + } + } + } + + @Override + public boolean allowCaption() { + return allowCaption; + } + + @Override + public Bitmap getThumbForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { + PhotoPickerPhotoCell cell = getCellForIndex(index); + if (cell != null) { + return cell.photoImage.getImageReceiver().getBitmap(); + } + return null; + } + + @Override + public void willSwitchFromPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View view = listView.getChildAt(a); + if (view.getTag() == null) { + continue; + } + PhotoPickerPhotoCell cell = (PhotoPickerPhotoCell) view; + int num = (Integer) view.getTag(); + if (selectedAlbum != null) { + if (num < 0 || num >= selectedAlbum.photos.size()) { + continue; + } + } else { + ArrayList array; + if (searchResult.isEmpty() && lastSearchString == null) { + array = recentImages; + } else { + array = searchResult; + } + if (num < 0 || num >= array.size()) { + continue; + } + } + if (num == index) { + cell.showCheck(true); + break; + } + } + } + + @Override + public void willHidePhotoViewer() { + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View view = listView.getChildAt(a); + if (view instanceof PhotoPickerPhotoCell) { + PhotoPickerPhotoCell cell = (PhotoPickerPhotoCell) view; + cell.showCheck(true); + } + } + } + + @Override + public boolean isPhotoChecked(int index) { + if (selectedAlbum != null) { + return !(index < 0 || index >= selectedAlbum.photos.size()) && selectedPhotos.containsKey(selectedAlbum.photos.get(index).imageId); + } else { + ArrayList array; + if (searchResult.isEmpty() && lastSearchString == null) { + array = recentImages; + } else { + array = searchResult; + } + return !(index < 0 || index >= array.size()) && selectedPhotos.containsKey(array.get(index).id); + } + } + + @Override + public int setPhotoChecked(int index, VideoEditedInfo videoEditedInfo) { + boolean add = true; + int num; + if (selectedAlbum != null) { + if (index < 0 || index >= selectedAlbum.photos.size()) { + return -1; + } + MediaController.PhotoEntry photoEntry = selectedAlbum.photos.get(index); + if ((num = addToSelectedPhotos(photoEntry, -1)) == -1) { + photoEntry.editedInfo = videoEditedInfo; + num = selectedPhotosOrder.indexOf(photoEntry.imageId); + } else { + add = false; + photoEntry.editedInfo = null; + } + } else { + ArrayList array; + if (searchResult.isEmpty() && lastSearchString == null) { + array = recentImages; + } else { + array = searchResult; + } + if (index < 0 || index >= array.size()) { + return -1; + } + MediaController.SearchImage photoEntry = array.get(index); + if ((num = addToSelectedPhotos(photoEntry, -1)) == -1) { + num = selectedPhotosOrder.indexOf(photoEntry.id); + } else { + add = false; + } + } + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View view = listView.getChildAt(a); + int tag = (Integer) view.getTag(); + if (tag == index) { + ((PhotoPickerPhotoCell) view).setChecked(allowIndices ? num : -1, add, false); + break; + } + } + pickerBottomLayout.updateSelectedCount(selectedPhotos.size(), true); + delegate.selectedPhotosChanged(); + return num; + } + + @Override + public boolean cancelButtonPressed() { + delegate.actionButtonPressed(true); + finishFragment(); + return true; + } + + @Override + public int getSelectedCount() { + return selectedPhotos.size(); + } + + @Override + public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo) { + if (selectedPhotos.isEmpty()) { + if (selectedAlbum != null) { + if (index < 0 || index >= selectedAlbum.photos.size()) { + return; + } + MediaController.PhotoEntry photoEntry = selectedAlbum.photos.get(index); + photoEntry.editedInfo = videoEditedInfo; + addToSelectedPhotos(photoEntry, -1); + } else { + ArrayList array; + if (searchResult.isEmpty() && lastSearchString == null) { + array = recentImages; + } else { + array = searchResult; + } + if (index < 0 || index >= array.size()) { + return; + } + addToSelectedPhotos(array.get(index), -1); + } + } + sendSelectedPhotos(); + } + + @Override + public void toggleGroupPhotosEnabled() { + if (imageOrderToggleButton != null) { + imageOrderToggleButton.setColorFilter(MediaController.getInstance().isGroupPhotosEnabled() ? new PorterDuffColorFilter(0xff66bffa, PorterDuff.Mode.MULTIPLY) : null); + } + } + + @Override + public ArrayList getSelectedPhotosOrder() { + return selectedPhotosOrder; + } + + @Override + public HashMap getSelectedPhotos() { + return selectedPhotos; + } + + @Override + public boolean allowGroupPhotos() { + return imageOrderToggleButton != null; + } + }; + + public PhotoPickerActivity(int type, MediaController.AlbumEntry selectedAlbum, HashMap selectedPhotos, ArrayList selectedPhotosOrder, ArrayList recentImages, boolean onlyOnePhoto, boolean allowCaption, ChatActivity chatActivity) { super(); this.selectedAlbum = selectedAlbum; this.selectedPhotos = selectedPhotos; - this.selectedWebPhotos = selectedWebPhotos; + this.selectedPhotosOrder = selectedPhotosOrder; this.type = type; this.recentImages = recentImages; this.singlePhoto = onlyOnePhoto; @@ -259,7 +521,7 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen fragmentView = new FrameLayout(context); - FrameLayout frameLayout = (FrameLayout) fragmentView; + frameLayout = (FrameLayout) fragmentView; frameLayout.setBackgroundColor(0xff000000); listView = new RecyclerListView(context); @@ -312,7 +574,7 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen AndroidUtilities.hideKeyboard(searchItem.getSearchField()); } PhotoViewer.getInstance().setParentActivity(getParentActivity()); - PhotoViewer.getInstance().openPhotoForSelect(arrayList, position, singlePhoto ? 1 : 0, PhotoPickerActivity.this, chatActivity); + PhotoViewer.getInstance().openPhotoForSelect(arrayList, position, singlePhoto ? 1 : 0, provider, chatActivity); } }); @@ -404,10 +666,26 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen }); if (singlePhoto) { pickerBottomLayout.setVisibility(View.GONE); + } else if ((selectedAlbum != null || type == 0) && chatActivity != null && chatActivity.allowGroupPhotos()) { + imageOrderToggleButton = new ImageView(context); + imageOrderToggleButton.setScaleType(ImageView.ScaleType.CENTER); + imageOrderToggleButton.setImageResource(R.drawable.photos_group); + pickerBottomLayout.addView(imageOrderToggleButton, LayoutHelper.createFrame(48, LayoutHelper.MATCH_PARENT, Gravity.CENTER)); + imageOrderToggleButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + MediaController.getInstance().toggleGroupPhotosEnabled(); + imageOrderToggleButton.setColorFilter(MediaController.getInstance().isGroupPhotosEnabled() ? new PorterDuffColorFilter(0xff66bffa, PorterDuff.Mode.MULTIPLY) : null); + showHint(false, MediaController.getInstance().isGroupPhotosEnabled()); + updateCheckedPhotoIndices(); + } + }); + imageOrderToggleButton.setColorFilter(MediaController.getInstance().isGroupPhotosEnabled() ? new PorterDuffColorFilter(0xff66bffa, PorterDuff.Mode.MULTIPLY) : null); } + allowIndices = (selectedAlbum != null || type == 0); listView.setEmptyView(emptyView); - pickerBottomLayout.updateSelectedCount(selectedPhotos.size() + selectedWebPhotos.size(), true); + pickerBottomLayout.updateSelectedCount(selectedPhotos.size(), true); return fragmentView; } @@ -445,6 +723,126 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen } } + private void hideHint() { + hintAnimation = new AnimatorSet(); + hintAnimation.playTogether( + ObjectAnimator.ofFloat(hintTextView, "alpha", 0.0f) + ); + hintAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animation.equals(hintAnimation)) { + hintAnimation = null; + hintHideRunnable = null; + if (hintTextView != null) { + hintTextView.setVisibility(View.GONE); + } + } + } + + @Override + public void onAnimationCancel(Animator animation) { + if (animation.equals(hintAnimation)) { + hintHideRunnable = null; + hintHideRunnable = null; + } + } + }); + hintAnimation.setDuration(300); + hintAnimation.start(); + } + + private void showHint(boolean hide, boolean enabled) { + if (getParentActivity() == null || fragmentView == null || hide && hintTextView == null) { + return; + } + if (hintTextView == null) { + hintTextView = new TextView(getParentActivity()); + hintTextView.setBackgroundDrawable(Theme.createRoundRectDrawable(AndroidUtilities.dp(3), Theme.getColor(Theme.key_chat_gifSaveHintBackground))); + hintTextView.setTextColor(Theme.getColor(Theme.key_chat_gifSaveHintText)); + hintTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + hintTextView.setPadding(AndroidUtilities.dp(8), AndroidUtilities.dp(7), AndroidUtilities.dp(8), AndroidUtilities.dp(7)); + hintTextView.setGravity(Gravity.CENTER_VERTICAL); + hintTextView.setAlpha(0.0f); + frameLayout.addView(hintTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, 5, 0, 5, 48 + 3)); + } + if (hide) { + if (hintAnimation != null) { + hintAnimation.cancel(); + hintAnimation = null; + } + AndroidUtilities.cancelRunOnUIThread(hintHideRunnable); + hintHideRunnable = null; + hideHint(); + return; + } + + hintTextView.setText(enabled ? LocaleController.getString("GroupPhotosHelp", R.string.GroupPhotosHelp) : LocaleController.getString("SinglePhotosHelp", R.string.SinglePhotosHelp)); + + if (hintHideRunnable != null) { + if (hintAnimation != null) { + hintAnimation.cancel(); + hintAnimation = null; + } else { + AndroidUtilities.cancelRunOnUIThread(hintHideRunnable); + AndroidUtilities.runOnUIThread(hintHideRunnable = new Runnable() { + @Override + public void run() { + hideHint(); + } + }, 2000); + return; + } + } else if (hintAnimation != null) { + return; + } + + hintTextView.setVisibility(View.VISIBLE); + hintAnimation = new AnimatorSet(); + hintAnimation.playTogether( + ObjectAnimator.ofFloat(hintTextView, "alpha", 1.0f) + ); + hintAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animation.equals(hintAnimation)) { + hintAnimation = null; + AndroidUtilities.runOnUIThread(hintHideRunnable = new Runnable() { + @Override + public void run() { + hideHint(); + } + }, 2000); + } + } + + @Override + public void onAnimationCancel(Animator animation) { + if (animation.equals(hintAnimation)) { + hintAnimation = null; + } + } + }); + hintAnimation.setDuration(300); + hintAnimation.start(); + } + + private void updateCheckedPhotoIndices() { + if (selectedAlbum == null) { + return; + } + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View view = listView.getChildAt(a); + if (view instanceof PhotoPickerPhotoCell) { + PhotoPickerPhotoCell cell = (PhotoPickerPhotoCell) view; + Integer index = (Integer) cell.getTag(); + MediaController.PhotoEntry photoEntry = selectedAlbum.photos.get(index); + cell.setNum(allowIndices ? selectedPhotosOrder.indexOf(photoEntry.imageId) : -1); + } + } + } + private PhotoPickerPhotoCell getCellForIndex(int index) { int count = listView.getChildCount(); @@ -476,230 +874,41 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen return null; } - @Override - public boolean scaleToFill() { - return false; - } - - @Override - public PhotoViewer.PlaceProviderObject getPlaceForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { - PhotoPickerPhotoCell cell = getCellForIndex(index); - if (cell != null) { - int coords[] = new int[2]; - cell.photoImage.getLocationInWindow(coords); - PhotoViewer.PlaceProviderObject object = new PhotoViewer.PlaceProviderObject(); - object.viewX = coords[0]; - object.viewY = coords[1] - (Build.VERSION.SDK_INT >= 21 ? 0 : AndroidUtilities.statusBarHeight); - object.parentView = listView; - object.imageReceiver = cell.photoImage.getImageReceiver(); - object.thumb = object.imageReceiver.getBitmap(); - object.scale = cell.photoImage.getScaleX(); - cell.showCheck(false); - return object; + private int addToSelectedPhotos(Object object, int index) { + Object key = null; + if (object instanceof MediaController.PhotoEntry) { + key = ((MediaController.PhotoEntry) object).imageId; + } else if (object instanceof MediaController.SearchImage) { + key = ((MediaController.SearchImage) object).id; } - return null; - } - - @Override - public void updatePhotoAtIndex(int index) { - PhotoPickerPhotoCell cell = getCellForIndex(index); - if (cell != null) { - if (selectedAlbum != null) { - cell.photoImage.setOrientation(0, true); - MediaController.PhotoEntry photoEntry = selectedAlbum.photos.get(index); - if (photoEntry.thumbPath != null) { - cell.photoImage.setImage(photoEntry.thumbPath, null, cell.getContext().getResources().getDrawable(R.drawable.nophotos)); - } else if (photoEntry.path != null) { - cell.photoImage.setOrientation(photoEntry.orientation, true); - if (photoEntry.isVideo) { - cell.photoImage.setImage("vthumb://" + photoEntry.imageId + ":" + photoEntry.path, null, cell.getContext().getResources().getDrawable(R.drawable.nophotos)); - } else { - cell.photoImage.setImage("thumb://" + photoEntry.imageId + ":" + photoEntry.path, null, cell.getContext().getResources().getDrawable(R.drawable.nophotos)); - } - } else { - cell.photoImage.setImageResource(R.drawable.nophotos); - } - } else { - ArrayList array; - if (searchResult.isEmpty() && lastSearchString == null) { - array = recentImages; - } else { - array = searchResult; - } - MediaController.SearchImage photoEntry = array.get(index); - if (photoEntry.document != null && photoEntry.document.thumb != null) { - cell.photoImage.setImage(photoEntry.document.thumb.location, null, cell.getContext().getResources().getDrawable(R.drawable.nophotos)); - } else if (photoEntry.thumbPath != null) { - cell.photoImage.setImage(photoEntry.thumbPath, null, cell.getContext().getResources().getDrawable(R.drawable.nophotos)); - } else if (photoEntry.thumbUrl != null && photoEntry.thumbUrl.length() > 0) { - cell.photoImage.setImage(photoEntry.thumbUrl, null, cell.getContext().getResources().getDrawable(R.drawable.nophotos)); - } else { - cell.photoImage.setImageResource(R.drawable.nophotos); - } - } + if (key == null) { + return -1; } - } - - @Override - public boolean allowCaption() { - return allowCaption; - } - - @Override - public Bitmap getThumbForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { - PhotoPickerPhotoCell cell = getCellForIndex(index); - if (cell != null) { - return cell.photoImage.getImageReceiver().getBitmap(); - } - return null; - } - - @Override - public void willSwitchFromPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { - int count = listView.getChildCount(); - for (int a = 0; a < count; a++) { - View view = listView.getChildAt(a); - if (view.getTag() == null) { - continue; + if (selectedPhotos.containsKey(key)) { + selectedPhotos.remove(key); + int position = selectedPhotosOrder.indexOf(key); + if (position >= 0) { + selectedPhotosOrder.remove(position); } - PhotoPickerPhotoCell cell = (PhotoPickerPhotoCell) view; - int num = (Integer) view.getTag(); - if (selectedAlbum != null) { - if (num < 0 || num >= selectedAlbum.photos.size()) { - continue; - } - } else { - ArrayList array; - if (searchResult.isEmpty() && lastSearchString == null) { - array = recentImages; - } else { - array = searchResult; - } - if (num < 0 || num >= array.size()) { - continue; + if (allowIndices) { + updateCheckedPhotoIndices(); + } + if (index >= 0) { + if (object instanceof MediaController.PhotoEntry) { + ((MediaController.PhotoEntry) object).reset(); + } else if (object instanceof MediaController.SearchImage) { + ((MediaController.SearchImage) object).reset(); } + provider.updatePhotoAtIndex(index); } - if (num == index) { - cell.showCheck(true); - break; - } - } - } - - @Override - public void willHidePhotoViewer() { - int count = listView.getChildCount(); - for (int a = 0; a < count; a++) { - View view = listView.getChildAt(a); - if (view instanceof PhotoPickerPhotoCell) { - PhotoPickerPhotoCell cell = (PhotoPickerPhotoCell) view; - cell.showCheck(true); - } - } - } - - @Override - public boolean isPhotoChecked(int index) { - if (selectedAlbum != null) { - return !(index < 0 || index >= selectedAlbum.photos.size()) && selectedPhotos.containsKey(selectedAlbum.photos.get(index).imageId); + return position; } else { - ArrayList array; - if (searchResult.isEmpty() && lastSearchString == null) { - array = recentImages; - } else { - array = searchResult; - } - return !(index < 0 || index >= array.size()) && selectedWebPhotos.containsKey(array.get(index).id); + selectedPhotos.put(key, object); + selectedPhotosOrder.add(key); + return -1; } } - @Override - public void setPhotoChecked(int index, VideoEditedInfo videoEditedInfo) { - boolean add = true; - if (selectedAlbum != null) { - if (index < 0 || index >= selectedAlbum.photos.size()) { - return; - } - MediaController.PhotoEntry photoEntry = selectedAlbum.photos.get(index); - if (selectedPhotos.containsKey(photoEntry.imageId)) { - selectedPhotos.remove(photoEntry.imageId); - photoEntry.editedInfo = null; - add = false; - } else { - selectedPhotos.put(photoEntry.imageId, photoEntry); - photoEntry.editedInfo = videoEditedInfo; - } - } else { - MediaController.SearchImage photoEntry; - ArrayList array; - if (searchResult.isEmpty() && lastSearchString == null) { - array = recentImages; - } else { - array = searchResult; - } - if (index < 0 || index >= array.size()) { - return; - } - photoEntry = array.get(index); - if (selectedWebPhotos.containsKey(photoEntry.id)) { - selectedWebPhotos.remove(photoEntry.id); - add = false; - } else { - selectedWebPhotos.put(photoEntry.id, photoEntry); - } - } - int count = listView.getChildCount(); - for (int a = 0; a < count; a++) { - View view = listView.getChildAt(a); - int num = (Integer) view.getTag(); - if (num == index) { - ((PhotoPickerPhotoCell) view).setChecked(add, false); - break; - } - } - pickerBottomLayout.updateSelectedCount(selectedPhotos.size() + selectedWebPhotos.size(), true); - delegate.selectedPhotosChanged(); - } - - @Override - public boolean cancelButtonPressed() { - delegate.actionButtonPressed(true); - finishFragment(); - return true; - } - - @Override - public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo) { - if (selectedAlbum != null) { - if (selectedPhotos.isEmpty()) { - if (index < 0 || index >= selectedAlbum.photos.size()) { - return; - } - MediaController.PhotoEntry photoEntry = selectedAlbum.photos.get(index); - photoEntry.editedInfo = videoEditedInfo; - selectedPhotos.put(photoEntry.imageId, photoEntry); - } - } else if (selectedPhotos.isEmpty()) { - ArrayList array; - if (searchResult.isEmpty() && lastSearchString == null) { - array = recentImages; - } else { - array = searchResult; - } - if (index < 0 || index >= array.size()) { - return; - } - MediaController.SearchImage photoEntry = array.get(index); - selectedWebPhotos.put(photoEntry.id, photoEntry); - } - sendSelectedPhotos(); - } - - @Override - public int getSelectedCount() { - return selectedPhotos.size() + selectedWebPhotos.size(); - } - @Override public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { if (isOpen && searchItem != null) { @@ -1017,7 +1226,7 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen } private void sendSelectedPhotos() { - if (selectedPhotos.isEmpty() && selectedWebPhotos.isEmpty() || delegate == null || sendPressed) { + if (selectedPhotos.isEmpty() || delegate == null || sendPressed) { return; } sendPressed = true; @@ -1119,21 +1328,17 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen View view; switch (viewType) { case 0: - PhotoPickerPhotoCell cell = new PhotoPickerPhotoCell(mContext); + PhotoPickerPhotoCell cell = new PhotoPickerPhotoCell(mContext, true); cell.checkFrame.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int index = (Integer) ((View) v.getParent()).getTag(); if (selectedAlbum != null) { MediaController.PhotoEntry photoEntry = selectedAlbum.photos.get(index); - if (selectedPhotos.containsKey(photoEntry.imageId)) { - selectedPhotos.remove(photoEntry.imageId); - photoEntry.reset(); - updatePhotoAtIndex(index); - } else { - selectedPhotos.put(photoEntry.imageId, photoEntry); - } - ((PhotoPickerPhotoCell) v.getParent()).setChecked(selectedPhotos.containsKey(photoEntry.imageId), true); + boolean added = !selectedPhotos.containsKey(photoEntry.imageId); + int num = allowIndices && added ? selectedPhotosOrder.size() : -1; + ((PhotoPickerPhotoCell) v.getParent()).setChecked(num, added, true); + addToSelectedPhotos(photoEntry, index); } else { AndroidUtilities.hideKeyboard(getParentActivity().getCurrentFocus()); MediaController.SearchImage photoEntry; @@ -1142,17 +1347,12 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen } else { photoEntry = searchResult.get((Integer) ((View) v.getParent()).getTag()); } - if (selectedWebPhotos.containsKey(photoEntry.id)) { - selectedWebPhotos.remove(photoEntry.id); - photoEntry.imagePath = null; - photoEntry.thumbPath = null; - updatePhotoAtIndex(index); - } else { - selectedWebPhotos.put(photoEntry.id, photoEntry); - } - ((PhotoPickerPhotoCell) v.getParent()).setChecked(selectedWebPhotos.containsKey(photoEntry.id), true); + boolean added = !selectedPhotos.containsKey(photoEntry.id); + int num = allowIndices && added ? selectedPhotosOrder.size() : -1; + ((PhotoPickerPhotoCell) v.getParent()).setChecked(num, added, true); + addToSelectedPhotos(photoEntry, index); } - pickerBottomLayout.updateSelectedCount(selectedPhotos.size() + selectedWebPhotos.size(), true); + pickerBottomLayout.updateSelectedCount(selectedPhotos.size(), true); delegate.selectedPhotosChanged(); } }); @@ -1202,7 +1402,7 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen } else { imageView.setImageResource(R.drawable.nophotos); } - cell.setChecked(selectedPhotos.containsKey(photoEntry.imageId), false); + cell.setChecked(allowIndices ? selectedPhotosOrder.indexOf(photoEntry.imageId) : -1, selectedPhotos.containsKey(photoEntry.imageId), false); showing = PhotoViewer.getInstance().isShowingImage(photoEntry.path); } else { MediaController.SearchImage photoEntry; @@ -1221,7 +1421,7 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen imageView.setImageResource(R.drawable.nophotos); } cell.videoInfoContainer.setVisibility(View.INVISIBLE); - cell.setChecked(selectedWebPhotos.containsKey(photoEntry.id), false); + cell.setChecked(allowIndices ? selectedPhotosOrder.indexOf(photoEntry.id) : -1, selectedPhotos.containsKey(photoEntry.id), false); if (photoEntry.document != null) { showing = PhotoViewer.getInstance().isShowingImage(FileLoader.getPathToAttach(photoEntry.document, true).getAbsolutePath()); } else { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java b/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java index 342432ccc..c4fa66e01 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java @@ -22,6 +22,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PixelFormat; @@ -37,6 +38,7 @@ import android.media.MediaCodecInfo; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.support.v4.content.FileProvider; import android.text.Layout; import android.text.SpannableStringBuilder; import android.text.StaticLayout; @@ -76,11 +78,14 @@ import com.googlecode.mp4parser.util.Matrix; import com.googlecode.mp4parser.util.Path; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.BuildConfig; import org.telegram.messenger.ChatObject; import org.telegram.messenger.ContactsController; import org.telegram.messenger.Emoji; +import org.telegram.messenger.EmojiSuggestion; import org.telegram.messenger.ImageLoader; import org.telegram.messenger.MessagesStorage; +import org.telegram.messenger.SendMessagesHelper; import org.telegram.messenger.UserObject; import org.telegram.messenger.VideoEditedInfo; import org.telegram.messenger.exoplayer2.C; @@ -96,6 +101,7 @@ import org.telegram.messenger.MessagesController; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; @@ -110,11 +116,14 @@ import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.ActionBarMenuItem; import org.telegram.ui.Cells.CheckBoxCell; +import org.telegram.ui.Cells.PhotoPickerPhotoCell; import org.telegram.ui.Components.AnimatedFileDrawable; +import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.Components.ChatAttachAlert; import org.telegram.ui.Components.CheckBox; import org.telegram.ui.Components.ClippingImageView; import org.telegram.messenger.ImageReceiver; +import org.telegram.ui.Components.CubicBezierInterpolator; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.NumberPicker; import org.telegram.ui.Components.PhotoCropView; @@ -132,7 +141,6 @@ import org.telegram.ui.Components.VideoTimelinePlayView; import java.io.File; import java.util.ArrayList; -import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -147,11 +155,16 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private boolean muteVideo; + private int slideshowMessageId; + private String nameOverride; + private int dateOverride; + private Activity parentActivity; private Context actvityContext; private ActionBar actionBar; private boolean isActionBarVisible = true; + private boolean isPhotosListViewVisible; private static Drawable[] progressDrawables; @@ -163,6 +176,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private TextView nameTextView; private TextView dateTextView; private ActionBarMenuItem menuItem; + private ActionBarMenuItem sendItem; private ActionBarMenuItem masksItem; private ImageView shareButton; private BackgroundDrawable backgroundDrawable = new BackgroundDrawable(0xff000000); @@ -181,10 +195,14 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private ImageView timeItem; private ImageView muteItem; private ImageView compressItem; + private GroupedPhotosListView groupedPhotosListView; + private RecyclerListView selectedPhotosListView; + private ListAdapter selectedPhotosAdapter; private AnimatorSet compressItemAnimation; private boolean isCurrentVideo; private AnimatorSet currentActionBarAnimation; + private AnimatorSet currentListViewAnimation; private PhotoCropView photoCropView; private PhotoFilterView photoFilterView; private PhotoPaintView photoPaintView; @@ -198,6 +216,10 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private AnimatedFileDrawable currentAnimation; private boolean allowShare; + private TextView hintTextView; + private Runnable hintHideRunnable; + private AnimatorSet hintAnimation; + private Object lastInsets; private boolean doneButtonPressed; @@ -384,6 +406,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private final static int gallery_menu_save = 1; private final static int gallery_menu_showall = 2; private final static int gallery_menu_send = 3; + private final static int gallery_menu_showinchat = 4; private final static int gallery_menu_delete = 6; private final static int gallery_menu_share = 10; private final static int gallery_menu_openin = 11; @@ -392,6 +415,581 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private static DecelerateInterpolator decelerateInterpolator; private static Paint progressPaint; + private class GroupedPhotosListView extends View implements GestureDetector.OnGestureListener { + + private Paint backgroundPaint = new Paint(); + private ArrayList unusedReceivers = new ArrayList<>(); + private ArrayList imagesToDraw = new ArrayList<>(); + private ArrayList currentPhotos = new ArrayList<>(); + private ArrayList currentObjects = new ArrayList<>(); + private int currentImage; + private long currentGroupId; + private int itemWidth; + private int itemHeight; + private int itemY; + private int itemSpacing; + private int drawDx; + private float moveLineProgress; + private float currentItemProgress = 1.0f; + private float nextItemProgress = 0.0f; + private int nextImage; + private long lastUpdateTime; + private boolean moving; + private boolean animateAllLine; + private int animateToDX; + private int animateToDXStart; + private int animateToItem = -1; + private Scroller scroll; + private GestureDetector gestureDetector; + private boolean scrolling; + private boolean stopedScrolling; + private boolean ignoreChanges; + private int nextPhotoScrolling = -1; + + public GroupedPhotosListView(Context context) { + super(context); + gestureDetector = new GestureDetector(context, this); + scroll = new Scroller(context); + itemWidth = AndroidUtilities.dp(42); + itemHeight = AndroidUtilities.dp(56); + itemSpacing = AndroidUtilities.dp(1); + itemY = AndroidUtilities.dp(3); + backgroundPaint.setColor(0x7f000000); + } + + public void clear() { + currentPhotos.clear(); + currentObjects.clear(); + imagesToDraw.clear(); + } + + public void fillList() { + if (ignoreChanges) { + ignoreChanges = false; + return; + } + boolean changed = false; + int newCount = 0; + Object currentObject = null; + if (!imagesArrLocations.isEmpty()) { + TLRPC.FileLocation location = imagesArrLocations.get(currentIndex); + newCount = imagesArrLocations.size(); + currentObject = location; + } else if (!imagesArr.isEmpty()) { + MessageObject messageObject = imagesArr.get(currentIndex); + currentObject = messageObject; + if (messageObject.messageOwner.grouped_id != currentGroupId) { + changed = true; + currentGroupId = messageObject.messageOwner.grouped_id; + } else { + int max = Math.min(currentIndex + 10, imagesArr.size()); + for (int a = currentIndex; a < max; a++) { + MessageObject object = imagesArr.get(a); + if (slideshowMessageId != 0 || object.messageOwner.grouped_id == currentGroupId) { + newCount++; + } else { + break; + } + } + int min = Math.max(currentIndex - 10, 0); + for (int a = currentIndex - 1; a >= min; a--) { + MessageObject object = imagesArr.get(a); + if (slideshowMessageId != 0 || object.messageOwner.grouped_id == currentGroupId) { + newCount++; + } else { + break; + } + } + } + } + if (currentObject == null) { + return; + } + if (!changed) { + if (newCount != currentPhotos.size() || currentObjects.indexOf(currentObject) == -1) { + changed = true; + } else { + int newImageIndex = currentObjects.indexOf(currentObject); + if (currentImage != newImageIndex && newImageIndex != -1) { + if (animateAllLine) { + nextImage = animateToItem = newImageIndex; + animateToDX = (currentImage - newImageIndex) * (itemWidth + itemSpacing); + moving = true; + animateAllLine = false; + lastUpdateTime = System.currentTimeMillis(); + invalidate(); + } else { + fillImages(true, (currentImage - newImageIndex) * (itemWidth + itemSpacing)); + currentImage = newImageIndex; + moving = false; + } + drawDx = 0; + } + } + } + if (changed) { + animateAllLine = false; + currentPhotos.clear(); + currentObjects.clear(); + if (!imagesArrLocations.isEmpty()) { + currentObjects.addAll(imagesArrLocations); + currentPhotos.addAll(imagesArrLocations); + currentImage = currentIndex; + animateToItem = -1; + } else if (!imagesArr.isEmpty()) { + if (currentGroupId != 0 || slideshowMessageId != 0) { + int max = Math.min(currentIndex + 10, imagesArr.size()); + for (int a = currentIndex; a < max; a++) { + MessageObject object = imagesArr.get(a); + if (slideshowMessageId != 0 || object.messageOwner.grouped_id == currentGroupId) { + currentObjects.add(object); + currentPhotos.add(FileLoader.getClosestPhotoSizeWithSize(object.photoThumbs, 56, true)); + } else { + break; + } + } + currentImage = 0; + animateToItem = -1; + int min = Math.max(currentIndex - 10, 0); + for (int a = currentIndex - 1; a >= min; a--) { + MessageObject object = imagesArr.get(a); + if (slideshowMessageId != 0 || object.messageOwner.grouped_id == currentGroupId) { + currentObjects.add(0, object); + currentPhotos.add(0, FileLoader.getClosestPhotoSizeWithSize(object.photoThumbs, 56, true)); + currentImage++; + } else { + break; + } + } + } + } + if (currentPhotos.size() == 1) { + currentPhotos.clear(); + currentObjects.clear(); + } + fillImages(false, 0); + } + } + + public void setMoveProgress(float progress) { + if (scrolling || animateToItem >= 0) { + return; + } + if (progress > 0) { + nextImage = currentImage - 1; + } else { + nextImage = currentImage + 1; + } + if (nextImage >= 0 && nextImage < currentPhotos.size()) { + currentItemProgress = 1.0f - Math.abs(progress); + } else { + currentItemProgress = 1.0f; + } + nextItemProgress = 1.0f - currentItemProgress; + moving = progress != 0; + invalidate(); + if (currentPhotos.isEmpty() || progress < 0 && currentImage == currentPhotos.size() - 1 || progress > 0 && currentImage == 0) { + return; + } + drawDx = (int) (progress * (itemWidth + itemSpacing)); + fillImages(true, drawDx); + } + + private ImageReceiver getFreeReceiver() { + ImageReceiver receiver; + if (unusedReceivers.isEmpty()) { + receiver = new ImageReceiver(this); + } else { + receiver = unusedReceivers.get(0); + unusedReceivers.remove(0); + } + imagesToDraw.add(receiver); + return receiver; + } + + private void fillImages(boolean move, int dx) { + if (!move && !imagesToDraw.isEmpty()) { + unusedReceivers.addAll(imagesToDraw); + imagesToDraw.clear(); + moving = false; + moveLineProgress = 1.0f; + currentItemProgress = 1.0f; + nextItemProgress = 0.0f; + } + invalidate(); + if (getMeasuredWidth() == 0 || currentPhotos.isEmpty()) { + return; + } + int width = getMeasuredWidth(); + int startX = getMeasuredWidth() / 2 - itemWidth / 2; + + int addRightIndex; + int addLeftIndex; + if (move) { + addRightIndex = Integer.MIN_VALUE; + addLeftIndex = Integer.MAX_VALUE; + int count = imagesToDraw.size(); + for (int a = 0; a < count; a++) { + ImageReceiver receiver = imagesToDraw.get(a); + int num = receiver.getParam(); + int x = startX + (num - currentImage) * (itemWidth + itemSpacing) + dx; + if (x > width || x + itemWidth < 0) { + unusedReceivers.add(receiver); + imagesToDraw.remove(a); + count--; + a--; + } + addLeftIndex = Math.min(addLeftIndex, num - 1); + addRightIndex = Math.max(addRightIndex, num + 1); + } + } else { + addRightIndex = currentImage; + addLeftIndex = currentImage - 1; + } + + if (addRightIndex != Integer.MIN_VALUE) { + int count = currentPhotos.size(); + for (int a = addRightIndex; a < count; a++) { + int x = startX + (a - currentImage) * (itemWidth + itemSpacing) + dx; + if (x < width) { + TLObject location = currentPhotos.get(a); + if (location instanceof TLRPC.PhotoSize) { + location = ((TLRPC.PhotoSize) location).location; + } + ImageReceiver receiver = getFreeReceiver(); + receiver.setImageCoords(x, itemY, itemWidth, itemHeight); + receiver.setImage(null, null, null, null, (TLRPC.FileLocation) location, "80_80", 0, null, 1); + receiver.setParam(a); + } else { + break; + } + } + } + if (addLeftIndex != Integer.MAX_VALUE) { + for (int a = addLeftIndex; a >= 0; a--) { + int x = startX + (a - currentImage) * (itemWidth + itemSpacing) + dx + itemWidth; + if (x > 0) { + TLObject location = currentPhotos.get(a); + if (location instanceof TLRPC.PhotoSize) { + location = ((TLRPC.PhotoSize) location).location; + } + ImageReceiver receiver = getFreeReceiver(); + receiver.setImageCoords(x, itemY, itemWidth, itemHeight); + receiver.setImage(null, null, null, null, (TLRPC.FileLocation) location, "80_80", 0, null, 1); + receiver.setParam(a); + } else { + break; + } + } + } + } + + @Override + public boolean onDown(MotionEvent e) { + if (!scroll.isFinished()) { + scroll.abortAnimation(); + } + animateToItem = -1; + return true; + } + + @Override + public void onShowPress(MotionEvent e) { + + } + + @Override + public boolean onSingleTapUp(MotionEvent e) { + stopScrolling(); + int count = imagesToDraw.size(); + for (int a = 0; a < count; a++) { + ImageReceiver receiver = imagesToDraw.get(a); + if (receiver.isInsideImage(e.getX(), e.getY())) { + int num = receiver.getParam(); + if (num < 0 || num >= currentObjects.size()) { + return true; + } + if (!imagesArr.isEmpty()) { + MessageObject messageObject = (MessageObject) currentObjects.get(num); + int idx = imagesArr.indexOf(messageObject); + if (currentIndex == idx) { + return true; + } + moveLineProgress = 1.0f; + animateAllLine = true; + currentIndex = -1; + setImageIndex(idx, false); + } else if (!imagesArrLocations.isEmpty()) { + TLRPC.FileLocation location = (TLRPC.FileLocation) currentObjects.get(num); + int idx = imagesArrLocations.indexOf(location); + if (currentIndex == idx) { + return true; + } + moveLineProgress = 1.0f; + animateAllLine = true; + currentIndex = -1; + setImageIndex(idx, false); + } + break; + } + } + return false; + } + + private void updateAfterScroll() { + int indexChange = 0; + int dx = drawDx; + if (Math.abs(dx) > itemWidth / 2 + itemSpacing) { + if (dx > 0) { + dx -= itemWidth / 2 + itemSpacing; + indexChange++; + } else { + dx += itemWidth / 2 + itemSpacing; + indexChange--; + } + indexChange += dx / (itemWidth + itemSpacing * 2); + } + nextPhotoScrolling = currentImage - indexChange; + if (currentIndex != nextPhotoScrolling && nextPhotoScrolling >= 0 && nextPhotoScrolling < currentPhotos.size()) { + Object photo = currentObjects.get(nextPhotoScrolling); + int nextPhoto = -1; + if (!imagesArr.isEmpty()) { + MessageObject messageObject = (MessageObject) photo; + nextPhoto = imagesArr.indexOf(messageObject); + } else if (!imagesArrLocations.isEmpty()) { + TLRPC.FileLocation location = (TLRPC.FileLocation) photo; + nextPhoto = imagesArrLocations.indexOf(location); + } + if (nextPhoto >= 0) { + ignoreChanges = true; + currentIndex = -1; + setImageIndex(nextPhoto, false); + } + } + if (!scrolling) { + scrolling = true; + stopedScrolling = false; + } + fillImages(true, drawDx); + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + drawDx -= distanceX; + int min = getMinScrollX(); + int max = getMaxScrollX(); + if (drawDx < min) { + drawDx = min; + } else if (drawDx > max) { + drawDx = max; + } + updateAfterScroll(); + return false; + } + + @Override + public void onLongPress(MotionEvent e) { + + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + scroll.abortAnimation(); + if (currentPhotos.size() >= 10) { + scroll.fling(drawDx, 0, Math.round(velocityX), 0, getMinScrollX(), getMaxScrollX(), 0, 0); + } + return false; + } + + private void stopScrolling() { + scrolling = false; + if (!scroll.isFinished()) { + scroll.abortAnimation(); + } + if (nextPhotoScrolling >= 0 && nextPhotoScrolling < currentObjects.size()) { + stopedScrolling = true; + + nextImage = animateToItem = nextPhotoScrolling; + animateToDX = (currentImage - nextPhotoScrolling) * (itemWidth + itemSpacing); + animateToDXStart = drawDx; + moveLineProgress = 1.0f; + nextPhotoScrolling = -1; + } + invalidate(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + boolean result = gestureDetector.onTouchEvent(event) || super.onTouchEvent(event); + if (scrolling && event.getAction() == MotionEvent.ACTION_UP && scroll.isFinished()) { + stopScrolling(); + } + return result; + } + + private int getMinScrollX() { + return -(currentPhotos.size() - currentImage - 1) * (itemWidth + itemSpacing * 2); + } + + private int getMaxScrollX() { + return currentImage * (itemWidth + itemSpacing * 2); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + fillImages(false, 0); + } + + @Override + protected void onDraw(Canvas canvas) { + if (imagesToDraw.isEmpty()) { + return; + } + canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), backgroundPaint); + int count = imagesToDraw.size(); + + int moveX = drawDx; + + int maxItemWidth = (int) (itemWidth * 2.0f); + int padding = AndroidUtilities.dp(8); + + TLObject object = currentPhotos.get(currentImage); + int trueWidth; + int currentPaddings; + if (object instanceof TLRPC.PhotoSize) { + TLRPC.PhotoSize photoSize = (TLRPC.PhotoSize) object; + trueWidth = Math.max(itemWidth, (int) (photoSize.w * (itemHeight / (float) photoSize.h))); + } else { + trueWidth = itemHeight; + } + trueWidth = Math.min(maxItemWidth, trueWidth); + currentPaddings = (int) (padding * 2 * currentItemProgress); + trueWidth = itemWidth + (int) ((trueWidth - itemWidth) * currentItemProgress) + currentPaddings; + + int nextTrueWidth; + int nextPaddings; + if (nextImage >= 0 && nextImage < currentPhotos.size()) { + object = currentPhotos.get(nextImage); + if (object instanceof TLRPC.PhotoSize) { + TLRPC.PhotoSize photoSize = (TLRPC.PhotoSize) object; + nextTrueWidth = Math.max(itemWidth, (int) (photoSize.w * (itemHeight / (float) photoSize.h))); + } else { + nextTrueWidth = itemHeight; + } + } else { + nextTrueWidth = itemWidth; + } + nextTrueWidth = Math.min(maxItemWidth, nextTrueWidth); + nextPaddings = (int) (padding * 2 * nextItemProgress); + moveX += (nextTrueWidth + nextPaddings - itemWidth) / 2 * nextItemProgress * (nextImage > currentImage ? -1 : 1); + nextTrueWidth = itemWidth + (int) ((nextTrueWidth - itemWidth) * nextItemProgress) + nextPaddings; + + int startX = (getMeasuredWidth() - trueWidth) / 2; + for (int a = 0; a < count; a++) { + ImageReceiver receiver = imagesToDraw.get(a); + int num = receiver.getParam(); + if (num == currentImage) { + receiver.setImageX(startX + moveX + currentPaddings / 2); + receiver.setImageWidth(trueWidth - currentPaddings); + } else { + if (nextImage < currentImage) { + if (num < currentImage) { + if (num <= nextImage) { + receiver.setImageX(startX + (receiver.getParam() - currentImage + 1) * (itemWidth + itemSpacing) - (nextTrueWidth + itemSpacing) + moveX); + } else { + receiver.setImageX(startX + (receiver.getParam() - currentImage) * (itemWidth + itemSpacing) + moveX); + } + } else { + receiver.setImageX(startX + trueWidth + itemSpacing + (receiver.getParam() - currentImage - 1) * (itemWidth + itemSpacing) + moveX); + } + } else { + if (num < currentImage) { + receiver.setImageX(startX + (receiver.getParam() - currentImage) * (itemWidth + itemSpacing) + moveX); + } else { + if (num <= nextImage) { + receiver.setImageX(startX + trueWidth + itemSpacing + (receiver.getParam() - currentImage - 1) * (itemWidth + itemSpacing) + moveX); + } else { + receiver.setImageX(startX + trueWidth + itemSpacing + (receiver.getParam() - currentImage - 2) * (itemWidth + itemSpacing) + (nextTrueWidth + itemSpacing) + moveX); + } + } + } + if (num == nextImage) { + receiver.setImageWidth(nextTrueWidth - nextPaddings); + receiver.setImageX(receiver.getImageX() + nextPaddings / 2); + } else { + receiver.setImageWidth(itemWidth); + } + } + receiver.draw(canvas); + } + + long newTime = System.currentTimeMillis(); + long dt = newTime - lastUpdateTime; + if (dt > 17) { + dt = 17; + } + lastUpdateTime = newTime; + if (animateToItem >= 0) { + if (moveLineProgress > 0.0f) { + moveLineProgress -= dt / 200.0f; + if (animateToItem == currentImage) { + if (currentItemProgress < 1.0f) { + currentItemProgress += dt / 200.0f; + if (currentItemProgress > 1.0f) { + currentItemProgress = 1.0f; + } + } + drawDx = animateToDXStart + (int) Math.ceil(currentItemProgress * (animateToDX - animateToDXStart)); + } else { + nextItemProgress = CubicBezierInterpolator.EASE_OUT.getInterpolation(1.0f - moveLineProgress); + if (stopedScrolling) { + if (currentItemProgress > 0.0f) { + currentItemProgress -= dt / 200.0f; + if (currentItemProgress < 0.0f) { + currentItemProgress = 0.0f; + } + } + drawDx = animateToDXStart + (int) Math.ceil(nextItemProgress * (animateToDX - animateToDXStart)); + } else { + currentItemProgress = CubicBezierInterpolator.EASE_OUT.getInterpolation(moveLineProgress); + drawDx = (int) Math.ceil(nextItemProgress * animateToDX); + } + } + if (moveLineProgress <= 0) { + currentImage = animateToItem; + moveLineProgress = 1.0f; + currentItemProgress = 1.0f; + nextItemProgress = 0.0f; + moving = false; + stopedScrolling = false; + drawDx = 0; + animateToItem = -1; + } + } + fillImages(true, drawDx); + invalidate(); + } + if (scrolling && currentItemProgress > 0.0f) { + currentItemProgress -= dt / 200.0f; + if (currentItemProgress < 0.0f) { + currentItemProgress = 0.0f; + } + invalidate(); + } + if (!scroll.isFinished()) { + if (scroll.computeScrollOffset()) { + drawDx = scroll.getCurrX(); + updateAfterScroll(); + invalidate(); + } + if (scroll.isFinished()) { + stopScrolling(); + } + } + } + } + private class BackgroundDrawable extends ColorDrawable { private Runnable drawRunnable; @@ -431,7 +1029,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat super.draw(canvas); if (getAlpha() != 0) { if (drawRunnable != null) { - drawRunnable.run(); + AndroidUtilities.runOnUIThread(drawRunnable); drawRunnable = null; } } @@ -447,6 +1045,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private int height; private RectF rect; private int currentCount = 0; + private float rotation; public CounterView(Context context) { super(context); @@ -459,6 +1058,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat paint.setColor(0xffffffff); paint.setStrokeWidth(AndroidUtilities.dp(2)); paint.setStyle(Paint.Style.STROKE); + paint.setStrokeJoin(Paint.Join.ROUND); rect = new RectF(); @@ -471,6 +1071,17 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat invalidate(); } + @Override + public void setRotationX(float rotationX) { + rotation = rotationX; + invalidate(); + } + + @Override + public float getRotationX() { + return rotation; + } + public void setCount(int value) { staticLayout = new StaticLayout("" + Math.max(1, value), textPaint, AndroidUtilities.dp(100), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); width = (int) Math.ceil(staticLayout.getLineWidth(0)); @@ -515,15 +1126,22 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat @Override protected void onDraw(Canvas canvas) { - int cx = getMeasuredWidth() / 2; int cy = getMeasuredHeight() / 2; + paint.setAlpha(255); rect.set(AndroidUtilities.dp(1), cy - AndroidUtilities.dp(14), getMeasuredWidth() - AndroidUtilities.dp(1), cy + AndroidUtilities.dp(14)); canvas.drawRoundRect(rect, AndroidUtilities.dp(15), AndroidUtilities.dp(15), paint); if (staticLayout != null) { + textPaint.setAlpha((int) ((1.0f - rotation) * 255)); canvas.save(); - canvas.translate((getMeasuredWidth() - width) / 2, (getMeasuredHeight() - height) / 2 + AndroidUtilities.dpf2(0.2f)); + canvas.translate((getMeasuredWidth() - width) / 2, (getMeasuredHeight() - height) / 2 + AndroidUtilities.dpf2(0.2f) + rotation * AndroidUtilities.dp(5)); staticLayout.draw(canvas); canvas.restore(); + paint.setAlpha((int) (rotation * 255)); + int cx = (int) rect.centerX(); + cy = (int) rect.centerY(); + cy -= AndroidUtilities.dp(5) * (1.0f - rotation) + AndroidUtilities.dp(3.0f); + canvas.drawLine(cx + AndroidUtilities.dp(0.5f), cy - AndroidUtilities.dp(0.5f), cx - AndroidUtilities.dp(6), cy + AndroidUtilities.dp(6), paint); + canvas.drawLine(cx - AndroidUtilities.dp(0.5f), cy - AndroidUtilities.dp(0.5f), cx + AndroidUtilities.dp(6), cy + AndroidUtilities.dp(6), paint); } } } @@ -702,8 +1320,8 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } @Override - public void setPhotoChecked(int index, VideoEditedInfo videoEditedInfo) { - + public int setPhotoChecked(int index, VideoEditedInfo videoEditedInfo) { + return -1; } @Override @@ -735,6 +1353,31 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat public boolean scaleToFill() { return false; } + + @Override + public void toggleGroupPhotosEnabled() { + + } + + @Override + public ArrayList getSelectedPhotosOrder() { + return null; + } + + @Override + public HashMap getSelectedPhotos() { + return null; + } + + @Override + public boolean canScrollAway() { + return true; + } + + @Override + public boolean allowGroupPhotos() { + return true; + } } public interface PhotoViewerProvider { @@ -748,7 +1391,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat boolean isPhotoChecked(int index); - void setPhotoChecked(int index, VideoEditedInfo videoEditedInfo); + int setPhotoChecked(int index, VideoEditedInfo videoEditedInfo); boolean cancelButtonPressed(); @@ -761,6 +1404,16 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat boolean allowCaption(); boolean scaleToFill(); + + void toggleGroupPhotosEnabled(); + + ArrayList getSelectedPhotosOrder(); + + HashMap getSelectedPhotos(); + + boolean canScrollAway(); + + boolean allowGroupPhotos(); } private class FrameLayoutDrawer extends SizeNotifierFrameLayoutPhoto { @@ -868,6 +1521,14 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } else { childTop = captionEditText.getBottom(); } + } else if (child == selectedPhotosListView) { + childTop = actionBar.getMeasuredHeight(); + } else if (child == captionTextView) { + if (!groupedPhotosListView.currentPhotos.isEmpty()) { + childTop -= groupedPhotosListView.getMeasuredHeight(); + } + } else if (hintTextView != null && child == hintTextView) { + childTop = selectedPhotosListView.getBottom() + AndroidUtilities.dp(3); } child.layout(childLeft, childTop, childLeft + width, childTop + height); } @@ -887,12 +1548,19 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { if (child == mentionListView || child == captionEditText) { - if (!captionEditText.isPopupShowing() && captionEditText.getEmojiPadding() == 0 && (AndroidUtilities.usingHardwareInput && getTag() == null || getKeyboardHeight() == 0)) { + if (!captionEditText.isPopupShowing() && captionEditText.getEmojiPadding() == 0 && (AndroidUtilities.usingHardwareInput && captionEditText.getTag() == null || getKeyboardHeight() == 0)) { return false; } - } else if (child == pickerView || child == captionTextView || child == checkImageView || child == photosCounterView || muteItem.getVisibility() == VISIBLE && child == bottomLayout) { + } else if (child == pickerView || child == captionTextView || muteItem.getVisibility() == VISIBLE && child == bottomLayout) { int paddingBottom = getKeyboardHeight() <= AndroidUtilities.dp(20) && !AndroidUtilities.isInMultiwindow ? captionEditText.getEmojiPadding() : 0; - if (captionEditText.isPopupShowing() || AndroidUtilities.usingHardwareInput && getTag() != null || getKeyboardHeight() > 0 || paddingBottom != 0) { + if (captionEditText.isPopupShowing() || AndroidUtilities.usingHardwareInput && captionEditText.getTag() != null || getKeyboardHeight() > 0 || paddingBottom != 0) { + bottomTouchEnabled = false; + return false; + } else { + bottomTouchEnabled = true; + } + } else if (child == checkImageView || child == photosCounterView) { + if (captionEditText.getTag() != null) { bottomTouchEnabled = false; return false; } else { @@ -960,13 +1628,13 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } } } else if (id == NotificationCenter.dialogPhotosLoaded) { - int guid = (Integer) args[4]; + int guid = (Integer) args[3]; int did = (Integer) args[0]; if (avatarsDialogId == did && classGuid == guid) { - boolean fromCache = (Boolean) args[3]; + boolean fromCache = (Boolean) args[2]; int setToImage = -1; - ArrayList photos = (ArrayList) args[5]; + ArrayList photos = (ArrayList) args[4]; if (photos.isEmpty()) { return; } @@ -1010,7 +1678,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat setImageIndex(0, true); } if (fromCache) { - MessagesController.getInstance().loadDialogPhotos(avatarsDialogId, 0, 80, 0, false, classGuid); + MessagesController.getInstance().loadDialogPhotos(avatarsDialogId, 80, 0, false, classGuid); } } } else if (id == NotificationCenter.mediaCountDidLoaded) { @@ -1030,7 +1698,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (needSearchImageInArr && isFirstLoading) { isFirstLoading = false; loadingMoreImages = true; - SharedMediaQuery.loadMedia(currentDialogId, 0, 80, 0, SharedMediaQuery.MEDIA_PHOTOVIDEO, true, classGuid); + SharedMediaQuery.loadMedia(currentDialogId, 80, 0, SharedMediaQuery.MEDIA_PHOTOVIDEO, true, classGuid); } else if (!imagesArr.isEmpty()) { if (opennedFromMedia) { actionBar.setTitle(LocaleController.formatString("Of", R.string.Of, currentIndex + 1, totalImagesCount + totalImagesCountMerge)); @@ -1119,9 +1787,9 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (!endReached[loadIndex]) { loadingMoreImages = true; if (opennedFromMedia) { - SharedMediaQuery.loadMedia(loadIndex == 0 ? currentDialogId : mergeDialogId, 0, 80, loadFromMaxId, SharedMediaQuery.MEDIA_PHOTOVIDEO, true, classGuid); + SharedMediaQuery.loadMedia(loadIndex == 0 ? currentDialogId : mergeDialogId, 80, loadFromMaxId, SharedMediaQuery.MEDIA_PHOTOVIDEO, true, classGuid); } else { - SharedMediaQuery.loadMedia(loadIndex == 0 ? currentDialogId : mergeDialogId, 0, 80, loadFromMaxId, SharedMediaQuery.MEDIA_PHOTOVIDEO, true, classGuid); + SharedMediaQuery.loadMedia(loadIndex == 0 ? currentDialogId : mergeDialogId, 80, loadFromMaxId, SharedMediaQuery.MEDIA_PHOTOVIDEO, true, classGuid); } } } @@ -1224,7 +1892,16 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat intent.setType("image/jpeg"); } } - intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(f)); + if (Build.VERSION.SDK_INT >= 24) { + try { + intent.putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(parentActivity, BuildConfig.APPLICATION_ID + ".provider", f)); + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } catch (Exception ignore) { + intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(f)); + } + } else { + intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(f)); + } parentActivity.startActivityForResult(Intent.createChooser(intent, LocaleController.getString("ShareFile", R.string.ShareFile)), 500); } else { @@ -1491,9 +2168,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat showAlertDialog(builder); } } else if (id == gallery_menu_showall) { - if (opennedFromMedia) { - closePhoto(true, false); - } else if (currentDialogId != 0) { + if (currentDialogId != 0) { disableShowCheck = true; Bundle args2 = new Bundle(); args2.putLong("dialog_id", currentDialogId); @@ -1504,39 +2179,85 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat closePhoto(false, false); ((LaunchActivity) parentActivity).presentFragment(mediaActivity, false, true); } + } else if (id == gallery_menu_showinchat) { + if (currentMessageObject == null) { + return; + } + Bundle args = new Bundle(); + int lower_part = (int) currentDialogId; + int high_id = (int) (currentDialogId >> 32); + if (lower_part != 0) { + if (high_id == 1) { + args.putInt("chat_id", lower_part); + } else { + if (lower_part > 0) { + args.putInt("user_id", lower_part); + } else if (lower_part < 0) { + TLRPC.Chat chat = MessagesController.getInstance().getChat(-lower_part); + if (chat != null && chat.migrated_to != null) { + args.putInt("migrated_to", lower_part); + lower_part = -chat.migrated_to.channel_id; + } + args.putInt("chat_id", -lower_part); + } + } + } else { + args.putInt("enc_id", high_id); + } + args.putInt("message_id", currentMessageObject.getId()); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); + ((LaunchActivity) parentActivity).presentFragment(new ChatActivity(args), true, true); + currentMessageObject = null; + closePhoto(false, false); } else if (id == gallery_menu_send) { - /*Intent intent = new Intent(this, MessagesActivity.class); - intent.putExtra("onlySelect", true); - startActivityForResult(intent, 10); - if (requestCode == 10) { - int chatId = data.getIntExtra("chatId", 0); - int userId = data.getIntExtra("userId", 0); - int dialog_id = 0; - if (chatId != 0) { - dialog_id = -chatId; - } else if (userId != 0) { - dialog_id = userId; - } - TLRPC.FileLocation location = getCurrentFile(); - if (dialog_id != 0 && location != null) { - Intent intent = new Intent(GalleryImageViewer.this, ChatActivity.class); - if (chatId != 0) { - intent.putExtra("chatId", chatId); + if (currentMessageObject == null || parentActivity == null) { + return; + } + Bundle args = new Bundle(); + args.putBoolean("onlySelect", true); + args.putInt("dialogsType", 3); + DialogsActivity fragment = new DialogsActivity(args); + final ArrayList fmessages = new ArrayList<>(); + fmessages.add(currentMessageObject); + fragment.setDelegate(new DialogsActivity.DialogsActivityDelegate() { + @Override + public void didSelectDialogs(DialogsActivity fragment, ArrayList dids, CharSequence message, boolean param) { + if (dids.size() > 1 || dids.get(0) == UserConfig.getClientUserId() || message != null) { + for (int a = 0; a < dids.size(); a++) { + long did = dids.get(a); + if (message != null) { + SendMessagesHelper.getInstance().sendMessage(message.toString(), did, null, null, true, null, null, null); + } + SendMessagesHelper.getInstance().sendMessage(fmessages, did); + } + fragment.finishFragment(); } else { - intent.putExtra("userId", userId); - } - startActivity(intent); - NotificationCenter.getInstance().postNotificationName(MessagesController.closeChats); - finish(); - if (withoutBottom) { - MessagesController.getInstance().sendMessage(location, dialog_id); - } else { - int item = mViewPager.getCurrentItem(); - MessageObject obj = localPagerAdapter.imagesArr.get(item); - MessagesController.getInstance().sendMessage(obj, dialog_id); + long did = dids.get(0); + int lower_part = (int) did; + int high_part = (int) (did >> 32); + Bundle args = new Bundle(); + args.putBoolean("scrollToTopOnResume", true); + if (lower_part != 0) { + if (lower_part > 0) { + args.putInt("user_id", lower_part); + } else if (lower_part < 0) { + args.putInt("chat_id", -lower_part); + } + } else { + args.putInt("enc_id", high_part); + } + NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); + ChatActivity chatActivity = new ChatActivity(args); + if (((LaunchActivity) parentActivity).presentFragment(chatActivity, true, false)) { + chatActivity.showReplyPanel(true, null, fmessages, null, false); + } else { + fragment.finishFragment(); + } } } - }*/ + }); + ((LaunchActivity) parentActivity).presentFragment(fragment, false, true); + closePhoto(false, false); } else if (id == gallery_menu_delete) { if (parentActivity == null) { return; @@ -1604,7 +2325,11 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (obj.isSent()) { closePhoto(false, false); ArrayList arr = new ArrayList<>(); - arr.add(obj.getId()); + if (slideshowMessageId != 0) { + arr.add(slideshowMessageId); + } else { + arr.add(obj.getId()); + } ArrayList random_ids = null; TLRPC.EncryptedChat encryptedChat = null; @@ -1705,17 +2430,24 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat masksItem = menu.addItem(gallery_menu_masks, R.drawable.ic_masks_msk1); + sendItem = menu.addItem(gallery_menu_send, R.drawable.msg_panel_reply); + menuItem = menu.addItem(0, R.drawable.ic_ab_other); - menuItem.addSubItem(gallery_menu_openin, LocaleController.getString("OpenInExternalApp", R.string.OpenInExternalApp)); - menuItem.addSubItem(gallery_menu_showall, LocaleController.getString("ShowAllMedia", R.string.ShowAllMedia)); - menuItem.addSubItem(gallery_menu_share, LocaleController.getString("ShareFile", R.string.ShareFile)); - menuItem.addSubItem(gallery_menu_save, LocaleController.getString("SaveToGallery", R.string.SaveToGallery)); - menuItem.addSubItem(gallery_menu_delete, LocaleController.getString("Delete", R.string.Delete)); + menuItem.addSubItem(gallery_menu_openin, LocaleController.getString("OpenInExternalApp", R.string.OpenInExternalApp)).setTextColor(0xfffafafa); + menuItem.addSubItem(gallery_menu_showall, LocaleController.getString("ShowAllMedia", R.string.ShowAllMedia)).setTextColor(0xfffafafa); + menuItem.addSubItem(gallery_menu_showinchat, LocaleController.getString("ShowInChat", R.string.ShowInChat)).setTextColor(0xfffafafa); + menuItem.addSubItem(gallery_menu_share, LocaleController.getString("ShareFile", R.string.ShareFile)).setTextColor(0xfffafafa); + menuItem.addSubItem(gallery_menu_save, LocaleController.getString("SaveToGallery", R.string.SaveToGallery)).setTextColor(0xfffafafa); + menuItem.addSubItem(gallery_menu_delete, LocaleController.getString("Delete", R.string.Delete)).setTextColor(0xfffafafa); + menuItem.redrawPopup(0xf9222222); bottomLayout = new FrameLayout(actvityContext); bottomLayout.setBackgroundColor(0x7f000000); containerView.addView(bottomLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.BOTTOM | Gravity.LEFT)); + groupedPhotosListView = new GroupedPhotosListView(actvityContext); + containerView.addView(groupedPhotosListView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 62, Gravity.BOTTOM | Gravity.LEFT, 0, 0, 0, 48)); + captionTextView = new TextView(actvityContext) { @Override public boolean onTouchEvent(MotionEvent event) { @@ -1973,6 +2705,9 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat @Override public void onPlayProgressChanged(float progress) { + if (videoPlayer == null) { + return; + } videoPlayer.seekTo((int) (videoDuration * progress)); } @@ -1990,12 +2725,12 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat pickerViewSendButton = new ImageView(parentActivity); pickerViewSendButton.setScaleType(ImageView.ScaleType.CENTER); - Drawable drawable = Theme.createSimpleSelectorCircleDrawable(AndroidUtilities.dp(56), 0xff6cabeb, 0xff6cabeb); + Drawable drawable = Theme.createSimpleSelectorCircleDrawable(AndroidUtilities.dp(56), 0xff66bffa, 0xff66bffa); pickerViewSendButton.setBackgroundDrawable(drawable); pickerViewSendButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chats_actionIcon), PorterDuff.Mode.MULTIPLY)); pickerViewSendButton.setPadding(AndroidUtilities.dp(4), 0, 0, 0); pickerViewSendButton.setImageResource(R.drawable.ic_send); - pickerView.addView(pickerViewSendButton, LayoutHelper.createFrame(56, 56, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.BOTTOM, 0, 0, 14, 14)); + pickerView.addView(pickerViewSendButton, LayoutHelper.createFrame(56, 56, Gravity.RIGHT | Gravity.BOTTOM, 0, 0, 14, 14)); pickerViewSendButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -2049,8 +2784,22 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat }); compressItem = new ImageView(parentActivity); + compressItem.setTag(1); compressItem.setScaleType(ImageView.ScaleType.CENTER); compressItem.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + selectedCompression = preferences.getInt("compress_video2", 1); + if (selectedCompression <= 0) { + compressItem.setImageResource(R.drawable.video_240); + } else if (selectedCompression == 1) { + compressItem.setImageResource(R.drawable.video_360); + } else if (selectedCompression == 2) { + compressItem.setImageResource(R.drawable.video_480); + } else if (selectedCompression == 3) { + compressItem.setImageResource(R.drawable.video_720); + } else if (selectedCompression == 4) { + compressItem.setImageResource(R.drawable.video_1080); + } itemsLayout.addView(compressItem, LayoutHelper.createLinear(70, 48)); compressItem.setOnClickListener(new View.OnClickListener() { @Override @@ -2068,6 +2817,14 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat @Override public void onClick(View v) { muteVideo = !muteVideo; + if (muteVideo && !checkImageView.isChecked()) { + checkImageView.callOnClick(); + } else { + Object object = imagesArrLocals.get(currentIndex); + if (object instanceof MediaController.PhotoEntry) { + ((MediaController.PhotoEntry) object).editedInfo = getCurrentVideoEditedInfo(); + } + } updateMuteButton(); } }); @@ -2338,7 +3095,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat checkImageView.setHasBorder(true); checkImageView.setSize(40); checkImageView.setCheckOffset(AndroidUtilities.dp(1)); - checkImageView.setColor(0xff6cabeb, 0xffffffff); + checkImageView.setColor(0xff66bffa, 0xffffffff); checkImageView.setVisibility(View.GONE); containerView.addView(checkImageView, LayoutHelper.createFrame(40, 40, Gravity.RIGHT | Gravity.TOP, 0, rotation == Surface.ROTATION_270 || rotation == Surface.ROTATION_90 ? 58 : 68, 10, 0)); if (Build.VERSION.SDK_INT >= 21) { @@ -2347,11 +3104,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat checkImageView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - if (placeProvider != null) { - placeProvider.setPhotoChecked(currentIndex, getCurrentVideoEditedInfo()); - checkImageView.setChecked(placeProvider.isPhotoChecked(currentIndex), true); - updateSelectedCount(); - } + setPhotoChecked(); } }); @@ -2363,7 +3116,51 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat photosCounterView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { + if (placeProvider == null || placeProvider.getSelectedPhotosOrder() == null || placeProvider.getSelectedPhotosOrder().isEmpty()) { + return; + } + togglePhotosListView(!isPhotosListViewVisible, true); + } + }); + selectedPhotosListView = new RecyclerListView(parentActivity); + selectedPhotosListView.setVisibility(View.GONE); + selectedPhotosListView.setAlpha(0.0f); + selectedPhotosListView.setTranslationY(-AndroidUtilities.dp(10)); + selectedPhotosListView.addItemDecoration(new RecyclerView.ItemDecoration() { + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + int position = parent.getChildAdapterPosition(view); + if (view instanceof PhotoPickerPhotoCell && position == 0) { + outRect.left = AndroidUtilities.dp(3); + } else { + outRect.left = 0; + } + outRect.right = AndroidUtilities.dp(3); + } + }); + selectedPhotosListView.setBackgroundColor(0x7f000000); + selectedPhotosListView.setPadding(0, AndroidUtilities.dp(3), 0, AndroidUtilities.dp(3)); + selectedPhotosListView.setLayoutManager(new LinearLayoutManager(parentActivity, LinearLayoutManager.HORIZONTAL, false)); + selectedPhotosListView.setAdapter(selectedPhotosAdapter = new ListAdapter(parentActivity)); + containerView.addView(selectedPhotosListView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 88, Gravity.LEFT | Gravity.TOP)); + selectedPhotosListView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + if (position == 0 && placeProvider.allowGroupPhotos()) { + boolean enabled = MediaController.getInstance().isGroupPhotosEnabled(); + MediaController.getInstance().toggleGroupPhotosEnabled(); + placeProvider.toggleGroupPhotosEnabled(); + ImageView imageView = (ImageView) view; + imageView.setColorFilter(!enabled ? new PorterDuffColorFilter(0xff66bffa, PorterDuff.Mode.MULTIPLY) : null); + showHint(false, !enabled); + } else { + int idx = imagesArrLocals.indexOf(view.getTag()); + if (idx >= 0) { + currentIndex = -1; + setImageIndex(idx, false); + } + } } }); @@ -2402,7 +3199,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat @Override public void onTextChanged(CharSequence text) { if (mentionsAdapter != null && captionEditText != null && parentChatActivity != null && text != null) { - mentionsAdapter.searchUsernameOrHashtag(text.toString(), captionEditText.getCursorPosition(), parentChatActivity.messages); + mentionsAdapter.searchUsernameOrHashtag(text.toString(), captionEditText.getCursorPosition(), parentChatActivity.messages, false); } } @@ -2548,10 +3345,14 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (object instanceof TLRPC.User) { TLRPC.User user = (TLRPC.User) object; if (user != null) { - captionEditText.replaceWithText(start, len, "@" + user.username + " "); + captionEditText.replaceWithText(start, len, "@" + user.username + " ", false); } } else if (object instanceof String) { - captionEditText.replaceWithText(start, len, object + " "); + captionEditText.replaceWithText(start, len, object + " ", false); + } else if (object instanceof EmojiSuggestion) { + String code = ((EmojiSuggestion) object).emoji; + captionEditText.addEmojiToRecent(code); + captionEditText.replaceWithText(start, len, code, true); } } }); @@ -2583,6 +3384,12 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (imageMoveAnimation != null || changeModeAnimation != null || currentEditMode != 0) { return; } + selectedPhotosListView.setVisibility(View.GONE); + selectedPhotosListView.setEnabled(false); + selectedPhotosListView.setAlpha(0.0f); + selectedPhotosListView.setTranslationY(-AndroidUtilities.dp(10)); + photosCounterView.setRotationX(0.0f); + isPhotosListViewVisible = false; captionEditText.setTag(1); captionEditText.openKeyboard(); lastTitle = actionBar.getTitle(); @@ -2595,7 +3402,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } private VideoEditedInfo getCurrentVideoEditedInfo() { - if (!isCurrentVideo || currentPlayingVideoFile == null) { + if (!isCurrentVideo || currentPlayingVideoFile == null || compressionsCount == 0) { return null; } VideoEditedInfo videoEditedInfo = new VideoEditedInfo(); @@ -2640,9 +3447,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } if (captionEditText.getFieldCharSequence().length() != 0 && !placeProvider.isPhotoChecked(currentIndex)) { - placeProvider.setPhotoChecked(currentIndex, getCurrentVideoEditedInfo()); - checkImageView.setChecked(placeProvider.isPhotoChecked(currentIndex), true); - updateSelectedCount(); + setPhotoChecked(); } } captionEditText.setTag(null); @@ -2665,7 +3470,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private void updateVideoPlayerTime() { String newText; if (videoPlayer == null) { - newText = "00:00 / 00:00"; + newText = String.format("%02d:%02d / %02d:%02d", 0, 0, 0, 0); } else { long current = videoPlayer.getCurrentPosition(); long total = videoPlayer.getDuration(); @@ -2681,7 +3486,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat total /= 1000; newText = String.format("%02d:%02d / %02d:%02d", current / 60, current % 60, total / 60, total % 60); } else { - newText = "00:00 / 00:00"; + newText = String.format("%02d:%02d / %02d:%02d", 0, 0, 0, 0); } } if (!TextUtils.equals(videoPlayerTime.getText(), newText)) { @@ -2918,13 +3723,18 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private void applyCurrentEditMode() { Bitmap bitmap = null; ArrayList stickers = null; + MediaController.SavedFilterState savedFilterState = null; + boolean removeSavedState = false; if (currentEditMode == 1) { bitmap = photoCropView.getBitmap(); + removeSavedState = true; } else if (currentEditMode == 2) { bitmap = photoFilterView.getBitmap(); + savedFilterState = photoFilterView.getSavedFilterState(); } else if (currentEditMode == 3) { bitmap = photoPaintView.getBitmap(); stickers = photoPaintView.getMasks(); + removeSavedState = true; } if (bitmap != null) { TLRPC.PhotoSize size = ImageLoader.scaleAndSaveImage(bitmap, AndroidUtilities.getPhotoSize(), AndroidUtilities.getPhotoSize(), 80, false, 101, 101); @@ -2950,6 +3760,11 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat paintItem.setColorFilter(new PorterDuffColorFilter(0xff3dadee, PorterDuff.Mode.MULTIPLY)); entry.isPainted = true; } + if (savedFilterState != null) { + entry.savedFilterState = savedFilterState; + } else if (removeSavedState) { + entry.savedFilterState = null; + } } else if (object instanceof MediaController.SearchImage) { MediaController.SearchImage entry = (MediaController.SearchImage) object; entry.imagePath = FileLoader.getPathToAttach(size, true).toString(); @@ -2970,13 +3785,16 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat paintItem.setColorFilter(new PorterDuffColorFilter(0xff3dadee, PorterDuff.Mode.MULTIPLY)); entry.isPainted = true; } + if (savedFilterState != null) { + entry.savedFilterState = savedFilterState; + } else if (removeSavedState) { + entry.savedFilterState = null; + } } if (sendPhotoType == 0 && placeProvider != null) { placeProvider.updatePhotoAtIndex(currentIndex); if (!placeProvider.isPhotoChecked(currentIndex)) { - placeProvider.setPhotoChecked(currentIndex, null); - checkImageView.setChecked(placeProvider.isPhotoChecked(currentIndex), true); - updateSelectedCount(); + setPhotoChecked(); } } if (currentEditMode == 1) { @@ -3000,6 +3818,26 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } } + private void setPhotoChecked() { + if (placeProvider != null) { + int num = placeProvider.setPhotoChecked(currentIndex, getCurrentVideoEditedInfo()); + boolean checked = placeProvider.isPhotoChecked(currentIndex); + checkImageView.setChecked(checked, true); + if (num >= 0) { + if (placeProvider.allowGroupPhotos()) { + num++; + } + if (checked) { + selectedPhotosAdapter.notifyItemInserted(num); + selectedPhotosListView.smoothScrollToPosition(num); + } else { + selectedPhotosAdapter.notifyItemRemoved(num); + } + } + updateSelectedCount(); + } + } + private void switchToEditMode(final int mode) { if (currentEditMode == mode || centerImage.getBitmap() == null || changeModeAnimation != null || imageMoveAnimation != null || photoProgressViews[0].backgroundState != -1 || captionEditText.getTag() != null) { return; @@ -3165,6 +4003,9 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat arrayList.add(ObjectAnimator.ofFloat(checkImageView, "alpha", 1, 0)); arrayList.add(ObjectAnimator.ofFloat(photosCounterView, "alpha", 1, 0)); } + if (selectedPhotosListView.getVisibility() == View.VISIBLE) { + arrayList.add(ObjectAnimator.ofFloat(selectedPhotosListView, "alpha", 1, 0)); + } changeModeAnimation.playTogether(arrayList); changeModeAnimation.setDuration(200); changeModeAnimation.addListener(new AnimatorListenerAdapter() { @@ -3172,6 +4013,12 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat public void onAnimationEnd(Animator animation) { changeModeAnimation = null; pickerView.setVisibility(View.GONE); + selectedPhotosListView.setVisibility(View.GONE); + selectedPhotosListView.setAlpha(0.0f); + selectedPhotosListView.setTranslationY(-AndroidUtilities.dp(10)); + photosCounterView.setRotationX(0.0f); + selectedPhotosListView.setEnabled(false); + isPhotosListViewVisible = false; if (needCaptionLayout) { captionTextView.setVisibility(View.INVISIBLE); } @@ -3240,7 +4087,33 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat changeModeAnimation.start(); } else if (mode == 2) { if (photoFilterView == null) { - photoFilterView = new PhotoFilterView(parentActivity, centerImage.getBitmap(), centerImage.getOrientation()); + MediaController.SavedFilterState state = null; + Bitmap bitmap; + String originalPath = null; + int orientation = 0; + if (!imagesArrLocals.isEmpty()) { + Object object = imagesArrLocals.get(currentIndex); + if (object instanceof MediaController.PhotoEntry) { + MediaController.PhotoEntry entry = (MediaController.PhotoEntry) object; + if (entry.imagePath == null) { + originalPath = entry.path; + state = entry.savedFilterState; + } + orientation = entry.orientation; + } else if (object instanceof MediaController.SearchImage) { + MediaController.SearchImage entry = (MediaController.SearchImage) object; + state = entry.savedFilterState; + originalPath = entry.imageUrl; + } + } + if (state == null) { + bitmap = centerImage.getBitmap(); + orientation = centerImage.getOrientation(); + } else { + bitmap = BitmapFactory.decodeFile(originalPath); + } + + photoFilterView = new PhotoFilterView(parentActivity, bitmap, orientation, state); containerView.addView(photoFilterView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); photoFilterView.getDoneTextView().setOnClickListener(new View.OnClickListener() { @Override @@ -3283,6 +4156,9 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat arrayList.add(ObjectAnimator.ofFloat(checkImageView, "alpha", 1, 0)); arrayList.add(ObjectAnimator.ofFloat(photosCounterView, "alpha", 1, 0)); } + if (selectedPhotosListView.getVisibility() == View.VISIBLE) { + arrayList.add(ObjectAnimator.ofFloat(selectedPhotosListView, "alpha", 1, 0)); + } changeModeAnimation.playTogether(arrayList); changeModeAnimation.setDuration(200); changeModeAnimation.addListener(new AnimatorListenerAdapter() { @@ -3291,6 +4167,12 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat changeModeAnimation = null; pickerView.setVisibility(View.GONE); actionBar.setVisibility(View.GONE); + selectedPhotosListView.setVisibility(View.GONE); + selectedPhotosListView.setAlpha(0.0f); + selectedPhotosListView.setTranslationY(-AndroidUtilities.dp(10)); + photosCounterView.setRotationX(0.0f); + selectedPhotosListView.setEnabled(false); + isPhotosListViewVisible = false; if (needCaptionLayout) { captionTextView.setVisibility(View.INVISIBLE); } @@ -3385,6 +4267,9 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat arrayList.add(ObjectAnimator.ofFloat(checkImageView, "alpha", 1, 0)); arrayList.add(ObjectAnimator.ofFloat(photosCounterView, "alpha", 1, 0)); } + if (selectedPhotosListView.getVisibility() == View.VISIBLE) { + arrayList.add(ObjectAnimator.ofFloat(selectedPhotosListView, "alpha", 1, 0)); + } changeModeAnimation.playTogether(arrayList); changeModeAnimation.setDuration(200); changeModeAnimation.addListener(new AnimatorListenerAdapter() { @@ -3392,6 +4277,12 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat public void onAnimationEnd(Animator animation) { changeModeAnimation = null; pickerView.setVisibility(View.GONE); + selectedPhotosListView.setVisibility(View.GONE); + selectedPhotosListView.setAlpha(0.0f); + selectedPhotosListView.setTranslationY(-AndroidUtilities.dp(10)); + photosCounterView.setRotationX(0.0f); + selectedPhotosListView.setEnabled(false); + isPhotosListViewVisible = false; if (needCaptionLayout) { captionTextView.setVisibility(View.INVISIBLE); } @@ -3486,6 +4377,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat ArrayList arrayList = new ArrayList<>(); arrayList.add(ObjectAnimator.ofFloat(actionBar, "alpha", show ? 1.0f : 0.0f)); arrayList.add(ObjectAnimator.ofFloat(bottomLayout, "alpha", show ? 1.0f : 0.0f)); + arrayList.add(ObjectAnimator.ofFloat(groupedPhotosListView, "alpha", show ? 1.0f : 0.0f)); if (captionTextView.getTag() != null) { arrayList.add(ObjectAnimator.ofFloat(captionTextView, "alpha", show ? 1.0f : 0.0f)); } @@ -3514,6 +4406,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } else { actionBar.setAlpha(show ? 1.0f : 0.0f); bottomLayout.setAlpha(show ? 1.0f : 0.0f); + groupedPhotosListView.setAlpha(show ? 1.0f : 0.0f); if (captionTextView.getTag() != null) { captionTextView.setAlpha(show ? 1.0f : 0.0f); } @@ -3529,6 +4422,46 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } } + private void togglePhotosListView(boolean show, final boolean animated) { + if (show == isPhotosListViewVisible) { + return; + } + if (show) { + selectedPhotosListView.setVisibility(View.VISIBLE); + } + isPhotosListViewVisible = show; + selectedPhotosListView.setEnabled(show); + + if (animated) { + ArrayList arrayList = new ArrayList<>(); + arrayList.add(ObjectAnimator.ofFloat(selectedPhotosListView, "alpha", show ? 1.0f : 0.0f)); + arrayList.add(ObjectAnimator.ofFloat(selectedPhotosListView, "translationY", show ? 0 : -AndroidUtilities.dp(10))); + arrayList.add(ObjectAnimator.ofFloat(photosCounterView, "rotationX", show ? 1.0f : 0.0f)); + currentListViewAnimation = new AnimatorSet(); + currentListViewAnimation.playTogether(arrayList); + if (!show) { + currentListViewAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (currentListViewAnimation != null && currentListViewAnimation.equals(animation)) { + selectedPhotosListView.setVisibility(View.GONE); + currentListViewAnimation = null; + } + } + }); + } + currentListViewAnimation.setDuration(200); + currentListViewAnimation.start(); + } else { + selectedPhotosListView.setAlpha(show ? 1.0f : 0.0f); + selectedPhotosListView.setTranslationY(show ? 0 : -AndroidUtilities.dp(10)); + photosCounterView.setRotationX(show ? 1.0f : 0.0f); + if (!show) { + selectedPhotosListView.setVisibility(View.GONE); + } + } + } + private String getFileName(int index) { if (index < 0) { return null; @@ -3610,6 +4543,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } } } else if (message.messageOwner.media instanceof TLRPC.TL_messageMediaPhoto && message.messageOwner.media.photo != null || message.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage && message.messageOwner.media.webpage != null) { + TLRPC.FileLocation location; TLRPC.PhotoSize sizeFull = FileLoader.getClosestPhotoSizeWithSize(message.photoThumbs, AndroidUtilities.getPhotoSize()); if (sizeFull != null) { size[0] = sizeFull.size; @@ -3637,7 +4571,11 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (placeProvider == null) { return; } - photosCounterView.setCount(placeProvider.getSelectedCount()); + int count = placeProvider.getSelectedCount(); + photosCounterView.setCount(count); + if (count == 0) { + togglePhotosListView(false, true); + } } private void onPhotoShow(final MessageObject messageObject, final TLRPC.FileLocation fileLocation, final ArrayList messages, final ArrayList photos, int index, final PlaceProviderObject object) { @@ -3679,6 +4617,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat currentThumb = object != null ? object.thumb : null; isEvent = object != null && object.isEvent; menuItem.setVisibility(View.VISIBLE); + sendItem.setVisibility(View.GONE); bottomLayout.setVisibility(View.VISIBLE); bottomLayout.setTranslationY(0); captionTextView.setTranslationY(0); @@ -3693,7 +4632,11 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat qualityChooseViewAnimation = null; } allowShare = false; + slideshowMessageId = 0; + nameOverride = null; + dateOverride = 0; menuItem.hideSubItem(gallery_menu_showall); + menuItem.hideSubItem(gallery_menu_showinchat); menuItem.hideSubItem(gallery_menu_share); menuItem.hideSubItem(gallery_menu_openin); actionBar.setTranslationY(0); @@ -3739,15 +4682,47 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } if (messageObject != null && messages == null) { - imagesArr.add(messageObject); - if (currentAnimation != null || messageObject.eventId != 0) { - needSearchImageInArr = false; - } else if (!(messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaInvoice) && !(messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage) && (messageObject.messageOwner.action == null || messageObject.messageOwner.action instanceof TLRPC.TL_messageActionEmpty)) { - needSearchImageInArr = true; - imagesByIds[0].put(messageObject.getId(), messageObject); - menuItem.showSubItem(gallery_menu_showall); + if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage && messageObject.messageOwner.media.webpage != null) { + TLRPC.WebPage webPage = messageObject.messageOwner.media.webpage; + String siteName = webPage.site_name; + if (siteName != null) { + siteName = siteName.toLowerCase(); + if (siteName.equals("instagram") || siteName.equals("twitter")) { + if (!TextUtils.isEmpty(webPage.author)) { + nameOverride = webPage.author; + } + if (webPage.cached_page instanceof TLRPC.TL_pageFull) { + for (int a = 0; a < webPage.cached_page.blocks.size(); a++) { + TLRPC.PageBlock block = webPage.cached_page.blocks.get(a); + if (block instanceof TLRPC.TL_pageBlockAuthorDate) { + dateOverride = ((TLRPC.TL_pageBlockAuthorDate) block).published_date; + break; + } + } + } + ArrayList arrayList = messageObject.getWebPagePhotos(null, null); + if (!arrayList.isEmpty()) { + slideshowMessageId = messageObject.getId(); + needSearchImageInArr = false; + imagesArr.addAll(arrayList); + totalImagesCount = imagesArr.size(); + setImageIndex(imagesArr.indexOf(messageObject), true); + } + } + } + } + if (slideshowMessageId == 0) { + imagesArr.add(messageObject); + if (currentAnimation != null || messageObject.eventId != 0) { + needSearchImageInArr = false; + } else if (!(messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaInvoice) && !(messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage) && (messageObject.messageOwner.action == null || messageObject.messageOwner.action instanceof TLRPC.TL_messageActionEmpty)) { + needSearchImageInArr = true; + imagesByIds[0].put(messageObject.getId(), messageObject); + menuItem.showSubItem(gallery_menu_showall); + sendItem.setVisibility(View.VISIBLE); + } + setImageIndex(0, true); } - setImageIndex(0, true); } else if (fileLocation != null) { avatarsDialogId = object.dialogId; imagesArrLocations.add(fileLocation); @@ -3764,13 +4739,10 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat setImageIndex(0, true); currentUserAvatarLocation = fileLocation; } else if (messages != null) { - menuItem.showSubItem(gallery_menu_showall); opennedFromMedia = true; + menuItem.showSubItem(gallery_menu_showinchat); + sendItem.setVisibility(View.VISIBLE); imagesArr.addAll(messages); - if (!opennedFromMedia) { - Collections.reverse(imagesArr); - index = imagesArr.size() - index - 1; - } for (int a = 0; a < imagesArr.size(); a++) { MessageObject message = imagesArr.get(a); imagesByIds[message.getDialogId() == currentDialogId ? 0 : 1].put(message.getId(), message); @@ -3828,7 +4800,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat SharedMediaQuery.getMediaCount(mergeDialogId, SharedMediaQuery.MEDIA_PHOTOVIDEO, classGuid, true); } } else if (avatarsDialogId != 0) { - MessagesController.getInstance().loadDialogPhotos(avatarsDialogId, 0, 80, 0, true, classGuid); + MessagesController.getInstance().loadDialogPhotos(avatarsDialogId, 80, 0, true, classGuid); } } if (currentMessageObject != null && currentMessageObject.isVideo() || currentBotInlineResult != null && (currentBotInlineResult.type.equals("video") || MessageObject.isVideoDocument(currentBotInlineResult.document))) { @@ -3836,7 +4808,8 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } else if (!imagesArrLocals.isEmpty()) { Object entry = imagesArrLocals.get(index); CharSequence caption = null; - boolean allowTimeItem = parentChatActivity != null && !parentChatActivity.isSecretChat() && parentChatActivity.getCurrentUser() != null; + TLRPC.User user = parentChatActivity != null ? parentChatActivity.getCurrentUser() : null; + boolean allowTimeItem = parentChatActivity != null && !parentChatActivity.isSecretChat() && user != null && !user.bot; if (entry instanceof MediaController.PhotoEntry) { MediaController.PhotoEntry photoEntry = ((MediaController.PhotoEntry) entry); if (photoEntry.isVideo) { @@ -3900,8 +4873,8 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat bottomLayout.setTranslationY(AndroidUtilities.dp(48)); captionTextView.setTranslationY(AndroidUtilities.dp(48)); } else { - masksItem.setVisibility(currentMessageObject.hasPhotoStickers() && (int) currentMessageObject.getDialogId() != 0 ? View.VISIBLE : View.INVISIBLE); - if (currentMessageObject.canDeleteMessage(null)) { + masksItem.setVisibility(currentMessageObject.hasPhotoStickers() && (int) currentMessageObject.getDialogId() != 0 ? View.VISIBLE : View.GONE); + if (currentMessageObject.canDeleteMessage(null) && slideshowMessageId == 0) { menuItem.showSubItem(gallery_menu_delete); } else { menuItem.hideSubItem(gallery_menu_delete); @@ -3911,22 +4884,31 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } else { menuItem.hideSubItem(gallery_menu_openin); } - if (currentMessageObject.isFromUser()) { - TLRPC.User user = MessagesController.getInstance().getUser(currentMessageObject.messageOwner.from_id); - if (user != null) { - nameTextView.setText(UserObject.getUserName(user)); - } else { - nameTextView.setText(""); - } + if (nameOverride != null) { + nameTextView.setText(nameOverride); } else { - TLRPC.Chat chat = MessagesController.getInstance().getChat(currentMessageObject.messageOwner.to_id.channel_id); - if (chat != null) { - nameTextView.setText(chat.title); + if (currentMessageObject.isFromUser()) { + TLRPC.User user = MessagesController.getInstance().getUser(currentMessageObject.messageOwner.from_id); + if (user != null) { + nameTextView.setText(UserObject.getUserName(user)); + } else { + nameTextView.setText(""); + } } else { - nameTextView.setText(""); + TLRPC.Chat chat = MessagesController.getInstance().getChat(currentMessageObject.messageOwner.to_id.channel_id); + if (chat != null) { + nameTextView.setText(chat.title); + } else { + nameTextView.setText(""); + } } } - long date = (long) currentMessageObject.messageOwner.date * 1000; + long date; + if (dateOverride != 0) { + date = (long) dateOverride * 1000; + } else { + date = (long) currentMessageObject.messageOwner.date * 1000; + } String dateString = LocaleController.formatString("formatDateAtTime", R.string.formatDateAtTime, LocaleController.getInstance().formatterYear.format(new Date(date)), LocaleController.getInstance().formatterDay.format(new Date(date))); if (currentFileNames[0] != null && isVideo) { dateTextView.setText(String.format("%s (%s)", dateString, AndroidUtilities.formatFileSize(currentMessageObject.getDocument().size))); @@ -3959,7 +4941,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } } - SharedMediaQuery.loadMedia(loadIndex == 0 ? currentDialogId : mergeDialogId, 0, 80, loadFromMaxId, SharedMediaQuery.MEDIA_PHOTOVIDEO, true, classGuid); + SharedMediaQuery.loadMedia(loadIndex == 0 ? currentDialogId : mergeDialogId, 80, loadFromMaxId, SharedMediaQuery.MEDIA_PHOTOVIDEO, true, classGuid); loadingMoreImages = true; } actionBar.setTitle(LocaleController.formatString("Of", R.string.Of, currentIndex + 1, totalImagesCount + totalImagesCountMerge)); @@ -3974,12 +4956,12 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } } - SharedMediaQuery.loadMedia(loadIndex == 0 ? currentDialogId : mergeDialogId, 0, 80, loadFromMaxId, SharedMediaQuery.MEDIA_PHOTOVIDEO, true, classGuid); + SharedMediaQuery.loadMedia(loadIndex == 0 ? currentDialogId : mergeDialogId, 80, loadFromMaxId, SharedMediaQuery.MEDIA_PHOTOVIDEO, true, classGuid); loadingMoreImages = true; } actionBar.setTitle(LocaleController.formatString("Of", R.string.Of, (totalImagesCount + totalImagesCountMerge - imagesArr.size()) + currentIndex + 1, totalImagesCount + totalImagesCountMerge)); } - } else if (currentMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage) { + } else if (slideshowMessageId == 0 && currentMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage) { if (currentMessageObject.isVideo()) { actionBar.setTitle(LocaleController.getString("AttachVideo", R.string.AttachVideo)); } else { @@ -3988,6 +4970,9 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } else if (isInvoice) { actionBar.setTitle(currentMessageObject.messageOwner.media.title); } + if ((int) currentDialogId == 0) { + sendItem.setVisibility(View.GONE); + } if (currentMessageObject.messageOwner.ttl != 0 && currentMessageObject.messageOwner.ttl < 60 * 60) { allowShare = false; menuItem.hideSubItem(gallery_menu_save); @@ -4004,6 +4989,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } } } + groupedPhotosListView.fillList(); } else if (!imagesArrLocations.isEmpty()) { nameTextView.setText(""); dateTextView.setText(""); @@ -4034,6 +5020,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } else { menuItem.showSubItem(gallery_menu_share); } + groupedPhotosListView.fillList(); } else if (!imagesArrLocals.isEmpty()) { if (index < 0 || index >= imagesArrLocals.size()) { closePhoto(false, false); @@ -4045,18 +5032,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat boolean isFiltered = false; boolean isPainted = false; boolean isCropped = false; - if (object instanceof MediaController.PhotoEntry) { - MediaController.PhotoEntry photoEntry = ((MediaController.PhotoEntry) object); - currentPathObject = photoEntry.path; - fromCamera = photoEntry.bucketId == 0 && photoEntry.dateTaken == 0 && imagesArrLocals.size() == 1; - caption = photoEntry.caption; - isVideo = photoEntry.isVideo; - videoPath = photoEntry.path; - ttl = photoEntry.ttl; - isFiltered = photoEntry.isFiltered; - isPainted = photoEntry.isPainted; - isCropped = photoEntry.isCropped; - } else if (object instanceof TLRPC.BotInlineResult) { + if (object instanceof TLRPC.BotInlineResult) { TLRPC.BotInlineResult botInlineResult = currentBotInlineResult = ((TLRPC.BotInlineResult) object); if (botInlineResult.document != null) { isVideo = MessageObject.isVideoDocument(botInlineResult.document); @@ -4068,40 +5044,78 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat currentPathObject = botInlineResult.content_url; isVideo = botInlineResult.type.equals("video"); } + pickerView.setPadding(0, AndroidUtilities.dp(14), 0, 0); //caption = botInlineResult.send_message.caption; - } else if (object instanceof MediaController.SearchImage) { - MediaController.SearchImage searchImage = (MediaController.SearchImage) object; - if (searchImage.document != null) { - currentPathObject = FileLoader.getPathToAttach(searchImage.document, true).getAbsolutePath(); - } else { - currentPathObject = searchImage.imageUrl; + } else { + boolean isAnimation = false; + if (object instanceof MediaController.PhotoEntry) { + MediaController.PhotoEntry photoEntry = ((MediaController.PhotoEntry) object); + currentPathObject = photoEntry.path; + isVideo = photoEntry.isVideo; + } else if (object instanceof MediaController.SearchImage) { + MediaController.SearchImage searchImage = (MediaController.SearchImage) object; + if (searchImage.document != null) { + currentPathObject = FileLoader.getPathToAttach(searchImage.document, true).getAbsolutePath(); + } else { + currentPathObject = searchImage.imageUrl; + } + if (searchImage.type == 1) { + isAnimation = true; + } + } + if (isVideo) { + muteItem.setVisibility(View.VISIBLE); + compressItem.setVisibility(View.VISIBLE); + isCurrentVideo = true; + boolean isMuted = false; + if (object instanceof MediaController.PhotoEntry) { + MediaController.PhotoEntry photoEntry = ((MediaController.PhotoEntry) object); + isMuted = photoEntry.editedInfo != null && photoEntry.editedInfo.muted; + } + processOpenVideo(currentPathObject, isMuted); + videoTimelineView.setVisibility(View.VISIBLE); + paintItem.setVisibility(View.GONE); + cropItem.setVisibility(View.GONE); + tuneItem.setVisibility(View.GONE); + } else { + videoTimelineView.setVisibility(View.GONE); + muteItem.setVisibility(View.GONE); + isCurrentVideo = false; + compressItem.setVisibility(View.GONE); + if (isAnimation) { + pickerView.setPadding(0, AndroidUtilities.dp(14), 0, 0); + paintItem.setVisibility(View.GONE); + cropItem.setVisibility(View.GONE); + tuneItem.setVisibility(View.GONE); + } else { + if (sendPhotoType != 1) { + pickerView.setPadding(0, 0, 0, 0); + } + paintItem.setVisibility(View.VISIBLE); + cropItem.setVisibility(View.VISIBLE); + tuneItem.setVisibility(View.VISIBLE); + } + actionBar.setSubtitle(null); + } + if (object instanceof MediaController.PhotoEntry) { + MediaController.PhotoEntry photoEntry = ((MediaController.PhotoEntry) object); + fromCamera = photoEntry.bucketId == 0 && photoEntry.dateTaken == 0 && imagesArrLocals.size() == 1; + caption = photoEntry.caption; + videoPath = photoEntry.path; + ttl = photoEntry.ttl; + isFiltered = photoEntry.isFiltered; + isPainted = photoEntry.isPainted; + isCropped = photoEntry.isCropped; + } else if (object instanceof MediaController.SearchImage) { + MediaController.SearchImage searchImage = (MediaController.SearchImage) object; + caption = searchImage.caption; + ttl = searchImage.ttl; + isFiltered = searchImage.isFiltered; + isPainted = searchImage.isPainted; + isCropped = searchImage.isCropped; } - caption = searchImage.caption; - ttl = searchImage.ttl; - isFiltered = searchImage.isFiltered; - isPainted = searchImage.isPainted; - isCropped = searchImage.isCropped; } bottomLayout.setVisibility(View.GONE); - if (isVideo) { - muteItem.setVisibility(View.VISIBLE); - compressItem.setVisibility(View.VISIBLE); - isCurrentVideo = true; - processOpenVideo(currentPathObject); - videoTimelineView.setVisibility(View.VISIBLE); - paintItem.setVisibility(View.GONE); - cropItem.setVisibility(View.GONE); - tuneItem.setVisibility(View.GONE); - } else { - videoTimelineView.setVisibility(View.GONE); - muteItem.setVisibility(View.GONE); - isCurrentVideo = false; - compressItem.setVisibility(View.GONE); - paintItem.setVisibility(View.VISIBLE); - cropItem.setVisibility(View.VISIBLE); - tuneItem.setVisibility(View.VISIBLE); - actionBar.setSubtitle(null); - } if (fromCamera) { if (isVideo) { actionBar.setTitle(LocaleController.getString("AttachVideo", R.string.AttachVideo)); @@ -4285,6 +5299,10 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat File f = null; boolean isVideo = false; if (currentMessageObject != null) { + if (index < 0 || index >= imagesArr.size()) { + photoProgressViews[a].setBackgroundState(-1, animated); + return; + } MessageObject messageObject = imagesArr.get(index); if (!TextUtils.isEmpty(messageObject.messageOwner.attachPath)) { f = new File(messageObject.messageOwner.attachPath); @@ -4297,6 +5315,10 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } isVideo = messageObject.isVideo(); } else if (currentBotInlineResult != null) { + if (index < 0 || index >= imagesArrLocals.size()) { + photoProgressViews[a].setBackgroundState(-1, animated); + return; + } TLRPC.BotInlineResult botInlineResult = (TLRPC.BotInlineResult) imagesArrLocals.get(index); if (botInlineResult.type.equals("video") || MessageObject.isVideoDocument(botInlineResult.document)) { if (botInlineResult.document != null) { @@ -4314,6 +5336,10 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat f = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), currentFileNames[a]); } } else if (currentFileLocation != null) { + if (index < 0 || index >= imagesArrLocations.size()) { + photoProgressViews[a].setBackgroundState(-1, animated); + return; + } TLRPC.FileLocation location = imagesArrLocations.get(index); f = FileLoader.getPathToAttach(location, avatarsDialogId != 0 || isEvent); } else if (currentPathObject != null) { @@ -4485,6 +5511,9 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat size[0] = -1; } TLRPC.PhotoSize thumbLocation = messageObject != null ? FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, 100) : null; + if (thumbLocation != null && thumbLocation.location == fileLocation) { + thumbLocation = null; + } imageReceiver.setImage(fileLocation, null, null, placeHolder != null ? new BitmapDrawable(null, placeHolder) : null, thumbLocation != null ? thumbLocation.location : null, "b", size[0], null, avatarsDialogId != 0 || isEvent ? 1 : 0); } } else { @@ -4619,6 +5648,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat placeProvider = provider; mergeDialogId = mDialogId; currentDialogId = dialogId; + selectedPhotosAdapter.notifyDataSetChanged(); if (velocityTracker == null) { velocityTracker = VelocityTracker.obtain(); @@ -4626,6 +5656,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat isVisible = true; toggleActionBar(true, false); + togglePhotosListView(false, false); if (object != null) { disableShowCheck = true; @@ -5053,7 +6084,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat currentAnimation = null; centerImage.setImageBitmap((Drawable) null); } - if (placeProvider instanceof EmptyPhotoViewerProvider) { + if (!placeProvider.canScrollAway()) { placeProvider.cancelButtonPressed(); } } @@ -5120,7 +6151,9 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (placeProvider != null) { placeProvider.willHidePhotoViewer(); } + groupedPhotosListView.clear(); placeProvider = null; + selectedPhotosAdapter.notifyDataSetChanged(); disableShowCheck = false; if (object != null) { object.imageReceiver.setVisible(true, true); @@ -5317,7 +6350,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat return true; } } - if (!(placeProvider instanceof EmptyPhotoViewerProvider) && currentEditMode == 0 && canDragDown && !draggingDown && scale == 1 && dy >= AndroidUtilities.dp(30) && dy / 2 > dx) { + if (placeProvider.canScrollAway() && currentEditMode == 0 && canDragDown && !draggingDown && scale == 1 && dy >= AndroidUtilities.dp(30) && dy / 2 > dx) { draggingDown = true; moving = false; dragY = ev.getY(); @@ -5325,6 +6358,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat toggleActionBar(false, true); } else if (pickerView.getVisibility() == View.VISIBLE) { toggleActionBar(false, true); + togglePhotosListView(false, true); toggleCheckImageView(false); } return true; @@ -5526,6 +6560,110 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat return animationValue; } + private void hideHint() { + hintAnimation = new AnimatorSet(); + hintAnimation.playTogether( + ObjectAnimator.ofFloat(hintTextView, "alpha", 0.0f) + ); + hintAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animation.equals(hintAnimation)) { + hintAnimation = null; + hintHideRunnable = null; + if (hintTextView != null) { + hintTextView.setVisibility(View.GONE); + } + } + } + + @Override + public void onAnimationCancel(Animator animation) { + if (animation.equals(hintAnimation)) { + hintHideRunnable = null; + hintHideRunnable = null; + } + } + }); + hintAnimation.setDuration(300); + hintAnimation.start(); + } + + private void showHint(boolean hide, boolean enabled) { + if (containerView == null || hide && hintTextView == null) { + return; + } + if (hintTextView == null) { + hintTextView = new TextView(containerView.getContext()); + hintTextView.setBackgroundDrawable(Theme.createRoundRectDrawable(AndroidUtilities.dp(3), Theme.getColor(Theme.key_chat_gifSaveHintBackground))); + hintTextView.setTextColor(Theme.getColor(Theme.key_chat_gifSaveHintText)); + hintTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + hintTextView.setPadding(AndroidUtilities.dp(8), AndroidUtilities.dp(7), AndroidUtilities.dp(8), AndroidUtilities.dp(7)); + hintTextView.setGravity(Gravity.CENTER_VERTICAL); + hintTextView.setAlpha(0.0f); + containerView.addView(hintTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 5, 0, 5, 3)); + } + if (hide) { + if (hintAnimation != null) { + hintAnimation.cancel(); + hintAnimation = null; + } + AndroidUtilities.cancelRunOnUIThread(hintHideRunnable); + hintHideRunnable = null; + hideHint(); + return; + } + + hintTextView.setText(enabled ? LocaleController.getString("GroupPhotosHelp", R.string.GroupPhotosHelp) : LocaleController.getString("SinglePhotosHelp", R.string.SinglePhotosHelp)); + + if (hintHideRunnable != null) { + if (hintAnimation != null) { + hintAnimation.cancel(); + hintAnimation = null; + } else { + AndroidUtilities.cancelRunOnUIThread(hintHideRunnable); + AndroidUtilities.runOnUIThread(hintHideRunnable = new Runnable() { + @Override + public void run() { + hideHint(); + } + }, 2000); + return; + } + } else if (hintAnimation != null) { + return; + } + + hintTextView.setVisibility(View.VISIBLE); + hintAnimation = new AnimatorSet(); + hintAnimation.playTogether( + ObjectAnimator.ofFloat(hintTextView, "alpha", 1.0f) + ); + hintAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animation.equals(hintAnimation)) { + hintAnimation = null; + AndroidUtilities.runOnUIThread(hintHideRunnable = new Runnable() { + @Override + public void run() { + hideHint(); + } + }, 2000); + } + } + + @Override + public void onAnimationCancel(Animator animation) { + if (animation.equals(hintAnimation)) { + hintAnimation = null; + } + } + }); + hintAnimation.setDuration(300); + hintAnimation.start(); + } + @SuppressLint({"NewApi", "DrawAllocation"}) private void onDraw(Canvas canvas) { if (animationInProgress == 1 || !isVisible && animationInProgress != 2) { @@ -5605,11 +6743,13 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } } - if (currentEditMode == 0 && scale == 1 && aty != -1 && !zoomAnimation) { - float maxValue = getContainerViewHeight() / 4.0f; - backgroundDrawable.setAlpha((int) Math.max(127, 255 * (1.0f - (Math.min(Math.abs(aty), maxValue) / maxValue)))); - } else { - backgroundDrawable.setAlpha(255); + if (animationInProgress != 2) { + if (currentEditMode == 0 && scale == 1 && aty != -1 && !zoomAnimation) { + float maxValue = getContainerViewHeight() / 4.0f; + backgroundDrawable.setAlpha((int) Math.max(127, 255 * (1.0f - (Math.min(Math.abs(aty), maxValue) / maxValue)))); + } else { + backgroundDrawable.setAlpha(255); + } } ImageReceiver sideImage = null; @@ -5619,6 +6759,8 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat sideImage = leftImage; } else if (currentTranslationX < minX - AndroidUtilities.dp(5)) { sideImage = rightImage; + } else { + groupedPhotosListView.setMoveProgress(0.0f); } } changingPage = sideImage != null; @@ -5653,6 +6795,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat sideImage.draw(canvas); canvas.restore(); } + groupedPhotosListView.setMoveProgress(-alpha); canvas.save(); canvas.translate(tranlateX, currentTranslationY / currentScale); @@ -5761,6 +6904,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat sideImage.draw(canvas); canvas.restore(); } + groupedPhotosListView.setMoveProgress(1.0f - alpha); canvas.save(); canvas.translate(currentTranslationX, currentTranslationY / currentScale); @@ -5968,6 +7112,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private int bitrate; private int originalBitrate; private float videoDuration; + private boolean videoHasAudio; private long startTime; private long endTime; private long audioFramesSize; @@ -6094,7 +7239,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } String text; if (a == compressionsCount - 1) { - text = originalHeight + "p"; + text = Math.min(originalWidth, originalHeight) + "p"; } else if (a == 0) { text = "240p"; } else if (a == 1) { @@ -6119,26 +7264,35 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (videoPlayer != null) { videoPlayer.setMute(muteVideo); } - if (muteVideo) { - actionBar.setSubtitle(null); - muteItem.setImageResource(R.drawable.volume_off); - muteItem.setColorFilter(new PorterDuffColorFilter(0xff3dadee, PorterDuff.Mode.MULTIPLY)); - if (compressItem.getTag() != null) { - compressItem.setClickable(false); - compressItem.setAlpha(0.5f); - compressItem.setEnabled(false); - } - videoTimelineView.setMaxProgressDiff(30000.0f / videoDuration); + if (!videoHasAudio) { + muteItem.setEnabled(false); + muteItem.setClickable(false); + muteItem.setAlpha(0.5f); } else { - muteItem.setColorFilter(null); - actionBar.setSubtitle(currentSubtitle); - muteItem.setImageResource(R.drawable.volume_on); - if (compressItem.getTag() != null) { - compressItem.setClickable(true); - compressItem.setAlpha(1.0f); - compressItem.setEnabled(true); + muteItem.setEnabled(true); + muteItem.setClickable(true); + muteItem.setAlpha(1.0f); + if (muteVideo) { + actionBar.setSubtitle(null); + muteItem.setImageResource(R.drawable.volume_off); + muteItem.setColorFilter(new PorterDuffColorFilter(0xff3dadee, PorterDuff.Mode.MULTIPLY)); + if (compressItem.getTag() != null) { + compressItem.setClickable(false); + compressItem.setAlpha(0.5f); + compressItem.setEnabled(false); + } + videoTimelineView.setMaxProgressDiff(30000.0f / videoDuration); + } else { + muteItem.setColorFilter(null); + actionBar.setSubtitle(currentSubtitle); + muteItem.setImageResource(R.drawable.volume_on); + if (compressItem.getTag() != null) { + compressItem.setClickable(true); + compressItem.setAlpha(1.0f); + compressItem.setEnabled(true); + } + videoTimelineView.setMaxProgressDiff(1.0f); } - videoTimelineView.setMaxProgressDiff(1.0f); } } @@ -6158,6 +7312,10 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (actionBar == null) { return; } + if (compressionsCount == 0) { + actionBar.setSubtitle(null); + return; + } if (selectedCompression == 0) { compressItem.setImageResource(R.drawable.video_240); @@ -6271,6 +7429,9 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } private void updateWidthHeightBitrateForCompression() { + if (compressionsCount <= 0) { + return; + } if (selectedCompression >= compressionsCount) { selectedCompression = compressionsCount - 1; } @@ -6292,7 +7453,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat break; case 3: default: - targetBitrate = 1600000; + targetBitrate = 2500000; maxSize = 1280.0f; break; } @@ -6374,14 +7535,14 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat qualityChooseViewAnimation.start(); } - private void processOpenVideo(final String videoPath) { + private void processOpenVideo(final String videoPath, boolean muted) { if (currentLoadingVideoRunnable != null) { Utilities.globalQueue.cancelRunnable(currentLoadingVideoRunnable); currentLoadingVideoRunnable = null; } videoPreviewMessageObject = null; setCompressItemEnabled(false, true); - muteVideo = false; + muteVideo = muted; videoTimelineView.setVideoPath(videoPath); compressionsCount = -1; rotationValue = 0; @@ -6399,22 +7560,20 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat try { IsoFile isoFile = new IsoFile(videoPath); List boxes = Path.getPaths(isoFile, "/moov/trak/"); - boolean isMp4A = true; Box boxTest = Path.getPath(isoFile, "/moov/trak/mdia/minf/stbl/stsd/mp4a/"); if (boxTest == null) { - isMp4A = false; - } - - if (!isMp4A) { - return; + FileLog.d("video hasn't mp4a atom"); } boxTest = Path.getPath(isoFile, "/moov/trak/mdia/minf/stbl/stsd/avc1/"); if (boxTest == null) { + FileLog.d("video hasn't avc1 atom"); isAvc = false; } + audioFramesSize = 0; + videoFramesSize = 0; for (int b = 0; b < boxes.size(); b++) { if (currentLoadingVideoRunnable != this) { return; @@ -6444,22 +7603,25 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } TrackHeaderBox headerBox = trackBox.getTrackHeaderBox(); if (headerBox.getWidth() != 0 && headerBox.getHeight() != 0) { - trackHeaderBox = headerBox; - originalBitrate = bitrate = (int) (trackBitrate / 100000 * 100000); - if (bitrate > 900000) { - bitrate = 900000; + if (trackHeaderBox == null || trackHeaderBox.getWidth() < headerBox.getWidth() || trackHeaderBox.getHeight() < headerBox.getHeight()) { + trackHeaderBox = headerBox; + originalBitrate = bitrate = (int) (trackBitrate / 100000 * 100000); + if (bitrate > 900000) { + bitrate = 900000; + } + videoFramesSize += sampleSizes; } - videoFramesSize += sampleSizes; } else { audioFramesSize += sampleSizes; } } } catch (Exception e) { FileLog.e(e); - return; + isAvc = false; } if (trackHeaderBox == null) { - return; + FileLog.d("video hasn't trackHeaderBox atom"); + isAvc = false; } final boolean isAvcFinal = isAvc; final TrackHeaderBox trackHeaderBoxFinal = trackHeaderBox; @@ -6473,68 +7635,73 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (parentActivity == null) { return; } - Matrix matrix = trackHeaderBoxFinal.getMatrix(); - if (matrix.equals(Matrix.ROTATE_90)) { - rotationValue = 90; - } else if (matrix.equals(Matrix.ROTATE_180)) { - rotationValue = 180; - } else if (matrix.equals(Matrix.ROTATE_270)) { - rotationValue = 270; - } else { - rotationValue = 0; - } - resultWidth = originalWidth = (int) trackHeaderBoxFinal.getWidth(); - resultHeight = originalHeight = (int) trackHeaderBoxFinal.getHeight(); + videoHasAudio = isAvcFinal; + if (isAvcFinal) { + Matrix matrix = trackHeaderBoxFinal.getMatrix(); + if (matrix.equals(Matrix.ROTATE_90)) { + rotationValue = 90; + } else if (matrix.equals(Matrix.ROTATE_180)) { + rotationValue = 180; + } else if (matrix.equals(Matrix.ROTATE_270)) { + rotationValue = 270; + } else { + rotationValue = 0; + } + resultWidth = originalWidth = (int) trackHeaderBoxFinal.getWidth(); + resultHeight = originalHeight = (int) trackHeaderBoxFinal.getHeight(); - if (!isAvcFinal && (resultWidth == originalWidth || resultHeight == originalHeight)) { - return; - } + videoDuration *= 1000; - videoDuration *= 1000; + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + selectedCompression = preferences.getInt("compress_video2", 1); + if (originalWidth > 1280 || originalHeight > 1280) { + compressionsCount = 5; + } else if (originalWidth > 848 || originalHeight > 848) { + compressionsCount = 4; + } else if (originalWidth > 640 || originalHeight > 640) { + compressionsCount = 3; + } else if (originalWidth > 480 || originalHeight > 480) { + compressionsCount = 2; + } else { + compressionsCount = 1; + } + updateWidthHeightBitrateForCompression(); - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); - selectedCompression = preferences.getInt("compress_video2", 1); - if (originalWidth > 1280 || originalHeight > 1280) { - compressionsCount = 5; - } else if (originalWidth > 848 || originalHeight > 848) { - compressionsCount = 4; - } else if (originalWidth > 640 || originalHeight > 640) { - compressionsCount = 3; - } else if (originalWidth > 480 || originalHeight > 480) { - compressionsCount = 2; - } else { - compressionsCount = 1; - } - updateWidthHeightBitrateForCompression(); - - setCompressItemEnabled(compressionsCount > 1, true); - if (Build.VERSION.SDK_INT < 18 && compressItem.getTag() != null) { - try { - MediaCodecInfo codecInfo = MediaController.selectCodec(MediaController.MIME_TYPE); - if (codecInfo == null) { - setCompressItemEnabled(false, true); - } else { - String name = codecInfo.getName(); - if (name.equals("OMX.google.h264.encoder") || - name.equals("OMX.ST.VFM.H264Enc") || - name.equals("OMX.Exynos.avc.enc") || - name.equals("OMX.MARVELL.VIDEO.HW.CODA7542ENCODER") || - name.equals("OMX.MARVELL.VIDEO.H264ENCODER") || - name.equals("OMX.k3.video.encoder.avc") || - name.equals("OMX.TI.DUCATI1.VIDEO.H264E")) { + setCompressItemEnabled(compressionsCount > 1, true); + FileLog.d("compressionsCount = " + compressionsCount + " w = " + originalWidth + " h = " + originalHeight); + if (Build.VERSION.SDK_INT < 18 && compressItem.getTag() != null) { + try { + MediaCodecInfo codecInfo = MediaController.selectCodec(MediaController.MIME_TYPE); + if (codecInfo == null) { + FileLog.d("no codec info for " + MediaController.MIME_TYPE); setCompressItemEnabled(false, true); } else { - if (MediaController.selectColorFormat(codecInfo, MediaController.MIME_TYPE) == 0) { + String name = codecInfo.getName(); + if (name.equals("OMX.google.h264.encoder") || + name.equals("OMX.ST.VFM.H264Enc") || + name.equals("OMX.Exynos.avc.enc") || + name.equals("OMX.MARVELL.VIDEO.HW.CODA7542ENCODER") || + name.equals("OMX.MARVELL.VIDEO.H264ENCODER") || + name.equals("OMX.k3.video.encoder.avc") || + name.equals("OMX.TI.DUCATI1.VIDEO.H264E")) { + FileLog.d("unsupported encoder = " + name); setCompressItemEnabled(false, true); + } else { + if (MediaController.selectColorFormat(codecInfo, MediaController.MIME_TYPE) == 0) { + FileLog.d("no color format for " + MediaController.MIME_TYPE); + setCompressItemEnabled(false, true); + } } } + } catch (Exception e) { + setCompressItemEnabled(false, true); + FileLog.e(e); } - } catch (Exception e) { - setCompressItemEnabled(false, true); - FileLog.e(e); } + qualityChooseView.invalidate(); + } else { + compressionsCount = 0; } - qualityChooseView.invalidate(); updateVideoInfo(); updateMuteButton(); @@ -6568,4 +7735,145 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat compressItem.setAlpha(enabled ? 1.0f : 0.5f); } } + + private class ListAdapter extends RecyclerListView.SelectionAdapter { + + private Context mContext; + + public ListAdapter(Context context) { + mContext = context; + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return true; + } + + @Override + public int getItemCount() { + if (placeProvider != null && placeProvider.getSelectedPhotosOrder() != null) { + if (placeProvider.allowGroupPhotos()) { + return 1 + placeProvider.getSelectedPhotosOrder().size(); + } else { + return placeProvider.getSelectedPhotosOrder().size(); + } + } + return 0; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + PhotoPickerPhotoCell cell = new PhotoPickerPhotoCell(mContext, false); + cell.checkFrame.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Object photoEntry = ((View) v.getParent()).getTag(); + int idx = imagesArrLocals.indexOf(photoEntry); + if (idx >= 0) { + int num = placeProvider.setPhotoChecked(idx, getCurrentVideoEditedInfo()); + boolean checked = placeProvider.isPhotoChecked(idx); + if (idx == currentIndex) { + checkImageView.setChecked(false, true); + } + if (num >= 0) { + if (placeProvider.allowGroupPhotos()) { + num++; + } + selectedPhotosAdapter.notifyItemRemoved(num); + } + updateSelectedCount(); + } + } + }); + view = cell; + break; + case 1: + default: + ImageView imageView = new ImageView(mContext) { + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(66), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.EXACTLY)); + } + }; + imageView.setScaleType(ImageView.ScaleType.CENTER); + imageView.setImageResource(R.drawable.photos_group); + view = imageView; + break; + } + return new RecyclerListView.Holder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case 0: { + PhotoPickerPhotoCell cell = (PhotoPickerPhotoCell) holder.itemView; + cell.itemWidth = AndroidUtilities.dp(82); + BackupImageView imageView = cell.photoImage; + boolean showing; + imageView.setOrientation(0, true); + ArrayList order = placeProvider.getSelectedPhotosOrder(); + if (placeProvider.allowGroupPhotos()) { + position--; + } + Object object = placeProvider.getSelectedPhotos().get(order.get(position)); + if (object instanceof MediaController.PhotoEntry) { + MediaController.PhotoEntry photoEntry = (MediaController.PhotoEntry) object; + cell.setTag(photoEntry); + cell.videoInfoContainer.setVisibility(View.INVISIBLE); + if (photoEntry.thumbPath != null) { + imageView.setImage(photoEntry.thumbPath, null, mContext.getResources().getDrawable(R.drawable.nophotos)); + } else if (photoEntry.path != null) { + imageView.setOrientation(photoEntry.orientation, true); + if (photoEntry.isVideo) { + cell.videoInfoContainer.setVisibility(View.VISIBLE); + int minutes = photoEntry.duration / 60; + int seconds = photoEntry.duration - minutes * 60; + cell.videoTextView.setText(String.format("%d:%02d", minutes, seconds)); + imageView.setImage("vthumb://" + photoEntry.imageId + ":" + photoEntry.path, null, mContext.getResources().getDrawable(R.drawable.nophotos)); + } else { + imageView.setImage("thumb://" + photoEntry.imageId + ":" + photoEntry.path, null, mContext.getResources().getDrawable(R.drawable.nophotos)); + } + } else { + imageView.setImageResource(R.drawable.nophotos); + } + cell.setChecked(-1, true, false); + cell.checkBox.setVisibility(View.VISIBLE); + } else if (object instanceof MediaController.SearchImage) { + MediaController.SearchImage photoEntry = (MediaController.SearchImage) object; + cell.setTag(photoEntry); + if (photoEntry.thumbPath != null) { + imageView.setImage(photoEntry.thumbPath, null, mContext.getResources().getDrawable(R.drawable.nophotos)); + } else if (photoEntry.thumbUrl != null && photoEntry.thumbUrl.length() > 0) { + imageView.setImage(photoEntry.thumbUrl, null, mContext.getResources().getDrawable(R.drawable.nophotos)); + } else if (photoEntry.document != null && photoEntry.document.thumb != null) { + imageView.setImage(photoEntry.document.thumb.location, null, mContext.getResources().getDrawable(R.drawable.nophotos)); + } else { + imageView.setImageResource(R.drawable.nophotos); + } + cell.videoInfoContainer.setVisibility(View.INVISIBLE); + cell.setChecked(-1, true, false); + cell.checkBox.setVisibility(View.VISIBLE); + } + break; + } + case 1: { + ImageView imageView = (ImageView) holder.itemView; + imageView.setColorFilter(MediaController.getInstance().isGroupPhotosEnabled() ? new PorterDuffColorFilter(0xff66bffa, PorterDuff.Mode.MULTIPLY) : null); + break; + } + } + } + + @Override + public int getItemViewType(int i) { + if (i == 0 && placeProvider.allowGroupPhotos()) { + return 1; + } + return 0; + } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PopupNotificationActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PopupNotificationActivity.java index ad202d55e..f7403e5bb 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PopupNotificationActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PopupNotificationActivity.java @@ -763,7 +763,7 @@ public class PopupNotificationActivity extends Activity implements NotificationC photoExist = false; } } - if (photoExist || MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_PHOTO)) { + if (photoExist || MediaController.getInstance().canDownloadMedia(messageObject)) { imageView.setImage(currentPhotoObject.location, "100_100", thumb.location, currentPhotoObject.size); photoSet = true; } else { @@ -822,7 +822,7 @@ public class PopupNotificationActivity extends Activity implements NotificationC } cell.setMessageObject(messageObject); - if (MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_AUDIO)) { + if (MediaController.getInstance().canDownloadMedia(messageObject)) { cell.downloadAudioIfNeed(); } } else { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PrivacySettingsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PrivacySettingsActivity.java index cfd265cae..74eeb4631 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PrivacySettingsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PrivacySettingsActivity.java @@ -45,6 +45,7 @@ import org.telegram.ui.Cells.TextInfoPrivacyCell; import org.telegram.ui.Cells.TextSettingsCell; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.RecyclerListView; +import org.telegram.ui.Components.voip.VoIPHelper; import java.util.ArrayList; @@ -90,11 +91,7 @@ public class PrivacySettingsActivity extends BaseFragment implements Notificatio privacySectionRow = rowCount++; blockedRow = rowCount++; lastSeenRow = rowCount++; - if (MessagesController.getInstance().callsEnabled) { - callsRow = rowCount++; - } else { - callsRow = -1; - } + callsRow = rowCount++; groupsRow = rowCount++; groupsDetailRow = rowCount++; securitySectionRow = rowCount++; @@ -108,6 +105,10 @@ public class PrivacySettingsActivity extends BaseFragment implements Notificatio paymentsSectionRow = rowCount++; paymentsClearRow = rowCount++; paymentsDetailRow = rowCount++; + callsSectionRow = rowCount++; + callsP2PRow = rowCount++; + callsDetailRow = rowCount++; + if (MessagesController.getInstance().secretWebpagePreview != 1) { secretSectionRow = rowCount++; secretWebpageRow = rowCount++; @@ -117,17 +118,9 @@ public class PrivacySettingsActivity extends BaseFragment implements Notificatio secretWebpageRow = -1; secretDetailRow = -1; } - if(MessagesController.getInstance().callsEnabled){ - callsSectionRow=rowCount++; - callsP2PRow=rowCount++; - callsDetailRow=rowCount++; - }else{ - callsSectionRow=-1; - callsP2PRow=-1; - callsDetailRow=-1; - } NotificationCenter.getInstance().addObserver(this, NotificationCenter.privacyRulesUpdated); + VoIPHelper.upgradeP2pSetting(); return true; } @@ -254,12 +247,22 @@ public class PrivacySettingsActivity extends BaseFragment implements Notificatio ((TextCheckCell) view).setChecked(MessagesController.getInstance().secretWebpagePreview == 1); } } else if (position == callsP2PRow) { - SharedPreferences prefs=getParentActivity().getSharedPreferences("mainconfig", Context.MODE_PRIVATE); - boolean enableP2p=!prefs.getBoolean("calls_p2p", true); - prefs.edit().putBoolean("calls_p2p", enableP2p).commit(); - if (view instanceof TextCheckCell) { - ((TextCheckCell) view).setChecked(enableP2p); - } + new AlertDialog.Builder(getParentActivity()) + .setTitle(LocaleController.getString("PrivacyCallsP2PTitle", R.string.PrivacyCallsP2PTitle)) + .setItems(new String[]{ + LocaleController.getString("LastSeenEverybody", R.string.LastSeenEverybody), + LocaleController.getString("LastSeenContacts", R.string.LastSeenContacts), + LocaleController.getString("LastSeenNobody", R.string.LastSeenNobody) + }, new DialogInterface.OnClickListener(){ + @Override + public void onClick(DialogInterface dialog, int which){ + SharedPreferences prefs=getParentActivity().getSharedPreferences("mainconfig", Context.MODE_PRIVATE); + prefs.edit().putInt("calls_p2p_new", which).apply(); + listAdapter.notifyDataSetChanged(); + } + }) + .setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null) + .show(); } else if (position == paymentsClearRow) { BottomSheet.Builder builder = new BottomSheet.Builder(getParentActivity()); builder.setApplyTopPadding(false); @@ -496,6 +499,22 @@ public class PrivacySettingsActivity extends BaseFragment implements Notificatio textCell.setTextAndValue(LocaleController.getString("DeleteAccountIfAwayFor", R.string.DeleteAccountIfAwayFor), value, false); } else if (position == paymentsClearRow) { textCell.setText(LocaleController.getString("PrivacyPaymentsClear", R.string.PrivacyPaymentsClear), false); + } else if (position == callsP2PRow) { + SharedPreferences prefs=getParentActivity().getSharedPreferences("mainconfig", Context.MODE_PRIVATE); + String value; + switch(prefs.getInt("calls_p2p_new", MessagesController.getInstance().defaultP2pContacts ? 1 : 0)){ + case 1: + value=LocaleController.getString("LastSeenContacts", R.string.LastSeenContacts); + break; + case 2: + value=LocaleController.getString("LastSeenNobody", R.string.LastSeenNobody); + break; + case 0: + default: + value=LocaleController.getString("LastSeenEverybody", R.string.LastSeenEverybody); + break; + } + textCell.setTextAndValue(LocaleController.getString("PrivacyCallsP2PTitle", R.string.PrivacyCallsP2PTitle), value, false); } break; case 1: @@ -540,9 +559,6 @@ public class PrivacySettingsActivity extends BaseFragment implements Notificatio TextCheckCell textCheckCell = (TextCheckCell) holder.itemView; if (position == secretWebpageRow) { textCheckCell.setTextAndCheck(LocaleController.getString("SecretWebPage", R.string.SecretWebPage), MessagesController.getInstance().secretWebpagePreview == 1, true); - } else if (position == callsP2PRow) { - SharedPreferences prefs=getParentActivity().getSharedPreferences("mainconfig", Context.MODE_PRIVATE); - textCheckCell.setTextAndCheck(LocaleController.getString("PrivacyCallsP2PTitle", R.string.PrivacyCallsP2PTitle), prefs.getBoolean("calls_p2p", true), true); } break; } @@ -550,13 +566,13 @@ public class PrivacySettingsActivity extends BaseFragment implements Notificatio @Override public int getItemViewType(int position) { - if (position == lastSeenRow || position == blockedRow || position == deleteAccountRow || position == sessionsRow || position == passwordRow || position == passcodeRow || position == groupsRow || position == paymentsClearRow) { + if (position == lastSeenRow || position == blockedRow || position == deleteAccountRow || position == sessionsRow || position == passwordRow || position == passcodeRow || position == groupsRow || position == paymentsClearRow || position == callsP2PRow) { return 0; } else if (position == deleteAccountDetailRow || position == groupsDetailRow || position == sessionsDetailRow || position == secretDetailRow || position == paymentsDetailRow || position==callsDetailRow) { return 1; } else if (position == securitySectionRow || position == deleteAccountSectionRow || position == privacySectionRow || position == secretSectionRow || position == paymentsSectionRow || position==callsSectionRow) { return 2; - } else if (position == secretWebpageRow || position==callsP2PRow) { + } else if (position == secretWebpageRow) { return 3; } return 0; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PrivacyUsersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PrivacyUsersActivity.java index 7aedbf633..6dcee01cf 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PrivacyUsersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PrivacyUsersActivity.java @@ -306,7 +306,7 @@ public class PrivacyUsersActivity extends BaseFragment implements NotificationCe new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"statusColor"}, null, null, сellDelegate, Theme.key_windowBackgroundWhiteGrayText), - new ThemeDescription(listView, 0, new Class[]{UserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, null, Theme.key_avatar_text), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java index 923f88557..6c4a6c849 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java @@ -22,7 +22,6 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Configuration; -import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Outline; @@ -59,7 +58,6 @@ import org.telegram.messenger.NotificationsController; import org.telegram.messenger.SecretChatHelper; import org.telegram.messenger.SendMessagesHelper; import org.telegram.messenger.UserObject; -import org.telegram.messenger.VideoEditedInfo; import org.telegram.messenger.query.BotQuery; import org.telegram.messenger.query.SharedMediaQuery; import org.telegram.messenger.ApplicationLoader; @@ -111,7 +109,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.concurrent.Semaphore; -public class ProfileActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, DialogsActivity.DialogsActivityDelegate, PhotoViewer.PhotoViewerProvider { +public class ProfileActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, DialogsActivity.DialogsActivityDelegate { private RecyclerListView listView; private LinearLayoutManager layoutManager; @@ -146,6 +144,8 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. private int initialAnimationExtraHeight; private float animationProgress; + private boolean isBot; + private AvatarUpdater avatarUpdater; private TLRPC.ChatFull info; private int selectedUser; @@ -203,6 +203,56 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. private int addMemberRow; private int rowCount = 0; + private PhotoViewer.PhotoViewerProvider provider = new PhotoViewer.EmptyPhotoViewerProvider() { + + @Override + public PhotoViewer.PlaceProviderObject getPlaceForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { + if (fileLocation == null) { + return null; + } + + TLRPC.FileLocation photoBig = null; + if (user_id != 0) { + TLRPC.User user = MessagesController.getInstance().getUser(user_id); + if (user != null && user.photo != null && user.photo.photo_big != null) { + photoBig = user.photo.photo_big; + } + } else if (chat_id != 0) { + TLRPC.Chat chat = MessagesController.getInstance().getChat(chat_id); + if (chat != null && chat.photo != null && chat.photo.photo_big != null) { + photoBig = chat.photo.photo_big; + } + } + + + if (photoBig != null && photoBig.local_id == fileLocation.local_id && photoBig.volume_id == fileLocation.volume_id && photoBig.dc_id == fileLocation.dc_id) { + int coords[] = new int[2]; + avatarImage.getLocationInWindow(coords); + PhotoViewer.PlaceProviderObject object = new PhotoViewer.PlaceProviderObject(); + object.viewX = coords[0]; + object.viewY = coords[1] - (Build.VERSION.SDK_INT >= 21 ? 0 : AndroidUtilities.statusBarHeight); + object.parentView = avatarImage; + object.imageReceiver = avatarImage.getImageReceiver(); + if (user_id != 0) { + object.dialogId = user_id; + } else if (chat_id != 0) { + object.dialogId = -chat_id; + } + object.thumb = object.imageReceiver.getBitmap(); + object.size = -1; + object.radius = avatarImage.getImageReceiver().getRoundRadius(); + object.scale = avatarImage.getScaleX(); + return object; + } + return null; + } + + @Override + public void willHidePhotoViewer() { + avatarImage.getImageReceiver().setVisible(true, true); + } + }; + private class TopView extends View { private int currentColor; @@ -265,6 +315,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } userBlocked = MessagesController.getInstance().blockedUsers.contains(user_id); if (user.bot) { + isBot = true; BotQuery.loadBotInfo(user.id, true, classGuid); } MessagesController.getInstance().loadFullUser(MessagesController.getInstance().getUser(user_id), classGuid, true); @@ -399,7 +450,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (user == null) { return; } - if (!user.bot) { + if (!isBot) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); if (!userBlocked) { builder.setMessage(LocaleController.getString("AreYouSureBlockContact", R.string.AreYouSureBlockContact)); @@ -437,7 +488,6 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } else if (id == share_contact) { Bundle args = new Bundle(); args.putBoolean("onlySelect", true); - args.putInt("dialogsType", 1); args.putString("selectAlertString", LocaleController.getString("SendContactTo", R.string.SendContactTo)); args.putString("selectAlertStringGroup", LocaleController.getString("SendContactToGroup", R.string.SendContactToGroup)); DialogsActivity fragment = new DialogsActivity(args); @@ -489,7 +539,8 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. DialogsActivity fragment = new DialogsActivity(args); fragment.setDelegate(new DialogsActivity.DialogsActivityDelegate() { @Override - public void didSelectDialog(DialogsActivity fragment, long did, boolean param) { + public void didSelectDialogs(DialogsActivity fragment, ArrayList dids, CharSequence message, boolean param) { + long did = dids.get(0); Bundle args = new Bundle(); args.putBoolean("scrollToTopOnResume", true); args.putInt("chat_id", -(int) did); @@ -546,11 +597,6 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. return; } AndroidUtilities.installShortcut(did); - /*try { - Toast.makeText(getParentActivity(), LocaleController.getString("ShortcutAdded", R.string.ShortcutAdded), Toast.LENGTH_SHORT).show(); - } catch (Exception e) { - FileLog.e(e); - }*/ } catch (Exception e) { FileLog.e(e); } @@ -562,9 +608,15 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } else if (id == search_members) { Bundle args = new Bundle(); args.putInt("chat_id", chat_id); - args.putInt("type", 2); - args.putBoolean("open_search", true); - presentFragment(new ChannelUsersActivity(args)); + if (ChatObject.isChannel(currentChat)) { + args.putInt("type", 2); + args.putBoolean("open_search", true); + presentFragment(new ChannelUsersActivity(args)); + } else { + ChatUsersActivity chatUsersActivity = new ChatUsersActivity(args); + chatUsersActivity.setInfo(info); + presentFragment(chatUsersActivity); + } } } }); @@ -1020,13 +1072,13 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. TLRPC.User user = MessagesController.getInstance().getUser(user_id); if (user.photo != null && user.photo.photo_big != null) { PhotoViewer.getInstance().setParentActivity(getParentActivity()); - PhotoViewer.getInstance().openPhoto(user.photo.photo_big, ProfileActivity.this); + PhotoViewer.getInstance().openPhoto(user.photo.photo_big, provider); } } else if (chat_id != 0) { TLRPC.Chat chat = MessagesController.getInstance().getChat(chat_id); if (chat.photo != null && chat.photo.photo_big != null) { PhotoViewer.getInstance().setParentActivity(getParentActivity()); - PhotoViewer.getInstance().openPhoto(chat.photo.photo_big, ProfileActivity.this); + PhotoViewer.getInstance().openPhoto(chat.photo.photo_big, provider); } } } @@ -1222,7 +1274,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. ArrayList items = new ArrayList<>(); final ArrayList actions = new ArrayList<>(); TLRPC.TL_userFull userFull = MessagesController.getInstance().getUserFull(user.id); - if (MessagesController.getInstance().callsEnabled && userFull != null && userFull.phone_calls_available) { + if (userFull != null && userFull.phone_calls_available) { items.add(LocaleController.getString("CallViaTelegram", R.string.CallViaTelegram)); actions.add(2); } @@ -1267,15 +1319,13 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (position == channelInfoRow) { about = info.about; } else { - TLRPC.TL_userFull userFull = MessagesController.getInstance().getUserFull(botInfo.user_id); + TLRPC.TL_userFull userFull = MessagesController.getInstance().getUserFull(user_id); about = userFull != null ? userFull.about : null; } - if (about == null) { + if (TextUtils.isEmpty(about)) { return; } - android.content.ClipboardManager clipboard = (android.content.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE); - android.content.ClipData clip = android.content.ClipData.newPlainText("label", about); - clipboard.setPrimaryClip(clip); + AndroidUtilities.addToClipboard(about); } catch (Exception e) { FileLog.e(e); } @@ -1976,100 +2026,6 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. return null; } - @Override - public void updatePhotoAtIndex(int index) { - - } - - @Override - public boolean allowCaption() { - return true; - } - - @Override - public boolean scaleToFill() { - return false; - } - - @Override - public PhotoViewer.PlaceProviderObject getPlaceForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { - if (fileLocation == null) { - return null; - } - - TLRPC.FileLocation photoBig = null; - if (user_id != 0) { - TLRPC.User user = MessagesController.getInstance().getUser(user_id); - if (user != null && user.photo != null && user.photo.photo_big != null) { - photoBig = user.photo.photo_big; - } - } else if (chat_id != 0) { - TLRPC.Chat chat = MessagesController.getInstance().getChat(chat_id); - if (chat != null && chat.photo != null && chat.photo.photo_big != null) { - photoBig = chat.photo.photo_big; - } - } - - - if (photoBig != null && photoBig.local_id == fileLocation.local_id && photoBig.volume_id == fileLocation.volume_id && photoBig.dc_id == fileLocation.dc_id) { - int coords[] = new int[2]; - avatarImage.getLocationInWindow(coords); - PhotoViewer.PlaceProviderObject object = new PhotoViewer.PlaceProviderObject(); - object.viewX = coords[0]; - object.viewY = coords[1] - (Build.VERSION.SDK_INT >= 21 ? 0 : AndroidUtilities.statusBarHeight); - object.parentView = avatarImage; - object.imageReceiver = avatarImage.getImageReceiver(); - if (user_id != 0) { - object.dialogId = user_id; - } else if (chat_id != 0) { - object.dialogId = -chat_id; - } - object.thumb = object.imageReceiver.getBitmap(); - object.size = -1; - object.radius = avatarImage.getImageReceiver().getRoundRadius(); - object.scale = avatarImage.getScaleX(); - return object; - } - return null; - } - - @Override - public Bitmap getThumbForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { - return null; - } - - @Override - public void willSwitchFromPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { - } - - @Override - public void willHidePhotoViewer() { - avatarImage.getImageReceiver().setVisible(true, true); - } - - @Override - public boolean isPhotoChecked(int index) { - return false; - } - - @Override - public void setPhotoChecked(int index, VideoEditedInfo videoEditedInfo) { - } - - @Override - public boolean cancelButtonPressed() { - return true; - } - - @Override - public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo) { - } - - @Override - public int getSelectedCount() { - return 0; - } - private void updateOnlineCount() { onlineCount = 0; int currentTime = ConnectionsManager.getInstance().getCurrentTime(); @@ -2209,17 +2165,17 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (user_id != 0) { TLRPC.User user = MessagesController.getInstance().getUser(user_id); emptyRow = rowCount++; - if ((user == null || !user.bot) && !TextUtils.isEmpty(user.phone)) { + if (!isBot && !TextUtils.isEmpty(user.phone)) { phoneRow = rowCount++; } - TLRPC.TL_userFull userFull = MessagesController.getInstance().getUserFull(user.id); + TLRPC.TL_userFull userFull = MessagesController.getInstance().getUserFull(user_id); boolean hasUsername = user != null && !TextUtils.isEmpty(user.username); if (userFull != null && !TextUtils.isEmpty(userFull.about)) { if (phoneRow != -1) { userSectionRow = rowCount++; } - if (hasUsername) { + if (hasUsername || isBot) { userInfoRow = rowCount++; } else { userInfoDetailedRow = rowCount++; @@ -2242,7 +2198,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (userFull != null && userFull.common_chats_count != 0) { groupsInCommonRow = rowCount++; } - if (user != null && !user.bot && currentEncryptedChat == null && user.id != UserConfig.getClientUserId()) { + if (user != null && !isBot && currentEncryptedChat == null && user.id != UserConfig.getClientUserId()) { startSecretChatRow = rowCount++; } } else if (chat_id != 0) { @@ -2320,6 +2276,20 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (avatarImage == null || nameTextView == null) { return; } + String onlineTextOverride; + int currentConnectionState = ConnectionsManager.getInstance().getConnectionState(); + if (currentConnectionState == ConnectionsManager.ConnectionStateWaitingForNetwork) { + onlineTextOverride = LocaleController.getString("WaitingForNetwork", R.string.WaitingForNetwork); + } else if (currentConnectionState == ConnectionsManager.ConnectionStateConnecting) { + onlineTextOverride = LocaleController.getString("Connecting", R.string.Connecting); + } else if (currentConnectionState == ConnectionsManager.ConnectionStateUpdating) { + onlineTextOverride = LocaleController.getString("Updating", R.string.Updating); + } else if (currentConnectionState == ConnectionsManager.ConnectionStateConnectingToProxy) { + onlineTextOverride = LocaleController.getString("ConnectingToProxy", R.string.ConnectingToProxy); + } else { + onlineTextOverride = null; + } + if (user_id != 0) { TLRPC.User user = MessagesController.getInstance().getUser(user_id); TLRPC.FileLocation photo = null; @@ -2338,7 +2308,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. newString = LocaleController.getString("ChatYourSelfName", R.string.ChatYourSelfName); } else if (user.id == 333000 || user.id == 777000) { newString2 = LocaleController.getString("ServiceNotifications", R.string.ServiceNotifications); - } else if (user.bot) { + } else if (isBot) { newString2 = LocaleController.getString("Bot", R.string.Bot); } else { newString2 = LocaleController.formatUserStatus(user); @@ -2358,8 +2328,12 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. nameTextView[a].setText(newString); } } - if (!onlineTextView[a].getText().equals(newString2)) { - onlineTextView[a].setText(newString2); + if (a == 0 && onlineTextOverride != null) { + onlineTextView[a].setText(onlineTextOverride); + } else { + if (!onlineTextView[a].getText().equals(newString2)) { + onlineTextView[a].setText(newString2); + } } Drawable leftIcon = currentEncryptedChat != null ? Theme.chat_lockIconDrawable : null; Drawable rightIcon = null; @@ -2403,7 +2377,11 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } else { int result[] = new int[1]; String shortNumber = LocaleController.formatShortNumber(info.participants_count, result); - newString = LocaleController.formatPluralString("Members", result[0]).replace(String.format("%d", result[0]), shortNumber); + if (currentChat.megagroup) { + newString = LocaleController.formatPluralString("Members", result[0]).replace(String.format("%d", result[0]), shortNumber); + } else { + newString = LocaleController.formatPluralString("Subscribers", result[0]).replace(String.format("%d", result[0]), shortNumber); + } } } } else { @@ -2435,17 +2413,25 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } else { nameTextView[a].setRightDrawable(MessagesController.getInstance().isDialogMuted((long) -chat_id) ? Theme.chat_muteIconDrawable : null); } - if (currentChat.megagroup && info != null && info.participants_count <= 200 && onlineCount > 0) { - if (!onlineTextView[a].getText().equals(newString)) { - onlineTextView[a].setText(newString); - } - } else if (a == 0 && ChatObject.isChannel(currentChat) && info != null && info.participants_count != 0 && (currentChat.megagroup || currentChat.broadcast)) { - int result[] = new int[1]; - String shortNumber = LocaleController.formatShortNumber(info.participants_count, result); - onlineTextView[a].setText(LocaleController.formatPluralString("Members", result[0]).replace(String.format("%d", result[0]), shortNumber)); + if (a == 0 && onlineTextOverride != null) { + onlineTextView[a].setText(onlineTextOverride); } else { - if (!onlineTextView[a].getText().equals(newString)) { - onlineTextView[a].setText(newString); + if (currentChat.megagroup && info != null && info.participants_count <= 200 && onlineCount > 0) { + if (!onlineTextView[a].getText().equals(newString)) { + onlineTextView[a].setText(newString); + } + } else if (a == 0 && ChatObject.isChannel(currentChat) && info != null && info.participants_count != 0 && (currentChat.megagroup || currentChat.broadcast)) { + int result[] = new int[1]; + String shortNumber = LocaleController.formatShortNumber(info.participants_count, result); + if (currentChat.megagroup) { + onlineTextView[a].setText(LocaleController.formatPluralString("Members", result[0]).replace(String.format("%d", result[0]), shortNumber)); + } else { + onlineTextView[a].setText(LocaleController.formatPluralString("Subscribers", result[0]).replace(String.format("%d", result[0]), shortNumber)); + } + } else { + if (!onlineTextView[a].getText().equals(newString)) { + onlineTextView[a].setText(newString); + } } } } @@ -2471,7 +2457,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (user_id != 0) { if (UserConfig.getClientUserId() != user_id) { TLRPC.TL_userFull userFull = MessagesController.getInstance().getUserFull(user_id); - if (MessagesController.getInstance().callsEnabled && userFull != null && userFull.phone_calls_available) { + if (userFull != null && userFull.phone_calls_available) { callItem = menu.addItem(call_item, R.drawable.ic_call_white_24dp); } if (ContactsController.getInstance().contactsDict.get(user_id) == null) { @@ -2480,7 +2466,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. return; } item = menu.addItem(10, R.drawable.ic_ab_other); - if (user.bot) { + if (isBot) { if (!user.bot_nochats) { item.addSubItem(invite_to_group, LocaleController.getString("BotInvite", R.string.BotInvite)); } @@ -2492,7 +2478,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. item.addSubItem(share_contact, LocaleController.getString("ShareContact", R.string.ShareContact)); item.addSubItem(block_contact, !userBlocked ? LocaleController.getString("BlockContact", R.string.BlockContact) : LocaleController.getString("Unblock", R.string.Unblock)); } else { - if (user.bot) { + if (isBot) { item.addSubItem(block_contact, !userBlocked ? LocaleController.getString("BotStop", R.string.BotStop) : LocaleController.getString("BotRestart", R.string.BotRestart)); } else { item.addSubItem(block_contact, !userBlocked ? LocaleController.getString("BlockContact", R.string.BlockContact) : LocaleController.getString("Unblock", R.string.Unblock)); @@ -2554,6 +2540,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (!chat.admins_enabled || chat.creator || chat.admin) { item.addSubItem(edit_name, LocaleController.getString("ChannelEdit", R.string.ChannelEdit)); } + item.addSubItem(search_members, LocaleController.getString("SearchMembers", R.string.SearchMembers)); if (chat.creator && (info == null || info.participants.participants.size() > 0)) { item.addSubItem(convert_to_supergroup, LocaleController.getString("ConvertGroupMenu", R.string.ConvertGroupMenu)); } @@ -2578,31 +2565,30 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } @Override - public void didSelectDialog(DialogsActivity fragment, long dialog_id, boolean param) { - if (dialog_id != 0) { - Bundle args = new Bundle(); - args.putBoolean("scrollToTopOnResume", true); - int lower_part = (int) dialog_id; - if (lower_part != 0) { - if (lower_part > 0) { - args.putInt("user_id", lower_part); - } else if (lower_part < 0) { - args.putInt("chat_id", -lower_part); - } - } else { - args.putInt("enc_id", (int) (dialog_id >> 32)); + public void didSelectDialogs(DialogsActivity fragment, ArrayList dids, CharSequence message, boolean param) { + long did = dids.get(0); + Bundle args = new Bundle(); + args.putBoolean("scrollToTopOnResume", true); + int lower_part = (int) did; + if (lower_part != 0) { + if (lower_part > 0) { + args.putInt("user_id", lower_part); + } else if (lower_part < 0) { + args.putInt("chat_id", -lower_part); } - if (!MessagesController.checkCanOpenChat(args, fragment)) { - return; - } - - NotificationCenter.getInstance().removeObserver(this, NotificationCenter.closeChats); - NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); - presentFragment(new ChatActivity(args), true); - removeSelfFromStack(); - TLRPC.User user = MessagesController.getInstance().getUser(user_id); - SendMessagesHelper.getInstance().sendMessage(user, dialog_id, null, null, null); + } else { + args.putInt("enc_id", (int) (did >> 32)); } + if (!MessagesController.checkCanOpenChat(args, fragment)) { + return; + } + + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.closeChats); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); + presentFragment(new ChatActivity(args), true); + removeSelfFromStack(); + TLRPC.User user = MessagesController.getInstance().getUser(user_id); + SendMessagesHelper.getInstance().sendMessage(user, did, null, null, null); } @Override @@ -2864,9 +2850,17 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } } else if (i == membersRow) { if (info != null) { - textCell.setTextAndValue(LocaleController.getString("ChannelMembers", R.string.ChannelMembers), String.format("%d", info.participants_count)); + if (ChatObject.isChannel(currentChat) && !currentChat.megagroup) { + textCell.setTextAndValue(LocaleController.getString("ChannelSubscribers", R.string.ChannelSubscribers), String.format("%d", info.participants_count)); + } else { + textCell.setTextAndValue(LocaleController.getString("ChannelMembers", R.string.ChannelMembers), String.format("%d", info.participants_count)); + } } else { - textCell.setText(LocaleController.getString("ChannelMembers", R.string.ChannelMembers)); + if (ChatObject.isChannel(currentChat) && !currentChat.megagroup) { + textCell.setText(LocaleController.getString("ChannelSubscribers", R.string.ChannelSubscribers)); + } else { + textCell.setText(LocaleController.getString("ChannelMembers", R.string.ChannelMembers)); + } } } break; @@ -2904,7 +2898,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. AboutLinkCell aboutLinkCell = (AboutLinkCell) holder.itemView; if (i == userInfoRow) { TLRPC.TL_userFull userFull = MessagesController.getInstance().getUserFull(user_id); - aboutLinkCell.setTextAndIcon(userFull != null ? userFull.about : null, R.drawable.profile_info, false); + aboutLinkCell.setTextAndIcon(userFull != null ? userFull.about : null, R.drawable.profile_info, isBot); } else if (i == channelInfoRow) { String text = info.about; while (text.contains("\n\n\n")) { @@ -3033,7 +3027,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ShadowSectionCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), - new ThemeDescription(avatarImage, 0, null, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(avatarImage, 0, null, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, null, Theme.key_avatar_text), new ThemeDescription(avatarImage, 0, null, null, new Drawable[]{avatarDrawable}, null, Theme.key_avatar_backgroundInProfileRed), new ThemeDescription(avatarImage, 0, null, null, new Drawable[]{avatarDrawable}, null, Theme.key_avatar_backgroundInProfileOrange), new ThemeDescription(avatarImage, 0, null, null, new Drawable[]{avatarDrawable}, null, Theme.key_avatar_backgroundInProfileViolet), @@ -3061,7 +3055,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"statusColor"}, null, null, сellDelegate, Theme.key_windowBackgroundWhiteGrayText), new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"statusOnlineColor"}, null, null, сellDelegate, Theme.key_windowBackgroundWhiteBlueText), - new ThemeDescription(listView, 0, new Class[]{UserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, null, Theme.key_avatar_text), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ProfileNotificationsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ProfileNotificationsActivity.java index 8bdf12bbe..d8430de42 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ProfileNotificationsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ProfileNotificationsActivity.java @@ -134,7 +134,7 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi popupInfoRow = -1; } - if (lower_id > 0 && MessagesController.getInstance().callsEnabled) { + if (lower_id > 0) { callsRow = rowCount++; callsVibrateRow = rowCount++; ringtoneRow = rowCount++; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ProxySettingsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ProxySettingsActivity.java index 6e7fad2a9..588ab4792 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ProxySettingsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ProxySettingsActivity.java @@ -46,6 +46,7 @@ import org.telegram.ui.Cells.HeaderCell; import org.telegram.ui.Cells.ShadowSectionCell; import org.telegram.ui.Cells.TextCheckCell; import org.telegram.ui.Cells.TextInfoPrivacyCell; +import org.telegram.ui.Components.EditTextBoldCursor; import org.telegram.ui.Components.LayoutHelper; import java.net.URLEncoder; @@ -59,14 +60,13 @@ public class ProxySettingsActivity extends BaseFragment implements NotificationC private final static int FIELD_PASSWORD = 3; private ActionBarMenuItem shareItem; - private EditText[] inputFields; + private EditTextBoldCursor[] inputFields; private ScrollView scrollView; private LinearLayout linearLayout2; private HeaderCell headerCell; private ArrayList dividers = new ArrayList<>(); private ShadowSectionCell sectionCell; private TextInfoPrivacyCell bottomCell; - private TextInfoPrivacyCell useForCallsInfoCell; private TextCheckCell checkCell1; private TextCheckCell useForCallsCell; @@ -209,12 +209,7 @@ public class ProxySettingsActivity extends BaseFragment implements NotificationC sectionCell = new ShadowSectionCell(context); linearLayout2.addView(sectionCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); - /*headerCell = new HeaderCell(context); - headerCell.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); - headerCell.setText(LocaleController.getString("PaymentShippingAddress", R.string.PaymentShippingAddress)); - linearLayout2.addView(headerCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));*/ - - inputFields = new EditText[4]; + inputFields = new EditTextBoldCursor[4]; for (int a = 0; a < 4; a++) { FrameLayout container = new FrameLayout(context); linearLayout2.addView(container, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48)); @@ -228,13 +223,15 @@ public class ProxySettingsActivity extends BaseFragment implements NotificationC container.addView(divider, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1, Gravity.LEFT | Gravity.BOTTOM)); } - inputFields[a] = new EditText(context); + inputFields[a] = new EditTextBoldCursor(context); inputFields[a].setTag(a); inputFields[a].setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); inputFields[a].setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); inputFields[a].setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); inputFields[a].setBackgroundDrawable(null); - AndroidUtilities.clearCursorDrawable(inputFields[a]); + inputFields[a].setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + inputFields[a].setCursorSize(AndroidUtilities.dp(20)); + inputFields[a].setCursorWidth(1.5f); if (a == FIELD_IP) { inputFields[a].addTextChangedListener(new TextWatcher() { @@ -373,7 +370,7 @@ public class ProxySettingsActivity extends BaseFragment implements NotificationC } }); - useForCallsInfoCell = new TextInfoPrivacyCell(context); + TextInfoPrivacyCell useForCallsInfoCell = new TextInfoPrivacyCell(context); useForCallsInfoCell.setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); useForCallsInfoCell.setText(LocaleController.getString("UseProxyForCallsInfo", R.string.UseProxyForCallsInfo)); linearLayout2.addView(useForCallsInfoCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ReportOtherActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ReportOtherActivity.java index 0927726c3..798229351 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ReportOtherActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ReportOtherActivity.java @@ -20,7 +20,6 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; -import android.widget.EditText; import android.widget.LinearLayout; import android.widget.TextView; @@ -38,11 +37,12 @@ import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Components.EditTextBoldCursor; import org.telegram.ui.Components.LayoutHelper; public class ReportOtherActivity extends BaseFragment { - private EditText firstNameField; + private EditTextBoldCursor firstNameField; private View headerLabelView; private long dialog_id; private View doneButton; @@ -96,7 +96,7 @@ public class ReportOtherActivity extends BaseFragment { } }); - firstNameField = new EditText(context); + firstNameField = new EditTextBoldCursor(context); firstNameField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); firstNameField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); firstNameField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); @@ -107,7 +107,9 @@ public class ReportOtherActivity extends BaseFragment { firstNameField.setInputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT); firstNameField.setImeOptions(EditorInfo.IME_ACTION_DONE); firstNameField.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); - AndroidUtilities.clearCursorDrawable(firstNameField); + firstNameField.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + firstNameField.setCursorSize(AndroidUtilities.dp(20)); + firstNameField.setCursorWidth(1.5f); firstNameField.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/SecretMediaViewer.java b/TMessagesProj/src/main/java/org/telegram/ui/SecretMediaViewer.java index 13f166712..2df3abf2e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/SecretMediaViewer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/SecretMediaViewer.java @@ -1155,7 +1155,7 @@ public class SecretMediaViewer implements NotificationCenter.NotificationCenterD } closeTime = System.currentTimeMillis(); final PhotoViewer.PlaceProviderObject object; - if (currentMessageObject.messageOwner.media.photo instanceof TLRPC.TL_photoEmpty || currentMessageObject.messageOwner.media.document instanceof TLRPC.TL_documentEmpty) { + if (currentProvider == null || currentMessageObject.messageOwner.media.photo instanceof TLRPC.TL_photoEmpty || currentMessageObject.messageOwner.media.document instanceof TLRPC.TL_documentEmpty) { object = null; } else { object = currentProvider.getPlaceForPhoto(currentMessageObject, null, 0); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/SessionsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/SessionsActivity.java index 5d48aa429..56b5cbff9 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/SessionsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/SessionsActivity.java @@ -193,6 +193,9 @@ public class SessionsActivity extends BaseFragment implements NotificationCenter builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); showDialog(builder.create()); } else if (position >= otherSessionsStartRow && position < otherSessionsEndRow) { + if (getParentActivity() == null) { + return; + } AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setMessage(LocaleController.getString("TerminateSessionQuestion", R.string.TerminateSessionQuestion)); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/SetAdminsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/SetAdminsActivity.java index 508df4d54..5a7c0bfdf 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/SetAdminsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/SetAdminsActivity.java @@ -674,7 +674,7 @@ public class SetAdminsActivity extends BaseFragment implements NotificationCente new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"statusColor"}, null, null, сellDelegate, Theme.key_windowBackgroundWhiteGrayText), new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"statusOnlineColor"}, null, null, сellDelegate, Theme.key_windowBackgroundWhiteBlueText), - new ThemeDescription(listView, 0, new Class[]{UserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, null, Theme.key_avatar_text), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/SettingsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/SettingsActivity.java index e0c8a9574..05a041262 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/SettingsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/SettingsActivity.java @@ -21,7 +21,6 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.res.Configuration; -import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Outline; import android.graphics.PorterDuff; @@ -31,6 +30,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; +import android.support.v4.content.FileProvider; import android.text.Html; import android.text.Spannable; import android.text.SpannableString; @@ -55,6 +55,7 @@ import android.widget.Toast; import org.telegram.messenger.AndroidUtilities; import org.telegram.PhoneFormat.PhoneFormat; +import org.telegram.messenger.BuildConfig; import org.telegram.messenger.ContactsController; import org.telegram.messenger.MediaController; import org.telegram.messenger.UserObject; @@ -62,7 +63,6 @@ import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.BuildVars; import org.telegram.messenger.LocaleController; import org.telegram.messenger.FileLoader; -import org.telegram.messenger.VideoEditedInfo; import org.telegram.messenger.browser.Browser; import org.telegram.messenger.query.StickersQuery; import org.telegram.messenger.support.widget.LinearLayoutManager; @@ -108,7 +108,7 @@ import java.io.File; import java.util.ArrayList; import java.util.Locale; -public class SettingsActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, PhotoViewer.PhotoViewerProvider { +public class SettingsActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { private RecyclerListView listView; private ListAdapter listAdapter; @@ -169,6 +169,41 @@ public class SettingsActivity extends BaseFragment implements NotificationCenter private final static int edit_name = 1; private final static int logout = 2; + private PhotoViewer.PhotoViewerProvider provider = new PhotoViewer.EmptyPhotoViewerProvider() { + + @Override + public PhotoViewer.PlaceProviderObject getPlaceForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { + if (fileLocation == null) { + return null; + } + TLRPC.User user = MessagesController.getInstance().getUser(UserConfig.getClientUserId()); + if (user != null && user.photo != null && user.photo.photo_big != null) { + TLRPC.FileLocation photoBig = user.photo.photo_big; + if (photoBig.local_id == fileLocation.local_id && photoBig.volume_id == fileLocation.volume_id && photoBig.dc_id == fileLocation.dc_id) { + int coords[] = new int[2]; + avatarImage.getLocationInWindow(coords); + PhotoViewer.PlaceProviderObject object = new PhotoViewer.PlaceProviderObject(); + object.viewX = coords[0]; + object.viewY = coords[1] - (Build.VERSION.SDK_INT >= 21 ? 0 : AndroidUtilities.statusBarHeight); + object.parentView = avatarImage; + object.imageReceiver = avatarImage.getImageReceiver(); + object.dialogId = UserConfig.getClientUserId(); + object.thumb = object.imageReceiver.getBitmap(); + object.size = -1; + object.radius = avatarImage.getImageReceiver().getRoundRadius(); + object.scale = avatarImage.getScaleX(); + return object; + } + } + return null; + } + + @Override + public void willHidePhotoViewer() { + avatarImage.getImageReceiver().setVisible(true, true); + } + }; + private static class LinkMovementMethodMy extends LinkMovementMethod { @Override public boolean onTouchEvent(@NonNull TextView widget, @NonNull Spannable buffer, @NonNull MotionEvent event) { @@ -238,6 +273,7 @@ public class SettingsActivity extends BaseFragment implements NotificationCenter NotificationCenter.getInstance().addObserver(this, NotificationCenter.updateInterfaces); NotificationCenter.getInstance().addObserver(this, NotificationCenter.featuredStickersDidLoaded); NotificationCenter.getInstance().addObserver(this, NotificationCenter.userInfoDidLoaded); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.emojiDidLoaded); rowCount = 0; overscrollRow = rowCount++; @@ -300,6 +336,7 @@ public class SettingsActivity extends BaseFragment implements NotificationCenter NotificationCenter.getInstance().removeObserver(this, NotificationCenter.updateInterfaces); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.featuredStickersDidLoaded); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.userInfoDidLoaded); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.emojiDidLoaded); avatarUpdater.clear(); } @@ -650,19 +687,43 @@ public class SettingsActivity extends BaseFragment implements NotificationCenter public boolean onItemClick(View view, int position) { if (position == versionRow) { pressCount++; - if (pressCount >= 2) { + if (pressCount >= 2 || BuildVars.DEBUG_PRIVATE_VERSION) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setTitle(LocaleController.getString("DebugMenu", R.string.DebugMenu)); - builder.setItems(new CharSequence[]{ - LocaleController.getString("DebugMenuImportContacts", R.string.DebugMenuImportContacts), - LocaleController.getString("DebugMenuReloadContacts", R.string.DebugMenuReloadContacts) - }, new DialogInterface.OnClickListener() { + CharSequence[] items; + if (BuildVars.DEBUG_PRIVATE_VERSION) { + items = new CharSequence[]{ + LocaleController.getString("DebugMenuImportContacts", R.string.DebugMenuImportContacts), + LocaleController.getString("DebugMenuReloadContacts", R.string.DebugMenuReloadContacts), + LocaleController.getString("DebugMenuResetContacts", R.string.DebugMenuResetContacts), + LocaleController.getString("DebugMenuResetDialogs", R.string.DebugMenuResetDialogs), + MediaController.getInstance().canInAppCamera() ? LocaleController.getString("DebugMenuDisableCamera", R.string.DebugMenuDisableCamera) : LocaleController.getString("DebugMenuEnableCamera", R.string.DebugMenuEnableCamera), + MediaController.getInstance().canRoundCamera16to9() ? "switch camera to 4:3" : "switch camera to 16:9" + }; + } else { + items = new CharSequence[]{ + LocaleController.getString("DebugMenuImportContacts", R.string.DebugMenuImportContacts), + LocaleController.getString("DebugMenuReloadContacts", R.string.DebugMenuReloadContacts), + LocaleController.getString("DebugMenuResetContacts", R.string.DebugMenuResetContacts), + LocaleController.getString("DebugMenuResetDialogs", R.string.DebugMenuResetDialogs), + MediaController.getInstance().canInAppCamera() ? LocaleController.getString("DebugMenuDisableCamera", R.string.DebugMenuDisableCamera) : LocaleController.getString("DebugMenuEnableCamera", R.string.DebugMenuEnableCamera) + }; + } + builder.setItems(items, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (which == 0) { ContactsController.getInstance().forceImportContacts(); } else if (which == 1) { - ContactsController.getInstance().loadContacts(false, true); + ContactsController.getInstance().loadContacts(false, 0); + } else if (which == 2) { + ContactsController.getInstance().resetImportedContacts(); + } else if (which == 3) { + MessagesController.getInstance().forceResetDialogs(); + } else if (which == 4) { + MediaController.getInstance().toggleInappCamera(); + } else if (which == 5) { + MediaController.getInstance().toggleRoundCamera16to9(); } } }); @@ -703,7 +764,7 @@ public class SettingsActivity extends BaseFragment implements NotificationCenter TLRPC.User user = MessagesController.getInstance().getUser(UserConfig.getClientUserId()); if (user != null && user.photo != null && user.photo.photo_big != null) { PhotoViewer.getInstance().setParentActivity(getParentActivity()); - PhotoViewer.getInstance().openPhoto(user.photo.photo_big, SettingsActivity.this); + PhotoViewer.getInstance().openPhoto(user.photo.photo_big, provider); } } }); @@ -825,85 +886,6 @@ public class SettingsActivity extends BaseFragment implements NotificationCenter return fragmentView; } - @Override - public void updatePhotoAtIndex(int index) { - - } - - @Override - public boolean allowCaption() { - return true; - } - - @Override - public boolean scaleToFill() { - return false; - } - - @Override - public PhotoViewer.PlaceProviderObject getPlaceForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { - if (fileLocation == null) { - return null; - } - TLRPC.User user = MessagesController.getInstance().getUser(UserConfig.getClientUserId()); - if (user != null && user.photo != null && user.photo.photo_big != null) { - TLRPC.FileLocation photoBig = user.photo.photo_big; - if (photoBig.local_id == fileLocation.local_id && photoBig.volume_id == fileLocation.volume_id && photoBig.dc_id == fileLocation.dc_id) { - int coords[] = new int[2]; - avatarImage.getLocationInWindow(coords); - PhotoViewer.PlaceProviderObject object = new PhotoViewer.PlaceProviderObject(); - object.viewX = coords[0]; - object.viewY = coords[1] - (Build.VERSION.SDK_INT >= 21 ? 0 : AndroidUtilities.statusBarHeight); - object.parentView = avatarImage; - object.imageReceiver = avatarImage.getImageReceiver(); - object.dialogId = UserConfig.getClientUserId(); - object.thumb = object.imageReceiver.getBitmap(); - object.size = -1; - object.radius = avatarImage.getImageReceiver().getRoundRadius(); - object.scale = avatarImage.getScaleX(); - return object; - } - } - return null; - } - - @Override - public Bitmap getThumbForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { - return null; - } - - @Override - public void willSwitchFromPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { - } - - @Override - public void willHidePhotoViewer() { - avatarImage.getImageReceiver().setVisible(true, true); - } - - @Override - public boolean isPhotoChecked(int index) { - return false; - } - - @Override - public void setPhotoChecked(int index, VideoEditedInfo videoEditedInfo) { - } - - @Override - public boolean cancelButtonPressed() { - return true; - } - - @Override - public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo) { - } - - @Override - public int getSelectedCount() { - return 0; - } - private void performAskAQuestion() { final SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); int uid = preferences.getInt("support_id", 0); @@ -1021,9 +1003,13 @@ public class SettingsActivity extends BaseFragment implements NotificationCenter } } else if (id == NotificationCenter.userInfoDidLoaded) { Integer uid = (Integer) args[0]; - if (uid == UserConfig.getClientUserId()) { + if (uid == UserConfig.getClientUserId() && listAdapter != null) { listAdapter.notifyItemChanged(bioRow); } + } else if (id == NotificationCenter.emojiDidLoaded) { + if (listView != null) { + listView.invalidateViews(); + } } } @@ -1164,14 +1150,22 @@ public class SettingsActivity extends BaseFragment implements NotificationCenter File sdCard = ApplicationLoader.applicationContext.getExternalFilesDir(null); File dir = new File(sdCard.getAbsolutePath() + "/logs"); File[] files = dir.listFiles(); + for (File file : files) { - uris.add(Uri.fromFile(file)); + if (Build.VERSION.SDK_INT >= 24) { + uris.add(FileProvider.getUriForFile(getParentActivity(), BuildConfig.APPLICATION_ID + ".provider", file)); + } else { + uris.add(Uri.fromFile(file)); + } } if (uris.isEmpty()) { return; } Intent i = new Intent(Intent.ACTION_SEND_MULTIPLE); + if (Build.VERSION.SDK_INT >= 24) { + i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } i.setType("message/rfc822"); i.putExtra(Intent.EXTRA_EMAIL, ""); i.putExtra(Intent.EXTRA_SUBJECT, "last logs"); @@ -1245,7 +1239,7 @@ public class SettingsActivity extends BaseFragment implements NotificationCenter } else if (position == switchBackendButtonRow) { textCell.setText("Switch Backend", true); } else if (position == telegramFaqRow) { - textCell.setText(LocaleController.getString("TelegramFAQ", R.string.TelegramFaq), true); + textCell.setText(LocaleController.getString("TelegramFAQ", R.string.TelegramFAQ), true); } else if (position == contactsReimportRow) { textCell.setText(LocaleController.getString("ImportContacts", R.string.ImportContacts), true); } else if (position == stickersRow) { @@ -1318,12 +1312,12 @@ public class SettingsActivity extends BaseFragment implements NotificationCenter String value; if (userFull == null) { value = LocaleController.getString("Loading", R.string.Loading); - } else if (userFull != null && !TextUtils.isEmpty(userFull.about)) { + } else if (!TextUtils.isEmpty(userFull.about)) { value = userFull.about; } else { value = LocaleController.getString("UserBioEmpty", R.string.UserBioEmpty); } - textCell.setTextAndValue(value, LocaleController.getString("UserBio", R.string.UserBio), false); + textCell.setTextWithEmojiAndValue(value, LocaleController.getString("UserBio", R.string.UserBio), false); } break; } @@ -1464,7 +1458,7 @@ public class SettingsActivity extends BaseFragment implements NotificationCenter new ThemeDescription(listView, 0, new Class[]{TextInfoCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText5), - new ThemeDescription(avatarImage, 0, null, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(avatarImage, 0, null, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, null, Theme.key_avatar_text), new ThemeDescription(avatarImage, 0, null, null, new Drawable[]{avatarDrawable}, null, Theme.key_avatar_backgroundInProfileBlue), new ThemeDescription(writeButton, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_profile_actionIcon), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ShareActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ShareActivity.java index 946fd982d..cc29f7af7 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ShareActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ShareActivity.java @@ -74,7 +74,7 @@ public class ShareActivity extends Activity { messageObject.messageOwner.with_my_score = true; try { - visibleDialog = new ShareAlert(this, messageObject, null, false, link, false); + visibleDialog = ShareAlert.createShareAlert(this, messageObject, null, false, link, false); visibleDialog.setCanceledOnTouchOutside(true); visibleDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/StickerPreviewViewer.java b/TMessagesProj/src/main/java/org/telegram/ui/StickerPreviewViewer.java index 6240f9c26..a4e05c409 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/StickerPreviewViewer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/StickerPreviewViewer.java @@ -24,6 +24,7 @@ import android.text.StaticLayout; import android.text.TextPaint; import android.text.TextUtils; import android.view.Gravity; +import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; @@ -36,7 +37,9 @@ import org.telegram.messenger.Emoji; import org.telegram.messenger.ImageReceiver; import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessageObject; import org.telegram.messenger.R; +import org.telegram.messenger.query.StickersQuery; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.BottomSheet; import org.telegram.ui.Cells.ContextLinkCell; @@ -45,6 +48,8 @@ import org.telegram.ui.Cells.StickerEmojiCell; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.RecyclerListView; +import java.util.ArrayList; + public class StickerPreviewViewer { private class FrameLayoutDrawer extends FrameLayout { @@ -90,21 +95,47 @@ public class StickerPreviewViewer { if (parentActivity == null || currentSet == null) { return; } + final boolean inFavs = StickersQuery.isStickerInFavorites(currentSticker); BottomSheet.Builder builder = new BottomSheet.Builder(parentActivity); - builder.setItems(new CharSequence[]{ - LocaleController.getString("SendStickerPreview", R.string.SendStickerPreview), - LocaleController.formatString("ViewPackPreview", R.string.ViewPackPreview)}, new DialogInterface.OnClickListener() { + ArrayList items = new ArrayList<>(); + final ArrayList actions = new ArrayList<>(); + ArrayList icons = new ArrayList<>(); + if (delegate != null) { + items.add(LocaleController.getString("SendStickerPreview", R.string.SendStickerPreview)); + icons.add(R.drawable.stickers_send); + actions.add(0); + items.add(LocaleController.formatString("ViewPackPreview", R.string.ViewPackPreview)); + icons.add(R.drawable.stickers_pack); + actions.add(1); + } + if (!MessageObject.isMaskDocument(currentSticker) && (inFavs || StickersQuery.canAddStickerToFavorites())) { + items.add(inFavs ? LocaleController.getString("DeleteFromFavorites", R.string.DeleteFromFavorites) : LocaleController.getString("AddToFavorites", R.string.AddToFavorites)); + icons.add(inFavs ? R.drawable.stickers_unfavorite : R.drawable.stickers_favorite); + actions.add(2); + } + if (items.isEmpty()) { + return; + } + int[] ic = new int[icons.size()]; + for (int a = 0; a < icons.size(); a++) { + ic[a] = icons.get(a); + } + builder.setItems(items.toArray(new CharSequence[items.size()]), ic, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, final int which) { if (parentActivity == null) { return; } - if (delegate != null) { - if (which == 0) { + if (actions.get(which) == 0) { + if (delegate != null) { delegate.sentSticker(currentSticker); - } else if (which == 1) { + } + } else if (actions.get(which) == 1) { + if (delegate != null) { delegate.openSet(currentSet); } + } else if (actions.get(which) == 2) { + StickersQuery.addRecentSticker(StickersQuery.TYPE_FAVE, currentSticker, (int) (System.currentTimeMillis() / 1000), inFavs); } } }); @@ -117,6 +148,7 @@ public class StickerPreviewViewer { } }); visibleDialog.show(); + containerView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); } }; @@ -316,7 +348,7 @@ public class StickerPreviewViewer { ((AbsListView) listView).setOnItemClickListener(null); ((AbsListView) listView).requestDisallowInterceptTouchEvent(true); } else if (listView instanceof RecyclerListView) { - ((RecyclerListView) listView).setOnItemClickListener(null); + ((RecyclerListView) listView).setOnItemClickListener((RecyclerListView.OnItemClickListener) null); ((RecyclerListView) listView).requestDisallowInterceptTouchEvent(true); } openStickerPreviewRunnable = null; @@ -401,26 +433,26 @@ public class StickerPreviewViewer { } TLRPC.InputStickerSet newSet = null; - if (isRecent) { - for (int a = 0; a < sticker.attributes.size(); a++) { - TLRPC.DocumentAttribute attribute = sticker.attributes.get(a); - if (attribute instanceof TLRPC.TL_documentAttributeSticker && attribute.stickerset != null) { - newSet = attribute.stickerset; - break; - } + for (int a = 0; a < sticker.attributes.size(); a++) { + TLRPC.DocumentAttribute attribute = sticker.attributes.get(a); + if (attribute instanceof TLRPC.TL_documentAttributeSticker && attribute.stickerset != null) { + newSet = attribute.stickerset; + break; } - if (newSet != null && currentSet != newSet) { - try { - if (visibleDialog != null) { - visibleDialog.setOnDismissListener(null); - visibleDialog.dismiss(); - } - } catch (Exception e) { - FileLog.e(e); + } + //&& (currentSet == null || currentSet.id != newSet.id) + if (newSet != null) { + try { + if (visibleDialog != null) { + visibleDialog.setOnDismissListener(null); + visibleDialog.dismiss(); + visibleDialog = null; } - AndroidUtilities.cancelRunOnUIThread(showSheetRunnable); - AndroidUtilities.runOnUIThread(showSheetRunnable, 2000); + } catch (Exception e) { + FileLog.e(e); } + AndroidUtilities.cancelRunOnUIThread(showSheetRunnable); + AndroidUtilities.runOnUIThread(showSheetRunnable, 1300); } currentSet = newSet; centerImage.setImage(sticker, null, sticker.thumb.location, null, "webp", 1); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/StickersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/StickersActivity.java index 5dc80604f..efad624a8 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/StickersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/StickersActivity.java @@ -409,7 +409,7 @@ public class StickersActivity extends BaseFragment implements NotificationCenter View view = null; switch (viewType) { case 0: - view = new StickerSetCell(mContext); + view = new StickerSetCell(mContext, 1); view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); ((StickerSetCell) view).setOnOptionsClick(new View.OnClickListener() { @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ThemeActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ThemeActivity.java index 6b0118198..3c21528ec 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ThemeActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ThemeActivity.java @@ -14,7 +14,9 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; +import android.os.Build; import android.os.Vibrator; +import android.support.v4.content.FileProvider; import android.text.InputType; import android.util.TypedValue; import android.view.Gravity; @@ -22,7 +24,6 @@ import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; -import android.widget.EditText; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.TextView; @@ -30,6 +31,7 @@ import android.widget.Toast; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.BuildConfig; import org.telegram.messenger.FileLoader; import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; @@ -46,6 +48,7 @@ import org.telegram.ui.Cells.ShadowSectionCell; import org.telegram.ui.Cells.TextInfoPrivacyCell; import org.telegram.ui.Cells.TextSettingsCell; import org.telegram.ui.Cells.ThemeCell; +import org.telegram.ui.Components.EditTextBoldCursor; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.RecyclerListView; import org.telegram.ui.Components.ThemeEditorView; @@ -92,7 +95,7 @@ public class ThemeActivity extends BaseFragment { if (getParentActivity() == null) { return; } - final EditText editText = new EditText(getParentActivity()); + final EditTextBoldCursor editText = new EditTextBoldCursor(getParentActivity()); editText.setBackgroundDrawable(Theme.createEditTextDrawable(getParentActivity(), true)); AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); @@ -124,7 +127,9 @@ public class ThemeActivity extends BaseFragment { editText.setGravity(Gravity.LEFT | Gravity.TOP); editText.setSingleLine(true); editText.setImeOptions(EditorInfo.IME_ACTION_DONE); - AndroidUtilities.clearCursorDrawable(editText); + editText.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + editText.setCursorSize(AndroidUtilities.dp(20)); + editText.setCursorWidth(1.5f); editText.setPadding(0, AndroidUtilities.dp(4), 0, 0); linearLayout.addView(editText, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36, Gravity.TOP | Gravity.LEFT, 24, 6, 24, 0)); editText.setOnEditorActionListener(new TextView.OnEditorActionListener() { @@ -287,7 +292,16 @@ public class ThemeActivity extends BaseFragment { } Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/xml"); - intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(finalFile)); + if (Build.VERSION.SDK_INT >= 24) { + try { + intent.putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(getParentActivity(), BuildConfig.APPLICATION_ID + ".provider", finalFile)); + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } catch (Exception ignore) { + intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(finalFile)); + } + } else { + intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(finalFile)); + } startActivityForResult(Intent.createChooser(intent, LocaleController.getString("ShareFile", R.string.ShareFile)), 500); } catch (Exception e) { FileLog.e(e); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ThemePreviewActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ThemePreviewActivity.java index 824ef2896..435f6a19b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ThemePreviewActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ThemePreviewActivity.java @@ -543,7 +543,7 @@ public class ThemePreviewActivity extends BaseFragment implements NotificationCe public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { View view = null; if (viewType == 0) { - view = new DialogCell(mContext); + view = new DialogCell(mContext, false); } else if (viewType == 1) { view = new LoadingCell(mContext); } @@ -708,7 +708,7 @@ public class ThemePreviewActivity extends BaseFragment implements NotificationCe messageObject.useCustomPhoto = true; messages.add(messageObject); - message = new TLRPC.Message(); + message = new TLRPC.TL_message(); message.message = LocaleController.formatDateChat(date); message.id = 0; message.date = date; @@ -810,6 +810,11 @@ public class ThemePreviewActivity extends BaseFragment implements NotificationCe public void didPressedInstantButton(ChatMessageCell cell, int type) { } + + @Override + public boolean isChatAdminCell(int uid) { + return false; + } }); } else if (viewType == 1) { view = new ChatActionCell(mContext); @@ -869,7 +874,7 @@ public class ThemePreviewActivity extends BaseFragment implements NotificationCe pinnedTop = false; } messageCell.setFullyDraw(true); - messageCell.setMessageObject(message, pinnedBotton, pinnedTop); + messageCell.setMessageObject(message, null, pinnedBotton, pinnedTop); } else if (view instanceof ChatActionCell) { ChatActionCell actionCell = (ChatActionCell) view; actionCell.setMessageObject(message); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/TwoStepVerificationActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/TwoStepVerificationActivity.java index be0e75eec..2e25a0ac1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/TwoStepVerificationActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/TwoStepVerificationActivity.java @@ -24,7 +24,6 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; -import android.widget.EditText; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.ScrollView; @@ -52,6 +51,7 @@ import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.TextInfoPrivacyCell; import org.telegram.ui.Cells.TextSettingsCell; +import org.telegram.ui.Components.EditTextBoldCursor; import org.telegram.ui.Components.EmptyTextProgressView; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.RecyclerListView; @@ -63,7 +63,7 @@ public class TwoStepVerificationActivity extends BaseFragment implements Notific private TextView titleTextView; private TextView bottomTextView; private TextView bottomButton; - private EditText passwordEditText; + private EditTextBoldCursor passwordEditText; private AlertDialog progressDialog; private EmptyTextProgressView emptyView; private ActionBarMenuItem doneItem; @@ -174,7 +174,7 @@ public class TwoStepVerificationActivity extends BaseFragment implements Notific titleTextView.setGravity(Gravity.CENTER_HORIZONTAL); linearLayout.addView(titleTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL, 0, 38, 0, 0)); - passwordEditText = new EditText(context); + passwordEditText = new EditTextBoldCursor(context); passwordEditText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); passwordEditText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); passwordEditText.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); @@ -186,7 +186,9 @@ public class TwoStepVerificationActivity extends BaseFragment implements Notific passwordEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); passwordEditText.setTransformationMethod(PasswordTransformationMethod.getInstance()); passwordEditText.setTypeface(Typeface.DEFAULT); - AndroidUtilities.clearCursorDrawable(passwordEditText); + passwordEditText.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + passwordEditText.setCursorSize(AndroidUtilities.dp(20)); + passwordEditText.setCursorWidth(1.5f); linearLayout.addView(passwordEditText, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36, Gravity.TOP | Gravity.LEFT, 40, 32, 40, 0)); passwordEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override @@ -659,7 +661,7 @@ public class TwoStepVerificationActivity extends BaseFragment implements Notific } if (email.length() > 0) { req.new_settings.flags |= 2; - req.new_settings.email = email; + req.new_settings.email = email.trim(); } } needShowProgress(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/VoIPActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/VoIPActivity.java index 8daf551e3..605a3f7fa 100755 --- a/TMessagesProj/src/main/java/org/telegram/ui/VoIPActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/VoIPActivity.java @@ -17,7 +17,6 @@ import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Activity; -import android.app.Dialog; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -25,10 +24,12 @@ import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.ColorFilter; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.Typeface; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; @@ -36,8 +37,10 @@ import android.graphics.drawable.GradientDrawable; import android.media.AudioManager; import android.os.Build; import android.os.Bundle; +import android.support.annotation.IntRange; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v7.graphics.Palette; -import android.text.Layout; import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.TextPaint; @@ -48,37 +51,32 @@ import android.util.TypedValue; import android.view.Gravity; import android.view.KeyEvent; import android.view.View; -import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; -import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; -import android.widget.Button; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.NumberPicker; -import android.widget.RelativeLayout; import android.widget.ScrollView; import android.widget.TextView; +import android.widget.Toast; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.BuildVars; import org.telegram.messenger.ContactsController; import org.telegram.messenger.Emoji; -import org.telegram.messenger.FileLog; +import org.telegram.messenger.EmojiData; import org.telegram.messenger.ImageReceiver; import org.telegram.messenger.LocaleController; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; -import org.telegram.messenger.UserConfig; import org.telegram.messenger.Utilities; import org.telegram.messenger.voip.EncryptionKeyEmojifier; +import org.telegram.messenger.voip.VoIPBaseService; import org.telegram.messenger.voip.VoIPController; import org.telegram.messenger.voip.VoIPService; import org.telegram.tgnet.TLRPC; -import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BottomSheet; import org.telegram.ui.ActionBar.Theme; @@ -93,13 +91,13 @@ import org.telegram.ui.Components.voip.FabBackgroundDrawable; import org.telegram.ui.Components.voip.VoIPHelper; import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; public class VoIPActivity extends Activity implements VoIPService.StateListener, NotificationCenter.NotificationCenterDelegate{ private static final String TAG = "tg-voip-ui"; private TextView stateText, nameText, stateText2; private TextView durationText; + private TextView brandingText; private View endBtn, acceptBtn, declineBtn, endBtnIcon, cancelBtn; private CheckableImageView spkToggle, micToggle; private ImageView chatBtn; @@ -131,6 +129,8 @@ public class VoIPActivity extends Activity implements VoIPService.StateListener, private FrameLayout content; private boolean retrying; private AnimatorSet retryAnim; + private int signalBarsCount; + private SignalBarsDrawable signalBarsDrawable; @Override protected void onCreate(Bundle savedInstanceState) { @@ -259,6 +259,7 @@ public class VoIPActivity extends Activity implements VoIPService.StateListener, break; } onAudioSettingsChanged(); + VoIPService.getSharedInstance().updateOutputGainControlState(); } }); bldr.show(); @@ -272,6 +273,7 @@ public class VoIPActivity extends Activity implements VoIPService.StateListener, }else{ am.setBluetoothScoOn(checked); } + svc.updateOutputGainControlState(); } }); micToggle.setOnClickListener(new View.OnClickListener() { @@ -480,12 +482,15 @@ public class VoIPActivity extends Activity implements VoIPService.StateListener, Drawable logo=getResources().getDrawable(R.drawable.notification).mutate(); logo.setAlpha(0xCC); logo.setBounds(0, 0, AndroidUtilities.dp(15), AndroidUtilities.dp(15)); - branding.setCompoundDrawables(LocaleController.isRTL ? null : logo, null, LocaleController.isRTL ? logo : null, null); + signalBarsDrawable=new SignalBarsDrawable(); + signalBarsDrawable.setBounds(0, 0, signalBarsDrawable.getIntrinsicWidth(), signalBarsDrawable.getIntrinsicHeight()); + branding.setCompoundDrawables(LocaleController.isRTL ? signalBarsDrawable : logo, null, LocaleController.isRTL ? logo : signalBarsDrawable, null); branding.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); branding.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); branding.setCompoundDrawablePadding(AndroidUtilities.dp(5)); branding.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - content.addView(branding, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP|Gravity.LEFT, 18, 18, 18, 0)); + content.addView(branding, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP|(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), 18, 18, 18, 0)); + brandingText=branding; TextView name=new TextView(this); name.setSingleLine(); @@ -631,7 +636,7 @@ public class VoIPActivity extends Activity implements VoIPService.StateListener, }); //keyEmojiText=new TextView(this); //keyEmojiText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); - content.addView(emojiWrap, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP|Gravity.RIGHT)); + content.addView(emojiWrap, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP|(LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT))); emojiWrap.setOnLongClickListener(new View.OnLongClickListener(){ @Override public boolean onLongClick(View v){ @@ -781,9 +786,9 @@ public class VoIPActivity extends Activity implements VoIPService.StateListener, return; IdenticonDrawable img = new IdenticonDrawable(); img.setColors(new int[]{0x00FFFFFF, 0xFFFFFFFF, 0x99FFFFFF, 0x33FFFFFF}); - TLRPC.EncryptedChat encryptedChat = new TLRPC.EncryptedChat(); - try{ - ByteArrayOutputStream buf=new ByteArrayOutputStream(); + TLRPC.EncryptedChat encryptedChat = new TLRPC.TL_encryptedChat(); + try { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); buf.write(VoIPService.getSharedInstance().getEncryptionKey()); buf.write(VoIPService.getSharedInstance().getGA()); encryptedChat.auth_key = buf.toByteArray(); @@ -791,10 +796,12 @@ public class VoIPActivity extends Activity implements VoIPService.StateListener, byte[] sha256 = Utilities.computeSHA256(encryptedChat.auth_key, 0, encryptedChat.auth_key.length); String[] emoji=EncryptionKeyEmojifier.emojifyForCall(sha256); //keyEmojiText.setText(Emoji.replaceEmoji(TextUtils.join(" ", emoji), keyEmojiText.getPaint().getFontMetricsInt(), AndroidUtilities.dp(32), false)); - for(int i=0;i<4;i++){ - Drawable drawable=Emoji.getEmojiDrawable(emoji[i]); - drawable.setBounds(0, 0, AndroidUtilities.dp(22), AndroidUtilities.dp(22)); - keyEmojiViews[i].setImageDrawable(drawable); + for(int i=0;i<4;i++) { + Drawable drawable = Emoji.getEmojiDrawable(emoji[i]); + if (drawable != null) { + drawable.setBounds(0, 0, AndroidUtilities.dp(22), AndroidUtilities.dp(22)); + keyEmojiViews[i].setImageDrawable(drawable); + } } } @@ -823,6 +830,7 @@ public class VoIPActivity extends Activity implements VoIPService.StateListener, private void showDebugAlert() { if(VoIPService.getSharedInstance()==null) return; + VoIPService.getSharedInstance().forceRating(); final LinearLayout debugOverlay=new LinearLayout(this); debugOverlay.setOrientation(LinearLayout.VERTICAL); debugOverlay.setBackgroundColor(0xCC000000); @@ -1075,6 +1083,7 @@ public class VoIPActivity extends Activity implements VoIPService.StateListener, public void run() { boolean wasFirstStateChange=firstStateChange; if (firstStateChange) { + spkToggle.setChecked(((AudioManager)getSystemService(AUDIO_SERVICE)).isSpeakerphoneOn()); if (isIncomingWaiting = state == VoIPService.STATE_WAITING_INCOMING) { swipeViewsWrap.setVisibility(View.VISIBLE); endBtn.setVisibility(View.GONE); @@ -1100,7 +1109,7 @@ public class VoIPActivity extends Activity implements VoIPService.StateListener, firstStateChange = false; } - if (isIncomingWaiting && state != VoIPService.STATE_WAITING_INCOMING && state!=VoIPService.STATE_ENDED && state!=VoIPService.STATE_HANGING_UP) { + if (isIncomingWaiting && state != VoIPService.STATE_WAITING_INCOMING && state!=VoIPBaseService.STATE_ENDED && state!=VoIPService.STATE_HANGING_UP) { isIncomingWaiting = false; if (!didAcceptFromHere) callAccepted(); @@ -1123,7 +1132,7 @@ public class VoIPActivity extends Activity implements VoIPService.StateListener, setStateTextAnimated(LocaleController.getString("VoipHangingUp", R.string.VoipHangingUp), true); endBtnIcon.setAlpha(.5f); endBtn.setEnabled(false); - } else if (state == VoIPService.STATE_ENDED) { + } else if (state == VoIPBaseService.STATE_ENDED) { setStateTextAnimated(LocaleController.getString("VoipCallEnded", R.string.VoipCallEnded), false); stateText.postDelayed(new Runnable() { @Override @@ -1191,6 +1200,18 @@ public class VoIPActivity extends Activity implements VoIPService.StateListener, }, 1000); } } + brandingText.invalidate(); + } + }); + } + + @Override + public void onSignalBarsCountChanged(final int count){ + runOnUiThread(new Runnable(){ + @Override + public void run(){ + signalBarsCount=count; + brandingText.invalidate(); } }); } @@ -1442,4 +1463,51 @@ public class VoIPActivity extends Activity implements VoIPService.StateListener, tp.setAlpha(alpha); } } + + private class SignalBarsDrawable extends Drawable{ + + private int[] barHeights={AndroidUtilities.dp(3), AndroidUtilities.dp(6), AndroidUtilities.dp(9), AndroidUtilities.dp(12)}; + private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG); + private RectF rect=new RectF(); + private int offsetStart=6; + + @Override + public void draw(@NonNull Canvas canvas){ + if(callState!=VoIPService.STATE_ESTABLISHED && callState!=VoIPService.STATE_RECONNECTING) + return; + paint.setColor(0xFFFFFFFF); + int x=getBounds().left+AndroidUtilities.dp(LocaleController.isRTL ? 0 : offsetStart); + int y=getBounds().top; + for(int i=0;i<4;i++){ + paint.setAlpha(i+1<=signalBarsCount ? 242 : 102); + rect.set(x+AndroidUtilities.dp(4*i), y+getIntrinsicHeight()-barHeights[i], x+AndroidUtilities.dp(4)*i+AndroidUtilities.dp(3), y+getIntrinsicHeight()); + canvas.drawRoundRect(rect, AndroidUtilities.dp(.3f), AndroidUtilities.dp(.3f), paint); + } + } + + @Override + public void setAlpha(@IntRange(from=0, to=255) int alpha){ + + } + + @Override + public void setColorFilter(@Nullable ColorFilter colorFilter){ + + } + + @Override + public int getIntrinsicWidth(){ + return AndroidUtilities.dp(15+offsetStart); + } + + @Override + public int getIntrinsicHeight(){ + return AndroidUtilities.dp(12); + } + + @Override + public int getOpacity(){ + return PixelFormat.TRANSLUCENT; + } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/WebviewActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/WebviewActivity.java index 714ec6937..06067d40f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/WebviewActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/WebviewActivity.java @@ -14,8 +14,11 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.app.Activity; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.SharedPreferences; +import android.net.Uri; import android.os.Build; import android.text.TextUtils; import android.view.View; @@ -83,7 +86,7 @@ public class WebviewActivity extends BaseFragment { currentMessageObject.messageOwner.with_my_score = true; break; } - showDialog(new ShareAlert(getParentActivity(), currentMessageObject, null, false, linkToCopy, false)); + showDialog(ShareAlert.createShareAlert(getParentActivity(), currentMessageObject, null, false, linkToCopy, false)); } }); } @@ -144,7 +147,7 @@ public class WebviewActivity extends BaseFragment { finishFragment(); } else if (id == share) { currentMessageObject.messageOwner.with_my_score = false; - showDialog(new ShareAlert(getParentActivity(), currentMessageObject, null, false, linkToCopy, false)); + showDialog(ShareAlert.createShareAlert(getParentActivity(), currentMessageObject, null, false, linkToCopy, false)); } else if (id == open_in) { openGameInBrowser(currentUrl, currentMessageObject, getParentActivity(), short_param, currentBot); } @@ -174,11 +177,41 @@ public class WebviewActivity extends BaseFragment { } webView.setWebViewClient(new WebViewClient() { + + private boolean isInternalUrl(String url) { + if (TextUtils.isEmpty(url)) { + return false; + } + Uri uri = Uri.parse(url); + if ("tg".equals(uri.getScheme())) { + finishFragment(false); + try { + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + ComponentName componentName = new ComponentName(ApplicationLoader.applicationContext.getPackageName(), LaunchActivity.class.getName()); + intent.setComponent(componentName); + intent.putExtra(android.provider.Browser.EXTRA_APPLICATION_ID, ApplicationLoader.applicationContext.getPackageName()); + ApplicationLoader.applicationContext.startActivity(intent); + } catch (Exception e) { + FileLog.e(e); + } + return true; + } + return false; + } + @Override public void onLoadResource(WebView view, String url) { + if (isInternalUrl(url)) { + return; + } super.onLoadResource(view, url); } + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + return isInternalUrl(url) || super.shouldOverrideUrlLoading(view, url); + } + @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); diff --git a/TMessagesProj/src/main/res/drawable-hdpi/animationpin.png b/TMessagesProj/src/main/res/drawable-hdpi/animationpin.png new file mode 100644 index 000000000..9ceaf6d52 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/animationpin.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/animationpinleft.png b/TMessagesProj/src/main/res/drawable-hdpi/animationpinleft.png new file mode 100644 index 000000000..d6706666b Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/animationpinleft.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/animationpinright.png b/TMessagesProj/src/main/res/drawable-hdpi/animationpinright.png new file mode 100644 index 000000000..597d4ab36 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/animationpinright.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/bookmark_large.png b/TMessagesProj/src/main/res/drawable-hdpi/bookmark_large.png new file mode 100755 index 000000000..e59b4a9b8 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/bookmark_large.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/btn_send_location_down.9.png b/TMessagesProj/src/main/res/drawable-hdpi/btn_send_location_down.9.png deleted file mode 100755 index a56c265ca..000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/btn_send_location_down.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/btn_send_location_up.9.png b/TMessagesProj/src/main/res/drawable-hdpi/btn_send_location_up.9.png deleted file mode 100755 index c0d5850a4..000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/btn_send_location_up.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/cloud.png b/TMessagesProj/src/main/res/drawable-hdpi/cloud.png deleted file mode 100755 index fabb17bfb..000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/cloud.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_fave.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_fave.png new file mode 100755 index 000000000..ef4fe8256 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_fave.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_unfave.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_unfave.png new file mode 100755 index 000000000..bbc49e436 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_unfave.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_masks_recent2.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_masks_recent2.png deleted file mode 100644 index 927aabcd9..000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_masks_recent2.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_more.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_more.png deleted file mode 100644 index c6e1eab37..000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_more.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_sad.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_sad.png new file mode 100755 index 000000000..804475310 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_sad.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_vpn_key_white_24dp.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_vpn_key_white_24dp.png deleted file mode 100755 index 0b6a1dfd0..000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_vpn_key_white_24dp.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/list_focused_holo.9.png b/TMessagesProj/src/main/res/drawable-hdpi/list_focused_holo.9.png deleted file mode 100644 index 555270842..000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/list_focused_holo.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/livelocationpin.png b/TMessagesProj/src/main/res/drawable-hdpi/livelocationpin.png new file mode 100644 index 000000000..0becef33d Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/livelocationpin.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/livepin.png b/TMessagesProj/src/main/res/drawable-hdpi/livepin.png new file mode 100644 index 000000000..6f2692940 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/livepin.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/managers.png b/TMessagesProj/src/main/res/drawable-hdpi/managers.png deleted file mode 100755 index ccf2c72fa..000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/managers.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/mentionbutton.png b/TMessagesProj/src/main/res/drawable-hdpi/mentionbutton.png new file mode 100644 index 000000000..c76eddb1c Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/mentionbutton.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/mentionchatslist.png b/TMessagesProj/src/main/res/drawable-hdpi/mentionchatslist.png new file mode 100644 index 000000000..c89f968f5 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/mentionchatslist.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/menu_saved.png b/TMessagesProj/src/main/res/drawable-hdpi/menu_saved.png new file mode 100755 index 000000000..083bb80dd Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/menu_saved.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/message_arrow.png b/TMessagesProj/src/main/res/drawable-hdpi/message_arrow.png new file mode 100755 index 000000000..c27c172d0 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/message_arrow.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_round_cancel_l.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_cancel_l.png deleted file mode 100755 index 253eecc90..000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/msg_round_cancel_l.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_round_cancel_s.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_cancel_s.png deleted file mode 100755 index aac1d5fa6..000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/msg_round_cancel_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_round_load_l.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_load_l.png deleted file mode 100755 index 9224129fb..000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/msg_round_load_l.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_round_load_s.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_load_s.png deleted file mode 100755 index 07f0fea6b..000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/msg_round_load_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_round_pause_s.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_pause_s.png deleted file mode 100755 index fe038fd26..000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/msg_round_pause_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_round_play_l.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_play_l.png deleted file mode 100755 index 6fcf16da9..000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/msg_round_play_l.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_round_play_s.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_play_s.png deleted file mode 100755 index 0f9dbe8de..000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/msg_round_play_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/music_reverse.png b/TMessagesProj/src/main/res/drawable-hdpi/music_reverse.png new file mode 100755 index 000000000..b805bdc94 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/music_reverse.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/photo_text.png b/TMessagesProj/src/main/res/drawable-hdpi/photo_text.png deleted file mode 100755 index 1009bbd79..000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/photo_text.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/photoborder.9.png b/TMessagesProj/src/main/res/drawable-hdpi/photoborder.9.png deleted file mode 100644 index 3eb03a164..000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/photoborder.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/photos_group.png b/TMessagesProj/src/main/res/drawable-hdpi/photos_group.png new file mode 100755 index 000000000..d6a33fe20 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/photos_group.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/phototime2.9.png b/TMessagesProj/src/main/res/drawable-hdpi/phototime2.9.png deleted file mode 100755 index 143a0fbdc..000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/phototime2.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/phototime2_b.9.png b/TMessagesProj/src/main/res/drawable-hdpi/phototime2_b.9.png deleted file mode 100755 index a0e4a272b..000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/phototime2_b.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pin.png b/TMessagesProj/src/main/res/drawable-hdpi/pin.png old mode 100755 new mode 100644 index e3a4f7962..5eaceb350 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/pin.png and b/TMessagesProj/src/main/res/drawable-hdpi/pin.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pl_back.png b/TMessagesProj/src/main/res/drawable-hdpi/pl_back.png deleted file mode 100644 index 8eb567adc..000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/pl_back.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pl_next.png b/TMessagesProj/src/main/res/drawable-hdpi/pl_next.png index 6617d44de..e66cb1e9a 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/pl_next.png and b/TMessagesProj/src/main/res/drawable-hdpi/pl_next.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pl_pause.png b/TMessagesProj/src/main/res/drawable-hdpi/pl_pause.png index c83137501..1c3af4310 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/pl_pause.png and b/TMessagesProj/src/main/res/drawable-hdpi/pl_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pl_play.png b/TMessagesProj/src/main/res/drawable-hdpi/pl_play.png index 1d2312f70..c6b899407 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/pl_play.png and b/TMessagesProj/src/main/res/drawable-hdpi/pl_play.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pl_previous.png b/TMessagesProj/src/main/res/drawable-hdpi/pl_previous.png index d9662db4a..6936afb03 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/pl_previous.png and b/TMessagesProj/src/main/res/drawable-hdpi/pl_previous.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pl_repeat.png b/TMessagesProj/src/main/res/drawable-hdpi/pl_repeat.png old mode 100755 new mode 100644 index 4450600ce..c2c227c32 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/pl_repeat.png and b/TMessagesProj/src/main/res/drawable-hdpi/pl_repeat.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pl_repeat1.png b/TMessagesProj/src/main/res/drawable-hdpi/pl_repeat1.png old mode 100755 new mode 100644 index 30357ce84..b5dc34773 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/pl_repeat1.png and b/TMessagesProj/src/main/res/drawable-hdpi/pl_repeat1.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pl_shuffle.png b/TMessagesProj/src/main/res/drawable-hdpi/pl_shuffle.png index fcd1cac3e..534637cf5 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/pl_shuffle.png and b/TMessagesProj/src/main/res/drawable-hdpi/pl_shuffle.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/rec.png b/TMessagesProj/src/main/res/drawable-hdpi/rec.png deleted file mode 100755 index a3ed5835a..000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/rec.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/search_light.9.png b/TMessagesProj/src/main/res/drawable-hdpi/search_light.9.png deleted file mode 100644 index 677298004..000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/search_light.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/search_light_activated.9.png b/TMessagesProj/src/main/res/drawable-hdpi/search_light_activated.9.png deleted file mode 100644 index 7fb04f901..000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/search_light_activated.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/sharedmedia.png b/TMessagesProj/src/main/res/drawable-hdpi/sharedmedia.png deleted file mode 100755 index 532e67de0..000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/sharedmedia.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/smallanimationpin.png b/TMessagesProj/src/main/res/drawable-hdpi/smallanimationpin.png new file mode 100644 index 000000000..fa0e0d346 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/smallanimationpin.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/smallanimationpinleft.png b/TMessagesProj/src/main/res/drawable-hdpi/smallanimationpinleft.png new file mode 100644 index 000000000..53e37c78c Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/smallanimationpinleft.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/smallanimationpinright.png b/TMessagesProj/src/main/res/drawable-hdpi/smallanimationpinright.png new file mode 100644 index 000000000..23825e3c5 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/smallanimationpinright.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/staredstickerstab.png b/TMessagesProj/src/main/res/drawable-hdpi/staredstickerstab.png new file mode 100644 index 000000000..0254c7118 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/staredstickerstab.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/stickers_favorite.png b/TMessagesProj/src/main/res/drawable-hdpi/stickers_favorite.png new file mode 100644 index 000000000..286f0da9c Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/stickers_favorite.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/stickers_pack.png b/TMessagesProj/src/main/res/drawable-hdpi/stickers_pack.png new file mode 100644 index 000000000..e7bb3e42d Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/stickers_pack.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/stickers_send.png b/TMessagesProj/src/main/res/drawable-hdpi/stickers_send.png new file mode 100644 index 000000000..4e9cae63f Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/stickers_send.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/stickers_unfavorite.png b/TMessagesProj/src/main/res/drawable-hdpi/stickers_unfavorite.png new file mode 100644 index 000000000..f6216a101 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/stickers_unfavorite.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/stickersclose.png b/TMessagesProj/src/main/res/drawable-hdpi/stickersclose.png new file mode 100644 index 000000000..0e6c2ddab Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/stickersclose.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/stickerset_close.png b/TMessagesProj/src/main/res/drawable-hdpi/stickerset_close.png new file mode 100644 index 000000000..1dea2df7d Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/stickerset_close.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/usersearch.png b/TMessagesProj/src/main/res/drawable-hdpi/usersearch.png new file mode 100644 index 000000000..540c9687c Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/usersearch.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/video_copy.png b/TMessagesProj/src/main/res/drawable-hdpi/video_copy.png new file mode 100644 index 000000000..3ea2b169a Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/video_copy.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/video_edit_play.png b/TMessagesProj/src/main/res/drawable-hdpi/video_edit_play.png deleted file mode 100755 index 01a4ef85a..000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/video_edit_play.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/video_pip.png b/TMessagesProj/src/main/res/drawable-hdpi/video_pip.png new file mode 100644 index 000000000..2b2c17734 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/video_pip.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/video_play.png b/TMessagesProj/src/main/res/drawable-hdpi/video_play.png deleted file mode 100644 index ce78ffabe..000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/video_play.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/video_time_l.png b/TMessagesProj/src/main/res/drawable-hdpi/video_time_l.png deleted file mode 100755 index 2f556db20..000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/video_time_l.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/video_time_r.png b/TMessagesProj/src/main/res/drawable-hdpi/video_time_r.png deleted file mode 100755 index d76238532..000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/video_time_r.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/animationpin.png b/TMessagesProj/src/main/res/drawable-mdpi/animationpin.png new file mode 100644 index 000000000..27a37e758 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/animationpin.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/animationpinleft.png b/TMessagesProj/src/main/res/drawable-mdpi/animationpinleft.png new file mode 100644 index 000000000..42247be21 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/animationpinleft.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/animationpinright.png b/TMessagesProj/src/main/res/drawable-mdpi/animationpinright.png new file mode 100644 index 000000000..4c09455f7 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/animationpinright.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/boog_group.png b/TMessagesProj/src/main/res/drawable-mdpi/book_group.png similarity index 100% rename from TMessagesProj/src/main/res/drawable-mdpi/boog_group.png rename to TMessagesProj/src/main/res/drawable-mdpi/book_group.png diff --git a/TMessagesProj/src/main/res/drawable-mdpi/bookmark_large.png b/TMessagesProj/src/main/res/drawable-mdpi/bookmark_large.png new file mode 100755 index 000000000..3391286e8 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/bookmark_large.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/btn_send_location_down.9.png b/TMessagesProj/src/main/res/drawable-mdpi/btn_send_location_down.9.png deleted file mode 100755 index 64a9c8672..000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/btn_send_location_down.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/btn_send_location_up.9.png b/TMessagesProj/src/main/res/drawable-mdpi/btn_send_location_up.9.png deleted file mode 100755 index eb2bc09db..000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/btn_send_location_up.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/cloud.png b/TMessagesProj/src/main/res/drawable-mdpi/cloud.png deleted file mode 100755 index 8525e949a..000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/cloud.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_fave.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_fave.png new file mode 100755 index 000000000..883e9aa73 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_fave.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_unfave.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_unfave.png new file mode 100755 index 000000000..a38f06a62 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_unfave.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_masks_recent2.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_masks_recent2.png deleted file mode 100644 index 17fa6a63a..000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_masks_recent2.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_more.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_more.png deleted file mode 100644 index 141a0fe33..000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_more.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_sad.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_sad.png new file mode 100755 index 000000000..242bd564d Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_sad.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_vpn_key_white_24dp.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_vpn_key_white_24dp.png deleted file mode 100755 index 21ba92179..000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_vpn_key_white_24dp.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/list_focused_holo.9.png b/TMessagesProj/src/main/res/drawable-mdpi/list_focused_holo.9.png deleted file mode 100644 index 00f05d8c9..000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/list_focused_holo.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/livelocationpin.png b/TMessagesProj/src/main/res/drawable-mdpi/livelocationpin.png new file mode 100644 index 000000000..8d1e07860 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/livelocationpin.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/livepin.png b/TMessagesProj/src/main/res/drawable-mdpi/livepin.png new file mode 100644 index 000000000..50e3a2d3c Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/livepin.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/managers.png b/TMessagesProj/src/main/res/drawable-mdpi/managers.png deleted file mode 100755 index b66987884..000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/managers.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/mentionbutton.png b/TMessagesProj/src/main/res/drawable-mdpi/mentionbutton.png new file mode 100644 index 000000000..03a8a06b9 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/mentionbutton.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/mentionchatslist.png b/TMessagesProj/src/main/res/drawable-mdpi/mentionchatslist.png new file mode 100644 index 000000000..dcc4d3e7e Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/mentionchatslist.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/menu_saved.png b/TMessagesProj/src/main/res/drawable-mdpi/menu_saved.png new file mode 100755 index 000000000..2b2c2ddaa Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/menu_saved.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/message_arrow.png b/TMessagesProj/src/main/res/drawable-mdpi/message_arrow.png new file mode 100755 index 000000000..8d6a060d4 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/message_arrow.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_round_cancel_l.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_cancel_l.png deleted file mode 100755 index 920a8969c..000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/msg_round_cancel_l.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_round_cancel_s.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_cancel_s.png deleted file mode 100755 index 775a201e7..000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/msg_round_cancel_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_round_load_l.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_load_l.png deleted file mode 100755 index fb790662b..000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/msg_round_load_l.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_round_load_s.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_load_s.png deleted file mode 100755 index ce4f71480..000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/msg_round_load_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_round_pause_s.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_pause_s.png deleted file mode 100755 index f95f61f40..000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/msg_round_pause_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_round_play_l.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_play_l.png deleted file mode 100755 index cbb415861..000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/msg_round_play_l.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_round_play_s.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_play_s.png deleted file mode 100755 index 0a40021c2..000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/msg_round_play_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/music_reverse.png b/TMessagesProj/src/main/res/drawable-mdpi/music_reverse.png new file mode 100755 index 000000000..248826ba9 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/music_reverse.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/photo_text.png b/TMessagesProj/src/main/res/drawable-mdpi/photo_text.png deleted file mode 100755 index b1301d532..000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/photo_text.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/photoborder.9.png b/TMessagesProj/src/main/res/drawable-mdpi/photoborder.9.png deleted file mode 100644 index 75b581198..000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/photoborder.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/photos_group.png b/TMessagesProj/src/main/res/drawable-mdpi/photos_group.png new file mode 100755 index 000000000..32fad9748 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/photos_group.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/phototime2.9.png b/TMessagesProj/src/main/res/drawable-mdpi/phototime2.9.png deleted file mode 100755 index 584a253b0..000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/phototime2.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/phototime2_b.9.png b/TMessagesProj/src/main/res/drawable-mdpi/phototime2_b.9.png deleted file mode 100755 index 59f83e02f..000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/phototime2_b.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pin.png b/TMessagesProj/src/main/res/drawable-mdpi/pin.png old mode 100755 new mode 100644 index 8aba83c90..4ef2499c7 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pin.png and b/TMessagesProj/src/main/res/drawable-mdpi/pin.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pl_next.png b/TMessagesProj/src/main/res/drawable-mdpi/pl_next.png index 2252cc911..361102ebe 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pl_next.png and b/TMessagesProj/src/main/res/drawable-mdpi/pl_next.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pl_pause.png b/TMessagesProj/src/main/res/drawable-mdpi/pl_pause.png index f2e357b1b..977e79f88 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pl_pause.png and b/TMessagesProj/src/main/res/drawable-mdpi/pl_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pl_play.png b/TMessagesProj/src/main/res/drawable-mdpi/pl_play.png index 330e5f4f2..7ade0e77c 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pl_play.png and b/TMessagesProj/src/main/res/drawable-mdpi/pl_play.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pl_previous.png b/TMessagesProj/src/main/res/drawable-mdpi/pl_previous.png index 13bd58e9e..2f48d1930 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pl_previous.png and b/TMessagesProj/src/main/res/drawable-mdpi/pl_previous.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pl_repeat.png b/TMessagesProj/src/main/res/drawable-mdpi/pl_repeat.png old mode 100755 new mode 100644 index 5c196f269..d76515010 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pl_repeat.png and b/TMessagesProj/src/main/res/drawable-mdpi/pl_repeat.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pl_repeat1.png b/TMessagesProj/src/main/res/drawable-mdpi/pl_repeat1.png old mode 100755 new mode 100644 index 36d40fa18..f53107316 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pl_repeat1.png and b/TMessagesProj/src/main/res/drawable-mdpi/pl_repeat1.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pl_shuffle.png b/TMessagesProj/src/main/res/drawable-mdpi/pl_shuffle.png index 11a6e7d6f..acad2cf25 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pl_shuffle.png and b/TMessagesProj/src/main/res/drawable-mdpi/pl_shuffle.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/rec.png b/TMessagesProj/src/main/res/drawable-mdpi/rec.png deleted file mode 100755 index 106e9f4a7..000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/rec.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/search_light.9.png b/TMessagesProj/src/main/res/drawable-mdpi/search_light.9.png deleted file mode 100644 index ee38ad349..000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/search_light.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/search_light_activated.9.png b/TMessagesProj/src/main/res/drawable-mdpi/search_light_activated.9.png deleted file mode 100644 index fe325ddec..000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/search_light_activated.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/sharedmedia.png b/TMessagesProj/src/main/res/drawable-mdpi/sharedmedia.png deleted file mode 100755 index cc21392b4..000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/sharedmedia.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/smallanimationpin.png b/TMessagesProj/src/main/res/drawable-mdpi/smallanimationpin.png new file mode 100644 index 000000000..129d223c7 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/smallanimationpin.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/smallanimationpinleft.png b/TMessagesProj/src/main/res/drawable-mdpi/smallanimationpinleft.png new file mode 100644 index 000000000..2cf1904cb Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/smallanimationpinleft.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/smallanimationpinright.png b/TMessagesProj/src/main/res/drawable-mdpi/smallanimationpinright.png new file mode 100644 index 000000000..baf812b84 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/smallanimationpinright.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/staredstickerstab.png b/TMessagesProj/src/main/res/drawable-mdpi/staredstickerstab.png new file mode 100644 index 000000000..1f7ea4c0a Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/staredstickerstab.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/stickers_favorite.png b/TMessagesProj/src/main/res/drawable-mdpi/stickers_favorite.png new file mode 100644 index 000000000..d34c70896 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/stickers_favorite.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pl_back.png b/TMessagesProj/src/main/res/drawable-mdpi/stickers_pack.png old mode 100755 new mode 100644 similarity index 55% rename from TMessagesProj/src/main/res/drawable-mdpi/pl_back.png rename to TMessagesProj/src/main/res/drawable-mdpi/stickers_pack.png index 4a9ebd193..f1111970e Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pl_back.png and b/TMessagesProj/src/main/res/drawable-mdpi/stickers_pack.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/stickers_send.png b/TMessagesProj/src/main/res/drawable-mdpi/stickers_send.png new file mode 100644 index 000000000..9aaa32cd2 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/stickers_send.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/stickers_unfavorite.png b/TMessagesProj/src/main/res/drawable-mdpi/stickers_unfavorite.png new file mode 100644 index 000000000..d099b43c7 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/stickers_unfavorite.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/stickersclose.png b/TMessagesProj/src/main/res/drawable-mdpi/stickersclose.png new file mode 100644 index 000000000..d231bbb94 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/stickersclose.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/stickerset_close.png b/TMessagesProj/src/main/res/drawable-mdpi/stickerset_close.png new file mode 100644 index 000000000..9b6095fa0 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/stickerset_close.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/usersearch.png b/TMessagesProj/src/main/res/drawable-mdpi/usersearch.png new file mode 100644 index 000000000..67cd30bd6 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/usersearch.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/video_copy.png b/TMessagesProj/src/main/res/drawable-mdpi/video_copy.png new file mode 100644 index 000000000..de6bb176b Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/video_copy.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/video_edit_play.png b/TMessagesProj/src/main/res/drawable-mdpi/video_edit_play.png deleted file mode 100755 index cf2116a79..000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/video_edit_play.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/video_pip.png b/TMessagesProj/src/main/res/drawable-mdpi/video_pip.png new file mode 100644 index 000000000..83fc887a8 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/video_pip.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/video_play.png b/TMessagesProj/src/main/res/drawable-mdpi/video_play.png deleted file mode 100644 index 9e930642f..000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/video_play.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/video_time_l.png b/TMessagesProj/src/main/res/drawable-mdpi/video_time_l.png deleted file mode 100755 index 13d9a3649..000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/video_time_l.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/video_time_r.png b/TMessagesProj/src/main/res/drawable-mdpi/video_time_r.png deleted file mode 100755 index ed74df2b7..000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/video_time_r.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/animationpin.png b/TMessagesProj/src/main/res/drawable-xhdpi/animationpin.png new file mode 100644 index 000000000..d14749d00 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/animationpin.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/animationpinleft.png b/TMessagesProj/src/main/res/drawable-xhdpi/animationpinleft.png new file mode 100644 index 000000000..9d13a3aea Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/animationpinleft.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/animationpinright.png b/TMessagesProj/src/main/res/drawable-xhdpi/animationpinright.png new file mode 100644 index 000000000..babf08563 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/animationpinright.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/bookmark_large.png b/TMessagesProj/src/main/res/drawable-xhdpi/bookmark_large.png new file mode 100755 index 000000000..bd5af322f Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/bookmark_large.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/btn_send_location_down.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/btn_send_location_down.9.png deleted file mode 100755 index ae9783b44..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/btn_send_location_down.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/btn_send_location_up.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/btn_send_location_up.9.png deleted file mode 100755 index c8f16fe69..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/btn_send_location_up.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/cloud.png b/TMessagesProj/src/main/res/drawable-xhdpi/cloud.png deleted file mode 100755 index f2b27823e..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/cloud.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_fave.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_fave.png new file mode 100755 index 000000000..2ee3d2d06 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_fave.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_unfave.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_unfave.png new file mode 100755 index 000000000..73da70d23 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_unfave.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_masks_recent2.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_masks_recent2.png deleted file mode 100755 index b43f836fd..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_masks_recent2.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_more.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_more.png deleted file mode 100644 index fc78d7e3f..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_more.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_sad.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_sad.png new file mode 100755 index 000000000..0fee4d667 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_sad.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_vpn_key_white_24dp.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_vpn_key_white_24dp.png deleted file mode 100755 index 93e7b0712..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_vpn_key_white_24dp.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/list_focused_holo.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/list_focused_holo.9.png deleted file mode 100644 index b545f8e57..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/list_focused_holo.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/livelocationpin.png b/TMessagesProj/src/main/res/drawable-xhdpi/livelocationpin.png new file mode 100644 index 000000000..d2263b4b7 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/livelocationpin.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/livepin.png b/TMessagesProj/src/main/res/drawable-xhdpi/livepin.png new file mode 100644 index 000000000..eb714bf8c Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/livepin.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/managers.png b/TMessagesProj/src/main/res/drawable-xhdpi/managers.png deleted file mode 100755 index f57278000..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/managers.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/mentionbutton.png b/TMessagesProj/src/main/res/drawable-xhdpi/mentionbutton.png new file mode 100644 index 000000000..681e2f3b1 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/mentionbutton.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/mentionchatslist.png b/TMessagesProj/src/main/res/drawable-xhdpi/mentionchatslist.png new file mode 100644 index 000000000..b30732e45 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/mentionchatslist.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/menu_saved.png b/TMessagesProj/src/main/res/drawable-xhdpi/menu_saved.png new file mode 100755 index 000000000..2e6fd9bfe Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/menu_saved.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/message_arrow.png b/TMessagesProj/src/main/res/drawable-xhdpi/message_arrow.png new file mode 100755 index 000000000..ae2ec11d0 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/message_arrow.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_cancel_l.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_cancel_l.png deleted file mode 100755 index 07cbd880d..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_cancel_l.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_cancel_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_cancel_s.png deleted file mode 100755 index 7ea1aa6d7..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_cancel_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_load_l.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_load_l.png deleted file mode 100755 index 913074707..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_load_l.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_load_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_load_s.png deleted file mode 100755 index 4215c60cb..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_load_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_pause_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_pause_s.png deleted file mode 100755 index f948b7aab..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_pause_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_play_l.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_play_l.png deleted file mode 100755 index 6944b3fd6..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_play_l.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_play_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_play_s.png deleted file mode 100755 index 9549ed9b0..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_play_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/music_reverse.png b/TMessagesProj/src/main/res/drawable-xhdpi/music_reverse.png new file mode 100755 index 000000000..970652465 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/music_reverse.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/photo_text.png b/TMessagesProj/src/main/res/drawable-xhdpi/photo_text.png deleted file mode 100755 index 4faf7715a..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/photo_text.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/photoborder.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/photoborder.9.png deleted file mode 100644 index 77ba09d40..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/photoborder.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/photos_group.png b/TMessagesProj/src/main/res/drawable-xhdpi/photos_group.png new file mode 100755 index 000000000..1cb0b933c Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/photos_group.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/phototime2.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/phototime2.9.png deleted file mode 100755 index 8ccee4710..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/phototime2.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/phototime2_b.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/phototime2_b.9.png deleted file mode 100755 index 504d8397a..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/phototime2_b.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pin.png b/TMessagesProj/src/main/res/drawable-xhdpi/pin.png old mode 100755 new mode 100644 index 46300d17d..314f0b24a Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/pin.png and b/TMessagesProj/src/main/res/drawable-xhdpi/pin.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pl_back.png b/TMessagesProj/src/main/res/drawable-xhdpi/pl_back.png deleted file mode 100644 index 5be0d0c96..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/pl_back.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pl_next.png b/TMessagesProj/src/main/res/drawable-xhdpi/pl_next.png index 2471be4ec..7fcb3e6ec 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/pl_next.png and b/TMessagesProj/src/main/res/drawable-xhdpi/pl_next.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pl_pause.png b/TMessagesProj/src/main/res/drawable-xhdpi/pl_pause.png index 98e88278a..06a465338 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/pl_pause.png and b/TMessagesProj/src/main/res/drawable-xhdpi/pl_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pl_play.png b/TMessagesProj/src/main/res/drawable-xhdpi/pl_play.png index 5d23c88b8..916d3d136 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/pl_play.png and b/TMessagesProj/src/main/res/drawable-xhdpi/pl_play.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pl_previous.png b/TMessagesProj/src/main/res/drawable-xhdpi/pl_previous.png index 293d9fc11..b592bfc4d 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/pl_previous.png and b/TMessagesProj/src/main/res/drawable-xhdpi/pl_previous.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pl_repeat.png b/TMessagesProj/src/main/res/drawable-xhdpi/pl_repeat.png old mode 100755 new mode 100644 index 2f36cc7b4..ef720019c Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/pl_repeat.png and b/TMessagesProj/src/main/res/drawable-xhdpi/pl_repeat.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pl_repeat1.png b/TMessagesProj/src/main/res/drawable-xhdpi/pl_repeat1.png old mode 100755 new mode 100644 index 785e0d712..7f31bac08 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/pl_repeat1.png and b/TMessagesProj/src/main/res/drawable-xhdpi/pl_repeat1.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pl_shuffle.png b/TMessagesProj/src/main/res/drawable-xhdpi/pl_shuffle.png index e7af0c031..55eb5dca1 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/pl_shuffle.png and b/TMessagesProj/src/main/res/drawable-xhdpi/pl_shuffle.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/rec.png b/TMessagesProj/src/main/res/drawable-xhdpi/rec.png deleted file mode 100755 index 9c03e36c4..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/rec.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/search_light.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/search_light.9.png deleted file mode 100644 index 71be4da7f..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/search_light.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/search_light_activated.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/search_light_activated.9.png deleted file mode 100644 index ce495c020..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/search_light_activated.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/sharedmedia.png b/TMessagesProj/src/main/res/drawable-xhdpi/sharedmedia.png deleted file mode 100755 index ad1203f6d..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/sharedmedia.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/smallanimationpin.png b/TMessagesProj/src/main/res/drawable-xhdpi/smallanimationpin.png new file mode 100644 index 000000000..bfff907ec Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/smallanimationpin.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/smallanimationpinleft.png b/TMessagesProj/src/main/res/drawable-xhdpi/smallanimationpinleft.png new file mode 100644 index 000000000..942a637e0 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/smallanimationpinleft.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/smallanimationpinright.png b/TMessagesProj/src/main/res/drawable-xhdpi/smallanimationpinright.png new file mode 100644 index 000000000..152dcd904 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/smallanimationpinright.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/staredstickerstab.png b/TMessagesProj/src/main/res/drawable-xhdpi/staredstickerstab.png new file mode 100644 index 000000000..87a733dfd Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/staredstickerstab.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/stickers_favorite.png b/TMessagesProj/src/main/res/drawable-xhdpi/stickers_favorite.png new file mode 100644 index 000000000..aaae6b242 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/stickers_favorite.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/stickers_pack.png b/TMessagesProj/src/main/res/drawable-xhdpi/stickers_pack.png new file mode 100644 index 000000000..6827906fc Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/stickers_pack.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/stickers_send.png b/TMessagesProj/src/main/res/drawable-xhdpi/stickers_send.png new file mode 100644 index 000000000..610c10da2 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/stickers_send.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/stickers_unfavorite.png b/TMessagesProj/src/main/res/drawable-xhdpi/stickers_unfavorite.png new file mode 100644 index 000000000..86963b6a4 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/stickers_unfavorite.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/stickersclose.png b/TMessagesProj/src/main/res/drawable-xhdpi/stickersclose.png new file mode 100644 index 000000000..ad4fa51aa Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/stickersclose.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/stickerset_close.png b/TMessagesProj/src/main/res/drawable-xhdpi/stickerset_close.png new file mode 100644 index 000000000..59e2b6898 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/stickerset_close.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/usersearch.png b/TMessagesProj/src/main/res/drawable-xhdpi/usersearch.png new file mode 100644 index 000000000..4553ae5c9 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/usersearch.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/video_edit_play.png b/TMessagesProj/src/main/res/drawable-xhdpi/video_edit_play.png deleted file mode 100755 index f2f29ba6c..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/video_edit_play.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/video_pip.png b/TMessagesProj/src/main/res/drawable-xhdpi/video_pip.png new file mode 100644 index 000000000..1000802e2 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/video_pip.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/video_play.png b/TMessagesProj/src/main/res/drawable-xhdpi/video_play.png deleted file mode 100644 index 1837fef41..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/video_play.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/video_png.png b/TMessagesProj/src/main/res/drawable-xhdpi/video_png.png new file mode 100644 index 000000000..9a623817e Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/video_png.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/video_time_l.png b/TMessagesProj/src/main/res/drawable-xhdpi/video_time_l.png deleted file mode 100755 index 3460e650e..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/video_time_l.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/video_time_r.png b/TMessagesProj/src/main/res/drawable-xhdpi/video_time_r.png deleted file mode 100755 index 3be4bd3ae..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/video_time_r.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/animationpin.png b/TMessagesProj/src/main/res/drawable-xxhdpi/animationpin.png new file mode 100644 index 000000000..297b098a0 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/animationpin.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/animationpinleft.png b/TMessagesProj/src/main/res/drawable-xxhdpi/animationpinleft.png new file mode 100644 index 000000000..ee4a43340 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/animationpinleft.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/animationpinright.png b/TMessagesProj/src/main/res/drawable-xxhdpi/animationpinright.png new file mode 100644 index 000000000..5294ef3be Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/animationpinright.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/bookmark_large.png b/TMessagesProj/src/main/res/drawable-xxhdpi/bookmark_large.png new file mode 100755 index 000000000..3717f7a4e Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/bookmark_large.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/btn_send_location_down.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/btn_send_location_down.9.png deleted file mode 100755 index 5ef0b1b44..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/btn_send_location_down.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/btn_send_location_up.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/btn_send_location_up.9.png deleted file mode 100755 index f05699e16..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/btn_send_location_up.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/cloud.png b/TMessagesProj/src/main/res/drawable-xxhdpi/cloud.png deleted file mode 100755 index dd57d88be..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/cloud.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_fave.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_fave.png new file mode 100755 index 000000000..092c4bc5e Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_fave.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_unfave.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_unfave.png new file mode 100755 index 000000000..ee5eacbd7 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_unfave.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_call_white_18dp.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_call_white_18dp.png deleted file mode 100755 index 6f4dcea1f..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_call_white_18dp.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_masks_recent2.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_masks_recent2.png deleted file mode 100755 index 37a8537db..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_masks_recent2.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_masks_sticker2.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_masks_sticker2.png deleted file mode 100755 index b36344986..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_masks_sticker2.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_more.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_more.png deleted file mode 100644 index 49fba0ce0..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_more.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_sad.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_sad.png new file mode 100755 index 000000000..1a8217383 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_sad.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_vpn_key_white_24dp.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_vpn_key_white_24dp.png deleted file mode 100755 index 54c92ace4..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_vpn_key_white_24dp.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/list_focused_holo.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/list_focused_holo.9.png deleted file mode 100644 index 76cad1739..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/list_focused_holo.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/livelocationpin.png b/TMessagesProj/src/main/res/drawable-xxhdpi/livelocationpin.png new file mode 100644 index 000000000..acf3ee183 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/livelocationpin.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/livepin.png b/TMessagesProj/src/main/res/drawable-xxhdpi/livepin.png new file mode 100644 index 000000000..dc6d6806a Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/livepin.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/managers.png b/TMessagesProj/src/main/res/drawable-xxhdpi/managers.png deleted file mode 100755 index f58abc4ae..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/managers.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/mentionbutton.png b/TMessagesProj/src/main/res/drawable-xxhdpi/mentionbutton.png new file mode 100644 index 000000000..e4c85025f Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/mentionbutton.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/mentionchatslist.png b/TMessagesProj/src/main/res/drawable-xxhdpi/mentionchatslist.png new file mode 100644 index 000000000..50cee8a1d Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/mentionchatslist.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/menu_saved.png b/TMessagesProj/src/main/res/drawable-xxhdpi/menu_saved.png new file mode 100755 index 000000000..dfb40119b Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/menu_saved.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/message_arrow.png b/TMessagesProj/src/main/res/drawable-xxhdpi/message_arrow.png new file mode 100755 index 000000000..298efdd58 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/message_arrow.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_cancel_l.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_cancel_l.png deleted file mode 100755 index a3eea7bda..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_cancel_l.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_cancel_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_cancel_s.png deleted file mode 100755 index 6ec74f451..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_cancel_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_load_l.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_load_l.png deleted file mode 100755 index 31cf7dfad..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_load_l.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_load_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_load_s.png deleted file mode 100755 index e88961eae..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_load_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_pause_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_pause_s.png deleted file mode 100755 index 63d1849d9..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_pause_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_play_l.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_play_l.png deleted file mode 100755 index 4d875efd8..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_play_l.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_play_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_play_s.png deleted file mode 100755 index 84285003b..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_play_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/music_reverse.png b/TMessagesProj/src/main/res/drawable-xxhdpi/music_reverse.png new file mode 100755 index 000000000..80c1d8dfa Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/music_reverse.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photo_text.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_text.png deleted file mode 100755 index 41c6315d7..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/photo_text.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photoborder.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photoborder.9.png deleted file mode 100644 index b46d44547..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/photoborder.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photos_group.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photos_group.png new file mode 100755 index 000000000..09c8f467b Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/photos_group.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/phototime2.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/phototime2.9.png deleted file mode 100755 index 2f52ccc15..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/phototime2.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/phototime2_b.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/phototime2_b.9.png deleted file mode 100755 index 6ce439aa2..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/phototime2_b.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pin.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pin.png old mode 100755 new mode 100644 index 3eded6c82..6663b7158 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/pin.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/pin.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_back.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_back.png deleted file mode 100644 index 10fcfc173..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_back.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_next.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_next.png index 8cf29a4aa..37928e0b4 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_next.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_next.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_pause.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_pause.png index 8808ce137..917bba404 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_pause.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_play.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_play.png index 55c92dbd7..c3f04d39d 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_play.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_play.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_previous.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_previous.png index c2e3c33fa..2b0733816 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_previous.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_previous.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_repeat.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_repeat.png old mode 100755 new mode 100644 index d50ffdef1..a2c09eaab Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_repeat.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_repeat.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_repeat1.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_repeat1.png old mode 100755 new mode 100644 index 6ba0fe87b..957cda76d Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_repeat1.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_repeat1.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_shuffle.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_shuffle.png index 00e3337e6..181ffc2c3 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_shuffle.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_shuffle.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/rec.png b/TMessagesProj/src/main/res/drawable-xxhdpi/rec.png deleted file mode 100755 index e95a6252b..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/rec.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/search_light.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/search_light.9.png deleted file mode 100644 index f2f7727f8..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/search_light.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/search_light_activated.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/search_light_activated.9.png deleted file mode 100644 index d7852f277..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/search_light_activated.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/sharedmedia.png b/TMessagesProj/src/main/res/drawable-xxhdpi/sharedmedia.png deleted file mode 100755 index 80dfc3838..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/sharedmedia.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/smallanimationpin.png b/TMessagesProj/src/main/res/drawable-xxhdpi/smallanimationpin.png new file mode 100644 index 000000000..b95b84822 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/smallanimationpin.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/smallanimationpinleft.png b/TMessagesProj/src/main/res/drawable-xxhdpi/smallanimationpinleft.png new file mode 100644 index 000000000..f9fba58c2 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/smallanimationpinleft.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/smallanimationpinright.png b/TMessagesProj/src/main/res/drawable-xxhdpi/smallanimationpinright.png new file mode 100644 index 000000000..fa74c81db Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/smallanimationpinright.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/staredstickerstab.png b/TMessagesProj/src/main/res/drawable-xxhdpi/staredstickerstab.png new file mode 100644 index 000000000..cd3e33c62 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/staredstickerstab.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/stickers_favorite.png b/TMessagesProj/src/main/res/drawable-xxhdpi/stickers_favorite.png new file mode 100644 index 000000000..1db011aa3 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/stickers_favorite.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/stickers_pack.png b/TMessagesProj/src/main/res/drawable-xxhdpi/stickers_pack.png new file mode 100644 index 000000000..f68d40e9b Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/stickers_pack.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/stickers_send.png b/TMessagesProj/src/main/res/drawable-xxhdpi/stickers_send.png new file mode 100644 index 000000000..3ed7351e7 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/stickers_send.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/stickers_unfavorite.png b/TMessagesProj/src/main/res/drawable-xxhdpi/stickers_unfavorite.png new file mode 100644 index 000000000..1d82d680e Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/stickers_unfavorite.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/stickersclose.png b/TMessagesProj/src/main/res/drawable-xxhdpi/stickersclose.png new file mode 100644 index 000000000..7017b9c50 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/stickersclose.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/stickerset_close.png b/TMessagesProj/src/main/res/drawable-xxhdpi/stickerset_close.png new file mode 100644 index 000000000..4dc9de7e4 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/stickerset_close.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/usersearch.png b/TMessagesProj/src/main/res/drawable-xxhdpi/usersearch.png new file mode 100644 index 000000000..209f4e0a4 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/usersearch.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/video_copy.png b/TMessagesProj/src/main/res/drawable-xxhdpi/video_copy.png new file mode 100644 index 000000000..c4b6d1a18 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/video_copy.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/video_edit_play.png b/TMessagesProj/src/main/res/drawable-xxhdpi/video_edit_play.png deleted file mode 100755 index 077d4c036..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/video_edit_play.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/video_pip.png b/TMessagesProj/src/main/res/drawable-xxhdpi/video_pip.png new file mode 100644 index 000000000..3e63ee780 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/video_pip.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/video_play.png b/TMessagesProj/src/main/res/drawable-xxhdpi/video_play.png deleted file mode 100644 index aa05f5c53..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/video_play.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/video_time_l.png b/TMessagesProj/src/main/res/drawable-xxhdpi/video_time_l.png deleted file mode 100755 index 8fd7d1319..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/video_time_l.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/video_time_r.png b/TMessagesProj/src/main/res/drawable-xxhdpi/video_time_r.png deleted file mode 100755 index a68cc7ed9..000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/video_time_r.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxxhdpi/sheet_shadow.9.png b/TMessagesProj/src/main/res/drawable-xxxhdpi/sheet_shadow.9.png index 1520a1fc0..d15a95606 100644 Binary files a/TMessagesProj/src/main/res/drawable-xxxhdpi/sheet_shadow.9.png and b/TMessagesProj/src/main/res/drawable-xxxhdpi/sheet_shadow.9.png differ diff --git a/TMessagesProj/src/main/res/drawable/field_carret.xml b/TMessagesProj/src/main/res/drawable/field_carret.xml deleted file mode 100644 index a19003262..000000000 --- a/TMessagesProj/src/main/res/drawable/field_carret.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/TMessagesProj/src/main/res/drawable/ic_masks_recent.xml b/TMessagesProj/src/main/res/drawable/ic_masks_recent.xml deleted file mode 100644 index efb82036d..000000000 --- a/TMessagesProj/src/main/res/drawable/ic_masks_recent.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/TMessagesProj/src/main/res/drawable/loading_animation2.xml b/TMessagesProj/src/main/res/drawable/loading_animation2.xml new file mode 100644 index 000000000..94b94dbd2 --- /dev/null +++ b/TMessagesProj/src/main/res/drawable/loading_animation2.xml @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable/location_send_button_states.xml b/TMessagesProj/src/main/res/drawable/location_send_button_states.xml deleted file mode 100644 index 94dc5b829..000000000 --- a/TMessagesProj/src/main/res/drawable/location_send_button_states.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable/search_carret.xml b/TMessagesProj/src/main/res/drawable/search_carret.xml deleted file mode 100644 index 3c44cd870..000000000 --- a/TMessagesProj/src/main/res/drawable/search_carret.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/TMessagesProj/src/main/res/drawable/ytlogo.png b/TMessagesProj/src/main/res/drawable/ytlogo.png new file mode 100644 index 000000000..ed1b4fb99 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable/ytlogo.png differ diff --git a/TMessagesProj/src/main/res/layout/player_big_notification.xml b/TMessagesProj/src/main/res/layout/player_big_notification.xml index 98c7f0bd2..e37aa6d49 100755 --- a/TMessagesProj/src/main/res/layout/player_big_notification.xml +++ b/TMessagesProj/src/main/res/layout/player_big_notification.xml @@ -3,8 +3,9 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="128dp" + xmlns:tools="http://schemas.android.com/tools" android:orientation="horizontal" - android:background="#ff424242" > + android:background="#ff424242"> + android:layout_marginRight="44dp" + tools:text="Song Title"/> + android:textSize="13dp" + tools:text="Artist Name"/> + + - - - تيليجرام نسخة تيليجرام التجريبية @@ -17,10 +14,12 @@ رمز البلد خاطئ تحقق الهاتف - تم إرسال رسالة قصيرة تحتوي على رمز التفعيل الخاص بك لرقمك ]]>%1$s]]>. - لقد قمنا بإرسال رمز الدخول إلى تطبيق ]]>تيليجرام]]> على جهازك الآخر - تم إرسال مكالمة تفعيل على رقمك ]]>%1$s]]>.\n\nلا داعي للرد عليها، تيليجرام سيقوم بعملية التفعيل تلقائيًا. - جاري الاتصال على رقمك ]]>%1$s]]> لإيصال رمز التفعيل. + تم إرسال رسالة قصيرة تحتوي على رمز التفعيل الخاص بك لرقمك **%1$s**. + لقد قمنا بإرسال رمز الدخول إلى تطبيق **تيليجرام** على جهازك الآخر + تم إرسال مكالمة تفعيل على رقمك **%1$s**. + +لا داعي للرد عليها، تيليجرام سيقوم بعملية التفعيل تلقائيًا. + جاري الاتصال على رقمك **%1$s** لإيصال رمز التفعيل. سنتصل بك خلال %1$d:%2$02d سنقوم بإرسال رسالة قصيرة خلال %1$d:%2$02d جاري الاتصال بك ... @@ -28,9 +27,13 @@ الرقم خاطئ؟ هل استقبلت الرمز؟ إلغاء إعادة تعيين الحساب - شخص ما لديه صلاحية الوصول لهاتفك ]]>%1$s]]> طلب حذف حسابك وتعطيل التحقق من حسابك بخطوتين.\n\nإذا لم يكن أنت، فضلًا قم بإدخال الرمز الذي أرسلناه للتو برسالة قصيرة. + شخص ما لديه صلاحية الوصول لهاتفك **%1$s** طلب حذف حسابك وتعطيل التحقق من حسابك بخطوتين. + +إذا لم يكن أنت، فضلًا قم بإدخال الرمز الذي أرسلناه للتو برسالة قصيرة. إعادة تعيين الحساب - بما أن الحساب ]]>%1$s]]> فعّال ومحمي بكلمة مرور، سنقوم بحذفه خلال أسبوع لأسباب أمنية.\n\nيمكنك إلغاء هذه العملية في أي وقت. + بما أن الحساب **%1$s** فعّال ومحمي بكلمة مرور، سنقوم بحذفه خلال أسبوع لأسباب أمنية. + +يمكنك إلغاء هذه العملية في أي وقت. ستتمكن من إعادة تعيين حسابك خلال: تم إلغاء محاولاتك السابقة لإعادة تعيين هذا الحساب. فضلًا أعد المحاولة بعد ٧ أيام. إعادة تعيين @@ -69,7 +72,6 @@ رمز الأمان (CVV) MM/YY عنوان الدفع - صاحب البطاقة الاسم حفظ معلومات الدفع يمكنك حفظ معلومات الدفع لاستخدامها لاحقًا. @@ -93,7 +95,20 @@ المعذرة، تم رفض عملية الدفع. تعذر الوصور إلى خادم الدفع. فضلًا تحقق من اتصال الانترنت وحاول مرة أخرى. تحذير - لا يمتلك تيليجرام، ولا %1$s صلاحيات الوصول إلى معلومات بطاقتك الإئتمانية. تفاصيل البطاقات الإئتمانية تتم معالجتها مباشرة عن طريق نظام الدفع, %2$s.\n\nالدفعات تذهب لشكل مباشر إلى مطور %1$s. تيليجرام لا يوفر أية ضمانات، لذلك استمر على مسؤوليتك. في حال وجود مشاكل، تواصل مع مطور %1$s أو البنك الخاص بك. + لا يمتلك تيليجرام، ولا %1$s صلاحيات الوصول إلى معلومات بطاقتك الإئتمانية. تفاصيل البطاقات الإئتمانية تتم معالجتها مباشرة عن طريق نظام الدفع, %2$s. + +الدفعات تذهب لشكل مباشر إلى مطور %1$s. تيليجرام لا يوفر أية ضمانات، لذلك استمر على مسؤوليتك. في حال وجود مشاكل، تواصل مع مطور %1$s أو البنك الخاص بك. + كلمة المرور وamp; البريد الإلكتروني + كلمة المرور + أدخل كلمة مرور + أعد إدخال كلمة المرور + يرجى اختيار كلمة مرور لحماية معلومات الدفع الخاصة بك. ستطلب منك عند تسجيل الدخول. + بريد الاسترداد الإلكتروني + بريدك الإلكتروني + يرجى إضافة بريدك الإلكتروني. هذه الطريقة الواحدة لاستعادة كلمة المرور المنسية. + سيتم إرسال الرقم إلى %1$s كمعلومات الدفع. + سيتم إرسال البريد الالكتروني إلى %1$s كمعلومات الدفع. + سيتم إرسال الرقم والبريد الإلكتروني إلى %1$s كمعلومات الدفع. محادثات جديدة الإعدادات @@ -102,7 +117,11 @@ أمس لا توجد نتائج ...لا توجد محادثات بعد - إبدأ المراسلة بالضغط على\nأيقونة النقاط في أعلى يمين الشاشة\nأو اذهب لقسم جهات الاتصال. + المستعرضة مؤخرًا + إخفاء + إبدأ المراسلة بالضغط على +أيقونة النقاط في أعلى يمين الشاشة +أو اذهب لقسم جهات الاتصال. في إنتظار الشبكة... جاري الاتصال... جاري الاتصال بالبروكسي... @@ -125,10 +144,12 @@ حذف المحادثة حساب محذوف اختر محادثة - اضغط بإستمرار على المستخدم العرض + أعد الإرسال إلى... صورة سرية فيديو سري - %1$s يستخدم إصدار قديم من تيليجرام، لذلك، الصور السرية ستظهر في وضع الموافقة.\n\nعندما يقوم %2$s بتحديث تيليجرام، الصور التي بها عداد دقيقة أو أقل ستعمل بطريقة \"الاستمرار بالضغط للإستعراض\"، وسيتم إخبارك عندما يلتقط المستقبل صورة من شاشته. + %1$s يستخدم إصدار قديم من تيليجرام، لذلك، الصور السرية ستظهر في وضع الموافقة. + +عندما يقوم %2$s بتحديث تيليجرام، الصور التي بها عداد دقيقة أو أقل ستعمل بطريقة \"الاستمرار بالضغط للإستعراض\"، وسيتم إخبارك عندما يلتقط المستقبل صورة من شاشته. الرسائل بحث كتم الإشعارات @@ -153,16 +174,12 @@ عريض مائل طبيعي - لتستطيع التواصل بشكل سلس مع الجميع، قم بالسماح لـ ]]>تيليجرام]]> بالوصول إلى جهات الاتصال الخاصة بك. + لتستطيع التواصل بشكل سلس مع الجميع، قم بالسماح لـ **تيليجرام** بالوصول إلى جهات الاتصال الخاصة بك. ليس الآن الاستمرار - عامة - خاصة ترقية ليكون مشرف لا يوجد مستخدمين محظورين - يمكنك كتابة وصف اختياري لمجموعتك. - مغادرة المجموعة حذف المجموعة مغادرة المجموعة حذف المجموعة @@ -188,6 +205,7 @@ un1 ثبت جهة اتصال un1 ثبت %1$s un1 ثبت خريطة + un1 قان بتثبيت مكان حي un1 ثبت صورة متحركة un1 ثبت مقطع صوتي تم ترقية هذه المجموعة لتصبح مجموعة خارقة @@ -235,6 +253,7 @@ جاري مراجعة الاسم... %1$s متاح. أعضاء + المشتركون قائمة المحظورين المستخدمون المحظورون المستخدمون المقيدون @@ -245,16 +264,15 @@ هل أنت متأكد من رغبتك في مغادرة القناة؟ ستخسر كافة الرسائل في هذه القناة. تعديل - يرجى ملاحظة أنه في حال اخترت رابط عام لمجموعتك، سيستطيع أي شخص إيجادها من خلال البحث والدخول إليها.\n\nلا تقم بإنشاء هذا الرابط إذا رغبت في أن تستمر قناتك خاصة. - يرجى ملاحظة أنه في حال اخترت رابط عام لقناتك، سيستطيع أي شخص إيجادها من خلال البحث والدخول إليها.\n\nلا تقم بإنشاء هذا الرابط إذا رغبت في أن تستمر قناتك خاصة. - فضلًا اختر رابط لقناتك العامة ليتمكن الناس من إيجادها من خلال البحث ومشاركتها مع غيرهم.\n\nإلى لم ترغب بذلك، يفضّل إنشاء قناة خاصة بدلًا من العامة. + فضلًا اختر رابط لقناتك العامة ليتمكن الناس من إيجادها من خلال البحث ومشاركتها مع غيرهم. + +إلى لم ترغب بذلك، يفضّل إنشاء قناة خاصة بدلًا من العامة. تم إنشاء القناة تم تغيير صورة القناة تم حذف صورة القناة تم تغيير اسم القناة إلى un2 المعذرة، قمت بحجز معرفات عامة كثيرة. يمكنك إلغاء بعض روابط مجموعاتك وقنواتك القديمة، أو قم بجعلها خاصة. المنشئ - إداري الصامت إلغاء الصامت إضافة مشرف @@ -262,10 +280,8 @@ ألغ الحظر اضغط بإستمرار على المستخدم لإلغاء الحظر استخدم رابط دعوة - هل أنت متأكد من رغبتك في تعيين %1$s كإداري؟ إزالة المشرف فقط الإداريّون يمكنهم مشاهدة هذه القائمة. - هذا المستخدم لم يقم بالدخول إلى القناة بعد. فضلًا، هل ترغب في دعوتهم؟ أي شخص يمتلك تيليجرام على جهازه سيمكنه الدخول لقناتك باستخدام هذا الرابط. يمكنك إضافة إداريّون لمساعدتك في إدارة القناة. اضغط باستمرار لحذف الإداريين. هل ترغب في الدخول لقناة \'%1$s\'؟ @@ -285,18 +301,6 @@ المعذرة، لا يمكنك إرسال رسائل لهذه القناة. %1$s قام بإضافتك لقناة %2$s تم تحديث صورة القناة %1$s - %1$s قام بإرسال رسالة للقناة %2$s - %1$s قام بإرسال صورة للقناة %2$s - %1$s قام بإرسال مقطع مرئي للقناة %2$s - %1$s قام بمشاركة جهة اتصال للقناة %2$s - %1$s قام بإرسال مكان للقناة %2$s - %1$s قام بإرسال ملف للقناة %2$s - %1$s أرسل صورة متحركة للقناة %2$s - %1$s أرسل رسالة صوتية للقناة %2$s - %1$s أرسل رسالة مرئية للقناة %2$s - %1$s قام بإرسال مقطع صوتي للقناة %2$s - %1$s قام بإرسال ملصق للقناة %2$s - %1$s أرسل %3$s ملصق للقناة %2$s %1$s أرسل رسالة %1$s أرسل صورة %1$s أرسل مقطع مرئي @@ -342,21 +346,35 @@ ضمن روابط محظور حتى إلى الأبد - حذف حظر وإخراج من المجموعة أدر المجموعة أدر القناة أدر المجموعة أدر القناة + سجل المحادثات للأعضاء الجدد + ظاهر + الأعضاء الجدد سيتمكنون من الإطلاع على الرسائل التي تم إرسالها قبل انضمامهم. + مخفي + الأعضاء الجدد لن يتمكنوا من مشاهدة الرسائل القديمة. الأفعال الحديثة كافة الأحداث الأحداث المختارة كافة المشرفين - لا توجد أحداث هنا بعد\n\nأعضاء المجموعة ومشرفيها\nلم يقوموا بأي عمليات\nخلال 48 ساعة الماضية. - لا توجد أحداث هنا بعد\n\nمشرفي القناة\nلم يقوموا بأي عمليات\nخلال 48 ساعة الماضية. - ]]>لم يتم العثور على أحداث ]]>\n\nلا توجد أحداث مؤخرًا تحتوي على ما بحثت عنه. - لا توجد أحداث مؤخرًا تحتوي على \']]>%1$s]]>\' تم العثور عليها. + **لا توجد أحداث هنا بعد** + +أعضاء المجموعة ومشرفيها +لم يقوموا بأي عمليات +خلال 48 ساعة الماضية. + **لا توجد أحداث هنا بعد** + +مشرفي القناة +لم يقوموا بأي عمليات +خلال 48 ساعة الماضية. + **لم يتم العثور على أحداث ** + +لا توجد أحداث مؤخرًا تحتوي على ما بحثت عنه. + لا توجد أحداث مؤخرًا تحتوي على \'**%1$s**\' تم العثور عليها. ما هي العمليات الحديثة؟ هذه قائمة بجميع العمليات التي قام بها أعضاء المجموعة ومشرفيها خلال 48 ساعة. هذه قائمة بجميع العمليات التي قام بها أعضاء القناة ومشرفيها خلال 48 ساعة. @@ -381,6 +399,8 @@ un1 ثبت هذه رسالة: un1 ألغى تثبيت رسالة un1 حذف هذه رسالة: + un1 غير حزمة ملصقات المجموعة + un1 حذف حزمة الملصقات un1 غير رابط المجموعة: un1 غير رابط القناة: un1 قام بحذف رابط المجموعة @@ -389,11 +409,15 @@ un1 عدل وصف المجموعة: un1 عدل وصف القناة: الوصف السابق + un1 قام بجعل سجل المحادثة في المجموعة ظاهر للأعضاء الجدد + un1 قام بإخفاء سجل المحادثات عن أعضاء المجموعة الجدد un1 فعل دعوات المجموعة un1 عطل الدعوات un1 فعل التواقيع un1 عطل التواقيع - غير القيود الخاصة بـ %1$s\n\nلمدة: %2$s + غير القيود الخاصة بـ %1$s + +لمدة: %2$s أرسل ملصقات وصور متحركة أرسل وسائط أرسل رسائل @@ -415,7 +439,6 @@ أعضاء جدد معلومات المجموعة معلومات القناة - إعدادات المجموعة رسائل محذوفة تعديل الرسائل رسائل مثبتة @@ -431,12 +454,13 @@ موسيقى الفنان غير معروف العنوان غير معروف + عشوائي + اعكس الترتيب اختر ملف متاح %1$s من %2$s حدث خطأ غير معروف خطأ في الوصول - لا يوجد ملفات بعد... حجم الملف لا يمكن أن يكون أكبر من %1$s الذاكرة غير مثبتة نقل USB مفعل @@ -446,11 +470,22 @@ بطاقة الذاكرة مجلد أرسل الصورة بدون ضغطها + + ملصقات المجموعة + اختر من ملصقاتك + يمكنك اختيار حزمة ستكون متاحة لكل أعضاء هذه المجموعة أثناء المراسلة في هذه المجموعة. + اختر حزمة ملصقات + حزمة ملصقات + يمكنك الآن صنع حزمة الملصقات الخاصة بك عن طريق البوت @stickers . + لم يتم العثور على الملصق + حاول مرة أخرى أو اختر من القائمة أدناه مخفي جاري الكتابة… يكتب… يكتبون… + %1$s يقوم بالكتابة... + %1$s يقوموا بالكتابة... %1$s يقوم بتسجيل رسالة صوتية... %1$s يقوم بتسجيل رسالة مرئية... %1$s يقوم بإرسال مقطع صوتي... @@ -469,8 +504,8 @@ يلعب لعبة... جاري إرسال المقطع المرئي... جاري إرسال الملف... - هل يوجد لديك سؤال\nحول تيليجرام؟ - التقط صورة + هل يوجد لديك سؤال +حول تيليجرام؟ صورة موقع مقطع مرئي @@ -479,6 +514,7 @@ ...لا توجد رسائل بعد الرسالة المعاد توجيهها من + من: لا توجد رسائل أحدث الرسالة الرسالة @@ -509,7 +545,6 @@ جاري جلب معلومات الرابط... فتح بواسطة... افتح باستخدام... - انسخ الرابط أرسل %1$s أرسل كملف أرسل كملفات @@ -523,9 +558,6 @@ هل أنت متأكد من رغبتك في الإبلاغ عن هذه القناة كغير مرغوب بها؟ المعذرة، فقط يمكنك مراسلة جهات الاتصال المشتركة في الوقت الحالي. المعذرة، فقط يمكنك إضافة جهات الاتصال المشتركة للمجموعات في الوقت الحالي. - لقد قمت بمراسلة أشخاص كثيرين اليوم من خارج جهات الاتصال لديك. فضلًا حاول مرة أخرى غدًا. ستتمكن من الرد اليوم إذا قام المستخدم بمراسلتك أولًا. - لا يمكنك إضافة هذا المستخدم لأنك قمت بمراسلة أشخاص كثيرين اليوم من خارج جهات الاتصال لديك. فضلًا حاول مرة أخرى غدًا. يمكنك الطلب من عضو آخر إضافة هذا المستخدم للمجموعة. - https://telegram.org/faq/can-39t-send-messages-to-non-contacts ملعومات إضافية أرسل إلى... اكتب ملاحظة @@ -534,6 +566,7 @@ أشعر جميع الأعضاء إزالة التثبيت هل ترغب في تثبيت هذه الرسالة في هذه المجموعة؟ + هل تريد تثبيت هذه الرسالة في هذه القناة؟ هل ترغب في إزالة تثبيت هذه الرسالة؟ حظر المستخدم الإبلاغ عن إزعاج @@ -552,8 +585,9 @@ المعذرة، انتهت مدة التعديل على الرسائل. إضافة اختصار ابحث عن أعضاء - تمت إضافة اختصار للشاشة الرئيسية - تحدث مع نفسك + الرسائل المحفوظة + الرسائل المحفوظة + حوّل الرسائل إلى هنا لحفظها. أنت حوّل الرسائل إلى هنا لحفظهم أرسل الوسائط والملفات لحفظهم @@ -579,6 +613,7 @@ مشرفو هذه المجموعة قاموا بوضع قيد يمنعك من إرسال محتوى في نفس السطر هنا مشرفو هذه المجموعة قاموا بوضع قيد يمنعك من إرسال الملصقات هنا مشرفو هذه المجموعة قاموا بوضع قيد يمنعك من الكتابة هنا + مشرف %1$s قام بتعيين عداد التدمير الذاتي إلى to %2$s لقد قمت بتعيين التدمير الذاتي إلى %1$s @@ -625,7 +660,16 @@ %1$s قام بإخراجك من المجموعة %2$s %1$s قام بمغادرة المجموعة %2$s %1$s قام بالتسجيل في تيليجرام! - %1$s,\nتم تسجيل الدخول لحسابك من جهاز جديد يوم %2$s\n\nالجهاز: %3$s\nالموقع: %4$s\n\nإذا لم يكن أنت من سجل الدخول، يمكنك الذهاب للإعدادات ثم تسجيل الخروج من كافة الأجهزة الأخرى. كما يمكنك تفعيل التحقق بخطوتين إذا رغبت بذلك عن طريق إعدادات الخصوصية.\n\nشكرًا,\nفريق عمل تيليجرام + %1$s, +تم تسجيل الدخول لحسابك من جهاز جديد يوم %2$s + +الجهاز: %3$s +الموقع: %4$s + +إذا لم يكن أنت من سجل الدخول، يمكنك الذهاب للإعدادات ثم تسجيل الخروج من كافة الأجهزة الأخرى. كما يمكنك تفعيل التحقق بخطوتين إذا رغبت بذلك عن طريق إعدادات الخصوصية. + +شكرًا, +فريق عمل تيليجرام %1$s قام بتغيير صورته الشخصية %1$s قام بالدخول للمجموعة %2$s باستخدام رابط الدعوة الرد @@ -644,11 +688,13 @@ %1$s ثبت رسالة مرئية في المجموعة %2$s %1$s ثبت جهة اتصال في المجموعة %2$s %1$s ثبت خريطة في المجموعة %2$s + %1$s قام بتثبيت مكان حي في المجموعة %2$s %1$s ثبت صورة متحركة في المجموعة %2$s %1$s ثبت مقطع صوتي في المجموعة %2$s %1$s ثبت \"%2$s\" %1$s ثبت رسالة %1$s ثبت صورة + %1$s ثبت لعبة %1$s ثبت فيديو %1$s ثبت ملف %1$s ثبت ملصق @@ -657,26 +703,31 @@ %1$s ثبت رسالة مرئية %1$s ثبت جهة اتصال %1$s ثبت خريطة + %1$s قام بتثبيت مكان حي %1$s ثبت صورة متحركة %1$s ثبت مقطع صوتي - %1$s ثبت لعبة اختر جهة اتصال لا توجد جهات اتصال بعد - https://telegram.org/dl مرحبا! هيا نستخدم تيليجرام: + مرحبا، أنا استخدم تيليجرام للمحادثة. انضم إلينا! قم بتنزيله من هنا: %1$s في أمس الساعة متصل آخر ظهور آخر ظهور - آخر ظهور قبل قليل قم بدعوة صديق + ابحث عن جهات اتصال بحث شامل آخر ظهور كان قريب آخر ظهور خلال أسبوع آخر ظهور خلال شهر آخر ظهور خلال فترة طويلة رسالة جديدة + اختر جهات الاتصال لدعوتهم لتيليجرام + قم بدعوة صديق لتيليجرام + شارك تيليجرام... + هل ترغب في تحديث جهات الاتصال؟ + تيليجرام قام باكتشاف جهات اتصال غير مزامنة، هل ترغب في مزامنتها الآن؟ اختر \"نعم\" إذا كنت تستخدم جهازك الخاص، شريحتك، وحساب جوجل الخاص بك. أضف أشخاص... ستتمكن من إضافة أعضاء أكثر إذا انتهيت من إنشاء المجموعة وقمت بتحويلها إلى مجموعة خارقة. @@ -684,7 +735,6 @@ اسم المجموعة تم اختيار %1$d من %2$d حتى %1$s - هل ترغب في الدخول للمحادثة \'%1$s\'؟ المعذرة، هذه المجموعة ممتلئة. المعذرة، هذه المحادثة غير موجودة. تم نسخ الرابط إلى الحافظة @@ -694,8 +744,11 @@ رابط الدعوة السابق لا يعمل الآن. سيتم إنشاء رابط جديد. إلغاء الرابط إلغاء الرابط - هل أنت متأكد من رغبتك في تعطيل الرابط ]]>%1$s]]>?\n\nالمجموعة \"]]>%2$s]]>\" ستصبح خاصة. - هل أنت متأكد من رغبتك في تعطيل الرابط ]]>%1$s]]>?\n\القناة \"]]>%2$s]]>\" ستصبح خاصة. + هل أنت متأكد من رغبتك في تعطيل الرابط **%1$s**? + +المجموعة \"**%2$s**\" ستصبح خاصة. + هل أنت متأكد من رغبتك في تعطيل الرابط **%1$s**? +\القناة \"**%2$s**\" ستصبح خاصة. نسخ الرابط شارك الرابط أي شخص يمتلك تيليجرام على جهازه سيسطيع الدخول لمجموعتك باستخدام الرابط التالي. @@ -705,8 +758,10 @@ جميع أعضاء القناة يستطيعون إضافة وحذف الأعضاء، والتعديل على اسم وصورة المجموعة. فقط مشرفو القناة يستطيعون حذف الأعضاء، والتعديل على اسم وصورة المجموعة. + أعضاء عدد الوسائط المشتركة الإعدادات + إضافة مشترك إضافة مشارك تعيين كمشرف حظر من المجموعة @@ -719,9 +774,22 @@ التحويل إلى مجموعة خارقة تحذير هذا القرار لا يمكن الرجوع عنه. لن تتمكن من إرجاع المجموعات الخارقة لتصبح مجموعات عادية. - ]]>تم الوصول للحد الأعلى للأعضاء]]>\n\nلتخطي هذا الحد والحصول على خصائص جديدة، يمكنك الترقية لمجموعة خارقة:\n\n• المجموعات الخارقة يمكن أن تصل إلى %1$s عضوs\n• الأعضاء الجدد يمكنهم مشاهدة سجل المحادثات كاملًا\n• الرسائل المحذوفة ستختفي من أجهزة جميع الأعضاء\n• يمكن للمشرفين تثبيت الرسائل\n• يمكن لمنشئ المجموعة تعيين رابط للمجموعة - ]]> في المجموعات الخارقة:]]>\n\n• الأعضاء الجدد يمكنهم مشاهدة سجل المحادثات كاملًا\n• الرسائل المحذوفة ستختفي من أجهزة جميع الأعضاء\n• يمكن للمشرفين تثبيت الرسائل\n• يمكن لمنشئ المجموعة تعيين رابط للمجموعة - ]]>ملاحظة:]]> لا يمكنك الرجوع عن هذا القرار. + **تم الوصول للحد الأعلى للأعضاء** + +لتخطي هذا الحد والحصول على خصائص جديدة، يمكنك الترقية لمجموعة خارقة: + +• المجموعات الخارقة يمكن أن تصل إلى %1$s عضوs +• الأعضاء الجدد يمكنهم مشاهدة سجل المحادثات كاملًا +• الرسائل المحذوفة ستختفي من أجهزة جميع الأعضاء +• يمكن للمشرفين تثبيت الرسائل +• يمكن لمنشئ المجموعة تعيين رابط للمجموعة + ** في المجموعات الخارقة:** + +• الأعضاء الجدد يمكنهم مشاهدة سجل المحادثات كاملًا +• الرسائل المحذوفة ستختفي من أجهزة جميع الأعضاء +• يمكن للمشرفين تثبيت الرسائل +• يمكن لمنشئ المجموعة تعيين رابط للمجموعة + **ملاحظة:** لا يمكنك الرجوع عن هذا القرار. مشاركة إضافة @@ -749,9 +817,12 @@ إذا اخترت العداد، الصورة ستقوم بتدمير ذاتها تلقائيًا بعد استعراضها. إذا اخترت العداد، الفيديو سيقوم بتدمير ذاته تلقائيًا بعد استعراضه. إيقاف - هذه الصورة والنص تم اشتقاقهم من مفتاح التشفير لهذه المحادثة السرية مع ]]>%1$s]]>.\n\nإذا كانت مطابقة لما يظهر على جهاز ]]>%2$s\'s]]> ، التشفير من البداية للنهاية مضمون.\n\nللاستزادة، اطلع على telegram.org + هذه الصورة والنص تم اشتقاقهم من مفتاح التشفير لهذه المحادثة السرية مع **%1$s**. + +إذا كانت مطابقة لما يظهر على جهاز **%2$s\'s** ، التشفير من البداية للنهاية مضمون. + +للاستزادة، اطلع على telegram.org https://telegram.org/faq#secret-chats - اضغط للتحويل لإيموجي غير معروف معلومات هاتف @@ -763,8 +834,11 @@ سم المستخدم يجب أن يتكوّن من ٥ حروف على الأقل. اسم المستخدم يجب ألا يتخطى ٣٢ حرف كحد أقصى. المعذرة، اسم المستخدم لا يمكن أن يبدأ برقم. - يمكنك اختيار اسم مستخدم في ]]>تيليجرام]]>. إذا قمت بذلك، سيستطيع الناس إيجادك باستخدام الاسم المستخدم والتواصل معك من دون معرفة رقمك.\n\nتستطيع استخدام ]]>حروف اللغة الإنجليزية]]>, ]]>وأرقامها ]]> وكذلك الخط. لا بد من استخدام ]]>٥]]> حروف على الأقل.. - هذا الرابط يقوم بفتح محادثة معك في تيليجرام:\n%1$s + يمكنك اختيار اسم مستخدم في **تيليجرام**. إذا قمت بذلك، سيستطيع الناس إيجادك باستخدام الاسم المستخدم والتواصل معك من دون معرفة رقمك. + +تستطيع استخدام **حروف اللغة الإنجليزية**, **وأرقامها ** وكذلك الخط. لا بد من استخدام **٥** حروف على الأقل.. + هذا الرابط يقوم بفتح محادثة معك في تيليجرام: +%1$s جارٍ مراجعة اسم المستخدم... %1$s متاح. لا يوجد @@ -774,6 +848,13 @@ إضافة ملصق إضافة أقنعة إضافة إلى الملصقات + أضف للمفضلة + تم إضافة الملصق للمفضلة + تم حذف الملصق من المفضلة + مستخدمة حديثًا + المفضلة + ملصقات المجموعة + احذف من المفضلة إضافة إلى الأقنعة لا يوجد ملصقات تم حذف الملصقات @@ -878,16 +959,17 @@ رسائل مثبتة اللغة مخصص - نرجو الأخذ بالعلم أن الدعم الفني في تيليجرام يقوم به مجموعة من المتطوعين. نحن نحاول الرد بسرعة قدر المستطاع، لكن ربما نستغرق القليل من الوقت.\n\nيرجى الإطلاع على صفحة الأسئلة الأكثر شيوعًا]]>: يوجد بها إجابات لمعظم الأسئلة و حلول للمشاكل و]]>. + نرجو الأخذ بالعلم أن الدعم الفني في تيليجرام يقوم به مجموعة من المتطوعين. نحن نحاول الرد بسرعة قدر المستطاع، لكن ربما نستغرق القليل من الوقت. + +يرجى الإطلاع على صفحة الأسئلة الأكثر شيوعًا]]>: يوجد بها إجابات لمعظم الأسئلة و حلول للمشاكل و]]>. اسأل أحد المتطوعين + الأسئلة المتكررة الأسئلة الشائعة عن تيليجرام https://telegram.org/faq/ar سياسة الخصوصية https://telegram.org/privacy حذف التعريب؟ ملف التعريب غير صحيح - تمكين - تعطيل خدمة التشغيل المستمر إعادة تشغيل التطبيق في حال تم إقفاله من المستخدم أو النظام. هذا سيساعد في إظهار التطبيق للإشعارات. الاتصال في الخلفية @@ -906,6 +988,9 @@ قصير طويل تنزيل تلقائي للوسائط + تنزيل الوسائط تلقائياً + إعادة تعيين إعدادات التنزيل التلقائي + هل أنت متأكد من رغبتك في إعادة تعيين إعدادات التنزيل التلقائي؟ عند استخدام البيانات الخلوية عند الاتصال بالشبكة اللاسلكية عند تواجدك خارج البلاد @@ -920,19 +1005,17 @@ الأولوية كما في الإعدادات افتراضي - منخفض مرتفع القصوى للأبد إعادة الإشعارات - يمكنك تغيير رقم تيليجرام الخاص بك هنا. حسابك وجميع بياناتك التي في السحاب بما فيها رسائلك، الوسائط، جهات الاتصال وغيرها سيتم نقلها للرقم الجديد.هام:]]> جميع جهات الاتصال الخاصة بك في تيليجرام سيضاف لها رقمك الجديد]]>، في حال تواجد بها رقمك القديم ولم تقم بحظره في تيليجرام. + يمكنك تغيير رقم تيليجرام الخاص بك هنا. حسابك وجميع بياناتك التي في السحاب بما فيها رسائلك، الوسائط، جهات الاتصال وغيرها سيتم نقلها للرقم الجديد.**هام:** جميع جهات الاتصال الخاصة بك في تيليجرام سيضاف لها **رقمك الجديد**، في حال تواجد بها رقمك القديم ولم تقم بحظره في تيليجرام. جميع جهات الاتصال الخاصة بك في تيليجرام سيضاف لها رقمك الجديد، في حال تواجد بها رقمك القديم ولم تقم بحظره في تيليجرام. تغيير الرقم الرقم الجديد سيتم إرسال رسالة قصيرة تحتوي على رمز التفعيل الخاص إلى رقمك الجديد. الرقم %1$s لديه حساب تيليجرام مسبقًا. يرجى حذف هذا الحساب قبل محاولة تغيير رقمك. آخر - تعطيل تعطيل مفعل معطل @@ -959,6 +1042,10 @@ قائمة المعالجة استيراد جهات الاتصال إعادة تنزيل جهات الاتصال + إعادة تعيين الحوارات + فعل الكاميرا داخل التطبيق + عطل الكاميرا داخل التطبيق + إعادة ضبط جهات الاتصال المستوردة يمكنك تغيير اللغة الخاصة بك من الإعدادات لاحقًا. اختر لغتك أخرى @@ -972,10 +1059,22 @@ إعدادات بروكسي SOCKS 5 استخدم البروكسي للمكالمات خوادم البروكسي ربما تقلل من جودة الاتصال في تيليجرام. + ملاحظة + مساحة تخزين جهازك تقريبًا ممتلئة. لإيجاد مساحة إضافية، يمكنك جعل تيليجرام القيام بتنزيل الوسائط الحديثة فقط. + احذف الوسائط بعد + لا تحذف للأبد + جهات الاتصال + المحادثات الخاصة + المحادثات الجماعية + القنوات + قيّد بالحجم + حتى %1$s قاعدة البيانات على الجهاز هل ترغب في مسح الرسائل المحفوظة في الذاكرة المخبئية؟ - مسح قاعدة البيانات على الجهاز سيحذف الرسائل التي تم تنزيلها على جهازك ويقوم بضغط قاعدة البيانات لتوفير مساحة على جهازك. تيليجرام يحتاج لبعض البيانات ليعمل، لذلك حجم قاعدة البيانات لن يصل إلى صفر.\n\nهذه العملية ربما تأخذ عدة دقائق لتتم. + مسح قاعدة البيانات على الجهاز سيحذف الرسائل التي تم تنزيلها على جهازك ويقوم بضغط قاعدة البيانات لتوفير مساحة على جهازك. تيليجرام يحتاج لبعض البيانات ليعمل، لذلك حجم قاعدة البيانات لن يصل إلى صفر. + +هذه العملية ربما تأخذ عدة دقائق لتتم. مسح الذاكرة المخبئية مسح جاري الحساب... @@ -988,7 +1087,7 @@ الملفات الأخرى إفراغ الإحتفاظ بالوسائط - الصور، المقاطع المرئية، وجميع الملفات المحفوظة في خوادمنا التي لم تستخدمها ]]> خلال هذه المدة سيتم حذفها لتوفير المساحة. ملفات الوسائط ستبقى في خوادم تيليجرام ويمكنك إعادة تنزيلها متى ما احتجتها مرة أخرى. + الصور، المقاطع المرئية، وجميع الملفات المحفوظة في خوادمنا التي لم ** تستخدمها ** خلال هذه المدة سيتم حذفها لتوفير المساحة. ملفات الوسائط ستبقى في خوادم تيليجرام ويمكنك إعادة تنزيلها متى ما احتجتها مرة أخرى. إلى الأبد الرسائل الصوتية الرسائل المرئية @@ -1005,8 +1104,9 @@ قفل رمز المرور غيًر رمز المرور - عندما تختار رمز مرور، ستظهر علامة قفل في صفحة المحادثات. اضغط عليها لقفل أو فتح تيليجرام.\n\nملاحظة: إذا نسيت رمز المرور، ستحتاج لحذف وإعادة تنزيل التطبيق. وستخسر كافة محادثاتك السرية وستعود المحادثات العادية فور إعادة تسجيل الدخول. - سوف ترى الآن أيقونة قفل في صفحة المحادثات. اضغط عليها لقفل تطبيق تيليجرام برمز المرور الجديد الخاص بك. + عندما تختار رمز مرور، ستظهر علامة قفل في صفحة المحادثات. اضغط عليها لقفل أو فتح تيليجرام. + +ملاحظة: إذا نسيت رمز المرور، ستحتاج لحذف وإعادة تنزيل التطبيق. وستخسر كافة محادثاتك السرية وستعود المحادثات العادية فور إعادة تسجيل الدخول. الرمز كلمة المرور قم بإدخال رمز المرور الخاص بك الحالي @@ -1014,7 +1114,6 @@ قم بإدخال رمز المرور الخاص بك الجديد قم بإدخال رمز المرور الخاص بك قم بإعادة إدخال رمز المرور الخاص بك الجديدة - رمز مرور غير صحيح رموز المرور غير متطابقة قفل تلقائي اطلب رمز المرور إذا غبت فترة محددة من الزمن. @@ -1025,7 +1124,9 @@ حساس اللمس لم يتم التعرف على البصمة. حاول مرة أخرى اسمح بتصوير الشاشة - إذا تم التفعيل، ستتمكن من التقاط صور للشاشة داخل التطبيق، لكن النظام سيعرض محادثاتك في شاشة تعدد المهام حتى في حال تفعيل رمز المرور.\n\nربما تحتاج لإعادة تشغيل التطبيق لتفعيل هذا الخيار. + إذا تم التفعيل، ستتمكن من التقاط صور للشاشة داخل التطبيق، لكن النظام سيعرض محادثاتك في شاشة تعدد المهام حتى في حال تفعيل رمز المرور. + +ربما تحتاج لإعادة تشغيل التطبيق لتفعيل هذا الخيار. الملفات المشاركة الوسائط المشتركة @@ -1046,19 +1147,40 @@ متر يبعد كيلومتر يبعد أرسل مكانك الحالي + شارك مكاني الحي لمدة... + أوقف مشاركة الموقع + هل أنت متأكد من رغبتك في إيقاف مشاركة موقعك الحي مع %1$s? + هل أنت متأكد من رغبتك في إيقاف مشاركة موقعك الحي مع %1$s? + هل أنت متأكد من رغبتك في إيقاف مشاركة موقعك الحي؟ + يتم تحديثه بشكل مباشر أثناء تحركك أرسل المكان المختار المكان + مكان دقيق لدرجة %1$s أو اختر مكان + اسحب للأعلى لمشاهدة الأماكن القريبة + المواقع الحية + لمدة 15 دقيقة + لمدة ساعة + لمدة 8 ساعات + تم تحديثه + تم تحديثه للتو + أنت و %1$s + %1$s يقوم بالمشاركة مع %2$s + إيقاف الكل + تقوم بمشاركة موقعك الحي مع %1$s + اختر المدة التي سيتمكن %1$s من مشاهدة موقعك بالضبط. + اختر المدة التي سيتمكن أعضاء هذه المجموعة من مشاهدة موقعك بالضبط. + يبدو أن خدمة تحديد المواقع معطلة لديك، يرجى تفعيلها لاستخدام خصائص تيليجرام التي تتطلبها. عرض كافة الوسائط + أظهر في المحادثة حفظ في الجهاز %1$d من %2$d الألبوم كافة الصور كافة الوسائط لا توجد صور حتى الآن - لا يوجد مقاطع مرئية بعد فضلًا، قم بتنزيل الوسائط أولًا لا توجد صور حديثة لا يوجد صور متحركة حديثة @@ -1068,7 +1190,6 @@ البحث على الإنترنت البحث عن صور متحركة قص الصورة - تعديل الصورة تحسين التفتيح التباين @@ -1080,15 +1201,12 @@ الحبوب زيادة الحدة تلاشي - تظليل الظلال الإضاءات - المنحنيات الكل أحمر أخضر أزرق - الطمس إيقاف طولي قطري @@ -1097,16 +1215,11 @@ تجاهل التغييرات؟ هل ترغب في مسح سجل البحث؟ مسح - صور - مقطع مرئي أضف تعليق... تعليق الصورة تعليق المقطع المرئي تعليق الصورة المتحركة تعليق - ارسم - ملصقات - نص حذف تعديل تكرار @@ -1115,6 +1228,8 @@ إعادة تعيين الأصل مربع + اعرض الصور كرسائل متفرقة + اجمع الصور في رسالة واحدة التحقق بخطوتين تعيين كلمة مرور إضافية @@ -1129,7 +1244,9 @@ فضلًا أضف بريد إلكتروني صحيح. هذه هي الطريقة الوحيدة لإستعادة كلمة المرور بعد نسيانها. تخطّي تحذير - الموضوع جدّيّ.\n\nإذا نسيت كلمة المرور الخاصة بك، لن تستطيع الدخول على حساب تيليجرام الخاص بك. لن يكون هناك طريقة لإستعادته. + الموضوع جدّيّ. + +إذا نسيت كلمة المرور الخاصة بك، لن تستطيع الدخول على حساب تيليجرام الخاص بك. لن يكون هناك طريقة لإستعادته. تقريبًا انتهينا! يرجى الذهاب لبريدك الإلكتروني والضغط على رابط التأكيد لإنهاء إعدادات التحقق بخطوتين. تأكد من الإطّلاع على مجلد الرسائل الغير مرغوب بها أيضًا. تم بنجاح! @@ -1143,19 +1260,28 @@ فضلًا اختر تلميحة لكلمة المرور الخاصة بك: كلمتي المرور غير متطابقة إنهاء عملية إعداد التحقق بخطوتين - فضلًا اتّبع هذه الخطوات لإكمال إعدادات التحقق بخطوتين:\n\n١. قم بالإطلاع على بريدة الإلكتروني بما فيه مجلد الرسائل الغير مرغوب بها\n%1$s\n\n٢. اضغط على رابط التأكيد + فضلًا اتّبع هذه الخطوات لإكمال إعدادات التحقق بخطوتين: + +١. قم بالإطلاع على بريدة الإلكتروني بما فيه مجلد الرسائل الغير مرغوب بها +%1$s + +٢. اضغط على رابط التأكيد التلميحة يجب أن تكون مختلفة عن كلمة المرور ذاتها بريد إلكتروني غير صحيح عذرًا لم تقم بإضافة بريد إلكتروني لإستعادة كلمة المرور عند اختيارها، لذلك خياراتك المتبقية هي تذكّر كلمة المرور أو إعادة تعيين حسابك. - لقد قمنا بإرسال رمز الإستعادة إلى بريدك الإلكتروني الذي اخترته مسبقًا:\n\n%1$s + لقد قمنا بإرسال رمز الإستعادة إلى بريدك الإلكتروني الذي اخترته مسبقًا: + +%1$s يرجى الإطلاع على بريدك الإلكتروني وإدخال الرمز المكون من ٦ أرقام الذي قمنا بإرساله هنا. هل تواجه صعوبات في الدخول على بريدك الإلكتروني %1$s؟ إذا لم تستطع الدخول على بريد الإسترداد الخاص بك، خياراتك المتبقية هي تذكّر كلمة المرور أو إعادة تعيين حسابك. إعادة تعيين حسابي إذا قمت بإعادة تعيين حسابك، ستفقد كافّة محادثاتك ورسائلك، بالإضافة إلى الوسائط والملفات التي تمت مشاركتها. تحذير - لا يمكنك الرجوع عن هذا القرار\n\nإذا قمت بإعادة تعيين حسابك، كافة رسائلك ومحادثاتك سيتم حذفها. + لا يمكنك الرجوع عن هذا القرار + +إذا قمت بإعادة تعيين حسابك، كافة رسائلك ومحادثاتك سيتم حذفها. إعادة تعيين كلمة المرور تم تفعيل التحقق بخطوتين، لذلك حسابك محميّ بكلمة مرور إضافية. @@ -1163,7 +1289,8 @@ كلمة مرور الإسترداد الرمز تم تعطيل كلمة المرور - لقد قمت بتفعيل التحقق بخطوتين.\nعند محاولة تسجيل الدخول على حساب تيليجرام الخاص بك من جهاز جديد، سيتم طلب كلمة المرور التي اخترتها هنا منك. + لقد قمت بتفعيل التحقق بخطوتين. +عند محاولة تسجيل الدخول على حساب تيليجرام الخاص بك من جهاز جديد، سيتم طلب كلمة المرور التي اخترتها هنا منك. البريد الإلكتروني لإسترداد الحساب %1$s غير فعّال بعد ويلزم تفعيله البيانات والتخزين @@ -1207,8 +1334,6 @@ تدمير ذاتي للحساب إذا كنت غائب لمدة إذا لم تقم بتسجيل الدخول خلال هذه الفترة، سيتم حذف حسابك بالإضافة إلى مجموعاتك، رسائلك، وجهات الاتصال الخاصة بك في تيليجرام. - هل ترغب في حذف حسابك؟ - قم بتغيير من يستطيع رؤية آخر ظهور خاص بك. من يستطيع رؤية آخر ظهور خاص بك؟ إضافة استثناءات هام: لن تستطيع رؤية آخر ظهور للأشخاص اللذين اخترت ألا يروا آخر ظهور لك. سيتم عرض آخر ظهور تقريبي كبديل (قريبًا، خلال أسبوع، خلال شهر). @@ -1238,7 +1363,6 @@ الند للند تعطيل الند للند سيجعل جميع المكالمات تعتمد على خوادم تيليجرام لتجنب الإفصاح عن الـ IP الخاص لك، لكن يقوم بتنزيل جودة الصوت قليلًا. - تحرير الفيديو جارٍ إرسال المقطع المرئي... جاري إرسال صورة متحركة... @@ -1255,8 +1379,6 @@ إيقاف البوت إعادة تعيين البوت - التالي - رجوع تم فتح حفظ @@ -1277,8 +1399,6 @@ تعيين موافق قطع - نعم - لا لقد قمت بالدخول للمجموعة باستخدام رابط الدعوة un1 قام بالدخول للمجموعة باستخدام رابط الدعوة @@ -1312,6 +1432,7 @@ انتهت صلاحية الفيديو صورة متحركة موقع + الموقع الحي جهة اتصال ملف ملصق @@ -1340,12 +1461,12 @@ هل ترغب بإضافة %1$s للمحادثة %2$s؟ عدد الرسائل الحديثة المراد إعادة تحويلها: إضافة %1$s للمجموعة؟ - هذا المستخدم عضو مسبق في هذه المجموعة - ؟%1$s هل تريد إعادة توجيه الرسائل إلى هل ترغب في إرسال رسالة إلى %1$s؟ هل ترغب في مشاركة اللعبة مع %1$s؟ أرسل جهة الاتصال إلى %1$s؟ - نرجو الأخذ بالعلم أنه يمكنك استخدام تيليجرام على أجهزتك المتعددة بسهولة تامة وفي وقت واحد.\n\nوتذكر، تسجيل الخروج يحذف كافة محادثاتك السرية. + نرجو الأخذ بالعلم أنه يمكنك استخدام تيليجرام على أجهزتك المتعددة بسهولة تامة وفي وقت واحد. + +وتذكر، تسجيل الخروج يحذف كافة محادثاتك السرية. هل أنت متأكد من تسجيل الخروج من جميع الأجهزة الأخرى باستثناء هذا الجهاز؟ هل أنت متأكد من أنك تريد حذف المجموعة والخروج منها؟ هل أنت متأكد من رغبتك في حذف المحادثة؟ @@ -1356,7 +1477,7 @@ هذا البوت يرغب في معرفة موقعك في كل مرة ترسل له طلب. هذا يمكن الاستفادة منه لإعطائك نتائج مناسبة لموقعك. هل ترغب في مشاركة رقم هاتفك؟ البوت سيعلم رقم هاتفك. سيكون هذا مفيد لتفعيله في خدمات أخرى. - هل أنت متأكد من رغبتك في مشاركة رقم هاتفك %1$s مع ]]>%2$s]]>؟ + هل أنت متأكد من رغبتك في مشاركة رقم هاتفك %1$s مع **%2$s**؟ هل أنت متأكد من رغبتك في مشاركة رقم هاتفك؟ هل أنت متأكد من رغبتك في حظر جهة الاتصال هذه؟ هل أنت متأكد من رغبتك في إزالة الحظر عن جهة الاتصال هذه؟ @@ -1365,18 +1486,15 @@ هل أنت متأكد من رغبتك في إلغاء التسجيل؟ هل أنت متأكد من رغبتك في حذف سجل المحادثات؟ حذف كافة المحادثات والوسائط المتعلقة بهذه القناة من الذاكرة المخبئية؟ - حذف كافة المحادثات والوسائط المتعلقة بهذه المجموعة من الذاكرة المخبئية؟ + حذف جميع الرسائل والوسائط من الذاكرة المخبئية للمجموعة هذه؟ هل أنت متأكد من رغبتك في حذف %1$s؟ هل ترغب في إرسال رسالة إلى %1$s؟ هل ترغب في مشاركة اللعبة مع %1$s؟ أرسل جهة الاتصال إلى %1$s؟ - ؟%1$s هل تريد إعادة توجيه الرسائل إلى - .Sorry, this feature is currently not available in your country لا يوجد حساب تيليجرام بهذا الاسم. هذا البوت لا يستطيع الدخول للمجموعات. هل ترغب في تفعيل معاينة الروابط المطولة في المحادثات السرية؟ تذكر أن هذه المعاينات يتم إنشاؤها في خوادم تيليجرام. يرجى ملاحظة أن البوتات أثناء الكتابة يتم تطويرها من مطورين مستقلين. لكي تعمل هذه البوتات، ما تكتبه بعد معرف البوت يذهب لمطور البوت. - هل ترغب في تفعيل خاصية رفع الجهاز لإرسال الرسائل الصوتية؟ المعذرة، لا يمكنك التعديل على هذه الرسالة. فصلًا قم بالسماح لتيليجرام باستقبال رسائل قصيرة ليتمكن من إدخال الرمز لك تلقائيًا. فصلًا قم بالسماح لتيليجرام باستقبال اتصالات ليتمكن من إدخال الرمز لك تلقائيًا. @@ -1407,14 +1525,20 @@ آمن قوي مرتبط بالسحاب - تيليجرام هو أسرع]]> تطبيق مراسلة في العالم.\n,وهو كذلك مجاني]]> و آمن]]>. - تيليجرام]]> يوصل الرّسائل أسرع من\nأي تطبيق آخر. - تيليجرام]]> مجّاني دائماً. لا إعلانات.\nدون رسوم إشتراك. - تيليجرام]]> يحمي الرسائل الخاصة بك\nمن هجمات المخترقين. - تيليجرام]]> لا يفرض حدوداً على حجم\nمحادثاتك و وسائطك. - تيليجرام]]> يمكنك من الوصول إلى الرسائل الخاصة بك\nمن أجهزة متعددة. + تيليجرام هو **أسرع** تطبيق مراسلة في العالم. +,وهو كذلك **مجاني** و **آمن**. + **تيليجرام** يوصل الرّسائل أسرع من +أي تطبيق آخر. + **تيليجرام** مجّاني دائماً. لا إعلانات. +دون رسوم إشتراك. + **تيليجرام** يحمي الرسائل الخاصة بك +من هجمات المخترقين. + **تيليجرام** لا يفرض حدوداً على حجم +محادثاتك و وسائطك. + **تيليجرام** يمكنك من الوصول إلى الرسائل الخاصة بك +من أجهزة متعددة. إبدأ المراسلة - + إعدادات الحساب استخدم بيانات أقل للاتصال. مكالمات واردة @@ -1431,7 +1555,7 @@ مكالمة تيليجرام الحالية إنهاء المكالمة مكالمة أخرى نشطة - لديك حاليًا مكالمة نشطة مع %1$s]]>. هل ترغب في إنهاء المكالمة وبدء مكالمة جديدة مع %2$s]]>؟ + لديك حاليًا مكالمة نشطة مع **%1$s**. هل ترغب في إنهاء المكالمة وبدء مكالمة جديدة مع **%2$s**؟ مكالمات صوتية النغمة يمكنك تخصيص نغمة الاتصال عندما يتصل بك هذا المعرف من خلال تيليجرام. @@ -1455,12 +1579,10 @@ مكالمات ملغاة مكالمة مرفوضة %1$s (%2$s) - هذه الصورة والنص تم اشتقاقهم من مفتاح التشفير لهذه المحادثة السرية مع ]]>%1$s]]>.\n\nإذا كانت مطابقة لما يظهر على جهاز ]]>%2$s\'s]]> ، التشفير من البداية للنهاية مضمون. لم تقم بإجراء أية مكالمة بعد. - إصدار تيليجرام الخاص بـ]]>%1$s]]> غير متوافق. ينبغي عليه تحديث إصدارهم لتتمكن من الاتصال بهم. - تطبيق تيليجرام الخاص بـ]]>%1$s]]> لا يدعم المكالمات. ينبغي عليه تحديث تطبيقهم لتتمكن من الاتصال بهم. + إصدار تيليجرام الخاص بـ**%1$s** غير متوافق. ينبغي عليه تحديث إصدارهم لتتمكن من الاتصال بهم. + تطبيق تيليجرام الخاص بـ**%1$s** لا يدعم المكالمات. ينبغي عليه تحديث تطبيقهم لتتمكن من الاتصال بهم. فضلًا قم بتقييم جودة الاتصال في تيليجرام - هل ترغب في ترك أية ملاحظات لتطوير خدمة المكالمات لدينا؟ تيليجرام يحتاج للسماح له بالوصول للمايكروفون الخاص بك لتتمكن من إجراء مكالمات. أضف تعليق اختياري أعد الاتصال بالرقم @@ -1472,7 +1594,7 @@ سماعة بلوتوث الرجوع للمكالمة - المعذرة، ]]>%1$s]]> لا يستقبل مكالمات. + المعذرة، **%1$s** لا يستقبل مكالمات. إذا كانت هذه الايموجيات مطابقة للتي تظهر على شاشة %1$s، فالمكالمة 100%% آمنة. قيم المكالمة ماذا حدث؟ @@ -1480,12 +1602,36 @@ هذا لن يفصح عن محتوى محادثتك، لكن سيساعد في حل المشكلة بشكل أسرع. شكرًا لمساعدتك في جعل مكالمة تيليجرام أفضل. + لا يوجد مستقبلين + مستقبل واحد + مستقبلان + %1$d مستقبلون + %1$d مستقبل + %1$d مستقبل %1$d متصل %1$d متصل %1$d متصل %1$d متصل %1$d متصل %1$d متصل + لا يوجد جهات اتصال على تيليجرام + جهة اتصال واحدة على تيليجرام + جهتي اتصال على تيليجرام + %1$d جهات اتصال على تيليجرام + %1$d جهة اتصال على تيليجرام + %1$d جهة اتصال على تيليجرام + مرحبا، أنا استخدم تيليجرام للمحادثة - انضم إلينا من هنا: %2$s + مرحبا، أنا استخدم تيليجرام للمحادثة - كذلك جهة اتصال أخرى. انضم إلينا من هنا: %2$s + مرحبا، أنا استخدم تيليجرام للمحادثة - كذلك جهتي اتصال أخرى. انضم إلينا من هنا: %2$s + مرحبا، أنا استخدم تيليجرام للمحادثة - كذلك %1$d جهات اتصال أخرى. انضم إلينا من هنا: %2$s + مرحبا، أنا استخدم تيليجرام للمحادثة - كذلك %1$d جهة اتصال أخرى. انضم إلينا من هنا: %2$s + مرحبا، أنا استخدم تيليجرام للمحادثة - كذلك %1$d جهة اتصال أخرى. انضم إلينا من هنا: %2$s + صفر + محادثة واحدة + محادثتين + %1$d محادثات + %1$d محادثة + %1$d محادثة %1$d أعضاء %1$d عضو %1$d عضوان @@ -1498,6 +1644,12 @@ كذلك %1$d أعضاء آخرون يقومون بالكتابة كذلك %1$d عضو آخرون يقومون بالكتابة كذلك %1$d عضو آخرون يقومون بالكتابة + + %1$s و شخص اخر يقوموا بالكتابة + %1$s و شخصان يقوموا بالكتابة + %1$s و %2$d أشخاص يقوموا بالكتابة + %1$s و %2$d شخص يقوموا بالكتابة + %1$s و %2$d شخص يقوموا بالكتابة لا يوجد رسائل جديدة %1$d رسالة جديدة %1$d رسالتان جديدتان @@ -1588,61 +1740,55 @@ %1$d ملصقات %1$d ملصق %1$d ملصق - %1$d صور - %1$d صورة - %1$d صور - %1$d صور - %1$d صور - %1$d صور + لا يوجد مشتركين + مشترك واحد + مشتركان + %1$d مشتركون + %1$d مشترك + %1$d مشترك %1$d %1$d %1$d %1$d %1$d %1$d - آخر ظهور قبل %1$d دقيقة - آخر ظهور قبل %1$d دقيقة - آخر ظهور قبل %1$d دقيقة - آخر ظهور قبل %1$d دقيقة - آخر ظهور قبل %1$d دقيقة - آخر ظهور قبل %1$d دقيقة - آخر ظهور قبل %1$d ساعة - آخر ظهور قبل %1$d ساعة - آخر ظهور قبل %1$d ساعة - آخر ظهور قبل %1$d ساعة - آخر ظهور قبل %1$d ساعة - آخر ظهور قبل %1$d ساعة - %1$d]]> ثواني - %1$d]]> ثانية - %1$d]]> ثانية - %1$d]]> ثانية - %1$d]]> ثانية - %1$d]]> ثانية - %1$d]]> دقائق - %1$d]]> دقيقة - %1$d]]> دقيقة - %1$d]]> دقيقة - %1$d]]> دقيقة - %1$d]]> دقيقة - %1$d]]> ساعات - %1$d]]> ساعة - %1$d]]> ساعة - %1$d]]> ساعة - %1$d]]> ساعة - %1$d]]> ساعة - %1$d]]> أيام - %1$d]]> يوم - %1$d]]> يوم - %1$d]]> يوم - %1$d]]> يوم - %1$d]]> يوم + تم تحديثه قبل ٠ دقيقة + تم تحديثه قبل دقيقة واحدة + تم تحديثه قبل دقيقتين + تم تحديثه قبل %1$d دقائق + تم تحديثه قبل %1$d دقيقة + تم تحديثه قبل %1$d دقيقة + **%1$d** ثواني + **%1$d** ثانية + **%1$d** ثانية + **%1$d** ثانية + **%1$d** ثانية + **%1$d** ثانية + **%1$d** دقائق + **%1$d** دقيقة + **%1$d** دقيقة + **%1$d** دقيقة + **%1$d** دقيقة + **%1$d** دقيقة + **%1$d** ساعات + **%1$d** ساعة + **%1$d** ساعة + **%1$d** ساعة + **%1$d** ساعة + **%1$d** ساعة + **%1$d** أيام + **%1$d** يوم + **%1$d** يوم + **%1$d** يوم + **%1$d** يوم + **%1$d** يوم - %1$d رسالة معاد توجيهها - الرسالة المعاد توجيهها - %1$d رسالة معاد توجيهها - %1$d رسالة معاد توجيهها - %1$d رسالة معاد توجيهها - %1$d رسالة معاد توجيهها + %1$d رسالة معاد توجيهها + الرسالة المعاد توجيهها + %1$d رسالة معاد توجيهها + %1$d رسالة معاد توجيهها + %1$d رسالة معاد توجيهها + %1$d رسالة معاد توجيهها %1$d ملف معاد توجيهه ملف معاد توجيهه %1$d ملف معاد توجيهه diff --git a/TMessagesProj/src/main/res/values-de/strings.xml b/TMessagesProj/src/main/res/values-de/strings.xml index f0b1ee3a2..a3d122bf0 100644 --- a/TMessagesProj/src/main/res/values-de/strings.xml +++ b/TMessagesProj/src/main/res/values-de/strings.xml @@ -1,7 +1,4 @@ - - - Telegram Telegram Beta @@ -14,23 +11,25 @@ Dein Telefon Bitte bestätige deine Landesvorwahl und deine Telefonnummer. Wähle ein Land - Falsche Landesvorwahl + Ungültige Landeskennzahl Nummer verifizieren - Wir haben dir eine SMS mit Aktivierungscode an ]]>%1$s]]> gesendet. - Code wurde dir per ]]>Telegram]]> an deine aktive App geschickt. - Wir rufen dich jetzt unter ]]>%1$s]]> an. Bitte den Anruf nicht annehmen, Telegram kümmert sich um alles. - Wir rufen ]]>%1$s]]>an, um dir einen Code zu diktieren. + Wir haben dir eine SMS mit Aktivierungscode an **%1$s** gesendet. + Code wurde dir per **Telegram** an deine aktive App geschickt. + Wir rufen dich jetzt unter **%1$s** an. Bitte den Anruf nicht annehmen, Telegram kümmert sich um alles. + Wir rufen **%1$s** an, um dir einen Code zu diktieren. Wir rufen dich an in %1$d:%2$02d - Wir senden Dir eine SMS in %1$d:%2$02d + Wir senden dir eine SMS in %1$d:%2$02d Wir rufen dich an… Code Falsche Nummer? Code nicht erhalten? Zurücksetzung abbrechen - Jemand mit Zugang zu deiner Telefonnummer ]]>%1$s]]> hat die Kontolöschung und Zurücksetzung der zweistufigen Bestätigung beantragt. Wenn du das nicht selbst gewesen bist, tippe den Code der SMS ein, den wir dir gerade gesendet haben. + Jemand mit Zugang zu deiner Telefonnummer **%1$s** hat die Kontolöschung und Zurücksetzung der zweistufigen Bestätigung beantragt. Wenn du das nicht selbst gewesen bist, tippe den Code der SMS ein, den wir dir gerade gesendet haben. Konto zurücksetzen - Da dein Konto ]]>%1$s]]> aktiv und durch ein Kennwort geschützt ist, löschen wir es aus Sicherheitsgründen in einer Woche. Du kannst den Vorgang jederzeit abbrechen. + Da dein Konto **%1$s** aktiv und durch ein Kennwort geschützt ist, löschen wir es aus Sicherheitsgründen in einer Woche. + +Du kannst den Vorgang jederzeit abbrechen. Du kannst dein Konto zurücksetzen in: Deine vorherigen Versuche das Konto zurückzusetzen wurden durch den aktiven Nutzer abgebrochen. Bitte in 7 Tagen erneut probieren. ZURÜCKSETZEN @@ -69,7 +68,6 @@ Kartenprüfnummer (CVV) MM/JJ Rechnungsadresse - Karteninhaber Vollständiger Name Zahlungsinformationen speichern Du kannst deine Zahlungsinformationen für zukünftige Bestellungen speichern. @@ -93,7 +91,20 @@ Tut uns sehr leid. Die Zahlung wurde abgelehnt. Kann den Zahlungsserver nicht erreichen. Bitte prüfe deine Internetverbindung und versuche es erneut. Warnung - Weder Telegram noch %1$s haben Zugriff auf deine Kreditkartendaten. Kreditkartendetails werden nur vom Zahlungssystem %2$s abgewickelt.\n\nZahlungen gehen direkt an den Entwickler von %1$s. Telegram kann keine Haftung übernehmen, du handelst auf eigene Gefahr. Wende dich bei Problemen bitte direkt an den Entwickler von %1$s oder an deine Bank. + Weder Telegram noch %1$s haben Zugriff auf deine Kreditkartendaten. Kreditkartendetails werden nur vom Zahlungssystem %2$s abgewickelt. + +Zahlungen gehen direkt an den Entwickler von %1$s. Telegram kann keine Haftung übernehmen, du handelst auf eigene Gefahr. Wende dich bei Problemen bitte direkt an den Entwickler von %1$s oder an deine Bank. + Kennwort & E-Mail + Kennwort + Kennwort eingeben + Kennwort erneut eingeben + Um deine Zahlungsinformationen zu schützen, erstelle bitte ein Kennwort. Du brauchst dieses bei der Anmeldung. + Wiederherstellung + Deine E-Mail + Falls du dein Kennwort vergisst, benötigen wir deine richtige E-Mail-Adresse. + Nummer wird an %1$s als Rechnungsinformation weitergeleitet. + E-Mail wird an %1$s als Rechnungsinformation weitergeleitet. + Nummer und E-Mail werden an %1$s als Rechnungsinformation weitergeleitet. Neue Unterhaltung Einstellungen @@ -102,7 +113,11 @@ gestern Keine Ergebnisse Noch keine Chats… - Tippe unten auf den Stift für deine erste\nChatnachricht oder auf den Menüknopf\num die restlichen Optionen zu öffnen. + Kürzlich angeschaut + AUSBLENDEN + Tippe unten auf den Stift für deine erste +Chatnachricht oder auf den Menüknopf +um die restlichen Optionen zu öffnen. Warte auf Netzwerk... Verbinde... Verbinde mit Proxy... @@ -119,16 +134,18 @@ Tausche Schlüssel aus... %s ist deinem geheimen Chat beigetreten. Du bist dem geheimen Chat beigetreten. - Verlauf löschen + Verlauf leeren Cache leeren Löschen und verlassen Chat löschen Gelöschtes Konto Chat auswählen - Tippen und Halten + Weiterleiten an... Geheimes Bild Geheimes Video - %1$s benutzt eine ältere Version von Telegram, sodass Bilder in Geheimen Chats im Kompatibilitätsmodus angezeigt werden.\n\nSobald %2$s Telegram aktualisiert, werden Bilder mit Timern von 1 Minute und kürzer per \"Tippen und Halten\" angezeigt. Du wirst benachrichtigt, sobald dein Chatpartner ein Bildschirmfoto macht. + %1$s benutzt eine ältere Version von Telegram, sodass Bilder in Geheimen Chats im Kompatibilitätsmodus angezeigt werden. + +Sobald %2$s Telegram aktualisiert, werden Bilder mit Timern von 1 Minute und kürzer per \"Tippen und Halten\" angezeigt. Du wirst benachrichtigt, sobald dein Chatpartner ein Bildschirmfoto macht. NACHRICHTEN Suche Stummschalten @@ -148,22 +165,18 @@ Rückmeldung zu dieser Vorschau hinterlassen Sticker senden Paket anzeigen - Anheften - Entfernen + Oben anheften + Oben entfernen Fett Kursiv Normal - Um dich nahtlos mit jedem zu verbinden, den du kennst, erlaube ]]>Telegram]]> Zugriff auf deine Kontakte. + Um dich nahtlos mit jedem zu verbinden, den du kennst, erlaube **Telegram** Zugriff auf deine Kontakte. JETZT NICHT WEITER - Öffentlich - Privat Zum Admin machen Keine gesperrten Nutzer - Beschreibe deine Gruppe (optional). - Gruppe verlassen - Gruppe Löschen + Gruppe löschen Gruppe verlassen Gruppe löschen Du verlierst alle Nachrichten der Gruppe. @@ -188,6 +201,7 @@ un1 hat einen Kontakt angeheftet un1 hat %1$s angeheftet un1 hat einen Standort angeheftet + un1 hat einen Live-Standort angeheftet un1 hat ein GIF angeheftet un1 hat ein Musikstück angeheftet Gruppe wurde in eine Supergruppe geändert @@ -235,6 +249,7 @@ Überprüfe Namen... %1$s ist verfügbar. Mitglieder + Abonnenten Sperrliste Gesperrte Nutzer Eingeschränke Nutzer @@ -245,16 +260,15 @@ Möchtest du wirklich diesen Kanal verlassen? Du verlierst dadurch alle Nachrichten des Kanals. Bearbeiten - Wenn du einen öffentlichen Link für deinen Kanal festlegst, kann ihn jeder über die Suche finden und beitreten.\n\nWenn du das nicht möchtest, erstelle besser keinen Link. - Wenn du einen öffentlichen Link für deinen Kanal festlegst, kann ihn jeder über die Suche finden und beitreten.\n\nWenn du das nicht möchtest, erstelle besser keinen Link. - Bitte wähle einen Link für deinen öffentlichen Kanal, damit andere ihn finden und weiter verbreiten können.\n\nWenn du das nicht möchtest, empfehlen wir dir einen privaten Kanal. + Bitte wähle einen Link für deinen öffentlichen Kanal, damit andere ihn finden und weiter verbreiten können. + +Wenn du das nicht möchtest, empfehlen wir dir einen privaten Kanal. Kanal erstellt Bild geändert Bild gelöscht Kanalname zu un2 geändert Du hast leider zu viele öffentliche Benutzernamen erstellt. Du kannst jederzeit den Link einer älteren Gruppe oder eines Kanals entfernen. Ersteller - Administrator STUMM STUMM AUS Admin hinzufügen @@ -262,10 +276,8 @@ Entsperren Gedrückt halten um zu entsperren Per Link einladen - Sicher, dass %1$s ein Administrator werden soll? Admin entlassen Nur Administratoren sehen diese Liste. - Dieser Nutzer ist noch nicht im Kanal; willst du ihn einladen? Jeder, der Telegram installiert hat, kann anhand dieses Links in deinen Kanal. Administratoren helfen dir, deinen Kanal zu verwalten. Tippen und halten, um sie zu löschen. Möchtest du dem Kanal \'%1$s\' beitreten? @@ -285,18 +297,6 @@ Du darfst in diesem Kanal nichts schreiben. %1$s hat dich dem Kanal %2$s hinzugefügt Kanal %1$s hat das Bild geändert - %1$s hat eine Nachricht an den Kanal %2$s gesendet - %1$s hat ein Bild an den Kanal %2$s gesendet - %1$s hat ein Video an den Kanal %2$s gesendet - %1$s hat einen Kontakt an den Kanal %2$s gesendet - %1$s hat einen Standort an den Kanal %2$s gesendet - %1$s hat eine Datei an den Kanal %2$s gesendet - %1$s hat ein GIF an den Kanal %2$s gesendet - %1$s hat eine Sprachnachricht an den Kanal %2$s gesendet - %1$s hat eine Videonachricht an den Kanal %2$s gesendet - %1$s hat ein Musikstück an den Kanal %2$s gesendet - %1$s hat einen Sticker an den Kanal %2$s gesendet - %1$s hat einen %3$s Sticker an den Kanal %2$s gesendet %1$s hat eine Nachricht gesendet %1$s hat ein Bild gesendet %1$s hat ein Video gesendet @@ -324,7 +324,7 @@ Nachrichten von anderen bearbeiten Nachrichten von anderen löschen Nachrichten löschen - Neue Admins hinfügen + Neue Admins hinzufügen Admin entlassen Nutzer sperren Nutzer hinzufügen @@ -342,21 +342,36 @@ Linkvorschau senden Gesperrt bis Dauerhaft - Entfernen Sperren und aus der Gruppe entfernen Gruppe verwalten Kanal verwalten Gruppe verwalten Kanal verwalten + Chatverlauf für neue Mitglieder + Sichtbar + Neue Mitglieder sehen Nachrichten, die vor ihrem Beitritt gesendet wurden. + Versteckt + Neue Mitglieder sehen keine vorherigen Nachrichten. Letzte Aktionen Alle Aktionen Ausgewählte Aktionen Alle Admins - ]]>Noch keine Aktionen!]]>\n\nMitglieder und Admins\nhaben noch keine Aktionen in den\nletzten 48 Stunden durchgeführt. - ]]>Noch keine Aktionen!]]>\n\nMitglieder und Admins\nhaben noch keine Aktionen\nin den letzten 48 Stunden durchgeführt. - ]]>Keine Aktionen gefunden]]>\n\nKeine kürzlichen Ereignisse gefunden,\ndie deinen Suchbegriff beinhalten. - Keine kürzlichen Aktionen gefunden, die \']]>%1$s]]>\' beinhalten. + **Noch keine Aktionen!** + +Mitglieder und Admins +haben noch keine Aktionen in den +letzten 48 Stunden durchgeführt. + **Noch keine Aktionen!** + +Mitglieder und Admins +haben noch keine Aktionen +in den letzten 48 Stunden durchgeführt. + **Keine Aktionen gefunden** + +Keine kürzlichen Ereignisse gefunden, +die deinen Suchbegriff beinhalten. + Keine kürzlichen Aktionen gefunden, die \'**%1$s**\' beinhalten. Was sind letzte Aktionen? Das ist eine Liste aller Aktionen, die von Gruppenmitgliedern und Admins in den letzten 48 Stunden durchgeführt wurden. Das ist eine Liste aller Aktionen, die von Gruppenmitgliedern und Admins in den letzten 48 Stunden durchgeführt wurden. @@ -381,19 +396,25 @@ un1 hat diese Nachricht angeheftet: un1 hat angeheftete Nachricht entfernt un1 hat diese Nachricht gelöscht: + un1 hat das Sticker-Paket der Gruppe geändert + un1 hat das Sticker-Paket der Gruppe entfernt un1 hat den Gruppen-Link geändert: un1 hat den Kanal-Link geändert: - un1 hat den Gruppen-Link entfernt - un1 hat den Kanal-Link entfernt + un1 hat Gruppen-Link entfernt + un1 hat Kanal-Link entfernt Vorheriger Link un1 hat die Gruppenbeschreibung geändert: un1 hat die Kanalbeschreibung geändert: Vorherige Beschreibung + un1 hat den Chatverlauf für neue Mitglieder sichtbar gemacht + un1 hat den Chatverlauf für neue Mitglieder versteckt un1 hat Gruppeneinladungen aktiviert un1 hat Gruppeneinladungen deaktiviert un1 hat Unterschriften aktiviert un1 hat Unterschriften deaktiviert - hat Einschränkungen für %1$s geändert\n\nDauer: %2$s + hat Einschränkungen für %1$s geändert + +Dauer: %2$s Sticker & GIFs senden Medien senden Nachrichten senden @@ -415,7 +436,6 @@ Neue Mitglieder Gruppen-Info Kanal-Info - Gruppen-Einstellungen Gelöschte Nachrichten Bearbeitete Nachrichten Angeheftete Nachrichten @@ -427,16 +447,17 @@ Empfänger hinzufügen Aus Broadcast Liste entfernen - Bitte füge Musikdateien einfach deiner Android Musikbibliothek hinzu, um sie hier zu sehen. + Füge deiner Musikbibliothek Musikdateien hinzu, um sie hier zu sehen. Musik Unbekannter Künstler Unbekannter Titel + Zufallswiedergabe + Reihenfolge umkehren Datei auswählen Freier Speicher: %1$s von %2$s Unbekannter Fehler Zugriffsfehler - Noch keine Dateien… Die Datei darf nicht größer als %1$s sein Speicher nicht eingebunden USB-Transfer aktiv @@ -446,11 +467,22 @@ SD-Karte Ordner Bilder ohne Komprimierung senden + + Gruppen-Sticker + Sticker-Paket auswählen + Paket wählen, welches alle in der Gruppe sehen können. + STICKER-PAKET WÄHLEN + sticker-paket + Erstelle dein eigenes Sticker-Paket mit dem @stickers Bot. + Sticker-Paket nicht gefunden + Erneut probieren oder unten auswählen unsichtbar tippt… tippt... tippen… + %1$s tippt... + %1$s tippen... %1$s nimmt eine Sprachnachricht auf... %1$s nimmt eine Videonachricht auf... %1$s schickt Audio... @@ -469,8 +501,8 @@ spielt ein Spiel... schickt Video... schickt Datei... - Hast du eine Frage\nzu Telegram? - Bild aufnehmen + Hast du eine Frage +zu Telegram? Galerie Standort Video @@ -479,13 +511,18 @@ Noch keine Nachrichten... Weitergeleitete Nachricht Von + von: Keine aktuellen Nachricht Nachricht Meine Nummer teilen Zu Kontakten hinzufügen - %s hat dich zu einem\nEnde-zu-Ende verschlüsselten\nGeheimen Chat eingeladen. - Du hast %s zu einem\nEnde-zu-Ende verschlüsselten\nGeheimen Chat eingeladen. + %s hat dich zu einem +Ende-zu-Ende verschlüsselten +Geheimen Chat eingeladen. + Du hast %s zu einem +Ende-zu-Ende verschlüsselten +Geheimen Chat eingeladen. Geheime Chats in Kürze: Keine Serverspeicherung Selbstzerstörungs-Timer @@ -509,10 +546,9 @@ Lade Linkvorschau... ÖFFNEN MIT... Öffnen in... - URL kopieren %1$s senden - Als Datei senden - Als Datei + Als Datei + Als Dateien URL %1$s öffnen? Darf %1$s deinen Anzeigenamen und deine id (nicht deine Telefonnummer) mit Internetseiten teilen, die du mit diesem Bot öffnest? SPAM MELDEN @@ -523,17 +559,15 @@ Sicher. dass du Spam von diesem Kanal melden möchtest? Du kannst im Moment nur Kontakten schreiben, die auch deine Nummer haben. Derzeit kannst du nur gemeinsame Kontakte Gruppen hinzufügen. - Du hast heute zu viele nicht-gemeinsame Kontakte angeschrieben, bitte probiere es morgen wieder. Du kannst auf Nachrichten antworten, wenn dir die Person zuerst geschrieben hat. - Du kannst diesen Nutzer nicht hinzufügen, weil du heute zu viele nicht-gemeinsame Kontakte angeschrieben hast. Bitte morgen wieder probieren oder einen anderen Teilnehmer der Gruppe fragen, den Nutzer hinzuzufügen. - https://telegram.org/faq/de#kann-keine-nachrichten-an-nicht-kontakte-senden Mehr Infos Sende an... Kommentar hinterlassen... Hier tippen um gespeicherte GIFs zu sehen Anheften Mitglieder benachrichtigen - Entfernen + Nicht mehr anheften Nachricht in der Gruppe wirklich anheften? + Nachricht im Kanal wirklich anheften? Angeheftete Nachricht wieder entfernen? Nutzer sperren Spam melden @@ -552,8 +586,9 @@ Bearbeitungszeit leider abgelaufen. Verknüpfung hinzufügen Mitglieder suchen - Zum Startbildschirm hinzugefügt - chatte mit dir selbst + gespeicherte Nachrichten + Gespeichertes + Weiterleiten zum Speichern Du Speichere deine Lieblings-Chats Sende Dateien jeglicher Art @@ -579,6 +614,7 @@ Die Admins dieser Gruppe haben dir die Berechtigung entzogen, Inline-Bot Inhalte zu senden Die Admins dieser Gruppe haben dir die Berechtigung entzogen, Sticker zu senden Die Admins dieser Gruppe haben dir die Berechtigung entzogen, Nachrichten zu senden + Admin %1$s hat den Selbstzerstörungs-Timer auf %2$s gesetzt Du hast den Selbstzerstörungs-Timer auf %1$s gesetzt @@ -625,7 +661,17 @@ %1$s hat dich aus der Gruppe %2$s entfernt %1$s hat die Gruppe %2$s verlassen %1$s benutzt jetzt Telegram! - %1$s,\nWir haben eine Anmeldung von einem neuen Gerät am %2$s festgestellt.\n\nGerät: %3$s\nStandort: %4$s\n\nWenn du das nicht selbst gewesen bist, melde die entsprechende Sitzung in den Telegram Einstellungen unter Privatsphäre und Sicherheit - Sitzungen unverzüglich ab.\n\nKennst du schon unsere zweistufige Bestätigung? Diese kannst du in den Telegram Einstellungen unter Privatsphäre und Sicherheit optional aktivieren.\n\nDein Telegram Team + %1$s, +Wir haben eine Anmeldung von einem neuen Gerät am %2$s festgestellt. + +Gerät: %3$s +Standort: %4$s + +Wenn du das nicht selbst gewesen bist, melde die entsprechende Sitzung in den Telegram Einstellungen unter Privatsphäre und Sicherheit - Sitzungen unverzüglich ab. + +Kennst du schon unsere zweistufige Bestätigung? Diese kannst du in den Telegram Einstellungen unter Privatsphäre und Sicherheit optional aktivieren. + +Dein Telegram Team %1$s hat das Profilbild geändert %1$s ist per Einladungslink der Gruppe %2$s beigetreten Antworten @@ -644,11 +690,13 @@ %1$s hat eine Videonachricht in der Gruppe %2$s angeheftet %1$s hat einen Kontakt in der Gruppe %2$s angeheftet %1$s hat einen Standort in der Gruppe %2$s angeheftet + %1$s hat einen Live-Standort in der Gruppe %2$s angeheftet %1$s hat ein GIF in der Gruppe %2$s angeheftet %1$s hat ein Musikstück in der Gruppe %2$s angeheftet %1$s hat \"%2$s\" angeheftet %1$s hat eine Nachricht angeheftet %1$s hat ein Bild angeheftet + %1$s hat ein Spiel angeheftet %1$s hat ein Video angeheftet %1$s hat eine Datei angeheftet %1$s hat einen Sticker angeheftet @@ -656,27 +704,32 @@ %1$s hat eine Sprachnachricht angeheftet %1$s hat eine Videonachricht angeheftet %1$s hat einen Kontakt angeheftet - %1$s hat einen Stamdort angeheftet + %1$s hat einen Standort angeheftet + %1$s hat einen Live-Standort angeheftet %1$s hat ein GIF angeheftet %1$s hat ein Musikstück angeheftet - %1$s hat ein Spiel angeheftet Kontakt auswählen Noch keine Kontakte - Hey, komm auch zu Telegram: https://telegram.org/dl + Hi, ich benutze Telegram zum Chatten. Hier kannst du es herunterladen: %1$s um gestern um online zul. online zul. online - zul. online gerade eben Freunde einladen + Freunde suchen GLOBALE SUCHE zuletzt kürzlich gesehen innerhalb einer Woche innerhalb eines Monats vor langer Zeit gesehen Neue Nachricht + Kontakte wählen, die du einladen möchtest + ZU TELEGRAM EINLADEN + Telegram teilen... + Kontakte aktualisieren? + Telegram hat viele nicht synchronisierte Kontakte erkannt. Möchte du diese jetzt synchronisieren? \'OK\' wählen, wenn du dein eigenes Gerät, SIM-Karte und Google-Konto benutzt. Leute hinzufügen... Sobald du diese Gruppe zu einer Supergruppe erweitert hast, kannst du mehr Nutzer einladen. @@ -684,7 +737,6 @@ Gruppenname %1$d von %2$d ausgewählt bis zu %1$s - Möchtest du dem Chat \'%1$s\' beitreten? Leider ist diese Gruppe schon voll. Leider gibt es diesen Chat nicht. Link in die Zwischenablage kopiert @@ -694,8 +746,12 @@ Der vorige Link ist nun inaktiv. Ein neuer Einladungslink wurde gerade erstellt. Widerrufen Link widerrufen - Bist du sicher, dass du den Link ]]>%1$s]]> widerrufen willst?\n\nDie Gruppe \"]]>%2$s]]>\" wird dann privat. - Bist du sicher, dass du den Link ]]>%1$s]]> widerrufen willst?\n\nDer Kanal \"]]>%2$s]]>\" wird dann privat. + Bist du sicher, dass du den Link **%1$s** widerrufen willst? + +Die Gruppe \"**%2$s**\" wird dann privat. + Bist du sicher, dass du den Link **%1$s** widerrufen willst? + +Der Kanal \"**%2$s**\" wird dann privat. Link kopieren Link teilen Jeder, der Telegram installiert hat, kann anhand dieses Links in deine Gruppe. @@ -705,8 +761,10 @@ Gruppenmitglieder können neue Leute hinzufügen sowie den Gruppennamen und das Bild ändern. Nur Admins können neue Leute hinzufügen und entfernen, den Gruppennamen und das Bild ändern. + Mitglieder Geteilte Medien Einstellungen + Abonnent hinzufügen Mitglied hinzufügen Administratoren NUTZER EINSCHRÄNKEN @@ -719,9 +777,22 @@ In Supergruppe ändern Warnung Du kannst die Supergruppe nicht mehr in eine normale Gruppe ändern. - ]]>Gruppenlimit erreicht.]]>\n\nFür weitere Funktionen und um das Limit aufzuheben in Supergruppe ändern:\n\n• Bis zu %1$s sind nun möglich\n• Neue Mitglieder sehen gesamten Verlauf\n• Gelöschte Nachrichten werden bei allen Mitgliedern entfernt\n• Admins können Nachrichten anheften\n• Gruppenersteller kann die Gruppe öffentlich machen - ]]>In Supergruppen:]]>\n\n• Neue Mitglieder sehen gesamten Verlauf\n• Nachrichten sind bei allen löschbar\n• Admins können Nachrichten anheften\n• Ersteller kann Gruppe öffentlich machen - ]]>Wichtig:]]> Die Änderung kann nicht rückgängig gemacht werden. + **Gruppenlimit erreicht.** + +Für weitere Funktionen und um das Limit aufzuheben in Supergruppe ändern: + +• Bis zu %1$s sind nun möglich +• Neue Mitglieder sehen gesamten Verlauf +• Gelöschte Nachrichten werden bei allen Mitgliedern entfernt +• Admins können Nachrichten anheften +• Gruppenersteller kann die Gruppe öffentlich machen + **In Supergruppen:** + +• Neue Mitglieder sehen gesamten Verlauf +• Nachrichten sind bei allen löschbar +• Admins können Nachrichten anheften +• Ersteller kann Gruppe öffentlich machen + **Wichtig:** Die Änderung kann nicht rückgängig gemacht werden. Teilen Hinzufügen @@ -749,9 +820,12 @@ Bild wird nach Ablauf der Zeit selbst zerstört. Video wird nach Ablauf der Zeit selbst zerstört. Aus - Bild und Text zeigen den Schlüssel dieses geheimen Chats mit ]]>%1$s]]>.\n\nSehen sie auf dem Gerät von ]]>%2$s\'s]]> genau so aus, ist Ende-zu-Ende Verschlüsselung garantiert.\n\nErfahre mehr unter telegram.org + Bild und Text zeigen den Schlüssel dieses geheimen Chats mit **%1$s**. + +Sehen sie auf dem Gerät von **%2$s\'s** genau so aus, ist Ende-zu-Ende Verschlüsselung garantiert. + +Erfahre mehr unter telegram.org https://telegram.org/faq/de#geheime-chats - Tippen für Emoji-Ansicht Unbekannt Info Telefon @@ -763,8 +837,11 @@ Ein Benutzername benötigt mindestens 5 Zeichen. Ein Benutzername darf maximal 32 Zeichen haben. Benutzernamen dürfen leider nicht mit einer Zahl anfangen. - Wähle einen öffentlichen Benutzernamen, wenn du von anderen bei ]]>Telegram]]> gefunden werden willst — ohne, dass sie deine Nummer kennen müssen.\n\nErlaubt sind ]]>a–z]]>, ]]>0–9]]> und Unterstriche. Die Mindestlänge beträgt ]]>5]]> Zeichen. - Dieser Link öffnet einen Chat mit dir:\n%1$s + Wähle einen öffentlichen Benutzernamen, wenn du von anderen bei **Telegram** gefunden werden willst — ohne, dass sie deine Nummer kennen müssen. + +Erlaubt sind **a–z**, **0–9** und Unterstriche. Die Mindestlänge beträgt **5** Zeichen. + Dieser Link öffnet einen Chat mit dir: +%1$s Prüfe Benutzername... %1$s ist verfügbar. Keiner @@ -774,6 +851,13 @@ Sticker hinzufügen Masken hinzufügen Sticker hinzufügen + Zu Favoriten hinzufügen + Sticker wurde Favoriten hinzugefügt + Sticker wurde aus Favoriten entfernt + Kürzlich benutzt + Favoriten + Gruppen-Sticker + Aus Favoriten entfernen Masken hinzufügen Sticker nicht gefunden Sticker entfernt @@ -829,7 +913,7 @@ Benachrichtigung Vorschau Gruppen - Nachrichtenton auswählen + Ton In-App In-App-Töne In-App-Vibration @@ -878,16 +962,17 @@ Angeheftete Nachrichten Sprache Eigene - Bedenke bitte, dass der Telegram Support von ehrenamtlichen Helfern betreut wird. Wir versuchen so schnell wie möglich zu antworten, dies kann jedoch manchmal ein bisschen dauern.\n\nBitte schau auch in den Telegram FAQ]]> nach. Dort findest du Antworten auf die meisten Fragen und wichtige Tipps zur Problembehandlung]]>. + Bedenke bitte, dass der Telegram Support von ehrenamtlichen Helfern betreut wird. Wir versuchen so schnell wie möglich zu antworten, dies kann jedoch manchmal ein bisschen dauern. + +Bitte schau auch in den Telegram FAQ]]> nach. Dort findest du Antworten auf die meisten Fragen und wichtige Tipps zur Problembehandlung]]>. Eine Frage stellen + Telegram FAQ Fragen und Antworten https://telegram.org/faq/de Datenschutzerklärung https://telegram.org/privacy Lokalisierung löschen? Falsche Sprachdatei - Aktiviert - Deaktiviert Keep-Alive-Dienst Sofern die App beendet wurde, diese automatisch neu starten, um Benachrichtigungen zu gewährleisten. Hintergrundverbindung @@ -906,8 +991,11 @@ Kurz Lang Automatischer Mediendownload + Medien Auto-Download + Einstellungen zurücksetzen + Möchtest du die Einstellungen für automatische Downloads wirklich zurücksetzen? Über Mobilfunk - Über W-LAN + Über WLAN Bei Roaming kein automatischer Download GIFs autom. Abspielen @@ -920,19 +1008,19 @@ Priorität Wie in Einstellungen Standard - Niedrig Hoch Max. Niemals Erneut benachrichtigen - Du kannst deine Telefonnummer hier ändern. Dein Konto und alle Daten in der Telegram-Cloud, also Nachrichten, Medien, Kontakte, etc. werden auf das neue Konto übertragen.\n\nWichtig:]]> Alle deine Kontakte erhalten deine neue Nummer]]> ihrem Telefonbuch hinzugefügt, sofern sie deine alte Nummer gespeichert hatten und du sie nicht blockiert hattest. + Du kannst deine Telefonnummer hier ändern. Dein Konto und alle Daten in der Telegram-Cloud, also Nachrichten, Medien, Kontakte, etc. werden auf das neue Konto übertragen. + +**Wichtig:** Alle deine Kontakte erhalten deine **neue Nummer** ihrem Telefonbuch hinzugefügt, sofern sie deine alte Nummer gespeichert hatten und du sie nicht blockiert hattest. Deinen Kontakten wird deine neue Nummer ihrem Telefonbuch hinzugefügt, sofern sie deine alte Nummer gespeichert hatten und du sie nicht blockiert hattest. NUMMER ÄNDERN Neue Nummer Der Bestätigungscode kommt per SMS an deine neue Nummer. Die Telefonnummer %1$s ist bereits ein Telegram Konto. Bitte lösche es, bevor du mit der Übertragung auf das neue Konto startest. - Sonstige - Deaktiviert + Sonstiges Deaktiviert Aktiviert Deaktiviert @@ -941,7 +1029,7 @@ In-Chat-Töne Standard Standard - Intelligente Benachrichtigungen + Schlaue Benachrichtigungen %1$d / %2$s Deaktiviert Anzahl Benachrichtigungen @@ -959,6 +1047,10 @@ Debug-Menü Kontakte importieren Kontakte neu laden + Dialoge zurücksetzen + In-App Kamera aktivieren + In-App Kamera deaktivieren + Importierte Kontakte zurücksetzen Du kannst die Sprache später in den Einstellungen ändern. Wähle deine Sprache Weitere @@ -972,10 +1064,22 @@ SOCKS5 Proxy-Einstellungen Proxy für Anrufe benutzen Proxyserver können die Qualität von Anrufen beinflussen. + Achtung + Du hast kaum noch freien Speicher. Lege fest, ob du nur kürzlich erhaltene Medien im Cache behalten willst. + Entferne Medien nach + Niemals entfernen + Kontakte + Private Chats + Gruppenchats + Kanäle + Maximale Größe + bis zu %1$s Lokale Datenbank Textnachrichten-Cache leeren? - Zwischengespeicherte Textnachrichten werden entfernt und die Datenbank optimiert um Speicherplatz zurückzuerhalten. Auf Null lässt sich die Größe jedoch nicht reduzieren, da die App einige Daten für den laufenden Betrieb benötigt.\n\nHinweis: Der Vorgang kann mehrere Minuten dauern. + Zwischengespeicherte Textnachrichten werden entfernt und die Datenbank optimiert um Speicherplatz zurückzuerhalten. Auf Null lässt sich die Größe jedoch nicht reduzieren, da die App einige Daten für den laufenden Betrieb benötigt. + +Hinweis: Der Vorgang kann mehrere Minuten dauern. Cache Leeren Leeren Berechne... @@ -988,7 +1092,9 @@ Sonstige Dateien Leer Medien behalten - Bilder, Videos und andere Dateien, auf die du während dieser Zeit nicht zugegriffen]]> hast, werden von diesem Gerät gelöscht, um Speicherplatz zu sparen.\n\nAlle Medien bleiben in der Telegram Cloud gespeichert und können jederzeit wieder heruntergeladen werden. + Bilder, Videos und andere Dateien, auf die du während dieser Zeit **nicht zugegriffen** hast, werden von diesem Gerät gelöscht, um Speicherplatz zu sparen. + +Alle Medien bleiben in der Telegram Cloud gespeichert und können jederzeit wieder heruntergeladen werden. Dauerhaft Sprachnachrichten Videonachrichten @@ -1005,8 +1111,9 @@ Pincode-Sperre Pincode ändern - Wenn du die Code-Sperre aktivierst, erscheint ein Schloss ganz oben über der Chatliste. Tippe auf das Schloss, um deine Chats zu sperren und zu entsperren.\n\nÜbrigens: Wenn du den Pincode vergisst, musst du Telegram löschen und neu installieren. Dadurch verlierst du alle Geheimen Chats. - Du wirst nun ein Schloss über der Chatliste sehen. Tippe darauf um deine Chats mit dem neuen Pincode zu sperren. + Wenn du die Code-Sperre aktivierst, erscheint ein Schloss ganz oben über der Chatliste. Tippe auf das Schloss, um deine Chats zu sperren und zu entsperren. + +Übrigens: Wenn du den Pincode vergisst, musst du Telegram löschen und neu installieren. Dadurch verlierst du alle Geheimen Chats. Pincode Kennwort Aktuellen Pincode eingeben @@ -1014,7 +1121,6 @@ Neuen Pincode eingeben Deinen Pincode eingeben Neuen Pincode erneut eingben - Ungültiger Pincode Pincode falsch Auto-Sperre Sperrt App bei Inaktivität automatisch. @@ -1025,7 +1131,9 @@ Berührungssensor Abdruck nicht erkannt; erneut versuchen Bildschirmfotos erlauben - Aktivierst du diese Funktion, so kannst du Screenshots in der App aufnehmen. Wenn du einen Pincode festgelegt hast, zeigt das System jedoch weiterhin deine Chats in der Liste der geöffneten App an.\n\nDu musst die App nach der Änderung neu starten. + Aktivierst du diese Funktion, so kannst du Screenshots in der App aufnehmen. Wenn du einen Pincode festgelegt hast, zeigt das System jedoch weiterhin deine Chats in der Liste der geöffneten App an. + +Du musst die App nach der Änderung neu starten. Geteilte Dateien Geteilte Medien @@ -1046,29 +1154,49 @@ m entfernt km entfernt Meinen Standort senden + Meinen Live-Standort teilen + Standortfreigabe beenden + Möchtest du deinen Live-Standort mit %1$s nicht mehr teilen? + Möchtest du deinen Live-Standort mit %1$s nicht mehr teilen? + Möchtest du deinen Live-Standort nicht mehr teilen? + Aktualisiert sich bei Bewegung Diesen Standort senden Standort + Ort Auf %1$s genau - Oder wähle einen Ort + ODER WÄHLE EINEN ORT + HOCHZIEHEN, FÜR ORTE IN DER NÄHE + LIVE-STANDORTE + für 15 Minuten + für 1 Stunde + für 8 Stunden + aktualisiert + gerade aktualisiert + Du und %1$s + %1$s mit %2$s geteilt + ALLE BEENDEN + Du teilst deinen Live-Standort mit %1$s + Wähle, wie lange %1$s deinen Live-Standort sehen darf. + Wähle, wie lange Leute in diesem Chat deinen Live-Standort sehen dürfen. + Dein GPS scheint deaktiviert zu sein. Aktiviere es, um auf standortbezogene Funktionen zugreifen zu können. Zeige alle Medien + Im Chat zeigen In der Galerie speichern %1$d von %2$d Galerie Alle Bilder Alle Medien Noch keine Bilder - Noch keine Videos Medien bitte zuerst herunterladen Suchverlauf Suchverlauf BILDERSUCHE - BILDERSUCHE + SUCHE IM WEB GIF-SUCHE Suche Bilder Suche GIFs Bild zuschneiden - Bild bearbeiten Verbessern Lichter Kontrast @@ -1080,15 +1208,12 @@ Körnung Schärfe Verblassen - Tönung TIEFEN LICHTER - Kurven ALLE ROT GRÜN BLAU - Unschärfe Aus Linear Radial @@ -1097,16 +1222,11 @@ Änderungen verwerfen? Suchverlauf löschen? Löschen - Bilder - Video Beschriftung... Bildbeschriftung Videobeschriftung GIF Beschriftung Beschriftung - Zeichnen - Sticker - Text Löschen Bearbeiten Klonen @@ -1115,6 +1235,8 @@ Zurücksetzen Original Quadrat + Zeigt Medien als einzelne Nachrichten + Zeigt alle Medien in einer Nachricht Zweistufige Bestätigung Zusätzliches Kennwort festlegen @@ -1129,7 +1251,9 @@ Falls du dein Kennwort vergisst, benötigen wir deine richtige E-Mail Adresse. Überspringen Warnung - Keine gute Idee.\n\nWenn du dein Passwort vergisst, verlierst du den Zugang zu deinem Telegram Konto. Für immer, ohne Ausnahme. + Keine gute Idee. + +Wenn du dein Passwort vergisst, verlierst du den Zugang zu deinem Telegram Konto. Für immer, ohne Ausnahme. Fast geschafft! Bitte überprüfe deine Mails (auch den Spam-Ordner) um die zweistufige Bestätigung abzuschließen. Geschafft! @@ -1137,33 +1261,43 @@ Kennwort ändern Kennwort deaktivieren E-Mailadresse festlegen - E‑Mail-Adresse zur Wiederherstellung ändern + E‑Mail-Adresse ändern Sicher, dass du dein Kennwort deaktivieren willst? Kennwort-Erinnerung Bitte Hinweis auf dein Kennwort eingeben Kennwörter stimmen nicht überein Einrichten abbrechen - Bitte folge diesen Schritten um die zweistufige Bestätigung abzuschließen:\n\n1. Überprüfe deine Mails (auch den Spam-Ordner)\n%1$s\n\n2. Auf den Link klicken. + Bitte folge diesen Schritten um die zweistufige Bestätigung abzuschließen: + +1. Überprüfe deine Mails (auch den Spam-Ordner) +%1$s + +2. Auf den Link klicken. Der Hinweis darf nicht das Kennwort sein. Ungültige E-Mail Tut uns leid Da du für diesen Fall keine E-Mail Adresse hinterlegt hast, kannst du nur noch hoffen, dass dir dein Kennwort wieder einfällt oder du musst dein Telegram Konto zurückzusetzen. - Wir haben den Wiederherstellungscode an diese Adresse geschickt:\n\n%1$s - Überprüfe deine Mails und gib den 6-stelligen Code aus userer E-Mail ein. + Wir haben den Wiederherstellungscode an diese Adresse geschickt: + +%1$s + Überprüfe deine Mails und gib den 6-stelligen Code aus unserer E-Mail ein. Du hast keinen Zugang zu deiner Adresse %1$s? Wenn du nicht in deine E-Mails kommst, kannst du nur hoffen, dass dir dein Kennwort wieder einfällt oder du musst dein Telegram Konto zurückzusetzen. KONTO ZURÜCKSETZEN Wenn du dein Konto zurücksetzt, verlierst du alle Chats und Nachrichten, ebenso deine geteilten Bilder und Videos. Warnung - Dies kann nicht rückgängig gemacht werden.\n\nWenn du dein Konto zurücksetzt, sind alle Chats gelöscht. + Dies kann nicht rückgängig gemacht werden. + +Wenn du dein Konto zurücksetzt, sind alle Chats gelöscht. Zurücksetzen Kennwort - Du hast die zweistufige Bestätigung aktiviert. Dein Konto ist mit einem zusätzlichem Kennwort geschützt. + Du hast die zweistufige Bestätigung aktiviert. Dein Konto ist mit einem zusätzlichen Kennwort geschützt. Kennwort vergessen? Kennwort zurücksetzen Code Kennwort deaktiviert - Du hast die zweistufige Bestätigung aktiviert.\nWenn du dich bei Telegram anmeldest, brauchst du dein Kennwort. + Du hast die zweistufige Bestätigung aktiviert. +Wenn du dich bei Telegram anmeldest, brauchst du dein Kennwort. Deine E-Mail Adresse %1$s wurde noch verifiziert und ist daher noch nicht aktiv. Daten und Speicher @@ -1207,8 +1341,6 @@ Automatische Kontolöschung Wenn inaktiv für Wenn du dich innerhalb dieser Zeit nicht anmeldest, wird dein Konto mit allen Nachrichten, Gruppen und Kontakten gelöscht. - Dein Konto löschen? - Bearbeite wer deinen Online Status sieht. Wer kann deinen Online Status sehen? Ausnahmen hinzufügen Wichtig: Du kannst den \"zuletzt gesehen\" Status nur von Personen sehen, mit denen du auch deinen teilst. Ansonsten wird die ungefähre Zeit angezeigt (kürzlich, innerhalb einer Woche, innerhalb eines Monats). @@ -1238,7 +1370,6 @@ Peer-to-Peer Deaktivierst du Peer-to-Peer, werden alle Anrufe über die Telegram Server geleitet. Dadurch ist deine IP-Adresse nicht mehr sichtbar, die Gesprächsqualität wird jedoch leicht abnehmen. - Video bearbeiten Sende Video... Sende GIF... @@ -1255,8 +1386,6 @@ Bot anhalten Bot neu starten - Weiter - Zurück Fertig Öffnen Speichern @@ -1277,8 +1406,6 @@ Wählen OK SCHNEIDEN - Ja - Nein Du bist der Gruppe per Link beigetreten un1 ist der Gruppe per Link beigetreten @@ -1312,6 +1439,7 @@ Video ist abgelaufen GIF Standort + Live-Standort Kontakt Datei Sticker @@ -1340,12 +1468,14 @@ %1$s dem Chat %2$s hinzufügen? Wieviele der letzten Nachrichten willst du weiterleiten? %1$s zur Gruppe hinzufügen? - Nutzer befindet sich schon in der Gruppe - Nachrichten an %1$s weiterleiten? Nachricht an %1$s senden? Spiel mit %1$s teilen? Kontakt senden an %1$s? - Wirklich abmelden?\n\nDu kannst Telegram von all deinen Geräten gleichzeitig nutzen.\n\nWichtig: Abmelden löscht deine Geheimen Chats. + Wirklich abmelden? + +Du kannst Telegram von all deinen Geräten gleichzeitig nutzen. + +Wichtig: Abmelden löscht deine Geheimen Chats. Sicher, dass du alle anderen Sitzungen abmelden möchtest? Gruppe löschen und verlassen? Möchtest du wirklich diesen Chat löschen? @@ -1356,7 +1486,7 @@ Dieser Bot möchte bei jeder Anfrage deinen Standort wissen. Das kann für standortabhängig aufbereitete Informationen benutzt werden. Deine Telefonnummer teilen? Der Bot wird deine Nummer sehen. Das kann für verschiedene Dienste nützlich sein. - Möchtest du wirklich deine Nummer %1$s mit ]]>%2$s]]> teilen? + Möchtest du wirklich deine Nummer %1$s mit **%2$s** teilen? Möchtest du wirklich deine Telefonnummer teilen? Diesen Kontakt wirklich blockieren? Blockierung für diesen Kontakt wirklich aufheben? @@ -1365,18 +1495,15 @@ Bist du dir sicher, dass du die Registrierung abbrechen willst? Möchtest du wirklich den Verlauf löschen? Cache des Kanals wirklich löschen? - Cache dieser Gruppe wirklich löschen? + Cache der Gruppe wirklich löschen? Sicher, dass du %1$s löschen willst? Nachricht an %1$s senden? Spiel mit %1$s teilen? Kontakt senden an %1$s? - Weiterleiten an %1$s? - Verzeihung, diese Funktion ist derzeit in deinem Land nicht verfügbar. Kein Konto mit diesem Benutzernamen Keine Gruppen mit diesem Bot möglich Möchtest du die erweiterte Linkvorschau in Geheimen Chats aktivieren? Die Vorschau wird auf den Telegram Servern generiert. Inline Bots werden von Drittentwicklern erstellt. Symbole, die du nach dem Botnamen eingibst, werden an den jeweiligen Entwickler geschickt, damit der Bot funktioniert. - Möchtest du \"Zum Sprechen ans Ohr\" für Sprachnachrichten aktivieren? Du kannst diese Nachricht nicht bearbeiten. Bitte erlaube Telegram den Zugriff auf SMS, so dass wir den Code automatisch in der App für dich eingeben können. Bitte erlaube Telegram den Zugriff auf Anrufe, so dass wir den Code automatisch in der App eingeben können. @@ -1391,7 +1518,7 @@ Die Admins dieser Gruppe haben dir die Berechtigung entzogen, Sticker zu senden. Die Admins dieser Gruppe haben dir die Berechtigung entzogen, Medien zu senden. - Telegram benötigt Zugriff auf deine Kontakte um dich auf all denen Geräten mit deinen Freunden zu verbinden. + Telegram benötigt Zugriff auf deine Kontakte, um dich auf all deinen Geräten mit deinen Freunden zu verbinden. Telegram benötigt Zugriff auf deinen Speicher, damit du Bilder, Videos und Musik senden und speichern kannst. Telegram benötigt Zugriff auf dein Mikrofon, damit du Sprachnachrichten senden kannst. Telegram benötigt Zugriff auf dein Mikrofon, damit du Videos aufnehmen kannst. @@ -1407,14 +1534,21 @@ Sicher Leistungsstark Cloud-Basiert - Die schnellste]]> Messaging App der Welt.\nKostenlos]]> und sicher]]>. - Telegram]]> übermittelt Nachrichten\nschneller als jede andere Anwendung. - Telegram]]> bleibt immer gratis.\nKeine Werbung. Keine Abo-Gebühren. - Telegram]]> schützt deine Nachrichten\nvor Hacker-Angriffen. - Telegram]]> hat keine Begrenzungen auf\ndie Größe deiner Medien oder Chats. - Telegram]]> kannst du vom Handy\nTablet oder auch Computer\nsynchronisiert benutzen. - Jetzt beginnen - + Die **schnellste** Messaging App der Welt. +**Kostenlos** und **sicher**. + **Telegram** übermittelt Nachrichten +schneller als jede andere Anwendung. + **Telegram** bleibt immer gratis. +Keine Werbung. Keine Abo-Gebühren. + **Telegram** schützt deine Nachrichten +vor Hacker-Angriffen. + **Telegram** hat keine Begrenzungen auf +die Größe deiner Medien oder Chats. + **Telegram** kannst du vom Handy +Tablet oder auch Computer +synchronisiert benutzen. + JETZT BEGINNEN + Kontoeinstellungen Weniger Daten benutzen Eingehender Anruf @@ -1429,9 +1563,9 @@ Besetzt Telegram-Anruf Laufender Telegram-Anruf - Anruf beenden + Auflegen Ein anderer Anruf ist bereits aktiv - Du bist mit %1$s]]> bereits im Gespräch. Möchtest du den Anruf beenden und einen neuen mit %2$s]]> starten? + Du bist mit **%1$s** bereits im Gespräch. Möchtest du den Anruf beenden und einen neuen mit **%2$s** starten? Sprachanrufe Klingelton Hier kannst du den Klingelton für Sprachanrufe festlegen. @@ -1455,12 +1589,10 @@ Abgebrochener Anruf Abgelehnter Anruf %1$s (%2$s) - Bild und Text zeigen den aktuellen Schlüssel dieses Sprachanrufs mit ]]>%1$s]]>.\n\nSehen sie auf dem Gerät von ]]>%2$s\'s]]> genau so aus, ist Ende-zu-Ende Verschlüsselung garantiert. Du hast noch keine Anrufe geführt. - ]]>%1$s]]>s App ist nicht mit unserem Protokoll kompatibel. Dein Chatpartner muss seine App aktualisieren, bevor du anrufen kannst. - ]]>%1$s]]> unterstützt keine Anrufe. Dein Chatpartner muss seine App aktualisieren, bevor du ihn anrufen kannst. + **%1$s**s App ist nicht mit unserem Protokoll kompatibel. Dein Chatpartner muss seine App aktualisieren, bevor du anrufen kannst. + **%1$s** unterstützt keine Anrufe. Dein Chatpartner muss seine App aktualisieren, bevor du ihn anrufen kannst. Bewerte bitte die Qualität deines Telegram-Anrufs - Möchtest du eine Rückmeldung zu unseren Sprachanrufen hinterlassen, damit wir den Dienst optimieren können? Telegram benötigt für Sprachanrufe Zugriff auf dein Mikrofon. Optionalen Kommentar hinzufügen Zurückrufen @@ -1472,20 +1604,44 @@ Lautsprecher Bluetooth ZURÜCK ZUM ANRUF - ]]>%1$s]]> akzeptiert leider keine Anrufe. + **%1$s** akzeptiert leider keine Anrufe. Wenn diese Emoji genau so bei %1$s aussehen, ist euer Anruf 100%% sicher. Anruf bewerten Was klappte nicht? Technische Infos senden Das hilft uns das Problem schneller zu beseitigen. Die Inhalte deiner Konversation werden nicht angezeigt. - Danke, dass du uns hilfst, Telegram Anrufe zu verbessern. + Danke, dass du uns hilfst, Telegram-Anrufe zu verbessern. + %1$d Empfänger + %1$d Empfänger + %1$d Empfänger + %1$d Empfänger + %1$d Empfänger + %1$d Empfänger %1$d online %1$d online %1$d online %1$d online %1$d online %1$d online + %1$d Kontakte bei Telegram + %1$d Kontakt bei Telegram + %1$d Kontakte bei Telegram + %1$d Kontakte bei Telegram + %1$d Kontakte bei Telegram + %1$d Kontakte bei Telegram + Hi, ich benutze Telegram zum Chatten – genau wie %1$d unserer Kontakte. Hier kannst du es herunterladen: %2$s + Hi, ich benutze Telegram zum Chatten – genau wie %1$d unserer Kontakte. Hier kannst du es herunterladen: %2$s + Hi, ich benutze Telegram zum Chatten – genau wie %1$d unserer Kontakte. Hier kannst du es herunterladen: %2$s + Hi, ich benutze Telegram zum Chatten – genau wie %1$d unserer Kontakte. Hier kannst du es herunterladen: %2$s + Hi, ich benutze Telegram zum Chatten – genau wie %1$d unserer Kontakte. Hier kannst du es herunterladen: %2$s + Hi, ich benutze Telegram zum Chatten – genau wie %1$d unserer Kontakte. Hier kannst du es herunterladen: %2$s + %1$d Chats + %1$d Chat + %1$d Chats + %1$d Chats + %1$d Chats + %1$d Chats %1$d Mitglieder %1$d Mitglied %1$d Mitglieder @@ -1498,25 +1654,31 @@ und %1$d weitere tippen und %1$d weitere tippen und %1$d weitere tippen - keine neuen Nachrichten + %1$s und %2$d weitere tippen + %1$s und %2$d weitere tippen + %1$s und %2$d weitere tippen + %1$s und %2$d weitere tippen + %1$s und %2$d weitere tippen + %1$s und %2$d weitere tippen + %1$d neue Nachrichten %1$d neue Nachricht %1$d neue Nachrichten %1$d neue Nachrichten %1$d neue Nachrichten %1$d neue Nachrichten - keine Nachrichten + %1$d Nachrichten %1$d Nachricht %1$d Nachrichten %1$d Nachrichten %1$d Nachrichten %1$d Nachrichten - keine Objekte + %1$d Objekte %1$d Objekt %1$d Objekte %1$d Objekte %1$d Objekte %1$d Objekte - von keinen Chats + von %1$d Chats von %1$d Chat von %1$d Chats von %1$d Chats @@ -1588,74 +1750,68 @@ %1$d Sticker %1$d Sticker %1$d Sticker - %1$d Bilder - %1$d Bild - %1$d Bilder - %1$d Bilder - %1$d Bilder - %1$d Bilder + %1$d Abonnenten + %1$d Abonnent + %1$d Abonnenten + %1$d Abonnenten + %1$d Abonnenten + %1$d Abonnenten %1$d Punkte %1$d Punkt %1$d Punkte %1$d Punkte %1$d Punkte %1$d Punkte - zul. online vor %1$d Minuten - zul. online vor %1$d Minute - zul. online vor %1$d Minuten - zul. online vor %1$d Minuten - zul. online vor %1$d Minuten - zul. online vor %1$d Minuten - zul. online vor %1$d Stunden - zul. online vor %1$d Stunde - zul. online vor %1$d Stunden - zul. online vor %1$d Stunden - zul. online vor %1$d Stunden - zul. online vor %1$d Stunden - %1$d]]> Sekunden - %1$d]]> Sekunde - %1$d]]> Sekunden - %1$d]]> Sekunden - %1$d]]> Sekunden - %1$d]]> Sekunden - %1$d]]> Minuten - %1$d]]> Minute - %1$d]]> Minuten - %1$d]]> Minuten - %1$d]]> Minuten - %1$d]]> Minuten - %1$d]]> Stunden - %1$d]]> Stunde - %1$d]]> Stunden - %1$d]]> Stunden - %1$d]]> Stunden - %1$d]]> Stunden - %1$d]]> Tage - %1$d]]> Tag - %1$d]]> Tage - %1$d]]> Tage - %1$d]]> Tage - %1$d]]> Tage + vor %1$d Min. + vor %1$d Min. + vor %1$d Min. + vor %1$d Min. + vor %1$d Min. + vor %1$d Min. + **%1$d** Sekunden + **%1$d** Sekunde + **%1$d** Sekunden + **%1$d** Sekunden + **%1$d** Sekunden + **%1$d** Sekunden + **%1$d** Minuten + **%1$d** Minute + **%1$d** Minuten + **%1$d** Minuten + **%1$d** Minuten + **%1$d** Minuten + **%1$d** Stunden + **%1$d** Stunde + **%1$d** Stunden + **%1$d** Stunden + **%1$d** Stunden + **%1$d** Stunden + **%1$d** Tage + **%1$d** Tag + **%1$d** Tage + **%1$d** Tage + **%1$d** Tage + **%1$d** Tage - %1$d angehängten Nachrichten - Angehängte Nachricht - %1$d angehängte Nachrichten - %1$d angehängte Nachrichten - %1$d angehängte Nachrichten - %1$d angehängte Nachrichten - %1$d angehängten Dateien + %1$d angehängte Nachrichten + Angehängte Nachricht + %1$d angehängte Nachrichten + %1$d angehängte Nachrichten + %1$d angehängte Nachrichten + %1$d angehängte Nachrichten + %1$d angehängte Dateien Angehängte Datei %1$d angehängte Dateien %1$d angehängte Dateien %1$d angehängte Dateien %1$d angehängte Dateien - %1$d angehängten Bilder + %1$d angehängte Bilder Angehängtes Bild %1$d angehängte Bilder %1$d angehängte Bilder %1$d angehängte Bilder %1$d angehängte Bilder - %1$d angehängten Videos + %1$d angehängte Videos Angehängtes Video %1$d angehängte Videos %1$d angehängte Videos @@ -1679,19 +1835,19 @@ %1$d angehängte Videonachrichten %1$d angehängte Videonachrichten %1$d angehängte Videonachrichten - %1$d weitergeleiteten Standorte + %1$d angehängte Standorte Angehängter Standort %1$d angehängte Standorte %1$d angehängte Standorte %1$d angehängte Standorte %1$d angehängte Standorte - %1$d weitergeleiteten Kontakt + %1$d angehängte Kontakte Angehängter Kontakt %1$d angehängte Kontakte %1$d angehängte Kontakte %1$d angehängte Kontakte %1$d angehängte Kontakte - %1$d angehängten Sticker + %1$d angehängte Sticker Angehängter Sticker %1$d angehängte Sticker %1$d angehängte Sticker diff --git a/TMessagesProj/src/main/res/values-es/strings.xml b/TMessagesProj/src/main/res/values-es/strings.xml index 17b886fda..393bcd796 100644 --- a/TMessagesProj/src/main/res/values-es/strings.xml +++ b/TMessagesProj/src/main/res/values-es/strings.xml @@ -1,7 +1,4 @@ - - - Telegram Telegram Beta @@ -9,18 +6,20 @@ Inglés Spanish es - Seguir en inglés + Continuar en español Tu teléfono - Por favor, confirma tu código de país y pon tu número de teléfono. + Por favor, confirma el código de tu país y pon tu número de teléfono. Elige un país Código de país incorrecto Verificación - Enviamos un SMS con el código de activación al ]]>%1$s]]>. - Enviamos el código a la aplicación ]]>Telegram]]> en tu otro dispositivo. - Enviamos una llamada de activación al ]]>%1$s]]>.\n\nNo la contestes. Telegram hará todo el proceso automáticamente. - Estamos llamando al ]]>%1$s]]> para dictar un código. + Enviamos un SMS con el código de activación al **%1$s**. + Enviamos el código a la aplicación **Telegram** en tu otro dispositivo. + Enviamos una llamada de activación al **%1$s**. + +No la contestes. Telegram hará todo el proceso automáticamente. + Estamos llamando al **%1$s** para dictar un código. Te llamaremos en %1$d:%2$02d Te enviaremos un SMS en %1$d:%2$02d Llamándote... @@ -28,9 +27,13 @@ ¿Número incorrecto? ¿No recibiste el código? No restablecer la cuenta - Alguien, con acceso a tu número de teléfono ]]>%1$s]]> , solicitó eliminar tu cuenta de Telegram y restablecer tu contraseña de la verificación en dos pasos.\n\nSi no eras tú, por favor, inserta el código que enviamos por SMS a tu número. + Alguien, con acceso a tu número de teléfono **%1$s** , solicitó eliminar tu cuenta de Telegram y restablecer tu contraseña de la verificación en dos pasos. + +Si no eras tú, por favor, inserta el código que enviamos por SMS a tu número. Restablecer la cuenta - Como la cuenta ]]>%1$s]]> está activa y protegida con una contraseña, la eliminaremos en 1 semana, por motivos de seguridad.\n\nPuedes cancelar el proceso en cualquier momento. + Como la cuenta **%1$s** está activa y protegida con una contraseña, la eliminaremos en 1 semana, por motivos de seguridad. + +Puedes cancelar el proceso en cualquier momento. Podrás restablecer tu cuenta en: Tus intentos para restablecer la cuenta fueron cancelados por su usuario activo. Por favor, reinténtalo en 7 días. RESTABLECER @@ -69,7 +72,6 @@ Código de seguridad (CVV) MM/AA Dirección de facturación - Dueño de la tarjeta Nombre Apellidos Guardar información de pago Puedes guardar la información de pago para usarla más adelante. @@ -93,7 +95,20 @@ Lo sentimos, el pago fue rechazado. No es posible acceder al servidor de pago. Por favor, revisa tu conexión a internet y reinténtalo. Advertencia - Ni Telegram, ni %1$s tendrán acceso a la información de tu tarjeta de crédito. Los detalles de la tarjeta de crédito serán manejados sólo por el sistema de pago, %2$s.\n\nLos pagos irán directamente al desarrollador de %1$s. Telegram no puede entregar garantías, así que es bajo tu propio riesgo. En caso de problemas, contacta al desarrollador de %1$s o a tu banco. + Ni Telegram, ni %1$s tendrán acceso a la información de tu tarjeta de crédito. Los detalles de la tarjeta de crédito serán manejados sólo por el sistema de pago, %2$s. + +Los pagos irán directamente al desarrollador de %1$s. Telegram no puede entregar garantías, así que es bajo tu propio riesgo. En caso de problemas, contacta al desarrollador de %1$s o a tu banco. + E-mail y contraseña + Contraseña + Pon una contraseña + Pon tu contraseña otra vez + Por favor, crea una contraseña para proteger tu información de pago. Será requerida al iniciar sesión. + E-mail de recuperación + Tu e-mail + Por favor, añade un e-mail válido. Es la única forma de recuperar una contraseña olvidada. + El teléfono será entregado a %1$s como información de facturación. + El e-mail será entregado a %1$s como información de facturación. + El e-mail y telefóno serán entregados a %1$s como información de facturación. Nuevo chat Ajustes @@ -102,7 +117,11 @@ ayer Sin resultados Aún no hay chats... - Envía mensajes tocando el botón para\nredactar, en la parte inferior derecha,\no pulsa el botón menú para más opciones. + Vistos recientemente + OCULTAR + Envía mensajes tocando el botón del +lápiz, en la esquina inferior derecha, +o pulsa el botón menú para más opciones. Esperando red... Conectando... Conectando al proxy... @@ -125,10 +144,12 @@ Eliminar chat Cuenta eliminada Elige el chat - Mantén pulsado para ver + Reenviar a... Foto secreta Vídeo secreto - %1$s usa una versión antigua de Telegram, así que las fotos secretas serán mostradas en un modo de compatibilidad.\n\nCuando %2$s actualice Telegram, las fotos con autodestrucción de 1 minuto o menos funcionarán con el modo “Mantén pulsado para ver”, y te notificaremos siempre que la otra parte haga una captura de pantalla. + %1$s usa una versión antigua de Telegram, así que las fotos secretas serán mostradas en un modo de compatibilidad. + +Cuando %2$s actualice Telegram, las fotos con autodestrucción de 1 minuto o menos funcionarán con el modo “Mantén pulsado para ver”, y te notificaremos siempre que la otra parte haga una captura de pantalla. MENSAJES Buscar Silenciar notificaciones @@ -153,16 +174,12 @@ Negrita Cursiva Normal - Para conectarte sin problemas con todas las personas que conoces, permite a ]]>Telegram]]> acceder a tus contactos. + Para conectarte sin problemas con todas las personas que conoces, permite a **Telegram** acceder a tus contactos. AHORA NO - SEGUIR + CONTINUAR - Público - Privado Ascender a administrador Sin suspendidos - Puedes poner una descripción para tu grupo. - Salir del grupo Eliminar grupo Salir del grupo Eliminar grupo @@ -188,6 +205,7 @@ un1 ancló un contacto un1 ancló %1$s un1 ancló un mapa + un1 ancló una ubicación en directo un1 ancló un GIF un1 ancló una pista Este grupo fue convertido en un supergrupo @@ -235,6 +253,7 @@ Verificando nombre... %1$s está disponible. Miembros + Suscriptores Suspendidos Usuarios suspendidos Usuarios restringidos @@ -245,16 +264,15 @@ ¿Quieres salir de este canal? Perderás todos los mensajes en este canal. Editar - Ten en cuenta que, si eliges un enlace público para tu grupo, cualquiera podrá encontrarlo en la búsqueda y unirse.\n\nNo crees este enlace si quieres que tu supergrupo sea privado. - Ten en cuenta que, si eliges un enlace público para tu canal, cualquiera podrá encontrarlo en la búsqueda y unirse.\n\nNo crees este enlace si quieres que tu canal sea privado. - Por favor, elige un enlace para tu canal público. Así, las personas podrán encontrarlo en la búsqueda y compartirlo con otros.\n\nSi no estás interesado, te sugerimos crear un canal privado. + Por favor, elige un enlace para tu canal público. Así, las personas podrán encontrarlo en la búsqueda y compartirlo con otros. + +Si no estás interesado, te sugerimos crear un canal privado. Canal creado Foto del canal cambiada Foto del canal eliminada Nombre del canal cambiado a un2 Lo sentimos, tienes demasiados alias públicos. Puedes anular el enlace de uno de tus grupos o canales anteriores, o crear uno privado. Creador - Administrador SILENCIAR NO SILENCIAR Añadir administrador @@ -262,10 +280,8 @@ Quitar suspensión Toca y mantén sobre un usuario para quitar su suspensión. Invitar con un enlace - ¿Quieres nombrar a %1$s como administrador? Eliminar administrador Sólo los administradores del canal pueden ver esta lista. - Este usuario aún no se ha unido al canal. ¿Quieres invitarlo? Cualquiera que tenga Telegram instalada podrá unirse a tu canal siguiendo este enlace. Puedes añadir administradores para que te ayuden en el canal. Mantén pulsado para eliminarlos. ¿Quieres unirte al canal “%1$s”? @@ -285,18 +301,6 @@ Lo sentimos, no puedes enviar mensajes en este canal. %1$s te añadió al canal %2$s El canal %1$s actualizó su foto - %1$s envió un mensaje al canal %2$s - %1$s envió una foto al canal %2$s - %1$s envió un vídeo al canal %2$s - %1$s compartió un contacto en el canal %2$s - %1$s envió una ubicación al canal %2$s - %1$s envió un archivo al canal %2$s - %1$s envió un GIF al canal %2$s - %1$s envió un mensaje de voz al canal %2$s - %1$s envió un videomensaje al canal %2$s - %1$s envió una pista al canal %2$s - %1$s envió un sticker al canal %2$s - %1$s envió un %3$s sticker al canal %2$s %1$s publicó un mensaje %1$s publicó una foto %1$s publicó un vídeo @@ -342,21 +346,36 @@ Vista previa de enlaces Suspendido hasta Siempre - Eliminar Suspender y eliminar del grupo Administrar grupo Administrar canal Administrar grupo Administrar canal + Historial para los nuevos miembros + Visible + Los nuevos miembros verán mensajes que fueron enviados antes de unirse. + Oculto + Los nuevos miembros no verán mensajes anteriores. Acciones recientes Todas las acciones Acciones seleccionadas Todos los administradores - ]]>Aún no hay eventos]]>\n\nLos administradores del grupo\nno han realizado acciones de servicio\nen las últimas 48 horas. - ]]>Aún no hay eventos]]>\n\nLos administradores del canal\nno han realizado acciones de servicio\nen las últimas 48 horas. - ]]>No se encontraron acciones]]>\n\nNo se encontraron acciones recientes\nque coincidan con tu solicitud. - No se encontraron acciones recientes que contengan \']]>%1$s]]>. + **Aún no hay eventos** + +Los administradores del grupo +no han realizado acciones de servicio +en las últimas 48 horas. + **Aún no hay eventos** + +Los administradores del canal +no han realizado acciones de servicio +en las últimas 48 horas. + **No se encontraron acciones** + +No se encontraron acciones recientes +que coincidan con tu solicitud. + No se encontraron acciones recientes que contengan \'**%1$s**. ¿Qué son las acciones recientes? Esta es una lista de las acciones de servicio realizadas por los miembros y administradores del grupo en las últimas 48 horas. Esta es una lista de las acciones de servicio realizadas por los administradores del canal en las últimas 48 horas. @@ -381,6 +400,8 @@ un1 ancló este mensaje: un1 desancló un mensaje un1 eliminó este mensaje: + un1 cambió el pack de stickers de grupo + un1 eliminó el pack de stickers de grupo un1 cambió el enlace del grupo: un1 cambió el enlace del canal: un1 eliminó el enlace del grupo @@ -389,11 +410,15 @@ un1 editó la descripción del grupo: un1 editó la descripción del canal: Descripción anterior + un1 ha dejado visible el historial para los nuevos miembros + un1 ha ocultado el historial para los nuevos miembros un1 activó las invitaciones un1 desactivó las invitaciones un1 activó las firmas un1 desactivó las firmas - cambió las restricciones para %1$s\n\nDuración: %2$s + cambió las restricciones para %1$s + +Duración: %2$s Enviar stickers y GIF Enviar multimedia Enviar mensajes @@ -415,7 +440,6 @@ Nuevos miembros Información del grupo Información del canal - Ajustes del grupo Mensajes eliminados Mensajes editados Mensajes anclados @@ -431,12 +455,13 @@ Música Artista desconocido Título desconocido + Aleatorio + Invertir orden Elegir archivo %1$s de %2$s libres Error desconocido Error de acceso - Aún no hay archivos... El archivo no debe superar los %1$s Almacenamiento no montado Transferencia USB activa @@ -446,11 +471,22 @@ Tarjeta SD Carpeta Para enviar imágenes sin compresión + + Stickers de grupo + Elige desde tus stickers + Puedes elegir un pack para que los miembros del grupo lo utilicen en él. + ELIGE UN PACK DE STICKERS + stickerset + Puedes crear tu pack de stickers personalizado usando el bot @stickers. + El pack no ha sido encontrado + Reinténtalo o elige uno de la lista de abajo invisible escribiendo... está escribiendo... están escribiendo... + %1$s está escribiendo... + %1$s están escribiendo... %1$s está grabando un mensaje de voz... %1$s está grabando un videomensaje... %1$s está enviando un audio... @@ -465,12 +501,12 @@ grabando un mensaje de voz... grabando un videomensaje... enviando audio... - enviando foto... + enviando una foto... jugando... - enviando vídeo... - enviando archivo... - ¿Tienes preguntas\nsobre Telegram? - Hacer foto + enviando un vídeo... + enviando un archivo... + ¿Tienes preguntas +sobre Telegram? Galería Ubicación Vídeo @@ -479,6 +515,7 @@ Aún no hay mensajes... Mensaje reenviado De + de: No hay recientes Mensaje Mensaje @@ -509,7 +546,6 @@ Obteniendo información... ABRIR EN... Abrir en... - Copiar URL Enviar %1$s Enviar como archivo Como archivo @@ -523,9 +559,6 @@ ¿Quieres reportar spam de este canal? Lo sentimos, por ahora puedes enviar mensajes sólo a contactos mutuos. Lo sentimos, por ahora sólo puedes añadir contactos mutuos a grupos. - Escribiste a demasiados usuarios que no son tus contactos hoy. Inténtalo de nuevo mañana. Podrás responder si ellos te escriben primero. - No puedes añadir a este usuario porque escribiste a demasiadas personas que no son tus contactos hoy. Inténtalo de nuevo mañana. Puedes pedir a otro miembro del grupo que lo añada. - https://telegram.org/faq/es#no-puedo-enviar-mensajes-a-quienes-no-son-mis-contactos Más información Enviar a... Escribe un comentario... @@ -534,6 +567,7 @@ Notificar a los miembros Desanclar ¿Quieres anclar este mensaje en el grupo? + ¿Quieres anclar este mensaje en el canal? ¿Quieres desanclar este mensaje? Suspender usuario Reportar spam @@ -552,8 +586,9 @@ Terminó el tiempo de edición. Añadir acceso directo Buscar miembros - Acceso directo añadido al escritorio - chat contigo + reenvía aquí para guardar + Mensajes guardados + Reenvía aquí para guardar. Guarda mensajes reenviándolos aquí Envía archivos para almacenarlos @@ -579,6 +614,7 @@ Los administradores del grupo han restringido que envíes contenido integrado Los administradores del grupo han restringido que envíes stickers Los administradores del grupo han restringido que escribas + administrador %1$s activó la autodestrucción en %2$s Activaste la autodestrucción en %1$s @@ -625,7 +661,18 @@ %1$s te eliminó del grupo %2$s %1$s dejó el grupo %2$s ¡%1$s se unió a Telegram! - %1$s,\nDetectamos un inicio de sesión en tu cuenta desde un nuevo dispositivo, el %2$s\n\nDispositivo: %3$s\nUbicación: %4$s\n\nSi no eras tú, puedes ir a Ajustes > Privacidad y seguridad > Sesiones activas y cerrar esa sesión.\n\nSi crees que alguien ha iniciado la sesión sin tu consentimiento, puedes activar la verificación en dos pasos, en los ajustes de privacidad y seguridad.\n\nAtentamente,\nEl equipo de Telegram + %1$s, +Detectamos un inicio de sesión en tu cuenta desde un nuevo dispositivo, el %2$s + +Dispositivo: %3$s +Ubicación: %4$s + +Si no eras tú, puedes ir a Ajustes > Privacidad y seguridad > Sesiones activas y cerrar esa sesión. + +Si crees que alguien ha iniciado la sesión sin tu consentimiento, puedes activar la verificación en dos pasos, en los ajustes de privacidad y seguridad. + +Atentamente, +El equipo de Telegram %1$s actualizó su foto de perfil %1$s se unió al grupo %2$s con un enlace de invitación Responder @@ -644,11 +691,13 @@ %1$s ancló un videomensaje en el grupo %2$s %1$s ancló un contacto en el grupo %2$s %1$s ancló un mapa en el grupo %2$s + %1$s ancló una ubicación en directo en el grupo %2$s %1$s ancló un GIF en el grupo %2$s %1$s ancló una pista en el grupo %2$s %1$s ancló \"%2$s\" %1$s ancló un mensaje %1$s ancló una foto + %1$s ancló un juego %1$s ancló un vídeo %1$s ancló un archivo %1$s ancló un sticker @@ -657,26 +706,31 @@ %1$s ancló un videomensaje %1$s ancló un contacto %1$s ancló un mapa + %1$s ancló una ubicación en directo %1$s ancló un GIF %1$s ancló una pista - %1$s ancló un juego Elegir contacto Aún no hay contactos - ¡Oye! Cambiémonos a Telegram: https://telegram.org/dl + Estoy usando Telegram para chatear. ¡Únete! Descárgala aquí: %1$s a las ayer a las en línea últ. vez últ. vez el - últ. vez hace un momento Invitar a amigos + Buscar amigos BÚSQUEDA GLOBAL últ. vez recientemente últ. vez hace unos días últ. vez hace unas semanas últ. vez hace mucho tiempo Mensaje nuevo + Elige contactos para invitarlos a Telegram + INVITAR A TELEGRAM + Compartir Telegram... + ¿Actualizar contactos? + Telegram ha detectado muchos contactos sin sincronizar. ¿Quieres sincronizarlos ahora? Elige “OK” si estás usando tu dispositivo, tarjeta SIM y cuenta de Google. Añadir personas... Podrás añadir más usuarios después de crear el grupo y convertirlo en un supergrupo. @@ -684,7 +738,6 @@ Nombre del grupo %1$d de %2$d seleccionados hasta %1$s - ¿Quieres unirte al chat “%1$s”? Lo sentimos. Este grupo está lleno. Lo sentimos, este chat no existe. Enlace copiado al portapapeles @@ -694,8 +747,12 @@ El enlace de invitación anterior está inactivo. Ha sido creado uno nuevo. Anular Anular enlace - ¿Quieres anular el enlace ]]>%1$s]]>?\n\nEl grupo \"]]>%2$s]]>\" pasará a ser privado. - ¿Quieres anular el enlace ]]>%1$s]]>?\n\nEl canal \"]]>%2$s]]>\" pasará a ser privado. + ¿Quieres anular el enlace **%1$s**? + +El grupo \"**%2$s**\" pasará a ser privado. + ¿Quieres anular el enlace **%1$s**? + +El canal \"**%2$s**\" pasará a ser privado. Copiar enlace Compartir enlace Cualquiera que tenga Telegram instalada podrá unirse a tu grupo siguiendo este enlace. @@ -705,8 +762,10 @@ Todos pueden añadir nuevos miembros, editar el nombre y la foto del grupo. Sólo los administradores pueden añadir y eliminar miembros, editar el nombre y la foto del grupo. + Miembros Multimedia Ajustes + Añadir suscriptor Añadir miembro Nombrar administradores SUSPENDER DEL GRUPO @@ -719,9 +778,22 @@ Convertir en supergrupo Advertencia Esta acción es irreversible. No puedes convertir un supergrupo en un grupo normal. - ]]>Límite de miembros alcanzado.]]>\n\nPara superar el límite y tener características adicionales, conviértelo en un supergrupo:\n\n• Permiten hasta %1$s\n• Los nuevos miembros ven todo el historial\n• Un mensaje eliminado desaparece para todos\n• Los administradores pueden anclar mensajes\n• El creador puede generar un enlace público - ]]>En los supergrupos:]]>\n\n• Los nuevos miembros ven todo el historial\n• Un mensaje eliminado desaparece para todos\n• Los administradores pueden anclar mensajes\n• El creador puede generar un enlace público - ]]>Importante:]]> Esta acción no se puede deshacer. + **Límite de miembros alcanzado.** + +Para superar el límite y tener características adicionales, conviértelo en un supergrupo: + +• Permiten hasta %1$s +• Los nuevos miembros ven todo el historial +• Un mensaje eliminado desaparece para todos +• Los administradores pueden anclar mensajes +• El creador puede generar un enlace público + **En los supergrupos:** + +• Los nuevos miembros ven todo el historial +• Un mensaje eliminado desaparece para todos +• Los administradores pueden anclar mensajes +• El creador puede generar un enlace público + **Importante:** Esta acción no se puede deshacer. Compartir Añadir @@ -749,9 +821,12 @@ Si pones un temporizador, la foto se autodestruirá después de ser vista. Si pones un temporizador, el vídeo se autodestruirá después ser visto. Apagada - El texto e imagen derivan de la clave de cifrado para el chat secreto creado con ]]>%1$s]]>.\n\nSi se ven igual en el dispositivo de ]]>%2$s]]>, el cifrado end-to-end está garantizado.\n\nConoce más en telegram.org + El texto e imagen derivan de la clave de cifrado para el chat secreto creado con **%1$s**. + +Si se ven igual en el dispositivo de **%2$s**, el cifrado end-to-end está garantizado. + +Conoce más en telegram.org https://telegram.org/faq/es#chats-secretos - Toca para ver emojis Desconocido Información Teléfono @@ -763,8 +838,11 @@ Un alias debe tener al menos 5 caracteres. El alias no debe exceder los 32 caracteres. Lo sentimos, un alias no puede comenzar con un número. - Puedes elegir un alias en ]]>Telegram]]>. Si lo haces, otras personas te podrán encontrar por ese alias y contactarte sin saber tu número de teléfono.\n\nPuedes usar ]]>a–z]]>, ]]>0–9]]> y guiones bajos. La longitud mínima es de ]]>5]]> caracteres. - Este enlace abre un chat contigo en Telegram:\n%1$s + Puedes elegir un alias en **Telegram**. Si lo haces, otras personas te podrán encontrar por ese alias y contactarte sin saber tu número de teléfono. + +Puedes usar **a–z**, **0–9** y guiones bajos. La longitud mínima es de **5** caracteres. + Este enlace abre un chat contigo en Telegram: +%1$s Verificando alias... %1$s está disponible. Ninguno @@ -774,6 +852,13 @@ Añadir stickers Añadir máscaras Añadir a stickers + Añadir a favoritos + Sticker añadido a favoritos + El sticker fue eliminado de favoritos + Recientes + Favoritos + Stickers de grupo + Eliminar de favoritos Añadir a máscaras Stickers no encontrados Stickers eliminados @@ -878,16 +963,17 @@ Mensajes anclados Idioma Personalizado - Por favor, considera que el soporte de Telegram está hecho por voluntarios. Respondemos lo antes posible, pero puede tomar tiempo.\n\nPor favor, mira las preguntas frecuentes de Telegram]]>: tienen respuestas para la mayoría de las preguntas y soluciones a problemas]]>. + Por favor, considera que el soporte de Telegram está hecho por voluntarios. Respondemos lo antes posible, pero puede tomar tiempo. + +Por favor, mira las preguntas frecuentes de Telegram]]>: tienen respuestas para la mayoría de las preguntas y soluciones a problemas]]>. Preguntar + Preguntas frecuentes Preguntas frecuentes https://telegram.org/faq/es Política de privacidad https://telegram.org/privacy ¿Eliminar traducción? Archivo de traducción incorrecto - Activadas - Desactivadas Servicio keep-alive Vuelve a abrir la app cuando el usuario o el sistema la cierra. Esto garantiza que la app pueda mostrar notificaciones. Conexión en segundo plano @@ -906,6 +992,9 @@ Cortas Largas Autodescarga de multimedia + Autodescarga de multimedia + Restablecer ajustes de autodescarga + ¿Quieres restablecer los ajustes de autodescarga? Con uso de datos móviles Con conexión a Wi-Fi Con itinerancia de datos @@ -920,19 +1009,19 @@ Prioridad Como en Ajustes Por defecto - Baja Alta Máxima Nunca Repetir notificaciones - Puedes cambiar tu número de Telegram aquí. Tu cuenta y todos tus datos de la nube, mensajes, archivos, grupos, contactos, etc., se moverán al nuevo número.\n\nImportante:]]> Todos tus contactos de Telegram tendrán tu nuevo número]]> añadido a sus agendas de contactos, siempre que hayan tenido tu número viejo y no los hayas bloqueado en Telegram. + Puedes cambiar tu número de Telegram aquí. Tu cuenta y todos tus datos de la nube, mensajes, archivos, grupos, contactos, etc., se moverán al nuevo número. + +**Importante:** Todos tus contactos de Telegram tendrán tu **nuevo número** añadido a sus agendas de contactos, siempre que hayan tenido tu número viejo y no los hayas bloqueado en Telegram. Todos tus contactos de Telegram tendrán tu número nuevo añadido a sus agendas de contactos, siempre que hayan tenido tu número viejo y no los hayas bloqueado en Telegram. CAMBIAR NÚMERO Nuevo número Enviaremos un SMS con el código de confirmación a tu nuevo número. El número %1$s ya está vinculado a una cuenta de Telegram. Por favor, elimina esa cuenta antes de migrar al nuevo número. Otras - Desactivadas Desactivadas Activadas Desactivadas @@ -959,6 +1048,10 @@ Menú de depuración Importar contactos Recargar contactos + Restablecer diálogos + Activar cámara interna + Desactivar cámara interna + Restablecer contactos importados Puedes cambiar el idioma después, en Ajustes. Elige tu idioma Otro @@ -972,10 +1065,22 @@ Ajustes de proxy SOCKS5 Usar proxy para llamadas El proxy puede disminuir la calidad de tus llamadas. + Atención + Queda poco espacio en tu dispositivo. Puedes liberar espacio al elegir conservar sólo la caché de la multimedia reciente en Telegram. + Eliminar multimedia tras + Nunca eliminarla + Contactos + Chats + Chats grupales + Canales + Limitar por tamaño + hasta %1$s Base de datos local ¿Eliminar los mensajes en la caché? - Al eliminar la base de datos se eliminarán los mensajes en la caché y se comprimirá la base de datos para liberar espacio de almacenamiento. Telegram requiere algunos datos para funcionar, así que la base de datos nunca podrá llegar a cero.\n\nEsto puede tardar algunos minutos. + Al eliminar la base de datos se eliminarán los mensajes en la caché y se comprimirá la base de datos para liberar espacio de almacenamiento. Telegram requiere algunos datos para funcionar, así que la base de datos nunca podrá llegar a cero. + +Esto puede tardar algunos minutos. Eliminar caché Eliminar Calculando... @@ -988,7 +1093,9 @@ Otros archivos Vacío Conservar multimedia - Las fotos, los vídeos y los archivos de los chats en la nube a los que no accedas]]> durante ese periodo de tiempo se eliminarán del dispositivo para liberar espacio.\n\nToda la multimedia permanecerá en la nube de Telegram y podrás volver a descargarla si la necesitas. + Las fotos, los vídeos y los archivos de los chats en la nube a los que **no accedas** durante ese periodo de tiempo se eliminarán del dispositivo para liberar espacio. + +Toda la multimedia permanecerá en la nube de Telegram y podrás volver a descargarla si la necesitas. Siempre Mensajes de voz Videomensajes @@ -1005,8 +1112,9 @@ Código de acceso Cambiar código de acceso - Cuando configuras un código, aparece un candado en la pantalla de chats. Toca sobre él para bloquear y desbloquear la aplicación.\n\nNota: si olvidas el código, tendrás que eliminar y reinstalar la aplicación. Perderás todos los chats secretos. - Ahora verás un candado en la pantalla de chats. Púlsalo para bloquear tu Telegram con tu nuevo código. + Cuando configuras un código, aparece un candado en la pantalla de chats. Toca sobre él para bloquear y desbloquear la aplicación. + +Nota: si olvidas el código, tendrás que eliminar y reinstalar la aplicación. Perderás todos los chats secretos. PIN Contraseña Pon tu código de acceso actual @@ -1014,7 +1122,6 @@ Pon tu nuevo código de acceso Pon tu código de acceso Pon, otra vez, tu nuevo código - Código de acceso inválido Los códigos de acceso no coinciden Autobloqueo El bloqueo se activará transcurrido este tiempo. @@ -1025,7 +1132,9 @@ Sensor táctil Huella digital no reconocida. Reinténtalo Permitir captura de pantalla - Si está activada, puedes hacer capturas de pantalla de la aplicación, pero el sistema mostrará tus chats en la multitarea aunque tengas activo el código de acceso.\n\nPuede ser necesario reiniciar la aplicación para que tenga efecto. + Si está activada, puedes hacer capturas de pantalla de la aplicación, pero el sistema mostrará tus chats en la multitarea aunque tengas activo el código de acceso. + +Puede ser necesario reiniciar la aplicación para que tenga efecto. Archivos Multimedia @@ -1046,19 +1155,40 @@ m de distancia km de distancia Enviar tu ubicación actual + Enviar mi ubicación durante... + Dejar de enviar mi ubicación + ¿Quieres dejar de enviar la ubicación en directo a %1$s? + ¿Quieres dejar de enviar la ubicación en directo a %1$s? + ¿Quieres dejar de enviar tu ubicación en directo? + Actualizada mientras te mueves Enviar la ubicación seleccionada Ubicación + Lugar Exacto a %1$s O ELIGE UN LUGAR + LEVANTA PARA VER LUGARES CERCANOS + UBICACIONES EN DIRECTO + por 15 minutos + por 1 hora + por 8 horas + actualizada + recién actualizada + Tú y %1$s + %1$s compartida con %2$s + DETENER TODO + Estás enviando tu ubicación en directo a %1$s + Elige por cuánto tiempo %1$s verá tu ubicación precisa. + Elige por cuánto tiempo las personas del chat verán tu ubicación precisa. + Tu GPS parece estar desactivado. Por favor, actívalo para acceder a las características basadas en la ubicación. Ir a Multimedia + Mostrar en el chat Guardar en galería %1$d de %2$d Galería Todas las fotos Toda la multimedia Aún no hay fotos - Aún sin vídeos Por favor, primero descarga la multimedia No hay fotos recientes No hay GIF recientes @@ -1068,7 +1198,6 @@ Buscar foto Buscar GIF Recortar imagen - Editar imagen Realzar Iluminación Contraste @@ -1080,15 +1209,12 @@ Grano Nitidez Desvanecer - Matiz SOMBRAS ILUMINACIÓN - Curvas TODO ROJO VERDE AZUL - Desenfoque Apagado Lineal Radial @@ -1097,16 +1223,11 @@ ¿Descartar cambios? ¿Quieres eliminar el historial de búsqueda? Eliminar - Fotos - Vídeo Añadir un comentario... Comentario de foto Comentario de vídeo Comentario en el GIF Comentario - Dibujar - Stickers - Texto Eliminar Editar Duplicar @@ -1115,6 +1236,8 @@ Restablecer Original 1:1 + Mostrar multimedia en mensajes por separado + Agrupar multimedia en un mensaje Verificación en dos pasos Poner contraseña adicional @@ -1129,13 +1252,15 @@ Por favor, añade un e-mail válido. Es la única forma de recuperar una contraseña olvidada. Omitir Advertencia - En serio.\n\nSi olvidas tu contraseña, perderás el acceso a tu cuenta de Telegram. No habrá manera de recuperarla. + En serio. + +Si olvidas tu contraseña, perderás el acceso a tu cuenta de Telegram. No habrá manera de recuperarla. ¡Ya casi! Por favor, revisa tu e-mail (no olvides la carpeta de spam) para completar la configuración de la verificación en dos pasos. ¡Listo! Tu contraseña para la verificación en dos pasos está activada. Cambiar contraseña - Desactivar la contraseña + Desactivar contraseña Poner e-mail de recuperación Cambiar e-mail de recuperación ¿Quieres desactivar tu contraseña? @@ -1143,19 +1268,28 @@ Por favor, crea una pista para tu contraseña Las contraseñas no coinciden Anular la configuración de la verificación en dos pasos - Por favor, sigue estos pasos para completar la configuración de la verificación en dos pasos:\n\n1. Revisa tu e-mail (no olvides la carpeta de spam).\n%1$s\n\n2. Haz click en el enlace de validación. + Por favor, sigue estos pasos para completar la configuración de la verificación en dos pasos: + +1. Revisa tu e-mail (no olvides la carpeta de spam). +%1$s + +2. Haz click en el enlace de validación. La pista debe ser diferente de tu contraseña E-mail inválido Lo sentimos Como no estableciste un e-mail de recuperación cuando configuraste tu contraseña, las opciones restantes son recordar tu contraseña o restablecer tu cuenta. - Enviamos un código de recuperación al e-mail que nos diste:\n\n%1$s + Enviamos un código de recuperación al e-mail que nos diste: + +%1$s Por favor, revisa tu e-mail y pon el código de 6 dígitos que te enviamos ahí. ¿Tienes problemas para acceder a tu e-mail %1$s? Si no puedes acceder a tu e-mail, las opciones restantes son recordar tu contraseña o restablecer tu cuenta. RESTABLECER MI CUENTA Si continúas con el reinicio de tu cuenta, perderás todos tus chats y mensajes, junto con toda la multimedia y archivos que compartiste. Advertencia - Esta acción no se puede deshacer.\n\nSi restableces tu cuenta, todos tus mensajes y chats se eliminarán. + Esta acción no se puede deshacer. + +Si restableces tu cuenta, todos tus mensajes y chats se eliminarán. Restablecer Contraseña Activaste la verificación en dos pasos, así que tu cuenta está protegida con una contraseña adicional. @@ -1163,7 +1297,8 @@ Recuperar contraseña Código Contraseña desactivada - Tienes activada la verificación en dos pasos.\nNecesitarás la contraseña que configuraste para iniciar tu sesión en Telegram. + Tienes activada la verificación en dos pasos. +Necesitarás la contraseña que configuraste para iniciar tu sesión en Telegram. Tu e-mail de recuperación %1$s aún no está activo y su confirmación está pendiente. Datos y almacenamiento @@ -1207,8 +1342,6 @@ Autodestrucción de la cuenta Si estoy fuera Si no estás en línea durante este tiempo, al menos una vez, tu cuenta se eliminará con todos tus grupos, mensajes y contactos. - ¿Queres eliminar tu cuenta? - Elige quién puede ver tu última conexión. ¿Quién puede ver tu última conexión? Añadir excepciones Importante: No podrás ver la última conexión de las personas con las que no compartes la tuya. En su lugar, se mostrarán conexiones indeterminadas (recientemente, hace unos días, hace unas semanas). @@ -1230,7 +1363,7 @@ No permitir Permitir... No permitir... - Estos usuarios podrán o no añadirte a grupos y canales, independiente de los ajustes de arriba. + Estos usuarios podrán o no añadirte a grupos y canales, sin importar los ajustes de arriba. Cambia quiénes pueden añadirte a grupos o canales. No puedes añadir este usuario a grupos debido a sus ajustes de privacidad. No puedes añadir este usuario a canales debido a sus ajustes de privacidad. @@ -1238,7 +1371,6 @@ Peer-to-Peer Al desactivar peer-to-peer todas las llamadas pasarán por los servidores de Telegram para evitar dar conocer tu dirección IP, pero disminuirá ligeramente la calidad de audio. - Editar vídeo Enviando vídeo... Enviando GIF... @@ -1255,8 +1387,6 @@ Detener bot Reiniciar bot - Siguiente - Atrás Hecho Abrir Guardar @@ -1277,8 +1407,6 @@ OK OK RECORTAR - - No Te uniste al grupo con un enlace de invitación un1 se unió al grupo con un enlace de invitación @@ -1312,6 +1440,7 @@ El vídeo expiró GIF Ubicación + Ubicación en directo Contacto Archivo Sticker @@ -1340,12 +1469,14 @@ ¿Añadir a %1$s al chat %2$s? Cantidad de últimos mensajes para reenviar: ¿Añadir a %1$s al grupo? - Este usuario ya está en el grupo - ¿Reenviar mensajes a %1$s? ¿Enviar mensajes a %1$s? ¿Compartir el juego con %1$s? ¿Enviar contacto a %1$s? - ¿Quieres cerrar sesión?\n\nRecuerda que puedes usar Telegram en todos tus dispositivos a la vez.\n\nNo olvides que, al cerrar sesión, eliminas todos tus chats secretos. + ¿Quieres cerrar sesión? + +Recuerda que puedes usar Telegram en todos tus dispositivos a la vez. + +No olvides que, al cerrar sesión, eliminas todos tus chats secretos. ¿Quieres terminar todas las otras sesiones? ¿Quieres eliminar y salir del grupo? ¿Quieres eliminar este chat? @@ -1356,7 +1487,7 @@ Este bot podría querer saber tu ubicación cada vez que le envías una solicitud. Esto puede ser usado para entregar resultados específicos para tu ubicación. ¿Compartir tu número de teléfono? El bot sabrá tu número de teléfono. Puede ser útil para la integración con otros servicios. - ¿Quieres compartir tu número de teléfono %1$s con ]]>%2$s]]>? + ¿Quieres compartir tu número de teléfono %1$s con **%2$s**? ¿Quieres compartir tu número de teléfono? ¿Quieres bloquear este contacto? ¿Quieres desbloquear este contacto? @@ -1365,18 +1496,15 @@ ¿Quieres cancelar el registro? ¿Quieres eliminar el historial? ¿Eliminar de la caché los mensajes y multimedia de este canal? - ¿Eliminar de la caché los mensajes y multimedia de este grupo? + ¿Eliminar todos los mensajes y multimedia en la caché de este grupo? ¿Quieres eliminar %1$s? ¿Enviar mensajes a %1$s? ¿Compartir el juego en %1$s? ¿Enviar contacto a %1$s? - ¿Reenviar mensajes a %1$s? - Lo sentimos, esta característica no está disponible en tu país actualmente. No hay ninguna cuenta de Telegram con este alias. Este bot no puede unirse a grupos. ¿Quieres permitir las vistas previas ampliadas en chats secretos? Ten en cuenta que son generadas en los servidores de Telegram. Ten en cuenta que los bots integrados son hechos por terceros. Para que funcione, los símbolos escritos después del alias del bot, son enviados al desarrollador respectivo. - ¿Quieres habilitar “Elevar para hablar” para mensajes de voz? No puedes editar este mensaje. Por favor, permite a Telegram recibir SMS, para ingresar el código automáticamente. Por favor, permite a Telegram recibir llamadas, para ingresar el código automáticamente. @@ -1407,14 +1535,20 @@ Segura Poderosa Basada en la nube - La aplicación de mensajería más veloz]]> del mundo.\nEs gratis]]> y segura]]>. - Telegram]]> entrega mensajes más rápido\nque cualquier otra aplicación. - Telegram]]> es gratis para siempre. Sin publicidad.\nSin suscripciones. - Telegram]]> mantiene tus mensajes\na salvo del ataque de hackers. - Telegram]]> no tiene límites en el\ntamaño de tus chats y archivos. - Telegram]]> te permite acceder a tus mensajes\ndesde múltiples dispositivos. + La aplicación de mensajería más **veloz** del mundo. +Es **gratis** y **segura**. + **Telegram** entrega mensajes más rápido +que cualquier otra aplicación. + **Telegram** es gratis para siempre. Sin publicidad. +Sin suscripciones. + **Telegram** mantiene tus mensajes +a salvo del ataque de hackers. + **Telegram** no tiene límites en el +tamaño de tus chats y archivos. + **Telegram** te permite acceder a tus mensajes +desde múltiples dispositivos. Comenzar - + Ajustes de la cuenta Usar menos datos Llamada entrante @@ -1431,14 +1565,14 @@ Llamada de Telegram en curso Finalizar llamada Otra llamada en curso - Actualmente tienes una llamada en curso con %1$s]]>. ¿Quieres colgar esa llamada y empezar una nueva con %2$s]]>? + Actualmente tienes una llamada en curso con **%1$s**. ¿Quieres colgar esa llamada y empezar una nueva con **%2$s**? Llamadas de voz Tono de llamada Puedes personalizar el tono de llamada desde este contacto en Telegram. Llamadas ¿Quién puede llamarme? Puedes restringir quién puede llamarte. - Estos usuarios podrán o no podrán llamarte, a partir de los ajustes de arriba. + Estos usuarios podrán o no podrán llamarte, sin importar los ajustes de arriba. Nunca Sólo datos móviles Siempre @@ -1455,12 +1589,10 @@ Llamada cancelada Llamada rechazada %1$s (%2$s) - El texto e imagen derivan de la clave de cifrado para la llamada con ]]>%1$s]]>.\n\nSi se ven igual en el dispositivo de ]]>%2$s]]> el cifrado end-to-end está garantizado. Aún no has realizado llamadas. - La app de ]]>%1$s]]> está usando un protocolo incompatible. Necesita actualizar la app para poder llamar. - La app de ]]>%1$s]]> no soporta llamadas. Necesita actualizar la app para poder llamar. + La app de **%1$s** está usando un protocolo incompatible. Necesita actualizar la app para poder llamar. + La app de **%1$s** no soporta llamadas. Necesita actualizar la app para poder llamar. Por favor, evalúa la calidad de tu llamada de Telegram - ¿Quieres dejar algún comentario para ayudarnos a mejorar las llamadas? Telegram necesita acceso a tu micrófono para poder realizar llamadas. Añade un comentario opcional Devolver llamada @@ -1472,7 +1604,7 @@ Altavoz Bluetooth VOLVER A LA LLAMADA - Lo sentimos, ]]>%1$s]]> no acepta llamadas. + Lo sentimos, **%1$s** no acepta llamadas. Si los emojis son los mismos para %1$s, la llamada es 100%% segura. Evalúa esta llamada ¿Qué ocurrió? @@ -1480,12 +1612,36 @@ Esto no revelará los contenidos de tu conversación, pero nos ayudará a resolver el problema más pronto. Gracias por ayudarnos a mejorar las llamadas de Telegram. + %1$d destinatarios + %1$d destinatario + %1$d destinatarios + %1$d destinatarios + %1$d destinatarios + %1$d destinatarios %1$d en línea %1$d en línea %1$d en línea %1$d en línea %1$d en línea %1$d en línea + %1$d contactos en Telegram + %1$d contacto en Telegram + %1$d contactos en Telegram + %1$d contactos en Telegram + %1$d contactos en Telegram + %1$d contactos en Telegram + Estoy usando Telegram para chatear, y también %1$d de nuestros otros contactos. ¡Únete! Descárgala aquí: %2$s + Estoy usando Telegram para chatear, y también %1$d de nuestros otros contactos. ¡Únete! Descárgala aquí: %2$s + Estoy usando Telegram para chatear, y también %1$d de nuestros otros contactos. ¡Únete! Descárgala aquí: %2$s + Estoy usando Telegram para chatear, y también %1$d de nuestros otros contactos. ¡Únete! Descárgala aquí: %2$s + Estoy usando Telegram para chatear, y también %1$d de nuestros otros contactos. ¡Únete! Descárgala aquí: %2$s + Estoy usando Telegram para chatear, y también %1$d de nuestros otros contactos. ¡Únete! Descárgala aquí: %2$s + %1$d chats + %1$d chat + %1$d chats + %1$d chats + %1$d chats + %1$d chats %1$d miembros %1$d miembro %1$d miembros @@ -1498,25 +1654,31 @@ y %1$d más están escribiendo y %1$d más están escribiendo y %1$d más están escribiendo - Sin mensajes nuevos + %1$s y %2$d más están escribiendo + %1$s y %2$d más está escribiendo + %1$s y %2$d más están escribiendo + %1$s y %2$d más están escribiendo + %1$s y %2$d más están escribiendo + %1$s y %2$d más están escribiendo + %1$d mensajes nuevos %1$d mensaje nuevo %1$d mensajes nuevos %1$d mensajes nuevos %1$d mensajes nuevos %1$d mensajes nuevos - Sin mensajes + %1$d mensajes %1$d mensaje %1$d mensajes %1$d mensajes %1$d mensajes %1$d mensajes - Sin ítems + %1$d ítems %1$d ítem %1$d ítems %1$d ítems %1$d ítems %1$d ítems - desde ningún chat + desde %1$d chats desde %1$d chat desde %1$d chats desde %1$d chats @@ -1588,61 +1750,55 @@ %1$d stickers %1$d stickers %1$d stickers - %1$d fotos - %1$d foto - %1$d fotos - %1$d fotos - %1$d fotos - %1$d fotos + %1$d suscriptores + %1$d suscriptor + %1$d suscriptores + %1$d suscriptores + %1$d suscriptores + %1$d suscriptores %1$d puntos %1$d punto %1$d puntos %1$d puntos %1$d puntos %1$d puntos - últ. vez hace %1$d minutos - últ. vez hace %1$d minuto - últ. vez hace %1$d minutos - últ. vez hace %1$d minutos - últ. vez hace %1$d minutos - últ. vez hace %1$d minutos - últ. vez hace %1$d horas - últ. vez hace %1$d hora - últ. vez hace %1$d horas - últ. vez hace %1$d horas - últ. vez hace %1$d horas - últ. vez hace %1$d horas - %1$d]]> segundos - %1$d]]> segundo - %1$d]]> segundos - %1$d]]> segundos - %1$d]]> segundos - %1$d]]> segundos - %1$d]]> minutos - %1$d]]> minuto - %1$d]]> minutos - %1$d]]> minutos - %1$d]]> minutos - %1$d]]> minutos - %1$d]]> horas - %1$d]]> hora - %1$d]]> horas - %1$d]]> horas - %1$d]]> horas - %1$d]]> horas - %1$d]]> días - %1$d]]> día - %1$d]]> días - %1$d]]> días - %1$d]]> días - %1$d]]> días + hace %1$d minutos + hace %1$d minuto + hace %1$d minutos + hace %1$d minutos + hace %1$d minutos + hace %1$d minutos + **%1$d** segundos + **%1$d** segundo + **%1$d** segundos + **%1$d** segundos + **%1$d** segundos + **%1$d** segundos + **%1$d** minutos + **%1$d** minuto + **%1$d** minutos + **%1$d** minutos + **%1$d** minutos + **%1$d** minutos + **%1$d** horas + **%1$d** hora + **%1$d** horas + **%1$d** horas + **%1$d** horas + **%1$d** horas + **%1$d** días + **%1$d** día + **%1$d** días + **%1$d** días + **%1$d** días + **%1$d** días - %1$d mensajes adjuntos - Mensaje adjunto - %1$d mensajes adjuntos - %1$d mensajes adjuntos - %1$d mensajes adjuntos - %1$d mensajes adjuntos + %1$d mensajes adjuntos + Mensaje adjunto + %1$d mensajes adjuntos + %1$d mensajes adjuntos + %1$d mensajes adjuntos + %1$d mensajes adjuntos %1$d archivos adjuntos Archivo adjunto %1$d archivos adjuntos @@ -1673,7 +1829,7 @@ %1$d mensajes de voz reenviados %1$d mensajes de voz reenviados %1$d mensajes de voz reenviados - %1$d reenvió videomensajes + %1$d videomensajes reenviados Videomensaje reenviado %1$d videomensajes reenviados %1$d videomensajes reenviados @@ -1697,7 +1853,7 @@ %1$d stickers adjuntos %1$d stickers adjuntos %1$d stickers adjuntos - y %1$d otros + y otros %1$d and %1$d más y otros %1$d y otros %1$d diff --git a/TMessagesProj/src/main/res/values-it/strings.xml b/TMessagesProj/src/main/res/values-it/strings.xml index 8011b9616..ecf44c666 100644 --- a/TMessagesProj/src/main/res/values-it/strings.xml +++ b/TMessagesProj/src/main/res/values-it/strings.xml @@ -1,7 +1,4 @@ - - - Telegram Telegram Beta @@ -9,28 +6,34 @@ Inglese Italian it - Continua in Italiano + Continua in italiano Il tuo telefono - Conferma il prefisso nazionale e inserisci il tuo numero di telefono. + Conferma il prefisso internazionale e inserisci il tuo numero di telefono. Scegli una nazione Prefisso errato Verifica numero - Abbiamo inviato un SMS con un codice di attivazione al tuo numero ]]>%1$s]]>. - Abbiamo inviato il codice su ]]>Telegram]]> nel tuo altro dispositivo. - Abbiamo inviato una chiamata di attivazione al tuo numero ]]>%1$s]]>.\n\nNon rispondere, Telegram farà tutto in automatico. - Stiamo chiamando il tuo numero ]]>%1$s]]> per dettarti un codice. - Ti telefoneremo tra %1$d:%2$02d + Abbiamo inviato un SMS con un codice di attivazione al tuo numero **%1$s**. + Abbiamo inviato il codice su **Telegram** nel tuo altro dispositivo. + Abbiamo inviato una chiamata di attivazione al tuo numero **%1$s**. + +Non rispondere, Telegram farà tutto in automatico. + Stiamo chiamando il tuo numero **%1$s** per dettarti un codice. + Ti chiameremo tra %1$d:%2$02d Ti invieremo un SMS tra %1$d:%2$02d Ti stiamo chiamando... Codice Numero errato? Non hai ricevuto il codice? Annulla ripristino account - Qualcuno con accesso al tuo numero di telefono ]]>%1$s ha richiesto l\'eliminazione del tuo account Telegram e il ripristino della password della verifica in due passaggi.\n\nSe non sei stato tu, per favore inserisci il codice che abbiamo appena inviato tramite SMS al tuo numero. + Qualcuno con accesso al tuo numero di telefono **%1$s** ha richiesto l\'eliminazione del tuo account Telegram e il ripristino della password della verifica in due passaggi. + +Se non sei stato tu, per favore inserisci il codice che abbiamo appena inviato tramite SMS al tuo numero. Ripristino account - Dato che l\'account ]]>%1$s è attivo e protetto da una password, lo elimineremo tra 1 settimana per motivi di sicurezza.\n\nPuoi annullare questo processo in qualsiasi momento. + Dato che l\'account **%1$s** è attivo e protetto da una password, lo elimineremo tra 1 settimana per motivi di sicurezza. + +Puoi annullare questo processo in qualsiasi momento. Potrai ripristinare il tuo account tra: I tuoi tentativi recenti di ripristinare questo account sono stati annullati dal suo utente attivo. Per favore riprova tra 7 giorni. RIPRISTINA @@ -40,7 +43,7 @@ Il tuo nome Inserisci il tuo nome e cognome - Nome (richiesto) + Nome (obbigatorio) Cognome (facoltativo) Annulla iscrizione @@ -59,17 +62,16 @@ Codice postale Destinatario Nome completo - Phone Number - E-Mail + Numero di telefono + EMail Salva info di spedizione Puoi salvare le tue informazioni di spedizione per uso futuro. Informazioni di pagamento Carta di credito Numero della carta Codice di sicurezza (CVV) - MM/YY + MM/AA Indirizzo di fatturazione - Titolare della carta Nome e cognome Salva informazioni di pagamento Puoi salvare le tue informazioni di pagamento per uso futuro. @@ -93,7 +95,20 @@ Spiacenti, il pagamento è stato rifiutato. Impossibile raggiungere il server di pagamento. Per favore controlla la tua connessione a Internet e riprova. Attenzione - Né Telegram, né %1$s avranno accesso alle informazioni della tua carta di credito. I dettagli della tua carta di credito saranno gestiti solamente dal sistema di pagamento, %2$s.\n\nI pagamenti saranno inviati direttamente allo sviluppatore di %1$s. Telegram non può fornire alcuna garanzia, per cui procedi a tuo rischio. In caso di problemi, per favore contatta lo sviluppatore di %1$s o la tua banca. + Né Telegram, né %1$s avranno accesso alle informazioni della tua carta di credito. I dettagli della tua carta di credito saranno gestiti solamente dal sistema di pagamento, %2$s. + +I pagamenti saranno inviati direttamente allo sviluppatore di %1$s. Telegram non può fornire alcuna garanzia, per cui procedi a tuo rischio. In caso di problemi, per favore contatta lo sviluppatore di %1$s o la tua banca. + Password e email + Password + Inserisci una password + Reinserisci la tua password + Per favore crea una password per proteggere le tue info di pagamento. Ti verrà chiesto di inserirla quando accedi. + Email di recupero + La tua email + Per favore inserisci un\'email valida. È l\'unico modo per recuperare una password dimenticata. + Il numero sarà passato a %1$s come info di fatturazione. + L\' e-mail sarà passata a %1$s come info di fatturazione. + Il numero e l\'e-mail saranno passati a %1$s come info di fatturazione. Nuova chat Impostazioni @@ -102,7 +117,11 @@ ieri Nessun risultato Ancora nessuna chat... - Inizia a messaggiare premendo il tasto\nnuovo messaggio in basso a destra\no apri il menù per avere più opzioni. + Visualizzato di recente + NASCONDI + Inizia a messaggiare premendo il tasto +nuovo messaggio in basso a destra +o apri il menù per avere più opzioni. Attendo la rete... Connetto... Connetto al proxy... @@ -125,10 +144,12 @@ Elimina chat Account eliminato Seleziona chat - Tieni premuto per vedere + Inoltra a... Foto segreta Video segreto - %1$s sta usando una versione vecchia di Telegram, quindi le foto segrete verranno visualizzate in modalità di compatibilità.\n\nUna volta che %2$s avrà aggiornato Telegram, le foto con il timer minore di 1 minuto funzioneranno in modalità \'Tieni premuto per vedere\' , e verrai notificato ogni volta che l\'altro esegue uno screenshot. + %1$s sta usando una versione vecchia di Telegram, quindi le foto segrete verranno visualizzate in modalità di compatibilità. + +Una volta che %2$s avrà aggiornato Telegram, le foto con il timer minore di 1 minuto funzioneranno in modalità \'Tieni premuto per vedere\' , e verrai notificato ogni volta che l\'altro esegue uno screenshot. MESSAGGI Cerca Silenzia notifiche @@ -143,8 +164,8 @@ Anteprima link Bozza Cronologia eliminata - di %1$s - %1$s di %2$s + da %1$s + %1$s da %2$s Lascia un feedback su questa anteprima Invia sticker Visualizza pacchetto @@ -153,16 +174,12 @@ Grassetto Corsivo Normale - Per connetterti senza problemi con tutti i tuoi conoscenti, permetti a ]]>Telegram]]> di accedere ai tuoi contatti. + Per connetterti senza problemi con tutti i tuoi conoscenti, permetti a **Telegram** di accedere ai tuoi contatti. NON ADESSO CONTINUA - Pubblico - Privato Rendi amministratore Nessun utente bloccato - Puoi inserire una descrizione opzionale per il tuo gruppo. - Lascia il gruppo Elimina gruppo Lascia il gruppo Elimina gruppo @@ -188,6 +205,7 @@ un1 ha fissato un contatto un1 ha fissato %1$s un1 ha fissato una posizione + un1 ha fissato una posizione attuale un1 ha fissato una GIF un1 ha fissato una traccia Questo gruppo è stato aggiornato a supergruppo @@ -235,6 +253,7 @@ Controllo il nome... %1$s è disponibile. Membri + Iscritti Utenti bloccati Utenti bloccati Utenti limitati @@ -245,16 +264,15 @@ Sei sicuro di voler lasciare il canale? Perderai tutti i messaggi in questo canale. Modifica - Per favore nota che se scegli un link pubblico per il tuo gruppo, chiunque potrà trovarlo nella ricerca e unirsi.\n\nNon creare questo link se vuoi che il tuo supergruppo rimanga privato. - Per favore nota che se scegli un link pubblico per il tuo canale, chiunque potrà trovarlo nella ricerca e unirsi.\n\nNon creare questo link se vuoi che il tuo canale rimanga privato. - Per favore scegli un link per il tuo canale pubblico, in modo che possa essere trovato nella ricerca e condiviso con altri.\n\nSe non sei interessato, ti consigliamo di creare un canale privato. + Per favore scegli un link per il tuo canale pubblico, in modo che possa essere trovato nella ricerca e condiviso con altri. + +Se non sei interessato, ti consigliamo di creare un canale privato. Canale creato Foto del canale cambiata Foto del canale rimossa Nome del canale cambiato in un2 Spiacenti, hai riservato troppi username pubblici. Puoi revocare il link da uno dei tuoi gruppi o canali più vecchi, o creare invece delle entità private. Creatore - Amministratore SILENZIA SUONA Aggiungi amministratore @@ -262,10 +280,8 @@ Sblocca Tieni premuto sull\'utente per sbloccarlo. Invita tramite link - Sei sicuro di voler rendere %1$s un amministratore? Rimuovi amministratore Solo gli amministratori del canale possono vedere questa lista. - Questo utente non si è ancora unito al canale. Vuoi invitarlo? Chiunque abbia Telegram installato potrà aggiungersi al tuo canale aprendo questo link. Puoi aggiungere amministratori per farti aiutare a gestire il tuo canale. Tieni premuto per rimuovere gli amministratori. Vuoi unirti al canale \'%1$s\'? @@ -285,18 +301,6 @@ Spiacenti, non puoi inviare messaggi in questo canale %1$s ti ha aggiunto al canale %2$s Il canale %1$s ha aggiornato la foto - %1$s ha inviato un messaggio al canale %2$s - %1$s ha inviato una foto al canale %2$s - %1$s ha inviato un video al canale %2$s - %1$s ha condiviso un contatto con il canale%2$s - %1$s ha inviato una posizione al canale %2$s - %1$s ha inviato un file al canale %2$s - %1$s ha inviato una GIF al canale %2$s - %1$s ha inviato un messaggio vocale al canale %2$s - %1$s ha inviato un videomessaggio al canale %2$s - %1$s ha inviato una traccia al canale %2$s - %1$s ha inviato uno sticker al canale %2$s - %1$s ha inviato uno %3$s sticker al canale %2$s %1$s ha pubblicato un messaggio %1$s ha pubblicato una foto %1$s ha pubblicato un video @@ -342,21 +346,36 @@ Inviare link con anteprima Bloccato fino a Sempre - Rimuovi Blocca e rimuovi dal gruppo Gestisci gruppo Gestisci canale Gestisci gruppo Gestisci canale + Cronologia per i nuovi membri + Visibile + I nuovi membri vedranno i messaggi che sono stati inviati prima che si unissero. + Nascosta + I nuovi membri non vedranno i messaggi precedenti. Azioni recenti Tutte le azioni Azioni selezionate Tutti gli amministratori - ]]>Ancora nessuna azione]]>\n\nI membri e gli amministratori del\ngruppo non hanno eseguito azioni\ndi servizio nelle ultime 48 ore. - ]]>Ancora nessuna azione]]>\n\nGli amministratori del canale non\nhanno eseguito azioni di servizio\nnelle ultime 48 ore. - ]]>Nessuna azione trovata]]>\n\nNon sono state trovate azioni\nrecenti che rispondono alla tua richiesta. - Non sono state trovate azioni recenti contenenti \']]>%1$s]]>\' . + **Ancora nessuna azione** + +I membri e gli amministratori del +gruppo non hanno eseguito azioni +di servizio nelle ultime 48 ore. + **Ancora nessuna azione** + +Gli amministratori del canale non +hanno eseguito azioni di servizio +nelle ultime 48 ore. + **Nessuna azione trovata** + +Non sono state trovate azioni +recenti che rispondono alla tua richiesta. + Non sono state trovate azioni recenti contenenti \'**%1$s**\' . Cosa sono le azioni recenti? Questa è una lista di tutte le azioni di servizio effettuate dai membri e dagli amministratori del gruppo nelle ultime 48 ore. Questa è una lista di tutte le azioni di servizio eseguite dagli amministratori del canale nelle ultime 48 ore. @@ -381,7 +400,9 @@ un1 ha fissato questo messaggio: un1 ha tolto un messaggio un1 ha eliminato questo messaggio: - un1 ha modificato il link del gruppo: + un1 ha cambiato il set di sticker del gruppo + un1 ha rimosso il set di sticker del gruppo + un1 ha cambiato il link del gruppo: un1 ha modificato il link del canale: un1 ha rimosso il link del gruppo un1 ha rimosso il link del canale @@ -389,11 +410,15 @@ un1 ha modificato la descrizione del gruppo: un1 ha modificato la descrizione del canale: Descrizione precedente + un1 ha reso visibile la cronologia del gruppo ai nuovi membri + un1 ha nascosto la cronologia del gruppo ai nuovi membri un1 ha attivato gli inviti del gruppo un1 ha disattivato gli inviti del gruppo un1 ha attivato le firme un1 ha disattivato le firme - ha cambiato le restrizioni di %1$s\n\nDurata: %2$s + ha cambiato le restrizioni di %1$s + +Durata: %2$s Inviare sticker e GIF Inviare media Inviare messaggi @@ -415,7 +440,6 @@ Nuovi membri Info gruppo Info canale - Impostazioni gruppo Messaggi eliminati Messaggi modificati Messaggi fissati @@ -431,12 +455,13 @@ Musica Artista sconosciuto Titolo sconosciuto + Casuale + Inverti ordine Seleziona file Liberi %1$s di %2$s Errore sconosciuto Errore di accesso - Ancora nessun file... La dimensione del file non dovrebbe superare i %1$s Archivio non montato Trasferimento USB attivo @@ -446,11 +471,22 @@ Scheda SD Cartella Per inviare immagini senza compressione + + Sticker del gruppo + Scegli dai tuoi sticker + Puoi rendere un set di sticker disponibile a tutti i membri che scrivono all\'interno di questo gruppo. + SCEGLI SET DI STICKER + stickerset + Puoi creare i tuoi set di sticker personalizzati tramite il bot @stickers. + Nessun set di sticker trovato + Riprova o scegli dalla lista seguente invisibile sta scrivendo... sta scrivendo... stanno scrivendo... + %1$s sta scrivendo... + %1$s stanno scrivendo... %1$s sta registrando un messaggio vocale... %1$s sta registrando un videomessaggio... %1$s sta inviando un audio... @@ -469,8 +505,8 @@ sta giocando a un gioco... sta inviando un video... sta inviando un file... - Hai una domanda\nsu Telegram? - Scatta foto + Hai una domanda +su Telegram? Galleria Posizione Video @@ -479,9 +515,10 @@ Ancora nessun messaggio qui... Messaggio inoltrato Da + da: Nessun recente Messaggio - Messaggi + Messaggio Condividi il mio contatto Aggiungi ai contatti %s ti ha invitato a unirti ad una chat segreta. @@ -509,7 +546,6 @@ Recupero le info del link... APRI IN... Apri in... - Copia URL Invia %1$s Invia come file Invia come file @@ -523,9 +559,6 @@ Sei sicuro di voler segnalare dello spam da questo canale? Spiacenti, al momento puoi scrivere solo ai contatti reciproci. Spiacenti, al momento puoi aggiungere ai gruppi solo i contatti reciproci . - Oggi hai scritto a troppi non-contatti, per favore riprova domani. Potrai rispondere oggi se questo utente ti scrive per primo. - Non puoi aggiungere questo utente perché hai scritto a troppi non-contatti oggi. Per favore riprova domani. Puoi chiedere a un altro membro di aggiungere questo utente al gruppo. - https://telegram.org/faq/it#non-posso-inviare-messaggi-a-chi-non-far-parte-dei-miei-contatti Maggiori info Invia a... Scrivi un commento... @@ -534,6 +567,7 @@ Notifica tutti i membri Togli Vuoi fissare questo messaggio in questo gruppo? + Vuoi fissare questo messaggio in questo canale? Vuoi togliere questo messaggio? Rimuovi utente Segnala spam @@ -552,8 +586,9 @@ Spiacenti, tempo di modifica scaduto. Aggiungi scorciatoia Cerca membri - Scorciatoia aggiunta alla schermata home - chat con te stesso + inoltra qui per salvare + Messaggi salvati + Inoltra qui per salvare. Tu Inoltra i messaggi qui per salvarli Invia media e file per archiviarli @@ -579,6 +614,7 @@ Gli amministratori di questo gruppo ti hanno vietato di inviare contenuti inline qui Gli amministratori di questo gruppo ti hanno vietato di inviare sticker qui Gli amministratori di questo gruppo ti hanno vietato di scrivere qui + amministratore %1$s ha impostato il timer di autodistruzione a %2$s Hai impostato il timer di autodistruzione a %1$s @@ -625,7 +661,18 @@ %1$s ti ha rimosso dal gruppo %2$s %1$s ha lasciato il gruppo %2$s %1$s si è unito a Telegram! - %1$s,\nAbbiamo rilevato un accesso al tuo account da un nuovo dispositivo il %2$s\n\nDispositivo: %3$s\nPosizione: %4$s\n\nSe non sei stato tu, puoi andare nelle Impostazioni - Privacy e sicurezza - Sessioni - Termina tutte le sessioni.\n\nSe pensi che qualcuno si sia collegato al tuo account contro il tuo volere, puoi attivare la verifica in due passaggi nelle impostazioni di Privacy e sicurezza.\n\nCordiali saluti,\nil team di Telegram + %1$s, +Abbiamo rilevato un accesso al tuo account da un nuovo dispositivo il %2$s + +Dispositivo: %3$s +Posizione: %4$s + +Se non sei stato tu, puoi andare nelle Impostazioni - Privacy e sicurezza - Sessioni - Termina tutte le sessioni. + +Se pensi che qualcuno si sia collegato al tuo account contro il tuo volere, puoi attivare la verifica in due passaggi nelle impostazioni di Privacy e sicurezza. + +Cordiali saluti, +il team di Telegram %1$s ha aggiornato la foto del profilo %1$s si è unito al gruppo %2$s tramite link d\'invito Rispondi @@ -644,11 +691,13 @@ %1$s ha fissato un videomessaggio nel gruppo %2$s %1$s ha fissato un contatto nel gruppo %2$s %1$s ha fissato una posizione nel gruppo %2$s + %1$s ha fissato una posizione attuale nel gruppo %2$s %1$s ha fissato una GIF nel gruppo %2$s %1$s ha fissato una traccia nel gruppo %2$s %1$s ha fissato \"%2$s\" %1$s ha fissato un messaggio %1$s ha fissato una foto + %1$s ha fissato un gioco %1$s ha fissato un video %1$s ha fissato un file %1$s ha fissato uno sticker @@ -657,26 +706,31 @@ %1$s ha fissato un videomessaggio %1$s ha fissato un contatto %1$s ha fissato una posizione + %1$s ha fissato una posizione attuale %1$s ha fissato una GIF %1$s ha fissato una traccia - %1$s ha fissato un gioco Seleziona contatto Ancora nessun contatto - Ehi, è il momento di passare a Telegram: https://telegram.org/dl + Ehi, sto usando Telegram per messaggiare. Unisciti a me! Scaricalo qui: %1$s alle ieri alle in linea ultimo accesso ultimo accesso - ultimo accesso adesso Invita amici + Cerca amici RICERCA GLOBALE ultimo accesso di recente ultimo accesso entro una settimana ultimo accesso entro un mese ultimo accesso molto tempo fa Nuovo messaggio + Seleziona dei contatti per invitarli su Telegram + INVITA SU TELEGRAM + Condividi Telegram... + Aggiornare i contatti? + Telegram ha trovato molti contatti non sincronizzati, vorresti sincronizzarli adesso? Scegli \'OK\' se stai usando il tuo dispositivo, la tua SIM e il tuo account Google. Aggiungi persone... Potrai aggiungere più utenti dopo aver creato il gruppo e averlo convertito in supergruppo. @@ -684,7 +738,6 @@ Nome del gruppo %1$d di %2$d fino a %1$s - Vuoi unirti alla chat \'%1$s\'? Spiacenti, questo gruppo è già pieno. Spiacenti, sembra che questa chat non esista. Link copiato negli appunti @@ -694,8 +747,12 @@ Il precedente link d\'invito è ora inattivo. Ne è stato creato uno nuovo. Revoca Revoca link - Sei sicuro di voler revocare il link ]]>%1$s]]>?\n\nIl gruppo \"]]>%2$s]]>\" diventerà privato. - Sei sicuro di voler revocare il link ]]>%1$s]]>?\n\nIl canale \"]]>%2$s]]>\" diventerà privato. + Sei sicuro di voler revocare il link **%1$s**? + +Il gruppo \"**%2$s**\" diventerà privato. + Sei sicuro di voler revocare il link **%1$s**? + +Il canale \"**%2$s**\" diventerà privato. Copia link Condividi link Chiunque abbia Telegram installato, potrà aggiungersi al tuo gruppo aprendo questo link. @@ -705,8 +762,10 @@ Tutti i membri possono aggiungere nuovi membri, modificare nome e foto del gruppo. Solo gli amministratori possono aggiungere e rimuovere membri e modificare nome e foto del gruppo. + Membri Media condivisi Impostazioni + Aggiungi iscritto Aggiungi membro Imposta amministratori RIMUOVI DAL GRUPPO @@ -719,9 +778,22 @@ Converti in supergruppo Attenzione Questa azione è irreversibile. Non è possibile trasformare un supergruppo in un gruppo normale. - ]]>Limite membri raggiunto.]]>\n\nPer superare il limite e sbloccare nuove funzioni, aggiorna a supergruppo:\n\n• I supergruppi hanno massimo %1$s\n• I nuovi membri vedono tutta la cronologia\n• I messaggi eliminati scompaiono per tutti\n• Gli admin possono fissare i messaggi importanti\n• Il creatore può creare un link pubblico per il gruppo - ]]>Nei supergruppi:]]>\n\n• I nuovi membri vedono tutta la cronologia\n• I messaggi eliminati scompaiono per tutti\n• Gli admin possono fissare i messaggi importanti\n• Il creatore può creare un link pubblico per il gruppo - ]]>Nota:]]> questa azione non può essere annullata. + **Limite membri raggiunto.** + +Per superare il limite e sbloccare nuove funzioni, aggiorna a supergruppo: + +• I supergruppi hanno massimo %1$s +• I nuovi membri vedono tutta la cronologia +• I messaggi eliminati scompaiono per tutti +• Gli admin possono fissare i messaggi importanti +• Il creatore può creare un link pubblico per il gruppo + **Nei supergruppi:** + +• I nuovi membri vedono tutta la cronologia +• I messaggi eliminati scompaiono per tutti +• Gli admin possono fissare i messaggi importanti +• Il creatore può creare un link pubblico per il gruppo + **Nota:** questa azione non può essere annullata. Condividi Aggiungi @@ -749,22 +821,28 @@ Se imposti un timer, la foto si autodistruggerà dopo essere stata vista. Se imposti un timer, il video si autodistruggerà dopo essere stato visto. Spento - L\'immagine e il testo sono derivati dalla chiave di crittografia di questa chat segreta con ]]>%1$s]]>.\n\nSe sono uguali sul dispositivo di ]]>%2$s]]>, la crittografia end-to-end è garantita.\n\nUlteriori informazioni su telegram.org + L\'immagine e il testo sono derivati dalla chiave di crittografia di questa chat segreta con **%1$s**. + +Se sono uguali sul dispositivo di **%2$s**, la crittografia end-to-end è garantita. + +Ulteriori informazioni su telegram.org https://telegram.org/faq/it#chat-segrete - Tocca per mostrare le emoji Sconosciuto Info Telefono Username - Il tuo Username + Il tuo username Spiacenti, questo username è già stato preso. - Spiacenti, questo username non valido. + Spiacenti, questo username non è valido. Un username deve avere almeno 5 caratteri. Il massimo per un username è 32 caratteri. Spiacenti, un username non può iniziare con un numero. - Puoi scegliere un username su ]]>Telegram]]>. Se lo fai, le altre persone potranno trovarti tramite questo username e contattarti senza conoscere il tuo numero di telefono.\n\nPuoi usare ]]>a–z]]>, ]]>0–9]]> e underscore. La lunghezza minima è di ]]>5]]> caratteri. - Questo link apre una chat con te su Telegram:\n%1$s + Puoi scegliere un username su **Telegram**. Se lo fai, le altre persone potranno trovarti tramite questo username e contattarti senza conoscere il tuo numero di telefono. + +Puoi usare **a–z**, **0–9** e underscore. La lunghezza minima è di **5** caratteri. + Questo link apre una chat con te su Telegram: +%1$s Controllo l\'username... %1$s è disponibile. Nessuno @@ -774,6 +852,13 @@ Aggiungi sticker Aggiungi maschere Aggiungi agli sticker + Aggiungi ai preferiti + Sticker aggiunto ai preferiti + Sticker rimosso dai preferiti + Usati di recente + Preferiti + Sticker del gruppo + Elimina dai preferiti Aggiungi alle maschere Sticker non trovati Sticker rimossi @@ -878,16 +963,17 @@ Messaggi fissati Lingua Personalizzata - Per favore nota che l\'assistenza di Telegram è fornita da volontari. Proviamo a rispondere quanto prima, ma potrebbe volerci del tempo.\n\nDai un\'occhiata alle domande frequenti di Telegram]]>: contengono suggerimenti importanti per risolvere i problemi]]> e risposte a quasi tutte le domande. + Per favore nota che l\'assistenza di Telegram è fornita da volontari. Proviamo a rispondere quanto prima, ma potrebbe volerci del tempo. + +Dai un\'occhiata alle domande frequenti di Telegram]]>: contengono suggerimenti importanti per risolvere i problemi]]> e risposte a quasi tutte le domande. Chiedi a un volontario + Domande frequenti Domande frequenti https://telegram.org/faq/it Informativa sulla privacy https://telegram.org/privacy Eliminare la traduzione? File di traduzione non valido - Abilitate - Disabilitata Servizio keep-alive Riavvia l\'app quando terminata dall\'utente o dal sistema. Questo assicura che l\'app possa mostrare le notifiche. Connessione in background @@ -906,6 +992,9 @@ Breve Lunga Download automatico media + Download automatico + Ripristina download automatico + Sei sicuro di voler ripristinare le impostazioni di download automatico? Quando utilizzi la rete cellulare Quando connesso tramite Wi-Fi In roaming @@ -920,19 +1009,19 @@ Priorità Come nelle Impostazioni Predefinita - Bassa Alta Massima Mai Ripeti notifiche - Puoi cambiare il tuo numero di telefono qui. Il tuo account e tutti i tuoi dati cloud — messaggi, file, contatti, etc. saranno trasferiti sul nuovo numero.\n\nImportante:]]> a tutti i tuoi contatti di Telegram verrà aggiunto il tuo nuovo numero]]> ai contatti, purché abbiano il tuo vecchio numero e tu non li abbia bloccati su Telegram. + Puoi cambiare il tuo numero di telefono qui. Il tuo account e tutti i tuoi dati cloud — messaggi, file, contatti, etc. saranno trasferiti sul nuovo numero. + +**Importante:** a tutti i tuoi contatti di Telegram verrà aggiunto il tuo **nuovo numero** ai contatti, purché abbiano il tuo vecchio numero e tu non li abbia bloccati su Telegram. Tutti i tuoi contatti Telegram avranno il tuo nuovo numero tra i loro contatti, purché abbiano il tuo vecchio numero e tu non li abbia bloccati su Telegram. CAMBIA NUMERO Nuovo numero Invieremo un SMS con un codice di conferma al tuo nuovo numero. Il numero %1$s è già connesso a un account Telegram. Per favore elimina quell\'account prima di migrare ad un nuovo numero. Altro - Disabilitate Disabilitata Abilitate Disabilitate @@ -959,6 +1048,10 @@ Menù debug Importa contatti Ricarica contatti + Ripristina conversazioni + Attiva fotocamera in-app + Disattiva fotocamera in-app + Ripristina contatti importati Puoi cambiare la lingua più tardi nelle Impostazioni. Scegli la tua lingua Altro @@ -972,10 +1065,22 @@ Impostazioni proxy SOCKS5 Usa il proxy per le chiamate I server proxy potrebbero ridurre la qualità delle tue chiamate. + Attenzione + Hai quasi esaurito lo spazio sul dispositivo. Per liberarlo, imposta Telegram per salvare nella cache solo i media recenti. + Rimuovi media dopo + Non rimuovere mai + Contatti + Chat private + Gruppi + Canali + Limita per dimensione + fino a %1$s Database locale Cancellare i messaggi salvati nella cache? - Svuotando il database locale, verranno cancellati i messaggi nella cache e il database verrà compresso per risparmiare spazio. Telegram ha bisogno di alcuni dati per funzionare, quindi il database non sarà azzerato.\n\nQuesta operazione può richiedere alcuni minuti. + Svuotando il database locale, verranno cancellati i messaggi nella cache e il database verrà compresso per risparmiare spazio. Telegram ha bisogno di alcuni dati per funzionare, quindi il database non sarà azzerato. + +Questa operazione può richiedere alcuni minuti. Vuota la cache Svuota Calcolo... @@ -988,7 +1093,9 @@ Altri file Vuota Mantieni media - Foto, video e altri file dalle chat nel cloud che non hai aperto]]> in questo periodo verranno eliminati dal dispositivo per preservare lo spazio sul disco.\n\nTutti i media rimarranno nel cloud di Telegram e potranno essere riscaricati ogni volta che ne avrai bisogno. + Foto, video e altri file dalle chat nel cloud che non hai **aperto** in questo periodo verranno eliminati dal dispositivo per preservare lo spazio sul disco. + +Tutti i media rimarranno nel cloud Telegram e potranno essere riscaricati ogni volta che ne avrai bisogno. Sempre Messaggi vocali Videomessaggi @@ -1005,8 +1112,9 @@ Codice di blocco Cambia codice - Quando imposti un codice, apparirà un\'icona col lucchetto nella pagina delle chat. Premila per bloccare e sbloccare l\'app.\n\nNota: se ti dimentichi il codice, dovrai disinstallare e reinstallare l\'app. Tutte le chat segrete verranno perse. - Ora visualizzerai un\'icona col lucchetto nella pagina delle chat. Premi su di essa per bloccare la tua app Telegram con il codice. + Quando imposti un codice aggiuntivo, apparirà un\'icona col lucchetto nella pagina delle chat. Premila per bloccare e sbloccare l\'app. + +Nota: se ti dimentichi il codice, dovrai disinstallare e reinstallare l\'app. Tutte le chat segrete verranno perse. PIN Password Inserisci il tuo codice corrente @@ -1014,10 +1122,9 @@ Inserisci il nuovo codice Inserisci il tuo codice Reinserisci il nuovo codice - Codice non valido I codici non corrispondono Blocco automatico - Richiede il codice se lontano per del tempo. + Richiede il codice se assente per del tempo. tra %1$s Disabilitato Sblocca con impronta digitale @@ -1025,7 +1132,9 @@ Sensore touch Impronta digitale non riconosciuta. Riprova Consenti cattura schermo - Se attivata, puoi fare screenshot dell\'app, ma il sistema mostrerà le tue chat nella lista delle app recenti anche quando il codice di blocco è attivo.\n\nPotresti dover riavviare l\'app per rendere effettive le modifiche. + Se attivata, puoi fare screenshot dell\'app, ma il sistema mostrerà le tue chat nella lista delle app recenti anche quando il codice di blocco è attivo. + +Potresti dover riavviare l\'app per rendere effettive le modifiche. File condivisi Media condivisi @@ -1043,22 +1152,43 @@ Mappa Satellite Ibrido - m di distanza - km di distanza + m + km Invia la tua posizione corrente + Condividi posizione attuale per... + Arresta condivisione + Sei sicuro di voler terminare la condivisione della posizione attuale con %1$s? + Sei sicuro di voler terminare la condivisione della posizione attuale con %1$s? + Sei sicuro di voler terminare la condivisione della tua posizione attuale? + Aggiornata in tempo reale Invia la posizione selezionata Posizione + Luogo Precisione di %1$s O SELEZIONA UN LUOGO + TIRA SU PER VEDERE I LUOGHI VICINI + POSIZIONI ATTUALE + per 15 minuti + per 1 ora + per 8 ore + aggiornata + aggiornata adesso + Tu e %1$s + %1$s condivisa con %2$s + ARRESTA TUTTO + Stai condividendo la tua posizione attuale con %1$s + Scegli per quanto tempo %1$s vedrà la tua posizione precisa. + Scegli per quanto tempo le persone in questa chat vedranno la tua posizione precisa. + Sembra che il tuo GPS sia disattivato, per favore attivalo per utilizzare le funzioni che necessitano della posizione. Mostra tutti i media + Visualizza in chat Salva nella galleria %1$d di %2$d Galleria Tutte le foto Tutti i media Ancora nessuna foto - Ancora nessun video Scarica prima il file Nessuna foto recente Nessuna GIF recente @@ -1068,7 +1198,6 @@ Cerca su web Cerca GIF Ritaglia immagine - Modifica immagine Migliora Alte luci Contrasto @@ -1080,15 +1209,12 @@ Grana Nitidezza Sfumatura - Colore OMBRE ALTE LUCI - Curve TUTTO ROSSO VERDE BLU - Sfocatura No Lineare Radiale @@ -1097,16 +1223,11 @@ Annullare le modifiche? Cancellare la cronologia di ricerca? Cancella - Foto - Video Aggiungi una didascalia... Didascalia foto Didascalia video Didascalia GIF Didascalia - Disegno - Sticker - Testo Elimina Modifica Duplica @@ -1115,6 +1236,8 @@ Ripristina Originale Quadrato + Mostra i media come messaggi separati + Mostra i media come messaggio unico Verifica in due passaggi Imposta password aggiuntiva @@ -1124,47 +1247,59 @@ Inserisci una password Inserisci la tua nuova password Reinserisci la tua password - E-mail di recupero - La tua e-mail - Inserisci un\'e-mail valida. È l\'unico modo per recuperare una password dimenticata. + Email di recupero + La tua email + Inserisci un\'email valida. È l\'unico modo per recuperare una password dimenticata. Salta Attenzione - No, seriamente.\n\nSe dimentichi la tua password, perderai l\'accesso al tuo account Telegram. Non ci sarà modo di ripristinarlo. + No, seriamente. + +Se dimentichi la tua password, perderai l\'accesso al tuo account Telegram. Non ci sarà modo di ripristinarlo. Ci siamo quasi! - Controlla la tua e-mail (anche lo spam) per completare la configurazione della verifica in due passaggi. + Controlla la tua email (anche lo spam) per completare la configurazione della verifica in due passaggi. Fatto! La password per la verifica in due passaggi è ora attiva. Cambia password Disattiva password - Imposta e-mail di recupero - Cambia e-mail di recupero + Imposta email di recupero + Cambia email di recupero Sei sicuro di voler disabilitare la tua password? Suggerimento password Crea un suggerimento per la tua password Le password non corrispondono Annulla configurazione della verifica in due passaggi - Segui questi step per completare la configurazione della verifica in due passaggi:\n\n1. Controlla la tua e-mail (anche lo spam)\n%1$s\n\n2. Clicca sul link di conferma. + Segui questi step per completare la configurazione della verifica in due passaggi: + +1. Controlla la tua email (anche lo spam) +%1$s + +2. Clicca sul link di conferma. Il suggerimento deve essere diverso dalla password - E-mail non valida + Email non valida Spiacenti Siccome non hai fornito un\'email di recupero quando hai impostato la tua password, non ti resta che ricordarti la password o ripristinare il tuo account. - Abbiamo inviato un codice di ripristino all\'e-mail che ci hai fornito:\n\n%1$s - Controlla la tua e-mail e inserisci il codice a 6 cifre che ti abbiamo inviato. - Hai problemi ad accedere alla tua e-mail %1$s? - Se non puoi ripristinare l\'accesso alla tua e-mail, non ti resta che ricordarti la password o ripristinare il tuo account. + Abbiamo inviato un codice di ripristino all\'email che ci hai fornito: + +%1$s + Controlla la tua email e inserisci il codice a 6 cifre che ti abbiamo inviato. + Hai problemi ad accedere alla tua email %1$s? + Se non puoi ripristinare l\'accesso alla tua email, non ti resta che ricordarti la password o ripristinare il tuo account. RIPRISTINA IL MIO ACCOUNT Perderai tutte le chat e i messaggi, insieme ai media e ai file condivisi, se procederai a ripristinare il tuo account. Attenzione - Questa azione non può essere annullata.\n\nSe ripristini il tuo account, tutti i tuoi messaggi e le tue chat saranno eliminati. + Questa azione non può essere annullata. + +Se ripristini il tuo account, tutti i tuoi messaggi e le tue chat saranno eliminati. Ripristina Password - Hai attivato la verifica in due passaggi, così il tuo account è protetto con una password aggiuntiva. + Hai attivato la verifica in due passaggi, quindi il tuo account è protetto con una password aggiuntiva. Password dimenticata? Recupero password Codice Password disattivata - Hai attivato la verifica in due passaggi.\nAvrai bisogno della password che hai impostato per accedere al tuo account Telegram. - La tua e-mail di recupero %1$s non è ancora attiva e attende la conferma. + Hai attivato la verifica in due passaggi. +Avrai bisogno della password che hai impostato per accedere al tuo account Telegram. + La tua email di recupero %1$s non è ancora attiva e attende la conferma. Dati e archivio Utilizzo disco e rete @@ -1205,10 +1340,8 @@ Nessuno (+%1$d) Sicurezza Elimina il mio account - Se lontano per + Se assente per Se non ti connetti almeno una volta in questo periodo, il tuo account verrà eliminato insieme a tutti i gruppi, i messaggi e i contatti. - Eliminare il tuo account? - Cambia chi può vedere il tuo ultimo accesso. Chi può vedere il tuo ultimo accesso? Aggiungi eccezioni Importante: non potrai vedere l\'ultimo accesso delle persone con cui non condividi l\'ultimo accesso. Verrà mostrato un orario approssimativo (di recente, entro una settimana, entro un mese). @@ -1231,14 +1364,13 @@ Consenti sempre... Non consentire mai... Questi utenti potranno o non potranno aggiungerti a gruppi e canali indipendentemente dalle impostazioni precedenti. - Cambia chi può aggiungerti a gruppi e canali. + Scegli chi può aggiungerti a gruppi e canali. Spiacenti, non puoi aggiungere questo utente al gruppo a causa delle sue impostazioni di privacy. Spiacenti, non puoi aggiungere questo utente al canale a causa delle sue impostazioni di privacy. Spiacenti, non puoi creare un gruppo con questi utenti a causa delle loro impostazioni di privacy. Peer-to-Peer Disattivando il peer-to-peer, tutte le chiamate verranno inviate tramite i server di Telegram per evitare di rivelare l\'indirizzo IP, ma la qualità audio diminuirà leggermente. - Modifica video Invio video... Invio GIF... @@ -1255,8 +1387,6 @@ Arresta bot Riavvia bot - Avanti - Indietro Fatto Apri Salva @@ -1277,8 +1407,6 @@ Imposta OK RITAGLIA - - No Ti sei unito al gruppo tramite link d\'invito un1 si è unito al gruppo tramite link d\'invito @@ -1312,6 +1440,7 @@ Il video è scaduto GIF Posizione + Posizione attuale Contatto File Sticker @@ -1323,7 +1452,7 @@ un1 ha fatto uno screenshot! Numero di telefono non valido - Numero di telefono vietato + Numero di telefono bloccato Codice scaduto, effettua di nuovo l\'accesso Troppi tentativi, riprova più tardi Troppi tentativi, per favore riprova di nuovo tra %1$s @@ -1340,12 +1469,14 @@ Aggiungere %1$s alla chat %2$s? Numero di ultimi messaggi da inoltrare: Aggiungere %1$s al gruppo? - Questo utente è già membro del gruppo - Vuoi inoltrare i messaggi a %1$s? Inviare i messaggi a %1$s? Condividere il gioco con %1$s? Inviare contatto a %1$s? - Sei sicuro di voler uscire?\n\nNota che puoi usare Telegram su tutti i tuoi dispositivi contemporaneamente.\n\nRicorda, uscendo eliminerai tutte le Chat Segrete. + Sei sicuro di voler uscire? + +Nota che puoi usare Telegram su tutti i tuoi dispositivi contemporaneamente. + +Ricorda, uscendo eliminerai tutte le Chat Segrete. Terminare tutte le altre sessioni? Sei sicuro di voler uscire ed eliminare il gruppo? Sei sicuro di voler eliminare questa chat? @@ -1356,7 +1487,7 @@ Questo bot vorrebbe sapere la tua posizione ogni volta che invii una richiesta. Questo può essere usato per fornire risultati specifici per la posizione. Condividere il tuo numero di telefono? Il bot saprà il tuo numero di telefono. Questo può essere utile per l\'integrazione con altri servizi. - Sicuro di voler condividere il tuo numero di telefono %1$s con ]]>%2$s]]>? + Sicuro di voler condividere il tuo numero di telefono %1$s con **%2$s**? Sei sicuro di voler condividere il tuo numero di telefono? Vuoi bloccare questo contatto? Vuoi sbloccare questo contatto? @@ -1364,19 +1495,16 @@ Iniziare una chat segreta? Sei sicuro di voler eliminare questa registrazione? Sei sicuro di voler eliminare la cronologia? - Eliminare tutti i testi e i media nella cache per questo canale? - Eliminare tutti i testi e i media nella cache per questo gruppo? + Eliminare tutti i testi e i media di questo canale dalla cache? + Eliminare tutti i testi e i media di questo gruppo dalla cache? Sei sicuro di voler eliminare %1$s? Inviare messaggi a %1$s? Condividere il gioco con %1$s? Inviare contatto a %1$s? - Inoltra messaggi a %1$s? - Spiacenti, questa funzione non è disponibile nel tuo paese. Non esiste alcun account Telegram con questo username. Questo bot non può unirsi ai gruppi. Vuoi attivare le anteprime estese per i link nelle Chat Segrete? Nota che le anteprime dei link sono generate sui server di Telegram. Per favore nota che i bot inline sono forniti da sviluppatori di terze parti. Per far funzionare il bot, i simboli che digiti dopo l\'username del bot sono inviati al rispettivo sviluppatore. - Vuoi attivare \"Alza per registrare\" per i messaggi vocali? Spiacenti, non puoi modificare questo messaggio. Per favore consenti a Telegram di ricevere SMS così potremo inserire in automatico il codice per te. Per favore consenti a Telegram di ricevere chiamate così potremo inserire in automatico il codice per te. @@ -1403,18 +1531,24 @@ Telegram Veloce - Gratuito + Gratis Sicuro Potente Basato sul cloud - L\'app di messaggi più veloce]]> al mondo.\nÈ gratuita]]> e sicura]]>. - Telegram]]> consegna i messaggi più\nvelocemente di qualsiasi altra app. - Telegram]]> sarà sempre gratuito.\nNessuna pubblicità. Nessun abbonamento. - Telegram]]> protegge i tuoi messaggi\ndagli attacchi degli hacker. - Telegram]]> non ha limiti di dimensione\nper le tue chat e i file multimediali. - Telegram]]> ti consente di accedere\nai tuoi messaggi da più dispositivi. + L\'app di messaggi **più veloce** al mondo. +È **gratuita** e **sicura**. + **Telegram** consegna i messaggi più +velocemente di qualsiasi altra app. + **Telegram** sarà sempre gratuito. +Nessuna pubblicità. Nessun abbonamento. + **Telegram** protegge i tuoi messaggi +dagli attacchi degli hacker. + **Telegram** non ha limiti di dimensione +per le tue chat e i file multimediali. + **Telegram** ti consente di accedere +ai tuoi messaggi da più dispositivi. Inizia a messaggiare - + Impostazioni account Utilizza meno dati per le chiamate Chiamata in entrata @@ -1431,7 +1565,7 @@ Chiamata Telegram in corso Termina chiamata Altra chiamata in corso - Al momento hai una chiamata in corso con %1$s]]>. Vuoi terminare quella chiamata e iniziarne una nuova con %2$s]]>? + Al momento hai una chiamata in corso con **%1$s**. Vuoi terminare quella chiamata e iniziarne una nuova con **%2$s**? Chiamate vocali Suoneria Puoi personalizzare la suoneria usata quando questo contatto ti chiama su Telegram. @@ -1455,12 +1589,10 @@ Chiamata annullata Chiamata rifiutata %1$s (%2$s) - L\'immagine e il testo sono derivati dalla chiave di crittografia di questa chiamata con ]]>%1$s]]>.\n\nSe sono uguali sul dispositivo di ]]>%2$s]]>, la crittografia end-to-end è garantita. Non hai ancora effettuato alcuna chiamata. - L\'app di ]]>%1$s]]> sta usando un protocollo non compatibile. Deve aggiornare la sua app prima che tu possa chiamarlo. - L\'app di ]]>%1$s]]> non supporta le chiamate. Deve aggiornare la sua app prima che tu possa chiamarlo. + L\'app di **%1$s** sta usando un protocollo non compatibile. Deve aggiornare la sua app prima che tu possa chiamarlo. + L\'app di **%1$s** non supporta le chiamate. Deve aggiornare la sua app prima che tu possa chiamarlo. Per favore valuta la qualità della tua chiamata Telegram - Vuoi lasciare un feedback per aiutarci a migliorare le chiamate? Telegram deve accedere al microfono per poter effettuare chiamate. Aggiungi un commento opzionale Richiama @@ -1472,7 +1604,7 @@ Altoparlante Bluetooth RITORNA ALLA CHIAMATA - Spiacenti, ]]>%1$s]]> non accetta le chiamate. + Spiacenti, **%1$s** non accetta le chiamate. Se le emoji sono uguali sullo schermo di %1$s, la chiamata è sicura al 100%%. Valuta la chiamata Cosa è andato storto? @@ -1480,12 +1612,36 @@ Questo non rivelerà i contenuti della tua conversazione, ma ci aiuterà a risolvere il problema più velocemente. Grazie per aver contribuito a rendere le chiamate di Telegram migliori. + %1$d destinatari + %1$d destinatario + %1$d destinatari + %1$d destinatari + %1$d destinatari + %1$d destinatari %1$d in linea %1$d in linea %1$d in linea %1$d in linea %1$d in linea %1$d in linea + %1$d contatti su Telegram + %1$d contatto su Telegram + %1$d contatti su Telegram + %1$d contatti su Telegram + %1$d contatti su Telegram + %1$d contatti su Telegram + Ehi, sto usando Telegram per messaggiare – così come %1$d dei nostri altri contatti. Unisciti a noi! Scaricalo qui: %2$s + Ehi, sto usando Telegram per messaggiare – così come %1$d dei nostri altri contatti. Unisciti a noi! Scaricalo qui: %2$s + Ehi, sto usando Telegram per messaggiare – così come %1$d dei nostri altri contatti. Unisciti a noi! Scaricalo qui: %2$s + Ehi, sto usando Telegram per messaggiare – così come %1$d dei nostri altri contatti. Unisciti a noi! Scaricalo qui: %2$s + Ehi, sto usando Telegram per messaggiare – così come %1$d dei nostri altri contatti. Unisciti a noi! Scaricalo qui: %2$s + Ehi, sto usando Telegram per messaggiare – così come %1$d dei nostri altri contatti. Unisciti a noi! Scaricalo qui: %2$s + %1$d chat + %1$d chat + %1$d chat + %1$d chat + %1$d chat + %1$d chat %1$d membri %1$d membro %1$d membri @@ -1498,25 +1654,31 @@ e altri %1$d stanno scrivendo e altri %1$d stanno scrivendo e altri %1$d stanno scrivendo - nessun nuovo messaggio + %1$s e altri %2$d stanno scrivendo + %1$s e %2$d altro stanno scrivendo + %1$s e altri %2$d stanno scrivendo + %1$s e altri %2$d stanno scrivendo + %1$s e altri %2$d stanno scrivendo + %1$s e altri %2$d stanno scrivendo + %1$d nuovi messaggi %1$d nuovo messaggio %1$d nuovi messaggi %1$d nuovi messaggi %1$d nuovi messaggi %1$d nuovi messaggi - nessun messaggio + %1$d messaggi %1$d messaggio %1$d messaggi %1$d messaggi %1$d messaggi %1$d messaggi - nessun oggetto + %1$d oggetti %1$d oggetto %1$d oggetti %1$d oggetti %1$d oggetti %1$d oggetti - da nessuna chat + da %1$d chat da %1$d chat da %1$d chat da %1$d chat @@ -1562,7 +1724,7 @@ %1$d anno %1$d anni %1$d anni - %1$d ann + %1$d anni %1$d anni %1$d utenti %1$d utente @@ -1588,61 +1750,55 @@ %1$d sticker %1$d sticker %1$d sticker - %1$d foto - %1$d foto - %1$d foto - %1$d foto - %1$d foto - %1$d foto + %1$d iscritti + %1$d iscritto + %1$d iscritti + %1$d iscritti + %1$d iscritti + %1$d iscritti %1$d punti %1$d punto %1$d punti %1$d punti %1$d punti %1$d punti - ultimo accesso %1$d minuti fa - ultimo accesso %1$d minuto fa - ultimo accesso %1$d minuti fa - ultimo accesso %1$d minuti fa - ultimo accesso %1$d minuti fa - ultimo accesso %1$d minuti fa - ultimo accesso %1$d ore fa - ultimo accesso %1$d ora fa - ultimo accesso %1$d ore fa - ultimo accesso %1$d ore fa - ultimo accesso %1$d ore fa - ultimo accesso %1$d ore fa - %1$d]]> secondi - %1$d]]> secondo - %1$d]]> secondi - %1$d]]> secondi - %1$d]]> secondi - %1$d]]> secondi - %1$d]]> minuti - %1$d]]> minuto - %1$d]]> minuti - %1$d]]> minuti - %1$d]]> minuti - %1$d]]> minuti - %1$d]]> ore - %1$d]]> ora - %1$d]]> ore - %1$d]]> ore - %1$d]]> ore - %1$d]]> ore - %1$d]]> giorni - %1$d]]> giorno - %1$d]]> giorni - %1$d]]> giorni - %1$d]]> giorni - %1$d]]> giorni + aggiornata %1$d minuti fa + aggiornata %1$d minuto fa + aggiornata %1$d minuti fa + aggiornata %1$d minuti fa + aggiornata %1$d minuti fa + aggiornata %1$d minuti fa + **%1$d** secondi + **%1$d** secondo + **%1$d** secondi + **%1$d** secondi + **%1$d** secondi + **%1$d** secondi + **%1$d** minuti + **%1$d** minuto + **%1$d** minuti + **%1$d** minuti + **%1$d** minuti + **%1$d** minuti + **%1$d** ore + **%1$d** ora + **%1$d** ore + **%1$d** ore + **%1$d** ore + **%1$d** ore + **%1$d** giorni + **%1$d** giorno + **%1$d** giorni + **%1$d** giorni + **%1$d** giorni + **%1$d** giorni - %1$d messaggi inoltrati - Messaggio inoltrato - %1$d messaggi inoltrati - %1$d messaggi inoltrati - %1$d messaggi inoltrati - %1$d messaggi inoltrati + %1$d messaggi inoltrati + Messaggio inoltrato + %1$d messaggi inoltrati + %1$d messaggi inoltrati + %1$d messaggi inoltrati + %1$d messaggi inoltrati %1$d file inoltrati File inoltrato %1$d file inoltrati @@ -1679,7 +1835,7 @@ %1$d videomessaggi inoltrati %1$d videomessaggi inoltrati %1$d videomessaggi inoltrati - %1$d posizioni inoltrate + %1$d posizione inoltrate Posizione inoltrata %1$d posizione inoltrate %1$d posizione inoltrate diff --git a/TMessagesProj/src/main/res/values-ko/strings.xml b/TMessagesProj/src/main/res/values-ko/strings.xml index b3518700e..de83f6489 100644 --- a/TMessagesProj/src/main/res/values-ko/strings.xml +++ b/TMessagesProj/src/main/res/values-ko/strings.xml @@ -1,7 +1,4 @@ - - - 텔레그램 텔레그램 베타 @@ -13,43 +10,49 @@ 전화번호 입력 국가번호와 전화번호를 입력하세요. (대한민국 국가번호: 82) - 국가를 선택하세요 + 국가 선택 올바른 국가번호를 입력하세요 휴대폰 인증 - 인증코드 문자를 전송하였습니다.]]>%1$s]]>. - 회원님 기기에 설치된 ]]>Telegram]]>앱으로 코드를 전송했습니다. - ]]>%1$s]]> 번호로 인증 전화를 걸었습니다. \n\n텔레그램이 자동으로 인증을 처리하기에 전화를 안 받으셔도 됩니다. - ]]>%1$s]]> 번호로 인증코드 안내 전화를 걸고 있습니다. + 인증 코드가 담긴 SMS를 **%1$s**(으)로 보냈습니다. + 회원님 기기에 설치된 **Telegram**앱으로 코드를 전송했습니다. + **%1$s** 번호로 인증 전화를 걸었습니다. + +텔레그램이 자동으로 인증을 처리하기에 전화를 안 받으셔도 됩니다. + **%1$s** 번호로 인증코드 안내 전화를 걸고 있습니다. 텔레그램이 %1$d:%2$02d 후에는 전화를 겁니다. - %1$d:%2$02d 에 SMS를 보낼 예정입니다. - 텔레그램이 전화 거는 중... + %1$d:%2$02d 뒤 SMS를 보낼 것입니다. + 회원님에게 전화 거는 중... 코드 전화번호가 틀렸나요? 코드를 받지 못하셨나요? 계정 초기화 취소 - ]]>%1$s]]> 번호에 접근이 가능한 누군가가 해당 계정 및 2단계 비밀번호 초기화를 요청하였습니다.\n\n 만약 본인이 아니실 경우 방금 보내드린 코드를 본인 번호에 SMS로 전송해주세요. + **%1$s** 번호에 접근이 가능한 누군가가 해당 계정 및 2단계 비밀번호 초기화를 요청하였습니다. + + 만약 본인이 아니실 경우 방금 보내드린 코드를 본인 번호에 SMS로 전송해주세요. 계정 초기화 - 보안목적으로, 계정 ]]>%1$s]]>이 비밀번호 설정이 되어져 있어, 약 1주일 후에 삭제를 할 예정입니다.\n\n 삭제는 언제든지 쥐소가 가능합니다. - 다음 이후 계정 초기화 가능 : - 활성화된 사용자가 계정취소를 취소하였습니다. 7일후에 다시 시도해주세요. + 보안목적으로, 계정 **%1$s**이 비밀번호 설정이 되어져 있어, 약 1주일 후에 삭제를 할 예정입니다. + + 삭제는 언제든지 쥐소가 가능합니다. + 아래 시간이 지나야 계정을 초기화하실 수 있습니다: + 이 계정으로 활동 중인 사용자가 계정 초기화를 취소했습니다. 7일 뒤 다시 시도하세요. 재설정 - 링크가 유효기간이 만료되었거나 폐기됨 - %1$s 계정에 대한 삭제가 취소 되었습니다. 이창을 닫아도 됩니다. + 링크가 폐지됐거나 만료했습니다. + 회원님의 %1$s 계정을 없애는 작업이 취소됐습니다. 이제 이 창을 닫으셔도 좋습니다. 이름 입력 이름을 입력하세요 - 이름(필수) - 성(선택) + 이름 (필수) + 성 (선택) 가입 취소 %3$s에 대한 %1$s 금액을 %2$s 에게 성공적으로 전송하였습니다. %1$s 금액을 %2$s 에게 성공적으로 전송하였습니다. 체크아웃 배송방법 - 죄송합니다. 이 주소로 배송이 가능하지 않습니다. - 배송정보 + 죄송합니다. 회원님의 주소로는 배송이 어렵습니다. + 배송 정보 배송주소 주소 1 (Street) 주소 2 (Street) @@ -61,15 +64,14 @@ 이름 전화번호 이메일 - 배송정보 저장 - 향후 사용을 위하여 배송정보를 저장 할 수 있습니다. - 결제정보 + 배송 정보 저장 + 향후 사용을 대비하여 배송 정보를 저장하실 수 있습니다. + 결제 정보 결제카드 카드번호 보안코드 (CVV) MM/YY 결제주소 - 카드소유자 결제정보 저장 향후 사용을 위하여 결제정보를 저장 할 수 있습니다. @@ -87,22 +89,39 @@ 연락주소 배송방법 영수증 - 다른 카드를 선택해주세요 + 다른 카드 선택 %1$s 카드정보가 파일로 있습니다. 이 카드로 결제하시려면 2단계 비밀번호를 입력해주세요 - 죄송합니다, 결제가 봇에 의하여 거절되었습니다. - 죄송합니다, 결제가 거절되었습니다. + 죄송합니다. 봇에 의해 결제가 취소됐습니다. + 죄송합니다. 결제가 거절됐습니다. 결제 서버로 연결을 할 수 없습니다. 인터넷 연결을 확인해주시고 다시 시도해주세요. 경고 - 신용카드 정보는 텔레그램이나 %1$s이 권한을 가지지 않습니다. 신용카드 정보는 %2$s 결제 시스템에서만 처리가 됩니다.\n\n결제는 %1$s 개발자에게 직접 됩니다. 텔레그램은 그 어떠한 보장도 하지 않음으로 진행시 위험을 감수 하셔야 합니다. 문제발생시, %1$s 개발자나 사용하시는 은행에게 문의를 부탁드립니다. + 신용카드 정보는 텔레그램이나 %1$s이 권한을 가지지 않습니다. 신용카드 정보는 %2$s 결제 시스템에서만 처리가 됩니다. + +결제는 %1$s 개발자에게 직접 됩니다. 텔레그램은 그 어떠한 보장도 하지 않음으로 진행시 위험을 감수 하셔야 합니다. 문제발생시, %1$s 개발자나 사용하시는 은행에게 문의를 부탁드립니다. + 비밀번호 & 이메일 + 비밀번호 + 비밀번호 입력 + 비밀번호 재입력 + 결제 정보를 지키기 위하여 비밀번호를 설정해주세요. 로그인시 입력하게 됩니다. + 복구 이메일 + 회원님 이메일 + 정확한 이메일을 입력해주세요. 분실한 비밀번호를 찾는 유일한 방법입니다. + 전화가 %1$s 님에게 청구서 정보로서 전달됩니다. + 이메일이 %1$s 님에게 청구서 정보로서 전달됩니다. + 전화와 이메일이 %1$s 님에게 청구서 정보로서 전달됩니다. 새로운 대화 설정 - 주소록 - 새 그룹 + 연락처 + 그룹 만들기 어제 결과 없음 - 채팅방이 없습니다... - 대화를 시작하려면 우측 상단의\n초대하기 버튼을 누르거나\n메뉴 버튼을 눌러 보세요. + 대화방이 없습니다... + 최근 방문 + 숨기기 + 대화를 시작하려면 우측 상단의 +초대하기 버튼을 누르거나 +메뉴 버튼을 눌러 보세요. 연결 대기 중... 연결 중... 프록시 연결 중... @@ -110,25 +129,27 @@ 비활성화 활성화 이 프록시를 비활성화 하겠습니까? - 나중에 설정에서 프록시 서버를 변경 할 수 있습니다. (데이터 및 저장소) - %1$s 프록시 서버를 비활성화 하겠습니까? 나중에 설정에서 변경 할 수 있습니다. (데이터 및 저장소) + 프록시 서버는 설정(데이터 및 저장소)에서 바꾸실 수 있습니다. + %1$s 프록시 서버를 정말 비활성화하겠습니까? 프록시 서버는 설정(데이터 및 저장소)에서 바꾸실 수 있습니다. 업데이트 중... - 비밀대화 시작 - %s님을 기다리는 중... - 비밀대화가 끝났습니다 - 암호키 교환중... - %s님이 비밀대화에 참여했습니다. - 비밀대화에 참여했습니다. - 대화내용 지우기 - 캐시에서 삭제 - 채팅방 나가기 - 이 대화 삭제 + 비밀 대화 시작 + %s 님을 기다리고 있습니다... + 비밀 대화가 끝났습니다 + 암호 키 교환 중... + %s 님이 비밀 대화에 들어왔습니다. + 비밀 대화에 들어오셨습니다. + 대화 내용 비우기 + 캐시 지우기 + 대화방 나가기 + 대화방 지우기 탈퇴한 계정 - 채팅방 선택 - 꾹 눌러서 보기 + 대화방 선택 + 다음에게 전달... 비밀 사진 비밀 동영상 - %1$s님의 텔레그램 버전이 낮아 비밀 사진을 호환성 모드로 표시합니다.\n\n%2$s님이 텔레그램을 업데이트하고 나면, 자동삭제 시간이 1분 이하인 사진은 \"탭하고 누르고 있어야 보임\" 상태가 되며, 상대방이 화면을 캡처할 때 마다 알림을 받습니다. + %1$s님의 텔레그램 버전이 낮아 비밀 사진을 호환성 모드로 표시합니다. + +%2$s님이 텔레그램을 업데이트하고 나면, 자동삭제 시간이 1분 이하인 사진은 \"탭하고 누르고 있어야 보임\" 상태가 되며, 상대방이 화면을 캡처할 때 마다 알림을 받습니다. 메시지 검색 알림 음소거 @@ -137,183 +158,166 @@ %1$s 후 비활성화 해시태그 - 최신 + 최근 대화 - %1$s를 추천에서 삭제하겠습니까? + %1$s 님을 추천 목록에서 지울까요? 링크 미리복 임시저장 - 내역이 초기화 됨 + 대화 내용 비움 %1$s 이내 %1$s by %2$s 미리보기에 대한 의견 남기기 - 스티커 전송 + 스티커 보내기 팩 보기 맨 위로 고정 맨 위 고정 해제 굵게 기울림 일반 - Telegram에 권한을 부여하여 주소록에 있는 분들과 연결하세요. - 아직 안됨 + 알고 지내는 모두와 계속해서 연락하려면, **텔레그램**이 회원님의 연락처에 접근할 수 있도록 승낙해주세요. + 나중에 계속 - 공개 - 비공개 - 관리자로 지명 - 차단한 사용자 없음 - 그룹에 추가 설명을 제공 할 수 있습니다. - 그룹 나가기 - 그룹 삭제 + 관리자로 승격 + 차단된 사용자 없음 + 그룹 없애기 그룹 나가기 - 그룹 삭제 - 그룹에 있는 모든 메시지가 삭제됩니다. - 그룹방 관리를 도울 수 있는 관리자를 추가 할 수 있습니다. 길게 탭을하면 관리자 삭제가 가능합니다. - 이 그룹방을 삭제하실 경우 모든 구성원과 메시지를 삭제 하게되며 복구가 안됩니다. 그래도 그룹방을 삭제하시겠습니까? - 그룹 생성됨 - 이 그룹방에 un1님이 초대하였습니다. - 정말 그룹방에서 나가겠습니까? - 죄송합니다, 이 유저를 그룹에 초대할 수 없습니다. - 죄송합니다, 그룹방의 인원이 최대치입니다. - 해당 유저가 스스로 그룹방에서 퇴장을 하여 다시 초대할 수 없습니다. - 죄송합니다, 그룹방에 너무 많은 관리자가 있습니다. - 죄송합니다, 그룹방에 너무 많은 봇이 있습니다. - un1 님이 \"%1$s\" 를 고정함 - un1 님이 메시지를 고정함 - un1 님이 사진을 고정함 - un1 님이 비디오를 고정함 - un1 님이 파일을 고정함 - un1 님이 스티커를 고정함 - un1 님이 음성메시지를 고정함 - un1 님이 영상메시지를 고정함 - un1 님이 연락처를 고정함 - un1 님이 %1$s 고정함 - un1 님이 위치를 고정함 - un1 님이 GIF를 고정함 - un1 님이 트랙을 고정함 - 이 그룹방은 슈퍼그룹방으로 업그레이드 되었습니다. - %1$s 그룹방은 슈퍼그룹방으로 업그레이드 되었습니다. - 그룹방에서 차단되어 퇴장당한 사용자는 관리자가 초대해야지만 그룹방에 입장이 가능합니다. 초대링크로는 초대가 되지 않습니다. - 그룹방에서 차단되어 퇴장당한 사용자는 관리자가 초대해야지만 그룹방에 입장이 가능합니다. 초대링크로는 초대가 되지 않습니다. - 새 채널 + 그룹 없애기 + 그룹에 있는 모든 메시지가 사라집니다. + 그룹을 관리하는 데 도움을 줄 만한 관리자를 추가하실 수 있습니다. 관리자를 제명하려면 길게 누르세요. + 잠깐만요! 이 그룹을 없애시면 안에 있는 모든 참가자와 메시지가 사라집니다. 무시하고 없앨까요? + 그룹 만들어짐 + un1 님이 이 그룹에 초대하셨습니다. + 그룹에서 정말 나가시겠습니까? + 죄송합니다. 이 사용자는 그룹에 추가하실 수 없습니다. + 죄송합니다, 그룹의 인원이 최대치입니다. + 죄송합니다. 이 사용자는 스스로 그룹에서 나갔기에 다시 초대하실 수 없습니다. + 죄송합니다, 그룹에 너무 많은 관리자가 있습니다. + 죄송합니다. 이 그룹에는 봇이 너무 많습니다. + un1 님이 \"%1$s\"을(를) 고정했습니다 + un1 님이 메시지를 고정했습니다 + un1 님이 사진을 고정했습니다 + un1 님이 동영상을 고정했습니다 + un1 님이 파일을 고정했습니다 + un1 님이 스티커를 고정했습니다 + un1 님이 음성 메시지를 고정했습니다 + un1 님이 동영상 메시지를 고정했습니다 + un1 님이 연락처를 고정했습니다 + un1 님이 %1$s을(를) 고정했습니다 + un1 님이 위치를 고정했습니다 + un1 님이 실시간 위치를 고정했습니다 + un1 님이 GIF를 고정했습니다 + un1 님이 트랙을 고정했습니다 + 이 그룹이 슈퍼그룹으로 업그레이드됐습니다. + %1$s 그룹이 슈퍼그룹으로 업그레이드됐습니다 + 블랙리스트에 적힌 사용자는 그룹에서 추방되며, 관리자가 초대해야만 다시 들어올 수 있습니다. 이 사용자에게는 초대 링크도 작동하지 않습니다. + 블랙리스트에 적힌 사용자는 그룹에서 추방되며, 관리자가 초대해야만 다시 들어올 수 있습니다. 이 사용자에게는 초대 링크도 작동하지 않습니다. + 채널 만들기 채널명 채널에 친구 추가 - 텔레그램 검색을 통하여 다른 유저들이 채널을 찾을 수 있습니다. - 텔레그램 검색을 통하여 다른 유저들이 그룹을 찾을 수 있습니다. + 사람들이 위 링크를 공유하며 텔레그램 검색으로 회원님의 채널을 찾을 수 있습니다. + 사람들이 위 링크를 공유하며 텔레그램 검색으로 회원님의 그룹을 찾을 수 있습니다. 링크 - 이 링크를 통하여 다른 유저들이 채널에 입장 할 수 있습니다. 이 링크는 언제든지 폐기 가능합니다. - 이 링크를 통하여 다른 유저들이 그룹방에 입장 할 수 있습니다. 이 링크는 언제든지 폐기 가능합니다. - 설명(선택) + 사람들이 위 링크를 따라 회원님의 채널에 들어올 수 있습니다. 링크는 언제든지 폐지하실 수 있습니다. + 사람들이 위 링크를 따라 회원님의 그룹에 들어올 수 있습니다. 링크는 언제든지 폐지하실 수 있습니다. + 설명 (선택) 설명 - 채널에 추가 설명을 제공 할 수 있습니다. + 채널에 설명을 제공하실 수 있습니다. 공개 채널 - 공개 그룹방 - 공개 채널은 검색이 가능하며, 누구나 입장 가능합니다 - 공개 그룹은 검색이 가능하며, 누구나 입장 가능합니다 + 공개 그룹 + 공개 채널은 검색으로 찾을 수 있으며 누구나 들어올 수 있습니다. + 공개 그룹은 검색으로 찾을 수 있으며 누구나 들어올 수 있습니다. 비공개 채널 - 비공개 그룹방 - 비공개 채널은 초대 링크로만 입장 가능합니다. - 비공개 그룹은 초대 링크로만 입장 가능합니다. + 비공개 그룹 + 비공개 채널에는 초대 링크로만 들어올 수 있습니다. + 비공개 그룹에는 초대되었거나 초대 링크가 있는 경우에만 들어올 수 있습니다. 링크 - 초대링크 - 구성원 추가 + 초대 링크 + 참가자 추가 채널 나가기 채널 나가기 설정 입장 - 모두에게 메시지 전달 + 알리기 조용한 공지 - 채널이 무엇인가요? - 채널은 많은 유저들에게 메시지를 한번에 전송 할 수 있는 새로운 기능입니다. - 채널 생성 - 이미 사용 중인 이름입니다. + 채널이란 무엇인가요? + 채널이란 회원님의 메시지를 많은 관중들에게 퍼뜨릴 수 있는 새로운 도구입니다. + 채널 만들기 + 이미 사용되고 있는 이름입니다. 올바르지 않은 이름입니다. 채널명은 최소 5 글자 이상 입력해야 합니다. - 이름은 최대 32자까지만 가능합니다. + 이름은 32자를 넘길 수 없습니다. 채널명은 숫자로 시작 할 수 없습니다. 그룹명은 최소 5 글자 이상 입력해야 합니다. 그룹명은 숫자로 시작 할 수 없습니다. 이름 확인 중.. - %1$s은 사용 가능합니다. - 구성원 + %1$s :사용하실 수 있습니다. + 참가자 + 구독자 블랙리스트 차단된 사용자 제한된 사용자 관리자 - 채널 삭제 - 채널 삭제 - 이 채널을 삭제하실 경우 모든 구성원과 메시지를 삭제를 하게되며 복구가 안됩니다. 그래도 채널을 삭제하시겠습니까? + 채널 없애기 + 채널 없애기 + 잠깐만요! 이 채널을 없애시면 안에 있는 모든 참가자와 메시지가 사라집니다. 무시하고 없앨까요? 채널에서 나가시겠습니까? - 채널에 있는 모든 메시지가 삭제됩니다. - 편집 - 그룹방에 대한 공개링크를 선택하신 경우, 누구나 검색을 통하여 입장 가능합니다.\n\n슈퍼그룹을 비공개로 유지를 하시고 싶으실 경우 링크 생성을 하지 말아주세요. - 채널에 대한 공개링크를 선택하신 경우, 누구나 검색을 통하여 입장 가능합니다.\n\n비공개 채널로 유지를 하시고 싶으실 경우 링크 생성을 하지 말아주세요 - 유저들이 공개 채널에 대하여 검색 및 공유가 가능하도록 링크를 선택하여 주세요.\n\n채널을 공개하시지 싫으실 경우, 비공개 채널을 추천드립니다. - 채널 생성됨 - 채널 사진 업데이트됨 - 채널 사진 삭제됨 - 채널명이 un2로 변경됨 - 죄송하지만, 너무 많은 공개아이디를 생성하였습니다. 기존 공개 채널 혹은 그룹방을 비공개로 전환하거나 삭제해주세요. + 채널에 있는 모든 메시지가 사라집니다. + 수정 + 사람들이 이 채널을 검색하여 찾고 공유할 수 있도록 링크를 설정하세요. + +이를 꺼리신다면 비공개 채널을 추천해드립니다. + 채널 만들어짐 + 채널 사진이 바뀌었습니다 + 채널 사진이 지워졌습니다 + 채널명이 un2(으)로 바뀜 + 죄송하지만, 너무 많은 공개 사용자명을 가지셨습니다. 다른 그룹이나 채널의 링크를 하나 폐지하거나 비공개로 바꾸세요. 생성자 - 관리자 음소거 음소거 취소 관리자 추가 사용자 차단 - 차단해제 - 차단을 해제하려면 대화상대를 길게 누르세요. + 차단 해제 + 차단을 해제하려면 사용자를 길게 누르세요. 링크를 통해 초대 - %1$s님을 관리자로 추가하시겠습니까? - 관리자 회수 + 관리자 해고 채널 관리자만 리스트를 볼 수 있습니다. - 아직 이 유저가 채널에 입장하지 않았습니다. 초대하시겠습니까? - 텔레그램이 설치된 분들은 링크를 타고 채널에 참여가 가능합니다. - 채널 관리를 도울 수 있는 관리자를 추가 할 수 있습니다. 길게 탭을하면 관리자 삭제가 가능합니다. + 텔레그램을 설치한 사람이 링크를 통해 회원님의 채널에 들어올 수 있습니다. + 채널을 관리하는 데 도움을 줄 만한 관리자를 추가하실 수 있습니다. 관리자를 제명하려면 길게 누르세요. \'%1$s\'채널에 참여하시겠습니까? - 죄송합니다, 이 채팅방에 더 이상 접근이 불가능 합니다. - 안타깝지만, 공개그룹 참여에 제한 되었습니다. - 죄송합니다, 이 채팅방에 더 이상 접근이 불가능 합니다. + 죄송합니다. 이 대화방에는 더 이상 접근하실 수 없습니다. + 안타깝지만, 공개 그룹에 참여하실 수 없습니다. + 죄송합니다. 이 대화방에는 더 이상 접근하실 수 없습니다. %1$s 님을 이 채널에 추가할까요 - 해당 사용자가 스스로 채널에서 퇴장을 하여 다시 초대할 수 없습니다. + 죄송합니다. 이 사용자는 스스로 채널에서 나갔기에 다시 초대하실 수 없습니다. 죄송합니다, 이 유저를 채널에 추가 할 수 없습니다. 죄송합니다, 채널에 너무 많은 관리자가 있습니다. - 죄송합니다, 채널에 너무 많은 봇이 있습니다. - 죄송합니다, 채널에는 첫 200명까지만 초대가 가능합니다. 채널 링크를 통하여 무제한 입장이 가능합니다. - 이 채널에 un1님이 초대하였습니다. - 채널에 참여하였습니다. + 죄송합니다. 이 채널에는 봇이 너무 많습니다. + 죄송합니다. 처음 200명까지만 채널에 추가하실 수 있습니다. 채널 링크로는 사람들을 무한정 들이실 수 있는 점 알아두세요. + un1 님이 회원님을 이 채널에 추가했습니다 + 채널에 들어오셨습니다 그룹에 참여하였습니다. - 채널에서 내보내기 - 채널에 글을 쓸 수 없습니다. + 채널에서 추방 + 죄송합니다. 이 채널에 메시지를 보내실 수 없습니다. %1$s님이 %2$s 채널에 초대했습니다 - %1$s님이 사진을 업데이트 하였습니다 - %1$s님이 %2$s 채널에 메시지를 보냈습니다 - %1$s님이 %2$s 채널에 사진을 보냈습니다 - %1$s님이 %2$s 채널에 비디오를 보냈습니다 - %1$s님이 %2$s 채널에 연락처를 공유했습니다 - %1$s님이 %2$s 채널에 위치를 보냈습니다 - %1$s님이 %2$s 채널에 파일을 보냈습니다 - %1$s님이 %2$s 채널에 GIF파일을 보냈습니다 - %1$s님이 %2$s 채널에 음성메시지를 보냈습니다 - %1$s님이 %2$s 채널에 영상메시지를 보냈습니다 - %1$s님이 %2$s 채널에 트랙을 보냈습니다 - %1$s님이 %2$s 채널에 스티커를 보냈습니다 - %1$s님이 %2$s 채널에 %3$s 스티커를 보냈습니다 + %1$s 채널 사진이 업데이트됐습니다 %1$s 님이 메시지를 보냈습니다 - %1$s 님이 사진을 보냈습니다 + %1$s 님이 사진을 올렸습니다 %1$s 님이 동영상을 보냈습니다 %1$s님이 연락처를 공유했습니다 %1$s님이 위치를 보냈습니다 %1$s 님이 파일을 보냈습니다 - %1$s 님이 GIF파일을 보냈습니다 + %1$s 님이 GIF를 올렸습니다 %1$s님이 음성메시지를 보냈습니다 - %1$s님이 영상메시지를 보냈습니다 + %1$s 님이 동영상 메시지를 올렸습니다 %1$s님이 트랙을 보냈습니다 %1$s님이 스티커를 보냈습니다 %1$s님이 %2$s스티커를 작성하였습니다 - 초대가 가능한 구성원 - 모든 구성원 - 관리자만 가능 - 메시지 알림 대상 - 메시지 미알림 대상 + 누가 새로운 참가자를 초대할 수 있나요? + 모든 참가자 + 관리자만 + 글을 올리시면 참가자들이 알림을 받습니다 + 글을 올리셔도 참가자들이 알림을 받지 않습니다 메시지 서명 메시지 작성하는 관리자 추가 관리자 수정 @@ -322,89 +326,109 @@ 그룹 정보 수정 메시지 작성 다른 사용자 메시지 수정 - 다른 사용자 메시지 삭제 - 메시지 삭제 - 새 관리자 추가 - 관리자 회수 + 다른 사람의 메시지 지우기 + 메시지 지우기 + 새로운 관리자 추가 + 관리자 해고 사용자 차단 사용자 추가 - 링크를 통하여 사용자 초대 + 링크를 통해 사용자 초대 메시지 고정 - %1$s 님이 추가 - 이 관리자의 권한을 수정할 수 없습니다. + %1$s 님이 추가함 + 이 관리자의 권한을 수정하실 수 없습니다. %1$s 님이 제한함 사용자 제한 메시지 읽기 이 사용자는 무엇을 할 수 있나요? - 메시지 전송 - 미디어 전송 - 스티커 & GIF 전송 + 메시지 보내기 + 미디어 보내기 + 스티커 및 GIF 보내기 링크 저장 다음까지 차단 영원히 - 삭제 - 그룹에서 차단 후 내보내기 + 차단한 뒤 그룹에서 추방 그룹 관리 채널 관리 그룹 관리 채널 관리 + 새로운 참가자에게 이전 대화 내용을 + 보이기 + 새로운 참가자는 들어오기 전부터 있던 메시지를 볼 수 있습니다. + 숨기기 + 새로운 참가자는 이전 메시지를 볼 수 없습니다. 최근 활동 모든 활동 선택한 활동 모든 관리자 - 아직 이벤트가 없습니다.\n\n그룹 사용자나 관리자가\n지난 48시간내에\n 아무런 활동이 없었습니다. - ]]>아직 이벤트가 없습니다.]]>\n\n이 채널 관리자는\n지난 48시간내에\n 아무런 활동이 없었습니다. - ]]>이벤트를 찾을 수 없습니다.]]>\n\n검색한 이벤트를 찾을 수 없습니다. - \'%1$s\' 에 대한 최근 이벤트를 찾을 수 없습니다. + **활동 없음** + +지난 48시간 사이에 +그룹 참가자와 관리자가 +활동한 기록이 없습니다. + **활동 없음** + +지난 48시간 사이에 +채널 관리자가 +활동한 기록이 없습니다. + **이벤트를 찾을 수 없습니다.** + +검색한 이벤트를 찾을 수 없습니다. + \'**%1$s**\' 에 대한 최근 활동을 찾지 못했습니다. 최근 활동이란 무엇인가요? - 그룹 사용자와 관리자의 지난 48시간동안 활동 리스트입니다. - 채널 관리자의 지난 48시간동안 활동 리스트입니다. - un1님이 그룹명을 \"%1$s\" 로 변경하였습니다. - un1님이 채널명을 \"%1$s\" 로 변경하였습니다. + 지난 48시간 사이에 채널 참가자와 관리자가 활동한 기록입니다. + 지난 48시간 사이에 채널 관리자가 활동한 기록입니다. + un1 님이 그룹 이름을 \"%1$s\"(으)로 바꿨습니다 + un1 님이 채널 이름을 \"%1$s\"(으)로 바꿨습니다 un1님이 그룹에서 나갔습니다. un1님이 채널에서 나갔습니다. - un1님이 un2님을 초대했습니다 + un1 님이 un2 님을 추가했습니다 un1 님이 그룹에 참여했습니다 %1$s 님이 차단됨 - %1$s 님이 차단해제됨 - un1님이 채널에 참여했습니다. + %1$s 님을 차단 해제함 + un1 님이 채널에 들어왔습니다 un1님이 그룹 사진 을 새로 설정했습니다. - un1님이 채널 사진 을 새로 설정했습니다. - un1님이 그룹 사진 을 삭제했습니다. - un1님이 채널 사진 을 삭제했습니다. - un1님이 이 메시지를 편집했습니다. + un1 님이 새 채널 사진을 설정했습니다 + un1 님이 그룹 사진을 지웠습니다 + un1 님이 채널 사진을 지웠습니다 + un1 님이 아래 메시지를 수정했습니다: un1님이 캡션을 수정했습니다: 원본 메시지 원본 캡션 - 없음 + 비어 있음 un1님이 이 메시지를 고정했습니다: un1 님이 메시지를 고정했습니다. - un1님이 이 메시지를 삭제했습니다: - un1님이 그룹링크를 변경했습니다: - un1님이 채널링크를 변경했습니다: - un1 님이 그룹링크를 삭제했습니다. - un1님이 채널 링크를 삭제했습니다. + un1 님이 아래 메시지를 지웠습니다: + un1 님이 그룹 스티커 묶음을 바꿨습니다 + un1 님이 그룹 스티커 묶음을 없앴습니다 + un1 님이 그룹 링크를 바꿨습니다: + un1 님이 채널 링크를 바꿨습니다: + un1 님이 그룹 링크를 없앴습니다. + un1 님이 채널 링크를 없앴습니다 기존 링크 - un1님이 그룹정보를 변경했습니다: - un1님이 채널정보를 변경했습니다: + un1 님이 그룹 설명을 바꿨습니다: + un1 님이 채널 설명을 바꿨습니다: 기존 정보 + un1 님이 새로운 참가자에게도 이전 대화 내용이 보이도록 설정했습니다 + un1 님이 새로운 참가자에게는 이전 대화 내용이 보이지 않도록 설정했습니다 un1 님이 그룹 초대를 허용함 un1 님이 그룹 초대를 불허함 un1님이 서명을 허용했습니다. un1 님이 서명을 불허함 - %1$s 님에 대한 제한을 변경했습니다.\n\n기한: %2$s - 스티커 & GIF 전송 - 미디어 전송 - 메시지 전송 + %1$s 님에 대한 제한 정도를 바꿨습니다 + +기한: %2$s + 스티커 및 GIF 보내기 + 미디어 보내기 + 메시지 보내기 링크 저장 메시지 읽기 - %1$s님의 권한을 수정했습니다. + %1$s 님의 권한 정도를 바꿨습니다 채널 정보 수정 그룹 정보 수정 메시지 작성 메시지 수정 - 메시지 삭제 + 메시지 지우기 관리자 추가 사용자 차단 사용자 추가 @@ -412,31 +436,31 @@ 모든 활동 새로운 제한 새로운 관리자 - 새로운 사용자 + 새로운 참가자 그룹 정보 채널 정보 - 그룹 설정 - 메시지 삭제 + 메시지 지우기 메시지 수정 메시지 고정 - 퇴장한 사용자 + 나간 참가자 새 단체 메시지 리스트 리스트 이름을 입력하세요 단체 메시지 리스트를 만들었습니다 받는 사람 추가 - 리스트에서 제외 + 방송 목록에서 지우기 음악 라이브러리에 파일을 추가하셔야지만 볼 수 있습니다. 음악 - 알수 없는 아티스트 + 알 수 없는 아티스트 알 수 없는 제목 + 섞기 + 역순 파일 선택 %2$s 중 %1$s 남음 알 수 없는 오류 접근 오류 - 파일이 없습니다 파일 크기는 %1$s보다 작아야 합니다 스토리지가 마운트되지 않음 USB 전송 활성 @@ -446,32 +470,43 @@ SD 카드 폴더 압축 없이 사진 보내기 + + 그룹 스티커 + 내 스티커에서 선택 + 그룹에서 그룹 참가자들이 대화를 할 때 모두가 사용할 수 있는 스티커 묶음을 고르실 수 있습니다. + 스티커 묶음 선택 + 스티커 묶음 + "@stickers 봇으로 회원님만의 스티커 묶음을 만드실 수 있습니다." + 해당 스티커 묶음을 찾지 못함 + 다시 시도하거나 아래 목록에서 고르세요 숨김 입력 중... 님이 입력 중... 님이 입력 중... + %1$s 님이 입력 중... + %1$s 님이 입력하는 중... %1$s님이 음성메시지를 녹음중입니다... - %1$s님이 영상메시지를 녹화중입니다... - %1$s님이 음성파일을 전송중입니다... - %1$s님이 사진 보내는 중... + %1$s 님이 동영상 메시지를 녹화하는 중... + %1$s 님이 녹음을 보내는 중... + %1$s 님이 사진을 보내는 중... 즉시 보기 공개 그룹 - 공개 채널 + 채널로 이동 밤시간에는 자동으로 어두운 테마가 작동됩니다 - %1$s님이 게임을 플레이중입니다. - %1$s님이 동영상 보내는 중... - %1$s님이 파일 보내는 중... - 영상메시지를 녹화중... - 영상메시지를 녹화중... - 음성파일 전송 중.. - 사진 전송 중.. - 게임 플레이중... - 동영상 전송 중.. - 파일 전송 중... - 텔레그램에 관해\n궁금한 사항이 있나요? - 사진 촬영 - 앨범 + %1$s 님이 게임을 플레이 중... + %1$s 님이 동영상을 보내는 중... + %1$s 님이 파일을 보내는 중... + 동영상 메시지 녹화 중... + 동영상 메시지 녹화 중... + 음성 녹음 보내는 중... + 사진 보내는 중... + 게임 플레이 중... + 동영상 보내는 중... + 파일 보내는 중... + 텔레그램에 관해 +궁금한 사항이 있나요? + 갤러리 위치 동영상 파일 @@ -479,66 +514,64 @@ 메시지가 없습니다... 전달된 메시지 보낸 사람 - 최근에 사용한 이모티콘 + 보낸 사람: + 최근 이모지 없음 메시지 메시지 내 연락처 공유 - 주소록에 추가 - %s님이 비밀대화에 초대했습니다. - %s님을 비밀대화에 초대했습니다. - 비밀대화는 + 연락처에 추가 + %s 님이 회원님을 비밀 대화에 초대했습니다. + %s 님을 비밀 대화에 초대하셨습니다. + 비밀 대화는 단대단 암호화를 사용합니다 서버에 어떤 흔적도 남기지 않습니다 - 일정 시간 후에 자동삭제가 가능합니다 + 자동 지우기 타이머가 있습니다 전달 기능이 허용되지 않습니다 - 그룹에서 퇴장당했습니다. + 그룹에서 추방되셨습니다 그룹을 떠났습니다 - 이 그룹 삭제 - 이 채팅방 삭제 + 이 그룹을 지웁니다 + 이 대화방을 지웁니다 밀어서 취소 다운로드 폴더에 저장 - GIF파일로 저장 - GIF파일을 삭제하겠습니까? + GIF로 저장 + GIF를 지울까요? 음악으로 저장 공유 언어 파일 적용 테마 파일 적용 지원하지 않는 형식입니다 - 자동삭제 타이머 설정 + 자동 지우기 타이머 맞추기 서비스 알림 링크 정보를 가져오는 중... 다음으로 열기.. - 다음으로 파일열기.. - URL 복사 - %1$s 전송 - 파일로 전송 + 다른 앱으로 열기... + 보내기 %1$s + 파일로 보내기 파일로 보내기 %1$s 링크를 여시겠습니까? - 이 bot을 통하여 %1$s이 텔레그램 이름과 아이디를(전화번호 아님) 웹페이지로 전달하시기를 허락하시나요? + %1$s이(가) 앞으로 여실 페이지로 회원님의 텔레그램 이름과 아이디(비밀번호 아님)를 전달할 수 있도록 허락할까요? 스팸 신고 스팸 신고 및 나가기 - 주소록에 추가 + 연락처에 추가 이 유저 메시지를 스팸신고 하시겠습니까? 이 그룹 메시지를 스팸신고 하시겠습니까? - 이 채널을 스팸신고 하시겠습니까? - 죄송합니다, 서로 연락처가 추가된 경우에만 메시지 전송이 가능합니다. - 죄송합니다, 서로 연락처가 추가된 경우에만 그룹에 구성원을 추가 할 수 있습니다. - 오늘 연락처가 없는 분들과 너무 많은 연락을 하셨습니다. 내일 다시 시도해주세요. 오늘은 메시지가 오는 분들하고만 대화가 가능합니다. - 오늘 연락처가 없는 분들과 너무 많은 연락을 하셨습니다. 내일 다시 시도해주세요. 그룹에 추가하시려면 다른 분이 초대해주셔야합니다. - https://telegram.org/faq#can-39t-send-messages-to-non-contacts + 이 채널을 스팸 신고하시겠습니까? + 죄송합니다. 서로의 연락처를 공유해야만 메시지를 보내실 수 있습니다. + 죄송합니다. 서로의 연락처를 공유해야만 그룹에 초대하실 수 있습니다. 더 보기 - 다음에게 보내기.. + 다음에게 보내기... 코멘트 쓰기.. 저장된 GIF 파일을 보려면 탭하세요. 고정 - 모두에게 알림 - 고정제거 + 모든 참가자에게 알리기 + 고정 해제 그룹에 이 메시지를 고정하시겠습니까? + 채널에 이 메시지를 고정하시겠습니까? 메시지를 고정 해제하시겠습니까? 사용자 차단 스팸 신고 - %1$s 전부 삭제 - 최근 사용한 이모티콘 삭제? + %1$s 님의 메시지 모두 지우기 + 최근에 사용한 이모지를 비울까요? 신고하기 스팸 폭력적 @@ -547,190 +580,225 @@ 설명 메시지 고정 수정됨 - 봇을 보려면 스크롤 다운하세요 + 봇을 보려면 끌어 올리세요 %1$s - 죄송합니다, 수정가능시간이 만료되었습니다. - 바로 가기 추가 + 죄송합니다. 수정 시간이 만료했습니다. + 바로가기 추가 사용자 검색 - 홈에 바로가기 추가 - 나와의 대화 + 여기로 전달하여 대화를 저장하세요 + 저장된 메시지 + 저장하려면 여기로 전달하세요 - 여기로 메시지를 전달하여 저장 - 미디어와 파일을 전송하여 저장 - 모든 기기에서 이 대화를 공유 - 빠른검색을 위하여 검색사용 + 여기로 메시지를 전달해 저장하세요 + 미디어와 파일을 보내어 저장하세요 + 모든 기기에서 이 대화방을 공유합니다 + 검색을 활용해 필요한 것을 빠르게 찾으세요 클라우드 저장소 - 날자로 이동 - %1$s 에게 삭제 - 모두에게 삭제 - 클립보드로 텍스트가 복하되었습니다. - 꾹 눌러 음성을 녹음하세요. 탭하여 영상으로 전환하세요. - 꾹 눌러 영상을 녹화하세요. 탭하여 음성으로 전환하세요. + 날짜로 이동 + %1$s 님에게서 지우기 + 모든 참가자에게서 지우기 + 글이 클립보드로 복사되었습니다 + 꾹 눌러 음성을 녹음하세요. 짧게 눌러 동영상으로 바꾸세요. + 꾹 눌러 동영상을 녹화하세요. 짧게 눌러 음성으로 바꾸세요. 음성 메시지 지우기 녹음을 중단하고 음성 메시지를 지우시겠습니까? - 영상 메시지 지우기 - 녹화를 중단하고 영상 메시지를 지우시겠습니까? - 지우기 + 동영상 메시지 버리기 + 정말 녹화를 그만두고 동영상 메시지를 버리시겠습니까? + 버리기 그룹 관리자가 회원님의 미디어 전송을 %1$s까지 제한했습니다. 그룹 관리자가 회원님의 인라인 명령어 전송을 %1$s까지 제한했습니다. 그룹 관리자가 회원님의 스티커 전송을 %1$s까지 제한했습니다. - 그룹 관리자가 회원님의 메시지 작성을 %1$s까지 제한했습니다. + 이 그룹의 관리자가 회원님이 %1$s까지 글을 올리실 수 없도록 제한했습니다 그룹 관리자가 회원님의 미디어 전송을 제한했습니다. 그룹 관리자가 회원님의 인라인 명령어 전송을 제한했습니다. 그룹 관리자가 회원님의 스티커 전송을 제한했습니다. - 그룹 관리자가 회원님의 메시지 작성을 제한했습니다. + 이 그룹의 관리자가 회원님이 글을 올리실 수 없도록 제한했습니다. + 관리자 - %1$s님이 자동삭제를 %2$s 후로 설정했습니다 - 자동삭제를 %1$s 후로 설정했습니다 - %1$s님이 자동삭제를 해제했습니다 - 자동삭제를 해제했습니다 - 새 메시지가 있습니다 + %1$s 님이 자동 지우기를 %2$s 뒤로 맞췄습니다 + 자동 지우기를 %1$s 뒤로 맞추셨습니다 + %1$s 님이 자동 지우기 타이머를 껐습니다 + 자동 지우기 타이머를 끄셨습니다 + 새로운 메시지 %1$s: %2$s %1$s님이 메시지를 보냈습니다 %1$s님이 사진을 보냈습니다 %1$s님이 동영상을 보냈습니다 - %1$s 님이 자동삭제되는 사진을 전송했습니다 - %1$s 님이 자동삭제되는 동영상을 전송했습니다 + %1$s 님이 자동으로 지워지는 사진을 보냈습니다 + %1$s 님이 자동으로 지워지는 동영상을 보냈습니다 %1$s님이 연락처를 공유했습니다 %1$s님이 위치를 보냈습니다 %1$s님이 %2$s 게임으로 초대했습니다 %1$s님이 파일을 보냈습니다 - %1$s 님께서 GIF파일을 보내셨습니다 + %1$s 님이 회원님에게 GIF를 보냈습니다 %1$s님이 음성메시지를 보냈습니다 - %1$s 님이 영상 메시지를 보냈습니다 + %1$s 님이 동영상 메시지를 보냈습니다 %1$s 님이 트랙을 보냈습니다 %1$s님이 스티커를 보냈습니다 %1$s님이 %2$s스티커를 보냈습니다 %1$s @ %2$s: %3$s %1$s님이 %2$s 그룹에 메시지를 보냈습니다 - %1$s님이 %2$s 그룹에 사진을 보냈습니다 + %1$s 님이 %2$s 그룹에 사진을 보냈습니다 %1$s님이 %2$s 그룹에 동영상을 보냈습니다 %1$s님이 %2$s 그룹에 연락처를 공유했습니다 %1$s님이 %2$s 그룹에 위치를 보냈습니다 %1$s 님이 %2$s 그룹에 %3$s 게임으로 초대했습니다 %1$s님이 %2$s 그룹에 파일을 보냈습니다 - %1$s 님께서 %2$s 그룹에 GIF파일을 보냈습니다 + %1$s 님이 %2$s 그룹에 GIF를 보냈습니다 %1$s님이 %2$s 그룹에 음성메시지를 보냈습니다 - %1$s 님이 %2$s 그룹에 영상 메시지를 보냈습니다 + %1$s 님이 %2$s 그룹에 동영상 메시지를 보냈습니다 %1$s님이 %2$s 그룹에 트랙을 보냈습니다 %1$s님이 %2$s 그룹에 스티커를 보냈습니다 %1$s님이 %2$s 그룹에 %3$s 스티커를 보냈습니다 %1$s님이 %2$s 그룹에 초대했습니다 - %1$s 님이 %2$s 그룹명을 변경하였습니다. - %1$s 님이 %2$s 그룹 사진을 변경하였습니다. + %1$s 님이 그룹 이름을 %2$s(으)로 바꿨습니다 + %1$s 님이 %2$s 그룹 사진을 바꿨습니다 %1$s님이 %3$s님을 %2$s 그룹에 초대했습니다 - %1$s 님이 %2$s 그룹으로 되돌아왔습니다 - %1$s 님이 %2$s 그룹에 참여했습니다 - %1$s님이 %3$s님을 %2$s 그룹에서 퇴장당했습니다. - %1$s님이 %2$s 그룹에서 퇴장당했습니다. + %1$s 님이 %2$s 그룹에 돌아왔습니다 + %1$s 님이 %2$s 그룹에 들어왔습니다 + %1$s 님이 %3$s 님을 %2$s 그룹에서 추방했습니다 + %1$s 님이 회원님을 %2$s 그룹에서 추방했습니다. %1$s님이 %2$s 그룹을 떠났습니다 - %1$s님이 텔레그램에 가입했습니다! - %1$s님,\n%2$s에 새 기기에서 회원님의 계정 로그인이 감지되었습니다. \n\n기기: %3$s\n위치: %4$s\n\n본인의 접속이 아니라면 \'설정\' 창에서 \'모든 세션 종료\' 기능을 실행하세요.\n\n만약 강제접속 의심이 되신다면 2단계 인증을 설정 - 개인정보 및 보안에서 설정할 수 있습니다.\n\n감사합니다.\n텔레그램 팀 - %1$s님이 프로필 사진을 변경했습니다 - 초대링크를 타고 %1$s님께서 %2$s 그룹에 참여하셨습니다. + %1$s 님이 텔레그램에 가입했습니다! + %1$s 님, +%2$s에 새로운 기기에서 회원님 계정으로 로그인한 것이 감지됐습니다. + +기기: %3$s +위치: %4$s + +본인이 접속하지 않았다면, \'개인 정보 및 보안\' 설정에서 해당 세션을 종료하세요. + +만약 누군가 강제로 접속했다는 의심이 가신다면, \'개인 정보 및 보안\' 설정에서 2단계 인증을 활성화하실 수 있습니다. + +텔레그램 팀 드림 + %1$s 님이 프로필 사진을 업데이트했습니다 + 초대 링크를 통해 %1$s 님이 %2$s 그룹에 들어왔습니다. 답장 %1$s 그룹에 답장하기 %1$s님에게 답장하기 %2$s %1$s - %1$s 님이 \"%2$s\" 메시지를 %3$s 그룹방에 고정함 - %1$s 님이 메시지를 %2$s 그룹방에 고정함 - %1$s 님이 사진을 %2$s 그룹방에 고정함 - %1$s 님이 게임을 %2$s 그룹방에 고정함 - %1$s 님이 비디오를 %2$s 그룹방에 고정함 - %1$s 님이 파일을 %2$s 그룹방에 고정함 - %1$s 님이 스티커를 %2$s 그룹방에 고정함 - %1$s 님이 %3$s 스티커를 %2$s 그룹방에 고정함 - %1$s 님이 음성메시지를 %2$s 그룹방에 고정함 - %1$s 님이 영상메시지를 %2$s 그룹방에 고정함 - %1$s 님이 연락처를 %2$s 그룹방에 고정함 - %1$s 님이 지도를 %2$s 그룹방에 고정함 - %1$s 님이 GIF를 %2$s 그룹방에 고정함 - %1$s 님이 트랙을 %2$s 그룹방에 고정함 - %1$s 님이 \"%2$s\" 를 고정함 - %1$s 님이 메시지를 고정함 - %1$s 님이 사진을 고정함 - %1$s 님이 비디오를 고정함 - %1$s 님이 파일을 고정함 - %1$s 님이 스티커를 고정함 - %1$s 님이 %2$s 스티커를 고정함 - %1$s 님이 음성메시지를 고정함 - %1$s 님이 영상메시지를 고정함 - %1$s 님이 연락처를 고정함 - %1$s 님이 지도를 고정함 - %1$s 님이 GIF를 고정함 - %1$s 님이 트랙을 고정함 - %1$s님이 게임을 고정함 + %1$s 님이 %3$s 그룹에 \"%2$s\"을(를) 고정했습니다 + %1$s 님이 %2$s 그룹에 메시지를 고정했습니다 + %1$s 님이 %2$s 그룹에 사진을 고정했습니다 + %1$s 님이 %2$s 그룹에 게임을 고정함 + %1$s 님이 %2$s 그룹에 동영상을 고정했습니다 + %1$s 님이 %2$s 그룹에 파일을 고정했습니다 + %1$s 님이 %2$s 그룹에 스티커를 고정했습니다 + %1$s 님이 %2$s 그룹에 %3$s 스티커를 고정했습니다 + %1$s 님이 %2$s 그룹에 음성 메시지를 고정했습니다 + %1$s 님이 %2$s 그룹에 동영상 메시지를 고정했습니다 + %1$s 님이 %2$s 그룹에 연락처를 고정했습니다 + %1$s 님이 %2$s 그룹에 지도를 고정했습니다 + %1$s 님이 %2$s 그룹에 실시간 위치를 고정했습니다 + %1$s 님이 %2$s 그룹에 GIF를 고정했습니다 + %1$s 님이 %2$s 그룹에 트랙을 고정함 + %1$s 님이 \"%2$s\"을(를) 고정했습니다 + %1$s 님이 메시지를 고정했습니다 + %1$s 님이 사진을 고정했습니다 + %1$s 님이 게임을 고정했습니다 + %1$s 님이 동영상을 고정했습니다 + %1$s 님이 파일을 고정했습니다 + %1$s 님이 스티커를 고정했습니다 + %1$s 님이 %2$s 스티커를 고정했습니다 + %1$s 님이 음성 메시지를 고정했습니다 + %1$s 님이 동영상 메시지를 고정했습니다 + %1$s 님이 연락처를 고정했습니다 + %1$s 님이 지도를 고정했습니다 + %1$s 님이 실시간 위치를 고정했습니다 + %1$s 님이 GIF를 고정했습니다 + %1$s 님이 트랙을 고정했습니다 - 대화상대 선택 - 대화상대가 없습니다 - Telegram을 사용해 보세요!: https://telegram.org/dl + 연락처 선택 + 연락처 없음 + 안녕하세요, 전 텔레그램이라는 메신저를 사용하고 있어요. 함께 사용해요! 여기서 다운로드하실 수 있습니다: %1$s 오늘 어제 온라인 마지막 접속: 마지막 접속: - 방금 전에 확인 친구 초대 + 친구 검색 전체 검색 - 최근에 접속 + 최근에 접속함 일주일 이내 마지막으로 접속 한 달 이내 마지막으로 접속 마지막으로 접속한 지 오래됨 - 새 메시지 + 새로운 메시지 + 텔레그램으로 초대할 연락 상대를 선택하세요 + 텔레그램에 초대 + 텔레그램 공유... + 연락처를 업데이트할까요? + 텔레그램이 아직 동기화되지 않은 여러 연락처를 찾아냈습니다. 연락처를 동기화하시겠습니까? 본인의 기기, SIM 카드와 Google 계정을 사용하는 중이라면 \'확인\'을 누르십시오. - 초대하기.. - 이 그룹 생성을 완료하시고 슈퍼그룹으로 전환하시면 더 많은 구성원을 추가 할 수 있습니다. + 추가... + 이 그룹을 마저 만들고 슈퍼그룹으로 전환한 뒤 사용자를 더 추가하실 수 있습니다. 그룹 이름 입력 그룹 이름 - %2$d 중 %1$d 선택됨 + %2$d명 중 %1$d명을 고름 최대 %1$s - \'%1$s\' 채널에 참여하시겠습니까? - 죄송합니다, 그룹방의 인원이 최대치입니다. - 죄송합니다, 채팅방이 더이상 존재하지 않습니다. - 클립보드로 링크가 복사되었습니다. - 링크를 통하여 그룹방에 초대하기 - 초대링크 - 초대링크를 폐지하시겠습니까? 진행하실 경우 해당 링크로 방에 참여할 수 없게 됩니다. - 기존 초대링크는 비활성화 되었습니다. 새로운 링크가 생성되었습니다. + 죄송합니다. 그룹이 가득 차 있습니다. + 죄송합니다. 대화방이 없어진 모양입니다. + 링크가 클립보드로 복사되었습니다 + 링크를 통해 그룹에 초대하기 + 초대 링크 + 현재 사용중인 초대 링크를 삭제할까요? 이후에는 이 초대 링크를 이용하여 대화방에 접근할 수 없습니다. + 이전 초대 링크는 현재 비활성화됐습니다. 새 초대 링크가 만들어졌습니다. 폐지하기 링크 폐지 - 초링크 ]]>%1$s]]>를 폐기하시겠습니까?\n\n \"]]>%2$s]]>\" 그룹은 비공개로 전환됩니다. - 링크 ]]>%1$s]]>를 폐기하시겠습니까?\n\n \"]]>%2$s]]>\" 채널은 비공개로 전환됩니다. + **%1$s** 링크를 정말 폐지하시겠습니까? + +\"**%2$s**\" 그룹은 비공개가 됩니다. + **%1$s** 링크를 폐지하시겠습니까? + + \"**%2$s**\" 채널이 비공개로 바뀔 것입니다. 링크 복사 링크 공유 - 텔레그램이 설치된 분들은 링크를 타고 그룹방에 참여가 가능합니다. + 텔레그램을 설치한 누구나 이 링크를 통해 그룹에 참여할 수 있습니다. 관리자 대화 - 모든 구성원이 관리자입니다. - 그룹에 있는 모든 구성원은 상대 초대, 이름 및 사진을 수정할 수 있습니다. - 그룹방에 있는 관리자만 구성원을 초대, 퇴장, 이름 편집 및 사진 수정할 수 있습니다. + 참가자 모두가 관리자입니다 + 모든 참가자는 새로운 참가자를 추가할 수 있으며 그룹의 이름과 사진을 바꿀 수 있습니다. + 관리자만이 참가자를 추가하고 추방하며 그룹 이름과 사진을 수정할 수 있습니다. - 공유한 미디어 + 참가자 + 공유된 미디어 설정 - 대화상대 추가 + 구독자 추가 + 참가자 추가 관리자 설정 그룹으로 부터 차단됨 그룹에서 나가기 알림 사용자 제한 - 그룹에서 내보내기 + 그룹에서 추방 슈퍼그룹으로 업그레이드 슈퍼그룹으로 변환 슈퍼그룹으로 변환 경고 - 이 작업은 되돌릴 수 없습니다. 슈퍼그룹에서 일반그룹방으로 다운 그레이드는 불가능 합니다. - ]]>구성원이 최대치입니다.]]>\n\n추가 기능 및 더 많은 구성원을 추가하려면 슈퍼그룹방으로 업그레이드하세요:\n\n• 슈퍼그룹방은 %1$s명까지 초대가능합니다.\n• 새로운 구성원은 모든 대화내역을 볼 수 있습니다.\n• 메시지 삭제시 모두에게 삭제가 됩니다.\n• 방 생성자는 그룹방 공개링크 생성이 가능합니다. - ]]>슈퍼그룹:]]>\n\n• 새로운 구성원은 모든 대화내역을 볼 수 있습니다.\n• 메시지 삭제시 모두에게 삭제가 됩니다.\n• 방 생성자는 그룹방 공개링크 생성이 가능합니다. - ]]>주의:]]> 이 작업은 되돌릴 수 없습니다. + 이 작업은 되돌릴 수 없습니다. 슈퍼그룹에서 일반 그룹으로 내리기는 불가능합니다. + **참가자 수 제한에 다다랐습니다.** + +더 많은 참가자를 추가하고 추가 기능을 사용하려면, 슈퍼그룹으로 업그레이드하세요. + +• 슈퍼그룹은 %1$s명 까지도 수용할 수 있습니다 +• 새로운 참가자가 대화 내용 전체를 볼 수 있습니다 +• 한 사람이 메시지를 지우면 모두에게서도 사라집니다 +• 생성자는 그룹의 공개 링크를 둘 수 있습니다 + **슈퍼그룹에서는** + +• 새로운 참가자가 대화 내용 전체를 볼 수 있습니다 +• 한 사람이 메시지를 지우면 모두에게서도 사라집니다 +• 생성자는 그룹의 공개 링크를 둘 수 있습니다 + **주의:** 이 작업은 되돌릴 수 없습니다. 공유 추가 - 대화상대 추가 + 연락처 추가 아직 %1$s님이 텔레그램에 가입하시지 않았습니다. 초대하시겠습니까? 초대 차단 - 편집 - 삭제 + 수정 + 지우기 휴대전화 직장 @@ -738,35 +806,41 @@ 자기소개 없음 - 자기소개에 대한 몇줄을 추가 할 수 있습니다. 회원님 정보를 보시는 분들에게 표시가 됩니다 - 비밀대화 시작 + 자신에 대한 짤막한 소개를 적으실 수 있습니다. 회원님의 프로필을 여는 누구에게나 보입니다. + 비밀 대화 시작 공통 그룹 공통 그룹 - 아직 공통 그룹이 없습니다 + 공통 그룹 없음 오류가 발생했습니다. 암호화 키 - 자동삭제 타이머 - 타이머를 설정시, 사진을 읽은 후 자동삭제가 됩니다. - 타이머 설정시, 동영상 시청 후 자동삭제가 됩니다. + 자동 지우기 타이머 + 타이머를 설정하시면 사진이 열람된 뒤 자동으로 지워집니다. + 타이머를 설정하시면 동영상이 시청된 뒤 자동으로 지워집니다. 해제 - 이 이미지와 텍스트는 ]]>%1$s]]>님과의 비밀대화시 생성된 암호화키에서 파생되었습니다.\n\n이 이미지와 텍스트가 ]]>%2$s\'s]]> 님의 휴대전화와 동일하다면 단말기간(end-to-end)의 암호화가 정상적으로 진행되고 있음을 보장합니다.\n\n더 자세한 사항은 telegram.org를 참고해 주세요. + 이 이미지와 문자는 **%1$s**님과의 비밀 대화에 사용된 암호화 키에서 파생됐습니다. + +이 이미지와 문자가 **%2$s\'s** 님 기기의 것과 같다면, 기기 간(end-to-end) 암호화에 문제가 없음을 보장합니다. + +자세한 내용은 telegram.org를 참조해주십시오. https://telegram.org/faq#secret-chats - 탭을 하여 이모티콘화 알 수 없음 정보 전화번호 - 아이디 - 아이디 - 이미 사용 중인 아이디입니다. - 올바른 아이디를 입력하세요. - 아이디는 최소 다섯 글자 이상 입력해야 합니다. - 아이디는 최대 32자까지만 가능합니다. - 아이디는 숫자로 시작할 수 없습니다. - ]]>텔레그램]]> 아이디를 설정할 수 있습니다. 아이디를 설정하면 회원님의 전화번호를 몰라도 아이디로 회원님을 찾아 대화를 나눌 수 있습니다.\n\n아이디는 ]]>a-z]]>,]]>0-9]]>와 밑줄을 사용할 수 있습니다. 아이디의 최소단위는 ]]>5]]>글자입니다. - 이 링크는 다음 상대와 텔레그램 대화를 열게 됩니다:\n%1$s - 아이디 확인 중... - %1$s: 사용 가능합니다. + 사용자명 + 회원님의 사용자명 + 이미 사용되고 있는 사용자명입니다. + 올바른 사용자명을 입력하세요. + 사용자명은 최소 다섯 자이어야 합니다. + 사용자명은 32자를 넘길 수 없습니다. + 사용자명은 숫자로 시작할 수 없습니다. + **텔레그램** 사용자명을 정하실 수 있습니다. 회원님의 전화번호를 모르는 사람도 이 사용자명만 있으면 회원님을 찾아 연락할 수 있습니다. + +**a부터 z 사이 글자**, **0부터 9 사이 숫자**와 밑줄을 사용하실 수 있습니다. 최소 길이는 **다섯 자**입니다. + 회원님과의 텔레그램 대화방 링크입니다: +%1$s + 사용자명 확인 중... + %1$s: 사용하실 수 있습니다. 없음 오류가 발생했습니다. @@ -774,29 +848,36 @@ 스티커 추가 마스크 추가 스티커 추가 + 즐겨찾기에 추가 + 스티커가 즐겨찾기에 추가됐습니다 + 스티커가 즐겨찾기에서 지워졌습니다 + 최근 사용 + 즐겨찾기 + 그룹스티커 + 즐겨찾기에서 지우기 마스크에 추가 - 스티커를 찾을 수 없음 - 스티커 제거됨 - 마스크가 삭제됨 - 새로운 스티커가 추가되었습니다. + 스티커를 찾지 못함 + 스티커 지워짐 + 마스크 지워짐 + 새로운 스티커가 추가됐습니다 새로운 마스크 추가 보관 공유 링크 복사 - 삭제 - 스티커가 아직 없음 - 마스크가 아직 없음 + 지우기 + 스티커 없음 + 마스크 없음 마스크 - 전송하시려는 사진에 마스크를 추가 할 수 있습니다. 사진 에디터를 전송하시기 전에 실행해주시면 됩니다. + 보내려는 사진에 마스크를 추가하실 수 있습니다. 사진을 보내기 전에 사진 편집기를 여시면 됩니다. 인기 스티커 - 현재 텔레그램에서 인기있는 스티커입니다. @stickers 봇을 통하여 커스텀 스티커 추가가 가능합니다. + 현재 텔레그램에서 인기를 끌고 있는 스티커들입니다. @sticker 봇으로 맞춤 스티커를 추가하실 수 있습니다. 보관된 스티커 보관된 마스크 보관된 스티커 없음 - 보관된 마스크가 없음 + 보관된 마스크 없음 최대 200개의 스티커를 설치 할 수 있습니다. 사용하지 않는 스티커는 새로추가시 보관됩니다. 최대 200개의 마스크를 설치 할 수 있습니다. 사용하지 않는 마스크는 새로추가시 보관됩니다. - 스티커 전송 + 스티커 보내기 보관된 스티커 오래된 스티커중 일부가 보관되어집니다. 스티커 설정에서 다시 재설정이 가능합니다. 보관된 마스크 @@ -805,42 +886,42 @@ 테마 다크 파랑 - 이 테마를 삭제하시겠습니까? + 이 테마를 지우시겠습니까? 올바르지 않은 테마 파일 테마명을 입력하세요 - 에디터 닫기 + 편집기 닫기 테마 저장 새로운 테마 적용 테마 미리보기 - 색상 선택 - 새로운 테마 생성 - 팔레트 아이콘을 클릭하여 화면 구성 리스트를 볼 수 있으며 - 편집을 할 수 있습니다. - 앱내에 색상을 변경하여 원하는 테마를 생성할 수 있습니다. 여기에서 언제든지 기존 텔레그램 테마로 복원이 가능합니다. + 색깔 선택 + 새로운 테마 만들기 + 각 화면의 구성 요소 목록을 보려면 갤판 아이콘을 누르세요. 수정하실 수도 있습니다. + 앱 안의 색깔들을 바꿔서 회원님만의 테마를 만드실 수 있습니다. 여기서 언제든지 텔레그램을 기본으로 복원하실 수 있습니다. - 모든 알림 설정이 초기화되었습니다 - 채팅 글자 크기 + 모든 알림 설정이 초기화됐습니다 + 메시지 글자 크기 질문하기 화면 전환 효과 사용 차단 해제 - 차단을 해제하려면 대화상대를 길게 누르세요. + 차단을 해제하려면 사용자를 길게 누르세요. 차단한 친구가 없습니다 메시지 알림 알림 사용 메시지 미리보기 그룹 알림 - 알림음 - 앱 내 알림 - 실행 중일 때 알림음 - 실행 중일 때 진동 + 소리 + 앱 안 알림 + 앱 안 소리 + 앱 안 진동 진동 - 실행 중일 때 미리보기 + 앱 안 미리보기 초기화 모든 알림 설정 초기화 연락처와 그룹에 대한 모든 알림 설정을 처음 상태로 되돌립니다. 알림 및 소리 - 개별 알림 - 알림 팝업 + 맞춤 알림 + 팝업 알림 이 연락처로 부터 수신되는 메시지는 텔레그램 미사용 중일시 화면에 뜨게 됩니다. LED 색깔 @@ -857,7 +938,7 @@ 보라색 주황색 LED는 일부 기기에서 새로운 메시지가 수신될때 반짝입니는 빛입니다 - 우선수위가 높은 알림은 휴대폰 기기가 무음이더라도 알림이 작동합니다. + 우선순위가 높은 알림은 휴대폰이 방해 금지 상태더라도 알림이 작동합니다. 일반 켜기 끄기 @@ -865,39 +946,40 @@ 끄기 차단 목록 로그아웃 - 알림음 없음 - 기본값 + 소리 없음 + 기본 지원 - 음소거일 중에만 - 채팅방 배경화면 + 음소거일 때만 + 대화방 배경 메시지 - 엔터키로 메시지 전송 + 엔터 키로 보내기 다른 모든 세션 종료 - 이벤트 - 친구의 텔레그램 가입 알림 + 확인할 일 + 친구의 텔레그램 가입 고정된 메시지 언어 사용자 지정 - 텔레그램에 관한 질문은 자원봉사자들이 답변해 드립니다. 신속한 답변을 위해 노력하지만 답변이 다소 늦을 수 있습니다. 일반적인 문제와 해결방법에 대해서는 \'자주 묻는 질문\']]>을 확인해 보세요 : FAQ에는 문제해결]]> 관련된 정보와 팁이 있습니다. - 질문하기 + 텔레그램 지원 사항은 자원봉사자분들이 처리합니다. 가능한 한 빨리 답변해드리고자 노력하지만 답변이 조금 늦어질 수도 있습니다. + +일반적인 문제와 해결방법에 대해서는 \'자주 묻는 질문\']]>을 확인해보세요: 여기에는 잦은 질문에 대한 답과 문제해결]]>을 위한 중요한 팁이 있습니다. + 봉사자에게 질문 + Telegram FAQ 자주 묻는 질문 https://telegram.org/faq/ko - 개인정보 정책 + 개인 정보 정책 https://telegram.org/privacy - 언어를 삭제할까요? + 언어를 지울까요? 언어 파일이 올바르지 않습니다. - 켜기 - 끄기 항상 활성화 서비스 - 시스템이나 사용자에 의하여 닫힌 앱을 재시작합니다. 해당 작업은 알림을 보여지게 합니다. + 앱이 시스템이나 사용자에 의해 닫힐 때 재시작합니다. 이 작업은 알림이 보이도록 합니다. 백그라운드 연결 - 알림을 받기 위하여 텔레그램 백그라운드 연결을 최소화로 유지합니다. 안정적인 알림을 유지합니다. + 알림을 받기 위해 텔레그램의 백그라운드 연결을 최소로 유지합니다. 안정적인 알림이 가능합니다. 정렬 연락처 가져오기 이름 LED 색상 - 알림 팝업 + 팝업 알림 사용 안 함 화면이 켜져 있을 때만 화면이 꺼져 있을 때만 @@ -905,61 +987,68 @@ 앱 아이콘에 알림 개수 표시 짧게 길게 - 사진/동영상 자동 다운로드 - 모바일 데이터를 사용 중일 때 - Wi-Fi에 연결 중일 때 - 로밍 중일 때 - 다운로드 안함 - GIF 자동재생 - 기기를 들어 말하기 - 앨범에 자동 저장 - 이름 편집 - 개인화 - 개별 - 개별 알림 활성화 + 미디어 자동 다운로드 + 미디어 자동 다운로드 + 자동 다운로드 설정 초기화 + 자동 다운로드 설정을 정말 초기화하시겠습니까? + 모바일 데이터를 사용할 때 + Wi-Fi에 연결할 때 + 로밍할 때 + 다운로드하지 않음 + GIF 자동 재생 + 기기 들어 말하기 + 갤러리에 자동 저장 + 이름 수정 + 맞춤 설정 + 맞춤 + 맞춤 알림 활성화 우선순위 설정과 동일 기본 - 낮음 높음 최우선 사용 안 함 알림 반복 - 텔레그램 가입 번호를 여기서 변경할 수 있습니다. 계정 및 클라우드에 저장된 메시지나 사진/동영상, 대화상대 등이 새 번호로 이동됩니다.\n\n주의:]]> 회원님의 옛 전화번호를 알고 있으며 회원님이 차단한 대화상대가 아니라면, 텔레그램 대화상대 모두의 연락처에 회원님의 새 전화번호]]>가 추가됩니다. + 텔레그램 번호를 여기서 바꾸실 수 있습니다. 계정과 클라우드에 저장된 메시지나 미디어, 연락처 등이 새 번호로 옮겨집니다. + +**중요:** 회원님의 옛 전화번호가 있으며 차단되지 않은 텔레그램 연락 상대 모두에게 회원님의 **새 전화번호**가 추가됩니다. 회원님의 옛 전화번호를 알고 있으며 회원님이 차단한 대화상대가 아니라면, 텔레그램 대화상대 모두의 연락처에 회원님의 새 전화번호가 추가됩니다. - 번호 변경 + 번호 바꾸기 새 번호 인증코드 메시지를 새 번호로 전송하겠습니다. 그 번호는 이미 텔레그램 계정에 연결되어 있습니다. 새 번호로 이동하기 전에 %1$s 계정에서 탈퇴해 주세요. 기타 - 비활성화됨 비활성화됨 활성화 비활성화 - 비활성화됨 - - 채팅중 소리 설정 - 기본값 - 기본값 + 비활성화 + 꺼짐 + 대화 중 소리 + 기본 + 기본 스마트 알림 %1$d / %2$s - 비활성화됨 + 비활성화 소리 알림 간격 %2$s 이내 %1$s - 링크 프리뷰 - 비밀대화 - 크롬 커스텀 탭 - 앱내에서 외부 링크 열기 + 링크 미리보기 + 비밀 대화 + 앱 안 브라우저 + 앱 안에서 외부 링크 열기 직접 공유 - 공유 메뉴에서 최근 대화 보기 - 이모티콘 - 한개의 큰 이모티콘 그리기 - 기본 시스템 이모티콘 사용 + 공유 메뉴에 최근 대화 보이기 + 이모지 + 큰 이모지 하나 그리기 + 시스템 기본 이모지 사용 텔레그램 안드로이드 %1$s 디버그 메뉴 연락처 가져오기 연락처 재동기화 - 나중에 설정에서 언어 변경이 가능합니다. + 대화 초기화 + 앱 안 카메라 활성화 + 앱 안 카메라 비활성화 + 가져온 연락처 초기화 + 언어는 설정에서 바꾸실 수 있습니다. 언어 선택 기타 프록시 설정 @@ -968,34 +1057,48 @@ 서버 비밀번호 포트 - 아이디 + 사용자명 SOCKS5 프록시 설정 전화에 프록시 사용 프록시 서버는 통화 품질을 저하시킬 수 있습니다. + 주의 + 기기에 남은 공간이 거의 없습니다. 텔레그램이 최근 미디어의 캐시만 저장하도록 설정하면 여유 공간을 확보하실 수 있습니다. + 뒤 미디어 지우기 + 지우지 말기 + 연락처 + 비밀 대화 + 그룹 대화 + 채널 + 용량 제한 + 최대 %1$s 로컬 데이터베이스 - 캐시된 텍스트 메시지를 삭제하시겠습니까? - 압축된 데이터베이스 및 캐시에 저장된 메시지를 로컬 데이터베이스에서 삭제하면 내부 저장공간이 증가합니다. 데이터베이스는 Telegram이 작동하는데 어느정도 필요함으로 완전히 삭제가 되지는 않습니다.\n\n이 작업은 완료되기까지 몇분정도 소요가 될 수 있습니다. - 캐시 삭제 - 삭제 - 계산중... + 캐시가 된 문자 메시지를 비울까요? + 압축된 데이터베이스 및 캐시에 저장된 메시지를 로컬 데이터베이스에서 삭제하면 내부 저장공간이 증가합니다. 데이터베이스는 Telegram이 작동하는데 어느정도 필요함으로 완전히 삭제가 되지는 않습니다. + +이 작업은 완료되기까지 몇분정도 소요가 될 수 있습니다. + 캐시 비우기 + 비우기 + 계산 중... 문서 사진 - 음성/영상 메시지 + 음성/동영상 메시지 동영상 음악 - GIF파일 + GIF 다른 파일 - 없음 + 비어 있음 미디어 저장 - 이 기간 동안 클라우드 채팅방에서 접근하지 않은]]> 사진이나 동영상, 기타 파일 등은 공간 절약을 위해 이 기기에서 삭제됩니다.\n\n모든 파일은 Telegram 클라우드에 여전히 남으며 필요하시면 언제든 다시 다운로드하실 수 있습니다. + 이 기간 동안 회원님이 **접근하지 않은** 클라우드 대화방의 사진, 동영상과 그 밖의 파일들이 디스크 공간 절약을 위해 이 기기에서 지워집니다. + +모든 미디어는 텔레그램 클라우드에 남으며 필요할 때 언제든 다시 다운로드하실 수 있습니다. 영원히 음성 메시지 - 영상 메시지 + 동영상 메시지 활성화된 세션 현재 세션 - 활성화된 세션이 없음 + 활성화된 세션 없음 동일한 휴대번호로 다른 휴대기기,태블릿과 데스크탑에서 텔레그램 로그인이 가능합니다. 모든 데이터는 즉시 동기화 됩니다. 활성화된 세션 다른 기기 세션 관리 @@ -1003,21 +1106,21 @@ 해당 세션을 종료하시겠습니까? 비공식앱 - 잠금코드 잠금 - 잠금번호 변경 - 잠금코드를 설정하셨을 경우, 대화방에 잠금 아이콘이 표시됩니다. 해당 아이콘을 클릭하여 텔레그램 잠금 설정을 할 수 있습니다.\n\n주의: 잠금코드를 잊어버렸을 경우 앱 삭제후 재설치를 해주셔야합니다. 이 경우 비밀대화 내용은 삭제가 됩니다. - 대화방에 잠금아이콘이 표시가 됩니다. 해당 아이콘을 클릭하여 새로운 잠금코드를 설정하여 텔레그램을 잠글수 있습니다. - 핀코드 - 잠금번호 - 현재 잠금코드 입력 - 잠금코드 입력 - 새로운 잠금코드 입력 - 잠금코드를 입력해주세요 - 잠금코드 재입력 - 올바르지 않은 잠금코드 - 잠금코드가 정확하지 않습니다 + 암호 설정 + 암호 바꾸기 + 암호를 설정하시면 대화방에 잠금 아이콘이 표시됩니다. 잠금 아이콘을 탭하면 텔레그램을 잠그거나 잠금 해제할 수 있습니다. + +주의: 암호를 잊어버리셨다면 앱을 삭제하고 재설치해주셔야 합니다. 이 경우 비밀 대화 내용은 삭제됩니다. + PIN + 암호 + 현재 암호를 입력해주세요. + 암호를 입력하세요. + 새로운 암호 입력 + 암호 입력 + 암호를 다시 입력해주세요. + 암호가 일치하지 않습니다. 자동 잠금 - 일정 시간 후에 잠금코드 활성화 + 일정 시간이 지나면 텔레그램을 잠급니다. %1$s 후에 비활성화됨 지문으로 언락하기 @@ -1025,50 +1128,72 @@ 터치 센서 지문인식이 실패하였습니다. 다시 시도해주세요. 화면 캡처 허용 - 활성화시 앱내에서 화면 캡처가 가능합니다. 다만, 화면전환시 잠금코드가 활성화 되어져 있더라도 대화내용이 뜰 수 있습니다.\n\n적용이 안될시, 앱 재시작이 필요할 수도 있습니다. + 활성화 시 앱 내에서 화면의 스크린샷을 찍을 수 있습니다. 다만, 화면 전환 시 암호가 활성화되어 있더라도 대화 내용이 뜰 수 있습니다. + +적용이 안 된다면 앱을 재시작하세요. - 공유한 파일 + 공유된 파일 공유된 미디어 - 공유한 링크 + 공유된 링크 공유된 음악 - 이 채팅방에서 사진이나 동영상을 공유하면 다른 기기에서도 보실 수 있습니다. - 이 채팅방에서 음악을 공유하면 다른 기기에서도 보실 수 있습니다. - 이 채팅방에서 파일이나 문서를 공유하면 다른 기기에서도 보실 수 있습니다. - 이 채팅방에서 파일이나 문서를 공유하면 다른 기기에서도 보실 수 있습니다. - 이 대화에서 공유한 사진과 비디오파일이 표시됩니다. - 이 대화에서 공유한 음악파일이 표시됩니다. - 이 대화에서 공유한 문서파일이 표시됩니다. - 이 대화에서 공유한 링크가 표시됩니다. + 이 대화방에 사진과 동영상을 공유해두면 회원님의 모든 기기에서 접근하실 수 있습니다. + 이 대화방에 음악을 공유해두면 회원님의 모든 기기에서 접근하실 수 있습니다. + 이 대화방에 파일과 문서를 공유해두면 회원님의 모든 기기에서 접근하실 수 있습니다. + 이 대화방에 링크를 공유해두면 회원님의 모든 기기에서 접근하실 수 있습니다. + 이 대화방에서 공유된 사진과 동영상이 여기에 표시됩니다. + 이 대화에서 공유된 음악이 여기에 표시됩니다. + 이 대화에서 공유된 문서가 여기에 표시됩니다. + 이 대화에서 공유된 링크가 여기에 표시됩니다. 지도 위성 혼합 m 떨어짐 km 떨어짐 - 현재 위치 전송 - 선택한 위치 전송 + 현재 위치 보내기 + 다음과 나의 실시간 위치 공유... + 위치 공유 중지 + %1$s 님과의 실시간 공유를 정말 그만두시겠습니까? + %1$s 님과의 실시간 위치 공유를 정말 그만두시겠습니까? + 실시간 위치 공유를 중단하겠습니까? + 움직임을 따라 실시간으로 갱신됩니다 + 선택한 위치 보내기 위치 + 장소 %1$s 반경 내 정확함 - 혹은 위치를 선택 + 아니면 장소 선택 + 위로 올려 근처 장소 확인 + 실시간 위치 + 15분 동안 + 1시간 동안 + 8시간 동안 + 갱신됨 + 방금 갱신됨 + 회원님과 %1$s 님 + %1$s님이 %2$s와 공유 중 + 모두 중지 + %1$s 님과 위치를 실시간으로 공유 중입니다. + %1$s 님이 얼마 동안 회원님의 정확한 위치를 볼 수 있도록 할지 고르세요. + 이 대화방에 있는 사람들이 얼마 동안 회원님의 정확한 위치를 볼 수 있도록 할지 고르세요. + 위치 기반 서비스를 사용하시려면 GPS기능을 활성화 해주셔야합니다. 모든 미디어 보기 - 앨범에 저장 + 대화 안에서 보이기 + 갤러리에 저장 %1$d / %2$d 앨범 모든 사진 모든 미디어 - 사진이 없습니다. - 동영상이 아직 없음 - 사진/동영상을 먼저 다운로드하세요 + 사진 없음 + 먼저 미디어를 다운로드하세요 최근 사진 없음 - 최근에 검색한 GIF + 최근 GIF 없음 이미지 검색 웹 검색 GIF 검색 웹 검색 GIF 검색 사진 자르기 - 이미지 편집 향상 하이라이트 대비 @@ -1080,34 +1205,26 @@ 그레인 선명 흐리기 - 색조 새도우 하이라이트 - 커브 모두 빨강 초록 파랑 - 흐림 선형 방사형 - 이 사진을 삭제하시겠습니까? - 이 동영상을 삭제하시겠습니까? - 변경을 취소하시겠습니까? - 검색기록을 지우시겠습니까? - 지우기 - 사진 - 동영상 - 설명 추가... + 이 사진을 정말 지우시겠습니까? + 이 동영상을 정말 지우시겠습니까? + 원상태로 되돌릴까요? + 검색 기록을 비울까요? + 비우기 + 설명을 추가하세요... 사진 설명 동영상 설명 GIF 설명 설명 - 그리기 - 스티커 - 텍스트 - 삭제 + 지우기 수정 복제 아웃라인 @@ -1115,6 +1232,8 @@ 초기화 원본 정사각형 + 미디어를 별도의 메시지로 표시 + 미디어를 하나의 메시지로 표시 2단계 인증 개별 비밀번호 설정 @@ -1129,33 +1248,45 @@ 올바른 이메일을 입력해주세요. 비밀번호 분실시 유일하게 복구가 가능한 수단입니다. 건너뛰기 경고 - 비밀번호 분실시\n\n텔레그램에 대한 모든 접속 권한을 상실하시게 됩니다.\n비밀번호 분실시 복구는 불가능 합니다. - 거의 마무리 되었습니다! + 비밀번호 분실시 + +텔레그램에 대한 모든 접속 권한을 상실하시게 됩니다. +비밀번호 분실시 복구는 불가능 합니다. + 거의 다 됐어요! 2단계 인증을 완료하시려면 이메일(스팸 폴더도 확인)을 확인해주세요. 성공! - 2단계 인증 비밀번호가 활성화 되었습니다. - 비밀번호 변경 + 2단계 인증 비밀번호가 활성화됐습니다. + 비밀번호 바꾸기 비밀번호 끄기 복구 이메일 설정 - 복구 이메일 변경 - 비밀번호를 정말로 삭제하시겠습니까? + 복구 이메일 바꾸기 + 비밀번호를 정말 비활성화하시겠습니까? 비밀번호 힌트 - 비밀번호 힌트를 생성해주세요 + 비밀번호 힌트를 만드세요 비밀번호가 정확하지 않습니다. 2단계 인증 설정 끝내기 - 2단계 인증 설정을 완료하시려면 아래의 절차대로 진행해주세요:\n\n1.이메일 확인(스팸 폴더 확인)\n%1$s\n\n2.인증 링크 클릭 + 2단계 인증 설정을 완료하시려면 아래의 절차대로 진행해주세요: + +1.이메일 확인(스팸 폴더 확인) +%1$s + +2.인증 링크 클릭 힌트는 비밀번호와 다르게 설정해주세요. 올바르지 않은 이메일 - 죄송합니다. + 죄송합니다 비밀번호 복구 이메일을 설정하지 않았기때문에, 비밀번호를 기억해내시거나 계정 초기화를 진행해주셔야합니다. - 다음 복구 이메일 주소로 복구 코드를 전송하였습니다:\n\n%1$s + 다음 복구 이메일 주소로 복구 코드를 전송하였습니다: + +%1$s 이메일을 확인하여 수신받은 6자리 코드를 입력해주세요. 이메일 %1$s 접근에 문제가 있으신가요? 이메일 접근을 하실 수 없을 경우, 비밀번호를 기억해내시거나 계정 초기화를 진행해주셔야 합니다. 계정 초기화 계정 초기화 진행시 모든 대화,메시지 및 공유받은 미디어와 파일이 삭제가 됩니다. 경고 - 진행하실 경우 취소가 불가능합니다.\n\n계정 초기화 진행시 모든 대화 및 메시지가 삭제됩니다. + 진행하실 경우 취소가 불가능합니다. + +계정 초기화 진행시 모든 대화 및 메시지가 삭제됩니다. 초기화 비밀번호 2단계 인증이 활성화되어 회원님 계정이 개별 비밀번호로 보안됩니다. @@ -1163,7 +1294,8 @@ 비밀번호 복구 코드 비밀번호 비활성화 - 2단계 인증을 활성화 하였습니다.\n설정된 개별 비밀번호를 사용하여 텔레그램 계정에 로그인 할 수 있습니다. + 2단계 인증을 활성화 하였습니다. +설정된 개별 비밀번호를 사용하여 텔레그램 계정에 로그인 할 수 있습니다. 복구 이메일 %1$s 이 아직 활성화 되지 않았으며 미승인 상태입니다. 데이터 및 저장소 @@ -1187,60 +1319,57 @@ %1$s 부터 사용된 네트워크 사용량 통계를 초기화 하시겠습니까? - 개인정보 및 보안 - 개인정보 + 개인 정보 및 보안 + 개인 정보 마지막 접속 결제 - 결제 및 배송정보 초기화 - 배송 정보를 삭제 및 결제사에게 저장된 신용카드 정보 삭제를 요청할 수 있습니다. 텔레그램 절대로 회원님의 신용카드 정보를 저장하지 않습니다. + 결제 및 배송 정보 비우기 + 배송 정보를 지우며 결제사에게 저장된 신용카드 정보를 없애달라 요청하실 수 있습니다. 텔레그램은 회원님의 신용카드 정보를 절대 저장하지 않습니다. 배송 정보 - 결제정보 - 전체 공개 - 내 대화상대 - 비공개 + 결제 정보 + 모두 + 내 연락 상대만 + 아무도 없음 전체 공개 (-%1$d) - 내 대화상대 (+%1$d) - 내 대화상대 (-%1$d) - 내 대화상대 (-%1$d, +%2$d) + 내 연락 상대만 (+%1$d) + 내 연락 상대만 (-%1$d) + 내 연락 상대만 (-%1$d, +%2$d) 비공개 (+%1$d) 보안 회원 탈퇴 자동 회원 탈퇴 - 이 기간 동안 최소 한 번 이상 로그인을 하지 않으면 자동으로 모든 메시지, 연락처와 대화상대를 삭제하고 텔레그램을 탈퇴합니다. - 텔레그램을 탈퇴할까요? - 누가 마지막 접속 시간을 볼 수 있는지 변경 - 마지막 접속 시간을 누구에게 공개할까요? + 이 기간 안에 최소 한 번 이상 들어오시지 않으면, 모든 그룹, 메시지, 연락처와 더불어 회원님의 계정이 사라집니다. + 누가 회원님의 마지막 접속 시간을 볼 수 있나요? 예외 추가 주의: 회원님의 마지막 접속 시간을 공유받지 않는 사람의 마지막 접속 시간은 확인할 수 없습니다. 대신 일주일 이내, 한달 이내 등으로 간략하게 표시됩니다. - 항상 공유할 대화상대 - 절대 공유하지 않을 대화 상대 - 이 설정은 위의 내용을 무시하고 작동합니다. + 항상 공유 + 절대 공유하지 않음 + 이는 앞선 설정을 무시하고 작동합니다. 항상 공유 - 항상 공유할 대화상대... + 항상 공유할 사용자... 절대 공유하지 않음 - 절대 공유하지 않을 대화상대... - 대화상대 추가 - 죄송합니다. 너무 많이 변경하셨습니다. 개인정보 설정을 변경할 수 없으니 잠시 기다려 주세요. - 이 기기를 제외한 다른 기기에서 로그아웃합니다. - 사용자를 탭하고 눌러서 삭제하세요. + 절대 공유하지 않을 사용자... + 사용자 추가 + 죄송합니다. 개인 정보 설정을 너무 많이 바꾸셨습니다. 잠시 기다리세요. + 이 밖의 모든 기기를 로그아웃시킵니다. + 사용자를 길게 눌러 지우세요 그룹 - 나를 그룹에 초대가능한 사용자 + 누가 회원님을 그룹에 추가할 수 있나요? 회원님을 그룹 및 채널에 초대할 수 있는 상대방을 세세하게 설정 가능합니다. 항상 허용 항상 거부 항상 허용.. - 항상 거부.. - 위의 설정과 무관하게 해당 사용자는 회원님을 그룹 및 채널에 초대 할 수 없습니다. - 그룹 및 채널에 초대가능한 사용자 변경 + 항상 거부... + 위 사용자는 앞선 설정과 무관하게 회원님을 그룹과 채널에 추가하지 못합니다. + 회원님을 그룹이나 채널에 추가할 수 있는 사람을 설정하세요. 죄송합니다, 이 이용자는 개인 설정으로 인하여 그룹에 초대 할 수 없습니다. 죄송합니다, 이 이용자는 개인 설정으로 인하여 채널에 초대 할 수 없습니다. - 죄송합니다, 이 이용자들의 개인 설정으로 인하여 그룹을 생성 할 수 없습니다. - 단-대-단 - 사용자의 아이피 주소 노출을 제한하기 위하여 모든 단-대-단 기능을 텔레그램 서버로 릴레이하지만, 음성 품질이 약간 저하됩니다. + 죄송합니다. 이 사용자들의 개인 설정으로 인해 함께 그룹을 만드실 수 없습니다. + 피어투피어 + 회원님의 IP 주소 노출을 제한하기 위해, 피어투피어를 비활성화하시면 모든 전화는 텔레그램 서버를 통해 전달됩니다. 다만 음성 품질이 약간 저하될 수 있습니다. - 동영상 편집 동영상 보내는 중... - GIF 전송중... + GIF 보내는 중... 공유 @@ -1252,153 +1381,149 @@ 이 봇은 무엇을 할 수 있나요? 시작 재시작 - 봇 정지 - 봇 재시작 + 봇 멈추기 + 봇 살리기 - 다음 - 뒤로 완료 열기 저장 취소 닫기 추가 - 편집 + 수정 보내기 전화 걸기 복사 - 삭제 - 삭제 및 정지 + 지우기 + 지우고 멈추기 전달 재전송 - 사진 촬영 - 앨범 - 사진 삭제 + 카메라 + 갤러리 + 사진 지우기 설정 확인 자르기 - - 아니오 - 초대링크를 타고 그룹에 참여하였습니다. - 초대링크를 타고 그룹에 un1님이 참여하였습니다. - un1님이 un2님을 퇴장시켰습니다. + 초대 링크를 통해 그룹에 들어오셨습니다 + un1 님이 초대 링크를 통해 그룹에 들어왔습니다 + un1 님이 un2 님을 추방했습니다 un1님이 그룹에서 나갔습니다. - un1님이 un2님을 초대했습니다 - un1님이 그룹 사진을 삭제했습니다 - un1님이 그룹 사진을 변경했습니다 - un1님이 그룹 이름을 un2 그룹으로 변경했습니다 - un1님이 그룹을 만들었습니다 - un2님을 퇴장시켰습니다. + un1 님이 un2 님을 추가습니다 + un1 님이 그룹 사진을 지웠습니다 + un1 님이 그룹 사진을 바꿨습니다 + un1 님이 그룹 이름을 un2(으)로 바꿨습니다 + un1 님이 그룹을 만들었습니다 + un2 님을 추방했습니다 그룹에서 나갔습니다. - un2님을 초대했습니다 + un2 님을 초대했습니다 %1$s 점 획득 un1님이 %1$s 점 획득 회원님이 un2에서 %1$s점 획득 un1님이 un2에서 %1$s점 획득 - 그룹 사진을 삭제했습니다 - 그룹 사진을 변경했습니다 - 그룹 이름을 un2 그룹으로 변경했습니다 + 그룹 사진을 지우셨습니다 + 그룹 사진을 바꾸셨습니다 + 그룹 이름을 un2(으)로 바꾸셨습니다 그룹을 만들었습니다 - un1님에 의해 퇴장당하셨습니다. - un1님이 그룹에 초대했습니다 - un1 님께서 그룹에 돌아오셨습니다 + un1 님이 회원님을 추방했습니다 + un1 님을 초대하셨습니다 + un1 님이 그룹에 돌아왔습니다 un1 님이 그룹에 참여했습니다 - 그룹에 돌아오셨습니다. - 이 메시지는 현재 사용 중인 버전의 Telegram에서 지원되지 않습니다. 메시지를 보려면 http://telegram.org/update에서 앱을 업데이트하세요. + 그룹에 돌아오셨습니다 + 이 메시지는 현재 텔레그램 버전에서 지원되지 않습니다. 메시지를 보시려면 http://telegram.org/update에서 앱을 업데이트하세요. 사진 동영상 - 사진이 만료되었습니다 - 동영상이 만료되었습니다 - GIF파일 + 사진이 만료했습니다 + 동영상이 만료했습니다 + GIF 위치 + 실시간 위치 연락처 파일 스티커 음성 메시지 - 영상 메시지 + 동영상 메시지 게임 화면을 캡처했습니다! - un1님이 화면을 캡처했습니다! + un1 님이 화면을 캡처했습니다! 올바른 전화번호를 입력해 주세요 차단된 전화번호 - 코드가 만료되었습니다. 다시 로그인하세요 + 코드가 만료했습니다. 다시 로그인하세요. 너무 많이 시도하셨습니다. 나중에 다시 시도하세요 너무 많이 시도하셨습니다. %1$s 초 후에 다시 시도하세요. 올바른 코드를 입력해 주세요 - 죄송합니다, 최근에 계정 재가입을 너무 많이 시도하였습니다. 몇일 후에 다시 시도해주세요. + 죄송합니다. 최근 들어 계정 재가입을 너무 많이 하셨습니다. 다시 가입하려면 며칠 기다리셔야 합니다. 올바른 이름을 입력해 주세요 올바른 성을 입력해 주세요 불러오는 중... - 동영상 재생 앱이 없습니다. 계속하려면 앱을 설치해 주세요. + 동영상 재생기가 없습니다. 계속하려면 앱을 설치하세요. 발생한 문제에 대하여 sms@stel.com 주소로 이메일을 보내주세요. \'%1$s\' 파일 형식을 처리할 앱이 없습니다. 계속하려면 앱을 설치해 주세요. - 친구가 아직 텔레그램을 사용하지 않네요. 초대해 보세요! + 지인분이 아직 텔레그램을 설치하지 않았습니다. 초대장을 보낼까요? 확실합니까? - %2$s 채팅방에 %1$s님을 추가할까요? - 전달할 마지막 대화내용 개수: + %2$s 대화방에 %1$s 님을 추가할까요? + 전달할 최근 메시지 개수: %1$s 님을 그룹에 추가할까요? - 이 사용자는 이미 그룹에 추가되었습니다. - %1$s님에게 메시지를 전달할까요? - %1$s님에게 메시지를 보낼까요? - %1$s에게 게임을 공유하겠습니까? - %1$s에게 연락처를 보내시겠습니까? - 정말로 로그아웃하시겠습니까?\n\n텔레그램은 여러 기기에서 동시에 사용이 가능합니다.\n\n로그아웃하시면 비밀대화가 삭제되는 점 유의해주세요. + %1$s 님에게 메시지를 보낼까요? + %1$s 님에게 게임을 공유할까요? + %1$s 님에게 연락처를 보낼까요? + 정말 로그아웃하시겠습니까? + +텔레그램은 여러 기기에서 동시에 사용이 가능합니다. + +로그아웃 시 모든 비밀 대화가 사라지는 점을 유의하시기 바랍니다. 현재 기기를 제외하고 다른 기기에 로그인된 세션을 모두 종료시킬까요? - 그룹에서 나갈까요? - 채팅방을 삭제할까요? + 이 그룹에서 정말 나가시겠습니까? + 이 대화방을 정말 지우시겠습니까? 위치를 공유하겠습니까? - 현재 위치를 봇에게 전송합니다. + 현재 위치를 봇에게 보냅니다. 앱에서 현재 위치를 확인하지 못하였습니다. - 수동 선택 + 직접 선택 요청시마다 봇이 위치를 확인합니다. 이 요청으로 인하여 특정 위치에 대한 결과 확인이 가능합니다. 전화번호를 공유하겠습니까? - 봇이 전화번호를 인지를 하며, 다른 서비스와의 통합에 활용이 됩니다. - ]]>%2$s]]>님에게 %1$s번호를 공유하겠습니까? - 정말로 전화번호를 공유하겠습니까? - 대화상대를 차단할까요? + 봇이 회원님의 전화번호를 감지할 것입니다. 이는 다른 서비스와 통합하는 데 도움이 됩니다. + **%2$s**님에게 %1$s번호를 공유하겠습니까? + 회원님의 전화번호를 정말 공유하시겠습니까? + 이 연락처를 정말 차단하시겠습니까? 차단을 해제할까요? - 대화상대를 삭제할까요? - 비밀대화를 시작할까요? - 정말로 가입을 취소하시겠습니까? - 정말로 대화내용을 지우시겠습니까? - 채널에서 캐시된 모든 텍스트 및 미디어를 삭제하시겠습니까? - 그룹에서 캐시된 모든 텍스트 및 미디어를 삭제하시겠습니까? - %1$s: 정말로 삭제하시겠습니까? + 이 연락처를 정말 지우시겠습니까? + 비밀 대화를 시작하시겠습니까? + 가입을 정말 취소하시겠습니까? + 대화 내용을 정말 지우시겠습니까? + 이 채널에 있는 문자와 미디어 캐시를 모두 지울까요? + 이 그룹에 있는 문자와 미디어 캐시를 모두 지울까요? + %1$s를 정말 지우시겠습니까? %1$s 그룹에 메시지를 보낼까요? - %1$s에게 게임을 공유하겠습니까? - %1$s에게 연락처를 보내시겠습니까? - %1$s 그룹에 메시지를 전달할까요? - 이 기능은 회원님의 국가에서는 사용할 수 없습니다. - 입력된 아이디와 일치하는 텔레그램 계정이 없습니다. - 이 봇은 그룹에 참여 할 수 없습니다. - 비밀대화에서 링크 미리보기를 활성화하시겠습니까? 링크 프리뷰는 텔레그램 서버에서 생성이 됩니다. + %1$s 님에게 게임을 공유할까요? + %1$s 님에게 연락처를 보낼까요? + 이 사용자명을 가진 텔레그램 계정이 없습니다. + 이 봇은 그룹에 들어올 수 없습니다. + 비밀 대화에서 링크 미리보기를 활성화하시겠습니까? 링크 미리보기는 텔레그램 서버에서 만들어지는 점을 유의하시기 바랍니다. 인라인 봇은 제3자 개발자로 부터 제공이 됩니다. 봇이 작동을 하려면 봇의 아이디 및 뒤의 메시지가 담당 개발자에게 전송이 됩니다. - \"기기를 들어 말하기\"기능을 음성 메시지에 활성화 하시겠습니까? 죄송합니다, 메시지 수정을 할 수 없습니다. 텔레그램이 SMS를 수신 할 수 있도록 설정해주셔야 자동으로 코드 입력이 가능합니다. 텔레그램이 전화를 수신 할 수 있도록 설정해주셔야 자동으로 코드 입력이 가능합니다. 텔레그램이 SMS 및 전화를 수신 할 수 있도록 설정해주셔야 자동으로 코드 입력이 가능합니다. 텔레그램이 SMS 및 전화를 수신 할 수 있도록 설정해주셔야 자동으로 코드 입력이 가능합니다. - 죄송합니다, 이 작업이 제한되었습니다. - 죄송합니다, 그룹에 이 이용자나 봇을 차단하였기 대문에 추가를 할 수 없습니다. 초대하시렴녀 차단해제를 부탁드립니다. - 그룹방 입장 + 죄송합니다. 이 작업은 진행하실 수 없습니다. + 죄송합니다. 차단된 사용자나 봇은 그룹에 추가하실 수 없습니다. 먼저 차단을 해제하세요. + 그룹 입장 죄송합니다, 이 사용자는 이 그룹에 속해있지 않으며 초대가 되지 않기 때문에 관리자로 추가할 수 없습니다. 죄송합니다, 이 사용자는 블랙리스트에 있으며 해제가 가능하지 않기 떄문에 관리자로 추가할 수 없습니다. 죄송합니다, 이 사용자는 그룹의 관리자이며 해제권한이 없으시기 떄문에 차단할 수 없습니다. - 죄송합니다, 그룹의 관리자가 회원님의 스티커 전송을 제한했습니다. - 죄송합니다, 그룹의 관리자가 회원님의 미디어 전송을 제한했습니다. + 죄송합니다. 이 그룹의 관리자가 회원님이 스티커를 보내실 수 없도록 제한했습니다. + 죄송합니다. 이 그룹의 관리자가 회원님이 미디어를 보내실 수 없도록 제한했습니다. - Telegram은 여러 기기에서 친구와 메시지를 주고받을 수 있도록 회원님의 연락처 접근이 필요합니다. - Telegram은 사진, 비디오, 음악 및 다양한 미디어를 공유 및 저장하기 위하여 스토리지 접근이 필요합니다. - Telegram이 음성 메시지를 보내기 위하여 마이크에 대한 접근이 필요합니다. - Telegram이 비디오 녹화를 위하여 마이크에 대한 접근이 필요합니다. - Telegram이 사진 및 비디오 촬영을 하기 위하여 카메라 접근 권한을 필요로 합니다. - Telegram이 위치를 친구분들과 공유하기 위해 위치에 대한 접근 권한을 필요로 합니다. - 텔레그램이 위치에 대한 권한이 필요합니다. - Telegram이 화면속화면 기능을 위하여 화면위 그리기에 대한 접근이 필요합니다. + 모든 기기에서 친구들과 연락하려면 텔레그램에게 연락처 접근 권한을 부여하셔야 합니다. + 사진, 동영상, 음악과 그 밖의 미디어를 보내고 저장하려면 텔레그램에게 저장소 접근 권한을 부여하셔야 합니다. + 음성 메시지를 보내려면 텔레그램에게 마이크 접근 권한을 부여하셔야 합니다. + 동영상을 녹화하려면 텔레그램에게 마이크 접근 권한을 부여하셔야 합니다. + 사진과 동영상을 촬영하려면 텔레그램에게 카메라 접근 권한을 부여하셔야 합니다. + 회원님의 위치를 친구들과 공유하려면 텔레그램에게 위치 접근 권한을 부여하셔야 합니다. + 텔레그램에게 위치 접근 권한이 필요합니다. + 화면속화면 모드로 동영상을 재생하려면 텔레그램에게 다른 앱 위에 그릴 수 있는 권한을 부여하셔야 합니다. 설정 텔레그램 @@ -1407,14 +1532,20 @@ 보안성 강력함 클라우드 기반 - 세상에서 가장 빠른]]> 메신저입니다.\n 무료]]> 이며 안전합니다]]>. - 텔레그램]]>은 어떤 메신저보다도\n빠르게 메시지를 전송합니다. - 텔레그램]]>은 영원히 무료입니다. \n광고도 없고 이용료도 없습니다. - 텔레그램]]>은 그 어떤 누구로부터도\n메시지를 안전하게 보호합니다. - 텔레그램]]>은 대화나 미디어의 용량에\n제한이 없습니다. - 텔레그램]]>은 다른 기기에서도\n동시에 사용할 수 있습니다. + 세상에서 가장 **빠른** 메신저입니다. + **무료** 이며 **안전합니다**. + **텔레그램**은 어떤 메신저보다도 +빠르게 메시지를 전송합니다. + **텔레그램**은 영원히 무료입니다. +광고도 없고 이용료도 없습니다. + **텔레그램**은 그 어떤 누구로부터도 +메시지를 안전하게 보호합니다. + **텔레그램**은 대화나 미디어의 용량에 +제한이 없습니다. + **텔레그램**은 다른 기기에서도 +동시에 사용할 수 있습니다. 시작하기 - + 계정설정 전화에 적은 데이터 사용 수신 전화 @@ -1428,17 +1559,17 @@ 전화 거는 중 통화 중 텔레그램 전화 - 진행중인 텔레그램 전화 + 텔레그램으로 통화 중 전화 종료 - 다른 전화가 진행중입니다 - 현재 %1$s]]>와 통화중입니다. 전화를 끊고 %2$s]]>와 새로운 통화를 하시겠습니까? + 이미 전화받고 계십니다 + 현재 **%1$s**와 통화중입니다. 전화를 끊고 **%2$s**와 새로운 통화를 하시겠습니까? 음성 전화 - 통화음 - 텔레그램으로 전화 수신시 이 연락처에 대한 개별 통화음을 설정할 수 있습니다. + 벨소리 + 이 연락처에서 전화가 걸려올 때 울리는 벨소리를 맞춤 설정하실 수 있습니다. 전화 - 나에게 전화가 가능한 사람은? - 전화차단 설정이 가능합니다. - 이 사용자들은 위의 설정과 무관하게 전화가 되거나 안될 수 있습니다. + 누가 회원님에게 전화를 걸 수 있나요? + 회원님에게 전화를 걸 수 있는 사람을 제한하실 수 있습니다. + 위 사용자는 앞선 설정과 무관하게 회원님에게 전화를 걸지 못합니다. 거부 모바일 데이터로만 가능 항상 @@ -1455,68 +1586,96 @@ 취소된 전화 거절한 전화 %1$s (%2$s) - 이 이미지와 텍스트는 ]]>%1$s]]>님과의 통화시 생성된 암호화키에서 파생되었습니다.\n\n이 이미지와 텍스트가 ]]>%2$s\'s]]> 님의 휴대전화와 동일하다면 단말기간(end-to-end)의 암호화가 정상적으로 진행되고 있음을 보장합니다. 아직 전화 내역이 없습니다. - ]]>%1$s]]>의 앱은 현재 호환되지 않은 프로토콜을 사용중입니다. 앱 업데이트가 필요합니다. - ]]>%1$s]]>은 현재 전화기능을 지원하고 있지 않습니다. 앱 업데이트 후에 전화가 가능합니다. + **%1$s** 님의 앱은 현재 호환되지 않는 프로토콜을 사용중입니다. 앱을 업데이트한 뒤 전화할 수 있습니다. + **%1$s** 님의 텔레그램은 전화를 지원하지 않습니다. 앱을 업데이트한 뒤 전화할 수 있습니다. 텔레그램 전화의 품질을 평가해주세요. - 전화서비스 품질개선을 위하여 피드백을 남겨주시겠나요? - Telegram이전화를 걸기 위하여 마이크에 대한 접근이 필요합니다. + 전화를 걸려면 텔레그램에게 마이크 접근 권한을 부여하셔야 합니다. 코멘트 추가 전화 회신 다시 전화걸기 - 기본값 - 이 통화내역을 삭제하시겠습니까? + 기본 + 이 전화 기록을 정말 지우시겠습니까? 텔레그램 전화 이어폰 스피커 블루투스 - 전화로 이동 - 죄송합니다, ]]>%1$s]]> 님은 수신거부 상태입니다. - %1$s님과 이모티콘이 일치한다면, 이 통화는 100%% 안전합니다 + 전화로 돌아가기 + 죄송합니다, **%1$s** 님은 수신거부 상태입니다. + 화면 속 이모지가 %1$s 님과 같다면 이 통화는 100% 안전합니다 전화 평가 무엇이 잘못됬나요? 기술적 정보 포함 이 작업은 대화의 내용이 노출되지 않지만, 향 후 문제해결에 도움이 됩니다. 텔레그램 전화가 개선될 수 있도록 도와주셔서 감사합니다. + %1$d명의 수신자 + %1$d명의 수신자 + %1$d명의 수신자 + %1$d명의 수신자 + %1$d명의 수신자 + %1$d명의 수신자 온라인 %1$d명 온라인 %1$d명 온라인 %1$d명 온라인 %1$d명 온라인 %1$d명 온라인 %1$d명 - 대화상대 %1$d명 - 대화상대 %1$d명 - 대화상대 %1$d명 - 대화상대 %1$d명 - 대화상대 %1$d명 - 대화상대 %1$d명 - 외 %1$d명이 입력 중 - 외 %1$d명이 입력 중 - 외 %1$d명이 입력 중 - 외 %1$d명이 입력 중 - 외 %1$d명이 입력 중 - 외 %1$d명이 입력 중 - 새 메시지 없음 - 새 메시지 %1$d건 - 새 메시지 %1$d건 - 새 메시지 %1$d건 - 새 메시지 %1$d건 - 새 메시지 %1$d건 - 메시지 없음 - 메시지 %1$d건 - 메시지 %1$d건 - 메시지 %1$d건 - 메시지 %1$d건 - 메시지 %1$d건 - 아이템이 없습니다. + 텔레그램에 %1$d개의 연락처가 있음 + 텔레그램에 %1$d개의 연락처가 있음 + 텔레그램에 %1$d개의 연락처가 있음 + 텔레그램에 %1$d개의 연락처가 있음 + 텔레그램에 %1$d개의 연락처가 있음 + 텔레그램에 %1$d개의 연락처가 있음 + 안녕하세요, %1$d명의 친구와 텔레그램이라는 메신저를 사용하고 있어요. 함께 사용해요! 여기서 다운로드하세요: %2$s + 안녕하세요, %1$d명의 친구와 텔레그램이라는 메신저를 사용하고 있어요. 함께 사용해요! 여기서 다운로드하세요: %2$s + 안녕하세요, %1$d명의 친구와 텔레그램이라는 메신저를 사용하고 있어요. 함께 사용해요! 여기서 다운로드하세요: %2$s + 안녕하세요, %1$d명의 친구와 텔레그램이라는 메신저를 사용하고 있어요. 함께 사용해요! 여기서 다운로드하세요: %2$s + 안녕하세요, %1$d명의 친구와 텔레그램이라는 메신저를 사용하고 있어요. 함께 사용해요! 여기서 다운로드하세요: %2$s + 안녕하세요, %1$d명의 친구와 텔레그램이라는 메신저를 사용하고 있어요. 함께 사용해요! 여기서 다운로드하세요: %2$s + %1$d개의 대화 + %1$d개의 대화 + %1$d개의 대화 + %1$d개의 대화 + %1$d개의 대화 + %1$d개의 대화 + 참가자 %1$d명 + 참가자 %1$d명 + 참가자 %1$d명 + 참가자 %1$d명 + 참가자 %1$d명 + 참가자 %1$d명 + 외 %1$d명이 입력하는 중 + 외 %1$d명이 입력하는 중 + 외 %1$d명이 입력하는 중 + 외 %1$d명이 입력하는 중 + 외 %1$d명이 입력하는 중 + 외 %1$d명이 입력하는 중 + %1$s 님 외 %2$d명이 입력하는 중 + %1$s 님 외 %2$d명이 입력하는 중 + %1$s 님 외 %2$d명이 입력하는 중 + %1$s 님 외 %2$d명이 입력하는 중 + %1$s 님 외 %2$d명이 입력하는 중 + %1$s 님 외 %2$d명이 입력하는 중 + 새로운 메시지 %1$d개 + 새로운 메시지 %1$d개 + 새로운 메시지 %1$d개 + 새로운 메시지 %1$d개 + 새로운 메시지 %1$d개 + 새로운 메시지 %1$d개 + 메시지 %1$d개 + 메시지 %1$d개 + 메시지 %1$d개 + 메시지 %1$d개 + 메시지 %1$d개 + 메시지 %1$d개 + %1$d개 아이템 %1$d개 아이템 %1$d개 아이템 %1$d개 아이템 %1$d개 아이템 %1$d개 아이템 - 받은 대화 없음 + %1$d개의 대화로부터 받음 %1$d개의 대화로부터 받음 %1$d개의 대화로부터 받음 %1$d개의 대화로부터 받음 @@ -1564,12 +1723,12 @@ %1$d년 %1$d년 %1$d년 - %1$d명의 대화상대 - %1$d명의 대화상대 - %1$d명의 대화상대 - %1$d명의 대화상대 - %1$d명의 대화상대 - %1$d명의 대화상대 + 사용자 %1$d명 + 사용자 %1$d명 + 사용자 %1$d명 + 사용자 %1$d명 + 사용자 %1$d명 + 사용자 %1$d명 %1$d 번 %1$d 번 %1$d 번 @@ -1588,115 +1747,169 @@ 스티커 %1$d개 스티커 %1$d개 스티커 %1$d개 - %1$d 개의 사진 - %1$d 개의 사진 - %1$d 개의 사진 - %1$d 개의 사진 - %1$d 개의 사진 - %1$d 개의 사진 + 구독자 %1$d명 + 구독자 %1$d명 + 구독자 %1$d명 + 구독자 %1$d명 + 구독자 %1$d명 + 구독자 %1$d명 %1$d %1$d %1$d %1$d %1$d %1$d - %1$d 분 전에 확인 - %1$d 분 전에 확인 - %1$d 분 전에 확인 - %1$d 분 전에 확인 - %1$d 분 전에 확인 - %1$d 분 전에 확인 - %1$d 시간 전에 확인 - %1$d 시간 전에 확인 - %1$d 시간 전에 확인 - %1$d 시간 전에 확인 - %1$d 시간 전에 확인 - %1$d 시간 전에 확인 - %1$d]]> 초 - %1$d]]> 초 - %1$d]]> 초 - %1$d]]> 초 - %1$d]]> 초 - %1$d]]> 초 - %1$d]]> 분 - %1$d]]> 분 - %1$d]]> 분 - %1$d]]> 분 - %1$d]]> 분 - %1$d]]> 분 - %1$d]]> 시간 - %1$d]]> 시간 - %1$d]]> 시간 - %1$d]]> 시간 - %1$d]]> 시간 - %1$d]]> 시간 - %1$d]]> 일 - %1$d]]> 일 - %1$d]]> 일 - %1$d]]> 일 - %1$d]]> 일 - %1$d]]> 일 + %1$d분 전 갱신됨 + %1$d분 전 갱신됨 + %1$d분 전 갱신됨 + %1$d분 전 갱신됨 + %1$d분 전 갱신됨 + %1$d분 전 갱신됨 + **%1$d** 초 + **%1$d** 초 + **%1$d** 초 + **%1$d** 초 + **%1$d** 초 + **%1$d** 초 + **%1$d**분 + **%1$d**분 + **%1$d**분 + **%1$d**분 + **%1$d**분 + **%1$d**분 + **%1$d** 시간 + **%1$d** 시간 + **%1$d** 시간 + **%1$d** 시간 + **%1$d** 시간 + **%1$d** 시간 + **%1$d** 일 + **%1$d** 일 + **%1$d** 일 + **%1$d** 일 + **%1$d** 일 + **%1$d** 일 - %1$d 개의 전달된 메시지 - 전달된 메시지 - %1$d 개의 전달된 메시지 - %1$d 개의 전달된 메시지 - %1$d 개의 전달된 메시지 - %1$d 개의 전달된 메시지 - %1$d 개의 전달된 파일 - 전달된 파일 - %1$d 개의 전달된 파일 - %1$d 개의 전달된 파일 - %1$d 개의 전달된 파일 - %1$d 개의 전달된 파일 - %1$d 개의 전달된 사진 - 전달된 사진 - %1$d 개의 전달된 사진 - %1$d 개의 전달된 사진 - %1$d 개의 전달된 사진 - %1$d 개의 전달된 사진 - %1$d 개의 전달된 사진 - 전달된 비디오 - %1$d 개의 전달된 비디오 - %1$d 개의 전달된 비디오 - %1$d 개의 전달된 비디오 - %1$d 개의 전달된 비디오 - %1$d개의 전달된 트랙 - 전달된 트랙 - %1$d개의 전달된 트랙 - %1$d 전달된 트랙% - %1$d 전달된 트랙 - %1$d 전달된 트랙 - %1$d 개의 전달된 메시지 - 전달된 음성메시지 - %1$d 개의 전달된 음성메시지 - %1$d 개의 전달된 음성메시지 - %1$d 개의 전달된 음성메시지 - %1$d 개의 전달된 음성 메시지 - %1$d 개의 전달된 영상메시지 - 전달된 영상 메시지 - %1$d 개의 전달된 영상메시지 - %1$d 개의 전달된 영상메시지 - %1$d 개의 전달된 영상메시지 - %1$d 개의 전달된 영상메시지 - %1$d 개의 전달된 위치 - 전달된 위치 - %1$d 개의 전달된 위치 - %1$d 개의 전달된 위치 - %1$d 개의 전달된 위치 - %1$d 개의 전달된 위치 - %1$d 개의 전달된 연락처 - 전달된 연락처 - %1$d 개의 전달된 연락처 - %1$d 개의 전달된 연락처 - %1$d 개의 전달된 연락처 - %1$d 개의 전달된 연락처 - %1$d 개의 전달된 스티커 - 전달된 스티커 - %1$d 개의 전달된 스티커 - %1$d 개의 전달된 스티커 - %1$d 개의 전달된 스티커 - %1$d 개의 전달된 스티커 + 전달되는 메시지 +전달되는 메시지 %1$d개 + 전달되는 메시지 +전달되는 메시지 %1$d개 + 전달되는 메시지 +전달되는 메시지 %1$d개 + 전달되는 메시지 +전달되는 메시지 %1$d개 + 전달되는 메시지 +전달되는 메시지 %1$d개 + 전달되는 메시지 +전달되는 메시지 %1$d개 + 전달되는 파일 +전달되는 파일 %1$d개 + 전달되는 파일 +전달되는 파일 %1$d개 + 전달되는 파일 +전달되는 파일 %1$d개 + 전달되는 파일 +전달되는 파일 %1$d개 + 전달되는 파일 +전달되는 파일 %1$d개 + 전달되는 파일 +전달되는 파일 %1$d개 + 전달되는 사진 +전달되는 사진 %1$d개 + 전달되는 사진 +전달되는 사진 %1$d개 + 전달되는 사진 +전달되는 사진 %1$d개 + 전달되는 사진 +전달되는 사진 %1$d개 + 전달되는 사진 +전달되는 사진 %1$d개 + 전달되는 사진 +전달되는 사진 %1$d개 + 전달되는 동영상 +전달되는 동영상 %1$d개 + 전달되는 동영상 +전달되는 동영상 %1$d개 + 전달되는 동영상 +전달되는 동영상 %1$d개 + 전달되는 동영상 +전달되는 동영상 %1$d개 + 전달되는 동영상 +전달되는 동영상 %1$d개 + 전달되는 동영상 +전달되는 동영상 %1$d개 + 전달되는 트랙 +전달되는 트랙 %1$d개 + 전달되는 트랙 +전달되는 트랙 %1$d개 + 전달되는 트랙 +전달되는 트랙 %1$d개 + 전달되는 트랙 +전달되는 트랙 %1$d개 + 전달되는 트랙 +전달되는 트랙 %1$d개 + 전달되는 트랙 +전달되는 트랙 %1$d개 + 전달되는 음성 메시지 +전달되는 음성 메시지 %1$d개 + 전달되는 음성 메시지 +전달되는 음성 메시지 %1$d개 + 전달되는 음성 메시지 +전달되는 음성 메시지 %1$d개 + 전달되는 음성 메시지 +전달되는 음성 메시지 %1$d개 + 전달되는 음성 메시지 +전달되는 음성 메시지 %1$d개 + 전달되는 음성 메시지 +전달되는 음성 메시지 %1$d개 + 전달되는 동영상 메시지 +전달되는 동영상 메시지 %1$d개 + 전달되는 동영상 메시지 +전달되는 동영상 메시지 %1$d개 + 전달되는 동영상 메시지 +전달되는 동영상 메시지 %1$d개 + 전달되는 동영상 메시지 +전달되는 동영상 메시지 %1$d개 + 전달되는 동영상 메시지 +전달되는 동영상 메시지 %1$d개 + 전달되는 동영상 메시지 +전달되는 동영상 메시지 %1$d개 + 전달되는 위치 +전달되는 위치 %1$d곳 + 전달되는 위치 +전달되는 위치 %1$d곳 + 전달되는 위치 +전달되는 위치 %1$d곳 + 전달되는 위치 +전달되는 위치 %1$d곳 + 전달되는 위치 +전달되는 위치 %1$d곳 + 전달되는 위치 +전달되는 위치 %1$d곳 + 전달되는 연락처 +전달되는 연락처 %1$d개 + 전달되는 연락처 +전달되는 연락처 %1$d개 + 전달되는 연락처 +전달되는 연락처 %1$d개 + 전달되는 연락처 +전달되는 연락처 %1$d개 + 전달되는 연락처 +전달되는 연락처 %1$d개 + 전달되는 연락처 +전달되는 연락처 %1$d개 + 전달되는 스티커 +전달되는 스티커 %1$d개 + 전달되는 스티커 +전달되는 스티커 %1$d개 + 전달되는 스티커 +전달되는 스티커 %1$d개 + 전달되는 스티커 +전달되는 스티커 %1$d개 + 전달되는 스티커 +전달되는 스티커 %1$d개 + 전달되는 스티커 +전달되는 스티커 %1$d개 및 %1$d 개 및 %1$d 개 및 %1$d 개 diff --git a/TMessagesProj/src/main/res/values-nl/strings.xml b/TMessagesProj/src/main/res/values-nl/strings.xml index 187b193ce..bfc98514b 100644 --- a/TMessagesProj/src/main/res/values-nl/strings.xml +++ b/TMessagesProj/src/main/res/values-nl/strings.xml @@ -1,7 +1,4 @@ - - - Telegram Telegram-bèta @@ -17,10 +14,12 @@ Onjuist landnummer Telefoonverificatie - We hebben een sms-bericht met activatiecode verzonden naar ]]>%1$s]]>. - We hebben de code naar je andere ]]>Telegram]]>-app gestuurd. - We doen een activatie-oproep naar ]]>%1$s]]>.\n\nNeem niet op, Telegram verwerkt de oproep automatisch. - We bellen je op ]]>%1$s]]> om een code te dicteren. + We hebben een sms-bericht met activatiecode verzonden naar **%1$s**. + We hebben de code naar je andere **Telegram**-app gestuurd. + We doen een activatie-oproep naar **%1$s**. + +Neem niet op, Telegram verwerkt de oproep automatisch. + We bellen je op **%1$s** om een code te dicteren. We bellen je over %1$d:%2$02d We sturen je een SMS over %1$d:%2$02d We bellen je @@ -28,9 +27,13 @@ Verkeerd nummer? Geen code ontvangen? Account reset annuleren - Iemand met toegang tot nummer ]]>%1$s]]> heeft geprobeerd om je Telegram account te verwijderen en je twee-staps-verificatie wachtwoord te resetten.\n\nAls jij dit niet was, geef dan de code in die we zojuist per SMS hebben gestuurd. + Iemand met toegang tot nummer **%1$s** heeft geprobeerd om je Telegram account te verwijderen en je twee-staps-verificatie wachtwoord te resetten. + +Als jij dit niet was, geef dan de code in die we zojuist per SMS hebben gestuurd. Account resetten - Account ]]>%1$s]]> is actief en beveiligd met een wachtwoord, het verwijderen stellen we daarom 1 week uit.\n\nJe kunt dit proces ieder moment annuleren. + Account **%1$s** is actief en beveiligd met een wachtwoord, het verwijderen stellen we daarom 1 week uit. + +Je kunt dit proces ieder moment annuleren. Je account kan gereset worden over: De actieve gebruiker heeft de recente poging om dit account te resetten geannuleerd. Probeer het over 7 dagen nog eens. RESETTEN @@ -69,7 +72,6 @@ Veiligheidscode (CVV) MM/JJ Factuuradres - Kaarthouder Achternaam Betalingsinformatie opslaan Je kunt je betalingsinformatie voor toekomstig gebruik opslaan. @@ -93,7 +95,20 @@ Sorry, de betaling werd geweigerd. Betalingsprovider kon niet worden bereikt, controleer je internetverbinding en probeer het opnieuw. Waarschuwing - Noch Telegram, noch %1$s krijgen toegang tot je creditcard-informatie. Creditkaartgegevens worden alleen gebruikt door het het betalingssysteem %2$s.\n\nBetalingen gaan direct naar de ontwikkelaar van %1$s. Telegram kan geen garanties afgeven, doorgaan is op eigen risico. Neem bij problemen contact op met de ontwikkelaar van %1$s of je bank. + Noch Telegram, noch %1$s krijgen toegang tot je creditcard-informatie. Creditkaartgegevens worden alleen gebruikt door het het betalingssysteem %2$s. + +Betalingen gaan direct naar de ontwikkelaar van %1$s. Telegram kan geen garanties afgeven, doorgaan is op eigen risico. Neem bij problemen contact op met de ontwikkelaar van %1$s of je bank. + Wachtwoord & E-Mail + Wachtwoord + Wachtwoord invoeren + Wachtwoord opnieuw invoeren + Stel een wachtwoord in om je betaalinformatie te beschermen. Tijdens het inloggen vragen we je om dit wachtwoord. + Herstel-e-mailadres + Je e-mailadres + Voeg je geldige e-mailadres toe. Het is de enige manier om je wachtwoord te herstellen. + Telefoonnummer wordt doorgegeven aan %1$s als factuurinformatie. + E-mail wordt doorgegeven aan %1$s als factuurinformatie. + Telefoonnummer en E-mail worden doorgegeven aan %1$s als factuurinformatie. NIeuwe chat Instellingen @@ -102,7 +117,11 @@ gisteren Geen resultaten Nog geen chats - Begin een gesprek door op de\nopstelknop rechtsonder te drukken\nof druk op de menuknop voor meer opties. + Recent bekeken + VERBERG + Begin een gesprek door op de +opstelknop rechtsonder te drukken +of druk op de menuknop voor meer opties. Wachten op netwerk Verbinden Verbinden met proxy... @@ -125,10 +144,12 @@ Chat verwijderen Verwijderd account Kies een chat - Druk en houd ingedrukt + Doorsturen naar Geheime foto Geheime video - %1$s gebruikt een oudere versie van Telegram, dus worden geheime foto\'s weergegeven in de compatibiliteitsmodus.\n\nZodra %2$s Telegram updatet werken foto\'s met timers voor 1 minuut of minder in de \'Druk en houd ingedrukt\'-modus en krijg je een bericht wanneer de andere partij een schermafdruk maakt. + %1$s gebruikt een oudere versie van Telegram, dus worden geheime foto\'s weergegeven in de compatibiliteitsmodus. + +Zodra %2$s Telegram updatet werken foto\'s met timers voor 1 minuut of minder in de \'Druk en houd ingedrukt\'-modus en krijg je een bericht wanneer de andere partij een schermafdruk maakt. BERICHTEN Zoeken Meldingen stil @@ -153,16 +174,12 @@ Vet Cursief Normaal - Geef ]]>Telegram]]> toegang tot je contacten zodat je makkelijk contact kunt opnemen met iedereen die je kent. + Geef **Telegram** toegang tot je contacten zodat je makkelijk contact kunt opnemen met iedereen die je kent. NU NIET DOORGAAN - Publiek - Privé Promoveren tot beheerder Geen geblokkeerde gebruikers - Optioneel kun je een groepsbeschrijving geven. - Groep verlaten Groep verwijderen Groep verlaten Groep verwijderen @@ -188,6 +205,7 @@ un1 heeft contact vastgezet un1 heeft %1$s vastgezet un1 heeft locatie vastgezet + un1 heeft live locatie vastgezet un1 heeft GIF vastgezet un1 heeft muziekbestand vastgezet De groep is opgewaardeerd naar een supergroep @@ -235,6 +253,7 @@ Naam controleren... %1$s is beschikbaar. Leden + Abonnees Blacklist Geblokkeerde gebruikers Beperkt @@ -245,16 +264,15 @@ Kanaal echt verlaten? Je raakt alle berichten in dit kanaal kwijt. Wijzig - Als je een publieke link voor je groep instelt kan iedereen deze vinden en lid worden via de zoekfunctie.\n\nStel geen link in als je je supergroep privé wilt houden. - Als je een publieke link voor je kanaal instelt kan iedereen deze vinden en lid worden via de zoekfunctie.\n\nStel geen link in als je je kanaal privé wilt houden. - Stel een link in voor je publieke kanaal, om deze vindbaar te maken via de zoekfunctie en te delen met anderen.\n\nWil je dit niet dan kun je een privé-kanaal aanmaken. + Stel een link in voor je publieke kanaal, om deze vindbaar te maken via de zoekfunctie en te delen met anderen. + +Wil je dit niet dan kun je een privé-kanaal aanmaken. Kanaal gemaakt Kanaalfoto bijgewerkt Kanaalfoto verwijderd Kanaalnaam gewijzigd naar un2 Het maximale aantal publieke gebruikersnamen is bereikt. Trek eerst een link van één van je oudere groepen of kanalen in, of gebruik de privé-variant. Maker - Beheerder GELUID UIT GELUID AAN Beheerder toevoegen @@ -262,10 +280,8 @@ Deblokkeer Gebruiker vasthouden om te deblokkeren. Uitnodigen via link - %1$s echt als beheerder toevoegen? Beheerder ontslaan Alleen kanaal-beheerders zien deze lijst. - Deze gebruiker is nog geen lid, uitnodigen? Andere Telegram-gebruikers kunnen lid worden van je groep door deze link te openen. Je kunt beheerders toevoegen om je te helpen je kanaal te beheren. Druk en houd ingedrukt om beheerders te verwijderen. Lid worden van kanaal \'%1$s\'? @@ -285,18 +301,6 @@ Je hebt alleen leesrechten in dit kanaal. %1$s heeft je toegevoegd aan het kanaal %2$s Kanaalfoto van %1$s bijgewerkt - %1$s heeft een bericht gestuurd naar het kanaal %2$s - %1$s heeft een foto gestuurd naar het kanaal %2$s - %1$s heeft een video gestuurd naar het kanaal %2$s - %1$s heeft een contact gedeeld met het kanaal %2$s - %1$s heeft een locatie gestuurd naar het kanaal %2$s - %1$s heeft een bestand gestuurd naar het kanaal %2$s - %1$s heeft een GIF gestuurd naar het kanaal %2$s - %1$s heeft een spraakbericht gestuurd naar het kanaal %2$s - %1$s heeft een videobericht gestuurd naar het kanaal %2$s - %1$s heeft een muziekbestand gestuurd naar het kanaal %2$s - %1$s heeft een sticker gestuurd naar het kanaal %2$s - %1$s heeft een %3$s sticker gestuurd naar kanaal %2$s %1$s plaatste een bericht %1$s plaatste een foto %1$s plaatste een video @@ -342,21 +346,36 @@ Link-voorbeeld sturen Blokkeer tot Voor altijd - Verwijder Blokkeer en verwijder uit groep Groep beheren Kanaal beheren Groep beheren Kanaal beheren + Geschiedenis voor nieuwe leden + Zichtbaar + Nieuwe leden krijgen chatgeschiedenis ouder dan de eigen deelname te zien. + Verborgen + Nieuwe leden zien oudere berichten niet. Recente gebeurtenissen Alle gebeurtenissen Geselecteerde gebeurtenissen Alle beheerders - ]]>Nog geen gebeurtenissen]]>\n\nBeheerders en leden\nhebben geen acties uitgevoerd\nin de afgelopen 48 uur. - ]]>Nog geen gebeurtenissen]]>\n\nBeheerders\nhebben geen acties uitgevoerd\nin de afgelopen 48 uur. - ]]>Geen gebeurtenissen]]>\n\nGeen recente gebeurtenissen gevonden\nvoor je zoekopdracht. - Geen recente gebeurtenissen met inhoud \']]>%1$s]]>\' gevonden + **Nog geen gebeurtenissen** + +Beheerders en leden +hebben geen acties uitgevoerd +in de afgelopen 48 uur. + **Nog geen gebeurtenissen** + +Beheerders +hebben geen acties uitgevoerd +in de afgelopen 48 uur. + **Geen gebeurtenissen** + +Geen recente gebeurtenissen gevonden +voor je zoekopdracht. + Geen recente gebeurtenissen met inhoud \'**%1$s**\' gevonden Wat is een logboek? Dit is een lijst met alle acties van de groepsleden en beheerders in de afgelopen 48 uur. Dit is een lijst met alle acties van de beheerders in de afgelopen 48 uur. @@ -381,6 +400,8 @@ bericht door un1 vastgezet: bericht door un1 losgemaakt bericht door un1 verwijderd: + groepsstickers door un1 gewijzigd + groepsstickers door un1 verwijderd link door un1 gewijzigd: link door un1 gewijzigd: un1 heeft de link verwijderd @@ -389,11 +410,15 @@ beschrijving door un1 gewijzigd beschrijving door un1 gewijzigd Vorige beschrijving + un1 heeft de groepsgeschiedenis ook voor nieuwe leden zichtbaar gemaakt + un1 heeft de groepsgeschiedenis voor nieuwe leden verborgen uitnodigingen door un1 ingeschakeld uitnodigingen door un1 uitgeschakeld ondertekenen door un1 ingeschakeld ondertekenen door un1 uitgeschakeld - beperkingen van %1$s\n\n tot: %2$s aangepast + beperkingen van %1$s + + tot: %2$s aangepast Stickers & GIF\'s versturen Media versturen Berichten versturen @@ -415,7 +440,6 @@ Nieuwe leden Groepsinformatie Kanaalinformatie - Groepsinstellingen Verwijderde berichten Gewijzigde berichten Vastgezette berichten @@ -431,12 +455,13 @@ Muziek Onbekende artiest Onbekende titel + Shuffle + Volgorde omdraaien Kies een bestand Vrij: %1$s van %2$s Onbekende fout Toegangsfout - Nog geen bestanden Bestanden mogen maximaal %1$s zijn Geen opslagmedium gevonden USB-overdracht actief @@ -446,11 +471,22 @@ SD-kaart Map Om bestanden ongecomprimeerd te versturen + + Groepsstickers + Kies uit je stickers + Je kunt een stickerbundel kiezen die voor iedereen in de groepschat beschikbaar is. + KIES STICKERBUNDEL + stickerbundel + Je kunt persoonlijke stickerbundels maken via de bot @stickers. + Stickerbundel niet gevonden + Probeer opnieuw of kies uit de lijst onzichtbaar aan het typen is aan het typen zijn aan het typen + %1$s is aan het typen... + %1$s zijn aan het typen... %1$s neemt een spraakbericht op... %1$s neemt een videobericht op... %1$s verstuurt een geluid... @@ -469,8 +505,8 @@ speelt een spel video versturen bestand versturen - Heb je een vraag\nover Telegram? - Foto maken + Heb je een vraag +over Telegram? Galerij Locatie Video @@ -479,6 +515,7 @@ Nog geen berichten Doorgestuurd bericht Van + van: Niets recents Bericht Bericht @@ -509,7 +546,6 @@ Link-preview ophalen... OPENEN IN... Open met... - Link kopiëren %1$s versturen Als bestand Als bestand @@ -523,9 +559,6 @@ Spam van dit kanaal echt melden? Je kunt momenteel alleen berichten sturen aan onderlingen contacten. Je kunt momenteel alleen onderlinge contacten aan groepen toevoegen - Je hebt vandaag teveel mensen aangeschreven die niet in je contacten staan, probeer het morgen nog eens. Je kunt wel antwoorden als deze gebruiker eerst contact met jou opneemt, - Je kunt deze gebruiker niet toevoegen omdat je vandaag teveel mensen hebt aangeschreven die niet in je contacten staan, probeer het morgen nog eens. Je kunt een ander lid vragen om deze gebruiker aan de groep toe te voegen. - https://telegram.org/faq#can-39t-send-messages-to-non-contacts Meer informatie Versturen naar... Opmerking toevoegen @@ -534,6 +567,7 @@ Alle leden informeren Losmaken Wil je dit bericht vastzetten? + Wil je dit bericht vastzetten? Wil je dit bericht losmaken? Blokkeer gebruiker Spam melden @@ -552,8 +586,9 @@ Wijzigen kan niet meer. Snelkoppeling toevoegen Leden zoeken - Snelkoppeling aan startscherm toegevoegd - Je Cloudopslag + Hierheen doorsturen om op te slaan + Bewaarde berichten + Hierheen doorsturen om op te slaan Jij • Berichten naartoe door te sturen • Media en bestanden te bewaren @@ -579,6 +614,7 @@ Je bent beperkt in het sturen van inline-content door de groepsbeheerders. Je bent beperkt in het sturen van stickers door de groepsbeheerders. Je bent beperkt in het plaatsen van berichten door de groepsbeheerders. + beheerder %1$s heeft de zelfvernietigingstimer ingesteld op %2$s Je hebt de zelfvernietigingstimer ingesteld op %1$s @@ -625,7 +661,18 @@ %1$s heeft je verwijderd uit de groep %2$s %1$s heeft de groep %2$s verlaten %1$s heeft nu Telegram! - %1$s,\nEr is op je account ingelogd vanaf een nieuw apparaat op %2$s\n\nApparaat: %3$s\nLocatie: %4$s\n\nAls jij dit niet was, kun je die sessie beëindigen via Instellingen - Privacy en veiligheid - Sessies.\n\nAls je dat denkt dat iemand anders zonder jouw toestemming is ingelogd kun je twee-staps-verificatie activeren via instellingen - privacy en veiligheid.\n\nBedankt,\nHet Telegram-team + %1$s, +Er is op je account ingelogd vanaf een nieuw apparaat op %2$s + +Apparaat: %3$s +Locatie: %4$s + +Als jij dit niet was, kun je die sessie beëindigen via Instellingen - Privacy en veiligheid - Sessies. + +Als je dat denkt dat iemand anders zonder jouw toestemming is ingelogd kun je twee-staps-verificatie activeren via instellingen - privacy en veiligheid. + +Bedankt, +Het Telegram-team %1$s heeft zijn/haar profielfoto gewijzigd %1$s is nu lid van de groep %2$s via uitnodigingslink Antwoord @@ -644,11 +691,13 @@ %1$s heeft videobericht vastgezet in de groep %2$s %1$s heeft contact vastgezet in de groep %2$s %1$s heeft locatie vastgezet in de groep %2$s + %1$s heeft live-locatie vastgezet in de groep %2$s %1$s heeft GIF vastgezet in de groep %2$s %1$s heeft muziekbestand vastgezet in de groep %2$s %1$s heeft \"%2$s\" vastgezet %1$s heeft bericht vastgezet %1$s heeft foto vastgezet + %1$s heeft een spel vastgezet %1$s heeft video vastgezet %1$s heeft bestand vastgezet %1$s heeft sticker vastgezet @@ -657,26 +706,31 @@ %1$s heeft videobericht vastgezet %1$s heeft contact vastgezet %1$s heeft locatie vastgezet + %1$s heeft een live-locatie vastgezet %1$s heeft GIF vastgezet %1$s heeft muziekbestand vastgezet - %1$s heeft een spel vastgezet Contact kiezen Nog geen contacten - Hey, zullen we overstappen op Telegram: https://telegram.org/dl + Ik gebruik Telegram om te chatten. Download de app hier: %1$s om gisteren om online gezien gezien - laatst gezien zojuist Vrienden uitnodigen + Vrienden zoeken WERELDWIJD ZOEKEN recent gezien afgelopen week gezien afgelopen maand gezien lang geleden gezien Nieuw bericht + Kies contacten en nodig ze uit voor Telegram + NODIG UIT VOOR TELEGRAM + Telegram delen... + Contacten bijwerken? + Veel van je contacten zijn niet gesynchroniseerd, wil je deze synchroniseren? Kies \'OK\' als je je eigen apparaat, SIM en Google-account gebruikt. Mensen toevoegen Nadat je de groep hebt aangemaakt kun je meer gebruikers toevoegen en omzetten naar een supergroep. @@ -684,7 +738,6 @@ Groepsnaam %1$d van %2$d geselecteerd tot aan %1$s - Wil je lid worden van de groep \"%1$s\"? Sorry, deze groep is al vol. Sorry, deze groep bestaat niet. Link gekopieerd naar klembord. @@ -694,8 +747,12 @@ De oude link is nu inactief. Een nieuwe link is aangemaakt. Intrekken Link intrekken - Link ]]>%1$s]]> echt intrekken?\n\nDe groep \"]]>%2$s]]>\" zal privé worden. - Link ]]>%1$s]]> echt intrekken?\n\nHet kanaal \"]]>%2$s]]>\" zal privé worden. + Link **%1$s** echt intrekken? + +De groep \"**%2$s**\" zal privé worden. + Link **%1$s** echt intrekken? + +Het kanaal \"**%2$s**\" zal privé worden. Link kopiëren Link delen Andere Telegram-gebruikers kunnen lid worden van je groep door deze link te openen. @@ -705,8 +762,10 @@ Iedereen mag leden toevoegen en de groepsfoto of naam wijzigen. Beheerders mogen leden beheren en de groepsfoto of naam wijzigen. + Leden Gedeelde media Instellingen + Abonnee toevoegen Lid toevoegen Beheerders instellen LID BEPERKEN @@ -719,9 +778,22 @@ Opwaarderen naar supergroep Waarschuwing Groep echt omzetten naar supergroep? Je kunt dit niet ongedaan maken. - ]]>Ledenlimiet bereikt.]]>\n\nWil je extra functies en een hogere limiet? Waardeer op naar een supergroep:\n\n• Supergroepen hebben maximaal %1$s\n• Nieuwe leden zien de hele geschiedenis\n•Gewiste berichten gelde voor iedereen\n• Beheerders kunnen berichten vastzetten\n• Maker kan een publieke groepslink instellen - ]]>Supergroepen:]]>\n\n• Nieuwe leden zien de hele geschiedenis\n• Gewiste berichten gelden voor iedereen\n• Beheerders kunnen berichten vastzetten\n• Maker kan een publieke groepslink instellen - ]]>Let op:]]> Je kunt dit niet ongedaan maken. + **Ledenlimiet bereikt.** + +Wil je extra functies en een hogere limiet? Waardeer op naar een supergroep: + +• Supergroepen hebben maximaal %1$s +• Nieuwe leden zien de hele geschiedenis +•Gewiste berichten gelde voor iedereen +• Beheerders kunnen berichten vastzetten +• Maker kan een publieke groepslink instellen + **Supergroepen:** + +• Nieuwe leden zien de hele geschiedenis +• Gewiste berichten gelden voor iedereen +• Beheerders kunnen berichten vastzetten +• Maker kan een publieke groepslink instellen + **Let op:** Je kunt dit niet ongedaan maken. Delen Toevoegen @@ -749,9 +821,12 @@ Als je een timer instelt vernietigt de foto zichzelf na het bekijken. Als je een timer instelt vernietigt de video zichzelf na het bekijken. Uit - Deze afbeelding en tekst zijn afgeleid van de encryptiesleutel voor deze geheime chat met ]]>%1$s]]>.\n\nAls dit er hetzelfde uitziet op het apparaat van ]]>%2$s]]>, dan is end-to-end-encryptie gegarandeerd.
    ]]>Lees meer op telegram.org
    + Deze afbeelding en tekst zijn afgeleid van de encryptiesleutel voor deze geheime chat met **%1$s**. + +Als dit er hetzelfde uitziet op het apparaat van **%2$s**, dan is end-to-end-encryptie gegarandeerd. + +Lees meer op telegram.org https://telegram.org/faq#secret-chats - Tik voor emoji-weergave Onbekend Info Telefoon @@ -763,8 +838,11 @@ Je naam moet minimaal 5 tekens hebben. Je naam mag niet langer zijn dan 32 tekens. Sorry, begincijfers zijn niet toegestaan. - Je kan een gebruikersnaam kiezen voor ]]>Telegram]]>. Hiermee kunnen anderen je vinden en contact met je opnemen zonder je telefoonnummer te weten.\n\nJe mag ]]>a–z]]>, ]]>0–9]]> en liggend streepje gebruiken. De minimale lengte is ]]>5]]> tekens. - Deze link opent een chat met je in Telegram:\n%1$s + Je kan een gebruikersnaam kiezen voor **Telegram**. Hiermee kunnen anderen je vinden en contact met je opnemen zonder je telefoonnummer te weten. + +Je mag **a–z**, **0–9** en liggend streepje gebruiken. De minimale lengte is **5** tekens. + Deze link opent een chat met je in Telegram: +%1$s Gebruikersnaam controleren. %1$s is beschikbaar. Geen @@ -774,6 +852,13 @@ Stickers toevoegen Maskers toevoegen Toevoegen aan stickers + Toevoegen aan favorieten + Sticker toegevoegd aan favorieten + Sticker verwijderd uit favorieten + Recent gebruikt + Favorieten + Groepsstickers + Verwijder uit favorieten Maskers toevoegen Stickers niet gevonden Stickers verwijderd @@ -789,7 +874,7 @@ Maskers Je kunt maskers toevoegen aan foto\'s , gebruik de fotobewerkingsoptie en verstuur ze. Populaire stickers - Deze stickers zijn momenteel populair op Telegram. Just kunt persoonlijke stickers toevoegen via de @stickers bot. + Deze stickers zijn momenteel populair op Telegram. Je kunt persoonlijke stickers toevoegen via de @stickers bot. Gearchiveerde stickers Gearchiveerde maskers Geen gearchiveerde stickers @@ -878,16 +963,17 @@ Vastgezette berichten Taal Aangepast - De ondersteuning van Telegram wordt gedaan door vrijwilligers. We doen ons best om zo snel mogelijk te antwoorden.\n\nBekijk ook de veelgestelde vragen]]>: Hier staan de antwoorden op de meeste vragen en belangrijke tips voor het oplossen van problemen]]>. + De ondersteuning van Telegram wordt gedaan door vrijwilligers. We doen ons best om zo snel mogelijk te antwoorden. + +Bekijk ook de veelgestelde vragen]]>: Hier staan de antwoorden op de meeste vragen en belangrijke tips voor het oplossen van problemen]]>. Vraag een vrijwilliger + Veelgestelde vragen Veelgestelde vragen https://telegram.org/faq Privacybeleid https://telegram.org/privacy Vertaling verwijderen? Ongeldig vertalingsbestand - Inschakelen - Uitgeschakeld Meldingenservice App automatisch herstarten bij afsluiten om meldingen weer te kunnen blijven geven. Achtergrondverbinding @@ -906,6 +992,9 @@ Kort Lang Automatisch media downloaden + Automatisch downloaden + Autom. downloaden resetten + Instellingen voor automatisch downloaden echt resetten? Bij mobiele verbinding Bij Wi-Fi-verbinding Bij roaming @@ -920,19 +1009,19 @@ Prioriteit Systeeminstelling gebruiken Standaard - Laag Hoog Max Nooit Meldingen herhalen - Je kan je telefoonnummer hier wijzigen. Al je clouddata — berichten, bestanden, groepen, contacten, etc. zullen worden gekopieërd naar je nieuwe nummer.\n\nLet op:]]> al je Telegram-contacten krijgen je nieuwe nummer]]> in hun adresboek, ervan uitgaande dat ze je oude nummer hadden en je hen niet had geblokkeerd in Telegram. + Je kan je telefoonnummer hier wijzigen. Al je clouddata — berichten, bestanden, groepen, contacten, etc. zullen worden gekopieërd naar je nieuwe nummer. + +**Let op:** al je Telegram-contacten krijgen je **nieuwe nummer** in hun adresboek, ervan uitgaande dat ze je oude nummer hadden en je hen niet had geblokkeerd in Telegram. Al je Telegram-contacten krijgen je nieuwe nummer in hun adresboek, ervan uitgaande dat ze je oude nummer hadden en je hen niet had geblokkeerd in Telegram. NUMMER WIJZIGEN Nieuw nummer We sturen een sms-bericht met verificatiecode naar je nieuwe nummer. Aan telefoonnummer %1$s is al een Telegram-account gekoppeld. Verwijder het account om te kunnen migreren naar het nieuwe nummer. Overig - Uitgeschakeld Uitgeschakeld Ingeschakeld Uitgeschakeld @@ -959,6 +1048,10 @@ Debug-menu Contacten importeren Contacten verversen + Chats herstellen + In-app camera inschakelen + In-app camera uitschakelen + Reset geimporteerde contacten Via instellingen kun je je taal altijd aanpassen. Kies je taal Overige @@ -972,10 +1065,22 @@ SOCKS5 proxy-instellingen Gebruik voor oproepen Je oproepkwaliteit kan slechter worden door het gebruik van een proxy-server. + Let op + Je opslagruimte begint vol te raken. Om ruimte te maken kun je Telegram instellen om alleen recente media te cachen. + Verwijder media na + Nooit verwijderen + Contacten + Chats + Groepschats + Kanalen + MAXIMALE GROOTTE + tot %1$s Lokale database Gecachet tekstberichten wissen? - Het opschonen van de lokale database zal de tekst van gecachet berichten verwijderen en de database comprimeren om ruimte te besparen. Telegram heeft wat ruimte nodig om te kunnen functioneren, dus de databasegrootte zal nooit nul worden.\n\nDit kan enkele minuten duren. + Het opschonen van de lokale database zal de tekst van gecachet berichten verwijderen en de database comprimeren om ruimte te besparen. Telegram heeft wat ruimte nodig om te kunnen functioneren, dus de databasegrootte zal nooit nul worden. + +Dit kan enkele minuten duren. Cache opschonen Opschonen Berekenen... @@ -988,7 +1093,9 @@ Overige bestanden Leeg Media bewaren - Foto\'s, video\'s en andere bestanden uit cloudchats die je gedurende deze periode niet hebt geopend]]> zullen worden verwijderd van dit apparaat om ruimte te besparen.\n\nAlle media zal in de Telegram-cloud bewaard blijven en kan opnieuw worden gedownload als je het weer nodig hebt. + Foto\'s, video\'s en andere bestanden uit cloudchats die je gedurende deze periode niet hebt **geopend** zullen worden verwijderd van dit apparaat om ruimte te besparen. + +Alle media zal in de Telegram-cloud bewaard blijven en kan opnieuw worden gedownload als je het weer nodig hebt. Voor altijd Spraakberichten Videoberichten @@ -1005,8 +1112,9 @@ Toegangscode-vergrendeling Toegangscode wijzigen - Als je een toegangscode instelt verschijnt er een slotje op de chatspagina. Tik erop om Telegram te vergrendelen of te ontgrendelen.\n\nLet op: vergeet je de toegangscode dan zul je de app moeten verwijderen en herinstalleren. Alle geheime chats gaan daarbij verloren. - Je zult nu een slotje op de chatspagina zien. Tik erop om Telegram te vergrendelen met je nieuwe toegangscode. + Als je een toegangscode instelt verschijnt er een slotje op de chatspagina. Tik erop om Telegram te vergrendelen of te ontgrendelen. + +Let op: vergeet je de toegangscode dan zul je de app moeten verwijderen en herinstalleren. Alle geheime chats gaan daarbij verloren. PIN Wachtwoord Huidige toegangscode invoeren @@ -1014,7 +1122,6 @@ Nieuwe toegangscode invoeren Toegangscode invoeren Toegangscode opnieuw invoeren - Ongeldige toegangscode Toegangscodes komen niet overeen Automatisch vergrendelen Vraag om toegangscode indien afwezig @@ -1025,7 +1132,9 @@ Vingerafdruksensor Vingerafdruk niet herkend, probeer opnieuw Schermafdruk toestaan - Met deze functie kun je in-app schermafdrukken maken, wel zijn chats dan zichtbaar in taakbeheer, ook met een ingeschakelde toegangscode.\n\nHerstart de app om deze optie actief te maken. + Met deze functie kun je in-app schermafdrukken maken, wel zijn chats dan zichtbaar in taakbeheer, ook met een ingeschakelde toegangscode. + +Herstart de app om deze optie actief te maken. Gedeelde bestanden Gedeelde media @@ -1043,22 +1152,43 @@ Kaart Satelliet Hybride - m hiervandaan - km hiervandaan + m + km Huidige locatie sturen + Deel mijn live-locatie + Delen van locatie stoppen + Echt stoppen met het delen van je live-locatie met %1$s? + Echt stoppen met het delen van je live-locatie met %1$s? + Echt stoppen met het delen van je live-locatie? + Bijgewerkt terwijl je je verplaatst Geselecteerde locatie sturen Locatie + Plek Nauwkeurig tot op %1$s OF KIES EEN PLEK + VEEG OMHOOG VOOR PLEKKEN DICHTBIJ + LIVE-LOCATIES + 15 minuten + 1 uur + 8 uur + bijgewerkt + zojuist bijgewerkt + Jij en %1$s + %1$s aan het delen met %2$s + ALLES STOPPEN + Je deelt je live-locatie met %1$s + Kies hoelang je je nauwkeurige locatie met %1$s wilt delen. + Kies hoelang je je nauwkeurige locatie met mensen in deze chat wilt delen. + GPS lijkt uitgeschakeld, schakel GPS in om locatiegebaseerde functies te gebruiken. Alle media weergeven + In chat weergeven Opslaan in galerij %1$d van %2$d Galerij Alle foto\'s Alle media Nog geen foto\'s - Nog geen video\'s Download media eerst Niets recents Niets recents @@ -1068,7 +1198,6 @@ Online zoeken GIF\'s zoeken Foto bijsnijden - Foto bewerken Verbeteren Accent Contrast @@ -1080,15 +1209,12 @@ Korrel Scherper Vervagen - Tint SCHADUWEN ACCENTUEREN - Curven ALLE ROOD GROEN BLAUW - Vager Uit Lineair Radiaal @@ -1097,16 +1223,11 @@ Wijzigingen negeren? Zoekgeschiedenis wissen? Wissen - Foto\'s - Video Onderschrift toevoegen Foto-onderschrift Video-onderschrift GIF-Onderschrift Onderschrift - Tekenen - Stickers - Tekst Verwijder Bewerk Klonen @@ -1115,6 +1236,8 @@ Resetten Origineel Vierkant + Geeft media als aparte berichten weer + Geef media als een enkel bericht weer Twee-staps-verificatie Extra wachtwoord instellen @@ -1129,7 +1252,9 @@ Voeg je geldige e-mailadres toe. Het is de enige manier om je wachtwoord te herstellen. Overslaan Waarschuwing - Let op:\n\nAls je je wachtwoord vergeet verlies je toegang tot je Telegram-account. Er is geen optie om dit te herstellen. + Let op: + +Als je je wachtwoord vergeet verlies je toegang tot je Telegram-account. Er is geen optie om dit te herstellen. Bijna klaar! Controleer je e-mail en klik op de bevestigingslink om de twee-staps-verificatie in te schakelen. Kijk voor de zekerheid ook in de ongewenste post. Gelukt! @@ -1143,19 +1268,28 @@ Stel een hint voor je wachtwoord in Wachtwoorden komen niet overeen Twee-staps-verificatie annuleren - Volg deze stappen om twee-staps-verificatie in te schakelen:\n\n1.Controleer je e-mail (kijk voor de zekerheid ook in de ongewenste post)\n%1$s\n\n2.n klik op de bevestigingslink. + Volg deze stappen om twee-staps-verificatie in te schakelen: + +1.Controleer je e-mail (kijk voor de zekerheid ook in de ongewenste post) +%1$s + +2.n klik op de bevestigingslink. De hint moet anders zijn dan je wachtwoord Ongeldig e-mailadres Sorry Omdat je geen herstel-e-mailadres hebt opgegeven voor je wachtwoord zul je bij verlies van je wachtwoord je account moeten resetten. - We hebben een herstelcode naar je opgegeven e-mailadres gestuurd:\n\n%1$s + We hebben een herstelcode naar je opgegeven e-mailadres gestuurd: + +%1$s Controleer je e-mail en geef de 6-cijferige code in die we je hebben gestuurd. Kun je je e-mail niet benaderen %1$s? Bij verlies van je wachtwoord zul je je account moeten resetten. ACCOUNT RESETTEN Al je chats, berichten en alle andere data gaan verloren als je verder gaat met de account-reset. Waarschuwing - Je kunt dit niet ongedaan maken.\n\nAl je chats, berichten en data gaan verloren als je je account reset. + Je kunt dit niet ongedaan maken. + +Al je chats, berichten en data gaan verloren als je je account reset. Resetten Wachtwoord Twee-staps-verificatie ingeschakeld. Je account is met een extra wachtwoord beveiligd. @@ -1163,7 +1297,8 @@ Wachtwoordherstel Code Wachtwoord uitgeschakeld - Je hebt twee-staps-verificatie ingeschakeld.\nAls je inlogt op je Telegram-account heb je het ingestelde wachtwoord nodig. + Je hebt twee-staps-verificatie ingeschakeld. +Als je inlogt op je Telegram-account heb je het ingestelde wachtwoord nodig. Je herstel-e-mailadres %1$s is nog niet actief en in afwachting van bevestiging. Data en opslag @@ -1207,8 +1342,6 @@ Account verwijderen Indien afwezig voor Als je binnen deze periode niet minimaal één keer ingelogd bent geweest zal je account worden verwijderd, inclusief alle groepen, berichten en contacten. - Je account verwijderen? - Wijzig wie je laatst gezien tijd kan zien. Wie kan mijn laatst gezien tijd zien? Uitzonderingen toevoegen Let op: van mensen waarmee je je laatst gezien tijd niet deelt is deze voor jou ook niet zichtbaar. In plaats daarvan krijg je tijden bij benadering te zien (recent, afgelopen week, afgelopen maand). @@ -1238,7 +1371,6 @@ Peer-to-Peer peer-to-peer uitschakelen zorgt ervoor dat alle gesprekken verlopen via Telegram-servers, dit voorkomt dat je IP-adres zichtbaar is maar verlaagt de audio-kwaliteit enigszins. - Video bewerken Video versturen GIF versturen... @@ -1255,8 +1387,6 @@ Bot stoppen Bot herstarten - Volgende - Vorige Gereed Openen Opslaan @@ -1277,8 +1407,6 @@ Stel in OK BIJSNIJDEN - Ja - Nee Je bent nu lid van de groep via uitnodigingslink un1 is nu lid van de groep via uitnodigingslink @@ -1312,6 +1440,7 @@ Video is verlopen GIF Locatie + Live-locatie Contact Bestand Sticker @@ -1340,12 +1469,14 @@ %1$s toevoegen aan de groep %2$s? Aantal recente berichten om door te sturen: %1$s toevoegen aan de groep? - Gebruiker is al een groepslid - Berichten doorsturen naar %1$s? Berichten naar %1$s versturen? Spel delen met %1$s? Contact delen met %1$s? - Weet je zeker dat je wilt uitloggen?\n\nTelegram kun je naadloos op al je apparaten tegelijkertijd gebruiken.\n\nLet op! Als je uitlogt worden al je geheime chats verwijderd. + Weet je zeker dat je wilt uitloggen? + +Telegram kun je naadloos op al je apparaten tegelijkertijd gebruiken. + +Let op! Als je uitlogt worden al je geheime chats verwijderd. Alle apparaten behalve het huidige apparaat uitloggen? Verwijderen en de groep verlaten? Chat echt verwijderen? @@ -1356,7 +1487,7 @@ Deze bot wil toegang tot je locatie voor ieder verzoek dat je stuurt. Dit kan gebruikt worden voor locatiespecifieke resultaten. Telefoonnummer delen? De bot krijgt je telefoonnummer, dit kan handig zijn voor de integratie met andere diensten. - Telefoonnummer %1$s delen met ]]>%2$s]]>? + Telefoonnummer %1$s delen met **%2$s**? Telefoonnummer echt delen? Weet je zeker dat je deze persoon wilt blokkeren? Weet je zeker dat je deze persoon wilt deblokkeren? @@ -1365,18 +1496,15 @@ Weet je zeker dat je de registratie wilt annuleren? Geschiedenis echt wissen? Kanaalcache opschonen? - Groepscache echt opschonen? + Gecachet tekst en media van deze groep wissen? %1$s echt verwijderen? Berichten naar %1$s versturen? Spel delen met %1$s? Contact delen met %1$s? - Berichten doorsturen naar %1$s? - Sorry, deze functie is momenteel niet beschikbaar in jouw land. Er is geen Telegram-account met deze gebruikersnaam. Deze bot kan geen groepslid worden. Wil je uitgebreide link-voorbeelden inschakelen voor geheime chats? Let op: deze worden gegenereerd op onze servers. Let op: inline-bots worden aangeboden door externe ontwikkelaars. Voor de werking van de bot worden de karakters die je na de botnaam typt naar deze ontwikkelaar verstuurd. - Wil je \"Houd bij oor\" inschakelen voor spraakberichten? Je mag dit bericht niet wijzigen. Sta het ontvangen van SMS toe zodat we automatisch je inlogcode kunnen invoeren. Sta het ontvangen van oproepen toe zodat we automatisch je inlogcode kunnen invoeren. @@ -1407,14 +1535,20 @@ Veilig Krachtig In de cloud - \'s Werelds snelste]]> berichtendienst.\nHet is gratis]]> en veilig]]>. - Telegram]]> bezorgt berichten sneller dan\nelke andere applicatie. - Telegram]]> is altijd gratis. Geen advertenties.\nGeen abonnementskosten. - Telegram]]> beveiligd je berichten\ntegen aanvallen van hackers. - Telegram]]> beperkt je niet in de grootte van\nje media of chats. - Telegram]]> biedt toegang tot je berichten\nvanaf meerdere apparaten. + \'s Werelds **snelste** berichtendienst. +Het is **gratis** en **veilig**. + **Telegram** bezorgt berichten sneller dan +elke andere applicatie. + **Telegram** is altijd gratis. Geen advertenties. +Geen abonnementskosten. + **Telegram** beveiligt je berichten +tegen aanvallen van hackers. + **Telegram** beperkt je niet in de grootte van +je media of chats. + **Telegram** biedt toegang tot je berichten +vanaf meerdere apparaten. Begin met chatten - + Accountinstellingen Minder data voor oproepen gebruiken Inkomende oproep @@ -1431,7 +1565,7 @@ Telegram-oproep bezig Oproep beëindigen Er is al een oproep actief - Er is al een oproep actief met %1$s]]>. Wil je deze oproep beëindigen en een nieuwe oproep starten met %2$s]]>? + Er is al een oproep actief met **%1$s**. Wil je deze oproep beëindigen en een nieuwe oproep starten met **%2$s**? Spraakoproepen Beltoon Je kunt een aangepaste beltoon instellen voor Telegram-oproepen van dit contact. @@ -1455,12 +1589,10 @@ Oproep geannuleerd Geweigerde oproep %1$s (%2$s) - Deze afbeelding en tekst zijn afgeleid van de encryptiesleutel voor deze oproep met ]]>%1$s]]>.\n\nAls dit er hetzelfde uitziet op het apparaat van ]]>%2$s]]>, dan is end-to-end-encryptie gegarandeerd. Je hebt nog geen oproepen - ]]>%1$s]]> maakt gebruik van een niet-compatibel protocol voor spraakoproepen en moet eerst een update uitvoeren. - ]]>%1$s]]> heeft nog geen ondersteuning voor spraakoproepen en zal eerst een update moeten uitvoeren. + **%1$s** maakt gebruik van een niet-compatibel protocol voor spraakoproepen en moet eerst een update uitvoeren. + **%1$s** heeft nog geen ondersteuning voor spraakoproepen en zal eerst een update moeten uitvoeren. Beoordeel de kwaliteit van je Telegram-oproep. - Wil je feedback geven zodat we onze oproepfunctie kunnen verbeteren? Telegram heeft toegang tot je microfoon nodig zodat je kunt bellen. Opmerking toevoegen Terugbellen @@ -1472,7 +1604,7 @@ Luidspreker Bluetooth TERUG NAAR GESPREK - Sorry, ]]>%1$s]]> neemt geen oproepen aan. + Sorry, **%1$s** neemt geen oproepen aan. Als deze emoji\'s gelijk zijn bij %1$s is dit gesprek 100%% veilig. Beoordeel deze oproep Wat is er misgegaan? @@ -1480,12 +1612,36 @@ We hebben geen inzage in je gesprek, maar kunnen hiermee het probleem wel sneller oplossen. Bedankt voor je hulp bij het verbeteren van Telegram-oproepen. + %1$d ontvangers + %1$d ontvanger + %1$d ontvangers + %1$d ontvangers + %1$d ontvangers + %1$d ontvangers %1$d online %1$d online %1$d online %1$d online %1$d online %1$d online + %1$d contacten op Telegram + %1$d contact op Telegram + %1$d contacten op Telegram + %1$d contacten op Telegram + %1$d contacten op Telegram + %1$d contacten op Telegram + Ik en nog %1$d van onze contacten gebruiken Telegram om te chatten! Download de app hier: %2$s + Ik en nog %1$d van onze contacten gebruiken Telegram om te chatten! Download de app hier: %2$s + Ik en nog %1$d van onze contacten gebruiken Telegram om te chatten! Download de app hier: %2$s + Ik en nog %1$d van onze contacten gebruiken Telegram om te chatten! Download de app hier: %2$s + Ik en nog %1$d van onze contacten gebruiken Telegram om te chatten! Download de app hier: %2$s + Ik en nog %1$d van onze contacten gebruiken Telegram om te chatten! Download de app hier: %2$s + %1$d chats + %1$d chat + %1$d chats + %1$d chats + %1$d chats + %1$d chats %1$d leden %1$d leden %1$d leden @@ -1498,25 +1654,31 @@ en %1$d meer zijn aan het typen en %1$d meer zijn aan het typen en %1$d meer zijn aan het typen - geen nieuwe berichten + %1$s en %2$d anderen zijn aan het typen + %1$s en %2$d andere zijn aan het typen + %1$s en %2$d anderen zijn aan het typen + %1$s en %2$d anderen zijn aan het typen + %1$s en %2$d anderen zijn aan het typen + %1$s en %2$d anderen zijn aan het typen + %1$d nieuwe berichten %1$d nieuw bericht %1$d nieuwe berichten %1$d nieuwe berichten %1$d nieuwe berichten %1$d nieuwe berichten - geen berichten + %1$d berichten %1$d bericht %1$d berichten %1$d berichten %1$d berichten %1$d berichten - geen items + %1$d items %1$d item %1$d items %1$d items %1$d items %1$d items - van geen chats + van %1$d chats van %1$d chat van %1$d chats van %1$d chats @@ -1588,61 +1750,55 @@ %1$d stickers %1$d stickers %1$d stickers - %1$d foto\'s - %1$d foto - %1$d foto\'s - %1$d foto\'s - %1$d foto\'s - %1$d foto\'s + %1$d abonnees + %1$d abonnee + %1$d abonnees + %1$d abonnees + %1$d abonnees + %1$d abonnees %1$d punten %1$d punt %1$d punten %1$d punten %1$d punten %1$d punten - laatst gezien %1$d min geleden - laatst gezien %1$d minuut geleden - laatst gezien %1$d min geleden - laatst gezien %1$d min geleden - laatst gezien %1$d min geleden - laatst gezien %1$d min geleden - laatst gezien %1$d uur geleden - laatst gezien %1$d uur geleden - laatst gezien %1$d uur geleden - laatst gezien %1$d uur geleden - laatst gezien %1$d uur geleden - laatst gezien %1$d uur geleden - %1$d]]> seconden - %1$d]]> seconde - %1$d]]> seconden - %1$d]]> seconden - %1$d]]> seconden - %1$d]]> seconden - %1$d]]> minuten - %1$d]]> minuut - %1$d]]> minuten - %1$d]]> minuten - %1$d]]> minuten - %1$d]]> minuten - %1$d]]> uur - %1$d]]> uur - %1$d]]> uur - %1$d]]> uur - %1$d]]> uur - %1$d]]> uur - %1$d]]> dagen - %1$d]]> dag - %1$d]]> dag - %1$d]]> dag - %1$d]]> dag - %1$d]]> dag + %1$d min geleden + %1$d min geleden + %1$d min geleden + %1$d min geleden + %1$d min geleden + %1$d min geleden + **%1$d** seconden + **%1$d** seconde + **%1$d** seconden + **%1$d** seconden + **%1$d** seconden + **%1$d** seconden + **%1$d** minuten + **%1$d** minuut + **%1$d** minuten + **%1$d** minuten + **%1$d** minuten + **%1$d** minuten + **%1$d** uur + **%1$d** uur + **%1$d** uur + **%1$d** uur + **%1$d** uur + **%1$d** uur + **%1$d** dag + **%1$d** dag + **%1$d** dag + **%1$d** dag + **%1$d** dag + **%1$d** dag - Bijlage: %1$d berichten - Bijlage: 1 bericht - Bijlage: %1$d berichten - Bijlage: %1$d berichten - Bijlage: %1$d berichten - Bijlage: %1$d berichten + Bijlage: %1$d berichten + Bijlage: 1 bericht + Bijlage: %1$d berichten + Bijlage: %1$d berichten + Bijlage: %1$d berichten + Bijlage: %1$d berichten Bijlage: %1$d bestanden Bijlage: 1 bestand Bijlage: %1$d bestanden diff --git a/TMessagesProj/src/main/res/values-pt-rBR/strings.xml b/TMessagesProj/src/main/res/values-pt-rBR/strings.xml index 8daf50da5..08744811c 100644 --- a/TMessagesProj/src/main/res/values-pt-rBR/strings.xml +++ b/TMessagesProj/src/main/res/values-pt-rBR/strings.xml @@ -1,7 +1,4 @@ - - - Telegram Telegram Beta @@ -17,10 +14,12 @@ Código do país incorreto Verificação do tel. - Enviamos um SMS com um código de ativação para ]]>%1$s]]>. - Nós enviamos o código para o aplicativo do ]]>Telegram]]> em seu outro dispositivo. - Enviamos uma ligação de ativação para ]]>%1$s]]>.\n\nNão atenda, o Telegram processará automaticamente. - Nós te ligaremos em ]]>%1$s]]> para ditar o código. + Enviamos um SMS com um código de ativação para **%1$s**. + Nós enviamos o código para o aplicativo do **Telegram** em seu outro dispositivo. + Enviamos uma ligação de ativação para **%1$s**. + +Não atenda, o Telegram processará automaticamente. + Nós te ligaremos em **%1$s** para ditar o código. Vamos te ligar em %1$d:%2$02d Vamos te enviar uma SMS em %1$d:%2$02d Estamos te ligando... @@ -28,9 +27,13 @@ Número incorreto? Não recebeu o código? Cancelar exclusão da conta - Alguém com acesso ao seu número de telefone ]]>%1$s]]> solicitou a exclusão de sua conta do Telegram e redefiniu sua senha de Verificação em Duas Etapas.\n\nSe não foi você, por favor insira o código que te enviamos via SMS. + Alguém com acesso ao seu número de telefone **%1$s** solicitou a exclusão de sua conta do Telegram e a redefinição de sua senha de Verificação em Duas Etapas. + +Se não foi você, por favor insira o código que te enviamos via SMS. Apagar conta - Uma vez que a conta ]]>%1$s]]> está ativa e protegida por senha, nós iremos desativá-la em 1 semana, por questões de segurança.\n\nVocê pode cancelar esse processo a qualquer momento. + Uma vez que a conta **%1$s** está ativa e protegida por senha, nós iremos desativá-la em 1 semana, por questões de segurança. + +Você pode cancelar esse processo a qualquer momento. Você poderá restaurar sua conta em: Suas tentativas recentes de restaurar essa conta foram canceladas pelo usuário ativo. Tente novamente em 7 dias. APAGAR @@ -69,7 +72,6 @@ Código de Segurança (CVV) MM/AA Endereço de cobrança - Titular do cartão Nome Sobrenome Salvar Dados de Pagamento Você pode salvar seus dados de pagamento para uso futuro. @@ -93,7 +95,20 @@ Desculpe, o pagamento foi recusado. Não foi possível encontrar o servidor de pagamento. Verifique sua conexão e tente novamente. Atenção - Nem o Telegram, nem %1$s terão acesso ao número do seu cartão de crédito. Os detalhes do cartão de crédito serão tratados apenas pelo sistema de pagamentos, %2$s.\n\nPagamentos irão diretamente para o desenvolvedor de %1$s. O Telegram não pode prover qualquer garantia, então proceda por sua conta e risco. Em caso de problemas, contate o desenvolvedor de %1$s ou o seu banco. + Nem o Telegram, nem %1$s terão acesso ao número do seu cartão de crédito. Os detalhes do cartão de crédito serão tratados apenas pelo sistema de pagamentos, %2$s. + +Pagamentos irão diretamente para o desenvolvedor de %1$s. O Telegram não pode prover qualquer garantia, então proceda por sua conta e risco. Em caso de problemas, contate o desenvolvedor de %1$s ou o seu banco. + Senha & E-Mail + Senha + Insira uma senha + Re-insira sua senha + Por favor, crie uma senha para proteger suas informações de pagamento. Você precisará digitá-la ao fazer login. + Recuperar E-Mail + Seu E-Mail + Por favor, adicione um e-mail válido. Essa é a única maneira de recuperar uma senha esquecida. + O telefone será passado ao %1$s como informação de faturamento. + O email será passado ao %1$s como informação de faturamento. + Telefone e Email serão passados ao %1$s como informação de faturamento. Nova conversa Configurações @@ -102,7 +117,11 @@ ontem Nenhum resultado Ainda não há chats... - Comece a conversar pressionando o\nbotão \'Nova Mensagem\' no canto inferior direito\nou vá para a seção \'Contatos\'. + Visualizado recentemente + ESCONDER + Comece a conversar pressionando o +botão \'Nova Mensagem\' no canto inferior direito +ou vá para a seção \'Contatos\'. Aguardando rede... Conectando... Conectando ao proxy... @@ -125,10 +144,12 @@ Apagar conversa Conta Excluída Selecione um Chat - Toque e segure para ver + Encaminhar para... Foto Secreta Vídeo Secreto - %1$s está usando uma versão mais antiga do Telegram, por isso fotos secretas serão mostradas em modo de compatibilidade.\n\nAssim que %2$s atualizar o Telegram, fotos com timers de 1 minuto ou menos passarão a funcionar no modo ‘Toque e segure para ver’, e você será notificado caso a outra pessoa salve a tela. + %1$s está usando uma versão mais antiga do Telegram, por isso fotos secretas serão mostradas em modo de compatibilidade. + +Assim que %2$s atualizar o Telegram, fotos com timers de 1 minuto ou menos passarão a funcionar no modo ‘Toque e segure para ver’, e você será notificado caso a outra pessoa salve a tela. MENSAGENS Busca Silenciar notificações @@ -153,16 +174,12 @@ Negrito Itálico Regular - Para se conectar com todos que você conhece, permita ao Telegram o acesso aos seus contatos. + Para se conectar com todos que você conhece, permita ao **Telegram** o acesso aos seus contatos. AGORA NÃO CONTINUAR - Público - Privado Promover a administrador Nenhum usuário banido - Você pode fornecer uma descrição opcional para seu grupo. - Sair do Grupo Apagar Grupo Sair do grupo Apagar Grupo @@ -188,6 +205,7 @@ un1 fixou um contato un1 fixou %1$s un1 fixou um mapa + un1 fixou uma localização em tempo real un1 fixou um GIF un1 fixou uma música Esse grupo foi convertido para um supergrupo @@ -235,26 +253,26 @@ Verificando nome... %1$s está disponível. Membros + Inscritos Lista Negra Usuários banidos Usuários restringidos Administradores Apagar Canal Apagar Canal - Espere! Apagando esse canal removerá todos os membros e todas as mensagens serão perdidas. Apagar assim mesmo? + Espere! Apagar esse canal removerá todos os membros e todas as mensagens serão perdidas. Apagar assim mesmo? Você tem certeza que deseja sair do canal? Você perderá todas as mensagens desse canal. Editar - Por favor, note que ao escolher um link público para o seu grupo, qualquer um poderá encontrá-lo na busca e entrar.\n\nNão crie um link se você deseja que seu supergrupo seja privado. - Por favor, note que ao escolher um link público para o seu canal, qualquer um poderá encontrá-lo na busca e entrar.\n\nNão crie um link se você deseja que seu canal seja privado. - Por favor, escolha um link para o seu canal público, assim as pessoas poderão encontrá-lo na busca e compartilhar com outros.\n\nSe não estiver interessado, sugerimos que crie um canal privado. + Por favor, escolha um link para o seu canal público, assim as pessoas poderão encontrá-lo na busca e compartilhar com outros. + +Se não estiver interessado, sugerimos que crie um canal privado. Canal criado Foto do canal alterada Foto do canal removida Nome do canal alterado para un2 Desculpe, você reservou muitos nomes públicos. Você pode remover o link público de um de seus grupos ou canais, ou criar de forma privada. Criador - Administrador SILENCIAR RESTAURAR SOM Adicionar Administrador @@ -262,10 +280,8 @@ Desbanir Toque e segure no usuário para desbanir Convidar via Link - Você tem certeza que deseja colocar %1$s como adiministrador? Descartar admin Somente os administradores do canal podem ver essa lista. - Esse usuário não entrou no seu canal ainda. Você deseja enviar um convite? Qualquer um com Telegram instalado poderá entrar no seu canal abrindo este link. Você pode adicionar administradores para ajudar você a gerenciar seu canal. Aperte e segure para removê-los. Você deseja entrar no canal \'%1$s\'? @@ -285,18 +301,6 @@ Desculp, você não pode enviar mensagens para esse canal. %1$s adicionou você ao canal %2$s Canal %1$s atualizou a foto - %1$s enviou uma mensagem ao canal %2$s - %1$s enviou uma foto para o canal %2$s - %1$s enviou um vídeo ao canal %2$s - %1$s enviou um contato ao canal %2$s - %1$s enviou uma localização ao canal %2$s - %1$s enviou um arquivo ao canal %2$s - %1$s enviou um GIF ao canal %2$s - %1$s enviou uma mensagem de voz ao canal %2$s - %1$s enviou uma mensagem de vídeo ao canal %2$s - %1$s enviou uma música ao canal %2$s - %1$s enviou um sticker ao canal %2$s - %1$s enviou um %3$s sticker ao canal %2$s %1$s postou uma mensagem %1$s postou uma foto %1$s postou um vídeo @@ -331,7 +335,7 @@ Convidar Usuários via Link Fixar Mensagens Adicionado por %1$s - Você não têm permissão para editar esse admin + Você não têm permissão para editar esse admin. Restringido por %1$s Restrições do Usuário Ler Mensagens @@ -342,21 +346,36 @@ Preview de Links Banido até Sempre - Remover Banir e remover do grupo Gerenciar Grupo Gerenciar Canal Gerenciar grupo Gerenciar canal + Histórico de conversa para novos membros + Visível + Novos membros verão mensagens que foram enviadas antes deles entrarem. + Escondido + Novos membros não verão mensagens anteriores. Ações Recentes Todas as ações Ações selecionadas Todos os Admins - ]]>Nenhuma ação ainda]]>\n\nOs membros e admins do grupo\nainda não tomaram nenhuma ação\nnas últimas 48 horas. - ]]>Nenhuma ação ainda]]>\n\nOs membros e admins do canal\nainda não tomaram nenhuma ação\nnas últimas 48 horas. - ]]>Nenhuma ação encontrada]]>\n\nNenhuma ação recente correspondente\nfoi encontrada. - Nenhuma ação recente que contenha \']]>%1$s]]>\' foi encontrada. + **Nenhuma ação ainda** + +Os membros e admins do grupo +ainda não tomaram nenhuma ação +nas últimas 48 horas. + **Nenhuma ação ainda** + +Os membros e admins do canal +ainda não tomaram nenhuma ação +nas últimas 48 horas. + **Nenhuma ação encontrada** + +Nenhuma ação recente correspondente +foi encontrada. + Nenhuma ação recente que contenha \'**%1$s**\' foi encontrada. O que são Ações Recentes? Essa é uma lista de todas as ações tomadas pelos membros e admins do grupo nas últimas 48 horas. Essa é uma lista de todas as ações tomadas pelos admins do canal nas últimas 48 horas. @@ -381,6 +400,8 @@ un1 fixou essa mensagem: un1 desafixou essa mensagem: un1 removeu essa mensagem: + un1 alterou o pacote de sticker do grupo + un1 removeu o pacote de sticker do grupo un1 alterou o link do grupo: un1 alterou o link do canal: un1 removeu o link do grupo @@ -389,11 +410,15 @@ un1 editou a descrição do grupo un1 editou a descrição do canal Descrição prévia + un1 tornou o histórico do grupo visível para novos membros + un1 tornou o histórico do grupo escondido para novos membros un1 ativou o convite ao grupo un1 desativou o convite ao grupo un1 ativou assinaturas un1 desativou assinaturas - alterou as restrições para %1$s\n\nDuração: %2$s + alterou as restrições para %1$s + +Duração: %2$s Enviar Stickers & GIFs Enviar mídia Enviar mensagem @@ -415,7 +440,6 @@ Novos membros Info do grupo Info do canal - Configurações do grupo Mensagens apagadas Mensagens editadas Mensagens fixadas @@ -431,12 +455,13 @@ Música Artista desconhecido Título desconhecido + Aleatório + Ordem reversa Selecione um Arquivo Disponível %1$s de %2$s Erro desconhecido Erro de acesso - Ainda não há arquivos Tamanho do arquivo não deve ser maior que %1$s Armazenamento não está montado Transferência USB ativa @@ -446,18 +471,29 @@ Cartão SD Pasta Para enviar imagens sem compressão + + Stickers do Grupo + Escolher de seus stickers + Você pode escolher um pacote de stickers que ficará disponível para todos do grupo enquanto estiverem conversando nesse grupo. + ESCOLHER PACOTE DE STICKER + pacote de sticker + Você pode criar seu próprio pacote de sticker usando o bot @stickers. + Nenhum pacote de sticker encontrado + Tente novamente para escolher através da lista invisível escrevendo... está escrevendo... estão escrevendo... + %1$s está escrevendo... + %1$s estão escrevendo... %1$s está gravando uma mensagem de voz... %1$s está gravando uma mensagem de vídeo... %1$s está enviando um áudio... %1$s está enviando uma foto... LEITURA RÁPIDA ABRIR GRUPO - ABRIR CANAL + VER CANAL O tema noturno será ativado automaticamente à noite %1$s está jogando... %1$s está enviando um vídeo... @@ -465,12 +501,12 @@ gravando uma mensagem de voz... gravando uma mensagem de vídeo... enviando áudio... - enviando foto... + enviando uma foto... jogando... - enviando vídeo... - enviando arquivo... - Tem alguma dúvida\nsobre o Telegram? - Tirar foto + enviando um vídeo... + enviando um arquivo... + Tem alguma dúvida +sobre o Telegram? Galeria Localização Vídeo @@ -479,6 +515,7 @@ Ainda não há mensagens aqui... Mensagem encaminhada De + de: Nada recente Mensagem Mensagem @@ -509,7 +546,6 @@ Obtendo informações... ABRIR EM... Abrir em... - Copiar URL Enviar %1$s Enviar como arquivo Enviar como Arquivos @@ -523,9 +559,6 @@ Você tem certeza que deseja reportar esse canal por spam? Desculpe, você só pode enviar mensagens para contatos mútuos no momento. Desculpe, você só pode adicionar contatos mútuos à grupos no momento. - Você contatou muitos não-contatos hoje, tente novamente amanhã. Você poderá responder hoje se esse usuário te enviar uma mensagem primeiro. - Você não pode adicionar esse usuário porque você contatou muitos não-contatos hoje. Tente novamente amanhã. Você pode pedir para outro membro adicionar este usuário ao grupo. - https://telegram.org/faq/br#no-consigo-enviar-mensagens-para-no-contatos Mais informações Enviar para... Escrever um comentário... @@ -534,6 +567,7 @@ Notificar todos os membros Desafixar Você deseja fixar essa mensagem no grupo? + Você deseja fixar essa mensagem no canal? Você deseja desafixar essa mensagem? Banir usuário Reportar spam @@ -552,8 +586,10 @@ Desculpe, o tempo para editar expirou. Adicionar atalho Buscar membros - Atalho adicionado à tela de início - chat consigo mesmo + encaminhe aqui para salvar + + Mensagens Salvas + Encaminhe aqui para salvar. Você Encaminhe mensagens aqui para salvá-las Envie mídias e arquivos aqui para salvá-los @@ -564,8 +600,8 @@ Apagar para %1$s Apagar para todos os membros Texto copiado para área de transferência - Segure para gravar um áudio. Toque para mudar para vídeo. - Segure para gravar um vídeo. Toque para mudar para áudio. + Segure para gravar áudio. Toque para vídeo. + Segure para gravar vídeo. Toque para áudio. Descartar Mensagem de Voz Você tem certeza que deseja parar de gravar e descartar sua mensagem de voz? Descartar Mensagem de Vídeo @@ -579,6 +615,7 @@ Os administradores do grupo restringiram você de usar bots integrados aqui Os administradores do grupo restringiram você de enviar stickers aqui Os administradores do grupo restringiram você de escrever aqui + admin %1$s estabeleceu o tempo de autodestruição para %2$s Você estabeleceu o tempo de autodestruição para %1$s @@ -625,7 +662,18 @@ %1$s removeu você do grupo %2$s %1$s saiu do grupo %2$s %1$s entrou para o Telegram! - %1$s,\nNós detectamos que alguém acessou a sua conta a partir de um novo aparelho em %2$s\n\nAparelho: %3$s\nLocalização: %4$s\n\nSe não foi você, você pode ir em Configurações - Privacidade e Segurança - Sessões, e terminar aquela sessão.\n\nSe você acha que alguém acessou a sua conta contra a sua vontade, você pode habilitar a verificação em duas etapas nas configurações de Privacidade e Segurança.\n\nAtenciosamente,\nEquipe Telegram + %1$s, +Nós detectamos que alguém acessou a sua conta a partir de um novo aparelho em %2$s + +Aparelho: %3$s +Localização: %4$s + +Se não foi você, você pode ir em Configurações - Privacidade e Segurança - Sessões, e terminar aquela sessão. + +Se você acha que alguém acessou a sua conta contra a sua vontade, você pode habilitar a verificação em duas etapas nas configurações de Privacidade e Segurança. + +Atenciosamente, +Equipe Telegram %1$s atualizou a foto do perfil %1$s entrou para o grupo %2$s via link de convite Responder @@ -644,11 +692,13 @@ %1$s fixou uma mensagem de vídeo no grupo %2$s %1$s fixou um contato no grupo %2$s %1$s fixou um mapa no grupo %2$s + %1$s fixou uma localização em tempo real no grupo %2$s %1$s fixou um GIF no grupo %2$s %1$s fixou uma música no grupo %2$s %1$s fixou \"%2$s\" %1$s fixou uma mensagem %1$s fixou uma foto + %1$s fixou um jogo %1$s fixou um vídeo %1$s fixou um arquivo %1$s fixou um sticker @@ -657,26 +707,31 @@ %1$s fixou uma mensagem de vídeo %1$s fixou um contato %1$s fixou um mapa + %1$s fixou uma localização em tempo real %1$s fixou um GIF %1$s fixou uma música - %1$s fixou um jogo Selecionar Contato Ainda não há contatos - Ei, vamos mudar para o Telegram: https://telegram.org/dl + Ei, estou usando o Telegram para conversar. Vem comigo! Baixe aqui: %1$s às ontem às online visto visto - visto agora mesmo Convidar Amigos + Buscar amigos BUSCA GLOBAL visto recentemente visto na última semana visto no último mês visto há muito tempo Nova Mensagem + Selecione os contatos para convidá-los ao Telegram + CONVIDAR PARA O TELEGRAM + Compartilhar Telegram... + Atualizar contatos? + O Telegram detectou muitos contatos não sincronizados, você gostaria de sincronizá-los agora? Escolha \'OK\' se você está usando seu próprio celular, cartão SIM e conta do Google. Adicionar pessoas... Você poderá adicionar mais usuários após finalizar a criação do grupo e convertê-lo em um supergrupo. @@ -684,7 +739,6 @@ Nome do grupo %1$d de %2$d selecionados até %1$s - Você deseja entrar no chat \'%1$s\'? Desculpe, este grupo já está lotado. Desculpe, esse chat não existe. Link copiado para área de transferência @@ -694,8 +748,12 @@ Este link de convite está inativo. Um novo link foi gerado. Desativar Desativar Link - Você tem certeza que deseja remover o link ]]>%1$s]]>?\n\nO grupo \"]]>%2$s]]>\" se tornará privado. - Você tem certeza que deseja remover o link ]]>%1$s]]>?\n\nO canal \"]]>%2$s]]>\" se tornará privado. + Você tem certeza que deseja remover o link **%1$s**? + +O grupo \"**%2$s**\" se tornará privado. + Você tem certeza que deseja remover o link **%1$s**? + +O canal \"**%2$s**\" se tornará privado. Copiar Link Compartilhar Link Qualquer um com Telegram instalado poderá entrar no seu grupo abrindo este link. @@ -705,8 +763,10 @@ Todos os membros podem adicionar novos membros, editar o nome e a foto do grupo. Somente administradores podem adicionar e remover membros, editar nome foto do grupo. + Membros Mídia Compartilhada Configurações + Adicionar Inscrito Adicionar membro Definir administradores BANIR DO GRUPO @@ -719,9 +779,22 @@ Converter a supergrupo Atenção Essa ação é irreversível. Não é possível voltar de um supergrupo para um grupo normal. - ]]>Limite de membros atingido.]]>\n\nPara ir além do limite e ter funções adcionais, converta para um supergrupo:\n\n• Supergrupos podem ter até %1$s\n• Novos membros veêm todo o histórico de conversas\n• Mensagens apagadas desaparecem para todos\n• Administradores podem fixar mensagens importantes\n• O criador pode definir um link público para o grupo - ]]>Em supergrupos:]]>\n\n• Novos membros podem visualizar todo o histórico de mensagens\n• Mensagens apagadas desaparecerão para todos\n• Administradores podem fixar mensagens importantes\n• O criador pode definir um link público para o grupo - ]]>Nota:]]> essa ação não pode ser desfeita. + **Limite de membros atingido.** + +Para ir além do limite e ter funções adcionais, converta para um supergrupo: + +• Supergrupos podem ter até %1$s +• Novos membros veêm todo o histórico de conversas +• Mensagens apagadas desaparecem para todos +• Administradores podem fixar mensagens importantes +• O criador pode definir um link público para o grupo + **Em supergrupos:** + +• Novos membros podem visualizar todo o histórico de mensagens +• Mensagens apagadas desaparecerão para todos +• Administradores podem fixar mensagens importantes +• O criador pode definir um link público para o grupo + **Nota:** essa ação não pode ser desfeita. Compartilhar Adicionar @@ -749,9 +822,10 @@ Se você definir um temporizador, a foto irá se auto-destruir após ser vista. Se você definir um temporizador, o vídeo irá se auto-destruir após ser visto. Desativado - Essa imagem e texto foram derivadas da chave criptográfica para esse chat secreto com ]]>%1$s]]>.\n\nSe você vê o mesmo no dispositivo de ]]>%2$s]]>, a criptografia ponta a ponta está garantida. + Essa imagem e texto foram derivadas da chave criptográfica para esse chat secreto com **%1$s**. + +Se você vê o mesmo no dispositivo de **%2$s**, a criptografia ponta a ponta está garantida. https://telegram.org/faq/br#chats-secretos - Toque para emojizar Desconhecido Info Telefone @@ -763,8 +837,11 @@ O nome de usuário deve ter pelo menos 5 caracteres. O nome de usuário não pode exceder 32 caracteres. Desculpe, o nome de usuário não pode começar com um número. - Você pode escolher um nome de usuário no ]]>Telegram]]>. Assim, outras pessoas poderão te encontrar pelo nome de usuário e entrar em contato sem precisar saber seu telefone.\n\nVocê pode usar ]]>a–z]]>, ]]>0–9]]> e underline. O tamanho mínimo é ]]>5]]> caracteres. - Esse link abre um chat com você mesmo no Telegram:\n%1$s + Você pode escolher um nome de usuário no **Telegram**. Assim, outras pessoas poderão te encontrar pelo nome de usuário e entrar em contato sem precisar saber seu telefone. + +Você pode usar **a–z**, **0–9** e underline. O tamanho mínimo é **5** caracteres. + Esse link abre um chat com você mesmo no Telegram: +%1$s Verificando nome de usuário... %1$s está disponível. Nenhum @@ -774,6 +851,13 @@ Adicionar Stickers Adicionar Máscaras Adicionar aos Stickers + Adicionar aos Favoritos + Sticker adicionado aos Favoritos + Sticker removido dos Favoritos + Recentemente Usado + Favoritos + Stickers do Grupo + Apagar dos Favoritos Adicionar Máscaras Stickers não encontrados Stickers removidos @@ -878,16 +962,17 @@ Mensagens Fixadas Idioma Personalizado - Note que o suporte do Telegram é feito por voluntários. Tentaremos responder o mais rápido possível, mas poderemos demorar um pouco. Por favor verifique a página de Perguntas Frequentes]]>: há dicas e respostas para a maioria dos problemas]]>. + Note que o suporte do Telegram é feito por voluntários. Tentaremos responder o mais rápido possível, mas poderemos demorar um pouco. + +Por favor verifique a página de Perguntas Frequentes]]>: há dicas e respostas para a maioria dos problemas]]>. Pergunte a um voluntário + Perguntas Frequentes Perguntas Frequentes https://telegram.org/faq/br Política de Privacidade https://telegram.org/privacy Apagar localização? Arquivo de localização incorreto - Ativado - Desativado Manter Serviço Ativo Reiniciar o app quando desligado pelo usuário ou pelo sistema. Isso irá garantir que o app mostre as notificações. Conexão em Segundo Plano @@ -906,6 +991,9 @@ Curta Longa Download automático de mídia + Baixar Automaticamente + Apagar Configurações de Download + Tem certeza que deseja apagar as configurações de download automático? Ao usar dados móveis Quando conectado ao Wi-Fi Quando em roaming @@ -920,19 +1008,19 @@ Prioridade Assim como em Configurações Padrão - Baixa Alta Máxima Nunca Repetir Notificações - Você pode alterar seu número do Telegram aqui. Sua conta e todos os seus dados — mensagens, mídia, contatos, etc. serão movidos para o novo número.\n\nImportante:]]> todos os contatos do Telegram terão seu novo número]]> adicionado às suas lista de contatos, desde que eles tenham seu número antigo e você não os tenha bloqueado no Telegram. + Você pode alterar seu número do Telegram aqui. Sua conta e todos os seus dados — mensagens, mídia, contatos, etc. serão movidos para o novo número. + +**Importante:** todos os contatos do Telegram terão seu **novo número** adicionado às suas lista de contatos, desde que eles tenham seu número antigo e você não os tenha bloqueado no Telegram. Todos os seus contatos do Telegram terão seu novo número adicionado às suas listas de contatos, desde que eles tenham seu antigo número e você não os tenha bloqueado no Telegram. ALTERAR NÚMERO Novo número Vamos enviar uma SMS com um código de confirmação para o seu novo número. O número %1$s já possui uma conta do Telegram. Por favor, exclua esta conta antes de migrar para o novo número. Outro - Desativado Desativado Ativado Desativado @@ -959,6 +1047,10 @@ Debug Menu Importar Contatos Recarregar Contatos + Limpar Dialogs + Ativar câmera no app + Desativar câmera no app + Restaurar Contatos Importados Você pode alterar seu idioma nas Configurações. Escolha o seu idioma Outro @@ -972,10 +1064,22 @@ Configuração de proxy SOCKS5 Usar proxy para chamadas Servidores proxy podem diminuir a qualidade de suas chamadas. + Atenção + Seu dispositivo está quase sem espaço. Para esvaziar um pouco, você pode configurar o Telegram para manter apenas mídias recentes. + Remover mídias após + Nunca remover + Contatos + Conversas Privadas + Conversas em Grupo + Canais + Limitar por Tamanho + até %1$s Banco de Dados Local Limpar todos os textos em cache? - Limpar o banco de dados local apagará todos os textos das mensagens em cache e compactará o banco de dados para economizar espaço. O Telegram precisa de alguns dados para trabalhar, então o tamanho do banco não vai chegar a zero.\n\nEssa operação pode demorar alguns minutos para ser concluída. + Limpar o banco de dados local apagará todos os textos das mensagens em cache e compactará o banco de dados para economizar espaço. O Telegram precisa de alguns dados para trabalhar, então o tamanho do banco não vai chegar a zero. + +Essa operação pode demorar alguns minutos para ser concluída. Limpar Cache Limpar Calculando... @@ -988,7 +1092,9 @@ Outros arquivos Vazio Manter Mídias - Fotos, vídeos e outros arquivos da nuvem que você não acessou]]> durante esse período serão removidos deste dispositivo para economizar espaço em disco.\n\nTodas as mídias permanecerão na nuvem do Telegram e poderão ser baixadas novamente conforme necessário. + Fotos, vídeos e outros arquivos da nuvem que você **não acessou** durante esse período serão removidos deste dispositivo para economizar espaço em disco. + +Todas as mídias permanecerão na nuvem do Telegram e poderão ser baixadas novamente conforme necessário. Permanentemente Mensagens de voz Mensagens de vídeo @@ -1005,8 +1111,9 @@ Senha de Bloqueio Alterar Senha - Quando você define uma senha adicional, um ícone de cadeado aparece na página de chats. Clique para bloquear e desbloquear o app.\n\nNota: se você esquecer a sua senha, terá de excluir e reinstalar o app. Todos os chats secretos serão perdidos. - Você verá o ícone do cadeado na página de chats. Clique para bloquear seu app do Telegram com a sua nova senha. + Quando você define uma senha adicional, um ícone de cadeado aparece na página de chats. Clique para bloquear e desbloquear o app. + +Nota: se você esquecer a sua senha, terá de excluir e reinstalar o app. Todos os chats secretos serão perdidos. PIN Senha Insira sua senha atual @@ -1014,7 +1121,6 @@ Insira sua nova senha Insira sua senha Re-insira sua nova senha - Senha inválida As senhas não são iguais Auto-bloquear Requisitar senha se estiver ausente por muito tempo. @@ -1025,7 +1131,9 @@ Toque o sensor Impressão digital não reconhecida. Permitir Captura de Tela - Se ativado, você pode tirar capturas de tela do app, mas o sistema irá mostrar suas conversas ao alternar entre janelas mesmo que a senha no app esteja habilitada.\n\nTalvez você precise reiniciar o app para que isso tenha efeito. + Se ativado, você pode tirar capturas de tela do app, mas o sistema irá mostrar suas conversas ao alternar entre janelas mesmo que a senha no app esteja habilitada. + +Talvez você precise reiniciar o app para que isso tenha efeito. Arquivos Compartilhados Mídia Compartilhada @@ -1046,19 +1154,40 @@ m de distância km de distância Enviar sua localização atual + Compartilhar Localização em Tempo Real... + Parar Compartilhamento + Tem certeza que deseja parar de compartilhar sua localização com %1$s? + Tem certeza que deseja parar de compartilhar sua localização com %1$s? + Tem certeza que deseja parar de compartilhar sua localização em tempo real? + Atualizado em tempo real enquanto você se move Enviar localização selecionada Localização + Local Precisão de %1$s OU ESCOLHA UM LUGAR + DESLIZE PARA VER LUGARES PRÓXIMOS + LOCALIZAÇÕES EM TEMPO REAL + por 15 minutos + por 1 hora + por 8 horas + atualizado + atualizado agora + Você e %1$s + %1$s em %2$s + PARAR + Você está compartilhando sua Localização em %1$s + Escolha por quanto tempo %1$s verá sua localização precisa. + Escolha por quanto tempo as pessoas nessa conversa verão sua localização precisa. + Seu GPS parece estar desabilitado, habilite-o para acessar as funções de localização. Mostrar todas as mídias + Mostrar na conversa Salvar na galeria %1$d de %2$d Galeria Todas as fotos Todas as mídias Ainda não há fotos - Nenhum vídeo ainda Baixar o vídeo primeiro Nenhuma foto recente Nenhum GIF recente @@ -1068,7 +1197,6 @@ Procurar na web Procurar GIFs Recortar imagem - Editar imagem Realçar Luzes Contraste @@ -1080,15 +1208,12 @@ Granulado Nitidez Fade - Matiz SOMBRAS LUZES - Curvas TUDO VERMELHO VERDE AZUL - Desfoque Desativado Linear Radial @@ -1097,16 +1222,11 @@ Descartar mudanças? Limpar histórico de busca? Limpar - Fotos - Vídeo Adicionar legenda... Legenda da Foto Legenda do Vídeo Legenda do GIF Legenda - Desenhar - Stickers - Texto Apagar Editar Duplicar @@ -1115,6 +1235,8 @@ Reiniciar Original Quadrado + Mostrar mídias em mensagens separadas + Mostrar mídias em uma só mensagem Verificação em Duas Etapas Configurar senha adicional @@ -1129,7 +1251,9 @@ Por favor, adicione um e-mail válido. Essa é a única forma de recuperar uma senha esquecida. Pular Atenção - É sério!\n\nSe você esquecer a sua senha, você perderá o acesso a sua conta do Telegram. Não há nenhuma forma de recuperá-la. + É sério! + +Se você esquecer a sua senha, você perderá o acesso a sua conta do Telegram. Não há nenhuma forma de recuperá-la. Quase lá! Por favor, verifique o seu e-mail (não esqueça da pasta spam) para completar a configuração da verificação em duas etapas. Pronto! @@ -1143,19 +1267,28 @@ Por favor, crie uma dica para a sua senha As senhas não são iguais Cancelar a configuração da verificação em duas etapas - Por favor, siga os seguintes passos para completar a configuração da autenticação em duas etapas:\n\n1. Verifique seu e-mail ( não esqueça da pasta spam)\n%1$s\n\n2. Clique no link de validação. + Por favor, siga os seguintes passos para completar a configuração da autenticação em duas etapas: + +1. Verifique seu e-mail ( não esqueça da pasta spam) +%1$s + +2. Clique no link de validação. A dica deve ser diferente da sua senha E-mail inválido Desculpe Como você não indicou um e-mail de recuperação quando configurou a sua senha, as únicas opções restantes são lembrar a senha ou apagar a sua conta. - O código de recuperação foi enviado para o e-mail fornecido: \n\n%1$s + O código de recuperação foi enviado para o e-mail fornecido: + +%1$s Por favor, verifique o seu e-mail e digite aqui o código de 6 dígitos recebido. Está tendo problemas para acessar seu e-mail %1$s? Se você não puder acessar o seu e-mail, as suas únicas opções são lembrar a senha ou apagar a sua conta. APAGAR MINHA CONTA Se você prosseguir e apagar a sua conta, você perderá todos os seus chats e mensagens, assim como todas as suas mídias e arquivos compartilhados. Aviso - Essa ação não pode ser desfeita.\n\nSe você apagar a sua conta, todas as suas mensagens e chats serão apagados. + Essa ação não pode ser desfeita. + +Se você apagar a sua conta, todas as suas mensagens e chats serão apagados. Apagar Senha Você habilitou a verificação em duas etapas, a sua conta está protegida com uma senha adicional. @@ -1163,7 +1296,8 @@ Recuperação de senha Código Senha desativada - Você habilitou a verificação em duas etapas. Toda vez que você entrar na sua conta em um novo aparelho, será preciso digitar a senha que você configurar aqui. + Você habilitou a verificação em duas etapas. +Toda vez que você entrar na sua conta em um novo aparelho, será preciso digitar a senha que você configurar aqui. O seu e-mail de recuperação %1$s ainda não está ativo e aguarda confirmação. Dados e Armazenamento @@ -1207,8 +1341,6 @@ Auto-destruição da conta Se você estiver inativo por Se você não ficar online ao menos uma vez nesse período, sua conta será apagada junto com seus grupos, mensagens e contatos. - Excluir sua conta? - Alterar quem pode ver o seu Último Acesso. Quem pode ver o seu Último Acesso? Adicionar exceções Importante: você não poderá ver quando foi o Último Acesso das pessoas com quem você não compartilha o seu Último Acesso. Você visualizará o Último acesso aproximado (recentemente, dentro de uma semana, dentro de um mês). @@ -1238,7 +1370,6 @@ Ponta a Ponta Desativar o ponta a ponta irá transmitir as chamadas através dos servidores do Telegram, para evitar revelar o seu endereço de IP, mas pode diminuir a qualidade do áudio. - Editar Vídeo Enviando vídeo... Enviando GIF... @@ -1255,8 +1386,6 @@ Parar bot Reiniciar bot - Próximo - Voltar Concluído Abrir Salvar @@ -1277,8 +1406,6 @@ Aplicar OK CORTAR - Sim - Não Você entrou para o grupo via link de convite un1 entrou para o grupo via link de convite @@ -1312,6 +1439,7 @@ O vídeo expirou GIF Localização + Localização em Tempo Real Contato Arquivo Sticker @@ -1337,15 +1465,17 @@ Você não possui um aplicativo que suporte o tipo de arquivo \'%1$s\', por favor instale um para continuar Este usuário ainda não possui Telegram, deseja enviar um convite? Você tem certeza? - Adcione %1$s ao chat %2$s? + Adicionar %1$s ao chat %2$s? Número de mensagens antigas para encaminhar: Adicionar %1$s no grupo? - Este usuário já está neste grupo - Encaminhar mensagem para %1$s? Enviar mensagens para %1$s? Compartilhar jogo com %1$s? Enviar contato para %1$s? - Você tem certeza que desejar sair?\n\nSaiba que você pode usar o Telegram em vários dispositivos de uma vez.\n\nLembre-se, sair apaga todos os seus Chats Secretos. + Você tem certeza que desejar sair? + +Saiba que você pode usar o Telegram em vários dispositivos de uma vez. + +Lembre-se, sair apaga todos os seus Chats Secretos. Você tem certeza que deseja terminar todas as outras sessões? Você tem certeza que apagar e sair do grupo? Você tem certeza que deseja apagar esta conversa? @@ -1356,7 +1486,7 @@ Esse bot gostaria de saber sua localização todas as vezes que você enviá-lo uma mensagem. Isso pode ser utilizado para providenciar resultados específicos de localização. Compartilhar seu número de telefone? O bot saberá seu número de telefone. Isso pode ser útil para a integração com outros serviços. - Tem certeza que deseja compartilhar seu número de telefone %1$s com ]]>%2$s]]>? + Tem certeza que deseja compartilhar seu número de telefone %1$s com **%2$s**? Tem certeza que deseja compartilhar seu número de telefone? Você tem certeza que deseja bloquear este contato? Você tem certeza que deseja desbloquear este contato? @@ -1365,18 +1495,15 @@ Você tem certeza que deseja cancelar o registro? Você tem certeza que deseja limpar o histórico? Apagar todos os textos e mídias em cache desse canal? - Apagar todos os textos e mídias em cache desse grupo? + Apagar todo o texto e mídia em cache desse grupo? Você tem certeza que deseja apagar %1$s? Enviar mensagens para %1$s? Compartilhar jogo com %1$s? Enviar contato para %1$s? - Encaminhar mensagem para %1$s? - Desculpe, esta funcionalidade não está disponível para seu país. Não há conta do Telegram com esse nome de usuário Esse bot não pode entrar em grupos. Você gostaria de ativar a pré-visualização estendida de links em Chats Secretos? Note que a pré-visualização é gerada nos servidores do Telegram. Os bots integrados são fornecidos por desenvolvedores terceiros. Para o bot funcionar, os símbolos que você digita depois do nome de usuário do bot são enviados para o respectivo desenvolvedor. - Gostaria de habilitar o \"Levantar para Falar\" para mensagens de voz? Desculpe, você não pode editar essa mensagem. Permita o acesso às SMS ao Telegram, assim podemos automaticamente adicionar o código para você. Permita o acesso às ligações ao Telegram, assim podemos automaticamente adicionar o código para você. @@ -1403,18 +1530,23 @@ Telegram Rápido - Gratuito + Grátis Seguro Poderoso - Baseado na nuvem - O aplicativo de mensagem mais rápido]]> do mundo.\nÉ gratuito]]> e seguro]]>. - O Telegram]]> envia mensagens mais rápido que qualquer outro aplicativo. - O Telegram]]> é gratuito para sempre. Sem propagandas.\nSem taxas. - O Telegram]]> mantém suas mensagens seguras\nde ataques de hackers. - O Telegram]]> não possui limites no tamanho de\nsuas mídia e conversas. - O Telegram]]> te permite acessar suas mensagens \nde múltiplos dispositivos. + Baseado em nuvem + O aplicativo de mensagem mais **rápido** do mundo. +É **gratuito** e **seguro**. + O **Telegram** envia mensagens mais rápido que qualquer outro aplicativo. + **Telegram** é grátis para sempre. +Sem anúncios. Sem taxas. + O **Telegram** mantém suas mensagens seguras +de ataques de hackers. + O **Telegram** não possui limites no tamanho de +suas mídia e conversas. + O **Telegram** te permite acessar suas mensagens +de múltiplos dispositivos. Comece a conversar - + Configurações de Conta Usar menos dados para chamadas Recebendo chamada @@ -1431,7 +1563,7 @@ Realizando chamada do Telegram Encerrar chamada Outra chamada em andamento - Você está em uma chamada com %1$s]]>. Gostaria de encerrar e iniciar uma nova chamada com %2$s]]>? + Você está em uma chamada com **%1$s**. Gostaria de encerrar e iniciar uma nova chamada com **%2$s**? Chamadas de voz Toque Você pode personalizar o toque usado quando esse contato te chamar no Telegram @@ -1455,12 +1587,10 @@ Chamada Cancelada Chamada Rejeitada %1$s (%2$s) - Essa imagem e texto foram derivadas da chave criptográfica para essa chamada com ]]>%1$s]]>.\n\nSe você vê o mesmo no dispositivo de ]]>%2$s]]>, a criptografia ponta a ponta está garantida. Você ainda não realizou chamadas. - O aplicativo de ]]>%1$s]]> está usando um protocolo incompatível. O aplicativo desse usuário precisa ser atualizado para que você possa chamá-lo. - O aplicativo de ]]>%1$s]]> não suporta chamadas. O aplicativo desse usuário precisa ser atualizado para que você possa chamá-lo. + O aplicativo de **%1$s** está usando um protocolo incompatível. O aplicativo desse usuário precisa ser atualizado para que você possa chamá-lo. + O aplicativo de **%1$s** não suporta chamadas. O aplicativo desse usuário precisa ser atualizado para que você possa chamá-lo. Avalie a qualidade de sua chamada do Telegram - Você gostaria de deixar algum feedback para nos ajudar a melhorar o serviço de chamadas? O Telegram precisa acessar seu microfone para poder fazer chamadas. Adicionar um comentário opcional Ligar de Volta @@ -1472,7 +1602,7 @@ Alto-falante Bluetooth RETORNAR À LIGAÇÃO - Desculpe, ]]>%1$s]]> não aceita chamadas. + Desculpe, **%1$s** não aceita chamadas. Se os emojis na tela de %1$s são os mesmos, essa chamada é 100%% segura. Avaliar Chamada O que aconteceu de errado? @@ -1480,12 +1610,36 @@ Isso não irá revelar o conteúdo de sua conversa, mas nos ajudará a resolver o problema. Obrigado por ajudar a tornar as chamadas do Telegram melhores. + %1$d destinatários + %1$d destinatário + %1$d destinatários + %1$d destinatários + %1$d destinatários + %1$d destinatários %1$d online %1$d online %1$d online %1$d online %1$d online %1$d online + %1$d contatos no Telegram + %1$d contato no Telegram + %1$d contatos no Telegram + %1$d contatos no Telegram + %1$d contatos no Telegram + %1$d contatos no Telegram + Ei, eu estou usando o Telegram para conversar – assim como %1$d dos nossos contatos. Junte-se a nós! Baixe aqui: %2$s + Ei, eu estou usando o Telegram para conversar – assim como %1$d dos nossos contatos. Junte-se a nós! Baixe aqui: %2$s + Ei, eu estou usando o Telegram para conversar – assim como %1$d dos nossos contatos. Junte-se a nós! Baixe aqui: %2$s + Ei, eu estou usando o Telegram para conversar – assim como %1$d dos nossos contatos. Junte-se a nós! Baixe aqui: %2$s + Ei, eu estou usando o Telegram para conversar – assim como %1$d dos nossos contatos. Junte-se a nós! Baixe aqui: %2$s + Ei, eu estou usando o Telegram para conversar – assim como %1$d dos nossos contatos. Junte-se a nós! Baixe aqui: %2$s + %1$d conversas + %1$d conversa + %1$d conversas + %1$d conversas + %1$d conversas + %1$d conversas %1$d membros %1$d membro %1$d membros @@ -1498,25 +1652,31 @@ e mais %1$d estão escrevendo e mais %1$d estão escrevendo e mais %1$d estão escrevendo - sem novas mensagens + %1$s and %2$d more are typing + %1$s e %2$d mais estão escrevendo + %1$s and %2$d more are typing + %1$s and %2$d more are typing + %1$s and %2$d more are typing + %1$s and %2$d more are typing + %1$d novas mensagens %1$d nova mensagem %1$d novas mensagens %1$d novas mensagens %1$d novas mensagens %1$d novas mensagens - sem mensagens + %1$d mensagens %1$d mensagem %1$d mensagens %1$d mensagens %1$d mensagens %1$d mensagens - nenhum item + %1$d itens %1$d item %1$d itens %1$d itens %1$d itens %1$d itens - de nenhum chat + de %1$d chats de %1$d chat de %1$d chats de %1$d chats @@ -1588,61 +1748,55 @@ %1$d stickers %1$d stickers %1$d stickers - %1$d fotos - %1$d foto - %1$d fotos - %1$d fotos - %1$d fotos - %1$d fotos - %1$d ponto + %1$d inscritos + %1$d inscrito + %1$d inscritos + %1$d inscritos + %1$d inscritos + %1$d inscritos + %1$d pontos %1$d ponto %1$d pontos %1$d pontos %1$d pontos %1$d pontos - visto há %1$d minutos - visto há %1$d minuto - visto há %1$d minutos - visto há %1$d minutos - visto há %1$d minutos - visto há %1$d minutos - visto há %1$d horas - visto há %1$d hora - visto há %1$d horas - visto há %1$d horas - visto há %1$d horas - visto há %1$d horas - %1$d]]> segundos - %1$d]]> segundo - %1$d]]> segundos - %1$d]]> segundos - %1$d]]> segundos - %1$d]]> segundos - %1$d]]> minutos - %1$d]]> minuto - %1$d]]> minutos - %1$d]]> minutos - %1$d]]> minutos - %1$d]]> minutos - %1$d]]> horas - %1$d]]> hora - %1$d]]> horas - %1$d]]> horas - %1$d]]> horas - %1$d]]> horas - %1$d]]> dias - %1$d]]> dia - %1$d]]> dias - %1$d]]> dias - %1$d]]> dias - %1$d]]> dias + atualizado há %1$d minutos + atualizado há %1$d minuto + atualizado há %1$d minutos + atualizado há %1$d minutos + atualizado há %1$d minutos + atualizado há %1$d minutos + **%1$d** segundos + **%1$d** segundo + **%1$d** segundos + **%1$d** segundos + **%1$d** segundos + **%1$d** segundos + **%1$d** minutos + **%1$d** minuto + **%1$d** minutos + **%1$d** minutos + **%1$d** minutos + **%1$d** minutos + **%1$d** horas + **%1$d** hora + **%1$d** horas + **%1$d** horas + **%1$d** horas + **%1$d** horas + **%1$d** dias + **%1$d** dia + **%1$d** dias + **%1$d** dias + **%1$d** dias + **%1$d** dias - %1$d mensagens encaminhadas - Mensagem encaminhada - %1$d mensagens encaminhadas - %1$d mensagens encaminhadas - %1$d mensagens encaminhadas - %1$d mensagens encaminhadas + %1$d mensagens encaminhadas + Mensagem encaminhada + %1$d mensagens encaminhadas + %1$d mensagens encaminhadas + %1$d mensagens encaminhadas + %1$d mensagens encaminhadas %1$d arquivos encaminhados Arquivo encaminhado %1$d arquivos encaminhados diff --git a/TMessagesProj/src/main/res/values/strings.xml b/TMessagesProj/src/main/res/values/strings.xml index 4b3f85b39..4343a5168 100644 --- a/TMessagesProj/src/main/res/values/strings.xml +++ b/TMessagesProj/src/main/res/values/strings.xml @@ -1,7 +1,4 @@ - - - Telegram Telegram Beta @@ -17,10 +14,10 @@ Wrong country code Phone verification - We\'ve sent an SMS with an activation code to your phone ]]>%1$s]]>. - We\'ve sent the code to the ]]>Telegram]]> app on your other device. - We\'ve sent an activation call to your phone ]]>%1$s]]>.\n\nDon\'t take it, Telegram will process everything automatically. - We are calling your phone ]]>%1$s]]> to dictate a code. + We\'ve sent an SMS with an activation code to your phone **%1$s**. + We\'ve sent the code to the **Telegram** app on your other device. + We\'ve sent an activation call to your phone **%1$s**.\n\nDon\'t take it, Telegram will process everything automatically. + We are calling your phone **%1$s** to dictate a code. We will call you in %1$d:%2$02d We will send you an SMS in %1$d:%2$02d Calling you... @@ -28,9 +25,9 @@ Wrong number? Didn\'t get the code? Cancel account reset - Somebody with access to your phone number ]]>%1$s]]> has requested to delete your Telegram account and reset your 2-Step Verification password.\n\nIf this wasn\'t you, please enter the code we\'ve just sent you via SMS to your number. + Somebody with access to your phone number **%1$s** has requested to delete your Telegram account and reset your 2-Step Verification password.\n\nIf this wasn\'t you, please enter the code we\'ve just sent you via SMS to your number. Reset account - Since the account ]]>%1$s]]> is active and protected by a password, we will delete it in 1 week for security purposes.\n\nYou can cancel this process at any time. + Since the account **%1$s** is active and protected by a password, we will delete it in 1 week for security purposes.\n\nYou can cancel this process at any time. You\'ll be able to reset your account in: Your recent attempts to reset this account have been cancelled by its active user. Please try again in 7 days. RESET @@ -69,7 +66,6 @@ Security Code (CVV) MM/YY Billing address - Card holder Name Surname Save Payment Information You can save your payment info for future use. @@ -94,6 +90,17 @@ Unable to reach payment server. Please check your internet connection and try again. Warning Neither Telegram, nor %1$s will have access to your credit card information. Credit card details will be handled only by the payment system, %2$s.\n\nPayments will go directly to the developer of %1$s. Telegram cannot provide any guarantees, so proceed at your own risk. In case of problems, please contact the developer of %1$s or your bank. + Password & E-Mail + Password + Enter a password + Re-enter your password + Please create a password to protect your payment info. You\'ll be asked to enter it when you log in. + Recovery E-Mail + Your E-Mail + Please add your valid e-mail. It is the only way to recover a forgotten password. + Phone will be passed to %1$s as billing info. + E-Mail will be passed to %1$s as billing info. + Phone and E-Mail will be passed to %1$s as billing info. New conversation Settings @@ -102,6 +109,8 @@ yesterday No results No chats yet... + Recently viewed + HIDE Start messaging by pressing the\nnew message button in the bottom right corner\nor tap the menu button for more options. Waiting for network... Connecting... @@ -125,7 +134,7 @@ Delete chat Deleted Account Select Chat - Tap and hold to view + Forward to... Secret Photo Secret Video %1$s is using an older version of Telegram, so secret photos will be shown in compatibility mode.\n\nOnce %2$s updates Telegram, photos with timers for 1 minute or less will start working in \'Tap and hold to view\' mode, and you will be notified whenever the other party takes a screenshot. @@ -153,16 +162,12 @@ Bold Italic Regular - To seamlessly connect with everyone you know, allow ]]>Telegram]]> access to your contacts. + To seamlessly connect with everyone you know, allow **Telegram** access to your contacts. NOT NOW CONTINUE - Public - Private Promote to admin No banned users - You can provide an optional description for your group. - Leave Group Delete Group Leave group Delete group @@ -188,6 +193,7 @@ un1 pinned a contact un1 pinned %1$s un1 pinned a map + un1 pinned a live location un1 pinned a GIF un1 pinned a track This group was upgraded to a supergroup @@ -235,6 +241,7 @@ Checking name… %1$s is available. Members + Subscribers Blacklist Banned users Restricted users @@ -245,8 +252,6 @@ Are you sure you want to leave the channel? You will lose all messages in this channel. Edit - Please note that if you choose a public link for your group, anyone will be able to find it in search and join.\n\nDo not create this link if you want your supergroup to stay private. - Please note that if you choose a public link for your channel, anyone will be able to find it in search and join.\n\nDo not create this link if you want your channel to stay private. Please choose a link for your public channel, so that people can find it in search and share with others.\n\nIf you\'re not interested, we suggest creating a private channel instead. Channel created Channel photo changed @@ -254,7 +259,6 @@ Channel name changed to un2 Sorry, you have reserved too many public usernames. You can revoke the link from one of your older groups or channels, or create a private entity instead. Creator - Administrator MUTE UNMUTE Add administrator @@ -262,10 +266,8 @@ Unban Tap and hold on user to unban. Invite via Link - Are you sure you want to make %1$s an administrator? Dismiss admin Only channel administrators can see this list. - This user hasn\'t joined the channel yet. Do you want to invite them? Anyone who has Telegram installed will be able to join your channel by following this link. You can add administrators to help you manage your channel. Tap and hold to remove admins. Do you want to join the channel \'%1$s\'? @@ -285,18 +287,6 @@ Sorry, you can\'t send messages to this channel. %1$s added you to the channel %2$s Channel %1$s updated photo - %1$s sent a message to the channel %2$s - %1$s sent a photo to the channel %2$s - %1$s sent a video to the channel %2$s - %1$s shared a contact in the channel %2$s - %1$s sent a location to the channel %2$s - %1$s sent a file to the channel %2$s - %1$s sent a GIF to the channel %2$s - %1$s sent a voice message to the channel %2$s - %1$s sent a video message to the channel %2$s - %1$s sent a track to the channel %2$s - %1$s sent a sticker to the channel %2$s - %1$s sent a %3$s sticker to the channel %2$s %1$s posted a message %1$s posted a photo %1$s posted a video @@ -342,21 +332,25 @@ Embed Links Banned until Forever - Remove Ban and remove from group Manage Group Manage Channel Manage group Manage channel + Chat history for new members + Visible + New members will see messages that were sent before they joined. + Hidden + New members won\'t see earlier messages. Recent Actions All actions Selected actions All admins - ]]>No actions here yet]]>\n\nThe group\'s members and admins\nhave not taken any service actions\nin the last 48 hours. - ]]>No actions here yet]]>\n\nThe channel\'s admins\nhave not taken any service actions\nin the last 48 hours. - ]]>No actions found]]>\n\nNo recent actions that match your query\nhave been found. - No recent actions that contain \']]>%1$s]]>\' have been found. + **No actions here yet**\n\nThe group\'s members and admins\nhave not taken any service actions\nin the last 48 hours. + **No actions here yet**\n\nThe channel\'s admins\nhave not taken any service actions\nin the last 48 hours. + **No actions found**\n\nNo recent actions that match your query\nhave been found. + No recent actions that contain \'**%1$s**\' have been found. What is the Recent Actions? This is a list of all service actions taken by the group\'s members and admins in the last 48 hours. This is a list of all service actions taken by the channel\'s admins in the last 48 hours. @@ -381,14 +375,18 @@ un1 pinned this message: un1 unpinned message un1 deleted this message: + un1 changed the group sticker set + un1 removed the group sticker set un1 changed the group link: un1 changed the channel link: - un1 removed group link - un1 removed channel link + un1 removed the group link + un1 removed the channel link Previous link un1 edited the group description: un1 edited the channel description: Previous description + un1 made group history visible for new members + un1 made group history hidden for new members un1 enabled group invites un1 disabled group invites un1 enabled signatures @@ -415,7 +413,6 @@ New members Group info Channel info - Group settings Deleted messages Edited messages Pinned messages @@ -431,12 +428,13 @@ Music Unknown artist Unknown title + Shuffle + Reverse order Select File Free %1$s of %2$s Unknown error Access error - No files yet... File size shouldn\'t be greater than %1$s Storage not mounted USB transfer active @@ -446,11 +444,22 @@ SD Card Folder To send images without compression + + Group Stickers + Choose from your stickers + You can choose a sticker set that will be available to all group members when they are chatting in this group. + CHOOSE STICKER SET + stickerset + You can create your own custom sticker set using the @stickers bot. + No such sticker set found + Try again or choose from the list below invisible typing... is typing... are typing... + %1$s is typing... + %1$s are typing... %1$s is recording a voice message... %1$s is recording a video message... %1$s is sending audio... @@ -470,7 +479,6 @@ sending video... sending file... Got a question\nabout Telegram? - Take photo Gallery Location Video @@ -479,6 +487,7 @@ No messages here yet... Forwarded message From + from: No recent Message Message @@ -509,7 +518,6 @@ Getting Link Info... OPEN IN... Open in... - Copy URL Send %1$s Send as file Send as files @@ -523,9 +531,6 @@ Are you sure you want to report spam from this channel? Sorry, you can only send messages to mutual contacts at the moment. Sorry, you can only add mutual contacts to groups at the moment. - You have contacted too many non-contacts today, please try again tomorrow. You will be able to reply today if this user messages you first. - You can\'t add this user because you have contacted too many non-contacts today. Please try again tomorrow. You can ask another member to add this user to the group. - https://telegram.org/faq#can-39t-send-messages-to-non-contacts More info Send to... Write a comment... @@ -534,6 +539,7 @@ Notify all members Unpin Do you want to pin this message in this group? + Do you want to pin this message in this channel? Do you want to unpin this message? Ban user Report spam @@ -552,8 +558,9 @@ Sorry, editing time expired. Add shortcut Search members - Shortcut added to home screen - chat with yourself + forward here to save + Saved Messages + Forward here to save. You Forward messages here to save them Send media and files to store them @@ -579,6 +586,7 @@ The admins of this group have restricted you from sending inline content here The admins of this group have restricted you from sending stickers here The admins of this group have restricted you from writing here + admin %1$s set the self-destruct timer to %2$s You set the self-destruct timer to %1$s @@ -644,11 +652,13 @@ %1$s pinned a video message in the group %2$s %1$s pinned a contact in the group %2$s %1$s pinned a map in the group %2$s + %1$s pinned a live location in the group %2$s %1$s pinned a GIF in the group %2$s %1$s pinned a track in the group %2$s %1$s pinned \"%2$s\" %1$s pinned a message %1$s pinned a photo + %1$s pinned a game %1$s pinned a video %1$s pinned a file %1$s pinned a sticker @@ -657,26 +667,31 @@ %1$s pinned a video message %1$s pinned a contact %1$s pinned a map + %1$s pinned a live location %1$s pinned a GIF %1$s pinned a track - %1$s pinned a game Select Contact No contacts yet - Hey, let\'s switch to Telegram: https://telegram.org/dl + Hey, I\'m using Telegram to chat. Join me! Download it here: %1$s at yesterday at online last seen last seen - last seen just now Invite Friends + Search friends GLOBAL SEARCH last seen recently last seen within a week last seen within a month last seen a long time ago New Message + Select contacts to invite them to Telegram + INVITE TO TELEGRAM + Share Telegram... + Update contacts? + Telegram has detected many unsynced contacts, would you like to sync them now? Choose \'OK\' if you\'re using your own device, SIM card and Google account. Add people... You will be able to add more users after you finish creating the group and convert it to a supergroup. @@ -684,7 +699,6 @@ Group name %1$d of %2$d selected up to %1$s - Do you want to join the chat \'%1$s\'? Sorry, this group is already full. Sorry, this chat does not seem to exist. Link copied to clipboard @@ -694,8 +708,8 @@ The previous invite link is now inactive. A new link has been generated. Revoke Revoke Link - Are you sure you want to revoke the link ]]>%1$s]]>?\n\nThe group \"]]>%2$s]]>\" will become private. - Are you sure you want to revoke the link ]]>%1$s]]>?\n\nThe channel \"]]>%2$s]]>\" will become private. + Are you sure you want to revoke the link **%1$s**?\n\nThe group \"**%2$s**\" will become private. + Are you sure you want to revoke the link **%1$s**?\n\nThe channel \"**%2$s**\" will become private. Copy Link Share Link Anyone who has Telegram installed will be able to join your group by following this link. @@ -705,8 +719,10 @@ All members can add new members, edit name and photo of the group. Only admins can add and remove members, edit name and photo of the group. + Members Shared Media Settings + Add subscriber Add member Set admins BAN FROM THE GROUP @@ -719,9 +735,9 @@ Convert to supergroup Warning This action is irreversible. It is not possible to downgrade a supergroup to a regular group. - ]]>Members limit reached.]]>\n\nTo go over the limit and get additional features, upgrade to a supergroup:\n\n• Supergroups can get up to %1$s\n• New members can see the full message history\n• Deleted messages will disappear for all members\n• Admins can pin important messages\n• Creator can set a public link for the group - ]]>In supergroups:]]>\n\n• New members can see the full message history\n• Deleted messages will disappear for all members\n• Admins can pin important messages\n• Creator can set a public link for the group - ]]>Note:]]> this action can\'t be undone. + **Members limit reached.**\n\nTo go over the limit and get additional features, upgrade to a supergroup:\n\n• Supergroups can get up to %1$s\n• New members can see the full message history\n• Deleted messages will disappear for all members\n• Admins can pin important messages\n• Creator can set a public link for the group + **In supergroups:**\n\n• New members can see the full message history\n• Deleted messages will disappear for all members\n• Admins can pin important messages\n• Creator can set a public link for the group + **Note:** this action can\'t be undone. Share Add @@ -749,9 +765,8 @@ If you set a timer, the photo will self-destruct after it was viewed. If you set a timer, the video will self-destruct after it was viewed. Off - This image and text were derived from the encryption key for this secret chat with ]]>%1$s]]>.\n\nIf they look the same on ]]>%2$s\'s]]> device, end-to-end encryption is guaranteed.\n\nLearn more at telegram.org + This image and text were derived from the encryption key for this secret chat with **%1$s**.\n\nIf they look the same on **%2$s\'s** device, end-to-end encryption is guaranteed.\n\nLearn more at telegram.org https://telegram.org/faq#secret-chats - Tap to emojify Unknown Info Phone @@ -763,7 +778,7 @@ A username must have at least 5 characters. The username must not exceed 32 characters. Sorry, a username can\'t start with a number. - You can choose a username on ]]>Telegram]]>. If you do, other people will be able to find you by this username and contact you without knowing your phone number.\n\nYou can use ]]>a–z]]>, ]]>0–9]]> and underscores. Minimum length is ]]>5]]> characters. + You can choose a username on **Telegram**. If you do, other people will be able to find you by this username and contact you without knowing your phone number.\n\nYou can use **a–z**, **0–9** and underscores. Minimum length is **5** characters. This link opens a chat with you on Telegram:\n%1$s Checking username... %1$s is available. @@ -774,6 +789,13 @@ Add Stickers Add Masks Add to Stickers + Add to Favorites + Sticker was added to Favorites + Sticker was removed from Favorites + Recently Used + Favorites + Group Stickers + Delete from Favorites Add to Masks Stickers not found Stickers removed @@ -880,14 +902,13 @@ Custom Please note that Telegram Support is done by volunteers. We try to respond as quickly as possible, but it may take a while.\n\nPlease take a look at the Telegram FAQ]]>: it has answers to most questions and important tips for troubleshooting]]>. Ask a volunteer + Telegram FAQ Telegram FAQ https://telegram.org/faq Privacy Policy https://telegram.org/privacy Delete localization? Incorrect localization file - Enabled - Disabled Keep-Alive Service Relaunch app when shut down by user or system. This will ensure app can show notifications. Background Connection @@ -906,6 +927,9 @@ Short Long Automatic media download + Auto-Download Media + Reset Auto-Download Settings + Are you sure you want to reset auto-download settings? When using mobile data When connected on Wi-Fi When roaming @@ -920,19 +944,17 @@ Priority Same as in Settings Default - Low High Max Never Repeat Notifications - You can change your Telegram number here. Your account and all your cloud data — messages, media, contacts, etc. will be moved to the new number.\n\nImportant:]]> all your Telegram contacts will get your new number]]> added to their address book, provided they had your old number and you haven\'t blocked them in Telegram. + You can change your Telegram number here. Your account and all your cloud data — messages, media, contacts, etc. will be moved to the new number.\n\n**Important:** all your Telegram contacts will get your **new number** added to their address book, provided they had your old number and you haven\'t blocked them in Telegram. All your Telegram contacts will get your new number added to their address book, provided they had your old number and you haven\'t blocked them in Telegram. CHANGE NUMBER New number We will send an SMS with a confirmation code to your new number. The number %1$s is already connected to a Telegram account. Please delete that account before migrating to the new number. Other - Disabled Disabled Enabled Disabled @@ -959,6 +981,10 @@ Debug Menu Import Contacts Reload Contacts + Reset Dialogs + Enable in-app camera + Disable in-app camera + Reset Imported Contacts You can change your language in the Settings later. Choose your language Other @@ -972,6 +998,16 @@ SOCKS5 proxy settings Use proxy for calls Proxy servers may degrade the quality of your calls. + Attention + Your device is almost out of disk space. To free some space, you can set up Telegram to cache only recent media. + Remove media after + Never remove + Contacts + Private Chats + Group Chats + Channels + Limit by Size + up to %1$s Local Database Clear cached text messages? @@ -988,7 +1024,7 @@ Other files Empty Keep Media - Photos, videos and other files from cloud chats that you have not accessed]]> during this period will be removed from this device to save disk space.\n\nAll media will stay in the Telegram cloud and can be re-downloaded if you need it again. + Photos, videos and other files from cloud chats that you have **not accessed** during this period will be removed from this device to save disk space.\n\nAll media will stay in the Telegram cloud and can be re-downloaded if you need it again. Forever Voice messages Video messages @@ -1006,7 +1042,6 @@ Passcode Lock Change Passcode When you set up an additional passcode, a lock icon will appear on the chats page. Tap it to lock and unlock your Telegram app.\n\nNote: if you forget the passcode, you\'ll need to delete and reinstall the app. All secret chats will be lost. - You will now see a lock icon on the chats page. Tap it to lock your Telegram app with your new passcode. PIN Password Enter your current passcode @@ -1014,7 +1049,6 @@ Enter your new passcode Enter your passcode Re-enter your new passcode - Invalid passcode Passcodes do not match Auto-lock Require passcode if away for a time. @@ -1045,20 +1079,41 @@ Hybrid m away km away - Send your current location + Send My Current Location + Share My Live Location for... + Stop Sharing Location + Are you sure you want to stop sharing live location with %1$s? + Are you sure you want to stop sharing live location with %1$s? + Are you sure you want to stop sharing your live location? + Updated in real time as you move Send selected location Location + Place Accurate to %1$s OR CHOOSE A PLACE + PULL UP TO SEE PLACES NEARBY + LIVE LOCATIONS + for 15 minutes + for 1 hour + for 8 hours + updated + updated just now + You and %1$s + %1$s is sharing with %2$s + STOP ALL + You are sharing your Live Location with %1$s + Choose for how long %1$s will see your accurate location. + Choose for how long people in this chat will see your accurate location. + Your GPS seems to be disabled, please enable it to access location-based features. Show all media + Show in chat Save to gallery %1$d of %2$d Gallery All photos All media No photos yet - No videos yet Please download media first No recent photos No recent GIFs @@ -1068,7 +1123,6 @@ Search web Search GIFs Crop image - Edit image Enhance Highlights Contrast @@ -1080,15 +1134,12 @@ Grain Sharpen Fade - Tint SHADOWS HIGHLIGHTS - Curves ALL RED GREEN BLUE - Blur Off Linear Radial @@ -1097,16 +1148,11 @@ Discard changes? Clear search history? Clear - Photos - Video Add a caption... Photo Caption Video Caption GIF Caption Caption - Draw - Stickers - Text Delete Edit Duplicate @@ -1115,6 +1161,8 @@ Reset Original Square + Show media as separate messages + Group media into one message Two-Step Verification Set Additional Password @@ -1207,8 +1255,6 @@ Account self-destructs If you\'re away for If you do not come online at least once within this period, your account will be deleted along with all groups, messages and contacts. - Delete your account? - Change who can see your Last Seen time. Who can see your Last Seen time? Add exceptions Important: you won\'t be able to see Last Seen times for people with whom you don\'t share your Last Seen time. Approximate last seen will be shown instead (recently, within a week, within a month). @@ -1238,7 +1284,6 @@ Peer-to-Peer Disabling peer-to-peer will relay all calls through Telegram servers to avoid revealing your IP address, but may slightly decrease audio quality. - Edit Video Sending video... Sending GIF... @@ -1255,8 +1300,6 @@ Stop bot Restart bot - Next - Back Done Open Save @@ -1277,8 +1320,6 @@ Set OK CROP - Yes - No You joined the group via invite link un1 joined the group via invite link @@ -1312,6 +1353,7 @@ Video has expired GIF Location + Live Location Contact File Sticker @@ -1340,8 +1382,6 @@ Add %1$s to the chat %2$s? Number of last messages to forward: Add %1$s to the group? - This user is already in this group - Forward messages to %1$s? Send messages to %1$s? Share game to %1$s? Send contact to %1$s? @@ -1356,7 +1396,7 @@ This bot would like to know your location each time you send it a request. This can be used to provide location-specific results. Share your phone number? The bot will know your phone number. This can be useful for integration with other services. - Are you sure you want to share your phone number %1$s with ]]>%2$s]]>? + Are you sure you want to share your phone number %1$s with **%2$s**? Are you sure you want to share your phone number? Are you sure you want to block this contact? Are you sure you want to unblock this contact? @@ -1365,18 +1405,15 @@ Are you sure you want to cancel registration? Are you sure you want to clear history? Delete all cached text and media from this channel? - Delete all cached text and media from this group? + Delete all cached text and media from this group? Are you sure you want to delete %1$s? Send messages to %1$s? Share game to %1$s? Send contact to %1$s? - Forward messages to %1$s? - Sorry, this feature is currently not available in your country. There is no Telegram account with this username. This bot can\'t join groups. Would you like to enable extended link previews in Secret Chats? Note that link previews are generated on Telegram servers. Please note that inline bots are provided by third-party developers. For the bot to work, the symbols you type after the bot\'s username are sent to the respective developer. - Would you like to enable "Raise to speak" for voice messages? Sorry, you can\'t edit this message. Please allow Telegram to receive SMS so that we can automatically enter your code for you. Please allow Telegram to receive calls so that we can automatically enter your code for you. @@ -1407,12 +1444,12 @@ Secure Powerful Cloud-Based - The world\'s fastest]]> messaging app.\nIt is free]]> and secure]]>. - Telegram]]> delivers messages faster than\nany other application. - Telegram]]> is free forever. No ads.\nNo subscription fees. - Telegram]]> keeps your messages safe\nfrom hacker attacks. - Telegram]]> has no limits on the size of\nyour media and chats. - Telegram]]> lets you access your messages\nfrom multiple devices. + The world\'s **fastest** messaging app.\nIt is **free** and **secure**. + **Telegram** delivers messages faster than\nany other application. + **Telegram** is free forever. No ads.\nNo subscription fees. + **Telegram** keeps your messages safe\nfrom hacker attacks. + **Telegram** has no limits on the size of\nyour media and chats. + **Telegram** lets you access your messages\nfrom multiple devices. Start Messaging Account Settings @@ -1431,7 +1468,7 @@ Ongoing Telegram call End call Another call in progress - You currently have an ongoing call with %1$s]]>. Would you like to hang up on that call and start a new one with %2$s]]>? + You currently have an ongoing call with **%1$s**. Would you like to hang up on that call and start a new one with **%2$s**? Voice calls Ringtone You can customize the ringtone used when this contact calls you on Telegram. @@ -1455,12 +1492,10 @@ Cancelled Call Declined Call %1$s (%2$s) - This image and text were derived from the encryption key for this call with ]]>%1$s]]>.\n\nIf they look the same on ]]>%2$s\'s]]> device, end-to-end encryption is guaranteed. You didn\'t make any calls yet. - ]]>%1$s]]>\'s app is using an incompatible protocol. They need to update their app before you can call them. - ]]>%1$s]]>\'s app does not support calls. They need to update their app before you can call them. + **%1$s**\'s app is using an incompatible protocol. They need to update their app before you can call them. + **%1$s**\'s app does not support calls. They need to update their app before you can call them. Please rate the quality of your Telegram call - Would you like to leave any feedback to help us improve the calling service? Telegram needs access to your microphone so that you can make calls. Add an optional comment Call Back @@ -1472,7 +1507,7 @@ Speaker Bluetooth RETURN TO CALL - Sorry, ]]>%1$s]]> doesn\'t accept calls. + Sorry, **%1$s** doesn\'t accept calls. If emoji on %1$s\'s screen are the same, this call is 100%% secure. Rate Call What went wrong? @@ -1480,12 +1515,36 @@ This won\'t reveal the contents of your conversation, but will help us fix the issue sooner. Thank you for helping make Telegram calls better. + %1$d recipients + %1$d recipient + %1$d recipients + %1$d recipients + %1$d recipients + %1$d recipients %1$d online %1$d online %1$d online %1$d online %1$d online %1$d online + %1$d contacts on Telegram + %1$d contact on Telegram + %1$d contacts on Telegram + %1$d contacts on Telegram + %1$d contacts on Telegram + %1$d contacts on Telegram + Hey, I\'m using Telegram to chat – and so are %1$d of our other contacts. Join us! Download it here: %2$s + Hey, I\'m using Telegram to chat – and so are %1$d of our other contact. Join us! Download it here: %2$s + Hey, I\'m using Telegram to chat – and so are %1$d of our other contacts. Join us! Download it here: %2$s + Hey, I\'m using Telegram to chat – and so are %1$d of our other contacts. Join us! Download it here: %2$s + Hey, I\'m using Telegram to chat – and so are %1$d of our other contacts. Join us! Download it here: %2$s + Hey, I\'m using Telegram to chat – and so are %1$d of our other contacts. Join us! Download it here: %2$s + %1$d chats + %1$d chat + %1$d chats + %1$d chats + %1$d chats + %1$d chats %1$d members %1$d member %1$d members @@ -1498,6 +1557,12 @@ and %1$d more are typing and %1$d more are typing and %1$d more are typing + %1$s and %2$d more are typing + %1$s and %2$d more are typing + %1$s and %2$d more are typing + %1$s and %2$d more are typing + %1$s and %2$d more are typing + %1$s and %2$d more are typing no new messages %1$d new message %1$d new messages @@ -1588,61 +1653,55 @@ %1$d stickers %1$d stickers %1$d stickers - %1$d photos - %1$d photo - %1$d photos - %1$d photos - %1$d photos - %1$d photos + %1$d subscribers + %1$d subscriber + %1$d subscribers + %1$d subscribers + %1$d subscribers + %1$d subscribers %1$d %1$d %1$d %1$d %1$d %1$d - last seen %1$d minutes ago - last seen %1$d minute ago - last seen %1$d minutes ago - last seen %1$d minutes ago - last seen %1$d minutes ago - last seen %1$d minutes ago - last seen %1$d hours ago - last seen %1$d hour ago - last seen %1$d hours ago - last seen %1$d hours ago - last seen %1$d hours ago - last seen %1$d hours ago - %1$d]]> seconds - %1$d]]> second - %1$d]]> seconds - %1$d]]> seconds - %1$d]]> seconds - %1$d]]> seconds - %1$d]]> minutes - %1$d]]> minute - %1$d]]> minutes - %1$d]]> minutes - %1$d]]> minutes - %1$d]]> minutes - %1$d]]> hours - %1$d]]> hour - %1$d]]> hours - %1$d]]> hours - %1$d]]> hours - %1$d]]> hours - %1$d]]> days - %1$d]]> day - %1$d]]> days - %1$d]]> days - %1$d]]> days - %1$d]]> days + updated %1$d minutes ago + updated %1$d minute ago + updated %1$d minutes ago + updated %1$d minutes ago + updated %1$d minutes ago + updated %1$d minutes ago + **%1$d** seconds + **%1$d** second + **%1$d** seconds + **%1$d** seconds + **%1$d** seconds + **%1$d** seconds + **%1$d** minutes + **%1$d** minute + **%1$d** minutes + **%1$d** minutes + **%1$d** minutes + **%1$d** minutes + **%1$d** hours + **%1$d** hour + **%1$d** hours + **%1$d** hours + **%1$d** hours + **%1$d** hours + **%1$d** days + **%1$d** day + **%1$d** days + **%1$d** days + **%1$d** days + **%1$d** days - %1$d forwarded messages - Forwarded message - %1$d forwarded messages - %1$d forwarded messages - %1$d forwarded messages - %1$d forwarded messages + %1$d forwarded messages + Forwarded message + %1$d forwarded messages + %1$d forwarded messages + %1$d forwarded messages + %1$d forwarded messages %1$d forwarded files Forwarded file %1$d forwarded files @@ -1720,4 +1779,4 @@ HH:mm h:mm a %1$s at %2$s - \ No newline at end of file + diff --git a/build.gradle b/build.gradle index 818a079d7..dee7a587f 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.2' - classpath 'com.google.gms:google-services:3.0.0' + classpath 'com.android.tools.build:gradle:3.0.1' + classpath 'com.google.gms:google-services:3.1.1' } } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0b0ba1d4f..e686b62a4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Jan 11 03:06:50 MSK 2017 +#Tue Oct 31 03:51:13 MSK 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip