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(), &region);
+  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(), &params);
+    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),
+                            &parameters);
+  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