mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2026-05-30 19:06:30 +03:00
Import chromium-64.0.3282.140
This commit is contained in:
48
base/android/java/src/org/chromium/base/ActivityState.java
Normal file
48
base/android/java/src/org/chromium/base/ActivityState.java
Normal file
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
// 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;
|
||||
|
||||
import org.chromium.base.annotations.MainDex;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@MainDex
|
||||
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);
|
||||
}
|
||||
@@ -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.PendingIntent;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.Resources.NotFoundException;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
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.text.Html;
|
||||
import android.text.Spanned;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup.MarginLayoutParams;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.view.ViewGroup.MarginLayoutParams#setMarginEnd(int)
|
||||
*/
|
||||
public static void setMarginEnd(MarginLayoutParams layoutParams, int end) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
layoutParams.setMarginEnd(end);
|
||||
} else {
|
||||
layoutParams.rightMargin = end;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see android.view.ViewGroup.MarginLayoutParams#getMarginEnd()
|
||||
*/
|
||||
public static int getMarginEnd(MarginLayoutParams layoutParams) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
return layoutParams.getMarginEnd();
|
||||
} else {
|
||||
return layoutParams.rightMargin;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see android.view.ViewGroup.MarginLayoutParams#setMarginStart(int)
|
||||
*/
|
||||
public static void setMarginStart(MarginLayoutParams layoutParams, int start) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
layoutParams.setMarginStart(start);
|
||||
} else {
|
||||
layoutParams.leftMargin = start;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see android.view.ViewGroup.MarginLayoutParams#getMarginStart()
|
||||
*/
|
||||
public static int getMarginStart(MarginLayoutParams layoutParams) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
return layoutParams.getMarginStart();
|
||||
} else {
|
||||
return layoutParams.leftMargin;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see android.view.View#setPaddingRelative(int, int, int, int)
|
||||
*/
|
||||
public static void setPaddingRelative(View view, int start, int top, int end, int bottom) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
view.setPaddingRelative(start, top, end, bottom);
|
||||
} else {
|
||||
// Before JB MR1, all layouts are left-to-right, so start == left, etc.
|
||||
view.setPadding(start, top, end, bottom);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see android.view.View#getPaddingStart()
|
||||
*/
|
||||
public static int getPaddingStart(View view) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
return view.getPaddingStart();
|
||||
} else {
|
||||
// Before JB MR1, all layouts are left-to-right, so start == left.
|
||||
return view.getPaddingLeft();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see android.view.View#getPaddingEnd()
|
||||
*/
|
||||
public static int getPaddingEnd(View view) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
return view.getPaddingEnd();
|
||||
} else {
|
||||
// Before JB MR1, all layouts are left-to-right, so end == right.
|
||||
return view.getPaddingRight();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see android.content.res.Resources#getDrawable(int id).
|
||||
*/
|
||||
@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.content.res.Resources#getColorStateList(int id).
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public static ColorStateList getColorStateList(Resources res, int id) throws NotFoundException {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
return res.getColorStateList(id, null);
|
||||
} else {
|
||||
return res.getColorStateList(id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe equivalent of {@code a.equals(b)}.
|
||||
*
|
||||
* @see Objects#equals(Object, Object)
|
||||
*/
|
||||
public static boolean objectEquals(Object a, Object b) {
|
||||
return (a == null) ? (b == null) : a.equals(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
58
base/android/java/src/org/chromium/base/ApkAssets.java
Normal file
58
base/android/java/src/org/chromium/base/ApkAssets.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
621
base/android/java/src/org/chromium/base/ApplicationStatus.java
Normal file
621
base/android/java/src/org/chromium/base/ApplicationStatus.java
Normal file
@@ -0,0 +1,621 @@
|
||||
// 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.Build;
|
||||
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 org.chromium.base.annotations.MainDex;
|
||||
|
||||
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")
|
||||
@MainDex
|
||||
public class ApplicationStatus {
|
||||
private static final String TOOLBAR_CALLBACK_INTERNAL_WRAPPER_CLASS =
|
||||
"android.support.v7.internal.app.ToolbarActionBar$ToolbarCallbackWrapper";
|
||||
// In builds using the --use_unpublished_apis flag, the ToolbarActionBar class name does not
|
||||
// include the "internal" package.
|
||||
private static final String TOOLBAR_CALLBACK_WRAPPER_CLASS =
|
||||
"android.support.v7.app.ToolbarActionBar$ToolbarCallbackWrapper";
|
||||
private static final String WINDOW_PROFILER_CALLBACK =
|
||||
"com.android.tools.profiler.support.event.WindowProfilerCallback";
|
||||
|
||||
private static class ActivityInfo {
|
||||
private int mStatus = ActivityState.DESTROYED;
|
||||
private ObserverList<ActivityStateListener> mListeners = new ObserverList<>();
|
||||
|
||||
/**
|
||||
* @return The current {@link ActivityState} of the activity.
|
||||
*/
|
||||
@ActivityState
|
||||
public int getStatus() {
|
||||
return mStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param status The new {@link ActivityState} of the activity.
|
||||
*/
|
||||
public void setStatus(@ActivityState int status) {
|
||||
mStatus = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A list of {@link ActivityStateListener}s listening to this activity.
|
||||
*/
|
||||
public ObserverList<ActivityStateListener> getListeners() {
|
||||
return mListeners;
|
||||
}
|
||||
}
|
||||
|
||||
private static final Object sCachedApplicationStateLock = new Object();
|
||||
|
||||
@SuppressLint("SupportAnnotationUsage")
|
||||
@ApplicationState
|
||||
private static Integer sCachedApplicationState;
|
||||
|
||||
/** 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() {
|
||||
assert sIsInitialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
if (newState == ActivityState.CREATED) {
|
||||
// TODO(tedchoc): crbug/691100. The timing of application callback lifecycles were
|
||||
// changed in O and the activity info may have been lazily created
|
||||
// on first access to avoid a crash on startup. This should be removed
|
||||
// once the new lifecycle APIs are available.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
assert !sActivityInfo.containsKey(activity);
|
||||
}
|
||||
sActivityInfo.put(activity, new ActivityInfo());
|
||||
}
|
||||
|
||||
// Invalidate the cached application state.
|
||||
synchronized (sCachedApplicationStateLock) {
|
||||
sCachedApplicationState = null;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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 (sCachedApplicationStateLock) {
|
||||
if (sCachedApplicationState == null) {
|
||||
sCachedApplicationState = determineApplicationState();
|
||||
}
|
||||
return sCachedApplicationState;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
assert activity != null;
|
||||
ApplicationStatus.assertInitialized();
|
||||
|
||||
ActivityInfo info = sActivityInfo.get(activity);
|
||||
// TODO(tedchoc): crbug/691100. The timing of application callback lifecycles were changed
|
||||
// in O and the activity info may need to be lazily created if the onCreate
|
||||
// event has not yet been received.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && info == null
|
||||
&& !activity.isDestroyed()) {
|
||||
info = new ActivityInfo();
|
||||
sActivityInfo.put(activity, info);
|
||||
}
|
||||
assert info != null && 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 (sCachedApplicationStateLock) {
|
||||
sCachedApplicationState = null;
|
||||
}
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.base;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
|
||||
import org.chromium.base.multidex.ChromiumMultiDexInstaller;
|
||||
|
||||
/**
|
||||
* Basic application functionality that should be shared among all browser applications.
|
||||
*/
|
||||
public class BaseChromiumApplication extends Application {
|
||||
private static final String TAG = "base";
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(base);
|
||||
assert getBaseContext() != null;
|
||||
checkAppBeingReplaced();
|
||||
if (BuildConfig.isMultidexEnabled()) {
|
||||
ChromiumMultiDexInstaller.install(this);
|
||||
}
|
||||
}
|
||||
|
||||
/** Initializes the {@link CommandLine}. */
|
||||
public void initCommandLine() {}
|
||||
|
||||
/**
|
||||
* This must only be called for contexts whose application is a subclass of
|
||||
* {@link BaseChromiumApplication}.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static void initCommandLine(Context context) {
|
||||
((BaseChromiumApplication) context.getApplicationContext()).initCommandLine();
|
||||
}
|
||||
|
||||
/** Ensure this application object is not out-of-date. */
|
||||
private void checkAppBeingReplaced() {
|
||||
// During app update the old apk can still be triggered by broadcasts and spin up an
|
||||
// out-of-date application. Kill old applications in this bad state. See
|
||||
// http://crbug.com/658130 for more context and http://b.android.com/56296 for the bug.
|
||||
if (getResources() == null) {
|
||||
Log.e(TAG, "getResources() null, closing app.");
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
32
base/android/java/src/org/chromium/base/BaseSwitches.java
Normal file
32
base/android/java/src/org/chromium/base/BaseSwitches.java
Normal file
@@ -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() {}
|
||||
}
|
||||
197
base/android/java/src/org/chromium/base/BuildInfo.java
Normal file
197
base/android/java/src/org/chromium/base/BuildInfo.java
Normal file
@@ -0,0 +1,197 @@
|
||||
// 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.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.os.Build;
|
||||
import android.os.Build.VERSION;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.chromium.base.annotations.CalledByNative;
|
||||
|
||||
/**
|
||||
* BuildInfo is a utility class providing easy access to {@link PackageInfo} information. This is
|
||||
* primarily of use for accessing package information from native code.
|
||||
*/
|
||||
public class BuildInfo {
|
||||
/**
|
||||
* Array index to access field in {@link BuildInfo#getAll()}.
|
||||
*/
|
||||
public static final int BRAND_INDEX = 0;
|
||||
public static final int DEVICE_INDEX = 1;
|
||||
public static final int ANDROID_BUILD_ID_INDEX = 2;
|
||||
public static final int MODEL_INDEX = 4;
|
||||
public static final int ANDROID_BUILD_FP_INDEX = 11;
|
||||
public static final int GMS_CORE_VERSION_INDEX = 12;
|
||||
public static final int INSTALLER_PACKAGE_NAME_INDEX = 13;
|
||||
public static final int ABI_NAME_INDEX = 14;
|
||||
|
||||
private static final String TAG = "BuildInfo";
|
||||
private static final int MAX_FINGERPRINT_LENGTH = 128;
|
||||
|
||||
/**
|
||||
* BuildInfo is a static utility class and therefore shouldn't be instantiated.
|
||||
*/
|
||||
private BuildInfo() {}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@CalledByNative
|
||||
public static String[] getAll() {
|
||||
try {
|
||||
String packageName = ContextUtils.getApplicationContext().getPackageName();
|
||||
PackageManager pm = ContextUtils.getApplicationContext().getPackageManager();
|
||||
PackageInfo pi = pm.getPackageInfo(packageName, 0);
|
||||
String versionCode = pi.versionCode <= 0 ? "" : Integer.toString(pi.versionCode);
|
||||
String versionName = pi.versionName == null ? "" : pi.versionName;
|
||||
|
||||
CharSequence label = pm.getApplicationLabel(pi.applicationInfo);
|
||||
String packageLabel = label == null ? "" : label.toString();
|
||||
|
||||
String installerPackageName = pm.getInstallerPackageName(packageName);
|
||||
if (installerPackageName == null) {
|
||||
installerPackageName = "";
|
||||
}
|
||||
|
||||
String abiString = null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
abiString = TextUtils.join(", ", Build.SUPPORTED_ABIS);
|
||||
} else {
|
||||
abiString = "ABI1: " + Build.CPU_ABI + ", ABI2: " + Build.CPU_ABI2;
|
||||
}
|
||||
|
||||
// Use lastUpdateTime when developing locally, since versionCode does not normally
|
||||
// change in this case.
|
||||
long version = pi.versionCode > 10 ? pi.versionCode : pi.lastUpdateTime;
|
||||
String extractedFileSuffix = String.format("@%s", Long.toHexString(version));
|
||||
|
||||
// Do not alter this list without updating callers of it.
|
||||
return new String[] {
|
||||
Build.BRAND, Build.DEVICE, Build.ID, Build.MANUFACTURER, Build.MODEL,
|
||||
String.valueOf(Build.VERSION.SDK_INT), Build.TYPE, packageLabel, packageName,
|
||||
versionCode, versionName, getAndroidBuildFingerprint(), getGMSVersionCode(pm),
|
||||
installerPackageName, abiString, extractedFileSuffix,
|
||||
};
|
||||
} catch (NameNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The build fingerprint for the current Android install. The value is truncated to a
|
||||
* 128 characters as this is used for crash and UMA reporting, which should avoid huge
|
||||
* strings.
|
||||
*/
|
||||
private static String getAndroidBuildFingerprint() {
|
||||
return Build.FINGERPRINT.substring(
|
||||
0, Math.min(Build.FINGERPRINT.length(), MAX_FINGERPRINT_LENGTH));
|
||||
}
|
||||
|
||||
private static String getGMSVersionCode(PackageManager packageManager) {
|
||||
String msg = "gms versionCode not available.";
|
||||
try {
|
||||
PackageInfo packageInfo = packageManager.getPackageInfo("com.google.android.gms", 0);
|
||||
msg = Integer.toString(packageInfo.versionCode);
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.d(TAG, "GMS package is not found.", e);
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static String getPackageVersionName() {
|
||||
return getAll()[10];
|
||||
}
|
||||
|
||||
/** Returns a string that is different each time the apk changes. */
|
||||
public static String getExtractedFileSuffix() {
|
||||
return getAll()[15];
|
||||
}
|
||||
|
||||
public static String getPackageLabel() {
|
||||
return getAll()[7];
|
||||
}
|
||||
|
||||
public static String getPackageName() {
|
||||
return ContextUtils.getApplicationContext().getPackageName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a debuggable build of Android. Use this to enable developer-only features.
|
||||
*/
|
||||
public static boolean isDebugAndroid() {
|
||||
return "eng".equals(Build.TYPE) || "userdebug".equals(Build.TYPE);
|
||||
}
|
||||
|
||||
// The markers Begin:BuildCompat and End:BuildCompat delimit code
|
||||
// that is autogenerated from Android sources.
|
||||
// Begin:BuildCompat O,OMR1,P
|
||||
|
||||
/**
|
||||
* Checks if the device is running on a pre-release version of Android O or newer.
|
||||
* <p>
|
||||
* @return {@code true} if O APIs are available for use, {@code false} otherwise
|
||||
* @deprecated Android O is a finalized release and this method is no longer necessary. It will
|
||||
* be removed in a future release of the Support Library. Instead use
|
||||
* {@code Build.SDK_INT >= Build.VERSION_CODES#O}.
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean isAtLeastO() {
|
||||
return VERSION.SDK_INT >= 26;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the device is running on a pre-release version of Android O MR1 or newer.
|
||||
* <p>
|
||||
* @return {@code true} if O MR1 APIs are available for use, {@code false} otherwise
|
||||
* @deprecated Android O MR1 is a finalized release and this method is no longer necessary. It
|
||||
* will be removed in a future release of the Support Library. Instead, use
|
||||
* {@code Build.SDK_INT >= Build.VERSION_CODES#O_MR1}.
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean isAtLeastOMR1() {
|
||||
return VERSION.SDK_INT >= 27;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the device is running on a pre-release version of Android P or newer.
|
||||
* <p>
|
||||
* <strong>Note:</strong> This method will return {@code false} on devices running release
|
||||
* versions of Android. When Android P is finalized for release, this method will be deprecated
|
||||
* and all calls should be replaced with {@code Build.SDK_INT >= Build.VERSION_CODES#P}.
|
||||
*
|
||||
* @return {@code true} if P APIs are available for use, {@code false} otherwise
|
||||
*/
|
||||
public static boolean isAtLeastP() {
|
||||
return VERSION.CODENAME.equals("P");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the application targets at least released SDK O
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean targetsAtLeastO() {
|
||||
return ContextUtils.getApplicationContext().getApplicationInfo().targetSdkVersion >= 26;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the application targets at least released SDK OMR1
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean targetsAtLeastOMR1() {
|
||||
return ContextUtils.getApplicationContext().getApplicationInfo().targetSdkVersion >= 27;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the application targets pre-release SDK P
|
||||
*/
|
||||
public static boolean targetsAtLeastP() {
|
||||
return isAtLeastP()
|
||||
&& ContextUtils.getApplicationContext().getApplicationInfo().targetSdkVersion
|
||||
== Build.VERSION_CODES.CUR_DEVELOPMENT;
|
||||
}
|
||||
|
||||
// End:BuildCompat
|
||||
}
|
||||
43
base/android/java/src/org/chromium/base/Callback.java
Normal file
43
base/android/java/src/org/chromium/base/Callback.java
Normal file
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
73
base/android/java/src/org/chromium/base/CollectionUtil.java
Normal file
73
base/android/java/src/org/chromium/base/CollectionUtil.java
Normal file
@@ -0,0 +1,73 @@
|
||||
// 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.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.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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
412
base/android/java/src/org/chromium/base/CommandLine.java
Normal file
412
base/android/java/src/org/chromium/base/CommandLine.java
Normal file
@@ -0,0 +1,412 @@
|
||||
// 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.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
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) {
|
||||
// Just field trials can take up to 10K of command line.
|
||||
char[] buffer = readUtf8FileFullyCrashIfTooBig(file, 64 * 1024);
|
||||
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) {
|
||||
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.
|
||||
* @param sizeLimit cap on the file size.
|
||||
* @return Array of chars read from the file, or null if the file cannot be read.
|
||||
* @throws RuntimeException if the file size exceeds |sizeLimit|.
|
||||
*/
|
||||
private static char[] readUtf8FileFullyCrashIfTooBig(String fileName, int sizeLimit) {
|
||||
Reader reader = null;
|
||||
File f = new File(fileName);
|
||||
long fileLength = f.length();
|
||||
|
||||
if (fileLength == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (fileLength > sizeLimit) {
|
||||
throw new RuntimeException(
|
||||
"File " + fileName + " length " + fileLength + " exceeds limit " + sizeLimit);
|
||||
}
|
||||
|
||||
try {
|
||||
char[] buffer = new char[(int) fileLength];
|
||||
reader = new InputStreamReader(new FileInputStream(f), "UTF-8");
|
||||
int charsRead = reader.read(buffer);
|
||||
// Debug check that we've exhausted the input stream (will fail e.g. if the
|
||||
// file grew after we inspected its length).
|
||||
assert !reader.ready();
|
||||
return charsRead < buffer.length ? Arrays.copyOfRange(buffer, 0, charsRead) : buffer;
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
} finally {
|
||||
try {
|
||||
if (reader != null) reader.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Unable to close file reader.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
112
base/android/java/src/org/chromium/base/CommandLineInitUtil.java
Normal file
112
base/android/java/src/org/chromium/base/CommandLineInitUtil.java
Normal file
@@ -0,0 +1,112 @@
|
||||
// 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 java.io.File;
|
||||
|
||||
/**
|
||||
* Provides implementation of command line initialization for Android.
|
||||
*/
|
||||
public final class CommandLineInitUtil {
|
||||
private static final String TAG = "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.
|
||||
*
|
||||
*/
|
||||
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 context The {@link Context} to use to query whether or not this application is being
|
||||
* debugged, and whether or not the publicly writable command line file should
|
||||
* be used.
|
||||
* @param fileName The name of the command line file to pull arguments from.
|
||||
*/
|
||||
public static void initCommandLine(Context context, String fileName) {
|
||||
if (!CommandLine.isInitialized()) {
|
||||
File commandLineFile = getAlternativeCommandLinePath(context, fileName);
|
||||
if (commandLineFile != null) {
|
||||
Log.i(TAG,
|
||||
"Initializing command line from alternative file "
|
||||
+ commandLineFile.getPath());
|
||||
} else {
|
||||
commandLineFile = new File(COMMAND_LINE_FILE_PATH, fileName);
|
||||
Log.d(TAG, "Initializing command line from " + commandLineFile.getPath());
|
||||
}
|
||||
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.
|
||||
*/
|
||||
private static File getAlternativeCommandLinePath(Context context, String fileName) {
|
||||
File alternativeCommandLineFile =
|
||||
new File(COMMAND_LINE_FILE_PATH_DEBUG_APP, fileName);
|
||||
if (!alternativeCommandLineFile.exists()) return null;
|
||||
try {
|
||||
if (BuildInfo.isDebugAndroid()) {
|
||||
return alternativeCommandLineFile;
|
||||
}
|
||||
|
||||
String debugApp = Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1
|
||||
? getDebugAppPreJBMR1(context)
|
||||
: getDebugAppJBMR1(context);
|
||||
|
||||
if (debugApp != null
|
||||
&& debugApp.equals(context.getApplicationContext().getPackageName())) {
|
||||
return alternativeCommandLineFile;
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG, "Unable to detect alternative command line file");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
251
base/android/java/src/org/chromium/base/ContentUriUtils.java
Normal file
251
base/android/java/src/org/chromium/base/ContentUriUtils.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
152
base/android/java/src/org/chromium/base/ContextUtils.java
Normal file
152
base/android/java/src/org/chromium/base/ContextUtils.java
Normal file
@@ -0,0 +1,152 @@
|
||||
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.base;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.AssetManager;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import org.chromium.base.annotations.JNINamespace;
|
||||
import org.chromium.base.annotations.MainDex;
|
||||
|
||||
/**
|
||||
* This class provides Android application context related utility methods.
|
||||
*/
|
||||
@JNINamespace("base::android")
|
||||
@MainDex
|
||||
public class ContextUtils {
|
||||
private static final String TAG = "ContextUtils";
|
||||
private static Context sApplicationContext;
|
||||
|
||||
/**
|
||||
* Initialization-on-demand holder. This exists for thread-safe lazy initialization.
|
||||
*/
|
||||
private static class Holder {
|
||||
// Not final for tests.
|
||||
private static SharedPreferences sSharedPreferences = fetchAppSharedPreferences();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Android application context.
|
||||
*
|
||||
* Under normal circumstances there is only one application context in a process, so it's safe
|
||||
* to treat this as a global. In WebView it's possible for more than one app using WebView to be
|
||||
* running in a single process, but this mechanism is rarely used and this is not the only
|
||||
* problem in that scenario, so we don't currently forbid using it as a global.
|
||||
*
|
||||
* Do not downcast the context returned by this method to Application (or any subclass). It may
|
||||
* not be an Application object; it may be wrapped in a ContextWrapper. The only assumption you
|
||||
* may make is that it is a Context whose lifetime is the same as the lifetime of the process.
|
||||
*/
|
||||
public static Context getApplicationContext() {
|
||||
return sApplicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the java application context.
|
||||
*
|
||||
* This should be called exactly once early on during startup, before native is loaded and
|
||||
* before any other clients make use of the application context through this class.
|
||||
*
|
||||
* @param appContext The application context.
|
||||
*/
|
||||
public static void initApplicationContext(Context appContext) {
|
||||
// Conceding that occasionally in tests, native is loaded before the browser process is
|
||||
// started, in which case the browser process re-sets the application context.
|
||||
if (sApplicationContext != null && sApplicationContext != appContext) {
|
||||
throw new RuntimeException("Attempting to set multiple global application contexts.");
|
||||
}
|
||||
initJavaSideApplicationContext(appContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Only called by the static holder class and tests.
|
||||
*
|
||||
* @return The application-wide shared preferences.
|
||||
*/
|
||||
private static SharedPreferences fetchAppSharedPreferences() {
|
||||
return PreferenceManager.getDefaultSharedPreferences(sApplicationContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used to ensure that we always use the application context to fetch the default shared
|
||||
* preferences. This avoids needless I/O for android N and above. It also makes it clear that
|
||||
* the app-wide shared preference is desired, rather than the potentially context-specific one.
|
||||
*
|
||||
* @return application-wide shared preferences.
|
||||
*/
|
||||
public static SharedPreferences getAppSharedPreferences() {
|
||||
return Holder.sSharedPreferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Occasionally tests cannot ensure the application context doesn't change between tests (junit)
|
||||
* and sometimes specific tests has its own special needs, initApplicationContext should be used
|
||||
* as much as possible, but this method can be used to override it.
|
||||
*
|
||||
* @param appContext The new application context.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static void initApplicationContextForTests(Context appContext) {
|
||||
// ApplicationStatus.initialize should be called to setup activity tracking for tests
|
||||
// that use Robolectric and set the application context manually. Instead of changing all
|
||||
// tests that do so, the call was put here instead.
|
||||
// TODO(mheikal): Require param to be of type Application
|
||||
if (appContext instanceof Application) {
|
||||
ApplicationStatus.initialize((Application) appContext);
|
||||
}
|
||||
initJavaSideApplicationContext(appContext);
|
||||
Holder.sSharedPreferences = fetchAppSharedPreferences();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a service with {@code Intent}. This will be started with the foreground expectation
|
||||
* on Android O and higher.
|
||||
* TODO(crbug.com/769683): Temporary measure until we roll the support library to 26.1.0.
|
||||
*
|
||||
* @param context Context to start Service from.
|
||||
* @param intent The description of the Service to start.
|
||||
*
|
||||
* @see {@link android.support.v4.content.ContextCompat#startForegroundService(Context,Intent)}
|
||||
* @see Context#startForegroundService(Intent)
|
||||
* @see Context#startService(Intent)
|
||||
*/
|
||||
public static void startForegroundService(Context context, Intent intent) {
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
context.startForegroundService(intent);
|
||||
} else {
|
||||
context.startService(intent);
|
||||
}
|
||||
}
|
||||
|
||||
private static void initJavaSideApplicationContext(Context appContext) {
|
||||
if (appContext == null) {
|
||||
throw new RuntimeException("Global application context cannot be set to null.");
|
||||
}
|
||||
sApplicationContext = appContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* In most cases, {@link Context#getAssets()} can be used directly. Modified resources are
|
||||
* used downstream and are set up on application startup, and this method provides access to
|
||||
* regular assets before that initialization is complete.
|
||||
*
|
||||
* This method should ONLY be used for accessing files within the assets folder.
|
||||
*
|
||||
* @return Application assets.
|
||||
*/
|
||||
public static AssetManager getApplicationAssets() {
|
||||
Context context = getApplicationContext();
|
||||
while (context instanceof ContextWrapper) {
|
||||
context = ((ContextWrapper) context).getBaseContext();
|
||||
}
|
||||
return context.getAssets();
|
||||
}
|
||||
}
|
||||
42
base/android/java/src/org/chromium/base/CpuFeatures.java
Normal file
42
base/android/java/src/org/chromium/base/CpuFeatures.java
Normal file
@@ -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();
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copyright 2017 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.base;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
/**
|
||||
* A DiscardableReferencePool allows handing out typed references to objects ("payloads") that can
|
||||
* be dropped in one batch ("drained"), e.g. under memory pressure. In contrast to {@link
|
||||
* java.lang.ref.WeakReference}s, which drop their referents when they get garbage collected, a
|
||||
* reference pool gives more precise control over when exactly it is drained.
|
||||
*
|
||||
* <p>Internally it uses a {@link WeakHashMap} with the reference itself as a key to allow the
|
||||
* payloads to be garbage collected regularly when the last reference goes away before the pool is
|
||||
* drained.
|
||||
*
|
||||
* <p>This class and its references are not thread-safe and should not be used simultaneously by
|
||||
* multiple threads.
|
||||
*/
|
||||
public class DiscardableReferencePool {
|
||||
/**
|
||||
* The underlying data storage. The wildcard type parameter allows using a single pool for
|
||||
* references of any type.
|
||||
*/
|
||||
private final Set<DiscardableReference<?>> mPool;
|
||||
|
||||
public DiscardableReferencePool() {
|
||||
WeakHashMap<DiscardableReference<?>, Boolean> map = new WeakHashMap<>();
|
||||
mPool = Collections.newSetFromMap(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to an object in the pool. Will be nulled out when the pool is drained.
|
||||
* @param <T> The type of the object.
|
||||
*/
|
||||
public static class DiscardableReference<T> {
|
||||
@Nullable
|
||||
private T mPayload;
|
||||
|
||||
private DiscardableReference(T payload) {
|
||||
assert payload != null;
|
||||
mPayload = payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The referent, or null if the pool has been drained.
|
||||
*/
|
||||
@Nullable
|
||||
public T get() {
|
||||
return mPayload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the referent.
|
||||
*/
|
||||
private void discard() {
|
||||
assert mPayload != null;
|
||||
mPayload = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param <T> The type of the object.
|
||||
* @param payload The payload to add to the pool.
|
||||
* @return A new reference to the {@code payload}.
|
||||
*/
|
||||
public <T> DiscardableReference<T> put(T payload) {
|
||||
assert payload != null;
|
||||
DiscardableReference<T> reference = new DiscardableReference<>(payload);
|
||||
mPool.add(reference);
|
||||
return reference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drains the pool, removing all references to objects in the pool and therefore allowing them
|
||||
* to be garbage collected.
|
||||
*/
|
||||
public void drain() {
|
||||
for (DiscardableReference<?> ref : mPool) {
|
||||
ref.discard();
|
||||
}
|
||||
mPool.clear();
|
||||
}
|
||||
}
|
||||
221
base/android/java/src/org/chromium/base/EarlyTraceEvent.java
Normal file
221
base/android/java/src/org/chromium/base/EarlyTraceEvent.java
Normal file
@@ -0,0 +1,221 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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> sPendingEvents;
|
||||
|
||||
/** @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.isInitialized()
|
||||
&& 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>();
|
||||
sPendingEvents = new HashMap<String, Event>();
|
||||
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 = sPendingEvents.put(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 = sPendingEvents.remove(name);
|
||||
if (event == null) return;
|
||||
event.end();
|
||||
sCompletedEvents.add(event);
|
||||
if (sState == STATE_FINISHING) maybeFinishLocked();
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static void resetForTesting() {
|
||||
sState = EarlyTraceEvent.STATE_DISABLED;
|
||||
sCompletedEvents = null;
|
||||
sPendingEvents = null;
|
||||
}
|
||||
|
||||
private static void maybeFinishLocked() {
|
||||
if (!sCompletedEvents.isEmpty()) {
|
||||
dumpEvents(sCompletedEvents);
|
||||
sCompletedEvents.clear();
|
||||
}
|
||||
if (sPendingEvents.isEmpty()) {
|
||||
sState = STATE_FINISHED;
|
||||
sPendingEvents = null;
|
||||
sCompletedEvents = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void dumpEvents(List<Event> events) {
|
||||
long nativeNowNanos = TimeUtils.nativeGetTimeTicksNowUs() * 1000;
|
||||
long javaNowNanos = Event.elapsedRealtimeNanos();
|
||||
long offsetNanos = nativeNowNanos - javaNowNanos;
|
||||
for (Event e : events) {
|
||||
nativeRecordEarlyEvent(e.mName, e.mBeginTimeNanos + offsetNanos,
|
||||
e.mEndTimeNanos + offsetNanos, e.mThreadId,
|
||||
e.mEndThreadTimeMillis - e.mBeginThreadTimeMillis);
|
||||
}
|
||||
}
|
||||
|
||||
private static native void nativeRecordEarlyEvent(String name, long beginTimNanos,
|
||||
long endTimeNanos, int threadId, long threadDurationMillis);
|
||||
}
|
||||
20
base/android/java/src/org/chromium/base/EventLog.java
Normal file
20
base/android/java/src/org/chromium/base/EventLog.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
46
base/android/java/src/org/chromium/base/FieldTrialList.java
Normal file
46
base/android/java/src/org/chromium/base/FieldTrialList.java
Normal file
@@ -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);
|
||||
}
|
||||
113
base/android/java/src/org/chromium/base/FileUtils.java
Normal file
113
base/android/java/src/org/chromium/base/FileUtils.java
Normal file
@@ -0,0 +1,113 @@
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
46
base/android/java/src/org/chromium/base/JNIUtils.java
Normal file
46
base/android/java/src/org/chromium/base/JNIUtils.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.base;
|
||||
|
||||
import android.support.annotation.UiThread;
|
||||
|
||||
import org.chromium.base.annotations.CalledByNative;
|
||||
import org.chromium.base.annotations.JNINamespace;
|
||||
import org.chromium.base.annotations.MainDex;
|
||||
|
||||
/**
|
||||
* This UncaughtExceptionHandler will create a breakpad minidump when there is an uncaught
|
||||
* exception.
|
||||
*
|
||||
* The exception's stack trace will be added to the minidump's data. This allows java-only crashes
|
||||
* to be reported in the same way as other native crashes.
|
||||
*/
|
||||
@JNINamespace("base::android")
|
||||
@MainDex
|
||||
public class JavaExceptionReporter implements Thread.UncaughtExceptionHandler {
|
||||
private final Thread.UncaughtExceptionHandler mParent;
|
||||
private final boolean mCrashAfterReport;
|
||||
private boolean mHandlingException;
|
||||
|
||||
private JavaExceptionReporter(
|
||||
Thread.UncaughtExceptionHandler parent, boolean crashAfterReport) {
|
||||
mParent = parent;
|
||||
mCrashAfterReport = crashAfterReport;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uncaughtException(Thread t, Throwable e) {
|
||||
if (!mHandlingException) {
|
||||
mHandlingException = true;
|
||||
nativeReportJavaException(mCrashAfterReport, e);
|
||||
}
|
||||
if (mParent != null) {
|
||||
mParent.uncaughtException(t, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Report and upload the stack trace as if it was a crash. This is very expensive and should
|
||||
* be called rarely and only on the UI thread to avoid corrupting other crash uploads. Ideally
|
||||
* only called in idle handlers.
|
||||
*
|
||||
* @param stackTrace The stack trace to report.
|
||||
*/
|
||||
@UiThread
|
||||
public static void reportStackTrace(String stackTrace) {
|
||||
assert ThreadUtils.runningOnUiThread();
|
||||
nativeReportJavaStackTrace(stackTrace);
|
||||
}
|
||||
|
||||
@CalledByNative
|
||||
private static void installHandler(boolean crashAfterReport) {
|
||||
Thread.setDefaultUncaughtExceptionHandler(new JavaExceptionReporter(
|
||||
Thread.getDefaultUncaughtExceptionHandler(), crashAfterReport));
|
||||
}
|
||||
|
||||
private static native void nativeReportJavaException(boolean crashAfterReport, Throwable e);
|
||||
private static native void nativeReportJavaStackTrace(String stackTrace);
|
||||
}
|
||||
142
base/android/java/src/org/chromium/base/JavaHandlerThread.java
Normal file
142
base/android/java/src/org/chromium/base/JavaHandlerThread.java
Normal file
@@ -0,0 +1,142 @@
|
||||
// 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.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
import android.os.MessageQueue;
|
||||
|
||||
import org.chromium.base.annotations.CalledByNative;
|
||||
import org.chromium.base.annotations.JNINamespace;
|
||||
|
||||
import java.lang.Thread.UncaughtExceptionHandler;
|
||||
|
||||
/**
|
||||
* Thread in Java with an Android Handler. This class is not thread safe.
|
||||
*/
|
||||
@JNINamespace("base::android")
|
||||
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) {
|
||||
mThread = new HandlerThread(name);
|
||||
}
|
||||
|
||||
@CalledByNative
|
||||
private static JavaHandlerThread create(String name) {
|
||||
return new JavaHandlerThread(name);
|
||||
}
|
||||
|
||||
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 stopOnThread(final long nativeThread) {
|
||||
nativeStopThread(nativeThread);
|
||||
MessageQueue queue = Looper.myQueue();
|
||||
// Add an idle handler so that the thread cleanup code can run after the message loop has
|
||||
// detected an idle state and quit properly.
|
||||
// This matches the behavior of base::Thread in that it will keep running non-delayed posted
|
||||
// tasks indefinitely (until an idle state is reached). HandlerThread#quit() and
|
||||
// HandlerThread#quitSafely() aren't sufficient because they prevent new tasks from being
|
||||
// added to the queue, and don't allow us to wait for the Runloop to quit properly before
|
||||
// stopping the thread.
|
||||
queue.addIdleHandler(new MessageQueue.IdleHandler() {
|
||||
@Override
|
||||
public boolean queueIdle() {
|
||||
// The MessageQueue may not be empty, but only delayed tasks remain. To
|
||||
// match the behavior of other platforms, we should quit now. Calling quit
|
||||
// here is equivalent to calling quitSafely(), but doesn't require target
|
||||
// API guards.
|
||||
mThread.getLooper().quit();
|
||||
nativeOnLooperStopped(nativeThread);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@CalledByNative
|
||||
private void joinThread() {
|
||||
boolean joined = false;
|
||||
while (!joined) {
|
||||
try {
|
||||
mThread.join();
|
||||
joined = true;
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@CalledByNative
|
||||
private void stop(final long nativeThread) {
|
||||
assert hasStarted();
|
||||
// Looper may be null if the thread crashed.
|
||||
Looper looper = mThread.getLooper();
|
||||
if (!isAlive() || looper == null) return;
|
||||
new Handler(looper).post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
stopOnThread(nativeThread);
|
||||
}
|
||||
});
|
||||
joinThread();
|
||||
}
|
||||
|
||||
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 nativeStopThread(long nativeJavaHandlerThread);
|
||||
private native void nativeOnLooperStopped(long nativeJavaHandlerThread);
|
||||
}
|
||||
209
base/android/java/src/org/chromium/base/LocaleUtils.java
Normal file
209
base/android/java/src/org/chromium/base/LocaleUtils.java
Normal file
@@ -0,0 +1,209 @@
|
||||
// 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.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This class provides the locale related methods.
|
||||
*/
|
||||
public class LocaleUtils {
|
||||
/**
|
||||
* Guards this class from being instantiated.
|
||||
*/
|
||||
private LocaleUtils() {
|
||||
}
|
||||
|
||||
private static final Map<String, String> LANGUAGE_MAP_FOR_CHROMIUM;
|
||||
private static final Map<String, String> LANGUAGE_MAP_FOR_ANDROID;
|
||||
|
||||
static {
|
||||
// A variation of this mapping also exists in:
|
||||
// build/android/gyp/package_resources.py
|
||||
HashMap<String, String> mapForChromium = new HashMap<>();
|
||||
mapForChromium.put("iw", "he"); // Hebrew
|
||||
mapForChromium.put("ji", "yi"); // Yiddish
|
||||
mapForChromium.put("in", "id"); // Indonesian
|
||||
mapForChromium.put("tl", "fil"); // Filipino
|
||||
LANGUAGE_MAP_FOR_CHROMIUM = Collections.unmodifiableMap(mapForChromium);
|
||||
}
|
||||
|
||||
static {
|
||||
HashMap<String, String> mapForAndroid = new HashMap<>();
|
||||
mapForAndroid.put("und", ""); // Undefined
|
||||
mapForAndroid.put("fil", "tl"); // Filipino
|
||||
LANGUAGE_MAP_FOR_ANDROID = Collections.unmodifiableMap(mapForAndroid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
String updatedLanguageCode = LANGUAGE_MAP_FOR_CHROMIUM.get(language);
|
||||
return updatedLanguageCode == null ? language : updatedLanguageCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 languageForChrome = LANGUAGE_MAP_FOR_CHROMIUM.get(locale.getLanguage());
|
||||
if (languageForChrome == null) {
|
||||
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) {
|
||||
String updatedLanguageCode = LANGUAGE_MAP_FOR_ANDROID.get(language);
|
||||
return updatedLanguageCode == null ? language : updatedLanguageCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 languageForAndroid = LANGUAGE_MAP_FOR_ANDROID.get(locale.getLanguage());
|
||||
if (languageForAndroid == null) {
|
||||
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) || language.equals("und")) {
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
387
base/android/java/src/org/chromium/base/Log.java
Normal file
387
base/android/java/src/org/chromium/base/Log.java
Normal file
@@ -0,0 +1,387 @@
|
||||
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.base;
|
||||
|
||||
import org.chromium.base.annotations.RemovableInRelease;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Utility class for Logging.
|
||||
*
|
||||
* <p>
|
||||
* Defines logging access points for each feature. They format and forward the logs to
|
||||
* {@link android.util.Log}, allowing to standardize the output, to make it easy to identify
|
||||
* the origin of logs, and enable or disable logging in different parts of the code.
|
||||
* </p>
|
||||
* <p>
|
||||
* Usage documentation: {@code //docs/android_logging.md}.
|
||||
* </p>
|
||||
*/
|
||||
public class Log {
|
||||
/** Convenience property, same as {@link android.util.Log#ASSERT}. */
|
||||
public static final int ASSERT = android.util.Log.ASSERT;
|
||||
|
||||
/** Convenience property, same as {@link android.util.Log#DEBUG}. */
|
||||
public static final int DEBUG = android.util.Log.DEBUG;
|
||||
|
||||
/** Convenience property, same as {@link android.util.Log#ERROR}. */
|
||||
public static final int ERROR = android.util.Log.ERROR;
|
||||
|
||||
/** Convenience property, same as {@link android.util.Log#INFO}. */
|
||||
public static final int INFO = android.util.Log.INFO;
|
||||
|
||||
/** Convenience property, same as {@link android.util.Log#VERBOSE}. */
|
||||
public static final int VERBOSE = android.util.Log.VERBOSE;
|
||||
|
||||
/** Convenience property, same as {@link android.util.Log#WARN}. */
|
||||
public static final int WARN = android.util.Log.WARN;
|
||||
|
||||
private static final String sTagPrefix = "cr_";
|
||||
private static final String sDeprecatedTagPrefix = "cr.";
|
||||
|
||||
private Log() {
|
||||
// Static only access
|
||||
}
|
||||
|
||||
/** Returns a formatted log message, using the supplied format and arguments.*/
|
||||
private static String formatLog(String messageTemplate, Object... params) {
|
||||
if (params != null && params.length != 0) {
|
||||
messageTemplate = String.format(Locale.US, messageTemplate, params);
|
||||
}
|
||||
|
||||
return messageTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a normalized tag that will be in the form: "cr_foo". This function is called by the
|
||||
* various Log overrides. If using {@link #isLoggable(String, int)}, you might want to call it
|
||||
* to get the tag that will actually be used.
|
||||
* @see #sTagPrefix
|
||||
*/
|
||||
public static String normalizeTag(String tag) {
|
||||
if (tag.startsWith(sTagPrefix)) return tag;
|
||||
|
||||
// TODO(dgn) simplify this once 'cr.' is out of the repo (http://crbug.com/533072)
|
||||
int unprefixedTagStart = 0;
|
||||
if (tag.startsWith(sDeprecatedTagPrefix)) {
|
||||
unprefixedTagStart = sDeprecatedTagPrefix.length();
|
||||
}
|
||||
|
||||
return sTagPrefix + tag.substring(unprefixedTagStart, tag.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a formatted log message, using the supplied format and arguments.
|
||||
* The message will be prepended with the filename and line number of the call.
|
||||
*/
|
||||
private static String formatLogWithStack(String messageTemplate, Object... params) {
|
||||
return "[" + getCallOrigin() + "] " + formatLog(messageTemplate, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function, forwards to {@link android.util.Log#isLoggable(String, int)}.
|
||||
*
|
||||
* Note: Has no effect on whether logs are sent or not. Use a method with
|
||||
* {@link RemovableInRelease} to log something in Debug builds only.
|
||||
*/
|
||||
public static boolean isLoggable(String tag, int level) {
|
||||
return android.util.Log.isLoggable(tag, level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a {@link android.util.Log#VERBOSE} log message.
|
||||
*
|
||||
* For optimization purposes, only the fixed parameters versions are visible. If you need more
|
||||
* than 7 parameters, consider building your log message using a function annotated with
|
||||
* {@link RemovableInRelease}.
|
||||
*
|
||||
* @param tag Used to identify the source of a log message. Might be modified in the output
|
||||
* (see {@link #normalizeTag(String)})
|
||||
* @param messageTemplate The message you would like logged. It is to be specified as a format
|
||||
* string.
|
||||
* @param args Arguments referenced by the format specifiers in the format string. If the last
|
||||
* one is a {@link Throwable}, its trace will be printed.
|
||||
*/
|
||||
private static void verbose(String tag, String messageTemplate, Object... args) {
|
||||
String message = formatLogWithStack(messageTemplate, args);
|
||||
Throwable tr = getThrowableToLog(args);
|
||||
if (tr != null) {
|
||||
android.util.Log.v(normalizeTag(tag), message, tr);
|
||||
} else {
|
||||
android.util.Log.v(normalizeTag(tag), message);
|
||||
}
|
||||
}
|
||||
|
||||
/** Sends a {@link android.util.Log#VERBOSE} log message. 0 args version. */
|
||||
@RemovableInRelease
|
||||
@VisibleForTesting
|
||||
public static void v(String tag, String message) {
|
||||
verbose(tag, message);
|
||||
}
|
||||
|
||||
/** Sends a {@link android.util.Log#VERBOSE} log message. 1 arg version. */
|
||||
@RemovableInRelease
|
||||
@VisibleForTesting
|
||||
public static void v(String tag, String messageTemplate, Object arg1) {
|
||||
verbose(tag, messageTemplate, arg1);
|
||||
}
|
||||
|
||||
/** Sends a {@link android.util.Log#VERBOSE} log message. 2 args version */
|
||||
@RemovableInRelease
|
||||
@VisibleForTesting
|
||||
public static void v(String tag, String messageTemplate, Object arg1, Object arg2) {
|
||||
verbose(tag, messageTemplate, arg1, arg2);
|
||||
}
|
||||
|
||||
/** Sends a {@link android.util.Log#VERBOSE} log message. 3 args version */
|
||||
@RemovableInRelease
|
||||
@VisibleForTesting
|
||||
public static void v(
|
||||
String tag, String messageTemplate, Object arg1, Object arg2, Object arg3) {
|
||||
verbose(tag, messageTemplate, arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
/** Sends a {@link android.util.Log#VERBOSE} log message. 4 args version */
|
||||
@RemovableInRelease
|
||||
@VisibleForTesting
|
||||
public static void v(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3,
|
||||
Object arg4) {
|
||||
verbose(tag, messageTemplate, arg1, arg2, arg3, arg4);
|
||||
}
|
||||
|
||||
/** Sends a {@link android.util.Log#VERBOSE} log message. 5 args version */
|
||||
@RemovableInRelease
|
||||
@VisibleForTesting
|
||||
public static void v(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3,
|
||||
Object arg4, Object arg5) {
|
||||
verbose(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5);
|
||||
}
|
||||
|
||||
/** Sends a {@link android.util.Log#VERBOSE} log message. 6 args version */
|
||||
@RemovableInRelease
|
||||
@VisibleForTesting
|
||||
public static void v(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3,
|
||||
Object arg4, Object arg5, Object arg6) {
|
||||
verbose(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5, arg6);
|
||||
}
|
||||
|
||||
/** Sends a {@link android.util.Log#VERBOSE} log message. 7 args version */
|
||||
@RemovableInRelease
|
||||
@VisibleForTesting
|
||||
public static void v(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3,
|
||||
Object arg4, Object arg5, Object arg6, Object arg7) {
|
||||
verbose(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a {@link android.util.Log#DEBUG} log message.
|
||||
*
|
||||
* For optimization purposes, only the fixed parameters versions are visible. If you need more
|
||||
* than 7 parameters, consider building your log message using a function annotated with
|
||||
* {@link RemovableInRelease}.
|
||||
*
|
||||
* @param tag Used to identify the source of a log message. Might be modified in the output
|
||||
* (see {@link #normalizeTag(String)})
|
||||
* @param messageTemplate The message you would like logged. It is to be specified as a format
|
||||
* string.
|
||||
* @param args Arguments referenced by the format specifiers in the format string. If the last
|
||||
* one is a {@link Throwable}, its trace will be printed.
|
||||
*/
|
||||
private static void debug(String tag, String messageTemplate, Object... args) {
|
||||
String message = formatLogWithStack(messageTemplate, args);
|
||||
Throwable tr = getThrowableToLog(args);
|
||||
if (tr != null) {
|
||||
android.util.Log.d(normalizeTag(tag), message, tr);
|
||||
} else {
|
||||
android.util.Log.d(normalizeTag(tag), message);
|
||||
}
|
||||
}
|
||||
|
||||
/** Sends a {@link android.util.Log#DEBUG} log message. 0 args version. */
|
||||
@RemovableInRelease
|
||||
@VisibleForTesting
|
||||
public static void d(String tag, String message) {
|
||||
debug(tag, message);
|
||||
}
|
||||
|
||||
/** Sends a {@link android.util.Log#DEBUG} log message. 1 arg version. */
|
||||
@RemovableInRelease
|
||||
@VisibleForTesting
|
||||
public static void d(String tag, String messageTemplate, Object arg1) {
|
||||
debug(tag, messageTemplate, arg1);
|
||||
}
|
||||
/** Sends a {@link android.util.Log#DEBUG} log message. 2 args version */
|
||||
@RemovableInRelease
|
||||
@VisibleForTesting
|
||||
public static void d(String tag, String messageTemplate, Object arg1, Object arg2) {
|
||||
debug(tag, messageTemplate, arg1, arg2);
|
||||
}
|
||||
/** Sends a {@link android.util.Log#DEBUG} log message. 3 args version */
|
||||
@RemovableInRelease
|
||||
@VisibleForTesting
|
||||
public static void d(
|
||||
String tag, String messageTemplate, Object arg1, Object arg2, Object arg3) {
|
||||
debug(tag, messageTemplate, arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
/** Sends a {@link android.util.Log#DEBUG} log message. 4 args version */
|
||||
@RemovableInRelease
|
||||
@VisibleForTesting
|
||||
public static void d(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3,
|
||||
Object arg4) {
|
||||
debug(tag, messageTemplate, arg1, arg2, arg3, arg4);
|
||||
}
|
||||
|
||||
/** Sends a {@link android.util.Log#DEBUG} log message. 5 args version */
|
||||
@RemovableInRelease
|
||||
@VisibleForTesting
|
||||
public static void d(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3,
|
||||
Object arg4, Object arg5) {
|
||||
debug(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5);
|
||||
}
|
||||
|
||||
/** Sends a {@link android.util.Log#DEBUG} log message. 6 args version */
|
||||
@RemovableInRelease
|
||||
@VisibleForTesting
|
||||
public static void d(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3,
|
||||
Object arg4, Object arg5, Object arg6) {
|
||||
debug(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5, arg6);
|
||||
}
|
||||
|
||||
/** Sends a {@link android.util.Log#DEBUG} log message. 7 args version */
|
||||
@RemovableInRelease
|
||||
@VisibleForTesting
|
||||
public static void d(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3,
|
||||
Object arg4, Object arg5, Object arg6, Object arg7) {
|
||||
debug(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an {@link android.util.Log#INFO} log message.
|
||||
*
|
||||
* @param tag Used to identify the source of a log message. Might be modified in the output
|
||||
* (see {@link #normalizeTag(String)})
|
||||
* @param messageTemplate The message you would like logged. It is to be specified as a format
|
||||
* string.
|
||||
* @param args Arguments referenced by the format specifiers in the format string. If the last
|
||||
* one is a {@link Throwable}, its trace will be printed.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static void i(String tag, String messageTemplate, Object... args) {
|
||||
String message = formatLog(messageTemplate, args);
|
||||
Throwable tr = getThrowableToLog(args);
|
||||
if (tr != null) {
|
||||
android.util.Log.i(normalizeTag(tag), message, tr);
|
||||
} else {
|
||||
android.util.Log.i(normalizeTag(tag), message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a {@link android.util.Log#WARN} log message.
|
||||
*
|
||||
* @param tag Used to identify the source of a log message. Might be modified in the output
|
||||
* (see {@link #normalizeTag(String)})
|
||||
* @param messageTemplate The message you would like logged. It is to be specified as a format
|
||||
* string.
|
||||
* @param args Arguments referenced by the format specifiers in the format string. If the last
|
||||
* one is a {@link Throwable}, its trace will be printed.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static void w(String tag, String messageTemplate, Object... args) {
|
||||
String message = formatLog(messageTemplate, args);
|
||||
Throwable tr = getThrowableToLog(args);
|
||||
if (tr != null) {
|
||||
android.util.Log.w(normalizeTag(tag), message, tr);
|
||||
} else {
|
||||
android.util.Log.w(normalizeTag(tag), message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an {@link android.util.Log#ERROR} log message.
|
||||
*
|
||||
* @param tag Used to identify the source of a log message. Might be modified in the output
|
||||
* (see {@link #normalizeTag(String)})
|
||||
* @param messageTemplate The message you would like logged. It is to be specified as a format
|
||||
* string.
|
||||
* @param args Arguments referenced by the format specifiers in the format string. If the last
|
||||
* one is a {@link Throwable}, its trace will be printed.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static void e(String tag, String messageTemplate, Object... args) {
|
||||
String message = formatLog(messageTemplate, args);
|
||||
Throwable tr = getThrowableToLog(args);
|
||||
if (tr != null) {
|
||||
android.util.Log.e(normalizeTag(tag), message, tr);
|
||||
} else {
|
||||
android.util.Log.e(normalizeTag(tag), message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* What a Terrible Failure: Used for conditions that should never happen, and logged at
|
||||
* the {@link android.util.Log#ASSERT} level. Depending on the configuration, it might
|
||||
* terminate the process.
|
||||
*
|
||||
* @see android.util.Log#wtf(String, String, Throwable)
|
||||
*
|
||||
* @param tag Used to identify the source of a log message. Might be modified in the output
|
||||
* (see {@link #normalizeTag(String)})
|
||||
* @param messageTemplate The message you would like logged. It is to be specified as a format
|
||||
* string.
|
||||
* @param args Arguments referenced by the format specifiers in the format string. If the last
|
||||
* one is a {@link Throwable}, its trace will be printed.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static void wtf(String tag, String messageTemplate, Object... args) {
|
||||
String message = formatLog(messageTemplate, args);
|
||||
Throwable tr = getThrowableToLog(args);
|
||||
if (tr != null) {
|
||||
android.util.Log.wtf(normalizeTag(tag), message, tr);
|
||||
} else {
|
||||
android.util.Log.wtf(normalizeTag(tag), message);
|
||||
}
|
||||
}
|
||||
|
||||
/** Handy function to get a loggable stack trace from a Throwable. */
|
||||
public static String getStackTraceString(Throwable tr) {
|
||||
return android.util.Log.getStackTraceString(tr);
|
||||
}
|
||||
|
||||
private static Throwable getThrowableToLog(Object[] args) {
|
||||
if (args == null || args.length == 0) return null;
|
||||
|
||||
Object lastArg = args[args.length - 1];
|
||||
|
||||
if (!(lastArg instanceof Throwable)) return null;
|
||||
return (Throwable) lastArg;
|
||||
}
|
||||
|
||||
/** Returns a string form of the origin of the log call, to be used as secondary tag.*/
|
||||
private static String getCallOrigin() {
|
||||
StackTraceElement[] st = Thread.currentThread().getStackTrace();
|
||||
|
||||
// The call stack should look like:
|
||||
// n [a variable number of calls depending on the vm used]
|
||||
// +0 getCallOrigin()
|
||||
// +1 privateLogFunction: verbose or debug
|
||||
// +2 formatLogWithStack()
|
||||
// +3 logFunction: v or d
|
||||
// +4 caller
|
||||
|
||||
int callerStackIndex;
|
||||
String logClassName = Log.class.getName();
|
||||
for (callerStackIndex = 0; callerStackIndex < st.length; callerStackIndex++) {
|
||||
if (st[callerStackIndex].getClassName().equals(logClassName)) {
|
||||
callerStackIndex += 4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return st[callerStackIndex].getFileName() + ":" + st[callerStackIndex].getLineNumber();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
// 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 android.content.res.Configuration;
|
||||
|
||||
import org.chromium.base.annotations.CalledByNative;
|
||||
import org.chromium.base.annotations.MainDex;
|
||||
|
||||
|
||||
/**
|
||||
* This is an internal implementation of the C++ counterpart.
|
||||
* It registers a ComponentCallbacks2 with the system, and dispatches into
|
||||
* native for levels that are considered actionable.
|
||||
*/
|
||||
@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";
|
||||
|
||||
@CalledByNative
|
||||
private static void registerSystemCallback() {
|
||||
ContextUtils.getApplicationContext().registerComponentCallbacks(
|
||||
new ComponentCallbacks2() {
|
||||
@Override
|
||||
public void onTrimMemory(int level) {
|
||||
maybeNotifyMemoryPresure(level);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLowMemory() {
|
||||
nativeOnMemoryPressure(MemoryPressureLevel.CRITICAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration configuration) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
public static void maybeNotifyMemoryPresure(int level) {
|
||||
if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
|
||||
nativeOnMemoryPressure(MemoryPressureLevel.CRITICAL);
|
||||
} else if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND
|
||||
|| level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
|
||||
// Don't notifiy on TRIM_MEMORY_UI_HIDDEN, since this class only
|
||||
// dispatches actionable memory pressure signals to native.
|
||||
nativeOnMemoryPressure(MemoryPressureLevel.MODERATE);
|
||||
}
|
||||
}
|
||||
|
||||
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(int memoryPressureType);
|
||||
}
|
||||
41
base/android/java/src/org/chromium/base/NonThreadSafe.java
Normal file
41
base/android/java/src/org/chromium/base/NonThreadSafe.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
249
base/android/java/src/org/chromium/base/ObserverList.java
Normal file
249
base/android/java/src/org/chromium/base/ObserverList.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
base/android/java/src/org/chromium/base/PackageUtils.java
Normal file
37
base/android/java/src/org/chromium/base/PackageUtils.java
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.base;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
|
||||
/**
|
||||
* This class provides package checking related methods.
|
||||
*/
|
||||
public class PackageUtils {
|
||||
/**
|
||||
* Retrieves the version of the given package installed on the device.
|
||||
*
|
||||
* @param context Any context.
|
||||
* @param packageName Name of the package to find.
|
||||
* @return The package's version code if found, -1 otherwise.
|
||||
*/
|
||||
public static int getPackageVersion(Context context, String packageName) {
|
||||
int versionCode = -1;
|
||||
PackageManager pm = context.getPackageManager();
|
||||
try {
|
||||
PackageInfo packageInfo = pm.getPackageInfo(packageName, 0);
|
||||
if (packageInfo != null) versionCode = packageInfo.versionCode;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// Do nothing, versionCode stays -1
|
||||
}
|
||||
return versionCode;
|
||||
}
|
||||
|
||||
private PackageUtils() {
|
||||
// Hide constructor
|
||||
}
|
||||
}
|
||||
26
base/android/java/src/org/chromium/base/PathService.java
Normal file
26
base/android/java/src/org/chromium/base/PathService.java
Normal file
@@ -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);
|
||||
}
|
||||
226
base/android/java/src/org/chromium/base/PathUtils.java
Normal file
226
base/android/java/src/org/chromium/base/PathUtils.java
Normal file
@@ -0,0 +1,226 @@
|
||||
// Copyright 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.base;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Environment;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import org.chromium.base.annotations.CalledByNative;
|
||||
import org.chromium.base.annotations.MainDex;
|
||||
import org.chromium.base.metrics.RecordHistogram;
|
||||
|
||||
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 THUMBNAIL_DIRECTORY_NAME = "textures";
|
||||
private static final String DOWNLOAD_INTERNAL_DIRECTORY_NAME = "download_internal";
|
||||
|
||||
private static final int DATA_DIRECTORY = 0;
|
||||
private static final int THUMBNAIL_DIRECTORY = 1;
|
||||
private static final int DATABASE_DIRECTORY = 2;
|
||||
private static final int CACHE_DIRECTORY = 3;
|
||||
private static final int DOWNLOAD_INTERNAL_DIRECTORY = 4;
|
||||
private static final int NUM_DIRECTORIES = 5;
|
||||
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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
paths[THUMBNAIL_DIRECTORY] = appContext.getDir(
|
||||
THUMBNAIL_DIRECTORY_NAME, Context.MODE_PRIVATE).getPath();
|
||||
paths[DOWNLOAD_INTERNAL_DIRECTORY] =
|
||||
appContext.getDir(DOWNLOAD_INTERNAL_DIRECTORY_NAME, Context.MODE_PRIVATE).getPath();
|
||||
paths[DATABASE_DIRECTORY] = appContext.getDatabasePath("foo").getParent();
|
||||
if (appContext.getCacheDir() != null) {
|
||||
paths[CACHE_DIRECTORY] = appContext.getCacheDir().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.
|
||||
* @see Context#getDir(String, int)
|
||||
*/
|
||||
public static void setPrivateDataDirectorySuffix(String suffix) {
|
||||
// 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;
|
||||
sDirPathFetchTask = new AsyncTask<Void, Void, String[]>() {
|
||||
@Override
|
||||
protected String[] doInBackground(Void... unused) {
|
||||
return PathUtils.setPrivateDataDirectorySuffixInternal();
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 private directory that is used to store application database.
|
||||
*/
|
||||
@CalledByNative
|
||||
public static String getDatabaseDirectory() {
|
||||
assert sDirPathFetchTask != null : "setDataDirectorySuffix must be called first.";
|
||||
return getDirectoryPath(DATABASE_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);
|
||||
}
|
||||
|
||||
@CalledByNative
|
||||
public static String getDownloadInternalDirectory() {
|
||||
assert sDirPathFetchTask != null : "setDataDirectorySuffix must be called first.";
|
||||
return getDirectoryPath(DOWNLOAD_INTERNAL_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 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();
|
||||
}
|
||||
}
|
||||
80
base/android/java/src/org/chromium/base/PowerMonitor.java
Normal file
80
base/android/java/src/org/chromium/base/PowerMonitor.java
Normal file
@@ -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();
|
||||
}
|
||||
294
base/android/java/src/org/chromium/base/Promise.java
Normal file
294
base/android/java/src/org/chromium/base/Promise.java
Normal file
@@ -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));
|
||||
}
|
||||
}
|
||||
263
base/android/java/src/org/chromium/base/ResourceExtractor.java
Normal file
263
base/android/java/src/org/chromium/base/ResourceExtractor.java
Normal file
@@ -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.content.res.AssetManager;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Handles extracting the necessary resources bundled in an APK and moving them to a location on
|
||||
* the file system accessible from the native code.
|
||||
*/
|
||||
public class ResourceExtractor {
|
||||
private static final String TAG = "base";
|
||||
private static final String ICU_DATA_FILENAME = "icudtl.dat";
|
||||
private static final String V8_NATIVES_DATA_FILENAME = "natives_blob.bin";
|
||||
private static final String V8_SNAPSHOT_DATA_FILENAME = "snapshot_blob.bin";
|
||||
private static final String FALLBACK_LOCALE = "en-US";
|
||||
|
||||
private class ExtractTask extends AsyncTask<Void, Void, Void> {
|
||||
private static final int BUFFER_SIZE = 16 * 1024;
|
||||
|
||||
private final List<Runnable> mCompletionCallbacks = new ArrayList<Runnable>();
|
||||
|
||||
private void extractResourceHelper(InputStream is, File outFile, byte[] buffer)
|
||||
throws IOException {
|
||||
OutputStream os = null;
|
||||
File tmpOutputFile = new File(outFile.getPath() + ".tmp");
|
||||
try {
|
||||
os = new FileOutputStream(tmpOutputFile);
|
||||
Log.i(TAG, "Extracting resource %s", outFile);
|
||||
|
||||
int count = 0;
|
||||
while ((count = is.read(buffer, 0, BUFFER_SIZE)) != -1) {
|
||||
os.write(buffer, 0, count);
|
||||
}
|
||||
} finally {
|
||||
StreamUtil.closeQuietly(os);
|
||||
StreamUtil.closeQuietly(is);
|
||||
}
|
||||
if (!tmpOutputFile.renameTo(outFile)) {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
private void doInBackgroundImpl() {
|
||||
final File outputDir = getOutputDir();
|
||||
if (!outputDir.exists() && !outputDir.mkdirs()) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
// Use a suffix for extracted files in order to guarantee that the version of the file
|
||||
// on disk matches up with the version of the APK.
|
||||
String extractSuffix = BuildInfo.getExtractedFileSuffix();
|
||||
String[] existingFileNames = outputDir.list();
|
||||
boolean allFilesExist = existingFileNames != null;
|
||||
if (allFilesExist) {
|
||||
List<String> existingFiles = Arrays.asList(existingFileNames);
|
||||
for (String assetName : mAssetsToExtract) {
|
||||
allFilesExist &= existingFiles.contains(assetName + extractSuffix);
|
||||
}
|
||||
}
|
||||
// This is the normal case.
|
||||
if (allFilesExist) {
|
||||
return;
|
||||
}
|
||||
// A missing file means Chrome has updated. Delete stale files first.
|
||||
deleteFiles(existingFileNames);
|
||||
|
||||
AssetManager assetManager = ContextUtils.getApplicationAssets();
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
for (String assetName : mAssetsToExtract) {
|
||||
File output = new File(outputDir, assetName + extractSuffix);
|
||||
TraceEvent.begin("ExtractResource");
|
||||
try {
|
||||
InputStream inputStream = assetManager.open(assetName);
|
||||
extractResourceHelper(inputStream, output, buffer);
|
||||
} catch (IOException e) {
|
||||
// The app would just crash later if files are missing.
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
TraceEvent.end("ExtractResource");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... unused) {
|
||||
TraceEvent.begin("ResourceExtractor.ExtractTask.doInBackground");
|
||||
try {
|
||||
doInBackgroundImpl();
|
||||
} finally {
|
||||
TraceEvent.end("ResourceExtractor.ExtractTask.doInBackground");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void onPostExecuteImpl() {
|
||||
for (int i = 0; i < mCompletionCallbacks.size(); i++) {
|
||||
mCompletionCallbacks.get(i).run();
|
||||
}
|
||||
mCompletionCallbacks.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
TraceEvent.begin("ResourceExtractor.ExtractTask.onPostExecute");
|
||||
try {
|
||||
onPostExecuteImpl();
|
||||
} finally {
|
||||
TraceEvent.end("ResourceExtractor.ExtractTask.onPostExecute");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ExtractTask mExtractTask;
|
||||
private final String[] mAssetsToExtract = detectFilesToExtract();
|
||||
|
||||
private static ResourceExtractor sInstance;
|
||||
|
||||
public static ResourceExtractor get() {
|
||||
if (sInstance == null) {
|
||||
sInstance = new ResourceExtractor();
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
private static String[] detectFilesToExtract() {
|
||||
Locale defaultLocale = Locale.getDefault();
|
||||
String language = LocaleUtils.getUpdatedLanguageForChromium(defaultLocale.getLanguage());
|
||||
// Currenty (Oct 2016), this array can be as big as 4 entries, so using a capacity
|
||||
// that allows a bit of growth, but is still in the right ballpark..
|
||||
ArrayList<String> activeLocalePakFiles = new ArrayList<String>(6);
|
||||
for (String locale : BuildConfig.COMPRESSED_LOCALES) {
|
||||
if (locale.startsWith(language)) {
|
||||
activeLocalePakFiles.add(locale + ".pak");
|
||||
}
|
||||
}
|
||||
if (activeLocalePakFiles.isEmpty() && BuildConfig.COMPRESSED_LOCALES.length > 0) {
|
||||
assert Arrays.asList(BuildConfig.COMPRESSED_LOCALES).contains(FALLBACK_LOCALE);
|
||||
activeLocalePakFiles.add(FALLBACK_LOCALE + ".pak");
|
||||
}
|
||||
Log.i(TAG, "Android Locale: %s requires .pak files: %s", defaultLocale,
|
||||
activeLocalePakFiles);
|
||||
return activeLocalePakFiles.toArray(new String[activeLocalePakFiles.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously wait for the resource extraction to be completed.
|
||||
* <p>
|
||||
* This method is bad and you should feel bad for using it.
|
||||
*
|
||||
* @see #addCompletionCallback(Runnable)
|
||||
*/
|
||||
public void waitForCompletion() {
|
||||
if (mExtractTask == null || shouldSkipPakExtraction()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
mExtractTask.get();
|
||||
} catch (Exception e) {
|
||||
assert false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a callback to be notified upon the completion of resource extraction.
|
||||
* <p>
|
||||
* If the resource task has already completed, the callback will be posted to the UI message
|
||||
* queue. Otherwise, it will be executed after all the resources have been extracted.
|
||||
* <p>
|
||||
* This must be called on the UI thread. The callback will also always be executed on
|
||||
* the UI thread.
|
||||
*
|
||||
* @param callback The callback to be enqueued.
|
||||
*/
|
||||
public void addCompletionCallback(Runnable callback) {
|
||||
ThreadUtils.assertOnUiThread();
|
||||
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
if (shouldSkipPakExtraction()) {
|
||||
handler.post(callback);
|
||||
return;
|
||||
}
|
||||
|
||||
assert mExtractTask != null;
|
||||
assert !mExtractTask.isCancelled();
|
||||
if (mExtractTask.getStatus() == AsyncTask.Status.FINISHED) {
|
||||
handler.post(callback);
|
||||
} else {
|
||||
mExtractTask.mCompletionCallbacks.add(callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This will extract the application pak resources in an
|
||||
* AsyncTask. Call waitForCompletion() at the point resources
|
||||
* are needed to block until the task completes.
|
||||
*/
|
||||
public void startExtractingResources() {
|
||||
if (mExtractTask != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If a previous release extracted resources, and the current release does not,
|
||||
// deleteFiles() will not run and some files will be left. This currently
|
||||
// can happen for ContentShell, but not for Chrome proper, since we always extract
|
||||
// locale pak files.
|
||||
if (shouldSkipPakExtraction()) {
|
||||
return;
|
||||
}
|
||||
|
||||
mExtractTask = new ExtractTask();
|
||||
mExtractTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
private File getAppDataDir() {
|
||||
return new File(PathUtils.getDataDirectory());
|
||||
}
|
||||
|
||||
private File getOutputDir() {
|
||||
return new File(getAppDataDir(), "paks");
|
||||
}
|
||||
|
||||
private static void deleteFile(File file) {
|
||||
if (file.exists() && !file.delete()) {
|
||||
Log.w(TAG, "Unable to remove %s", file.getName());
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteFiles(String[] existingFileNames) {
|
||||
// These used to be extracted, but no longer are, so just clean them up.
|
||||
deleteFile(new File(getAppDataDir(), ICU_DATA_FILENAME));
|
||||
deleteFile(new File(getAppDataDir(), V8_NATIVES_DATA_FILENAME));
|
||||
deleteFile(new File(getAppDataDir(), V8_SNAPSHOT_DATA_FILENAME));
|
||||
if (existingFileNames != null) {
|
||||
for (String fileName : existingFileNames) {
|
||||
deleteFile(new File(getOutputDir(), fileName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pak extraction not necessarily required by the embedder.
|
||||
*/
|
||||
private static boolean shouldSkipPakExtraction() {
|
||||
return get().mAssetsToExtract.length == 0;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
28
base/android/java/src/org/chromium/base/StreamUtil.java
Normal file
28
base/android/java/src/org/chromium/base/StreamUtil.java
Normal file
@@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright 2017 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.base;
|
||||
|
||||
import android.os.StrictMode;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
/**
|
||||
* Enables try-with-resources compatible StrictMode violation whitelisting.
|
||||
*
|
||||
* Example:
|
||||
* <pre>
|
||||
* try (StrictModeContext unused = StrictModeContext.allowDiskWrites()) {
|
||||
* return Example.doThingThatRequiresDiskWrites();
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
public final class StrictModeContext implements Closeable {
|
||||
private final StrictMode.ThreadPolicy mThreadPolicy;
|
||||
private final StrictMode.VmPolicy mVmPolicy;
|
||||
|
||||
private StrictModeContext(StrictMode.ThreadPolicy threadPolicy, StrictMode.VmPolicy vmPolicy) {
|
||||
mThreadPolicy = threadPolicy;
|
||||
mVmPolicy = vmPolicy;
|
||||
}
|
||||
|
||||
private StrictModeContext(StrictMode.ThreadPolicy threadPolicy) {
|
||||
this(threadPolicy, null);
|
||||
}
|
||||
|
||||
private StrictModeContext(StrictMode.VmPolicy vmPolicy) {
|
||||
this(null, vmPolicy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for disabling all VM-level StrictMode checks with try-with-resources.
|
||||
* Includes everything listed here:
|
||||
* https://developer.android.com/reference/android/os/StrictMode.VmPolicy.Builder.html
|
||||
*/
|
||||
public static StrictModeContext allowAllVmPolicies() {
|
||||
StrictMode.VmPolicy oldPolicy = StrictMode.getVmPolicy();
|
||||
StrictMode.setVmPolicy(StrictMode.VmPolicy.LAX);
|
||||
return new StrictModeContext(oldPolicy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for disabling StrictMode for disk-writes with try-with-resources.
|
||||
*/
|
||||
public static StrictModeContext allowDiskWrites() {
|
||||
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
|
||||
return new StrictModeContext(oldPolicy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for disabling StrictMode for disk-reads with try-with-resources.
|
||||
*/
|
||||
public static StrictModeContext allowDiskReads() {
|
||||
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
|
||||
return new StrictModeContext(oldPolicy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (mThreadPolicy != null) {
|
||||
StrictMode.setThreadPolicy(mThreadPolicy);
|
||||
}
|
||||
if (mVmPolicy != null) {
|
||||
StrictMode.setVmPolicy(mVmPolicy);
|
||||
}
|
||||
}
|
||||
}
|
||||
199
base/android/java/src/org/chromium/base/SysUtils.java
Normal file
199
base/android/java/src/org/chromium/base/SysUtils.java
Normal file
@@ -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();
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
// 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.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.MessageQueue.IdleHandler;
|
||||
|
||||
import org.chromium.base.annotations.CalledByNative;
|
||||
import org.chromium.base.annotations.JNINamespace;
|
||||
import org.chromium.base.annotations.MainDex;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
@MainDex
|
||||
@JNINamespace("base")
|
||||
class SystemMessageHandler extends Handler {
|
||||
private static final String TAG = "cr.SysMessageHandler";
|
||||
|
||||
private static final int SCHEDULED_WORK = 1;
|
||||
private static final int DELAYED_SCHEDULED_WORK = 2;
|
||||
|
||||
private long mNativeMessagePumpForUI;
|
||||
private boolean mScheduledDelayedWork;
|
||||
|
||||
private final IdleHandler mIdleHandler = new IdleHandler() {
|
||||
@Override
|
||||
public boolean queueIdle() {
|
||||
if (mNativeMessagePumpForUI == 0) return false;
|
||||
nativeDoIdleWork(mNativeMessagePumpForUI);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
protected SystemMessageHandler(long nativeMessagePumpForUI) {
|
||||
mNativeMessagePumpForUI = nativeMessagePumpForUI;
|
||||
Looper.myQueue().addIdleHandler(mIdleHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
if (mNativeMessagePumpForUI == 0) return;
|
||||
boolean delayed = msg.what == DELAYED_SCHEDULED_WORK;
|
||||
if (delayed) mScheduledDelayedWork = false;
|
||||
nativeDoRunLoopOnce(mNativeMessagePumpForUI, delayed);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@CalledByNative
|
||||
private void scheduleWork() {
|
||||
sendMessage(obtainAsyncMessage(SCHEDULED_WORK));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@CalledByNative
|
||||
private void scheduleDelayedWork(long millis) {
|
||||
if (mScheduledDelayedWork) removeMessages(DELAYED_SCHEDULED_WORK);
|
||||
mScheduledDelayedWork = true;
|
||||
sendMessageDelayed(obtainAsyncMessage(DELAYED_SCHEDULED_WORK), millis);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@CalledByNative
|
||||
private void shutdown() {
|
||||
// No need to perform a slow removeMessages call, we should have executed all of the
|
||||
// outstanding tasks already, and if there happen to be any left, we'll ignore them.
|
||||
// The idleHandler will also remove itself next time the queue goes idle, so no need to
|
||||
// remove it here.
|
||||
mNativeMessagePumpForUI = 0;
|
||||
}
|
||||
|
||||
private Message obtainAsyncMessage(int what) {
|
||||
// Marking the message async provides fair Chromium task dispatch when
|
||||
// served by the Android UI thread's Looper, avoiding stalls when the
|
||||
// Looper has a sync barrier.
|
||||
Message msg = Message.obtain();
|
||||
msg.what = what;
|
||||
MessageCompat.setAsynchronous(msg, true);
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstraction utility class for marking a Message as asynchronous. Prior
|
||||
* to L MR1 the async Message API was hidden, and for such cases we fall
|
||||
* back to using reflection to obtain the necessary method.
|
||||
*/
|
||||
private static class MessageCompat {
|
||||
/**
|
||||
* @See android.os.Message#setAsynchronous(boolean)
|
||||
*/
|
||||
public static void setAsynchronous(Message message, boolean async) {
|
||||
IMPL.setAsynchronous(message, async);
|
||||
}
|
||||
|
||||
interface MessageWrapperImpl {
|
||||
/**
|
||||
* @See android.os.Message#setAsynchronous(boolean)
|
||||
*/
|
||||
public void setAsynchronous(Message message, boolean async);
|
||||
}
|
||||
|
||||
static final MessageWrapperImpl IMPL;
|
||||
static {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
|
||||
IMPL = new LollipopMr1MessageWrapperImpl();
|
||||
} else {
|
||||
IMPL = new LegacyMessageWrapperImpl();
|
||||
}
|
||||
}
|
||||
|
||||
static class LollipopMr1MessageWrapperImpl implements MessageWrapperImpl {
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public void setAsynchronous(Message msg, boolean async) {
|
||||
msg.setAsynchronous(async);
|
||||
}
|
||||
}
|
||||
|
||||
static class LegacyMessageWrapperImpl implements MessageWrapperImpl {
|
||||
// Reflected API for marking a message as asynchronous.
|
||||
// Note: Use of this API is experimental and likely to evolve in the future.
|
||||
private Method mMessageMethodSetAsynchronous;
|
||||
|
||||
LegacyMessageWrapperImpl() {
|
||||
try {
|
||||
mMessageMethodSetAsynchronous =
|
||||
Message.class.getMethod("setAsynchronous", new Class[] {boolean.class});
|
||||
} catch (NoSuchMethodException e) {
|
||||
Log.e(TAG, "Failed to load Message.setAsynchronous method", e);
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG, "Exception while loading Message.setAsynchronous method", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAsynchronous(Message msg, boolean async) {
|
||||
if (mMessageMethodSetAsynchronous == null) return;
|
||||
// If invocation fails, assume this is indicative of future
|
||||
// failures, and avoid log spam by nulling the reflected method.
|
||||
try {
|
||||
mMessageMethodSetAsynchronous.invoke(msg, async);
|
||||
} catch (IllegalAccessException e) {
|
||||
Log.e(TAG, "Illegal access to async message creation, disabling.");
|
||||
mMessageMethodSetAsynchronous = null;
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.e(TAG, "Illegal argument for async message creation, disabling.");
|
||||
mMessageMethodSetAsynchronous = null;
|
||||
} catch (InvocationTargetException e) {
|
||||
Log.e(TAG, "Invocation exception during async message creation, disabling.");
|
||||
mMessageMethodSetAsynchronous = null;
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG, "Runtime exception during async message creation, disabling.");
|
||||
mMessageMethodSetAsynchronous = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@CalledByNative
|
||||
private static SystemMessageHandler create(long nativeMessagePumpForUI) {
|
||||
return new SystemMessageHandler(nativeMessagePumpForUI);
|
||||
}
|
||||
|
||||
private native void nativeDoRunLoopOnce(long nativeMessagePumpForUI, boolean delayed);
|
||||
private native void nativeDoIdleWork(long nativeMessagePumpForUI);
|
||||
}
|
||||
269
base/android/java/src/org/chromium/base/ThreadUtils.java
Normal file
269
base/android/java/src/org/chromium/base/ThreadUtils.java
Normal file
@@ -0,0 +1,269 @@
|
||||
// Copyright 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.base;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Process;
|
||||
|
||||
import org.chromium.base.annotations.CalledByNative;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
/**
|
||||
* Helper methods to deal with threading related tasks.
|
||||
*/
|
||||
public class ThreadUtils {
|
||||
|
||||
private static final Object sLock = new Object();
|
||||
|
||||
private static boolean sWillOverride;
|
||||
|
||||
private static Handler sUiThreadHandler;
|
||||
|
||||
private static boolean sThreadAssertsDisabled;
|
||||
|
||||
public static void setWillOverrideUiThread() {
|
||||
synchronized (sLock) {
|
||||
sWillOverride = true;
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void setUiThread(Looper looper) {
|
||||
synchronized (sLock) {
|
||||
if (looper == null) {
|
||||
// Used to reset the looper after tests.
|
||||
sUiThreadHandler = null;
|
||||
return;
|
||||
}
|
||||
if (sUiThreadHandler != null && sUiThreadHandler.getLooper() != looper) {
|
||||
throw new RuntimeException("UI thread looper is already set to "
|
||||
+ sUiThreadHandler.getLooper() + " (Main thread looper is "
|
||||
+ Looper.getMainLooper() + "), cannot set to new looper " + looper);
|
||||
} else {
|
||||
sUiThreadHandler = new Handler(looper);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Handler getUiThreadHandler() {
|
||||
synchronized (sLock) {
|
||||
if (sUiThreadHandler == null) {
|
||||
if (sWillOverride) {
|
||||
throw new RuntimeException("Did not yet override the UI thread");
|
||||
}
|
||||
sUiThreadHandler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
return sUiThreadHandler;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the supplied Runnable on the main thread. The method will block until the Runnable
|
||||
* completes.
|
||||
*
|
||||
* @param r The Runnable to run.
|
||||
*/
|
||||
public static void runOnUiThreadBlocking(final Runnable r) {
|
||||
if (runningOnUiThread()) {
|
||||
r.run();
|
||||
} else {
|
||||
FutureTask<Void> task = new FutureTask<Void>(r, null);
|
||||
postOnUiThread(task);
|
||||
try {
|
||||
task.get();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Exception occurred while waiting for runnable", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the supplied Callable on the main thread, wrapping any exceptions in a RuntimeException.
|
||||
* The method will block until the Callable completes.
|
||||
*
|
||||
* @param c The Callable to run
|
||||
* @return The result of the callable
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static <T> T runOnUiThreadBlockingNoException(Callable<T> c) {
|
||||
try {
|
||||
return runOnUiThreadBlocking(c);
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException("Error occurred waiting for callable", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the supplied Callable on the main thread, The method will block until the Callable
|
||||
* completes.
|
||||
*
|
||||
* @param c The Callable to run
|
||||
* @return The result of the callable
|
||||
* @throws ExecutionException c's exception
|
||||
*/
|
||||
public static <T> T runOnUiThreadBlocking(Callable<T> c) throws ExecutionException {
|
||||
FutureTask<T> task = new FutureTask<T>(c);
|
||||
runOnUiThread(task);
|
||||
try {
|
||||
return task.get();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException("Interrupted waiting for callable", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the supplied FutureTask on the main thread. The method will block only if the current
|
||||
* thread is the main thread.
|
||||
*
|
||||
* @param task The FutureTask to run
|
||||
* @return The queried task (to aid inline construction)
|
||||
*/
|
||||
public static <T> FutureTask<T> runOnUiThread(FutureTask<T> task) {
|
||||
if (runningOnUiThread()) {
|
||||
task.run();
|
||||
} else {
|
||||
postOnUiThread(task);
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the supplied Callable on the main thread. The method will block only if the current
|
||||
* thread is the main thread.
|
||||
*
|
||||
* @param c The Callable to run
|
||||
* @return A FutureTask wrapping the callable to retrieve results
|
||||
*/
|
||||
public static <T> FutureTask<T> runOnUiThread(Callable<T> c) {
|
||||
return runOnUiThread(new FutureTask<T>(c));
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the supplied Runnable on the main thread. The method will block only if the current
|
||||
* thread is the main thread.
|
||||
*
|
||||
* @param r The Runnable to run
|
||||
*/
|
||||
public static void runOnUiThread(Runnable r) {
|
||||
if (runningOnUiThread()) {
|
||||
r.run();
|
||||
} else {
|
||||
getUiThreadHandler().post(r);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Post the supplied FutureTask to run on the main thread. The method will not block, even if
|
||||
* called on the UI thread.
|
||||
*
|
||||
* @param task The FutureTask to run
|
||||
* @return The queried task (to aid inline construction)
|
||||
*/
|
||||
public static <T> FutureTask<T> postOnUiThread(FutureTask<T> task) {
|
||||
getUiThreadHandler().post(task);
|
||||
return task;
|
||||
}
|
||||
|
||||
/**
|
||||
* Post the supplied Runnable to run on the main thread. The method will not block, even if
|
||||
* called on the UI thread.
|
||||
*
|
||||
* @param task The Runnable to run
|
||||
*/
|
||||
public static void postOnUiThread(Runnable task) {
|
||||
getUiThreadHandler().post(task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Post the supplied Runnable to run on the main thread after the given amount of time. The
|
||||
* method will not block, even if called on the UI thread.
|
||||
*
|
||||
* @param task The Runnable to run
|
||||
* @param delayMillis The delay in milliseconds until the Runnable will be run
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static void postOnUiThreadDelayed(Runnable task, long delayMillis) {
|
||||
getUiThreadHandler().postDelayed(task, delayMillis);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an exception (when DCHECKs are enabled) if currently not running on the UI thread.
|
||||
*
|
||||
* Can be disabled by setThreadAssertsDisabledForTesting(true).
|
||||
*/
|
||||
public static void assertOnUiThread() {
|
||||
if (sThreadAssertsDisabled) return;
|
||||
|
||||
assert runningOnUiThread() : "Must be called on the UI thread.";
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an exception (regardless of build) if currently not running on the UI thread.
|
||||
*
|
||||
* Can be disabled by setThreadAssertsEnabledForTesting(false).
|
||||
*
|
||||
* @see #assertOnUiThread()
|
||||
*/
|
||||
public static void checkUiThread() {
|
||||
if (!sThreadAssertsDisabled && !runningOnUiThread()) {
|
||||
throw new IllegalStateException("Must be called on the UI thread.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an exception (when DCHECKs are enabled) if currently running on the UI thread.
|
||||
*
|
||||
* Can be disabled by setThreadAssertsDisabledForTesting(true).
|
||||
*/
|
||||
public static void assertOnBackgroundThread() {
|
||||
if (sThreadAssertsDisabled) return;
|
||||
|
||||
assert !runningOnUiThread() : "Must be called on a thread other than UI.";
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables thread asserts.
|
||||
*
|
||||
* Can be used by tests where code that normally runs multi-threaded is going to run
|
||||
* single-threaded for the test (otherwise asserts that are valid in production would fail in
|
||||
* those tests).
|
||||
*/
|
||||
public static void setThreadAssertsDisabledForTesting(boolean disabled) {
|
||||
sThreadAssertsDisabled = disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true iff the current thread is the main (UI) thread.
|
||||
*/
|
||||
public static boolean runningOnUiThread() {
|
||||
return getUiThreadHandler().getLooper() == Looper.myLooper();
|
||||
}
|
||||
|
||||
public static Looper getUiThreadLooper() {
|
||||
return getUiThreadHandler().getLooper();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set thread priority to audio.
|
||||
*/
|
||||
@CalledByNative
|
||||
public static void setThreadPriorityAudio(int tid) {
|
||||
Process.setThreadPriority(tid, Process.THREAD_PRIORITY_AUDIO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether Thread priority is THREAD_PRIORITY_AUDIO or not.
|
||||
* @param tid Thread id.
|
||||
* @return true for THREAD_PRIORITY_AUDIO and false otherwise.
|
||||
*/
|
||||
@CalledByNative
|
||||
private static boolean isThreadPriorityAudio(int tid) {
|
||||
return Process.getThreadPriority(tid) == Process.THREAD_PRIORITY_AUDIO;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
18
base/android/java/src/org/chromium/base/TimeUtils.java
Normal file
18
base/android/java/src/org/chromium/base/TimeUtils.java
Normal file
@@ -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();
|
||||
}
|
||||
36
base/android/java/src/org/chromium/base/TimezoneUtils.java
Normal file
36
base/android/java/src/org/chromium/base/TimezoneUtils.java
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2017 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.base;
|
||||
|
||||
import android.os.StrictMode;
|
||||
|
||||
import org.chromium.base.annotations.CalledByNative;
|
||||
import org.chromium.base.annotations.JNINamespace;
|
||||
import org.chromium.base.annotations.MainDex;
|
||||
|
||||
import java.util.TimeZone;
|
||||
|
||||
@JNINamespace("base::android")
|
||||
@MainDex
|
||||
class TimezoneUtils {
|
||||
/**
|
||||
* Guards this class from being instantiated.
|
||||
*/
|
||||
|
||||
private TimezoneUtils() {}
|
||||
|
||||
/**
|
||||
* @return the Olson timezone ID of the current system time zone.
|
||||
*/
|
||||
@CalledByNative
|
||||
private static String getDefaultTimeZoneId() {
|
||||
// On Android N or earlier, getting the default timezone requires the disk
|
||||
// access when a device set up is skipped.
|
||||
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
|
||||
String timezoneID = TimeZone.getDefault().getID();
|
||||
StrictMode.setThreadPolicy(oldPolicy);
|
||||
return timezoneID;
|
||||
}
|
||||
}
|
||||
377
base/android/java/src/org/chromium/base/TraceEvent.java
Normal file
377
base/android/java/src/org/chromium/base/TraceEvent.java
Normal file
@@ -0,0 +1,377 @@
|
||||
// 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) {
|
||||
mName = name;
|
||||
begin(name);
|
||||
}
|
||||
|
||||
@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.
|
||||
* @return a TraceEvent, or null if tracing is not enabled.
|
||||
*/
|
||||
public static TraceEvent scoped(String name) {
|
||||
if (!(EarlyTraceEvent.enabled() || enabled())) return null;
|
||||
return new TraceEvent(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.base;
|
||||
|
||||
/**
|
||||
* Annotation used to mark code that has wider visibility or present for testing code.
|
||||
*/
|
||||
public @interface VisibleForTesting {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.base.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @AccessedByNative is used to ensure proguard will keep this field, since it's
|
||||
* only accessed by native.
|
||||
*/
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
public @interface AccessedByNative {
|
||||
public String value() default "";
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.base.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @CalledByNative is used by the JNI generator to create the necessary JNI
|
||||
* bindings and expose this method to native code.
|
||||
*/
|
||||
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
public @interface CalledByNative {
|
||||
/*
|
||||
* If present, tells which inner class the method belongs to.
|
||||
*/
|
||||
public String value() default "";
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.base.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @CalledByNativeUnchecked is used to generate JNI bindings that do not check for exceptions.
|
||||
* It only makes sense to use this annotation on methods that declare a throws... spec.
|
||||
* However, note that the exception received native side maybe an 'unchecked' (RuntimeExpception)
|
||||
* such as NullPointerException, so the native code should differentiate these cases.
|
||||
* Usage of this should be very rare; where possible handle exceptions in the Java side and use a
|
||||
* return value to indicate success / failure.
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
public @interface CalledByNativeUnchecked {
|
||||
/*
|
||||
* If present, tells which inner class the method belongs to.
|
||||
*/
|
||||
public String value() default "";
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.base.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* JNIAdditionalImport is used by the JNI generator to qualify inner types used on JNI methods. Must
|
||||
* be used when an inner class is used from a class within the same package. Example:
|
||||
*
|
||||
* <pre>
|
||||
* @JNIAdditionImport(Foo.class)
|
||||
* public class Bar {
|
||||
* @CalledByNative static void doSomethingWithInner(Foo.Inner inner) {
|
||||
* ...
|
||||
* }
|
||||
* }
|
||||
* <pre>
|
||||
* <p>
|
||||
* Notes:
|
||||
* 1) Foo must be in the same package as Bar
|
||||
* 2) For classes in different packages, they should be imported as:
|
||||
* import other.package.Foo;
|
||||
* and this annotation should not be used.
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
public @interface JNIAdditionalImport {
|
||||
Class<?>[] value();
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.base.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @JNINamespace is used by the JNI generator to create the necessary JNI
|
||||
* bindings and expose this method to native code using the specified namespace.
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface JNINamespace {
|
||||
public String value();
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.base.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* An annotation that signals that a class should be kept in the main dex file.
|
||||
*
|
||||
* This generally means it's used by renderer processes, which can't load secondary dexes
|
||||
* on K and below.
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
public @interface MainDex {
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.base.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @NativeCall is used by the JNI generator to create the necessary JNI bindings
|
||||
* so a native function can be bound to a Java inner class. The native class for
|
||||
* which the JNI method will be generated is specified by the first parameter.
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
public @interface NativeCall {
|
||||
/*
|
||||
* Value determines which native class the method should map to.
|
||||
*/
|
||||
public String value() default "";
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.base.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @NativeClassQualifiedName is used by the JNI generator to create the necessary JNI
|
||||
* bindings to call into the specified native class name.
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface NativeClassQualifiedName {
|
||||
/*
|
||||
* Tells which native class the method is going to be bound to.
|
||||
* The first parameter of the annotated method must be an int nativePtr pointing to
|
||||
* an instance of this class.
|
||||
*/
|
||||
public String value();
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.base.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* The annotated function can be removed in release builds.
|
||||
*
|
||||
* Calls to this function will be removed if its return value is not used. If all calls are removed,
|
||||
* the function definition itself will be candidate for removal.
|
||||
* It works by indicating to Proguard that the function has no side effects.
|
||||
*/
|
||||
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
|
||||
public @interface RemovableInRelease {}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.base.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation used for marking methods and fields that are called by reflection.
|
||||
* Useful for keeping components that would otherwise be removed by Proguard.
|
||||
* Use the value parameter to mention a file that calls this method.
|
||||
*
|
||||
* Note that adding this annotation to a method is not enough to guarantee that
|
||||
* it is kept - either its class must be referenced elsewhere in the program, or
|
||||
* the class must be annotated with this as well.
|
||||
*/
|
||||
@Target({
|
||||
ElementType.METHOD, ElementType.FIELD, ElementType.TYPE,
|
||||
ElementType.CONSTRUCTOR })
|
||||
public @interface UsedByReflection {
|
||||
String value();
|
||||
}
|
||||
@@ -0,0 +1,639 @@
|
||||
// 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.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
|
||||
import org.chromium.base.Log;
|
||||
import org.chromium.base.SysUtils;
|
||||
import org.chromium.base.ThreadUtils;
|
||||
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;
|
||||
|
||||
/*
|
||||
* For more, see Technical note, Security considerations, and the explanation
|
||||
* of how this class is supposed to be used in Linker.java.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides a concrete implementation of the Chromium Linker.
|
||||
*
|
||||
* This Linker implementation uses the crazy linker to map and then run Chrome
|
||||
* for Android.
|
||||
*
|
||||
* For more on the operations performed by the Linker, see {@link Linker}.
|
||||
*/
|
||||
@MainDex
|
||||
class LegacyLinker extends Linker {
|
||||
// Log tag for this class.
|
||||
private static final String TAG = "LibraryLoader";
|
||||
|
||||
// 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;
|
||||
|
||||
// Private singleton constructor, and singleton factory method.
|
||||
private LegacyLinker() { }
|
||||
static Linker create() {
|
||||
return new LegacyLinker();
|
||||
}
|
||||
|
||||
// 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 || !NativeLibraries.sUseLinker) {
|
||||
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.
|
||||
*/
|
||||
@Override
|
||||
public boolean isUsingBrowserSharedRelros() {
|
||||
synchronized (mLock) {
|
||||
ensureInitializedLocked();
|
||||
return mInBrowserProcess && mBrowserUsesSharedRelro;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this method just before loading any native shared libraries in this process.
|
||||
*/
|
||||
@Override
|
||||
public void prepareLibraryLoad() {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "prepareLibraryLoad() called");
|
||||
}
|
||||
synchronized (mLock) {
|
||||
ensureInitializedLocked();
|
||||
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().
|
||||
*/
|
||||
@Override
|
||||
public 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.
|
||||
*/
|
||||
@Override
|
||||
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.
|
||||
*/
|
||||
@Override
|
||||
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.
|
||||
*/
|
||||
@Override
|
||||
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.
|
||||
*/
|
||||
@Override
|
||||
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.
|
||||
*/
|
||||
@Override
|
||||
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 zipFilePath The path of the zip file containing the library (or null).
|
||||
* @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.
|
||||
*/
|
||||
@Override
|
||||
void loadLibraryImpl(@Nullable String zipFilePath,
|
||||
String libFilePath,
|
||||
boolean isFixedAddressPermitted) {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "loadLibraryImpl: "
|
||||
+ zipFilePath + ", " + 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String sharedRelRoName = libFilePath;
|
||||
if (zipFilePath != null) {
|
||||
if (!nativeLoadLibraryInZipFile(zipFilePath, libFilePath, loadAddress, libInfo)) {
|
||||
String errorMessage = "Unable to load library: " + libFilePath
|
||||
+ ", in: " + zipFilePath;
|
||||
Log.e(TAG, errorMessage);
|
||||
throw new UnsatisfiedLinkError(errorMessage);
|
||||
}
|
||||
sharedRelRoName = zipFilePath;
|
||||
} else {
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
public 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 load a library which is inside a zipfile.
|
||||
*
|
||||
* @param zipfileName Filename of the zip file containing the 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 nativeLoadLibraryInZipFile(@Nullable String zipfileName,
|
||||
String libraryName,
|
||||
long loadAddress,
|
||||
LibInfo libInfo);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
@@ -0,0 +1,595 @@
|
||||
// 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 android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.StrictMode;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import org.chromium.base.CommandLine;
|
||||
import org.chromium.base.ContextUtils;
|
||||
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.CalledByNative;
|
||||
import org.chromium.base.annotations.JNINamespace;
|
||||
import org.chromium.base.annotations.MainDex;
|
||||
import org.chromium.base.metrics.RecordHistogram;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
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.
|
||||
*/
|
||||
@JNINamespace("base::android")
|
||||
@MainDex
|
||||
public class LibraryLoader {
|
||||
private static final String TAG = "LibraryLoader";
|
||||
|
||||
// Set to true to enable debug logs.
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
// Guards all access to the libraries
|
||||
private static final Object sLock = new Object();
|
||||
|
||||
// SharedPreferences key for "don't prefetch libraries" flag
|
||||
private static final String DONT_PREFETCH_LIBRARIES_KEY = "dont_prefetch_libraries";
|
||||
|
||||
// The singleton instance of NativeLibraryPreloader.
|
||||
private static NativeLibraryPreloader sLibraryPreloader;
|
||||
private static boolean sLibraryPreloaderCalled;
|
||||
|
||||
// The singleton instance of LibraryLoader.
|
||||
private static volatile LibraryLoader sInstance;
|
||||
|
||||
// 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 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 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.
|
||||
// This member can be accessed from multiple threads simultaneously, so it have to be
|
||||
// final (like now) or be protected in some way (volatile of synchronized).
|
||||
private final int mLibraryProcessType;
|
||||
|
||||
// One-way switch that becomes true once
|
||||
// {@link asyncPrefetchLibrariesToMemory} has been called.
|
||||
private final AtomicBoolean mPrefetchLibraryHasBeenCalled;
|
||||
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* 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 static void setNativeLibraryPreloader(NativeLibraryPreloader loader) {
|
||||
synchronized (sLock) {
|
||||
assert sLibraryPreloader == null && (sInstance == null || !sInstance.mLoaded);
|
||||
sLibraryPreloader = loader;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param libraryProcessType the process the shared library is loaded in. refer to
|
||||
* LibraryProcessType for possible values.
|
||||
* @return LibraryLoader if existing, otherwise create a new one.
|
||||
*/
|
||||
public static LibraryLoader get(int libraryProcessType) throws ProcessInitException {
|
||||
synchronized (sLock) {
|
||||
if (sInstance != null) {
|
||||
if (sInstance.mLibraryProcessType == libraryProcessType) return sInstance;
|
||||
throw new ProcessInitException(
|
||||
LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_LOAD_FAILED);
|
||||
}
|
||||
sInstance = new LibraryLoader(libraryProcessType);
|
||||
return sInstance;
|
||||
}
|
||||
}
|
||||
|
||||
private LibraryLoader(int libraryProcessType) {
|
||||
mLibraryProcessType = libraryProcessType;
|
||||
mPrefetchLibraryHasBeenCalled = new AtomicBoolean();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method blocks until the library is fully loaded and initialized.
|
||||
*/
|
||||
public void ensureInitialized() throws ProcessInitException {
|
||||
synchronized (sLock) {
|
||||
if (mInitialized) {
|
||||
// Already initialized, nothing to do.
|
||||
return;
|
||||
}
|
||||
loadAlreadyLocked(ContextUtils.getApplicationContext());
|
||||
initializeAlreadyLocked();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (sLock) {
|
||||
if (!Linker.isUsed()) {
|
||||
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 !Linker.isUsed();
|
||||
if (sLibraryPreloader != null && !sLibraryPreloaderCalled) {
|
||||
mLibraryPreloaderStatus = sLibraryPreloader.loadLibrary(appContext);
|
||||
sLibraryPreloaderCalled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if library is fully loaded and initialized.
|
||||
*/
|
||||
public static boolean isInitialized() {
|
||||
return sInstance != null && sInstance.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 (sLock) {
|
||||
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.
|
||||
*/
|
||||
public void initialize() throws ProcessInitException {
|
||||
synchronized (sLock) {
|
||||
initializeAlreadyLocked();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
try (TraceEvent e =
|
||||
TraceEvent.scoped("LibraryLoader.asyncPrefetchLibrariesToMemory")) {
|
||||
int percentage = nativePercentageOfResidentNativeLibraryCode();
|
||||
boolean success = false;
|
||||
// Arbitrary percentage threshold. If most of the native library is already
|
||||
// resident (likely with monochrome), don't bother creating a prefetch process.
|
||||
boolean prefetch = coldStart && percentage < 90;
|
||||
if (prefetch) {
|
||||
success = nativeForkAndPrefetchNativeLibrary();
|
||||
if (!success) {
|
||||
Log.w(TAG, "Forking a process to prefetch the native library failed.");
|
||||
}
|
||||
}
|
||||
// As this runs in a background thread, it can be called before histograms are
|
||||
// initialized. In this instance, histograms are dropped.
|
||||
RecordHistogram.initialize();
|
||||
if (prefetch) {
|
||||
RecordHistogram.recordBooleanHistogram(
|
||||
"LibraryLoader.PrefetchStatus", success);
|
||||
}
|
||||
if (percentage != -1) {
|
||||
String histogram = "LibraryLoader.PercentageOfResidentCodeBeforePrefetch"
|
||||
+ (coldStart ? ".ColdStartup" : ".WarmStartup");
|
||||
RecordHistogram.recordPercentageHistogram(histogram, percentage);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
// Helper for loadAlreadyLocked(). Load a native shared library with the Chromium linker.
|
||||
// Sets UMA flags depending on the results of loading.
|
||||
private void loadLibrary(Linker linker, @Nullable String zipFilePath, String libFilePath) {
|
||||
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(zipFilePath, libFilePath);
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
Log.w(TAG, "Failed to load native library with shared RELRO, retrying without");
|
||||
mLoadAtFixedAddressFailed = true;
|
||||
linker.loadLibraryNoFixedAddress(zipFilePath, libFilePath);
|
||||
}
|
||||
} else {
|
||||
// No attempt to use shared RELROs in the browser, so load as normal.
|
||||
linker.loadLibrary(zipFilePath, libFilePath);
|
||||
}
|
||||
|
||||
// Loaded successfully, so record if we loaded directly from an APK.
|
||||
if (zipFilePath != null) {
|
||||
mLibraryWasLoadedFromApk = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Invoke either Linker.loadLibrary(...) or System.loadLibrary(...), triggering
|
||||
// JNI_OnLoad in native code
|
||||
// TODO(crbug.com/635567): Fix this properly.
|
||||
@SuppressLint("DefaultLocale")
|
||||
private void loadAlreadyLocked(Context appContext) throws ProcessInitException {
|
||||
try (TraceEvent te = TraceEvent.scoped("LibraryLoader.loadAlreadyLocked")) {
|
||||
if (!mLoaded) {
|
||||
assert !mInitialized;
|
||||
|
||||
long startTime = SystemClock.uptimeMillis();
|
||||
|
||||
if (Linker.isUsed()) {
|
||||
// Load libraries using the Chromium linker.
|
||||
Linker linker = Linker.getInstance();
|
||||
linker.prepareLibraryLoad();
|
||||
|
||||
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 zipFilePath = null;
|
||||
String libFilePath = System.mapLibraryName(library);
|
||||
if (Linker.isInZipFile()) {
|
||||
// Load directly from the APK.
|
||||
zipFilePath = appContext.getApplicationInfo().sourceDir;
|
||||
Log.i(TAG, "Loading " + library + " from within " + zipFilePath);
|
||||
} else {
|
||||
// The library is in its own file.
|
||||
Log.i(TAG, "Loading " + library);
|
||||
}
|
||||
|
||||
try {
|
||||
// Load the library using this Linker. May throw UnsatisfiedLinkError.
|
||||
loadLibrary(linker, zipFilePath, libFilePath);
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
Log.e(TAG, "Unable to load library: " + library);
|
||||
throw(e);
|
||||
}
|
||||
}
|
||||
|
||||
linker.finishLibraryLoad();
|
||||
} else {
|
||||
preloadAlreadyLocked(appContext);
|
||||
// Load libraries using the system linker.
|
||||
for (String library : NativeLibraries.LIBRARIES) {
|
||||
try {
|
||||
System.loadLibrary(library);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 (sLock) {
|
||||
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() throws ProcessInitException {
|
||||
if (mInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
ensureCommandLineSwitchedAlreadyLocked();
|
||||
|
||||
if (!nativeLibraryLoaded()) {
|
||||
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();
|
||||
|
||||
// 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() {
|
||||
recordBrowserProcessHistogram();
|
||||
}
|
||||
|
||||
// Record Chromium linker histogram state for the main browser process. Called from
|
||||
// onNativeInitializationComplete().
|
||||
private void recordBrowserProcessHistogram() {
|
||||
if (Linker.getInstance().isUsed()) {
|
||||
nativeRecordChromiumAndroidLinkerBrowserHistogram(
|
||||
mIsUsingBrowserSharedRelros,
|
||||
mLoadAtFixedAddressFailed,
|
||||
getLibraryLoadFromApkStatus(),
|
||||
mLibraryLoadTimeMs);
|
||||
}
|
||||
if (sLibraryPreloader != null) {
|
||||
nativeRecordLibraryPreloaderBrowserHistogram(mLibraryPreloaderStatus);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the device's status for loading a library directly from the APK file.
|
||||
// This method can only be called when the Chromium linker is used.
|
||||
private int getLibraryLoadFromApkStatus() {
|
||||
assert Linker.getInstance().isUsed();
|
||||
|
||||
if (mLibraryWasLoadedFromApk) {
|
||||
return LibraryLoadFromApkStatusCodes.SUCCESSFUL;
|
||||
}
|
||||
|
||||
// There were no libraries to be loaded directly from the APK file.
|
||||
return LibraryLoadFromApkStatusCodes.UNKNOWN;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if (Linker.getInstance().isUsed()) {
|
||||
nativeRegisterChromiumAndroidLinkerRendererHistogram(requestedSharedRelro,
|
||||
loadAtFixedAddressFailed,
|
||||
mLibraryLoadTimeMs);
|
||||
}
|
||||
if (sLibraryPreloader != null) {
|
||||
nativeRegisterLibraryPreloaderRendererHistogram(mLibraryPreloaderStatus);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the process the shared library is loaded in, see the LibraryProcessType
|
||||
* for possible values.
|
||||
*/
|
||||
@CalledByNative
|
||||
public static int getLibraryProcessType() {
|
||||
if (sInstance == null) return LibraryProcessType.PROCESS_UNINITIALIZED;
|
||||
return sInstance.mLibraryProcessType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
// 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 boolean 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();
|
||||
}
|
||||
@@ -0,0 +1,827 @@
|
||||
// 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.os.Build;
|
||||
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.annotations.AccessedByNative;
|
||||
|
||||
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 abstract 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. Not applicable to ModernLinker.
|
||||
// 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.
|
||||
// Not used by ModernLinker.
|
||||
// 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(). Not applicable to ModernLinker.
|
||||
// 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().
|
||||
// Not used by ModernLinker.
|
||||
protected 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.
|
||||
protected 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.
|
||||
protected static final int ADDRESS_SPACE_RESERVATION = 192 * 1024 * 1024;
|
||||
|
||||
// Constants used to indicate a given Linker implementation, for testing.
|
||||
// LEGACY -> Always uses the LegacyLinker implementation.
|
||||
// MODERN -> Always uses the ModernLinker implementation.
|
||||
// NOTE: These names are known and expected by the Linker test scripts.
|
||||
public static final int LINKER_IMPLEMENTATION_LEGACY = 1;
|
||||
public static final int LINKER_IMPLEMENTATION_MODERN = 2;
|
||||
|
||||
// Singleton.
|
||||
private static Linker sSingleton;
|
||||
private static Object sSingletonLock = new Object();
|
||||
|
||||
// Protected singleton constructor.
|
||||
protected Linker() { }
|
||||
|
||||
/**
|
||||
* Get singleton instance. Returns either a LegacyLinker or a ModernLinker.
|
||||
*
|
||||
* Returns a ModernLinker if running on Android M or later, otherwise returns
|
||||
* a LegacyLinker.
|
||||
*
|
||||
* ModernLinker requires OS features from Android M and later: a system linker
|
||||
* that handles packed relocations and load from APK, and android_dlopen_ext()
|
||||
* for shared RELRO support. It cannot run on Android releases earlier than M.
|
||||
*
|
||||
* LegacyLinker runs on all Android releases but it is slower and more complex
|
||||
* than ModernLinker, so ModernLinker is preferred for Android M and later.
|
||||
*
|
||||
* @return the Linker implementation instance.
|
||||
*/
|
||||
public static final Linker getInstance() {
|
||||
synchronized (sSingletonLock) {
|
||||
if (sSingleton == null) {
|
||||
// With incremental install, it's important to fall back to the "normal"
|
||||
// library loading path in order for the libraries to be found.
|
||||
String appClass =
|
||||
ContextUtils.getApplicationContext().getApplicationInfo().className;
|
||||
boolean isIncrementalInstall =
|
||||
appClass != null && appClass.contains("incrementalinstall");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isIncrementalInstall) {
|
||||
sSingleton = ModernLinker.create();
|
||||
} else {
|
||||
sSingleton = LegacyLinker.create();
|
||||
}
|
||||
Log.i(TAG, "Using linker: " + sSingleton.getClass().getName());
|
||||
}
|
||||
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 for testing.
|
||||
* Hard assertion. Cannot be disabled. Used only by testing methods.
|
||||
*/
|
||||
private static void assertForTesting(boolean flag) {
|
||||
if (!flag) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
if (!NativeLibraries.sEnableLinkerTests) {
|
||||
throw new AssertionError("Testing method called in non-testing context");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Linker implementation type.
|
||||
* For testing. Sets either a LegacyLinker or a ModernLinker. Must be called
|
||||
* before getInstance().
|
||||
*
|
||||
* @param type LINKER_IMPLEMENTATION_LEGACY or LINKER_IMPLEMENTATION_MODERN
|
||||
*/
|
||||
public static final void setImplementationForTesting(int type) {
|
||||
// Sanity check. This method may only be called during tests.
|
||||
assertLinkerTestsAreEnabled();
|
||||
assertForTesting(type == LINKER_IMPLEMENTATION_LEGACY
|
||||
|| type == LINKER_IMPLEMENTATION_MODERN);
|
||||
|
||||
synchronized (sSingletonLock) {
|
||||
assertForTesting(sSingleton == null);
|
||||
|
||||
if (type == LINKER_IMPLEMENTATION_MODERN) {
|
||||
sSingleton = ModernLinker.create();
|
||||
} else if (type == LINKER_IMPLEMENTATION_LEGACY) {
|
||||
sSingleton = LegacyLinker.create();
|
||||
}
|
||||
Log.i(TAG, "Forced linker: " + sSingleton.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Linker implementation type.
|
||||
* For testing.
|
||||
*
|
||||
* @return LINKER_IMPLEMENTATION_LEGACY or LINKER_IMPLEMENTATION_MODERN
|
||||
*/
|
||||
public final int getImplementationForTesting() {
|
||||
// Sanity check. This method may only be called during tests.
|
||||
assertLinkerTestsAreEnabled();
|
||||
|
||||
synchronized (sSingletonLock) {
|
||||
assertForTesting(sSingleton == this);
|
||||
|
||||
if (sSingleton instanceof ModernLinker) {
|
||||
return LINKER_IMPLEMENTATION_MODERN;
|
||||
} else if (sSingleton instanceof LegacyLinker) {
|
||||
return LINKER_IMPLEMENTATION_LEGACY;
|
||||
} else {
|
||||
Log.wtf(TAG, "Invalid linker: " + sSingleton.getClass().getName());
|
||||
assertForTesting(false);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the TestRunner by its class name. It will be instantiated at
|
||||
* runtime after all libraries are loaded.
|
||||
*
|
||||
* @param testRunnerClassName null or a String for the class name of the
|
||||
* TestRunner to use.
|
||||
*/
|
||||
public final void setTestRunnerClassNameForTesting(String testRunnerClassName) {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "setTestRunnerClassNameForTesting(" + testRunnerClassName + ") called");
|
||||
}
|
||||
// Sanity check. This method may only be called during tests.
|
||||
assertLinkerTestsAreEnabled();
|
||||
|
||||
synchronized (mLock) {
|
||||
assertForTesting(mTestRunnerClassName == null);
|
||||
mTestRunnerClassName = testRunnerClassName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the Linker for a test.
|
||||
* Convenience function that calls setImplementationForTesting() to force an
|
||||
* implementation, and then setTestRunnerClassNameForTesting() to set the test
|
||||
* class name.
|
||||
*
|
||||
* On first call, instantiates a Linker of the requested type and sets its test
|
||||
* runner class name. On subsequent calls, checks that the singleton produced by
|
||||
* the first call matches the requested type and test runner class name.
|
||||
*/
|
||||
public static final void setupForTesting(int type, String testRunnerClassName) {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "setupForTesting(" + type + ", " + testRunnerClassName + ") called");
|
||||
}
|
||||
// Sanity check. This method may only be called during tests.
|
||||
assertLinkerTestsAreEnabled();
|
||||
|
||||
synchronized (sSingletonLock) {
|
||||
// If this is the first call, configure the Linker to the given type and test class.
|
||||
if (sSingleton == null) {
|
||||
setImplementationForTesting(type);
|
||||
sSingleton.setTestRunnerClassNameForTesting(testRunnerClassName);
|
||||
return;
|
||||
}
|
||||
|
||||
// If not the first call, check that the Linker configuration matches this request.
|
||||
assertForTesting(sSingleton.getImplementationForTesting() == type);
|
||||
String ourTestRunnerClassName = sSingleton.getTestRunnerClassNameForTesting();
|
||||
if (testRunnerClassName == null) {
|
||||
assertForTesting(ourTestRunnerClassName == null);
|
||||
} else {
|
||||
assertForTesting(ourTestRunnerClassName.equals(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 LegacyLinker memory config, or 0 if unused
|
||||
* @param inBrowserProcess true if in the browser process
|
||||
*/
|
||||
protected 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");
|
||||
assertForTesting(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);
|
||||
assertForTesting(false);
|
||||
}
|
||||
|
||||
if (!testRunner.runChecks(memoryDeviceConfig, inBrowserProcess)) {
|
||||
Log.wtf(TAG, "Linker runtime tests failed in this process");
|
||||
assertForTesting(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();
|
||||
assertForTesting(memoryDeviceConfig == MEMORY_DEVICE_CONFIG_LOW
|
||||
|| memoryDeviceConfig == MEMORY_DEVICE_CONFIG_NORMAL);
|
||||
|
||||
synchronized (mLock) {
|
||||
assertForTesting(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.
|
||||
*/
|
||||
public boolean isChromiumLinkerLibrary(String library) {
|
||||
return library.equals(LINKER_JNI_LIBRARY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the Linker JNI library. Throws UnsatisfiedLinkError on error.
|
||||
*/
|
||||
protected static void loadLinkerJniLibrary() {
|
||||
String libName = "lib" + LINKER_JNI_LIBRARY + ".so";
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Loading " + libName);
|
||||
}
|
||||
System.loadLibrary(LINKER_JNI_LIBRARY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a random base load address at which to place loaded libraries.
|
||||
*
|
||||
* @return new base load address
|
||||
*/
|
||||
protected 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. 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 zipFilePath The path of the zip file containing the library (or null).
|
||||
* @param libFilePath The path of the library (possibly in the zip file).
|
||||
*/
|
||||
public void loadLibrary(@Nullable String zipFilePath, String libFilePath) {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "loadLibrary: " + zipFilePath + ", " + libFilePath);
|
||||
}
|
||||
final boolean isFixedAddressPermitted = true;
|
||||
loadLibraryImpl(zipFilePath, libFilePath, isFixedAddressPermitted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a native shared library with the Chromium linker, ignoring any
|
||||
* requested fixed address for RELRO sharing. 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 zipFilePath The path of the zip file containing the library (or null).
|
||||
* @param libFilePath The path of the library (possibly in the zip file).
|
||||
*/
|
||||
public void loadLibraryNoFixedAddress(@Nullable String zipFilePath, String libFilePath) {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "loadLibraryAtAnyAddress: " + zipFilePath + ", " + libFilePath);
|
||||
}
|
||||
final boolean isFixedAddressPermitted = false;
|
||||
loadLibraryImpl(zipFilePath, libFilePath, isFixedAddressPermitted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this method to determine if the chromium project must load the library
|
||||
* directly from a zip file.
|
||||
*/
|
||||
public static boolean isInZipFile() {
|
||||
// The auto-generated NativeLibraries.sUseLibraryInZipFile variable will be true
|
||||
// if the library remains embedded in the APK zip file on the target.
|
||||
return NativeLibraries.sUseLibraryInZipFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 isUsed() {
|
||||
// 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 linker will try to use shared RELROs
|
||||
* for the browser process.
|
||||
*/
|
||||
public abstract boolean isUsingBrowserSharedRelros();
|
||||
|
||||
/**
|
||||
* Call this method just before loading any native shared libraries in this process.
|
||||
*/
|
||||
public abstract void prepareLibraryLoad();
|
||||
|
||||
/**
|
||||
* Call this method just after loading all native shared libraries in this process.
|
||||
*/
|
||||
public abstract void finishLibraryLoad();
|
||||
|
||||
/**
|
||||
* 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 abstract void useSharedRelros(Bundle bundle);
|
||||
|
||||
/**
|
||||
* 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 abstract Bundle getSharedRelros();
|
||||
|
||||
|
||||
/**
|
||||
* Call this method before loading any libraries to indicate that this
|
||||
* process shall neither create or reuse shared RELRO sections.
|
||||
*/
|
||||
public abstract void disableSharedRelros();
|
||||
|
||||
/**
|
||||
* 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 abstract void initServiceProcess(long 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 abstract long getBaseLoadAddress();
|
||||
|
||||
/**
|
||||
* Implements loading a native shared library with the Chromium linker.
|
||||
*
|
||||
* @param zipFilePath The path of the zip file containing the library (or null).
|
||||
* @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.
|
||||
*/
|
||||
abstract void loadLibraryImpl(@Nullable String zipFilePath,
|
||||
String libFilePath,
|
||||
boolean isFixedAddressPermitted);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public static class LibInfo implements Parcelable {
|
||||
|
||||
public LibInfo() {
|
||||
mLoadAddress = 0;
|
||||
mLoadSize = 0;
|
||||
mRelroStart = 0;
|
||||
mRelroSize = 0;
|
||||
mRelroFd = -1;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (mRelroFd >= 0) {
|
||||
try {
|
||||
ParcelFileDescriptor.adoptFd(mRelroFd).close();
|
||||
} catch (java.io.IOException e) {
|
||||
if (DEBUG) {
|
||||
Log.e(TAG, "Failed to close fd: " + mRelroFd);
|
||||
}
|
||||
}
|
||||
mRelroFd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// from Parcelable
|
||||
public 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.
|
||||
mRelroFd = (fd == null) ? -1 : fd.detachFd();
|
||||
}
|
||||
|
||||
// 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];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(Locale.US,
|
||||
"[load=0x%x-0x%x relro=0x%x-0x%x fd=%d]",
|
||||
mLoadAddress,
|
||||
mLoadAddress + mLoadSize,
|
||||
mRelroStart,
|
||||
mRelroStart + mRelroSize,
|
||||
mRelroFd);
|
||||
}
|
||||
|
||||
// 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; // shared RELRO file descriptor, or -1
|
||||
}
|
||||
|
||||
// Create a Bundle from a map of LibInfo objects.
|
||||
protected 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.
|
||||
protected 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.
|
||||
protected void closeLibInfoMap(HashMap<String, LibInfo> map) {
|
||||
for (Map.Entry<String, LibInfo> entry : map.entrySet()) {
|
||||
entry.getValue().close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,485 @@
|
||||
// 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.os.Bundle;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import org.chromium.base.Log;
|
||||
import org.chromium.base.PathUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/*
|
||||
* For more, see Technical note, Security considerations, and the explanation
|
||||
* of how this class is supposed to be used in Linker.java.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides a concrete implementation of the Chromium Linker.
|
||||
*
|
||||
* This Linker implementation uses the Android M and later system linker to map and then
|
||||
* run Chrome for Android.
|
||||
*
|
||||
* For more on the operations performed by the Linker, see {@link Linker}.
|
||||
*/
|
||||
class ModernLinker extends Linker {
|
||||
// Log tag for this class.
|
||||
private static final String TAG = "LibraryLoader";
|
||||
|
||||
// Becomes true after linker initialization.
|
||||
private boolean mInitialized;
|
||||
|
||||
// Becomes true to indicate this process needs to wait for a shared RELRO in LibraryLoad().
|
||||
private boolean mWaitForSharedRelros;
|
||||
|
||||
// The map of all RELRO sections either created or used in this process.
|
||||
private HashMap<String, LibInfo> mSharedRelros;
|
||||
|
||||
// Cached Bundle representation of the RELRO sections map for transfer across processes.
|
||||
private Bundle mSharedRelrosBundle;
|
||||
|
||||
// Set to true if this runs in the browser process. Disabled by initServiceProcess().
|
||||
private boolean mInBrowserProcess = true;
|
||||
|
||||
// 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().
|
||||
// Initialized to mBaseLoadAddress in prepareLibraryLoad(), and then adjusted as each
|
||||
// library is loaded by loadLibrary().
|
||||
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;
|
||||
|
||||
// Private singleton constructor, and singleton factory method.
|
||||
private ModernLinker() { }
|
||||
static Linker create() {
|
||||
return new ModernLinker();
|
||||
}
|
||||
|
||||
// Used internally to initialize the linker's data. Assumes lock is held.
|
||||
private void ensureInitializedLocked() {
|
||||
assert Thread.holdsLock(mLock);
|
||||
assert NativeLibraries.sUseLinker;
|
||||
|
||||
// On first call, load libchromium_android_linker.so. Cannot be done in the
|
||||
// constructor because the instance is constructed on the UI thread.
|
||||
if (!mInitialized) {
|
||||
loadLinkerJniLibrary();
|
||||
mInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this method to determine if the linker will try to use shared RELROs
|
||||
* for the browser process.
|
||||
*/
|
||||
@Override
|
||||
public boolean isUsingBrowserSharedRelros() {
|
||||
// This Linker does not attempt to share RELROS between the browser and
|
||||
// the renderers, but only between renderers.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this method just before loading any native shared libraries in this process.
|
||||
* Loads the Linker's JNI library, and initializes the variables involved in the
|
||||
* implementation of shared RELROs.
|
||||
*/
|
||||
@Override
|
||||
public void prepareLibraryLoad() {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "prepareLibraryLoad() called");
|
||||
}
|
||||
assert NativeLibraries.sUseLinker;
|
||||
|
||||
synchronized (mLock) {
|
||||
assert !mPrepareLibraryLoadCalled;
|
||||
ensureInitializedLocked();
|
||||
|
||||
// If in the browser, generate a random base load address for service processes
|
||||
// and create an empty shared RELROs map. For service processes, the shared
|
||||
// RELROs map must remain null until set by useSharedRelros().
|
||||
if (mInBrowserProcess) {
|
||||
setupBaseLoadAddressLocked();
|
||||
mSharedRelros = new HashMap<String, LibInfo>();
|
||||
}
|
||||
|
||||
// Create an empty loaded libraries map.
|
||||
mLoadedLibraries = new HashMap<String, LibInfo>();
|
||||
|
||||
// Start the current load address at the base load address.
|
||||
mCurrentLoadAddress = mBaseLoadAddress;
|
||||
|
||||
mPrepareLibraryLoadCalled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this method just after loading all native shared libraries in this process.
|
||||
* If not in the browser, closes the LibInfo entries used for RELRO sharing.
|
||||
*/
|
||||
@Override
|
||||
public void finishLibraryLoad() {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "finishLibraryLoad() called");
|
||||
}
|
||||
|
||||
synchronized (mLock) {
|
||||
assert mPrepareLibraryLoadCalled;
|
||||
|
||||
// Close shared RELRO file descriptors if not in the browser.
|
||||
if (!mInBrowserProcess && mSharedRelros != null) {
|
||||
closeLibInfoMap(mSharedRelros);
|
||||
mSharedRelros = null;
|
||||
}
|
||||
|
||||
// If testing, run tests now that all libraries are loaded and initialized.
|
||||
if (NativeLibraries.sEnableLinkerTests) {
|
||||
runTestRunnerClassForTesting(0, mInBrowserProcess);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Used internally to wait for shared RELROs. Returns once useSharedRelros() has been
|
||||
// called to supply a valid shared RELROs bundle.
|
||||
private void waitForSharedRelrosLocked() {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "waitForSharedRelros called");
|
||||
}
|
||||
assert Thread.holdsLock(mLock);
|
||||
|
||||
// Return immediately if shared RELROs are already available.
|
||||
if (mSharedRelros != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait until notified by useSharedRelros() that shared RELROs have arrived.
|
||||
long startTime = DEBUG ? SystemClock.uptimeMillis() : 0;
|
||||
while (mSharedRelros == null) {
|
||||
try {
|
||||
mLock.wait();
|
||||
} catch (InterruptedException e) {
|
||||
// Restore the thread's interrupt status.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, String.format(
|
||||
Locale.US, "Time to wait for shared RELRO: %d ms",
|
||||
SystemClock.uptimeMillis() - startTime));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this to send a Bundle containing the shared RELRO sections to be
|
||||
* used in this process. If initServiceProcess() was previously called,
|
||||
* libraryLoad() will wait 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.
|
||||
*/
|
||||
@Override
|
||||
public void useSharedRelros(Bundle bundle) {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "useSharedRelros() called with " + bundle);
|
||||
}
|
||||
|
||||
synchronized (mLock) {
|
||||
mSharedRelros = createLibInfoMapFromBundle(bundle);
|
||||
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.
|
||||
*/
|
||||
@Override
|
||||
public Bundle getSharedRelros() {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "getSharedRelros() called");
|
||||
}
|
||||
synchronized (mLock) {
|
||||
if (!mInBrowserProcess) {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Not in browser, so returning null Bundle");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create a new Bundle containing RELRO section information for all the shared
|
||||
// RELROs created while loading libraries.
|
||||
if (mSharedRelrosBundle == null && mSharedRelros != null) {
|
||||
mSharedRelrosBundle = createBundleFromLibInfoMap(mSharedRelros);
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Shared RELRO bundle created from map: " + mSharedRelrosBundle);
|
||||
}
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Returning " + mSharedRelrosBundle);
|
||||
}
|
||||
return mSharedRelrosBundle;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Call this method before loading any libraries to indicate that this
|
||||
* process shall neither create or reuse shared RELRO sections.
|
||||
*/
|
||||
@Override
|
||||
public void disableSharedRelros() {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "disableSharedRelros() called");
|
||||
}
|
||||
synchronized (mLock) {
|
||||
// Mark this as a service process, and disable wait for shared RELRO.
|
||||
mInBrowserProcess = false;
|
||||
mWaitForSharedRelros = 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.
|
||||
*/
|
||||
@Override
|
||||
public void initServiceProcess(long baseLoadAddress) {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, String.format(
|
||||
Locale.US, "initServiceProcess(0x%x) called",
|
||||
baseLoadAddress));
|
||||
}
|
||||
synchronized (mLock) {
|
||||
assert !mPrepareLibraryLoadCalled;
|
||||
|
||||
// Mark this as a service process, and flag wait for shared RELRO.
|
||||
// Save the base load address passed in.
|
||||
mInBrowserProcess = false;
|
||||
mWaitForSharedRelros = true;
|
||||
mBaseLoadAddress = baseLoadAddress;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the base load address for libraries that share RELROs.
|
||||
*
|
||||
* @return a common, random base load address, or 0 if RELRO sharing is
|
||||
* disabled.
|
||||
*/
|
||||
@Override
|
||||
public long getBaseLoadAddress() {
|
||||
synchronized (mLock) {
|
||||
ensureInitializedLocked();
|
||||
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);
|
||||
|
||||
// No-op if the base load address is already set up.
|
||||
if (mBaseLoadAddress == -1) {
|
||||
mBaseLoadAddress = getRandomBaseLoadAddress();
|
||||
}
|
||||
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");
|
||||
mWaitForSharedRelros = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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. The library must not be the Chromium linker library.
|
||||
*
|
||||
* If asked to wait for shared RELROs, this function will block library loads
|
||||
* until the shared RELROs bundle is received by useSharedRelros().
|
||||
*
|
||||
* @param zipFilePath The path of the zip file containing the library (or null).
|
||||
* @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.
|
||||
*/
|
||||
@Override
|
||||
void loadLibraryImpl(@Nullable String zipFilePath,
|
||||
String libFilePath,
|
||||
boolean isFixedAddressPermitted) {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "loadLibraryImpl: "
|
||||
+ zipFilePath + ", " + libFilePath + ", " + isFixedAddressPermitted);
|
||||
}
|
||||
|
||||
synchronized (mLock) {
|
||||
assert mPrepareLibraryLoadCalled;
|
||||
|
||||
String dlopenExtPath;
|
||||
if (zipFilePath != null) {
|
||||
// The android_dlopen_ext() function understands strings with the format
|
||||
// <zip_path>!/<file_path> to represent the file_path element within the zip
|
||||
// file at zip_path. This enables directly loading from APK. We add the
|
||||
// "crazy." prefix to the path in the zip file to prevent the Android package
|
||||
// manager from seeing this as a library and so extracting it from the APK.
|
||||
String cpuAbi = nativeGetCpuAbi();
|
||||
dlopenExtPath = zipFilePath + "!/lib/" + cpuAbi + "/crazy." + libFilePath;
|
||||
} else {
|
||||
// Not loading from APK directly, so simply pass the library name to
|
||||
// android_dlopen_ext().
|
||||
dlopenExtPath = libFilePath;
|
||||
}
|
||||
|
||||
if (mLoadedLibraries.containsKey(dlopenExtPath)) {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Not loading " + libFilePath + " twice");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If not in the browser, shared RELROs are not disabled, and fixed addresses
|
||||
// have not been disallowed, load the library at a fixed address. Otherwise,
|
||||
// load anywhere.
|
||||
long loadAddress = 0;
|
||||
if (!mInBrowserProcess && mWaitForSharedRelros && isFixedAddressPermitted) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
LibInfo libInfo = new LibInfo();
|
||||
|
||||
if (mInBrowserProcess && mCurrentLoadAddress != 0) {
|
||||
// We are in the browser, and with a current load address that indicates that
|
||||
// there is enough address space for shared RELRO to operate. Create the
|
||||
// shared RELRO, and store it in the map.
|
||||
String relroPath = PathUtils.getDataDirectory() + "/RELRO:" + libFilePath;
|
||||
if (nativeCreateSharedRelro(dlopenExtPath,
|
||||
mCurrentLoadAddress, relroPath, libInfo)) {
|
||||
mSharedRelros.put(dlopenExtPath, libInfo);
|
||||
} else {
|
||||
String errorMessage = "Unable to create shared relro: " + relroPath;
|
||||
Log.w(TAG, errorMessage);
|
||||
}
|
||||
} else if (!mInBrowserProcess && mCurrentLoadAddress != 0 && mWaitForSharedRelros) {
|
||||
// We are in a service process, again with a current load address that is
|
||||
// suitable for shared RELRO, and we are to wait for shared RELROs. So
|
||||
// do that, then use the map we receive to provide libinfo for library load.
|
||||
waitForSharedRelrosLocked();
|
||||
if (mSharedRelros.containsKey(dlopenExtPath)) {
|
||||
libInfo = mSharedRelros.get(dlopenExtPath);
|
||||
}
|
||||
}
|
||||
|
||||
// Load the library. In the browser, loadAddress is 0, so nativeLoadLibrary()
|
||||
// will load without shared RELRO. Otherwise, it uses shared RELRO if the attached
|
||||
// libInfo is usable.
|
||||
if (!nativeLoadLibrary(dlopenExtPath, loadAddress, libInfo)) {
|
||||
String errorMessage = "Unable to load library: " + dlopenExtPath;
|
||||
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 (loadAddress != 0 && mCurrentLoadAddress != 0) {
|
||||
// Compute the next current load address. If mCurrentLoadAddress
|
||||
// is not 0, this is an explicit library load address.
|
||||
mCurrentLoadAddress = libInfo.mLoadAddress + libInfo.mLoadSize
|
||||
+ BREAKPAD_GUARD_REGION_BYTES;
|
||||
}
|
||||
|
||||
mLoadedLibraries.put(dlopenExtPath, libInfo);
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Library details " + libInfo.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Native method to return the CPU ABI.
|
||||
* Obtaining it from the linker's native code means that we always correctly
|
||||
* match the loaded library's ABI to the linker's ABI.
|
||||
*
|
||||
* @return CPU ABI string.
|
||||
*/
|
||||
private static native String nativeGetCpuAbi();
|
||||
|
||||
/**
|
||||
* Native method used to load a library.
|
||||
*
|
||||
* @param dlopenExtPath For load from APK, the path to the enclosing
|
||||
* zipfile concatenated with "!/" and the path to the library within the zipfile;
|
||||
* otherwise the 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 dlopenExtPath,
|
||||
long loadAddress,
|
||||
LibInfo libInfo);
|
||||
|
||||
/**
|
||||
* Native method used to create a shared RELRO section.
|
||||
* Creates a shared RELRO file for the given library. Done by loading a
|
||||
* a new temporary library at the specified address, saving the RELRO section
|
||||
* from it, then unloading.
|
||||
*
|
||||
* @param dlopenExtPath For load from APK, the path to the enclosing
|
||||
* zipfile concatenated with "!/" and the path to the library within the zipfile;
|
||||
* otherwise the platform specific library name (e.g. libfoo.so).
|
||||
* @param loadAddress load address, which can be different from the one
|
||||
* used to load the library in the current process!
|
||||
* @param relroPath Path to the shared RELRO file for this library.
|
||||
* @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 dlopenExtPath,
|
||||
long loadAddress,
|
||||
String relroPath,
|
||||
LibInfo libInfo);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
// 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.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.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.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);
|
||||
mTimeUnit = timeUnit;
|
||||
}
|
||||
|
||||
public void record(long sample) {
|
||||
synchronized (CachedMetric.sMetrics) {
|
||||
if (LibraryLoader.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.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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
// 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 UMA_HISTOGRAM_SPARSE_SLOWLY.
|
||||
* @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.
|
||||
* @param name name of the histogram
|
||||
* @param duration duration to be recorded
|
||||
* @param timeUnit the unit of the duration argument
|
||||
*/
|
||||
public static void recordTimesHistogram(String name, long duration, TimeUnit 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.
|
||||
* @param name name of the histogram
|
||||
* @param duration duration to be recorded
|
||||
* @param timeUnit the unit of the duration argument
|
||||
*/
|
||||
public static void recordMediumTimesHistogram(String name, long duration, TimeUnit 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.
|
||||
* @param name name of the histogram
|
||||
* @param duration duration to be recorded
|
||||
* @param timeUnit the unit of the duration argument
|
||||
*/
|
||||
public static void recordLongTimesHistogram(String name, long duration, TimeUnit timeUnit) {
|
||||
recordCustomTimesHistogramMilliseconds(
|
||||
name, timeUnit.toMillis(duration), 1, TimeUnit.HOURS.toMillis(1), 50);
|
||||
}
|
||||
|
||||
/**
|
||||
* Records a sample in a histogram of times with custom buckets. This is the Java equivalent of
|
||||
* the UMA_HISTOGRAM_CUSTOM_TIMES C++ macro.
|
||||
* @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
|
||||
* @param numBuckets the number of buckets
|
||||
*/
|
||||
public static void recordCustomTimesHistogram(
|
||||
String name, long duration, long min, long max, TimeUnit timeUnit, int numBuckets) {
|
||||
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);
|
||||
}
|
||||
|
||||
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.
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the metrics system.
|
||||
*/
|
||||
public static void initialize() {
|
||||
if (sDisabledBy != null) return;
|
||||
nativeInitialize();
|
||||
}
|
||||
|
||||
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);
|
||||
private static native void nativeInitialize();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// 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.app.ActivityManager;
|
||||
import android.app.ActivityManager.RunningAppProcessInfo;
|
||||
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.Log;
|
||||
import org.chromium.base.VisibleForTesting;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Performs multidex installation for non-isolated processes.
|
||||
*/
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
private static String getProcessName(Context context) {
|
||||
try {
|
||||
String currentProcessName = null;
|
||||
int pid = android.os.Process.myPid();
|
||||
|
||||
ActivityManager manager =
|
||||
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
for (RunningAppProcessInfo processInfo : manager.getRunningAppProcesses()) {
|
||||
if (processInfo.pid == pid) {
|
||||
currentProcessName = processInfo.processName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return currentProcessName;
|
||||
} catch (SecurityException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
try {
|
||||
Method isIsolatedMethod =
|
||||
android.os.Process.class.getMethod("isIsolated");
|
||||
Object retVal = isIsolatedMethod.invoke(null);
|
||||
if (retVal != null && retVal instanceof Boolean && ((Boolean) retVal)) {
|
||||
return false;
|
||||
}
|
||||
} catch (IllegalAccessException | IllegalArgumentException
|
||||
| InvocationTargetException | NoSuchMethodException e) {
|
||||
// Ignore and fall back to checking the app processes.
|
||||
}
|
||||
|
||||
String currentProcessName = getProcessName(context);
|
||||
if (currentProcessName == null) return true;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
// 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.ObserverList;
|
||||
import org.chromium.base.VisibleForTesting;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 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";
|
||||
|
||||
/** Listener that clients can use to get notified when connections get allocated/freed. */
|
||||
public abstract static class Listener {
|
||||
/** Called when a connection has been allocated, before it gets bound. */
|
||||
public void onConnectionAllocated(
|
||||
ChildConnectionAllocator allocator, ChildProcessConnection connection) {}
|
||||
|
||||
/** Called when a connection has been freed. */
|
||||
public void onConnectionFreed(
|
||||
ChildConnectionAllocator allocator, ChildProcessConnection connection) {}
|
||||
}
|
||||
|
||||
/** 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;
|
||||
|
||||
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 ObserverList<Listener> mListeners = new ObserverList<>();
|
||||
|
||||
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,
|
||||
String packageName, String serviceClassNameManifestKey,
|
||||
String numChildServicesManifestKey, boolean bindToCaller, boolean bindAsExternalService,
|
||||
boolean useStrongBinding) {
|
||||
String serviceClassName = null;
|
||||
int numServices = -1;
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
try {
|
||||
ApplicationInfo appInfo =
|
||||
packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
|
||||
if (appInfo.metaData != null) {
|
||||
serviceClassName = appInfo.metaData.getString(serviceClassNameManifestKey);
|
||||
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, packageName, serviceClassName,
|
||||
bindToCaller, bindAsExternalService, useStrongBinding, numServices);
|
||||
}
|
||||
|
||||
// TODO(jcivelli): remove this method once crbug.com/693484 has been addressed.
|
||||
public static int getNumberOfServices(
|
||||
Context context, String packageName, String numChildServicesManifestKey) {
|
||||
int numServices = -1;
|
||||
try {
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
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", e);
|
||||
}
|
||||
if (numServices < 0) {
|
||||
throw new RuntimeException("Illegal meta data value for number of child services");
|
||||
}
|
||||
return 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(String packageName,
|
||||
String serviceClassName, int serviceCount, boolean bindToCaller,
|
||||
boolean bindAsExternalService, boolean useStrongBinding) {
|
||||
return new ChildConnectionAllocator(new Handler(), packageName, serviceClassName,
|
||||
bindToCaller, bindAsExternalService, useStrongBinding, serviceCount);
|
||||
}
|
||||
|
||||
private ChildConnectionAllocator(Handler launcherHandler, String packageName,
|
||||
String serviceClassName, boolean bindToCaller, boolean bindAsExternalService,
|
||||
boolean useStrongBinding, int numChildServices) {
|
||||
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;
|
||||
|
||||
for (Listener listener : mListeners) {
|
||||
listener.onConnectionAllocated(this, 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);
|
||||
}
|
||||
|
||||
for (Listener listener : mListeners) {
|
||||
listener.onConnectionFreed(this, connection);
|
||||
}
|
||||
}
|
||||
|
||||
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 void addListener(Listener listener) {
|
||||
assert !mListeners.hasObserver(listener);
|
||||
mListeners.addObserver(listener);
|
||||
}
|
||||
|
||||
public void removeListener(Listener listener) {
|
||||
boolean removed = mListeners.removeObserver(listener);
|
||||
assert removed;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,644 @@
|
||||
// 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.Log;
|
||||
import org.chromium.base.TraceEvent;
|
||||
import org.chromium.base.VisibleForTesting;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Manages a connection between the browser activity and a child service.
|
||||
*/
|
||||
public class ChildProcessConnection {
|
||||
private static final String TAG = "ChildProcessConn";
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// Inital moderate binding.
|
||||
private final ChildServiceConnection mInitialBinding;
|
||||
|
||||
// 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.
|
||||
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;
|
||||
|
||||
// Indicates whether the connection only has the waived binding (if the connection is unbound,
|
||||
// it contains the state at time of unbinding).
|
||||
private boolean mWaivedBoundOnly;
|
||||
|
||||
// Set to true once unbind() was called.
|
||||
private boolean mUnbound;
|
||||
|
||||
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);
|
||||
|
||||
mInitialBinding = connectionFactory.createConnection(intent, defaultFlags, delegate);
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// 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 = useStrongBinding ? mStrongBinding.bind() : mInitialBinding.bind();
|
||||
if (!success) return false;
|
||||
|
||||
updateWaivedBoundOnlyState();
|
||||
mWaivedBinding.bind();
|
||||
return true;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected void unbind() {
|
||||
assert isRunningOnLauncherThread();
|
||||
mService = null;
|
||||
mConnectionParams = null;
|
||||
mUnbound = true;
|
||||
mStrongBinding.unbind();
|
||||
mWaivedBinding.unbind();
|
||||
mModerateBinding.unbind();
|
||||
mInitialBinding.unbind();
|
||||
// Note that we don't update the waived bound only state here as to preserve the state when
|
||||
// disconnected.
|
||||
}
|
||||
|
||||
public boolean isInitialBindingBound() {
|
||||
assert isRunningOnLauncherThread();
|
||||
return mInitialBinding.isBound();
|
||||
}
|
||||
|
||||
public void addInitialBinding() {
|
||||
assert isRunningOnLauncherThread();
|
||||
mInitialBinding.bind();
|
||||
updateWaivedBoundOnlyState();
|
||||
}
|
||||
|
||||
public boolean isStrongBindingBound() {
|
||||
assert isRunningOnLauncherThread();
|
||||
return mStrongBinding.isBound();
|
||||
}
|
||||
|
||||
public void removeInitialBinding() {
|
||||
assert isRunningOnLauncherThread();
|
||||
mInitialBinding.unbind();
|
||||
updateWaivedBoundOnlyState();
|
||||
}
|
||||
|
||||
public void addStrongBinding() {
|
||||
assert isRunningOnLauncherThread();
|
||||
if (!isConnected()) {
|
||||
Log.w(TAG, "The connection is not bound for %d", getPid());
|
||||
return;
|
||||
}
|
||||
if (mStrongBindingCount == 0) {
|
||||
mStrongBinding.bind();
|
||||
updateWaivedBoundOnlyState();
|
||||
}
|
||||
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();
|
||||
updateWaivedBoundOnlyState();
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
updateWaivedBoundOnlyState();
|
||||
}
|
||||
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();
|
||||
updateWaivedBoundOnlyState();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 boolean isWaivedBoundOnlyOrWasWhenDied() {
|
||||
// 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.
|
||||
return mWaivedBoundOnly;
|
||||
}
|
||||
|
||||
// Should be called every time the mInitialBinding or mStrongBinding are bound/unbound.
|
||||
private void updateWaivedBoundOnlyState() {
|
||||
if (!mUnbound) {
|
||||
mWaivedBoundOnly = !mInitialBinding.isBound() && !mStrongBinding.isBound()
|
||||
&& !mModerateBinding.isBound();
|
||||
}
|
||||
}
|
||||
|
||||
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.crashIntentionallyForTesting();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public boolean didOnServiceConnectedForTesting() {
|
||||
return mDidOnServiceConnected;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected Handler getLauncherHandler() {
|
||||
return mLauncherHandler;
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
// 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;
|
||||
}
|
||||
// No connection is available at this time. Add a listener so when one becomes
|
||||
// available we can create the service.
|
||||
mConnectionAllocator.addListener(new ChildConnectionAllocator.Listener() {
|
||||
@Override
|
||||
public void onConnectionFreed(
|
||||
ChildConnectionAllocator allocator, ChildProcessConnection connection) {
|
||||
assert allocator == mConnectionAllocator;
|
||||
if (!allocator.isFreeConnectionAvailable()) return;
|
||||
allocator.removeListener(this);
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// 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.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public abstract class ChildProcessService extends Service {
|
||||
private final ChildProcessServiceImpl mChildProcessServiceImpl;
|
||||
|
||||
protected ChildProcessService(ChildProcessServiceDelegate delegate) {
|
||||
mChildProcessServiceImpl = new ChildProcessServiceImpl(delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
mChildProcessServiceImpl.create(getApplicationContext(), getApplicationContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
mChildProcessServiceImpl.destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
// 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();
|
||||
return mChildProcessServiceImpl.bind(intent, -1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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. */
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
// 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.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.Parcel;
|
||||
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.annotations.JNINamespace;
|
||||
import org.chromium.base.annotations.MainDex;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
|
||||
/**
|
||||
* This class implements all of the functionality for {@link ChildProcessService} which owns an
|
||||
* object of {@link ChildProcessServiceImpl}.
|
||||
* It makes it possible for other consumer services (such as WebAPKs) to reuse that logic.
|
||||
*/
|
||||
@JNINamespace("base::android")
|
||||
@MainDex
|
||||
public class ChildProcessServiceImpl {
|
||||
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;
|
||||
|
||||
/**
|
||||
* If >= 0 enables "validation of caller of {@link mBinder}'s methods". A RemoteException
|
||||
* is thrown when an application with a uid other than {@link mAuthorizedCallerUid} calls
|
||||
* {@link mBinder}'s methods.
|
||||
* Only set once in {@link bind}, does not require synchronization.
|
||||
*/
|
||||
private int mAuthorizedCallerUid;
|
||||
|
||||
private final Semaphore mActivitySemaphore = new Semaphore(1);
|
||||
|
||||
public ChildProcessServiceImpl(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 crashIntentionallyForTesting() {
|
||||
assert mServiceBound;
|
||||
Process.killProcess(Process.myPid());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTransact(int arg0, Parcel arg1, Parcel arg2, int arg3)
|
||||
throws RemoteException {
|
||||
assert mServiceBound;
|
||||
if (mAuthorizedCallerUid >= 0) {
|
||||
int callingUid = Binder.getCallingUid();
|
||||
if (callingUid != mAuthorizedCallerUid) {
|
||||
throw new RemoteException("Unauthorized caller " + callingUid
|
||||
+ "does not match expected host=" + mAuthorizedCallerUid);
|
||||
}
|
||||
}
|
||||
return super.onTransact(arg0, arg1, arg2, arg3);
|
||||
}
|
||||
};
|
||||
|
||||
// The ClassLoader for the host context.
|
||||
private ClassLoader mHostClassLoader;
|
||||
private Context mHostContext;
|
||||
|
||||
/**
|
||||
* Loads Chrome's native libraries and initializes a ChildProcessServiceImpl.
|
||||
* @param context The application context.
|
||||
* @param hostContext The host context the library should be loaded with (i.e. Chrome).
|
||||
*/
|
||||
// For sCreateCalled check.
|
||||
public void create(final Context context, final Context hostContext) {
|
||||
mHostClassLoader = hostContext.getClassLoader();
|
||||
mHostContext = hostContext;
|
||||
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 ChildProcessServiceImpl object.
|
||||
ContextUtils.initApplicationContext(context);
|
||||
|
||||
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(hostContext);
|
||||
} 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();
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
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.
|
||||
* @param authorizedCallerUid If >= 0, enables "validation of service caller". A RemoteException
|
||||
* is thrown when an application with a uid other than {@link authorizedCallerUid} calls the
|
||||
* service's methods.
|
||||
* @return the binder used by the client to setup the connection.
|
||||
*/
|
||||
public IBinder bind(Intent intent, int authorizedCallerUid) {
|
||||
assert !mServiceBound;
|
||||
mAuthorizedCallerUid = authorizedCallerUid;
|
||||
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(mHostContext));
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
private void processConnectionBundle(Bundle bundle, List<IBinder> clientInterfaces) {
|
||||
// Required to unparcel FileDescriptorInfo.
|
||||
bundle.setClassLoader(mHostClassLoader);
|
||||
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();
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.base.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);
|
||||
|
||||
// Asks the child service to crash so that we can test the termination logic.
|
||||
oneway void crashIntentionallyForTesting();
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
boliu@chromium.org
|
||||
jcivelli@chromium.org
|
||||
|
||||
per-file *.aidl=set noparent
|
||||
per-file *.aidl=file://ipc/SECURITY_OWNERS
|
||||
49
base/android/java/templates/BuildConfig.template
Normal file
49
base/android/java/templates/BuildConfig.template
Normal file
@@ -0,0 +1,49 @@
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* Build configuration. Generated on a per-target basis.
|
||||
*/
|
||||
public class BuildConfig {
|
||||
|
||||
/** Whether multidex is enabled for this target.
|
||||
*
|
||||
* This has to be a function instead of a static final boolean s.t. the initial false value
|
||||
* doesn't get optimized into {@link ChromiumMultiDexInstaller} at base_java compile time.
|
||||
*/
|
||||
public static boolean isMultidexEnabled() {
|
||||
#if defined(ENABLE_MULTIDEX)
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// DCHECK_IS_ON does not change between targets, can be final and optimized out.
|
||||
#if defined(_DCHECK_IS_ON)
|
||||
public static final boolean DCHECK_IS_ON = true;
|
||||
#else
|
||||
public static final boolean DCHECK_IS_ON = false;
|
||||
#endif
|
||||
|
||||
// Sorted list of locales that have a compressed .pak within assets.
|
||||
// Stored as an array because AssetManager.list() is slow.
|
||||
public static final String[] COMPRESSED_LOCALES =
|
||||
#if defined(COMPRESSED_LOCALE_LIST)
|
||||
COMPRESSED_LOCALE_LIST;
|
||||
#else
|
||||
{};
|
||||
#endif
|
||||
|
||||
// Sorted list of locales that have an uncompressed .pak within assets.
|
||||
// Stored as an array because AssetManager.list() is slow.
|
||||
public static final String[] UNCOMPRESSED_LOCALES =
|
||||
#if defined(UNCOMPRESSED_LOCALE_LIST)
|
||||
UNCOMPRESSED_LOCALE_LIST;
|
||||
#else
|
||||
{};
|
||||
#endif
|
||||
}
|
||||
92
base/android/java/templates/NativeLibraries.template
Normal file
92
base/android/java/templates/NativeLibraries.template
Normal file
@@ -0,0 +1,92 @@
|
||||
// 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.
|
||||
*/
|
||||
|
||||
#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
|
||||
}
|
||||
Reference in New Issue
Block a user