diff options
author | Jeff Davidson <jpd@google.com> | 2018-02-08 15:30:06 -0800 |
---|---|---|
committer | Jeff Davidson <jpd@google.com> | 2018-02-08 15:30:06 -0800 |
commit | a192cc2a132cb0ee8588e2df755563ec7008c179 (patch) | |
tree | 380e4db22df19c819bd37df34bf06e7568916a50 /android/app | |
parent | 98fe7819c6d14f4f464a5cac047f9e82dee5da58 (diff) | |
download | android-28-a192cc2a132cb0ee8588e2df755563ec7008c179.tar.gz |
Update fullsdk to 4575844
/google/data/ro/projects/android/fetch_artifact \
--bid 4575844 \
--target sdk_phone_x86_64-sdk \
sdk-repo-linux-sources-4575844.zip
Test: TreeHugger
Change-Id: I81e0eb157b4ac3b38408d0ef86f9d6286471f87a
Diffstat (limited to 'android/app')
53 files changed, 3401 insertions, 610 deletions
diff --git a/android/app/Activity.java b/android/app/Activity.java index aa099eb1..cd029c06 100644 --- a/android/app/Activity.java +++ b/android/app/Activity.java @@ -16,6 +16,8 @@ package android.app; +import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS; + import static java.lang.Character.MIN_VALUE; import android.annotation.CallSuper; @@ -98,6 +100,7 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; +import android.view.RemoteAnimationDefinition; import android.view.SearchEvent; import android.view.View; import android.view.View.OnCreateContextMenuListener; @@ -857,6 +860,7 @@ public class Activity extends ContextThemeWrapper private boolean mHasCurrentPermissionsRequest; private boolean mAutoFillResetNeeded; + private boolean mAutoFillIgnoreFirstResumePause; /** The last autofill id that was returned from {@link #getNextAutofillId()} */ private int mLastAutofillId = View.LAST_APP_AUTOFILL_ID; @@ -1253,10 +1257,7 @@ public class Activity extends ContextThemeWrapper getApplication().dispatchActivityStarted(this); if (mAutoFillResetNeeded) { - AutofillManager afm = getAutofillManager(); - if (afm != null) { - afm.onVisibleForAutofill(); - } + getAutofillManager().onVisibleForAutofill(); } } @@ -1320,6 +1321,20 @@ public class Activity extends ContextThemeWrapper if (DEBUG_LIFECYCLE) Slog.v(TAG, "onResume " + this); getApplication().dispatchActivityResumed(this); mActivityTransitionState.onResume(this, isTopOfTask()); + if (mAutoFillResetNeeded) { + if (!mAutoFillIgnoreFirstResumePause) { + View focus = getCurrentFocus(); + if (focus != null && focus.canNotifyAutofillEnterExitEvent()) { + // TODO: in Activity killed/recreated case, i.e. SessionLifecycleTest# + // testDatasetVisibleWhileAutofilledAppIsLifecycled: the View's initial + // window visibility after recreation is INVISIBLE in onResume() and next frame + // ViewRootImpl.performTraversals() changes window visibility to VISIBLE. + // So we cannot call View.notifyEnterOrExited() which will do nothing + // when View.isVisibleToUser() is false. + getAutofillManager().notifyViewEntered(focus); + } + } + } mCalled = true; } @@ -1681,6 +1696,19 @@ public class Activity extends ContextThemeWrapper protected void onPause() { if (DEBUG_LIFECYCLE) Slog.v(TAG, "onPause " + this); getApplication().dispatchActivityPaused(this); + if (mAutoFillResetNeeded) { + if (!mAutoFillIgnoreFirstResumePause) { + if (DEBUG_LIFECYCLE) Slog.v(TAG, "autofill notifyViewExited " + this); + View focus = getCurrentFocus(); + if (focus != null && focus.canNotifyAutofillEnterExitEvent()) { + getAutofillManager().notifyViewExited(focus); + } + } else { + // reset after first pause() + if (DEBUG_LIFECYCLE) Slog.v(TAG, "autofill got first pause " + this); + mAutoFillIgnoreFirstResumePause = false; + } + } mCalled = true; } @@ -1871,6 +1899,10 @@ public class Activity extends ContextThemeWrapper mTranslucentCallback = null; mCalled = true; + if (mAutoFillResetNeeded) { + getAutofillManager().onInvisibleForAutofill(); + } + if (isFinishing()) { if (mAutoFillResetNeeded) { getAutofillManager().onActivityFinished(); @@ -2587,6 +2619,7 @@ public class Activity extends ContextThemeWrapper * @param id the ID to search for * @return a view with given ID if found, or {@code null} otherwise * @see View#findViewById(int) + * @see Activity#requireViewById(int) */ @Nullable public <T extends View> T findViewById(@IdRes int id) { @@ -2594,6 +2627,30 @@ public class Activity extends ContextThemeWrapper } /** + * Finds a view that was identified by the {@code android:id} XML attribute that was processed + * in {@link #onCreate}, or throws an IllegalArgumentException if the ID is invalid, or there is + * no matching view in the hierarchy. + * <p> + * <strong>Note:</strong> In most cases -- depending on compiler support -- + * the resulting view is automatically cast to the target class type. If + * the target class type is unconstrained, an explicit cast may be + * necessary. + * + * @param id the ID to search for + * @return a view with given ID + * @see View#requireViewById(int) + * @see Activity#findViewById(int) + */ + @NonNull + public final <T extends View> T requireViewById(@IdRes int id) { + T view = findViewById(id); + if (view == null) { + throw new IllegalArgumentException("ID does not reference a View inside this Activity"); + } + return view; + } + + /** * Retrieve a reference to this activity's ActionBar. * * @return The Activity's ActionBar, or null if it does not have one. @@ -4640,6 +4697,7 @@ 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 @@ -4648,7 +4706,7 @@ public class Activity extends ContextThemeWrapper * @hide */ public void startActivityAsCaller(Intent intent, @Nullable Bundle options, - boolean ignoreTargetSecurity, int userId) { + IBinder permissionToken, boolean ignoreTargetSecurity, int userId) { if (mParent != null) { throw new RuntimeException("Can't be called from a child"); } @@ -4656,7 +4714,7 @@ public class Activity extends ContextThemeWrapper Instrumentation.ActivityResult ar = mInstrumentation.execStartActivityAsCaller( this, mMainThread.getApplicationThread(), mToken, this, - intent, -1, options, ignoreTargetSecurity, userId); + intent, -1, options, permissionToken, ignoreTargetSecurity, userId); if (ar != null) { mMainThread.sendActivityResult( mToken, mEmbeddedID, -1, ar.getResultCode(), @@ -6266,7 +6324,7 @@ public class Activity extends ContextThemeWrapper mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix); - final AutofillManager afm = getAutofillManager(); + final AutofillManager afm = mAutofillManager; if (afm != null) { afm.dump(prefix, writer); } else { @@ -6616,7 +6674,6 @@ public class Activity extends ContextThemeWrapper * to run as a {@link android.service.vr.VrListenerService} is not installed, or has * not been enabled in user settings. * - * @see android.content.pm.PackageManager#FEATURE_VR_MODE * @see android.content.pm.PackageManager#FEATURE_VR_MODE_HIGH_PERFORMANCE * @see android.service.vr.VrListenerService * @see android.provider.Settings#ACTION_VR_LISTENER_SETTINGS @@ -7120,13 +7177,23 @@ public class Activity extends ContextThemeWrapper } } - final void performResume() { + final void performResume(boolean followedByPause) { performRestart(true /* start */); mFragments.execPendingActions(); mLastNonConfigurationInstances = null; + if (mAutoFillResetNeeded) { + // When Activity is destroyed in paused state, and relaunch activity, there will be + // extra onResume and onPause event, ignore the first onResume and onPause. + // see ActivityThread.handleRelaunchActivity() + mAutoFillIgnoreFirstResumePause = followedByPause; + if (mAutoFillIgnoreFirstResumePause && DEBUG_LIFECYCLE) { + Slog.v(TAG, "autofill will ignore first pause when relaunching " + this); + } + } + mCalled = false; // mResumed is set by the instrumentation mInstrumentation.callActivityOnResume(this); @@ -7311,7 +7378,7 @@ public class Activity extends ContextThemeWrapper } } else if (who.startsWith(AUTO_FILL_AUTH_WHO_PREFIX)) { Intent resultData = (resultCode == Activity.RESULT_OK) ? data : null; - getAutofillManager().onAuthenticationResult(requestCode, resultData); + getAutofillManager().onAuthenticationResult(requestCode, resultData, getCurrentFocus()); } else { Fragment frag = mFragments.findFragmentByWho(who); if (frag != null) { @@ -7585,6 +7652,12 @@ public class Activity extends ContextThemeWrapper return !mStopped; } + /** @hide */ + @Override + public boolean isDisablingEnterExitEventForAutofill() { + return mAutoFillIgnoreFirstResumePause || !mResumed; + } + /** * If set to true, this indicates to the system that it should never take a * screenshot of the activity to be used as a representation while it is not in a started state. @@ -7659,6 +7732,22 @@ public class Activity extends ContextThemeWrapper } } + /** + * Registers remote animations per transition type for this activity. + * + * @param definition The remote animation definition that defines which transition whould run + * which remote animation. + * @hide + */ + @RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS) + public void registerRemoteAnimations(RemoteAnimationDefinition definition) { + try { + ActivityManager.getService().registerRemoteAnimations(mToken, definition); + } catch (RemoteException e) { + Log.e(TAG, "Failed to call registerRemoteAnimations", e); + } + } + class HostCallbacks extends FragmentHostCallback<Activity> { public HostCallbacks() { super(Activity.this /*activity*/); diff --git a/android/app/ActivityManager.java b/android/app/ActivityManager.java index 1adae7a8..80350584 100644 --- a/android/app/ActivityManager.java +++ b/android/app/ActivityManager.java @@ -60,6 +60,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.os.UserHandle; +import android.os.WorkSource; import android.text.TextUtils; import android.util.ArrayMap; import android.util.DisplayMetrics; @@ -180,7 +181,8 @@ public class ActivityManager { BUGREPORT_OPTION_INTERACTIVE, BUGREPORT_OPTION_REMOTE, BUGREPORT_OPTION_WEAR, - BUGREPORT_OPTION_TELEPHONY + BUGREPORT_OPTION_TELEPHONY, + BUGREPORT_OPTION_WIFI }) public @interface BugreportMode {} /** @@ -215,6 +217,12 @@ public class ActivityManager { public static final int BUGREPORT_OPTION_TELEPHONY = 4; /** + * Takes a lightweight bugreport that only includes a few sections related to Wifi. + * @hide + */ + public static final int BUGREPORT_OPTION_WIFI = 5; + + /** * <a href="{@docRoot}guide/topics/manifest/meta-data-element.html">{@code * <meta-data>}</a> name for a 'home' Activity that declares a package that is to be * uninstalled in lieu of the declaring one. The package named here must be @@ -442,6 +450,31 @@ 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; @@ -484,11 +517,11 @@ public class ActivityManager { * all activities that are visible to the user. */ public static final int PROCESS_STATE_TOP = 2; - /** @hide Process is hosting a foreground service due to a system binding. */ - public static final int PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 3; - /** @hide Process is hosting a foreground service. */ - public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4; + public static final int PROCESS_STATE_FOREGROUND_SERVICE = 3; + + /** @hide Process is hosting a foreground service due to a system binding. */ + public static final int PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 4; /** @hide Process is important to the user, and something they are aware of. */ public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 5; @@ -3085,11 +3118,11 @@ public class ActivityManager { } else if (importance >= IMPORTANCE_VISIBLE) { return PROCESS_STATE_IMPORTANT_FOREGROUND; } else if (importance >= IMPORTANCE_TOP_SLEEPING_PRE_28) { - return PROCESS_STATE_FOREGROUND_SERVICE; + return PROCESS_STATE_IMPORTANT_FOREGROUND; } else if (importance >= IMPORTANCE_FOREGROUND_SERVICE) { return PROCESS_STATE_FOREGROUND_SERVICE; } else { - return PROCESS_STATE_BOUND_FOREGROUND_SERVICE; + return PROCESS_STATE_TOP; } } @@ -3911,10 +3944,10 @@ public class ActivityManager { /** * @hide */ - public static void noteWakeupAlarm(PendingIntent ps, int sourceUid, String sourcePkg, - String tag) { + public static void noteWakeupAlarm(PendingIntent ps, WorkSource workSource, int sourceUid, + String sourcePkg, String tag) { try { - getService().noteWakeupAlarm((ps != null) ? ps.getTarget() : null, + getService().noteWakeupAlarm((ps != null) ? ps.getTarget() : null, workSource, sourceUid, sourcePkg, tag); } catch (RemoteException ex) { } @@ -3923,19 +3956,24 @@ public class ActivityManager { /** * @hide */ - public static void noteAlarmStart(PendingIntent ps, int sourceUid, String tag) { + public static void noteAlarmStart(PendingIntent ps, WorkSource workSource, int sourceUid, + String tag) { try { - getService().noteAlarmStart((ps != null) ? ps.getTarget() : null, sourceUid, tag); + getService().noteAlarmStart((ps != null) ? ps.getTarget() : null, workSource, + sourceUid, tag); } catch (RemoteException ex) { } } + /** * @hide */ - public static void noteAlarmFinish(PendingIntent ps, int sourceUid, String tag) { + public static void noteAlarmFinish(PendingIntent ps, WorkSource workSource, int sourceUid, + String tag) { try { - getService().noteAlarmFinish((ps != null) ? ps.getTarget() : null, sourceUid, tag); + getService().noteAlarmFinish((ps != null) ? ps.getTarget() : null, workSource, + sourceUid, tag); } catch (RemoteException ex) { } } diff --git a/android/app/ActivityManagerInternal.java b/android/app/ActivityManagerInternal.java index 60a5a110..da9f7285 100644 --- a/android/app/ActivityManagerInternal.java +++ b/android/app/ActivityManagerInternal.java @@ -319,4 +319,29 @@ public abstract class ActivityManagerInternal { } public abstract void registerScreenObserver(ScreenObserver observer); + + /** + * Returns if more users can be started without stopping currently running users. + */ + public abstract boolean canStartMoreUsers(); + + /** + * Sets the user switcher message for switching from {@link android.os.UserHandle#SYSTEM}. + */ + public abstract void setSwitchingFromSystemUserMessage(String switchingFromSystemUserMessage); + + /** + * Sets the user switcher message for switching to {@link android.os.UserHandle#SYSTEM}. + */ + public abstract void setSwitchingToSystemUserMessage(String switchingToSystemUserMessage); + + /** + * Returns maximum number of users that can run simultaneously. + */ + public abstract int getMaxRunningUsers(); + + /** + * Returns is the caller has the same uid as the Recents component + */ + public abstract boolean isCallerRecents(int callingUid); } diff --git a/android/app/ActivityManagerNative.java b/android/app/ActivityManagerNative.java index c09403c2..4c558f37 100644 --- a/android/app/ActivityManagerNative.java +++ b/android/app/ActivityManagerNative.java @@ -75,20 +75,20 @@ public abstract class ActivityManagerNative { */ static public void noteWakeupAlarm(PendingIntent ps, int sourceUid, String sourcePkg, String tag) { - ActivityManager.noteWakeupAlarm(ps, sourceUid, sourcePkg, tag); + ActivityManager.noteWakeupAlarm(ps, null, sourceUid, sourcePkg, tag); } /** * @deprecated use ActivityManager.noteAlarmStart instead. */ static public void noteAlarmStart(PendingIntent ps, int sourceUid, String tag) { - ActivityManager.noteAlarmStart(ps, sourceUid, tag); + ActivityManager.noteAlarmStart(ps, null, sourceUid, tag); } /** * @deprecated use ActivityManager.noteAlarmFinish instead. */ static public void noteAlarmFinish(PendingIntent ps, int sourceUid, String tag) { - ActivityManager.noteAlarmFinish(ps, sourceUid, tag); + ActivityManager.noteAlarmFinish(ps, null, sourceUid, tag); } } diff --git a/android/app/ActivityOptions.java b/android/app/ActivityOptions.java index e61c5b7c..fee58274 100644 --- a/android/app/ActivityOptions.java +++ b/android/app/ActivityOptions.java @@ -16,12 +16,14 @@ package android.app; +import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS; import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.Display.INVALID_DISPLAY; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.TestApi; import android.content.ComponentName; import android.content.Context; @@ -44,6 +46,7 @@ import android.util.Pair; import android.util.Slog; import android.view.AppTransitionAnimationSpec; import android.view.IAppTransitionAnimationSpecsFuture; +import android.view.RemoteAnimationAdapter; import android.view.View; import android.view.ViewGroup; import android.view.Window; @@ -204,6 +207,12 @@ public class ActivityOptions { "android.activity.taskOverlayCanResume"; /** + * See {@link #setAvoidMoveToFront()}. + * @hide + */ + private static final String KEY_AVOID_MOVE_TO_FRONT = "android.activity.avoidMoveToFront"; + + /** * Where the split-screen-primary stack should be positioned. * @hide */ @@ -241,6 +250,8 @@ public class ActivityOptions { private static final String KEY_INSTANT_APP_VERIFICATION_BUNDLE = "android:instantapps.installerbundle"; private static final String KEY_SPECS_FUTURE = "android:activity.specsFuture"; + private static final String KEY_REMOTE_ANIMATION_ADAPTER + = "android:activity.remoteAnimationAdapter"; /** @hide */ public static final int ANIM_NONE = 0; @@ -268,6 +279,8 @@ public class ActivityOptions { public static final int ANIM_CLIP_REVEAL = 11; /** @hide */ public static final int ANIM_OPEN_CROSS_PROFILE_APPS = 12; + /** @hide */ + public static final int ANIM_REMOTE_ANIMATION = 13; private String mPackageName; private Rect mLaunchBounds; @@ -300,10 +313,12 @@ public class ActivityOptions { private boolean mDisallowEnterPictureInPictureWhileLaunching; private boolean mTaskOverlay; private boolean mTaskOverlayCanResume; + private boolean mAvoidMoveToFront; private AppTransitionAnimationSpec mAnimSpecs[]; private int mRotationAnimationHint = -1; private Bundle mAppVerificationBundle; private IAppTransitionAnimationSpecsFuture mSpecsFuture; + private RemoteAnimationAdapter mRemoteAnimationAdapter; /** * Create an ActivityOptions specifying a custom animation to run when @@ -826,6 +841,20 @@ public class ActivityOptions { return opts; } + /** + * Create an {@link ActivityOptions} instance that lets the application control the entire + * animation using a {@link RemoteAnimationAdapter}. + * @hide + */ + @RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS) + public static ActivityOptions makeRemoteAnimation( + RemoteAnimationAdapter remoteAnimationAdapter) { + final ActivityOptions opts = new ActivityOptions(); + opts.mRemoteAnimationAdapter = remoteAnimationAdapter; + opts.mAnimationType = ANIM_REMOTE_ANIMATION; + return opts; + } + /** @hide */ public boolean getLaunchTaskBehind() { return mAnimationType == ANIM_LAUNCH_TASK_BEHIND; @@ -901,6 +930,7 @@ public class ActivityOptions { mLaunchTaskId = opts.getInt(KEY_LAUNCH_TASK_ID, -1); mTaskOverlay = opts.getBoolean(KEY_TASK_OVERLAY, false); mTaskOverlayCanResume = opts.getBoolean(KEY_TASK_OVERLAY_CAN_RESUME, false); + mAvoidMoveToFront = opts.getBoolean(KEY_AVOID_MOVE_TO_FRONT, false); mSplitScreenCreateMode = opts.getInt(KEY_SPLIT_SCREEN_CREATE_MODE, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT); mDisallowEnterPictureInPictureWhileLaunching = opts.getBoolean( @@ -922,6 +952,7 @@ public class ActivityOptions { mSpecsFuture = IAppTransitionAnimationSpecsFuture.Stub.asInterface(opts.getBinder( KEY_SPECS_FUTURE)); } + mRemoteAnimationAdapter = opts.getParcelable(KEY_REMOTE_ANIMATION_ADAPTER); } /** @@ -1070,6 +1101,11 @@ public class ActivityOptions { } /** @hide */ + public RemoteAnimationAdapter getRemoteAnimationAdapter() { + return mRemoteAnimationAdapter; + } + + /** @hide */ public static ActivityOptions fromBundle(Bundle bOptions) { return bOptions != null ? new ActivityOptions(bOptions) : null; } @@ -1211,6 +1247,25 @@ public class ActivityOptions { return mTaskOverlayCanResume; } + /** + * Sets whether the activity launched should not cause the activity stack it is contained in to + * be moved to the front as a part of launching. + * + * @hide + */ + public void setAvoidMoveToFront() { + mAvoidMoveToFront = true; + } + + /** + * @return whether the activity launch should prevent moving the associated activity stack to + * the front. + * @hide + */ + public boolean getAvoidMoveToFront() { + return mAvoidMoveToFront; + } + /** @hide */ public int getSplitScreenCreateMode() { return mSplitScreenCreateMode; @@ -1309,6 +1364,7 @@ public class ActivityOptions { mAnimSpecs = otherOptions.mAnimSpecs; mAnimationFinishedListener = otherOptions.mAnimationFinishedListener; mSpecsFuture = otherOptions.mSpecsFuture; + mRemoteAnimationAdapter = otherOptions.mRemoteAnimationAdapter; } /** @@ -1387,6 +1443,7 @@ public class ActivityOptions { b.putInt(KEY_LAUNCH_TASK_ID, mLaunchTaskId); b.putBoolean(KEY_TASK_OVERLAY, mTaskOverlay); b.putBoolean(KEY_TASK_OVERLAY_CAN_RESUME, mTaskOverlayCanResume); + b.putBoolean(KEY_AVOID_MOVE_TO_FRONT, mAvoidMoveToFront); b.putInt(KEY_SPLIT_SCREEN_CREATE_MODE, mSplitScreenCreateMode); b.putBoolean(KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING, mDisallowEnterPictureInPictureWhileLaunching); @@ -1403,7 +1460,9 @@ public class ActivityOptions { if (mAppVerificationBundle != null) { b.putBundle(KEY_INSTANT_APP_VERIFICATION_BUNDLE, mAppVerificationBundle); } - + if (mRemoteAnimationAdapter != null) { + b.putParcelable(KEY_REMOTE_ANIMATION_ADAPTER, mRemoteAnimationAdapter); + } return b; } diff --git a/android/app/ActivityThread.java b/android/app/ActivityThread.java index aaa6bf03..934b0f3c 100644 --- a/android/app/ActivityThread.java +++ b/android/app/ActivityThread.java @@ -166,6 +166,7 @@ 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.List; @@ -220,6 +221,9 @@ public final class ActivityThread extends ClientTransactionHandler { // Whether to invoke an activity callback after delivering new configuration. 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; + /** * Denotes an invalid sequence number corresponding to a process state change. */ @@ -252,6 +256,8 @@ 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<>(); + // List of new activities (via ActivityRecord.nextIdle) that should // be reported when next we idle. ActivityClientRecord mNewActivities = null; @@ -1752,9 +1758,11 @@ public final class ActivityThread extends ClientTransactionHandler { handleLocalVoiceInteractionStarted((IBinder) ((SomeArgs) msg.obj).arg1, (IVoiceInteractor) ((SomeArgs) msg.obj).arg2); break; - case ATTACH_AGENT: - handleAttachAgent((String) msg.obj); + case ATTACH_AGENT: { + Application app = getApplication(); + handleAttachAgent((String) msg.obj, app != null ? app.mLoadedApk : null); break; + } case APPLICATION_INFO_CHANGED: mUpdatingSystemConfig = true; try { @@ -1770,7 +1778,12 @@ public final class ActivityThread extends ClientTransactionHandler { case EXECUTE_TRANSACTION: final ClientTransaction transaction = (ClientTransaction) msg.obj; mTransactionExecutor.execute(transaction); - transaction.recycle(); + if (isSystem()) { + // Client transactions inside system process are recycled on the client side + // instead of ClientLifecycleManager to avoid being cleared before this + // message is handled. + transaction.recycle(); + } break; } Object obj = msg.obj; @@ -2161,6 +2174,18 @@ public final class ActivityThread extends ClientTransactionHandler { pw.println(String.format(format, objs)); } + @Override + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "mActivities:"); + + for (ArrayMap.Entry<IBinder, ActivityClientRecord> entry : mActivities.entrySet()) { + pw.println(prefix + " [token:" + entry.getKey().hashCode() + " record:" + + entry.getValue().toString() + "]"); + } + + pw.println(prefix + "mRecentTokens:" + mRecentTokens); + } + public static void dumpMemInfoTable(PrintWriter pw, Debug.MemoryInfo memInfo, boolean checkin, boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly, int pid, String processName, @@ -2845,6 +2870,11 @@ 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; @@ -3066,7 +3096,7 @@ public final class ActivityThread extends ClientTransactionHandler { checkAndBlockForNetworkAccess(); deliverNewIntents(r, intents); if (resumed) { - r.activity.performResume(); + r.activity.performResume(false); r.activity.mTemporaryPause = false; } @@ -3241,11 +3271,23 @@ public final class ActivityThread extends ClientTransactionHandler { } } - static final void handleAttachAgent(String agent) { + private static boolean attemptAttachAgent(String agent, ClassLoader classLoader) { try { - VMDebug.attachAgent(agent); + VMDebug.attachAgent(agent, classLoader); + return true; } catch (IOException e) { - Slog.e(TAG, "Attaching agent failed: " + agent); + Slog.e(TAG, "Attaching agent with " + classLoader + " failed: " + agent); + return false; + } + } + + static void handleAttachAgent(String agent, LoadedApk loadedApk) { + ClassLoader classLoader = loadedApk != null ? loadedApk.getClassLoader() : null; + if (attemptAttachAgent(agent, classLoader)) { + return; + } + if (classLoader != null) { + attemptAttachAgent(agent, null); } } @@ -3676,7 +3718,7 @@ public final class ActivityThread extends ClientTransactionHandler { deliverResults(r, r.pendingResults); r.pendingResults = null; } - r.activity.performResume(); + r.activity.performResume(r.startsNotResumed); synchronized (mResourcesManager) { // If there is a pending local relaunch that was requested when the activity was @@ -4395,7 +4437,7 @@ public final class ActivityThread extends ClientTransactionHandler { checkAndBlockForNetworkAccess(); deliverResults(r, results); if (resumed) { - r.activity.performResume(); + r.activity.performResume(false); r.activity.mTemporaryPause = false; } } @@ -5537,12 +5579,16 @@ public final class ActivityThread extends ClientTransactionHandler { mCompatConfiguration = new Configuration(data.config); mProfiler = new Profiler(); + String agent = null; if (data.initProfilerInfo != null) { mProfiler.profileFile = data.initProfilerInfo.profileFile; mProfiler.profileFd = data.initProfilerInfo.profileFd; mProfiler.samplingInterval = data.initProfilerInfo.samplingInterval; mProfiler.autoStopProfiler = data.initProfilerInfo.autoStopProfiler; mProfiler.streamingOutput = data.initProfilerInfo.streamingOutput; + if (data.initProfilerInfo.attachAgentDuringBind) { + agent = data.initProfilerInfo.agent; + } } // send up app name; do this *before* waiting for debugger @@ -5592,6 +5638,10 @@ public final class ActivityThread extends ClientTransactionHandler { data.loadedApk = getLoadedApkNoCheck(data.appInfo, data.compatInfo); + if (agent != null) { + handleAttachAgent(agent, data.loadedApk); + } + /** * Switch this process to density compatibility mode if needed. */ diff --git a/android/app/ActivityView.java b/android/app/ActivityView.java index 9f1e9839..5d0143a5 100644 --- a/android/app/ActivityView.java +++ b/android/app/ActivityView.java @@ -17,6 +17,7 @@ package android.app; import android.annotation.NonNull; +import android.app.ActivityManager.StackInfo; import android.content.Context; import android.content.Intent; import android.hardware.display.DisplayManager; @@ -34,9 +35,12 @@ import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.ViewGroup; import android.view.WindowManager; +import android.view.WindowManagerGlobal; import dalvik.system.CloseGuard; +import java.util.List; + /** * Activity container that allows launching activities into itself and does input forwarding. * <p>Creation of this view is only allowed to callers who have @@ -57,7 +61,12 @@ public class ActivityView extends ViewGroup { private final SurfaceCallback mSurfaceCallback; private StateCallback mActivityViewCallback; + private IActivityManager mActivityManager; private IInputForwarder mInputForwarder; + // Temp container to store view coordinates on screen. + private final int[] mLocationOnScreen = new int[2]; + + private TaskStackListener mTaskStackListener; private final CloseGuard mGuard = CloseGuard.get(); private boolean mOpened; // Protected by mGuard. @@ -73,6 +82,7 @@ public class ActivityView extends ViewGroup { public ActivityView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); + mActivityManager = ActivityManager.getService(); mSurfaceView = new SurfaceView(context); mSurfaceCallback = new SurfaceCallback(); mSurfaceView.getHolder().addCallback(mSurfaceCallback); @@ -198,11 +208,30 @@ public class ActivityView extends ViewGroup { performRelease(); } + /** + * Triggers an update of {@link ActivityView}'s location on screen to properly set touch exclude + * regions and avoid focus switches by touches on this view. + */ + public void onLocationChanged() { + updateLocation(); + } + @Override public void onLayout(boolean changed, int l, int t, int r, int b) { mSurfaceView.layout(0 /* left */, 0 /* top */, r - l /* right */, b - t /* bottom */); } + /** Send current location and size to the WM to set tap exclude region for this view. */ + private void updateLocation() { + try { + getLocationOnScreen(mLocationOnScreen); + WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(), + mLocationOnScreen[0], mLocationOnScreen[1], getWidth(), getHeight()); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + @Override public boolean onTouchEvent(MotionEvent event) { return injectInputEvent(event) || super.onTouchEvent(event); @@ -241,6 +270,7 @@ public class ActivityView extends ViewGroup { } else { mVirtualDisplay.setSurface(surfaceHolder.getSurface()); } + updateLocation(); } @Override @@ -248,6 +278,7 @@ public class ActivityView extends ViewGroup { if (mVirtualDisplay != null) { mVirtualDisplay.resize(width, height, getBaseDisplayDensity()); } + updateLocation(); } @Override @@ -257,6 +288,7 @@ public class ActivityView extends ViewGroup { if (mVirtualDisplay != null) { mVirtualDisplay.setSurface(null); } + cleanTapExcludeRegion(); } } @@ -278,6 +310,12 @@ public class ActivityView extends ViewGroup { mInputForwarder = InputManager.getInstance().createInputForwarder( mVirtualDisplay.getDisplay().getDisplayId()); + mTaskStackListener = new TaskBackgroundChangeListener(); + try { + mActivityManager.registerTaskStackListener(mTaskStackListener); + } catch (RemoteException e) { + Log.e(TAG, "Failed to register task stack listener", e); + } } private void performRelease() { @@ -290,6 +328,16 @@ public class ActivityView extends ViewGroup { if (mInputForwarder != null) { mInputForwarder = null; } + cleanTapExcludeRegion(); + + if (mTaskStackListener != null) { + try { + mActivityManager.unregisterTaskStackListener(mTaskStackListener); + } catch (RemoteException e) { + Log.e(TAG, "Failed to unregister task stack listener", e); + } + mTaskStackListener = null; + } final boolean displayReleased; if (mVirtualDisplay != null) { @@ -313,6 +361,17 @@ public class ActivityView extends ViewGroup { mOpened = false; } + /** Report to server that tap exclude region on hosting display should be cleared. */ + private void cleanTapExcludeRegion() { + // Update tap exclude region with an empty rect to clean the state on server. + try { + WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(), + 0 /* left */, 0 /* top */, 0 /* width */, 0 /* height */); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + /** Get density of the hosting display. */ private int getBaseDisplayDensity() { final WindowManager wm = mContext.getSystemService(WindowManager.class); @@ -332,4 +391,42 @@ public class ActivityView extends ViewGroup { super.finalize(); } } + + /** + * A task change listener that detects background color change of the topmost stack on our + * virtual display and updates the background of the surface view. This background will be shown + * when surface view is resized, but the app hasn't drawn its content in new size yet. + */ + private class TaskBackgroundChangeListener extends TaskStackListener { + + @Override + public void onTaskDescriptionChanged(int taskId, ActivityManager.TaskDescription td) + throws RemoteException { + if (mVirtualDisplay == null) { + return; + } + + // Find the topmost task on our virtual display - it will define the background + // color of the surface view during resizing. + final int displayId = mVirtualDisplay.getDisplay().getDisplayId(); + final List<StackInfo> stackInfoList = mActivityManager.getAllStackInfos(); + + // Iterate through stacks from top to bottom. + final int stackCount = stackInfoList.size(); + for (int i = 0; i < stackCount; i++) { + final StackInfo stackInfo = stackInfoList.get(i); + // Only look for stacks on our virtual display. + if (stackInfo.displayId != displayId) { + continue; + } + // Found the topmost stack on target display. Now check if the topmost task's + // description changed. + if (taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) { + mSurfaceView.setResizeBackgroundColor(td.getBackgroundColor()); + } + break; + } + } + } + } diff --git a/android/app/AppOpsManager.java b/android/app/AppOpsManager.java index ea22d332..e923fb21 100644 --- a/android/app/AppOpsManager.java +++ b/android/app/AppOpsManager.java @@ -20,6 +20,7 @@ import android.Manifest; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; import android.app.usage.UsageStatsManager; import android.content.Context; import android.media.AudioAttributes.AttributeUsage; @@ -37,6 +38,7 @@ import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsService; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -106,6 +108,7 @@ public class AppOpsManager { // when adding one of these: // - increment _NUM_OP + // - define an OPSTR_* constant (marked as @SystemApi) // - add rows to sOpToSwitch, sOpToString, sOpNames, sOpToPerms, sOpDefault // - add descriptive strings to Settings/res/values/arrays.xml // - add the op to the appropriate template in AppOpsState.OpsTemplate (settings app) @@ -260,8 +263,10 @@ public class AppOpsManager { public static final int OP_REQUEST_DELETE_PACKAGES = 72; /** @hide Bind an accessibility service. */ 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 */ - public static final int _NUM_OP = 74; + public static final int _NUM_OP = 75; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -278,7 +283,7 @@ public class AppOpsManager { public static final String OPSTR_GET_USAGE_STATS = "android:get_usage_stats"; /** Activate a VPN connection without user intervention. @hide */ - @SystemApi + @SystemApi @TestApi public static final String OPSTR_ACTIVATE_VPN = "android:activate_vpn"; /** Allows an application to read the user's contacts data. */ @@ -360,6 +365,7 @@ public class AppOpsManager { public static final String OPSTR_WRITE_SETTINGS = "android:write_settings"; /** @hide Get device accounts. */ + @SystemApi @TestApi public static final String OPSTR_GET_ACCOUNTS = "android:get_accounts"; public static final String OPSTR_READ_PHONE_NUMBERS @@ -368,11 +374,133 @@ public class AppOpsManager { public static final String OPSTR_PICTURE_IN_PICTURE = "android:picture_in_picture"; /** @hide */ + @SystemApi @TestApi public static final String OPSTR_INSTANT_APP_START_FOREGROUND = "android:instant_app_start_foreground"; /** Answer incoming phone calls */ public static final String OPSTR_ANSWER_PHONE_CALLS = "android:answer_phone_calls"; + /** + * Accept call handover + * @hide + */ + @SystemApi @TestApi + public static final String OPSTR_ACCEPT_HANDOVER + = "android:accept_handover"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_GPS = "android:gps"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_VIBRATE = "android:vibrate"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_WIFI_SCAN = "android:wifi_scan"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_POST_NOTIFICATION = "android:post_notification"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_NEIGHBORING_CELLS = "android:neighboring_cells"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_WRITE_SMS = "android:write_sms"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST = + "android:receive_emergency_broadcast"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_READ_ICC_SMS = "android:read_icc_sms"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_WRITE_ICC_SMS = "android:write_icc_sms"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_PLAY_AUDIO = "android:play_audio"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_READ_CLIPBOARD = "android:read_clipboard"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_WRITE_CLIPBOARD = "android:write_clipboard"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_TAKE_MEDIA_BUTTONS = "android:take_media_buttons"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_TAKE_AUDIO_FOCUS = "android:take_audio_focus"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_AUDIO_MASTER_VOLUME = "android:audio_master_volume"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_AUDIO_VOICE_VOLUME = "android:audio_voice_volume"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_AUDIO_RING_VOLUME = "android:audio_ring_volume"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_AUDIO_MEDIA_VOLUME = "android:audio_media_volume"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_AUDIO_ALARM_VOLUME = "android:audio_alarm_volume"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_AUDIO_NOTIFICATION_VOLUME = + "android:audio_notification_volume"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_AUDIO_BLUETOOTH_VOLUME = "android:audio_bluetooth_volume"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_WAKE_LOCK = "android:wake_lock"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_MUTE_MICROPHONE = "android:mute_microphone"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_TOAST_WINDOW = "android:toast_window"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_PROJECT_MEDIA = "android:project_media"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_WRITE_WALLPAPER = "android:write_wallpaper"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_ASSIST_STRUCTURE = "android:assist_structure"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_ASSIST_SCREENSHOT = "android:assist_screenshot"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_TURN_SCREEN_ON = "android:turn_screen_on"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_RUN_IN_BACKGROUND = "android:run_in_background"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_AUDIO_ACCESSIBILITY_VOLUME = + "android:audio_accessibility_volume"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_REQUEST_INSTALL_PACKAGES = "android:request_install_packages"; + /** @hide */ + @SystemApi @TestApi + 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"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_REQUEST_DELETE_PACKAGES = "request_delete_packages"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_BIND_ACCESSIBILITY_SERVICE = "bind_accessibility_service"; // Warning: If an permission is added here it also has to be added to // com.android.packageinstaller.permission.utils.EventLogger @@ -408,6 +536,7 @@ public class AppOpsManager { OP_USE_SIP, OP_PROCESS_OUTGOING_CALLS, OP_ANSWER_PHONE_CALLS, + OP_ACCEPT_HANDOVER, // Microphone OP_RECORD_AUDIO, // Camera @@ -506,64 +635,64 @@ public class AppOpsManager { OP_CHANGE_WIFI_STATE, OP_REQUEST_DELETE_PACKAGES, OP_BIND_ACCESSIBILITY_SERVICE, + OP_ACCEPT_HANDOVER, }; /** * This maps each operation to the public string constant for it. - * If it doesn't have a public string constant, it maps to null. */ - private static String[] sOpToString = new String[] { + private static String[] sOpToString = new String[]{ OPSTR_COARSE_LOCATION, OPSTR_FINE_LOCATION, - null, - null, + OPSTR_GPS, + OPSTR_VIBRATE, OPSTR_READ_CONTACTS, OPSTR_WRITE_CONTACTS, OPSTR_READ_CALL_LOG, OPSTR_WRITE_CALL_LOG, OPSTR_READ_CALENDAR, OPSTR_WRITE_CALENDAR, - null, - null, - null, + OPSTR_WIFI_SCAN, + OPSTR_POST_NOTIFICATION, + OPSTR_NEIGHBORING_CELLS, OPSTR_CALL_PHONE, OPSTR_READ_SMS, - null, + OPSTR_WRITE_SMS, OPSTR_RECEIVE_SMS, - null, + OPSTR_RECEIVE_EMERGENCY_BROADCAST, OPSTR_RECEIVE_MMS, OPSTR_RECEIVE_WAP_PUSH, OPSTR_SEND_SMS, - null, - null, + OPSTR_READ_ICC_SMS, + OPSTR_WRITE_ICC_SMS, OPSTR_WRITE_SETTINGS, OPSTR_SYSTEM_ALERT_WINDOW, - null, + OPSTR_ACCESS_NOTIFICATIONS, OPSTR_CAMERA, OPSTR_RECORD_AUDIO, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, + OPSTR_PLAY_AUDIO, + OPSTR_READ_CLIPBOARD, + OPSTR_WRITE_CLIPBOARD, + OPSTR_TAKE_MEDIA_BUTTONS, + OPSTR_TAKE_AUDIO_FOCUS, + OPSTR_AUDIO_MASTER_VOLUME, + OPSTR_AUDIO_VOICE_VOLUME, + OPSTR_AUDIO_RING_VOLUME, + OPSTR_AUDIO_MEDIA_VOLUME, + OPSTR_AUDIO_ALARM_VOLUME, + OPSTR_AUDIO_NOTIFICATION_VOLUME, + OPSTR_AUDIO_BLUETOOTH_VOLUME, + OPSTR_WAKE_LOCK, OPSTR_MONITOR_LOCATION, OPSTR_MONITOR_HIGH_POWER_LOCATION, OPSTR_GET_USAGE_STATS, - null, - null, - null, + OPSTR_MUTE_MICROPHONE, + OPSTR_TOAST_WINDOW, + OPSTR_PROJECT_MEDIA, OPSTR_ACTIVATE_VPN, - null, - null, - null, + OPSTR_WRITE_WALLPAPER, + OPSTR_ASSIST_STRUCTURE, + OPSTR_ASSIST_SCREENSHOT, OPSTR_READ_PHONE_STATE, OPSTR_ADD_VOICEMAIL, OPSTR_USE_SIP, @@ -574,19 +703,20 @@ public class AppOpsManager { OPSTR_MOCK_LOCATION, OPSTR_READ_EXTERNAL_STORAGE, OPSTR_WRITE_EXTERNAL_STORAGE, - null, + OPSTR_TURN_SCREEN_ON, OPSTR_GET_ACCOUNTS, - null, - null, // OP_AUDIO_ACCESSIBILITY_VOLUME + OPSTR_RUN_IN_BACKGROUND, + OPSTR_AUDIO_ACCESSIBILITY_VOLUME, OPSTR_READ_PHONE_NUMBERS, - null, // OP_REQUEST_INSTALL_PACKAGES + OPSTR_REQUEST_INSTALL_PACKAGES, OPSTR_PICTURE_IN_PICTURE, OPSTR_INSTANT_APP_START_FOREGROUND, OPSTR_ANSWER_PHONE_CALLS, - null, // OP_RUN_ANY_IN_BACKGROUND - null, // OP_CHANGE_WIFI_STATE - null, // OP_REQUEST_DELETE_PACKAGES - null, // OP_BIND_ACCESSIBILITY_SERVICE + OPSTR_RUN_ANY_IN_BACKGROUND, + OPSTR_CHANGE_WIFI_STATE, + OPSTR_REQUEST_DELETE_PACKAGES, + OPSTR_BIND_ACCESSIBILITY_SERVICE, + OPSTR_ACCEPT_HANDOVER, }; /** @@ -668,6 +798,7 @@ public class AppOpsManager { "CHANGE_WIFI_STATE", "REQUEST_DELETE_PACKAGES", "BIND_ACCESSIBILITY_SERVICE", + "ACCEPT_HANDOVER", }; /** @@ -749,6 +880,7 @@ public class AppOpsManager { Manifest.permission.CHANGE_WIFI_STATE, Manifest.permission.REQUEST_DELETE_PACKAGES, Manifest.permission.BIND_ACCESSIBILITY_SERVICE, + Manifest.permission.ACCEPT_HANDOVER, }; /** @@ -831,6 +963,7 @@ public class AppOpsManager { null, // OP_CHANGE_WIFI_STATE null, // REQUEST_DELETE_PACKAGES null, // OP_BIND_ACCESSIBILITY_SERVICE + null, // ACCEPT_HANDOVER }; /** @@ -912,6 +1045,7 @@ public class AppOpsManager { false, // OP_CHANGE_WIFI_STATE false, // OP_REQUEST_DELETE_PACKAGES false, // OP_BIND_ACCESSIBILITY_SERVICE + false, // ACCEPT_HANDOVER }; /** @@ -992,6 +1126,7 @@ public class AppOpsManager { 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 }; /** @@ -1076,6 +1211,7 @@ public class AppOpsManager { false, // OP_CHANGE_WIFI_STATE false, // OP_REQUEST_DELETE_PACKAGES false, // OP_BIND_ACCESSIBILITY_SERVICE + false, // ACCEPT_HANDOVER }; /** @@ -1207,6 +1343,25 @@ public class AppOpsManager { } /** + * Retrieve the human readable mode. + * @hide + */ + public static String modeToString(int mode) { + switch (mode) { + case MODE_ALLOWED: + return "allow"; + case MODE_IGNORED: + return "ignore"; + case MODE_ERRORED: + return "deny"; + case MODE_DEFAULT: + return "default"; + default: + return "mode=" + mode; + } + } + + /** * Retrieve whether the op allows itself to be reset. * @hide */ @@ -1482,6 +1637,7 @@ public class AppOpsManager { } /** @hide */ + @TestApi public void setMode(int code, int uid, String packageName, int mode) { try { mService.setMode(code, uid, packageName, mode); @@ -1997,4 +2153,14 @@ public class AppOpsManager { throw e.rethrowFromSystemServer(); } } + + /** + * Returns all supported operation names. + * @hide + */ + @SystemApi + @TestApi + public static String[] getOpStrs() { + return Arrays.copyOf(sOpToString, sOpToString.length); + } } diff --git a/android/app/ApplicationPackageManager.java b/android/app/ApplicationPackageManager.java index 8641a21a..cc68c051 100644 --- a/android/app/ApplicationPackageManager.java +++ b/android/app/ApplicationPackageManager.java @@ -64,7 +64,6 @@ import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; -import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -223,9 +222,18 @@ public class ApplicationPackageManager extends PackageManager { @Override public Intent getLeanbackLaunchIntentForPackage(String packageName) { - // Try to find a main leanback_launcher activity. + return getLaunchIntentForPackageAndCategory(packageName, Intent.CATEGORY_LEANBACK_LAUNCHER); + } + + @Override + public Intent getCarLaunchIntentForPackage(String packageName) { + return getLaunchIntentForPackageAndCategory(packageName, Intent.CATEGORY_CAR_LAUNCHER); + } + + private Intent getLaunchIntentForPackageAndCategory(String packageName, String category) { + // Try to find a main launcher activity for the given categories. Intent intentToResolve = new Intent(Intent.ACTION_MAIN); - intentToResolve.addCategory(Intent.CATEGORY_LEANBACK_LAUNCHER); + intentToResolve.addCategory(category); intentToResolve.setPackage(packageName); List<ResolveInfo> ris = queryIntentActivities(intentToResolve, 0); @@ -691,6 +699,26 @@ public class ApplicationPackageManager extends PackageManager { } @Override + public boolean hasSigningCertificate( + String packageName, byte[] certificate, @PackageManager.CertificateInputType int type) { + try { + return mPM.hasSigningCertificate(packageName, certificate, type); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public boolean hasSigningCertificate( + int uid, byte[] certificate, @PackageManager.CertificateInputType int type) { + try { + return mPM.hasUidSigningCertificate(uid, certificate, type); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override public String[] getPackagesForUid(int uid) { try { return mPM.getPackagesForUid(uid); @@ -1683,22 +1711,6 @@ public class ApplicationPackageManager extends PackageManager { } @Override - public void installPackage(Uri packageURI, - PackageInstallObserver observer, int flags, String installerPackageName) { - if (!"file".equals(packageURI.getScheme())) { - throw new UnsupportedOperationException("Only file:// URIs are supported"); - } - - final String originPath = packageURI.getPath(); - try { - mPM.installPackageAsUser(originPath, observer.getBinder(), flags, installerPackageName, - mContext.getUserId()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - @Override public int installExistingPackage(String packageName) throws NameNotFoundException { return installExistingPackage(packageName, PackageManager.INSTALL_REASON_UNKNOWN); } @@ -2755,6 +2767,24 @@ public class ApplicationPackageManager extends PackageManager { } @Override + public CharSequence getHarmfulAppWarning(String packageName) { + try { + return mPM.getHarmfulAppWarning(packageName, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + @Override + public void setHarmfulAppWarning(String packageName, CharSequence warning) { + try { + mPM.setHarmfulAppWarning(packageName, warning, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + @Override public ArtManager getArtManager() { synchronized (mLock) { if (mArtManager == null) { diff --git a/android/app/ClientTransactionHandler.java b/android/app/ClientTransactionHandler.java index 45c0e0cd..0f66652a 100644 --- a/android/app/ClientTransactionHandler.java +++ b/android/app/ClientTransactionHandler.java @@ -24,6 +24,7 @@ import android.os.IBinder; import com.android.internal.content.ReferrerIntent; +import java.io.PrintWriter; import java.util.List; /** @@ -121,4 +122,11 @@ public abstract class ClientTransactionHandler { * provided token. */ public abstract ActivityThread.ActivityClientRecord getActivityClient(IBinder token); + + /** + * Debugging output. + * @param pw {@link PrintWriter} to write logs to. + * @param prefix Prefix to prepend to output. + */ + public abstract void dump(PrintWriter pw, String prefix); } diff --git a/android/app/ContextImpl.java b/android/app/ContextImpl.java index 16534305..4914ffaf 100644 --- a/android/app/ContextImpl.java +++ b/android/app/ContextImpl.java @@ -872,13 +872,19 @@ class ContextImpl extends Context { // Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is // generally not allowed, except if the caller specifies the task id the activity should - // be launched in. - if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0 - && options != null && ActivityOptions.fromBundle(options).getLaunchTaskId() == -1) { + // be launched in. A bug was existed between N and O-MR1 which allowed this to work. We + // maintain this for backwards compatibility. + final int targetSdkVersion = getApplicationInfo().targetSdkVersion; + + if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == 0 + && (targetSdkVersion < Build.VERSION_CODES.N + || targetSdkVersion >= Build.VERSION_CODES.P) + && (options == null + || ActivityOptions.fromBundle(options).getLaunchTaskId() == -1)) { throw new AndroidRuntimeException( "Calling startActivity() from outside of an Activity " - + " context requires the FLAG_ACTIVITY_NEW_TASK flag." - + " Is this really what you want?"); + + " context requires the FLAG_ACTIVITY_NEW_TASK flag." + + " Is this really what you want?"); } mMainThread.getInstrumentation().execStartActivity( getOuterContext(), mMainThread.getApplicationThread(), null, diff --git a/android/app/Dialog.java b/android/app/Dialog.java index b162cb16..2b648ea6 100644 --- a/android/app/Dialog.java +++ b/android/app/Dialog.java @@ -16,10 +16,6 @@ package android.app; -import com.android.internal.R; -import com.android.internal.app.WindowDecorActionBar; -import com.android.internal.policy.PhoneWindow; - import android.annotation.CallSuper; import android.annotation.DrawableRes; import android.annotation.IdRes; @@ -32,8 +28,8 @@ import android.content.ComponentName; import android.content.Context; import android.content.ContextWrapper; import android.content.DialogInterface; -import android.content.res.Configuration; import android.content.pm.ApplicationInfo; +import android.content.res.Configuration; import android.content.res.ResourceId; import android.graphics.drawable.Drawable; import android.net.Uri; @@ -62,6 +58,10 @@ import android.view.Window; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; +import com.android.internal.R; +import com.android.internal.app.WindowDecorActionBar; +import com.android.internal.policy.PhoneWindow; + import java.lang.ref.WeakReference; /** @@ -512,6 +512,7 @@ public class Dialog implements DialogInterface, Window.Callback, * @param id the ID to search for * @return a view with given ID if found, or {@code null} otherwise * @see View#findViewById(int) + * @see Dialog#requireViewById(int) */ @Nullable public <T extends View> T findViewById(@IdRes int id) { @@ -519,6 +520,30 @@ public class Dialog implements DialogInterface, Window.Callback, } /** + * Finds the first descendant view with the given ID or throws an IllegalArgumentException if + * the ID is invalid (< 0), there is no matching view in the hierarchy, or the dialog has not + * yet been fully created (for example, via {@link #show()} or {@link #create()}). + * <p> + * <strong>Note:</strong> In most cases -- depending on compiler support -- + * the resulting view is automatically cast to the target class type. If + * the target class type is unconstrained, an explicit cast may be + * necessary. + * + * @param id the ID to search for + * @return a view with given ID + * @see View#requireViewById(int) + * @see Dialog#findViewById(int) + */ + @NonNull + public final <T extends View> T requireViewById(@IdRes int id) { + T view = findViewById(id); + if (view == null) { + throw new IllegalArgumentException("ID does not reference a View inside this Dialog"); + } + return view; + } + + /** * Set the screen content from a layout resource. The resource will be * inflated, adding all top-level views to the screen. * diff --git a/android/app/Instrumentation.java b/android/app/Instrumentation.java index b469de56..3c38a4ec 100644 --- a/android/app/Instrumentation.java +++ b/android/app/Instrumentation.java @@ -17,6 +17,7 @@ package android.app; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ActivityNotFoundException; import android.content.ComponentName; @@ -458,7 +459,8 @@ public class Instrumentation { * * @see Context#startActivity(Intent, Bundle) */ - public Activity startActivitySync(Intent intent, @Nullable Bundle options) { + @NonNull + public Activity startActivitySync(@NonNull Intent intent, @Nullable Bundle options) { validateNotAppThread(); synchronized (mSync) { @@ -1872,8 +1874,8 @@ public class Instrumentation { */ public ActivityResult execStartActivityAsCaller( Context who, IBinder contextThread, IBinder token, Activity target, - Intent intent, int requestCode, Bundle options, boolean ignoreTargetSecurity, - int userId) { + Intent intent, int requestCode, Bundle options, IBinder permissionToken, + boolean ignoreTargetSecurity, int userId) { IApplicationThread whoThread = (IApplicationThread) contextThread; if (mActivityMonitors != null) { synchronized (mSync) { @@ -1904,7 +1906,8 @@ public class Instrumentation { .startActivityAsCaller(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null ? target.mEmbeddedID : null, - requestCode, 0, null, options, ignoreTargetSecurity, userId); + requestCode, 0, null, options, permissionToken, + 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 d0f84c8e..553099f2 100644 --- a/android/app/KeyguardManager.java +++ b/android/app/KeyguardManager.java @@ -20,6 +20,7 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.annotation.SystemService; import android.app.trust.ITrustManager; import android.content.Context; @@ -166,24 +167,26 @@ public class KeyguardManager { * clicking this button, the activity returns * {@link #RESULT_ALTERNATE} * - * @return the intent for launching the activity or null if the credential of the previous - * owner can not be verified (e.g. because there was none, or the device does not support - * verifying credentials after a factory reset, or device setup has already been completed). - * + * @return the intent for launching the activity or null if the previous owner of the device + * did not set a credential. + * @throws UnsupportedOperationException if the device does not support factory reset + * credentials + * @throws IllegalStateException if the device has already been provisioned * @hide */ + @SystemApi public Intent createConfirmFactoryResetCredentialIntent( CharSequence title, CharSequence description, CharSequence alternateButtonLabel) { if (!LockPatternUtils.frpCredentialEnabled(mContext)) { Log.w(TAG, "Factory reset credentials not supported."); - return null; + throw new UnsupportedOperationException("not supported on this device"); } // Cannot verify credential if the device is provisioned if (Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0) { Log.e(TAG, "Factory reset credential cannot be verified after provisioning."); - return null; + throw new IllegalStateException("must not be provisioned yet"); } // Make sure we have a credential @@ -192,8 +195,10 @@ public class KeyguardManager { ServiceManager.getService(Context.PERSISTENT_DATA_BLOCK_SERVICE)); if (pdb == null) { Log.e(TAG, "No persistent data block service"); - return null; + throw new UnsupportedOperationException("not supported on this device"); } + // The following will throw an UnsupportedOperationException if the device does not + // support factory reset credentials (or something went wrong retrieving it). if (!pdb.hasFrpCredentialHandle()) { Log.i(TAG, "The persistent data block does not have a factory reset credential."); return null; @@ -475,6 +480,39 @@ public class KeyguardManager { */ public void requestDismissKeyguard(@NonNull Activity activity, @Nullable KeyguardDismissCallback callback) { + requestDismissKeyguard(activity, null /* message */, callback); + } + + /** + * If the device is currently locked (see {@link #isKeyguardLocked()}, requests the Keyguard to + * be dismissed. + * <p> + * If the Keyguard is not secure or the device is currently in a trusted state, calling this + * method will immediately dismiss the Keyguard without any user interaction. + * <p> + * If the Keyguard is secure and the device is not in a trusted state, this will bring up the + * UI so the user can enter their credentials. + * <p> + * If the value set for the {@link Activity} attr {@link android.R.attr#turnScreenOn} is true, + * the screen will turn on when the keyguard is dismissed. + * + * @param activity The activity requesting the dismissal. The activity must be either visible + * by using {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} or must be in a state in + * which it would be visible if Keyguard would not be hiding it. If that's not + * the case, the request will fail immediately and + * {@link KeyguardDismissCallback#onDismissError} will be invoked. + * @param message A message that will be shown in the keyguard explaining why the user + * would want to dismiss it. + * @param callback The callback to be called if the request to dismiss Keyguard was successful + * or {@code null} if the caller isn't interested in knowing the result. The + * callback will not be invoked if the activity was destroyed before the + * callback was received. + * @hide + */ + @RequiresPermission(Manifest.permission.SHOW_KEYGUARD_MESSAGE) + @SystemApi + public void requestDismissKeyguard(@NonNull Activity activity, @Nullable CharSequence message, + @Nullable KeyguardDismissCallback callback) { try { mAm.dismissKeyguard(activity.getActivityToken(), new IKeyguardDismissCallback.Stub() { @Override @@ -497,9 +535,9 @@ public class KeyguardManager { activity.mHandler.post(callback::onDismissCancelled); } } - }); + }, message); } catch (RemoteException e) { - Log.i(TAG, "Failed to dismiss keyguard: " + e); + throw e.rethrowFromSystemServer(); } } diff --git a/android/app/Notification.java b/android/app/Notification.java index 85c3be82..d6fddfca 100644 --- a/android/app/Notification.java +++ b/android/app/Notification.java @@ -1022,10 +1022,18 @@ public class Notification implements Parcelable /** * {@link #extras} key: A String array containing the people that this notification relates to, * each of which was supplied to {@link Builder#addPerson(String)}. + * + * @deprecated the actual objects are now in {@link #EXTRA_PEOPLE_LIST} */ public static final String EXTRA_PEOPLE = "android.people"; /** + * {@link #extras} key: An arrayList of {@link Person} objects containing the people that + * this notification relates to. + */ + public static final String EXTRA_PEOPLE_LIST = "android.people.list"; + + /** * Allow certain system-generated notifications to appear before the device is provisioned. * Only available to notifications coming from the android package. * @hide @@ -1063,10 +1071,20 @@ public class Notification implements Parcelable * direct replies * {@link android.app.Notification.MessagingStyle} notification. This extra is a * {@link CharSequence} + * + * @deprecated use {@link #EXTRA_MESSAGING_PERSON} */ public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName"; /** + * {@link #extras} key: the person to be displayed for all messages sent by the user including + * direct replies + * {@link android.app.Notification.MessagingStyle} notification. This extra is a + * {@link Person} + */ + public static final String EXTRA_MESSAGING_PERSON = "android.messagingUser"; + + /** * {@link #extras} key: a {@link CharSequence} to be displayed as the title to a conversation * represented by a {@link android.app.Notification.MessagingStyle} */ @@ -1250,10 +1268,67 @@ public class Notification implements Parcelable */ private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS"; + /** + * {@link }: No semantic action defined. + */ + public static final int SEMANTIC_ACTION_NONE = 0; + + /** + * {@code SemanticAction}: Reply to a conversation, chat, group, or wherever replies + * may be appropriate. + */ + public static final int SEMANTIC_ACTION_REPLY = 1; + + /** + * {@code SemanticAction}: Mark content as read. + */ + public static final int SEMANTIC_ACTION_MARK_AS_READ = 2; + + /** + * {@code SemanticAction}: Mark content as unread. + */ + public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3; + + /** + * {@code SemanticAction}: Delete the content associated with the notification. This + * could mean deleting an email, message, etc. + */ + public static final int SEMANTIC_ACTION_DELETE = 4; + + /** + * {@code SemanticAction}: Archive the content associated with the notification. This + * could mean archiving an email, message, etc. + */ + public static final int SEMANTIC_ACTION_ARCHIVE = 5; + + /** + * {@code SemanticAction}: Mute the content associated with the notification. This could + * mean silencing a conversation or currently playing media. + */ + public static final int SEMANTIC_ACTION_MUTE = 6; + + /** + * {@code SemanticAction}: Unmute the content associated with the notification. This could + * mean un-silencing a conversation or currently playing media. + */ + public static final int SEMANTIC_ACTION_UNMUTE = 7; + + /** + * {@code SemanticAction}: Mark content with a thumbs up. + */ + public static final int SEMANTIC_ACTION_THUMBS_UP = 8; + + /** + * {@code SemanticAction}: Mark content with a thumbs down. + */ + public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9; + + private final Bundle mExtras; private Icon mIcon; private final RemoteInput[] mRemoteInputs; private boolean mAllowGeneratedReplies = true; + private final @SemanticAction int mSemanticAction; /** * Small icon representing the action. @@ -1288,6 +1363,7 @@ public class Notification implements Parcelable mExtras = Bundle.setDefusable(in.readBundle(), true); mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR); mAllowGeneratedReplies = in.readInt() == 1; + mSemanticAction = in.readInt(); } /** @@ -1295,12 +1371,14 @@ public class Notification implements Parcelable */ @Deprecated public Action(int icon, CharSequence title, PendingIntent intent) { - this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true); + this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true, + SEMANTIC_ACTION_NONE); } /** Keep in sync with {@link Notification.Action.Builder#Builder(Action)}! */ private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras, - RemoteInput[] remoteInputs, boolean allowGeneratedReplies) { + RemoteInput[] remoteInputs, boolean allowGeneratedReplies, + @SemanticAction int semanticAction) { this.mIcon = icon; if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) { this.icon = icon.getResId(); @@ -1310,6 +1388,7 @@ public class Notification implements Parcelable this.mExtras = extras != null ? extras : new Bundle(); this.mRemoteInputs = remoteInputs; this.mAllowGeneratedReplies = allowGeneratedReplies; + this.mSemanticAction = semanticAction; } /** @@ -1348,6 +1427,15 @@ public class Notification implements Parcelable } /** + * Returns the {@code SemanticAction} associated with this {@link Action}. A + * {@code SemanticAction} denotes what an {@link Action}'s {@link PendingIntent} will do + * (eg. reply, mark as read, delete, etc). + */ + public @SemanticAction int getSemanticAction() { + return mSemanticAction; + } + + /** * Get the list of inputs to be collected from the user that ONLY accept data when this * action is sent. These remote inputs are guaranteed to return true on a call to * {@link RemoteInput#isDataOnly}. @@ -1371,6 +1459,7 @@ public class Notification implements Parcelable private boolean mAllowGeneratedReplies = true; private final Bundle mExtras; private ArrayList<RemoteInput> mRemoteInputs; + private @SemanticAction int mSemanticAction; /** * Construct a new builder for {@link Action} object. @@ -1390,7 +1479,7 @@ public class Notification implements Parcelable * @param intent the {@link PendingIntent} to fire when users trigger this action */ public Builder(Icon icon, CharSequence title, PendingIntent intent) { - this(icon, title, intent, new Bundle(), null, true); + this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE); } /** @@ -1401,11 +1490,12 @@ public class Notification implements Parcelable public Builder(Action action) { this(action.getIcon(), action.title, action.actionIntent, new Bundle(action.mExtras), action.getRemoteInputs(), - action.getAllowGeneratedReplies()); + action.getAllowGeneratedReplies(), action.getSemanticAction()); } private Builder(Icon icon, CharSequence title, PendingIntent intent, Bundle extras, - RemoteInput[] remoteInputs, boolean allowGeneratedReplies) { + RemoteInput[] remoteInputs, boolean allowGeneratedReplies, + @SemanticAction int semanticAction) { mIcon = icon; mTitle = title; mIntent = intent; @@ -1415,6 +1505,7 @@ public class Notification implements Parcelable Collections.addAll(mRemoteInputs, remoteInputs); } mAllowGeneratedReplies = allowGeneratedReplies; + mSemanticAction = semanticAction; } /** @@ -1470,6 +1561,19 @@ public class Notification implements Parcelable } /** + * Sets the {@code SemanticAction} for this {@link Action}. A + * {@code SemanticAction} denotes what an {@link Action}'s + * {@link PendingIntent} will do (eg. reply, mark as read, delete, etc). + * @param semanticAction a SemanticAction defined within {@link Action} with + * {@code SEMANTIC_ACTION_} prefixes + * @return this object for method chaining + */ + public Builder setSemanticAction(@SemanticAction int semanticAction) { + mSemanticAction = semanticAction; + return this; + } + + /** * Apply an extender to this action builder. Extenders may be used to add * metadata or change options on this builder. */ @@ -1510,7 +1614,7 @@ public class Notification implements Parcelable RemoteInput[] textInputsArr = textInputs.isEmpty() ? null : textInputs.toArray(new RemoteInput[textInputs.size()]); return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr, - mAllowGeneratedReplies); + mAllowGeneratedReplies, mSemanticAction); } } @@ -1522,12 +1626,15 @@ public class Notification implements Parcelable actionIntent, // safe to alias mExtras == null ? new Bundle() : new Bundle(mExtras), getRemoteInputs(), - getAllowGeneratedReplies()); + getAllowGeneratedReplies(), + getSemanticAction()); } + @Override public int describeContents() { return 0; } + @Override public void writeToParcel(Parcel out, int flags) { final Icon ic = getIcon(); @@ -1547,7 +1654,9 @@ public class Notification implements Parcelable out.writeBundle(mExtras); out.writeTypedArray(mRemoteInputs, flags); out.writeInt(mAllowGeneratedReplies ? 1 : 0); + out.writeInt(mSemanticAction); } + public static final Parcelable.Creator<Action> CREATOR = new Parcelable.Creator<Action>() { public Action createFromParcel(Parcel in) { @@ -1809,6 +1918,29 @@ public class Notification implements Parcelable return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0; } } + + /** + * Provides meaning to an {@link Action} that hints at what the associated + * {@link PendingIntent} will do. For example, an {@link Action} with a + * {@link PendingIntent} that replies to a text message notification may have the + * {@link #SEMANTIC_ACTION_REPLY} {@code SemanticAction} set within it. + * + * @hide + */ + @IntDef(prefix = { "SEMANTIC_ACTION_" }, value = { + SEMANTIC_ACTION_NONE, + SEMANTIC_ACTION_REPLY, + SEMANTIC_ACTION_MARK_AS_READ, + SEMANTIC_ACTION_MARK_AS_UNREAD, + SEMANTIC_ACTION_DELETE, + SEMANTIC_ACTION_ARCHIVE, + SEMANTIC_ACTION_MUTE, + SEMANTIC_ACTION_UNMUTE, + SEMANTIC_ACTION_THUMBS_UP, + SEMANTIC_ACTION_THUMBS_DOWN + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SemanticAction {} } /** @@ -2819,7 +2951,7 @@ public class Notification implements Parcelable private Bundle mUserExtras = new Bundle(); private Style mStyle; private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS); - private ArrayList<String> mPersonList = new ArrayList<String>(); + private ArrayList<Person> mPersonList = new ArrayList<>(); private NotificationColorUtil mColorUtil; private boolean mIsLegacy; private boolean mIsLegacyInitialized; @@ -2910,8 +3042,9 @@ public class Notification implements Parcelable Collections.addAll(mActions, mN.actions); } - if (mN.extras.containsKey(EXTRA_PEOPLE)) { - Collections.addAll(mPersonList, mN.extras.getStringArray(EXTRA_PEOPLE)); + if (mN.extras.containsKey(EXTRA_PEOPLE_LIST)) { + ArrayList<Person> people = mN.extras.getParcelableArrayList(EXTRA_PEOPLE_LIST); + mPersonList.addAll(people); } if (mN.getSmallIcon() == null && mN.icon != 0) { @@ -3621,13 +3754,41 @@ public class Notification implements Parcelable * URIs. The path part of these URIs must exist in the contacts database, in the * appropriate column, or the reference will be discarded as invalid. Telephone schema * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}. + * It is also possible to provide a URI with the schema {@code name:} in order to uniquely + * identify a person without an entry in the contacts database. * </P> * * @param uri A URI for the person. * @see Notification#EXTRA_PEOPLE + * @deprecated use {@link #addPerson(Person)} */ public Builder addPerson(String uri) { - mPersonList.add(uri); + addPerson(new Person().setUri(uri)); + return this; + } + + /** + * Add a person that is relevant to this notification. + * + * <P> + * Depending on user preferences, this annotation may allow the notification to pass + * through interruption filters, if this notification is of category {@link #CATEGORY_CALL} + * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to + * appear more prominently in the user interface. + * </P> + * + * <P> + * A person should usually contain a uri in order to benefit from the ranking boost. + * However, even if no uri is provided, it's beneficial to provide other people in the + * notification, such that listeners and voice only devices can announce and handle them + * properly. + * </P> + * + * @param person the person to add. + * @see Notification#EXTRA_PEOPLE_LIST + */ + public Builder addPerson(Person person) { + mPersonList.add(person); return this; } @@ -3934,7 +4095,10 @@ public class Notification implements Parcelable contentView.setViewVisibility(R.id.chronometer, View.GONE); contentView.setViewVisibility(R.id.header_text, View.GONE); contentView.setTextViewText(R.id.header_text, null); + contentView.setViewVisibility(R.id.header_text_secondary, View.GONE); + contentView.setTextViewText(R.id.header_text_secondary, null); contentView.setViewVisibility(R.id.header_text_divider, View.GONE); + contentView.setViewVisibility(R.id.header_text_secondary_divider, View.GONE); contentView.setViewVisibility(R.id.time_divider, View.GONE); contentView.setViewVisibility(R.id.time, View.GONE); contentView.setImageViewIcon(R.id.profile_badge, null); @@ -3965,8 +4129,8 @@ public class Notification implements Parcelable final Bundle ex = mN.extras; updateBackgroundColor(contentView); - bindNotificationHeader(contentView, p.ambient); - bindLargeIcon(contentView, p.hideLargeIcon, p.alwaysShowReply); + bindNotificationHeader(contentView, p.ambient, p.headerTextSecondary); + bindLargeIcon(contentView, p.hideLargeIcon || p.ambient, p.alwaysShowReply); boolean showProgress = handleProgressBar(p.hasProgress, contentView, ex); if (p.title != null) { contentView.setViewVisibility(R.id.title, View.VISIBLE); @@ -4248,12 +4412,14 @@ public class Notification implements Parcelable return null; } - private void bindNotificationHeader(RemoteViews contentView, boolean ambient) { + private void bindNotificationHeader(RemoteViews contentView, boolean ambient, + CharSequence secondaryHeaderText) { bindSmallIcon(contentView, ambient); bindHeaderAppName(contentView, ambient); if (!ambient) { // Ambient view does not have these bindHeaderText(contentView); + bindHeaderTextSecondary(contentView, secondaryHeaderText); bindHeaderChronometerAndTime(contentView); bindProfileBadge(contentView); } @@ -4322,6 +4488,17 @@ public class Notification implements Parcelable } } + private void bindHeaderTextSecondary(RemoteViews contentView, CharSequence secondaryText) { + if (!TextUtils.isEmpty(secondaryText)) { + contentView.setTextViewText(R.id.header_text_secondary, processTextSpans( + processLegacyText(secondaryText))); + setTextViewColorSecondary(contentView, R.id.header_text_secondary); + contentView.setViewVisibility(R.id.header_text_secondary, View.VISIBLE); + contentView.setViewVisibility(R.id.header_text_secondary_divider, View.VISIBLE); + setTextViewColorSecondary(contentView, R.id.header_text_secondary_divider); + } + } + /** * @hide */ @@ -4555,7 +4732,7 @@ public class Notification implements Parcelable ambient ? R.layout.notification_template_ambient_header : R.layout.notification_template_header); resetNotificationHeader(header); - bindNotificationHeader(header, ambient); + bindNotificationHeader(header, ambient, null); if (colorized != null) { mN.extras.putBoolean(EXTRA_COLORIZED, colorized); } else { @@ -4968,8 +5145,7 @@ public class Notification implements Parcelable mActions.toArray(mN.actions); } if (!mPersonList.isEmpty()) { - mN.extras.putStringArray(EXTRA_PEOPLE, - mPersonList.toArray(new String[mPersonList.size()])); + mN.extras.putParcelableArrayList(EXTRA_PEOPLE_LIST, mPersonList); } if (mN.bigContentView != null || mN.contentView != null || mN.headsUpContentView != null) { @@ -5965,7 +6141,7 @@ public class Notification implements Parcelable */ public static final int MAXIMUM_RETAINED_MESSAGES = 25; - CharSequence mUserDisplayName; + @NonNull Person mUser; @Nullable CharSequence mConversationTitle; List<Message> mMessages = new ArrayList<>(); List<Message> mHistoricMessages = new ArrayList<>(); @@ -5979,23 +6155,54 @@ public class Notification implements Parcelable * user before the posting app reposts the notification with those messages after they've * been actually sent and in previous messages sent by the user added in * {@link #addMessage(Notification.MessagingStyle.Message)} + * + * @deprecated use {@code MessagingStyle(Person)} */ public MessagingStyle(@NonNull CharSequence userDisplayName) { - mUserDisplayName = userDisplayName; + this(new Person().setName(userDisplayName)); + } + + /** + * @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. + */ + public MessagingStyle(@NonNull Person user) { + mUser = user; + if (user == null || user.getName() == null) { + throw new RuntimeException("user must be valid and have a name"); + } + } + + /** + * @return the user to be displayed for any replies sent by the user + */ + public Person getUser() { + return mUser; } /** * Returns the name to be displayed for any replies sent by the user + * + * @deprecated use {@link #getUser()} instead */ public CharSequence getUserDisplayName() { - return mUserDisplayName; + return mUser.getName(); } /** * Sets the title to be displayed on this conversation. May be set to {@code null}. * - * @param conversationTitle A name for the conversation, or {@code null} - * @return this object for method chaining. + * <p>This API's behavior was changed in SDK version {@link Build.VERSION_CODES#P}. If your + * application's target version is less than {@link Build.VERSION_CODES#P}, setting a + * conversation title to a non-null value will make {@link #isGroupConversation()} return + * {@code true} and passing {@code null} will make it return {@code false}. In + * {@link Build.VERSION_CODES#P} and beyond, use {@link #setGroupConversation(boolean)} + * to set group conversation status. + * + * @param conversationTitle Title displayed for this conversation + * @return this object for method chaining */ public MessagingStyle setConversationTitle(@Nullable CharSequence conversationTitle) { mConversationTitle = conversationTitle; @@ -6024,8 +6231,28 @@ public class Notification implements Parcelable * @see Message#Message(CharSequence, long, CharSequence) * * @return this object for method chaining + * + * @deprecated use {@link #addMessage(CharSequence, long, Person)} */ public MessagingStyle addMessage(CharSequence text, long timestamp, CharSequence sender) { + return addMessage(text, timestamp, + sender == null ? null : new Person().setName(sender)); + } + + /** + * Adds a message for display by this notification. Convenience call for a simple + * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}. + * @param text A {@link CharSequence} to be displayed as the message content + * @param timestamp Time at which the message arrived + * @param sender The {@link Person} who sent the message. + * Should be <code>null</code> for messages by the current user, in which case + * the platform will insert the user set in {@code MessagingStyle(Person)}. + * + * @see Message#Message(CharSequence, long, CharSequence) + * + * @return this object for method chaining + */ + public MessagingStyle addMessage(CharSequence text, long timestamp, Person sender) { return addMessage(new Message(text, timestamp, sender)); } @@ -6083,6 +6310,7 @@ public class Notification implements Parcelable /** * Sets whether this conversation notification represents a group. + * * @param isGroupConversation {@code true} if the conversation represents a group, * {@code false} otherwise. * @return this object for method chaining @@ -6093,9 +6321,27 @@ public class Notification implements Parcelable } /** - * Returns {@code true} if this notification represents a group conversation. + * Returns {@code true} if this notification represents a group conversation, otherwise + * {@code false}. + * + * <p> If the application that generated this {@link MessagingStyle} targets an SDK version + * less than {@link Build.VERSION_CODES#P}, this method becomes dependent on whether or + * not the conversation title is set; returning {@code true} if the conversation title is + * a non-null value, or {@code false} otherwise. From {@link Build.VERSION_CODES#P} forward, + * this method returns what's set by {@link #setGroupConversation(boolean)} allowing for + * named, non-group conversations. + * + * @see #setConversationTitle(CharSequence) */ public boolean isGroupConversation() { + // When target SDK version is < P, a non-null conversation title dictates if this is + // as group conversation. + if (mBuilder != null + && mBuilder.mContext.getApplicationInfo().targetSdkVersion + < Build.VERSION_CODES.P) { + return mConversationTitle != null; + } + return mIsGroupConversation; } @@ -6105,8 +6351,10 @@ public class Notification implements Parcelable @Override public void addExtras(Bundle extras) { super.addExtras(extras); - if (mUserDisplayName != null) { - extras.putCharSequence(EXTRA_SELF_DISPLAY_NAME, mUserDisplayName); + if (mUser != null) { + // For legacy usages + extras.putCharSequence(EXTRA_SELF_DISPLAY_NAME, mUser.getName()); + extras.putParcelable(EXTRA_MESSAGING_PERSON, mUser); } if (mConversationTitle != null) { extras.putCharSequence(EXTRA_CONVERSATION_TITLE, mConversationTitle); @@ -6126,14 +6374,15 @@ public class Notification implements Parcelable Message m = findLatestIncomingMessage(); CharSequence text = (m == null) ? null : m.mText; CharSequence sender = m == null ? null - : TextUtils.isEmpty(m.mSender) ? mUserDisplayName : m.mSender; + : m.mSender == null || TextUtils.isEmpty(m.mSender.getName()) + ? mUser.getName() : m.mSender.getName(); CharSequence title; if (!TextUtils.isEmpty(mConversationTitle)) { if (!TextUtils.isEmpty(sender)) { BidiFormatter bidi = BidiFormatter.getInstance(); title = mBuilder.mContext.getString( com.android.internal.R.string.notification_messaging_title_template, - bidi.unicodeWrap(mConversationTitle), bidi.unicodeWrap(m.mSender)); + bidi.unicodeWrap(mConversationTitle), bidi.unicodeWrap(sender)); } else { title = mConversationTitle; } @@ -6156,7 +6405,11 @@ public class Notification implements Parcelable protected void restoreFromExtras(Bundle extras) { super.restoreFromExtras(extras); - mUserDisplayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME); + mUser = extras.getParcelable(EXTRA_MESSAGING_PERSON); + if (mUser == null) { + CharSequence displayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME); + mUser = new Person().setName(displayName); + } mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE); Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES); mMessages = Message.getMessagesFromBundleArray(messages); @@ -6172,7 +6425,7 @@ public class Notification implements Parcelable public RemoteViews makeContentView(boolean increasedHeight) { mBuilder.mOriginalActions = mBuilder.mActions; mBuilder.mActions = new ArrayList<>(); - RemoteViews remoteViews = makeBigContentView(); + RemoteViews remoteViews = makeBigContentView(true /* showRightIcon */); mBuilder.mActions = mBuilder.mOriginalActions; mBuilder.mOriginalActions = null; return remoteViews; @@ -6191,7 +6444,7 @@ public class Notification implements Parcelable for (int i = messages.size() - 1; i >= 0; i--) { Message m = messages.get(i); // Incoming messages have a non-empty sender. - if (!TextUtils.isEmpty(m.mSender)) { + if (m.mSender != null && !TextUtils.isEmpty(m.mSender.getName())) { return m; } } @@ -6207,27 +6460,40 @@ public class Notification implements Parcelable */ @Override public RemoteViews makeBigContentView() { + return makeBigContentView(false /* showRightIcon */); + } + + @NonNull + private RemoteViews makeBigContentView(boolean showRightIcon) { CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle) ? super.mBigContentTitle : mConversationTitle; boolean isOneToOne = TextUtils.isEmpty(conversationTitle); - if (isOneToOne) { - // Let's add the conversationTitle in case we didn't have one before and all - // messages are from the same sender - conversationTitle = createConversationTitleFromMessages(); - } else if (hasOnlyWhiteSpaceSenders()) { + CharSequence nameReplacement = null; + if (hasOnlyWhiteSpaceSenders()) { isOneToOne = true; + nameReplacement = conversationTitle; + conversationTitle = null; } - boolean hasTitle = !TextUtils.isEmpty(conversationTitle); RemoteViews contentView = mBuilder.applyStandardTemplateWithActions( mBuilder.getMessagingLayoutResource(), mBuilder.mParams.reset().hasProgress(false).title(conversationTitle).text(null) - .hideLargeIcon(isOneToOne).alwaysShowReply(true)); + .hideLargeIcon(!showRightIcon || isOneToOne) + .headerTextSecondary(conversationTitle) + .alwaysShowReply(showRightIcon)); 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) { + 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.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon", mBuilder.mN.mLargeIcon); + contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement", + nameReplacement); contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne", isOneToOne); contentView.setBundle(R.id.status_bar_latest_event_content, "setData", @@ -6238,8 +6504,8 @@ public class Notification implements Parcelable private boolean hasOnlyWhiteSpaceSenders() { for (int i = 0; i < mMessages.size(); i++) { Message m = mMessages.get(i); - CharSequence sender = m.getSender(); - if (!isWhiteSpace(sender)) { + Person sender = m.getSenderPerson(); + if (sender != null && !isWhiteSpace(sender.getName())) { return false; } } @@ -6268,9 +6534,9 @@ public class Notification implements Parcelable ArraySet<CharSequence> names = new ArraySet<>(); for (int i = 0; i < mMessages.size(); i++) { Message m = mMessages.get(i); - CharSequence sender = m.getSender(); + Person sender = m.getSenderPerson(); if (sender != null) { - names.add(sender); + names.add(sender.getName()); } } SpannableStringBuilder title = new SpannableStringBuilder(); @@ -6290,7 +6556,7 @@ public class Notification implements Parcelable */ @Override public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { - RemoteViews remoteViews = makeBigContentView(); + RemoteViews remoteViews = makeBigContentView(true /* showRightIcon */); remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1); return remoteViews; } @@ -6305,13 +6571,15 @@ public class Notification implements Parcelable static final String KEY_TEXT = "text"; static final String KEY_TIMESTAMP = "time"; static final String KEY_SENDER = "sender"; + static final String KEY_SENDER_PERSON = "sender_person"; static final String KEY_DATA_MIME_TYPE = "type"; static final String KEY_DATA_URI= "uri"; static final String KEY_EXTRAS_BUNDLE = "extras"; private final CharSequence mText; private final long mTimestamp; - private final CharSequence mSender; + @Nullable + private final Person mSender; private Bundle mExtras = new Bundle(); private String mDataMimeType; @@ -6326,8 +6594,28 @@ public class Notification implements Parcelable * the platform will insert {@link MessagingStyle#getUserDisplayName()}. * Should be unique amongst all individuals in the conversation, and should be * consistent during re-posts of the notification. + * + * @deprecated use {@code Message(CharSequence, long, Person)} */ public Message(CharSequence text, long timestamp, CharSequence sender){ + this(text, timestamp, sender == null ? null : new Person().setName(sender)); + } + + /** + * Constructor + * @param text A {@link CharSequence} to be displayed as the message content + * @param timestamp Time at which the message arrived + * @param sender The {@link Person} who sent the message. + * Should be <code>null</code> for messages by the current user, in which case + * the platform will insert the user set in {@code MessagingStyle(Person)}. + * <p> + * The person provided should contain an Icon, set with {@link Person#setIcon(Icon)} + * and also have a name provided with {@link Person#setName(CharSequence)}. If multiple + * users have the same name, consider providing a key with {@link Person#setKey(String)} + * in order to differentiate between the different users. + * </p> + */ + public Message(CharSequence text, long timestamp, @Nullable Person sender){ mText = text; mTimestamp = timestamp; mSender = sender; @@ -6390,8 +6678,18 @@ public class Notification implements Parcelable /** * Get the text used to display the contact's name in the messaging experience + * + * @deprecated use {@link #getSenderPerson()} */ public CharSequence getSender() { + return mSender == null ? null : mSender.getName(); + } + + /** + * Get the sender associated with this message. + */ + @Nullable + public Person getSenderPerson() { return mSender; } @@ -6417,7 +6715,9 @@ public class Notification implements Parcelable } bundle.putLong(KEY_TIMESTAMP, mTimestamp); if (mSender != null) { - bundle.putCharSequence(KEY_SENDER, mSender); + // Legacy listeners need this + bundle.putCharSequence(KEY_SENDER, mSender.getName()); + bundle.putParcelable(KEY_SENDER_PERSON, mSender); } if (mDataMimeType != null) { bundle.putString(KEY_DATA_MIME_TYPE, mDataMimeType); @@ -6466,8 +6766,20 @@ public class Notification implements Parcelable if (!bundle.containsKey(KEY_TEXT) || !bundle.containsKey(KEY_TIMESTAMP)) { return null; } else { + + Person senderPerson = bundle.getParcelable(KEY_SENDER_PERSON); + if (senderPerson == null) { + // Legacy apps that use compat don't actually provide the sender objects + // We need to fix the compat version to provide people / use + // the native api instead + CharSequence senderName = bundle.getCharSequence(KEY_SENDER); + if (senderName != null) { + senderPerson = new Person().setName(senderName); + } + } Message message = new Message(bundle.getCharSequence(KEY_TEXT), - bundle.getLong(KEY_TIMESTAMP), bundle.getCharSequence(KEY_SENDER)); + bundle.getLong(KEY_TIMESTAMP), + senderPerson); if (bundle.containsKey(KEY_DATA_MIME_TYPE) && bundle.containsKey(KEY_DATA_URI)) { message.setData(bundle.getString(KEY_DATA_MIME_TYPE), @@ -7102,6 +7414,176 @@ public class Notification implements Parcelable } } + /** + * A Person associated with this Notification. + */ + public static final class Person implements Parcelable { + @Nullable private CharSequence mName; + @Nullable private Icon mIcon; + @Nullable private String mUri; + @Nullable private String mKey; + + protected Person(Parcel in) { + mName = in.readCharSequence(); + if (in.readInt() != 0) { + mIcon = Icon.CREATOR.createFromParcel(in); + } + mUri = in.readString(); + mKey = in.readString(); + } + + /** + * Create a new person. + */ + public Person() { + } + + /** + * Give this person a name. + * + * @param name the name of this person + */ + public Person setName(@Nullable CharSequence name) { + this.mName = name; + return this; + } + + /** + * Add an icon for this person. + * <br /> + * This is currently only used for {@link MessagingStyle} notifications and should not be + * provided otherwise, in order to save memory. The system will prefer this icon over any + * images that are resolved from the URI. + * + * @param icon the icon of the person + */ + public Person setIcon(@Nullable Icon icon) { + this.mIcon = icon; + return this; + } + + /** + * Set a URI associated with this person. + * + * <P> + * Depending on user preferences, adding a URI to a Person may allow the notification to + * pass through interruption filters, if this notification is of + * category {@link #CATEGORY_CALL} or {@link #CATEGORY_MESSAGE}. + * The addition of people may also cause this notification to appear more prominently in + * the user interface. + * </P> + * + * <P> + * The person should be specified by the {@code String} representation of a + * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}. + * </P> + * + * <P>The system will also attempt to resolve {@code mailto:} and {@code tel:} schema + * URIs. The path part of these URIs must exist in the contacts database, in the + * appropriate column, or the reference will be discarded as invalid. Telephone schema + * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}. + * </P> + * + * @param uri a URI for the person + */ + public Person setUri(@Nullable String uri) { + mUri = uri; + return this; + } + + /** + * Add a key to this person in order to uniquely identify it. + * This is especially useful if the name doesn't uniquely identify this person or if the + * display name is a short handle of the actual name. + * + * <P>If no key is provided, the name serves as as the key for the purpose of + * identification.</P> + * + * @param key the key that uniquely identifies this person + */ + public Person setKey(@Nullable String key) { + mKey = key; + return this; + } + + + /** + * @return the uri provided for this person or {@code null} if no Uri was provided + */ + @Nullable + public String getUri() { + return mUri; + } + + /** + * @return the name provided for this person or {@code null} if no name was provided + */ + @Nullable + public CharSequence getName() { + return mName; + } + + /** + * @return the icon provided for this person or {@code null} if no icon was provided + */ + @Nullable + public Icon getIcon() { + return mIcon; + } + + /** + * @return the key provided for this person or {@code null} if no key was provided + */ + @Nullable + public String getKey() { + return mKey; + } + + /** + * @return the URI associated with this person, or "name:mName" otherwise + * @hide + */ + public String resolveToLegacyUri() { + if (mUri != null) { + return mUri; + } + if (mName != null) { + return "name:" + mName; + } + return ""; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, @WriteFlags int flags) { + dest.writeCharSequence(mName); + if (mIcon != null) { + dest.writeInt(1); + mIcon.writeToParcel(dest, 0); + } else { + dest.writeInt(0); + } + dest.writeString(mUri); + dest.writeString(mKey); + } + + public static final Creator<Person> CREATOR = new Creator<Person>() { + @Override + public Person createFromParcel(Parcel in) { + return new Person(in); + } + + @Override + public Person[] newArray(int size) { + return new Person[size]; + } + }; + } + // When adding a new Style subclass here, don't forget to update // Builder.getNotificationStyleClass. @@ -8541,6 +9023,7 @@ public class Notification implements Parcelable boolean ambient = false; CharSequence title; CharSequence text; + CharSequence headerTextSecondary; boolean hideLargeIcon; public boolean alwaysShowReply; @@ -8549,6 +9032,7 @@ public class Notification implements Parcelable ambient = false; title = null; text = null; + headerTextSecondary = null; return this; } @@ -8567,6 +9051,11 @@ public class Notification implements Parcelable return this; } + final StandardTemplateParams headerTextSecondary(CharSequence text) { + this.headerTextSecondary = text; + return this; + } + final StandardTemplateParams alwaysShowReply(boolean alwaysShowReply) { this.alwaysShowReply = alwaysShowReply; return this; diff --git a/android/app/NotificationChannel.java b/android/app/NotificationChannel.java index c06ad3f3..30f2697c 100644 --- a/android/app/NotificationChannel.java +++ b/android/app/NotificationChannel.java @@ -32,8 +32,6 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.util.Preconditions; -import com.android.internal.util.Preconditions; - import org.json.JSONException; import org.json.JSONObject; import org.xmlpull.v1.XmlPullParser; @@ -936,7 +934,9 @@ public final class NotificationChannel implements Parcelable { } /** @hide */ - public void toProto(ProtoOutputStream proto) { + public void writeToProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(NotificationChannelProto.ID, mId); proto.write(NotificationChannelProto.NAME, mName); proto.write(NotificationChannelProto.DESCRIPTION, mDesc); @@ -959,10 +959,10 @@ public final class NotificationChannel implements Parcelable { proto.write(NotificationChannelProto.IS_DELETED, mDeleted); proto.write(NotificationChannelProto.GROUP, mGroup); if (mAudioAttributes != null) { - long aToken = proto.start(NotificationChannelProto.AUDIO_ATTRIBUTES); - mAudioAttributes.toProto(proto); - proto.end(aToken); + mAudioAttributes.writeToProto(proto, NotificationChannelProto.AUDIO_ATTRIBUTES); } proto.write(NotificationChannelProto.IS_BLOCKABLE_SYSTEM, mBlockableSystem); + + proto.end(token); } } diff --git a/android/app/NotificationChannelGroup.java b/android/app/NotificationChannelGroup.java index 5cb7fb7a..16166f7c 100644 --- a/android/app/NotificationChannelGroup.java +++ b/android/app/NotificationChannelGroup.java @@ -298,13 +298,17 @@ public final class NotificationChannelGroup implements Parcelable { } /** @hide */ - public void toProto(ProtoOutputStream proto) { + public void writeToProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(NotificationChannelGroupProto.ID, mId); proto.write(NotificationChannelGroupProto.NAME, mName.toString()); proto.write(NotificationChannelGroupProto.DESCRIPTION, mDescription); proto.write(NotificationChannelGroupProto.IS_BLOCKED, mBlocked); for (NotificationChannel channel : mChannels) { - channel.toProto(proto); + channel.writeToProto(proto, NotificationChannelGroupProto.CHANNELS); } + + proto.end(token); } } diff --git a/android/app/NotificationManager.java b/android/app/NotificationManager.java index 659cf169..49c03ab9 100644 --- a/android/app/NotificationManager.java +++ b/android/app/NotificationManager.java @@ -93,6 +93,18 @@ public class NotificationManager { private static boolean localLOGV = false; /** + * Intent that is broadcast when an application is blocked or unblocked. + * + * This broadcast is only sent to the app whose block state has changed. + * + * Input: nothing + * Output: nothing + */ + @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_APP_BLOCK_STATE_CHANGED = + "android.app.action.APP_BLOCK_STATE_CHANGED"; + + /** * Intent that is broadcast when a {@link NotificationChannel} is blocked * (when {@link NotificationChannel#getImportance()} is {@link #IMPORTANCE_NONE}) or unblocked * (when {@link NotificationChannel#getImportance()} is anything other than @@ -1133,7 +1145,7 @@ public class NotificationManager { } /** @hide */ - public void toProto(ProtoOutputStream proto, long fieldId) { + public void writeToProto(ProtoOutputStream proto, long fieldId) { final long pToken = proto.start(fieldId); bitwiseToProtoEnum(proto, PolicyProto.PRIORITY_CATEGORIES, priorityCategories); diff --git a/android/app/PendingIntent.java b/android/app/PendingIntent.java index 8b76cc7c..d6429ae9 100644 --- a/android/app/PendingIntent.java +++ b/android/app/PendingIntent.java @@ -867,19 +867,30 @@ public final class PendingIntent implements Parcelable { @Nullable OnFinished onFinished, @Nullable Handler handler, @Nullable String requiredPermission, @Nullable Bundle options) throws CanceledException { + if (sendAndReturnResult(context, code, intent, onFinished, handler, requiredPermission, + options) < 0) { + throw new CanceledException(); + } + } + + /** + * Like {@link #send}, but returns the result + * @hide + */ + public int sendAndReturnResult(Context context, int code, @Nullable Intent intent, + @Nullable OnFinished onFinished, @Nullable Handler handler, + @Nullable String requiredPermission, @Nullable Bundle options) + throws CanceledException { try { String resolvedType = intent != null ? intent.resolveTypeIfNeeded(context.getContentResolver()) : null; - int res = ActivityManager.getService().sendIntentSender( + return ActivityManager.getService().sendIntentSender( mTarget, mWhitelistToken, code, intent, resolvedType, onFinished != null ? new FinishedDispatcher(this, onFinished, handler) : null, requiredPermission, options); - if (res < 0) { - throw new CanceledException(); - } } catch (RemoteException e) { throw new CanceledException(e); } diff --git a/android/app/ProfilerInfo.java b/android/app/ProfilerInfo.java index d5234278..0ed1b082 100644 --- a/android/app/ProfilerInfo.java +++ b/android/app/ProfilerInfo.java @@ -20,6 +20,7 @@ import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; import android.util.Slog; +import android.util.proto.ProtoOutputStream; import java.io.IOException; import java.util.Objects; @@ -55,14 +56,24 @@ public class ProfilerInfo implements Parcelable { */ public final String agent; + /** + * Whether the {@link agent} should be attached early (before bind-application) or during + * bind-application. Agents attached prior to binding cannot be loaded from the app's APK + * directly and must be given as an absolute path (or available in the default LD_LIBRARY_PATH). + * Agents attached during bind-application will miss early setup (e.g., resource initialization + * and classloader generation), but are searched in the app's library search path. + */ + public final boolean attachAgentDuringBind; + public ProfilerInfo(String filename, ParcelFileDescriptor fd, int interval, boolean autoStop, - boolean streaming, String agent) { + boolean streaming, String agent, boolean attachAgentDuringBind) { profileFile = filename; profileFd = fd; samplingInterval = interval; autoStopProfiler = autoStop; streamingOutput = streaming; this.agent = agent; + this.attachAgentDuringBind = attachAgentDuringBind; } public ProfilerInfo(ProfilerInfo in) { @@ -72,6 +83,7 @@ public class ProfilerInfo implements Parcelable { autoStopProfiler = in.autoStopProfiler; streamingOutput = in.streamingOutput; agent = in.agent; + attachAgentDuringBind = in.attachAgentDuringBind; } /** @@ -110,6 +122,21 @@ public class ProfilerInfo implements Parcelable { out.writeInt(autoStopProfiler ? 1 : 0); out.writeInt(streamingOutput ? 1 : 0); out.writeString(agent); + out.writeBoolean(attachAgentDuringBind); + } + + /** @hide */ + public void writeToProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(ProfilerInfoProto.PROFILE_FILE, profileFile); + if (profileFd != null) { + proto.write(ProfilerInfoProto.PROFILE_FD, profileFd.getFd()); + } + proto.write(ProfilerInfoProto.SAMPLING_INTERVAL, samplingInterval); + proto.write(ProfilerInfoProto.AUTO_STOP_PROFILER, autoStopProfiler); + proto.write(ProfilerInfoProto.STREAMING_OUTPUT, streamingOutput); + proto.write(ProfilerInfoProto.AGENT, agent); + proto.end(token); } public static final Parcelable.Creator<ProfilerInfo> CREATOR = @@ -132,6 +159,7 @@ public class ProfilerInfo implements Parcelable { autoStopProfiler = in.readInt() != 0; streamingOutput = in.readInt() != 0; agent = in.readString(); + attachAgentDuringBind = in.readBoolean(); } @Override diff --git a/android/app/RemoteInput.java b/android/app/RemoteInput.java index 02a01242..b7100e6f 100644 --- a/android/app/RemoteInput.java +++ b/android/app/RemoteInput.java @@ -24,6 +24,7 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.util.ArraySet; + import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -73,6 +74,15 @@ 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. */ + private static final String EXTRA_RESULTS_SOURCE = "android.remoteinput.resultsSource"; + + /** The user manually entered the data. */ + public static final int SOURCE_FREE_FORM_INPUT = 0; + + /** The user selected one of the choices from {@link #getChoices}. */ + public static final int SOURCE_CHOICE = 1; + // Flags bitwise-ored to mFlags private static final int FLAG_ALLOW_FREE_FORM_INPUT = 0x1; @@ -416,6 +426,48 @@ public final class RemoteInput implements Parcelable { intent.setClipData(ClipData.newIntent(RESULTS_CLIP_LABEL, clipDataIntent)); } + /** + * Set the source of the RemoteInput results. This method should only be called by remote + * input collection services (e.g. + * {@link android.service.notification.NotificationListenerService}) + * when sending results to a pending intent. + * + * @see #SOURCE_FREE_FORM_INPUT + * @see #SOURCE_CHOICE + * + * @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) { + Intent clipDataIntent = getClipDataIntentFromIntent(intent); + if (clipDataIntent == null) { + clipDataIntent = new Intent(); // First time we've added a result. + } + clipDataIntent.putExtra(EXTRA_RESULTS_SOURCE, source); + intent.setClipData(ClipData.newIntent(RESULTS_CLIP_LABEL, clipDataIntent)); + } + + /** + * Get the source of the RemoteInput results. + * + * @see #SOURCE_FREE_FORM_INPUT + * @see #SOURCE_CHOICE + * + * @param intent The intent object that fired in response to an action or content intent + * which also had one or more remote input requested. + * @return The source of the results. If no source was set, {@link #SOURCE_FREE_FORM_INPUT} will + * be returned. + */ + public static int getResultsSource(Intent intent) { + Intent clipDataIntent = getClipDataIntentFromIntent(intent); + if (clipDataIntent == null) { + return SOURCE_FREE_FORM_INPUT; + } + return clipDataIntent.getExtras().getInt(EXTRA_RESULTS_SOURCE, SOURCE_FREE_FORM_INPUT); + } + private static String getExtraResultsKeyForData(String mimeType) { return EXTRA_DATA_TYPE_RESULTS_DATA + mimeType; } diff --git a/android/app/SharedPreferencesImpl.java b/android/app/SharedPreferencesImpl.java index 6dca4004..6ac15a5f 100644 --- a/android/app/SharedPreferencesImpl.java +++ b/android/app/SharedPreferencesImpl.java @@ -50,11 +50,6 @@ import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.FutureTask; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; final class SharedPreferencesImpl implements SharedPreferences { private static final String TAG = "SharedPreferencesImpl"; @@ -74,12 +69,18 @@ final class SharedPreferencesImpl implements SharedPreferences { private final Object mLock = new Object(); private final Object mWritingToDiskLock = new Object(); - private Future<Map<String, Object>> mMap; + @GuardedBy("mLock") + private Map<String, Object> mMap; + @GuardedBy("mLock") + private Throwable mThrowable; @GuardedBy("mLock") private int mDiskWritesInFlight = 0; @GuardedBy("mLock") + private boolean mLoaded = false; + + @GuardedBy("mLock") private StructTimespec mStatTimestamp; @GuardedBy("mLock") @@ -106,18 +107,28 @@ final class SharedPreferencesImpl implements SharedPreferences { mFile = file; mBackupFile = makeBackupFile(file); mMode = mode; + mLoaded = false; mMap = null; + mThrowable = null; startLoadFromDisk(); } private void startLoadFromDisk() { - FutureTask<Map<String, Object>> futureTask = new FutureTask<>(() -> loadFromDisk()); - mMap = futureTask; - new Thread(futureTask, "SharedPreferencesImpl-load").start(); + synchronized (mLock) { + mLoaded = false; + } + new Thread("SharedPreferencesImpl-load") { + public void run() { + loadFromDisk(); + } + }.start(); } - private Map<String, Object> loadFromDisk() { + private void loadFromDisk() { synchronized (mLock) { + if (mLoaded) { + return; + } if (mBackupFile.exists()) { mFile.delete(); mBackupFile.renameTo(mFile); @@ -131,13 +142,14 @@ final class SharedPreferencesImpl implements SharedPreferences { Map<String, Object> map = null; StructStat stat = null; + Throwable thrown = null; try { stat = Os.stat(mFile.getPath()); if (mFile.canRead()) { BufferedInputStream str = null; try { str = new BufferedInputStream( - new FileInputStream(mFile), 16*1024); + new FileInputStream(mFile), 16 * 1024); map = (Map<String, Object>) XmlUtils.readMapXml(str); } catch (Exception e) { Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e); @@ -146,18 +158,37 @@ final class SharedPreferencesImpl implements SharedPreferences { } } } catch (ErrnoException e) { - /* ignore */ + // An errno exception means the stat failed. Treat as empty/non-existing by + // ignoring. + } catch (Throwable t) { + thrown = t; } synchronized (mLock) { - if (map != null) { - mStatTimestamp = stat.st_mtim; - mStatSize = stat.st_size; - } else { - map = new HashMap<>(); + mLoaded = true; + mThrowable = thrown; + + // It's important that we always signal waiters, even if we'll make + // them fail with an exception. The try-finally is pretty wide, but + // better safe than sorry. + try { + if (thrown == null) { + if (map != null) { + mMap = map; + mStatTimestamp = stat.st_mtim; + mStatSize = stat.st_size; + } else { + mMap = new HashMap<>(); + } + } + // In case of a thrown exception, we retain the old map. That allows + // any open editors to commit and store updates. + } catch (Throwable t) { + mThrowable = t; + } finally { + mLock.notifyAll(); } } - return map; } static File makeBackupFile(File prefsFile) { @@ -216,42 +247,40 @@ final class SharedPreferencesImpl implements SharedPreferences { } } - private @GuardedBy("mLock") Map<String, Object> getLoaded() { - // For backwards compatibility, we need to ignore any interrupts. b/70122540. - for (;;) { - try { - return mMap.get(); - } catch (ExecutionException e) { - throw new IllegalStateException(e); - } catch (InterruptedException e) { - // Ignore and try again. - } - } - } - private @GuardedBy("mLock") Map<String, Object> getLoadedWithBlockGuard() { - if (!mMap.isDone()) { + @GuardedBy("mLock") + private void awaitLoadedLocked() { + if (!mLoaded) { // Raise an explicit StrictMode onReadFromDisk for this // thread, since the real read will be in a different // thread and otherwise ignored by StrictMode. BlockGuard.getThreadPolicy().onReadFromDisk(); } - return getLoaded(); + while (!mLoaded) { + try { + mLock.wait(); + } catch (InterruptedException unused) { + } + } + if (mThrowable != null) { + throw new IllegalStateException(mThrowable); + } } @Override public Map<String, ?> getAll() { - Map<String, Object> map = getLoadedWithBlockGuard(); synchronized (mLock) { - return new HashMap<String, Object>(map); + awaitLoadedLocked(); + //noinspection unchecked + return new HashMap<String, Object>(mMap); } } @Override @Nullable public String getString(String key, @Nullable String defValue) { - Map<String, Object> map = getLoadedWithBlockGuard(); synchronized (mLock) { - String v = (String) map.get(key); + awaitLoadedLocked(); + String v = (String)mMap.get(key); return v != null ? v : defValue; } } @@ -259,65 +288,66 @@ final class SharedPreferencesImpl implements SharedPreferences { @Override @Nullable public Set<String> getStringSet(String key, @Nullable Set<String> defValues) { - Map<String, Object> map = getLoadedWithBlockGuard(); synchronized (mLock) { - @SuppressWarnings("unchecked") - Set<String> v = (Set<String>) map.get(key); + awaitLoadedLocked(); + Set<String> v = (Set<String>) mMap.get(key); return v != null ? v : defValues; } } @Override public int getInt(String key, int defValue) { - Map<String, Object> map = getLoadedWithBlockGuard(); synchronized (mLock) { - Integer v = (Integer) map.get(key); + awaitLoadedLocked(); + Integer v = (Integer)mMap.get(key); return v != null ? v : defValue; } } @Override public long getLong(String key, long defValue) { - Map<String, Object> map = getLoadedWithBlockGuard(); synchronized (mLock) { - Long v = (Long) map.get(key); + awaitLoadedLocked(); + Long v = (Long)mMap.get(key); return v != null ? v : defValue; } } @Override public float getFloat(String key, float defValue) { - Map<String, Object> map = getLoadedWithBlockGuard(); synchronized (mLock) { - Float v = (Float) map.get(key); + awaitLoadedLocked(); + Float v = (Float)mMap.get(key); return v != null ? v : defValue; } } @Override public boolean getBoolean(String key, boolean defValue) { - Map<String, Object> map = getLoadedWithBlockGuard(); synchronized (mLock) { - Boolean v = (Boolean) map.get(key); + awaitLoadedLocked(); + Boolean v = (Boolean)mMap.get(key); return v != null ? v : defValue; } } @Override public boolean contains(String key) { - Map<String, Object> map = getLoadedWithBlockGuard(); synchronized (mLock) { - return map.containsKey(key); + awaitLoadedLocked(); + return mMap.containsKey(key); } } @Override public Editor edit() { - // TODO: remove the need to call getLoaded() when + // TODO: remove the need to call awaitLoadedLocked() when // requesting an editor. will require some work on the // Editor, but then we should be able to do: // // context.getSharedPreferences(..).edit().putString(..).apply() // // ... all without blocking. - getLoadedWithBlockGuard(); + synchronized (mLock) { + awaitLoadedLocked(); + } return new EditorImpl(); } @@ -471,43 +501,13 @@ final class SharedPreferencesImpl implements SharedPreferences { // a memory commit comes in when we're already // writing to disk. if (mDiskWritesInFlight > 0) { - // We can't modify our map as a currently + // We can't modify our mMap as a currently // in-flight write owns it. Clone it before // modifying it. // noinspection unchecked - mMap = new Future<Map<String, Object>>() { - private Map<String, Object> mCopiedMap = - new HashMap<String, Object>(getLoaded()); - - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - return false; - } - - @Override - public boolean isCancelled() { - return false; - } - - @Override - public boolean isDone() { - return true; - } - - @Override - public Map<String, Object> get() - throws InterruptedException, ExecutionException { - return mCopiedMap; - } - - @Override - public Map<String, Object> get(long timeout, TimeUnit unit) - throws InterruptedException, ExecutionException, TimeoutException { - return mCopiedMap; - } - }; + mMap = new HashMap<String, Object>(mMap); } - mapToWriteToDisk = getLoaded(); + mapToWriteToDisk = mMap; mDiskWritesInFlight++; boolean hasListeners = mListeners.size() > 0; diff --git a/android/app/StatsManager.java b/android/app/StatsManager.java new file mode 100644 index 00000000..963fc776 --- /dev/null +++ b/android/app/StatsManager.java @@ -0,0 +1,238 @@ +/* + * 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; + +import android.Manifest; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.os.IBinder; +import android.os.IStatsManager; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Slog; + +/** + * API for statsd clients to send configurations and retrieve data. + * + * @hide + */ +@SystemApi +public final class StatsManager extends android.util.StatsManager { // TODO: Remove the extends. + IStatsManager mService; + private static final String TAG = "StatsManager"; + + /** 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. */ + public static final String EXTRA_STATS_SUBSCRIPTION_RULE_ID = + "android.app.extra.STATS_SUBSCRIPTION_RULE_ID"; + /** + * Extra of a {@link android.os.StatsDimensionsValue} representing sliced dimension value + * information. + */ + public static final String EXTRA_STATS_DIMENSIONS_VALUE = + "android.app.extra.STATS_DIMENSIONS_VALUE"; + + /** + * Constructor for StatsManagerClient. + * + * @hide + */ + public StatsManager() { + } + + /** + * Clients can send a configuration and simultaneously registers the name of a broadcast + * receiver that listens for when it should request data. + * + * @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) { + synchronized (this) { + try { + IStatsManager service = getIStatsManagerLocked(); + if (service == null) { + Slog.d(TAG, "Failed to find statsd when adding configuration"); + return false; + } + return service.addConfiguration(configKey, config, pkg, cls); + } catch (RemoteException e) { + Slog.d(TAG, "Failed to connect to statsd when adding configuration"); + return false; + } + } + } + + /** + * Remove a configuration from logging. + * + * @param configKey Configuration key to remove. + * @return true if successful + */ + @RequiresPermission(Manifest.permission.DUMP) + public boolean removeConfiguration(long configKey) { + synchronized (this) { + try { + IStatsManager service = getIStatsManagerLocked(); + if (service == null) { + Slog.d(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"); + return false; + } + } + } + + /** + * 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}. + * + * <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 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) { + synchronized (this) { + try { + IStatsManager service = getIStatsManagerLocked(); + if (service == null) { + Slog.w(TAG, "Failed to find statsd when adding broadcast subscriber"); + return false; + } + if (pendingIntent != null) { + // Extracts IIntentSender from the PendingIntent and turns it into an IBinder. + IBinder intentSender = pendingIntent.getTarget().asBinder(); + return service.setBroadcastSubscriber(configKey, subscriberId, intentSender); + } else { + return service.unsetBroadcastSubscriber(configKey, subscriberId); + } + } catch (RemoteException e) { + Slog.w(TAG, "Failed to connect to statsd when adding broadcast subscriber", e); + return false; + } + } + } + + /** + * Clients can request data with a binder call. This getter is destructive and also clears + * the retrieved metrics from statsd memory. + * + * @param configKey Configuration key to retrieve data from. + * @return Serialized ConfigMetricsReportList proto. Returns null on failure. + */ + @RequiresPermission(Manifest.permission.DUMP) + public byte[] getData(long configKey) { + synchronized (this) { + try { + IStatsManager service = getIStatsManagerLocked(); + if (service == null) { + Slog.d(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"); + return null; + } + } + } + + /** + * Clients can request metadata for statsd. Will contain stats across all configurations but not + * 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. + */ + @RequiresPermission(Manifest.permission.DUMP) + public byte[] getMetadata() { + synchronized (this) { + try { + IStatsManager service = getIStatsManagerLocked(); + if (service == null) { + Slog.d(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"); + return null; + } + } + } + + private class StatsdDeathRecipient implements IBinder.DeathRecipient { + @Override + public void binderDied() { + synchronized (this) { + mService = null; + } + } + } + + private IStatsManager getIStatsManagerLocked() throws RemoteException { + if (mService != null) { + return mService; + } + mService = IStatsManager.Stub.asInterface(ServiceManager.getService("stats")); + if (mService != null) { + mService.asBinder().linkToDeath(new StatsdDeathRecipient(), 0); + } + return mService; + } +} diff --git a/android/app/SystemServiceRegistry.java b/android/app/SystemServiceRegistry.java index 66cf9915..4310434c 100644 --- a/android/app/SystemServiceRegistry.java +++ b/android/app/SystemServiceRegistry.java @@ -38,12 +38,12 @@ import android.content.ClipboardManager; import android.content.Context; import android.content.IRestrictionsManager; import android.content.RestrictionsManager; +import android.content.pm.CrossProfileApps; +import android.content.pm.ICrossProfileApps; import android.content.pm.IShortcutService; import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.content.pm.ShortcutManager; -import android.content.pm.crossprofile.CrossProfileApps; -import android.content.pm.crossprofile.ICrossProfileApps; import android.content.res.Resources; import android.hardware.ConsumerIrManager; import android.hardware.ISerialManager; @@ -112,6 +112,7 @@ import android.os.IBinder; import android.os.IHardwarePropertiesManager; import android.os.IPowerManager; import android.os.IRecoverySystem; +import android.os.ISystemUpdateManager; import android.os.IUserManager; import android.os.IncidentManager; import android.os.PowerManager; @@ -119,6 +120,7 @@ import android.os.Process; import android.os.RecoverySystem; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; +import android.os.SystemUpdateManager; import android.os.SystemVibrator; import android.os.UserHandle; import android.os.UserManager; @@ -136,9 +138,9 @@ import android.telecom.TelecomManager; import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.telephony.euicc.EuiccCardManager; import android.telephony.euicc.EuiccManager; import android.util.Log; -import android.util.StatsManager; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.WindowManager; @@ -484,6 +486,17 @@ final class SystemServiceRegistry { return new StorageStatsManager(ctx, service); }}); + registerService(Context.SYSTEM_UPDATE_SERVICE, SystemUpdateManager.class, + new CachedServiceFetcher<SystemUpdateManager>() { + @Override + public SystemUpdateManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + IBinder b = ServiceManager.getServiceOrThrow( + Context.SYSTEM_UPDATE_SERVICE); + ISystemUpdateManager service = ISystemUpdateManager.Stub.asInterface(b); + return new SystemUpdateManager(service); + }}); + registerService(Context.TELEPHONY_SERVICE, TelephonyManager.class, new CachedServiceFetcher<TelephonyManager>() { @Override @@ -494,7 +507,7 @@ final class SystemServiceRegistry { registerService(Context.TELEPHONY_SUBSCRIPTION_SERVICE, SubscriptionManager.class, new CachedServiceFetcher<SubscriptionManager>() { @Override - public SubscriptionManager createService(ContextImpl ctx) { + public SubscriptionManager createService(ContextImpl ctx) throws ServiceNotFoundException { return new SubscriptionManager(ctx.getOuterContext()); }}); @@ -519,6 +532,13 @@ final class SystemServiceRegistry { return new EuiccManager(ctx.getOuterContext()); }}); + registerService(Context.EUICC_CARD_SERVICE, EuiccCardManager.class, + new CachedServiceFetcher<EuiccCardManager>() { + @Override + public EuiccCardManager createService(ContextImpl ctx) { + return new EuiccCardManager(ctx.getOuterContext()); + }}); + registerService(Context.UI_MODE_SERVICE, UiModeManager.class, new CachedServiceFetcher<UiModeManager>() { @Override diff --git a/android/app/UiAutomation.java b/android/app/UiAutomation.java index 8f016853..ba39740b 100644 --- a/android/app/UiAutomation.java +++ b/android/app/UiAutomation.java @@ -24,7 +24,6 @@ import android.accessibilityservice.IAccessibilityServiceConnection; import android.annotation.NonNull; import android.annotation.TestApi; import android.graphics.Bitmap; -import android.graphics.Canvas; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; @@ -47,10 +46,14 @@ import android.view.accessibility.AccessibilityInteractionClient; 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; @@ -580,6 +583,8 @@ 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. @@ -600,14 +605,14 @@ public final class UiAutomation { if (filter.accept(event)) { return event; } - event.recycle(); + eventsReceived = CollectionUtils.add(eventsReceived, event); } // 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."); + + timeoutMillis + " ms, among " + eventsReceived); } synchronized (mLock) { if (mEventQueue.isEmpty()) { @@ -620,6 +625,10 @@ 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(); diff --git a/android/app/admin/ConnectEvent.java b/android/app/admin/ConnectEvent.java index f06a9257..d511c57b 100644 --- a/android/app/admin/ConnectEvent.java +++ b/android/app/admin/ConnectEvent.java @@ -68,7 +68,7 @@ public final class ConnectEvent extends NetworkEvent implements Parcelable { @Override public String toString() { - return String.format("ConnectEvent(%s, %d, %d, %s)", mIpAddress, mPort, mTimestamp, + return String.format("ConnectEvent(%d, %s, %d, %d, %s)", mId, mIpAddress, mPort, mTimestamp, mPackageName); } diff --git a/android/app/admin/DeviceAdminReceiver.java b/android/app/admin/DeviceAdminReceiver.java index 2e697ac0..28e845a0 100644 --- a/android/app/admin/DeviceAdminReceiver.java +++ b/android/app/admin/DeviceAdminReceiver.java @@ -29,10 +29,14 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; +import android.os.PersistableBundle; 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; @@ -335,7 +339,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { /** * Broadcast action: notify the device owner that a user or profile has been removed. * Carries an extra {@link Intent#EXTRA_USER} that has the {@link UserHandle} of - * the new user. + * the user. * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) @@ -343,6 +347,36 @@ public class DeviceAdminReceiver extends BroadcastReceiver { public static final String ACTION_USER_REMOVED = "android.app.action.USER_REMOVED"; /** + * Broadcast action: notify the device owner that a user or profile has been started. + * Carries an extra {@link Intent#EXTRA_USER} that has the {@link UserHandle} of + * the user. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) + public static final String ACTION_USER_STARTED = "android.app.action.USER_STARTED"; + + /** + * Broadcast action: notify the device owner that a user or profile has been stopped. + * Carries an extra {@link Intent#EXTRA_USER} that has the {@link UserHandle} of + * the user. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) + public static final String ACTION_USER_STOPPED = "android.app.action.USER_STOPPED"; + + /** + * Broadcast action: notify the device owner that a user or profile has been switched to. + * Carries an extra {@link Intent#EXTRA_USER} that has the {@link UserHandle} of + * the user. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) + public static final String ACTION_USER_SWITCHED = "android.app.action.USER_SWITCHED"; + + /** * A string containing the SHA-256 hash of the bugreport file. * * @see #ACTION_BUGREPORT_SHARE @@ -438,6 +472,65 @@ public class DeviceAdminReceiver extends BroadcastReceiver { // TO DO: describe syntax. public static final String DEVICE_ADMIN_META_DATA = "android.app.device_admin"; + /** + * Broadcast action: notify the newly transferred administrator that the transfer + * from the original administrator was successful. + * + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_TRANSFER_OWNERSHIP_COMPLETE = + "android.app.action.TRANSFER_OWNERSHIP_COMPLETE"; + + /** + * Broadcast action: notify the device owner that the ownership of one of its affiliated + * profiles is transferred. + * + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE = + "android.app.action.AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE"; + + /** + * A {@link android.os.Parcelable} extra of type {@link android.os.PersistableBundle} that + * allows a mobile device management application to pass data to the management application + * instance after owner transfer. + * + * <p>If the transfer is successful, the new owner receives the data in + * {@link DeviceAdminReceiver#onTransferOwnershipComplete(Context, PersistableBundle)}. + * The bundle is not changed during the ownership transfer. + * + * @see DevicePolicyManager#transferOwnership(ComponentName, ComponentName, PersistableBundle) + */ + 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; @@ -860,6 +953,76 @@ public class DeviceAdminReceiver extends BroadcastReceiver { } /** + * Called when a user or profile is started. + * + * <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 startedUser The {@link UserHandle} of the user that has just been started. + */ + public void onUserStarted(Context context, Intent intent, UserHandle startedUser) { + } + + /** + * Called when a user or profile is stopped. + * + * <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 stoppedUser The {@link UserHandle} of the user that has just been stopped. + */ + public void onUserStopped(Context context, Intent intent, UserHandle stoppedUser) { + } + + /** + * Called when a user or profile is switched to. + * + * <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 switchedUser The {@link UserHandle} of the user that has just been switched to. + */ + public void onUserSwitched(Context context, Intent intent, UserHandle switchedUser) { + } + + /** + * Called on the newly assigned owner (either device owner or profile owner) when the ownership + * transfer has completed successfully. + * + * <p> The {@code bundle} parameter allows the original owner to pass data + * to the new one. + * + * @param context the running context as per {@link #onReceive} + * @param bundle the data to be passed to the new owner + */ + public void onTransferOwnershipComplete(@NonNull Context context, + @Nullable PersistableBundle bundle) { + } + + /** + * Called on the device owner when the ownership of one of its affiliated profiles is + * transferred. + * + * <p>This can be used when transferring both device and profile ownership when using + * work profile on a fully managed device. The process would look like this: + * <ol> + * <li>Transfer profile ownership</li> + * <li>The device owner gets notified with this callback</li> + * <li>Transfer device ownership</li> + * <li>Both profile and device ownerships have been transferred</li> + * </ol> + * + * @param context the running context as per {@link #onReceive} + * @param user the {@link UserHandle} of the affiliated user + * @see DevicePolicyManager#transferOwnership(ComponentName, ComponentName, PersistableBundle) + */ + public void onTransferAffiliatedProfileOwnershipComplete(Context context, UserHandle user) { + } + + /** * Intercept standard device administrator broadcasts. Implementations * should not override this method; it is better to implement the * convenience callbacks for each action. @@ -921,6 +1084,19 @@ public class DeviceAdminReceiver extends BroadcastReceiver { onUserAdded(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER)); } else if (ACTION_USER_REMOVED.equals(action)) { onUserRemoved(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER)); + } else if (ACTION_USER_STARTED.equals(action)) { + onUserStarted(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER)); + } else if (ACTION_USER_STOPPED.equals(action)) { + onUserStopped(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER)); + } else if (ACTION_USER_SWITCHED.equals(action)) { + onUserSwitched(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER)); + } else if (ACTION_TRANSFER_OWNERSHIP_COMPLETE.equals(action)) { + PersistableBundle bundle = + intent.getParcelableExtra(EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE); + onTransferOwnershipComplete(context, bundle); + } else if (ACTION_AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE.equals(action)) { + onTransferAffiliatedProfileOwnershipComplete(context, + intent.getParcelableExtra(Intent.EXTRA_USER)); } } } diff --git a/android/app/admin/DevicePolicyManager.java b/android/app/admin/DevicePolicyManager.java index 7e80ac7b..8f76032b 100644 --- a/android/app/admin/DevicePolicyManager.java +++ b/android/app/admin/DevicePolicyManager.java @@ -18,7 +18,6 @@ package android.app.admin; import android.annotation.CallbackExecutor; import android.annotation.ColorInt; -import android.annotation.Condemned; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -50,8 +49,6 @@ import android.graphics.Bitmap; import android.net.ProxyInfo; import android.net.Uri; import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerExecutor; import android.os.Parcelable; import android.os.PersistableBundle; import android.os.Process; @@ -71,6 +68,7 @@ import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.ParcelableKeyGenParameterSpec; import android.service.restrictions.RestrictionsReceiver; import android.telephony.TelephonyManager; +import android.telephony.data.ApnSetting; import android.util.ArraySet; import android.util.Log; @@ -1124,6 +1122,7 @@ public class DevicePolicyManager { * * This broadcast is sent only to the primary user. * @see #ACTION_PROVISION_MANAGED_DEVICE + * @see DevicePolicyManager#transferOwnership(ComponentName, ComponentName, PersistableBundle) */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_DEVICE_OWNER_CHANGED @@ -1159,9 +1158,17 @@ public class DevicePolicyManager { public static final String POLICY_DISABLE_SCREEN_CAPTURE = "policy_disable_screen_capture"; /** + * Constant to indicate the feature of mandatory backups. Used as argument to + * {@link #createAdminSupportIntent(String)}. + * @see #setMandatoryBackupTransport(ComponentName, ComponentName) + */ + public static final String POLICY_MANDATORY_BACKUPS = "policy_mandatory_backups"; + + /** * A String indicating a specific restricted feature. Can be a user restriction from the * {@link UserManager}, e.g. {@link UserManager#DISALLOW_ADJUST_VOLUME}, or one of the values - * {@link #POLICY_DISABLE_CAMERA} or {@link #POLICY_DISABLE_SCREEN_CAPTURE}. + * {@link #POLICY_DISABLE_CAMERA}, {@link #POLICY_DISABLE_SCREEN_CAPTURE} or + * {@link #POLICY_MANDATORY_BACKUPS}. * @see #createAdminSupportIntent(String) * @hide */ @@ -1253,6 +1260,26 @@ public class DevicePolicyManager { = "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED"; /** + * Broadcast action to notify ManagedProvisioning that + * {@link UserManager#DISALLOW_SHARE_INTO_MANAGED_PROFILE} restriction has changed. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DATA_SHARING_RESTRICTION_CHANGED = + "android.app.action.DATA_SHARING_RESTRICTION_CHANGED"; + + /** + * Broadcast action from ManagedProvisioning to notify that the latest change to + * {@link UserManager#DISALLOW_SHARE_INTO_MANAGED_PROFILE} restriction has been successfully + * applied (cross profile intent filters updated). Only usesd for CTS tests. + * @hide + */ + @TestApi + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = + "android.app.action.DATA_SHARING_RESTRICTION_APPLIED"; + + /** * Permission policy to prompt user for new permission requests for runtime permissions. * Already granted or denied permissions are not affected by this. */ @@ -1668,6 +1695,56 @@ public class DevicePolicyManager { public static final String ACTION_DEVICE_ADMIN_SERVICE = "android.app.action.DEVICE_ADMIN_SERVICE"; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = {"ID_TYPE_"}, value = { + ID_TYPE_BASE_INFO, + ID_TYPE_SERIAL, + ID_TYPE_IMEI, + ID_TYPE_MEID + }) + public @interface AttestationIdType {} + + /** + * Specifies that the device should attest its manufacturer details. For use with + * {@link #generateKeyPair}. + * + * @see #generateKeyPair + */ + public static final int ID_TYPE_BASE_INFO = 1; + + /** + * Specifies that the device should attest its serial number. For use with + * {@link #generateKeyPair}. + * + * @see #generateKeyPair + */ + public static final int ID_TYPE_SERIAL = 2; + + /** + * Specifies that the device should attest its IMEI. For use with {@link #generateKeyPair}. + * + * @see #generateKeyPair + */ + public static final int ID_TYPE_IMEI = 4; + + /** + * Specifies that the device should attest its MEID. For use with {@link #generateKeyPair}. + * + * @see #generateKeyPair + */ + public static final int ID_TYPE_MEID = 8; + + /** + * 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. + * @see DevicePolicyManager#transferOwnership(ComponentName, ComponentName, PersistableBundle) + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_PROFILE_OWNER_CHANGED = + "android.app.action.PROFILE_OWNER_CHANGED"; + /** * Return true if the given administrator component is currently active (enabled) in the system. * @@ -4106,22 +4183,46 @@ public class DevicePolicyManager { * @param algorithm The key generation algorithm, see {@link java.security.KeyPairGenerator}. * @param keySpec Specification of the key to generate, see * {@link java.security.KeyPairGenerator}. + * @param idAttestationFlags A bitmask of all the identifiers that should be included in the + * attestation record ({@code ID_TYPE_BASE_INFO}, {@code ID_TYPE_SERIAL}, + * {@code ID_TYPE_IMEI} and {@code ID_TYPE_MEID}), or {@code 0} if no device + * identification is required in the attestation record. + * Device owner, profile owner and their delegated certificate installer can use + * {@link #ID_TYPE_BASE_INFO} to request inclusion of the general device information + * including manufacturer, model, brand, device and product in the attestation record. + * Only device owner and their delegated certificate installer can use + * {@link #ID_TYPE_SERIAL}, {@link #ID_TYPE_IMEI} and {@link #ID_TYPE_MEID} to request + * unique device identifiers to be attested. + * <p> + * If any of {@link #ID_TYPE_SERIAL}, {@link #ID_TYPE_IMEI} and {@link #ID_TYPE_MEID} + * is set, it is implicitly assumed that {@link #ID_TYPE_BASE_INFO} is also set. + * <p> + * If any flag is specified, then an attestation challenge must be included in the + * {@code keySpec}. * @return A non-null {@code AttestedKeyPair} if the key generation succeeded, null otherwise. * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile - * owner. - * @throws IllegalArgumentException if the alias in {@code keySpec} is empty, or if the + * owner. If Device ID attestation is requested (using {@link #ID_TYPE_SERIAL}, + * {@link #ID_TYPE_IMEI} or {@link #ID_TYPE_MEID}), the caller must be the Device Owner + * or the Certificate Installer delegate. + * @throws IllegalArgumentException if the alias in {@code keySpec} is empty, if the * algorithm specification in {@code keySpec} is not {@code RSAKeyGenParameterSpec} - * or {@code ECGenParameterSpec}. + * or {@code ECGenParameterSpec}, or if Device ID attestation was requested but the + * {@code keySpec} does not contain an attestation challenge. + * @see KeyGenParameterSpec.Builder#setAttestationChallenge(byte[]) */ public AttestedKeyPair generateKeyPair(@Nullable ComponentName admin, - @NonNull String algorithm, @NonNull KeyGenParameterSpec keySpec) { + @NonNull String algorithm, @NonNull KeyGenParameterSpec keySpec, + @AttestationIdType int idAttestationFlags) { throwIfParentInstance("generateKeyPair"); try { final ParcelableKeyGenParameterSpec parcelableSpec = new ParcelableKeyGenParameterSpec(keySpec); KeymasterCertificateChain attestationChain = new KeymasterCertificateChain(); + + // Translate ID attestation flags to values used by AttestationUtils final boolean success = mService.generateKeyPair( - admin, mContext.getPackageName(), algorithm, parcelableSpec, attestationChain); + admin, mContext.getPackageName(), algorithm, parcelableSpec, + idAttestationFlags, attestationChain); if (!success) { Log.e(TAG, "Error generating key via DevicePolicyManagerService."); return null; @@ -5982,6 +6083,13 @@ public class DevicePolicyManager { * Called by a profile owner of a managed profile to remove the cross-profile intent filters * that go from the managed profile to the parent, or from the parent to the managed profile. * Only removes those that have been set by the profile owner. + * <p> + * <em>Note</em>: A list of default cross profile intent filters are set up by the system when + * the profile is created, some of them ensure the proper functioning of the profile, while + * others enable sharing of data from the parent to the managed profile for user convenience. + * These default intent filters are not cleared when this API is called. If the default cross + * profile data sharing is not desired, they can be disabled with + * {@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. @@ -6404,12 +6512,6 @@ public class DevicePolicyManager { public static final int MAKE_USER_DEMO = 0x0004; /** - * Flag used by {@link #createAndManageUser} to specify that the newly created user should be - * started in the background as part of the user creation. - */ - public static final int START_USER_IN_BACKGROUND = 0x0008; - - /** * Flag used by {@link #createAndManageUser} to specify that the newly created user should skip * the disabling of system apps during provisioning. */ @@ -6422,7 +6524,6 @@ public class DevicePolicyManager { SKIP_SETUP_WIZARD, MAKE_USER_EPHEMERAL, MAKE_USER_DEMO, - START_USER_IN_BACKGROUND, LEAVE_ALL_SYSTEM_APPS_ENABLED }) @Retention(RetentionPolicy.SOURCE) @@ -6451,7 +6552,8 @@ public class DevicePolicyManager { * IllegalArgumentException is thrown. * @param adminExtras Extras that will be passed to onEnable of the admin receiver on the new * user. - * @param flags {@link #SKIP_SETUP_WIZARD} is supported. + * @param flags {@link #SKIP_SETUP_WIZARD}, {@link #MAKE_USER_EPHEMERAL} and + * {@link #LEAVE_ALL_SYSTEM_APPS_ENABLED} are supported. * @see UserHandle * @return the {@link android.os.UserHandle} object for the created user, or {@code null} if the * user could not be created. @@ -6470,8 +6572,8 @@ public class DevicePolicyManager { } /** - * Called by a device owner to remove a user and all associated data. The primary user can not - * be removed. + * Called by a device owner to remove a user/profile and all associated data. The primary user + * can not be removed. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param userHandle the user to remove. @@ -6488,14 +6590,14 @@ public class DevicePolicyManager { } /** - * Called by a device owner to switch the specified user to the foreground. - * <p> This cannot be used to switch to a managed profile. + * Called by a device owner to switch the specified secondary user to the foreground. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param userHandle the user to switch to; null will switch to primary. * @return {@code true} if the switch was successful, {@code false} otherwise. * @throws SecurityException if {@code admin} is not a device owner. * @see Intent#ACTION_USER_FOREGROUND + * @see #getSecondaryUsers(ComponentName) */ public boolean switchUser(@NonNull ComponentName admin, @Nullable UserHandle userHandle) { throwIfParentInstance("switchUser"); @@ -6507,13 +6609,32 @@ 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. + * @throws SecurityException if {@code admin} is not a device owner. + * @see #getSecondaryUsers(ComponentName) + */ + public boolean startUserInBackground( + @NonNull ComponentName admin, @NonNull UserHandle userHandle) { + throwIfParentInstance("startUserInBackground"); + try { + return mService.startUserInBackground(admin, userHandle); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Called by a device owner to stop the specified secondary user. - * <p> This cannot be used to stop the primary user or a managed profile. * * @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. * @throws SecurityException if {@code admin} is not a device owner. + * @see #getSecondaryUsers(ComponentName) */ public boolean stopUser(@NonNull ComponentName admin, @NonNull UserHandle userHandle) { throwIfParentInstance("stopUser"); @@ -6525,14 +6646,13 @@ public class DevicePolicyManager { } /** - * Called by a profile owner that is affiliated with the device to stop the calling user - * and switch back to primary. - * <p> This has no effect when called on a managed profile. + * Called by a profile owner of secondary user that is affiliated with the device to stop the + * 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. * @throws SecurityException if {@code admin} is not a profile owner affiliated with the device. - * @see #isAffiliatedUser + * @see #getSecondaryUsers(ComponentName) */ public boolean logoutUser(@NonNull ComponentName admin) { throwIfParentInstance("logoutUser"); @@ -6544,17 +6664,18 @@ public class DevicePolicyManager { } /** - * Called by a device owner to list all secondary users on the device, excluding managed - * profiles. + * Called by a device owner to list all secondary users on the device. Managed profiles are not + * considered as secondary users. * <p> Used for various user management APIs, including {@link #switchUser}, {@link #removeUser} * and {@link #stopUser}. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @return list of other {@link UserHandle}s on the device. * @throws SecurityException if {@code admin} is not a device owner. - * @see #switchUser - * @see #removeUser - * @see #stopUser + * @see #removeUser(ComponentName, UserHandle) + * @see #switchUser(ComponentName, UserHandle) + * @see #startUserInBackground(ComponentName, UserHandle) + * @see #stopUser(ComponentName, UserHandle) */ public List<UserHandle> getSecondaryUsers(@NonNull ComponentName admin) { throwIfParentInstance("getSecondaryUsers"); @@ -6694,7 +6815,8 @@ public class DevicePolicyManager { * @param restriction Indicates for which feature the dialog should be displayed. Can be a * user restriction from {@link UserManager}, e.g. * {@link UserManager#DISALLOW_ADJUST_VOLUME}, or one of the constants - * {@link #POLICY_DISABLE_CAMERA} or {@link #POLICY_DISABLE_SCREEN_CAPTURE}. + * {@link #POLICY_DISABLE_CAMERA}, {@link #POLICY_DISABLE_SCREEN_CAPTURE} or + * {@link #POLICY_MANDATORY_BACKUPS}. * @return Intent An intent to be used to start the dialog-activity if the restriction is * set by an admin, or null if the restriction does not exist or no admin set it. */ @@ -6915,14 +7037,14 @@ public class DevicePolicyManager { * task. From {@link android.os.Build.VERSION_CODES#M} removing packages from the lock task * package list results in locked tasks belonging to those packages to be finished. * <p> - * This function can only be called by the device owner or by a profile owner of a user/profile - * that is affiliated with the device. See {@link #isAffiliatedUser}. Any packages - * set via this method will be cleared if the user becomes unaffiliated. + * 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 package set via this method will be cleared if the user becomes unaffiliated. * * @param packages The list of packages allowed to enter lock task mode * @param admin Which {@link DeviceAdminReceiver} this request is associated with. - * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of - * an affiliated user or profile. + * @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 * @see Activity#startLockTask() * @see DeviceAdminReceiver#onLockTaskModeEntering(Context, Intent, String) @@ -6944,8 +7066,8 @@ public class DevicePolicyManager { /** * Returns the list of packages allowed to start the lock task mode. * - * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of - * an affiliated user or profile. + * @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 * @see #setLockTaskPackages */ @@ -6985,9 +7107,9 @@ public class DevicePolicyManager { * 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 or by a profile owner of a user/profile - * that is affiliated with the device. See {@link #isAffiliatedUser}. Any features - * set via this method will be cleared if the user becomes unaffiliated. + * 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. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param flags Bitfield of feature flags: @@ -6998,9 +7120,10 @@ public class DevicePolicyManager { * {@link #LOCK_TASK_FEATURE_RECENTS}, * {@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS}, * {@link #LOCK_TASK_FEATURE_KEYGUARD} - * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of - * an affiliated user or profile. + * @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"); @@ -7018,8 +7141,8 @@ public class DevicePolicyManager { * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @return bitfield of flags. See {@link #setLockTaskFeatures(ComponentName, int)} for a list. - * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of - * an affiliated user or profile. + * @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 * @see #setLockTaskFeatures */ @@ -7454,7 +7577,8 @@ public class DevicePolicyManager { } /** - * Called by a device owner to disable the keyguard altogether. + * Called by a device owner or profile owner of secondary users that is affiliated with the + * device to disable the keyguard altogether. * <p> * Setting the keyguard to disabled has the same effect as choosing "None" as the screen lock * type. However, this call has no effect if a password, pin or pattern is currently set. If a @@ -7469,7 +7593,10 @@ public class DevicePolicyManager { * @param disabled {@code true} disables the keyguard, {@code false} reenables it. * @return {@code false} if attempting to disable the keyguard while a lock password was in * place. {@code true} otherwise. - * @throws SecurityException if {@code admin} is not a device owner. + * @throws SecurityException if {@code admin} is not the device owner, or a profile owner of + * secondary user that is affiliated with the device. + * @see #isAffiliatedUser + * @see #getSecondaryUsers */ public boolean setKeyguardDisabled(@NonNull ComponentName admin, boolean disabled) { throwIfParentInstance("setKeyguardDisabled"); @@ -7481,9 +7608,9 @@ public class DevicePolicyManager { } /** - * Called by device owner to disable the status bar. Disabling the status bar blocks - * notifications, quick settings and other screen overlays that allow escaping from a single use - * device. + * Called by device owner or profile owner of secondary users that is affiliated with the + * device to disable the status bar. Disabling the status bar blocks notifications, quick + * settings and other screen overlays that allow escaping from a single use device. * <p> * <strong>Note:</strong> This method has no effect for LockTask mode. The behavior of the * status bar in LockTask mode can be configured with @@ -7494,7 +7621,10 @@ public class DevicePolicyManager { * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param disabled {@code true} disables the status bar, {@code false} reenables it. * @return {@code false} if attempting to disable the status bar failed. {@code true} otherwise. - * @throws SecurityException if {@code admin} is not a device owner. + * @throws SecurityException if {@code admin} is not the device owner, or a profile owner of + * secondary user that is affiliated with the device. + * @see #isAffiliatedUser + * @see #getSecondaryUsers */ public boolean setStatusBarDisabled(@NonNull ComponentName admin, boolean disabled) { throwIfParentInstance("setStatusBarDisabled"); @@ -8100,6 +8230,47 @@ public class DevicePolicyManager { } /** + * Called by a device or profile owner to restrict packages from accessing 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, + @NonNull List<String> packageNames) { + throwIfParentInstance("setMeteredDataDisabled"); + if (mService != null) { + try { + return mService.setMeteredDataDisabled(admin, packageNames); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + return packageNames; + } + + /** + * Called by a device or profile owner to retrieve the list of packages which are restricted + * by the admin from accessing 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) { + throwIfParentInstance("getMeteredDataDisabled"); + if (mService != null) { + try { + return mService.getMeteredDataDisabled(admin); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + return new ArrayList<>(); + } + + /** * 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 @@ -8511,6 +8682,13 @@ public class DevicePolicyManager { * * <p> Backup service is off by default when device owner is present. * + * <p> If backups are made mandatory by specifying a non-null mandatory backup transport using + * the {@link DevicePolicyManager#setMandatoryBackupTransport} method, the backup service is + * automatically enabled. + * + * <p> If the backup service is disabled using this method after the mandatory backup transport + * has been set, the mandatory backup transport is cleared. + * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param enabled {@code true} to enable the backup service, {@code false} to disable it. * @throws SecurityException if {@code admin} is not a device owner. @@ -8542,6 +8720,43 @@ public class DevicePolicyManager { } /** + * Makes backups mandatory and enforces the usage of the specified backup transport. + * + * <p>When a {@code null} backup transport is specified, backups are made optional again. + * <p>Only device owner can call this method. + * <p>If backups were disabled and a non-null backup transport {@link ComponentName} is + * specified, backups will be enabled. + * + * @param admin admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param backupTransportComponent The backup transport layer to be used for mandatory backups. + * @throws SecurityException if {@code admin} is not a device owner. + */ + public void setMandatoryBackupTransport( + @NonNull ComponentName admin, @Nullable ComponentName backupTransportComponent) { + try { + mService.setMandatoryBackupTransport(admin, backupTransportComponent); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Returns the backup transport which has to be used for backups if backups are mandatory or + * {@code null} if backups are not mandatory. + * + * @return a {@link ComponentName} of the backup transport layer to be used if backups are + * mandatory or {@code null} if backups are not mandatory. + */ + public ComponentName getMandatoryBackupTransport() { + try { + return mService.getMandatoryBackupTransport(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + + /** * Called by a device owner to control the network logging feature. * * <p> Network logs contain DNS lookup and connect() library call events. The following library @@ -8817,15 +9032,6 @@ public class DevicePolicyManager { } } - /** {@hide} */ - @Condemned - @Deprecated - public boolean clearApplicationUserData(@NonNull ComponentName admin, - @NonNull String packageName, @NonNull OnClearApplicationUserDataListener listener, - @NonNull Handler handler) { - return clearApplicationUserData(admin, packageName, listener, new HandlerExecutor(handler)); - } - /** * Called by the device owner or profile owner to clear application user data of a given * package. The behaviour of this is equivalent to the target application calling @@ -8836,14 +9042,14 @@ public class DevicePolicyManager { * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param packageName The name of the package which will have its user data wiped. - * @param listener A callback object that will inform the caller when the clearing is done. * @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, - @NonNull String packageName, @NonNull OnClearApplicationUserDataListener listener, - @NonNull @CallbackExecutor Executor executor) { + @NonNull String packageName, @NonNull @CallbackExecutor Executor executor, + @NonNull OnClearApplicationUserDataListener listener) { throwIfParentInstance("clearAppData"); Preconditions.checkNotNull(executor); try { @@ -8926,41 +9132,312 @@ public class DevicePolicyManager { } } - //TODO STOPSHIP Add link to onTransferComplete callback when implemented. /** - * Transfers the current administrator. All policies from the current administrator are - * migrated to the new administrator. The whole operation is atomic - the transfer is either - * complete or not done at all. + * Changes the current administrator to another one. All policies from the current + * administrator are migrated to the new administrator. The whole operation is atomic - + * the transfer is either complete or not done at all. * - * Depending on the current administrator (device owner, profile owner, corporate owned - * profile owner), you have the following expected behaviour: + * <p>Depending on the current administrator (device owner, profile owner), you have the + * following expected behaviour: * <ul> * <li>A device owner can only be transferred to a new device owner</li> * <li>A profile owner can only be transferred to a new profile owner</li> - * <li>A corporate owned managed profile can have two cases: - * <ul> - * <li>If the device owner and profile owner are the same package, - * both will be transferred.</li> - * <li>If the device owner and profile owner are different packages, - * and if this method is called from the profile owner, only the profile owner - * is transferred. Similarly, if it is called from the device owner, only - * the device owner is transferred.</li> - * </ul> - * </li> * </ul> * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. - * @param target Which {@link DeviceAdminReceiver} we want the new administrator to be. - * @param bundle Parameters - This bundle allows the current administrator to pass data to the - * new administrator. The parameters will be received in the - * onTransferComplete callback. - * @hide + * <p>Use the {@code bundle} parameter to pass data to the new administrator. The data + * will be received in the + * {@link DeviceAdminReceiver#onTransferOwnershipComplete(Context, PersistableBundle)} + * callback of the new administrator. + * + * <p>The transfer has failed if the original administrator is still the corresponding owner + * 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. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with + * @param target which {@link DeviceAdminReceiver} we want the new administrator to be + * @param bundle data to be sent to the new administrator + * @throws SecurityException if {@code admin} is not a device owner nor a profile owner + * @throws IllegalArgumentException if {@code admin} or {@code target} is {@code null}, they + * are components in the same package or {@code target} is not an active admin + */ + public void transferOwnership(@NonNull ComponentName admin, @NonNull ComponentName target, + @Nullable PersistableBundle bundle) { + throwIfParentInstance("transferOwnership"); + try { + mService.transferOwnership(admin, target, bundle); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Called by a device owner to specify the user session start message. This may be displayed + * during a user switch. + * <p> + * The message should be limited to a short statement or it may be truncated. + * <p> + * If the message needs to be localized, it is the responsibility of the + * {@link DeviceAdminReceiver} to listen to the {@link Intent#ACTION_LOCALE_CHANGED} broadcast + * and set a new version of this message accordingly. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with. + * @param startUserSessionMessage message for starting user session, or {@code null} to use + * system default message. + * @throws SecurityException if {@code admin} is not a device owner. + */ + public void setStartUserSessionMessage( + @NonNull ComponentName admin, @Nullable CharSequence startUserSessionMessage) { + throwIfParentInstance("setStartUserSessionMessage"); + try { + mService.setStartUserSessionMessage(admin, startUserSessionMessage); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Called by a device owner to specify the user session end message. This may be displayed + * during a user switch. + * <p> + * The message should be limited to a short statement or it may be truncated. + * <p> + * If the message needs to be localized, it is the responsibility of the + * {@link DeviceAdminReceiver} to listen to the {@link Intent#ACTION_LOCALE_CHANGED} broadcast + * and set a new version of this message accordingly. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with. + * @param endUserSessionMessage message for ending user session, or {@code null} to use system + * default message. + * @throws SecurityException if {@code admin} is not a device owner. + */ + public void setEndUserSessionMessage( + @NonNull ComponentName admin, @Nullable CharSequence endUserSessionMessage) { + throwIfParentInstance("setEndUserSessionMessage"); + try { + mService.setEndUserSessionMessage(admin, endUserSessionMessage); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Returns the user session start message. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with. + * @throws SecurityException if {@code admin} is not a device owner. + */ + public CharSequence getStartUserSessionMessage(@NonNull ComponentName admin) { + throwIfParentInstance("getStartUserSessionMessage"); + try { + return mService.getStartUserSessionMessage(admin); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Returns the user session end message. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with. + * @throws SecurityException if {@code admin} is not a device owner. + */ + public CharSequence getEndUserSessionMessage(@NonNull ComponentName admin) { + throwIfParentInstance("getEndUserSessionMessage"); + try { + return mService.getEndUserSessionMessage(admin); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * 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. + * + * @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 + * the database. + * @throws SecurityException if {@code admin} is not a device owner. + * + * @see #setOverrideApnsEnabled(ComponentName, boolean) + */ + public int addOverrideApn(@NonNull ComponentName admin, @NonNull ApnSetting apnSetting) { + throwIfParentInstance("addOverrideApn"); + if (mService != null) { + try { + return mService.addOverrideApn(admin, apnSetting); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return -1; + } + + /** + * Called by device owner to update an override APN. + * + * @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 + * @return {@code true} if the required override APN is successfully updated, + * {@code false} otherwise. + * @throws SecurityException if {@code admin} is not a device owner. + * + * @see #setOverrideApnsEnabled(ComponentName, boolean) + */ + public boolean updateOverrideApn(@NonNull ComponentName admin, int apnId, + @NonNull ApnSetting apnSetting) { + throwIfParentInstance("updateOverrideApn"); + if (mService != null) { + try { + return mService.updateOverrideApn(admin, apnId, apnSetting); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return false; + } + + /** + * Called by device owner to remove an override APN. + * + * @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} + * otherwise. + * @throws SecurityException if {@code admin} is not a device owner. + * + * @see #setOverrideApnsEnabled(ComponentName, boolean) + */ + public boolean removeOverrideApn(@NonNull ComponentName admin, int apnId) { + throwIfParentInstance("removeOverrideApn"); + if (mService != null) { + try { + return mService.removeOverrideApn(admin, apnId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return false; + } + + /** + * Called by device owner to get all override APNs inserted by device owner. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with + * @return A list of override APNs inserted by device owner. + * @throws SecurityException if {@code admin} is not a device owner. + * + * @see #setOverrideApnsEnabled(ComponentName, boolean) + */ + public List<ApnSetting> getOverrideApns(@NonNull ComponentName admin) { + throwIfParentInstance("getOverrideApns"); + if (mService != null) { + try { + return mService.getOverrideApns(admin); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return Collections.emptyList(); + } + + /** + * Called by device owner to set if override APNs should be enabled. + * <p> Override APNs are separated from other APNs on the device, and can only be inserted or + * modified by the device owner. When enabled, only override APNs are in use, any other APNs + * are ignored. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with + * @param enabled {@code true} if override APNs should be enabled, {@code false} otherwise + * @throws SecurityException if {@code admin} is not a device owner. + */ + public void setOverrideApnsEnabled(@NonNull ComponentName admin, boolean enabled) { + throwIfParentInstance("setOverrideApnEnabled"); + if (mService != null) { + try { + mService.setOverrideApnsEnabled(admin, enabled); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Called by device owner to check if override APNs are currently enabled. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with + * @return {@code true} if override APNs are currently enabled, {@code false} otherwise. + * @throws SecurityException if {@code admin} is not a device owner. + * + * @see #setOverrideApnsEnabled(ComponentName, boolean) + */ + public boolean isOverrideApnEnabled(@NonNull ComponentName admin) { + throwIfParentInstance("isOverrideApnEnabled"); + if (mService != null) { + try { + return mService.isOverrideApnEnabled(admin); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return false; + } + + /** + * 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)}. + * + * <p>Returns <code>null</code> if no ownership transfer was started for the calling user. + * + * @see #transferOwnership + * @see DeviceAdminReceiver#onTransferOwnershipComplete(Context, PersistableBundle) */ - public void transferOwner(@NonNull ComponentName admin, @NonNull ComponentName target, - PersistableBundle bundle) { - throwIfParentInstance("transferOwner"); + @Nullable + public PersistableBundle getTransferOwnershipBundle() { + throwIfParentInstance("getTransferOwnershipBundle"); try { - mService.transferOwner(admin, target, bundle); + return mService.getTransferOwnershipBundle(); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } diff --git a/android/app/admin/DevicePolicyManagerInternal.java b/android/app/admin/DevicePolicyManagerInternal.java index b692ffd9..ebaf4648 100644 --- a/android/app/admin/DevicePolicyManagerInternal.java +++ b/android/app/admin/DevicePolicyManagerInternal.java @@ -123,4 +123,22 @@ public abstract class DevicePolicyManagerInternal { * @param userId User ID of the profile. */ public abstract void reportSeparateProfileChallengeChanged(@UserIdInt int userId); + + /** + * Check whether the user could have their password reset in an untrusted manor due to there + * being an admin which can call {@link #resetPassword} to reset the password without knowledge + * of the previous password. + * + * @param userId The user in question + */ + public abstract boolean canUserHaveUntrustedCredentialReset(@UserIdInt int userId); + + /** + * Return text of error message if printing is disabled. + * Called by Print Service when printing is disabled by PO or DO when printing is attempted. + * + * @param userId The user in question + * @return localized error message + */ + public abstract CharSequence getPrintingDisabledReasonForUser(@UserIdInt int userId); } diff --git a/android/app/admin/DnsEvent.java b/android/app/admin/DnsEvent.java index 4ddf13e0..a2d704b8 100644 --- a/android/app/admin/DnsEvent.java +++ b/android/app/admin/DnsEvent.java @@ -96,7 +96,7 @@ public final class DnsEvent extends NetworkEvent implements Parcelable { @Override public String toString() { - return String.format("DnsEvent(%s, %s, %d, %d, %s)", mHostname, + return String.format("DnsEvent(%d, %s, %s, %d, %d, %s)", mId, mHostname, (mIpAddresses == null) ? "NONE" : String.join(" ", mIpAddresses), mIpAddressesCount, mTimestamp, mPackageName); } diff --git a/android/app/assist/AssistStructure.java b/android/app/assist/AssistStructure.java index 7b549cd5..87f22712 100644 --- a/android/app/assist/AssistStructure.java +++ b/android/app/assist/AssistStructure.java @@ -32,6 +32,8 @@ import android.view.WindowManagerGlobal; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; +import com.android.internal.util.Preconditions; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -624,6 +626,7 @@ public class AssistStructure implements Parcelable { int mMinEms = -1; int mMaxEms = -1; int mMaxLength = -1; + @Nullable String mTextIdEntry; // POJO used to override some autofill-related values when the node is parcelized. // Not written to parcel. @@ -701,7 +704,7 @@ public class AssistStructure implements Parcelable { final int flags = mFlags; if ((flags&FLAGS_HAS_ID) != 0) { mId = in.readInt(); - if (mId != 0) { + if (mId != View.NO_ID) { mIdEntry = preader.readString(); if (mIdEntry != null) { mIdType = preader.readString(); @@ -724,6 +727,7 @@ public class AssistStructure implements Parcelable { mMinEms = in.readInt(); mMaxEms = in.readInt(); mMaxLength = in.readInt(); + mTextIdEntry = preader.readString(); } if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) { mX = in.readInt(); @@ -857,7 +861,7 @@ public class AssistStructure implements Parcelable { out.writeInt(writtenFlags); if ((flags&FLAGS_HAS_ID) != 0) { out.writeInt(mId); - if (mId != 0) { + if (mId != View.NO_ID) { pwriter.writeString(mIdEntry); if (mIdEntry != null) { pwriter.writeString(mIdType); @@ -890,6 +894,7 @@ public class AssistStructure implements Parcelable { out.writeInt(mMinEms); out.writeInt(mMaxEms); out.writeInt(mMaxLength); + pwriter.writeString(mTextIdEntry); } if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) { out.writeInt(mX); @@ -1430,6 +1435,17 @@ public class AssistStructure implements Parcelable { } /** + * Gets the identifier used to set the text associated with this view. + * + * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes, + * not for assist purposes. + */ + @Nullable + public String getTextIdEntry() { + return mTextIdEntry; + } + + /** * Return additional hint text associated with the node; this is typically used with * a node that takes user input, describing to the user what the input means. */ @@ -1684,6 +1700,11 @@ public class AssistStructure implements Parcelable { } @Override + public void setTextIdEntry(@NonNull String entryName) { + mNode.mTextIdEntry = Preconditions.checkNotNull(entryName); + } + + @Override public void setHint(CharSequence hint) { getNodeText().mHint = hint != null ? hint.toString() : null; } @@ -2082,6 +2103,7 @@ public class AssistStructure implements Parcelable { Log.i(TAG, prefix + " Text color fg: #" + Integer.toHexString(node.getTextColor()) + ", bg: #" + Integer.toHexString(node.getTextBackgroundColor())); Log.i(TAG, prefix + " Input type: " + node.getInputType()); + Log.i(TAG, prefix + " Resource id: " + node.getTextIdEntry()); } String webDomain = node.getWebDomain(); if (webDomain != null) { diff --git a/android/app/backup/BackupManager.java b/android/app/backup/BackupManager.java index 6512b98c..12f44831 100644 --- a/android/app/backup/BackupManager.java +++ b/android/app/backup/BackupManager.java @@ -27,6 +27,7 @@ import android.os.Handler; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.util.Log; import android.util.Pair; @@ -387,6 +388,29 @@ public class BackupManager { } /** + * Report whether the backup mechanism is currently active. + * When it is inactive, the device will not perform any backup operations, nor will it + * deliver data for restore, although clients can still safely call BackupManager methods. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.BACKUP) + public boolean isBackupServiceActive(UserHandle user) { + mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, + "isBackupServiceActive"); + checkServiceBinder(); + if (sService != null) { + try { + return sService.isBackupServiceActive(user.getIdentifier()); + } catch (RemoteException e) { + Log.e(TAG, "isBackupEnabled() couldn't connect"); + } + } + return false; + } + + /** * Enable/disable data restore at application install time. When enabled, app * installation will include an attempt to fetch the app's historical data from * the archival restore dataset (if any). When disabled, no such attempt will @@ -707,7 +731,6 @@ public class BackupManager { * redirects them into main-thread actions. This serializes the backup * progress callbacks nicely within the usual main-thread lifecycle pattern. */ - @SystemApi private class BackupObserverWrapper extends IBackupObserver.Stub { final Handler mHandler; final BackupObserver mObserver; diff --git a/android/app/backup/BackupManagerMonitor.java b/android/app/backup/BackupManagerMonitor.java index ae4a98a4..a91aded1 100644 --- a/android/app/backup/BackupManagerMonitor.java +++ b/android/app/backup/BackupManagerMonitor.java @@ -172,6 +172,12 @@ public class BackupManagerMonitor { public static final int LOG_EVENT_ID_NO_PACKAGES = 49; public static final int LOG_EVENT_ID_TRANSPORT_IS_NULL = 50; + /** + * 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 da81d19c..266f58df 100644 --- a/android/app/backup/BackupTransport.java +++ b/android/app/backup/BackupTransport.java @@ -51,10 +51,40 @@ public class BackupTransport { public static final int AGENT_UNKNOWN = -1004; public static final int TRANSPORT_QUOTA_EXCEEDED = -1005; + /** + * Indicates that the transport cannot accept a diff backup for this package. + * + * <p>Backup manager should clear its state for this package and immediately retry a + * non-incremental backup. This might be used if the transport no longer has data for this + * package in its backing store. + * + * <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; + // Indicates that operation was initiated by user, not a scheduled one. // Transport should ignore its own moratoriums for call with this flag set. public static final int FLAG_USER_INITIATED = 1; + /** + * 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 + */ + public static final int FLAG_INCREMENTAL = 1 << 1; + + /** + * 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 + */ + public static final int FLAG_NON_INCREMENTAL = 1 << 2; + IBackupTransport mBinderImpl = new TransportImpl(); public IBinder getBinder() { @@ -231,18 +261,33 @@ public class BackupTransport { * {@link #TRANSPORT_OK}, {@link #finishBackup} will then be called to ensure the data * is sent and recorded successfully. * + * If the backup data is a diff against the previous backup then the flag {@link + * BackupTransport#FLAG_INCREMENTAL} will be set. Otherwise, if the data is a complete backup + * set then {@link BackupTransport#FLAG_NON_INCREMENTAL} will be set. Before P neither flag will + * be set regardless of whether the backup is incremental or not. + * + * <p>If {@link BackupTransport#FLAG_INCREMENTAL} is set and the transport does not have data + * for this package in its storage backend then it cannot apply the incremental diff. Thus it + * should return {@link BackupTransport#TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED} to indicate + * that backup manager should delete its state and retry the package as a non-incremental + * backup. Before P, or if this is a non-incremental backup, then this return code is equivalent + * to {@link BackupTransport#TRANSPORT_ERROR}. + * * @param packageInfo The identity of the application whose data is being backed up. * This specifically includes the signature list for the package. * @param inFd Descriptor of file with data that resulted from invoking the application's * BackupService.doBackup() method. This may be a pipe rather than a file on * persistent media, so it may not be seekable. - * @param flags {@link BackupTransport#FLAG_USER_INITIATED} or 0. + * @param flags a combination of {@link BackupTransport#FLAG_USER_INITIATED}, {@link + * BackupTransport#FLAG_NON_INCREMENTAL}, {@link BackupTransport#FLAG_INCREMENTAL}, or 0. * @return one of {@link BackupTransport#TRANSPORT_OK} (OK so far), * {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED} (to suppress backup of this * specific package, but allow others to proceed), - * {@link BackupTransport#TRANSPORT_ERROR} (on network error or other failure), or - * {@link BackupTransport#TRANSPORT_NOT_INITIALIZED} (if the backend dataset has - * become lost due to inactivity purge or some other reason and needs re-initializing) + * {@link BackupTransport#TRANSPORT_ERROR} (on network error or other failure), {@link + * BackupTransport#TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED} (if the transport cannot accept + * an incremental backup for this package), or {@link + * BackupTransport#TRANSPORT_NOT_INITIALIZED} (if the backend dataset has become lost due to + * inactivity purge or some other reason and needs re-initializing) */ public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags) { return performBackup(packageInfo, inFd); diff --git a/android/app/job/JobInfo.java b/android/app/job/JobInfo.java index 7c40b4ea..cba9dcc3 100644 --- a/android/app/job/JobInfo.java +++ b/android/app/job/JobInfo.java @@ -253,6 +253,11 @@ public class JobInfo implements Parcelable { /** * @hide */ + public static final int FLAG_IS_PREFETCH = 1 << 2; + + /** + * @hide + */ public static final int CONSTRAINT_FLAG_CHARGING = 1 << 0; /** @@ -1364,6 +1369,28 @@ public class JobInfo implements Parcelable { } /** + * 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 + * of interest to the current user. + * <p> + * The system may use this signal to relax the network constraints you + * originally requested, such as allowing a + * {@link JobInfo#NETWORK_TYPE_UNMETERED} job to run over a metered + * 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. + */ + public Builder setIsPrefetch(boolean isPrefetch) { + if (isPrefetch) { + mFlags |= FLAG_IS_PREFETCH; + } else { + mFlags &= (~FLAG_IS_PREFETCH); + } + return this; + } + + /** * Set whether or not to persist this job across device reboots. * * @param isPersisted True to indicate that the job will be written to diff --git a/android/app/job/JobParameters.java b/android/app/job/JobParameters.java index 5053dc6f..c71bf2e6 100644 --- a/android/app/job/JobParameters.java +++ b/android/app/job/JobParameters.java @@ -70,6 +70,7 @@ public class JobParameters implements Parcelable { private final Network network; private int stopReason; // Default value of stopReason is REASON_CANCELED + private String debugStopReason; // Human readable stop reason for debugging. /** @hide */ public JobParameters(IBinder callback, int jobId, PersistableBundle extras, @@ -104,6 +105,14 @@ public class JobParameters implements Parcelable { } /** + * Reason onStopJob() was called on this job. + * @hide + */ + public String getDebugStopReason() { + return debugStopReason; + } + + /** * @return The extras you passed in when constructing this job with * {@link android.app.job.JobInfo.Builder#setExtras(android.os.PersistableBundle)}. This will * never be null. If you did not set any extras this will be an empty bundle. @@ -288,11 +297,13 @@ public class JobParameters implements Parcelable { network = null; } stopReason = in.readInt(); + debugStopReason = in.readString(); } /** @hide */ - public void setStopReason(int reason) { + public void setStopReason(int reason, String debugStopReason) { stopReason = reason; + this.debugStopReason = debugStopReason; } @Override @@ -323,6 +334,7 @@ public class JobParameters implements Parcelable { dest.writeInt(0); } dest.writeInt(stopReason); + dest.writeString(debugStopReason); } public static final Creator<JobParameters> CREATOR = new Creator<JobParameters>() { diff --git a/android/app/servertransaction/ActivityLifecycleItem.java b/android/app/servertransaction/ActivityLifecycleItem.java index 0fdc7c56..9a50a009 100644 --- a/android/app/servertransaction/ActivityLifecycleItem.java +++ b/android/app/servertransaction/ActivityLifecycleItem.java @@ -17,7 +17,9 @@ package android.app.servertransaction; import android.annotation.IntDef; +import android.os.Parcel; +import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -26,6 +28,7 @@ import java.lang.annotation.RetentionPolicy; * @hide */ public abstract class ActivityLifecycleItem extends ClientTransactionItem { + private String mDescription; @IntDef(prefix = { "UNDEFINED", "PRE_", "ON_" }, value = { UNDEFINED, @@ -53,4 +56,39 @@ public abstract class ActivityLifecycleItem extends ClientTransactionItem { /** A final lifecycle state that an activity should reach. */ @LifecycleState public abstract int getTargetState(); + + + protected ActivityLifecycleItem() { + } + + protected ActivityLifecycleItem(Parcel in) { + mDescription = in.readString(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mDescription); + } + + /** + * Sets a description that can be retrieved later for debugging purposes. + * @param description Description to set. + * @return The {@link ActivityLifecycleItem}. + */ + public ActivityLifecycleItem setDescription(String description) { + mDescription = description; + return this; + } + + /** + * Retrieves description if set through {@link #setDescription(String)}. + */ + public String getDescription() { + return mDescription; + } + + void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "target state:" + getTargetState()); + pw.println(prefix + "description: " + mDescription); + } } diff --git a/android/app/servertransaction/ClientTransaction.java b/android/app/servertransaction/ClientTransaction.java index 3c96f069..fc078798 100644 --- a/android/app/servertransaction/ClientTransaction.java +++ b/android/app/servertransaction/ClientTransaction.java @@ -24,6 +24,9 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; +import com.android.internal.annotations.VisibleForTesting; + +import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -83,7 +86,8 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { } /** Get the target state lifecycle request. */ - ActivityLifecycleItem getLifecycleStateRequest() { + @VisibleForTesting + public ActivityLifecycleItem getLifecycleStateRequest() { return mLifecycleStateRequest; } @@ -234,4 +238,12 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { result = 31 * result + Objects.hashCode(mLifecycleStateRequest); return result; } + + void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "mActivityToken:" + mActivityToken.hashCode()); + pw.println(prefix + "mLifecycleStateRequest:"); + if (mLifecycleStateRequest != null) { + mLifecycleStateRequest.dump(pw, prefix + " "); + } + } } diff --git a/android/app/servertransaction/DestroyActivityItem.java b/android/app/servertransaction/DestroyActivityItem.java index 83da5f33..cbcf6c75 100644 --- a/android/app/servertransaction/DestroyActivityItem.java +++ b/android/app/servertransaction/DestroyActivityItem.java @@ -76,12 +76,14 @@ public class DestroyActivityItem extends ActivityLifecycleItem { /** Write to Parcel. */ @Override public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); dest.writeBoolean(mFinished); dest.writeInt(mConfigChanges); } /** Read from Parcel. */ private DestroyActivityItem(Parcel in) { + super(in); mFinished = in.readBoolean(); mConfigChanges = in.readInt(); } diff --git a/android/app/servertransaction/PauseActivityItem.java b/android/app/servertransaction/PauseActivityItem.java index 880fef73..70a4755f 100644 --- a/android/app/servertransaction/PauseActivityItem.java +++ b/android/app/servertransaction/PauseActivityItem.java @@ -114,6 +114,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { /** Write to Parcel. */ @Override public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); dest.writeBoolean(mFinished); dest.writeBoolean(mUserLeaving); dest.writeInt(mConfigChanges); @@ -122,6 +123,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { /** Read from Parcel. */ private PauseActivityItem(Parcel in) { + super(in); mFinished = in.readBoolean(); mUserLeaving = in.readBoolean(); mConfigChanges = in.readInt(); diff --git a/android/app/servertransaction/ResumeActivityItem.java b/android/app/servertransaction/ResumeActivityItem.java index 9249c6e8..ed90f2cb 100644 --- a/android/app/servertransaction/ResumeActivityItem.java +++ b/android/app/servertransaction/ResumeActivityItem.java @@ -113,6 +113,7 @@ public class ResumeActivityItem extends ActivityLifecycleItem { /** Write to Parcel. */ @Override public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); dest.writeInt(mProcState); dest.writeBoolean(mUpdateProcState); dest.writeBoolean(mIsForward); @@ -120,6 +121,7 @@ public class ResumeActivityItem extends ActivityLifecycleItem { /** Read from Parcel. */ private ResumeActivityItem(Parcel in) { + super(in); mProcState = in.readInt(); mUpdateProcState = in.readBoolean(); mIsForward = in.readBoolean(); diff --git a/android/app/servertransaction/StopActivityItem.java b/android/app/servertransaction/StopActivityItem.java index 5c5c3041..b814d1ae 100644 --- a/android/app/servertransaction/StopActivityItem.java +++ b/android/app/servertransaction/StopActivityItem.java @@ -83,12 +83,14 @@ public class StopActivityItem extends ActivityLifecycleItem { /** Write to Parcel. */ @Override public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); dest.writeBoolean(mShowWindow); dest.writeInt(mConfigChanges); } /** Read from Parcel. */ private StopActivityItem(Parcel in) { + super(in); mShowWindow = in.readBoolean(); mConfigChanges = in.readInt(); } diff --git a/android/app/servertransaction/TransactionExecutor.java b/android/app/servertransaction/TransactionExecutor.java index 5b0ea6b1..78b393a8 100644 --- a/android/app/servertransaction/TransactionExecutor.java +++ b/android/app/servertransaction/TransactionExecutor.java @@ -33,6 +33,8 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.List; /** @@ -122,6 +124,21 @@ public class TransactionExecutor { final IBinder token = transaction.getActivityToken(); final ActivityClientRecord r = mTransactionHandler.getActivityClient(token); + // TODO(b/71506345): Remove once root cause is found. + if (r == null) { + final StringWriter stringWriter = new StringWriter(); + final PrintWriter pw = new PrintWriter(stringWriter); + final String prefix = " "; + + pw.println("Lifecycle transaction does not have valid ActivityClientRecord."); + pw.println("Transaction:"); + transaction.dump(pw, prefix); + pw.println("Executor:"); + dump(pw, prefix); + + Slog.wtf(TAG, stringWriter.toString()); + } + // Cycle to the state right before the final requested state. cycleToPath(r, lifecycleItem.getTargetState(), true /* excludeLastState */); @@ -245,4 +262,9 @@ public class TransactionExecutor { private static void log(String message) { if (DEBUG_RESOLVER) Slog.d(TAG, message); } + + private void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "mTransactionHandler:"); + mTransactionHandler.dump(pw, prefix + " "); + } } diff --git a/android/app/slice/Slice.java b/android/app/slice/Slice.java index 5c7f6741..5808f8b5 100644 --- a/android/app/slice/Slice.java +++ b/android/app/slice/Slice.java @@ -21,12 +21,10 @@ import android.annotation.Nullable; import android.annotation.StringDef; import android.app.PendingIntent; import android.app.RemoteInput; -import android.content.ContentProvider; import android.content.ContentResolver; import android.content.Context; import android.content.IContentProvider; import android.content.Intent; -import android.content.pm.ResolveInfo; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Bundle; @@ -67,6 +65,7 @@ public final class Slice implements Parcelable { HINT_TOGGLE, HINT_HORIZONTAL, HINT_PARTIAL, + HINT_SEE_MORE }) @Retention(RetentionPolicy.SOURCE) public @interface SliceHint {} @@ -151,7 +150,19 @@ public final class Slice implements Parcelable { * 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. + */ + public static final String HINT_CALLER_NEEDED = "caller_needed"; /** * Key to retrieve an extra added to an intent when a control is changed. */ @@ -184,6 +195,10 @@ public final class Slice implements Parcelable { * Subtype to tag an item representing priority. */ public static final String SUBTYPE_PRIORITY = "priority"; + /** + * Subtype to tag an item to use as a content description. + */ + public static final String SUBTYPE_CONTENT_DESCRIPTION = "content_description"; private final SliceItem[] mItems; private final @SliceHint String[] mHints; @@ -415,28 +430,6 @@ public final class Slice implements Parcelable { * Add a color to the slice being constructed * @param subType Optional template-specific type information * @see {@link SliceItem#getSubType()} - * @deprecated will be removed once supportlib updates - */ - public Builder addColor(int color, @Nullable String subType, @SliceHint String... hints) { - mItems.add(new SliceItem(color, 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 will be removed once supportlib updates - */ - public Builder addColor(int color, @Nullable String subType, - @SliceHint List<String> hints) { - return addColor(color, subType, hints.toArray(new String[hints.size()])); - } - - /** - * Add a color 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) { mItems.add(new SliceItem(value, SliceItem.FORMAT_INT, subType, hints)); @@ -549,16 +542,11 @@ public final class Slice implements Parcelable { } /** - * Turns a slice Uri into slice content. - * - * @param resolver ContentResolver to be used. - * @param uri The URI to a slice provider - * @param supportedSpecs List of supported specs. - * @return The Slice provided by the app or null if none is given. - * @see Slice - */ - public static @Nullable Slice bindSlice(ContentResolver resolver, @NonNull Uri uri, - List<SliceSpec> supportedSpecs) { + * @deprecated TO BE REMOVED. + */ + @Deprecated + public static @Nullable Slice bindSlice(ContentResolver resolver, + @NonNull Uri uri, @NonNull List<SliceSpec> supportedSpecs) { Preconditions.checkNotNull(uri, "uri"); IContentProvider provider = resolver.acquireProvider(uri); if (provider == null) { @@ -586,60 +574,11 @@ public final class Slice implements Parcelable { } /** - * Turns a slice intent into slice content. Expects an explicit intent. If there is no - * {@link ContentProvider} associated with the given intent this will throw - * {@link IllegalArgumentException}. - * - * @param context The context to use. - * @param intent The intent associated with a slice. - * @param supportedSpecs List of supported specs. - * @return The Slice provided by the app or null if none is given. - * @see Slice - * @see SliceProvider#onMapIntentToUri(Intent) - * @see Intent + * @deprecated TO BE REMOVED. */ + @Deprecated public static @Nullable Slice bindSlice(Context context, @NonNull Intent intent, - List<SliceSpec> supportedSpecs) { - Preconditions.checkNotNull(intent, "intent"); - Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null, - "Slice intent must be explicit " + intent); - ContentResolver resolver = context.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 bindSlice(resolver, intentData, supportedSpecs); - } - // Otherwise ask the app - List<ResolveInfo> providers = - context.getPackageManager().queryIntentContentProviders(intent, 0); - if (providers == null) { - throw new IllegalArgumentException("Unable to resolve intent " + intent); - } - 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 { - Bundle extras = new Bundle(); - extras.putParcelable(SliceProvider.EXTRA_INTENT, intent); - extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS, - new ArrayList<>(supportedSpecs)); - final Bundle res = provider.call(resolver.getPackageName(), - SliceProvider.METHOD_MAP_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; - } finally { - resolver.releaseProvider(provider); - } + @NonNull List<SliceSpec> supportedSpecs) { + return context.getSystemService(SliceManager.class).bindSlice(intent, supportedSpecs); } } diff --git a/android/app/slice/SliceItem.java b/android/app/slice/SliceItem.java index bcfd413f..9eb2bb89 100644 --- a/android/app/slice/SliceItem.java +++ b/android/app/slice/SliceItem.java @@ -98,11 +98,6 @@ public final class SliceItem implements Parcelable { */ public static final String FORMAT_INT = "int"; /** - * A {@link SliceItem} that contains an int. - * @deprecated to be removed - */ - public static final String FORMAT_COLOR = "color"; - /** * A {@link SliceItem} that contains a timestamp. */ public static final String FORMAT_TIMESTAMP = "timestamp"; @@ -231,13 +226,6 @@ public final class SliceItem implements Parcelable { } /** - * @deprecated to be removed. - */ - public int getColor() { - return (Integer) mObj; - } - - /** * @return The slice held by this {@link #FORMAT_ACTION} or {@link #FORMAT_SLICE} SliceItem */ public Slice getSlice() { diff --git a/android/app/slice/SliceManager.java b/android/app/slice/SliceManager.java index 0c5f225d..2fa9d8e0 100644 --- a/android/app/slice/SliceManager.java +++ b/android/app/slice/SliceManager.java @@ -16,18 +16,31 @@ package android.app.slice; +import android.annotation.CallbackExecutor; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemService; +import android.content.ContentResolver; import android.content.Context; +import android.content.IContentProvider; +import android.content.Intent; +import android.content.pm.ResolveInfo; import android.net.Uri; +import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; import android.util.ArrayMap; +import android.util.Log; import android.util.Pair; +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.concurrent.Executor; @@ -39,12 +52,36 @@ import java.util.concurrent.Executor; @SystemService(Context.SLICE_SERVICE) public class SliceManager { + private static final String TAG = "SliceManager"; + + /** + * @hide + */ + public static final String ACTION_REQUEST_SLICE_PERMISSION = + "android.intent.action.REQUEST_SLICE_PERMISSION"; + private final ISliceManager mService; private final Context mContext; private final ArrayMap<Pair<Uri, SliceCallback>, ISliceListener> mListenerLookup = new ArrayMap<>(); /** + * Permission denied. + * @hide + */ + public static final int PERMISSION_DENIED = -1; + /** + * Permission granted. + * @hide + */ + public static final int PERMISSION_GRANTED = 0; + /** + * Permission just granted by the user, and should be granted uri permission as well. + * @hide + */ + public static final int PERMISSION_USER_GRANTED = 1; + + /** * @hide */ public SliceManager(Context context, Handler handler) throws ServiceNotFoundException { @@ -54,21 +91,21 @@ public class SliceManager { } /** - * 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) + * @deprecated TO BE REMOVED. */ + @Deprecated public void registerSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback, @NonNull List<SliceSpec> specs) { - registerSliceCallback(uri, callback, specs, Handler.getMain()); + 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); } /** @@ -84,19 +121,9 @@ public class SliceManager { * @param specs The list of supported {@link SliceSpec}s of the callback. * @see SliceProvider#onSlicePinned(Uri) */ - public void registerSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback, - @NonNull List<SliceSpec> specs, Handler handler) { - try { - mService.addSliceListener(uri, mContext.getPackageName(), - getListener(uri, callback, new ISliceListener.Stub() { - @Override - public void onSliceUpdated(Slice s) throws RemoteException { - handler.post(() -> callback.onSliceUpdated(s)); - } - }), specs.toArray(new SliceSpec[specs.size()])); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + public void registerSliceCallback(@NonNull Uri uri, @NonNull List<SliceSpec> specs, + @NonNull SliceCallback callback) { + registerSliceCallback(uri, specs, mContext.getMainExecutor(), callback); } /** @@ -112,8 +139,8 @@ public class SliceManager { * @param specs The list of supported {@link SliceSpec}s of the callback. * @see SliceProvider#onSlicePinned(Uri) */ - public void registerSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback, - @NonNull List<SliceSpec> specs, Executor executor) { + 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() { @@ -224,6 +251,165 @@ public class SliceManager { } /** + * Obtains a list of slices that are descendants of the specified Uri. + * <p> + * Not all slice providers will implement this functionality, in which case, + * an empty collection will be returned. + * + * @param uri The uri to look for descendants under. + * @return All slices within the space. + * @see SliceProvider#onGetSliceDescendants(Uri) + */ + public @NonNull Collection<Uri> getSliceDescendants(@NonNull Uri uri) { + ContentResolver resolver = mContext.getContentResolver(); + IContentProvider provider = resolver.acquireProvider(uri); + try { + Bundle extras = new Bundle(); + extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri); + final Bundle res = provider.call(resolver.getPackageName(), + 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(); + } + + /** + * Turns a slice Uri into slice content. + * + * @param uri The URI to a slice provider + * @param supportedSpecs List of supported specs. + * @return The Slice provided by the app or null if none is given. + * @see Slice + */ + 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 { + 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); + Bundle.setDefusable(res, true); + 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; + } finally { + resolver.releaseProvider(provider); + } + } + + /** + * Turns a slice intent into slice content. Expects an explicit intent. If there is no + * {@link android.content.ContentProvider} associated with the given intent this will throw + * {@link IllegalArgumentException}. + * + * @param intent The intent associated with a slice. + * @param supportedSpecs List of supported specs. + * @return The Slice provided by the app or null if none is given. + * @see Slice + * @see SliceProvider#onMapIntentToUri(Intent) + * @see Intent + */ + 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); + 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 bindSlice(intentData, supportedSpecs); + } + // Otherwise ask the app + List<ResolveInfo> providers = + mContext.getPackageManager().queryIntentContentProviders(intent, 0); + if (providers == null) { + throw new IllegalArgumentException("Unable to resolve intent " + intent); + } + 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 { + 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); + 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; + } finally { + resolver.releaseProvider(provider); + } + } + + /** + * 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) { + try { + if (pkg == null) { + throw new SecurityException("No pkg specified"); + } + int result = mService.checkSlicePermission(uri, pkg, pid, uid); + if (result == PERMISSION_DENIED) { + throw new SecurityException("User " + uid + " does not have slice permission for " + + uri + "."); + } + if (result == PERMISSION_USER_GRANTED) { + // We just had a user grant of this permission and need to grant this to the app + // permanently. + mContext.grantUriPermission(pkg, uri.buildUpon().path("").build(), + Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION + | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Called by SystemUI to grant a slice permission after a dialog is shown. + * @hide + */ + public void grantPermissionFromUser(Uri uri, String pkg, boolean allSlices) { + try { + mService.grantPermissionFromUser(uri, pkg, mContext.getPackageName(), allSlices); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Class that listens to changes in {@link Slice}s. */ public interface SliceCallback { diff --git a/android/app/slice/SliceProvider.java b/android/app/slice/SliceProvider.java index 8483931c..00e8ccad 100644 --- a/android/app/slice/SliceProvider.java +++ b/android/app/slice/SliceProvider.java @@ -15,13 +15,19 @@ */ package android.app.slice; -import android.Manifest.permission; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.PendingIntent; +import android.content.ComponentName; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; +import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ProviderInfo; import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; @@ -36,6 +42,9 @@ import android.os.StrictMode.ThreadPolicy; import android.os.UserHandle; import android.util.Log; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; @@ -80,7 +89,7 @@ import java.util.concurrent.CountDownLatch; */ public abstract class SliceProvider extends ContentProvider { /** - * This is the Android platform's MIME type for a slice: URI + * This is the Android platform's MIME type for a URI * containing a slice implemented through {@link SliceProvider}. */ public static final String SLICE_TYPE = "vnd.android.slice"; @@ -113,14 +122,53 @@ public abstract class SliceProvider extends ContentProvider { /** * @hide */ + public static final String METHOD_GET_DESCENDANTS = "get_descendants"; + /** + * @hide + */ public static final String EXTRA_INTENT = "slice_intent"; /** * @hide */ public static final String EXTRA_SLICE = "slice"; + /** + * @hide + */ + public static final String EXTRA_SLICE_DESCENDANTS = "slice_descendants"; + /** + * @hide + */ + public static final String EXTRA_PKG = "pkg"; + /** + * @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 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)}. + */ + public final @Nullable String getBindingPackage() { + return mBindingPkg; + } + + @Override + public void attachInfo(Context context, ProviderInfo info) { + super.attachInfo(context, info); + mSliceManager = context.getSystemService(SliceManager.class); + } + /** * Implemented to create a slice. Will be called on the main thread. * <p> @@ -139,14 +187,6 @@ public abstract class SliceProvider extends ContentProvider { * @see {@link Slice#HINT_PARTIAL} */ public Slice onBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs) { - return onBindSlice(sliceUri); - } - - /** - * @deprecated migrating to {@link #onBindSlice(Uri, List)} - */ - @Deprecated - public Slice onBindSlice(Uri sliceUri) { return null; } @@ -183,6 +223,20 @@ public abstract class SliceProvider extends ContentProvider { } /** + * Obtains a list of slices that are descendants of the specified Uri. + * <p> + * Implementing this is optional for a SliceProvider, but does provide a good + * discovery mechanism for finding slice Uris. + * + * @param uri The uri to look for descendants under. + * @return All slices within the space. + * @see SliceManager#getSliceDescendants(Uri) + */ + public @NonNull Collection<Uri> onGetSliceDescendants(@NonNull Uri uri) { + return Collections.emptyList(); + } + + /** * This method must be overridden if an {@link IntentFilter} is specified on the SliceProvider. * 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}. @@ -244,56 +298,74 @@ public abstract class SliceProvider extends ContentProvider { @Override public Bundle call(String method, String arg, Bundle extras) { if (method.equals(METHOD_SLICE)) { - Uri uri = extras.getParcelable(EXTRA_BIND_URI); - if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) { - getContext().enforceUriPermission(uri, permission.BIND_SLICE, - permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(), - Intent.FLAG_GRANT_WRITE_URI_PERMISSION, - "Slice binding requires the permission BIND_SLICE"); - } + Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_BIND_URI)); List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS); - Slice s = handleBindSlice(uri, supportedSpecs); + 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); Bundle b = new Bundle(); b.putParcelable(EXTRA_SLICE, s); return b; } else if (method.equals(METHOD_MAP_INTENT)) { - getContext().enforceCallingPermission(permission.BIND_SLICE, - "Slice binding requires the permission BIND_SLICE"); Intent intent = extras.getParcelable(EXTRA_INTENT); if (intent == null) return null; Uri uri = onMapIntentToUri(intent); List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS); Bundle b = new Bundle(); if (uri != null) { - Slice s = handleBindSlice(uri, supportedSpecs); + Slice s = handleBindSlice(uri, supportedSpecs, getCallingPackage()); b.putParcelable(EXTRA_SLICE, s); } else { b.putParcelable(EXTRA_SLICE, null); } return b; } else if (method.equals(METHOD_PIN)) { - Uri uri = extras.getParcelable(EXTRA_BIND_URI); - if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) { - getContext().enforceUriPermission(uri, permission.BIND_SLICE, - permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(), - Intent.FLAG_GRANT_WRITE_URI_PERMISSION, - "Slice binding requires the permission BIND_SLICE"); + Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_BIND_URI)); + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Only the system can pin/unpin slices"); } handlePinSlice(uri); } else if (method.equals(METHOD_UNPIN)) { - Uri uri = extras.getParcelable(EXTRA_BIND_URI); - if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) { - getContext().enforceUriPermission(uri, permission.BIND_SLICE, - permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(), - Intent.FLAG_GRANT_WRITE_URI_PERMISSION, - "Slice binding requires the permission BIND_SLICE"); + Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_BIND_URI)); + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Only the system can pin/unpin slices"); } handleUnpinSlice(uri); + } else if (method.equals(METHOD_GET_DESCENDANTS)) { + Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_BIND_URI)); + Bundle b = new Bundle(); + b.putParcelableArrayList(EXTRA_SLICE_DESCENDANTS, + new ArrayList<>(handleGetDescendants(uri))); + return b; } return super.call(method, arg, extras); } + private Collection<Uri> handleGetDescendants(Uri uri) { + if (Looper.myLooper() == Looper.getMainLooper()) { + 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); + } + } + } + private void handlePinSlice(Uri sliceUri) { if (Looper.myLooper() == Looper.getMainLooper()) { onSlicePinned(sliceUri); @@ -328,14 +400,27 @@ public abstract class SliceProvider extends ContentProvider { } } - private Slice handleBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs) { + private Slice handleBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs, + String callingPkg) { + // 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); + } + } if (Looper.myLooper() == Looper.getMainLooper()) { - return onBindSliceStrict(sliceUri, supportedSpecs); + return onBindSliceStrict(sliceUri, supportedSpecs, pkg); } else { CountDownLatch latch = new CountDownLatch(1); Slice[] output = new Slice[1]; Handler.getMain().post(() -> { - output[0] = onBindSliceStrict(sliceUri, supportedSpecs); + output[0] = onBindSliceStrict(sliceUri, supportedSpecs, pkg); latch.countDown(); }); try { @@ -347,15 +432,66 @@ public abstract class SliceProvider extends ContentProvider { } } - private Slice onBindSliceStrict(Uri sliceUri, List<SliceSpec> supportedSpecs) { + /** + * @hide + */ + public static 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(); + } + + /** + * @hide + */ + public static PendingIntent createPermissionIntent(Context context, Uri sliceUri, + String callingPackage) { + Intent intent = new Intent(SliceManager.ACTION_REQUEST_SLICE_PERMISSION); + intent.setComponent(new ComponentName("com.android.systemui", + "com.android.systemui.SlicePermissionActivity")); + intent.putExtra(EXTRA_BIND_URI, sliceUri); + intent.putExtra(EXTRA_PKG, callingPackage); + intent.putExtra(EXTRA_PROVIDER_PKG, context.getPackageName()); + // Unique pending intent. + intent.setData(sliceUri.buildUpon().appendQueryParameter("package", callingPackage) + .build()); + + return PendingIntent.getActivity(context, 0, intent, 0); + } + + /** + * @hide + */ + public static CharSequence getPermissionString(Context context, String callingPackage) { + PackageManager pm = context.getPackageManager(); + try { + return context.getString( + com.android.internal.R.string.slices_permission_request, + pm.getApplicationInfo(callingPackage, 0).loadLabel(pm), + context.getApplicationInfo().loadLabel(pm)); + } catch (NameNotFoundException e) { + // This shouldn't be possible since the caller is verified. + throw new RuntimeException("Unknown calling app", e); + } + } + + private Slice onBindSliceStrict(Uri sliceUri, List<SliceSpec> supportedSpecs, + String callingPackage) { 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); } } diff --git a/android/app/timezone/RulesState.java b/android/app/timezone/RulesState.java index 16309fab..e86d348a 100644 --- a/android/app/timezone/RulesState.java +++ b/android/app/timezone/RulesState.java @@ -126,9 +126,6 @@ public final class RulesState implements Parcelable { mStagedOperationType == STAGED_OPERATION_INSTALL /* requireNotNull */, "stagedDistroRulesVersion", stagedDistroRulesVersion); - if (operationInProgress && distroStatus != DISTRO_STATUS_UNKNOWN) { - throw new IllegalArgumentException("distroInstalled != DISTRO_STATUS_UNKNOWN"); - } this.mDistroStatus = validateDistroStatus(distroStatus); this.mInstalledDistroRulesVersion = validateConditionalNull( mDistroStatus == DISTRO_STATUS_INSTALLED/* requireNotNull */, diff --git a/android/app/trust/TrustManager.java b/android/app/trust/TrustManager.java index 852cb8e0..8ab0b706 100644 --- a/android/app/trust/TrustManager.java +++ b/android/app/trust/TrustManager.java @@ -36,9 +36,11 @@ public class TrustManager { private static final int MSG_TRUST_CHANGED = 1; private static final int MSG_TRUST_MANAGED_CHANGED = 2; + private static final int MSG_TRUST_ERROR = 3; private static final String TAG = "TrustManager"; private static final String DATA_FLAGS = "initiatedByUser"; + private static final String DATA_MESSAGE = "message"; private final ITrustManager mService; private final ArrayMap<TrustListener, ITrustListener> mTrustListeners; @@ -148,6 +150,13 @@ public class TrustManager { mHandler.obtainMessage(MSG_TRUST_MANAGED_CHANGED, (managed ? 1 : 0), userId, trustListener).sendToTarget(); } + + @Override + public void onTrustError(CharSequence message) { + Message m = mHandler.obtainMessage(MSG_TRUST_ERROR); + m.getData().putCharSequence(DATA_MESSAGE, message); + m.sendToTarget(); + } }; mService.registerTrustListener(iTrustListener); mTrustListeners.put(trustListener, iTrustListener); @@ -221,6 +230,10 @@ public class TrustManager { break; case MSG_TRUST_MANAGED_CHANGED: ((TrustListener)msg.obj).onTrustManagedChanged(msg.arg1 != 0, msg.arg2); + break; + case MSG_TRUST_ERROR: + final CharSequence message = msg.peekData().getCharSequence(DATA_MESSAGE); + ((TrustListener)msg.obj).onTrustError(message); } } }; @@ -229,9 +242,9 @@ public class TrustManager { /** * Reports that the trust state has changed. - * @param enabled if true, the system believes the environment to be trusted. - * @param userId the user, for which the trust changed. - * @param flags flags specified by the trust agent when granting trust. See + * @param enabled If true, the system believes the environment to be trusted. + * @param userId The user, for which the trust changed. + * @param flags Flags specified by the trust agent when granting trust. See * {@link android.service.trust.TrustAgentService#grantTrust(CharSequence, long, int) * TrustAgentService.grantTrust(CharSequence, long, int)}. */ @@ -239,9 +252,15 @@ public class TrustManager { /** * Reports that whether trust is managed has changed - * @param enabled if true, at least one trust agent is managing trust. - * @param userId the user, for which the state changed. + * @param enabled If true, at least one trust agent is managing trust. + * @param userId The user, for which the state changed. */ void onTrustManagedChanged(boolean enabled, int userId); + + /** + * Reports that an error happened on a TrustAgentService. + * @param message A message that should be displayed on the UI. + */ + void onTrustError(CharSequence message); } } diff --git a/android/app/usage/NetworkStats.java b/android/app/usage/NetworkStats.java index 2e44a630..da36157d 100644 --- a/android/app/usage/NetworkStats.java +++ b/android/app/usage/NetworkStats.java @@ -227,6 +227,30 @@ public final class NetworkStats implements AutoCloseable { */ public static final int ROAMING_YES = 0x2; + /** @hide */ + @IntDef(prefix = { "DEFAULT_NETWORK_" }, value = { + DEFAULT_NETWORK_ALL, + DEFAULT_NETWORK_NO, + DEFAULT_NETWORK_YES + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DefaultNetwork {} + + /** + * Combined usage for this network regardless of whether it was the active default network. + */ + public static final int DEFAULT_NETWORK_ALL = -1; + + /** + * Usage that occurs while this network is not the active default network. + */ + public static final int DEFAULT_NETWORK_NO = 0x1; + + /** + * Usage that occurs while this network is the active default network. + */ + public static final int DEFAULT_NETWORK_YES = 0x2; + /** * Special TAG value for total data across all tags */ @@ -235,6 +259,7 @@ public final class NetworkStats implements AutoCloseable { private int mUid; private int mTag; private int mState; + private int mDefaultNetwork; private int mMetered; private int mRoaming; private long mBeginTimeStamp; @@ -286,6 +311,15 @@ public final class NetworkStats implements AutoCloseable { return 0; } + private static @DefaultNetwork int convertDefaultNetwork(int defaultNetwork) { + switch (defaultNetwork) { + case android.net.NetworkStats.DEFAULT_NETWORK_ALL : return DEFAULT_NETWORK_ALL; + case android.net.NetworkStats.DEFAULT_NETWORK_NO: return DEFAULT_NETWORK_NO; + case android.net.NetworkStats.DEFAULT_NETWORK_YES: return DEFAULT_NETWORK_YES; + } + return 0; + } + public Bucket() { } @@ -351,6 +385,21 @@ public final class NetworkStats implements AutoCloseable { } /** + * Default network state. One of the following values:<p/> + * <ul> + * <li>{@link #DEFAULT_NETWORK_ALL}</li> + * <li>{@link #DEFAULT_NETWORK_NO}</li> + * <li>{@link #DEFAULT_NETWORK_YES}</li> + * </ul> + * <p>Indicates whether the network usage occurred on the system default network for this + * type of traffic, or whether the application chose to send this traffic on a network that + * was not the one selected by the system. + */ + public @DefaultNetwork int getDefaultNetwork() { + return mDefaultNetwork; + } + + /** * Start timestamp of the bucket's time interval. Defined in terms of "Unix time", see * {@link java.lang.System#currentTimeMillis}. * @return Start of interval. @@ -551,6 +600,8 @@ public final class NetworkStats implements AutoCloseable { bucketOut.mUid = Bucket.convertUid(mRecycledSummaryEntry.uid); bucketOut.mTag = Bucket.convertTag(mRecycledSummaryEntry.tag); bucketOut.mState = Bucket.convertState(mRecycledSummaryEntry.set); + bucketOut.mDefaultNetwork = Bucket.convertDefaultNetwork( + mRecycledSummaryEntry.defaultNetwork); bucketOut.mMetered = Bucket.convertMetered(mRecycledSummaryEntry.metered); bucketOut.mRoaming = Bucket.convertRoaming(mRecycledSummaryEntry.roaming); bucketOut.mBeginTimeStamp = mStartTimeStamp; @@ -600,6 +651,7 @@ public final class NetworkStats implements AutoCloseable { bucketOut.mUid = Bucket.convertUid(getUid()); bucketOut.mTag = Bucket.convertTag(mTag); bucketOut.mState = Bucket.STATE_ALL; + bucketOut.mDefaultNetwork = Bucket.DEFAULT_NETWORK_ALL; bucketOut.mMetered = Bucket.METERED_ALL; bucketOut.mRoaming = Bucket.ROAMING_ALL; bucketOut.mBeginTimeStamp = mRecycledHistoryEntry.bucketStart; diff --git a/android/app/usage/NetworkStatsManager.java b/android/app/usage/NetworkStatsManager.java index 853b0033..5576e86e 100644 --- a/android/app/usage/NetworkStatsManager.java +++ b/android/app/usage/NetworkStatsManager.java @@ -60,10 +60,11 @@ import android.util.Log; * {@link #queryDetailsForUid} <p /> * {@link #queryDetails} <p /> * These queries do not aggregate over time but do aggregate over state, metered and roaming. - * Therefore there can be multiple buckets for a particular key but all Bucket's state is going to - * be {@link NetworkStats.Bucket#STATE_ALL}, all Bucket's metered is going to be - * {@link NetworkStats.Bucket#METERED_ALL}, and all Bucket's roaming is going to be - * {@link NetworkStats.Bucket#ROAMING_ALL}. + * Therefore there can be multiple buckets for a particular key. However, all Buckets will have + * {@code state} {@link NetworkStats.Bucket#STATE_ALL}, + * {@code defaultNetwork} {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL}, + * {@code metered } {@link NetworkStats.Bucket#METERED_ALL}, + * {@code roaming} {@link NetworkStats.Bucket#ROAMING_ALL}. * <p /> * <b>NOTE:</b> Calling {@link #querySummaryForDevice} or accessing stats for apps other than the * calling app requires the permission {@link android.Manifest.permission#PACKAGE_USAGE_STATS}, @@ -130,13 +131,26 @@ public class NetworkStatsManager { } } + /** @hide */ + public Bucket querySummaryForDevice(NetworkTemplate template, + long startTime, long endTime) throws SecurityException, RemoteException { + Bucket bucket = null; + NetworkStats stats = new NetworkStats(mContext, template, mFlags, startTime, endTime); + bucket = stats.getDeviceSummaryForNetwork(); + + stats.close(); + return bucket; + } + /** * Query network usage statistics summaries. Result is summarised data usage for the whole * device. Result is a single Bucket aggregated over time, state, uid, tag, metered, and * roaming. This means the bucket's start and end timestamp are going to be the same as the * 'startTime' and 'endTime' parameters. State is going to be * {@link NetworkStats.Bucket#STATE_ALL}, uid {@link NetworkStats.Bucket#UID_ALL}, - * tag {@link NetworkStats.Bucket#TAG_NONE}, metered {@link NetworkStats.Bucket#METERED_ALL}, + * tag {@link NetworkStats.Bucket#TAG_NONE}, + * default network {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL}, + * metered {@link NetworkStats.Bucket#METERED_ALL}, * and roaming {@link NetworkStats.Bucket#ROAMING_ALL}. * * @param networkType As defined in {@link ConnectivityManager}, e.g. @@ -160,12 +174,7 @@ public class NetworkStatsManager { return null; } - Bucket bucket = null; - NetworkStats stats = new NetworkStats(mContext, template, mFlags, startTime, endTime); - bucket = stats.getDeviceSummaryForNetwork(); - - stats.close(); - return bucket; + return querySummaryForDevice(template, startTime, endTime); } /** @@ -209,10 +218,10 @@ public class NetworkStatsManager { /** * Query network usage statistics summaries. Result filtered to include only uids belonging to * calling user. Result is aggregated over time, hence all buckets will have the same start and - * end timestamps. Not aggregated over state, uid, metered, or roaming. This means buckets' - * start and end timestamps are going to be the same as the 'startTime' and 'endTime' - * parameters. State, uid, metered, and roaming are going to vary, and tag is going to be the - * same. + * end timestamps. Not aggregated over state, uid, default network, metered, or roaming. This + * means buckets' start and end timestamps are going to be the same as the 'startTime' and + * 'endTime' parameters. State, uid, metered, and roaming are going to vary, and tag is going to + * be the same. * * @param networkType As defined in {@link ConnectivityManager}, e.g. * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} @@ -258,9 +267,10 @@ public class NetworkStatsManager { * 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. metered is going to be - * {@link NetworkStats.Bucket#METERED_ALL}, and roaming is going to be - * {@link NetworkStats.Bucket#ROAMING_ALL}. + * same as the 'uid' parameter and tag the same as 'tag' 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}. * <p>Only includes buckets that atomically occur in the inclusive time range. Doesn't * interpolate across partial buckets. Since bucket length is in the order of hours, this * method cannot be used to measure data usage on a fine grained time scale. @@ -301,9 +311,10 @@ public class NetworkStatsManager { * metered, nor roaming. 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 will vary, - * tag {@link NetworkStats.Bucket#TAG_NONE}, metered is going to be - * {@link NetworkStats.Bucket#METERED_ALL}, and roaming is going to be - * {@link NetworkStats.Bucket#ROAMING_ALL}. + * tag {@link NetworkStats.Bucket#TAG_NONE}, + * default network 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}. * <p>Only includes buckets that atomically occur in the inclusive time range. Doesn't * interpolate across partial buckets. Since bucket length is in the order of hours, this * method cannot be used to measure data usage on a fine grained time scale. @@ -335,6 +346,37 @@ public class NetworkStatsManager { return result; } + /** @hide */ + public void registerUsageCallback(NetworkTemplate template, int networkType, + long thresholdBytes, UsageCallback callback, @Nullable Handler handler) { + checkNotNull(callback, "UsageCallback cannot be null"); + + final Looper looper; + if (handler == null) { + looper = Looper.myLooper(); + } else { + looper = handler.getLooper(); + } + + DataUsageRequest request = new DataUsageRequest(DataUsageRequest.REQUEST_ID_UNSET, + template, thresholdBytes); + try { + CallbackHandler callbackHandler = new CallbackHandler(looper, networkType, + template.getSubscriberId(), callback); + callback.request = mService.registerUsageCallback( + mContext.getOpPackageName(), request, new Messenger(callbackHandler), + new Binder()); + if (DBG) Log.d(TAG, "registerUsageCallback returned " + callback.request); + + if (callback.request == null) { + Log.e(TAG, "Request from callback is null; should not happen"); + } + } catch (RemoteException e) { + if (DBG) Log.d(TAG, "Remote exception when registering callback"); + throw e.rethrowFromSystemServer(); + } + } + /** * Registers to receive notifications about data usage on specified networks. * @@ -363,15 +405,7 @@ public class NetworkStatsManager { */ public void registerUsageCallback(int networkType, String subscriberId, long thresholdBytes, UsageCallback callback, @Nullable Handler handler) { - checkNotNull(callback, "UsageCallback cannot be null"); - - final Looper looper; - if (handler == null) { - looper = Looper.myLooper(); - } else { - looper = handler.getLooper(); - } - + NetworkTemplate template = createTemplate(networkType, subscriberId); if (DBG) { Log.d(TAG, "registerUsageCallback called with: {" + " networkType=" + networkType @@ -379,25 +413,7 @@ public class NetworkStatsManager { + " thresholdBytes=" + thresholdBytes + " }"); } - - NetworkTemplate template = createTemplate(networkType, subscriberId); - DataUsageRequest request = new DataUsageRequest(DataUsageRequest.REQUEST_ID_UNSET, - template, thresholdBytes); - try { - CallbackHandler callbackHandler = new CallbackHandler(looper, networkType, - subscriberId, callback); - callback.request = mService.registerUsageCallback( - mContext.getOpPackageName(), request, new Messenger(callbackHandler), - new Binder()); - if (DBG) Log.d(TAG, "registerUsageCallback returned " + callback.request); - - if (callback.request == null) { - Log.e(TAG, "Request from callback is null; should not happen"); - } - } catch (RemoteException e) { - if (DBG) Log.d(TAG, "Remote exception when registering callback"); - throw e.rethrowFromSystemServer(); - } + registerUsageCallback(template, networkType, thresholdBytes, callback, handler); } /** diff --git a/android/app/usage/UsageEvents.java b/android/app/usage/UsageEvents.java index f04e9074..edb992bd 100644 --- a/android/app/usage/UsageEvents.java +++ b/android/app/usage/UsageEvents.java @@ -106,6 +106,12 @@ public final class UsageEvents implements Parcelable { */ public static final int NOTIFICATION_SEEN = 10; + /** + * An event type denoting a change in App Standby Bucket. + * @hide + */ + public static final int STANDBY_BUCKET_CHANGED = 11; + /** @hide */ public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0; @@ -170,6 +176,13 @@ public final class UsageEvents implements Parcelable { */ public String[] mContentAnnotations; + /** + * The app standby bucket assigned. + * Only present for {@link #STANDBY_BUCKET_CHANGED} event types + * {@hide} + */ + public int mBucket; + /** @hide */ @EventFlags public int mFlags; @@ -189,6 +202,7 @@ public final class UsageEvents implements Parcelable { mContentType = orig.mContentType; mContentAnnotations = orig.mContentAnnotations; mFlags = orig.mFlags; + mBucket = orig.mBucket; } /** @@ -399,6 +413,9 @@ public final class UsageEvents implements Parcelable { p.writeString(event.mContentType); p.writeStringArray(event.mContentAnnotations); break; + case Event.STANDBY_BUCKET_CHANGED: + p.writeInt(event.mBucket); + break; } } @@ -442,6 +459,9 @@ public final class UsageEvents implements Parcelable { eventOut.mContentType = p.readString(); eventOut.mContentAnnotations = p.createStringArray(); break; + case Event.STANDBY_BUCKET_CHANGED: + eventOut.mBucket = p.readInt(); + break; } } diff --git a/android/app/usage/UsageStatsManagerInternal.java b/android/app/usage/UsageStatsManagerInternal.java index 4b4fe72f..bd978e3d 100644 --- a/android/app/usage/UsageStatsManagerInternal.java +++ b/android/app/usage/UsageStatsManagerInternal.java @@ -16,11 +16,13 @@ package android.app.usage; +import android.annotation.UserIdInt; import android.app.usage.UsageStatsManager.StandbyBuckets; import android.content.ComponentName; import android.content.res.Configuration; import java.util.List; +import java.util.Set; /** * UsageStatsManager local system service interface. @@ -37,7 +39,7 @@ public abstract class UsageStatsManagerInternal { * @param eventType The event that occurred. Valid values can be found at * {@link UsageEvents} */ - public abstract void reportEvent(ComponentName component, int userId, int eventType); + public abstract void reportEvent(ComponentName component, @UserIdInt int userId, int eventType); /** * Reports an event to the UsageStatsManager. @@ -47,14 +49,14 @@ public abstract class UsageStatsManagerInternal { * @param eventType The event that occurred. Valid values can be found at * {@link UsageEvents} */ - public abstract void reportEvent(String packageName, int userId, int eventType); + public abstract void reportEvent(String packageName, @UserIdInt int userId, int eventType); /** * Reports a configuration change to the UsageStatsManager. * * @param config The new device configuration. */ - public abstract void reportConfigurationChange(Configuration config, int userId); + public abstract void reportConfigurationChange(Configuration config, @UserIdInt int userId); /** * Reports that an action equivalent to a ShortcutInfo is taken by the user. @@ -65,7 +67,8 @@ public abstract class UsageStatsManagerInternal { * * @see android.content.pm.ShortcutManager#reportShortcutUsed(String) */ - public abstract void reportShortcutUsage(String packageName, String shortcutId, int userId); + public abstract void reportShortcutUsage(String packageName, String shortcutId, + @UserIdInt int userId); /** * Reports that a content provider has been accessed by a foreground app. @@ -73,7 +76,8 @@ public abstract class UsageStatsManagerInternal { * @param pkgName The package name of the content provider * @param userId The user in which the content provider was accessed. */ - public abstract void reportContentProviderUsage(String name, String pkgName, int userId); + public abstract void reportContentProviderUsage(String name, String pkgName, + @UserIdInt int userId); /** * Prepares the UsageStatsService for shutdown. @@ -89,7 +93,7 @@ public abstract class UsageStatsManagerInternal { * @param userId * @return */ - public abstract boolean isAppIdle(String packageName, int uidForAppId, int userId); + public abstract boolean isAppIdle(String packageName, int uidForAppId, @UserIdInt int userId); /** * Returns the app standby bucket that the app is currently in. This accessor does @@ -101,15 +105,15 @@ public abstract class UsageStatsManagerInternal { * @return the AppStandby bucket code the app currently resides in. If the app is * unknown in the given user, STANDBY_BUCKET_NEVER is returned. */ - @StandbyBuckets public abstract int getAppStandbyBucket(String packageName, int userId, - long nowElapsed); + @StandbyBuckets public abstract int getAppStandbyBucket(String packageName, + @UserIdInt int userId, long nowElapsed); /** * Returns all of the uids for a given user where all packages associating with that uid * are in the app idle state -- there are no associated apps that are not idle. This means * all of the returned uids can be safely considered app idle. */ - public abstract int[] getIdleUidsForUser(int userId); + public abstract int[] getIdleUidsForUser(@UserIdInt int userId); /** * @return True if currently app idle parole mode is on. This means all idle apps are allow to @@ -134,8 +138,8 @@ public abstract class UsageStatsManagerInternal { public static abstract class AppIdleStateChangeListener { /** Callback to inform listeners that the idle state has changed to a new bucket. */ - public abstract void onAppIdleStateChanged(String packageName, int userId, boolean idle, - int bucket); + public abstract void onAppIdleStateChanged(String packageName, @UserIdInt int userId, + boolean idle, int bucket); /** * Callback to inform listeners that the parole state has changed. This means apps are @@ -144,10 +148,38 @@ public abstract class UsageStatsManagerInternal { public abstract void onParoleStateChanged(boolean isParoleOn); } - /* Backup/Restore API */ - public abstract byte[] getBackupPayload(int user, String key); + /** Backup/Restore API */ + public abstract byte[] getBackupPayload(@UserIdInt int userId, String key); - public abstract void applyRestoredPayload(int user, String key, byte[] payload); + /** + * ? + * @param userId + * @param key + * @param payload + */ + public abstract void applyRestoredPayload(@UserIdInt int userId, String key, byte[] payload); + + /** + * Called by DevicePolicyManagerService to inform that a new admin has been added. + * + * @param packageName the package in which the admin component is part of. + * @param userId the userId in which the admin has been added. + */ + public abstract void onActiveAdminAdded(String packageName, int userId); + + /** + * Called by DevicePolicyManagerService to inform about the active admins in an user. + * + * @param adminApps the set of active admins in {@param userId} or null if there are none. + * @param userId the userId to which the admin apps belong. + */ + public abstract void setActiveAdminApps(Set<String> adminApps, int userId); + + /** + * Called by DevicePolicyManagerService during boot to inform that admin data is loaded and + * pushed to UsageStatsService. + */ + public abstract void onAdminDataAvailable(); /** * Return usage stats. @@ -155,6 +187,29 @@ public abstract class UsageStatsManagerInternal { * @param obfuscateInstantApps whether instant app package names need to be obfuscated in the * result. */ - public abstract List<UsageStats> queryUsageStatsForUser( - int userId, int interval, long beginTime, long endTime, boolean obfuscateInstantApps); + public abstract List<UsageStats> queryUsageStatsForUser(@UserIdInt int userId, int interval, + long beginTime, long endTime, boolean obfuscateInstantApps); + + /** + * Used to persist the last time a job was run for this app, in order to make decisions later + * whether a job should be deferred until later. The time passed in should be in elapsed + * realtime since boot. + * @param packageName the app that executed a job. + * @param userId the user associated with the job. + * @param elapsedRealtime the time when the job was executed, in elapsed realtime millis since + * boot. + */ + public abstract void setLastJobRunTime(String packageName, @UserIdInt int userId, + long elapsedRealtime); + + /** + * Returns the time in millis since a job was executed for this app, in elapsed realtime + * timebase. This value can be larger than the current elapsed realtime if the job was executed + * before the device was rebooted. The default value is {@link Long#MAX_VALUE}. + * @param packageName the app you're asking about. + * @param userId the user associated with the job. + * @return the time in millis since a job was last executed for the app, provided it was + * indicated here before by a call to {@link #setLastJobRunTime(String, int, long)}. + */ + public abstract long getTimeSinceLastJobRun(String packageName, @UserIdInt int userId); } |