summaryrefslogtreecommitdiff
path: root/base/android/java
diff options
context:
space:
mode:
Diffstat (limited to 'base/android/java')
-rw-r--r--base/android/java/src/org/chromium/base/ActivityState.java48
-rw-r--r--base/android/java/src/org/chromium/base/AnimationFrameTimeHistogram.java145
-rw-r--r--base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java705
-rw-r--r--base/android/java/src/org/chromium/base/ApkAssets.java58
-rw-r--r--base/android/java/src/org/chromium/base/ApplicationStatus.java620
-rw-r--r--base/android/java/src/org/chromium/base/BaseSwitches.java32
-rw-r--r--base/android/java/src/org/chromium/base/Callback.java43
-rw-r--r--base/android/java/src/org/chromium/base/CollectionUtil.java99
-rw-r--r--base/android/java/src/org/chromium/base/CommandLine.java389
-rw-r--r--base/android/java/src/org/chromium/base/CommandLineInitUtil.java103
-rw-r--r--base/android/java/src/org/chromium/base/ContentUriUtils.java251
-rw-r--r--base/android/java/src/org/chromium/base/CpuFeatures.java42
-rw-r--r--base/android/java/src/org/chromium/base/EarlyTraceEvent.java299
-rw-r--r--base/android/java/src/org/chromium/base/EventLog.java20
-rw-r--r--base/android/java/src/org/chromium/base/FieldTrialList.java46
-rw-r--r--base/android/java/src/org/chromium/base/FileUtils.java149
-rw-r--r--base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java31
-rw-r--r--base/android/java/src/org/chromium/base/JNIUtils.java46
-rw-r--r--base/android/java/src/org/chromium/base/JavaHandlerThread.java119
-rw-r--r--base/android/java/src/org/chromium/base/LocaleUtils.java207
-rw-r--r--base/android/java/src/org/chromium/base/MemoryPressureListener.java130
-rw-r--r--base/android/java/src/org/chromium/base/NonThreadSafe.java41
-rw-r--r--base/android/java/src/org/chromium/base/ObserverList.java249
-rw-r--r--base/android/java/src/org/chromium/base/PathService.java26
-rw-r--r--base/android/java/src/org/chromium/base/PathUtils.java263
-rw-r--r--base/android/java/src/org/chromium/base/PowerMonitor.java80
-rw-r--r--base/android/java/src/org/chromium/base/Promise.java294
-rw-r--r--base/android/java/src/org/chromium/base/SecureRandomInitializer.java35
-rw-r--r--base/android/java/src/org/chromium/base/StreamUtil.java28
-rw-r--r--base/android/java/src/org/chromium/base/SysUtils.java199
-rw-r--r--base/android/java/src/org/chromium/base/ThrowUncaughtException.java21
-rw-r--r--base/android/java/src/org/chromium/base/TimeUtils.java18
-rw-r--r--base/android/java/src/org/chromium/base/TraceEvent.java387
-rw-r--r--base/android/java/src/org/chromium/base/UnguessableToken.java91
-rw-r--r--base/android/java/src/org/chromium/base/annotations/DoNotInline.java20
-rw-r--r--base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java829
-rw-r--r--base/android/java/src/org/chromium/base/library_loader/Linker.java1160
-rw-r--r--base/android/java/src/org/chromium/base/library_loader/LoaderErrors.java16
-rw-r--r--base/android/java/src/org/chromium/base/library_loader/NativeLibraryPreloader.java20
-rw-r--r--base/android/java/src/org/chromium/base/library_loader/ProcessInitException.java35
-rw-r--r--base/android/java/src/org/chromium/base/memory/MemoryPressureCallback.java15
-rw-r--r--base/android/java/src/org/chromium/base/memory/MemoryPressureMonitor.java301
-rw-r--r--base/android/java/src/org/chromium/base/memory/MemoryPressureUma.java113
-rw-r--r--base/android/java/src/org/chromium/base/metrics/CachedMetrics.java307
-rw-r--r--base/android/java/src/org/chromium/base/metrics/RecordHistogram.java331
-rw-r--r--base/android/java/src/org/chromium/base/metrics/RecordUserAction.java85
-rw-r--r--base/android/java/src/org/chromium/base/metrics/StatisticsRecorderAndroid.java27
-rw-r--r--base/android/java/src/org/chromium/base/multidex/ChromiumMultiDexInstaller.java78
-rw-r--r--base/android/java/src/org/chromium/base/process_launcher/ChildConnectionAllocator.java305
-rw-r--r--base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java766
-rw-r--r--base/android/java/src/org/chromium/base/process_launcher/ChildProcessConstants.java27
-rw-r--r--base/android/java/src/org/chromium/base/process_launcher/ChildProcessLauncher.java278
-rw-r--r--base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java346
-rw-r--r--base/android/java/src/org/chromium/base/process_launcher/ChildProcessServiceDelegate.java76
-rw-r--r--base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.aidl7
-rw-r--r--base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.java68
-rw-r--r--base/android/java/src/org/chromium/base/process_launcher/ICallbackInt.aidl9
-rw-r--r--base/android/java/src/org/chromium/base/process_launcher/IChildProcessService.aidl26
-rw-r--r--base/android/java/templates/BuildConfig.template70
-rw-r--r--base/android/java/templates/NativeLibraries.template109
60 files changed, 10738 insertions, 0 deletions
diff --git a/base/android/java/src/org/chromium/base/ActivityState.java b/base/android/java/src/org/chromium/base/ActivityState.java
new file mode 100644
index 0000000000..b14814c1c0
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/AnimationFrameTimeHistogram.java b/base/android/java/src/org/chromium/base/AnimationFrameTimeHistogram.java
new file mode 100644
index 0000000000..ad5cdd815b
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java b/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java
new file mode 100644
index 0000000000..d1c4693c4a
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java
@@ -0,0 +1,705 @@
+// 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.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.Rect;
+import android.graphics.drawable.Drawable;
+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.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.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);
+ }
+ }
+
+ /**
+ * @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();
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/ApkAssets.java b/base/android/java/src/org/chromium/base/ApkAssets.java
new file mode 100644
index 0000000000..19108e5957
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/ApplicationStatus.java b/base/android/java/src/org/chromium/base/ApplicationStatus.java
new file mode 100644
index 0000000000..9496da8c1e
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/ApplicationStatus.java
@@ -0,0 +1,620 @@
+// 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;
+ }
+ }
+
+ static {
+ // Chrome initializes this only for the main process. This assert aims to try and catch
+ // usages from GPU / renderers, while still allowing tests.
+ assert ContextUtils.isMainProcess()
+ || ContextUtils.getProcessName().contains(":test")
+ : "Cannot use ApplicationState from process: "
+ + ContextUtils.getProcessName();
+ }
+
+ 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/base/android/java/src/org/chromium/base/BaseSwitches.java b/base/android/java/src/org/chromium/base/BaseSwitches.java
new file mode 100644
index 0000000000..fe47cdda1d
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/Callback.java b/base/android/java/src/org/chromium/base/Callback.java
new file mode 100644
index 0000000000..f5f20b9c75
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/CollectionUtil.java b/base/android/java/src/org/chromium/base/CollectionUtil.java
new file mode 100644
index 0000000000..60933807b8
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/CommandLine.java b/base/android/java/src/org/chromium/base/CommandLine.java
new file mode 100644
index 0000000000..963b1464af
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/CommandLineInitUtil.java b/base/android/java/src/org/chromium/base/CommandLineInitUtil.java
new file mode 100644
index 0000000000..e51b95d6b5
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/ContentUriUtils.java b/base/android/java/src/org/chromium/base/ContentUriUtils.java
new file mode 100644
index 0000000000..ba92a56c4f
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/ContentUriUtils.java
@@ -0,0 +1,251 @@
+// 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.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 "";
+ }
+
+ /**
+ * 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/base/android/java/src/org/chromium/base/CpuFeatures.java b/base/android/java/src/org/chromium/base/CpuFeatures.java
new file mode 100644
index 0000000000..ae4969c99e
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/EarlyTraceEvent.java b/base/android/java/src/org/chromium/base/EarlyTraceEvent.java
new file mode 100644
index 0000000000..0f64fc2329
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/EarlyTraceEvent.java
@@ -0,0 +1,299 @@
+// 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.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;
+
+ // 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();
+ 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.
+ }
+ }
+ } 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;
+ }
+
+ /** @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/base/android/java/src/org/chromium/base/EventLog.java b/base/android/java/src/org/chromium/base/EventLog.java
new file mode 100644
index 0000000000..f889175b7a
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/FieldTrialList.java b/base/android/java/src/org/chromium/base/FieldTrialList.java
new file mode 100644
index 0000000000..c3468a4af0
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/FileUtils.java b/base/android/java/src/org/chromium/base/FileUtils.java
new file mode 100644
index 0000000000..e44cd928ae
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java b/base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java
new file mode 100644
index 0000000000..cbaf7f76a1
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/JNIUtils.java b/base/android/java/src/org/chromium/base/JNIUtils.java
new file mode 100644
index 0000000000..3fcec91316
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/JavaHandlerThread.java b/base/android/java/src/org/chromium/base/JavaHandlerThread.java
new file mode 100644
index 0000000000..9a1c924398
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/LocaleUtils.java b/base/android/java/src/org/chromium/base/LocaleUtils.java
new file mode 100644
index 0000000000..05d39029a5
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/MemoryPressureListener.java b/base/android/java/src/org/chromium/base/MemoryPressureListener.java
new file mode 100644
index 0000000000..6c80970f48
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/NonThreadSafe.java b/base/android/java/src/org/chromium/base/NonThreadSafe.java
new file mode 100644
index 0000000000..53f38d2c81
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/ObserverList.java b/base/android/java/src/org/chromium/base/ObserverList.java
new file mode 100644
index 0000000000..59276c6ea8
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/PathService.java b/base/android/java/src/org/chromium/base/PathService.java
new file mode 100644
index 0000000000..9807c2e82a
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/PathUtils.java b/base/android/java/src/org/chromium/base/PathUtils.java
new file mode 100644
index 0000000000..e6fc8029b8
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/PathUtils.java
@@ -0,0 +1,263 @@
+// 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 java.io.File;
+import java.util.ArrayList;
+import java.util.concurrent.ExecutionException;
+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 AsyncTask<Void, Void, String[]> sDirPathFetchTask;
+
+ // If the AsyncTask 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 AsyncTask in setPrivateDataDirectorySuffix(), but if we need the
+ * result before the AsyncTask 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;
+ sDirPathFetchTask = new AsyncTask<Void, Void, String[]>() {
+ @Override
+ protected String[] doInBackground(Void... unused) {
+ return PathUtils.setPrivateDataDirectorySuffixInternal();
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+ }
+
+ 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();
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/PowerMonitor.java b/base/android/java/src/org/chromium/base/PowerMonitor.java
new file mode 100644
index 0000000000..ae36a75d00
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/Promise.java b/base/android/java/src/org/chromium/base/Promise.java
new file mode 100644
index 0000000000..4319148d9c
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/SecureRandomInitializer.java b/base/android/java/src/org/chromium/base/SecureRandomInitializer.java
new file mode 100644
index 0000000000..bfd7b4943a
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/StreamUtil.java b/base/android/java/src/org/chromium/base/StreamUtil.java
new file mode 100644
index 0000000000..f8cbfeeb9e
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/StreamUtil.java
@@ -0,0 +1,28 @@
+// 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;
+
+/**
+ * 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.
+ }
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/SysUtils.java b/base/android/java/src/org/chromium/base/SysUtils.java
new file mode 100644
index 0000000000..d4eb30de5b
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/ThrowUncaughtException.java b/base/android/java/src/org/chromium/base/ThrowUncaughtException.java
new file mode 100644
index 0000000000..d5f18a278d
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/ThrowUncaughtException.java
@@ -0,0 +1,21 @@
+// 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.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.MainDex;
+
+@MainDex
+abstract class ThrowUncaughtException {
+ @CalledByNative
+ private static void post() {
+ ThreadUtils.postOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ throw new RuntimeException("Intentional exception not caught by JNI");
+ }
+ });
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/TimeUtils.java b/base/android/java/src/org/chromium/base/TimeUtils.java
new file mode 100644
index 0000000000..dcacabf205
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/TraceEvent.java b/base/android/java/src/org/chromium/base/TraceEvent.java
new file mode 100644
index 0000000000..96590900e0
--- /dev/null
+++ b/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 name 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/base/android/java/src/org/chromium/base/UnguessableToken.java b/base/android/java/src/org/chromium/base/UnguessableToken.java
new file mode 100644
index 0000000000..4b1619dae8
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/annotations/DoNotInline.java b/base/android/java/src/org/chromium/base/annotations/DoNotInline.java
new file mode 100644
index 0000000000..9252f3a79b
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java b/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java
new file mode 100644
index 0000000000..5bc62042d4
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java
@@ -0,0 +1,829 @@
+// 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.Process;
+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.AsyncTask;
+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.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.metrics.RecordHistogram;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.atomic.AtomicBoolean;
+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, Void, Void> {
+ private final boolean mColdStart;
+
+ public LibraryPrefetchTask(boolean coldStart) {
+ mColdStart = coldStart;
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ try (TraceEvent e = TraceEvent.scoped("LibraryLoader.asyncPrefetchLibrariesToMemory")) {
+ int percentage = nativePercentageOfResidentNativeLibraryCode();
+ // 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", "NewApi", "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 = Process.is64Bit();
+ 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()) {
+ try (ZipFile zipFile = new ZipFile(apkPath);
+ InputStream inputStream =
+ zipFile.getInputStream(zipFile.getEntry(pathWithinApk))) {
+ if (zipFile.getEntry(pathWithinApk) == null)
+ throw new RuntimeException("Cannot find ZipEntry" + pathWithinApk);
+
+ FileUtils.copyFileStreamAtomicWithBuffer(
+ inputStream, libraryFile, new byte[16 * 1024]);
+ libraryFile.setReadable(true, false);
+ libraryFile.setExecutable(true, false);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ 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/base/android/java/src/org/chromium/base/library_loader/Linker.java b/base/android/java/src/org/chromium/base/library_loader/Linker.java
new file mode 100644
index 0000000000..5e30cfa496
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/library_loader/LoaderErrors.java b/base/android/java/src/org/chromium/base/library_loader/LoaderErrors.java
new file mode 100644
index 0000000000..2b94370bd8
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/library_loader/NativeLibraryPreloader.java b/base/android/java/src/org/chromium/base/library_loader/NativeLibraryPreloader.java
new file mode 100644
index 0000000000..6f8008d645
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/library_loader/ProcessInitException.java b/base/android/java/src/org/chromium/base/library_loader/ProcessInitException.java
new file mode 100644
index 0000000000..106667536d
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/memory/MemoryPressureCallback.java b/base/android/java/src/org/chromium/base/memory/MemoryPressureCallback.java
new file mode 100644
index 0000000000..258aa0bbdf
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/memory/MemoryPressureMonitor.java b/base/android/java/src/org/chromium/base/memory/MemoryPressureMonitor.java
new file mode 100644
index 0000000000..c8af484682
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/memory/MemoryPressureUma.java b/base/android/java/src/org/chromium/base/memory/MemoryPressureUma.java
new file mode 100644
index 0000000000..dc90f5706e
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/metrics/CachedMetrics.java b/base/android/java/src/org/chromium/base/metrics/CachedMetrics.java
new file mode 100644
index 0000000000..ba03e51275
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/metrics/RecordHistogram.java b/base/android/java/src/org/chromium/base/metrics/RecordHistogram.java
new file mode 100644
index 0000000000..898f0094ab
--- /dev/null
+++ b/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 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/base/android/java/src/org/chromium/base/metrics/RecordUserAction.java b/base/android/java/src/org/chromium/base/metrics/RecordUserAction.java
new file mode 100644
index 0000000000..0d2ba548d2
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/metrics/StatisticsRecorderAndroid.java b/base/android/java/src/org/chromium/base/metrics/StatisticsRecorderAndroid.java
new file mode 100644
index 0000000000..bff3fae763
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/multidex/ChromiumMultiDexInstaller.java b/base/android/java/src/org/chromium/base/multidex/ChromiumMultiDexInstaller.java
new file mode 100644
index 0000000000..5588ec5bdf
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/process_launcher/ChildConnectionAllocator.java b/base/android/java/src/org/chromium/base/process_launcher/ChildConnectionAllocator.java
new file mode 100644
index 0000000000..43ae2591d4
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java
new file mode 100644
index 0000000000..bfa5d5cc6a
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java
@@ -0,0 +1,766 @@
+// 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();
+ }
+ }
+
+ // Synchronize on this for access.
+ @GuardedBy("sAllBindingStateCounts")
+ private static final int[] sAllBindingStateCounts = new int[NUM_BINDING_STATES];
+
+ @VisibleForTesting
+ static void resetBindingStateCountsForTesting() {
+ synchronized (sAllBindingStateCounts) {
+ 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.
+ private @ChildBindingState int mBindingState;
+
+ // Protects access to instance variables that are also accessed on the client thread.
+ private final Object mClientThreadLock = new Object();
+
+ // Same as above except it no longer updates after |unbind()|.
+ @GuardedBy("mClientThreadLock")
+ private @ChildBindingState int mBindingStateCurrentOrWhenDied;
+
+ // Indicate |kill()| was called to intentionally kill this process.
+ @GuardedBy("mClientThreadLock")
+ private boolean mKilledByUs;
+
+ // Copy of |sAllBindingStateCounts| at the time this is unbound.
+ @GuardedBy("mClientThreadLock")
+ 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 (mClientThreadLock) {
+ mKilledByUs = true;
+ }
+ notifyChildProcessDied();
+ }
+
+ private 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");
+ }
+ }
+
+ private 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();
+
+ int[] bindingStateCounts;
+ synchronized (sAllBindingStateCounts) {
+ bindingStateCounts = Arrays.copyOf(sAllBindingStateCounts, NUM_BINDING_STATES);
+ }
+ synchronized (mClientThreadLock) {
+ mAllBindingStateCountsWhenDied = bindingStateCounts;
+ }
+
+ 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 (mClientThreadLock) {
+ 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 (mClientThreadLock) {
+ return mKilledByUs;
+ }
+ }
+
+ public int[] bindingStateCountsCurrentOrWhenDied() {
+ // 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 (mClientThreadLock) {
+ if (mAllBindingStateCountsWhenDied != null) {
+ return Arrays.copyOf(mAllBindingStateCountsWhenDied, NUM_BINDING_STATES);
+ }
+ }
+ synchronized (sAllBindingStateCounts) {
+ return Arrays.copyOf(sAllBindingStateCounts, NUM_BINDING_STATES);
+ }
+ }
+
+ // Should be called any binding is bound or unbound.
+ private void updateBindingState() {
+ int oldBindingState = mBindingState;
+ if (mUnbound) {
+ mBindingState = ChildBindingState.UNBOUND;
+ } else if (mStrongBinding.isBound()) {
+ mBindingState = ChildBindingState.STRONG;
+ } else if (mModerateBinding.isBound()) {
+ mBindingState = ChildBindingState.MODERATE;
+ } else {
+ assert mWaivedBinding.isBound();
+ mBindingState = ChildBindingState.WAIVED;
+ }
+
+ if (mBindingState != oldBindingState) {
+ synchronized (sAllBindingStateCounts) {
+ if (oldBindingState != ChildBindingState.UNBOUND) {
+ assert sAllBindingStateCounts[oldBindingState] > 0;
+ sAllBindingStateCounts[oldBindingState]--;
+ }
+ if (mBindingState != ChildBindingState.UNBOUND) {
+ sAllBindingStateCounts[mBindingState]++;
+ }
+ }
+ }
+
+ if (!mUnbound) {
+ synchronized (mClientThreadLock) {
+ 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() throws RemoteException {
+ mService.forceKill();
+ }
+
+ @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/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConstants.java b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConstants.java
new file mode 100644
index 0000000000..ec232d7c16
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/process_launcher/ChildProcessLauncher.java b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessLauncher.java
new file mode 100644
index 0000000000..7cdc8528bd
--- /dev/null
+++ b/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) {
+ assert mConnection == connection;
+ onServiceConnected();
+ }
+ };
+ Bundle connectionBundle = createConnectionBundle();
+ mDelegate.onBeforeConnectionSetup(connectionBundle);
+ mConnection.setupConnection(connectionBundle, getClientInterfaces(), connectionCallback);
+ }
+
+ private void onServiceConnected() {
+ assert isRunningOnLauncherThread();
+
+ 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/base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java
new file mode 100644
index 0000000000..876ebbb175
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java
@@ -0,0 +1,346 @@
+// 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.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 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);
+
+ 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 (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/base/android/java/src/org/chromium/base/process_launcher/ChildProcessServiceDelegate.java b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessServiceDelegate.java
new file mode 100644
index 0000000000..7beffeffa2
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.aidl b/base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.aidl
new file mode 100644
index 0000000000..e37d8c748d
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.java b/base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.java
new file mode 100644
index 0000000000..3dc366389a
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/process_launcher/ICallbackInt.aidl b/base/android/java/src/org/chromium/base/process_launcher/ICallbackInt.aidl
new file mode 100644
index 0000000000..db93cb0dbd
--- /dev/null
+++ b/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/base/android/java/src/org/chromium/base/process_launcher/IChildProcessService.aidl b/base/android/java/src/org/chromium/base/process_launcher/IChildProcessService.aidl
new file mode 100644
index 0000000000..298e0bf49a
--- /dev/null
+++ b/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/base/android/java/templates/BuildConfig.template b/base/android/java/templates/BuildConfig.template
new file mode 100644
index 0000000000..1006d12ee1
--- /dev/null
+++ b/base/android/java/templates/BuildConfig.template
@@ -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;
+
+#define Q(x) #x
+#define QUOTE(x) Q(x)
+
+#if defined(USE_FINAL)
+#define MAYBE_FINAL final
+#else
+#define MAYBE_FINAL
+#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 = 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 = false;
+#endif
+
+#if defined(_IS_UBSAN)
+ public static MAYBE_FINAL boolean IS_UBSAN = true;
+#else
+ public static MAYBE_FINAL boolean IS_UBSAN = 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 = 0;
+#endif
+}
diff --git a/base/android/java/templates/NativeLibraries.template b/base/android/java/templates/NativeLibraries.template
new file mode 100644
index 0000000000..68277df753
--- /dev/null
+++ b/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 = false;
+#endif
+
+#if defined(ENABLE_CHROMIUM_LINKER_LIBRARY_IN_ZIP_FILE)
+ public static boolean sUseLibraryInZipFile = true;
+#else
+ public static boolean sUseLibraryInZipFile = false;
+#endif
+
+#if defined(ENABLE_CHROMIUM_LINKER_TESTS)
+ public static boolean sEnableLinkerTests = true;
+#else
+ public static boolean sEnableLinkerTests = false;
+#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
+
+}