Import Cobalt 19.master.0.205881
diff --git a/src/base/android/android_hardware_buffer_compat.cc b/src/base/android/android_hardware_buffer_compat.cc new file mode 100644 index 0000000..4457631 --- /dev/null +++ b/src/base/android/android_hardware_buffer_compat.cc
@@ -0,0 +1,131 @@ +// Copyright 2017 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. + +#include "base/android/android_hardware_buffer_compat.h" + +#include "base/android/build_info.h" +#include "base/lazy_instance.h" +#include "base/logging.h" + +#include <dlfcn.h> + +#include "starboard/types.h" + +namespace base { + +namespace { + +static base::LazyInstance<AndroidHardwareBufferCompat>::Leaky g_compat = + LAZY_INSTANCE_INITIALIZER; + +} // namespace + +AndroidHardwareBufferCompat::AndroidHardwareBufferCompat() { + DCHECK(IsSupportAvailable()); + + // TODO(klausw): If the Chromium build requires __ANDROID_API__ >= 26 at some + // point in the future, we could directly use the global functions instead of + // dynamic loading. However, since this would be incompatible with pre-Oreo + // devices, this is unlikely to happen in the foreseeable future, so just + // unconditionally use dynamic loading. + + // cf. base/android/linker/modern_linker_jni.cc + void* main_dl_handle = dlopen(nullptr, RTLD_NOW); + + *reinterpret_cast<void**>(&allocate_) = + dlsym(main_dl_handle, "AHardwareBuffer_allocate"); + DCHECK(allocate_); + + *reinterpret_cast<void**>(&acquire_) = + dlsym(main_dl_handle, "AHardwareBuffer_acquire"); + DCHECK(acquire_); + + *reinterpret_cast<void**>(&describe_) = + dlsym(main_dl_handle, "AHardwareBuffer_describe"); + DCHECK(describe_); + + *reinterpret_cast<void**>(&lock_) = + dlsym(main_dl_handle, "AHardwareBuffer_lock"); + DCHECK(lock_); + + *reinterpret_cast<void**>(&recv_handle_) = + dlsym(main_dl_handle, "AHardwareBuffer_recvHandleFromUnixSocket"); + DCHECK(recv_handle_); + + *reinterpret_cast<void**>(&release_) = + dlsym(main_dl_handle, "AHardwareBuffer_release"); + DCHECK(release_); + + *reinterpret_cast<void**>(&send_handle_) = + dlsym(main_dl_handle, "AHardwareBuffer_sendHandleToUnixSocket"); + DCHECK(send_handle_); + + *reinterpret_cast<void**>(&unlock_) = + dlsym(main_dl_handle, "AHardwareBuffer_unlock"); + DCHECK(unlock_); +} + +// static +bool AndroidHardwareBufferCompat::IsSupportAvailable() { + return base::android::BuildInfo::GetInstance()->sdk_int() >= + base::android::SDK_VERSION_OREO; +} + +// static +AndroidHardwareBufferCompat AndroidHardwareBufferCompat::GetInstance() { + return g_compat.Get(); +} + +void AndroidHardwareBufferCompat::Allocate(const AHardwareBuffer_Desc* desc, + AHardwareBuffer** out_buffer) { + DCHECK(IsSupportAvailable()); + allocate_(desc, out_buffer); +} + +void AndroidHardwareBufferCompat::Acquire(AHardwareBuffer* buffer) { + DCHECK(IsSupportAvailable()); + acquire_(buffer); +} + +void AndroidHardwareBufferCompat::Describe(const AHardwareBuffer* buffer, + AHardwareBuffer_Desc* out_desc) { + DCHECK(IsSupportAvailable()); + describe_(buffer, out_desc); +} + +int AndroidHardwareBufferCompat::Lock(AHardwareBuffer* buffer, + uint64_t usage, + int32_t fence, + const ARect* rect, + void** out_virtual_address) { + DCHECK(IsSupportAvailable()); + return lock_(buffer, usage, fence, rect, out_virtual_address); +} + +int AndroidHardwareBufferCompat::RecvHandleFromUnixSocket( + int socket_fd, + AHardwareBuffer** out_buffer) { + DCHECK(IsSupportAvailable()); + return recv_handle_(socket_fd, out_buffer); +} + +void AndroidHardwareBufferCompat::Release(AHardwareBuffer* buffer) { + DCHECK(IsSupportAvailable()); + release_(buffer); +} + +int AndroidHardwareBufferCompat::SendHandleToUnixSocket( + const AHardwareBuffer* buffer, + int socket_fd) { + DCHECK(IsSupportAvailable()); + return send_handle_(buffer, socket_fd); +} + +int AndroidHardwareBufferCompat::Unlock(AHardwareBuffer* buffer, + int32_t* fence) { + DCHECK(IsSupportAvailable()); + return unlock_(buffer, fence); +} + +} // namespace base
diff --git a/src/base/android/android_hardware_buffer_compat.h b/src/base/android/android_hardware_buffer_compat.h new file mode 100644 index 0000000..f447ef6 --- /dev/null +++ b/src/base/android/android_hardware_buffer_compat.h
@@ -0,0 +1,74 @@ +// Copyright 2017 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_ANDROID_ANDROID_HARDWARE_BUFFER_COMPAT_H_ +#define BASE_ANDROID_ANDROID_HARDWARE_BUFFER_COMPAT_H_ + +#include <android/hardware_buffer.h> +#include <android/sensor.h> + +#include "base/base_export.h" +#include "base/lazy_instance.h" +#include "starboard/types.h" + +extern "C" { +using PFAHardwareBuffer_allocate = void (*)(const AHardwareBuffer_Desc* desc, + AHardwareBuffer** outBuffer); +using PFAHardwareBuffer_acquire = void (*)(AHardwareBuffer* buffer); +using PFAHardwareBuffer_describe = void (*)(const AHardwareBuffer* buffer, + AHardwareBuffer_Desc* outDesc); +using PFAHardwareBuffer_lock = int (*)(AHardwareBuffer* buffer, + uint64_t usage, + int32_t fence, + const ARect* rect, + void** outVirtualAddress); +using PFAHardwareBuffer_recvHandleFromUnixSocket = + int (*)(int socketFd, AHardwareBuffer** outBuffer); +using PFAHardwareBuffer_release = void (*)(AHardwareBuffer* buffer); +using PFAHardwareBuffer_sendHandleToUnixSocket = + int (*)(const AHardwareBuffer* buffer, int socketFd); +using PFAHardwareBuffer_unlock = int (*)(AHardwareBuffer* buffer, + int32_t* fence); +} + +namespace base { + +// This class provides runtime support for working with AHardwareBuffer objects +// on Android O systems without requiring building for the Android O NDK level. +// Don't call GetInstance() unless IsSupportAvailable() returns true. +class BASE_EXPORT AndroidHardwareBufferCompat { + public: + static bool IsSupportAvailable(); + static AndroidHardwareBufferCompat GetInstance(); + + void Allocate(const AHardwareBuffer_Desc* desc, AHardwareBuffer** outBuffer); + void Acquire(AHardwareBuffer* buffer); + void Describe(const AHardwareBuffer* buffer, AHardwareBuffer_Desc* outDesc); + int Lock(AHardwareBuffer* buffer, + uint64_t usage, + int32_t fence, + const ARect* rect, + void** out_virtual_address); + int RecvHandleFromUnixSocket(int socketFd, AHardwareBuffer** outBuffer); + void Release(AHardwareBuffer* buffer); + int SendHandleToUnixSocket(const AHardwareBuffer* buffer, int socketFd); + int Unlock(AHardwareBuffer* buffer, int32_t* fence); + + private: + friend struct base::LazyInstanceTraitsBase<AndroidHardwareBufferCompat>; + AndroidHardwareBufferCompat(); + + PFAHardwareBuffer_allocate allocate_; + PFAHardwareBuffer_acquire acquire_; + PFAHardwareBuffer_describe describe_; + PFAHardwareBuffer_lock lock_; + PFAHardwareBuffer_recvHandleFromUnixSocket recv_handle_; + PFAHardwareBuffer_release release_; + PFAHardwareBuffer_sendHandleToUnixSocket send_handle_; + PFAHardwareBuffer_unlock unlock_; +}; + +} // namespace base + +#endif // BASE_ANDROID_ANDROID_HARDWARE_BUFFER_COMPAT_H_
diff --git a/src/base/android/android_image_reader_abi.h b/src/base/android/android_image_reader_abi.h new file mode 100644 index 0000000..b55582e --- /dev/null +++ b/src/base/android/android_image_reader_abi.h
@@ -0,0 +1,99 @@ +// Copyright 2018 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_ANDROID_ANDROID_IMAGE_READER_ABI_H_ +#define BASE_ANDROID_ANDROID_IMAGE_READER_ABI_H_ + +// Minimal binary interface definitions for AImage,AImageReader +// and ANativeWindow based on include/media/NdkImage.h, +// include/media/NdkImageReader.h and include/android/native_window_jni.h +// from the Android NDK for platform level 26+. This is only +// intended for use from the AndroidImageReader wrapper for building +// without NDK platform level support, it is not a general-use header +// and is not complete. Only the functions/data types which +// are currently needed by media/gpu/android/image_reader_gl_owner.h are +// included in this ABI +// +// Please refer to the API documentation for details: +// https://developer.android.com/ndk/reference/group/media (AIMage and +// AImageReader) +// https://developer.android.com/ndk/reference/group/native-activity +// (ANativeWindow) + +#include <android/native_window.h> +#include <media/NdkMediaError.h> + +#include <jni.h> + +#include "starboard/types.h" + +// Use "C" linkage to match the original header file. This isn't strictly +// required since the file is not declaring global functions, but the types +// should remain in the global namespace for compatibility, and it's a reminder +// that forward declarations elsewhere should use "extern "C" to avoid +// namespace issues. +extern "C" { + +// For AImage +typedef struct AHardwareBuffer AHardwareBuffer; + +typedef struct AImage AImage; + +enum AIMAGE_FORMATS { + AIMAGE_FORMAT_YUV_420_888 = 0x23, + AIMAGE_FORMAT_PRIVATE = 0x22 +}; + +using pAImage_delete = void (*)(AImage* image); + +using pAImage_deleteAsync = void (*)(AImage* image, int releaseFenceFd); + +using pAImage_getHardwareBuffer = media_status_t (*)(const AImage* image, + AHardwareBuffer** buffer); + +using pAImage_getWidth = media_status_t (*)(const AImage* image, + int32_t* width); + +using pAImage_getHeight = media_status_t (*)(const AImage* image, + int32_t* height); + +// For AImageReader + +typedef struct AImageReader AImageReader; + +typedef void (*AImageReader_ImageCallback)(void* context, AImageReader* reader); + +typedef struct AImageReader_ImageListener { + void* context; + AImageReader_ImageCallback onImageAvailable; +} AImageReader_ImageListener; + +using pAImageReader_newWithUsage = media_status_t (*)(int32_t width, + int32_t height, + int32_t format, + uint64_t usage, + int32_t maxImages, + AImageReader** reader); + +using pAImageReader_setImageListener = + media_status_t (*)(AImageReader* reader, + AImageReader_ImageListener* listener); + +using pAImageReader_delete = void (*)(AImageReader* reader); + +using pAImageReader_getWindow = media_status_t (*)(AImageReader* reader, + ANativeWindow** window); + +using pAImageReader_acquireLatestImageAsync = + media_status_t (*)(AImageReader* reader, + AImage** image, + int* acquireFenceFd); + +// For ANativeWindow +using pANativeWindow_toSurface = jobject (*)(JNIEnv* env, + ANativeWindow* window); + +} // extern "C" + +#endif // BASE_ANDROID_ANDROID_IMAGE_READER_ABI_H_
diff --git a/src/base/android/android_image_reader_compat.cc b/src/base/android/android_image_reader_compat.cc new file mode 100644 index 0000000..e5223c4 --- /dev/null +++ b/src/base/android/android_image_reader_compat.cc
@@ -0,0 +1,152 @@ +// Copyright 2018 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. + +#include "base/android/android_image_reader_compat.h" + +#include <dlfcn.h> + +#include "base/android/build_info.h" +#include "base/feature_list.h" +#include "base/logging.h" +#include "starboard/types.h" + +#define LOAD_FUNCTION(lib, func) \ + do { \ + func##_ = reinterpret_cast<p##func>(dlsym(lib, #func)); \ + if (!func##_) { \ + DLOG(ERROR) << "Unable to load function " << #func; \ + return false; \ + } \ + } while (0) + +namespace base { +namespace android { + +bool AndroidImageReader::disable_support_ = false; + +AndroidImageReader& AndroidImageReader::GetInstance() { + // C++11 static local variable initialization is + // thread-safe. + static base::NoDestructor<AndroidImageReader> instance; + return *instance; +} + +void AndroidImageReader::DisableSupport() { + disable_support_ = true; +} + +bool AndroidImageReader::IsSupported() { + return !disable_support_ && is_supported_; +} + +AndroidImageReader::AndroidImageReader() { + is_supported_ = LoadFunctions(); +} + +bool AndroidImageReader::LoadFunctions() { + // If the Chromium build requires __ANDROID_API__ >= 26 at some + // point in the future, we could directly use the global functions instead of + // dynamic loading. However, since this would be incompatible with pre-Oreo + // devices, this is unlikely to happen in the foreseeable future, so we use + // dynamic loading. + + // Functions are not present for android version older than OREO + if (base::android::BuildInfo::GetInstance()->sdk_int() < + base::android::SDK_VERSION_OREO) { + return false; + } + + void* libmediandk = dlopen("libmediandk.so", RTLD_NOW); + if (libmediandk == nullptr) { + LOG(ERROR) << "Couldnt open libmediandk.so"; + return false; + } + + LOAD_FUNCTION(libmediandk, AImage_delete); + LOAD_FUNCTION(libmediandk, AImage_deleteAsync); + LOAD_FUNCTION(libmediandk, AImage_getHardwareBuffer); + LOAD_FUNCTION(libmediandk, AImage_getWidth); + LOAD_FUNCTION(libmediandk, AImage_getHeight); + LOAD_FUNCTION(libmediandk, AImageReader_newWithUsage); + LOAD_FUNCTION(libmediandk, AImageReader_setImageListener); + LOAD_FUNCTION(libmediandk, AImageReader_delete); + LOAD_FUNCTION(libmediandk, AImageReader_getWindow); + LOAD_FUNCTION(libmediandk, AImageReader_acquireLatestImageAsync); + + void* libandroid = dlopen("libandroid.so", RTLD_NOW); + if (libandroid == nullptr) { + LOG(ERROR) << "Couldnt open libandroid.so"; + return false; + } + + LOAD_FUNCTION(libandroid, ANativeWindow_toSurface); + + return true; +} + +void AndroidImageReader::AImage_delete(AImage* image) { + AImage_delete_(image); +} + +void AndroidImageReader::AImage_deleteAsync(AImage* image, int releaseFenceFd) { + AImage_deleteAsync_(image, releaseFenceFd); +} + +media_status_t AndroidImageReader::AImage_getHardwareBuffer( + const AImage* image, + AHardwareBuffer** buffer) { + return AImage_getHardwareBuffer_(image, buffer); +} + +media_status_t AndroidImageReader::AImage_getWidth(const AImage* image, + int32_t* width) { + return AImage_getWidth_(image, width); +} + +media_status_t AndroidImageReader::AImage_getHeight(const AImage* image, + int32_t* height) { + return AImage_getHeight_(image, height); +} + +media_status_t AndroidImageReader::AImageReader_newWithUsage( + int32_t width, + int32_t height, + int32_t format, + uint64_t usage, + int32_t maxImages, + AImageReader** reader) { + return AImageReader_newWithUsage_(width, height, format, usage, maxImages, + reader); +} + +media_status_t AndroidImageReader::AImageReader_setImageListener( + AImageReader* reader, + AImageReader_ImageListener* listener) { + return AImageReader_setImageListener_(reader, listener); +} + +void AndroidImageReader::AImageReader_delete(AImageReader* reader) { + AImageReader_delete_(reader); +} + +media_status_t AndroidImageReader::AImageReader_getWindow( + AImageReader* reader, + ANativeWindow** window) { + return AImageReader_getWindow_(reader, window); +} + +media_status_t AndroidImageReader::AImageReader_acquireLatestImageAsync( + AImageReader* reader, + AImage** image, + int* acquireFenceFd) { + return AImageReader_acquireLatestImageAsync_(reader, image, acquireFenceFd); +} + +jobject AndroidImageReader::ANativeWindow_toSurface(JNIEnv* env, + ANativeWindow* window) { + return ANativeWindow_toSurface_(env, window); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/android_image_reader_compat.h b/src/base/android/android_image_reader_compat.h new file mode 100644 index 0000000..0731189 --- /dev/null +++ b/src/base/android/android_image_reader_compat.h
@@ -0,0 +1,84 @@ +// Copyright 2018 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_ANDROID_ANDROID_IMAGE_READER_COMPAT_H_ +#define BASE_ANDROID_ANDROID_IMAGE_READER_COMPAT_H_ + +#include "base/android/android_image_reader_abi.h" +#include "base/base_export.h" +#include "base/macros.h" +#include "base/no_destructor.h" + +namespace base { +namespace android { + +// This class provides runtime support for working with AImage, AImageReader and +// ANativeWindow objects on Android O systems without requiring building for the +// Android O NDK level. Don't call GetInstance() unless IsSupported() returns +// true. +class BASE_EXPORT AndroidImageReader { + public: + // Thread safe GetInstance. + static AndroidImageReader& GetInstance(); + + // Disable image reader support. + static void DisableSupport(); + + // Check if the image reader usage is supported. This function returns TRUE + // if android version is >=OREO, image reader support is not disabled and all + // the required functions are loaded. + bool IsSupported(); + + // Naming convention of all the below functions are chosen to exactly match + // the function names in the NDK. + void AImage_delete(AImage* image); + void AImage_deleteAsync(AImage* image, int releaseFenceFd); + media_status_t AImage_getHardwareBuffer(const AImage* image, + AHardwareBuffer** buffer); + media_status_t AImage_getWidth(const AImage* image, int32_t* width); + media_status_t AImage_getHeight(const AImage* image, int32_t* height); + media_status_t AImageReader_newWithUsage(int32_t width, + int32_t height, + int32_t format, + uint64_t usage, + int32_t maxImages, + AImageReader** reader); + media_status_t AImageReader_setImageListener( + AImageReader* reader, + AImageReader_ImageListener* listener); + void AImageReader_delete(AImageReader* reader); + media_status_t AImageReader_getWindow(AImageReader* reader, + ANativeWindow** window); + media_status_t AImageReader_acquireLatestImageAsync(AImageReader* reader, + AImage** image, + int* acquireFenceFd); + jobject ANativeWindow_toSurface(JNIEnv* env, ANativeWindow* window); + + private: + friend class base::NoDestructor<AndroidImageReader>; + + AndroidImageReader(); + bool LoadFunctions(); + + static bool disable_support_; + bool is_supported_; + pAImage_delete AImage_delete_; + pAImage_deleteAsync AImage_deleteAsync_; + pAImage_getHardwareBuffer AImage_getHardwareBuffer_; + pAImage_getWidth AImage_getWidth_; + pAImage_getHeight AImage_getHeight_; + pAImageReader_newWithUsage AImageReader_newWithUsage_; + pAImageReader_setImageListener AImageReader_setImageListener_; + pAImageReader_delete AImageReader_delete_; + pAImageReader_getWindow AImageReader_getWindow_; + pAImageReader_acquireLatestImageAsync AImageReader_acquireLatestImageAsync_; + pANativeWindow_toSurface ANativeWindow_toSurface_; + + DISALLOW_COPY_AND_ASSIGN(AndroidImageReader); +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_ANDROID_IMAGE_READER_COMPAT_H_
diff --git a/src/base/android/android_image_reader_compat_unittest.cc b/src/base/android/android_image_reader_compat_unittest.cc new file mode 100644 index 0000000..2caef9a --- /dev/null +++ b/src/base/android/android_image_reader_compat_unittest.cc
@@ -0,0 +1,43 @@ +// Copyright 2018 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. + +#include "base/android/android_image_reader_compat.h" + +#include <memory> + +#include "base/android/build_info.h" +#include "base/test/scoped_feature_list.h" +#include "starboard/types.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +class AndroidImageReaderTest : public testing::Test { + public: + AndroidImageReaderTest() = default; + ~AndroidImageReaderTest() override = default; +}; + +// Getting instance of AndroidImageReader will invoke AndroidImageReader +// constructor which will dlopen the mediandk and androidndk .so files and do +// all the required symbol lookups. +TEST_F(AndroidImageReaderTest, GetImageReaderInstance) { + // It is expected that image reader support will be available from android + // version OREO. + EXPECT_EQ(AndroidImageReader::GetInstance().IsSupported(), + base::android::BuildInfo::GetInstance()->sdk_int() >= + base::android::SDK_VERSION_OREO); +} + +// There should be only 1 instance of AndroidImageReader im memory. Hence 2 +// instances should have same memory address. +TEST_F(AndroidImageReaderTest, CompareImageReaderInstance) { + AndroidImageReader& a1 = AndroidImageReader::GetInstance(); + AndroidImageReader& a2 = AndroidImageReader::GetInstance(); + ASSERT_EQ(&a1, &a2); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/animation_frame_time_histogram.cc b/src/base/android/animation_frame_time_histogram.cc new file mode 100644 index 0000000..23dffd8 --- /dev/null +++ b/src/base/android/animation_frame_time_histogram.cc
@@ -0,0 +1,26 @@ +// Copyright 2015 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. + +#include "base/android/jni_string.h" +#include "base/metrics/histogram_macros.h" +#include "jni/AnimationFrameTimeHistogram_jni.h" + +using base::android::JavaParamRef; + +// static +void JNI_AnimationFrameTimeHistogram_SaveHistogram( + JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + const JavaParamRef<jstring>& j_histogram_name, + const JavaParamRef<jlongArray>& j_frame_times_ms, + jint j_count) { + jlong *frame_times_ms = env->GetLongArrayElements(j_frame_times_ms, NULL); + std::string histogram_name = base::android::ConvertJavaStringToUTF8( + env, j_histogram_name); + + for (int i = 0; i < j_count; ++i) { + UMA_HISTOGRAM_TIMES(histogram_name.c_str(), + base::TimeDelta::FromMilliseconds(frame_times_ms[i])); + } +}
diff --git a/src/base/android/apk_assets.cc b/src/base/android/apk_assets.cc new file mode 100644 index 0000000..5116a33 --- /dev/null +++ b/src/base/android/apk_assets.cc
@@ -0,0 +1,48 @@ +// Copyright 2015 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. + +#include <jni.h> + +#include "base/android/apk_assets.h" + +#include "base/android/jni_array.h" +#include "base/android/jni_string.h" +#include "base/android/scoped_java_ref.h" +#include "base/file_descriptor_store.h" +#include "jni/ApkAssets_jni.h" +#include "starboard/types.h" + +namespace base { +namespace android { + +int OpenApkAsset(const std::string& file_path, + base::MemoryMappedFile::Region* region) { + // The AssetManager API of the NDK does not expose a method for accessing raw + // resources :( + JNIEnv* env = base::android::AttachCurrentThread(); + ScopedJavaLocalRef<jlongArray> jarr = Java_ApkAssets_open( + env, base::android::ConvertUTF8ToJavaString(env, file_path)); + std::vector<jlong> results; + base::android::JavaLongArrayToLongVector(env, jarr.obj(), &results); + CHECK_EQ(3U, results.size()); + int fd = static_cast<int>(results[0]); + region->offset = results[1]; + region->size = results[2]; + return fd; +} + +bool RegisterApkAssetWithFileDescriptorStore(const std::string& key, + const base::FilePath& file_path) { + base::MemoryMappedFile::Region region = + base::MemoryMappedFile::Region::kWholeFile; + int asset_fd = OpenApkAsset(file_path.value(), ®ion); + if (asset_fd == -1) + return false; + base::FileDescriptorStore::GetInstance().Set(key, base::ScopedFD(asset_fd), + region); + return true; +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/apk_assets.h b/src/base/android/apk_assets.h new file mode 100644 index 0000000..cdac000 --- /dev/null +++ b/src/base/android/apk_assets.h
@@ -0,0 +1,39 @@ +// Copyright 2015 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_ANDROID_APK_ASSETS_H_ +#define BASE_ANDROID_APK_ASSETS_H_ + +#include <string> + +#include "base/android/jni_android.h" +#include "base/files/file_path.h" +#include "base/files/memory_mapped_file.h" + +namespace base { +namespace android { + +// Opens an asset (e.g. a .pak file) from the apk. +// Can be used from renderer process. +// Fails if the asset is not stored uncompressed within the .apk. +// Returns: The File Descriptor of the asset, or -1 upon failure. +// Input arguments: +// - |file_path|: Path to file within .apk. e.g.: assets/foo.pak +// Output arguments: +// - |region|: size & offset (in bytes) within the .apk of the asset. +BASE_EXPORT int OpenApkAsset( + const std::string& file_path, + base::MemoryMappedFile::Region* region); + +// Registers an uncompressed asset from within the apk in the +// FileDescriptorStore. +// Returns: true in case of success, false otherwise. +BASE_EXPORT bool RegisterApkAssetWithFileDescriptorStore( + const std::string& key, + const base::FilePath& file_path); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_APK_ASSETS_H_
diff --git a/src/base/android/application_status_listener.cc b/src/base/android/application_status_listener.cc new file mode 100644 index 0000000..621cc23 --- /dev/null +++ b/src/base/android/application_status_listener.cc
@@ -0,0 +1,103 @@ +// Copyright 2014 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. + +#include "base/android/application_status_listener.h" + +#include <jni.h> + +#include "base/lazy_instance.h" +#include "base/observer_list_threadsafe.h" +#include "jni/ApplicationStatus_jni.h" +#include "starboard/types.h" + +namespace base { +namespace android { + +namespace { + +class ApplicationStatusListenerImpl; + +struct LeakyLazyObserverListTraits + : base::internal::LeakyLazyInstanceTraits< + ObserverListThreadSafe<ApplicationStatusListenerImpl>> { + static ObserverListThreadSafe<ApplicationStatusListenerImpl>* New( + void* instance) { + ObserverListThreadSafe<ApplicationStatusListenerImpl>* ret = + base::internal::LeakyLazyInstanceTraits<ObserverListThreadSafe< + ApplicationStatusListenerImpl>>::New(instance); + // Leaky. + ret->AddRef(); + return ret; + } +}; + +LazyInstance<ObserverListThreadSafe<ApplicationStatusListenerImpl>, + LeakyLazyObserverListTraits> + g_observers = LAZY_INSTANCE_INITIALIZER; + +class ApplicationStatusListenerImpl : public ApplicationStatusListener { + public: + ApplicationStatusListenerImpl( + const ApplicationStateChangeCallback& callback) { + SetCallback(callback); + g_observers.Get().AddObserver(this); + + Java_ApplicationStatus_registerThreadSafeNativeApplicationStateListener( + AttachCurrentThread()); + } + + ~ApplicationStatusListenerImpl() override { + g_observers.Get().RemoveObserver(this); + } + + void SetCallback(const ApplicationStateChangeCallback& callback) override { + DCHECK(!callback_); + DCHECK(callback); + callback_ = callback; + } + + void Notify(ApplicationState state) override { + if (callback_) + callback_.Run(state); + } + + private: + ApplicationStateChangeCallback callback_; +}; + +} // namespace + +ApplicationStatusListener::ApplicationStatusListener() = default; +ApplicationStatusListener::~ApplicationStatusListener() = default; + +// static +std::unique_ptr<ApplicationStatusListener> ApplicationStatusListener::New( + const ApplicationStateChangeCallback& callback) { + return std::make_unique<ApplicationStatusListenerImpl>(callback); +} + +// static +void ApplicationStatusListener::NotifyApplicationStateChange( + ApplicationState state) { + TRACE_COUNTER1("browser", "ApplicationState", static_cast<int>(state)); + g_observers.Get().Notify(FROM_HERE, &ApplicationStatusListenerImpl::Notify, + state); +} + +// static +ApplicationState ApplicationStatusListener::GetState() { + return static_cast<ApplicationState>( + Java_ApplicationStatus_getStateForApplication(AttachCurrentThread())); +} + +static void JNI_ApplicationStatus_OnApplicationStateChange( + JNIEnv* env, + const JavaParamRef<jclass>& clazz, + jint new_state) { + ApplicationState application_state = static_cast<ApplicationState>(new_state); + ApplicationStatusListener::NotifyApplicationStateChange(application_state); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/application_status_listener.h b/src/base/android/application_status_listener.h new file mode 100644 index 0000000..b6b9c08 --- /dev/null +++ b/src/base/android/application_status_listener.h
@@ -0,0 +1,96 @@ +// Copyright 2014 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_ANDROID_APPLICATION_STATUS_LISTENER_H_ +#define BASE_ANDROID_APPLICATION_STATUS_LISTENER_H_ + +#include <jni.h> + +#include "base/android/jni_android.h" +#include "base/base_export.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/singleton.h" +#include "base/observer_list_threadsafe.h" +#include "starboard/types.h" + +namespace base { +namespace android { + +// Define application state values like APPLICATION_STATE_VISIBLE in a +// way that ensures they're always the same than their Java counterpart. +// +// Note that these states represent the most visible Activity state. +// If there are activities with states paused and stopped, only +// HAS_PAUSED_ACTIVITIES should be returned. +// +// A Java counterpart will be generated for this enum. +// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.base +enum ApplicationState { + APPLICATION_STATE_UNKNOWN = 0, + APPLICATION_STATE_HAS_RUNNING_ACTIVITIES = 1, + APPLICATION_STATE_HAS_PAUSED_ACTIVITIES = 2, + APPLICATION_STATE_HAS_STOPPED_ACTIVITIES = 3, + APPLICATION_STATE_HAS_DESTROYED_ACTIVITIES = 4 +}; + +// A native helper class to listen to state changes of the Android +// Application. This mirrors org.chromium.base.ApplicationStatus. +// any thread. +// +// To start listening, create a new instance, passing a callback to a +// function that takes an ApplicationState parameter. To stop listening, +// simply delete the listener object. The implementation guarantees +// that the callback will always be called on the thread that created +// the listener. +// +// Example: +// +// void OnApplicationStateChange(ApplicationState state) { +// ... +// } +// +// // Start listening. +// auto my_listener = ApplicationStatusListener::New( +// base::BindRepeating(&OnApplicationStateChange)); +// +// ... +// +// // Stop listening. +// my_listener.reset(); +// +class BASE_EXPORT ApplicationStatusListener { + public: + using ApplicationStateChangeCallback = + base::RepeatingCallback<void(ApplicationState)>; + + virtual ~ApplicationStatusListener(); + + // Sets the callback to call when application state changes. + virtual void SetCallback(const ApplicationStateChangeCallback& callback) = 0; + + // Notify observers that application state has changed. + virtual void Notify(ApplicationState state) = 0; + + // Create a new listener. This object should only be used on a single thread. + static std::unique_ptr<ApplicationStatusListener> New( + const ApplicationStateChangeCallback& callback); + + // Internal use only: must be public to be called from JNI and unit tests. + static void NotifyApplicationStateChange(ApplicationState state); + + // Expose jni call for ApplicationStatus.getStateForApplication. + static ApplicationState GetState(); + + protected: + ApplicationStatusListener(); + + private: + DISALLOW_COPY_AND_ASSIGN(ApplicationStatusListener); +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_APPLICATION_STATUS_LISTENER_H_
diff --git a/src/base/android/application_status_listener_unittest.cc b/src/base/android/application_status_listener_unittest.cc new file mode 100644 index 0000000..de5a954 --- /dev/null +++ b/src/base/android/application_status_listener_unittest.cc
@@ -0,0 +1,131 @@ +// Copyright 2014 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. + +#include "base/android/application_status_listener.h" + +#include <memory> + +#include "base/bind.h" +#include "base/callback_forward.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +namespace { + +using base::android::ScopedJavaLocalRef; + +// An invalid ApplicationState value. +const ApplicationState kInvalidApplicationState = + static_cast<ApplicationState>(100); + +// Used to generate a callback that stores the new state at a given location. +void StoreStateTo(ApplicationState* target, ApplicationState state) { + *target = state; +} + +void RunTasksUntilIdle() { + RunLoop run_loop; + run_loop.RunUntilIdle(); +} + +// Shared state for the multi-threaded test. +// This uses a thread to register for events and listen to them, while state +// changes are forced on the main thread. +class MultiThreadedTest { + public: + MultiThreadedTest() + : state_(kInvalidApplicationState), + event_(WaitableEvent::ResetPolicy::AUTOMATIC, + WaitableEvent::InitialState::NOT_SIGNALED), + thread_("ApplicationStatusTest thread"), + main_() {} + + void Run() { + // Start the thread and tell it to register for events. + thread_.Start(); + thread_.task_runner()->PostTask( + FROM_HERE, base::Bind(&MultiThreadedTest::RegisterThreadForEvents, + base::Unretained(this))); + + // Wait for its completion. + event_.Wait(); + + // Change state, then wait for the thread to modify state. + ApplicationStatusListener::NotifyApplicationStateChange( + APPLICATION_STATE_HAS_RUNNING_ACTIVITIES); + event_.Wait(); + EXPECT_EQ(APPLICATION_STATE_HAS_RUNNING_ACTIVITIES, state_); + + // Again + ApplicationStatusListener::NotifyApplicationStateChange( + APPLICATION_STATE_HAS_DESTROYED_ACTIVITIES); + event_.Wait(); + EXPECT_EQ(APPLICATION_STATE_HAS_DESTROYED_ACTIVITIES, state_); + } + + private: + void ExpectOnThread() { + EXPECT_EQ(thread_.message_loop(), base::MessageLoop::current()); + } + + void RegisterThreadForEvents() { + ExpectOnThread(); + listener_ = ApplicationStatusListener::New(base::BindRepeating( + &MultiThreadedTest::StoreStateAndSignal, base::Unretained(this))); + EXPECT_TRUE(listener_.get()); + event_.Signal(); + } + + void StoreStateAndSignal(ApplicationState state) { + ExpectOnThread(); + state_ = state; + event_.Signal(); + } + + ApplicationState state_; + base::WaitableEvent event_; + base::Thread thread_; + base::MessageLoop main_; + std::unique_ptr<ApplicationStatusListener> listener_; +}; + +} // namespace + +TEST(ApplicationStatusListenerTest, SingleThread) { + MessageLoop message_loop; + + ApplicationState result = kInvalidApplicationState; + + // Create a new listener that stores the new state into |result| on every + // state change. + auto listener = ApplicationStatusListener::New( + base::Bind(&StoreStateTo, base::Unretained(&result))); + + EXPECT_EQ(kInvalidApplicationState, result); + + ApplicationStatusListener::NotifyApplicationStateChange( + APPLICATION_STATE_HAS_RUNNING_ACTIVITIES); + RunTasksUntilIdle(); + EXPECT_EQ(APPLICATION_STATE_HAS_RUNNING_ACTIVITIES, result); + + ApplicationStatusListener::NotifyApplicationStateChange( + APPLICATION_STATE_HAS_DESTROYED_ACTIVITIES); + RunTasksUntilIdle(); + EXPECT_EQ(APPLICATION_STATE_HAS_DESTROYED_ACTIVITIES, result); +} + +TEST(ApplicationStatusListenerTest, TwoThreads) { + MultiThreadedTest test; + test.Run(); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/base_jni_onload.cc b/src/base/android/base_jni_onload.cc new file mode 100644 index 0000000..170dd84 --- /dev/null +++ b/src/base/android/base_jni_onload.cc
@@ -0,0 +1,24 @@ +// Copyright 2015 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. + +#include "base/android/base_jni_onload.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_utils.h" +#include "base/android/library_loader/library_loader_hooks.h" +#include "base/bind.h" + +namespace base { +namespace android { + +bool OnJNIOnLoadInit() { + InitAtExitManager(); + JNIEnv* env = base::android::AttachCurrentThread(); + base::android::InitReplacementClassLoader(env, + base::android::GetClassLoader(env)); + return true; +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/base_jni_onload.h b/src/base/android/base_jni_onload.h new file mode 100644 index 0000000..cf634a3 --- /dev/null +++ b/src/base/android/base_jni_onload.h
@@ -0,0 +1,24 @@ +// Copyright 2015 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_ANDROID_BASE_JNI_ONLOAD_H_ +#define BASE_ANDROID_BASE_JNI_ONLOAD_H_ + +#include <jni.h> +#include <vector> + +#include "base/base_export.h" +#include "base/callback.h" +#include "starboard/types.h" + +namespace base { +namespace android { + +// Returns whether initialization succeeded. +BASE_EXPORT bool OnJNIOnLoadInit(); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_BASE_JNI_ONLOAD_H_
diff --git a/src/base/android/build_info.cc b/src/base/android/build_info.cc new file mode 100644 index 0000000..bebf901 --- /dev/null +++ b/src/base/android/build_info.cc
@@ -0,0 +1,87 @@ +// Copyright (c) 2012 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. + +#include "base/android/build_info.h" + +#include <string> + +#include "base/android/jni_android.h" +#include "base/android/jni_array.h" +#include "base/android/scoped_java_ref.h" +#include "base/logging.h" +#include "base/memory/singleton.h" +#include "base/strings/string_number_conversions.h" +#include "jni/BuildInfo_jni.h" + +namespace base { +namespace android { + +namespace { + +// We are leaking these strings. +const char* StrDupParam(const std::vector<std::string>& params, int index) { + return strdup(params[index].c_str()); +} + +int GetIntParam(const std::vector<std::string>& params, int index) { + int ret = 0; + bool success = StringToInt(params[index], &ret); + DCHECK(success); + return ret; +} + +} // namespace + +struct BuildInfoSingletonTraits { + static BuildInfo* New() { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobjectArray> params_objs = Java_BuildInfo_getAll(env); + std::vector<std::string> params; + AppendJavaStringArrayToStringVector(env, params_objs.obj(), ¶ms); + return new BuildInfo(params); + } + + static void Delete(BuildInfo* x) { + // We're leaking this type, see kRegisterAtExit. + NOTREACHED(); + } + + static const bool kRegisterAtExit = false; +#if DCHECK_IS_ON() + static const bool kAllowedToAccessOnNonjoinableThread = true; +#endif +}; + +BuildInfo::BuildInfo(const std::vector<std::string>& params) + : brand_(StrDupParam(params, 0)), + device_(StrDupParam(params, 1)), + android_build_id_(StrDupParam(params, 2)), + manufacturer_(StrDupParam(params, 3)), + model_(StrDupParam(params, 4)), + sdk_int_(GetIntParam(params, 5)), + build_type_(StrDupParam(params, 6)), + board_(StrDupParam(params, 7)), + host_package_name_(StrDupParam(params, 8)), + host_version_code_(StrDupParam(params, 9)), + host_package_label_(StrDupParam(params, 10)), + package_name_(StrDupParam(params, 11)), + package_version_code_(StrDupParam(params, 12)), + package_version_name_(StrDupParam(params, 13)), + android_build_fp_(StrDupParam(params, 14)), + gms_version_code_(StrDupParam(params, 15)), + installer_package_name_(StrDupParam(params, 16)), + abi_name_(StrDupParam(params, 17)), + firebase_app_id_(StrDupParam(params, 18)), + custom_themes_(StrDupParam(params, 19)), + resources_version_(StrDupParam(params, 20)), + extracted_file_suffix_(params[21]), + is_at_least_p_(GetIntParam(params, 22)) {} + +// static +BuildInfo* BuildInfo::GetInstance() { + return Singleton<BuildInfo, BuildInfoSingletonTraits >::get(); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/build_info.h b/src/base/android/build_info.h new file mode 100644 index 0000000..7c31337 --- /dev/null +++ b/src/base/android/build_info.h
@@ -0,0 +1,166 @@ +// Copyright (c) 2012 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_ANDROID_BUILD_INFO_H_ +#define BASE_ANDROID_BUILD_INFO_H_ + +#include <jni.h> + +#include <string> +#include <vector> + +#include "base/base_export.h" +#include "base/macros.h" +#include "base/memory/singleton.h" +#include "starboard/types.h" + +namespace base { +namespace android { + +// This enumeration maps to the values returned by BuildInfo::sdk_int(), +// indicating the Android release associated with a given SDK version. +enum SdkVersion { + SDK_VERSION_JELLY_BEAN = 16, + SDK_VERSION_JELLY_BEAN_MR1 = 17, + SDK_VERSION_JELLY_BEAN_MR2 = 18, + SDK_VERSION_KITKAT = 19, + SDK_VERSION_KITKAT_WEAR = 20, + SDK_VERSION_LOLLIPOP = 21, + SDK_VERSION_LOLLIPOP_MR1 = 22, + SDK_VERSION_MARSHMALLOW = 23, + SDK_VERSION_NOUGAT = 24, + SDK_VERSION_NOUGAT_MR1 = 25, + SDK_VERSION_OREO = 26, +}; + +// BuildInfo is a singleton class that stores android build and device +// information. It will be called from Android specific code and gets used +// primarily in crash reporting. +class BASE_EXPORT BuildInfo { + public: + + ~BuildInfo() {} + + // Static factory method for getting the singleton BuildInfo instance. + // Note that ownership is not conferred on the caller and the BuildInfo in + // question isn't actually freed until shutdown. This is ok because there + // should only be one instance of BuildInfo ever created. + static BuildInfo* GetInstance(); + + // Const char* is used instead of std::strings because these values must be + // available even if the process is in a crash state. Sadly + // std::string.c_str() doesn't guarantee that memory won't be allocated when + // it is called. + const char* device() const { + return device_; + } + + const char* manufacturer() const { + return manufacturer_; + } + + const char* model() const { + return model_; + } + + const char* brand() const { + return brand_; + } + + const char* android_build_id() const { + return android_build_id_; + } + + const char* android_build_fp() const { + return android_build_fp_; + } + + const char* gms_version_code() const { + return gms_version_code_; + } + + const char* host_package_name() const { return host_package_name_; } + + const char* host_version_code() const { return host_version_code_; } + + const char* host_package_label() const { return host_package_label_; } + + const char* package_version_code() const { + return package_version_code_; + } + + const char* package_version_name() const { + return package_version_name_; + } + + const char* package_name() const { + return package_name_; + } + + // Will be empty string if no app id is assigned. + const char* firebase_app_id() const { return firebase_app_id_; } + + const char* custom_themes() const { return custom_themes_; } + + const char* resources_version() const { return resources_version_; } + + const char* build_type() const { + return build_type_; + } + + const char* board() const { return board_; } + + const char* installer_package_name() const { return installer_package_name_; } + + const char* abi_name() const { return abi_name_; } + + std::string extracted_file_suffix() const { return extracted_file_suffix_; } + + int sdk_int() const { + return sdk_int_; + } + + bool is_at_least_p() const { return is_at_least_p_; } + + private: + friend struct BuildInfoSingletonTraits; + + explicit BuildInfo(const std::vector<std::string>& params); + + // Const char* is used instead of std::strings because these values must be + // available even if the process is in a crash state. Sadly + // std::string.c_str() doesn't guarantee that memory won't be allocated when + // it is called. + const char* const brand_; + const char* const device_; + const char* const android_build_id_; + const char* const manufacturer_; + const char* const model_; + const int sdk_int_; + const char* const build_type_; + const char* const board_; + const char* const host_package_name_; + const char* const host_version_code_; + const char* const host_package_label_; + const char* const package_name_; + const char* const package_version_code_; + const char* const package_version_name_; + const char* const android_build_fp_; + const char* const gms_version_code_; + const char* const installer_package_name_; + const char* const abi_name_; + const char* const firebase_app_id_; + const char* const custom_themes_; + const char* const resources_version_; + // Not needed by breakpad. + const std::string extracted_file_suffix_; + const int is_at_least_p_; + + DISALLOW_COPY_AND_ASSIGN(BuildInfo); +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_BUILD_INFO_H_
diff --git a/src/base/android/callback_android.cc b/src/base/android/callback_android.cc new file mode 100644 index 0000000..7143664 --- /dev/null +++ b/src/base/android/callback_android.cc
@@ -0,0 +1,44 @@ +// Copyright 2016 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. + +#include "base/android/callback_android.h" + +#include "base/android/jni_array.h" +#include "base/android/jni_string.h" +#include "base/android/scoped_java_ref.h" +#include "jni/Callback_jni.h" + +namespace base { +namespace android { + +void RunObjectCallbackAndroid(const JavaRef<jobject>& callback, + const JavaRef<jobject>& arg) { + Java_Helper_onObjectResultFromNative(AttachCurrentThread(), callback, arg); +} + +void RunBooleanCallbackAndroid(const JavaRef<jobject>& callback, bool arg) { + Java_Helper_onBooleanResultFromNative(AttachCurrentThread(), callback, + static_cast<jboolean>(arg)); +} + +void RunIntCallbackAndroid(const JavaRef<jobject>& callback, int arg) { + Java_Helper_onIntResultFromNative(AttachCurrentThread(), callback, arg); +} + +void RunStringCallbackAndroid(const JavaRef<jobject>& callback, + const std::string& arg) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> java_string = ConvertUTF8ToJavaString(env, arg); + Java_Helper_onObjectResultFromNative(env, callback, java_string); +} + +void RunByteArrayCallbackAndroid(const JavaRef<jobject>& callback, + const std::vector<uint8_t>& arg) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jbyteArray> j_bytes = ToJavaByteArray(env, arg); + Java_Helper_onObjectResultFromNative(env, callback, j_bytes); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/callback_android.h b/src/base/android/callback_android.h new file mode 100644 index 0000000..f0e9a47 --- /dev/null +++ b/src/base/android/callback_android.h
@@ -0,0 +1,39 @@ +// Copyright 2016 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_ANDROID_CALLBACK_ANDROID_H_ +#define BASE_ANDROID_CALLBACK_ANDROID_H_ + +#include <jni.h> +#include <string> +#include <vector> + +#include "base/android/scoped_java_ref.h" +#include "base/base_export.h" +#include "starboard/types.h" + +// Provides helper utility methods that run the given callback with the +// specified argument. +namespace base { +namespace android { + +void BASE_EXPORT RunObjectCallbackAndroid(const JavaRef<jobject>& callback, + const JavaRef<jobject>& arg); + +void BASE_EXPORT RunBooleanCallbackAndroid(const JavaRef<jobject>& callback, + bool arg); + +void BASE_EXPORT RunIntCallbackAndroid(const JavaRef<jobject>& callback, + int arg); + +void BASE_EXPORT RunStringCallbackAndroid(const JavaRef<jobject>& callback, + const std::string& arg); + +void BASE_EXPORT RunByteArrayCallbackAndroid(const JavaRef<jobject>& callback, + const std::vector<uint8_t>& arg); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_CALLBACK_ANDROID_H_
diff --git a/src/base/android/child_process_binding_types.h b/src/base/android/child_process_binding_types.h new file mode 100644 index 0000000..a3900d5 --- /dev/null +++ b/src/base/android/child_process_binding_types.h
@@ -0,0 +1,25 @@ +// Copyright 2018 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_ANDROID_CHILD_PROCESS_BINDING_TYPES_H_ +#define BASE_ANDROID_CHILD_PROCESS_BINDING_TYPES_H_ + +namespace base { +namespace android { + +// Defines the state of bindgings with child process. See ChildProcessConnection +// to see what the bindings are. Note these values are used as array indices. +// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.base +enum class ChildBindingState { + UNBOUND, + WAIVED, + MODERATE, + STRONG, + MAX_VALUE = STRONG +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_CHILD_PROCESS_BINDING_TYPES_H_
diff --git a/src/base/android/child_process_service.cc b/src/base/android/child_process_service.cc new file mode 100644 index 0000000..013a70b --- /dev/null +++ b/src/base/android/child_process_service.cc
@@ -0,0 +1,79 @@ +// Copyright 2017 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. + +#include "base/android/jni_array.h" +#include "base/android/jni_string.h" +#include "base/android/library_loader/library_loader_hooks.h" +#include "base/file_descriptor_store.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/optional.h" +#include "base/posix/global_descriptors.h" +#include "jni/ChildProcessService_jni.h" + +using base::android::JavaIntArrayToIntVector; +using base::android::JavaParamRef; + +namespace base { +namespace android { + +void JNI_ChildProcessService_RegisterFileDescriptors( + JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jobjectArray>& j_keys, + const JavaParamRef<jintArray>& j_ids, + const JavaParamRef<jintArray>& j_fds, + const JavaParamRef<jlongArray>& j_offsets, + const JavaParamRef<jlongArray>& j_sizes) { + std::vector<base::Optional<std::string>> keys; + jsize keys_size = env->GetArrayLength(j_keys); + keys.reserve(keys_size); + for (jsize i = 0; i < keys_size; i++) { + base::android::ScopedJavaLocalRef<jstring> str( + env, static_cast<jstring>(env->GetObjectArrayElement(j_keys, i))); + base::Optional<std::string> key; + if (!str.is_null()) { + key = base::android::ConvertJavaStringToUTF8(env, str); + } + keys.push_back(std::move(key)); + } + + std::vector<int> ids; + base::android::JavaIntArrayToIntVector(env, j_ids, &ids); + std::vector<int> fds; + base::android::JavaIntArrayToIntVector(env, j_fds, &fds); + std::vector<int64_t> offsets; + base::android::JavaLongArrayToInt64Vector(env, j_offsets, &offsets); + std::vector<int64_t> sizes; + base::android::JavaLongArrayToInt64Vector(env, j_sizes, &sizes); + + DCHECK_EQ(keys.size(), ids.size()); + DCHECK_EQ(ids.size(), fds.size()); + DCHECK_EQ(fds.size(), offsets.size()); + DCHECK_EQ(offsets.size(), sizes.size()); + + for (size_t i = 0; i < ids.size(); i++) { + base::MemoryMappedFile::Region region = {offsets.at(i), sizes.at(i)}; + const base::Optional<std::string>& key = keys.at(i); + int id = ids.at(i); + int fd = fds.at(i); + if (key) { + base::FileDescriptorStore::GetInstance().Set(*key, base::ScopedFD(fd), + region); + } else { + base::GlobalDescriptors::GetInstance()->Set(id, fd, region); + } + } +} + +void JNI_ChildProcessService_ExitChildProcess( + JNIEnv* env, + const JavaParamRef<jclass>& clazz) { + VLOG(0) << "ChildProcessService: Exiting child process."; + base::android::LibraryLoaderExitHook(); + _exit(0); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/command_line_android.cc b/src/base/android/command_line_android.cc new file mode 100644 index 0000000..c9b545f --- /dev/null +++ b/src/base/android/command_line_android.cc
@@ -0,0 +1,89 @@ +// Copyright 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. + +#include "base/android/jni_array.h" +#include "base/android/jni_string.h" +#include "base/command_line.h" +#include "base/logging.h" +#include "jni/CommandLine_jni.h" + +using base::android::ConvertUTF8ToJavaString; +using base::android::ConvertJavaStringToUTF8; +using base::android::JavaParamRef; +using base::android::ScopedJavaLocalRef; +using base::CommandLine; + +namespace { + +void JNI_CommandLine_AppendJavaStringArrayToCommandLine( + JNIEnv* env, + const JavaParamRef<jobjectArray>& array, + bool includes_program) { + std::vector<std::string> vec; + if (array) + base::android::AppendJavaStringArrayToStringVector(env, array, &vec); + if (!includes_program) + vec.insert(vec.begin(), std::string()); + CommandLine extra_command_line(vec); + CommandLine::ForCurrentProcess()->AppendArguments(extra_command_line, + includes_program); +} + +} // namespace + +static jboolean JNI_CommandLine_HasSwitch( + JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jstring>& jswitch) { + std::string switch_string(ConvertJavaStringToUTF8(env, jswitch)); + return CommandLine::ForCurrentProcess()->HasSwitch(switch_string); +} + +static ScopedJavaLocalRef<jstring> JNI_CommandLine_GetSwitchValue( + JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jstring>& jswitch) { + std::string switch_string(ConvertJavaStringToUTF8(env, jswitch)); + std::string value(CommandLine::ForCurrentProcess()->GetSwitchValueNative( + switch_string)); + if (value.empty()) + return ScopedJavaLocalRef<jstring>(); + return ConvertUTF8ToJavaString(env, value); +} + +static void JNI_CommandLine_AppendSwitch(JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jstring>& jswitch) { + std::string switch_string(ConvertJavaStringToUTF8(env, jswitch)); + CommandLine::ForCurrentProcess()->AppendSwitch(switch_string); +} + +static void JNI_CommandLine_AppendSwitchWithValue( + JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jstring>& jswitch, + const JavaParamRef<jstring>& jvalue) { + std::string switch_string(ConvertJavaStringToUTF8(env, jswitch)); + std::string value_string (ConvertJavaStringToUTF8(env, jvalue)); + CommandLine::ForCurrentProcess()->AppendSwitchASCII(switch_string, + value_string); +} + +static void JNI_CommandLine_AppendSwitchesAndArguments( + JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jobjectArray>& array) { + JNI_CommandLine_AppendJavaStringArrayToCommandLine(env, array, false); +} + +static void JNI_CommandLine_Init( + JNIEnv* env, + const JavaParamRef<jclass>& jclazz, + const JavaParamRef<jobjectArray>& init_command_line) { + // TODO(port): Make an overload of Init() that takes StringVector rather than + // have to round-trip via AppendArguments. + CommandLine::Init(0, nullptr); + JNI_CommandLine_AppendJavaStringArrayToCommandLine(env, init_command_line, + true); +}
diff --git a/src/base/android/content_uri_utils.cc b/src/base/android/content_uri_utils.cc new file mode 100644 index 0000000..c8553c4 --- /dev/null +++ b/src/base/android/content_uri_utils.cc
@@ -0,0 +1,63 @@ +// Copyright 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. + +#include "base/android/content_uri_utils.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "jni/ContentUriUtils_jni.h" + +using base::android::ConvertUTF8ToJavaString; +using base::android::ScopedJavaLocalRef; + +namespace base { + +bool ContentUriExists(const FilePath& content_uri) { + JNIEnv* env = base::android::AttachCurrentThread(); + ScopedJavaLocalRef<jstring> j_uri = + ConvertUTF8ToJavaString(env, content_uri.value()); + return Java_ContentUriUtils_contentUriExists(env, j_uri); +} + +File OpenContentUriForRead(const FilePath& content_uri) { + JNIEnv* env = base::android::AttachCurrentThread(); + ScopedJavaLocalRef<jstring> j_uri = + ConvertUTF8ToJavaString(env, content_uri.value()); + jint fd = Java_ContentUriUtils_openContentUriForRead(env, j_uri); + if (fd < 0) + return File(); + return File(fd); +} + +std::string GetContentUriMimeType(const FilePath& content_uri) { + JNIEnv* env = base::android::AttachCurrentThread(); + ScopedJavaLocalRef<jstring> j_uri = + ConvertUTF8ToJavaString(env, content_uri.value()); + ScopedJavaLocalRef<jstring> j_mime = + Java_ContentUriUtils_getMimeType(env, j_uri); + if (j_mime.is_null()) + return std::string(); + + return base::android::ConvertJavaStringToUTF8(env, j_mime.obj()); +} + +bool MaybeGetFileDisplayName(const FilePath& content_uri, + base::string16* file_display_name) { + DCHECK(content_uri.IsContentUri()); + DCHECK(file_display_name); + + JNIEnv* env = base::android::AttachCurrentThread(); + ScopedJavaLocalRef<jstring> j_uri = + ConvertUTF8ToJavaString(env, content_uri.value()); + ScopedJavaLocalRef<jstring> j_display_name = + Java_ContentUriUtils_maybeGetDisplayName(env, j_uri); + + if (j_display_name.is_null()) + return false; + + *file_display_name = base::android::ConvertJavaStringToUTF16(j_display_name); + return true; +} + +} // namespace base
diff --git a/src/base/android/content_uri_utils.h b/src/base/android/content_uri_utils.h new file mode 100644 index 0000000..e678452 --- /dev/null +++ b/src/base/android/content_uri_utils.h
@@ -0,0 +1,34 @@ +// Copyright 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_ANDROID_CONTENT_URI_UTILS_H_ +#define BASE_ANDROID_CONTENT_URI_UTILS_H_ + +#include <jni.h> + +#include "base/base_export.h" +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "starboard/types.h" + +namespace base { + +// Opens a content URI for read and returns the file descriptor to the caller. +// Returns -1 if the URI is invalid. +BASE_EXPORT File OpenContentUriForRead(const FilePath& content_uri); + +// Check whether a content URI exists. +BASE_EXPORT bool ContentUriExists(const FilePath& content_uri); + +// Gets MIME type from a content URI. Returns an empty string if the URI is +// invalid. +BASE_EXPORT std::string GetContentUriMimeType(const FilePath& content_uri); + +// Gets the display name from a content URI. Returns true if the name was found. +BASE_EXPORT bool MaybeGetFileDisplayName(const FilePath& content_uri, + base::string16* file_display_name); + +} // namespace base + +#endif // BASE_ANDROID_CONTENT_URI_UTILS_H_
diff --git a/src/base/android/content_uri_utils_unittest.cc b/src/base/android/content_uri_utils_unittest.cc new file mode 100644 index 0000000..a87dd71 --- /dev/null +++ b/src/base/android/content_uri_utils_unittest.cc
@@ -0,0 +1,43 @@ +// Copyright 2014 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. + +#include "base/android/content_uri_utils.h" +#include "base/files/file_util.h" +#include "base/path_service.h" +#include "base/test/test_file_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +// Disable on Android ASAN bot due to consistent failures: crbug.com/807080. +#if !defined(ADDRESS_SANITIZER) +TEST(ContentUriUtilsTest, ContentUriMimeTest) { + // Get the test image path. + FilePath data_dir; + ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &data_dir)); + data_dir = data_dir.Append(FILE_PATH_LITERAL("base")); + data_dir = data_dir.Append(FILE_PATH_LITERAL("test")); + data_dir = data_dir.Append(FILE_PATH_LITERAL("data")); + data_dir = data_dir.AppendASCII("file_util"); + ASSERT_TRUE(PathExists(data_dir)); + FilePath image_file = data_dir.Append(FILE_PATH_LITERAL("red.png")); + + // Insert the image into MediaStore. MediaStore will do some conversions, and + // return the content URI. + FilePath path = base::InsertImageIntoMediaStore(image_file); + EXPECT_TRUE(path.IsContentUri()); + EXPECT_TRUE(PathExists(path)); + + std::string mime = GetContentUriMimeType(path); + EXPECT_EQ(mime, std::string("image/png")); + + FilePath invalid_path("content://foo.bar"); + mime = GetContentUriMimeType(invalid_path); + EXPECT_TRUE(mime.empty()); +} +#endif + +} // namespace android +} // namespace base
diff --git a/src/base/android/cpu_features.cc b/src/base/android/cpu_features.cc new file mode 100644 index 0000000..34649e5 --- /dev/null +++ b/src/base/android/cpu_features.cc
@@ -0,0 +1,23 @@ +// Copyright (c) 2012 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. + +#include <cpu-features.h> + +#include "base/android/jni_android.h" +#include "jni/CpuFeatures_jni.h" +#include "starboard/types.h" + +namespace base { +namespace android { + +jint JNI_CpuFeatures_GetCoreCount(JNIEnv*, const JavaParamRef<jclass>&) { + return android_getCpuCount(); +} + +jlong JNI_CpuFeatures_GetCpuFeatures(JNIEnv*, const JavaParamRef<jclass>&) { + return android_getCpuFeatures(); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/early_trace_event_binding.cc b/src/base/android/early_trace_event_binding.cc new file mode 100644 index 0000000..6a82021 --- /dev/null +++ b/src/base/android/early_trace_event_binding.cc
@@ -0,0 +1,80 @@ +// Copyright 2016 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. + +#include "base/android/early_trace_event_binding.h" + +#include "base/android/jni_string.h" +#include "base/time/time.h" +#include "base/trace_event/trace_event.h" +#include "jni/EarlyTraceEvent_jni.h" +#include "starboard/types.h" + +namespace base { +namespace android { + +const char kEarlyJavaCategory[] = "EarlyJava"; + +static void JNI_EarlyTraceEvent_RecordEarlyEvent( + JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jstring>& jname, + jlong begin_time_ns, + jlong end_time_ns, + jint thread_id, + jlong thread_duration_ms) { + std::string name = ConvertJavaStringToUTF8(env, jname); + int64_t begin_us = begin_time_ns / 1000; + int64_t end_us = end_time_ns / 1000; + int64_t thread_duration_us = thread_duration_ms * 1000; + + INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMPS( + kEarlyJavaCategory, name.c_str(), trace_event_internal::kNoId, thread_id, + TimeTicks::FromInternalValue(begin_us), + TimeTicks::FromInternalValue(end_us), + ThreadTicks::Now() + TimeDelta::FromMicroseconds(thread_duration_us), + TRACE_EVENT_FLAG_COPY); +} + +static void JNI_EarlyTraceEvent_RecordEarlyStartAsyncEvent( + JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jstring>& jname, + jlong id, + jlong timestamp_ns) { + std::string name = ConvertJavaStringToUTF8(env, jname); + int64_t timestamp_us = timestamp_ns / 1000; + + TRACE_EVENT_COPY_ASYNC_BEGIN_WITH_TIMESTAMP0( + kEarlyJavaCategory, name.c_str(), id, + base::TimeTicks() + base::TimeDelta::FromMicroseconds(timestamp_us)); +} + +static void JNI_EarlyTraceEvent_RecordEarlyFinishAsyncEvent( + JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jstring>& jname, + jlong id, + jlong timestamp_ns) { + std::string name = ConvertJavaStringToUTF8(env, jname); + int64_t timestamp_us = timestamp_ns / 1000; + + TRACE_EVENT_COPY_ASYNC_END_WITH_TIMESTAMP0( + kEarlyJavaCategory, name.c_str(), id, + base::TimeTicks() + base::TimeDelta::FromMicroseconds(timestamp_us)); +} + +bool GetBackgroundStartupTracingFlag() { + JNIEnv* env = base::android::AttachCurrentThread(); + return base::android::Java_EarlyTraceEvent_getBackgroundStartupTracingFlag( + env); +} + +void SetBackgroundStartupTracingFlag(bool enabled) { + JNIEnv* env = base::android::AttachCurrentThread(); + base::android::Java_EarlyTraceEvent_setBackgroundStartupTracingFlag(env, + enabled); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/early_trace_event_binding.h b/src/base/android/early_trace_event_binding.h new file mode 100644 index 0000000..617f804 --- /dev/null +++ b/src/base/android/early_trace_event_binding.h
@@ -0,0 +1,24 @@ +// Copyright 2018 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_ANDROID_EARLY_TRACE_EVENT_BINDING_H_ +#define BASE_ANDROID_EARLY_TRACE_EVENT_BINDING_H_ + +#include "base/base_export.h" + +namespace base { +namespace android { + +// Returns true if background startup tracing flag was set on the previous +// startup. +BASE_EXPORT bool GetBackgroundStartupTracingFlag(); + +// Sets a flag to chrome application preferences to enable startup tracing next +// time the app is started. +BASE_EXPORT void SetBackgroundStartupTracingFlag(bool enabled); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_EARLY_TRACE_EVENT_BINDING_H_
diff --git a/src/base/android/event_log.cc b/src/base/android/event_log.cc new file mode 100644 index 0000000..3eb5926 --- /dev/null +++ b/src/base/android/event_log.cc
@@ -0,0 +1,16 @@ +// Copyright 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. + +#include "base/android/event_log.h" +#include "jni/EventLog_jni.h" + +namespace base { +namespace android { + +void EventLogWriteInt(int tag, int value) { + Java_EventLog_writeEvent(AttachCurrentThread(), tag, value); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/event_log.h b/src/base/android/event_log.h new file mode 100644 index 0000000..cf0bd75 --- /dev/null +++ b/src/base/android/event_log.h
@@ -0,0 +1,21 @@ +// Copyright 2014 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_ANDROID_EVENT_LOG_H_ +#define BASE_ANDROID_EVENT_LOG_H_ + +#include <jni.h> + +#include "base/base_export.h" +#include "starboard/types.h" + +namespace base { +namespace android { + +void BASE_EXPORT EventLogWriteInt(int tag, int value); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_EVENT_LOG_H_
diff --git a/src/base/android/field_trial_list.cc b/src/base/android/field_trial_list.cc new file mode 100644 index 0000000..fe7ff6c --- /dev/null +++ b/src/base/android/field_trial_list.cc
@@ -0,0 +1,48 @@ +// Copyright 2014 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. + +#include <jni.h> + +#include <map> +#include <string> + +#include "base/android/jni_string.h" +#include "base/metrics/field_trial.h" +#include "base/metrics/field_trial_params.h" +#include "jni/FieldTrialList_jni.h" +#include "starboard/types.h" + +using base::android::ConvertJavaStringToUTF8; +using base::android::ConvertUTF8ToJavaString; +using base::android::JavaParamRef; +using base::android::ScopedJavaLocalRef; + +static ScopedJavaLocalRef<jstring> JNI_FieldTrialList_FindFullName( + JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jstring>& jtrial_name) { + std::string trial_name(ConvertJavaStringToUTF8(env, jtrial_name)); + return ConvertUTF8ToJavaString( + env, base::FieldTrialList::FindFullName(trial_name)); +} + +static jboolean JNI_FieldTrialList_TrialExists( + JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jstring>& jtrial_name) { + std::string trial_name(ConvertJavaStringToUTF8(env, jtrial_name)); + return base::FieldTrialList::TrialExists(trial_name); +} + +static ScopedJavaLocalRef<jstring> JNI_FieldTrialList_GetVariationParameter( + JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jstring>& jtrial_name, + const JavaParamRef<jstring>& jparameter_key) { + std::map<std::string, std::string> parameters; + base::GetFieldTrialParams(ConvertJavaStringToUTF8(env, jtrial_name), + ¶meters); + return ConvertUTF8ToJavaString( + env, parameters[ConvertJavaStringToUTF8(env, jparameter_key)]); +}
diff --git a/src/base/android/important_file_writer_android.cc b/src/base/android/important_file_writer_android.cc new file mode 100644 index 0000000..fcaa3b1 --- /dev/null +++ b/src/base/android/important_file_writer_android.cc
@@ -0,0 +1,37 @@ +// Copyright 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. + +#include <string> + +#include "base/android/jni_string.h" +#include "base/files/important_file_writer.h" +#include "base/threading/thread_restrictions.h" +#include "jni/ImportantFileWriterAndroid_jni.h" + +namespace base { +namespace android { + +static jboolean JNI_ImportantFileWriterAndroid_WriteFileAtomically( + JNIEnv* env, + const JavaParamRef<jclass>& /* clazz */, + const JavaParamRef<jstring>& file_name, + const JavaParamRef<jbyteArray>& data) { + // This is called on the UI thread during shutdown to save tab data, so + // needs to enable IO. + base::ThreadRestrictions::ScopedAllowIO allow_io; + std::string native_file_name; + base::android::ConvertJavaStringToUTF8(env, file_name, &native_file_name); + base::FilePath path(native_file_name); + int data_length = env->GetArrayLength(data); + jbyte* native_data = env->GetByteArrayElements(data, NULL); + std::string native_data_string(reinterpret_cast<char *>(native_data), + data_length); + bool result = base::ImportantFileWriter::WriteFileAtomically( + path, native_data_string); + env->ReleaseByteArrayElements(data, native_data, JNI_ABORT); + return result; +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/java/src/org/chromium/base/ActivityState.java b/src/base/android/java/src/org/chromium/base/ActivityState.java new file mode 100644 index 0000000..b14814c --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/ActivityState.java
@@ -0,0 +1,48 @@ +// Copyright 2014 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. + +package org.chromium.base; + +import android.support.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A set of states that represent the last state change of an Activity. + */ +@Retention(RetentionPolicy.SOURCE) +@IntDef({ActivityState.CREATED, ActivityState.STARTED, ActivityState.RESUMED, ActivityState.PAUSED, + ActivityState.STOPPED, ActivityState.DESTROYED}) +public @interface ActivityState { + /** + * Represents Activity#onCreate(). + */ + int CREATED = 1; + + /** + * Represents Activity#onStart(). + */ + int STARTED = 2; + + /** + * Represents Activity#onResume(). + */ + int RESUMED = 3; + + /** + * Represents Activity#onPause(). + */ + int PAUSED = 4; + + /** + * Represents Activity#onStop(). + */ + int STOPPED = 5; + + /** + * Represents Activity#onDestroy(). This is also used when the state of an Activity is unknown. + */ + int DESTROYED = 6; +}
diff --git a/src/base/android/java/src/org/chromium/base/AnimationFrameTimeHistogram.java b/src/base/android/java/src/org/chromium/base/AnimationFrameTimeHistogram.java new file mode 100644 index 0000000..ad5cdd8 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/AnimationFrameTimeHistogram.java
@@ -0,0 +1,145 @@ +// Copyright 2015 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. + +package org.chromium.base; + +import android.animation.Animator; +import android.animation.Animator.AnimatorListener; +import android.animation.AnimatorListenerAdapter; +import android.animation.TimeAnimator; +import android.animation.TimeAnimator.TimeListener; +import android.util.Log; + +/** + * Record Android animation frame rate and save it to UMA histogram. This is mainly for monitoring + * any jankiness of short Chrome Android animations. It is limited to few seconds of recording. + */ +public class AnimationFrameTimeHistogram { + private static final String TAG = "AnimationFrameTimeHistogram"; + private static final int MAX_FRAME_TIME_NUM = 600; // 10 sec on 60 fps. + + private final Recorder mRecorder = new Recorder(); + private final String mHistogramName; + + /** + * @param histogramName The histogram name that the recorded frame times will be saved. + * This must be also defined in histograms.xml + * @return An AnimatorListener instance that records frame time histogram on start and end + * automatically. + */ + public static AnimatorListener getAnimatorRecorder(final String histogramName) { + return new AnimatorListenerAdapter() { + private final AnimationFrameTimeHistogram mAnimationFrameTimeHistogram = + new AnimationFrameTimeHistogram(histogramName); + + @Override + public void onAnimationStart(Animator animation) { + mAnimationFrameTimeHistogram.startRecording(); + } + + @Override + public void onAnimationEnd(Animator animation) { + mAnimationFrameTimeHistogram.endRecording(); + } + + @Override + public void onAnimationCancel(Animator animation) { + mAnimationFrameTimeHistogram.endRecording(); + } + }; + } + + /** + * @param histogramName The histogram name that the recorded frame times will be saved. + * This must be also defined in histograms.xml + */ + public AnimationFrameTimeHistogram(String histogramName) { + mHistogramName = histogramName; + } + + /** + * Start recording frame times. The recording can fail if it exceeds a few seconds. + */ + public void startRecording() { + mRecorder.startRecording(); + } + + /** + * End recording and save it to histogram. It won't save histogram if the recording wasn't + * successful. + */ + public void endRecording() { + if (mRecorder.endRecording()) { + nativeSaveHistogram(mHistogramName, + mRecorder.getFrameTimesMs(), mRecorder.getFrameTimesCount()); + } + mRecorder.cleanUp(); + } + + /** + * Record Android animation frame rate and return the result. + */ + private static class Recorder implements TimeListener { + // TODO(kkimlabs): If we can use in the future, migrate to Choreographer for minimal + // workload. + private final TimeAnimator mAnimator = new TimeAnimator(); + private long[] mFrameTimesMs; + private int mFrameTimesCount; + + private Recorder() { + mAnimator.setTimeListener(this); + } + + private void startRecording() { + assert !mAnimator.isRunning(); + mFrameTimesCount = 0; + mFrameTimesMs = new long[MAX_FRAME_TIME_NUM]; + mAnimator.start(); + } + + /** + * @return Whether the recording was successful. If successful, the result is available via + * getFrameTimesNs and getFrameTimesCount. + */ + private boolean endRecording() { + boolean succeeded = mAnimator.isStarted(); + mAnimator.end(); + return succeeded; + } + + private long[] getFrameTimesMs() { + return mFrameTimesMs; + } + + private int getFrameTimesCount() { + return mFrameTimesCount; + } + + /** + * Deallocates the temporary buffer to record frame times. Must be called after ending + * the recording and getting the result. + */ + private void cleanUp() { + mFrameTimesMs = null; + } + + @Override + public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) { + if (mFrameTimesCount == mFrameTimesMs.length) { + mAnimator.end(); + cleanUp(); + Log.w(TAG, "Animation frame time recording reached the maximum number. It's either" + + "the animation took too long or recording end is not called."); + return; + } + + // deltaTime is 0 for the first frame. + if (deltaTime > 0) { + mFrameTimesMs[mFrameTimesCount++] = deltaTime; + } + } + } + + private native void nativeSaveHistogram(String histogramName, long[] frameTimesMs, int count); +}
diff --git a/src/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java b/src/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java new file mode 100644 index 0000000..e9dd36d --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java
@@ -0,0 +1,779 @@ +// Copyright 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. + +package org.chromium.base; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.ActivityManager; +import android.app.ActivityOptions; +import android.app.PendingIntent; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.res.ColorStateList; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.Resources.NotFoundException; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.graphics.drawable.VectorDrawable; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.PowerManager; +import android.os.Process; +import android.os.StatFs; +import android.os.StrictMode; +import android.os.UserManager; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.widget.ImageViewCompat; +import android.text.Html; +import android.text.Spanned; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodSubtype; +import android.view.textclassifier.TextClassifier; +import android.widget.ImageView; +import android.widget.TextView; + +import java.io.File; +import java.io.UnsupportedEncodingException; + +/** + * Utility class to use new APIs that were added after ICS (API level 14). + */ +@TargetApi(Build.VERSION_CODES.LOLLIPOP) +public class ApiCompatibilityUtils { + private ApiCompatibilityUtils() { + } + + /** + * Compares two long values numerically. The value returned is identical to what would be + * returned by {@link Long#compare(long, long)} which is available since API level 19. + */ + public static int compareLong(long lhs, long rhs) { + return lhs < rhs ? -1 : (lhs == rhs ? 0 : 1); + } + + /** + * Compares two boolean values. The value returned is identical to what would be returned by + * {@link Boolean#compare(boolean, boolean)} which is available since API level 19. + */ + public static int compareBoolean(boolean lhs, boolean rhs) { + return lhs == rhs ? 0 : lhs ? 1 : -1; + } + + /** + * Checks that the object reference is not null and throws NullPointerException if it is. + * See {@link Objects#requireNonNull} which is available since API level 19. + * @param obj The object to check + */ + @NonNull + public static <T> T requireNonNull(T obj) { + if (obj == null) throw new NullPointerException(); + return obj; + } + + /** + * Checks that the object reference is not null and throws NullPointerException if it is. + * See {@link Objects#requireNonNull} which is available since API level 19. + * @param obj The object to check + * @param message The message to put into NullPointerException + */ + @NonNull + public static <T> T requireNonNull(T obj, String message) { + if (obj == null) throw new NullPointerException(message); + return obj; + } + + /** + * {@link String#getBytes()} but specifying UTF-8 as the encoding and capturing the resulting + * UnsupportedEncodingException. + */ + public static byte[] getBytesUtf8(String str) { + try { + return str.getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException("UTF-8 encoding not available.", e); + } + } + + /** + * Returns true if view's layout direction is right-to-left. + * + * @param view the View whose layout is being considered + */ + public static boolean isLayoutRtl(View view) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + return view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; + } else { + // All layouts are LTR before JB MR1. + return false; + } + } + + /** + * @see Configuration#getLayoutDirection() + */ + public static int getLayoutDirection(Configuration configuration) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + return configuration.getLayoutDirection(); + } else { + // All layouts are LTR before JB MR1. + return View.LAYOUT_DIRECTION_LTR; + } + } + + /** + * @return True if the running version of the Android supports printing. + */ + public static boolean isPrintingSupported() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + } + + /** + * @return True if the running version of the Android supports elevation. Elevation of a view + * determines the visual appearance of its shadow. + */ + public static boolean isElevationSupported() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; + } + + /** + * @see android.view.View#setLayoutDirection(int) + */ + public static void setLayoutDirection(View view, int layoutDirection) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + view.setLayoutDirection(layoutDirection); + } else { + // Do nothing. RTL layouts aren't supported before JB MR1. + } + } + + /** + * @see android.view.View#setTextAlignment(int) + */ + public static void setTextAlignment(View view, int textAlignment) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + view.setTextAlignment(textAlignment); + } else { + // Do nothing. RTL text isn't supported before JB MR1. + } + } + + /** + * @see android.view.View#setTextDirection(int) + */ + public static void setTextDirection(View view, int textDirection) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + view.setTextDirection(textDirection); + } else { + // Do nothing. RTL text isn't supported before JB MR1. + } + } + + /** + * See {@link android.view.View#setLabelFor(int)}. + */ + public static void setLabelFor(View labelView, int id) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + labelView.setLabelFor(id); + } else { + // Do nothing. #setLabelFor() isn't supported before JB MR1. + } + } + + /** + * @see android.widget.TextView#getCompoundDrawablesRelative() + */ + public static Drawable[] getCompoundDrawablesRelative(TextView textView) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + return textView.getCompoundDrawablesRelative(); + } else { + return textView.getCompoundDrawables(); + } + } + + /** + * @see android.widget.TextView#setCompoundDrawablesRelative(Drawable, Drawable, Drawable, + * Drawable) + */ + public static void setCompoundDrawablesRelative(TextView textView, Drawable start, Drawable top, + Drawable end, Drawable bottom) { + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) { + // On JB MR1, due to a platform bug, setCompoundDrawablesRelative() is a no-op if the + // view has ever been measured. As a workaround, use setCompoundDrawables() directly. + // See: http://crbug.com/368196 and http://crbug.com/361709 + boolean isRtl = isLayoutRtl(textView); + textView.setCompoundDrawables(isRtl ? end : start, top, isRtl ? start : end, bottom); + } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1) { + textView.setCompoundDrawablesRelative(start, top, end, bottom); + } else { + textView.setCompoundDrawables(start, top, end, bottom); + } + } + + /** + * @see android.widget.TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable, + * Drawable, Drawable, Drawable) + */ + public static void setCompoundDrawablesRelativeWithIntrinsicBounds(TextView textView, + Drawable start, Drawable top, Drawable end, Drawable bottom) { + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) { + // Work around the platform bug described in setCompoundDrawablesRelative() above. + boolean isRtl = isLayoutRtl(textView); + textView.setCompoundDrawablesWithIntrinsicBounds(isRtl ? end : start, top, + isRtl ? start : end, bottom); + } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1) { + textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom); + } else { + textView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom); + } + } + + /** + * @see android.widget.TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(int, int, int, + * int) + */ + public static void setCompoundDrawablesRelativeWithIntrinsicBounds(TextView textView, + int start, int top, int end, int bottom) { + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) { + // Work around the platform bug described in setCompoundDrawablesRelative() above. + boolean isRtl = isLayoutRtl(textView); + textView.setCompoundDrawablesWithIntrinsicBounds(isRtl ? end : start, top, + isRtl ? start : end, bottom); + } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1) { + textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom); + } else { + textView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom); + } + } + + /** + * @see android.text.Html#toHtml(Spanned, int) + * @param option is ignored on below N + */ + @SuppressWarnings("deprecation") + public static String toHtml(Spanned spanned, int option) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return Html.toHtml(spanned, option); + } else { + return Html.toHtml(spanned); + } + } + + // These methods have a new name, and the old name is deprecated. + + /** + * @see android.app.PendingIntent#getCreatorPackage() + */ + @SuppressWarnings("deprecation") + public static String getCreatorPackage(PendingIntent intent) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + return intent.getCreatorPackage(); + } else { + return intent.getTargetPackage(); + } + } + + /** + * @see android.provider.Settings.Global#DEVICE_PROVISIONED + */ + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) + public static boolean isDeviceProvisioned(Context context) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) return true; + if (context == null) return true; + if (context.getContentResolver() == null) return true; + return Settings.Global.getInt( + context.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0; + } + + /** + * @see android.app.Activity#finishAndRemoveTask() + */ + public static void finishAndRemoveTask(Activity activity) { + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) { + activity.finishAndRemoveTask(); + } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) { + // crbug.com/395772 : Fallback for Activity.finishAndRemoveTask() failing. + new FinishAndRemoveTaskWithRetry(activity).run(); + } else { + activity.finish(); + } + } + + /** + * Set elevation if supported. + */ + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public static boolean setElevation(View view, float elevationValue) { + if (!isElevationSupported()) return false; + + view.setElevation(elevationValue); + return true; + } + + /** + * Gets an intent to start the Android system notification settings activity for an app. + * + * @param context Context of the app whose settings intent should be returned. + */ + public static Intent getNotificationSettingsIntent(Context context) { + Intent intent = new Intent(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS); + intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName()); + } else { + intent.setAction("android.settings.ACTION_APP_NOTIFICATION_SETTINGS"); + intent.putExtra("app_package", context.getPackageName()); + intent.putExtra("app_uid", context.getApplicationInfo().uid); + } + return intent; + } + + private static class FinishAndRemoveTaskWithRetry implements Runnable { + private static final long RETRY_DELAY_MS = 500; + private static final long MAX_TRY_COUNT = 3; + private final Activity mActivity; + private int mTryCount; + + FinishAndRemoveTaskWithRetry(Activity activity) { + mActivity = activity; + } + + @Override + public void run() { + mActivity.finishAndRemoveTask(); + mTryCount++; + if (!mActivity.isFinishing()) { + if (mTryCount < MAX_TRY_COUNT) { + ThreadUtils.postOnUiThreadDelayed(this, RETRY_DELAY_MS); + } else { + mActivity.finish(); + } + } + } + } + + /** + * @return Whether the screen of the device is interactive. + */ + @SuppressWarnings("deprecation") + public static boolean isInteractive(Context context) { + PowerManager manager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) { + return manager.isInteractive(); + } else { + return manager.isScreenOn(); + } + } + + @SuppressWarnings("deprecation") + public static int getActivityNewDocumentFlag() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + return Intent.FLAG_ACTIVITY_NEW_DOCUMENT; + } else { + return Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET; + } + } + + /** + * @see android.provider.Settings.Secure#SKIP_FIRST_USE_HINTS + */ + public static boolean shouldSkipFirstUseHints(ContentResolver contentResolver) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + return Settings.Secure.getInt( + contentResolver, Settings.Secure.SKIP_FIRST_USE_HINTS, 0) != 0; + } else { + return false; + } + } + + /** + * @param activity Activity that should get the task description update. + * @param title Title of the activity. + * @param icon Icon of the activity. + * @param color Color of the activity. It must be a fully opaque color. + */ + public static void setTaskDescription(Activity activity, String title, Bitmap icon, int color) { + // TaskDescription requires an opaque color. + assert Color.alpha(color) == 255; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + ActivityManager.TaskDescription description = + new ActivityManager.TaskDescription(title, icon, color); + activity.setTaskDescription(description); + } + } + + /** + * @see android.view.Window#setStatusBarColor(int color). + */ + public static void setStatusBarColor(Window window, int statusBarColor) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return; + + // If both system bars are black, we can remove these from our layout, + // removing or shrinking the SurfaceFlinger overlay required for our views. + // This benefits battery usage on L and M. However, this no longer provides a battery + // benefit as of N and starts to cause flicker bugs on O, so don't bother on O and up. + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O && statusBarColor == Color.BLACK + && window.getNavigationBarColor() == Color.BLACK) { + window.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + } else { + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + } + window.setStatusBarColor(statusBarColor); + } + + /** + * Sets the status bar icons to dark or light. Note that this is only valid for + * Android M+. + * + * @param rootView The root view used to request updates to the system UI theming. + * @param useDarkIcons Whether the status bar icons should be dark. + */ + public static void setStatusBarIconColor(View rootView, boolean useDarkIcons) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return; + + int systemUiVisibility = rootView.getSystemUiVisibility(); + if (useDarkIcons) { + systemUiVisibility |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + } else { + systemUiVisibility &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + } + rootView.setSystemUiVisibility(systemUiVisibility); + } + + /** + * @see android.content.res.Resources#getDrawable(int id). + * TODO(ltian): use {@link AppCompatResources} to parse drawable to prevent fail on + * {@link VectorDrawable}. (http://crbug.com/792129) + */ + @SuppressWarnings("deprecation") + public static Drawable getDrawable(Resources res, int id) throws NotFoundException { + StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + return res.getDrawable(id, null); + } else { + return res.getDrawable(id); + } + } finally { + StrictMode.setThreadPolicy(oldPolicy); + } + } + + public static void setImageTintList( + @NonNull ImageView view, @Nullable ColorStateList tintList) { + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) { + // Work around broken workaround in ImageViewCompat, see https://crbug.com/891609#c3. + if (tintList != null && view.getImageTintMode() == null) { + view.setImageTintMode(PorterDuff.Mode.SRC_IN); + } + } + ImageViewCompat.setImageTintList(view, tintList); + } + + /** + * @see android.content.res.Resources#getDrawableForDensity(int id, int density). + */ + @SuppressWarnings("deprecation") + public static Drawable getDrawableForDensity(Resources res, int id, int density) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + return res.getDrawableForDensity(id, density, null); + } else { + return res.getDrawableForDensity(id, density); + } + } + + /** + * @see android.app.Activity#finishAfterTransition(). + */ + public static void finishAfterTransition(Activity activity) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + activity.finishAfterTransition(); + } else { + activity.finish(); + } + } + + /** + * @see android.content.pm.PackageManager#getUserBadgedIcon(Drawable, android.os.UserHandle). + */ + public static Drawable getUserBadgedIcon(Context context, int id) { + Drawable drawable = getDrawable(context.getResources(), id); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + PackageManager packageManager = context.getPackageManager(); + drawable = packageManager.getUserBadgedIcon(drawable, Process.myUserHandle()); + } + return drawable; + } + + /** + * @see android.content.pm.PackageManager#getUserBadgedDrawableForDensity(Drawable drawable, + * UserHandle user, Rect badgeLocation, int badgeDensity). + */ + public static Drawable getUserBadgedDrawableForDensity( + Context context, Drawable drawable, Rect badgeLocation, int density) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + PackageManager packageManager = context.getPackageManager(); + return packageManager.getUserBadgedDrawableForDensity( + drawable, Process.myUserHandle(), badgeLocation, density); + } + return drawable; + } + + /** + * @see android.content.res.Resources#getColor(int id). + */ + @SuppressWarnings("deprecation") + public static int getColor(Resources res, int id) throws NotFoundException { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return res.getColor(id, null); + } else { + return res.getColor(id); + } + } + + /** + * @see android.graphics.drawable.Drawable#getColorFilter(). + */ + @SuppressWarnings("NewApi") + public static ColorFilter getColorFilter(Drawable drawable) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + return drawable.getColorFilter(); + } else { + return null; + } + } + + /** + * @see android.widget.TextView#setTextAppearance(int id). + */ + @SuppressWarnings("deprecation") + public static void setTextAppearance(TextView view, int id) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + view.setTextAppearance(id); + } else { + view.setTextAppearance(view.getContext(), id); + } + } + + /** + * See {@link android.os.StatFs#getAvailableBlocksLong}. + */ + @SuppressWarnings("deprecation") + public static long getAvailableBlocks(StatFs statFs) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + return statFs.getAvailableBlocksLong(); + } else { + return statFs.getAvailableBlocks(); + } + } + + /** + * See {@link android.os.StatFs#getBlockCount}. + */ + @SuppressWarnings("deprecation") + public static long getBlockCount(StatFs statFs) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + return statFs.getBlockCountLong(); + } else { + return statFs.getBlockCount(); + } + } + + /** + * See {@link android.os.StatFs#getBlockSize}. + */ + @SuppressWarnings("deprecation") + public static long getBlockSize(StatFs statFs) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + return statFs.getBlockSizeLong(); + } else { + return statFs.getBlockSize(); + } + } + + /** + * @param context The Android context, used to retrieve the UserManager system service. + * @return Whether the device is running in demo mode. + */ + @SuppressWarnings("NewApi") + public static boolean isDemoUser(Context context) { + // UserManager#isDemoUser() is only available in Android NMR1+. + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) return false; + + UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE); + return userManager.isDemoUser(); + } + + /** + * @see Context#checkPermission(String, int, int) + */ + public static int checkPermission(Context context, String permission, int pid, int uid) { + try { + return context.checkPermission(permission, pid, uid); + } catch (RuntimeException e) { + // Some older versions of Android throw odd errors when checking for permissions, so + // just swallow the exception and treat it as the permission is denied. + // crbug.com/639099 + return PackageManager.PERMISSION_DENIED; + } + } + + /** + * @see android.view.inputmethod.InputMethodSubType#getLocate() + */ + @SuppressWarnings("deprecation") + public static String getLocale(InputMethodSubtype inputMethodSubType) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return inputMethodSubType.getLanguageTag(); + } else { + return inputMethodSubType.getLocale(); + } + } + + /** + * Get a URI for |file| which has the image capture. This function assumes that path of |file| + * is based on the result of UiUtils.getDirectoryForImageCapture(). + * + * @param file image capture file. + * @return URI for |file|. + */ + public static Uri getUriForImageCaptureFile(File file) { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 + ? ContentUriUtils.getContentUriFromFile(file) + : Uri.fromFile(file); + } + + /** + * Get the URI for a downloaded file. + * + * @param file A downloaded file. + * @return URI for |file|. + */ + public static Uri getUriForDownloadedFile(File file) { + return Build.VERSION.SDK_INT > Build.VERSION_CODES.M + ? FileUtils.getUriForFile(file) + : Uri.fromFile(file); + } + + /** + * @see android.view.Window#FEATURE_INDETERMINATE_PROGRESS + */ + public static void setWindowIndeterminateProgress(Window window) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + @SuppressWarnings("deprecation") + int featureNumber = Window.FEATURE_INDETERMINATE_PROGRESS; + + @SuppressWarnings("deprecation") + int featureValue = Window.PROGRESS_VISIBILITY_OFF; + + window.setFeatureInt(featureNumber, featureValue); + } + } + + /** + * @param activity The {@link Activity} to check. + * @return Whether or not {@code activity} is currently in Android N+ multi-window mode. + */ + public static boolean isInMultiWindowMode(Activity activity) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + return false; + } + return activity.isInMultiWindowMode(); + } + + /** + * Disables the Smart Select {@link TextClassifier} for the given {@link TextView} instance. + * @param textView The {@link TextView} that should have its classifier disabled. + */ + @TargetApi(Build.VERSION_CODES.O) + public static void disableSmartSelectionTextClassifier(TextView textView) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return; + + textView.setTextClassifier(TextClassifier.NO_OP); + } + + /** + * Creates an ActivityOptions Bundle with basic options and the LaunchDisplayId set. + * @param displayId The id of the display to launch into. + * @return The created bundle, or null if unsupported. + */ + public static Bundle createLaunchDisplayIdActivityOptions(int displayId) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return null; + + ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchDisplayId(displayId); + return options.toBundle(); + } + + /** + * @see View#setAccessibilityTraversalBefore(int) + */ + public static void setAccessibilityTraversalBefore(View view, int viewFocusedAfter) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + view.setAccessibilityTraversalBefore(viewFocusedAfter); + } + } + + /** + * Creates regular LayerDrawable on Android L+. On older versions creates a helper class that + * fixes issues around {@link LayerDrawable#mutate()}. See https://crbug.com/890317 for details. + * @param layers A list of drawables to use as layers in this new drawable. + */ + public static LayerDrawable createLayerDrawable(@NonNull Drawable[] layers) { + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { + return new LayerDrawableCompat(layers); + } + return new LayerDrawable(layers); + } + + private static class LayerDrawableCompat extends LayerDrawable { + private boolean mMutated; + + LayerDrawableCompat(@NonNull Drawable[] layers) { + super(layers); + } + + @Override + public Drawable mutate() { + // LayerDrawable in Android K loses bounds of layers, so this method works around that. + if (mMutated) { + // This object has already been mutated and shouldn't have any shared state. + return this; + } + + // Save bounds before mutation. + Rect[] oldBounds = new Rect[getNumberOfLayers()]; + for (int i = 0; i < getNumberOfLayers(); i++) { + oldBounds[i] = getDrawable(i).getBounds(); + } + + Drawable superResult = super.mutate(); + if (superResult != this) { + // Unexpected, LayerDrawable.mutate() always returns this. + return superResult; + } + + // Restore the saved bounds. + for (int i = 0; i < getNumberOfLayers(); i++) { + getDrawable(i).setBounds(oldBounds[i]); + } + mMutated = true; + return this; + } + } +}
diff --git a/src/base/android/java/src/org/chromium/base/ApkAssets.java b/src/base/android/java/src/org/chromium/base/ApkAssets.java new file mode 100644 index 0000000..19108e5 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/ApkAssets.java
@@ -0,0 +1,58 @@ +// Copyright 2014 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. + +package org.chromium.base; + +import android.content.res.AssetFileDescriptor; +import android.content.res.AssetManager; +import android.util.Log; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; + +import java.io.IOException; + +/** + * A utility class to retrieve references to uncompressed assets insides the apk. A reference is + * defined as tuple (file descriptor, offset, size) enabling direct mapping without deflation. + * This can be used even within the renderer process, since it just dup's the apk's fd. + */ +@JNINamespace("base::android") +public class ApkAssets { + private static final String LOGTAG = "ApkAssets"; + + @CalledByNative + public static long[] open(String fileName) { + AssetFileDescriptor afd = null; + try { + AssetManager manager = ContextUtils.getApplicationContext().getAssets(); + afd = manager.openNonAssetFd(fileName); + return new long[] {afd.getParcelFileDescriptor().detachFd(), afd.getStartOffset(), + afd.getLength()}; + } catch (IOException e) { + // As a general rule there's no point logging here because the caller should handle + // receiving an fd of -1 sensibly, and the log message is either mirrored later, or + // unwanted (in the case where a missing file is expected), or wanted but will be + // ignored, as most non-fatal logs are. + // It makes sense to log here when the file exists, but is unable to be opened as an fd + // because (for example) it is unexpectedly compressed in an apk. In that case, the log + // message might save someone some time working out what has gone wrong. + // For that reason, we only suppress the message when the exception message doesn't look + // informative (Android framework passes the filename as the message on actual file not + // found, and the empty string also wouldn't give any useful information for debugging). + if (!e.getMessage().equals("") && !e.getMessage().equals(fileName)) { + Log.e(LOGTAG, "Error while loading asset " + fileName + ": " + e); + } + return new long[] {-1, -1, -1}; + } finally { + try { + if (afd != null) { + afd.close(); + } + } catch (IOException e2) { + Log.e(LOGTAG, "Unable to close AssetFileDescriptor", e2); + } + } + } +}
diff --git a/src/base/android/java/src/org/chromium/base/ApplicationStatus.java b/src/base/android/java/src/org/chromium/base/ApplicationStatus.java new file mode 100644 index 0000000..7c8c358 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/ApplicationStatus.java
@@ -0,0 +1,611 @@ +// Copyright 2014 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. + +package org.chromium.base; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.Application; +import android.app.Application.ActivityLifecycleCallbacks; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.view.Window; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; + +import java.lang.ref.WeakReference; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Provides information about the current activity's status, and a way + * to register / unregister listeners for state changes. + */ +@JNINamespace("base::android") +public class ApplicationStatus { + private static final String TOOLBAR_CALLBACK_INTERNAL_WRAPPER_CLASS = + "android.support.v7.internal.app.ToolbarActionBar$ToolbarCallbackWrapper"; + // In builds using the --use_unpublished_apis flag, the ToolbarActionBar class name does not + // include the "internal" package. + private static final String TOOLBAR_CALLBACK_WRAPPER_CLASS = + "android.support.v7.app.ToolbarActionBar$ToolbarCallbackWrapper"; + private static final String WINDOW_PROFILER_CALLBACK = + "com.android.tools.profiler.support.event.WindowProfilerCallback"; + + private static class ActivityInfo { + private int mStatus = ActivityState.DESTROYED; + private ObserverList<ActivityStateListener> mListeners = new ObserverList<>(); + + /** + * @return The current {@link ActivityState} of the activity. + */ + @ActivityState + public int getStatus() { + return mStatus; + } + + /** + * @param status The new {@link ActivityState} of the activity. + */ + public void setStatus(@ActivityState int status) { + mStatus = status; + } + + /** + * @return A list of {@link ActivityStateListener}s listening to this activity. + */ + public ObserverList<ActivityStateListener> getListeners() { + return mListeners; + } + } + + private static final Object sCurrentApplicationStateLock = new Object(); + + @SuppressLint("SupportAnnotationUsage") + @ApplicationState + // The getStateForApplication() historically returned ApplicationState.HAS_DESTROYED_ACTIVITIES + // when no activity has been observed. + private static Integer sCurrentApplicationState = ApplicationState.HAS_DESTROYED_ACTIVITIES; + + /** Last activity that was shown (or null if none or it was destroyed). */ + @SuppressLint("StaticFieldLeak") + private static Activity sActivity; + + /** A lazily initialized listener that forwards application state changes to native. */ + private static ApplicationStateListener sNativeApplicationStateListener; + + private static boolean sIsInitialized; + + /** + * A map of which observers listen to state changes from which {@link Activity}. + */ + private static final Map<Activity, ActivityInfo> sActivityInfo = new ConcurrentHashMap<>(); + + /** + * A list of observers to be notified when any {@link Activity} has a state change. + */ + private static final ObserverList<ActivityStateListener> sGeneralActivityStateListeners = + new ObserverList<>(); + + /** + * A list of observers to be notified when the visibility state of this {@link Application} + * changes. See {@link #getStateForApplication()}. + */ + private static final ObserverList<ApplicationStateListener> sApplicationStateListeners = + new ObserverList<>(); + + /** + * A list of observers to be notified when the window focus changes. + * See {@link #registerWindowFocusChangedListener}. + */ + private static final ObserverList<WindowFocusChangedListener> sWindowFocusListeners = + new ObserverList<>(); + + /** + * Interface to be implemented by listeners. + */ + public interface ApplicationStateListener { + /** + * Called when the application's state changes. + * @param newState The application state. + */ + void onApplicationStateChange(@ApplicationState int newState); + } + + /** + * Interface to be implemented by listeners. + */ + public interface ActivityStateListener { + /** + * Called when the activity's state changes. + * @param activity The activity that had a state change. + * @param newState New activity state. + */ + void onActivityStateChange(Activity activity, @ActivityState int newState); + } + + /** + * Interface to be implemented by listeners for window focus events. + */ + public interface WindowFocusChangedListener { + /** + * Called when the window focus changes for {@code activity}. + * @param activity The {@link Activity} that has a window focus changed event. + * @param hasFocus Whether or not {@code activity} gained or lost focus. + */ + public void onWindowFocusChanged(Activity activity, boolean hasFocus); + } + + private ApplicationStatus() {} + + /** + * Registers a listener to receive window focus updates on activities in this application. + * @param listener Listener to receive window focus events. + */ + public static void registerWindowFocusChangedListener(WindowFocusChangedListener listener) { + sWindowFocusListeners.addObserver(listener); + } + + /** + * Unregisters a listener from receiving window focus updates on activities in this application. + * @param listener Listener that doesn't want to receive window focus events. + */ + public static void unregisterWindowFocusChangedListener(WindowFocusChangedListener listener) { + sWindowFocusListeners.removeObserver(listener); + } + + /** + * Intercepts calls to an existing Window.Callback. Most invocations are passed on directly + * to the composed Window.Callback but enables intercepting/manipulating others. + * + * This is used to relay window focus changes throughout the app and remedy a bug in the + * appcompat library. + */ + private static class WindowCallbackProxy implements InvocationHandler { + private final Window.Callback mCallback; + private final Activity mActivity; + + public WindowCallbackProxy(Activity activity, Window.Callback callback) { + mCallback = callback; + mActivity = activity; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (method.getName().equals("onWindowFocusChanged") && args.length == 1 + && args[0] instanceof Boolean) { + onWindowFocusChanged((boolean) args[0]); + return null; + } else { + try { + return method.invoke(mCallback, args); + } catch (InvocationTargetException e) { + // Special-case for when a method is not defined on the underlying + // Window.Callback object. Because we're using a Proxy to forward all method + // calls, this breaks the Android framework's handling for apps built against + // an older SDK. The framework expects an AbstractMethodError but due to + // reflection it becomes wrapped inside an InvocationTargetException. Undo the + // wrapping to signal the framework accordingly. + if (e.getCause() instanceof AbstractMethodError) { + throw e.getCause(); + } + throw e; + } + } + } + + public void onWindowFocusChanged(boolean hasFocus) { + mCallback.onWindowFocusChanged(hasFocus); + + for (WindowFocusChangedListener listener : sWindowFocusListeners) { + listener.onWindowFocusChanged(mActivity, hasFocus); + } + } + } + + /** + * Initializes the activity status for a specified application. + * + * @param application The application whose status you wish to monitor. + */ + public static void initialize(Application application) { + if (sIsInitialized) return; + sIsInitialized = true; + + registerWindowFocusChangedListener(new WindowFocusChangedListener() { + @Override + public void onWindowFocusChanged(Activity activity, boolean hasFocus) { + if (!hasFocus || activity == sActivity) return; + + int state = getStateForActivity(activity); + + if (state != ActivityState.DESTROYED && state != ActivityState.STOPPED) { + sActivity = activity; + } + + // TODO(dtrainor): Notify of active activity change? + } + }); + + application.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { + @Override + public void onActivityCreated(final Activity activity, Bundle savedInstanceState) { + onStateChange(activity, ActivityState.CREATED); + Window.Callback callback = activity.getWindow().getCallback(); + activity.getWindow().setCallback((Window.Callback) Proxy.newProxyInstance( + Window.Callback.class.getClassLoader(), new Class[] {Window.Callback.class}, + new ApplicationStatus.WindowCallbackProxy(activity, callback))); + } + + @Override + public void onActivityDestroyed(Activity activity) { + onStateChange(activity, ActivityState.DESTROYED); + checkCallback(activity); + } + + @Override + public void onActivityPaused(Activity activity) { + onStateChange(activity, ActivityState.PAUSED); + checkCallback(activity); + } + + @Override + public void onActivityResumed(Activity activity) { + onStateChange(activity, ActivityState.RESUMED); + checkCallback(activity); + } + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle outState) { + checkCallback(activity); + } + + @Override + public void onActivityStarted(Activity activity) { + onStateChange(activity, ActivityState.STARTED); + checkCallback(activity); + } + + @Override + public void onActivityStopped(Activity activity) { + onStateChange(activity, ActivityState.STOPPED); + checkCallback(activity); + } + + private void checkCallback(Activity activity) { + if (BuildConfig.DCHECK_IS_ON) { + Class<? extends Window.Callback> callback = + activity.getWindow().getCallback().getClass(); + assert(Proxy.isProxyClass(callback) + || callback.getName().equals(TOOLBAR_CALLBACK_WRAPPER_CLASS) + || callback.getName().equals(TOOLBAR_CALLBACK_INTERNAL_WRAPPER_CLASS) + || callback.getName().equals(WINDOW_PROFILER_CALLBACK)); + } + } + }); + } + + /** + * Asserts that initialize method has been called. + */ + private static void assertInitialized() { + if (!sIsInitialized) { + throw new IllegalStateException("ApplicationStatus has not been initialized yet."); + } + } + + /** + * Must be called by the main activity when it changes state. + * + * @param activity Current activity. + * @param newState New state value. + */ + private static void onStateChange(Activity activity, @ActivityState int newState) { + if (activity == null) throw new IllegalArgumentException("null activity is not supported"); + + if (sActivity == null + || newState == ActivityState.CREATED + || newState == ActivityState.RESUMED + || newState == ActivityState.STARTED) { + sActivity = activity; + } + + int oldApplicationState = getStateForApplication(); + ActivityInfo info; + + synchronized (sCurrentApplicationStateLock) { + if (newState == ActivityState.CREATED) { + assert !sActivityInfo.containsKey(activity); + sActivityInfo.put(activity, new ActivityInfo()); + } + + info = sActivityInfo.get(activity); + info.setStatus(newState); + + // Remove before calling listeners so that isEveryActivityDestroyed() returns false when + // this was the last activity. + if (newState == ActivityState.DESTROYED) { + sActivityInfo.remove(activity); + if (activity == sActivity) sActivity = null; + } + + sCurrentApplicationState = determineApplicationState(); + } + + // Notify all state observers that are specifically listening to this activity. + for (ActivityStateListener listener : info.getListeners()) { + listener.onActivityStateChange(activity, newState); + } + + // Notify all state observers that are listening globally for all activity state + // changes. + for (ActivityStateListener listener : sGeneralActivityStateListeners) { + listener.onActivityStateChange(activity, newState); + } + + int applicationState = getStateForApplication(); + if (applicationState != oldApplicationState) { + for (ApplicationStateListener listener : sApplicationStateListeners) { + listener.onApplicationStateChange(applicationState); + } + } + } + + /** + * Testing method to update the state of the specified activity. + */ + @VisibleForTesting + public static void onStateChangeForTesting(Activity activity, int newState) { + onStateChange(activity, newState); + } + + /** + * @return The most recent focused {@link Activity} tracked by this class. Being focused means + * out of all the activities tracked here, it has most recently gained window focus. + */ + public static Activity getLastTrackedFocusedActivity() { + return sActivity; + } + + /** + * @return A {@link List} of all non-destroyed {@link Activity}s. + */ + public static List<WeakReference<Activity>> getRunningActivities() { + assertInitialized(); + List<WeakReference<Activity>> activities = new ArrayList<>(); + for (Activity activity : sActivityInfo.keySet()) { + activities.add(new WeakReference<>(activity)); + } + return activities; + } + + /** + * Query the state for a given activity. If the activity is not being tracked, this will + * return {@link ActivityState#DESTROYED}. + * + * <p> + * Please note that Chrome can have multiple activities running simultaneously. Please also + * look at {@link #getStateForApplication()} for more details. + * + * <p> + * When relying on this method, be familiar with the expected life cycle state + * transitions: + * <a href="http://developer.android.com/guide/components/activities.html#Lifecycle"> + * Activity Lifecycle + * </a> + * + * <p> + * During activity transitions (activity B launching in front of activity A), A will completely + * paused before the creation of activity B begins. + * + * <p> + * A basic flow for activity A starting, followed by activity B being opened and then closed: + * <ul> + * <li> -- Starting Activity A -- + * <li> Activity A - ActivityState.CREATED + * <li> Activity A - ActivityState.STARTED + * <li> Activity A - ActivityState.RESUMED + * <li> -- Starting Activity B -- + * <li> Activity A - ActivityState.PAUSED + * <li> Activity B - ActivityState.CREATED + * <li> Activity B - ActivityState.STARTED + * <li> Activity B - ActivityState.RESUMED + * <li> Activity A - ActivityState.STOPPED + * <li> -- Closing Activity B, Activity A regaining focus -- + * <li> Activity B - ActivityState.PAUSED + * <li> Activity A - ActivityState.STARTED + * <li> Activity A - ActivityState.RESUMED + * <li> Activity B - ActivityState.STOPPED + * <li> Activity B - ActivityState.DESTROYED + * </ul> + * + * @param activity The activity whose state is to be returned. + * @return The state of the specified activity (see {@link ActivityState}). + */ + @ActivityState + public static int getStateForActivity(@Nullable Activity activity) { + ApplicationStatus.assertInitialized(); + if (activity == null) return ActivityState.DESTROYED; + ActivityInfo info = sActivityInfo.get(activity); + return info != null ? info.getStatus() : ActivityState.DESTROYED; + } + + /** + * @return The state of the application (see {@link ApplicationState}). + */ + @ApplicationState + @CalledByNative + public static int getStateForApplication() { + synchronized (sCurrentApplicationStateLock) { + return sCurrentApplicationState; + } + } + + /** + * Checks whether or not any Activity in this Application is visible to the user. Note that + * this includes the PAUSED state, which can happen when the Activity is temporarily covered + * by another Activity's Fragment (e.g.). + * @return Whether any Activity under this Application is visible. + */ + public static boolean hasVisibleActivities() { + int state = getStateForApplication(); + return state == ApplicationState.HAS_RUNNING_ACTIVITIES + || state == ApplicationState.HAS_PAUSED_ACTIVITIES; + } + + /** + * Checks to see if there are any active Activity instances being watched by ApplicationStatus. + * @return True if all Activities have been destroyed. + */ + public static boolean isEveryActivityDestroyed() { + return sActivityInfo.isEmpty(); + } + + /** + * Registers the given listener to receive state changes for all activities. + * @param listener Listener to receive state changes. + */ + public static void registerStateListenerForAllActivities(ActivityStateListener listener) { + sGeneralActivityStateListeners.addObserver(listener); + } + + /** + * Registers the given listener to receive state changes for {@code activity}. After a call to + * {@link ActivityStateListener#onActivityStateChange(Activity, int)} with + * {@link ActivityState#DESTROYED} all listeners associated with that particular + * {@link Activity} are removed. + * @param listener Listener to receive state changes. + * @param activity Activity to track or {@code null} to track all activities. + */ + @SuppressLint("NewApi") + public static void registerStateListenerForActivity(ActivityStateListener listener, + Activity activity) { + if (activity == null) { + throw new IllegalStateException("Attempting to register listener on a null activity."); + } + ApplicationStatus.assertInitialized(); + + ActivityInfo info = sActivityInfo.get(activity); + if (info == null) { + throw new IllegalStateException( + "Attempting to register listener on an untracked activity."); + } + assert info.getStatus() != ActivityState.DESTROYED; + info.getListeners().addObserver(listener); + } + + /** + * Unregisters the given listener from receiving activity state changes. + * @param listener Listener that doesn't want to receive state changes. + */ + public static void unregisterActivityStateListener(ActivityStateListener listener) { + sGeneralActivityStateListeners.removeObserver(listener); + + // Loop through all observer lists for all activities and remove the listener. + for (ActivityInfo info : sActivityInfo.values()) { + info.getListeners().removeObserver(listener); + } + } + + /** + * Registers the given listener to receive state changes for the application. + * @param listener Listener to receive state state changes. + */ + public static void registerApplicationStateListener(ApplicationStateListener listener) { + sApplicationStateListeners.addObserver(listener); + } + + /** + * Unregisters the given listener from receiving state changes. + * @param listener Listener that doesn't want to receive state changes. + */ + public static void unregisterApplicationStateListener(ApplicationStateListener listener) { + sApplicationStateListeners.removeObserver(listener); + } + + /** + * Robolectric JUnit tests create a new application between each test, while all the context + * in static classes isn't reset. This function allows to reset the application status to avoid + * being in a dirty state. + */ + public static void destroyForJUnitTests() { + sApplicationStateListeners.clear(); + sGeneralActivityStateListeners.clear(); + sActivityInfo.clear(); + sWindowFocusListeners.clear(); + sIsInitialized = false; + synchronized (sCurrentApplicationStateLock) { + sCurrentApplicationState = determineApplicationState(); + } + sActivity = null; + sNativeApplicationStateListener = null; + } + + /** + * Registers the single thread-safe native activity status listener. + * This handles the case where the caller is not on the main thread. + * Note that this is used by a leaky singleton object from the native + * side, hence lifecycle management is greatly simplified. + */ + @CalledByNative + private static void registerThreadSafeNativeApplicationStateListener() { + ThreadUtils.runOnUiThread(new Runnable() { + @Override + public void run() { + if (sNativeApplicationStateListener != null) return; + + sNativeApplicationStateListener = new ApplicationStateListener() { + @Override + public void onApplicationStateChange(int newState) { + nativeOnApplicationStateChange(newState); + } + }; + registerApplicationStateListener(sNativeApplicationStateListener); + } + }); + } + + /** + * Determines the current application state as defined by {@link ApplicationState}. This will + * loop over all the activities and check their state to determine what the general application + * state should be. + * @return HAS_RUNNING_ACTIVITIES if any activity is not paused, stopped, or destroyed. + * HAS_PAUSED_ACTIVITIES if none are running and one is paused. + * HAS_STOPPED_ACTIVITIES if none are running/paused and one is stopped. + * HAS_DESTROYED_ACTIVITIES if none are running/paused/stopped. + */ + @ApplicationState + private static int determineApplicationState() { + boolean hasPausedActivity = false; + boolean hasStoppedActivity = false; + + for (ActivityInfo info : sActivityInfo.values()) { + int state = info.getStatus(); + if (state != ActivityState.PAUSED + && state != ActivityState.STOPPED + && state != ActivityState.DESTROYED) { + return ApplicationState.HAS_RUNNING_ACTIVITIES; + } else if (state == ActivityState.PAUSED) { + hasPausedActivity = true; + } else if (state == ActivityState.STOPPED) { + hasStoppedActivity = true; + } + } + + if (hasPausedActivity) return ApplicationState.HAS_PAUSED_ACTIVITIES; + if (hasStoppedActivity) return ApplicationState.HAS_STOPPED_ACTIVITIES; + return ApplicationState.HAS_DESTROYED_ACTIVITIES; + } + + // Called to notify the native side of state changes. + // IMPORTANT: This is always called on the main thread! + private static native void nativeOnApplicationStateChange(@ApplicationState int newState); +}
diff --git a/src/base/android/java/src/org/chromium/base/BaseSwitches.java b/src/base/android/java/src/org/chromium/base/BaseSwitches.java new file mode 100644 index 0000000..fe47cdd --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/BaseSwitches.java
@@ -0,0 +1,32 @@ +// Copyright 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. + +package org.chromium.base; + +/** + * Contains all of the command line switches that are specific to the base/ + * portion of Chromium on Android. + */ +public abstract class BaseSwitches { + // Block ChildProcessMain thread of render process service until a Java debugger is attached. + // To pause even earlier: am set-debug-app org.chromium.chrome:sandbox_process0 + // However, this flag is convenient when you don't know the process number, or want + // all renderers to pause (set-debug-app applies to only one process at a time). + public static final String RENDERER_WAIT_FOR_JAVA_DEBUGGER = "renderer-wait-for-java-debugger"; + + // Force low-end device mode when set. + public static final String ENABLE_LOW_END_DEVICE_MODE = "enable-low-end-device-mode"; + + // Force disabling of low-end device mode when set. + public static final String DISABLE_LOW_END_DEVICE_MODE = "disable-low-end-device-mode"; + + // Adds additional thread idle time information into the trace event output. + public static final String ENABLE_IDLE_TRACING = "enable-idle-tracing"; + + // Default country code to be used for search engine localization. + public static final String DEFAULT_COUNTRY_CODE_AT_INSTALL = "default-country-code"; + + // Prevent instantiation. + private BaseSwitches() {} +}
diff --git a/src/base/android/java/src/org/chromium/base/BuildInfo.java b/src/base/android/java/src/org/chromium/base/BuildInfo.java new file mode 100644 index 0000000..111bca8 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/BuildInfo.java
@@ -0,0 +1,193 @@ +// Copyright 2012 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. + +package org.chromium.base; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Build; +import android.os.Build.VERSION; +import android.text.TextUtils; + +import org.chromium.base.annotations.CalledByNative; + +/** + * BuildInfo is a utility class providing easy access to {@link PackageInfo} information. This is + * primarily of use for accessing package information from native code. + */ +public class BuildInfo { + private static final String TAG = "BuildInfo"; + private static final int MAX_FINGERPRINT_LENGTH = 128; + + private static PackageInfo sBrowserPackageInfo; + private static boolean sInitialized; + + /** The application name (e.g. "Chrome"). For WebView, this is name of the embedding app. */ + public final String hostPackageLabel; + /** By default: same as versionCode. For WebView: versionCode of the embedding app. */ + public final int hostVersionCode; + /** The packageName of Chrome/WebView. Use application context for host app packageName. */ + public final String packageName; + /** The versionCode of the apk. */ + public final int versionCode; + /** The versionName of Chrome/WebView. Use application context for host app versionName. */ + public final String versionName; + /** Result of PackageManager.getInstallerPackageName(). Never null, but may be "". */ + public final String installerPackageName; + /** The versionCode of Play Services (for crash reporting). */ + public final String gmsVersionCode; + /** Formatted ABI string (for crash reporting). */ + public final String abiString; + /** Truncated version of Build.FINGERPRINT (for crash reporting). */ + public final String androidBuildFingerprint; + /** A string that is different each time the apk changes. */ + public final String extractedFileSuffix; + /** Whether or not the device has apps installed for using custom themes. */ + public final String customThemes; + /** Product version as stored in Android resources. */ + public final String resourcesVersion; + + private static class Holder { private static BuildInfo sInstance = new BuildInfo(); } + + @CalledByNative + private static String[] getAll() { + BuildInfo buildInfo = getInstance(); + String hostPackageName = ContextUtils.getApplicationContext().getPackageName(); + return new String[] { + Build.BRAND, Build.DEVICE, Build.ID, Build.MANUFACTURER, Build.MODEL, + String.valueOf(Build.VERSION.SDK_INT), Build.TYPE, Build.BOARD, hostPackageName, + String.valueOf(buildInfo.hostVersionCode), buildInfo.hostPackageLabel, + buildInfo.packageName, String.valueOf(buildInfo.versionCode), buildInfo.versionName, + buildInfo.androidBuildFingerprint, buildInfo.gmsVersionCode, + buildInfo.installerPackageName, buildInfo.abiString, BuildConfig.FIREBASE_APP_ID, + buildInfo.customThemes, buildInfo.resourcesVersion, buildInfo.extractedFileSuffix, + isAtLeastP() ? "1" : "0", + }; + } + + private static String nullToEmpty(CharSequence seq) { + return seq == null ? "" : seq.toString(); + } + + /** + * @param packageInfo Package for Chrome/WebView (as opposed to host app). + */ + public static void setBrowserPackageInfo(PackageInfo packageInfo) { + assert !sInitialized; + sBrowserPackageInfo = packageInfo; + } + + public static BuildInfo getInstance() { + return Holder.sInstance; + } + + private BuildInfo() { + sInitialized = true; + try { + Context appContext = ContextUtils.getApplicationContext(); + String hostPackageName = appContext.getPackageName(); + PackageManager pm = appContext.getPackageManager(); + PackageInfo pi = pm.getPackageInfo(hostPackageName, 0); + hostVersionCode = pi.versionCode; + if (sBrowserPackageInfo != null) { + packageName = sBrowserPackageInfo.packageName; + versionCode = sBrowserPackageInfo.versionCode; + versionName = nullToEmpty(sBrowserPackageInfo.versionName); + sBrowserPackageInfo = null; + } else { + packageName = hostPackageName; + versionCode = hostVersionCode; + versionName = nullToEmpty(pi.versionName); + } + + hostPackageLabel = nullToEmpty(pm.getApplicationLabel(pi.applicationInfo)); + installerPackageName = nullToEmpty(pm.getInstallerPackageName(packageName)); + + PackageInfo gmsPackageInfo = null; + try { + gmsPackageInfo = pm.getPackageInfo("com.google.android.gms", 0); + } catch (NameNotFoundException e) { + Log.d(TAG, "GMS package is not found.", e); + } + gmsVersionCode = gmsPackageInfo != null ? String.valueOf(gmsPackageInfo.versionCode) + : "gms versionCode not available."; + + String hasCustomThemes = "true"; + try { + // Substratum is a theme engine that enables users to use custom themes provided + // by theme apps. Sometimes these can cause crashs if not installed correctly. + // These crashes can be difficult to debug, so knowing if the theme manager is + // present on the device is useful (http://crbug.com/820591). + pm.getPackageInfo("projekt.substratum", 0); + } catch (NameNotFoundException e) { + hasCustomThemes = "false"; + } + customThemes = hasCustomThemes; + + String currentResourcesVersion = "Not Enabled"; + // Controlled by target specific build flags. + if (BuildConfig.R_STRING_PRODUCT_VERSION != 0) { + try { + // This value can be compared with the actual product version to determine if + // corrupted resources were the cause of a crash. This can happen if the app + // loads resources from the outdated package during an update + // (http://crbug.com/820591). + currentResourcesVersion = ContextUtils.getApplicationContext().getString( + BuildConfig.R_STRING_PRODUCT_VERSION); + } catch (Exception e) { + currentResourcesVersion = "Not found"; + } + } + resourcesVersion = currentResourcesVersion; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + abiString = TextUtils.join(", ", Build.SUPPORTED_ABIS); + } else { + abiString = String.format("ABI1: %s, ABI2: %s", Build.CPU_ABI, Build.CPU_ABI2); + } + + // Use lastUpdateTime when developing locally, since versionCode does not normally + // change in this case. + long version = versionCode > 10 ? versionCode : pi.lastUpdateTime; + extractedFileSuffix = String.format("@%x", version); + + // The value is truncated, as this is used for crash and UMA reporting. + androidBuildFingerprint = Build.FINGERPRINT.substring( + 0, Math.min(Build.FINGERPRINT.length(), MAX_FINGERPRINT_LENGTH)); + } catch (NameNotFoundException e) { + throw new RuntimeException(e); + } + } + + /** + * Check if this is a debuggable build of Android. Use this to enable developer-only features. + */ + public static boolean isDebugAndroid() { + return "eng".equals(Build.TYPE) || "userdebug".equals(Build.TYPE); + } + + // The markers Begin:BuildCompat and End:BuildCompat delimit code + // that is autogenerated from Android sources. + // Begin:BuildCompat P + + /** + * Checks if the device is running on a pre-release version of Android P or newer. + * <p> + * @return {@code true} if P APIs are available for use, {@code false} otherwise + */ + public static boolean isAtLeastP() { + return VERSION.SDK_INT >= 28; + } + + /** + * Checks if the application targets at least released SDK P + */ + public static boolean targetsAtLeastP() { + return ContextUtils.getApplicationContext().getApplicationInfo().targetSdkVersion >= 28; + } + + // End:BuildCompat +}
diff --git a/src/base/android/java/src/org/chromium/base/Callback.java b/src/base/android/java/src/org/chromium/base/Callback.java new file mode 100644 index 0000000..f5f20b9 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/Callback.java
@@ -0,0 +1,43 @@ +// Copyright 2015 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. + +package org.chromium.base; + +import org.chromium.base.annotations.CalledByNative; + +/** + * A simple single-argument callback to handle the result of a computation. + * + * @param <T> The type of the computation's result. + */ +public interface Callback<T> { + /** + * Invoked with the result of a computation. + */ + void onResult(T result); + + /** + * JNI Generator does not know how to target static methods on interfaces + * (which is new in Java 8, and requires desugaring). + */ + abstract class Helper { + @SuppressWarnings("unchecked") + @CalledByNative("Helper") + static void onObjectResultFromNative(Callback callback, Object result) { + callback.onResult(result); + } + + @SuppressWarnings("unchecked") + @CalledByNative("Helper") + static void onBooleanResultFromNative(Callback callback, boolean result) { + callback.onResult(Boolean.valueOf(result)); + } + + @SuppressWarnings("unchecked") + @CalledByNative("Helper") + static void onIntResultFromNative(Callback callback, int result) { + callback.onResult(Integer.valueOf(result)); + } + } +}
diff --git a/src/base/android/java/src/org/chromium/base/CollectionUtil.java b/src/base/android/java/src/org/chromium/base/CollectionUtil.java new file mode 100644 index 0000000..6093380 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/CollectionUtil.java
@@ -0,0 +1,99 @@ +// Copyright 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. + +package org.chromium.base; + +import android.support.annotation.NonNull; +import android.util.Pair; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * Functions used for easier initialization of Java collections. Inspired by + * functionality in com.google.common.collect in Guava but cherry-picked to + * bare-minimum functionality to avoid bloat. (http://crbug.com/272790 provides + * further details) + */ +public final class CollectionUtil { + private CollectionUtil() {} + + @SafeVarargs + public static <E> HashSet<E> newHashSet(E... elements) { + HashSet<E> set = new HashSet<E>(elements.length); + Collections.addAll(set, elements); + return set; + } + + @SafeVarargs + public static <E> ArrayList<E> newArrayList(E... elements) { + ArrayList<E> list = new ArrayList<E>(elements.length); + Collections.addAll(list, elements); + return list; + } + + @VisibleForTesting + public static <E> ArrayList<E> newArrayList(Iterable<E> iterable) { + ArrayList<E> list = new ArrayList<E>(); + for (E element : iterable) { + list.add(element); + } + return list; + } + + @SafeVarargs + public static <K, V> HashMap<K, V> newHashMap(Pair<? extends K, ? extends V>... entries) { + HashMap<K, V> map = new HashMap<>(); + for (Pair<? extends K, ? extends V> entry : entries) { + map.put(entry.first, entry.second); + } + return map; + } + + public static boolean[] booleanListToBooleanArray(@NonNull List<Boolean> list) { + boolean[] array = new boolean[list.size()]; + for (int i = 0; i < list.size(); i++) { + array[i] = list.get(i); + } + return array; + } + + public static int[] integerListToIntArray(@NonNull List<Integer> list) { + int[] array = new int[list.size()]; + for (int i = 0; i < list.size(); i++) { + array[i] = list.get(i); + } + return array; + } + + public static long[] longListToLongArray(@NonNull List<Long> list) { + long[] array = new long[list.size()]; + for (int i = 0; i < list.size(); i++) { + array[i] = list.get(i); + } + return array; + } + + // This is a utility helper method that adds functionality available in API 24 (see + // Collection.forEach). + public static <T> void forEach(Collection<? extends T> collection, Callback<T> worker) { + for (T entry : collection) worker.onResult(entry); + } + + // This is a utility helper method that adds functionality available in API 24 (see + // Collection.forEach). + @SuppressWarnings("unchecked") + public static <K, V> void forEach( + Map<? extends K, ? extends V> map, Callback<Entry<K, V>> worker) { + for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) { + worker.onResult((Map.Entry<K, V>) entry); + } + } +}
diff --git a/src/base/android/java/src/org/chromium/base/CommandLine.java b/src/base/android/java/src/org/chromium/base/CommandLine.java new file mode 100644 index 0000000..963b146 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/CommandLine.java
@@ -0,0 +1,389 @@ +// Copyright 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. + +package org.chromium.base; + +import android.support.annotation.Nullable; +import android.text.TextUtils; +import android.util.Log; + +import org.chromium.base.annotations.MainDex; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Java mirror of base/command_line.h. + * Android applications don't have command line arguments. Instead, they're "simulated" by reading a + * file at a specific location early during startup. Applications each define their own files, e.g., + * ContentShellApplication.COMMAND_LINE_FILE. +**/ +@MainDex +public abstract class CommandLine { + // Public abstract interface, implemented in derived classes. + // All these methods reflect their native-side counterparts. + /** + * Returns true if this command line contains the given switch. + * (Switch names ARE case-sensitive). + */ + @VisibleForTesting + public abstract boolean hasSwitch(String switchString); + + /** + * Return the value associated with the given switch, or null. + * @param switchString The switch key to lookup. It should NOT start with '--' ! + * @return switch value, or null if the switch is not set or set to empty. + */ + public abstract String getSwitchValue(String switchString); + + /** + * Return the value associated with the given switch, or {@code defaultValue} if the switch + * was not specified. + * @param switchString The switch key to lookup. It should NOT start with '--' ! + * @param defaultValue The default value to return if the switch isn't set. + * @return Switch value, or {@code defaultValue} if the switch is not set or set to empty. + */ + public String getSwitchValue(String switchString, String defaultValue) { + String value = getSwitchValue(switchString); + return TextUtils.isEmpty(value) ? defaultValue : value; + } + + /** + * Append a switch to the command line. There is no guarantee + * this action happens before the switch is needed. + * @param switchString the switch to add. It should NOT start with '--' ! + */ + @VisibleForTesting + public abstract void appendSwitch(String switchString); + + /** + * Append a switch and value to the command line. There is no + * guarantee this action happens before the switch is needed. + * @param switchString the switch to add. It should NOT start with '--' ! + * @param value the value for this switch. + * For example, --foo=bar becomes 'foo', 'bar'. + */ + public abstract void appendSwitchWithValue(String switchString, String value); + + /** + * Append switch/value items in "command line" format (excluding argv[0] program name). + * E.g. { '--gofast', '--username=fred' } + * @param array an array of switch or switch/value items in command line format. + * Unlike the other append routines, these switches SHOULD start with '--' . + * Unlike init(), this does not include the program name in array[0]. + */ + public abstract void appendSwitchesAndArguments(String[] array); + + /** + * Determine if the command line is bound to the native (JNI) implementation. + * @return true if the underlying implementation is delegating to the native command line. + */ + public boolean isNativeImplementation() { + return false; + } + + /** + * Returns the switches and arguments passed into the program, with switches and their + * values coming before all of the arguments. + */ + protected abstract String[] getCommandLineArguments(); + + /** + * Destroy the command line. Called when a different instance is set. + * @see #setInstance + */ + protected void destroy() {} + + private static final AtomicReference<CommandLine> sCommandLine = + new AtomicReference<CommandLine>(); + + /** + * @return true if the command line has already been initialized. + */ + public static boolean isInitialized() { + return sCommandLine.get() != null; + } + + // Equivalent to CommandLine::ForCurrentProcess in C++. + @VisibleForTesting + public static CommandLine getInstance() { + CommandLine commandLine = sCommandLine.get(); + assert commandLine != null; + return commandLine; + } + + /** + * Initialize the singleton instance, must be called exactly once (either directly or + * via one of the convenience wrappers below) before using the static singleton instance. + * @param args command line flags in 'argv' format: args[0] is the program name. + */ + public static void init(@Nullable String[] args) { + setInstance(new JavaCommandLine(args)); + } + + /** + * Initialize the command line from the command-line file. + * + * @param file The fully qualified command line file. + */ + public static void initFromFile(String file) { + char[] buffer = readFileAsUtf8(file); + init(buffer == null ? null : tokenizeQuotedArguments(buffer)); + } + + /** + * Resets both the java proxy and the native command lines. This allows the entire + * command line initialization to be re-run including the call to onJniLoaded. + */ + @VisibleForTesting + public static void reset() { + setInstance(null); + } + + /** + * Parse command line flags from a flat buffer, supporting double-quote enclosed strings + * containing whitespace. argv elements are derived by splitting the buffer on whitepace; + * double quote characters may enclose tokens containing whitespace; a double-quote literal + * may be escaped with back-slash. (Otherwise backslash is taken as a literal). + * @param buffer A command line in command line file format as described above. + * @return the tokenized arguments, suitable for passing to init(). + */ + @VisibleForTesting + static String[] tokenizeQuotedArguments(char[] buffer) { + // Just field trials can take up to 10K of command line. + if (buffer.length > 64 * 1024) { + // Check that our test runners are setting a reasonable number of flags. + throw new RuntimeException("Flags file too big: " + buffer.length); + } + + ArrayList<String> args = new ArrayList<String>(); + StringBuilder arg = null; + final char noQuote = '\0'; + final char singleQuote = '\''; + final char doubleQuote = '"'; + char currentQuote = noQuote; + for (char c : buffer) { + // Detect start or end of quote block. + if ((currentQuote == noQuote && (c == singleQuote || c == doubleQuote)) + || c == currentQuote) { + if (arg != null && arg.length() > 0 && arg.charAt(arg.length() - 1) == '\\') { + // Last char was a backslash; pop it, and treat c as a literal. + arg.setCharAt(arg.length() - 1, c); + } else { + currentQuote = currentQuote == noQuote ? c : noQuote; + } + } else if (currentQuote == noQuote && Character.isWhitespace(c)) { + if (arg != null) { + args.add(arg.toString()); + arg = null; + } + } else { + if (arg == null) arg = new StringBuilder(); + arg.append(c); + } + } + if (arg != null) { + if (currentQuote != noQuote) { + Log.w(TAG, "Unterminated quoted string: " + arg); + } + args.add(arg.toString()); + } + return args.toArray(new String[args.size()]); + } + + private static final String TAG = "CommandLine"; + private static final String SWITCH_PREFIX = "--"; + private static final String SWITCH_TERMINATOR = SWITCH_PREFIX; + private static final String SWITCH_VALUE_SEPARATOR = "="; + + public static void enableNativeProxy() { + // Make a best-effort to ensure we make a clean (atomic) switch over from the old to + // the new command line implementation. If another thread is modifying the command line + // when this happens, all bets are off. (As per the native CommandLine). + sCommandLine.set(new NativeCommandLine(getJavaSwitchesOrNull())); + } + + @Nullable + public static String[] getJavaSwitchesOrNull() { + CommandLine commandLine = sCommandLine.get(); + if (commandLine != null) { + return commandLine.getCommandLineArguments(); + } + return null; + } + + private static void setInstance(CommandLine commandLine) { + CommandLine oldCommandLine = sCommandLine.getAndSet(commandLine); + if (oldCommandLine != null) { + oldCommandLine.destroy(); + } + } + + /** + * @param fileName the file to read in. + * @return Array of chars read from the file, or null if the file cannot be read. + */ + private static char[] readFileAsUtf8(String fileName) { + File f = new File(fileName); + try (FileReader reader = new FileReader(f)) { + char[] buffer = new char[(int) f.length()]; + int charsRead = reader.read(buffer); + // charsRead < f.length() in the case of multibyte characters. + return Arrays.copyOfRange(buffer, 0, charsRead); + } catch (IOException e) { + return null; // Most likely file not found. + } + } + + private CommandLine() {} + + private static class JavaCommandLine extends CommandLine { + private HashMap<String, String> mSwitches = new HashMap<String, String>(); + private ArrayList<String> mArgs = new ArrayList<String>(); + + // The arguments begin at index 1, since index 0 contains the executable name. + private int mArgsBegin = 1; + + JavaCommandLine(@Nullable String[] args) { + if (args == null || args.length == 0 || args[0] == null) { + mArgs.add(""); + } else { + mArgs.add(args[0]); + appendSwitchesInternal(args, 1); + } + // Invariant: we always have the argv[0] program name element. + assert mArgs.size() > 0; + } + + @Override + protected String[] getCommandLineArguments() { + return mArgs.toArray(new String[mArgs.size()]); + } + + @Override + public boolean hasSwitch(String switchString) { + return mSwitches.containsKey(switchString); + } + + @Override + public String getSwitchValue(String switchString) { + // This is slightly round about, but needed for consistency with the NativeCommandLine + // version which does not distinguish empty values from key not present. + String value = mSwitches.get(switchString); + return value == null || value.isEmpty() ? null : value; + } + + @Override + public void appendSwitch(String switchString) { + appendSwitchWithValue(switchString, null); + } + + /** + * Appends a switch to the current list. + * @param switchString the switch to add. It should NOT start with '--' ! + * @param value the value for this switch. + */ + @Override + public void appendSwitchWithValue(String switchString, String value) { + mSwitches.put(switchString, value == null ? "" : value); + + // Append the switch and update the switches/arguments divider mArgsBegin. + String combinedSwitchString = SWITCH_PREFIX + switchString; + if (value != null && !value.isEmpty()) { + combinedSwitchString += SWITCH_VALUE_SEPARATOR + value; + } + + mArgs.add(mArgsBegin++, combinedSwitchString); + } + + @Override + public void appendSwitchesAndArguments(String[] array) { + appendSwitchesInternal(array, 0); + } + + // Add the specified arguments, but skipping the first |skipCount| elements. + private void appendSwitchesInternal(String[] array, int skipCount) { + boolean parseSwitches = true; + for (String arg : array) { + if (skipCount > 0) { + --skipCount; + continue; + } + + if (arg.equals(SWITCH_TERMINATOR)) { + parseSwitches = false; + } + + if (parseSwitches && arg.startsWith(SWITCH_PREFIX)) { + String[] parts = arg.split(SWITCH_VALUE_SEPARATOR, 2); + String value = parts.length > 1 ? parts[1] : null; + appendSwitchWithValue(parts[0].substring(SWITCH_PREFIX.length()), value); + } else { + mArgs.add(arg); + } + } + } + } + + private static class NativeCommandLine extends CommandLine { + public NativeCommandLine(@Nullable String[] args) { + nativeInit(args); + } + + @Override + public boolean hasSwitch(String switchString) { + return nativeHasSwitch(switchString); + } + + @Override + public String getSwitchValue(String switchString) { + return nativeGetSwitchValue(switchString); + } + + @Override + public void appendSwitch(String switchString) { + nativeAppendSwitch(switchString); + } + + @Override + public void appendSwitchWithValue(String switchString, String value) { + nativeAppendSwitchWithValue(switchString, value); + } + + @Override + public void appendSwitchesAndArguments(String[] array) { + nativeAppendSwitchesAndArguments(array); + } + + @Override + public boolean isNativeImplementation() { + return true; + } + + @Override + protected String[] getCommandLineArguments() { + assert false; + return null; + } + + @Override + protected void destroy() { + // TODO(https://crbug.com/771205): Downgrade this to an assert once we have eliminated + // tests that do this. + throw new IllegalStateException("Can't destroy native command line after startup"); + } + } + + private static native void nativeInit(String[] args); + private static native boolean nativeHasSwitch(String switchString); + private static native String nativeGetSwitchValue(String switchString); + private static native void nativeAppendSwitch(String switchString); + private static native void nativeAppendSwitchWithValue(String switchString, String value); + private static native void nativeAppendSwitchesAndArguments(String[] array); +}
diff --git a/src/base/android/java/src/org/chromium/base/CommandLineInitUtil.java b/src/base/android/java/src/org/chromium/base/CommandLineInitUtil.java new file mode 100644 index 0000000..e51b95d --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/CommandLineInitUtil.java
@@ -0,0 +1,103 @@ +// Copyright 2015 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. + +package org.chromium.base; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Build; +import android.provider.Settings; +import android.support.annotation.Nullable; + +import java.io.File; + +/** + * Provides implementation of command line initialization for Android. + */ +public final class CommandLineInitUtil { + /** + * The location of the command line file needs to be in a protected + * directory so requires root access to be tweaked, i.e., no other app in a + * regular (non-rooted) device can change this file's contents. + * See below for debugging on a regular (non-rooted) device. + */ + private static final String COMMAND_LINE_FILE_PATH = "/data/local"; + + /** + * This path (writable by the shell in regular non-rooted "user" builds) is used when: + * 1) The "debug app" is set to the application calling this. + * and + * 2) ADB is enabled. + * 3) Force enabled by the embedder. + */ + private static final String COMMAND_LINE_FILE_PATH_DEBUG_APP = "/data/local/tmp"; + + private CommandLineInitUtil() { + } + + /** + * Initializes the CommandLine class, pulling command line arguments from {@code fileName}. + * @param fileName The name of the command line file to pull arguments from. + */ + public static void initCommandLine(String fileName) { + initCommandLine(fileName, null); + } + + /** + * Initializes the CommandLine class, pulling command line arguments from {@code fileName}. + * @param fileName The name of the command line file to pull arguments from. + * @param shouldUseDebugFlags If non-null, returns whether debug flags are allowed to be used. + */ + public static void initCommandLine( + String fileName, @Nullable Supplier<Boolean> shouldUseDebugFlags) { + assert !CommandLine.isInitialized(); + File commandLineFile = new File(COMMAND_LINE_FILE_PATH_DEBUG_APP, fileName); + // shouldUseDebugCommandLine() uses IPC, so don't bother calling it if no flags file exists. + boolean debugFlagsExist = commandLineFile.exists(); + if (!debugFlagsExist || !shouldUseDebugCommandLine(shouldUseDebugFlags)) { + commandLineFile = new File(COMMAND_LINE_FILE_PATH, fileName); + } + CommandLine.initFromFile(commandLineFile.getPath()); + } + + /** + * Use an alternative path if: + * - The current build is "eng" or "userdebug", OR + * - adb is enabled and this is the debug app, OR + * - Force enabled by the embedder. + * @param shouldUseDebugFlags If non-null, returns whether debug flags are allowed to be used. + */ + private static boolean shouldUseDebugCommandLine( + @Nullable Supplier<Boolean> shouldUseDebugFlags) { + if (shouldUseDebugFlags != null && shouldUseDebugFlags.get()) return true; + Context context = ContextUtils.getApplicationContext(); + String debugApp = Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1 + ? getDebugAppPreJBMR1(context) + : getDebugAppJBMR1(context); + // Check isDebugAndroid() last to get full code coverage when using userdebug devices. + return context.getPackageName().equals(debugApp) || BuildInfo.isDebugAndroid(); + } + + @SuppressLint("NewApi") + private static String getDebugAppJBMR1(Context context) { + boolean adbEnabled = Settings.Global.getInt(context.getContentResolver(), + Settings.Global.ADB_ENABLED, 0) == 1; + if (adbEnabled) { + return Settings.Global.getString(context.getContentResolver(), + Settings.Global.DEBUG_APP); + } + return null; + } + + @SuppressWarnings("deprecation") + private static String getDebugAppPreJBMR1(Context context) { + boolean adbEnabled = Settings.System.getInt(context.getContentResolver(), + Settings.System.ADB_ENABLED, 0) == 1; + if (adbEnabled) { + return Settings.System.getString(context.getContentResolver(), + Settings.System.DEBUG_APP); + } + return null; + } +}
diff --git a/src/base/android/java/src/org/chromium/base/ContentUriUtils.java b/src/base/android/java/src/org/chromium/base/ContentUriUtils.java new file mode 100644 index 0000000..40a5c1d --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/ContentUriUtils.java
@@ -0,0 +1,280 @@ +// Copyright 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. + +package org.chromium.base; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.ParcelFileDescriptor; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.support.annotation.Nullable; +import android.text.TextUtils; +import android.util.Log; +import android.webkit.MimeTypeMap; + +import org.chromium.base.annotations.CalledByNative; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +/** + * This class provides methods to access content URI schemes. + */ +public abstract class ContentUriUtils { + private static final String TAG = "ContentUriUtils"; + private static FileProviderUtil sFileProviderUtil; + + // Guards access to sFileProviderUtil. + private static final Object sLock = new Object(); + + /** + * Provides functionality to translate a file into a content URI for use + * with a content provider. + */ + public interface FileProviderUtil { + /** + * Generate a content URI from the given file. + * + * @param file The file to be translated. + */ + Uri getContentUriFromFile(File file); + } + + // Prevent instantiation. + private ContentUriUtils() {} + + public static void setFileProviderUtil(FileProviderUtil util) { + synchronized (sLock) { + sFileProviderUtil = util; + } + } + + public static Uri getContentUriFromFile(File file) { + synchronized (sLock) { + if (sFileProviderUtil != null) { + return sFileProviderUtil.getContentUriFromFile(file); + } + } + return null; + } + + /** + * Opens the content URI for reading, and returns the file descriptor to + * the caller. The caller is responsible for closing the file descriptor. + * + * @param uriString the content URI to open + * @return file descriptor upon success, or -1 otherwise. + */ + @CalledByNative + public static int openContentUriForRead(String uriString) { + AssetFileDescriptor afd = getAssetFileDescriptor(uriString); + if (afd != null) { + return afd.getParcelFileDescriptor().detachFd(); + } + return -1; + } + + /** + * Check whether a content URI exists. + * + * @param uriString the content URI to query. + * @return true if the URI exists, or false otherwise. + */ + @CalledByNative + public static boolean contentUriExists(String uriString) { + AssetFileDescriptor asf = null; + try { + asf = getAssetFileDescriptor(uriString); + return asf != null; + } finally { + // Do not use StreamUtil.closeQuietly here, as AssetFileDescriptor + // does not implement Closeable until KitKat. + if (asf != null) { + try { + asf.close(); + } catch (IOException e) { + // Closing quietly. + } + } + } + } + + /** + * Retrieve the MIME type for the content URI. + * + * @param uriString the content URI to look up. + * @return MIME type or null if the input params are empty or invalid. + */ + @CalledByNative + public static String getMimeType(String uriString) { + ContentResolver resolver = ContextUtils.getApplicationContext().getContentResolver(); + Uri uri = Uri.parse(uriString); + if (isVirtualDocument(uri)) { + String[] streamTypes = resolver.getStreamTypes(uri, "*/*"); + return (streamTypes != null && streamTypes.length > 0) ? streamTypes[0] : null; + } + return resolver.getType(uri); + } + + /** + * Helper method to open a content URI and returns the ParcelFileDescriptor. + * + * @param uriString the content URI to open. + * @return AssetFileDescriptor of the content URI, or NULL if the file does not exist. + */ + private static AssetFileDescriptor getAssetFileDescriptor(String uriString) { + ContentResolver resolver = ContextUtils.getApplicationContext().getContentResolver(); + Uri uri = Uri.parse(uriString); + + try { + if (isVirtualDocument(uri)) { + String[] streamTypes = resolver.getStreamTypes(uri, "*/*"); + if (streamTypes != null && streamTypes.length > 0) { + AssetFileDescriptor afd = + resolver.openTypedAssetFileDescriptor(uri, streamTypes[0], null); + if (afd != null && afd.getStartOffset() != 0) { + // Do not use StreamUtil.closeQuietly here, as AssetFileDescriptor + // does not implement Closeable until KitKat. + try { + afd.close(); + } catch (IOException e) { + // Closing quietly. + } + throw new SecurityException("Cannot open files with non-zero offset type."); + } + return afd; + } + } else { + ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r"); + if (pfd != null) { + return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH); + } + } + } catch (FileNotFoundException e) { + Log.w(TAG, "Cannot find content uri: " + uriString, e); + } catch (SecurityException e) { + Log.w(TAG, "Cannot open content uri: " + uriString, e); + } catch (Exception e) { + Log.w(TAG, "Unknown content uri: " + uriString, e); + } + return null; + } + + /** + * Method to resolve the display name of a content URI. + * + * @param uri the content URI to be resolved. + * @param context {@link Context} in interest. + * @param columnField the column field to query. + * @return the display name of the @code uri if present in the database + * or an empty string otherwise. + */ + public static String getDisplayName(Uri uri, Context context, String columnField) { + if (uri == null) return ""; + ContentResolver contentResolver = context.getContentResolver(); + try (Cursor cursor = contentResolver.query(uri, null, null, null, null)) { + if (cursor != null && cursor.getCount() >= 1) { + cursor.moveToFirst(); + int displayNameIndex = cursor.getColumnIndex(columnField); + if (displayNameIndex == -1) { + return ""; + } + String displayName = cursor.getString(displayNameIndex); + // For Virtual documents, try to modify the file extension so it's compatible + // with the alternative MIME type. + if (hasVirtualFlag(cursor)) { + String[] mimeTypes = contentResolver.getStreamTypes(uri, "*/*"); + if (mimeTypes != null && mimeTypes.length > 0) { + String ext = + MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeTypes[0]); + if (ext != null) { + // Just append, it's simpler and more secure than altering an + // existing extension. + displayName += "." + ext; + } + } + } + return displayName; + } + } catch (NullPointerException e) { + // Some android models don't handle the provider call correctly. + // see crbug.com/345393 + return ""; + } + return ""; + } + + /** + * Method to resolve the display name of a content URI if possible. + * + * @param uriString the content URI to look up. + * @return the display name of the uri if present in the database or null otherwise. + */ + @Nullable + @CalledByNative + public static String maybeGetDisplayName(String uriString) { + Uri uri = Uri.parse(uriString); + + try { + String displayName = getDisplayName(uri, ContextUtils.getApplicationContext(), + MediaStore.MediaColumns.DISPLAY_NAME); + return TextUtils.isEmpty(displayName) ? null : displayName; + } catch (Exception e) { + // There are a few Exceptions we can hit here (e.g. SecurityException), but we don't + // particularly care what kind of Exception we hit. If we hit one, just don't return a + // display name. + Log.w(TAG, "Cannot open content uri: " + uriString, e); + } + + // If we are unable to query the content URI, just return null. + return null; + } + + /** + * Checks whether the passed Uri represents a virtual document. + * + * @param uri the content URI to be resolved. + * @return True for virtual file, false for any other file. + */ + private static boolean isVirtualDocument(Uri uri) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return false; + if (uri == null) return false; + if (!DocumentsContract.isDocumentUri(ContextUtils.getApplicationContext(), uri)) { + return false; + } + ContentResolver contentResolver = ContextUtils.getApplicationContext().getContentResolver(); + try (Cursor cursor = contentResolver.query(uri, null, null, null, null)) { + if (cursor != null && cursor.getCount() >= 1) { + cursor.moveToFirst(); + return hasVirtualFlag(cursor); + } + } catch (NullPointerException e) { + // Some android models don't handle the provider call correctly. + // see crbug.com/345393 + return false; + } + return false; + } + + /** + * Checks whether the passed cursor for a document has a virtual document flag. + * + * The called must close the passed cursor. + * + * @param cursor Cursor with COLUMN_FLAGS. + * @return True for virtual file, false for any other file. + */ + private static boolean hasVirtualFlag(Cursor cursor) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return false; + int index = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_FLAGS); + return index > -1 + && (cursor.getLong(index) & DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT) != 0; + } +}
diff --git a/src/base/android/java/src/org/chromium/base/ContextUtils.java b/src/base/android/java/src/org/chromium/base/ContextUtils.java new file mode 100644 index 0000000..a592ac8 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/ContextUtils.java
@@ -0,0 +1,171 @@ +// Copyright 2015 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. + +package org.chromium.base; + +import android.app.Application; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.SharedPreferences; +import android.content.res.AssetManager; +import android.os.Process; +import android.preference.PreferenceManager; + +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.MainDex; + +/** + * This class provides Android application context related utility methods. + */ +@JNINamespace("base::android") +public class ContextUtils { + private static final String TAG = "ContextUtils"; + private static Context sApplicationContext; + // TODO(agrieve): Remove sProcessName caching when we stop supporting JB. + private static String sProcessName; + + /** + * Initialization-on-demand holder. This exists for thread-safe lazy initialization. + */ + private static class Holder { + // Not final for tests. + private static SharedPreferences sSharedPreferences = fetchAppSharedPreferences(); + } + + /** + * Get the Android application context. + * + * Under normal circumstances there is only one application context in a process, so it's safe + * to treat this as a global. In WebView it's possible for more than one app using WebView to be + * running in a single process, but this mechanism is rarely used and this is not the only + * problem in that scenario, so we don't currently forbid using it as a global. + * + * Do not downcast the context returned by this method to Application (or any subclass). It may + * not be an Application object; it may be wrapped in a ContextWrapper. The only assumption you + * may make is that it is a Context whose lifetime is the same as the lifetime of the process. + */ + public static Context getApplicationContext() { + return sApplicationContext; + } + + /** + * Initializes the java application context. + * + * This should be called exactly once early on during startup, before native is loaded and + * before any other clients make use of the application context through this class. + * + * @param appContext The application context. + */ + @MainDex // TODO(agrieve): Could add to whole class if not for ApplicationStatus.initialize(). + public static void initApplicationContext(Context appContext) { + // Conceding that occasionally in tests, native is loaded before the browser process is + // started, in which case the browser process re-sets the application context. + if (sApplicationContext != null && sApplicationContext != appContext) { + throw new RuntimeException("Attempting to set multiple global application contexts."); + } + initJavaSideApplicationContext(appContext); + } + + /** + * Only called by the static holder class and tests. + * + * @return The application-wide shared preferences. + */ + private static SharedPreferences fetchAppSharedPreferences() { + return PreferenceManager.getDefaultSharedPreferences(sApplicationContext); + } + + /** + * This is used to ensure that we always use the application context to fetch the default shared + * preferences. This avoids needless I/O for android N and above. It also makes it clear that + * the app-wide shared preference is desired, rather than the potentially context-specific one. + * + * @return application-wide shared preferences. + */ + public static SharedPreferences getAppSharedPreferences() { + return Holder.sSharedPreferences; + } + + /** + * Occasionally tests cannot ensure the application context doesn't change between tests (junit) + * and sometimes specific tests has its own special needs, initApplicationContext should be used + * as much as possible, but this method can be used to override it. + * + * @param appContext The new application context. + */ + @VisibleForTesting + public static void initApplicationContextForTests(Context appContext) { + // ApplicationStatus.initialize should be called to setup activity tracking for tests + // that use Robolectric and set the application context manually. Instead of changing all + // tests that do so, the call was put here instead. + // TODO(mheikal): Require param to be of type Application + if (appContext instanceof Application) { + ApplicationStatus.initialize((Application) appContext); + } + initJavaSideApplicationContext(appContext); + Holder.sSharedPreferences = fetchAppSharedPreferences(); + } + + private static void initJavaSideApplicationContext(Context appContext) { + if (appContext == null) { + throw new RuntimeException("Global application context cannot be set to null."); + } + sApplicationContext = appContext; + } + + /** + * In most cases, {@link Context#getAssets()} can be used directly. Modified resources are + * used downstream and are set up on application startup, and this method provides access to + * regular assets before that initialization is complete. + * + * This method should ONLY be used for accessing files within the assets folder. + * + * @return Application assets. + */ + public static AssetManager getApplicationAssets() { + Context context = getApplicationContext(); + while (context instanceof ContextWrapper) { + context = ((ContextWrapper) context).getBaseContext(); + } + return context.getAssets(); + } + + /** + * @return Whether the process is isolated. + */ + public static boolean isIsolatedProcess() { + try { + return (Boolean) Process.class.getMethod("isIsolated").invoke(null); + } catch (Exception e) { // No multi-catch below API level 19 for reflection exceptions. + // If fallback logic is ever needed, refer to: + // https://chromium-review.googlesource.com/c/chromium/src/+/905563/1 + throw new RuntimeException(e); + } + } + + /** @return The name of the current process. E.g. "org.chromium.chrome:privileged_process0". */ + public static String getProcessName() { + // Once we drop support JB, this method can be simplified to not cache sProcessName and call + // ActivityThread.currentProcessName(). + if (sProcessName != null) { + return sProcessName; + } + try { + // An even more convenient ActivityThread.currentProcessName() exists, but was not added + // until JB MR2. + Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread"); + Object activityThread = + activityThreadClazz.getMethod("currentActivityThread").invoke(null); + // Before JB MR2, currentActivityThread() returns null when called on a non-UI thread. + // Cache the name to allow other threads to access it. + sProcessName = + (String) activityThreadClazz.getMethod("getProcessName").invoke(activityThread); + return sProcessName; + } catch (Exception e) { // No multi-catch below API level 19 for reflection exceptions. + // If fallback logic is ever needed, refer to: + // https://chromium-review.googlesource.com/c/chromium/src/+/905563/1 + throw new RuntimeException(e); + } + } +}
diff --git a/src/base/android/java/src/org/chromium/base/CpuFeatures.java b/src/base/android/java/src/org/chromium/base/CpuFeatures.java new file mode 100644 index 0000000..ae4969c --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/CpuFeatures.java
@@ -0,0 +1,42 @@ +// Copyright 2012 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. + +package org.chromium.base; + +import org.chromium.base.annotations.JNINamespace; + +// The only purpose of this class is to allow sending CPU properties +// from the browser process to sandboxed renderer processes. This is +// needed because sandboxed processes cannot, on ARM, query the kernel +// about the CPU's properties by parsing /proc, so this operation must +// be performed in the browser process, and the result passed to +// renderer ones. +// +// For more context, see http://crbug.com/164154 +// +// Technically, this is a wrapper around the native NDK cpufeatures +// library. The exact CPU features bits are never used in Java so +// there is no point in duplicating their definitions here. +// +@JNINamespace("base::android") +public abstract class CpuFeatures { + /** + * Return the number of CPU Cores on the device. + */ + public static int getCount() { + return nativeGetCoreCount(); + } + + /** + * Return the CPU feature mask. + * This is a 64-bit integer that corresponds to the CPU's features. + * The value comes directly from android_getCpuFeatures(). + */ + public static long getMask() { + return nativeGetCpuFeatures(); + } + + private static native int nativeGetCoreCount(); + private static native long nativeGetCpuFeatures(); +}
diff --git a/src/base/android/java/src/org/chromium/base/DiscardableReferencePool.java b/src/base/android/java/src/org/chromium/base/DiscardableReferencePool.java new file mode 100644 index 0000000..566df70 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/DiscardableReferencePool.java
@@ -0,0 +1,90 @@ +// Copyright 2017 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. + +package org.chromium.base; + +import android.support.annotation.Nullable; + +import java.util.Collections; +import java.util.Set; +import java.util.WeakHashMap; + +/** + * A DiscardableReferencePool allows handing out typed references to objects ("payloads") that can + * be dropped in one batch ("drained"), e.g. under memory pressure. In contrast to {@link + * java.lang.ref.WeakReference}s, which drop their referents when they get garbage collected, a + * reference pool gives more precise control over when exactly it is drained. + * + * <p>Internally it uses a {@link WeakHashMap} with the reference itself as a key to allow the + * payloads to be garbage collected regularly when the last reference goes away before the pool is + * drained. + * + * <p>This class and its references are not thread-safe and should not be used simultaneously by + * multiple threads. + */ +public class DiscardableReferencePool { + /** + * The underlying data storage. The wildcard type parameter allows using a single pool for + * references of any type. + */ + private final Set<DiscardableReference<?>> mPool; + + public DiscardableReferencePool() { + WeakHashMap<DiscardableReference<?>, Boolean> map = new WeakHashMap<>(); + mPool = Collections.newSetFromMap(map); + } + + /** + * A reference to an object in the pool. Will be nulled out when the pool is drained. + * @param <T> The type of the object. + */ + public static class DiscardableReference<T> { + @Nullable + private T mPayload; + + private DiscardableReference(T payload) { + assert payload != null; + mPayload = payload; + } + + /** + * @return The referent, or null if the pool has been drained. + */ + @Nullable + public T get() { + return mPayload; + } + + /** + * Clear the referent. + */ + private void discard() { + assert mPayload != null; + mPayload = null; + } + } + + /** + * @param <T> The type of the object. + * @param payload The payload to add to the pool. + * @return A new reference to the {@code payload}. + */ + public <T> DiscardableReference<T> put(T payload) { + assert payload != null; + DiscardableReference<T> reference = new DiscardableReference<>(payload); + mPool.add(reference); + return reference; + } + + /** + * Drains the pool, removing all references to objects in the pool and therefore allowing them + * to be garbage collected. + */ + public void drain() { + for (DiscardableReference<?> ref : mPool) { + ref.discard(); + } + mPool.clear(); + } +}
diff --git a/src/base/android/java/src/org/chromium/base/EarlyTraceEvent.java b/src/base/android/java/src/org/chromium/base/EarlyTraceEvent.java new file mode 100644 index 0000000..f3f769d --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/EarlyTraceEvent.java
@@ -0,0 +1,338 @@ +// Copyright 2016 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. + +package org.chromium.base; + +import android.annotation.SuppressLint; +import android.os.Build; +import android.os.Process; +import android.os.StrictMode; +import android.os.SystemClock; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.MainDex; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** Support for early tracing, before the native library is loaded. + * + * This is limited, as: + * - Arguments are not supported + * - Thread time is not reported + * - Two events with the same name cannot be in progress at the same time. + * + * Events recorded here are buffered in Java until the native library is available. Then it waits + * for the completion of pending events, and sends the events to the native side. + * + * Locking: This class is threadsafe. It is enabled when general tracing is, and then disabled when + * tracing is enabled from the native side. Event completions are still processed as long + * as some are pending, then early tracing is permanently disabled after dumping the + * events. This means that if any early event is still pending when tracing is disabled, + * all early events are dropped. + */ +@JNINamespace("base::android") +@MainDex +public class EarlyTraceEvent { + // Must be kept in sync with the native kAndroidTraceConfigFile. + private static final String TRACE_CONFIG_FILENAME = "/data/local/chrome-trace-config.json"; + + /** Single trace event. */ + @VisibleForTesting + static final class Event { + final String mName; + final int mThreadId; + final long mBeginTimeNanos; + final long mBeginThreadTimeMillis; + long mEndTimeNanos; + long mEndThreadTimeMillis; + + Event(String name) { + mName = name; + mThreadId = Process.myTid(); + mBeginTimeNanos = elapsedRealtimeNanos(); + mBeginThreadTimeMillis = SystemClock.currentThreadTimeMillis(); + } + + void end() { + assert mEndTimeNanos == 0; + assert mEndThreadTimeMillis == 0; + mEndTimeNanos = elapsedRealtimeNanos(); + mEndThreadTimeMillis = SystemClock.currentThreadTimeMillis(); + } + + @VisibleForTesting + @SuppressLint("NewApi") + static long elapsedRealtimeNanos() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + return SystemClock.elapsedRealtimeNanos(); + } else { + return SystemClock.elapsedRealtime() * 1000000; + } + } + } + + @VisibleForTesting + static final class AsyncEvent { + final boolean mIsStart; + final String mName; + final long mId; + final long mTimestampNanos; + + AsyncEvent(String name, long id, boolean isStart) { + mName = name; + mId = id; + mIsStart = isStart; + mTimestampNanos = Event.elapsedRealtimeNanos(); + } + } + + // State transitions are: + // - enable(): DISABLED -> ENABLED + // - disable(): ENABLED -> FINISHING + // - Once there are no pending events: FINISHING -> FINISHED. + @VisibleForTesting static final int STATE_DISABLED = 0; + @VisibleForTesting static final int STATE_ENABLED = 1; + @VisibleForTesting static final int STATE_FINISHING = 2; + @VisibleForTesting static final int STATE_FINISHED = 3; + + private static final String BACKGROUND_STARTUP_TRACING_ENABLED_KEY = "bg_startup_tracing"; + private static boolean sCachedBackgroundStartupTracingFlag; + + // Locks the fields below. + private static final Object sLock = new Object(); + + @VisibleForTesting static volatile int sState = STATE_DISABLED; + // Not final as these object are not likely to be used at all. + @VisibleForTesting static List<Event> sCompletedEvents; + @VisibleForTesting + static Map<String, Event> sPendingEventByKey; + @VisibleForTesting static List<AsyncEvent> sAsyncEvents; + @VisibleForTesting static List<String> sPendingAsyncEvents; + + /** @see TraceEvent#MaybeEnableEarlyTracing(). + */ + static void maybeEnable() { + ThreadUtils.assertOnUiThread(); + if (sState != STATE_DISABLED) return; + boolean shouldEnable = false; + // Checking for the trace config filename touches the disk. + StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); + try { + if (CommandLine.getInstance().hasSwitch("trace-startup")) { + shouldEnable = true; + } else { + try { + shouldEnable = (new File(TRACE_CONFIG_FILENAME)).exists(); + } catch (SecurityException e) { + // Access denied, not enabled. + } + } + if (ContextUtils.getAppSharedPreferences().getBoolean( + BACKGROUND_STARTUP_TRACING_ENABLED_KEY, false)) { + if (shouldEnable) { + // If user has enabled tracing, then force disable background tracing for this + // session. + setBackgroundStartupTracingFlag(false); + sCachedBackgroundStartupTracingFlag = false; + } else { + sCachedBackgroundStartupTracingFlag = true; + shouldEnable = true; + } + } + } finally { + StrictMode.setThreadPolicy(oldPolicy); + } + if (shouldEnable) enable(); + } + + @VisibleForTesting + static void enable() { + synchronized (sLock) { + if (sState != STATE_DISABLED) return; + sCompletedEvents = new ArrayList<Event>(); + sPendingEventByKey = new HashMap<String, Event>(); + sAsyncEvents = new ArrayList<AsyncEvent>(); + sPendingAsyncEvents = new ArrayList<String>(); + sState = STATE_ENABLED; + } + } + + /** + * Disables Early tracing. + * + * Once this is called, no new event will be registered. However, end() calls are still recorded + * as long as there are pending events. Once there are none left, pass the events to the native + * side. + */ + static void disable() { + synchronized (sLock) { + if (!enabled()) return; + sState = STATE_FINISHING; + maybeFinishLocked(); + } + } + + /** + * Returns whether early tracing is currently active. + * + * Active means that Early Tracing is either enabled or waiting to complete pending events. + */ + static boolean isActive() { + int state = sState; + return (state == STATE_ENABLED || state == STATE_FINISHING); + } + + static boolean enabled() { + return sState == STATE_ENABLED; + } + + /** + * Sets the background startup tracing enabled in app preferences for next startup. + */ + @CalledByNative + static void setBackgroundStartupTracingFlag(boolean enabled) { + ContextUtils.getAppSharedPreferences() + .edit() + .putBoolean(BACKGROUND_STARTUP_TRACING_ENABLED_KEY, enabled) + .apply(); + } + + /** + * Returns true if the background startup tracing flag is set. + * + * This does not return the correct value if called before maybeEnable() was called. But that is + * called really early in startup. + */ + @CalledByNative + public static boolean getBackgroundStartupTracingFlag() { + return sCachedBackgroundStartupTracingFlag; + } + + /** @see {@link TraceEvent#begin()}. */ + public static void begin(String name) { + // begin() and end() are going to be called once per TraceEvent, this avoids entering a + // synchronized block at each and every call. + if (!enabled()) return; + Event event = new Event(name); + Event conflictingEvent; + synchronized (sLock) { + if (!enabled()) return; + conflictingEvent = sPendingEventByKey.put(makeEventKeyForCurrentThread(name), event); + } + if (conflictingEvent != null) { + throw new IllegalArgumentException( + "Multiple pending trace events can't have the same name"); + } + } + + /** @see {@link TraceEvent#end()}. */ + public static void end(String name) { + if (!isActive()) return; + synchronized (sLock) { + if (!isActive()) return; + Event event = sPendingEventByKey.remove(makeEventKeyForCurrentThread(name)); + if (event == null) return; + event.end(); + sCompletedEvents.add(event); + if (sState == STATE_FINISHING) maybeFinishLocked(); + } + } + + /** @see {@link TraceEvent#startAsync()}. */ + public static void startAsync(String name, long id) { + if (!enabled()) return; + AsyncEvent event = new AsyncEvent(name, id, true /*isStart*/); + synchronized (sLock) { + if (!enabled()) return; + sAsyncEvents.add(event); + sPendingAsyncEvents.add(name); + } + } + + /** @see {@link TraceEvent#finishAsync()}. */ + public static void finishAsync(String name, long id) { + if (!isActive()) return; + AsyncEvent event = new AsyncEvent(name, id, false /*isStart*/); + synchronized (sLock) { + if (!isActive()) return; + if (!sPendingAsyncEvents.remove(name)) return; + sAsyncEvents.add(event); + if (sState == STATE_FINISHING) maybeFinishLocked(); + } + } + + @VisibleForTesting + static void resetForTesting() { + sState = EarlyTraceEvent.STATE_DISABLED; + sCompletedEvents = null; + sPendingEventByKey = null; + sAsyncEvents = null; + sPendingAsyncEvents = null; + } + + private static void maybeFinishLocked() { + if (!sCompletedEvents.isEmpty()) { + dumpEvents(sCompletedEvents); + sCompletedEvents.clear(); + } + if (!sAsyncEvents.isEmpty()) { + dumpAsyncEvents(sAsyncEvents); + sAsyncEvents.clear(); + } + if (sPendingEventByKey.isEmpty() && sPendingAsyncEvents.isEmpty()) { + sState = STATE_FINISHED; + sPendingEventByKey = null; + sCompletedEvents = null; + sPendingAsyncEvents = null; + sAsyncEvents = null; + } + } + + private static void dumpEvents(List<Event> events) { + long offsetNanos = getOffsetNanos(); + for (Event e : events) { + nativeRecordEarlyEvent(e.mName, e.mBeginTimeNanos + offsetNanos, + e.mEndTimeNanos + offsetNanos, e.mThreadId, + e.mEndThreadTimeMillis - e.mBeginThreadTimeMillis); + } + } + private static void dumpAsyncEvents(List<AsyncEvent> events) { + long offsetNanos = getOffsetNanos(); + for (AsyncEvent e : events) { + if (e.mIsStart) { + nativeRecordEarlyStartAsyncEvent(e.mName, e.mId, e.mTimestampNanos + offsetNanos); + } else { + nativeRecordEarlyFinishAsyncEvent(e.mName, e.mId, e.mTimestampNanos + offsetNanos); + } + } + } + + private static long getOffsetNanos() { + long nativeNowNanos = TimeUtils.nativeGetTimeTicksNowUs() * 1000; + long javaNowNanos = Event.elapsedRealtimeNanos(); + return nativeNowNanos - javaNowNanos; + } + + /** + * Returns a key which consists of |name| and the ID of the current thread. + * The key is used with pending events making them thread-specific, thus avoiding + * an exception when similarly named events are started from multiple threads. + */ + @VisibleForTesting + static String makeEventKeyForCurrentThread(String name) { + return name + "@" + Process.myTid(); + } + + private static native void nativeRecordEarlyEvent(String name, long beginTimNanos, + long endTimeNanos, int threadId, long threadDurationMillis); + private static native void nativeRecordEarlyStartAsyncEvent( + String name, long id, long timestamp); + private static native void nativeRecordEarlyFinishAsyncEvent( + String name, long id, long timestamp); +}
diff --git a/src/base/android/java/src/org/chromium/base/EventLog.java b/src/base/android/java/src/org/chromium/base/EventLog.java new file mode 100644 index 0000000..f889175 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/EventLog.java
@@ -0,0 +1,20 @@ +// Copyright 2014 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. + +package org.chromium.base; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; + +/** + * A simple interface to Android's EventLog to be used by native code. + */ +@JNINamespace("base::android") +public class EventLog { + + @CalledByNative + public static void writeEvent(int tag, int value) { + android.util.EventLog.writeEvent(tag, value); + } +}
diff --git a/src/base/android/java/src/org/chromium/base/FieldTrialList.java b/src/base/android/java/src/org/chromium/base/FieldTrialList.java new file mode 100644 index 0000000..c3468a4 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/FieldTrialList.java
@@ -0,0 +1,46 @@ +// Copyright 2014 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. + +package org.chromium.base; + +import org.chromium.base.annotations.MainDex; + +/** + * Helper to get field trial information. + */ +@MainDex +public class FieldTrialList { + + private FieldTrialList() {} + + /** + * @param trialName The name of the trial to get the group for. + * @return The group name chosen for the named trial, or the empty string if the trial does + * not exist. + */ + public static String findFullName(String trialName) { + return nativeFindFullName(trialName); + } + + /** + * @param trialName The name of the trial to get the group for. + * @return Whether the trial exists or not. + */ + public static boolean trialExists(String trialName) { + return nativeTrialExists(trialName); + } + + /** + * @param trialName The name of the trial with the parameter. + * @param parameterKey The key of the parameter. + * @return The value of the parameter or an empty string if not found. + */ + public static String getVariationParameter(String trialName, String parameterKey) { + return nativeGetVariationParameter(trialName, parameterKey); + } + + private static native String nativeFindFullName(String trialName); + private static native boolean nativeTrialExists(String trialName); + private static native String nativeGetVariationParameter(String trialName, String parameterKey); +}
diff --git a/src/base/android/java/src/org/chromium/base/FileUtils.java b/src/base/android/java/src/org/chromium/base/FileUtils.java new file mode 100644 index 0000000..e44cd92 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/FileUtils.java
@@ -0,0 +1,149 @@ +// Copyright 2015 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. + +package org.chromium.base; + +import android.content.Context; +import android.net.Uri; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; +import java.util.Locale; + +/** + * Helper methods for dealing with Files. + */ +public class FileUtils { + private static final String TAG = "FileUtils"; + + /** + * Delete the given File and (if it's a directory) everything within it. + */ + public static void recursivelyDeleteFile(File currentFile) { + ThreadUtils.assertOnBackgroundThread(); + if (currentFile.isDirectory()) { + File[] files = currentFile.listFiles(); + if (files != null) { + for (File file : files) { + recursivelyDeleteFile(file); + } + } + } + + if (!currentFile.delete()) Log.e(TAG, "Failed to delete: " + currentFile); + } + + /** + * Delete the given files or directories by calling {@link #recursivelyDeleteFile(File)}. + * @param files The files to delete. + */ + public static void batchDeleteFiles(List<File> files) { + ThreadUtils.assertOnBackgroundThread(); + + for (File file : files) { + if (file.exists()) recursivelyDeleteFile(file); + } + } + + /** + * Extracts an asset from the app's APK to a file. + * @param context + * @param assetName Name of the asset to extract. + * @param dest File to extract the asset to. + * @return true on success. + */ + public static boolean extractAsset(Context context, String assetName, File dest) { + InputStream inputStream = null; + OutputStream outputStream = null; + try { + inputStream = context.getAssets().open(assetName); + outputStream = new BufferedOutputStream(new FileOutputStream(dest)); + byte[] buffer = new byte[8192]; + int c; + while ((c = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, c); + } + inputStream.close(); + outputStream.close(); + return true; + } catch (IOException e) { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException ex) { + } + } + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException ex) { + } + } + } + return false; + } + + /** + * Atomically copies the data from an input stream into an output file. + * @param is Input file stream to read data from. + * @param outFile Output file path. + * @param buffer Caller-provided buffer. Provided to avoid allocating the same + * buffer on each call when copying several files in sequence. + * @throws IOException in case of I/O error. + */ + public static void copyFileStreamAtomicWithBuffer(InputStream is, File outFile, byte[] buffer) + throws IOException { + File tmpOutputFile = new File(outFile.getPath() + ".tmp"); + try (OutputStream os = new FileOutputStream(tmpOutputFile)) { + Log.i(TAG, "Writing to %s", outFile); + + int count = 0; + while ((count = is.read(buffer, 0, buffer.length)) != -1) { + os.write(buffer, 0, count); + } + } + if (!tmpOutputFile.renameTo(outFile)) { + throw new IOException(); + } + } + + /** + * Returns a URI that points at the file. + * @param file File to get a URI for. + * @return URI that points at that file, either as a content:// URI or a file:// URI. + */ + public static Uri getUriForFile(File file) { + // TODO(crbug/709584): Uncomment this when http://crbug.com/709584 has been fixed. + // assert !ThreadUtils.runningOnUiThread(); + Uri uri = null; + + try { + // Try to obtain a content:// URI, which is preferred to a file:// URI so that + // receiving apps don't attempt to determine the file's mime type (which often fails). + uri = ContentUriUtils.getContentUriFromFile(file); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Could not create content uri: " + e); + } + + if (uri == null) uri = Uri.fromFile(file); + + return uri; + } + + /** + * Returns the file extension, or an empty string if none. + * @param file Name of the file, with or without the full path. + * @return empty string if no extension, extension otherwise. + */ + public static String getExtension(String file) { + int index = file.lastIndexOf('.'); + if (index == -1) return ""; + return file.substring(index + 1).toLowerCase(Locale.US); + } +}
diff --git a/src/base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java b/src/base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java new file mode 100644 index 0000000..cbaf7f7 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java
@@ -0,0 +1,31 @@ +// Copyright 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. + +package org.chromium.base; + +import org.chromium.base.annotations.JNINamespace; + +/** + * This class provides an interface to the native class for writing + * important data files without risking data loss. + */ +@JNINamespace("base::android") +public class ImportantFileWriterAndroid { + + /** + * Write a binary file atomically. + * + * This either writes all the data or leaves the file unchanged. + * + * @param fileName The complete path of the file to be written + * @param data The data to be written to the file + * @return true if the data was written to the file, false if not. + */ + public static boolean writeFileAtomically(String fileName, byte[] data) { + return nativeWriteFileAtomically(fileName, data); + } + + private static native boolean nativeWriteFileAtomically( + String fileName, byte[] data); +}
diff --git a/src/base/android/java/src/org/chromium/base/JNIUtils.java b/src/base/android/java/src/org/chromium/base/JNIUtils.java new file mode 100644 index 0000000..3fcec91 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/JNIUtils.java
@@ -0,0 +1,46 @@ +// Copyright 2014 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. + +package org.chromium.base; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.MainDex; + +/** + * This class provides JNI-related methods to the native library. + */ +@MainDex +public class JNIUtils { + private static Boolean sSelectiveJniRegistrationEnabled; + + /** + * This returns a ClassLoader that is capable of loading Chromium Java code. Such a ClassLoader + * is needed for the few cases where the JNI mechanism is unable to automatically determine the + * appropriate ClassLoader instance. + */ + @CalledByNative + public static Object getClassLoader() { + return JNIUtils.class.getClassLoader(); + } + + /** + * @return whether or not the current process supports selective JNI registration. + */ + @CalledByNative + public static boolean isSelectiveJniRegistrationEnabled() { + if (sSelectiveJniRegistrationEnabled == null) { + sSelectiveJniRegistrationEnabled = false; + } + return sSelectiveJniRegistrationEnabled; + } + + /** + * Allow this process to selectively perform JNI registration. This must be called before + * loading native libraries or it will have no effect. + */ + public static void enableSelectiveJniRegistration() { + assert sSelectiveJniRegistrationEnabled == null; + sSelectiveJniRegistrationEnabled = true; + } +}
diff --git a/src/base/android/java/src/org/chromium/base/JavaExceptionReporter.java b/src/base/android/java/src/org/chromium/base/JavaExceptionReporter.java new file mode 100644 index 0000000..f192f78 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/JavaExceptionReporter.java
@@ -0,0 +1,65 @@ +// Copyright 2015 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. + +package org.chromium.base; + +import android.support.annotation.UiThread; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.MainDex; + +/** + * This UncaughtExceptionHandler will create a breakpad minidump when there is an uncaught + * exception. + * + * The exception's stack trace will be added to the minidump's data. This allows java-only crashes + * to be reported in the same way as other native crashes. + */ +@JNINamespace("base::android") +@MainDex +public class JavaExceptionReporter implements Thread.UncaughtExceptionHandler { + private final Thread.UncaughtExceptionHandler mParent; + private final boolean mCrashAfterReport; + private boolean mHandlingException; + + private JavaExceptionReporter( + Thread.UncaughtExceptionHandler parent, boolean crashAfterReport) { + mParent = parent; + mCrashAfterReport = crashAfterReport; + } + + @Override + public void uncaughtException(Thread t, Throwable e) { + if (!mHandlingException) { + mHandlingException = true; + nativeReportJavaException(mCrashAfterReport, e); + } + if (mParent != null) { + mParent.uncaughtException(t, e); + } + } + + /** + * Report and upload the stack trace as if it was a crash. This is very expensive and should + * be called rarely and only on the UI thread to avoid corrupting other crash uploads. Ideally + * only called in idle handlers. + * + * @param stackTrace The stack trace to report. + */ + @UiThread + public static void reportStackTrace(String stackTrace) { + assert ThreadUtils.runningOnUiThread(); + nativeReportJavaStackTrace(stackTrace); + } + + @CalledByNative + private static void installHandler(boolean crashAfterReport) { + Thread.setDefaultUncaughtExceptionHandler(new JavaExceptionReporter( + Thread.getDefaultUncaughtExceptionHandler(), crashAfterReport)); + } + + private static native void nativeReportJavaException(boolean crashAfterReport, Throwable e); + private static native void nativeReportJavaStackTrace(String stackTrace); +}
diff --git a/src/base/android/java/src/org/chromium/base/JavaHandlerThread.java b/src/base/android/java/src/org/chromium/base/JavaHandlerThread.java new file mode 100644 index 0000000..9a1c924 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/JavaHandlerThread.java
@@ -0,0 +1,119 @@ +// Copyright 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. + +package org.chromium.base; + +import android.os.Build; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.MainDex; + +import java.lang.Thread.UncaughtExceptionHandler; + +/** + * Thread in Java with an Android Handler. This class is not thread safe. + */ +@JNINamespace("base::android") +@MainDex +public class JavaHandlerThread { + private final HandlerThread mThread; + + private Throwable mUnhandledException; + + /** + * Construct a java-only instance. Can be connected with native side later. + * Useful for cases where a java thread is needed before native library is loaded. + */ + public JavaHandlerThread(String name, int priority) { + mThread = new HandlerThread(name, priority); + } + + @CalledByNative + private static JavaHandlerThread create(String name, int priority) { + return new JavaHandlerThread(name, priority); + } + + public Looper getLooper() { + assert hasStarted(); + return mThread.getLooper(); + } + + public void maybeStart() { + if (hasStarted()) return; + mThread.start(); + } + + @CalledByNative + private void startAndInitialize(final long nativeThread, final long nativeEvent) { + maybeStart(); + new Handler(mThread.getLooper()).post(new Runnable() { + @Override + public void run() { + nativeInitializeThread(nativeThread, nativeEvent); + } + }); + } + + @CalledByNative + private void quitThreadSafely(final long nativeThread) { + // Allow pending java tasks to run, but don't run any delayed or newly queued up tasks. + new Handler(mThread.getLooper()).post(new Runnable() { + @Override + public void run() { + mThread.quit(); + nativeOnLooperStopped(nativeThread); + } + }); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + // When we can, signal that new tasks queued up won't be run. + mThread.getLooper().quitSafely(); + } + } + + @CalledByNative + private void joinThread() { + boolean joined = false; + while (!joined) { + try { + mThread.join(); + joined = true; + } catch (InterruptedException e) { + } + } + } + + private boolean hasStarted() { + return mThread.getState() != Thread.State.NEW; + } + + @CalledByNative + private boolean isAlive() { + return mThread.isAlive(); + } + + // This should *only* be used for tests. In production we always need to call the original + // uncaught exception handler (the framework's) after any uncaught exception handling we do, as + // it generates crash dumps and kills the process. + @CalledByNative + private void listenForUncaughtExceptionsForTesting() { + mThread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + mUnhandledException = e; + } + }); + } + + @CalledByNative + private Throwable getUncaughtExceptionIfAny() { + return mUnhandledException; + } + + private native void nativeInitializeThread(long nativeJavaHandlerThread, long nativeEvent); + private native void nativeOnLooperStopped(long nativeJavaHandlerThread); +}
diff --git a/src/base/android/java/src/org/chromium/base/LocaleUtils.java b/src/base/android/java/src/org/chromium/base/LocaleUtils.java new file mode 100644 index 0000000..05d3902 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/LocaleUtils.java
@@ -0,0 +1,207 @@ +// Copyright 2014 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. + +package org.chromium.base; + +import android.annotation.TargetApi; +import android.os.Build; +import android.os.LocaleList; +import android.text.TextUtils; + +import org.chromium.base.annotations.CalledByNative; + +import java.util.ArrayList; +import java.util.Locale; + +/** + * This class provides the locale related methods. + */ +public class LocaleUtils { + /** + * Guards this class from being instantiated. + */ + private LocaleUtils() { + } + + /** + * Java keeps deprecated language codes for Hebrew, Yiddish and Indonesian but Chromium uses + * updated ones. Similarly, Android uses "tl" while Chromium uses "fil" for Tagalog/Filipino. + * So apply a mapping here. + * See http://developer.android.com/reference/java/util/Locale.html + * @return a updated language code for Chromium with given language string. + */ + public static String getUpdatedLanguageForChromium(String language) { + // IMPORTANT: Keep in sync with the mapping found in: + // build/android/gyp/util/resource_utils.py + switch (language) { + case "iw": + return "he"; // Hebrew + case "ji": + return "yi"; // Yiddish + case "in": + return "id"; // Indonesian + case "tl": + return "fil"; // Filipino + default: + return language; + } + } + + /** + * @return a locale with updated language codes for Chromium, with translated modern language + * codes used by Chromium. + */ + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @VisibleForTesting + public static Locale getUpdatedLocaleForChromium(Locale locale) { + String language = locale.getLanguage(); + String languageForChrome = getUpdatedLanguageForChromium(language); + if (languageForChrome.equals(language)) { + return locale; + } + return new Locale.Builder().setLocale(locale).setLanguage(languageForChrome).build(); + } + + /** + * Android uses "tl" while Chromium uses "fil" for Tagalog/Filipino. + * So apply a mapping here. + * See http://developer.android.com/reference/java/util/Locale.html + * @return a updated language code for Android with given language string. + */ + public static String getUpdatedLanguageForAndroid(String language) { + // IMPORTANT: Keep in sync with the mapping found in: + // build/android/gyp/util/resource_utils.py + switch (language) { + case "und": + return ""; // Undefined + case "fil": + return "tl"; // Filipino + default: + return language; + } + } + + /** + * @return a locale with updated language codes for Android, from translated modern language + * codes used by Chromium. + */ + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @VisibleForTesting + public static Locale getUpdatedLocaleForAndroid(Locale locale) { + String language = locale.getLanguage(); + String languageForAndroid = getUpdatedLanguageForAndroid(language); + if (languageForAndroid.equals(language)) { + return locale; + } + return new Locale.Builder().setLocale(locale).setLanguage(languageForAndroid).build(); + } + + /** + * This function creates a Locale object from xx-XX style string where xx is language code + * and XX is a country code. This works for API level lower than 21. + * @return the locale that best represents the language tag. + */ + public static Locale forLanguageTagCompat(String languageTag) { + String[] tag = languageTag.split("-"); + if (tag.length == 0) { + return new Locale(""); + } + String language = getUpdatedLanguageForAndroid(tag[0]); + if ((language.length() != 2 && language.length() != 3)) { + return new Locale(""); + } + if (tag.length == 1) { + return new Locale(language); + } + String country = tag[1]; + if (country.length() != 2 && country.length() != 3) { + return new Locale(language); + } + return new Locale(language, country); + } + + /** + * This function creates a Locale object from xx-XX style string where xx is language code + * and XX is a country code. + * @return the locale that best represents the language tag. + */ + public static Locale forLanguageTag(String languageTag) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + Locale locale = Locale.forLanguageTag(languageTag); + return getUpdatedLocaleForAndroid(locale); + } + return forLanguageTagCompat(languageTag); + } + + /** + * Converts Locale object to the BCP 47 compliant string format. + * This works for API level lower than 24. + * + * Note that for Android M or before, we cannot use Locale.getLanguage() and + * Locale.toLanguageTag() for this purpose. Since Locale.getLanguage() returns deprecated + * language code even if the Locale object is constructed with updated language code. As for + * Locale.toLanguageTag(), it does a special conversion from deprecated language code to updated + * one, but it is only usable for Android N or after. + * @return a well-formed IETF BCP 47 language tag with language and country code that + * represents this locale. + */ + public static String toLanguageTag(Locale locale) { + String language = getUpdatedLanguageForChromium(locale.getLanguage()); + String country = locale.getCountry(); + if (language.equals("no") && country.equals("NO") && locale.getVariant().equals("NY")) { + return "nn-NO"; + } + return country.isEmpty() ? language : language + "-" + country; + } + + /** + * Converts LocaleList object to the comma separated BCP 47 compliant string format. + * + * @return a well-formed IETF BCP 47 language tag with language and country code that + * represents this locale list. + */ + @TargetApi(Build.VERSION_CODES.N) + public static String toLanguageTags(LocaleList localeList) { + ArrayList<String> newLocaleList = new ArrayList<>(); + for (int i = 0; i < localeList.size(); i++) { + Locale locale = getUpdatedLocaleForChromium(localeList.get(i)); + newLocaleList.add(toLanguageTag(locale)); + } + return TextUtils.join(",", newLocaleList); + } + + /** + * @return a comma separated language tags string that represents a default locale. + * Each language tag is well-formed IETF BCP 47 language tag with language and country + * code. + */ + @CalledByNative + public static String getDefaultLocaleString() { + return toLanguageTag(Locale.getDefault()); + } + + /** + * @return a comma separated language tags string that represents a default locale or locales. + * Each language tag is well-formed IETF BCP 47 language tag with language and country + * code. + */ + public static String getDefaultLocaleListString() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return toLanguageTags(LocaleList.getDefault()); + } + return getDefaultLocaleString(); + } + + /** + * @return The default country code set during install. + */ + @CalledByNative + private static String getDefaultCountryCode() { + CommandLine commandLine = CommandLine.getInstance(); + return commandLine.hasSwitch(BaseSwitches.DEFAULT_COUNTRY_CODE_AT_INSTALL) + ? commandLine.getSwitchValue(BaseSwitches.DEFAULT_COUNTRY_CODE_AT_INSTALL) + : Locale.getDefault().getCountry(); + } + +}
diff --git a/src/base/android/java/src/org/chromium/base/Log.java b/src/base/android/java/src/org/chromium/base/Log.java new file mode 100644 index 0000000..399f16d --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/Log.java
@@ -0,0 +1,387 @@ +// Copyright 2015 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. + +package org.chromium.base; + +import org.chromium.base.annotations.RemovableInRelease; + +import java.util.Locale; + +/** + * Utility class for Logging. + * + * <p> + * Defines logging access points for each feature. They format and forward the logs to + * {@link android.util.Log}, allowing to standardize the output, to make it easy to identify + * the origin of logs, and enable or disable logging in different parts of the code. + * </p> + * <p> + * Usage documentation: {@code //docs/android_logging.md}. + * </p> + */ +public class Log { + /** Convenience property, same as {@link android.util.Log#ASSERT}. */ + public static final int ASSERT = android.util.Log.ASSERT; + + /** Convenience property, same as {@link android.util.Log#DEBUG}. */ + public static final int DEBUG = android.util.Log.DEBUG; + + /** Convenience property, same as {@link android.util.Log#ERROR}. */ + public static final int ERROR = android.util.Log.ERROR; + + /** Convenience property, same as {@link android.util.Log#INFO}. */ + public static final int INFO = android.util.Log.INFO; + + /** Convenience property, same as {@link android.util.Log#VERBOSE}. */ + public static final int VERBOSE = android.util.Log.VERBOSE; + + /** Convenience property, same as {@link android.util.Log#WARN}. */ + public static final int WARN = android.util.Log.WARN; + + private static final String sTagPrefix = "cr_"; + private static final String sDeprecatedTagPrefix = "cr."; + + private Log() { + // Static only access + } + + /** Returns a formatted log message, using the supplied format and arguments.*/ + private static String formatLog(String messageTemplate, Object... params) { + if (params != null && params.length != 0) { + messageTemplate = String.format(Locale.US, messageTemplate, params); + } + + return messageTemplate; + } + + /** + * Returns a normalized tag that will be in the form: "cr_foo". This function is called by the + * various Log overrides. If using {@link #isLoggable(String, int)}, you might want to call it + * to get the tag that will actually be used. + * @see #sTagPrefix + */ + public static String normalizeTag(String tag) { + if (tag.startsWith(sTagPrefix)) return tag; + + // TODO(dgn) simplify this once 'cr.' is out of the repo (http://crbug.com/533072) + int unprefixedTagStart = 0; + if (tag.startsWith(sDeprecatedTagPrefix)) { + unprefixedTagStart = sDeprecatedTagPrefix.length(); + } + + return sTagPrefix + tag.substring(unprefixedTagStart, tag.length()); + } + + /** + * Returns a formatted log message, using the supplied format and arguments. + * The message will be prepended with the filename and line number of the call. + */ + private static String formatLogWithStack(String messageTemplate, Object... params) { + return "[" + getCallOrigin() + "] " + formatLog(messageTemplate, params); + } + + /** + * Convenience function, forwards to {@link android.util.Log#isLoggable(String, int)}. + * + * Note: Has no effect on whether logs are sent or not. Use a method with + * {@link RemovableInRelease} to log something in Debug builds only. + */ + public static boolean isLoggable(String tag, int level) { + return android.util.Log.isLoggable(tag, level); + } + + /** + * Sends a {@link android.util.Log#VERBOSE} log message. + * + * For optimization purposes, only the fixed parameters versions are visible. If you need more + * than 7 parameters, consider building your log message using a function annotated with + * {@link RemovableInRelease}. + * + * @param tag Used to identify the source of a log message. Might be modified in the output + * (see {@link #normalizeTag(String)}) + * @param messageTemplate The message you would like logged. It is to be specified as a format + * string. + * @param args Arguments referenced by the format specifiers in the format string. If the last + * one is a {@link Throwable}, its trace will be printed. + */ + private static void verbose(String tag, String messageTemplate, Object... args) { + String message = formatLogWithStack(messageTemplate, args); + Throwable tr = getThrowableToLog(args); + if (tr != null) { + android.util.Log.v(normalizeTag(tag), message, tr); + } else { + android.util.Log.v(normalizeTag(tag), message); + } + } + + /** Sends a {@link android.util.Log#VERBOSE} log message. 0 args version. */ + @RemovableInRelease + @VisibleForTesting + public static void v(String tag, String message) { + verbose(tag, message); + } + + /** Sends a {@link android.util.Log#VERBOSE} log message. 1 arg version. */ + @RemovableInRelease + @VisibleForTesting + public static void v(String tag, String messageTemplate, Object arg1) { + verbose(tag, messageTemplate, arg1); + } + + /** Sends a {@link android.util.Log#VERBOSE} log message. 2 args version */ + @RemovableInRelease + @VisibleForTesting + public static void v(String tag, String messageTemplate, Object arg1, Object arg2) { + verbose(tag, messageTemplate, arg1, arg2); + } + + /** Sends a {@link android.util.Log#VERBOSE} log message. 3 args version */ + @RemovableInRelease + @VisibleForTesting + public static void v( + String tag, String messageTemplate, Object arg1, Object arg2, Object arg3) { + verbose(tag, messageTemplate, arg1, arg2, arg3); + } + + /** Sends a {@link android.util.Log#VERBOSE} log message. 4 args version */ + @RemovableInRelease + @VisibleForTesting + public static void v(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, + Object arg4) { + verbose(tag, messageTemplate, arg1, arg2, arg3, arg4); + } + + /** Sends a {@link android.util.Log#VERBOSE} log message. 5 args version */ + @RemovableInRelease + @VisibleForTesting + public static void v(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, + Object arg4, Object arg5) { + verbose(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5); + } + + /** Sends a {@link android.util.Log#VERBOSE} log message. 6 args version */ + @RemovableInRelease + @VisibleForTesting + public static void v(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, + Object arg4, Object arg5, Object arg6) { + verbose(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5, arg6); + } + + /** Sends a {@link android.util.Log#VERBOSE} log message. 7 args version */ + @RemovableInRelease + @VisibleForTesting + public static void v(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, + Object arg4, Object arg5, Object arg6, Object arg7) { + verbose(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5, arg6, arg7); + } + + /** + * Sends a {@link android.util.Log#DEBUG} log message. + * + * For optimization purposes, only the fixed parameters versions are visible. If you need more + * than 7 parameters, consider building your log message using a function annotated with + * {@link RemovableInRelease}. + * + * @param tag Used to identify the source of a log message. Might be modified in the output + * (see {@link #normalizeTag(String)}) + * @param messageTemplate The message you would like logged. It is to be specified as a format + * string. + * @param args Arguments referenced by the format specifiers in the format string. If the last + * one is a {@link Throwable}, its trace will be printed. + */ + private static void debug(String tag, String messageTemplate, Object... args) { + String message = formatLogWithStack(messageTemplate, args); + Throwable tr = getThrowableToLog(args); + if (tr != null) { + android.util.Log.d(normalizeTag(tag), message, tr); + } else { + android.util.Log.d(normalizeTag(tag), message); + } + } + + /** Sends a {@link android.util.Log#DEBUG} log message. 0 args version. */ + @RemovableInRelease + @VisibleForTesting + public static void d(String tag, String message) { + debug(tag, message); + } + + /** Sends a {@link android.util.Log#DEBUG} log message. 1 arg version. */ + @RemovableInRelease + @VisibleForTesting + public static void d(String tag, String messageTemplate, Object arg1) { + debug(tag, messageTemplate, arg1); + } + /** Sends a {@link android.util.Log#DEBUG} log message. 2 args version */ + @RemovableInRelease + @VisibleForTesting + public static void d(String tag, String messageTemplate, Object arg1, Object arg2) { + debug(tag, messageTemplate, arg1, arg2); + } + /** Sends a {@link android.util.Log#DEBUG} log message. 3 args version */ + @RemovableInRelease + @VisibleForTesting + public static void d( + String tag, String messageTemplate, Object arg1, Object arg2, Object arg3) { + debug(tag, messageTemplate, arg1, arg2, arg3); + } + + /** Sends a {@link android.util.Log#DEBUG} log message. 4 args version */ + @RemovableInRelease + @VisibleForTesting + public static void d(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, + Object arg4) { + debug(tag, messageTemplate, arg1, arg2, arg3, arg4); + } + + /** Sends a {@link android.util.Log#DEBUG} log message. 5 args version */ + @RemovableInRelease + @VisibleForTesting + public static void d(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, + Object arg4, Object arg5) { + debug(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5); + } + + /** Sends a {@link android.util.Log#DEBUG} log message. 6 args version */ + @RemovableInRelease + @VisibleForTesting + public static void d(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, + Object arg4, Object arg5, Object arg6) { + debug(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5, arg6); + } + + /** Sends a {@link android.util.Log#DEBUG} log message. 7 args version */ + @RemovableInRelease + @VisibleForTesting + public static void d(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, + Object arg4, Object arg5, Object arg6, Object arg7) { + debug(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5, arg6, arg7); + } + + /** + * Sends an {@link android.util.Log#INFO} log message. + * + * @param tag Used to identify the source of a log message. Might be modified in the output + * (see {@link #normalizeTag(String)}) + * @param messageTemplate The message you would like logged. It is to be specified as a format + * string. + * @param args Arguments referenced by the format specifiers in the format string. If the last + * one is a {@link Throwable}, its trace will be printed. + */ + @VisibleForTesting + public static void i(String tag, String messageTemplate, Object... args) { + String message = formatLog(messageTemplate, args); + Throwable tr = getThrowableToLog(args); + if (tr != null) { + android.util.Log.i(normalizeTag(tag), message, tr); + } else { + android.util.Log.i(normalizeTag(tag), message); + } + } + + /** + * Sends a {@link android.util.Log#WARN} log message. + * + * @param tag Used to identify the source of a log message. Might be modified in the output + * (see {@link #normalizeTag(String)}) + * @param messageTemplate The message you would like logged. It is to be specified as a format + * string. + * @param args Arguments referenced by the format specifiers in the format string. If the last + * one is a {@link Throwable}, its trace will be printed. + */ + @VisibleForTesting + public static void w(String tag, String messageTemplate, Object... args) { + String message = formatLog(messageTemplate, args); + Throwable tr = getThrowableToLog(args); + if (tr != null) { + android.util.Log.w(normalizeTag(tag), message, tr); + } else { + android.util.Log.w(normalizeTag(tag), message); + } + } + + /** + * Sends an {@link android.util.Log#ERROR} log message. + * + * @param tag Used to identify the source of a log message. Might be modified in the output + * (see {@link #normalizeTag(String)}) + * @param messageTemplate The message you would like logged. It is to be specified as a format + * string. + * @param args Arguments referenced by the format specifiers in the format string. If the last + * one is a {@link Throwable}, its trace will be printed. + */ + @VisibleForTesting + public static void e(String tag, String messageTemplate, Object... args) { + String message = formatLog(messageTemplate, args); + Throwable tr = getThrowableToLog(args); + if (tr != null) { + android.util.Log.e(normalizeTag(tag), message, tr); + } else { + android.util.Log.e(normalizeTag(tag), message); + } + } + + /** + * What a Terrible Failure: Used for conditions that should never happen, and logged at + * the {@link android.util.Log#ASSERT} level. Depending on the configuration, it might + * terminate the process. + * + * @see android.util.Log#wtf(String, String, Throwable) + * + * @param tag Used to identify the source of a log message. Might be modified in the output + * (see {@link #normalizeTag(String)}) + * @param messageTemplate The message you would like logged. It is to be specified as a format + * string. + * @param args Arguments referenced by the format specifiers in the format string. If the last + * one is a {@link Throwable}, its trace will be printed. + */ + @VisibleForTesting + public static void wtf(String tag, String messageTemplate, Object... args) { + String message = formatLog(messageTemplate, args); + Throwable tr = getThrowableToLog(args); + if (tr != null) { + android.util.Log.wtf(normalizeTag(tag), message, tr); + } else { + android.util.Log.wtf(normalizeTag(tag), message); + } + } + + /** Handy function to get a loggable stack trace from a Throwable. */ + public static String getStackTraceString(Throwable tr) { + return android.util.Log.getStackTraceString(tr); + } + + private static Throwable getThrowableToLog(Object[] args) { + if (args == null || args.length == 0) return null; + + Object lastArg = args[args.length - 1]; + + if (!(lastArg instanceof Throwable)) return null; + return (Throwable) lastArg; + } + + /** Returns a string form of the origin of the log call, to be used as secondary tag.*/ + private static String getCallOrigin() { + StackTraceElement[] st = Thread.currentThread().getStackTrace(); + + // The call stack should look like: + // n [a variable number of calls depending on the vm used] + // +0 getCallOrigin() + // +1 privateLogFunction: verbose or debug + // +2 formatLogWithStack() + // +3 logFunction: v or d + // +4 caller + + int callerStackIndex; + String logClassName = Log.class.getName(); + for (callerStackIndex = 0; callerStackIndex < st.length; callerStackIndex++) { + if (st[callerStackIndex].getClassName().equals(logClassName)) { + callerStackIndex += 4; + break; + } + } + + return st[callerStackIndex].getFileName() + ":" + st[callerStackIndex].getLineNumber(); + } +}
diff --git a/src/base/android/java/src/org/chromium/base/MemoryPressureListener.java b/src/base/android/java/src/org/chromium/base/MemoryPressureListener.java new file mode 100644 index 0000000..6c80970 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/MemoryPressureListener.java
@@ -0,0 +1,130 @@ +// Copyright 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. + +package org.chromium.base; + +import android.app.Activity; +import android.content.ComponentCallbacks2; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.MainDex; +import org.chromium.base.memory.MemoryPressureCallback; + +/** + * This class is Java equivalent of base::MemoryPressureListener: it distributes pressure + * signals to callbacks. + * + * The class also serves as an entry point to the native side - once native code is ready, + * it adds native callback. + * + * notifyMemoryPressure() is called exclusively by MemoryPressureMonitor, which + * monitors and throttles pressure signals. + * + * NOTE: this class should only be used on UiThread as defined by ThreadUtils (which is + * Android main thread for Chrome, but can be some other thread for WebView). + */ +@MainDex +public class MemoryPressureListener { + /** + * Sending an intent with this action to Chrome will cause it to issue a call to onLowMemory + * thus simulating a low memory situations. + */ + private static final String ACTION_LOW_MEMORY = "org.chromium.base.ACTION_LOW_MEMORY"; + + /** + * Sending an intent with this action to Chrome will cause it to issue a call to onTrimMemory + * thus simulating a low memory situations. + */ + private static final String ACTION_TRIM_MEMORY = "org.chromium.base.ACTION_TRIM_MEMORY"; + + /** + * Sending an intent with this action to Chrome will cause it to issue a call to onTrimMemory + * with notification level TRIM_MEMORY_RUNNING_CRITICAL thus simulating a low memory situation + */ + private static final String ACTION_TRIM_MEMORY_RUNNING_CRITICAL = + "org.chromium.base.ACTION_TRIM_MEMORY_RUNNING_CRITICAL"; + + /** + * Sending an intent with this action to Chrome will cause it to issue a call to onTrimMemory + * with notification level TRIM_MEMORY_MODERATE thus simulating a low memory situation + */ + private static final String ACTION_TRIM_MEMORY_MODERATE = + "org.chromium.base.ACTION_TRIM_MEMORY_MODERATE"; + + private static final ObserverList<MemoryPressureCallback> sCallbacks = new ObserverList<>(); + + /** + * Called by the native side to add native callback. + */ + @CalledByNative + private static void addNativeCallback() { + addCallback(MemoryPressureListener::nativeOnMemoryPressure); + } + + /** + * Adds a memory pressure callback. + * Callback is only added once, regardless of the number of addCallback() calls. + * This method should be called only on ThreadUtils.UiThread. + */ + public static void addCallback(MemoryPressureCallback callback) { + sCallbacks.addObserver(callback); + } + + /** + * Removes previously added memory pressure callback. + * This method should be called only on ThreadUtils.UiThread. + */ + public static void removeCallback(MemoryPressureCallback callback) { + sCallbacks.removeObserver(callback); + } + + /** + * Distributes |pressure| to all callbacks. + * This method should be called only on ThreadUtils.UiThread. + */ + public static void notifyMemoryPressure(@MemoryPressureLevel int pressure) { + for (MemoryPressureCallback callback : sCallbacks) { + callback.onPressure(pressure); + } + } + + /** + * Used by applications to simulate a memory pressure signal. By throwing certain intent + * actions. + */ + public static boolean handleDebugIntent(Activity activity, String action) { + if (ACTION_LOW_MEMORY.equals(action)) { + simulateLowMemoryPressureSignal(activity); + } else if (ACTION_TRIM_MEMORY.equals(action)) { + simulateTrimMemoryPressureSignal(activity, ComponentCallbacks2.TRIM_MEMORY_COMPLETE); + } else if (ACTION_TRIM_MEMORY_RUNNING_CRITICAL.equals(action)) { + simulateTrimMemoryPressureSignal(activity, + ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL); + } else if (ACTION_TRIM_MEMORY_MODERATE.equals(action)) { + simulateTrimMemoryPressureSignal(activity, ComponentCallbacks2.TRIM_MEMORY_MODERATE); + } else { + return false; + } + + return true; + } + + private static void simulateLowMemoryPressureSignal(Activity activity) { + // The Application and the Activity each have a list of callbacks they notify when this + // method is called. Notifying these will simulate the event at the App/Activity level + // as well as trigger the listener bound from native in this process. + activity.getApplication().onLowMemory(); + activity.onLowMemory(); + } + + private static void simulateTrimMemoryPressureSignal(Activity activity, int level) { + // The Application and the Activity each have a list of callbacks they notify when this + // method is called. Notifying these will simulate the event at the App/Activity level + // as well as trigger the listener bound from native in this process. + activity.getApplication().onTrimMemory(level); + activity.onTrimMemory(level); + } + + private static native void nativeOnMemoryPressure(@MemoryPressureLevel int pressure); +}
diff --git a/src/base/android/java/src/org/chromium/base/NonThreadSafe.java b/src/base/android/java/src/org/chromium/base/NonThreadSafe.java new file mode 100644 index 0000000..53f38d2 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/NonThreadSafe.java
@@ -0,0 +1,41 @@ +// Copyright 2015 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. + +package org.chromium.base; + +/** + * NonThreadSafe is a helper class used to help verify that methods of a + * class are called from the same thread. + */ +public class NonThreadSafe { + private Long mThreadId; + + public NonThreadSafe() { + ensureThreadIdAssigned(); + } + + /** + * Changes the thread that is checked for in CalledOnValidThread. This may + * be useful when an object may be created on one thread and then used + * exclusively on another thread. + */ + @VisibleForTesting + public synchronized void detachFromThread() { + mThreadId = null; + } + + /** + * Checks if the method is called on the valid thread. + * Assigns the current thread if no thread was assigned. + */ + @SuppressWarnings("NoSynchronizedMethodCheck") + public synchronized boolean calledOnValidThread() { + ensureThreadIdAssigned(); + return mThreadId.equals(Thread.currentThread().getId()); + } + + private void ensureThreadIdAssigned() { + if (mThreadId == null) mThreadId = Thread.currentThread().getId(); + } +}
diff --git a/src/base/android/java/src/org/chromium/base/ObserverList.java b/src/base/android/java/src/org/chromium/base/ObserverList.java new file mode 100644 index 0000000..59276c6 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/ObserverList.java
@@ -0,0 +1,249 @@ +// Copyright 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. + +package org.chromium.base; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +import javax.annotation.concurrent.NotThreadSafe; + +/** + * A container for a list of observers. + * <p/> + * This container can be modified during iteration without invalidating the iterator. + * So, it safely handles the case of an observer removing itself or other observers from the list + * while observers are being notified. + * <p/> + * The implementation (and the interface) is heavily influenced by the C++ ObserverList. + * Notable differences: + * - The iterator implements NOTIFY_EXISTING_ONLY. + * - The range-based for loop is left to the clients to implement in terms of iterator(). + * <p/> + * This class is not threadsafe. Observers MUST be added, removed and will be notified on the same + * thread this is created. + * + * @param <E> The type of observers that this list should hold. + */ +@NotThreadSafe +public class ObserverList<E> implements Iterable<E> { + /** + * Extended iterator interface that provides rewind functionality. + */ + public interface RewindableIterator<E> extends Iterator<E> { + /** + * Rewind the iterator back to the beginning. + * + * If we need to iterate multiple times, we can avoid iterator object reallocation by using + * this method. + */ + public void rewind(); + } + + public final List<E> mObservers = new ArrayList<E>(); + private int mIterationDepth; + private int mCount; + private boolean mNeedsCompact; + + public ObserverList() {} + + /** + * Add an observer to the list. + * <p/> + * An observer should not be added to the same list more than once. If an iteration is already + * in progress, this observer will be not be visible during that iteration. + * + * @return true if the observer list changed as a result of the call. + */ + public boolean addObserver(E obs) { + // Avoid adding null elements to the list as they may be removed on a compaction. + if (obs == null || mObservers.contains(obs)) { + return false; + } + + // Structurally modifying the underlying list here. This means we + // cannot use the underlying list's iterator to iterate over the list. + boolean result = mObservers.add(obs); + assert result; + + ++mCount; + return true; + } + + /** + * Remove an observer from the list if it is in the list. + * + * @return true if an element was removed as a result of this call. + */ + public boolean removeObserver(E obs) { + if (obs == null) { + return false; + } + + int index = mObservers.indexOf(obs); + if (index == -1) { + return false; + } + + if (mIterationDepth == 0) { + // No one is iterating over the list. + mObservers.remove(index); + } else { + mNeedsCompact = true; + mObservers.set(index, null); + } + --mCount; + assert mCount >= 0; + + return true; + } + + public boolean hasObserver(E obs) { + return mObservers.contains(obs); + } + + public void clear() { + mCount = 0; + + if (mIterationDepth == 0) { + mObservers.clear(); + return; + } + + int size = mObservers.size(); + mNeedsCompact |= size != 0; + for (int i = 0; i < size; i++) { + mObservers.set(i, null); + } + } + + @Override + public Iterator<E> iterator() { + return new ObserverListIterator(); + } + + /** + * It's the same as {@link ObserverList#iterator()} but the return type is + * {@link RewindableIterator}. Use this iterator type if you need to use + * {@link RewindableIterator#rewind()}. + */ + public RewindableIterator<E> rewindableIterator() { + return new ObserverListIterator(); + } + + /** + * Returns the number of observers currently registered in the ObserverList. + * This is equivalent to the number of non-empty spaces in |mObservers|. + */ + public int size() { + return mCount; + } + + /** + * Returns true if the ObserverList contains no observers. + */ + public boolean isEmpty() { + return mCount == 0; + } + + /** + * Compact the underlying list be removing null elements. + * <p/> + * Should only be called when mIterationDepth is zero. + */ + private void compact() { + assert mIterationDepth == 0; + for (int i = mObservers.size() - 1; i >= 0; i--) { + if (mObservers.get(i) == null) { + mObservers.remove(i); + } + } + } + + private void incrementIterationDepth() { + mIterationDepth++; + } + + private void decrementIterationDepthAndCompactIfNeeded() { + mIterationDepth--; + assert mIterationDepth >= 0; + if (mIterationDepth > 0) return; + if (!mNeedsCompact) return; + mNeedsCompact = false; + compact(); + } + + /** + * Returns the size of the underlying storage of the ObserverList. + * It will take into account the empty spaces inside |mObservers|. + */ + private int capacity() { + return mObservers.size(); + } + + private E getObserverAt(int index) { + return mObservers.get(index); + } + + private class ObserverListIterator implements RewindableIterator<E> { + private int mListEndMarker; + private int mIndex; + private boolean mIsExhausted; + + private ObserverListIterator() { + ObserverList.this.incrementIterationDepth(); + mListEndMarker = ObserverList.this.capacity(); + } + + @Override + public void rewind() { + compactListIfNeeded(); + ObserverList.this.incrementIterationDepth(); + mListEndMarker = ObserverList.this.capacity(); + mIsExhausted = false; + mIndex = 0; + } + + @Override + public boolean hasNext() { + int lookupIndex = mIndex; + while (lookupIndex < mListEndMarker + && ObserverList.this.getObserverAt(lookupIndex) == null) { + lookupIndex++; + } + if (lookupIndex < mListEndMarker) return true; + + // We have reached the end of the list, allow for compaction. + compactListIfNeeded(); + return false; + } + + @Override + public E next() { + // Advance if the current element is null. + while (mIndex < mListEndMarker && ObserverList.this.getObserverAt(mIndex) == null) { + mIndex++; + } + if (mIndex < mListEndMarker) return ObserverList.this.getObserverAt(mIndex++); + + // We have reached the end of the list, allow for compaction. + compactListIfNeeded(); + throw new NoSuchElementException(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + private void compactListIfNeeded() { + if (!mIsExhausted) { + mIsExhausted = true; + ObserverList.this.decrementIterationDepthAndCompactIfNeeded(); + } + } + } +}
diff --git a/src/base/android/java/src/org/chromium/base/PackageUtils.java b/src/base/android/java/src/org/chromium/base/PackageUtils.java new file mode 100644 index 0000000..a8e487b --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/PackageUtils.java
@@ -0,0 +1,56 @@ +// Copyright 2015 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. + +package org.chromium.base; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; + +/** + * This class provides package checking related methods. + */ +public class PackageUtils { + /** + * Retrieves the version of the given package installed on the device. + * + * @param context Any context. + * @param packageName Name of the package to find. + * @return The package's version code if found, -1 otherwise. + */ + public static int getPackageVersion(Context context, String packageName) { + int versionCode = -1; + PackageManager pm = context.getPackageManager(); + try { + PackageInfo packageInfo = pm.getPackageInfo(packageName, 0); + if (packageInfo != null) versionCode = packageInfo.versionCode; + } catch (PackageManager.NameNotFoundException e) { + // Do nothing, versionCode stays -1 + } + return versionCode; + } + + /** + * Decodes into a Bitmap an Image resource stored in another package. + * @param otherPackage The package containing the resource. + * @param resourceId The id of the resource. + * @return A Bitmap containing the resource or null if the package could not be found. + */ + public static Bitmap decodeImageResource(String otherPackage, int resourceId) { + PackageManager packageManager = ContextUtils.getApplicationContext().getPackageManager(); + try { + Resources resources = packageManager.getResourcesForApplication(otherPackage); + return BitmapFactory.decodeResource(resources, resourceId); + } catch (PackageManager.NameNotFoundException e) { + return null; + } + } + + private PackageUtils() { + // Hide constructor + } +}
diff --git a/src/base/android/java/src/org/chromium/base/PathService.java b/src/base/android/java/src/org/chromium/base/PathService.java new file mode 100644 index 0000000..9807c2e --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/PathService.java
@@ -0,0 +1,26 @@ +// Copyright 2012 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. + +package org.chromium.base; + +import org.chromium.base.annotations.JNINamespace; + +/** + * This class provides java side access to the native PathService. + */ +@JNINamespace("base::android") +public abstract class PathService { + + // Must match the value of DIR_MODULE in base/base_paths.h! + public static final int DIR_MODULE = 3; + + // Prevent instantiation. + private PathService() {} + + public static void override(int what, String path) { + nativeOverride(what, path); + } + + private static native void nativeOverride(int what, String path); +}
diff --git a/src/base/android/java/src/org/chromium/base/PathUtils.java b/src/base/android/java/src/org/chromium/base/PathUtils.java new file mode 100644 index 0000000..d09d6bf --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/PathUtils.java
@@ -0,0 +1,274 @@ +// Copyright 2012 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. + +package org.chromium.base; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.os.Build; +import android.os.Environment; +import android.os.SystemClock; +import android.system.Os; +import android.text.TextUtils; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.MainDex; +import org.chromium.base.metrics.RecordHistogram; +import org.chromium.base.task.AsyncTask; + +import java.io.File; +import java.util.ArrayList; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * This class provides the path related methods for the native library. + */ +@MainDex +public abstract class PathUtils { + private static final String TAG = "PathUtils"; + private static final String THUMBNAIL_DIRECTORY_NAME = "textures"; + + private static final int DATA_DIRECTORY = 0; + private static final int THUMBNAIL_DIRECTORY = 1; + private static final int CACHE_DIRECTORY = 2; + private static final int NUM_DIRECTORIES = 3; + private static final AtomicBoolean sInitializationStarted = new AtomicBoolean(); + private static FutureTask<String[]> sDirPathFetchTask; + + // If the FutureTask started in setPrivateDataDirectorySuffix() fails to complete by the time we + // need the values, we will need the suffix so that we can restart the task synchronously on + // the UI thread. + private static String sDataDirectorySuffix; + private static String sCacheSubDirectory; + + // Prevent instantiation. + private PathUtils() {} + + /** + * Initialization-on-demand holder. This exists for thread-safe lazy initialization. It will + * cause getOrComputeDirectoryPaths() to be called (safely) the first time DIRECTORY_PATHS is + * accessed. + * + * <p>See https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom. + */ + private static class Holder { + private static final String[] DIRECTORY_PATHS = getOrComputeDirectoryPaths(); + } + + /** + * Get the directory paths from sDirPathFetchTask if available, or compute it synchronously + * on the UI thread otherwise. This should only be called as part of Holder's initialization + * above to guarantee thread-safety as part of the initialization-on-demand holder idiom. + */ + private static String[] getOrComputeDirectoryPaths() { + try { + // We need to call sDirPathFetchTask.cancel() here to prevent races. If it returns + // true, that means that the task got canceled successfully (and thus, it did not + // finish running its task). Otherwise, it failed to cancel, meaning that it was + // already finished. + if (sDirPathFetchTask.cancel(false)) { + // Allow disk access here because we have no other choice. + try (StrictModeContext unused = StrictModeContext.allowDiskWrites()) { + // sDirPathFetchTask did not complete. We have to run the code it was supposed + // to be responsible for synchronously on the UI thread. + return PathUtils.setPrivateDataDirectorySuffixInternal(); + } + } else { + // sDirPathFetchTask succeeded, and the values we need should be ready to access + // synchronously in its internal future. + return sDirPathFetchTask.get(); + } + } catch (InterruptedException e) { + } catch (ExecutionException e) { + } + + return null; + } + + @SuppressLint("NewApi") + private static void chmod(String path, int mode) { + // Both Os.chmod and ErrnoException require SDK >= 21. But while Dalvik on < 21 tolerates + // Os.chmod, it throws VerifyError for ErrnoException, so catch Exception instead. + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return; + try { + Os.chmod(path, mode); + } catch (Exception e) { + Log.e(TAG, "Failed to set permissions for path \"" + path + "\""); + } + } + + /** + * Fetch the path of the directory where private data is to be stored by the application. This + * is meant to be called in an FutureTask in setPrivateDataDirectorySuffix(), but if we need the + * result before the FutureTask has had a chance to finish, then it's best to cancel the task + * and run it on the UI thread instead, inside getOrComputeDirectoryPaths(). + * + * @see Context#getDir(String, int) + */ + private static String[] setPrivateDataDirectorySuffixInternal() { + String[] paths = new String[NUM_DIRECTORIES]; + Context appContext = ContextUtils.getApplicationContext(); + paths[DATA_DIRECTORY] = appContext.getDir( + sDataDirectorySuffix, Context.MODE_PRIVATE).getPath(); + // MODE_PRIVATE results in rwxrwx--x, but we want rwx------, as a defence-in-depth measure. + chmod(paths[DATA_DIRECTORY], 0700); + paths[THUMBNAIL_DIRECTORY] = appContext.getDir( + THUMBNAIL_DIRECTORY_NAME, Context.MODE_PRIVATE).getPath(); + if (appContext.getCacheDir() != null) { + if (sCacheSubDirectory == null) { + paths[CACHE_DIRECTORY] = appContext.getCacheDir().getPath(); + } else { + paths[CACHE_DIRECTORY] = + new File(appContext.getCacheDir(), sCacheSubDirectory).getPath(); + } + } + return paths; + } + + /** + * Starts an asynchronous task to fetch the path of the directory where private data is to be + * stored by the application. + * + * <p>This task can run long (or more likely be delayed in a large task queue), in which case we + * want to cancel it and run on the UI thread instead. Unfortunately, this means keeping a bit + * of extra static state - we need to store the suffix and the application context in case we + * need to try to re-execute later. + * + * @param suffix The private data directory suffix. + * @param cacheSubDir The subdirectory in the cache directory to use, if non-null. + * @see Context#getDir(String, int) + */ + public static void setPrivateDataDirectorySuffix(String suffix, String cacheSubDir) { + // This method should only be called once, but many tests end up calling it multiple times, + // so adding a guard here. + if (!sInitializationStarted.getAndSet(true)) { + assert ContextUtils.getApplicationContext() != null; + sDataDirectorySuffix = suffix; + sCacheSubDirectory = cacheSubDir; + + // We don't use an AsyncTask because this function is called in early Webview startup + // and it won't always have a UI thread available. Thus, we can't use AsyncTask which + // inherently posts to the UI thread for onPostExecute(). + sDirPathFetchTask = new FutureTask<>(PathUtils::setPrivateDataDirectorySuffixInternal); + AsyncTask.THREAD_POOL_EXECUTOR.execute(sDirPathFetchTask); + } + } + + public static void setPrivateDataDirectorySuffix(String suffix) { + setPrivateDataDirectorySuffix(suffix, null); + } + + /** + * @param index The index of the cached directory path. + * @return The directory path requested. + */ + private static String getDirectoryPath(int index) { + return Holder.DIRECTORY_PATHS[index]; + } + + /** + * @return the private directory that is used to store application data. + */ + @CalledByNative + public static String getDataDirectory() { + assert sDirPathFetchTask != null : "setDataDirectorySuffix must be called first."; + return getDirectoryPath(DATA_DIRECTORY); + } + + /** + * @return the cache directory. + */ + @CalledByNative + public static String getCacheDirectory() { + assert sDirPathFetchTask != null : "setDataDirectorySuffix must be called first."; + return getDirectoryPath(CACHE_DIRECTORY); + } + + @CalledByNative + public static String getThumbnailCacheDirectory() { + assert sDirPathFetchTask != null : "setDataDirectorySuffix must be called first."; + return getDirectoryPath(THUMBNAIL_DIRECTORY); + } + + /** + * @return the public downloads directory. + */ + @SuppressWarnings("unused") + @CalledByNative + private static String getDownloadsDirectory() { + // Temporarily allowing disk access while fixing. TODO: http://crbug.com/508615 + try (StrictModeContext unused = StrictModeContext.allowDiskReads()) { + long time = SystemClock.elapsedRealtime(); + String downloadsPath = + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + .getPath(); + RecordHistogram.recordTimesHistogram("Android.StrictMode.DownloadsDir", + SystemClock.elapsedRealtime() - time, TimeUnit.MILLISECONDS); + return downloadsPath; + } + } + + /** + * @return Download directories including the default storage directory on SD card, and a + * private directory on external SD card. + */ + @SuppressWarnings("unused") + @CalledByNative + public static String[] getAllPrivateDownloadsDirectories() { + File[] files; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + try (StrictModeContext unused = StrictModeContext.allowDiskWrites()) { + files = ContextUtils.getApplicationContext().getExternalFilesDirs( + Environment.DIRECTORY_DOWNLOADS); + } + } else { + files = new File[] {Environment.getExternalStorageDirectory()}; + } + + ArrayList<String> absolutePaths = new ArrayList<String>(); + for (int i = 0; i < files.length; ++i) { + if (files[i] == null || TextUtils.isEmpty(files[i].getAbsolutePath())) continue; + absolutePaths.add(files[i].getAbsolutePath()); + } + + return absolutePaths.toArray(new String[absolutePaths.size()]); + } + + /** + * @return the path to native libraries. + */ + @SuppressWarnings("unused") + @CalledByNative + private static String getNativeLibraryDirectory() { + ApplicationInfo ai = ContextUtils.getApplicationContext().getApplicationInfo(); + if ((ai.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0 + || (ai.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { + return ai.nativeLibraryDir; + } + + return "/system/lib/"; + } + + /** + * @return the external storage directory. + */ + @SuppressWarnings("unused") + @CalledByNative + public static String getExternalStorageDirectory() { + return Environment.getExternalStorageDirectory().getAbsolutePath(); + } + + /** + * @ return the path to the base apk. + */ + @SuppressWarnings("unused") + @CalledByNative + public static String getPathToBaseApk() { + return ContextUtils.getApplicationContext().getApplicationInfo().sourceDir; + } +}
diff --git a/src/base/android/java/src/org/chromium/base/PowerMonitor.java b/src/base/android/java/src/org/chromium/base/PowerMonitor.java new file mode 100644 index 0000000..ae36a75 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/PowerMonitor.java
@@ -0,0 +1,80 @@ +// Copyright 2012 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. + +package org.chromium.base; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; + +/** + * Integrates native PowerMonitor with the java side. + */ +@JNINamespace("base::android") +public class PowerMonitor { + private static PowerMonitor sInstance; + + private boolean mIsBatteryPower; + + public static void createForTests() { + // Applications will create this once the JNI side has been fully wired up both sides. For + // tests, we just need native -> java, that is, we don't need to notify java -> native on + // creation. + sInstance = new PowerMonitor(); + } + + /** + * Create a PowerMonitor instance if none exists. + */ + public static void create() { + ThreadUtils.assertOnUiThread(); + + if (sInstance != null) return; + + Context context = ContextUtils.getApplicationContext(); + sInstance = new PowerMonitor(); + IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); + Intent batteryStatusIntent = context.registerReceiver(null, ifilter); + if (batteryStatusIntent != null) onBatteryChargingChanged(batteryStatusIntent); + + IntentFilter powerConnectedFilter = new IntentFilter(); + powerConnectedFilter.addAction(Intent.ACTION_POWER_CONNECTED); + powerConnectedFilter.addAction(Intent.ACTION_POWER_DISCONNECTED); + context.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + PowerMonitor.onBatteryChargingChanged(intent); + } + }, powerConnectedFilter); + } + + private PowerMonitor() { + } + + private static void onBatteryChargingChanged(Intent intent) { + assert sInstance != null; + int chargePlug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); + // If we're not plugged, assume we're running on battery power. + sInstance.mIsBatteryPower = chargePlug != BatteryManager.BATTERY_PLUGGED_USB + && chargePlug != BatteryManager.BATTERY_PLUGGED_AC; + nativeOnBatteryChargingChanged(); + } + + @CalledByNative + private static boolean isBatteryPower() { + // Creation of the PowerMonitor can be deferred based on the browser startup path. If the + // battery power is requested prior to the browser triggering the creation, force it to be + // created now. + if (sInstance == null) create(); + + return sInstance.mIsBatteryPower; + } + + private static native void nativeOnBatteryChargingChanged(); +}
diff --git a/src/base/android/java/src/org/chromium/base/Promise.java b/src/base/android/java/src/org/chromium/base/Promise.java new file mode 100644 index 0000000..4319148 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/Promise.java
@@ -0,0 +1,294 @@ +// Copyright 2016 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. + +package org.chromium.base; + +import android.os.Handler; +import android.support.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.LinkedList; +import java.util.List; + +/** + * A Promise class to be used as a placeholder for a result that will be provided asynchronously. + * It must only be accessed from a single thread. + * @param <T> The type the Promise will be fulfilled with. + */ +public class Promise<T> { + // TODO(peconn): Implement rejection handlers that can recover from rejection. + + @Retention(RetentionPolicy.SOURCE) + @IntDef({UNFULFILLED, FULFILLED, REJECTED}) + private @interface PromiseState {} + + private static final int UNFULFILLED = 0; + private static final int FULFILLED = 1; + private static final int REJECTED = 2; + + @PromiseState + private int mState = UNFULFILLED; + + private T mResult; + private final List<Callback<T>> mFulfillCallbacks = new LinkedList<>(); + + private Exception mRejectReason; + private final List<Callback<Exception>> mRejectCallbacks = new LinkedList<>(); + + private final Thread mThread = Thread.currentThread(); + private final Handler mHandler = new Handler(); + + private boolean mThrowingRejectionHandler; + + /** + * A function class for use when chaining Promises with {@link Promise#then(Function)}. + * @param <A> The type of the function input. + * @param <R> The type of the function output. + */ + public interface Function<A, R> { + R apply(A argument); + } + + /** + * A function class for use when chaining Promises with {@link Promise#then(AsyncFunction)}. + * @param <A> The type of the function input. + * @param <R> The type of the function output. + */ + public interface AsyncFunction<A, R> { + Promise<R> apply(A argument); + } + + /** + * An exception class for when a rejected Promise is not handled and cannot pass the rejection + * to a subsequent Promise. + */ + public static class UnhandledRejectionException extends RuntimeException { + public UnhandledRejectionException(String message, Throwable cause) { + super(message, cause); + } + } + + /** + * Convenience method that calls {@link #then(Callback, Callback)} providing a rejection + * {@link Callback} that throws a {@link UnhandledRejectionException}. Only use this on + * Promises that do not have rejection handlers or dependant Promises. + */ + public void then(Callback<T> onFulfill) { + checkThread(); + + // Allow multiple single argument then(Callback)'s, but don't bother adding duplicate + // throwing rejection handlers. + if (mThrowingRejectionHandler) { + thenInner(onFulfill); + return; + } + + assert mRejectCallbacks.size() == 0 : "Do not call the single argument " + + "Promise.then(Callback) on a Promise that already has a rejection handler."; + + Callback<Exception> onReject = reason -> { + throw new UnhandledRejectionException( + "Promise was rejected without a rejection handler.", reason); + }; + + then(onFulfill, onReject); + mThrowingRejectionHandler = true; + } + + /** + * Queues {@link Callback}s to be run when the Promise is either fulfilled or rejected. If the + * Promise is already fulfilled or rejected, the appropriate callback will be run on the next + * iteration of the message loop. + * + * @param onFulfill The Callback to be called on fulfillment. + * @param onReject The Callback to be called on rejection. The argument to onReject will + * may be null if the Promise was rejected manually. + */ + public void then(Callback<T> onFulfill, Callback<Exception> onReject) { + checkThread(); + thenInner(onFulfill); + exceptInner(onReject); + } + + /** + * Adds a rejection handler to the Promise. This handler will be called if this Promise or any + * Promises this Promise depends on is rejected or fails. The {@link Callback} will be given + * the exception that caused the rejection, or null if the rejection was manual (caused by a + * call to {@link #reject()}. + */ + public void except(Callback<Exception> onReject) { + checkThread(); + exceptInner(onReject); + } + + private void thenInner(Callback<T> onFulfill) { + if (mState == FULFILLED) { + postCallbackToLooper(onFulfill, mResult); + } else if (mState == UNFULFILLED) { + mFulfillCallbacks.add(onFulfill); + } + } + + private void exceptInner(Callback<Exception> onReject) { + assert !mThrowingRejectionHandler : "Do not add an exception handler to a Promise you have " + + "called the single argument Promise.then(Callback) on."; + + if (mState == REJECTED) { + postCallbackToLooper(onReject, mRejectReason); + } else if (mState == UNFULFILLED) { + mRejectCallbacks.add(onReject); + } + } + + /** + * Queues a {@link Promise.Function} to be run when the Promise is fulfilled. When this Promise + * is fulfilled, the function will be run and its result will be place in the returned Promise. + */ + public <R> Promise<R> then(final Function<T, R> function) { + checkThread(); + + // Create a new Promise to store the result of the function. + final Promise<R> promise = new Promise<>(); + + // Once this Promise is fulfilled: + // - Apply the given function to the result. + // - Fulfill the new Promise. + thenInner(result -> { + try { + promise.fulfill(function.apply(result)); + } catch (Exception e) { + // If function application fails, reject the next Promise. + promise.reject(e); + } + }); + + // If this Promise is rejected, reject the next Promise. + exceptInner(promise::reject); + + return promise; + } + + /** + * Queues a {@link Promise.AsyncFunction} to be run when the Promise is fulfilled. When this + * Promise is fulfilled, the AsyncFunction will be run. When the result of the AsyncFunction is + * available, it will be placed in the returned Promise. + */ + public <R> Promise<R> then(final AsyncFunction<T, R> function) { + checkThread(); + + // Create a new Promise to be returned. + final Promise<R> promise = new Promise<>(); + + // Once this Promise is fulfilled: + // - Apply the given function to the result (giving us an inner Promise). + // - On fulfillment of this inner Promise, fulfill our return Promise. + thenInner(result -> { + try { + // When the inner Promise is fulfilled, fulfill the return Promise. + // Alternatively, if the inner Promise is rejected, reject the return Promise. + function.apply(result).then(promise::fulfill, promise::reject); + } catch (Exception e) { + // If creating the inner Promise failed, reject the next Promise. + promise.reject(e); + } + }); + + // If this Promise is rejected, reject the next Promise. + exceptInner(promise::reject); + + return promise; + } + + /** + * Fulfills the Promise with the result and passes it to any {@link Callback}s previously queued + * on the next iteration of the message loop. + */ + public void fulfill(final T result) { + checkThread(); + assert mState == UNFULFILLED; + + mState = FULFILLED; + mResult = result; + + for (final Callback<T> callback : mFulfillCallbacks) { + postCallbackToLooper(callback, result); + } + + mFulfillCallbacks.clear(); + } + + /** + * Rejects the Promise, rejecting all those Promises that rely on it. + * + * This may throw an exception if a dependent Promise fails to handle the rejection, so it is + * important to make it explicit when a Promise may be rejected, so that users of that Promise + * know to provide rejection handling. + */ + public void reject(final Exception reason) { + checkThread(); + assert mState == UNFULFILLED; + + mState = REJECTED; + mRejectReason = reason; + + for (final Callback<Exception> callback : mRejectCallbacks) { + postCallbackToLooper(callback, reason); + } + mRejectCallbacks.clear(); + } + + /** + * Rejects a Promise, see {@link #reject(Exception)}. + */ + public void reject() { + reject(null); + } + + /** + * Returns whether the promise is fulfilled. + */ + public boolean isFulfilled() { + checkThread(); + return mState == FULFILLED; + } + + /** + * Returns whether the promise is rejected. + */ + public boolean isRejected() { + checkThread(); + return mState == REJECTED; + } + + /** + * Must be called after the promise has been fulfilled. + * + * @return The promised result. + */ + public T getResult() { + assert isFulfilled(); + return mResult; + } + + /** + * Convenience method to return a Promise fulfilled with the given result. + */ + public static <T> Promise<T> fulfilled(T result) { + Promise<T> promise = new Promise<>(); + promise.fulfill(result); + return promise; + } + + private void checkThread() { + assert mThread == Thread.currentThread() : "Promise must only be used on a single Thread."; + } + + // We use a different template parameter here so this can be used for both T and Throwables. + private <S> void postCallbackToLooper(final Callback<S> callback, final S result) { + // Post the callbacks to the Thread looper so we don't get a long chain of callbacks + // holding up the thread. + mHandler.post(() -> callback.onResult(result)); + } +}
diff --git a/src/base/android/java/src/org/chromium/base/SecureRandomInitializer.java b/src/base/android/java/src/org/chromium/base/SecureRandomInitializer.java new file mode 100644 index 0000000..bfd7b49 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/SecureRandomInitializer.java
@@ -0,0 +1,35 @@ +// Copyright 2014 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. + +package org.chromium.base; + +import android.annotation.SuppressLint; + +import java.io.FileInputStream; +import java.io.IOException; +import java.security.SecureRandom; + +/** + * This class contains code to initialize a SecureRandom generator securely on Android platforms + * <= 4.3. See + * {@link http://android-developers.blogspot.com/2013/08/some-securerandom-thoughts.html}. + */ +// TODO(crbug.com/635567): Fix this properly. +@SuppressLint("SecureRandom") +public class SecureRandomInitializer { + private static final int NUM_RANDOM_BYTES = 16; + + /** + * Safely initializes the random number generator, by seeding it with data from /dev/urandom. + */ + public static void initialize(SecureRandom generator) throws IOException { + try (FileInputStream fis = new FileInputStream("/dev/urandom")) { + byte[] seedBytes = new byte[NUM_RANDOM_BYTES]; + if (fis.read(seedBytes) != seedBytes.length) { + throw new IOException("Failed to get enough random data."); + } + generator.setSeed(seedBytes); + } + } +}
diff --git a/src/base/android/java/src/org/chromium/base/StreamUtil.java b/src/base/android/java/src/org/chromium/base/StreamUtil.java new file mode 100644 index 0000000..aa5e389 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/StreamUtil.java
@@ -0,0 +1,44 @@ +// Copyright 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. + +package org.chromium.base; + +import java.io.Closeable; +import java.io.IOException; +import java.util.zip.ZipFile; + +/** + * Helper methods to deal with stream related tasks. + */ +public class StreamUtil { + /** + * Handle closing a {@link java.io.Closeable} via {@link java.io.Closeable#close()} and catch + * the potentially thrown {@link java.io.IOException}. + * @param closeable The Closeable to be closed. + */ + public static void closeQuietly(Closeable closeable) { + if (closeable == null) return; + + try { + closeable.close(); + } catch (IOException ex) { + // Ignore the exception on close. + } + } + + /** + * Overload of the above function for {@link ZipFile} which implements Closeable only starting + * from api19. + * @param zipFile - the ZipFile to be closed. + */ + public static void closeQuietly(ZipFile zipFile) { + if (zipFile == null) return; + + try { + zipFile.close(); + } catch (IOException ex) { + // Ignore the exception on close. + } + } +}
diff --git a/src/base/android/java/src/org/chromium/base/StrictModeContext.java b/src/base/android/java/src/org/chromium/base/StrictModeContext.java new file mode 100644 index 0000000..beaaac0 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/StrictModeContext.java
@@ -0,0 +1,85 @@ +// Copyright 2017 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. + +package org.chromium.base; + +import android.os.StrictMode; + +import java.io.Closeable; + +/** + * Enables try-with-resources compatible StrictMode violation whitelisting. + * + * Example: + * <pre> + * try (StrictModeContext unused = StrictModeContext.allowDiskWrites()) { + * return Example.doThingThatRequiresDiskWrites(); + * } + * </pre> + * + */ +public final class StrictModeContext implements Closeable { + private final StrictMode.ThreadPolicy mThreadPolicy; + private final StrictMode.VmPolicy mVmPolicy; + + private StrictModeContext(StrictMode.ThreadPolicy threadPolicy, StrictMode.VmPolicy vmPolicy) { + mThreadPolicy = threadPolicy; + mVmPolicy = vmPolicy; + } + + private StrictModeContext(StrictMode.ThreadPolicy threadPolicy) { + this(threadPolicy, null); + } + + private StrictModeContext(StrictMode.VmPolicy vmPolicy) { + this(null, vmPolicy); + } + + /** + * Convenience method for disabling all VM-level StrictMode checks with try-with-resources. + * Includes everything listed here: + * https://developer.android.com/reference/android/os/StrictMode.VmPolicy.Builder.html + */ + public static StrictModeContext allowAllVmPolicies() { + StrictMode.VmPolicy oldPolicy = StrictMode.getVmPolicy(); + StrictMode.setVmPolicy(StrictMode.VmPolicy.LAX); + return new StrictModeContext(oldPolicy); + } + + /** + * Convenience method for disabling StrictMode for disk-writes with try-with-resources. + */ + public static StrictModeContext allowDiskWrites() { + StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); + return new StrictModeContext(oldPolicy); + } + + /** + * Convenience method for disabling StrictMode for disk-reads with try-with-resources. + */ + public static StrictModeContext allowDiskReads() { + StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); + return new StrictModeContext(oldPolicy); + } + + /** + * Convenience method for disabling StrictMode for slow calls with try-with-resources. + */ + public static StrictModeContext allowSlowCalls() { + StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); + StrictMode.setThreadPolicy( + new StrictMode.ThreadPolicy.Builder(oldPolicy).permitCustomSlowCalls().build()); + return new StrictModeContext(oldPolicy); + } + + @Override + public void close() { + if (mThreadPolicy != null) { + StrictMode.setThreadPolicy(mThreadPolicy); + } + if (mVmPolicy != null) { + StrictMode.setVmPolicy(mVmPolicy); + } + } +} \ No newline at end of file
diff --git a/src/base/android/java/src/org/chromium/base/Supplier.java b/src/base/android/java/src/org/chromium/base/Supplier.java new file mode 100644 index 0000000..350da57 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/Supplier.java
@@ -0,0 +1,18 @@ +// Copyright 2018 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. + +package org.chromium.base; + +/** + * Based on Java 8's java.util.function.Supplier. + * Same as Callable<T>, but without a checked Exception. + * + * @param <T> Return type. + */ +public interface Supplier<T> { + /** + * Returns a value. + */ + T get(); +}
diff --git a/src/base/android/java/src/org/chromium/base/SysUtils.java b/src/base/android/java/src/org/chromium/base/SysUtils.java new file mode 100644 index 0000000..d4eb30d --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/SysUtils.java
@@ -0,0 +1,199 @@ +// Copyright 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. + +package org.chromium.base; + +import android.annotation.TargetApi; +import android.app.ActivityManager; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.StrictMode; +import android.util.Log; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.metrics.CachedMetrics; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Exposes system related information about the current device. + */ +@JNINamespace("base::android") +public class SysUtils { + // A device reporting strictly more total memory in megabytes cannot be considered 'low-end'. + private static final int ANDROID_LOW_MEMORY_DEVICE_THRESHOLD_MB = 512; + private static final int ANDROID_O_LOW_MEMORY_DEVICE_THRESHOLD_MB = 1024; + + private static final String TAG = "SysUtils"; + + private static Boolean sLowEndDevice; + private static Integer sAmountOfPhysicalMemoryKB; + + private static CachedMetrics.BooleanHistogramSample sLowEndMatches = + new CachedMetrics.BooleanHistogramSample("Android.SysUtilsLowEndMatches"); + + private SysUtils() { } + + /** + * Return the amount of physical memory on this device in kilobytes. + * @return Amount of physical memory in kilobytes, or 0 if there was + * an error trying to access the information. + */ + private static int detectAmountOfPhysicalMemoryKB() { + // Extract total memory RAM size by parsing /proc/meminfo, note that + // this is exactly what the implementation of sysconf(_SC_PHYS_PAGES) + // does. However, it can't be called because this method must be + // usable before any native code is loaded. + + // An alternative is to use ActivityManager.getMemoryInfo(), but this + // requires a valid ActivityManager handle, which can only come from + // a valid Context object, which itself cannot be retrieved + // during early startup, where this method is called. And making it + // an explicit parameter here makes all call paths _much_ more + // complicated. + + Pattern pattern = Pattern.compile("^MemTotal:\\s+([0-9]+) kB$"); + // Synchronously reading files in /proc in the UI thread is safe. + StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); + try { + FileReader fileReader = new FileReader("/proc/meminfo"); + try { + BufferedReader reader = new BufferedReader(fileReader); + try { + String line; + for (;;) { + line = reader.readLine(); + if (line == null) { + Log.w(TAG, "/proc/meminfo lacks a MemTotal entry?"); + break; + } + Matcher m = pattern.matcher(line); + if (!m.find()) continue; + + int totalMemoryKB = Integer.parseInt(m.group(1)); + // Sanity check. + if (totalMemoryKB <= 1024) { + Log.w(TAG, "Invalid /proc/meminfo total size in kB: " + m.group(1)); + break; + } + + return totalMemoryKB; + } + + } finally { + reader.close(); + } + } finally { + fileReader.close(); + } + } catch (Exception e) { + Log.w(TAG, "Cannot get total physical size from /proc/meminfo", e); + } finally { + StrictMode.setThreadPolicy(oldPolicy); + } + + return 0; + } + + /** + * @return Whether or not this device should be considered a low end device. + */ + @CalledByNative + public static boolean isLowEndDevice() { + if (sLowEndDevice == null) { + sLowEndDevice = detectLowEndDevice(); + } + return sLowEndDevice.booleanValue(); + } + + /** + * @return Whether or not this device should be considered a low end device. + */ + public static int amountOfPhysicalMemoryKB() { + if (sAmountOfPhysicalMemoryKB == null) { + sAmountOfPhysicalMemoryKB = detectAmountOfPhysicalMemoryKB(); + } + return sAmountOfPhysicalMemoryKB.intValue(); + } + + /** + * @return Whether or not the system has low available memory. + */ + @CalledByNative + public static boolean isCurrentlyLowMemory() { + ActivityManager am = + (ActivityManager) ContextUtils.getApplicationContext().getSystemService( + Context.ACTIVITY_SERVICE); + ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo(); + am.getMemoryInfo(info); + return info.lowMemory; + } + + /** + * Resets the cached value, if any. + */ + @VisibleForTesting + public static void resetForTesting() { + sLowEndDevice = null; + sAmountOfPhysicalMemoryKB = null; + } + + public static boolean hasCamera(final Context context) { + final PackageManager pm = context.getPackageManager(); + // JellyBean support. + boolean hasCamera = pm.hasSystemFeature(PackageManager.FEATURE_CAMERA); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + hasCamera |= pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY); + } + return hasCamera; + } + + @TargetApi(Build.VERSION_CODES.KITKAT) + private static boolean detectLowEndDevice() { + assert CommandLine.isInitialized(); + if (CommandLine.getInstance().hasSwitch(BaseSwitches.ENABLE_LOW_END_DEVICE_MODE)) { + return true; + } + if (CommandLine.getInstance().hasSwitch(BaseSwitches.DISABLE_LOW_END_DEVICE_MODE)) { + return false; + } + + sAmountOfPhysicalMemoryKB = detectAmountOfPhysicalMemoryKB(); + boolean isLowEnd = true; + if (sAmountOfPhysicalMemoryKB <= 0) { + isLowEnd = false; + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + isLowEnd = sAmountOfPhysicalMemoryKB / 1024 <= ANDROID_O_LOW_MEMORY_DEVICE_THRESHOLD_MB; + } else { + isLowEnd = sAmountOfPhysicalMemoryKB / 1024 <= ANDROID_LOW_MEMORY_DEVICE_THRESHOLD_MB; + } + + // For evaluation purposes check whether our computation agrees with Android API value. + Context appContext = ContextUtils.getApplicationContext(); + boolean isLowRam = false; + if (appContext != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + isLowRam = ((ActivityManager) ContextUtils.getApplicationContext().getSystemService( + Context.ACTIVITY_SERVICE)) + .isLowRamDevice(); + } + sLowEndMatches.record(isLowEnd == isLowRam); + + return isLowEnd; + } + + /** + * Creates a new trace event to log the number of minor / major page faults, if tracing is + * enabled. + */ + public static void logPageFaultCountToTracing() { + nativeLogPageFaultCountToTracing(); + } + + private static native void nativeLogPageFaultCountToTracing(); +}
diff --git a/src/base/android/java/src/org/chromium/base/ThreadUtils.java b/src/base/android/java/src/org/chromium/base/ThreadUtils.java new file mode 100644 index 0000000..61872a0 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/ThreadUtils.java
@@ -0,0 +1,268 @@ +// Copyright 2012 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. + +package org.chromium.base; + +import android.os.Handler; +import android.os.Looper; +import android.os.Process; + +import org.chromium.base.annotations.CalledByNative; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; + +/** + * Helper methods to deal with threading related tasks. + */ +public class ThreadUtils { + + private static final Object sLock = new Object(); + + private static boolean sWillOverride; + + private static Handler sUiThreadHandler; + + private static boolean sThreadAssertsDisabled; + + public static void setWillOverrideUiThread() { + synchronized (sLock) { + sWillOverride = true; + } + } + + public static void setUiThread(Looper looper) { + synchronized (sLock) { + if (looper == null) { + // Used to reset the looper after tests. + sUiThreadHandler = null; + return; + } + if (sUiThreadHandler != null && sUiThreadHandler.getLooper() != looper) { + throw new RuntimeException("UI thread looper is already set to " + + sUiThreadHandler.getLooper() + " (Main thread looper is " + + Looper.getMainLooper() + "), cannot set to new looper " + looper); + } else { + sUiThreadHandler = new Handler(looper); + } + } + } + + public static Handler getUiThreadHandler() { + synchronized (sLock) { + if (sUiThreadHandler == null) { + if (sWillOverride) { + throw new RuntimeException("Did not yet override the UI thread"); + } + sUiThreadHandler = new Handler(Looper.getMainLooper()); + } + return sUiThreadHandler; + } + } + + /** + * Run the supplied Runnable on the main thread. The method will block until the Runnable + * completes. + * + * @param r The Runnable to run. + */ + public static void runOnUiThreadBlocking(final Runnable r) { + if (runningOnUiThread()) { + r.run(); + } else { + FutureTask<Void> task = new FutureTask<Void>(r, null); + postOnUiThread(task); + try { + task.get(); + } catch (Exception e) { + throw new RuntimeException("Exception occurred while waiting for runnable", e); + } + } + } + + /** + * Run the supplied Callable on the main thread, wrapping any exceptions in a RuntimeException. + * The method will block until the Callable completes. + * + * @param c The Callable to run + * @return The result of the callable + */ + @VisibleForTesting + public static <T> T runOnUiThreadBlockingNoException(Callable<T> c) { + try { + return runOnUiThreadBlocking(c); + } catch (ExecutionException e) { + throw new RuntimeException("Error occurred waiting for callable", e); + } + } + + /** + * Run the supplied Callable on the main thread, The method will block until the Callable + * completes. + * + * @param c The Callable to run + * @return The result of the callable + * @throws ExecutionException c's exception + */ + public static <T> T runOnUiThreadBlocking(Callable<T> c) throws ExecutionException { + FutureTask<T> task = new FutureTask<T>(c); + runOnUiThread(task); + try { + return task.get(); + } catch (InterruptedException e) { + throw new RuntimeException("Interrupted waiting for callable", e); + } + } + + /** + * Run the supplied FutureTask on the main thread. The method will block only if the current + * thread is the main thread. + * + * @param task The FutureTask to run + * @return The queried task (to aid inline construction) + */ + public static <T> FutureTask<T> runOnUiThread(FutureTask<T> task) { + if (runningOnUiThread()) { + task.run(); + } else { + postOnUiThread(task); + } + return task; + } + + /** + * Run the supplied Callable on the main thread. The method will block only if the current + * thread is the main thread. + * + * @param c The Callable to run + * @return A FutureTask wrapping the callable to retrieve results + */ + public static <T> FutureTask<T> runOnUiThread(Callable<T> c) { + return runOnUiThread(new FutureTask<T>(c)); + } + + /** + * Run the supplied Runnable on the main thread. The method will block only if the current + * thread is the main thread. + * + * @param r The Runnable to run + */ + public static void runOnUiThread(Runnable r) { + if (runningOnUiThread()) { + r.run(); + } else { + getUiThreadHandler().post(r); + } + } + + /** + * Post the supplied FutureTask to run on the main thread. The method will not block, even if + * called on the UI thread. + * + * @param task The FutureTask to run + * @return The queried task (to aid inline construction) + */ + public static <T> FutureTask<T> postOnUiThread(FutureTask<T> task) { + getUiThreadHandler().post(task); + return task; + } + + /** + * Post the supplied Runnable to run on the main thread. The method will not block, even if + * called on the UI thread. + * + * @param task The Runnable to run + */ + public static void postOnUiThread(Runnable task) { + getUiThreadHandler().post(task); + } + + /** + * Post the supplied Runnable to run on the main thread after the given amount of time. The + * method will not block, even if called on the UI thread. + * + * @param task The Runnable to run + * @param delayMillis The delay in milliseconds until the Runnable will be run + */ + @VisibleForTesting + public static void postOnUiThreadDelayed(Runnable task, long delayMillis) { + getUiThreadHandler().postDelayed(task, delayMillis); + } + + /** + * Throw an exception (when DCHECKs are enabled) if currently not running on the UI thread. + * + * Can be disabled by setThreadAssertsDisabledForTesting(true). + */ + public static void assertOnUiThread() { + if (sThreadAssertsDisabled) return; + + assert runningOnUiThread() : "Must be called on the UI thread."; + } + + /** + * Throw an exception (regardless of build) if currently not running on the UI thread. + * + * Can be disabled by setThreadAssertsEnabledForTesting(false). + * + * @see #assertOnUiThread() + */ + public static void checkUiThread() { + if (!sThreadAssertsDisabled && !runningOnUiThread()) { + throw new IllegalStateException("Must be called on the UI thread."); + } + } + + /** + * Throw an exception (when DCHECKs are enabled) if currently running on the UI thread. + * + * Can be disabled by setThreadAssertsDisabledForTesting(true). + */ + public static void assertOnBackgroundThread() { + if (sThreadAssertsDisabled) return; + + assert !runningOnUiThread() : "Must be called on a thread other than UI."; + } + + /** + * Disables thread asserts. + * + * Can be used by tests where code that normally runs multi-threaded is going to run + * single-threaded for the test (otherwise asserts that are valid in production would fail in + * those tests). + */ + public static void setThreadAssertsDisabledForTesting(boolean disabled) { + sThreadAssertsDisabled = disabled; + } + + /** + * @return true iff the current thread is the main (UI) thread. + */ + public static boolean runningOnUiThread() { + return getUiThreadHandler().getLooper() == Looper.myLooper(); + } + + public static Looper getUiThreadLooper() { + return getUiThreadHandler().getLooper(); + } + + /** + * Set thread priority to audio. + */ + @CalledByNative + public static void setThreadPriorityAudio(int tid) { + Process.setThreadPriority(tid, Process.THREAD_PRIORITY_AUDIO); + } + + /** + * Checks whether Thread priority is THREAD_PRIORITY_AUDIO or not. + * @param tid Thread id. + * @return true for THREAD_PRIORITY_AUDIO and false otherwise. + */ + @CalledByNative + private static boolean isThreadPriorityAudio(int tid) { + return Process.getThreadPriority(tid) == Process.THREAD_PRIORITY_AUDIO; + } +}
diff --git a/src/base/android/java/src/org/chromium/base/TimeUtils.java b/src/base/android/java/src/org/chromium/base/TimeUtils.java new file mode 100644 index 0000000..dcacabf --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/TimeUtils.java
@@ -0,0 +1,18 @@ +// Copyright 2016 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. + +package org.chromium.base; + +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.MainDex; + +/** Time-related utilities. */ +@JNINamespace("base::android") +@MainDex +public class TimeUtils { + private TimeUtils() {} + + /** Returns TimeTicks::Now() in microseconds. */ + public static native long nativeGetTimeTicksNowUs(); +}
diff --git a/src/base/android/java/src/org/chromium/base/TimezoneUtils.java b/src/base/android/java/src/org/chromium/base/TimezoneUtils.java new file mode 100644 index 0000000..cddd3d9 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/TimezoneUtils.java
@@ -0,0 +1,36 @@ +// Copyright 2017 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. + +package org.chromium.base; + +import android.os.StrictMode; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.MainDex; + +import java.util.TimeZone; + +@JNINamespace("base::android") +@MainDex +class TimezoneUtils { + /** + * Guards this class from being instantiated. + */ + + private TimezoneUtils() {} + + /** + * @return the Olson timezone ID of the current system time zone. + */ + @CalledByNative + private static String getDefaultTimeZoneId() { + // On Android N or earlier, getting the default timezone requires the disk + // access when a device set up is skipped. + StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); + String timezoneID = TimeZone.getDefault().getID(); + StrictMode.setThreadPolicy(oldPolicy); + return timezoneID; + } +}
diff --git a/src/base/android/java/src/org/chromium/base/TraceEvent.java b/src/base/android/java/src/org/chromium/base/TraceEvent.java new file mode 100644 index 0000000..b741df5 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/TraceEvent.java
@@ -0,0 +1,387 @@ +// Copyright 2014 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. + +package org.chromium.base; + +import android.os.Looper; +import android.os.MessageQueue; +import android.os.SystemClock; +import android.util.Log; +import android.util.Printer; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.MainDex; +/** + * Java mirror of Chrome trace event API. See base/trace_event/trace_event.h. + * + * To get scoped trace events, use the "try with resource" construct, for instance: + * <pre>{@code + * try (TraceEvent e = TraceEvent.scoped("MyTraceEvent")) { + * // code. + * } + * }</pre> + * + * It is OK to use tracing before the native library has loaded, in a slightly restricted fashion. + * @see EarlyTraceEvent for details. + */ +@JNINamespace("base::android") +@MainDex +public class TraceEvent implements AutoCloseable { + private static volatile boolean sEnabled; + private static volatile boolean sATraceEnabled; // True when taking an Android systrace. + + private static class BasicLooperMonitor implements Printer { + private static final String EARLY_TOPLEVEL_TASK_NAME = "Looper.dispatchMessage: "; + + @Override + public void println(final String line) { + if (line.startsWith(">")) { + beginHandling(line); + } else { + assert line.startsWith("<"); + endHandling(line); + } + } + + void beginHandling(final String line) { + // May return an out-of-date value. this is not an issue as EarlyTraceEvent#begin() + // will filter the event in this case. + boolean earlyTracingActive = EarlyTraceEvent.isActive(); + if (sEnabled || earlyTracingActive) { + String target = getTarget(line); + if (sEnabled) { + nativeBeginToplevel(target); + } else if (earlyTracingActive) { + // Synthesize a task name instead of using a parameter, as early tracing doesn't + // support parameters. + EarlyTraceEvent.begin(EARLY_TOPLEVEL_TASK_NAME + target); + } + } + } + + void endHandling(final String line) { + if (EarlyTraceEvent.isActive()) { + EarlyTraceEvent.end(EARLY_TOPLEVEL_TASK_NAME + getTarget(line)); + } + if (sEnabled) nativeEndToplevel(); + } + + /** + * Android Looper formats |line| as ">>>>> Dispatching to (TARGET) [...]" since at least + * 2009 (Donut). Extracts the TARGET part of the message. + */ + private static String getTarget(String logLine) { + int start = logLine.indexOf('(', 21); // strlen(">>>>> Dispatching to ") + int end = start == -1 ? -1 : logLine.indexOf(')', start); + return end != -1 ? logLine.substring(start + 1, end) : ""; + } + } + + /** + * A class that records, traces and logs statistics about the UI thead's Looper. + * The output of this class can be used in a number of interesting ways: + * <p> + * <ol><li> + * When using chrometrace, there will be a near-continuous line of + * measurements showing both event dispatches as well as idles; + * </li><li> + * Logging messages are output for events that run too long on the + * event dispatcher, making it easy to identify problematic areas; + * </li><li> + * Statistics are output whenever there is an idle after a non-trivial + * amount of activity, allowing information to be gathered about task + * density and execution cadence on the Looper; + * </li></ol> + * <p> + * The class attaches itself as an idle handler to the main Looper, and + * monitors the execution of events and idle notifications. Task counters + * accumulate between idle notifications and get reset when a new idle + * notification is received. + */ + private static final class IdleTracingLooperMonitor extends BasicLooperMonitor + implements MessageQueue.IdleHandler { + // Tags for dumping to logcat or TraceEvent + private static final String TAG = "TraceEvent.LooperMonitor"; + private static final String IDLE_EVENT_NAME = "Looper.queueIdle"; + + // Calculation constants + private static final long FRAME_DURATION_MILLIS = 1000L / 60L; // 60 FPS + // A reasonable threshold for defining a Looper event as "long running" + private static final long MIN_INTERESTING_DURATION_MILLIS = + FRAME_DURATION_MILLIS; + // A reasonable threshold for a "burst" of tasks on the Looper + private static final long MIN_INTERESTING_BURST_DURATION_MILLIS = + MIN_INTERESTING_DURATION_MILLIS * 3; + + // Stats tracking + private long mLastIdleStartedAt; + private long mLastWorkStartedAt; + private int mNumTasksSeen; + private int mNumIdlesSeen; + private int mNumTasksSinceLastIdle; + + // State + private boolean mIdleMonitorAttached; + + // Called from within the begin/end methods only. + // This method can only execute on the looper thread, because that is + // the only thread that is permitted to call Looper.myqueue(). + private final void syncIdleMonitoring() { + if (sEnabled && !mIdleMonitorAttached) { + // approximate start time for computational purposes + mLastIdleStartedAt = SystemClock.elapsedRealtime(); + Looper.myQueue().addIdleHandler(this); + mIdleMonitorAttached = true; + Log.v(TAG, "attached idle handler"); + } else if (mIdleMonitorAttached && !sEnabled) { + Looper.myQueue().removeIdleHandler(this); + mIdleMonitorAttached = false; + Log.v(TAG, "detached idle handler"); + } + } + + @Override + final void beginHandling(final String line) { + // Close-out any prior 'idle' period before starting new task. + if (mNumTasksSinceLastIdle == 0) { + TraceEvent.end(IDLE_EVENT_NAME); + } + mLastWorkStartedAt = SystemClock.elapsedRealtime(); + syncIdleMonitoring(); + super.beginHandling(line); + } + + @Override + final void endHandling(final String line) { + final long elapsed = SystemClock.elapsedRealtime() + - mLastWorkStartedAt; + if (elapsed > MIN_INTERESTING_DURATION_MILLIS) { + traceAndLog(Log.WARN, "observed a task that took " + + elapsed + "ms: " + line); + } + super.endHandling(line); + syncIdleMonitoring(); + mNumTasksSeen++; + mNumTasksSinceLastIdle++; + } + + private static void traceAndLog(int level, String message) { + TraceEvent.instant("TraceEvent.LooperMonitor:IdleStats", message); + Log.println(level, TAG, message); + } + + @Override + public final boolean queueIdle() { + final long now = SystemClock.elapsedRealtime(); + if (mLastIdleStartedAt == 0) mLastIdleStartedAt = now; + final long elapsed = now - mLastIdleStartedAt; + mNumIdlesSeen++; + TraceEvent.begin(IDLE_EVENT_NAME, mNumTasksSinceLastIdle + " tasks since last idle."); + if (elapsed > MIN_INTERESTING_BURST_DURATION_MILLIS) { + // Dump stats + String statsString = mNumTasksSeen + " tasks and " + + mNumIdlesSeen + " idles processed so far, " + + mNumTasksSinceLastIdle + " tasks bursted and " + + elapsed + "ms elapsed since last idle"; + traceAndLog(Log.DEBUG, statsString); + } + mLastIdleStartedAt = now; + mNumTasksSinceLastIdle = 0; + return true; // stay installed + } + } + + // Holder for monitor avoids unnecessary construction on non-debug runs + private static final class LooperMonitorHolder { + private static final BasicLooperMonitor sInstance = + CommandLine.getInstance().hasSwitch(BaseSwitches.ENABLE_IDLE_TRACING) + ? new IdleTracingLooperMonitor() : new BasicLooperMonitor(); + } + + private final String mName; + + /** + * Constructor used to support the "try with resource" construct. + */ + private TraceEvent(String name, String arg) { + mName = name; + begin(name, arg); + } + + @Override + public void close() { + end(mName); + } + + /** + * Factory used to support the "try with resource" construct. + * + * Note that if tracing is not enabled, this will not result in allocating an object. + * + * @param name Trace event name. + * @param arg The arguments of the event. + * @return a TraceEvent, or null if tracing is not enabled. + */ + public static TraceEvent scoped(String name, String arg) { + if (!(EarlyTraceEvent.enabled() || enabled())) return null; + return new TraceEvent(name, arg); + } + + /** + * Similar to {@link #scoped(String, String arg)}, but uses null for |arg|. + */ + public static TraceEvent scoped(String name) { + return scoped(name, null); + } + + /** + * Register an enabled observer, such that java traces are always enabled with native. + */ + public static void registerNativeEnabledObserver() { + nativeRegisterEnabledObserver(); + } + + /** + * Notification from native that tracing is enabled/disabled. + */ + @CalledByNative + public static void setEnabled(boolean enabled) { + if (enabled) EarlyTraceEvent.disable(); + // Only disable logging if Chromium enabled it originally, so as to not disrupt logging done + // by other applications + if (sEnabled != enabled) { + sEnabled = enabled; + // Android M+ systrace logs this on its own. Only log it if not writing to Android + // systrace. + if (sATraceEnabled) return; + ThreadUtils.getUiThreadLooper().setMessageLogging( + enabled ? LooperMonitorHolder.sInstance : null); + } + } + + /** + * May enable early tracing depending on the environment. + * + * Must be called after the command-line has been read. + */ + public static void maybeEnableEarlyTracing() { + EarlyTraceEvent.maybeEnable(); + if (EarlyTraceEvent.isActive()) { + ThreadUtils.getUiThreadLooper().setMessageLogging(LooperMonitorHolder.sInstance); + } + } + + /** + * Enables or disabled Android systrace path of Chrome tracing. If enabled, all Chrome + * traces will be also output to Android systrace. Because of the overhead of Android + * systrace, this is for WebView only. + */ + public static void setATraceEnabled(boolean enabled) { + if (sATraceEnabled == enabled) return; + sATraceEnabled = enabled; + if (enabled) { + // Calls TraceEvent.setEnabled(true) via + // TraceLog::EnabledStateObserver::OnTraceLogEnabled + nativeStartATrace(); + } else { + // Calls TraceEvent.setEnabled(false) via + // TraceLog::EnabledStateObserver::OnTraceLogDisabled + nativeStopATrace(); + } + } + + /** + * @return True if tracing is enabled, false otherwise. + * It is safe to call trace methods without checking if TraceEvent + * is enabled. + */ + public static boolean enabled() { + return sEnabled; + } + + /** + * Triggers the 'instant' native trace event with no arguments. + * @param name The name of the event. + */ + public static void instant(String name) { + if (sEnabled) nativeInstant(name, null); + } + + /** + * Triggers the 'instant' native trace event. + * @param name The name of the event. + * @param arg The arguments of the event. + */ + public static void instant(String name, String arg) { + if (sEnabled) nativeInstant(name, arg); + } + + /** + * Triggers the 'start' native trace event with no arguments. + * @param name The name of the event. + * @param id The id of the asynchronous event. + */ + public static void startAsync(String name, long id) { + EarlyTraceEvent.startAsync(name, id); + if (sEnabled) nativeStartAsync(name, id); + } + + /** + * Triggers the 'finish' native trace event with no arguments. + * @param name The name of the event. + * @param id The id of the asynchronous event. + */ + public static void finishAsync(String name, long id) { + EarlyTraceEvent.finishAsync(name, id); + if (sEnabled) nativeFinishAsync(name, id); + } + + /** + * Triggers the 'begin' native trace event with no arguments. + * @param name The name of the event. + */ + public static void begin(String name) { + begin(name, null); + } + + /** + * Triggers the 'begin' native trace event. + * @param name The name of the event. + * @param arg The arguments of the event. + */ + public static void begin(String name, String arg) { + EarlyTraceEvent.begin(name); + if (sEnabled) nativeBegin(name, arg); + } + + /** + * Triggers the 'end' native trace event with no arguments. + * @param name The name of the event. + */ + public static void end(String name) { + end(name, null); + } + + /** + * Triggers the 'end' native trace event. + * @param name The name of the event. + * @param arg The arguments of the event. + */ + public static void end(String name, String arg) { + EarlyTraceEvent.end(name); + if (sEnabled) nativeEnd(name, arg); + } + + private static native void nativeRegisterEnabledObserver(); + private static native void nativeStartATrace(); + private static native void nativeStopATrace(); + private static native void nativeInstant(String name, String arg); + private static native void nativeBegin(String name, String arg); + private static native void nativeEnd(String name, String arg); + private static native void nativeBeginToplevel(String target); + private static native void nativeEndToplevel(); + private static native void nativeStartAsync(String name, long id); + private static native void nativeFinishAsync(String name, long id); +}
diff --git a/src/base/android/java/src/org/chromium/base/UnguessableToken.java b/src/base/android/java/src/org/chromium/base/UnguessableToken.java new file mode 100644 index 0000000..4b1619d --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/UnguessableToken.java
@@ -0,0 +1,91 @@ +// Copyright 2016 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. + +package org.chromium.base; + +import android.os.Parcel; +import android.os.Parcelable; + +import org.chromium.base.annotations.CalledByNative; + +/** + * This class mirrors unguessable_token.h . Since tokens are passed by value, + * we don't bother to maintain a native token. This implements Parcelable so + * that it may be sent via binder. + * + * To get one of these from native, one must start with a + * base::UnguessableToken, then create a Java object from it. See + * jni_unguessable_token.h for information. + */ +public class UnguessableToken implements Parcelable { + private final long mHigh; + private final long mLow; + + private UnguessableToken(long high, long low) { + mHigh = high; + mLow = low; + } + + @CalledByNative + private static UnguessableToken create(long high, long low) { + return new UnguessableToken(high, low); + } + + @CalledByNative + public long getHighForSerialization() { + return mHigh; + } + + @CalledByNative + public long getLowForSerialization() { + return mLow; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(mHigh); + dest.writeLong(mLow); + } + + public static final Parcelable.Creator<UnguessableToken> CREATOR = + new Parcelable.Creator<UnguessableToken>() { + @Override + public UnguessableToken createFromParcel(Parcel source) { + long high = source.readLong(); + long low = source.readLong(); + if (high == 0 || low == 0) { + // Refuse to create an empty UnguessableToken. + return null; + } + return new UnguessableToken(high, low); + } + + @Override + public UnguessableToken[] newArray(int size) { + return new UnguessableToken[size]; + } + }; + + // To avoid unwieldy calls in JNI for tests, parcel and unparcel. + // TODO(liberato): It would be nice if we could include this only with a + // java driver that's linked only with unit tests, but i don't see a way + // to do that. + @CalledByNative + private UnguessableToken parcelAndUnparcelForTesting() { + Parcel parcel = Parcel.obtain(); + writeToParcel(parcel, 0); + + // Rewind the parcel and un-parcel. + parcel.setDataPosition(0); + UnguessableToken token = CREATOR.createFromParcel(parcel); + parcel.recycle(); + + return token; + } +};
diff --git a/src/base/android/java/src/org/chromium/base/UserData.java b/src/base/android/java/src/org/chromium/base/UserData.java new file mode 100644 index 0000000..bee85a3 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/UserData.java
@@ -0,0 +1,20 @@ +// Copyright 2018 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. + +package org.chromium.base; + +/** + * Interface to be implemented by the classes make themselves attacheable to + * a host class that holds {@link UserDataHost}. + */ +public interface UserData { + /** + * Called when {@link UserData} object needs to be destroyed. + * WARNING: This method is not guaranteed to be called. Each host class should + * call {@link UserDataHost#destroy()} explicitly at the end of its + * lifetime to have all of its {@link UserData#destroy()} get invoked. + */ + default void + destroy() {} +}
diff --git a/src/base/android/java/src/org/chromium/base/UserDataHost.java b/src/base/android/java/src/org/chromium/base/UserDataHost.java new file mode 100644 index 0000000..a8eb046 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/UserDataHost.java
@@ -0,0 +1,121 @@ +// Copyright 2018 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. + +package org.chromium.base; + +import android.os.Process; + +import java.util.HashMap; + +/** + * A class that implements type-safe heterogeneous container. It can associate + * an object of type T with a type token (T.class) as a key. Mismatch of the + * type between them can be checked at compile time, hence type-safe. Objects + * are held using strong reference in the container. {@code null} is not allowed + * for key or object. + * <p> + * Can be used for an object that needs to have other objects attached to it + * without having to manage explicit references to them. Attached objects need + * to implement {@link UserData} so that they can be destroyed by {@link #destroy()}. + * <p> + * No operation takes effect once {@link #destroy()} is called. + * <p> + * Usage: + + * + * <code> + * public class Foo { + * // Defines the container. + * private final UserDataHost mUserDataHost = new UserDataHost(); + * + * public UserDataHost getUserDataHost() { + * return mUserDataHost; + * } + * } + * + * public class FooBar implements UserData { + * + * public FooBar from(UserDataHost host) { + * FooBar foobar = host.getUserData(FooBar.class); + * // Instantiate FooBar upon the first access. + * return foobar != null ? foobar : host.setUserData(FooBar.class, new FooBar()); + * } + * } + * + * Foo foo = new Foo(); + * ... + * + * FooBar bar = FooBar.from(foo.getUserDataHost()); + * + * ... + * + * </code> + */ +public final class UserDataHost { + private final long mThreadId = Process.myTid(); + + private HashMap<Class<? extends UserData>, UserData> mUserDataMap = new HashMap<>(); + + private void checkThreadAndState() { + assert mThreadId == Process.myTid() : "UserData must only be used on a single thread."; + assert mUserDataMap != null : "Operation is not allowed after destroy()"; + } + + /** + * Associates the specified object with the specified key. + * @param key Type token with which the specified object is to be associated. + * @param object Object to be associated with the specified key. + * @return the object just stored, or {@code null} if storing the object failed. + */ + public <T extends UserData> T setUserData(Class<T> key, T object) { + checkThreadAndState(); + assert key != null && object != null : "Neither key nor object of UserDataHost can be null"; + + mUserDataMap.put(key, object); + return getUserData(key); + } + + /** + * Returns the value to which the specified key is mapped, or null if this map + * contains no mapping for the key. + * @param key Type token for which the specified object is to be returned. + * @return the value to which the specified key is mapped, or null if this map + * contains no mapping for {@code key}. + */ + public <T extends UserData> T getUserData(Class<T> key) { + checkThreadAndState(); + assert key != null : "UserDataHost key cannot be null"; + + return key.cast(mUserDataMap.get(key)); + } + + /** + * Removes the mapping for a key from this map. Assertion will be thrown if + * the given key has no mapping. + * @param key Type token for which the specified object is to be removed. + * @return The previous value associated with {@code key}. + */ + public <T extends UserData> T removeUserData(Class<T> key) { + checkThreadAndState(); + assert key != null : "UserDataHost key cannot be null"; + + assert mUserDataMap.containsKey(key) : "UserData for the key is not present"; + return key.cast(mUserDataMap.remove(key)); + } + + /** + * Destroy all the managed {@link UserData} instances. This should be invoked at + * the end of the lifetime of the host that user data instances hang on to. + * The host stops managing them after this method is called. + */ + public void destroy() { + checkThreadAndState(); + + // Nulls out |mUserDataMap| first in order to prevent concurrent modification that + // might happen in the for loop below. + HashMap<Class<? extends UserData>, UserData> map = mUserDataMap; + mUserDataMap = null; + for (UserData userData : map.values()) userData.destroy(); + } +}
diff --git a/src/base/android/java/src/org/chromium/base/VisibleForTesting.java b/src/base/android/java/src/org/chromium/base/VisibleForTesting.java new file mode 100644 index 0000000..24cbfad --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/VisibleForTesting.java
@@ -0,0 +1,12 @@ +// Copyright 2014 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. + +package org.chromium.base; + +/** + * Annotation used to mark code that has wider visibility or present for testing code. + */ +public @interface VisibleForTesting { + +}
diff --git a/src/base/android/java/src/org/chromium/base/annotations/AccessedByNative.java b/src/base/android/java/src/org/chromium/base/annotations/AccessedByNative.java new file mode 100644 index 0000000..6df7c11 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/annotations/AccessedByNative.java
@@ -0,0 +1,20 @@ +// Copyright 2012 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. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @AccessedByNative is used to ensure proguard will keep this field, since it's + * only accessed by native. + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.CLASS) +public @interface AccessedByNative { + public String value() default ""; +}
diff --git a/src/base/android/java/src/org/chromium/base/annotations/CalledByNative.java b/src/base/android/java/src/org/chromium/base/annotations/CalledByNative.java new file mode 100644 index 0000000..52f5b7e --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/annotations/CalledByNative.java
@@ -0,0 +1,23 @@ +// Copyright 2012 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. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @CalledByNative is used by the JNI generator to create the necessary JNI + * bindings and expose this method to native code. + */ +@Target({ElementType.CONSTRUCTOR, ElementType.METHOD}) +@Retention(RetentionPolicy.CLASS) +public @interface CalledByNative { + /* + * If present, tells which inner class the method belongs to. + */ + public String value() default ""; +}
diff --git a/src/base/android/java/src/org/chromium/base/annotations/CalledByNativeUnchecked.java b/src/base/android/java/src/org/chromium/base/annotations/CalledByNativeUnchecked.java new file mode 100644 index 0000000..c0abcbe --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/annotations/CalledByNativeUnchecked.java
@@ -0,0 +1,27 @@ +// Copyright 2012 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. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @CalledByNativeUnchecked is used to generate JNI bindings that do not check for exceptions. + * It only makes sense to use this annotation on methods that declare a throws... spec. + * However, note that the exception received native side maybe an 'unchecked' (RuntimeExpception) + * such as NullPointerException, so the native code should differentiate these cases. + * Usage of this should be very rare; where possible handle exceptions in the Java side and use a + * return value to indicate success / failure. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface CalledByNativeUnchecked { + /* + * If present, tells which inner class the method belongs to. + */ + public String value() default ""; +}
diff --git a/src/base/android/java/src/org/chromium/base/annotations/DoNotInline.java b/src/base/android/java/src/org/chromium/base/annotations/DoNotInline.java new file mode 100644 index 0000000..9252f3a --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/annotations/DoNotInline.java
@@ -0,0 +1,20 @@ +// Copyright 2018 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. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The annotated method or class should never be inlined. + * + * The annotated method (or methods on the annotated class) are guaranteed not to be inlined by + * Proguard. Other optimizations may still apply. + */ +@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.CLASS) +public @interface DoNotInline {}
diff --git a/src/base/android/java/src/org/chromium/base/annotations/JNIAdditionalImport.java b/src/base/android/java/src/org/chromium/base/annotations/JNIAdditionalImport.java new file mode 100644 index 0000000..f1bf85e --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/annotations/JNIAdditionalImport.java
@@ -0,0 +1,35 @@ +// Copyright 2014 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. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * JNIAdditionalImport is used by the JNI generator to qualify inner types used on JNI methods. Must + * be used when an inner class is used from a class within the same package. Example: + * + * <pre> + * @JNIAdditionImport(Foo.class) + * public class Bar { + * @CalledByNative static void doSomethingWithInner(Foo.Inner inner) { + * ... + * } + * } + * <pre> + * <p> + * Notes: + * 1) Foo must be in the same package as Bar + * 2) For classes in different packages, they should be imported as: + * import other.package.Foo; + * and this annotation should not be used. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.CLASS) +public @interface JNIAdditionalImport { + Class<?>[] value(); +}
diff --git a/src/base/android/java/src/org/chromium/base/annotations/JNINamespace.java b/src/base/android/java/src/org/chromium/base/annotations/JNINamespace.java new file mode 100644 index 0000000..4cd5531 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/annotations/JNINamespace.java
@@ -0,0 +1,20 @@ +// Copyright 2012 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. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @JNINamespace is used by the JNI generator to create the necessary JNI + * bindings and expose this method to native code using the specified namespace. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface JNINamespace { + public String value(); +}
diff --git a/src/base/android/java/src/org/chromium/base/annotations/JniStaticNatives.java b/src/base/android/java/src/org/chromium/base/annotations/JniStaticNatives.java new file mode 100644 index 0000000..b387854 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/annotations/JniStaticNatives.java
@@ -0,0 +1,13 @@ +// Copyright 2018 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. +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.SOURCE) +public @interface JniStaticNatives {}
diff --git a/src/base/android/java/src/org/chromium/base/annotations/MainDex.java b/src/base/android/java/src/org/chromium/base/annotations/MainDex.java new file mode 100644 index 0000000..56aab74 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/annotations/MainDex.java
@@ -0,0 +1,20 @@ +// Copyright 2015 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. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation that signals that a class should be kept in the main dex file. + * + * This generally means it's used by renderer processes, which can't load secondary dexes + * on K and below. + */ +@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.CLASS) +public @interface MainDex {}
diff --git a/src/base/android/java/src/org/chromium/base/annotations/NativeCall.java b/src/base/android/java/src/org/chromium/base/annotations/NativeCall.java new file mode 100644 index 0000000..b69cd17 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/annotations/NativeCall.java
@@ -0,0 +1,24 @@ +// Copyright 2014 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. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @NativeCall is used by the JNI generator to create the necessary JNI bindings + * so a native function can be bound to a Java inner class. The native class for + * which the JNI method will be generated is specified by the first parameter. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface NativeCall { + /* + * Value determines which native class the method should map to. + */ + public String value() default ""; +}
diff --git a/src/base/android/java/src/org/chromium/base/annotations/NativeClassQualifiedName.java b/src/base/android/java/src/org/chromium/base/annotations/NativeClassQualifiedName.java new file mode 100644 index 0000000..afbc368 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/annotations/NativeClassQualifiedName.java
@@ -0,0 +1,25 @@ +// Copyright 2012 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. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @NativeClassQualifiedName is used by the JNI generator to create the necessary JNI + * bindings to call into the specified native class name. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface NativeClassQualifiedName { + /* + * Tells which native class the method is going to be bound to. + * The first parameter of the annotated method must be an int nativePtr pointing to + * an instance of this class. + */ + public String value(); +}
diff --git a/src/base/android/java/src/org/chromium/base/annotations/RemovableInRelease.java b/src/base/android/java/src/org/chromium/base/annotations/RemovableInRelease.java new file mode 100644 index 0000000..2191334 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/annotations/RemovableInRelease.java
@@ -0,0 +1,18 @@ +// Copyright 2015 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. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/** + * The annotated function can be removed in release builds. + * + * Calls to this function will be removed if its return value is not used. If all calls are removed, + * the function definition itself will be candidate for removal. + * It works by indicating to Proguard that the function has no side effects. + */ +@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) +public @interface RemovableInRelease {}
diff --git a/src/base/android/java/src/org/chromium/base/annotations/UsedByReflection.java b/src/base/android/java/src/org/chromium/base/annotations/UsedByReflection.java new file mode 100644 index 0000000..a2af704 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/annotations/UsedByReflection.java
@@ -0,0 +1,24 @@ +// Copyright 2014 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. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/** + * Annotation used for marking methods and fields that are called by reflection. + * Useful for keeping components that would otherwise be removed by Proguard. + * Use the value parameter to mention a file that calls this method. + * + * Note that adding this annotation to a method is not enough to guarantee that + * it is kept - either its class must be referenced elsewhere in the program, or + * the class must be annotated with this as well. + */ +@Target({ + ElementType.METHOD, ElementType.FIELD, ElementType.TYPE, + ElementType.CONSTRUCTOR }) +public @interface UsedByReflection { + String value(); +}
diff --git a/src/base/android/java/src/org/chromium/base/compat/ApiHelperForM.java b/src/base/android/java/src/org/chromium/base/compat/ApiHelperForM.java new file mode 100644 index 0000000..1fcf9ad --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/compat/ApiHelperForM.java
@@ -0,0 +1,108 @@ +// Copyright 2018 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. + +package org.chromium.base.compat; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkInfo; +import android.os.Build; +import android.os.Process; +import android.view.ActionMode; +import android.view.ViewConfiguration; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import org.chromium.base.annotations.DoNotInline; + +/** + * Utility class to use new APIs that were added in M (API level 23). These need to exist in a + * separate class so that Android framework can successfully verify classes without + * encountering the new APIs. + */ +@DoNotInline +@TargetApi(Build.VERSION_CODES.M) +public final class ApiHelperForM { + private ApiHelperForM() {} + + /** + * See {@link WebViewClient#onPageCommitVisible(WebView, String)}, which was added in M. + */ + public static void onPageCommitVisible( + WebViewClient webViewClient, WebView webView, String url) { + webViewClient.onPageCommitVisible(webView, url); + } + + /** + * See {@link Process#is64Bit()}. + */ + public static boolean isProcess64Bit() { + return Process.is64Bit(); + } + + /** See {@link ConnectivityManager#getBoundNetworkForProcess() } */ + public static Network getBoundNetworkForProcess(ConnectivityManager connectivityManager) { + return connectivityManager.getBoundNetworkForProcess(); + } + + /** See {@link Network#getNetworkHandle() } */ + public static long getNetworkHandle(Network network) { + return network.getNetworkHandle(); + } + + /** See @{link ConnectivityManager#getActiveNetwork() } */ + public static Network getActiveNetwork(ConnectivityManager connectivityManager) { + return connectivityManager.getActiveNetwork(); + } + + /** See @{link ConnectivityManager#getNetworkInfo(Network) } */ + public static NetworkInfo getNetworkInfo( + ConnectivityManager connectivityManager, Network network) { + return connectivityManager.getNetworkInfo(network); + } + + /** See {@link Activity#requestPermissions(String[], int)}. */ + public static void requestActivityPermissions( + Activity activity, String[] permissions, int requestCode) { + activity.requestPermissions(permissions, requestCode); + } + + /** See {@link Activity#shouldShowRequestPermissionRationale(String)}. */ + public static boolean shouldShowRequestPermissionRationale( + Activity activity, String permission) { + return activity.shouldShowRequestPermissionRationale(permission); + } + + /** See {@link PackageManager#isPermissionRevokedByPolicy(String, String)}. */ + public static boolean isPermissionRevokedByPolicy(Activity activity, String permission) { + return activity.getPackageManager().isPermissionRevokedByPolicy( + permission, activity.getPackageName()); + } + + /* + * See {@link ActionMode#invalidateContentRect()}. + * @param actionMode + */ + public static void invalidateContentRectOnActionMode(ActionMode actionMode) { + actionMode.invalidateContentRect(); + } + + public static void onWindowFocusChangedOnActionMode(ActionMode actionMode, boolean gainFocus) { + actionMode.onWindowFocusChanged(gainFocus); + } + + public static int getActionModeType(ActionMode actionMode) { + return actionMode.getType(); + } + + public static long getDefaultActionModeHideDuration() { + return ViewConfiguration.getDefaultActionModeHideDuration(); + } + + public static void hideActionMode(ActionMode actionMode, long duration) { + actionMode.hide(duration); + } +}
diff --git a/src/base/android/java/src/org/chromium/base/compat/ApiHelperForN.java b/src/base/android/java/src/org/chromium/base/compat/ApiHelperForN.java new file mode 100644 index 0000000..7448286 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/compat/ApiHelperForN.java
@@ -0,0 +1,67 @@ +// Copyright 2018 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. + +package org.chromium.base.compat; + +import android.annotation.TargetApi; +import android.app.job.JobInfo; +import android.app.job.JobScheduler; +import android.content.ClipData; +import android.graphics.Bitmap; +import android.media.MediaCodec.CryptoInfo; +import android.os.Build; +import android.view.PointerIcon; +import android.view.View; +import android.view.View.DragShadowBuilder; +import android.webkit.WebResourceRequest; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import org.chromium.base.annotations.DoNotInline; + +/** + * Utility class to use new APIs that were added in N (API level 24). These need to exist in a + * separate class so that Android framework can successfully verify classes without + * encountering the new APIs. + */ +@DoNotInline +@TargetApi(Build.VERSION_CODES.N) +public final class ApiHelperForN { + private ApiHelperForN() {} + + /** + * See {@link WebViewClient#shouldOverrideUrlLoading(WebView, WebResourceRequest)}, which was + * added in N. + */ + public static boolean shouldOverrideUrlLoading( + WebViewClient webViewClient, WebView webView, WebResourceRequest request) { + return webViewClient.shouldOverrideUrlLoading(webView, request); + } + + /** See {@link JobScheduler#getPendingJob(int)}. */ + public static JobInfo getPendingJob(JobScheduler scheduler, int jobId) { + return scheduler.getPendingJob(jobId); + } + + /** See {@link View#startDragAndDrop(ClipData, DragShadowBuilder, Object, int)}. */ + public static boolean startDragAndDrop(View view, ClipData data, + DragShadowBuilder shadowBuilder, Object myLocalState, int flags) { + return view.startDragAndDrop(data, shadowBuilder, myLocalState, flags); + } + + /** See {@link View#setPointerIcon(PointerIcon)}. */ + public static void setPointerIcon(View view, PointerIcon icon) { + view.setPointerIcon(icon); + } + + /** See {@link PointerIcon#create(Bitmap, float, float)}. */ + public static PointerIcon createPointerIcon(Bitmap bitmap, float width, float height) { + return PointerIcon.create(bitmap, width, height); + } + + /** See {@link CryptoInfo#setPattern(Pattern)}. */ + public static void setCryptoInfoPattern(CryptoInfo cryptoInfo, int encrypt, int skip) { + cryptoInfo.setPattern(new CryptoInfo.Pattern(encrypt, skip)); + } +}
diff --git a/src/base/android/java/src/org/chromium/base/compat/ApiHelperForO.java b/src/base/android/java/src/org/chromium/base/compat/ApiHelperForO.java new file mode 100644 index 0000000..58a71e1 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/compat/ApiHelperForO.java
@@ -0,0 +1,33 @@ +// Copyright 2018 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. + +package org.chromium.base.compat; + +import android.annotation.TargetApi; +import android.content.res.Configuration; +import android.os.Build; +import android.view.Display; + +import org.chromium.base.annotations.DoNotInline; + +/** + * Utility class to use new APIs that were added in O (API level 26). These need to exist in a + * separate class so that Android framework can successfully verify classes without + * encountering the new APIs. + */ +@DoNotInline +@TargetApi(Build.VERSION_CODES.O) +public final class ApiHelperForO { + private ApiHelperForO() {} + + /** See {@link Display#isWideColorGamut() }. */ + public static boolean isWideColorGamut(Display display) { + return display.isWideColorGamut(); + } + + /** See {@link Configuration#isScreenWideColorGamut() }. */ + public static boolean isScreenWideColorGamut(Configuration configuration) { + return configuration.isScreenWideColorGamut(); + } +}
diff --git a/src/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java b/src/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java new file mode 100644 index 0000000..30f341e --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java
@@ -0,0 +1,836 @@ +// Copyright 2014 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. + +package org.chromium.base.library_loader; + +import static org.chromium.base.metrics.CachedMetrics.EnumeratedHistogramSample; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Build; +import android.os.Build.VERSION_CODES; +import android.os.StrictMode; +import android.os.SystemClock; +import android.support.annotation.NonNull; +import android.support.v4.content.ContextCompat; +import android.system.Os; + +import org.chromium.base.BuildConfig; +import org.chromium.base.BuildInfo; +import org.chromium.base.CommandLine; +import org.chromium.base.ContextUtils; +import org.chromium.base.FileUtils; +import org.chromium.base.Log; +import org.chromium.base.StreamUtil; +import org.chromium.base.SysUtils; +import org.chromium.base.TraceEvent; +import org.chromium.base.VisibleForTesting; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.MainDex; +import org.chromium.base.compat.ApiHelperForM; +import org.chromium.base.metrics.RecordHistogram; +import org.chromium.base.task.AsyncTask; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import javax.annotation.Nullable; + +/** + * This class provides functionality to load and register the native libraries. + * Callers are allowed to separate loading the libraries from initializing them. + * This may be an advantage for Android Webview, where the libraries can be loaded + * by the zygote process, but then needs per process initialization after the + * application processes are forked from the zygote process. + * + * The libraries may be loaded and initialized from any thread. Synchronization + * primitives are used to ensure that overlapping requests from different + * threads are handled sequentially. + * + * See also base/android/library_loader/library_loader_hooks.cc, which contains + * the native counterpart to this class. + */ +@MainDex +@JNINamespace("base::android") +public class LibraryLoader { + private static final String TAG = "LibraryLoader"; + + // Set to true to enable debug logs. + private static final boolean DEBUG = false; + + // Experience shows that on some devices, the PackageManager fails to properly extract + // native shared libraries to the /data partition at installation or upgrade time, + // which creates all kind of chaos (https://crbug.com/806998). + // + // We implement a fallback when we detect the issue by manually extracting the library + // into Chromium's own data directory, then retrying to load the new library from here. + // + // This will work for any device running K-. Starting with Android L, render processes + // cannot access the file system anymore, and extraction will always fail for them. + // However, the issue doesn't seem to appear in the field for Android L. + // + // Also, starting with M, the issue doesn't exist if shared libraries are stored + // uncompressed in the APK (as Chromium does), because the system linker can access them + // directly, and the PackageManager will thus never extract them in the first place. + static public final boolean PLATFORM_REQUIRES_NATIVE_FALLBACK_EXTRACTION = + Build.VERSION.SDK_INT <= VERSION_CODES.KITKAT; + + // Location of extracted native libraries. + private static final String LIBRARY_DIR = "native_libraries"; + + // SharedPreferences key for "don't prefetch libraries" flag + private static final String DONT_PREFETCH_LIBRARIES_KEY = "dont_prefetch_libraries"; + + private static final EnumeratedHistogramSample sRelinkerCountHistogram = + new EnumeratedHistogramSample("ChromiumAndroidLinker.RelinkerFallbackCount", 2); + + // The singleton instance of LibraryLoader. Never null (not final for tests). + private static LibraryLoader sInstance = new LibraryLoader(); + + // One-way switch becomes true when the libraries are initialized ( + // by calling nativeLibraryLoaded, which forwards to LibraryLoaded(...) in + // library_loader_hooks.cc). + // Note that this member should remain a one-way switch, since it accessed from multiple + // threads without a lock. + private volatile boolean mInitialized; + + // One-way switch that becomes true once + // {@link asyncPrefetchLibrariesToMemory} has been called. + private final AtomicBoolean mPrefetchLibraryHasBeenCalled = new AtomicBoolean(); + + // Guards all fields below. + private final Object mLock = new Object(); + + private NativeLibraryPreloader mLibraryPreloader; + private boolean mLibraryPreloaderCalled; + + // One-way switch becomes true when the libraries are loaded. + private boolean mLoaded; + + // One-way switch becomes true when the Java command line is switched to + // native. + private boolean mCommandLineSwitched; + + // One-way switches recording attempts to use Relro sharing in the browser. + // The flags are used to report UMA stats later. + private boolean mIsUsingBrowserSharedRelros; + private boolean mLoadAtFixedAddressFailed; + + // One-way switch becomes true if the Chromium library was loaded from the + // APK file directly. + private boolean mLibraryWasLoadedFromApk; + + // The type of process the shared library is loaded in. + private @LibraryProcessType int mLibraryProcessType; + + // The number of milliseconds it took to load all the native libraries, which + // will be reported via UMA. Set once when the libraries are done loading. + private long mLibraryLoadTimeMs; + + // The return value of NativeLibraryPreloader.loadLibrary(), which will be reported + // via UMA, it is initialized to the invalid value which shouldn't showup in UMA + // report. + private int mLibraryPreloaderStatus = -1; + + /** + * Call this method to determine if this chromium project must + * use this linker. If not, System.loadLibrary() should be used to load + * libraries instead. + */ + public static boolean useCrazyLinker() { + // TODO(digit): Remove this early return GVR is loadable. + // A non-monochrome APK (such as ChromePublic.apk or ChromeModernPublic.apk) on N+ cannot + // use the Linker because the latter is incompatible with the GVR library. Fall back + // to using System.loadLibrary() or System.load() at the cost of no RELRO sharing. + // + // A non-monochrome APK (such as ChromePublic.apk) can be installed on N+ in these + // circumstances: + // * installing APK manually + // * after OTA from M to N + // * side-installing Chrome (possibly from another release channel) + // * Play Store bugs leading to incorrect APK flavor being installed + // + if (Build.VERSION.SDK_INT >= VERSION_CODES.N) return false; + + // The auto-generated NativeLibraries.sUseLinker variable will be true if the + // build has not explicitly disabled Linker features. + return NativeLibraries.sUseLinker; + } + + /** + * Call this method to determine if the chromium project must load the library + * directly from a zip file. + */ + private static boolean isInZipFile() { + // The auto-generated NativeLibraries.sUseLibraryInZipFile variable will be true + // iff the library remains embedded in the APK zip file on the target. + return NativeLibraries.sUseLibraryInZipFile; + } + + /** + * Set native library preloader, if set, the NativeLibraryPreloader.loadLibrary will be invoked + * before calling System.loadLibrary, this only applies when not using the chromium linker. + * + * @param loader the NativeLibraryPreloader, it shall only be set once and before the + * native library loaded. + */ + public void setNativeLibraryPreloader(NativeLibraryPreloader loader) { + synchronized (mLock) { + assert mLibraryPreloader == null && !mLoaded; + mLibraryPreloader = loader; + } + } + + public static LibraryLoader getInstance() { + return sInstance; + } + + private LibraryLoader() {} + + /** + * This method blocks until the library is fully loaded and initialized. + * + * @param processType the process the shared library is loaded in. + */ + public void ensureInitialized(@LibraryProcessType int processType) throws ProcessInitException { + synchronized (mLock) { + if (mInitialized) { + // Already initialized, nothing to do. + return; + } + loadAlreadyLocked(ContextUtils.getApplicationContext()); + initializeAlreadyLocked(processType); + } + } + + /** + * Calls native library preloader (see {@link #setNativeLibraryPreloader}) with the app + * context. If there is no preloader set, this function does nothing. + * Preloader is called only once, so calling it explicitly via this method means + * that it won't be (implicitly) called during library loading. + */ + public void preloadNow() { + preloadNowOverrideApplicationContext(ContextUtils.getApplicationContext()); + } + + /** + * Similar to {@link #preloadNow}, but allows specifying app context to use. + */ + public void preloadNowOverrideApplicationContext(Context appContext) { + synchronized (mLock) { + if (!useCrazyLinker()) { + preloadAlreadyLocked(appContext); + } + } + } + + private void preloadAlreadyLocked(Context appContext) { + try (TraceEvent te = TraceEvent.scoped("LibraryLoader.preloadAlreadyLocked")) { + // Preloader uses system linker, we shouldn't preload if Chromium linker is used. + assert !useCrazyLinker(); + if (mLibraryPreloader != null && !mLibraryPreloaderCalled) { + mLibraryPreloaderStatus = mLibraryPreloader.loadLibrary(appContext); + mLibraryPreloaderCalled = true; + } + } + } + + /** + * Checks if library is fully loaded and initialized. + */ + public boolean isInitialized() { + return mInitialized; + } + + /** + * Loads the library and blocks until the load completes. The caller is responsible + * for subsequently calling ensureInitialized(). + * May be called on any thread, but should only be called once. Note the thread + * this is called on will be the thread that runs the native code's static initializers. + * See the comment in doInBackground() for more considerations on this. + * + * @throws ProcessInitException if the native library failed to load. + */ + public void loadNow() throws ProcessInitException { + loadNowOverrideApplicationContext(ContextUtils.getApplicationContext()); + } + + /** + * Override kept for callers that need to load from a different app context. Do not use unless + * specifically required to load from another context that is not the current process's app + * context. + * + * @param appContext The overriding app context to be used to load libraries. + * @throws ProcessInitException if the native library failed to load with this context. + */ + public void loadNowOverrideApplicationContext(Context appContext) throws ProcessInitException { + synchronized (mLock) { + if (mLoaded && appContext != ContextUtils.getApplicationContext()) { + throw new IllegalStateException("Attempt to load again from alternate context."); + } + loadAlreadyLocked(appContext); + } + } + + /** + * Initializes the library here and now: must be called on the thread that the + * native will call its "main" thread. The library must have previously been + * loaded with loadNow. + * + * @param processType the process the shared library is loaded in. + */ + public void initialize(@LibraryProcessType int processType) throws ProcessInitException { + synchronized (mLock) { + initializeAlreadyLocked(processType); + } + } + + /** + * Disables prefetching for subsequent runs. The value comes from "DontPrefetchLibraries" + * finch experiment, and is pushed on every run. I.e. the effect of the finch experiment + * lags by one run, which is the best we can do considering that prefetching happens way + * before finch is initialized. Note that since LibraryLoader is in //base, it can't depend + * on ChromeFeatureList, and has to rely on external code pushing the value. + * + * @param dontPrefetch whether not to prefetch libraries + */ + public static void setDontPrefetchLibrariesOnNextRuns(boolean dontPrefetch) { + ContextUtils.getAppSharedPreferences() + .edit() + .putBoolean(DONT_PREFETCH_LIBRARIES_KEY, dontPrefetch) + .apply(); + } + + /** + * @return whether not to prefetch libraries (see setDontPrefetchLibrariesOnNextRun()). + */ + private static boolean isNotPrefetchingLibraries() { + // This might be the first time getAppSharedPreferences() is used, so relax strict mode + // to allow disk reads. + StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); + try { + return ContextUtils.getAppSharedPreferences().getBoolean( + DONT_PREFETCH_LIBRARIES_KEY, false); + } finally { + StrictMode.setThreadPolicy(oldPolicy); + } + } + + /** Prefetches the native libraries in a background thread. + * + * Launches an AsyncTask that, through a short-lived forked process, reads a + * part of each page of the native library. This is done to warm up the + * page cache, turning hard page faults into soft ones. + * + * This is done this way, as testing shows that fadvise(FADV_WILLNEED) is + * detrimental to the startup time. + */ + public void asyncPrefetchLibrariesToMemory() { + SysUtils.logPageFaultCountToTracing(); + if (isNotPrefetchingLibraries()) return; + + final boolean coldStart = mPrefetchLibraryHasBeenCalled.compareAndSet(false, true); + + // Collection should start close to the native library load, but doesn't have + // to be simultaneous with it. Also, don't prefetch in this case, as this would + // skew the results. + if (coldStart && CommandLine.getInstance().hasSwitch("log-native-library-residency")) { + // nativePeriodicallyCollectResidency() sleeps, run it on another thread, + // and not on the AsyncTask thread pool. + new Thread(LibraryLoader::nativePeriodicallyCollectResidency).start(); + return; + } + + new LibraryPrefetchTask(coldStart).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private static class LibraryPrefetchTask extends AsyncTask<Void> { + private final boolean mColdStart; + + public LibraryPrefetchTask(boolean coldStart) { + mColdStart = coldStart; + } + + @Override + protected Void doInBackground() { + int percentage = nativePercentageOfResidentNativeLibraryCode(); + try (TraceEvent e = TraceEvent.scoped("LibraryLoader.asyncPrefetchLibrariesToMemory", + Integer.toString(percentage))) { + // Arbitrary percentage threshold. If most of the native library is already + // resident (likely with monochrome), don't bother creating a prefetch process. + boolean prefetch = mColdStart && percentage < 90; + if (prefetch) { + nativeForkAndPrefetchNativeLibrary(); + } + if (percentage != -1) { + String histogram = "LibraryLoader.PercentageOfResidentCodeBeforePrefetch" + + (mColdStart ? ".ColdStartup" : ".WarmStartup"); + RecordHistogram.recordPercentageHistogram(histogram, percentage); + } + } + return null; + } + } + + // Helper for loadAlreadyLocked(). Load a native shared library with the Chromium linker. + // Sets UMA flags depending on the results of loading. + private void loadLibraryWithCustomLinkerAlreadyLocked( + Linker linker, @Nullable String zipFilePath, String libFilePath) { + assert Thread.holdsLock(mLock); + if (linker.isUsingBrowserSharedRelros()) { + // If the browser is set to attempt shared RELROs then we try first with shared + // RELROs enabled, and if that fails then retry without. + mIsUsingBrowserSharedRelros = true; + try { + linker.loadLibrary(libFilePath); + } catch (UnsatisfiedLinkError e) { + Log.w(TAG, "Failed to load native library with shared RELRO, retrying without"); + mLoadAtFixedAddressFailed = true; + linker.loadLibraryNoFixedAddress(libFilePath); + } + } else { + // No attempt to use shared RELROs in the browser, so load as normal. + linker.loadLibrary(libFilePath); + } + + // Loaded successfully, so record if we loaded directly from an APK. + if (zipFilePath != null) { + mLibraryWasLoadedFromApk = true; + } + } + + static void incrementRelinkerCountHitHistogram() { + sRelinkerCountHistogram.record(1); + } + + static void incrementRelinkerCountNotHitHistogram() { + sRelinkerCountHistogram.record(0); + } + + // Experience shows that on some devices, the system sometimes fails to extract native libraries + // at installation or update time from the APK. This function will extract the library and + // return the extracted file path. + static String getExtractedLibraryPath(Context appContext, String libName) { + assert PLATFORM_REQUIRES_NATIVE_FALLBACK_EXTRACTION; + Log.w(TAG, "Failed to load libName %s, attempting fallback extraction then trying again", + libName); + String libraryEntry = LibraryLoader.makeLibraryPathInZipFile(libName, false, false); + return extractFileIfStale(appContext, libraryEntry, makeLibraryDirAndSetPermission()); + } + + // Invoke either Linker.loadLibrary(...), System.loadLibrary(...) or System.load(...), + // triggering JNI_OnLoad in native code. + // TODO(crbug.com/635567): Fix this properly. + @SuppressLint({"DefaultLocale", "UnsafeDynamicallyLoadedCode"}) + private void loadAlreadyLocked(Context appContext) throws ProcessInitException { + try (TraceEvent te = TraceEvent.scoped("LibraryLoader.loadAlreadyLocked")) { + if (!mLoaded) { + assert !mInitialized; + + long startTime = SystemClock.uptimeMillis(); + + if (useCrazyLinker()) { + // Load libraries using the Chromium linker. + Linker linker = Linker.getInstance(); + + String apkFilePath = + isInZipFile() ? appContext.getApplicationInfo().sourceDir : null; + linker.prepareLibraryLoad(apkFilePath); + + for (String library : NativeLibraries.LIBRARIES) { + // Don't self-load the linker. This is because the build system is + // not clever enough to understand that all the libraries packaged + // in the final .apk don't need to be explicitly loaded. + if (linker.isChromiumLinkerLibrary(library)) { + if (DEBUG) Log.i(TAG, "ignoring self-linker load"); + continue; + } + + // Determine where the library should be loaded from. + String libFilePath = System.mapLibraryName(library); + if (apkFilePath != null) { + Log.i(TAG, " Loading " + library + " from within " + apkFilePath); + } else { + Log.i(TAG, "Loading " + library); + } + + try { + // Load the library using this Linker. May throw UnsatisfiedLinkError. + loadLibraryWithCustomLinkerAlreadyLocked( + linker, apkFilePath, libFilePath); + incrementRelinkerCountNotHitHistogram(); + } catch (UnsatisfiedLinkError e) { + if (!isInZipFile() + && PLATFORM_REQUIRES_NATIVE_FALLBACK_EXTRACTION) { + loadLibraryWithCustomLinkerAlreadyLocked( + linker, null, getExtractedLibraryPath(appContext, library)); + incrementRelinkerCountHitHistogram(); + } else { + Log.e(TAG, "Unable to load library: " + library); + throw(e); + } + } + } + + linker.finishLibraryLoad(); + } else { + setEnvForNative(); + preloadAlreadyLocked(appContext); + + // If the libraries are located in the zip file, assert that the device API + // level is M or higher. On devices lower than M, the libraries should + // always be loaded by Linker. + assert !isInZipFile() || Build.VERSION.SDK_INT >= VERSION_CODES.M; + + // Load libraries using the system linker. + for (String library : NativeLibraries.LIBRARIES) { + try { + if (!isInZipFile()) { + // The extract and retry logic isn't needed because this path is + // used only for local development. + System.loadLibrary(library); + } else { + // Load directly from the APK. + boolean is64Bit = ApiHelperForM.isProcess64Bit(); + String zipFilePath = appContext.getApplicationInfo().sourceDir; + // In API level 23 and above, it’s possible to open a .so file + // directly from the APK of the path form + // "my_zip_file.zip!/libs/libstuff.so". See: + // https://android.googlesource.com/platform/bionic/+/master/android-changes-for-ndk-developers.md#opening-shared-libraries-directly-from-an-apk + String libraryName = zipFilePath + "!/" + + makeLibraryPathInZipFile(library, true, is64Bit); + Log.i(TAG, "libraryName: " + libraryName); + System.load(libraryName); + } + } catch (UnsatisfiedLinkError e) { + Log.e(TAG, "Unable to load library: " + library); + throw(e); + } + } + } + + long stopTime = SystemClock.uptimeMillis(); + mLibraryLoadTimeMs = stopTime - startTime; + Log.i(TAG, String.format("Time to load native libraries: %d ms (timestamps %d-%d)", + mLibraryLoadTimeMs, + startTime % 10000, + stopTime % 10000)); + + mLoaded = true; + } + } catch (UnsatisfiedLinkError e) { + throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_LOAD_FAILED, e); + } + } + + /** + * @param library The library name that is looking for. + * @param crazyPrefix true iff adding crazy linker prefix to the file name. + * @param is64Bit true if the caller think it's run on a 64 bit device. + * @return the library path name in the zip file. + */ + @NonNull + public static String makeLibraryPathInZipFile( + String library, boolean crazyPrefix, boolean is64Bit) { + // Determine the ABI string that Android uses to find native libraries. Values are described + // in: https://developer.android.com/ndk/guides/abis.html + // The 'armeabi' is omitted here because it is not supported in Chrome/WebView, while Cronet + // and Cast load the native library via other paths. + String cpuAbi; + switch (NativeLibraries.sCpuFamily) { + case NativeLibraries.CPU_FAMILY_ARM: + cpuAbi = is64Bit ? "arm64-v8a" : "armeabi-v7a"; + break; + case NativeLibraries.CPU_FAMILY_X86: + cpuAbi = is64Bit ? "x86_64" : "x86"; + break; + case NativeLibraries.CPU_FAMILY_MIPS: + cpuAbi = is64Bit ? "mips64" : "mips"; + break; + default: + throw new RuntimeException("Unknown CPU ABI for native libraries"); + } + + // When both the Chromium linker and zip-uncompressed native libraries are used, + // the build system renames the native shared libraries with a 'crazy.' prefix + // (e.g. "/lib/armeabi-v7a/libfoo.so" -> "/lib/armeabi-v7a/crazy.libfoo.so"). + // + // This prevents the package manager from extracting them at installation/update time + // to the /data directory. The libraries can still be accessed directly by the Chromium + // linker from the APK. + String crazyPart = crazyPrefix ? "crazy." : ""; + return String.format("lib/%s/%s%s", cpuAbi, crazyPart, System.mapLibraryName(library)); + } + + // The WebView requires the Command Line to be switched over before + // initialization is done. This is okay in the WebView's case since the + // JNI is already loaded by this point. + public void switchCommandLineForWebView() { + synchronized (mLock) { + ensureCommandLineSwitchedAlreadyLocked(); + } + } + + // Switch the CommandLine over from Java to native if it hasn't already been done. + // This must happen after the code is loaded and after JNI is ready (since after the + // switch the Java CommandLine will delegate all calls the native CommandLine). + private void ensureCommandLineSwitchedAlreadyLocked() { + assert mLoaded; + if (mCommandLineSwitched) { + return; + } + CommandLine.enableNativeProxy(); + mCommandLineSwitched = true; + } + + // Invoke base::android::LibraryLoaded in library_loader_hooks.cc + private void initializeAlreadyLocked(@LibraryProcessType int processType) + throws ProcessInitException { + if (mInitialized) { + if (mLibraryProcessType != processType) { + throw new ProcessInitException( + LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_LOAD_FAILED); + } + return; + } + mLibraryProcessType = processType; + + ensureCommandLineSwitchedAlreadyLocked(); + + if (!nativeLibraryLoaded(mLibraryProcessType)) { + Log.e(TAG, "error calling nativeLibraryLoaded"); + throw new ProcessInitException(LoaderErrors.LOADER_ERROR_FAILED_TO_REGISTER_JNI); + } + + // Check that the version of the library we have loaded matches the version we expect + Log.i(TAG, String.format("Expected native library version number \"%s\", " + + "actual native library version number \"%s\"", + NativeLibraries.sVersionNumber, nativeGetVersionNumber())); + if (!NativeLibraries.sVersionNumber.equals(nativeGetVersionNumber())) { + throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_WRONG_VERSION); + } + + // From now on, keep tracing in sync with native. + TraceEvent.registerNativeEnabledObserver(); + + if (processType == LibraryProcessType.PROCESS_BROWSER + && PLATFORM_REQUIRES_NATIVE_FALLBACK_EXTRACTION) { + // Perform the detection and deletion of obsolete native libraries on a background + // background thread. + AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { + @Override + public void run() { + final String suffix = BuildInfo.getInstance().extractedFileSuffix; + final File[] files = getLibraryDir().listFiles(); + if (files == null) return; + + for (File file : files) { + // NOTE: Do not simply look for <suffix> at the end of the file. + // + // Extracted library files have names like 'libfoo.so<suffix>', but + // extractFileIfStale() will use FileUtils.copyFileStreamAtomicWithBuffer() + // to create them, and this method actually uses a transient temporary file + // named like 'libfoo.so<suffix>.tmp' to do that. These temporary files, if + // detected here, should be preserved; hence the reason why contains() is + // used below. + if (!file.getName().contains(suffix)) { + String fileName = file.getName(); + if (!file.delete()) { + Log.w(TAG, "Unable to remove %s", fileName); + } else { + Log.i(TAG, "Removed obsolete file %s", fileName); + } + } + } + } + }); + } + + // From this point on, native code is ready to use and checkIsReady() + // shouldn't complain from now on (and in fact, it's used by the + // following calls). + // Note that this flag can be accessed asynchronously, so any initialization + // must be performed before. + mInitialized = true; + } + + // Called after all native initializations are complete. + public void onNativeInitializationComplete() { + synchronized (mLock) { + recordBrowserProcessHistogramAlreadyLocked(); + } + } + + // Record Chromium linker histogram state for the main browser process. Called from + // onNativeInitializationComplete(). + private void recordBrowserProcessHistogramAlreadyLocked() { + assert Thread.holdsLock(mLock); + if (useCrazyLinker()) { + nativeRecordChromiumAndroidLinkerBrowserHistogram(mIsUsingBrowserSharedRelros, + mLoadAtFixedAddressFailed, + mLibraryWasLoadedFromApk ? LibraryLoadFromApkStatusCodes.SUCCESSFUL + : LibraryLoadFromApkStatusCodes.UNKNOWN, + mLibraryLoadTimeMs); + } + if (mLibraryPreloader != null) { + nativeRecordLibraryPreloaderBrowserHistogram(mLibraryPreloaderStatus); + } + } + + // Register pending Chromium linker histogram state for renderer processes. This cannot be + // recorded as a histogram immediately because histograms and IPC are not ready at the + // time it are captured. This function stores a pending value, so that a later call to + // RecordChromiumAndroidLinkerRendererHistogram() will record it correctly. + public void registerRendererProcessHistogram(boolean requestedSharedRelro, + boolean loadAtFixedAddressFailed) { + synchronized (mLock) { + if (useCrazyLinker()) { + nativeRegisterChromiumAndroidLinkerRendererHistogram( + requestedSharedRelro, loadAtFixedAddressFailed, mLibraryLoadTimeMs); + } + if (mLibraryPreloader != null) { + nativeRegisterLibraryPreloaderRendererHistogram(mLibraryPreloaderStatus); + } + } + } + + /** + * Override the library loader (normally with a mock) for testing. + * @param loader the mock library loader. + */ + @VisibleForTesting + public static void setLibraryLoaderForTesting(LibraryLoader loader) { + sInstance = loader; + } + + /** + * Configure ubsan using $UBSAN_OPTIONS. This function needs to be called before any native + * libraries are loaded because ubsan reads its configuration from $UBSAN_OPTIONS when the + * native library is loaded. + */ + public static void setEnvForNative() { + // The setenv API was added in L. On older versions of Android, we should still see ubsan + // reports, but they will not have stack traces. + if (BuildConfig.IS_UBSAN && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + try { + // This value is duplicated in build/android/pylib/constants/__init__.py. + Os.setenv("UBSAN_OPTIONS", + "print_stacktrace=1 stack_trace_format='#%n pc %o %m' " + + "handle_segv=0 handle_sigbus=0 handle_sigfpe=0", + true); + } catch (Exception e) { + Log.w(TAG, "failed to set UBSAN_OPTIONS", e); + } + } + } + + // Android system sometimes fails to extract libraries from APK (https://crbug.com/806998). + // This function manually extract libraries as a fallback. + @SuppressLint({"SetWorldReadable"}) + private static String extractFileIfStale( + Context appContext, String pathWithinApk, File destDir) { + assert PLATFORM_REQUIRES_NATIVE_FALLBACK_EXTRACTION; + + String apkPath = appContext.getApplicationInfo().sourceDir; + String fileName = + (new File(pathWithinApk)).getName() + BuildInfo.getInstance().extractedFileSuffix; + File libraryFile = new File(destDir, fileName); + + if (!libraryFile.exists()) { + ZipFile zipFile = null; + try { + zipFile = new ZipFile(apkPath); + ZipEntry zipEntry = zipFile.getEntry(pathWithinApk); + if (zipEntry == null) + throw new RuntimeException("Cannot find ZipEntry" + pathWithinApk); + InputStream inputStream = zipFile.getInputStream(zipEntry); + + FileUtils.copyFileStreamAtomicWithBuffer( + inputStream, libraryFile, new byte[16 * 1024]); + libraryFile.setReadable(true, false); + libraryFile.setExecutable(true, false); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + StreamUtil.closeQuietly(zipFile); + } + } + return libraryFile.getAbsolutePath(); + } + + // Ensure the extracted native libraries is created with the right permissions. + private static File makeLibraryDirAndSetPermission() { + if (!ContextUtils.isIsolatedProcess()) { + File cacheDir = ContextCompat.getCodeCacheDir(ContextUtils.getApplicationContext()); + File libDir = new File(cacheDir, LIBRARY_DIR); + cacheDir.mkdir(); + cacheDir.setExecutable(true, false); + libDir.mkdir(); + libDir.setExecutable(true, false); + } + return getLibraryDir(); + } + + // Return File object for the directory containing extracted native libraries. + private static File getLibraryDir() { + return new File( + ContextCompat.getCodeCacheDir(ContextUtils.getApplicationContext()), LIBRARY_DIR); + } + + // Only methods needed before or during normal JNI registration are during System.OnLoad. + // nativeLibraryLoaded is then called to register everything else. This process is called + // "initialization". This method will be mapped (by generated code) to the LibraryLoaded + // definition in base/android/library_loader/library_loader_hooks.cc. + // + // Return true on success and false on failure. + private native boolean nativeLibraryLoaded(@LibraryProcessType int processType); + + // Method called to record statistics about the Chromium linker operation for the main + // browser process. Indicates whether the linker attempted relro sharing for the browser, + // and if it did, whether the library failed to load at a fixed address. Also records + // support for loading a library directly from the APK file, and the number of milliseconds + // it took to load the libraries. + private native void nativeRecordChromiumAndroidLinkerBrowserHistogram( + boolean isUsingBrowserSharedRelros, + boolean loadAtFixedAddressFailed, + int libraryLoadFromApkStatus, + long libraryLoadTime); + + // Method called to record the return value of NativeLibraryPreloader.loadLibrary for the main + // browser process. + private native void nativeRecordLibraryPreloaderBrowserHistogram(int status); + + // Method called to register (for later recording) statistics about the Chromium linker + // operation for a renderer process. Indicates whether the linker attempted relro sharing, + // and if it did, whether the library failed to load at a fixed address. Also records the + // number of milliseconds it took to load the libraries. + private native void nativeRegisterChromiumAndroidLinkerRendererHistogram( + boolean requestedSharedRelro, + boolean loadAtFixedAddressFailed, + long libraryLoadTime); + + // Method called to register (for later recording) the return value of + // NativeLibraryPreloader.loadLibrary for a renderer process. + private native void nativeRegisterLibraryPreloaderRendererHistogram(int status); + + // Get the version of the native library. This is needed so that we can check we + // have the right version before initializing the (rest of the) JNI. + private native String nativeGetVersionNumber(); + + // Finds the ranges corresponding to the native library pages, forks a new + // process to prefetch these pages and waits for it. The new process then + // terminates. This is blocking. + private static native void nativeForkAndPrefetchNativeLibrary(); + + // Returns the percentage of the native library code page that are currently reseident in + // memory. + private static native int nativePercentageOfResidentNativeLibraryCode(); + + // Periodically logs native library residency from this thread. + private static native void nativePeriodicallyCollectResidency(); +}
diff --git a/src/base/android/java/src/org/chromium/base/library_loader/Linker.java b/src/base/android/java/src/org/chromium/base/library_loader/Linker.java new file mode 100644 index 0000000..5e30cfa --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/library_loader/Linker.java
@@ -0,0 +1,1160 @@ +// Copyright 2015 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. + +package org.chromium.base.library_loader; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.os.Parcel; +import android.os.ParcelFileDescriptor; +import android.os.Parcelable; + +import org.chromium.base.ContextUtils; +import org.chromium.base.Log; +import org.chromium.base.StreamUtil; +import org.chromium.base.SysUtils; +import org.chromium.base.ThreadUtils; +import org.chromium.base.annotations.AccessedByNative; +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.MainDex; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import javax.annotation.Nullable; + +/* + * Technical note: + * + * The point of this class is to provide an alternative to System.loadLibrary() + * to load native shared libraries. One specific feature that it supports is the + * ability to save RAM by sharing the ELF RELRO sections between renderer + * processes. + * + * When two processes load the same native library at the _same_ memory address, + * the content of their RELRO section (which includes C++ vtables or any + * constants that contain pointers) will be largely identical [1]. + * + * By default, the RELRO section is backed by private RAM in each process, + * which is still significant on mobile (e.g. 1.28 MB / process on Chrome 30 for + * Android). + * + * However, it is possible to save RAM by creating a shared memory region, + * copy the RELRO content into it, then have each process swap its private, + * regular RELRO, with a shared, read-only, mapping of the shared one. + * + * This trick saves 98% of the RELRO section size per extra process, after the + * first one. On the other hand, this requires careful communication between + * the process where the shared RELRO is created and the one(s) where it is used. + * + * Note that swapping the regular RELRO with the shared one is not an atomic + * operation. Care must be taken that no other thread tries to run native code + * that accesses it during it. In practice, this means the swap must happen + * before library native code is executed. + * + * [1] The exceptions are pointers to external, randomized, symbols, like + * those from some system libraries, but these are very few in practice. + */ + +/* + * Security considerations: + * + * - Whether the browser process loads its native libraries at the same + * addresses as the service ones (to save RAM by sharing the RELRO too) + * depends on the configuration variable BROWSER_SHARED_RELRO_CONFIG. + * + * Not using fixed library addresses in the browser process is preferred + * for regular devices since it maintains the efficacy of ASLR as an + * exploit mitigation across the render <-> browser privilege boundary. + * + * - The shared RELRO memory region is always forced read-only after creation, + * which means it is impossible for a compromised service process to map + * it read-write (e.g. by calling mmap() or mprotect()) and modify its + * content, altering values seen in other service processes. + * + * - Once the RELRO ashmem region or file is mapped into a service process's + * address space, the corresponding file descriptor is immediately closed. The + * file descriptor is kept opened in the browser process, because a copy needs + * to be sent to each new potential service process. + * + * - The common library load addresses are randomized for each instance of + * the program on the device. See getRandomBaseLoadAddress() for more + * details on how this is obtained. + * + * - When loading several libraries in service processes, a simple incremental + * approach from the original random base load address is used. This is + * sufficient to deal correctly with component builds (which can use dozens + * of shared libraries), while regular builds always embed a single shared + * library per APK. + */ + +/** + * Here's an explanation of how this class is supposed to be used: + * + * - Native shared libraries should be loaded with Linker.loadLibrary(), + * instead of System.loadLibrary(). The two functions should behave the same + * (at a high level). + * + * - Before loading any library, prepareLibraryLoad() should be called. + * + * - After loading all libraries, finishLibraryLoad() should be called, before + * running any native code from any of the libraries (except their static + * constructors, which can't be avoided). + * + * - A service process shall call either initServiceProcess() or + * disableSharedRelros() early (i.e. before any loadLibrary() call). + * Otherwise, the linker considers that it is running inside the browser + * process. This is because various Chromium projects have vastly + * different initialization paths. + * + * disableSharedRelros() completely disables shared RELROs, and loadLibrary() + * will behave exactly like System.loadLibrary(). + * + * initServiceProcess(baseLoadAddress) indicates that shared RELROs are to be + * used in this process. + * + * - The browser is in charge of deciding where in memory each library should + * be loaded. This address must be passed to each service process (see + * ChromiumLinkerParams.java in content for a helper class to do so). + * + * - The browser will also generate shared RELROs for each library it loads. + * More specifically, by default when in the browser process, the linker + * will: + * + * - Load libraries randomly (just like System.loadLibrary()). + * - Compute the fixed address to be used to load the same library + * in service processes. + * - Create a shared memory region populated with the RELRO region + * content pre-relocated for the specific fixed address above. + * + * Note that these shared RELRO regions cannot be used inside the browser + * process. They are also never mapped into it. + * + * This behaviour is altered by the BROWSER_SHARED_RELRO_CONFIG configuration + * variable below, which may force the browser to load the libraries at + * fixed addresses too. + * + * - Once all libraries are loaded in the browser process, one can call + * getSharedRelros() which returns a Bundle instance containing a map that + * links each loaded library to its shared RELRO region. + * + * This Bundle must be passed to each service process, for example through + * a Binder call (note that the Bundle includes file descriptors and cannot + * be added as an Intent extra). + * + * - In a service process, finishLibraryLoad() and/or loadLibrary() may + * block until the RELRO section Bundle is received. This is typically + * done by calling useSharedRelros() from another thread. + * + * This method also ensures the process uses the shared RELROs. + */ +public class Linker { + // Log tag for this class. + private static final String TAG = "LibraryLoader"; + + // Name of the library that contains our JNI code. + private static final String LINKER_JNI_LIBRARY = "chromium_android_linker"; + + // Constants used to control the behaviour of the browser process with + // regards to the shared RELRO section. + // NEVER -> The browser never uses it itself. + // LOW_RAM_ONLY -> It is only used on devices with low RAM. + // ALWAYS -> It is always used. + // NOTE: These names are known and expected by the Linker test scripts. + public static final int BROWSER_SHARED_RELRO_CONFIG_NEVER = 0; + public static final int BROWSER_SHARED_RELRO_CONFIG_LOW_RAM_ONLY = 1; + public static final int BROWSER_SHARED_RELRO_CONFIG_ALWAYS = 2; + + // Configuration variable used to control how the browser process uses the + // shared RELRO. Only change this while debugging linker-related issues. + // NOTE: This variable's name is known and expected by the Linker test scripts. + public static final int BROWSER_SHARED_RELRO_CONFIG = + BROWSER_SHARED_RELRO_CONFIG_LOW_RAM_ONLY; + + // Constants used to control the memory device config. Can be set explicitly + // by setMemoryDeviceConfigForTesting(). + // INIT -> Value is undetermined (will check at runtime). + // LOW -> This is a low-memory device. + // NORMAL -> This is not a low-memory device. + public static final int MEMORY_DEVICE_CONFIG_INIT = 0; + public static final int MEMORY_DEVICE_CONFIG_LOW = 1; + public static final int MEMORY_DEVICE_CONFIG_NORMAL = 2; + + // Indicates if this is a low-memory device or not. The default is to + // determine this by probing the system at runtime, but this can be forced + // for testing by calling setMemoryDeviceConfigForTesting(). + private int mMemoryDeviceConfig = MEMORY_DEVICE_CONFIG_INIT; + + // Set to true to enable debug logs. + protected static final boolean DEBUG = false; + + // Used to pass the shared RELRO Bundle through Binder. + public static final String EXTRA_LINKER_SHARED_RELROS = + "org.chromium.base.android.linker.shared_relros"; + + // Guards all access to the linker. + protected final Object mLock = new Object(); + + // The name of a class that implements TestRunner. + private String mTestRunnerClassName; + + // Size of reserved Breakpad guard region. Should match the value of + // kBreakpadGuardRegionBytes on the JNI side. Used when computing the load + // addresses of multiple loaded libraries. Set to 0 to disable the guard. + private static final int BREAKPAD_GUARD_REGION_BYTES = 16 * 1024 * 1024; + + // Size of the area requested when using ASLR to obtain a random load address. + // Should match the value of kAddressSpaceReservationSize on the JNI side. + // Used when computing the load addresses of multiple loaded libraries to + // ensure that we don't try to load outside the area originally requested. + private static final int ADDRESS_SPACE_RESERVATION = 192 * 1024 * 1024; + + // Becomes true after linker initialization. + private boolean mInitialized; + + // Set to true if this runs in the browser process. Disabled by initServiceProcess(). + private boolean mInBrowserProcess = true; + + // Becomes true to indicate this process needs to wait for a shared RELRO in + // finishLibraryLoad(). + private boolean mWaitForSharedRelros; + + // Becomes true when initialization determines that the browser process can use the + // shared RELRO. + private boolean mBrowserUsesSharedRelro; + + // The map of all RELRO sections either created or used in this process. + private Bundle mSharedRelros; + + // Current common random base load address. A value of -1 indicates not yet initialized. + private long mBaseLoadAddress = -1; + + // Current fixed-location load address for the next library called by loadLibrary(). + // A value of -1 indicates not yet initialized. + private long mCurrentLoadAddress = -1; + + // Becomes true once prepareLibraryLoad() has been called. + private boolean mPrepareLibraryLoadCalled; + + // The map of libraries that are currently loaded in this process. + private HashMap<String, LibInfo> mLoadedLibraries; + + // Singleton. + private static final Linker sSingleton = new Linker(); + + // Private singleton constructor. + private Linker() { + // Ensure this class is not referenced unless it's used. + assert LibraryLoader.useCrazyLinker(); + } + + /** + * Get singleton instance. Returns a Linker. + * + * On N+ Monochrome is selected by Play Store. With Monochrome this code is not used, instead + * Chrome asks the WebView to provide the library (and the shared RELRO). If the WebView fails + * to provide the library, the system linker is used as a fallback. + * + * Linker runs on all Android releases, but is incompatible with GVR library on N+. + * Linker is preferred on M- because it does not write the shared RELRO to disk at + * almost every cold startup. + * + * @return the Linker implementation instance. + */ + public static Linker getInstance() { + return sSingleton; + } + + /** + * Check that native library linker tests are enabled. + * If not enabled, calls to testing functions will fail with an assertion + * error. + * + * @return true if native library linker tests are enabled. + */ + public static boolean areTestsEnabled() { + return NativeLibraries.sEnableLinkerTests; + } + + /** + * Assert NativeLibraries.sEnableLinkerTests is true. + * Hard assertion that we are in a testing context. Cannot be disabled. The + * test methods in this module permit injection of runnable code by class + * name. To protect against both malicious and accidental use of these + * methods, we ensure that NativeLibraries.sEnableLinkerTests is true when + * any is called. + */ + private static void assertLinkerTestsAreEnabled() { + assert NativeLibraries.sEnableLinkerTests : "Testing method called in non-testing context"; + } + + /** + * A public interface used to run runtime linker tests after loading + * libraries. Should only be used to implement the linker unit tests, + * which is controlled by the value of NativeLibraries.sEnableLinkerTests + * configured at build time. + */ + public interface TestRunner { + /** + * Run runtime checks and return true if they all pass. + * + * @param memoryDeviceConfig The current memory device configuration. + * @param inBrowserProcess true iff this is the browser process. + * @return true if all checks pass. + */ + public boolean runChecks(int memoryDeviceConfig, boolean inBrowserProcess); + } + + /** + * Call this to retrieve the name of the current TestRunner class name + * if any. This can be useful to pass it from the browser process to + * child ones. + * + * @return null or a String holding the name of the class implementing + * the TestRunner set by calling setTestRunnerClassNameForTesting() previously. + */ + public final String getTestRunnerClassNameForTesting() { + // Sanity check. This method may only be called during tests. + assertLinkerTestsAreEnabled(); + + synchronized (mLock) { + return mTestRunnerClassName; + } + } + + /** + * Sets the test class name. + * + * On the first call, instantiates a Linker and sets its test runner class name. On subsequent + * calls, checks that the singleton produced by the first call matches the test runner class + * name. + */ + public static final void setupForTesting(String testRunnerClassName) { + if (DEBUG) { + Log.i(TAG, "setupForTesting(" + testRunnerClassName + ") called"); + } + // Sanity check. This method may only be called during tests. + assertLinkerTestsAreEnabled(); + + synchronized (sSingleton) { + sSingleton.mTestRunnerClassName = testRunnerClassName; + } + } + + /** + * Instantiate and run the current TestRunner, if any. The TestRunner implementation + * must be instantiated _after_ all libraries are loaded to ensure that its + * native methods are properly registered. + * + * @param memoryDeviceConfig Linker memory config, or 0 if unused + * @param inBrowserProcess true if in the browser process + */ + private final void runTestRunnerClassForTesting( + int memoryDeviceConfig, boolean inBrowserProcess) { + if (DEBUG) { + Log.i(TAG, "runTestRunnerClassForTesting called"); + } + // Sanity check. This method may only be called during tests. + assertLinkerTestsAreEnabled(); + + synchronized (mLock) { + if (mTestRunnerClassName == null) { + Log.wtf(TAG, "Linker runtime tests not set up for this process"); + assert false; + } + if (DEBUG) { + Log.i(TAG, "Instantiating " + mTestRunnerClassName); + } + TestRunner testRunner = null; + try { + testRunner = (TestRunner) Class.forName(mTestRunnerClassName) + .getDeclaredConstructor() + .newInstance(); + } catch (Exception e) { + Log.wtf(TAG, "Could not instantiate test runner class by name", e); + assert false; + } + + if (!testRunner.runChecks(memoryDeviceConfig, inBrowserProcess)) { + Log.wtf(TAG, "Linker runtime tests failed in this process"); + assert false; + } + + Log.i(TAG, "All linker tests passed"); + } + } + + /** + * Call this method before any other Linker method to force a specific + * memory device configuration. Should only be used for testing. + * + * @param memoryDeviceConfig MEMORY_DEVICE_CONFIG_LOW or MEMORY_DEVICE_CONFIG_NORMAL. + */ + public final void setMemoryDeviceConfigForTesting(int memoryDeviceConfig) { + if (DEBUG) { + Log.i(TAG, "setMemoryDeviceConfigForTesting(" + memoryDeviceConfig + ") called"); + } + // Sanity check. This method may only be called during tests. + assertLinkerTestsAreEnabled(); + assert memoryDeviceConfig == MEMORY_DEVICE_CONFIG_LOW + || memoryDeviceConfig == MEMORY_DEVICE_CONFIG_NORMAL; + + synchronized (mLock) { + assert mMemoryDeviceConfig == MEMORY_DEVICE_CONFIG_INIT; + + mMemoryDeviceConfig = memoryDeviceConfig; + if (DEBUG) { + if (mMemoryDeviceConfig == MEMORY_DEVICE_CONFIG_LOW) { + Log.i(TAG, "Simulating a low-memory device"); + } else { + Log.i(TAG, "Simulating a regular-memory device"); + } + } + } + } + + /** + * Determine whether a library is the linker library. + * + * @param library the name of the library. + * @return true is the library is the Linker's own JNI library. + */ + boolean isChromiumLinkerLibrary(String library) { + return library.equals(LINKER_JNI_LIBRARY); + } + + /** + * Load the Linker JNI library. Throws UnsatisfiedLinkError on error. + */ + @SuppressLint({"UnsafeDynamicallyLoadedCode"}) + private static void loadLinkerJniLibrary() { + LibraryLoader.setEnvForNative(); + if (DEBUG) { + String libName = "lib" + LINKER_JNI_LIBRARY + ".so"; + Log.i(TAG, "Loading " + libName); + } + try { + System.loadLibrary(LINKER_JNI_LIBRARY); + LibraryLoader.incrementRelinkerCountNotHitHistogram(); + } catch (UnsatisfiedLinkError e) { + if (LibraryLoader.PLATFORM_REQUIRES_NATIVE_FALLBACK_EXTRACTION) { + System.load(LibraryLoader.getExtractedLibraryPath( + ContextUtils.getApplicationContext(), LINKER_JNI_LIBRARY)); + LibraryLoader.incrementRelinkerCountHitHistogram(); + } + } + } + + /** + * Obtain a random base load address at which to place loaded libraries. + * + * @return new base load address + */ + private long getRandomBaseLoadAddress() { + // nativeGetRandomBaseLoadAddress() returns an address at which it has previously + // successfully mapped an area larger than the largest library we expect to load, + // on the basis that we will be able, with high probability, to map our library + // into it. + // + // One issue with this is that we do not yet know the size of the library that + // we will load is. If it is smaller than the size we used to obtain a random + // address the library mapping may still succeed. The other issue is that + // although highly unlikely, there is no guarantee that something else does not + // map into the area we are going to use between here and when we try to map into it. + // + // The above notes mean that all of this is probablistic. It is however okay to do + // because if, worst case and unlikely, we get unlucky in our choice of address, + // the back-out and retry without the shared RELRO in the ChildProcessService will + // keep things running. + final long address = nativeGetRandomBaseLoadAddress(); + if (DEBUG) { + Log.i(TAG, String.format(Locale.US, "Random native base load address: 0x%x", address)); + } + return address; + } + + /** + * Load a native shared library with the Chromium linker. Note the crazy linker treats + * libraries and files as equivalent, so you can only open one library in a given zip + * file. The library must not be the Chromium linker library. + * + * @param libFilePath The path of the library (possibly in the zip file). + */ + void loadLibrary(String libFilePath) { + if (DEBUG) { + Log.i(TAG, "loadLibrary: " + libFilePath); + } + final boolean isFixedAddressPermitted = true; + loadLibraryImpl(libFilePath, isFixedAddressPermitted); + } + + /** + * Load a native shared library with the Chromium linker, ignoring any + * requested fixed address for RELRO sharing. Note the crazy linker treats libraries and + * files as equivalent, so you can only open one library in a given zip file. The + * library must not be the Chromium linker library. + * + * @param libFilePath The path of the library (possibly in the zip file). + */ + void loadLibraryNoFixedAddress(String libFilePath) { + if (DEBUG) { + Log.i(TAG, "loadLibraryAtAnyAddress: " + libFilePath); + } + final boolean isFixedAddressPermitted = false; + loadLibraryImpl(libFilePath, isFixedAddressPermitted); + } + + // Used internally to initialize the linker's data. Assumes lock is held. + // Loads JNI, and sets mMemoryDeviceConfig and mBrowserUsesSharedRelro. + private void ensureInitializedLocked() { + assert Thread.holdsLock(mLock); + + if (mInitialized) { + return; + } + + // On first call, load libchromium_android_linker.so. Cannot be done in the + // constructor because instantiation occurs on the UI thread. + loadLinkerJniLibrary(); + + if (mMemoryDeviceConfig == MEMORY_DEVICE_CONFIG_INIT) { + if (SysUtils.isLowEndDevice()) { + mMemoryDeviceConfig = MEMORY_DEVICE_CONFIG_LOW; + } else { + mMemoryDeviceConfig = MEMORY_DEVICE_CONFIG_NORMAL; + } + } + + // Cannot run in the constructor because SysUtils.isLowEndDevice() relies + // on CommandLine, which may not be available at instantiation. + switch (BROWSER_SHARED_RELRO_CONFIG) { + case BROWSER_SHARED_RELRO_CONFIG_NEVER: + mBrowserUsesSharedRelro = false; + break; + case BROWSER_SHARED_RELRO_CONFIG_LOW_RAM_ONLY: + if (mMemoryDeviceConfig == MEMORY_DEVICE_CONFIG_LOW) { + mBrowserUsesSharedRelro = true; + Log.w(TAG, "Low-memory device: shared RELROs used in all processes"); + } else { + mBrowserUsesSharedRelro = false; + } + break; + case BROWSER_SHARED_RELRO_CONFIG_ALWAYS: + Log.w(TAG, "Beware: shared RELROs used in all processes!"); + mBrowserUsesSharedRelro = true; + break; + default: + Log.wtf(TAG, "FATAL: illegal shared RELRO config"); + throw new AssertionError(); + } + + mInitialized = true; + } + + /** + * Call this method to determine if the linker will try to use shared RELROs + * for the browser process. + */ + public boolean isUsingBrowserSharedRelros() { + synchronized (mLock) { + ensureInitializedLocked(); + return mInBrowserProcess && mBrowserUsesSharedRelro; + } + } + + /** + * Call this method just before loading any native shared libraries in this process. + * + * @param apkFilePath Optional current APK file path. If provided, the linker + * will try to load libraries directly from it. + */ + public void prepareLibraryLoad(@Nullable String apkFilePath) { + if (DEBUG) { + Log.i(TAG, "prepareLibraryLoad() called"); + } + synchronized (mLock) { + ensureInitializedLocked(); + if (apkFilePath != null) { + nativeAddZipArchivePath(apkFilePath); + } + mPrepareLibraryLoadCalled = true; + + if (mInBrowserProcess) { + // Force generation of random base load address, as well + // as creation of shared RELRO sections in this process. + setupBaseLoadAddressLocked(); + } + } + } + + /** + * Call this method just after loading all native shared libraries in this process. + * Note that when in a service process, this will block until the RELRO bundle is + * received, i.e. when another thread calls useSharedRelros(). + */ + void finishLibraryLoad() { + if (DEBUG) { + Log.i(TAG, "finishLibraryLoad() called"); + } + synchronized (mLock) { + ensureInitializedLocked(); + if (DEBUG) { + Log.i(TAG, + String.format(Locale.US, + "mInBrowserProcess=%b mBrowserUsesSharedRelro=%b mWaitForSharedRelros=%b", + mInBrowserProcess, mBrowserUsesSharedRelro, mWaitForSharedRelros)); + } + + if (mLoadedLibraries == null) { + if (DEBUG) { + Log.i(TAG, "No libraries loaded"); + } + } else { + if (mInBrowserProcess) { + // Create new Bundle containing RELRO section information + // for all loaded libraries. Make it available to getSharedRelros(). + mSharedRelros = createBundleFromLibInfoMap(mLoadedLibraries); + if (DEBUG) { + Log.i(TAG, "Shared RELRO created"); + dumpBundle(mSharedRelros); + } + + if (mBrowserUsesSharedRelro) { + useSharedRelrosLocked(mSharedRelros); + } + } + + if (mWaitForSharedRelros) { + assert !mInBrowserProcess; + + // Wait until the shared relro bundle is received from useSharedRelros(). + while (mSharedRelros == null) { + try { + mLock.wait(); + } catch (InterruptedException ie) { + // Restore the thread's interrupt status. + Thread.currentThread().interrupt(); + } + } + useSharedRelrosLocked(mSharedRelros); + // Clear the Bundle to ensure its file descriptor references can't be reused. + mSharedRelros.clear(); + mSharedRelros = null; + } + } + + // If testing, run tests now that all libraries are loaded and initialized. + if (NativeLibraries.sEnableLinkerTests) { + runTestRunnerClassForTesting(mMemoryDeviceConfig, mInBrowserProcess); + } + } + if (DEBUG) { + Log.i(TAG, "finishLibraryLoad() exiting"); + } + } + + /** + * Call this to send a Bundle containing the shared RELRO sections to be + * used in this process. If initServiceProcess() was previously called, + * finishLibraryLoad() will not exit until this method is called in another + * thread with a non-null value. + * + * @param bundle The Bundle instance containing a map of shared RELRO sections + * to use in this process. + */ + public void useSharedRelros(Bundle bundle) { + // Ensure the bundle uses the application's class loader, not the framework + // one which doesn't know anything about LibInfo. + // Also, hold a fresh copy of it so the caller can't recycle it. + Bundle clonedBundle = null; + if (bundle != null) { + bundle.setClassLoader(LibInfo.class.getClassLoader()); + clonedBundle = new Bundle(LibInfo.class.getClassLoader()); + Parcel parcel = Parcel.obtain(); + bundle.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + clonedBundle.readFromParcel(parcel); + parcel.recycle(); + } + if (DEBUG) { + Log.i(TAG, "useSharedRelros() called with " + bundle + ", cloned " + clonedBundle); + } + synchronized (mLock) { + // Note that in certain cases, this can be called before + // initServiceProcess() in service processes. + mSharedRelros = clonedBundle; + // Tell any listener blocked in finishLibraryLoad() about it. + mLock.notifyAll(); + } + } + + /** + * Call this to retrieve the shared RELRO sections created in this process, + * after loading all libraries. + * + * @return a new Bundle instance, or null if RELRO sharing is disabled on + * this system, or if initServiceProcess() was called previously. + */ + public Bundle getSharedRelros() { + if (DEBUG) { + Log.i(TAG, "getSharedRelros() called"); + } + synchronized (mLock) { + if (!mInBrowserProcess) { + if (DEBUG) { + Log.i(TAG, "... returning null Bundle"); + } + return null; + } + + // Return the Bundle created in finishLibraryLoad(). + if (DEBUG) { + Log.i(TAG, "... returning " + mSharedRelros); + } + return mSharedRelros; + } + } + + /** + * Call this method before loading any libraries to indicate that this + * process shall neither create or reuse shared RELRO sections. + */ + public void disableSharedRelros() { + if (DEBUG) { + Log.i(TAG, "disableSharedRelros() called"); + } + synchronized (mLock) { + ensureInitializedLocked(); + mInBrowserProcess = false; + mWaitForSharedRelros = false; + mBrowserUsesSharedRelro = false; + } + } + + /** + * Call this method before loading any libraries to indicate that this + * process is ready to reuse shared RELRO sections from another one. + * Typically used when starting service processes. + * + * @param baseLoadAddress the base library load address to use. + */ + public void initServiceProcess(long baseLoadAddress) { + if (DEBUG) { + Log.i(TAG, + String.format(Locale.US, "initServiceProcess(0x%x) called", baseLoadAddress)); + } + synchronized (mLock) { + ensureInitializedLocked(); + mInBrowserProcess = false; + mBrowserUsesSharedRelro = false; + mWaitForSharedRelros = true; + mBaseLoadAddress = baseLoadAddress; + mCurrentLoadAddress = baseLoadAddress; + } + } + + /** + * Retrieve the base load address of all shared RELRO sections. + * This also enforces the creation of shared RELRO sections in + * prepareLibraryLoad(), which can later be retrieved with getSharedRelros(). + * + * @return a common, random base load address, or 0 if RELRO sharing is + * disabled. + */ + public long getBaseLoadAddress() { + synchronized (mLock) { + ensureInitializedLocked(); + if (!mInBrowserProcess) { + Log.w(TAG, "Shared RELRO sections are disabled in this process!"); + return 0; + } + + setupBaseLoadAddressLocked(); + if (DEBUG) { + Log.i(TAG, + String.format( + Locale.US, "getBaseLoadAddress() returns 0x%x", mBaseLoadAddress)); + } + return mBaseLoadAddress; + } + } + + // Used internally to lazily setup the common random base load address. + private void setupBaseLoadAddressLocked() { + assert Thread.holdsLock(mLock); + if (mBaseLoadAddress == -1) { + mBaseLoadAddress = getRandomBaseLoadAddress(); + mCurrentLoadAddress = mBaseLoadAddress; + if (mBaseLoadAddress == 0) { + // If the random address is 0 there are issues with finding enough + // free address space, so disable RELRO shared / fixed load addresses. + Log.w(TAG, "Disabling shared RELROs due address space pressure"); + mBrowserUsesSharedRelro = false; + mWaitForSharedRelros = false; + } + } + } + + // Used for debugging only. + private void dumpBundle(Bundle bundle) { + if (DEBUG) { + Log.i(TAG, "Bundle has " + bundle.size() + " items: " + bundle); + } + } + + /** + * Use the shared RELRO section from a Bundle received form another process. + * Call this after calling setBaseLoadAddress() then loading all libraries + * with loadLibrary(). + * + * @param bundle Bundle instance generated with createSharedRelroBundle() in + * another process. + */ + private void useSharedRelrosLocked(Bundle bundle) { + assert Thread.holdsLock(mLock); + + if (DEBUG) { + Log.i(TAG, "Linker.useSharedRelrosLocked() called"); + } + + if (bundle == null) { + if (DEBUG) { + Log.i(TAG, "null bundle!"); + } + return; + } + + if (mLoadedLibraries == null) { + if (DEBUG) { + Log.i(TAG, "No libraries loaded!"); + } + return; + } + + if (DEBUG) { + dumpBundle(bundle); + } + HashMap<String, LibInfo> relroMap = createLibInfoMapFromBundle(bundle); + + // Apply the RELRO section to all libraries that were already loaded. + for (Map.Entry<String, LibInfo> entry : relroMap.entrySet()) { + String libName = entry.getKey(); + LibInfo libInfo = entry.getValue(); + if (!nativeUseSharedRelro(libName, libInfo)) { + Log.w(TAG, "Could not use shared RELRO section for " + libName); + } else { + if (DEBUG) { + Log.i(TAG, "Using shared RELRO section for " + libName); + } + } + } + + // In service processes, close all file descriptors from the map now. + if (!mInBrowserProcess) { + closeLibInfoMap(relroMap); + } + + if (DEBUG) { + Log.i(TAG, "Linker.useSharedRelrosLocked() exiting"); + } + } + + /** + * Implements loading a native shared library with the Chromium linker. + * + * Load a native shared library with the Chromium linker. If the zip file + * is not null, the shared library must be uncompressed and page aligned + * inside the zipfile. Note the crazy linker treats libraries and files as + * equivalent, so you can only open one library in a given zip file. The + * library must not be the Chromium linker library. + * + * @param libFilePath The path of the library (possibly in the zip file). + * @param isFixedAddressPermitted If true, uses a fixed load address if one was + * supplied, otherwise ignores the fixed address and loads wherever available. + */ + void loadLibraryImpl(String libFilePath, boolean isFixedAddressPermitted) { + if (DEBUG) { + Log.i(TAG, "loadLibraryImpl: " + libFilePath + ", " + isFixedAddressPermitted); + } + synchronized (mLock) { + ensureInitializedLocked(); + + // Security: Ensure prepareLibraryLoad() was called before. + // In theory, this can be done lazily here, but it's more consistent + // to use a pair of functions (i.e. prepareLibraryLoad() + finishLibraryLoad()) + // that wrap all calls to loadLibrary() in the library loader. + assert mPrepareLibraryLoadCalled; + + if (mLoadedLibraries == null) { + mLoadedLibraries = new HashMap<String, LibInfo>(); + } + + if (mLoadedLibraries.containsKey(libFilePath)) { + if (DEBUG) { + Log.i(TAG, "Not loading " + libFilePath + " twice"); + } + return; + } + + LibInfo libInfo = new LibInfo(); + long loadAddress = 0; + if (isFixedAddressPermitted) { + if ((mInBrowserProcess && mBrowserUsesSharedRelro) || mWaitForSharedRelros) { + // Load the library at a fixed address. + loadAddress = mCurrentLoadAddress; + + // For multiple libraries, ensure we stay within reservation range. + if (loadAddress > mBaseLoadAddress + ADDRESS_SPACE_RESERVATION) { + String errorMessage = + "Load address outside reservation, for: " + libFilePath; + Log.e(TAG, errorMessage); + throw new UnsatisfiedLinkError(errorMessage); + } + } + } + + final String sharedRelRoName = libFilePath; + if (!nativeLoadLibrary(libFilePath, loadAddress, libInfo)) { + String errorMessage = "Unable to load library: " + libFilePath; + Log.e(TAG, errorMessage); + throw new UnsatisfiedLinkError(errorMessage); + } + + // Print the load address to the logcat when testing the linker. The format + // of the string is expected by the Python test_runner script as one of: + // BROWSER_LIBRARY_ADDRESS: <library-name> <address> + // RENDERER_LIBRARY_ADDRESS: <library-name> <address> + // Where <library-name> is the library name, and <address> is the hexadecimal load + // address. + if (NativeLibraries.sEnableLinkerTests) { + String tag = + mInBrowserProcess ? "BROWSER_LIBRARY_ADDRESS" : "RENDERER_LIBRARY_ADDRESS"; + Log.i(TAG, + String.format( + Locale.US, "%s: %s %x", tag, libFilePath, libInfo.mLoadAddress)); + } + + if (mInBrowserProcess) { + // Create a new shared RELRO section at the 'current' fixed load address. + if (!nativeCreateSharedRelro(sharedRelRoName, mCurrentLoadAddress, libInfo)) { + Log.w(TAG, + String.format(Locale.US, "Could not create shared RELRO for %s at %x", + libFilePath, mCurrentLoadAddress)); + } else { + if (DEBUG) { + Log.i(TAG, + String.format(Locale.US, "Created shared RELRO for %s at %x: %s", + sharedRelRoName, mCurrentLoadAddress, libInfo.toString())); + } + } + } + + if (loadAddress != 0 && mCurrentLoadAddress != 0) { + // Compute the next current load address. If mCurrentLoadAddress + // is not 0, this is an explicit library load address. Otherwise, + // this is an explicit load address for relocated RELRO sections + // only. + mCurrentLoadAddress = + libInfo.mLoadAddress + libInfo.mLoadSize + BREAKPAD_GUARD_REGION_BYTES; + } + + mLoadedLibraries.put(sharedRelRoName, libInfo); + if (DEBUG) { + Log.i(TAG, "Library details " + libInfo.toString()); + } + } + } + + /** + * Record information for a given library. + * IMPORTANT: Native code knows about this class's fields, so + * don't change them without modifying the corresponding C++ sources. + * Also, the LibInfo instance owns the shared RELRO file descriptor. + */ + private static class LibInfo implements Parcelable { + LibInfo() {} + + // from Parcelable + LibInfo(Parcel in) { + mLoadAddress = in.readLong(); + mLoadSize = in.readLong(); + mRelroStart = in.readLong(); + mRelroSize = in.readLong(); + ParcelFileDescriptor fd = ParcelFileDescriptor.CREATOR.createFromParcel(in); + // If CreateSharedRelro fails, the OS file descriptor will be -1 and |fd| will be null. + if (fd != null) { + mRelroFd = fd.detachFd(); + } + } + + public void close() { + if (mRelroFd >= 0) { + StreamUtil.closeQuietly(ParcelFileDescriptor.adoptFd(mRelroFd)); + mRelroFd = -1; + } + } + + // from Parcelable + @Override + public void writeToParcel(Parcel out, int flags) { + if (mRelroFd >= 0) { + out.writeLong(mLoadAddress); + out.writeLong(mLoadSize); + out.writeLong(mRelroStart); + out.writeLong(mRelroSize); + try { + ParcelFileDescriptor fd = ParcelFileDescriptor.fromFd(mRelroFd); + fd.writeToParcel(out, 0); + fd.close(); + } catch (java.io.IOException e) { + Log.e(TAG, "Can't write LibInfo file descriptor to parcel", e); + } + } + } + + // from Parcelable + @Override + public int describeContents() { + return Parcelable.CONTENTS_FILE_DESCRIPTOR; + } + + // from Parcelable + public static final Parcelable.Creator<LibInfo> CREATOR = + new Parcelable.Creator<LibInfo>() { + @Override + public LibInfo createFromParcel(Parcel in) { + return new LibInfo(in); + } + + @Override + public LibInfo[] newArray(int size) { + return new LibInfo[size]; + } + }; + + // IMPORTANT: Don't change these fields without modifying the + // native code that accesses them directly! + @AccessedByNative + public long mLoadAddress; // page-aligned library load address. + @AccessedByNative + public long mLoadSize; // page-aligned library load size. + @AccessedByNative + public long mRelroStart; // page-aligned address in memory, or 0 if none. + @AccessedByNative + public long mRelroSize; // page-aligned size in memory, or 0. + @AccessedByNative + public int mRelroFd = -1; // shared RELRO file descriptor, or -1 + } + + // Create a Bundle from a map of LibInfo objects. + private Bundle createBundleFromLibInfoMap(HashMap<String, LibInfo> map) { + Bundle bundle = new Bundle(map.size()); + for (Map.Entry<String, LibInfo> entry : map.entrySet()) { + bundle.putParcelable(entry.getKey(), entry.getValue()); + } + return bundle; + } + + // Create a new LibInfo map from a Bundle. + private HashMap<String, LibInfo> createLibInfoMapFromBundle(Bundle bundle) { + HashMap<String, LibInfo> map = new HashMap<String, LibInfo>(); + for (String library : bundle.keySet()) { + LibInfo libInfo = bundle.getParcelable(library); + map.put(library, libInfo); + } + return map; + } + + // Call the close() method on all values of a LibInfo map. + private void closeLibInfoMap(HashMap<String, LibInfo> map) { + for (Map.Entry<String, LibInfo> entry : map.entrySet()) { + entry.getValue().close(); + } + } + + /** + * Move activity from the native thread to the main UI thread. + * Called from native code on its own thread. Posts a callback from + * the UI thread back to native code. + * + * @param opaque Opaque argument. + */ + @CalledByNative + @MainDex + private static void postCallbackOnMainThread(final long opaque) { + ThreadUtils.postOnUiThread(new Runnable() { + @Override + public void run() { + nativeRunCallbackOnUiThread(opaque); + } + }); + } + + /** + * Native method to run callbacks on the main UI thread. + * Supplied by the crazy linker and called by postCallbackOnMainThread. + * + * @param opaque Opaque crazy linker arguments. + */ + private static native void nativeRunCallbackOnUiThread(long opaque); + + /** + * Native method used to load a library. + * + * @param library Platform specific library name (e.g. libfoo.so) + * @param loadAddress Explicit load address, or 0 for randomized one. + * @param libInfo If not null, the mLoadAddress and mLoadSize fields + * of this LibInfo instance will set on success. + * @return true for success, false otherwise. + */ + private static native boolean nativeLoadLibrary( + String library, long loadAddress, LibInfo libInfo); + + /** + * Native method used to add a zip archive or APK to the search path + * for native libraries. Allows loading directly from it. + * + * @param zipfilePath Path of the zip file containing the libraries. + * @return true for success, false otherwise. + */ + private static native boolean nativeAddZipArchivePath(String zipFilePath); + + /** + * Native method used to create a shared RELRO section. + * If the library was already loaded at the same address using + * nativeLoadLibrary(), this creates the RELRO for it. Otherwise, + * this loads a new temporary library at the specified address, + * creates and extracts the RELRO section from it, then unloads it. + * + * @param library Library name. + * @param loadAddress load address, which can be different from the one + * used to load the library in the current process! + * @param libInfo libInfo instance. On success, the mRelroStart, mRelroSize + * and mRelroFd will be set. + * @return true on success, false otherwise. + */ + private static native boolean nativeCreateSharedRelro( + String library, long loadAddress, LibInfo libInfo); + + /** + * Native method used to use a shared RELRO section. + * + * @param library Library name. + * @param libInfo A LibInfo instance containing valid RELRO information + * @return true on success. + */ + private static native boolean nativeUseSharedRelro(String library, LibInfo libInfo); + + /** + * Return a random address that should be free to be mapped with the given size. + * Maps an area large enough for the largest library we might attempt to load, + * and if successful then unmaps it and returns the address of the area allocated + * by the system (with ASLR). The idea is that this area should remain free of + * other mappings until we map our library into it. + * + * @return address to pass to future mmap, or 0 on error. + */ + private static native long nativeGetRandomBaseLoadAddress(); +}
diff --git a/src/base/android/java/src/org/chromium/base/library_loader/LoaderErrors.java b/src/base/android/java/src/org/chromium/base/library_loader/LoaderErrors.java new file mode 100644 index 0000000..2b94370 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/library_loader/LoaderErrors.java
@@ -0,0 +1,16 @@ +// Copyright 2014 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. + +package org.chromium.base.library_loader; + +/** + * These are the possible failures from the LibraryLoader + */ +public class LoaderErrors { + public static final int LOADER_ERROR_NORMAL_COMPLETION = 0; + public static final int LOADER_ERROR_FAILED_TO_REGISTER_JNI = 1; + public static final int LOADER_ERROR_NATIVE_LIBRARY_LOAD_FAILED = 2; + public static final int LOADER_ERROR_NATIVE_LIBRARY_WRONG_VERSION = 3; + public static final int LOADER_ERROR_NATIVE_STARTUP_FAILED = 4; +}
diff --git a/src/base/android/java/src/org/chromium/base/library_loader/NativeLibraryPreloader.java b/src/base/android/java/src/org/chromium/base/library_loader/NativeLibraryPreloader.java new file mode 100644 index 0000000..6f8008d --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/library_loader/NativeLibraryPreloader.java
@@ -0,0 +1,20 @@ +// Copyright 2016 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. + +package org.chromium.base.library_loader; + +import android.content.Context; + +/** + * This is interface to preload the native library before calling System.loadLibrary. + * + * Preloading shouldn't call System.loadLibrary() or otherwise cause any Chromium + * code to be run, because it can be called before Chromium command line is known. + * It can however open the library via dlopen() or android_dlopen_ext() so that + * dlopen() later called by System.loadLibrary() becomes a noop. This is what the + * only subclass (MonochromeLibraryPreloader) is doing. + */ +public abstract class NativeLibraryPreloader { + public abstract int loadLibrary(Context context); +}
diff --git a/src/base/android/java/src/org/chromium/base/library_loader/ProcessInitException.java b/src/base/android/java/src/org/chromium/base/library_loader/ProcessInitException.java new file mode 100644 index 0000000..1066675 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/library_loader/ProcessInitException.java
@@ -0,0 +1,35 @@ +// Copyright 2014 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. + +package org.chromium.base.library_loader; + +/** + * The exception that is thrown when the intialization of a process was failed. + */ +public class ProcessInitException extends Exception { + private int mErrorCode = LoaderErrors.LOADER_ERROR_NORMAL_COMPLETION; + + /** + * @param errorCode This will be one of the LoaderErrors error codes. + */ + public ProcessInitException(int errorCode) { + mErrorCode = errorCode; + } + + /** + * @param errorCode This will be one of the LoaderErrors error codes. + * @param throwable The wrapped throwable obj. + */ + public ProcessInitException(int errorCode, Throwable throwable) { + super(null, throwable); + mErrorCode = errorCode; + } + + /** + * Return the error code. + */ + public int getErrorCode() { + return mErrorCode; + } +}
diff --git a/src/base/android/java/src/org/chromium/base/memory/MemoryPressureCallback.java b/src/base/android/java/src/org/chromium/base/memory/MemoryPressureCallback.java new file mode 100644 index 0000000..258aa0b --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/memory/MemoryPressureCallback.java
@@ -0,0 +1,15 @@ +// Copyright 2018 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. + +package org.chromium.base.memory; + +import org.chromium.base.MemoryPressureLevel; + +/** + * Memory pressure callback interface. + */ +@FunctionalInterface +public interface MemoryPressureCallback { + public void onPressure(@MemoryPressureLevel int pressure); +}
diff --git a/src/base/android/java/src/org/chromium/base/memory/MemoryPressureMonitor.java b/src/base/android/java/src/org/chromium/base/memory/MemoryPressureMonitor.java new file mode 100644 index 0000000..c8af484 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/memory/MemoryPressureMonitor.java
@@ -0,0 +1,301 @@ +// Copyright 2018 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. + +package org.chromium.base.memory; + +import android.app.ActivityManager; +import android.content.ComponentCallbacks2; +import android.content.res.Configuration; +import android.os.Build; +import android.os.SystemClock; + +import org.chromium.base.ContextUtils; +import org.chromium.base.MemoryPressureLevel; +import org.chromium.base.MemoryPressureListener; +import org.chromium.base.Supplier; +import org.chromium.base.ThreadUtils; +import org.chromium.base.VisibleForTesting; +import org.chromium.base.annotations.MainDex; +import org.chromium.base.metrics.CachedMetrics; + +import java.util.concurrent.TimeUnit; + +/** + * This class monitors memory pressure and reports it to the native side. + * Even though there can be other callbacks besides MemoryPressureListener (which reports + * pressure to the native side, and is added implicitly), the class is designed to suite + * needs of native MemoryPressureListeners. + * + * There are two groups of MemoryPressureListeners: + * + * 1. Stateless, i.e. ones that simply free memory (caches, etc.) in response to memory + * pressure. These listeners need to be called periodically (to have effect), but not + * too frequently (to avoid regressing performance too much). + * + * 2. Stateful, i.e. ones that change their behavior based on the last received memory + * pressure (in addition to freeing memory). These listeners need to know when the + * pressure subsides, i.e. they need to be notified about CRITICAL->MODERATE changes. + * + * Android notifies about memory pressure through onTrimMemory() / onLowMemory() callbacks + * from ComponentCallbacks2, but these are unreliable (e.g. called too early, called just + * once, not called when memory pressure subsides, etc., see https://crbug.com/813909 for + * more examples). + * + * There is also ActivityManager.getMyMemoryState() API which returns current pressure for + * the calling process. It has its caveats, for example it can't be called from isolated + * processes (renderers). Plus we don't want to poll getMyMemoryState() unnecessarily, for + * example there is no reason to poll it when Chrome is in the background. + * + * This class implements the following principles: + * + * 1. Throttle pressure signals sent to callbacks. + * Callbacks are called at most once during throttling interval. If same pressure is + * reported several times during the interval, all reports except the first one are + * ignored. + * + * 2. Always report changes in pressure. + * If pressure changes during the interval, the change is not ignored, but delayed + * until the end of the interval. + * + * 3. Poll on CRITICAL memory pressure. + * Once CRITICAL pressure is reported, getMyMemoryState API is used to periodically + * query pressure until it subsides (becomes non-CRITICAL). + * + * Zooming out, the class is used as follows: + * + * 1. Only the browser process / WebView process poll, and it only polls when it makes + * sense to do so (when Chrome is in the foreground / there are WebView instances + * around). + * + * 2. Services (GPU, renderers) don't poll, instead they get additional pressure signals + * from the main process. + * + * NOTE: This class should only be used on UiThread as defined by ThreadUtils (which is + * Android main thread for Chrome, but can be some other thread for WebView). + */ +@MainDex +public class MemoryPressureMonitor { + private static final int DEFAULT_THROTTLING_INTERVAL_MS = 60 * 1000; + + private final int mThrottlingIntervalMs; + + // Pressure reported to callbacks in the current throttling interval. + private @MemoryPressureLevel int mLastReportedPressure = MemoryPressureLevel.NONE; + + // Pressure received (but not reported) during the current throttling interval, + // or null if no pressure was received. + private @MemoryPressureLevel Integer mThrottledPressure; + + // Whether we need to throttle pressure signals. + private boolean mIsInsideThrottlingInterval; + + private boolean mPollingEnabled; + + // Changed by tests. + private Supplier<Integer> mCurrentPressureSupplier = + MemoryPressureMonitor::getCurrentMemoryPressure; + + // Changed by tests. + private MemoryPressureCallback mReportingCallback = + MemoryPressureListener::notifyMemoryPressure; + + private final Runnable mThrottlingIntervalTask = this ::onThrottlingIntervalFinished; + + // ActivityManager.getMyMemoryState() time histograms, recorded by getCurrentMemoryPressure(). + // Using Count1MHistogramSample because TimesHistogramSample doesn't support microsecond + // precision. + private static final CachedMetrics.Count1MHistogramSample sGetMyMemoryStateSucceededTime = + new CachedMetrics.Count1MHistogramSample( + "Android.MemoryPressureMonitor.GetMyMemoryState.Succeeded.Time"); + private static final CachedMetrics.Count1MHistogramSample sGetMyMemoryStateFailedTime = + new CachedMetrics.Count1MHistogramSample( + "Android.MemoryPressureMonitor.GetMyMemoryState.Failed.Time"); + + // The only instance. + public static final MemoryPressureMonitor INSTANCE = + new MemoryPressureMonitor(DEFAULT_THROTTLING_INTERVAL_MS); + + @VisibleForTesting + protected MemoryPressureMonitor(int throttlingIntervalMs) { + mThrottlingIntervalMs = throttlingIntervalMs; + } + + /** + * Starts listening to ComponentCallbacks2. + */ + public void registerComponentCallbacks() { + ThreadUtils.assertOnUiThread(); + + ContextUtils.getApplicationContext().registerComponentCallbacks(new ComponentCallbacks2() { + @Override + public void onTrimMemory(int level) { + Integer pressure = memoryPressureFromTrimLevel(level); + if (pressure != null) { + notifyPressure(pressure); + } + } + + @Override + public void onLowMemory() { + notifyPressure(MemoryPressureLevel.CRITICAL); + } + + @Override + public void onConfigurationChanged(Configuration configuration) {} + }); + } + + /** + * Enables memory pressure polling. + * See class comment for specifics. This method also does a single pressure check to get + * the current pressure. + */ + public void enablePolling() { + ThreadUtils.assertOnUiThread(); + if (mPollingEnabled) return; + + mPollingEnabled = true; + if (!mIsInsideThrottlingInterval) { + reportCurrentPressure(); + } + } + + /** + * Disables memory pressure polling. + */ + public void disablePolling() { + ThreadUtils.assertOnUiThread(); + if (!mPollingEnabled) return; + + mPollingEnabled = false; + } + + /** + * Notifies the class about change in memory pressure. + * Note that |pressure| might get throttled or delayed, i.e. calling this method doesn't + * necessarily call the callbacks. See the class comment. + */ + public void notifyPressure(@MemoryPressureLevel int pressure) { + ThreadUtils.assertOnUiThread(); + + if (mIsInsideThrottlingInterval) { + // We've already reported during this interval. Save |pressure| and act on + // it later, when the interval finishes. + mThrottledPressure = pressure; + return; + } + + reportPressure(pressure); + } + + /** + * Last pressure that was reported to MemoryPressureListener. + * Returns MemoryPressureLevel.NONE if nothing was reported yet. + */ + public @MemoryPressureLevel int getLastReportedPressure() { + ThreadUtils.assertOnUiThread(); + return mLastReportedPressure; + } + + private void reportPressure(@MemoryPressureLevel int pressure) { + assert !mIsInsideThrottlingInterval : "Can't report pressure when throttling."; + + startThrottlingInterval(); + + mLastReportedPressure = pressure; + mReportingCallback.onPressure(pressure); + } + + private void onThrottlingIntervalFinished() { + mIsInsideThrottlingInterval = false; + + // If there was a pressure change during the interval, report it. + if (mThrottledPressure != null && mLastReportedPressure != mThrottledPressure) { + int throttledPressure = mThrottledPressure; + mThrottledPressure = null; + reportPressure(throttledPressure); + return; + } + + // The pressure didn't change during the interval. Report current pressure + // (starting a new interval) if we need to. + if (mPollingEnabled && mLastReportedPressure == MemoryPressureLevel.CRITICAL) { + reportCurrentPressure(); + } + } + + private void reportCurrentPressure() { + Integer pressure = mCurrentPressureSupplier.get(); + if (pressure != null) { + reportPressure(pressure); + } + } + + private void startThrottlingInterval() { + ThreadUtils.postOnUiThreadDelayed(mThrottlingIntervalTask, mThrottlingIntervalMs); + mIsInsideThrottlingInterval = true; + } + + @VisibleForTesting + public void setCurrentPressureSupplierForTesting(Supplier<Integer> supplier) { + mCurrentPressureSupplier = supplier; + } + + @VisibleForTesting + public void setReportingCallbackForTesting(MemoryPressureCallback callback) { + mReportingCallback = callback; + } + + /** + * Queries current memory pressure. + * Returns null if the pressure couldn't be determined. + */ + private static @MemoryPressureLevel Integer getCurrentMemoryPressure() { + long startNanos = elapsedRealtimeNanos(); + try { + ActivityManager.RunningAppProcessInfo processInfo = + new ActivityManager.RunningAppProcessInfo(); + ActivityManager.getMyMemoryState(processInfo); + recordRealtimeNanosDuration(sGetMyMemoryStateSucceededTime, startNanos); + return memoryPressureFromTrimLevel(processInfo.lastTrimLevel); + } catch (Exception e) { + // Defensively catch all exceptions, just in case. + recordRealtimeNanosDuration(sGetMyMemoryStateFailedTime, startNanos); + return null; + } + } + + private static void recordRealtimeNanosDuration( + CachedMetrics.Count1MHistogramSample histogram, long startNanos) { + // We're using Count1MHistogram, so we need to calculate duration in microseconds + long durationUs = TimeUnit.NANOSECONDS.toMicros(elapsedRealtimeNanos() - startNanos); + // record() takes int, so we need to clamp. + histogram.record((int) Math.min(durationUs, Integer.MAX_VALUE)); + } + + private static long elapsedRealtimeNanos() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + return SystemClock.elapsedRealtimeNanos(); + } else { + return SystemClock.elapsedRealtime() * 1000000; + } + } + + /** + * Maps ComponentCallbacks2.TRIM_* value to MemoryPressureLevel. + * Returns null if |level| couldn't be mapped and should be ignored. + */ + @VisibleForTesting + public static @MemoryPressureLevel Integer memoryPressureFromTrimLevel(int level) { + if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE + || level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) { + return MemoryPressureLevel.CRITICAL; + } else if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) { + // Don't notify on TRIM_MEMORY_UI_HIDDEN, since this class only + // dispatches actionable memory pressure signals to native. + return MemoryPressureLevel.MODERATE; + } + return null; + } +}
diff --git a/src/base/android/java/src/org/chromium/base/memory/MemoryPressureUma.java b/src/base/android/java/src/org/chromium/base/memory/MemoryPressureUma.java new file mode 100644 index 0000000..dc90f57 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/memory/MemoryPressureUma.java
@@ -0,0 +1,113 @@ +// Copyright 2018 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. + +package org.chromium.base.memory; + +import android.content.ComponentCallbacks2; +import android.content.res.Configuration; +import android.support.annotation.IntDef; + +import org.chromium.base.ContextUtils; +import org.chromium.base.ThreadUtils; +import org.chromium.base.metrics.RecordHistogram; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Centralizes UMA data collection for Android-specific memory conditions. + */ +public class MemoryPressureUma implements ComponentCallbacks2 { + @IntDef({ + Notification.UNKNOWN_TRIM_LEVEL, Notification.TRIM_MEMORY_COMPLETE, + Notification.TRIM_MEMORY_MODERATE, Notification.TRIM_MEMORY_BACKGROUND, + Notification.TRIM_MEMORY_UI_HIDDEN, Notification.TRIM_MEMORY_RUNNING_CRITICAL, + Notification.TRIM_MEMORY_RUNNING_LOW, Notification.TRIM_MEMORY_RUNNING_MODERATE, + Notification.ON_LOW_MEMORY, Notification.NOTIFICATION_MAX, + }) + @Retention(RetentionPolicy.SOURCE) + private @interface Notification { + // WARNING: These values are persisted to logs. Entries should not be + // renumbered and numeric values should never be reused. + // Keep in sync with "Android.MemoryPressureNotification" UMA enum. + int UNKNOWN_TRIM_LEVEL = 0; + int TRIM_MEMORY_COMPLETE = 1; + int TRIM_MEMORY_MODERATE = 2; + int TRIM_MEMORY_BACKGROUND = 3; + int TRIM_MEMORY_UI_HIDDEN = 4; + int TRIM_MEMORY_RUNNING_CRITICAL = 5; + int TRIM_MEMORY_RUNNING_LOW = 6; + int TRIM_MEMORY_RUNNING_MODERATE = 7; + int ON_LOW_MEMORY = 8; + + // Must be the last one. + int NOTIFICATION_MAX = 9; + } + + private final String mHistogramName; + + private static MemoryPressureUma sInstance; + + public static void initializeForBrowser() { + initializeInstance("Browser"); + } + + public static void initializeForChildService() { + initializeInstance("ChildService"); + } + + private static void initializeInstance(String processType) { + ThreadUtils.assertOnUiThread(); + assert sInstance == null; + sInstance = new MemoryPressureUma(processType); + ContextUtils.getApplicationContext().registerComponentCallbacks(sInstance); + } + + private MemoryPressureUma(String processType) { + mHistogramName = "Android.MemoryPressureNotification." + processType; + } + + @Override + public void onLowMemory() { + record(Notification.ON_LOW_MEMORY); + } + + @Override + public void onTrimMemory(int level) { + switch (level) { + case TRIM_MEMORY_COMPLETE: + record(Notification.TRIM_MEMORY_COMPLETE); + break; + case TRIM_MEMORY_MODERATE: + record(Notification.TRIM_MEMORY_MODERATE); + break; + case TRIM_MEMORY_BACKGROUND: + record(Notification.TRIM_MEMORY_BACKGROUND); + break; + case TRIM_MEMORY_UI_HIDDEN: + record(Notification.TRIM_MEMORY_UI_HIDDEN); + break; + case TRIM_MEMORY_RUNNING_CRITICAL: + record(Notification.TRIM_MEMORY_RUNNING_CRITICAL); + break; + case TRIM_MEMORY_RUNNING_LOW: + record(Notification.TRIM_MEMORY_RUNNING_LOW); + break; + case TRIM_MEMORY_RUNNING_MODERATE: + record(Notification.TRIM_MEMORY_RUNNING_MODERATE); + break; + default: + record(Notification.UNKNOWN_TRIM_LEVEL); + break; + } + } + + @Override + public void onConfigurationChanged(Configuration configuration) {} + + private void record(@Notification int notification) { + RecordHistogram.recordEnumeratedHistogram( + mHistogramName, notification, Notification.NOTIFICATION_MAX); + } +}
diff --git a/src/base/android/java/src/org/chromium/base/metrics/CachedMetrics.java b/src/base/android/java/src/org/chromium/base/metrics/CachedMetrics.java new file mode 100644 index 0000000..ba03e51 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/metrics/CachedMetrics.java
@@ -0,0 +1,307 @@ +// Copyright 2016 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. + +package org.chromium.base.metrics; + +import org.chromium.base.library_loader.LibraryLoader; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * Utility classes for recording UMA metrics before the native library + * may have been loaded. Metrics are cached until the library is known + * to be loaded, then committed to the MetricsService all at once. + */ +public class CachedMetrics { + /** + * Base class for cached metric objects. Subclasses are expected to call + * addToCache() when some metric state gets recorded that requires a later + * commit operation when the native library is loaded. + */ + private abstract static class CachedMetric { + private static final List<CachedMetric> sMetrics = new ArrayList<CachedMetric>(); + + protected final String mName; + protected boolean mCached; + + /** + * @param name Name of the metric to record. + */ + protected CachedMetric(String name) { + mName = name; + } + + /** + * Adds this object to the sMetrics cache, if it hasn't been added already. + * Must be called while holding the synchronized(sMetrics) lock. + * Note: The synchronization is not done inside this function because subclasses + * need to increment their held values under lock to ensure thread-safety. + */ + protected final void addToCache() { + assert Thread.holdsLock(sMetrics); + + if (mCached) return; + sMetrics.add(this); + mCached = true; + } + + /** + * Commits the metric. Expects the native library to be loaded. + * Must be called while holding the synchronized(sMetrics) lock. + */ + protected abstract void commitAndClear(); + } + + /** + * Caches an action that will be recorded after native side is loaded. + */ + public static class ActionEvent extends CachedMetric { + private int mCount; + + public ActionEvent(String actionName) { + super(actionName); + } + + public void record() { + synchronized (CachedMetric.sMetrics) { + if (LibraryLoader.getInstance().isInitialized()) { + recordWithNative(); + } else { + mCount++; + addToCache(); + } + } + } + + private void recordWithNative() { + RecordUserAction.record(mName); + } + + @Override + protected void commitAndClear() { + while (mCount > 0) { + recordWithNative(); + mCount--; + } + } + } + + /** Caches a set of integer histogram samples. */ + public static class SparseHistogramSample extends CachedMetric { + private final List<Integer> mSamples = new ArrayList<Integer>(); + + public SparseHistogramSample(String histogramName) { + super(histogramName); + } + + public void record(int sample) { + synchronized (CachedMetric.sMetrics) { + if (LibraryLoader.getInstance().isInitialized()) { + recordWithNative(sample); + } else { + mSamples.add(sample); + addToCache(); + } + } + } + + private void recordWithNative(int sample) { + RecordHistogram.recordSparseSlowlyHistogram(mName, sample); + } + + @Override + protected void commitAndClear() { + for (Integer sample : mSamples) { + recordWithNative(sample); + } + mSamples.clear(); + } + } + + /** Caches a set of enumerated histogram samples. */ + public static class EnumeratedHistogramSample extends CachedMetric { + private final List<Integer> mSamples = new ArrayList<Integer>(); + private final int mMaxValue; + + public EnumeratedHistogramSample(String histogramName, int maxValue) { + super(histogramName); + mMaxValue = maxValue; + } + + public void record(int sample) { + synchronized (CachedMetric.sMetrics) { + if (LibraryLoader.getInstance().isInitialized()) { + recordWithNative(sample); + } else { + mSamples.add(sample); + addToCache(); + } + } + } + + private void recordWithNative(int sample) { + RecordHistogram.recordEnumeratedHistogram(mName, sample, mMaxValue); + } + + @Override + protected void commitAndClear() { + for (Integer sample : mSamples) { + recordWithNative(sample); + } + mSamples.clear(); + } + } + + /** Caches a set of times histogram samples. */ + public static class TimesHistogramSample extends CachedMetric { + private final List<Long> mSamples = new ArrayList<Long>(); + private final TimeUnit mTimeUnit; + + public TimesHistogramSample(String histogramName, TimeUnit timeUnit) { + super(histogramName); + RecordHistogram.assertTimesHistogramSupportsUnit(timeUnit); + mTimeUnit = timeUnit; + } + + public void record(long sample) { + synchronized (CachedMetric.sMetrics) { + if (LibraryLoader.getInstance().isInitialized()) { + recordWithNative(sample); + } else { + mSamples.add(sample); + addToCache(); + } + } + } + + private void recordWithNative(long sample) { + RecordHistogram.recordTimesHistogram(mName, sample, mTimeUnit); + } + + @Override + protected void commitAndClear() { + for (Long sample : mSamples) { + recordWithNative(sample); + } + mSamples.clear(); + } + } + + /** Caches a set of boolean histogram samples. */ + public static class BooleanHistogramSample extends CachedMetric { + private final List<Boolean> mSamples = new ArrayList<Boolean>(); + + public BooleanHistogramSample(String histogramName) { + super(histogramName); + } + + public void record(boolean sample) { + synchronized (CachedMetric.sMetrics) { + if (LibraryLoader.getInstance().isInitialized()) { + recordWithNative(sample); + } else { + mSamples.add(sample); + addToCache(); + } + } + } + + private void recordWithNative(boolean sample) { + RecordHistogram.recordBooleanHistogram(mName, sample); + } + + @Override + protected void commitAndClear() { + for (Boolean sample : mSamples) { + recordWithNative(sample); + } + mSamples.clear(); + } + } + + /** + * Caches a set of custom count histogram samples. + * Corresponds to UMA_HISTOGRAM_CUSTOM_COUNTS C++ macro. + */ + public static class CustomCountHistogramSample extends CachedMetric { + private final List<Integer> mSamples = new ArrayList<Integer>(); + private final int mMin; + private final int mMax; + private final int mNumBuckets; + + public CustomCountHistogramSample(String histogramName, int min, int max, int numBuckets) { + super(histogramName); + mMin = min; + mMax = max; + mNumBuckets = numBuckets; + } + + public void record(int sample) { + synchronized (CachedMetric.sMetrics) { + if (LibraryLoader.getInstance().isInitialized()) { + recordWithNative(sample); + } else { + mSamples.add(sample); + addToCache(); + } + } + } + + private void recordWithNative(int sample) { + RecordHistogram.recordCustomCountHistogram(mName, sample, mMin, mMax, mNumBuckets); + } + + @Override + protected void commitAndClear() { + for (Integer sample : mSamples) { + recordWithNative(sample); + } + mSamples.clear(); + } + } + + /** + * Caches a set of count histogram samples in range [1, 100). + * Corresponds to UMA_HISTOGRAM_COUNTS_100 C++ macro. + */ + public static class Count100HistogramSample extends CustomCountHistogramSample { + public Count100HistogramSample(String histogramName) { + super(histogramName, 1, 100, 50); + } + } + + /** + * Caches a set of count histogram samples in range [1, 1000). + * Corresponds to UMA_HISTOGRAM_COUNTS_1000 C++ macro. + */ + public static class Count1000HistogramSample extends CustomCountHistogramSample { + public Count1000HistogramSample(String histogramName) { + super(histogramName, 1, 1000, 50); + } + } + + /** + * Caches a set of count histogram samples in range [1, 1000000). + * Corresponds to UMA_HISTOGRAM_COUNTS_1M C++ macro. + */ + public static class Count1MHistogramSample extends CustomCountHistogramSample { + public Count1MHistogramSample(String histogramName) { + super(histogramName, 1, 1000000, 50); + } + } + + /** + * Calls out to native code to commit any cached histograms and events. + * Should be called once the native library has been loaded. + */ + public static void commitCachedMetrics() { + synchronized (CachedMetric.sMetrics) { + for (CachedMetric metric : CachedMetric.sMetrics) { + metric.commitAndClear(); + } + } + } +}
diff --git a/src/base/android/java/src/org/chromium/base/metrics/RecordHistogram.java b/src/base/android/java/src/org/chromium/base/metrics/RecordHistogram.java new file mode 100644 index 0000000..03267dc --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/metrics/RecordHistogram.java
@@ -0,0 +1,331 @@ +// Copyright 2014 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. + +package org.chromium.base.metrics; + +import org.chromium.base.VisibleForTesting; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.MainDex; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * Java API for recording UMA histograms. + * + * Internally, histograms objects are cached on the Java side by their pointer + * values (converted to long). This is safe to do because C++ Histogram objects + * are never freed. Caching them on the Java side prevents needing to do costly + * Java String to C++ string conversions on the C++ side during lookup. + * + * Note: the JNI calls are relatively costly - avoid calling these methods in performance-critical + * code. + */ +@JNINamespace("base::android") +@MainDex +public class RecordHistogram { + private static Throwable sDisabledBy; + private static Map<String, Long> sCache = + Collections.synchronizedMap(new HashMap<String, Long>()); + + /** + * Tests may not have native initialized, so they may need to disable metrics. The value should + * be reset after the test done, to avoid carrying over state to unrelated tests. + * + * In JUnit tests this can be done automatically using + * {@link org.chromium.chrome.browser.DisableHistogramsRule} + */ + @VisibleForTesting + public static void setDisabledForTests(boolean disabled) { + if (disabled && sDisabledBy != null) { + throw new IllegalStateException("Histograms are already disabled.", sDisabledBy); + } + sDisabledBy = disabled ? new Throwable() : null; + } + + private static long getCachedHistogramKey(String name) { + Long key = sCache.get(name); + // Note: If key is null, we don't have it cached. In that case, pass 0 + // to the native code, which gets converted to a null histogram pointer + // which will cause the native code to look up the object on the native + // side. + return (key == null ? 0 : key); + } + + /** + * Records a sample in a boolean UMA histogram of the given name. Boolean histogram has two + * buckets, corresponding to success (true) and failure (false). This is the Java equivalent of + * the UMA_HISTOGRAM_BOOLEAN C++ macro. + * @param name name of the histogram + * @param sample sample to be recorded, either true or false + */ + public static void recordBooleanHistogram(String name, boolean sample) { + if (sDisabledBy != null) return; + long key = getCachedHistogramKey(name); + long result = nativeRecordBooleanHistogram(name, key, sample); + if (result != key) sCache.put(name, result); + } + + /** + * Records a sample in an enumerated histogram of the given name and boundary. Note that + * |boundary| identifies the histogram - it should be the same at every invocation. This is the + * Java equivalent of the UMA_HISTOGRAM_ENUMERATION C++ macro. + * @param name name of the histogram + * @param sample sample to be recorded, at least 0 and at most |boundary| - 1 + * @param boundary upper bound for legal sample values - all sample values have to be strictly + * lower than |boundary| + */ + public static void recordEnumeratedHistogram(String name, int sample, int boundary) { + if (sDisabledBy != null) return; + long key = getCachedHistogramKey(name); + long result = nativeRecordEnumeratedHistogram(name, key, sample, boundary); + if (result != key) sCache.put(name, result); + } + + /** + * Records a sample in a count histogram. This is the Java equivalent of the + * UMA_HISTOGRAM_COUNTS_1M C++ macro. + * @param name name of the histogram + * @param sample sample to be recorded, at least 1 and at most 999999 + */ + public static void recordCountHistogram(String name, int sample) { + recordCustomCountHistogram(name, sample, 1, 1000000, 50); + } + + /** + * Records a sample in a count histogram. This is the Java equivalent of the + * UMA_HISTOGRAM_COUNTS_100 C++ macro. + * @param name name of the histogram + * @param sample sample to be recorded, at least 1 and at most 99 + */ + public static void recordCount100Histogram(String name, int sample) { + recordCustomCountHistogram(name, sample, 1, 100, 50); + } + + /** + * Records a sample in a count histogram. This is the Java equivalent of the + * UMA_HISTOGRAM_COUNTS_1000 C++ macro. + * @param name name of the histogram + * @param sample sample to be recorded, at least 1 and at most 999 + */ + public static void recordCount1000Histogram(String name, int sample) { + recordCustomCountHistogram(name, sample, 1, 1000, 50); + } + + /** + * Records a sample in a count histogram. This is the Java equivalent of the + * UMA_HISTOGRAM_CUSTOM_COUNTS C++ macro. + * @param name name of the histogram + * @param sample sample to be recorded, at least |min| and at most |max| - 1 + * @param min lower bound for expected sample values. It must be >= 1 + * @param max upper bounds for expected sample values + * @param numBuckets the number of buckets + */ + public static void recordCustomCountHistogram( + String name, int sample, int min, int max, int numBuckets) { + if (sDisabledBy != null) return; + long key = getCachedHistogramKey(name); + long result = nativeRecordCustomCountHistogram(name, key, sample, min, max, numBuckets); + if (result != key) sCache.put(name, result); + } + + /** + * Records a sample in a linear histogram. This is the Java equivalent for using + * base::LinearHistogram. + * @param name name of the histogram + * @param sample sample to be recorded, at least |min| and at most |max| - 1. + * @param min lower bound for expected sample values, should be at least 1. + * @param max upper bounds for expected sample values + * @param numBuckets the number of buckets + */ + public static void recordLinearCountHistogram( + String name, int sample, int min, int max, int numBuckets) { + if (sDisabledBy != null) return; + long key = getCachedHistogramKey(name); + long result = nativeRecordLinearCountHistogram(name, key, sample, min, max, numBuckets); + if (result != key) sCache.put(name, result); + } + + /** + * Records a sample in a percentage histogram. This is the Java equivalent of the + * UMA_HISTOGRAM_PERCENTAGE C++ macro. + * @param name name of the histogram + * @param sample sample to be recorded, at least 0 and at most 100. + */ + public static void recordPercentageHistogram(String name, int sample) { + if (sDisabledBy != null) return; + long key = getCachedHistogramKey(name); + long result = nativeRecordEnumeratedHistogram(name, key, sample, 101); + if (result != key) sCache.put(name, result); + } + + /** + * Records a sparse histogram. This is the Java equivalent of UmaHistogramSparse. + * @param name name of the histogram + * @param sample sample to be recorded. All values of |sample| are valid, including negative + * values. + */ + public static void recordSparseSlowlyHistogram(String name, int sample) { + if (sDisabledBy != null) return; + long key = getCachedHistogramKey(name); + long result = nativeRecordSparseHistogram(name, key, sample); + if (result != key) sCache.put(name, result); + } + + /** + * Records a sample in a histogram of times. Useful for recording short durations. This is the + * Java equivalent of the UMA_HISTOGRAM_TIMES C++ macro. + * Note that histogram samples will always be converted to milliseconds when logged. + * @param name name of the histogram + * @param duration duration to be recorded + * @param timeUnit the unit of the duration argument (must be >= MILLISECONDS) + */ + public static void recordTimesHistogram(String name, long duration, TimeUnit timeUnit) { + assertTimesHistogramSupportsUnit(timeUnit); + recordCustomTimesHistogramMilliseconds( + name, timeUnit.toMillis(duration), 1, TimeUnit.SECONDS.toMillis(10), 50); + } + + /** + * Records a sample in a histogram of times. Useful for recording medium durations. This is the + * Java equivalent of the UMA_HISTOGRAM_MEDIUM_TIMES C++ macro. + * Note that histogram samples will always be converted to milliseconds when logged. + * @param name name of the histogram + * @param duration duration to be recorded + * @param timeUnit the unit of the duration argument (must be >= MILLISECONDS) + */ + public static void recordMediumTimesHistogram(String name, long duration, TimeUnit timeUnit) { + assertTimesHistogramSupportsUnit(timeUnit); + recordCustomTimesHistogramMilliseconds( + name, timeUnit.toMillis(duration), 10, TimeUnit.MINUTES.toMillis(3), 50); + } + + /** + * Records a sample in a histogram of times. Useful for recording long durations. This is the + * Java equivalent of the UMA_HISTOGRAM_LONG_TIMES C++ macro. + * Note that histogram samples will always be converted to milliseconds when logged. + * @param name name of the histogram + * @param duration duration to be recorded + * @param timeUnit the unit of the duration argument (must be >= MILLISECONDS) + */ + public static void recordLongTimesHistogram(String name, long duration, TimeUnit timeUnit) { + assertTimesHistogramSupportsUnit(timeUnit); + recordCustomTimesHistogramMilliseconds( + name, timeUnit.toMillis(duration), 1, TimeUnit.HOURS.toMillis(1), 50); + } + + /** + * Records a sample in a histogram of times. Useful for recording long durations. This is the + * Java equivalent of the UMA_HISTOGRAM_LONG_TIMES_100 C++ macro. + * Note that histogram samples will always be converted to milliseconds when logged. + * @param name name of the histogram + * @param duration duration to be recorded + * @param timeUnit the unit of the duration argument (must be >= MILLISECONDS) + */ + public static void recordLongTimesHistogram100(String name, long duration, TimeUnit timeUnit) { + assertTimesHistogramSupportsUnit(timeUnit); + recordCustomTimesHistogramMilliseconds( + name, timeUnit.toMillis(duration), 1, TimeUnit.HOURS.toMillis(1), 100); + } + + /** + * Records a sample in a histogram of times with custom buckets. This is the Java equivalent of + * the UMA_HISTOGRAM_CUSTOM_TIMES C++ macro. + * Note that histogram samples will always be converted to milliseconds when logged. + * @param name name of the histogram + * @param duration duration to be recorded + * @param min the minimum bucket value + * @param max the maximum bucket value + * @param timeUnit the unit of the duration, min, and max arguments (must be >= MILLISECONDS) + * @param numBuckets the number of buckets + */ + public static void recordCustomTimesHistogram( + String name, long duration, long min, long max, TimeUnit timeUnit, int numBuckets) { + assertTimesHistogramSupportsUnit(timeUnit); + recordCustomTimesHistogramMilliseconds(name, timeUnit.toMillis(duration), + timeUnit.toMillis(min), timeUnit.toMillis(max), numBuckets); + } + + /** + * Records a sample in a histogram of sizes in KB. This is the Java equivalent of the + * UMA_HISTOGRAM_MEMORY_KB C++ macro. + * + * Good for sizes up to about 500MB. + * + * @param name name of the histogram. + * @param sizeInkB Sample to record in KB. + */ + public static void recordMemoryKBHistogram(String name, int sizeInKB) { + recordCustomCountHistogram(name, sizeInKB, 1000, 500000, 50); + } + + /** + * Asserts that the time unit is supported by TimesHistogram. + * @param timeUnit the unit, must be >= MILLISECONDS + */ + /* package */ static void assertTimesHistogramSupportsUnit(TimeUnit timeUnit) { + // Use extra variable, or else 'git cl format' produces weird results. + boolean supported = timeUnit != TimeUnit.NANOSECONDS && timeUnit != TimeUnit.MICROSECONDS; + assert supported : "TimesHistogram doesn't support MICROSECOND and NANOSECONDS time units. " + + "Consider using CountHistogram instead."; + } + + private static int clampToInt(long value) { + if (value > Integer.MAX_VALUE) return Integer.MAX_VALUE; + // Note: Clamping to MIN_VALUE rather than 0, to let base/ histograms code + // do its own handling of negative values in the future. + if (value < Integer.MIN_VALUE) return Integer.MIN_VALUE; + return (int) value; + } + + private static void recordCustomTimesHistogramMilliseconds( + String name, long duration, long min, long max, int numBuckets) { + if (sDisabledBy != null) return; + long key = getCachedHistogramKey(name); + // Note: Duration, min and max are clamped to int here because that's what's expected by + // the native histograms API. Callers of these functions still pass longs because that's + // the types returned by TimeUnit and System.currentTimeMillis() APIs, from which these + // values come. + assert max == clampToInt(max); + long result = nativeRecordCustomTimesHistogramMilliseconds( + name, key, clampToInt(duration), clampToInt(min), clampToInt(max), numBuckets); + if (result != key) sCache.put(name, result); + } + + /** + * Returns the number of samples recorded in the given bucket of the given histogram. + * @param name name of the histogram to look up + * @param sample the bucket containing this sample value will be looked up + */ + @VisibleForTesting + public static int getHistogramValueCountForTesting(String name, int sample) { + return nativeGetHistogramValueCountForTesting(name, sample); + } + + /** + * Returns the number of samples recorded for the given histogram. + * @param name name of the histogram to look up. + */ + @VisibleForTesting + public static int getHistogramTotalCountForTesting(String name) { + return nativeGetHistogramTotalCountForTesting(name); + } + + private static native long nativeRecordCustomTimesHistogramMilliseconds( + String name, long key, int duration, int min, int max, int numBuckets); + + private static native long nativeRecordBooleanHistogram(String name, long key, boolean sample); + private static native long nativeRecordEnumeratedHistogram( + String name, long key, int sample, int boundary); + private static native long nativeRecordCustomCountHistogram( + String name, long key, int sample, int min, int max, int numBuckets); + private static native long nativeRecordLinearCountHistogram( + String name, long key, int sample, int min, int max, int numBuckets); + private static native long nativeRecordSparseHistogram(String name, long key, int sample); + + private static native int nativeGetHistogramValueCountForTesting(String name, int sample); + private static native int nativeGetHistogramTotalCountForTesting(String name); +}
diff --git a/src/base/android/java/src/org/chromium/base/metrics/RecordUserAction.java b/src/base/android/java/src/org/chromium/base/metrics/RecordUserAction.java new file mode 100644 index 0000000..0d2ba54 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/metrics/RecordUserAction.java
@@ -0,0 +1,85 @@ +// Copyright 2015 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. + +package org.chromium.base.metrics; + +import org.chromium.base.ThreadUtils; +import org.chromium.base.VisibleForTesting; +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; + +/** + * Java API for recording UMA actions. + * + * WARNINGS: + * JNI calls are relatively costly - avoid using in performance-critical code. + * + * We use a script (extract_actions.py) to scan the source code and extract actions. A string + * literal (not a variable) must be passed to record(). + */ +@JNINamespace("base::android") +public class RecordUserAction { + private static Throwable sDisabledBy; + + /** + * Tests may not have native initialized, so they may need to disable metrics. The value should + * be reset after the test done, to avoid carrying over state to unrelated tests. + */ + @VisibleForTesting + public static void setDisabledForTests(boolean disabled) { + if (disabled && sDisabledBy != null) { + throw new IllegalStateException("UserActions are already disabled.", sDisabledBy); + } + sDisabledBy = disabled ? new Throwable() : null; + } + + public static void record(final String action) { + if (sDisabledBy != null) return; + + if (ThreadUtils.runningOnUiThread()) { + nativeRecordUserAction(action); + return; + } + + ThreadUtils.runOnUiThread(new Runnable() { + @Override + public void run() { + nativeRecordUserAction(action); + } + }); + } + + /** + * Interface to a class that receives a callback for each UserAction that is recorded. + */ + public interface UserActionCallback { + @CalledByNative("UserActionCallback") + void onActionRecorded(String action); + } + + private static long sNativeActionCallback; + + /** + * Register a callback that is executed for each recorded UserAction. + * Only one callback can be registered at a time. + * The callback has to be unregistered using removeActionCallbackForTesting(). + */ + public static void setActionCallbackForTesting(UserActionCallback callback) { + assert sNativeActionCallback == 0; + sNativeActionCallback = nativeAddActionCallbackForTesting(callback); + } + + /** + * Unregister the UserActionCallback. + */ + public static void removeActionCallbackForTesting() { + assert sNativeActionCallback != 0; + nativeRemoveActionCallbackForTesting(sNativeActionCallback); + sNativeActionCallback = 0; + } + + private static native void nativeRecordUserAction(String action); + private static native long nativeAddActionCallbackForTesting(UserActionCallback callback); + private static native void nativeRemoveActionCallbackForTesting(long callbackId); +}
diff --git a/src/base/android/java/src/org/chromium/base/metrics/StatisticsRecorderAndroid.java b/src/base/android/java/src/org/chromium/base/metrics/StatisticsRecorderAndroid.java new file mode 100644 index 0000000..bff3fae --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/metrics/StatisticsRecorderAndroid.java
@@ -0,0 +1,27 @@ +// Copyright 2016 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. + +package org.chromium.base.metrics; + +import org.chromium.base.annotations.JNINamespace; + +/** + * Java API which exposes the registered histograms on the native side as + * JSON test. + */ +@JNINamespace("base::android") +public final class StatisticsRecorderAndroid { + private StatisticsRecorderAndroid() {} + + /** + * @param verbosityLevel controls the information that should be included when dumping each of + * the histogram. + * @return All the registered histograms as JSON text. + */ + public static String toJson(@JSONVerbosityLevel int verbosityLevel) { + return nativeToJson(verbosityLevel); + } + + private static native String nativeToJson(@JSONVerbosityLevel int verbosityLevel); +} \ No newline at end of file
diff --git a/src/base/android/java/src/org/chromium/base/multidex/ChromiumMultiDexInstaller.java b/src/base/android/java/src/org/chromium/base/multidex/ChromiumMultiDexInstaller.java new file mode 100644 index 0000000..5588ec5 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/multidex/ChromiumMultiDexInstaller.java
@@ -0,0 +1,78 @@ +// Copyright 2015 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. + +package org.chromium.base.multidex; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Build; +import android.support.multidex.MultiDex; + +import org.chromium.base.ContextUtils; +import org.chromium.base.Log; +import org.chromium.base.VisibleForTesting; +import org.chromium.base.annotations.MainDex; + +/** + * Performs multidex installation for non-isolated processes. + */ +@MainDex +public class ChromiumMultiDexInstaller { + private static final String TAG = "base_multidex"; + + /** + * Suffix for the meta-data tag in the AndroidManifext.xml that determines whether loading + * secondary dexes should be skipped for a given process name. + */ + private static final String IGNORE_MULTIDEX_KEY = ".ignore_multidex"; + + /** + * Installs secondary dexes if possible/necessary. + * + * Isolated processes (e.g. renderer processes) can't load secondary dex files on + * K and below, so we don't even try in that case. + * + * In release builds of app apks (as opposed to test apks), this is a no-op because: + * - multidex isn't necessary in release builds because we run proguard there and + * thus aren't threatening to hit the dex limit; and + * - calling MultiDex.install, even in the absence of secondary dexes, causes a + * significant regression in start-up time (crbug.com/525695). + * + * @param context The application context. + */ + @VisibleForTesting + public static void install(Context context) { + // TODO(jbudorick): Back out this version check once support for K & below works. + // http://crbug.com/512357 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP + && !shouldInstallMultiDex(context)) { + Log.i(TAG, "Skipping multidex installation: not needed for process."); + } else { + MultiDex.install(context); + Log.i(TAG, "Completed multidex installation."); + } + } + + // Determines whether MultiDex should be installed for the current process. Isolated + // Processes should skip MultiDex as they can not actually access the files on disk. + // Privileged processes need ot have all of their dependencies in the MainDex for + // performance reasons. + private static boolean shouldInstallMultiDex(Context context) { + if (ContextUtils.isIsolatedProcess()) { + return false; + } + String currentProcessName = ContextUtils.getProcessName(); + PackageManager packageManager = context.getPackageManager(); + try { + ApplicationInfo appInfo = packageManager.getApplicationInfo(context.getPackageName(), + PackageManager.GET_META_DATA); + if (appInfo == null || appInfo.metaData == null) return true; + return !appInfo.metaData.getBoolean(currentProcessName + IGNORE_MULTIDEX_KEY, false); + } catch (PackageManager.NameNotFoundException e) { + return true; + } + } + +}
diff --git a/src/base/android/java/src/org/chromium/base/process_launcher/ChildConnectionAllocator.java b/src/base/android/java/src/org/chromium/base/process_launcher/ChildConnectionAllocator.java new file mode 100644 index 0000000..43ae259 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/process_launcher/ChildConnectionAllocator.java
@@ -0,0 +1,305 @@ +// Copyright 2017 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. + +package org.chromium.base.process_launcher; + +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; + +import org.chromium.base.Log; +import org.chromium.base.VisibleForTesting; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Queue; + +/** + * This class is responsible for allocating and managing connections to child + * process services. These connections are in a pool (the services are defined + * in the AndroidManifest.xml). + */ +public class ChildConnectionAllocator { + private static final String TAG = "ChildConnAllocator"; + + /** Factory interface. Used by tests to specialize created connections. */ + @VisibleForTesting + public interface ConnectionFactory { + ChildProcessConnection createConnection(Context context, ComponentName serviceName, + boolean bindToCaller, boolean bindAsExternalService, Bundle serviceBundle); + } + + /** Default implementation of the ConnectionFactory that creates actual connections. */ + private static class ConnectionFactoryImpl implements ConnectionFactory { + @Override + public ChildProcessConnection createConnection(Context context, ComponentName serviceName, + boolean bindToCaller, boolean bindAsExternalService, Bundle serviceBundle) { + return new ChildProcessConnection( + context, serviceName, bindToCaller, bindAsExternalService, serviceBundle); + } + } + + // Delay between the call to freeConnection and the connection actually beeing freed. + private static final long FREE_CONNECTION_DELAY_MILLIS = 1; + + // The handler of the thread on which all interations should happen. + private final Handler mLauncherHandler; + + // Connections to services. Indices of the array correspond to the service numbers. + private final ChildProcessConnection[] mChildProcessConnections; + + // Runnable which will be called when allocator wants to allocate a new connection, but does + // not have any more free slots. May be null. + private final Runnable mFreeSlotCallback; + private final String mPackageName; + private final String mServiceClassName; + private final boolean mBindToCaller; + private final boolean mBindAsExternalService; + private final boolean mUseStrongBinding; + + // The list of free (not bound) service indices. + private final ArrayList<Integer> mFreeConnectionIndices; + + private final Queue<Runnable> mPendingAllocations = new ArrayDeque<>(); + + private ConnectionFactory mConnectionFactory = new ConnectionFactoryImpl(); + + /** + * Factory method that retrieves the service name and number of service from the + * AndroidManifest.xml. + */ + public static ChildConnectionAllocator create(Context context, Handler launcherHandler, + Runnable freeSlotCallback, String packageName, String serviceClassName, + String numChildServicesManifestKey, boolean bindToCaller, boolean bindAsExternalService, + boolean useStrongBinding) { + int numServices = -1; + PackageManager packageManager = context.getPackageManager(); + try { + ApplicationInfo appInfo = + packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA); + if (appInfo.metaData != null) { + numServices = appInfo.metaData.getInt(numChildServicesManifestKey, -1); + } + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException("Could not get application info."); + } + + if (numServices < 0) { + throw new RuntimeException("Illegal meta data value for number of child services"); + } + + // Check that the service exists. + try { + // PackageManager#getServiceInfo() throws an exception if the service does not exist. + packageManager.getServiceInfo( + new ComponentName(packageName, serviceClassName + "0"), 0); + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException("Illegal meta data value: the child service doesn't exist"); + } + + return new ChildConnectionAllocator(launcherHandler, freeSlotCallback, packageName, + serviceClassName, bindToCaller, bindAsExternalService, useStrongBinding, + numServices); + } + + /** + * Factory method used with some tests to create an allocator with values passed in directly + * instead of being retrieved from the AndroidManifest.xml. + */ + @VisibleForTesting + public static ChildConnectionAllocator createForTest(Runnable freeSlotCallback, + String packageName, String serviceClassName, int serviceCount, boolean bindToCaller, + boolean bindAsExternalService, boolean useStrongBinding) { + return new ChildConnectionAllocator(new Handler(), freeSlotCallback, packageName, + serviceClassName, bindToCaller, bindAsExternalService, useStrongBinding, + serviceCount); + } + + private ChildConnectionAllocator(Handler launcherHandler, Runnable freeSlotCallback, + String packageName, String serviceClassName, boolean bindToCaller, + boolean bindAsExternalService, boolean useStrongBinding, int numChildServices) { + mFreeSlotCallback = freeSlotCallback; + mLauncherHandler = launcherHandler; + assert isRunningOnLauncherThread(); + mPackageName = packageName; + mServiceClassName = serviceClassName; + mBindToCaller = bindToCaller; + mBindAsExternalService = bindAsExternalService; + mUseStrongBinding = useStrongBinding; + mChildProcessConnections = new ChildProcessConnection[numChildServices]; + mFreeConnectionIndices = new ArrayList<Integer>(numChildServices); + for (int i = 0; i < numChildServices; i++) { + mFreeConnectionIndices.add(i); + } + } + + /** @return a bound connection, or null if there are no free slots. */ + public ChildProcessConnection allocate(Context context, Bundle serviceBundle, + final ChildProcessConnection.ServiceCallback serviceCallback) { + assert isRunningOnLauncherThread(); + if (mFreeConnectionIndices.isEmpty()) { + Log.d(TAG, "Ran out of services to allocate."); + return null; + } + int slot = mFreeConnectionIndices.remove(0); + assert mChildProcessConnections[slot] == null; + ComponentName serviceName = new ComponentName(mPackageName, mServiceClassName + slot); + + // Wrap the service callbacks so that: + // - we can intercept onChildProcessDied and clean-up connections + // - the callbacks are actually posted so that this method will return before the callbacks + // are called (so that the caller may set any reference to the returned connection before + // any callback logic potentially tries to access that connection). + ChildProcessConnection.ServiceCallback serviceCallbackWrapper = + new ChildProcessConnection.ServiceCallback() { + @Override + public void onChildStarted() { + assert isRunningOnLauncherThread(); + if (serviceCallback != null) { + mLauncherHandler.post(new Runnable() { + @Override + public void run() { + serviceCallback.onChildStarted(); + } + }); + } + } + + @Override + public void onChildStartFailed(final ChildProcessConnection connection) { + assert isRunningOnLauncherThread(); + if (serviceCallback != null) { + mLauncherHandler.post(new Runnable() { + @Override + public void run() { + serviceCallback.onChildStartFailed(connection); + } + }); + } + freeConnectionWithDelay(connection); + } + + @Override + public void onChildProcessDied(final ChildProcessConnection connection) { + assert isRunningOnLauncherThread(); + if (serviceCallback != null) { + mLauncherHandler.post(new Runnable() { + @Override + public void run() { + serviceCallback.onChildProcessDied(connection); + } + }); + } + freeConnectionWithDelay(connection); + } + + private void freeConnectionWithDelay(final ChildProcessConnection connection) { + // Freeing a service should be delayed. This is so that we avoid immediately + // reusing the freed service (see http://crbug.com/164069): the framework + // might keep a service process alive when it's been unbound for a short + // time. If a new connection to the same service is bound at that point, the + // process is reused and bad things happen (mostly static variables are set + // when we don't expect them to). + mLauncherHandler.postDelayed(new Runnable() { + @Override + public void run() { + free(connection); + } + }, FREE_CONNECTION_DELAY_MILLIS); + } + }; + + ChildProcessConnection connection = mConnectionFactory.createConnection( + context, serviceName, mBindToCaller, mBindAsExternalService, serviceBundle); + mChildProcessConnections[slot] = connection; + + connection.start(mUseStrongBinding, serviceCallbackWrapper); + Log.d(TAG, "Allocator allocated and bound a connection, name: %s, slot: %d", + mServiceClassName, slot); + return connection; + } + + /** Frees a connection and notifies listeners. */ + private void free(ChildProcessConnection connection) { + assert isRunningOnLauncherThread(); + + // mChildProcessConnections is relatively short (20 items at max at this point). + // We are better of iterating than caching in a map. + int slot = Arrays.asList(mChildProcessConnections).indexOf(connection); + if (slot == -1) { + Log.e(TAG, "Unable to find connection to free."); + assert false; + } else { + mChildProcessConnections[slot] = null; + assert !mFreeConnectionIndices.contains(slot); + mFreeConnectionIndices.add(slot); + Log.d(TAG, "Allocator freed a connection, name: %s, slot: %d", mServiceClassName, slot); + } + + if (mPendingAllocations.isEmpty()) return; + mPendingAllocations.remove().run(); + assert mFreeConnectionIndices.isEmpty(); + if (!mPendingAllocations.isEmpty() && mFreeSlotCallback != null) mFreeSlotCallback.run(); + } + + // Can only be called once all slots are full, ie when allocate returns null. + // The callback will be called when a slot becomes free, and should synchronous call + // allocate to take the slot. + public void queueAllocation(Runnable runnable) { + assert mFreeConnectionIndices.isEmpty(); + boolean wasEmpty = mPendingAllocations.isEmpty(); + mPendingAllocations.add(runnable); + if (wasEmpty && mFreeSlotCallback != null) mFreeSlotCallback.run(); + } + + public String getPackageName() { + return mPackageName; + } + + public boolean anyConnectionAllocated() { + return mFreeConnectionIndices.size() < mChildProcessConnections.length; + } + + public boolean isFreeConnectionAvailable() { + assert isRunningOnLauncherThread(); + return !mFreeConnectionIndices.isEmpty(); + } + + public int getNumberOfServices() { + return mChildProcessConnections.length; + } + + public boolean isConnectionFromAllocator(ChildProcessConnection connection) { + for (ChildProcessConnection existingConnection : mChildProcessConnections) { + if (existingConnection == connection) return true; + } + return false; + } + + @VisibleForTesting + public void setConnectionFactoryForTesting(ConnectionFactory connectionFactory) { + mConnectionFactory = connectionFactory; + } + + /** @return the count of connections managed by the allocator */ + @VisibleForTesting + public int allocatedConnectionsCountForTesting() { + assert isRunningOnLauncherThread(); + return mChildProcessConnections.length - mFreeConnectionIndices.size(); + } + + @VisibleForTesting + public ChildProcessConnection getChildProcessConnectionAtSlotForTesting(int slotNumber) { + return mChildProcessConnections[slotNumber]; + } + + private boolean isRunningOnLauncherThread() { + return mLauncherHandler.getLooper() == Looper.myLooper(); + } +}
diff --git a/src/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java b/src/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java new file mode 100644 index 0000000..f63408c --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java
@@ -0,0 +1,778 @@ +// Copyright 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. + +package org.chromium.base.process_launcher; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.RemoteException; + +import org.chromium.base.ChildBindingState; +import org.chromium.base.Log; +import org.chromium.base.MemoryPressureLevel; +import org.chromium.base.MemoryPressureListener; +import org.chromium.base.ThreadUtils; +import org.chromium.base.TraceEvent; +import org.chromium.base.VisibleForTesting; +import org.chromium.base.memory.MemoryPressureCallback; + +import java.util.Arrays; +import java.util.List; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.GuardedBy; + +/** + * Manages a connection between the browser activity and a child service. + */ +public class ChildProcessConnection { + private static final String TAG = "ChildProcessConn"; + private static final int NUM_BINDING_STATES = ChildBindingState.MAX_VALUE + 1; + + /** + * Used to notify the consumer about the process start. These callbacks will be invoked before + * the ConnectionCallbacks. + */ + public interface ServiceCallback { + /** + * Called when the child process has successfully started and is ready for connection + * setup. + */ + void onChildStarted(); + + /** + * Called when the child process failed to start. This can happen if the process is already + * in use by another client. The client will not receive any other callbacks after this one. + */ + void onChildStartFailed(ChildProcessConnection connection); + + /** + * Called when the service has been disconnected. whether it was stopped by the client or + * if it stopped unexpectedly (process crash). + * This is the last callback from this interface that a client will receive for a specific + * connection. + */ + void onChildProcessDied(ChildProcessConnection connection); + } + + /** + * Used to notify the consumer about the connection being established. + */ + public interface ConnectionCallback { + /** + * Called when the connection to the service is established. + * @param connection the connection object to the child process + */ + void onConnected(ChildProcessConnection connection); + } + + /** + * Delegate that ChildServiceConnection should call when the service connects/disconnects. + * These callbacks are expected to happen on a background thread. + */ + @VisibleForTesting + protected interface ChildServiceConnectionDelegate { + void onServiceConnected(IBinder service); + void onServiceDisconnected(); + } + + @VisibleForTesting + protected interface ChildServiceConnectionFactory { + ChildServiceConnection createConnection( + Intent bindIntent, int bindFlags, ChildServiceConnectionDelegate delegate); + } + + /** Interface representing a connection to the Android service. Can be mocked in unit-tests. */ + @VisibleForTesting + protected interface ChildServiceConnection { + boolean bind(); + void unbind(); + boolean isBound(); + } + + /** Implementation of ChildServiceConnection that does connect to a service. */ + private static class ChildServiceConnectionImpl + implements ChildServiceConnection, ServiceConnection { + private final Context mContext; + private final Intent mBindIntent; + private final int mBindFlags; + private final ChildServiceConnectionDelegate mDelegate; + private boolean mBound; + + private ChildServiceConnectionImpl(Context context, Intent bindIntent, int bindFlags, + ChildServiceConnectionDelegate delegate) { + mContext = context; + mBindIntent = bindIntent; + mBindFlags = bindFlags; + mDelegate = delegate; + } + + @Override + public boolean bind() { + if (!mBound) { + try { + TraceEvent.begin("ChildProcessConnection.ChildServiceConnectionImpl.bind"); + mBound = mContext.bindService(mBindIntent, this, mBindFlags); + } finally { + TraceEvent.end("ChildProcessConnection.ChildServiceConnectionImpl.bind"); + } + } + return mBound; + } + + @Override + public void unbind() { + if (mBound) { + mContext.unbindService(this); + mBound = false; + } + } + + @Override + public boolean isBound() { + return mBound; + } + + @Override + public void onServiceConnected(ComponentName className, final IBinder service) { + mDelegate.onServiceConnected(service); + } + + // Called on the main thread to notify that the child service did not disconnect gracefully. + @Override + public void onServiceDisconnected(ComponentName className) { + mDelegate.onServiceDisconnected(); + } + } + + // Global lock to protect all the fields that can be accessed outside launcher thread. + private static final Object sBindingStateLock = new Object(); + + @GuardedBy("sBindingStateLock") + private static final int[] sAllBindingStateCounts = new int[NUM_BINDING_STATES]; + + @VisibleForTesting + static void resetBindingStateCountsForTesting() { + synchronized (sBindingStateLock) { + for (int i = 0; i < NUM_BINDING_STATES; ++i) { + sAllBindingStateCounts[i] = 0; + } + } + } + + private final Handler mLauncherHandler; + private final ComponentName mServiceName; + + // Parameters passed to the child process through the service binding intent. + // If the service gets recreated by the framework the intent will be reused, so these parameters + // should be common to all processes of that type. + private final Bundle mServiceBundle; + + // Whether bindToCaller should be called on the service after setup to check that only one + // process is bound to the service. + private final boolean mBindToCaller; + + private static class ConnectionParams { + final Bundle mConnectionBundle; + final List<IBinder> mClientInterfaces; + + ConnectionParams(Bundle connectionBundle, List<IBinder> clientInterfaces) { + mConnectionBundle = connectionBundle; + mClientInterfaces = clientInterfaces; + } + } + + // This is set in start() and is used in onServiceConnected(). + private ServiceCallback mServiceCallback; + + // This is set in setupConnection() and is later used in doConnectionSetup(), after which the + // variable is cleared. Therefore this is only valid while the connection is being set up. + private ConnectionParams mConnectionParams; + + // Callback provided in setupConnection() that will communicate the result to the caller. This + // has to be called exactly once after setupConnection(), even if setup fails, so that the + // caller can free up resources associated with the setup attempt. This is set to null after the + // call. + private ConnectionCallback mConnectionCallback; + + private IChildProcessService mService; + + // Set to true when the service connection callback runs. This differs from + // mServiceConnectComplete, which tracks that the connection completed successfully. + private boolean mDidOnServiceConnected; + + // Set to true when the service connected successfully. + private boolean mServiceConnectComplete; + + // Set to true when the service disconnects, as opposed to being properly closed. This happens + // when the process crashes or gets killed by the system out-of-memory killer. + private boolean mServiceDisconnected; + + // Process ID of the corresponding child process. + private int mPid; + + // Strong binding will make the service priority equal to the priority of the activity. + private final ChildServiceConnection mStrongBinding; + + // Moderate binding will make the service priority equal to the priority of a visible process + // while the app is in the foreground. + // This is also used as the initial binding before any priorities are set. + private final ChildServiceConnection mModerateBinding; + + // Low priority binding maintained in the entire lifetime of the connection, i.e. between calls + // to start() and stop(). + private final ChildServiceConnection mWaivedBinding; + + // Refcount of bindings. + private int mStrongBindingCount; + private int mModerateBindingCount; + + // Set to true once unbind() was called. + private boolean mUnbound; + + // Binding state of this connection. + @GuardedBy("sBindingStateLock") + private @ChildBindingState int mBindingState; + + // Same as above except it no longer updates after |unbind()|. + @GuardedBy("sBindingStateLock") + private @ChildBindingState int mBindingStateCurrentOrWhenDied; + + // Indicate |kill()| was called to intentionally kill this process. + @GuardedBy("sBindingStateLock") + private boolean mKilledByUs; + + // Copy of |sAllBindingStateCounts| at the time this is unbound. + @GuardedBy("sBindingStateLock") + private int[] mAllBindingStateCountsWhenDied; + + private MemoryPressureCallback mMemoryPressureCallback; + + public ChildProcessConnection(Context context, ComponentName serviceName, boolean bindToCaller, + boolean bindAsExternalService, Bundle serviceBundle) { + this(context, serviceName, bindToCaller, bindAsExternalService, serviceBundle, + null /* connectionFactory */); + } + + @VisibleForTesting + public ChildProcessConnection(final Context context, ComponentName serviceName, + boolean bindToCaller, boolean bindAsExternalService, Bundle serviceBundle, + ChildServiceConnectionFactory connectionFactory) { + mLauncherHandler = new Handler(); + assert isRunningOnLauncherThread(); + mServiceName = serviceName; + mServiceBundle = serviceBundle != null ? serviceBundle : new Bundle(); + mServiceBundle.putBoolean(ChildProcessConstants.EXTRA_BIND_TO_CALLER, bindToCaller); + mBindToCaller = bindToCaller; + + if (connectionFactory == null) { + connectionFactory = new ChildServiceConnectionFactory() { + @Override + public ChildServiceConnection createConnection( + Intent bindIntent, int bindFlags, ChildServiceConnectionDelegate delegate) { + return new ChildServiceConnectionImpl(context, bindIntent, bindFlags, delegate); + } + }; + } + + ChildServiceConnectionDelegate delegate = new ChildServiceConnectionDelegate() { + @Override + public void onServiceConnected(final IBinder service) { + mLauncherHandler.post(new Runnable() { + @Override + public void run() { + onServiceConnectedOnLauncherThread(service); + } + }); + } + + @Override + public void onServiceDisconnected() { + mLauncherHandler.post(new Runnable() { + @Override + public void run() { + onServiceDisconnectedOnLauncherThread(); + } + }); + } + }; + + Intent intent = new Intent(); + intent.setComponent(serviceName); + if (serviceBundle != null) { + intent.putExtras(serviceBundle); + } + + int defaultFlags = Context.BIND_AUTO_CREATE + | (bindAsExternalService ? Context.BIND_EXTERNAL_SERVICE : 0); + + mModerateBinding = connectionFactory.createConnection(intent, defaultFlags, delegate); + mStrongBinding = connectionFactory.createConnection( + intent, defaultFlags | Context.BIND_IMPORTANT, delegate); + mWaivedBinding = connectionFactory.createConnection( + intent, defaultFlags | Context.BIND_WAIVE_PRIORITY, delegate); + } + + public final IChildProcessService getService() { + assert isRunningOnLauncherThread(); + return mService; + } + + public final ComponentName getServiceName() { + assert isRunningOnLauncherThread(); + return mServiceName; + } + + public boolean isConnected() { + return mService != null; + } + + /** + * @return the connection pid, or 0 if not yet connected + */ + public int getPid() { + assert isRunningOnLauncherThread(); + return mPid; + } + + /** + * Starts a connection to an IChildProcessService. This must be followed by a call to + * setupConnection() to setup the connection parameters. start() and setupConnection() are + * separate to allow to pass whatever parameters are available in start(), and complete the + * remainder addStrongBinding while reducing the connection setup latency. + * @param useStrongBinding whether a strong binding should be bound by default. If false, an + * initial moderate binding is used. + * @param serviceCallback (optional) callbacks invoked when the child process starts or fails to + * start and when the service stops. + */ + public void start(boolean useStrongBinding, ServiceCallback serviceCallback) { + try { + TraceEvent.begin("ChildProcessConnection.start"); + assert isRunningOnLauncherThread(); + assert mConnectionParams + == null : "setupConnection() called before start() in ChildProcessConnection."; + + mServiceCallback = serviceCallback; + + if (!bind(useStrongBinding)) { + Log.e(TAG, "Failed to establish the service connection."); + // We have to notify the caller so that they can free-up associated resources. + // TODO(ppi): Can we hard-fail here? + notifyChildProcessDied(); + } + } finally { + TraceEvent.end("ChildProcessConnection.start"); + } + } + + /** + * Sets-up the connection after it was started with start(). + * @param connectionBundle a bundle passed to the service that can be used to pass various + * parameters to the service + * @param clientInterfaces optional client specified interfaces that the child can use to + * communicate with the parent process + * @param connectionCallback will be called exactly once after the connection is set up or the + * setup fails + */ + public void setupConnection(Bundle connectionBundle, @Nullable List<IBinder> clientInterfaces, + ConnectionCallback connectionCallback) { + assert isRunningOnLauncherThread(); + assert mConnectionParams == null; + if (mServiceDisconnected) { + Log.w(TAG, "Tried to setup a connection that already disconnected."); + connectionCallback.onConnected(null); + return; + } + try { + TraceEvent.begin("ChildProcessConnection.setupConnection"); + mConnectionCallback = connectionCallback; + mConnectionParams = new ConnectionParams(connectionBundle, clientInterfaces); + // Run the setup if the service is already connected. If not, doConnectionSetup() will + // be called from onServiceConnected(). + if (mServiceConnectComplete) { + doConnectionSetup(); + } + } finally { + TraceEvent.end("ChildProcessConnection.setupConnection"); + } + } + + /** + * Terminates the connection to IChildProcessService, closing all bindings. It is safe to call + * this multiple times. + */ + public void stop() { + assert isRunningOnLauncherThread(); + unbind(); + notifyChildProcessDied(); + } + + public void kill() { + assert isRunningOnLauncherThread(); + IChildProcessService service = mService; + unbind(); + try { + if (service != null) service.forceKill(); + } catch (RemoteException e) { + // Intentionally ignore since we are killing it anyway. + } + synchronized (sBindingStateLock) { + mKilledByUs = true; + } + notifyChildProcessDied(); + } + + @VisibleForTesting + protected void onServiceConnectedOnLauncherThread(IBinder service) { + assert isRunningOnLauncherThread(); + // A flag from the parent class ensures we run the post-connection logic only once + // (instead of once per each ChildServiceConnection). + if (mDidOnServiceConnected) { + return; + } + try { + TraceEvent.begin("ChildProcessConnection.ChildServiceConnection.onServiceConnected"); + mDidOnServiceConnected = true; + mService = IChildProcessService.Stub.asInterface(service); + + if (mBindToCaller) { + try { + if (!mService.bindToCaller()) { + if (mServiceCallback != null) { + mServiceCallback.onChildStartFailed(this); + } + unbind(); + return; + } + } catch (RemoteException ex) { + // Do not trigger the StartCallback here, since the service is already + // dead and the onChildStopped callback will run from onServiceDisconnected(). + Log.e(TAG, "Failed to bind service to connection.", ex); + return; + } + } + + if (mServiceCallback != null) { + mServiceCallback.onChildStarted(); + } + + mServiceConnectComplete = true; + + if (mMemoryPressureCallback == null) { + final MemoryPressureCallback callback = this ::onMemoryPressure; + ThreadUtils.postOnUiThread(() -> MemoryPressureListener.addCallback(callback)); + mMemoryPressureCallback = callback; + } + + // Run the setup if the connection parameters have already been provided. If + // not, doConnectionSetup() will be called from setupConnection(). + if (mConnectionParams != null) { + doConnectionSetup(); + } + } finally { + TraceEvent.end("ChildProcessConnection.ChildServiceConnection.onServiceConnected"); + } + } + + @VisibleForTesting + protected void onServiceDisconnectedOnLauncherThread() { + assert isRunningOnLauncherThread(); + // Ensure that the disconnection logic runs only once (instead of once per each + // ChildServiceConnection). + if (mServiceDisconnected) { + return; + } + mServiceDisconnected = true; + Log.w(TAG, "onServiceDisconnected (crash or killed by oom): pid=%d", mPid); + stop(); // We don't want to auto-restart on crash. Let the browser do that. + + // If we have a pending connection callback, we need to communicate the failure to + // the caller. + if (mConnectionCallback != null) { + mConnectionCallback.onConnected(null); + mConnectionCallback = null; + } + } + + private void onSetupConnectionResult(int pid) { + mPid = pid; + assert mPid != 0 : "Child service claims to be run by a process of pid=0."; + + if (mConnectionCallback != null) { + mConnectionCallback.onConnected(this); + } + mConnectionCallback = null; + } + + /** + * Called after the connection parameters have been set (in setupConnection()) *and* a + * connection has been established (as signaled by onServiceConnected()). These two events can + * happen in any order. + */ + private void doConnectionSetup() { + try { + TraceEvent.begin("ChildProcessConnection.doConnectionSetup"); + assert mServiceConnectComplete && mService != null; + assert mConnectionParams != null; + + ICallbackInt pidCallback = new ICallbackInt.Stub() { + @Override + public void call(final int pid) { + mLauncherHandler.post(new Runnable() { + @Override + public void run() { + onSetupConnectionResult(pid); + } + }); + } + }; + try { + mService.setupConnection(mConnectionParams.mConnectionBundle, pidCallback, + mConnectionParams.mClientInterfaces); + } catch (RemoteException re) { + Log.e(TAG, "Failed to setup connection.", re); + } + mConnectionParams = null; + } finally { + TraceEvent.end("ChildProcessConnection.doConnectionSetup"); + } + } + + private boolean bind(boolean useStrongBinding) { + assert isRunningOnLauncherThread(); + assert !mUnbound; + + boolean success; + if (useStrongBinding) { + success = mStrongBinding.bind(); + } else { + mModerateBindingCount++; + success = mModerateBinding.bind(); + } + if (!success) return false; + + mWaivedBinding.bind(); + updateBindingState(); + return true; + } + + @VisibleForTesting + protected void unbind() { + assert isRunningOnLauncherThread(); + mService = null; + mConnectionParams = null; + mUnbound = true; + mStrongBinding.unbind(); + mWaivedBinding.unbind(); + mModerateBinding.unbind(); + updateBindingState(); + + synchronized (sBindingStateLock) { + mAllBindingStateCountsWhenDied = + Arrays.copyOf(sAllBindingStateCounts, NUM_BINDING_STATES); + } + + if (mMemoryPressureCallback != null) { + final MemoryPressureCallback callback = mMemoryPressureCallback; + ThreadUtils.postOnUiThread(() -> MemoryPressureListener.removeCallback(callback)); + mMemoryPressureCallback = null; + } + } + + public boolean isStrongBindingBound() { + assert isRunningOnLauncherThread(); + return mStrongBinding.isBound(); + } + + public void addStrongBinding() { + assert isRunningOnLauncherThread(); + if (!isConnected()) { + Log.w(TAG, "The connection is not bound for %d", getPid()); + return; + } + if (mStrongBindingCount == 0) { + mStrongBinding.bind(); + updateBindingState(); + } + mStrongBindingCount++; + } + + public void removeStrongBinding() { + assert isRunningOnLauncherThread(); + if (!isConnected()) { + Log.w(TAG, "The connection is not bound for %d", getPid()); + return; + } + assert mStrongBindingCount > 0; + mStrongBindingCount--; + if (mStrongBindingCount == 0) { + mStrongBinding.unbind(); + updateBindingState(); + } + } + + public boolean isModerateBindingBound() { + assert isRunningOnLauncherThread(); + return mModerateBinding.isBound(); + } + + public void addModerateBinding() { + assert isRunningOnLauncherThread(); + if (!isConnected()) { + Log.w(TAG, "The connection is not bound for %d", getPid()); + return; + } + if (mModerateBindingCount == 0) { + mModerateBinding.bind(); + updateBindingState(); + } + mModerateBindingCount++; + } + + public void removeModerateBinding() { + assert isRunningOnLauncherThread(); + if (!isConnected()) { + Log.w(TAG, "The connection is not bound for %d", getPid()); + return; + } + assert mModerateBindingCount > 0; + mModerateBindingCount--; + if (mModerateBindingCount == 0) { + mModerateBinding.unbind(); + updateBindingState(); + } + } + + /** + * @return true if the connection is bound and only bound with the waived binding or if the + * connection is unbound and was only bound with the waived binding when it disconnected. + */ + public @ChildBindingState int bindingStateCurrentOrWhenDied() { + // WARNING: this method can be called from a thread other than the launcher thread. + // Note that it returns the current waived bound only state and is racy. This not really + // preventable without changing the caller's API, short of blocking. + synchronized (sBindingStateLock) { + return mBindingStateCurrentOrWhenDied; + } + } + + /** + * @return true if the connection is intentionally killed by calling kill(). + */ + public boolean isKilledByUs() { + // WARNING: this method can be called from a thread other than the launcher thread. + // Note that it returns the current waived bound only state and is racy. This not really + // preventable without changing the caller's API, short of blocking. + synchronized (sBindingStateLock) { + return mKilledByUs; + } + } + + /** + * Returns the binding state of remaining processes, excluding the current connection. + * + * If the current process is dead then returns the binding state of all processes when it died. + * Otherwise returns current state. + */ + public int[] remainingBindingStateCountsCurrentOrWhenDied() { + // WARNING: this method can be called from a thread other than the launcher thread. + // Note that it returns the current waived bound only state and is racy. This not really + // preventable without changing the caller's API, short of blocking. + synchronized (sBindingStateLock) { + if (mAllBindingStateCountsWhenDied != null) { + return Arrays.copyOf(mAllBindingStateCountsWhenDied, NUM_BINDING_STATES); + } + + int[] counts = Arrays.copyOf(sAllBindingStateCounts, NUM_BINDING_STATES); + // If current process is still bound then remove it from the counts. + if (mBindingState != ChildBindingState.UNBOUND) { + assert counts[mBindingState] > 0; + counts[mBindingState]--; + } + return counts; + } + } + + // Should be called any binding is bound or unbound. + private void updateBindingState() { + int newBindingState; + if (mUnbound) { + newBindingState = ChildBindingState.UNBOUND; + } else if (mStrongBinding.isBound()) { + newBindingState = ChildBindingState.STRONG; + } else if (mModerateBinding.isBound()) { + newBindingState = ChildBindingState.MODERATE; + } else { + assert mWaivedBinding.isBound(); + newBindingState = ChildBindingState.WAIVED; + } + + synchronized (sBindingStateLock) { + if (newBindingState != mBindingState) { + if (mBindingState != ChildBindingState.UNBOUND) { + assert sAllBindingStateCounts[mBindingState] > 0; + sAllBindingStateCounts[mBindingState]--; + } + if (newBindingState != ChildBindingState.UNBOUND) { + sAllBindingStateCounts[newBindingState]++; + } + } + mBindingState = newBindingState; + if (!mUnbound) { + mBindingStateCurrentOrWhenDied = mBindingState; + } + } + } + + private void notifyChildProcessDied() { + if (mServiceCallback != null) { + // Guard against nested calls to this method. + ServiceCallback serviceCallback = mServiceCallback; + mServiceCallback = null; + serviceCallback.onChildProcessDied(this); + } + } + + private boolean isRunningOnLauncherThread() { + return mLauncherHandler.getLooper() == Looper.myLooper(); + } + + @VisibleForTesting + public void crashServiceForTesting() { + try { + mService.forceKill(); + } catch (RemoteException e) { + // Expected. Ignore. + } + } + + @VisibleForTesting + public boolean didOnServiceConnectedForTesting() { + return mDidOnServiceConnected; + } + + @VisibleForTesting + protected Handler getLauncherHandler() { + return mLauncherHandler; + } + + private void onMemoryPressure(@MemoryPressureLevel int pressure) { + mLauncherHandler.post(() -> onMemoryPressureOnLauncherThread(pressure)); + } + + private void onMemoryPressureOnLauncherThread(@MemoryPressureLevel int pressure) { + if (mService == null) return; + try { + mService.onMemoryPressure(pressure); + } catch (RemoteException ex) { + // Ignore + } + } +}
diff --git a/src/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConstants.java b/src/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConstants.java new file mode 100644 index 0000000..ec232d7 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConstants.java
@@ -0,0 +1,27 @@ +// Copyright 2017 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. + +package org.chromium.base.process_launcher; + +/** + * Constants to be used by child processes. + */ +public interface ChildProcessConstants { + // Below are the names for the items placed in the bind or start command intent. + // Note that because that intent maybe reused if a service is restarted, none should be process + // specific. + + public static final String EXTRA_BIND_TO_CALLER = + "org.chromium.base.process_launcher.extra.bind_to_caller"; + + // Below are the names for the items placed in the Bundle passed in the + // IChildProcessService.setupConnection call, once the connection has been established. + + // Key for the command line. + public static final String EXTRA_COMMAND_LINE = + "org.chromium.base.process_launcher.extra.command_line"; + + // Key for the file descriptors that should be mapped in the child process. + public static final String EXTRA_FILES = "org.chromium.base.process_launcher.extra.extraFiles"; +}
diff --git a/src/base/android/java/src/org/chromium/base/process_launcher/ChildProcessLauncher.java b/src/base/android/java/src/org/chromium/base/process_launcher/ChildProcessLauncher.java new file mode 100644 index 0000000..d8737e1 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/process_launcher/ChildProcessLauncher.java
@@ -0,0 +1,278 @@ +// Copyright 2017 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. + +package org.chromium.base.process_launcher; + +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; + +import org.chromium.base.ContextUtils; +import org.chromium.base.Log; +import org.chromium.base.TraceEvent; + +import java.io.IOException; +import java.util.List; + +/** + * This class is used to start a child process by connecting to a ChildProcessService. + */ +public class ChildProcessLauncher { + private static final String TAG = "ChildProcLauncher"; + + /** Delegate that client should use to customize the process launching. */ + public abstract static class Delegate { + /** + * Called when the launcher is about to start. Gives the embedder a chance to provide an + * already bound connection if it has one. (allowing for warm-up connections: connections + * that are already bound in advance to speed up child process start-up time). + * Note that onBeforeConnectionAllocated will not be called if this method returns a + * connection. + * @param connectionAllocator the allocator the returned connection should have been + * allocated of. + * @param serviceCallback the service callback that the connection should use. + * @return a bound connection to use to connect to the child process service, or null if a + * connection should be allocated and bound by the launcher. + */ + public ChildProcessConnection getBoundConnection( + ChildConnectionAllocator connectionAllocator, + ChildProcessConnection.ServiceCallback serviceCallback) { + return null; + } + + /** + * Called before a connection is allocated. + * Note that this is only called if the ChildProcessLauncher is created with + * {@link #createWithConnectionAllocator}. + * @param serviceBundle the bundle passed in the service intent. Clients can add their own + * extras to the bundle. + */ + public void onBeforeConnectionAllocated(Bundle serviceBundle) {} + + /** + * Called before setup is called on the connection. + * @param connectionBundle the bundle passed to the {@link ChildProcessService} in the + * setup call. Clients can add their own extras to the bundle. + */ + public void onBeforeConnectionSetup(Bundle connectionBundle) {} + + /** + * Called when the connection was successfully established, meaning the setup call on the + * service was successful. + * @param connection the connection over which the setup call was made. + */ + public void onConnectionEstablished(ChildProcessConnection connection) {} + + /** + * Called when a connection has been disconnected. Only invoked if onConnectionEstablished + * was called, meaning the connection was already established. + * @param connection the connection that got disconnected. + */ + public void onConnectionLost(ChildProcessConnection connection) {} + } + + // Represents an invalid process handle; same as base/process/process.h kNullProcessHandle. + private static final int NULL_PROCESS_HANDLE = 0; + + // The handle for the thread we were created on and on which all methods should be called. + private final Handler mLauncherHandler; + + private final Delegate mDelegate; + + private final String[] mCommandLine; + private final FileDescriptorInfo[] mFilesToBeMapped; + + // The allocator used to create the connection. + private final ChildConnectionAllocator mConnectionAllocator; + + // The IBinder interfaces provided to the created service. + private final List<IBinder> mClientInterfaces; + + // The actual service connection. Set once we have connected to the service. + private ChildProcessConnection mConnection; + + /** + * Constructor. + * + * @param launcherHandler the handler for the thread where all operations should happen. + * @param delegate the delagate that gets notified of the launch progress. + * @param commandLine the command line that should be passed to the started process. + * @param filesToBeMapped the files that should be passed to the started process. + * @param connectionAllocator the allocator used to create connections to the service. + * @param clientInterfaces the interfaces that should be passed to the started process so it can + * communicate with the parent process. + */ + public ChildProcessLauncher(Handler launcherHandler, Delegate delegate, String[] commandLine, + FileDescriptorInfo[] filesToBeMapped, ChildConnectionAllocator connectionAllocator, + List<IBinder> clientInterfaces) { + assert connectionAllocator != null; + mLauncherHandler = launcherHandler; + isRunningOnLauncherThread(); + mCommandLine = commandLine; + mConnectionAllocator = connectionAllocator; + mDelegate = delegate; + mFilesToBeMapped = filesToBeMapped; + mClientInterfaces = clientInterfaces; + } + + /** + * Starts the child process and calls setup on it if {@param setupConnection} is true. + * @param setupConnection whether the setup should be performed on the connection once + * established + * @param queueIfNoFreeConnection whether to queue that request if no service connection is + * available. If the launcher was created with a connection provider, this parameter has no + * effect. + * @return true if the connection was started or was queued. + */ + public boolean start(final boolean setupConnection, final boolean queueIfNoFreeConnection) { + assert isRunningOnLauncherThread(); + try { + TraceEvent.begin("ChildProcessLauncher.start"); + ChildProcessConnection.ServiceCallback serviceCallback = + new ChildProcessConnection.ServiceCallback() { + @Override + public void onChildStarted() {} + + @Override + public void onChildStartFailed(ChildProcessConnection connection) { + assert isRunningOnLauncherThread(); + assert mConnection == connection; + Log.e(TAG, "ChildProcessConnection.start failed, trying again"); + mLauncherHandler.post(new Runnable() { + @Override + public void run() { + // The child process may already be bound to another client + // (this can happen if multi-process WebView is used in more + // than one process), so try starting the process again. + // This connection that failed to start has not been freed, + // so a new bound connection will be allocated. + mConnection = null; + start(setupConnection, queueIfNoFreeConnection); + } + }); + } + + @Override + public void onChildProcessDied(ChildProcessConnection connection) { + assert isRunningOnLauncherThread(); + assert mConnection == connection; + ChildProcessLauncher.this.onChildProcessDied(); + } + }; + mConnection = mDelegate.getBoundConnection(mConnectionAllocator, serviceCallback); + if (mConnection != null) { + assert mConnectionAllocator.isConnectionFromAllocator(mConnection); + setupConnection(); + return true; + } + if (!allocateAndSetupConnection( + serviceCallback, setupConnection, queueIfNoFreeConnection) + && !queueIfNoFreeConnection) { + return false; + } + return true; + } finally { + TraceEvent.end("ChildProcessLauncher.start"); + } + } + + public ChildProcessConnection getConnection() { + return mConnection; + } + + public ChildConnectionAllocator getConnectionAllocator() { + return mConnectionAllocator; + } + + private boolean allocateAndSetupConnection( + final ChildProcessConnection.ServiceCallback serviceCallback, + final boolean setupConnection, final boolean queueIfNoFreeConnection) { + assert mConnection == null; + Bundle serviceBundle = new Bundle(); + mDelegate.onBeforeConnectionAllocated(serviceBundle); + + mConnection = mConnectionAllocator.allocate( + ContextUtils.getApplicationContext(), serviceBundle, serviceCallback); + if (mConnection == null) { + if (!queueIfNoFreeConnection) { + Log.d(TAG, "Failed to allocate a child connection (no queuing)."); + return false; + } + mConnectionAllocator.queueAllocation( + () -> allocateAndSetupConnection( + serviceCallback, setupConnection, queueIfNoFreeConnection)); + return false; + } + + if (setupConnection) { + setupConnection(); + } + return true; + } + + private void setupConnection() { + ChildProcessConnection.ConnectionCallback connectionCallback = + new ChildProcessConnection.ConnectionCallback() { + @Override + public void onConnected(ChildProcessConnection connection) { + onServiceConnected(connection); + } + }; + Bundle connectionBundle = createConnectionBundle(); + mDelegate.onBeforeConnectionSetup(connectionBundle); + mConnection.setupConnection(connectionBundle, getClientInterfaces(), connectionCallback); + } + + private void onServiceConnected(ChildProcessConnection connection) { + assert isRunningOnLauncherThread(); + assert mConnection == connection || connection == null; + + Log.d(TAG, "on connect callback, pid=%d", mConnection.getPid()); + + mDelegate.onConnectionEstablished(mConnection); + + // Proactively close the FDs rather than waiting for the GC to do it. + try { + for (FileDescriptorInfo fileInfo : mFilesToBeMapped) { + fileInfo.fd.close(); + } + } catch (IOException ioe) { + Log.w(TAG, "Failed to close FD.", ioe); + } + } + + public int getPid() { + assert isRunningOnLauncherThread(); + return mConnection == null ? NULL_PROCESS_HANDLE : mConnection.getPid(); + } + + public List<IBinder> getClientInterfaces() { + return mClientInterfaces; + } + + private boolean isRunningOnLauncherThread() { + return mLauncherHandler.getLooper() == Looper.myLooper(); + } + + private Bundle createConnectionBundle() { + Bundle bundle = new Bundle(); + bundle.putStringArray(ChildProcessConstants.EXTRA_COMMAND_LINE, mCommandLine); + bundle.putParcelableArray(ChildProcessConstants.EXTRA_FILES, mFilesToBeMapped); + return bundle; + } + + private void onChildProcessDied() { + assert isRunningOnLauncherThread(); + if (getPid() != 0) { + mDelegate.onConnectionLost(mConnection); + } + } + + public void stop() { + assert isRunningOnLauncherThread(); + Log.d(TAG, "stopping child connection: pid=%d", mConnection.getPid()); + mConnection.stop(); + } +}
diff --git a/src/base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java b/src/base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java new file mode 100644 index 0000000..f9d7ca7 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java
@@ -0,0 +1,377 @@ +// Copyright 2017 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. + +package org.chromium.base.process_launcher; + +import android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Parcelable; +import android.os.Process; +import android.os.RemoteException; +import android.support.annotation.IntDef; +import android.util.SparseArray; + +import org.chromium.base.BaseSwitches; +import org.chromium.base.CommandLine; +import org.chromium.base.ContextUtils; +import org.chromium.base.Log; +import org.chromium.base.MemoryPressureLevel; +import org.chromium.base.ThreadUtils; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.MainDex; +import org.chromium.base.memory.MemoryPressureMonitor; +import org.chromium.base.metrics.RecordHistogram; + +import java.util.List; +import java.util.concurrent.Semaphore; + +import javax.annotation.concurrent.GuardedBy; + +/** + * This is the base class for child services; the embedding application should contain + * ProcessService0, 1.. etc subclasses that provide the concrete service entry points, so it can + * connect to more than one distinct process (i.e. one process per service number, up to limit of + * N). + * The embedding application must declare these service instances in the application section + * of its AndroidManifest.xml, first with some meta-data describing the services: + * <meta-data android:name="org.chromium.test_app.SERVICES_NAME" + * android:value="org.chromium.test_app.ProcessService"/> + * and then N entries of the form: + * <service android:name="org.chromium.test_app.ProcessServiceX" + * android:process=":processX" /> + * + * Subclasses must also provide a delegate in this class constructor. That delegate is responsible + * for loading native libraries and running the main entry point of the service. + */ +@JNINamespace("base::android") +@MainDex +public abstract class ChildProcessService extends Service { + private static final String MAIN_THREAD_NAME = "ChildProcessMain"; + private static final String TAG = "ChildProcessService"; + + // Only for a check that create is only called once. + private static boolean sCreateCalled; + + private final ChildProcessServiceDelegate mDelegate; + + private final Object mBinderLock = new Object(); + private final Object mLibraryInitializedLock = new Object(); + + // True if we should enforce that bindToCaller() is called before setupConnection(). + // Only set once in bind(), does not require synchronization. + private boolean mBindToCallerCheck; + + // PID of the client of this service, set in bindToCaller(), if mBindToCallerCheck is true. + @GuardedBy("mBinderLock") + private int mBoundCallingPid; + + // This is the native "Main" thread for the renderer / utility process. + private Thread mMainThread; + + // Parameters received via IPC, only accessed while holding the mMainThread monitor. + private String[] mCommandLineParams; + + // File descriptors that should be registered natively. + private FileDescriptorInfo[] mFdInfos; + + @GuardedBy("mLibraryInitializedLock") + private boolean mLibraryInitialized; + + // Called once the service is bound and all service related member variables have been set. + // Only set once in bind(), does not require synchronization. + private boolean mServiceBound; + + private final Semaphore mActivitySemaphore = new Semaphore(1); + + // These values are persisted to logs. Entries should not be renumbered and numeric values + // should never be reused. + @IntDef({SplitApkWorkaroundResult.NOT_RUN, SplitApkWorkaroundResult.NO_ENTRIES, + SplitApkWorkaroundResult.ONE_ENTRY, SplitApkWorkaroundResult.MULTIPLE_ENTRIES, + SplitApkWorkaroundResult.TOPLEVEL_EXCEPTION, SplitApkWorkaroundResult.LOOP_EXCEPTION}) + public @interface SplitApkWorkaroundResult { + int NOT_RUN = 0; + int NO_ENTRIES = 1; + int ONE_ENTRY = 2; + int MULTIPLE_ENTRIES = 3; + int TOPLEVEL_EXCEPTION = 4; + int LOOP_EXCEPTION = 5; + // Keep this one at the end and increment appropriately when adding new results. + int SPLIT_APK_WORKAROUND_RESULT_COUNT = 6; + } + + private static @SplitApkWorkaroundResult int sSplitApkWorkaroundResult = + SplitApkWorkaroundResult.NOT_RUN; + + public static void setSplitApkWorkaroundResult(@SplitApkWorkaroundResult int result) { + sSplitApkWorkaroundResult = result; + } + + public ChildProcessService(ChildProcessServiceDelegate delegate) { + mDelegate = delegate; + } + + // Binder object used by clients for this service. + private final IChildProcessService.Stub mBinder = new IChildProcessService.Stub() { + // NOTE: Implement any IChildProcessService methods here. + @Override + public boolean bindToCaller() { + assert mBindToCallerCheck; + assert mServiceBound; + synchronized (mBinderLock) { + int callingPid = Binder.getCallingPid(); + if (mBoundCallingPid == 0) { + mBoundCallingPid = callingPid; + } else if (mBoundCallingPid != callingPid) { + Log.e(TAG, "Service is already bound by pid %d, cannot bind for pid %d", + mBoundCallingPid, callingPid); + return false; + } + } + return true; + } + + @Override + public void setupConnection(Bundle args, ICallbackInt pidCallback, List<IBinder> callbacks) + throws RemoteException { + assert mServiceBound; + synchronized (mBinderLock) { + if (mBindToCallerCheck && mBoundCallingPid == 0) { + Log.e(TAG, "Service has not been bound with bindToCaller()"); + pidCallback.call(-1); + return; + } + } + + pidCallback.call(Process.myPid()); + processConnectionBundle(args, callbacks); + } + + @Override + public void forceKill() { + assert mServiceBound; + Process.killProcess(Process.myPid()); + } + + @Override + public void onMemoryPressure(@MemoryPressureLevel int pressure) { + // This method is called by the host process when the host process reports pressure + // to its native side. The key difference between the host process and its services is + // that the host process polls memory pressure when it gets CRITICAL, and periodically + // invokes pressure listeners until pressure subsides. (See MemoryPressureMonitor for + // more info.) + // + // Services don't poll, so this side-channel is used to notify services about memory + // pressure from the host process's POV. + // + // However, since both host process and services listen to ComponentCallbacks2, we + // can't be sure that the host process won't get better signals than their services. + // I.e. we need to watch out for a situation where a service gets CRITICAL, but the + // host process gets MODERATE - in this case we need to ignore MODERATE. + // + // So we're ignoring pressure from the host process if it's better than the last + // reported pressure. I.e. the host process can drive pressure up, but it'll go + // down only when we the service get a signal through ComponentCallbacks2. + ThreadUtils.postOnUiThread(() -> { + if (pressure >= MemoryPressureMonitor.INSTANCE.getLastReportedPressure()) { + MemoryPressureMonitor.INSTANCE.notifyPressure(pressure); + } + }); + } + }; + + /** + * Loads Chrome's native libraries and initializes a ChildProcessService. + */ + // For sCreateCalled check. + @Override + public void onCreate() { + super.onCreate(); + Log.i(TAG, "Creating new ChildProcessService pid=%d", Process.myPid()); + if (sCreateCalled) { + throw new RuntimeException("Illegal child process reuse."); + } + sCreateCalled = true; + + // Initialize the context for the application that owns this ChildProcessService object. + ContextUtils.initApplicationContext(getApplicationContext()); + + mDelegate.onServiceCreated(); + + mMainThread = new Thread(new Runnable() { + @Override + public void run() { + try { + // CommandLine must be initialized before everything else. + synchronized (mMainThread) { + while (mCommandLineParams == null) { + mMainThread.wait(); + } + } + assert mServiceBound; + CommandLine.init(mCommandLineParams); + + if (CommandLine.getInstance().hasSwitch( + BaseSwitches.RENDERER_WAIT_FOR_JAVA_DEBUGGER)) { + android.os.Debug.waitForDebugger(); + } + + boolean nativeLibraryLoaded = false; + try { + nativeLibraryLoaded = mDelegate.loadNativeLibrary(getApplicationContext()); + } catch (Exception e) { + Log.e(TAG, "Failed to load native library.", e); + } + if (!nativeLibraryLoaded) { + System.exit(-1); + } + + synchronized (mLibraryInitializedLock) { + mLibraryInitialized = true; + mLibraryInitializedLock.notifyAll(); + } + synchronized (mMainThread) { + mMainThread.notifyAll(); + while (mFdInfos == null) { + mMainThread.wait(); + } + } + + SparseArray<String> idsToKeys = mDelegate.getFileDescriptorsIdsToKeys(); + + int[] fileIds = new int[mFdInfos.length]; + String[] keys = new String[mFdInfos.length]; + int[] fds = new int[mFdInfos.length]; + long[] regionOffsets = new long[mFdInfos.length]; + long[] regionSizes = new long[mFdInfos.length]; + for (int i = 0; i < mFdInfos.length; i++) { + FileDescriptorInfo fdInfo = mFdInfos[i]; + String key = idsToKeys != null ? idsToKeys.get(fdInfo.id) : null; + if (key != null) { + keys[i] = key; + } else { + fileIds[i] = fdInfo.id; + } + fds[i] = fdInfo.fd.detachFd(); + regionOffsets[i] = fdInfo.offset; + regionSizes[i] = fdInfo.size; + } + nativeRegisterFileDescriptors(keys, fileIds, fds, regionOffsets, regionSizes); + + mDelegate.onBeforeMain(); + if (ContextUtils.isIsolatedProcess()) { + RecordHistogram.recordEnumeratedHistogram( + "Android.WebView.SplitApkWorkaroundResult", + sSplitApkWorkaroundResult, + SplitApkWorkaroundResult.SPLIT_APK_WORKAROUND_RESULT_COUNT); + } + if (mActivitySemaphore.tryAcquire()) { + mDelegate.runMain(); + nativeExitChildProcess(); + } + } catch (InterruptedException e) { + Log.w(TAG, "%s startup failed: %s", MAIN_THREAD_NAME, e); + } + } + }, MAIN_THREAD_NAME); + mMainThread.start(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + Log.i(TAG, "Destroying ChildProcessService pid=%d", Process.myPid()); + if (mActivitySemaphore.tryAcquire()) { + // TODO(crbug.com/457406): This is a bit hacky, but there is no known better solution + // as this service will get reused (at least if not sandboxed). + // In fact, we might really want to always exit() from onDestroy(), not just from + // the early return here. + System.exit(0); + return; + } + synchronized (mLibraryInitializedLock) { + try { + while (!mLibraryInitialized) { + // Avoid a potential race in calling through to native code before the library + // has loaded. + mLibraryInitializedLock.wait(); + } + } catch (InterruptedException e) { + // Ignore + } + } + mDelegate.onDestroy(); + } + + /* + * Returns the communication channel to the service. Note that even if multiple clients were to + * connect, we should only get one call to this method. So there is no need to synchronize + * member variables that are only set in this method and accessed from binder methods, as binder + * methods can't be called until this method returns. + * @param intent The intent that was used to bind to the service. + * @return the binder used by the client to setup the connection. + */ + @Override + public IBinder onBind(Intent intent) { + assert !mServiceBound; + + // We call stopSelf() to request that this service be stopped as soon as the client unbinds. + // Otherwise the system may keep it around and available for a reconnect. The child + // processes do not currently support reconnect; they must be initialized from scratch every + // time. + stopSelf(); + + mBindToCallerCheck = + intent.getBooleanExtra(ChildProcessConstants.EXTRA_BIND_TO_CALLER, false); + mServiceBound = true; + mDelegate.onServiceBound(intent); + // Don't block bind() with any extra work, post it to the application thread instead. + new Handler(Looper.getMainLooper()) + .post(() -> mDelegate.preloadNativeLibrary(getApplicationContext())); + return mBinder; + } + + private void processConnectionBundle(Bundle bundle, List<IBinder> clientInterfaces) { + // Required to unparcel FileDescriptorInfo. + ClassLoader classLoader = getApplicationContext().getClassLoader(); + bundle.setClassLoader(classLoader); + synchronized (mMainThread) { + if (mCommandLineParams == null) { + mCommandLineParams = + bundle.getStringArray(ChildProcessConstants.EXTRA_COMMAND_LINE); + mMainThread.notifyAll(); + } + // We must have received the command line by now + assert mCommandLineParams != null; + Parcelable[] fdInfosAsParcelable = + bundle.getParcelableArray(ChildProcessConstants.EXTRA_FILES); + if (fdInfosAsParcelable != null) { + // For why this arraycopy is necessary: + // http://stackoverflow.com/questions/8745893/i-dont-get-why-this-classcastexception-occurs + mFdInfos = new FileDescriptorInfo[fdInfosAsParcelable.length]; + System.arraycopy(fdInfosAsParcelable, 0, mFdInfos, 0, fdInfosAsParcelable.length); + } + mDelegate.onConnectionSetup(bundle, clientInterfaces); + mMainThread.notifyAll(); + } + } + + /** + * Helper for registering FileDescriptorInfo objects with GlobalFileDescriptors or + * FileDescriptorStore. + * This includes the IPC channel, the crash dump signals and resource related + * files. + */ + private static native void nativeRegisterFileDescriptors( + String[] keys, int[] id, int[] fd, long[] offset, long[] size); + + /** + * Force the child process to exit. + */ + private static native void nativeExitChildProcess(); +}
diff --git a/src/base/android/java/src/org/chromium/base/process_launcher/ChildProcessServiceDelegate.java b/src/base/android/java/src/org/chromium/base/process_launcher/ChildProcessServiceDelegate.java new file mode 100644 index 0000000..7beffef --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/process_launcher/ChildProcessServiceDelegate.java
@@ -0,0 +1,76 @@ +// Copyright 2017 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. + +package org.chromium.base.process_launcher; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; +import android.util.SparseArray; + +import java.util.List; + +/** + * The interface that embedders should implement to specialize child service creation. + */ +public interface ChildProcessServiceDelegate { + /** Invoked when the service was created. This is the first method invoked on the delegate. */ + void onServiceCreated(); + + /** + * Called when the service is bound. Invoked on a background thread. + * @param intent the intent that started the service. + */ + void onServiceBound(Intent intent); + + /** + * Called once the connection has been setup. Invoked on a background thread. + * @param connectionBundle the bundle pass to the setupConnection call + * @param clientInterfaces the IBinders interfaces provided by the client + */ + void onConnectionSetup(Bundle connectionBundle, List<IBinder> clientInterfaces); + + /** + * Called when the service gets destroyed. + * Note that the system might kill the process hosting the service without this method being + * called. + */ + void onDestroy(); + + /** + * Called when the delegate should load the native library. + * @param hostContext The host context the library should be loaded with (i.e. Chrome). + * @return true if the library was loaded successfully, false otherwise in which case the + * service stops. + */ + boolean loadNativeLibrary(Context hostContext); + + /** + * Called when the delegate should preload the native library. + * Preloading is automatically done during library loading, but can also be called explicitly + * to speed up the loading. See {@link LibraryLoader.preloadNow}. + * @param hostContext The host context the library should be preloaded with (i.e. Chrome). + */ + void preloadNativeLibrary(Context hostContext); + + /** + * Should return a map that associatesfile descriptors' IDs to keys. + * This is needed as at the moment we use 2 different stores for the FDs in native code: + * base::FileDescriptorStore which associates FDs with string identifiers (the key), and + * base::GlobalDescriptors which associates FDs with int ids. + * FDs for which the returned map contains a mapping are added to base::FileDescriptorStore with + * the associated key, all others are added to base::GlobalDescriptors. + */ + SparseArray<String> getFileDescriptorsIdsToKeys(); + + /** Called before the main method is invoked. */ + void onBeforeMain(); + + /** + * The main entry point for the service. This method should block as long as the service should + * be running. + */ + void runMain(); +}
diff --git a/src/base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.aidl b/src/base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.aidl new file mode 100644 index 0000000..e37d8c7 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.aidl
@@ -0,0 +1,7 @@ +// Copyright 2016 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. + +package org.chromium.base.process_launcher; + +parcelable FileDescriptorInfo;
diff --git a/src/base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.java b/src/base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.java new file mode 100644 index 0000000..3dc3663 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.java
@@ -0,0 +1,68 @@ +// Copyright 2017 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. + +package org.chromium.base.process_launcher; + +import android.os.Parcel; +import android.os.ParcelFileDescriptor; +import android.os.Parcelable; + +import org.chromium.base.annotations.MainDex; +import org.chromium.base.annotations.UsedByReflection; + +import javax.annotation.concurrent.Immutable; + +/** + * Parcelable class that contains file descriptor and file region information to + * be passed to child processes. + */ +@Immutable +@MainDex +@UsedByReflection("child_process_launcher_helper_android.cc") +public final class FileDescriptorInfo implements Parcelable { + public final int id; + public final ParcelFileDescriptor fd; + public final long offset; + public final long size; + + public FileDescriptorInfo(int id, ParcelFileDescriptor fd, long offset, long size) { + this.id = id; + this.fd = fd; + this.offset = offset; + this.size = size; + } + + FileDescriptorInfo(Parcel in) { + id = in.readInt(); + fd = in.readParcelable(ParcelFileDescriptor.class.getClassLoader()); + offset = in.readLong(); + size = in.readLong(); + } + + @Override + public int describeContents() { + return CONTENTS_FILE_DESCRIPTOR; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(id); + dest.writeParcelable(fd, CONTENTS_FILE_DESCRIPTOR); + dest.writeLong(offset); + dest.writeLong(size); + } + + public static final Parcelable.Creator<FileDescriptorInfo> CREATOR = + new Parcelable.Creator<FileDescriptorInfo>() { + @Override + public FileDescriptorInfo createFromParcel(Parcel in) { + return new FileDescriptorInfo(in); + } + + @Override + public FileDescriptorInfo[] newArray(int size) { + return new FileDescriptorInfo[size]; + } + }; +}
diff --git a/src/base/android/java/src/org/chromium/base/process_launcher/ICallbackInt.aidl b/src/base/android/java/src/org/chromium/base/process_launcher/ICallbackInt.aidl new file mode 100644 index 0000000..db93cb0 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/process_launcher/ICallbackInt.aidl
@@ -0,0 +1,9 @@ +// Copyright 2017 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. + +package org.chromium.base.process_launcher; + +oneway interface ICallbackInt { + void call(int value); +}
diff --git a/src/base/android/java/src/org/chromium/base/process_launcher/IChildProcessService.aidl b/src/base/android/java/src/org/chromium/base/process_launcher/IChildProcessService.aidl new file mode 100644 index 0000000..298e0bf --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/process_launcher/IChildProcessService.aidl
@@ -0,0 +1,26 @@ +// Copyright 2012 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. + +package org.chromium.base.process_launcher; + +import android.os.Bundle; + +import org.chromium.base.process_launcher.ICallbackInt; + +interface IChildProcessService { + // On the first call to this method, the service will record the calling PID + // and return true. Subsequent calls will only return true if the calling PID + // is the same as the recorded one. + boolean bindToCaller(); + + // Sets up the initial IPC channel. + oneway void setupConnection(in Bundle args, ICallbackInt pidCallback, + in List<IBinder> clientInterfaces); + + // Forcefully kills the child process. + oneway void forceKill(); + + // Notifies about memory pressure. The argument is MemoryPressureLevel enum. + oneway void onMemoryPressure(int pressure); +}
diff --git a/src/base/android/java/src/org/chromium/base/task/AsyncTask.java b/src/base/android/java/src/org/chromium/base/task/AsyncTask.java new file mode 100644 index 0000000..8bed613 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/task/AsyncTask.java
@@ -0,0 +1,368 @@ +// Copyright 2018 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. + +package org.chromium.base.task; + +import android.os.Binder; +import android.os.Process; +import android.support.annotation.MainThread; +import android.support.annotation.WorkerThread; + +import org.chromium.base.ThreadUtils; +import org.chromium.base.TraceEvent; +import org.chromium.base.annotations.DoNotInline; + +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.FutureTask; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * A Chromium version of android.os.AsyncTask. + * + * The API is quite close to Android's Oreo version, but with a number of things removed. + * @param <Result> Return type of the background task. + */ +public abstract class AsyncTask<Result> { + private static final String TAG = "AsyncTask"; + + /** + * An {@link Executor} that can be used to execute tasks in parallel. + */ + public static final Executor THREAD_POOL_EXECUTOR = new ChromeThreadPoolExecutor(); + + /** + * An {@link Executor} that executes tasks one at a time in serial + * order. This serialization is global to a particular process. + */ + public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); + + private static final StealRunnableHandler STEAL_RUNNABLE_HANDLER = new StealRunnableHandler(); + + private final Callable<Result> mWorker; + private final FutureTask<Result> mFuture; + + private volatile Status mStatus = Status.PENDING; + + private final AtomicBoolean mCancelled = new AtomicBoolean(); + private final AtomicBoolean mTaskInvoked = new AtomicBoolean(); + + private static class StealRunnableHandler implements RejectedExecutionHandler { + @Override + public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { + THREAD_POOL_EXECUTOR.execute(r); + } + } + + /** + * Indicates the current status of the task. Each status will be set only once + * during the lifetime of a task. + */ + public enum Status { + /** + * Indicates that the task has not been executed yet. + */ + PENDING, + /** + * Indicates that the task is running. + */ + RUNNING, + /** + * Indicates that {@link AsyncTask#onPostExecute} has finished. + */ + FINISHED, + } + + @SuppressWarnings("NoAndroidAsyncTaskCheck") + public static void takeOverAndroidThreadPool() { + ThreadPoolExecutor exec = (ThreadPoolExecutor) android.os.AsyncTask.THREAD_POOL_EXECUTOR; + exec.setRejectedExecutionHandler(STEAL_RUNNABLE_HANDLER); + exec.shutdown(); + } + + /** + * Creates a new asynchronous task. This constructor must be invoked on the UI thread. + */ + public AsyncTask() { + mWorker = new Callable<Result>() { + @Override + public Result call() throws Exception { + mTaskInvoked.set(true); + Result result = null; + try { + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + result = doInBackground(); + Binder.flushPendingCommands(); + } catch (Throwable tr) { + mCancelled.set(true); + throw tr; + } finally { + postResult(result); + } + return result; + } + }; + + mFuture = new NamedFutureTask(mWorker); + } + + private void postResultIfNotInvoked(Result result) { + final boolean wasTaskInvoked = mTaskInvoked.get(); + if (!wasTaskInvoked) { + postResult(result); + } + } + + private void postResult(Result result) { + ThreadUtils.postOnUiThread(() -> { finish(result); }); + } + + /** + * Returns the current status of this task. + * + * @return The current status. + */ + public final Status getStatus() { + return mStatus; + } + + /** + * Override this method to perform a computation on a background thread. + * + * @return A result, defined by the subclass of this task. + * + * @see #onPreExecute() + * @see #onPostExecute + */ + @WorkerThread + protected abstract Result doInBackground(); + + /** + * Runs on the UI thread before {@link #doInBackground}. + * + * @see #onPostExecute + * @see #doInBackground + */ + @MainThread + protected void onPreExecute() {} + + /** + * <p>Runs on the UI thread after {@link #doInBackground}. The + * specified result is the value returned by {@link #doInBackground}.</p> + * + * <p>This method won't be invoked if the task was cancelled.</p> + * + * @param result The result of the operation computed by {@link #doInBackground}. + * + * @see #onPreExecute + * @see #doInBackground + * @see #onCancelled(Object) + */ + @SuppressWarnings({"UnusedDeclaration"}) + @MainThread + protected void onPostExecute(Result result) {} + + /** + * <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and + * {@link #doInBackground()} has finished.</p> + * + * <p>The default implementation simply invokes {@link #onCancelled()} and + * ignores the result. If you write your own implementation, do not call + * <code>super.onCancelled(result)</code>.</p> + * + * @param result The result, if any, computed in + * {@link #doInBackground()}, can be null + * + * @see #cancel(boolean) + * @see #isCancelled() + */ + @SuppressWarnings({"UnusedParameters"}) + @MainThread + protected void onCancelled(Result result) { + onCancelled(); + } + + /** + * <p>Applications should preferably override {@link #onCancelled(Object)}. + * This method is invoked by the default implementation of + * {@link #onCancelled(Object)}.</p> + * + * <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and + * {@link #doInBackground()} has finished.</p> + * + * @see #onCancelled(Object) + * @see #cancel(boolean) + * @see #isCancelled() + */ + @MainThread + protected void onCancelled() {} + + /** + * Returns <tt>true</tt> if this task was cancelled before it completed + * normally. If you are calling {@link #cancel(boolean)} on the task, + * the value returned by this method should be checked periodically from + * {@link #doInBackground()} to end the task as soon as possible. + * + * @return <tt>true</tt> if task was cancelled before it completed + * + * @see #cancel(boolean) + */ + public final boolean isCancelled() { + return mCancelled.get(); + } + + /** + * <p>Attempts to cancel execution of this task. This attempt will + * fail if the task has already completed, already been cancelled, + * or could not be cancelled for some other reason. If successful, + * and this task has not started when <tt>cancel</tt> is called, + * this task should never run. If the task has already started, + * then the <tt>mayInterruptIfRunning</tt> parameter determines + * whether the thread executing this task should be interrupted in + * an attempt to stop the task.</p> + * + * <p>Calling this method will result in {@link #onCancelled(Object)} being + * invoked on the UI thread after {@link #doInBackground()} + * returns. Calling this method guarantees that {@link #onPostExecute(Object)} + * is never invoked. After invoking this method, you should check the + * value returned by {@link #isCancelled()} periodically from + * {@link #doInBackground()} to finish the task as early as + * possible.</p> + * + * @param mayInterruptIfRunning <tt>true</tt> if the thread executing this + * task should be interrupted; otherwise, in-progress tasks are allowed + * to complete. + * + * @return <tt>false</tt> if the task could not be cancelled, + * typically because it has already completed normally; + * <tt>true</tt> otherwise + * + * @see #isCancelled() + * @see #onCancelled(Object) + */ + public final boolean cancel(boolean mayInterruptIfRunning) { + mCancelled.set(true); + return mFuture.cancel(mayInterruptIfRunning); + } + + /** + * Waits if necessary for the computation to complete, and then + * retrieves its result. + * + * @return The computed result. + * + * @throws CancellationException If the computation was cancelled. + * @throws ExecutionException If the computation threw an exception. + * @throws InterruptedException If the current thread was interrupted + * while waiting. + */ + @DoNotInline + public final Result get() throws InterruptedException, ExecutionException { + Result r; + if (getStatus() != Status.FINISHED && ThreadUtils.runningOnUiThread()) { + StackTraceElement[] stackTrace = new Exception().getStackTrace(); + String caller = ""; + if (stackTrace.length > 1) { + caller = stackTrace[1].getClassName() + '.' + stackTrace[1].getMethodName() + '.'; + } + try (TraceEvent e = TraceEvent.scoped(caller + "AsyncTask.get")) { + r = mFuture.get(); + } + } else { + r = mFuture.get(); + } + return r; + } + + /** + * Executes the task with the specified parameters. The task returns + * itself (this) so that the caller can keep a reference to it. + * + * <p>This method is typically used with {@link #THREAD_POOL_EXECUTOR} to + * allow multiple tasks to run in parallel on a pool of threads managed by + * AsyncTask, however you can also use your own {@link Executor} for custom + * behavior. + * + * <p><em>Warning:</em> Allowing multiple tasks to run in parallel from + * a thread pool is generally <em>not</em> what one wants, because the order + * of their operation is not defined. For example, if these tasks are used + * to modify any state in common (such as writing a file due to a button click), + * there are no guarantees on the order of the modifications. + * Without careful work it is possible in rare cases for the newer version + * of the data to be over-written by an older one, leading to obscure data + * loss and stability issues. Such changes are best + * executed in serial; to guarantee such work is serialized regardless of + * platform version you can use this function with {@link #SERIAL_EXECUTOR}. + * + * <p>This method must be invoked on the UI thread. + * + * @param exec The executor to use. {@link #THREAD_POOL_EXECUTOR} is available as a + * convenient process-wide thread pool for tasks that are loosely coupled. + * + * @return This instance of AsyncTask. + * + * @throws IllegalStateException If {@link #getStatus()} returns either + * {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}. + */ + @SuppressWarnings({"MissingCasesInEnumSwitch"}) + @MainThread + public final AsyncTask<Result> executeOnExecutor(Executor exec) { + if (mStatus != Status.PENDING) { + switch (mStatus) { + case RUNNING: + throw new IllegalStateException("Cannot execute task:" + + " the task is already running."); + case FINISHED: + throw new IllegalStateException("Cannot execute task:" + + " the task has already been executed " + + "(a task can be executed only once)"); + } + } + + mStatus = Status.RUNNING; + + onPreExecute(); + + exec.execute(mFuture); + + return this; + } + + private void finish(Result result) { + if (isCancelled()) { + onCancelled(result); + } else { + onPostExecute(result); + } + mStatus = Status.FINISHED; + } + + class NamedFutureTask extends FutureTask<Result> { + NamedFutureTask(Callable<Result> c) { + super(c); + } + + Class getBlamedClass() { + return AsyncTask.this.getClass(); + } + + @Override + protected void done() { + try { + postResultIfNotInvoked(get()); + } catch (InterruptedException e) { + android.util.Log.w(TAG, e); + } catch (ExecutionException e) { + throw new RuntimeException( + "An error occurred while executing doInBackground()", e.getCause()); + } catch (CancellationException e) { + postResultIfNotInvoked(null); + } + } + } +}
diff --git a/src/base/android/java/src/org/chromium/base/task/ChromeThreadPoolExecutor.java b/src/base/android/java/src/org/chromium/base/task/ChromeThreadPoolExecutor.java new file mode 100644 index 0000000..7538843 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/task/ChromeThreadPoolExecutor.java
@@ -0,0 +1,123 @@ +// Copyright 2018 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. + +package org.chromium.base.task; + +import static java.util.concurrent.TimeUnit.SECONDS; + +import org.chromium.base.BuildConfig; +import org.chromium.base.VisibleForTesting; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +class ChromeThreadPoolExecutor extends ThreadPoolExecutor { + private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); + + // Core pool is still used despite allowCoreThreadTimeOut(true) being called - while the core + // pool can still timeout, the thread pool will still start up threads more aggressively while + // under the CORE_POOL_SIZE. + private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4)); + private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; + private static final int KEEP_ALIVE_SECONDS = 30; + + private static final ThreadFactory sThreadFactory = new ThreadFactory() { + private final AtomicInteger mCount = new AtomicInteger(1); + @Override + public Thread newThread(Runnable r) { + return new Thread(r, "CrAsyncTask #" + mCount.getAndIncrement()); + } + }; + + private static final BlockingQueue<Runnable> sPoolWorkQueue = + new ArrayBlockingQueue<Runnable>(128); + + // May have to be lowered if we are not capturing any Runnable sources. + private static final int RUNNABLE_WARNING_COUNT = 32; + + ChromeThreadPoolExecutor() { + this(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, SECONDS, sPoolWorkQueue, + sThreadFactory); + } + + @VisibleForTesting + ChromeThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, + TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) { + super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory); + allowCoreThreadTimeOut(true); + } + + @SuppressWarnings("NoAndroidAsyncTaskCheck") + private static String getClassName(Runnable runnable) { + Class blamedClass = runnable.getClass(); + try { + if (blamedClass == AsyncTask.NamedFutureTask.class) { + blamedClass = ((AsyncTask.NamedFutureTask) runnable).getBlamedClass(); + } else if (blamedClass.getEnclosingClass() == android.os.AsyncTask.class) { + // This gets the AsyncTask that produced the runnable. + Field field = blamedClass.getDeclaredField("this$0"); + field.setAccessible(true); + blamedClass = field.get(runnable).getClass(); + } + } catch (NoSuchFieldException e) { + if (BuildConfig.DCHECK_IS_ON) { + throw new RuntimeException(e); + } + } catch (IllegalAccessException e) { + if (BuildConfig.DCHECK_IS_ON) { + throw new RuntimeException(e); + } + } + return blamedClass.getName(); + } + + private Map<String, Integer> getNumberOfClassNameOccurrencesInQueue() { + Map<String, Integer> counts = new HashMap<>(); + Runnable[] copiedQueue = getQueue().toArray(new Runnable[0]); + for (Runnable runnable : copiedQueue) { + String className = getClassName(runnable); + int count = counts.containsKey(className) ? counts.get(className) : 0; + counts.put(className, count + 1); + } + return counts; + } + + private String findClassNamesWithTooManyRunnables(Map<String, Integer> counts) { + // We only show the classes over RUNNABLE_WARNING_COUNT appearances so that these + // crashes group up together in the reporting dashboards. If we were to print all + // the Runnables or their counts, this would fragment the reporting, with one for + // each unique set of Runnables/counts. + StringBuilder classesWithTooManyRunnables = new StringBuilder(); + for (Map.Entry<String, Integer> entry : counts.entrySet()) { + if (entry.getValue() > RUNNABLE_WARNING_COUNT) { + classesWithTooManyRunnables.append(entry.getKey()).append(' '); + } + } + if (classesWithTooManyRunnables.length() == 0) { + return "NO CLASSES FOUND"; + } + return classesWithTooManyRunnables.toString(); + } + + @Override + public void execute(Runnable command) { + try { + super.execute(command); + } catch (RejectedExecutionException e) { + Map<String, Integer> counts = getNumberOfClassNameOccurrencesInQueue(); + + throw new RejectedExecutionException( + "Prominent classes in AsyncTask: " + findClassNamesWithTooManyRunnables(counts), + e); + } + } +}
diff --git a/src/base/android/java/src/org/chromium/base/task/SequencedTaskRunner.java b/src/base/android/java/src/org/chromium/base/task/SequencedTaskRunner.java new file mode 100644 index 0000000..69118fd --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/task/SequencedTaskRunner.java
@@ -0,0 +1,13 @@ +// Copyright 2018 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. + +package org.chromium.base.task; + +/** + * Tasks posted will be run in order with respect to this sequence, but they may be executed + * on arbitrary threads. Unless specified otherwise by the provider of a given + * SequencedTaskRunner, tasks posted to it have no ordering, nor mutual exclusion, execution + * guarantees w.r.t. other SequencedTaskRunners. + */ +public interface SequencedTaskRunner extends TaskRunner {}
diff --git a/src/base/android/java/src/org/chromium/base/task/SerialExecutor.java b/src/base/android/java/src/org/chromium/base/task/SerialExecutor.java new file mode 100644 index 0000000..3e1e934 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/task/SerialExecutor.java
@@ -0,0 +1,36 @@ +// Copyright 2018 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. + +package org.chromium.base.task; + +import java.util.ArrayDeque; +import java.util.concurrent.Executor; + +class SerialExecutor implements Executor { + final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>(); + Runnable mActive; + + @Override + public synchronized void execute(final Runnable r) { + mTasks.offer(new Runnable() { + @Override + public void run() { + try { + r.run(); + } finally { + scheduleNext(); + } + } + }); + if (mActive == null) { + scheduleNext(); + } + } + + protected synchronized void scheduleNext() { + if ((mActive = mTasks.poll()) != null) { + AsyncTask.THREAD_POOL_EXECUTOR.execute(mActive); + } + } +}
diff --git a/src/base/android/java/src/org/chromium/base/task/SingleThreadTaskRunner.java b/src/base/android/java/src/org/chromium/base/task/SingleThreadTaskRunner.java new file mode 100644 index 0000000..b72e6e1 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/task/SingleThreadTaskRunner.java
@@ -0,0 +1,13 @@ +// Copyright 2018 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. + +package org.chromium.base.task; + +/** + * Tasks posted will be run in order on a single thread. Multiple SingleThreadTaskRunners + * can share a single thread. When sharing a thread, mutual exclusion is guaranteed but + * unless specified otherwise by the provider of a given SingleThreadTaskRunner there are + * no ordering guarantees w.r.t. other SingleThreadTaskRunner. + */ +public interface SingleThreadTaskRunner extends SequencedTaskRunner {}
diff --git a/src/base/android/java/src/org/chromium/base/task/TaskRunner.java b/src/base/android/java/src/org/chromium/base/task/TaskRunner.java new file mode 100644 index 0000000..0a2eaac --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/task/TaskRunner.java
@@ -0,0 +1,27 @@ +// Copyright 2018 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. + +package org.chromium.base.task; + +/** + * A task queue that posts Java tasks onto the C++ browser scheduler, if loaded. Otherwise this + * will be backed by an {@link android.os.Handler} or the java thread pool. The TaskQueue interface + * provides no guarantee over the order or the thread on which the task will be executed. + * + * Very similar to {@link java.util.concurrent.Executor} but conforms to chromium terminology. + */ +public interface TaskRunner { + /** + * Posts a task to run immediately. + * + * @param task The task to be run immediately. + */ + public void postTask(Runnable task); + + /** + * Instructs the TaskRunner to initialize the native TaskRunner and migrate any tasks over to + * it. + */ + void initNativeTaskRunner(); +}
diff --git a/src/base/android/java/templates/BuildConfig.template b/src/base/android/java/templates/BuildConfig.template new file mode 100644 index 0000000..8e7342c --- /dev/null +++ b/src/base/android/java/templates/BuildConfig.template
@@ -0,0 +1,74 @@ +// Copyright 2015 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. + +package org.chromium.base; + +#define Q(x) #x +#define QUOTE(x) Q(x) + +#if defined(USE_FINAL) +#define MAYBE_FINAL final +#define MAYBE_ZERO = 0 +#define MAYBE_FALSE = false +#else +#define MAYBE_FINAL +#define MAYBE_ZERO +#define MAYBE_FALSE +#endif + +/** + * Build configuration. Generated on a per-target basis. + */ +public class BuildConfig { + +#if defined(ENABLE_MULTIDEX) + public static MAYBE_FINAL boolean IS_MULTIDEX_ENABLED = true; +#else + public static MAYBE_FINAL boolean IS_MULTIDEX_ENABLED MAYBE_FALSE; +#endif + +#if defined(_FIREBASE_APP_ID) + public static MAYBE_FINAL String FIREBASE_APP_ID = QUOTE(_FIREBASE_APP_ID); +#else + public static MAYBE_FINAL String FIREBASE_APP_ID = ""; +#endif + +#if defined(_DCHECK_IS_ON) + public static MAYBE_FINAL boolean DCHECK_IS_ON = true; +#else + public static MAYBE_FINAL boolean DCHECK_IS_ON MAYBE_FALSE; +#endif + +#if defined(_IS_UBSAN) + public static MAYBE_FINAL boolean IS_UBSAN = true; +#else + public static MAYBE_FINAL boolean IS_UBSAN MAYBE_FALSE; +#endif + + // Sorted list of locales that have a compressed .pak within assets. + // Stored as an array because AssetManager.list() is slow. +#if defined(COMPRESSED_LOCALE_LIST) + public static MAYBE_FINAL String[] COMPRESSED_LOCALES = COMPRESSED_LOCALE_LIST; +#else + public static MAYBE_FINAL String[] COMPRESSED_LOCALES = {}; +#endif + + // Sorted list of locales that have an uncompressed .pak within assets. + // Stored as an array because AssetManager.list() is slow. +#if defined(UNCOMPRESSED_LOCALE_LIST) + public static MAYBE_FINAL String[] UNCOMPRESSED_LOCALES = UNCOMPRESSED_LOCALE_LIST; +#else + public static MAYBE_FINAL String[] UNCOMPRESSED_LOCALES = {}; +#endif + + // The ID of the android string resource that stores the product version. + // This layer of indirection is necessary to make the resource dependency + // optional for android_apk targets/base_java (ex. for cronet). +#if defined(_RESOURCES_VERSION_VARIABLE) + public static MAYBE_FINAL int R_STRING_PRODUCT_VERSION = _RESOURCES_VERSION_VARIABLE; +#else + // Default value, do not use. + public static MAYBE_FINAL int R_STRING_PRODUCT_VERSION MAYBE_ZERO; +#endif +}
diff --git a/src/base/android/java/templates/NativeLibraries.template b/src/base/android/java/templates/NativeLibraries.template new file mode 100644 index 0000000..0d4035c --- /dev/null +++ b/src/base/android/java/templates/NativeLibraries.template
@@ -0,0 +1,109 @@ +// Copyright 2014 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. + +package org.chromium.base.library_loader; + +public class NativeLibraries { + /** + * IMPORTANT NOTE: The variables defined here must _not_ be 'final'. + * + * The reason for this is very subtle: + * + * - This template is used to generate several distinct, but similar + * files used in different contexts: + * + * o .../gen/templates/org/chromium/base/library_loader/NativeLibraries.java + * + * This file is used to build base.jar, which is the library + * jar used by chromium projects. However, the + * corresponding NativeLibraries.class file will _not_ be part + * of the final base.jar. + * + * o .../$PROJECT/native_libraries_java/NativeLibraries.java + * + * This file is used to build an APK (e.g. $PROJECT + * could be 'content_shell_apk'). Its content will depend on + * this target's specific build configuration, and differ from + * the source file above. + * + * - During the final link, all .jar files are linked together into + * a single .dex file, and the second version of NativeLibraries.class + * will be put into the final output file, and used at runtime. + * + * - If the variables were defined as 'final', their value would be + * optimized out inside of 'base.jar', and could not be specialized + * for every chromium program. This, however, doesn't apply to arrays of + * strings, which can be defined as final. + * + * This exotic scheme is used to avoid injecting project-specific, or + * even build-specific, values into the base layer. E.g. this is + * how the component build is supported on Android without modifying + * the sources of each and every Chromium-based target. + */ + + public static final int CPU_FAMILY_UNKNOWN = 0; + public static final int CPU_FAMILY_ARM = 1; + public static final int CPU_FAMILY_MIPS = 2; + public static final int CPU_FAMILY_X86 = 3; + +#if defined(ENABLE_CHROMIUM_LINKER_LIBRARY_IN_ZIP_FILE) && \ + !defined(ENABLE_CHROMIUM_LINKER) +#error "Must have ENABLE_CHROMIUM_LINKER to enable library in zip file" +#endif + + // Set to true to enable the use of the Chromium Linker. +#if defined(ENABLE_CHROMIUM_LINKER) + public static boolean sUseLinker = true; +#else + public static boolean sUseLinker; +#endif + +#if defined(ENABLE_CHROMIUM_LINKER_LIBRARY_IN_ZIP_FILE) + public static boolean sUseLibraryInZipFile = true; +#else + public static boolean sUseLibraryInZipFile; +#endif + +#if defined(ENABLE_CHROMIUM_LINKER_TESTS) + public static boolean sEnableLinkerTests = true; +#else + public static boolean sEnableLinkerTests; +#endif + + // This is the list of native libraries to be loaded (in the correct order) + // by LibraryLoader.java. The base java library is compiled with no + // array defined, and then the build system creates a version of the file + // with the real list of libraries required (which changes based upon which + // .apk is being built). + // TODO(cjhopman): This is public since it is referenced by NativeTestActivity.java + // directly. The two ways of library loading should be refactored into one. + public static final String[] LIBRARIES = +#if defined(NATIVE_LIBRARIES_LIST) + NATIVE_LIBRARIES_LIST; +#else + {}; +#endif + + // This is the expected version of the 'main' native library, which is the one that + // implements the initial set of base JNI functions including + // base::android::nativeGetVersionName() + static String sVersionNumber = +#if defined(NATIVE_LIBRARIES_VERSION_NUMBER) + NATIVE_LIBRARIES_VERSION_NUMBER; +#else + ""; +#endif + + public static int sCpuFamily = +#if defined(ANDROID_APP_CPU_FAMILY_ARM) + CPU_FAMILY_ARM; +#elif defined(ANDROID_APP_CPU_FAMILY_X86) + CPU_FAMILY_X86; +#elif defined(ANDROID_APP_CPU_FAMILY_MIPS) + CPU_FAMILY_MIPS; +#else + CPU_FAMILY_UNKNOWN; +#endif + +}
diff --git a/src/base/android/java_exception_reporter.cc b/src/base/android/java_exception_reporter.cc new file mode 100644 index 0000000..96eb38e --- /dev/null +++ b/src/base/android/java_exception_reporter.cc
@@ -0,0 +1,70 @@ +// Copyright 2015 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. + +#include "base/android/java_exception_reporter.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/debug/dump_without_crashing.h" +#include "jni/JavaExceptionReporter_jni.h" + +using base::android::JavaParamRef; + +namespace base { +namespace android { + +namespace { + +void (*g_java_exception_callback)(const char*); + +} // namespace + +void InitJavaExceptionReporter() { + JNIEnv* env = base::android::AttachCurrentThread(); + constexpr bool crash_after_report = false; + Java_JavaExceptionReporter_installHandler(env, crash_after_report); +} + +void InitJavaExceptionReporterForChildProcess() { + JNIEnv* env = base::android::AttachCurrentThread(); + constexpr bool crash_after_report = true; + Java_JavaExceptionReporter_installHandler(env, crash_after_report); +} + +void SetJavaExceptionCallback(void (*callback)(const char*)) { + DCHECK(!g_java_exception_callback); + g_java_exception_callback = callback; +} + +void SetJavaException(const char* exception) { + DCHECK(g_java_exception_callback); + g_java_exception_callback(exception); +} + +void JNI_JavaExceptionReporter_ReportJavaException( + JNIEnv* env, + const JavaParamRef<jclass>& jcaller, + jboolean crash_after_report, + const JavaParamRef<jthrowable>& e) { + std::string exception_info = base::android::GetJavaExceptionInfo(env, e); + SetJavaException(exception_info.c_str()); + if (crash_after_report) { + LOG(ERROR) << exception_info; + LOG(FATAL) << "Uncaught exception"; + } + base::debug::DumpWithoutCrashing(); + SetJavaException(nullptr); +} + +void JNI_JavaExceptionReporter_ReportJavaStackTrace( + JNIEnv* env, + const JavaParamRef<jclass>& jcaller, + const JavaParamRef<jstring>& stackTrace) { + SetJavaException(ConvertJavaStringToUTF8(stackTrace).c_str()); + base::debug::DumpWithoutCrashing(); + SetJavaException(nullptr); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/java_exception_reporter.h b/src/base/android/java_exception_reporter.h new file mode 100644 index 0000000..070126c --- /dev/null +++ b/src/base/android/java_exception_reporter.h
@@ -0,0 +1,34 @@ +// Copyright 2015 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_ANDROID_JAVA_EXCEPTION_REPORTER_H_ +#define BASE_ANDROID_JAVA_EXCEPTION_REPORTER_H_ + +#include <jni.h> + +#include "base/base_export.h" +#include "starboard/types.h" + +namespace base { +namespace android { + +// Install the exception handler. This should only be called once per process. +BASE_EXPORT void InitJavaExceptionReporter(); + +// Same as above except the handler ensures child process exists immediately +// after an unhandled exception. This is used for child processes because +// DumpWithoutCrashing does not work for child processes on Android. +BASE_EXPORT void InitJavaExceptionReporterForChildProcess(); + +// Sets a callback to be called with the contents of a Java exception, which may +// be nullptr. +BASE_EXPORT void SetJavaExceptionCallback(void (*)(const char* exception)); + +// Calls the Java exception callback, if any, with exception. +void SetJavaException(const char* exception); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_JAVA_EXCEPTION_REPORTER_H_
diff --git a/src/base/android/java_handler_thread.cc b/src/base/android/java_handler_thread.cc new file mode 100644 index 0000000..a4a47bf --- /dev/null +++ b/src/base/android/java_handler_thread.cc
@@ -0,0 +1,149 @@ +// Copyright 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. + +#include "base/android/java_handler_thread.h" + +#include <jni.h> + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/bind.h" +#include "base/run_loop.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/platform_thread_internal_posix.h" +#include "base/threading/thread_id_name_manager.h" +#include "base/threading/thread_restrictions.h" +#include "jni/JavaHandlerThread_jni.h" +#include "starboard/types.h" + +using base::android::AttachCurrentThread; + +namespace base { + +namespace android { + +JavaHandlerThread::JavaHandlerThread(const char* name, + base::ThreadPriority priority) + : JavaHandlerThread( + name, + Java_JavaHandlerThread_create( + AttachCurrentThread(), + ConvertUTF8ToJavaString(AttachCurrentThread(), name), + base::internal::ThreadPriorityToNiceValue(priority))) {} + +JavaHandlerThread::JavaHandlerThread( + const char* name, + const base::android::ScopedJavaLocalRef<jobject>& obj) + : name_(name), java_thread_(obj) {} + +JavaHandlerThread::~JavaHandlerThread() { + JNIEnv* env = base::android::AttachCurrentThread(); + DCHECK(!Java_JavaHandlerThread_isAlive(env, java_thread_)); + DCHECK(!message_loop_ || message_loop_->IsAborted()); + // TODO(mthiesse): We shouldn't leak the MessageLoop as this could affect + // future tests. + if (message_loop_ && message_loop_->IsAborted()) { + // When the message loop has been aborted due to a crash, we intentionally + // leak the message loop because the message loop hasn't been shut down + // properly and would trigger DCHECKS. This should only happen in tests, + // where we handle the exception instead of letting it take down the + // process. + message_loop_.release(); + } +} + +void JavaHandlerThread::Start() { + // Check the thread has not already been started. + DCHECK(!message_loop_); + + JNIEnv* env = base::android::AttachCurrentThread(); + base::WaitableEvent initialize_event( + WaitableEvent::ResetPolicy::AUTOMATIC, + WaitableEvent::InitialState::NOT_SIGNALED); + Java_JavaHandlerThread_startAndInitialize( + env, java_thread_, reinterpret_cast<intptr_t>(this), + reinterpret_cast<intptr_t>(&initialize_event)); + // Wait for thread to be initialized so it is ready to be used when Start + // returns. + base::ThreadRestrictions::ScopedAllowWait wait_allowed; + initialize_event.Wait(); +} + +void JavaHandlerThread::Stop() { + DCHECK(!task_runner()->BelongsToCurrentThread()); + task_runner()->PostTask( + FROM_HERE, + base::BindOnce(&JavaHandlerThread::StopOnThread, base::Unretained(this))); + JNIEnv* env = base::android::AttachCurrentThread(); + Java_JavaHandlerThread_joinThread(env, java_thread_); +} + +void JavaHandlerThread::InitializeThread(JNIEnv* env, + const JavaParamRef<jobject>& obj, + jlong event) { + base::ThreadIdNameManager::GetInstance()->RegisterThread( + base::PlatformThread::CurrentHandle().platform_handle(), + base::PlatformThread::CurrentId()); + + if (name_) + PlatformThread::SetName(name_); + + // TYPE_JAVA to get the Android java style message loop. + message_loop_ = + std::make_unique<MessageLoopForUI>(base::MessageLoop::TYPE_JAVA); + Init(); + reinterpret_cast<base::WaitableEvent*>(event)->Signal(); +} + +void JavaHandlerThread::OnLooperStopped(JNIEnv* env, + const JavaParamRef<jobject>& obj) { + DCHECK(task_runner()->BelongsToCurrentThread()); + message_loop_.reset(); + + CleanUp(); + + base::ThreadIdNameManager::GetInstance()->RemoveName( + base::PlatformThread::CurrentHandle().platform_handle(), + base::PlatformThread::CurrentId()); +} + +void JavaHandlerThread::StopMessageLoopForTesting() { + DCHECK(task_runner()->BelongsToCurrentThread()); + StopOnThread(); +} + +void JavaHandlerThread::JoinForTesting() { + DCHECK(!task_runner()->BelongsToCurrentThread()); + JNIEnv* env = base::android::AttachCurrentThread(); + Java_JavaHandlerThread_joinThread(env, java_thread_); +} + +void JavaHandlerThread::ListenForUncaughtExceptionsForTesting() { + DCHECK(!task_runner()->BelongsToCurrentThread()); + JNIEnv* env = base::android::AttachCurrentThread(); + Java_JavaHandlerThread_listenForUncaughtExceptionsForTesting(env, + java_thread_); +} + +ScopedJavaLocalRef<jthrowable> JavaHandlerThread::GetUncaughtExceptionIfAny() { + DCHECK(!task_runner()->BelongsToCurrentThread()); + JNIEnv* env = base::android::AttachCurrentThread(); + return Java_JavaHandlerThread_getUncaughtExceptionIfAny(env, java_thread_); +} + +void JavaHandlerThread::StopOnThread() { + DCHECK(task_runner()->BelongsToCurrentThread()); + message_loop_->QuitWhenIdle(base::BindOnce( + &JavaHandlerThread::QuitThreadSafely, base::Unretained(this))); +} + +void JavaHandlerThread::QuitThreadSafely() { + DCHECK(task_runner()->BelongsToCurrentThread()); + JNIEnv* env = base::android::AttachCurrentThread(); + Java_JavaHandlerThread_quitThreadSafely(env, java_thread_, + reinterpret_cast<intptr_t>(this)); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/java_handler_thread.h b/src/base/android/java_handler_thread.h new file mode 100644 index 0000000..b973144 --- /dev/null +++ b/src/base/android/java_handler_thread.h
@@ -0,0 +1,98 @@ +// Copyright 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_ANDROID_JAVA_HANDLER_THREAD_H_ +#define BASE_ANDROID_JAVA_HANDLER_THREAD_H_ + +#include <jni.h> + +#include <memory> + +#include "base/android/scoped_java_ref.h" +#include "base/message_loop/message_loop.h" +#include "base/single_thread_task_runner.h" +#include "starboard/types.h" + +namespace base { + +class MessageLoop; + +namespace android { + +// A Java Thread with a native message loop. To run tasks, post them +// to the message loop and they will be scheduled along with Java tasks +// on the thread. +// This is useful for callbacks where the receiver expects a thread +// with a prepared Looper. +class BASE_EXPORT JavaHandlerThread { + public: + // Create new thread. + explicit JavaHandlerThread( + const char* name, + base::ThreadPriority priority = base::ThreadPriority::NORMAL); + // Wrap and connect to an existing JavaHandlerThread. + // |obj| is an instance of JavaHandlerThread. + explicit JavaHandlerThread( + const char* name, + const base::android::ScopedJavaLocalRef<jobject>& obj); + virtual ~JavaHandlerThread(); + + // Called from any thread. + base::MessageLoop* message_loop() const { return message_loop_.get(); } + + // Gets the TaskRunner associated with the message loop. + // Called from any thread. + scoped_refptr<SingleThreadTaskRunner> task_runner() const { + return message_loop_ ? message_loop_->task_runner() : nullptr; + } + + // Called from the parent thread. + void Start(); + void Stop(); + + // Called from java on the newly created thread. + // Start() will not return before this methods has finished. + void InitializeThread(JNIEnv* env, + const JavaParamRef<jobject>& obj, + jlong event); + // Called from java on this thread. + void OnLooperStopped(JNIEnv* env, const JavaParamRef<jobject>& obj); + + // Called from this thread. + void StopMessageLoopForTesting(); + // Called from this thread. + void JoinForTesting(); + + // Called from this thread. + // See comment in JavaHandlerThread.java regarding use of this function. + void ListenForUncaughtExceptionsForTesting(); + // Called from this thread. + ScopedJavaLocalRef<jthrowable> GetUncaughtExceptionIfAny(); + + protected: + // Semantically the same as base::Thread#Init(), but unlike base::Thread the + // Android Looper will already be running. This Init() call will still run + // before other tasks are posted to the thread. + virtual void Init() {} + + // Semantically the same as base::Thread#CleanUp(), called after the message + // loop ends. The Android Looper will also have been quit by this point. + virtual void CleanUp() {} + + std::unique_ptr<base::MessageLoopForUI> message_loop_; + + private: + void StartMessageLoop(); + + void StopOnThread(); + void QuitThreadSafely(); + + const char* name_; + ScopedJavaGlobalRef<jobject> java_thread_; +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_JAVA_HANDLER_THREAD_H_
diff --git a/src/base/android/java_runtime.cc b/src/base/android/java_runtime.cc new file mode 100644 index 0000000..5fae49a --- /dev/null +++ b/src/base/android/java_runtime.cc
@@ -0,0 +1,21 @@ +// Copyright 2015 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. + +#include "base/android/java_runtime.h" + +#include "jni/Runtime_jni.h" + +namespace base { +namespace android { + +void JavaRuntime::GetMemoryUsage(long* total_memory, long* free_memory) { + JNIEnv* env = base::android::AttachCurrentThread(); + base::android::ScopedJavaLocalRef<jobject> runtime = + JNI_Runtime::Java_Runtime_getRuntime(env); + *total_memory = JNI_Runtime::Java_Runtime_totalMemory(env, runtime); + *free_memory = JNI_Runtime::Java_Runtime_freeMemory(env, runtime); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/java_runtime.h b/src/base/android/java_runtime.h new file mode 100644 index 0000000..2034fb9 --- /dev/null +++ b/src/base/android/java_runtime.h
@@ -0,0 +1,25 @@ +// Copyright 2015 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_ANDROID_JAVA_RUNTIME_H_ +#define BASE_ANDROID_JAVA_RUNTIME_H_ + +#include "base/android/scoped_java_ref.h" +#include "base/base_export.h" + +namespace base { +namespace android { + +// Wrapper class for using the java.lang.Runtime object from jni. +class BASE_EXPORT JavaRuntime { + public: + // Fills the total memory used and memory allocated for objects by the java + // heap in the current process. Returns true on success. + static void GetMemoryUsage(long* total_memory, long* free_memory); +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_JAVA_RUNTIME_H_
diff --git a/src/base/android/javatests/src/org/chromium/base/AdvancedMockContextTest.java b/src/base/android/javatests/src/org/chromium/base/AdvancedMockContextTest.java new file mode 100644 index 0000000..20c626d --- /dev/null +++ b/src/base/android/javatests/src/org/chromium/base/AdvancedMockContextTest.java
@@ -0,0 +1,77 @@ +// Copyright 2017 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. + +package org.chromium.base; + +import android.app.Application; +import android.content.ComponentCallbacks; +import android.content.ComponentCallbacks2; +import android.content.Context; +import android.content.res.Configuration; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.BaseJUnit4ClassRunner; +import org.chromium.base.test.util.AdvancedMockContext; + +/** + * Tests for {@link org.chromium.base.test.util.AdvancedMockContext}. + */ +@RunWith(BaseJUnit4ClassRunner.class) +public class AdvancedMockContextTest { + private static class Callback1 implements ComponentCallbacks { + protected Configuration mConfiguration; + protected boolean mOnLowMemoryCalled; + + @Override + public void onConfigurationChanged(Configuration configuration) { + mConfiguration = configuration; + } + + @Override + public void onLowMemory() { + mOnLowMemoryCalled = true; + } + } + + private static class Callback2 extends Callback1 implements ComponentCallbacks2 { + private int mLevel; + + @Override + public void onTrimMemory(int level) { + mLevel = level; + } + } + + @Test + @SmallTest + public void testComponentCallbacksForTargetContext() { + Context targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + Application targetApplication = (Application) targetContext.getApplicationContext(); + AdvancedMockContext context = new AdvancedMockContext(targetContext); + Callback1 callback1 = new Callback1(); + Callback2 callback2 = new Callback2(); + context.registerComponentCallbacks(callback1); + context.registerComponentCallbacks(callback2); + + targetApplication.onLowMemory(); + Assert.assertTrue("onLowMemory should have been called.", callback1.mOnLowMemoryCalled); + Assert.assertTrue("onLowMemory should have been called.", callback2.mOnLowMemoryCalled); + + Configuration configuration = new Configuration(); + targetApplication.onConfigurationChanged(configuration); + Assert.assertEquals("onConfigurationChanged should have been called.", configuration, + callback1.mConfiguration); + Assert.assertEquals("onConfigurationChanged should have been called.", configuration, + callback2.mConfiguration); + + targetApplication.onTrimMemory(ComponentCallbacks2.TRIM_MEMORY_MODERATE); + Assert.assertEquals("onTrimMemory should have been called.", + ComponentCallbacks2.TRIM_MEMORY_MODERATE, callback2.mLevel); + } +}
diff --git a/src/base/android/javatests/src/org/chromium/base/ApiCompatibilityUtilsTest.java b/src/base/android/javatests/src/org/chromium/base/ApiCompatibilityUtilsTest.java new file mode 100644 index 0000000..de0858a --- /dev/null +++ b/src/base/android/javatests/src/org/chromium/base/ApiCompatibilityUtilsTest.java
@@ -0,0 +1,88 @@ +// Copyright 2017 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. + +package org.chromium.base; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.os.Build; +import android.os.SystemClock; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.BaseJUnit4ClassRunner; + +/** + * Test of ApiCompatibilityUtils + */ +@RunWith(BaseJUnit4ClassRunner.class) +public class ApiCompatibilityUtilsTest { + private static final long WAIT_TIMEOUT_IN_MS = 5000; + private static final long SLEEP_INTERVAL_IN_MS = 50; + + static class MockActivity extends Activity { + int mFinishAndRemoveTaskCallbackCount; + int mFinishCallbackCount; + boolean mIsFinishing; + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @Override + public void finishAndRemoveTask() { + mFinishAndRemoveTaskCallbackCount++; + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) mIsFinishing = true; + } + + @Override + public void finish() { + mFinishCallbackCount++; + mIsFinishing = true; + } + + @Override + public boolean isFinishing() { + return mIsFinishing; + } + } + + @Test + @SmallTest + public void testFinishAndRemoveTask() { + InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { + @Override + public void run() { + MockActivity activity = new MockActivity(); + ApiCompatibilityUtils.finishAndRemoveTask(activity); + + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) { + Assert.assertEquals(1, activity.mFinishAndRemoveTaskCallbackCount); + Assert.assertEquals(0, activity.mFinishCallbackCount); + } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) { + long startTime = SystemClock.uptimeMillis(); + while (activity.mFinishCallbackCount == 0 + && SystemClock.uptimeMillis() - startTime < WAIT_TIMEOUT_IN_MS) { + try { + Thread.sleep(SLEEP_INTERVAL_IN_MS); + } catch (InterruptedException e) { + throw new RuntimeException("Interrupted thread sleep", e); + } + } + + // MockActivity#finishAndRemoveTask() never sets isFinishing() to true for + // LOLLIPOP to simulate an exceptional case. In that case, MockActivity#finish() + // should be called after 3 tries. + Assert.assertEquals(3, activity.mFinishAndRemoveTaskCallbackCount); + Assert.assertEquals(1, activity.mFinishCallbackCount); + } else { + Assert.assertEquals(0, activity.mFinishAndRemoveTaskCallbackCount); + Assert.assertEquals(1, activity.mFinishCallbackCount); + } + Assert.assertTrue(activity.mIsFinishing); + } + }); + } +}
diff --git a/src/base/android/javatests/src/org/chromium/base/AssertsTest.java b/src/base/android/javatests/src/org/chromium/base/AssertsTest.java new file mode 100644 index 0000000..37e3b40 --- /dev/null +++ b/src/base/android/javatests/src/org/chromium/base/AssertsTest.java
@@ -0,0 +1,39 @@ +// Copyright 2018 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. + +package org.chromium.base; + +import android.support.test.filters.SmallTest; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.BaseJUnit4ClassRunner; + +/** + * Test that ensures Java asserts are working. + * + * Not a robolectric test because we want to make sure asserts are enabled after dexing. + */ +@RunWith(BaseJUnit4ClassRunner.class) +public class AssertsTest { + @Test + @SmallTest + @SuppressWarnings("UseCorrectAssertInTests") + public void testAssertsWorkAsExpected() { + if (BuildConfig.DCHECK_IS_ON) { + try { + assert false; + } catch (AssertionError e) { + // When DCHECK is on, asserts should throw AssertionErrors. + return; + } + Assert.fail("Java assert unexpectedly didn't fire."); + } else { + // When DCHECK isn't on, asserts should be removed by proguard. + assert false : "Java assert unexpectedly fired."; + } + } +}
diff --git a/src/base/android/javatests/src/org/chromium/base/CommandLineInitUtilTest.java b/src/base/android/javatests/src/org/chromium/base/CommandLineInitUtilTest.java new file mode 100644 index 0000000..a3be5dd --- /dev/null +++ b/src/base/android/javatests/src/org/chromium/base/CommandLineInitUtilTest.java
@@ -0,0 +1,35 @@ +// Copyright 2016 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. + +package org.chromium.base; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.util.CommandLineFlags; +import org.chromium.base.test.util.Feature; + +/** + * Test class for {@link CommandLineInitUtil}. + */ +@RunWith(AndroidJUnit4.class) +public class CommandLineInitUtilTest { + /** + * Verifies that the default command line flags get set for Chrome Public tests. + */ + @Test + @SmallTest + @Feature({"CommandLine"}) + public void testDefaultCommandLineFlagsSet() { + CommandLineInitUtil.initCommandLine(CommandLineFlags.getTestCmdLineFile()); + Assert.assertTrue("CommandLine not initialized.", CommandLine.isInitialized()); + + final CommandLine commandLine = CommandLine.getInstance(); + Assert.assertTrue(commandLine.hasSwitch("enable-test-intents")); + } +}
diff --git a/src/base/android/javatests/src/org/chromium/base/CommandLineTest.java b/src/base/android/javatests/src/org/chromium/base/CommandLineTest.java new file mode 100644 index 0000000..787f85d --- /dev/null +++ b/src/base/android/javatests/src/org/chromium/base/CommandLineTest.java
@@ -0,0 +1,141 @@ +// Copyright 2017 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. + +package org.chromium.base; + +import android.support.test.filters.SmallTest; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.BaseJUnit4ClassRunner; +import org.chromium.base.test.util.Feature; + +/** + * Tests for {@link CommandLine}. + * TODO(bauerb): Convert to local JUnit test + */ +@RunWith(BaseJUnit4ClassRunner.class) +public class CommandLineTest { + // A reference command line. Note that switch2 is [brea\d], switch3 is [and "butter"], + // and switch4 is [a "quoted" 'food'!] + static final String INIT_SWITCHES[] = { "init_command", "--SWITCH", "Arg", + "--switch2=brea\\d", "--switch3=and \"butter\"", + "--switch4=a \"quoted\" 'food'!", + "--", "--actually_an_arg" }; + + // The same command line, but in quoted string format. + static final char INIT_SWITCHES_BUFFER[] = + ("init_command --SWITCH Arg --switch2=brea\\d --switch3=\"and \\\"butt\"er\\\" " + + "--switch4='a \"quoted\" \\'food\\'!' " + + "-- --actually_an_arg").toCharArray(); + + static final String CL_ADDED_SWITCH = "zappo-dappo-doggy-trainer"; + static final String CL_ADDED_SWITCH_2 = "username"; + static final String CL_ADDED_VALUE_2 = "bozo"; + + @Before + public void setUp() throws Exception { + CommandLine.reset(); + } + + void checkInitSwitches() { + CommandLine cl = CommandLine.getInstance(); + Assert.assertFalse(cl.hasSwitch("init_command")); + Assert.assertFalse(cl.hasSwitch("switch")); + Assert.assertTrue(cl.hasSwitch("SWITCH")); + Assert.assertFalse(cl.hasSwitch("--SWITCH")); + Assert.assertFalse(cl.hasSwitch("Arg")); + Assert.assertFalse(cl.hasSwitch("actually_an_arg")); + Assert.assertEquals("brea\\d", cl.getSwitchValue("switch2")); + Assert.assertEquals("and \"butter\"", cl.getSwitchValue("switch3")); + Assert.assertEquals("a \"quoted\" 'food'!", cl.getSwitchValue("switch4")); + Assert.assertNull(cl.getSwitchValue("SWITCH")); + Assert.assertNull(cl.getSwitchValue("non-existant")); + } + + void checkSettingThenGetting() { + CommandLine cl = CommandLine.getInstance(); + + // Add a plain switch. + Assert.assertFalse(cl.hasSwitch(CL_ADDED_SWITCH)); + cl.appendSwitch(CL_ADDED_SWITCH); + Assert.assertTrue(cl.hasSwitch(CL_ADDED_SWITCH)); + + // Add a switch paired with a value. + Assert.assertFalse(cl.hasSwitch(CL_ADDED_SWITCH_2)); + Assert.assertNull(cl.getSwitchValue(CL_ADDED_SWITCH_2)); + cl.appendSwitchWithValue(CL_ADDED_SWITCH_2, CL_ADDED_VALUE_2); + Assert.assertTrue(CL_ADDED_VALUE_2.equals(cl.getSwitchValue(CL_ADDED_SWITCH_2))); + + // Append a few new things. + final String switchesAndArgs[] = { "dummy", "--superfast", "--speed=turbo" }; + Assert.assertFalse(cl.hasSwitch("dummy")); + Assert.assertFalse(cl.hasSwitch("superfast")); + Assert.assertNull(cl.getSwitchValue("speed")); + cl.appendSwitchesAndArguments(switchesAndArgs); + Assert.assertFalse(cl.hasSwitch("dummy")); + Assert.assertFalse(cl.hasSwitch("command")); + Assert.assertTrue(cl.hasSwitch("superfast")); + Assert.assertTrue("turbo".equals(cl.getSwitchValue("speed"))); + } + + void checkTokenizer(String[] expected, String toParse) { + String[] actual = CommandLine.tokenizeQuotedArguments(toParse.toCharArray()); + Assert.assertEquals(expected.length, actual.length); + for (int i = 0; i < expected.length; ++i) { + Assert.assertEquals("comparing element " + i, expected[i], actual[i]); + } + } + + @Test + @SmallTest + @Feature({"Android-AppBase"}) + public void testJavaInitialization() { + CommandLine.init(INIT_SWITCHES); + checkInitSwitches(); + checkSettingThenGetting(); + } + + @Test + @SmallTest + @Feature({"Android-AppBase"}) + public void testBufferInitialization() { + CommandLine.init(CommandLine.tokenizeQuotedArguments(INIT_SWITCHES_BUFFER)); + checkInitSwitches(); + checkSettingThenGetting(); + } + + @Test + @SmallTest + @Feature({"Android-AppBase"}) + public void testArgumentTokenizer() { + String toParse = " a\"\\bc de\\\"f g\"\\h ij k\" \"lm"; + String[] expected = { "a\\bc de\"f g\\h", + "ij", + "k lm" }; + checkTokenizer(expected, toParse); + + toParse = ""; + expected = new String[0]; + checkTokenizer(expected, toParse); + + toParse = " \t\n"; + checkTokenizer(expected, toParse); + + toParse = " \"a'b\" 'c\"d' \"e\\\"f\" 'g\\'h' \"i\\'j\" 'k\\\"l'" + + " m\"n\\'o\"p q'r\\\"s't"; + expected = new String[] { "a'b", + "c\"d", + "e\"f", + "g'h", + "i\\'j", + "k\\\"l", + "mn\\'op", + "qr\\\"st"}; + checkTokenizer(expected, toParse); + } +}
diff --git a/src/base/android/javatests/src/org/chromium/base/EarlyTraceEventTest.java b/src/base/android/javatests/src/org/chromium/base/EarlyTraceEventTest.java new file mode 100644 index 0000000..dfd2320 --- /dev/null +++ b/src/base/android/javatests/src/org/chromium/base/EarlyTraceEventTest.java
@@ -0,0 +1,305 @@ +// Copyright 2017 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. + +package org.chromium.base; + +import static org.chromium.base.EarlyTraceEvent.AsyncEvent; +import static org.chromium.base.EarlyTraceEvent.Event; + +import android.os.Process; +import android.support.test.filters.SmallTest; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.library_loader.LibraryLoader; +import org.chromium.base.library_loader.LibraryProcessType; +import org.chromium.base.test.BaseJUnit4ClassRunner; +import org.chromium.base.test.util.Feature; + +/** + * Tests for {@link EarlyTraceEvent}. + * + * TODO(lizeb): Move to roboelectric tests. + */ +@RunWith(BaseJUnit4ClassRunner.class) +public class EarlyTraceEventTest { + private static final String EVENT_NAME = "MyEvent"; + private static final String EVENT_NAME2 = "MyOtherEvent"; + private static final long EVENT_ID = 1; + private static final long EVENT_ID2 = 2; + + @Before + public void setUp() throws Exception { + LibraryLoader.getInstance().ensureInitialized(LibraryProcessType.PROCESS_BROWSER); + EarlyTraceEvent.resetForTesting(); + } + + @Test + @SmallTest + @Feature({"Android-AppBase"}) + public void testCanRecordEvent() { + EarlyTraceEvent.enable(); + long myThreadId = Process.myTid(); + long beforeNanos = Event.elapsedRealtimeNanos(); + EarlyTraceEvent.begin(EVENT_NAME); + EarlyTraceEvent.end(EVENT_NAME); + long afterNanos = Event.elapsedRealtimeNanos(); + + Assert.assertEquals(1, EarlyTraceEvent.sCompletedEvents.size()); + Assert.assertTrue(EarlyTraceEvent.sPendingEventByKey.isEmpty()); + Event event = EarlyTraceEvent.sCompletedEvents.get(0); + Assert.assertEquals(EVENT_NAME, event.mName); + Assert.assertEquals(myThreadId, event.mThreadId); + Assert.assertTrue( + beforeNanos <= event.mBeginTimeNanos && event.mBeginTimeNanos <= afterNanos); + Assert.assertTrue(event.mBeginTimeNanos <= event.mEndTimeNanos); + Assert.assertTrue(beforeNanos <= event.mEndTimeNanos && event.mEndTimeNanos <= afterNanos); + } + + @Test + @SmallTest + @Feature({"Android-AppBase"}) + public void testCanRecordAsyncEvent() { + EarlyTraceEvent.enable(); + long beforeNanos = Event.elapsedRealtimeNanos(); + EarlyTraceEvent.startAsync(EVENT_NAME, EVENT_ID); + EarlyTraceEvent.finishAsync(EVENT_NAME, EVENT_ID); + long afterNanos = Event.elapsedRealtimeNanos(); + + Assert.assertEquals(2, EarlyTraceEvent.sAsyncEvents.size()); + Assert.assertTrue(EarlyTraceEvent.sPendingEventByKey.isEmpty()); + AsyncEvent eventStart = EarlyTraceEvent.sAsyncEvents.get(0); + AsyncEvent eventEnd = EarlyTraceEvent.sAsyncEvents.get(1); + Assert.assertEquals(EVENT_NAME, eventStart.mName); + Assert.assertEquals(EVENT_ID, eventStart.mId); + Assert.assertEquals(EVENT_NAME, eventEnd.mName); + Assert.assertEquals(EVENT_ID, eventEnd.mId); + Assert.assertTrue(beforeNanos <= eventStart.mTimestampNanos + && eventEnd.mTimestampNanos <= afterNanos); + Assert.assertTrue(eventStart.mTimestampNanos <= eventEnd.mTimestampNanos); + } + + @Test + @SmallTest + @Feature({"Android-AppBase"}) + public void testRecordAsyncFinishEventWhenFinishing() { + EarlyTraceEvent.enable(); + EarlyTraceEvent.startAsync(EVENT_NAME, EVENT_ID); + EarlyTraceEvent.disable(); + + Assert.assertEquals(EarlyTraceEvent.STATE_FINISHING, EarlyTraceEvent.sState); + Assert.assertTrue(EarlyTraceEvent.sAsyncEvents.isEmpty()); + Assert.assertEquals(1, EarlyTraceEvent.sPendingAsyncEvents.size()); + EarlyTraceEvent.finishAsync(EVENT_NAME, EVENT_ID); + Assert.assertEquals(EarlyTraceEvent.STATE_FINISHED, EarlyTraceEvent.sState); + } + + @Test + @SmallTest + @Feature({"Android-AppBase"}) + public void testCanRecordEventUsingTryWith() { + EarlyTraceEvent.enable(); + long myThreadId = Process.myTid(); + long beforeNanos = Event.elapsedRealtimeNanos(); + try (TraceEvent e = TraceEvent.scoped(EVENT_NAME)) { + // Required comment to pass presubmit checks. + } + long afterNanos = Event.elapsedRealtimeNanos(); + + Assert.assertEquals(1, EarlyTraceEvent.sCompletedEvents.size()); + Assert.assertTrue(EarlyTraceEvent.sPendingEventByKey.isEmpty()); + Event event = EarlyTraceEvent.sCompletedEvents.get(0); + Assert.assertEquals(EVENT_NAME, event.mName); + Assert.assertEquals(myThreadId, event.mThreadId); + Assert.assertTrue( + beforeNanos <= event.mBeginTimeNanos && event.mBeginTimeNanos <= afterNanos); + Assert.assertTrue(event.mBeginTimeNanos <= event.mEndTimeNanos); + Assert.assertTrue(beforeNanos <= event.mEndTimeNanos && event.mEndTimeNanos <= afterNanos); + } + + @Test + @SmallTest + @Feature({"Android-AppBase"}) + public void testIncompleteEvent() { + EarlyTraceEvent.enable(); + EarlyTraceEvent.begin(EVENT_NAME); + + Assert.assertTrue(EarlyTraceEvent.sCompletedEvents.isEmpty()); + Assert.assertEquals(1, EarlyTraceEvent.sPendingEventByKey.size()); + EarlyTraceEvent.Event event = EarlyTraceEvent.sPendingEventByKey.get( + EarlyTraceEvent.makeEventKeyForCurrentThread(EVENT_NAME)); + Assert.assertEquals(EVENT_NAME, event.mName); + } + + @Test + @SmallTest + @Feature({"Android-AppBase"}) + public void testNoDuplicatePendingEventsFromSameThread() { + EarlyTraceEvent.enable(); + EarlyTraceEvent.begin(EVENT_NAME); + try { + EarlyTraceEvent.begin(EVENT_NAME); + } catch (IllegalArgumentException e) { + // Expected. + return; + } + Assert.fail(); + } + + @Test + @SmallTest + @Feature({"Android-AppBase"}) + public void testDuplicatePendingEventsFromDifferentThreads() throws Exception { + EarlyTraceEvent.enable(); + + Thread otherThread = new Thread(() -> { EarlyTraceEvent.begin(EVENT_NAME); }); + otherThread.start(); + otherThread.join(); + + // At this point we have a pending event with EVENT_NAME name. But events are per + // thread, so we should be able to start EVENT_NAME event in a different thread. + EarlyTraceEvent.begin(EVENT_NAME); + } + + @Test + @SmallTest + @Feature({"Android-AppBase"}) + public void testIgnoreEventsWhenDisabled() { + EarlyTraceEvent.begin(EVENT_NAME); + EarlyTraceEvent.end(EVENT_NAME); + try (TraceEvent e = TraceEvent.scoped(EVENT_NAME2)) { + // Required comment to pass presubmit checks. + } + Assert.assertNull(EarlyTraceEvent.sCompletedEvents); + } + + @Test + @SmallTest + @Feature({"Android-AppBase"}) + public void testIgnoreAsyncEventsWhenDisabled() { + EarlyTraceEvent.startAsync(EVENT_NAME, EVENT_ID); + EarlyTraceEvent.finishAsync(EVENT_NAME, EVENT_ID); + Assert.assertNull(EarlyTraceEvent.sAsyncEvents); + } + + @Test + @SmallTest + @Feature({"Android-AppBase"}) + public void testIgnoreNewEventsWhenFinishing() { + EarlyTraceEvent.enable(); + EarlyTraceEvent.begin(EVENT_NAME); + EarlyTraceEvent.disable(); + + Assert.assertEquals(EarlyTraceEvent.STATE_FINISHING, EarlyTraceEvent.sState); + EarlyTraceEvent.begin(EVENT_NAME2); + EarlyTraceEvent.end(EVENT_NAME2); + + Assert.assertEquals(1, EarlyTraceEvent.sPendingEventByKey.size()); + Assert.assertTrue(EarlyTraceEvent.sCompletedEvents.isEmpty()); + } + + @Test + @SmallTest + @Feature({"Android-AppBase"}) + public void testIgnoreNewAsyncEventsWhenFinishing() { + EarlyTraceEvent.enable(); + EarlyTraceEvent.startAsync(EVENT_NAME, EVENT_ID); + EarlyTraceEvent.disable(); + + Assert.assertEquals(EarlyTraceEvent.STATE_FINISHING, EarlyTraceEvent.sState); + EarlyTraceEvent.startAsync(EVENT_NAME2, EVENT_ID2); + + Assert.assertEquals(1, EarlyTraceEvent.sPendingAsyncEvents.size()); + Assert.assertTrue(EarlyTraceEvent.sAsyncEvents.isEmpty()); + } + + @Test + @SmallTest + @Feature({"Android-AppBase"}) + public void testFinishingToFinished() { + EarlyTraceEvent.enable(); + EarlyTraceEvent.begin(EVENT_NAME); + EarlyTraceEvent.disable(); + + Assert.assertEquals(EarlyTraceEvent.STATE_FINISHING, EarlyTraceEvent.sState); + EarlyTraceEvent.begin(EVENT_NAME2); + EarlyTraceEvent.end(EVENT_NAME2); + EarlyTraceEvent.end(EVENT_NAME); + + Assert.assertEquals(EarlyTraceEvent.STATE_FINISHED, EarlyTraceEvent.sState); + } + + @Test + @SmallTest + @Feature({"Android-AppBase"}) + public void testCannotBeReenabledOnceFinished() { + EarlyTraceEvent.enable(); + EarlyTraceEvent.begin(EVENT_NAME); + EarlyTraceEvent.end(EVENT_NAME); + EarlyTraceEvent.disable(); + Assert.assertEquals(EarlyTraceEvent.STATE_FINISHED, EarlyTraceEvent.sState); + + EarlyTraceEvent.enable(); + Assert.assertEquals(EarlyTraceEvent.STATE_FINISHED, EarlyTraceEvent.sState); + } + + @Test + @SmallTest + @Feature({"Android-AppBase"}) + public void testThreadIdIsRecorded() throws Exception { + EarlyTraceEvent.enable(); + final long[] threadId = {0}; + + Thread thread = new Thread() { + @Override + public void run() { + TraceEvent.begin(EVENT_NAME); + threadId[0] = Process.myTid(); + TraceEvent.end(EVENT_NAME); + } + }; + thread.start(); + thread.join(); + + Assert.assertEquals(1, EarlyTraceEvent.sCompletedEvents.size()); + EarlyTraceEvent.Event event = EarlyTraceEvent.sCompletedEvents.get(0); + Assert.assertEquals(threadId[0], event.mThreadId); + } + + @Test + @SmallTest + @Feature({"Android-AppBase"}) + public void testEnableAtStartup() { + ThreadUtils.setThreadAssertsDisabledForTesting(true); + EarlyTraceEvent.maybeEnable(); + Assert.assertFalse(EarlyTraceEvent.enabled()); + EarlyTraceEvent.setBackgroundStartupTracingFlag(false); + Assert.assertFalse(EarlyTraceEvent.enabled()); + + EarlyTraceEvent.setBackgroundStartupTracingFlag(true); + EarlyTraceEvent.maybeEnable(); + Assert.assertTrue(EarlyTraceEvent.getBackgroundStartupTracingFlag()); + Assert.assertTrue(EarlyTraceEvent.enabled()); + EarlyTraceEvent.disable(); + EarlyTraceEvent.setBackgroundStartupTracingFlag(false); + } + + @Test + @SmallTest + @Feature({"Android-AppBase"}) + public void testUserOverrideBackgroundTracing() { + ThreadUtils.setThreadAssertsDisabledForTesting(true); + // Setting command line should disable the background tracing flag. + CommandLine.getInstance().appendSwitch("trace-startup"); + EarlyTraceEvent.setBackgroundStartupTracingFlag(true); + EarlyTraceEvent.maybeEnable(); + Assert.assertFalse(EarlyTraceEvent.getBackgroundStartupTracingFlag()); + Assert.assertTrue(EarlyTraceEvent.enabled()); + EarlyTraceEvent.disable(); + EarlyTraceEvent.setBackgroundStartupTracingFlag(false); + } +}
diff --git a/src/base/android/javatests/src/org/chromium/base/LocaleUtilsTest.java b/src/base/android/javatests/src/org/chromium/base/LocaleUtilsTest.java new file mode 100644 index 0000000..fc37a1f --- /dev/null +++ b/src/base/android/javatests/src/org/chromium/base/LocaleUtilsTest.java
@@ -0,0 +1,262 @@ +// Copyright 2017 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. + +package org.chromium.base; + +import android.annotation.SuppressLint; +import android.os.Build; +import android.os.LocaleList; +import android.support.test.filters.SmallTest; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.BaseJUnit4ClassRunner; +import org.chromium.base.test.util.MinAndroidSdkLevel; + +import java.util.Locale; + +/** + * Tests for the LocaleUtils class. + */ +@RunWith(BaseJUnit4ClassRunner.class) +public class LocaleUtilsTest { + // This is also a part of test for toLanguageTag when API level is lower than 24 + @Test + @SmallTest + public void testGetUpdatedLanguageForChromium() { + String language = "en"; + String updatedLanguage = LocaleUtils.getUpdatedLanguageForChromium(language); + Assert.assertEquals(language, updatedLanguage); + + language = "iw"; + updatedLanguage = LocaleUtils.getUpdatedLanguageForChromium(language); + Assert.assertEquals("he", updatedLanguage); + + language = "ji"; + updatedLanguage = LocaleUtils.getUpdatedLanguageForChromium(language); + Assert.assertEquals("yi", updatedLanguage); + + language = "in"; + updatedLanguage = LocaleUtils.getUpdatedLanguageForChromium(language); + Assert.assertEquals("id", updatedLanguage); + + language = "tl"; + updatedLanguage = LocaleUtils.getUpdatedLanguageForChromium(language); + Assert.assertEquals("fil", updatedLanguage); + } + + // This is also a part of test for toLanguageTags when API level is 24 or higher + @Test + @SmallTest + @MinAndroidSdkLevel(Build.VERSION_CODES.LOLLIPOP) + public void testGetUpdatedLocaleForChromium() { + Locale locale = new Locale("jp"); + Locale updatedLocale = LocaleUtils.getUpdatedLocaleForChromium(locale); + Assert.assertEquals(locale, updatedLocale); + + locale = new Locale("iw"); + updatedLocale = LocaleUtils.getUpdatedLocaleForChromium(locale); + Assert.assertEquals(new Locale("he"), updatedLocale); + + locale = new Locale("ji"); + updatedLocale = LocaleUtils.getUpdatedLocaleForChromium(locale); + Assert.assertEquals(new Locale("yi"), updatedLocale); + + locale = new Locale("in"); + updatedLocale = LocaleUtils.getUpdatedLocaleForChromium(locale); + Assert.assertEquals(new Locale("id"), updatedLocale); + + locale = new Locale("tl"); + updatedLocale = LocaleUtils.getUpdatedLocaleForChromium(locale); + Assert.assertEquals(new Locale("fil"), updatedLocale); + } + + // This is also a part of test for forLanguageTag when API level is lower than 21 + @Test + @SmallTest + public void testGetUpdatedLanguageForAndroid() { + String language = "en"; + String updatedLanguage = LocaleUtils.getUpdatedLanguageForAndroid(language); + Assert.assertEquals(language, updatedLanguage); + + language = "und"; + updatedLanguage = LocaleUtils.getUpdatedLanguageForAndroid(language); + Assert.assertEquals("", updatedLanguage); + + language = "fil"; + updatedLanguage = LocaleUtils.getUpdatedLanguageForAndroid(language); + Assert.assertEquals("tl", updatedLanguage); + } + + // This is also a part of test for forLanguageTag when API level is 21 or higher + @Test + @SmallTest + @MinAndroidSdkLevel(Build.VERSION_CODES.LOLLIPOP) + public void testGetUpdatedLocaleForAndroid() { + Locale locale = new Locale("jp"); + Locale updatedLocale = LocaleUtils.getUpdatedLocaleForAndroid(locale); + Assert.assertEquals(locale, updatedLocale); + + locale = new Locale("und"); + updatedLocale = LocaleUtils.getUpdatedLocaleForAndroid(locale); + Assert.assertEquals(new Locale(""), updatedLocale); + + locale = new Locale("fil"); + updatedLocale = LocaleUtils.getUpdatedLocaleForAndroid(locale); + Assert.assertEquals(new Locale("tl"), updatedLocale); + } + + // Test for toLanguageTag when API level is lower than 24 + @Test + @SmallTest + public void testToLanguageTag() { + Locale locale = new Locale("en", "US"); + String localeString = LocaleUtils.toLanguageTag(locale); + Assert.assertEquals("en-US", localeString); + + locale = new Locale("jp"); + localeString = LocaleUtils.toLanguageTag(locale); + Assert.assertEquals("jp", localeString); + + locale = new Locale("mas"); + localeString = LocaleUtils.toLanguageTag(locale); + Assert.assertEquals("mas", localeString); + + locale = new Locale("es", "005"); + localeString = LocaleUtils.toLanguageTag(locale); + Assert.assertEquals("es-005", localeString); + + locale = new Locale("iw"); + localeString = LocaleUtils.toLanguageTag(locale); + Assert.assertEquals("he", localeString); + + locale = new Locale("ji"); + localeString = LocaleUtils.toLanguageTag(locale); + Assert.assertEquals("yi", localeString); + + locale = new Locale("in", "ID"); + localeString = LocaleUtils.toLanguageTag(locale); + Assert.assertEquals("id-ID", localeString); + + locale = new Locale("tl", "PH"); + localeString = LocaleUtils.toLanguageTag(locale); + Assert.assertEquals("fil-PH", localeString); + + locale = new Locale("no", "NO", "NY"); + localeString = LocaleUtils.toLanguageTag(locale); + Assert.assertEquals("nn-NO", localeString); + } + + // Test for toLanguageTags when API level is 24 or higher + @Test + @SmallTest + @MinAndroidSdkLevel(Build.VERSION_CODES.N) + @SuppressLint("NewApi") + public void testToLanguageTags() { + Locale locale1 = new Locale("en", "US"); + Locale locale2 = new Locale("es", "005"); + LocaleList localeList = new LocaleList(locale1, locale2); + String localeString = LocaleUtils.toLanguageTags(localeList); + Assert.assertEquals("en-US,es-005", localeString); + + locale1 = new Locale("jp"); + locale2 = new Locale("mas"); + localeList = new LocaleList(locale1, locale2); + localeString = LocaleUtils.toLanguageTags(localeList); + Assert.assertEquals("jp,mas", localeString); + + locale1 = new Locale("iw"); + locale2 = new Locale("ji"); + localeList = new LocaleList(locale1, locale2); + localeString = LocaleUtils.toLanguageTags(localeList); + Assert.assertEquals("he,yi", localeString); + + locale1 = new Locale("in", "ID"); + locale2 = new Locale("tl", "PH"); + localeList = new LocaleList(locale1, locale2); + localeString = LocaleUtils.toLanguageTags(localeList); + Assert.assertEquals("id-ID,fil-PH", localeString); + + locale1 = new Locale("no", "NO", "NY"); + localeList = new LocaleList(locale1); + localeString = LocaleUtils.toLanguageTags(localeList); + Assert.assertEquals("nn-NO", localeString); + } + + // Test for forLanguageTag when API level is lower than 21 + @Test + @SmallTest + public void testForLanguageTagCompat() { + String languageTag = ""; + Locale locale = new Locale(""); + Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag)); + + languageTag = "und"; + locale = new Locale(""); + Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag)); + + languageTag = "en"; + locale = new Locale("en"); + Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag)); + + languageTag = "mas"; + locale = new Locale("mas"); + Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag)); + + languageTag = "en-GB"; + locale = new Locale("en", "GB"); + Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag)); + + languageTag = "es-419"; + locale = new Locale("es", "419"); + Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag)); + + // Tests if updated Chromium language code and deprecated language code + // are pointing to the same Locale Object. + languageTag = "he"; + locale = new Locale("iw"); + Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag)); + + languageTag = "iw"; + locale = new Locale("he"); + Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag)); + + languageTag = "ji"; + locale = new Locale("yi"); + Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag)); + + languageTag = "yi"; + locale = new Locale("ji"); + Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag)); + + languageTag = "in"; + locale = new Locale("id"); + Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag)); + + languageTag = "id"; + locale = new Locale("in"); + Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag)); + + // Tests for Tagalog/Filipino if updated Chromium language code and + // language code are pointing to the same Locale Object. + languageTag = "tl"; + locale = new Locale("tl"); + Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag)); + + languageTag = "fil"; + locale = new Locale("tl"); + Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag)); + + // Test with invalid inputs. + languageTag = "notValidLanguage"; + locale = new Locale(""); + Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag)); + + languageTag = "en-notValidCountry"; + locale = new Locale("en"); + Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag)); + } +}
diff --git a/src/base/android/javatests/src/org/chromium/base/ObserverListTest.java b/src/base/android/javatests/src/org/chromium/base/ObserverListTest.java new file mode 100644 index 0000000..96d1b6e --- /dev/null +++ b/src/base/android/javatests/src/org/chromium/base/ObserverListTest.java
@@ -0,0 +1,340 @@ +// Copyright 2017 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. + +package org.chromium.base; + +import android.support.test.filters.SmallTest; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.BaseJUnit4ClassRunner; +import org.chromium.base.test.util.Feature; + +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Tests for (@link ObserverList}. + */ +@RunWith(BaseJUnit4ClassRunner.class) +public class ObserverListTest { + interface Observer { + void observe(int x); + } + + private static class Foo implements Observer { + private final int mScalar; + private int mTotal; + + Foo(int scalar) { + mScalar = scalar; + } + + @Override + public void observe(int x) { + mTotal += x * mScalar; + } + } + + /** + * An observer which add a given Observer object to the list when observe is called. + */ + private static class FooAdder implements Observer { + private final ObserverList<Observer> mList; + private final Observer mLucky; + + FooAdder(ObserverList<Observer> list, Observer oblivious) { + mList = list; + mLucky = oblivious; + } + + @Override + public void observe(int x) { + mList.addObserver(mLucky); + } + } + + /** + * An observer which removes a given Observer object from the list when observe is called. + */ + private static class FooRemover implements Observer { + private final ObserverList<Observer> mList; + private final Observer mDoomed; + + FooRemover(ObserverList<Observer> list, Observer innocent) { + mList = list; + mDoomed = innocent; + } + + @Override + public void observe(int x) { + mList.removeObserver(mDoomed); + } + } + + private static <T> int getSizeOfIterable(Iterable<T> iterable) { + if (iterable instanceof Collection<?>) return ((Collection<?>) iterable).size(); + int num = 0; + for (T el : iterable) num++; + return num; + } + + @Test + @SmallTest + @Feature({"Android-AppBase"}) + public void testRemoveWhileIteration() { + ObserverList<Observer> observerList = new ObserverList<Observer>(); + Foo a = new Foo(1); + Foo b = new Foo(-1); + Foo c = new Foo(1); + Foo d = new Foo(-1); + Foo e = new Foo(-1); + FooRemover evil = new FooRemover(observerList, c); + + observerList.addObserver(a); + observerList.addObserver(b); + + for (Observer obs : observerList) obs.observe(10); + + // Removing an observer not in the list should do nothing. + observerList.removeObserver(e); + + observerList.addObserver(evil); + observerList.addObserver(c); + observerList.addObserver(d); + + for (Observer obs : observerList) obs.observe(10); + + // observe should be called twice on a. + Assert.assertEquals(20, a.mTotal); + // observe should be called twice on b. + Assert.assertEquals(-20, b.mTotal); + // evil removed c from the observerList before it got any callbacks. + Assert.assertEquals(0, c.mTotal); + // observe should be called once on d. + Assert.assertEquals(-10, d.mTotal); + // e was never added to the list, observe should not be called. + Assert.assertEquals(0, e.mTotal); + } + + @Test + @SmallTest + @Feature({"Android-AppBase"}) + public void testAddWhileIteration() { + ObserverList<Observer> observerList = new ObserverList<Observer>(); + Foo a = new Foo(1); + Foo b = new Foo(-1); + Foo c = new Foo(1); + FooAdder evil = new FooAdder(observerList, c); + + observerList.addObserver(evil); + observerList.addObserver(a); + observerList.addObserver(b); + + for (Observer obs : observerList) obs.observe(10); + + Assert.assertTrue(observerList.hasObserver(c)); + Assert.assertEquals(10, a.mTotal); + Assert.assertEquals(-10, b.mTotal); + Assert.assertEquals(0, c.mTotal); + } + + @Test + @SmallTest + @Feature({"Android-AppBase"}) + public void testIterator() { + ObserverList<Integer> observerList = new ObserverList<Integer>(); + observerList.addObserver(5); + observerList.addObserver(10); + observerList.addObserver(15); + Assert.assertEquals(3, getSizeOfIterable(observerList)); + + observerList.removeObserver(10); + Assert.assertEquals(2, getSizeOfIterable(observerList)); + + Iterator<Integer> it = observerList.iterator(); + Assert.assertTrue(it.hasNext()); + Assert.assertTrue(5 == it.next()); + Assert.assertTrue(it.hasNext()); + Assert.assertTrue(15 == it.next()); + Assert.assertFalse(it.hasNext()); + + boolean removeExceptionThrown = false; + try { + it.remove(); + Assert.fail("Expecting UnsupportedOperationException to be thrown here."); + } catch (UnsupportedOperationException e) { + removeExceptionThrown = true; + } + Assert.assertTrue(removeExceptionThrown); + Assert.assertEquals(2, getSizeOfIterable(observerList)); + + boolean noElementExceptionThrown = false; + try { + it.next(); + Assert.fail("Expecting NoSuchElementException to be thrown here."); + } catch (NoSuchElementException e) { + noElementExceptionThrown = true; + } + Assert.assertTrue(noElementExceptionThrown); + } + + @Test + @SmallTest + @Feature({"Android-AppBase"}) + public void testRewindableIterator() { + ObserverList<Integer> observerList = new ObserverList<Integer>(); + observerList.addObserver(5); + observerList.addObserver(10); + observerList.addObserver(15); + Assert.assertEquals(3, getSizeOfIterable(observerList)); + + ObserverList.RewindableIterator<Integer> it = observerList.rewindableIterator(); + Assert.assertTrue(it.hasNext()); + Assert.assertTrue(5 == it.next()); + Assert.assertTrue(it.hasNext()); + Assert.assertTrue(10 == it.next()); + Assert.assertTrue(it.hasNext()); + Assert.assertTrue(15 == it.next()); + Assert.assertFalse(it.hasNext()); + + it.rewind(); + + Assert.assertTrue(it.hasNext()); + Assert.assertTrue(5 == it.next()); + Assert.assertTrue(it.hasNext()); + Assert.assertTrue(10 == it.next()); + Assert.assertTrue(it.hasNext()); + Assert.assertTrue(15 == it.next()); + Assert.assertEquals(5, (int) observerList.mObservers.get(0)); + observerList.removeObserver(5); + Assert.assertEquals(null, observerList.mObservers.get(0)); + + it.rewind(); + + Assert.assertEquals(10, (int) observerList.mObservers.get(0)); + Assert.assertTrue(it.hasNext()); + Assert.assertTrue(10 == it.next()); + Assert.assertTrue(it.hasNext()); + Assert.assertTrue(15 == it.next()); + } + + @Test + @SmallTest + @Feature({"Android-AppBase"}) + public void testAddObserverReturnValue() { + ObserverList<Object> observerList = new ObserverList<Object>(); + + Object a = new Object(); + Assert.assertTrue(observerList.addObserver(a)); + Assert.assertFalse(observerList.addObserver(a)); + + Object b = new Object(); + Assert.assertTrue(observerList.addObserver(b)); + Assert.assertFalse(observerList.addObserver(null)); + } + + @Test + @SmallTest + @Feature({"Android-AppBase"}) + public void testRemoveObserverReturnValue() { + ObserverList<Object> observerList = new ObserverList<Object>(); + + Object a = new Object(); + Object b = new Object(); + observerList.addObserver(a); + observerList.addObserver(b); + + Assert.assertTrue(observerList.removeObserver(a)); + Assert.assertFalse(observerList.removeObserver(a)); + Assert.assertFalse(observerList.removeObserver(new Object())); + Assert.assertTrue(observerList.removeObserver(b)); + Assert.assertFalse(observerList.removeObserver(null)); + + // If we remove an object while iterating, it will be replaced by 'null'. + observerList.addObserver(a); + Assert.assertTrue(observerList.removeObserver(a)); + Assert.assertFalse(observerList.removeObserver(null)); + } + + @Test + @SmallTest + @Feature({"Android-AppBase"}) + public void testSize() { + ObserverList<Object> observerList = new ObserverList<Object>(); + + Assert.assertEquals(0, observerList.size()); + Assert.assertTrue(observerList.isEmpty()); + + observerList.addObserver(null); + Assert.assertEquals(0, observerList.size()); + Assert.assertTrue(observerList.isEmpty()); + + Object a = new Object(); + observerList.addObserver(a); + Assert.assertEquals(1, observerList.size()); + Assert.assertFalse(observerList.isEmpty()); + + observerList.addObserver(a); + Assert.assertEquals(1, observerList.size()); + Assert.assertFalse(observerList.isEmpty()); + + observerList.addObserver(null); + Assert.assertEquals(1, observerList.size()); + Assert.assertFalse(observerList.isEmpty()); + + Object b = new Object(); + observerList.addObserver(b); + Assert.assertEquals(2, observerList.size()); + Assert.assertFalse(observerList.isEmpty()); + + observerList.removeObserver(null); + Assert.assertEquals(2, observerList.size()); + Assert.assertFalse(observerList.isEmpty()); + + observerList.removeObserver(new Object()); + Assert.assertEquals(2, observerList.size()); + Assert.assertFalse(observerList.isEmpty()); + + observerList.removeObserver(b); + Assert.assertEquals(1, observerList.size()); + Assert.assertFalse(observerList.isEmpty()); + + observerList.removeObserver(b); + Assert.assertEquals(1, observerList.size()); + Assert.assertFalse(observerList.isEmpty()); + + observerList.removeObserver(a); + Assert.assertEquals(0, observerList.size()); + Assert.assertTrue(observerList.isEmpty()); + + observerList.removeObserver(a); + observerList.removeObserver(b); + observerList.removeObserver(null); + observerList.removeObserver(new Object()); + Assert.assertEquals(0, observerList.size()); + Assert.assertTrue(observerList.isEmpty()); + + observerList.addObserver(new Object()); + observerList.addObserver(new Object()); + observerList.addObserver(new Object()); + observerList.addObserver(a); + Assert.assertEquals(4, observerList.size()); + Assert.assertFalse(observerList.isEmpty()); + + observerList.clear(); + Assert.assertEquals(0, observerList.size()); + Assert.assertTrue(observerList.isEmpty()); + + observerList.removeObserver(a); + observerList.removeObserver(b); + observerList.removeObserver(null); + observerList.removeObserver(new Object()); + Assert.assertEquals(0, observerList.size()); + Assert.assertTrue(observerList.isEmpty()); + } +}
diff --git a/src/base/android/javatests/src/org/chromium/base/StrictModeContextTest.java b/src/base/android/javatests/src/org/chromium/base/StrictModeContextTest.java new file mode 100644 index 0000000..59f38ed --- /dev/null +++ b/src/base/android/javatests/src/org/chromium/base/StrictModeContextTest.java
@@ -0,0 +1,118 @@ +// Copyright 2017 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. + +package org.chromium.base; + +import android.os.StrictMode; +import android.support.test.filters.SmallTest; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.BaseJUnit4ClassRunner; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * Tests for the StrictModeContext class. + */ +@RunWith(BaseJUnit4ClassRunner.class) +public class StrictModeContextTest { + private StrictMode.ThreadPolicy mOldThreadPolicy; + private StrictMode.VmPolicy mOldVmPolicy; + private FileOutputStream mFosForWriting; + private FileInputStream mFisForReading; + + @Before + public void setUp() throws Exception { + mFosForWriting = new FileOutputStream(File.createTempFile("foo", "bar")); + mFisForReading = new FileInputStream(File.createTempFile("foo", "baz")); + enableStrictMode(); + } + + @After + public void tearDown() throws Exception { + disableStrictMode(); + mFosForWriting.close(); + mFisForReading.close(); + } + + private void enableStrictMode() { + mOldThreadPolicy = StrictMode.getThreadPolicy(); + mOldVmPolicy = StrictMode.getVmPolicy(); + StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() + .detectAll() + .penaltyLog() + .penaltyDeath() + .build()); + StrictMode.setVmPolicy( + new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().penaltyDeath().build()); + } + + private void disableStrictMode() { + StrictMode.setThreadPolicy(mOldThreadPolicy); + StrictMode.setVmPolicy(mOldVmPolicy); + } + + private void writeToDisk() { + try { + mFosForWriting.write(ApiCompatibilityUtils.getBytesUtf8("Foo")); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void assertWriteToDiskThrows() { + boolean didThrow = false; + try { + writeToDisk(); + } catch (Exception e) { + didThrow = true; + } + Assert.assertTrue("Expected disk write to throw.", didThrow); + } + + private void readFromDisk() { + try { + mFisForReading.read(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void assertReadFromDiskThrows() { + boolean didThrow = false; + try { + readFromDisk(); + } catch (Exception e) { + didThrow = true; + } + Assert.assertTrue("Expected disk read to throw.", didThrow); + } + + @Test + @SmallTest + public void testAllowDiskWrites() { + try (StrictModeContext unused = StrictModeContext.allowDiskWrites()) { + writeToDisk(); + } + assertWriteToDiskThrows(); + } + + @Test + @SmallTest + public void testAllowDiskReads() { + try (StrictModeContext unused = StrictModeContext.allowDiskReads()) { + readFromDisk(); + assertWriteToDiskThrows(); + } + assertReadFromDiskThrows(); + } +}
diff --git a/src/base/android/javatests/src/org/chromium/base/UserDataHostTest.java b/src/base/android/javatests/src/org/chromium/base/UserDataHostTest.java new file mode 100644 index 0000000..fba5458 --- /dev/null +++ b/src/base/android/javatests/src/org/chromium/base/UserDataHostTest.java
@@ -0,0 +1,170 @@ +// Copyright 2018 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. + +package org.chromium.base; + +import android.support.test.filters.SmallTest; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.BaseJUnit4ClassRunner; +import org.chromium.base.test.util.DisabledTest; + +/** + * Test class for {@link UserDataHost}. + */ +@RunWith(BaseJUnit4ClassRunner.class) +public class UserDataHostTest { + private final UserDataHost mHost = new UserDataHost(); + + private static class TestObjectA implements UserData { + private boolean mDestroyed; + + @Override + public void destroy() { + mDestroyed = true; + } + + private boolean isDestroyed() { + return mDestroyed; + } + } + + private static class TestObjectB implements UserData { + private boolean mDestroyed; + + @Override + public void destroy() { + mDestroyed = true; + } + + private boolean isDestroyed() { + return mDestroyed; + } + } + + private <T extends UserData> void assertGetUserData(Class<T> key) { + boolean exception = false; + try { + mHost.getUserData(key); + } catch (AssertionError e) { + exception = true; + } + Assert.assertTrue(exception); + } + + private <T extends UserData> void assertSetUserData(Class<T> key, T obj) { + boolean exception = false; + try { + mHost.setUserData(key, obj); + } catch (AssertionError e) { + exception = true; + } + Assert.assertTrue(exception); + } + + private <T extends UserData> void assertRemoveUserData(Class<T> key) { + boolean exception = false; + try { + mHost.removeUserData(key); + } catch (AssertionError e) { + exception = true; + } + Assert.assertTrue(exception); + } + + /** + * Verifies basic operations. + */ + @Test + @SmallTest + @DisabledTest + public void testBasicOperations() { + TestObjectA obj = new TestObjectA(); + mHost.setUserData(TestObjectA.class, obj); + Assert.assertEquals(obj, mHost.getUserData(TestObjectA.class)); + Assert.assertEquals(obj, mHost.removeUserData(TestObjectA.class)); + Assert.assertNull(mHost.getUserData(TestObjectA.class)); + assertRemoveUserData(TestObjectA.class); + } + + /** + * Verifies nulled key or data are not allowed. + */ + @Test + @SmallTest + @DisabledTest + public void testNullKeyOrDataAreDisallowed() { + TestObjectA obj = new TestObjectA(); + assertSetUserData(null, null); + assertSetUserData(TestObjectA.class, null); + assertSetUserData(null, obj); + assertGetUserData(null); + assertRemoveUserData(null); + } + + /** + * Verifies {@link #setUserData()} overwrites current data. + */ + @Test + @SmallTest + public void testSetUserDataOverwrites() { + TestObjectA obj1 = new TestObjectA(); + mHost.setUserData(TestObjectA.class, obj1); + Assert.assertEquals(obj1, mHost.getUserData(TestObjectA.class)); + + TestObjectA obj2 = new TestObjectA(); + mHost.setUserData(TestObjectA.class, obj2); + Assert.assertEquals(obj2, mHost.getUserData(TestObjectA.class)); + } + + /** + * Verifies operation on a different thread is not allowed. + */ + @Test + @SmallTest + @DisabledTest + public void testSingleThreadPolicy() { + TestObjectA obj = new TestObjectA(); + mHost.setUserData(TestObjectA.class, obj); + ThreadUtils.runOnUiThreadBlocking(() -> assertGetUserData(TestObjectA.class)); + } + + /** + * Verifies {@link UserHostData#destroy()} detroyes each {@link UserData} object. + */ + @Test + @SmallTest + public void testDestroy() { + TestObjectA objA = new TestObjectA(); + TestObjectB objB = new TestObjectB(); + mHost.setUserData(TestObjectA.class, objA); + mHost.setUserData(TestObjectB.class, objB); + Assert.assertEquals(objA, mHost.getUserData(TestObjectA.class)); + Assert.assertEquals(objB, mHost.getUserData(TestObjectB.class)); + + mHost.destroy(); + Assert.assertTrue(objA.isDestroyed()); + Assert.assertTrue(objB.isDestroyed()); + } + + /** + * Verifies that no operation is allowed after {@link #destroy()} is called. + */ + @Test + @SmallTest + @DisabledTest + public void testOperationsDisallowedAfterDestroy() { + TestObjectA obj = new TestObjectA(); + mHost.setUserData(TestObjectA.class, obj); + Assert.assertEquals(obj, mHost.getUserData(TestObjectA.class)); + + mHost.destroy(); + assertGetUserData(TestObjectA.class); + assertSetUserData(TestObjectA.class, obj); + assertRemoveUserData(TestObjectA.class); + } +}
diff --git a/src/base/android/javatests/src/org/chromium/base/metrics/RecordHistogramTest.java b/src/base/android/javatests/src/org/chromium/base/metrics/RecordHistogramTest.java new file mode 100644 index 0000000..3ecfb3a --- /dev/null +++ b/src/base/android/javatests/src/org/chromium/base/metrics/RecordHistogramTest.java
@@ -0,0 +1,201 @@ +// Copyright 2017 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. + +package org.chromium.base.metrics; + +import android.support.test.filters.SmallTest; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.library_loader.LibraryLoader; +import org.chromium.base.library_loader.LibraryProcessType; +import org.chromium.base.test.BaseJUnit4ClassRunner; +import org.chromium.base.test.util.MetricsUtils.HistogramDelta; + +import java.util.concurrent.TimeUnit; + +/** + * Tests for the Java API for recording UMA histograms. + */ +@RunWith(BaseJUnit4ClassRunner.class) +public class RecordHistogramTest { + @Before + public void setUp() throws Exception { + LibraryLoader.getInstance().ensureInitialized(LibraryProcessType.PROCESS_BROWSER); + } + + /** + * Tests recording of boolean histograms. + */ + @Test + @SmallTest + public void testRecordBooleanHistogram() { + String histogram = "HelloWorld.BooleanMetric"; + HistogramDelta falseCount = new HistogramDelta(histogram, 0); + HistogramDelta trueCount = new HistogramDelta(histogram, 1); + Assert.assertEquals(0, trueCount.getDelta()); + Assert.assertEquals(0, falseCount.getDelta()); + + RecordHistogram.recordBooleanHistogram(histogram, true); + Assert.assertEquals(1, trueCount.getDelta()); + Assert.assertEquals(0, falseCount.getDelta()); + + RecordHistogram.recordBooleanHistogram(histogram, true); + Assert.assertEquals(2, trueCount.getDelta()); + Assert.assertEquals(0, falseCount.getDelta()); + + RecordHistogram.recordBooleanHistogram(histogram, false); + Assert.assertEquals(2, trueCount.getDelta()); + Assert.assertEquals(1, falseCount.getDelta()); + } + + /** + * Tests recording of enumerated histograms. + */ + @Test + @SmallTest + public void testRecordEnumeratedHistogram() { + String histogram = "HelloWorld.EnumeratedMetric"; + HistogramDelta zeroCount = new HistogramDelta(histogram, 0); + HistogramDelta oneCount = new HistogramDelta(histogram, 1); + HistogramDelta twoCount = new HistogramDelta(histogram, 2); + final int boundary = 3; + + Assert.assertEquals(0, zeroCount.getDelta()); + Assert.assertEquals(0, oneCount.getDelta()); + Assert.assertEquals(0, twoCount.getDelta()); + + RecordHistogram.recordEnumeratedHistogram(histogram, 0, boundary); + Assert.assertEquals(1, zeroCount.getDelta()); + Assert.assertEquals(0, oneCount.getDelta()); + Assert.assertEquals(0, twoCount.getDelta()); + + RecordHistogram.recordEnumeratedHistogram(histogram, 0, boundary); + Assert.assertEquals(2, zeroCount.getDelta()); + Assert.assertEquals(0, oneCount.getDelta()); + Assert.assertEquals(0, twoCount.getDelta()); + + RecordHistogram.recordEnumeratedHistogram(histogram, 2, boundary); + Assert.assertEquals(2, zeroCount.getDelta()); + Assert.assertEquals(0, oneCount.getDelta()); + Assert.assertEquals(1, twoCount.getDelta()); + } + + /** + * Tests recording of count histograms. + */ + @Test + @SmallTest + public void testRecordCountHistogram() { + String histogram = "HelloWorld.CountMetric"; + HistogramDelta zeroCount = new HistogramDelta(histogram, 0); + HistogramDelta oneCount = new HistogramDelta(histogram, 1); + HistogramDelta twoCount = new HistogramDelta(histogram, 2); + HistogramDelta eightThousandCount = new HistogramDelta(histogram, 8000); + + Assert.assertEquals(0, zeroCount.getDelta()); + Assert.assertEquals(0, oneCount.getDelta()); + Assert.assertEquals(0, twoCount.getDelta()); + Assert.assertEquals(0, eightThousandCount.getDelta()); + + RecordHistogram.recordCountHistogram(histogram, 0); + Assert.assertEquals(1, zeroCount.getDelta()); + Assert.assertEquals(0, oneCount.getDelta()); + Assert.assertEquals(0, twoCount.getDelta()); + Assert.assertEquals(0, eightThousandCount.getDelta()); + + RecordHistogram.recordCountHistogram(histogram, 0); + Assert.assertEquals(2, zeroCount.getDelta()); + Assert.assertEquals(0, oneCount.getDelta()); + Assert.assertEquals(0, twoCount.getDelta()); + Assert.assertEquals(0, eightThousandCount.getDelta()); + + RecordHistogram.recordCountHistogram(histogram, 2); + Assert.assertEquals(2, zeroCount.getDelta()); + Assert.assertEquals(0, oneCount.getDelta()); + Assert.assertEquals(1, twoCount.getDelta()); + Assert.assertEquals(0, eightThousandCount.getDelta()); + + RecordHistogram.recordCountHistogram(histogram, 8000); + Assert.assertEquals(2, zeroCount.getDelta()); + Assert.assertEquals(0, oneCount.getDelta()); + Assert.assertEquals(1, twoCount.getDelta()); + Assert.assertEquals(1, eightThousandCount.getDelta()); + } + + /** + * Tests recording of custom times histograms. + */ + @Test + @SmallTest + public void testRecordCustomTimesHistogram() { + String histogram = "HelloWorld.CustomTimesMetric"; + HistogramDelta zeroCount = new HistogramDelta(histogram, 0); + HistogramDelta oneCount = new HistogramDelta(histogram, 1); + HistogramDelta twoCount = new HistogramDelta(histogram, 100); + + Assert.assertEquals(0, zeroCount.getDelta()); + Assert.assertEquals(0, oneCount.getDelta()); + Assert.assertEquals(0, twoCount.getDelta()); + + TimeUnit milli = TimeUnit.MILLISECONDS; + + RecordHistogram.recordCustomTimesHistogram(histogram, 0, 1, 100, milli, 3); + Assert.assertEquals(1, zeroCount.getDelta()); + Assert.assertEquals(0, oneCount.getDelta()); + Assert.assertEquals(0, twoCount.getDelta()); + + RecordHistogram.recordCustomTimesHistogram(histogram, 0, 1, 100, milli, 3); + Assert.assertEquals(2, zeroCount.getDelta()); + Assert.assertEquals(0, oneCount.getDelta()); + Assert.assertEquals(0, twoCount.getDelta()); + + RecordHistogram.recordCustomTimesHistogram(histogram, 95, 1, 100, milli, 3); + Assert.assertEquals(2, zeroCount.getDelta()); + Assert.assertEquals(1, oneCount.getDelta()); + Assert.assertEquals(0, twoCount.getDelta()); + + RecordHistogram.recordCustomTimesHistogram(histogram, 200, 1, 100, milli, 3); + Assert.assertEquals(2, zeroCount.getDelta()); + Assert.assertEquals(1, oneCount.getDelta()); + Assert.assertEquals(1, twoCount.getDelta()); + } + + /** + * Tests recording of linear count histograms. + */ + @Test + @SmallTest + public void testRecordLinearCountHistogram() { + String histogram = "HelloWorld.LinearCountMetric"; + HistogramDelta zeroCount = new HistogramDelta(histogram, 0); + HistogramDelta oneCount = new HistogramDelta(histogram, 1); + HistogramDelta twoCount = new HistogramDelta(histogram, 2); + final int min = 1; + final int max = 3; + final int numBuckets = 4; + + Assert.assertEquals(0, zeroCount.getDelta()); + Assert.assertEquals(0, oneCount.getDelta()); + Assert.assertEquals(0, twoCount.getDelta()); + + RecordHistogram.recordLinearCountHistogram(histogram, 0, min, max, numBuckets); + Assert.assertEquals(1, zeroCount.getDelta()); + Assert.assertEquals(0, oneCount.getDelta()); + Assert.assertEquals(0, twoCount.getDelta()); + + RecordHistogram.recordLinearCountHistogram(histogram, 0, min, max, numBuckets); + Assert.assertEquals(2, zeroCount.getDelta()); + Assert.assertEquals(0, oneCount.getDelta()); + Assert.assertEquals(0, twoCount.getDelta()); + + RecordHistogram.recordLinearCountHistogram(histogram, 2, min, max, numBuckets); + Assert.assertEquals(2, zeroCount.getDelta()); + Assert.assertEquals(0, oneCount.getDelta()); + Assert.assertEquals(1, twoCount.getDelta()); + } +}
diff --git a/src/base/android/javatests/src/org/chromium/base/task/AsyncTaskTest.java b/src/base/android/javatests/src/org/chromium/base/task/AsyncTaskTest.java new file mode 100644 index 0000000..61dea5c --- /dev/null +++ b/src/base/android/javatests/src/org/chromium/base/task/AsyncTaskTest.java
@@ -0,0 +1,129 @@ +// Copyright 2018 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. + +package org.chromium.base.task; + +import android.support.annotation.NonNull; +import android.support.test.filters.SmallTest; + +import org.hamcrest.CoreMatchers; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +import org.chromium.base.test.BaseJUnit4ClassRunner; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +/** + * Tests for our AsyncTask modifications + * + * Not a robolectric test because the reflection doesn't work with ShadowAsyncTask. + */ +@RunWith(BaseJUnit4ClassRunner.class) +public class AsyncTaskTest { + private static class SpecialChromeAsyncTask extends AsyncTask<Void> { + @Override + protected Void doInBackground() { + return null; + } + } + + @SuppressWarnings("NoAndroidAsyncTaskCheck") + private static class SpecialOsAsyncTask extends android.os.AsyncTask<Void, Void, Void> { + @Override + protected Void doInBackground(Void... params) { + return null; + } + } + + private static class SpecialRunnable implements Runnable { + @Override + public void run() {} + } + + private static final int QUEUE_SIZE = 40; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + /** + * Test filling the queue with basic Runnables, then add a final AsyncTask to overfill it, and + * ensure the Runnable is the one blamed in the exception message. + */ + @Test + @SmallTest + public void testChromeThreadPoolExecutorRunnables() { + Executor executor = new ChromeThreadPoolExecutor(1, 1, 1, TimeUnit.SECONDS, + new ArrayBlockingQueue<Runnable>(QUEUE_SIZE), new ThreadFactory() { + @Override + public Thread newThread(@NonNull Runnable r) { + return null; + } + }); + for (int i = 0; i < QUEUE_SIZE; i++) { + executor.execute(new SpecialRunnable()); + } + thrown.expect(RejectedExecutionException.class); + thrown.expectMessage(CoreMatchers.containsString( + "org.chromium.base.task.AsyncTaskTest$SpecialRunnable")); + thrown.expectMessage( + CoreMatchers.not(CoreMatchers.containsString("SpecialChromeAsyncTask"))); + new SpecialChromeAsyncTask().executeOnExecutor(executor); + } + + /** + * Test filling the queue with Chrome AsyncTasks, then add a final OS AsyncTask to + * overfill it and ensure the Chrome AsyncTask is the one blamed in the exception message. + */ + @Test + @SmallTest + public void testChromeThreadPoolExecutorChromeAsyncTask() { + Executor executor = new ChromeThreadPoolExecutor(1, 1, 1, TimeUnit.SECONDS, + new ArrayBlockingQueue<Runnable>(QUEUE_SIZE), new ThreadFactory() { + @Override + public Thread newThread(@NonNull Runnable r) { + return null; + } + }); + for (int i = 0; i < QUEUE_SIZE; i++) { + new SpecialChromeAsyncTask().executeOnExecutor(executor); + } + thrown.expect(RejectedExecutionException.class); + thrown.expectMessage(CoreMatchers.containsString( + "org.chromium.base.task.AsyncTaskTest$SpecialChromeAsyncTask")); + thrown.expectMessage(CoreMatchers.not(CoreMatchers.containsString("SpecialOsAsyncTask"))); + new SpecialOsAsyncTask().executeOnExecutor(executor); + } + + /** + * Test filling the queue with android.os.AsyncTasks, then add a final ChromeAsyncTask to + * overfill it and ensure the OsAsyncTask is the one blamed in the exception message. + */ + @Test + @SmallTest + public void testChromeThreadPoolExecutorOsAsyncTask() { + Executor executor = new ChromeThreadPoolExecutor(1, 1, 1, TimeUnit.SECONDS, + new ArrayBlockingQueue<Runnable>(QUEUE_SIZE), new ThreadFactory() { + @Override + public Thread newThread(@NonNull Runnable r) { + return null; + } + }); + for (int i = 0; i < QUEUE_SIZE; i++) { + new SpecialOsAsyncTask().executeOnExecutor(executor); + } + thrown.expect(RejectedExecutionException.class); + thrown.expectMessage(CoreMatchers.containsString( + "org.chromium.base.task.AsyncTaskTest$SpecialOsAsyncTask")); + thrown.expectMessage( + CoreMatchers.not(CoreMatchers.containsString("SpecialChromeAsyncTask"))); + new SpecialChromeAsyncTask().executeOnExecutor(executor); + } +}
diff --git a/src/base/android/jni_android.cc b/src/base/android/jni_android.cc new file mode 100644 index 0000000..4369595 --- /dev/null +++ b/src/base/android/jni_android.cc
@@ -0,0 +1,322 @@ +// Copyright (c) 2012 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. + +#include "base/android/jni_android.h" + +#include <sys/prctl.h> + +#include <map> + +#include "base/android/java_exception_reporter.h" +#include "base/android/jni_string.h" +#include "base/debug/debugging_buildflags.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/threading/thread_local.h" +#include "starboard/memory.h" +#include "starboard/string.h" +#include "starboard/types.h" + +namespace { +using base::android::GetClass; +using base::android::MethodID; +using base::android::ScopedJavaLocalRef; + +JavaVM* g_jvm = NULL; +base::LazyInstance<base::android::ScopedJavaGlobalRef<jobject>>::Leaky + g_class_loader = LAZY_INSTANCE_INITIALIZER; +jmethodID g_class_loader_load_class_method_id = 0; + +#if BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS) +base::LazyInstance<base::ThreadLocalPointer<void>>::Leaky + g_stack_frame_pointer = LAZY_INSTANCE_INITIALIZER; +#endif + +bool g_fatal_exception_occurred = false; + +} // namespace + +namespace base { +namespace android { + +JNIEnv* AttachCurrentThread() { + DCHECK(g_jvm); + JNIEnv* env = nullptr; + jint ret = g_jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_2); + if (ret == JNI_EDETACHED || !env) { + JavaVMAttachArgs args; + args.version = JNI_VERSION_1_2; + args.group = nullptr; + + // 16 is the maximum size for thread names on Android. + char thread_name[16]; + int err = prctl(PR_GET_NAME, thread_name); + if (err < 0) { + DPLOG(ERROR) << "prctl(PR_GET_NAME)"; + args.name = nullptr; + } else { + args.name = thread_name; + } + + ret = g_jvm->AttachCurrentThread(&env, &args); + DCHECK_EQ(JNI_OK, ret); + } + return env; +} + +JNIEnv* AttachCurrentThreadWithName(const std::string& thread_name) { + DCHECK(g_jvm); + JavaVMAttachArgs args; + args.version = JNI_VERSION_1_2; + args.name = thread_name.c_str(); + args.group = NULL; + JNIEnv* env = NULL; + jint ret = g_jvm->AttachCurrentThread(&env, &args); + DCHECK_EQ(JNI_OK, ret); + return env; +} + +void DetachFromVM() { + // Ignore the return value, if the thread is not attached, DetachCurrentThread + // will fail. But it is ok as the native thread may never be attached. + if (g_jvm) + g_jvm->DetachCurrentThread(); +} + +void InitVM(JavaVM* vm) { + DCHECK(!g_jvm || g_jvm == vm); + g_jvm = vm; +} + +bool IsVMInitialized() { + return g_jvm != NULL; +} + +void InitReplacementClassLoader(JNIEnv* env, + const JavaRef<jobject>& class_loader) { + DCHECK(g_class_loader.Get().is_null()); + DCHECK(!class_loader.is_null()); + + ScopedJavaLocalRef<jclass> class_loader_clazz = + GetClass(env, "java/lang/ClassLoader"); + CHECK(!ClearException(env)); + g_class_loader_load_class_method_id = + env->GetMethodID(class_loader_clazz.obj(), + "loadClass", + "(Ljava/lang/String;)Ljava/lang/Class;"); + CHECK(!ClearException(env)); + + DCHECK(env->IsInstanceOf(class_loader.obj(), class_loader_clazz.obj())); + g_class_loader.Get().Reset(class_loader); +} + +ScopedJavaLocalRef<jclass> GetClass(JNIEnv* env, const char* class_name) { + jclass clazz; + if (!g_class_loader.Get().is_null()) { + // ClassLoader.loadClass expects a classname with components separated by + // dots instead of the slashes that JNIEnv::FindClass expects. The JNI + // generator generates names with slashes, so we have to replace them here. + // TODO(torne): move to an approach where we always use ClassLoader except + // for the special case of base::android::GetClassLoader(), and change the + // JNI generator to generate dot-separated names. http://crbug.com/461773 + size_t bufsize = SbStringGetLength(class_name) + 1; + char dotted_name[bufsize]; + SbMemoryMove(dotted_name, class_name, bufsize); + for (size_t i = 0; i < bufsize; ++i) { + if (dotted_name[i] == '/') { + dotted_name[i] = '.'; + } + } + + clazz = static_cast<jclass>( + env->CallObjectMethod(g_class_loader.Get().obj(), + g_class_loader_load_class_method_id, + ConvertUTF8ToJavaString(env, dotted_name).obj())); + } else { + clazz = env->FindClass(class_name); + } + if (ClearException(env) || !clazz) { + LOG(FATAL) << "Failed to find class " << class_name; + } + return ScopedJavaLocalRef<jclass>(env, clazz); +} + +jclass LazyGetClass( + JNIEnv* env, + const char* class_name, + std::atomic<jclass>* atomic_class_id) { + const jclass value = std::atomic_load(atomic_class_id); + if (value) + return value; + ScopedJavaGlobalRef<jclass> clazz; + clazz.Reset(GetClass(env, class_name)); + jclass cas_result = nullptr; + if (std::atomic_compare_exchange_strong(atomic_class_id, &cas_result, + clazz.obj())) { + // We intentionally leak the global ref since we now storing it as a raw + // pointer in |atomic_class_id|. + return clazz.Release(); + } else { + return cas_result; + } +} + +template<MethodID::Type type> +jmethodID MethodID::Get(JNIEnv* env, + jclass clazz, + const char* method_name, + const char* jni_signature) { + auto get_method_ptr = type == MethodID::TYPE_STATIC ? + &JNIEnv::GetStaticMethodID : + &JNIEnv::GetMethodID; + jmethodID id = (env->*get_method_ptr)(clazz, method_name, jni_signature); + if (base::android::ClearException(env) || !id) { + LOG(FATAL) << "Failed to find " << + (type == TYPE_STATIC ? "static " : "") << + "method " << method_name << " " << jni_signature; + } + return id; +} + +// If |atomic_method_id| set, it'll return immediately. Otherwise, it'll call +// into ::Get() above. If there's a race, it's ok since the values are the same +// (and the duplicated effort will happen only once). +template<MethodID::Type type> +jmethodID MethodID::LazyGet(JNIEnv* env, + jclass clazz, + const char* method_name, + const char* jni_signature, + std::atomic<jmethodID>* atomic_method_id) { + const jmethodID value = std::atomic_load(atomic_method_id); + if (value) + return value; + jmethodID id = MethodID::Get<type>(env, clazz, method_name, jni_signature); + std::atomic_store(atomic_method_id, id); + return id; +} + +// Various template instantiations. +template jmethodID MethodID::Get<MethodID::TYPE_STATIC>( + JNIEnv* env, jclass clazz, const char* method_name, + const char* jni_signature); + +template jmethodID MethodID::Get<MethodID::TYPE_INSTANCE>( + JNIEnv* env, jclass clazz, const char* method_name, + const char* jni_signature); + +template jmethodID MethodID::LazyGet<MethodID::TYPE_STATIC>( + JNIEnv* env, jclass clazz, const char* method_name, + const char* jni_signature, std::atomic<jmethodID>* atomic_method_id); + +template jmethodID MethodID::LazyGet<MethodID::TYPE_INSTANCE>( + JNIEnv* env, jclass clazz, const char* method_name, + const char* jni_signature, std::atomic<jmethodID>* atomic_method_id); + +bool HasException(JNIEnv* env) { + return env->ExceptionCheck() != JNI_FALSE; +} + +bool ClearException(JNIEnv* env) { + if (!HasException(env)) + return false; + env->ExceptionDescribe(); + env->ExceptionClear(); + return true; +} + +void CheckException(JNIEnv* env) { + if (!HasException(env)) + return; + + jthrowable java_throwable = env->ExceptionOccurred(); + if (java_throwable) { + // Clear the pending exception, since a local reference is now held. + env->ExceptionDescribe(); + env->ExceptionClear(); + + if (g_fatal_exception_occurred) { + // Another exception (probably OOM) occurred during GetJavaExceptionInfo. + base::android::SetJavaException( + "Java OOM'ed in exception handling, check logcat"); + } else { + g_fatal_exception_occurred = true; + // RVO should avoid any extra copies of the exception string. + base::android::SetJavaException( + GetJavaExceptionInfo(env, java_throwable).c_str()); + } + } + + // Now, feel good about it and die. + LOG(FATAL) << "Please include Java exception stack in crash report"; +} + +std::string GetJavaExceptionInfo(JNIEnv* env, jthrowable java_throwable) { + ScopedJavaLocalRef<jclass> throwable_clazz = + GetClass(env, "java/lang/Throwable"); + jmethodID throwable_printstacktrace = + MethodID::Get<MethodID::TYPE_INSTANCE>( + env, throwable_clazz.obj(), "printStackTrace", + "(Ljava/io/PrintStream;)V"); + + // Create an instance of ByteArrayOutputStream. + ScopedJavaLocalRef<jclass> bytearray_output_stream_clazz = + GetClass(env, "java/io/ByteArrayOutputStream"); + jmethodID bytearray_output_stream_constructor = + MethodID::Get<MethodID::TYPE_INSTANCE>( + env, bytearray_output_stream_clazz.obj(), "<init>", "()V"); + jmethodID bytearray_output_stream_tostring = + MethodID::Get<MethodID::TYPE_INSTANCE>( + env, bytearray_output_stream_clazz.obj(), "toString", + "()Ljava/lang/String;"); + ScopedJavaLocalRef<jobject> bytearray_output_stream(env, + env->NewObject(bytearray_output_stream_clazz.obj(), + bytearray_output_stream_constructor)); + CheckException(env); + + // Create an instance of PrintStream. + ScopedJavaLocalRef<jclass> printstream_clazz = + GetClass(env, "java/io/PrintStream"); + jmethodID printstream_constructor = + MethodID::Get<MethodID::TYPE_INSTANCE>( + env, printstream_clazz.obj(), "<init>", + "(Ljava/io/OutputStream;)V"); + ScopedJavaLocalRef<jobject> printstream(env, + env->NewObject(printstream_clazz.obj(), printstream_constructor, + bytearray_output_stream.obj())); + CheckException(env); + + // Call Throwable.printStackTrace(PrintStream) + env->CallVoidMethod(java_throwable, throwable_printstacktrace, + printstream.obj()); + CheckException(env); + + // Call ByteArrayOutputStream.toString() + ScopedJavaLocalRef<jstring> exception_string( + env, static_cast<jstring>( + env->CallObjectMethod(bytearray_output_stream.obj(), + bytearray_output_stream_tostring))); + CheckException(env); + + return ConvertJavaStringToUTF8(exception_string); +} + +#if BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS) + +JNIStackFrameSaver::JNIStackFrameSaver(void* current_fp) { + previous_fp_ = g_stack_frame_pointer.Pointer()->Get(); + g_stack_frame_pointer.Pointer()->Set(current_fp); +} + +JNIStackFrameSaver::~JNIStackFrameSaver() { + g_stack_frame_pointer.Pointer()->Set(previous_fp_); +} + +void* JNIStackFrameSaver::SavedFrame() { + return g_stack_frame_pointer.Pointer()->Get(); +} + +#endif // BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS) + +} // namespace android +} // namespace base
diff --git a/src/base/android/jni_android.h b/src/base/android/jni_android.h new file mode 100644 index 0000000..fe79032 --- /dev/null +++ b/src/base/android/jni_android.h
@@ -0,0 +1,175 @@ +// Copyright (c) 2012 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_ANDROID_JNI_ANDROID_H_ +#define BASE_ANDROID_JNI_ANDROID_H_ + +#include <jni.h> +#include <sys/types.h> + +#include <atomic> +#include <string> + +#include "base/android/scoped_java_ref.h" +#include "base/atomicops.h" +#include "base/base_export.h" +#include "base/compiler_specific.h" +#include "base/debug/debugging_buildflags.h" +#include "base/debug/stack_trace.h" +#include "base/macros.h" +#include "starboard/types.h" + +#if BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS) + +// When profiling is enabled (enable_profiling=true) this macro is added to +// all generated JNI stubs so that it becomes the last thing that runs before +// control goes into Java. +// +// This macro saves stack frame pointer of the current function. Saved value +// used later by JNI_LINK_SAVED_FRAME_POINTER. +#define JNI_SAVE_FRAME_POINTER \ + base::android::JNIStackFrameSaver jni_frame_saver(__builtin_frame_address(0)) + +// When profiling is enabled (enable_profiling=true) this macro is added to +// all generated JNI callbacks so that it becomes the first thing that runs +// after control returns from Java. +// +// This macro links stack frame of the current function to the stack frame +// saved by JNI_SAVE_FRAME_POINTER, allowing frame-based unwinding +// (used by the heap profiler) to produce complete traces. +#define JNI_LINK_SAVED_FRAME_POINTER \ + base::debug::ScopedStackFrameLinker jni_frame_linker( \ + __builtin_frame_address(0), \ + base::android::JNIStackFrameSaver::SavedFrame()) + +#else + +// Frame-based stack unwinding is not supported, do nothing. +#define JNI_SAVE_FRAME_POINTER +#define JNI_LINK_SAVED_FRAME_POINTER + +#endif // BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS) + +namespace base { +namespace android { + +// Used to mark symbols to be exported in a shared library's symbol table. +#define JNI_EXPORT __attribute__ ((visibility("default"))) + +// Contains the registration method information for initializing JNI bindings. +struct RegistrationMethod { + const char* name; + bool (*func)(JNIEnv* env); +}; + +// Attaches the current thread to the VM (if necessary) and return the JNIEnv*. +BASE_EXPORT JNIEnv* AttachCurrentThread(); + +// Same to AttachCurrentThread except that thread name will be set to +// |thread_name| if it is the first call. Otherwise, thread_name won't be +// changed. AttachCurrentThread() doesn't regard underlying platform thread +// name, but just resets it to "Thread-???". This function should be called +// right after new thread is created if it is important to keep thread name. +BASE_EXPORT JNIEnv* AttachCurrentThreadWithName(const std::string& thread_name); + +// Detaches the current thread from VM if it is attached. +BASE_EXPORT void DetachFromVM(); + +// Initializes the global JVM. +BASE_EXPORT void InitVM(JavaVM* vm); + +// Returns true if the global JVM has been initialized. +BASE_EXPORT bool IsVMInitialized(); + +// Initializes the global ClassLoader used by the GetClass and LazyGetClass +// methods. This is needed because JNI will use the base ClassLoader when there +// is no Java code on the stack. The base ClassLoader doesn't know about any of +// the application classes and will fail to lookup anything other than system +// classes. +BASE_EXPORT void InitReplacementClassLoader( + JNIEnv* env, + const JavaRef<jobject>& class_loader); + +// Finds the class named |class_name| and returns it. +// Use this method instead of invoking directly the JNI FindClass method (to +// prevent leaking local references). +// This method triggers a fatal assertion if the class could not be found. +// Use HasClass if you need to check whether the class exists. +BASE_EXPORT ScopedJavaLocalRef<jclass> GetClass(JNIEnv* env, + const char* class_name); + +// The method will initialize |atomic_class_id| to contain a global ref to the +// class. And will return that ref on subsequent calls. It's the caller's +// responsibility to release the ref when it is no longer needed. +// The caller is responsible to zero-initialize |atomic_method_id|. +// It's fine to simultaneously call this on multiple threads referencing the +// same |atomic_method_id|. +BASE_EXPORT jclass LazyGetClass( + JNIEnv* env, + const char* class_name, + std::atomic<jclass>* atomic_class_id); + +// This class is a wrapper for JNIEnv Get(Static)MethodID. +class BASE_EXPORT MethodID { + public: + enum Type { + TYPE_STATIC, + TYPE_INSTANCE, + }; + + // Returns the method ID for the method with the specified name and signature. + // This method triggers a fatal assertion if the method could not be found. + template<Type type> + static jmethodID Get(JNIEnv* env, + jclass clazz, + const char* method_name, + const char* jni_signature); + + // The caller is responsible to zero-initialize |atomic_method_id|. + // It's fine to simultaneously call this on multiple threads referencing the + // same |atomic_method_id|. + template<Type type> + static jmethodID LazyGet(JNIEnv* env, + jclass clazz, + const char* method_name, + const char* jni_signature, + std::atomic<jmethodID>* atomic_method_id); +}; + +// Returns true if an exception is pending in the provided JNIEnv*. +BASE_EXPORT bool HasException(JNIEnv* env); + +// If an exception is pending in the provided JNIEnv*, this function clears it +// and returns true. +BASE_EXPORT bool ClearException(JNIEnv* env); + +// This function will call CHECK() macro if there's any pending exception. +BASE_EXPORT void CheckException(JNIEnv* env); + +// This returns a string representation of the java stack trace. +BASE_EXPORT std::string GetJavaExceptionInfo(JNIEnv* env, + jthrowable java_throwable); + +#if BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS) + +// Saves caller's PC and stack frame in a thread-local variable. +// Implemented only when profiling is enabled (enable_profiling=true). +class BASE_EXPORT JNIStackFrameSaver { + public: + JNIStackFrameSaver(void* current_fp); + ~JNIStackFrameSaver(); + static void* SavedFrame(); + + private: + void* previous_fp_; + + DISALLOW_COPY_AND_ASSIGN(JNIStackFrameSaver); +}; + +#endif // BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS) + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_JNI_ANDROID_H_
diff --git a/src/base/android/jni_android_unittest.cc b/src/base/android/jni_android_unittest.cc new file mode 100644 index 0000000..3f25775 --- /dev/null +++ b/src/base/android/jni_android_unittest.cc
@@ -0,0 +1,61 @@ +// Copyright (c) 2012 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. + +#include "base/android/jni_android.h" + +#include "base/at_exit.h" +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +namespace { + +std::atomic<jmethodID> g_atomic_id(nullptr); +int LazyMethodIDCall(JNIEnv* env, jclass clazz, int p) { + jmethodID id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, clazz, + "abs", + "(I)I", + &g_atomic_id); + + return env->CallStaticIntMethod(clazz, id, p); +} + +int MethodIDCall(JNIEnv* env, jclass clazz, jmethodID id, int p) { + return env->CallStaticIntMethod(clazz, id, p); +} + +} // namespace + +TEST(JNIAndroidMicrobenchmark, MethodId) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jclass> clazz(GetClass(env, "java/lang/Math")); + base::Time start_lazy = base::Time::Now(); + int o = 0; + for (int i = 0; i < 1024; ++i) + o += LazyMethodIDCall(env, clazz.obj(), i); + base::Time end_lazy = base::Time::Now(); + + jmethodID id = g_atomic_id; + base::Time start = base::Time::Now(); + for (int i = 0; i < 1024; ++i) + o += MethodIDCall(env, clazz.obj(), id, i); + base::Time end = base::Time::Now(); + + // On a Galaxy Nexus, results were in the range of: + // JNI LazyMethodIDCall (us) 1984 + // JNI MethodIDCall (us) 1861 + LOG(ERROR) << "JNI LazyMethodIDCall (us) " << + base::TimeDelta(end_lazy - start_lazy).InMicroseconds(); + LOG(ERROR) << "JNI MethodIDCall (us) " << + base::TimeDelta(end - start).InMicroseconds(); + LOG(ERROR) << "JNI " << o; +} + + +} // namespace android +} // namespace base
diff --git a/src/base/android/jni_array.cc b/src/base/android/jni_array.cc new file mode 100644 index 0000000..403c56e --- /dev/null +++ b/src/base/android/jni_array.cc
@@ -0,0 +1,328 @@ +// Copyright (c) 2012 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. + +#include "base/android/jni_array.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/logging.h" + +namespace base { +namespace android { +namespace { + +// As |GetArrayLength| makes no guarantees about the returned value (e.g., it +// may be -1 if |array| is not a valid Java array), provide a safe wrapper +// that always returns a valid, non-negative size. +template <typename JavaArrayType> +size_t SafeGetArrayLength(JNIEnv* env, JavaArrayType jarray) { + DCHECK(jarray); + jsize length = env->GetArrayLength(jarray); + DCHECK_GE(length, 0) << "Invalid array length: " << length; + return static_cast<size_t>(std::max(0, length)); +} + +} // namespace + +ScopedJavaLocalRef<jbyteArray> ToJavaByteArray(JNIEnv* env, + const uint8_t* bytes, + size_t len) { + jbyteArray byte_array = env->NewByteArray(len); + CheckException(env); + DCHECK(byte_array); + + env->SetByteArrayRegion( + byte_array, 0, len, reinterpret_cast<const jbyte*>(bytes)); + CheckException(env); + + return ScopedJavaLocalRef<jbyteArray>(env, byte_array); +} + +ScopedJavaLocalRef<jbyteArray> ToJavaByteArray( + JNIEnv* env, + const std::vector<uint8_t>& bytes) { + return ToJavaByteArray(env, bytes.data(), bytes.size()); +} + +ScopedJavaLocalRef<jbyteArray> ToJavaByteArray(JNIEnv* env, + const std::string& str) { + return ToJavaByteArray(env, reinterpret_cast<const uint8_t*>(str.data()), + str.size()); +} + +ScopedJavaLocalRef<jbooleanArray> ToJavaBooleanArray(JNIEnv* env, + const bool* bools, + size_t len) { + jbooleanArray boolean_array = env->NewBooleanArray(len); + CheckException(env); + DCHECK(boolean_array); + + env->SetBooleanArrayRegion(boolean_array, 0, len, + reinterpret_cast<const jboolean*>(bools)); + CheckException(env); + + return ScopedJavaLocalRef<jbooleanArray>(env, boolean_array); +} + +ScopedJavaLocalRef<jintArray> ToJavaIntArray( + JNIEnv* env, const int* ints, size_t len) { + jintArray int_array = env->NewIntArray(len); + CheckException(env); + DCHECK(int_array); + + env->SetIntArrayRegion( + int_array, 0, len, reinterpret_cast<const jint*>(ints)); + CheckException(env); + + return ScopedJavaLocalRef<jintArray>(env, int_array); +} + +ScopedJavaLocalRef<jintArray> ToJavaIntArray( + JNIEnv* env, const std::vector<int>& ints) { + return ToJavaIntArray(env, ints.data(), ints.size()); +} + +ScopedJavaLocalRef<jlongArray> ToJavaLongArray(JNIEnv* env, + const int64_t* longs, + size_t len) { + jlongArray long_array = env->NewLongArray(len); + CheckException(env); + DCHECK(long_array); + + env->SetLongArrayRegion( + long_array, 0, len, reinterpret_cast<const jlong*>(longs)); + CheckException(env); + + return ScopedJavaLocalRef<jlongArray>(env, long_array); +} + +// Returns a new Java long array converted from the given int64_t array. +BASE_EXPORT ScopedJavaLocalRef<jlongArray> ToJavaLongArray( + JNIEnv* env, + const std::vector<int64_t>& longs) { + return ToJavaLongArray(env, longs.data(), longs.size()); +} + +// Returns a new Java float array converted from the given C++ float array. +BASE_EXPORT ScopedJavaLocalRef<jfloatArray> ToJavaFloatArray( + JNIEnv* env, const float* floats, size_t len) { + jfloatArray float_array = env->NewFloatArray(len); + CheckException(env); + DCHECK(float_array); + + env->SetFloatArrayRegion( + float_array, 0, len, reinterpret_cast<const jfloat*>(floats)); + CheckException(env); + + return ScopedJavaLocalRef<jfloatArray>(env, float_array); +} + +BASE_EXPORT ScopedJavaLocalRef<jfloatArray> ToJavaFloatArray( + JNIEnv* env, + const std::vector<float>& floats) { + return ToJavaFloatArray(env, floats.data(), floats.size()); +} + +ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfByteArray( + JNIEnv* env, const std::vector<std::string>& v) { + ScopedJavaLocalRef<jclass> byte_array_clazz = GetClass(env, "[B"); + jobjectArray joa = env->NewObjectArray(v.size(), + byte_array_clazz.obj(), NULL); + CheckException(env); + + for (size_t i = 0; i < v.size(); ++i) { + ScopedJavaLocalRef<jbyteArray> byte_array = ToJavaByteArray( + env, reinterpret_cast<const uint8_t*>(v[i].data()), v[i].length()); + env->SetObjectArrayElement(joa, i, byte_array.obj()); + } + return ScopedJavaLocalRef<jobjectArray>(env, joa); +} + +ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfStrings( + JNIEnv* env, const std::vector<std::string>& v) { + ScopedJavaLocalRef<jclass> string_clazz = GetClass(env, "java/lang/String"); + jobjectArray joa = env->NewObjectArray(v.size(), string_clazz.obj(), NULL); + CheckException(env); + + for (size_t i = 0; i < v.size(); ++i) { + ScopedJavaLocalRef<jstring> item = ConvertUTF8ToJavaString(env, v[i]); + env->SetObjectArrayElement(joa, i, item.obj()); + } + return ScopedJavaLocalRef<jobjectArray>(env, joa); +} + +ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfStrings( + JNIEnv* env, const std::vector<string16>& v) { + ScopedJavaLocalRef<jclass> string_clazz = GetClass(env, "java/lang/String"); + jobjectArray joa = env->NewObjectArray(v.size(), string_clazz.obj(), NULL); + CheckException(env); + + for (size_t i = 0; i < v.size(); ++i) { + ScopedJavaLocalRef<jstring> item = ConvertUTF16ToJavaString(env, v[i]); + env->SetObjectArrayElement(joa, i, item.obj()); + } + return ScopedJavaLocalRef<jobjectArray>(env, joa); +} + +void AppendJavaStringArrayToStringVector(JNIEnv* env, + jobjectArray array, + std::vector<string16>* out) { + DCHECK(out); + if (!array) + return; + size_t len = SafeGetArrayLength(env, array); + size_t back = out->size(); + out->resize(back + len); + for (size_t i = 0; i < len; ++i) { + ScopedJavaLocalRef<jstring> str(env, + static_cast<jstring>(env->GetObjectArrayElement(array, i))); + ConvertJavaStringToUTF16(env, str.obj(), out->data() + back + i); + } +} + +void AppendJavaStringArrayToStringVector(JNIEnv* env, + jobjectArray array, + std::vector<std::string>* out) { + DCHECK(out); + if (!array) + return; + size_t len = SafeGetArrayLength(env, array); + size_t back = out->size(); + out->resize(back + len); + for (size_t i = 0; i < len; ++i) { + ScopedJavaLocalRef<jstring> str(env, + static_cast<jstring>(env->GetObjectArrayElement(array, i))); + ConvertJavaStringToUTF8(env, str.obj(), out->data() + back + i); + } +} + +void AppendJavaByteArrayToByteVector(JNIEnv* env, + jbyteArray byte_array, + std::vector<uint8_t>* out) { + DCHECK(out); + if (!byte_array) + return; + size_t len = SafeGetArrayLength(env, byte_array); + if (!len) + return; + size_t back = out->size(); + out->resize(back + len); + env->GetByteArrayRegion(byte_array, 0, len, + reinterpret_cast<int8_t*>(out->data() + back)); +} + +void JavaByteArrayToByteVector(JNIEnv* env, + jbyteArray byte_array, + std::vector<uint8_t>* out) { + DCHECK(out); + DCHECK(byte_array); + out->clear(); + AppendJavaByteArrayToByteVector(env, byte_array, out); +} + +void JavaByteArrayToString(JNIEnv* env, + jbyteArray byte_array, + std::string* out) { + DCHECK(out); + DCHECK(byte_array); + + std::vector<uint8_t> byte_vector; + JavaByteArrayToByteVector(env, byte_array, &byte_vector); + out->assign(byte_vector.begin(), byte_vector.end()); +} + +void JavaBooleanArrayToBoolVector(JNIEnv* env, + jbooleanArray boolean_array, + std::vector<bool>* out) { + DCHECK(out); + if (!boolean_array) + return; + size_t len = SafeGetArrayLength(env, boolean_array); + if (!len) + return; + out->resize(len); + // It is not possible to get bool* out of vector<bool>. + jboolean* values = env->GetBooleanArrayElements(boolean_array, nullptr); + for (size_t i = 0; i < len; ++i) { + out->at(i) = static_cast<bool>(values[i]); + } +} + +void JavaIntArrayToIntVector(JNIEnv* env, + jintArray int_array, + std::vector<int>* out) { + DCHECK(out); + size_t len = SafeGetArrayLength(env, int_array); + out->resize(len); + if (!len) + return; + env->GetIntArrayRegion(int_array, 0, len, out->data()); +} + +void JavaLongArrayToInt64Vector(JNIEnv* env, + jlongArray long_array, + std::vector<int64_t>* out) { + DCHECK(out); + std::vector<jlong> temp; + JavaLongArrayToLongVector(env, long_array, &temp); + out->resize(0); + out->insert(out->begin(), temp.begin(), temp.end()); +} + +void JavaLongArrayToLongVector(JNIEnv* env, + jlongArray long_array, + std::vector<jlong>* out) { + DCHECK(out); + size_t len = SafeGetArrayLength(env, long_array); + out->resize(len); + if (!len) + return; + env->GetLongArrayRegion(long_array, 0, len, out->data()); +} + +void JavaFloatArrayToFloatVector(JNIEnv* env, + jfloatArray float_array, + std::vector<float>* out) { + DCHECK(out); + size_t len = SafeGetArrayLength(env, float_array); + out->resize(len); + if (!len) + return; + env->GetFloatArrayRegion(float_array, 0, len, out->data()); +} + +void JavaArrayOfByteArrayToStringVector( + JNIEnv* env, + jobjectArray array, + std::vector<std::string>* out) { + DCHECK(out); + size_t len = SafeGetArrayLength(env, array); + out->resize(len); + for (size_t i = 0; i < len; ++i) { + ScopedJavaLocalRef<jbyteArray> bytes_array( + env, static_cast<jbyteArray>( + env->GetObjectArrayElement(array, i))); + jsize bytes_len = env->GetArrayLength(bytes_array.obj()); + jbyte* bytes = env->GetByteArrayElements(bytes_array.obj(), nullptr); + (*out)[i].assign(reinterpret_cast<const char*>(bytes), bytes_len); + env->ReleaseByteArrayElements(bytes_array.obj(), bytes, JNI_ABORT); + } +} + +void JavaArrayOfIntArrayToIntVector( + JNIEnv* env, + jobjectArray array, + std::vector<std::vector<int>>* out) { + DCHECK(out); + size_t len = SafeGetArrayLength(env, array); + out->resize(len); + for (size_t i = 0; i < len; ++i) { + ScopedJavaLocalRef<jintArray> int_array( + env, static_cast<jintArray>(env->GetObjectArrayElement(array, i))); + JavaIntArrayToIntVector(env, int_array.obj(), &out->at(i)); + } +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/jni_array.h b/src/base/android/jni_array.h new file mode 100644 index 0000000..3211177 --- /dev/null +++ b/src/base/android/jni_array.h
@@ -0,0 +1,145 @@ +// Copyright (c) 2012 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_ANDROID_JNI_ARRAY_H_ +#define BASE_ANDROID_JNI_ARRAY_H_ + +#include <jni.h> +#include <string> +#include <vector> + +#include "base/android/scoped_java_ref.h" +#include "base/strings/string16.h" +#include "starboard/types.h" + +namespace base { +namespace android { + +// Returns a new Java byte array converted from the given bytes array. +BASE_EXPORT ScopedJavaLocalRef<jbyteArray> ToJavaByteArray(JNIEnv* env, + const uint8_t* bytes, + size_t len); + +BASE_EXPORT ScopedJavaLocalRef<jbyteArray> ToJavaByteArray( + JNIEnv* env, + const std::vector<uint8_t>& bytes); + +// Returns a new Java byte array converted from the given string. No UTF-8 +// conversion is performed. +BASE_EXPORT ScopedJavaLocalRef<jbyteArray> ToJavaByteArray( + JNIEnv* env, + const std::string& str); + +// Returns a new Java boolean array converted from the given bool array. +BASE_EXPORT ScopedJavaLocalRef<jbooleanArray> +ToJavaBooleanArray(JNIEnv* env, const bool* bools, size_t len); + +// Returns a new Java int array converted from the given int array. +BASE_EXPORT ScopedJavaLocalRef<jintArray> ToJavaIntArray( + JNIEnv* env, const int* ints, size_t len); + +BASE_EXPORT ScopedJavaLocalRef<jintArray> ToJavaIntArray( + JNIEnv* env, const std::vector<int>& ints); + +// Returns a new Java long array converted from the given int64_t array. +BASE_EXPORT ScopedJavaLocalRef<jlongArray> ToJavaLongArray(JNIEnv* env, + const int64_t* longs, + size_t len); + +BASE_EXPORT ScopedJavaLocalRef<jlongArray> ToJavaLongArray( + JNIEnv* env, + const std::vector<int64_t>& longs); + +// Returns a new Java float array converted from the given C++ float array. +BASE_EXPORT ScopedJavaLocalRef<jfloatArray> ToJavaFloatArray( + JNIEnv* env, const float* floats, size_t len); + +BASE_EXPORT ScopedJavaLocalRef<jfloatArray> ToJavaFloatArray( + JNIEnv* env, + const std::vector<float>& floats); + +// Returns a array of Java byte array converted from |v|. +BASE_EXPORT ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfByteArray( + JNIEnv* env, const std::vector<std::string>& v); + +BASE_EXPORT ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfStrings( + JNIEnv* env, const std::vector<std::string>& v); + +BASE_EXPORT ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfStrings( + JNIEnv* env, const std::vector<string16>& v); + +// Converts a Java string array to a native array. +BASE_EXPORT void AppendJavaStringArrayToStringVector( + JNIEnv* env, + jobjectArray array, + std::vector<string16>* out); + +BASE_EXPORT void AppendJavaStringArrayToStringVector( + JNIEnv* env, + jobjectArray array, + std::vector<std::string>* out); + +// Appends the Java bytes in |bytes_array| onto the end of |out|. +BASE_EXPORT void AppendJavaByteArrayToByteVector(JNIEnv* env, + jbyteArray byte_array, + std::vector<uint8_t>* out); + +// Replaces the content of |out| with the Java bytes in |byte_array|. +BASE_EXPORT void JavaByteArrayToByteVector(JNIEnv* env, + jbyteArray byte_array, + std::vector<uint8_t>* out); + +// Replaces the content of |out| with the Java bytes in |byte_array|. No UTF-8 +// conversion is performed. +BASE_EXPORT void JavaByteArrayToString(JNIEnv* env, + jbyteArray byte_array, + std::string* out); + +// Replaces the content of |out| with the Java booleans in |boolean_array|. +BASE_EXPORT void JavaBooleanArrayToBoolVector(JNIEnv* env, + jbooleanArray boolean_array, + std::vector<bool>* out); + +// Replaces the content of |out| with the Java ints in |int_array|. +BASE_EXPORT void JavaIntArrayToIntVector( + JNIEnv* env, + jintArray int_array, + std::vector<int>* out); + +// Replaces the content of |out| with the Java longs in |long_array|. +BASE_EXPORT void JavaLongArrayToInt64Vector(JNIEnv* env, + jlongArray long_array, + std::vector<int64_t>* out); + +// Replaces the content of |out| with the Java longs in |long_array|. +BASE_EXPORT void JavaLongArrayToLongVector( + JNIEnv* env, + jlongArray long_array, + std::vector<jlong>* out); + +// Replaces the content of |out| with the Java floats in |float_array|. +BASE_EXPORT void JavaFloatArrayToFloatVector( + JNIEnv* env, + jfloatArray float_array, + std::vector<float>* out); + +// Assuming |array| is an byte[][] (array of byte arrays), replaces the +// content of |out| with the corresponding vector of strings. No UTF-8 +// conversion is performed. +BASE_EXPORT void JavaArrayOfByteArrayToStringVector( + JNIEnv* env, + jobjectArray array, + std::vector<std::string>* out); + +// Assuming |array| is an int[][] (array of int arrays), replaces the +// contents of |out| with the corresponding vectors of ints. +BASE_EXPORT void JavaArrayOfIntArrayToIntVector( + JNIEnv* env, + jobjectArray array, + std::vector<std::vector<int>>* out); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_JNI_ARRAY_H_
diff --git a/src/base/android/jni_array_unittest.cc b/src/base/android/jni_array_unittest.cc new file mode 100644 index 0000000..ce46a48 --- /dev/null +++ b/src/base/android/jni_array_unittest.cc
@@ -0,0 +1,400 @@ +// Copyright (c) 2012 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. + +#include "base/android/jni_array.h" + +#include <limits> + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/macros.h" +#include "starboard/string.h" +#include "starboard/types.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +TEST(JniArray, BasicConversions) { + const uint8_t kBytes[] = {0, 1, 2, 3}; + const size_t kLen = arraysize(kBytes); + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jbyteArray> bytes = ToJavaByteArray(env, kBytes, kLen); + ASSERT_TRUE(bytes.obj()); + + std::vector<uint8_t> inputVector(kBytes, kBytes + kLen); + ScopedJavaLocalRef<jbyteArray> bytesFromVector = + ToJavaByteArray(env, inputVector); + ASSERT_TRUE(bytesFromVector.obj()); + + std::vector<uint8_t> vectorFromBytes(5); + std::vector<uint8_t> vectorFromVector(5); + JavaByteArrayToByteVector(env, bytes.obj(), &vectorFromBytes); + JavaByteArrayToByteVector(env, bytesFromVector.obj(), &vectorFromVector); + EXPECT_EQ(4U, vectorFromBytes.size()); + EXPECT_EQ(4U, vectorFromVector.size()); + std::vector<uint8_t> expected_vec(kBytes, kBytes + kLen); + EXPECT_EQ(expected_vec, vectorFromBytes); + EXPECT_EQ(expected_vec, vectorFromVector); + + AppendJavaByteArrayToByteVector(env, bytes.obj(), &vectorFromBytes); + EXPECT_EQ(8U, vectorFromBytes.size()); + expected_vec.insert(expected_vec.end(), kBytes, kBytes + kLen); + EXPECT_EQ(expected_vec, vectorFromBytes); +} + +TEST(JniArray, ByteArrayStringConversions) { + JNIEnv* env = AttachCurrentThread(); + std::string inputString("hello\0world"); + ScopedJavaLocalRef<jbyteArray> bytesFromString = + ToJavaByteArray(env, inputString); + ASSERT_TRUE(bytesFromString.obj()); + + std::string stringFromString; + JavaByteArrayToString(env, bytesFromString.obj(), &stringFromString); + EXPECT_EQ(inputString, stringFromString); +} + +void CheckBoolConversion(JNIEnv* env, + const bool* bool_array, + const size_t len, + const ScopedJavaLocalRef<jbooleanArray>& booleans) { + ASSERT_TRUE(booleans.obj()); + + jsize java_array_len = env->GetArrayLength(booleans.obj()); + ASSERT_EQ(static_cast<jsize>(len), java_array_len); + + jboolean value; + for (size_t i = 0; i < len; ++i) { + env->GetBooleanArrayRegion(booleans.obj(), i, 1, &value); + ASSERT_EQ(bool_array[i], value); + } +} + +TEST(JniArray, BoolConversions) { + const bool kBools[] = {false, true, false}; + const size_t kLen = arraysize(kBools); + + JNIEnv* env = AttachCurrentThread(); + CheckBoolConversion(env, kBools, kLen, ToJavaBooleanArray(env, kBools, kLen)); +} + +void CheckIntConversion( + JNIEnv* env, + const int* int_array, + const size_t len, + const ScopedJavaLocalRef<jintArray>& ints) { + ASSERT_TRUE(ints.obj()); + + jsize java_array_len = env->GetArrayLength(ints.obj()); + ASSERT_EQ(static_cast<jsize>(len), java_array_len); + + jint value; + for (size_t i = 0; i < len; ++i) { + env->GetIntArrayRegion(ints.obj(), i, 1, &value); + ASSERT_EQ(int_array[i], value); + } +} + +TEST(JniArray, IntConversions) { + const int kInts[] = {0, 1, -1, std::numeric_limits<int32_t>::min(), + std::numeric_limits<int32_t>::max()}; + const size_t kLen = arraysize(kInts); + + JNIEnv* env = AttachCurrentThread(); + CheckIntConversion(env, kInts, kLen, ToJavaIntArray(env, kInts, kLen)); + + const std::vector<int> vec(kInts, kInts + kLen); + CheckIntConversion(env, kInts, kLen, ToJavaIntArray(env, vec)); +} + +void CheckLongConversion(JNIEnv* env, + const int64_t* long_array, + const size_t len, + const ScopedJavaLocalRef<jlongArray>& longs) { + ASSERT_TRUE(longs.obj()); + + jsize java_array_len = env->GetArrayLength(longs.obj()); + ASSERT_EQ(static_cast<jsize>(len), java_array_len); + + jlong value; + for (size_t i = 0; i < len; ++i) { + env->GetLongArrayRegion(longs.obj(), i, 1, &value); + ASSERT_EQ(long_array[i], value); + } +} + +TEST(JniArray, LongConversions) { + const int64_t kLongs[] = {0, 1, -1, std::numeric_limits<int64_t>::min(), + std::numeric_limits<int64_t>::max()}; + const size_t kLen = arraysize(kLongs); + + JNIEnv* env = AttachCurrentThread(); + CheckLongConversion(env, kLongs, kLen, ToJavaLongArray(env, kLongs, kLen)); + + const std::vector<int64_t> vec(kLongs, kLongs + kLen); + CheckLongConversion(env, kLongs, kLen, ToJavaLongArray(env, vec)); +} + +void CheckIntArrayConversion(JNIEnv* env, + ScopedJavaLocalRef<jintArray> jints, + std::vector<int> int_vector, + const size_t len) { + jint value; + for (size_t i = 0; i < len; ++i) { + env->GetIntArrayRegion(jints.obj(), i, 1, &value); + ASSERT_EQ(int_vector[i], value); + } +} + +void CheckBoolArrayConversion(JNIEnv* env, + ScopedJavaLocalRef<jbooleanArray> jbooleans, + std::vector<bool> bool_vector, + const size_t len) { + jboolean value; + for (size_t i = 0; i < len; ++i) { + env->GetBooleanArrayRegion(jbooleans.obj(), i, 1, &value); + ASSERT_EQ(bool_vector[i], value); + } +} + +void CheckFloatConversion( + JNIEnv* env, + const float* float_array, + const size_t len, + const ScopedJavaLocalRef<jfloatArray>& floats) { + ASSERT_TRUE(floats.obj()); + + jsize java_array_len = env->GetArrayLength(floats.obj()); + ASSERT_EQ(static_cast<jsize>(len), java_array_len); + + jfloat value; + for (size_t i = 0; i < len; ++i) { + env->GetFloatArrayRegion(floats.obj(), i, 1, &value); + ASSERT_EQ(float_array[i], value); + } +} + +TEST(JniArray, FloatConversions) { + const float kFloats[] = { 0.0f, 1.0f, -10.0f}; + const size_t kLen = arraysize(kFloats); + + JNIEnv* env = AttachCurrentThread(); + CheckFloatConversion(env, kFloats, kLen, + ToJavaFloatArray(env, kFloats, kLen)); + + const std::vector<float> vec(kFloats, kFloats + kLen); + CheckFloatConversion(env, kFloats, kLen, ToJavaFloatArray(env, vec)); +} + +TEST(JniArray, JavaBooleanArrayToBoolVector) { + const bool kBools[] = {false, true, false}; + const size_t kLen = arraysize(kBools); + + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jbooleanArray> jbooleans(env, env->NewBooleanArray(kLen)); + ASSERT_TRUE(jbooleans.obj()); + + for (size_t i = 0; i < kLen; ++i) { + jboolean j = static_cast<jboolean>(kBools[i]); + env->SetBooleanArrayRegion(jbooleans.obj(), i, 1, &j); + ASSERT_FALSE(HasException(env)); + } + + std::vector<bool> bools; + JavaBooleanArrayToBoolVector(env, jbooleans.obj(), &bools); + + ASSERT_EQ(static_cast<jsize>(bools.size()), + env->GetArrayLength(jbooleans.obj())); + + CheckBoolArrayConversion(env, jbooleans, bools, kLen); +} + +TEST(JniArray, JavaIntArrayToIntVector) { + const int kInts[] = {0, 1, -1}; + const size_t kLen = arraysize(kInts); + + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jintArray> jints(env, env->NewIntArray(kLen)); + ASSERT_TRUE(jints.obj()); + + for (size_t i = 0; i < kLen; ++i) { + jint j = static_cast<jint>(kInts[i]); + env->SetIntArrayRegion(jints.obj(), i, 1, &j); + ASSERT_FALSE(HasException(env)); + } + + std::vector<int> ints; + JavaIntArrayToIntVector(env, jints.obj(), &ints); + + ASSERT_EQ(static_cast<jsize>(ints.size()), env->GetArrayLength(jints.obj())); + + CheckIntArrayConversion(env, jints, ints, kLen); +} + +TEST(JniArray, JavaLongArrayToInt64Vector) { + const int64_t kInt64s[] = {0LL, 1LL, -1LL}; + const size_t kLen = arraysize(kInt64s); + + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jlongArray> jlongs(env, env->NewLongArray(kLen)); + ASSERT_TRUE(jlongs.obj()); + + for (size_t i = 0; i < kLen; ++i) { + jlong j = static_cast<jlong>(kInt64s[i]); + env->SetLongArrayRegion(jlongs.obj(), i, 1, &j); + ASSERT_FALSE(HasException(env)); + } + + std::vector<int64_t> int64s; + JavaLongArrayToInt64Vector(env, jlongs.obj(), &int64s); + + ASSERT_EQ(static_cast<jsize>(int64s.size()), + env->GetArrayLength(jlongs.obj())); + + jlong value; + for (size_t i = 0; i < kLen; ++i) { + env->GetLongArrayRegion(jlongs.obj(), i, 1, &value); + ASSERT_EQ(int64s[i], value); + ASSERT_EQ(kInt64s[i], int64s[i]); + } +} + +TEST(JniArray, JavaLongArrayToLongVector) { + const int64_t kInt64s[] = {0LL, 1LL, -1LL}; + const size_t kLen = arraysize(kInt64s); + + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jlongArray> jlongs(env, env->NewLongArray(kLen)); + ASSERT_TRUE(jlongs.obj()); + + for (size_t i = 0; i < kLen; ++i) { + jlong j = static_cast<jlong>(kInt64s[i]); + env->SetLongArrayRegion(jlongs.obj(), i, 1, &j); + ASSERT_FALSE(HasException(env)); + } + + std::vector<jlong> jlongs_vector; + JavaLongArrayToLongVector(env, jlongs.obj(), &jlongs_vector); + + ASSERT_EQ(static_cast<jsize>(jlongs_vector.size()), + env->GetArrayLength(jlongs.obj())); + + jlong value; + for (size_t i = 0; i < kLen; ++i) { + env->GetLongArrayRegion(jlongs.obj(), i, 1, &value); + ASSERT_EQ(jlongs_vector[i], value); + } +} + +TEST(JniArray, JavaFloatArrayToFloatVector) { + const float kFloats[] = {0.0, 0.5, -0.5}; + const size_t kLen = arraysize(kFloats); + + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jfloatArray> jfloats(env, env->NewFloatArray(kLen)); + ASSERT_TRUE(jfloats.obj()); + + for (size_t i = 0; i < kLen; ++i) { + jfloat j = static_cast<jfloat>(kFloats[i]); + env->SetFloatArrayRegion(jfloats.obj(), i, 1, &j); + ASSERT_FALSE(HasException(env)); + } + + std::vector<float> floats; + JavaFloatArrayToFloatVector(env, jfloats.obj(), &floats); + + ASSERT_EQ(static_cast<jsize>(floats.size()), + env->GetArrayLength(jfloats.obj())); + + jfloat value; + for (size_t i = 0; i < kLen; ++i) { + env->GetFloatArrayRegion(jfloats.obj(), i, 1, &value); + ASSERT_EQ(floats[i], value); + } +} + +TEST(JniArray, JavaArrayOfByteArrayToStringVector) { + const int kMaxItems = 50; + JNIEnv* env = AttachCurrentThread(); + + // Create a byte[][] object. + ScopedJavaLocalRef<jclass> byte_array_clazz(env, env->FindClass("[B")); + ASSERT_TRUE(byte_array_clazz.obj()); + + ScopedJavaLocalRef<jobjectArray> array( + env, env->NewObjectArray(kMaxItems, byte_array_clazz.obj(), NULL)); + ASSERT_TRUE(array.obj()); + + // Create kMaxItems byte buffers. + char text[16]; + for (int i = 0; i < kMaxItems; ++i) { + snprintf(text, sizeof text, "%d", i); + ScopedJavaLocalRef<jbyteArray> byte_array = + ToJavaByteArray(env, reinterpret_cast<uint8_t*>(text), + static_cast<size_t>(SbStringGetLength(text))); + ASSERT_TRUE(byte_array.obj()); + + env->SetObjectArrayElement(array.obj(), i, byte_array.obj()); + ASSERT_FALSE(HasException(env)); + } + + // Convert to std::vector<std::string>, check the content. + std::vector<std::string> vec; + JavaArrayOfByteArrayToStringVector(env, array.obj(), &vec); + + EXPECT_EQ(static_cast<size_t>(kMaxItems), vec.size()); + for (int i = 0; i < kMaxItems; ++i) { + snprintf(text, sizeof text, "%d", i); + EXPECT_STREQ(text, vec[i].c_str()); + } +} + +TEST(JniArray, JavaArrayOfIntArrayToIntVector) { + const size_t kNumItems = 4; + JNIEnv* env = AttachCurrentThread(); + + // Create an int[][] object. + ScopedJavaLocalRef<jclass> int_array_clazz(env, env->FindClass("[I")); + ASSERT_TRUE(int_array_clazz.obj()); + + ScopedJavaLocalRef<jobjectArray> array( + env, env->NewObjectArray(kNumItems, int_array_clazz.obj(), nullptr)); + ASSERT_TRUE(array.obj()); + + // Populate int[][] object. + const int kInts0[] = {0, 1, -1, std::numeric_limits<int32_t>::min(), + std::numeric_limits<int32_t>::max()}; + const size_t kLen0 = arraysize(kInts0); + ScopedJavaLocalRef<jintArray> int_array0 = ToJavaIntArray(env, kInts0, kLen0); + env->SetObjectArrayElement(array.obj(), 0, int_array0.obj()); + + const int kInts1[] = {3, 4, 5}; + const size_t kLen1 = arraysize(kInts1); + ScopedJavaLocalRef<jintArray> int_array1 = ToJavaIntArray(env, kInts1, kLen1); + env->SetObjectArrayElement(array.obj(), 1, int_array1.obj()); + + const int kInts2[] = {}; + const size_t kLen2 = 0; + ScopedJavaLocalRef<jintArray> int_array2 = ToJavaIntArray(env, kInts2, kLen2); + env->SetObjectArrayElement(array.obj(), 2, int_array2.obj()); + + const int kInts3[] = {16}; + const size_t kLen3 = arraysize(kInts3); + ScopedJavaLocalRef<jintArray> int_array3 = ToJavaIntArray(env, kInts3, kLen3); + env->SetObjectArrayElement(array.obj(), 3, int_array3.obj()); + + // Convert to std::vector<std::vector<int>>, check the content. + std::vector<std::vector<int>> out; + JavaArrayOfIntArrayToIntVector(env, array.obj(), &out); + + EXPECT_EQ(kNumItems, out.size()); + CheckIntArrayConversion(env, int_array0, out[0], kLen0); + CheckIntArrayConversion(env, int_array1, out[1], kLen1); + CheckIntArrayConversion(env, int_array2, out[2], kLen2); + CheckIntArrayConversion(env, int_array3, out[3], kLen3); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/jni_generator/AndroidManifest.xml b/src/base/android/jni_generator/AndroidManifest.xml new file mode 100644 index 0000000..ec28ff5 --- /dev/null +++ b/src/base/android/jni_generator/AndroidManifest.xml
@@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2017 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.jni.generator"> + + <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="24" /> + <application></application> + +</manifest>
diff --git a/src/base/android/jni_generator/BUILD.gn b/src/base/android/jni_generator/BUILD.gn new file mode 100644 index 0000000..e67e763 --- /dev/null +++ b/src/base/android/jni_generator/BUILD.gn
@@ -0,0 +1,92 @@ +# Copyright 2016 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. + +import("//base/android/jni_generator/jni_exception_list.gni") +import("//build/config/android/rules.gni") +import("//testing/test.gni") + +generate_jni("jni_sample_header") { + sources = [ + "java/src/org/chromium/example/jni_generator/SampleForTests.java", + ] + jni_package = "example" +} + +android_library("jni_sample_java") { + java_files = + [ "java/src/org/chromium/example/jni_generator/SampleForTests.java" ] + deps = [ + "//base:base_java", + ] +} + +android_library("jni_annotation_sample_java") { + java_files = [ "java/src/org/chromium/example/jni_generator/SampleForAnnotationProcessor.java" ] + deps = [ + "//base:jni_processor_annotations_java", + ] + annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ] +} + +source_set("jni_sample_native_side") { + deps = [ + ":jni_sample_header", + "//base", + ] + sources = [ + "sample_for_tests.cc", + "sample_for_tests.h", + ] +} + +shared_library("jni_sample_lib") { + sources = [ + "sample_entry_point.cc", + ] + + deps = [ + ":jni_sample_native_side", + ":sample_jni_registration", + "//base", + ] +} + +android_apk("sample_jni_apk") { + apk_name = "SampleJni" + android_manifest = "AndroidManifest.xml" + deps = [ + ":jni_sample_java", + "//base:base_java", + ] + shared_libraries = [ ":jni_sample_lib" ] +} + +generate_jni_registration("sample_jni_registration") { + target = ":sample_jni_apk" + output = "$target_gen_dir/${target_name}.h" + exception_files = jni_exception_files +} + +# Serves to test that generated bindings compile properly. +group("jni_generator_tests") { + deps = [ + ":jni_annotation_sample_java", + ":sample_jni_apk", + ] +} + +java_annotation_processor("jni_processor") { + java_files = [ "java/src/org/chromium/jni_generator/JniProcessor.java" ] + + main_class = "org.chromium.jni_generator.JniProcessor" + + annotation_processor_deps = [ "//third_party/auto:auto_service_processor" ] + + deps = [ + "//base:jni_processor_annotations_java", + "//third_party/android_deps:com_squareup_javapoet_java", + "//third_party/auto:auto_service_java", + "//third_party/guava:guava_java", + ] +}
diff --git a/src/base/android/jni_generator/README.md b/src/base/android/jni_generator/README.md new file mode 100644 index 0000000..17ad150 --- /dev/null +++ b/src/base/android/jni_generator/README.md
@@ -0,0 +1,118 @@ +# Overview +JNI (Java Native Interface) is the mechanism that enables Java code to call +native functions, and native code to call Java functions. + + * Native code calls into Java using apis from `<jni.h>`, which basically mirror + Java's reflection APIs. + * Java code calls native functions by declaring body-less functions with the + `native` keyword, and then calling them as normal Java functions. + +`jni_generator` generates boiler-plate code with the goal of making our code: + 1. easier to write, and + 2. typesafe. + +`jni_generator` uses regular expressions to parse .Java files, so don't do +anything too fancy. E.g.: + * Classes must be either explicitly imported, or are assumed to be in +the same package. To use `java.lang` classes, add an explicit import. + * Inner classes need to be referenced through the outer class. E.g.: + `void call(Outer.Inner inner)` + +The presense of any JNI within a class will result in ProGuard obfuscation for +the class to be disabled. + +### Exposing Native Methods + +**Without Crazy Linker:** + * Java->Native calls are exported from the shared library and lazily resolved + by the runtime (via `dlsym()`). + +**With Crazy Linker:** + * Java->Native calls are explicitly registered with JNI on the native side. + Explicit registration is necessary because crazy linker provides its own + `dlsym()`, but JNI is hardcoded to use the system's `dlsym()`. + * The logic to explicitly register stubs is generated by + `jni_registration_generator.py`. + * This script finds all native methods by scanning all source `.java` files + of an APK. Inefficient, but very convenient. + * Since `dlsym()` is not used in this case, we use a linker script to avoid + the cost of exporting symbols from the shared library (refer to + `//build/config/android:hide_all_but_jni_onload`). + * `jni_registration_generator.py` exposes two registrations methods: + * `RegisterNonMainDexNatives` - Registers native functions needed by multiple + process types (e.g. Rendereres, GPU process). + * `RegisterMainDexNatives` - Registers native functions needed only by the + browser process. + +### Exposing Java Methods + +Java methods just need to be annotated with `@CalledByNative`. The generated +functions can be put into a namespace using `@JNINamespace("your_namespace")`. + +## Usage + +Because the generator does not generate any source files, generated headers must +not be `#included` by multiple sources. If there are Java functions that need to +be called by multiple sources, one source should be chosen to expose the +functions to the others via additional wrapper functions. + +### Calling Java -> Native + + * Methods marked as `native` will have stubs generated for them that forward + calls to C++ function (that you must write). + * If the first parameter is a C++ object (e.g. `long mNativePointer`), then the + bindings will automatically generate the appropriate cast and call into C++ + code (JNI itself is only C). + +### Calling Native -> Java + + * Methods annotated with `@CalledByNative` will have stubs generated for them. + * Just call the generated stubs defined in generated `.h` files. + +### Java Objects and Garbage Collection + +All pointers to Java objects must be registered with JNI in order to prevent +garbage collection from invalidating them. + +For Strings & Arrays - it's common practice to use the `//base/android/jni_*` +helpers to convert them to `std::vectors` and `std::strings` as soon as +possible. + +For other objects - use smart pointers to store them: + * `ScopedJavaLocalRef<>` - When lifetime is the current function's scope. + * `ScopedJavaGlobalRef<>` - When lifetime is longer than the current function's + scope. + * `JavaObjectWeakGlobalRef<>` - Weak reference (do not prevent garbage + collection). + * `JavaParamRef<>` - Use to accept any of the above as a parameter to a + function without creating a redundant registration. + +### Additional Guidelines / Advice + +Minimize the surface API between the two sides. Rather than calling multiple +functions across boundaries, call only one (and then on the other side, call as +many little functions as required). + +If a Java object "owns" a native one, store the pointer via +`"long mNativeClassName"`. Ensure to eventually call a native method to delete +the object. For example, have a `close()` that deletes the native object. + +The best way to pass "compound" types across in either direction is to +create an inner class with PODs and a factory function. If possible, make mark +all the fields as "final". + +## Build Rules + + * `generate_jni` - Generates a header file with stubs for given `.java` files + * `generate_jar_jni` - Generates a header file with stubs for a given `.jar` + file + * `generate_jni_registration` - Generates a header file with functions to + register native-side JNI methods (required only when using crazy linker). + +Refer to [//build/config/android/rules.gni](https://cs.chromium.org/chromium/src/build/config/android/rules.gni) +for more about the GN templates. + +## Changing `jni_generator` + + * Python unit tests live in `jni_generator_tests.py` + * A working demo app exists as `//base/android/jni_generator:sample_jni_apk`
diff --git a/src/base/android/jni_generator/SampleForTests_jni.golden b/src/base/android/jni_generator/SampleForTests_jni.golden new file mode 100644 index 0000000..7909207 --- /dev/null +++ b/src/base/android/jni_generator/SampleForTests_jni.golden
@@ -0,0 +1,496 @@ +// Copyright 2014 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. + + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// org/chromium/example/jni_generator/SampleForTests + +#ifndef org_chromium_example_jni_generator_SampleForTests_JNI +#define org_chromium_example_jni_generator_SampleForTests_JNI + +#include <jni.h> + +#include "base/android/jni_generator/jni_generator_helper.h" + + +// Step 1: Forward declarations. + +JNI_REGISTRATION_EXPORT extern const char + kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA[]; +const char kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA[] = + "org/chromium/example/jni_generator/SampleForTests$InnerStructA"; + +JNI_REGISTRATION_EXPORT extern const char + kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024InnerClass[]; +const char kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024InnerClass[] = + "org/chromium/example/jni_generator/SampleForTests$InnerClass"; + +JNI_REGISTRATION_EXPORT extern const char + kClassPath_org_chromium_example_jni_1generator_SampleForTests[]; +const char kClassPath_org_chromium_example_jni_1generator_SampleForTests[] = + "org/chromium/example/jni_generator/SampleForTests"; + +JNI_REGISTRATION_EXPORT extern const char + kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB[]; +const char kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB[] = + "org/chromium/example/jni_generator/SampleForTests$InnerStructB"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +JNI_REGISTRATION_EXPORT std::atomic<jclass> + g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz(nullptr); +#ifndef org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz_defined +#define org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz_defined +inline jclass org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz(JNIEnv* + env) { + return base::android::LazyGetClass(env, + kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA, + &g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz); +} +#endif +// Leaking this jclass as we cannot use LazyInstance from some threads. +JNI_REGISTRATION_EXPORT std::atomic<jclass> + g_org_chromium_example_jni_1generator_SampleForTests_00024InnerClass_clazz(nullptr); +#ifndef org_chromium_example_jni_1generator_SampleForTests_00024InnerClass_clazz_defined +#define org_chromium_example_jni_1generator_SampleForTests_00024InnerClass_clazz_defined +inline jclass org_chromium_example_jni_1generator_SampleForTests_00024InnerClass_clazz(JNIEnv* env) + { + return base::android::LazyGetClass(env, + kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024InnerClass, + &g_org_chromium_example_jni_1generator_SampleForTests_00024InnerClass_clazz); +} +#endif +// Leaking this jclass as we cannot use LazyInstance from some threads. +JNI_REGISTRATION_EXPORT std::atomic<jclass> + g_org_chromium_example_jni_1generator_SampleForTests_clazz(nullptr); +#ifndef org_chromium_example_jni_1generator_SampleForTests_clazz_defined +#define org_chromium_example_jni_1generator_SampleForTests_clazz_defined +inline jclass org_chromium_example_jni_1generator_SampleForTests_clazz(JNIEnv* env) { + return base::android::LazyGetClass(env, + kClassPath_org_chromium_example_jni_1generator_SampleForTests, + &g_org_chromium_example_jni_1generator_SampleForTests_clazz); +} +#endif +// Leaking this jclass as we cannot use LazyInstance from some threads. +JNI_REGISTRATION_EXPORT std::atomic<jclass> + g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz(nullptr); +#ifndef org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz_defined +#define org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz_defined +inline jclass org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz(JNIEnv* + env) { + return base::android::LazyGetClass(env, + kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB, + &g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz); +} +#endif + + +// Step 2: Constants (optional). + + +// Step 3: Method stubs. +namespace base { +namespace android { + +static jlong JNI_SampleForTests_Init(JNIEnv* env, const base::android::JavaParamRef<jobject>& + jcaller, + const base::android::JavaParamRef<jstring>& param); + +JNI_GENERATOR_EXPORT jlong Java_org_chromium_example_jni_1generator_SampleForTests_nativeInit( + JNIEnv* env, + jobject jcaller, + jstring param) { + return JNI_SampleForTests_Init(env, base::android::JavaParamRef<jobject>(env, jcaller), + base::android::JavaParamRef<jstring>(env, param)); +} + +JNI_GENERATOR_EXPORT void Java_org_chromium_example_jni_1generator_SampleForTests_nativeDestroy( + JNIEnv* env, + jobject jcaller, + jlong nativeCPPClass) { + CPPClass* native = reinterpret_cast<CPPClass*>(nativeCPPClass); + CHECK_NATIVE_PTR(env, jcaller, native, "Destroy"); + return native->Destroy(env, base::android::JavaParamRef<jobject>(env, jcaller)); +} + +static jdouble JNI_SampleForTests_GetDoubleFunction(JNIEnv* env, const + base::android::JavaParamRef<jobject>& jcaller); + +JNI_GENERATOR_EXPORT jdouble + Java_org_chromium_example_jni_1generator_SampleForTests_nativeGetDoubleFunction( + JNIEnv* env, + jobject jcaller) { + return JNI_SampleForTests_GetDoubleFunction(env, base::android::JavaParamRef<jobject>(env, + jcaller)); +} + +static jfloat JNI_SampleForTests_GetFloatFunction(JNIEnv* env, const + base::android::JavaParamRef<jclass>& jcaller); + +JNI_GENERATOR_EXPORT jfloat + Java_org_chromium_example_jni_1generator_SampleForTests_nativeGetFloatFunction( + JNIEnv* env, + jclass jcaller) { + return JNI_SampleForTests_GetFloatFunction(env, base::android::JavaParamRef<jclass>(env, + jcaller)); +} + +static void JNI_SampleForTests_SetNonPODDatatype(JNIEnv* env, const + base::android::JavaParamRef<jobject>& jcaller, + const base::android::JavaParamRef<jobject>& rect); + +JNI_GENERATOR_EXPORT void + Java_org_chromium_example_jni_1generator_SampleForTests_nativeSetNonPODDatatype( + JNIEnv* env, + jobject jcaller, + jobject rect) { + return JNI_SampleForTests_SetNonPODDatatype(env, base::android::JavaParamRef<jobject>(env, + jcaller), base::android::JavaParamRef<jobject>(env, rect)); +} + +static base::android::ScopedJavaLocalRef<jobject> JNI_SampleForTests_GetNonPODDatatype(JNIEnv* env, + const base::android::JavaParamRef<jobject>& jcaller); + +JNI_GENERATOR_EXPORT jobject + Java_org_chromium_example_jni_1generator_SampleForTests_nativeGetNonPODDatatype( + JNIEnv* env, + jobject jcaller) { + return JNI_SampleForTests_GetNonPODDatatype(env, base::android::JavaParamRef<jobject>(env, + jcaller)).Release(); +} + +JNI_GENERATOR_EXPORT jint Java_org_chromium_example_jni_1generator_SampleForTests_nativeMethod( + JNIEnv* env, + jobject jcaller, + jlong nativeCPPClass) { + CPPClass* native = reinterpret_cast<CPPClass*>(nativeCPPClass); + CHECK_NATIVE_PTR(env, jcaller, native, "Method", 0); + return native->Method(env, base::android::JavaParamRef<jobject>(env, jcaller)); +} + +JNI_GENERATOR_EXPORT jdouble + Java_org_chromium_example_jni_1generator_SampleForTests_nativeMethodOtherP0( + JNIEnv* env, + jobject jcaller, + jlong nativePtr) { + CPPClass::InnerClass* native = reinterpret_cast<CPPClass::InnerClass*>(nativePtr); + CHECK_NATIVE_PTR(env, jcaller, native, "MethodOtherP0", 0); + return native->MethodOtherP0(env, base::android::JavaParamRef<jobject>(env, jcaller)); +} + +JNI_GENERATOR_EXPORT void Java_org_chromium_example_jni_1generator_SampleForTests_nativeAddStructB( + JNIEnv* env, + jobject jcaller, + jlong nativeCPPClass, + jobject b) { + CPPClass* native = reinterpret_cast<CPPClass*>(nativeCPPClass); + CHECK_NATIVE_PTR(env, jcaller, native, "AddStructB"); + return native->AddStructB(env, base::android::JavaParamRef<jobject>(env, jcaller), + base::android::JavaParamRef<jobject>(env, b)); +} + +JNI_GENERATOR_EXPORT void + Java_org_chromium_example_jni_1generator_SampleForTests_nativeIterateAndDoSomethingWithStructB( + JNIEnv* env, + jobject jcaller, + jlong nativeCPPClass) { + CPPClass* native = reinterpret_cast<CPPClass*>(nativeCPPClass); + CHECK_NATIVE_PTR(env, jcaller, native, "IterateAndDoSomethingWithStructB"); + return native->IterateAndDoSomethingWithStructB(env, base::android::JavaParamRef<jobject>(env, + jcaller)); +} + +JNI_GENERATOR_EXPORT jstring + Java_org_chromium_example_jni_1generator_SampleForTests_nativeReturnAString( + JNIEnv* env, + jobject jcaller, + jlong nativeCPPClass) { + CPPClass* native = reinterpret_cast<CPPClass*>(nativeCPPClass); + CHECK_NATIVE_PTR(env, jcaller, native, "ReturnAString", NULL); + return native->ReturnAString(env, base::android::JavaParamRef<jobject>(env, jcaller)).Release(); +} + +static jint JNI_InnerClass_GetInnerIntFunction(JNIEnv* env, const + base::android::JavaParamRef<jclass>& jcaller); + +JNI_GENERATOR_EXPORT jint + Java_org_chromium_example_jni_1generator_SampleForTests_00024InnerClass_nativeGetInnerIntFunction( + JNIEnv* env, + jclass jcaller) { + return JNI_InnerClass_GetInnerIntFunction(env, base::android::JavaParamRef<jclass>(env, jcaller)); +} + + +static std::atomic<jmethodID> + g_org_chromium_example_jni_1generator_SampleForTests_javaMethod(nullptr); +static jint Java_SampleForTests_javaMethod(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + JniIntWrapper foo, + JniIntWrapper bar) { + CHECK_CLAZZ(env, obj.obj(), + org_chromium_example_jni_1generator_SampleForTests_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_example_jni_1generator_SampleForTests_clazz(env), + "javaMethod", + "(II)I", + &g_org_chromium_example_jni_1generator_SampleForTests_javaMethod); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id, as_jint(foo), as_jint(bar)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> + g_org_chromium_example_jni_1generator_SampleForTests_staticJavaMethod(nullptr); +static jboolean Java_SampleForTests_staticJavaMethod(JNIEnv* env) { + CHECK_CLAZZ(env, org_chromium_example_jni_1generator_SampleForTests_clazz(env), + org_chromium_example_jni_1generator_SampleForTests_clazz(env), false); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, org_chromium_example_jni_1generator_SampleForTests_clazz(env), + "staticJavaMethod", + "()Z", + &g_org_chromium_example_jni_1generator_SampleForTests_staticJavaMethod); + + jboolean ret = + env->CallStaticBooleanMethod(org_chromium_example_jni_1generator_SampleForTests_clazz(env), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> + g_org_chromium_example_jni_1generator_SampleForTests_packagePrivateJavaMethod(nullptr); +static void Java_SampleForTests_packagePrivateJavaMethod(JNIEnv* env, const + base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + org_chromium_example_jni_1generator_SampleForTests_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_example_jni_1generator_SampleForTests_clazz(env), + "packagePrivateJavaMethod", + "()V", + &g_org_chromium_example_jni_1generator_SampleForTests_packagePrivateJavaMethod); + + env->CallVoidMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); +} + +static std::atomic<jmethodID> + g_org_chromium_example_jni_1generator_SampleForTests_methodWithGenericParams(nullptr); +static void Java_SampleForTests_methodWithGenericParams(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, const base::android::JavaRef<jobject>& foo, + const base::android::JavaRef<jobject>& bar) { + CHECK_CLAZZ(env, obj.obj(), + org_chromium_example_jni_1generator_SampleForTests_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_example_jni_1generator_SampleForTests_clazz(env), + "methodWithGenericParams", + "(Ljava/util/Map;Ljava/util/LinkedList;)V", + &g_org_chromium_example_jni_1generator_SampleForTests_methodWithGenericParams); + + env->CallVoidMethod(obj.obj(), + method_id, foo.obj(), bar.obj()); + jni_generator::CheckException(env); +} + +static std::atomic<jmethodID> + g_org_chromium_example_jni_1generator_SampleForTests_Constructor(nullptr); +static base::android::ScopedJavaLocalRef<jobject> Java_SampleForTests_Constructor(JNIEnv* env, + JniIntWrapper foo, + JniIntWrapper bar) { + CHECK_CLAZZ(env, org_chromium_example_jni_1generator_SampleForTests_clazz(env), + org_chromium_example_jni_1generator_SampleForTests_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_example_jni_1generator_SampleForTests_clazz(env), + "<init>", + "(II)V", + &g_org_chromium_example_jni_1generator_SampleForTests_Constructor); + + jobject ret = + env->NewObject(org_chromium_example_jni_1generator_SampleForTests_clazz(env), + method_id, as_jint(foo), as_jint(bar)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jobject>(env, ret); +} + +static std::atomic<jmethodID> + g_org_chromium_example_jni_1generator_SampleForTests_methodThatThrowsException(nullptr); +static void Java_SampleForTests_methodThatThrowsException(JNIEnv* env, const + base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + org_chromium_example_jni_1generator_SampleForTests_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_example_jni_1generator_SampleForTests_clazz(env), + "methodThatThrowsException", + "()V", + &g_org_chromium_example_jni_1generator_SampleForTests_methodThatThrowsException); + + env->CallVoidMethod(obj.obj(), + method_id); +} + +static std::atomic<jmethodID> + g_org_chromium_example_jni_1generator_SampleForTests_javaMethodWithAnnotatedParam(nullptr); +static void Java_SampleForTests_javaMethodWithAnnotatedParam(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper foo) { + CHECK_CLAZZ(env, obj.obj(), + org_chromium_example_jni_1generator_SampleForTests_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_example_jni_1generator_SampleForTests_clazz(env), + "javaMethodWithAnnotatedParam", + "(I)V", + &g_org_chromium_example_jni_1generator_SampleForTests_javaMethodWithAnnotatedParam); + + env->CallVoidMethod(obj.obj(), + method_id, as_jint(foo)); + jni_generator::CheckException(env); +} + +static std::atomic<jmethodID> + g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_create(nullptr); +static base::android::ScopedJavaLocalRef<jobject> Java_InnerStructA_create(JNIEnv* env, jlong l, + JniIntWrapper i, + const base::android::JavaRef<jstring>& s) { + CHECK_CLAZZ(env, org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz(env), + org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz(env), + "create", + "(JILjava/lang/String;)Lorg/chromium/example/jni_generator/SampleForTests$InnerStructA;", + &g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_create); + + jobject ret = +env->CallStaticObjectMethod(org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz(env), + method_id, l, as_jint(i), s.obj()); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jobject>(env, ret); +} + +static std::atomic<jmethodID> + g_org_chromium_example_jni_1generator_SampleForTests_addStructA(nullptr); +static void Java_SampleForTests_addStructA(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + const base::android::JavaRef<jobject>& a) { + CHECK_CLAZZ(env, obj.obj(), + org_chromium_example_jni_1generator_SampleForTests_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_example_jni_1generator_SampleForTests_clazz(env), + "addStructA", + "(Lorg/chromium/example/jni_generator/SampleForTests$InnerStructA;)V", + &g_org_chromium_example_jni_1generator_SampleForTests_addStructA); + + env->CallVoidMethod(obj.obj(), + method_id, a.obj()); + jni_generator::CheckException(env); +} + +static std::atomic<jmethodID> + g_org_chromium_example_jni_1generator_SampleForTests_iterateAndDoSomething(nullptr); +static void Java_SampleForTests_iterateAndDoSomething(JNIEnv* env, const + base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + org_chromium_example_jni_1generator_SampleForTests_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_example_jni_1generator_SampleForTests_clazz(env), + "iterateAndDoSomething", + "()V", + &g_org_chromium_example_jni_1generator_SampleForTests_iterateAndDoSomething); + + env->CallVoidMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); +} + +static std::atomic<jmethodID> + g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_getKey(nullptr); +static jlong Java_InnerStructB_getKey(JNIEnv* env, const base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz(env), + "getKey", + "()J", + &g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_getKey); + + jlong ret = + env->CallLongMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> + g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_getValue(nullptr); +static base::android::ScopedJavaLocalRef<jstring> Java_InnerStructB_getValue(JNIEnv* env, const + base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz(env), + "getValue", + "()Ljava/lang/String;", + &g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_getValue); + + jstring ret = + static_cast<jstring>(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jstring>(env, ret); +} + +static std::atomic<jmethodID> + g_org_chromium_example_jni_1generator_SampleForTests_getInnerInterface(nullptr); +static base::android::ScopedJavaLocalRef<jobject> Java_SampleForTests_getInnerInterface(JNIEnv* env) + { + CHECK_CLAZZ(env, org_chromium_example_jni_1generator_SampleForTests_clazz(env), + org_chromium_example_jni_1generator_SampleForTests_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, org_chromium_example_jni_1generator_SampleForTests_clazz(env), + "getInnerInterface", + "()Lorg/chromium/example/jni_generator/SampleForTests$InnerInterface;", + &g_org_chromium_example_jni_1generator_SampleForTests_getInnerInterface); + + jobject ret = + env->CallStaticObjectMethod(org_chromium_example_jni_1generator_SampleForTests_clazz(env), + method_id); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jobject>(env, ret); +} + +static std::atomic<jmethodID> + g_org_chromium_example_jni_1generator_SampleForTests_getInnerEnum(nullptr); +static base::android::ScopedJavaLocalRef<jobject> Java_SampleForTests_getInnerEnum(JNIEnv* env) { + CHECK_CLAZZ(env, org_chromium_example_jni_1generator_SampleForTests_clazz(env), + org_chromium_example_jni_1generator_SampleForTests_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, org_chromium_example_jni_1generator_SampleForTests_clazz(env), + "getInnerEnum", + "()Lorg/chromium/example/jni_generator/SampleForTests$InnerEnum;", + &g_org_chromium_example_jni_1generator_SampleForTests_getInnerEnum); + + jobject ret = + env->CallStaticObjectMethod(org_chromium_example_jni_1generator_SampleForTests_clazz(env), + method_id); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jobject>(env, ret); +} + +} // namespace android +} // namespace base + +#endif // org_chromium_example_jni_generator_SampleForTests_JNI
diff --git a/src/base/android/jni_generator/android_jar.classes b/src/base/android/jni_generator/android_jar.classes new file mode 100644 index 0000000..7d412ce --- /dev/null +++ b/src/base/android/jni_generator/android_jar.classes
@@ -0,0 +1,98 @@ +java/lang/AbstractMethodError.class +java/lang/AbstractStringBuilder.class +java/lang/Appendable.class +java/lang/ArithmeticException.class +java/lang/ArrayIndexOutOfBoundsException.class +java/lang/ArrayStoreException.class +java/lang/AssertionError.class +java/lang/AutoCloseable.class +java/lang/Boolean.class +java/lang/Byte.class +java/lang/Character.class +java/lang/Character$Subset.class +java/lang/Character$UnicodeBlock.class +java/lang/CharSequence.class +java/lang/ClassCastException.class +java/lang/ClassCircularityError.class +java/lang/Class.class +java/lang/ClassFormatError.class +java/lang/ClassLoader.class +java/lang/ClassNotFoundException.class +java/lang/Cloneable.class +java/lang/CloneNotSupportedException.class +java/lang/Comparable.class +java/lang/Compiler.class +java/lang/Deprecated.class +java/lang/Double.class +java/lang/Enum.class +java/lang/EnumConstantNotPresentException.class +java/lang/Error.class +java/lang/Exception.class +java/lang/ExceptionInInitializerError.class +java/lang/Float.class +java/lang/IllegalAccessError.class +java/lang/IllegalAccessException.class +java/lang/IllegalArgumentException.class +java/lang/IllegalMonitorStateException.class +java/lang/IllegalStateException.class +java/lang/IncompatibleClassChangeError.class +java/lang/IndexOutOfBoundsException.class +java/lang/InheritableThreadLocal.class +java/lang/InstantiationError.class +java/lang/InstantiationException.class +java/lang/Integer.class +java/lang/InternalError.class +java/lang/InterruptedException.class +java/lang/Iterable.class +java/lang/LinkageError.class +java/lang/Long.class +java/lang/Math.class +java/lang/NegativeArraySizeException.class +java/lang/NoClassDefFoundError.class +java/lang/NoSuchFieldError.class +java/lang/NoSuchFieldException.class +java/lang/NoSuchMethodError.class +java/lang/NoSuchMethodException.class +java/lang/NullPointerException.class +java/lang/Number.class +java/lang/NumberFormatException.class +java/lang/Object.class +java/lang/OutOfMemoryError.class +java/lang/Override.class +java/lang/Package.class +java/lang/ProcessBuilder.class +java/lang/Process.class +java/lang/Readable.class +java/lang/ReflectiveOperationException.class +java/lang/Runnable.class +java/lang/Runtime.class +java/lang/RuntimeException.class +java/lang/RuntimePermission.class +java/lang/SafeVarargs.class +java/lang/SecurityException.class +java/lang/SecurityManager.class +java/lang/Short.class +java/lang/StackOverflowError.class +java/lang/StackTraceElement.class +java/lang/StrictMath.class +java/lang/StringBuffer.class +java/lang/StringBuilder.class +java/lang/String.class +java/lang/StringIndexOutOfBoundsException.class +java/lang/SuppressWarnings.class +java/lang/System.class +java/lang/Thread.class +java/lang/ThreadDeath.class +java/lang/ThreadGroup.class +java/lang/ThreadLocal.class +java/lang/Thread$State.class +java/lang/Thread$UncaughtExceptionHandler.class +java/lang/Throwable.class +java/lang/TypeNotPresentException.class +java/lang/UnknownError.class +java/lang/UnsatisfiedLinkError.class +java/lang/UnsupportedClassVersionError.class +java/lang/UnsupportedOperationException.class +java/lang/VerifyError.class +java/lang/VirtualMachineError.class +java/lang/Void.class
diff --git a/src/base/android/jni_generator/java/src/org/chromium/example/jni_generator/SampleForAnnotationProcessor.java b/src/base/android/jni_generator/java/src/org/chromium/example/jni_generator/SampleForAnnotationProcessor.java new file mode 100644 index 0000000..97095a4 --- /dev/null +++ b/src/base/android/jni_generator/java/src/org/chromium/example/jni_generator/SampleForAnnotationProcessor.java
@@ -0,0 +1,46 @@ +// Copyright 2018 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. + +package org.chromium.example.jni_generator; + +import org.chromium.base.annotations.JniStaticNatives; + +/** + * Sample class that uses the JNI annotation processor for static methods. + * See generated files at bottom. + */ +class SampleForAnnotationProcessor { + /** + * Static methods declared here, each of these refer to a native method + * which will have its declaration generated by our annotation processor. + * There will also be a class generated to wrap these native methods + * with the name SampleForAnnotationProcessorJni which will implement + * Natives. + */ + @JniStaticNatives + interface Natives { + void foo(String a, int b, char c, int[] d); + SampleForAnnotationProcessor bar(SampleForAnnotationProcessor sample); + String revString(String stringToReverse); + String[] sendToNative(String[] strs); + SampleForAnnotationProcessor[] sendSamplesToNative(SampleForAnnotationProcessor[] strs); + boolean hasPhalange(); + } + + void test() { + int[] x = new int[] {1, 2, 3, 4, 5}; + String[] strs = new String[] {"the", "quick", "brown", "fox"}; + strs = SampleForAnnotationProcessorJni.get().sendToNative(strs); + + SampleForAnnotationProcessor[] samples = + new SampleForAnnotationProcessor[] {this, this, this}; + samples = SampleForAnnotationProcessorJni.get().sendSamplesToNative(samples); + + // Instance of Natives accessed through (classname + "Jni").get(). + SampleForAnnotationProcessorJni.get().foo("Test", 5, 'c', x); + SampleForAnnotationProcessor sample = SampleForAnnotationProcessorJni.get().bar(this); + boolean hasPhalange = SampleForAnnotationProcessorJni.get().hasPhalange(); + String s = SampleForAnnotationProcessorJni.get().revString("abcd"); + } +}
diff --git a/src/base/android/jni_generator/java/src/org/chromium/example/jni_generator/SampleForTests.java b/src/base/android/jni_generator/java/src/org/chromium/example/jni_generator/SampleForTests.java new file mode 100644 index 0000000..ba3abe7 --- /dev/null +++ b/src/base/android/jni_generator/java/src/org/chromium/example/jni_generator/SampleForTests.java
@@ -0,0 +1,284 @@ +// Copyright 2012 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. + +package org.chromium.example.jni_generator; + +import android.graphics.Rect; + +import org.chromium.base.annotations.AccessedByNative; +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.CalledByNativeUnchecked; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.NativeCall; +import org.chromium.base.annotations.NativeClassQualifiedName; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +// This class serves as a reference test for the bindings generator, and as example documentation +// for how to use the jni generator. +// The C++ counter-part is sample_for_tests.cc. +// jni_generator/BUILD.gn has a jni_generator_tests target that will: +// * Generate a header file for the JNI bindings based on this file. +// * Generate a header file containing registration methods required to use C++ methods from this +// file. +// * Compile sample_for_tests.cc using the generated header file. +// * link a native executable to prove the generated header + cc file are self-contained. +// All comments are informational only, and are ignored by the jni generator. +// +// This JNINamespace annotation indicates that all native methods should be +// generated inside this namespace, including the native class that this +// object binds to. +@JNINamespace("base::android") +class SampleForTests { + // Classes can store their C++ pointer counterpart as an int that is normally initialized by + // calling out a nativeInit() function. Replace "CPPClass" with your particular class name! + long mNativeCPPObject; + + // You can define methods and attributes on the java class just like any other. + // Methods without the @CalledByNative annotation won't be exposed to JNI. + public SampleForTests() { + } + + public void startExample() { + // Calls C++ Init(...) method and holds a pointer to the C++ class. + mNativeCPPObject = nativeInit("myParam"); + } + + public void doStuff() { + // This will call CPPClass::Method() using nativePtr as a pointer to the object. This must + // be done to: + // * avoid leaks. + // * using finalizers are not allowed to destroy the cpp class. + nativeMethod(mNativeCPPObject); + } + + public void finishExample() { + // We're done, so let's destroy nativePtr object. + nativeDestroy(mNativeCPPObject); + } + + // --------------------------------------------------------------------------------------------- + // The following methods demonstrate exporting Java methods for invocation from C++ code. + // Java functions are mapping into C global functions by prefixing the method name with + // "Java_<Class>_" + // This is triggered by the @CalledByNative annotation; the methods may be named as you wish. + + // Exported to C++ as: + // Java_SampleForTests_javaMethod(JNIEnv* env, jobject caller, jint foo, jint bar) + // Typically the C++ code would have obtained the jobject via the Init() call described above. + @CalledByNative + public int javaMethod(int foo, int bar) { + return 0; + } + + // Exported to C++ as Java_SampleForTests_staticJavaMethod(JNIEnv* env) + // Note no jobject argument, as it is static. + @CalledByNative + public static boolean staticJavaMethod() { + return true; + } + + // No prefix, so this method is package private. It will still be exported. + @CalledByNative + void packagePrivateJavaMethod() { + } + + // Method signature with generics in params. + @CalledByNative + public void methodWithGenericParams( + Map<String, Map<String, String>> foo, LinkedList<Integer> bar) {} + + // Constructors will be exported to C++ as: + // Java_SampleForTests_Constructor(JNIEnv* env, jint foo, jint bar) + @CalledByNative + public SampleForTests(int foo, int bar) {} + + // Note the "Unchecked" suffix. By default, @CalledByNative will always generate bindings that + // call CheckException(). With "@CalledByNativeUnchecked", the client C++ code is responsible to + // call ClearException() and act as appropriate. + // See more details at the "@CalledByNativeUnchecked" annotation. + @CalledByNativeUnchecked + void methodThatThrowsException() throws Exception {} + + // The generator is not confused by inline comments: + // @CalledByNative void thisShouldNotAppearInTheOutput(); + // @CalledByNativeUnchecked public static void neitherShouldThis(int foo); + + /** + * The generator is not confused by block comments: + * @CalledByNative void thisShouldNotAppearInTheOutputEither(); + * @CalledByNativeUnchecked public static void andDefinitelyNotThis(int foo); + */ + + // String constants that look like comments don't confuse the generator: + private String mArrgh = "*/*"; + + private @interface SomeAnnotation {} + + // The generator is not confused by @Annotated parameters. + @CalledByNative + void javaMethodWithAnnotatedParam(@SomeAnnotation int foo) { + } + + // --------------------------------------------------------------------------------------------- + // Java fields which are accessed from C++ code only must be annotated with @AccessedByNative to + // prevent them being eliminated when unreferenced code is stripped. + @AccessedByNative + private int mJavaField; + + // --------------------------------------------------------------------------------------------- + // The following methods demonstrate declaring methods to call into C++ from Java. + // The generator detects the "native" and "static" keywords, the type and name of the first + // parameter, and the "native" prefix to the function name to determine the C++ function + // signatures. Besides these constraints the methods can be freely named. + + // This declares a C++ function which the application code must implement: + // static jint Init(JNIEnv* env, jobject caller); + // The jobject parameter refers back to this java side object instance. + // The implementation must return the pointer to the C++ object cast to jint. + // The caller of this method should store it, and supply it as a the nativeCPPClass param to + // subsequent native method calls (see the methods below that take an "int native..." as first + // param). + private native long nativeInit(String param); + + // This defines a function binding to the associated C++ class member function. The name is + // derived from |nativeDestroy| and |nativeCPPClass| to arrive at CPPClass::Destroy() (i.e. + // native prefixes stripped). + // + // The |nativeCPPClass| is automatically cast to type CPPClass*, in order to obtain the object + // on + // which to invoke the member function. Replace "CPPClass" with your particular class name! + private native void nativeDestroy(long nativeCPPClass); + + // This declares a C++ function which the application code must implement: + // static jdouble GetDoubleFunction(JNIEnv* env, jobject caller); + // The jobject parameter refers back to this java side object instance. + private native double nativeGetDoubleFunction(); + + // Similar to nativeGetDoubleFunction(), but here the C++ side will receive a jclass rather than + // jobject param, as the function is declared static. + private static native float nativeGetFloatFunction(); + + // This function takes a non-POD datatype. We have a list mapping them to their full classpath + // in jni_generator.py JavaParamToJni. If you require a new datatype, make sure you add to that + // function. + private native void nativeSetNonPODDatatype(Rect rect); + + // This declares a C++ function which the application code must implement: + // static ScopedJavaLocalRef<jobject> GetNonPODDatatype(JNIEnv* env, jobject caller); + // The jobject parameter refers back to this java side object instance. + // Note that it returns a ScopedJavaLocalRef<jobject> so that you don' have to worry about + // deleting the JNI local reference. This is similar with Strings and arrays. + private native Object nativeGetNonPODDatatype(); + + // Similar to nativeDestroy above, this will cast nativeCPPClass into pointer of CPPClass type + // and call its Method member function. Replace "CPPClass" with your particular class name! + private native int nativeMethod(long nativeCPPClass); + + // Similar to nativeMethod above, but here the C++ fully qualified class name is taken from the + // annotation rather than parameter name, which can thus be chosen freely. + @NativeClassQualifiedName("CPPClass::InnerClass") + private native double nativeMethodOtherP0(long nativePtr); + + // This "struct" will be created by the native side using |createInnerStructA|, + // and used by the java-side somehow. + // Note that |@CalledByNative| has to contain the inner class name. + static class InnerStructA { + private final long mLong; + private final int mInt; + private final String mString; + + private InnerStructA(long l, int i, String s) { + mLong = l; + mInt = i; + mString = s; + } + + @CalledByNative("InnerStructA") + private static InnerStructA create(long l, int i, String s) { + return new InnerStructA(l, i, s); + } + } + + private List<InnerStructA> mListInnerStructA = new ArrayList<InnerStructA>(); + + @CalledByNative + private void addStructA(InnerStructA a) { + // Called by the native side to append another element. + mListInnerStructA.add(a); + } + + @CalledByNative + private void iterateAndDoSomething() { + Iterator<InnerStructA> it = mListInnerStructA.iterator(); + while (it.hasNext()) { + InnerStructA element = it.next(); + // Now, do something with element. + } + // Done, clear the list. + mListInnerStructA.clear(); + } + + // This "struct" will be created by the java side passed to native, which + // will use its getters. + // Note that |@CalledByNative| has to contain the inner class name. + static class InnerStructB { + private final long mKey; + private final String mValue; + + private InnerStructB(long k, String v) { + mKey = k; + mValue = v; + } + + @CalledByNative("InnerStructB") + private long getKey() { + return mKey; + } + + @CalledByNative("InnerStructB") + private String getValue() { + return mValue; + } + } + + List<InnerStructB> mListInnerStructB = new ArrayList<InnerStructB>(); + + void iterateAndDoSomethingWithMap() { + Iterator<InnerStructB> it = mListInnerStructB.iterator(); + while (it.hasNext()) { + InnerStructB element = it.next(); + // Now, do something with element. + nativeAddStructB(mNativeCPPObject, element); + } + nativeIterateAndDoSomethingWithStructB(mNativeCPPObject); + } + + native void nativeAddStructB(long nativeCPPClass, InnerStructB b); + native void nativeIterateAndDoSomethingWithStructB(long nativeCPPClass); + native String nativeReturnAString(long nativeCPPClass); + + // This inner class shows how to annotate native methods on inner classes. + static class InnerClass { + @NativeCall("InnerClass") + private static native int nativeGetInnerIntFunction(); + } + + interface InnerInterface {} + enum InnerEnum {} + + @CalledByNative + static InnerInterface getInnerInterface() { + return null; + } + + @CalledByNative + static InnerEnum getInnerEnum() { + return null; + } +}
diff --git a/src/base/android/jni_generator/java/src/org/chromium/jni_generator/JniProcessor.java b/src/base/android/jni_generator/java/src/org/chromium/jni_generator/JniProcessor.java new file mode 100644 index 0000000..7300b52 --- /dev/null +++ b/src/base/android/jni_generator/java/src/org/chromium/jni_generator/JniProcessor.java
@@ -0,0 +1,397 @@ +// Copyright 2018 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. + +package org.chromium.jni_generator; + +import com.google.auto.service.AutoService; +import com.google.common.base.Charsets; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; + +import org.chromium.base.annotations.JniStaticNatives; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Generated; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Processor; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.tools.Diagnostic; + +/** + * Annotation processor that finds inner interfaces annotated with + * @JniStaticNatives and generates a class with native bindings + * (GEN_JNI) and a class specific wrapper class with name (classnameJni) + * + * NativeClass - refers to the class that contains all native declarations. + * NativeWrapperClass - refers to the class that is generated for each class + * containing an interface annotated with JniStaticNatives. + * + */ +@AutoService(Processor.class) +public class JniProcessor extends AbstractProcessor { + private static final Class<JniStaticNatives> JNI_STATIC_NATIVES_CLASS = JniStaticNatives.class; + + static final String NATIVE_WRAPPER_CLASS_POSTFIX = "Jni"; + + static final String NATIVE_CLASS_NAME_STR = "GEN_JNI"; + static final String NATIVE_CLASS_PACKAGE_NAME = "org.chromium.base.natives"; + static final ClassName NATIVE_CLASS_NAME = + ClassName.get(NATIVE_CLASS_PACKAGE_NAME, NATIVE_CLASS_NAME_STR); + + // Builder for NativeClass which will hold all our native method declarations. + private TypeSpec.Builder mNativesBuilder; + + // Hash function for native method names. + private static MessageDigest sNativeMethodHashFunction; + + // If true, native methods in GEN_JNI will be named as a hash of their descriptor. + private static final boolean USE_HASH_FOR_METHODS = true; + + // Limits the number characters of the Base64 encoded hash + // of the method descriptor used as name of the generated + // native method in GEN_JNI (prefixed with "M") + private static final int MAX_CHARS_FOR_HASHED_NATIVE_METHODS = 8; + + static String getNameOfWrapperClass(String containingClassName) { + return containingClassName + NATIVE_WRAPPER_CLASS_POSTFIX; + } + + @Override + public Set<String> getSupportedAnnotationTypes() { + return ImmutableSet.of(JNI_STATIC_NATIVES_CLASS.getCanonicalName()); + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + public JniProcessor() { + // State of mNativesBuilder needs to be preserved between processing rounds. + mNativesBuilder = TypeSpec.classBuilder(NATIVE_CLASS_NAME) + .addAnnotation(createGeneratedAnnotation()) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL); + + try { + sNativeMethodHashFunction = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + // MD5 support is required for all Java platforms so this should never happen. + printError(e.getMessage()); + } + } + + /** + * Processes annotations that match getSupportedAnnotationTypes() + * Called each 'round' of annotation processing, must fail gracefully if set is empty. + */ + @Override + public boolean process( + Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) { + // Do nothing on an empty round. + if (annotations.isEmpty()) { + return true; + } + + List<JavaFile> writeQueue = Lists.newArrayList(); + for (Element e : roundEnvironment.getElementsAnnotatedWith(JNI_STATIC_NATIVES_CLASS)) { + // @JniStaticNatives can only annotate types so this is safe. + TypeElement type = (TypeElement) e; + + if (!e.getKind().isInterface()) { + printError("@JniStaticNatives must annotate an interface", e); + } + + // Interface must be nested within a class. + Element outerElement = e.getEnclosingElement(); + if (!(outerElement instanceof TypeElement)) { + printError( + "Interface annotated with @JNIInterface must be nested within a class", e); + } + + TypeElement outerType = (TypeElement) outerElement; + ClassName outerTypeName = ClassName.get(outerType); + String outerClassName = outerTypeName.simpleName(); + String packageName = outerTypeName.packageName(); + + // Get all methods in annotated interface. + List<ExecutableElement> interfaceMethods = getMethodsFromType(type); + + // Map from the current method names to the method spec for a static native + // method that will be in our big NativeClass. + // Collisions are not allowed - no overloading. + Map<String, MethodSpec> methodMap = + createNativeMethodSpecs(interfaceMethods, outerTypeName); + + // Add these to our NativeClass. + for (MethodSpec method : methodMap.values()) { + mNativesBuilder.addMethod(method); + } + + // Generate a NativeWrapperClass for outerType by implementing the + // annotated interface. Need to pass it the method map because each + // method overridden will be a wrapper that calls its + // native counterpart in NativeClass. + boolean isNativesInterfacePublic = type.getModifiers().contains(Modifier.PUBLIC); + TypeSpec nativeWrapperClassSpec = + createNativeWrapperClassSpec(getNameOfWrapperClass(outerClassName), + isNativesInterfacePublic, type, methodMap); + + // Queue this file for writing. + // Can't write right now because the wrapper class depends on NativeClass + // to be written and we can't write NativeClass until all @JNINatives + // interfaces are processed because each will add new native methods. + JavaFile file = JavaFile.builder(packageName, nativeWrapperClassSpec).build(); + writeQueue.add(file); + } + + // Nothing needs to be written this round. + if (writeQueue.size() == 0) { + return true; + } + + try { + // Need to write NativeClass first because the wrapper classes + // depend on it. + JavaFile nativeClassFile = + JavaFile.builder(NATIVE_CLASS_PACKAGE_NAME, mNativesBuilder.build()).build(); + + nativeClassFile.writeTo(processingEnv.getFiler()); + + for (JavaFile f : writeQueue) { + f.writeTo(processingEnv.getFiler()); + } + } catch (Exception e) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage()); + } + return true; + } + + List<ExecutableElement> getMethodsFromType(TypeElement t) { + List<ExecutableElement> methods = Lists.newArrayList(); + for (Element e : t.getEnclosedElements()) { + if (e.getKind() == ElementKind.METHOD) { + methods.add((ExecutableElement) e); + } + } + return methods; + } + + /** + * Gets method name for methods inside of NativeClass + */ + String getNativeMethodName(String packageName, String className, String oldMethodName) { + // e.g. org_chromium_base_fooclass_bar() + String descriptor = + packageName.replaceAll("\\.", "_") + "_" + className + "_" + oldMethodName; + if (USE_HASH_FOR_METHODS) { + // Must start with a character. + byte[] hash = sNativeMethodHashFunction.digest(descriptor.getBytes(Charsets.UTF_8)); + + String methodName = "M" + + Base64.getEncoder() + .encodeToString(hash) + .replace("/", "_") + .replace("+", "$") + .replace("=", ""); + + return methodName.substring( + 0, Math.min(MAX_CHARS_FOR_HASHED_NATIVE_METHODS, methodName.length())); + } + return descriptor; + } + + /** + * Creates method specs for the native methods of NativeClass given + * the method declarations from a JNINative annotated interface + * @param interfaceMethods method declarations from a JNINative annotated interface + * @param outerType ClassName of class that contains the annotated interface + * @return map from old method name to new native method specification + */ + Map<String, MethodSpec> createNativeMethodSpecs( + List<ExecutableElement> interfaceMethods, ClassName outerType) { + Map<String, MethodSpec> methodMap = Maps.newTreeMap(); + for (ExecutableElement m : interfaceMethods) { + String oldMethodName = m.getSimpleName().toString(); + String newMethodName = getNativeMethodName( + outerType.packageName(), outerType.simpleName(), oldMethodName); + MethodSpec.Builder builder = MethodSpec.methodBuilder(newMethodName) + .addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.FINAL) + .addModifiers(Modifier.STATIC) + .addModifiers(Modifier.NATIVE); + + copyMethodParamsAndReturnType(builder, m, true); + if (methodMap.containsKey(oldMethodName)) { + printError("Overloading is not currently implemented with this processor ", m); + } + methodMap.put(oldMethodName, builder.build()); + } + return methodMap; + } + + /** + * Creates a generated annotation that contains the name of this class. + */ + static AnnotationSpec createGeneratedAnnotation() { + return AnnotationSpec.builder(Generated.class) + .addMember("value", String.format("\"%s\"", JniProcessor.class.getCanonicalName())) + .build(); + } + + void printError(String s) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, s); + } + + void printError(String s, Element e) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, + String.format("Error processing @JniStaticNatives interface: %s", s), e); + } + + /** + * Creates a class spec for an implementation of an @JNINatives annotated interface that will + * wrap calls to the NativesClass which contains the actual native method declarations. + * + * @param name name of the wrapper class. + * @param isPublic if true, a public modifier will be added to this native wrapper. + * @param nativeInterface the @JNINatives annotated type that this native wrapper + * will implement. + * @param methodMap a map from the old method name to the new method spec in NativeClass. + * */ + TypeSpec createNativeWrapperClassSpec(String name, boolean isPublic, + TypeElement nativeInterface, Map<String, MethodSpec> methodMap) { + TypeName nativeInterfaceType = TypeName.get(nativeInterface.asType()); + + TypeSpec.Builder builder = TypeSpec.classBuilder(name) + .addModifiers(Modifier.FINAL) + .addAnnotation(createGeneratedAnnotation()) + .addSuperinterface(nativeInterfaceType); + if (isPublic) { + builder.addModifiers(Modifier.PUBLIC); + } + + // Target is a field that holds an instance of some NativeInterface. + // Is initialized with an instance of this class. + // Target is final for now so it gets inlined. + FieldSpec target = FieldSpec.builder(nativeInterfaceType, "mNatives") + .addModifiers(Modifier.PRIVATE, Modifier.STATIC) + .addModifiers(Modifier.FINAL) + .initializer("new $N()", name) + .build(); + + builder.addField(target); + + for (Element enclosed : nativeInterface.getEnclosedElements()) { + if (enclosed.getKind() != ElementKind.METHOD) { + printError( + "Cannot have a non-method in a @JNINatives annotated interface", enclosed); + } + + // ElementKind.Method is ExecutableElement so this cast is safe. + // interfaceMethod will is the method we are overloading. + ExecutableElement interfaceMethod = (ExecutableElement) enclosed; + + // Method in NativesClass that we'll be calling. + MethodSpec nativesMethod = methodMap.get(interfaceMethod.getSimpleName().toString()); + + // Add a matching method in this class that overrides the declaration + // in nativeInterface. It will just call the actual natives method in + // NativeClass. + builder.addMethod(createNativeWrapperMethod(interfaceMethod, nativesMethod)); + } + + // Getter for target. + MethodSpec instanceGetter = MethodSpec.methodBuilder("get") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .addCode("return $N;\n", target) + .returns(nativeInterfaceType) + .build(); + builder.addMethod(instanceGetter); + + return builder.build(); + } + + /** + * Creates a wrapper method that overrides interfaceMethod and calls staticNativeMethod. + * @param interfaceMethod method that will be overridden in a @JNINatives annotated interface. + * @param staticNativeMethod method that will be called in NativeClass. + */ + MethodSpec createNativeWrapperMethod( + ExecutableElement interfaceMethod, MethodSpec staticNativeMethod) { + // Method will have the same name and be public. + MethodSpec.Builder builder = + MethodSpec.methodBuilder(interfaceMethod.getSimpleName().toString()) + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class); + + // Method will need the same params and return type as the one we're overriding. + copyMethodParamsAndReturnType(builder, interfaceMethod); + + // Add return if method return type is not void. + if (!interfaceMethod.getReturnType().toString().equals("void")) { + // Also need to cast because non-primitives are Objects in NativeClass. + builder.addCode("return ($T)", interfaceMethod.getReturnType()); + } + + // Make call to native function. + builder.addCode("$T.$N(", NATIVE_CLASS_NAME, staticNativeMethod); + + // Add params to native call. + ArrayList<String> paramNames = new ArrayList<>(); + for (VariableElement param : interfaceMethod.getParameters()) { + paramNames.add(param.getSimpleName().toString()); + } + + builder.addCode(String.join(", ", paramNames) + ");\n"); + return builder.build(); + } + + void copyMethodParamsAndReturnType(MethodSpec.Builder builder, ExecutableElement method) { + copyMethodParamsAndReturnType(builder, method, false); + } + + void copyMethodParamsAndReturnType( + MethodSpec.Builder builder, ExecutableElement method, boolean useObjects) { + for (VariableElement param : method.getParameters()) { + builder.addParameter(createParamSpec(param, useObjects)); + } + TypeName returnType = TypeName.get(method.getReturnType()); + if (useObjects && !returnType.isPrimitive()) { + returnType = TypeName.OBJECT; + } + builder.returns(returnType); + } + + ParameterSpec createParamSpec(VariableElement param, boolean useObject) { + TypeName paramType = TypeName.get(param.asType()); + if (useObject && !paramType.isPrimitive()) { + paramType = TypeName.OBJECT; + } + return ParameterSpec.builder(paramType, param.getSimpleName().toString()) + .addModifiers(param.getModifiers()) + .build(); + } +}
diff --git a/src/base/android/jni_generator/jni_exception_list.gni b/src/base/android/jni_generator/jni_exception_list.gni new file mode 100644 index 0000000..11c7f6a --- /dev/null +++ b/src/base/android/jni_generator/jni_exception_list.gni
@@ -0,0 +1,13 @@ +# Copyright 2017 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. + +import("//device/vr/buildflags/buildflags.gni") + +jni_exception_files = + [ "//base/android/java/src/org/chromium/base/library_loader/Linker.java" ] + +# Exclude it from JNI registration if VR is not enabled. +if (!enable_vr) { + jni_exception_files += [ "//chrome/android/java/src/org/chromium/chrome/browser/vr/VrModuleProvider.java" ] +}
diff --git a/src/base/android/jni_generator/jni_generator.py b/src/base/android/jni_generator/jni_generator.py new file mode 100755 index 0000000..ee6a52b --- /dev/null +++ b/src/base/android/jni_generator/jni_generator.py
@@ -0,0 +1,1405 @@ +#!/usr/bin/env python +# Copyright (c) 2012 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. + +"""Extracts native methods from a Java file and generates the JNI bindings. +If you change this, please run and update the tests.""" + +import collections +import errno +import optparse +import os +import re +from string import Template +import subprocess +import sys +import textwrap +import zipfile + +CHROMIUM_SRC = os.path.join( + os.path.dirname(__file__), os.pardir, os.pardir, os.pardir) +BUILD_ANDROID_GYP = os.path.join( + CHROMIUM_SRC, 'build', 'android', 'gyp') + +sys.path.append(BUILD_ANDROID_GYP) + +from util import build_utils + + +# Match single line comments, multiline comments, character literals, and +# double-quoted strings. +_COMMENT_REMOVER_REGEX = re.compile( + r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', + re.DOTALL | re.MULTILINE) + +_EXTRACT_NATIVES_REGEX = re.compile( + r'(@NativeClassQualifiedName' + r'\(\"(?P<native_class_name>.*?)\"\)\s+)?' + r'(@NativeCall(\(\"(?P<java_class_name>.*?)\"\))\s+)?' + r'(?P<qualifiers>\w+\s\w+|\w+|\s+)\s*native ' + r'(?P<return_type>\S*) ' + r'(?P<name>native\w+)\((?P<params>.*?)\);') + +_MAIN_DEX_REGEX = re.compile( + r'^\s*(?:@(?:\w+\.)*\w+\s+)*@MainDex\b', + re.MULTILINE) + +# Use 100 columns rather than 80 because it makes many lines more readable. +_WRAP_LINE_LENGTH = 100 +# WrapOutput() is fairly slow. Pre-creating TextWrappers helps a bit. +_WRAPPERS_BY_INDENT = [ + textwrap.TextWrapper(width=_WRAP_LINE_LENGTH, expand_tabs=False, + replace_whitespace=False, + subsequent_indent=' ' * (indent + 4), + break_long_words=False) + for indent in xrange(50)] # 50 chosen experimentally. + + +class ParseError(Exception): + """Exception thrown when we can't parse the input file.""" + + def __init__(self, description, *context_lines): + Exception.__init__(self) + self.description = description + self.context_lines = context_lines + + def __str__(self): + context = '\n'.join(self.context_lines) + return '***\nERROR: %s\n\n%s\n***' % (self.description, context) + + +class Param(object): + """Describes a param for a method, either java or native.""" + + def __init__(self, **kwargs): + self.datatype = kwargs['datatype'] + self.name = kwargs['name'] + + +class NativeMethod(object): + """Describes a C/C++ method that is called by Java code""" + + def __init__(self, **kwargs): + self.static = kwargs['static'] + self.java_class_name = kwargs['java_class_name'] + self.return_type = kwargs['return_type'] + self.name = kwargs['name'] + self.params = kwargs['params'] + if self.params: + assert type(self.params) is list + assert type(self.params[0]) is Param + if (self.params and + self.params[0].datatype == kwargs.get('ptr_type', 'int') and + self.params[0].name.startswith('native')): + self.type = 'method' + self.p0_type = self.params[0].name[len('native'):] + if kwargs.get('native_class_name'): + self.p0_type = kwargs['native_class_name'] + else: + self.type = 'function' + self.method_id_var_name = kwargs.get('method_id_var_name', None) + + +class CalledByNative(object): + """Describes a java method exported to c/c++""" + + def __init__(self, **kwargs): + self.system_class = kwargs['system_class'] + self.unchecked = kwargs['unchecked'] + self.static = kwargs['static'] + self.java_class_name = kwargs['java_class_name'] + self.return_type = kwargs['return_type'] + self.name = kwargs['name'] + self.params = kwargs['params'] + self.method_id_var_name = kwargs.get('method_id_var_name', None) + self.signature = kwargs.get('signature') + self.is_constructor = kwargs.get('is_constructor', False) + self.env_call = GetEnvCall(self.is_constructor, self.static, + self.return_type) + self.static_cast = GetStaticCastForReturnType(self.return_type) + + +class ConstantField(object): + def __init__(self, **kwargs): + self.name = kwargs['name'] + self.value = kwargs['value'] + + +def JavaDataTypeToC(java_type): + """Returns a C datatype for the given java type.""" + java_pod_type_map = { + 'int': 'jint', + 'byte': 'jbyte', + 'char': 'jchar', + 'short': 'jshort', + 'boolean': 'jboolean', + 'long': 'jlong', + 'double': 'jdouble', + 'float': 'jfloat', + } + java_type_map = { + 'void': 'void', + 'String': 'jstring', + 'Class': 'jclass', + 'Throwable': 'jthrowable', + 'java/lang/String': 'jstring', + 'java/lang/Class': 'jclass', + 'java/lang/Throwable': 'jthrowable', + } + + java_type = _StripGenerics(java_type) + if java_type in java_pod_type_map: + return java_pod_type_map[java_type] + elif java_type in java_type_map: + return java_type_map[java_type] + elif java_type.endswith('[]'): + if java_type[:-2] in java_pod_type_map: + return java_pod_type_map[java_type[:-2]] + 'Array' + return 'jobjectArray' + else: + return 'jobject' + + +def WrapCTypeForDeclaration(c_type): + """Wrap the C datatype in a JavaRef if required.""" + if re.match(RE_SCOPED_JNI_TYPES, c_type): + return 'const base::android::JavaParamRef<' + c_type + '>&' + else: + return c_type + + +def _JavaDataTypeToCForDeclaration(java_type): + """Returns a JavaRef-wrapped C datatype for the given java type.""" + return WrapCTypeForDeclaration(JavaDataTypeToC(java_type)) + + +def JavaDataTypeToCForCalledByNativeParam(java_type): + """Returns a C datatype to be when calling from native.""" + if java_type == 'int': + return 'JniIntWrapper' + else: + c_type = JavaDataTypeToC(java_type) + if re.match(RE_SCOPED_JNI_TYPES, c_type): + return 'const base::android::JavaRef<' + c_type + '>&' + else: + return c_type + + +def JavaReturnValueToC(java_type): + """Returns a valid C return value for the given java type.""" + java_pod_type_map = { + 'int': '0', + 'byte': '0', + 'char': '0', + 'short': '0', + 'boolean': 'false', + 'long': '0', + 'double': '0', + 'float': '0', + 'void': '' + } + return java_pod_type_map.get(java_type, 'NULL') + + +def _GetJNIFirstParamType(native): + if native.type == 'function' and native.static: + return 'jclass' + return 'jobject' + + +def _GetJNIFirstParam(native, for_declaration): + c_type = _GetJNIFirstParamType(native) + if for_declaration: + c_type = WrapCTypeForDeclaration(c_type) + return [c_type + ' jcaller'] + + +def _GetParamsInDeclaration(native): + """Returns the params for the forward declaration. + + Args: + native: the native dictionary describing the method. + + Returns: + A string containing the params. + """ + return ',\n '.join(_GetJNIFirstParam(native, True) + + [_JavaDataTypeToCForDeclaration(param.datatype) + ' ' + + param.name + for param in native.params]) + + +def GetParamsInStub(native): + """Returns the params for the stub declaration. + + Args: + native: the native dictionary describing the method. + + Returns: + A string containing the params. + """ + params = [JavaDataTypeToC(p.datatype) + ' ' + p.name for p in native.params] + return ',\n '.join(_GetJNIFirstParam(native, False) + params) + + +def _StripGenerics(value): + """Strips Java generics from a string.""" + nest_level = 0 # How deeply we are nested inside the generics. + start_index = 0 # Starting index of the last non-generic region. + out = [] + + for i, c in enumerate(value): + if c == '<': + if nest_level == 0: + out.append(value[start_index:i]) + nest_level += 1 + elif c == '>': + start_index = i + 1 + nest_level -= 1 + out.append(value[start_index:]) + + return ''.join(out) + + +class JniParams(object): + """Get JNI related parameters.""" + + def __init__(self, fully_qualified_class): + self._fully_qualified_class = 'L' + fully_qualified_class + self._package = '/'.join(fully_qualified_class.split('/')[:-1]) + self._imports = [] + self._inner_classes = [] + self._implicit_imports = [] + + def ExtractImportsAndInnerClasses(self, contents): + contents = contents.replace('\n', '') + re_import = re.compile(r'import.*?(?P<class>\S*?);') + for match in re.finditer(re_import, contents): + self._imports += ['L' + match.group('class').replace('.', '/')] + + re_inner = re.compile(r'(class|interface|enum)\s+?(?P<name>\w+?)\W') + for match in re.finditer(re_inner, contents): + inner = match.group('name') + if not self._fully_qualified_class.endswith(inner): + self._inner_classes += [self._fully_qualified_class + '$' + + inner] + + re_additional_imports = re.compile( + r'@JNIAdditionalImport\(\s*{?(?P<class_names>.*?)}?\s*\)') + for match in re.finditer(re_additional_imports, contents): + for class_name in match.group('class_names').split(','): + self._AddAdditionalImport(class_name.strip()) + + def JavaToJni(self, param): + """Converts a java param into a JNI signature type.""" + pod_param_map = { + 'int': 'I', + 'boolean': 'Z', + 'char': 'C', + 'short': 'S', + 'long': 'J', + 'double': 'D', + 'float': 'F', + 'byte': 'B', + 'void': 'V', + } + object_param_list = [ + 'Ljava/lang/Boolean', + 'Ljava/lang/Integer', + 'Ljava/lang/Long', + 'Ljava/lang/Object', + 'Ljava/lang/String', + 'Ljava/lang/Class', + 'Ljava/lang/CharSequence', + 'Ljava/lang/Runnable', + 'Ljava/lang/Throwable', + ] + + prefix = '' + # Array? + while param[-2:] == '[]': + prefix += '[' + param = param[:-2] + # Generic? + if '<' in param: + param = param[:param.index('<')] + if param in pod_param_map: + return prefix + pod_param_map[param] + if '/' in param: + # Coming from javap, use the fully qualified param directly. + return prefix + 'L' + param + ';' + + for qualified_name in (object_param_list + + [self._fully_qualified_class] + self._inner_classes): + if (qualified_name.endswith('/' + param) or + qualified_name.endswith('$' + param.replace('.', '$')) or + qualified_name == 'L' + param): + return prefix + qualified_name + ';' + + # Is it from an import? (e.g. referecing Class from import pkg.Class; + # note that referencing an inner class Inner from import pkg.Class.Inner + # is not supported). + for qualified_name in self._imports: + if qualified_name.endswith('/' + param): + # Ensure it's not an inner class. + components = qualified_name.split('/') + if len(components) > 2 and components[-2][0].isupper(): + raise SyntaxError('Inner class (%s) can not be imported ' + 'and used by JNI (%s). Please import the outer ' + 'class and use Outer.Inner instead.' % + (qualified_name, param)) + return prefix + qualified_name + ';' + + # Is it an inner class from an outer class import? (e.g. referencing + # Class.Inner from import pkg.Class). + if '.' in param: + components = param.split('.') + outer = '/'.join(components[:-1]) + inner = components[-1] + for qualified_name in self._imports: + if qualified_name.endswith('/' + outer): + return (prefix + qualified_name + '$' + inner + ';') + raise SyntaxError('Inner class (%s) can not be ' + 'used directly by JNI. Please import the outer ' + 'class, probably:\n' + 'import %s.%s;' % + (param, self._package.replace('/', '.'), + outer.replace('/', '.'))) + + self._CheckImplicitImports(param) + + # Type not found, falling back to same package as this class. + return (prefix + 'L' + self._package + '/' + param + ';') + + def _AddAdditionalImport(self, class_name): + assert class_name.endswith('.class') + raw_class_name = class_name[:-len('.class')] + if '.' in raw_class_name: + raise SyntaxError('%s cannot be used in @JNIAdditionalImport. ' + 'Only import unqualified outer classes.' % class_name) + new_import = 'L%s/%s' % (self._package, raw_class_name) + if new_import in self._imports: + raise SyntaxError('Do not use JNIAdditionalImport on an already ' + 'imported class: %s' % (new_import.replace('/', '.'))) + self._imports += [new_import] + + def _CheckImplicitImports(self, param): + # Ensure implicit imports, such as java.lang.*, are not being treated + # as being in the same package. + if not self._implicit_imports: + # This file was generated from android.jar and lists + # all classes that are implicitly imported. + with file(os.path.join(os.path.dirname(sys.argv[0]), + 'android_jar.classes'), 'r') as f: + self._implicit_imports = f.readlines() + for implicit_import in self._implicit_imports: + implicit_import = implicit_import.strip().replace('.class', '') + implicit_import = implicit_import.replace('/', '.') + if implicit_import.endswith('.' + param): + raise SyntaxError('Ambiguous class (%s) can not be used directly ' + 'by JNI.\nPlease import it, probably:\n\n' + 'import %s;' % + (param, implicit_import)) + + def Signature(self, params, returns): + """Returns the JNI signature for the given datatypes.""" + items = ['('] + items += [self.JavaToJni(param.datatype) for param in params] + items += [')'] + items += [self.JavaToJni(returns)] + return '"{}"'.format(''.join(items)) + + @staticmethod + def ParseJavaPSignature(signature_line): + prefix = 'Signature: ' + index = signature_line.find(prefix) + if index == -1: + prefix = 'descriptor: ' + index = signature_line.index(prefix) + return '"%s"' % signature_line[index + len(prefix):] + + @staticmethod + def Parse(params): + """Parses the params into a list of Param objects.""" + if not params: + return [] + ret = [] + params = _StripGenerics(params) + for p in params.split(','): + items = p.split() + + # Remove @Annotations from parameters. + while items[0].startswith('@'): + del items[0] + + if 'final' in items: + items.remove('final') + + param = Param( + datatype=items[0], + name=(items[1] if len(items) > 1 else 'p%s' % len(ret)), + ) + ret += [param] + return ret + + +def ExtractJNINamespace(contents): + re_jni_namespace = re.compile('.*?@JNINamespace\("(.*?)"\)') + m = re.findall(re_jni_namespace, contents) + if not m: + return '' + return m[0] + + +def ExtractFullyQualifiedJavaClassName(java_file_name, contents): + re_package = re.compile('.*?package (.*?);') + matches = re.findall(re_package, contents) + if not matches: + raise SyntaxError('Unable to find "package" line in %s' % java_file_name) + return (matches[0].replace('.', '/') + '/' + + os.path.splitext(os.path.basename(java_file_name))[0]) + + +def ExtractNatives(contents, ptr_type): + """Returns a list of dict containing information about a native method.""" + contents = contents.replace('\n', '') + natives = [] + for match in _EXTRACT_NATIVES_REGEX.finditer(contents): + native = NativeMethod( + static='static' in match.group('qualifiers'), + java_class_name=match.group('java_class_name'), + native_class_name=match.group('native_class_name'), + return_type=match.group('return_type'), + name=match.group('name').replace('native', ''), + params=JniParams.Parse(match.group('params')), + ptr_type=ptr_type) + natives += [native] + return natives + + +def IsMainDexJavaClass(contents): + """Returns True if the class or any of its methods are annotated as @MainDex. + + JNI registration doesn't always need to be completed for non-browser processes + since most Java code is only used by the browser process. Classes that are + needed by non-browser processes must explicitly be annotated with @MainDex + to force JNI registration. + """ + return bool(_MAIN_DEX_REGEX.search(contents)) + + +def GetBinaryClassName(fully_qualified_class): + """Returns a string concatenating the Java package and class.""" + escaped = fully_qualified_class.replace('_', '_1') + return escaped.replace('/', '_').replace('$', '_00024') + + +def GetRegistrationFunctionName(fully_qualified_class): + """Returns the register name with a given class.""" + return 'RegisterNative_' + GetBinaryClassName(fully_qualified_class) + + +def GetStaticCastForReturnType(return_type): + type_map = { 'String' : 'jstring', + 'java/lang/String' : 'jstring', + 'Class': 'jclass', + 'java/lang/Class': 'jclass', + 'Throwable': 'jthrowable', + 'java/lang/Throwable': 'jthrowable', + 'boolean[]': 'jbooleanArray', + 'byte[]': 'jbyteArray', + 'char[]': 'jcharArray', + 'short[]': 'jshortArray', + 'int[]': 'jintArray', + 'long[]': 'jlongArray', + 'float[]': 'jfloatArray', + 'double[]': 'jdoubleArray' } + return_type = _StripGenerics(return_type) + ret = type_map.get(return_type, None) + if ret: + return ret + if return_type.endswith('[]'): + return 'jobjectArray' + return None + + +def GetEnvCall(is_constructor, is_static, return_type): + """Maps the types availabe via env->Call__Method.""" + if is_constructor: + return 'NewObject' + env_call_map = {'boolean': 'Boolean', + 'byte': 'Byte', + 'char': 'Char', + 'short': 'Short', + 'int': 'Int', + 'long': 'Long', + 'float': 'Float', + 'void': 'Void', + 'double': 'Double', + 'Object': 'Object', + } + call = env_call_map.get(return_type, 'Object') + if is_static: + call = 'Static' + call + return 'Call' + call + 'Method' + + +def GetMangledParam(datatype): + """Returns a mangled identifier for the datatype.""" + if len(datatype) <= 2: + return datatype.replace('[', 'A') + ret = '' + for i in range(1, len(datatype)): + c = datatype[i] + if c == '[': + ret += 'A' + elif c.isupper() or datatype[i - 1] in ['/', 'L']: + ret += c.upper() + return ret + + +def GetMangledMethodName(jni_params, name, params, return_type): + """Returns a mangled method name for the given signature. + + The returned name can be used as a C identifier and will be unique for all + valid overloads of the same method. + + Args: + jni_params: JniParams object. + name: string. + params: list of Param. + return_type: string. + + Returns: + A mangled name. + """ + mangled_items = [] + for datatype in [return_type] + [x.datatype for x in params]: + mangled_items += [GetMangledParam(jni_params.JavaToJni(datatype))] + mangled_name = name + '_'.join(mangled_items) + assert re.match(r'[0-9a-zA-Z_]+', mangled_name) + return mangled_name + + +def MangleCalledByNatives(jni_params, called_by_natives): + """Mangles all the overloads from the call_by_natives list.""" + method_counts = collections.defaultdict( + lambda: collections.defaultdict(lambda: 0)) + for called_by_native in called_by_natives: + java_class_name = called_by_native.java_class_name + name = called_by_native.name + method_counts[java_class_name][name] += 1 + for called_by_native in called_by_natives: + java_class_name = called_by_native.java_class_name + method_name = called_by_native.name + method_id_var_name = method_name + if method_counts[java_class_name][method_name] > 1: + method_id_var_name = GetMangledMethodName(jni_params, method_name, + called_by_native.params, + called_by_native.return_type) + called_by_native.method_id_var_name = method_id_var_name + return called_by_natives + + +# Regex to match the JNI types that should be wrapped in a JavaRef. +RE_SCOPED_JNI_TYPES = re.compile('jobject|jclass|jstring|jthrowable|.*Array') + + +# Regex to match a string like "@CalledByNative public void foo(int bar)". +RE_CALLED_BY_NATIVE = re.compile( + r'@CalledByNative(?P<Unchecked>(?:Unchecked)?)(?:\("(?P<annotation>.*)"\))?' + r'(?:\s+@\w+(?:\(.*\))?)*' # Ignore any other annotations. + r'\s+(?P<prefix>(' + r'(private|protected|public|static|abstract|final|default|synchronized)' + r'\s*)*)' + r'(?:\s*@\w+)?' # Ignore annotations in return types. + r'\s*(?P<return_type>\S*?)' + r'\s*(?P<name>\w+)' + r'\s*\((?P<params>[^\)]*)\)') + +# Removes empty lines that are indented (i.e. start with 2x spaces). +def RemoveIndentedEmptyLines(string): + return re.sub('^(?: {2})+$\n', '', string, flags=re.MULTILINE) + + +def ExtractCalledByNatives(jni_params, contents): + """Parses all methods annotated with @CalledByNative. + + Args: + jni_params: JniParams object. + contents: the contents of the java file. + + Returns: + A list of dict with information about the annotated methods. + TODO(bulach): return a CalledByNative object. + + Raises: + ParseError: if unable to parse. + """ + called_by_natives = [] + for match in re.finditer(RE_CALLED_BY_NATIVE, contents): + return_type = match.group('return_type') + name = match.group('name') + if not return_type: + is_constructor = True + return_type = name + name = "Constructor" + else: + is_constructor = False + + called_by_natives += [CalledByNative( + system_class=False, + unchecked='Unchecked' in match.group('Unchecked'), + static='static' in match.group('prefix'), + java_class_name=match.group('annotation') or '', + return_type=return_type, + name=name, + is_constructor=is_constructor, + params=JniParams.Parse(match.group('params')))] + # Check for any @CalledByNative occurrences that weren't matched. + unmatched_lines = re.sub(RE_CALLED_BY_NATIVE, '', contents).split('\n') + for line1, line2 in zip(unmatched_lines, unmatched_lines[1:]): + if '@CalledByNative' in line1: + raise ParseError('could not parse @CalledByNative method signature', + line1, line2) + return MangleCalledByNatives(jni_params, called_by_natives) + + +def RemoveComments(contents): + # We need to support both inline and block comments, and we need to handle + # strings that contain '//' or '/*'. + # TODO(bulach): This is a bit hacky. It would be cleaner to use a real Java + # parser. Maybe we could ditch JNIFromJavaSource and just always use + # JNIFromJavaP; or maybe we could rewrite this script in Java and use APT. + # http://code.google.com/p/chromium/issues/detail?id=138941 + def replacer(match): + # Replace matches that are comments with nothing; return literals/strings + # unchanged. + s = match.group(0) + if s.startswith('/'): + return '' + else: + return s + return _COMMENT_REMOVER_REGEX.sub(replacer, contents) + + +class JNIFromJavaP(object): + """Uses 'javap' to parse a .class file and generate the JNI header file.""" + + def __init__(self, contents, options): + self.contents = contents + self.namespace = options.namespace + for line in contents: + class_name = re.match( + '.*?(public).*?(class|interface) (?P<class_name>\S+?)( |\Z)', + line) + if class_name: + self.fully_qualified_class = class_name.group('class_name') + break + self.fully_qualified_class = self.fully_qualified_class.replace('.', '/') + # Java 7's javap includes type parameters in output, like HashSet<T>. Strip + # away the <...> and use the raw class name that Java 6 would've given us. + self.fully_qualified_class = self.fully_qualified_class.split('<', 1)[0] + self.jni_params = JniParams(self.fully_qualified_class) + self.java_class_name = self.fully_qualified_class.split('/')[-1] + if not self.namespace: + self.namespace = 'JNI_' + self.java_class_name + re_method = re.compile('(?P<prefix>.*?)(?P<return_type>\S+?) (?P<name>\w+?)' + '\((?P<params>.*?)\)') + self.called_by_natives = [] + for lineno, content in enumerate(contents[2:], 2): + match = re.match(re_method, content) + if not match: + continue + self.called_by_natives += [CalledByNative( + system_class=True, + unchecked=False, + static='static' in match.group('prefix'), + java_class_name='', + return_type=match.group('return_type').replace('.', '/'), + name=match.group('name'), + params=JniParams.Parse(match.group('params').replace('.', '/')), + signature=JniParams.ParseJavaPSignature(contents[lineno + 1]))] + re_constructor = re.compile('(.*?)public ' + + self.fully_qualified_class.replace('/', '.') + + '\((?P<params>.*?)\)') + for lineno, content in enumerate(contents[2:], 2): + match = re.match(re_constructor, content) + if not match: + continue + self.called_by_natives += [CalledByNative( + system_class=True, + unchecked=False, + static=False, + java_class_name='', + return_type=self.fully_qualified_class, + name='Constructor', + params=JniParams.Parse(match.group('params').replace('.', '/')), + signature=JniParams.ParseJavaPSignature(contents[lineno + 1]), + is_constructor=True)] + self.called_by_natives = MangleCalledByNatives(self.jni_params, + self.called_by_natives) + self.constant_fields = [] + re_constant_field = re.compile('.*?public static final int (?P<name>.*?);') + re_constant_field_value = re.compile( + '.*?Constant(Value| value): int (?P<value>(-*[0-9]+)?)') + for lineno, content in enumerate(contents[2:], 2): + match = re.match(re_constant_field, content) + if not match: + continue + value = re.match(re_constant_field_value, contents[lineno + 2]) + if not value: + value = re.match(re_constant_field_value, contents[lineno + 3]) + if value: + self.constant_fields.append( + ConstantField(name=match.group('name'), + value=value.group('value'))) + + self.inl_header_file_generator = InlHeaderFileGenerator( + self.namespace, self.fully_qualified_class, [], self.called_by_natives, + self.constant_fields, self.jni_params, options) + + def GetContent(self): + return self.inl_header_file_generator.GetContent() + + @staticmethod + def CreateFromClass(class_file, options): + class_name = os.path.splitext(os.path.basename(class_file))[0] + p = subprocess.Popen(args=[options.javap, '-c', '-verbose', + '-s', class_name], + cwd=os.path.dirname(class_file), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, _ = p.communicate() + jni_from_javap = JNIFromJavaP(stdout.split('\n'), options) + return jni_from_javap + + +class JNIFromJavaSource(object): + """Uses the given java source file to generate the JNI header file.""" + + def __init__(self, contents, fully_qualified_class, options): + contents = RemoveComments(contents) + self.jni_params = JniParams(fully_qualified_class) + self.jni_params.ExtractImportsAndInnerClasses(contents) + jni_namespace = ExtractJNINamespace(contents) or options.namespace + natives = ExtractNatives(contents, options.ptr_type) + called_by_natives = ExtractCalledByNatives(self.jni_params, contents) + if len(natives) == 0 and len(called_by_natives) == 0: + raise SyntaxError('Unable to find any JNI methods for %s.' % + fully_qualified_class) + inl_header_file_generator = InlHeaderFileGenerator( + jni_namespace, fully_qualified_class, natives, called_by_natives, [], + self.jni_params, options) + self.content = inl_header_file_generator.GetContent() + + def GetContent(self): + return self.content + + @staticmethod + def CreateFromFile(java_file_name, options): + contents = file(java_file_name).read() + fully_qualified_class = ExtractFullyQualifiedJavaClassName(java_file_name, + contents) + return JNIFromJavaSource(contents, fully_qualified_class, options) + + +class HeaderFileGeneratorHelper(object): + """Include helper methods for header generators.""" + + def __init__(self, class_name, fully_qualified_class): + self.class_name = class_name + self.fully_qualified_class = fully_qualified_class + + def GetStubName(self, native): + """Return the name of the stub function for this native method. + + Args: + native: the native dictionary describing the method. + + Returns: + A string with the stub function name (used by the JVM). + """ + template = Template("Java_${JAVA_NAME}_native${NAME}") + + java_name = self.fully_qualified_class + if native.java_class_name: + java_name += '$' + native.java_class_name + + values = {'NAME': native.name, + 'JAVA_NAME': GetBinaryClassName(java_name)} + return template.substitute(values) + + def GetUniqueClasses(self, origin): + ret = {self.class_name: self.fully_qualified_class} + for entry in origin: + class_name = self.class_name + jni_class_path = self.fully_qualified_class + if entry.java_class_name: + class_name = entry.java_class_name + jni_class_path = self.fully_qualified_class + '$' + class_name + ret[class_name] = jni_class_path + return ret + + def GetClassPathLines(self, classes, declare_only=False): + """Returns the ClassPath constants.""" + ret = [] + if declare_only: + template = Template(""" +extern const char kClassPath_${JAVA_CLASS}[]; +""") + else: + template = Template(""" +JNI_REGISTRATION_EXPORT extern const char kClassPath_${JAVA_CLASS}[]; +const char kClassPath_${JAVA_CLASS}[] = \ +"${JNI_CLASS_PATH}"; +""") + + for full_clazz in classes.itervalues(): + values = { + 'JAVA_CLASS': GetBinaryClassName(full_clazz), + 'JNI_CLASS_PATH': full_clazz, + } + ret += [template.substitute(values)] + + class_getter = """\ +#ifndef ${JAVA_CLASS}_clazz_defined +#define ${JAVA_CLASS}_clazz_defined +inline jclass ${JAVA_CLASS}_clazz(JNIEnv* env) { + return base::android::LazyGetClass(env, kClassPath_${JAVA_CLASS}, \ +&g_${JAVA_CLASS}_clazz); +} +#endif +""" + if declare_only: + template = Template("""\ +extern std::atomic<jclass> g_${JAVA_CLASS}_clazz; +""" + class_getter) + else: + template = Template("""\ +// Leaking this jclass as we cannot use LazyInstance from some threads. +JNI_REGISTRATION_EXPORT std::atomic<jclass> g_${JAVA_CLASS}_clazz(nullptr); +""" + class_getter) + + for full_clazz in classes.itervalues(): + values = { + 'JAVA_CLASS': GetBinaryClassName(full_clazz), + } + ret += [template.substitute(values)] + + return ''.join(ret) + + +class InlHeaderFileGenerator(object): + """Generates an inline header file for JNI integration.""" + + def __init__(self, namespace, fully_qualified_class, natives, + called_by_natives, constant_fields, jni_params, options): + self.namespace = namespace + self.fully_qualified_class = fully_qualified_class + self.class_name = self.fully_qualified_class.split('/')[-1] + self.natives = natives + self.called_by_natives = called_by_natives + self.header_guard = fully_qualified_class.replace('/', '_') + '_JNI' + self.constant_fields = constant_fields + self.jni_params = jni_params + self.options = options + self.helper = HeaderFileGeneratorHelper( + self.class_name, fully_qualified_class) + + + def GetContent(self): + """Returns the content of the JNI binding file.""" + template = Template("""\ +// Copyright 2014 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. + + +// This file is autogenerated by +// ${SCRIPT_NAME} +// For +// ${FULLY_QUALIFIED_CLASS} + +#ifndef ${HEADER_GUARD} +#define ${HEADER_GUARD} + +#include <jni.h> + +${INCLUDES} + +// Step 1: Forward declarations. +$CLASS_PATH_DEFINITIONS + +// Step 2: Constants (optional). + +$CONSTANT_FIELDS\ + +// Step 3: Method stubs. +$METHOD_STUBS + +#endif // ${HEADER_GUARD} +""") + values = { + 'SCRIPT_NAME': self.options.script_name, + 'FULLY_QUALIFIED_CLASS': self.fully_qualified_class, + 'CLASS_PATH_DEFINITIONS': self.GetClassPathDefinitionsString(), + 'CONSTANT_FIELDS': self.GetConstantFieldsString(), + 'METHOD_STUBS': self.GetMethodStubsString(), + 'HEADER_GUARD': self.header_guard, + 'INCLUDES': self.GetIncludesString(), + } + open_namespace = self.GetOpenNamespaceString() + if open_namespace: + close_namespace = self.GetCloseNamespaceString() + values['METHOD_STUBS'] = '\n'.join([ + open_namespace, values['METHOD_STUBS'], close_namespace]) + + constant_fields = values['CONSTANT_FIELDS'] + if constant_fields: + values['CONSTANT_FIELDS'] = '\n'.join([ + open_namespace, constant_fields, close_namespace]) + + return WrapOutput(template.substitute(values)) + + def GetClassPathDefinitionsString(self): + classes = self.helper.GetUniqueClasses(self.called_by_natives) + classes.update(self.helper.GetUniqueClasses(self.natives)) + return self.helper.GetClassPathLines(classes) + + def GetConstantFieldsString(self): + if not self.constant_fields: + return '' + ret = ['enum Java_%s_constant_fields {' % self.class_name] + for c in self.constant_fields: + ret += [' %s = %s,' % (c.name, c.value)] + ret += ['};', ''] + return '\n'.join(ret) + + def GetMethodStubsString(self): + """Returns the code corresponding to method stubs.""" + ret = [] + for native in self.natives: + ret += [self.GetNativeStub(native)] + ret += self.GetLazyCalledByNativeMethodStubs() + return '\n'.join(ret) + + def GetLazyCalledByNativeMethodStubs(self): + return [self.GetLazyCalledByNativeMethodStub(called_by_native) + for called_by_native in self.called_by_natives] + + def GetIncludesString(self): + if not self.options.includes: + return '' + includes = self.options.includes.split(',') + return '\n'.join('#include "%s"' % x for x in includes) + '\n' + + def GetOpenNamespaceString(self): + if self.namespace: + all_namespaces = ['namespace %s {' % ns + for ns in self.namespace.split('::')] + return '\n'.join(all_namespaces) + '\n' + return '' + + def GetCloseNamespaceString(self): + if self.namespace: + all_namespaces = ['} // namespace %s' % ns + for ns in self.namespace.split('::')] + all_namespaces.reverse() + return '\n' + '\n'.join(all_namespaces) + return '' + + def GetCalledByNativeParamsInDeclaration(self, called_by_native): + return ',\n '.join([ + JavaDataTypeToCForCalledByNativeParam(param.datatype) + ' ' + + param.name + for param in called_by_native.params]) + + def GetJavaParamRefForCall(self, c_type, name): + return Template( + 'base::android::JavaParamRef<${TYPE}>(env, ${NAME})').substitute({ + 'TYPE': c_type, + 'NAME': name, + }) + + def GetJNIFirstParamForCall(self, native): + c_type = _GetJNIFirstParamType(native) + return [self.GetJavaParamRefForCall(c_type, 'jcaller')] + + def GetImplementationMethodName(self, native): + class_name = self.class_name + if native.java_class_name is not None: + # Inner class + class_name = native.java_class_name + return "JNI_%s_%s" % (class_name, native.name) + + def GetNativeStub(self, native): + is_method = native.type == 'method' + + if is_method: + params = native.params[1:] + else: + params = native.params + params_in_call = ['env'] + self.GetJNIFirstParamForCall(native) + for p in params: + c_type = JavaDataTypeToC(p.datatype) + if re.match(RE_SCOPED_JNI_TYPES, c_type): + params_in_call.append(self.GetJavaParamRefForCall(c_type, p.name)) + else: + params_in_call.append(p.name) + params_in_call = ', '.join(params_in_call) + + return_type = return_declaration = JavaDataTypeToC(native.return_type) + post_call = '' + if re.match(RE_SCOPED_JNI_TYPES, return_type): + post_call = '.Release()' + return_declaration = ('base::android::ScopedJavaLocalRef<' + return_type + + '>') + profiling_entered_native = '' + if self.options.enable_profiling: + profiling_entered_native = ' JNI_LINK_SAVED_FRAME_POINTER;\n' + values = { + 'RETURN': return_type, + 'RETURN_DECLARATION': return_declaration, + 'NAME': native.name, + 'IMPL_METHOD_NAME': self.GetImplementationMethodName(native), + 'PARAMS': _GetParamsInDeclaration(native), + 'PARAMS_IN_STUB': GetParamsInStub(native), + 'PARAMS_IN_CALL': params_in_call, + 'POST_CALL': post_call, + 'STUB_NAME': self.helper.GetStubName(native), + 'PROFILING_ENTERED_NATIVE': profiling_entered_native, + 'TRACE_EVENT': '', + } + + namespace_qual = self.namespace + '::' if self.namespace else '' + if is_method: + optional_error_return = JavaReturnValueToC(native.return_type) + if optional_error_return: + optional_error_return = ', ' + optional_error_return + values.update({ + 'OPTIONAL_ERROR_RETURN': optional_error_return, + 'PARAM0_NAME': native.params[0].name, + 'P0_TYPE': native.p0_type, + }) + if self.options.enable_tracing: + values['TRACE_EVENT'] = self.GetTraceEventForNameTemplate( + namespace_qual + '${P0_TYPE}::${NAME}', values); + template = Template("""\ +JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}( + JNIEnv* env, + ${PARAMS_IN_STUB}) { +${PROFILING_ENTERED_NATIVE}\ +${TRACE_EVENT}\ + ${P0_TYPE}* native = reinterpret_cast<${P0_TYPE}*>(${PARAM0_NAME}); + CHECK_NATIVE_PTR(env, jcaller, native, "${NAME}"${OPTIONAL_ERROR_RETURN}); + return native->${NAME}(${PARAMS_IN_CALL})${POST_CALL}; +} +""") + else: + if self.options.enable_tracing: + values['TRACE_EVENT'] = self.GetTraceEventForNameTemplate( + namespace_qual + '${IMPL_METHOD_NAME}', values) + template = Template("""\ +static ${RETURN_DECLARATION} ${IMPL_METHOD_NAME}(JNIEnv* env, ${PARAMS}); + +JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}( + JNIEnv* env, + ${PARAMS_IN_STUB}) { +${PROFILING_ENTERED_NATIVE}\ +${TRACE_EVENT}\ + return ${IMPL_METHOD_NAME}(${PARAMS_IN_CALL})${POST_CALL}; +} +""") + + return RemoveIndentedEmptyLines(template.substitute(values)) + + def GetArgument(self, param): + if param.datatype == 'int': + return 'as_jint(' + param.name + ')' + elif re.match(RE_SCOPED_JNI_TYPES, JavaDataTypeToC(param.datatype)): + return param.name + '.obj()' + else: + return param.name + + def GetArgumentsInCall(self, params): + """Return a string of arguments to call from native into Java""" + return [self.GetArgument(p) for p in params] + + def GetCalledByNativeValues(self, called_by_native): + """Fills in necessary values for the CalledByNative methods.""" + java_class_only = called_by_native.java_class_name or self.class_name + java_class = self.fully_qualified_class + if called_by_native.java_class_name: + java_class += '$' + called_by_native.java_class_name + + if called_by_native.static or called_by_native.is_constructor: + first_param_in_declaration = '' + first_param_in_call = ('%s_clazz(env)' % GetBinaryClassName(java_class)) + else: + first_param_in_declaration = ( + ', const base::android::JavaRef<jobject>& obj') + first_param_in_call = 'obj.obj()' + params_in_declaration = self.GetCalledByNativeParamsInDeclaration( + called_by_native) + if params_in_declaration: + params_in_declaration = ', ' + params_in_declaration + params_in_call = ', '.join(self.GetArgumentsInCall(called_by_native.params)) + if params_in_call: + params_in_call = ', ' + params_in_call + pre_call = '' + post_call = '' + if called_by_native.static_cast: + pre_call = 'static_cast<%s>(' % called_by_native.static_cast + post_call = ')' + check_exception = '' + if not called_by_native.unchecked: + check_exception = 'jni_generator::CheckException(env);' + return_type = JavaDataTypeToC(called_by_native.return_type) + optional_error_return = JavaReturnValueToC(called_by_native.return_type) + if optional_error_return: + optional_error_return = ', ' + optional_error_return + return_declaration = '' + return_clause = '' + if return_type != 'void': + pre_call = ' ' + pre_call + return_declaration = return_type + ' ret =' + if re.match(RE_SCOPED_JNI_TYPES, return_type): + return_type = 'base::android::ScopedJavaLocalRef<' + return_type + '>' + return_clause = 'return ' + return_type + '(env, ret);' + else: + return_clause = 'return ret;' + profiling_leaving_native = '' + if self.options.enable_profiling: + profiling_leaving_native = ' JNI_SAVE_FRAME_POINTER;\n' + jni_name = called_by_native.name + jni_return_type = called_by_native.return_type + if called_by_native.is_constructor: + jni_name = '<init>' + jni_return_type = 'void' + if called_by_native.signature: + jni_signature = called_by_native.signature + else: + jni_signature = self.jni_params.Signature( + called_by_native.params, jni_return_type) + java_name_full = java_class.replace('/', '.') + '.' + jni_name + return { + 'JAVA_CLASS_ONLY': java_class_only, + 'JAVA_CLASS': GetBinaryClassName(java_class), + 'RETURN_TYPE': return_type, + 'OPTIONAL_ERROR_RETURN': optional_error_return, + 'RETURN_DECLARATION': return_declaration, + 'RETURN_CLAUSE': return_clause, + 'FIRST_PARAM_IN_DECLARATION': first_param_in_declaration, + 'PARAMS_IN_DECLARATION': params_in_declaration, + 'PRE_CALL': pre_call, + 'POST_CALL': post_call, + 'ENV_CALL': called_by_native.env_call, + 'FIRST_PARAM_IN_CALL': first_param_in_call, + 'PARAMS_IN_CALL': params_in_call, + 'CHECK_EXCEPTION': check_exception, + 'PROFILING_LEAVING_NATIVE': profiling_leaving_native, + 'JNI_NAME': jni_name, + 'JNI_SIGNATURE': jni_signature, + 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name, + 'METHOD_ID_TYPE': 'STATIC' if called_by_native.static else 'INSTANCE', + 'JAVA_NAME_FULL': java_name_full, + } + + def GetLazyCalledByNativeMethodStub(self, called_by_native): + """Returns a string.""" + function_signature_template = Template("""\ +static ${RETURN_TYPE} Java_${JAVA_CLASS_ONLY}_${METHOD_ID_VAR_NAME}(\ +JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION})""") + function_header_template = Template("""\ +${FUNCTION_SIGNATURE} {""") + function_header_with_unused_template = Template("""\ +${FUNCTION_SIGNATURE} __attribute__ ((unused)); +${FUNCTION_SIGNATURE} {""") + template = Template(""" +static std::atomic<jmethodID> g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}(nullptr); +${FUNCTION_HEADER} + CHECK_CLAZZ(env, ${FIRST_PARAM_IN_CALL}, + ${JAVA_CLASS}_clazz(env)${OPTIONAL_ERROR_RETURN}); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_${METHOD_ID_TYPE}>( + env, ${JAVA_CLASS}_clazz(env), + "${JNI_NAME}", + ${JNI_SIGNATURE}, + &g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}); + +${TRACE_EVENT}\ +${PROFILING_LEAVING_NATIVE}\ + ${RETURN_DECLARATION} + ${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL}, + method_id${PARAMS_IN_CALL})${POST_CALL}; + ${CHECK_EXCEPTION} + ${RETURN_CLAUSE} +}""") + values = self.GetCalledByNativeValues(called_by_native) + values['FUNCTION_SIGNATURE'] = ( + function_signature_template.substitute(values)) + if called_by_native.system_class: + values['FUNCTION_HEADER'] = ( + function_header_with_unused_template.substitute(values)) + else: + values['FUNCTION_HEADER'] = function_header_template.substitute(values) + if self.options.enable_tracing: + values['TRACE_EVENT'] = self.GetTraceEventForNameTemplate( + '${JAVA_NAME_FULL}', values) + else: + values['TRACE_EVENT'] = '' + return RemoveIndentedEmptyLines(template.substitute(values)) + + def GetTraceEventForNameTemplate(self, name_template, values): + name = Template(name_template).substitute(values) + return ' TRACE_EVENT0("jni", "%s");' % name + + +def WrapOutput(output): + ret = [] + for line in output.splitlines(): + # Do not wrap preprocessor directives or comments. + if len(line) < _WRAP_LINE_LENGTH or line[0] == '#' or line.startswith('//'): + ret.append(line) + else: + # Assumes that the line is not already indented as a continuation line, + # which is not always true (oh well). + first_line_indent = (len(line) - len(line.lstrip())) + wrapper = _WRAPPERS_BY_INDENT[first_line_indent] + ret.extend(wrapper.wrap(line)) + ret += [''] + return '\n'.join(ret) + + +def ExtractJarInputFile(jar_file, input_file, out_dir): + """Extracts input file from jar and returns the filename. + + The input file is extracted to the same directory that the generated jni + headers will be placed in. This is passed as an argument to script. + + Args: + jar_file: the jar file containing the input files to extract. + input_files: the list of files to extract from the jar file. + out_dir: the name of the directories to extract to. + + Returns: + the name of extracted input file. + """ + jar_file = zipfile.ZipFile(jar_file) + + out_dir = os.path.join(out_dir, os.path.dirname(input_file)) + try: + os.makedirs(out_dir) + except OSError as e: + if e.errno != errno.EEXIST: + raise + extracted_file_name = os.path.join(out_dir, os.path.basename(input_file)) + with open(extracted_file_name, 'w') as outfile: + outfile.write(jar_file.read(input_file)) + + return extracted_file_name + + +def GenerateJNIHeader(input_file, output_file, options): + try: + if os.path.splitext(input_file)[1] == '.class': + jni_from_javap = JNIFromJavaP.CreateFromClass(input_file, options) + content = jni_from_javap.GetContent() + else: + jni_from_java_source = JNIFromJavaSource.CreateFromFile( + input_file, options) + content = jni_from_java_source.GetContent() + except ParseError, e: + print e + sys.exit(1) + if output_file: + WriteOutput(output_file, content) + else: + print content + + +def WriteOutput(output_file, content): + if os.path.exists(output_file): + with open(output_file) as f: + existing_content = f.read() + if existing_content == content: + return + with open(output_file, 'w') as f: + f.write(content) + + +def GetScriptName(): + script_components = os.path.abspath(sys.argv[0]).split(os.path.sep) + base_index = 0 + for idx, value in enumerate(script_components): + if value == 'base' or value == 'third_party': + base_index = idx + break + return os.sep.join(script_components[base_index:]) + + +def main(argv): + usage = """usage: %prog [OPTIONS] +This script will parse the given java source code extracting the native +declarations and print the header file to stdout (or a file). +See SampleForTests.java for more details. + """ + option_parser = optparse.OptionParser(usage=usage) + + option_parser.add_option('-j', '--jar_file', dest='jar_file', + help='Extract the list of input files from' + ' a specified jar file.' + ' Uses javap to extract the methods from a' + ' pre-compiled class. --input should point' + ' to pre-compiled Java .class files.') + option_parser.add_option('-n', dest='namespace', + help='Uses as a namespace in the generated header ' + 'instead of the javap class name, or when there is ' + 'no JNINamespace annotation in the java source.') + option_parser.add_option('--input_file', + help='Single input file name. The output file name ' + 'will be derived from it. Must be used with ' + '--output_dir.') + option_parser.add_option('--output_dir', + help='The output directory. Must be used with ' + '--input') + option_parser.add_option('--script_name', default=GetScriptName(), + help='The name of this script in the generated ' + 'header.') + option_parser.add_option('--includes', + help='The comma-separated list of header files to ' + 'include in the generated header.') + option_parser.add_option('--ptr_type', default='int', + type='choice', choices=['int', 'long'], + help='The type used to represent native pointers in ' + 'Java code. For 32-bit, use int; ' + 'for 64-bit, use long.') + option_parser.add_option('--cpp', default='cpp', + help='The path to cpp command.') + option_parser.add_option('--javap', default='javap', + help='The path to javap command.') + option_parser.add_option('--enable_profiling', action='store_true', + help='Add additional profiling instrumentation.') + option_parser.add_option('--enable_tracing', action='store_true', + help='Add TRACE_EVENTs to generated functions.') + options, args = option_parser.parse_args(argv) + if options.jar_file: + input_file = ExtractJarInputFile(options.jar_file, options.input_file, + options.output_dir) + elif options.input_file: + input_file = options.input_file + else: + option_parser.print_help() + print '\nError: Must specify --jar_file or --input_file.' + return 1 + output_file = None + if options.output_dir: + root_name = os.path.splitext(os.path.basename(input_file))[0] + output_file = os.path.join(options.output_dir, root_name) + '_jni.h' + GenerateJNIHeader(input_file, output_file, options) + + +if __name__ == '__main__': + sys.exit(main(sys.argv))
diff --git a/src/base/android/jni_generator/jni_generator.pydeps b/src/base/android/jni_generator/jni_generator.pydeps new file mode 100644 index 0000000..f3db670 --- /dev/null +++ b/src/base/android/jni_generator/jni_generator.pydeps
@@ -0,0 +1,7 @@ +# Generated by running: +# build/print_python_deps.py --root base/android/jni_generator --output base/android/jni_generator/jni_generator.pydeps base/android/jni_generator/jni_generator.py +../../../build/android/gyp/util/__init__.py +../../../build/android/gyp/util/build_utils.py +../../../build/android/gyp/util/md5_check.py +../../../build/gn_helpers.py +jni_generator.py
diff --git a/src/base/android/jni_generator/jni_generator_helper.h b/src/base/android/jni_generator/jni_generator_helper.h new file mode 100644 index 0000000..037a01b --- /dev/null +++ b/src/base/android/jni_generator/jni_generator_helper.h
@@ -0,0 +1,57 @@ +// Copyright 2014 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_ANDROID_JNI_GENERATOR_JNI_GENERATOR_HELPER_H_ +#define BASE_ANDROID_JNI_GENERATOR_JNI_GENERATOR_HELPER_H_ + +#include <jni.h> + +#include "base/android/jni_android.h" +#include "base/android/jni_int_wrapper.h" +#include "base/android/scoped_java_ref.h" +#include "base/logging.h" +#include "base/trace_event/trace_event.h" +#include "build/build_config.h" +#include "starboard/types.h" + +// Project-specific macros used by the header files generated by +// jni_generator.py. Different projects can then specify their own +// implementation for this file. +#define CHECK_NATIVE_PTR(env, jcaller, native_ptr, method_name, ...) \ + DCHECK(native_ptr) << method_name; + +#define CHECK_CLAZZ(env, jcaller, clazz, ...) DCHECK(clazz); + +#if defined(ARCH_CPU_X86) +// Dalvik JIT generated code doesn't guarantee 16-byte stack alignment on +// x86 - use force_align_arg_pointer to realign the stack at the JNI +// boundary. crbug.com/655248 +#define JNI_GENERATOR_EXPORT \ + extern "C" __attribute__((visibility("default"), force_align_arg_pointer)) +#else +#define JNI_GENERATOR_EXPORT extern "C" __attribute__((visibility("default"))) +#endif + +// Used to export JNI registration functions. +#if defined(COMPONENT_BUILD) +#define JNI_REGISTRATION_EXPORT __attribute__((visibility("default"))) +#else +#define JNI_REGISTRATION_EXPORT +#endif + +namespace jni_generator { + +inline void HandleRegistrationError(JNIEnv* env, + jclass clazz, + const char* filename) { + LOG(ERROR) << "RegisterNatives failed in " << filename; +} + +inline void CheckException(JNIEnv* env) { + base::android::CheckException(env); +} + +} // namespace jni_generator + +#endif // BASE_ANDROID_JNI_GENERATOR_JNI_GENERATOR_HELPER_H_
diff --git a/src/base/android/jni_generator/jni_generator_tests.py b/src/base/android/jni_generator/jni_generator_tests.py new file mode 100755 index 0000000..62ee862 --- /dev/null +++ b/src/base/android/jni_generator/jni_generator_tests.py
@@ -0,0 +1,1192 @@ +#!/usr/bin/env python +# Copyright (c) 2012 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. + +"""Tests for jni_generator.py. + +This test suite contains various tests for the JNI generator. +It exercises the low-level parser all the way up to the +code generator and ensures the output matches a golden +file. +""" + +import difflib +import inspect +import optparse +import os +import sys +import unittest +import jni_generator +import jni_registration_generator +from jni_generator import CalledByNative +from jni_generator import IsMainDexJavaClass +from jni_generator import NativeMethod +from jni_generator import Param + + +SCRIPT_NAME = 'base/android/jni_generator/jni_generator.py' +INCLUDES = ( + 'base/android/jni_generator/jni_generator_helper.h' +) + +# Set this environment variable in order to regenerate the golden text +# files. +REBASELINE_ENV = 'REBASELINE' + +class TestOptions(object): + """The mock options object which is passed to the jni_generator.py script.""" + + def __init__(self): + self.namespace = None + self.script_name = SCRIPT_NAME + self.includes = INCLUDES + self.ptr_type = 'long' + self.cpp = 'cpp' + self.javap = 'javap' + self.native_exports_optional = True + self.enable_profiling = False + self.enable_tracing = False + +class TestGenerator(unittest.TestCase): + def assertObjEquals(self, first, second): + dict_first = first.__dict__ + dict_second = second.__dict__ + self.assertEquals(dict_first.keys(), dict_second.keys()) + for key, value in dict_first.iteritems(): + if (type(value) is list and len(value) and + isinstance(type(value[0]), object)): + self.assertListEquals(value, second.__getattribute__(key)) + else: + actual = second.__getattribute__(key) + self.assertEquals(value, actual, + 'Key ' + key + ': ' + str(value) + '!=' + str(actual)) + + def assertListEquals(self, first, second): + self.assertEquals(len(first), len(second)) + for i in xrange(len(first)): + if isinstance(first[i], object): + self.assertObjEquals(first[i], second[i]) + else: + self.assertEquals(first[i], second[i]) + + def assertTextEquals(self, golden_text, generated_text): + if not self.compareText(golden_text, generated_text): + self.fail('Golden text mismatch.') + + def compareText(self, golden_text, generated_text): + def FilterText(text): + return [ + l.strip() for l in text.split('\n') + if not l.startswith('// Copyright') + ] + stripped_golden = FilterText(golden_text) + stripped_generated = FilterText(generated_text) + if stripped_golden == stripped_generated: + return True + print self.id() + for line in difflib.context_diff(stripped_golden, stripped_generated): + print line + print '\n\nGenerated' + print '=' * 80 + print generated_text + print '=' * 80 + print 'Run with:' + print 'REBASELINE=1', sys.argv[0] + print 'to regenerate the data files.' + + def _ReadGoldenFile(self, golden_file): + if not os.path.exists(golden_file): + return None + with file(golden_file, 'r') as f: + return f.read() + + def assertGoldenTextEquals(self, generated_text, suffix=''): + script_dir = os.path.dirname(sys.argv[0]) + # This is the caller test method. + caller = inspect.stack()[1][3] + self.assertTrue(caller.startswith('test'), + 'assertGoldenTextEquals can only be called from a ' + 'test* method, not %s' % caller) + golden_file = os.path.join(script_dir, '%s%s.golden' % (caller, suffix)) + golden_text = self._ReadGoldenFile(golden_file) + if os.environ.get(REBASELINE_ENV): + if golden_text != generated_text: + with file(golden_file, 'w') as f: + f.write(generated_text) + return + self.assertTextEquals(golden_text, generated_text) + + def testInspectCaller(self): + def willRaise(): + # This function can only be called from a test* method. + self.assertGoldenTextEquals('') + self.assertRaises(AssertionError, willRaise) + + def testNatives(self): + test_data = """" + import android.graphics.Bitmap; + import android.view.View; + + interface OnFrameAvailableListener {} + private native int nativeInit(); + private native void nativeDestroy(int nativeChromeBrowserProvider); + private native long nativeAddBookmark( + int nativeChromeBrowserProvider, + String url, String title, boolean isFolder, long parentId); + private static native String nativeGetDomainAndRegistry(String url); + private static native void nativeCreateHistoricalTabFromState( + byte[] state, int tab_index); + private native byte[] nativeGetStateAsByteArray(View view); + private static native String[] nativeGetAutofillProfileGUIDs(); + private native void nativeSetRecognitionResults( + int sessionId, String[] results); + private native long nativeAddBookmarkFromAPI( + int nativeChromeBrowserProvider, + String url, Long created, Boolean isBookmark, + Long date, byte[] favicon, String title, Integer visits); + native int nativeFindAll(String find); + private static native OnFrameAvailableListener nativeGetInnerClass(); + private native Bitmap nativeQueryBitmap( + int nativeChromeBrowserProvider, + String[] projection, String selection, + String[] selectionArgs, String sortOrder); + private native void nativeGotOrientation( + int nativeDataFetcherImplAndroid, + double alpha, double beta, double gamma); + private static native Throwable nativeMessWithJavaException(Throwable e); + """ + jni_params = jni_generator.JniParams( + 'org/chromium/example/jni_generator/SampleForTests') + jni_params.ExtractImportsAndInnerClasses(test_data) + natives = jni_generator.ExtractNatives(test_data, 'int') + golden_natives = [ + NativeMethod(return_type='int', static=False, + name='Init', + params=[], + java_class_name=None, + type='function'), + NativeMethod(return_type='void', static=False, name='Destroy', + params=[Param(datatype='int', + name='nativeChromeBrowserProvider')], + java_class_name=None, + type='method', + p0_type='ChromeBrowserProvider'), + NativeMethod(return_type='long', static=False, name='AddBookmark', + params=[Param(datatype='int', + name='nativeChromeBrowserProvider'), + Param(datatype='String', + name='url'), + Param(datatype='String', + name='title'), + Param(datatype='boolean', + name='isFolder'), + Param(datatype='long', + name='parentId')], + java_class_name=None, + type='method', + p0_type='ChromeBrowserProvider'), + NativeMethod(return_type='String', static=True, + name='GetDomainAndRegistry', + params=[Param(datatype='String', + name='url')], + java_class_name=None, + type='function'), + NativeMethod(return_type='void', static=True, + name='CreateHistoricalTabFromState', + params=[Param(datatype='byte[]', + name='state'), + Param(datatype='int', + name='tab_index')], + java_class_name=None, + type='function'), + NativeMethod(return_type='byte[]', static=False, + name='GetStateAsByteArray', + params=[Param(datatype='View', name='view')], + java_class_name=None, + type='function'), + NativeMethod(return_type='String[]', static=True, + name='GetAutofillProfileGUIDs', params=[], + java_class_name=None, + type='function'), + NativeMethod(return_type='void', static=False, + name='SetRecognitionResults', + params=[Param(datatype='int', name='sessionId'), + Param(datatype='String[]', name='results')], + java_class_name=None, + type='function'), + NativeMethod(return_type='long', static=False, + name='AddBookmarkFromAPI', + params=[Param(datatype='int', + name='nativeChromeBrowserProvider'), + Param(datatype='String', + name='url'), + Param(datatype='Long', + name='created'), + Param(datatype='Boolean', + name='isBookmark'), + Param(datatype='Long', + name='date'), + Param(datatype='byte[]', + name='favicon'), + Param(datatype='String', + name='title'), + Param(datatype='Integer', + name='visits')], + java_class_name=None, + type='method', + p0_type='ChromeBrowserProvider'), + NativeMethod(return_type='int', static=False, + name='FindAll', + params=[Param(datatype='String', + name='find')], + java_class_name=None, + type='function'), + NativeMethod(return_type='OnFrameAvailableListener', static=True, + name='GetInnerClass', + params=[], + java_class_name=None, + type='function'), + NativeMethod(return_type='Bitmap', + static=False, + name='QueryBitmap', + params=[Param(datatype='int', + name='nativeChromeBrowserProvider'), + Param(datatype='String[]', + name='projection'), + Param(datatype='String', + name='selection'), + Param(datatype='String[]', + name='selectionArgs'), + Param(datatype='String', + name='sortOrder'), + ], + java_class_name=None, + type='method', + p0_type='ChromeBrowserProvider'), + NativeMethod(return_type='void', static=False, + name='GotOrientation', + params=[Param(datatype='int', + name='nativeDataFetcherImplAndroid'), + Param(datatype='double', + name='alpha'), + Param(datatype='double', + name='beta'), + Param(datatype='double', + name='gamma'), + ], + java_class_name=None, + type='method', + p0_type='content::DataFetcherImplAndroid'), + NativeMethod(return_type='Throwable', static=True, + name='MessWithJavaException', + params=[Param(datatype='Throwable', name='e')], + java_class_name=None, + type='function') + ] + self.assertListEquals(golden_natives, natives) + h1 = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', + natives, [], [], jni_params, + TestOptions()) + self.assertGoldenTextEquals(h1.GetContent()) + h2 = jni_registration_generator.HeaderGenerator( + '', 'org/chromium/TestJni', natives, jni_params, True) + content = h2.Generate() + for k in jni_registration_generator.MERGEABLE_KEYS: + content[k] = content.get(k, '') + content['HEADER_GUARD'] = 'HEADER_GUARD' + content['NAMESPACE'] = 'test' + + self.assertGoldenTextEquals( + jni_registration_generator.CreateFromDict(content), + suffix='Registrations') + + + def testInnerClassNatives(self): + test_data = """ + class MyInnerClass { + @NativeCall("MyInnerClass") + private native int nativeInit(); + } + """ + natives = jni_generator.ExtractNatives(test_data, 'int') + golden_natives = [ + NativeMethod(return_type='int', static=False, + name='Init', params=[], + java_class_name='MyInnerClass', + type='function') + ] + self.assertListEquals(golden_natives, natives) + jni_params = jni_generator.JniParams('') + h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', + natives, [], [], jni_params, + TestOptions()) + self.assertGoldenTextEquals(h.GetContent()) + + def testInnerClassNativesMultiple(self): + test_data = """ + class MyInnerClass { + @NativeCall("MyInnerClass") + private native int nativeInit(); + } + class MyOtherInnerClass { + @NativeCall("MyOtherInnerClass") + private native int nativeInit(); + } + """ + natives = jni_generator.ExtractNatives(test_data, 'int') + golden_natives = [ + NativeMethod(return_type='int', static=False, + name='Init', params=[], + java_class_name='MyInnerClass', + type='function'), + NativeMethod(return_type='int', static=False, + name='Init', params=[], + java_class_name='MyOtherInnerClass', + type='function') + ] + self.assertListEquals(golden_natives, natives) + jni_params = jni_generator.JniParams('') + h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', + natives, [], [], jni_params, + TestOptions()) + self.assertGoldenTextEquals(h.GetContent()) + + def testInnerClassNativesBothInnerAndOuter(self): + test_data = """ + class MyOuterClass { + private native int nativeInit(); + class MyOtherInnerClass { + @NativeCall("MyOtherInnerClass") + private native int nativeInit(); + } + } + """ + natives = jni_generator.ExtractNatives(test_data, 'int') + golden_natives = [ + NativeMethod(return_type='int', static=False, + name='Init', params=[], + java_class_name=None, + type='function'), + NativeMethod(return_type='int', static=False, + name='Init', params=[], + java_class_name='MyOtherInnerClass', + type='function') + ] + self.assertListEquals(golden_natives, natives) + jni_params = jni_generator.JniParams('') + h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', + natives, [], [], jni_params, + TestOptions()) + self.assertGoldenTextEquals(h.GetContent()) + + h2 = jni_registration_generator.HeaderGenerator( + '', 'org/chromium/TestJni', natives, jni_params, True) + content = h2.Generate() + for k in jni_registration_generator.MERGEABLE_KEYS: + content[k] = content.get(k, '') + content['HEADER_GUARD'] = 'HEADER_GUARD' + content['NAMESPACE'] = 'test' + + self.assertGoldenTextEquals( + jni_registration_generator.CreateFromDict(content), + suffix='Registrations') + + def testCalledByNatives(self): + test_data = """" + import android.graphics.Bitmap; + import android.view.View; + import java.io.InputStream; + import java.util.List; + + class InnerClass {} + + @CalledByNative + @SomeOtherA + @SomeOtherB + public InnerClass showConfirmInfoBar(int nativeInfoBar, + String buttonOk, String buttonCancel, String title, Bitmap icon) { + InfoBar infobar = new ConfirmInfoBar(nativeInfoBar, mContext, + buttonOk, buttonCancel, + title, icon); + return infobar; + } + @CalledByNative + InnerClass showAutoLoginInfoBar(int nativeInfoBar, + String realm, String account, String args) { + AutoLoginInfoBar infobar = new AutoLoginInfoBar(nativeInfoBar, mContext, + realm, account, args); + if (infobar.displayedAccountCount() == 0) + infobar = null; + return infobar; + } + @CalledByNative("InfoBar") + void dismiss(); + @SuppressWarnings("unused") + @CalledByNative + private static boolean shouldShowAutoLogin(View view, + String realm, String account, String args) { + AccountManagerContainer accountManagerContainer = + new AccountManagerContainer((Activity)contentView.getContext(), + realm, account, args); + String[] logins = accountManagerContainer.getAccountLogins(null); + return logins.length != 0; + } + @CalledByNative + static InputStream openUrl(String url) { + return null; + } + @CalledByNative + private void activateHardwareAcceleration(final boolean activated, + final int iPid, final int iType, + final int iPrimaryID, final int iSecondaryID) { + if (!activated) { + return + } + } + @CalledByNative + public static @Status int updateStatus(@Status int status) { + return getAndUpdateStatus(status); + } + @CalledByNativeUnchecked + private void uncheckedCall(int iParam); + + @CalledByNative + public byte[] returnByteArray(); + + @CalledByNative + public boolean[] returnBooleanArray(); + + @CalledByNative + public char[] returnCharArray(); + + @CalledByNative + public short[] returnShortArray(); + + @CalledByNative + public int[] returnIntArray(); + + @CalledByNative + public long[] returnLongArray(); + + @CalledByNative + public double[] returnDoubleArray(); + + @CalledByNative + public Object[] returnObjectArray(); + + @CalledByNative + public byte[][] returnArrayOfByteArray(); + + @CalledByNative + public Bitmap.CompressFormat getCompressFormat(); + + @CalledByNative + public List<Bitmap.CompressFormat> getCompressFormatList(); + """ + jni_params = jni_generator.JniParams('org/chromium/Foo') + jni_params.ExtractImportsAndInnerClasses(test_data) + called_by_natives = jni_generator.ExtractCalledByNatives(jni_params, + test_data) + golden_called_by_natives = [ + CalledByNative( + return_type='InnerClass', + system_class=False, + static=False, + name='showConfirmInfoBar', + method_id_var_name='showConfirmInfoBar', + java_class_name='', + params=[Param(datatype='int', name='nativeInfoBar'), + Param(datatype='String', name='buttonOk'), + Param(datatype='String', name='buttonCancel'), + Param(datatype='String', name='title'), + Param(datatype='Bitmap', name='icon')], + env_call=('Object', ''), + unchecked=False, + ), + CalledByNative( + return_type='InnerClass', + system_class=False, + static=False, + name='showAutoLoginInfoBar', + method_id_var_name='showAutoLoginInfoBar', + java_class_name='', + params=[Param(datatype='int', name='nativeInfoBar'), + Param(datatype='String', name='realm'), + Param(datatype='String', name='account'), + Param(datatype='String', name='args')], + env_call=('Object', ''), + unchecked=False, + ), + CalledByNative( + return_type='void', + system_class=False, + static=False, + name='dismiss', + method_id_var_name='dismiss', + java_class_name='InfoBar', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='boolean', + system_class=False, + static=True, + name='shouldShowAutoLogin', + method_id_var_name='shouldShowAutoLogin', + java_class_name='', + params=[Param(datatype='View', name='view'), + Param(datatype='String', name='realm'), + Param(datatype='String', name='account'), + Param(datatype='String', name='args')], + env_call=('Boolean', ''), + unchecked=False, + ), + CalledByNative( + return_type='InputStream', + system_class=False, + static=True, + name='openUrl', + method_id_var_name='openUrl', + java_class_name='', + params=[Param(datatype='String', name='url')], + env_call=('Object', ''), + unchecked=False, + ), + CalledByNative( + return_type='void', + system_class=False, + static=False, + name='activateHardwareAcceleration', + method_id_var_name='activateHardwareAcceleration', + java_class_name='', + params=[Param(datatype='boolean', name='activated'), + Param(datatype='int', name='iPid'), + Param(datatype='int', name='iType'), + Param(datatype='int', name='iPrimaryID'), + Param(datatype='int', name='iSecondaryID'), + ], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='int', + system_class=False, + static=True, + name='updateStatus', + method_id_var_name='updateStatus', + java_class_name='', + params=[Param(datatype='int', name='status')], + env_call=('Integer', ''), + unchecked=False, + ), + CalledByNative( + return_type='void', + system_class=False, + static=False, + name='uncheckedCall', + method_id_var_name='uncheckedCall', + java_class_name='', + params=[Param(datatype='int', name='iParam')], + env_call=('Void', ''), + unchecked=True, + ), + CalledByNative( + return_type='byte[]', + system_class=False, + static=False, + name='returnByteArray', + method_id_var_name='returnByteArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='boolean[]', + system_class=False, + static=False, + name='returnBooleanArray', + method_id_var_name='returnBooleanArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='char[]', + system_class=False, + static=False, + name='returnCharArray', + method_id_var_name='returnCharArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='short[]', + system_class=False, + static=False, + name='returnShortArray', + method_id_var_name='returnShortArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='int[]', + system_class=False, + static=False, + name='returnIntArray', + method_id_var_name='returnIntArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='long[]', + system_class=False, + static=False, + name='returnLongArray', + method_id_var_name='returnLongArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='double[]', + system_class=False, + static=False, + name='returnDoubleArray', + method_id_var_name='returnDoubleArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='Object[]', + system_class=False, + static=False, + name='returnObjectArray', + method_id_var_name='returnObjectArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='byte[][]', + system_class=False, + static=False, + name='returnArrayOfByteArray', + method_id_var_name='returnArrayOfByteArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='Bitmap.CompressFormat', + system_class=False, + static=False, + name='getCompressFormat', + method_id_var_name='getCompressFormat', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='List<Bitmap.CompressFormat>', + system_class=False, + static=False, + name='getCompressFormatList', + method_id_var_name='getCompressFormatList', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + ] + self.assertListEquals(golden_called_by_natives, called_by_natives) + h = jni_generator.InlHeaderFileGenerator( + '', 'org/chromium/TestJni', [], called_by_natives, [], jni_params, + TestOptions()) + self.assertGoldenTextEquals(h.GetContent()) + + def testCalledByNativeParseError(self): + try: + jni_params = jni_generator.JniParams('') + jni_generator.ExtractCalledByNatives(jni_params, """ +@CalledByNative +public static int foo(); // This one is fine + +@CalledByNative +scooby doo +""") + self.fail('Expected a ParseError') + except jni_generator.ParseError, e: + self.assertEquals(('@CalledByNative', 'scooby doo'), e.context_lines) + + def testFullyQualifiedClassName(self): + contents = """ +// Copyright (c) 2010 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. + +package org.chromium.content.browser; + +import org.chromium.base.BuildInfo; +""" + self.assertEquals('org/chromium/content/browser/Foo', + jni_generator.ExtractFullyQualifiedJavaClassName( + 'org/chromium/content/browser/Foo.java', contents)) + self.assertEquals('org/chromium/content/browser/Foo', + jni_generator.ExtractFullyQualifiedJavaClassName( + 'frameworks/Foo.java', contents)) + self.assertRaises(SyntaxError, + jni_generator.ExtractFullyQualifiedJavaClassName, + 'com/foo/Bar', 'no PACKAGE line') + + def testMethodNameMangling(self): + jni_params = jni_generator.JniParams('') + self.assertEquals('closeV', + jni_generator.GetMangledMethodName(jni_params, 'close', [], 'void')) + self.assertEquals('readI_AB_I_I', + jni_generator.GetMangledMethodName(jni_params, 'read', + [Param(name='p1', + datatype='byte[]'), + Param(name='p2', + datatype='int'), + Param(name='p3', + datatype='int'),], + 'int')) + self.assertEquals('openJIIS_JLS', + jni_generator.GetMangledMethodName(jni_params, 'open', + [Param(name='p1', + datatype='java/lang/String'),], + 'java/io/InputStream')) + + def testFromJavaPGenerics(self): + contents = """ +public abstract class java.util.HashSet<T> extends java.util.AbstractSet<E> + implements java.util.Set<E>, java.lang.Cloneable, java.io.Serializable { + public void dummy(); + Signature: ()V + public java.lang.Class<?> getClass(); + Signature: ()Ljava/lang/Class<*>; +} +""" + jni_from_javap = jni_generator.JNIFromJavaP(contents.split('\n'), + TestOptions()) + self.assertEquals(2, len(jni_from_javap.called_by_natives)) + self.assertGoldenTextEquals(jni_from_javap.GetContent()) + + def testSnippnetJavap6_7_8(self): + content_javap6 = """ +public class java.util.HashSet { +public boolean add(java.lang.Object); + Signature: (Ljava/lang/Object;)Z +} +""" + + content_javap7 = """ +public class java.util.HashSet { +public boolean add(E); + Signature: (Ljava/lang/Object;)Z +} +""" + + content_javap8 = """ +public class java.util.HashSet { + public boolean add(E); + descriptor: (Ljava/lang/Object;)Z +} +""" + + jni_from_javap6 = jni_generator.JNIFromJavaP(content_javap6.split('\n'), + TestOptions()) + jni_from_javap7 = jni_generator.JNIFromJavaP(content_javap7.split('\n'), + TestOptions()) + jni_from_javap8 = jni_generator.JNIFromJavaP(content_javap8.split('\n'), + TestOptions()) + self.assertTrue(jni_from_javap6.GetContent()) + self.assertTrue(jni_from_javap7.GetContent()) + self.assertTrue(jni_from_javap8.GetContent()) + # Ensure the javap7 is correctly parsed and uses the Signature field rather + # than the "E" parameter. + self.assertTextEquals(jni_from_javap6.GetContent(), + jni_from_javap7.GetContent()) + # Ensure the javap8 is correctly parsed and uses the descriptor field. + self.assertTextEquals(jni_from_javap7.GetContent(), + jni_from_javap8.GetContent()) + + def testFromJavaP(self): + contents = self._ReadGoldenFile(os.path.join(os.path.dirname(sys.argv[0]), + 'testInputStream.javap')) + jni_from_javap = jni_generator.JNIFromJavaP(contents.split('\n'), + TestOptions()) + self.assertEquals(10, len(jni_from_javap.called_by_natives)) + self.assertGoldenTextEquals(jni_from_javap.GetContent()) + + def testConstantsFromJavaP(self): + for f in ['testMotionEvent.javap', 'testMotionEvent.javap7']: + contents = self._ReadGoldenFile(os.path.join(os.path.dirname(sys.argv[0]), + f)) + jni_from_javap = jni_generator.JNIFromJavaP(contents.split('\n'), + TestOptions()) + self.assertEquals(86, len(jni_from_javap.called_by_natives)) + self.assertGoldenTextEquals(jni_from_javap.GetContent()) + + def testREForNatives(self): + # We should not match "native SyncSetupFlow" inside the comment. + test_data = """ + /** + * Invoked when the setup process is complete so we can disconnect from the + * native-side SyncSetupFlowHandler. + */ + public void destroy() { + Log.v(TAG, "Destroying native SyncSetupFlow"); + if (mNativeSyncSetupFlow != 0) { + nativeSyncSetupEnded(mNativeSyncSetupFlow); + mNativeSyncSetupFlow = 0; + } + } + private native void nativeSyncSetupEnded( + int nativeAndroidSyncSetupFlowHandler); + """ + jni_from_java = jni_generator.JNIFromJavaSource( + test_data, 'foo/bar', TestOptions()) + + def testRaisesOnNonJNIMethod(self): + test_data = """ + class MyInnerClass { + private int Foo(int p0) { + } + } + """ + self.assertRaises(SyntaxError, + jni_generator.JNIFromJavaSource, + test_data, 'foo/bar', TestOptions()) + + def testJniSelfDocumentingExample(self): + script_dir = os.path.dirname(sys.argv[0]) + content = file(os.path.join(script_dir, + 'java/src/org/chromium/example/jni_generator/SampleForTests.java') + ).read() + golden_file = os.path.join(script_dir, 'SampleForTests_jni.golden') + golden_content = file(golden_file).read() + jni_from_java = jni_generator.JNIFromJavaSource( + content, 'org/chromium/example/jni_generator/SampleForTests', + TestOptions()) + generated_text = jni_from_java.GetContent() + if not self.compareText(golden_content, generated_text): + if os.environ.get(REBASELINE_ENV): + with file(golden_file, 'w') as f: + f.write(generated_text) + return + self.fail('testJniSelfDocumentingExample') + + def testNoWrappingPreprocessorLines(self): + test_data = """ + package com.google.lookhowextremelylongiam.snarf.icankeepthisupallday; + + class ReallyLongClassNamesAreAllTheRage { + private static native int nativeTest(); + } + """ + jni_from_java = jni_generator.JNIFromJavaSource( + test_data, ('com/google/lookhowextremelylongiam/snarf/' + 'icankeepthisupallday/ReallyLongClassNamesAreAllTheRage'), + TestOptions()) + jni_lines = jni_from_java.GetContent().split('\n') + line = filter(lambda line: line.lstrip().startswith('#ifndef'), + jni_lines)[0] + self.assertTrue(len(line) > 80, + ('Expected #ifndef line to be > 80 chars: ', line)) + + def testImports(self): + import_header = """ +// Copyright (c) 2012 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. + +package org.chromium.content.app; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.graphics.SurfaceTexture; +import android.os.Bundle; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.os.RemoteException; +import android.util.Log; +import android.view.Surface; + +import java.util.ArrayList; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.content.app.ContentMain; +import org.chromium.content.browser.SandboxedProcessConnection; +import org.chromium.content.common.ISandboxedProcessCallback; +import org.chromium.content.common.ISandboxedProcessService; +import org.chromium.content.common.WillNotRaise.AnException; +import org.chromium.content.common.WillRaise.AnException; + +import static org.chromium.Bar.Zoo; + +class Foo { + public static class BookmarkNode implements Parcelable { + } + public interface PasswordListObserver { + } +} + """ + jni_params = jni_generator.JniParams('org/chromium/content/app/Foo') + jni_params.ExtractImportsAndInnerClasses(import_header) + self.assertTrue('Lorg/chromium/content/common/ISandboxedProcessService' in + jni_params._imports) + self.assertTrue('Lorg/chromium/Bar/Zoo' in + jni_params._imports) + self.assertTrue('Lorg/chromium/content/app/Foo$BookmarkNode' in + jni_params._inner_classes) + self.assertTrue('Lorg/chromium/content/app/Foo$PasswordListObserver' in + jni_params._inner_classes) + self.assertEquals('Lorg/chromium/content/app/ContentMain$Inner;', + jni_params.JavaToJni('ContentMain.Inner')) + self.assertRaises(SyntaxError, + jni_params.JavaToJni, 'AnException') + + def testJniParamsJavaToJni(self): + jni_params = jni_generator.JniParams('') + self.assertTextEquals('I', jni_params.JavaToJni('int')) + self.assertTextEquals('[B', jni_params.JavaToJni('byte[]')) + self.assertTextEquals( + '[Ljava/nio/ByteBuffer;', jni_params.JavaToJni('java/nio/ByteBuffer[]')) + + def testNativesLong(self): + test_options = TestOptions() + test_options.ptr_type = 'long' + test_data = """" + private native void nativeDestroy(long nativeChromeBrowserProvider); + """ + jni_params = jni_generator.JniParams('') + jni_params.ExtractImportsAndInnerClasses(test_data) + natives = jni_generator.ExtractNatives(test_data, test_options.ptr_type) + golden_natives = [ + NativeMethod(return_type='void', static=False, name='Destroy', + params=[Param(datatype='long', + name='nativeChromeBrowserProvider')], + java_class_name=None, + type='method', + p0_type='ChromeBrowserProvider', + ptr_type=test_options.ptr_type), + ] + self.assertListEquals(golden_natives, natives) + h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', + natives, [], [], jni_params, + test_options) + self.assertGoldenTextEquals(h.GetContent()) + + def testMainDexAnnotation(self): + mainDexEntries = [ + '@MainDex public class Test {', + '@MainDex public class Test{', + """@MainDex + public class Test { + """, + """@MainDex public class Test + { + """, + '@MainDex /* This class is a test */ public class Test {', + '@MainDex public class Test implements java.io.Serializable {', + '@MainDex public class Test implements java.io.Serializable, Bidule {', + '@MainDex public class Test extends BaseTest {', + """@MainDex + public class Test extends BaseTest implements Bidule { + """, + """@MainDex + public class Test extends BaseTest implements Bidule, Machin, Chose { + """, + """@MainDex + public class Test implements Testable<java.io.Serializable> { + """, + '@MainDex public class Test implements Testable<java.io.Serializable> {', + '@a.B @MainDex @C public class Test extends Testable<Serializable> {', + """public class Test extends Testable<java.io.Serializable> { + @MainDex void func() {} + """, + ] + for entry in mainDexEntries: + self.assertEquals(True, IsMainDexJavaClass(entry), entry) + + def testNoMainDexAnnotation(self): + noMainDexEntries = [ + 'public class Test {', + '@NotMainDex public class Test {', + '// @MainDex public class Test {', + '/* @MainDex */ public class Test {', + 'public class Test implements java.io.Serializable {', + '@MainDexNot public class Test {', + 'public class Test extends BaseTest {' + ] + for entry in noMainDexEntries: + self.assertEquals(False, IsMainDexJavaClass(entry)) + + def testNativeExportsOnlyOption(self): + test_data = """ + package org.chromium.example.jni_generator; + + /** The pointer to the native Test. */ + long nativeTest; + + class Test { + private static native int nativeStaticMethod(long nativeTest, int arg1); + private native int nativeMethod(long nativeTest, int arg1); + @CalledByNative + private void testMethodWithParam(int iParam); + @CalledByNative + private String testMethodWithParamAndReturn(int iParam); + @CalledByNative + private static int testStaticMethodWithParam(int iParam); + @CalledByNative + private static double testMethodWithNoParam(); + @CalledByNative + private static String testStaticMethodWithNoParam(); + + class MyInnerClass { + @NativeCall("MyInnerClass") + private native int nativeInit(); + } + class MyOtherInnerClass { + @NativeCall("MyOtherInnerClass") + private native int nativeInit(); + } + } + """ + options = TestOptions() + options.native_exports_optional = False + jni_from_java = jni_generator.JNIFromJavaSource( + test_data, 'org/chromium/example/jni_generator/SampleForTests', options) + self.assertGoldenTextEquals(jni_from_java.GetContent()) + + def testOuterInnerRaises(self): + test_data = """ + package org.chromium.media; + + @CalledByNative + static int getCaptureFormatWidth(VideoCapture.CaptureFormat format) { + return format.getWidth(); + } + """ + def willRaise(): + jni_generator.JNIFromJavaSource( + test_data, + 'org/chromium/media/VideoCaptureFactory', + TestOptions()) + self.assertRaises(SyntaxError, willRaise) + + def testSingleJNIAdditionalImport(self): + test_data = """ + package org.chromium.foo; + + @JNIAdditionalImport(Bar.class) + class Foo { + + @CalledByNative + private static void calledByNative(Bar.Callback callback) { + } + + private static native void nativeDoSomething(Bar.Callback callback); + } + """ + jni_from_java = jni_generator.JNIFromJavaSource(test_data, + 'org/chromium/foo/Foo', + TestOptions()) + self.assertGoldenTextEquals(jni_from_java.GetContent()) + + def testMultipleJNIAdditionalImport(self): + test_data = """ + package org.chromium.foo; + + @JNIAdditionalImport({Bar1.class, Bar2.class}) + class Foo { + + @CalledByNative + private static void calledByNative(Bar1.Callback callback1, + Bar2.Callback callback2) { + } + + private static native void nativeDoSomething(Bar1.Callback callback1, + Bar2.Callback callback2); + } + """ + jni_from_java = jni_generator.JNIFromJavaSource(test_data, + 'org/chromium/foo/Foo', + TestOptions()) + self.assertGoldenTextEquals(jni_from_java.GetContent()) + + def testTracing(self): + test_data = """ + package org.chromium.foo; + + @JNINamespace("org::chromium_foo") + class Foo { + + @CalledByNative + Foo(); + + @CalledByNative + void callbackFromNative(); + + native void nativeInstanceMethod(long nativeInstance); + + static native void nativeStaticMethod(); + } + """ + options_with_tracing = TestOptions() + options_with_tracing.enable_tracing = True + jni_from_java = jni_generator.JNIFromJavaSource(test_data, + 'org/chromium/foo/Foo', + options_with_tracing) + self.assertGoldenTextEquals(jni_from_java.GetContent()) + + +def TouchStamp(stamp_path): + dir_name = os.path.dirname(stamp_path) + if not os.path.isdir(dir_name): + os.makedirs(dir_name) + + with open(stamp_path, 'a'): + os.utime(stamp_path, None) + + +def main(argv): + parser = optparse.OptionParser() + parser.add_option('--stamp', help='Path to touch on success.') + parser.add_option('--verbose', action="store_true", + help='Whether to output details.') + options, _ = parser.parse_args(argv[1:]) + + test_result = unittest.main( + argv=argv[0:1], + exit=False, + verbosity=(2 if options.verbose else 1)) + + if test_result.result.wasSuccessful() and options.stamp: + TouchStamp(options.stamp) + + return not test_result.result.wasSuccessful() + + +if __name__ == '__main__': + sys.exit(main(sys.argv))
diff --git a/src/base/android/jni_generator/jni_registration_generator.py b/src/base/android/jni_generator/jni_registration_generator.py new file mode 100755 index 0000000..44f97df --- /dev/null +++ b/src/base/android/jni_generator/jni_registration_generator.py
@@ -0,0 +1,355 @@ +#!/usr/bin/env python +# Copyright 2017 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. + +"""Generate JNI registration entry points + +Creates a header file with two static functions: RegisterMainDexNatives() and +RegisterNonMainDexNatives(). Together, these will use manual JNI registration +to register all native methods that exist within an application.""" + +import argparse +import jni_generator +import multiprocessing +import os +import string +import sys +from util import build_utils + + +# All but FULL_CLASS_NAME, which is used only for sorting. +MERGEABLE_KEYS = [ + 'CLASS_PATH_DECLARATIONS', + 'FORWARD_DECLARATIONS', + 'JNI_NATIVE_METHOD', + 'JNI_NATIVE_METHOD_ARRAY', + 'REGISTER_MAIN_DEX_NATIVES', + 'REGISTER_NON_MAIN_DEX_NATIVES', +] + + +def GenerateJNIHeader(java_file_paths, output_file, args): + """Generate a header file including two registration functions. + + Forward declares all JNI registration functions created by jni_generator.py. + Calls the functions in RegisterMainDexNatives() if they are main dex. And + calls them in RegisterNonMainDexNatives() if they are non-main dex. + + Args: + java_file_paths: A list of java file paths. + output_file: A relative path to output file. + args: All input arguments. + """ + # Without multiprocessing, script takes ~13 seconds for chrome_public_apk + # on a z620. With multiprocessing, takes ~2 seconds. + pool = multiprocessing.Pool() + paths = (p for p in java_file_paths if p not in args.no_register_java) + results = [d for d in pool.imap_unordered(_DictForPath, paths) if d] + pool.close() + + # Sort to make output deterministic. + results.sort(key=lambda d: d['FULL_CLASS_NAME']) + + combined_dict = {} + for key in MERGEABLE_KEYS: + combined_dict[key] = ''.join(d.get(key, '') for d in results) + + combined_dict['HEADER_GUARD'] = \ + os.path.splitext(output_file)[0].replace('/', '_').upper() + '_' + combined_dict['NAMESPACE'] = args.namespace + + header_content = CreateFromDict(combined_dict) + if output_file: + jni_generator.WriteOutput(output_file, header_content) + else: + print header_content + + +def _DictForPath(path): + with open(path) as f: + contents = jni_generator.RemoveComments(f.read()) + natives = jni_generator.ExtractNatives(contents, 'long') + if len(natives) == 0: + return None + namespace = jni_generator.ExtractJNINamespace(contents) + fully_qualified_class = jni_generator.ExtractFullyQualifiedJavaClassName( + path, contents) + jni_params = jni_generator.JniParams(fully_qualified_class) + jni_params.ExtractImportsAndInnerClasses(contents) + main_dex = jni_generator.IsMainDexJavaClass(contents) + header_generator = HeaderGenerator( + namespace, fully_qualified_class, natives, jni_params, main_dex) + return header_generator.Generate() + + +def CreateFromDict(registration_dict): + """Returns the content of the header file.""" + + template = string.Template("""\ +// Copyright 2017 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. + + +// This file is autogenerated by +// base/android/jni_generator/jni_registration_generator.py +// Please do not change its content. + +#ifndef ${HEADER_GUARD} +#define ${HEADER_GUARD} + +#include <jni.h> + +#include "base/android/jni_generator/jni_generator_helper.h" +#include "base/android/jni_int_wrapper.h" + + +// Step 1: Forward declarations (classes). +${CLASS_PATH_DECLARATIONS} + +// Step 2: Forward declarations (methods). + +${FORWARD_DECLARATIONS} + +// Step 3: Method declarations. + +${JNI_NATIVE_METHOD_ARRAY} +${JNI_NATIVE_METHOD} +// Step 4: Main dex and non-main dex registration functions. + +namespace ${NAMESPACE} { + +bool RegisterMainDexNatives(JNIEnv* env) { +${REGISTER_MAIN_DEX_NATIVES} + return true; +} + +bool RegisterNonMainDexNatives(JNIEnv* env) { +${REGISTER_NON_MAIN_DEX_NATIVES} + return true; +} + +} // namespace ${NAMESPACE} + +#endif // ${HEADER_GUARD} +""") + if len(registration_dict['FORWARD_DECLARATIONS']) == 0: + return '' + + return template.substitute(registration_dict) + + +class HeaderGenerator(object): + """Generates an inline header file for JNI registration.""" + + def __init__(self, namespace, fully_qualified_class, natives, jni_params, + main_dex): + self.namespace = namespace + self.natives = natives + self.fully_qualified_class = fully_qualified_class + self.jni_params = jni_params + self.class_name = self.fully_qualified_class.split('/')[-1] + self.main_dex = main_dex + self.helper = jni_generator.HeaderFileGeneratorHelper( + self.class_name, fully_qualified_class) + self.registration_dict = None + + def Generate(self): + self.registration_dict = {'FULL_CLASS_NAME': self.fully_qualified_class} + self._AddClassPathDeclarations() + self._AddForwardDeclaration() + self._AddJNINativeMethodsArrays() + self._AddRegisterNativesCalls() + self._AddRegisterNativesFunctions() + return self.registration_dict + + def _SetDictValue(self, key, value): + self.registration_dict[key] = jni_generator.WrapOutput(value) + + def _AddClassPathDeclarations(self): + classes = self.helper.GetUniqueClasses(self.natives) + self._SetDictValue('CLASS_PATH_DECLARATIONS', + self.helper.GetClassPathLines(classes, declare_only=True)) + + def _AddForwardDeclaration(self): + """Add the content of the forward declaration to the dictionary.""" + template = string.Template("""\ +JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}( + JNIEnv* env, + ${PARAMS_IN_STUB}); +""") + forward_declaration = '' + for native in self.natives: + value = { + 'RETURN': jni_generator.JavaDataTypeToC(native.return_type), + 'STUB_NAME': self.helper.GetStubName(native), + 'PARAMS_IN_STUB': jni_generator.GetParamsInStub(native), + } + forward_declaration += template.substitute(value) + self._SetDictValue('FORWARD_DECLARATIONS', forward_declaration) + + def _AddRegisterNativesCalls(self): + """Add the body of the RegisterNativesImpl method to the dictionary.""" + template = string.Template("""\ + if (!${REGISTER_NAME}(env)) + return false; +""") + value = { + 'REGISTER_NAME': + jni_generator.GetRegistrationFunctionName( + self.fully_qualified_class) + } + register_body = template.substitute(value) + if self.main_dex: + self._SetDictValue('REGISTER_MAIN_DEX_NATIVES', register_body) + else: + self._SetDictValue('REGISTER_NON_MAIN_DEX_NATIVES', register_body) + + def _AddJNINativeMethodsArrays(self): + """Returns the implementation of the array of native methods.""" + template = string.Template("""\ +static const JNINativeMethod kMethods_${JAVA_CLASS}[] = { +${KMETHODS} +}; + +""") + open_namespace = '' + close_namespace = '' + if self.namespace: + parts = self.namespace.split('::') + all_namespaces = ['namespace %s {' % ns for ns in parts] + open_namespace = '\n'.join(all_namespaces) + '\n' + all_namespaces = ['} // namespace %s' % ns for ns in parts] + all_namespaces.reverse() + close_namespace = '\n'.join(all_namespaces) + '\n\n' + + body = self._SubstituteNativeMethods(template) + self._SetDictValue('JNI_NATIVE_METHOD_ARRAY', + ''.join((open_namespace, body, close_namespace))) + + def _GetKMethodsString(self, clazz): + ret = [] + for native in self.natives: + if (native.java_class_name == clazz or + (not native.java_class_name and clazz == self.class_name)): + ret += [self._GetKMethodArrayEntry(native)] + return '\n'.join(ret) + + def _GetKMethodArrayEntry(self, native): + template = string.Template(' { "native${NAME}", ${JNI_SIGNATURE}, ' + + 'reinterpret_cast<void*>(${STUB_NAME}) },') + values = { + 'NAME': native.name, + 'JNI_SIGNATURE': self.jni_params.Signature( + native.params, native.return_type), + 'STUB_NAME': self.helper.GetStubName(native) + } + return template.substitute(values) + + def _SubstituteNativeMethods(self, template): + """Substitutes NAMESPACE, JAVA_CLASS and KMETHODS in the provided + template.""" + ret = [] + all_classes = self.helper.GetUniqueClasses(self.natives) + all_classes[self.class_name] = self.fully_qualified_class + for clazz, full_clazz in all_classes.iteritems(): + kmethods = self._GetKMethodsString(clazz) + namespace_str = '' + if self.namespace: + namespace_str = self.namespace + '::' + if kmethods: + values = {'NAMESPACE': namespace_str, + 'JAVA_CLASS': jni_generator.GetBinaryClassName(full_clazz), + 'KMETHODS': kmethods} + ret += [template.substitute(values)] + if not ret: return '' + return '\n'.join(ret) + + def GetJNINativeMethodsString(self): + """Returns the implementation of the array of native methods.""" + template = string.Template("""\ +static const JNINativeMethod kMethods_${JAVA_CLASS}[] = { +${KMETHODS} + +}; +""") + return self._SubstituteNativeMethods(template) + + def _AddRegisterNativesFunctions(self): + """Returns the code for RegisterNatives.""" + natives = self._GetRegisterNativesImplString() + if not natives: + return '' + template = string.Template("""\ +JNI_REGISTRATION_EXPORT bool ${REGISTER_NAME}(JNIEnv* env) { +${NATIVES}\ + return true; +} + +""") + values = { + 'REGISTER_NAME': jni_generator.GetRegistrationFunctionName( + self.fully_qualified_class), + 'NATIVES': natives + } + self._SetDictValue('JNI_NATIVE_METHOD', template.substitute(values)) + + def _GetRegisterNativesImplString(self): + """Returns the shared implementation for RegisterNatives.""" + template = string.Template("""\ + const int kMethods_${JAVA_CLASS}Size = + arraysize(${NAMESPACE}kMethods_${JAVA_CLASS}); + if (env->RegisterNatives( + ${JAVA_CLASS}_clazz(env), + ${NAMESPACE}kMethods_${JAVA_CLASS}, + kMethods_${JAVA_CLASS}Size) < 0) { + jni_generator::HandleRegistrationError(env, + ${JAVA_CLASS}_clazz(env), + __FILE__); + return false; + } + +""") + return self._SubstituteNativeMethods(template) + + +def main(argv): + arg_parser = argparse.ArgumentParser() + build_utils.AddDepfileOption(arg_parser) + + arg_parser.add_argument('--sources_files', + help='A list of .sources files which contain Java ' + 'file paths. Must be used with --output.') + arg_parser.add_argument('--output', + help='The output file path.') + arg_parser.add_argument('--no_register_java', + default=[], + help='A list of Java files which should be ignored ' + 'by the parser.') + arg_parser.add_argument('--namespace', + default='', + help='Namespace to wrap the registration functions ' + 'into.') + args = arg_parser.parse_args(build_utils.ExpandFileArgs(argv[1:])) + args.sources_files = build_utils.ParseGnList(args.sources_files) + + if not args.sources_files: + print '\nError: Must specify --sources_files.' + return 1 + + java_file_paths = [] + for f in args.sources_files: + # java_file_paths stores each Java file path as a string. + java_file_paths += build_utils.ReadSourcesList(f) + output_file = args.output + GenerateJNIHeader(java_file_paths, output_file, args) + + if args.depfile: + build_utils.WriteDepfile(args.depfile, output_file, + args.sources_files + java_file_paths, + add_pydeps=False) + + +if __name__ == '__main__': + sys.exit(main(sys.argv))
diff --git a/src/base/android/jni_generator/jni_registration_generator.pydeps b/src/base/android/jni_generator/jni_registration_generator.pydeps new file mode 100644 index 0000000..14b0ae2 --- /dev/null +++ b/src/base/android/jni_generator/jni_registration_generator.pydeps
@@ -0,0 +1,8 @@ +# Generated by running: +# build/print_python_deps.py --root base/android/jni_generator --output base/android/jni_generator/jni_registration_generator.pydeps base/android/jni_generator/jni_registration_generator.py +../../../build/android/gyp/util/__init__.py +../../../build/android/gyp/util/build_utils.py +../../../build/android/gyp/util/md5_check.py +../../../build/gn_helpers.py +jni_generator.py +jni_registration_generator.py
diff --git a/src/base/android/jni_generator/sample_entry_point.cc b/src/base/android/jni_generator/sample_entry_point.cc new file mode 100644 index 0000000..86f7e48 --- /dev/null +++ b/src/base/android/jni_generator/sample_entry_point.cc
@@ -0,0 +1,27 @@ +// Copyright 2017 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. + +#include "base/android/jni_android.h" +#include "base/android/jni_generator/sample_jni_registration.h" +#include "base/android/jni_utils.h" + +// This is called by the VM when the shared library is first loaded. +JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { + // By default, all JNI methods are registered. However, since render processes + // don't need very much Java code, we enable selective JNI registration on the + // Java side and only register a subset of JNI methods. + base::android::InitVM(vm); + JNIEnv* env = base::android::AttachCurrentThread(); + + if (!base::android::IsSelectiveJniRegistrationEnabled(env)) { + if (!RegisterNonMainDexNatives(env)) { + return -1; + } + } + + if (!RegisterMainDexNatives(env)) { + return -1; + } + return JNI_VERSION_1_4; +}
diff --git a/src/base/android/jni_generator/sample_for_tests.cc b/src/base/android/jni_generator/sample_for_tests.cc new file mode 100644 index 0000000..890103e --- /dev/null +++ b/src/base/android/jni_generator/sample_for_tests.cc
@@ -0,0 +1,154 @@ +// Copyright (c) 2012 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. + +#include <iostream> + +#include "base/android/jni_generator/sample_for_tests.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/android/scoped_java_ref.h" +// Generated file for JNI bindings from C++ to Java @CalledByNative methods. +// Only to be included in one .cc file. +// Name is based on the java file name: *.java -> jni/*_jni.h +#include "jni/SampleForTests_jni.h" // Generated by JNI. + +using base::android::AttachCurrentThread; +using base::android::ConvertJavaStringToUTF8; +using base::android::ConvertUTF8ToJavaString; +using base::android::ScopedJavaLocalRef; + +namespace base { +namespace android { + +jdouble CPPClass::InnerClass::MethodOtherP0( + JNIEnv* env, + const JavaParamRef<jobject>& caller) { + return 0.0; +} + +CPPClass::CPPClass() { +} + +CPPClass::~CPPClass() { +} + +// static +void CPPClass::Destroy(JNIEnv* env, const JavaParamRef<jobject>& caller) { + delete this; +} + +jint CPPClass::Method(JNIEnv* env, const JavaParamRef<jobject>& caller) { + return 0; +} + +void CPPClass::AddStructB(JNIEnv* env, + const JavaParamRef<jobject>& caller, + const JavaParamRef<jobject>& structb) { + long key = Java_InnerStructB_getKey(env, structb); + std::string value = + ConvertJavaStringToUTF8(env, Java_InnerStructB_getValue(env, structb)); + map_[key] = value; +} + +void CPPClass::IterateAndDoSomethingWithStructB( + JNIEnv* env, + const JavaParamRef<jobject>& caller) { + // Iterate over the elements and do something with them. + for (std::map<long, std::string>::const_iterator it = map_.begin(); + it != map_.end(); ++it) { + long key = it->first; + std::string value = it->second; + std::cout << key << value; + } + map_.clear(); +} + +ScopedJavaLocalRef<jstring> CPPClass::ReturnAString( + JNIEnv* env, + const JavaParamRef<jobject>& caller) { + return ConvertUTF8ToJavaString(env, "test"); +} + +// Static free functions declared and called directly from java. +static jlong JNI_SampleForTests_Init(JNIEnv* env, + const JavaParamRef<jobject>& caller, + const JavaParamRef<jstring>& param) { + return 0; +} + +static jdouble JNI_SampleForTests_GetDoubleFunction( + JNIEnv*, + const JavaParamRef<jobject>&) { + return 0; +} + +static jfloat JNI_SampleForTests_GetFloatFunction(JNIEnv*, + const JavaParamRef<jclass>&) { + return 0; +} + +static void JNI_SampleForTests_SetNonPODDatatype(JNIEnv*, + const JavaParamRef<jobject>&, + const JavaParamRef<jobject>&) { +} + +static ScopedJavaLocalRef<jobject> JNI_SampleForTests_GetNonPODDatatype( + JNIEnv*, + const JavaParamRef<jobject>&) { + return ScopedJavaLocalRef<jobject>(); +} + +static jint JNI_InnerClass_GetInnerIntFunction(JNIEnv*, + const JavaParamRef<jclass>&) { + return 0; +} + +} // namespace android +} // namespace base + +int main() { + // On a regular application, you'd call AttachCurrentThread(). This sample is + // not yet linking with all the libraries. + JNIEnv* env = /* AttachCurrentThread() */ NULL; + + // This is how you call a java static method from C++. + bool foo = base::android::Java_SampleForTests_staticJavaMethod(env); + + // This is how you call a java method from C++. Note that you must have + // obtained the jobject somehow. + ScopedJavaLocalRef<jobject> my_java_object; + int bar = base::android::Java_SampleForTests_javaMethod( + env, my_java_object, 1, 2); + + base::android::Java_SampleForTests_methodWithGenericParams( + env, my_java_object, nullptr, nullptr); + + // This is how you call a java constructor method from C++. + ScopedJavaLocalRef<jobject> my_created_object = + base::android::Java_SampleForTests_Constructor(env, 1, 2); + + std::cout << foo << bar; + + for (int i = 0; i < 10; ++i) { + // Creates a "struct" that will then be used by the java side. + ScopedJavaLocalRef<jobject> struct_a = + base::android::Java_InnerStructA_create( + env, 0, 1, ConvertUTF8ToJavaString(env, "test")); + base::android::Java_SampleForTests_addStructA(env, my_java_object, + struct_a); + } + base::android::Java_SampleForTests_iterateAndDoSomething(env, my_java_object); + base::android::Java_SampleForTests_packagePrivateJavaMethod(env, + my_java_object); + base::android::Java_SampleForTests_methodThatThrowsException(env, + my_java_object); + base::android::Java_SampleForTests_javaMethodWithAnnotatedParam( + env, my_java_object, 42); + + base::android::Java_SampleForTests_getInnerInterface(env); + base::android::Java_SampleForTests_getInnerEnum(env); + + return 0; +}
diff --git a/src/base/android/jni_generator/sample_for_tests.h b/src/base/android/jni_generator/sample_for_tests.h new file mode 100644 index 0000000..bb7254f --- /dev/null +++ b/src/base/android/jni_generator/sample_for_tests.h
@@ -0,0 +1,115 @@ +// 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_ANDROID_JNI_GENERATOR_SAMPLE_FOR_TESTS_H_ +#define BASE_ANDROID_JNI_GENERATOR_SAMPLE_FOR_TESTS_H_ + +#include <jni.h> +#include <map> +#include <string> + +#include "base/android/jni_android.h" +#include "starboard/types.h" + +namespace base { +namespace android { + +// This file is used to: +// - document the best practices and guidelines on JNI usage. +// - ensure sample_for_tests_jni.h compiles and the functions declared in it +// as expected. +// +// Methods are called directly from Java. More documentation in +// SampleForTests.java. See BUILD.gn for the build rules necessary for JNI +// to be used in an APK. +// +// For C++ to access Java methods: +// - GN Build must be configured to generate bindings: +// # Add import at top of file: +// if (is_android) { +// import("//build/config/android/rules.gni") # For generate_jni(). +// } +// # ... +// # An example target that will rely on JNI: +// component("foo") { +// # ... normal sources, defines, deps. +// # For each jni generated .java -> .h header file in jni_headers +// # target there will be a single .cc file here that includes it. +// # +// # Add a dep for JNI: +// if (is_android) { +// deps += [ ":foo_jni" ] +// } +// } +// # ... +// # Create target for JNI: +// if (is_android) { +// generate_jni("jni_headers") { +// sources = [ +// "java/src/org/chromium/example/jni_generator/SampleForTests.java", +// ] +// jni_package = "foo" +// } +// android_library("java") { +// java_files = [ +// "java/src/org/chromium/example/jni_generator/SampleForTests.java", +// "java/src/org/chromium/example/jni_generator/NonJniFile.java", +// ] +// } +// } +// The build rules above are generally that that's needed when adding new +// JNI methods/files. For a full GN example, see +// base/android/jni_generator/BUILD.gn +// +// For C++ methods to be exposed to Java: +// - The Java class must be part of an android_apk target that depends on +// a generate_jni_registration target. This generate_jni_registration target +// automatically generates all necessary registration functions. The +// generated header file exposes two functions that should be called when a +// library is first loaded: +// 1) RegisterMainDexNatives() +// - Registers all methods that are used outside the browser process +// 2) RegisterNonMainDexNatives() +// - Registers all methods used in the browser process +// +class CPPClass { + public: + CPPClass(); + ~CPPClass(); + + // Java @CalledByNative methods implicitly available to C++ via the _jni.h + // file included in the .cc file. + + class InnerClass { + public: + jdouble MethodOtherP0(JNIEnv* env, + const base::android::JavaParamRef<jobject>& caller); + }; + + void Destroy(JNIEnv* env, const base::android::JavaParamRef<jobject>& caller); + + jint Method(JNIEnv* env, const base::android::JavaParamRef<jobject>& caller); + + void AddStructB(JNIEnv* env, + const base::android::JavaParamRef<jobject>& caller, + const base::android::JavaParamRef<jobject>& structb); + + void IterateAndDoSomethingWithStructB( + JNIEnv* env, + const base::android::JavaParamRef<jobject>& caller); + + base::android::ScopedJavaLocalRef<jstring> ReturnAString( + JNIEnv* env, + const base::android::JavaParamRef<jobject>& caller); + + private: + std::map<long, std::string> map_; + + DISALLOW_COPY_AND_ASSIGN(CPPClass); +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_JNI_GENERATOR_SAMPLE_FOR_TESTS_H_
diff --git a/src/base/android/jni_generator/testCalledByNatives.golden b/src/base/android/jni_generator/testCalledByNatives.golden new file mode 100644 index 0000000..09ecb6c --- /dev/null +++ b/src/base/android/jni_generator/testCalledByNatives.golden
@@ -0,0 +1,419 @@ +// Copyright 2014 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. + + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// org/chromium/TestJni + +#ifndef org_chromium_TestJni_JNI +#define org_chromium_TestJni_JNI + +#include <jni.h> + +#include "base/android/jni_generator/jni_generator_helper.h" + + +// Step 1: Forward declarations. + +JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_TestJni[]; +const char kClassPath_org_chromium_TestJni[] = "org/chromium/TestJni"; + +JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_TestJni_00024InfoBar[]; +const char kClassPath_org_chromium_TestJni_00024InfoBar[] = "org/chromium/TestJni$InfoBar"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +JNI_REGISTRATION_EXPORT std::atomic<jclass> g_org_chromium_TestJni_clazz(nullptr); +#ifndef org_chromium_TestJni_clazz_defined +#define org_chromium_TestJni_clazz_defined +inline jclass org_chromium_TestJni_clazz(JNIEnv* env) { + return base::android::LazyGetClass(env, kClassPath_org_chromium_TestJni, + &g_org_chromium_TestJni_clazz); +} +#endif +// Leaking this jclass as we cannot use LazyInstance from some threads. +JNI_REGISTRATION_EXPORT std::atomic<jclass> g_org_chromium_TestJni_00024InfoBar_clazz(nullptr); +#ifndef org_chromium_TestJni_00024InfoBar_clazz_defined +#define org_chromium_TestJni_00024InfoBar_clazz_defined +inline jclass org_chromium_TestJni_00024InfoBar_clazz(JNIEnv* env) { + return base::android::LazyGetClass(env, kClassPath_org_chromium_TestJni_00024InfoBar, + &g_org_chromium_TestJni_00024InfoBar_clazz); +} +#endif + + +// Step 2: Constants (optional). + + +// Step 3: Method stubs. + +static std::atomic<jmethodID> g_org_chromium_TestJni_showConfirmInfoBar(nullptr); +static base::android::ScopedJavaLocalRef<jobject> Java_TestJni_showConfirmInfoBar(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper nativeInfoBar, + const base::android::JavaRef<jstring>& buttonOk, + const base::android::JavaRef<jstring>& buttonCancel, + const base::android::JavaRef<jstring>& title, + const base::android::JavaRef<jobject>& icon) { + CHECK_CLAZZ(env, obj.obj(), + org_chromium_TestJni_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_TestJni_clazz(env), + "showConfirmInfoBar", +"(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Landroid/graphics/Bitmap;)Lorg/chromium/Foo$InnerClass;", + &g_org_chromium_TestJni_showConfirmInfoBar); + + jobject ret = + env->CallObjectMethod(obj.obj(), + method_id, as_jint(nativeInfoBar), buttonOk.obj(), buttonCancel.obj(), title.obj(), + icon.obj()); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jobject>(env, ret); +} + +static std::atomic<jmethodID> g_org_chromium_TestJni_showAutoLoginInfoBar(nullptr); +static base::android::ScopedJavaLocalRef<jobject> Java_TestJni_showAutoLoginInfoBar(JNIEnv* env, + const base::android::JavaRef<jobject>& obj, JniIntWrapper nativeInfoBar, + const base::android::JavaRef<jstring>& realm, + const base::android::JavaRef<jstring>& account, + const base::android::JavaRef<jstring>& args) { + CHECK_CLAZZ(env, obj.obj(), + org_chromium_TestJni_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_TestJni_clazz(env), + "showAutoLoginInfoBar", + "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lorg/chromium/Foo$InnerClass;", + &g_org_chromium_TestJni_showAutoLoginInfoBar); + + jobject ret = + env->CallObjectMethod(obj.obj(), + method_id, as_jint(nativeInfoBar), realm.obj(), account.obj(), args.obj()); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jobject>(env, ret); +} + +static std::atomic<jmethodID> g_org_chromium_TestJni_00024InfoBar_dismiss(nullptr); +static void Java_InfoBar_dismiss(JNIEnv* env, const base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + org_chromium_TestJni_00024InfoBar_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_TestJni_00024InfoBar_clazz(env), + "dismiss", + "()V", + &g_org_chromium_TestJni_00024InfoBar_dismiss); + + env->CallVoidMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); +} + +static std::atomic<jmethodID> g_org_chromium_TestJni_shouldShowAutoLogin(nullptr); +static jboolean Java_TestJni_shouldShowAutoLogin(JNIEnv* env, const base::android::JavaRef<jobject>& + view, + const base::android::JavaRef<jstring>& realm, + const base::android::JavaRef<jstring>& account, + const base::android::JavaRef<jstring>& args) { + CHECK_CLAZZ(env, org_chromium_TestJni_clazz(env), + org_chromium_TestJni_clazz(env), false); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, org_chromium_TestJni_clazz(env), + "shouldShowAutoLogin", + "(Landroid/view/View;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z", + &g_org_chromium_TestJni_shouldShowAutoLogin); + + jboolean ret = + env->CallStaticBooleanMethod(org_chromium_TestJni_clazz(env), + method_id, view.obj(), realm.obj(), account.obj(), args.obj()); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_org_chromium_TestJni_openUrl(nullptr); +static base::android::ScopedJavaLocalRef<jobject> Java_TestJni_openUrl(JNIEnv* env, const + base::android::JavaRef<jstring>& url) { + CHECK_CLAZZ(env, org_chromium_TestJni_clazz(env), + org_chromium_TestJni_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, org_chromium_TestJni_clazz(env), + "openUrl", + "(Ljava/lang/String;)Ljava/io/InputStream;", + &g_org_chromium_TestJni_openUrl); + + jobject ret = + env->CallStaticObjectMethod(org_chromium_TestJni_clazz(env), + method_id, url.obj()); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jobject>(env, ret); +} + +static std::atomic<jmethodID> g_org_chromium_TestJni_activateHardwareAcceleration(nullptr); +static void Java_TestJni_activateHardwareAcceleration(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, jboolean activated, + JniIntWrapper iPid, + JniIntWrapper iType, + JniIntWrapper iPrimaryID, + JniIntWrapper iSecondaryID) { + CHECK_CLAZZ(env, obj.obj(), + org_chromium_TestJni_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_TestJni_clazz(env), + "activateHardwareAcceleration", + "(ZIIII)V", + &g_org_chromium_TestJni_activateHardwareAcceleration); + + env->CallVoidMethod(obj.obj(), + method_id, activated, as_jint(iPid), as_jint(iType), as_jint(iPrimaryID), + as_jint(iSecondaryID)); + jni_generator::CheckException(env); +} + +static std::atomic<jmethodID> g_org_chromium_TestJni_updateStatus(nullptr); +static jint Java_TestJni_updateStatus(JNIEnv* env, JniIntWrapper status) { + CHECK_CLAZZ(env, org_chromium_TestJni_clazz(env), + org_chromium_TestJni_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, org_chromium_TestJni_clazz(env), + "updateStatus", + "(I)I", + &g_org_chromium_TestJni_updateStatus); + + jint ret = + env->CallStaticIntMethod(org_chromium_TestJni_clazz(env), + method_id, as_jint(status)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_org_chromium_TestJni_uncheckedCall(nullptr); +static void Java_TestJni_uncheckedCall(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + JniIntWrapper iParam) { + CHECK_CLAZZ(env, obj.obj(), + org_chromium_TestJni_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_TestJni_clazz(env), + "uncheckedCall", + "(I)V", + &g_org_chromium_TestJni_uncheckedCall); + + env->CallVoidMethod(obj.obj(), + method_id, as_jint(iParam)); +} + +static std::atomic<jmethodID> g_org_chromium_TestJni_returnByteArray(nullptr); +static base::android::ScopedJavaLocalRef<jbyteArray> Java_TestJni_returnByteArray(JNIEnv* env, const + base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + org_chromium_TestJni_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_TestJni_clazz(env), + "returnByteArray", + "()[B", + &g_org_chromium_TestJni_returnByteArray); + + jbyteArray ret = + static_cast<jbyteArray>(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jbyteArray>(env, ret); +} + +static std::atomic<jmethodID> g_org_chromium_TestJni_returnBooleanArray(nullptr); +static base::android::ScopedJavaLocalRef<jbooleanArray> Java_TestJni_returnBooleanArray(JNIEnv* env, + const base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + org_chromium_TestJni_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_TestJni_clazz(env), + "returnBooleanArray", + "()[Z", + &g_org_chromium_TestJni_returnBooleanArray); + + jbooleanArray ret = + static_cast<jbooleanArray>(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jbooleanArray>(env, ret); +} + +static std::atomic<jmethodID> g_org_chromium_TestJni_returnCharArray(nullptr); +static base::android::ScopedJavaLocalRef<jcharArray> Java_TestJni_returnCharArray(JNIEnv* env, const + base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + org_chromium_TestJni_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_TestJni_clazz(env), + "returnCharArray", + "()[C", + &g_org_chromium_TestJni_returnCharArray); + + jcharArray ret = + static_cast<jcharArray>(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jcharArray>(env, ret); +} + +static std::atomic<jmethodID> g_org_chromium_TestJni_returnShortArray(nullptr); +static base::android::ScopedJavaLocalRef<jshortArray> Java_TestJni_returnShortArray(JNIEnv* env, + const base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + org_chromium_TestJni_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_TestJni_clazz(env), + "returnShortArray", + "()[S", + &g_org_chromium_TestJni_returnShortArray); + + jshortArray ret = + static_cast<jshortArray>(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jshortArray>(env, ret); +} + +static std::atomic<jmethodID> g_org_chromium_TestJni_returnIntArray(nullptr); +static base::android::ScopedJavaLocalRef<jintArray> Java_TestJni_returnIntArray(JNIEnv* env, const + base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + org_chromium_TestJni_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_TestJni_clazz(env), + "returnIntArray", + "()[I", + &g_org_chromium_TestJni_returnIntArray); + + jintArray ret = + static_cast<jintArray>(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jintArray>(env, ret); +} + +static std::atomic<jmethodID> g_org_chromium_TestJni_returnLongArray(nullptr); +static base::android::ScopedJavaLocalRef<jlongArray> Java_TestJni_returnLongArray(JNIEnv* env, const + base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + org_chromium_TestJni_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_TestJni_clazz(env), + "returnLongArray", + "()[J", + &g_org_chromium_TestJni_returnLongArray); + + jlongArray ret = + static_cast<jlongArray>(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jlongArray>(env, ret); +} + +static std::atomic<jmethodID> g_org_chromium_TestJni_returnDoubleArray(nullptr); +static base::android::ScopedJavaLocalRef<jdoubleArray> Java_TestJni_returnDoubleArray(JNIEnv* env, + const base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + org_chromium_TestJni_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_TestJni_clazz(env), + "returnDoubleArray", + "()[D", + &g_org_chromium_TestJni_returnDoubleArray); + + jdoubleArray ret = + static_cast<jdoubleArray>(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jdoubleArray>(env, ret); +} + +static std::atomic<jmethodID> g_org_chromium_TestJni_returnObjectArray(nullptr); +static base::android::ScopedJavaLocalRef<jobjectArray> Java_TestJni_returnObjectArray(JNIEnv* env, + const base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + org_chromium_TestJni_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_TestJni_clazz(env), + "returnObjectArray", + "()[Ljava/lang/Object;", + &g_org_chromium_TestJni_returnObjectArray); + + jobjectArray ret = + static_cast<jobjectArray>(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jobjectArray>(env, ret); +} + +static std::atomic<jmethodID> g_org_chromium_TestJni_returnArrayOfByteArray(nullptr); +static base::android::ScopedJavaLocalRef<jobjectArray> Java_TestJni_returnArrayOfByteArray(JNIEnv* + env, const base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + org_chromium_TestJni_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_TestJni_clazz(env), + "returnArrayOfByteArray", + "()[[B", + &g_org_chromium_TestJni_returnArrayOfByteArray); + + jobjectArray ret = + static_cast<jobjectArray>(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jobjectArray>(env, ret); +} + +static std::atomic<jmethodID> g_org_chromium_TestJni_getCompressFormat(nullptr); +static base::android::ScopedJavaLocalRef<jobject> Java_TestJni_getCompressFormat(JNIEnv* env, const + base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + org_chromium_TestJni_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_TestJni_clazz(env), + "getCompressFormat", + "()Landroid/graphics/Bitmap$CompressFormat;", + &g_org_chromium_TestJni_getCompressFormat); + + jobject ret = + env->CallObjectMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jobject>(env, ret); +} + +static std::atomic<jmethodID> g_org_chromium_TestJni_getCompressFormatList(nullptr); +static base::android::ScopedJavaLocalRef<jobject> Java_TestJni_getCompressFormatList(JNIEnv* env, + const base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + org_chromium_TestJni_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_TestJni_clazz(env), + "getCompressFormatList", + "()Ljava/util/List;", + &g_org_chromium_TestJni_getCompressFormatList); + + jobject ret = + env->CallObjectMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jobject>(env, ret); +} + +#endif // org_chromium_TestJni_JNI
diff --git a/src/base/android/jni_generator/testConstantsFromJavaP.golden b/src/base/android/jni_generator/testConstantsFromJavaP.golden new file mode 100644 index 0000000..043685f --- /dev/null +++ b/src/base/android/jni_generator/testConstantsFromJavaP.golden
@@ -0,0 +1,2061 @@ +// Copyright 2014 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. + + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// android/view/MotionEvent + +#ifndef android_view_MotionEvent_JNI +#define android_view_MotionEvent_JNI + +#include <jni.h> + +#include "base/android/jni_generator/jni_generator_helper.h" + + +// Step 1: Forward declarations. + +JNI_REGISTRATION_EXPORT extern const char kClassPath_android_view_MotionEvent[]; +const char kClassPath_android_view_MotionEvent[] = "android/view/MotionEvent"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +JNI_REGISTRATION_EXPORT std::atomic<jclass> g_android_view_MotionEvent_clazz(nullptr); +#ifndef android_view_MotionEvent_clazz_defined +#define android_view_MotionEvent_clazz_defined +inline jclass android_view_MotionEvent_clazz(JNIEnv* env) { + return base::android::LazyGetClass(env, kClassPath_android_view_MotionEvent, + &g_android_view_MotionEvent_clazz); +} +#endif + + +// Step 2: Constants (optional). + +namespace JNI_MotionEvent { + +enum Java_MotionEvent_constant_fields { + INVALID_POINTER_ID = -1, + ACTION_MASK = 255, + ACTION_DOWN = 0, + ACTION_UP = 1, + ACTION_MOVE = 2, + ACTION_CANCEL = 3, + ACTION_OUTSIDE = 4, + ACTION_POINTER_DOWN = 5, + ACTION_POINTER_UP = 6, + ACTION_HOVER_MOVE = 7, + ACTION_SCROLL = 8, + ACTION_HOVER_ENTER = 9, + ACTION_HOVER_EXIT = 10, + ACTION_POINTER_INDEX_MASK = 65280, + ACTION_POINTER_INDEX_SHIFT = 8, + ACTION_POINTER_1_DOWN = 5, + ACTION_POINTER_2_DOWN = 261, + ACTION_POINTER_3_DOWN = 517, + ACTION_POINTER_1_UP = 6, + ACTION_POINTER_2_UP = 262, + ACTION_POINTER_3_UP = 518, + ACTION_POINTER_ID_MASK = 65280, + ACTION_POINTER_ID_SHIFT = 8, + FLAG_WINDOW_IS_OBSCURED = 1, + EDGE_TOP = 1, + EDGE_BOTTOM = 2, + EDGE_LEFT = 4, + EDGE_RIGHT = 8, + AXIS_X = 0, + AXIS_Y = 1, + AXIS_PRESSURE = 2, + AXIS_SIZE = 3, + AXIS_TOUCH_MAJOR = 4, + AXIS_TOUCH_MINOR = 5, + AXIS_TOOL_MAJOR = 6, + AXIS_TOOL_MINOR = 7, + AXIS_ORIENTATION = 8, + AXIS_VSCROLL = 9, + AXIS_HSCROLL = 10, + AXIS_Z = 11, + AXIS_RX = 12, + AXIS_RY = 13, + AXIS_RZ = 14, + AXIS_HAT_X = 15, + AXIS_HAT_Y = 16, + AXIS_LTRIGGER = 17, + AXIS_RTRIGGER = 18, + AXIS_THROTTLE = 19, + AXIS_RUDDER = 20, + AXIS_WHEEL = 21, + AXIS_GAS = 22, + AXIS_BRAKE = 23, + AXIS_DISTANCE = 24, + AXIS_TILT = 25, + AXIS_GENERIC_1 = 32, + AXIS_GENERIC_2 = 33, + AXIS_GENERIC_3 = 34, + AXIS_GENERIC_4 = 35, + AXIS_GENERIC_5 = 36, + AXIS_GENERIC_6 = 37, + AXIS_GENERIC_7 = 38, + AXIS_GENERIC_8 = 39, + AXIS_GENERIC_9 = 40, + AXIS_GENERIC_10 = 41, + AXIS_GENERIC_11 = 42, + AXIS_GENERIC_12 = 43, + AXIS_GENERIC_13 = 44, + AXIS_GENERIC_14 = 45, + AXIS_GENERIC_15 = 46, + AXIS_GENERIC_16 = 47, + BUTTON_PRIMARY = 1, + BUTTON_SECONDARY = 2, + BUTTON_TERTIARY = 4, + BUTTON_BACK = 8, + BUTTON_FORWARD = 16, + TOOL_TYPE_UNKNOWN = 0, + TOOL_TYPE_FINGER = 1, + TOOL_TYPE_STYLUS = 2, + TOOL_TYPE_MOUSE = 3, + TOOL_TYPE_ERASER = 4, +}; + + +} // namespace JNI_MotionEvent +// Step 3: Method stubs. +namespace JNI_MotionEvent { + + +static std::atomic<jmethodID> g_android_view_MotionEvent_finalize(nullptr); +static void Java_MotionEvent_finalize(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + __attribute__ ((unused)); +static void Java_MotionEvent_finalize(JNIEnv* env, const base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "finalize", + "()V", + &g_android_view_MotionEvent_finalize); + + env->CallVoidMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); +} + +static std::atomic<jmethodID> + g_android_view_MotionEvent_obtainAVME_J_J_I_I_LAVMEPP_LAVMEPC_I_I_F_F_I_I_I_I(nullptr); +static base::android::ScopedJavaLocalRef<jobject> + Java_MotionEvent_obtainAVME_J_J_I_I_LAVMEPP_LAVMEPC_I_I_F_F_I_I_I_I(JNIEnv* env, jlong p0, + jlong p1, + JniIntWrapper p2, + JniIntWrapper p3, + const base::android::JavaRef<jobjectArray>& p4, + const base::android::JavaRef<jobjectArray>& p5, + JniIntWrapper p6, + JniIntWrapper p7, + jfloat p8, + jfloat p9, + JniIntWrapper p10, + JniIntWrapper p11, + JniIntWrapper p12, + JniIntWrapper p13) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef<jobject> + Java_MotionEvent_obtainAVME_J_J_I_I_LAVMEPP_LAVMEPC_I_I_F_F_I_I_I_I(JNIEnv* env, jlong p0, + jlong p1, + JniIntWrapper p2, + JniIntWrapper p3, + const base::android::JavaRef<jobjectArray>& p4, + const base::android::JavaRef<jobjectArray>& p5, + JniIntWrapper p6, + JniIntWrapper p7, + jfloat p8, + jfloat p9, + JniIntWrapper p10, + JniIntWrapper p11, + JniIntWrapper p12, + JniIntWrapper p13) { + CHECK_CLAZZ(env, android_view_MotionEvent_clazz(env), + android_view_MotionEvent_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, android_view_MotionEvent_clazz(env), + "obtain", +"(JJII[Landroid/view/MotionEvent$PointerProperties;[Landroid/view/MotionEvent$PointerCoords;IIFFIIII)Landroid/view/MotionEvent;", + &g_android_view_MotionEvent_obtainAVME_J_J_I_I_LAVMEPP_LAVMEPC_I_I_F_F_I_I_I_I); + + jobject ret = + env->CallStaticObjectMethod(android_view_MotionEvent_clazz(env), + method_id, p0, p1, as_jint(p2), as_jint(p3), p4.obj(), p5.obj(), as_jint(p6), as_jint(p7), + p8, p9, as_jint(p10), as_jint(p11), as_jint(p12), as_jint(p13)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jobject>(env, ret); +} + +static std::atomic<jmethodID> + g_android_view_MotionEvent_obtainAVME_J_J_I_I_AI_LAVMEPC_I_F_F_I_I_I_I(nullptr); +static base::android::ScopedJavaLocalRef<jobject> + Java_MotionEvent_obtainAVME_J_J_I_I_AI_LAVMEPC_I_F_F_I_I_I_I(JNIEnv* env, jlong p0, + jlong p1, + JniIntWrapper p2, + JniIntWrapper p3, + const base::android::JavaRef<jintArray>& p4, + const base::android::JavaRef<jobjectArray>& p5, + JniIntWrapper p6, + jfloat p7, + jfloat p8, + JniIntWrapper p9, + JniIntWrapper p10, + JniIntWrapper p11, + JniIntWrapper p12) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef<jobject> + Java_MotionEvent_obtainAVME_J_J_I_I_AI_LAVMEPC_I_F_F_I_I_I_I(JNIEnv* env, jlong p0, + jlong p1, + JniIntWrapper p2, + JniIntWrapper p3, + const base::android::JavaRef<jintArray>& p4, + const base::android::JavaRef<jobjectArray>& p5, + JniIntWrapper p6, + jfloat p7, + jfloat p8, + JniIntWrapper p9, + JniIntWrapper p10, + JniIntWrapper p11, + JniIntWrapper p12) { + CHECK_CLAZZ(env, android_view_MotionEvent_clazz(env), + android_view_MotionEvent_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, android_view_MotionEvent_clazz(env), + "obtain", + "(JJII[I[Landroid/view/MotionEvent$PointerCoords;IFFIIII)Landroid/view/MotionEvent;", + &g_android_view_MotionEvent_obtainAVME_J_J_I_I_AI_LAVMEPC_I_F_F_I_I_I_I); + + jobject ret = + env->CallStaticObjectMethod(android_view_MotionEvent_clazz(env), + method_id, p0, p1, as_jint(p2), as_jint(p3), p4.obj(), p5.obj(), as_jint(p6), p7, p8, + as_jint(p9), as_jint(p10), as_jint(p11), as_jint(p12)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jobject>(env, ret); +} + +static std::atomic<jmethodID> + g_android_view_MotionEvent_obtainAVME_J_J_I_F_F_F_F_I_F_F_I_I(nullptr); +static base::android::ScopedJavaLocalRef<jobject> + Java_MotionEvent_obtainAVME_J_J_I_F_F_F_F_I_F_F_I_I(JNIEnv* env, jlong p0, + jlong p1, + JniIntWrapper p2, + jfloat p3, + jfloat p4, + jfloat p5, + jfloat p6, + JniIntWrapper p7, + jfloat p8, + jfloat p9, + JniIntWrapper p10, + JniIntWrapper p11) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef<jobject> + Java_MotionEvent_obtainAVME_J_J_I_F_F_F_F_I_F_F_I_I(JNIEnv* env, jlong p0, + jlong p1, + JniIntWrapper p2, + jfloat p3, + jfloat p4, + jfloat p5, + jfloat p6, + JniIntWrapper p7, + jfloat p8, + jfloat p9, + JniIntWrapper p10, + JniIntWrapper p11) { + CHECK_CLAZZ(env, android_view_MotionEvent_clazz(env), + android_view_MotionEvent_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, android_view_MotionEvent_clazz(env), + "obtain", + "(JJIFFFFIFFII)Landroid/view/MotionEvent;", + &g_android_view_MotionEvent_obtainAVME_J_J_I_F_F_F_F_I_F_F_I_I); + + jobject ret = + env->CallStaticObjectMethod(android_view_MotionEvent_clazz(env), + method_id, p0, p1, as_jint(p2), p3, p4, p5, p6, as_jint(p7), p8, p9, as_jint(p10), + as_jint(p11)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jobject>(env, ret); +} + +static std::atomic<jmethodID> + g_android_view_MotionEvent_obtainAVME_J_J_I_I_F_F_F_F_I_F_F_I_I(nullptr); +static base::android::ScopedJavaLocalRef<jobject> + Java_MotionEvent_obtainAVME_J_J_I_I_F_F_F_F_I_F_F_I_I(JNIEnv* env, jlong p0, + jlong p1, + JniIntWrapper p2, + JniIntWrapper p3, + jfloat p4, + jfloat p5, + jfloat p6, + jfloat p7, + JniIntWrapper p8, + jfloat p9, + jfloat p10, + JniIntWrapper p11, + JniIntWrapper p12) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef<jobject> + Java_MotionEvent_obtainAVME_J_J_I_I_F_F_F_F_I_F_F_I_I(JNIEnv* env, jlong p0, + jlong p1, + JniIntWrapper p2, + JniIntWrapper p3, + jfloat p4, + jfloat p5, + jfloat p6, + jfloat p7, + JniIntWrapper p8, + jfloat p9, + jfloat p10, + JniIntWrapper p11, + JniIntWrapper p12) { + CHECK_CLAZZ(env, android_view_MotionEvent_clazz(env), + android_view_MotionEvent_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, android_view_MotionEvent_clazz(env), + "obtain", + "(JJIIFFFFIFFII)Landroid/view/MotionEvent;", + &g_android_view_MotionEvent_obtainAVME_J_J_I_I_F_F_F_F_I_F_F_I_I); + + jobject ret = + env->CallStaticObjectMethod(android_view_MotionEvent_clazz(env), + method_id, p0, p1, as_jint(p2), as_jint(p3), p4, p5, p6, p7, as_jint(p8), p9, p10, + as_jint(p11), as_jint(p12)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jobject>(env, ret); +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_obtainAVME_J_J_I_F_F_I(nullptr); +static base::android::ScopedJavaLocalRef<jobject> Java_MotionEvent_obtainAVME_J_J_I_F_F_I(JNIEnv* + env, jlong p0, + jlong p1, + JniIntWrapper p2, + jfloat p3, + jfloat p4, + JniIntWrapper p5) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef<jobject> Java_MotionEvent_obtainAVME_J_J_I_F_F_I(JNIEnv* + env, jlong p0, + jlong p1, + JniIntWrapper p2, + jfloat p3, + jfloat p4, + JniIntWrapper p5) { + CHECK_CLAZZ(env, android_view_MotionEvent_clazz(env), + android_view_MotionEvent_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, android_view_MotionEvent_clazz(env), + "obtain", + "(JJIFFI)Landroid/view/MotionEvent;", + &g_android_view_MotionEvent_obtainAVME_J_J_I_F_F_I); + + jobject ret = + env->CallStaticObjectMethod(android_view_MotionEvent_clazz(env), + method_id, p0, p1, as_jint(p2), p3, p4, as_jint(p5)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jobject>(env, ret); +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_obtainAVME_AVME(nullptr); +static base::android::ScopedJavaLocalRef<jobject> Java_MotionEvent_obtainAVME_AVME(JNIEnv* env, + const base::android::JavaRef<jobject>& p0) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef<jobject> Java_MotionEvent_obtainAVME_AVME(JNIEnv* env, + const base::android::JavaRef<jobject>& p0) { + CHECK_CLAZZ(env, android_view_MotionEvent_clazz(env), + android_view_MotionEvent_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, android_view_MotionEvent_clazz(env), + "obtain", + "(Landroid/view/MotionEvent;)Landroid/view/MotionEvent;", + &g_android_view_MotionEvent_obtainAVME_AVME); + + jobject ret = + env->CallStaticObjectMethod(android_view_MotionEvent_clazz(env), + method_id, p0.obj()); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jobject>(env, ret); +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_obtainNoHistory(nullptr); +static base::android::ScopedJavaLocalRef<jobject> Java_MotionEvent_obtainNoHistory(JNIEnv* env, + const base::android::JavaRef<jobject>& p0) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef<jobject> Java_MotionEvent_obtainNoHistory(JNIEnv* env, + const base::android::JavaRef<jobject>& p0) { + CHECK_CLAZZ(env, android_view_MotionEvent_clazz(env), + android_view_MotionEvent_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, android_view_MotionEvent_clazz(env), + "obtainNoHistory", + "(Landroid/view/MotionEvent;)Landroid/view/MotionEvent;", + &g_android_view_MotionEvent_obtainNoHistory); + + jobject ret = + env->CallStaticObjectMethod(android_view_MotionEvent_clazz(env), + method_id, p0.obj()); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jobject>(env, ret); +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_recycle(nullptr); +static void Java_MotionEvent_recycle(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + __attribute__ ((unused)); +static void Java_MotionEvent_recycle(JNIEnv* env, const base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "recycle", + "()V", + &g_android_view_MotionEvent_recycle); + + env->CallVoidMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getDeviceId(nullptr); +static jint Java_MotionEvent_getDeviceId(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + __attribute__ ((unused)); +static jint Java_MotionEvent_getDeviceId(JNIEnv* env, const base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getDeviceId", + "()I", + &g_android_view_MotionEvent_getDeviceId); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getSource(nullptr); +static jint Java_MotionEvent_getSource(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + __attribute__ ((unused)); +static jint Java_MotionEvent_getSource(JNIEnv* env, const base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getSource", + "()I", + &g_android_view_MotionEvent_getSource); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_setSource(nullptr); +static void Java_MotionEvent_setSource(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + JniIntWrapper p0) __attribute__ ((unused)); +static void Java_MotionEvent_setSource(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "setSource", + "(I)V", + &g_android_view_MotionEvent_setSource); + + env->CallVoidMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getAction(nullptr); +static jint Java_MotionEvent_getAction(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + __attribute__ ((unused)); +static jint Java_MotionEvent_getAction(JNIEnv* env, const base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getAction", + "()I", + &g_android_view_MotionEvent_getAction); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getActionMasked(nullptr); +static jint Java_MotionEvent_getActionMasked(JNIEnv* env, const base::android::JavaRef<jobject>& + obj) __attribute__ ((unused)); +static jint Java_MotionEvent_getActionMasked(JNIEnv* env, const base::android::JavaRef<jobject>& + obj) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getActionMasked", + "()I", + &g_android_view_MotionEvent_getActionMasked); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getActionIndex(nullptr); +static jint Java_MotionEvent_getActionIndex(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + __attribute__ ((unused)); +static jint Java_MotionEvent_getActionIndex(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getActionIndex", + "()I", + &g_android_view_MotionEvent_getActionIndex); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getFlags(nullptr); +static jint Java_MotionEvent_getFlags(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + __attribute__ ((unused)); +static jint Java_MotionEvent_getFlags(JNIEnv* env, const base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getFlags", + "()I", + &g_android_view_MotionEvent_getFlags); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getDownTime(nullptr); +static jlong Java_MotionEvent_getDownTime(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + __attribute__ ((unused)); +static jlong Java_MotionEvent_getDownTime(JNIEnv* env, const base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getDownTime", + "()J", + &g_android_view_MotionEvent_getDownTime); + + jlong ret = + env->CallLongMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getEventTime(nullptr); +static jlong Java_MotionEvent_getEventTime(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + __attribute__ ((unused)); +static jlong Java_MotionEvent_getEventTime(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getEventTime", + "()J", + &g_android_view_MotionEvent_getEventTime); + + jlong ret = + env->CallLongMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getXF(nullptr); +static jfloat Java_MotionEvent_getXF(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + __attribute__ ((unused)); +static jfloat Java_MotionEvent_getXF(JNIEnv* env, const base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getX", + "()F", + &g_android_view_MotionEvent_getXF); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getYF(nullptr); +static jfloat Java_MotionEvent_getYF(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + __attribute__ ((unused)); +static jfloat Java_MotionEvent_getYF(JNIEnv* env, const base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getY", + "()F", + &g_android_view_MotionEvent_getYF); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getPressureF(nullptr); +static jfloat Java_MotionEvent_getPressureF(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + __attribute__ ((unused)); +static jfloat Java_MotionEvent_getPressureF(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getPressure", + "()F", + &g_android_view_MotionEvent_getPressureF); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getSizeF(nullptr); +static jfloat Java_MotionEvent_getSizeF(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + __attribute__ ((unused)); +static jfloat Java_MotionEvent_getSizeF(JNIEnv* env, const base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getSize", + "()F", + &g_android_view_MotionEvent_getSizeF); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getTouchMajorF(nullptr); +static jfloat Java_MotionEvent_getTouchMajorF(JNIEnv* env, const base::android::JavaRef<jobject>& + obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getTouchMajorF(JNIEnv* env, const base::android::JavaRef<jobject>& + obj) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getTouchMajor", + "()F", + &g_android_view_MotionEvent_getTouchMajorF); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getTouchMinorF(nullptr); +static jfloat Java_MotionEvent_getTouchMinorF(JNIEnv* env, const base::android::JavaRef<jobject>& + obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getTouchMinorF(JNIEnv* env, const base::android::JavaRef<jobject>& + obj) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getTouchMinor", + "()F", + &g_android_view_MotionEvent_getTouchMinorF); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getToolMajorF(nullptr); +static jfloat Java_MotionEvent_getToolMajorF(JNIEnv* env, const base::android::JavaRef<jobject>& + obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getToolMajorF(JNIEnv* env, const base::android::JavaRef<jobject>& + obj) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getToolMajor", + "()F", + &g_android_view_MotionEvent_getToolMajorF); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getToolMinorF(nullptr); +static jfloat Java_MotionEvent_getToolMinorF(JNIEnv* env, const base::android::JavaRef<jobject>& + obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getToolMinorF(JNIEnv* env, const base::android::JavaRef<jobject>& + obj) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getToolMinor", + "()F", + &g_android_view_MotionEvent_getToolMinorF); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getOrientationF(nullptr); +static jfloat Java_MotionEvent_getOrientationF(JNIEnv* env, const base::android::JavaRef<jobject>& + obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getOrientationF(JNIEnv* env, const base::android::JavaRef<jobject>& + obj) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getOrientation", + "()F", + &g_android_view_MotionEvent_getOrientationF); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getAxisValueF_I(nullptr); +static jfloat Java_MotionEvent_getAxisValueF_I(JNIEnv* env, const base::android::JavaRef<jobject>& + obj, JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getAxisValueF_I(JNIEnv* env, const base::android::JavaRef<jobject>& + obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getAxisValue", + "(I)F", + &g_android_view_MotionEvent_getAxisValueF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getPointerCount(nullptr); +static jint Java_MotionEvent_getPointerCount(JNIEnv* env, const base::android::JavaRef<jobject>& + obj) __attribute__ ((unused)); +static jint Java_MotionEvent_getPointerCount(JNIEnv* env, const base::android::JavaRef<jobject>& + obj) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getPointerCount", + "()I", + &g_android_view_MotionEvent_getPointerCount); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getPointerId(nullptr); +static jint Java_MotionEvent_getPointerId(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + JniIntWrapper p0) __attribute__ ((unused)); +static jint Java_MotionEvent_getPointerId(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getPointerId", + "(I)I", + &g_android_view_MotionEvent_getPointerId); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getToolType(nullptr); +static jint Java_MotionEvent_getToolType(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + JniIntWrapper p0) __attribute__ ((unused)); +static jint Java_MotionEvent_getToolType(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getToolType", + "(I)I", + &g_android_view_MotionEvent_getToolType); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_findPointerIndex(nullptr); +static jint Java_MotionEvent_findPointerIndex(JNIEnv* env, const base::android::JavaRef<jobject>& + obj, JniIntWrapper p0) __attribute__ ((unused)); +static jint Java_MotionEvent_findPointerIndex(JNIEnv* env, const base::android::JavaRef<jobject>& + obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "findPointerIndex", + "(I)I", + &g_android_view_MotionEvent_findPointerIndex); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getXF_I(nullptr); +static jfloat Java_MotionEvent_getXF_I(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getXF_I(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getX", + "(I)F", + &g_android_view_MotionEvent_getXF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getYF_I(nullptr); +static jfloat Java_MotionEvent_getYF_I(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getYF_I(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getY", + "(I)F", + &g_android_view_MotionEvent_getYF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getPressureF_I(nullptr); +static jfloat Java_MotionEvent_getPressureF_I(JNIEnv* env, const base::android::JavaRef<jobject>& + obj, JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getPressureF_I(JNIEnv* env, const base::android::JavaRef<jobject>& + obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getPressure", + "(I)F", + &g_android_view_MotionEvent_getPressureF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getSizeF_I(nullptr); +static jfloat Java_MotionEvent_getSizeF_I(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getSizeF_I(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getSize", + "(I)F", + &g_android_view_MotionEvent_getSizeF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getTouchMajorF_I(nullptr); +static jfloat Java_MotionEvent_getTouchMajorF_I(JNIEnv* env, const base::android::JavaRef<jobject>& + obj, JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getTouchMajorF_I(JNIEnv* env, const base::android::JavaRef<jobject>& + obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getTouchMajor", + "(I)F", + &g_android_view_MotionEvent_getTouchMajorF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getTouchMinorF_I(nullptr); +static jfloat Java_MotionEvent_getTouchMinorF_I(JNIEnv* env, const base::android::JavaRef<jobject>& + obj, JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getTouchMinorF_I(JNIEnv* env, const base::android::JavaRef<jobject>& + obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getTouchMinor", + "(I)F", + &g_android_view_MotionEvent_getTouchMinorF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getToolMajorF_I(nullptr); +static jfloat Java_MotionEvent_getToolMajorF_I(JNIEnv* env, const base::android::JavaRef<jobject>& + obj, JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getToolMajorF_I(JNIEnv* env, const base::android::JavaRef<jobject>& + obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getToolMajor", + "(I)F", + &g_android_view_MotionEvent_getToolMajorF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getToolMinorF_I(nullptr); +static jfloat Java_MotionEvent_getToolMinorF_I(JNIEnv* env, const base::android::JavaRef<jobject>& + obj, JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getToolMinorF_I(JNIEnv* env, const base::android::JavaRef<jobject>& + obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getToolMinor", + "(I)F", + &g_android_view_MotionEvent_getToolMinorF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getOrientationF_I(nullptr); +static jfloat Java_MotionEvent_getOrientationF_I(JNIEnv* env, const base::android::JavaRef<jobject>& + obj, JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getOrientationF_I(JNIEnv* env, const base::android::JavaRef<jobject>& + obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getOrientation", + "(I)F", + &g_android_view_MotionEvent_getOrientationF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getAxisValueF_I_I(nullptr); +static jfloat Java_MotionEvent_getAxisValueF_I_I(JNIEnv* env, const base::android::JavaRef<jobject>& + obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getAxisValueF_I_I(JNIEnv* env, const base::android::JavaRef<jobject>& + obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getAxisValue", + "(II)F", + &g_android_view_MotionEvent_getAxisValueF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getPointerCoords(nullptr); +static void Java_MotionEvent_getPointerCoords(JNIEnv* env, const base::android::JavaRef<jobject>& + obj, JniIntWrapper p0, + const base::android::JavaRef<jobject>& p1) __attribute__ ((unused)); +static void Java_MotionEvent_getPointerCoords(JNIEnv* env, const base::android::JavaRef<jobject>& + obj, JniIntWrapper p0, + const base::android::JavaRef<jobject>& p1) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getPointerCoords", + "(ILandroid/view/MotionEvent$PointerCoords;)V", + &g_android_view_MotionEvent_getPointerCoords); + + env->CallVoidMethod(obj.obj(), + method_id, as_jint(p0), p1.obj()); + jni_generator::CheckException(env); +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getPointerProperties(nullptr); +static void Java_MotionEvent_getPointerProperties(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0, + const base::android::JavaRef<jobject>& p1) __attribute__ ((unused)); +static void Java_MotionEvent_getPointerProperties(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0, + const base::android::JavaRef<jobject>& p1) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getPointerProperties", + "(ILandroid/view/MotionEvent$PointerProperties;)V", + &g_android_view_MotionEvent_getPointerProperties); + + env->CallVoidMethod(obj.obj(), + method_id, as_jint(p0), p1.obj()); + jni_generator::CheckException(env); +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getMetaState(nullptr); +static jint Java_MotionEvent_getMetaState(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + __attribute__ ((unused)); +static jint Java_MotionEvent_getMetaState(JNIEnv* env, const base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getMetaState", + "()I", + &g_android_view_MotionEvent_getMetaState); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getButtonState(nullptr); +static jint Java_MotionEvent_getButtonState(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + __attribute__ ((unused)); +static jint Java_MotionEvent_getButtonState(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getButtonState", + "()I", + &g_android_view_MotionEvent_getButtonState); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getRawX(nullptr); +static jfloat Java_MotionEvent_getRawX(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + __attribute__ ((unused)); +static jfloat Java_MotionEvent_getRawX(JNIEnv* env, const base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getRawX", + "()F", + &g_android_view_MotionEvent_getRawX); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getRawY(nullptr); +static jfloat Java_MotionEvent_getRawY(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + __attribute__ ((unused)); +static jfloat Java_MotionEvent_getRawY(JNIEnv* env, const base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getRawY", + "()F", + &g_android_view_MotionEvent_getRawY); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getXPrecision(nullptr); +static jfloat Java_MotionEvent_getXPrecision(JNIEnv* env, const base::android::JavaRef<jobject>& + obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getXPrecision(JNIEnv* env, const base::android::JavaRef<jobject>& + obj) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getXPrecision", + "()F", + &g_android_view_MotionEvent_getXPrecision); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getYPrecision(nullptr); +static jfloat Java_MotionEvent_getYPrecision(JNIEnv* env, const base::android::JavaRef<jobject>& + obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getYPrecision(JNIEnv* env, const base::android::JavaRef<jobject>& + obj) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getYPrecision", + "()F", + &g_android_view_MotionEvent_getYPrecision); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getHistorySize(nullptr); +static jint Java_MotionEvent_getHistorySize(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + __attribute__ ((unused)); +static jint Java_MotionEvent_getHistorySize(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getHistorySize", + "()I", + &g_android_view_MotionEvent_getHistorySize); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalEventTime(nullptr); +static jlong Java_MotionEvent_getHistoricalEventTime(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0) __attribute__ ((unused)); +static jlong Java_MotionEvent_getHistoricalEventTime(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getHistoricalEventTime", + "(I)J", + &g_android_view_MotionEvent_getHistoricalEventTime); + + jlong ret = + env->CallLongMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalXF_I(nullptr); +static jfloat Java_MotionEvent_getHistoricalXF_I(JNIEnv* env, const base::android::JavaRef<jobject>& + obj, JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalXF_I(JNIEnv* env, const base::android::JavaRef<jobject>& + obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getHistoricalX", + "(I)F", + &g_android_view_MotionEvent_getHistoricalXF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalYF_I(nullptr); +static jfloat Java_MotionEvent_getHistoricalYF_I(JNIEnv* env, const base::android::JavaRef<jobject>& + obj, JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalYF_I(JNIEnv* env, const base::android::JavaRef<jobject>& + obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getHistoricalY", + "(I)F", + &g_android_view_MotionEvent_getHistoricalYF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalPressureF_I(nullptr); +static jfloat Java_MotionEvent_getHistoricalPressureF_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalPressureF_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getHistoricalPressure", + "(I)F", + &g_android_view_MotionEvent_getHistoricalPressureF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalSizeF_I(nullptr); +static jfloat Java_MotionEvent_getHistoricalSizeF_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalSizeF_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getHistoricalSize", + "(I)F", + &g_android_view_MotionEvent_getHistoricalSizeF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalTouchMajorF_I(nullptr); +static jfloat Java_MotionEvent_getHistoricalTouchMajorF_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalTouchMajorF_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getHistoricalTouchMajor", + "(I)F", + &g_android_view_MotionEvent_getHistoricalTouchMajorF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalTouchMinorF_I(nullptr); +static jfloat Java_MotionEvent_getHistoricalTouchMinorF_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalTouchMinorF_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getHistoricalTouchMinor", + "(I)F", + &g_android_view_MotionEvent_getHistoricalTouchMinorF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalToolMajorF_I(nullptr); +static jfloat Java_MotionEvent_getHistoricalToolMajorF_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalToolMajorF_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getHistoricalToolMajor", + "(I)F", + &g_android_view_MotionEvent_getHistoricalToolMajorF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalToolMinorF_I(nullptr); +static jfloat Java_MotionEvent_getHistoricalToolMinorF_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalToolMinorF_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getHistoricalToolMinor", + "(I)F", + &g_android_view_MotionEvent_getHistoricalToolMinorF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalOrientationF_I(nullptr); +static jfloat Java_MotionEvent_getHistoricalOrientationF_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalOrientationF_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getHistoricalOrientation", + "(I)F", + &g_android_view_MotionEvent_getHistoricalOrientationF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalAxisValueF_I_I(nullptr); +static jfloat Java_MotionEvent_getHistoricalAxisValueF_I_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalAxisValueF_I_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getHistoricalAxisValue", + "(II)F", + &g_android_view_MotionEvent_getHistoricalAxisValueF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalXF_I_I(nullptr); +static jfloat Java_MotionEvent_getHistoricalXF_I_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalXF_I_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getHistoricalX", + "(II)F", + &g_android_view_MotionEvent_getHistoricalXF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalYF_I_I(nullptr); +static jfloat Java_MotionEvent_getHistoricalYF_I_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalYF_I_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getHistoricalY", + "(II)F", + &g_android_view_MotionEvent_getHistoricalYF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalPressureF_I_I(nullptr); +static jfloat Java_MotionEvent_getHistoricalPressureF_I_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalPressureF_I_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getHistoricalPressure", + "(II)F", + &g_android_view_MotionEvent_getHistoricalPressureF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalSizeF_I_I(nullptr); +static jfloat Java_MotionEvent_getHistoricalSizeF_I_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalSizeF_I_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getHistoricalSize", + "(II)F", + &g_android_view_MotionEvent_getHistoricalSizeF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalTouchMajorF_I_I(nullptr); +static jfloat Java_MotionEvent_getHistoricalTouchMajorF_I_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalTouchMajorF_I_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getHistoricalTouchMajor", + "(II)F", + &g_android_view_MotionEvent_getHistoricalTouchMajorF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalTouchMinorF_I_I(nullptr); +static jfloat Java_MotionEvent_getHistoricalTouchMinorF_I_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalTouchMinorF_I_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getHistoricalTouchMinor", + "(II)F", + &g_android_view_MotionEvent_getHistoricalTouchMinorF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalToolMajorF_I_I(nullptr); +static jfloat Java_MotionEvent_getHistoricalToolMajorF_I_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalToolMajorF_I_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getHistoricalToolMajor", + "(II)F", + &g_android_view_MotionEvent_getHistoricalToolMajorF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalToolMinorF_I_I(nullptr); +static jfloat Java_MotionEvent_getHistoricalToolMinorF_I_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalToolMinorF_I_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getHistoricalToolMinor", + "(II)F", + &g_android_view_MotionEvent_getHistoricalToolMinorF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalOrientationF_I_I(nullptr); +static jfloat Java_MotionEvent_getHistoricalOrientationF_I_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalOrientationF_I_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getHistoricalOrientation", + "(II)F", + &g_android_view_MotionEvent_getHistoricalOrientationF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalAxisValueF_I_I_I(nullptr); +static jfloat Java_MotionEvent_getHistoricalAxisValueF_I_I_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0, + JniIntWrapper p1, + JniIntWrapper p2) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalAxisValueF_I_I_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0, + JniIntWrapper p1, + JniIntWrapper p2) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getHistoricalAxisValue", + "(III)F", + &g_android_view_MotionEvent_getHistoricalAxisValueF_I_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1), as_jint(p2)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalPointerCoords(nullptr); +static void Java_MotionEvent_getHistoricalPointerCoords(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0, + JniIntWrapper p1, + const base::android::JavaRef<jobject>& p2) __attribute__ ((unused)); +static void Java_MotionEvent_getHistoricalPointerCoords(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper p0, + JniIntWrapper p1, + const base::android::JavaRef<jobject>& p2) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getHistoricalPointerCoords", + "(IILandroid/view/MotionEvent$PointerCoords;)V", + &g_android_view_MotionEvent_getHistoricalPointerCoords); + + env->CallVoidMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1), p2.obj()); + jni_generator::CheckException(env); +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_getEdgeFlags(nullptr); +static jint Java_MotionEvent_getEdgeFlags(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + __attribute__ ((unused)); +static jint Java_MotionEvent_getEdgeFlags(JNIEnv* env, const base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "getEdgeFlags", + "()I", + &g_android_view_MotionEvent_getEdgeFlags); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_setEdgeFlags(nullptr); +static void Java_MotionEvent_setEdgeFlags(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + JniIntWrapper p0) __attribute__ ((unused)); +static void Java_MotionEvent_setEdgeFlags(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "setEdgeFlags", + "(I)V", + &g_android_view_MotionEvent_setEdgeFlags); + + env->CallVoidMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_setAction(nullptr); +static void Java_MotionEvent_setAction(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + JniIntWrapper p0) __attribute__ ((unused)); +static void Java_MotionEvent_setAction(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "setAction", + "(I)V", + &g_android_view_MotionEvent_setAction); + + env->CallVoidMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_offsetLocation(nullptr); +static void Java_MotionEvent_offsetLocation(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + jfloat p0, + jfloat p1) __attribute__ ((unused)); +static void Java_MotionEvent_offsetLocation(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + jfloat p0, + jfloat p1) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "offsetLocation", + "(FF)V", + &g_android_view_MotionEvent_offsetLocation); + + env->CallVoidMethod(obj.obj(), + method_id, p0, p1); + jni_generator::CheckException(env); +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_setLocation(nullptr); +static void Java_MotionEvent_setLocation(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + jfloat p0, + jfloat p1) __attribute__ ((unused)); +static void Java_MotionEvent_setLocation(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + jfloat p0, + jfloat p1) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "setLocation", + "(FF)V", + &g_android_view_MotionEvent_setLocation); + + env->CallVoidMethod(obj.obj(), + method_id, p0, p1); + jni_generator::CheckException(env); +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_transform(nullptr); +static void Java_MotionEvent_transform(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + const base::android::JavaRef<jobject>& p0) __attribute__ ((unused)); +static void Java_MotionEvent_transform(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + const base::android::JavaRef<jobject>& p0) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "transform", + "(Landroid/graphics/Matrix;)V", + &g_android_view_MotionEvent_transform); + + env->CallVoidMethod(obj.obj(), + method_id, p0.obj()); + jni_generator::CheckException(env); +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_addBatchV_J_F_F_F_F_I(nullptr); +static void Java_MotionEvent_addBatchV_J_F_F_F_F_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, jlong p0, + jfloat p1, + jfloat p2, + jfloat p3, + jfloat p4, + JniIntWrapper p5) __attribute__ ((unused)); +static void Java_MotionEvent_addBatchV_J_F_F_F_F_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, jlong p0, + jfloat p1, + jfloat p2, + jfloat p3, + jfloat p4, + JniIntWrapper p5) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "addBatch", + "(JFFFFI)V", + &g_android_view_MotionEvent_addBatchV_J_F_F_F_F_I); + + env->CallVoidMethod(obj.obj(), + method_id, p0, p1, p2, p3, p4, as_jint(p5)); + jni_generator::CheckException(env); +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_addBatchV_J_LAVMEPC_I(nullptr); +static void Java_MotionEvent_addBatchV_J_LAVMEPC_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, jlong p0, + const base::android::JavaRef<jobjectArray>& p1, + JniIntWrapper p2) __attribute__ ((unused)); +static void Java_MotionEvent_addBatchV_J_LAVMEPC_I(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, jlong p0, + const base::android::JavaRef<jobjectArray>& p1, + JniIntWrapper p2) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "addBatch", + "(J[Landroid/view/MotionEvent$PointerCoords;I)V", + &g_android_view_MotionEvent_addBatchV_J_LAVMEPC_I); + + env->CallVoidMethod(obj.obj(), + method_id, p0, p1.obj(), as_jint(p2)); + jni_generator::CheckException(env); +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_toString(nullptr); +static base::android::ScopedJavaLocalRef<jstring> Java_MotionEvent_toString(JNIEnv* env, const + base::android::JavaRef<jobject>& obj) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef<jstring> Java_MotionEvent_toString(JNIEnv* env, const + base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "toString", + "()Ljava/lang/String;", + &g_android_view_MotionEvent_toString); + + jstring ret = + static_cast<jstring>(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jstring>(env, ret); +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_actionToString(nullptr); +static base::android::ScopedJavaLocalRef<jstring> Java_MotionEvent_actionToString(JNIEnv* env, + JniIntWrapper p0) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef<jstring> Java_MotionEvent_actionToString(JNIEnv* env, + JniIntWrapper p0) { + CHECK_CLAZZ(env, android_view_MotionEvent_clazz(env), + android_view_MotionEvent_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, android_view_MotionEvent_clazz(env), + "actionToString", + "(I)Ljava/lang/String;", + &g_android_view_MotionEvent_actionToString); + + jstring ret = + static_cast<jstring>(env->CallStaticObjectMethod(android_view_MotionEvent_clazz(env), + method_id, as_jint(p0))); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jstring>(env, ret); +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_axisToString(nullptr); +static base::android::ScopedJavaLocalRef<jstring> Java_MotionEvent_axisToString(JNIEnv* env, + JniIntWrapper p0) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef<jstring> Java_MotionEvent_axisToString(JNIEnv* env, + JniIntWrapper p0) { + CHECK_CLAZZ(env, android_view_MotionEvent_clazz(env), + android_view_MotionEvent_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, android_view_MotionEvent_clazz(env), + "axisToString", + "(I)Ljava/lang/String;", + &g_android_view_MotionEvent_axisToString); + + jstring ret = + static_cast<jstring>(env->CallStaticObjectMethod(android_view_MotionEvent_clazz(env), + method_id, as_jint(p0))); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jstring>(env, ret); +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_axisFromString(nullptr); +static jint Java_MotionEvent_axisFromString(JNIEnv* env, const base::android::JavaRef<jstring>& p0) + __attribute__ ((unused)); +static jint Java_MotionEvent_axisFromString(JNIEnv* env, const base::android::JavaRef<jstring>& p0) + { + CHECK_CLAZZ(env, android_view_MotionEvent_clazz(env), + android_view_MotionEvent_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, android_view_MotionEvent_clazz(env), + "axisFromString", + "(Ljava/lang/String;)I", + &g_android_view_MotionEvent_axisFromString); + + jint ret = + env->CallStaticIntMethod(android_view_MotionEvent_clazz(env), + method_id, p0.obj()); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_android_view_MotionEvent_writeToParcel(nullptr); +static void Java_MotionEvent_writeToParcel(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + const base::android::JavaRef<jobject>& p0, + JniIntWrapper p1) __attribute__ ((unused)); +static void Java_MotionEvent_writeToParcel(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + const base::android::JavaRef<jobject>& p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + android_view_MotionEvent_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, android_view_MotionEvent_clazz(env), + "writeToParcel", + "(Landroid/os/Parcel;I)V", + &g_android_view_MotionEvent_writeToParcel); + + env->CallVoidMethod(obj.obj(), + method_id, p0.obj(), as_jint(p1)); + jni_generator::CheckException(env); +} + +} // namespace JNI_MotionEvent + +#endif // android_view_MotionEvent_JNI
diff --git a/src/base/android/jni_generator/testFromJavaP.golden b/src/base/android/jni_generator/testFromJavaP.golden new file mode 100644 index 0000000..9225f6e --- /dev/null +++ b/src/base/android/jni_generator/testFromJavaP.golden
@@ -0,0 +1,246 @@ +// Copyright 2014 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. + + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// java/io/InputStream + +#ifndef java_io_InputStream_JNI +#define java_io_InputStream_JNI + +#include <jni.h> + +#include "base/android/jni_generator/jni_generator_helper.h" + + +// Step 1: Forward declarations. + +JNI_REGISTRATION_EXPORT extern const char kClassPath_java_io_InputStream[]; +const char kClassPath_java_io_InputStream[] = "java/io/InputStream"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +JNI_REGISTRATION_EXPORT std::atomic<jclass> g_java_io_InputStream_clazz(nullptr); +#ifndef java_io_InputStream_clazz_defined +#define java_io_InputStream_clazz_defined +inline jclass java_io_InputStream_clazz(JNIEnv* env) { + return base::android::LazyGetClass(env, kClassPath_java_io_InputStream, + &g_java_io_InputStream_clazz); +} +#endif + + +// Step 2: Constants (optional). + + +// Step 3: Method stubs. +namespace JNI_InputStream { + + +static std::atomic<jmethodID> g_java_io_InputStream_available(nullptr); +static jint Java_InputStream_available(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + __attribute__ ((unused)); +static jint Java_InputStream_available(JNIEnv* env, const base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + java_io_InputStream_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, java_io_InputStream_clazz(env), + "available", + "()I", + &g_java_io_InputStream_available); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_java_io_InputStream_close(nullptr); +static void Java_InputStream_close(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + __attribute__ ((unused)); +static void Java_InputStream_close(JNIEnv* env, const base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + java_io_InputStream_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, java_io_InputStream_clazz(env), + "close", + "()V", + &g_java_io_InputStream_close); + + env->CallVoidMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); +} + +static std::atomic<jmethodID> g_java_io_InputStream_mark(nullptr); +static void Java_InputStream_mark(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + JniIntWrapper p0) __attribute__ ((unused)); +static void Java_InputStream_mark(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + java_io_InputStream_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, java_io_InputStream_clazz(env), + "mark", + "(I)V", + &g_java_io_InputStream_mark); + + env->CallVoidMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); +} + +static std::atomic<jmethodID> g_java_io_InputStream_markSupported(nullptr); +static jboolean Java_InputStream_markSupported(JNIEnv* env, const base::android::JavaRef<jobject>& + obj) __attribute__ ((unused)); +static jboolean Java_InputStream_markSupported(JNIEnv* env, const base::android::JavaRef<jobject>& + obj) { + CHECK_CLAZZ(env, obj.obj(), + java_io_InputStream_clazz(env), false); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, java_io_InputStream_clazz(env), + "markSupported", + "()Z", + &g_java_io_InputStream_markSupported); + + jboolean ret = + env->CallBooleanMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_java_io_InputStream_readI(nullptr); +static jint Java_InputStream_readI(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + __attribute__ ((unused)); +static jint Java_InputStream_readI(JNIEnv* env, const base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + java_io_InputStream_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, java_io_InputStream_clazz(env), + "read", + "()I", + &g_java_io_InputStream_readI); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_java_io_InputStream_readI_AB(nullptr); +static jint Java_InputStream_readI_AB(JNIEnv* env, const base::android::JavaRef<jobject>& obj, const + base::android::JavaRef<jbyteArray>& p0) __attribute__ ((unused)); +static jint Java_InputStream_readI_AB(JNIEnv* env, const base::android::JavaRef<jobject>& obj, const + base::android::JavaRef<jbyteArray>& p0) { + CHECK_CLAZZ(env, obj.obj(), + java_io_InputStream_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, java_io_InputStream_clazz(env), + "read", + "([B)I", + &g_java_io_InputStream_readI_AB); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id, p0.obj()); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_java_io_InputStream_readI_AB_I_I(nullptr); +static jint Java_InputStream_readI_AB_I_I(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + const base::android::JavaRef<jbyteArray>& p0, + JniIntWrapper p1, + JniIntWrapper p2) __attribute__ ((unused)); +static jint Java_InputStream_readI_AB_I_I(JNIEnv* env, const base::android::JavaRef<jobject>& obj, + const base::android::JavaRef<jbyteArray>& p0, + JniIntWrapper p1, + JniIntWrapper p2) { + CHECK_CLAZZ(env, obj.obj(), + java_io_InputStream_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, java_io_InputStream_clazz(env), + "read", + "([BII)I", + &g_java_io_InputStream_readI_AB_I_I); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id, p0.obj(), as_jint(p1), as_jint(p2)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_java_io_InputStream_reset(nullptr); +static void Java_InputStream_reset(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + __attribute__ ((unused)); +static void Java_InputStream_reset(JNIEnv* env, const base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + java_io_InputStream_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, java_io_InputStream_clazz(env), + "reset", + "()V", + &g_java_io_InputStream_reset); + + env->CallVoidMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); +} + +static std::atomic<jmethodID> g_java_io_InputStream_skip(nullptr); +static jlong Java_InputStream_skip(JNIEnv* env, const base::android::JavaRef<jobject>& obj, jlong + p0) __attribute__ ((unused)); +static jlong Java_InputStream_skip(JNIEnv* env, const base::android::JavaRef<jobject>& obj, jlong + p0) { + CHECK_CLAZZ(env, obj.obj(), + java_io_InputStream_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, java_io_InputStream_clazz(env), + "skip", + "(J)J", + &g_java_io_InputStream_skip); + + jlong ret = + env->CallLongMethod(obj.obj(), + method_id, p0); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> g_java_io_InputStream_Constructor(nullptr); +static base::android::ScopedJavaLocalRef<jobject> Java_InputStream_Constructor(JNIEnv* env) + __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef<jobject> Java_InputStream_Constructor(JNIEnv* env) { + CHECK_CLAZZ(env, java_io_InputStream_clazz(env), + java_io_InputStream_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, java_io_InputStream_clazz(env), + "<init>", + "()V", + &g_java_io_InputStream_Constructor); + + jobject ret = + env->NewObject(java_io_InputStream_clazz(env), + method_id); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jobject>(env, ret); +} + +} // namespace JNI_InputStream + +#endif // java_io_InputStream_JNI
diff --git a/src/base/android/jni_generator/testFromJavaPGenerics.golden b/src/base/android/jni_generator/testFromJavaPGenerics.golden new file mode 100644 index 0000000..cf2646f3 --- /dev/null +++ b/src/base/android/jni_generator/testFromJavaPGenerics.golden
@@ -0,0 +1,81 @@ +// Copyright 2014 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. + + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// java/util/HashSet + +#ifndef java_util_HashSet_JNI +#define java_util_HashSet_JNI + +#include <jni.h> + +#include "base/android/jni_generator/jni_generator_helper.h" + + +// Step 1: Forward declarations. + +JNI_REGISTRATION_EXPORT extern const char kClassPath_java_util_HashSet[]; +const char kClassPath_java_util_HashSet[] = "java/util/HashSet"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +JNI_REGISTRATION_EXPORT std::atomic<jclass> g_java_util_HashSet_clazz(nullptr); +#ifndef java_util_HashSet_clazz_defined +#define java_util_HashSet_clazz_defined +inline jclass java_util_HashSet_clazz(JNIEnv* env) { + return base::android::LazyGetClass(env, kClassPath_java_util_HashSet, &g_java_util_HashSet_clazz); +} +#endif + + +// Step 2: Constants (optional). + + +// Step 3: Method stubs. +namespace JNI_HashSet { + + +static std::atomic<jmethodID> g_java_util_HashSet_dummy(nullptr); +static void Java_HashSet_dummy(JNIEnv* env, const base::android::JavaRef<jobject>& obj) + __attribute__ ((unused)); +static void Java_HashSet_dummy(JNIEnv* env, const base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + java_util_HashSet_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, java_util_HashSet_clazz(env), + "dummy", + "()V", + &g_java_util_HashSet_dummy); + + env->CallVoidMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); +} + +static std::atomic<jmethodID> g_java_util_HashSet_getClass(nullptr); +static base::android::ScopedJavaLocalRef<jclass> Java_HashSet_getClass(JNIEnv* env, const + base::android::JavaRef<jobject>& obj) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef<jclass> Java_HashSet_getClass(JNIEnv* env, const + base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + java_util_HashSet_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, java_util_HashSet_clazz(env), + "getClass", + "()Ljava/lang/Class<*>;", + &g_java_util_HashSet_getClass); + + jclass ret = + static_cast<jclass>(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jclass>(env, ret); +} + +} // namespace JNI_HashSet + +#endif // java_util_HashSet_JNI
diff --git a/src/base/android/jni_generator/testInnerClassNatives.golden b/src/base/android/jni_generator/testInnerClassNatives.golden new file mode 100644 index 0000000..6cad2b0 --- /dev/null +++ b/src/base/android/jni_generator/testInnerClassNatives.golden
@@ -0,0 +1,60 @@ +// Copyright 2014 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. + + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// org/chromium/TestJni + +#ifndef org_chromium_TestJni_JNI +#define org_chromium_TestJni_JNI + +#include <jni.h> + +#include "base/android/jni_generator/jni_generator_helper.h" + + +// Step 1: Forward declarations. + +JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_TestJni[]; +const char kClassPath_org_chromium_TestJni[] = "org/chromium/TestJni"; + +JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_TestJni_00024MyInnerClass[]; +const char kClassPath_org_chromium_TestJni_00024MyInnerClass[] = + "org/chromium/TestJni$MyInnerClass"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +JNI_REGISTRATION_EXPORT std::atomic<jclass> g_org_chromium_TestJni_clazz(nullptr); +#ifndef org_chromium_TestJni_clazz_defined +#define org_chromium_TestJni_clazz_defined +inline jclass org_chromium_TestJni_clazz(JNIEnv* env) { + return base::android::LazyGetClass(env, kClassPath_org_chromium_TestJni, + &g_org_chromium_TestJni_clazz); +} +#endif +// Leaking this jclass as we cannot use LazyInstance from some threads. +JNI_REGISTRATION_EXPORT std::atomic<jclass> g_org_chromium_TestJni_00024MyInnerClass_clazz(nullptr); +#ifndef org_chromium_TestJni_00024MyInnerClass_clazz_defined +#define org_chromium_TestJni_00024MyInnerClass_clazz_defined +inline jclass org_chromium_TestJni_00024MyInnerClass_clazz(JNIEnv* env) { + return base::android::LazyGetClass(env, kClassPath_org_chromium_TestJni_00024MyInnerClass, + &g_org_chromium_TestJni_00024MyInnerClass_clazz); +} +#endif + + +// Step 2: Constants (optional). + + +// Step 3: Method stubs. +static jint JNI_MyInnerClass_Init(JNIEnv* env, const base::android::JavaParamRef<jobject>& jcaller); + +JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_00024MyInnerClass_nativeInit( + JNIEnv* env, + jobject jcaller) { + return JNI_MyInnerClass_Init(env, base::android::JavaParamRef<jobject>(env, jcaller)); +} + + +#endif // org_chromium_TestJni_JNI
diff --git a/src/base/android/jni_generator/testInnerClassNativesBothInnerAndOuter.golden b/src/base/android/jni_generator/testInnerClassNativesBothInnerAndOuter.golden new file mode 100644 index 0000000..ccaf121 --- /dev/null +++ b/src/base/android/jni_generator/testInnerClassNativesBothInnerAndOuter.golden
@@ -0,0 +1,70 @@ +// Copyright 2014 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. + + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// org/chromium/TestJni + +#ifndef org_chromium_TestJni_JNI +#define org_chromium_TestJni_JNI + +#include <jni.h> + +#include "base/android/jni_generator/jni_generator_helper.h" + + +// Step 1: Forward declarations. + +JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_TestJni_00024MyOtherInnerClass[]; +const char kClassPath_org_chromium_TestJni_00024MyOtherInnerClass[] = + "org/chromium/TestJni$MyOtherInnerClass"; + +JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_TestJni[]; +const char kClassPath_org_chromium_TestJni[] = "org/chromium/TestJni"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +JNI_REGISTRATION_EXPORT std::atomic<jclass> + g_org_chromium_TestJni_00024MyOtherInnerClass_clazz(nullptr); +#ifndef org_chromium_TestJni_00024MyOtherInnerClass_clazz_defined +#define org_chromium_TestJni_00024MyOtherInnerClass_clazz_defined +inline jclass org_chromium_TestJni_00024MyOtherInnerClass_clazz(JNIEnv* env) { + return base::android::LazyGetClass(env, kClassPath_org_chromium_TestJni_00024MyOtherInnerClass, + &g_org_chromium_TestJni_00024MyOtherInnerClass_clazz); +} +#endif +// Leaking this jclass as we cannot use LazyInstance from some threads. +JNI_REGISTRATION_EXPORT std::atomic<jclass> g_org_chromium_TestJni_clazz(nullptr); +#ifndef org_chromium_TestJni_clazz_defined +#define org_chromium_TestJni_clazz_defined +inline jclass org_chromium_TestJni_clazz(JNIEnv* env) { + return base::android::LazyGetClass(env, kClassPath_org_chromium_TestJni, + &g_org_chromium_TestJni_clazz); +} +#endif + + +// Step 2: Constants (optional). + + +// Step 3: Method stubs. +static jint JNI_TestJni_Init(JNIEnv* env, const base::android::JavaParamRef<jobject>& jcaller); + +JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_nativeInit( + JNIEnv* env, + jobject jcaller) { + return JNI_TestJni_Init(env, base::android::JavaParamRef<jobject>(env, jcaller)); +} + +static jint JNI_MyOtherInnerClass_Init(JNIEnv* env, const base::android::JavaParamRef<jobject>& + jcaller); + +JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_00024MyOtherInnerClass_nativeInit( + JNIEnv* env, + jobject jcaller) { + return JNI_MyOtherInnerClass_Init(env, base::android::JavaParamRef<jobject>(env, jcaller)); +} + + +#endif // org_chromium_TestJni_JNI
diff --git a/src/base/android/jni_generator/testInnerClassNativesBothInnerAndOuterRegistrations.golden b/src/base/android/jni_generator/testInnerClassNativesBothInnerAndOuterRegistrations.golden new file mode 100644 index 0000000..1109d14 --- /dev/null +++ b/src/base/android/jni_generator/testInnerClassNativesBothInnerAndOuterRegistrations.golden
@@ -0,0 +1,113 @@ +// Copyright 2017 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. + + +// This file is autogenerated by +// base/android/jni_generator/jni_registration_generator.py +// Please do not change its content. + +#ifndef HEADER_GUARD +#define HEADER_GUARD + +#include <jni.h> + +#include "base/android/jni_generator/jni_generator_helper.h" +#include "base/android/jni_int_wrapper.h" + + +// Step 1: Forward declarations (classes). + +extern const char kClassPath_org_chromium_TestJni_00024MyOtherInnerClass[]; + +extern const char kClassPath_org_chromium_TestJni[]; +extern std::atomic<jclass> g_org_chromium_TestJni_00024MyOtherInnerClass_clazz; +#ifndef org_chromium_TestJni_00024MyOtherInnerClass_clazz_defined +#define org_chromium_TestJni_00024MyOtherInnerClass_clazz_defined +inline jclass org_chromium_TestJni_00024MyOtherInnerClass_clazz(JNIEnv* env) { + return base::android::LazyGetClass(env, kClassPath_org_chromium_TestJni_00024MyOtherInnerClass, + &g_org_chromium_TestJni_00024MyOtherInnerClass_clazz); +} +#endif +extern std::atomic<jclass> g_org_chromium_TestJni_clazz; +#ifndef org_chromium_TestJni_clazz_defined +#define org_chromium_TestJni_clazz_defined +inline jclass org_chromium_TestJni_clazz(JNIEnv* env) { + return base::android::LazyGetClass(env, kClassPath_org_chromium_TestJni, + &g_org_chromium_TestJni_clazz); +} +#endif + + +// Step 2: Forward declarations (methods). + +JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_nativeInit( + JNIEnv* env, + jobject jcaller); +JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_00024MyOtherInnerClass_nativeInit( + JNIEnv* env, + jobject jcaller); + + +// Step 3: Method declarations. + +static const JNINativeMethod kMethods_org_chromium_TestJni_00024MyOtherInnerClass[] = { + { "nativeInit", "()I", + reinterpret_cast<void*>(Java_org_chromium_TestJni_00024MyOtherInnerClass_nativeInit) }, +}; + + +static const JNINativeMethod kMethods_org_chromium_TestJni[] = { + { "nativeInit", "()I", reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeInit) }, +}; + + +JNI_REGISTRATION_EXPORT bool RegisterNative_org_chromium_TestJni(JNIEnv* env) { + const int kMethods_org_chromium_TestJni_00024MyOtherInnerClassSize = + arraysize(kMethods_org_chromium_TestJni_00024MyOtherInnerClass); + if (env->RegisterNatives( + org_chromium_TestJni_00024MyOtherInnerClass_clazz(env), + kMethods_org_chromium_TestJni_00024MyOtherInnerClass, + kMethods_org_chromium_TestJni_00024MyOtherInnerClassSize) < 0) { + jni_generator::HandleRegistrationError(env, + org_chromium_TestJni_00024MyOtherInnerClass_clazz(env), + __FILE__); + return false; + } + + + const int kMethods_org_chromium_TestJniSize = + arraysize(kMethods_org_chromium_TestJni); + if (env->RegisterNatives( + org_chromium_TestJni_clazz(env), + kMethods_org_chromium_TestJni, + kMethods_org_chromium_TestJniSize) < 0) { + jni_generator::HandleRegistrationError(env, + org_chromium_TestJni_clazz(env), + __FILE__); + return false; + } + + return true; +} + + +// Step 4: Main dex and non-main dex registration functions. + +namespace test { + +bool RegisterMainDexNatives(JNIEnv* env) { + if (!RegisterNative_org_chromium_TestJni(env)) + return false; + + return true; +} + +bool RegisterNonMainDexNatives(JNIEnv* env) { + + return true; +} + +} // namespace test + +#endif // HEADER_GUARD
diff --git a/src/base/android/jni_generator/testInnerClassNativesMultiple.golden b/src/base/android/jni_generator/testInnerClassNativesMultiple.golden new file mode 100644 index 0000000..3e53ece --- /dev/null +++ b/src/base/android/jni_generator/testInnerClassNativesMultiple.golden
@@ -0,0 +1,83 @@ +// Copyright 2014 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. + + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// org/chromium/TestJni + +#ifndef org_chromium_TestJni_JNI +#define org_chromium_TestJni_JNI + +#include <jni.h> + +#include "base/android/jni_generator/jni_generator_helper.h" + + +// Step 1: Forward declarations. + +JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_TestJni_00024MyOtherInnerClass[]; +const char kClassPath_org_chromium_TestJni_00024MyOtherInnerClass[] = + "org/chromium/TestJni$MyOtherInnerClass"; + +JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_TestJni[]; +const char kClassPath_org_chromium_TestJni[] = "org/chromium/TestJni"; + +JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_TestJni_00024MyInnerClass[]; +const char kClassPath_org_chromium_TestJni_00024MyInnerClass[] = + "org/chromium/TestJni$MyInnerClass"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +JNI_REGISTRATION_EXPORT std::atomic<jclass> + g_org_chromium_TestJni_00024MyOtherInnerClass_clazz(nullptr); +#ifndef org_chromium_TestJni_00024MyOtherInnerClass_clazz_defined +#define org_chromium_TestJni_00024MyOtherInnerClass_clazz_defined +inline jclass org_chromium_TestJni_00024MyOtherInnerClass_clazz(JNIEnv* env) { + return base::android::LazyGetClass(env, kClassPath_org_chromium_TestJni_00024MyOtherInnerClass, + &g_org_chromium_TestJni_00024MyOtherInnerClass_clazz); +} +#endif +// Leaking this jclass as we cannot use LazyInstance from some threads. +JNI_REGISTRATION_EXPORT std::atomic<jclass> g_org_chromium_TestJni_clazz(nullptr); +#ifndef org_chromium_TestJni_clazz_defined +#define org_chromium_TestJni_clazz_defined +inline jclass org_chromium_TestJni_clazz(JNIEnv* env) { + return base::android::LazyGetClass(env, kClassPath_org_chromium_TestJni, + &g_org_chromium_TestJni_clazz); +} +#endif +// Leaking this jclass as we cannot use LazyInstance from some threads. +JNI_REGISTRATION_EXPORT std::atomic<jclass> g_org_chromium_TestJni_00024MyInnerClass_clazz(nullptr); +#ifndef org_chromium_TestJni_00024MyInnerClass_clazz_defined +#define org_chromium_TestJni_00024MyInnerClass_clazz_defined +inline jclass org_chromium_TestJni_00024MyInnerClass_clazz(JNIEnv* env) { + return base::android::LazyGetClass(env, kClassPath_org_chromium_TestJni_00024MyInnerClass, + &g_org_chromium_TestJni_00024MyInnerClass_clazz); +} +#endif + + +// Step 2: Constants (optional). + + +// Step 3: Method stubs. +static jint JNI_MyInnerClass_Init(JNIEnv* env, const base::android::JavaParamRef<jobject>& jcaller); + +JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_00024MyInnerClass_nativeInit( + JNIEnv* env, + jobject jcaller) { + return JNI_MyInnerClass_Init(env, base::android::JavaParamRef<jobject>(env, jcaller)); +} + +static jint JNI_MyOtherInnerClass_Init(JNIEnv* env, const base::android::JavaParamRef<jobject>& + jcaller); + +JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_00024MyOtherInnerClass_nativeInit( + JNIEnv* env, + jobject jcaller) { + return JNI_MyOtherInnerClass_Init(env, base::android::JavaParamRef<jobject>(env, jcaller)); +} + + +#endif // org_chromium_TestJni_JNI
diff --git a/src/base/android/jni_generator/testInputStream.javap b/src/base/android/jni_generator/testInputStream.javap new file mode 100644 index 0000000..50ab617 --- /dev/null +++ b/src/base/android/jni_generator/testInputStream.javap
@@ -0,0 +1,228 @@ +Compiled from "InputStream.java" +public abstract class java.io.InputStream extends java.lang.Object implements java.io.Closeable + SourceFile: "InputStream.java" + minor version: 0 + major version: 49 + Constant pool: +const #1 = Method #6.#39; // java/lang/Object."<init>":()V +const #2 = class #40; // java/lang/RuntimeException +const #3 = String #41; // Stub! +const #4 = Method #2.#42; // java/lang/RuntimeException."<init>":(Ljava/lang/String;)V +const #5 = class #43; // java/io/InputStream +const #6 = class #44; // java/lang/Object +const #7 = class #45; // java/io/Closeable +const #8 = Asciz <init>; +const #9 = Asciz ()V; +const #10 = Asciz Code; +const #11 = Asciz LineNumberTable; +const #12 = Asciz LocalVariableTable; +const #13 = Asciz this; +const #14 = Asciz Ljava/io/InputStream;; +const #15 = Asciz available; +const #16 = Asciz ()I; +const #17 = Asciz Exceptions; +const #18 = class #46; // java/io/IOException +const #19 = Asciz close; +const #20 = Asciz mark; +const #21 = Asciz (I)V; +const #22 = Asciz readlimit; +const #23 = Asciz I; +const #24 = Asciz markSupported; +const #25 = Asciz ()Z; +const #26 = Asciz read; +const #27 = Asciz ([B)I; +const #28 = Asciz buffer; +const #29 = Asciz [B; +const #30 = Asciz ([BII)I; +const #31 = Asciz byteOffset; +const #32 = Asciz byteCount; +const #33 = Asciz reset; +const #34 = Asciz skip; +const #35 = Asciz (J)J; +const #36 = Asciz J; +const #37 = Asciz SourceFile; +const #38 = Asciz InputStream.java; +const #39 = NameAndType #8:#9;// "<init>":()V +const #40 = Asciz java/lang/RuntimeException; +const #41 = Asciz Stub!; +const #42 = NameAndType #8:#47;// "<init>":(Ljava/lang/String;)V +const #43 = Asciz java/io/InputStream; +const #44 = Asciz java/lang/Object; +const #45 = Asciz java/io/Closeable; +const #46 = Asciz java/io/IOException; +const #47 = Asciz (Ljava/lang/String;)V; + +{ +public java.io.InputStream(); + Signature: ()V + Code: + Stack=3, Locals=1, Args_size=1 + 0: aload_0 + 1: invokespecial #1; //Method java/lang/Object."<init>":()V + 4: new #2; //class java/lang/RuntimeException + 7: dup + 8: ldc #3; //String Stub! + 10: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 13: athrow + LineNumberTable: + line 5: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 14 0 this Ljava/io/InputStream; + + +public int available() throws java.io.IOException; + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 6: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Ljava/io/InputStream; + + Exceptions: + throws java.io.IOException +public void close() throws java.io.IOException; + Signature: ()V + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 7: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Ljava/io/InputStream; + + Exceptions: + throws java.io.IOException +public void mark(int); + Signature: (I)V + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 8: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Ljava/io/InputStream; + 0 10 1 readlimit I + + +public boolean markSupported(); + Signature: ()Z + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 9: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Ljava/io/InputStream; + + +public abstract int read() throws java.io.IOException; + Signature: ()I + Exceptions: + throws java.io.IOException +public int read(byte[]) throws java.io.IOException; + Signature: ([B)I + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 11: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Ljava/io/InputStream; + 0 10 1 buffer [B + + Exceptions: + throws java.io.IOException +public int read(byte[], int, int) throws java.io.IOException; + Signature: ([BII)I + Code: + Stack=3, Locals=4, Args_size=4 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 12: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Ljava/io/InputStream; + 0 10 1 buffer [B + 0 10 2 byteOffset I + 0 10 3 byteCount I + + Exceptions: + throws java.io.IOException +public synchronized void reset() throws java.io.IOException; + Signature: ()V + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 13: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Ljava/io/InputStream; + + Exceptions: + throws java.io.IOException +public long skip(long) throws java.io.IOException; + Signature: (J)J + Code: + Stack=3, Locals=3, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 14: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Ljava/io/InputStream; + 0 10 1 byteCount J + + Exceptions: + throws java.io.IOException +} +
diff --git a/src/base/android/jni_generator/testMotionEvent.javap b/src/base/android/jni_generator/testMotionEvent.javap new file mode 100644 index 0000000..0746943 --- /dev/null +++ b/src/base/android/jni_generator/testMotionEvent.javap
@@ -0,0 +1,2295 @@ +Compiled from "MotionEvent.java" +public final class android.view.MotionEvent extends android.view.InputEvent implements android.os.Parcelable + SourceFile: "MotionEvent.java" + InnerClass: + public final #10= #9 of #6; //PointerProperties=class android/view/MotionEvent$PointerProperties of class android/view/MotionEvent + public final #13= #12 of #6; //PointerCoords=class android/view/MotionEvent$PointerCoords of class android/view/MotionEvent + public abstract #150= #149 of #8; //Creator=class android/os/Parcelable$Creator of class android/os/Parcelable + minor version: 0 + major version: 49 + Constant pool: +const #1 = Method #7.#293; // android/view/InputEvent."<init>":()V +const #2 = class #294; // java/lang/RuntimeException +const #3 = String #295; // Stub! +const #4 = Method #2.#296; // java/lang/RuntimeException."<init>":(Ljava/lang/String;)V +const #5 = Field #6.#297; // android/view/MotionEvent.CREATOR:Landroid/os/Parcelable$Creator; +const #6 = class #298; // android/view/MotionEvent +const #7 = class #299; // android/view/InputEvent +const #8 = class #300; // android/os/Parcelable +const #9 = class #301; // android/view/MotionEvent$PointerProperties +const #10 = Asciz PointerProperties; +const #11 = Asciz InnerClasses; +const #12 = class #302; // android/view/MotionEvent$PointerCoords +const #13 = Asciz PointerCoords; +const #14 = Asciz INVALID_POINTER_ID; +const #15 = Asciz I; +const #16 = Asciz ConstantValue; +const #17 = int -1; +const #18 = Asciz ACTION_MASK; +const #19 = int 255; +const #20 = Asciz ACTION_DOWN; +const #21 = int 0; +const #22 = Asciz ACTION_UP; +const #23 = int 1; +const #24 = Asciz ACTION_MOVE; +const #25 = int 2; +const #26 = Asciz ACTION_CANCEL; +const #27 = int 3; +const #28 = Asciz ACTION_OUTSIDE; +const #29 = int 4; +const #30 = Asciz ACTION_POINTER_DOWN; +const #31 = int 5; +const #32 = Asciz ACTION_POINTER_UP; +const #33 = int 6; +const #34 = Asciz ACTION_HOVER_MOVE; +const #35 = int 7; +const #36 = Asciz ACTION_SCROLL; +const #37 = int 8; +const #38 = Asciz ACTION_HOVER_ENTER; +const #39 = int 9; +const #40 = Asciz ACTION_HOVER_EXIT; +const #41 = int 10; +const #42 = Asciz ACTION_POINTER_INDEX_MASK; +const #43 = int 65280; +const #44 = Asciz ACTION_POINTER_INDEX_SHIFT; +const #45 = Asciz ACTION_POINTER_1_DOWN; +const #46 = Asciz Deprecated; +const #47 = Asciz RuntimeVisibleAnnotations; +const #48 = Asciz Ljava/lang/Deprecated;; +const #49 = Asciz ACTION_POINTER_2_DOWN; +const #50 = int 261; +const #51 = Asciz ACTION_POINTER_3_DOWN; +const #52 = int 517; +const #53 = Asciz ACTION_POINTER_1_UP; +const #54 = Asciz ACTION_POINTER_2_UP; +const #55 = int 262; +const #56 = Asciz ACTION_POINTER_3_UP; +const #57 = int 518; +const #58 = Asciz ACTION_POINTER_ID_MASK; +const #59 = Asciz ACTION_POINTER_ID_SHIFT; +const #60 = Asciz FLAG_WINDOW_IS_OBSCURED; +const #61 = Asciz EDGE_TOP; +const #62 = Asciz EDGE_BOTTOM; +const #63 = Asciz EDGE_LEFT; +const #64 = Asciz EDGE_RIGHT; +const #65 = Asciz AXIS_X; +const #66 = Asciz AXIS_Y; +const #67 = Asciz AXIS_PRESSURE; +const #68 = Asciz AXIS_SIZE; +const #69 = Asciz AXIS_TOUCH_MAJOR; +const #70 = Asciz AXIS_TOUCH_MINOR; +const #71 = Asciz AXIS_TOOL_MAJOR; +const #72 = Asciz AXIS_TOOL_MINOR; +const #73 = Asciz AXIS_ORIENTATION; +const #74 = Asciz AXIS_VSCROLL; +const #75 = Asciz AXIS_HSCROLL; +const #76 = Asciz AXIS_Z; +const #77 = int 11; +const #78 = Asciz AXIS_RX; +const #79 = int 12; +const #80 = Asciz AXIS_RY; +const #81 = int 13; +const #82 = Asciz AXIS_RZ; +const #83 = int 14; +const #84 = Asciz AXIS_HAT_X; +const #85 = int 15; +const #86 = Asciz AXIS_HAT_Y; +const #87 = int 16; +const #88 = Asciz AXIS_LTRIGGER; +const #89 = int 17; +const #90 = Asciz AXIS_RTRIGGER; +const #91 = int 18; +const #92 = Asciz AXIS_THROTTLE; +const #93 = int 19; +const #94 = Asciz AXIS_RUDDER; +const #95 = int 20; +const #96 = Asciz AXIS_WHEEL; +const #97 = int 21; +const #98 = Asciz AXIS_GAS; +const #99 = int 22; +const #100 = Asciz AXIS_BRAKE; +const #101 = int 23; +const #102 = Asciz AXIS_DISTANCE; +const #103 = int 24; +const #104 = Asciz AXIS_TILT; +const #105 = int 25; +const #106 = Asciz AXIS_GENERIC_1; +const #107 = int 32; +const #108 = Asciz AXIS_GENERIC_2; +const #109 = int 33; +const #110 = Asciz AXIS_GENERIC_3; +const #111 = int 34; +const #112 = Asciz AXIS_GENERIC_4; +const #113 = int 35; +const #114 = Asciz AXIS_GENERIC_5; +const #115 = int 36; +const #116 = Asciz AXIS_GENERIC_6; +const #117 = int 37; +const #118 = Asciz AXIS_GENERIC_7; +const #119 = int 38; +const #120 = Asciz AXIS_GENERIC_8; +const #121 = int 39; +const #122 = Asciz AXIS_GENERIC_9; +const #123 = int 40; +const #124 = Asciz AXIS_GENERIC_10; +const #125 = int 41; +const #126 = Asciz AXIS_GENERIC_11; +const #127 = int 42; +const #128 = Asciz AXIS_GENERIC_12; +const #129 = int 43; +const #130 = Asciz AXIS_GENERIC_13; +const #131 = int 44; +const #132 = Asciz AXIS_GENERIC_14; +const #133 = int 45; +const #134 = Asciz AXIS_GENERIC_15; +const #135 = int 46; +const #136 = Asciz AXIS_GENERIC_16; +const #137 = int 47; +const #138 = Asciz BUTTON_PRIMARY; +const #139 = Asciz BUTTON_SECONDARY; +const #140 = Asciz BUTTON_TERTIARY; +const #141 = Asciz BUTTON_BACK; +const #142 = Asciz BUTTON_FORWARD; +const #143 = Asciz TOOL_TYPE_UNKNOWN; +const #144 = Asciz TOOL_TYPE_FINGER; +const #145 = Asciz TOOL_TYPE_STYLUS; +const #146 = Asciz TOOL_TYPE_MOUSE; +const #147 = Asciz TOOL_TYPE_ERASER; +const #148 = Asciz CREATOR; +const #149 = class #303; // android/os/Parcelable$Creator +const #150 = Asciz Creator; +const #151 = Asciz Landroid/os/Parcelable$Creator;; +const #152 = Asciz Signature; +const #153 = Asciz Landroid/os/Parcelable$Creator<Landroid/view/MotionEvent;>;; +const #154 = Asciz <init>; +const #155 = Asciz ()V; +const #156 = Asciz Code; +const #157 = Asciz LineNumberTable; +const #158 = Asciz LocalVariableTable; +const #159 = Asciz this; +const #160 = Asciz Landroid/view/MotionEvent;; +const #161 = Asciz finalize; +const #162 = Asciz Exceptions; +const #163 = class #304; // java/lang/Throwable +const #164 = Asciz obtain; +const #165 = Asciz (JJII[Landroid/view/MotionEvent$PointerProperties;[Landroid/view/MotionEvent$PointerCoords;IIFFIIII)Landroid/view/MotionEvent;; +const #166 = Asciz downTime; +const #167 = Asciz J; +const #168 = Asciz eventTime; +const #169 = Asciz action; +const #170 = Asciz pointerCount; +const #171 = Asciz pointerProperties; +const #172 = Asciz [Landroid/view/MotionEvent$PointerProperties;; +const #173 = Asciz pointerCoords; +const #174 = Asciz [Landroid/view/MotionEvent$PointerCoords;; +const #175 = Asciz metaState; +const #176 = Asciz buttonState; +const #177 = Asciz xPrecision; +const #178 = Asciz F; +const #179 = Asciz yPrecision; +const #180 = Asciz deviceId; +const #181 = Asciz edgeFlags; +const #182 = Asciz source; +const #183 = Asciz flags; +const #184 = Asciz (JJII[I[Landroid/view/MotionEvent$PointerCoords;IFFIIII)Landroid/view/MotionEvent;; +const #185 = Asciz pointerIds; +const #186 = Asciz [I; +const #187 = Asciz (JJIFFFFIFFII)Landroid/view/MotionEvent;; +const #188 = Asciz x; +const #189 = Asciz y; +const #190 = Asciz pressure; +const #191 = Asciz size; +const #192 = Asciz (JJIIFFFFIFFII)Landroid/view/MotionEvent;; +const #193 = Asciz (JJIFFI)Landroid/view/MotionEvent;; +const #194 = Asciz (Landroid/view/MotionEvent;)Landroid/view/MotionEvent;; +const #195 = Asciz other; +const #196 = Asciz obtainNoHistory; +const #197 = Asciz recycle; +const #198 = Asciz getDeviceId; +const #199 = Asciz ()I; +const #200 = Asciz getSource; +const #201 = Asciz setSource; +const #202 = Asciz (I)V; +const #203 = Asciz getAction; +const #204 = Asciz getActionMasked; +const #205 = Asciz getActionIndex; +const #206 = Asciz getFlags; +const #207 = Asciz getDownTime; +const #208 = Asciz ()J; +const #209 = Asciz getEventTime; +const #210 = Asciz getX; +const #211 = Asciz ()F; +const #212 = Asciz getY; +const #213 = Asciz getPressure; +const #214 = Asciz getSize; +const #215 = Asciz getTouchMajor; +const #216 = Asciz getTouchMinor; +const #217 = Asciz getToolMajor; +const #218 = Asciz getToolMinor; +const #219 = Asciz getOrientation; +const #220 = Asciz getAxisValue; +const #221 = Asciz (I)F; +const #222 = Asciz axis; +const #223 = Asciz getPointerCount; +const #224 = Asciz getPointerId; +const #225 = Asciz (I)I; +const #226 = Asciz pointerIndex; +const #227 = Asciz getToolType; +const #228 = Asciz findPointerIndex; +const #229 = Asciz pointerId; +const #230 = Asciz (II)F; +const #231 = Asciz getPointerCoords; +const #232 = Asciz (ILandroid/view/MotionEvent$PointerCoords;)V; +const #233 = Asciz outPointerCoords; +const #234 = Asciz Landroid/view/MotionEvent$PointerCoords;; +const #235 = Asciz getPointerProperties; +const #236 = Asciz (ILandroid/view/MotionEvent$PointerProperties;)V; +const #237 = Asciz outPointerProperties; +const #238 = Asciz Landroid/view/MotionEvent$PointerProperties;; +const #239 = Asciz getMetaState; +const #240 = Asciz getButtonState; +const #241 = Asciz getRawX; +const #242 = Asciz getRawY; +const #243 = Asciz getXPrecision; +const #244 = Asciz getYPrecision; +const #245 = Asciz getHistorySize; +const #246 = Asciz getHistoricalEventTime; +const #247 = Asciz (I)J; +const #248 = Asciz pos; +const #249 = Asciz getHistoricalX; +const #250 = Asciz getHistoricalY; +const #251 = Asciz getHistoricalPressure; +const #252 = Asciz getHistoricalSize; +const #253 = Asciz getHistoricalTouchMajor; +const #254 = Asciz getHistoricalTouchMinor; +const #255 = Asciz getHistoricalToolMajor; +const #256 = Asciz getHistoricalToolMinor; +const #257 = Asciz getHistoricalOrientation; +const #258 = Asciz getHistoricalAxisValue; +const #259 = Asciz (III)F; +const #260 = Asciz getHistoricalPointerCoords; +const #261 = Asciz (IILandroid/view/MotionEvent$PointerCoords;)V; +const #262 = Asciz getEdgeFlags; +const #263 = Asciz setEdgeFlags; +const #264 = Asciz setAction; +const #265 = Asciz offsetLocation; +const #266 = Asciz (FF)V; +const #267 = Asciz deltaX; +const #268 = Asciz deltaY; +const #269 = Asciz setLocation; +const #270 = Asciz transform; +const #271 = Asciz (Landroid/graphics/Matrix;)V; +const #272 = Asciz matrix; +const #273 = Asciz Landroid/graphics/Matrix;; +const #274 = Asciz addBatch; +const #275 = Asciz (JFFFFI)V; +const #276 = Asciz (J[Landroid/view/MotionEvent$PointerCoords;I)V; +const #277 = Asciz toString; +const #278 = Asciz ()Ljava/lang/String;; +const #279 = Asciz actionToString; +const #280 = Asciz (I)Ljava/lang/String;; +const #281 = Asciz axisToString; +const #282 = Asciz axisFromString; +const #283 = Asciz (Ljava/lang/String;)I; +const #284 = Asciz symbolicName; +const #285 = Asciz Ljava/lang/String;; +const #286 = Asciz writeToParcel; +const #287 = Asciz (Landroid/os/Parcel;I)V; +const #288 = Asciz out; +const #289 = Asciz Landroid/os/Parcel;; +const #290 = Asciz <clinit>; +const #291 = Asciz SourceFile; +const #292 = Asciz MotionEvent.java; +const #293 = NameAndType #154:#155;// "<init>":()V +const #294 = Asciz java/lang/RuntimeException; +const #295 = Asciz Stub!; +const #296 = NameAndType #154:#305;// "<init>":(Ljava/lang/String;)V +const #297 = NameAndType #148:#151;// CREATOR:Landroid/os/Parcelable$Creator; +const #298 = Asciz android/view/MotionEvent; +const #299 = Asciz android/view/InputEvent; +const #300 = Asciz android/os/Parcelable; +const #301 = Asciz android/view/MotionEvent$PointerProperties; +const #302 = Asciz android/view/MotionEvent$PointerCoords; +const #303 = Asciz android/os/Parcelable$Creator; +const #304 = Asciz java/lang/Throwable; +const #305 = Asciz (Ljava/lang/String;)V; + +{ +public static final int INVALID_POINTER_ID; + Signature: I + Constant value: int -1 + +public static final int ACTION_MASK; + Signature: I + Constant value: int 255 + +public static final int ACTION_DOWN; + Signature: I + Constant value: int 0 + +public static final int ACTION_UP; + Signature: I + Constant value: int 1 + +public static final int ACTION_MOVE; + Signature: I + Constant value: int 2 + +public static final int ACTION_CANCEL; + Signature: I + Constant value: int 3 + +public static final int ACTION_OUTSIDE; + Signature: I + Constant value: int 4 + +public static final int ACTION_POINTER_DOWN; + Signature: I + Constant value: int 5 + +public static final int ACTION_POINTER_UP; + Signature: I + Constant value: int 6 + +public static final int ACTION_HOVER_MOVE; + Signature: I + Constant value: int 7 + +public static final int ACTION_SCROLL; + Signature: I + Constant value: int 8 + +public static final int ACTION_HOVER_ENTER; + Signature: I + Constant value: int 9 + +public static final int ACTION_HOVER_EXIT; + Signature: I + Constant value: int 10 + +public static final int ACTION_POINTER_INDEX_MASK; + Signature: I + Constant value: int 65280 + +public static final int ACTION_POINTER_INDEX_SHIFT; + Signature: I + Constant value: int 8 + +public static final int ACTION_POINTER_1_DOWN; + Signature: I + Constant value: int 5Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + + +public static final int ACTION_POINTER_2_DOWN; + Signature: I + Constant value: int 261Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + + +public static final int ACTION_POINTER_3_DOWN; + Signature: I + Constant value: int 517Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + + +public static final int ACTION_POINTER_1_UP; + Signature: I + Constant value: int 6Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + + +public static final int ACTION_POINTER_2_UP; + Signature: I + Constant value: int 262Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + + +public static final int ACTION_POINTER_3_UP; + Signature: I + Constant value: int 518Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + + +public static final int ACTION_POINTER_ID_MASK; + Signature: I + Constant value: int 65280Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + + +public static final int ACTION_POINTER_ID_SHIFT; + Signature: I + Constant value: int 8Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + + +public static final int FLAG_WINDOW_IS_OBSCURED; + Signature: I + Constant value: int 1 + +public static final int EDGE_TOP; + Signature: I + Constant value: int 1 + +public static final int EDGE_BOTTOM; + Signature: I + Constant value: int 2 + +public static final int EDGE_LEFT; + Signature: I + Constant value: int 4 + +public static final int EDGE_RIGHT; + Signature: I + Constant value: int 8 + +public static final int AXIS_X; + Signature: I + Constant value: int 0 + +public static final int AXIS_Y; + Signature: I + Constant value: int 1 + +public static final int AXIS_PRESSURE; + Signature: I + Constant value: int 2 + +public static final int AXIS_SIZE; + Signature: I + Constant value: int 3 + +public static final int AXIS_TOUCH_MAJOR; + Signature: I + Constant value: int 4 + +public static final int AXIS_TOUCH_MINOR; + Signature: I + Constant value: int 5 + +public static final int AXIS_TOOL_MAJOR; + Signature: I + Constant value: int 6 + +public static final int AXIS_TOOL_MINOR; + Signature: I + Constant value: int 7 + +public static final int AXIS_ORIENTATION; + Signature: I + Constant value: int 8 + +public static final int AXIS_VSCROLL; + Signature: I + Constant value: int 9 + +public static final int AXIS_HSCROLL; + Signature: I + Constant value: int 10 + +public static final int AXIS_Z; + Signature: I + Constant value: int 11 + +public static final int AXIS_RX; + Signature: I + Constant value: int 12 + +public static final int AXIS_RY; + Signature: I + Constant value: int 13 + +public static final int AXIS_RZ; + Signature: I + Constant value: int 14 + +public static final int AXIS_HAT_X; + Signature: I + Constant value: int 15 + +public static final int AXIS_HAT_Y; + Signature: I + Constant value: int 16 + +public static final int AXIS_LTRIGGER; + Signature: I + Constant value: int 17 + +public static final int AXIS_RTRIGGER; + Signature: I + Constant value: int 18 + +public static final int AXIS_THROTTLE; + Signature: I + Constant value: int 19 + +public static final int AXIS_RUDDER; + Signature: I + Constant value: int 20 + +public static final int AXIS_WHEEL; + Signature: I + Constant value: int 21 + +public static final int AXIS_GAS; + Signature: I + Constant value: int 22 + +public static final int AXIS_BRAKE; + Signature: I + Constant value: int 23 + +public static final int AXIS_DISTANCE; + Signature: I + Constant value: int 24 + +public static final int AXIS_TILT; + Signature: I + Constant value: int 25 + +public static final int AXIS_GENERIC_1; + Signature: I + Constant value: int 32 + +public static final int AXIS_GENERIC_2; + Signature: I + Constant value: int 33 + +public static final int AXIS_GENERIC_3; + Signature: I + Constant value: int 34 + +public static final int AXIS_GENERIC_4; + Signature: I + Constant value: int 35 + +public static final int AXIS_GENERIC_5; + Signature: I + Constant value: int 36 + +public static final int AXIS_GENERIC_6; + Signature: I + Constant value: int 37 + +public static final int AXIS_GENERIC_7; + Signature: I + Constant value: int 38 + +public static final int AXIS_GENERIC_8; + Signature: I + Constant value: int 39 + +public static final int AXIS_GENERIC_9; + Signature: I + Constant value: int 40 + +public static final int AXIS_GENERIC_10; + Signature: I + Constant value: int 41 + +public static final int AXIS_GENERIC_11; + Signature: I + Constant value: int 42 + +public static final int AXIS_GENERIC_12; + Signature: I + Constant value: int 43 + +public static final int AXIS_GENERIC_13; + Signature: I + Constant value: int 44 + +public static final int AXIS_GENERIC_14; + Signature: I + Constant value: int 45 + +public static final int AXIS_GENERIC_15; + Signature: I + Constant value: int 46 + +public static final int AXIS_GENERIC_16; + Signature: I + Constant value: int 47 + +public static final int BUTTON_PRIMARY; + Signature: I + Constant value: int 1 + +public static final int BUTTON_SECONDARY; + Signature: I + Constant value: int 2 + +public static final int BUTTON_TERTIARY; + Signature: I + Constant value: int 4 + +public static final int BUTTON_BACK; + Signature: I + Constant value: int 8 + +public static final int BUTTON_FORWARD; + Signature: I + Constant value: int 16 + +public static final int TOOL_TYPE_UNKNOWN; + Signature: I + Constant value: int 0 + +public static final int TOOL_TYPE_FINGER; + Signature: I + Constant value: int 1 + +public static final int TOOL_TYPE_STYLUS; + Signature: I + Constant value: int 2 + +public static final int TOOL_TYPE_MOUSE; + Signature: I + Constant value: int 3 + +public static final int TOOL_TYPE_ERASER; + Signature: I + Constant value: int 4 + +public static final android.os.Parcelable$Creator CREATOR; + Signature: Landroid/os/Parcelable$Creator; + Signature: length = 0x2 + 00 FFFFFF99 + + +android.view.MotionEvent(); + Signature: ()V + Code: + Stack=3, Locals=1, Args_size=1 + 0: aload_0 + 1: invokespecial #1; //Method android/view/InputEvent."<init>":()V + 4: new #2; //class java/lang/RuntimeException + 7: dup + 8: ldc #3; //String Stub! + 10: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 13: athrow + LineNumberTable: + line 35: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 14 0 this Landroid/view/MotionEvent; + + +protected void finalize() throws java.lang.Throwable; + Signature: ()V + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 36: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + Exceptions: + throws java.lang.Throwable +public static android.view.MotionEvent obtain(long, long, int, int, android.view.MotionEvent$PointerProperties[], android.view.MotionEvent$PointerCoords[], int, int, float, float, int, int, int, int); + Signature: (JJII[Landroid/view/MotionEvent$PointerProperties;[Landroid/view/MotionEvent$PointerCoords;IIFFIIII)Landroid/view/MotionEvent; + Code: + Stack=3, Locals=16, Args_size=14 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 37: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 pointerCount I + 0 10 6 pointerProperties [Landroid/view/MotionEvent$PointerProperties; + 0 10 7 pointerCoords [Landroid/view/MotionEvent$PointerCoords; + 0 10 8 metaState I + 0 10 9 buttonState I + 0 10 10 xPrecision F + 0 10 11 yPrecision F + 0 10 12 deviceId I + 0 10 13 edgeFlags I + 0 10 14 source I + 0 10 15 flags I + + +public static android.view.MotionEvent obtain(long, long, int, int, int[], android.view.MotionEvent$PointerCoords[], int, float, float, int, int, int, int); + Signature: (JJII[I[Landroid/view/MotionEvent$PointerCoords;IFFIIII)Landroid/view/MotionEvent; + Code: + Stack=3, Locals=15, Args_size=13 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 39: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 pointerCount I + 0 10 6 pointerIds [I + 0 10 7 pointerCoords [Landroid/view/MotionEvent$PointerCoords; + 0 10 8 metaState I + 0 10 9 xPrecision F + 0 10 10 yPrecision F + 0 10 11 deviceId I + 0 10 12 edgeFlags I + 0 10 13 source I + 0 10 14 flags I + + Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + +public static android.view.MotionEvent obtain(long, long, int, float, float, float, float, int, float, float, int, int); + Signature: (JJIFFFFIFFII)Landroid/view/MotionEvent; + Code: + Stack=3, Locals=14, Args_size=12 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 40: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 x F + 0 10 6 y F + 0 10 7 pressure F + 0 10 8 size F + 0 10 9 metaState I + 0 10 10 xPrecision F + 0 10 11 yPrecision F + 0 10 12 deviceId I + 0 10 13 edgeFlags I + + +public static android.view.MotionEvent obtain(long, long, int, int, float, float, float, float, int, float, float, int, int); + Signature: (JJIIFFFFIFFII)Landroid/view/MotionEvent; + Code: + Stack=3, Locals=15, Args_size=13 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 42: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 pointerCount I + 0 10 6 x F + 0 10 7 y F + 0 10 8 pressure F + 0 10 9 size F + 0 10 10 metaState I + 0 10 11 xPrecision F + 0 10 12 yPrecision F + 0 10 13 deviceId I + 0 10 14 edgeFlags I + + Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + +public static android.view.MotionEvent obtain(long, long, int, float, float, int); + Signature: (JJIFFI)Landroid/view/MotionEvent; + Code: + Stack=3, Locals=8, Args_size=6 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 43: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 x F + 0 10 6 y F + 0 10 7 metaState I + + +public static android.view.MotionEvent obtain(android.view.MotionEvent); + Signature: (Landroid/view/MotionEvent;)Landroid/view/MotionEvent; + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 44: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 other Landroid/view/MotionEvent; + + +public static android.view.MotionEvent obtainNoHistory(android.view.MotionEvent); + Signature: (Landroid/view/MotionEvent;)Landroid/view/MotionEvent; + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 45: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 other Landroid/view/MotionEvent; + + +public final void recycle(); + Signature: ()V + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 46: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final int getDeviceId(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 47: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final int getSource(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 48: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final void setSource(int); + Signature: (I)V + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 49: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 source I + + +public final int getAction(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 50: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final int getActionMasked(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 51: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final int getActionIndex(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 52: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final int getFlags(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 53: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final long getDownTime(); + Signature: ()J + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 54: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final long getEventTime(); + Signature: ()J + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 55: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getX(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 56: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getY(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 57: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getPressure(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 58: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getSize(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 59: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getTouchMajor(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 60: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getTouchMinor(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 61: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getToolMajor(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 62: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getToolMinor(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 63: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getOrientation(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 64: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getAxisValue(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 65: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 axis I + + +public final int getPointerCount(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 66: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final int getPointerId(int); + Signature: (I)I + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 67: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final int getToolType(int); + Signature: (I)I + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 68: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final int findPointerIndex(int); + Signature: (I)I + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 69: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerId I + + +public final float getX(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 70: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final float getY(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 71: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final float getPressure(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 72: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final float getSize(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 73: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final float getTouchMajor(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 74: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final float getTouchMinor(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 75: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final float getToolMajor(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 76: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final float getToolMinor(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 77: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final float getOrientation(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 78: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final float getAxisValue(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 79: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 axis I + 0 10 2 pointerIndex I + + +public final void getPointerCoords(int, android.view.MotionEvent$PointerCoords); + Signature: (ILandroid/view/MotionEvent$PointerCoords;)V + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 80: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 outPointerCoords Landroid/view/MotionEvent$PointerCoords; + + +public final void getPointerProperties(int, android.view.MotionEvent$PointerProperties); + Signature: (ILandroid/view/MotionEvent$PointerProperties;)V + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 81: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 outPointerProperties Landroid/view/MotionEvent$PointerProperties; + + +public final int getMetaState(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 82: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final int getButtonState(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 83: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getRawX(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 84: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getRawY(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 85: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getXPrecision(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 86: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getYPrecision(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 87: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final int getHistorySize(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 88: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final long getHistoricalEventTime(int); + Signature: (I)J + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 89: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalX(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 90: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalY(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 91: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalPressure(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 92: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalSize(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 93: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalTouchMajor(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 94: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalTouchMinor(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 95: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalToolMajor(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 96: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalToolMinor(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 97: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalOrientation(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 98: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalAxisValue(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 99: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 axis I + 0 10 2 pos I + + +public final float getHistoricalX(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 100: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + +public final float getHistoricalY(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 101: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + +public final float getHistoricalPressure(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 102: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + +public final float getHistoricalSize(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 103: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + +public final float getHistoricalTouchMajor(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 104: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + +public final float getHistoricalTouchMinor(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 105: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + +public final float getHistoricalToolMajor(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 106: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + +public final float getHistoricalToolMinor(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 107: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + +public final float getHistoricalOrientation(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 108: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + +public final float getHistoricalAxisValue(int, int, int); + Signature: (III)F + Code: + Stack=3, Locals=4, Args_size=4 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 109: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 axis I + 0 10 2 pointerIndex I + 0 10 3 pos I + + +public final void getHistoricalPointerCoords(int, int, android.view.MotionEvent$PointerCoords); + Signature: (IILandroid/view/MotionEvent$PointerCoords;)V + Code: + Stack=3, Locals=4, Args_size=4 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 110: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + 0 10 3 outPointerCoords Landroid/view/MotionEvent$PointerCoords; + + +public final int getEdgeFlags(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 111: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final void setEdgeFlags(int); + Signature: (I)V + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 112: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 flags I + + +public final void setAction(int); + Signature: (I)V + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 113: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 action I + + +public final void offsetLocation(float, float); + Signature: (FF)V + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 114: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 deltaX F + 0 10 2 deltaY F + + +public final void setLocation(float, float); + Signature: (FF)V + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 115: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 x F + 0 10 2 y F + + +public final void transform(android.graphics.Matrix); + Signature: (Landroid/graphics/Matrix;)V + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 116: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 matrix Landroid/graphics/Matrix; + + +public final void addBatch(long, float, float, float, float, int); + Signature: (JFFFFI)V + Code: + Stack=3, Locals=8, Args_size=7 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 117: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 eventTime J + 0 10 3 x F + 0 10 4 y F + 0 10 5 pressure F + 0 10 6 size F + 0 10 7 metaState I + + +public final void addBatch(long, android.view.MotionEvent$PointerCoords[], int); + Signature: (J[Landroid/view/MotionEvent$PointerCoords;I)V + Code: + Stack=3, Locals=5, Args_size=4 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 118: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 eventTime J + 0 10 3 pointerCoords [Landroid/view/MotionEvent$PointerCoords; + 0 10 4 metaState I + + +public java.lang.String toString(); + Signature: ()Ljava/lang/String; + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 119: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public static java.lang.String actionToString(int); + Signature: (I)Ljava/lang/String; + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 120: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 action I + + +public static java.lang.String axisToString(int); + Signature: (I)Ljava/lang/String; + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 121: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 axis I + + +public static int axisFromString(java.lang.String); + Signature: (Ljava/lang/String;)I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 122: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 symbolicName Ljava/lang/String; + + +public void writeToParcel(android.os.Parcel, int); + Signature: (Landroid/os/Parcel;I)V + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 123: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 out Landroid/os/Parcel; + 0 10 2 flags I + + +static {}; + Signature: ()V + Code: + Stack=1, Locals=0, Args_size=0 + 0: aconst_null + 1: putstatic #5; //Field CREATOR:Landroid/os/Parcelable$Creator; + 4: return + LineNumberTable: + line 213: 0 + + +} +
diff --git a/src/base/android/jni_generator/testMotionEvent.javap7 b/src/base/android/jni_generator/testMotionEvent.javap7 new file mode 100644 index 0000000..f4f5444 --- /dev/null +++ b/src/base/android/jni_generator/testMotionEvent.javap7
@@ -0,0 +1,2370 @@ +Classfile out_android/Debug/gen/content/jni/android/view/MotionEvent.class + Last modified Feb 27, 2014; size 13369 bytes + MD5 checksum 3718d77a994cb8aceb7b35c5df3c4dd1 + Compiled from "MotionEvent.java" +public final class android.view.MotionEvent extends android.view.InputEvent implements android.os.Parcelable + SourceFile: "MotionEvent.java" + InnerClasses: + public static final #10= #9 of #6; //PointerProperties=class android/view/MotionEvent$PointerProperties of class android/view/MotionEvent + public static final #13= #12 of #6; //PointerCoords=class android/view/MotionEvent$PointerCoords of class android/view/MotionEvent + public static #150= #149 of #8; //Creator=class android/os/Parcelable$Creator of class android/os/Parcelable + minor version: 0 + major version: 49 + flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER +Constant pool: + #1 = Methodref #7.#293 // android/view/InputEvent."<init>":()V + #2 = Class #294 // java/lang/RuntimeException + #3 = String #295 // Stub! + #4 = Methodref #2.#296 // java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + #5 = Fieldref #6.#297 // android/view/MotionEvent.CREATOR:Landroid/os/Parcelable$Creator; + #6 = Class #298 // android/view/MotionEvent + #7 = Class #299 // android/view/InputEvent + #8 = Class #300 // android/os/Parcelable + #9 = Class #301 // android/view/MotionEvent$PointerProperties + #10 = Utf8 PointerProperties + #11 = Utf8 InnerClasses + #12 = Class #302 // android/view/MotionEvent$PointerCoords + #13 = Utf8 PointerCoords + #14 = Utf8 INVALID_POINTER_ID + #15 = Utf8 I + #16 = Utf8 ConstantValue + #17 = Integer -1 + #18 = Utf8 ACTION_MASK + #19 = Integer 255 + #20 = Utf8 ACTION_DOWN + #21 = Integer 0 + #22 = Utf8 ACTION_UP + #23 = Integer 1 + #24 = Utf8 ACTION_MOVE + #25 = Integer 2 + #26 = Utf8 ACTION_CANCEL + #27 = Integer 3 + #28 = Utf8 ACTION_OUTSIDE + #29 = Integer 4 + #30 = Utf8 ACTION_POINTER_DOWN + #31 = Integer 5 + #32 = Utf8 ACTION_POINTER_UP + #33 = Integer 6 + #34 = Utf8 ACTION_HOVER_MOVE + #35 = Integer 7 + #36 = Utf8 ACTION_SCROLL + #37 = Integer 8 + #38 = Utf8 ACTION_HOVER_ENTER + #39 = Integer 9 + #40 = Utf8 ACTION_HOVER_EXIT + #41 = Integer 10 + #42 = Utf8 ACTION_POINTER_INDEX_MASK + #43 = Integer 65280 + #44 = Utf8 ACTION_POINTER_INDEX_SHIFT + #45 = Utf8 ACTION_POINTER_1_DOWN + #46 = Utf8 Deprecated + #47 = Utf8 RuntimeVisibleAnnotations + #48 = Utf8 Ljava/lang/Deprecated; + #49 = Utf8 ACTION_POINTER_2_DOWN + #50 = Integer 261 + #51 = Utf8 ACTION_POINTER_3_DOWN + #52 = Integer 517 + #53 = Utf8 ACTION_POINTER_1_UP + #54 = Utf8 ACTION_POINTER_2_UP + #55 = Integer 262 + #56 = Utf8 ACTION_POINTER_3_UP + #57 = Integer 518 + #58 = Utf8 ACTION_POINTER_ID_MASK + #59 = Utf8 ACTION_POINTER_ID_SHIFT + #60 = Utf8 FLAG_WINDOW_IS_OBSCURED + #61 = Utf8 EDGE_TOP + #62 = Utf8 EDGE_BOTTOM + #63 = Utf8 EDGE_LEFT + #64 = Utf8 EDGE_RIGHT + #65 = Utf8 AXIS_X + #66 = Utf8 AXIS_Y + #67 = Utf8 AXIS_PRESSURE + #68 = Utf8 AXIS_SIZE + #69 = Utf8 AXIS_TOUCH_MAJOR + #70 = Utf8 AXIS_TOUCH_MINOR + #71 = Utf8 AXIS_TOOL_MAJOR + #72 = Utf8 AXIS_TOOL_MINOR + #73 = Utf8 AXIS_ORIENTATION + #74 = Utf8 AXIS_VSCROLL + #75 = Utf8 AXIS_HSCROLL + #76 = Utf8 AXIS_Z + #77 = Integer 11 + #78 = Utf8 AXIS_RX + #79 = Integer 12 + #80 = Utf8 AXIS_RY + #81 = Integer 13 + #82 = Utf8 AXIS_RZ + #83 = Integer 14 + #84 = Utf8 AXIS_HAT_X + #85 = Integer 15 + #86 = Utf8 AXIS_HAT_Y + #87 = Integer 16 + #88 = Utf8 AXIS_LTRIGGER + #89 = Integer 17 + #90 = Utf8 AXIS_RTRIGGER + #91 = Integer 18 + #92 = Utf8 AXIS_THROTTLE + #93 = Integer 19 + #94 = Utf8 AXIS_RUDDER + #95 = Integer 20 + #96 = Utf8 AXIS_WHEEL + #97 = Integer 21 + #98 = Utf8 AXIS_GAS + #99 = Integer 22 + #100 = Utf8 AXIS_BRAKE + #101 = Integer 23 + #102 = Utf8 AXIS_DISTANCE + #103 = Integer 24 + #104 = Utf8 AXIS_TILT + #105 = Integer 25 + #106 = Utf8 AXIS_GENERIC_1 + #107 = Integer 32 + #108 = Utf8 AXIS_GENERIC_2 + #109 = Integer 33 + #110 = Utf8 AXIS_GENERIC_3 + #111 = Integer 34 + #112 = Utf8 AXIS_GENERIC_4 + #113 = Integer 35 + #114 = Utf8 AXIS_GENERIC_5 + #115 = Integer 36 + #116 = Utf8 AXIS_GENERIC_6 + #117 = Integer 37 + #118 = Utf8 AXIS_GENERIC_7 + #119 = Integer 38 + #120 = Utf8 AXIS_GENERIC_8 + #121 = Integer 39 + #122 = Utf8 AXIS_GENERIC_9 + #123 = Integer 40 + #124 = Utf8 AXIS_GENERIC_10 + #125 = Integer 41 + #126 = Utf8 AXIS_GENERIC_11 + #127 = Integer 42 + #128 = Utf8 AXIS_GENERIC_12 + #129 = Integer 43 + #130 = Utf8 AXIS_GENERIC_13 + #131 = Integer 44 + #132 = Utf8 AXIS_GENERIC_14 + #133 = Integer 45 + #134 = Utf8 AXIS_GENERIC_15 + #135 = Integer 46 + #136 = Utf8 AXIS_GENERIC_16 + #137 = Integer 47 + #138 = Utf8 BUTTON_PRIMARY + #139 = Utf8 BUTTON_SECONDARY + #140 = Utf8 BUTTON_TERTIARY + #141 = Utf8 BUTTON_BACK + #142 = Utf8 BUTTON_FORWARD + #143 = Utf8 TOOL_TYPE_UNKNOWN + #144 = Utf8 TOOL_TYPE_FINGER + #145 = Utf8 TOOL_TYPE_STYLUS + #146 = Utf8 TOOL_TYPE_MOUSE + #147 = Utf8 TOOL_TYPE_ERASER + #148 = Utf8 CREATOR + #149 = Class #303 // android/os/Parcelable$Creator + #150 = Utf8 Creator + #151 = Utf8 Landroid/os/Parcelable$Creator; + #152 = Utf8 Signature + #153 = Utf8 Landroid/os/Parcelable$Creator<Landroid/view/MotionEvent;>; + #154 = Utf8 <init> + #155 = Utf8 ()V + #156 = Utf8 Code + #157 = Utf8 LineNumberTable + #158 = Utf8 LocalVariableTable + #159 = Utf8 this + #160 = Utf8 Landroid/view/MotionEvent; + #161 = Utf8 finalize + #162 = Utf8 Exceptions + #163 = Class #304 // java/lang/Throwable + #164 = Utf8 obtain + #165 = Utf8 (JJII[Landroid/view/MotionEvent$PointerProperties;[Landroid/view/MotionEvent$PointerCoords;IIFFIIII)Landroid/view/MotionEvent; + #166 = Utf8 downTime + #167 = Utf8 J + #168 = Utf8 eventTime + #169 = Utf8 action + #170 = Utf8 pointerCount + #171 = Utf8 pointerProperties + #172 = Utf8 [Landroid/view/MotionEvent$PointerProperties; + #173 = Utf8 pointerCoords + #174 = Utf8 [Landroid/view/MotionEvent$PointerCoords; + #175 = Utf8 metaState + #176 = Utf8 buttonState + #177 = Utf8 xPrecision + #178 = Utf8 F + #179 = Utf8 yPrecision + #180 = Utf8 deviceId + #181 = Utf8 edgeFlags + #182 = Utf8 source + #183 = Utf8 flags + #184 = Utf8 (JJII[I[Landroid/view/MotionEvent$PointerCoords;IFFIIII)Landroid/view/MotionEvent; + #185 = Utf8 pointerIds + #186 = Utf8 [I + #187 = Utf8 (JJIFFFFIFFII)Landroid/view/MotionEvent; + #188 = Utf8 x + #189 = Utf8 y + #190 = Utf8 pressure + #191 = Utf8 size + #192 = Utf8 (JJIIFFFFIFFII)Landroid/view/MotionEvent; + #193 = Utf8 (JJIFFI)Landroid/view/MotionEvent; + #194 = Utf8 (Landroid/view/MotionEvent;)Landroid/view/MotionEvent; + #195 = Utf8 other + #196 = Utf8 obtainNoHistory + #197 = Utf8 recycle + #198 = Utf8 getDeviceId + #199 = Utf8 ()I + #200 = Utf8 getSource + #201 = Utf8 setSource + #202 = Utf8 (I)V + #203 = Utf8 getAction + #204 = Utf8 getActionMasked + #205 = Utf8 getActionIndex + #206 = Utf8 getFlags + #207 = Utf8 getDownTime + #208 = Utf8 ()J + #209 = Utf8 getEventTime + #210 = Utf8 getX + #211 = Utf8 ()F + #212 = Utf8 getY + #213 = Utf8 getPressure + #214 = Utf8 getSize + #215 = Utf8 getTouchMajor + #216 = Utf8 getTouchMinor + #217 = Utf8 getToolMajor + #218 = Utf8 getToolMinor + #219 = Utf8 getOrientation + #220 = Utf8 getAxisValue + #221 = Utf8 (I)F + #222 = Utf8 axis + #223 = Utf8 getPointerCount + #224 = Utf8 getPointerId + #225 = Utf8 (I)I + #226 = Utf8 pointerIndex + #227 = Utf8 getToolType + #228 = Utf8 findPointerIndex + #229 = Utf8 pointerId + #230 = Utf8 (II)F + #231 = Utf8 getPointerCoords + #232 = Utf8 (ILandroid/view/MotionEvent$PointerCoords;)V + #233 = Utf8 outPointerCoords + #234 = Utf8 Landroid/view/MotionEvent$PointerCoords; + #235 = Utf8 getPointerProperties + #236 = Utf8 (ILandroid/view/MotionEvent$PointerProperties;)V + #237 = Utf8 outPointerProperties + #238 = Utf8 Landroid/view/MotionEvent$PointerProperties; + #239 = Utf8 getMetaState + #240 = Utf8 getButtonState + #241 = Utf8 getRawX + #242 = Utf8 getRawY + #243 = Utf8 getXPrecision + #244 = Utf8 getYPrecision + #245 = Utf8 getHistorySize + #246 = Utf8 getHistoricalEventTime + #247 = Utf8 (I)J + #248 = Utf8 pos + #249 = Utf8 getHistoricalX + #250 = Utf8 getHistoricalY + #251 = Utf8 getHistoricalPressure + #252 = Utf8 getHistoricalSize + #253 = Utf8 getHistoricalTouchMajor + #254 = Utf8 getHistoricalTouchMinor + #255 = Utf8 getHistoricalToolMajor + #256 = Utf8 getHistoricalToolMinor + #257 = Utf8 getHistoricalOrientation + #258 = Utf8 getHistoricalAxisValue + #259 = Utf8 (III)F + #260 = Utf8 getHistoricalPointerCoords + #261 = Utf8 (IILandroid/view/MotionEvent$PointerCoords;)V + #262 = Utf8 getEdgeFlags + #263 = Utf8 setEdgeFlags + #264 = Utf8 setAction + #265 = Utf8 offsetLocation + #266 = Utf8 (FF)V + #267 = Utf8 deltaX + #268 = Utf8 deltaY + #269 = Utf8 setLocation + #270 = Utf8 transform + #271 = Utf8 (Landroid/graphics/Matrix;)V + #272 = Utf8 matrix + #273 = Utf8 Landroid/graphics/Matrix; + #274 = Utf8 addBatch + #275 = Utf8 (JFFFFI)V + #276 = Utf8 (J[Landroid/view/MotionEvent$PointerCoords;I)V + #277 = Utf8 toString + #278 = Utf8 ()Ljava/lang/String; + #279 = Utf8 actionToString + #280 = Utf8 (I)Ljava/lang/String; + #281 = Utf8 axisToString + #282 = Utf8 axisFromString + #283 = Utf8 (Ljava/lang/String;)I + #284 = Utf8 symbolicName + #285 = Utf8 Ljava/lang/String; + #286 = Utf8 writeToParcel + #287 = Utf8 (Landroid/os/Parcel;I)V + #288 = Utf8 out + #289 = Utf8 Landroid/os/Parcel; + #290 = Utf8 <clinit> + #291 = Utf8 SourceFile + #292 = Utf8 MotionEvent.java + #293 = NameAndType #154:#155 // "<init>":()V + #294 = Utf8 java/lang/RuntimeException + #295 = Utf8 Stub! + #296 = NameAndType #154:#305 // "<init>":(Ljava/lang/String;)V + #297 = NameAndType #148:#151 // CREATOR:Landroid/os/Parcelable$Creator; + #298 = Utf8 android/view/MotionEvent + #299 = Utf8 android/view/InputEvent + #300 = Utf8 android/os/Parcelable + #301 = Utf8 android/view/MotionEvent$PointerProperties + #302 = Utf8 android/view/MotionEvent$PointerCoords + #303 = Utf8 android/os/Parcelable$Creator + #304 = Utf8 java/lang/Throwable + #305 = Utf8 (Ljava/lang/String;)V +{ + public static final int INVALID_POINTER_ID; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int -1 + + + public static final int ACTION_MASK; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 255 + + + public static final int ACTION_DOWN; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 0 + + + public static final int ACTION_UP; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 1 + + + public static final int ACTION_MOVE; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 2 + + + public static final int ACTION_CANCEL; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 3 + + + public static final int ACTION_OUTSIDE; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 4 + + + public static final int ACTION_POINTER_DOWN; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 5 + + + public static final int ACTION_POINTER_UP; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 6 + + + public static final int ACTION_HOVER_MOVE; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 7 + + + public static final int ACTION_SCROLL; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 8 + + + public static final int ACTION_HOVER_ENTER; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 9 + + + public static final int ACTION_HOVER_EXIT; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 10 + + + public static final int ACTION_POINTER_INDEX_MASK; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 65280 + + + public static final int ACTION_POINTER_INDEX_SHIFT; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 8 + + + public static final int ACTION_POINTER_1_DOWN; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 5 + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + + public static final int ACTION_POINTER_2_DOWN; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 261 + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + + public static final int ACTION_POINTER_3_DOWN; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 517 + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + + public static final int ACTION_POINTER_1_UP; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 6 + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + + public static final int ACTION_POINTER_2_UP; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 262 + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + + public static final int ACTION_POINTER_3_UP; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 518 + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + + public static final int ACTION_POINTER_ID_MASK; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 65280 + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + + public static final int ACTION_POINTER_ID_SHIFT; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 8 + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + + public static final int FLAG_WINDOW_IS_OBSCURED; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 1 + + + public static final int EDGE_TOP; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 1 + + + public static final int EDGE_BOTTOM; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 2 + + + public static final int EDGE_LEFT; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 4 + + + public static final int EDGE_RIGHT; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 8 + + + public static final int AXIS_X; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 0 + + + public static final int AXIS_Y; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 1 + + + public static final int AXIS_PRESSURE; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 2 + + + public static final int AXIS_SIZE; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 3 + + + public static final int AXIS_TOUCH_MAJOR; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 4 + + + public static final int AXIS_TOUCH_MINOR; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 5 + + + public static final int AXIS_TOOL_MAJOR; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 6 + + + public static final int AXIS_TOOL_MINOR; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 7 + + + public static final int AXIS_ORIENTATION; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 8 + + + public static final int AXIS_VSCROLL; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 9 + + + public static final int AXIS_HSCROLL; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 10 + + + public static final int AXIS_Z; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 11 + + + public static final int AXIS_RX; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 12 + + + public static final int AXIS_RY; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 13 + + + public static final int AXIS_RZ; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 14 + + + public static final int AXIS_HAT_X; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 15 + + + public static final int AXIS_HAT_Y; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 16 + + + public static final int AXIS_LTRIGGER; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 17 + + + public static final int AXIS_RTRIGGER; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 18 + + + public static final int AXIS_THROTTLE; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 19 + + + public static final int AXIS_RUDDER; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 20 + + + public static final int AXIS_WHEEL; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 21 + + + public static final int AXIS_GAS; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 22 + + + public static final int AXIS_BRAKE; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 23 + + + public static final int AXIS_DISTANCE; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 24 + + + public static final int AXIS_TILT; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 25 + + + public static final int AXIS_GENERIC_1; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 32 + + + public static final int AXIS_GENERIC_2; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 33 + + + public static final int AXIS_GENERIC_3; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 34 + + + public static final int AXIS_GENERIC_4; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 35 + + + public static final int AXIS_GENERIC_5; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 36 + + + public static final int AXIS_GENERIC_6; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 37 + + + public static final int AXIS_GENERIC_7; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 38 + + + public static final int AXIS_GENERIC_8; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 39 + + + public static final int AXIS_GENERIC_9; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 40 + + + public static final int AXIS_GENERIC_10; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 41 + + + public static final int AXIS_GENERIC_11; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 42 + + + public static final int AXIS_GENERIC_12; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 43 + + + public static final int AXIS_GENERIC_13; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 44 + + + public static final int AXIS_GENERIC_14; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 45 + + + public static final int AXIS_GENERIC_15; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 46 + + + public static final int AXIS_GENERIC_16; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 47 + + + public static final int BUTTON_PRIMARY; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 1 + + + public static final int BUTTON_SECONDARY; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 2 + + + public static final int BUTTON_TERTIARY; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 4 + + + public static final int BUTTON_BACK; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 8 + + + public static final int BUTTON_FORWARD; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 16 + + + public static final int TOOL_TYPE_UNKNOWN; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 0 + + + public static final int TOOL_TYPE_FINGER; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 1 + + + public static final int TOOL_TYPE_STYLUS; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 2 + + + public static final int TOOL_TYPE_MOUSE; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 3 + + + public static final int TOOL_TYPE_ERASER; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 4 + + + public static final android.os.Parcelable$Creator<android.view.MotionEvent> CREATOR; + Signature: Landroid/os/Parcelable$Creator; + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + Signature: #153 // Landroid/os/Parcelable$Creator<Landroid/view/MotionEvent;>; + + + android.view.MotionEvent(); + Signature: ()V + flags: + Code: + stack=3, locals=1, args_size=1 + 0: aload_0 + 1: invokespecial #1 // Method android/view/InputEvent."<init>":()V + 4: new #2 // class java/lang/RuntimeException + 7: dup + 8: ldc #3 // String Stub! + 10: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 13: athrow + LineNumberTable: + line 35: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 14 0 this Landroid/view/MotionEvent; + + protected void finalize() throws java.lang.Throwable; + Signature: ()V + flags: ACC_PROTECTED + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 36: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + Exceptions: + throws java.lang.Throwable + + public static android.view.MotionEvent obtain(long, long, int, int, android.view.MotionEvent$PointerProperties[], android.view.MotionEvent$PointerCoords[], int, int, float, float, int, int, int, int); + Signature: (JJII[Landroid/view/MotionEvent$PointerProperties;[Landroid/view/MotionEvent$PointerCoords;IIFFIIII)Landroid/view/MotionEvent; + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=16, args_size=14 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 37: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 pointerCount I + 0 10 6 pointerProperties [Landroid/view/MotionEvent$PointerProperties; + 0 10 7 pointerCoords [Landroid/view/MotionEvent$PointerCoords; + 0 10 8 metaState I + 0 10 9 buttonState I + 0 10 10 xPrecision F + 0 10 11 yPrecision F + 0 10 12 deviceId I + 0 10 13 edgeFlags I + 0 10 14 source I + 0 10 15 flags I + + public static android.view.MotionEvent obtain(long, long, int, int, int[], android.view.MotionEvent$PointerCoords[], int, float, float, int, int, int, int); + Signature: (JJII[I[Landroid/view/MotionEvent$PointerCoords;IFFIIII)Landroid/view/MotionEvent; + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=15, args_size=13 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 39: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 pointerCount I + 0 10 6 pointerIds [I + 0 10 7 pointerCoords [Landroid/view/MotionEvent$PointerCoords; + 0 10 8 metaState I + 0 10 9 xPrecision F + 0 10 10 yPrecision F + 0 10 11 deviceId I + 0 10 12 edgeFlags I + 0 10 13 source I + 0 10 14 flags I + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + public static android.view.MotionEvent obtain(long, long, int, float, float, float, float, int, float, float, int, int); + Signature: (JJIFFFFIFFII)Landroid/view/MotionEvent; + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=14, args_size=12 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 40: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 x F + 0 10 6 y F + 0 10 7 pressure F + 0 10 8 size F + 0 10 9 metaState I + 0 10 10 xPrecision F + 0 10 11 yPrecision F + 0 10 12 deviceId I + 0 10 13 edgeFlags I + + public static android.view.MotionEvent obtain(long, long, int, int, float, float, float, float, int, float, float, int, int); + Signature: (JJIIFFFFIFFII)Landroid/view/MotionEvent; + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=15, args_size=13 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 42: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 pointerCount I + 0 10 6 x F + 0 10 7 y F + 0 10 8 pressure F + 0 10 9 size F + 0 10 10 metaState I + 0 10 11 xPrecision F + 0 10 12 yPrecision F + 0 10 13 deviceId I + 0 10 14 edgeFlags I + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + public static android.view.MotionEvent obtain(long, long, int, float, float, int); + Signature: (JJIFFI)Landroid/view/MotionEvent; + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=8, args_size=6 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 43: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 x F + 0 10 6 y F + 0 10 7 metaState I + + public static android.view.MotionEvent obtain(android.view.MotionEvent); + Signature: (Landroid/view/MotionEvent;)Landroid/view/MotionEvent; + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 44: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 other Landroid/view/MotionEvent; + + public static android.view.MotionEvent obtainNoHistory(android.view.MotionEvent); + Signature: (Landroid/view/MotionEvent;)Landroid/view/MotionEvent; + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 45: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 other Landroid/view/MotionEvent; + + public final void recycle(); + Signature: ()V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 46: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final int getDeviceId(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 47: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final int getSource(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 48: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final void setSource(int); + Signature: (I)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 49: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 source I + + public final int getAction(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 50: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final int getActionMasked(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 51: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final int getActionIndex(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 52: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final int getFlags(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 53: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final long getDownTime(); + Signature: ()J + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 54: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final long getEventTime(); + Signature: ()J + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 55: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getX(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 56: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getY(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 57: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getPressure(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 58: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getSize(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 59: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getTouchMajor(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 60: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getTouchMinor(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 61: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getToolMajor(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 62: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getToolMinor(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 63: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getOrientation(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 64: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getAxisValue(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 65: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 axis I + + public final int getPointerCount(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 66: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final int getPointerId(int); + Signature: (I)I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 67: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final int getToolType(int); + Signature: (I)I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 68: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final int findPointerIndex(int); + Signature: (I)I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 69: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerId I + + public final float getX(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 70: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final float getY(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 71: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final float getPressure(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 72: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final float getSize(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 73: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final float getTouchMajor(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 74: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final float getTouchMinor(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 75: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final float getToolMajor(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 76: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final float getToolMinor(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 77: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final float getOrientation(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 78: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final float getAxisValue(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 79: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 axis I + 0 10 2 pointerIndex I + + public final void getPointerCoords(int, android.view.MotionEvent$PointerCoords); + Signature: (ILandroid/view/MotionEvent$PointerCoords;)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 80: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 outPointerCoords Landroid/view/MotionEvent$PointerCoords; + + public final void getPointerProperties(int, android.view.MotionEvent$PointerProperties); + Signature: (ILandroid/view/MotionEvent$PointerProperties;)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 81: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 outPointerProperties Landroid/view/MotionEvent$PointerProperties; + + public final int getMetaState(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 82: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final int getButtonState(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 83: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getRawX(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 84: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getRawY(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 85: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getXPrecision(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 86: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getYPrecision(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 87: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final int getHistorySize(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 88: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final long getHistoricalEventTime(int); + Signature: (I)J + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 89: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalX(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 90: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalY(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 91: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalPressure(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 92: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalSize(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 93: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalTouchMajor(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 94: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalTouchMinor(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 95: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalToolMajor(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 96: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalToolMinor(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 97: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalOrientation(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 98: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalAxisValue(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 99: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 axis I + 0 10 2 pos I + + public final float getHistoricalX(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 100: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + public final float getHistoricalY(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 101: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + public final float getHistoricalPressure(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 102: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + public final float getHistoricalSize(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 103: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + public final float getHistoricalTouchMajor(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 104: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + public final float getHistoricalTouchMinor(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 105: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + public final float getHistoricalToolMajor(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 106: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + public final float getHistoricalToolMinor(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 107: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + public final float getHistoricalOrientation(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 108: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + public final float getHistoricalAxisValue(int, int, int); + Signature: (III)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=4, args_size=4 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 109: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 axis I + 0 10 2 pointerIndex I + 0 10 3 pos I + + public final void getHistoricalPointerCoords(int, int, android.view.MotionEvent$PointerCoords); + Signature: (IILandroid/view/MotionEvent$PointerCoords;)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=4, args_size=4 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 110: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + 0 10 3 outPointerCoords Landroid/view/MotionEvent$PointerCoords; + + public final int getEdgeFlags(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 111: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final void setEdgeFlags(int); + Signature: (I)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 112: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 flags I + + public final void setAction(int); + Signature: (I)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 113: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 action I + + public final void offsetLocation(float, float); + Signature: (FF)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 114: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 deltaX F + 0 10 2 deltaY F + + public final void setLocation(float, float); + Signature: (FF)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 115: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 x F + 0 10 2 y F + + public final void transform(android.graphics.Matrix); + Signature: (Landroid/graphics/Matrix;)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 116: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 matrix Landroid/graphics/Matrix; + + public final void addBatch(long, float, float, float, float, int); + Signature: (JFFFFI)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=8, args_size=7 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 117: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 eventTime J + 0 10 3 x F + 0 10 4 y F + 0 10 5 pressure F + 0 10 6 size F + 0 10 7 metaState I + + public final void addBatch(long, android.view.MotionEvent$PointerCoords[], int); + Signature: (J[Landroid/view/MotionEvent$PointerCoords;I)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=5, args_size=4 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 118: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 eventTime J + 0 10 3 pointerCoords [Landroid/view/MotionEvent$PointerCoords; + 0 10 4 metaState I + + public java.lang.String toString(); + Signature: ()Ljava/lang/String; + flags: ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 119: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public static java.lang.String actionToString(int); + Signature: (I)Ljava/lang/String; + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 120: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 action I + + public static java.lang.String axisToString(int); + Signature: (I)Ljava/lang/String; + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 121: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 axis I + + public static int axisFromString(java.lang.String); + Signature: (Ljava/lang/String;)I + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 122: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 symbolicName Ljava/lang/String; + + public void writeToParcel(android.os.Parcel, int); + Signature: (Landroid/os/Parcel;I)V + flags: ACC_PUBLIC + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 123: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 out Landroid/os/Parcel; + 0 10 2 flags I + + static {}; + Signature: ()V + flags: ACC_STATIC + Code: + stack=1, locals=0, args_size=0 + 0: aconst_null + 1: putstatic #5 // Field CREATOR:Landroid/os/Parcelable$Creator; + 4: return + LineNumberTable: + line 213: 0 +}
diff --git a/src/base/android/jni_generator/testMultipleJNIAdditionalImport.golden b/src/base/android/jni_generator/testMultipleJNIAdditionalImport.golden new file mode 100644 index 0000000..ab2496c --- /dev/null +++ b/src/base/android/jni_generator/testMultipleJNIAdditionalImport.golden
@@ -0,0 +1,70 @@ +// Copyright 2014 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. + + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// org/chromium/foo/Foo + +#ifndef org_chromium_foo_Foo_JNI +#define org_chromium_foo_Foo_JNI + +#include <jni.h> + +#include "base/android/jni_generator/jni_generator_helper.h" + + +// Step 1: Forward declarations. + +JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_foo_Foo[]; +const char kClassPath_org_chromium_foo_Foo[] = "org/chromium/foo/Foo"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +JNI_REGISTRATION_EXPORT std::atomic<jclass> g_org_chromium_foo_Foo_clazz(nullptr); +#ifndef org_chromium_foo_Foo_clazz_defined +#define org_chromium_foo_Foo_clazz_defined +inline jclass org_chromium_foo_Foo_clazz(JNIEnv* env) { + return base::android::LazyGetClass(env, kClassPath_org_chromium_foo_Foo, + &g_org_chromium_foo_Foo_clazz); +} +#endif + + +// Step 2: Constants (optional). + + +// Step 3: Method stubs. +static void JNI_Foo_DoSomething(JNIEnv* env, const base::android::JavaParamRef<jclass>& jcaller, + const base::android::JavaParamRef<jobject>& callback1, + const base::android::JavaParamRef<jobject>& callback2); + +JNI_GENERATOR_EXPORT void Java_org_chromium_foo_Foo_nativeDoSomething( + JNIEnv* env, + jclass jcaller, + jobject callback1, + jobject callback2) { + return JNI_Foo_DoSomething(env, base::android::JavaParamRef<jclass>(env, jcaller), + base::android::JavaParamRef<jobject>(env, callback1), + base::android::JavaParamRef<jobject>(env, callback2)); +} + + +static std::atomic<jmethodID> g_org_chromium_foo_Foo_calledByNative(nullptr); +static void Java_Foo_calledByNative(JNIEnv* env, const base::android::JavaRef<jobject>& callback1, + const base::android::JavaRef<jobject>& callback2) { + CHECK_CLAZZ(env, org_chromium_foo_Foo_clazz(env), + org_chromium_foo_Foo_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, org_chromium_foo_Foo_clazz(env), + "calledByNative", + "(Lorg/chromium/foo/Bar1$Callback;Lorg/chromium/foo/Bar2$Callback;)V", + &g_org_chromium_foo_Foo_calledByNative); + + env->CallStaticVoidMethod(org_chromium_foo_Foo_clazz(env), + method_id, callback1.obj(), callback2.obj()); + jni_generator::CheckException(env); +} + +#endif // org_chromium_foo_Foo_JNI
diff --git a/src/base/android/jni_generator/testNativeExportsOnlyOption.golden b/src/base/android/jni_generator/testNativeExportsOnlyOption.golden new file mode 100644 index 0000000..7553007 --- /dev/null +++ b/src/base/android/jni_generator/testNativeExportsOnlyOption.golden
@@ -0,0 +1,214 @@ +// Copyright 2014 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. + + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// org/chromium/example/jni_generator/SampleForTests + +#ifndef org_chromium_example_jni_generator_SampleForTests_JNI +#define org_chromium_example_jni_generator_SampleForTests_JNI + +#include <jni.h> + +#include "base/android/jni_generator/jni_generator_helper.h" + + +// Step 1: Forward declarations. + +JNI_REGISTRATION_EXPORT extern const char + kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024MyOtherInnerClass[]; +const char kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024MyOtherInnerClass[] = + "org/chromium/example/jni_generator/SampleForTests$MyOtherInnerClass"; + +JNI_REGISTRATION_EXPORT extern const char + kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024MyInnerClass[]; +const char kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024MyInnerClass[] = + "org/chromium/example/jni_generator/SampleForTests$MyInnerClass"; + +JNI_REGISTRATION_EXPORT extern const char + kClassPath_org_chromium_example_jni_1generator_SampleForTests[]; +const char kClassPath_org_chromium_example_jni_1generator_SampleForTests[] = + "org/chromium/example/jni_generator/SampleForTests"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +JNI_REGISTRATION_EXPORT std::atomic<jclass> + g_org_chromium_example_jni_1generator_SampleForTests_00024MyOtherInnerClass_clazz(nullptr); +#ifndef org_chromium_example_jni_1generator_SampleForTests_00024MyOtherInnerClass_clazz_defined +#define org_chromium_example_jni_1generator_SampleForTests_00024MyOtherInnerClass_clazz_defined +inline jclass + org_chromium_example_jni_1generator_SampleForTests_00024MyOtherInnerClass_clazz(JNIEnv* env) { + return base::android::LazyGetClass(env, + kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024MyOtherInnerClass, + &g_org_chromium_example_jni_1generator_SampleForTests_00024MyOtherInnerClass_clazz); +} +#endif +// Leaking this jclass as we cannot use LazyInstance from some threads. +JNI_REGISTRATION_EXPORT std::atomic<jclass> + g_org_chromium_example_jni_1generator_SampleForTests_00024MyInnerClass_clazz(nullptr); +#ifndef org_chromium_example_jni_1generator_SampleForTests_00024MyInnerClass_clazz_defined +#define org_chromium_example_jni_1generator_SampleForTests_00024MyInnerClass_clazz_defined +inline jclass org_chromium_example_jni_1generator_SampleForTests_00024MyInnerClass_clazz(JNIEnv* + env) { + return base::android::LazyGetClass(env, + kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024MyInnerClass, + &g_org_chromium_example_jni_1generator_SampleForTests_00024MyInnerClass_clazz); +} +#endif +// Leaking this jclass as we cannot use LazyInstance from some threads. +JNI_REGISTRATION_EXPORT std::atomic<jclass> + g_org_chromium_example_jni_1generator_SampleForTests_clazz(nullptr); +#ifndef org_chromium_example_jni_1generator_SampleForTests_clazz_defined +#define org_chromium_example_jni_1generator_SampleForTests_clazz_defined +inline jclass org_chromium_example_jni_1generator_SampleForTests_clazz(JNIEnv* env) { + return base::android::LazyGetClass(env, + kClassPath_org_chromium_example_jni_1generator_SampleForTests, + &g_org_chromium_example_jni_1generator_SampleForTests_clazz); +} +#endif + + +// Step 2: Constants (optional). + + +// Step 3: Method stubs. +JNI_GENERATOR_EXPORT jint + Java_org_chromium_example_jni_1generator_SampleForTests_nativeStaticMethod( + JNIEnv* env, + jobject jcaller, + jlong nativeTest, + jint arg1) { + Test* native = reinterpret_cast<Test*>(nativeTest); + CHECK_NATIVE_PTR(env, jcaller, native, "StaticMethod", 0); + return native->StaticMethod(env, base::android::JavaParamRef<jobject>(env, jcaller), arg1); +} + +JNI_GENERATOR_EXPORT jint Java_org_chromium_example_jni_1generator_SampleForTests_nativeMethod( + JNIEnv* env, + jobject jcaller, + jlong nativeTest, + jint arg1) { + Test* native = reinterpret_cast<Test*>(nativeTest); + CHECK_NATIVE_PTR(env, jcaller, native, "Method", 0); + return native->Method(env, base::android::JavaParamRef<jobject>(env, jcaller), arg1); +} + +static jint JNI_MyInnerClass_Init(JNIEnv* env, const base::android::JavaParamRef<jobject>& jcaller); + +JNI_GENERATOR_EXPORT jint + Java_org_chromium_example_jni_1generator_SampleForTests_00024MyInnerClass_nativeInit( + JNIEnv* env, + jobject jcaller) { + return JNI_MyInnerClass_Init(env, base::android::JavaParamRef<jobject>(env, jcaller)); +} + +static jint JNI_MyOtherInnerClass_Init(JNIEnv* env, const base::android::JavaParamRef<jobject>& + jcaller); + +JNI_GENERATOR_EXPORT jint + Java_org_chromium_example_jni_1generator_SampleForTests_00024MyOtherInnerClass_nativeInit( + JNIEnv* env, + jobject jcaller) { + return JNI_MyOtherInnerClass_Init(env, base::android::JavaParamRef<jobject>(env, jcaller)); +} + + +static std::atomic<jmethodID> + g_org_chromium_example_jni_1generator_SampleForTests_testMethodWithParam(nullptr); +static void Java_SampleForTests_testMethodWithParam(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper iParam) { + CHECK_CLAZZ(env, obj.obj(), + org_chromium_example_jni_1generator_SampleForTests_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_example_jni_1generator_SampleForTests_clazz(env), + "testMethodWithParam", + "(I)V", + &g_org_chromium_example_jni_1generator_SampleForTests_testMethodWithParam); + + env->CallVoidMethod(obj.obj(), + method_id, as_jint(iParam)); + jni_generator::CheckException(env); +} + +static std::atomic<jmethodID> + g_org_chromium_example_jni_1generator_SampleForTests_testMethodWithParamAndReturn(nullptr); +static base::android::ScopedJavaLocalRef<jstring> + Java_SampleForTests_testMethodWithParamAndReturn(JNIEnv* env, const + base::android::JavaRef<jobject>& obj, JniIntWrapper iParam) { + CHECK_CLAZZ(env, obj.obj(), + org_chromium_example_jni_1generator_SampleForTests_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_example_jni_1generator_SampleForTests_clazz(env), + "testMethodWithParamAndReturn", + "(I)Ljava/lang/String;", + &g_org_chromium_example_jni_1generator_SampleForTests_testMethodWithParamAndReturn); + + jstring ret = + static_cast<jstring>(env->CallObjectMethod(obj.obj(), + method_id, as_jint(iParam))); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jstring>(env, ret); +} + +static std::atomic<jmethodID> + g_org_chromium_example_jni_1generator_SampleForTests_testStaticMethodWithParam(nullptr); +static jint Java_SampleForTests_testStaticMethodWithParam(JNIEnv* env, JniIntWrapper iParam) { + CHECK_CLAZZ(env, org_chromium_example_jni_1generator_SampleForTests_clazz(env), + org_chromium_example_jni_1generator_SampleForTests_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, org_chromium_example_jni_1generator_SampleForTests_clazz(env), + "testStaticMethodWithParam", + "(I)I", + &g_org_chromium_example_jni_1generator_SampleForTests_testStaticMethodWithParam); + + jint ret = + env->CallStaticIntMethod(org_chromium_example_jni_1generator_SampleForTests_clazz(env), + method_id, as_jint(iParam)); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> + g_org_chromium_example_jni_1generator_SampleForTests_testMethodWithNoParam(nullptr); +static jdouble Java_SampleForTests_testMethodWithNoParam(JNIEnv* env) { + CHECK_CLAZZ(env, org_chromium_example_jni_1generator_SampleForTests_clazz(env), + org_chromium_example_jni_1generator_SampleForTests_clazz(env), 0); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, org_chromium_example_jni_1generator_SampleForTests_clazz(env), + "testMethodWithNoParam", + "()D", + &g_org_chromium_example_jni_1generator_SampleForTests_testMethodWithNoParam); + + jdouble ret = + env->CallStaticDoubleMethod(org_chromium_example_jni_1generator_SampleForTests_clazz(env), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static std::atomic<jmethodID> + g_org_chromium_example_jni_1generator_SampleForTests_testStaticMethodWithNoParam(nullptr); +static base::android::ScopedJavaLocalRef<jstring> + Java_SampleForTests_testStaticMethodWithNoParam(JNIEnv* env) { + CHECK_CLAZZ(env, org_chromium_example_jni_1generator_SampleForTests_clazz(env), + org_chromium_example_jni_1generator_SampleForTests_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, org_chromium_example_jni_1generator_SampleForTests_clazz(env), + "testStaticMethodWithNoParam", + "()Ljava/lang/String;", + &g_org_chromium_example_jni_1generator_SampleForTests_testStaticMethodWithNoParam); + + jstring ret = +static_cast<jstring>(env->CallStaticObjectMethod(org_chromium_example_jni_1generator_SampleForTests_clazz(env), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jstring>(env, ret); +} + +#endif // org_chromium_example_jni_generator_SampleForTests_JNI
diff --git a/src/base/android/jni_generator/testNatives.golden b/src/base/android/jni_generator/testNatives.golden new file mode 100644 index 0000000..23598ff --- /dev/null +++ b/src/base/android/jni_generator/testNatives.golden
@@ -0,0 +1,222 @@ +// Copyright 2014 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. + + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// org/chromium/TestJni + +#ifndef org_chromium_TestJni_JNI +#define org_chromium_TestJni_JNI + +#include <jni.h> + +#include "base/android/jni_generator/jni_generator_helper.h" + + +// Step 1: Forward declarations. + +JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_TestJni[]; +const char kClassPath_org_chromium_TestJni[] = "org/chromium/TestJni"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +JNI_REGISTRATION_EXPORT std::atomic<jclass> g_org_chromium_TestJni_clazz(nullptr); +#ifndef org_chromium_TestJni_clazz_defined +#define org_chromium_TestJni_clazz_defined +inline jclass org_chromium_TestJni_clazz(JNIEnv* env) { + return base::android::LazyGetClass(env, kClassPath_org_chromium_TestJni, + &g_org_chromium_TestJni_clazz); +} +#endif + + +// Step 2: Constants (optional). + + +// Step 3: Method stubs. +static jint JNI_TestJni_Init(JNIEnv* env, const base::android::JavaParamRef<jobject>& jcaller); + +JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_nativeInit( + JNIEnv* env, + jobject jcaller) { + return JNI_TestJni_Init(env, base::android::JavaParamRef<jobject>(env, jcaller)); +} + +JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeDestroy( + JNIEnv* env, + jobject jcaller, + jint nativeChromeBrowserProvider) { + ChromeBrowserProvider* native = + reinterpret_cast<ChromeBrowserProvider*>(nativeChromeBrowserProvider); + CHECK_NATIVE_PTR(env, jcaller, native, "Destroy"); + return native->Destroy(env, base::android::JavaParamRef<jobject>(env, jcaller)); +} + +JNI_GENERATOR_EXPORT jlong Java_org_chromium_TestJni_nativeAddBookmark( + JNIEnv* env, + jobject jcaller, + jint nativeChromeBrowserProvider, + jstring url, + jstring title, + jboolean isFolder, + jlong parentId) { + ChromeBrowserProvider* native = + reinterpret_cast<ChromeBrowserProvider*>(nativeChromeBrowserProvider); + CHECK_NATIVE_PTR(env, jcaller, native, "AddBookmark", 0); + return native->AddBookmark(env, base::android::JavaParamRef<jobject>(env, jcaller), + base::android::JavaParamRef<jstring>(env, url), base::android::JavaParamRef<jstring>(env, + title), isFolder, parentId); +} + +static base::android::ScopedJavaLocalRef<jstring> JNI_TestJni_GetDomainAndRegistry(JNIEnv* env, + const base::android::JavaParamRef<jclass>& jcaller, + const base::android::JavaParamRef<jstring>& url); + +JNI_GENERATOR_EXPORT jstring Java_org_chromium_TestJni_nativeGetDomainAndRegistry( + JNIEnv* env, + jclass jcaller, + jstring url) { + return JNI_TestJni_GetDomainAndRegistry(env, base::android::JavaParamRef<jclass>(env, jcaller), + base::android::JavaParamRef<jstring>(env, url)).Release(); +} + +static void JNI_TestJni_CreateHistoricalTabFromState(JNIEnv* env, const + base::android::JavaParamRef<jclass>& jcaller, + const base::android::JavaParamRef<jbyteArray>& state, + jint tab_index); + +JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeCreateHistoricalTabFromState( + JNIEnv* env, + jclass jcaller, + jbyteArray state, + jint tab_index) { + return JNI_TestJni_CreateHistoricalTabFromState(env, base::android::JavaParamRef<jclass>(env, + jcaller), base::android::JavaParamRef<jbyteArray>(env, state), tab_index); +} + +static base::android::ScopedJavaLocalRef<jbyteArray> JNI_TestJni_GetStateAsByteArray(JNIEnv* env, + const base::android::JavaParamRef<jobject>& jcaller, + const base::android::JavaParamRef<jobject>& view); + +JNI_GENERATOR_EXPORT jbyteArray Java_org_chromium_TestJni_nativeGetStateAsByteArray( + JNIEnv* env, + jobject jcaller, + jobject view) { + return JNI_TestJni_GetStateAsByteArray(env, base::android::JavaParamRef<jobject>(env, jcaller), + base::android::JavaParamRef<jobject>(env, view)).Release(); +} + +static base::android::ScopedJavaLocalRef<jobjectArray> JNI_TestJni_GetAutofillProfileGUIDs(JNIEnv* + env, const base::android::JavaParamRef<jclass>& jcaller); + +JNI_GENERATOR_EXPORT jobjectArray Java_org_chromium_TestJni_nativeGetAutofillProfileGUIDs( + JNIEnv* env, + jclass jcaller) { + return JNI_TestJni_GetAutofillProfileGUIDs(env, base::android::JavaParamRef<jclass>(env, + jcaller)).Release(); +} + +static void JNI_TestJni_SetRecognitionResults(JNIEnv* env, const + base::android::JavaParamRef<jobject>& jcaller, + jint sessionId, + const base::android::JavaParamRef<jobjectArray>& results); + +JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeSetRecognitionResults( + JNIEnv* env, + jobject jcaller, + jint sessionId, + jobjectArray results) { + return JNI_TestJni_SetRecognitionResults(env, base::android::JavaParamRef<jobject>(env, jcaller), + sessionId, base::android::JavaParamRef<jobjectArray>(env, results)); +} + +JNI_GENERATOR_EXPORT jlong Java_org_chromium_TestJni_nativeAddBookmarkFromAPI( + JNIEnv* env, + jobject jcaller, + jint nativeChromeBrowserProvider, + jstring url, + jobject created, + jobject isBookmark, + jobject date, + jbyteArray favicon, + jstring title, + jobject visits) { + ChromeBrowserProvider* native = + reinterpret_cast<ChromeBrowserProvider*>(nativeChromeBrowserProvider); + CHECK_NATIVE_PTR(env, jcaller, native, "AddBookmarkFromAPI", 0); + return native->AddBookmarkFromAPI(env, base::android::JavaParamRef<jobject>(env, jcaller), + base::android::JavaParamRef<jstring>(env, url), base::android::JavaParamRef<jobject>(env, + created), base::android::JavaParamRef<jobject>(env, isBookmark), + base::android::JavaParamRef<jobject>(env, date), base::android::JavaParamRef<jbyteArray>(env, + favicon), base::android::JavaParamRef<jstring>(env, title), + base::android::JavaParamRef<jobject>(env, visits)); +} + +static jint JNI_TestJni_FindAll(JNIEnv* env, const base::android::JavaParamRef<jobject>& jcaller, + const base::android::JavaParamRef<jstring>& find); + +JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_nativeFindAll( + JNIEnv* env, + jobject jcaller, + jstring find) { + return JNI_TestJni_FindAll(env, base::android::JavaParamRef<jobject>(env, jcaller), + base::android::JavaParamRef<jstring>(env, find)); +} + +static base::android::ScopedJavaLocalRef<jobject> JNI_TestJni_GetInnerClass(JNIEnv* env, const + base::android::JavaParamRef<jclass>& jcaller); + +JNI_GENERATOR_EXPORT jobject Java_org_chromium_TestJni_nativeGetInnerClass( + JNIEnv* env, + jclass jcaller) { + return JNI_TestJni_GetInnerClass(env, base::android::JavaParamRef<jclass>(env, + jcaller)).Release(); +} + +JNI_GENERATOR_EXPORT jobject Java_org_chromium_TestJni_nativeQueryBitmap( + JNIEnv* env, + jobject jcaller, + jint nativeChromeBrowserProvider, + jobjectArray projection, + jstring selection, + jobjectArray selectionArgs, + jstring sortOrder) { + ChromeBrowserProvider* native = + reinterpret_cast<ChromeBrowserProvider*>(nativeChromeBrowserProvider); + CHECK_NATIVE_PTR(env, jcaller, native, "QueryBitmap", NULL); + return native->QueryBitmap(env, base::android::JavaParamRef<jobject>(env, jcaller), + base::android::JavaParamRef<jobjectArray>(env, projection), + base::android::JavaParamRef<jstring>(env, selection), + base::android::JavaParamRef<jobjectArray>(env, selectionArgs), + base::android::JavaParamRef<jstring>(env, sortOrder)).Release(); +} + +JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeGotOrientation( + JNIEnv* env, + jobject jcaller, + jint nativeDataFetcherImplAndroid, + jdouble alpha, + jdouble beta, + jdouble gamma) { + DataFetcherImplAndroid* native = + reinterpret_cast<DataFetcherImplAndroid*>(nativeDataFetcherImplAndroid); + CHECK_NATIVE_PTR(env, jcaller, native, "GotOrientation"); + return native->GotOrientation(env, base::android::JavaParamRef<jobject>(env, jcaller), alpha, + beta, gamma); +} + +static base::android::ScopedJavaLocalRef<jthrowable> JNI_TestJni_MessWithJavaException(JNIEnv* env, + const base::android::JavaParamRef<jclass>& jcaller, + const base::android::JavaParamRef<jthrowable>& e); + +JNI_GENERATOR_EXPORT jthrowable Java_org_chromium_TestJni_nativeMessWithJavaException( + JNIEnv* env, + jclass jcaller, + jthrowable e) { + return JNI_TestJni_MessWithJavaException(env, base::android::JavaParamRef<jclass>(env, jcaller), + base::android::JavaParamRef<jthrowable>(env, e)).Release(); +} + + +#endif // org_chromium_TestJni_JNI
diff --git a/src/base/android/jni_generator/testNativesLong.golden b/src/base/android/jni_generator/testNativesLong.golden new file mode 100644 index 0000000..52169dc --- /dev/null +++ b/src/base/android/jni_generator/testNativesLong.golden
@@ -0,0 +1,49 @@ +// Copyright 2014 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. + + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// org/chromium/TestJni + +#ifndef org_chromium_TestJni_JNI +#define org_chromium_TestJni_JNI + +#include <jni.h> + +#include "base/android/jni_generator/jni_generator_helper.h" + + +// Step 1: Forward declarations. + +JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_TestJni[]; +const char kClassPath_org_chromium_TestJni[] = "org/chromium/TestJni"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +JNI_REGISTRATION_EXPORT std::atomic<jclass> g_org_chromium_TestJni_clazz(nullptr); +#ifndef org_chromium_TestJni_clazz_defined +#define org_chromium_TestJni_clazz_defined +inline jclass org_chromium_TestJni_clazz(JNIEnv* env) { + return base::android::LazyGetClass(env, kClassPath_org_chromium_TestJni, + &g_org_chromium_TestJni_clazz); +} +#endif + + +// Step 2: Constants (optional). + + +// Step 3: Method stubs. +JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeDestroy( + JNIEnv* env, + jobject jcaller, + jlong nativeChromeBrowserProvider) { + ChromeBrowserProvider* native = + reinterpret_cast<ChromeBrowserProvider*>(nativeChromeBrowserProvider); + CHECK_NATIVE_PTR(env, jcaller, native, "Destroy"); + return native->Destroy(env, base::android::JavaParamRef<jobject>(env, jcaller)); +} + + +#endif // org_chromium_TestJni_JNI
diff --git a/src/base/android/jni_generator/testNativesRegistrations.golden b/src/base/android/jni_generator/testNativesRegistrations.golden new file mode 100644 index 0000000..b3b9974 --- /dev/null +++ b/src/base/android/jni_generator/testNativesRegistrations.golden
@@ -0,0 +1,179 @@ +// Copyright 2017 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. + + +// This file is autogenerated by +// base/android/jni_generator/jni_registration_generator.py +// Please do not change its content. + +#ifndef HEADER_GUARD +#define HEADER_GUARD + +#include <jni.h> + +#include "base/android/jni_generator/jni_generator_helper.h" +#include "base/android/jni_int_wrapper.h" + + +// Step 1: Forward declarations (classes). + +extern const char kClassPath_org_chromium_TestJni[]; +extern std::atomic<jclass> g_org_chromium_TestJni_clazz; +#ifndef org_chromium_TestJni_clazz_defined +#define org_chromium_TestJni_clazz_defined +inline jclass org_chromium_TestJni_clazz(JNIEnv* env) { + return base::android::LazyGetClass(env, kClassPath_org_chromium_TestJni, + &g_org_chromium_TestJni_clazz); +} +#endif + + +// Step 2: Forward declarations (methods). + +JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_nativeInit( + JNIEnv* env, + jobject jcaller); +JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeDestroy( + JNIEnv* env, + jobject jcaller, + jint nativeChromeBrowserProvider); +JNI_GENERATOR_EXPORT jlong Java_org_chromium_TestJni_nativeAddBookmark( + JNIEnv* env, + jobject jcaller, + jint nativeChromeBrowserProvider, + jstring url, + jstring title, + jboolean isFolder, + jlong parentId); +JNI_GENERATOR_EXPORT jstring Java_org_chromium_TestJni_nativeGetDomainAndRegistry( + JNIEnv* env, + jclass jcaller, + jstring url); +JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeCreateHistoricalTabFromState( + JNIEnv* env, + jclass jcaller, + jbyteArray state, + jint tab_index); +JNI_GENERATOR_EXPORT jbyteArray Java_org_chromium_TestJni_nativeGetStateAsByteArray( + JNIEnv* env, + jobject jcaller, + jobject view); +JNI_GENERATOR_EXPORT jobjectArray Java_org_chromium_TestJni_nativeGetAutofillProfileGUIDs( + JNIEnv* env, + jclass jcaller); +JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeSetRecognitionResults( + JNIEnv* env, + jobject jcaller, + jint sessionId, + jobjectArray results); +JNI_GENERATOR_EXPORT jlong Java_org_chromium_TestJni_nativeAddBookmarkFromAPI( + JNIEnv* env, + jobject jcaller, + jint nativeChromeBrowserProvider, + jstring url, + jobject created, + jobject isBookmark, + jobject date, + jbyteArray favicon, + jstring title, + jobject visits); +JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_nativeFindAll( + JNIEnv* env, + jobject jcaller, + jstring find); +JNI_GENERATOR_EXPORT jobject Java_org_chromium_TestJni_nativeGetInnerClass( + JNIEnv* env, + jclass jcaller); +JNI_GENERATOR_EXPORT jobject Java_org_chromium_TestJni_nativeQueryBitmap( + JNIEnv* env, + jobject jcaller, + jint nativeChromeBrowserProvider, + jobjectArray projection, + jstring selection, + jobjectArray selectionArgs, + jstring sortOrder); +JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeGotOrientation( + JNIEnv* env, + jobject jcaller, + jint nativeDataFetcherImplAndroid, + jdouble alpha, + jdouble beta, + jdouble gamma); +JNI_GENERATOR_EXPORT jthrowable Java_org_chromium_TestJni_nativeMessWithJavaException( + JNIEnv* env, + jclass jcaller, + jthrowable e); + + +// Step 3: Method declarations. + +static const JNINativeMethod kMethods_org_chromium_TestJni[] = { + { "nativeInit", "()I", reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeInit) }, + { "nativeDestroy", "(I)V", reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeDestroy) }, + { "nativeAddBookmark", "(ILjava/lang/String;Ljava/lang/String;ZJ)J", + reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeAddBookmark) }, + { "nativeGetDomainAndRegistry", "(Ljava/lang/String;)Ljava/lang/String;", + reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeGetDomainAndRegistry) }, + { "nativeCreateHistoricalTabFromState", "([BI)V", + reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeCreateHistoricalTabFromState) }, + { "nativeGetStateAsByteArray", "(Landroid/view/View;)[B", + reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeGetStateAsByteArray) }, + { "nativeGetAutofillProfileGUIDs", "()[Ljava/lang/String;", + reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeGetAutofillProfileGUIDs) }, + { "nativeSetRecognitionResults", "(I[Ljava/lang/String;)V", + reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeSetRecognitionResults) }, + { "nativeAddBookmarkFromAPI", + "(ILjava/lang/String;Ljava/lang/Long;Ljava/lang/Boolean;Ljava/lang/Long;[BLjava/lang/String;Ljava/lang/Integer;)J", + reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeAddBookmarkFromAPI) }, + { "nativeFindAll", "(Ljava/lang/String;)I", + reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeFindAll) }, + { "nativeGetInnerClass", + "()Lorg/chromium/example/jni_generator/SampleForTests$OnFrameAvailableListener;", + reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeGetInnerClass) }, + { "nativeQueryBitmap", + "(I[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/graphics/Bitmap;", + reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeQueryBitmap) }, + { "nativeGotOrientation", "(IDDD)V", + reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeGotOrientation) }, + { "nativeMessWithJavaException", "(Ljava/lang/Throwable;)Ljava/lang/Throwable;", + reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeMessWithJavaException) }, +}; + + +JNI_REGISTRATION_EXPORT bool RegisterNative_org_chromium_TestJni(JNIEnv* env) { + const int kMethods_org_chromium_TestJniSize = + arraysize(kMethods_org_chromium_TestJni); + if (env->RegisterNatives( + org_chromium_TestJni_clazz(env), + kMethods_org_chromium_TestJni, + kMethods_org_chromium_TestJniSize) < 0) { + jni_generator::HandleRegistrationError(env, + org_chromium_TestJni_clazz(env), + __FILE__); + return false; + } + + return true; +} + + +// Step 4: Main dex and non-main dex registration functions. + +namespace test { + +bool RegisterMainDexNatives(JNIEnv* env) { + if (!RegisterNative_org_chromium_TestJni(env)) + return false; + + return true; +} + +bool RegisterNonMainDexNatives(JNIEnv* env) { + + return true; +} + +} // namespace test + +#endif // HEADER_GUARD
diff --git a/src/base/android/jni_generator/testSingleJNIAdditionalImport.golden b/src/base/android/jni_generator/testSingleJNIAdditionalImport.golden new file mode 100644 index 0000000..7164c0b --- /dev/null +++ b/src/base/android/jni_generator/testSingleJNIAdditionalImport.golden
@@ -0,0 +1,66 @@ +// Copyright 2014 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. + + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// org/chromium/foo/Foo + +#ifndef org_chromium_foo_Foo_JNI +#define org_chromium_foo_Foo_JNI + +#include <jni.h> + +#include "base/android/jni_generator/jni_generator_helper.h" + + +// Step 1: Forward declarations. + +JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_foo_Foo[]; +const char kClassPath_org_chromium_foo_Foo[] = "org/chromium/foo/Foo"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +JNI_REGISTRATION_EXPORT std::atomic<jclass> g_org_chromium_foo_Foo_clazz(nullptr); +#ifndef org_chromium_foo_Foo_clazz_defined +#define org_chromium_foo_Foo_clazz_defined +inline jclass org_chromium_foo_Foo_clazz(JNIEnv* env) { + return base::android::LazyGetClass(env, kClassPath_org_chromium_foo_Foo, + &g_org_chromium_foo_Foo_clazz); +} +#endif + + +// Step 2: Constants (optional). + + +// Step 3: Method stubs. +static void JNI_Foo_DoSomething(JNIEnv* env, const base::android::JavaParamRef<jclass>& jcaller, + const base::android::JavaParamRef<jobject>& callback); + +JNI_GENERATOR_EXPORT void Java_org_chromium_foo_Foo_nativeDoSomething( + JNIEnv* env, + jclass jcaller, + jobject callback) { + return JNI_Foo_DoSomething(env, base::android::JavaParamRef<jclass>(env, jcaller), + base::android::JavaParamRef<jobject>(env, callback)); +} + + +static std::atomic<jmethodID> g_org_chromium_foo_Foo_calledByNative(nullptr); +static void Java_Foo_calledByNative(JNIEnv* env, const base::android::JavaRef<jobject>& callback) { + CHECK_CLAZZ(env, org_chromium_foo_Foo_clazz(env), + org_chromium_foo_Foo_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, org_chromium_foo_Foo_clazz(env), + "calledByNative", + "(Lorg/chromium/foo/Bar$Callback;)V", + &g_org_chromium_foo_Foo_calledByNative); + + env->CallStaticVoidMethod(org_chromium_foo_Foo_clazz(env), + method_id, callback.obj()); + jni_generator::CheckException(env); +} + +#endif // org_chromium_foo_Foo_JNI
diff --git a/src/base/android/jni_generator/testTracing.golden b/src/base/android/jni_generator/testTracing.golden new file mode 100644 index 0000000..88dbec9 --- /dev/null +++ b/src/base/android/jni_generator/testTracing.golden
@@ -0,0 +1,99 @@ +// Copyright 2014 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. + + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// org/chromium/foo/Foo + +#ifndef org_chromium_foo_Foo_JNI +#define org_chromium_foo_Foo_JNI + +#include <jni.h> + +#include "base/android/jni_generator/jni_generator_helper.h" + + +// Step 1: Forward declarations. + +JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_foo_Foo[]; +const char kClassPath_org_chromium_foo_Foo[] = "org/chromium/foo/Foo"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +JNI_REGISTRATION_EXPORT std::atomic<jclass> g_org_chromium_foo_Foo_clazz(nullptr); +#ifndef org_chromium_foo_Foo_clazz_defined +#define org_chromium_foo_Foo_clazz_defined +inline jclass org_chromium_foo_Foo_clazz(JNIEnv* env) { + return base::android::LazyGetClass(env, kClassPath_org_chromium_foo_Foo, + &g_org_chromium_foo_Foo_clazz); +} +#endif + + +// Step 2: Constants (optional). + + +// Step 3: Method stubs. +namespace org { +namespace chromium_foo { + +JNI_GENERATOR_EXPORT void Java_org_chromium_foo_Foo_nativeInstanceMethod( + JNIEnv* env, + jobject jcaller, + jlong nativeInstance) { + TRACE_EVENT0("jni", "org::chromium_foo::Instance::InstanceMethod"); Instance* native = + reinterpret_cast<Instance*>(nativeInstance); + CHECK_NATIVE_PTR(env, jcaller, native, "InstanceMethod"); + return native->InstanceMethod(env, base::android::JavaParamRef<jobject>(env, jcaller)); +} + +static void JNI_Foo_StaticMethod(JNIEnv* env, const base::android::JavaParamRef<jclass>& jcaller); + +JNI_GENERATOR_EXPORT void Java_org_chromium_foo_Foo_nativeStaticMethod( + JNIEnv* env, + jclass jcaller) { + TRACE_EVENT0("jni", "org::chromium_foo::JNI_Foo_StaticMethod"); return JNI_Foo_StaticMethod(env, + base::android::JavaParamRef<jclass>(env, jcaller)); +} + + +static std::atomic<jmethodID> g_org_chromium_foo_Foo_Constructor(nullptr); +static base::android::ScopedJavaLocalRef<jobject> Java_Foo_Constructor(JNIEnv* env) { + CHECK_CLAZZ(env, org_chromium_foo_Foo_clazz(env), + org_chromium_foo_Foo_clazz(env), NULL); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_foo_Foo_clazz(env), + "<init>", + "()V", + &g_org_chromium_foo_Foo_Constructor); + + TRACE_EVENT0("jni", "org.chromium.foo.Foo.<init>"); jobject ret = + env->NewObject(org_chromium_foo_Foo_clazz(env), + method_id); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef<jobject>(env, ret); +} + +static std::atomic<jmethodID> g_org_chromium_foo_Foo_callbackFromNative(nullptr); +static void Java_Foo_callbackFromNative(JNIEnv* env, const base::android::JavaRef<jobject>& obj) { + CHECK_CLAZZ(env, obj.obj(), + org_chromium_foo_Foo_clazz(env)); + jmethodID method_id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, org_chromium_foo_Foo_clazz(env), + "callbackFromNative", + "()V", + &g_org_chromium_foo_Foo_callbackFromNative); + + TRACE_EVENT0("jni", "org.chromium.foo.Foo.callbackFromNative"); + env->CallVoidMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); +} + +} // namespace chromium_foo +} // namespace org + +#endif // org_chromium_foo_Foo_JNI
diff --git a/src/base/android/jni_int_wrapper.h b/src/base/android/jni_int_wrapper.h new file mode 100644 index 0000000..fa0f3d5 --- /dev/null +++ b/src/base/android/jni_int_wrapper.h
@@ -0,0 +1,56 @@ +// Copyright 2014 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_ANDROID_JNI_INT_WRAPPER_H_ +#define BASE_ANDROID_JNI_INT_WRAPPER_H_ + +// Wrapper used to receive int when calling Java from native. +// The wrapper disallows automatic conversion of long to int. +// This is to avoid a common anti-pattern where a Java int is used +// to receive a native pointer. Please use a Java long to receive +// native pointers, so that the code works on both 32-bit and 64-bit +// platforms. Note the wrapper allows other lossy conversions into +// jint that could be consider anti-patterns, such as from size_t. + +// Checking is only done in debugging builds. + +#ifdef NDEBUG + +typedef jint JniIntWrapper; + +// This inline is sufficiently trivial that it does not change the +// final code generated by g++. +inline jint as_jint(JniIntWrapper wrapper) { + return wrapper; +} + +#else + +class JniIntWrapper { + public: + JniIntWrapper() : i_(0) {} + JniIntWrapper(int i) : i_(i) {} + JniIntWrapper(const JniIntWrapper& ji) : i_(ji.i_) {} + template <class T> JniIntWrapper(const T& t) : i_(t) {} + jint as_jint() const { return i_; } + private: + // If you get an "is private" error at the line below it is because you used + // an implicit conversion to convert a long to an int when calling Java. + // We disallow this, as a common anti-pattern allows converting a native + // pointer (intptr_t) to a Java int. Please use a Java long to represent + // a native pointer. If you want a lossy conversion, please use an + // explicit conversion in your C++ code. Note an error is only seen when + // compiling on a 64-bit platform, as intptr_t is indistinguishable from + // int on 32-bit platforms. + JniIntWrapper(long); + jint i_; +}; + +inline jint as_jint(const JniIntWrapper& wrapper) { + return wrapper.as_jint(); +} + +#endif // NDEBUG + +#endif // BASE_ANDROID_JNI_INT_WRAPPER_H_
diff --git a/src/base/android/jni_registrar.cc b/src/base/android/jni_registrar.cc new file mode 100644 index 0000000..8e13e60 --- /dev/null +++ b/src/base/android/jni_registrar.cc
@@ -0,0 +1,30 @@ +// Copyright (c) 2012 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. + +#include "base/android/jni_registrar.h" + +#include "base/logging.h" +#include "base/android/jni_android.h" +#include "base/trace_event/trace_event.h" + +namespace base { +namespace android { + +bool RegisterNativeMethods(JNIEnv* env, + const RegistrationMethod* method, + size_t count) { + TRACE_EVENT0("startup", "base_android::RegisterNativeMethods") + const RegistrationMethod* end = method + count; + while (method != end) { + if (!method->func(env)) { + DLOG(ERROR) << method->name << " failed registration!"; + return false; + } + method++; + } + return true; +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/jni_registrar.h b/src/base/android/jni_registrar.h new file mode 100644 index 0000000..9d229bb --- /dev/null +++ b/src/base/android/jni_registrar.h
@@ -0,0 +1,28 @@ +// Copyright (c) 2012 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_ANDROID_JNI_REGISTRAR_H_ +#define BASE_ANDROID_JNI_REGISTRAR_H_ + +#include <jni.h> + +#include "base/base_export.h" +#include "starboard/types.h" + +namespace base { +namespace android { + +struct RegistrationMethod; + +// Registers the JNI bindings for the specified |method| definition containing +// |count| elements. Returns whether the registration of the given methods +// succeeded. +BASE_EXPORT bool RegisterNativeMethods(JNIEnv* env, + const RegistrationMethod* method, + size_t count); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_JNI_REGISTRAR_H_
diff --git a/src/base/android/jni_string.cc b/src/base/android/jni_string.cc new file mode 100644 index 0000000..f28f649 --- /dev/null +++ b/src/base/android/jni_string.cc
@@ -0,0 +1,121 @@ +// Copyright (c) 2012 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. + +#include "base/android/jni_string.h" + +#include "base/android/jni_android.h" +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" + +namespace { + +// Internal version that does not use a scoped local pointer. +jstring ConvertUTF16ToJavaStringImpl(JNIEnv* env, + const base::StringPiece16& str) { + jstring result = env->NewString(str.data(), str.length()); + base::android::CheckException(env); + return result; +} + +} // namespace + +namespace base { +namespace android { + +void ConvertJavaStringToUTF8(JNIEnv* env, jstring str, std::string* result) { + DCHECK(str); + if (!str) { + LOG(WARNING) << "ConvertJavaStringToUTF8 called with null string."; + result->clear(); + return; + } + const jsize length = env->GetStringLength(str); + if (!length) { + result->clear(); + CheckException(env); + return; + } + // JNI's GetStringUTFChars() returns strings in Java "modified" UTF8, so + // instead get the String in UTF16 and convert using chromium's conversion + // function that yields plain (non Java-modified) UTF8. + const jchar* chars = env->GetStringChars(str, NULL); + DCHECK(chars); + UTF16ToUTF8(chars, length, result); + env->ReleaseStringChars(str, chars); + CheckException(env); +} + +std::string ConvertJavaStringToUTF8(JNIEnv* env, jstring str) { + std::string result; + ConvertJavaStringToUTF8(env, str, &result); + return result; +} + +std::string ConvertJavaStringToUTF8(const JavaRef<jstring>& str) { + return ConvertJavaStringToUTF8(AttachCurrentThread(), str.obj()); +} + +std::string ConvertJavaStringToUTF8(JNIEnv* env, const JavaRef<jstring>& str) { + return ConvertJavaStringToUTF8(env, str.obj()); +} + +ScopedJavaLocalRef<jstring> ConvertUTF8ToJavaString( + JNIEnv* env, + const base::StringPiece& str) { + // JNI's NewStringUTF expects "modified" UTF8 so instead create the string + // via our own UTF16 conversion utility. + // Further, Dalvik requires the string passed into NewStringUTF() to come from + // a trusted source. We can't guarantee that all UTF8 will be sanitized before + // it gets here, so constructing via UTF16 side-steps this issue. + // (Dalvik stores strings internally as UTF16 anyway, so there shouldn't be + // a significant performance hit by doing it this way). + return ScopedJavaLocalRef<jstring>(env, ConvertUTF16ToJavaStringImpl( + env, UTF8ToUTF16(str))); +} + +void ConvertJavaStringToUTF16(JNIEnv* env, jstring str, string16* result) { + DCHECK(str); + if (!str) { + LOG(WARNING) << "ConvertJavaStringToUTF16 called with null string."; + result->clear(); + return; + } + const jsize length = env->GetStringLength(str); + if (!length) { + result->clear(); + CheckException(env); + return; + } + const jchar* chars = env->GetStringChars(str, NULL); + DCHECK(chars); + // GetStringChars isn't required to NULL-terminate the strings + // it returns, so the length must be explicitly checked. + result->assign(chars, length); + env->ReleaseStringChars(str, chars); + CheckException(env); +} + +string16 ConvertJavaStringToUTF16(JNIEnv* env, jstring str) { + string16 result; + ConvertJavaStringToUTF16(env, str, &result); + return result; +} + +string16 ConvertJavaStringToUTF16(const JavaRef<jstring>& str) { + return ConvertJavaStringToUTF16(AttachCurrentThread(), str.obj()); +} + +string16 ConvertJavaStringToUTF16(JNIEnv* env, const JavaRef<jstring>& str) { + return ConvertJavaStringToUTF16(env, str.obj()); +} + +ScopedJavaLocalRef<jstring> ConvertUTF16ToJavaString( + JNIEnv* env, + const base::StringPiece16& str) { + return ScopedJavaLocalRef<jstring>(env, + ConvertUTF16ToJavaStringImpl(env, str)); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/jni_string.h b/src/base/android/jni_string.h new file mode 100644 index 0000000..95abec0 --- /dev/null +++ b/src/base/android/jni_string.h
@@ -0,0 +1,50 @@ +// Copyright (c) 2012 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_ANDROID_JNI_STRING_H_ +#define BASE_ANDROID_JNI_STRING_H_ + +#include <jni.h> +#include <string> + +#include "base/android/scoped_java_ref.h" +#include "base/base_export.h" +#include "base/strings/string_piece.h" +#include "starboard/types.h" + +namespace base { +namespace android { + +// Convert a Java string to UTF8. Returns a std string. +BASE_EXPORT void ConvertJavaStringToUTF8(JNIEnv* env, + jstring str, + std::string* result); +BASE_EXPORT std::string ConvertJavaStringToUTF8(JNIEnv* env, jstring str); +BASE_EXPORT std::string ConvertJavaStringToUTF8(const JavaRef<jstring>& str); +BASE_EXPORT std::string ConvertJavaStringToUTF8(JNIEnv* env, + const JavaRef<jstring>& str); + +// Convert a std string to Java string. +BASE_EXPORT ScopedJavaLocalRef<jstring> ConvertUTF8ToJavaString( + JNIEnv* env, + const base::StringPiece& str); + +// Convert a Java string to UTF16. Returns a string16. +BASE_EXPORT void ConvertJavaStringToUTF16(JNIEnv* env, + jstring str, + string16* result); +BASE_EXPORT string16 ConvertJavaStringToUTF16(JNIEnv* env, jstring str); +BASE_EXPORT string16 ConvertJavaStringToUTF16(const JavaRef<jstring>& str); +BASE_EXPORT string16 ConvertJavaStringToUTF16(JNIEnv* env, + const JavaRef<jstring>& str); + +// Convert a string16 to a Java string. +BASE_EXPORT ScopedJavaLocalRef<jstring> ConvertUTF16ToJavaString( + JNIEnv* env, + const base::StringPiece16& str); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_JNI_STRING_H_
diff --git a/src/base/android/jni_string_unittest.cc b/src/base/android/jni_string_unittest.cc new file mode 100644 index 0000000..3da8b87 --- /dev/null +++ b/src/base/android/jni_string_unittest.cc
@@ -0,0 +1,48 @@ +// Copyright (c) 2012 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. + +#include "base/android/jni_string.h" + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +TEST(JniString, BasicConversionsUTF8) { + const std::string kSimpleString = "SimpleTest8"; + JNIEnv* env = AttachCurrentThread(); + std::string result = + ConvertJavaStringToUTF8(ConvertUTF8ToJavaString(env, kSimpleString)); + EXPECT_EQ(kSimpleString, result); +} + +TEST(JniString, BasicConversionsUTF16) { + const string16 kSimpleString = UTF8ToUTF16("SimpleTest16"); + JNIEnv* env = AttachCurrentThread(); + string16 result = + ConvertJavaStringToUTF16(ConvertUTF16ToJavaString(env, kSimpleString)); + EXPECT_EQ(kSimpleString, result); +} + +TEST(JniString, EmptyConversionUTF8) { + const std::string kEmptyString = ""; + JNIEnv* env = AttachCurrentThread(); + std::string result = + ConvertJavaStringToUTF8(ConvertUTF8ToJavaString(env, kEmptyString)); + EXPECT_EQ(kEmptyString, result); +} + +TEST(JniString, EmptyConversionUTF16) { + const string16 kEmptyString = UTF8ToUTF16(""); + JNIEnv* env = AttachCurrentThread(); + string16 result = + ConvertJavaStringToUTF16(ConvertUTF16ToJavaString(env, kEmptyString)); + EXPECT_EQ(kEmptyString, result); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/jni_utils.cc b/src/base/android/jni_utils.cc new file mode 100644 index 0000000..c5e370c --- /dev/null +++ b/src/base/android/jni_utils.cc
@@ -0,0 +1,24 @@ +// Copyright 2014 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. + +#include "base/android/jni_utils.h" + +#include "base/android/scoped_java_ref.h" + +#include "jni/JNIUtils_jni.h" + +namespace base { +namespace android { + +ScopedJavaLocalRef<jobject> GetClassLoader(JNIEnv* env) { + return Java_JNIUtils_getClassLoader(env); +} + +bool IsSelectiveJniRegistrationEnabled(JNIEnv* env) { + return Java_JNIUtils_isSelectiveJniRegistrationEnabled(env); +} + +} // namespace android +} // namespace base +
diff --git a/src/base/android/jni_utils.h b/src/base/android/jni_utils.h new file mode 100644 index 0000000..b2e7076 --- /dev/null +++ b/src/base/android/jni_utils.h
@@ -0,0 +1,29 @@ +// Copyright 2014 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_ANDROID_JNI_UTILS_H_ +#define BASE_ANDROID_JNI_UTILS_H_ + +#include <jni.h> + +#include "base/android/scoped_java_ref.h" +#include "starboard/types.h" + +namespace base { + +namespace android { + +// Gets a ClassLoader instance capable of loading Chromium java classes. +// This should be called either from JNI_OnLoad or from within a method called +// via JNI from Java. +BASE_EXPORT ScopedJavaLocalRef<jobject> GetClassLoader(JNIEnv* env); + +// Returns true if the current process permits selective JNI registration. +BASE_EXPORT bool IsSelectiveJniRegistrationEnabled(JNIEnv* env); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_JNI_UTILS_H_ +
diff --git a/src/base/android/jni_weak_ref.cc b/src/base/android/jni_weak_ref.cc new file mode 100644 index 0000000..88efa72 --- /dev/null +++ b/src/base/android/jni_weak_ref.cc
@@ -0,0 +1,79 @@ +// Copyright 2014 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. + +#include "base/android/jni_weak_ref.h" + +#include <utility> + +#include "base/android/jni_android.h" +#include "base/logging.h" + +using base::android::AttachCurrentThread; + +JavaObjectWeakGlobalRef::JavaObjectWeakGlobalRef() : obj_(nullptr) {} + +JavaObjectWeakGlobalRef::JavaObjectWeakGlobalRef( + const JavaObjectWeakGlobalRef& orig) + : obj_(nullptr) { + Assign(orig); +} + +JavaObjectWeakGlobalRef::JavaObjectWeakGlobalRef( + JavaObjectWeakGlobalRef&& orig) noexcept + : obj_(orig.obj_) { + orig.obj_ = nullptr; +} + +JavaObjectWeakGlobalRef::JavaObjectWeakGlobalRef(JNIEnv* env, jobject obj) + : obj_(env->NewWeakGlobalRef(obj)) { +} + +JavaObjectWeakGlobalRef::JavaObjectWeakGlobalRef( + JNIEnv* env, + const base::android::JavaRef<jobject>& obj) + : obj_(env->NewWeakGlobalRef(obj.obj())) { +} + +JavaObjectWeakGlobalRef::~JavaObjectWeakGlobalRef() { + reset(); +} + +void JavaObjectWeakGlobalRef::operator=(const JavaObjectWeakGlobalRef& rhs) { + Assign(rhs); +} + +void JavaObjectWeakGlobalRef::operator=(JavaObjectWeakGlobalRef&& rhs) { + std::swap(obj_, rhs.obj_); +} + +void JavaObjectWeakGlobalRef::reset() { + if (obj_) { + AttachCurrentThread()->DeleteWeakGlobalRef(obj_); + obj_ = nullptr; + } +} + +base::android::ScopedJavaLocalRef<jobject> + JavaObjectWeakGlobalRef::get(JNIEnv* env) const { + return GetRealObject(env, obj_); +} + +base::android::ScopedJavaLocalRef<jobject> GetRealObject( + JNIEnv* env, jweak obj) { + jobject real = nullptr; + if (obj) + real = env->NewLocalRef(obj); + return base::android::ScopedJavaLocalRef<jobject>(env, real); +} + +void JavaObjectWeakGlobalRef::Assign(const JavaObjectWeakGlobalRef& other) { + if (&other == this) + return; + + JNIEnv* env = AttachCurrentThread(); + if (obj_) + env->DeleteWeakGlobalRef(obj_); + + obj_ = other.obj_ ? env->NewWeakGlobalRef(other.obj_) : nullptr; +}
diff --git a/src/base/android/jni_weak_ref.h b/src/base/android/jni_weak_ref.h new file mode 100644 index 0000000..d7b0bdb --- /dev/null +++ b/src/base/android/jni_weak_ref.h
@@ -0,0 +1,52 @@ +// Copyright 2014 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_ANDROID_JNI_WEAK_REF_H_ +#define BASE_ANDROID_JNI_WEAK_REF_H_ + +#include <jni.h> + +#include "base/android/scoped_java_ref.h" +#include "base/base_export.h" +#include "starboard/types.h" + +// Manages WeakGlobalRef lifecycle. +// This class is not thread-safe w.r.t. get() and reset(). Multiple threads may +// safely use get() concurrently, but if the user calls reset() (or of course, +// calls the destructor) they'll need to provide their own synchronization. +class BASE_EXPORT JavaObjectWeakGlobalRef { + public: + JavaObjectWeakGlobalRef(); + JavaObjectWeakGlobalRef(const JavaObjectWeakGlobalRef& orig); + JavaObjectWeakGlobalRef(JavaObjectWeakGlobalRef&& orig) noexcept; + JavaObjectWeakGlobalRef(JNIEnv* env, jobject obj); + JavaObjectWeakGlobalRef(JNIEnv* env, + const base::android::JavaRef<jobject>& obj); + virtual ~JavaObjectWeakGlobalRef(); + + void operator=(const JavaObjectWeakGlobalRef& rhs); + void operator=(JavaObjectWeakGlobalRef&& rhs); + + base::android::ScopedJavaLocalRef<jobject> get(JNIEnv* env) const; + + // Returns true if the weak reference has not been initialized to point at + // an object (or ḣas had reset() called). + // Do not call this to test if the object referred to still exists! The weak + // reference remains initialized even if the target object has been collected. + bool is_uninitialized() const { return obj_ == nullptr; } + + void reset(); + + private: + void Assign(const JavaObjectWeakGlobalRef& rhs); + + jweak obj_; +}; + +// Get the real object stored in the weak reference returned as a +// ScopedJavaLocalRef. +BASE_EXPORT base::android::ScopedJavaLocalRef<jobject> GetRealObject( + JNIEnv* env, jweak obj); + +#endif // BASE_ANDROID_JNI_WEAK_REF_H_
diff --git a/src/base/android/junit/src/org/chromium/base/ApplicationStatusTest.java b/src/base/android/junit/src/org/chromium/base/ApplicationStatusTest.java new file mode 100644 index 0000000..eb18295 --- /dev/null +++ b/src/base/android/junit/src/org/chromium/base/ApplicationStatusTest.java
@@ -0,0 +1,70 @@ +// Copyright 2015 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. + +package org.chromium.base; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.app.Activity; +import android.view.KeyEvent; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.android.controller.ActivityController; +import org.robolectric.annotation.Config; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowActivity; +import org.robolectric.shadows.multidex.ShadowMultiDex; + +import org.chromium.base.test.BaseRobolectricTestRunner; + +/** Unit tests for {@link ApplicationStatus}. */ +@RunWith(BaseRobolectricTestRunner.class) +@Config(manifest = Config.NONE, + shadows = {ApplicationStatusTest.TrackingShadowActivity.class, ShadowMultiDex.class}) +public class ApplicationStatusTest { + /** Shadow that tracks calls to onWindowFocusChanged and dispatchKeyEvent. */ + @Implements(Activity.class) + public static class TrackingShadowActivity extends ShadowActivity { + private int mWindowFocusCalls; + private int mDispatchKeyEventCalls; + private boolean mReturnValueForKeyDispatch; + + @Implementation + public void onWindowFocusChanged(@SuppressWarnings("unused") boolean hasFocus) { + mWindowFocusCalls++; + } + + @Implementation + public boolean dispatchKeyEvent(@SuppressWarnings("unused") KeyEvent event) { + mDispatchKeyEventCalls++; + return mReturnValueForKeyDispatch; + } + } + + @Test + public void testWindowsFocusChanged() throws Exception { + ApplicationStatus.initialize(RuntimeEnvironment.application); + + ApplicationStatus.WindowFocusChangedListener mock = + mock(ApplicationStatus.WindowFocusChangedListener.class); + ApplicationStatus.registerWindowFocusChangedListener(mock); + + ActivityController<Activity> controller = + Robolectric.buildActivity(Activity.class).create().start().visible(); + TrackingShadowActivity shadow = (TrackingShadowActivity) Shadows.shadowOf(controller.get()); + + controller.get().getWindow().getCallback().onWindowFocusChanged(true); + // Assert that listeners were notified. + verify(mock).onWindowFocusChanged(controller.get(), true); + // Also ensure that the original activity is forwarded the notification. + Assert.assertEquals(1, shadow.mWindowFocusCalls); + } +}
diff --git a/src/base/android/junit/src/org/chromium/base/DiscardableReferencePoolTest.java b/src/base/android/junit/src/org/chromium/base/DiscardableReferencePoolTest.java new file mode 100644 index 0000000..5eff6c9 --- /dev/null +++ b/src/base/android/junit/src/org/chromium/base/DiscardableReferencePoolTest.java
@@ -0,0 +1,80 @@ +// Copyright 2017 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. + +package org.chromium.base; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +import org.chromium.base.DiscardableReferencePool.DiscardableReference; +import org.chromium.base.test.BaseRobolectricTestRunner; +import org.chromium.base.test.util.RetryOnFailure; + +import java.lang.ref.WeakReference; + +/** + * Tests for {@link DiscardableReferencePool}. + */ +@RunWith(BaseRobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class DiscardableReferencePoolTest { + /** + * Tests that draining the pool clears references and allows objects to be garbage collected. + */ + @Test + public void testDrain() { + DiscardableReferencePool pool = new DiscardableReferencePool(); + + Object object = new Object(); + WeakReference<Object> weakReference = new WeakReference<>(object); + + DiscardableReference<Object> discardableReference = pool.put(object); + Assert.assertEquals(object, discardableReference.get()); + + // Drop reference to the object itself, to allow it to be garbage-collected. + object = null; + + pool.drain(); + + // The discardable reference should be null now. + Assert.assertNull(discardableReference.get()); + + // The object is not (strongly) reachable anymore, so the weak reference may or may not be + // null (it could be if a GC has happened since the pool was drained). + // After an explicit GC call it definitely should be null. + Runtime.getRuntime().gc(); + + Assert.assertNull(weakReference.get()); + } + + /** + * Tests that dropping the (last) discardable reference to an object allows it to be regularly + * garbage collected. + */ + @Test + @RetryOnFailure + public void testReferenceGCd() { + DiscardableReferencePool pool = new DiscardableReferencePool(); + + Object object = new Object(); + WeakReference<Object> weakReference = new WeakReference<>(object); + + DiscardableReference<Object> discardableReference = pool.put(object); + Assert.assertEquals(object, discardableReference.get()); + + // Drop reference to the object itself and to the discardable reference, allowing the object + // to be garbage-collected. + object = null; + discardableReference = null; + + // The object is not (strongly) reachable anymore, so the weak reference may or may not be + // null (it could be if a GC has happened since the pool was drained). + // After an explicit GC call it definitely should be null. + Runtime.getRuntime().gc(); + + Assert.assertNull(weakReference.get()); + } +}
diff --git a/src/base/android/junit/src/org/chromium/base/LogTest.java b/src/base/android/junit/src/org/chromium/base/LogTest.java new file mode 100644 index 0000000..a3832a0 --- /dev/null +++ b/src/base/android/junit/src/org/chromium/base/LogTest.java
@@ -0,0 +1,87 @@ +// Copyright 2015 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. + +package org.chromium.base; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowLog; + +import org.chromium.base.test.BaseRobolectricTestRunner; + +import java.util.List; + +/** Unit tests for {@link Log}. */ +@RunWith(BaseRobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class LogTest { + /** Tests that the computed call origin is the correct one. */ + @Test + public void callOriginTest() { + Log.d("Foo", "Bar"); + + List<ShadowLog.LogItem> logs = ShadowLog.getLogs(); + assertEquals("Only one log should be written", 1, logs.size()); + + assertTrue("The origin of the log message (" + logs.get(0).msg + ") looks wrong.", + logs.get(0).msg.matches("\\[LogTest.java:\\d+\\].*")); + } + + @Test + public void normalizeTagTest() { + assertEquals("cr_foo", Log.normalizeTag("cr.foo")); + assertEquals("cr_foo", Log.normalizeTag("cr_foo")); + assertEquals("cr_foo", Log.normalizeTag("foo")); + assertEquals("cr_ab_foo", Log.normalizeTag("ab_foo")); + } + + /** Tests that exceptions provided to the log functions are properly recognized and printed. */ + @Test + public void exceptionLoggingTest() { + Throwable t = new Throwable() { + @Override + public String toString() { + return "MyThrowable"; + } + }; + + Throwable t2 = new Throwable() { + @Override + public String toString() { + return "MyOtherThrowable"; + } + }; + + List<ShadowLog.LogItem> logs; + + // The throwable gets printed out + Log.i("Foo", "Bar", t); + logs = ShadowLog.getLogs(); + assertEquals(t, logs.get(logs.size() - 1).throwable); + assertEquals("Bar", logs.get(logs.size() - 1).msg); + + // The throwable can be both added to the message itself and printed out + Log.i("Foo", "Bar %s", t); + logs = ShadowLog.getLogs(); + assertEquals(t, logs.get(logs.size() - 1).throwable); + assertEquals("Bar MyThrowable", logs.get(logs.size() - 1).msg); + + // Non throwable are properly identified + Log.i("Foo", "Bar %s", t, "Baz"); + logs = ShadowLog.getLogs(); + assertNull(logs.get(logs.size() - 1).throwable); + assertEquals("Bar MyThrowable", logs.get(logs.size() - 1).msg); + + // The last throwable is the one used that is going to be printed out + Log.i("Foo", "Bar %s %s", t, t2); + logs = ShadowLog.getLogs(); + assertEquals(t2, logs.get(logs.size() - 1).throwable); + assertEquals("Bar MyThrowable MyOtherThrowable", logs.get(logs.size() - 1).msg); + } +}
diff --git a/src/base/android/junit/src/org/chromium/base/NonThreadSafeTest.java b/src/base/android/junit/src/org/chromium/base/NonThreadSafeTest.java new file mode 100644 index 0000000..9c57199 --- /dev/null +++ b/src/base/android/junit/src/org/chromium/base/NonThreadSafeTest.java
@@ -0,0 +1,63 @@ +// Copyright 2014 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. + +package org.chromium.base; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.BlockJUnit4ClassRunner; + +import org.chromium.base.test.util.Feature; + +/** + * Tests for NonThreadSafe. + */ +@RunWith(BlockJUnit4ClassRunner.class) +public class NonThreadSafeTest { + /** + * Test for creating and using on the same thread + */ + @Test + @Feature({"Android-AppBase"}) + public void testCreateAndUseOnSameThread() { + NonThreadSafe t = new NonThreadSafe(); + Assert.assertTrue(t.calledOnValidThread()); + } + + /** + * Test if calledOnValidThread returns false if used on another thread. + */ + @Test + @Feature({"Android-AppBase"}) + public void testCreateAndUseOnDifferentThread() { + final NonThreadSafe t = new NonThreadSafe(); + + new Thread(new Runnable() { + @Override + public void run() { + Assert.assertFalse(t.calledOnValidThread()); + } + }).start(); + } + + /** + * Test if detachFromThread reassigns the thread. + */ + @Test + @Feature({"Android-AppBase"}) + public void testDetachFromThread() { + final NonThreadSafe t = new NonThreadSafe(); + Assert.assertTrue(t.calledOnValidThread()); + t.detachFromThread(); + + new Thread(new Runnable() { + @Override + public void run() { + Assert.assertTrue(t.calledOnValidThread()); + Assert.assertTrue(t.calledOnValidThread()); + } + }).start(); + } +}
diff --git a/src/base/android/junit/src/org/chromium/base/PromiseTest.java b/src/base/android/junit/src/org/chromium/base/PromiseTest.java new file mode 100644 index 0000000..58d3956 --- /dev/null +++ b/src/base/android/junit/src/org/chromium/base/PromiseTest.java
@@ -0,0 +1,316 @@ +// Copyright 2016 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. + +package org.chromium.base; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowLooper; + +import org.chromium.base.Promise.UnhandledRejectionException; +import org.chromium.base.test.BaseRobolectricTestRunner; + +/** Unit tests for {@link Promise}. */ +@RunWith(BaseRobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class PromiseTest { + // We need a simple mutable reference type for testing. + private static class Value { + private int mValue; + + public int get() { + return mValue; + } + + public void set(int value) { + mValue = value; + } + } + + /** Tests that the callback is called on fulfillment. */ + @Test + public void callback() { + final Value value = new Value(); + + Promise<Integer> promise = new Promise<Integer>(); + promise.then(PromiseTest.<Integer>setValue(value, 1)); + + assertEquals(value.get(), 0); + + promise.fulfill(new Integer(1)); + assertEquals(value.get(), 1); + } + + /** Tests that multiple callbacks are called. */ + @Test + public void multipleCallbacks() { + final Value value = new Value(); + + Promise<Integer> promise = new Promise<Integer>(); + Callback<Integer> callback = new Callback<Integer>() { + @Override + public void onResult(Integer result) { + value.set(value.get() + 1); + } + }; + promise.then(callback); + promise.then(callback); + + assertEquals(value.get(), 0); + + promise.fulfill(new Integer(0)); + assertEquals(value.get(), 2); + } + + /** Tests that a callback is called immediately when given to a fulfilled Promise. */ + @Test + public void callbackOnFulfilled() { + final Value value = new Value(); + + Promise<Integer> promise = Promise.fulfilled(new Integer(0)); + assertEquals(value.get(), 0); + + promise.then(PromiseTest.<Integer>setValue(value, 1)); + + assertEquals(value.get(), 1); + } + + /** Tests that promises can chain synchronous functions correctly. */ + @Test + public void promiseChaining() { + Promise<Integer> promise = new Promise<Integer>(); + final Value value = new Value(); + + promise.then(new Promise.Function<Integer, String>(){ + @Override + public String apply(Integer arg) { + return arg.toString(); + } + }).then(new Promise.Function<String, String>(){ + @Override + public String apply(String arg) { + return arg + arg; + } + }).then(new Callback<String>() { + @Override + public void onResult(String result) { + value.set(result.length()); + } + }); + + promise.fulfill(new Integer(123)); + ShadowLooper.runUiThreadTasksIncludingDelayedTasks(); + assertEquals(6, value.get()); + } + + /** Tests that promises can chain asynchronous functions correctly. */ + @Test + public void promiseChainingAsyncFunctions() { + Promise<Integer> promise = new Promise<Integer>(); + final Value value = new Value(); + + final Promise<String> innerPromise = new Promise<String>(); + + promise.then(new Promise.AsyncFunction<Integer, String>() { + @Override + public Promise<String> apply(Integer arg) { + return innerPromise; + } + }).then(new Callback<String>(){ + @Override + public void onResult(String result) { + value.set(result.length()); + } + }); + + assertEquals(0, value.get()); + + promise.fulfill(5); + ShadowLooper.runUiThreadTasksIncludingDelayedTasks(); + assertEquals(0, value.get()); + + innerPromise.fulfill("abc"); + ShadowLooper.runUiThreadTasksIncludingDelayedTasks(); + assertEquals(3, value.get()); + } + + /** Tests that a Promise that does not use its result does not throw on rejection. */ + @Test + public void rejectPromiseNoCallbacks() { + Promise<Integer> promise = new Promise<Integer>(); + + boolean caught = false; + try { + promise.reject(); + ShadowLooper.runUiThreadTasksIncludingDelayedTasks(); + } catch (UnhandledRejectionException e) { + caught = true; + } + assertFalse(caught); + } + + /** Tests that a Promise that uses its result throws on rejection if it has no handler. */ + @Test + public void rejectPromiseNoHandler() { + Promise<Integer> promise = new Promise<Integer>(); + promise.then(PromiseTest.<Integer>identity()).then(PromiseTest.<Integer>pass()); + + boolean caught = false; + try { + promise.reject(); + ShadowLooper.runUiThreadTasksIncludingDelayedTasks(); + } catch (UnhandledRejectionException e) { + caught = true; + } + assertTrue(caught); + } + + /** Tests that a Promise that handles rejection does not throw on rejection. */ + @Test + public void rejectPromiseHandled() { + Promise<Integer> promise = new Promise<Integer>(); + promise.then(PromiseTest.<Integer>identity()) + .then(PromiseTest.<Integer>pass(), PromiseTest.<Exception>pass()); + + boolean caught = false; + try { + promise.reject(); + ShadowLooper.runUiThreadTasksIncludingDelayedTasks(); + } catch (UnhandledRejectionException e) { + caught = true; + } + assertFalse(caught); + } + + /** Tests that rejections carry the exception information. */ + @Test + public void rejectionInformation() { + Promise<Integer> promise = new Promise<Integer>(); + promise.then(PromiseTest.<Integer>pass()); + + String message = "Promise Test"; + try { + promise.reject(new NegativeArraySizeException(message)); + ShadowLooper.runUiThreadTasksIncludingDelayedTasks(); + fail(); + } catch (UnhandledRejectionException e) { + assertTrue(e.getCause() instanceof NegativeArraySizeException); + assertEquals(e.getCause().getMessage(), message); + } + } + + /** Tests that rejections propagate. */ + @Test + public void rejectionChaining() { + final Value value = new Value(); + Promise<Integer> promise = new Promise<Integer>(); + + Promise<Integer> result = + promise.then(PromiseTest.<Integer>identity()).then(PromiseTest.<Integer>identity()); + + result.then(PromiseTest.<Integer>pass(), PromiseTest.<Exception>setValue(value, 5)); + + promise.reject(new Exception()); + ShadowLooper.runUiThreadTasksIncludingDelayedTasks(); + + assertEquals(value.get(), 5); + assertTrue(result.isRejected()); + } + + /** Tests that Promises get rejected if a Function throws. */ + @Test + public void rejectOnThrow() { + Value value = new Value(); + Promise<Integer> promise = new Promise<Integer>(); + promise.then(new Promise.Function<Integer, Integer>() { + @Override + public Integer apply(Integer argument) { + throw new IllegalArgumentException(); + } + }).then(PromiseTest.<Integer>pass(), PromiseTest.<Exception>setValue(value, 5)); + + promise.fulfill(0); + + ShadowLooper.runUiThreadTasksIncludingDelayedTasks(); + assertEquals(value.get(), 5); + } + + /** Tests that Promises get rejected if an AsyncFunction throws. */ + @Test + public void rejectOnAsyncThrow() { + Value value = new Value(); + Promise<Integer> promise = new Promise<Integer>(); + + promise.then(new Promise.AsyncFunction<Integer, Integer>() { + @Override + public Promise<Integer> apply(Integer argument) { + throw new IllegalArgumentException(); + } + }).then(PromiseTest.<Integer>pass(), PromiseTest.<Exception>setValue(value, 5)); + + promise.fulfill(0); + + ShadowLooper.runUiThreadTasksIncludingDelayedTasks(); + assertEquals(value.get(), 5); + } + + /** Tests that Promises get rejected if an AsyncFunction rejects. */ + @Test + public void rejectOnAsyncReject() { + Value value = new Value(); + Promise<Integer> promise = new Promise<Integer>(); + final Promise<Integer> inner = new Promise<Integer>(); + + promise.then(new Promise.AsyncFunction<Integer, Integer>() { + @Override + public Promise<Integer> apply(Integer argument) { + return inner; + } + }).then(PromiseTest.<Integer>pass(), PromiseTest.<Exception>setValue(value, 5)); + + promise.fulfill(0); + + ShadowLooper.runUiThreadTasksIncludingDelayedTasks(); + assertEquals(value.get(), 0); + + inner.reject(); + + ShadowLooper.runUiThreadTasksIncludingDelayedTasks(); + assertEquals(value.get(), 5); + } + + /** Convenience method that returns a Callback that does nothing with its result. */ + private static <T> Callback<T> pass() { + return new Callback<T>() { + @Override + public void onResult(T result) {} + }; + } + + /** Convenience method that returns a Function that just passes through its argument. */ + private static <T> Promise.Function<T, T> identity() { + return new Promise.Function<T, T>() { + @Override + public T apply(T argument) { + return argument; + } + }; + } + + /** Convenience method that returns a Callback that sets the given Value on execution. */ + private static <T> Callback<T> setValue(final Value toSet, final int value) { + return new Callback<T>() { + @Override + public void onResult(T result) { + toSet.set(value); + } + }; + } +}
diff --git a/src/base/android/junit/src/org/chromium/base/memory/MemoryPressureMonitorTest.java b/src/base/android/junit/src/org/chromium/base/memory/MemoryPressureMonitorTest.java new file mode 100644 index 0000000..1249a66 --- /dev/null +++ b/src/base/android/junit/src/org/chromium/base/memory/MemoryPressureMonitorTest.java
@@ -0,0 +1,356 @@ +// Copyright 2018 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. + +package org.chromium.base.memory; + +import android.content.ComponentCallbacks2; +import android.os.Looper; +import android.support.test.filters.SmallTest; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowLooper; + +import org.chromium.base.MemoryPressureLevel; +import org.chromium.base.Supplier; +import org.chromium.base.ThreadUtils; +import org.chromium.base.test.BaseRobolectricTestRunner; + +import java.util.concurrent.TimeUnit; + +/** + * Test for MemoryPressureMonitor. + */ +@RunWith(BaseRobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class MemoryPressureMonitorTest { + private MemoryPressureMonitor mMonitor; + + private static class TestPressureCallback implements MemoryPressureCallback { + private Integer mReportedPressure; + + public void assertCalledWith(@MemoryPressureLevel int expectedPressure) { + Assert.assertNotNull("Callback was not called", mReportedPressure); + Assert.assertEquals(expectedPressure, (int) mReportedPressure); + } + + public void assertNotCalled() { + Assert.assertNull(mReportedPressure); + } + + public void reset() { + mReportedPressure = null; + } + + @Override + public void onPressure(@MemoryPressureLevel int pressure) { + assertNotCalled(); + mReportedPressure = pressure; + } + } + + private static class TestPressureSupplier implements Supplier<Integer> { + private @MemoryPressureLevel Integer mPressure; + private boolean mIsCalled; + + public TestPressureSupplier(@MemoryPressureLevel Integer pressure) { + mPressure = pressure; + } + + @Override + public @MemoryPressureLevel Integer get() { + assertNotCalled(); + mIsCalled = true; + return mPressure; + } + + public void assertCalled() { + Assert.assertTrue(mIsCalled); + } + + public void assertNotCalled() { + Assert.assertFalse(mIsCalled); + } + } + + private static final int THROTTLING_INTERVAL_MS = 1000; + + @Before + public void setUp() { + // Explicitly set main thread as UiThread. Other places rely on that. + ThreadUtils.setUiThread(Looper.getMainLooper()); + + // Pause main thread to get control over when tasks are run (see runUiThreadFor()). + ShadowLooper.pauseMainLooper(); + + mMonitor = new MemoryPressureMonitor(THROTTLING_INTERVAL_MS); + mMonitor.setCurrentPressureSupplierForTesting(null); + } + + /** + * Runs all UiThread tasks posted |delayMs| in the future. + * @param delayMs + */ + private void runUiThreadFor(long delayMs) { + ShadowLooper.idleMainLooper(delayMs, TimeUnit.MILLISECONDS); + } + + @Test + @SmallTest + public void testTrimLevelTranslation() { + Integer[][] trimLevelToPressureMap = {// + // Levels >= TRIM_MEMORY_COMPLETE map to CRITICAL. + {ComponentCallbacks2.TRIM_MEMORY_COMPLETE + 1, MemoryPressureLevel.CRITICAL}, + {ComponentCallbacks2.TRIM_MEMORY_COMPLETE, MemoryPressureLevel.CRITICAL}, + + // TRIM_MEMORY_RUNNING_CRITICAL maps to CRITICAL. + {ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL, MemoryPressureLevel.CRITICAL}, + + // Levels < TRIM_MEMORY_COMPLETE && >= TRIM_MEMORY_BACKGROUND map to MODERATE. + {ComponentCallbacks2.TRIM_MEMORY_COMPLETE - 1, MemoryPressureLevel.MODERATE}, + {ComponentCallbacks2.TRIM_MEMORY_BACKGROUND + 1, MemoryPressureLevel.MODERATE}, + {ComponentCallbacks2.TRIM_MEMORY_BACKGROUND, MemoryPressureLevel.MODERATE}, + + // Other levels are not mapped. + {ComponentCallbacks2.TRIM_MEMORY_BACKGROUND - 1, null}, + {ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW, null}, + {ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE, null}, + {ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN, null}}; + for (Integer[] trimLevelAndPressure : trimLevelToPressureMap) { + int trimLevel = trimLevelAndPressure[0]; + Integer expectedPressure = trimLevelAndPressure[1]; + Integer actualPressure = MemoryPressureMonitor.memoryPressureFromTrimLevel(trimLevel); + Assert.assertEquals(expectedPressure, actualPressure); + } + } + + @Test + @SmallTest + public void testThrottleInterval() { + TestPressureCallback callback = new TestPressureCallback(); + mMonitor.setReportingCallbackForTesting(callback); + + // First notification should go through. + mMonitor.notifyPressure(MemoryPressureLevel.NONE); + callback.assertCalledWith(MemoryPressureLevel.NONE); + + callback.reset(); + + // This one should be throttled. + mMonitor.notifyPressure(MemoryPressureLevel.NONE); + callback.assertNotCalled(); + + runUiThreadFor(THROTTLING_INTERVAL_MS - 1); + + // We're still within the throttling interval, so this notification should + // still be throttled. + mMonitor.notifyPressure(MemoryPressureLevel.NONE); + callback.assertNotCalled(); + + runUiThreadFor(1); + + // We're past the throttling interval at this point, so this notification + // should go through. + mMonitor.notifyPressure(MemoryPressureLevel.NONE); + callback.assertCalledWith(MemoryPressureLevel.NONE); + } + + @Test + @SmallTest + public void testChangeNotIgnored() { + TestPressureCallback callback = new TestPressureCallback(); + mMonitor.setReportingCallbackForTesting(callback); + + mMonitor.notifyPressure(MemoryPressureLevel.NONE); + callback.assertCalledWith(MemoryPressureLevel.NONE); + + callback.reset(); + + // Second notification is throttled, but should be reported after the + // throttling interval ends. + mMonitor.notifyPressure(MemoryPressureLevel.MODERATE); + callback.assertNotCalled(); + + runUiThreadFor(THROTTLING_INTERVAL_MS - 1); + + // Shouldn't be reported at this point. + callback.assertNotCalled(); + + runUiThreadFor(1); + + callback.assertCalledWith(MemoryPressureLevel.MODERATE); + } + + @Test + @SmallTest + public void testNoopChangeIgnored() { + TestPressureCallback callback = new TestPressureCallback(); + mMonitor.setReportingCallbackForTesting(callback); + + mMonitor.notifyPressure(MemoryPressureLevel.NONE); + callback.assertCalledWith(MemoryPressureLevel.NONE); + + callback.reset(); + + // Report MODERATE and then NONE, so that the throttling interval finishes with the + // same pressure that started it (i.e. NONE). In this case MODERATE should be ignored. + mMonitor.notifyPressure(MemoryPressureLevel.MODERATE); + mMonitor.notifyPressure(MemoryPressureLevel.NONE); + + runUiThreadFor(THROTTLING_INTERVAL_MS); + + callback.assertNotCalled(); + } + + @Test + @SmallTest + public void testPollingInitiallyDisabled() { + TestPressureSupplier pressureSupplier = + new TestPressureSupplier(MemoryPressureLevel.MODERATE); + mMonitor.setCurrentPressureSupplierForTesting(pressureSupplier); + + mMonitor.notifyPressure(MemoryPressureLevel.CRITICAL); + runUiThreadFor(THROTTLING_INTERVAL_MS); + + // We finished the interval with CRITICAL, but since polling is disabled, we shouldn't + // poll the current pressure. + pressureSupplier.assertNotCalled(); + } + + @Test + @SmallTest + public void testEnablePollingPolls() { + TestPressureCallback callback = new TestPressureCallback(); + mMonitor.setReportingCallbackForTesting(callback); + + TestPressureSupplier pressureSupplier = + new TestPressureSupplier(MemoryPressureLevel.MODERATE); + mMonitor.setCurrentPressureSupplierForTesting(pressureSupplier); + + mMonitor.enablePolling(); + + // When polling is enabled, current pressure should be retrieved and reported. + pressureSupplier.assertCalled(); + callback.assertCalledWith(MemoryPressureLevel.MODERATE); + } + + @Test + @SmallTest + public void testNullSupplierResultIgnored() { + TestPressureCallback callback = new TestPressureCallback(); + mMonitor.setReportingCallbackForTesting(callback); + + TestPressureSupplier pressureSupplier = new TestPressureSupplier(null); + mMonitor.setCurrentPressureSupplierForTesting(pressureSupplier); + + mMonitor.enablePolling(); + + // The pressure supplier should be called, but its null result should be ignored. + pressureSupplier.assertCalled(); + callback.assertNotCalled(); + } + + @Test + @SmallTest + public void testEnablePollingRespectsThrottling() { + TestPressureSupplier pressureSupplier = + new TestPressureSupplier(MemoryPressureLevel.MODERATE); + mMonitor.setCurrentPressureSupplierForTesting(pressureSupplier); + + mMonitor.notifyPressure(MemoryPressureLevel.NONE); + + // The notification above started a throttling interval, so we shouldn't ask for the + // current pressure when polling is enabled. + mMonitor.enablePolling(); + + pressureSupplier.assertNotCalled(); + } + + @Test + @SmallTest + public void testPollingIfCRITICAL() { + TestPressureCallback callback = new TestPressureCallback(); + mMonitor.setReportingCallbackForTesting(callback); + + TestPressureSupplier pressureSupplier = + new TestPressureSupplier(MemoryPressureLevel.MODERATE); + mMonitor.setCurrentPressureSupplierForTesting(pressureSupplier); + + mMonitor.notifyPressure(MemoryPressureLevel.CRITICAL); + callback.reset(); + + mMonitor.enablePolling(); + + runUiThreadFor(THROTTLING_INTERVAL_MS - 1); + + // Pressure should be polled after the interval ends, not before. + pressureSupplier.assertNotCalled(); + + runUiThreadFor(1); + + // We started and finished the throttling interval with CRITICAL pressure, so + // we should poll the current pressure at the end of the interval. + pressureSupplier.assertCalled(); + callback.assertCalledWith(MemoryPressureLevel.MODERATE); + } + + @Test + @SmallTest + public void testNoPollingIfNotCRITICAL() { + TestPressureSupplier pressureSupplier = new TestPressureSupplier(MemoryPressureLevel.NONE); + mMonitor.setCurrentPressureSupplierForTesting(pressureSupplier); + + mMonitor.notifyPressure(MemoryPressureLevel.MODERATE); + + mMonitor.enablePolling(); + + runUiThreadFor(THROTTLING_INTERVAL_MS); + + // We started and finished the throttling interval with non-CRITICAL pressure, + // so no polling should take place. + pressureSupplier.assertNotCalled(); + } + + @Test + @SmallTest + public void testNoPollingIfChangedToCRITICAL() { + TestPressureSupplier pressureSupplier = new TestPressureSupplier(MemoryPressureLevel.NONE); + mMonitor.setCurrentPressureSupplierForTesting(pressureSupplier); + + mMonitor.notifyPressure(MemoryPressureLevel.MODERATE); + mMonitor.notifyPressure(MemoryPressureLevel.CRITICAL); + + mMonitor.enablePolling(); + + runUiThreadFor(THROTTLING_INTERVAL_MS); + + // We finished the throttling interval with CRITITCAL, but started with MODERATE, + // so no polling should take place. + pressureSupplier.assertNotCalled(); + } + + @Test + @SmallTest + public void testDisablePolling() { + TestPressureSupplier pressureSupplier = new TestPressureSupplier(MemoryPressureLevel.NONE); + mMonitor.setCurrentPressureSupplierForTesting(pressureSupplier); + + mMonitor.notifyPressure(MemoryPressureLevel.CRITICAL); + + mMonitor.enablePolling(); + + runUiThreadFor(THROTTLING_INTERVAL_MS - 1); + + // Whether polling is enabled or not should be taken into account only after the interval + // finishes, so disabling it here should have the same affect as if it was never enabled. + mMonitor.disablePolling(); + + runUiThreadFor(1); + + pressureSupplier.assertNotCalled(); + } +}
diff --git a/src/base/android/junit/src/org/chromium/base/metrics/test/ShadowRecordHistogram.java b/src/base/android/junit/src/org/chromium/base/metrics/test/ShadowRecordHistogram.java new file mode 100644 index 0000000..4b0c049 --- /dev/null +++ b/src/base/android/junit/src/org/chromium/base/metrics/test/ShadowRecordHistogram.java
@@ -0,0 +1,69 @@ +// Copyright 2017 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. + +package org.chromium.base.metrics.test; + +import android.util.Pair; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; + +import org.chromium.base.metrics.RecordHistogram; + +import java.util.HashMap; +import java.util.concurrent.TimeUnit; + +/** + * Implementation of RecordHistogram which does not rely on native and still enables testing of + * histogram counts. + */ +@Implements(RecordHistogram.class) +public class ShadowRecordHistogram { + private static HashMap<Pair<String, Integer>, Integer> sSamples = + new HashMap<Pair<String, Integer>, Integer>(); + + @Resetter + public static void reset() { + sSamples.clear(); + } + + @Implementation + public static void recordCountHistogram(String name, int sample) { + Pair<String, Integer> key = Pair.create(name, sample); + incrementSampleCount(key); + } + + @Implementation + public static void recordCount100Histogram(String name, int sample) { + Pair<String, Integer> key = Pair.create(name, sample); + incrementSampleCount(key); + } + + @Implementation + public static void recordEnumeratedHistogram(String name, int sample, int boundary) { + assert sample < boundary : "Sample " + sample + " is not within boundary " + boundary + "!"; + incrementSampleCount(Pair.create(name, sample)); + } + + @Implementation + public static void recordLongTimesHistogram100(String name, long duration, TimeUnit timeUnit) { + Pair<String, Integer> key = Pair.create(name, (int) timeUnit.toMillis(duration)); + incrementSampleCount(key); + } + + @Implementation + public static int getHistogramValueCountForTesting(String name, int sample) { + Integer i = sSamples.get(Pair.create(name, sample)); + return (i != null) ? i : 0; + } + + private static void incrementSampleCount(Pair<String, Integer> key) { + Integer i = sSamples.get(key); + if (i == null) { + i = 0; + } + sSamples.put(key, i + 1); + } +}
diff --git a/src/base/android/junit/src/org/chromium/base/process_launcher/ChildConnectionAllocatorTest.java b/src/base/android/junit/src/org/chromium/base/process_launcher/ChildConnectionAllocatorTest.java new file mode 100644 index 0000000..5c5bca9 --- /dev/null +++ b/src/base/android/junit/src/org/chromium/base/process_launcher/ChildConnectionAllocatorTest.java
@@ -0,0 +1,332 @@ +// Copyright 2017 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. + +package org.chromium.base.process_launcher; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.ComponentName; +import android.content.Context; +import android.os.Bundle; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowLooper; + +import org.chromium.base.test.BaseRobolectricTestRunner; +import org.chromium.base.test.util.Feature; + +import java.util.HashSet; +import java.util.Set; + +/** Unit tests for the ChildConnectionAllocator class. */ +@Config(manifest = Config.NONE) +@RunWith(BaseRobolectricTestRunner.class) +public class ChildConnectionAllocatorTest { + private static final String TEST_PACKAGE_NAME = "org.chromium.allocator_test"; + + private static final int MAX_CONNECTION_NUMBER = 2; + + private static final int FREE_CONNECTION_TEST_CALLBACK_START_FAILED = 1; + private static final int FREE_CONNECTION_TEST_CALLBACK_PROCESS_DIED = 2; + + @Mock + private ChildProcessConnection.ServiceCallback mServiceCallback; + + static class TestConnectionFactory implements ChildConnectionAllocator.ConnectionFactory { + private ComponentName mLastServiceName; + + private ChildProcessConnection mConnection; + + private ChildProcessConnection.ServiceCallback mConnectionServiceCallback; + + @Override + public ChildProcessConnection createConnection(Context context, ComponentName serviceName, + boolean bindToCaller, boolean bindAsExternalService, Bundle serviceBundle) { + mLastServiceName = serviceName; + if (mConnection == null) { + mConnection = mock(ChildProcessConnection.class); + // Retrieve the ServiceCallback so we can simulate the service process dying. + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) { + mConnectionServiceCallback = + (ChildProcessConnection.ServiceCallback) invocation.getArgument(1); + return null; + } + }) + .when(mConnection) + .start(anyBoolean(), any(ChildProcessConnection.ServiceCallback.class)); + } + return mConnection; + } + + public ComponentName getAndResetLastServiceName() { + ComponentName serviceName = mLastServiceName; + mLastServiceName = null; + return serviceName; + } + + // Use this method to have a callback invoked when the connection is started on the next + // created connection. + public void invokeCallbackOnConnectionStart(final boolean onChildStarted, + final boolean onStartFailed, final boolean onChildProcessDied) { + final ChildProcessConnection connection = mock(ChildProcessConnection.class); + mConnection = connection; + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) { + ChildProcessConnection.ServiceCallback serviceCallback = + (ChildProcessConnection.ServiceCallback) invocation.getArgument(1); + if (onChildStarted) { + serviceCallback.onChildStarted(); + } + if (onStartFailed) { + serviceCallback.onChildStartFailed(connection); + } + if (onChildProcessDied) { + serviceCallback.onChildProcessDied(connection); + } + return null; + } + }) + .when(mConnection) + .start(anyBoolean(), any(ChildProcessConnection.ServiceCallback.class)); + } + + public void simulateServiceStartFailed() { + mConnectionServiceCallback.onChildStartFailed(mConnection); + } + + public void simulateServiceProcessDying() { + mConnectionServiceCallback.onChildProcessDied(mConnection); + } + } + + private final TestConnectionFactory mTestConnectionFactory = new TestConnectionFactory(); + + private ChildConnectionAllocator mAllocator; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mAllocator = ChildConnectionAllocator.createForTest(null, TEST_PACKAGE_NAME, + "AllocatorTest", MAX_CONNECTION_NUMBER, true /* bindToCaller */, + false /* bindAsExternalService */, false /* useStrongBinding */); + mAllocator.setConnectionFactoryForTesting(mTestConnectionFactory); + } + + @Test + @Feature({"ProcessManagement"}) + public void testPlainAllocate() { + assertFalse(mAllocator.anyConnectionAllocated()); + assertEquals(MAX_CONNECTION_NUMBER, mAllocator.getNumberOfServices()); + + ChildProcessConnection connection = + mAllocator.allocate(null /* context */, null /* serviceBundle */, mServiceCallback); + assertNotNull(connection); + + verify(connection, times(1)) + .start(eq(false) /* useStrongBinding */, + any(ChildProcessConnection.ServiceCallback.class)); + assertTrue(mAllocator.anyConnectionAllocated()); + } + + /** Tests that different services are created until we reach the max number specified. */ + @Test + @Feature({"ProcessManagement"}) + public void testAllocateMaxNumber() { + assertTrue(mAllocator.isFreeConnectionAvailable()); + Set<ComponentName> serviceNames = new HashSet<>(); + for (int i = 0; i < MAX_CONNECTION_NUMBER; i++) { + ChildProcessConnection connection = mAllocator.allocate( + null /* context */, null /* serviceBundle */, mServiceCallback); + assertNotNull(connection); + ComponentName serviceName = mTestConnectionFactory.getAndResetLastServiceName(); + assertFalse(serviceNames.contains(serviceName)); + serviceNames.add(serviceName); + } + assertFalse(mAllocator.isFreeConnectionAvailable()); + assertNull(mAllocator.allocate( + null /* context */, null /* serviceBundle */, mServiceCallback)); + } + + @Test + @Feature({"ProcessManagement"}) + public void testQueueAllocation() { + Runnable freeConnectionCallback = mock(Runnable.class); + mAllocator = ChildConnectionAllocator.createForTest(freeConnectionCallback, + TEST_PACKAGE_NAME, "AllocatorTest", 1, true /* bindToCaller */, + false /* bindAsExternalService */, false /* useStrongBinding */); + mAllocator.setConnectionFactoryForTesting(mTestConnectionFactory); + // Occupy all slots. + ChildProcessConnection connection = + mAllocator.allocate(null /* context */, null /* serviceBundle */, mServiceCallback); + assertNotNull(connection); + assertFalse(mAllocator.isFreeConnectionAvailable()); + + final ChildProcessConnection newConnection[] = new ChildProcessConnection[2]; + Runnable allocate1 = () -> { + newConnection[0] = mAllocator.allocate( + null /* context */, null /* serviceBundle */, mServiceCallback); + }; + Runnable allocate2 = () -> { + newConnection[1] = mAllocator.allocate( + null /* context */, null /* serviceBundle */, mServiceCallback); + }; + mAllocator.queueAllocation(allocate1); + mAllocator.queueAllocation(allocate2); + verify(freeConnectionCallback, times(1)).run(); + assertNull(newConnection[0]); + + mTestConnectionFactory.simulateServiceProcessDying(); + ShadowLooper.runUiThreadTasksIncludingDelayedTasks(); + assertNotNull(newConnection[0]); + assertNull(newConnection[1]); + + mTestConnectionFactory.simulateServiceProcessDying(); + ShadowLooper.runUiThreadTasksIncludingDelayedTasks(); + assertNotNull(newConnection[1]); + } + + /** + * Tests that the connection is created with the useStrongBinding parameter specified in the + * allocator. + */ + @Test + @Feature({"ProcessManagement"}) + public void testStrongBindingParam() { + for (boolean useStrongBinding : new boolean[] {true, false}) { + ChildConnectionAllocator allocator = ChildConnectionAllocator.createForTest(null, + TEST_PACKAGE_NAME, "AllocatorTest", MAX_CONNECTION_NUMBER, + true /* bindToCaller */, false /* bindAsExternalService */, useStrongBinding); + allocator.setConnectionFactoryForTesting(mTestConnectionFactory); + ChildProcessConnection connection = allocator.allocate( + null /* context */, null /* serviceBundle */, mServiceCallback); + verify(connection, times(0)).start(useStrongBinding, mServiceCallback); + } + } + + /** + * Tests that the various ServiceCallbacks are propagated and posted, so they happen after the + * ChildProcessAllocator,allocate() method has returned. + */ + public void runTestWithConnectionCallbacks( + boolean onChildStarted, boolean onChildStartFailed, boolean onChildProcessDied) { + // We have to pause the Roboletric looper or it'll execute the posted tasks synchronoulsy. + ShadowLooper.pauseMainLooper(); + mTestConnectionFactory.invokeCallbackOnConnectionStart( + onChildStarted, onChildStartFailed, onChildProcessDied); + ChildProcessConnection connection = + mAllocator.allocate(null /* context */, null /* serviceBundle */, mServiceCallback); + assertNotNull(connection); + + // Callbacks are posted. + verify(mServiceCallback, never()).onChildStarted(); + verify(mServiceCallback, never()).onChildStartFailed(any()); + verify(mServiceCallback, never()).onChildProcessDied(any()); + ShadowLooper.unPauseMainLooper(); + ShadowLooper.runUiThreadTasksIncludingDelayedTasks(); + verify(mServiceCallback, times(onChildStarted ? 1 : 0)).onChildStarted(); + verify(mServiceCallback, times(onChildStartFailed ? 1 : 0)).onChildStartFailed(any()); + verify(mServiceCallback, times(onChildProcessDied ? 1 : 0)).onChildProcessDied(any()); + } + + @Test + @Feature({"ProcessManagement"}) + public void testOnChildStartedCallback() { + runTestWithConnectionCallbacks(true /* onChildStarted */, false /* onChildStartFailed */, + false /* onChildProcessDied */); + } + + @Test + @Feature({"ProcessManagement"}) + public void testOnChildStartFailedCallback() { + runTestWithConnectionCallbacks(false /* onChildStarted */, true /* onChildStartFailed */, + false /* onChildProcessDied */); + } + + @Test + @Feature({"ProcessManagement"}) + public void testOnChildProcessDiedCallback() { + runTestWithConnectionCallbacks(false /* onChildStarted */, false /* onChildStartFailed */, + true /* onChildProcessDied */); + } + + /** + * Tests that the allocator clears the connection when it fails to bind/process dies. + */ + private void testFreeConnection(int callbackType) { + ChildProcessConnection connection = + mAllocator.allocate(null /* context */, null /* serviceBundle */, mServiceCallback); + + assertNotNull(connection); + ComponentName serviceName = mTestConnectionFactory.getAndResetLastServiceName(); + verify(connection, times(1)) + .start(eq(false) /* useStrongBinding */, + any(ChildProcessConnection.ServiceCallback.class)); + assertTrue(mAllocator.anyConnectionAllocated()); + int onChildStartFailedExpectedCount = 0; + int onChildProcessDiedExpectedCount = 0; + switch (callbackType) { + case FREE_CONNECTION_TEST_CALLBACK_START_FAILED: + mTestConnectionFactory.simulateServiceStartFailed(); + onChildStartFailedExpectedCount = 1; + break; + case FREE_CONNECTION_TEST_CALLBACK_PROCESS_DIED: + mTestConnectionFactory.simulateServiceProcessDying(); + onChildProcessDiedExpectedCount = 1; + break; + default: + fail(); + break; + } + ShadowLooper.runUiThreadTasksIncludingDelayedTasks(); + assertFalse(mAllocator.anyConnectionAllocated()); + verify(mServiceCallback, never()).onChildStarted(); + verify(mServiceCallback, times(onChildStartFailedExpectedCount)) + .onChildStartFailed(connection); + verify(mServiceCallback, times(onChildProcessDiedExpectedCount)) + .onChildProcessDied(connection); + + // Allocate a new connection to make sure we are not getting the same connection. + connection = + mAllocator.allocate(null /* context */, null /* serviceBundle */, mServiceCallback); + assertNotNull(connection); + assertNotEquals(mTestConnectionFactory.getAndResetLastServiceName(), serviceName); + } + + @Test + @Feature({"ProcessManagement"}) + public void testFreeConnectionOnChildStartFailed() { + testFreeConnection(FREE_CONNECTION_TEST_CALLBACK_START_FAILED); + } + + @Test + @Feature({"ProcessManagement"}) + public void testFreeConnectionOnChildProcessDied() { + testFreeConnection(FREE_CONNECTION_TEST_CALLBACK_PROCESS_DIED); + } +}
diff --git a/src/base/android/junit/src/org/chromium/base/process_launcher/ChildProcessConnectionTest.java b/src/base/android/junit/src/org/chromium/base/process_launcher/ChildProcessConnectionTest.java new file mode 100644 index 0000000..a12d50d --- /dev/null +++ b/src/base/android/junit/src/org/chromium/base/process_launcher/ChildProcessConnectionTest.java
@@ -0,0 +1,425 @@ +// Copyright 2017 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. + +package org.chromium.base.process_launcher; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.AdditionalMatchers.or; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ComponentName; +import android.content.Intent; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowLooper; + +import org.chromium.base.ChildBindingState; +import org.chromium.base.test.BaseRobolectricTestRunner; + +/** Unit tests for ChildProcessConnection. */ +@RunWith(BaseRobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class ChildProcessConnectionTest { + private static class ChildServiceConnectionMock + implements ChildProcessConnection.ChildServiceConnection { + private final Intent mBindIntent; + private final ChildProcessConnection.ChildServiceConnectionDelegate mDelegate; + private boolean mBound; + + public ChildServiceConnectionMock( + Intent bindIntent, ChildProcessConnection.ChildServiceConnectionDelegate delegate) { + mBindIntent = bindIntent; + mDelegate = delegate; + } + + @Override + public boolean bind() { + mBound = true; + return true; + } + + @Override + public void unbind() { + mBound = false; + } + + @Override + public boolean isBound() { + return mBound; + } + + public void notifyServiceConnected(IBinder service) { + mDelegate.onServiceConnected(service); + } + + public void notifyServiceDisconnected() { + mDelegate.onServiceDisconnected(); + } + + public Intent getBindIntent() { + return mBindIntent; + } + }; + + private final ChildProcessConnection.ChildServiceConnectionFactory mServiceConnectionFactory = + new ChildProcessConnection.ChildServiceConnectionFactory() { + @Override + public ChildProcessConnection.ChildServiceConnection createConnection( + Intent bindIntent, int bindFlags, + ChildProcessConnection.ChildServiceConnectionDelegate delegate) { + ChildServiceConnectionMock connection = + spy(new ChildServiceConnectionMock(bindIntent, delegate)); + if (mFirstServiceConnection == null) { + mFirstServiceConnection = connection; + } + return connection; + } + }; + + @Mock + private ChildProcessConnection.ServiceCallback mServiceCallback; + + @Mock + private ChildProcessConnection.ConnectionCallback mConnectionCallback; + + private IChildProcessService mIChildProcessService; + + private Binder mChildProcessServiceBinder; + + private ChildServiceConnectionMock mFirstServiceConnection; + + // Parameters captured from the IChildProcessService.setupConnection() call + private Bundle mConnectionBundle; + private ICallbackInt mConnectionPidCallback; + private IBinder mConnectionIBinderCallback; + + @Before + public void setUp() throws RemoteException { + MockitoAnnotations.initMocks(this); + + mIChildProcessService = mock(IChildProcessService.class); + // Capture the parameters passed to the IChildProcessService.setupConnection() call. + doAnswer(new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) { + mConnectionBundle = (Bundle) invocation.getArgument(0); + mConnectionPidCallback = (ICallbackInt) invocation.getArgument(1); + mConnectionIBinderCallback = (IBinder) invocation.getArgument(2); + return null; + } + }) + .when(mIChildProcessService) + .setupConnection( + or(isNull(), any(Bundle.class)), or(isNull(), any()), or(isNull(), any())); + + mChildProcessServiceBinder = new Binder(); + mChildProcessServiceBinder.attachInterface( + mIChildProcessService, IChildProcessService.class.getName()); + } + + private ChildProcessConnection createDefaultTestConnection() { + return createTestConnection(false /* bindToCaller */, false /* bindAsExternalService */, + null /* serviceBundle */); + } + + private ChildProcessConnection createTestConnection( + boolean bindToCaller, boolean bindAsExternalService, Bundle serviceBundle) { + String packageName = "org.chromium.test"; + String serviceName = "TestService"; + return new ChildProcessConnection(null /* context */, + new ComponentName(packageName, serviceName), bindToCaller, bindAsExternalService, + serviceBundle, mServiceConnectionFactory); + } + + @Test + public void testStrongBinding() { + ChildProcessConnection connection = createDefaultTestConnection(); + connection.start(true /* useStrongBinding */, null /* serviceCallback */); + assertTrue(connection.isStrongBindingBound()); + + connection = createDefaultTestConnection(); + connection.start(false /* useStrongBinding */, null /* serviceCallback */); + assertFalse(connection.isStrongBindingBound()); + } + + @Test + public void testServiceBundle() { + Bundle serviceBundle = new Bundle(); + final String intKey = "org.chromium.myInt"; + final int intValue = 34; + final int defaultValue = -1; + serviceBundle.putInt(intKey, intValue); + String stringKey = "org.chromium.myString"; + String stringValue = "thirty four"; + serviceBundle.putString(stringKey, stringValue); + + ChildProcessConnection connection = createTestConnection( + false /* bindToCaller */, false /* bindAsExternalService */, serviceBundle); + // Start the connection without the ChildServiceConnection connecting. + connection.start(false /* useStrongBinding */, null /* serviceCallback */); + assertNotNull(mFirstServiceConnection); + Intent bindIntent = mFirstServiceConnection.getBindIntent(); + assertNotNull(bindIntent); + assertEquals(intValue, bindIntent.getIntExtra(intKey, defaultValue)); + assertEquals(stringValue, bindIntent.getStringExtra(stringKey)); + } + + @Test + public void testServiceStartsSuccessfully() { + ChildProcessConnection connection = createDefaultTestConnection(); + assertNotNull(mFirstServiceConnection); + connection.start(false /* useStrongBinding */, mServiceCallback); + Assert.assertTrue(connection.isModerateBindingBound()); + Assert.assertFalse(connection.didOnServiceConnectedForTesting()); + verify(mServiceCallback, never()).onChildStarted(); + verify(mServiceCallback, never()).onChildStartFailed(any()); + verify(mServiceCallback, never()).onChildProcessDied(any()); + + // The service connects. + mFirstServiceConnection.notifyServiceConnected(null /* iBinder */); + Assert.assertTrue(connection.didOnServiceConnectedForTesting()); + verify(mServiceCallback, times(1)).onChildStarted(); + verify(mServiceCallback, never()).onChildStartFailed(any()); + verify(mServiceCallback, never()).onChildProcessDied(any()); + } + + @Test + public void testServiceStartsAndFailsToBind() { + ChildProcessConnection connection = createDefaultTestConnection(); + assertNotNull(mFirstServiceConnection); + // Note we use doReturn so the actual bind() method is not called (it would with + // when(mFirstServiceConnection.bind()).thenReturn(false). + doReturn(false).when(mFirstServiceConnection).bind(); + connection.start(false /* useStrongBinding */, mServiceCallback); + + Assert.assertFalse(connection.isModerateBindingBound()); + Assert.assertFalse(connection.didOnServiceConnectedForTesting()); + verify(mServiceCallback, never()).onChildStarted(); + verify(mServiceCallback, never()).onChildStartFailed(any()); + verify(mServiceCallback, times(1)).onChildProcessDied(connection); + } + + @Test + public void testServiceStops() { + ChildProcessConnection connection = createDefaultTestConnection(); + assertNotNull(mFirstServiceConnection); + connection.start(false /* useStrongBinding */, mServiceCallback); + mFirstServiceConnection.notifyServiceConnected(null /* iBinder */); + connection.stop(); + verify(mServiceCallback, times(1)).onChildStarted(); + verify(mServiceCallback, never()).onChildStartFailed(any()); + verify(mServiceCallback, times(1)).onChildProcessDied(connection); + } + + @Test + public void testServiceDisconnects() { + ChildProcessConnection connection = createDefaultTestConnection(); + assertNotNull(mFirstServiceConnection); + connection.start(false /* useStrongBinding */, mServiceCallback); + mFirstServiceConnection.notifyServiceConnected(null /* iBinder */); + mFirstServiceConnection.notifyServiceDisconnected(); + verify(mServiceCallback, times(1)).onChildStarted(); + verify(mServiceCallback, never()).onChildStartFailed(any()); + verify(mServiceCallback, times(1)).onChildProcessDied(connection); + } + + @Test + public void testNotBoundToCaller() throws RemoteException { + ChildProcessConnection connection = createTestConnection(false /* bindToCaller */, + false /* bindAsExternalService */, null /* serviceBundle */); + assertNotNull(mFirstServiceConnection); + connection.start(false /* useStrongBinding */, mServiceCallback); + mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder); + // Service is started and bindToCallback is not called. + verify(mServiceCallback, times(1)).onChildStarted(); + verify(mServiceCallback, never()).onChildStartFailed(any()); + verify(mServiceCallback, never()).onChildProcessDied(connection); + verify(mIChildProcessService, never()).bindToCaller(); + } + + @Test + public void testBoundToCallerSuccess() throws RemoteException { + ChildProcessConnection connection = createTestConnection(true /* bindToCaller */, + false /* bindAsExternalService */, null /* serviceBundle */); + assertNotNull(mFirstServiceConnection); + connection.start(false /* useStrongBinding */, mServiceCallback); + when(mIChildProcessService.bindToCaller()).thenReturn(true); + mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder); + // Service is started and bindToCallback is called. + verify(mServiceCallback, times(1)).onChildStarted(); + verify(mServiceCallback, never()).onChildStartFailed(any()); + verify(mServiceCallback, never()).onChildProcessDied(connection); + verify(mIChildProcessService, times(1)).bindToCaller(); + } + + @Test + public void testBoundToCallerFailure() throws RemoteException { + ChildProcessConnection connection = createTestConnection(true /* bindToCaller */, + false /* bindAsExternalService */, null /* serviceBundle */); + assertNotNull(mFirstServiceConnection); + connection.start(false /* useStrongBinding */, mServiceCallback); + // Pretend bindToCaller returns false, i.e. the service is already bound to a different + // service. + when(mIChildProcessService.bindToCaller()).thenReturn(false); + mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder); + // Service fails to start. + verify(mServiceCallback, never()).onChildStarted(); + verify(mServiceCallback, times(1)).onChildStartFailed(any()); + verify(mServiceCallback, never()).onChildProcessDied(connection); + verify(mIChildProcessService, times(1)).bindToCaller(); + } + + @Test + public void testSetupConnectionBeforeServiceConnected() throws RemoteException { + ChildProcessConnection connection = createDefaultTestConnection(); + assertNotNull(mFirstServiceConnection); + connection.start(false /* useStrongBinding */, null /* serviceCallback */); + connection.setupConnection( + null /* connectionBundle */, null /* callback */, mConnectionCallback); + verify(mConnectionCallback, never()).onConnected(any()); + mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder); + ShadowLooper.runUiThreadTasks(); + assertNotNull(mConnectionPidCallback); + mConnectionPidCallback.call(34 /* pid */); + verify(mConnectionCallback, times(1)).onConnected(connection); + } + + @Test + public void testSetupConnectionAfterServiceConnected() throws RemoteException { + ChildProcessConnection connection = createDefaultTestConnection(); + assertNotNull(mFirstServiceConnection); + connection.start(false /* useStrongBinding */, null /* serviceCallback */); + mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder); + connection.setupConnection( + null /* connectionBundle */, null /* callback */, mConnectionCallback); + verify(mConnectionCallback, never()).onConnected(any()); + ShadowLooper.runUiThreadTasks(); + assertNotNull(mConnectionPidCallback); + mConnectionPidCallback.call(34 /* pid */); + verify(mConnectionCallback, times(1)).onConnected(connection); + } + + @Test + public void testKill() throws RemoteException { + ChildProcessConnection connection = createDefaultTestConnection(); + assertNotNull(mFirstServiceConnection); + connection.start(false /* useStrongBinding */, null /* serviceCallback */); + mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder); + connection.setupConnection( + null /* connectionBundle */, null /* callback */, mConnectionCallback); + verify(mConnectionCallback, never()).onConnected(any()); + ShadowLooper.runUiThreadTasks(); + assertNotNull(mConnectionPidCallback); + mConnectionPidCallback.call(34 /* pid */); + verify(mConnectionCallback, times(1)).onConnected(connection); + + // Add strong binding so that connection is oom protected. + connection.removeModerateBinding(); + assertEquals(ChildBindingState.WAIVED, connection.bindingStateCurrentOrWhenDied()); + connection.addModerateBinding(); + assertEquals(ChildBindingState.MODERATE, connection.bindingStateCurrentOrWhenDied()); + connection.addStrongBinding(); + assertEquals(ChildBindingState.STRONG, connection.bindingStateCurrentOrWhenDied()); + + // Kill and verify state. + connection.kill(); + verify(mIChildProcessService).forceKill(); + assertEquals(ChildBindingState.STRONG, connection.bindingStateCurrentOrWhenDied()); + Assert.assertTrue(connection.isKilledByUs()); + } + + @Test + public void testBindingStateCounts() throws RemoteException { + ChildProcessConnection.resetBindingStateCountsForTesting(); + ChildProcessConnection connection0 = createDefaultTestConnection(); + ChildServiceConnectionMock connectionMock0 = mFirstServiceConnection; + mFirstServiceConnection = null; + ChildProcessConnection connection1 = createDefaultTestConnection(); + ChildServiceConnectionMock connectionMock1 = mFirstServiceConnection; + mFirstServiceConnection = null; + ChildProcessConnection connection2 = createDefaultTestConnection(); + ChildServiceConnectionMock connectionMock2 = mFirstServiceConnection; + mFirstServiceConnection = null; + + assertArrayEquals( + connection0.remainingBindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 0, 0}); + + connection0.start(false /* useStrongBinding */, null /* serviceCallback */); + assertArrayEquals( + connection0.remainingBindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 0, 0}); + + connection1.start(true /* useStrongBinding */, null /* serviceCallback */); + assertArrayEquals( + connection0.remainingBindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 0, 1}); + + connection2.start(false /* useStrongBinding */, null /* serviceCallback */); + assertArrayEquals( + connection0.remainingBindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 1, 1}); + + Binder binder0 = new Binder(); + Binder binder1 = new Binder(); + Binder binder2 = new Binder(); + binder0.attachInterface(mIChildProcessService, IChildProcessService.class.getName()); + binder1.attachInterface(mIChildProcessService, IChildProcessService.class.getName()); + binder2.attachInterface(mIChildProcessService, IChildProcessService.class.getName()); + connectionMock0.notifyServiceConnected(binder0); + connectionMock1.notifyServiceConnected(binder1); + connectionMock2.notifyServiceConnected(binder2); + ShadowLooper.runUiThreadTasks(); + + // Add and remove moderate binding works as expected. + connection2.removeModerateBinding(); + assertArrayEquals( + connection0.remainingBindingStateCountsCurrentOrWhenDied(), new int[] {0, 1, 0, 1}); + connection2.addModerateBinding(); + assertArrayEquals( + connection0.remainingBindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 1, 1}); + + // Add and remove strong binding works as expected. + connection0.addStrongBinding(); + assertArrayEquals( + connection0.remainingBindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 1, 1}); + connection0.removeStrongBinding(); + assertArrayEquals( + connection0.remainingBindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 1, 1}); + + // Stopped connection should no longe update. + connection0.stop(); + assertArrayEquals( + connection0.remainingBindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 1, 1}); + assertArrayEquals( + connection1.remainingBindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 1, 0}); + + connection2.removeModerateBinding(); + assertArrayEquals( + connection0.remainingBindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 1, 1}); + assertArrayEquals( + connection1.remainingBindingStateCountsCurrentOrWhenDied(), new int[] {0, 1, 0, 0}); + } +}
diff --git a/src/base/android/library_loader/README.md b/src/base/android/library_loader/README.md new file mode 100644 index 0000000..7773b32 --- /dev/null +++ b/src/base/android/library_loader/README.md
@@ -0,0 +1,10 @@ +# //base/android/library_loader + +Native code is split between this directory and: + * [//third_party/android_crazy_linker](../../../third_party/android_crazy_linker/README.chromium) + +Java code lives at: + * [//base/android/java/src/org/chromium/base/library_loader/](../java/src/org/chromium/base/library_loader/) + +A high-level guide to native code on Android exists at: + * [//docs/android_native_libraries.md](../../../docs/android_native_libraries.md)
diff --git a/src/base/android/library_loader/anchor_functions.cc b/src/base/android/library_loader/anchor_functions.cc new file mode 100644 index 0000000..0865d9d --- /dev/null +++ b/src/base/android/library_loader/anchor_functions.cc
@@ -0,0 +1,79 @@ +// Copyright 2017 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. + +#include "base/android/library_loader/anchor_functions.h" + +#include "base/logging.h" +#include "build/build_config.h" + +#if BUILDFLAG(SUPPORTS_CODE_ORDERING) + +// These functions are here to delimit the start and end of the ordered part of +// .text. They require a suitably constructed orderfile, with these functions at +// the beginning and end. +// +// These functions are weird: this is due to ICF (Identical Code Folding). +// The linker merges functions that have the same code, which would be the case +// if these functions were empty, or simple. +// Gold's flag --icf=safe will *not* alias functions when their address is used +// in code, but as of November 2017, we use the default setting that +// deduplicates function in this case as well. +// +// Thus these functions are made to be unique, using inline .word in assembly. +// +// Note that code |CheckOrderingSanity()| below will make sure that these +// functions are not aliased, in case the toolchain becomes really clever. +extern "C" { + +// These functions have a well-defined ordering in this file, see the comment +// in |IsOrderingSane()|. +void dummy_function_end_of_ordered_text() { + asm(".word 0x21bad44d"); + asm(".word 0xb815c5b0"); +} + +void dummy_function_start_of_ordered_text() { + asm(".word 0xe4a07375"); + asm(".word 0x66dda6dc"); +} + +// These two symbols are defined by anchor_functions.lds and delimit the start +// and end of .text. +void linker_script_start_of_text(); +void linker_script_end_of_text(); + +} // extern "C" + +namespace base { +namespace android { + +const size_t kStartOfText = + reinterpret_cast<size_t>(linker_script_start_of_text); +const size_t kEndOfText = reinterpret_cast<size_t>(linker_script_end_of_text); +const size_t kStartOfOrderedText = + reinterpret_cast<size_t>(dummy_function_start_of_ordered_text); +const size_t kEndOfOrderedText = + reinterpret_cast<size_t>(dummy_function_end_of_ordered_text); + +bool IsOrderingSane() { + size_t here = reinterpret_cast<size_t>(&IsOrderingSane); + // The symbols linker_script_start_of_text and linker_script_end_of_text + // should cover all of .text, and dummy_function_start_of_ordered_text and + // dummy_function_end_of_ordered_text should cover the ordered part of it. + // This check is intended to catch the lack of ordering. + // + // Ordered text can start at the start of text, but should not cover the + // entire range. Most addresses are distinct nonetheless as the symbols are + // different, but linker-defined symbols have zero size and therefore the + // start address could be the same as the address of + // dummy_function_start_of_ordered_text. + return kStartOfText < here && here < kEndOfText && + kStartOfOrderedText < kEndOfOrderedText && + kStartOfText <= kStartOfOrderedText && kEndOfOrderedText < kEndOfText; +} + +} // namespace android +} // namespace base + +#endif // BUILDFLAG(SUPPORTS_CODE_ORDERING)
diff --git a/src/base/android/library_loader/anchor_functions.h b/src/base/android/library_loader/anchor_functions.h new file mode 100644 index 0000000..9894583 --- /dev/null +++ b/src/base/android/library_loader/anchor_functions.h
@@ -0,0 +1,32 @@ +// Copyright 2017 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_ANDROID_LIBRARY_LOADER_ANCHOR_FUNCTIONS_H_ +#define BASE_ANDROID_LIBRARY_LOADER_ANCHOR_FUNCTIONS_H_ + +#include <cstdint> +#include "base/android/library_loader/anchor_functions_buildflags.h" + +#include "base/base_export.h" + +#if BUILDFLAG(SUPPORTS_CODE_ORDERING) + +namespace base { +namespace android { + +// Start and end of .text, respectively. +BASE_EXPORT extern const size_t kStartOfText; +BASE_EXPORT extern const size_t kEndOfText; +// Start and end of the ordered part of .text, respectively. +BASE_EXPORT extern const size_t kStartOfOrderedText; +BASE_EXPORT extern const size_t kEndOfOrderedText; + +// Returns true if the ordering looks sane. +BASE_EXPORT bool IsOrderingSane(); + +} // namespace android +} // namespace base +#endif // BUILDFLAG(SUPPORTS_CODE_ORDERING) + +#endif // BASE_ANDROID_LIBRARY_LOADER_ANCHOR_FUNCTIONS_H_
diff --git a/src/base/android/library_loader/anchor_functions.lds b/src/base/android/library_loader/anchor_functions.lds new file mode 100644 index 0000000..cdeaaa2 --- /dev/null +++ b/src/base/android/library_loader/anchor_functions.lds
@@ -0,0 +1,7 @@ +# Copyright 2018 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. + +# Define symbols that point to the start and end of the .text section. +PROVIDE_HIDDEN(linker_script_start_of_text = ADDR(.text)); +PROVIDE_HIDDEN(linker_script_end_of_text = ADDR(.text) + SIZEOF(.text));
diff --git a/src/base/android/library_loader/library_load_from_apk_status_codes.h b/src/base/android/library_loader/library_load_from_apk_status_codes.h new file mode 100644 index 0000000..8910d48 --- /dev/null +++ b/src/base/android/library_loader/library_load_from_apk_status_codes.h
@@ -0,0 +1,49 @@ +// Copyright 2014 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_ANDROID_LIBRARY_LOADER_LIBRARY_LOAD_FROM_APK_STATUS_CODES_H_ +#define BASE_ANDROID_LIBRARY_LOADER_LIBRARY_LOAD_FROM_APK_STATUS_CODES_H_ + +namespace base { +namespace android { + +namespace { + +// This enum must be kept in sync with the LibraryLoadFromApkStatus enum in +// tools/metrics/histograms/histograms.xml. +// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.base.library_loader +enum LibraryLoadFromApkStatusCodes { + // The loader was unable to determine whether the functionality is supported. + LIBRARY_LOAD_FROM_APK_STATUS_CODES_UNKNOWN = 0, + + // The device does not support loading a library directly from the APK file + // (obsolete). + LIBRARY_LOAD_FROM_APK_STATUS_CODES_NOT_SUPPORTED_OBSOLETE = 1, + + // The device supports loading a library directly from the APK file. + // (obsolete). + LIBRARY_LOAD_FROM_APK_STATUS_CODES_SUPPORTED_OBSOLETE = 2, + + // The Chromium library was successfully loaded directly from the APK file. + LIBRARY_LOAD_FROM_APK_STATUS_CODES_SUCCESSFUL = 3, + + // The Chromium library was successfully loaded using the unpack library + // fallback because it was compressed or not page aligned in the APK file. + LIBRARY_LOAD_FROM_APK_STATUS_CODES_USED_UNPACK_LIBRARY_FALLBACK = 4, + + // The Chromium library was successfully loaded using the no map executable + // support fallback (obsolete). + LIBRARY_LOAD_FROM_APK_STATUS_CODES_USED_NO_MAP_EXEC_SUPPORT_FALLBACK_OBSOLETE + = 5, + + // End sentinel. + LIBRARY_LOAD_FROM_APK_STATUS_CODES_MAX = 6, +}; + +} // namespace + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_LIBRARY_LOADER_LIBRARY_LOAD_FROM_APK_STATUS_CODES_H_
diff --git a/src/base/android/library_loader/library_loader_hooks.cc b/src/base/android/library_loader/library_loader_hooks.cc new file mode 100644 index 0000000..40bff5e --- /dev/null +++ b/src/base/android/library_loader/library_loader_hooks.cc
@@ -0,0 +1,254 @@ +// Copyright 2014 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. + +#include "base/android/library_loader/library_loader_hooks.h" + +#include "base/android/jni_string.h" +#include "base/android/library_loader/anchor_functions_buildflags.h" +#include "base/android/library_loader/library_load_from_apk_status_codes.h" +#include "base/android/library_loader/library_prefetcher.h" +#include "base/android/orderfile/orderfile_buildflags.h" +#include "base/at_exit.h" +#include "base/base_switches.h" +#include "base/command_line.h" +#include "base/metrics/histogram.h" +#include "base/metrics/histogram_functions.h" +#include "base/metrics/histogram_macros.h" +#include "jni/LibraryLoader_jni.h" + +#if BUILDFLAG(ORDERFILE_INSTRUMENTATION) +#include "base/android/orderfile/orderfile_instrumentation.h" +#endif + +namespace base { +namespace android { + +namespace { + +base::AtExitManager* g_at_exit_manager = NULL; +const char* g_library_version_number = ""; +LibraryLoadedHook* g_registration_callback = NULL; +NativeInitializationHook* g_native_initialization_hook = NULL; + +enum RendererHistogramCode { + // Renderer load at fixed address success, fail, or not attempted. + // Renderers do not attempt to load at at fixed address if on a + // low-memory device on which browser load at fixed address has already + // failed. + LFA_SUCCESS = 0, + LFA_BACKOFF_USED = 1, + LFA_NOT_ATTEMPTED = 2, + + // End sentinel, also used as nothing-pending indicator. + MAX_RENDERER_HISTOGRAM_CODE = 3, + NO_PENDING_HISTOGRAM_CODE = MAX_RENDERER_HISTOGRAM_CODE +}; + +enum BrowserHistogramCode { + // Non-low-memory random address browser loads. + NORMAL_LRA_SUCCESS = 0, + + // Low-memory browser loads at fixed address, success or fail. + LOW_MEMORY_LFA_SUCCESS = 1, + LOW_MEMORY_LFA_BACKOFF_USED = 2, + + MAX_BROWSER_HISTOGRAM_CODE = 3, +}; + +RendererHistogramCode g_renderer_histogram_code = NO_PENDING_HISTOGRAM_CODE; + +// Indicate whether g_library_preloader_renderer_histogram_code is valid +bool g_library_preloader_renderer_histogram_code_registered = false; + +// The return value of NativeLibraryPreloader.loadLibrary() in child processes, +// it is initialized to the invalid value which shouldn't showup in UMA report. +int g_library_preloader_renderer_histogram_code = -1; + +// The amount of time, in milliseconds, that it took to load the shared +// libraries in the renderer. Set in +// RegisterChromiumAndroidLinkerRendererHistogram. +long g_renderer_library_load_time_ms = 0; + +void RecordChromiumAndroidLinkerRendererHistogram() { + if (g_renderer_histogram_code == NO_PENDING_HISTOGRAM_CODE) + return; + // Record and release the pending histogram value. + UMA_HISTOGRAM_ENUMERATION("ChromiumAndroidLinker.RendererStates", + g_renderer_histogram_code, + MAX_RENDERER_HISTOGRAM_CODE); + g_renderer_histogram_code = NO_PENDING_HISTOGRAM_CODE; + + // Record how long it took to load the shared libraries. + UMA_HISTOGRAM_TIMES("ChromiumAndroidLinker.RendererLoadTime", + base::TimeDelta::FromMilliseconds(g_renderer_library_load_time_ms)); +} + +void RecordLibraryPreloaderRendereHistogram() { + if (g_library_preloader_renderer_histogram_code_registered) { + UmaHistogramSparse("Android.NativeLibraryPreloader.Result.Renderer", + g_library_preloader_renderer_histogram_code); + } +} + +#if BUILDFLAG(SUPPORTS_CODE_ORDERING) +bool ShouldDoOrderfileMemoryOptimization() { + return CommandLine::ForCurrentProcess()->HasSwitch( + switches::kOrderfileMemoryOptimization); +} +#endif + +} // namespace + +static void JNI_LibraryLoader_RegisterChromiumAndroidLinkerRendererHistogram( + JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + jboolean requested_shared_relro, + jboolean load_at_fixed_address_failed, + jlong library_load_time_ms) { + // Note a pending histogram value for later recording. + if (requested_shared_relro) { + g_renderer_histogram_code = load_at_fixed_address_failed + ? LFA_BACKOFF_USED : LFA_SUCCESS; + } else { + g_renderer_histogram_code = LFA_NOT_ATTEMPTED; + } + + g_renderer_library_load_time_ms = library_load_time_ms; +} + +static void JNI_LibraryLoader_RecordChromiumAndroidLinkerBrowserHistogram( + JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + jboolean is_using_browser_shared_relros, + jboolean load_at_fixed_address_failed, + jint library_load_from_apk_status, + jlong library_load_time_ms) { + // For low-memory devices, record whether or not we successfully loaded the + // browser at a fixed address. Otherwise just record a normal invocation. + BrowserHistogramCode histogram_code; + if (is_using_browser_shared_relros) { + histogram_code = load_at_fixed_address_failed + ? LOW_MEMORY_LFA_BACKOFF_USED : LOW_MEMORY_LFA_SUCCESS; + } else { + histogram_code = NORMAL_LRA_SUCCESS; + } + UMA_HISTOGRAM_ENUMERATION("ChromiumAndroidLinker.BrowserStates", + histogram_code, + MAX_BROWSER_HISTOGRAM_CODE); + + // Record the device support for loading a library directly from the APK file. + UMA_HISTOGRAM_ENUMERATION( + "ChromiumAndroidLinker.LibraryLoadFromApkStatus", + static_cast<LibraryLoadFromApkStatusCodes>(library_load_from_apk_status), + LIBRARY_LOAD_FROM_APK_STATUS_CODES_MAX); + + // Record how long it took to load the shared libraries. + UMA_HISTOGRAM_TIMES("ChromiumAndroidLinker.BrowserLoadTime", + base::TimeDelta::FromMilliseconds(library_load_time_ms)); +} + +static void JNI_LibraryLoader_RecordLibraryPreloaderBrowserHistogram( + JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + jint status) { + UmaHistogramSparse("Android.NativeLibraryPreloader.Result.Browser", status); +} + +static void JNI_LibraryLoader_RegisterLibraryPreloaderRendererHistogram( + JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + jint status) { + g_library_preloader_renderer_histogram_code = status; + g_library_preloader_renderer_histogram_code_registered = true; +} + +void SetNativeInitializationHook( + NativeInitializationHook native_initialization_hook) { + g_native_initialization_hook = native_initialization_hook; +} + +void RecordLibraryLoaderRendererHistograms() { + RecordChromiumAndroidLinkerRendererHistogram(); + RecordLibraryPreloaderRendereHistogram(); +} + +void SetLibraryLoadedHook(LibraryLoadedHook* func) { + g_registration_callback = func; +} + +static jboolean JNI_LibraryLoader_LibraryLoaded( + JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + jint library_process_type) { +#if BUILDFLAG(ORDERFILE_INSTRUMENTATION) + orderfile::StartDelayedDump(); +#endif + +#if BUILDFLAG(SUPPORTS_CODE_ORDERING) + if (ShouldDoOrderfileMemoryOptimization()) { + NativeLibraryPrefetcher::MadviseForOrderfile(); + } +#endif + + if (g_native_initialization_hook && + !g_native_initialization_hook( + static_cast<LibraryProcessType>(library_process_type))) + return false; + if (g_registration_callback && !g_registration_callback(env, nullptr)) + return false; + return true; +} + +void LibraryLoaderExitHook() { + if (g_at_exit_manager) { + delete g_at_exit_manager; + g_at_exit_manager = NULL; + } +} + +static void JNI_LibraryLoader_ForkAndPrefetchNativeLibrary( + JNIEnv* env, + const JavaParamRef<jclass>& clazz) { +#if BUILDFLAG(SUPPORTS_CODE_ORDERING) + return NativeLibraryPrefetcher::ForkAndPrefetchNativeLibrary( + ShouldDoOrderfileMemoryOptimization()); +#endif +} + +static jint JNI_LibraryLoader_PercentageOfResidentNativeLibraryCode( + JNIEnv* env, + const JavaParamRef<jclass>& clazz) { +#if BUILDFLAG(SUPPORTS_CODE_ORDERING) + return NativeLibraryPrefetcher::PercentageOfResidentNativeLibraryCode(); +#else + return -1; +#endif +} + +static void JNI_LibraryLoader_PeriodicallyCollectResidency( + JNIEnv* env, + const JavaParamRef<jclass>& clazz) { +#if BUILDFLAG(SUPPORTS_CODE_ORDERING) + NativeLibraryPrefetcher::PeriodicallyCollectResidency(); +#else + LOG(WARNING) << "Collecting residency is not supported."; +#endif +} + +void SetVersionNumber(const char* version_number) { + g_library_version_number = strdup(version_number); +} + +ScopedJavaLocalRef<jstring> JNI_LibraryLoader_GetVersionNumber( + JNIEnv* env, + const JavaParamRef<jobject>& jcaller) { + return ConvertUTF8ToJavaString(env, g_library_version_number); +} + +void InitAtExitManager() { + g_at_exit_manager = new base::AtExitManager(); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/library_loader/library_loader_hooks.h b/src/base/android/library_loader/library_loader_hooks.h new file mode 100644 index 0000000..a6e75ab --- /dev/null +++ b/src/base/android/library_loader/library_loader_hooks.h
@@ -0,0 +1,74 @@ +// Copyright 2014 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_ANDROID_LIBRARY_LOADER_LIBRARY_LOADER_HOOKS_H_ +#define BASE_ANDROID_LIBRARY_LOADER_LIBRARY_LOADER_HOOKS_H_ + +#include <jni.h> + +#include "base/base_export.h" +#include "base/callback.h" +#include "starboard/types.h" + +namespace base { +namespace android { + +// The process the shared library is loaded in. +// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.base.library_loader +enum LibraryProcessType { + // The LibraryLoad has not been initialized. + PROCESS_UNINITIALIZED = 0, + // Shared library is running in browser process. + PROCESS_BROWSER = 1, + // Shared library is running in child process. + PROCESS_CHILD = 2, + // Shared library is running in the app that uses webview. + PROCESS_WEBVIEW = 3, + // Shared library is running in child process as part of webview. + PROCESS_WEBVIEW_CHILD = 4, +}; + +typedef bool NativeInitializationHook(LibraryProcessType library_process_type); + +BASE_EXPORT void SetNativeInitializationHook( + NativeInitializationHook native_initialization_hook); + +// Record any pending renderer histogram value as histograms. Pending values +// are set by RegisterChromiumAndroidLinkerRendererHistogram and +// RegisterLibraryPreloaderRendererHistogram. +BASE_EXPORT void RecordLibraryLoaderRendererHistograms(); + +// Typedef for hook function to be called (indirectly from Java) once the +// libraries are loaded. The hook function should register the JNI bindings +// required to start the application. It should return true for success and +// false for failure. +// Note: this can't use base::Callback because there is no way of initializing +// the default callback without using static objects, which we forbid. +typedef bool LibraryLoadedHook(JNIEnv* env, + jclass clazz); + +// Set the hook function to be called (from Java) once the libraries are loaded. +// SetLibraryLoadedHook may only be called from JNI_OnLoad. The hook function +// should register the JNI bindings required to start the application. + +BASE_EXPORT void SetLibraryLoadedHook(LibraryLoadedHook* func); + +// Pass the version name to the loader. This used to check that the library +// version matches the version expected by Java before completing JNI +// registration. +// Note: argument must remain valid at least until library loading is complete. +BASE_EXPORT void SetVersionNumber(const char* version_number); + +// Call on exit to delete the AtExitManager which OnLibraryLoadedOnUIThread +// created. +BASE_EXPORT void LibraryLoaderExitHook(); + +// Initialize AtExitManager, this must be done at the begining of loading +// shared library. +void InitAtExitManager(); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_LIBRARY_LOADER_LIBRARY_LOADER_HOOKS_H_
diff --git a/src/base/android/library_loader/library_prefetcher.cc b/src/base/android/library_loader/library_prefetcher.cc new file mode 100644 index 0000000..0543cc7 --- /dev/null +++ b/src/base/android/library_loader/library_prefetcher.cc
@@ -0,0 +1,333 @@ +// Copyright 2015 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. + +#include "base/android/library_loader/library_prefetcher.h" + +#include <sys/mman.h> +#include <sys/resource.h> +#include <sys/wait.h> +#include <unistd.h> +#include <algorithm> +#include <atomic> +#include <cstdlib> +#include <memory> +#include <utility> +#include <vector> + +#include "base/android/library_loader/anchor_functions.h" +#include "base/android/orderfile/orderfile_buildflags.h" +#include "base/bits.h" +#include "base/files/file.h" +#include "base/format_macros.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/metrics/histogram_macros.h" +#include "base/posix/eintr_wrapper.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "build/build_config.h" + +#if BUILDFLAG(ORDERFILE_INSTRUMENTATION) +#include "base/android/orderfile/orderfile_instrumentation.h" +#include "starboard/types.h" +#endif + +#if BUILDFLAG(SUPPORTS_CODE_ORDERING) + +namespace base { +namespace android { + +namespace { + +// Android defines the background priority to this value since at least 2009 +// (see Process.java). +constexpr int kBackgroundPriority = 10; +// Valid for all Android architectures. +constexpr size_t kPageSize = 4096; + +// Reads a byte per page between |start| and |end| to force it into the page +// cache. +// Heap allocations, syscalls and library functions are not allowed in this +// function. +// Returns true for success. +#if defined(ADDRESS_SANITIZER) +// Disable AddressSanitizer instrumentation for this function. It is touching +// memory that hasn't been allocated by the app, though the addresses are +// valid. Furthermore, this takes place in a child process. See crbug.com/653372 +// for the context. +__attribute__((no_sanitize_address)) +#endif +void Prefetch(size_t start, size_t end) { + unsigned char* start_ptr = reinterpret_cast<unsigned char*>(start); + unsigned char* end_ptr = reinterpret_cast<unsigned char*>(end); + unsigned char dummy = 0; + for (unsigned char* ptr = start_ptr; ptr < end_ptr; ptr += kPageSize) { + // Volatile is required to prevent the compiler from eliminating this + // loop. + dummy ^= *static_cast<volatile unsigned char*>(ptr); + } +} + +// Populates the per-page residency between |start| and |end| in |residency|. If +// successful, |residency| has the size of |end| - |start| in pages. +// Returns true for success. +bool Mincore(size_t start, size_t end, std::vector<unsigned char>* residency) { + if (start % kPageSize || end % kPageSize) + return false; + size_t size = end - start; + size_t size_in_pages = size / kPageSize; + if (residency->size() != size_in_pages) + residency->resize(size_in_pages); + int err = HANDLE_EINTR( + mincore(reinterpret_cast<void*>(start), size, &(*residency)[0])); + PLOG_IF(ERROR, err) << "mincore() failed"; + return !err; +} + +// Returns the start and end of .text, aligned to the lower and upper page +// boundaries, respectively. +std::pair<size_t, size_t> GetTextRange() { + // |kStartOfText| may not be at the beginning of a page, since .plt can be + // before it, yet in the same mapping for instance. + size_t start_page = kStartOfText - kStartOfText % kPageSize; + // Set the end to the page on which the beginning of the last symbol is. The + // actual symbol may spill into the next page by a few bytes, but this is + // outside of the executable code range anyway. + size_t end_page = base::bits::Align(kEndOfText, kPageSize); + return {start_page, end_page}; +} + +// Returns the start and end pages of the unordered section of .text, aligned to +// lower and upper page boundaries, respectively. +std::pair<size_t, size_t> GetOrderedTextRange() { + size_t start_page = kStartOfOrderedText - kStartOfOrderedText % kPageSize; + // kEndOfUnorderedText is not considered ordered, but the byte immediately + // before is considered ordered and so can not be contained in the start page. + size_t end_page = base::bits::Align(kEndOfOrderedText, kPageSize); + return {start_page, end_page}; +} + +// Calls madvise(advice) on the specified range. Does nothing if the range is +// empty. +void MadviseOnRange(const std::pair<size_t, size_t>& range, int advice) { + if (range.first >= range.second) { + return; + } + size_t size = range.second - range.first; + int err = madvise(reinterpret_cast<void*>(range.first), size, advice); + if (err) { + PLOG(ERROR) << "madvise() failed"; + } +} + +// Timestamp in ns since Unix Epoch, and residency, as returned by mincore(). +struct TimestampAndResidency { + uint64_t timestamp_nanos; + std::vector<unsigned char> residency; + + TimestampAndResidency(uint64_t timestamp_nanos, + std::vector<unsigned char>&& residency) + : timestamp_nanos(timestamp_nanos), residency(residency) {} +}; + +// Returns true for success. +bool CollectResidency(size_t start, + size_t end, + std::vector<TimestampAndResidency>* data) { + // Not using base::TimeTicks() to not call too many base:: symbol that would + // pollute the reached symbols dumps. + struct timespec ts; + if (HANDLE_EINTR(clock_gettime(CLOCK_MONOTONIC, &ts))) { + PLOG(ERROR) << "Cannot get the time."; + return false; + } + uint64_t now = + static_cast<uint64_t>(ts.tv_sec) * 1000 * 1000 * 1000 + ts.tv_nsec; + std::vector<unsigned char> residency; + if (!Mincore(start, end, &residency)) + return false; + + data->emplace_back(now, std::move(residency)); + return true; +} + +void DumpResidency(size_t start, + size_t end, + std::unique_ptr<std::vector<TimestampAndResidency>> data) { + auto path = base::FilePath( + base::StringPrintf("/data/local/tmp/chrome/residency-%d.txt", getpid())); + auto file = + base::File(path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); + if (!file.IsValid()) { + PLOG(ERROR) << "Cannot open file to dump the residency data " + << path.value(); + return; + } + + // First line: start-end of text range. + CHECK(IsOrderingSane()); + CHECK_LT(start, kStartOfText); + CHECK_LT(kEndOfText, end); + auto start_end = base::StringPrintf("%" PRIuS " %" PRIuS "\n", + kStartOfText - start, kEndOfText - start); + file.WriteAtCurrentPos(start_end.c_str(), start_end.size()); + + for (const auto& data_point : *data) { + auto timestamp = + base::StringPrintf("%" PRIu64 " ", data_point.timestamp_nanos); + file.WriteAtCurrentPos(timestamp.c_str(), timestamp.size()); + + std::vector<char> dump; + dump.reserve(data_point.residency.size() + 1); + for (auto c : data_point.residency) + dump.push_back(c ? '1' : '0'); + dump[dump.size() - 1] = '\n'; + file.WriteAtCurrentPos(&dump[0], dump.size()); + } +} + +// These values are persisted to logs. Entries should not be renumbered and +// numeric values should never be reused. +// Used for "LibraryLoader.PrefetchDetailedStatus". +enum class PrefetchStatus { + kSuccess = 0, + kWrongOrdering = 1, + kForkFailed = 2, + kChildProcessCrashed = 3, + kChildProcessKilled = 4, + kMaxValue = kChildProcessKilled +}; + +PrefetchStatus ForkAndPrefetch(bool ordered_only) { + if (!IsOrderingSane()) { + LOG(WARNING) << "Incorrect code ordering"; + return PrefetchStatus::kWrongOrdering; + } + + // Looking for ranges is done before the fork, to avoid syscalls and/or memory + // allocations in the forked process. The child process inherits the lock + // state of its parent thread. It cannot rely on being able to acquire any + // lock (unless special care is taken in a pre-fork handler), including being + // able to call malloc(). + // + // Always prefetch the ordered section first, as it's reached early during + // startup, and not necessarily located at the beginning of .text. + std::vector<std::pair<size_t, size_t>> ranges = {GetOrderedTextRange()}; + if (!ordered_only) + ranges.push_back(GetTextRange()); + + pid_t pid = fork(); + if (pid == 0) { + setpriority(PRIO_PROCESS, 0, kBackgroundPriority); + // _exit() doesn't call the atexit() handlers. + for (const auto& range : ranges) { + Prefetch(range.first, range.second); + } + _exit(EXIT_SUCCESS); + } else { + if (pid < 0) { + return PrefetchStatus::kForkFailed; + } + int status; + const pid_t result = HANDLE_EINTR(waitpid(pid, &status, 0)); + if (result == pid) { + if (WIFEXITED(status)) + return PrefetchStatus::kSuccess; + if (WIFSIGNALED(status)) { + int signal = WTERMSIG(status); + switch (signal) { + case SIGSEGV: + case SIGBUS: + return PrefetchStatus::kChildProcessCrashed; + break; + case SIGKILL: + case SIGTERM: + default: + return PrefetchStatus::kChildProcessKilled; + } + } + } + // Should not happen. Per man waitpid(2), errors are: + // - EINTR: handled. + // - ECHILD if the process doesn't have an unwaited-for child with this PID. + // - EINVAL. + return PrefetchStatus::kChildProcessKilled; + } +} + +} // namespace + +// static +void NativeLibraryPrefetcher::ForkAndPrefetchNativeLibrary(bool ordered_only) { +#if BUILDFLAG(ORDERFILE_INSTRUMENTATION) + // Avoid forking with orderfile instrumentation because the child process + // would create a dump as well. + return; +#endif + + PrefetchStatus status = ForkAndPrefetch(ordered_only); + UMA_HISTOGRAM_BOOLEAN("LibraryLoader.PrefetchStatus", + status == PrefetchStatus::kSuccess); + UMA_HISTOGRAM_ENUMERATION("LibraryLoader.PrefetchDetailedStatus", status); + if (status != PrefetchStatus::kSuccess) { + LOG(WARNING) << "Cannot prefetch the library. status = " + << static_cast<int>(status); + } +} + +// static +int NativeLibraryPrefetcher::PercentageOfResidentCode(size_t start, + size_t end) { + size_t total_pages = 0; + size_t resident_pages = 0; + + std::vector<unsigned char> residency; + bool ok = Mincore(start, end, &residency); + if (!ok) + return -1; + total_pages += residency.size(); + resident_pages += std::count_if(residency.begin(), residency.end(), + [](unsigned char x) { return x & 1; }); + if (total_pages == 0) + return -1; + return static_cast<int>((100 * resident_pages) / total_pages); +} + +// static +int NativeLibraryPrefetcher::PercentageOfResidentNativeLibraryCode() { + if (!IsOrderingSane()) { + LOG(WARNING) << "Incorrect code ordering"; + return -1; + } + const auto& range = GetTextRange(); + return PercentageOfResidentCode(range.first, range.second); +} + +// static +void NativeLibraryPrefetcher::PeriodicallyCollectResidency() { + CHECK_EQ(static_cast<long>(kPageSize), sysconf(_SC_PAGESIZE)); + + const auto& range = GetTextRange(); + auto data = std::make_unique<std::vector<TimestampAndResidency>>(); + for (int i = 0; i < 60; ++i) { + if (!CollectResidency(range.first, range.second, data.get())) + return; + usleep(2e5); + } + DumpResidency(range.first, range.second, std::move(data)); +} + +// static +void NativeLibraryPrefetcher::MadviseForOrderfile() { + CHECK(IsOrderingSane()); + LOG(WARNING) << "Performing experimental madvise from orderfile information"; + // First MADV_RANDOM on all of text, then turn the ordered text range back to + // normal. The ordered range may be placed anywhere within .text. + MadviseOnRange(GetTextRange(), MADV_RANDOM); + MadviseOnRange(GetOrderedTextRange(), MADV_NORMAL); +} + +} // namespace android +} // namespace base +#endif // BUILDFLAG(SUPPORTS_CODE_ORDERING)
diff --git a/src/base/android/library_loader/library_prefetcher.h b/src/base/android/library_loader/library_prefetcher.h new file mode 100644 index 0000000..bee7407 --- /dev/null +++ b/src/base/android/library_loader/library_prefetcher.h
@@ -0,0 +1,65 @@ +// Copyright 2015 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_ANDROID_LIBRARY_LOADER_LIBRARY_PREFETCHER_H_ +#define BASE_ANDROID_LIBRARY_LOADER_LIBRARY_PREFETCHER_H_ + +#include <jni.h> + +#include "base/android/library_loader/anchor_functions_buildflags.h" +#include "base/base_export.h" +#include "base/gtest_prod_util.h" +#include "base/macros.h" +#include "starboard/types.h" + +#if BUILDFLAG(SUPPORTS_CODE_ORDERING) + +namespace base { +namespace android { + +// Forks and waits for a process prefetching the native library. This is done in +// a forked process for the following reasons: +// - Isolating the main process from mistakes in getting the address range, only +// crashing the forked process in case of mistake. +// - Not inflating the memory used by the main process uselessly, which could +// increase its likelihood to be killed. +// The forked process has background priority and, since it is not declared to +// the Android runtime, can be killed at any time, which is not an issue here. +class BASE_EXPORT NativeLibraryPrefetcher { + public: + // Finds the executable code range, forks a low priority process pre-fetching + // it wait()s for the process to exit or die. If ordered_only is true, only + // the ordered section is prefetched. See GetOrdrderedTextRange() in + // library_prefetcher.cc. + static void ForkAndPrefetchNativeLibrary(bool ordered_only); + + // Returns the percentage of the native library code currently resident in + // memory, or -1 in case of error. + static int PercentageOfResidentNativeLibraryCode(); + + // Collects residency for the native library executable multiple times, then + // dumps it to disk. + static void PeriodicallyCollectResidency(); + + // Calls madvise() on the native library executable, using orderfile + // information to decide how to advise each part of the library. + static void MadviseForOrderfile(); + + private: + // Returns the percentage of [start, end] currently resident in + // memory, or -1 in case of error. + static int PercentageOfResidentCode(size_t start, size_t end); + + FRIEND_TEST_ALL_PREFIXES(NativeLibraryPrefetcherTest, + TestPercentageOfResidentCode); + + DISALLOW_IMPLICIT_CONSTRUCTORS(NativeLibraryPrefetcher); +}; + +} // namespace android +} // namespace base + +#endif // BUILDFLAG(SUPPORTS_CODE_ORDERING) + +#endif // BASE_ANDROID_LIBRARY_LOADER_LIBRARY_PREFETCHER_H_
diff --git a/src/base/android/library_loader/library_prefetcher_unittest.cc b/src/base/android/library_loader/library_prefetcher_unittest.cc new file mode 100644 index 0000000..d5c206e --- /dev/null +++ b/src/base/android/library_loader/library_prefetcher_unittest.cc
@@ -0,0 +1,45 @@ +// Copyright 2015 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. + +#include "base/android/library_loader/library_prefetcher.h" + +#include <sys/mman.h> +#include "base/android/library_loader/anchor_functions_buildflags.h" +#include "base/memory/shared_memory.h" +#include "build/build_config.h" +#include "starboard/types.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if BUILDFLAG(SUPPORTS_CODE_ORDERING) +namespace base { +namespace android { + +// Fails with ASAN, crbug.com/570423. +#if !defined(ADDRESS_SANITIZER) +namespace { +const size_t kPageSize = 4096; +} // namespace + +TEST(NativeLibraryPrefetcherTest, TestPercentageOfResidentCode) { + size_t length = 4 * kPageSize; + base::SharedMemory shared_mem; + ASSERT_TRUE(shared_mem.CreateAndMapAnonymous(length)); + void* address = shared_mem.memory(); + size_t start = reinterpret_cast<size_t>(address); + size_t end = start + length; + + // Remove everything. + ASSERT_EQ(0, madvise(address, length, MADV_DONTNEED)); + EXPECT_EQ(0, NativeLibraryPrefetcher::PercentageOfResidentCode(start, end)); + + // Get everything back. + ASSERT_EQ(0, mlock(address, length)); + EXPECT_EQ(100, NativeLibraryPrefetcher::PercentageOfResidentCode(start, end)); + munlock(address, length); +} +#endif // !defined(ADDRESS_SANITIZER) + +} // namespace android +} // namespace base +#endif // BUILDFLAG(SUPPORTS_CODE_ORDERING)
diff --git a/src/base/android/linker/BUILD.gn b/src/base/android/linker/BUILD.gn new file mode 100644 index 0000000..ee6f569 --- /dev/null +++ b/src/base/android/linker/BUILD.gn
@@ -0,0 +1,25 @@ +# Copyright 2014 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. + +import("//build/config/android/config.gni") + +assert(is_android) + +shared_library("chromium_android_linker") { + sources = [ + "linker_jni.cc", + ] + + # The NDK contains the crazy_linker here: + # '<(android_ndk_root)/crazy_linker.gyp:crazy_linker' + # However, we use our own fork. See bug 384700. + deps = [ + "//build:buildflag_header_h", + "//third_party/android_crazy_linker", + ] + + # Export JNI symbols. + configs -= [ "//build/config/android:hide_all_but_jni_onload" ] + configs += [ "//build/config/android:hide_all_but_jni" ] +}
diff --git a/src/base/android/linker/config.gni b/src/base/android/linker/config.gni new file mode 100644 index 0000000..27793ff --- /dev/null +++ b/src/base/android/linker/config.gni
@@ -0,0 +1,13 @@ +# Copyright 2014 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. + +import("//build/config/android/config.gni") +import("//build/config/compiler/compiler.gni") +import("//build/config/sanitizers/sanitizers.gni") + +# Chromium linker doesn't reliably support loading multiple libraries; +# disable for component builds, see crbug.com/657093. +# Chromium linker causes instrumentation to return incorrect results. +chromium_linker_supported = + !is_component_build && !enable_profiling && !use_order_profiling && !is_asan
diff --git a/src/base/android/linker/linker_jni.cc b/src/base/android/linker/linker_jni.cc new file mode 100644 index 0000000..7165a12 --- /dev/null +++ b/src/base/android/linker/linker_jni.cc
@@ -0,0 +1,698 @@ +// Copyright 2015 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. + +// This is the Android-specific Chromium linker, a tiny shared library +// implementing a custom dynamic linker that can be used to load the +// real Chromium libraries. + +// The main point of this linker is to be able to share the RELRO +// section of libchrome.so (or equivalent) between renderer processes. + +// This source code *cannot* depend on anything from base/ or the C++ +// STL, to keep the final library small, and avoid ugly dependency issues. + +#include <android/log.h> +#include <jni.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> + +#include "build/build_config.h" + +#include <crazy_linker.h> + +#include "starboard/types.h" + +// Set this to 1 to enable debug traces to the Android log. +// Note that LOG() from "base/logging.h" cannot be used, since it is +// in base/ which hasn't been loaded yet. +#define DEBUG 0 + +#define TAG "cr_ChromiumAndroidLinker" + +#if DEBUG +#define LOG_INFO(FORMAT, ...) \ + __android_log_print(ANDROID_LOG_INFO, TAG, "%s: " FORMAT, __FUNCTION__, \ + ##__VA_ARGS__) +#else +#define LOG_INFO(FORMAT, ...) ((void)0) +#endif +#define LOG_ERROR(FORMAT, ...) \ + __android_log_print(ANDROID_LOG_ERROR, TAG, "%s: " FORMAT, __FUNCTION__, \ + ##__VA_ARGS__) + +#define UNUSED __attribute__((unused)) + +// See commentary in crazy_linker_elf_loader.cpp for the effect of setting +// this. If changing there, change here also. +// +// For more, see: +// https://crbug.com/504410 +#define RESERVE_BREAKPAD_GUARD_REGION 1 + +#if defined(ARCH_CPU_X86) +// Dalvik JIT generated code doesn't guarantee 16-byte stack alignment on +// x86 - use force_align_arg_pointer to realign the stack at the JNI +// boundary. https://crbug.com/655248 +#define JNI_GENERATOR_EXPORT \ + extern "C" __attribute__((visibility("default"), force_align_arg_pointer)) +#else +#define JNI_GENERATOR_EXPORT extern "C" __attribute__((visibility("default"))) +#endif + +namespace chromium_android_linker { + +namespace { + +// Larger than the largest library we might attempt to load. +constexpr size_t kAddressSpaceReservationSize = 192 * 1024 * 1024; + +// Size of any Breakpad guard region. 16MB is comfortably larger than the +// ~6MB relocation packing of the current 64-bit libchrome.so, the largest we +// expect to encounter. +#if RESERVE_BREAKPAD_GUARD_REGION +constexpr size_t kBreakpadGuardRegionBytes = 16 * 1024 * 1024; +#endif + +// A simple scoped UTF String class that can be initialized from +// a Java jstring handle. Modeled like std::string, which cannot +// be used here. +class String { + public: + String(JNIEnv* env, jstring str); + + inline ~String() { ::free(ptr_); } + + inline const char* c_str() const { return ptr_ ? ptr_ : ""; } + inline size_t size() const { return size_; } + + private: + char* ptr_; + size_t size_; +}; + +// Simple scoped UTF String class constructor. +String::String(JNIEnv* env, jstring str) { + size_ = env->GetStringUTFLength(str); + ptr_ = static_cast<char*>(::malloc(size_ + 1)); + + // Note: This runs before browser native code is loaded, and so cannot + // rely on anything from base/. This means that we must use + // GetStringUTFChars() and not base::android::ConvertJavaStringToUTF8(). + // + // GetStringUTFChars() suffices because the only strings used here are + // paths to APK files or names of shared libraries, all of which are + // plain ASCII, defined and hard-coded by the Chromium Android build. + // + // For more: see + // https://crbug.com/508876 + // + // Note: GetStringUTFChars() returns Java UTF-8 bytes. This is good + // enough for the linker though. + const char* bytes = env->GetStringUTFChars(str, nullptr); + ::memcpy(ptr_, bytes, size_); + ptr_[size_] = '\0'; + + env->ReleaseStringUTFChars(str, bytes); +} + +// Find the jclass JNI reference corresponding to a given |class_name|. +// |env| is the current JNI environment handle. +// On success, return true and set |*clazz|. +bool InitClassReference(JNIEnv* env, const char* class_name, jclass* clazz) { + *clazz = env->FindClass(class_name); + if (!*clazz) { + LOG_ERROR("Could not find class for %s", class_name); + return false; + } + return true; +} + +// Initialize a jfieldID corresponding to the field of a given |clazz|, +// with name |field_name| and signature |field_sig|. +// |env| is the current JNI environment handle. +// On success, return true and set |*field_id|. +bool InitFieldId(JNIEnv* env, + jclass clazz, + const char* field_name, + const char* field_sig, + jfieldID* field_id) { + *field_id = env->GetFieldID(clazz, field_name, field_sig); + if (!*field_id) { + LOG_ERROR("Could not find ID for field '%s'", field_name); + return false; + } + LOG_INFO("Found ID %p for field '%s'", *field_id, field_name); + return true; +} + +// Initialize a jmethodID corresponding to the static method of a given +// |clazz|, with name |method_name| and signature |method_sig|. +// |env| is the current JNI environment handle. +// On success, return true and set |*method_id|. +bool InitStaticMethodId(JNIEnv* env, + jclass clazz, + const char* method_name, + const char* method_sig, + jmethodID* method_id) { + *method_id = env->GetStaticMethodID(clazz, method_name, method_sig); + if (!*method_id) { + LOG_ERROR("Could not find ID for static method '%s'", method_name); + return false; + } + LOG_INFO("Found ID %p for static method '%s'", *method_id, method_name); + return true; +} + +// Initialize a jfieldID corresponding to the static field of a given |clazz|, +// with name |field_name| and signature |field_sig|. +// |env| is the current JNI environment handle. +// On success, return true and set |*field_id|. +bool InitStaticFieldId(JNIEnv* env, + jclass clazz, + const char* field_name, + const char* field_sig, + jfieldID* field_id) { + *field_id = env->GetStaticFieldID(clazz, field_name, field_sig); + if (!*field_id) { + LOG_ERROR("Could not find ID for static field '%s'", field_name); + return false; + } + LOG_INFO("Found ID %p for static field '%s'", *field_id, field_name); + return true; +} + +// Initialize a jint corresponding to the static integer field of a class +// with class name |class_name| and field name |field_name|. +// |env| is the current JNI environment handle. +// On success, return true and set |*value|. +bool InitStaticInt(JNIEnv* env, + const char* class_name, + const char* field_name, + jint* value) { + jclass clazz; + if (!InitClassReference(env, class_name, &clazz)) + return false; + + jfieldID field_id; + if (!InitStaticFieldId(env, clazz, field_name, "I", &field_id)) + return false; + + *value = env->GetStaticIntField(clazz, field_id); + LOG_INFO("Found value %d for class '%s', static field '%s'", + *value, class_name, field_name); + + return true; +} + +// A class used to model the field IDs of the org.chromium.base.Linker +// LibInfo inner class, used to communicate data with the Java side +// of the linker. +struct LibInfo_class { + jfieldID load_address_id; + jfieldID load_size_id; + jfieldID relro_start_id; + jfieldID relro_size_id; + jfieldID relro_fd_id; + + // Initialize an instance. + bool Init(JNIEnv* env) { + jclass clazz; + if (!InitClassReference( + env, "org/chromium/base/library_loader/Linker$LibInfo", &clazz)) { + return false; + } + + return InitFieldId(env, clazz, "mLoadAddress", "J", &load_address_id) && + InitFieldId(env, clazz, "mLoadSize", "J", &load_size_id) && + InitFieldId(env, clazz, "mRelroStart", "J", &relro_start_id) && + InitFieldId(env, clazz, "mRelroSize", "J", &relro_size_id) && + InitFieldId(env, clazz, "mRelroFd", "I", &relro_fd_id); + } + + void SetLoadInfo(JNIEnv* env, + jobject library_info_obj, + size_t load_address, + size_t load_size) { + env->SetLongField(library_info_obj, load_address_id, load_address); + env->SetLongField(library_info_obj, load_size_id, load_size); + } + + void SetRelroInfo(JNIEnv* env, + jobject library_info_obj, + size_t relro_start, + size_t relro_size, + int relro_fd) { + env->SetLongField(library_info_obj, relro_start_id, relro_start); + env->SetLongField(library_info_obj, relro_size_id, relro_size); + env->SetIntField(library_info_obj, relro_fd_id, relro_fd); + } + + // Use this instance to convert a RelroInfo reference into + // a crazy_library_info_t. + void GetRelroInfo(JNIEnv* env, + jobject library_info_obj, + size_t* relro_start, + size_t* relro_size, + int* relro_fd) { + if (relro_start) { + *relro_start = static_cast<size_t>( + env->GetLongField(library_info_obj, relro_start_id)); + } + + if (relro_size) { + *relro_size = static_cast<size_t>( + env->GetLongField(library_info_obj, relro_size_id)); + } + + if (relro_fd) { + *relro_fd = env->GetIntField(library_info_obj, relro_fd_id); + } + } +}; + +// Variable containing LibInfo for the loaded library. +LibInfo_class s_lib_info_fields; + +// Return true iff |address| is a valid address for the target CPU. +inline bool IsValidAddress(jlong address) { + return static_cast<jlong>(static_cast<size_t>(address)) == address; +} + +// The linker uses a single crazy_context_t object created on demand. +// There is no need to protect this against concurrent access, locking +// is already handled on the Java side. +crazy_context_t* GetCrazyContext() { + static crazy_context_t* s_crazy_context = nullptr; + + if (!s_crazy_context) { + // Create new context. + s_crazy_context = crazy_context_create(); + + // Ensure libraries located in the same directory as the linker + // can be loaded before system ones. + crazy_context_add_search_path_for_address( + s_crazy_context, reinterpret_cast<void*>(&GetCrazyContext)); + } + + return s_crazy_context; +} + +// A scoped crazy_library_t that automatically closes the handle +// on scope exit, unless Release() has been called. +class ScopedLibrary { + public: + ScopedLibrary() : lib_(nullptr) {} + + ~ScopedLibrary() { + if (lib_) + crazy_library_close_with_context(lib_, GetCrazyContext()); + } + + crazy_library_t* Get() { return lib_; } + + crazy_library_t** GetPtr() { return &lib_; } + + crazy_library_t* Release() { + crazy_library_t* ret = lib_; + lib_ = nullptr; + return ret; + } + + private: + crazy_library_t* lib_; +}; + +// Retrieve the SDK build version and pass it into the crazy linker. This +// needs to be done early in initialization, before any other crazy linker +// code is run. +// |env| is the current JNI environment handle. +// On success, return true. +bool InitSDKVersionInfo(JNIEnv* env) { + jint value = 0; + if (!InitStaticInt(env, "android/os/Build$VERSION", "SDK_INT", &value)) + return false; + + crazy_set_sdk_build_version(static_cast<int>(value)); + LOG_INFO("Set SDK build version to %d", static_cast<int>(value)); + + return true; +} + +} // namespace + +// Use Android ASLR to create a random address into which we expect to be +// able to load libraries. Note that this is probabilistic; we unmap the +// address we get from mmap and assume we can re-map into it later. This +// works the majority of the time. If it doesn't, client code backs out and +// then loads the library normally at any available address. +// |env| is the current JNI environment handle, and |clazz| a class. +// Returns the address selected by ASLR, or 0 on error. +JNI_GENERATOR_EXPORT jlong +Java_org_chromium_base_library_1loader_Linker_nativeGetRandomBaseLoadAddress( + JNIEnv* env, + jclass clazz) { + size_t bytes = kAddressSpaceReservationSize; + +#if RESERVE_BREAKPAD_GUARD_REGION + // Pad the requested address space size for a Breakpad guard region. + bytes += kBreakpadGuardRegionBytes; +#endif + + void* address = + mmap(nullptr, bytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (address == MAP_FAILED) { + LOG_INFO("Random base load address not determinable"); + return 0; + } + munmap(address, bytes); + +#if RESERVE_BREAKPAD_GUARD_REGION + // Allow for a Breakpad guard region ahead of the returned address. + address = reinterpret_cast<void*>( + reinterpret_cast<uintptr_t>(address) + kBreakpadGuardRegionBytes); +#endif + + LOG_INFO("Random base load address is %p", address); + return static_cast<jlong>(reinterpret_cast<uintptr_t>(address)); +} + +// We identify the abi tag for which the linker is running. This allows +// us to select the library which matches the abi of the linker. + +#if defined(__arm__) && defined(__ARM_ARCH_7A__) +#define CURRENT_ABI "armeabi-v7a" +#elif defined(__arm__) +#define CURRENT_ABI "armeabi" +#elif defined(__i386__) +#define CURRENT_ABI "x86" +#elif defined(__mips__) +#define CURRENT_ABI "mips" +#elif defined(__x86_64__) +#define CURRENT_ABI "x86_64" +#elif defined(__aarch64__) +#define CURRENT_ABI "arm64-v8a" +#else +#error "Unsupported target abi" +#endif + +// Add a zip archive file path to the context's current search path +// list. Making it possible to load libraries directly from it. +JNI_GENERATOR_EXPORT bool +Java_org_chromium_base_library_1loader_Linker_nativeAddZipArchivePath( + JNIEnv* env, + jclass clazz, + jstring apk_path_obj) { + String apk_path(env, apk_path_obj); + + char search_path[512]; + snprintf(search_path, sizeof(search_path), "%s!lib/" CURRENT_ABI "/", + apk_path.c_str()); + + crazy_context_t* context = GetCrazyContext(); + crazy_context_add_search_path(context, search_path); + return true; +} + +// Load a library with the chromium linker. This will also call its +// JNI_OnLoad() method, which shall register its methods. Note that +// lazy native method resolution will _not_ work after this, because +// Dalvik uses the system's dlsym() which won't see the new library, +// so explicit registration is mandatory. +// +// |env| is the current JNI environment handle. +// |clazz| is the static class handle for org.chromium.base.Linker, +// and is ignored here. +// |library_name| is the library name (e.g. libfoo.so). +// |load_address| is an explicit load address. +// |lib_info_obj| is a LibInfo handle used to communicate information +// with the Java side. +// Return true on success. +JNI_GENERATOR_EXPORT bool +Java_org_chromium_base_library_1loader_Linker_nativeLoadLibrary( + JNIEnv* env, + jclass clazz, + jstring lib_name_obj, + jlong load_address, + jobject lib_info_obj) { + String library_name(env, lib_name_obj); + LOG_INFO("Called for %s, at address 0x%llx", library_name, load_address); + crazy_context_t* context = GetCrazyContext(); + + if (!IsValidAddress(load_address)) { + LOG_ERROR("Invalid address 0x%llx", + static_cast<unsigned long long>(load_address)); + return false; + } + + // Set the desired load address (0 means randomize it). + crazy_context_set_load_address(context, static_cast<size_t>(load_address)); + + ScopedLibrary library; + if (!crazy_library_open(library.GetPtr(), library_name.c_str(), context)) { + return false; + } + + crazy_library_info_t info; + if (!crazy_library_get_info(library.Get(), context, &info)) { + LOG_ERROR("Could not get library information for %s: %s", + library_name.c_str(), crazy_context_get_error(context)); + return false; + } + + // Release library object to keep it alive after the function returns. + library.Release(); + + s_lib_info_fields.SetLoadInfo(env, lib_info_obj, info.load_address, + info.load_size); + LOG_INFO("Success loading library %s", library_name.c_str()); + return true; +} + +// Class holding the Java class and method ID for the Java side Linker +// postCallbackOnMainThread method. +struct JavaCallbackBindings_class { + jclass clazz; + jmethodID method_id; + + // Initialize an instance. + bool Init(JNIEnv* env, jclass linker_class) { + clazz = reinterpret_cast<jclass>(env->NewGlobalRef(linker_class)); + return InitStaticMethodId(env, linker_class, "postCallbackOnMainThread", + "(J)V", &method_id); + } +}; + +static JavaCallbackBindings_class s_java_callback_bindings; + +// Designated receiver function for callbacks from Java. Its name is known +// to the Java side. +// |env| is the current JNI environment handle and is ignored here. +// |clazz| is the static class handle for org.chromium.base.Linker, +// and is ignored here. +// |arg| is a pointer to an allocated crazy_callback_t, deleted after use. +JNI_GENERATOR_EXPORT void +Java_org_chromium_base_library_1loader_Linker_nativeRunCallbackOnUiThread( + JNIEnv* env, + jclass clazz, + jlong arg) { + crazy_callback_t* callback = reinterpret_cast<crazy_callback_t*>(arg); + + LOG_INFO("Called back from java with handler %p, opaque %p", + callback->handler, callback->opaque); + + crazy_callback_run(callback); + delete callback; +} + +// Request a callback from Java. The supplied crazy_callback_t is valid only +// for the duration of this call, so we copy it to a newly allocated +// crazy_callback_t and then call the Java side's postCallbackOnMainThread. +// This will call back to to our RunCallbackOnUiThread some time +// later on the UI thread. +// |callback_request| is a crazy_callback_t. +// |poster_opaque| is unused. +// Returns true if the callback request succeeds. +static bool PostForLaterExecution(crazy_callback_t* callback_request, + void* poster_opaque UNUSED) { + crazy_context_t* context = GetCrazyContext(); + + JavaVM* vm; + int minimum_jni_version; + crazy_context_get_java_vm(context, reinterpret_cast<void**>(&vm), + &minimum_jni_version); + + // Do not reuse JNIEnv from JNI_OnLoad, but retrieve our own. + JNIEnv* env; + if (JNI_OK != + vm->GetEnv(reinterpret_cast<void**>(&env), minimum_jni_version)) { + LOG_ERROR("Could not create JNIEnv"); + return false; + } + + // Copy the callback; the one passed as an argument may be temporary. + crazy_callback_t* callback = new crazy_callback_t(); + *callback = *callback_request; + + LOG_INFO("Calling back to java with handler %p, opaque %p", callback->handler, + callback->opaque); + + jlong arg = static_cast<jlong>(reinterpret_cast<uintptr_t>(callback)); + + env->CallStaticVoidMethod(s_java_callback_bindings.clazz, + s_java_callback_bindings.method_id, arg); + + // Back out and return false if we encounter a JNI exception. + if (env->ExceptionCheck() == JNI_TRUE) { + env->ExceptionDescribe(); + env->ExceptionClear(); + delete callback; + return false; + } + + return true; +} + +JNI_GENERATOR_EXPORT jboolean +Java_org_chromium_base_library_1loader_Linker_nativeCreateSharedRelro( + JNIEnv* env, + jclass clazz, + jstring library_name, + jlong load_address, + jobject lib_info_obj) { + String lib_name(env, library_name); + + LOG_INFO("Called for %s", lib_name.c_str()); + + if (!IsValidAddress(load_address)) { + LOG_ERROR("Invalid address 0x%llx", + static_cast<unsigned long long>(load_address)); + return false; + } + + ScopedLibrary library; + if (!crazy_library_find_by_name(lib_name.c_str(), library.GetPtr())) { + LOG_ERROR("Could not find %s", lib_name.c_str()); + return false; + } + + crazy_context_t* context = GetCrazyContext(); + size_t relro_start = 0; + size_t relro_size = 0; + int relro_fd = -1; + + if (!crazy_library_create_shared_relro( + library.Get(), context, static_cast<size_t>(load_address), + &relro_start, &relro_size, &relro_fd)) { + LOG_ERROR("Could not create shared RELRO sharing for %s: %s\n", + lib_name.c_str(), crazy_context_get_error(context)); + return false; + } + + s_lib_info_fields.SetRelroInfo(env, lib_info_obj, relro_start, relro_size, + relro_fd); + return true; +} + +JNI_GENERATOR_EXPORT jboolean +Java_org_chromium_base_library_1loader_Linker_nativeUseSharedRelro( + JNIEnv* env, + jclass clazz, + jstring library_name, + jobject lib_info_obj) { + String lib_name(env, library_name); + + LOG_INFO("Called for %s, lib_info_ref=%p", lib_name.c_str(), lib_info_obj); + + ScopedLibrary library; + if (!crazy_library_find_by_name(lib_name.c_str(), library.GetPtr())) { + LOG_ERROR("Could not find %s", lib_name.c_str()); + return false; + } + + crazy_context_t* context = GetCrazyContext(); + size_t relro_start = 0; + size_t relro_size = 0; + int relro_fd = -1; + s_lib_info_fields.GetRelroInfo(env, lib_info_obj, &relro_start, &relro_size, + &relro_fd); + + LOG_INFO("library=%s relro start=%p size=%p fd=%d", lib_name.c_str(), + (void*)relro_start, (void*)relro_size, relro_fd); + + if (!crazy_library_use_shared_relro(library.Get(), context, relro_start, + relro_size, relro_fd)) { + LOG_ERROR("Could not use shared RELRO for %s: %s", lib_name.c_str(), + crazy_context_get_error(context)); + return false; + } + + LOG_INFO("Library %s using shared RELRO section!", lib_name.c_str()); + + return true; +} + +static bool LinkerJNIInit(JavaVM* vm, JNIEnv* env) { + LOG_INFO("Entering"); + + // Initialize SDK version info. + LOG_INFO("Retrieving SDK version info"); + if (!InitSDKVersionInfo(env)) + return false; + + // Find LibInfo field ids. + LOG_INFO("Caching field IDs"); + if (!s_lib_info_fields.Init(env)) { + return false; + } + + // Register native methods. + jclass linker_class; + if (!InitClassReference(env, "org/chromium/base/library_loader/Linker", + &linker_class)) + return false; + + // Resolve and save the Java side Linker callback class and method. + LOG_INFO("Resolving callback bindings"); + if (!s_java_callback_bindings.Init(env, linker_class)) { + return false; + } + + // Save JavaVM* handle into context. + crazy_context_t* context = GetCrazyContext(); + crazy_context_set_java_vm(context, vm, JNI_VERSION_1_4); + + // Register the function that the crazy linker can call to post code + // for later execution. + crazy_context_set_callback_poster(context, &PostForLaterExecution, nullptr); + + return true; +} + +// JNI_OnLoad() hook called when the linker library is loaded through +// the regular System.LoadLibrary) API. This shall save the Java VM +// handle and initialize LibInfo fields. +jint JNI_OnLoad(JavaVM* vm, void* reserved) { + LOG_INFO("Entering"); + // Get new JNIEnv + JNIEnv* env; + if (JNI_OK != vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_4)) { + LOG_ERROR("Could not create JNIEnv"); + return -1; + } + + // Initialize linker base and implementations. + if (!LinkerJNIInit(vm, env)) { + return -1; + } + + LOG_INFO("Done"); + return JNI_VERSION_1_4; +} + +} // namespace chromium_android_linker + +jint JNI_OnLoad(JavaVM* vm, void* reserved) { + return chromium_android_linker::JNI_OnLoad(vm, reserved); +}
diff --git a/src/base/android/locale_utils.cc b/src/base/android/locale_utils.cc new file mode 100644 index 0000000..b3a2346 --- /dev/null +++ b/src/base/android/locale_utils.cc
@@ -0,0 +1,27 @@ +// Copyright 2014 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. + +#include "base/android/locale_utils.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "jni/LocaleUtils_jni.h" + +namespace base { +namespace android { + +std::string GetDefaultCountryCode() { + JNIEnv* env = base::android::AttachCurrentThread(); + return ConvertJavaStringToUTF8(Java_LocaleUtils_getDefaultCountryCode(env)); +} + +std::string GetDefaultLocaleString() { + JNIEnv* env = base::android::AttachCurrentThread(); + ScopedJavaLocalRef<jstring> locale = + Java_LocaleUtils_getDefaultLocaleString(env); + return ConvertJavaStringToUTF8(locale); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/locale_utils.h b/src/base/android/locale_utils.h new file mode 100644 index 0000000..84ee201 --- /dev/null +++ b/src/base/android/locale_utils.h
@@ -0,0 +1,26 @@ +// Copyright 2014 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_ANDROID_LOCALE_UTILS_H_ +#define BASE_ANDROID_LOCALE_UTILS_H_ + +#include <jni.h> + +#include <string> + +#include "base/base_export.h" +#include "starboard/types.h" + +namespace base { +namespace android { + +BASE_EXPORT std::string GetDefaultCountryCode(); + +// Return the current default locale of the device as string. +BASE_EXPORT std::string GetDefaultLocaleString(); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_LOCALE_UTILS_H_
diff --git a/src/base/android/memory_pressure_listener_android.cc b/src/base/android/memory_pressure_listener_android.cc new file mode 100644 index 0000000..cab66e1 --- /dev/null +++ b/src/base/android/memory_pressure_listener_android.cc
@@ -0,0 +1,30 @@ +// Copyright 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. + +#include "base/android/memory_pressure_listener_android.h" + +#include "base/memory/memory_pressure_listener.h" +#include "jni/MemoryPressureListener_jni.h" + +using base::android::JavaParamRef; + +// Defined and called by JNI. +static void JNI_MemoryPressureListener_OnMemoryPressure( + JNIEnv* env, + const JavaParamRef<jclass>& clazz, + jint memory_pressure_level) { + base::MemoryPressureListener::NotifyMemoryPressure( + static_cast<base::MemoryPressureListener::MemoryPressureLevel>( + memory_pressure_level)); +} + +namespace base { +namespace android { + +void MemoryPressureListenerAndroid::Initialize(JNIEnv* env) { + Java_MemoryPressureListener_addNativeCallback(env); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/memory_pressure_listener_android.h b/src/base/android/memory_pressure_listener_android.h new file mode 100644 index 0000000..9edfd42 --- /dev/null +++ b/src/base/android/memory_pressure_listener_android.h
@@ -0,0 +1,29 @@ +// Copyright 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_ANDROID_MEMORY_PRESSURE_LISTENER_ANDROID_H_ +#define BASE_ANDROID_MEMORY_PRESSURE_LISTENER_ANDROID_H_ + +#include "base/android/jni_android.h" +#include "base/macros.h" + +namespace base { +namespace android { + +// Implements the C++ counter part of MemoryPressureListener.java +class BASE_EXPORT MemoryPressureListenerAndroid { + public: + static void Initialize(JNIEnv* env); + + // Called by JNI. + static void OnMemoryPressure(int memory_pressure_type); + + private: + DISALLOW_COPY_AND_ASSIGN(MemoryPressureListenerAndroid); +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_MEMORY_PRESSURE_LISTENER_ANDROID_H_
diff --git a/src/base/android/orderfile/BUILD.gn b/src/base/android/orderfile/BUILD.gn new file mode 100644 index 0000000..ff0bfff --- /dev/null +++ b/src/base/android/orderfile/BUILD.gn
@@ -0,0 +1,34 @@ +# Copyright 2018 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. + +import("//build/config/android/config.gni") + +if (use_order_profiling && target_cpu == "arm") { + static_library("orderfile_instrumentation") { + sources = [ + "orderfile_instrumentation.cc", + "orderfile_instrumentation.h", + ] + deps = [ + "//base", + ] + } + + executable("orderfile_instrumentation_perftest") { + testonly = true + + sources = [ + "orderfile_instrumentation_perftest.cc", + ] + + deps = [ + ":orderfile_instrumentation", + "//base", + "//testing/gtest", + "//testing/perf", + ] + + configs -= [ "//build/config/android:default_orderfile_instrumentation" ] + } +}
diff --git a/src/base/android/orderfile/orderfile_instrumentation.cc b/src/base/android/orderfile/orderfile_instrumentation.cc new file mode 100644 index 0000000..1d8b09c --- /dev/null +++ b/src/base/android/orderfile/orderfile_instrumentation.cc
@@ -0,0 +1,330 @@ +// Copyright (c) 2017 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. + +#include "base/android/orderfile/orderfile_instrumentation.h" + +#include <time.h> +#include <unistd.h> + +#include <atomic> +#include <cstdio> +#include <cstring> +#include <string> +#include <thread> +#include <vector> + +#include "base/android/library_loader/anchor_functions.h" +#include "base/android/orderfile/orderfile_buildflags.h" +#include "base/files/file.h" +#include "base/format_macros.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/strings/stringprintf.h" +#include "build/build_config.h" + +#if BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING) +#include <sstream> + +#include "base/command_line.h" +#include "base/time/time.h" +#include "base/trace_event/memory_dump_manager.h" +#include "base/trace_event/memory_dump_provider.h" +#include "starboard/memory.h" +#include "starboard/types.h" +#endif // BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING) + +#if !defined(ARCH_CPU_ARMEL) +#error Only supported on ARM. +#endif // !defined(ARCH_CPU_ARMEL) + +// Must be applied to all functions within this file. +#define NO_INSTRUMENT_FUNCTION __attribute__((no_instrument_function)) + +namespace base { +namespace android { +namespace orderfile { + +namespace { +// Constants used for StartDelayedDump(). +constexpr int kDelayInSeconds = 30; +constexpr int kInitialDelayInSeconds = kPhases == 1 ? kDelayInSeconds : 5; + +#if BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING) +// This is defined in content/public/common/content_switches.h, which is not +// accessible in ::base. +constexpr const char kProcessTypeSwitch[] = "type"; +#endif // BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING) + +// These are large overestimates, which is not an issue, as the data is +// allocated in .bss, and on linux doesn't take any actual memory when it's not +// touched. +constexpr size_t kBitfieldSize = 1 << 22; +constexpr size_t kMaxTextSizeInBytes = kBitfieldSize * (4 * 32); +constexpr size_t kMaxElements = 1 << 20; + +// Data required to log reached offsets. +struct LogData { + std::atomic<uint32_t> offsets[kBitfieldSize]; + std::atomic<size_t> ordered_offsets[kMaxElements]; + std::atomic<size_t> index; +}; + +LogData g_data[kPhases]; +std::atomic<int> g_data_index; + +#if BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING) +// Dump offsets when a memory dump is requested. Used only if +// switches::kDevtoolsInstrumentationDumping is set. +class OrderfileMemoryDumpHook : public base::trace_event::MemoryDumpProvider { + NO_INSTRUMENT_FUNCTION bool OnMemoryDump( + const base::trace_event::MemoryDumpArgs& args, + base::trace_event::ProcessMemoryDump* pmd) override { + // Disable instrumentation now to cut down on orderfile pollution. + if (!Disable()) { + return true; // A dump has already been started. + } + std::stringstream process_type_str; + Dump(base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + kProcessTypeSwitch)); + return true; // If something goes awry, a fatal error will be created + // internally. + } +}; +#endif // BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING) + +// |RecordAddress()| adds an element to a concurrent bitset and to a concurrent +// append-only list of offsets. +// +// Ordering: +// Two consecutive calls to |RecordAddress()| from the same thread will be +// ordered in the same way in the result, as written by +// |StopAndDumpToFile()|. The result will contain exactly one instance of each +// unique offset relative to |kStartOfText| passed to |RecordAddress()|. +// +// Implementation: +// The "set" part is implemented with a bitfield, |g_offset|. The insertion +// order is recorded in |g_ordered_offsets|. +// This is not a class to make sure there isn't a static constructor, as it +// would cause issue with an instrumented static constructor calling this code. +// +// Limitations: +// - Only records offsets to addresses between |kStartOfText| and |kEndOfText|. +// - Capacity of the set is limited by |kMaxElements|. +// - Some insertions at the end of collection may be lost. + +// Records that |address| has been reached, if recording is enabled. +// To avoid infinite recursion, this *must* *never* call any instrumented +// function, unless |Disable()| is called first. +template <bool for_testing> +__attribute__((always_inline, no_instrument_function)) void RecordAddress( + size_t address) { + int index = g_data_index.load(std::memory_order_relaxed); + if (index >= kPhases) + return; + + const size_t start = + for_testing ? kStartOfTextForTesting : base::android::kStartOfText; + const size_t end = + for_testing ? kEndOfTextForTesting : base::android::kEndOfText; + if (UNLIKELY(address < start || address > end)) { + Disable(); + // If the start and end addresses are set incorrectly, this code path is + // likely happening during a static initializer. Logging at this time is + // prone to deadlock. By crashing immediately we at least have a chance to + // get a stack trace from the system to give some clue about the nature of + // the problem. + IMMEDIATE_CRASH(); + } + + size_t offset = address - start; + static_assert(sizeof(int) == 4, + "Collection and processing code assumes that sizeof(int) == 4"); + size_t offset_index = offset / 4; + + auto* offsets = g_data[index].offsets; + // Atomically set the corresponding bit in the array. + std::atomic<uint32_t>* element = offsets + (offset_index / 32); + // First, a racy check. This saves a CAS if the bit is already set, and + // allows the cache line to remain shared acoss CPUs in this case. + uint32_t value = element->load(std::memory_order_relaxed); + uint32_t mask = 1 << (offset_index % 32); + if (value & mask) + return; + + auto before = element->fetch_or(mask, std::memory_order_relaxed); + if (before & mask) + return; + + // We were the first one to set the element, record it in the ordered + // elements list. + // Use relaxed ordering, as the value is not published, or used for + // synchronization. + auto* ordered_offsets = g_data[index].ordered_offsets; + auto& ordered_offsets_index = g_data[index].index; + size_t insertion_index = + ordered_offsets_index.fetch_add(1, std::memory_order_relaxed); + if (UNLIKELY(insertion_index >= kMaxElements)) { + Disable(); + LOG(FATAL) << "Too many reached offsets"; + } + ordered_offsets[insertion_index].store(offset, std::memory_order_relaxed); +} + +NO_INSTRUMENT_FUNCTION bool DumpToFile(const base::FilePath& path, + const LogData& data) { + auto file = + base::File(path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); + if (!file.IsValid()) { + PLOG(ERROR) << "Could not open " << path; + return false; + } + + if (data.index == 0) { + LOG(ERROR) << "No entries to dump"; + return false; + } + + size_t count = data.index - 1; + for (size_t i = 0; i < count; i++) { + // |g_ordered_offsets| is initialized to 0, so a 0 in the middle of it + // indicates a case where the index was incremented, but the write is not + // visible in this thread yet. Safe to skip, also because the function at + // the start of text is never called. + auto offset = data.ordered_offsets[i].load(std::memory_order_relaxed); + if (!offset) + continue; + auto offset_str = base::StringPrintf("%" PRIuS "\n", offset); + if (file.WriteAtCurrentPos(offset_str.c_str(), + static_cast<int>(offset_str.size())) < 0) { + // If the file could be opened, but writing has failed, it's likely that + // data was partially written. Producing incomplete profiling data would + // lead to a poorly performing orderfile, but might not be otherwised + // noticed. So we crash instead. + LOG(FATAL) << "Error writing profile data"; + } + } + return true; +} + +// Stops recording, and outputs the data to |path|. +NO_INSTRUMENT_FUNCTION void StopAndDumpToFile(int pid, + uint64_t start_ns_since_epoch, + const std::string& tag) { + Disable(); + + for (int phase = 0; phase < kPhases; phase++) { + std::string tag_str; + if (!tag.empty()) + tag_str = base::StringPrintf("%s-", tag.c_str()); + auto path = base::StringPrintf( + "/data/local/tmp/chrome/orderfile/profile-hitmap-%s%d-%" PRIu64 + ".txt_%d", + tag_str.c_str(), pid, start_ns_since_epoch, phase); + if (!DumpToFile(base::FilePath(path), g_data[phase])) { + LOG(ERROR) << "Problem with dump " << phase << " (" << tag << ")"; + } + } +} + +} // namespace + +NO_INSTRUMENT_FUNCTION bool Disable() { + auto old_phase = g_data_index.exchange(kPhases, std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_seq_cst); + return old_phase != kPhases; +} + +NO_INSTRUMENT_FUNCTION void SanityChecks() { + CHECK_LT(base::android::kEndOfText - base::android::kStartOfText, + kMaxTextSizeInBytes); + CHECK(base::android::IsOrderingSane()); +} + +NO_INSTRUMENT_FUNCTION bool SwitchToNextPhaseOrDump( + int pid, + uint64_t start_ns_since_epoch) { + int before = g_data_index.fetch_add(1, std::memory_order_relaxed); + if (before + 1 == kPhases) { + StopAndDumpToFile(pid, start_ns_since_epoch, ""); + return true; + } + return false; +} + +NO_INSTRUMENT_FUNCTION void StartDelayedDump() { + // Using std::thread and not using base::TimeTicks() in order to to not call + // too many base:: symbols that would pollute the reached symbol dumps. + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts)) + PLOG(FATAL) << "clock_gettime."; + uint64_t start_ns_since_epoch = + static_cast<uint64_t>(ts.tv_sec) * 1000 * 1000 * 1000 + ts.tv_nsec; + int pid = getpid(); + +#if BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING) + static auto* g_orderfile_memory_dump_hook = new OrderfileMemoryDumpHook(); + base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider( + g_orderfile_memory_dump_hook, "Orderfile", nullptr); +#endif // BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING) + + std::thread([pid, start_ns_since_epoch]() { + sleep(kInitialDelayInSeconds); +#if BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING) + SwitchToNextPhaseOrDump(pid, start_ns_since_epoch); +// Return, letting devtools tracing handle any post-startup phases. +#else + while (!SwitchToNextPhaseOrDump(pid, start_ns_since_epoch)) + sleep(kDelayInSeconds); +#endif // BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING) + }) + .detach(); +} + +NO_INSTRUMENT_FUNCTION void Dump(const std::string& tag) { + // As profiling has been disabled, none of the uses of ::base symbols below + // will enter the symbol dump. + StopAndDumpToFile( + getpid(), (base::Time::Now() - base::Time::UnixEpoch()).InNanoseconds(), + tag); +} + +NO_INSTRUMENT_FUNCTION void ResetForTesting() { + Disable(); + g_data_index = 0; + for (int i = 0; i < kPhases; i++) { + SbMemorySet(reinterpret_cast<uint32_t*>(g_data[i].offsets), 0, + sizeof(uint32_t) * kBitfieldSize); + SbMemorySet(reinterpret_cast<uint32_t*>(g_data[i].ordered_offsets), 0, + sizeof(uint32_t) * kMaxElements); + g_data[i].index.store(0); + } +} + +NO_INSTRUMENT_FUNCTION void RecordAddressForTesting(size_t address) { + return RecordAddress<true>(address); +} + +NO_INSTRUMENT_FUNCTION std::vector<size_t> GetOrderedOffsetsForTesting() { + std::vector<size_t> result; + size_t max_index = g_data[0].index.load(std::memory_order_relaxed); + for (size_t i = 0; i < max_index; ++i) { + auto value = g_data[0].ordered_offsets[i].load(std::memory_order_relaxed); + if (value) + result.push_back(value); + } + return result; +} + +} // namespace orderfile +} // namespace android +} // namespace base + +extern "C" { + +NO_INSTRUMENT_FUNCTION void __cyg_profile_func_enter_bare() { + base::android::orderfile::RecordAddress<false>( + reinterpret_cast<size_t>(__builtin_return_address(0))); +} + +} // extern "C"
diff --git a/src/base/android/orderfile/orderfile_instrumentation.h b/src/base/android/orderfile/orderfile_instrumentation.h new file mode 100644 index 0000000..8db1943 --- /dev/null +++ b/src/base/android/orderfile/orderfile_instrumentation.h
@@ -0,0 +1,56 @@ +// Copyright 2017 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_ANDROID_ORDERFILE_ORDERFILE_INSTRUMENTATION_H_ +#define BASE_ANDROID_ORDERFILE_ORDERFILE_INSTRUMENTATION_H_ + +#include <cstdint> +#include <vector> + +#include "base/android/orderfile/orderfile_buildflags.h" + +namespace base { +namespace android { +namespace orderfile { +#if BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING) +constexpr int kPhases = 2; +#else +constexpr int kPhases = 1; +#endif // BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING) + +constexpr size_t kStartOfTextForTesting = 1000; +constexpr size_t kEndOfTextForTesting = kStartOfTextForTesting + 1000 * 1000; + +// Stop recording. Returns false if recording was already disabled. +bool Disable(); + +// CHECK()s that the offsets are correctly set up. +void SanityChecks(); + +// Switches to the next recording phase. If called from the last phase, dumps +// the data to disk, and returns |true|. |pid| is the current process pid, and +// |start_ns_since_epoch| the process start timestamp. +bool SwitchToNextPhaseOrDump(int pid, uint64_t start_ns_since_epoch); + +// Starts a thread to dump instrumentation after a delay. +void StartDelayedDump(); + +// Dumps all information for the current process, annotating the dump file name +// with the given tag. Will disable instrumentation. Instrumentation must be +// disabled before this is called. +void Dump(const std::string& tag); + +// Record an |address|, if recording is enabled. Only for testing. +void RecordAddressForTesting(size_t address); + +// Resets the state. Only for testing. +void ResetForTesting(); + +// Returns an ordered list of reached offsets. Only for testing. +std::vector<size_t> GetOrderedOffsetsForTesting(); +} // namespace orderfile +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_ORDERFILE_ORDERFILE_INSTRUMENTATION_H_
diff --git a/src/base/android/orderfile/orderfile_instrumentation_perftest.cc b/src/base/android/orderfile/orderfile_instrumentation_perftest.cc new file mode 100644 index 0000000..e1a69c9 --- /dev/null +++ b/src/base/android/orderfile/orderfile_instrumentation_perftest.cc
@@ -0,0 +1,135 @@ +// Copyright 2017 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. + +#include "base/android/orderfile/orderfile_instrumentation.h" + +#include <thread> + +#include "base/android/library_loader/anchor_functions.h" +#include "base/strings/stringprintf.h" +#include "base/time/time.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/perf/perf_test.h" + +namespace base { +namespace android { +namespace orderfile { + +namespace { + +// Records |addresses_count| distinct addresses |iterations| times, in +// |threads|. +void RunBenchmark(int iterations, int addresses_count, int threads) { + ResetForTesting(); + auto iterate = [iterations, addresses_count]() { + for (int i = 0; i < iterations; i++) { + for (size_t addr = kStartOfTextForTesting; + addr < static_cast<size_t>(addresses_count); addr += sizeof(int)) { + RecordAddressForTesting(addr); + } + } + }; + if (threads != 1) { + for (int i = 0; i < threads - 1; ++i) + std::thread(iterate).detach(); + } + auto tick = base::TimeTicks::Now(); + iterate(); + auto tock = base::TimeTicks::Now(); + double nanos = static_cast<double>((tock - tick).InNanoseconds()); + auto ns_per_call = + nanos / (iterations * static_cast<double>(addresses_count)); + auto modifier = + base::StringPrintf("_%d_%d_%d", iterations, addresses_count, threads); + perf_test::PrintResult("RecordAddressCostPerCall", modifier, "", ns_per_call, + "ns", true); +} + +} // namespace + +class OrderfileInstrumentationTest : public ::testing::Test { + // Any tests need to run ResetForTesting() when they start. Because this + // perftest is built with instrumentation enabled, all code including + // ::testing::Test is instrumented. If ResetForTesting() is called earlier, + // for example in setUp(), any test harness code between setUp() and the + // actual test will change the instrumentation offset record in unpredictable + // ways and make these tests unreliable. +}; + +TEST_F(OrderfileInstrumentationTest, RecordOffset) { + ResetForTesting(); + size_t first = 1234, second = 1456; + RecordAddressForTesting(first); + RecordAddressForTesting(second); + RecordAddressForTesting(first); // No duplicates. + RecordAddressForTesting(first + 1); // 4 bytes granularity. + Disable(); + + auto reached = GetOrderedOffsetsForTesting(); + EXPECT_EQ(2UL, reached.size()); + EXPECT_EQ(first - kStartOfTextForTesting, reached[0]); + EXPECT_EQ(second - kStartOfTextForTesting, reached[1]); +} + +TEST_F(OrderfileInstrumentationTest, RecordingStops) { + ResetForTesting(); + size_t first = 1234, second = 1456, third = 1789; + RecordAddressForTesting(first); + RecordAddressForTesting(second); + Disable(); + RecordAddressForTesting(third); + + auto reached = GetOrderedOffsetsForTesting(); + ASSERT_EQ(2UL, reached.size()); + ASSERT_EQ(first - kStartOfTextForTesting, reached[0]); + ASSERT_EQ(second - kStartOfTextForTesting, reached[1]); +} + +TEST_F(OrderfileInstrumentationTest, OutOfBounds) { + ResetForTesting(); + EXPECT_DEATH(RecordAddressForTesting(kEndOfTextForTesting + 100), ""); + EXPECT_DEATH(RecordAddressForTesting(kStartOfTextForTesting - 100), ""); +} + +TEST(OrderfileInstrumentationPerfTest, RecordAddress_10_10000) { + RunBenchmark(10, 10000, 1); +} + +TEST(OrderfileInstrumentationPerfTest, RecordAddress_100_10000) { + RunBenchmark(100, 10000, 1); +} + +TEST(OrderfileInstrumentationPerfTest, RecordAddress_10_100000) { + RunBenchmark(10, 100000, 1); +} + +TEST(OrderfileInstrumentationPerfTest, RecordAddress_100_100000) { + RunBenchmark(100, 100000, 1); +} + +TEST(OrderfileInstrumentationPerfTest, RecordAddress_1000_100000_2) { + RunBenchmark(1000, 100000, 2); +} + +TEST(OrderfileInstrumentationPerfTest, RecordAddress_1000_100000_3) { + RunBenchmark(1000, 100000, 3); +} + +TEST(OrderfileInstrumentationPerfTest, RecordAddress_1000_100000_4) { + RunBenchmark(1000, 100000, 4); +} + +TEST(OrderfileInstrumentationPerfTest, RecordAddress_1000_100000_6) { + RunBenchmark(1000, 100000, 6); +} + +} // namespace orderfile +} // namespace android +} // namespace base + +// Custom runner implementation since base's one requires JNI on Android. +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}
diff --git a/src/base/android/path_service_android.cc b/src/base/android/path_service_android.cc new file mode 100644 index 0000000..51be530 --- /dev/null +++ b/src/base/android/path_service_android.cc
@@ -0,0 +1,23 @@ +// Copyright (c) 2012 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. + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/files/file_path.h" +#include "base/path_service.h" +#include "jni/PathService_jni.h" + +namespace base { +namespace android { + +void JNI_PathService_Override(JNIEnv* env, + const JavaParamRef<jclass>& clazz, + jint what, + const JavaParamRef<jstring>& path) { + FilePath file_path(ConvertJavaStringToUTF8(env, path)); + PathService::Override(what, file_path); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/path_utils.cc b/src/base/android/path_utils.cc new file mode 100644 index 0000000..dfb68e6 --- /dev/null +++ b/src/base/android/path_utils.cc
@@ -0,0 +1,91 @@ +// Copyright (c) 2012 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. + +#include "base/android/path_utils.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_array.h" +#include "base/android/jni_string.h" +#include "base/android/scoped_java_ref.h" +#include "base/files/file_path.h" + +#include "jni/PathUtils_jni.h" + +namespace base { +namespace android { + +bool GetDataDirectory(FilePath* result) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> path = Java_PathUtils_getDataDirectory(env); + FilePath data_path(ConvertJavaStringToUTF8(path)); + *result = data_path; + return true; +} + +bool GetCacheDirectory(FilePath* result) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> path = Java_PathUtils_getCacheDirectory(env); + FilePath cache_path(ConvertJavaStringToUTF8(path)); + *result = cache_path; + return true; +} + +bool GetThumbnailCacheDirectory(FilePath* result) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> path = + Java_PathUtils_getThumbnailCacheDirectory(env); + FilePath thumbnail_cache_path(ConvertJavaStringToUTF8(path)); + *result = thumbnail_cache_path; + return true; +} + +bool GetDownloadsDirectory(FilePath* result) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> path = Java_PathUtils_getDownloadsDirectory(env); + FilePath downloads_path(ConvertJavaStringToUTF8(path)); + *result = downloads_path; + return true; +} + +std::vector<FilePath> GetAllPrivateDownloadsDirectories() { + std::vector<std::string> dirs; + JNIEnv* env = AttachCurrentThread(); + auto jarray = Java_PathUtils_getAllPrivateDownloadsDirectories(env); + base::android::AppendJavaStringArrayToStringVector(env, jarray.obj(), &dirs); + + std::vector<base::FilePath> file_paths; + for (const auto& dir : dirs) + file_paths.emplace_back(dir); + return file_paths; +} + +bool GetNativeLibraryDirectory(FilePath* result) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> path = + Java_PathUtils_getNativeLibraryDirectory(env); + FilePath library_path(ConvertJavaStringToUTF8(path)); + *result = library_path; + return true; +} + +bool GetExternalStorageDirectory(FilePath* result) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> path = + Java_PathUtils_getExternalStorageDirectory(env); + FilePath storage_path(ConvertJavaStringToUTF8(path)); + *result = storage_path; + return true; +} + +bool GetPathToBaseApk(FilePath* result) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> path = + Java_PathUtils_getPathToBaseApk(env); + FilePath apk_path(ConvertJavaStringToUTF8(path)); + *result = apk_path; + return true; +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/path_utils.h b/src/base/android/path_utils.h new file mode 100644 index 0000000..93a4cf1 --- /dev/null +++ b/src/base/android/path_utils.h
@@ -0,0 +1,60 @@ +// Copyright (c) 2012 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_ANDROID_PATH_UTILS_H_ +#define BASE_ANDROID_PATH_UTILS_H_ + +#include <jni.h> +#include <vector> + +#include "base/base_export.h" +#include "starboard/types.h" + +namespace base { + +class FilePath; + +namespace android { + +// Retrieves the absolute path to the data directory of the current +// application. The result is placed in the FilePath pointed to by 'result'. +// This method is dedicated for base_paths_android.c, Using +// PathService::Get(base::DIR_ANDROID_APP_DATA, ...) gets the data dir. +BASE_EXPORT bool GetDataDirectory(FilePath* result); + +// Retrieves the absolute path to the cache directory. The result is placed in +// the FilePath pointed to by 'result'. This method is dedicated for +// base_paths_android.c, Using PathService::Get(base::DIR_CACHE, ...) gets the +// cache dir. +BASE_EXPORT bool GetCacheDirectory(FilePath* result); + +// Retrieves the path to the thumbnail cache directory. The result is placed +// in the FilePath pointed to by 'result'. +BASE_EXPORT bool GetThumbnailCacheDirectory(FilePath* result); + +// Retrieves the path to the public downloads directory. The result is placed +// in the FilePath pointed to by 'result'. +BASE_EXPORT bool GetDownloadsDirectory(FilePath* result); + +// Retrieves the paths to all download directories, including default storage +// directory, and a private directory on external SD card. +BASE_EXPORT std::vector<FilePath> GetAllPrivateDownloadsDirectories(); + +// Retrieves the path to the native JNI libraries via +// ApplicationInfo.nativeLibraryDir on the Java side. The result is placed in +// the FilePath pointed to by 'result'. +BASE_EXPORT bool GetNativeLibraryDirectory(FilePath* result); + +// Retrieves the absolute path to the external storage directory. The result +// is placed in the FilePath pointed to by 'result'. +BASE_EXPORT bool GetExternalStorageDirectory(FilePath* result); + +// Retrieves the absolute path the base APK. The result is placed in the +// FilePath pointed to by 'result'. +BASE_EXPORT bool GetPathToBaseApk(FilePath* result); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_PATH_UTILS_H_
diff --git a/src/base/android/path_utils_unittest.cc b/src/base/android/path_utils_unittest.cc new file mode 100644 index 0000000..dca8ca1 --- /dev/null +++ b/src/base/android/path_utils_unittest.cc
@@ -0,0 +1,65 @@ +// Copyright (c) 2012 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. + +#include "base/android/path_utils.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/strings/string_util.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +typedef testing::Test PathUtilsTest; + +namespace { +void ExpectEither(const std::string& expected1, + const std::string& expected2, + const std::string& actual) { + EXPECT_TRUE(expected1 == actual || expected2 == actual) + << "Value of: " << actual << std::endl + << "Expected either: " << expected1 << std::endl + << "or: " << expected2; +} +} // namespace + +TEST_F(PathUtilsTest, TestGetDataDirectory) { + // The string comes from the Java side and depends on the APK + // we are running in. Assumes that we are packaged in + // org.chromium.native_test + FilePath path; + GetDataDirectory(&path); + + ExpectEither("/data/data/org.chromium.native_test/app_chrome", + "/data/user/0/org.chromium.native_test/app_chrome", + path.value()); +} + +TEST_F(PathUtilsTest, TestGetCacheDirectory) { + // The string comes from the Java side and depends on the APK + // we are running in. Assumes that we are packaged in + // org.chromium.native_test + FilePath path; + GetCacheDirectory(&path); + ExpectEither("/data/data/org.chromium.native_test/cache", + "/data/user/0/org.chromium.native_test/cache", + path.value()); +} + +TEST_F(PathUtilsTest, TestGetNativeLibraryDirectory) { + // The string comes from the Java side and depends on the APK + // we are running in. Assumes that the directory contains + // the base tests shared object. + FilePath path; + GetNativeLibraryDirectory(&path); + EXPECT_TRUE( + base::PathExists(path.Append("libbase_unittests.so")) || + base::PathExists(path.Append("libbase_unittests.cr.so")) || + base::PathExists(path.Append("lib_base_unittests__library.so")) || + base::PathExists(path.Append("lib_base_unittests__library.cr.so"))); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/proguard/chromium_apk.flags b/src/base/android/proguard/chromium_apk.flags new file mode 100644 index 0000000..ac3d7f8 --- /dev/null +++ b/src/base/android/proguard/chromium_apk.flags
@@ -0,0 +1,65 @@ +# Copyright 2016 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. + +# Contains flags that we'd like all Chromium .apks to use. + +# Not needed for Android and saves a bit of processing time. +-dontpreverify + +# Keep line number information, useful for stack traces. +-keepattributes SourceFile,LineNumberTable + +# Keep all CREATOR fields within Parcelable that are kept. +-keepclassmembers class * implements android.os.Parcelable { + public static *** CREATOR; +} + +# Don't obfuscate Parcelables as they might be marshalled outside Chrome. +# If we annotated all Parcelables that get put into Bundles other than +# for saveInstanceState (e.g. PendingIntents), then we could actually keep the +# names of just those ones. For now, we'll just keep them all. +-keepnames class * implements android.os.Parcelable + +# Keep all enum values and valueOf methods. See +# http://proguard.sourceforge.net/index.html#manual/examples.html +# for the reason for this. Also, see http://crbug.com/248037. +-keepclassmembers enum * { + public static **[] values(); +} + +# Keep classes implementing ParameterProvider -- these will be instantiated +# via reflection. +-keep class * implements org.chromium.base.test.params.ParameterProvider + +# Allows Proguard freedom in removing these log related calls. We ask for debug +# and verbose logs to be stripped out in base.Log, so we are just ensuring we +# get rid of all other debug/verbose logs. +-assumenosideeffects class android.util.Log { + static *** d(...); + static *** v(...); + static *** isLoggable(...); +} + +# The following chart was created on July 20, 2016, to decide on 3 optimization +# passes for Chrome. +# optimization passes | time | .dex size | dirty memory per process +# ----------------------------------------------------------------- +# 1 | 0:48 | 5805676 | 488972 +# 2 | 1:07 | 5777376 | 487092 +# 3 | 1:24 | 5772192 | 486596 +# 4 | 1:42 | 5771124 | 486484 +# 5 | 1:56 | 5770504 | 486432 +-optimizationpasses 3 + +# Horizontal class merging marginally increases dex size (as of Mar 2018). +-optimizations !class/merging/horizontal + +# Allowing Proguard to change modifiers. This change shrinks the .dex size by +# ~1%, and reduces the method count by ~4%. +-allowaccessmodification + +# The support library contains references to newer platform versions. +# Don't warn about those in case this app is linking against an older +# platform version. We know about them, and they are safe. +-dontwarn android.support.**
diff --git a/src/base/android/proguard/chromium_code.flags b/src/base/android/proguard/chromium_code.flags new file mode 100644 index 0000000..8a3ec58 --- /dev/null +++ b/src/base/android/proguard/chromium_code.flags
@@ -0,0 +1,75 @@ +# Copyright 2016 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. + +# Contains flags that can be safely shared with Cronet, and thus would be +# appropriate for third-party apps to include. + +# Keep all annotation related attributes that can affect runtime +-keepattributes RuntimeVisible*Annotations +-keepattributes AnnotationDefault + +# Keep the annotations, because if we don't, the ProGuard rules that use them +# will not be respected. These classes then show up in our final dex, which we +# do not want - see crbug.com/628226. +-keep @interface org.chromium.base.annotations.AccessedByNative +-keep @interface org.chromium.base.annotations.CalledByNative +-keep @interface org.chromium.base.annotations.CalledByNativeUnchecked +-keep @interface org.chromium.base.annotations.DoNotInline +-keep @interface org.chromium.base.annotations.RemovableInRelease +-keep @interface org.chromium.base.annotations.UsedByReflection + +# Keeps for class level annotations. +-keep @org.chromium.base.annotations.UsedByReflection class * + +# Keeps for method level annotations. +-keepclasseswithmembers class * { + @org.chromium.base.annotations.AccessedByNative <fields>; +} +-keepclasseswithmembers,includedescriptorclasses class * { + @org.chromium.base.annotations.CalledByNative <methods>; +} +-keepclasseswithmembers,includedescriptorclasses class * { + @org.chromium.base.annotations.CalledByNativeUnchecked <methods>; +} +-keepclasseswithmembers class * { + @org.chromium.base.annotations.UsedByReflection <methods>; +} +-keepclasseswithmembers class * { + @org.chromium.base.annotations.UsedByReflection <fields>; +} +-keepclasseswithmembers,includedescriptorclasses class * { + native <methods>; +} + +# Remove methods annotated with this if their return value is unused. +-assumenosideeffects class ** { + @org.chromium.base.annotations.RemovableInRelease <methods>; +} + +# Never inline classes or methods with this annotation, but allow shrinking and +# obfuscation. +-keepnames,allowobfuscation @org.chromium.base.annotations.DoNotInline class * { + *; +} +-keepclassmembernames,allowobfuscation class * { + @org.chromium.base.annotations.DoNotInline <methods>; +} + +# Keep all CREATOR fields within Parcelable that are kept. +-keepclassmembers class org.chromium.** implements android.os.Parcelable { + public static *** CREATOR; +} + +# Don't obfuscate Parcelables as they might be marshalled outside Chrome. +# If we annotated all Parcelables that get put into Bundles other than +# for saveInstanceState (e.g. PendingIntents), then we could actually keep the +# names of just those ones. For now, we'll just keep them all. +-keepnames class org.chromium.** implements android.os.Parcelable + +# Keep all enum values and valueOf methods. See +# http://proguard.sourceforge.net/index.html#manual/examples.html +# for the reason for this. Also, see http://crbug.com/248037. +-keepclassmembers enum org.chromium.** { + public static **[] values(); +}
diff --git a/src/base/android/proguard/disable_all_obfuscation.flags b/src/base/android/proguard/disable_all_obfuscation.flags new file mode 100644 index 0000000..deca250 --- /dev/null +++ b/src/base/android/proguard/disable_all_obfuscation.flags
@@ -0,0 +1,8 @@ +# Copyright 2016 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. + +# Disables obfuscation while still allowing optimizations. +-keepnames,allowoptimization class *** { + *; +}
diff --git a/src/base/android/proguard/disable_chromium_obfuscation.flags b/src/base/android/proguard/disable_chromium_obfuscation.flags new file mode 100644 index 0000000..b410239 --- /dev/null +++ b/src/base/android/proguard/disable_chromium_obfuscation.flags
@@ -0,0 +1,8 @@ +# Copyright 2016 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. + +# Disables obfuscation for chromium packages. +-keepnames,allowoptimization class com.google.android.apps.chrome.**,org.chromium.** { + *; +}
diff --git a/src/base/android/proguard/enable_obfuscation.flags b/src/base/android/proguard/enable_obfuscation.flags new file mode 100644 index 0000000..030d77f --- /dev/null +++ b/src/base/android/proguard/enable_obfuscation.flags
@@ -0,0 +1,8 @@ +# Copyright 2016 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. + +# As of August 11, 2016, obfuscation was found to save 660kb on our .dex size +# and 53kb memory/process (through shrinking method/string counts). +-renamesourcefileattribute PG +-repackageclasses ''
diff --git a/src/base/android/proguard/proguard.gni b/src/base/android/proguard/proguard.gni new file mode 100644 index 0000000..1e78bc0 --- /dev/null +++ b/src/base/android/proguard/proguard.gni
@@ -0,0 +1,9 @@ +# Copyright 2018 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. + +declare_args() { + # Controls whether proguard obfuscation is enabled for targets + # configured to use it. + enable_proguard_obfuscation = true +}
diff --git a/src/base/android/record_histogram.cc b/src/base/android/record_histogram.cc new file mode 100644 index 0000000..4451594 --- /dev/null +++ b/src/base/android/record_histogram.cc
@@ -0,0 +1,342 @@ +// Copyright 2014 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. + +#include <map> +#include <string> + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/lazy_instance.h" +#include "base/macros.h" +#include "base/metrics/histogram.h" +#include "base/metrics/sparse_histogram.h" +#include "base/metrics/statistics_recorder.h" +#include "base/strings/stringprintf.h" +#include "base/synchronization/lock.h" +#include "base/time/time.h" +#include "jni/RecordHistogram_jni.h" +#include "starboard/types.h" + +namespace base { +namespace android { +namespace { + +// Simple thread-safe wrapper for caching histograms. This avoids +// relatively expensive JNI string translation for each recording. +class HistogramCache { + public: + HistogramCache() {} + + std::string HistogramConstructionParamsToString(HistogramBase* histogram) { + std::string params_str = histogram->histogram_name(); + switch (histogram->GetHistogramType()) { + case HISTOGRAM: + case LINEAR_HISTOGRAM: + case BOOLEAN_HISTOGRAM: + case CUSTOM_HISTOGRAM: { + Histogram* hist = static_cast<Histogram*>(histogram); + params_str += StringPrintf("/%d/%d/%d", hist->declared_min(), + hist->declared_max(), hist->bucket_count()); + break; + } + case SPARSE_HISTOGRAM: + case DUMMY_HISTOGRAM: + break; + } + return params_str; + } + + void JNI_RecordHistogram_CheckHistogramArgs(JNIEnv* env, + jstring j_histogram_name, + int32_t expected_min, + int32_t expected_max, + uint32_t expected_bucket_count, + HistogramBase* histogram) { + std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name); + bool valid_arguments = Histogram::InspectConstructionArguments( + histogram_name, &expected_min, &expected_max, &expected_bucket_count); + DCHECK(valid_arguments); + DCHECK(histogram->HasConstructionArguments(expected_min, expected_max, + expected_bucket_count)) + << histogram_name << "/" << expected_min << "/" << expected_max << "/" + << expected_bucket_count << " vs. " + << HistogramConstructionParamsToString(histogram); + } + + HistogramBase* JNI_RecordHistogram_BooleanHistogram(JNIEnv* env, + jstring j_histogram_name, + jlong j_histogram_key) { + DCHECK(j_histogram_name); + HistogramBase* histogram = HistogramFromKey(j_histogram_key); + if (histogram) + return histogram; + + std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name); + histogram = BooleanHistogram::FactoryGet( + histogram_name, HistogramBase::kUmaTargetedHistogramFlag); + return histogram; + } + + HistogramBase* JNI_RecordHistogram_EnumeratedHistogram( + JNIEnv* env, + jstring j_histogram_name, + jlong j_histogram_key, + jint j_boundary) { + DCHECK(j_histogram_name); + HistogramBase* histogram = HistogramFromKey(j_histogram_key); + int32_t boundary = static_cast<int32_t>(j_boundary); + if (histogram) { + JNI_RecordHistogram_CheckHistogramArgs(env, j_histogram_name, 1, boundary, + boundary + 1, histogram); + return histogram; + } + + std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name); + histogram = + LinearHistogram::FactoryGet(histogram_name, 1, boundary, boundary + 1, + HistogramBase::kUmaTargetedHistogramFlag); + return histogram; + } + + HistogramBase* JNI_RecordHistogram_CustomCountHistogram( + JNIEnv* env, + jstring j_histogram_name, + jlong j_histogram_key, + jint j_min, + jint j_max, + jint j_num_buckets) { + DCHECK(j_histogram_name); + int32_t min = static_cast<int32_t>(j_min); + int32_t max = static_cast<int32_t>(j_max); + int32_t num_buckets = static_cast<int32_t>(j_num_buckets); + HistogramBase* histogram = HistogramFromKey(j_histogram_key); + if (histogram) { + JNI_RecordHistogram_CheckHistogramArgs(env, j_histogram_name, min, max, + num_buckets, histogram); + return histogram; + } + + DCHECK_GE(min, 1) << "The min expected sample must be >= 1"; + + std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name); + histogram = + Histogram::FactoryGet(histogram_name, min, max, num_buckets, + HistogramBase::kUmaTargetedHistogramFlag); + return histogram; + } + + HistogramBase* JNI_RecordHistogram_LinearCountHistogram( + JNIEnv* env, + jstring j_histogram_name, + jlong j_histogram_key, + jint j_min, + jint j_max, + jint j_num_buckets) { + DCHECK(j_histogram_name); + int32_t min = static_cast<int32_t>(j_min); + int32_t max = static_cast<int32_t>(j_max); + int32_t num_buckets = static_cast<int32_t>(j_num_buckets); + HistogramBase* histogram = HistogramFromKey(j_histogram_key); + if (histogram) { + JNI_RecordHistogram_CheckHistogramArgs(env, j_histogram_name, min, max, + num_buckets, histogram); + return histogram; + } + + std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name); + histogram = + LinearHistogram::FactoryGet(histogram_name, min, max, num_buckets, + HistogramBase::kUmaTargetedHistogramFlag); + return histogram; + } + + HistogramBase* JNI_RecordHistogram_SparseHistogram(JNIEnv* env, + jstring j_histogram_name, + jlong j_histogram_key) { + DCHECK(j_histogram_name); + HistogramBase* histogram = HistogramFromKey(j_histogram_key); + if (histogram) + return histogram; + + std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name); + histogram = SparseHistogram::FactoryGet( + histogram_name, HistogramBase::kUmaTargetedHistogramFlag); + return histogram; + } + + HistogramBase* JNI_RecordHistogram_CustomTimesHistogram( + JNIEnv* env, + jstring j_histogram_name, + jlong j_histogram_key, + jint j_min, + jint j_max, + jint j_bucket_count) { + DCHECK(j_histogram_name); + HistogramBase* histogram = HistogramFromKey(j_histogram_key); + int32_t min = static_cast<int32_t>(j_min); + int32_t max = static_cast<int32_t>(j_max); + int32_t bucket_count = static_cast<int32_t>(j_bucket_count); + if (histogram) { + JNI_RecordHistogram_CheckHistogramArgs(env, j_histogram_name, min, max, + bucket_count, histogram); + return histogram; + } + + std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name); + // This intentionally uses FactoryGet and not FactoryTimeGet. FactoryTimeGet + // is just a convenience for constructing the underlying Histogram with + // TimeDelta arguments. + histogram = Histogram::FactoryGet(histogram_name, min, max, bucket_count, + HistogramBase::kUmaTargetedHistogramFlag); + return histogram; + } + + private: + // Convert a jlong |histogram_key| from Java to a HistogramBase* via a cast. + // The Java side caches these in a map (see RecordHistogram.java), which is + // safe to do since C++ Histogram objects are never freed. + static HistogramBase* HistogramFromKey(jlong j_histogram_key) { + return reinterpret_cast<HistogramBase*>(j_histogram_key); + } + + DISALLOW_COPY_AND_ASSIGN(HistogramCache); +}; + +LazyInstance<HistogramCache>::Leaky g_histograms; + +} // namespace + +jlong JNI_RecordHistogram_RecordBooleanHistogram( + JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jstring>& j_histogram_name, + jlong j_histogram_key, + jboolean j_sample) { + bool sample = static_cast<bool>(j_sample); + HistogramBase* histogram = + g_histograms.Get().JNI_RecordHistogram_BooleanHistogram( + env, j_histogram_name, j_histogram_key); + histogram->AddBoolean(sample); + return reinterpret_cast<jlong>(histogram); +} + +jlong JNI_RecordHistogram_RecordEnumeratedHistogram( + JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jstring>& j_histogram_name, + jlong j_histogram_key, + jint j_sample, + jint j_boundary) { + int sample = static_cast<int>(j_sample); + + HistogramBase* histogram = + g_histograms.Get().JNI_RecordHistogram_EnumeratedHistogram( + env, j_histogram_name, j_histogram_key, j_boundary); + histogram->Add(sample); + return reinterpret_cast<jlong>(histogram); +} + +jlong JNI_RecordHistogram_RecordCustomCountHistogram( + JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jstring>& j_histogram_name, + jlong j_histogram_key, + jint j_sample, + jint j_min, + jint j_max, + jint j_num_buckets) { + int sample = static_cast<int>(j_sample); + + HistogramBase* histogram = + g_histograms.Get().JNI_RecordHistogram_CustomCountHistogram( + env, j_histogram_name, j_histogram_key, j_min, j_max, j_num_buckets); + histogram->Add(sample); + return reinterpret_cast<jlong>(histogram); +} + +jlong JNI_RecordHistogram_RecordLinearCountHistogram( + JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jstring>& j_histogram_name, + jlong j_histogram_key, + jint j_sample, + jint j_min, + jint j_max, + jint j_num_buckets) { + int sample = static_cast<int>(j_sample); + + HistogramBase* histogram = + g_histograms.Get().JNI_RecordHistogram_LinearCountHistogram( + env, j_histogram_name, j_histogram_key, j_min, j_max, j_num_buckets); + histogram->Add(sample); + return reinterpret_cast<jlong>(histogram); +} + +jlong JNI_RecordHistogram_RecordSparseHistogram( + JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jstring>& j_histogram_name, + jlong j_histogram_key, + jint j_sample) { + int sample = static_cast<int>(j_sample); + HistogramBase* histogram = + g_histograms.Get().JNI_RecordHistogram_SparseHistogram( + env, j_histogram_name, j_histogram_key); + histogram->Add(sample); + return reinterpret_cast<jlong>(histogram); +} + +jlong JNI_RecordHistogram_RecordCustomTimesHistogramMilliseconds( + JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jstring>& j_histogram_name, + jlong j_histogram_key, + jint j_duration, + jint j_min, + jint j_max, + jint j_num_buckets) { + HistogramBase* histogram = + g_histograms.Get().JNI_RecordHistogram_CustomTimesHistogram( + env, j_histogram_name, j_histogram_key, j_min, j_max, j_num_buckets); + histogram->AddTime( + TimeDelta::FromMilliseconds(static_cast<int64_t>(j_duration))); + return reinterpret_cast<jlong>(histogram); +} + +// This backs a Java test util for testing histograms - +// MetricsUtils.HistogramDelta. It should live in a test-specific file, but we +// currently can't have test-specific native code packaged in test-specific Java +// targets - see http://crbug.com/415945. +jint JNI_RecordHistogram_GetHistogramValueCountForTesting( + JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jstring>& histogram_name, + jint sample) { + HistogramBase* histogram = StatisticsRecorder::FindHistogram( + android::ConvertJavaStringToUTF8(env, histogram_name)); + if (histogram == nullptr) { + // No samples have been recorded for this histogram (yet?). + return 0; + } + + std::unique_ptr<HistogramSamples> samples = histogram->SnapshotSamples(); + return samples->GetCount(static_cast<int>(sample)); +} + +jint JNI_RecordHistogram_GetHistogramTotalCountForTesting( + JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jstring>& histogram_name) { + HistogramBase* histogram = StatisticsRecorder::FindHistogram( + android::ConvertJavaStringToUTF8(env, histogram_name)); + if (histogram == nullptr) { + // No samples have been recorded for this histogram. + return 0; + } + + return histogram->SnapshotSamples()->TotalCount(); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/record_user_action.cc b/src/base/android/record_user_action.cc new file mode 100644 index 0000000..683add6 --- /dev/null +++ b/src/base/android/record_user_action.cc
@@ -0,0 +1,59 @@ +// Copyright 2015 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. + +#include "base/android/jni_string.h" +#include "base/bind.h" +#include "base/callback.h" +#include "base/metrics/user_metrics.h" +#include "jni/RecordUserAction_jni.h" + +namespace { + +struct ActionCallbackWrapper { + base::ActionCallback action_callback; +}; + +} // namespace + +namespace base { +namespace android { + +static void JNI_RecordUserAction_RecordUserAction( + JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jstring>& j_action) { + RecordComputedAction(ConvertJavaStringToUTF8(env, j_action)); +} + +static void OnActionRecorded(const JavaRef<jobject>& callback, + const std::string& action) { + JNIEnv* env = AttachCurrentThread(); + Java_UserActionCallback_onActionRecorded( + env, callback, ConvertUTF8ToJavaString(env, action)); +} + +static jlong JNI_RecordUserAction_AddActionCallbackForTesting( + JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jobject>& callback) { + // Create a wrapper for the ActionCallback, so it can life on the heap until + // RemoveActionCallbackForTesting() is called. + auto* wrapper = new ActionCallbackWrapper{base::Bind( + &OnActionRecorded, ScopedJavaGlobalRef<jobject>(env, callback))}; + base::AddActionCallback(wrapper->action_callback); + return reinterpret_cast<intptr_t>(wrapper); +} + +static void JNI_RecordUserAction_RemoveActionCallbackForTesting( + JNIEnv* env, + const JavaParamRef<jclass>& clazz, + jlong callback_id) { + DCHECK(callback_id); + auto* wrapper = reinterpret_cast<ActionCallbackWrapper*>(callback_id); + base::RemoveActionCallback(wrapper->action_callback); + delete wrapper; +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/scoped_hardware_buffer_handle.cc b/src/base/android/scoped_hardware_buffer_handle.cc new file mode 100644 index 0000000..315fba8 --- /dev/null +++ b/src/base/android/scoped_hardware_buffer_handle.cc
@@ -0,0 +1,121 @@ +// Copyright 2018 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. + +#include "base/android/scoped_hardware_buffer_handle.h" + +#include "base/android/android_hardware_buffer_compat.h" +#include "base/logging.h" +#include "base/posix/unix_domain_socket.h" + +namespace base { +namespace android { + +ScopedHardwareBufferHandle::ScopedHardwareBufferHandle() = default; + +ScopedHardwareBufferHandle::ScopedHardwareBufferHandle( + ScopedHardwareBufferHandle&& other) { + *this = std::move(other); +} + +ScopedHardwareBufferHandle::~ScopedHardwareBufferHandle() { + reset(); +} + +// static +ScopedHardwareBufferHandle ScopedHardwareBufferHandle::Adopt( + AHardwareBuffer* buffer) { + return ScopedHardwareBufferHandle(buffer); +} + +// static +ScopedHardwareBufferHandle ScopedHardwareBufferHandle::Create( + AHardwareBuffer* buffer) { + AndroidHardwareBufferCompat::GetInstance().Acquire(buffer); + return ScopedHardwareBufferHandle(buffer); +} + +ScopedHardwareBufferHandle& ScopedHardwareBufferHandle::operator=( + ScopedHardwareBufferHandle&& other) { + reset(); + std::swap(buffer_, other.buffer_); + return *this; +} + +bool ScopedHardwareBufferHandle::is_valid() const { + return buffer_ != nullptr; +} + +AHardwareBuffer* ScopedHardwareBufferHandle::get() const { + return buffer_; +} + +void ScopedHardwareBufferHandle::reset() { + if (buffer_) { + AndroidHardwareBufferCompat::GetInstance().Release(buffer_); + buffer_ = nullptr; + } +} + +AHardwareBuffer* ScopedHardwareBufferHandle::Take() { + AHardwareBuffer* buffer = nullptr; + std::swap(buffer, buffer_); + return buffer; +} + +ScopedHardwareBufferHandle ScopedHardwareBufferHandle::Clone() const { + DCHECK(buffer_); + AndroidHardwareBufferCompat::GetInstance().Acquire(buffer_); + return ScopedHardwareBufferHandle(buffer_); +} + +ScopedFD ScopedHardwareBufferHandle::SerializeAsFileDescriptor() const { + DCHECK(is_valid()); + + ScopedFD reader, writer; + if (!CreateSocketPair(&reader, &writer)) { + PLOG(ERROR) << "socketpair"; + return ScopedFD(); + } + + // NOTE: SendHandleToUnixSocket does NOT acquire or retain a reference to the + // buffer object. The caller is therefore responsible for ensuring that the + // buffer remains alive through the lifetime of this file descriptor. + int result = + AndroidHardwareBufferCompat::GetInstance().SendHandleToUnixSocket( + buffer_, writer.get()); + if (result < 0) { + PLOG(ERROR) << "send"; + return ScopedFD(); + } + + return reader; +} + +// static +ScopedHardwareBufferHandle +ScopedHardwareBufferHandle::DeserializeFromFileDescriptor(ScopedFD fd) { + DCHECK(fd.is_valid()); + DCHECK(AndroidHardwareBufferCompat::IsSupportAvailable()); + AHardwareBuffer* buffer = nullptr; + + // NOTE: Upon success, RecvHandleFromUnixSocket acquires a new reference to + // the AHardwareBuffer. + int result = + AndroidHardwareBufferCompat::GetInstance().RecvHandleFromUnixSocket( + fd.get(), &buffer); + if (result < 0) { + PLOG(ERROR) << "recv"; + return ScopedHardwareBufferHandle(); + } + + return ScopedHardwareBufferHandle(buffer); +} + +ScopedHardwareBufferHandle::ScopedHardwareBufferHandle(AHardwareBuffer* buffer) + : buffer_(buffer) { + DCHECK(AndroidHardwareBufferCompat::IsSupportAvailable()); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/scoped_hardware_buffer_handle.h b/src/base/android/scoped_hardware_buffer_handle.h new file mode 100644 index 0000000..6d2a7d5 --- /dev/null +++ b/src/base/android/scoped_hardware_buffer_handle.h
@@ -0,0 +1,90 @@ +// Copyright 2018 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_ANDROID_SCOPED_HARDWARE_BUFFER_HANDLE_H_ +#define BASE_ANDROID_SCOPED_HARDWARE_BUFFER_HANDLE_H_ + +#include "base/base_export.h" +#include "base/files/scoped_file.h" +#include "base/macros.h" + +extern "C" typedef struct AHardwareBuffer AHardwareBuffer; + +namespace base { +namespace android { + +// Owns a single reference to an AHardwareBuffer object. +class BASE_EXPORT ScopedHardwareBufferHandle { + public: + ScopedHardwareBufferHandle(); + + // Takes ownership of |other|'s buffer reference. Does NOT acquire a new one. + ScopedHardwareBufferHandle(ScopedHardwareBufferHandle&& other); + + // Releases this handle's reference to the underlying buffer object if still + // valid. + ~ScopedHardwareBufferHandle(); + + // Assumes ownership of an existing reference to |buffer|. This does NOT + // acquire a new reference. + static ScopedHardwareBufferHandle Adopt(AHardwareBuffer* buffer); + + // Adds a reference to |buffer| managed by this handle. + static ScopedHardwareBufferHandle Create(AHardwareBuffer* buffer); + + // Takes ownership of |other|'s buffer reference. Does NOT acquire a new one. + ScopedHardwareBufferHandle& operator=(ScopedHardwareBufferHandle&& other); + + bool is_valid() const; + + AHardwareBuffer* get() const; + + // Releases this handle's reference to the underlying buffer object if still + // valid. Invalidates this handle. + void reset(); + + // Passes implicit ownership of this handle's reference over to the caller, + // invalidating |this|. Returns the raw buffer handle. + // + // The caller is responsible for eventually releasing this reference to the + // buffer object. + AHardwareBuffer* Take() WARN_UNUSED_RESULT; + + // Creates a new handle with its own newly acquired reference to the + // underlying buffer object. |this| must be a valid handle. + ScopedHardwareBufferHandle Clone() const; + + // Consumes a handle and returns a file descriptor which can be used to + // transmit the handle over IPC. A subsequent receiver may use + // |DeserializeFromFileDescriptor()| to recover the buffer handle. + // + // NOTE: The returned file descriptor DOES NOT own a reference to the + // underlying AHardwareBuffer. When using this for IPC, the caller is + // responsible for retaining at least one reference to the buffer object to + // keep it alive while the descriptor is in transit. + ScopedFD SerializeAsFileDescriptor() const; + + // Consumes the supplied single-use file descriptor (which must have been + // returned by a previous call to |SerializeAsFileDescriptor()|, perhaps in + // a different process), and recovers an AHardwareBuffer object from it. + // + // This acquires a new reference to the AHardwareBuffer, with ownership passed + // to the caller via the returned ScopedHardwareBufferHandle. + static ScopedHardwareBufferHandle DeserializeFromFileDescriptor(ScopedFD fd) + WARN_UNUSED_RESULT; + + private: + // Assumes ownership of an existing reference to |buffer|. This does NOT + // acquire a new reference. + explicit ScopedHardwareBufferHandle(AHardwareBuffer* buffer); + + AHardwareBuffer* buffer_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(ScopedHardwareBufferHandle); +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_SCOPED_HARDWARE_BUFFER_HANDLE_H_
diff --git a/src/base/android/scoped_java_ref.cc b/src/base/android/scoped_java_ref.cc new file mode 100644 index 0000000..7d31a75 --- /dev/null +++ b/src/base/android/scoped_java_ref.cc
@@ -0,0 +1,92 @@ +// Copyright (c) 2012 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. + +#include "base/android/scoped_java_ref.h" + +#include "base/android/jni_android.h" +#include "base/logging.h" + +namespace base { +namespace android { +namespace { + +const int kDefaultLocalFrameCapacity = 16; + +} // namespace + +ScopedJavaLocalFrame::ScopedJavaLocalFrame(JNIEnv* env) : env_(env) { + int failed = env_->PushLocalFrame(kDefaultLocalFrameCapacity); + DCHECK(!failed); +} + +ScopedJavaLocalFrame::ScopedJavaLocalFrame(JNIEnv* env, int capacity) + : env_(env) { + int failed = env_->PushLocalFrame(capacity); + DCHECK(!failed); +} + +ScopedJavaLocalFrame::~ScopedJavaLocalFrame() { + env_->PopLocalFrame(nullptr); +} + +#if DCHECK_IS_ON() +// This constructor is inlined when DCHECKs are disabled; don't add anything +// else here. +JavaRef<jobject>::JavaRef(JNIEnv* env, jobject obj) : obj_(obj) { + if (obj) { + DCHECK(env && env->GetObjectRefType(obj) == JNILocalRefType); + } +} +#endif + +JNIEnv* JavaRef<jobject>::SetNewLocalRef(JNIEnv* env, jobject obj) { + if (!env) { + env = AttachCurrentThread(); + } else { + DCHECK_EQ(env, AttachCurrentThread()); // Is |env| on correct thread. + } + if (obj) + obj = env->NewLocalRef(obj); + if (obj_) + env->DeleteLocalRef(obj_); + obj_ = obj; + return env; +} + +void JavaRef<jobject>::SetNewGlobalRef(JNIEnv* env, jobject obj) { + if (!env) { + env = AttachCurrentThread(); + } else { + DCHECK_EQ(env, AttachCurrentThread()); // Is |env| on correct thread. + } + if (obj) + obj = env->NewGlobalRef(obj); + if (obj_) + env->DeleteGlobalRef(obj_); + obj_ = obj; +} + +void JavaRef<jobject>::ResetLocalRef(JNIEnv* env) { + if (obj_) { + DCHECK_EQ(env, AttachCurrentThread()); // Is |env| on correct thread. + env->DeleteLocalRef(obj_); + obj_ = nullptr; + } +} + +void JavaRef<jobject>::ResetGlobalRef() { + if (obj_) { + AttachCurrentThread()->DeleteGlobalRef(obj_); + obj_ = nullptr; + } +} + +jobject JavaRef<jobject>::ReleaseInternal() { + jobject obj = obj_; + obj_ = nullptr; + return obj; +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/scoped_java_ref.h b/src/base/android/scoped_java_ref.h new file mode 100644 index 0000000..9e596a8 --- /dev/null +++ b/src/base/android/scoped_java_ref.h
@@ -0,0 +1,285 @@ +// Copyright (c) 2012 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_ANDROID_SCOPED_JAVA_REF_H_ +#define BASE_ANDROID_SCOPED_JAVA_REF_H_ + +#include <jni.h> + +#include <type_traits> +#include <utility> + +#include "base/base_export.h" +#include "base/logging.h" +#include "base/macros.h" +#include "starboard/types.h" + +namespace base { +namespace android { + +// Creates a new local reference frame, in which at least a given number of +// local references can be created. Note that local references already created +// in previous local frames are still valid in the current local frame. +class BASE_EXPORT ScopedJavaLocalFrame { + public: + explicit ScopedJavaLocalFrame(JNIEnv* env); + ScopedJavaLocalFrame(JNIEnv* env, int capacity); + ~ScopedJavaLocalFrame(); + + private: + // This class is only good for use on the thread it was created on so + // it's safe to cache the non-threadsafe JNIEnv* inside this object. + JNIEnv* env_; + + DISALLOW_COPY_AND_ASSIGN(ScopedJavaLocalFrame); +}; + +// Forward declare the generic java reference template class. +template<typename T> class JavaRef; + +// Template specialization of JavaRef, which acts as the base class for all +// other JavaRef<> template types. This allows you to e.g. pass +// ScopedJavaLocalRef<jstring> into a function taking const JavaRef<jobject>& +template<> +class BASE_EXPORT JavaRef<jobject> { + public: + // Initializes a null reference. Don't add anything else here; it's inlined. + constexpr JavaRef() : obj_(nullptr) {} + + // Allow nullptr to be converted to JavaRef. This avoids having to declare an + // empty JavaRef just to pass null to a function, and makes C++ "nullptr" and + // Java "null" equivalent. + constexpr JavaRef(std::nullptr_t) : JavaRef() {} + + // Public to allow destruction of null JavaRef objects. + // Don't add anything else here; it's inlined. + ~JavaRef() {} + + jobject obj() const { return obj_; } + + bool is_null() const { return obj_ == nullptr; } + + protected: + // Takes ownership of the |obj| reference passed; requires it to be a local + // reference type. +#if DCHECK_IS_ON() + // Implementation contains a DCHECK; implement out-of-line when DCHECK_IS_ON. + JavaRef(JNIEnv* env, jobject obj); +#else + // Don't add anything else here; it's inlined. + JavaRef(JNIEnv* env, jobject obj) : obj_(obj) {} +#endif + + void swap(JavaRef& other) { std::swap(obj_, other.obj_); } + + // The following are implementation detail convenience methods, for + // use by the sub-classes. + JNIEnv* SetNewLocalRef(JNIEnv* env, jobject obj); + void SetNewGlobalRef(JNIEnv* env, jobject obj); + void ResetLocalRef(JNIEnv* env); + void ResetGlobalRef(); + jobject ReleaseInternal(); + + private: + jobject obj_; + + DISALLOW_COPY_AND_ASSIGN(JavaRef); +}; + +// Generic base class for ScopedJavaLocalRef and ScopedJavaGlobalRef. Useful +// for allowing functions to accept a reference without having to mandate +// whether it is a local or global type. +template<typename T> +class JavaRef : public JavaRef<jobject> { + public: + JavaRef() {} + JavaRef(std::nullptr_t) : JavaRef<jobject>(nullptr) {} + ~JavaRef() {} + + T obj() const { return static_cast<T>(JavaRef<jobject>::obj()); } + + protected: + JavaRef(JNIEnv* env, T obj) : JavaRef<jobject>(env, obj) {} + + private: + DISALLOW_COPY_AND_ASSIGN(JavaRef); +}; + +// Holds a local reference to a JNI method parameter. +// Method parameters should not be deleted, and so this class exists purely to +// wrap them as a JavaRef<T> in the JNI binding generator. Do not create +// instances manually. +template<typename T> +class JavaParamRef : public JavaRef<T> { + public: + // Assumes that |obj| is a parameter passed to a JNI method from Java. + // Does not assume ownership as parameters should not be deleted. + JavaParamRef(JNIEnv* env, T obj) : JavaRef<T>(env, obj) {} + + // Allow nullptr to be converted to JavaParamRef. Some unit tests call JNI + // methods directly from C++ and pass null for objects which are not actually + // used by the implementation (e.g. the caller object); allow this to keep + // working. + JavaParamRef(std::nullptr_t) : JavaRef<T>(nullptr) {} + + ~JavaParamRef() {} + + // TODO(torne): remove this cast once we're using JavaRef consistently. + // http://crbug.com/506850 + operator T() const { return JavaRef<T>::obj(); } + + private: + DISALLOW_COPY_AND_ASSIGN(JavaParamRef); +}; + +// Holds a local reference to a Java object. The local reference is scoped +// to the lifetime of this object. +// Instances of this class may hold onto any JNIEnv passed into it until +// destroyed. Therefore, since a JNIEnv is only suitable for use on a single +// thread, objects of this class must be created, used, and destroyed, on a +// single thread. +// Therefore, this class should only be used as a stack-based object and from a +// single thread. If you wish to have the reference outlive the current +// callstack (e.g. as a class member) or you wish to pass it across threads, +// use a ScopedJavaGlobalRef instead. +template<typename T> +class ScopedJavaLocalRef : public JavaRef<T> { + public: + constexpr ScopedJavaLocalRef() : env_(nullptr) {} + constexpr ScopedJavaLocalRef(std::nullptr_t) : env_(nullptr) {} + + // Non-explicit copy constructor, to allow ScopedJavaLocalRef to be returned + // by value as this is the normal usage pattern. + ScopedJavaLocalRef(const ScopedJavaLocalRef<T>& other) + : env_(other.env_) { + this->SetNewLocalRef(env_, other.obj()); + } + + ScopedJavaLocalRef(ScopedJavaLocalRef<T>&& other) : env_(other.env_) { + this->swap(other); + } + + explicit ScopedJavaLocalRef(const JavaRef<T>& other) : env_(nullptr) { + this->Reset(other); + } + + // Assumes that |obj| is a local reference to a Java object and takes + // ownership of this local reference. + // TODO(torne): this shouldn't be used outside of JNI helper functions but + // there are currently some cases where there aren't helpers for things. + ScopedJavaLocalRef(JNIEnv* env, T obj) : JavaRef<T>(env, obj), env_(env) {} + + ~ScopedJavaLocalRef() { + this->Reset(); + } + + // Overloaded assignment operator defined for consistency with the implicit + // copy constructor. + void operator=(const ScopedJavaLocalRef<T>& other) { + this->Reset(other); + } + + void operator=(ScopedJavaLocalRef<T>&& other) { + env_ = other.env_; + this->swap(other); + } + + void Reset() { + this->ResetLocalRef(env_); + } + + void Reset(const ScopedJavaLocalRef<T>& other) { + // We can copy over env_ here as |other| instance must be from the same + // thread as |this| local ref. (See class comment for multi-threading + // limitations, and alternatives). + this->Reset(other.env_, other.obj()); + } + + void Reset(const JavaRef<T>& other) { + // If |env_| was not yet set (is still null) it will be attached to the + // current thread in SetNewLocalRef(). + this->Reset(env_, other.obj()); + } + + // Creates a new local reference to the Java object, unlike the constructor + // with the same parameters that takes ownership of the existing reference. + // TODO(torne): these should match as this is confusing. + void Reset(JNIEnv* env, T obj) { env_ = this->SetNewLocalRef(env, obj); } + + // Releases the local reference to the caller. The caller *must* delete the + // local reference when it is done with it. Note that calling a Java method + // is *not* a transfer of ownership and Release() should not be used. + T Release() { + return static_cast<T>(this->ReleaseInternal()); + } + + private: + // This class is only good for use on the thread it was created on so + // it's safe to cache the non-threadsafe JNIEnv* inside this object. + JNIEnv* env_; + + // Prevent ScopedJavaLocalRef(JNIEnv*, T obj) from being used to take + // ownership of a JavaParamRef's underlying object - parameters are not + // allowed to be deleted and so should not be owned by ScopedJavaLocalRef. + // TODO(torne): this can be removed once JavaParamRef no longer has an + // implicit conversion back to T. + ScopedJavaLocalRef(JNIEnv* env, const JavaParamRef<T>& other); +}; + +// Holds a global reference to a Java object. The global reference is scoped +// to the lifetime of this object. This class does not hold onto any JNIEnv* +// passed to it, hence it is safe to use across threads (within the constraints +// imposed by the underlying Java object that it references). +template<typename T> +class ScopedJavaGlobalRef : public JavaRef<T> { + public: + constexpr ScopedJavaGlobalRef() {} + constexpr ScopedJavaGlobalRef(std::nullptr_t) {} + + ScopedJavaGlobalRef(const ScopedJavaGlobalRef<T>& other) { + this->Reset(other); + } + + ScopedJavaGlobalRef(ScopedJavaGlobalRef<T>&& other) { this->swap(other); } + + ScopedJavaGlobalRef(JNIEnv* env, T obj) { this->Reset(env, obj); } + + explicit ScopedJavaGlobalRef(const JavaRef<T>& other) { this->Reset(other); } + + ~ScopedJavaGlobalRef() { + this->Reset(); + } + + // Overloaded assignment operator defined for consistency with the implicit + // copy constructor. + void operator=(const ScopedJavaGlobalRef<T>& other) { + this->Reset(other); + } + + void operator=(ScopedJavaGlobalRef<T>&& other) { this->swap(other); } + + void Reset() { + this->ResetGlobalRef(); + } + + void Reset(const JavaRef<T>& other) { this->Reset(nullptr, other.obj()); } + + void Reset(JNIEnv* env, const JavaParamRef<T>& other) { + this->Reset(env, other.obj()); + } + + void Reset(JNIEnv* env, T obj) { this->SetNewGlobalRef(env, obj); } + + // Releases the global reference to the caller. The caller *must* delete the + // global reference when it is done with it. Note that calling a Java method + // is *not* a transfer of ownership and Release() should not be used. + T Release() { + return static_cast<T>(this->ReleaseInternal()); + } +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_SCOPED_JAVA_REF_H_
diff --git a/src/base/android/scoped_java_ref_unittest.cc b/src/base/android/scoped_java_ref_unittest.cc new file mode 100644 index 0000000..99d035b --- /dev/null +++ b/src/base/android/scoped_java_ref_unittest.cc
@@ -0,0 +1,133 @@ +// Copyright (c) 2012 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. + +#include "base/android/scoped_java_ref.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +namespace { +int g_local_refs = 0; +int g_global_refs = 0; + +const JNINativeInterface* g_previous_functions; + +jobject NewGlobalRef(JNIEnv* env, jobject obj) { + ++g_global_refs; + return g_previous_functions->NewGlobalRef(env, obj); +} + +void DeleteGlobalRef(JNIEnv* env, jobject obj) { + --g_global_refs; + return g_previous_functions->DeleteGlobalRef(env, obj); +} + +jobject NewLocalRef(JNIEnv* env, jobject obj) { + ++g_local_refs; + return g_previous_functions->NewLocalRef(env, obj); +} + +void DeleteLocalRef(JNIEnv* env, jobject obj) { + --g_local_refs; + return g_previous_functions->DeleteLocalRef(env, obj); +} +} // namespace + +class ScopedJavaRefTest : public testing::Test { + protected: + void SetUp() override { + g_local_refs = 0; + g_global_refs = 0; + JNIEnv* env = AttachCurrentThread(); + g_previous_functions = env->functions; + hooked_functions = *g_previous_functions; + env->functions = &hooked_functions; + // We inject our own functions in JNINativeInterface so we can keep track + // of the reference counting ourselves. + hooked_functions.NewGlobalRef = &NewGlobalRef; + hooked_functions.DeleteGlobalRef = &DeleteGlobalRef; + hooked_functions.NewLocalRef = &NewLocalRef; + hooked_functions.DeleteLocalRef = &DeleteLocalRef; + } + + void TearDown() override { + JNIEnv* env = AttachCurrentThread(); + env->functions = g_previous_functions; + } + // From JellyBean release, the instance of this struct provided in JNIEnv is + // read-only, so we deep copy it to allow individual functions to be hooked. + JNINativeInterface hooked_functions; +}; + +// The main purpose of this is testing the various conversions compile. +TEST_F(ScopedJavaRefTest, Conversions) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> str = ConvertUTF8ToJavaString(env, "string"); + ScopedJavaGlobalRef<jstring> global(str); + { + ScopedJavaGlobalRef<jobject> global_obj(str); + ScopedJavaLocalRef<jobject> local_obj(global); + const JavaRef<jobject>& obj_ref1(str); + const JavaRef<jobject>& obj_ref2(global); + EXPECT_TRUE(env->IsSameObject(obj_ref1.obj(), obj_ref2.obj())); + EXPECT_TRUE(env->IsSameObject(global_obj.obj(), obj_ref2.obj())); + } + global.Reset(str); + const JavaRef<jstring>& str_ref = str; + EXPECT_EQ("string", ConvertJavaStringToUTF8(str_ref)); + str.Reset(); +} + +TEST_F(ScopedJavaRefTest, RefCounts) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> str; + // The ConvertJavaStringToUTF8 below creates a new string that would normally + // return a local ref. We simulate that by starting the g_local_refs count at + // 1. + g_local_refs = 1; + str.Reset(ConvertUTF8ToJavaString(env, "string")); + EXPECT_EQ(1, g_local_refs); + EXPECT_EQ(0, g_global_refs); + { + ScopedJavaGlobalRef<jstring> global_str(str); + ScopedJavaGlobalRef<jobject> global_obj(global_str); + EXPECT_EQ(1, g_local_refs); + EXPECT_EQ(2, g_global_refs); + + ScopedJavaLocalRef<jstring> str2(env, str.Release()); + EXPECT_EQ(1, g_local_refs); + { + ScopedJavaLocalRef<jstring> str3(str2); + EXPECT_EQ(2, g_local_refs); + } + EXPECT_EQ(1, g_local_refs); + { + ScopedJavaLocalRef<jstring> str4((ScopedJavaLocalRef<jstring>(str2))); + EXPECT_EQ(2, g_local_refs); + } + EXPECT_EQ(1, g_local_refs); + { + ScopedJavaLocalRef<jstring> str5; + str5 = ScopedJavaLocalRef<jstring>(str2); + EXPECT_EQ(2, g_local_refs); + } + EXPECT_EQ(1, g_local_refs); + str2.Reset(); + EXPECT_EQ(0, g_local_refs); + global_str.Reset(); + EXPECT_EQ(1, g_global_refs); + ScopedJavaGlobalRef<jobject> global_obj2(global_obj); + EXPECT_EQ(2, g_global_refs); + } + + EXPECT_EQ(0, g_local_refs); + EXPECT_EQ(0, g_global_refs); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/statistics_recorder_android.cc b/src/base/android/statistics_recorder_android.cc new file mode 100644 index 0000000..346a7c7 --- /dev/null +++ b/src/base/android/statistics_recorder_android.cc
@@ -0,0 +1,29 @@ +// Copyright 2016 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. + +#include <string> + +#include "base/android/jni_string.h" +#include "base/metrics/histogram_base.h" +#include "base/metrics/statistics_recorder.h" +#include "base/sys_info.h" +#include "jni/StatisticsRecorderAndroid_jni.h" + +using base::android::JavaParamRef; +using base::android::ConvertUTF8ToJavaString; + +namespace base { +namespace android { + +static ScopedJavaLocalRef<jstring> JNI_StatisticsRecorderAndroid_ToJson( + JNIEnv* env, + const JavaParamRef<jclass>& clazz, + jint verbosityLevel) { + return ConvertUTF8ToJavaString( + env, base::StatisticsRecorder::ToJSON( + static_cast<JSONVerbosityLevel>(verbosityLevel))); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/sys_utils.cc b/src/base/android/sys_utils.cc new file mode 100644 index 0000000..7872b2f --- /dev/null +++ b/src/base/android/sys_utils.cc
@@ -0,0 +1,51 @@ +// Copyright 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. + +#include "base/android/sys_utils.h" + +#include <memory> + +#include "base/android/build_info.h" +#include "base/process/process_metrics.h" +#include "base/sys_info.h" +#include "base/trace_event/trace_event.h" +#include "jni/SysUtils_jni.h" + +namespace base { +namespace android { + +bool SysUtils::IsLowEndDeviceFromJni() { + JNIEnv* env = AttachCurrentThread(); + return Java_SysUtils_isLowEndDevice(env); +} + +bool SysUtils::IsCurrentlyLowMemory() { + JNIEnv* env = AttachCurrentThread(); + return Java_SysUtils_isCurrentlyLowMemory(env); +} + +// Logs the number of minor / major page faults to tracing (and also the time to +// collect) the metrics. Does nothing if tracing is not enabled. +static void JNI_SysUtils_LogPageFaultCountToTracing( + JNIEnv* env, + const base::android::JavaParamRef<jclass>& jcaller) { + // This is racy, but we are OK losing data, and collecting it is potentially + // expensive (reading and parsing a file). + bool enabled; + TRACE_EVENT_CATEGORY_GROUP_ENABLED("startup", &enabled); + if (!enabled) + return; + TRACE_EVENT_BEGIN2("memory", "CollectPageFaultCount", "minor", 0, "major", 0); + std::unique_ptr<base::ProcessMetrics> process_metrics( + base::ProcessMetrics::CreateProcessMetrics( + base::GetCurrentProcessHandle())); + base::PageFaultCounts counts; + process_metrics->GetPageFaultCounts(&counts); + TRACE_EVENT_END2("memory", "CollectPageFaults", "minor", counts.minor, + "major", counts.major); +} + +} // namespace android + +} // namespace base
diff --git a/src/base/android/sys_utils.h b/src/base/android/sys_utils.h new file mode 100644 index 0000000..b1e368b --- /dev/null +++ b/src/base/android/sys_utils.h
@@ -0,0 +1,24 @@ +// Copyright 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_ANDROID_SYS_UTILS_H_ +#define BASE_ANDROID_SYS_UTILS_H_ + +#include "base/android/jni_android.h" + +namespace base { +namespace android { + +class BASE_EXPORT SysUtils { + public: + // Returns true iff this is a low-end device. + static bool IsLowEndDeviceFromJni(); + // Returns true if system has low available memory. + static bool IsCurrentlyLowMemory(); +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_SYS_UTILS_H_
diff --git a/src/base/android/sys_utils_unittest.cc b/src/base/android/sys_utils_unittest.cc new file mode 100644 index 0000000..d287d3e --- /dev/null +++ b/src/base/android/sys_utils_unittest.cc
@@ -0,0 +1,24 @@ +// Copyright 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. + +#include <unistd.h> + +#include "base/sys_info.h" +#include "starboard/types.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +TEST(SysUtils, AmountOfPhysicalMemory) { + // Check that the RAM size reported by sysconf() matches the one + // computed by base::SysInfo::AmountOfPhysicalMemory(). + size_t sys_ram_size = + static_cast<size_t>(sysconf(_SC_PHYS_PAGES) * PAGE_SIZE); + EXPECT_EQ(sys_ram_size, + static_cast<size_t>(SysInfo::AmountOfPhysicalMemory())); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/time_utils.cc b/src/base/android/time_utils.cc new file mode 100644 index 0000000..8794f54 --- /dev/null +++ b/src/base/android/time_utils.cc
@@ -0,0 +1,19 @@ +// Copyright 2016 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. + +#include "base/time/time.h" +#include "jni/TimeUtils_jni.h" +#include "starboard/types.h" + +namespace base { +namespace android { + +static jlong JNI_TimeUtils_GetTimeTicksNowUs( + JNIEnv* env, + const JavaParamRef<jclass>& clazz) { + return (TimeTicks::Now() - TimeTicks()).InMicroseconds(); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/timezone_utils.cc b/src/base/android/timezone_utils.cc new file mode 100644 index 0000000..5243cdc --- /dev/null +++ b/src/base/android/timezone_utils.cc
@@ -0,0 +1,23 @@ +// Copyright 2017 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. + +#include "base/android/timezone_utils.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/strings/string16.h" +#include "jni/TimezoneUtils_jni.h" + +namespace base { +namespace android { + +base::string16 GetDefaultTimeZoneId() { + JNIEnv* env = base::android::AttachCurrentThread(); + ScopedJavaLocalRef<jstring> timezone_id = + Java_TimezoneUtils_getDefaultTimeZoneId(env); + return ConvertJavaStringToUTF16(timezone_id); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/timezone_utils.h b/src/base/android/timezone_utils.h new file mode 100644 index 0000000..01cff2e --- /dev/null +++ b/src/base/android/timezone_utils.h
@@ -0,0 +1,23 @@ +// Copyright 2017 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_ANDROID_TIMEZONE_UTILS_H_ +#define BASE_ANDROID_TIMEZONE_UTILS_H_ + +#include <jni.h> + +#include "base/base_export.h" +#include "base/strings/string16.h" +#include "starboard/types.h" + +namespace base { +namespace android { + +// Return an ICU timezone created from the host timezone. +BASE_EXPORT base::string16 GetDefaultTimeZoneId(); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_TIMEZONE_UTILS_H_
diff --git a/src/base/android/trace_event_binding.cc b/src/base/android/trace_event_binding.cc new file mode 100644 index 0000000..a8b64e8 --- /dev/null +++ b/src/base/android/trace_event_binding.cc
@@ -0,0 +1,153 @@ +// Copyright 2014 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. + +#include <jni.h> + +#include <set> + +#include "base/android/jni_string.h" +#include "base/macros.h" +#include "base/trace_event/trace_event.h" +#include "base/trace_event/trace_event_impl.h" +#include "jni/TraceEvent_jni.h" +#include "starboard/types.h" + +namespace base { +namespace android { + +namespace { + +const char kJavaCategory[] = "Java"; +const char kToplevelCategory[] = "toplevel"; +const char kLooperDispatchMessage[] = "Looper.dispatchMessage"; + +// Boilerplate for safely converting Java data to TRACE_EVENT data. +class TraceEventDataConverter { + public: + TraceEventDataConverter(JNIEnv* env, jstring jname, jstring jarg) + : name_(ConvertJavaStringToUTF8(env, jname)), + has_arg_(jarg != nullptr), + arg_(jarg ? ConvertJavaStringToUTF8(env, jarg) : "") {} + ~TraceEventDataConverter() { + } + + // Return saves values to pass to TRACE_EVENT macros. + const char* name() { return name_.c_str(); } + const char* arg_name() { return has_arg_ ? "arg" : nullptr; } + const char* arg() { return has_arg_ ? arg_.c_str() : nullptr; } + + private: + std::string name_; + bool has_arg_; + std::string arg_; + + DISALLOW_COPY_AND_ASSIGN(TraceEventDataConverter); +}; + +class TraceEnabledObserver + : public trace_event::TraceLog::EnabledStateObserver { + public: + void OnTraceLogEnabled() override { + JNIEnv* env = base::android::AttachCurrentThread(); + base::android::Java_TraceEvent_setEnabled(env, true); + } + void OnTraceLogDisabled() override { + JNIEnv* env = base::android::AttachCurrentThread(); + base::android::Java_TraceEvent_setEnabled(env, false); + } +}; + +} // namespace + +static void JNI_TraceEvent_RegisterEnabledObserver( + JNIEnv* env, + const JavaParamRef<jclass>& clazz) { + bool enabled = trace_event::TraceLog::GetInstance()->IsEnabled(); + base::android::Java_TraceEvent_setEnabled(env, enabled); + trace_event::TraceLog::GetInstance()->AddOwnedEnabledStateObserver( + std::make_unique<TraceEnabledObserver>()); +} + +static void JNI_TraceEvent_StartATrace(JNIEnv* env, + const JavaParamRef<jclass>& clazz) { + base::trace_event::TraceLog::GetInstance()->StartATrace(); +} + +static void JNI_TraceEvent_StopATrace(JNIEnv* env, + const JavaParamRef<jclass>& clazz) { + base::trace_event::TraceLog::GetInstance()->StopATrace(); +} + +static void JNI_TraceEvent_Instant(JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jstring>& jname, + const JavaParamRef<jstring>& jarg) { + TraceEventDataConverter converter(env, jname, jarg); + if (converter.arg()) { + TRACE_EVENT_COPY_INSTANT1(kJavaCategory, converter.name(), + TRACE_EVENT_SCOPE_THREAD, + converter.arg_name(), converter.arg()); + } else { + TRACE_EVENT_COPY_INSTANT0(kJavaCategory, converter.name(), + TRACE_EVENT_SCOPE_THREAD); + } +} + +static void JNI_TraceEvent_Begin(JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jstring>& jname, + const JavaParamRef<jstring>& jarg) { + TraceEventDataConverter converter(env, jname, jarg); + if (converter.arg()) { + TRACE_EVENT_COPY_BEGIN1(kJavaCategory, converter.name(), + converter.arg_name(), converter.arg()); + } else { + TRACE_EVENT_COPY_BEGIN0(kJavaCategory, converter.name()); + } +} + +static void JNI_TraceEvent_End(JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jstring>& jname, + const JavaParamRef<jstring>& jarg) { + TraceEventDataConverter converter(env, jname, jarg); + if (converter.arg()) { + TRACE_EVENT_COPY_END1(kJavaCategory, converter.name(), + converter.arg_name(), converter.arg()); + } else { + TRACE_EVENT_COPY_END0(kJavaCategory, converter.name()); + } +} + +static void JNI_TraceEvent_BeginToplevel(JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jstring>& jtarget) { + std::string target = ConvertJavaStringToUTF8(env, jtarget); + TRACE_EVENT_BEGIN1(kToplevelCategory, kLooperDispatchMessage, "target", + target); +} + +static void JNI_TraceEvent_EndToplevel(JNIEnv* env, + const JavaParamRef<jclass>& clazz) { + TRACE_EVENT_END0(kToplevelCategory, kLooperDispatchMessage); +} + +static void JNI_TraceEvent_StartAsync(JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jstring>& jname, + jlong jid) { + TraceEventDataConverter converter(env, jname, nullptr); + TRACE_EVENT_COPY_ASYNC_BEGIN0(kJavaCategory, converter.name(), jid); +} + +static void JNI_TraceEvent_FinishAsync(JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jstring>& jname, + jlong jid) { + TraceEventDataConverter converter(env, jname, nullptr); + TRACE_EVENT_COPY_ASYNC_END0(kJavaCategory, converter.name(), jid); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/unguessable_token_android.cc b/src/base/android/unguessable_token_android.cc new file mode 100644 index 0000000..d041557 --- /dev/null +++ b/src/base/android/unguessable_token_android.cc
@@ -0,0 +1,41 @@ +// Copyright 2016 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. + +#include "base/android/unguessable_token_android.h" + +#include "jni/UnguessableToken_jni.h" + +namespace base { +namespace android { + +ScopedJavaLocalRef<jobject> UnguessableTokenAndroid::Create( + JNIEnv* env, + const base::UnguessableToken& token) { + const uint64_t high = token.GetHighForSerialization(); + const uint64_t low = token.GetLowForSerialization(); + DCHECK(high); + DCHECK(low); + return Java_UnguessableToken_create(env, high, low); +} + +base::UnguessableToken UnguessableTokenAndroid::FromJavaUnguessableToken( + JNIEnv* env, + const JavaRef<jobject>& token) { + const uint64_t high = + Java_UnguessableToken_getHighForSerialization(env, token); + const uint64_t low = Java_UnguessableToken_getLowForSerialization(env, token); + DCHECK(high); + DCHECK(low); + return base::UnguessableToken::Deserialize(high, low); +} + +ScopedJavaLocalRef<jobject> +UnguessableTokenAndroid::ParcelAndUnparcelForTesting( + JNIEnv* env, + const JavaRef<jobject>& token) { + return Java_UnguessableToken_parcelAndUnparcelForTesting(env, token); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/unguessable_token_android.h b/src/base/android/unguessable_token_android.h new file mode 100644 index 0000000..f1a612e --- /dev/null +++ b/src/base/android/unguessable_token_android.h
@@ -0,0 +1,44 @@ +// Copyright 2016 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_ANDROID_UNGUESSABLE_TOKEN_ANDROID_H_ +#define BASE_ANDROID_UNGUESSABLE_TOKEN_ANDROID_H_ + +#include <jni.h> + +#include "base/android/scoped_java_ref.h" +#include "base/base_export.h" +#include "base/unguessable_token.h" +#include "starboard/types.h" + +namespace base { +namespace android { + +class BASE_EXPORT UnguessableTokenAndroid { + public: + // Create a Java UnguessableToken with the same value as |token|. + static ScopedJavaLocalRef<jobject> Create( + JNIEnv* env, + const base::UnguessableToken& token); + + // Create a native UnguessableToken from Java UnguessableToken |token|. + static base::UnguessableToken FromJavaUnguessableToken( + JNIEnv* env, + const JavaRef<jobject>& token); + + // Parcel UnguessableToken |token| and unparcel it, and return the result. + // While this method is intended for facilitating unit tests, it results only + // in a clone of |token|. + static ScopedJavaLocalRef<jobject> ParcelAndUnparcelForTesting( + JNIEnv* env, + const JavaRef<jobject>& token); + + private: + DISALLOW_COPY_AND_ASSIGN(UnguessableTokenAndroid); +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_UNGUESSABLE_TOKEN_ANDROID_H_
diff --git a/src/base/android/unguessable_token_android_unittest.cc b/src/base/android/unguessable_token_android_unittest.cc new file mode 100644 index 0000000..bdad746 --- /dev/null +++ b/src/base/android/unguessable_token_android_unittest.cc
@@ -0,0 +1,42 @@ +// Copyright 2016 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. + +#include "base/android/unguessable_token_android.h" + +#include "base/android/jni_android.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +TEST(UnguessableTokenAndroid, BasicCreateToken) { + JNIEnv* env = AttachCurrentThread(); + uint64_t high = 0x1234567812345678; + uint64_t low = 0x0583503029282304; + base::UnguessableToken token = base::UnguessableToken::Deserialize(high, low); + ScopedJavaLocalRef<jobject> jtoken = + UnguessableTokenAndroid::Create(env, token); + base::UnguessableToken result = + UnguessableTokenAndroid::FromJavaUnguessableToken(env, jtoken); + + EXPECT_EQ(token, result); +} + +TEST(UnguessableTokenAndroid, ParcelAndUnparcel) { + JNIEnv* env = AttachCurrentThread(); + uint64_t high = 0x1234567812345678; + uint64_t low = 0x0583503029282304; + base::UnguessableToken token = base::UnguessableToken::Deserialize(high, low); + ScopedJavaLocalRef<jobject> jtoken = + UnguessableTokenAndroid::Create(env, token); + ScopedJavaLocalRef<jobject> jtoken_clone = + UnguessableTokenAndroid::ParcelAndUnparcelForTesting(env, jtoken); + base::UnguessableToken token_clone = + UnguessableTokenAndroid::FromJavaUnguessableToken(env, jtoken_clone); + + EXPECT_EQ(token, token_clone); +} + +} // namespace android +} // namespace base