Import chromium-64.0.3282.140

This commit is contained in:
klzgrad
2018-02-02 05:49:39 -05:00
commit 86b64329f6
19589 changed files with 4029211 additions and 0 deletions

View 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;
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View 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);
}
}
}
}

View 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);
}

View File

@@ -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);
}
}
}

View 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() {}
}

View 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
}

View 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));
}
}
}

View 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);
}
}
}

View 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);
}

View 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;
}
}

View 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;
}
}

View 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();
}
}

View 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();
}

View File

@@ -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();
}
}

View 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);
}

View 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);
}
}

View 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);
}

View 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;
}
}

View File

@@ -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);
}

View 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;
}
}

View File

@@ -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);
}

View 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);
}

View 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();
}
}

View 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();
}
}

View File

@@ -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);
}

View 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();
}
}

View 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();
}
}
}
}

View 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
}
}

View 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);
}

View 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();
}
}

View 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();
}

View 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));
}
}

View 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;
}
}

View File

@@ -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);
}
}
}

View 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.
}
}
}

View File

@@ -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);
}
}
}

View 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();
}

View File

@@ -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);
}

View 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;
}
}

View File

@@ -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");
}
});
}
}

View 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();
}

View 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;
}
}

View 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);
}

View File

@@ -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;
}
};

View File

@@ -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 {
}

View File

@@ -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 "";
}

View File

@@ -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 "";
}

View File

@@ -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 "";
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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 {
}

View File

@@ -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 "";
}

View File

@@ -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();
}

View File

@@ -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 {}

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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);
}

View 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.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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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";
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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];
}
};
}

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -0,0 +1,5 @@
boliu@chromium.org
jcivelli@chromium.org
per-file *.aidl=set noparent
per-file *.aidl=file://ipc/SECURITY_OWNERS

View 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
}

View 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
}