diff options
author | Justin Klaassen <justinklaassen@google.com> | 2018-04-03 23:21:57 -0400 |
---|---|---|
committer | Justin Klaassen <justinklaassen@google.com> | 2018-04-03 23:21:57 -0400 |
commit | 4d01eeaffaa720e4458a118baa137a11614f00f7 (patch) | |
tree | 66751893566986236788e3c796a7cc5e90d05f52 /android/app | |
parent | a192cc2a132cb0ee8588e2df755563ec7008c179 (diff) | |
download | android-28-4d01eeaffaa720e4458a118baa137a11614f00f7.tar.gz |
Import Android SDK Platform P [4697573]
/google/data/ro/projects/android/fetch_artifact \
--bid 4697573 \
--target sdk_phone_armv7-win_sdk \
sdk-repo-linux-sources-4697573.zip
AndroidVersion.ApiLevel has been modified to appear as 28
Change-Id: If80578c3c657366cc9cf75f8db13d46e2dd4e077
Diffstat (limited to 'android/app')
90 files changed, 7731 insertions, 2579 deletions
diff --git a/android/app/Activity.java b/android/app/Activity.java index cd029c06..20149dec 100644 --- a/android/app/Activity.java +++ b/android/app/Activity.java @@ -114,6 +114,7 @@ import android.view.Window.WindowControllerCallback; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityEvent; +import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillManager.AutofillClient; import android.view.autofill.AutofillPopupWindow; @@ -123,17 +124,20 @@ import android.widget.Toast; import android.widget.Toolbar; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IVoiceInteractor; import com.android.internal.app.ToolbarActionBar; import com.android.internal.app.WindowDecorActionBar; -import com.android.internal.policy.DecorView; import com.android.internal.policy.PhoneWindow; +import dalvik.system.VMRuntime; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -421,9 +425,15 @@ import java.util.List; * vs. those targeting prior platforms. Starting with Honeycomb, an application * is not in the killable state until its {@link #onStop} has returned. This * impacts when {@link #onSaveInstanceState(Bundle)} may be called (it may be - * safely called after {@link #onPause()} and allows and application to safely + * safely called after {@link #onPause()}) and allows an application to safely * wait until {@link #onStop()} to save persistent state.</p> * + * <p class="note">For applications targeting platforms starting with + * {@link android.os.Build.VERSION_CODES#P} {@link #onSaveInstanceState(Bundle)} + * will always be called after {@link #onStop}, so an application may safely + * perform fragment transactions in {@link #onStop} and will be able to save + * persistent state later.</p> + * * <p>For those methods that are not marked as being killable, the activity's * process will not be killed by the system starting from the time the method * is called and continuing after it returns. Thus an activity is in the killable @@ -647,13 +657,13 @@ import java.util.List; * <a name="ProcessLifecycle"></a> * <h3>Process Lifecycle</h3> * - * <p>The Android system attempts to keep application process around for as + * <p>The Android system attempts to keep an application process around for as * long as possible, but eventually will need to remove old processes when - * memory runs low. As described in <a href="#ActivityLifecycle">Activity + * memory runs low. As described in <a href="#ActivityLifecycle">Activity * Lifecycle</a>, the decision about which process to remove is intimately - * tied to the state of the user's interaction with it. In general, there + * tied to the state of the user's interaction with it. In general, there * are four states a process can be in based on the activities running in it, - * listed here in order of importance. The system will kill less important + * listed here in order of importance. The system will kill less important * processes (the last ones) before it resorts to killing more important * processes (the first ones). * @@ -742,6 +752,14 @@ public class Activity extends ContextThemeWrapper private static final String KEYBOARD_SHORTCUTS_RECEIVER_PKG_NAME = "com.android.systemui"; + private static final int LOG_AM_ON_CREATE_CALLED = 30057; + private static final int LOG_AM_ON_START_CALLED = 30059; + private static final int LOG_AM_ON_RESUME_CALLED = 30022; + private static final int LOG_AM_ON_PAUSE_CALLED = 30021; + private static final int LOG_AM_ON_STOP_CALLED = 30049; + private static final int LOG_AM_ON_RESTART_CALLED = 30058; + private static final int LOG_AM_ON_DESTROY_CALLED = 30060; + private static class ManagedDialog { Dialog mDialog; Bundle mArgs; @@ -978,9 +996,9 @@ public class Activity extends ContextThemeWrapper * cursors for data being displayed, etc. * * <p>You can call {@link #finish} from within this function, in - * which case onDestroy() will be immediately called without any of the rest - * of the activity lifecycle ({@link #onStart}, {@link #onResume}, - * {@link #onPause}, etc) executing. + * which case onDestroy() will be immediately called after {@link #onCreate} without any of the + * rest of the activity lifecycle ({@link #onStart}, {@link #onResume}, {@link #onPause}, etc) + * executing. * * <p><em>Derived classes must call through to the super class's * implementation of this method. If they do not, an exception will be @@ -1383,6 +1401,7 @@ public class Activity extends ContextThemeWrapper * * {@hide} */ + @Override public int getNextAutofillId() { if (mLastAutofillId == Integer.MAX_VALUE - 1) { mLastAutofillId = View.LAST_APP_AUTOFILL_ID; @@ -1394,6 +1413,14 @@ public class Activity extends ContextThemeWrapper } /** + * @hide + */ + @Override + public AutofillId autofillClientGetNextAutofillId() { + return new AutofillId(getNextAutofillId()); + } + + /** * Check whether this activity is running as part of a voice interaction with the user. * If true, it should perform its interaction with the user through the * {@link VoiceInteractor} returned by {@link #getVoiceInteractor}. @@ -1576,8 +1603,11 @@ public class Activity extends ContextThemeWrapper * call through to the default implementation, otherwise be prepared to save * all of the state of each view yourself. * - * <p>If called, this method will occur before {@link #onStop}. There are - * no guarantees about whether it will occur before or after {@link #onPause}. + * <p>If called, this method will occur after {@link #onStop} for applications + * targeting platforms starting with {@link android.os.Build.VERSION_CODES#P}. + * For applications targeting earlier platform versions this method will occur + * before {@link #onStop} and there are no guarantees about whether it will + * occur before or after {@link #onPause}. * * @param outState Bundle in which to place your saved state. * @@ -1723,7 +1753,7 @@ public class Activity extends ContextThemeWrapper * * <p>This callback and {@link #onUserInteraction} are intended to help * activities manage status bar notifications intelligently; specifically, - * for helping activities determine the proper time to cancel a notfication. + * for helping activities determine the proper time to cancel a notification. * * @see #onUserInteraction() */ @@ -1731,32 +1761,16 @@ public class Activity extends ContextThemeWrapper } /** - * Generate a new thumbnail for this activity. This method is called before - * pausing the activity, and should draw into <var>outBitmap</var> the - * imagery for the desired thumbnail in the dimensions of that bitmap. It - * can use the given <var>canvas</var>, which is configured to draw into the - * bitmap, for rendering if desired. - * - * <p>The default implementation returns fails and does not draw a thumbnail; - * this will result in the platform creating its own thumbnail if needed. - * - * @param outBitmap The bitmap to contain the thumbnail. - * @param canvas Can be used to render into the bitmap. - * - * @return Return true if you have drawn into the bitmap; otherwise after - * you return it will be filled with a default thumbnail. - * - * @see #onCreateDescription - * @see #onSaveInstanceState - * @see #onPause + * @deprecated Method doesn't do anything and will be removed in the future. */ + @Deprecated public boolean onCreateThumbnail(Bitmap outBitmap, Canvas canvas) { return false; } /** * Generate a new description for this activity. This method is called - * before pausing the activity and can, if desired, return some textual + * before stopping the activity and can, if desired, return some textual * description of its current state to be displayed to the user. * * <p>The default implementation returns null, which will cause you to @@ -1767,9 +1781,8 @@ public class Activity extends ContextThemeWrapper * @return A description of what the user is doing. It should be short and * sweet (only a few words). * - * @see #onCreateThumbnail * @see #onSaveInstanceState - * @see #onPause + * @see #onStop */ @Nullable public CharSequence onCreateDescription() { @@ -1905,7 +1918,7 @@ public class Activity extends ContextThemeWrapper if (isFinishing()) { if (mAutoFillResetNeeded) { - getAutofillManager().onActivityFinished(); + getAutofillManager().onActivityFinishing(); } else if (mIntent != null && mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)) { // Activity was launched when user tapped a link in the Autofill Save UI - since @@ -2136,11 +2149,15 @@ public class Activity extends ContextThemeWrapper * @param params non-null parameters to be combined with previously set parameters when entering * picture-in-picture. * - * @return true if the system puts this activity into picture-in-picture mode or was already - * in picture-in-picture mode (@see {@link #isInPictureInPictureMode()) + * @return true if the system successfully put this activity into picture-in-picture mode or was + * already in picture-in-picture mode (@see {@link #isInPictureInPictureMode()). If the device + * does not support picture-in-picture, return false. */ public boolean enterPictureInPictureMode(@NonNull PictureInPictureParams params) { try { + if (!deviceSupportsPictureInPictureMode()) { + return false; + } if (params == null) { throw new IllegalArgumentException("Expected non-null picture-in-picture params"); } @@ -2168,6 +2185,9 @@ public class Activity extends ContextThemeWrapper */ public void setPictureInPictureParams(@NonNull PictureInPictureParams params) { try { + if (!deviceSupportsPictureInPictureMode()) { + return; + } if (params == null) { throw new IllegalArgumentException("Expected non-null picture-in-picture params"); } @@ -2190,6 +2210,13 @@ public class Activity extends ContextThemeWrapper } } + /** + * @return Whether this device supports picture-in-picture. + */ + private boolean deviceSupportsPictureInPictureMode() { + return getPackageManager().hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE); + } + void dispatchMovedToDisplay(int displayId, Configuration config) { updateDisplay(displayId); onMovedToDisplay(displayId, config); @@ -4135,9 +4162,10 @@ public class Activity extends ContextThemeWrapper * <p>You can override this function to force global search, e.g. in response to a dedicated * search key, or to block search entirely (by simply returning false). * - * <p>Note: when running in a {@link Configuration#UI_MODE_TYPE_TELEVISION}, the default - * implementation changes to simply return false and you must supply your own custom - * implementation if you want to support search.</p> + * <p>Note: when running in a {@link Configuration#UI_MODE_TYPE_TELEVISION} or + * {@link Configuration#UI_MODE_TYPE_WATCH}, the default implementation changes to simply + * return false and you must supply your own custom implementation if you want to support + * search. * * @param searchEvent The {@link SearchEvent} that signaled this search. * @return Returns {@code true} if search launched, and {@code false} if the activity does @@ -4157,8 +4185,10 @@ public class Activity extends ContextThemeWrapper * @see #onSearchRequested(SearchEvent) */ public boolean onSearchRequested() { - if ((getResources().getConfiguration().uiMode&Configuration.UI_MODE_TYPE_MASK) - != Configuration.UI_MODE_TYPE_TELEVISION) { + final int uiMode = getResources().getConfiguration().uiMode + & Configuration.UI_MODE_TYPE_MASK; + if (uiMode != Configuration.UI_MODE_TYPE_TELEVISION + && uiMode != Configuration.UI_MODE_TYPE_WATCH) { startSearch(null, false, null, false); return true; } else { @@ -4187,6 +4217,9 @@ public class Activity extends ContextThemeWrapper * is to inject specific data such as context data, it is preferred to <i>override</i> * onSearchRequested(), so that any callers to it will benefit from the override. * + * <p>Note: when running in a {@link Configuration#UI_MODE_TYPE_WATCH}, use of this API is + * not supported. + * * @param initialQuery Any non-null non-empty string will be inserted as * pre-entered text in the search query box. * @param selectInitialQuery If true, the initial query will be preselected, which means that @@ -4697,7 +4730,6 @@ public class Activity extends ContextThemeWrapper * their launch had come from the original activity. * @param intent The Intent to start. * @param options ActivityOptions or null. - * @param permissionToken Token received from the system that permits this call to be made. * @param ignoreTargetSecurity If true, the activity manager will not check whether the * caller it is doing the start is, is actually allowed to start the target activity. * If you set this to true, you must set an explicit component in the Intent and do any @@ -4706,7 +4738,7 @@ public class Activity extends ContextThemeWrapper * @hide */ public void startActivityAsCaller(Intent intent, @Nullable Bundle options, - IBinder permissionToken, boolean ignoreTargetSecurity, int userId) { + boolean ignoreTargetSecurity, int userId) { if (mParent != null) { throw new RuntimeException("Can't be called from a child"); } @@ -4714,7 +4746,7 @@ public class Activity extends ContextThemeWrapper Instrumentation.ActivityResult ar = mInstrumentation.execStartActivityAsCaller( this, mMainThread.getApplicationThread(), mToken, this, - intent, -1, options, permissionToken, ignoreTargetSecurity, userId); + intent, -1, options, ignoreTargetSecurity, userId); if (ar != null) { mMainThread.sendActivityResult( mToken, mEmbeddedID, -1, ar.getResultCode(), @@ -5545,13 +5577,7 @@ public class Activity extends ContextThemeWrapper if (mParent != null) { throw new IllegalStateException("Can only be called on top-level activity"); } - if (Looper.myLooper() != mMainThread.getLooper()) { - throw new IllegalStateException("Must be called from main thread"); - } - try { - ActivityManager.getService().requestActivityRelaunch(mToken); - } catch (RemoteException e) { - } + mMainThread.handleRelaunchActivityLocally(mToken); } /** @@ -5809,7 +5835,7 @@ public class Activity extends ContextThemeWrapper ActivityManager.INTENT_SENDER_ACTIVITY_RESULT, packageName, mParent == null ? mToken : mParent.mToken, mEmbeddedID, requestCode, new Intent[] { data }, null, flags, null, - UserHandle.myUserId()); + getUserId()); return target != null ? new PendingIntent(target) : null; } catch (RemoteException e) { // Empty @@ -5938,12 +5964,16 @@ public class Activity extends ContextThemeWrapper * * @return Returns the complete component name for this activity */ - @Override - public ComponentName getComponentName() - { + public ComponentName getComponentName() { return mComponent; } + /** @hide */ + @Override + public final ComponentName autofillClientGetComponentName() { + return getComponentName(); + } + /** * Retrieve a {@link SharedPreferences} object for accessing preferences * that are private to this activity. This simply calls the underlying @@ -6239,7 +6269,6 @@ public class Activity extends ContextThemeWrapper * * @param action the action to run on the UI thread */ - @Override public final void runOnUiThread(Runnable action) { if (Thread.currentThread() != mUiThread) { mHandler.post(action); @@ -6248,6 +6277,12 @@ public class Activity extends ContextThemeWrapper } } + /** @hide */ + @Override + public final void autofillClientRunOnUiThread(Runnable action) { + runOnUiThread(action); + } + /** * Standard implementation of * {@link android.view.LayoutInflater.Factory#onCreateView} used when @@ -6324,12 +6359,16 @@ public class Activity extends ContextThemeWrapper mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix); - final AutofillManager afm = mAutofillManager; + final AutofillManager afm = getAutofillManager(); if (afm != null) { + writer.print(prefix); writer.print("Autofill Compat Mode: "); + writer.println(isAutofillCompatibilityEnabled()); afm.dump(prefix, writer); } else { writer.print(prefix); writer.println("No AutofillManager"); } + + ResourcesManager.getInstance().dump(prefix, writer); } /** @@ -7053,6 +7092,18 @@ public class Activity extends ContextThemeWrapper mCurrentConfig = config; mWindow.setColorMode(info.colorMode); + + setAutofillCompatibilityEnabled(application.isAutofillCompatibilityEnabled()); + enableAutofillCompatibilityIfNeeded(); + } + + private void enableAutofillCompatibilityIfNeeded() { + if (isAutofillCompatibilityEnabled()) { + final AutofillManager afm = getSystemService(AutofillManager.class); + if (afm != null) { + afm.enableCompatibilityMode(); + } + } } /** @hide */ @@ -7060,6 +7111,12 @@ public class Activity extends ContextThemeWrapper return mParent != null ? mParent.getActivityToken() : mToken; } + /** @hide */ + @VisibleForTesting + public final ActivityThread getActivityThread() { + return mMainThread; + } + final void performCreate(Bundle icicle) { performCreate(icicle, null); } @@ -7072,6 +7129,7 @@ public class Activity extends ContextThemeWrapper } else { onCreate(icicle); } + writeEventLog(LOG_AM_ON_CREATE_CALLED, "performCreate"); mActivityTransitionState.readState(icicle); mVisibleFromClient = !mWindow.getWindowStyle().getBoolean( @@ -7085,12 +7143,14 @@ public class Activity extends ContextThemeWrapper onNewIntent(intent); } - final void performStart() { + final void performStart(String reason) { mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions()); mFragments.noteStateNotSaved(); mCalled = false; mFragments.execPendingActions(); mInstrumentation.callActivityOnStart(this); + writeEventLog(LOG_AM_ON_START_CALLED, reason); + if (!mCalled) { throw new SuperNotCalledException( "Activity " + mComponent.toShortString() + @@ -7099,11 +7159,12 @@ public class Activity extends ContextThemeWrapper mFragments.dispatchStart(); mFragments.reportLoaderStart(); - // This property is set for all builds except final release - boolean isDlwarningEnabled = SystemProperties.getInt("ro.bionic.ld.warning", 0) == 1; boolean isAppDebuggable = (mApplication.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + // This property is set for all non-user builds except final release + boolean isDlwarningEnabled = SystemProperties.getInt("ro.bionic.ld.warning", 0) == 1; + if (isAppDebuggable || isDlwarningEnabled) { String dlwarning = getDlWarning(); if (dlwarning != null) { @@ -7124,6 +7185,31 @@ public class Activity extends ContextThemeWrapper } } + // This property is set for all non-user builds except final release + boolean isApiWarningEnabled = SystemProperties.getInt("ro.art.hiddenapi.warning", 0) == 1; + + if (isAppDebuggable || isApiWarningEnabled) { + if (!mMainThread.mHiddenApiWarningShown && VMRuntime.getRuntime().hasUsedHiddenApi()) { + // Only show the warning once per process. + mMainThread.mHiddenApiWarningShown = true; + + String appName = getApplicationInfo().loadLabel(getPackageManager()) + .toString(); + String warning = "Detected problems with API compatibility\n" + + "(visit g.co/dev/appcompat for more info)"; + if (isAppDebuggable) { + new AlertDialog.Builder(this) + .setTitle(appName) + .setMessage(warning) + .setPositiveButton(android.R.string.ok, null) + .setCancelable(false) + .show(); + } else { + Toast.makeText(this, appName + "\n" + warning, Toast.LENGTH_LONG).show(); + } + } + } + mActivityTransitionState.enterReady(this); } @@ -7133,7 +7219,7 @@ public class Activity extends ContextThemeWrapper * The option to not start immediately is needed in case a transaction with * multiple lifecycle transitions is in progress. */ - final void performRestart(boolean start) { + final void performRestart(boolean start, String reason) { mCanEnterPictureInPicture = true; mFragments.noteStateNotSaved(); @@ -7166,19 +7252,20 @@ public class Activity extends ContextThemeWrapper mCalled = false; mInstrumentation.callActivityOnRestart(this); + writeEventLog(LOG_AM_ON_RESTART_CALLED, reason); if (!mCalled) { throw new SuperNotCalledException( "Activity " + mComponent.toShortString() + " did not call through to super.onRestart()"); } if (start) { - performStart(); + performStart(reason); } } } - final void performResume(boolean followedByPause) { - performRestart(true /* start */); + final void performResume(boolean followedByPause, String reason) { + performRestart(true /* start */, reason); mFragments.execPendingActions(); @@ -7197,6 +7284,7 @@ public class Activity extends ContextThemeWrapper mCalled = false; // mResumed is set by the instrumentation mInstrumentation.callActivityOnResume(this); + writeEventLog(LOG_AM_ON_RESUME_CALLED, reason); if (!mCalled) { throw new SuperNotCalledException( "Activity " + mComponent.toShortString() + @@ -7233,6 +7321,7 @@ public class Activity extends ContextThemeWrapper mFragments.dispatchPause(); mCalled = false; onPause(); + writeEventLog(LOG_AM_ON_PAUSE_CALLED, "performPause"); mResumed = false; if (!mCalled && getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.GINGERBREAD) { @@ -7240,7 +7329,6 @@ public class Activity extends ContextThemeWrapper "Activity " + mComponent.toShortString() + " did not call through to super.onPause()"); } - mResumed = false; } final void performUserLeaving() { @@ -7248,7 +7336,7 @@ public class Activity extends ContextThemeWrapper onUserLeaveHint(); } - final void performStop(boolean preserveWindow) { + final void performStop(boolean preserveWindow, String reason) { mDoReportFullyDrawn = false; mFragments.doLoaderStop(mChangingConfigurations /*retain*/); @@ -7271,6 +7359,7 @@ public class Activity extends ContextThemeWrapper mCalled = false; mInstrumentation.callActivityOnStop(this); + writeEventLog(LOG_AM_ON_STOP_CALLED, reason); if (!mCalled) { throw new SuperNotCalledException( "Activity " + mComponent.toShortString() + @@ -7298,6 +7387,7 @@ public class Activity extends ContextThemeWrapper mWindow.destroy(); mFragments.dispatchDestroy(); onDestroy(); + writeEventLog(LOG_AM_ON_DESTROY_CALLED, "performDestroy"); mFragments.doLoaderDestroy(); if (mVoiceInteractor != null) { mVoiceInteractor.detachActivity(); @@ -7523,7 +7613,7 @@ public class Activity extends ContextThemeWrapper /** @hide */ @Override - final public void autofillCallbackAuthenticate(int authenticationId, IntentSender intent, + public final void autofillClientAuthenticate(int authenticationId, IntentSender intent, Intent fillInIntent) { try { startIntentSenderForResultInner(intent, AUTO_FILL_AUTH_WHO_PREFIX, @@ -7535,13 +7625,13 @@ public class Activity extends ContextThemeWrapper /** @hide */ @Override - final public void autofillCallbackResetableStateAvailable() { + public final void autofillClientResetableStateAvailable() { mAutoFillResetNeeded = true; } /** @hide */ @Override - final public boolean autofillCallbackRequestShowFillUi(@NonNull View anchor, int width, + public final boolean autofillClientRequestShowFillUi(@NonNull View anchor, int width, int height, @Nullable Rect anchorBounds, IAutofillWindowPresenter presenter) { final boolean wasShowing; @@ -7558,7 +7648,20 @@ public class Activity extends ContextThemeWrapper /** @hide */ @Override - final public boolean autofillCallbackRequestHideFillUi() { + public final void autofillClientDispatchUnhandledKey(@NonNull View anchor, + @NonNull KeyEvent keyEvent) { + ViewRootImpl rootImpl = anchor.getViewRootImpl(); + if (rootImpl != null) { + // dont care if anchorView is current focus, for example a custom view may only receive + // touchEvent, not focusable but can still trigger autofill window. The Key handling + // might be inside parent of the custom view. + rootImpl.dispatchKeyFromAutofill(keyEvent); + } + } + + /** @hide */ + @Override + public final boolean autofillClientRequestHideFillUi() { if (mAutofillPopupWindow == null) { return false; } @@ -7569,8 +7672,16 @@ public class Activity extends ContextThemeWrapper /** @hide */ @Override - @NonNull public View[] findViewsByAutofillIdTraversal(@NonNull int[] viewIds) { - final View[] views = new View[viewIds.length]; + public final boolean autofillClientIsFillUiShowing() { + return mAutofillPopupWindow != null && mAutofillPopupWindow.isShowing(); + } + + /** @hide */ + @Override + @NonNull + public final View[] autofillClientFindViewsByAutofillIdTraversal( + @NonNull AutofillId[] autofillId) { + final View[] views = new View[autofillId.length]; final ArrayList<ViewRootImpl> roots = WindowManagerGlobal.getInstance().getRootViews(getActivityToken()); @@ -7578,10 +7689,11 @@ public class Activity extends ContextThemeWrapper final View rootView = roots.get(rootNum).getView(); if (rootView != null) { - for (int viewNum = 0; viewNum < viewIds.length; viewNum++) { + final int viewCount = autofillId.length; + for (int viewNum = 0; viewNum < viewCount; viewNum++) { if (views[viewNum] == null) { views[viewNum] = rootView.findViewByAutofillIdTraversal( - viewIds[viewNum]); + autofillId[viewNum].getViewId()); } } } @@ -7592,14 +7704,15 @@ public class Activity extends ContextThemeWrapper /** @hide */ @Override - @Nullable public View findViewByAutofillIdTraversal(int viewId) { + @Nullable + public final View autofillClientFindViewByAutofillIdTraversal(AutofillId autofillId) { final ArrayList<ViewRootImpl> roots = WindowManagerGlobal.getInstance().getRootViews(getActivityToken()); for (int rootNum = 0; rootNum < roots.size(); rootNum++) { final View rootView = roots.get(rootNum).getView(); if (rootView != null) { - final View view = rootView.findViewByAutofillIdTraversal(viewId); + final View view = rootView.findViewByAutofillIdTraversal(autofillId.getViewId()); if (view != null) { return view; } @@ -7611,50 +7724,65 @@ public class Activity extends ContextThemeWrapper /** @hide */ @Override - @NonNull public boolean[] getViewVisibility(@NonNull int[] viewIds) { - final boolean[] isVisible = new boolean[viewIds.length]; - final View views[] = findViewsByAutofillIdTraversal(viewIds); - - for (int i = 0; i < viewIds.length; i++) { - View view = views[i]; - if (view == null) { - isVisible[i] = false; - continue; - } - - isVisible[i] = true; - - // Check if the view is visible by checking all parents - while (true) { - if (view instanceof DecorView && view.getViewRootImpl() == view.getParent()) { - break; - } - - if (view.getVisibility() != View.VISIBLE) { - isVisible[i] = false; - break; + public final @NonNull boolean[] autofillClientGetViewVisibility( + @NonNull AutofillId[] autofillIds) { + final int autofillIdCount = autofillIds.length; + final boolean[] visible = new boolean[autofillIdCount]; + for (int i = 0; i < autofillIdCount; i++) { + final AutofillId autofillId = autofillIds[i]; + final View view = autofillClientFindViewByAutofillIdTraversal(autofillId); + if (view != null) { + if (!autofillId.isVirtual()) { + visible[i] = view.isVisibleToUser(); + } else { + visible[i] = view.isVisibleToUserForAutofill(autofillId.getVirtualChildId()); } + } + } + if (android.view.autofill.Helper.sVerbose) { + Log.v(TAG, "autofillClientGetViewVisibility(): " + Arrays.toString(visible)); + } + return visible; + } - if (view.getParent() instanceof View) { - view = (View) view.getParent(); - } else { - break; + /** @hide */ + public final @Nullable View autofillClientFindViewByAccessibilityIdTraversal(int viewId, + int windowId) { + final ArrayList<ViewRootImpl> roots = WindowManagerGlobal.getInstance() + .getRootViews(getActivityToken()); + for (int rootNum = 0; rootNum < roots.size(); rootNum++) { + final View rootView = roots.get(rootNum).getView(); + if (rootView != null && rootView.getAccessibilityWindowId() == windowId) { + final View view = rootView.findViewByAccessibilityIdTraversal(viewId); + if (view != null) { + return view; } } } + return null; + } - return isVisible; + /** @hide */ + @Override + public final @Nullable IBinder autofillClientGetActivityToken() { + return getActivityToken(); } /** @hide */ @Override - public boolean isVisibleForAutofill() { + public final boolean autofillClientIsVisibleForAutofill() { return !mStopped; } /** @hide */ @Override - public boolean isDisablingEnterExitEventForAutofill() { + public final boolean autofillClientIsCompatibilityModeEnabled() { + return isAutofillCompatibilityEnabled(); + } + + /** @hide */ + @Override + public final boolean isDisablingEnterExitEventForAutofill() { return mAutoFillIgnoreFirstResumePause || !mResumed; } @@ -7676,7 +7804,6 @@ public class Activity extends ContextThemeWrapper * @param disable {@code true} to disable preview screenshots; {@code false} otherwise. * @hide */ - @SystemApi public void setDisablePreviewScreenshots(boolean disable) { try { ActivityManager.getService().setDisablePreviewScreenshots(mToken, disable); @@ -7748,6 +7875,12 @@ public class Activity extends ContextThemeWrapper } } + /** Log a lifecycle event for current user id and component class. */ + private void writeEventLog(int event, String reason) { + EventLog.writeEvent(event, UserHandle.myUserId(), getComponentName().getClassName(), + reason); + } + class HostCallbacks extends FragmentHostCallback<Activity> { public HostCallbacks() { super(Activity.this /*activity*/); diff --git a/android/app/ActivityManager.java b/android/app/ActivityManager.java index 80350584..289a4dd7 100644 --- a/android/app/ActivityManager.java +++ b/android/app/ActivityManager.java @@ -28,7 +28,6 @@ import android.annotation.TestApi; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.UriPermission; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ConfigurationInfo; @@ -71,6 +70,7 @@ import com.android.internal.app.procstats.ProcessStats; import com.android.internal.os.RoSystemProperties; import com.android.internal.os.TransferPipe; import com.android.internal.util.FastPrintWriter; +import com.android.internal.util.MemInfoReader; import com.android.server.LocalServices; import org.xmlpull.v1.XmlSerializer; @@ -450,31 +450,6 @@ public class ActivityManager { */ public static final int INTENT_SENDER_FOREGROUND_SERVICE = 5; - /** - * Extra included on intents that are delegating the call to - * ActivityManager#startActivityAsCaller to another app. This token is necessary for that call - * to succeed. Type is IBinder. - * @hide - */ - public static final String EXTRA_PERMISSION_TOKEN = "android.app.extra.PERMISSION_TOKEN"; - - /** - * Extra included on intents that contain an EXTRA_INTENT, with options that the contained - * intent may want to be started with. Type is Bundle. - * TODO: remove once the ChooserActivity moves to systemui - * @hide - */ - public static final String EXTRA_OPTIONS = "android.app.extra.OPTIONS"; - - /** - * Extra included on intents that contain an EXTRA_INTENT, use this boolean value for the - * parameter of the same name when starting the contained intent. - * TODO: remove once the ChooserActivity moves to systemui - * @hide - */ - public static final String EXTRA_IGNORE_TARGET_SECURITY = - "android.app.extra.EXTRA_IGNORE_TARGET_SECURITY"; - /** @hide User operation call: success! */ public static final int USER_OP_SUCCESS = 0; @@ -576,18 +551,68 @@ public class ActivityManager { /** @hide Process does not exist. */ public static final int PROCESS_STATE_NONEXISTENT = 19; - // NOTE: If PROCESS_STATEs are added or changed, then new fields must be added - // to frameworks/base/core/proto/android/app/activitymanager.proto and the following method must + // NOTE: If PROCESS_STATEs are added, then new fields must be added + // to frameworks/base/core/proto/android/app/enums.proto and the following method must // be updated to correctly map between them. + // However, if the current ActivityManager values are merely modified, no update should be made + // to enums.proto, to which values can only be added but never modified. Note that the proto + // versions do NOT have the ordering restrictions of the ActivityManager process state. /** - * Maps ActivityManager.PROCESS_STATE_ values to ProcessState enum. + * Maps ActivityManager.PROCESS_STATE_ values to enums.proto ProcessStateEnum value. * * @param amInt a process state of the form ActivityManager.PROCESS_STATE_ - * @return the value of the corresponding ActivityManager's ProcessState enum. + * @return the value of the corresponding enums.proto ProcessStateEnum value. * @hide */ public static final int processStateAmToProto(int amInt) { - return amInt * 100; + switch (amInt) { + case PROCESS_STATE_UNKNOWN: + return AppProtoEnums.PROCESS_STATE_UNKNOWN; + case PROCESS_STATE_PERSISTENT: + return AppProtoEnums.PROCESS_STATE_PERSISTENT; + case PROCESS_STATE_PERSISTENT_UI: + return AppProtoEnums.PROCESS_STATE_PERSISTENT_UI; + case PROCESS_STATE_TOP: + return AppProtoEnums.PROCESS_STATE_TOP; + case PROCESS_STATE_FOREGROUND_SERVICE: + return AppProtoEnums.PROCESS_STATE_FOREGROUND_SERVICE; + case PROCESS_STATE_BOUND_FOREGROUND_SERVICE: + return AppProtoEnums.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; + case PROCESS_STATE_IMPORTANT_FOREGROUND: + return AppProtoEnums.PROCESS_STATE_IMPORTANT_FOREGROUND; + case PROCESS_STATE_IMPORTANT_BACKGROUND: + return AppProtoEnums.PROCESS_STATE_IMPORTANT_BACKGROUND; + case PROCESS_STATE_TRANSIENT_BACKGROUND: + return AppProtoEnums.PROCESS_STATE_TRANSIENT_BACKGROUND; + case PROCESS_STATE_BACKUP: + return AppProtoEnums.PROCESS_STATE_BACKUP; + case PROCESS_STATE_SERVICE: + return AppProtoEnums.PROCESS_STATE_SERVICE; + case PROCESS_STATE_RECEIVER: + return AppProtoEnums.PROCESS_STATE_RECEIVER; + case PROCESS_STATE_TOP_SLEEPING: + return AppProtoEnums.PROCESS_STATE_TOP_SLEEPING; + case PROCESS_STATE_HEAVY_WEIGHT: + return AppProtoEnums.PROCESS_STATE_HEAVY_WEIGHT; + case PROCESS_STATE_HOME: + return AppProtoEnums.PROCESS_STATE_HOME; + case PROCESS_STATE_LAST_ACTIVITY: + return AppProtoEnums.PROCESS_STATE_LAST_ACTIVITY; + case PROCESS_STATE_CACHED_ACTIVITY: + return AppProtoEnums.PROCESS_STATE_CACHED_ACTIVITY; + case PROCESS_STATE_CACHED_ACTIVITY_CLIENT: + return AppProtoEnums.PROCESS_STATE_CACHED_ACTIVITY_CLIENT; + case PROCESS_STATE_CACHED_RECENT: + return AppProtoEnums.PROCESS_STATE_CACHED_RECENT; + case PROCESS_STATE_CACHED_EMPTY: + return AppProtoEnums.PROCESS_STATE_CACHED_EMPTY; + case PROCESS_STATE_NONEXISTENT: + return AppProtoEnums.PROCESS_STATE_NONEXISTENT; + default: + // ActivityManager process state (amInt) + // could not be mapped to an AppProtoEnums ProcessState state. + return AppProtoEnums.PROCESS_STATE_UNKNOWN_TO_PROTO; + } } /** @hide The lowest process state number */ @@ -934,8 +959,21 @@ public class ActivityManager { * @hide */ static public boolean isHighEndGfx() { - return !isLowRamDeviceStatic() && - !Resources.getSystem().getBoolean(com.android.internal.R.bool.config_avoidGfxAccel); + return !isLowRamDeviceStatic() + && !RoSystemProperties.CONFIG_AVOID_GFX_ACCEL + && !Resources.getSystem() + .getBoolean(com.android.internal.R.bool.config_avoidGfxAccel); + } + + /** + * Return the total number of bytes of RAM this device has. + * @hide + */ + @TestApi + public long getTotalRam() { + MemInfoReader memreader = new MemInfoReader(); + memreader.readMemInfo(); + return memreader.getTotalSize(); } /** @@ -1667,7 +1705,7 @@ public class ActivityManager { if (maxNum < 0) { throw new IllegalArgumentException("The requested number of tasks should be >= 0"); } - return getService().getRecentTasks(maxNum, flags, UserHandle.myUserId()).getList(); + return getService().getRecentTasks(maxNum, flags, mContext.getUserId()).getList(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2065,15 +2103,17 @@ public class ActivityManager { private final int mOrientation; private final Rect mContentInsets; private final boolean mReducedResolution; + private final boolean mIsRealSnapshot; private final float mScale; public TaskSnapshot(GraphicBuffer snapshot, int orientation, Rect contentInsets, - boolean reducedResolution, float scale) { + boolean reducedResolution, float scale, boolean isRealSnapshot) { mSnapshot = snapshot; mOrientation = orientation; mContentInsets = new Rect(contentInsets); mReducedResolution = reducedResolution; mScale = scale; + mIsRealSnapshot = isRealSnapshot; } private TaskSnapshot(Parcel source) { @@ -2082,6 +2122,7 @@ public class ActivityManager { mContentInsets = source.readParcelable(null /* classLoader */); mReducedResolution = source.readBoolean(); mScale = source.readFloat(); + mIsRealSnapshot = source.readBoolean(); } /** @@ -2114,6 +2155,14 @@ public class ActivityManager { } /** + * @return Whether or not the snapshot is a real snapshot or an app-theme generated snapshot + * due to the task having a secure window or having previews disabled. + */ + public boolean isRealSnapshot() { + return mIsRealSnapshot; + } + + /** * @return The scale this snapshot was taken in. */ public float getScale() { @@ -2132,13 +2181,15 @@ public class ActivityManager { dest.writeParcelable(mContentInsets, 0); dest.writeBoolean(mReducedResolution); dest.writeFloat(mScale); + dest.writeBoolean(mIsRealSnapshot); } @Override public String toString() { return "TaskSnapshot{mSnapshot=" + mSnapshot + " mOrientation=" + mOrientation + " mContentInsets=" + mContentInsets.toShortString() - + " mReducedResolution=" + mReducedResolution + " mScale=" + mScale; + + " mReducedResolution=" + mReducedResolution + " mScale=" + mScale + + " mIsRealSnapshot=" + mIsRealSnapshot; } public static final Creator<TaskSnapshot> CREATOR = new Creator<TaskSnapshot>() { @@ -2661,7 +2712,7 @@ public class ActivityManager { public boolean clearApplicationUserData(String packageName, IPackageDataObserver observer) { try { return getService().clearApplicationUserData(packageName, false, - observer, UserHandle.myUserId()); + observer, mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2686,17 +2737,22 @@ public class ActivityManager { /** * Permits an application to get the persistent URI permissions granted to another. * - * <p>Typically called by Settings. + * <p>Typically called by Settings or DocumentsUI, requires + * {@code GET_APP_GRANTED_URI_PERMISSIONS}. * - * @param packageName application to look for the granted permissions + * @param packageName application to look for the granted permissions, or {@code null} to get + * granted permissions for all applications * @return list of granted URI permissions * * @hide */ - public ParceledListSlice<UriPermission> getGrantedUriPermissions(String packageName) { + public ParceledListSlice<GrantedUriPermission> getGrantedUriPermissions( + @Nullable String packageName) { try { - return getService().getGrantedUriPermissions(packageName, - UserHandle.myUserId()); + @SuppressWarnings("unchecked") + final ParceledListSlice<GrantedUriPermission> castedList = getService() + .getGrantedUriPermissions(packageName, mContext.getUserId()); + return castedList; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2705,7 +2761,7 @@ public class ActivityManager { /** * Permits an application to clear the persistent URI permissions granted to another. * - * <p>Typically called by Settings. + * <p>Typically called by Settings, requires {@code CLEAR_APP_GRANTED_URI_PERMISSIONS}. * * @param packageName application to clear its granted permissions * @@ -2714,7 +2770,7 @@ public class ActivityManager { public void clearGrantedUriPermissions(String packageName) { try { getService().clearGrantedUriPermissions(packageName, - UserHandle.myUserId()); + mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -3281,6 +3337,28 @@ public class ActivityManager { } /** + * Query whether the user has enabled background restrictions for this app. + * + * <p> The user may chose to do this, if they see that an app is consuming an unreasonable + * amount of battery while in the background. </p> + * + * <p> If true, any work that the app tries to do will be aggressively restricted while it is in + * the background. At a minimum, jobs and alarms will not execute and foreground services + * cannot be started unless an app activity is in the foreground. </p> + * + * <p><b> Note that these restrictions stay in effect even when the device is charging.</b></p> + * + * @return true if user has enforced background restrictions for this app, false otherwise. + */ + public boolean isBackgroundRestricted() { + try { + return getService().isBackgroundRestricted(mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Sets the memory trim mode for a process and schedules a memory trim operation. * * <p><b>Note: this method is only intended for testing framework.</b></p> @@ -3499,7 +3577,7 @@ public class ActivityManager { public void killBackgroundProcesses(String packageName) { try { getService().killBackgroundProcesses(packageName, - UserHandle.myUserId()); + mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -3557,7 +3635,7 @@ public class ActivityManager { @SystemApi @RequiresPermission(Manifest.permission.FORCE_STOP_PACKAGES) public void forceStopPackage(String packageName) { - forceStopPackageAsUser(packageName, UserHandle.myUserId()); + forceStopPackageAsUser(packageName, mContext.getUserId()); } /** @@ -3669,6 +3747,24 @@ public class ActivityManager { } /** + * Unsupported compiled sdk warning should always be shown for the intput activity + * even in cases where the system would normally not show the warning. E.g. when running in a + * test harness. + * + * @param activity The component name of the activity to always show the warning for. + * + * @hide + */ + @TestApi + public void alwaysShowUnsupportedCompileSdkWarning(ComponentName activity) { + try { + getService().alwaysShowUnsupportedCompileSdkWarning(activity); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns the launch count of each installed package. * * @hide diff --git a/android/app/ActivityManagerInternal.java b/android/app/ActivityManagerInternal.java index da9f7285..db9d9234 100644 --- a/android/app/ActivityManagerInternal.java +++ b/android/app/ActivityManagerInternal.java @@ -27,6 +27,7 @@ import android.os.IBinder; import android.os.SystemClock; import android.service.voice.IVoiceInteractionSession; import android.util.SparseIntArray; +import android.view.RemoteAnimationAdapter; import com.android.internal.app.IVoiceInteractor; @@ -43,25 +44,36 @@ public abstract class ActivityManagerInternal { * Type for {@link #notifyAppTransitionStarting}: The transition was started because we drew * the splash screen. */ - public static final int APP_TRANSITION_SPLASH_SCREEN = 1; + public static final int APP_TRANSITION_SPLASH_SCREEN = + AppProtoEnums.APP_TRANSITION_SPLASH_SCREEN; // 1 /** * Type for {@link #notifyAppTransitionStarting}: The transition was started because we all * app windows were drawn */ - public static final int APP_TRANSITION_WINDOWS_DRAWN = 2; + public static final int APP_TRANSITION_WINDOWS_DRAWN = + AppProtoEnums.APP_TRANSITION_WINDOWS_DRAWN; // 2 /** * Type for {@link #notifyAppTransitionStarting}: The transition was started because of a * timeout. */ - public static final int APP_TRANSITION_TIMEOUT = 3; + public static final int APP_TRANSITION_TIMEOUT = + AppProtoEnums.APP_TRANSITION_TIMEOUT; // 3 /** * Type for {@link #notifyAppTransitionStarting}: The transition was started because of a * we drew a task snapshot. */ - public static final int APP_TRANSITION_SNAPSHOT = 4; + public static final int APP_TRANSITION_SNAPSHOT = + AppProtoEnums.APP_TRANSITION_SNAPSHOT; // 4 + + /** + * Type for {@link #notifyAppTransitionStarting}: The transition was started because it was a + * recents animation and we only needed to wait on the wallpaper. + */ + public static final int APP_TRANSITION_RECENTS_ANIM = + AppProtoEnums.APP_TRANSITION_RECENTS_ANIM; // 5 /** * The bundle key to extract the assist data. @@ -150,8 +162,8 @@ public abstract class ActivityManagerInternal { * Callback for window manager to let activity manager know that we are finally starting the * app transition; * - * @param reasons A map from stack id to a reason integer why the transition was started,, which - * must be one of the APP_TRANSITION_* values. + * @param reasons A map from windowing mode to a reason integer why the transition was started, + * which must be one of the APP_TRANSITION_* values. * @param timestamp The time at which the app transition started in * {@link SystemClock#uptimeMillis()} timebase. */ @@ -215,6 +227,9 @@ public abstract class ActivityManagerInternal { /** * Start activity {@code intents} as if {@code packageName} on user {@code userId} did it. * + * - DO NOT call it with the calling UID cleared. + * - All the necessary caller permission checks must be done at callsites. + * * @return error codes used by {@link IActivityManager#startActivity} and its siblings. */ public abstract int startActivitiesAsPackage(String packageName, @@ -257,6 +272,17 @@ public abstract class ActivityManagerInternal { public abstract void setHasOverlayUi(int pid, boolean hasOverlayUi); /** + * Sets if the given pid is currently running a remote animation, which is taken a signal for + * determining oom adjustment and scheduling behavior. + * + * @param pid The pid we are setting overlay UI for. + * @param runningRemoteAnimation True if the process is running a remote animation, false + * otherwise. + * @see RemoteAnimationAdapter + */ + public abstract void setRunningRemoteAnimation(int pid, boolean runningRemoteAnimation); + + /** * Called after the network policy rules are updated by * {@link com.android.server.net.NetworkPolicyManagerService} for a specific {@param uid} and * {@param procStateSeq}. @@ -344,4 +370,25 @@ public abstract class ActivityManagerInternal { * Returns is the caller has the same uid as the Recents component */ public abstract boolean isCallerRecents(int callingUid); + + /** + * Returns whether the recents component is the home activity for the given user. + */ + public abstract boolean isRecentsComponentHomeActivity(int userId); + + /** + * Whether an UID is active or idle. + */ + public abstract boolean isUidActive(int uid); + + /** + * Returns a list that contains the memory stats for currently running processes. + */ + public abstract List<ProcessMemoryState> getMemoryStateForProcesses(); + + /** + * This enforces {@code func} can only be called if either the caller is Recents activity or + * has {@code permission}. + */ + public abstract void enforceCallerIsRecentsOrHasPermission(String permission, String func); } diff --git a/android/app/ActivityOptions.java b/android/app/ActivityOptions.java index fee58274..09dcbf21 100644 --- a/android/app/ActivityOptions.java +++ b/android/app/ActivityOptions.java @@ -164,7 +164,7 @@ public class ActivityOptions { /** * Whether the activity should be launched into LockTask mode. - * @see #setLockTaskMode(boolean) + * @see #setLockTaskEnabled(boolean) */ private static final String KEY_LOCK_TASK_MODE = "android:activity.lockTaskMode"; @@ -1106,6 +1106,11 @@ public class ActivityOptions { } /** @hide */ + public void setRemoteAnimationAdapter(RemoteAnimationAdapter remoteAnimationAdapter) { + mRemoteAnimationAdapter = remoteAnimationAdapter; + } + + /** @hide */ public static ActivityOptions fromBundle(Bundle bOptions) { return bOptions != null ? new ActivityOptions(bOptions) : null; } @@ -1143,7 +1148,7 @@ public class ActivityOptions { * @see Activity#startLockTask() * @see android.app.admin.DevicePolicyManager#setLockTaskPackages(ComponentName, String[]) */ - public ActivityOptions setLockTaskMode(boolean lockTaskMode) { + public ActivityOptions setLockTaskEnabled(boolean lockTaskMode) { mLockTaskMode = lockTaskMode; return this; } diff --git a/android/app/ActivityThread.java b/android/app/ActivityThread.java index 934b0f3c..50a43989 100644 --- a/android/app/ActivityThread.java +++ b/android/app/ActivityThread.java @@ -30,12 +30,15 @@ import android.annotation.Nullable; import android.app.assist.AssistContent; import android.app.assist.AssistStructure; import android.app.backup.BackupAgent; +import android.app.servertransaction.ActivityLifecycleItem; import android.app.servertransaction.ActivityLifecycleItem.LifecycleState; +import android.app.servertransaction.ActivityRelaunchItem; import android.app.servertransaction.ActivityResultItem; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.PendingTransactionActions; import android.app.servertransaction.PendingTransactionActions.StopInfo; import android.app.servertransaction.TransactionExecutor; +import android.app.servertransaction.TransactionExecutorHelper; import android.content.BroadcastReceiver; import android.content.ComponentCallbacks2; import android.content.ComponentName; @@ -64,6 +67,7 @@ import android.database.sqlite.SQLiteDebug; import android.database.sqlite.SQLiteDebug.DbStats; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.ImageDecoder; import android.hardware.display.DisplayManagerGlobal; import android.net.ConnectivityManager; import android.net.IConnectivityManager; @@ -113,6 +117,7 @@ import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; import android.util.LogPrinter; +import android.util.MergedConfiguration; import android.util.Pair; import android.util.PrintWriterPrinter; import android.util.Slog; @@ -142,7 +147,7 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; import com.android.org.conscrypt.OpenSSLSocketImpl; import com.android.org.conscrypt.TrustedCertificateStore; -import com.android.server.am.proto.MemInfoProto; +import com.android.server.am.MemInfoDumpProto; import dalvik.system.BaseDexClassLoader; import dalvik.system.CloseGuard; @@ -166,9 +171,9 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.InetAddress; import java.text.DateFormat; -import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; +import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; @@ -204,12 +209,9 @@ public final class ActivityThread extends ClientTransactionHandler { private static final boolean DEBUG_SERVICE = false; public static final boolean DEBUG_MEMORY_TRIM = false; private static final boolean DEBUG_PROVIDER = false; - private static final boolean DEBUG_ORDER = false; + public static final boolean DEBUG_ORDER = false; private static final long MIN_TIME_BETWEEN_GCS = 5*1000; private static final int SQLITE_MEM_RELEASED_EVENT_LOG_TAG = 75003; - private static final int LOG_AM_ON_PAUSE_CALLED = 30021; - private static final int LOG_AM_ON_RESUME_CALLED = 30022; - private static final int LOG_AM_ON_STOP_CALLED = 30049; /** Type for IActivityManager.serviceDoneExecuting: anonymous operation */ public static final int SERVICE_DONE_EXECUTING_ANON = 0; @@ -222,7 +224,7 @@ public final class ActivityThread extends ClientTransactionHandler { private static final boolean REPORT_TO_ACTIVITY = true; // Maximum number of recent tokens to maintain for debugging purposes - private static final int MAX_RECENT_TOKENS = 10; + private static final int MAX_DESTROYED_ACTIVITIES = 10; /** * Denotes an invalid sequence number corresponding to a process state change. @@ -256,7 +258,7 @@ public final class ActivityThread extends ClientTransactionHandler { final H mH = new H(); final Executor mExecutor = new HandlerExecutor(mH); final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>(); - final ArrayDeque<Integer> mRecentTokens = new ArrayDeque<>(); + final ArrayList<DestroyedActivityInfo> mRecentDestroyedActivities = new ArrayList<>(); // List of new activities (via ActivityRecord.nextIdle) that should // be reported when next we idle. @@ -291,6 +293,7 @@ public final class ActivityThread extends ClientTransactionHandler { boolean mJitEnabled = false; boolean mSomeActivitiesChanged = false; boolean mUpdatingSystemConfig = false; + /* package */ boolean mHiddenApiWarningShown = false; // These can be accessed by multiple threads; mResourcesManager is the lock. // XXX For now we keep around information about all packages we have @@ -338,6 +341,26 @@ public final class ActivityThread extends ClientTransactionHandler { } } + /** + * TODO(b/71506345): Remove this once bug is resolved. + */ + private static final class DestroyedActivityInfo { + private final Integer mToken; + private final String mReason; + private final long mTime; + + DestroyedActivityInfo(Integer token, String reason) { + mToken = token; + mReason = reason; + mTime = System.currentTimeMillis(); + } + + void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "[token:" + mToken + " | time:" + mTime + " | reason:" + mReason + + "]"); + } + } + // The lock of mProviderMap protects the following variables. final ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap = new ArrayMap<ProviderKey, ProviderClientRecord>(); @@ -389,7 +412,7 @@ public final class ActivityThread extends ClientTransactionHandler { ActivityInfo activityInfo; CompatibilityInfo compatInfo; - public LoadedApk loadedApk; + public LoadedApk packageInfo; List<ResultInfo> pendingResults; List<ReferrerIntent> pendingIntents; @@ -397,7 +420,6 @@ public final class ActivityThread extends ClientTransactionHandler { boolean startsNotResumed; public final boolean isForward; int pendingConfigChanges; - boolean onlyLocalRequest; Window mPendingRemoveWindow; WindowManager mPendingRemoveWindowManager; @@ -432,7 +454,7 @@ public final class ActivityThread extends ClientTransactionHandler { this.isForward = isForward; this.profilerInfo = profilerInfo; this.overrideConfig = overrideConfig; - this.loadedApk = client.getLoadedApkNoCheck(activityInfo.applicationInfo, + this.packageInfo = client.getPackageInfoNoCheck(activityInfo.applicationInfo, compatInfo); init(); } @@ -487,18 +509,24 @@ public final class ActivityThread extends ClientTransactionHandler { } } - public boolean isPreHoneycomb() { - if (activity != null) { - return activity.getApplicationInfo().targetSdkVersion - < android.os.Build.VERSION_CODES.HONEYCOMB; - } - return false; + private boolean isPreHoneycomb() { + return activity != null && activity.getApplicationInfo().targetSdkVersion + < android.os.Build.VERSION_CODES.HONEYCOMB; + } + + private boolean isPreP() { + return activity != null && activity.getApplicationInfo().targetSdkVersion + < android.os.Build.VERSION_CODES.P; } public boolean isPersistable() { return activityInfo.persistableMode == ActivityInfo.PERSIST_ACROSS_REBOOTS; } + public boolean isVisibleFromServer() { + return activity != null && activity.mVisibleFromServer; + } + public String toString() { ComponentName componentName = intent != null ? intent.getComponent() : null; return "ActivityRecord{" @@ -517,7 +545,6 @@ public final class ActivityThread extends ClientTransactionHandler { sb.append(", startsNotResumed=").append(startsNotResumed); sb.append(", isForward=").append(isForward); sb.append(", pendingConfigChanges=").append(pendingConfigChanges); - sb.append(", onlyLocalRequest=").append(onlyLocalRequest); sb.append(", preserveWindow=").append(mPreserveWindow); if (activity != null) { sb.append(", Activity{"); @@ -614,7 +641,7 @@ public final class ActivityThread extends ClientTransactionHandler { } static final class AppBindData { - LoadedApk loadedApk; + LoadedApk info; String processName; ApplicationInfo appInfo; List<ProviderInfo> providers; @@ -634,6 +661,8 @@ public final class ActivityThread extends ClientTransactionHandler { /** Initial values for {@link Profiler}. */ ProfilerInfo initProfilerInfo; + boolean autofillCompatibilityEnabled; + public String toString() { return "AppBindData{appInfo=" + appInfo + "}"; } @@ -683,7 +712,7 @@ public final class ActivityThread extends ClientTransactionHandler { streamingOutput); profiling = true; } catch (RuntimeException e) { - Slog.w(TAG, "Profiling failed on path " + profileFile); + Slog.w(TAG, "Profiling failed on path " + profileFile, e); try { profileFd.close(); profileFd = null; @@ -760,15 +789,6 @@ public final class ActivityThread extends ClientTransactionHandler { sendMessage(H.SLEEPING, token, sleeping ? 1 : 0); } - @Override - public final void scheduleRelaunchActivity(IBinder token, - List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents, - int configChanges, boolean notResumed, Configuration config, - Configuration overrideConfig, boolean preserveWindow) { - requestRelaunchActivity(token, pendingResults, pendingNewIntents, - configChanges, notResumed, config, overrideConfig, true, preserveWindow); - } - public final void scheduleReceiver(Intent intent, ActivityInfo info, CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras, boolean sync, int sendingUser, int processState) { @@ -860,7 +880,7 @@ public final class ActivityThread extends ClientTransactionHandler { boolean enableBinderTracking, boolean trackAllocation, boolean isRestrictedBackupMode, boolean persistent, Configuration config, CompatibilityInfo compatInfo, Map services, Bundle coreSettings, - String buildSerial) { + String buildSerial, boolean autofillCompatibilityEnabled) { if (services != null) { // Setup the service cache in the ServiceManager @@ -886,6 +906,7 @@ public final class ActivityThread extends ClientTransactionHandler { data.compatInfo = compatInfo; data.initProfilerInfo = profilerInfo; data.buildSerial = buildSerial; + data.autofillCompatibilityEnabled = autofillCompatibilityEnabled; sendMessage(H.BIND_APPLICATION, data); } @@ -1238,55 +1259,62 @@ public final class ActivityThread extends ClientTransactionHandler { long parcelCount = Parcel.getGlobalAllocCount(); SQLiteDebug.PagerStats stats = SQLiteDebug.getDatabaseInfo(); - final long mToken = proto.start(MemInfoProto.AppData.PROCESS_MEMORY); - proto.write(MemInfoProto.ProcessMemory.PID, Process.myPid()); - proto.write(MemInfoProto.ProcessMemory.PROCESS_NAME, + final long mToken = proto.start(MemInfoDumpProto.AppData.PROCESS_MEMORY); + proto.write(MemInfoDumpProto.ProcessMemory.PID, Process.myPid()); + proto.write(MemInfoDumpProto.ProcessMemory.PROCESS_NAME, (mBoundApplication != null) ? mBoundApplication.processName : "unknown"); dumpMemInfoTable(proto, memInfo, dumpDalvik, dumpSummaryOnly, nativeMax, nativeAllocated, nativeFree, dalvikMax, dalvikAllocated, dalvikFree); proto.end(mToken); - final long oToken = proto.start(MemInfoProto.AppData.OBJECTS); - proto.write(MemInfoProto.AppData.ObjectStats.VIEW_INSTANCE_COUNT, viewInstanceCount); - proto.write(MemInfoProto.AppData.ObjectStats.VIEW_ROOT_INSTANCE_COUNT, + final long oToken = proto.start(MemInfoDumpProto.AppData.OBJECTS); + proto.write(MemInfoDumpProto.AppData.ObjectStats.VIEW_INSTANCE_COUNT, + viewInstanceCount); + proto.write(MemInfoDumpProto.AppData.ObjectStats.VIEW_ROOT_INSTANCE_COUNT, viewRootInstanceCount); - proto.write(MemInfoProto.AppData.ObjectStats.APP_CONTEXT_INSTANCE_COUNT, + proto.write(MemInfoDumpProto.AppData.ObjectStats.APP_CONTEXT_INSTANCE_COUNT, appContextInstanceCount); - proto.write(MemInfoProto.AppData.ObjectStats.ACTIVITY_INSTANCE_COUNT, + proto.write(MemInfoDumpProto.AppData.ObjectStats.ACTIVITY_INSTANCE_COUNT, activityInstanceCount); - proto.write(MemInfoProto.AppData.ObjectStats.GLOBAL_ASSET_COUNT, globalAssetCount); - proto.write(MemInfoProto.AppData.ObjectStats.GLOBAL_ASSET_MANAGER_COUNT, + proto.write(MemInfoDumpProto.AppData.ObjectStats.GLOBAL_ASSET_COUNT, + globalAssetCount); + proto.write(MemInfoDumpProto.AppData.ObjectStats.GLOBAL_ASSET_MANAGER_COUNT, globalAssetManagerCount); - proto.write(MemInfoProto.AppData.ObjectStats.LOCAL_BINDER_OBJECT_COUNT, + proto.write(MemInfoDumpProto.AppData.ObjectStats.LOCAL_BINDER_OBJECT_COUNT, binderLocalObjectCount); - proto.write(MemInfoProto.AppData.ObjectStats.PROXY_BINDER_OBJECT_COUNT, + proto.write(MemInfoDumpProto.AppData.ObjectStats.PROXY_BINDER_OBJECT_COUNT, binderProxyObjectCount); - proto.write(MemInfoProto.AppData.ObjectStats.PARCEL_MEMORY_KB, parcelSize / 1024); - proto.write(MemInfoProto.AppData.ObjectStats.PARCEL_COUNT, parcelCount); - proto.write(MemInfoProto.AppData.ObjectStats.BINDER_OBJECT_DEATH_COUNT, + proto.write(MemInfoDumpProto.AppData.ObjectStats.PARCEL_MEMORY_KB, + parcelSize / 1024); + proto.write(MemInfoDumpProto.AppData.ObjectStats.PARCEL_COUNT, parcelCount); + proto.write(MemInfoDumpProto.AppData.ObjectStats.BINDER_OBJECT_DEATH_COUNT, binderDeathObjectCount); - proto.write(MemInfoProto.AppData.ObjectStats.OPEN_SSL_SOCKET_COUNT, openSslSocketCount); - proto.write(MemInfoProto.AppData.ObjectStats.WEBVIEW_INSTANCE_COUNT, + proto.write(MemInfoDumpProto.AppData.ObjectStats.OPEN_SSL_SOCKET_COUNT, + openSslSocketCount); + proto.write(MemInfoDumpProto.AppData.ObjectStats.WEBVIEW_INSTANCE_COUNT, webviewInstanceCount); proto.end(oToken); // SQLite mem info - final long sToken = proto.start(MemInfoProto.AppData.SQL); - proto.write(MemInfoProto.AppData.SqlStats.MEMORY_USED_KB, stats.memoryUsed / 1024); - proto.write(MemInfoProto.AppData.SqlStats.PAGECACHE_OVERFLOW_KB, + final long sToken = proto.start(MemInfoDumpProto.AppData.SQL); + proto.write(MemInfoDumpProto.AppData.SqlStats.MEMORY_USED_KB, + stats.memoryUsed / 1024); + proto.write(MemInfoDumpProto.AppData.SqlStats.PAGECACHE_OVERFLOW_KB, stats.pageCacheOverflow / 1024); - proto.write(MemInfoProto.AppData.SqlStats.MALLOC_SIZE_KB, stats.largestMemAlloc / 1024); + proto.write(MemInfoDumpProto.AppData.SqlStats.MALLOC_SIZE_KB, + stats.largestMemAlloc / 1024); int n = stats.dbStats.size(); for (int i = 0; i < n; i++) { DbStats dbStats = stats.dbStats.get(i); - final long dToken = proto.start(MemInfoProto.AppData.SqlStats.DATABASES); - proto.write(MemInfoProto.AppData.SqlStats.Database.NAME, dbStats.dbName); - proto.write(MemInfoProto.AppData.SqlStats.Database.PAGE_SIZE, dbStats.pageSize); - proto.write(MemInfoProto.AppData.SqlStats.Database.DB_SIZE, dbStats.dbSize); - proto.write(MemInfoProto.AppData.SqlStats.Database.LOOKASIDE_B, dbStats.lookaside); - proto.write(MemInfoProto.AppData.SqlStats.Database.CACHE, dbStats.cache); + final long dToken = proto.start(MemInfoDumpProto.AppData.SqlStats.DATABASES); + proto.write(MemInfoDumpProto.AppData.SqlStats.Database.NAME, dbStats.dbName); + proto.write(MemInfoDumpProto.AppData.SqlStats.Database.PAGE_SIZE, dbStats.pageSize); + proto.write(MemInfoDumpProto.AppData.SqlStats.Database.DB_SIZE, dbStats.dbSize); + proto.write(MemInfoDumpProto.AppData.SqlStats.Database.LOOKASIDE_B, + dbStats.lookaside); + proto.write(MemInfoDumpProto.AppData.SqlStats.Database.CACHE, dbStats.cache); proto.end(dToken); } proto.end(sToken); @@ -1294,7 +1322,7 @@ public final class ActivityThread extends ClientTransactionHandler { // Asset details. String assetAlloc = AssetManager.getAssetAllocations(); if (assetAlloc != null) { - proto.write(MemInfoProto.AppData.ASSET_ALLOCATIONS, assetAlloc); + proto.write(MemInfoDumpProto.AppData.ASSET_ALLOCATIONS, assetAlloc); } // Unreachable native memory @@ -1302,7 +1330,7 @@ public final class ActivityThread extends ClientTransactionHandler { int flags = mBoundApplication == null ? 0 : mBoundApplication.appInfo.flags; boolean showContents = (flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0 || android.os.Build.IS_DEBUGGABLE; - proto.write(MemInfoProto.AppData.UNREACHABLE_MEMORY, + proto.write(MemInfoDumpProto.AppData.UNREACHABLE_MEMORY, Debug.getUnreachableMemory(100, showContents)); } } @@ -1525,7 +1553,6 @@ public final class ActivityThread extends ClientTransactionHandler { public static final int UNBIND_SERVICE = 122; public static final int DUMP_SERVICE = 123; public static final int LOW_MEMORY = 124; - public static final int RELAUNCH_ACTIVITY = 126; public static final int PROFILER_CONTROL = 127; public static final int CREATE_BACKUP_AGENT = 128; public static final int DESTROY_BACKUP_AGENT = 129; @@ -1571,7 +1598,6 @@ public final class ActivityThread extends ClientTransactionHandler { case UNBIND_SERVICE: return "UNBIND_SERVICE"; case DUMP_SERVICE: return "DUMP_SERVICE"; case LOW_MEMORY: return "LOW_MEMORY"; - case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY"; case PROFILER_CONTROL: return "PROFILER_CONTROL"; case CREATE_BACKUP_AGENT: return "CREATE_BACKUP_AGENT"; case DESTROY_BACKUP_AGENT: return "DESTROY_BACKUP_AGENT"; @@ -1605,12 +1631,6 @@ public final class ActivityThread extends ClientTransactionHandler { public void handleMessage(Message msg) { if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what)); switch (msg.what) { - case RELAUNCH_ACTIVITY: { - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart"); - ActivityClientRecord r = (ActivityClientRecord)msg.obj; - handleRelaunchActivity(r); - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - } break; case BIND_APPLICATION: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication"); AppBindData data = (AppBindData)msg.obj; @@ -1784,6 +1804,7 @@ public final class ActivityThread extends ClientTransactionHandler { // message is handled. transaction.recycle(); } + // TODO(lifecycler): Recycle locally scheduled transactions. break; } Object obj = msg.obj; @@ -1913,13 +1934,13 @@ public final class ActivityThread extends ClientTransactionHandler { return mH; } - public final LoadedApk getLoadedApkForPackageName(String packageName, - CompatibilityInfo compatInfo, int flags) { - return getLoadedApkForPackageName(packageName, compatInfo, flags, UserHandle.myUserId()); + public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo, + int flags) { + return getPackageInfo(packageName, compatInfo, flags, UserHandle.myUserId()); } - public final LoadedApk getLoadedApkForPackageName(String packageName, - CompatibilityInfo compatInfo, int flags, int userId) { + public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo, + int flags, int userId) { final boolean differentUser = (UserHandle.myUserId() != userId); synchronized (mResourcesManager) { WeakReference<LoadedApk> ref; @@ -1932,13 +1953,13 @@ public final class ActivityThread extends ClientTransactionHandler { ref = mResourcePackages.get(packageName); } - LoadedApk loadedApk = ref != null ? ref.get() : null; - //Slog.i(TAG, "getLoadedApkForPackageName " + packageName + ": " + loadedApk); - //if (loadedApk != null) Slog.i(TAG, "isUptoDate " + loadedApk.mResDir - // + ": " + loadedApk.mResources.getAssets().isUpToDate()); - if (loadedApk != null && (loadedApk.mResources == null - || loadedApk.mResources.getAssets().isUpToDate())) { - if (loadedApk.isSecurityViolation() + LoadedApk packageInfo = ref != null ? ref.get() : null; + //Slog.i(TAG, "getPackageInfo " + packageName + ": " + packageInfo); + //if (packageInfo != null) Slog.i(TAG, "isUptoDate " + packageInfo.mResDir + // + ": " + packageInfo.mResources.getAssets().isUpToDate()); + if (packageInfo != null && (packageInfo.mResources == null + || packageInfo.mResources.getAssets().isUpToDate())) { + if (packageInfo.isSecurityViolation() && (flags&Context.CONTEXT_IGNORE_SECURITY) == 0) { throw new SecurityException( "Requesting code from " + packageName @@ -1946,7 +1967,7 @@ public final class ActivityThread extends ClientTransactionHandler { + mBoundApplication.processName + "/" + mBoundApplication.appInfo.uid); } - return loadedApk; + return packageInfo; } } @@ -1961,13 +1982,13 @@ public final class ActivityThread extends ClientTransactionHandler { } if (ai != null) { - return getLoadedApk(ai, compatInfo, flags); + return getPackageInfo(ai, compatInfo, flags); } return null; } - public final LoadedApk getLoadedApk(ApplicationInfo ai, CompatibilityInfo compatInfo, + public final LoadedApk getPackageInfo(ApplicationInfo ai, CompatibilityInfo compatInfo, int flags) { boolean includeCode = (flags&Context.CONTEXT_INCLUDE_CODE) != 0; boolean securityViolation = includeCode && ai.uid != 0 @@ -1989,17 +2010,17 @@ public final class ActivityThread extends ClientTransactionHandler { throw new SecurityException(msg); } } - return getLoadedApk(ai, compatInfo, null, securityViolation, includeCode, + return getPackageInfo(ai, compatInfo, null, securityViolation, includeCode, registerPackage); } @Override - public final LoadedApk getLoadedApkNoCheck(ApplicationInfo ai, + public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo) { - return getLoadedApk(ai, compatInfo, null, false, true, false); + return getPackageInfo(ai, compatInfo, null, false, true, false); } - public final LoadedApk peekLoadedApk(String packageName, boolean includeCode) { + public final LoadedApk peekPackageInfo(String packageName, boolean includeCode) { synchronized (mResourcesManager) { WeakReference<LoadedApk> ref; if (includeCode) { @@ -2011,7 +2032,7 @@ public final class ActivityThread extends ClientTransactionHandler { } } - private LoadedApk getLoadedApk(ApplicationInfo aInfo, CompatibilityInfo compatInfo, + private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo, ClassLoader baseLoader, boolean securityViolation, boolean includeCode, boolean registerPackage) { final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid)); @@ -2026,35 +2047,35 @@ public final class ActivityThread extends ClientTransactionHandler { ref = mResourcePackages.get(aInfo.packageName); } - LoadedApk loadedApk = ref != null ? ref.get() : null; - if (loadedApk == null || (loadedApk.mResources != null - && !loadedApk.mResources.getAssets().isUpToDate())) { + LoadedApk packageInfo = ref != null ? ref.get() : null; + if (packageInfo == null || (packageInfo.mResources != null + && !packageInfo.mResources.getAssets().isUpToDate())) { if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package " : "Loading resource-only package ") + aInfo.packageName + " (in " + (mBoundApplication != null ? mBoundApplication.processName : null) + ")"); - loadedApk = + packageInfo = new LoadedApk(this, aInfo, compatInfo, baseLoader, securityViolation, includeCode && (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage); if (mSystemThread && "android".equals(aInfo.packageName)) { - loadedApk.installSystemApplicationInfo(aInfo, - getSystemContext().mLoadedApk.getClassLoader()); + packageInfo.installSystemApplicationInfo(aInfo, + getSystemContext().mPackageInfo.getClassLoader()); } if (differentUser) { // Caching not supported across users } else if (includeCode) { mPackages.put(aInfo.packageName, - new WeakReference<LoadedApk>(loadedApk)); + new WeakReference<LoadedApk>(packageInfo)); } else { mResourcePackages.put(aInfo.packageName, - new WeakReference<LoadedApk>(loadedApk)); + new WeakReference<LoadedApk>(packageInfo)); } } - return loadedApk; + return packageInfo; } } @@ -2176,14 +2197,28 @@ public final class ActivityThread extends ClientTransactionHandler { @Override public void dump(PrintWriter pw, String prefix) { - pw.println(prefix + "mActivities:"); + pw.println(prefix + "Activities:"); + + if (!mActivities.isEmpty()) { + final Iterator<Map.Entry<IBinder, ActivityClientRecord>> activitiesIterator = + mActivities.entrySet().iterator(); - for (ArrayMap.Entry<IBinder, ActivityClientRecord> entry : mActivities.entrySet()) { - pw.println(prefix + " [token:" + entry.getKey().hashCode() + " record:" - + entry.getValue().toString() + "]"); + while (activitiesIterator.hasNext()) { + final ArrayMap.Entry<IBinder, ActivityClientRecord> entry = + activitiesIterator.next(); + pw.println(prefix + " [token:" + entry.getKey().hashCode() + " record:" + + entry.getValue().toString() + "]"); + } } - pw.println(prefix + "mRecentTokens:" + mRecentTokens); + if (!mRecentDestroyedActivities.isEmpty()) { + pw.println(prefix + "Recent destroyed activities:"); + for (int i = 0, size = mRecentDestroyedActivities.size(); i < size; i++) { + final DestroyedActivityInfo info = mRecentDestroyedActivities.get(i); + pw.print(prefix); + info.dump(pw, " "); + } + } } public static void dumpMemInfoTable(PrintWriter pw, Debug.MemoryInfo memInfo, boolean checkin, @@ -2486,17 +2521,17 @@ public final class ActivityThread extends ClientTransactionHandler { boolean hasSwappedOutPss, int dirtySwap, int dirtySwapPss) { final long token = proto.start(fieldId); - proto.write(MemInfoProto.ProcessMemory.MemoryInfo.NAME, name); - proto.write(MemInfoProto.ProcessMemory.MemoryInfo.TOTAL_PSS_KB, pss); - proto.write(MemInfoProto.ProcessMemory.MemoryInfo.CLEAN_PSS_KB, cleanPss); - proto.write(MemInfoProto.ProcessMemory.MemoryInfo.SHARED_DIRTY_KB, sharedDirty); - proto.write(MemInfoProto.ProcessMemory.MemoryInfo.PRIVATE_DIRTY_KB, privateDirty); - proto.write(MemInfoProto.ProcessMemory.MemoryInfo.SHARED_CLEAN_KB, sharedClean); - proto.write(MemInfoProto.ProcessMemory.MemoryInfo.PRIVATE_CLEAN_KB, privateClean); + proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.NAME, name); + proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.TOTAL_PSS_KB, pss); + proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.CLEAN_PSS_KB, cleanPss); + proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.SHARED_DIRTY_KB, sharedDirty); + proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.PRIVATE_DIRTY_KB, privateDirty); + proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.SHARED_CLEAN_KB, sharedClean); + proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.PRIVATE_CLEAN_KB, privateClean); if (hasSwappedOutPss) { - proto.write(MemInfoProto.ProcessMemory.MemoryInfo.DIRTY_SWAP_PSS_KB, dirtySwapPss); + proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.DIRTY_SWAP_PSS_KB, dirtySwapPss); } else { - proto.write(MemInfoProto.ProcessMemory.MemoryInfo.DIRTY_SWAP_KB, dirtySwap); + proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.DIRTY_SWAP_KB, dirtySwap); } proto.end(token); @@ -2511,26 +2546,26 @@ public final class ActivityThread extends ClientTransactionHandler { long dalvikMax, long dalvikAllocated, long dalvikFree) { if (!dumpSummaryOnly) { - final long nhToken = proto.start(MemInfoProto.ProcessMemory.NATIVE_HEAP); - dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.HeapInfo.MEM_INFO, "Native Heap", + final long nhToken = proto.start(MemInfoDumpProto.ProcessMemory.NATIVE_HEAP); + dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.HeapInfo.MEM_INFO, "Native Heap", memInfo.nativePss, memInfo.nativeSwappablePss, memInfo.nativeSharedDirty, memInfo.nativePrivateDirty, memInfo.nativeSharedClean, memInfo.nativePrivateClean, memInfo.hasSwappedOutPss, memInfo.nativeSwappedOut, memInfo.nativeSwappedOutPss); - proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB, nativeMax); - proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB, nativeAllocated); - proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_FREE_KB, nativeFree); + proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB, nativeMax); + proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB, nativeAllocated); + proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_FREE_KB, nativeFree); proto.end(nhToken); - final long dvToken = proto.start(MemInfoProto.ProcessMemory.DALVIK_HEAP); - dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.HeapInfo.MEM_INFO, "Dalvik Heap", + final long dvToken = proto.start(MemInfoDumpProto.ProcessMemory.DALVIK_HEAP); + dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.HeapInfo.MEM_INFO, "Dalvik Heap", memInfo.dalvikPss, memInfo.dalvikSwappablePss, memInfo.dalvikSharedDirty, memInfo.dalvikPrivateDirty, memInfo.dalvikSharedClean, memInfo.dalvikPrivateClean, memInfo.hasSwappedOutPss, memInfo.dalvikSwappedOut, memInfo.dalvikSwappedOutPss); - proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB, dalvikMax); - proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB, dalvikAllocated); - proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_FREE_KB, dalvikFree); + proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB, dalvikMax); + proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB, dalvikAllocated); + proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_FREE_KB, dalvikFree); proto.end(dvToken); int otherPss = memInfo.otherPss; @@ -2554,7 +2589,7 @@ public final class ActivityThread extends ClientTransactionHandler { if (myPss != 0 || mySharedDirty != 0 || myPrivateDirty != 0 || mySharedClean != 0 || myPrivateClean != 0 || (memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut) != 0) { - dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.OTHER_HEAPS, + dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.OTHER_HEAPS, Debug.MemoryInfo.getOtherLabel(i), myPss, mySwappablePss, mySharedDirty, myPrivateDirty, mySharedClean, myPrivateClean, @@ -2571,21 +2606,23 @@ public final class ActivityThread extends ClientTransactionHandler { } } - dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.UNKNOWN_HEAP, "Unknown", + dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.UNKNOWN_HEAP, "Unknown", otherPss, otherSwappablePss, otherSharedDirty, otherPrivateDirty, otherSharedClean, otherPrivateClean, memInfo.hasSwappedOutPss, otherSwappedOut, otherSwappedOutPss); - final long tToken = proto.start(MemInfoProto.ProcessMemory.TOTAL_HEAP); - dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.HeapInfo.MEM_INFO, "TOTAL", + final long tToken = proto.start(MemInfoDumpProto.ProcessMemory.TOTAL_HEAP); + dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.HeapInfo.MEM_INFO, "TOTAL", memInfo.getTotalPss(), memInfo.getTotalSwappablePss(), memInfo.getTotalSharedDirty(), memInfo.getTotalPrivateDirty(), memInfo.getTotalSharedClean(), memInfo.getTotalPrivateClean(), memInfo.hasSwappedOutPss, memInfo.getTotalSwappedOut(), memInfo.getTotalSwappedOutPss()); - proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB, nativeMax + dalvikMax); - proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB, + proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB, + nativeMax + dalvikMax); + proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB, nativeAllocated + dalvikAllocated); - proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_FREE_KB, nativeFree + dalvikFree); + proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_FREE_KB, + nativeFree + dalvikFree); proto.end(tToken); if (dumpDalvik) { @@ -2603,7 +2640,7 @@ public final class ActivityThread extends ClientTransactionHandler { if (myPss != 0 || mySharedDirty != 0 || myPrivateDirty != 0 || mySharedClean != 0 || myPrivateClean != 0 || (memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut) != 0) { - dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.DALVIK_DETAILS, + dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.DALVIK_DETAILS, Debug.MemoryInfo.getOtherLabel(i), myPss, mySwappablePss, mySharedDirty, myPrivateDirty, mySharedClean, myPrivateClean, @@ -2613,24 +2650,26 @@ public final class ActivityThread extends ClientTransactionHandler { } } - final long asToken = proto.start(MemInfoProto.ProcessMemory.APP_SUMMARY); - proto.write(MemInfoProto.ProcessMemory.AppSummary.JAVA_HEAP_PSS_KB, + final long asToken = proto.start(MemInfoDumpProto.ProcessMemory.APP_SUMMARY); + proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.JAVA_HEAP_PSS_KB, memInfo.getSummaryJavaHeap()); - proto.write(MemInfoProto.ProcessMemory.AppSummary.NATIVE_HEAP_PSS_KB, + proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.NATIVE_HEAP_PSS_KB, memInfo.getSummaryNativeHeap()); - proto.write(MemInfoProto.ProcessMemory.AppSummary.CODE_PSS_KB, memInfo.getSummaryCode()); - proto.write(MemInfoProto.ProcessMemory.AppSummary.STACK_PSS_KB, memInfo.getSummaryStack()); - proto.write(MemInfoProto.ProcessMemory.AppSummary.GRAPHICS_PSS_KB, + proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.CODE_PSS_KB, + memInfo.getSummaryCode()); + proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.STACK_PSS_KB, + memInfo.getSummaryStack()); + proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.GRAPHICS_PSS_KB, memInfo.getSummaryGraphics()); - proto.write(MemInfoProto.ProcessMemory.AppSummary.PRIVATE_OTHER_PSS_KB, + proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.PRIVATE_OTHER_PSS_KB, memInfo.getSummaryPrivateOther()); - proto.write(MemInfoProto.ProcessMemory.AppSummary.SYSTEM_PSS_KB, + proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.SYSTEM_PSS_KB, memInfo.getSummarySystem()); if (memInfo.hasSwappedOutPss) { - proto.write(MemInfoProto.ProcessMemory.AppSummary.TOTAL_SWAP_PSS, + proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.TOTAL_SWAP_PSS, memInfo.getSummaryTotalSwapPss()); } else { - proto.write(MemInfoProto.ProcessMemory.AppSummary.TOTAL_SWAP_PSS, + proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.TOTAL_SWAP_PSS, memInfo.getSummaryTotalSwap()); } proto.end(asToken); @@ -2724,6 +2763,11 @@ public final class ActivityThread extends ClientTransactionHandler { } } + @Override + TransactionExecutor getTransactionExecutor() { + return mTransactionExecutor; + } + void sendMessage(int what, Object obj) { sendMessage(what, obj, 0, 0, false); } @@ -2778,8 +2822,8 @@ public final class ActivityThread extends ClientTransactionHandler { /** Core implementation of activity launch. */ private Activity performLaunchActivity(ActivityClientRecord r) { ActivityInfo aInfo = r.activityInfo; - if (r.loadedApk == null) { - r.loadedApk = getLoadedApk(aInfo.applicationInfo, r.compatInfo, + if (r.packageInfo == null) { + r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo, Context.CONTEXT_INCLUDE_CODE); } @@ -2816,15 +2860,15 @@ public final class ActivityThread extends ClientTransactionHandler { } try { - Application app = r.loadedApk.makeApplication(false, mInstrumentation); + Application app = r.packageInfo.makeApplication(false, mInstrumentation); if (localLOGV) Slog.v(TAG, "Performing launch of " + r); if (localLOGV) Slog.v( TAG, r + ": app=" + app + ", appName=" + app.getPackageName() - + ", pkg=" + r.loadedApk.getPackageName() + + ", pkg=" + r.packageInfo.getPackageName() + ", comp=" + r.intent.getComponent().toShortString() - + ", dir=" + r.loadedApk.getAppDir()); + + ", dir=" + r.packageInfo.getAppDir()); if (activity != null) { CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); @@ -2870,11 +2914,6 @@ public final class ActivityThread extends ClientTransactionHandler { r.setState(ON_CREATE); mActivities.put(r.token, r); - mRecentTokens.push(r.token.hashCode()); - - if (mRecentTokens.size() > MAX_RECENT_TOKENS) { - mRecentTokens.removeLast(); - } } catch (SuperNotCalledException e) { throw e; @@ -2907,7 +2946,7 @@ public final class ActivityThread extends ClientTransactionHandler { } // Start - activity.performStart(); + activity.performStart("handleStartActivity"); r.setState(ON_START); if (pendingActions == null) { @@ -2969,7 +3008,7 @@ public final class ActivityThread extends ClientTransactionHandler { } ContextImpl appContext = ContextImpl.createActivityContext( - this, r.loadedApk, r.activityInfo, r.token, displayId, r.overrideConfig); + this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig); final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance(); // For debugging purposes, if the activity's package name contains the value of @@ -2977,7 +3016,7 @@ public final class ActivityThread extends ClientTransactionHandler { // its content on a secondary display if there is one. String pkgName = SystemProperties.get("debug.second-display.pkg"); if (pkgName != null && !pkgName.isEmpty() - && r.loadedApk.mPackageName.contains(pkgName)) { + && r.packageInfo.mPackageName.contains(pkgName)) { for (int id : dm.getDisplayIds()) { if (id != Display.DEFAULT_DISPLAY) { Display display = @@ -3096,7 +3135,7 @@ public final class ActivityThread extends ClientTransactionHandler { checkAndBlockForNetworkAccess(); deliverNewIntents(r, intents); if (resumed) { - r.activity.performResume(false); + r.activity.performResume(false, "performNewIntents"); r.activity.mTemporaryPause = false; } @@ -3309,7 +3348,7 @@ public final class ActivityThread extends ClientTransactionHandler { String component = data.intent.getComponent().getClassName(); - LoadedApk loadedApk = getLoadedApkNoCheck( + LoadedApk packageInfo = getPackageInfoNoCheck( data.info.applicationInfo, data.compatInfo); IActivityManager mgr = ActivityManager.getService(); @@ -3318,7 +3357,7 @@ public final class ActivityThread extends ClientTransactionHandler { BroadcastReceiver receiver; ContextImpl context; try { - app = loadedApk.makeApplication(false, mInstrumentation); + app = packageInfo.makeApplication(false, mInstrumentation); context = (ContextImpl) app.getBaseContext(); if (data.info.splitName != null) { context = (ContextImpl) context.createContextForSplit(data.info.splitName); @@ -3327,7 +3366,7 @@ public final class ActivityThread extends ClientTransactionHandler { data.intent.setExtrasClassLoader(cl); data.intent.prepareToEnterProcess(); data.setExtrasClassLoader(cl); - receiver = loadedApk.getAppFactory() + receiver = packageInfo.getAppFactory() .instantiateReceiver(cl, data.info.name, data.intent); } catch (Exception e) { if (DEBUG_BROADCAST) Slog.i(TAG, @@ -3343,9 +3382,9 @@ public final class ActivityThread extends ClientTransactionHandler { TAG, "Performing receive of " + data.intent + ": app=" + app + ", appName=" + app.getPackageName() - + ", pkg=" + loadedApk.getPackageName() + + ", pkg=" + packageInfo.getPackageName() + ", comp=" + data.intent.getComponent().toShortString() - + ", dir=" + loadedApk.getAppDir()); + + ", dir=" + packageInfo.getAppDir()); sCurrentBroadcastIntent.set(data.intent); receiver.setPendingResult(data); @@ -3390,8 +3429,8 @@ public final class ActivityThread extends ClientTransactionHandler { unscheduleGcIdler(); // instantiate the BackupAgent class named in the manifest - LoadedApk loadedApk = getLoadedApkNoCheck(data.appInfo, data.compatInfo); - String packageName = loadedApk.mPackageName; + LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo, data.compatInfo); + String packageName = packageInfo.mPackageName; if (packageName == null) { Slog.d(TAG, "Asked to create backup agent for nonexistent package"); return; @@ -3417,11 +3456,11 @@ public final class ActivityThread extends ClientTransactionHandler { try { if (DEBUG_BACKUP) Slog.v(TAG, "Initializing agent class " + classname); - java.lang.ClassLoader cl = loadedApk.getClassLoader(); + java.lang.ClassLoader cl = packageInfo.getClassLoader(); agent = (BackupAgent) cl.loadClass(classname).newInstance(); // set up the agent's context - ContextImpl context = ContextImpl.createAppContext(this, loadedApk); + ContextImpl context = ContextImpl.createAppContext(this, packageInfo); context.setOuterContext(agent); agent.attach(context); @@ -3457,8 +3496,8 @@ public final class ActivityThread extends ClientTransactionHandler { private void handleDestroyBackupAgent(CreateBackupAgentData data) { if (DEBUG_BACKUP) Slog.v(TAG, "handleDestroyBackupAgent: " + data); - LoadedApk loadedApk = getLoadedApkNoCheck(data.appInfo, data.compatInfo); - String packageName = loadedApk.mPackageName; + LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo, data.compatInfo); + String packageName = packageInfo.mPackageName; BackupAgent agent = mBackupAgents.get(packageName); if (agent != null) { try { @@ -3478,12 +3517,12 @@ public final class ActivityThread extends ClientTransactionHandler { // we are back active so skip it. unscheduleGcIdler(); - LoadedApk loadedApk = getLoadedApkNoCheck( + LoadedApk packageInfo = getPackageInfoNoCheck( data.info.applicationInfo, data.compatInfo); Service service = null; try { - java.lang.ClassLoader cl = loadedApk.getClassLoader(); - service = loadedApk.getAppFactory() + java.lang.ClassLoader cl = packageInfo.getClassLoader(); + service = packageInfo.getAppFactory() .instantiateService(cl, data.info.name, data.intent); } catch (Exception e) { if (!mInstrumentation.onException(service, e)) { @@ -3496,10 +3535,10 @@ public final class ActivityThread extends ClientTransactionHandler { try { if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name); - ContextImpl context = ContextImpl.createAppContext(this, loadedApk); + ContextImpl context = ContextImpl.createAppContext(this, packageInfo); context.setOuterContext(service); - Application app = loadedApk.makeApplication(false, mInstrumentation); + Application app = packageInfo.makeApplication(false, mInstrumentation); service.attach(context, this, data.info.name, data.token, app, ActivityManager.getService()); service.onCreate(); @@ -3697,56 +3736,64 @@ public final class ActivityThread extends ClientTransactionHandler { //Slog.i(TAG, "Running services: " + mServices); } - ActivityClientRecord performResumeActivity(IBinder token, boolean clearHide, String reason) { - ActivityClientRecord r = mActivities.get(token); - if (localLOGV) Slog.v(TAG, "Performing resume of " + r - + " finished=" + r.activity.mFinished); - if (r != null && !r.activity.mFinished) { - if (clearHide) { - r.hideForNow = false; - r.activity.mStartedActivity = false; + /** + * Resume the activity. + * @param token Target activity token. + * @param finalStateRequest Flag indicating if this is part of final state resolution for a + * transaction. + * @param reason Reason for performing the action. + * + * @return The {@link ActivityClientRecord} that was resumed, {@code null} otherwise. + */ + @VisibleForTesting + public ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest, + String reason) { + final ActivityClientRecord r = mActivities.get(token); + if (localLOGV) { + Slog.v(TAG, "Performing resume of " + r + " finished=" + r.activity.mFinished); + } + if (r == null || r.activity.mFinished) { + return null; + } + if (r.getLifecycleState() == ON_RESUME) { + if (!finalStateRequest) { + final RuntimeException e = new IllegalStateException( + "Trying to resume activity which is already resumed"); + Slog.e(TAG, e.getMessage(), e); + Slog.e(TAG, r.getStateString()); + // TODO(lifecycler): A double resume request is possible when an activity + // receives two consequent transactions with relaunch requests and "resumed" + // final state requests and the second relaunch is omitted. We still try to + // handle two resume requests for the final state. For cases other than this + // one, we don't expect it to happen. } - try { - r.activity.onStateNotSaved(); - r.activity.mFragments.noteStateNotSaved(); - checkAndBlockForNetworkAccess(); - if (r.pendingIntents != null) { - deliverNewIntents(r, r.pendingIntents); - r.pendingIntents = null; - } - if (r.pendingResults != null) { - deliverResults(r, r.pendingResults); - r.pendingResults = null; - } - r.activity.performResume(r.startsNotResumed); - - synchronized (mResourcesManager) { - // If there is a pending local relaunch that was requested when the activity was - // paused, it will put the activity into paused state when it finally happens. - // Since the activity resumed before being relaunched, we don't want that to - // happen, so we need to clear the request to relaunch paused. - for (int i = mRelaunchingActivities.size() - 1; i >= 0; i--) { - final ActivityClientRecord relaunching = mRelaunchingActivities.get(i); - if (relaunching.token == r.token - && relaunching.onlyLocalRequest && relaunching.startsNotResumed) { - relaunching.startsNotResumed = false; - } - } - } - - EventLog.writeEvent(LOG_AM_ON_RESUME_CALLED, UserHandle.myUserId(), - r.activity.getComponentName().getClassName(), reason); + return null; + } + if (finalStateRequest) { + r.hideForNow = false; + r.activity.mStartedActivity = false; + } + try { + r.activity.onStateNotSaved(); + r.activity.mFragments.noteStateNotSaved(); + checkAndBlockForNetworkAccess(); + if (r.pendingIntents != null) { + deliverNewIntents(r, r.pendingIntents); + r.pendingIntents = null; + } + if (r.pendingResults != null) { + deliverResults(r, r.pendingResults); + r.pendingResults = null; + } + r.activity.performResume(r.startsNotResumed, reason); - r.state = null; - r.persistentState = null; - r.setState(ON_RESUME); - } catch (Exception e) { - if (!mInstrumentation.onException(r.activity, e)) { - throw new RuntimeException( - "Unable to resume activity " - + r.intent.getComponent().toShortString() - + ": " + e.toString(), e); - } + r.state = null; + r.persistentState = null; + r.setState(ON_RESUME); + } catch (Exception e) { + if (!mInstrumentation.onException(r.activity, e)) { + throw new RuntimeException("Unable to resume activity " + + r.intent.getComponent().toShortString() + ": " + e.toString(), e); } } return r; @@ -3770,7 +3817,7 @@ public final class ActivityThread extends ClientTransactionHandler { } @Override - public void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, + public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) { // If we are getting ready to gc after going to the background, well // we are back active so skip it. @@ -3778,190 +3825,121 @@ public final class ActivityThread extends ClientTransactionHandler { mSomeActivitiesChanged = true; // TODO Push resumeArgs into the activity for consideration - final ActivityClientRecord r = performResumeActivity(token, clearHide, reason); + final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason); + if (r == null) { + // We didn't actually resume the activity, so skipping any follow-up actions. + return; + } - if (r != null) { - final Activity a = r.activity; + final Activity a = r.activity; - if (localLOGV) Slog.v( - TAG, "Resume " + r + " started activity: " + - a.mStartedActivity + ", hideForNow: " + r.hideForNow - + ", finished: " + a.mFinished); - - final int forwardBit = isForward ? - WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0; - - // If the window hasn't yet been added to the window manager, - // and this guy didn't finish itself or start another activity, - // then go ahead and add the window. - boolean willBeVisible = !a.mStartedActivity; - if (!willBeVisible) { - try { - willBeVisible = ActivityManager.getService().willActivityBeVisible( - a.getActivityToken()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + if (localLOGV) { + Slog.v(TAG, "Resume " + r + " started activity: " + a.mStartedActivity + + ", hideForNow: " + r.hideForNow + ", finished: " + a.mFinished); + } + + final int forwardBit = isForward + ? WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0; + + // If the window hasn't yet been added to the window manager, + // and this guy didn't finish itself or start another activity, + // then go ahead and add the window. + boolean willBeVisible = !a.mStartedActivity; + if (!willBeVisible) { + try { + willBeVisible = ActivityManager.getService().willActivityBeVisible( + a.getActivityToken()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + if (r.window == null && !a.mFinished && willBeVisible) { + r.window = r.activity.getWindow(); + View decor = r.window.getDecorView(); + decor.setVisibility(View.INVISIBLE); + ViewManager wm = a.getWindowManager(); + WindowManager.LayoutParams l = r.window.getAttributes(); + a.mDecor = decor; + l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; + l.softInputMode |= forwardBit; + if (r.mPreserveWindow) { + a.mWindowAdded = true; + r.mPreserveWindow = false; + // Normally the ViewRoot sets up callbacks with the Activity + // in addView->ViewRootImpl#setView. If we are instead reusing + // the decor view we have to notify the view root that the + // callbacks may have changed. + ViewRootImpl impl = decor.getViewRootImpl(); + if (impl != null) { + impl.notifyChildRebuilt(); } } - if (r.window == null && !a.mFinished && willBeVisible) { - r.window = r.activity.getWindow(); - View decor = r.window.getDecorView(); - decor.setVisibility(View.INVISIBLE); - ViewManager wm = a.getWindowManager(); - WindowManager.LayoutParams l = r.window.getAttributes(); - a.mDecor = decor; - l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; - l.softInputMode |= forwardBit; - if (r.mPreserveWindow) { + if (a.mVisibleFromClient) { + if (!a.mWindowAdded) { a.mWindowAdded = true; - r.mPreserveWindow = false; - // Normally the ViewRoot sets up callbacks with the Activity - // in addView->ViewRootImpl#setView. If we are instead reusing - // the decor view we have to notify the view root that the - // callbacks may have changed. - ViewRootImpl impl = decor.getViewRootImpl(); - if (impl != null) { - impl.notifyChildRebuilt(); - } - } - if (a.mVisibleFromClient) { - if (!a.mWindowAdded) { - a.mWindowAdded = true; - wm.addView(decor, l); - } else { - // The activity will get a callback for this {@link LayoutParams} change - // earlier. However, at that time the decor will not be set (this is set - // in this method), so no action will be taken. This call ensures the - // callback occurs with the decor set. - a.onWindowAttributesChanged(l); - } + wm.addView(decor, l); + } else { + // The activity will get a callback for this {@link LayoutParams} change + // earlier. However, at that time the decor will not be set (this is set + // in this method), so no action will be taken. This call ensures the + // callback occurs with the decor set. + a.onWindowAttributesChanged(l); } + } // If the window has already been added, but during resume // we started another activity, then don't yet make the // window visible. - } else if (!willBeVisible) { - if (localLOGV) Slog.v( - TAG, "Launch " + r + " mStartedActivity set"); - r.hideForNow = true; - } - - // Get rid of anything left hanging around. - cleanUpPendingRemoveWindows(r, false /* force */); - - // The window is now visible if it has been added, we are not - // simply finishing, and we are not starting another activity. - if (!r.activity.mFinished && willBeVisible - && r.activity.mDecor != null && !r.hideForNow) { - if (r.newConfig != null) { - performConfigurationChangedForActivity(r, r.newConfig); - if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity " - + r.activityInfo.name + " with newConfig " + r.activity.mCurrentConfig); - r.newConfig = null; - } - if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward=" - + isForward); - WindowManager.LayoutParams l = r.window.getAttributes(); - if ((l.softInputMode - & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) - != forwardBit) { - l.softInputMode = (l.softInputMode - & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)) - | forwardBit; - if (r.activity.mVisibleFromClient) { - ViewManager wm = a.getWindowManager(); - View decor = r.window.getDecorView(); - wm.updateViewLayout(decor, l); - } - } - - r.activity.mVisibleFromServer = true; - mNumVisibleActivities++; - if (r.activity.mVisibleFromClient) { - r.activity.makeVisible(); - } - } - - if (!r.onlyLocalRequest) { - r.nextIdle = mNewActivities; - mNewActivities = r; - if (localLOGV) Slog.v( - TAG, "Scheduling idle handler for " + r); - Looper.myQueue().addIdleHandler(new Idler()); - } - r.onlyLocalRequest = false; - } else { - // If an exception was thrown when trying to resume, then - // just end this activity. - try { - ActivityManager.getService() - .finishActivity(token, Activity.RESULT_CANCELED, null, - Activity.DONT_FINISH_TASK_WITH_ACTIVITY); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } + } else if (!willBeVisible) { + if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set"); + r.hideForNow = true; } - } - private int mThumbnailWidth = -1; - private int mThumbnailHeight = -1; - private Bitmap mAvailThumbnailBitmap = null; - private Canvas mThumbnailCanvas = null; + // Get rid of anything left hanging around. + cleanUpPendingRemoveWindows(r, false /* force */); - private Bitmap createThumbnailBitmap(ActivityClientRecord r) { - Bitmap thumbnail = mAvailThumbnailBitmap; - try { - if (thumbnail == null) { - int w = mThumbnailWidth; - int h; - if (w < 0) { - Resources res = r.activity.getResources(); - int wId = com.android.internal.R.dimen.thumbnail_width; - int hId = com.android.internal.R.dimen.thumbnail_height; - mThumbnailWidth = w = res.getDimensionPixelSize(wId); - mThumbnailHeight = h = res.getDimensionPixelSize(hId); - } else { - h = mThumbnailHeight; - } - - // On platforms where we don't want thumbnails, set dims to (0,0) - if ((w > 0) && (h > 0)) { - thumbnail = Bitmap.createBitmap(r.activity.getResources().getDisplayMetrics(), - w, h, THUMBNAIL_FORMAT); - thumbnail.eraseColor(0); - } - } - - if (thumbnail != null) { - Canvas cv = mThumbnailCanvas; - if (cv == null) { - mThumbnailCanvas = cv = new Canvas(); + // The window is now visible if it has been added, we are not + // simply finishing, and we are not starting another activity. + if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) { + if (r.newConfig != null) { + performConfigurationChangedForActivity(r, r.newConfig); + if (DEBUG_CONFIGURATION) { + Slog.v(TAG, "Resuming activity " + r.activityInfo.name + " with newConfig " + + r.activity.mCurrentConfig); } - - cv.setBitmap(thumbnail); - if (!r.activity.onCreateThumbnail(thumbnail, cv)) { - mAvailThumbnailBitmap = thumbnail; - thumbnail = null; + r.newConfig = null; + } + if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward=" + isForward); + WindowManager.LayoutParams l = r.window.getAttributes(); + if ((l.softInputMode + & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) + != forwardBit) { + l.softInputMode = (l.softInputMode + & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)) + | forwardBit; + if (r.activity.mVisibleFromClient) { + ViewManager wm = a.getWindowManager(); + View decor = r.window.getDecorView(); + wm.updateViewLayout(decor, l); } - cv.setBitmap(null); } - } catch (Exception e) { - if (!mInstrumentation.onException(r.activity, e)) { - throw new RuntimeException( - "Unable to create thumbnail of " - + r.intent.getComponent().toShortString() - + ": " + e.toString(), e); + r.activity.mVisibleFromServer = true; + mNumVisibleActivities++; + if (r.activity.mVisibleFromClient) { + r.activity.makeVisible(); } - thumbnail = null; } - return thumbnail; + r.nextIdle = mNewActivities; + mNewActivities = r; + if (localLOGV) Slog.v(TAG, "Scheduling idle handler for " + r); + Looper.myQueue().addIdleHandler(new Idler()); } @Override public void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving, - int configChanges, boolean dontReport, PendingTransactionActions pendingActions) { + int configChanges, PendingTransactionActions pendingActions, String reason) { ActivityClientRecord r = mActivities.get(token); if (r != null) { if (userLeaving) { @@ -3969,22 +3947,12 @@ public final class ActivityThread extends ClientTransactionHandler { } r.activity.mConfigChangeFlags |= configChanges; - performPauseActivity(token, finished, r.isPreHoneycomb(), "handlePauseActivity", - pendingActions); + performPauseActivity(r, finished, reason, pendingActions); // Make sure any pending writes are now committed. if (r.isPreHoneycomb()) { QueuedWork.waitToFinish(); } - - // Tell the activity manager we have paused. - if (!dontReport) { - try { - ActivityManager.getService().activityPaused(token); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } - } mSomeActivitiesChanged = true; } } @@ -3993,16 +3961,18 @@ public final class ActivityThread extends ClientTransactionHandler { mInstrumentation.callActivityOnUserLeaving(r.activity); } - final Bundle performPauseActivity(IBinder token, boolean finished, - boolean saveState, String reason, PendingTransactionActions pendingActions) { + final Bundle performPauseActivity(IBinder token, boolean finished, String reason, + PendingTransactionActions pendingActions) { ActivityClientRecord r = mActivities.get(token); - return r != null - ? performPauseActivity(r, finished, saveState, reason, pendingActions) - : null; + return r != null ? performPauseActivity(r, finished, reason, pendingActions) : null; } - private Bundle performPauseActivity(ActivityClientRecord r, boolean finished, boolean saveState, - String reason, PendingTransactionActions pendingActions) { + /** + * Pause the activity. + * @return Saved instance state for pre-Honeycomb apps if it was saved, {@code null} otherwise. + */ + private Bundle performPauseActivity(ActivityClientRecord r, boolean finished, String reason, + PendingTransactionActions pendingActions) { if (r.paused) { if (r.activity.mFinished) { // If we are finishing, we won't call onResume() in certain cases. @@ -4019,9 +3989,10 @@ public final class ActivityThread extends ClientTransactionHandler { r.activity.mFinished = true; } - // Next have the activity save its current state and managed dialogs... - if (!r.activity.mFinished && saveState) { - callCallActivityOnSaveInstanceState(r); + // Pre-Honeycomb apps always save their state before pausing + final boolean shouldSaveState = !r.activity.mFinished && r.isPreHoneycomb(); + if (shouldSaveState) { + callActivityOnSaveInstanceState(r); } performPauseActivityIfNeeded(r, reason); @@ -4048,7 +4019,7 @@ public final class ActivityThread extends ClientTransactionHandler { } } - return !r.activity.mFinished && saveState ? r.state : null; + return shouldSaveState ? r.state : null; } private void performPauseActivityIfNeeded(ActivityClientRecord r, String reason) { @@ -4060,8 +4031,6 @@ public final class ActivityThread extends ClientTransactionHandler { try { r.activity.mCalled = false; mInstrumentation.callActivityOnPause(r.activity); - EventLog.writeEvent(LOG_AM_ON_PAUSE_CALLED, UserHandle.myUserId(), - r.activity.getComponentName().getClassName(), reason); if (!r.activity.mCalled) { throw new SuperNotCalledException("Activity " + safeToComponentShortString(r.intent) + " did not call through to super.onPause()"); @@ -4077,9 +4046,11 @@ public final class ActivityThread extends ClientTransactionHandler { r.setState(ON_PAUSE); } + /** Called from {@link LocalActivityManager}. */ final void performStopActivity(IBinder token, boolean saveState, String reason) { ActivityClientRecord r = mActivities.get(token); - performStopActivityInner(r, null, false, saveState, reason); + performStopActivityInner(r, null /* stopInfo */, false /* keepShown */, saveState, + false /* finalStateRequest */, reason); } private static final class ProviderRefCount { @@ -4111,9 +4082,16 @@ public final class ActivityThread extends ClientTransactionHandler { * it the result when it is done, but the window may still be visible. * For the client, we want to call onStop()/onStart() to indicate when * the activity's UI visibility changes. + * @param r Target activity client record. + * @param info Action that will report activity stop to server. + * @param keepShown Flag indicating whether the activity is still shown. + * @param saveState Flag indicating whether the activity state should be saved. + * @param finalStateRequest Flag indicating if this call is handling final lifecycle state + * request for a transaction. + * @param reason Reason for performing this operation. */ private void performStopActivityInner(ActivityClientRecord r, StopInfo info, boolean keepShown, - boolean saveState, String reason) { + boolean saveState, boolean finalStateRequest, String reason) { if (localLOGV) Slog.v(TAG, "Performing stop of " + r); if (r != null) { if (!keepShown && r.stopped) { @@ -4123,11 +4101,13 @@ public final class ActivityThread extends ClientTransactionHandler { // if the activity isn't resumed. return; } - RuntimeException e = new RuntimeException( - "Performing stop of activity that is already stopped: " - + r.intent.getComponent().toShortString()); - Slog.e(TAG, e.getMessage(), e); - Slog.e(TAG, r.getStateString()); + if (!finalStateRequest) { + final RuntimeException e = new RuntimeException( + "Performing stop of activity that is already stopped: " + + r.intent.getComponent().toShortString()); + Slog.e(TAG, e.getMessage(), e); + Slog.e(TAG, r.getStateString()); + } } // One must first be paused before stopped... @@ -4149,30 +4129,45 @@ public final class ActivityThread extends ClientTransactionHandler { } } - // Next have the activity save its current state and managed dialogs... - if (!r.activity.mFinished && saveState) { - if (r.state == null) { - callCallActivityOnSaveInstanceState(r); - } + if (!keepShown) { + callActivityOnStop(r, saveState, reason); } + } + } - if (!keepShown) { - try { - // Now we are idle. - r.activity.performStop(false /*preserveWindow*/); - } catch (Exception e) { - if (!mInstrumentation.onException(r.activity, e)) { - throw new RuntimeException( - "Unable to stop activity " + /** + * Calls {@link Activity#onStop()} and {@link Activity#onSaveInstanceState(Bundle)}, and updates + * the client record's state. + * All calls to stop an activity must be done through this method to make sure that + * {@link Activity#onSaveInstanceState(Bundle)} is also executed in the same call. + */ + private void callActivityOnStop(ActivityClientRecord r, boolean saveState, String reason) { + // Before P onSaveInstanceState was called before onStop, starting with P it's + // called after. Before Honeycomb state was always saved before onPause. + final boolean shouldSaveState = saveState && !r.activity.mFinished && r.state == null + && !r.isPreHoneycomb(); + final boolean isPreP = r.isPreP(); + if (shouldSaveState && isPreP) { + callActivityOnSaveInstanceState(r); + } + + try { + r.activity.performStop(false /*preserveWindow*/, reason); + } catch (SuperNotCalledException e) { + throw e; + } catch (Exception e) { + if (!mInstrumentation.onException(r.activity, e)) { + throw new RuntimeException( + "Unable to stop activity " + r.intent.getComponent().toShortString() + ": " + e.toString(), e); - } - } - r.setState(ON_STOP); - EventLog.writeEvent(LOG_AM_ON_STOP_CALLED, UserHandle.myUserId(), - r.activity.getComponentName().getClassName(), reason); } } + r.setState(ON_STOP); + + if (shouldSaveState && !isPreP) { + callActivityOnSaveInstanceState(r); + } } private void updateVisibility(ActivityClientRecord r, boolean show) { @@ -4205,12 +4200,13 @@ public final class ActivityThread extends ClientTransactionHandler { @Override public void handleStopActivity(IBinder token, boolean show, int configChanges, - PendingTransactionActions pendingActions) { + PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) { final ActivityClientRecord r = mActivities.get(token); r.activity.mConfigChangeFlags |= configChanges; final StopInfo stopInfo = new StopInfo(); - performStopActivityInner(r, stopInfo, show, true, "handleStopActivity"); + performStopActivityInner(r, stopInfo, show, true /* saveState */, finalStateRequest, + reason); if (localLOGV) Slog.v( TAG, "Finishing stop of " + r + ": show=" + show @@ -4245,7 +4241,7 @@ public final class ActivityThread extends ClientTransactionHandler { public void performRestartActivity(IBinder token, boolean start) { ActivityClientRecord r = mActivities.get(token); if (r.stopped) { - r.activity.performRestart(start); + r.activity.performRestart(start, "performRestartActivity"); if (start) { r.setState(ON_START); } @@ -4262,13 +4258,14 @@ public final class ActivityThread extends ClientTransactionHandler { } if (!show && !r.stopped) { - performStopActivityInner(r, null, show, false, "handleWindowVisibility"); + performStopActivityInner(r, null /* stopInfo */, show, false /* saveState */, + false /* finalStateRequest */, "handleWindowVisibility"); } else if (show && r.stopped) { // If we are getting ready to gc after going to the background, well // we are back active so skip it. unscheduleGcIdler(); - r.activity.performRestart(true /* start */); + r.activity.performRestart(true /* start */, "handleWindowVisibility"); r.setState(ON_START); } if (r.activity.mDecor != null) { @@ -4292,24 +4289,7 @@ public final class ActivityThread extends ClientTransactionHandler { if (sleeping) { if (!r.stopped && !r.isPreHoneycomb()) { - if (!r.activity.mFinished && r.state == null) { - callCallActivityOnSaveInstanceState(r); - } - - try { - // Now we are idle. - r.activity.performStop(false /*preserveWindow*/); - } catch (Exception e) { - if (!mInstrumentation.onException(r.activity, e)) { - throw new RuntimeException( - "Unable to stop activity " - + r.intent.getComponent().toShortString() - + ": " + e.toString(), e); - } - } - r.setState(ON_STOP); - EventLog.writeEvent(LOG_AM_ON_STOP_CALLED, UserHandle.myUserId(), - r.activity.getComponentName().getClassName(), "sleeping"); + callActivityOnStop(r, true /* saveState */, "sleeping"); } // Make sure any pending writes are now committed. @@ -4325,7 +4305,7 @@ public final class ActivityThread extends ClientTransactionHandler { } } else { if (r.stopped && r.activity.mVisibleFromServer) { - r.activity.performRestart(true /* start */); + r.activity.performRestart(true /* start */, "handleSleeping"); r.setState(ON_START); } } @@ -4345,29 +4325,25 @@ public final class ActivityThread extends ClientTransactionHandler { View.mDebugViewAttributes = debugViewAttributes; // request all activities to relaunch for the changes to take place - requestRelaunchAllActivities(); + relaunchAllActivities(); } } - private void requestRelaunchAllActivities() { + private void relaunchAllActivities() { for (Map.Entry<IBinder, ActivityClientRecord> entry : mActivities.entrySet()) { final Activity activity = entry.getValue().activity; if (!activity.mFinished) { - try { - ActivityManager.getService().requestActivityRelaunch(entry.getKey()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + handleRelaunchActivityLocally(entry.getKey()); } } } private void handleUpdatePackageCompatibilityInfo(UpdateCompatibilityData data) { - LoadedApk apk = peekLoadedApk(data.pkg, false); + LoadedApk apk = peekPackageInfo(data.pkg, false); if (apk != null) { apk.setCompatibilityInfo(data.info); } - apk = peekLoadedApk(data.pkg, true); + apk = peekPackageInfo(data.pkg, true); if (apk != null) { apk.setCompatibilityInfo(data.info); } @@ -4437,7 +4413,7 @@ public final class ActivityThread extends ClientTransactionHandler { checkAndBlockForNetworkAccess(); deliverResults(r, results); if (resumed) { - r.activity.performResume(false); + r.activity.performResume(false, "handleSendResult"); r.activity.mTemporaryPause = false; } } @@ -4445,7 +4421,7 @@ public final class ActivityThread extends ClientTransactionHandler { /** Core implementation of activity destroy call. */ ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing, - int configChanges, boolean getNonConfigInstance) { + int configChanges, boolean getNonConfigInstance, String reason) { ActivityClientRecord r = mActivities.get(token); Class<? extends Activity> activityClass = null; if (localLOGV) Slog.v(TAG, "Performing finish of " + r); @@ -4459,21 +4435,7 @@ public final class ActivityThread extends ClientTransactionHandler { performPauseActivityIfNeeded(r, "destroy"); if (!r.stopped) { - try { - r.activity.performStop(r.mPreserveWindow); - } catch (SuperNotCalledException e) { - throw e; - } catch (Exception e) { - if (!mInstrumentation.onException(r.activity, e)) { - throw new RuntimeException( - "Unable to stop activity " - + safeToComponentShortString(r.intent) - + ": " + e.toString(), e); - } - } - r.setState(ON_STOP); - EventLog.writeEvent(LOG_AM_ON_STOP_CALLED, UserHandle.myUserId(), - r.activity.getComponentName().getClassName(), "destroy"); + callActivityOnStop(r, false /* saveState */, "destroy"); } if (getNonConfigInstance) { try { @@ -4511,6 +4473,12 @@ public final class ActivityThread extends ClientTransactionHandler { r.setState(ON_DESTROY); } mActivities.remove(token); + mRecentDestroyedActivities.add(0, new DestroyedActivityInfo(token.hashCode(), reason)); + + final int recentDestroyedActivitiesSize = mRecentDestroyedActivities.size(); + if (recentDestroyedActivitiesSize > MAX_DESTROYED_ACTIVITIES) { + mRecentDestroyedActivities.remove(recentDestroyedActivitiesSize - 1); + } StrictMode.decrementExpectedActivityCount(activityClass); return r; } @@ -4522,9 +4490,9 @@ public final class ActivityThread extends ClientTransactionHandler { @Override public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges, - boolean getNonConfigInstance) { + boolean getNonConfigInstance, String reason) { ActivityClientRecord r = performDestroyActivity(token, finishing, - configChanges, getNonConfigInstance); + configChanges, getNonConfigInstance, reason); if (r != null) { cleanUpPendingRemoveWindows(r, finishing); WindowManager wm = r.activity.getWindowManager(); @@ -4592,15 +4560,12 @@ public final class ActivityThread extends ClientTransactionHandler { mSomeActivitiesChanged = true; } - /** - * @param preserveWindow Whether the activity should try to reuse the window it created, - * including the decor view after the relaunch. - */ - public final void requestRelaunchActivity(IBinder token, + @Override + public ActivityClientRecord prepareRelaunchActivity(IBinder token, List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents, - int configChanges, boolean notResumed, Configuration config, - Configuration overrideConfig, boolean fromServer, boolean preserveWindow) { + int configChanges, MergedConfiguration config, boolean preserveWindow) { ActivityClientRecord target = null; + boolean scheduleRelaunch = false; synchronized (mResourcesManager) { for (int i=0; i<mRelaunchingActivities.size(); i++) { @@ -4622,57 +4587,31 @@ public final class ActivityThread extends ClientTransactionHandler { r.pendingIntents = pendingNewIntents; } } - - // For each relaunch request, activity manager expects an answer - if (!r.onlyLocalRequest && fromServer) { - try { - ActivityManager.getService().activityRelaunched(token); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } break; } } if (target == null) { - if (DEBUG_ORDER) Slog.d(TAG, "requestRelaunchActivity: target is null, fromServer:" - + fromServer); + if (DEBUG_ORDER) Slog.d(TAG, "requestRelaunchActivity: target is null"); target = new ActivityClientRecord(); target.token = token; target.pendingResults = pendingResults; target.pendingIntents = pendingNewIntents; target.mPreserveWindow = preserveWindow; - if (!fromServer) { - final ActivityClientRecord existing = mActivities.get(token); - if (DEBUG_ORDER) Slog.d(TAG, "requestRelaunchActivity: " + existing); - if (existing != null) { - if (DEBUG_ORDER) Slog.d(TAG, "requestRelaunchActivity: paused= " - + existing.paused);; - target.startsNotResumed = existing.paused; - target.overrideConfig = existing.overrideConfig; - } - target.onlyLocalRequest = true; - } mRelaunchingActivities.add(target); - sendMessage(H.RELAUNCH_ACTIVITY, target); - } - - if (fromServer) { - target.startsNotResumed = notResumed; - target.onlyLocalRequest = false; - } - if (config != null) { - target.createdConfig = config; - } - if (overrideConfig != null) { - target.overrideConfig = overrideConfig; + scheduleRelaunch = true; } + target.createdConfig = config.getGlobalConfiguration(); + target.overrideConfig = config.getOverrideConfiguration(); target.pendingConfigChanges |= configChanges; } + + return scheduleRelaunch ? target : null; } - private void handleRelaunchActivity(ActivityClientRecord tmp) { + @Override + public void handleRelaunchActivity(ActivityClientRecord tmp, + PendingTransactionActions pendingActions) { // If we are getting ready to gc after going to the background, well // we are back active so skip it. unscheduleGcIdler(); @@ -4741,18 +4680,10 @@ public final class ActivityThread extends ClientTransactionHandler { ActivityClientRecord r = mActivities.get(tmp.token); if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handling relaunch of " + r); if (r == null) { - if (!tmp.onlyLocalRequest) { - try { - ActivityManager.getService().activityRelaunched(tmp.token); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } return; } r.activity.mConfigChangeFlags |= configChanges; - r.onlyLocalRequest = tmp.onlyLocalRequest; r.mPreserveWindow = tmp.mPreserveWindow; r.activity.mChangingConfigurations = true; @@ -4769,70 +4700,113 @@ public final class ActivityThread extends ClientTransactionHandler { // preserved by the server, so we want to notify it that we are preparing to replace // everything try { - if (r.mPreserveWindow || r.onlyLocalRequest) { + if (r.mPreserveWindow) { WindowManagerGlobal.getWindowSession().prepareToReplaceWindows( - r.token, !r.onlyLocalRequest); + r.token, true /* childrenOnly */); } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } + handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents, + pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity"); + + if (pendingActions != null) { + // Only report a successful relaunch to WindowManager. + pendingActions.setReportRelaunchToWindowManager(true); + } + } + + /** Performs the activity relaunch locally vs. requesting from system-server. */ + void handleRelaunchActivityLocally(IBinder token) { + if (Looper.myLooper() != getLooper()) { + throw new IllegalStateException("Must be called from main thread"); + } + + final ActivityClientRecord r = mActivities.get(token); + if (r == null) { + return; + } + + final int prevState = r.getLifecycleState(); + + if (prevState < ON_RESUME) { + Log.w(TAG, "Activity needs to be already resumed in other to be relaunched."); + return; + } + + + // Initialize a relaunch request. + final MergedConfiguration mergedConfiguration = new MergedConfiguration( + r.createdConfig != null ? r.createdConfig : mConfiguration, + r.overrideConfig); + final ActivityRelaunchItem activityRelaunchItem = ActivityRelaunchItem.obtain( + null /* pendingResults */, null /* pendingIntents */, 0 /* configChanges */, + mergedConfiguration, r.mPreserveWindow); + // Make sure to match the existing lifecycle state in the end of the transaction. + final ActivityLifecycleItem lifecycleRequest = + TransactionExecutorHelper.getLifecycleRequestForCurrentState(r); + // Schedule the transaction. + final ClientTransaction transaction = ClientTransaction.obtain(this.mAppThread, r.token); + transaction.addCallback(activityRelaunchItem); + transaction.setLifecycleStateRequest(lifecycleRequest); + executeTransaction(transaction); + } + + private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges, + List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents, + PendingTransactionActions pendingActions, boolean startsNotResumed, + Configuration overrideConfig, String reason) { // Need to ensure state is saved. if (!r.paused) { - performPauseActivity(r.token, false, r.isPreHoneycomb(), "handleRelaunchActivity", - null /* pendingActions */); + performPauseActivity(r, false, reason, null /* pendingActions */); } - if (r.state == null && !r.stopped && !r.isPreHoneycomb()) { - callCallActivityOnSaveInstanceState(r); + if (!r.stopped) { + callActivityOnStop(r, true /* saveState */, reason); } - handleDestroyActivity(r.token, false, configChanges, true); + handleDestroyActivity(r.token, false, configChanges, true, reason); r.activity = null; r.window = null; r.hideForNow = false; r.nextIdle = null; // Merge any pending results and pending intents; don't just replace them - if (tmp.pendingResults != null) { + if (pendingResults != null) { if (r.pendingResults == null) { - r.pendingResults = tmp.pendingResults; + r.pendingResults = pendingResults; } else { - r.pendingResults.addAll(tmp.pendingResults); + r.pendingResults.addAll(pendingResults); } } - if (tmp.pendingIntents != null) { + if (pendingIntents != null) { if (r.pendingIntents == null) { - r.pendingIntents = tmp.pendingIntents; + r.pendingIntents = pendingIntents; } else { - r.pendingIntents.addAll(tmp.pendingIntents); + r.pendingIntents.addAll(pendingIntents); } } - r.startsNotResumed = tmp.startsNotResumed; - r.overrideConfig = tmp.overrideConfig; + r.startsNotResumed = startsNotResumed; + r.overrideConfig = overrideConfig; - // TODO(lifecycler): Move relaunch to lifecycler. - PendingTransactionActions pendingActions = new PendingTransactionActions(); handleLaunchActivity(r, pendingActions); - handleStartActivity(r, pendingActions); - handleResumeActivity(r.token, false /* clearHide */, r.isForward, "relaunch"); - if (r.startsNotResumed) { - performPauseActivity(r, false /* finished */, r.isPreHoneycomb(), "relaunch", - pendingActions); - } + } - if (!tmp.onlyLocalRequest) { - try { - ActivityManager.getService().activityRelaunched(r.token); - if (r.window != null) { - r.window.reportActivityRelaunched(); - } - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + @Override + public void reportRelaunch(IBinder token, PendingTransactionActions pendingActions) { + try { + ActivityManager.getService().activityRelaunched(token); + final ActivityClientRecord r = mActivities.get(token); + if (pendingActions.shouldReportRelaunchToWindowManager() && r != null + && r.window != null) { + r.window.reportActivityRelaunched(); } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } } - private void callCallActivityOnSaveInstanceState(ActivityClientRecord r) { + private void callActivityOnSaveInstanceState(ActivityClientRecord r) { r.state = new Bundle(); r.state.setAllowFds(false); if (r.isPersistable()) { @@ -4861,7 +4835,7 @@ public final class ActivityThread extends ClientTransactionHandler { if (a != null) { Configuration thisConfig = applyConfigCompatMainThread( mCurDefaultDisplayDpi, newConfig, - ar.loadedApk.getCompatibilityInfo()); + ar.packageInfo.getCompatibilityInfo()); if (!ar.activity.mFinished && (allActivities || !ar.paused)) { // If the activity is currently resumed, its configuration // needs to change right now. @@ -5114,6 +5088,8 @@ public final class ActivityThread extends ClientTransactionHandler { // as that method uses the same check on the activity config override as well. final boolean equivalent = config != null && mConfiguration != null && (0 == mConfiguration.diffPublicOnly(config)); + final Theme systemTheme = getSystemContext().getTheme(); + final Theme systemUiTheme = getSystemUiContext().getTheme(); synchronized (mResourcesManager) { if (mPendingConfiguration != null) { @@ -5146,12 +5122,10 @@ public final class ActivityThread extends ClientTransactionHandler { configDiff = mConfiguration.updateFrom(config); config = applyCompatConfiguration(mCurDefaultDisplayDpi); - final Theme systemTheme = getSystemContext().getTheme(); if ((systemTheme.getChangingConfigurations() & configDiff) != 0) { systemTheme.rebase(); } - final Theme systemUiTheme = getSystemUiContext().getTheme(); if ((systemUiTheme.getChangingConfigurations() & configDiff) != 0) { systemUiTheme.rebase(); } @@ -5218,7 +5192,7 @@ public final class ActivityThread extends ClientTransactionHandler { newConfig.assetsSeq = (mConfiguration != null ? mConfiguration.assetsSeq : 0) + 1; handleConfigurationChanged(newConfig, null); - requestRelaunchAllActivities(); + relaunchAllActivities(); } static void freeTextLayoutCachesIfNeeded(int configDiff) { @@ -5347,7 +5321,7 @@ public final class ActivityThread extends ClientTransactionHandler { } final void handleDispatchPackageBroadcast(int cmd, String[] packages) { - boolean hasLoadedApk = false; + boolean hasPkgInfo = false; switch (cmd) { case ApplicationThreadConstants.PACKAGE_REMOVED: case ApplicationThreadConstants.PACKAGE_REMOVED_DONT_KILL: @@ -5358,14 +5332,14 @@ public final class ActivityThread extends ClientTransactionHandler { } synchronized (mResourcesManager) { for (int i = packages.length - 1; i >= 0; i--) { - if (!hasLoadedApk) { + if (!hasPkgInfo) { WeakReference<LoadedApk> ref = mPackages.get(packages[i]); if (ref != null && ref.get() != null) { - hasLoadedApk = true; + hasPkgInfo = true; } else { ref = mResourcePackages.get(packages[i]); if (ref != null && ref.get() != null) { - hasLoadedApk = true; + hasPkgInfo = true; } } } @@ -5385,21 +5359,21 @@ public final class ActivityThread extends ClientTransactionHandler { synchronized (mResourcesManager) { for (int i = packages.length - 1; i >= 0; i--) { WeakReference<LoadedApk> ref = mPackages.get(packages[i]); - LoadedApk loadedApk = ref != null ? ref.get() : null; - if (loadedApk != null) { - hasLoadedApk = true; + LoadedApk pkgInfo = ref != null ? ref.get() : null; + if (pkgInfo != null) { + hasPkgInfo = true; } else { ref = mResourcePackages.get(packages[i]); - loadedApk = ref != null ? ref.get() : null; - if (loadedApk != null) { - hasLoadedApk = true; + pkgInfo = ref != null ? ref.get() : null; + if (pkgInfo != null) { + hasPkgInfo = true; } } // If the package is being replaced, yet it still has a valid // LoadedApk object, the package was updated with _DONT_KILL. // Adjust it's internal references to the application info and // resources. - if (loadedApk != null) { + if (pkgInfo != null) { try { final String packageName = packages[i]; final ApplicationInfo aInfo = @@ -5413,13 +5387,13 @@ public final class ActivityThread extends ClientTransactionHandler { if (ar.activityInfo.applicationInfo.packageName .equals(packageName)) { ar.activityInfo.applicationInfo = aInfo; - ar.loadedApk = loadedApk; + ar.packageInfo = pkgInfo; } } } final List<String> oldPaths = sPackageManager.getPreviousCodePaths(packageName); - loadedApk.updateApplicationInfo(aInfo, oldPaths); + pkgInfo.updateApplicationInfo(aInfo, oldPaths); } catch (RemoteException e) { } } @@ -5428,7 +5402,7 @@ public final class ActivityThread extends ClientTransactionHandler { break; } } - ApplicationPackageManager.handlePackageBroadcast(cmd, packages, hasLoadedApk); + ApplicationPackageManager.handlePackageBroadcast(cmd, packages, hasPkgInfo); } final void handleLowMemory() { @@ -5610,6 +5584,13 @@ public final class ActivityThread extends ClientTransactionHandler { Message.updateCheckRecycle(data.appInfo.targetSdkVersion); + // Prior to P, internal calls to decode Bitmaps used BitmapFactory, + // which may scale up to account for density. In P, we switched to + // ImageDecoder, which skips the upscale to save memory. ImageDecoder + // needs to still scale up in older apps, in case they rely on the + // size of the Bitmap without considering its density. + ImageDecoder.sApiLevel = data.appInfo.targetSdkVersion; + /* * Before spawning a new process, reset the time zone to be the system time zone. * This needs to be done because the system time zone could have changed after the @@ -5636,10 +5617,10 @@ public final class ActivityThread extends ClientTransactionHandler { applyCompatConfiguration(mCurDefaultDisplayDpi); } - data.loadedApk = getLoadedApkNoCheck(data.appInfo, data.compatInfo); + data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo); if (agent != null) { - handleAttachAgent(agent, data.loadedApk); + handleAttachAgent(agent, data.info); } /** @@ -5684,7 +5665,7 @@ public final class ActivityThread extends ClientTransactionHandler { // XXX should have option to change the port. Debug.changeDebugPort(8100); if (data.debugMode == ApplicationThreadConstants.DEBUG_WAIT) { - Slog.w(TAG, "Application " + data.loadedApk.getPackageName() + Slog.w(TAG, "Application " + data.info.getPackageName() + " is waiting for the debugger on port 8100..."); IActivityManager mgr = ActivityManager.getService(); @@ -5703,7 +5684,7 @@ public final class ActivityThread extends ClientTransactionHandler { } } else { - Slog.w(TAG, "Application " + data.loadedApk.getPackageName() + Slog.w(TAG, "Application " + data.info.getPackageName() + " can be debugged on port 8100..."); } } @@ -5711,6 +5692,7 @@ public final class ActivityThread extends ClientTransactionHandler { // Allow application-generated systrace messages if we're debuggable. boolean isAppDebuggable = (data.appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; Trace.setAppTracingAllowed(isAppDebuggable); + ThreadedRenderer.setDebuggingEnabled(isAppDebuggable || Build.IS_DEBUGGABLE); if (isAppDebuggable && data.enableBinderTracking) { Binder.enableTracing(); } @@ -5751,14 +5733,14 @@ public final class ActivityThread extends ClientTransactionHandler { mInstrumentationAppDir = ii.sourceDir; mInstrumentationSplitAppDirs = ii.splitSourceDirs; mInstrumentationLibDir = getInstrumentationLibrary(data.appInfo, ii); - mInstrumentedAppDir = data.loadedApk.getAppDir(); - mInstrumentedSplitAppDirs = data.loadedApk.getSplitAppDirs(); - mInstrumentedLibDir = data.loadedApk.getLibDir(); + mInstrumentedAppDir = data.info.getAppDir(); + mInstrumentedSplitAppDirs = data.info.getSplitAppDirs(); + mInstrumentedLibDir = data.info.getLibDir(); } else { ii = null; } - final ContextImpl appContext = ContextImpl.createAppContext(this, data.loadedApk); + final ContextImpl appContext = ContextImpl.createAppContext(this, data.info); updateLocaleListFromAppContext(appContext, mResourcesManager.getConfiguration().getLocales()); @@ -5769,6 +5751,8 @@ public final class ActivityThread extends ClientTransactionHandler { } finally { StrictMode.setThreadPolicyMask(oldMask); } + } else { + ThreadedRenderer.setIsolatedProcess(true); } // If we use profiles, setup the dex reporter to notify package manager @@ -5802,9 +5786,9 @@ public final class ActivityThread extends ClientTransactionHandler { } ii.copyTo(instrApp); instrApp.initForUser(UserHandle.myUserId()); - final LoadedApk loadedApk = getLoadedApk(instrApp, data.compatInfo, + final LoadedApk pi = getPackageInfo(instrApp, data.compatInfo, appContext.getClassLoader(), false, true, false); - final ContextImpl instrContext = ContextImpl.createAppContext(this, loadedApk); + final ContextImpl instrContext = ContextImpl.createAppContext(this, pi); try { final ClassLoader cl = instrContext.getClassLoader(); @@ -5849,7 +5833,11 @@ public final class ActivityThread extends ClientTransactionHandler { try { // If the app is being launched for full backup or restore, bring it up in // a restricted environment with the base application class. - app = data.loadedApk.makeApplication(data.restrictedBackupMode, null); + app = data.info.makeApplication(data.restrictedBackupMode, null); + + // Propagate autofill compat state + app.setAutofillCompatibilityEnabled(data.autofillCompatibilityEnabled); + mInitialApplication = app; // don't bring up providers in restricted mode; they may depend on the @@ -5893,21 +5881,23 @@ public final class ActivityThread extends ClientTransactionHandler { // Preload fonts resources FontsContract.setApplicationContextForResources(appContext); - try { - final ApplicationInfo info = - getPackageManager().getApplicationInfo( - data.appInfo.packageName, - PackageManager.GET_META_DATA /*flags*/, - UserHandle.myUserId()); - if (info.metaData != null) { - final int preloadedFontsResource = info.metaData.getInt( - ApplicationInfo.METADATA_PRELOADED_FONTS, 0); - if (preloadedFontsResource != 0) { - data.loadedApk.getResources().preloadFonts(preloadedFontsResource); + if (!Process.isIsolated()) { + try { + final ApplicationInfo info = + getPackageManager().getApplicationInfo( + data.appInfo.packageName, + PackageManager.GET_META_DATA /*flags*/, + UserHandle.myUserId()); + if (info.metaData != null) { + final int preloadedFontsResource = info.metaData.getInt( + ApplicationInfo.METADATA_PRELOADED_FONTS, 0); + if (preloadedFontsResource != 0) { + data.info.getResources().preloadFonts(preloadedFontsResource); + } } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); } } @@ -6361,12 +6351,12 @@ public final class ActivityThread extends ClientTransactionHandler { try { final java.lang.ClassLoader cl = c.getClassLoader(); - LoadedApk loadedApk = peekLoadedApk(ai.packageName, true); - if (loadedApk == null) { + LoadedApk packageInfo = peekPackageInfo(ai.packageName, true); + if (packageInfo == null) { // System startup case. - loadedApk = getSystemContext().mLoadedApk; + packageInfo = getSystemContext().mPackageInfo; } - localProvider = loadedApk.getAppFactory() + localProvider = packageInfo.getAppFactory() .instantiateProvider(cl, info.name); provider = localProvider.getIContentProvider(); if (provider == null) { @@ -6515,8 +6505,8 @@ public final class ActivityThread extends ClientTransactionHandler { mInstrumentation = new Instrumentation(); mInstrumentation.basicInit(this); ContextImpl context = ContextImpl.createAppContext( - this, getSystemContext().mLoadedApk); - mInitialApplication = context.mLoadedApk.makeApplication(true, null); + this, getSystemContext().mPackageInfo); + mInitialApplication = context.mPackageInfo.makeApplication(true, null); mInitialApplication.onCreate(); } catch (Exception e) { throw new RuntimeException( diff --git a/android/app/ActivityView.java b/android/app/ActivityView.java index 5d0143a5..7032a2fe 100644 --- a/android/app/ActivityView.java +++ b/android/app/ActivityView.java @@ -27,6 +27,7 @@ import android.os.RemoteException; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; +import android.view.IWindowManager; import android.view.InputDevice; import android.view.InputEvent; import android.view.MotionEvent; @@ -308,8 +309,14 @@ public class ActivityView extends ViewGroup { return; } - mInputForwarder = InputManager.getInstance().createInputForwarder( - mVirtualDisplay.getDisplay().getDisplayId()); + final int displayId = mVirtualDisplay.getDisplay().getDisplayId(); + final IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); + try { + wm.dontOverrideDisplayInfo(displayId); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + mInputForwarder = InputManager.getInstance().createInputForwarder(displayId); mTaskStackListener = new TaskBackgroundChangeListener(); try { mActivityManager.registerTaskStackListener(mTaskStackListener); diff --git a/android/app/AlarmManager.java b/android/app/AlarmManager.java index 382719b4..124f9338 100644 --- a/android/app/AlarmManager.java +++ b/android/app/AlarmManager.java @@ -178,6 +178,7 @@ public class AlarmManager { public static final int FLAG_IDLE_UNTIL = 1<<4; private final IAlarmManager mService; + private final Context mContext; private final String mPackageName; private final boolean mAlwaysExact; private final int mTargetSdkVersion; @@ -265,6 +266,7 @@ public class AlarmManager { AlarmManager(IAlarmManager service, Context ctx) { mService = service; + mContext = ctx; mPackageName = ctx.getPackageName(); mTargetSdkVersion = ctx.getApplicationInfo().targetSdkVersion; mAlwaysExact = (mTargetSdkVersion < Build.VERSION_CODES.KITKAT); @@ -1028,7 +1030,7 @@ public class AlarmManager { * @see #ACTION_NEXT_ALARM_CLOCK_CHANGED */ public AlarmClockInfo getNextAlarmClock() { - return getNextAlarmClock(UserHandle.myUserId()); + return getNextAlarmClock(mContext.getUserId()); } /** diff --git a/android/app/AppComponentFactory.java b/android/app/AppComponentFactory.java index 4df73799..cfaeec90 100644 --- a/android/app/AppComponentFactory.java +++ b/android/app/AppComponentFactory.java @@ -36,6 +36,10 @@ public class AppComponentFactory { * Allows application to override the creation of the application object. This can be used to * perform things such as dependency injection or class loader changes to these * classes. + * <p> + * This method is only intended to provide a hook for instantiation. It does not provide + * earlier access to the Application object. The returned object will not be initialized + * as a Context yet and should not be used to interact with other android APIs. * * @param cl The default classloader to use for instantiation. * @param className The class to be instantiated. @@ -50,6 +54,10 @@ public class AppComponentFactory { * Allows application to override the creation of activities. This can be used to * perform things such as dependency injection or class loader changes to these * classes. + * <p> + * This method is only intended to provide a hook for instantiation. It does not provide + * earlier access to the Activity object. The returned object will not be initialized + * as a Context yet and should not be used to interact with other android APIs. * * @param cl The default classloader to use for instantiation. * @param className The class to be instantiated. @@ -80,6 +88,10 @@ public class AppComponentFactory { * Allows application to override the creation of services. This can be used to * perform things such as dependency injection or class loader changes to these * classes. + * <p> + * This method is only intended to provide a hook for instantiation. It does not provide + * earlier access to the Service object. The returned object will not be initialized + * as a Context yet and should not be used to interact with other android APIs. * * @param cl The default classloader to use for instantiation. * @param className The class to be instantiated. @@ -95,6 +107,10 @@ public class AppComponentFactory { * Allows application to override the creation of providers. This can be used to * perform things such as dependency injection or class loader changes to these * classes. + * <p> + * This method is only intended to provide a hook for instantiation. It does not provide + * earlier access to the ContentProvider object. The returned object will not be initialized + * with a Context yet and should not be used to interact with other android APIs. * * @param cl The default classloader to use for instantiation. * @param className The class to be instantiated. @@ -108,5 +124,5 @@ public class AppComponentFactory { /** * @hide */ - public static AppComponentFactory DEFAULT = new AppComponentFactory(); + public static final AppComponentFactory DEFAULT = new AppComponentFactory(); } diff --git a/android/app/AppOpsManager.java b/android/app/AppOpsManager.java index e923fb21..ab0001ca 100644 --- a/android/app/AppOpsManager.java +++ b/android/app/AppOpsManager.java @@ -17,6 +17,7 @@ package android.app; import android.Manifest; +import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; @@ -30,12 +31,13 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.Process; import android.os.RemoteException; -import android.os.UserHandle; import android.os.UserManager; import android.util.ArrayMap; +import com.android.internal.app.IAppOpsActiveCallback; import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsService; +import com.android.internal.util.Preconditions; import java.util.ArrayList; import java.util.Arrays; @@ -74,8 +76,9 @@ public class AppOpsManager { final Context mContext; final IAppOpsService mService; - final ArrayMap<OnOpChangedListener, IAppOpsCallback> mModeWatchers - = new ArrayMap<OnOpChangedListener, IAppOpsCallback>(); + final ArrayMap<OnOpChangedListener, IAppOpsCallback> mModeWatchers = new ArrayMap<>(); + final ArrayMap<OnOpActiveChangedListener, IAppOpsActiveCallback> mActiveWatchers = + new ArrayMap<>(); static IBinder sToken; @@ -164,12 +167,14 @@ public class AppOpsManager { /** @hide */ public static final int OP_WRITE_SETTINGS = 23; /** @hide Required to draw on top of other apps. */ + @TestApi public static final int OP_SYSTEM_ALERT_WINDOW = 24; /** @hide */ public static final int OP_ACCESS_NOTIFICATIONS = 25; /** @hide */ public static final int OP_CAMERA = 26; /** @hide */ + @TestApi public static final int OP_RECORD_AUDIO = 27; /** @hide */ public static final int OP_PLAY_AUDIO = 28; @@ -265,8 +270,12 @@ public class AppOpsManager { public static final int OP_BIND_ACCESSIBILITY_SERVICE = 73; /** @hide Continue handover of a call from another app */ public static final int OP_ACCEPT_HANDOVER = 74; + /** @hide Create and Manage IPsec Tunnels */ + public static final int OP_MANAGE_IPSEC_TUNNELS = 75; + /** @hide Any app start foreground service. */ + public static final int OP_START_FOREGROUND = 76; /** @hide */ - public static final int _NUM_OP = 75; + public static final int _NUM_OP = 77; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -494,13 +503,20 @@ public class AppOpsManager { public static final String OPSTR_RUN_ANY_IN_BACKGROUND = "android:run_any_in_background"; /** @hide */ @SystemApi @TestApi - public static final String OPSTR_CHANGE_WIFI_STATE = "change_wifi_state"; + public static final String OPSTR_CHANGE_WIFI_STATE = "android:change_wifi_state"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_REQUEST_DELETE_PACKAGES = "android:request_delete_packages"; /** @hide */ @SystemApi @TestApi - public static final String OPSTR_REQUEST_DELETE_PACKAGES = "request_delete_packages"; + public static final String OPSTR_BIND_ACCESSIBILITY_SERVICE = + "android:bind_accessibility_service"; /** @hide */ @SystemApi @TestApi - public static final String OPSTR_BIND_ACCESSIBILITY_SERVICE = "bind_accessibility_service"; + public static final String OPSTR_MANAGE_IPSEC_TUNNELS = "android:manage_ipsec_tunnels"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_START_FOREGROUND = "android:start_foreground"; // Warning: If an permission is added here it also has to be added to // com.android.packageinstaller.permission.utils.EventLogger @@ -543,13 +559,13 @@ public class AppOpsManager { OP_CAMERA, // Body sensors OP_BODY_SENSORS, - OP_REQUEST_DELETE_PACKAGES, // APPOP PERMISSIONS OP_ACCESS_NOTIFICATIONS, OP_SYSTEM_ALERT_WINDOW, OP_WRITE_SETTINGS, OP_REQUEST_INSTALL_PACKAGES, + OP_START_FOREGROUND, }; /** @@ -636,6 +652,8 @@ public class AppOpsManager { OP_REQUEST_DELETE_PACKAGES, OP_BIND_ACCESSIBILITY_SERVICE, OP_ACCEPT_HANDOVER, + OP_MANAGE_IPSEC_TUNNELS, + OP_START_FOREGROUND, }; /** @@ -717,6 +735,8 @@ public class AppOpsManager { OPSTR_REQUEST_DELETE_PACKAGES, OPSTR_BIND_ACCESSIBILITY_SERVICE, OPSTR_ACCEPT_HANDOVER, + OPSTR_MANAGE_IPSEC_TUNNELS, + OPSTR_START_FOREGROUND, }; /** @@ -799,6 +819,8 @@ public class AppOpsManager { "REQUEST_DELETE_PACKAGES", "BIND_ACCESSIBILITY_SERVICE", "ACCEPT_HANDOVER", + "MANAGE_IPSEC_TUNNELS", + "START_FOREGROUND", }; /** @@ -881,6 +903,8 @@ public class AppOpsManager { Manifest.permission.REQUEST_DELETE_PACKAGES, Manifest.permission.BIND_ACCESSIBILITY_SERVICE, Manifest.permission.ACCEPT_HANDOVER, + null, // no permission for OP_MANAGE_IPSEC_TUNNELS + Manifest.permission.FOREGROUND_SERVICE, }; /** @@ -964,6 +988,8 @@ public class AppOpsManager { null, // REQUEST_DELETE_PACKAGES null, // OP_BIND_ACCESSIBILITY_SERVICE null, // ACCEPT_HANDOVER + null, // MANAGE_IPSEC_TUNNELS + null, // START_FOREGROUND }; /** @@ -1046,6 +1072,8 @@ public class AppOpsManager { false, // OP_REQUEST_DELETE_PACKAGES false, // OP_BIND_ACCESSIBILITY_SERVICE false, // ACCEPT_HANDOVER + false, // MANAGE_IPSEC_HANDOVERS + false, // START_FOREGROUND }; /** @@ -1121,12 +1149,14 @@ public class AppOpsManager { AppOpsManager.MODE_DEFAULT, // OP_REQUEST_INSTALL_PACKAGES AppOpsManager.MODE_ALLOWED, // OP_PICTURE_IN_PICTURE AppOpsManager.MODE_DEFAULT, // OP_INSTANT_APP_START_FOREGROUND - AppOpsManager.MODE_ALLOWED, // ANSWER_PHONE_CALLS + AppOpsManager.MODE_ALLOWED, // ANSWER_PHONE_CALLS AppOpsManager.MODE_ALLOWED, // OP_RUN_ANY_IN_BACKGROUND AppOpsManager.MODE_ALLOWED, // OP_CHANGE_WIFI_STATE AppOpsManager.MODE_ALLOWED, // REQUEST_DELETE_PACKAGES AppOpsManager.MODE_ALLOWED, // OP_BIND_ACCESSIBILITY_SERVICE AppOpsManager.MODE_ALLOWED, // ACCEPT_HANDOVER + AppOpsManager.MODE_ERRORED, // MANAGE_IPSEC_TUNNELS + AppOpsManager.MODE_ALLOWED, // OP_START_FOREGROUND }; /** @@ -1212,6 +1242,8 @@ public class AppOpsManager { false, // OP_REQUEST_DELETE_PACKAGES false, // OP_BIND_ACCESSIBILITY_SERVICE false, // ACCEPT_HANDOVER + false, // MANAGE_IPSEC_TUNNELS + false, // START_FOREGROUND }; /** @@ -1533,6 +1565,24 @@ public class AppOpsManager { } /** + * Callback for notification of changes to operation active state. + * + * @hide + */ + @TestApi + public interface OnOpActiveChangedListener { + /** + * Called when the active state of an app op changes. + * + * @param code The op code. + * @param uid The UID performing the operation. + * @param packageName The package performing the operation. + * @param active Whether the operation became active or inactive. + */ + void onOpActiveChanged(int code, int uid, String packageName, boolean active); + } + + /** * Callback for notification of changes to operation state. * This allows you to see the raw op codes instead of strings. * @hide @@ -1587,6 +1637,7 @@ public class AppOpsManager { * @param mode The app op mode to set. * @hide */ + @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) public void setUidMode(int code, int uid, int mode) { try { mService.setUidMode(code, uid, mode); @@ -1606,7 +1657,7 @@ public class AppOpsManager { * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS) + @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) public void setUidMode(String appOp, int uid, int mode) { try { mService.setUidMode(AppOpsManager.strOpToOp(appOp), uid, mode); @@ -1638,6 +1689,7 @@ public class AppOpsManager { /** @hide */ @TestApi + @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) public void setMode(int code, int uid, String packageName, int mode) { try { mService.setMode(code, uid, packageName, mode); @@ -1647,6 +1699,27 @@ public class AppOpsManager { } /** + * Change the operating mode for the given op in the given app package. You must pass + * in both the uid and name of the application whose mode is being modified; if these + * do not match, the modification will not be applied. + * + * @param op The operation to modify. One of the OPSTR_* constants. + * @param uid The user id of the application whose mode will be changed. + * @param packageName The name of the application package name whose mode will + * be changed. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) + public void setMode(String op, int uid, String packageName, int mode) { + try { + mService.setMode(strOpToOp(op), uid, packageName, mode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Set a non-persisted restriction on an audio operation at a stream-level. * Restrictions are temporary additional constraints imposed on top of the persisted rules * defined by {@link #setMode}. @@ -1657,6 +1730,7 @@ public class AppOpsManager { * @param exceptionPackages Optional list of packages to exclude from the restriction. * @hide */ + @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) public void setRestriction(int code, @AttributeUsage int usage, int mode, String[] exceptionPackages) { try { @@ -1668,9 +1742,10 @@ public class AppOpsManager { } /** @hide */ + @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) public void resetAllModes() { try { - mService.resetAllModes(UserHandle.myUserId(), null); + mService.resetAllModes(mContext.getUserId(), null); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1696,6 +1771,8 @@ public class AppOpsManager { /** * Monitor for changes to the operating mode for the given op in the given app package. + * You can watch op changes only for your UID. + * * @param op The operation to monitor, one of OPSTR_*. * @param packageName The name of the application to monitor. * @param callback Where to report changes. @@ -1707,11 +1784,16 @@ public class AppOpsManager { /** * Monitor for changes to the operating mode for the given op in the given app package. + * + * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission + * you can watch changes only for your UID. + * * @param op The operation to monitor, one of OP_*. * @param packageName The name of the application to monitor. * @param callback Where to report changes. * @hide */ + @RequiresPermission(value=android.Manifest.permission.WATCH_APPOPS, conditional=true) public void startWatchingMode(int op, String packageName, final OnOpChangedListener callback) { synchronized (mModeWatchers) { IAppOpsCallback cb = mModeWatchers.get(callback); @@ -1753,6 +1835,80 @@ public class AppOpsManager { } } + /** + * Start watching for changes to the active state of app ops. An app op may be + * long running and it has a clear start and stop delimiters. If an op is being + * started or stopped by any package you will get a callback. To change the + * watched ops for a registered callback you need to unregister and register it + * again. + * + * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission + * you can watch changes only for your UID. + * + * @param ops The ops to watch. + * @param callback Where to report changes. + * + * @see #isOperationActive(int, int, String) + * @see #stopWatchingActive(OnOpActiveChangedListener) + * @see #startOp(int, int, String) + * @see #finishOp(int, int, String) + * + * @hide + */ + @TestApi + // TODO: Uncomment below annotation once b/73559440 is fixed + // @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true) + public void startWatchingActive(@NonNull int[] ops, + @NonNull OnOpActiveChangedListener callback) { + Preconditions.checkNotNull(ops, "ops cannot be null"); + Preconditions.checkNotNull(callback, "callback cannot be null"); + IAppOpsActiveCallback cb; + synchronized (mActiveWatchers) { + cb = mActiveWatchers.get(callback); + if (cb != null) { + return; + } + cb = new IAppOpsActiveCallback.Stub() { + @Override + public void opActiveChanged(int op, int uid, String packageName, boolean active) { + callback.onOpActiveChanged(op, uid, packageName, active); + } + }; + mActiveWatchers.put(callback, cb); + } + try { + mService.startWatchingActive(ops, cb); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Stop watching for changes to the active state of an app op. An app op may be + * long running and it has a clear start and stop delimiters. Unregistering a + * non-registered callback has no effect. + * + * @see #isOperationActive#(int, int, String) + * @see #startWatchingActive(int[], OnOpActiveChangedListener) + * @see #startOp(int, int, String) + * @see #finishOp(int, int, String) + * + * @hide + */ + @TestApi + public void stopWatchingActive(@NonNull OnOpActiveChangedListener callback) { + synchronized (mActiveWatchers) { + final IAppOpsActiveCallback cb = mActiveWatchers.get(callback); + if (cb != null) { + try { + mService.stopWatchingActive(cb); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + } + private String buildSecurityExceptionMsg(int op, int uid, String packageName) { return packageName + " from uid " + uid + " not allowed to perform " + sOpNames[op]; } @@ -1991,15 +2147,11 @@ public class AppOpsManager { * @hide */ public int noteOp(int op, int uid, String packageName) { - try { - int mode = mService.noteOperation(op, uid, packageName); - if (mode == MODE_ERRORED) { - throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName)); - } - return mode; - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + final int mode = noteOpNoThrow(op, uid, packageName); + if (mode == MODE_ERRORED) { + throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName)); } + return mode; } /** @@ -2078,6 +2230,11 @@ public class AppOpsManager { } } + /** @hide */ + public int startOp(int op) { + return startOp(op, Process.myUid(), mContext.getOpPackageName()); + } + /** * Report that an application has started executing a long-running operation. Note that you * must pass in both the uid and name of the application to be checked; this function will @@ -2086,6 +2243,7 @@ public class AppOpsManager { * the current time and the operation will be marked as "running". In this case you must * later call {@link #finishOp(int, int, String)} to report when the application is no * longer performing the operation. + * * @param op The operation to start. One of the OP_* constants. * @param uid The user id of the application attempting to perform the operation. * @param packageName The name of the application attempting to perform the operation. @@ -2096,15 +2254,34 @@ public class AppOpsManager { * @hide */ public int startOp(int op, int uid, String packageName) { - try { - int mode = mService.startOperation(getToken(mService), op, uid, packageName); - if (mode == MODE_ERRORED) { - throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName)); - } - return mode; - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + return startOp(op, uid, packageName, false); + } + + /** + * Report that an application has started executing a long-running operation. Similar + * to {@link #startOp(String, int, String) except that if the mode is {@link #MODE_DEFAULT} + * the operation should succeed since the caller has performed its standard permission + * checks which passed and would perform the protected operation for this mode. + * + * @param op The operation to start. One of the OP_* constants. + * @param uid The user id of the application attempting to perform the operation. + * @param packageName The name of the application attempting to perform the operation. + * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or + * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without + * causing the app to crash). + * @param startIfModeDefault Whether to start if mode is {@link #MODE_DEFAULT}. + * + * @throws SecurityException If the app has been configured to crash on this op or + * the package is not in the passed in UID. + * + * @hide + */ + public int startOp(int op, int uid, String packageName, boolean startIfModeDefault) { + final int mode = startOpNoThrow(op, uid, packageName, startIfModeDefault); + if (mode == MODE_ERRORED) { + throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName)); } + return mode; } /** @@ -2113,18 +2290,32 @@ public class AppOpsManager { * @hide */ public int startOpNoThrow(int op, int uid, String packageName) { + return startOpNoThrow(op, uid, packageName, false); + } + + /** + * Like {@link #startOp(int, int, String, boolean)} but instead of throwing a + * {@link SecurityException} it returns {@link #MODE_ERRORED}. + * + * @param op The operation to start. One of the OP_* constants. + * @param uid The user id of the application attempting to perform the operation. + * @param packageName The name of the application attempting to perform the operation. + * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or + * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without + * causing the app to crash). + * @param startIfModeDefault Whether to start if mode is {@link #MODE_DEFAULT}. + * + * @hide + */ + public int startOpNoThrow(int op, int uid, String packageName, boolean startIfModeDefault) { try { - return mService.startOperation(getToken(mService), op, uid, packageName); + return mService.startOperation(getToken(mService), op, uid, packageName, + startIfModeDefault); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } - /** @hide */ - public int startOp(int op) { - return startOp(op, Process.myUid(), mContext.getOpPackageName()); - } - /** * Report that an application is no longer performing an operation that had previously * been started with {@link #startOp(int, int, String)}. There is no validation of input @@ -2145,7 +2336,21 @@ public class AppOpsManager { finishOp(op, Process.myUid(), mContext.getOpPackageName()); } - /** @hide */ + /** + * Checks whether the given op for a UID and package is active. + * + * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission + * you can query only for your UID. + * + * @see #startWatchingActive(int[], OnOpActiveChangedListener) + * @see #stopWatchingMode(OnOpChangedListener) + * @see #finishOp(int) + * @see #startOp(int) + * + * @hide */ + @TestApi + // TODO: Uncomment below annotation once b/73559440 is fixed + // @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true) public boolean isOperationActive(int code, int uid, String packageName) { try { return mService.isOperationActive(code, uid, packageName); diff --git a/android/app/Application.java b/android/app/Application.java index 5822f5c8..4531f53b 100644 --- a/android/app/Application.java +++ b/android/app/Application.java @@ -16,8 +16,6 @@ package android.app; -import java.util.ArrayList; - import android.annotation.CallSuper; import android.content.ComponentCallbacks; import android.content.ComponentCallbacks2; @@ -26,6 +24,10 @@ import android.content.ContextWrapper; import android.content.Intent; import android.content.res.Configuration; import android.os.Bundle; +import android.util.Log; +import android.view.autofill.AutofillManager; + +import java.util.ArrayList; /** * Base class for maintaining global application state. You can provide your own @@ -45,6 +47,7 @@ import android.os.Bundle; * </p> */ public class Application extends ContextWrapper implements ComponentCallbacks2 { + private static final String TAG = "Application"; private ArrayList<ComponentCallbacks> mComponentCallbacks = new ArrayList<ComponentCallbacks>(); private ArrayList<ActivityLifecycleCallbacks> mActivityLifecycleCallbacks = @@ -86,11 +89,21 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 { /** * Called when the application is starting, before any activity, service, * or receiver objects (excluding content providers) have been created. - * Implementations should be as quick as possible (for example using + * + * <p>Implementations should be as quick as possible (for example using * lazy initialization of state) since the time spent in this function * directly impacts the performance of starting the first activity, - * service, or receiver in a process. - * If you override this method, be sure to call super.onCreate(). + * service, or receiver in a process.</p> + * + * <p>If you override this method, be sure to call {@code super.onCreate()}.</p> + * + * <p class="note">Be aware that direct boot may also affect callback order on + * Android {@link android.os.Build.VERSION_CODES#N} and later devices. + * Until the user unlocks the device, only direct boot aware components are + * allowed to run. You should consider that all direct boot unaware + * components, including such {@link android.content.ContentProvider}, are + * disabled until user unlock happens, especially when component callback + * order matters.</p> */ @CallSuper public void onCreate() { @@ -180,6 +193,16 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 { } } + /** + * Returns the name of the current process. A package's default process name + * is the same as its package name. Non-default processes will look like + * "$PACKAGE_NAME:$NAME", where $NAME corresponds to an android:process + * attribute within AndroidManifest.xml. + */ + public static String getProcessName() { + return ActivityThread.currentProcessName(); + } + // ------------------ Internal API ------------------ /** @@ -187,7 +210,7 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 { */ /* package */ final void attach(Context context) { attachBaseContext(context); - mLoadedApk = ContextImpl.getImpl(context).mLoadedApk; + mLoadedApk = ContextImpl.getImpl(context).mPackageInfo; } /* package */ void dispatchActivityCreated(Activity activity, Bundle savedInstanceState) { @@ -289,4 +312,47 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 { } } } -}
\ No newline at end of file + + /** @hide */ + @Override + public AutofillManager.AutofillClient getAutofillClient() { + final AutofillManager.AutofillClient client = super.getAutofillClient(); + if (client != null) { + return client; + } + if (android.view.autofill.Helper.sVerbose) { + Log.v(TAG, "getAutofillClient(): null on super, trying to find activity thread"); + } + // Okay, ppl use the application context when they should not. This breaks + // autofill among other things. We pick the focused activity since autofill + // interacts only with the currently focused activity and we need the fill + // client only if a call comes from the focused activity. Sigh... + final ActivityThread activityThread = ActivityThread.currentActivityThread(); + if (activityThread == null) { + return null; + } + final int activityCount = activityThread.mActivities.size(); + for (int i = 0; i < activityCount; i++) { + final ActivityThread.ActivityClientRecord record = + activityThread.mActivities.valueAt(i); + if (record == null) { + continue; + } + final Activity activity = record.activity; + if (activity == null) { + continue; + } + if (activity.getWindow().getDecorView().hasFocus()) { + if (android.view.autofill.Helper.sVerbose) { + Log.v(TAG, "getAutofillClient(): found activity for " + this + ": " + activity); + } + return activity; + } + } + if (android.view.autofill.Helper.sVerbose) { + Log.v(TAG, "getAutofillClient(): none of the " + activityCount + " activities on " + + this + " have focus"); + } + return null; + } +} diff --git a/android/app/ApplicationPackageManager.java b/android/app/ApplicationPackageManager.java index cc68c051..a68136b5 100644 --- a/android/app/ApplicationPackageManager.java +++ b/android/app/ApplicationPackageManager.java @@ -20,6 +20,7 @@ import android.annotation.DrawableRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringRes; +import android.annotation.UserIdInt; import android.annotation.XmlRes; import android.content.ComponentName; import android.content.ContentResolver; @@ -69,6 +70,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; import android.os.SystemProperties; @@ -140,6 +142,11 @@ public class ApplicationPackageManager extends PackageManager { } @Override + public int getUserId() { + return mContext.getUserId(); + } + + @Override public PackageInfo getPackageInfo(String packageName, int flags) throws NameNotFoundException { return getPackageInfoAsUser(packageName, flags, mContext.getUserId()); @@ -1026,19 +1033,25 @@ public class ApplicationPackageManager extends PackageManager { } @Override - public ResolveInfo resolveService(Intent intent, int flags) { + public ResolveInfo resolveServiceAsUser(Intent intent, @ResolveInfoFlags int flags, + @UserIdInt int userId) { try { return mPM.resolveService( intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), flags, - mContext.getUserId()); + userId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @Override + public ResolveInfo resolveService(Intent intent, int flags) { + return resolveServiceAsUser(intent, flags, mContext.getUserId()); + } + + @Override @SuppressWarnings("unchecked") public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int flags, int userId) { try { @@ -1349,11 +1362,10 @@ public class ApplicationPackageManager extends PackageManager { if (badgeColor == null) { return null; } - badgeColor.setTint(getUserBadgeColor(user)); Drawable badgeForeground = getDrawableForDensity( com.android.internal.R.drawable.ic_corp_badge_case, density); - Drawable badge = new LayerDrawable( - new Drawable[] {badgeColor, badgeForeground }); + badgeForeground.setTint(getUserBadgeColor(user)); + Drawable badge = new LayerDrawable(new Drawable[] {badgeColor, badgeForeground }); return badge; } @@ -1409,7 +1421,7 @@ public class ApplicationPackageManager extends PackageManager { sameUid ? app.sourceDir : app.publicSourceDir, sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs, app.resourceDirs, app.sharedLibraryFiles, Display.DEFAULT_DISPLAY, - mContext.mLoadedApk); + mContext.mPackageInfo); if (r != null) { return r; } @@ -2140,16 +2152,43 @@ public class ApplicationPackageManager extends PackageManager { } @Override - public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended, - int userId) { + public String[] setPackagesSuspended(String[] packageNames, boolean suspended, + PersistableBundle appExtras, PersistableBundle launcherExtras, + String dialogMessage) { + // TODO (b/75332201): Pass in the dialogMessage and use it in the interceptor dialog try { - return mPM.setPackagesSuspendedAsUser(packageNames, suspended, userId); + return mPM.setPackagesSuspendedAsUser(packageNames, suspended, appExtras, + launcherExtras, mContext.getOpPackageName(), mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @Override + public PersistableBundle getSuspendedPackageAppExtras(String packageName) { + try { + return mPM.getSuspendedPackageAppExtras(packageName, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public Bundle getSuspendedPackageAppExtras() { + final PersistableBundle extras = getSuspendedPackageAppExtras(mContext.getOpPackageName()); + return extras != null ? new Bundle(extras.deepCopy()) : null; + } + + @Override + public void setSuspendedPackageAppExtras(String packageName, PersistableBundle appExtras) { + try { + mPM.setSuspendedPackageAppExtras(packageName, appExtras, mContext.getUserId()); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + @Override public boolean isPackageSuspendedForUser(String packageName, int userId) { try { return mPM.isPackageSuspendedForUser(packageName, userId); @@ -2160,6 +2199,17 @@ public class ApplicationPackageManager extends PackageManager { /** @hide */ @Override + public boolean isPackageSuspended(String packageName) { + return isPackageSuspendedForUser(packageName, mContext.getUserId()); + } + + @Override + public boolean isPackageSuspended() { + return isPackageSuspendedForUser(mContext.getOpPackageName(), mContext.getUserId()); + } + + /** @hide */ + @Override public void setApplicationCategoryHint(String packageName, int categoryHint) { try { mPM.setApplicationCategoryHint(packageName, categoryHint, @@ -2797,4 +2847,22 @@ public class ApplicationPackageManager extends PackageManager { return mArtManager; } } + + @Override + public String getSystemTextClassifierPackageName() { + try { + return mPM.getSystemTextClassifierPackageName(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + @Override + public boolean isPackageStateProtected(String packageName, int userId) { + try { + return mPM.isPackageStateProtected(packageName, userId); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } } diff --git a/android/app/ClientTransactionHandler.java b/android/app/ClientTransactionHandler.java index 0f66652a..0639b000 100644 --- a/android/app/ClientTransactionHandler.java +++ b/android/app/ClientTransactionHandler.java @@ -17,11 +17,14 @@ package android.app; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.PendingTransactionActions; +import android.app.servertransaction.TransactionExecutor; import android.content.pm.ApplicationInfo; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.os.IBinder; +import android.util.MergedConfiguration; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.ReferrerIntent; import java.io.PrintWriter; @@ -42,6 +45,23 @@ public abstract class ClientTransactionHandler { sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction); } + /** + * Execute transaction immediately without scheduling it. This is used for local requests, so + * it will also recycle the transaction. + */ + @VisibleForTesting + public void executeTransaction(ClientTransaction transaction) { + transaction.preExecute(this); + getTransactionExecutor().execute(transaction); + transaction.recycle(); + } + + /** + * Get the {@link TransactionExecutor} that will be performing lifecycle transitions and + * callbacks for activities. + */ + abstract TransactionExecutor getTransactionExecutor(); + abstract void sendMessage(int what, Object obj); @@ -60,19 +80,36 @@ public abstract class ClientTransactionHandler { /** Destroy the activity. */ public abstract void handleDestroyActivity(IBinder token, boolean finishing, int configChanges, - boolean getNonConfigInstance); + boolean getNonConfigInstance, String reason); /** Pause the activity. */ public abstract void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving, - int configChanges, boolean dontReport, PendingTransactionActions pendingActions); + int configChanges, PendingTransactionActions pendingActions, String reason); - /** Resume the activity. */ - public abstract void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, - String reason); + /** + * Resume the activity. + * @param token Target activity token. + * @param finalStateRequest Flag indicating if this call is handling final lifecycle state + * request for a transaction. + * @param isForward Flag indicating if next transition is forward. + * @param reason Reason for performing this operation. + */ + public abstract void handleResumeActivity(IBinder token, boolean finalStateRequest, + boolean isForward, String reason); - /** Stop the activity. */ + /** + * Stop the activity. + * @param token Target activity token. + * @param show Flag indicating whether activity is still shown. + * @param configChanges Activity configuration changes. + * @param pendingActions Pending actions to be used on this or later stages of activity + * transaction. + * @param finalStateRequest Flag indicating if this call is handling final lifecycle state + * request for a transaction. + * @param reason Reason for performing this operation. + */ public abstract void handleStopActivity(IBinder token, boolean show, int configChanges, - PendingTransactionActions pendingActions); + PendingTransactionActions pendingActions, boolean finalStateRequest, String reason); /** Report that activity was stopped to server. */ public abstract void reportStop(PendingTransactionActions pendingActions); @@ -111,7 +148,7 @@ public abstract class ClientTransactionHandler { PendingTransactionActions pendingActions); /** Get package info. */ - public abstract LoadedApk getLoadedApkNoCheck(ApplicationInfo ai, + public abstract LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo); /** Deliver app configuration change notification. */ @@ -124,6 +161,39 @@ public abstract class ClientTransactionHandler { public abstract ActivityThread.ActivityClientRecord getActivityClient(IBinder token); /** + * Prepare activity relaunch to update internal bookkeeping. This is used to track multiple + * relaunch and config update requests. + * @param token Activity token. + * @param pendingResults Activity results to be delivered. + * @param pendingNewIntents New intent messages to be delivered. + * @param configChanges Mask of configuration changes that have occurred. + * @param config New configuration applied to the activity. + * @param preserveWindow Whether the activity should try to reuse the window it created, + * including the decor view after the relaunch. + * @return An initialized instance of {@link ActivityThread.ActivityClientRecord} to use during + * relaunch, or {@code null} if relaunch cancelled. + */ + public abstract ActivityThread.ActivityClientRecord prepareRelaunchActivity(IBinder token, + List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents, + int configChanges, MergedConfiguration config, boolean preserveWindow); + + /** + * Perform activity relaunch. + * @param r Activity client record prepared for relaunch. + * @param pendingActions Pending actions to be used on later stages of activity transaction. + * */ + public abstract void handleRelaunchActivity(ActivityThread.ActivityClientRecord r, + PendingTransactionActions pendingActions); + + /** + * Report that relaunch request was handled. + * @param token Target activity token. + * @param pendingActions Pending actions initialized on earlier stages of activity transaction. + * Used to check if we should report relaunch to WM. + * */ + public abstract void reportRelaunch(IBinder token, PendingTransactionActions pendingActions); + + /** * Debugging output. * @param pw {@link PrintWriter} to write logs to. * @param prefix Prefix to prepend to output. diff --git a/android/app/ContextImpl.java b/android/app/ContextImpl.java index 4914ffaf..71b88fa4 100644 --- a/android/app/ContextImpl.java +++ b/android/app/ContextImpl.java @@ -18,6 +18,7 @@ package android.app; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentProvider; @@ -91,6 +92,7 @@ import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Objects; import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicInteger; class ReceiverRestrictedContext extends ContextWrapper { ReceiverRestrictedContext(Context base) { @@ -159,12 +161,12 @@ class ContextImpl extends Context { private ArrayMap<String, File> mSharedPrefsPaths; final @NonNull ActivityThread mMainThread; - final @NonNull LoadedApk mLoadedApk; + final @NonNull LoadedApk mPackageInfo; private @Nullable ClassLoader mClassLoader; private final @Nullable IBinder mActivityToken; - private final @Nullable UserHandle mUser; + private final @NonNull UserHandle mUser; private final ApplicationContentResolver mContentResolver; @@ -187,6 +189,7 @@ class ContextImpl extends Context { private @Nullable String mSplitName = null; private AutofillClient mAutofillClient = null; + private boolean mIsAutofillCompatEnabled; private final Object mSync = new Object(); @@ -206,6 +209,17 @@ class ContextImpl extends Context { // The system service cache for the system services that are cached per-ContextImpl. final Object[] mServiceCache = SystemServiceRegistry.createServiceCache(); + static final int STATE_UNINITIALIZED = 0; + static final int STATE_INITIALIZING = 1; + static final int STATE_READY = 2; + + /** + * Initialization state for each service. Any of {@link #STATE_UNINITIALIZED}, + * {@link #STATE_INITIALIZING} or {@link #STATE_READY}, + */ + final AtomicInteger[] mServiceInitializationStateArray = + SystemServiceRegistry.createServiceInitializationStateArray(); + static ContextImpl getImpl(Context context) { Context nextContext; while ((context instanceof ContextWrapper) && @@ -257,8 +271,8 @@ class ContextImpl extends Context { @Override public Context getApplicationContext() { - return (mLoadedApk != null) ? - mLoadedApk.getApplication() : mMainThread.getApplication(); + return (mPackageInfo != null) ? + mPackageInfo.getApplication() : mMainThread.getApplication(); } @Override @@ -302,15 +316,15 @@ class ContextImpl extends Context { @Override public ClassLoader getClassLoader() { - return mClassLoader != null ? mClassLoader : (mLoadedApk != null ? mLoadedApk.getClassLoader() : ClassLoader.getSystemClassLoader()); + return mClassLoader != null ? mClassLoader : (mPackageInfo != null ? mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader()); } @Override public String getPackageName() { - if (mLoadedApk != null) { - return mLoadedApk.getPackageName(); + if (mPackageInfo != null) { + return mPackageInfo.getPackageName(); } - // No mLoadedApk means this is a Context for the system itself, + // No mPackageInfo means this is a Context for the system itself, // and this here is its name. return "android"; } @@ -329,24 +343,24 @@ class ContextImpl extends Context { @Override public ApplicationInfo getApplicationInfo() { - if (mLoadedApk != null) { - return mLoadedApk.getApplicationInfo(); + if (mPackageInfo != null) { + return mPackageInfo.getApplicationInfo(); } throw new RuntimeException("Not supported in system context"); } @Override public String getPackageResourcePath() { - if (mLoadedApk != null) { - return mLoadedApk.getResDir(); + if (mPackageInfo != null) { + return mPackageInfo.getResDir(); } throw new RuntimeException("Not supported in system context"); } @Override public String getPackageCodePath() { - if (mLoadedApk != null) { - return mLoadedApk.getAppDir(); + if (mPackageInfo != null) { + return mPackageInfo.getAppDir(); } throw new RuntimeException("Not supported in system context"); } @@ -356,7 +370,7 @@ class ContextImpl extends Context { // At least one application in the world actually passes in a null // name. This happened to work because when we generated the file name // we would stringify it to "null.xml". Nice. - if (mLoadedApk.getApplicationInfo().targetSdkVersion < + if (mPackageInfo.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.KITKAT) { if (name == null) { name = "null"; @@ -408,6 +422,7 @@ class ContextImpl extends Context { return sp; } + @GuardedBy("ContextImpl.class") private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() { if (sSharedPrefsCache == null) { sSharedPrefsCache = new ArrayMap<>(); @@ -913,14 +928,14 @@ class ContextImpl extends Context { /** @hide */ @Override - public void startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) { + public int startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) { if ((intents[0].getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { throw new AndroidRuntimeException( "Calling startActivities() from outside of an Activity " + " context requires the FLAG_ACTIVITY_NEW_TASK flag on first Intent." + " Is this really what you want?"); } - mMainThread.getInstrumentation().execStartActivitiesAsUser( + return mMainThread.getInstrumentation().execStartActivitiesAsUser( getOuterContext(), mMainThread.getApplicationThread(), null, (Activity) null, intents, options, userHandle.getIdentifier()); } @@ -1104,11 +1119,11 @@ class ContextImpl extends Context { warnIfCallingFromSystemProcess(); IIntentReceiver rd = null; if (resultReceiver != null) { - if (mLoadedApk != null) { + if (mPackageInfo != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } - rd = mLoadedApk.getReceiverDispatcher( + rd = mPackageInfo.getReceiverDispatcher( resultReceiver, getOuterContext(), scheduler, mMainThread.getInstrumentation(), false); } else { @@ -1208,11 +1223,11 @@ class ContextImpl extends Context { Handler scheduler, int initialCode, String initialData, Bundle initialExtras) { IIntentReceiver rd = null; if (resultReceiver != null) { - if (mLoadedApk != null) { + if (mPackageInfo != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } - rd = mLoadedApk.getReceiverDispatcher( + rd = mPackageInfo.getReceiverDispatcher( resultReceiver, getOuterContext(), scheduler, mMainThread.getInstrumentation(), false); } else { @@ -1262,11 +1277,11 @@ class ContextImpl extends Context { warnIfCallingFromSystemProcess(); IIntentReceiver rd = null; if (resultReceiver != null) { - if (mLoadedApk != null) { + if (mPackageInfo != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } - rd = mLoadedApk.getReceiverDispatcher( + rd = mPackageInfo.getReceiverDispatcher( resultReceiver, getOuterContext(), scheduler, mMainThread.getInstrumentation(), false); } else { @@ -1344,11 +1359,11 @@ class ContextImpl extends Context { Bundle initialExtras) { IIntentReceiver rd = null; if (resultReceiver != null) { - if (mLoadedApk != null) { + if (mPackageInfo != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } - rd = mLoadedApk.getReceiverDispatcher( + rd = mPackageInfo.getReceiverDispatcher( resultReceiver, getOuterContext(), scheduler, mMainThread.getInstrumentation(), false); } else { @@ -1425,11 +1440,11 @@ class ContextImpl extends Context { Handler scheduler, Context context, int flags) { IIntentReceiver rd = null; if (receiver != null) { - if (mLoadedApk != null && context != null) { + if (mPackageInfo != null && context != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } - rd = mLoadedApk.getReceiverDispatcher( + rd = mPackageInfo.getReceiverDispatcher( receiver, context, scheduler, mMainThread.getInstrumentation(), true); } else { @@ -1456,8 +1471,8 @@ class ContextImpl extends Context { @Override public void unregisterReceiver(BroadcastReceiver receiver) { - if (mLoadedApk != null) { - IIntentReceiver rd = mLoadedApk.forgetReceiverDispatcher( + if (mPackageInfo != null) { + IIntentReceiver rd = mPackageInfo.forgetReceiverDispatcher( getOuterContext(), receiver); try { ActivityManager.getService().unregisterReceiver(rd); @@ -1565,8 +1580,7 @@ class ContextImpl extends Context { public boolean bindService(Intent service, ServiceConnection conn, int flags) { warnIfCallingFromSystemProcess(); - return bindServiceCommon(service, conn, flags, mMainThread.getHandler(), - Process.myUserHandle()); + return bindServiceCommon(service, conn, flags, mMainThread.getHandler(), getUser()); } /** @hide */ @@ -1590,7 +1604,7 @@ class ContextImpl extends Context { @Override public IServiceConnection getServiceDispatcher(ServiceConnection conn, Handler handler, int flags) { - return mLoadedApk.getServiceDispatcher(conn, getOuterContext(), handler, flags); + return mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler, flags); } /** @hide */ @@ -1612,16 +1626,16 @@ class ContextImpl extends Context { if (conn == null) { throw new IllegalArgumentException("connection is null"); } - if (mLoadedApk != null) { - sd = mLoadedApk.getServiceDispatcher(conn, getOuterContext(), handler, flags); + if (mPackageInfo != null) { + sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler, flags); } else { throw new RuntimeException("Not supported in system context"); } validateServiceIntent(service); try { IBinder token = getActivityToken(); - if (token == null && (flags&BIND_AUTO_CREATE) == 0 && mLoadedApk != null - && mLoadedApk.getApplicationInfo().targetSdkVersion + if (token == null && (flags&BIND_AUTO_CREATE) == 0 && mPackageInfo != null + && mPackageInfo.getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) { flags |= BIND_WAIVE_PRIORITY; } @@ -1645,8 +1659,8 @@ class ContextImpl extends Context { if (conn == null) { throw new IllegalArgumentException("connection is null"); } - if (mLoadedApk != null) { - IServiceConnection sd = mLoadedApk.forgetServiceDispatcher( + if (mPackageInfo != null) { + IServiceConnection sd = mPackageInfo.forgetServiceDispatcher( getOuterContext(), conn); try { ActivityManager.getService().unbindService(sd); @@ -1699,6 +1713,9 @@ class ContextImpl extends Context { Slog.w(TAG, "Missing ActivityManager; assuming " + uid + " holds " + permission); return PackageManager.PERMISSION_GRANTED; } + Slog.w(TAG, "Missing ActivityManager; assuming " + uid + " does not hold " + + permission); + return PackageManager.PERMISSION_DENIED; } try { @@ -1991,20 +2008,40 @@ class ContextImpl extends Context { } } + private static Resources createResources(IBinder activityToken, LoadedApk pi, String splitName, + int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo) { + final String[] splitResDirs; + final ClassLoader classLoader; + try { + splitResDirs = pi.getSplitPaths(splitName); + classLoader = pi.getSplitClassLoader(splitName); + } catch (NameNotFoundException e) { + throw new RuntimeException(e); + } + return ResourcesManager.getInstance().getResources(activityToken, + pi.getResDir(), + splitResDirs, + pi.getOverlayDirs(), + pi.getApplicationInfo().sharedLibraryFiles, + displayId, + overrideConfig, + compatInfo, + classLoader); + } + @Override public Context createApplicationContext(ApplicationInfo application, int flags) throws NameNotFoundException { - LoadedApk loadedApk = mMainThread.getLoadedApk(application, - mResources.getCompatibilityInfo(), + LoadedApk pi = mMainThread.getPackageInfo(application, mResources.getCompatibilityInfo(), flags | CONTEXT_REGISTER_PACKAGE); - if (loadedApk != null) { - ContextImpl c = new ContextImpl(this, mMainThread, loadedApk, null, mActivityToken, + if (pi != null) { + ContextImpl c = new ContextImpl(this, mMainThread, pi, null, mActivityToken, new UserHandle(UserHandle.getUserId(application.uid)), flags, null); final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY; - c.setResources(loadedApk.createResources(mActivityToken, null, displayId, null, + c.setResources(createResources(mActivityToken, pi, null, displayId, null, getDisplayAdjustments(displayId).getCompatibilityInfo())); if (c.mResources != null) { return c; @@ -2018,8 +2055,7 @@ class ContextImpl extends Context { @Override public Context createPackageContext(String packageName, int flags) throws NameNotFoundException { - return createPackageContextAsUser(packageName, flags, - mUser != null ? mUser : Process.myUserHandle()); + return createPackageContextAsUser(packageName, flags, mUser); } @Override @@ -2028,21 +2064,20 @@ class ContextImpl extends Context { if (packageName.equals("system") || packageName.equals("android")) { // The system resources are loaded in every application, so we can safely copy // the context without reloading Resources. - return new ContextImpl(this, mMainThread, mLoadedApk, null, mActivityToken, user, + return new ContextImpl(this, mMainThread, mPackageInfo, null, mActivityToken, user, flags, null); } - LoadedApk loadedApk = mMainThread.getLoadedApkForPackageName(packageName, - mResources.getCompatibilityInfo(), + LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(), flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier()); - if (loadedApk != null) { - ContextImpl c = new ContextImpl(this, mMainThread, loadedApk, null, mActivityToken, user, + if (pi != null) { + ContextImpl c = new ContextImpl(this, mMainThread, pi, null, mActivityToken, user, flags, null); final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY; - c.setResources(loadedApk.createResources(mActivityToken, null, displayId, null, + c.setResources(createResources(mActivityToken, pi, null, displayId, null, getDisplayAdjustments(displayId).getCompatibilityInfo())); if (c.mResources != null) { return c; @@ -2056,21 +2091,30 @@ class ContextImpl extends Context { @Override public Context createContextForSplit(String splitName) throws NameNotFoundException { - if (!mLoadedApk.getApplicationInfo().requestsIsolatedSplitLoading()) { + if (!mPackageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) { // All Splits are always loaded. return this; } - final ClassLoader classLoader = mLoadedApk.getSplitClassLoader(splitName); + final ClassLoader classLoader = mPackageInfo.getSplitClassLoader(splitName); + final String[] paths = mPackageInfo.getSplitPaths(splitName); - final ContextImpl context = new ContextImpl(this, mMainThread, mLoadedApk, splitName, + final ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, splitName, mActivityToken, mUser, mFlags, classLoader); final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY; - context.setResources(mLoadedApk.getOrCreateResourcesForSplit(splitName, - mActivityToken, displayId)); + context.setResources(ResourcesManager.getInstance().getResources( + mActivityToken, + mPackageInfo.getResDir(), + paths, + mPackageInfo.getOverlayDirs(), + mPackageInfo.getApplicationInfo().sharedLibraryFiles, + displayId, + null, + mPackageInfo.getCompatibilityInfo(), + classLoader)); return context; } @@ -2080,11 +2124,11 @@ class ContextImpl extends Context { throw new IllegalArgumentException("overrideConfiguration must not be null"); } - ContextImpl context = new ContextImpl(this, mMainThread, mLoadedApk, mSplitName, + ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mSplitName, mActivityToken, mUser, mFlags, mClassLoader); final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY; - context.setResources(mLoadedApk.createResources(mActivityToken, mSplitName, displayId, + context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId, overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo())); return context; } @@ -2095,11 +2139,11 @@ class ContextImpl extends Context { throw new IllegalArgumentException("display must not be null"); } - ContextImpl context = new ContextImpl(this, mMainThread, mLoadedApk, mSplitName, + ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mSplitName, mActivityToken, mUser, mFlags, mClassLoader); final int displayId = display.getDisplayId(); - context.setResources(mLoadedApk.createResources(mActivityToken, mSplitName, displayId, + context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId, null, getDisplayAdjustments(displayId).getCompatibilityInfo())); context.mDisplay = display; return context; @@ -2109,7 +2153,7 @@ class ContextImpl extends Context { public Context createDeviceProtectedStorageContext() { final int flags = (mFlags & ~Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE) | Context.CONTEXT_DEVICE_PROTECTED_STORAGE; - return new ContextImpl(this, mMainThread, mLoadedApk, mSplitName, mActivityToken, mUser, + return new ContextImpl(this, mMainThread, mPackageInfo, mSplitName, mActivityToken, mUser, flags, mClassLoader); } @@ -2117,7 +2161,7 @@ class ContextImpl extends Context { public Context createCredentialProtectedStorageContext() { final int flags = (mFlags & ~Context.CONTEXT_DEVICE_PROTECTED_STORAGE) | Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE; - return new ContextImpl(this, mMainThread, mLoadedApk, mSplitName, mActivityToken, mUser, + return new ContextImpl(this, mMainThread, mPackageInfo, mSplitName, mActivityToken, mUser, flags, mClassLoader); } @@ -2166,14 +2210,14 @@ class ContextImpl extends Context { @Override public File getDataDir() { - if (mLoadedApk != null) { + if (mPackageInfo != null) { File res = null; if (isCredentialProtectedStorage()) { - res = mLoadedApk.getCredentialProtectedDataDirFile(); + res = mPackageInfo.getCredentialProtectedDataDirFile(); } else if (isDeviceProtectedStorage()) { - res = mLoadedApk.getDeviceProtectedDataDirFile(); + res = mPackageInfo.getDeviceProtectedDataDirFile(); } else { - res = mLoadedApk.getDataDirFile(); + res = mPackageInfo.getDataDirFile(); } if (res != null) { @@ -2207,6 +2251,12 @@ class ContextImpl extends Context { /** {@hide} */ @Override + public UserHandle getUser() { + return mUser; + } + + /** {@hide} */ + @Override public int getUserId() { return mUser.getIdentifier(); } @@ -2223,11 +2273,24 @@ class ContextImpl extends Context { mAutofillClient = client; } + /** @hide */ + @Override + public boolean isAutofillCompatibilityEnabled() { + return mIsAutofillCompatEnabled; + } + + /** @hide */ + @TestApi + @Override + public void setAutofillCompatibilityEnabled(boolean autofillCompatEnabled) { + mIsAutofillCompatEnabled = autofillCompatEnabled; + } + static ContextImpl createSystemContext(ActivityThread mainThread) { - LoadedApk loadedApk = new LoadedApk(mainThread); - ContextImpl context = new ContextImpl(null, mainThread, loadedApk, null, null, null, 0, + LoadedApk packageInfo = new LoadedApk(mainThread); + ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0, null); - context.setResources(loadedApk.getResources()); + context.setResources(packageInfo.getResources()); context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(), context.mResourcesManager.getDisplayMetrics()); return context; @@ -2238,35 +2301,35 @@ class ContextImpl extends Context { * Make sure that the created system UI context shares the same LoadedApk as the system context. */ static ContextImpl createSystemUiContext(ContextImpl systemContext) { - final LoadedApk loadedApk = systemContext.mLoadedApk; - ContextImpl context = new ContextImpl(null, systemContext.mMainThread, loadedApk, null, + final LoadedApk packageInfo = systemContext.mPackageInfo; + ContextImpl context = new ContextImpl(null, systemContext.mMainThread, packageInfo, null, null, null, 0, null); - context.setResources(loadedApk.createResources(null, null, Display.DEFAULT_DISPLAY, null, - loadedApk.getCompatibilityInfo())); + context.setResources(createResources(null, packageInfo, null, Display.DEFAULT_DISPLAY, null, + packageInfo.getCompatibilityInfo())); return context; } - static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk loadedApk) { - if (loadedApk == null) throw new IllegalArgumentException("loadedApk"); - ContextImpl context = new ContextImpl(null, mainThread, loadedApk, null, null, null, 0, + static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) { + if (packageInfo == null) throw new IllegalArgumentException("packageInfo"); + ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0, null); - context.setResources(loadedApk.getResources()); + context.setResources(packageInfo.getResources()); return context; } static ContextImpl createActivityContext(ActivityThread mainThread, - LoadedApk loadedApk, ActivityInfo activityInfo, IBinder activityToken, int displayId, + LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId, Configuration overrideConfiguration) { - if (loadedApk == null) throw new IllegalArgumentException("loadedApk"); + if (packageInfo == null) throw new IllegalArgumentException("packageInfo"); - String[] splitDirs = loadedApk.getSplitResDirs(); - ClassLoader classLoader = loadedApk.getClassLoader(); + String[] splitDirs = packageInfo.getSplitResDirs(); + ClassLoader classLoader = packageInfo.getClassLoader(); - if (loadedApk.getApplicationInfo().requestsIsolatedSplitLoading()) { + if (packageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) { Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "SplitDependencies"); try { - classLoader = loadedApk.getSplitClassLoader(activityInfo.splitName); - splitDirs = loadedApk.getSplitPaths(activityInfo.splitName); + classLoader = packageInfo.getSplitClassLoader(activityInfo.splitName); + splitDirs = packageInfo.getSplitPaths(activityInfo.splitName); } catch (NameNotFoundException e) { // Nothing above us can handle a NameNotFoundException, better crash. throw new RuntimeException(e); @@ -2275,14 +2338,14 @@ class ContextImpl extends Context { } } - ContextImpl context = new ContextImpl(null, mainThread, loadedApk, activityInfo.splitName, + ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName, activityToken, null, 0, classLoader); // Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY. displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY; final CompatibilityInfo compatInfo = (displayId == Display.DEFAULT_DISPLAY) - ? loadedApk.getCompatibilityInfo() + ? packageInfo.getCompatibilityInfo() : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; final ResourcesManager resourcesManager = ResourcesManager.getInstance(); @@ -2290,10 +2353,10 @@ class ContextImpl extends Context { // Create the base resources for which all configuration contexts for this Activity // will be rebased upon. context.setResources(resourcesManager.createBaseActivityResources(activityToken, - loadedApk.getResDir(), + packageInfo.getResDir(), splitDirs, - loadedApk.getOverlayDirs(), - loadedApk.getApplicationInfo().sharedLibraryFiles, + packageInfo.getOverlayDirs(), + packageInfo.getApplicationInfo().sharedLibraryFiles, displayId, overrideConfiguration, compatInfo, @@ -2304,7 +2367,7 @@ class ContextImpl extends Context { } private ContextImpl(@Nullable ContextImpl container, @NonNull ActivityThread mainThread, - @NonNull LoadedApk loadedApk, @Nullable String splitName, + @NonNull LoadedApk packageInfo, @Nullable String splitName, @Nullable IBinder activityToken, @Nullable UserHandle user, int flags, @Nullable ClassLoader classLoader) { mOuterContext = this; @@ -2313,10 +2376,10 @@ class ContextImpl extends Context { // location for application. if ((flags & (Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE | Context.CONTEXT_DEVICE_PROTECTED_STORAGE)) == 0) { - final File dataDir = loadedApk.getDataDirFile(); - if (Objects.equals(dataDir, loadedApk.getCredentialProtectedDataDirFile())) { + final File dataDir = packageInfo.getDataDirFile(); + if (Objects.equals(dataDir, packageInfo.getCredentialProtectedDataDirFile())) { flags |= Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE; - } else if (Objects.equals(dataDir, loadedApk.getDeviceProtectedDataDirFile())) { + } else if (Objects.equals(dataDir, packageInfo.getDeviceProtectedDataDirFile())) { flags |= Context.CONTEXT_DEVICE_PROTECTED_STORAGE; } } @@ -2330,7 +2393,7 @@ class ContextImpl extends Context { } mUser = user; - mLoadedApk = loadedApk; + mPackageInfo = packageInfo; mSplitName = splitName; mClassLoader = classLoader; mResourcesManager = ResourcesManager.getInstance(); @@ -2341,8 +2404,8 @@ class ContextImpl extends Context { setResources(container.mResources); mDisplay = container.mDisplay; } else { - mBasePackageName = loadedApk.mPackageName; - ApplicationInfo ainfo = loadedApk.getApplicationInfo(); + mBasePackageName = packageInfo.mPackageName; + ApplicationInfo ainfo = packageInfo.getApplicationInfo(); if (ainfo.uid == Process.SYSTEM_UID && ainfo.uid != Process.myUid()) { // Special case: system components allow themselves to be loaded in to other // processes. For purposes of app ops, we must then consider the context as @@ -2354,7 +2417,7 @@ class ContextImpl extends Context { } } - mContentResolver = new ApplicationContentResolver(this, mainThread, user); + mContentResolver = new ApplicationContentResolver(this, mainThread); } void setResources(Resources r) { @@ -2365,7 +2428,7 @@ class ContextImpl extends Context { } void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) { - mLoadedApk.installSystemApplicationInfo(info, classLoader); + mPackageInfo.installSystemApplicationInfo(info, classLoader); } final void scheduleFinalCleanup(String who, String what) { @@ -2374,7 +2437,7 @@ class ContextImpl extends Context { final void performFinalCleanup(String who, String what) { //Log.i(TAG, "Cleanup up context: " + this); - mLoadedApk.removeContextRegistrations(getOuterContext(), who, what); + mPackageInfo.removeContextRegistrations(getOuterContext(), who, what); } final Context getReceiverRestrictedContext() { @@ -2470,13 +2533,10 @@ class ContextImpl extends Context { private static final class ApplicationContentResolver extends ContentResolver { private final ActivityThread mMainThread; - private final UserHandle mUser; - public ApplicationContentResolver( - Context context, ActivityThread mainThread, UserHandle user) { + public ApplicationContentResolver(Context context, ActivityThread mainThread) { super(context); mMainThread = Preconditions.checkNotNull(mainThread); - mUser = Preconditions.checkNotNull(user); } @Override @@ -2522,7 +2582,7 @@ class ContextImpl extends Context { /** @hide */ protected int resolveUserIdFromAuthority(String auth) { - return ContentProvider.getUserIdFromAuthority(auth, mUser.getIdentifier()); + return ContentProvider.getUserIdFromAuthority(auth, getUserId()); } } } diff --git a/android/app/Dialog.java b/android/app/Dialog.java index 2b648ea6..e4a05834 100644 --- a/android/app/Dialog.java +++ b/android/app/Dialog.java @@ -291,7 +291,10 @@ public class Dialog implements DialogInterface, Window.Callback, if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) { mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR); } - mDecor.setVisibility(View.VISIBLE); + if (mDecor.getVisibility() != View.VISIBLE) { + mDecor.setVisibility(View.VISIBLE); + sendShowMessage(); + } } return; } @@ -318,16 +321,20 @@ public class Dialog implements DialogInterface, Window.Callback, } WindowManager.LayoutParams l = mWindow.getAttributes(); + boolean restoreSoftInputMode = false; if ((l.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) { - WindowManager.LayoutParams nl = new WindowManager.LayoutParams(); - nl.copyFrom(l); - nl.softInputMode |= + l.softInputMode |= WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; - l = nl; + restoreSoftInputMode = true; } mWindowManager.addView(mDecor, l); + if (restoreSoftInputMode) { + l.softInputMode &= + ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; + } + mShowing = true; sendShowMessage(); @@ -609,18 +616,19 @@ public class Dialog implements DialogInterface, Window.Callback, /** * A key was pressed down. + * <p> + * If the focused view didn't want this event, this method is called. + * <p> + * Default implementation consumes {@link KeyEvent#KEYCODE_BACK KEYCODE_BACK} + * and, as of {@link android.os.Build.VERSION_CODES#P P}, {@link KeyEvent#KEYCODE_ESCAPE + * KEYCODE_ESCAPE} to later handle them in {@link #onKeyUp}. * - * <p>If the focused view didn't want this event, this method is called. - * - * <p>The default implementation consumed the KEYCODE_BACK to later - * handle it in {@link #onKeyUp}. - * * @see #onKeyUp * @see android.view.KeyEvent */ @Override public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_BACK) { + if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) { event.startTracking(); return true; } @@ -640,16 +648,18 @@ public class Dialog implements DialogInterface, Window.Callback, /** * A key was released. - * - * <p>The default implementation handles KEYCODE_BACK to close the - * dialog. + * <p> + * Default implementation consumes {@link KeyEvent#KEYCODE_BACK KEYCODE_BACK} + * and, as of {@link android.os.Build.VERSION_CODES#P P}, {@link KeyEvent#KEYCODE_ESCAPE + * KEYCODE_ESCAPE} to close the dialog. * * @see #onKeyDown - * @see KeyEvent + * @see android.view.KeyEvent */ @Override public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() + if ((keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) + && event.isTracking() && !event.isCanceled()) { onBackPressed(); return true; diff --git a/android/app/EphemeralResolverService.java b/android/app/EphemeralResolverService.java deleted file mode 100644 index 427a0386..00000000 --- a/android/app/EphemeralResolverService.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.app; - -import android.annotation.SystemApi; -import android.app.Service; -import android.app.InstantAppResolverService.InstantAppResolutionCallback; -import android.content.Context; -import android.content.Intent; -import android.content.pm.EphemeralResolveInfo; -import android.content.pm.InstantAppResolveInfo; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.IRemoteCallback; -import android.os.Looper; -import android.os.Message; -import android.os.RemoteException; -import android.util.Log; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * Base class for implementing the resolver service. - * @hide - * @removed - * @deprecated use InstantAppResolverService instead - */ -@Deprecated -@SystemApi -public abstract class EphemeralResolverService extends InstantAppResolverService { - private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE; - private static final String TAG = "PackageManager"; - - /** - * Called to retrieve resolve info for ephemeral applications. - * - * @param digestPrefix The hash prefix of the ephemeral's domain. - * @param prefixMask A mask that was applied to each digest prefix. This should - * be used when comparing against the digest prefixes as all bits might - * not be set. - * @deprecated use {@link #onGetEphemeralResolveInfo(int[])} instead - */ - @Deprecated - public abstract List<EphemeralResolveInfo> onEphemeralResolveInfoList( - int digestPrefix[], int prefix); - - /** - * Called to retrieve resolve info for ephemeral applications. - * - * @param digestPrefix The hash prefix of the ephemeral's domain. - */ - public List<EphemeralResolveInfo> onGetEphemeralResolveInfo(int digestPrefix[]) { - return onEphemeralResolveInfoList(digestPrefix, 0xFFFFF000); - } - - /** - * Called to retrieve intent filters for ephemeral applications. - * - * @param hostName The name of the host to get intent filters for. - */ - public EphemeralResolveInfo onGetEphemeralIntentFilter(String hostName) { - throw new IllegalStateException("Must define"); - } - - @Override - public Looper getLooper() { - return super.getLooper(); - } - - @Override - void _onGetInstantAppResolveInfo(int[] digestPrefix, String token, - InstantAppResolutionCallback callback) { - if (DEBUG_EPHEMERAL) { - Log.d(TAG, "Legacy resolver; getInstantAppResolveInfo;" - + " prefix: " + Arrays.toString(digestPrefix)); - } - final List<EphemeralResolveInfo> response = onGetEphemeralResolveInfo(digestPrefix); - final int responseSize = response == null ? 0 : response.size(); - final List<InstantAppResolveInfo> resultList = new ArrayList<>(responseSize); - for (int i = 0; i < responseSize; i++) { - resultList.add(response.get(i).getInstantAppResolveInfo()); - } - callback.onInstantAppResolveInfo(resultList); - } - - @Override - void _onGetInstantAppIntentFilter(int[] digestPrefix, String token, - String hostName, InstantAppResolutionCallback callback) { - if (DEBUG_EPHEMERAL) { - Log.d(TAG, "Legacy resolver; getInstantAppIntentFilter;" - + " prefix: " + Arrays.toString(digestPrefix)); - } - final EphemeralResolveInfo response = onGetEphemeralIntentFilter(hostName); - final List<InstantAppResolveInfo> resultList = new ArrayList<>(1); - resultList.add(response.getInstantAppResolveInfo()); - callback.onInstantAppResolveInfo(resultList); - } -} diff --git a/android/app/Fragment.java b/android/app/Fragment.java index 4ff07f2e..8e8270a0 100644 --- a/android/app/Fragment.java +++ b/android/app/Fragment.java @@ -2311,7 +2311,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * transaction have called {@link #startPostponedEnterTransition()}. * <p> * This method should be called before being added to the FragmentTransaction or - * in {@link #onCreate(Bundle), {@link #onAttach(Context)}, or + * in {@link #onCreate(Bundle)}, {@link #onAttach(Context)}, or * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}}. * {@link #startPostponedEnterTransition()} must be called to allow the Fragment to * start the transitions. diff --git a/android/app/GrantedUriPermission.java b/android/app/GrantedUriPermission.java new file mode 100644 index 00000000..9e84fe13 --- /dev/null +++ b/android/app/GrantedUriPermission.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.UriPermission; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Represents an {@link UriPermission} granted to a package. + * + * {@hide} + */ +public class GrantedUriPermission implements Parcelable { + + public final Uri uri; + public final String packageName; + + public GrantedUriPermission(@NonNull Uri uri, @Nullable String packageName) { + this.uri = uri; + this.packageName = packageName; + } + + @Override + public String toString() { + return packageName + ":" + uri; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeParcelable(uri, flags); + out.writeString(packageName); + } + + public static final Parcelable.Creator<GrantedUriPermission> CREATOR = + new Parcelable.Creator<GrantedUriPermission>() { + @Override + public GrantedUriPermission createFromParcel(Parcel in) { + return new GrantedUriPermission(in); + } + + @Override + public GrantedUriPermission[] newArray(int size) { + return new GrantedUriPermission[size]; + } + }; + + private GrantedUriPermission(Parcel in) { + uri = in.readParcelable(null); + packageName = in.readString(); + } +} diff --git a/android/app/InstantAppResolverService.java b/android/app/InstantAppResolverService.java index c5dc86c7..58d0aaf5 100644 --- a/android/app/InstantAppResolverService.java +++ b/android/app/InstantAppResolverService.java @@ -17,7 +17,6 @@ package android.app; import android.annotation.SystemApi; -import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.pm.InstantAppResolveInfo; @@ -35,6 +34,7 @@ import android.util.Slog; import com.android.internal.os.SomeArgs; import java.util.Arrays; +import java.util.Collections; import java.util.List; /** @@ -43,7 +43,7 @@ import java.util.List; */ @SystemApi public abstract class InstantAppResolverService extends Service { - private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE; + private static final boolean DEBUG_INSTANT = Build.IS_DEBUGGABLE; private static final String TAG = "PackageManager"; /** @hide */ @@ -53,23 +53,89 @@ public abstract class InstantAppResolverService extends Service { Handler mHandler; /** - * Called to retrieve resolve info for instant applications. + * Called to retrieve resolve info for instant applications immediately. * * @param digestPrefix The hash prefix of the instant app's domain. + * @deprecated should implement {@link #onGetInstantAppResolveInfo(Intent, int[], String, + * InstantAppResolutionCallback)} */ + @Deprecated public void onGetInstantAppResolveInfo( int digestPrefix[], String token, InstantAppResolutionCallback callback) { - throw new IllegalStateException("Must define"); + throw new IllegalStateException("Must define onGetInstantAppResolveInfo"); } /** - * Called to retrieve intent filters for instant applications. + * Called to retrieve intent filters for instant applications from potentially expensive + * sources. * * @param digestPrefix The hash prefix of the instant app's domain. + * @deprecated should implement {@link #onGetInstantAppIntentFilter(Intent, int[], String, + * InstantAppResolutionCallback)} */ + @Deprecated public void onGetInstantAppIntentFilter( int digestPrefix[], String token, InstantAppResolutionCallback callback) { - throw new IllegalStateException("Must define"); + throw new IllegalStateException("Must define onGetInstantAppIntentFilter"); + } + + /** + * Called to retrieve resolve info for instant applications immediately. The response will be + * ignored if not provided within a reasonable time. {@link InstantAppResolveInfo}s provided + * in response to this method may be partial to request a second phase of resolution which will + * result in a subsequent call to + * {@link #onGetInstantAppIntentFilter(Intent, int[], String, InstantAppResolutionCallback)} + * + * + * @param sanitizedIntent The sanitized {@link Intent} used for resolution. A sanitized Intent + * is an intent with potential PII removed from the original intent. + * Fields removed include extras and the host + path of the data, if + * defined. + * @param hostDigestPrefix The hash prefix of the instant app's domain. + * @param token A unique identifier that will be provided in calls to + * {@link #onGetInstantAppIntentFilter(Intent, int[], String, + * InstantAppResolutionCallback)} + * and provided to the installer via {@link Intent#EXTRA_INSTANT_APP_TOKEN} to + * tie a single launch together. + * @param callback The {@link InstantAppResolutionCallback} to provide results to. + * + * @see InstantAppResolveInfo + */ + public void onGetInstantAppResolveInfo(Intent sanitizedIntent, int[] hostDigestPrefix, + String token, InstantAppResolutionCallback callback) { + // if not overridden, forward to old methods and filter out non-web intents + if (sanitizedIntent.isWebIntent()) { + onGetInstantAppResolveInfo(hostDigestPrefix, token, callback); + } else { + callback.onInstantAppResolveInfo(Collections.emptyList()); + } + } + + /** + * Called to retrieve intent filters for potentially matching instant applications. Unlike + * {@link #onGetInstantAppResolveInfo(Intent, int[], String, InstantAppResolutionCallback)}, + * the response may take as long as necessary to respond. All {@link InstantAppResolveInfo}s + * provided in response to this method must be completely populated. + * + * @param sanitizedIntent The sanitized {@link Intent} used for resolution. + * @param hostDigestPrefix The hash prefix of the instant app's domain or null if no host is + * defined. + * @param token A unique identifier that was provided in + * {@link #onGetInstantAppResolveInfo(Intent, int[], String, + * InstantAppResolutionCallback)} + * and provided to the currently visible installer via + * {@link Intent#EXTRA_INSTANT_APP_TOKEN}. + * @param callback The {@link InstantAppResolutionCallback} to provide results to + */ + public void onGetInstantAppIntentFilter(Intent sanitizedIntent, int[] hostDigestPrefix, + String token, InstantAppResolutionCallback callback) { + Log.e(TAG, "New onGetInstantAppIntentFilter is not overridden"); + // if not overridden, forward to old methods and filter out non-web intents + if (sanitizedIntent.isWebIntent()) { + onGetInstantAppIntentFilter(hostDigestPrefix, token, callback); + } else { + callback.onInstantAppResolveInfo(Collections.emptyList()); + } } /** @@ -89,33 +155,33 @@ public abstract class InstantAppResolverService extends Service { public final IBinder onBind(Intent intent) { return new IInstantAppResolver.Stub() { @Override - public void getInstantAppResolveInfoList( - int digestPrefix[], String token, int sequence, IRemoteCallback callback) { - if (DEBUG_EPHEMERAL) { + public void getInstantAppResolveInfoList(Intent sanitizedIntent, int[] digestPrefix, + String token, int sequence, IRemoteCallback callback) { + if (DEBUG_INSTANT) { Slog.v(TAG, "[" + token + "] Phase1 called; posting"); } final SomeArgs args = SomeArgs.obtain(); args.arg1 = callback; args.arg2 = digestPrefix; args.arg3 = token; - mHandler.obtainMessage( - ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO, sequence, 0, args) - .sendToTarget(); + args.arg4 = sanitizedIntent; + mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO, + sequence, 0, args).sendToTarget(); } @Override - public void getInstantAppIntentFilterList( - int digestPrefix[], String token, String hostName, IRemoteCallback callback) { - if (DEBUG_EPHEMERAL) { + public void getInstantAppIntentFilterList(Intent sanitizedIntent, + int[] digestPrefix, String token, IRemoteCallback callback) { + if (DEBUG_INSTANT) { Slog.v(TAG, "[" + token + "] Phase2 called; posting"); } final SomeArgs args = SomeArgs.obtain(); args.arg1 = callback; args.arg2 = digestPrefix; args.arg3 = token; - args.arg4 = hostName; - mHandler.obtainMessage( - ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER, callback).sendToTarget(); + args.arg4 = sanitizedIntent; + mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER, + callback).sendToTarget(); } }; } @@ -142,29 +208,9 @@ public abstract class InstantAppResolverService extends Service { } } - @Deprecated - void _onGetInstantAppResolveInfo(int[] digestPrefix, String token, - InstantAppResolutionCallback callback) { - if (DEBUG_EPHEMERAL) { - Slog.d(TAG, "[" + token + "] Phase1 request;" - + " prefix: " + Arrays.toString(digestPrefix)); - } - onGetInstantAppResolveInfo(digestPrefix, token, callback); - } - @Deprecated - void _onGetInstantAppIntentFilter(int digestPrefix[], String token, String hostName, - InstantAppResolutionCallback callback) { - if (DEBUG_EPHEMERAL) { - Slog.d(TAG, "[" + token + "] Phase2 request;" - + " prefix: " + Arrays.toString(digestPrefix)); - } - onGetInstantAppIntentFilter(digestPrefix, token, callback); - } - private final class ServiceHandler extends Handler { public static final int MSG_GET_INSTANT_APP_RESOLVE_INFO = 1; public static final int MSG_GET_INSTANT_APP_INTENT_FILTER = 2; - public ServiceHandler(Looper looper) { super(looper, null /*callback*/, true /*async*/); } @@ -179,9 +225,13 @@ public abstract class InstantAppResolverService extends Service { final IRemoteCallback callback = (IRemoteCallback) args.arg1; final int[] digestPrefix = (int[]) args.arg2; final String token = (String) args.arg3; + final Intent intent = (Intent) args.arg4; final int sequence = message.arg1; - _onGetInstantAppResolveInfo( - digestPrefix, token, + if (DEBUG_INSTANT) { + Slog.d(TAG, "[" + token + "] Phase1 request;" + + " prefix: " + Arrays.toString(digestPrefix)); + } + onGetInstantAppResolveInfo(intent, digestPrefix, token, new InstantAppResolutionCallback(sequence, callback)); } break; @@ -190,9 +240,12 @@ public abstract class InstantAppResolverService extends Service { final IRemoteCallback callback = (IRemoteCallback) args.arg1; final int[] digestPrefix = (int[]) args.arg2; final String token = (String) args.arg3; - final String hostName = (String) args.arg4; - _onGetInstantAppIntentFilter( - digestPrefix, token, hostName, + final Intent intent = (Intent) args.arg4; + if (DEBUG_INSTANT) { + Slog.d(TAG, "[" + token + "] Phase2 request;" + + " prefix: " + Arrays.toString(digestPrefix)); + } + onGetInstantAppIntentFilter(intent, digestPrefix, token, new InstantAppResolutionCallback(-1 /*sequence*/, callback)); } break; diff --git a/android/app/Instrumentation.java b/android/app/Instrumentation.java index 3c38a4ec..7f87814a 100644 --- a/android/app/Instrumentation.java +++ b/android/app/Instrumentation.java @@ -1216,10 +1216,10 @@ public class Instrumentation { + " disabling AppComponentFactory", new Throwable()); return AppComponentFactory.DEFAULT; } - LoadedApk loadedApk = mThread.peekLoadedApk(pkg, true); + LoadedApk apk = mThread.peekPackageInfo(pkg, true); // This is in the case of starting up "android". - if (loadedApk == null) loadedApk = mThread.getSystemContext().mLoadedApk; - return loadedApk.getAppFactory(); + if (apk == null) apk = mThread.getSystemContext().mPackageInfo; + return apk.getAppFactory(); } private void prePerformCreate(Activity activity) { @@ -1679,7 +1679,7 @@ public class Instrumentation { public void execStartActivities(Context who, IBinder contextThread, IBinder token, Activity target, Intent[] intents, Bundle options) { execStartActivitiesAsUser(who, contextThread, token, target, intents, options, - UserHandle.myUserId()); + who.getUserId()); } /** @@ -1688,9 +1688,13 @@ public class Instrumentation { * {@link ActivityMonitor} objects only match against the first activity in * the array. * + * @return The corresponding flag {@link ActivityManager#START_CANCELED}, + * {@link ActivityManager#START_SUCCESS} etc. indicating whether the launch was + * successful. + * * {@hide} */ - public void execStartActivitiesAsUser(Context who, IBinder contextThread, + public int execStartActivitiesAsUser(Context who, IBinder contextThread, IBinder token, Activity target, Intent[] intents, Bundle options, int userId) { IApplicationThread whoThread = (IApplicationThread) contextThread; @@ -1705,11 +1709,11 @@ public class Instrumentation { } if (result != null) { am.mHits++; - return; + return ActivityManager.START_CANCELED; } else if (am.match(who, null, intents[0])) { am.mHits++; if (am.isBlocking()) { - return; + return ActivityManager.START_CANCELED; } break; } @@ -1727,6 +1731,7 @@ public class Instrumentation { .startActivities(whoThread, who.getBasePackageName(), intents, resolvedTypes, token, options, userId); checkStartActivityResult(result, intents[0]); + return result; } catch (RemoteException e) { throw new RuntimeException("Failure from system", e); } @@ -1874,8 +1879,8 @@ public class Instrumentation { */ public ActivityResult execStartActivityAsCaller( Context who, IBinder contextThread, IBinder token, Activity target, - Intent intent, int requestCode, Bundle options, IBinder permissionToken, - boolean ignoreTargetSecurity, int userId) { + Intent intent, int requestCode, Bundle options, boolean ignoreTargetSecurity, + int userId) { IApplicationThread whoThread = (IApplicationThread) contextThread; if (mActivityMonitors != null) { synchronized (mSync) { @@ -1906,8 +1911,7 @@ public class Instrumentation { .startActivityAsCaller(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null ? target.mEmbeddedID : null, - requestCode, 0, null, options, permissionToken, - ignoreTargetSecurity, userId); + requestCode, 0, null, options, ignoreTargetSecurity, userId); checkStartActivityResult(result, intent); } catch (RemoteException e) { throw new RuntimeException("Failure from system", e); diff --git a/android/app/KeyguardManager.java b/android/app/KeyguardManager.java index 553099f2..4a3fcaf3 100644 --- a/android/app/KeyguardManager.java +++ b/android/app/KeyguardManager.java @@ -407,7 +407,7 @@ public class KeyguardManager { * password. */ public boolean isDeviceLocked() { - return isDeviceLocked(UserHandle.myUserId()); + return isDeviceLocked(mContext.getUserId()); } /** @@ -432,7 +432,7 @@ public class KeyguardManager { * @return true if a PIN, pattern or password was set. */ public boolean isDeviceSecure() { - return isDeviceSecure(UserHandle.myUserId()); + return isDeviceSecure(mContext.getUserId()); } /** diff --git a/android/app/LoadedApk.java b/android/app/LoadedApk.java index 26f49808..fc7d9a55 100644 --- a/android/app/LoadedApk.java +++ b/android/app/LoadedApk.java @@ -28,14 +28,13 @@ import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.dex.ArtManager; import android.content.pm.split.SplitDependencyLoader; import android.content.res.AssetManager; import android.content.res.CompatibilityInfo; -import android.content.res.Configuration; import android.content.res.Resources; import android.os.Build; import android.os.Bundle; -import android.os.Environment; import android.os.FileUtils; import android.os.Handler; import android.os.IBinder; @@ -49,13 +48,15 @@ import android.text.TextUtils; import android.util.AndroidRuntimeException; import android.util.ArrayMap; import android.util.Log; -import android.util.LogPrinter; import android.util.Slog; import android.util.SparseArray; import android.view.Display; import android.view.DisplayAdjustments; + import com.android.internal.util.ArrayUtils; + import dalvik.system.VMRuntime; + import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -218,7 +219,7 @@ public final class LoadedApk { } private AppComponentFactory createAppFactory(ApplicationInfo appInfo, ClassLoader cl) { - if (appInfo.appComponentFactory != null) { + if (appInfo.appComponentFactory != null && cl != null) { try { return (AppComponentFactory) cl.loadClass(appInfo.appComponentFactory) .newInstance(); @@ -612,6 +613,7 @@ public final class LoadedApk { } else { mClassLoader = ClassLoader.getSystemClassLoader(); } + mAppComponentFactory = createAppFactory(mApplicationInfo, mClassLoader); return; } @@ -686,6 +688,7 @@ public final class LoadedApk { librarySearchPath, libraryPermittedPath, mBaseClassLoader, null /* classLoaderName */); StrictMode.setThreadPolicy(oldPolicy); + mAppComponentFactory = createAppFactory(mApplicationInfo, mClassLoader); } return; @@ -713,6 +716,7 @@ public final class LoadedApk { mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath, libraryPermittedPath, mBaseClassLoader, mApplicationInfo.classLoaderName); + mAppComponentFactory = createAppFactory(mApplicationInfo, mClassLoader); StrictMode.setThreadPolicy(oldPolicy); // Setup the class loader paths for profiling. @@ -749,13 +753,6 @@ public final class LoadedApk { } } - // Keep in sync with installd (frameworks/native/cmds/installd/commands.cpp). - private static File getPrimaryProfileFile(String packageName) { - File profileDir = Environment.getDataProfilesDePackageDirectory( - UserHandle.myUserId(), packageName); - return new File(profileDir, "primary.prof"); - } - private void setupJitProfileSupport() { if (!SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false)) { return; @@ -783,10 +780,12 @@ public final class LoadedApk { return; } - final File profileFile = getPrimaryProfileFile(mPackageName); - - VMRuntime.registerAppInfo(profileFile.getPath(), - codePaths.toArray(new String[codePaths.size()])); + for (int i = codePaths.size() - 1; i >= 0; i--) { + String splitName = i == 0 ? null : mApplicationInfo.splitNames[i - 1]; + String profileFile = ArtManager.getCurrentProfilePath( + mPackageName, UserHandle.myUserId(), splitName); + VMRuntime.registerAppInfo(profileFile, new String[] {codePaths.get(i)}); + } // Register the app data directory with the reporter. It will // help deciding whether or not a dex file is the primary apk or a @@ -967,78 +966,14 @@ public final class LoadedApk { throw new AssertionError("null split not found"); } - mResources = ResourcesManager.getInstance().getResources( - null, - mResDir, - splitPaths, - mOverlayDirs, - mApplicationInfo.sharedLibraryFiles, - Display.DEFAULT_DISPLAY, - null, - getCompatibilityInfo(), + mResources = ResourcesManager.getInstance().getResources(null, mResDir, + splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles, + Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(), getClassLoader()); } return mResources; } - public Resources getOrCreateResourcesForSplit(@NonNull String splitName, - @Nullable IBinder activityToken, int displayId) throws NameNotFoundException { - return ResourcesManager.getInstance().getResources( - activityToken, - mResDir, - getSplitPaths(splitName), - mOverlayDirs, - mApplicationInfo.sharedLibraryFiles, - displayId, - null, - getCompatibilityInfo(), - getSplitClassLoader(splitName)); - } - - /** - * Creates the top level resources for the given package. Will return an existing - * Resources if one has already been created. - */ - public Resources getOrCreateTopLevelResources(@NonNull ApplicationInfo appInfo) { - // Request for this app, short circuit - if (appInfo.uid == Process.myUid()) { - return getResources(); - } - - // Get resources for a different package - return ResourcesManager.getInstance().getResources( - null, - appInfo.publicSourceDir, - appInfo.splitPublicSourceDirs, - appInfo.resourceDirs, - appInfo.sharedLibraryFiles, - Display.DEFAULT_DISPLAY, - null, - getCompatibilityInfo(), - getClassLoader()); - } - - public Resources createResources(IBinder activityToken, String splitName, - int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo) { - final String[] splitResDirs; - final ClassLoader classLoader; - try { - splitResDirs = getSplitPaths(splitName); - classLoader = getSplitClassLoader(splitName); - } catch (NameNotFoundException e) { - throw new RuntimeException(e); - } - return ResourcesManager.getInstance().getResources(activityToken, - mResDir, - splitResDirs, - mOverlayDirs, - mApplicationInfo.sharedLibraryFiles, - displayId, - overrideConfig, - compatInfo, - classLoader); - } - public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) { if (mApplication != null) { diff --git a/android/app/LocalActivityManager.java b/android/app/LocalActivityManager.java index 998ac5f2..e297719f 100644 --- a/android/app/LocalActivityManager.java +++ b/android/app/LocalActivityManager.java @@ -16,6 +16,8 @@ package android.app; +import android.app.ActivityThread.ActivityClientRecord; +import android.app.servertransaction.PendingTransactionActions; import android.content.Intent; import android.content.pm.ActivityInfo; import android.os.Binder; @@ -141,6 +143,21 @@ public class LocalActivityManager { } r.window = r.activity.getWindow(); r.instanceState = null; + + final ActivityClientRecord clientRecord = mActivityThread.getActivityClient(r); + final PendingTransactionActions pendingActions; + + if (!r.activity.mFinished) { + // This matches pending actions set in ActivityThread#handleLaunchActivity + pendingActions = new PendingTransactionActions(); + pendingActions.setOldState(clientRecord.state); + pendingActions.setRestoreInstanceState(true); + pendingActions.setCallOnPostCreate(true); + } else { + pendingActions = null; + } + + mActivityThread.handleStartActivity(clientRecord, pendingActions); r.curState = STARTED; if (desiredState == RESUMED) { @@ -207,8 +224,8 @@ public class LocalActivityManager { private void performPause(LocalActivityRecord r, boolean finishing) { final boolean needState = r.instanceState == null; - final Bundle instanceState = mActivityThread.performPauseActivity( - r, finishing, needState, "performPause", null /* pendingActions */); + final Bundle instanceState = mActivityThread.performPauseActivity(r, finishing, + "performPause", null /* pendingActions */); if (needState) { r.instanceState = instanceState; } @@ -363,7 +380,7 @@ public class LocalActivityManager { } if (localLOGV) Log.v(TAG, r.id + ": destroying"); mActivityThread.performDestroyActivity(r, finish, 0 /* configChanges */, - false /* getNonConfigInstance */); + false /* getNonConfigInstance */, "LocalActivityManager::performDestroy"); r.activity = null; r.window = null; if (finish) { @@ -628,7 +645,7 @@ public class LocalActivityManager { LocalActivityRecord r = mActivityArray.get(i); if (localLOGV) Log.v(TAG, r.id + ": destroying"); mActivityThread.performDestroyActivity(r, finishing, 0 /* configChanges */, - false /* getNonConfigInstance */); + false /* getNonConfigInstance */, "LocalActivityManager::dispatchDestroy"); } mActivities.clear(); mActivityArray.clear(); diff --git a/android/app/Notification.java b/android/app/Notification.java index d6fddfca..4326ee3e 100644 --- a/android/app/Notification.java +++ b/android/app/Notification.java @@ -89,6 +89,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.Set; /** @@ -200,6 +201,16 @@ public class Notification implements Parcelable */ private static final int MAX_REPLY_HISTORY = 5; + + /** + * If the notification contained an unsent draft for a RemoteInput when the user clicked on it, + * we're adding the draft as a String extra to the {@link #contentIntent} using this key. + * + * <p>Apps may use this extra to prepopulate text fields in the app, where the user usually + * sends messages.</p> + */ + public static final String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft"; + /** * A timestamp related to this notification, in milliseconds since the epoch. * @@ -350,6 +361,23 @@ public class Notification implements Parcelable @Deprecated public RemoteViews headsUpContentView; + private boolean mUsesStandardHeader; + + private static final ArraySet<Integer> STANDARD_LAYOUTS = new ArraySet<>(); + static { + STANDARD_LAYOUTS.add(R.layout.notification_template_material_base); + STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_base); + STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_picture); + STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_text); + STANDARD_LAYOUTS.add(R.layout.notification_template_material_inbox); + STANDARD_LAYOUTS.add(R.layout.notification_template_material_messaging); + STANDARD_LAYOUTS.add(R.layout.notification_template_material_media); + STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_media); + STANDARD_LAYOUTS.add(R.layout.notification_template_ambient_header); + STANDARD_LAYOUTS.add(R.layout.notification_template_header); + STANDARD_LAYOUTS.add(R.layout.notification_template_material_ambient); + } + /** * A large bitmap to be shown in the notification content area. * @@ -483,7 +511,7 @@ public class Notification implements Parcelable * </ul> * <p> * Since hardware varies, you are not guaranteed that any of the values - * you pass are honored exactly. Use the system defaults (TODO) if possible + * you pass are honored exactly. Use the system defaults if possible * because they will be set to values that work on any given hardware. * <p> * The alpha channel must be set for forward compatibility. @@ -736,6 +764,11 @@ public class Notification implements Parcelable public static final String CATEGORY_CALL = "call"; /** + * Notification category: map turn-by-turn navigation. + */ + public static final String CATEGORY_NAVIGATION = "navigation"; + + /** * Notification category: incoming direct message (SMS, instant message, etc.). */ public static final String CATEGORY_MESSAGE = "msg"; @@ -808,6 +841,27 @@ public class Notification implements Parcelable public static final String CATEGORY_REMINDER = "reminder"; /** + * Notification category: extreme car emergencies. + * @hide + */ + @SystemApi + public static final String CATEGORY_CAR_EMERGENCY = "car_emergency"; + + /** + * Notification category: car warnings. + * @hide + */ + @SystemApi + public static final String CATEGORY_CAR_WARNING = "car_warning"; + + /** + * Notification category: general car system information. + * @hide + */ + @SystemApi + public static final String CATEGORY_CAR_INFORMATION = "car_information"; + + /** * One of the predefined notification categories (see the <code>CATEGORY_*</code> constants) * that best describes this Notification. May be used by the system for ranking and filtering. */ @@ -1045,11 +1099,10 @@ public class Notification implements Parcelable /** * {@link #extras} key: A * {@link android.content.ContentUris content URI} pointing to an image that can be displayed - * in the background when the notification is selected. The URI must point to an image stream - * suitable for passing into + * in the background when the notification is selected. Used on television platforms. + * The URI must point to an image stream suitable for passing into * {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream) - * BitmapFactory.decodeStream}; all other content types will be ignored. The content provider - * URI used for this purpose must require no permissions to read the image data. + * BitmapFactory.decodeStream}; all other content types will be ignored. */ public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri"; @@ -1166,11 +1219,11 @@ public class Notification implements Parcelable public static final String EXTRA_SUBSTITUTE_APP_NAME = "android.substName"; /** - * This is set on the notification shown by the activity manager about all apps - * running in the background. It indicates that the notification should be shown - * only if any of the given apps do not already have a {@link #FLAG_FOREGROUND_SERVICE} - * notification currently visible to the user. This is a string array of all - * package names of the apps. + * This is set on the notifications shown by system_server about apps running foreground + * services. It indicates that the notification should be shown + * only if any of the given apps do not already have a properly tagged + * {@link #FLAG_FOREGROUND_SERVICE} notification currently visible to the user. + * This is a string array of all package names of the apps. * @hide */ public static final String EXTRA_FOREGROUND_APPS = "android.foregroundApps"; @@ -1323,6 +1376,10 @@ public class Notification implements Parcelable */ public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9; + /** + * {@code SemanticAction}: Call a contact, group, etc. + */ + public static final int SEMANTIC_ACTION_CALL = 10; private final Bundle mExtras; private Icon mIcon; @@ -1812,6 +1869,7 @@ public class Notification implements Parcelable * @param label the label to display while the action is being prepared to execute * @return this object for method chaining */ + @Deprecated public WearableExtender setInProgressLabel(CharSequence label) { mInProgressLabel = label; return this; @@ -1823,6 +1881,7 @@ public class Notification implements Parcelable * * @return the label to display while the action is being prepared to execute */ + @Deprecated public CharSequence getInProgressLabel() { return mInProgressLabel; } @@ -1834,6 +1893,7 @@ public class Notification implements Parcelable * @param label the label to confirm the action should be executed * @return this object for method chaining */ + @Deprecated public WearableExtender setConfirmLabel(CharSequence label) { mConfirmLabel = label; return this; @@ -1845,6 +1905,7 @@ public class Notification implements Parcelable * * @return the label to confirm the action should be executed */ + @Deprecated public CharSequence getConfirmLabel() { return mConfirmLabel; } @@ -1856,6 +1917,7 @@ public class Notification implements Parcelable * @param label the label to display to cancel the action * @return this object for method chaining */ + @Deprecated public WearableExtender setCancelLabel(CharSequence label) { mCancelLabel = label; return this; @@ -1867,6 +1929,7 @@ public class Notification implements Parcelable * * @return the label to display to cancel the action */ + @Deprecated public CharSequence getCancelLabel() { return mCancelLabel; } @@ -1937,7 +2000,8 @@ public class Notification implements Parcelable SEMANTIC_ACTION_MUTE, SEMANTIC_ACTION_UNMUTE, SEMANTIC_ACTION_THUMBS_UP, - SEMANTIC_ACTION_THUMBS_DOWN + SEMANTIC_ACTION_THUMBS_DOWN, + SEMANTIC_ACTION_CALL }) @Retention(RetentionPolicy.SOURCE) public @interface SemanticAction {} @@ -2514,6 +2578,8 @@ public class Notification implements Parcelable } parcel.writeInt(mGroupAlertBehavior); + + // mUsesStandardHeader is not written because it should be recomputed in listeners } /** @@ -2534,6 +2600,80 @@ public class Notification implements Parcelable }; /** + * @hide + */ + public static boolean areActionsVisiblyDifferent(Notification first, Notification second) { + Notification.Action[] firstAs = first.actions; + Notification.Action[] secondAs = second.actions; + if (firstAs == null && secondAs != null || firstAs != null && secondAs == null) { + return true; + } + if (firstAs != null && secondAs != null) { + if (firstAs.length != secondAs.length) { + return true; + } + for (int i = 0; i < firstAs.length; i++) { + if (!Objects.equals(firstAs[i].title, secondAs[i].title)) { + return true; + } + RemoteInput[] firstRs = firstAs[i].getRemoteInputs(); + RemoteInput[] secondRs = secondAs[i].getRemoteInputs(); + if (firstRs == null) { + firstRs = new RemoteInput[0]; + } + if (secondRs == null) { + secondRs = new RemoteInput[0]; + } + if (firstRs.length != secondRs.length) { + return true; + } + for (int j = 0; j < firstRs.length; j++) { + if (!Objects.equals(firstRs[j].getLabel(), secondRs[j].getLabel())) { + return true; + } + CharSequence[] firstCs = firstRs[j].getChoices(); + CharSequence[] secondCs = secondRs[j].getChoices(); + if (firstCs == null) { + firstCs = new CharSequence[0]; + } + if (secondCs == null) { + secondCs = new CharSequence[0]; + } + if (firstCs.length != secondCs.length) { + return true; + } + for (int k = 0; k < firstCs.length; k++) { + if (!Objects.equals(firstCs[k], secondCs[k])) { + return true; + } + } + } + } + } + return false; + } + + /** + * @hide + */ + public static boolean areStyledNotificationsVisiblyDifferent(Builder first, Builder second) { + if (first.getStyle() == null) { + return second.getStyle() != null; + } + if (second.getStyle() == null) { + return true; + } + return first.getStyle().areNotificationsVisiblyDifferent(second.getStyle()); + } + + /** + * @hide + */ + public static boolean areRemoteViewsChanged(Builder first, Builder second) { + return !first.usesStandardHeader() || !second.usesStandardHeader(); + } + + /** * Parcelling creates multiple copies of objects in {@code extras}. Fix them. * <p> * For backwards compatibility {@code extras} holds some references to "real" member data such @@ -2978,7 +3118,6 @@ public class Notification implements Parcelable private int mActionBarColor = COLOR_INVALID; private int mBackgroundColor = COLOR_INVALID; private int mForegroundColor = COLOR_INVALID; - private int mBackgroundColorHint = COLOR_INVALID; /** * A temporary location where actions are stored. If != null the view originally has action * but doesn't have any for this inflation. @@ -3974,6 +4113,13 @@ public class Notification implements Parcelable } /** + * Returns the style set by {@link #setStyle(Style)}. + */ + public Style getStyle() { + return mStyle; + } + + /** * Specify the value of {@link #visibility}. * * @return The same Builder. @@ -4072,6 +4218,25 @@ public class Notification implements Parcelable } } + /** + * @hide + */ + public boolean usesStandardHeader() { + if (mN.mUsesStandardHeader) { + return true; + } + if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) { + if (mN.contentView == null && mN.bigContentView == null) { + return true; + } + } + boolean contentViewUsesHeader = mN.contentView == null + || STANDARD_LAYOUTS.contains(mN.contentView.getLayoutId()); + boolean bigContentViewUsesHeader = mN.bigContentView == null + || STANDARD_LAYOUTS.contains(mN.bigContentView.getLayoutId()); + return contentViewUsesHeader && bigContentViewUsesHeader; + } + private void resetStandardTemplate(RemoteViews contentView) { resetNotificationHeader(contentView); resetContentMargins(contentView); @@ -4103,6 +4268,7 @@ public class Notification implements Parcelable contentView.setViewVisibility(R.id.time, View.GONE); contentView.setImageViewIcon(R.id.profile_badge, null); contentView.setViewVisibility(R.id.profile_badge, View.GONE); + mN.mUsesStandardHeader = false; } private void resetContentMargins(RemoteViews contentView) { @@ -4220,8 +4386,7 @@ public class Notification implements Parcelable backgroundColor); mSecondaryTextColor = NotificationColorUtil.resolveSecondaryColor(mContext, backgroundColor); - if (backgroundColor != COLOR_DEFAULT - && (mBackgroundColorHint != COLOR_INVALID || isColorized())) { + if (backgroundColor != COLOR_DEFAULT && isColorized()) { mPrimaryTextColor = NotificationColorUtil.findAlphaToMeetContrast( mPrimaryTextColor, backgroundColor, 4.5); mSecondaryTextColor = NotificationColorUtil.findAlphaToMeetContrast( @@ -4424,24 +4589,17 @@ public class Notification implements Parcelable bindProfileBadge(contentView); } bindExpandButton(contentView); + mN.mUsesStandardHeader = true; } private void bindExpandButton(RemoteViews contentView) { - int color = getPrimaryHighlightColor(); + int color = isColorized() ? getPrimaryTextColor() : getSecondaryTextColor(); contentView.setDrawableTint(R.id.expand_button, false, color, PorterDuff.Mode.SRC_ATOP); contentView.setInt(R.id.notification_header, "setOriginalNotificationColor", color); } - /** - * @return the color that is used as the first primary highlight color. This is applied - * in several places like the action buttons or the app name in the header. - */ - private int getPrimaryHighlightColor() { - return isColorized() ? getPrimaryTextColor() : resolveContrastColor(); - } - private void bindHeaderChronometerAndTime(RemoteViews contentView) { if (showsTimeOrChronometer()) { contentView.setViewVisibility(R.id.time_divider, View.VISIBLE); @@ -4538,7 +4696,7 @@ public class Notification implements Parcelable setTextViewColorPrimary(contentView, R.id.app_name_text); } else { contentView.setTextColor(R.id.app_name_text, - ambient ? resolveAmbientColor() : resolveContrastColor()); + ambient ? resolveAmbientColor() : getSecondaryTextColor()); } } @@ -4573,7 +4731,8 @@ public class Notification implements Parcelable big.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE); big.setTextViewText(R.id.notification_material_reply_text_3, null); - big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, 0); + big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, + R.dimen.notification_content_margin); } private RemoteViews applyStandardTemplateWithActions(int layoutId) { @@ -4594,23 +4753,19 @@ public class Notification implements Parcelable if (N > 0) { big.setViewVisibility(R.id.actions_container, View.VISIBLE); big.setViewVisibility(R.id.actions, View.VISIBLE); - if (p.ambient) { - big.setInt(R.id.actions, "setBackgroundColor", Color.TRANSPARENT); - } else if (isColorized()) { - big.setInt(R.id.actions, "setBackgroundColor", getActionBarColor()); - } else { - big.setInt(R.id.actions, "setBackgroundColor", mContext.getColor( - R.color.notification_action_list)); - } - big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, - R.dimen.notification_action_list_height); + big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, 0); if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS; for (int i=0; i<N; i++) { Action action = mActions.get(i); - validRemoteInput |= hasValidRemoteInput(action); + boolean actionHasValidInput = hasValidRemoteInput(action); + validRemoteInput |= actionHasValidInput; final RemoteViews button = generateActionButton(action, emphazisedMode, i % 2 != 0, p.ambient); + if (actionHasValidInput) { + // Clear the drawable + button.setInt(R.id.action0, "setBackgroundResource", 0); + } big.addView(R.id.actions, button); } } else { @@ -5069,7 +5224,14 @@ public class Notification implements Parcelable private void processSmallIconColor(Icon smallIcon, RemoteViews contentView, boolean ambient) { boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon); - int color = ambient ? resolveAmbientColor() : getPrimaryHighlightColor(); + int color; + if (ambient) { + color = resolveAmbientColor(); + } else if (isColorized()) { + color = getPrimaryTextColor(); + } else { + color = resolveContrastColor(); + } if (colorable) { contentView.setDrawableTint(R.id.icon, false, color, PorterDuff.Mode.SRC_ATOP); @@ -5105,14 +5267,11 @@ public class Notification implements Parcelable } int color; - int background = mBackgroundColorHint; - if (mBackgroundColorHint == COLOR_INVALID) { - background = mContext.getColor( - com.android.internal.R.color.notification_material_background_color); - } + int background = mContext.getColor( + com.android.internal.R.color.notification_material_background_color); if (mN.color == COLOR_DEFAULT) { ensureColors(); - color = mSecondaryTextColor; + color = NotificationColorUtil.resolveDefaultColor(mContext, background); } else { color = NotificationColorUtil.resolveContrastColor(mContext, mN.color, background, mInNightMode); @@ -5207,6 +5366,7 @@ public class Notification implements Parcelable if (mStyle != null) { mStyle.reduceImageSizes(mContext); mStyle.purgeResources(); + mStyle.validate(mContext); mStyle.buildStyled(mN); } mN.reduceImageSizes(mContext); @@ -5351,8 +5511,7 @@ public class Notification implements Parcelable if (isColorized()) { return mBackgroundColor != COLOR_INVALID ? mBackgroundColor : mN.color; } else { - return mBackgroundColorHint != COLOR_INVALID ? mBackgroundColorHint - : COLOR_DEFAULT; + return COLOR_DEFAULT; } } @@ -5389,18 +5548,6 @@ public class Notification implements Parcelable } /** - * Sets the background color for this notification to be a different one then the default. - * This is mainly used to calculate contrast and won't necessarily be applied to the - * background. - * - * @hide - */ - public void setBackgroundColorHint(int backgroundColor) { - mBackgroundColorHint = backgroundColor; - } - - - /** * Forces all styled remoteViews to be built from scratch and not use any cached * RemoteViews. * This is needed for legacy apps that are baking in their remoteviews into the @@ -5411,6 +5558,24 @@ public class Notification implements Parcelable public void setRebuildStyledRemoteViews(boolean rebuild) { mRebuildStyledRemoteViews = rebuild; } + + /** + * Get the text that should be displayed in the statusBar when heads upped. This is + * usually just the app name, but may be different depending on the style. + * + * @param publicMode If true, return a text that is safe to display in public. + * + * @hide + */ + public CharSequence getHeadsUpStatusBarText(boolean publicMode) { + if (mStyle != null && !publicMode) { + CharSequence text = mStyle.getHeadsUpStatusBarText(); + if (!TextUtils.isEmpty(text)) { + return text; + } + } + return loadHeaderAppName(); + } } /** @@ -5776,6 +5941,28 @@ public class Notification implements Parcelable */ public void reduceImageSizes(Context context) { } + + /** + * Validate that this style was properly composed. This is called at build time. + * @hide + */ + public void validate(Context context) { + } + + /** + * @hide + */ + public abstract boolean areNotificationsVisiblyDifferent(Style other); + + /** + * @return the the text that should be displayed in the statusBar when heads-upped. + * If {@code null} is returned, the default implementation will be used. + * + * @hide + */ + public CharSequence getHeadsUpStatusBarText() { + return null; + } } /** @@ -5829,6 +6016,13 @@ public class Notification implements Parcelable } /** + * @hide + */ + public Bitmap getBigPicture() { + return mPicture; + } + + /** * Provide the bitmap to be used as the payload for the BigPicture notification. */ public BigPictureStyle bigPicture(Bitmap b) { @@ -5968,6 +6162,18 @@ public class Notification implements Parcelable public boolean hasSummaryInHeader() { return false; } + + /** + * @hide + */ + @Override + public boolean areNotificationsVisiblyDifferent(Style other) { + if (other == null || getClass() != other.getClass()) { + return true; + } + BigPictureStyle otherS = (BigPictureStyle) other; + return !Objects.equals(getBigPicture(), otherS.getBigPicture()); + } } /** @@ -6031,6 +6237,13 @@ public class Notification implements Parcelable /** * @hide */ + public CharSequence getBigText() { + return mBigText; + } + + /** + * @hide + */ public void addExtras(Bundle extras) { super.addExtras(extras); @@ -6100,6 +6313,18 @@ public class Notification implements Parcelable return contentView; } + /** + * @hide + */ + @Override + public boolean areNotificationsVisiblyDifferent(Style other) { + if (other == null || getClass() != other.getClass()) { + return true; + } + BigTextStyle newS = (BigTextStyle) other; + return !Objects.equals(getBigText(), newS.getBigText()); + } + static void applyBigTextContentView(Builder builder, RemoteViews contentView, CharSequence bigTextText) { contentView.setTextViewText(R.id.big_text, builder.processTextSpans(bigTextText)); @@ -6166,13 +6391,41 @@ public class Notification implements Parcelable * @param user Required - The person displayed for any messages that are sent by the * user. Any messages added with {@link #addMessage(Notification.MessagingStyle.Message)} * who don't have a Person associated with it will be displayed as if they were sent - * by this user. The user also needs to have a valid name associated with it. + * by this user. The user also needs to have a valid name associated with it, which will + * be enforced starting in Android P. */ public MessagingStyle(@NonNull Person user) { mUser = user; - if (user == null || user.getName() == null) { - throw new RuntimeException("user must be valid and have a name"); + } + + /** + * Validate that this style was properly composed. This is called at build time. + * @hide + */ + @Override + public void validate(Context context) { + super.validate(context); + if (context.getApplicationInfo().targetSdkVersion + >= Build.VERSION_CODES.P && (mUser == null || mUser.getName() == null)) { + throw new RuntimeException("User must be valid and have a name."); + } + } + + /** + * @return the the text that should be displayed in the statusBar when heads upped. + * If {@code null} is returned, the default implementation will be used. + * + * @hide + */ + @Override + public CharSequence getHeadsUpStatusBarText() { + CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle) + ? super.mBigContentTitle + : mConversationTitle; + if (!TextUtils.isEmpty(conversationTitle) && !hasOnlyWhiteSpaceSenders()) { + return conversationTitle; } + return null; } /** @@ -6425,12 +6678,64 @@ public class Notification implements Parcelable public RemoteViews makeContentView(boolean increasedHeight) { mBuilder.mOriginalActions = mBuilder.mActions; mBuilder.mActions = new ArrayList<>(); - RemoteViews remoteViews = makeBigContentView(true /* showRightIcon */); + RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */); mBuilder.mActions = mBuilder.mOriginalActions; mBuilder.mOriginalActions = null; return remoteViews; } + /** + * @hide + */ + @Override + public boolean areNotificationsVisiblyDifferent(Style other) { + if (other == null || getClass() != other.getClass()) { + return true; + } + MessagingStyle newS = (MessagingStyle) other; + List<MessagingStyle.Message> oldMs = getMessages(); + List<MessagingStyle.Message> newMs = newS.getMessages(); + + if (oldMs == null) { + oldMs = new ArrayList<>(); + } + if (newMs == null) { + newMs = new ArrayList<>(); + } + + int n = oldMs.size(); + if (n != newMs.size()) { + return true; + } + for (int i = 0; i < n; i++) { + MessagingStyle.Message oldM = oldMs.get(i); + MessagingStyle.Message newM = newMs.get(i); + if (!Objects.equals(oldM.getText(), newM.getText())) { + return true; + } + if (!Objects.equals(oldM.getDataUri(), newM.getDataUri())) { + return true; + } + CharSequence oldSender = oldM.getSenderPerson() == null ? oldM.getSender() + : oldM.getSenderPerson().getName(); + CharSequence newSender = newM.getSenderPerson() == null ? newM.getSender() + : newM.getSenderPerson().getName(); + if (!Objects.equals(oldSender, newSender)) { + return true; + } + + String oldKey = oldM.getSenderPerson() == null + ? null : oldM.getSenderPerson().getKey(); + String newKey = newM.getSenderPerson() == null + ? null : newM.getSenderPerson().getKey(); + if (!Objects.equals(oldKey, newKey)) { + return true; + } + // Other fields (like timestamp) intentionally excluded + } + return false; + } + private Message findLatestIncomingMessage() { return findLatestIncomingMessage(mMessages); } @@ -6460,11 +6765,11 @@ public class Notification implements Parcelable */ @Override public RemoteViews makeBigContentView() { - return makeBigContentView(false /* showRightIcon */); + return makeMessagingView(false /* isCollapsed */); } @NonNull - private RemoteViews makeBigContentView(boolean showRightIcon) { + private RemoteViews makeMessagingView(boolean isCollapsed) { CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle) ? super.mBigContentTitle : mConversationTitle; @@ -6475,21 +6780,24 @@ public class Notification implements Parcelable nameReplacement = conversationTitle; conversationTitle = null; } + boolean hideLargeIcon = !isCollapsed || isOneToOne; RemoteViews contentView = mBuilder.applyStandardTemplateWithActions( mBuilder.getMessagingLayoutResource(), mBuilder.mParams.reset().hasProgress(false).title(conversationTitle).text(null) - .hideLargeIcon(!showRightIcon || isOneToOne) + .hideLargeIcon(hideLargeIcon) .headerTextSecondary(conversationTitle) - .alwaysShowReply(showRightIcon)); + .alwaysShowReply(isCollapsed)); addExtras(mBuilder.mN.extras); // also update the end margin if there is an image int endMargin = R.dimen.notification_content_margin_end; - if (mBuilder.mN.hasLargeIcon() && showRightIcon) { + if (isCollapsed) { endMargin = R.dimen.notification_content_plus_picture_margin_end; } contentView.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin); contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", mBuilder.resolveContrastColor()); + contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed", + isCollapsed); contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon", mBuilder.mN.mLargeIcon); contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement", @@ -6556,7 +6864,7 @@ public class Notification implements Parcelable */ @Override public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { - RemoteViews remoteViews = makeBigContentView(true /* showRightIcon */); + RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */); remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1); return remoteViews; } @@ -6859,6 +7167,13 @@ public class Notification implements Parcelable /** * @hide */ + public ArrayList<CharSequence> getLines() { + return mTexts; + } + + /** + * @hide + */ public void addExtras(Bundle extras) { super.addExtras(extras); @@ -6937,6 +7252,18 @@ public class Notification implements Parcelable return contentView; } + /** + * @hide + */ + @Override + public boolean areNotificationsVisiblyDifferent(Style other) { + if (other == null || getClass() != other.getClass()) { + return true; + } + InboxStyle newS = (InboxStyle) other; + return !Objects.equals(getLines(), newS.getLines()); + } + private void handleInboxImageMargin(RemoteViews contentView, int id, boolean first) { int endMargin = 0; if (first) { @@ -7100,6 +7427,18 @@ public class Notification implements Parcelable } } + /** + * @hide + */ + @Override + public boolean areNotificationsVisiblyDifferent(Style other) { + if (other == null || getClass() != other.getClass()) { + return true; + } + // All fields to compare are on the Notification object + return false; + } + private RemoteViews generateMediaActionButton(Action action, int color) { final boolean tombstone = (action.actionIntent == null); RemoteViews button = new BuilderRemoteViews(mBuilder.mContext.getApplicationInfo(), @@ -7140,8 +7479,7 @@ public class Notification implements Parcelable } final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]); - final RemoteViews button = generateMediaActionButton(action, - getPrimaryHighlightColor()); + final RemoteViews button = generateMediaActionButton(action, getActionColor()); view.addView(com.android.internal.R.id.media_actions, button); } } @@ -7155,8 +7493,9 @@ public class Notification implements Parcelable return view; } - private int getPrimaryHighlightColor() { - return mBuilder.getPrimaryHighlightColor(); + private int getActionColor() { + return mBuilder.isColorized() ? mBuilder.getPrimaryTextColor() + : mBuilder.resolveContrastColor(); } private RemoteViews makeMediaBigContentView() { @@ -7176,7 +7515,7 @@ public class Notification implements Parcelable big.removeAllViews(com.android.internal.R.id.media_actions); for (int i = 0; i < actionCount; i++) { final RemoteViews button = generateMediaActionButton(mBuilder.mActions.get(i), - getPrimaryHighlightColor()); + getActionColor()); big.addView(com.android.internal.R.id.media_actions, button); } } @@ -7309,6 +7648,18 @@ public class Notification implements Parcelable } remoteViews.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin); } + + /** + * @hide + */ + @Override + public boolean areNotificationsVisiblyDifferent(Style other) { + if (other == null || getClass() != other.getClass()) { + return true; + } + // Comparison done for all custom RemoteViews, independent of style + return false; + } } /** @@ -7399,6 +7750,18 @@ public class Notification implements Parcelable return makeBigContentViewWithCustomContent(customRemoteView); } + /** + * @hide + */ + @Override + public boolean areNotificationsVisiblyDifferent(Style other) { + if (other == null || getClass() != other.getClass()) { + return true; + } + // Comparison done for all custom RemoteViews, independent of style + return false; + } + private RemoteViews buildIntoRemoteView(RemoteViews remoteViews, int id, RemoteViews customContent) { if (customContent != null) { @@ -7422,6 +7785,8 @@ public class Notification implements Parcelable @Nullable private Icon mIcon; @Nullable private String mUri; @Nullable private String mKey; + private boolean mBot; + private boolean mImportant; protected Person(Parcel in) { mName = in.readCharSequence(); @@ -7430,6 +7795,8 @@ public class Notification implements Parcelable } mUri = in.readString(); mKey = in.readString(); + mImportant = in.readBoolean(); + mBot = in.readBoolean(); } /** @@ -7506,6 +7873,27 @@ public class Notification implements Parcelable return this; } + /** + * Sets whether this is an important person. Use this method to denote users who frequently + * interact with the user of this device, when it is not possible to refer to the user + * by {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}. + * + * @param isImportant {@code true} if this is an important person, {@code false} otherwise. + */ + public Person setImportant(boolean isImportant) { + mImportant = isImportant; + return this; + } + + /** + * Sets whether this person is a machine rather than a human. + * + * @param isBot {@code true} if this person is a machine, {@code false} otherwise. + */ + public Person setBot(boolean isBot) { + mBot = isBot; + return this; + } /** * @return the uri provided for this person or {@code null} if no Uri was provided @@ -7540,6 +7928,20 @@ public class Notification implements Parcelable } /** + * @return whether this Person is a machine. + */ + public boolean isBot() { + return mBot; + } + + /** + * @return whether this Person is important. + */ + public boolean isImportant() { + return mImportant; + } + + /** * @return the URI associated with this person, or "name:mName" otherwise * @hide */ @@ -7569,6 +7971,8 @@ public class Notification implements Parcelable } dest.writeString(mUri); dest.writeString(mKey); + dest.writeBoolean(mImportant); + dest.writeBoolean(mBot); } public static final Creator<Person> CREATOR = new Creator<Person>() { @@ -8049,6 +8453,7 @@ public class Notification implements Parcelable /** * Set an icon that goes with the content of this notification. */ + @Deprecated public WearableExtender setContentIcon(int icon) { mContentIcon = icon; return this; @@ -8057,6 +8462,7 @@ public class Notification implements Parcelable /** * Get an icon that goes with the content of this notification. */ + @Deprecated public int getContentIcon() { return mContentIcon; } @@ -8067,6 +8473,7 @@ public class Notification implements Parcelable * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. * @see #setContentIcon */ + @Deprecated public WearableExtender setContentIconGravity(int contentIconGravity) { mContentIconGravity = contentIconGravity; return this; @@ -8078,6 +8485,7 @@ public class Notification implements Parcelable * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. * @see #getContentIcon */ + @Deprecated public int getContentIconGravity() { return mContentIconGravity; } @@ -8125,6 +8533,7 @@ public class Notification implements Parcelable * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}. * The default value is {@link android.view.Gravity#BOTTOM}. */ + @Deprecated public WearableExtender setGravity(int gravity) { mGravity = gravity; return this; @@ -8136,6 +8545,7 @@ public class Notification implements Parcelable * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}. * The default value is {@link android.view.Gravity#BOTTOM}. */ + @Deprecated public int getGravity() { return mGravity; } @@ -8149,6 +8559,7 @@ public class Notification implements Parcelable * documentation for the preset in question. See also * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}. */ + @Deprecated public WearableExtender setCustomSizePreset(int sizePreset) { mCustomSizePreset = sizePreset; return this; @@ -8162,6 +8573,7 @@ public class Notification implements Parcelable * using {@link #setDisplayIntent}. Check the documentation for the preset in question. * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}. */ + @Deprecated public int getCustomSizePreset() { return mCustomSizePreset; } @@ -8173,6 +8585,7 @@ public class Notification implements Parcelable * {@link android.app.Notification.WearableExtender#setCustomSizePreset} and * {@link #getCustomContentHeight}. */ + @Deprecated public WearableExtender setCustomContentHeight(int height) { mCustomContentHeight = height; return this; @@ -8184,6 +8597,7 @@ public class Notification implements Parcelable * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and * {@link #setCustomContentHeight}. */ + @Deprecated public int getCustomContentHeight() { return mCustomContentHeight; } @@ -8234,6 +8648,7 @@ public class Notification implements Parcelable * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise. * @return this object for method chaining */ + @Deprecated public WearableExtender setHintHideIcon(boolean hintHideIcon) { setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon); return this; @@ -8244,6 +8659,7 @@ public class Notification implements Parcelable * @return {@code true} if this icon should not be displayed, false otherwise. * The default value is {@code false} if this was never set. */ + @Deprecated public boolean getHintHideIcon() { return (mFlags & FLAG_HINT_HIDE_ICON) != 0; } @@ -8253,6 +8669,7 @@ public class Notification implements Parcelable * displayed, and other semantic content should be hidden. This hint is only applicable * to sub-pages added using {@link #addPage}. */ + @Deprecated public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) { setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly); return this; @@ -8263,6 +8680,7 @@ public class Notification implements Parcelable * displayed, and other semantic content should be hidden. This hint is only applicable * to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}. */ + @Deprecated public boolean getHintShowBackgroundOnly() { return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0; } @@ -8274,6 +8692,7 @@ public class Notification implements Parcelable * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible. * @return this object for method chaining */ + @Deprecated public WearableExtender setHintAvoidBackgroundClipping( boolean hintAvoidBackgroundClipping) { setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping); @@ -8287,6 +8706,7 @@ public class Notification implements Parcelable * @return {@code true} if it's ok if the background is clipped on the screen, false * otherwise. The default value is {@code false} if this was never set. */ + @Deprecated public boolean getHintAvoidBackgroundClipping() { return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0; } @@ -8298,6 +8718,7 @@ public class Notification implements Parcelable * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}. * @return this object for method chaining */ + @Deprecated public WearableExtender setHintScreenTimeout(int timeout) { mHintScreenTimeout = timeout; return this; @@ -8309,6 +8730,7 @@ public class Notification implements Parcelable * @return the duration in milliseconds if > 0, or either one of the sentinel values * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}. */ + @Deprecated public int getHintScreenTimeout() { return mHintScreenTimeout; } @@ -8845,6 +9267,7 @@ public class Notification implements Parcelable private static final String EXTRA_CONTENT_INTENT = "content_intent"; private static final String EXTRA_DELETE_INTENT = "delete_intent"; private static final String EXTRA_CHANNEL_ID = "channel_id"; + private static final String EXTRA_SUPPRESS_SHOW_OVER_APPS = "suppressShowOverApps"; // Flags bitwise-ored to mFlags private static final int FLAG_AVAILABLE_ON_TV = 0x1; @@ -8853,6 +9276,7 @@ public class Notification implements Parcelable private String mChannelId; private PendingIntent mContentIntent; private PendingIntent mDeleteIntent; + private boolean mSuppressShowOverApps; /** * Create a {@link TvExtender} with default options. @@ -8872,6 +9296,7 @@ public class Notification implements Parcelable if (bundle != null) { mFlags = bundle.getInt(EXTRA_FLAGS); mChannelId = bundle.getString(EXTRA_CHANNEL_ID); + mSuppressShowOverApps = bundle.getBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS); mContentIntent = bundle.getParcelable(EXTRA_CONTENT_INTENT); mDeleteIntent = bundle.getParcelable(EXTRA_DELETE_INTENT); } @@ -8888,6 +9313,7 @@ public class Notification implements Parcelable bundle.putInt(EXTRA_FLAGS, mFlags); bundle.putString(EXTRA_CHANNEL_ID, mChannelId); + bundle.putBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS, mSuppressShowOverApps); if (mContentIntent != null) { bundle.putParcelable(EXTRA_CONTENT_INTENT, mContentIntent); } @@ -8980,6 +9406,23 @@ public class Notification implements Parcelable public PendingIntent getDeleteIntent() { return mDeleteIntent; } + + /** + * Specifies whether this notification should suppress showing a message over top of apps + * outside of the launcher. + */ + public TvExtender setSuppressShowOverApps(boolean suppress) { + mSuppressShowOverApps = suppress; + return this; + } + + /** + * Returns true if this notification should not show messages over top of apps + * outside of the launcher. + */ + public boolean getSuppressShowOverApps() { + return mSuppressShowOverApps; + } } /** diff --git a/android/app/NotificationChannel.java b/android/app/NotificationChannel.java index 30f2697c..4a7cf623 100644 --- a/android/app/NotificationChannel.java +++ b/android/app/NotificationChannel.java @@ -463,7 +463,11 @@ public final class NotificationChannel implements Parcelable { /** * Returns the user specified importance e.g. {@link NotificationManager#IMPORTANCE_LOW} for - * notifications posted to this channel. + * notifications posted to this channel. Note: This value might be > + * {@link NotificationManager#IMPORTANCE_NONE}, but notifications posted to this channel will + * not be shown to the user if the parent {@link NotificationChannelGroup} or app is blocked. + * See {@link NotificationChannelGroup#isBlocked()} and + * {@link NotificationManager#areNotificationsEnabled()}. */ public int getImportance() { return mImportance; diff --git a/android/app/NotificationChannelGroup.java b/android/app/NotificationChannelGroup.java index 16166f7c..0fa3c7fa 100644 --- a/android/app/NotificationChannelGroup.java +++ b/android/app/NotificationChannelGroup.java @@ -145,7 +145,9 @@ public final class NotificationChannelGroup implements Parcelable { /** * Returns whether or not notifications posted to {@link NotificationChannel channels} belonging - * to this group are blocked. + * to this group are blocked. This value is independent of + * {@link NotificationManager#areNotificationsEnabled()} and + * {@link NotificationChannel#getImportance()}. */ public boolean isBlocked() { return mBlocked; diff --git a/android/app/NotificationManager.java b/android/app/NotificationManager.java index 49c03ab9..46d1264f 100644 --- a/android/app/NotificationManager.java +++ b/android/app/NotificationManager.java @@ -24,6 +24,7 @@ import android.annotation.TestApi; import android.app.Notification.Builder; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.pm.ParceledListSlice; import android.graphics.drawable.Icon; import android.net.Uri; @@ -98,7 +99,7 @@ public class NotificationManager { * This broadcast is only sent to the app whose block state has changed. * * Input: nothing - * Output: nothing + * Output: {@link #EXTRA_BLOCKED_STATE} */ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_APP_BLOCK_STATE_CHANGED = @@ -113,24 +114,31 @@ public class NotificationManager { * This broadcast is only sent to the app that owns the channel that has changed. * * Input: nothing - * Output: {@link #EXTRA_BLOCK_STATE_CHANGED_ID} + * Output: {@link #EXTRA_NOTIFICATION_CHANNEL_ID} + * Output: {@link #EXTRA_BLOCKED_STATE} */ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED"; /** - * Extra for {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} or - * {@link #ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED} containing the id of the - * object which has a new blocked state. + * Extra for {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} containing the id of the + * {@link NotificationChannel} which has a new blocked state. * - * The value will be the {@link NotificationChannel#getId()} of the channel for - * {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} and - * the {@link NotificationChannelGroup#getId()} of the group for - * {@link #ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED}. + * The value will be the {@link NotificationChannel#getId()} of the channel. */ - public static final String EXTRA_BLOCK_STATE_CHANGED_ID = - "android.app.extra.BLOCK_STATE_CHANGED_ID"; + public static final String EXTRA_NOTIFICATION_CHANNEL_ID = + "android.app.extra.NOTIFICATION_CHANNEL_ID"; + + /** + * Extra for {@link #ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED} containing the id + * of the {@link NotificationChannelGroup} which has a new blocked state. + * + * The value will be the {@link NotificationChannelGroup#getId()} of the group. + */ + public static final String EXTRA_NOTIFICATION_CHANNEL_GROUP_ID = + "android.app.extra.NOTIFICATION_CHANNEL_GROUP_ID"; + /** * Extra for {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} or @@ -142,7 +150,6 @@ public class NotificationManager { */ public static final String EXTRA_BLOCKED_STATE = "android.app.extra.BLOCKED_STATE"; - /** * Intent that is broadcast when a {@link NotificationChannelGroup} is * {@link NotificationChannelGroup#isBlocked() blocked} or unblocked. @@ -150,7 +157,8 @@ public class NotificationManager { * This broadcast is only sent to the app that owns the channel group that has changed. * * Input: nothing - * Output: {@link #EXTRA_BLOCK_STATE_CHANGED_ID} + * Output: {@link #EXTRA_NOTIFICATION_CHANNEL_GROUP_ID} + * Output: {@link #EXTRA_BLOCKED_STATE} */ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED = @@ -343,6 +351,14 @@ public class NotificationManager { * the same tag and id has already been posted by your application and has not yet been * canceled, it will be replaced by the updated information. * + * All {@link android.service.notification.NotificationListenerService listener services} will + * be granted {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} access to any {@link Uri uris} + * provided on this notification or the + * {@link NotificationChannel} this notification is posted to using + * {@link Context#grantUriPermission(String, Uri, int)}. Permission will be revoked when the + * notification is canceled, or you can revoke permissions with + * {@link Context#revokeUriPermission(Uri, int)}. + * * @param tag A string identifier for this notification. May be {@code null}. * @param id An identifier for this notification. The pair (tag, id) must be unique * within your application. @@ -351,7 +367,7 @@ public class NotificationManager { */ public void notify(String tag, int id, Notification notification) { - notifyAsUser(tag, id, notification, new UserHandle(UserHandle.myUserId())); + notifyAsUser(tag, id, notification, mContext.getUser()); } /** @@ -363,11 +379,13 @@ public class NotificationManager { String pkg = mContext.getPackageName(); // Fix the notification as best we can. Notification.addFieldsFromContext(mContext, notification); + if (notification.sound != null) { notification.sound = notification.sound.getCanonicalUri(); if (StrictMode.vmFileUriExposureEnabled()) { notification.sound.checkFileUriExposed("Notification.sound"); } + } fixLegacySmallIcon(notification, pkg); if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) { @@ -378,6 +396,7 @@ public class NotificationManager { } if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")"); notification.reduceImageSizes(mContext); + ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); boolean isLowRam = am.isLowRamDevice(); final Notification copy = Builder.maybeCloneStrippedForDelivery(notification, isLowRam); @@ -412,7 +431,7 @@ public class NotificationManager { */ public void cancel(String tag, int id) { - cancelAsUser(tag, id, new UserHandle(UserHandle.myUserId())); + cancelAsUser(tag, id, mContext.getUser()); } /** @@ -440,7 +459,7 @@ public class NotificationManager { String pkg = mContext.getPackageName(); if (localLOGV) Log.v(TAG, pkg + ": cancelAll()"); try { - service.cancelAllNotifications(pkg, UserHandle.myUserId()); + service.cancelAllNotifications(pkg, mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1012,12 +1031,18 @@ public class NotificationManager { public static final int PRIORITY_CATEGORY_REPEAT_CALLERS = 1 << 4; /** Alarms are prioritized */ public static final int PRIORITY_CATEGORY_ALARMS = 1 << 5; - /** Media, system, game (catch-all for non-never suppressible sounds) are prioritized */ - public static final int PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER = 1 << 6; + /** Media, game, voice navigation are prioritized */ + public static final int PRIORITY_CATEGORY_MEDIA = 1 << 6; + /**System (catch-all for non-never suppressible sounds) are prioritized */ + public static final int PRIORITY_CATEGORY_SYSTEM = 1 << 7; - private static final int[] ALL_PRIORITY_CATEGORIES = { + /** + * @hide + */ + public static final int[] ALL_PRIORITY_CATEGORIES = { PRIORITY_CATEGORY_ALARMS, - PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER, + PRIORITY_CATEGORY_MEDIA, + PRIORITY_CATEGORY_SYSTEM, PRIORITY_CATEGORY_REMINDERS, PRIORITY_CATEGORY_EVENTS, PRIORITY_CATEGORY_MESSAGES, @@ -1047,20 +1072,77 @@ public class NotificationManager { * @hide */ public static final int SUPPRESSED_EFFECTS_UNSET = -1; + /** * Whether notifications suppressed by DND should not interrupt visually (e.g. with * notification lights or by turning the screen on) when the screen is off. + * + * @deprecated use {@link #SUPPRESSED_EFFECT_FULL_SCREEN_INTENT} and + * {@link #SUPPRESSED_EFFECT_AMBIENT} and {@link #SUPPRESSED_EFFECT_LIGHTS} individually. */ + @Deprecated public static final int SUPPRESSED_EFFECT_SCREEN_OFF = 1 << 0; /** * Whether notifications suppressed by DND should not interrupt visually when the screen * is on (e.g. by peeking onto the screen). + * + * @deprecated use {@link #SUPPRESSED_EFFECT_PEEK}. */ + @Deprecated public static final int SUPPRESSED_EFFECT_SCREEN_ON = 1 << 1; + /** + * Whether {@link Notification#fullScreenIntent full screen intents} from + * notifications intercepted by DND are blocked. + */ + public static final int SUPPRESSED_EFFECT_FULL_SCREEN_INTENT = 1 << 2; + + /** + * Whether {@link NotificationChannel#shouldShowLights() notification lights} from + * notifications intercepted by DND are blocked. + */ + public static final int SUPPRESSED_EFFECT_LIGHTS = 1 << 3; + + /** + * Whether notifications intercepted by DND are prevented from peeking. + */ + public static final int SUPPRESSED_EFFECT_PEEK = 1 << 4; + + /** + * Whether notifications intercepted by DND are prevented from appearing in the status bar, + * on devices that support status bars. + */ + public static final int SUPPRESSED_EFFECT_STATUS_BAR = 1 << 5; + + /** + * Whether {@link NotificationChannel#canShowBadge() badges} from + * notifications intercepted by DND are blocked on devices that support badging. + */ + public static final int SUPPRESSED_EFFECT_BADGE = 1 << 6; + + /** + * Whether notification intercepted by DND are prevented from appearing on ambient displays + * on devices that support ambient display. + */ + public static final int SUPPRESSED_EFFECT_AMBIENT = 1 << 7; + + /** + * Whether notification intercepted by DND are prevented from appearing in notification + * list views like the notification shade or lockscreen on devices that support those + * views. + */ + public static final int SUPPRESSED_EFFECT_NOTIFICATION_LIST = 1 << 8; + private static final int[] ALL_SUPPRESSED_EFFECTS = { SUPPRESSED_EFFECT_SCREEN_OFF, SUPPRESSED_EFFECT_SCREEN_ON, + SUPPRESSED_EFFECT_FULL_SCREEN_INTENT, + SUPPRESSED_EFFECT_LIGHTS, + SUPPRESSED_EFFECT_PEEK, + SUPPRESSED_EFFECT_STATUS_BAR, + SUPPRESSED_EFFECT_BADGE, + SUPPRESSED_EFFECT_AMBIENT, + SUPPRESSED_EFFECT_NOTIFICATION_LIST }; /** @@ -1072,6 +1154,12 @@ public class NotificationManager { /** * Constructs a policy for Do Not Disturb priority mode behavior. * + * <p> + * Apps that target API levels below {@link Build.VERSION_CODES#P} cannot + * change user-designated values to allow or disallow + * {@link Policy#PRIORITY_CATEGORY_ALARMS}, {@link Policy#PRIORITY_CATEGORY_SYSTEM}, and + * {@link Policy#PRIORITY_CATEGORY_MEDIA} from bypassing dnd. + * * @param priorityCategories bitmask of categories of notifications that can bypass DND. * @param priorityCallSenders which callers can bypass DND. * @param priorityMessageSenders which message senders can bypass DND. @@ -1084,6 +1172,26 @@ public class NotificationManager { /** * Constructs a policy for Do Not Disturb priority mode behavior. * + * <p> + * Apps that target API levels below {@link Build.VERSION_CODES#P} cannot + * change user-designated values to allow or disallow + * {@link Policy#PRIORITY_CATEGORY_ALARMS}, {@link Policy#PRIORITY_CATEGORY_SYSTEM}, and + * {@link Policy#PRIORITY_CATEGORY_MEDIA} from bypassing dnd. + * <p> + * Additionally, apps that target API levels below {@link Build.VERSION_CODES#P} can + * only modify the {@link #SUPPRESSED_EFFECT_SCREEN_ON} and + * {@link #SUPPRESSED_EFFECT_SCREEN_OFF} bits of the suppressed visual effects field. + * All other suppressed effects will be ignored and reconstituted from the screen on + * and screen off values. + * <p> + * Apps that target {@link Build.VERSION_CODES#P} or above can set any + * suppressed visual effects. However, if any suppressed effects > + * {@link #SUPPRESSED_EFFECT_SCREEN_ON} are set, {@link #SUPPRESSED_EFFECT_SCREEN_ON} + * and {@link #SUPPRESSED_EFFECT_SCREEN_OFF} will be ignored and reconstituted from + * the more specific suppressed visual effect bits. Apps should migrate to targeting + * specific effects instead of the deprecated {@link #SUPPRESSED_EFFECT_SCREEN_ON} and + * {@link #SUPPRESSED_EFFECT_SCREEN_OFF} effects. + * * @param priorityCategories bitmask of categories of notifications that can bypass DND. * @param priorityCallSenders which callers can bypass DND. * @param priorityMessageSenders which message senders can bypass DND. @@ -1165,6 +1273,30 @@ public class NotificationManager { } } + /** + * @hide + */ + public static int getAllSuppressedVisualEffects() { + int effects = 0; + for (int i = 0; i < ALL_SUPPRESSED_EFFECTS.length; i++) { + effects |= ALL_SUPPRESSED_EFFECTS[i]; + } + return effects; + } + + /** + * @hide + */ + public static boolean areAllVisualEffectsSuppressed(int effects) { + for (int i = 0; i < ALL_SUPPRESSED_EFFECTS.length; i++) { + final int effect = ALL_SUPPRESSED_EFFECTS[i]; + if ((effects & effect) == 0) { + return false; + } + } + return true; + } + public static String suppressedEffectsToString(int effects) { if (effects <= 0) return ""; final StringBuilder sb = new StringBuilder(); @@ -1203,9 +1335,26 @@ public class NotificationManager { private static String effectToString(int effect) { switch (effect) { - case SUPPRESSED_EFFECT_SCREEN_OFF: return "SUPPRESSED_EFFECT_SCREEN_OFF"; - case SUPPRESSED_EFFECT_SCREEN_ON: return "SUPPRESSED_EFFECT_SCREEN_ON"; - case SUPPRESSED_EFFECTS_UNSET: return "SUPPRESSED_EFFECTS_UNSET"; + case SUPPRESSED_EFFECT_FULL_SCREEN_INTENT: + return "SUPPRESSED_EFFECT_FULL_SCREEN_INTENT"; + case SUPPRESSED_EFFECT_LIGHTS: + return "SUPPRESSED_EFFECT_LIGHTS"; + case SUPPRESSED_EFFECT_PEEK: + return "SUPPRESSED_EFFECT_PEEK"; + case SUPPRESSED_EFFECT_STATUS_BAR: + return "SUPPRESSED_EFFECT_STATUS_BAR"; + case SUPPRESSED_EFFECT_BADGE: + return "SUPPRESSED_EFFECT_BADGE"; + case SUPPRESSED_EFFECT_AMBIENT: + return "SUPPRESSED_EFFECT_AMBIENT"; + case SUPPRESSED_EFFECT_NOTIFICATION_LIST: + return "SUPPRESSED_EFFECT_NOTIFICATION_LIST"; + case SUPPRESSED_EFFECT_SCREEN_OFF: + return "SUPPRESSED_EFFECT_SCREEN_OFF"; + case SUPPRESSED_EFFECT_SCREEN_ON: + return "SUPPRESSED_EFFECT_SCREEN_ON"; + case SUPPRESSED_EFFECTS_UNSET: + return "SUPPRESSED_EFFECTS_UNSET"; default: return "UNKNOWN_" + effect; } } @@ -1218,8 +1367,8 @@ public class NotificationManager { case PRIORITY_CATEGORY_CALLS: return "PRIORITY_CATEGORY_CALLS"; case PRIORITY_CATEGORY_REPEAT_CALLERS: return "PRIORITY_CATEGORY_REPEAT_CALLERS"; case PRIORITY_CATEGORY_ALARMS: return "PRIORITY_CATEGORY_ALARMS"; - case PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER: - return "PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER"; + case PRIORITY_CATEGORY_MEDIA: return "PRIORITY_CATEGORY_MEDIA"; + case PRIORITY_CATEGORY_SYSTEM: return "PRIORITY_CATEGORY_SYSTEM"; default: return "PRIORITY_CATEGORY_UNKNOWN_" + priorityCategory; } } @@ -1264,7 +1413,7 @@ public class NotificationManager { final String pkg = mContext.getPackageName(); try { final ParceledListSlice<StatusBarNotification> parceledList - = service.getAppActiveNotifications(pkg, UserHandle.myUserId()); + = service.getAppActiveNotifications(pkg, mContext.getUserId()); final List<StatusBarNotification> list = parceledList.getList(); return list.toArray(new StatusBarNotification[list.size()]); } catch (RemoteException e) { diff --git a/android/app/PendingIntent.java b/android/app/PendingIntent.java index d6429ae9..315259bd 100644 --- a/android/app/PendingIntent.java +++ b/android/app/PendingIntent.java @@ -345,7 +345,7 @@ public final class PendingIntent implements Parcelable { ActivityManager.INTENT_SENDER_ACTIVITY, packageName, null, null, requestCode, new Intent[] { intent }, resolvedType != null ? new String[] { resolvedType } : null, - flags, options, UserHandle.myUserId()); + flags, options, context.getUserId()); return target != null ? new PendingIntent(target) : null; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -486,7 +486,7 @@ public final class PendingIntent implements Parcelable { ActivityManager.getService().getIntentSender( ActivityManager.INTENT_SENDER_ACTIVITY, packageName, null, null, requestCode, intents, resolvedTypes, flags, options, - UserHandle.myUserId()); + context.getUserId()); return target != null ? new PendingIntent(target) : null; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -544,8 +544,7 @@ public final class PendingIntent implements Parcelable { */ public static PendingIntent getBroadcast(Context context, int requestCode, Intent intent, @Flags int flags) { - return getBroadcastAsUser(context, requestCode, intent, flags, - new UserHandle(UserHandle.myUserId())); + return getBroadcastAsUser(context, requestCode, intent, flags, context.getUser()); } /** @@ -644,7 +643,7 @@ public final class PendingIntent implements Parcelable { serviceKind, packageName, null, null, requestCode, new Intent[] { intent }, resolvedType != null ? new String[] { resolvedType } : null, - flags, null, UserHandle.myUserId()); + flags, null, context.getUserId()); return target != null ? new PendingIntent(target) : null; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); diff --git a/android/app/PendingIntentPerfTest.java b/android/app/PendingIntentPerfTest.java new file mode 100644 index 00000000..f8fd51d7 --- /dev/null +++ b/android/app/PendingIntentPerfTest.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package android.app; + +import android.content.Context; +import android.content.Intent; +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; +import android.perftests.utils.StubActivity; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +// Due to b/71353150, you might get "java.lang.AssertionError: Binder ProxyMap has too many +// entries", but it's flaky. Adding "Runtime.getRuntime().gc()" between each iteration solves +// the problem, but it doesn't seem like it's currently needed. + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class PendingIntentPerfTest { + + private Context mContext; + + @Rule + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + private Intent mIntent; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getTargetContext(); + mIntent = StubActivity.createLaunchIntent(mContext); + } + + /** + * Benchmark time to create a PendingIntent. + */ + @Test + public void create() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + state.resumeTiming(); + + final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, mIntent, + 0); + + state.pauseTiming(); + pendingIntent.cancel(); + state.resumeTiming(); + } + } + + /** + * Benchmark time to create a PendingIntent with FLAG_CANCEL_CURRENT, already having an active + * PendingIntent. + */ + @Test + public void createWithCancelFlag() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + final PendingIntent previousPendingIntent = PendingIntent.getActivity(mContext, 0, + mIntent, 0); + state.resumeTiming(); + + final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, mIntent, + PendingIntent.FLAG_CANCEL_CURRENT); + + state.pauseTiming(); + pendingIntent.cancel(); + state.resumeTiming(); + } + } + + /** + * Benchmark time to create a PendingIntent with FLAG_UPDATE_CURRENT, already having an active + * PendingIntent. + */ + @Test + public void createWithUpdateFlag() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + final PendingIntent previousPendingIntent = PendingIntent.getActivity(mContext, 0, + mIntent, 0); + state.resumeTiming(); + + final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, mIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + + state.pauseTiming(); + previousPendingIntent.cancel(); + pendingIntent.cancel(); + state.resumeTiming(); + } + } + + /** + * Benchmark time to cancel a PendingIntent. + */ + @Test + public void cancel() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, + mIntent, 0); + state.resumeTiming(); + + pendingIntent.cancel(); + } + } +} + diff --git a/android/app/ProcessMemoryState.java b/android/app/ProcessMemoryState.java new file mode 100644 index 00000000..39db16d1 --- /dev/null +++ b/android/app/ProcessMemoryState.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * The memory stats for a process. + * {@hide} + */ +public class ProcessMemoryState implements Parcelable { + public int uid; + public String processName; + public int oomScore; + public long pgfault; + public long pgmajfault; + public long rssInBytes; + public long cacheInBytes; + public long swapInBytes; + + public ProcessMemoryState(int uid, String processName, int oomScore, long pgfault, + long pgmajfault, long rssInBytes, long cacheInBytes, + long swapInBytes) { + this.uid = uid; + this.processName = processName; + this.oomScore = oomScore; + this.pgfault = pgfault; + this.pgmajfault = pgmajfault; + this.rssInBytes = rssInBytes; + this.cacheInBytes = cacheInBytes; + this.swapInBytes = swapInBytes; + } + + private ProcessMemoryState(Parcel in) { + uid = in.readInt(); + processName = in.readString(); + oomScore = in.readInt(); + pgfault = in.readLong(); + pgmajfault = in.readLong(); + rssInBytes = in.readLong(); + cacheInBytes = in.readLong(); + swapInBytes = in.readLong(); + } + + public static final Creator<ProcessMemoryState> CREATOR = new Creator<ProcessMemoryState>() { + @Override + public ProcessMemoryState createFromParcel(Parcel in) { + return new ProcessMemoryState(in); + } + + @Override + public ProcessMemoryState[] newArray(int size) { + return new ProcessMemoryState[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int i) { + parcel.writeInt(uid); + parcel.writeString(processName); + parcel.writeInt(oomScore); + parcel.writeLong(pgfault); + parcel.writeLong(pgmajfault); + parcel.writeLong(rssInBytes); + parcel.writeLong(cacheInBytes); + parcel.writeLong(swapInBytes); + } +} diff --git a/android/app/ProfilerInfo.java b/android/app/ProfilerInfo.java index 0ed1b082..6fbe9c6e 100644 --- a/android/app/ProfilerInfo.java +++ b/android/app/ProfilerInfo.java @@ -87,6 +87,15 @@ public class ProfilerInfo implements Parcelable { } /** + * Return a new ProfilerInfo instance, with fields populated from this object, + * and {@link agent} and {@link attachAgentDuringBind} as given. + */ + public ProfilerInfo setAgent(String agent, boolean attachAgentDuringBind) { + return new ProfilerInfo(this.profileFile, this.profileFd, this.samplingInterval, + this.autoStopProfiler, this.streamingOutput, agent, attachAgentDuringBind); + } + + /** * Close profileFd, if it is open. The field will be null after a call to this function. */ public void closeFd() { diff --git a/android/app/RemoteAction.java b/android/app/RemoteAction.java index e7fe407b..47741c02 100644 --- a/android/app/RemoteAction.java +++ b/android/app/RemoteAction.java @@ -18,14 +18,9 @@ package android.app; import android.annotation.NonNull; import android.graphics.drawable.Icon; -import android.os.Handler; -import android.os.Message; -import android.os.Messenger; import android.os.Parcel; import android.os.Parcelable; -import android.os.RemoteException; import android.text.TextUtils; -import android.util.Log; import java.io.PrintWriter; @@ -42,6 +37,7 @@ public final class RemoteAction implements Parcelable { private final CharSequence mContentDescription; private final PendingIntent mActionIntent; private boolean mEnabled; + private boolean mShouldShowIcon; RemoteAction(Parcel in) { mIcon = Icon.CREATOR.createFromParcel(in); @@ -49,6 +45,7 @@ public final class RemoteAction implements Parcelable { mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); mActionIntent = PendingIntent.CREATOR.createFromParcel(in); mEnabled = in.readBoolean(); + mShouldShowIcon = in.readBoolean(); } public RemoteAction(@NonNull Icon icon, @NonNull CharSequence title, @@ -62,6 +59,7 @@ public final class RemoteAction implements Parcelable { mContentDescription = contentDescription; mActionIntent = intent; mEnabled = true; + mShouldShowIcon = true; } /** @@ -79,6 +77,20 @@ public final class RemoteAction implements Parcelable { } /** + * Sets whether the icon should be shown. + */ + public void setShouldShowIcon(boolean shouldShowIcon) { + mShouldShowIcon = shouldShowIcon; + } + + /** + * Return whether the icon should be shown. + */ + public boolean shouldShowIcon() { + return mShouldShowIcon; + } + + /** * Return an icon representing the action. */ public @NonNull Icon getIcon() { @@ -125,6 +137,7 @@ public final class RemoteAction implements Parcelable { TextUtils.writeToParcel(mContentDescription, out, flags); mActionIntent.writeToParcel(out, flags); out.writeBoolean(mEnabled); + out.writeBoolean(mShouldShowIcon); } public void dump(String prefix, PrintWriter pw) { @@ -134,6 +147,7 @@ public final class RemoteAction implements Parcelable { pw.print(" contentDescription=" + mContentDescription); pw.print(" icon=" + mIcon); pw.print(" action=" + mActionIntent.getIntent()); + pw.print(" shouldShowIcon=" + mShouldShowIcon); pw.println(); } diff --git a/android/app/RemoteInput.java b/android/app/RemoteInput.java index b7100e6f..6feb38e9 100644 --- a/android/app/RemoteInput.java +++ b/android/app/RemoteInput.java @@ -16,6 +16,7 @@ package android.app; +import android.annotation.IntDef; import android.content.ClipData; import android.content.ClipDescription; import android.content.Intent; @@ -25,6 +26,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.ArraySet; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -74,9 +77,14 @@ public final class RemoteInput implements Parcelable { private static final String EXTRA_DATA_TYPE_RESULTS_DATA = "android.remoteinput.dataTypeResultsData"; - /** Extra added to a clip data intent object identifying the source of the results. */ + /** Extra added to a clip data intent object identifying the {@link Source} of the results. */ private static final String EXTRA_RESULTS_SOURCE = "android.remoteinput.resultsSource"; + /** @hide */ + @IntDef(prefix = {"SOURCE_"}, value = {SOURCE_FREE_FORM_INPUT, SOURCE_CHOICE}) + @Retention(RetentionPolicy.SOURCE) + public @interface Source {} + /** The user manually entered the data. */ public static final int SOURCE_FREE_FORM_INPUT = 0; @@ -437,10 +445,9 @@ public final class RemoteInput implements Parcelable { * * @param intent The intent to add remote input source to. The {@link ClipData} * field of the intent will be modified to contain the source. - * field of the intent will be modified to contain the source. * @param source The source of the results. */ - public static void setResultsSource(Intent intent, int source) { + public static void setResultsSource(Intent intent, @Source int source) { Intent clipDataIntent = getClipDataIntentFromIntent(intent); if (clipDataIntent == null) { clipDataIntent = new Intent(); // First time we've added a result. @@ -460,6 +467,7 @@ public final class RemoteInput implements Parcelable { * @return The source of the results. If no source was set, {@link #SOURCE_FREE_FORM_INPUT} will * be returned. */ + @Source public static int getResultsSource(Intent intent) { Intent clipDataIntent = getClipDataIntentFromIntent(intent); if (clipDataIntent == null) { diff --git a/android/app/ResourcesManager.java b/android/app/ResourcesManager.java index fb11272d..fc5ea660 100644 --- a/android/app/ResourcesManager.java +++ b/android/app/ResourcesManager.java @@ -21,6 +21,7 @@ import static android.app.ActivityThread.DEBUG_CONFIGURATION; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.ActivityInfo; +import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.content.res.CompatResources; import android.content.res.CompatibilityInfo; @@ -34,6 +35,7 @@ import android.os.Trace; import android.util.ArrayMap; import android.util.DisplayMetrics; import android.util.Log; +import android.util.LruCache; import android.util.Pair; import android.util.Slog; import android.view.Display; @@ -41,9 +43,13 @@ import android.view.DisplayAdjustments; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.IndentingPrintWriter; +import java.io.IOException; +import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Collection; import java.util.Objects; import java.util.WeakHashMap; import java.util.function.Predicate; @@ -59,12 +65,7 @@ public class ResourcesManager { * Predicate that returns true if a WeakReference is gc'ed. */ private static final Predicate<WeakReference<Resources>> sEmptyReferencePredicate = - new Predicate<WeakReference<Resources>>() { - @Override - public boolean test(WeakReference<Resources> weakRef) { - return weakRef == null || weakRef.get() == null; - } - }; + weakRef -> weakRef == null || weakRef.get() == null; /** * The global compatibility settings. @@ -89,6 +90,48 @@ public class ResourcesManager { */ private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>(); + private static class ApkKey { + public final String path; + public final boolean sharedLib; + public final boolean overlay; + + ApkKey(String path, boolean sharedLib, boolean overlay) { + this.path = path; + this.sharedLib = sharedLib; + this.overlay = overlay; + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + this.path.hashCode(); + result = 31 * result + Boolean.hashCode(this.sharedLib); + result = 31 * result + Boolean.hashCode(this.overlay); + return result; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ApkKey)) { + return false; + } + ApkKey other = (ApkKey) obj; + return this.path.equals(other.path) && this.sharedLib == other.sharedLib + && this.overlay == other.overlay; + } + } + + /** + * The ApkAssets we are caching and intend to hold strong references to. + */ + private final LruCache<ApkKey, ApkAssets> mLoadedApkAssets = new LruCache<>(15); + + /** + * The ApkAssets that are being referenced in the wild that we can reuse, even if they aren't + * in our LRU cache. Bonus resources :) + */ + private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>(); + /** * Resources and base configuration override associated with an Activity. */ @@ -260,6 +303,43 @@ public class ResourcesManager { } } + private static String overlayPathToIdmapPath(String path) { + return "/data/resource-cache/" + path.substring(1).replace('/', '@') + "@idmap"; + } + + private @NonNull ApkAssets loadApkAssets(String path, boolean sharedLib, boolean overlay) + throws IOException { + final ApkKey newKey = new ApkKey(path, sharedLib, overlay); + ApkAssets apkAssets = mLoadedApkAssets.get(newKey); + if (apkAssets != null) { + return apkAssets; + } + + // Optimistically check if this ApkAssets exists somewhere else. + final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(newKey); + if (apkAssetsRef != null) { + apkAssets = apkAssetsRef.get(); + if (apkAssets != null) { + mLoadedApkAssets.put(newKey, apkAssets); + return apkAssets; + } else { + // Clean up the reference. + mCachedApkAssets.remove(newKey); + } + } + + // We must load this from disk. + if (overlay) { + apkAssets = ApkAssets.loadOverlayFromPath(overlayPathToIdmapPath(path), + false /*system*/); + } else { + apkAssets = ApkAssets.loadFromPath(path, false /*system*/, sharedLib); + } + mLoadedApkAssets.put(newKey, apkAssets); + mCachedApkAssets.put(newKey, new WeakReference<>(apkAssets)); + return apkAssets; + } + /** * Creates an AssetManager from the paths within the ResourcesKey. * @@ -270,13 +350,16 @@ public class ResourcesManager { */ @VisibleForTesting protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) { - AssetManager assets = new AssetManager(); + final AssetManager.Builder builder = new AssetManager.Builder(); // resDir can be null if the 'android' package is creating a new Resources object. // This is fine, since each AssetManager automatically loads the 'android' package // already. if (key.mResDir != null) { - if (assets.addAssetPath(key.mResDir) == 0) { + try { + builder.addApkAssets(loadApkAssets(key.mResDir, false /*sharedLib*/, + false /*overlay*/)); + } catch (IOException e) { Log.e(TAG, "failed to add asset path " + key.mResDir); return null; } @@ -284,7 +367,10 @@ public class ResourcesManager { if (key.mSplitResDirs != null) { for (final String splitResDir : key.mSplitResDirs) { - if (assets.addAssetPath(splitResDir) == 0) { + try { + builder.addApkAssets(loadApkAssets(splitResDir, false /*sharedLib*/, + false /*overlay*/)); + } catch (IOException e) { Log.e(TAG, "failed to add split asset path " + splitResDir); return null; } @@ -293,7 +379,14 @@ public class ResourcesManager { if (key.mOverlayDirs != null) { for (final String idmapPath : key.mOverlayDirs) { - assets.addOverlayPath(idmapPath); + try { + builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/, + true /*overlay*/)); + } catch (IOException e) { + Log.w(TAG, "failed to add overlay path " + idmapPath); + + // continue. + } } } @@ -302,14 +395,73 @@ public class ResourcesManager { if (libDir.endsWith(".apk")) { // Avoid opening files we know do not have resources, // like code-only .jar files. - if (assets.addAssetPathAsSharedLibrary(libDir) == 0) { + try { + builder.addApkAssets(loadApkAssets(libDir, true /*sharedLib*/, + false /*overlay*/)); + } catch (IOException e) { Log.w(TAG, "Asset path '" + libDir + "' does not exist or contains no resources."); + + // continue. } } } } - return assets; + + return builder.build(); + } + + private static <T> int countLiveReferences(Collection<WeakReference<T>> collection) { + int count = 0; + for (WeakReference<T> ref : collection) { + final T value = ref != null ? ref.get() : null; + if (value != null) { + count++; + } + } + return count; + } + + /** + * @hide + */ + public void dump(String prefix, PrintWriter printWriter) { + synchronized (this) { + IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); + for (int i = 0; i < prefix.length() / 2; i++) { + pw.increaseIndent(); + } + + pw.println("ResourcesManager:"); + pw.increaseIndent(); + pw.print("cached apks: total="); + pw.print(mLoadedApkAssets.size()); + pw.print(" created="); + pw.print(mLoadedApkAssets.createCount()); + pw.print(" evicted="); + pw.print(mLoadedApkAssets.evictionCount()); + pw.print(" hit="); + pw.print(mLoadedApkAssets.hitCount()); + pw.print(" miss="); + pw.print(mLoadedApkAssets.missCount()); + pw.print(" max="); + pw.print(mLoadedApkAssets.maxSize()); + pw.println(); + + pw.print("total apks: "); + pw.println(countLiveReferences(mCachedApkAssets.values())); + + pw.print("resources: "); + + int references = countLiveReferences(mResourceReferences); + for (ActivityResources activityResources : mActivityResourceReferences.values()) { + references += countLiveReferences(activityResources.activityResources); + } + pw.println(references); + + pw.print("resource impls: "); + pw.println(countLiveReferences(mResourceImpls.values())); + } } private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) { @@ -630,28 +782,16 @@ public class ResourcesManager { // We will create the ResourcesImpl object outside of holding this lock. } - } - - // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now. - ResourcesImpl resourcesImpl = createResourcesImpl(key); - if (resourcesImpl == null) { - return null; - } - synchronized (this) { - ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key); - if (existingResourcesImpl != null) { - if (DEBUG) { - Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl - + " new impl=" + resourcesImpl); - } - resourcesImpl.getAssets().close(); - resourcesImpl = existingResourcesImpl; - } else { - // Add this ResourcesImpl to the cache. - mResourceImpls.put(key, new WeakReference<>(resourcesImpl)); + // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now. + ResourcesImpl resourcesImpl = createResourcesImpl(key); + if (resourcesImpl == null) { + return null; } + // Add this ResourcesImpl to the cache. + mResourceImpls.put(key, new WeakReference<>(resourcesImpl)); + final Resources resources; if (activityToken != null) { resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader, diff --git a/android/app/SearchManager.java b/android/app/SearchManager.java index ea990ad2..49faf402 100644 --- a/android/app/SearchManager.java +++ b/android/app/SearchManager.java @@ -33,7 +33,6 @@ import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; -import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; @@ -48,6 +47,9 @@ import java.util.List; * and the {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} * {@link android.content.Intent Intent}. * + * <p> + * {@link Configuration#UI_MODE_TYPE_WATCH} does not support this system service. + * * <div class="special reference"> * <h3>Developer Guides</h3> * <p>For more information about using the search dialog and adding search @@ -749,6 +751,8 @@ public class SearchManager * * <p>This function can be safely called at any time (even if no search is active.) * + * <p>{@link Configuration#UI_MODE_TYPE_TELEVISION} does not support this method. + * * @see #startSearch */ public void stopSearch() { @@ -799,6 +803,8 @@ public class SearchManager /** * Set or clear the callback that will be invoked whenever the search UI is dismissed. * + * <p>{@link Configuration#UI_MODE_TYPE_TELEVISION} does not support this method. + * * @param listener The {@link OnDismissListener} to use, or null. */ public void setOnDismissListener(final OnDismissListener listener) { @@ -808,6 +814,8 @@ public class SearchManager /** * Set or clear the callback that will be invoked whenever the search UI is canceled. * + * <p>{@link Configuration#UI_MODE_TYPE_TELEVISION} does not support this method. + * * @param listener The {@link OnCancelListener} to use, or null. */ public void setOnCancelListener(OnCancelListener listener) { diff --git a/android/app/Service.java b/android/app/Service.java index 256c4793..ea0fd75b 100644 --- a/android/app/Service.java +++ b/android/app/Service.java @@ -471,14 +471,6 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac * {@link #onStart} and returns either {@link #START_STICKY} * or {@link #START_STICKY_COMPATIBILITY}. * - * <p>If you need your application to run on platform versions prior to API - * level 5, you can use the following model to handle the older {@link #onStart} - * callback in that case. The <code>handleCommand</code> method is implemented by - * you as appropriate: - * - * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/ForegroundService.java - * start_compatibility} - * * <p class="caution">Note that the system calls this on your * service's main thread. A service's main thread is the same * thread where UI operations take place for Activities running in the @@ -687,6 +679,10 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac * {@link #startService(Intent)} first to tell the system it should keep the service running, * and then use this method to tell it to keep it running harder.</p> * + * <p>Apps targeting API {@link android.os.Build.VERSION_CODES#P} or later must request + * the permission {@link android.Manifest.permission#FOREGROUND_SERVICE} in order to use + * this API.</p> + * * @param id The identifier for this notification as per * {@link NotificationManager#notify(int, Notification) * NotificationManager.notify(int, Notification)}; must not be 0. diff --git a/android/app/StatsManager.java b/android/app/StatsManager.java index 963fc776..4a6fa8c2 100644 --- a/android/app/StatsManager.java +++ b/android/app/StatsManager.java @@ -16,6 +16,7 @@ package android.app; import android.Manifest; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.os.IBinder; @@ -30,23 +31,36 @@ import android.util.Slog; * @hide */ @SystemApi -public final class StatsManager extends android.util.StatsManager { // TODO: Remove the extends. +public final class StatsManager { IStatsManager mService; private static final String TAG = "StatsManager"; + private static final boolean DEBUG = false; - /** Long extra of uid that added the relevant stats config. */ - public static final String EXTRA_STATS_CONFIG_UID = - "android.app.extra.STATS_CONFIG_UID"; - /** Long extra of the relevant stats config's configKey. */ - public static final String EXTRA_STATS_CONFIG_KEY = - "android.app.extra.STATS_CONFIG_KEY"; - /** Long extra of the relevant statsd_config.proto's Subscription.id. */ + /** + * Long extra of uid that added the relevant stats config. + */ + public static final String EXTRA_STATS_CONFIG_UID = "android.app.extra.STATS_CONFIG_UID"; + /** + * Long extra of the relevant stats config's configKey. + */ + public static final String EXTRA_STATS_CONFIG_KEY = "android.app.extra.STATS_CONFIG_KEY"; + /** + * Long extra of the relevant statsd_config.proto's Subscription.id. + */ public static final String EXTRA_STATS_SUBSCRIPTION_ID = "android.app.extra.STATS_SUBSCRIPTION_ID"; - /** Long extra of the relevant statsd_config.proto's Subscription.rule_id. */ + /** + * Long extra of the relevant statsd_config.proto's Subscription.rule_id. + */ public static final String EXTRA_STATS_SUBSCRIPTION_RULE_ID = "android.app.extra.STATS_SUBSCRIPTION_RULE_ID"; /** + * List<String> of the relevant statsd_config.proto's BroadcastSubscriberDetails.cookie. + * Obtain using {@link android.content.Intent#getStringArrayListExtra(String)}. + */ + public static final String EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES = + "android.app.extra.STATS_BROADCAST_SUBSCRIBER_COOKIES"; + /** * Extra of a {@link android.os.StatsDimensionsValue} representing sliced dimension value * information. */ @@ -54,6 +68,12 @@ public final class StatsManager extends android.util.StatsManager { // TODO: Rem "android.app.extra.STATS_DIMENSIONS_VALUE"; /** + * Broadcast Action: Statsd has started. + * Configurations and PendingIntents can now be sent to it. + */ + public static final String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED"; + + /** * Constructor for StatsManagerClient. * * @hide @@ -68,22 +88,20 @@ public final class StatsManager extends android.util.StatsManager { // TODO: Rem * @param configKey An arbitrary integer that allows clients to track the configuration. * @param config Wire-encoded StatsDConfig proto that specifies metrics (and all * dependencies eg, conditions and matchers). - * @param pkg The package name to receive the broadcast. - * @param cls The name of the class that receives the broadcast. * @return true if successful */ @RequiresPermission(Manifest.permission.DUMP) - public boolean addConfiguration(long configKey, byte[] config, String pkg, String cls) { + public boolean addConfiguration(long configKey, byte[] config) { synchronized (this) { try { IStatsManager service = getIStatsManagerLocked(); if (service == null) { - Slog.d(TAG, "Failed to find statsd when adding configuration"); + Slog.e(TAG, "Failed to find statsd when adding configuration"); return false; } - return service.addConfiguration(configKey, config, pkg, cls); + return service.addConfiguration(configKey, config); } catch (RemoteException e) { - Slog.d(TAG, "Failed to connect to statsd when adding configuration"); + Slog.e(TAG, "Failed to connect to statsd when adding configuration"); return false; } } @@ -101,12 +119,12 @@ public final class StatsManager extends android.util.StatsManager { // TODO: Rem try { IStatsManager service = getIStatsManagerLocked(); if (service == null) { - Slog.d(TAG, "Failed to find statsd when removing configuration"); + Slog.e(TAG, "Failed to find statsd when removing configuration"); return false; } return service.removeConfiguration(configKey); } catch (RemoteException e) { - Slog.d(TAG, "Failed to connect to statsd when removing configuration"); + Slog.e(TAG, "Failed to connect to statsd when removing configuration"); return false; } } @@ -115,43 +133,40 @@ public final class StatsManager extends android.util.StatsManager { // TODO: Rem /** * Set the PendingIntent to be used when broadcasting subscriber information to the given * subscriberId within the given config. - * * <p> * Suppose that the calling uid has added a config with key configKey, and that in this config * it is specified that when a particular anomaly is detected, a broadcast should be sent to * a BroadcastSubscriber with id subscriberId. This function links the given pendingIntent with * that subscriberId (for that config), so that this pendingIntent is used to send the broadcast * when the anomaly is detected. - * * <p> * When statsd sends the broadcast, the PendingIntent will used to send an intent with * information of - * {@link #EXTRA_STATS_CONFIG_UID}, - * {@link #EXTRA_STATS_CONFIG_KEY}, - * {@link #EXTRA_STATS_SUBSCRIPTION_ID}, - * {@link #EXTRA_STATS_SUBSCRIPTION_RULE_ID}, and - * {@link #EXTRA_STATS_DIMENSIONS_VALUE}. - * + * {@link #EXTRA_STATS_CONFIG_UID}, + * {@link #EXTRA_STATS_CONFIG_KEY}, + * {@link #EXTRA_STATS_SUBSCRIPTION_ID}, + * {@link #EXTRA_STATS_SUBSCRIPTION_RULE_ID}, + * {@link #EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES}, and + * {@link #EXTRA_STATS_DIMENSIONS_VALUE}. * <p> * This function can only be called by the owner (uid) of the config. It must be called each * time statsd starts. The config must have been added first (via addConfiguration()). * - * @param configKey The integer naming the config to which this subscriber is attached. - * @param subscriberId ID of the subscriber, as used in the config. + * @param configKey The integer naming the config to which this subscriber is attached. + * @param subscriberId ID of the subscriber, as used in the config. * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber * associated with the given subscriberId. May be null, in which case * it undoes any previous setting of this subscriberId. * @return true if successful */ @RequiresPermission(Manifest.permission.DUMP) - public boolean setBroadcastSubscriber(long configKey, - long subscriberId, - PendingIntent pendingIntent) { + public boolean setBroadcastSubscriber( + long configKey, long subscriberId, PendingIntent pendingIntent) { synchronized (this) { try { IStatsManager service = getIStatsManagerLocked(); if (service == null) { - Slog.w(TAG, "Failed to find statsd when adding broadcast subscriber"); + Slog.e(TAG, "Failed to find statsd when adding broadcast subscriber"); return false; } if (pendingIntent != null) { @@ -162,7 +177,45 @@ public final class StatsManager extends android.util.StatsManager { // TODO: Rem return service.unsetBroadcastSubscriber(configKey, subscriberId); } } catch (RemoteException e) { - Slog.w(TAG, "Failed to connect to statsd when adding broadcast subscriber", e); + Slog.e(TAG, "Failed to connect to statsd when adding broadcast subscriber", e); + return false; + } + } + } + + /** + * Registers the operation that is called to retrieve the metrics data. This must be called + * each time statsd starts. The config must have been added first (via addConfiguration(), + * although addConfiguration could have been called on a previous boot). This operation allows + * statsd to send metrics data whenever statsd determines that the metrics in memory are + * approaching the memory limits. The fetch operation should call {@link #getData} to fetch the + * data, which also deletes the retrieved metrics from statsd's memory. + * + * @param configKey The integer naming the config to which this operation is attached. + * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber + * associated with the given subscriberId. May be null, in which case + * it removes any associated pending intent with this configKey. + * @return true if successful + */ + @RequiresPermission(Manifest.permission.DUMP) + public boolean setDataFetchOperation(long configKey, PendingIntent pendingIntent) { + synchronized (this) { + try { + IStatsManager service = getIStatsManagerLocked(); + if (service == null) { + Slog.e(TAG, "Failed to find statsd when registering data listener."); + return false; + } + if (pendingIntent == null) { + return service.removeDataFetchOperation(configKey); + } else { + // Extracts IIntentSender from the PendingIntent and turns it into an IBinder. + IBinder intentSender = pendingIntent.getTarget().asBinder(); + return service.setDataFetchOperation(configKey, intentSender); + } + + } catch (RemoteException e) { + Slog.e(TAG, "Failed to connect to statsd when registering data listener."); return false; } } @@ -173,20 +226,21 @@ public final class StatsManager extends android.util.StatsManager { // TODO: Rem * the retrieved metrics from statsd memory. * * @param configKey Configuration key to retrieve data from. - * @return Serialized ConfigMetricsReportList proto. Returns null on failure. + * @return Serialized ConfigMetricsReportList proto. Returns null on failure (eg, if statsd + * crashed). */ @RequiresPermission(Manifest.permission.DUMP) - public byte[] getData(long configKey) { + public @Nullable byte[] getData(long configKey) { synchronized (this) { try { IStatsManager service = getIStatsManagerLocked(); if (service == null) { - Slog.d(TAG, "Failed to find statsd when getting data"); + Slog.e(TAG, "Failed to find statsd when getting data"); return null; } return service.getData(configKey); } catch (RemoteException e) { - Slog.d(TAG, "Failed to connecto statsd when getting data"); + Slog.e(TAG, "Failed to connect to statsd when getting data"); return null; } } @@ -197,20 +251,20 @@ public final class StatsManager extends android.util.StatsManager { // TODO: Rem * the actual metrics themselves (metrics must be collected via {@link #getData(String)}. * This getter is not destructive and will not reset any metrics/counters. * - * @return Serialized StatsdStatsReport proto. Returns null on failure. + * @return Serialized StatsdStatsReport proto. Returns null on failure (eg, if statsd crashed). */ @RequiresPermission(Manifest.permission.DUMP) - public byte[] getMetadata() { + public @Nullable byte[] getMetadata() { synchronized (this) { try { IStatsManager service = getIStatsManagerLocked(); if (service == null) { - Slog.d(TAG, "Failed to find statsd when getting metadata"); + Slog.e(TAG, "Failed to find statsd when getting metadata"); return null; } return service.getMetadata(); } catch (RemoteException e) { - Slog.d(TAG, "Failed to connecto statsd when getting metadata"); + Slog.e(TAG, "Failed to connect to statsd when getting metadata"); return null; } } diff --git a/android/app/StatusBarManager.java b/android/app/StatusBarManager.java index 85a9be35..b83b44d2 100644 --- a/android/app/StatusBarManager.java +++ b/android/app/StatusBarManager.java @@ -74,11 +74,12 @@ public class StatusBarManager { public static final int DISABLE2_SYSTEM_ICONS = 1 << 1; public static final int DISABLE2_NOTIFICATION_SHADE = 1 << 2; public static final int DISABLE2_GLOBAL_ACTIONS = 1 << 3; + public static final int DISABLE2_ROTATE_SUGGESTIONS = 1 << 4; public static final int DISABLE2_NONE = 0x00000000; public static final int DISABLE2_MASK = DISABLE2_QUICK_SETTINGS | DISABLE2_SYSTEM_ICONS - | DISABLE2_NOTIFICATION_SHADE | DISABLE2_GLOBAL_ACTIONS; + | DISABLE2_NOTIFICATION_SHADE | DISABLE2_GLOBAL_ACTIONS | DISABLE2_ROTATE_SUGGESTIONS; @IntDef(flag = true, prefix = { "DISABLE2_" }, value = { DISABLE2_NONE, @@ -86,7 +87,8 @@ public class StatusBarManager { DISABLE2_QUICK_SETTINGS, DISABLE2_SYSTEM_ICONS, DISABLE2_NOTIFICATION_SHADE, - DISABLE2_GLOBAL_ACTIONS + DISABLE2_GLOBAL_ACTIONS, + DISABLE2_ROTATE_SUGGESTIONS }) @Retention(RetentionPolicy.SOURCE) public @interface Disable2Flags {} diff --git a/android/app/SystemServiceRegistry.java b/android/app/SystemServiceRegistry.java index 4310434c..1776eace 100644 --- a/android/app/SystemServiceRegistry.java +++ b/android/app/SystemServiceRegistry.java @@ -89,7 +89,6 @@ import android.net.lowpan.ILowpanManager; import android.net.lowpan.LowpanManager; import android.net.nsd.INsdManager; import android.net.nsd.NsdManager; -import android.net.wifi.IRttManager; import android.net.wifi.IWifiManager; import android.net.wifi.IWifiScanner; import android.net.wifi.RttManager; @@ -116,7 +115,6 @@ import android.os.ISystemUpdateManager; import android.os.IUserManager; import android.os.IncidentManager; import android.os.PowerManager; -import android.os.Process; import android.os.RecoverySystem; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; @@ -162,6 +160,7 @@ import com.android.internal.os.IDropBoxManagerService; import com.android.internal.policy.PhoneLayoutInflater; import java.util.HashMap; +import java.util.concurrent.atomic.AtomicInteger; /** * Manages all of the system services that can be returned by {@link Context#getSystemService}. @@ -638,13 +637,13 @@ final class SystemServiceRegistry { registerService(Context.WIFI_RTT_SERVICE, RttManager.class, new CachedServiceFetcher<RttManager>() { - @Override - public RttManager createService(ContextImpl ctx) throws ServiceNotFoundException { - IBinder b = ServiceManager.getServiceOrThrow(Context.WIFI_RTT_SERVICE); - IRttManager service = IRttManager.Stub.asInterface(b); - return new RttManager(ctx.getOuterContext(), service, - ConnectivityThread.getInstanceLooper()); - }}); + @Override + public RttManager createService(ContextImpl ctx) throws ServiceNotFoundException { + IBinder b = ServiceManager.getServiceOrThrow(Context.WIFI_RTT_RANGING_SERVICE); + IWifiRttManager service = IWifiRttManager.Stub.asInterface(b); + return new RttManager(ctx.getOuterContext(), + new WifiRttManager(ctx.getOuterContext(), service)); + }}); registerService(Context.WIFI_RTT_RANGING_SERVICE, WifiRttManager.class, new CachedServiceFetcher<WifiRttManager>() { @@ -724,8 +723,9 @@ final class SystemServiceRegistry { service = IPrintManager.Stub.asInterface(ServiceManager .getServiceOrThrow(Context.PRINT_SERVICE)); } - return new PrintManager(ctx.getOuterContext(), service, UserHandle.myUserId(), - UserHandle.getAppId(Process.myUid())); + final int userId = ctx.getUserId(); + final int appId = UserHandle.getAppId(ctx.getApplicationInfo().uid); + return new PrintManager(ctx.getOuterContext(), service, userId, appId); }}); registerService(Context.COMPANION_DEVICE_SERVICE, CompanionDeviceManager.class, @@ -780,12 +780,12 @@ final class SystemServiceRegistry { }}); registerService(Context.TV_INPUT_SERVICE, TvInputManager.class, - new StaticServiceFetcher<TvInputManager>() { + new CachedServiceFetcher<TvInputManager>() { @Override - public TvInputManager createService() throws ServiceNotFoundException { + public TvInputManager createService(ContextImpl ctx) throws ServiceNotFoundException { IBinder iBinder = ServiceManager.getServiceOrThrow(Context.TV_INPUT_SERVICE); ITvInputManager service = ITvInputManager.Stub.asInterface(iBinder); - return new TvInputManager(service, UserHandle.myUserId()); + return new TvInputManager(service, ctx.getUserId()); }}); registerService(Context.NETWORK_SCORE_SERVICE, NetworkScoreManager.class, @@ -993,6 +993,10 @@ final class SystemServiceRegistry { return new Object[sServiceCacheSize]; } + public static AtomicInteger[] createServiceInitializationStateArray() { + return new AtomicInteger[sServiceCacheSize]; + } + /** * Gets a system service from a given context. */ @@ -1041,19 +1045,95 @@ final class SystemServiceRegistry { @SuppressWarnings("unchecked") public final T getService(ContextImpl ctx) { final Object[] cache = ctx.mServiceCache; + + // Fast path. If it's already cached, just return it. + Object service = cache[mCacheIndex]; + if (service != null) { + return (T) service; + } + + // Slow path. + final AtomicInteger[] gates = ctx.mServiceInitializationStateArray; + final AtomicInteger gate; + synchronized (cache) { - // Fetch or create the service. - Object service = cache[mCacheIndex]; - if (service == null) { + // See if it's cached or not again, with the lock held this time. + service = cache[mCacheIndex]; + if (service != null) { + return (T) service; + } + + // Not initialized yet. Create an atomic boolean to control which thread should + // instantiate the service. + if (gates[mCacheIndex] != null) { + gate = gates[mCacheIndex]; + } else { + gate = new AtomicInteger(ContextImpl.STATE_UNINITIALIZED); + gates[mCacheIndex] = gate; + } + } + + // Not cached yet. + // + // Note multiple threads can reach here for the same service on the same context + // concurrently. + // + // Now we're going to instantiate the service, but do so without the cache held; + // otherwise it could deadlock. (b/71882178) + // + // However we still don't want to instantiate the same service multiple times, so + // use the atomic integer to ensure only one thread will call createService(). + + if (gate.compareAndSet( + ContextImpl.STATE_UNINITIALIZED, ContextImpl.STATE_INITIALIZING)) { + try { + // This thread is the first one to get here. Instantiate the service + // *without* the cache lock held. try { service = createService(ctx); - cache[mCacheIndex] = service; + + synchronized (cache) { + cache[mCacheIndex] = service; + } } catch (ServiceNotFoundException e) { onServiceNotFound(e); } + } finally { + // Tell the all other threads that the cache is ready now. + // (But it's still be null in case of ServiceNotFoundException.) + synchronized (gate) { + gate.set(ContextImpl.STATE_READY); + gate.notifyAll(); + } + } + return (T) service; + } + // Other threads will wait on the gate lock. + synchronized (gate) { + boolean interrupted = false; + + // Note: We check whether "state == STATE_READY", not + // "cache[mCacheIndex] != null", because "cache[mCacheIndex] == null" + // is still a valid outcome in the ServiceNotFoundException case. + while (gate.get() != ContextImpl.STATE_READY) { + try { + gate.wait(); + } catch (InterruptedException e) { + Log.w(TAG, "getService() interrupted"); + interrupted = true; + } } - return (T)service; + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + // Now the first thread has initialized it. + // It may still be null if ServiceNotFoundException was thrown, but that shouldn't + // happen, so we'll just return null here in that case. + synchronized (cache) { + service = cache[mCacheIndex]; } + return (T) service; } public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException; diff --git a/android/app/TaskStackBuilder.java b/android/app/TaskStackBuilder.java index bab993f8..b99b3274 100644 --- a/android/app/TaskStackBuilder.java +++ b/android/app/TaskStackBuilder.java @@ -213,13 +213,13 @@ public class TaskStackBuilder { * Start the task stack constructed by this builder. * @hide */ - public void startActivities(Bundle options, UserHandle userHandle) { + public int startActivities(Bundle options, UserHandle userHandle) { if (mIntents.isEmpty()) { throw new IllegalStateException( "No intents added to TaskStackBuilder; cannot startActivities"); } - mSourceContext.startActivitiesAsUser(getIntents(), options, userHandle); + return mSourceContext.startActivitiesAsUser(getIntents(), options, userHandle); } /** @@ -230,7 +230,7 @@ public class TaskStackBuilder { * Context.startActivity(Intent, Bundle)} for more details. */ public void startActivities(Bundle options) { - startActivities(options, new UserHandle(UserHandle.myUserId())); + startActivities(options, mSourceContext.getUser()); } /** diff --git a/android/app/UiAutomation.java b/android/app/UiAutomation.java index ba39740b..bd4933a2 100644 --- a/android/app/UiAutomation.java +++ b/android/app/UiAutomation.java @@ -47,13 +47,10 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.IAccessibilityInteractionConnection; -import com.android.internal.util.CollectionUtils; - import libcore.io.IoUtils; import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.concurrent.TimeoutException; @@ -583,8 +580,6 @@ public final class UiAutomation { // Execute the command *without* the lock being held. command.run(); - List<AccessibilityEvent> eventsReceived = Collections.emptyList(); - // Acquire the lock and wait for the event. try { // Wait for the event. @@ -605,14 +600,14 @@ public final class UiAutomation { if (filter.accept(event)) { return event; } - eventsReceived = CollectionUtils.add(eventsReceived, event); + event.recycle(); } // Check if timed out and if not wait. final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis; if (remainingTimeMillis <= 0) { throw new TimeoutException("Expected event not received within: " - + timeoutMillis + " ms, among " + eventsReceived); + + timeoutMillis + " ms."); } synchronized (mLock) { if (mEventQueue.isEmpty()) { @@ -625,10 +620,6 @@ public final class UiAutomation { } } } finally { - for (int i = 0; i < CollectionUtils.size(eventsReceived); i++) { - AccessibilityEvent event = eventsReceived.get(i); - event.recycle(); - } synchronized (mLock) { mWaitingForEventDelivery = false; mEventQueue.clear(); @@ -885,16 +876,36 @@ public final class UiAutomation { } /** - * Grants a runtime permission to a package for a user. + * Grants a runtime permission to a package. * @param packageName The package to which to grant. * @param permission The permission to grant. - * @return Whether granting succeeded. - * + * @throws SecurityException if unable to grant the permission. + */ + public void grantRuntimePermission(String packageName, String permission) { + grantRuntimePermissionAsUser(packageName, permission, android.os.Process.myUserHandle()); + } + + /** + * @deprecated replaced by + * {@link #grantRuntimePermissionAsUser(String, String, UserHandle)}. * @hide */ + @Deprecated @TestApi public boolean grantRuntimePermission(String packageName, String permission, UserHandle userHandle) { + grantRuntimePermissionAsUser(packageName, permission, userHandle); + return true; + } + + /** + * Grants a runtime permission to a package for a user. + * @param packageName The package to which to grant. + * @param permission The permission to grant. + * @throws SecurityException if unable to grant the permission. + */ + public void grantRuntimePermissionAsUser(String packageName, String permission, + UserHandle userHandle) { synchronized (mLock) { throwIfNotConnectedLocked(); } @@ -905,25 +916,42 @@ public final class UiAutomation { // Calling out without a lock held. mUiAutomationConnection.grantRuntimePermission(packageName, permission, userHandle.getIdentifier()); - // TODO: The package manager API should return boolean. - return true; - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error granting runtime permission", re); + } catch (Exception e) { + throw new SecurityException("Error granting runtime permission", e); } - return false; } /** - * Revokes a runtime permission from a package for a user. - * @param packageName The package from which to revoke. - * @param permission The permission to revoke. - * @return Whether revoking succeeded. - * + * Revokes a runtime permission from a package. + * @param packageName The package to which to grant. + * @param permission The permission to grant. + * @throws SecurityException if unable to revoke the permission. + */ + public void revokeRuntimePermission(String packageName, String permission) { + revokeRuntimePermissionAsUser(packageName, permission, android.os.Process.myUserHandle()); + } + + /** + * @deprecated replaced by + * {@link #revokeRuntimePermissionAsUser(String, String, UserHandle)}. * @hide */ + @Deprecated @TestApi public boolean revokeRuntimePermission(String packageName, String permission, UserHandle userHandle) { + revokeRuntimePermissionAsUser(packageName, permission, userHandle); + return true; + } + + /** + * Revokes a runtime permission from a package. + * @param packageName The package to which to grant. + * @param permission The permission to grant. + * @throws SecurityException if unable to revoke the permission. + */ + public void revokeRuntimePermissionAsUser(String packageName, String permission, + UserHandle userHandle) { synchronized (mLock) { throwIfNotConnectedLocked(); } @@ -934,12 +962,9 @@ public final class UiAutomation { // Calling out without a lock held. mUiAutomationConnection.revokeRuntimePermission(packageName, permission, userHandle.getIdentifier()); - // TODO: The package manager API should return boolean. - return true; - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error revoking runtime permission", re); + } catch (Exception e) { + throw new SecurityException("Error granting runtime permission", e); } - return false; } /** @@ -958,6 +983,7 @@ public final class UiAutomation { synchronized (mLock) { throwIfNotConnectedLocked(); } + warnIfBetterCommand(command); ParcelFileDescriptor source = null; ParcelFileDescriptor sink = null; @@ -1000,6 +1026,7 @@ public final class UiAutomation { synchronized (mLock) { throwIfNotConnectedLocked(); } + warnIfBetterCommand(command); ParcelFileDescriptor source_read = null; ParcelFileDescriptor sink_read = null; @@ -1065,6 +1092,16 @@ public final class UiAutomation { } } + private void warnIfBetterCommand(String cmd) { + if (cmd.startsWith("pm grant ")) { + Log.w(LOG_TAG, "UiAutomation.grantRuntimePermission() " + + "is more robust and should be used instead of 'pm grant'"); + } else if (cmd.startsWith("pm revoke ")) { + Log.w(LOG_TAG, "UiAutomation.revokeRuntimePermission() " + + "is more robust and should be used instead of 'pm revoke'"); + } + } + private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper { public IAccessibilityServiceClientImpl(Looper looper) { diff --git a/android/app/WallpaperColors.java b/android/app/WallpaperColors.java index a2864b9d..2d007adf 100644 --- a/android/app/WallpaperColors.java +++ b/android/app/WallpaperColors.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; @@ -107,6 +108,11 @@ public final class WallpaperColors implements Parcelable { * @param drawable Source where to extract from. */ public static WallpaperColors fromDrawable(Drawable drawable) { + if (drawable == null) { + throw new IllegalArgumentException("Drawable cannot be null"); + } + + Rect initialBounds = drawable.copyBounds(); int width = drawable.getIntrinsicWidth(); int height = drawable.getIntrinsicHeight(); @@ -126,6 +132,7 @@ public final class WallpaperColors implements Parcelable { final WallpaperColors colors = WallpaperColors.fromBitmap(bitmap); bitmap.recycle(); + drawable.setBounds(initialBounds); return colors; } @@ -137,6 +144,13 @@ public final class WallpaperColors implements Parcelable { * @param bitmap Source where to extract from. */ public static WallpaperColors fromBitmap(@NonNull Bitmap bitmap) { + return fromBitmap(bitmap, false /* computeHints */); + } + + /** + * @hide + */ + public static WallpaperColors fromBitmap(@NonNull Bitmap bitmap, boolean computeHints) { if (bitmap == null) { throw new IllegalArgumentException("Bitmap can't be null"); } @@ -186,7 +200,7 @@ public final class WallpaperColors implements Parcelable { } } - int hints = calculateDarkHints(bitmap); + int hints = computeHints ? calculateDarkHints(bitmap) : 0; if (shouldRecycle) { bitmap.recycle(); diff --git a/android/app/WallpaperManager.java b/android/app/WallpaperManager.java index f21746cd..465340f6 100644 --- a/android/app/WallpaperManager.java +++ b/android/app/WallpaperManager.java @@ -51,15 +51,13 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.DeadSystemException; +import android.os.FileUtils; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.ParcelFileDescriptor; import android.os.RemoteException; -import android.os.ServiceManager; import android.os.SystemProperties; -import android.os.UserHandle; -import android.service.wallpaper.WallpaperService; import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -443,6 +441,9 @@ public class WallpaperManager { synchronized (this) { mCachedWallpaper = null; mCachedWallpaperUserId = 0; + if (mDefaultWallpaper != null) { + mDefaultWallpaper.recycle(); + } mDefaultWallpaper = null; } } @@ -914,9 +915,14 @@ public class WallpaperManager { /** * Get the primary colors of a wallpaper. * - * <p>You can expect null if: - * • Colors are still being processed by the system. - * • A live wallpaper doesn't implement {@link WallpaperService.Engine#onComputeColors()}. + * <p>This method can return {@code null} when: + * <ul> + * <li>Colors are still being processed by the system.</li> + * <li>The user has chosen to use a live wallpaper: live wallpapers might not + * implement + * {@link android.service.wallpaper.WallpaperService.Engine#onComputeColors() + * WallpaperService.Engine#onComputeColors()}.</li> + * </ul> * * @param which Wallpaper type. Must be either {@link #FLAG_SYSTEM} or * {@link #FLAG_LOCK}. @@ -928,7 +934,7 @@ public class WallpaperManager { } /** - * Get the primary colors of a wallpaper + * Get the primary colors of the wallpaper configured in the given user. * @param which wallpaper type. Must be either {@link #FLAG_SYSTEM} or * {@link #FLAG_LOCK} * @param userId Owner of the wallpaper. @@ -1002,7 +1008,7 @@ public class WallpaperManager { Log.w(TAG, "WallpaperService not running"); throw new RuntimeException(new DeadSystemException()); } else { - return sGlobals.mService.getWallpaperInfo(UserHandle.myUserId()); + return sGlobals.mService.getWallpaperInfo(mContext.getUserId()); } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -1150,7 +1156,7 @@ public class WallpaperManager { ParcelFileDescriptor fd = sGlobals.mService.setWallpaper( "res:" + resources.getResourceName(resid), mContext.getOpPackageName(), null, false, result, which, completion, - UserHandle.myUserId()); + mContext.getUserId()); if (fd != null) { FileOutputStream fos = null; boolean ok = false; @@ -1255,7 +1261,7 @@ public class WallpaperManager { boolean allowBackup, @SetWallpaperFlags int which) throws IOException { return setBitmap(fullImage, visibleCropHint, allowBackup, which, - UserHandle.myUserId()); + mContext.getUserId()); } /** @@ -1329,11 +1335,7 @@ public class WallpaperManager { private void copyStreamToWallpaperFile(InputStream data, FileOutputStream fos) throws IOException { - byte[] buffer = new byte[32768]; - int amt; - while ((amt=data.read(buffer)) > 0) { - fos.write(buffer, 0, amt); - } + FileUtils.copy(data, fos); } /** @@ -1406,7 +1408,7 @@ public class WallpaperManager { try { ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null, mContext.getOpPackageName(), visibleCropHint, allowBackup, - result, which, completion, UserHandle.myUserId()); + result, which, completion, mContext.getUserId()); if (fd != null) { FileOutputStream fos = null; try { @@ -1562,11 +1564,13 @@ public class WallpaperManager { * Specify extra padding that the wallpaper should have outside of the display. * That is, the given padding supplies additional pixels the wallpaper should extend * outside of the display itself. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#SET_WALLPAPER_HINTS}. + * * @param padding The number of pixels the wallpaper should extend beyond the display, * on its left, top, right, and bottom sides. - * @hide */ - @SystemApi @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_HINTS) public void setDisplayPadding(Rect padding) { try { @@ -1603,11 +1607,11 @@ public class WallpaperManager { } /** - * Clear the wallpaper. + * Reset all wallpaper to the factory default. * - * @hide + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#SET_WALLPAPER}. */ - @SystemApi @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public void clearWallpaper() { clearWallpaper(FLAG_LOCK, mContext.getUserId()); @@ -1643,7 +1647,7 @@ public class WallpaperManager { @SystemApi @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) public boolean setWallpaperComponent(ComponentName name) { - return setWallpaperComponent(name, UserHandle.myUserId()); + return setWallpaperComponent(name, mContext.getUserId()); } /** diff --git a/android/app/WindowConfiguration.java b/android/app/WindowConfiguration.java index 085fc79f..46566e79 100644 --- a/android/app/WindowConfiguration.java +++ b/android/app/WindowConfiguration.java @@ -146,6 +146,9 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu }) public @interface WindowConfig {} + /** @hide */ + public static final int PINNED_WINDOWING_MODE_ELEVATION_IN_DIP = 5; + public WindowConfiguration() { unset(); } diff --git a/android/app/admin/DeviceAdminInfo.java b/android/app/admin/DeviceAdminInfo.java index 1de1d2fb..5fbe5b39 100644 --- a/android/app/admin/DeviceAdminInfo.java +++ b/android/app/admin/DeviceAdminInfo.java @@ -16,31 +16,31 @@ package android.app.admin; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; - import android.annotation.NonNull; import android.content.ComponentName; import android.content.Context; import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; import android.content.res.Resources; +import android.content.res.Resources.NotFoundException; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; -import android.content.res.Resources.NotFoundException; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; +import android.os.PersistableBundle; import android.util.AttributeSet; import android.util.Log; import android.util.Printer; import android.util.SparseArray; import android.util.Xml; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -75,6 +75,10 @@ public final class DeviceAdminInfo implements Parcelable { * * <p>To control this policy, the device admin must have a "limit-password" * tag in the "uses-policies" section of its meta-data. + * + * <p>This policy is deprecated for use by a device admin. In future releases, it will + * only be possible for a device owner or profile owner to enforce constraints on user + * passwords. */ public static final int USES_POLICY_LIMIT_PASSWORD = 0; @@ -136,6 +140,9 @@ public final class DeviceAdminInfo implements Parcelable { * * <p>To control this policy, the device admin must have an "expire-password" * tag in the "uses-policies" section of its meta-data. + * + * <p>This policy is deprecated for use by a device admin. In future releases, it will + * only be possible for a device owner or profile owner to enforce password expiry. */ public static final int USES_POLICY_EXPIRE_PASSWORD = 6; @@ -152,6 +159,9 @@ public final class DeviceAdminInfo implements Parcelable { * * <p>To control this policy, the device admin must have a "disable-camera" * tag in the "uses-policies" section of its meta-data. + * + * <p>This policy is deprecated for use by a device admin. In future releases, it will + * only be possible for a device owner or profile owner to disable use of the camera. */ public static final int USES_POLICY_DISABLE_CAMERA = 8; @@ -160,6 +170,10 @@ public final class DeviceAdminInfo implements Parcelable { * * <p>To control this policy, the device admin must have a "disable-keyguard-features" * tag in the "uses-policies" section of its meta-data. + * + * <p>This policy is deprecated for use by a device admin. In future releases, it will + * only be possible for a device owner or profile owner to disable use of keyguard + * features. */ public static final int USES_POLICY_DISABLE_KEYGUARD_FEATURES = 9; @@ -252,6 +266,12 @@ public final class DeviceAdminInfo implements Parcelable { */ int mUsesPolicies; + /** + * Whether this administrator can be a target in an ownership transfer. + * + * @see DevicePolicyManager#transferOwnership(ComponentName, ComponentName, PersistableBundle) + */ + boolean mSupportsTransferOwnership; /** * Constructor. @@ -333,6 +353,12 @@ public final class DeviceAdminInfo implements Parcelable { + getComponent() + ": " + policyName); } } + } else if (tagName.equals("support-transfer-ownership")) { + if (parser.next() != XmlPullParser.END_TAG) { + throw new XmlPullParserException( + "support-transfer-ownership tag must be empty."); + } + mSupportsTransferOwnership = true; } } } catch (NameNotFoundException e) { @@ -346,6 +372,7 @@ public final class DeviceAdminInfo implements Parcelable { DeviceAdminInfo(Parcel source) { mActivityInfo = ActivityInfo.CREATOR.createFromParcel(source); mUsesPolicies = source.readInt(); + mSupportsTransferOwnership = source.readBoolean(); } /** @@ -444,6 +471,13 @@ public final class DeviceAdminInfo implements Parcelable { return sRevKnownPolicies.get(policyIdent).tag; } + /** + * Return true if this administrator can be a target in an ownership transfer. + */ + public boolean supportsTransferOwnership() { + return mSupportsTransferOwnership; + } + /** @hide */ public ArrayList<PolicyInfo> getUsedPolicies() { ArrayList<PolicyInfo> res = new ArrayList<PolicyInfo>(); @@ -488,6 +522,7 @@ public final class DeviceAdminInfo implements Parcelable { public void writeToParcel(Parcel dest, int flags) { mActivityInfo.writeToParcel(dest, flags); dest.writeInt(mUsesPolicies); + dest.writeBoolean(mSupportsTransferOwnership); } /** diff --git a/android/app/admin/DeviceAdminReceiver.java b/android/app/admin/DeviceAdminReceiver.java index 28e845a0..1c9477d0 100644 --- a/android/app/admin/DeviceAdminReceiver.java +++ b/android/app/admin/DeviceAdminReceiver.java @@ -19,6 +19,8 @@ package android.app.admin; import android.accounts.AccountManager; import android.annotation.BroadcastBehavior; import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; @@ -34,9 +36,6 @@ import android.os.Process; import android.os.UserHandle; import android.security.KeyChain; -import libcore.util.NonNull; -import libcore.util.Nullable; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -506,31 +505,6 @@ public class DeviceAdminReceiver extends BroadcastReceiver { public static final String EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE = "android.app.extra.TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE"; - /** - * Name under which a device administration component indicates whether it supports transfer of - * ownership. This meta-data is of type <code>boolean</code>. A value of <code>true</code> - * allows this administrator to be used as a target administrator for a transfer. If the value - * is <code>false</code>, ownership cannot be transferred to this administrator. The default - * value is <code>false</code>. - * <p>This metadata is used to avoid ownership transfer migration to an administrator with a - * version which does not yet support it. - * <p>Usage: - * <pre> - * <receiver name="..." android:permission="android.permission.BIND_DEVICE_ADMIN"> - * <meta-data - * android:name="android.app.device_admin" - * android:resource="@xml/..." /> - * <meta-data - * android:name="android.app.support_transfer_ownership" - * android:value="true" /> - * </receiver> - * </pre> - * - * @see DevicePolicyManager#transferOwnership(ComponentName, ComponentName, PersistableBundle) - */ - public static final String SUPPORT_TRANSFER_OWNERSHIP_META_DATA = - "android.app.support_transfer_ownership"; - private DevicePolicyManager mManager; private ComponentName mWho; @@ -928,29 +902,29 @@ public class DeviceAdminReceiver extends BroadcastReceiver { int networkLogsCount) { } - /** - * Called when a user or profile is created. - * - * <p>This callback is only applicable to device owners. - * - * @param context The running context as per {@link #onReceive}. - * @param intent The received intent as per {@link #onReceive}. - * @param newUser The {@link UserHandle} of the user that has just been added. - */ - public void onUserAdded(Context context, Intent intent, UserHandle newUser) { - } - - /** - * Called when a user or profile is removed. - * - * <p>This callback is only applicable to device owners. - * - * @param context The running context as per {@link #onReceive}. - * @param intent The received intent as per {@link #onReceive}. - * @param removedUser The {@link UserHandle} of the user that has just been removed. - */ - public void onUserRemoved(Context context, Intent intent, UserHandle removedUser) { - } + /** + * Called when a user or profile is created. + * + * <p>This callback is only applicable to device owners. + * + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + * @param newUser The {@link UserHandle} of the user that has just been added. + */ + public void onUserAdded(Context context, Intent intent, @NonNull UserHandle newUser) { + } + + /** + * Called when a user or profile is removed. + * + * <p>This callback is only applicable to device owners. + * + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + * @param removedUser The {@link UserHandle} of the user that has just been removed. + */ + public void onUserRemoved(Context context, Intent intent, @NonNull UserHandle removedUser) { + } /** * Called when a user or profile is started. @@ -961,7 +935,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * @param intent The received intent as per {@link #onReceive}. * @param startedUser The {@link UserHandle} of the user that has just been started. */ - public void onUserStarted(Context context, Intent intent, UserHandle startedUser) { + public void onUserStarted(Context context, Intent intent, @NonNull UserHandle startedUser) { } /** @@ -973,7 +947,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * @param intent The received intent as per {@link #onReceive}. * @param stoppedUser The {@link UserHandle} of the user that has just been stopped. */ - public void onUserStopped(Context context, Intent intent, UserHandle stoppedUser) { + public void onUserStopped(Context context, Intent intent, @NonNull UserHandle stoppedUser) { } /** @@ -985,7 +959,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * @param intent The received intent as per {@link #onReceive}. * @param switchedUser The {@link UserHandle} of the user that has just been switched to. */ - public void onUserSwitched(Context context, Intent intent, UserHandle switchedUser) { + public void onUserSwitched(Context context, Intent intent, @NonNull UserHandle switchedUser) { } /** diff --git a/android/app/admin/DevicePolicyCache.java b/android/app/admin/DevicePolicyCache.java new file mode 100644 index 00000000..fbb8ddfe --- /dev/null +++ b/android/app/admin/DevicePolicyCache.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.app.admin; + +import android.annotation.UserIdInt; + +import com.android.server.LocalServices; + +/** + * Stores a copy of the set of device policies maintained by {@link DevicePolicyManager} that + * can be accessed from any place without risking dead locks. + * + * @hide + */ +public abstract class DevicePolicyCache { + protected DevicePolicyCache() { + } + + /** + * @return the instance. + */ + public static DevicePolicyCache getInstance() { + final DevicePolicyManagerInternal dpmi = + LocalServices.getService(DevicePolicyManagerInternal.class); + return (dpmi != null) ? dpmi.getDevicePolicyCache() : EmptyDevicePolicyCache.INSTANCE; + } + + /** + * See {@link DevicePolicyManager#getScreenCaptureDisabled} + */ + public abstract boolean getScreenCaptureDisabled(@UserIdInt int userHandle); + + /** + * Empty implementation. + */ + private static class EmptyDevicePolicyCache extends DevicePolicyCache { + private static final EmptyDevicePolicyCache INSTANCE = new EmptyDevicePolicyCache(); + + @Override + public boolean getScreenCaptureDisabled(int userHandle) { + return false; + } + } +} diff --git a/android/app/admin/DevicePolicyManager.java b/android/app/admin/DevicePolicyManager.java index 8f76032b..b64aae52 100644 --- a/android/app/admin/DevicePolicyManager.java +++ b/android/app/admin/DevicePolicyManager.java @@ -21,9 +21,11 @@ import android.annotation.ColorInt; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.StringDef; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; @@ -54,9 +56,13 @@ import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteCallback; import android.os.RemoteException; +import android.os.ServiceSpecificException; import android.os.UserHandle; import android.os.UserManager; +import android.os.UserManager.UserOperationException; +import android.os.UserManager.UserOperationResult; import android.provider.ContactsContract.Directory; +import android.provider.Settings; import android.security.AttestedKeyPair; import android.security.Credentials; import android.security.KeyChain; @@ -116,7 +122,9 @@ import java.util.concurrent.Executor; * guide. </div> */ @SystemService(Context.DEVICE_POLICY_SERVICE) +@RequiresFeature(PackageManager.FEATURE_DEVICE_ADMIN) public class DevicePolicyManager { + private static String TAG = "DevicePolicyManager"; private final Context mContext; @@ -140,7 +148,7 @@ public class DevicePolicyManager { /** @hide test will override it. */ @VisibleForTesting protected int myUserId() { - return UserHandle.myUserId(); + return mContext.getUserId(); } /** @@ -1605,8 +1613,6 @@ public class DevicePolicyManager { * <li>keyguard * </ul> * - * This is the default configuration for LockTask. - * * @see #setLockTaskFeatures(ComponentName, int) */ public static final int LOCK_TASK_FEATURE_NONE = 0; @@ -1624,7 +1630,10 @@ public class DevicePolicyManager { /** * Enable notifications during LockTask mode. This includes notification icons on the status * bar, heads-up notifications, and the expandable notification shade. Note that the Quick - * Settings panel will still be disabled. + * Settings panel remains disabled. This feature flag can only be used in combination with + * {@link #LOCK_TASK_FEATURE_HOME}. {@link #setLockTaskFeatures(ComponentName, int)} + * throws an {@link IllegalArgumentException} if this feature flag is defined without + * {@link #LOCK_TASK_FEATURE_HOME}. * * @see #setLockTaskFeatures(ComponentName, int) */ @@ -1642,17 +1651,24 @@ public class DevicePolicyManager { public static final int LOCK_TASK_FEATURE_HOME = 1 << 2; /** - * Enable the Recents button and the Recents screen during LockTask mode. + * Enable the Overview button and the Overview screen during LockTask mode. This feature flag + * can only be used in combination with {@link #LOCK_TASK_FEATURE_HOME}, and + * {@link #setLockTaskFeatures(ComponentName, int)} will throw an + * {@link IllegalArgumentException} if this feature flag is defined without + * {@link #LOCK_TASK_FEATURE_HOME}. * * @see #setLockTaskFeatures(ComponentName, int) */ - public static final int LOCK_TASK_FEATURE_RECENTS = 1 << 3; + public static final int LOCK_TASK_FEATURE_OVERVIEW = 1 << 3; /** * Enable the global actions dialog during LockTask mode. This is the dialog that shows up when * the user long-presses the power button, for example. Note that the user may not be able to * power off the device if this flag is not set. * + * <p>This flag is enabled by default until {@link #setLockTaskFeatures(ComponentName, int)} is + * called for the first time. + * * @see #setLockTaskFeatures(ComponentName, int) */ public static final int LOCK_TASK_FEATURE_GLOBAL_ACTIONS = 1 << 4; @@ -1678,7 +1694,7 @@ public class DevicePolicyManager { LOCK_TASK_FEATURE_SYSTEM_INFO, LOCK_TASK_FEATURE_NOTIFICATIONS, LOCK_TASK_FEATURE_HOME, - LOCK_TASK_FEATURE_RECENTS, + LOCK_TASK_FEATURE_OVERVIEW, LOCK_TASK_FEATURE_GLOBAL_ACTIONS, LOCK_TASK_FEATURE_KEYGUARD }) @@ -1736,6 +1752,25 @@ public class DevicePolicyManager { public static final int ID_TYPE_MEID = 8; /** + * Specifies that the calling app should be granted access to the installed credentials + * immediately. Otherwise, access to the credentials will be gated by user approval. + * For use with {@link #installKeyPair(ComponentName, PrivateKey, Certificate[], String, int)} + * + * @see #installKeyPair(ComponentName, PrivateKey, Certificate[], String, int) + */ + public static final int INSTALLKEY_REQUEST_CREDENTIALS_ACCESS = 1; + + /** + * Specifies that a user can select the key via the Certificate Selection prompt. + * If this flag is not set when calling {@link #installKeyPair}, the key can only be granted + * access by implementing {@link android.app.admin.DeviceAdminReceiver#onChoosePrivateKeyAlias}. + * For use with {@link #installKeyPair(ComponentName, PrivateKey, Certificate[], String, int)} + * + * @see #installKeyPair(ComponentName, PrivateKey, Certificate[], String, int) + */ + public static final int INSTALLKEY_SET_USER_SELECTABLE = 2; + + /** * Broadcast action: sent when the profile owner is set, changed or cleared. * * This broadcast is sent only to the user managed by the new profile owner. @@ -2712,110 +2747,6 @@ public class DevicePolicyManager { } /** - * The maximum number of characters allowed in the password blacklist. - */ - private static final int PASSWORD_BLACKLIST_CHARACTER_LIMIT = 128 * 1000; - - /** - * Throws an exception if the password blacklist is too large. - * - * @hide - */ - public static void enforcePasswordBlacklistSize(List<String> blacklist) { - if (blacklist == null) { - return; - } - long characterCount = 0; - for (final String item : blacklist) { - characterCount += item.length(); - } - if (characterCount > PASSWORD_BLACKLIST_CHARACTER_LIMIT) { - throw new IllegalArgumentException("128 thousand blacklist character limit exceeded by " - + (characterCount - PASSWORD_BLACKLIST_CHARACTER_LIMIT) + " characters"); - } - } - - /** - * Called by an application that is administering the device to blacklist passwords. - * <p> - * Any blacklisted password or PIN is prevented from being enrolled by the user or the admin. - * Note that the match against the blacklist is case insensitive. The blacklist applies for all - * password qualities requested by {@link #setPasswordQuality} however it is not taken into - * consideration by {@link #isActivePasswordSufficient}. - * <p> - * The blacklist can be cleared by passing {@code null} or an empty list. The blacklist is - * given a name that is used to track which blacklist is currently set by calling {@link - * #getPasswordBlacklistName}. If the blacklist is being cleared, the name is ignored and {@link - * #getPasswordBlacklistName} will return {@code null}. The name can only be {@code null} when - * the blacklist is being cleared. - * <p> - * The blacklist is limited to a total of 128 thousand characters rather than limiting to a - * number of entries. - * <p> - * This method can be called on the {@link DevicePolicyManager} instance returned by - * {@link #getParentProfileInstance(ComponentName)} in order to set restrictions on the parent - * profile. - * - * @param admin the {@link DeviceAdminReceiver} this request is associated with - * @param name name to associate with the blacklist - * @param blacklist list of passwords to blacklist or {@code null} to clear the blacklist - * @return whether the new blacklist was successfully installed - * @throws SecurityException if {@code admin} is not a device or profile owner - * @throws IllegalArgumentException if the blacklist surpasses the character limit - * @throws NullPointerException if {@code name} is {@code null} when setting a non-empty list - * - * @see #getPasswordBlacklistName - * @see #isActivePasswordSufficient - * @see #resetPasswordWithToken - */ - public boolean setPasswordBlacklist(@NonNull ComponentName admin, @Nullable String name, - @Nullable List<String> blacklist) { - enforcePasswordBlacklistSize(blacklist); - - try { - return mService.setPasswordBlacklist(admin, name, blacklist, mParentInstance); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } - } - - /** - * Get the name of the password blacklist set by the given admin. - * - * @param admin the {@link DeviceAdminReceiver} this request is associated with - * @return the name of the blacklist or {@code null} if no blacklist is set - * - * @see #setPasswordBlacklist - */ - public @Nullable String getPasswordBlacklistName(@NonNull ComponentName admin) { - try { - return mService.getPasswordBlacklistName(admin, myUserId(), mParentInstance); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } - } - - /** - * Test if a given password is blacklisted. - * - * @param userId the user to valiate for - * @param password the password to check against the blacklist - * @return whether the password is blacklisted - * - * @see #setPasswordBlacklist - * - * @hide - */ - @RequiresPermission(android.Manifest.permission.TEST_BLACKLISTED_PASSWORD) - public boolean isPasswordBlacklisted(@UserIdInt int userId, @NonNull String password) { - try { - return mService.isPasswordBlacklisted(userId, password); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } - } - - /** * Determine whether the current password the user has set is sufficient to meet the policy * requirements (e.g. quality, minimum length) that have been requested by the admins of this * user and its participating profiles. Restrictions on profiles that have a separate challenge @@ -2850,8 +2781,8 @@ public class DevicePolicyManager { * When called by a profile owner of a managed profile returns true if the profile uses unified * challenge with its parent user. * - * <strong>Note: This method is not concerned with password quality and will return false if - * the profile has empty password as a separate challenge. + * <strong>Note</strong>: This method is not concerned with password quality and will return + * false if the profile has empty password as a separate challenge. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @throws SecurityException if {@code admin} is not a profile owner of a managed profile. @@ -3444,9 +3375,6 @@ public class DevicePolicyManager { /** * Flag for {@link #wipeData(int)}: also erase the device's eUICC data. - * - * TODO(b/35851809): make this public. - * @hide */ public static final int WIPE_EUICC = 0x0004; @@ -3490,18 +3418,18 @@ public class DevicePolicyManager { * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} * @throws IllegalArgumentException if the input reason string is null or empty. */ - public void wipeDataWithReason(int flags, @NonNull CharSequence reason) { - throwIfParentInstance("wipeDataWithReason"); + public void wipeData(int flags, @NonNull CharSequence reason) { + throwIfParentInstance("wipeData"); Preconditions.checkNotNull(reason, "CharSequence is null"); wipeDataInternal(flags, reason.toString()); } /** * Internal function for both {@link #wipeData(int)} and - * {@link #wipeDataWithReason(int, CharSequence)} to call. + * {@link #wipeData(int, CharSequence)} to call. * * @see #wipeData(int) - * @see #wipeDataWithReason(int, CharSequence) + * @see #wipeData(int, CharSequence) * @hide */ private void wipeDataInternal(int flags, @NonNull String wipeReasonForUser) { @@ -3704,7 +3632,9 @@ public class DevicePolicyManager { public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0; /** - * Disable all keyguard widgets. Has no effect. + * Disable all keyguard widgets. Has no effect starting from + * {@link android.os.Build.VERSION_CODES#LOLLIPOP} since keyguard widget is only supported + * on Android versions lower than 5.0. */ public static final int KEYGUARD_DISABLE_WIDGETS_ALL = 1 << 0; @@ -3724,13 +3654,15 @@ public class DevicePolicyManager { public static final int KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS = 1 << 3; /** - * Ignore trust agent state on secure keyguard screens - * (e.g. PIN/Pattern/Password). + * Disable trust agents on secure keyguard screens (e.g. PIN/Pattern/Password). + * By setting this flag alone, all trust agents are disabled. If the admin then wants to + * whitelist specific features of some trust agent, {@link #setTrustAgentConfiguration} can be + * used in conjuction to set trust-agent-specific configurations. */ public static final int KEYGUARD_DISABLE_TRUST_AGENTS = 1 << 4; /** - * Disable fingerprint sensor on keyguard secure screens (e.g. PIN/Pattern/Password). + * Disable fingerprint authentication on keyguard secure screens (e.g. PIN/Pattern/Password). */ public static final int KEYGUARD_DISABLE_FINGERPRINT = 1 << 5; @@ -3740,6 +3672,25 @@ public class DevicePolicyManager { public static final int KEYGUARD_DISABLE_REMOTE_INPUT = 1 << 6; /** + * Disable face authentication on keyguard secure screens (e.g. PIN/Pattern/Password). + */ + public static final int KEYGUARD_DISABLE_FACE = 1 << 7; + + /** + * Disable iris authentication on keyguard secure screens (e.g. PIN/Pattern/Password). + */ + public static final int KEYGUARD_DISABLE_IRIS = 1 << 8; + + /** + * Disable all biometric authentication on keyguard secure screens (e.g. PIN/Pattern/Password). + */ + public static final int KEYGUARD_DISABLE_BIOMETRICS = + DevicePolicyManager.KEYGUARD_DISABLE_FACE + | DevicePolicyManager.KEYGUARD_DISABLE_IRIS + | DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT; + + + /** * Disable all current and future keyguard customizations. */ public static final int KEYGUARD_DISABLE_FEATURES_ALL = 0x7fffffff; @@ -3757,7 +3708,7 @@ public class DevicePolicyManager { /** * Called by an application that is administering the device to request that the storage system - * be encrypted. + * be encrypted. Does nothing if the caller is on a secondary user or a managed profile. * <p> * When multiple device administrators attempt to control device encryption, the most secure, * supported setting will always be used. If any device administrator requests device @@ -3779,10 +3730,13 @@ public class DevicePolicyManager { * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param encrypt true to request encryption, false to release any previous request - * @return the new request status (for all active admins) - will be one of - * {@link #ENCRYPTION_STATUS_UNSUPPORTED}, {@link #ENCRYPTION_STATUS_INACTIVE}, or - * {@link #ENCRYPTION_STATUS_ACTIVE}. This is the value of the requests; Use - * {@link #getStorageEncryptionStatus()} to query the actual device state. + * @return the new total request status (for all active admins), or {@link + * DevicePolicyManager#ENCRYPTION_STATUS_UNSUPPORTED} if called for a non-system user. + * Will be one of {@link #ENCRYPTION_STATUS_UNSUPPORTED}, {@link + * #ENCRYPTION_STATUS_INACTIVE}, or {@link #ENCRYPTION_STATUS_ACTIVE}. This is the value + * of the requests; use {@link #getStorageEncryptionStatus()} to query the actual device + * state. + * * @throws SecurityException if {@code admin} is not an active administrator or does not use * {@link DeviceAdminInfo#USES_ENCRYPTED_STORAGE} */ @@ -4088,7 +4042,11 @@ public class DevicePolicyManager { */ public boolean installKeyPair(@Nullable ComponentName admin, @NonNull PrivateKey privKey, @NonNull Certificate[] certs, @NonNull String alias, boolean requestAccess) { - return installKeyPair(admin, privKey, certs, alias, requestAccess, true); + int flags = INSTALLKEY_SET_USER_SELECTABLE; + if (requestAccess) { + flags |= INSTALLKEY_REQUEST_CREDENTIALS_ACCESS; + } + return installKeyPair(admin, privKey, certs, alias, flags); } /** @@ -4112,13 +4070,9 @@ public class DevicePolicyManager { * {@link android.security.KeyChain#getCertificateChain}. * @param alias The private key alias under which to install the certificate. If a certificate * with that alias already exists, it will be overwritten. - * @param requestAccess {@code true} to request that the calling app be granted access to the - * credentials immediately. Otherwise, access to the credentials will be gated by user - * approval. - * @param isUserSelectable {@code true} to indicate that a user can select this key via the - * Certificate Selection prompt, false to indicate that this key can only be granted - * access by implementing - * {@link android.app.admin.DeviceAdminReceiver#onChoosePrivateKeyAlias}. + * @param flags Flags to request that the calling app be granted access to the credentials + * and set the key to be user-selectable. See {@link #INSTALLKEY_SET_USER_SELECTABLE} and + * {@link #INSTALLKEY_REQUEST_CREDENTIALS_ACCESS}. * @return {@code true} if the keys were installed, {@code false} otherwise. * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile * owner. @@ -4127,9 +4081,12 @@ public class DevicePolicyManager { * @see #DELEGATION_CERT_INSTALL */ public boolean installKeyPair(@Nullable ComponentName admin, @NonNull PrivateKey privKey, - @NonNull Certificate[] certs, @NonNull String alias, boolean requestAccess, - boolean isUserSelectable) { + @NonNull Certificate[] certs, @NonNull String alias, int flags) { throwIfParentInstance("installKeyPair"); + boolean requestAccess = (flags & INSTALLKEY_REQUEST_CREDENTIALS_ACCESS) + == INSTALLKEY_REQUEST_CREDENTIALS_ACCESS; + boolean isUserSelectable = (flags & INSTALLKEY_SET_USER_SELECTABLE) + == INSTALLKEY_SET_USER_SELECTABLE; try { final byte[] pemCert = Credentials.convertToPem(certs[0]); byte[] pemChain = null; @@ -4208,6 +4165,8 @@ public class DevicePolicyManager { * algorithm specification in {@code keySpec} is not {@code RSAKeyGenParameterSpec} * or {@code ECGenParameterSpec}, or if Device ID attestation was requested but the * {@code keySpec} does not contain an attestation challenge. + * @throws UnsupportedOperationException if Device ID attestation was requested but the + * underlying hardware does not support it. * @see KeyGenParameterSpec.Builder#setAttestationChallenge(byte[]) */ public AttestedKeyPair generateKeyPair(@Nullable ComponentName admin, @@ -5511,7 +5470,7 @@ public class DevicePolicyManager { @SystemApi public @Nullable ComponentName getProfileOwner() throws IllegalArgumentException { throwIfParentInstance("getProfileOwner"); - return getProfileOwnerAsUser(Process.myUserHandle().getIdentifier()); + return getProfileOwnerAsUser(mContext.getUserId()); } /** @@ -5539,7 +5498,7 @@ public class DevicePolicyManager { public @Nullable String getProfileOwnerName() throws IllegalArgumentException { if (mService != null) { try { - return mService.getProfileOwnerName(Process.myUserHandle().getIdentifier()); + return mService.getProfileOwnerName(mContext.getUserId()); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -5569,10 +5528,13 @@ public class DevicePolicyManager { } /** - * Called by a profile owner or device owner to add a default intent handler activity for - * intents that match a certain intent filter. This activity will remain the default intent - * handler even if the set of potential event handlers for the intent filter changes and if the - * intent preferences are reset. + * Called by a profile owner or device owner to set a default activity that the system selects + * to handle intents that match the given {@link IntentFilter}. This activity will remain the + * default intent handler even if the set of potential event handlers for the intent filter + * changes and if the intent preferences are reset. + * <p> + * Note that the caller should still declare the activity in the manifest, the API just sets + * the activity to be the default one to handle the given intent filter. * <p> * The default disambiguation mechanism takes over if the activity is not installed (anymore). * When the activity is (re)installed, it is automatically reset as default intent handler for @@ -5622,6 +5584,29 @@ public class DevicePolicyManager { } /** + * Called by a device owner to set the default SMS application. + * <p> + * The calling device admin must be a device owner. If it is not, a security exception will be + * thrown. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param packageName The name of the package to set as the default SMS application. + * @throws SecurityException if {@code admin} is not a device owner. + * + * @hide + */ + public void setDefaultSmsApplication(@NonNull ComponentName admin, String packageName) { + throwIfParentInstance("setDefaultSmsApplication"); + if (mService != null) { + try { + mService.setDefaultSmsApplication(admin, packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** * Called by a profile owner or device owner to grant permission to a package to manage * application restrictions for the calling user via {@link #setApplicationRestrictions} and * {@link #getApplicationRestrictions}. @@ -5760,11 +5745,20 @@ public class DevicePolicyManager { } /** - * Sets a list of configuration features to enable for a TrustAgent component. This is meant to + * Sets a list of configuration features to enable for a trust agent component. This is meant to * be used in conjunction with {@link #KEYGUARD_DISABLE_TRUST_AGENTS}, which disables all trust * agents but those enabled by this function call. If flag * {@link #KEYGUARD_DISABLE_TRUST_AGENTS} is not set, then this call has no effect. * <p> + * For any specific trust agent, whether it is disabled or not depends on the aggregated state + * of each admin's {@link #KEYGUARD_DISABLE_TRUST_AGENTS} setting and its trust agent + * configuration as set by this function call. In particular: if any admin sets + * {@link #KEYGUARD_DISABLE_TRUST_AGENTS} and does not additionally set any + * trust agent configuration, the trust agent is disabled completely. Otherwise, the trust agent + * will receive the list of configurations from all admins who set + * {@link #KEYGUARD_DISABLE_TRUST_AGENTS} and aggregate the configurations to determine its + * behavior. The exact meaning of aggregation is trust-agent-specific. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_DISABLE_KEYGUARD_FEATURES} to be able to call this method; * if not, a security exception will be thrown. @@ -5774,17 +5768,10 @@ public class DevicePolicyManager { * the parent profile. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. - * @param target Component name of the agent to be enabled. - * @param configuration TrustAgent-specific feature bundle. If null for any admin, agent will be - * strictly disabled according to the state of the - * {@link #KEYGUARD_DISABLE_TRUST_AGENTS} flag. - * <p> - * If {@link #KEYGUARD_DISABLE_TRUST_AGENTS} is set and options is not null for all - * admins, then it's up to the TrustAgent itself to aggregate the values from all - * device admins. - * <p> - * Consult documentation for the specific TrustAgent to determine legal options - * parameters. + * @param target Component name of the agent to be configured. + * @param configuration Trust-agent-specific feature configuration bundle. Please consult + * documentation of the specific trust agent to determine the interpretation of this + * bundle. * @throws SecurityException if {@code admin} is not an active administrator or does not use * {@link DeviceAdminInfo#USES_POLICY_DISABLE_KEYGUARD_FEATURES} */ @@ -5845,7 +5832,7 @@ public class DevicePolicyManager { * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param disabled If true caller-Id information in the managed profile is not displayed. - * @throws SecurityException if {@code admin} is not a device or profile owner. + * @throws SecurityException if {@code admin} is not a profile owner. */ public void setCrossProfileCallerIdDisabled(@NonNull ComponentName admin, boolean disabled) { throwIfParentInstance("setCrossProfileCallerIdDisabled"); @@ -5866,7 +5853,7 @@ public class DevicePolicyManager { * thrown. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. - * @throws SecurityException if {@code admin} is not a device or profile owner. + * @throws SecurityException if {@code admin} is not a profile owner. */ public boolean getCrossProfileCallerIdDisabled(@NonNull ComponentName admin) { throwIfParentInstance("getCrossProfileCallerIdDisabled"); @@ -5906,7 +5893,7 @@ public class DevicePolicyManager { * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param disabled If true contacts search in the managed profile is not displayed. - * @throws SecurityException if {@code admin} is not a device or profile owner. + * @throws SecurityException if {@code admin} is not a profile owner. */ public void setCrossProfileContactsSearchDisabled(@NonNull ComponentName admin, boolean disabled) { @@ -5928,7 +5915,7 @@ public class DevicePolicyManager { * thrown. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. - * @throws SecurityException if {@code admin} is not a device or profile owner. + * @throws SecurityException if {@code admin} is not a profile owner. */ public boolean getCrossProfileContactsSearchDisabled(@NonNull ComponentName admin) { throwIfParentInstance("getCrossProfileContactsSearchDisabled"); @@ -5999,7 +5986,7 @@ public class DevicePolicyManager { * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param disabled If true, bluetooth devices cannot access enterprise contacts. - * @throws SecurityException if {@code admin} is not a device or profile owner. + * @throws SecurityException if {@code admin} is not a profile owner. */ public void setBluetoothContactSharingDisabled(@NonNull ComponentName admin, boolean disabled) { throwIfParentInstance("setBluetoothContactSharingDisabled"); @@ -6022,7 +6009,7 @@ public class DevicePolicyManager { * This API works on managed profile only. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. - * @throws SecurityException if {@code admin} is not a device or profile owner. + * @throws SecurityException if {@code admin} is not a profile owner. */ public boolean getBluetoothContactSharingDisabled(@NonNull ComponentName admin) { throwIfParentInstance("getBluetoothContactSharingDisabled"); @@ -6092,7 +6079,7 @@ public class DevicePolicyManager { * {@link UserManager#DISALLOW_SHARE_INTO_MANAGED_PROFILE}. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. - * @throws SecurityException if {@code admin} is not a device or profile owner. + * @throws SecurityException if {@code admin} is not a profile owner. */ public void clearCrossProfileIntentFilters(@NonNull ComponentName admin) { throwIfParentInstance("clearCrossProfileIntentFilters"); @@ -6106,21 +6093,22 @@ public class DevicePolicyManager { } /** - * Called by a profile or device owner to set the permitted accessibility services. When set by + * Called by a profile or device owner to set the permitted + * {@link android.accessibilityservice.AccessibilityService}. When set by * a device owner or profile owner the restriction applies to all profiles of the user the - * device owner or profile owner is an admin for. By default the user can use any accessiblity - * service. When zero or more packages have been added, accessiblity services that are not in + * device owner or profile owner is an admin for. By default, the user can use any accessibility + * service. When zero or more packages have been added, accessibility services that are not in * the list and not part of the system can not be enabled by the user. * <p> * Calling with a null value for the list disables the restriction so that all services can be - * used, calling with an empty list only allows the builtin system's services. + * used, calling with an empty list only allows the built-in system services. Any non-system + * accessibility service that's currently enabled must be included in the list. * <p> * System accessibility services are always available to the user the list can't modify this. - * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param packageNames List of accessibility service package names. - * @return true if setting the restriction succeeded. It fail if there is one or more non-system - * accessibility services enabled, that are not in the list. + * @return {@code true} if the operation succeeded, or {@code false} if the list didn't + * contain every enabled non-system accessibility service. * @throws SecurityException if {@code admin} is not a device or profile owner. */ public boolean setPermittedAccessibilityServices(@NonNull ComponentName admin, @@ -6209,10 +6197,11 @@ public class DevicePolicyManager { /** * Called by a profile or device owner to set the permitted input methods services. When set by * a device owner or profile owner the restriction applies to all profiles of the user the - * device owner or profile owner is an admin for. By default the user can use any input method. + * device owner or profile owner is an admin for. By default, the user can use any input method. * When zero or more packages have been added, input method that are not in the list and not * part of the system can not be enabled by the user. This method will fail if it is called for - * a admin that is not for the foreground user or a profile of the foreground user. + * a admin that is not for the foreground user or a profile of the foreground user. Any + * non-system input method service that's currently enabled must be included in the list. * <p> * Calling with a null value for the list disables the restriction so that all input methods can * be used, calling with an empty list disables all but the system's own input methods. @@ -6221,8 +6210,8 @@ public class DevicePolicyManager { * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param packageNames List of input method package names. - * @return true if setting the restriction succeeded. It will fail if there are one or more - * non-system input methods currently enabled that are not in the packageNames list. + * @return {@code true} if the operation succeeded, or {@code false} if the list didn't + * contain every enabled non-system input method service. * @throws SecurityException if {@code admin} is not a device or profile owner. */ public boolean setPermittedInputMethods( @@ -6295,6 +6284,7 @@ public class DevicePolicyManager { * @hide */ @SystemApi + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public @Nullable List<String> getPermittedInputMethodsForCurrentUser() { throwIfParentInstance("getPermittedInputMethodsForCurrentUser"); if (mService != null) { @@ -6544,6 +6534,9 @@ public class DevicePolicyManager { * <p> * If the adminExtras are not null, they will be stored on the device until the user is started * for the first time. Then the extras will be passed to the admin when onEnable is called. + * <p>From {@link android.os.Build.VERSION_CODES#P} onwards, if targeting + * {@link android.os.Build.VERSION_CODES#P}, throws {@link UserOperationException} instead of + * returning {@code null} on failure. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param name The user's name. @@ -6558,6 +6551,9 @@ public class DevicePolicyManager { * @return the {@link android.os.UserHandle} object for the created user, or {@code null} if the * user could not be created. * @throws SecurityException if {@code admin} is not a device owner. + * @throws UserOperationException if the user could not be created and the calling app is + * targeting {@link android.os.Build.VERSION_CODES#P} and running on + * {@link android.os.Build.VERSION_CODES#P}. */ public @Nullable UserHandle createAndManageUser(@NonNull ComponentName admin, @NonNull String name, @@ -6566,6 +6562,8 @@ public class DevicePolicyManager { throwIfParentInstance("createAndManageUser"); try { return mService.createAndManageUser(admin, name, profileOwner, adminExtras, flags); + } catch (ServiceSpecificException e) { + throw new UserOperationException(e.getMessage(), e.errorCode); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -6612,12 +6610,16 @@ public class DevicePolicyManager { * Called by a device owner to start the specified secondary user in background. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. - * @param userHandle the user to be stopped. - * @return {@code true} if the user can be started, {@code false} otherwise. + * @param userHandle the user to be started in background. + * @return one of the following result codes: + * {@link UserManager#USER_OPERATION_ERROR_UNKNOWN}, + * {@link UserManager#USER_OPERATION_SUCCESS}, + * {@link UserManager#USER_OPERATION_ERROR_MANAGED_PROFILE}, + * {@link UserManager#USER_OPERATION_ERROR_MAX_RUNNING_USERS}, * @throws SecurityException if {@code admin} is not a device owner. * @see #getSecondaryUsers(ComponentName) */ - public boolean startUserInBackground( + public @UserOperationResult int startUserInBackground( @NonNull ComponentName admin, @NonNull UserHandle userHandle) { throwIfParentInstance("startUserInBackground"); try { @@ -6632,11 +6634,16 @@ public class DevicePolicyManager { * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param userHandle the user to be stopped. - * @return {@code true} if the user can be stopped, {@code false} otherwise. + * @return one of the following result codes: + * {@link UserManager#USER_OPERATION_ERROR_UNKNOWN}, + * {@link UserManager#USER_OPERATION_SUCCESS}, + * {@link UserManager#USER_OPERATION_ERROR_MANAGED_PROFILE}, + * {@link UserManager#USER_OPERATION_ERROR_CURRENT_USER} * @throws SecurityException if {@code admin} is not a device owner. * @see #getSecondaryUsers(ComponentName) */ - public boolean stopUser(@NonNull ComponentName admin, @NonNull UserHandle userHandle) { + public @UserOperationResult int stopUser( + @NonNull ComponentName admin, @NonNull UserHandle userHandle) { throwIfParentInstance("stopUser"); try { return mService.stopUser(admin, userHandle); @@ -6650,11 +6657,15 @@ public class DevicePolicyManager { * calling user and switch back to primary. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. - * @return {@code true} if the exit was successful, {@code false} otherwise. + * @return one of the following result codes: + * {@link UserManager#USER_OPERATION_ERROR_UNKNOWN}, + * {@link UserManager#USER_OPERATION_SUCCESS}, + * {@link UserManager#USER_OPERATION_ERROR_MANAGED_PROFILE}, + * {@link UserManager#USER_OPERATION_ERROR_CURRENT_USER} * @throws SecurityException if {@code admin} is not a profile owner affiliated with the device. * @see #getSecondaryUsers(ComponentName) */ - public boolean logoutUser(@NonNull ComponentName admin) { + public @UserOperationResult int logoutUser(@NonNull ComponentName admin) { throwIfParentInstance("logoutUser"); try { return mService.logoutUser(admin); @@ -7101,30 +7112,24 @@ public class DevicePolicyManager { } /** - * Sets which system features to enable for LockTask mode. - * <p> - * Feature flags set through this method will only take effect for the duration when the device - * is in LockTask mode. If this method is not called, none of the features listed here will be - * enabled. - * <p> - * This function can only be called by the device owner, a profile owner of an affiliated user - * or profile, or the profile owner when no device owner is set. See {@link #isAffiliatedUser}. - * Any features set via this method will be cleared if the user becomes unaffiliated. + * Sets which system features are enabled when the device runs in lock task mode. This method + * doesn't affect the features when lock task mode is inactive. Any system features not included + * in {@code flags} are implicitly disabled when calling this method. By default, only + * {@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS} is enabled—all the other features are disabled. To + * disable the global actions dialog, call this method omitting + * {@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS}. + * + * <p>This method can only be called by the device owner, a profile owner of an affiliated + * user or profile, or the profile owner when no device owner is set. See + * {@link #isAffiliatedUser}. + * Any features set using this method are cleared if the user becomes unaffiliated. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. - * @param flags Bitfield of feature flags: - * {@link #LOCK_TASK_FEATURE_NONE} (default), - * {@link #LOCK_TASK_FEATURE_SYSTEM_INFO}, - * {@link #LOCK_TASK_FEATURE_NOTIFICATIONS}, - * {@link #LOCK_TASK_FEATURE_HOME}, - * {@link #LOCK_TASK_FEATURE_RECENTS}, - * {@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS}, - * {@link #LOCK_TASK_FEATURE_KEYGUARD} + * @param flags The system features enabled during lock task mode. * @throws SecurityException if {@code admin} is not the device owner, the profile owner of an * affiliated user or profile, or the profile owner when no device owner is set. * @see #isAffiliatedUser - * @throws SecurityException if {@code admin} is not the device owner or the profile owner. - */ + **/ public void setLockTaskFeatures(@NonNull ComponentName admin, @LockTaskFeature int flags) { throwIfParentInstance("setLockTaskFeatures"); if (mService != null) { @@ -7207,12 +7212,22 @@ public class DevicePolicyManager { } } + /** @hide */ + @StringDef({ + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS, + Settings.System.SCREEN_OFF_TIMEOUT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SystemSettingsWhitelist {} + /** - * Called by device owner to update {@link android.provider.Settings.System} settings. - * Validation that the value of the setting is in the correct form for the setting type should - * be performed by the caller. + * Called by a device or profile owner to update {@link android.provider.Settings.System} + * settings. Validation that the value of the setting is in the correct form for the setting + * type should be performed by the caller. * <p> - * The settings that can be updated with this method are: + * The settings that can be updated by a device owner or profile owner of secondary user with + * this method are: * <ul> * <li>{@link android.provider.Settings.System#SCREEN_BRIGHTNESS}</li> * <li>{@link android.provider.Settings.System#SCREEN_BRIGHTNESS_MODE}</li> @@ -7224,10 +7239,10 @@ public class DevicePolicyManager { * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param setting The name of the setting to update. * @param value The value to update the setting to. - * @throws SecurityException if {@code admin} is not a device owner. + * @throws SecurityException if {@code admin} is not a device or profile owner. */ - public void setSystemSetting(@NonNull ComponentName admin, @NonNull String setting, - String value) { + public void setSystemSetting(@NonNull ComponentName admin, + @NonNull @SystemSettingsWhitelist String setting, String value) { throwIfParentInstance("setSystemSetting"); if (mService != null) { try { @@ -7540,13 +7555,28 @@ public class DevicePolicyManager { /** * Called by device owners to set a local system update policy. When a new policy is set, * {@link #ACTION_SYSTEM_UPDATE_POLICY_CHANGED} is broadcasted. + * <p> + * If the supplied system update policy has freeze periods set but the freeze periods do not + * meet 90-day maximum length or 60-day minimum separation requirement set out in + * {@link SystemUpdatePolicy#setFreezePeriods}, + * {@link SystemUpdatePolicy.ValidationFailedException} will the thrown. Note that the system + * keeps a record of freeze periods the device experienced previously, and combines them with + * the new freeze periods to be set when checking the maximum freeze length and minimum freeze + * separation constraints. As a result, freeze periods that passed validation during + * {@link SystemUpdatePolicy#setFreezePeriods} might fail the additional checks here due to + * the freeze period history. If this is causing issues during development, + * {@code adb shell dpm clear-freeze-period-record} can be used to clear the record. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. All * components in the device owner package can set system update policies and the most * recent policy takes effect. * @param policy the new policy, or {@code null} to clear the current policy. * @throws SecurityException if {@code admin} is not a device owner. + * @throws IllegalArgumentException if the policy type or maintenance window is not valid. + * @throws SystemUpdatePolicy.ValidationFailedException if the policy's freeze period does not + * meet the requirement. * @see SystemUpdatePolicy + * @see SystemUpdatePolicy#setFreezePeriods(List) */ public void setSystemUpdatePolicy(@NonNull ComponentName admin, SystemUpdatePolicy policy) { throwIfParentInstance("setSystemUpdatePolicy"); @@ -7577,6 +7607,23 @@ public class DevicePolicyManager { } /** + * Reset record of previous system update freeze period the device went through. + * Only callable by ADB. + * @hide + */ + public void clearSystemUpdatePolicyFreezePeriodRecord() { + throwIfParentInstance("clearSystemUpdatePolicyFreezePeriodRecord"); + if (mService == null) { + return; + } + try { + mService.clearSystemUpdatePolicyFreezePeriodRecord(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Called by a device owner or profile owner of secondary users that is affiliated with the * device to disable the keyguard altogether. * <p> @@ -7759,11 +7806,14 @@ public class DevicePolicyManager { * {@link #PERMISSION_GRANT_STATE_DEFAULT default} in which a user can manage it through the UI, * {@link #PERMISSION_GRANT_STATE_DENIED denied}, in which the permission is denied and the user * cannot manage it through the UI, and {@link #PERMISSION_GRANT_STATE_GRANTED granted} in which - * the permission is granted and the user cannot manage it through the UI. This might affect all - * permissions in a group that the runtime permission belongs to. This method can only be called - * by a profile owner, device owner, or a delegate given the + * the permission is granted and the user cannot manage it through the UI. This method can only + * be called by a profile owner, device owner, or a delegate given the * {@link #DELEGATION_PERMISSION_GRANT} scope via {@link #setDelegatedScopes}. * <p/> + * Note that user cannot manage other permissions in the affected group through the UI + * either and their granted state will be kept as the current value. Thus, it's recommended that + * you set the grant state of all the permissions in the affected group. + * <p/> * Setting the grant state to {@link #PERMISSION_GRANT_STATE_DEFAULT default} does not revoke * the permission. It retains the previous grant, if any. * <p/> @@ -8214,6 +8264,22 @@ public class DevicePolicyManager { } /** + * Forces a batch of security logs to be fetched from logd and makes it available for DPC. + * Only callable by ADB. If throttled, returns time to wait in milliseconds, otherwise 0. + * @hide + */ + public long forceSecurityLogs() { + if (mService == null) { + return 0; + } + try { + return mService.forceSecurityLogs(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Called by the system to obtain a {@link DevicePolicyManager} whose calls act on the parent * profile. * @@ -8230,19 +8296,19 @@ public class DevicePolicyManager { } /** - * Called by a device or profile owner to restrict packages from accessing metered data. + * Called by a device or profile owner to restrict packages from using metered data. * * @param admin which {@link DeviceAdminReceiver} this request is associated with. * @param packageNames the list of package names to be restricted. * @return a list of package names which could not be restricted. * @throws SecurityException if {@code admin} is not a device or profile owner. */ - public @NonNull List<String> setMeteredDataDisabled(@NonNull ComponentName admin, + public @NonNull List<String> setMeteredDataDisabledPackages(@NonNull ComponentName admin, @NonNull List<String> packageNames) { throwIfParentInstance("setMeteredDataDisabled"); if (mService != null) { try { - return mService.setMeteredDataDisabled(admin, packageNames); + return mService.setMeteredDataDisabledPackages(admin, packageNames); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -8252,17 +8318,17 @@ public class DevicePolicyManager { /** * Called by a device or profile owner to retrieve the list of packages which are restricted - * by the admin from accessing metered data. + * by the admin from using metered data. * * @param admin which {@link DeviceAdminReceiver} this request is associated with. * @return the list of restricted package names. * @throws SecurityException if {@code admin} is not a device or profile owner. */ - public @NonNull List<String> getMeteredDataDisabled(@NonNull ComponentName admin) { + public @NonNull List<String> getMeteredDataDisabledPackages(@NonNull ComponentName admin) { throwIfParentInstance("getMeteredDataDisabled"); if (mService != null) { try { - return mService.getMeteredDataDisabled(admin); + return mService.getMeteredDataDisabledPackages(admin); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -8271,6 +8337,30 @@ public class DevicePolicyManager { } /** + * Called by the system to check if a package is restricted from using metered data + * by {@param admin}. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with. + * @param packageName the package whose restricted status is needed. + * @param userId the user to which {@param packageName} belongs. + * @return {@code true} if the package is restricted by admin, otherwise {@code false} + * @throws SecurityException if the caller doesn't run with {@link Process#SYSTEM_UID} + * @hide + */ + public boolean isMeteredDataDisabledPackageForUser(@NonNull ComponentName admin, + String packageName, @UserIdInt int userId) { + throwIfParentInstance("getMeteredDataDisabledForUser"); + if (mService != null) { + try { + return mService.isMeteredDataDisabledPackageForUser(admin, packageName, userId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + return false; + } + + /** * Called by device owners to retrieve device logs from before the device's last reboot. * <p> * <strong> This API is not supported on all devices. Calling this API on unsupported devices @@ -8460,6 +8550,7 @@ public class DevicePolicyManager { * @hide */ @SystemApi + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) @UserProvisioningState public int getUserProvisioningState() { throwIfParentInstance("getUserProvisioningState"); @@ -8604,6 +8695,7 @@ public class DevicePolicyManager { * @hide */ @SystemApi + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned() { try { return mService.isDeviceProvisioned(); @@ -8727,14 +8819,20 @@ public class DevicePolicyManager { * <p>If backups were disabled and a non-null backup transport {@link ComponentName} is * specified, backups will be enabled. * + * <p>NOTE: The method shouldn't be called on the main thread. + * * @param admin admin Which {@link DeviceAdminReceiver} this request is associated with. * @param backupTransportComponent The backup transport layer to be used for mandatory backups. + * @return {@code true} if the backup transport was successfully set; {@code false} otherwise. * @throws SecurityException if {@code admin} is not a device owner. */ - public void setMandatoryBackupTransport( - @NonNull ComponentName admin, @Nullable ComponentName backupTransportComponent) { + @WorkerThread + public boolean setMandatoryBackupTransport( + @NonNull ComponentName admin, + @Nullable ComponentName backupTransportComponent) { + throwIfParentInstance("setMandatoryBackupTransport"); try { - mService.setMandatoryBackupTransport(admin, backupTransportComponent); + return mService.setMandatoryBackupTransport(admin, backupTransportComponent); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -8748,6 +8846,7 @@ public class DevicePolicyManager { * mandatory or {@code null} if backups are not mandatory. */ public ComponentName getMandatoryBackupTransport() { + throwIfParentInstance("getMandatoryBackupTransport"); try { return mService.getMandatoryBackupTransport(); } catch (RemoteException re) { @@ -9045,15 +9144,15 @@ public class DevicePolicyManager { * @param executor The executor through which the listener should be invoked. * @param listener A callback object that will inform the caller when the clearing is done. * @throws SecurityException if the caller is not the device owner/profile owner. - * @return whether the clearing succeeded. */ - public boolean clearApplicationUserData(@NonNull ComponentName admin, + public void clearApplicationUserData(@NonNull ComponentName admin, @NonNull String packageName, @NonNull @CallbackExecutor Executor executor, @NonNull OnClearApplicationUserDataListener listener) { throwIfParentInstance("clearAppData"); Preconditions.checkNotNull(executor); + Preconditions.checkNotNull(listener); try { - return mService.clearApplicationUserData(admin, packageName, + mService.clearApplicationUserData(admin, packageName, new IPackageDataObserver.Stub() { public void onRemoveCompleted(String pkg, boolean succeeded) { executor.execute(() -> @@ -9153,9 +9252,10 @@ public class DevicePolicyManager { * after calling this method. * * <p>The incoming target administrator must have the - * {@link DeviceAdminReceiver#SUPPORT_TRANSFER_OWNERSHIP_META_DATA} <code>meta-data</code> tag - * included in its corresponding <code>receiver</code> component with a value of {@code true}. - * Otherwise an {@link IllegalArgumentException} will be thrown. + * <code><support-transfer-ownership /></code> tag inside the + * <code><device-admin></device-admin></code> tags in the xml file referenced by + * {@link DeviceAdminReceiver#DEVICE_ADMIN_META_DATA}. Otherwise an + * {@link IllegalArgumentException} will be thrown. * * @param admin which {@link DeviceAdminReceiver} this request is associated with * @param target which {@link DeviceAdminReceiver} we want the new administrator to be @@ -9255,43 +9355,27 @@ public class DevicePolicyManager { } /** - * Allows/disallows printing. - * - * Called by a device owner or a profile owner. - * Device owner changes policy for all users. Profile owner can override it if present. - * Printing is enabled by default. If {@code FEATURE_PRINTING} is absent, the call is ignored. - * - * @param admin which {@link DeviceAdminReceiver} this request is associated with. - * @param enabled whether printing should be allowed or not. - * @throws SecurityException if {@code admin} is neither device, nor profile owner. - */ - public void setPrintingEnabled(@NonNull ComponentName admin, boolean enabled) { - try { - mService.setPrintingEnabled(admin, enabled); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } - } - - /** - * Returns whether printing is enabled for this user. - * - * Always {@code false} if {@code FEATURE_PRINTING} is absent. - * Otherwise, {@code true} by default. - * - * @return {@code true} iff printing is enabled. - */ - public boolean isPrintingEnabled() { - try { - return mService.isPrintingEnabled(); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } - } - - /** * Called by device owner to add an override APN. * + * <p>This method may returns {@code -1} if {@code apnSetting} conflicts with an existing + * override APN. Update the existing conflicted APN with + * {@link #updateOverrideApn(ComponentName, int, ApnSetting)} instead of adding a new entry. + * <p>Two override APNs are considered to conflict when all the following APIs return + * the same values on both override APNs: + * <ul> + * <li>{@link ApnSetting#getOperatorNumeric()}</li> + * <li>{@link ApnSetting#getApnName()}</li> + * <li>{@link ApnSetting#getProxyAddress()}</li> + * <li>{@link ApnSetting#getProxyPort()}</li> + * <li>{@link ApnSetting#getMmsProxyAddress()}</li> + * <li>{@link ApnSetting#getMmsProxyPort()}</li> + * <li>{@link ApnSetting#getMmsc()}</li> + * <li>{@link ApnSetting#isEnabled()}</li> + * <li>{@link ApnSetting#getMvnoType()}</li> + * <li>{@link ApnSetting#getProtocol()}</li> + * <li>{@link ApnSetting#getRoamingProtocol()}</li> + * </ul> + * * @param admin which {@link DeviceAdminReceiver} this request is associated with * @param apnSetting the override APN to insert * @return The {@code id} of inserted override APN. Or {@code -1} when failed to insert into @@ -9315,6 +9399,12 @@ public class DevicePolicyManager { /** * Called by device owner to update an override APN. * + * <p>This method may returns {@code false} if there is no override APN with the given + * {@code apnId}. + * <p>This method may also returns {@code false} if {@code apnSetting} conflicts with an + * existing override APN. Update the existing conflicted APN instead. + * <p>See {@link #addOverrideApn} for the definition of conflict. + * * @param admin which {@link DeviceAdminReceiver} this request is associated with * @param apnId the {@code id} of the override APN to update * @param apnSetting the override APN to update @@ -9340,6 +9430,9 @@ public class DevicePolicyManager { /** * Called by device owner to remove an override APN. * + * <p>This method may returns {@code false} if there is no override APN with the given + * {@code apnId}. + * * @param admin which {@link DeviceAdminReceiver} this request is associated with * @param apnId the {@code id} of the override APN to remove * @return {@code true} if the required override APN is successfully removed, {@code false} @@ -9426,12 +9519,19 @@ public class DevicePolicyManager { /** * Returns the data passed from the current administrator to the new administrator during an * ownership transfer. This is the same {@code bundle} passed in - * {@link #transferOwnership(ComponentName, ComponentName, PersistableBundle)}. + * {@link #transferOwnership(ComponentName, ComponentName, PersistableBundle)}. The bundle is + * persisted until the profile owner or device owner is removed. + * + * <p>This is the same <code>bundle</code> received in the + * {@link DeviceAdminReceiver#onTransferOwnershipComplete(Context, PersistableBundle)}. + * Use this method to retrieve it after the transfer as long as the new administrator is the + * active device or profile owner. * * <p>Returns <code>null</code> if no ownership transfer was started for the calling user. * * @see #transferOwnership * @see DeviceAdminReceiver#onTransferOwnershipComplete(Context, PersistableBundle) + * @throws SecurityException if the caller is not a device or profile owner. */ @Nullable public PersistableBundle getTransferOwnershipBundle() { diff --git a/android/app/admin/DevicePolicyManagerInternal.java b/android/app/admin/DevicePolicyManagerInternal.java index ebaf4648..de929789 100644 --- a/android/app/admin/DevicePolicyManagerInternal.java +++ b/android/app/admin/DevicePolicyManagerInternal.java @@ -141,4 +141,10 @@ public abstract class DevicePolicyManagerInternal { * @return localized error message */ public abstract CharSequence getPrintingDisabledReasonForUser(@UserIdInt int userId); + + /** + * @return cached version of DPM policies that can be accessed without risking deadlocks. + * Do not call it directly. Use {@link DevicePolicyCache#getInstance()} instead. + */ + protected abstract DevicePolicyCache getDevicePolicyCache(); } diff --git a/android/app/admin/FreezeInterval.java b/android/app/admin/FreezeInterval.java new file mode 100644 index 00000000..de5e21ac --- /dev/null +++ b/android/app/admin/FreezeInterval.java @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.app.admin; + +import android.app.admin.SystemUpdatePolicy.ValidationFailedException; +import android.util.Log; +import android.util.Pair; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +/** + * An interval representing one freeze period which repeats annually. We use the number of days + * since the start of (non-leap) year to define the start and end dates of an interval, both + * inclusive. If the end date is smaller than the start date, the interval is considered wrapped + * around the year-end. As far as an interval is concerned, February 29th should be treated as + * if it were February 28th: so an interval starting or ending on February 28th are not + * distinguishable from an interval on February 29th. When calulating interval length or + * distance between two dates, February 29th is also disregarded. + * + * @see SystemUpdatePolicy#setFreezePeriods + * @hide + */ +public class FreezeInterval { + private static final String TAG = "FreezeInterval"; + + private static final int DUMMY_YEAR = 2001; + static final int DAYS_IN_YEAR = 365; // 365 since DUMMY_YEAR is not a leap year + + final int mStartDay; // [1,365] + final int mEndDay; // [1,365] + + FreezeInterval(int startDay, int endDay) { + if (startDay < 1 || startDay > 365 || endDay < 1 || endDay > 365) { + throw new RuntimeException("Bad dates for Interval: " + startDay + "," + endDay); + } + mStartDay = startDay; + mEndDay = endDay; + } + + int getLength() { + return getEffectiveEndDay() - mStartDay + 1; + } + + boolean isWrapped() { + return mEndDay < mStartDay; + } + + /** + * Returns the effective end day, taking wrapping around year-end into consideration + */ + int getEffectiveEndDay() { + if (!isWrapped()) { + return mEndDay; + } else { + return mEndDay + DAYS_IN_YEAR; + } + } + + boolean contains(LocalDate localDate) { + final int daysOfYear = dayOfYearDisregardLeapYear(localDate); + if (!isWrapped()) { + // ---[start---now---end]--- + return (mStartDay <= daysOfYear) && (daysOfYear <= mEndDay); + } else { + // ---end]---[start---now--- + // or ---now---end]---[start--- + return (mStartDay <= daysOfYear) || (daysOfYear <= mEndDay); + } + } + + boolean after(LocalDate localDate) { + return mStartDay > dayOfYearDisregardLeapYear(localDate); + } + + /** + * Instantiate the current interval to real calendar dates, given a calendar date + * {@code now}. If the interval contains now, the returned calendar dates should be the + * current interval (in real calendar dates) that includes now. If the interval does not + * include now, the returned dates represents the next future interval. + * The result will always have the same month and dayOfMonth value as the non-instantiated + * interval itself. + */ + Pair<LocalDate, LocalDate> toCurrentOrFutureRealDates(LocalDate now) { + final int nowDays = dayOfYearDisregardLeapYear(now); + final int startYearAdjustment, endYearAdjustment; + if (contains(now)) { + // current interval + if (mStartDay <= nowDays) { + // ----------[start---now---end]--- + // or ---end]---[start---now---------- + startYearAdjustment = 0; + endYearAdjustment = isWrapped() ? 1 : 0; + } else /* nowDays <= mEndDay */ { + // or ---now---end]---[start---------- + startYearAdjustment = -1; + endYearAdjustment = 0; + } + } else { + // next interval + if (mStartDay > nowDays) { + // ----------now---[start---end]--- + // or ---end]---now---[start---------- + startYearAdjustment = 0; + endYearAdjustment = isWrapped() ? 1 : 0; + } else /* mStartDay <= nowDays */ { + // or ---[start---end]---now---------- + startYearAdjustment = 1; + endYearAdjustment = 1; + } + } + final LocalDate startDate = LocalDate.ofYearDay(DUMMY_YEAR, mStartDay).withYear( + now.getYear() + startYearAdjustment); + final LocalDate endDate = LocalDate.ofYearDay(DUMMY_YEAR, mEndDay).withYear( + now.getYear() + endYearAdjustment); + return new Pair<>(startDate, endDate); + } + + @Override + public String toString() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM dd"); + return LocalDate.ofYearDay(DUMMY_YEAR, mStartDay).format(formatter) + " - " + + LocalDate.ofYearDay(DUMMY_YEAR, mEndDay).format(formatter); + } + + // Treat the supplied date as in a non-leap year and return its day of year. + static int dayOfYearDisregardLeapYear(LocalDate date) { + return date.withYear(DUMMY_YEAR).getDayOfYear(); + } + + /** + * Compute the number of days between first (inclusive) and second (exclusive), + * treating all years in between as non-leap. + */ + public static int distanceWithoutLeapYear(LocalDate first, LocalDate second) { + return dayOfYearDisregardLeapYear(first) - dayOfYearDisregardLeapYear(second) + + DAYS_IN_YEAR * (first.getYear() - second.getYear()); + } + + /** + * Sort, de-duplicate and merge an interval list + * + * Instead of using any fancy logic for merging intervals which has loads of corner cases, + * simply flatten the interval onto a list of 365 calendar days and recreate the interval list + * from that. + * + * This method should return a list of intervals with the following post-conditions: + * 1. Interval.startDay in strictly ascending order + * 2. No two intervals should overlap or touch + * 3. At most one wrapped Interval remains, and it will be at the end of the list + * @hide + */ + protected static List<FreezeInterval> canonicalizeIntervals(List<FreezeInterval> intervals) { + boolean[] taken = new boolean[DAYS_IN_YEAR]; + // First convert the intervals into flat array + for (FreezeInterval interval : intervals) { + for (int i = interval.mStartDay; i <= interval.getEffectiveEndDay(); i++) { + taken[(i - 1) % DAYS_IN_YEAR] = true; + } + } + // Then reconstruct intervals from the array + List<FreezeInterval> result = new ArrayList<>(); + int i = 0; + while (i < DAYS_IN_YEAR) { + if (!taken[i]) { + i++; + continue; + } + final int intervalStart = i + 1; + while (i < DAYS_IN_YEAR && taken[i]) i++; + result.add(new FreezeInterval(intervalStart, i)); + } + // Check if the last entry can be merged to the first entry to become one single + // wrapped interval + final int lastIndex = result.size() - 1; + if (lastIndex > 0 && result.get(lastIndex).mEndDay == DAYS_IN_YEAR + && result.get(0).mStartDay == 1) { + FreezeInterval wrappedInterval = new FreezeInterval(result.get(lastIndex).mStartDay, + result.get(0).mEndDay); + result.set(lastIndex, wrappedInterval); + result.remove(0); + } + return result; + } + + /** + * Verifies if the supplied freeze periods satisfies the constraints set out in + * {@link SystemUpdatePolicy#setFreezePeriods(List)}, and in particular, any single freeze + * period cannot exceed {@link SystemUpdatePolicy#FREEZE_PERIOD_MAX_LENGTH} days, and two freeze + * periods need to be at least {@link SystemUpdatePolicy#FREEZE_PERIOD_MIN_SEPARATION} days + * apart. + * + * @hide + */ + protected static void validatePeriods(List<FreezeInterval> periods) { + List<FreezeInterval> allPeriods = FreezeInterval.canonicalizeIntervals(periods); + if (allPeriods.size() != periods.size()) { + throw SystemUpdatePolicy.ValidationFailedException.duplicateOrOverlapPeriods(); + } + for (int i = 0; i < allPeriods.size(); i++) { + FreezeInterval current = allPeriods.get(i); + if (current.getLength() > SystemUpdatePolicy.FREEZE_PERIOD_MAX_LENGTH) { + throw SystemUpdatePolicy.ValidationFailedException.freezePeriodTooLong("Freeze " + + "period " + current + " is too long: " + current.getLength() + " days"); + } + FreezeInterval previous = i > 0 ? allPeriods.get(i - 1) + : allPeriods.get(allPeriods.size() - 1); + if (previous != current) { + final int separation; + if (i == 0 && !previous.isWrapped()) { + // -->[current]---[-previous-]<--- + separation = current.mStartDay + + (DAYS_IN_YEAR - previous.mEndDay) - 1; + } else { + // --[previous]<--->[current]--------- + // OR ----prev---]<--->[current]---[prev- + separation = current.mStartDay - previous.mEndDay - 1; + } + if (separation < SystemUpdatePolicy.FREEZE_PERIOD_MIN_SEPARATION) { + throw SystemUpdatePolicy.ValidationFailedException.freezePeriodTooClose("Freeze" + + " periods " + previous + " and " + current + " are too close " + + "together: " + separation + " days apart"); + } + } + } + } + + /** + * Verifies that the current freeze periods are still legal, considering the previous freeze + * periods the device went through. In particular, when combined with the previous freeze + * period, the maximum freeze length or the minimum freeze separation should not be violated. + * + * @hide + */ + protected static void validateAgainstPreviousFreezePeriod(List<FreezeInterval> periods, + LocalDate prevPeriodStart, LocalDate prevPeriodEnd, LocalDate now) { + if (periods.size() == 0 || prevPeriodStart == null || prevPeriodEnd == null) { + return; + } + if (prevPeriodStart.isAfter(now) || prevPeriodEnd.isAfter(now)) { + Log.w(TAG, "Previous period (" + prevPeriodStart + "," + prevPeriodEnd + ") is after" + + " current date " + now); + // Clock was adjusted backwards. We can continue execution though, the separation + // and length validation below still works under this condition. + } + List<FreezeInterval> allPeriods = FreezeInterval.canonicalizeIntervals(periods); + // Given current time now, find the freeze period that's either current, or the one + // that's immediately afterwards. For the later case, it might be after the year-end, + // but this can only happen if there is only one freeze period. + FreezeInterval curOrNextFreezePeriod = allPeriods.get(0); + for (FreezeInterval interval : allPeriods) { + if (interval.contains(now) + || interval.mStartDay > FreezeInterval.dayOfYearDisregardLeapYear(now)) { + curOrNextFreezePeriod = interval; + break; + } + } + Pair<LocalDate, LocalDate> curOrNextFreezeDates = curOrNextFreezePeriod + .toCurrentOrFutureRealDates(now); + if (now.isAfter(curOrNextFreezeDates.first)) { + curOrNextFreezeDates = new Pair<>(now, curOrNextFreezeDates.second); + } + if (curOrNextFreezeDates.first.isAfter(curOrNextFreezeDates.second)) { + throw new IllegalStateException("Current freeze dates inverted: " + + curOrNextFreezeDates.first + "-" + curOrNextFreezeDates.second); + } + // Now validate [prevPeriodStart, prevPeriodEnd] against curOrNextFreezeDates + final String periodsDescription = "Prev: " + prevPeriodStart + "," + prevPeriodEnd + + "; cur: " + curOrNextFreezeDates.first + "," + curOrNextFreezeDates.second; + long separation = FreezeInterval.distanceWithoutLeapYear(curOrNextFreezeDates.first, + prevPeriodEnd) - 1; + if (separation > 0) { + // Two intervals do not overlap, check separation + if (separation < SystemUpdatePolicy.FREEZE_PERIOD_MIN_SEPARATION) { + throw ValidationFailedException.combinedPeriodTooClose("Previous freeze period " + + "too close to new period: " + separation + ", " + periodsDescription); + } + } else { + // Two intervals overlap, check combined length + long length = FreezeInterval.distanceWithoutLeapYear(curOrNextFreezeDates.second, + prevPeriodStart) + 1; + if (length > SystemUpdatePolicy.FREEZE_PERIOD_MAX_LENGTH) { + throw ValidationFailedException.combinedPeriodTooLong("Combined freeze period " + + "exceeds maximum days: " + length + ", " + periodsDescription); + } + } + } +} diff --git a/android/app/admin/SecurityLog.java b/android/app/admin/SecurityLog.java index d3b66d0d..38b4f8ff 100644 --- a/android/app/admin/SecurityLog.java +++ b/android/app/admin/SecurityLog.java @@ -18,6 +18,7 @@ package android.app.admin; import android.annotation.IntDef; import android.annotation.TestApi; +import android.content.ComponentName; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemProperties; @@ -53,64 +54,396 @@ public class SecurityLog { TAG_APP_PROCESS_START, TAG_KEYGUARD_DISMISSED, TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, - TAG_KEYGUARD_SECURED + TAG_KEYGUARD_SECURED, + TAG_OS_STARTUP, + TAG_OS_SHUTDOWN, + TAG_LOGGING_STARTED, + TAG_LOGGING_STOPPED, + TAG_MEDIA_MOUNT, + TAG_MEDIA_UNMOUNT, + TAG_LOG_BUFFER_SIZE_CRITICAL, + TAG_PASSWORD_EXPIRATION_SET, + TAG_PASSWORD_COMPLEXITY_SET, + TAG_PASSWORD_HISTORY_LENGTH_SET, + TAG_MAX_SCREEN_LOCK_TIMEOUT_SET, + TAG_MAX_PASSWORD_ATTEMPTS_SET, + TAG_KEYGUARD_DISABLED_FEATURES_SET, + TAG_REMOTE_LOCK, + TAG_USER_RESTRICTION_ADDED, + TAG_USER_RESTRICTION_REMOVED, + TAG_WIPE_FAILURE, + TAG_KEY_GENERATED, + TAG_KEY_IMPORT, + TAG_KEY_DESTRUCTION, + TAG_CERT_AUTHORITY_INSTALLED, + TAG_CERT_AUTHORITY_REMOVED, + TAG_CRYPTO_SELF_TEST_COMPLETED, + TAG_KEY_INTEGRITY_VIOLATION, + TAG_CERT_VALIDATION_FAILURE, }) public @interface SecurityLogTag {} + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "LEVEL_" }, value = { + LEVEL_INFO, + LEVEL_WARNING, + LEVEL_ERROR + }) + public @interface SecurityLogLevel {} + /** - * Indicate that an ADB interactive shell was opened via "adb shell". + * Indicates that an ADB interactive shell was opened via "adb shell". * There is no extra payload in the log event. */ public static final int TAG_ADB_SHELL_INTERACTIVE = SecurityLogTags.SECURITY_ADB_SHELL_INTERACTIVE; + /** - * Indicate that an shell command was issued over ADB via "adb shell command" - * The log entry contains a string data of the shell command, accessible via - * {@link SecurityEvent#getData()} + * Indicates that a shell command was issued over ADB via {@code adb shell <command>} + * The log entry contains a {@code String} payload containing the shell command, accessible + * via {@link SecurityEvent#getData()}. */ public static final int TAG_ADB_SHELL_CMD = SecurityLogTags.SECURITY_ADB_SHELL_COMMAND; + /** - * Indicate that a file was pulled from the device via the adb daemon, for example via - * "adb pull". The log entry contains a string data of the path of the pulled file, - * accessible via {@link SecurityEvent#getData()} + * Indicates that a file was pulled from the device via the adb daemon, for example via + * {@code adb pull}. The log entry contains a {@code String} payload containing the path of the + * pulled file on the device, accessible via {@link SecurityEvent#getData()}. */ public static final int TAG_SYNC_RECV_FILE = SecurityLogTags.SECURITY_ADB_SYNC_RECV; + /** - * Indicate that a file was pushed to the device via the adb daemon, for example via - * "adb push". The log entry contains a string data of the destination path of the - * pushed file, accessible via {@link SecurityEvent#getData()} + * Indicates that a file was pushed to the device via the adb daemon, for example via + * {@code adb push}. The log entry contains a {@code String} payload containing the destination + * path of the pushed file, accessible via {@link SecurityEvent#getData()}. */ public static final int TAG_SYNC_SEND_FILE = SecurityLogTags.SECURITY_ADB_SYNC_SEND; + /** - * Indicate that an app process was started. The log entry contains the following + * Indicates that an app process was started. The log entry contains the following * information about the process encapsulated in an {@link Object} array, accessible via * {@link SecurityEvent#getData()}: - * process name (String), exact start time (long), app Uid (integer), app Pid (integer), - * seinfo tag (String), SHA-256 hash of the base APK in hexadecimal (String) + * <li> [0] process name ({@code String}) + * <li> [1] exact start time in milliseconds according to {@code System.currentTimeMillis()} + * ({@code Long}) + * <li> [2] app uid ({@code Integer}) + * <li> [3] app pid ({@code Integer}) + * <li> [4] seinfo tag ({@code String}) + * <li> [5] SHA-256 hash of the base APK in hexadecimal ({@code String}) */ public static final int TAG_APP_PROCESS_START = SecurityLogTags.SECURITY_APP_PROCESS_START; + /** - * Indicate that keyguard is being dismissed. + * Indicates that keyguard has been dismissed. * There is no extra payload in the log event. */ - public static final int TAG_KEYGUARD_DISMISSED = - SecurityLogTags.SECURITY_KEYGUARD_DISMISSED; + public static final int TAG_KEYGUARD_DISMISSED = SecurityLogTags.SECURITY_KEYGUARD_DISMISSED; + /** - * Indicate that there has been an authentication attempt to dismiss the keyguard. The log entry - * contains the following information about the attempt encapsulated in an {@link Object} array, - * accessible via {@link SecurityEvent#getData()}: - * attempt result (integer, 1 for successful, 0 for unsuccessful), strength of auth method - * (integer, 1 if strong auth method was used, 0 otherwise) + * Indicates that there has been an authentication attempt to dismiss the keyguard. The log + * entry contains the following information about the attempt encapsulated in an {@link Object} + * array, accessible via {@link SecurityEvent#getData()}: + * <li> [0] attempt result ({@code Integer}, 1 for successful, 0 for unsuccessful) + * <li> [1] strength of authentication method ({@code Integer}, 1 if strong authentication + * method was used, 0 otherwise) */ public static final int TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT = SecurityLogTags.SECURITY_KEYGUARD_DISMISS_AUTH_ATTEMPT; + /** - * Indicate that the device has been locked, either by user or by timeout. - * There is no extra payload in the log event. + * Indicates that the device has been locked, either by the user or by a timeout. There is no + * extra payload in the log event. */ public static final int TAG_KEYGUARD_SECURED = SecurityLogTags.SECURITY_KEYGUARD_SECURED; /** + * Indicates that the Android OS has started. The log entry contains the following information + * about the startup time software integrity check encapsulated in an {@link Object} array, + * accessible via {@link SecurityEvent#getData()}: + * <li> [0] Verified Boot state ({@code String}) + * <li> [1] dm-verity mode ({@code String}). + * <p>Verified Boot state can be one of the following: + * <li> {@code green} indicates that there is a full chain of trust extending from the + * bootloader to verified partitions including the bootloader, boot partition, and all verified + * partitions. + * <li> {@code yellow} indicates that the boot partition has been verified using the embedded + * certificate and the signature is valid. + * <li> {@code orange} indicates that the device may be freely modified. Device integrity is + * left to the user to verify out-of-band. + * <p>dm-verity mode can be one of the following: + * <li> {@code enforcing} indicates that the device will be restarted when corruption is + * detected. + * <li> {@code eio} indicates that an I/O error will be returned for an attempt to read + * corrupted data blocks. + * For details see Verified Boot documentation. + */ + public static final int TAG_OS_STARTUP = SecurityLogTags.SECURITY_OS_STARTUP; + + /** + * Indicates that the Android OS has shutdown. There is no extra payload in the log event. + */ + public static final int TAG_OS_SHUTDOWN = SecurityLogTags.SECURITY_OS_SHUTDOWN; + + /** + * Indicates start-up of audit logging. There is no extra payload in the log event. + */ + public static final int TAG_LOGGING_STARTED = SecurityLogTags.SECURITY_LOGGING_STARTED; + + /** + * Indicates shutdown of audit logging. There is no extra payload in the log event. + */ + public static final int TAG_LOGGING_STOPPED = SecurityLogTags.SECURITY_LOGGING_STOPPED; + + /** + * Indicates that removable media has been mounted on the device. The log entry contains the + * following information about the event, encapsulated in an {@link Object} array and + * accessible via {@link SecurityEvent#getData()}: + * <li> [0] mount point ({@code String}) + * <li> [1] volume label ({@code String}). + */ + public static final int TAG_MEDIA_MOUNT = SecurityLogTags.SECURITY_MEDIA_MOUNTED; + + /** + * Indicates that removable media was unmounted from the device. The log entry contains the + * following information about the event, encapsulated in an {@link Object} array and + * accessible via {@link SecurityEvent#getData()}: + * <li> [0] mount point ({@code String}) + * <li> [1] volume label ({@code String}). + */ + public static final int TAG_MEDIA_UNMOUNT = SecurityLogTags.SECURITY_MEDIA_UNMOUNTED; + + /** + * Indicates that the audit log buffer has reached 90% of its capacity. There is no extra + * payload in the log event. + */ + public static final int TAG_LOG_BUFFER_SIZE_CRITICAL = + SecurityLogTags.SECURITY_LOG_BUFFER_SIZE_CRITICAL; + + /** + * Indicates that an admin has set a password expiration timeout. The log entry contains the + * following information about the event, encapsulated in an {@link Object} array and accessible + * via {@link SecurityEvent#getData()}: + * <li> [0] admin package name ({@code String}) + * <li> [1] admin user ID ({@code Integer}) + * <li> [2] target user ID ({@code Integer}) + * <li> [3] new password expiration timeout in milliseconds ({@code Long}). + * @see DevicePolicyManager#setPasswordExpirationTimeout(ComponentName, long) + */ + public static final int TAG_PASSWORD_EXPIRATION_SET = + SecurityLogTags.SECURITY_PASSWORD_EXPIRATION_SET; + + /** + * Indicates that an admin has set a requirement for password complexity. The log entry contains + * the following information about the event, encapsulated in an {@link Object} array and + * accessible via {@link SecurityEvent#getData()}: + * <li> [0] admin package name ({@code String}) + * <li> [1] admin user ID ({@code Integer}) + * <li> [2] target user ID ({@code Integer}) + * <li> [3] minimum password length ({@code Integer}) + * <li> [4] password quality constraint ({@code Integer}) + * <li> [5] minimum number of letters ({@code Integer}) + * <li> [6] minimum number of non-letters ({@code Integer}) + * <li> [7] minimum number of digits ({@code Integer}) + * <li> [8] minimum number of uppercase letters ({@code Integer}) + * <li> [9] minimum number of lowercase letters ({@code Integer}) + * <li> [10] minimum number of symbols ({@code Integer}) + * + * @see DevicePolicyManager#setPasswordMinimumLength(ComponentName, int) + * @see DevicePolicyManager#setPasswordQuality(ComponentName, int) + * @see DevicePolicyManager#setPasswordMinimumLetters(ComponentName, int) + * @see DevicePolicyManager#setPasswordMinimumNonLetter(ComponentName, int) + * @see DevicePolicyManager#setPasswordMinimumLowerCase(ComponentName, int) + * @see DevicePolicyManager#setPasswordMinimumUpperCase(ComponentName, int) + * @see DevicePolicyManager#setPasswordMinimumNumeric(ComponentName, int) + * @see DevicePolicyManager#setPasswordMinimumSymbols(ComponentName, int) + */ + public static final int TAG_PASSWORD_COMPLEXITY_SET = + SecurityLogTags.SECURITY_PASSWORD_COMPLEXITY_SET; + + /** + * Indicates that an admin has set a password history length. The log entry contains the + * following information about the event encapsulated in an {@link Object} array, accessible + * via {@link SecurityEvent#getData()}: + * <li> [0] admin package name ({@code String}) + * <li> [1] admin user ID ({@code Integer}) + * <li> [2] target user ID ({@code Integer}) + * <li> [3] new password history length value ({@code Integer}) + * @see DevicePolicyManager#setPasswordHistoryLength(ComponentName, int) + */ + public static final int TAG_PASSWORD_HISTORY_LENGTH_SET = + SecurityLogTags.SECURITY_PASSWORD_HISTORY_LENGTH_SET; + + /** + * Indicates that an admin has set a maximum screen lock timeout. The log entry contains the + * following information about the event encapsulated in an {@link Object} array, accessible + * via {@link SecurityEvent#getData()}: + * <li> [0] admin package name ({@code String}) + * <li> [1] admin user ID ({@code Integer}) + * <li> [2] target user ID ({@code Integer}) + * <li> [3] new screen lock timeout in milliseconds ({@code Long}) + * @see DevicePolicyManager#setMaximumTimeToLock(ComponentName, long) + */ + public static final int TAG_MAX_SCREEN_LOCK_TIMEOUT_SET = + SecurityLogTags.SECURITY_MAX_SCREEN_LOCK_TIMEOUT_SET; + + /** + * Indicates that an admin has set a maximum number of failed password attempts before wiping + * data. The log entry contains the following information about the event encapsulated in an + * {@link Object} array, accessible via {@link SecurityEvent#getData()}: + * <li> [0] admin package name ({@code String}) + * <li> [1] admin user ID ({@code Integer}) + * <li> [2] target user ID ({@code Integer}) + * <li> [3] new maximum number of failed password attempts ({@code Integer}) + * @see DevicePolicyManager#setMaximumFailedPasswordsForWipe(ComponentName, int) + */ + public static final int TAG_MAX_PASSWORD_ATTEMPTS_SET = + SecurityLogTags.SECURITY_MAX_PASSWORD_ATTEMPTS_SET; + + /** + * Indicates that an admin has set disabled keyguard features. The log entry contains the + * following information about the event encapsulated in an {@link Object} array, accessible via + * {@link SecurityEvent#getData()}: + * <li> [0] admin package name ({@code String}) + * <li> [1] admin user ID ({@code Integer}) + * <li> [2] target user ID ({@code Integer}) + * <li> [3] disabled keyguard feature mask ({@code Integer}). + * @see DevicePolicyManager#setKeyguardDisabledFeatures(ComponentName, int) + */ + public static final int TAG_KEYGUARD_DISABLED_FEATURES_SET = + SecurityLogTags.SECURITY_KEYGUARD_DISABLED_FEATURES_SET; + + /** + * Indicates that an admin remotely locked the device or profile. The log entry contains the + * following information about the event encapsulated in an {@link Object} array, accessible via + * {@link SecurityEvent#getData()}: + * <li> [0] admin package name ({@code String}), + * <li> [1] admin user ID ({@code Integer}). + * <li> [2] target user ID ({@code Integer}) + */ + public static final int TAG_REMOTE_LOCK = SecurityLogTags.SECURITY_REMOTE_LOCK; + + /** + * Indicates a failure to wipe device or user data. There is no extra payload in the log event. + */ + public static final int TAG_WIPE_FAILURE = SecurityLogTags.SECURITY_WIPE_FAILED; + + /** + * Indicates that an authentication key was generated. The log entry contains the following + * information about the event, encapsulated in an {@link Object} array and accessible via + * {@link SecurityEvent#getData()}: + * <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded) + * <li> [1] alias of the key ({@code String}) + * <li> [2] requesting process uid ({@code Integer}). + */ + public static final int TAG_KEY_GENERATED = + SecurityLogTags.SECURITY_KEY_GENERATED; + + /** + * Indicates that a cryptographic key was imported. The log entry contains the following + * information about the event, encapsulated in an {@link Object} array and accessible via + * {@link SecurityEvent#getData()}: + * <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded) + * <li> [1] alias of the key ({@code String}) + * <li> [2] requesting process uid ({@code Integer}). + */ + public static final int TAG_KEY_IMPORT = SecurityLogTags.SECURITY_KEY_IMPORTED; + + /** + * Indicates that a cryptographic key was destroyed. The log entry contains the following + * information about the event, encapsulated in an {@link Object} array and accessible via + * {@link SecurityEvent#getData()}: + * <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded) + * <li> [1] alias of the key ({@code String}) + * <li> [2] requesting process uid ({@code Integer}). + */ + public static final int TAG_KEY_DESTRUCTION = SecurityLogTags.SECURITY_KEY_DESTROYED; + + /** + * Indicates that a new root certificate has been installed into system's trusted credential + * storage. The log entry contains the following information about the event, encapsulated in an + * {@link Object} array and accessible via {@link SecurityEvent#getData()}: + * <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded) + * <li> [1] subject of the certificate ({@code String}). + */ + public static final int TAG_CERT_AUTHORITY_INSTALLED = + SecurityLogTags.SECURITY_CERT_AUTHORITY_INSTALLED; + + /** + * Indicates that a new root certificate has been removed from system's trusted credential + * storage. The log entry contains the following information about the event, encapsulated in an + * {@link Object} array and accessible via {@link SecurityEvent#getData()}: + * <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded) + * <li> [1] subject of the certificate ({@code String}). + */ + public static final int TAG_CERT_AUTHORITY_REMOVED = + SecurityLogTags.SECURITY_CERT_AUTHORITY_REMOVED; + + /** + * Indicates that an admin has set a user restriction. The log entry contains the following + * information about the event, encapsulated in an {@link Object} array and accessible via + * {@link SecurityEvent#getData()}: + * <li> [0] admin package name ({@code String}) + * <li> [1] admin user ID ({@code Integer}) + * <li> [2] user restriction ({@code String}) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + */ + public static final int TAG_USER_RESTRICTION_ADDED = + SecurityLogTags.SECURITY_USER_RESTRICTION_ADDED; + + /** + * Indicates that an admin has removed a user restriction. The log entry contains the following + * information about the event, encapsulated in an {@link Object} array and accessible via + * {@link SecurityEvent#getData()}: + * <li> [0] admin package name ({@code String}) + * <li> [1] admin user ID ({@code Integer}) + * <li> [2] user restriction ({@code String}) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) + */ + public static final int TAG_USER_RESTRICTION_REMOVED = + SecurityLogTags.SECURITY_USER_RESTRICTION_REMOVED; + + /** + * Indicates that cryptographic functionality self test has completed. The log entry contains an + * {@code Integer} payload, indicating the result of the test (0 if the test failed, 1 if + * succeeded) and accessible via {@link SecurityEvent#getData()}. + */ + public static final int TAG_CRYPTO_SELF_TEST_COMPLETED = + SecurityLogTags.SECURITY_CRYPTO_SELF_TEST_COMPLETED; + + /** + * Indicates a failed cryptographic key integrity check. The log entry contains the following + * information about the event, encapsulated in an {@link Object} array and accessible via + * {@link SecurityEvent#getData()}: + * <li> [0] alias of the key ({@code String}) + * <li> [1] owner application uid ({@code Integer}). + */ + public static final int TAG_KEY_INTEGRITY_VIOLATION = + SecurityLogTags.SECURITY_KEY_INTEGRITY_VIOLATION; + + /** + * Indicates a failure to validate X.509v3 certificate. The log entry contains a {@code String} + * payload indicating the failure reason, accessible via {@link SecurityEvent#getData()}. + */ + public static final int TAG_CERT_VALIDATION_FAILURE = + SecurityLogTags.SECURITY_CERT_VALIDATION_FAILURE; + + /** + * Event severity level indicating that the event corresponds to normal workflow. + */ + public static final int LEVEL_INFO = 1; + + /** + * Event severity level indicating that the event may require admin attention. + */ + public static final int LEVEL_WARNING = 2; + + /** + * Event severity level indicating that the event requires urgent admin action. + */ + public static final int LEVEL_ERROR = 3; + + /** * Returns if security logging is enabled. Log producers should only write new logs if this is * true. Under the hood this is the logical AND of whether device owner exists and whether * it enables logging by setting the system property {@link #PROPERTY_LOGGING_ENABLED}. @@ -198,6 +531,64 @@ public class SecurityLog { return mId; } + /** + * Returns severity level for the event. + */ + public @SecurityLogLevel int getLogLevel() { + switch (mEvent.getTag()) { + case TAG_ADB_SHELL_INTERACTIVE: + case TAG_ADB_SHELL_CMD: + case TAG_SYNC_RECV_FILE: + case TAG_SYNC_SEND_FILE: + case TAG_APP_PROCESS_START: + case TAG_KEYGUARD_DISMISSED: + case TAG_KEYGUARD_SECURED: + case TAG_OS_STARTUP: + case TAG_OS_SHUTDOWN: + case TAG_LOGGING_STARTED: + case TAG_LOGGING_STOPPED: + case TAG_MEDIA_MOUNT: + case TAG_MEDIA_UNMOUNT: + case TAG_PASSWORD_EXPIRATION_SET: + case TAG_PASSWORD_COMPLEXITY_SET: + case TAG_PASSWORD_HISTORY_LENGTH_SET: + case TAG_MAX_SCREEN_LOCK_TIMEOUT_SET: + case TAG_MAX_PASSWORD_ATTEMPTS_SET: + case TAG_USER_RESTRICTION_ADDED: + case TAG_USER_RESTRICTION_REMOVED: + return LEVEL_INFO; + case TAG_CERT_AUTHORITY_REMOVED: + case TAG_CRYPTO_SELF_TEST_COMPLETED: + return getSuccess() ? LEVEL_INFO : LEVEL_ERROR; + case TAG_CERT_AUTHORITY_INSTALLED: + case TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT: + case TAG_KEY_IMPORT: + case TAG_KEY_DESTRUCTION: + case TAG_KEY_GENERATED: + return getSuccess() ? LEVEL_INFO : LEVEL_WARNING; + case TAG_LOG_BUFFER_SIZE_CRITICAL: + case TAG_WIPE_FAILURE: + case TAG_KEY_INTEGRITY_VIOLATION: + return LEVEL_ERROR; + case TAG_CERT_VALIDATION_FAILURE: + return LEVEL_WARNING; + default: + return LEVEL_INFO; + } + } + + // Success/failure if present is encoded as an integer in the first (0th) element of data. + private boolean getSuccess() { + final Object data = getData(); + if (data == null || !(data instanceof Object[])) { + return false; + } + + final Object[] array = (Object[]) data; + return array.length >= 1 && array[0] instanceof Integer && (Integer) array[0] != 0; + } + + @Override public int describeContents() { return 0; @@ -263,8 +654,8 @@ public class SecurityLog { throws IOException; /** - * Retrieve all security logs whose timestamp (in nanosceonds) is equal to or greater than the - * given timestamp. This method will block until either the last log earlier than the given + * Retrieve all security logs whose timestamp is equal to or greater than the given timestamp in + * nanoseconds. This method will block until either the last log earlier than the given * timestamp is about to be pruned, or after a 2-hour timeout has passed. * @hide */ diff --git a/android/app/admin/SystemUpdatePolicy.java b/android/app/admin/SystemUpdatePolicy.java index 232a6887..47b3a81d 100644 --- a/android/app/admin/SystemUpdatePolicy.java +++ b/android/app/admin/SystemUpdatePolicy.java @@ -16,16 +16,34 @@ package android.app.admin; +import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; +import static org.xmlpull.v1.XmlPullParser.END_TAG; +import static org.xmlpull.v1.XmlPullParser.TEXT; + import android.annotation.IntDef; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import android.util.Log; +import android.util.Pair; import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; /** * A class that represents a local system update policy set by the device owner. @@ -34,6 +52,7 @@ import java.lang.annotation.RetentionPolicy; * @see DevicePolicyManager#getSystemUpdatePolicy */ public class SystemUpdatePolicy implements Parcelable { + private static final String TAG = "SystemUpdatePolicy"; /** @hide */ @IntDef(prefix = { "TYPE_" }, value = { @@ -91,23 +110,173 @@ public class SystemUpdatePolicy implements Parcelable { */ public static final int TYPE_POSTPONE = 3; + /** + * Incoming system updates (including security updates) should be blocked. This flag is not + * exposed to third-party apps (and any attempt to set it will raise exceptions). This is used + * to represent the current installation option type to the privileged system update clients, + * for example to indicate OTA freeze is currently in place or when system is outside a daily + * maintenance window. + * + * @see InstallationOption + * @hide + */ + @SystemApi + public static final int TYPE_PAUSE = 4; + private static final String KEY_POLICY_TYPE = "policy_type"; private static final String KEY_INSTALL_WINDOW_START = "install_window_start"; private static final String KEY_INSTALL_WINDOW_END = "install_window_end"; + private static final String KEY_FREEZE_TAG = "freeze"; + private static final String KEY_FREEZE_START = "start"; + private static final String KEY_FREEZE_END = "end"; + /** * The upper boundary of the daily maintenance window: 24 * 60 minutes. */ private static final int WINDOW_BOUNDARY = 24 * 60; + /** + * The maximum length of a single freeze period: 90 days. + */ + static final int FREEZE_PERIOD_MAX_LENGTH = 90; + + /** + * The minimum allowed time between two adjacent freeze period (from the end of the first + * freeze period to the start of the second freeze period, both exclusive): 60 days. + */ + static final int FREEZE_PERIOD_MIN_SEPARATION = 60; + + + /** + * An exception class that represents various validation errors thrown from + * {@link SystemUpdatePolicy#setFreezePeriods} and + * {@link DevicePolicyManager#setSystemUpdatePolicy} + */ + public static final class ValidationFailedException extends IllegalArgumentException + implements Parcelable { + + /** @hide */ + @IntDef(prefix = { "ERROR_" }, value = { + ERROR_NONE, + ERROR_DUPLICATE_OR_OVERLAP, + ERROR_NEW_FREEZE_PERIOD_TOO_LONG, + ERROR_NEW_FREEZE_PERIOD_TOO_CLOSE, + ERROR_COMBINED_FREEZE_PERIOD_TOO_LONG, + ERROR_COMBINED_FREEZE_PERIOD_TOO_CLOSE, + }) + @Retention(RetentionPolicy.SOURCE) + @interface ValidationFailureType {} + + /** @hide */ + public static final int ERROR_NONE = 0; + + /** + * The freeze periods contains duplicates, periods that overlap with each + * other or periods whose start and end joins. + */ + public static final int ERROR_DUPLICATE_OR_OVERLAP = 1; + + /** + * There exists at least one freeze period whose length exceeds 90 days. + */ + public static final int ERROR_NEW_FREEZE_PERIOD_TOO_LONG = 2; + + /** + * There exists some freeze period which starts within 60 days of the preceding period's + * end time. + */ + public static final int ERROR_NEW_FREEZE_PERIOD_TOO_CLOSE = 3; + + /** + * The device has been in a freeze period and when combining with the new freeze period + * to be set, it will result in the total freeze period being longer than 90 days. + */ + public static final int ERROR_COMBINED_FREEZE_PERIOD_TOO_LONG = 4; + + /** + * The device has been in a freeze period and some new freeze period to be set is less + * than 60 days from the end of the last freeze period the device went through. + */ + public static final int ERROR_COMBINED_FREEZE_PERIOD_TOO_CLOSE = 5; + + @ValidationFailureType + private final int mErrorCode; + + private ValidationFailedException(int errorCode, String message) { + super(message); + mErrorCode = errorCode; + } + + /** + * Returns the type of validation error associated with this exception. + */ + public @ValidationFailureType int getErrorCode() { + return mErrorCode; + } + + /** @hide */ + public static ValidationFailedException duplicateOrOverlapPeriods() { + return new ValidationFailedException(ERROR_DUPLICATE_OR_OVERLAP, + "Found duplicate or overlapping periods"); + } + + /** @hide */ + public static ValidationFailedException freezePeriodTooLong(String message) { + return new ValidationFailedException(ERROR_NEW_FREEZE_PERIOD_TOO_LONG, message); + } + + /** @hide */ + public static ValidationFailedException freezePeriodTooClose(String message) { + return new ValidationFailedException(ERROR_NEW_FREEZE_PERIOD_TOO_CLOSE, message); + } + + /** @hide */ + public static ValidationFailedException combinedPeriodTooLong(String message) { + return new ValidationFailedException(ERROR_COMBINED_FREEZE_PERIOD_TOO_LONG, message); + } + + /** @hide */ + public static ValidationFailedException combinedPeriodTooClose(String message) { + return new ValidationFailedException(ERROR_COMBINED_FREEZE_PERIOD_TOO_CLOSE, message); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mErrorCode); + dest.writeString(getMessage()); + } + + public static final Parcelable.Creator<ValidationFailedException> CREATOR = + new Parcelable.Creator<ValidationFailedException>() { + @Override + public ValidationFailedException createFromParcel(Parcel source) { + return new ValidationFailedException(source.readInt(), source.readString()); + } + + @Override + public ValidationFailedException[] newArray(int size) { + return new ValidationFailedException[size]; + } + + }; + } + @SystemUpdatePolicyType private int mPolicyType; private int mMaintenanceWindowStart; private int mMaintenanceWindowEnd; + private final ArrayList<FreezeInterval> mFreezePeriods; private SystemUpdatePolicy() { mPolicyType = TYPE_UNKNOWN; + mFreezePeriods = new ArrayList<>(); } /** @@ -206,38 +375,305 @@ public class SystemUpdatePolicy implements Parcelable { } /** - * Return if this object represents a valid policy. + * Return if this object represents a valid policy with: + * 1. Correct type + * 2. Valid maintenance window if applicable + * 3. Valid freeze periods * @hide */ public boolean isValid() { - if (mPolicyType == TYPE_INSTALL_AUTOMATIC || mPolicyType == TYPE_POSTPONE) { + try { + validateType(); + validateFreezePeriods(); return true; + } catch (IllegalArgumentException e) { + return false; + } + } + + /** + * Validate the type and maintenance window (if applicable) of this policy object, + * throws {@link IllegalArgumentException} if it's invalid. + * @hide + */ + public void validateType() { + if (mPolicyType == TYPE_INSTALL_AUTOMATIC || mPolicyType == TYPE_POSTPONE) { + return; } else if (mPolicyType == TYPE_INSTALL_WINDOWED) { - return mMaintenanceWindowStart >= 0 && mMaintenanceWindowStart < WINDOW_BOUNDARY - && mMaintenanceWindowEnd >= 0 && mMaintenanceWindowEnd < WINDOW_BOUNDARY; + if (!(mMaintenanceWindowStart >= 0 && mMaintenanceWindowStart < WINDOW_BOUNDARY + && mMaintenanceWindowEnd >= 0 && mMaintenanceWindowEnd < WINDOW_BOUNDARY)) { + throw new IllegalArgumentException("Invalid maintenance window"); + } } else { - return false; + throw new IllegalArgumentException("Invalid system update policy type."); + } + } + + /** + * Configure a list of freeze periods on top of the current policy. When the device's clock is + * within any of the freeze periods, all incoming system updates including security patches will + * be blocked and cannot be installed. When the device is outside the freeze periods, the normal + * policy behavior will apply. + * <p> + * Each freeze period is defined by a starting and finishing date (both inclusive). Since the + * freeze period repeats annually, both of these dates are simply represented by integers + * counting the number of days since year start, similar to {@link LocalDate#getDayOfYear()}. We + * do not consider leap year when handling freeze period so the valid range of the integer is + * always [1,365] (see last section for more details on leap year). If the finishing date is + * smaller than the starting date, the freeze period is considered to be spanning across + * year-end. + * <p> + * Each individual freeze period is allowed to be at most 90 days long, and adjacent freeze + * periods need to be at least 60 days apart. Also, the list of freeze periods should not + * contain duplicates or overlap with each other. If any of these conditions is not met, a + * {@link ValidationFailedException} will be thrown. + * <p> + * Handling of leap year: we do not consider leap year when handling freeze period, in + * particular, + * <ul> + * <li>When a freeze period is defined by the day of year, February 29th does not count as one + * day, so day 59 is February 28th while day 60 is March 1st.</li> + * <li>When applying freeze period behavior to the device, a system clock of February 29th is + * treated as if it were February 28th</li> + * <li>When calculating the number of days of a freeze period or separation between two freeze + * periods, February 29th is also ignored and not counted as one day.</li> + * </ul> + * + * @param freezePeriods the list of freeze periods + * @throws ValidationFailedException if the supplied freeze periods do not meet the + * requirement set above + * @return this instance + */ + public SystemUpdatePolicy setFreezePeriods(List<Pair<Integer, Integer>> freezePeriods) { + List<FreezeInterval> newPeriods = freezePeriods.stream().map( + p -> new FreezeInterval(p.first, p.second)).collect(Collectors.toList()); + FreezeInterval.validatePeriods(newPeriods); + mFreezePeriods.clear(); + mFreezePeriods.addAll(newPeriods); + return this; + } + + /** + * Returns the list of freeze periods previously set on this system update policy object. + * + * @return the list of freeze periods, or an empty list if none was set. + */ + public List<Pair<Integer, Integer>> getFreezePeriods() { + List<Pair<Integer, Integer>> result = new ArrayList<>(mFreezePeriods.size()); + for (FreezeInterval interval : mFreezePeriods) { + result.add(new Pair<>(interval.mStartDay, interval.mEndDay)); + } + return result; + } + + /** + * Returns the real calendar dates of the current freeze period, or null if the device + * is not in a freeze period at the moment. + * @hide + */ + public Pair<LocalDate, LocalDate> getCurrentFreezePeriod(LocalDate now) { + for (FreezeInterval interval : mFreezePeriods) { + if (interval.contains(now)) { + return interval.toCurrentOrFutureRealDates(now); + } + } + return null; + } + + /** + * Returns time (in milliseconds) until the start of the next freeze period, assuming now + * is not within a freeze period. + */ + private long timeUntilNextFreezePeriod(long now) { + List<FreezeInterval> sortedPeriods = FreezeInterval.canonicalizeIntervals(mFreezePeriods); + LocalDate nowDate = millisToDate(now); + LocalDate nextFreezeStart = null; + for (FreezeInterval interval : sortedPeriods) { + if (interval.after(nowDate)) { + nextFreezeStart = interval.toCurrentOrFutureRealDates(nowDate).first; + break; + } else if (interval.contains(nowDate)) { + throw new IllegalArgumentException("Given date is inside a freeze period"); + } + } + if (nextFreezeStart == null) { + // If no interval is after now, then it must be the one that starts at the beginning + // of next year + nextFreezeStart = sortedPeriods.get(0).toCurrentOrFutureRealDates(nowDate).first; + } + return dateToMillis(nextFreezeStart) - now; + } + + /** @hide */ + public void validateFreezePeriods() { + FreezeInterval.validatePeriods(mFreezePeriods); + } + + /** @hide */ + public void validateAgainstPreviousFreezePeriod(LocalDate prevPeriodStart, + LocalDate prevPeriodEnd, LocalDate now) { + FreezeInterval.validateAgainstPreviousFreezePeriod(mFreezePeriods, prevPeriodStart, + prevPeriodEnd, now); + } + + /** + * An installation option represents how system update clients should act on incoming system + * updates and how long this action is valid for, given the current system update policy. Its + * action could be one of the following + * <ul> + * <li> {@code TYPE_INSTALL_AUTOMATIC} system updates should be installed immedately and without + * user intervention as soon as they become available. + * <li> {@code TYPE_POSTPONE} system updates should be postponed for a maximum of 30 days + * <li> {@code TYPE_PAUSE} system updates should be postponed indefinitely until further notice + * </ul> + * + * The effective time measures how long this installation option is valid for from the queried + * time, in milliseconds. + * + * This is an internal API for system update clients. + * @hide + */ + @SystemApi + public static class InstallationOption { + private final int mType; + private long mEffectiveTime; + + InstallationOption(int type, long effectiveTime) { + this.mType = type; + this.mEffectiveTime = effectiveTime; + } + + public int getType() { + return mType; + } + + public long getEffectiveTime() { + return mEffectiveTime; + } + + /** @hide */ + protected void limitEffectiveTime(long otherTime) { + mEffectiveTime = Long.min(mEffectiveTime, otherTime); } } + /** + * Returns the installation option at the specified time, under the current + * {@code SystemUpdatePolicy} object. This is a convenience method for system update clients + * so they can instantiate this policy at any given time and find out what to do with incoming + * system updates, without the need of examining the overall policy structure. + * + * Normally the system update clients will query the current installation option by calling this + * method with the current timestamp, and act on the returned option until its effective time + * lapses. It can then query the latest option using a new timestamp. It should also listen + * for {@code DevicePolicyManager#ACTION_SYSTEM_UPDATE_POLICY_CHANGED} broadcast, in case the + * whole policy is updated. + * + * @param when At what time the intallation option is being queried, specified in number of + milliseonds since the epoch. + * @see InstallationOption + * @hide + */ + @SystemApi + public InstallationOption getInstallationOptionAt(long when) { + LocalDate whenDate = millisToDate(when); + Pair<LocalDate, LocalDate> current = getCurrentFreezePeriod(whenDate); + if (current != null) { + return new InstallationOption(TYPE_PAUSE, + dateToMillis(roundUpLeapDay(current.second).plusDays(1)) - when); + } + // We are not within a freeze period, query the underlying policy. + // But also consider the start of the next freeze period, which might + // reduce the effective time of the current installation option + InstallationOption option = getInstallationOptionRegardlessFreezeAt(when); + if (mFreezePeriods.size() > 0) { + option.limitEffectiveTime(timeUntilNextFreezePeriod(when)); + } + return option; + } + + private InstallationOption getInstallationOptionRegardlessFreezeAt(long when) { + if (mPolicyType == TYPE_INSTALL_AUTOMATIC || mPolicyType == TYPE_POSTPONE) { + return new InstallationOption(mPolicyType, Long.MAX_VALUE); + } else if (mPolicyType == TYPE_INSTALL_WINDOWED) { + Calendar query = Calendar.getInstance(); + query.setTimeInMillis(when); + // Calculate the number of milliseconds since midnight of the time specified by when + long whenMillis = TimeUnit.HOURS.toMillis(query.get(Calendar.HOUR_OF_DAY)) + + TimeUnit.MINUTES.toMillis(query.get(Calendar.MINUTE)) + + TimeUnit.SECONDS.toMillis(query.get(Calendar.SECOND)) + + query.get(Calendar.MILLISECOND); + long windowStartMillis = TimeUnit.MINUTES.toMillis(mMaintenanceWindowStart); + long windowEndMillis = TimeUnit.MINUTES.toMillis(mMaintenanceWindowEnd); + final long dayInMillis = TimeUnit.DAYS.toMillis(1); + + if ((windowStartMillis <= whenMillis && whenMillis <= windowEndMillis) + || ((windowStartMillis > windowEndMillis) + && (windowStartMillis <= whenMillis || whenMillis <= windowEndMillis))) { + return new InstallationOption(TYPE_INSTALL_AUTOMATIC, + (windowEndMillis - whenMillis + dayInMillis) % dayInMillis); + } else { + return new InstallationOption(TYPE_PAUSE, + (windowStartMillis - whenMillis + dayInMillis) % dayInMillis); + } + } else { + throw new RuntimeException("Unknown policy type"); + } + } + + private static LocalDate roundUpLeapDay(LocalDate date) { + if (date.isLeapYear() && date.getMonthValue() == 2 && date.getDayOfMonth() == 28) { + return date.plusDays(1); + } else { + return date; + } + } + + /** Convert a timestamp since epoch to a LocalDate using default timezone, truncating + * the hour/min/seconds part. + */ + private static LocalDate millisToDate(long when) { + return Instant.ofEpochMilli(when).atZone(ZoneId.systemDefault()).toLocalDate(); + } + + /** + * Returns the timestamp since epoch of a LocalDate, assuming the time is 00:00:00. + */ + private static long dateToMillis(LocalDate when) { + return LocalDateTime.of(when, LocalTime.MIN).atZone(ZoneId.systemDefault()).toInstant() + .toEpochMilli(); + } + @Override public String toString() { - return String.format("SystemUpdatePolicy (type: %d, windowStart: %d, windowEnd: %d)", - mPolicyType, mMaintenanceWindowStart, mMaintenanceWindowEnd); + return String.format("SystemUpdatePolicy (type: %d, windowStart: %d, windowEnd: %d, " + + "freezes: [%s])", + mPolicyType, mMaintenanceWindowStart, mMaintenanceWindowEnd, + mFreezePeriods.stream().map(n -> n.toString()).collect(Collectors.joining(","))); } + @SystemApi @Override public int describeContents() { return 0; } + @SystemApi @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mPolicyType); dest.writeInt(mMaintenanceWindowStart); dest.writeInt(mMaintenanceWindowEnd); + int freezeCount = mFreezePeriods.size(); + dest.writeInt(freezeCount); + for (int i = 0; i < freezeCount; i++) { + FreezeInterval interval = mFreezePeriods.get(i); + dest.writeInt(interval.mStartDay); + dest.writeInt(interval.mEndDay); + } } + @SystemApi public static final Parcelable.Creator<SystemUpdatePolicy> CREATOR = new Parcelable.Creator<SystemUpdatePolicy>() { @@ -247,6 +683,12 @@ public class SystemUpdatePolicy implements Parcelable { policy.mPolicyType = source.readInt(); policy.mMaintenanceWindowStart = source.readInt(); policy.mMaintenanceWindowEnd = source.readInt(); + int freezeCount = source.readInt(); + policy.mFreezePeriods.ensureCapacity(freezeCount); + for (int i = 0; i < freezeCount; i++) { + policy.mFreezePeriods.add( + new FreezeInterval(source.readInt(), source.readInt())); + } return policy; } @@ -256,8 +698,10 @@ public class SystemUpdatePolicy implements Parcelable { } }; - /** + * Restore a previously saved SystemUpdatePolicy from XML. No need to validate + * the reconstructed policy since the XML is supposed to be created by the + * system server from a validated policy object previously. * @hide */ public static SystemUpdatePolicy restoreFromXml(XmlPullParser parser) { @@ -275,10 +719,26 @@ public class SystemUpdatePolicy implements Parcelable { if (value != null) { policy.mMaintenanceWindowEnd = Integer.parseInt(value); } + + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != END_DOCUMENT + && (type != END_TAG || parser.getDepth() > outerDepth)) { + if (type == END_TAG || type == TEXT) { + continue; + } + if (!parser.getName().equals(KEY_FREEZE_TAG)) { + continue; + } + policy.mFreezePeriods.add(new FreezeInterval( + Integer.parseInt(parser.getAttributeValue(null, KEY_FREEZE_START)), + Integer.parseInt(parser.getAttributeValue(null, KEY_FREEZE_END)))); + } return policy; } - } catch (NumberFormatException e) { + } catch (NumberFormatException | XmlPullParserException | IOException e) { // Fail through + Log.w(TAG, "Load xml failed", e); } return null; } @@ -290,6 +750,13 @@ public class SystemUpdatePolicy implements Parcelable { out.attribute(null, KEY_POLICY_TYPE, Integer.toString(mPolicyType)); out.attribute(null, KEY_INSTALL_WINDOW_START, Integer.toString(mMaintenanceWindowStart)); out.attribute(null, KEY_INSTALL_WINDOW_END, Integer.toString(mMaintenanceWindowEnd)); + for (int i = 0; i < mFreezePeriods.size(); i++) { + FreezeInterval interval = mFreezePeriods.get(i); + out.startTag(null, KEY_FREEZE_TAG); + out.attribute(null, KEY_FREEZE_START, Integer.toString(interval.mStartDay)); + out.attribute(null, KEY_FREEZE_END, Integer.toString(interval.mEndDay)); + out.endTag(null, KEY_FREEZE_TAG); + } } } diff --git a/android/app/assist/AssistStructure.java b/android/app/assist/AssistStructure.java index 87f22712..d5686624 100644 --- a/android/app/assist/AssistStructure.java +++ b/android/app/assist/AssistStructure.java @@ -4,6 +4,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.content.ComponentName; +import android.content.Context; import android.graphics.Matrix; import android.graphics.Rect; import android.net.Uri; @@ -23,6 +24,7 @@ import android.text.TextUtils; import android.util.Log; import android.util.Pair; import android.view.View; +import android.view.View.AutofillImportance; import android.view.ViewRootImpl; import android.view.ViewStructure; import android.view.ViewStructure.HtmlInfo; @@ -500,9 +502,8 @@ public class AssistStructure implements Parcelable { ViewNodeBuilder builder = new ViewNodeBuilder(assist, mRoot, false); if ((root.getWindowFlags() & WindowManager.LayoutParams.FLAG_SECURE) != 0) { if (forAutoFill) { - final int autofillFlags = (flags & FillRequest.FLAG_MANUAL_REQUEST) != 0 - ? View.AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS : 0; - view.onProvideAutofillStructure(builder, autofillFlags); + final int viewFlags = resolveViewAutofillFlags(view.getContext(), flags); + view.onProvideAutofillStructure(builder, viewFlags); } else { // This is a secure window, so it doesn't want a screenshot, and that // means we should also not copy out its view hierarchy for Assist @@ -512,9 +513,8 @@ public class AssistStructure implements Parcelable { } } if (forAutoFill) { - final int autofillFlags = (flags & FillRequest.FLAG_MANUAL_REQUEST) != 0 - ? View.AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS : 0; - view.dispatchProvideAutofillStructure(builder, autofillFlags); + final int viewFlags = resolveViewAutofillFlags(view.getContext(), flags); + view.dispatchProvideAutofillStructure(builder, viewFlags); } else { view.dispatchProvideStructure(builder); } @@ -532,6 +532,12 @@ public class AssistStructure implements Parcelable { mRoot = new ViewNode(reader, 0); } + int resolveViewAutofillFlags(Context context, int fillRequestFlags) { + return (fillRequestFlags & FillRequest.FLAG_MANUAL_REQUEST) != 0 + || context.isAutofillCompatibilityEnabled() + ? View.AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS : 0; + } + void writeSelfToParcel(Parcel out, PooledStringWriter pwriter, float[] tmpMatrix) { out.writeInt(mX); out.writeInt(mY); @@ -627,6 +633,7 @@ public class AssistStructure implements Parcelable { int mMaxEms = -1; int mMaxLength = -1; @Nullable String mTextIdEntry; + @AutofillImportance int mImportantForAutofill; // POJO used to override some autofill-related values when the node is parcelized. // Not written to parcel. @@ -728,6 +735,7 @@ public class AssistStructure implements Parcelable { mMaxEms = in.readInt(); mMaxLength = in.readInt(); mTextIdEntry = preader.readString(); + mImportantForAutofill = in.readInt(); } if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) { mX = in.readInt(); @@ -895,6 +903,7 @@ public class AssistStructure implements Parcelable { out.writeInt(mMaxEms); out.writeInt(mMaxLength); pwriter.writeString(mTextIdEntry); + out.writeInt(mImportantForAutofill); } if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) { out.writeInt(mX); @@ -1294,6 +1303,22 @@ public class AssistStructure implements Parcelable { } /** + * @hide + */ + public void setWebDomain(@Nullable String domain) { + if (domain == null) return; + + final Uri uri = Uri.parse(domain); + if (uri == null) { + // Cannot log domain because it could contain PII; + Log.w(TAG, "Failed to parse web domain"); + return; + } + mWebScheme = uri.getScheme(); + mWebDomain = uri.getHost(); + } + + /** * Returns the scheme of the HTML document represented by this view. * * <p>Typically used when the view associated with the view is a container for an HTML @@ -1507,6 +1532,16 @@ public class AssistStructure implements Parcelable { public int getMaxTextLength() { return mMaxLength; } + + /** + * Gets the {@link View#setImportantForAutofill(int) importantForAutofill mode} of + * the view associated with this node. + * + * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes. + */ + public @AutofillImportance int getImportantForAutofill() { + return mImportantForAutofill; + } } /** @@ -1839,6 +1874,11 @@ public class AssistStructure implements Parcelable { } @Override + public void setImportantForAutofill(@AutofillImportance int mode) { + mNode.mImportantForAutofill = mode; + } + + @Override public void setInputType(int inputType) { mNode.mInputType = inputType; } @@ -1865,14 +1905,7 @@ public class AssistStructure implements Parcelable { @Override public void setWebDomain(@Nullable String domain) { - if (domain == null) { - mNode.mWebScheme = null; - mNode.mWebDomain = null; - return; - } - Uri uri = Uri.parse(domain); - mNode.mWebScheme = uri.getScheme(); - mNode.mWebDomain = uri.getHost(); + mNode.setWebDomain(domain); } @Override @@ -2139,7 +2172,8 @@ public class AssistStructure implements Parcelable { + ", options=" + Arrays.toString(node.getAutofillOptions()) + ", hints=" + Arrays.toString(node.getAutofillHints()) + ", value=" + node.getAutofillValue() - + ", sanitized=" + node.isSanitized()); + + ", sanitized=" + node.isSanitized() + + ", importantFor=" + node.getImportantForAutofill()); } final int NCHILDREN = node.getChildCount(); @@ -2203,6 +2237,22 @@ public class AssistStructure implements Parcelable { return mWindowNodes.get(index); } + // TODO(b/35708678): temporary method that disable one-way warning flag on binder. + /** @hide */ + public void ensureDataForAutofill() { + if (mHaveData) { + return; + } + mHaveData = true; + Binder.allowBlocking(mReceiveChannel); + try { + ParcelTransferReader reader = new ParcelTransferReader(mReceiveChannel); + reader.go(); + } finally { + Binder.defaultBlocking(mReceiveChannel); + } + } + /** @hide */ public void ensureData() { if (mHaveData) { diff --git a/android/app/backup/BackupAgent.java b/android/app/backup/BackupAgent.java index 861cb9a8..d1c957b8 100644 --- a/android/app/backup/BackupAgent.java +++ b/android/app/backup/BackupAgent.java @@ -18,6 +18,7 @@ package android.app.backup; import android.app.IBackupAgent; import android.app.QueuedWork; +import android.app.backup.FullBackup.BackupScheme.PathWithRequiredFlags; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.ApplicationInfo; @@ -143,6 +144,36 @@ public abstract class BackupAgent extends ContextWrapper { /** @hide */ public static final int TYPE_SYMLINK = 3; + /** + * Flag for {@link BackupDataOutput#getTransportFlags()} and + * {@link FullBackupDataOutput#getTransportFlags()} only. + * + * <p>The transport has client-side encryption enabled. i.e., the user's backup has been + * encrypted with a key known only to the device, and not to the remote storage solution. Even + * if an attacker had root access to the remote storage provider they should not be able to + * decrypt the user's backup data. + */ + public static final int FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED = 1; + + /** + * Flag for {@link BackupDataOutput#getTransportFlags()} and + * {@link FullBackupDataOutput#getTransportFlags()} only. + * + * <p>The transport is for a device-to-device transfer. There is no third party or intermediate + * storage. The user's backup data is sent directly to another device over e.g., USB or WiFi. + */ + public static final int FLAG_DEVICE_TO_DEVICE_TRANSFER = 2; + + /** + * Flag for {@link BackupDataOutput#getTransportFlags()} and + * {@link FullBackupDataOutput#getTransportFlags()} only. + * + * <p>Used for internal testing only. Do not check this flag in production code. + * + * @hide + */ + public static final int FLAG_FAKE_CLIENT_SIDE_ENCRYPTION_ENABLED = 1 << 31; + Handler mHandler = null; Handler getHandler() { @@ -313,8 +344,8 @@ public abstract class BackupAgent extends ContextWrapper { return; } - Map<String, Set<String>> manifestIncludeMap; - ArraySet<String> manifestExcludeSet; + Map<String, Set<PathWithRequiredFlags>> manifestIncludeMap; + ArraySet<PathWithRequiredFlags> manifestExcludeSet; try { manifestIncludeMap = backupScheme.maybeParseAndGetCanonicalIncludePaths(); @@ -484,14 +515,13 @@ public abstract class BackupAgent extends ContextWrapper { /** * Check whether the xml yielded any <include/> tag for the provided <code>domainToken</code>. * If so, perform a {@link #fullBackupFileTree} which backs up the file or recurses if the path - * is a directory. + * is a directory, but only if all the required flags of the include rule are satisfied by + * the transport. */ private void applyXmlFiltersAndDoFullBackupForDomain(String packageName, String domainToken, - Map<String, Set<String>> includeMap, - ArraySet<String> filterSet, - ArraySet<String> traversalExcludeSet, - FullBackupDataOutput data) - throws IOException { + Map<String, Set<PathWithRequiredFlags>> includeMap, + ArraySet<PathWithRequiredFlags> filterSet, ArraySet<String> traversalExcludeSet, + FullBackupDataOutput data) throws IOException { if (includeMap == null || includeMap.size() == 0) { // Do entire sub-tree for the provided token. fullBackupFileTree(packageName, domainToken, @@ -500,13 +530,22 @@ public abstract class BackupAgent extends ContextWrapper { } else if (includeMap.get(domainToken) != null) { // This will be null if the xml parsing didn't yield any rules for // this domain (there may still be rules for other domains). - for (String includeFile : includeMap.get(domainToken)) { - fullBackupFileTree(packageName, domainToken, includeFile, filterSet, - traversalExcludeSet, data); + for (PathWithRequiredFlags includeFile : includeMap.get(domainToken)) { + if (areIncludeRequiredTransportFlagsSatisfied(includeFile.getRequiredFlags(), + data.getTransportFlags())) { + fullBackupFileTree(packageName, domainToken, includeFile.getPath(), filterSet, + traversalExcludeSet, data); + } } } } + private boolean areIncludeRequiredTransportFlagsSatisfied(int includeFlags, + int transportFlags) { + // all bits that are set in includeFlags must also be set in transportFlags + return (transportFlags & includeFlags) == includeFlags; + } + /** * Write an entire file as part of a full-backup operation. The file's contents * will be delivered to the backup destination along with the metadata necessary @@ -653,7 +692,7 @@ public abstract class BackupAgent extends ContextWrapper { * @hide */ protected final void fullBackupFileTree(String packageName, String domain, String startingPath, - ArraySet<String> manifestExcludes, + ArraySet<PathWithRequiredFlags> manifestExcludes, ArraySet<String> systemExcludes, FullBackupDataOutput output) { // Pull out the domain and set it aside to use when making the tarball. @@ -684,7 +723,8 @@ public abstract class BackupAgent extends ContextWrapper { filePath = file.getCanonicalPath(); // prune this subtree? - if (manifestExcludes != null && manifestExcludes.contains(filePath)) { + if (manifestExcludes != null + && manifestExcludesContainFilePath(manifestExcludes, filePath)) { continue; } if (systemExcludes != null && systemExcludes.contains(filePath)) { @@ -720,6 +760,17 @@ public abstract class BackupAgent extends ContextWrapper { } } + private boolean manifestExcludesContainFilePath( + ArraySet<PathWithRequiredFlags> manifestExcludes, String filePath) { + for (PathWithRequiredFlags exclude : manifestExcludes) { + String excludePath = exclude.getPath(); + if (excludePath != null && excludePath.equals(filePath)) { + return true; + } + } + return false; + } + /** * Handle the data delivered via the given file descriptor during a full restore * operation. The agent is given the path to the file's original location as well @@ -766,8 +817,8 @@ public abstract class BackupAgent extends ContextWrapper { return false; } - Map<String, Set<String>> includes = null; - ArraySet<String> excludes = null; + Map<String, Set<PathWithRequiredFlags>> includes = null; + ArraySet<PathWithRequiredFlags> excludes = null; final String destinationCanonicalPath = destination.getCanonicalPath(); try { includes = bs.maybeParseAndGetCanonicalIncludePaths(); @@ -796,7 +847,7 @@ public abstract class BackupAgent extends ContextWrapper { // Rather than figure out the <include/> domain based on the path (a lot of code, and // it's a small list), we'll go through and look for it. boolean explicitlyIncluded = false; - for (Set<String> domainIncludes : includes.values()) { + for (Set<PathWithRequiredFlags> domainIncludes : includes.values()) { explicitlyIncluded |= isFileSpecifiedInPathList(destination, domainIncludes); if (explicitlyIncluded) { break; @@ -819,9 +870,10 @@ public abstract class BackupAgent extends ContextWrapper { * @return True if the provided file is either directly in the provided list, or the provided * file is within a directory in the list. */ - private boolean isFileSpecifiedInPathList(File file, Collection<String> canonicalPathList) - throws IOException { - for (String canonicalPath : canonicalPathList) { + private boolean isFileSpecifiedInPathList(File file, + Collection<PathWithRequiredFlags> canonicalPathList) throws IOException { + for (PathWithRequiredFlags canonical : canonicalPathList) { + String canonicalPath = canonical.getPath(); File fileFromList = new File(canonicalPath); if (fileFromList.isDirectory()) { if (file.isDirectory()) { @@ -920,12 +972,14 @@ public abstract class BackupAgent extends ContextWrapper { public void doBackup(ParcelFileDescriptor oldState, ParcelFileDescriptor data, ParcelFileDescriptor newState, - long quotaBytes, int token, IBackupManager callbackBinder) throws RemoteException { + long quotaBytes, int token, IBackupManager callbackBinder, int transportFlags) + throws RemoteException { // Ensure that we're running with the app's normal permission level long ident = Binder.clearCallingIdentity(); if (DEBUG) Log.v(TAG, "doBackup() invoked"); - BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor(), quotaBytes); + BackupDataOutput output = new BackupDataOutput( + data.getFileDescriptor(), quotaBytes, transportFlags); try { BackupAgent.this.onBackup(oldState, output, newState); @@ -999,7 +1053,7 @@ public abstract class BackupAgent extends ContextWrapper { @Override public void doFullBackup(ParcelFileDescriptor data, - long quotaBytes, int token, IBackupManager callbackBinder) { + long quotaBytes, int token, IBackupManager callbackBinder, int transportFlags) { // Ensure that we're running with the app's normal permission level long ident = Binder.clearCallingIdentity(); @@ -1010,7 +1064,8 @@ public abstract class BackupAgent extends ContextWrapper { waitForSharedPrefs(); try { - BackupAgent.this.onFullBackup(new FullBackupDataOutput(data, quotaBytes)); + BackupAgent.this.onFullBackup(new FullBackupDataOutput( + data, quotaBytes, transportFlags)); } catch (IOException ex) { Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex); throw new RuntimeException(ex); @@ -1044,10 +1099,12 @@ public abstract class BackupAgent extends ContextWrapper { } } - public void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder) { + public void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder, + int transportFlags) { // Ensure that we're running with the app's normal permission level final long ident = Binder.clearCallingIdentity(); - FullBackupDataOutput measureOutput = new FullBackupDataOutput(quotaBytes); + FullBackupDataOutput measureOutput = + new FullBackupDataOutput(quotaBytes, transportFlags); waitForSharedPrefs(); try { diff --git a/android/app/backup/BackupDataOutput.java b/android/app/backup/BackupDataOutput.java index c7586a29..5a66f340 100644 --- a/android/app/backup/BackupDataOutput.java +++ b/android/app/backup/BackupDataOutput.java @@ -18,6 +18,7 @@ package android.app.backup; import android.annotation.SystemApi; import android.os.ParcelFileDescriptor; + import java.io.FileDescriptor; import java.io.IOException; @@ -62,7 +63,10 @@ import java.io.IOException; * @see BackupAgent */ public class BackupDataOutput { - final long mQuota; + + private final long mQuota; + private final int mTransportFlags; + long mBackupWriter; /** @@ -71,14 +75,20 @@ public class BackupDataOutput { * @hide */ @SystemApi public BackupDataOutput(FileDescriptor fd) { - this(fd, -1); + this(fd, /*quota=*/ -1, /*transportFlags=*/ 0); } /** @hide */ @SystemApi public BackupDataOutput(FileDescriptor fd, long quota) { + this(fd, quota, /*transportFlags=*/ 0); + } + + /** @hide */ + public BackupDataOutput(FileDescriptor fd, long quota, int transportFlags) { if (fd == null) throw new NullPointerException(); mQuota = quota; + mTransportFlags = transportFlags; mBackupWriter = ctor(fd); if (mBackupWriter == 0) { throw new RuntimeException("Native initialization failed with fd=" + fd); @@ -96,6 +106,16 @@ public class BackupDataOutput { } /** + * Returns flags with additional information about the backup transport. For supported flags see + * {@link android.app.backup.BackupAgent} + * + * @see FullBackupDataOutput#getTransportFlags() + */ + public int getTransportFlags() { + return mTransportFlags; + } + + /** * Mark the beginning of one record in the backup data stream. This must be called before * {@link #writeEntityData}. * @param key A string key that uniquely identifies the data record within the application. diff --git a/android/app/backup/BackupManager.java b/android/app/backup/BackupManager.java index 12f44831..debc32bd 100644 --- a/android/app/backup/BackupManager.java +++ b/android/app/backup/BackupManager.java @@ -19,6 +19,7 @@ package android.app.backup; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -252,6 +253,8 @@ public class BackupManager { } /** + * @deprecated Since Android P app can no longer request restoring of its backup. + * * Restore the calling application from backup. The data will be restored from the * current backup dataset if the application has stored data there, or from * the dataset used during the last full device setup operation if the current @@ -269,6 +272,7 @@ public class BackupManager { * * @return Zero on success; nonzero on error. */ + @Deprecated public int requestRestore(RestoreObserver observer) { return requestRestore(observer, null); } @@ -276,6 +280,8 @@ public class BackupManager { // system APIs start here /** + * @deprecated Since Android P app can no longer request restoring of its backup. + * * Restore the calling application from backup. The data will be restored from the * current backup dataset if the application has stored data there, or from * the dataset used during the last full device setup operation if the current @@ -298,28 +304,12 @@ public class BackupManager { * * @hide */ + @Deprecated @SystemApi public int requestRestore(RestoreObserver observer, BackupManagerMonitor monitor) { - int result = -1; - checkServiceBinder(); - if (sService != null) { - RestoreSession session = null; - try { - IRestoreSession binder = sService.beginRestoreSession(mContext.getPackageName(), - null); - if (binder != null) { - session = new RestoreSession(mContext, binder); - result = session.restorePackage(mContext.getPackageName(), observer, monitor); - } - } catch (RemoteException e) { - Log.e(TAG, "restoreSelf() unable to contact service"); - } finally { - if (session != null) { - session.endRestoreSession(); - } - } - } - return result; + Log.w(TAG, "requestRestore(): Since Android P app can no longer request restoring" + + " of its backup."); + return -1; } /** @@ -726,6 +716,92 @@ public class BackupManager { } } + /** + * Returns an {@link Intent} for the specified transport's configuration UI. + * This value is set by {@link #updateTransportAttributes(ComponentName, String, Intent, String, + * Intent, String)}. + * @param transportName The name of the registered transport. + * @hide + */ + @SystemApi + @TestApi + @RequiresPermission(android.Manifest.permission.BACKUP) + public Intent getConfigurationIntent(String transportName) { + if (sService != null) { + try { + return sService.getConfigurationIntent(transportName); + } catch (RemoteException e) { + Log.e(TAG, "getConfigurationIntent() couldn't connect"); + } + } + return null; + } + + /** + * Returns a {@link String} describing where the specified transport is sending data. + * This value is set by {@link #updateTransportAttributes(ComponentName, String, Intent, String, + * Intent, String)}. + * @param transportName The name of the registered transport. + * @hide + */ + @SystemApi + @TestApi + @RequiresPermission(android.Manifest.permission.BACKUP) + public String getDestinationString(String transportName) { + if (sService != null) { + try { + return sService.getDestinationString(transportName); + } catch (RemoteException e) { + Log.e(TAG, "getDestinationString() couldn't connect"); + } + } + return null; + } + + /** + * Returns an {@link Intent} for the specified transport's data management UI. + * This value is set by {@link #updateTransportAttributes(ComponentName, String, Intent, String, + * Intent, String)}. + * @param transportName The name of the registered transport. + * @hide + */ + @SystemApi + @TestApi + @RequiresPermission(android.Manifest.permission.BACKUP) + public Intent getDataManagementIntent(String transportName) { + if (sService != null) { + try { + return sService.getDataManagementIntent(transportName); + } catch (RemoteException e) { + Log.e(TAG, "getDataManagementIntent() couldn't connect"); + } + } + return null; + } + + /** + * Returns a {@link String} describing what the specified transport's data management intent is + * used for. + * This value is set by {@link #updateTransportAttributes(ComponentName, String, Intent, String, + * Intent, String)}. + * + * @param transportName The name of the registered transport. + * @hide + */ + @SystemApi + @TestApi + @RequiresPermission(android.Manifest.permission.BACKUP) + public String getDataManagementLabel(String transportName) { + if (sService != null) { + try { + return sService.getDataManagementLabel(transportName); + } catch (RemoteException e) { + Log.e(TAG, "getDataManagementLabel() couldn't connect"); + } + } + return null; + } + /* * We wrap incoming binder calls with a private class implementation that * redirects them into main-thread actions. This serializes the backup diff --git a/android/app/backup/BackupManagerMonitor.java b/android/app/backup/BackupManagerMonitor.java index a91aded1..07e7688a 100644 --- a/android/app/backup/BackupManagerMonitor.java +++ b/android/app/backup/BackupManagerMonitor.java @@ -174,7 +174,6 @@ public class BackupManagerMonitor { /** * The transport returned {@link BackupTransport#TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED}. - * @hide */ public static final int LOG_EVENT_ID_TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED = 51; diff --git a/android/app/backup/BackupTransport.java b/android/app/backup/BackupTransport.java index 266f58df..0963594b 100644 --- a/android/app/backup/BackupTransport.java +++ b/android/app/backup/BackupTransport.java @@ -60,8 +60,6 @@ public class BackupTransport { * * <p>This is only valid when backup manager called {@link * #performBackup(PackageInfo, ParcelFileDescriptor, int)} with {@link #FLAG_INCREMENTAL}. - * - * @hide */ public static final int TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED = -1006; @@ -73,7 +71,7 @@ public class BackupTransport { * For key value backup, indicates that the backup data is a diff from a previous backup. The * transport must apply this diff to an existing backup to build the new backup set. * - * @hide + * @see #performBackup(PackageInfo, ParcelFileDescriptor, int) */ public static final int FLAG_INCREMENTAL = 1 << 1; @@ -81,10 +79,17 @@ public class BackupTransport { * For key value backup, indicates that the backup data is a complete set, not a diff from a * previous backup. The transport should clear any previous backup when storing this backup. * - * @hide + * @see #performBackup(PackageInfo, ParcelFileDescriptor, int) */ public static final int FLAG_NON_INCREMENTAL = 1 << 2; + /** + * Used as a boolean extra in the binding intent of transports. We pass {@code true} to + * notify transports that the current connection is used for registering the transport. + */ + public static final String EXTRA_TRANSPORT_REGISTRATION = + "android.app.backup.extra.TRANSPORT_REGISTRATION"; + IBackupTransport mBinderImpl = new TransportImpl(); public IBinder getBinder() { @@ -609,6 +614,15 @@ public class BackupTransport { } /** + * Returns flags with additional information about the transport, which is accessible to the + * {@link android.app.backup.BackupAgent}. This allows the agent to decide what to do based on + * properties of the transport. + */ + public int getTransportFlags() { + return 0; + } + + /** * Bridge between the actual IBackupTransport implementation and the stable API. If the * binder interface needs to change, we use this layer to translate so that we can * (if appropriate) decouple those framework-side changes from the BackupTransport @@ -740,6 +754,11 @@ public class BackupTransport { } @Override + public int getTransportFlags() { + return BackupTransport.this.getTransportFlags(); + } + + @Override public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) { return BackupTransport.this.getNextFullRestoreDataChunk(socket); } diff --git a/android/app/backup/FullBackup.java b/android/app/backup/FullBackup.java index a5dd5bd3..b7a8da59 100644 --- a/android/app/backup/FullBackup.java +++ b/android/app/backup/FullBackup.java @@ -82,6 +82,11 @@ public class FullBackup { public static final String FULL_RESTORE_INTENT_ACTION = "fullrest"; public static final String CONF_TOKEN_INTENT_EXTRA = "conftoken"; + public static final String FLAG_REQUIRED_CLIENT_SIDE_ENCRYPTION = "clientSideEncryption"; + public static final String FLAG_REQUIRED_DEVICE_TO_DEVICE_TRANSFER = "deviceToDeviceTransfer"; + public static final String FLAG_REQUIRED_FAKE_CLIENT_SIDE_ENCRYPTION = + "fakeClientSideEncryption"; + /** * @hide */ @@ -224,6 +229,9 @@ public class FullBackup { private final File EXTERNAL_DIR; + private final static String TAG_INCLUDE = "include"; + private final static String TAG_EXCLUDE = "exclude"; + final int mFullBackupContent; final PackageManager mPackageManager; final StorageManager mStorageManager; @@ -303,15 +311,45 @@ public class FullBackup { } /** - * A map of domain -> list of canonical file names in that domain that are to be included. - * We keep track of the domain so that we can go through the file system in order later on. - */ - Map<String, Set<String>> mIncludes; - /**e - * List that will be populated with the canonical names of each file or directory that is - * to be excluded. + * Represents a path attribute specified in an <include /> rule along with optional + * transport flags required from the transport to include file(s) under that path as + * specified by requiredFlags attribute. If optional requiredFlags attribute is not + * provided, default requiredFlags to 0. + * Note: since our parsing codepaths were the same for <include /> and <exclude /> tags, + * this structure is also used for <exclude /> tags to preserve that, however you can expect + * the getRequiredFlags() to always return 0 for exclude rules. + */ + public static class PathWithRequiredFlags { + private final String mPath; + private final int mRequiredFlags; + + public PathWithRequiredFlags(String path, int requiredFlags) { + mPath = path; + mRequiredFlags = requiredFlags; + } + + public String getPath() { + return mPath; + } + + public int getRequiredFlags() { + return mRequiredFlags; + } + } + + /** + * A map of domain -> set of pairs (canonical file; required transport flags) in that + * domain that are to be included if the transport has decared the required flags. + * We keep track of the domain so that we can go through the file system in order later on. + */ + Map<String, Set<PathWithRequiredFlags>> mIncludes; + + /** + * Set that will be populated with pairs (canonical file; requiredFlags=0) for each file or + * directory that is to be excluded. Note that for excludes, the requiredFlags attribute is + * ignored and the value should be always set to 0. */ - ArraySet<String> mExcludes; + ArraySet<PathWithRequiredFlags> mExcludes; BackupScheme(Context context) { mFullBackupContent = context.getApplicationInfo().fullBackupContent; @@ -356,13 +394,14 @@ public class FullBackup { } /** - * @return A mapping of domain -> canonical paths within that domain. Each of these paths - * specifies a file that the client has explicitly included in their backup set. If this - * map is empty we will back up the entire data directory (including managed external - * storage). + * @return A mapping of domain -> set of pairs (canonical file; required transport flags) + * in that domain that are to be included if the transport has decared the required flags. + * Each of these paths specifies a file that the client has explicitly included in their + * backup set. If this map is empty we will back up the entire data directory (including + * managed external storage). */ - public synchronized Map<String, Set<String>> maybeParseAndGetCanonicalIncludePaths() - throws IOException, XmlPullParserException { + public synchronized Map<String, Set<PathWithRequiredFlags>> + maybeParseAndGetCanonicalIncludePaths() throws IOException, XmlPullParserException { if (mIncludes == null) { maybeParseBackupSchemeLocked(); } @@ -370,9 +409,10 @@ public class FullBackup { } /** - * @return A set of canonical paths that are to be excluded from the backup/restore set. + * @return A set of (canonical paths; requiredFlags=0) that are to be excluded from the + * backup/restore set. */ - public synchronized ArraySet<String> maybeParseAndGetCanonicalExcludePaths() + public synchronized ArraySet<PathWithRequiredFlags> maybeParseAndGetCanonicalExcludePaths() throws IOException, XmlPullParserException { if (mExcludes == null) { maybeParseBackupSchemeLocked(); @@ -382,8 +422,8 @@ public class FullBackup { private void maybeParseBackupSchemeLocked() throws IOException, XmlPullParserException { // This not being null is how we know that we've tried to parse the xml already. - mIncludes = new ArrayMap<String, Set<String>>(); - mExcludes = new ArraySet<String>(); + mIncludes = new ArrayMap<String, Set<PathWithRequiredFlags>>(); + mExcludes = new ArraySet<PathWithRequiredFlags>(); if (mFullBackupContent == 0) { // android:fullBackupContent="true" which means that we'll do everything. @@ -415,8 +455,8 @@ public class FullBackup { @VisibleForTesting public void parseBackupSchemeFromXmlLocked(XmlPullParser parser, - Set<String> excludes, - Map<String, Set<String>> includes) + Set<PathWithRequiredFlags> excludes, + Map<String, Set<PathWithRequiredFlags>> includes) throws IOException, XmlPullParserException { int event = parser.getEventType(); // START_DOCUMENT while (event != XmlPullParser.START_TAG) { @@ -441,8 +481,7 @@ public class FullBackup { case XmlPullParser.START_TAG: validateInnerTagContents(parser); final String domainFromXml = parser.getAttributeValue(null, "domain"); - final File domainDirectory = - getDirectoryForCriteriaDomain(domainFromXml); + final File domainDirectory = getDirectoryForCriteriaDomain(domainFromXml); if (domainDirectory == null) { if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) { Log.v(TAG_XML_PARSER, "...parsing \"" + parser.getName() + "\": " @@ -457,12 +496,23 @@ public class FullBackup { break; } - Set<String> activeSet = parseCurrentTagForDomain( + int requiredFlags = 0; // no transport flags are required by default + if (TAG_INCLUDE.equals(parser.getName())) { + // requiredFlags are only supported for <include /> tag, for <exclude /> + // we should always leave them as the default = 0 + requiredFlags = getRequiredFlagsFromString( + parser.getAttributeValue(null, "requireFlags")); + } + + // retrieve the include/exclude set we'll be adding this rule to + Set<PathWithRequiredFlags> activeSet = parseCurrentTagForDomain( parser, excludes, includes, domainFromXml); - activeSet.add(canonicalFile.getCanonicalPath()); + activeSet.add(new PathWithRequiredFlags(canonicalFile.getCanonicalPath(), + requiredFlags)); if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) { Log.v(TAG_XML_PARSER, "...parsed " + canonicalFile.getCanonicalPath() - + " for domain \"" + domainFromXml + "\""); + + " for domain \"" + domainFromXml + "\", requiredFlags + \"" + + requiredFlags + "\""); } // Special case journal files (not dirs) for sqlite database. frowny-face. @@ -472,14 +522,16 @@ public class FullBackup { if ("database".equals(domainFromXml) && !canonicalFile.isDirectory()) { final String canonicalJournalPath = canonicalFile.getCanonicalPath() + "-journal"; - activeSet.add(canonicalJournalPath); + activeSet.add(new PathWithRequiredFlags(canonicalJournalPath, + requiredFlags)); if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) { Log.v(TAG_XML_PARSER, "...automatically generated " + canonicalJournalPath + ". Ignore if nonexistent."); } final String canonicalWalPath = canonicalFile.getCanonicalPath() + "-wal"; - activeSet.add(canonicalWalPath); + activeSet.add(new PathWithRequiredFlags(canonicalWalPath, + requiredFlags)); if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) { Log.v(TAG_XML_PARSER, "...automatically generated " + canonicalWalPath + ". Ignore if nonexistent."); @@ -491,7 +543,8 @@ public class FullBackup { !canonicalFile.getCanonicalPath().endsWith(".xml")) { final String canonicalXmlPath = canonicalFile.getCanonicalPath() + ".xml"; - activeSet.add(canonicalXmlPath); + activeSet.add(new PathWithRequiredFlags(canonicalXmlPath, + requiredFlags)); if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) { Log.v(TAG_XML_PARSER, "...automatically generated " + canonicalXmlPath + ". Ignore if nonexistent."); @@ -508,10 +561,12 @@ public class FullBackup { Log.v(TAG_XML_PARSER, " ...nothing specified (This means the entirety of app" + " data minus excludes)"); } else { - for (Map.Entry<String, Set<String>> entry : includes.entrySet()) { + for (Map.Entry<String, Set<PathWithRequiredFlags>> entry + : includes.entrySet()) { Log.v(TAG_XML_PARSER, " domain=" + entry.getKey()); - for (String includeData : entry.getValue()) { - Log.v(TAG_XML_PARSER, " " + includeData); + for (PathWithRequiredFlags includeData : entry.getValue()) { + Log.v(TAG_XML_PARSER, " path: " + includeData.getPath() + + " requiredFlags: " + includeData.getRequiredFlags()); } } } @@ -520,8 +575,9 @@ public class FullBackup { if (excludes.isEmpty()) { Log.v(TAG_XML_PARSER, " ...nothing to exclude."); } else { - for (String excludeData : excludes) { - Log.v(TAG_XML_PARSER, " " + excludeData); + for (PathWithRequiredFlags excludeData : excludes) { + Log.v(TAG_XML_PARSER, " path: " + excludeData.getPath() + + " requiredFlags: " + excludeData.getRequiredFlags()); } } @@ -531,20 +587,43 @@ public class FullBackup { } } - private Set<String> parseCurrentTagForDomain(XmlPullParser parser, - Set<String> excludes, - Map<String, Set<String>> includes, - String domain) + private int getRequiredFlagsFromString(String requiredFlags) { + int flags = 0; + if (requiredFlags == null || requiredFlags.length() == 0) { + // requiredFlags attribute was missing or empty in <include /> tag + return flags; + } + String[] flagsStr = requiredFlags.split("\\|"); + for (String f : flagsStr) { + switch (f) { + case FLAG_REQUIRED_CLIENT_SIDE_ENCRYPTION: + flags |= BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED; + break; + case FLAG_REQUIRED_DEVICE_TO_DEVICE_TRANSFER: + flags |= BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER; + break; + case FLAG_REQUIRED_FAKE_CLIENT_SIDE_ENCRYPTION: + flags |= BackupAgent.FLAG_FAKE_CLIENT_SIDE_ENCRYPTION_ENABLED; + default: + Log.w(TAG, "Unrecognized requiredFlag provided, value: \"" + f + "\""); + } + } + return flags; + } + + private Set<PathWithRequiredFlags> parseCurrentTagForDomain(XmlPullParser parser, + Set<PathWithRequiredFlags> excludes, + Map<String, Set<PathWithRequiredFlags>> includes, String domain) throws XmlPullParserException { - if ("include".equals(parser.getName())) { + if (TAG_INCLUDE.equals(parser.getName())) { final String domainToken = getTokenForXmlDomain(domain); - Set<String> includeSet = includes.get(domainToken); + Set<PathWithRequiredFlags> includeSet = includes.get(domainToken); if (includeSet == null) { - includeSet = new ArraySet<String>(); + includeSet = new ArraySet<PathWithRequiredFlags>(); includes.put(domainToken, includeSet); } return includeSet; - } else if ("exclude".equals(parser.getName())) { + } else if (TAG_EXCLUDE.equals(parser.getName())) { return excludes; } else { // Unrecognised tag => hard failure. @@ -589,8 +668,8 @@ public class FullBackup { /** * * @param domain Directory where the specified file should exist. Not null. - * @param filePathFromXml parsed from xml. Not sanitised before calling this function so may be - * null. + * @param filePathFromXml parsed from xml. Not sanitised before calling this function so may + * be null. * @return The canonical path of the file specified or null if no such file exists. */ private File extractCanonicalFile(File domain, String filePathFromXml) { @@ -650,15 +729,27 @@ public class FullBackup { * Let's be strict about the type of xml the client can write. If we see anything untoward, * throw an XmlPullParserException. */ - private void validateInnerTagContents(XmlPullParser parser) - throws XmlPullParserException { - if (parser.getAttributeCount() > 2) { - throw new XmlPullParserException("At most 2 tag attributes allowed for \"" - + parser.getName() + "\" tag (\"domain\" & \"path\"."); + private void validateInnerTagContents(XmlPullParser parser) throws XmlPullParserException { + if (parser == null) { + return; } - if (!"include".equals(parser.getName()) && !"exclude".equals(parser.getName())) { - throw new XmlPullParserException("A valid tag is one of \"<include/>\" or" + - " \"<exclude/>. You provided \"" + parser.getName() + "\""); + switch (parser.getName()) { + case TAG_INCLUDE: + if (parser.getAttributeCount() > 3) { + throw new XmlPullParserException("At most 3 tag attributes allowed for " + + "\"include\" tag (\"domain\" & \"path\"" + + " & optional \"requiredFlags\")."); + } + break; + case TAG_EXCLUDE: + if (parser.getAttributeCount() > 2) { + throw new XmlPullParserException("At most 2 tag attributes allowed for " + + "\"exclude\" tag (\"domain\" & \"path\"."); + } + break; + default: + throw new XmlPullParserException("A valid tag is one of \"<include/>\" or" + + " \"<exclude/>. You provided \"" + parser.getName() + "\""); } } } diff --git a/android/app/backup/FullBackupDataOutput.java b/android/app/backup/FullBackupDataOutput.java index 5deedd03..18f42833 100644 --- a/android/app/backup/FullBackupDataOutput.java +++ b/android/app/backup/FullBackupDataOutput.java @@ -11,6 +11,7 @@ public class FullBackupDataOutput { // Currently a name-scoping shim around BackupDataOutput private final BackupDataOutput mData; private final long mQuota; + private final int mTransportFlags; private long mSize; /** @@ -23,22 +24,49 @@ public class FullBackupDataOutput { return mQuota; } + /** + * Returns flags with additional information about the backup transport. For supported flags see + * {@link android.app.backup.BackupAgent} + * + * @see BackupDataOutput#getTransportFlags() + */ + public int getTransportFlags() { + return mTransportFlags; + } + /** @hide - used only in measure operation */ public FullBackupDataOutput(long quota) { mData = null; mQuota = quota; mSize = 0; + mTransportFlags = 0; + } + + /** @hide - used only in measure operation */ + public FullBackupDataOutput(long quota, int transportFlags) { + mData = null; + mQuota = quota; + mSize = 0; + mTransportFlags = transportFlags; } /** @hide */ public FullBackupDataOutput(ParcelFileDescriptor fd, long quota) { - mData = new BackupDataOutput(fd.getFileDescriptor(), quota); + mData = new BackupDataOutput(fd.getFileDescriptor(), quota, 0); + mQuota = quota; + mTransportFlags = 0; + } + + /** @hide */ + public FullBackupDataOutput(ParcelFileDescriptor fd, long quota, int transportFlags) { + mData = new BackupDataOutput(fd.getFileDescriptor(), quota, transportFlags); mQuota = quota; + mTransportFlags = transportFlags; } /** @hide - used only internally to the backup manager service's stream construction */ public FullBackupDataOutput(ParcelFileDescriptor fd) { - this(fd, -1); + this(fd, /*quota=*/ -1, /*transportFlags=*/ 0); } /** @hide */ diff --git a/android/app/job/JobInfo.java b/android/app/job/JobInfo.java index cba9dcc3..02afcc7c 100644 --- a/android/app/job/JobInfo.java +++ b/android/app/job/JobInfo.java @@ -65,7 +65,6 @@ public class JobInfo implements Parcelable { NETWORK_TYPE_UNMETERED, NETWORK_TYPE_NOT_ROAMING, NETWORK_TYPE_CELLULAR, - NETWORK_TYPE_METERED, }) @Retention(RetentionPolicy.SOURCE) public @interface NetworkType {} @@ -253,7 +252,15 @@ public class JobInfo implements Parcelable { /** * @hide */ - public static final int FLAG_IS_PREFETCH = 1 << 2; + public static final int FLAG_PREFETCH = 1 << 2; + + /** + * This job needs to be exempted from the app standby throttling. Only the system (UID 1000) + * can set it. Jobs with a time constrant must not have it. + * + * @hide + */ + public static final int FLAG_EXEMPT_FROM_APP_STANDBY = 1 << 3; /** * @hide @@ -288,7 +295,8 @@ public class JobInfo implements Parcelable { private final boolean hasEarlyConstraint; private final boolean hasLateConstraint; private final NetworkRequest networkRequest; - private final long networkBytes; + private final long networkDownloadBytes; + private final long networkUploadBytes; private final long minLatencyMillis; private final long maxExecutionDelayMillis; private final boolean isPeriodic; @@ -309,30 +317,28 @@ public class JobInfo implements Parcelable { } /** - * Bundle of extras which are returned to your application at execution time. + * @see JobInfo.Builder#setExtras(PersistableBundle) */ public @NonNull PersistableBundle getExtras() { return extras; } /** - * Bundle of transient extras which are returned to your application at execution time, - * but not persisted by the system. + * @see JobInfo.Builder#setTransientExtras(Bundle) */ public @NonNull Bundle getTransientExtras() { return transientExtras; } /** - * ClipData of information that is returned to your application at execution time, - * but not persisted by the system. + * @see JobInfo.Builder#setClipData(ClipData, int) */ public @Nullable ClipData getClipData() { return clipData; } /** - * Permission grants that go along with {@link #getClipData}. + * @see JobInfo.Builder#setClipData(ClipData, int) */ public int getClipGrantFlags() { return clipGrantFlags; @@ -355,33 +361,34 @@ public class JobInfo implements Parcelable { return flags; } + /** @hide */ + public boolean isExemptedFromAppStandby() { + return ((flags & FLAG_EXEMPT_FROM_APP_STANDBY) != 0) && !isPeriodic(); + } + /** - * Whether this job requires that the device be charging (or be a non-battery-powered - * device connected to permanent power, such as Android TV devices). + * @see JobInfo.Builder#setRequiresCharging(boolean) */ public boolean isRequireCharging() { return (constraintFlags & CONSTRAINT_FLAG_CHARGING) != 0; } /** - * Whether this job needs the device's battery level to not be at below the critical threshold. + * @see JobInfo.Builder#setRequiresBatteryNotLow(boolean) */ public boolean isRequireBatteryNotLow() { return (constraintFlags & CONSTRAINT_FLAG_BATTERY_NOT_LOW) != 0; } /** - * Whether this job requires that the user <em>not</em> be interacting with the device. - * - * <p class="note">This is <em>not</em> the same as "doze" or "device idle"; - * it is purely about the user's direct interactions.</p> + * @see JobInfo.Builder#setRequiresDeviceIdle(boolean) */ public boolean isRequireDeviceIdle() { return (constraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0; } /** - * Whether this job needs the device's storage to not be low. + * @see JobInfo.Builder#setRequiresStorageNotLow(boolean) */ public boolean isRequireStorageNotLow() { return (constraintFlags & CONSTRAINT_FLAG_STORAGE_NOT_LOW) != 0; @@ -397,6 +404,7 @@ public class JobInfo implements Parcelable { /** * Which content: URIs must change for the job to be scheduled. Returns null * if there are none required. + * @see JobInfo.Builder#addTriggerContentUri(TriggerContentUri) */ public @Nullable TriggerContentUri[] getTriggerContentUris() { return triggerContentUris; @@ -405,6 +413,7 @@ public class JobInfo implements Parcelable { /** * When triggering on content URI changes, this is the delay from when a change * is detected until the job is scheduled. + * @see JobInfo.Builder#setTriggerContentUpdateDelay(long) */ public long getTriggerContentUpdateDelay() { return triggerContentUpdateDelay; @@ -413,6 +422,7 @@ public class JobInfo implements Parcelable { /** * When triggering on content URI changes, this is the maximum delay we will * use before scheduling the job. + * @see JobInfo.Builder#setTriggerContentMaxDelay(long) */ public long getTriggerContentMaxDelay() { return triggerContentMaxDelay; @@ -453,28 +463,59 @@ public class JobInfo implements Parcelable { } /** - * Return the estimated size of network traffic that will be performed by + * @deprecated replaced by {@link #getEstimatedNetworkDownloadBytes()} and + * {@link #getEstimatedNetworkUploadBytes()}. + * @removed + */ + @Deprecated + public @BytesLong long getEstimatedNetworkBytes() { + if (networkDownloadBytes == NETWORK_BYTES_UNKNOWN + && networkUploadBytes == NETWORK_BYTES_UNKNOWN) { + return NETWORK_BYTES_UNKNOWN; + } else if (networkDownloadBytes == NETWORK_BYTES_UNKNOWN) { + return networkUploadBytes; + } else if (networkUploadBytes == NETWORK_BYTES_UNKNOWN) { + return networkDownloadBytes; + } else { + return networkDownloadBytes + networkUploadBytes; + } + } + + /** + * Return the estimated size of download traffic that will be performed by * this job, in bytes. * - * @return Estimated size of network traffic, or + * @return Estimated size of download traffic, or * {@link #NETWORK_BYTES_UNKNOWN} when unknown. - * @see Builder#setEstimatedNetworkBytes(long) + * @see Builder#setEstimatedNetworkBytes(long, long) */ - public @BytesLong long getEstimatedNetworkBytes() { - return networkBytes; + public @BytesLong long getEstimatedNetworkDownloadBytes() { + return networkDownloadBytes; + } + + /** + * Return the estimated size of upload traffic that will be performed by + * this job, in bytes. + * + * @return Estimated size of upload traffic, or + * {@link #NETWORK_BYTES_UNKNOWN} when unknown. + * @see Builder#setEstimatedNetworkBytes(long, long) + */ + public @BytesLong long getEstimatedNetworkUploadBytes() { + return networkUploadBytes; } /** * Set for a job that does not recur periodically, to specify a delay after which the job * will be eligible for execution. This value is not set if the job recurs periodically. + * @see JobInfo.Builder#setMinimumLatency(long) */ public long getMinLatencyMillis() { return minLatencyMillis; } /** - * See {@link Builder#setOverrideDeadline(long)}. This value is not set if the job recurs - * periodically. + * @see JobInfo.Builder#setOverrideDeadline(long) */ public long getMaxExecutionDelayMillis() { return maxExecutionDelayMillis; @@ -482,13 +523,15 @@ public class JobInfo implements Parcelable { /** * Track whether this job will repeat with a given period. + * @see JobInfo.Builder#setPeriodic(long) + * @see JobInfo.Builder#setPeriodic(long, long) */ public boolean isPeriodic() { return isPeriodic; } /** - * @return Whether or not this job should be persisted across device reboots. + * @see JobInfo.Builder#setPersisted(boolean) */ public boolean isPersisted() { return isPersisted; @@ -497,6 +540,8 @@ public class JobInfo implements Parcelable { /** * Set to the interval between occurrences of this job. This value is <b>not</b> set if the * job does not recur periodically. + * @see JobInfo.Builder#setPeriodic(long) + * @see JobInfo.Builder#setPeriodic(long, long) */ public long getIntervalMillis() { return intervalMillis; @@ -505,6 +550,8 @@ public class JobInfo implements Parcelable { /** * Flex time for this job. Only valid if this is a periodic job. The job can * execute at any time in a window of flex length at the end of the period. + * @see JobInfo.Builder#setPeriodic(long) + * @see JobInfo.Builder#setPeriodic(long, long) */ public long getFlexMillis() { return flexMillis; @@ -514,6 +561,7 @@ public class JobInfo implements Parcelable { * The amount of time the JobScheduler will wait before rescheduling a failed job. This value * will be increased depending on the backoff policy specified at job creation time. Defaults * to 30 seconds, minimum is currently 10 seconds. + * @see JobInfo.Builder#setBackoffCriteria(long, int) */ public long getInitialBackoffMillis() { return initialBackoffMillis; @@ -521,12 +569,27 @@ public class JobInfo implements Parcelable { /** * Return the backoff policy of this job. + * @see JobInfo.Builder#setBackoffCriteria(long, int) */ public @BackoffPolicy int getBackoffPolicy() { return backoffPolicy; } /** + * @see JobInfo.Builder#setImportantWhileForeground(boolean) + */ + public boolean isImportantWhileForeground() { + return (flags & FLAG_IMPORTANT_WHILE_FOREGROUND) != 0; + } + + /** + * @see JobInfo.Builder#setPrefetch(boolean) + */ + public boolean isPrefetch() { + return (flags & FLAG_PREFETCH) != 0; + } + + /** * User can specify an early constraint of 0L, which is valid, so we keep track of whether the * function was called at all. * @hide @@ -597,7 +660,10 @@ public class JobInfo implements Parcelable { if (!Objects.equals(networkRequest, j.networkRequest)) { return false; } - if (networkBytes != j.networkBytes) { + if (networkDownloadBytes != j.networkDownloadBytes) { + return false; + } + if (networkUploadBytes != j.networkUploadBytes) { return false; } if (minLatencyMillis != j.minLatencyMillis) { @@ -660,7 +726,8 @@ public class JobInfo implements Parcelable { if (networkRequest != null) { hashCode = 31 * hashCode + networkRequest.hashCode(); } - hashCode = 31 * hashCode + Long.hashCode(networkBytes); + hashCode = 31 * hashCode + Long.hashCode(networkDownloadBytes); + hashCode = 31 * hashCode + Long.hashCode(networkUploadBytes); hashCode = 31 * hashCode + Long.hashCode(minLatencyMillis); hashCode = 31 * hashCode + Long.hashCode(maxExecutionDelayMillis); hashCode = 31 * hashCode + Boolean.hashCode(isPeriodic); @@ -695,7 +762,8 @@ public class JobInfo implements Parcelable { } else { networkRequest = null; } - networkBytes = in.readLong(); + networkDownloadBytes = in.readLong(); + networkUploadBytes = in.readLong(); minLatencyMillis = in.readLong(); maxExecutionDelayMillis = in.readLong(); isPeriodic = in.readInt() == 1; @@ -724,7 +792,8 @@ public class JobInfo implements Parcelable { triggerContentUpdateDelay = b.mTriggerContentUpdateDelay; triggerContentMaxDelay = b.mTriggerContentMaxDelay; networkRequest = b.mNetworkRequest; - networkBytes = b.mNetworkBytes; + networkDownloadBytes = b.mNetworkDownloadBytes; + networkUploadBytes = b.mNetworkUploadBytes; minLatencyMillis = b.mMinLatencyMillis; maxExecutionDelayMillis = b.mMaxExecutionDelayMillis; isPeriodic = b.mIsPeriodic; @@ -767,7 +836,8 @@ public class JobInfo implements Parcelable { } else { out.writeInt(0); } - out.writeLong(networkBytes); + out.writeLong(networkDownloadBytes); + out.writeLong(networkUploadBytes); out.writeLong(minLatencyMillis); out.writeLong(maxExecutionDelayMillis); out.writeInt(isPeriodic ? 1 : 0); @@ -901,7 +971,8 @@ public class JobInfo implements Parcelable { // Requirements. private int mConstraintFlags; private NetworkRequest mNetworkRequest; - private long mNetworkBytes = NETWORK_BYTES_UNKNOWN; + private long mNetworkDownloadBytes = NETWORK_BYTES_UNKNOWN; + private long mNetworkUploadBytes = NETWORK_BYTES_UNKNOWN; private ArrayList<TriggerContentUri> mTriggerContentUris; private long mTriggerContentUpdateDelay = -1; private long mTriggerContentMaxDelay = -1; @@ -952,6 +1023,7 @@ public class JobInfo implements Parcelable { /** * Set optional extras. This is persisted, so we only allow primitive types. * @param extras Bundle containing extras you want the scheduler to hold on to for you. + * @see JobInfo#getExtras() */ public Builder setExtras(@NonNull PersistableBundle extras) { mExtras = extras; @@ -966,6 +1038,7 @@ public class JobInfo implements Parcelable { * {@link android.app.job.JobInfo.Builder#build()} is called.</p> * * @param extras Bundle containing extras you want the scheduler to hold on to for you. + * @see JobInfo#getTransientExtras() */ public Builder setTransientExtras(@NonNull Bundle extras) { mTransientExtras = extras; @@ -993,6 +1066,8 @@ public class JobInfo implements Parcelable { * a combination of {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION}, * {@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION}, and * {@link android.content.Intent#FLAG_GRANT_PREFIX_URI_PERMISSION}. + * @see JobInfo#getClipData() + * @see JobInfo#getClipGrantFlags() */ public Builder setClipData(@Nullable ClipData clip, int grantFlags) { mClipData = clip; @@ -1083,6 +1158,16 @@ public class JobInfo implements Parcelable { } /** + * @deprecated replaced by + * {@link #setEstimatedNetworkBytes(long, long)}. + * @removed + */ + @Deprecated + public Builder setEstimatedNetworkBytes(@BytesLong long networkBytes) { + return setEstimatedNetworkBytes(networkBytes, NETWORK_BYTES_UNKNOWN); + } + + /** * Set the estimated size of network traffic that will be performed by * this job, in bytes. * <p> @@ -1099,23 +1184,30 @@ public class JobInfo implements Parcelable { * <li>A job that synchronizes email could end up using an extreme range * of data, from under 1KB when nothing has changed, to dozens of MB * when there are new emails with attachments. Jobs that cannot provide - * reasonable estimates should leave this estimated value undefined. + * reasonable estimates should use the sentinel value + * {@link JobInfo#NETWORK_BYTES_UNKNOWN}. * </ul> * Note that the system may choose to delay jobs with large network * usage estimates when the device has a poor network connection, in * order to save battery. + * <p> + * The values provided here only reflect the traffic that will be + * performed by the base job; if you're using {@link JobWorkItem} then + * you also need to define the network traffic used by each work item + * when constructing them. * - * @param networkBytes The estimated size of network traffic that will - * be performed by this job, in bytes. This value only - * reflects the traffic that will be performed by the base - * job; if you're using {@link JobWorkItem} then you also - * need to define the network traffic used by each work item - * when constructing them. - * @see JobInfo#getEstimatedNetworkBytes() - * @see JobWorkItem#JobWorkItem(android.content.Intent, long) + * @param downloadBytes The estimated size of network traffic that will + * be downloaded by this job, in bytes. + * @param uploadBytes The estimated size of network traffic that will be + * uploaded by this job, in bytes. + * @see JobInfo#getEstimatedNetworkDownloadBytes() + * @see JobInfo#getEstimatedNetworkUploadBytes() + * @see JobWorkItem#JobWorkItem(android.content.Intent, long, long) */ - public Builder setEstimatedNetworkBytes(@BytesLong long networkBytes) { - mNetworkBytes = networkBytes; + public Builder setEstimatedNetworkBytes(@BytesLong long downloadBytes, + @BytesLong long uploadBytes) { + mNetworkDownloadBytes = downloadBytes; + mNetworkUploadBytes = uploadBytes; return this; } @@ -1133,6 +1225,7 @@ public class JobInfo implements Parcelable { * * @param requiresCharging Pass {@code true} to require that the device be * charging in order to run the job. + * @see JobInfo#isRequireCharging() */ public Builder setRequiresCharging(boolean requiresCharging) { mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_CHARGING) @@ -1146,6 +1239,7 @@ public class JobInfo implements Parcelable { * is not low, which is generally the point where the user is given a "low battery" * warning. * @param batteryNotLow Whether or not the device's battery level must not be low. + * @see JobInfo#isRequireBatteryNotLow() */ public Builder setRequiresBatteryNotLow(boolean batteryNotLow) { mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_BATTERY_NOT_LOW) @@ -1170,6 +1264,7 @@ public class JobInfo implements Parcelable { * * @param requiresDeviceIdle Pass {@code true} to prevent the job from running * while the device is being used interactively. + * @see JobInfo#isRequireDeviceIdle() */ public Builder setRequiresDeviceIdle(boolean requiresDeviceIdle) { mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_DEVICE_IDLE) @@ -1183,6 +1278,7 @@ public class JobInfo implements Parcelable { * in a low storage state, which is generally the point where the user is given a * "low storage" warning. * @param storageNotLow Whether or not the device's available storage must not be low. + * @see JobInfo#isRequireStorageNotLow() */ public Builder setRequiresStorageNotLow(boolean storageNotLow) { mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_STORAGE_NOT_LOW) @@ -1215,6 +1311,7 @@ public class JobInfo implements Parcelable { * job} * * @param uri The content: URI to monitor. + * @see JobInfo#getTriggerContentUris() */ public Builder addTriggerContentUri(@NonNull TriggerContentUri uri) { if (mTriggerContentUris == null) { @@ -1229,6 +1326,7 @@ public class JobInfo implements Parcelable { * the job is scheduled. If there are more changes during that time, the delay * will be reset to start at the time of the most recent change. * @param durationMs Delay after most recent content change, in milliseconds. + * @see JobInfo#getTriggerContentUpdateDelay() */ public Builder setTriggerContentUpdateDelay(long durationMs) { mTriggerContentUpdateDelay = durationMs; @@ -1239,6 +1337,7 @@ public class JobInfo implements Parcelable { * Set the maximum total delay (in milliseconds) that is allowed from the first * time a content change is detected until the job is scheduled. * @param durationMs Delay after initial content change, in milliseconds. + * @see JobInfo#getTriggerContentMaxDelay() */ public Builder setTriggerContentMaxDelay(long durationMs) { mTriggerContentMaxDelay = durationMs; @@ -1252,6 +1351,8 @@ public class JobInfo implements Parcelable { * Setting this function on the builder with {@link #setMinimumLatency(long)} or * {@link #setOverrideDeadline(long)} will result in an error. * @param intervalMillis Millisecond interval for which this job will repeat. + * @see JobInfo#getIntervalMillis() + * @see JobInfo#getFlexMillis() */ public Builder setPeriodic(long intervalMillis) { return setPeriodic(intervalMillis, intervalMillis); @@ -1265,6 +1366,8 @@ public class JobInfo implements Parcelable { * @param flexMillis Millisecond flex for this job. Flex is clamped to be at least * {@link #getMinFlexMillis()} or 5 percent of the period, whichever is * higher. + * @see JobInfo#getIntervalMillis() + * @see JobInfo#getFlexMillis() */ public Builder setPeriodic(long intervalMillis, long flexMillis) { final long minPeriod = getMinPeriodMillis(); @@ -1296,6 +1399,7 @@ public class JobInfo implements Parcelable { * {@link android.app.job.JobInfo.Builder#build()} is called. * @param minLatencyMillis Milliseconds before which this job will not be considered for * execution. + * @see JobInfo#getMinLatencyMillis() */ public Builder setMinimumLatency(long minLatencyMillis) { mMinLatencyMillis = minLatencyMillis; @@ -1309,6 +1413,7 @@ public class JobInfo implements Parcelable { * this property on a periodic job, doing so will throw an * {@link java.lang.IllegalArgumentException} when * {@link android.app.job.JobInfo.Builder#build()} is called. + * @see JobInfo#getMaxExecutionDelayMillis() */ public Builder setOverrideDeadline(long maxExecutionDelayMillis) { mMaxExecutionDelayMillis = maxExecutionDelayMillis; @@ -1328,6 +1433,8 @@ public class JobInfo implements Parcelable { * mode. * @param initialBackoffMillis Millisecond time interval to wait initially when job has * failed. + * @see JobInfo#getInitialBackoffMillis() + * @see JobInfo#getBackoffPolicy() */ public Builder setBackoffCriteria(long initialBackoffMillis, @BackoffPolicy int backoffPolicy) { @@ -1358,6 +1465,7 @@ public class JobInfo implements Parcelable { * * @param importantWhileForeground whether to relax doze restrictions for this job when the * app is in the foreground. False by default. + * @see JobInfo#isImportantWhileForeground() */ public Builder setImportantWhileForeground(boolean importantWhileForeground) { if (importantWhileForeground) { @@ -1369,6 +1477,15 @@ public class JobInfo implements Parcelable { } /** + * @removed + * @deprecated replaced with {@link #setPrefetch(boolean)} + */ + @Deprecated + public Builder setIsPrefetch(boolean isPrefetch) { + return setPrefetch(isPrefetch); + } + + /** * Setting this to true indicates that this job is designed to prefetch * content that will make a material improvement to the experience of * the specific user of this device. For example, fetching top headlines @@ -1380,12 +1497,13 @@ public class JobInfo implements Parcelable { * network when there is a surplus of metered data available. The system * may also use this signal in combination with end user usage patterns * to ensure data is prefetched before the user launches your app. + * @see JobInfo#isPrefetch() */ - public Builder setIsPrefetch(boolean isPrefetch) { - if (isPrefetch) { - mFlags |= FLAG_IS_PREFETCH; + public Builder setPrefetch(boolean prefetch) { + if (prefetch) { + mFlags |= FLAG_PREFETCH; } else { - mFlags &= (~FLAG_IS_PREFETCH); + mFlags &= (~FLAG_PREFETCH); } return this; } @@ -1395,6 +1513,7 @@ public class JobInfo implements Parcelable { * * @param isPersisted True to indicate that the job will be written to * disk and loaded at boot. + * @see JobInfo#isPersisted() */ @RequiresPermission(android.Manifest.permission.RECEIVE_BOOT_COMPLETED) public Builder setPersisted(boolean isPersisted) { @@ -1414,7 +1533,7 @@ public class JobInfo implements Parcelable { "constraints, this is not allowed."); } // Check that network estimates require network type - if (mNetworkBytes > 0 && mNetworkRequest == null) { + if ((mNetworkDownloadBytes > 0 || mNetworkUploadBytes > 0) && mNetworkRequest == null) { throw new IllegalArgumentException( "Can't provide estimated network usage without requiring a network"); } diff --git a/android/app/job/JobParameters.java b/android/app/job/JobParameters.java index c71bf2e6..d67f11bb 100644 --- a/android/app/job/JobParameters.java +++ b/android/app/job/JobParameters.java @@ -36,15 +36,16 @@ import android.os.RemoteException; public class JobParameters implements Parcelable { /** @hide */ - public static final int REASON_CANCELED = 0; + public static final int REASON_CANCELED = JobProtoEnums.STOP_REASON_CANCELLED; // 0. /** @hide */ - public static final int REASON_CONSTRAINTS_NOT_SATISFIED = 1; + public static final int REASON_CONSTRAINTS_NOT_SATISFIED = + JobProtoEnums.STOP_REASON_CONSTRAINTS_NOT_SATISFIED; //1. /** @hide */ - public static final int REASON_PREEMPT = 2; + public static final int REASON_PREEMPT = JobProtoEnums.STOP_REASON_PREEMPT; // 2. /** @hide */ - public static final int REASON_TIMEOUT = 3; + public static final int REASON_TIMEOUT = JobProtoEnums.STOP_REASON_TIMEOUT; // 3. /** @hide */ - public static final int REASON_DEVICE_IDLE = 4; + public static final int REASON_DEVICE_IDLE = JobProtoEnums.STOP_REASON_DEVICE_IDLE; // 4. /** @hide */ public static String getReasonName(int reason) { diff --git a/android/app/job/JobWorkItem.java b/android/app/job/JobWorkItem.java index 1c46e8ec..995f5226 100644 --- a/android/app/job/JobWorkItem.java +++ b/android/app/job/JobWorkItem.java @@ -16,6 +16,8 @@ package android.app.job; +import static android.app.job.JobInfo.NETWORK_BYTES_UNKNOWN; + import android.annotation.BytesLong; import android.content.Intent; import android.os.Parcel; @@ -28,7 +30,8 @@ import android.os.Parcelable; */ final public class JobWorkItem implements Parcelable { final Intent mIntent; - final long mNetworkBytes; + final long mNetworkDownloadBytes; + final long mNetworkUploadBytes; int mDeliveryCount; int mWorkId; Object mGrants; @@ -41,22 +44,36 @@ final public class JobWorkItem implements Parcelable { */ public JobWorkItem(Intent intent) { mIntent = intent; - mNetworkBytes = JobInfo.NETWORK_BYTES_UNKNOWN; + mNetworkDownloadBytes = NETWORK_BYTES_UNKNOWN; + mNetworkUploadBytes = NETWORK_BYTES_UNKNOWN; + } + + /** + * @deprecated replaced by {@link #JobWorkItem(Intent, long, long)} + * @removed + */ + @Deprecated + public JobWorkItem(Intent intent, @BytesLong long networkBytes) { + this(intent, networkBytes, NETWORK_BYTES_UNKNOWN); } /** * Create a new piece of work, which can be submitted to * {@link JobScheduler#enqueue JobScheduler.enqueue}. + * <p> + * See {@link JobInfo.Builder#setEstimatedNetworkBytes(long, long)} for + * details about how to estimate network traffic. * * @param intent The general Intent describing this work. - * @param networkBytes The estimated size of network traffic that will be - * performed by this job work item, in bytes. See - * {@link JobInfo.Builder#setEstimatedNetworkBytes(long)} for - * details about how to estimate. + * @param downloadBytes The estimated size of network traffic that will be + * downloaded by this job work item, in bytes. + * @param uploadBytes The estimated size of network traffic that will be + * uploaded by this job work item, in bytes. */ - public JobWorkItem(Intent intent, @BytesLong long networkBytes) { + public JobWorkItem(Intent intent, @BytesLong long downloadBytes, @BytesLong long uploadBytes) { mIntent = intent; - mNetworkBytes = networkBytes; + mNetworkDownloadBytes = downloadBytes; + mNetworkUploadBytes = uploadBytes; } /** @@ -67,14 +84,44 @@ final public class JobWorkItem implements Parcelable { } /** - * Return the estimated size of network traffic that will be performed by + * @deprecated replaced by {@link #getEstimatedNetworkDownloadBytes()} and + * {@link #getEstimatedNetworkUploadBytes()}. + * @removed + */ + @Deprecated + public @BytesLong long getEstimatedNetworkBytes() { + if (mNetworkDownloadBytes == NETWORK_BYTES_UNKNOWN + && mNetworkUploadBytes == NETWORK_BYTES_UNKNOWN) { + return NETWORK_BYTES_UNKNOWN; + } else if (mNetworkDownloadBytes == NETWORK_BYTES_UNKNOWN) { + return mNetworkUploadBytes; + } else if (mNetworkUploadBytes == NETWORK_BYTES_UNKNOWN) { + return mNetworkDownloadBytes; + } else { + return mNetworkDownloadBytes + mNetworkUploadBytes; + } + } + + /** + * Return the estimated size of download traffic that will be performed by + * this job, in bytes. + * + * @return Estimated size of download traffic, or + * {@link JobInfo#NETWORK_BYTES_UNKNOWN} when unknown. + */ + public @BytesLong long getEstimatedNetworkDownloadBytes() { + return mNetworkDownloadBytes; + } + + /** + * Return the estimated size of upload traffic that will be performed by * this job work item, in bytes. * - * @return estimated size, or {@link JobInfo#NETWORK_BYTES_UNKNOWN} when - * unknown. + * @return Estimated size of upload traffic, or + * {@link JobInfo#NETWORK_BYTES_UNKNOWN} when unknown. */ - public @BytesLong long getEstimatedNetworkBytes() { - return mNetworkBytes; + public @BytesLong long getEstimatedNetworkUploadBytes() { + return mNetworkUploadBytes; } /** @@ -128,9 +175,13 @@ final public class JobWorkItem implements Parcelable { sb.append(mWorkId); sb.append(" intent="); sb.append(mIntent); - if (mNetworkBytes != JobInfo.NETWORK_BYTES_UNKNOWN) { - sb.append(" networkBytes="); - sb.append(mNetworkBytes); + if (mNetworkDownloadBytes != NETWORK_BYTES_UNKNOWN) { + sb.append(" downloadBytes="); + sb.append(mNetworkDownloadBytes); + } + if (mNetworkUploadBytes != NETWORK_BYTES_UNKNOWN) { + sb.append(" uploadBytes="); + sb.append(mNetworkUploadBytes); } if (mDeliveryCount != 0) { sb.append(" dcount="); @@ -151,7 +202,8 @@ final public class JobWorkItem implements Parcelable { } else { out.writeInt(0); } - out.writeLong(mNetworkBytes); + out.writeLong(mNetworkDownloadBytes); + out.writeLong(mNetworkUploadBytes); out.writeInt(mDeliveryCount); out.writeInt(mWorkId); } @@ -173,7 +225,8 @@ final public class JobWorkItem implements Parcelable { } else { mIntent = null; } - mNetworkBytes = in.readLong(); + mNetworkDownloadBytes = in.readLong(); + mNetworkUploadBytes = in.readLong(); mDeliveryCount = in.readInt(); mWorkId = in.readInt(); } diff --git a/android/app/servertransaction/ActivityLifecycleItem.java b/android/app/servertransaction/ActivityLifecycleItem.java index 9a50a009..7f8c50cd 100644 --- a/android/app/servertransaction/ActivityLifecycleItem.java +++ b/android/app/servertransaction/ActivityLifecycleItem.java @@ -91,4 +91,9 @@ public abstract class ActivityLifecycleItem extends ClientTransactionItem { pw.println(prefix + "target state:" + getTargetState()); pw.println(prefix + "description: " + mDescription); } + + @Override + public void recycle() { + setDescription(null); + } } diff --git a/android/app/servertransaction/ActivityRelaunchItem.java b/android/app/servertransaction/ActivityRelaunchItem.java new file mode 100644 index 00000000..d8a7463c --- /dev/null +++ b/android/app/servertransaction/ActivityRelaunchItem.java @@ -0,0 +1,176 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.servertransaction; + +import static android.app.ActivityThread.DEBUG_ORDER; + +import android.app.ActivityThread; +import android.app.ClientTransactionHandler; +import android.app.ResultInfo; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Trace; +import android.util.MergedConfiguration; +import android.util.Slog; + +import com.android.internal.content.ReferrerIntent; + +import java.util.List; +import java.util.Objects; + +/** + * Activity relaunch callback. + * @hide + */ +public class ActivityRelaunchItem extends ClientTransactionItem { + + private static final String TAG = "ActivityRelaunchItem"; + + private List<ResultInfo> mPendingResults; + private List<ReferrerIntent> mPendingNewIntents; + private int mConfigChanges; + private MergedConfiguration mConfig; + private boolean mPreserveWindow; + + /** + * A record that was properly configured for relaunch. Execution will be cancelled if not + * initialized after {@link #preExecute(ClientTransactionHandler, IBinder)}. + */ + private ActivityThread.ActivityClientRecord mActivityClientRecord; + + @Override + public void preExecute(ClientTransactionHandler client, IBinder token) { + mActivityClientRecord = client.prepareRelaunchActivity(token, mPendingResults, + mPendingNewIntents, mConfigChanges, mConfig, mPreserveWindow); + } + + @Override + public void execute(ClientTransactionHandler client, IBinder token, + PendingTransactionActions pendingActions) { + if (mActivityClientRecord == null) { + if (DEBUG_ORDER) Slog.d(TAG, "Activity relaunch cancelled"); + return; + } + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart"); + client.handleRelaunchActivity(mActivityClientRecord, pendingActions); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } + + @Override + public void postExecute(ClientTransactionHandler client, IBinder token, + PendingTransactionActions pendingActions) { + client.reportRelaunch(token, pendingActions); + } + + // ObjectPoolItem implementation + + private ActivityRelaunchItem() {} + + /** Obtain an instance initialized with provided params. */ + public static ActivityRelaunchItem obtain(List<ResultInfo> pendingResults, + List<ReferrerIntent> pendingNewIntents, int configChanges, MergedConfiguration config, + boolean preserveWindow) { + ActivityRelaunchItem instance = ObjectPool.obtain(ActivityRelaunchItem.class); + if (instance == null) { + instance = new ActivityRelaunchItem(); + } + instance.mPendingResults = pendingResults; + instance.mPendingNewIntents = pendingNewIntents; + instance.mConfigChanges = configChanges; + instance.mConfig = config; + instance.mPreserveWindow = preserveWindow; + + return instance; + } + + @Override + public void recycle() { + mPendingResults = null; + mPendingNewIntents = null; + mConfigChanges = 0; + mConfig = null; + mPreserveWindow = false; + mActivityClientRecord = null; + ObjectPool.recycle(this); + } + + + // Parcelable implementation + + /** Write to Parcel. */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeTypedList(mPendingResults, flags); + dest.writeTypedList(mPendingNewIntents, flags); + dest.writeInt(mConfigChanges); + dest.writeTypedObject(mConfig, flags); + dest.writeBoolean(mPreserveWindow); + } + + /** Read from Parcel. */ + private ActivityRelaunchItem(Parcel in) { + mPendingResults = in.createTypedArrayList(ResultInfo.CREATOR); + mPendingNewIntents = in.createTypedArrayList(ReferrerIntent.CREATOR); + mConfigChanges = in.readInt(); + mConfig = in.readTypedObject(MergedConfiguration.CREATOR); + mPreserveWindow = in.readBoolean(); + } + + public static final Creator<ActivityRelaunchItem> CREATOR = + new Creator<ActivityRelaunchItem>() { + public ActivityRelaunchItem createFromParcel(Parcel in) { + return new ActivityRelaunchItem(in); + } + + public ActivityRelaunchItem[] newArray(int size) { + return new ActivityRelaunchItem[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final ActivityRelaunchItem other = (ActivityRelaunchItem) o; + return Objects.equals(mPendingResults, other.mPendingResults) + && Objects.equals(mPendingNewIntents, other.mPendingNewIntents) + && mConfigChanges == other.mConfigChanges && Objects.equals(mConfig, other.mConfig) + && mPreserveWindow == other.mPreserveWindow; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + Objects.hashCode(mPendingResults); + result = 31 * result + Objects.hashCode(mPendingNewIntents); + result = 31 * result + mConfigChanges; + result = 31 * result + Objects.hashCode(mConfig); + result = 31 * result + (mPreserveWindow ? 1 : 0); + return result; + } + + @Override + public String toString() { + return "ActivityRelaunchItem{pendingResults=" + mPendingResults + + ",pendingNewIntents=" + mPendingNewIntents + ",configChanges=" + mConfigChanges + + ",config=" + mConfig + ",preserveWindow" + mPreserveWindow + "}"; + } +} diff --git a/android/app/servertransaction/ActivityResultItem.java b/android/app/servertransaction/ActivityResultItem.java index 73b5ec44..545463c1 100644 --- a/android/app/servertransaction/ActivityResultItem.java +++ b/android/app/servertransaction/ActivityResultItem.java @@ -16,7 +16,7 @@ package android.app.servertransaction; -import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE; +import static android.app.servertransaction.ActivityLifecycleItem.ON_RESUME; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import android.app.ClientTransactionHandler; @@ -38,8 +38,8 @@ public class ActivityResultItem extends ClientTransactionItem { private List<ResultInfo> mResultInfoList; @Override - public int getPreExecutionState() { - return ON_PAUSE; + public int getPostExecutionState() { + return ON_RESUME; } @Override diff --git a/android/app/servertransaction/ClientTransactionItem.java b/android/app/servertransaction/ClientTransactionItem.java index 6f2cc007..d94f08b6 100644 --- a/android/app/servertransaction/ClientTransactionItem.java +++ b/android/app/servertransaction/ClientTransactionItem.java @@ -32,12 +32,6 @@ import android.os.Parcelable; */ public abstract class ClientTransactionItem implements BaseClientRequest, Parcelable { - /** Get the state in which this callback can be executed. */ - @LifecycleState - public int getPreExecutionState() { - return UNDEFINED; - } - /** Get the state that must follow this callback. */ @LifecycleState public int getPostExecutionState() { diff --git a/android/app/servertransaction/DestroyActivityItem.java b/android/app/servertransaction/DestroyActivityItem.java index cbcf6c75..0edcf188 100644 --- a/android/app/servertransaction/DestroyActivityItem.java +++ b/android/app/servertransaction/DestroyActivityItem.java @@ -37,7 +37,7 @@ public class DestroyActivityItem extends ActivityLifecycleItem { PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy"); client.handleDestroyActivity(token, mFinished, mConfigChanges, - false /* getNonConfigInstance */); + false /* getNonConfigInstance */, getDescription()); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } @@ -65,6 +65,7 @@ public class DestroyActivityItem extends ActivityLifecycleItem { @Override public void recycle() { + super.recycle(); mFinished = false; mConfigChanges = 0; ObjectPool.recycle(this); diff --git a/android/app/servertransaction/NewIntentItem.java b/android/app/servertransaction/NewIntentItem.java index 7dfde73c..e5ce3b00 100644 --- a/android/app/servertransaction/NewIntentItem.java +++ b/android/app/servertransaction/NewIntentItem.java @@ -38,11 +38,6 @@ public class NewIntentItem extends ClientTransactionItem { // TODO(lifecycler): Switch new intent handling to this scheme. /*@Override - public int getPreExecutionState() { - return ON_PAUSE; - } - - @Override public int getPostExecutionState() { return ON_RESUME; }*/ diff --git a/android/app/servertransaction/PauseActivityItem.java b/android/app/servertransaction/PauseActivityItem.java index 70a4755f..65e42912 100644 --- a/android/app/servertransaction/PauseActivityItem.java +++ b/android/app/servertransaction/PauseActivityItem.java @@ -42,8 +42,8 @@ public class PauseActivityItem extends ActivityLifecycleItem { public void execute(ClientTransactionHandler client, IBinder token, PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityPause"); - client.handlePauseActivity(token, mFinished, mUserLeaving, mConfigChanges, mDontReport, - pendingActions); + client.handlePauseActivity(token, mFinished, mUserLeaving, mConfigChanges, pendingActions, + "PAUSE_ACTIVITY_ITEM"); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } @@ -102,6 +102,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { @Override public void recycle() { + super.recycle(); mFinished = false; mUserLeaving = false; mConfigChanges = 0; diff --git a/android/app/servertransaction/PendingTransactionActions.java b/android/app/servertransaction/PendingTransactionActions.java index 8304c1c5..af7b7a21 100644 --- a/android/app/servertransaction/PendingTransactionActions.java +++ b/android/app/servertransaction/PendingTransactionActions.java @@ -44,6 +44,7 @@ public class PendingTransactionActions { private boolean mCallOnPostCreate; private Bundle mOldState; private StopInfo mStopInfo; + private boolean mReportRelaunchToWM; public PendingTransactionActions() { clear(); @@ -91,6 +92,24 @@ public class PendingTransactionActions { mStopInfo = stopInfo; } + /** + * Check if we should report an activity relaunch to WindowManager. We report back for every + * relaunch request to ActivityManager, but only for those that were actually finished to we + * report to WindowManager. + */ + public boolean shouldReportRelaunchToWindowManager() { + return mReportRelaunchToWM; + } + + /** + * Set if we should report an activity relaunch to WindowManager. We report back for every + * relaunch request to ActivityManager, but only for those that were actually finished we report + * to WindowManager. + */ + public void setReportRelaunchToWindowManager(boolean reportToWm) { + mReportRelaunchToWM = reportToWm; + } + /** Reports to server about activity stop. */ public static class StopInfo implements Runnable { private static final String TAG = "ActivityStopInfo"; @@ -134,7 +153,7 @@ public class PendingTransactionActions { Bundle.dumpStats(pw, mPersistentState); if (ex instanceof TransactionTooLargeException - && mActivity.loadedApk.getTargetSdkVersion() < Build.VERSION_CODES.N) { + && mActivity.packageInfo.getTargetSdkVersion() < Build.VERSION_CODES.N) { Log.e(TAG, "App sent too much data in instance state, so it was ignored", ex); return; } diff --git a/android/app/servertransaction/ResumeActivityItem.java b/android/app/servertransaction/ResumeActivityItem.java index ed90f2cb..d16bc97c 100644 --- a/android/app/servertransaction/ResumeActivityItem.java +++ b/android/app/servertransaction/ResumeActivityItem.java @@ -48,7 +48,8 @@ public class ResumeActivityItem extends ActivityLifecycleItem { public void execute(ClientTransactionHandler client, IBinder token, PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityResume"); - client.handleResumeActivity(token, true /* clearHide */, mIsForward, "RESUME_ACTIVITY"); + client.handleResumeActivity(token, true /* finalStateRequest */, mIsForward, + "RESUME_ACTIVITY"); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } @@ -101,6 +102,7 @@ public class ResumeActivityItem extends ActivityLifecycleItem { @Override public void recycle() { + super.recycle(); mProcState = ActivityManager.PROCESS_STATE_UNKNOWN; mUpdateProcState = false; mIsForward = false; diff --git a/android/app/servertransaction/StopActivityItem.java b/android/app/servertransaction/StopActivityItem.java index b814d1ae..8db38d36 100644 --- a/android/app/servertransaction/StopActivityItem.java +++ b/android/app/servertransaction/StopActivityItem.java @@ -38,7 +38,8 @@ public class StopActivityItem extends ActivityLifecycleItem { public void execute(ClientTransactionHandler client, IBinder token, PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStop"); - client.handleStopActivity(token, mShowWindow, mConfigChanges, pendingActions); + client.handleStopActivity(token, mShowWindow, mConfigChanges, pendingActions, + true /* finalStateRequest */, "STOP_ACTIVITY_ITEM"); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } @@ -72,6 +73,7 @@ public class StopActivityItem extends ActivityLifecycleItem { @Override public void recycle() { + super.recycle(); mShowWindow = false; mConfigChanges = 0; ObjectPool.recycle(this); diff --git a/android/app/servertransaction/TransactionExecutor.java b/android/app/servertransaction/TransactionExecutor.java index 78b393a8..553c3ae1 100644 --- a/android/app/servertransaction/TransactionExecutor.java +++ b/android/app/servertransaction/TransactionExecutor.java @@ -24,6 +24,7 @@ import static android.app.servertransaction.ActivityLifecycleItem.ON_RESUME; import static android.app.servertransaction.ActivityLifecycleItem.ON_START; import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP; import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED; +import static android.app.servertransaction.TransactionExecutorHelper.lastCallbackRequestingState; import android.app.ActivityThread.ActivityClientRecord; import android.app.ClientTransactionHandler; @@ -48,11 +49,7 @@ public class TransactionExecutor { private ClientTransactionHandler mTransactionHandler; private PendingTransactionActions mPendingActions = new PendingTransactionActions(); - - // Temp holder for lifecycle path. - // No direct transition between two states should take more than one complete cycle of 6 states. - @ActivityLifecycleItem.LifecycleState - private IntArray mLifecycleSequence = new IntArray(6); + private TransactionExecutorHelper mHelper = new TransactionExecutorHelper(); /** Initialize an instance with transaction handler, that will execute all requested actions. */ public TransactionExecutor(ClientTransactionHandler clientTransactionHandler) { @@ -89,13 +86,25 @@ public class TransactionExecutor { final IBinder token = transaction.getActivityToken(); ActivityClientRecord r = mTransactionHandler.getActivityClient(token); + + // In case when post-execution state of the last callback matches the final state requested + // for the activity in this transaction, we won't do the last transition here and do it when + // moving to final state instead (because it may contain additional parameters from server). + final ActivityLifecycleItem finalStateRequest = transaction.getLifecycleStateRequest(); + final int finalState = finalStateRequest != null ? finalStateRequest.getTargetState() + : UNDEFINED; + // Index of the last callback that requests some post-execution state. + final int lastCallbackRequestingState = lastCallbackRequestingState(transaction); + final int size = callbacks.size(); for (int i = 0; i < size; ++i) { final ClientTransactionItem item = callbacks.get(i); log("Resolving callback: " + item); - final int preExecutionState = item.getPreExecutionState(); - if (preExecutionState != UNDEFINED) { - cycleToPath(r, preExecutionState); + final int postExecutionState = item.getPostExecutionState(); + final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r, + item.getPostExecutionState()); + if (closestPreExecutionState != UNDEFINED) { + cycleToPath(r, closestPreExecutionState); } item.execute(mTransactionHandler, token, mPendingActions); @@ -105,9 +114,11 @@ public class TransactionExecutor { r = mTransactionHandler.getActivityClient(token); } - final int postExecutionState = item.getPostExecutionState(); - if (postExecutionState != UNDEFINED) { - cycleToPath(r, postExecutionState); + if (postExecutionState != UNDEFINED && r != null) { + // Skip the very last transition and perform it by explicit state request instead. + final boolean shouldExcludeLastTransition = + i == lastCallbackRequestingState && finalState == postExecutionState; + cycleToPath(r, postExecutionState, shouldExcludeLastTransition); } } } @@ -136,7 +147,10 @@ public class TransactionExecutor { pw.println("Executor:"); dump(pw, prefix); - Slog.wtf(TAG, stringWriter.toString()); + Slog.w(TAG, stringWriter.toString()); + + // Ignore requests for non-existent client records for now. + return; } // Cycle to the state right before the final requested state. @@ -162,15 +176,15 @@ public class TransactionExecutor { boolean excludeLastState) { final int start = r.getLifecycleState(); log("Cycle from: " + start + " to: " + finish + " excludeLastState:" + excludeLastState); - initLifecyclePath(start, finish, excludeLastState); - performLifecycleSequence(r); + final IntArray path = mHelper.getLifecyclePath(start, finish, excludeLastState); + performLifecycleSequence(r, path); } /** Transition the client through previously initialized state sequence. */ - private void performLifecycleSequence(ActivityClientRecord r) { - final int size = mLifecycleSequence.size(); + private void performLifecycleSequence(ActivityClientRecord r, IntArray path) { + final int size = path.size(); for (int i = 0, state; i < size; i++) { - state = mLifecycleSequence.get(i); + state = path.get(i); log("Transitioning to state: " + state); switch (state) { case ON_CREATE: @@ -180,21 +194,23 @@ public class TransactionExecutor { mTransactionHandler.handleStartActivity(r, mPendingActions); break; case ON_RESUME: - mTransactionHandler.handleResumeActivity(r.token, false /* clearHide */, + mTransactionHandler.handleResumeActivity(r.token, false /* finalStateRequest */, r.isForward, "LIFECYCLER_RESUME_ACTIVITY"); break; case ON_PAUSE: mTransactionHandler.handlePauseActivity(r.token, false /* finished */, - false /* userLeaving */, 0 /* configChanges */, - true /* dontReport */, mPendingActions); + false /* userLeaving */, 0 /* configChanges */, mPendingActions, + "LIFECYCLER_PAUSE_ACTIVITY"); break; case ON_STOP: mTransactionHandler.handleStopActivity(r.token, false /* show */, - 0 /* configChanges */, mPendingActions); + 0 /* configChanges */, mPendingActions, false /* finalStateRequest */, + "LIFECYCLER_STOP_ACTIVITY"); break; case ON_DESTROY: mTransactionHandler.handleDestroyActivity(r.token, false /* finishing */, - 0 /* configChanges */, false /* getNonConfigInstance */); + 0 /* configChanges */, false /* getNonConfigInstance */, + "performLifecycleSequence. cycling to:" + path.get(size - 1)); break; case ON_RESTART: mTransactionHandler.performRestartActivity(r.token, false /* start */); @@ -205,60 +221,6 @@ public class TransactionExecutor { } } - /** - * Calculate the path through main lifecycle states for an activity and fill - * @link #mLifecycleSequence} with values starting with the state that follows the initial - * state. - */ - public void initLifecyclePath(int start, int finish, boolean excludeLastState) { - mLifecycleSequence.clear(); - if (finish >= start) { - // just go there - for (int i = start + 1; i <= finish; i++) { - mLifecycleSequence.add(i); - } - } else { // finish < start, can't just cycle down - if (start == ON_PAUSE && finish == ON_RESUME) { - // Special case when we can just directly go to resumed state. - mLifecycleSequence.add(ON_RESUME); - } else if (start <= ON_STOP && finish >= ON_START) { - // Restart and go to required state. - - // Go to stopped state first. - for (int i = start + 1; i <= ON_STOP; i++) { - mLifecycleSequence.add(i); - } - // Restart - mLifecycleSequence.add(ON_RESTART); - // Go to required state - for (int i = ON_START; i <= finish; i++) { - mLifecycleSequence.add(i); - } - } else { - // Relaunch and go to required state - - // Go to destroyed state first. - for (int i = start + 1; i <= ON_DESTROY; i++) { - mLifecycleSequence.add(i); - } - // Go to required state - for (int i = ON_CREATE; i <= finish; i++) { - mLifecycleSequence.add(i); - } - } - } - - // Remove last transition in case we want to perform it with some specific params. - if (excludeLastState && mLifecycleSequence.size() != 0) { - mLifecycleSequence.remove(mLifecycleSequence.size() - 1); - } - } - - @VisibleForTesting - public int[] getLifecycleSequence() { - return mLifecycleSequence.toArray(); - } - private static void log(String message) { if (DEBUG_RESOLVER) Slog.d(TAG, message); } diff --git a/android/app/servertransaction/TransactionExecutorHelper.java b/android/app/servertransaction/TransactionExecutorHelper.java new file mode 100644 index 00000000..01b13a28 --- /dev/null +++ b/android/app/servertransaction/TransactionExecutorHelper.java @@ -0,0 +1,246 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.servertransaction; + +import static android.app.servertransaction.ActivityLifecycleItem.ON_CREATE; +import static android.app.servertransaction.ActivityLifecycleItem.ON_DESTROY; +import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE; +import static android.app.servertransaction.ActivityLifecycleItem.ON_RESTART; +import static android.app.servertransaction.ActivityLifecycleItem.ON_RESUME; +import static android.app.servertransaction.ActivityLifecycleItem.ON_START; +import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP; +import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE; +import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED; + +import android.app.ActivityThread.ActivityClientRecord; +import android.util.IntArray; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.List; + +/** + * Helper class for {@link TransactionExecutor} that contains utils for lifecycle path resolution. + * @hide + */ +public class TransactionExecutorHelper { + // A penalty applied to path with destruction when looking for the shortest one. + private static final int DESTRUCTION_PENALTY = 10; + + private static final int[] ON_RESUME_PRE_EXCUTION_STATES = new int[] { ON_START, ON_PAUSE }; + + // Temp holder for lifecycle path. + // No direct transition between two states should take more than one complete cycle of 6 states. + @ActivityLifecycleItem.LifecycleState + private IntArray mLifecycleSequence = new IntArray(6); + + /** + * Calculate the path through main lifecycle states for an activity and fill + * @link #mLifecycleSequence} with values starting with the state that follows the initial + * state. + * <p>NOTE: The returned value is used internally in this class and is not a copy. It's contents + * may change after calling other methods of this class.</p> + */ + @VisibleForTesting + public IntArray getLifecyclePath(int start, int finish, boolean excludeLastState) { + if (start == UNDEFINED || finish == UNDEFINED) { + throw new IllegalArgumentException("Can't resolve lifecycle path for undefined state"); + } + if (start == ON_RESTART || finish == ON_RESTART) { + throw new IllegalArgumentException( + "Can't start or finish in intermittent RESTART state"); + } + if (finish == PRE_ON_CREATE && start != finish) { + throw new IllegalArgumentException("Can only start in pre-onCreate state"); + } + + mLifecycleSequence.clear(); + if (finish >= start) { + // just go there + for (int i = start + 1; i <= finish; i++) { + mLifecycleSequence.add(i); + } + } else { // finish < start, can't just cycle down + if (start == ON_PAUSE && finish == ON_RESUME) { + // Special case when we can just directly go to resumed state. + mLifecycleSequence.add(ON_RESUME); + } else if (start <= ON_STOP && finish >= ON_START) { + // Restart and go to required state. + + // Go to stopped state first. + for (int i = start + 1; i <= ON_STOP; i++) { + mLifecycleSequence.add(i); + } + // Restart + mLifecycleSequence.add(ON_RESTART); + // Go to required state + for (int i = ON_START; i <= finish; i++) { + mLifecycleSequence.add(i); + } + } else { + // Relaunch and go to required state + + // Go to destroyed state first. + for (int i = start + 1; i <= ON_DESTROY; i++) { + mLifecycleSequence.add(i); + } + // Go to required state + for (int i = ON_CREATE; i <= finish; i++) { + mLifecycleSequence.add(i); + } + } + } + + // Remove last transition in case we want to perform it with some specific params. + if (excludeLastState && mLifecycleSequence.size() != 0) { + mLifecycleSequence.remove(mLifecycleSequence.size() - 1); + } + + return mLifecycleSequence; + } + + /** + * Pick a state that goes before provided post-execution state and would require the least + * lifecycle transitions to get to. + * It will also make sure to try avoiding a path with activity destruction and relaunch if + * possible. + * @param r An activity that we're trying to resolve the transition for. + * @param postExecutionState Post execution state to compute for. + * @return One of states that precede the provided post-execution state, or + * {@link ActivityLifecycleItem#UNDEFINED} if there is not path. + */ + @VisibleForTesting + public int getClosestPreExecutionState(ActivityClientRecord r, + int postExecutionState) { + switch (postExecutionState) { + case UNDEFINED: + return UNDEFINED; + case ON_RESUME: + return getClosestOfStates(r, ON_RESUME_PRE_EXCUTION_STATES); + default: + throw new UnsupportedOperationException("Pre-execution states for state: " + + postExecutionState + " is not supported."); + } + } + + /** + * Pick a state that would require the least lifecycle transitions to get to. + * It will also make sure to try avoiding a path with activity destruction and relaunch if + * possible. + * @param r An activity that we're trying to resolve the transition for. + * @param finalStates An array of valid final states. + * @return One of the provided final states, or {@link ActivityLifecycleItem#UNDEFINED} if none + * were provided or there is not path. + */ + @VisibleForTesting + public int getClosestOfStates(ActivityClientRecord r, int[] finalStates) { + if (finalStates == null || finalStates.length == 0) { + return UNDEFINED; + } + + final int currentState = r.getLifecycleState(); + int closestState = UNDEFINED; + for (int i = 0, shortestPath = Integer.MAX_VALUE, pathLength; i < finalStates.length; i++) { + getLifecyclePath(currentState, finalStates[i], false /* excludeLastState */); + pathLength = mLifecycleSequence.size(); + if (pathInvolvesDestruction(mLifecycleSequence)) { + pathLength += DESTRUCTION_PENALTY; + } + if (shortestPath > pathLength) { + shortestPath = pathLength; + closestState = finalStates[i]; + } + } + return closestState; + } + + /** Get the lifecycle state request to match the current state in the end of a transaction. */ + public static ActivityLifecycleItem getLifecycleRequestForCurrentState(ActivityClientRecord r) { + final int prevState = r.getLifecycleState(); + final ActivityLifecycleItem lifecycleItem; + switch (prevState) { + // TODO(lifecycler): Extend to support all possible states. + case ON_PAUSE: + lifecycleItem = PauseActivityItem.obtain(); + break; + case ON_STOP: + lifecycleItem = StopActivityItem.obtain(r.isVisibleFromServer(), + 0 /* configChanges */); + break; + default: + lifecycleItem = ResumeActivityItem.obtain(false /* isForward */); + break; + } + + return lifecycleItem; + } + + /** + * Check if there is a destruction involved in the path. We want to avoid a lifecycle sequence + * that involves destruction and recreation if there is another path. + */ + private static boolean pathInvolvesDestruction(IntArray lifecycleSequence) { + final int size = lifecycleSequence.size(); + for (int i = 0; i < size; i++) { + if (lifecycleSequence.get(i) == ON_DESTROY) { + return true; + } + } + return false; + } + + /** + * Return the index of the last callback that requests the state in which activity will be after + * execution. If there is a group of callbacks in the end that requests the same specific state + * or doesn't request any - we will find the first one from such group. + * + * E.g. ActivityResult requests RESUMED post-execution state, Configuration does not request any + * specific state. If there is a sequence + * Configuration - ActivityResult - Configuration - ActivityResult + * index 1 will be returned, because ActivityResult request on position 1 will be the last + * request that moves activity to the RESUMED state where it will eventually end. + */ + static int lastCallbackRequestingState(ClientTransaction transaction) { + final List<ClientTransactionItem> callbacks = transaction.getCallbacks(); + if (callbacks == null || callbacks.size() == 0) { + return -1; + } + + // Go from the back of the list to front, look for the request closes to the beginning that + // requests the state in which activity will end after all callbacks are executed. + int lastRequestedState = UNDEFINED; + int lastRequestingCallback = -1; + for (int i = callbacks.size() - 1; i >= 0; i--) { + final ClientTransactionItem callback = callbacks.get(i); + final int postExecutionState = callback.getPostExecutionState(); + if (postExecutionState != UNDEFINED) { + // Found a callback that requests some post-execution state. + if (lastRequestedState == UNDEFINED || lastRequestedState == postExecutionState) { + // It's either a first-from-end callback that requests state or it requests + // the same state as the last one. In both cases, we will use it as the new + // candidate. + lastRequestedState = postExecutionState; + lastRequestingCallback = i; + } else { + break; + } + } + } + + return lastRequestingCallback; + } +} diff --git a/android/app/slice/Slice.java b/android/app/slice/Slice.java index 5808f8b5..bf3398ad 100644 --- a/android/app/slice/Slice.java +++ b/android/app/slice/Slice.java @@ -65,20 +65,32 @@ public final class Slice implements Parcelable { HINT_TOGGLE, HINT_HORIZONTAL, HINT_PARTIAL, - HINT_SEE_MORE + HINT_SEE_MORE, + HINT_KEYWORDS, + HINT_ERROR, + HINT_TTL, + HINT_LAST_UPDATED, + HINT_PERMISSION_REQUEST, }) @Retention(RetentionPolicy.SOURCE) public @interface SliceHint {} - /** - * The meta-data key that allows an activity to easily be linked directly to a slice. - * <p> - * An activity can be statically linked to a slice uri by including a meta-data item - * for this key that contains a valid slice uri for the same application declaring - * the activity. * @hide */ - public static final String SLICE_METADATA_KEY = "android.metadata.SLICE_URI"; + @StringDef(prefix = { "SUBTYPE_" }, value = { + SUBTYPE_COLOR, + SUBTYPE_CONTENT_DESCRIPTION, + SUBTYPE_MAX, + SUBTYPE_MESSAGE, + SUBTYPE_PRIORITY, + SUBTYPE_RANGE, + SUBTYPE_SOURCE, + SUBTYPE_TOGGLE, + SUBTYPE_VALUE, + SUBTYPE_LAYOUT_DIRECTION, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SliceSubtype {} /** * Hint that this content is a title of other content in the slice. This can also indicate that @@ -146,45 +158,98 @@ public final class Slice implements Parcelable { */ public static final String HINT_PARTIAL = "partial"; /** - * A hint representing that this item is the max value possible for the slice containing this. - * Used to indicate the maximum integer value for a {@link #SUBTYPE_SLIDER}. - */ - public static final String HINT_MAX = "max"; - /** * A hint representing that this item should be used to indicate that there's more * content associated with this slice. */ public static final String HINT_SEE_MORE = "see_more"; /** - * A hint used when implementing app-specific slice permissions. - * Tells the system that for this slice the return value of - * {@link SliceProvider#onBindSlice(Uri, List)} may be different depending on - * {@link SliceProvider#getBindingPackage} and should not be cached for multiple - * apps. + * @see Builder#setCallerNeeded + * @hide */ public static final String HINT_CALLER_NEEDED = "caller_needed"; /** + * A hint to indicate that the contents of this subslice represent a list of keywords + * related to the parent slice. + * Expected to be on an item of format {@link SliceItem#FORMAT_SLICE}. + */ + public static final String HINT_KEYWORDS = "keywords"; + /** + * A hint to indicate that this slice represents an error. + */ + public static final String HINT_ERROR = "error"; + /** + * Hint indicating an item representing a time-to-live for the content. + */ + public static final String HINT_TTL = "ttl"; + /** + * Hint indicating an item representing when the content was created or last updated. + */ + public static final String HINT_LAST_UPDATED = "last_updated"; + /** + * A hint to indicate that this slice represents a permission request for showing + * slices. + */ + public static final String HINT_PERMISSION_REQUEST = "permission_request"; + /** + * Subtype to indicate that this item indicates the layout direction for content + * in the slice. + * Expected to be an item of format {@link SliceItem#FORMAT_INT}. + */ + public static final String SUBTYPE_LAYOUT_DIRECTION = "layout_direction"; + /** * Key to retrieve an extra added to an intent when a control is changed. */ public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE"; /** + * Key to retrieve an extra added to an intent when the value of a slider is changed. + * @deprecated remove once support lib is update to use EXTRA_RANGE_VALUE instead + */ + @Deprecated + public static final String EXTRA_SLIDER_VALUE = "android.app.slice.extra.SLIDER_VALUE"; + /** + * Key to retrieve an extra added to an intent when the value of an input range is changed. + */ + public static final String EXTRA_RANGE_VALUE = "android.app.slice.extra.RANGE_VALUE"; + /** * Subtype to indicate that this is a message as part of a communication * sequence in this slice. + * Expected to be on an item of format {@link SliceItem#FORMAT_SLICE}. */ public static final String SUBTYPE_MESSAGE = "message"; /** * Subtype to tag the source (i.e. sender) of a {@link #SUBTYPE_MESSAGE}. + * Expected to be on an item of format {@link SliceItem#FORMAT_TEXT}, + * {@link SliceItem#FORMAT_IMAGE} or an {@link SliceItem#FORMAT_SLICE} containing them. */ public static final String SUBTYPE_SOURCE = "source"; /** * Subtype to tag an item as representing a color. + * Expected to be on an item of format {@link SliceItem#FORMAT_INT}. */ public static final String SUBTYPE_COLOR = "color"; /** - * Subtype to tag an item represents a slider. + * Subtype to tag an item as representing a slider. + * @deprecated remove once support lib is update to use SUBTYPE_RANGE instead */ + @Deprecated public static final String SUBTYPE_SLIDER = "slider"; /** + * Subtype to tag an item as representing a range. + * Expected to be on an item of format {@link SliceItem#FORMAT_SLICE} containing + * a {@link #SUBTYPE_VALUE} and possibly a {@link #SUBTYPE_MAX}. + */ + public static final String SUBTYPE_RANGE = "range"; + /** + * Subtype to tag an item as representing the max int value for a {@link #SUBTYPE_RANGE}. + * Expected to be on an item of format {@link SliceItem#FORMAT_INT}. + */ + public static final String SUBTYPE_MAX = "max"; + /** + * Subtype to tag an item as representing the current int value for a {@link #SUBTYPE_RANGE}. + * Expected to be on an item of format {@link SliceItem#FORMAT_INT}. + */ + public static final String SUBTYPE_VALUE = "value"; + /** * Subtype to indicate that this content has a toggle action associated with it. To indicate * that the toggle is on, use {@link #HINT_SELECTED}. When the toggle state changes, the * intent associated with it will be sent along with an extra {@link #EXTRA_TOGGLE_STATE} @@ -193,12 +258,19 @@ public final class Slice implements Parcelable { public static final String SUBTYPE_TOGGLE = "toggle"; /** * Subtype to tag an item representing priority. + * Expected to be on an item of format {@link SliceItem#FORMAT_INT}. */ public static final String SUBTYPE_PRIORITY = "priority"; /** * Subtype to tag an item to use as a content description. + * Expected to be on an item of format {@link SliceItem#FORMAT_TEXT}. */ public static final String SUBTYPE_CONTENT_DESCRIPTION = "content_description"; + /** + * Subtype to tag an item as representing a time in milliseconds since midnight, + * January 1, 1970 UTC. + */ + public static final String SUBTYPE_MILLIS = "millis"; private final SliceItem[] mItems; private final @SliceHint String[] mHints; @@ -275,6 +347,14 @@ public final class Slice implements Parcelable { } /** + * Returns whether the caller for this slice matters. + * @see Builder#setCallerNeeded + */ + public boolean isCallerNeeded() { + return hasHint(HINT_CALLER_NEEDED); + } + + /** * A Builder used to construct {@link Slice}s */ public static class Builder { @@ -285,14 +365,24 @@ public final class Slice implements Parcelable { private SliceSpec mSpec; /** - * Create a builder which will construct a {@link Slice} for the Given Uri. - * @param uri Uri to tag for this slice. + * @deprecated TO BE REMOVED */ + @Deprecated public Builder(@NonNull Uri uri) { mUri = uri; } /** + * Create a builder which will construct a {@link Slice} for the given Uri. + * @param uri Uri to tag for this slice. + * @param spec the spec for this slice. + */ + public Builder(@NonNull Uri uri, SliceSpec spec) { + mUri = uri; + mSpec = spec; + } + + /** * Create a builder for a {@link Slice} that is a sub-slice of the slice * being constructed by the provided builder. * @param parent The builder constructing the parent slice @@ -303,10 +393,17 @@ public final class Slice implements Parcelable { } /** - * Add hints to the Slice being constructed + * Tells the system whether for this slice the return value of + * {@link SliceProvider#onBindSlice(Uri, List)} may be different depending on + * {@link SliceProvider#getCallingPackage()} and should not be cached for multiple + * apps. */ - public Builder addHints(@SliceHint String... hints) { - mHints.addAll(Arrays.asList(hints)); + public Builder setCallerNeeded(boolean callerNeeded) { + if (callerNeeded) { + mHints.add(HINT_CALLER_NEEDED); + } else { + mHints.remove(HINT_CALLER_NEEDED); + } return this; } @@ -314,11 +411,12 @@ public final class Slice implements Parcelable { * Add hints to the Slice being constructed */ public Builder addHints(@SliceHint List<String> hints) { - return addHints(hints.toArray(new String[hints.size()])); + mHints.addAll(hints); + return this; } /** - * Add the spec for this slice. + * @deprecated TO BE REMOVED */ public Builder setSpec(SliceSpec spec) { mSpec = spec; @@ -327,17 +425,10 @@ public final class Slice implements Parcelable { /** * Add a sub-slice to the slice being constructed - */ - public Builder addSubSlice(@NonNull Slice slice) { - return addSubSlice(slice, null); - } - - /** - * Add a sub-slice to the slice being constructed * @param subType Optional template-specific type information * @see {@link SliceItem#getSubType()} */ - public Builder addSubSlice(@NonNull Slice slice, @Nullable String subType) { + public Builder addSubSlice(@NonNull Slice slice, @Nullable @SliceSubtype String subType) { mItems.add(new SliceItem(slice, SliceItem.FORMAT_SLICE, subType, slice.getHints().toArray(new String[slice.getHints().size()]))); return this; @@ -345,18 +436,11 @@ public final class Slice implements Parcelable { /** * Add an action to the slice being constructed - */ - public Slice.Builder addAction(@NonNull PendingIntent action, @NonNull Slice s) { - return addAction(action, s, null); - } - - /** - * Add an action to the slice being constructed * @param subType Optional template-specific type information * @see {@link SliceItem#getSubType()} */ public Slice.Builder addAction(@NonNull PendingIntent action, @NonNull Slice s, - @Nullable String subType) { + @Nullable @SliceSubtype String subType) { List<String> hints = s.getHints(); s.mSpec = null; mItems.add(new SliceItem(action, s, SliceItem.FORMAT_ACTION, subType, hints.toArray( @@ -369,103 +453,66 @@ public final class Slice implements Parcelable { * @param subType Optional template-specific type information * @see {@link SliceItem#getSubType()} */ - public Builder addText(CharSequence text, @Nullable String subType, - @SliceHint String... hints) { + public Builder addText(CharSequence text, @Nullable @SliceSubtype String subType, + @SliceHint List<String> hints) { mItems.add(new SliceItem(text, SliceItem.FORMAT_TEXT, subType, hints)); return this; } /** - * Add text to the slice being constructed - * @param subType Optional template-specific type information - * @see {@link SliceItem#getSubType()} - */ - public Builder addText(CharSequence text, @Nullable String subType, - @SliceHint List<String> hints) { - return addText(text, subType, hints.toArray(new String[hints.size()])); - } - - /** * Add an image to the slice being constructed * @param subType Optional template-specific type information * @see {@link SliceItem#getSubType()} */ - public Builder addIcon(Icon icon, @Nullable String subType, @SliceHint String... hints) { + public Builder addIcon(Icon icon, @Nullable @SliceSubtype String subType, + @SliceHint List<String> hints) { mItems.add(new SliceItem(icon, SliceItem.FORMAT_IMAGE, subType, hints)); return this; } /** - * Add an image to the slice being constructed - * @param subType Optional template-specific type information - * @see {@link SliceItem#getSubType()} - */ - public Builder addIcon(Icon icon, @Nullable String subType, @SliceHint List<String> hints) { - return addIcon(icon, subType, hints.toArray(new String[hints.size()])); - } - - /** * Add remote input to the slice being constructed * @param subType Optional template-specific type information * @see {@link SliceItem#getSubType()} */ - public Slice.Builder addRemoteInput(RemoteInput remoteInput, @Nullable String subType, + public Slice.Builder addRemoteInput(RemoteInput remoteInput, + @Nullable @SliceSubtype String subType, @SliceHint List<String> hints) { - return addRemoteInput(remoteInput, subType, hints.toArray(new String[hints.size()])); - } - - /** - * Add remote input to the slice being constructed - * @param subType Optional template-specific type information - * @see {@link SliceItem#getSubType()} - */ - public Slice.Builder addRemoteInput(RemoteInput remoteInput, @Nullable String subType, - @SliceHint String... hints) { mItems.add(new SliceItem(remoteInput, SliceItem.FORMAT_REMOTE_INPUT, subType, hints)); return this; } /** - * Add a color to the slice being constructed + * Add an integer to the slice being constructed * @param subType Optional template-specific type information * @see {@link SliceItem#getSubType()} */ - public Builder addInt(int value, @Nullable String subType, @SliceHint String... hints) { + public Builder addInt(int value, @Nullable @SliceSubtype String subType, + @SliceHint List<String> hints) { mItems.add(new SliceItem(value, SliceItem.FORMAT_INT, subType, hints)); return this; } /** - * Add a color to the slice being constructed - * @param subType Optional template-specific type information - * @see {@link SliceItem#getSubType()} + * @deprecated TO BE REMOVED. */ - public Builder addInt(int value, @Nullable String subType, + @Deprecated + public Slice.Builder addTimestamp(long time, @Nullable @SliceSubtype String subType, @SliceHint List<String> hints) { - return addInt(value, subType, hints.toArray(new String[hints.size()])); - } - - /** - * Add a timestamp to the slice being constructed - * @param subType Optional template-specific type information - * @see {@link SliceItem#getSubType()} - */ - public Slice.Builder addTimestamp(long time, @Nullable String subType, - @SliceHint String... hints) { - mItems.add(new SliceItem(time, SliceItem.FORMAT_TIMESTAMP, subType, - hints)); - return this; + return addLong(time, subType, hints); } /** - * Add a timestamp to the slice being constructed + * Add a long to the slice being constructed * @param subType Optional template-specific type information * @see {@link SliceItem#getSubType()} */ - public Slice.Builder addTimestamp(long time, @Nullable String subType, + public Slice.Builder addLong(long value, @Nullable @SliceSubtype String subType, @SliceHint List<String> hints) { - return addTimestamp(time, subType, hints.toArray(new String[hints.size()])); + mItems.add(new SliceItem(value, SliceItem.FORMAT_LONG, subType, + hints.toArray(new String[hints.size()]))); + return this; } /** @@ -475,26 +522,14 @@ public final class Slice implements Parcelable { * @param subType Optional template-specific type information * @see {@link SliceItem#getSubType()} */ - public Slice.Builder addBundle(Bundle bundle, @Nullable String subType, - @SliceHint String... hints) { + public Slice.Builder addBundle(Bundle bundle, @Nullable @SliceSubtype String subType, + @SliceHint List<String> hints) { mItems.add(new SliceItem(bundle, SliceItem.FORMAT_BUNDLE, subType, hints)); return this; } /** - * Add a bundle to the slice being constructed. - * <p>Expected to be used for support library extension, should not be used for general - * development - * @param subType Optional template-specific type information - * @see {@link SliceItem#getSubType()} - */ - public Slice.Builder addBundle(Bundle bundle, @Nullable String subType, - @SliceHint List<String> hints) { - return addBundle(bundle, subType, hints.toArray(new String[hints.size()])); - } - - /** * Construct the slice. */ public Slice build() { diff --git a/android/app/slice/SliceItem.java b/android/app/slice/SliceItem.java index 9eb2bb89..019ae492 100644 --- a/android/app/slice/SliceItem.java +++ b/android/app/slice/SliceItem.java @@ -67,7 +67,7 @@ public final class SliceItem implements Parcelable { FORMAT_IMAGE, FORMAT_ACTION, FORMAT_INT, - FORMAT_TIMESTAMP, + FORMAT_LONG, FORMAT_REMOTE_INPUT, FORMAT_BUNDLE, }) @@ -98,9 +98,14 @@ public final class SliceItem implements Parcelable { */ public static final String FORMAT_INT = "int"; /** - * A {@link SliceItem} that contains a timestamp. + * A {@link SliceItem} that contains a long. */ - public static final String FORMAT_TIMESTAMP = "timestamp"; + public static final String FORMAT_LONG = "long"; + /** + * @deprecated TO BE REMOVED + */ + @Deprecated + public static final String FORMAT_TIMESTAMP = FORMAT_LONG; /** * A {@link SliceItem} that contains a {@link RemoteInput}. */ @@ -123,6 +128,14 @@ public final class SliceItem implements Parcelable { * @hide */ public SliceItem(Object obj, @SliceType String format, String subType, + List<String> hints) { + this(obj, format, subType, hints.toArray(new String[hints.size()])); + } + + /** + * @hide + */ + public SliceItem(Object obj, @SliceType String format, String subType, @Slice.SliceHint String[] hints) { mHints = hints; mFormat = format; diff --git a/android/app/slice/SliceManager.java b/android/app/slice/SliceManager.java index 2fa9d8e0..0285e9f9 100644 --- a/android/app/slice/SliceManager.java +++ b/android/app/slice/SliceManager.java @@ -16,24 +16,29 @@ package android.app.slice; -import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemService; +import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Context; -import android.content.IContentProvider; import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.PermissionResult; import android.content.pm.ResolveInfo; import android.net.Uri; +import android.os.Binder; import android.os.Bundle; import android.os.Handler; +import android.os.IBinder; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; -import android.util.ArrayMap; +import android.os.UserHandle; import android.util.Log; -import android.util.Pair; import com.android.internal.util.Preconditions; @@ -42,7 +47,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.concurrent.Executor; /** * Class to handle interactions with {@link Slice}s. @@ -60,10 +64,41 @@ public class SliceManager { public static final String ACTION_REQUEST_SLICE_PERMISSION = "android.intent.action.REQUEST_SLICE_PERMISSION"; + /** + * Category used to resolve intents that can be rendered as slices. + * <p> + * This category should be included on intent filters on providers that extend + * {@link SliceProvider}. + * @see SliceProvider + * @see SliceProvider#onMapIntentToUri(Intent) + * @see #mapIntentToUri(Intent) + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_SLICE = "android.app.slice.category.SLICE"; + + /** + * The meta-data key that allows an activity to easily be linked directly to a slice. + * <p> + * An activity can be statically linked to a slice uri by including a meta-data item + * for this key that contains a valid slice uri for the same application declaring + * the activity. + * + * <pre class="prettyprint"> + * {@literal + * <activity android:name="com.example.mypkg.MyActivity"> + * <meta-data android:name="android.metadata.SLICE_URI" + * android:value="content://com.example.mypkg/main_slice" /> + * </activity>} + * </pre> + * + * @see #mapIntentToUri(Intent) + * @see SliceProvider#onMapIntentToUri(Intent) + */ + public static final String SLICE_METADATA_KEY = "android.metadata.SLICE_URI"; + private final ISliceManager mService; private final Context mContext; - private final ArrayMap<Pair<Uri, SliceCallback>, ISliceListener> mListenerLookup = - new ArrayMap<>(); + private final IBinder mToken = new Binder(); /** * Permission denied. @@ -91,117 +126,24 @@ public class SliceManager { } /** - * @deprecated TO BE REMOVED. - */ - @Deprecated - public void registerSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback, - @NonNull List<SliceSpec> specs) { - registerSliceCallback(uri, specs, mContext.getMainExecutor(), callback); - } - - /** - * @deprecated TO BE REMOVED. - */ - @Deprecated - public void registerSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback, - @NonNull List<SliceSpec> specs, Executor executor) { - registerSliceCallback(uri, specs, executor, callback); - } - - /** - * Adds a callback to a specific slice uri. - * <p> - * This is a convenience that performs a few slice actions at once. It will put - * the slice in a pinned state since there is a callback attached. It will also - * listen for content changes, when a content change observes, the android system - * will bind the new slice and provide it to all registered {@link SliceCallback}s. - * - * @param uri The uri of the slice being listened to. - * @param callback The listener that should receive the callbacks. - * @param specs The list of supported {@link SliceSpec}s of the callback. - * @see SliceProvider#onSlicePinned(Uri) - */ - public void registerSliceCallback(@NonNull Uri uri, @NonNull List<SliceSpec> specs, - @NonNull SliceCallback callback) { - registerSliceCallback(uri, specs, mContext.getMainExecutor(), callback); - } - - /** - * Adds a callback to a specific slice uri. - * <p> - * This is a convenience that performs a few slice actions at once. It will put - * the slice in a pinned state since there is a callback attached. It will also - * listen for content changes, when a content change observes, the android system - * will bind the new slice and provide it to all registered {@link SliceCallback}s. - * - * @param uri The uri of the slice being listened to. - * @param callback The listener that should receive the callbacks. - * @param specs The list of supported {@link SliceSpec}s of the callback. - * @see SliceProvider#onSlicePinned(Uri) - */ - public void registerSliceCallback(@NonNull Uri uri, @NonNull List<SliceSpec> specs, - @NonNull @CallbackExecutor Executor executor, @NonNull SliceCallback callback) { - try { - mService.addSliceListener(uri, mContext.getPackageName(), - getListener(uri, callback, new ISliceListener.Stub() { - @Override - public void onSliceUpdated(Slice s) throws RemoteException { - executor.execute(() -> callback.onSliceUpdated(s)); - } - }), specs.toArray(new SliceSpec[specs.size()])); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - private ISliceListener getListener(Uri uri, SliceCallback callback, - ISliceListener listener) { - Pair<Uri, SliceCallback> key = new Pair<>(uri, callback); - if (mListenerLookup.containsKey(key)) { - try { - mService.removeSliceListener(uri, mContext.getPackageName(), - mListenerLookup.get(key)); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - mListenerLookup.put(key, listener); - return listener; - } - - /** - * Removes a callback for a specific slice uri. - * <p> - * Removes the app from the pinned state (if there are no other apps/callbacks pinning it) - * in addition to removing the callback. - * - * @param uri The uri of the slice being listened to - * @param callback The listener that should no longer receive callbacks. - * @see #registerSliceCallback - */ - public void unregisterSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback) { - try { - mService.removeSliceListener(uri, mContext.getPackageName(), - mListenerLookup.remove(new Pair<>(uri, callback))); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** * Ensures that a slice is in a pinned state. * <p> * Pinned state is not persisted across reboots, so apps are expected to re-pin any slices * they still care about after a reboot. + * <p> + * This may only be called by apps that are the default launcher for the device + * or the default voice interaction service. Otherwise will throw {@link SecurityException}. * * @param uri The uri of the slice being pinned. * @param specs The list of supported {@link SliceSpec}s of the callback. * @see SliceProvider#onSlicePinned(Uri) + * @see Intent#ACTION_ASSIST + * @see Intent#CATEGORY_HOME */ public void pinSlice(@NonNull Uri uri, @NonNull List<SliceSpec> specs) { try { mService.pinSlice(mContext.getPackageName(), uri, - specs.toArray(new SliceSpec[specs.size()])); + specs.toArray(new SliceSpec[specs.size()]), mToken); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -211,14 +153,19 @@ public class SliceManager { * Remove a pin for a slice. * <p> * If the slice has no other pins/callbacks then the slice will be unpinned. + * <p> + * This may only be called by apps that are the default launcher for the device + * or the default voice interaction service. Otherwise will throw {@link SecurityException}. * * @param uri The uri of the slice being unpinned. * @see #pinSlice * @see SliceProvider#onSliceUnpinned(Uri) + * @see Intent#ACTION_ASSIST + * @see Intent#CATEGORY_HOME */ public void unpinSlice(@NonNull Uri uri) { try { - mService.unpinSlice(mContext.getPackageName(), uri); + mService.unpinSlice(mContext.getPackageName(), uri, mToken); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -251,6 +198,18 @@ public class SliceManager { } /** + * Get the list of currently pinned slices for this app. + * @see SliceProvider#onSlicePinned + */ + public @NonNull List<Uri> getPinnedSlices() { + try { + return Arrays.asList(mService.getPinnedSlices(mContext.getPackageName())); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Obtains a list of slices that are descendants of the specified Uri. * <p> * Not all slice providers will implement this functionality, in which case, @@ -262,17 +221,13 @@ public class SliceManager { */ public @NonNull Collection<Uri> getSliceDescendants(@NonNull Uri uri) { ContentResolver resolver = mContext.getContentResolver(); - IContentProvider provider = resolver.acquireProvider(uri); - try { + try (ContentProviderClient provider = resolver.acquireContentProviderClient(uri)) { Bundle extras = new Bundle(); extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri); - final Bundle res = provider.call(resolver.getPackageName(), - SliceProvider.METHOD_GET_DESCENDANTS, null, extras); + final Bundle res = provider.call(SliceProvider.METHOD_GET_DESCENDANTS, null, extras); return res.getParcelableArrayList(SliceProvider.EXTRA_SLICE_DESCENDANTS); } catch (RemoteException e) { Log.e(TAG, "Unable to get slice descendants", e); - } finally { - resolver.releaseProvider(provider); } return Collections.emptyList(); } @@ -288,17 +243,15 @@ public class SliceManager { public @Nullable Slice bindSlice(@NonNull Uri uri, @NonNull List<SliceSpec> supportedSpecs) { Preconditions.checkNotNull(uri, "uri"); ContentResolver resolver = mContext.getContentResolver(); - IContentProvider provider = resolver.acquireProvider(uri); - if (provider == null) { - throw new IllegalArgumentException("Unknown URI " + uri); - } - try { + try (ContentProviderClient provider = resolver.acquireContentProviderClient(uri)) { + if (provider == null) { + throw new IllegalArgumentException("Unknown URI " + uri); + } Bundle extras = new Bundle(); extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri); extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS, new ArrayList<>(supportedSpecs)); - final Bundle res = provider.call(mContext.getPackageName(), SliceProvider.METHOD_SLICE, - null, extras); + final Bundle res = provider.call(SliceProvider.METHOD_SLICE, null, extras); Bundle.setDefusable(res, true); if (res == null) { return null; @@ -308,8 +261,80 @@ public class SliceManager { // Arbitrary and not worth documenting, as Activity // Manager will kill this process shortly anyway. return null; - } finally { - resolver.releaseProvider(provider); + } + } + + /** + * Turns a slice intent into a slice uri. Expects an explicit intent. + * <p> + * This goes through a several stage resolution process to determine if any slice + * can represent this intent. + * <ol> + * <li> If the intent contains data that {@link ContentResolver#getType} is + * {@link SliceProvider#SLICE_TYPE} then the data will be returned.</li> + * <li>If the intent with {@link #CATEGORY_SLICE} added resolves to a provider, then + * the provider will be asked to {@link SliceProvider#onMapIntentToUri} and that result + * will be returned.</li> + * <li>Lastly, if the intent explicitly points at an activity, and that activity has + * meta-data for key {@link #SLICE_METADATA_KEY}, then the Uri specified there will be + * returned.</li> + * <li>If no slice is found, then {@code null} is returned.</li> + * </ol> + * @param intent The intent associated with a slice. + * @return The Slice Uri provided by the app or null if none exists. + * @see Slice + * @see SliceProvider#onMapIntentToUri(Intent) + * @see Intent + */ + public @Nullable Uri mapIntentToUri(@NonNull Intent intent) { + Preconditions.checkNotNull(intent, "intent"); + Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null + || intent.getData() != null, + "Slice intent must be explicit %s", intent); + ContentResolver resolver = mContext.getContentResolver(); + + // Check if the intent has data for the slice uri on it and use that + final Uri intentData = intent.getData(); + if (intentData != null && SliceProvider.SLICE_TYPE.equals(resolver.getType(intentData))) { + return intentData; + } + // Otherwise ask the app + Intent queryIntent = new Intent(intent); + if (!queryIntent.hasCategory(CATEGORY_SLICE)) { + queryIntent.addCategory(CATEGORY_SLICE); + } + List<ResolveInfo> providers = + mContext.getPackageManager().queryIntentContentProviders(queryIntent, 0); + if (providers == null || providers.isEmpty()) { + // There are no providers, see if this activity has a direct link. + ResolveInfo resolve = mContext.getPackageManager().resolveActivity(intent, + PackageManager.GET_META_DATA); + if (resolve != null && resolve.activityInfo != null + && resolve.activityInfo.metaData != null + && resolve.activityInfo.metaData.containsKey(SLICE_METADATA_KEY)) { + return Uri.parse( + resolve.activityInfo.metaData.getString(SLICE_METADATA_KEY)); + } + return null; + } + String authority = providers.get(0).providerInfo.authority; + Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority(authority).build(); + try (ContentProviderClient provider = resolver.acquireContentProviderClient(uri)) { + if (provider == null) { + throw new IllegalArgumentException("Unknown URI " + uri); + } + Bundle extras = new Bundle(); + extras.putParcelable(SliceProvider.EXTRA_INTENT, intent); + final Bundle res = provider.call(SliceProvider.METHOD_MAP_ONLY_INTENT, null, extras); + if (res == null) { + return null; + } + return res.getParcelable(SliceProvider.EXTRA_SLICE); + } catch (RemoteException e) { + // Arbitrary and not worth documenting, as Activity + // Manager will kill this process shortly anyway. + return null; } } @@ -328,8 +353,9 @@ public class SliceManager { public @Nullable Slice bindSlice(@NonNull Intent intent, @NonNull List<SliceSpec> supportedSpecs) { Preconditions.checkNotNull(intent, "intent"); - Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null, - "Slice intent must be explicit " + intent); + Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null + || intent.getData() != null, + "Slice intent must be explicit %s", intent); ContentResolver resolver = mContext.getContentResolver(); // Check if the intent has data for the slice uri on it and use that @@ -340,23 +366,30 @@ public class SliceManager { // Otherwise ask the app List<ResolveInfo> providers = mContext.getPackageManager().queryIntentContentProviders(intent, 0); - if (providers == null) { - throw new IllegalArgumentException("Unable to resolve intent " + intent); + if (providers == null || providers.isEmpty()) { + // There are no providers, see if this activity has a direct link. + ResolveInfo resolve = mContext.getPackageManager().resolveActivity(intent, + PackageManager.GET_META_DATA); + if (resolve != null && resolve.activityInfo != null + && resolve.activityInfo.metaData != null + && resolve.activityInfo.metaData.containsKey(SLICE_METADATA_KEY)) { + return bindSlice(Uri.parse(resolve.activityInfo.metaData + .getString(SLICE_METADATA_KEY)), supportedSpecs); + } + return null; } String authority = providers.get(0).providerInfo.authority; Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) .authority(authority).build(); - IContentProvider provider = resolver.acquireProvider(uri); - if (provider == null) { - throw new IllegalArgumentException("Unknown URI " + uri); - } - try { + try (ContentProviderClient provider = resolver.acquireContentProviderClient(uri)) { + if (provider == null) { + throw new IllegalArgumentException("Unknown URI " + uri); + } Bundle extras = new Bundle(); extras.putParcelable(SliceProvider.EXTRA_INTENT, intent); extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS, new ArrayList<>(supportedSpecs)); - final Bundle res = provider.call(mContext.getPackageName(), - SliceProvider.METHOD_MAP_INTENT, null, extras); + final Bundle res = provider.call(SliceProvider.METHOD_MAP_INTENT, null, extras); if (res == null) { return null; } @@ -365,21 +398,82 @@ public class SliceManager { // Arbitrary and not worth documenting, as Activity // Manager will kill this process shortly anyway. return null; - } finally { - resolver.releaseProvider(provider); } } /** + * Determine whether a particular process and user ID has been granted + * permission to access a specific slice URI. + * + * @param uri The uri that is being checked. + * @param pid The process ID being checked against. Must be > 0. + * @param uid The user ID being checked against. A uid of 0 is the root + * user, which will pass every permission check. + * + * @return {@link PackageManager#PERMISSION_GRANTED} if the given + * pid/uid is allowed to access that uri, or + * {@link PackageManager#PERMISSION_DENIED} if it is not. + * + * @see #grantSlicePermission(String, Uri) + */ + public @PermissionResult int checkSlicePermission(@NonNull Uri uri, int pid, int uid) { + // TODO: Switch off Uri permissions. + return mContext.checkUriPermission(uri, pid, uid, + Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + } + + /** + * Grant permission to access a specific slice Uri to another package. + * + * @param toPackage The package you would like to allow to access the Uri. + * @param uri The Uri you would like to grant access to. + * + * @see #revokeSlicePermission + */ + public void grantSlicePermission(@NonNull String toPackage, @NonNull Uri uri) { + // TODO: Switch off Uri permissions. + mContext.grantUriPermission(toPackage, uri, + Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION + | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); + } + + /** + * Remove permissions to access a particular content provider Uri + * that were previously added with {@link #grantSlicePermission} for a specific target + * package. The given Uri will match all previously granted Uris that are the same or a + * sub-path of the given Uri. That is, revoking "content://foo/target" will + * revoke both "content://foo/target" and "content://foo/target/sub", but not + * "content://foo". It will not remove any prefix grants that exist at a + * higher level. + * + * @param toPackage The package you would like to allow to access the Uri. + * @param uri The Uri you would like to revoke access to. + * + * @see #grantSlicePermission + */ + public void revokeSlicePermission(@NonNull String toPackage, @NonNull Uri uri) { + // TODO: Switch off Uri permissions. + mContext.revokeUriPermission(toPackage, uri, + Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION + | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); + } + + /** * Does the permission check to see if a caller has access to a specific slice. * @hide */ - public void enforceSlicePermission(Uri uri, String pkg, int pid, int uid) { + public void enforceSlicePermission(Uri uri, String pkg, int pid, int uid, + String[] autoGrantPermissions) { try { + if (UserHandle.isSameApp(uid, Process.myUid())) { + return; + } if (pkg == null) { throw new SecurityException("No pkg specified"); } - int result = mService.checkSlicePermission(uri, pkg, pid, uid); + int result = mService.checkSlicePermission(uri, pkg, pid, uid, autoGrantPermissions); if (result == PERMISSION_DENIED) { throw new SecurityException("User " + uid + " does not have slice permission for " + uri + "."); @@ -391,6 +485,8 @@ public class SliceManager { Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); + // Notify a change has happened because we just granted a permission. + mContext.getContentResolver().notifyChange(uri, null); } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -408,18 +504,4 @@ public class SliceManager { throw e.rethrowFromSystemServer(); } } - - /** - * Class that listens to changes in {@link Slice}s. - */ - public interface SliceCallback { - - /** - * Called when slice is updated. - * - * @param s The updated slice. - * @see #registerSliceCallback - */ - void onSliceUpdated(Slice s); - } } diff --git a/android/app/slice/SliceMetrics.java b/android/app/slice/SliceMetrics.java new file mode 100644 index 00000000..20c1390b --- /dev/null +++ b/android/app/slice/SliceMetrics.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.slice; + +import android.annotation.NonNull; +import android.content.Context; +import android.net.Uri; + +import com.android.internal.logging.MetricsLogger; + +/** + * Metrics interface for slices. + * + * This is called by SliceView, so Slice developers should + * not need to reference this class. + * + * @see androidx.slice.widget.SliceView + */ +public class SliceMetrics { + + private static final String TAG = "SliceMetrics"; + private MetricsLogger mMetricsLogger; + + /** + * An object to be used throughout the life of a slice to register events. + */ + public SliceMetrics(@NonNull Context context, @NonNull Uri uri) { + mMetricsLogger = new MetricsLogger(); + } + + /** + * To be called whenever the slice becomes visible to the user. + */ + public void logVisible() { + } + + /** + * To be called whenever the slice becomes invisible to the user. + */ + public void logHidden() { + } + + /** + * To be called whenever the user invokes a discrete action via a slice. + * + * <P> + * Use this for discrete events like a tap or the end of a drag, + * not for a continuous streams of events, such as the motion during a gesture. + * </P> + * + * @see androidx.slice.widget.EventInfo#actionType + * + * @param actionType The type of the event. + * @param subSlice The URI of the sub-slice that is the subject of the interaction. + */ + public void logTouch(int actionType, @NonNull Uri subSlice) { + } +} diff --git a/android/app/slice/SliceProvider.java b/android/app/slice/SliceProvider.java index 00e8ccad..fe5742d6 100644 --- a/android/app/slice/SliceProvider.java +++ b/android/app/slice/SliceProvider.java @@ -16,7 +16,6 @@ package android.app.slice; import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.PendingIntent; import android.content.ComponentName; import android.content.ContentProvider; @@ -35,18 +34,16 @@ import android.os.Binder; import android.os.Bundle; import android.os.CancellationSignal; import android.os.Handler; -import android.os.Looper; import android.os.Process; import android.os.StrictMode; import android.os.StrictMode.ThreadPolicy; -import android.os.UserHandle; import android.util.Log; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.concurrent.CountDownLatch; /** * A SliceProvider allows an app to provide content to be displayed in system spaces. This content @@ -65,8 +62,8 @@ import java.util.concurrent.CountDownLatch; * <pre class="prettyprint"> * {@literal * <provider - * android:name="com.android.mypkg.MySliceProvider" - * android:authorities="com.android.mypkg" />} + * android:name="com.example.mypkg.MySliceProvider" + * android:authorities="com.example.mypkg" />} * </pre> * <p> * Slices can be identified by a Uri or by an Intent. To link an Intent with a slice, the provider @@ -77,10 +74,11 @@ import java.util.concurrent.CountDownLatch; * <pre class="prettyprint"> * {@literal * <provider - * android:name="com.android.mypkg.MySliceProvider" - * android:authorities="com.android.mypkg"> + * android:name="com.example.mypkg.MySliceProvider" + * android:authorities="com.example.mypkg"> * <intent-filter> - * <action android:name="android.intent.action.MY_SLICE_INTENT" /> + * <action android:name="com.example.mypkg.intent.action.MY_SLICE_INTENT" /> + * <category android:name="android.app.slice.category.SLICE" /> * </intent-filter> * </provider>} * </pre> @@ -114,6 +112,10 @@ public abstract class SliceProvider extends ContentProvider { /** * @hide */ + public static final String METHOD_MAP_ONLY_INTENT = "map_only"; + /** + * @hide + */ public static final String METHOD_PIN = "pin"; /** * @hide @@ -143,24 +145,33 @@ public abstract class SliceProvider extends ContentProvider { * @hide */ public static final String EXTRA_PROVIDER_PKG = "provider_pkg"; - /** - * @hide - */ - public static final String EXTRA_OVERRIDE_PKG = "override_pkg"; private static final boolean DEBUG = false; - private String mBindingPkg; + private static final long SLICE_BIND_ANR = 2000; + private final String[] mAutoGrantPermissions; + + private String mCallback; private SliceManager mSliceManager; /** - * Return the package name of the caller that initiated the binding request - * currently happening. The returned package will have been - * verified to belong to the calling UID. Returns {@code null} if not - * currently performing an {@link #onBindSlice(Uri, List)}. + * A version of constructing a SliceProvider that allows autogranting slice permissions + * to apps that hold specific platform permissions. + * <p> + * When an app tries to bind a slice from this provider that it does not have access to, + * This provider will check if the caller holds permissions to any of the autoGrantPermissions + * specified, if they do they will be granted persisted uri access to all slices of this + * provider. + * + * @param autoGrantPermissions List of permissions that holders are auto-granted access + * to slices. */ - public final @Nullable String getBindingPackage() { - return mBindingPkg; + public SliceProvider(@NonNull String... autoGrantPermissions) { + mAutoGrantPermissions = autoGrantPermissions; + } + + public SliceProvider() { + mAutoGrantPermissions = new String[0]; } @Override @@ -170,12 +181,12 @@ public abstract class SliceProvider extends ContentProvider { } /** - * Implemented to create a slice. Will be called on the main thread. + * Implemented to create a slice. * <p> * onBindSlice should return as quickly as possible so that the UI tied * to this slice can be responsive. No network or other IO will be allowed * during onBindSlice. Any loading that needs to be done should happen - * off the main thread with a call to {@link ContentResolver#notifyChange(Uri, ContentObserver)} + * in the background with a call to {@link ContentResolver#notifyChange(Uri, ContentObserver)} * when the app is ready to provide the complete data in onBindSlice. * <p> * The slice returned should have a spec that is compatible with one of @@ -241,14 +252,36 @@ public abstract class SliceProvider extends ContentProvider { * In that case, this method can be called and is expected to return a non-null Uri representing * a slice. Otherwise this will throw {@link UnsupportedOperationException}. * + * Any intent filter added to a slice provider should also contain + * {@link SliceManager#CATEGORY_SLICE}, because otherwise it will not be detected by + * {@link SliceManager#mapIntentToUri(Intent)}. + * * @return Uri representing the slice associated with the provided intent. - * @see {@link Slice} + * @see Slice + * @see SliceManager#mapIntentToUri(Intent) */ public @NonNull Uri onMapIntentToUri(Intent intent) { throw new UnsupportedOperationException( "This provider has not implemented intent to uri mapping"); } + /** + * Called when an app requests a slice it does not have write permission + * to the uri for. + * <p> + * The return value will be the action on a slice that prompts the user that + * the calling app wants to show slices from this app. The default implementation + * launches a dialog that allows the user to grant access to this slice. Apps + * that do not want to allow this user grant, can override this and instead + * launch their own dialog with different behavior. + * + * @param sliceUri the Uri of the slice attempting to be bound. + * @see #getCallingPackage() + */ + public @NonNull PendingIntent onCreatePermissionRequest(Uri sliceUri) { + return createPermissionIntent(getContext(), sliceUri, getCallingPackage()); + } + @Override public final int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { @@ -302,13 +335,10 @@ public abstract class SliceProvider extends ContentProvider { List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS); String callingPackage = getCallingPackage(); - if (extras.containsKey(EXTRA_OVERRIDE_PKG)) { - if (Binder.getCallingUid() != Process.SYSTEM_UID) { - throw new SecurityException("Only the system can override calling pkg"); - } - callingPackage = extras.getString(EXTRA_OVERRIDE_PKG); - } - Slice s = handleBindSlice(uri, supportedSpecs, callingPackage); + int callingUid = Binder.getCallingUid(); + int callingPid = Binder.getCallingPid(); + + Slice s = handleBindSlice(uri, supportedSpecs, callingPackage, callingUid, callingPid); Bundle b = new Bundle(); b.putParcelable(EXTRA_SLICE, s); return b; @@ -319,12 +349,20 @@ public abstract class SliceProvider extends ContentProvider { List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS); Bundle b = new Bundle(); if (uri != null) { - Slice s = handleBindSlice(uri, supportedSpecs, getCallingPackage()); + Slice s = handleBindSlice(uri, supportedSpecs, getCallingPackage(), + Binder.getCallingUid(), Binder.getCallingPid()); b.putParcelable(EXTRA_SLICE, s); } else { b.putParcelable(EXTRA_SLICE, null); } return b; + } else if (method.equals(METHOD_MAP_ONLY_INTENT)) { + Intent intent = extras.getParcelable(EXTRA_INTENT); + if (intent == null) return null; + Uri uri = onMapIntentToUri(intent); + Bundle b = new Bundle(); + b.putParcelable(EXTRA_SLICE, uri); + return b; } else if (method.equals(METHOD_PIN)) { Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_BIND_URI)); if (Binder.getCallingUid() != Process.SYSTEM_UID) { @@ -348,102 +386,80 @@ public abstract class SliceProvider extends ContentProvider { } private Collection<Uri> handleGetDescendants(Uri uri) { - if (Looper.myLooper() == Looper.getMainLooper()) { + mCallback = "onGetSliceDescendants"; + Handler.getMain().postDelayed(mAnr, SLICE_BIND_ANR); + try { return onGetSliceDescendants(uri); - } else { - CountDownLatch latch = new CountDownLatch(1); - Collection<Uri>[] output = new Collection[1]; - Handler.getMain().post(() -> { - output[0] = onGetSliceDescendants(uri); - latch.countDown(); - }); - try { - latch.await(); - return output[0]; - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + } finally { + Handler.getMain().removeCallbacks(mAnr); } } private void handlePinSlice(Uri sliceUri) { - if (Looper.myLooper() == Looper.getMainLooper()) { + mCallback = "onSlicePinned"; + Handler.getMain().postDelayed(mAnr, SLICE_BIND_ANR); + try { onSlicePinned(sliceUri); - } else { - CountDownLatch latch = new CountDownLatch(1); - Handler.getMain().post(() -> { - onSlicePinned(sliceUri); - latch.countDown(); - }); - try { - latch.await(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + } finally { + Handler.getMain().removeCallbacks(mAnr); } } private void handleUnpinSlice(Uri sliceUri) { - if (Looper.myLooper() == Looper.getMainLooper()) { + mCallback = "onSliceUnpinned"; + Handler.getMain().postDelayed(mAnr, SLICE_BIND_ANR); + try { onSliceUnpinned(sliceUri); - } else { - CountDownLatch latch = new CountDownLatch(1); - Handler.getMain().post(() -> { - onSliceUnpinned(sliceUri); - latch.countDown(); - }); - try { - latch.await(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + } finally { + Handler.getMain().removeCallbacks(mAnr); } } private Slice handleBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs, - String callingPkg) { + String callingPkg, int callingUid, int callingPid) { // This can be removed once Slice#bindSlice is removed and everyone is using // SliceManager#bindSlice. String pkg = callingPkg != null ? callingPkg - : getContext().getPackageManager().getNameForUid(Binder.getCallingUid()); - if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) { - try { - mSliceManager.enforceSlicePermission(sliceUri, pkg, - Binder.getCallingPid(), Binder.getCallingUid()); - } catch (SecurityException e) { - return createPermissionSlice(getContext(), sliceUri, pkg); - } + : getContext().getPackageManager().getNameForUid(callingUid); + try { + mSliceManager.enforceSlicePermission(sliceUri, pkg, + callingPid, callingUid, mAutoGrantPermissions); + } catch (SecurityException e) { + return createPermissionSlice(getContext(), sliceUri, pkg); } - if (Looper.myLooper() == Looper.getMainLooper()) { - return onBindSliceStrict(sliceUri, supportedSpecs, pkg); - } else { - CountDownLatch latch = new CountDownLatch(1); - Slice[] output = new Slice[1]; - Handler.getMain().post(() -> { - output[0] = onBindSliceStrict(sliceUri, supportedSpecs, pkg); - latch.countDown(); - }); - try { - latch.await(); - return output[0]; - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + mCallback = "onBindSlice"; + Handler.getMain().postDelayed(mAnr, SLICE_BIND_ANR); + try { + return onBindSliceStrict(sliceUri, supportedSpecs); + } finally { + Handler.getMain().removeCallbacks(mAnr); } } /** * @hide */ - public static Slice createPermissionSlice(Context context, Uri sliceUri, + public Slice createPermissionSlice(Context context, Uri sliceUri, String callingPackage) { - return new Slice.Builder(sliceUri) - .addAction(createPermissionIntent(context, sliceUri, callingPackage), - new Slice.Builder(sliceUri.buildUpon().appendPath("permission").build()) - .addText(getPermissionString(context, callingPackage), null) - .build()) - .addHints(Slice.HINT_LIST_ITEM) - .build(); + PendingIntent action; + mCallback = "onCreatePermissionRequest"; + Handler.getMain().postDelayed(mAnr, SLICE_BIND_ANR); + try { + action = onCreatePermissionRequest(sliceUri); + } finally { + Handler.getMain().removeCallbacks(mAnr); + } + Slice.Builder parent = new Slice.Builder(sliceUri); + Slice.Builder childAction = new Slice.Builder(parent) + .addHints(Arrays.asList(Slice.HINT_TITLE, Slice.HINT_SHORTCUT)) + .addAction(action, new Slice.Builder(parent).build(), null); + + parent.addSubSlice(new Slice.Builder(sliceUri.buildUpon().appendPath("permission").build()) + .addText(getPermissionString(context, callingPackage), null, + Collections.emptyList()) + .addSubSlice(childAction.build(), null) + .build(), null); + return parent.addHints(Arrays.asList(Slice.HINT_PERMISSION_REQUEST)).build(); } /** @@ -480,19 +496,21 @@ public abstract class SliceProvider extends ContentProvider { } } - private Slice onBindSliceStrict(Uri sliceUri, List<SliceSpec> supportedSpecs, - String callingPackage) { + private Slice onBindSliceStrict(Uri sliceUri, List<SliceSpec> supportedSpecs) { ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); try { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectAll() .penaltyDeath() .build()); - mBindingPkg = callingPackage; return onBindSlice(sliceUri, supportedSpecs); } finally { - mBindingPkg = null; StrictMode.setThreadPolicy(oldPolicy); } } + + private final Runnable mAnr = () -> { + Process.sendSignal(Process.myPid(), Process.SIGNAL_QUIT); + Log.wtf(TAG, "Timed out while handling slice callback " + mCallback); + }; } diff --git a/android/app/slice/SliceSpec.java b/android/app/slice/SliceSpec.java index 8cc0384c..03ffe6df 100644 --- a/android/app/slice/SliceSpec.java +++ b/android/app/slice/SliceSpec.java @@ -89,7 +89,7 @@ public final class SliceSpec implements Parcelable { * * @param candidate candidate format of data. * @return true if versions are compatible. - * @see androidx.app.slice.widget.SliceView + * @see androidx.slice.widget.SliceView */ public boolean canRender(@NonNull SliceSpec candidate) { if (!mType.equals(candidate.mType)) return false; diff --git a/android/app/timezone/RulesManager.java b/android/app/timezone/RulesManager.java index 0a38eb9a..fe83113b 100644 --- a/android/app/timezone/RulesManager.java +++ b/android/app/timezone/RulesManager.java @@ -36,7 +36,7 @@ import java.util.Arrays; * <p>This interface is intended for use with the default APK-based time zone rules update * application but it can also be used by OEMs if that mechanism is turned off using configuration. * All callers must possess the {@link android.Manifest.permission#UPDATE_TIME_ZONE_RULES} system - * permission. + * permission unless otherwise stated. * * <p>When using the default mechanism, when properly configured the Android system will send a * {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent with a @@ -68,6 +68,23 @@ public final class RulesManager { private static final String TAG = "timezone.RulesManager"; private static final boolean DEBUG = false; + /** + * The action of the intent that the Android system will broadcast when a time zone rules update + * operation has been successfully staged (i.e. to be applied next reboot) or unstaged. + * + * <p>See {@link #EXTRA_OPERATION_STAGED} + * + * <p>This is a protected intent that can only be sent by the system. + */ + public static final String ACTION_RULES_UPDATE_OPERATION = + "com.android.intent.action.timezone.RULES_UPDATE_OPERATION"; + + /** + * The key for a boolean extra for the {@link #ACTION_RULES_UPDATE_OPERATION} intent used to + * indicate whether the operation was a "stage" or an "unstage". + */ + public static final String EXTRA_OPERATION_STAGED = "staged"; + @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "SUCCESS", "ERROR_" }, value = { SUCCESS, @@ -103,9 +120,12 @@ public final class RulesManager { /** * Returns information about the current time zone rules state such as the IANA version of - * the system and any currently installed distro. This method is intended to allow clients to - * determine if the current state can be improved; for example by passing the information to a - * server that may provide a new distro for download. + * the system and any currently installed distro. This method allows clients to determine the + * current device state, perhaps to see if it can be improved; for example by passing the + * information to a server that may provide a new distro for download. + * + * <p>Callers must possess the {@link android.Manifest.permission#QUERY_TIME_ZONE_RULES} system + * permission. */ public RulesState getRulesState() { try { diff --git a/android/app/usage/AppStandbyInfo.java b/android/app/usage/AppStandbyInfo.java new file mode 100644 index 00000000..51fe0e27 --- /dev/null +++ b/android/app/usage/AppStandbyInfo.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.usage; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A pair of {package, bucket} to denote the app standby bucket for a given package. + * Used as a vehicle of data across the binder IPC. + * @hide + */ +public final class AppStandbyInfo implements Parcelable { + + public String mPackageName; + public @UsageStatsManager.StandbyBuckets int mStandbyBucket; + + private AppStandbyInfo(Parcel in) { + mPackageName = in.readString(); + mStandbyBucket = in.readInt(); + } + + public AppStandbyInfo(String packageName, int bucket) { + mPackageName = packageName; + mStandbyBucket = bucket; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mPackageName); + dest.writeInt(mStandbyBucket); + } + + public static final Creator<AppStandbyInfo> CREATOR = new Creator<AppStandbyInfo>() { + @Override + public AppStandbyInfo createFromParcel(Parcel source) { + return new AppStandbyInfo(source); + } + + @Override + public AppStandbyInfo[] newArray(int size) { + return new AppStandbyInfo[size]; + } + }; +} diff --git a/android/app/usage/EventStats.java b/android/app/usage/EventStats.java new file mode 100644 index 00000000..ea95a055 --- /dev/null +++ b/android/app/usage/EventStats.java @@ -0,0 +1,181 @@ +/** + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package android.app.usage; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Contains usage statistics for an event type for a specific + * time range. + */ +public final class EventStats implements Parcelable { + + /** + * {@hide} + */ + public int mEventType; + + /** + * {@hide} + */ + public long mBeginTimeStamp; + + /** + * {@hide} + */ + public long mEndTimeStamp; + + /** + * {@hide} + */ + public long mLastEventTime; + + /** + * {@hide} + */ + public long mTotalTime; + + /** + * {@hide} + */ + public int mCount; + + /** + * {@hide} + */ + public EventStats() { + } + + public EventStats(EventStats stats) { + mEventType = stats.mEventType; + mBeginTimeStamp = stats.mBeginTimeStamp; + mEndTimeStamp = stats.mEndTimeStamp; + mLastEventTime = stats.mLastEventTime; + mTotalTime = stats.mTotalTime; + mCount = stats.mCount; + } + + /** + * Return the type of event this is usage for. May be one of the event + * constants in {@link UsageEvents.Event}. + */ + public int getEventType() { + return mEventType; + } + + /** + * Get the beginning of the time range this {@link android.app.usage.EventStats} represents, + * measured in milliseconds since the epoch. + * <p/> + * See {@link System#currentTimeMillis()}. + */ + public long getFirstTimeStamp() { + return mBeginTimeStamp; + } + + /** + * Get the end of the time range this {@link android.app.usage.EventStats} represents, + * measured in milliseconds since the epoch. + * <p/> + * See {@link System#currentTimeMillis()}. + */ + public long getLastTimeStamp() { + return mEndTimeStamp; + } + + /** + * Get the last time this event triggered, measured in milliseconds since the epoch. + * <p/> + * See {@link System#currentTimeMillis()}. + */ + public long getLastEventTime() { + return mLastEventTime; + } + + /** + * Return the number of times that this event occurred over the interval. + */ + public int getCount() { + return mCount; + } + + /** + * Get the total time this event was active, measured in milliseconds. + */ + public long getTotalTime() { + return mTotalTime; + } + + /** + * Add the statistics from the right {@link EventStats} to the left. The event type for + * both {@link UsageStats} objects must be the same. + * @param right The {@link EventStats} object to merge into this one. + * @throws java.lang.IllegalArgumentException if the event types of the two + * {@link UsageStats} objects are different. + */ + public void add(EventStats right) { + if (mEventType != right.mEventType) { + throw new IllegalArgumentException("Can't merge EventStats for event #" + + mEventType + " with EventStats for event #" + right.mEventType); + } + + // We use the mBeginTimeStamp due to a bug where UsageStats files can overlap with + // regards to their mEndTimeStamp. + if (right.mBeginTimeStamp > mBeginTimeStamp) { + mLastEventTime = Math.max(mLastEventTime, right.mLastEventTime); + } + mBeginTimeStamp = Math.min(mBeginTimeStamp, right.mBeginTimeStamp); + mEndTimeStamp = Math.max(mEndTimeStamp, right.mEndTimeStamp); + mTotalTime += right.mTotalTime; + mCount += right.mCount; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mEventType); + dest.writeLong(mBeginTimeStamp); + dest.writeLong(mEndTimeStamp); + dest.writeLong(mLastEventTime); + dest.writeLong(mTotalTime); + dest.writeInt(mCount); + } + + public static final Creator<EventStats> CREATOR = new Creator<EventStats>() { + @Override + public EventStats createFromParcel(Parcel in) { + EventStats stats = new EventStats(); + stats.mEventType = in.readInt(); + stats.mBeginTimeStamp = in.readLong(); + stats.mEndTimeStamp = in.readLong(); + stats.mLastEventTime = in.readLong(); + stats.mTotalTime = in.readLong(); + stats.mCount = in.readInt(); + return stats; + } + + @Override + public EventStats[] newArray(int size) { + return new EventStats[size]; + } + }; +} diff --git a/android/app/usage/NetworkStats.java b/android/app/usage/NetworkStats.java index da36157d..7252f028 100644 --- a/android/app/usage/NetworkStats.java +++ b/android/app/usage/NetworkStats.java @@ -24,7 +24,6 @@ import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; import android.net.TrafficStats; import android.os.RemoteException; -import android.os.ServiceManager; import android.util.IntArray; import android.util.Log; @@ -69,6 +68,11 @@ public final class NetworkStats implements AutoCloseable { private int mTag = android.net.NetworkStats.TAG_NONE; /** + * State in case it was not specified in the query. + */ + private int mState = Bucket.STATE_ALL; + + /** * The session while the query requires it, null if all the stats have been collected or close() * has been called. */ @@ -98,9 +102,8 @@ public final class NetworkStats implements AutoCloseable { /** @hide */ NetworkStats(Context context, NetworkTemplate template, int flags, long startTimestamp, - long endTimestamp) throws RemoteException, SecurityException { - final INetworkStatsService statsService = INetworkStatsService.Stub.asInterface( - ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); + long endTimestamp, INetworkStatsService statsService) + throws RemoteException, SecurityException { // Open network stats session mSession = statsService.openSessionForUsageStats(flags, context.getOpPackageName()); mCloseGuard.open("close"); @@ -269,6 +272,15 @@ public final class NetworkStats implements AutoCloseable { private long mTxBytes; private long mTxPackets; + private static int convertSet(@State int state) { + switch (state) { + case STATE_ALL: return android.net.NetworkStats.SET_ALL; + case STATE_DEFAULT: return android.net.NetworkStats.SET_DEFAULT; + case STATE_FOREGROUND: return android.net.NetworkStats.SET_FOREGROUND; + } + return 0; + } + private static @State int convertState(int networkStatsSet) { switch (networkStatsSet) { case android.net.NetworkStats.SET_ALL : return STATE_ALL; @@ -529,20 +541,13 @@ public final class NetworkStats implements AutoCloseable { /** * Collects history results for uid and resets history enumeration index. */ - void startHistoryEnumeration(int uid) { - startHistoryEnumeration(uid, android.net.NetworkStats.TAG_NONE); - } - - /** - * Collects history results for uid and resets history enumeration index. - */ - void startHistoryEnumeration(int uid, int tag) { + void startHistoryEnumeration(int uid, int tag, int state) { mHistory = null; try { mHistory = mSession.getHistoryIntervalForUid(mTemplate, uid, - android.net.NetworkStats.SET_ALL, tag, - NetworkStatsHistory.FIELD_ALL, mStartTimeStamp, mEndTimeStamp); - setSingleUidTag(uid, tag); + Bucket.convertSet(state), tag, NetworkStatsHistory.FIELD_ALL, + mStartTimeStamp, mEndTimeStamp); + setSingleUidTagState(uid, tag, state); } catch (RemoteException e) { Log.w(TAG, e); // Leaving mHistory null @@ -638,6 +643,7 @@ public final class NetworkStats implements AutoCloseable { fillBucketFromSummaryEntry(bucket); return bucket; } + /** * Getting the next item in a history enumeration. * @param bucketOut Next item will be set here. @@ -650,7 +656,7 @@ public final class NetworkStats implements AutoCloseable { mRecycledHistoryEntry); bucketOut.mUid = Bucket.convertUid(getUid()); bucketOut.mTag = Bucket.convertTag(mTag); - bucketOut.mState = Bucket.STATE_ALL; + bucketOut.mState = mState; bucketOut.mDefaultNetwork = Bucket.DEFAULT_NETWORK_ALL; bucketOut.mMetered = Bucket.METERED_ALL; bucketOut.mRoaming = Bucket.ROAMING_ALL; @@ -693,9 +699,10 @@ public final class NetworkStats implements AutoCloseable { return mUidOrUidIndex; } - private void setSingleUidTag(int uid, int tag) { + private void setSingleUidTagState(int uid, int tag, int state) { mUidOrUidIndex = uid; mTag = tag; + mState = state; } private void stepUid() { diff --git a/android/app/usage/NetworkStatsManager.java b/android/app/usage/NetworkStatsManager.java index 5576e86e..b2fe9586 100644 --- a/android/app/usage/NetworkStatsManager.java +++ b/android/app/usage/NetworkStatsManager.java @@ -37,6 +37,8 @@ import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; + /** * Provides access to network usage history and statistics. Usage data is collected in * discrete bins of time called 'Buckets'. See {@link NetworkStats.Bucket} for details. @@ -107,9 +109,15 @@ public class NetworkStatsManager { * {@hide} */ public NetworkStatsManager(Context context) throws ServiceNotFoundException { + this(context, INetworkStatsService.Stub.asInterface( + ServiceManager.getServiceOrThrow(Context.NETWORK_STATS_SERVICE))); + } + + /** @hide */ + @VisibleForTesting + public NetworkStatsManager(Context context, INetworkStatsService service) { mContext = context; - mService = INetworkStatsService.Stub.asInterface( - ServiceManager.getServiceOrThrow(Context.NETWORK_STATS_SERVICE)); + mService = service; setPollOnOpen(true); } @@ -135,7 +143,8 @@ public class NetworkStatsManager { public Bucket querySummaryForDevice(NetworkTemplate template, long startTime, long endTime) throws SecurityException, RemoteException { Bucket bucket = null; - NetworkStats stats = new NetworkStats(mContext, template, mFlags, startTime, endTime); + NetworkStats stats = new NetworkStats(mContext, template, mFlags, startTime, endTime, + mService); bucket = stats.getDeviceSummaryForNetwork(); stats.close(); @@ -208,7 +217,7 @@ public class NetworkStatsManager { } NetworkStats stats; - stats = new NetworkStats(mContext, template, mFlags, startTime, endTime); + stats = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService); stats.startSummaryEnumeration(); stats.close(); @@ -245,7 +254,7 @@ public class NetworkStatsManager { } NetworkStats result; - result = new NetworkStats(mContext, template, mFlags, startTime, endTime); + result = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService); result.startSummaryEnumeration(); return result; @@ -254,20 +263,31 @@ public class NetworkStatsManager { /** * Query network usage statistics details for a given uid. * - * #see queryDetailsForUidTag(int, String, long, long, int, int) + * #see queryDetailsForUidTagState(int, String, long, long, int, int, int) */ public NetworkStats queryDetailsForUid(int networkType, String subscriberId, - long startTime, long endTime, int uid) throws SecurityException, RemoteException { - return queryDetailsForUidTag(networkType, subscriberId, startTime, endTime, uid, - NetworkStats.Bucket.TAG_NONE); + long startTime, long endTime, int uid) throws SecurityException { + return queryDetailsForUidTagState(networkType, subscriberId, startTime, endTime, uid, + NetworkStats.Bucket.TAG_NONE, NetworkStats.Bucket.STATE_ALL); + } + + /** + * Query network usage statistics details for a given uid and tag. + * + * #see queryDetailsForUidTagState(int, String, long, long, int, int, int) + */ + public NetworkStats queryDetailsForUidTag(int networkType, String subscriberId, + long startTime, long endTime, int uid, int tag) throws SecurityException { + return queryDetailsForUidTagState(networkType, subscriberId, startTime, endTime, uid, + tag, NetworkStats.Bucket.STATE_ALL); } /** - * Query network usage statistics details for a given uid and tag. Only usable for uids - * belonging to calling user. Result is aggregated over state but not aggregated over time. - * This means buckets' start and end timestamps are going to be between 'startTime' and - * 'endTime' parameters. State is going to be {@link NetworkStats.Bucket#STATE_ALL}, uid the - * same as the 'uid' parameter and tag the same as 'tag' parameter. + * Query network usage statistics details for a given uid, tag, and state. Only usable for uids + * belonging to calling user. Result is not aggregated over time. This means buckets' start and + * end timestamps are going to be between 'startTime' and 'endTime' parameters. The uid is going + * to be the same as the 'uid' parameter, the tag the same as the 'tag' parameter, and the state + * the same as the 'state' parameter. * defaultNetwork is going to be {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL}, * metered is going to be {@link NetworkStats.Bucket#METERED_ALL}, and * roaming is going to be {@link NetworkStats.Bucket#ROAMING_ALL}. @@ -288,17 +308,18 @@ public class NetworkStatsManager { * @return Statistics object or null if an error happened during statistics collection. * @throws SecurityException if permissions are insufficient to read network statistics. */ - public NetworkStats queryDetailsForUidTag(int networkType, String subscriberId, - long startTime, long endTime, int uid, int tag) throws SecurityException { + public NetworkStats queryDetailsForUidTagState(int networkType, String subscriberId, + long startTime, long endTime, int uid, int tag, int state) throws SecurityException { NetworkTemplate template; template = createTemplate(networkType, subscriberId); NetworkStats result; try { - result = new NetworkStats(mContext, template, mFlags, startTime, endTime); - result.startHistoryEnumeration(uid, tag); + result = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService); + result.startHistoryEnumeration(uid, tag, state); } catch (RemoteException e) { - Log.e(TAG, "Error while querying stats for uid=" + uid + " tag=" + tag, e); + Log.e(TAG, "Error while querying stats for uid=" + uid + " tag=" + tag + + " state=" + state, e); return null; } @@ -341,7 +362,7 @@ public class NetworkStatsManager { } NetworkStats result; - result = new NetworkStats(mContext, template, mFlags, startTime, endTime); + result = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService); result.startUserUidEnumeration(); return result; } @@ -451,19 +472,20 @@ public class NetworkStatsManager { } private static NetworkTemplate createTemplate(int networkType, String subscriberId) { - NetworkTemplate template = null; + final NetworkTemplate template; switch (networkType) { - case ConnectivityManager.TYPE_MOBILE: { - template = NetworkTemplate.buildTemplateMobileAll(subscriberId); - } break; - case ConnectivityManager.TYPE_WIFI: { + case ConnectivityManager.TYPE_MOBILE: + template = subscriberId == null + ? NetworkTemplate.buildTemplateMobileWildcard() + : NetworkTemplate.buildTemplateMobileAll(subscriberId); + break; + case ConnectivityManager.TYPE_WIFI: template = NetworkTemplate.buildTemplateWifiWildcard(); - } break; - default: { + break; + default: throw new IllegalArgumentException("Cannot create template for network type " + networkType + ", subscriberId '" + NetworkIdentity.scrubSubscriberId(subscriberId) + "'."); - } } return template; } diff --git a/android/app/usage/TimeSparseArray.java b/android/app/usage/TimeSparseArray.java index 7974fa70..9ef88e41 100644 --- a/android/app/usage/TimeSparseArray.java +++ b/android/app/usage/TimeSparseArray.java @@ -17,6 +17,7 @@ package android.app.usage; import android.util.LongSparseArray; +import android.util.Slog; /** * An array that indexes by a long timestamp, representing milliseconds since the epoch. @@ -24,6 +25,8 @@ import android.util.LongSparseArray; * {@hide} */ public class TimeSparseArray<E> extends LongSparseArray<E> { + private static final String TAG = TimeSparseArray.class.getSimpleName(); + public TimeSparseArray() { super(); } @@ -70,6 +73,30 @@ public class TimeSparseArray<E> extends LongSparseArray<E> { } /** + * {@inheritDoc} + * + * Overridden to ensure no collisions. The key (time in milliseconds) is incremented till an + * empty place is found. + */ + @Override + public void put(long key, E value) { + final long origKey = key; + int keyIndex = indexOfKey(key); + if (keyIndex >= 0) { + final long sz = size(); + while (keyIndex < sz && keyAt(keyIndex) == key) { + key++; + keyIndex++; + } + if (key >= origKey + 10) { + Slog.w(TAG, "Value " + value + " supposed to be inserted at " + origKey + + " displaced to " + key); + } + } + super.put(key, value); + } + + /** * Finds the index of the first element whose timestamp is less than or equal to * the given time. * diff --git a/android/app/usage/UsageEvents.java b/android/app/usage/UsageEvents.java index edb992bd..84f57a30 100644 --- a/android/app/usage/UsageEvents.java +++ b/android/app/usage/UsageEvents.java @@ -16,6 +16,7 @@ package android.app.usage; import android.annotation.IntDef; +import android.annotation.SystemApi; import android.content.res.Configuration; import android.os.Parcel; import android.os.Parcelable; @@ -80,6 +81,7 @@ public final class UsageEvents implements Parcelable { * An event type denoting that a package was interacted with in some way by the system. * @hide */ + @SystemApi public static final int SYSTEM_INTERACTION = 6; /** @@ -104,14 +106,64 @@ public final class UsageEvents implements Parcelable { * An event type denoting that a notification was viewed by the user. * @hide */ + @SystemApi public static final int NOTIFICATION_SEEN = 10; /** - * An event type denoting a change in App Standby Bucket. - * @hide + * An event type denoting a change in App Standby Bucket. The new bucket can be + * retrieved by calling {@link #getStandbyBucket()}. + * + * @see UsageStatsManager#getAppStandbyBucket() */ public static final int STANDBY_BUCKET_CHANGED = 11; + /** + * An event type denoting that an app posted an interruptive notification. Visual and + * audible interruptions are included. + * @hide + */ + @SystemApi + public static final int NOTIFICATION_INTERRUPTION = 12; + + /** + * A Slice was pinned by the default launcher or the default assistant. + * @hide + */ + @SystemApi + public static final int SLICE_PINNED_PRIV = 13; + + /** + * A Slice was pinned by an app. + * @hide + */ + @SystemApi + public static final int SLICE_PINNED = 14; + + /** + * An event type denoting that the screen has gone in to an interactive state (turned + * on for full user interaction, not ambient display or other non-interactive state). + */ + public static final int SCREEN_INTERACTIVE = 15; + + /** + * An event type denoting that the screen has gone in to a non-interactive state + * (completely turned off or turned on only in a non-interactive state like ambient + * display). + */ + public static final int SCREEN_NON_INTERACTIVE = 16; + + /** + * An event type denoting that the screen's keyguard has been shown, whether or not + * the screen is off. + */ + public static final int KEYGUARD_SHOWN = 17; + + /** + * An event type denoting that the screen's keyguard has been hidden. This typically + * happens when the user unlocks their phone after turning it on. + */ + public static final int KEYGUARD_HIDDEN = 18; + /** @hide */ public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0; @@ -177,11 +229,20 @@ public final class UsageEvents implements Parcelable { public String[] mContentAnnotations; /** - * The app standby bucket assigned. + * The app standby bucket assigned and reason. Bucket is the high order 16 bits, reason + * is the low order 16 bits. * Only present for {@link #STANDBY_BUCKET_CHANGED} event types * {@hide} */ - public int mBucket; + public int mBucketAndReason; + + /** + * The id of the {@link android.app.NotificationChannel} to which an interruptive + * notification was posted. + * Only present for {@link #NOTIFICATION_INTERRUPTION} event types. + * {@hide} + */ + public String mNotificationChannelId; /** @hide */ @EventFlags @@ -202,7 +263,8 @@ public final class UsageEvents implements Parcelable { mContentType = orig.mContentType; mContentAnnotations = orig.mContentAnnotations; mFlags = orig.mFlags; - mBucket = orig.mBucket; + mBucketAndReason = orig.mBucketAndReason; + mNotificationChannelId = orig.mNotificationChannelId; } /** @@ -232,8 +294,11 @@ public final class UsageEvents implements Parcelable { /** * The event type. * - * See {@link #MOVE_TO_BACKGROUND} - * See {@link #MOVE_TO_FOREGROUND} + * @see #MOVE_TO_BACKGROUND + * @see #MOVE_TO_FOREGROUND + * @see #CONFIGURATION_CHANGE + * @see #USER_INTERACTION + * @see #STANDBY_BUCKET_CHANGED */ public int getEventType() { return mEventType; @@ -257,6 +322,38 @@ public final class UsageEvents implements Parcelable { return mShortcutId; } + /** + * Returns the standby bucket of the app, if the event is of type + * {@link #STANDBY_BUCKET_CHANGED}, otherwise returns 0. + * @return the standby bucket associated with the event. + * + */ + public int getStandbyBucket() { + return (mBucketAndReason & 0xFFFF0000) >>> 16; + } + + /** + * Returns the reason for the bucketing, if the event is of type + * {@link #STANDBY_BUCKET_CHANGED}, otherwise returns 0. Reason values include + * the main reason which is one of REASON_MAIN_*, OR'ed with REASON_SUB_*, if there + * are sub-reasons for the main reason, such as REASON_SUB_USAGE_* when the main reason + * is REASON_MAIN_USAGE. + * @hide + */ + public int getStandbyReason() { + return mBucketAndReason & 0x0000FFFF; + } + + /** + * Returns the ID of the {@link android.app.NotificationChannel} for this event if the + * event is of type {@link #NOTIFICATION_INTERRUPTION}, otherwise it returns null; + * @hide + */ + @SystemApi + public String getNotificationChannelId() { + return mNotificationChannelId; + } + /** @hide */ public Event getObfuscatedIfInstantApp() { if ((mFlags & FLAG_IS_PACKAGE_INSTANT_APP) == 0) { @@ -414,7 +511,10 @@ public final class UsageEvents implements Parcelable { p.writeStringArray(event.mContentAnnotations); break; case Event.STANDBY_BUCKET_CHANGED: - p.writeInt(event.mBucket); + p.writeInt(event.mBucketAndReason); + break; + case Event.NOTIFICATION_INTERRUPTION: + p.writeString(event.mNotificationChannelId); break; } } @@ -445,6 +545,7 @@ public final class UsageEvents implements Parcelable { eventOut.mAction = null; eventOut.mContentType = null; eventOut.mContentAnnotations = null; + eventOut.mNotificationChannelId = null; switch (eventOut.mEventType) { case Event.CONFIGURATION_CHANGE: @@ -460,7 +561,10 @@ public final class UsageEvents implements Parcelable { eventOut.mContentAnnotations = p.createStringArray(); break; case Event.STANDBY_BUCKET_CHANGED: - eventOut.mBucket = p.readInt(); + eventOut.mBucketAndReason = p.readInt(); + break; + case Event.NOTIFICATION_INTERRUPTION: + eventOut.mNotificationChannelId = p.readString(); break; } } diff --git a/android/app/usage/UsageStats.java b/android/app/usage/UsageStats.java index 7eef85c4..2b14841f 100644 --- a/android/app/usage/UsageStats.java +++ b/android/app/usage/UsageStats.java @@ -16,6 +16,7 @@ package android.app.usage; +import android.annotation.SystemApi; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -61,6 +62,11 @@ public final class UsageStats implements Parcelable { /** * {@hide} */ + public int mAppLaunchCount; + + /** + * {@hide} + */ public int mLastEvent; /** @@ -81,6 +87,7 @@ public final class UsageStats implements Parcelable { mLastTimeUsed = stats.mLastTimeUsed; mTotalTimeInForeground = stats.mTotalTimeInForeground; mLaunchCount = stats.mLaunchCount; + mAppLaunchCount = stats.mAppLaunchCount; mLastEvent = stats.mLastEvent; mChooserCounts = stats.mChooserCounts; } @@ -137,6 +144,16 @@ public final class UsageStats implements Parcelable { } /** + * Returns the number of times the app was launched as an activity from outside of the app. + * Excludes intra-app activity transitions. + * @hide + */ + @SystemApi + public int getAppLaunchCount() { + return mAppLaunchCount; + } + + /** * Add the statistics from the right {@link UsageStats} to the left. The package name for * both {@link UsageStats} objects must be the same. * @param right The {@link UsageStats} object to merge into this one. @@ -161,6 +178,7 @@ public final class UsageStats implements Parcelable { mEndTimeStamp = Math.max(mEndTimeStamp, right.mEndTimeStamp); mTotalTimeInForeground += right.mTotalTimeInForeground; mLaunchCount += right.mLaunchCount; + mAppLaunchCount += right.mAppLaunchCount; if (mChooserCounts == null) { mChooserCounts = right.mChooserCounts; } else if (right.mChooserCounts != null) { @@ -196,6 +214,7 @@ public final class UsageStats implements Parcelable { dest.writeLong(mLastTimeUsed); dest.writeLong(mTotalTimeInForeground); dest.writeInt(mLaunchCount); + dest.writeInt(mAppLaunchCount); dest.writeInt(mLastEvent); Bundle allCounts = new Bundle(); if (mChooserCounts != null) { @@ -224,6 +243,7 @@ public final class UsageStats implements Parcelable { stats.mLastTimeUsed = in.readLong(); stats.mTotalTimeInForeground = in.readLong(); stats.mLaunchCount = in.readInt(); + stats.mAppLaunchCount = in.readInt(); stats.mLastEvent = in.readInt(); Bundle allCounts = in.readBundle(); if (allCounts != null) { diff --git a/android/app/usage/UsageStatsManager.java b/android/app/usage/UsageStatsManager.java index edb6a74b..7fb97d3a 100644 --- a/android/app/usage/UsageStatsManager.java +++ b/android/app/usage/UsageStatsManager.java @@ -17,9 +17,11 @@ package android.app.usage; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.app.PendingIntent; import android.content.Context; import android.content.pm.ParceledListSlice; import android.os.RemoteException; @@ -28,9 +30,11 @@ import android.util.ArrayMap; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; /** * Provides access to device usage history and statistics. Usage data is aggregated into @@ -51,10 +55,13 @@ import java.util.Map; * </pre> * A request for data in the middle of a time interval will include that interval. * <p/> - * <b>NOTE:</b> This API requires the permission android.permission.PACKAGE_USAGE_STATS. - * However, declaring the permission implies intention to use the API and the user of the device - * still needs to grant permission through the Settings application. - * See {@link android.provider.Settings#ACTION_USAGE_ACCESS_SETTINGS} + * <b>NOTE:</b> Most methods on this API require the permission + * android.permission.PACKAGE_USAGE_STATS. However, declaring the permission implies intention to + * use the API and the user of the device still needs to grant permission through the Settings + * application. + * See {@link android.provider.Settings#ACTION_USAGE_ACCESS_SETTINGS}. + * Methods which only return the information for the calling package do not require this permission. + * E.g. {@link #getAppStandbyBucket()} and {@link #queryEventsForSelf(long, long)}. */ @SystemService(Context.USAGE_STATS_SERVICE) public final class UsageStatsManager { @@ -101,25 +108,35 @@ public final class UsageStatsManager { public static final int STANDBY_BUCKET_EXEMPTED = 5; /** - * The app was used very recently, currently in use or likely to be used very soon. + * The app was used very recently, currently in use or likely to be used very soon. Standby + * bucket values that are ≤ {@link #STANDBY_BUCKET_ACTIVE} will not be throttled by the + * system while they are in this bucket. Buckets > {@link #STANDBY_BUCKET_ACTIVE} will most + * likely be restricted in some way. For instance, jobs and alarms may be deferred. * @see #getAppStandbyBucket() */ public static final int STANDBY_BUCKET_ACTIVE = 10; /** - * The app was used recently and/or likely to be used in the next few hours. + * The app was used recently and/or likely to be used in the next few hours. Restrictions will + * apply to these apps, such as deferral of jobs and alarms. * @see #getAppStandbyBucket() */ public static final int STANDBY_BUCKET_WORKING_SET = 20; /** * The app was used in the last few days and/or likely to be used in the next few days. + * Restrictions will apply to these apps, such as deferral of jobs and alarms. The delays may be + * greater than for apps in higher buckets (lower bucket value). Bucket values > + * {@link #STANDBY_BUCKET_FREQUENT} may additionally have network access limited. * @see #getAppStandbyBucket() */ public static final int STANDBY_BUCKET_FREQUENT = 30; /** * The app has not be used for several days and/or is unlikely to be used for several days. + * Apps in this bucket will have the most restrictions, including network restrictions, except + * during certain short periods (at a minimum, once a day) when they are allowed to execute + * jobs, access the network, etc. * @see #getAppStandbyBucket() */ public static final int STANDBY_BUCKET_RARE = 40; @@ -131,24 +148,47 @@ public final class UsageStatsManager { @SystemApi public static final int STANDBY_BUCKET_NEVER = 50; - /** {@hide} Reason for bucketing -- default initial state */ - public static final String REASON_DEFAULT = "default"; - - /** {@hide} Reason for bucketing -- timeout */ - public static final String REASON_TIMEOUT = "timeout"; + /** @hide */ + public static final int REASON_MAIN_MASK = 0xFF00; + /** @hide */ + public static final int REASON_MAIN_DEFAULT = 0x0100; + /** @hide */ + public static final int REASON_MAIN_TIMEOUT = 0x0200; + /** @hide */ + public static final int REASON_MAIN_USAGE = 0x0300; + /** @hide */ + public static final int REASON_MAIN_FORCED = 0x0400; + /** @hide */ + public static final int REASON_MAIN_PREDICTED = 0x0500; - /** {@hide} Reason for bucketing -- usage */ - public static final String REASON_USAGE = "usage"; + /** @hide */ + public static final int REASON_SUB_MASK = 0x00FF; + /** @hide */ + public static final int REASON_SUB_USAGE_SYSTEM_INTERACTION = 0x0001; + /** @hide */ + public static final int REASON_SUB_USAGE_NOTIFICATION_SEEN = 0x0002; + /** @hide */ + public static final int REASON_SUB_USAGE_USER_INTERACTION = 0x0003; + /** @hide */ + public static final int REASON_SUB_USAGE_MOVE_TO_FOREGROUND = 0x0004; + /** @hide */ + public static final int REASON_SUB_USAGE_MOVE_TO_BACKGROUND = 0x0005; + /** @hide */ + public static final int REASON_SUB_USAGE_SYSTEM_UPDATE = 0x0006; + /** @hide */ + public static final int REASON_SUB_USAGE_ACTIVE_TIMEOUT = 0x0007; + /** @hide */ + public static final int REASON_SUB_USAGE_SYNC_ADAPTER = 0x0008; + /** @hide */ + public static final int REASON_SUB_USAGE_SLICE_PINNED = 0x0009; + /** @hide */ + public static final int REASON_SUB_USAGE_SLICE_PINNED_PRIV = 0x000A; + /** @hide */ + public static final int REASON_SUB_USAGE_EXEMPTED_SYNC_START = 0x000B; - /** {@hide} Reason for bucketing -- forced by user / shell command */ - public static final String REASON_FORCED = "forced"; + /** @hide */ + public static final int REASON_SUB_PREDICTED_RESTORED = 0x0001; - /** - * {@hide} - * Reason for bucketing -- predicted. This is a prefix and the UID of the bucketeer will - * be appended. - */ - public static final String REASON_PREDICTED = "predicted"; /** @hide */ @IntDef(flag = false, prefix = { "STANDBY_BUCKET_" }, value = { @@ -162,6 +202,31 @@ public final class UsageStatsManager { @Retention(RetentionPolicy.SOURCE) public @interface StandbyBuckets {} + /** + * Observer id of the registered observer for the group of packages that reached the usage + * time limit. Included as an extra in the PendingIntent that was registered. + * @hide + */ + @SystemApi + public static final String EXTRA_OBSERVER_ID = "android.app.usage.extra.OBSERVER_ID"; + + /** + * Original time limit in milliseconds specified by the registered observer for the group of + * packages that reached the usage time limit. Included as an extra in the PendingIntent that + * was registered. + * @hide + */ + @SystemApi + public static final String EXTRA_TIME_LIMIT = "android.app.usage.extra.TIME_LIMIT"; + + /** + * Actual usage time in milliseconds for the group of packages that reached the specified time + * limit. Included as an extra in the PendingIntent that was registered. + * @hide + */ + @SystemApi + public static final String EXTRA_TIME_USED = "android.app.usage.extra.TIME_USED"; + private static final UsageEvents sEmptyResults = new UsageEvents(); private final Context mContext; @@ -192,6 +257,8 @@ public final class UsageStatsManager { * 2014 - com.example.charlie * </pre> * + * <p> The caller must have {@link android.Manifest.permission#PACKAGE_USAGE_STATS} </p> + * * @param intervalType The time interval by which the stats are aggregated. * @param beginTime The inclusive beginning of the range of stats to include in the results. * @param endTime The exclusive end of the range of stats to include in the results. @@ -221,6 +288,7 @@ public final class UsageStatsManager { * Gets the hardware configurations the device was in for the given time range, aggregated by * the specified interval. The results are ordered as in * {@link #queryUsageStats(int, long, long)}. + * <p> The caller must have {@link android.Manifest.permission#PACKAGE_USAGE_STATS} </p> * * @param intervalType The time interval by which the stats are aggregated. * @param beginTime The inclusive beginning of the range of stats to include in the results. @@ -243,8 +311,49 @@ public final class UsageStatsManager { } /** + * Gets aggregated event stats for the given time range, aggregated by the specified interval. + * <p>The returned list will contain a {@link EventStats} object for each event type that + * is being aggregated and has data for an interval that is a subset of the time range given. + * + * <p>The current event types that will be aggregated here are:</p> + * <ul> + * <li>{@link UsageEvents.Event#SCREEN_INTERACTIVE}</li> + * <li>{@link UsageEvents.Event#SCREEN_NON_INTERACTIVE}</li> + * <li>{@link UsageEvents.Event#KEYGUARD_SHOWN}</li> + * <li>{@link UsageEvents.Event#KEYGUARD_HIDDEN}</li> + * </ul> + * + * <p> The caller must have {@link android.Manifest.permission#PACKAGE_USAGE_STATS} </p> + * + * @param intervalType The time interval by which the stats are aggregated. + * @param beginTime The inclusive beginning of the range of stats to include in the results. + * @param endTime The exclusive end of the range of stats to include in the results. + * @return A list of {@link EventStats} + * + * @see #INTERVAL_DAILY + * @see #INTERVAL_WEEKLY + * @see #INTERVAL_MONTHLY + * @see #INTERVAL_YEARLY + * @see #INTERVAL_BEST + */ + public List<EventStats> queryEventStats(int intervalType, long beginTime, long endTime) { + try { + @SuppressWarnings("unchecked") + ParceledListSlice<EventStats> slice = mService.queryEventStats(intervalType, beginTime, + endTime, mContext.getOpPackageName()); + if (slice != null) { + return slice.getList(); + } + } catch (RemoteException e) { + // fallthrough and return the empty list. + } + return Collections.emptyList(); + } + + /** * Query for events in the given time range. Events are only kept by the system for a few * days. + * <p> The caller must have {@link android.Manifest.permission#PACKAGE_USAGE_STATS} </p> * * @param beginTime The inclusive beginning of the range of events to include in the results. * @param endTime The exclusive end of the range of events to include in the results. @@ -264,9 +373,32 @@ public final class UsageStatsManager { } /** + * Like {@link #queryEvents(long, long)}, but only returns events for the calling package. + * + * @param beginTime The inclusive beginning of the range of events to include in the results. + * @param endTime The exclusive end of the range of events to include in the results. + * @return A {@link UsageEvents} object. + * + * @see #queryEvents(long, long) + */ + public UsageEvents queryEventsForSelf(long beginTime, long endTime) { + try { + final UsageEvents events = mService.queryEventsForPackage(beginTime, endTime, + mContext.getOpPackageName()); + if (events != null) { + return events; + } + } catch (RemoteException e) { + // fallthrough + } + return sEmptyResults; + } + + /** * A convenience method that queries for all stats in the given range (using the best interval * for that range), merges the resulting data, and keys it by package name. * See {@link #queryUsageStats(int, long, long)}. + * <p> The caller must have {@link android.Manifest.permission#PACKAGE_USAGE_STATS} </p> * * @param beginTime The inclusive beginning of the range of stats to include in the results. * @param endTime The exclusive end of the range of stats to include in the results. @@ -301,7 +433,7 @@ public final class UsageStatsManager { */ public boolean isAppInactive(String packageName) { try { - return mService.isAppInactive(packageName, UserHandle.myUserId()); + return mService.isAppInactive(packageName, mContext.getUserId()); } catch (RemoteException e) { // fall through and return default } @@ -313,7 +445,7 @@ public final class UsageStatsManager { */ public void setAppInactive(String packageName, boolean inactive) { try { - mService.setAppInactive(packageName, inactive, UserHandle.myUserId()); + mService.setAppInactive(packageName, inactive, mContext.getUserId()); } catch (RemoteException e) { // fall through } @@ -322,11 +454,19 @@ public final class UsageStatsManager { /** * Returns the current standby bucket of the calling app. The system determines the standby * state of the app based on app usage patterns. Standby buckets determine how much an app will - * be restricted from running background tasks such as jobs, alarms and certain PendingIntent - * callbacks. + * be restricted from running background tasks such as jobs and alarms. * <p>Restrictions increase progressively from {@link #STANDBY_BUCKET_ACTIVE} to * {@link #STANDBY_BUCKET_RARE}, with {@link #STANDBY_BUCKET_ACTIVE} being the least * restrictive. The battery level of the device might also affect the restrictions. + * <p>Apps in buckets ≤ {@link #STANDBY_BUCKET_ACTIVE} have no standby restrictions imposed. + * Apps in buckets > {@link #STANDBY_BUCKET_FREQUENT} may have network access restricted when + * running in the background. + * <p>The standby state of an app can change at any time either due to a user interaction or a + * system interaction or some algorithm determining that the app can be restricted for a period + * of time before the user has a need for it. + * <p>You can also query the recent history of standby bucket changes by calling + * {@link #queryEventsForSelf(long, long)} and searching for + * {@link UsageEvents.Event#STANDBY_BUCKET_CHANGED}. * * @return the current standby bucket of the calling app. One of STANDBY_BUCKET_* constants. */ @@ -388,8 +528,16 @@ public final class UsageStatsManager { @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public Map<String, Integer> getAppStandbyBuckets() { try { - return (Map<String, Integer>) mService.getAppStandbyBuckets( + final ParceledListSlice<AppStandbyInfo> slice = mService.getAppStandbyBuckets( mContext.getOpPackageName(), mContext.getUserId()); + final List<AppStandbyInfo> bucketList = slice.getList(); + final ArrayMap<String, Integer> bucketMap = new ArrayMap<>(); + final int n = bucketList.size(); + for (int i = 0; i < n; i++) { + final AppStandbyInfo bucketInfo = bucketList.get(i); + bucketMap.put(bucketInfo.mPackageName, bucketInfo.mStandbyBucket); + } + return bucketMap; } catch (RemoteException e) { } return Collections.EMPTY_MAP; @@ -404,13 +552,134 @@ public final class UsageStatsManager { @SystemApi @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE) public void setAppStandbyBuckets(Map<String, Integer> appBuckets) { + if (appBuckets == null) { + return; + } + final List<AppStandbyInfo> bucketInfoList = new ArrayList<>(appBuckets.size()); + for (Map.Entry<String, Integer> bucketEntry : appBuckets.entrySet()) { + bucketInfoList.add(new AppStandbyInfo(bucketEntry.getKey(), bucketEntry.getValue())); + } + final ParceledListSlice<AppStandbyInfo> slice = new ParceledListSlice<>(bucketInfoList); + try { + mService.setAppStandbyBuckets(slice, mContext.getUserId()); + } catch (RemoteException e) { + } + } + + /** + * @hide + * Register an app usage limit observer that receives a callback on the provided intent when + * the sum of usages of apps in the packages array exceeds the {@code timeLimit} specified. The + * observer will automatically be unregistered when the time limit is reached and the intent + * is delivered. Registering an {@code observerId} that was already registered will override + * the previous one. + * @param observerId A unique id associated with the group of apps to be monitored. There can + * be multiple groups with common packages and different time limits. + * @param packages The list of packages to observe for foreground activity time. Cannot be null + * and must include at least one package. + * @param timeLimit The total time the set of apps can be in the foreground before the + * callbackIntent is delivered. Must be greater than 0. + * @param timeUnit The unit for time specified in {@code timeLimit}. Cannot be null. + * @param callbackIntent The PendingIntent that will be dispatched when the time limit is + * exceeded by the group of apps. The delivered Intent will also contain + * the extras {@link #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and + * {@link #EXTRA_TIME_USED}. Cannot be null. + * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission or + * is not the profile owner of this user. + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) + public void registerAppUsageObserver(int observerId, @NonNull String[] packages, long timeLimit, + @NonNull TimeUnit timeUnit, @NonNull PendingIntent callbackIntent) { try { - mService.setAppStandbyBuckets(appBuckets, mContext.getUserId()); + mService.registerAppUsageObserver(observerId, packages, timeUnit.toMillis(timeLimit), + callbackIntent, mContext.getOpPackageName()); } catch (RemoteException e) { } } /** + * @hide + * Unregister the app usage observer specified by the {@code observerId}. This will only apply + * to any observer registered by this application. Unregistering an observer that was already + * unregistered or never registered will have no effect. + * @param observerId The id of the observer that was previously registered. + * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission or is + * not the profile owner of this user. + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) + public void unregisterAppUsageObserver(int observerId) { + try { + mService.unregisterAppUsageObserver(observerId, mContext.getOpPackageName()); + } catch (RemoteException e) { + } + } + + /** @hide */ + public static String reasonToString(int standbyReason) { + StringBuilder sb = new StringBuilder(); + switch (standbyReason & REASON_MAIN_MASK) { + case REASON_MAIN_DEFAULT: + sb.append("d"); + break; + case REASON_MAIN_FORCED: + sb.append("f"); + break; + case REASON_MAIN_PREDICTED: + sb.append("p"); + switch (standbyReason & REASON_SUB_MASK) { + case REASON_SUB_PREDICTED_RESTORED: + sb.append("-r"); + break; + } + break; + case REASON_MAIN_TIMEOUT: + sb.append("t"); + break; + case REASON_MAIN_USAGE: + sb.append("u"); + switch (standbyReason & REASON_SUB_MASK) { + case REASON_SUB_USAGE_SYSTEM_INTERACTION: + sb.append("-si"); + break; + case REASON_SUB_USAGE_NOTIFICATION_SEEN: + sb.append("-ns"); + break; + case REASON_SUB_USAGE_USER_INTERACTION: + sb.append("-ui"); + break; + case REASON_SUB_USAGE_MOVE_TO_FOREGROUND: + sb.append("-mf"); + break; + case REASON_SUB_USAGE_MOVE_TO_BACKGROUND: + sb.append("-mb"); + break; + case REASON_SUB_USAGE_SYSTEM_UPDATE: + sb.append("-su"); + break; + case REASON_SUB_USAGE_ACTIVE_TIMEOUT: + sb.append("-at"); + break; + case REASON_SUB_USAGE_SYNC_ADAPTER: + sb.append("-sa"); + break; + case REASON_SUB_USAGE_SLICE_PINNED: + sb.append("slp"); + break; + case REASON_SUB_USAGE_SLICE_PINNED_PRIV: + sb.append("slpp"); + break; + case REASON_SUB_USAGE_EXEMPTED_SYNC_START: + sb.append("es"); + break; + } + break; + } + return sb.toString(); + } + + /** * {@hide} * Temporarily whitelist the specified app for a short duration. This is to allow an app * receiving a high priority message to be able to access the network and acquire wakelocks diff --git a/android/app/usage/UsageStatsManagerInternal.java b/android/app/usage/UsageStatsManagerInternal.java index bd978e3d..b8628a4d 100644 --- a/android/app/usage/UsageStatsManagerInternal.java +++ b/android/app/usage/UsageStatsManagerInternal.java @@ -59,6 +59,16 @@ public abstract class UsageStatsManagerInternal { public abstract void reportConfigurationChange(Configuration config, @UserIdInt int userId); /** + * Reports that an application has posted an interruptive notification. + * + * @param packageName The package name of the app that posted the notification + * @param channelId The ID of the NotificationChannel to which the notification was posted + * @param userId The user in which the notification was posted + */ + public abstract void reportInterruptiveNotification(String packageName, String channelId, + @UserIdInt int userId); + + /** * Reports that an action equivalent to a ShortcutInfo is taken by the user. * * @param packageName The package name of the shortcut publisher @@ -139,13 +149,21 @@ public abstract class UsageStatsManagerInternal { /** Callback to inform listeners that the idle state has changed to a new bucket. */ public abstract void onAppIdleStateChanged(String packageName, @UserIdInt int userId, - boolean idle, int bucket); + boolean idle, int bucket, int reason); /** * Callback to inform listeners that the parole state has changed. This means apps are * allowed to do work even if they're idle or in a low bucket. */ public abstract void onParoleStateChanged(boolean isParoleOn); + + /** + * Optional callback to inform the listener that the app has transitioned into + * an active state due to user interaction. + */ + public void onUserInteractionStarted(String packageName, @UserIdInt int userId) { + // No-op by default + } } /** Backup/Restore API */ @@ -212,4 +230,25 @@ public abstract class UsageStatsManagerInternal { * indicated here before by a call to {@link #setLastJobRunTime(String, int, long)}. */ public abstract long getTimeSinceLastJobRun(String packageName, @UserIdInt int userId); + + /** + * Report a few data points about an app's job state at the current time. + * + * @param packageName the app whose job state is being described + * @param userId which user the app is associated with + * @param numDeferredJobs the number of pending jobs that were deferred + * due to bucketing policy + * @param timeSinceLastJobRun number of milliseconds since the last time one of + * this app's jobs was executed + */ + public abstract void reportAppJobState(String packageName, @UserIdInt int userId, + int numDeferredJobs, long timeSinceLastJobRun); + + /** + * Report a sync that was scheduled by an active app is about to be executed. + * + * @param packageName name of the package that owns the sync adapter. + * @param userId which user the app is associated with + */ + public abstract void reportExemptedSyncStart(String packageName, @UserIdInt int userId); } |