From 8693eabf20e798634427e812d69018232a926191 Mon Sep 17 00:00:00 2001 From: Mady Mellor Date: Mon, 15 Mar 2021 10:22:04 -0700 Subject: [PATCH] API Update: extra_is_bubbled -> Activity#isLaunchedFromBubble Per API council, moving this off of an intent and into ActivityOptions. This information is exposed via a new method: Activity#isLaunchedFromBubble In this CL: * Remove extra_is_bubbled bits & update javadoc * Adds launchedFromBubble bit on ActivityOptions * Check if the client is allowed to set this bit, only system / sysui can set it * Need to add this to the record / LaunchActivityItem as ActivityOptions get null'd out once they're used so save the bubble bit separately * Adds Activity#isLaunchedAsBubble * Sets bit on BubbleExpandedView Bug: 180945896 Test: atest NotificationManagerTest Change-Id: Ia2711f05fc4ebb521cc48e95c16336c13f2d47f8 --- core/api/current.txt | 2 +- core/api/test-current.txt | 1 + core/java/android/app/Activity.java | 22 ++++++++++++++++ core/java/android/app/ActivityOptions.java | 25 +++++++++++++++++++ core/java/android/app/ActivityThread.java | 7 +++++- core/java/android/app/Notification.java | 18 +++---------- .../servertransaction/LaunchActivityItem.java | 20 ++++++++++----- core/java/android/content/Intent.java | 10 -------- .../app/servertransaction/TestUtils.java | 8 +++++- .../activity/ActivityThreadClientTest.java | 3 ++- .../wm/shell/bubbles/BubbleExpandedView.java | 2 +- .../server/pm/LauncherAppsService.java | 1 - .../com/android/server/wm/ActivityRecord.java | 8 ++++++ .../server/wm/ActivityTaskSupervisor.java | 3 ++- .../server/wm/SafeActivityOptions.java | 14 +++++++++++ 15 files changed, 107 insertions(+), 37 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index 89cf257d894cb..061bddb9829ab 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -3958,6 +3958,7 @@ package android.app { method public boolean isImmersive(); method public boolean isInMultiWindowMode(); method public boolean isInPictureInPictureMode(); + method public boolean isLaunchedFromBubble(); method public boolean isLocalVoiceInteractionSupported(); method public boolean isTaskRoot(); method public boolean isVoiceInteraction(); @@ -11219,7 +11220,6 @@ package android.content { field public static final String EXTRA_INITIAL_INTENTS = "android.intent.extra.INITIAL_INTENTS"; field public static final String EXTRA_INSTALLER_PACKAGE_NAME = "android.intent.extra.INSTALLER_PACKAGE_NAME"; field public static final String EXTRA_INTENT = "android.intent.extra.INTENT"; - field public static final String EXTRA_IS_BUBBLED = "android.intent.extra.IS_BUBBLED"; field public static final String EXTRA_KEY_EVENT = "android.intent.extra.KEY_EVENT"; field public static final String EXTRA_LOCAL_ONLY = "android.intent.extra.LOCAL_ONLY"; field public static final String EXTRA_LOCUS_ID = "android.intent.extra.LOCUS_ID"; diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 6745a69e001e3..569cada33b18c 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -137,6 +137,7 @@ package android.app { method public void setLaunchActivityType(int); method public void setLaunchTaskId(int); method public void setLaunchWindowingMode(int); + method public void setLaunchedFromBubble(boolean); method public void setTaskAlwaysOnTop(boolean); method public void setTaskOverlay(boolean, boolean); } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 0f38b5fdb5c31..2a79684d2bb36 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -899,6 +899,9 @@ public class Activity extends ContextThemeWrapper /** The options for scene transition. */ ActivityOptions mPendingOptions; + /** Whether this activity was launched from a bubble. **/ + boolean mLaunchedFromBubble; + private static final class ManagedCursor { ManagedCursor(Cursor cursor) { mCursor = cursor; @@ -6805,6 +6808,25 @@ public class Activity extends ContextThemeWrapper return getSharedPreferences(getLocalClassName(), mode); } + /** + * Indicates whether this activity is launched from a bubble. A bubble is a floating shortcut + * on the screen that expands to show an activity. + * + * If your activity can be used normally or as a bubble, you might use this method to check + * if the activity is bubbled to modify any behaviour that might be different between the + * normal activity and the bubbled activity. For example, if you normally cancel the + * notification associated with the activity when you open the activity, you might not want to + * do that when you're bubbled as that would remove the bubble. + * + * @return {@code true} if the activity is launched from a bubble. + * + * @see Notification.Builder#setBubbleMetadata(Notification.BubbleMetadata) + * @see Notification.BubbleMetadata.Builder#Builder(String) + */ + public boolean isLaunchedFromBubble() { + return mLaunchedFromBubble; + } + private void ensureSearchManager() { if (mSearchManager != null) { return; diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 006b2b5161d38..c21de62ed38ea 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -323,6 +323,9 @@ public class ActivityOptions { /** See {@link #setRemoveWithTaskOrganizer(boolean)}. */ private static final String KEY_REMOVE_WITH_TASK_ORGANIZER = "android.activity.removeWithTaskOrganizer"; + /** See {@link #setLaunchedFromBubble(boolean)}. */ + private static final String KEY_LAUNCHED_FROM_BUBBLE = + "android.activity.launchTypeBubble"; /** * @see #setLaunchCookie @@ -410,6 +413,7 @@ public class ActivityOptions { private boolean mOverrideTaskTransition; private int mSplashScreenThemeResId; private boolean mRemoveWithTaskOrganizer; + private boolean mLaunchedFromBubble; /** * Create an ActivityOptions specifying a custom animation to run when @@ -1161,6 +1165,7 @@ public class ActivityOptions { mOverrideTaskTransition = opts.getBoolean(KEY_OVERRIDE_TASK_TRANSITION); mSplashScreenThemeResId = opts.getInt(KEY_SPLASH_SCREEN_THEME); mRemoveWithTaskOrganizer = opts.getBoolean(KEY_REMOVE_WITH_TASK_ORGANIZER); + mLaunchedFromBubble = opts.getBoolean(KEY_LAUNCHED_FROM_BUBBLE); } /** @@ -1646,6 +1651,23 @@ public class ActivityOptions { return mRemoveWithTaskOrganizer; } + /** + * Sets whether this activity is launched from a bubble. + * @hide + */ + @TestApi + public void setLaunchedFromBubble(boolean fromBubble) { + mLaunchedFromBubble = fromBubble; + } + + /** + * @return whether the activity was launched from a bubble. + * @hide + */ + public boolean getLaunchedFromBubble() { + return mLaunchedFromBubble; + } + /** * Update the current values in this ActivityOptions from those supplied * in otherOptions. Any values @@ -1883,6 +1905,9 @@ public class ActivityOptions { if (mRemoveWithTaskOrganizer) { b.putBoolean(KEY_REMOVE_WITH_TASK_ORGANIZER, mRemoveWithTaskOrganizer); } + if (mLaunchedFromBubble) { + b.putBoolean(KEY_LAUNCHED_FROM_BUBBLE, mLaunchedFromBubble); + } return b; } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index e596e7c2ab068..4d7357a77ab25 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -600,6 +600,9 @@ public final class ActivityThread extends ClientTransactionHandler */ FixedRotationAdjustments mPendingFixedRotationAdjustments; + /** Whether this activiy was launched from a bubble. */ + boolean mLaunchedFromBubble; + @LifecycleState private int mLifecycleState = PRE_ON_CREATE; @@ -619,7 +622,7 @@ public final class ActivityThread extends ClientTransactionHandler List pendingNewIntents, ActivityOptions activityOptions, boolean isForward, ProfilerInfo profilerInfo, ClientTransactionHandler client, IBinder assistToken, FixedRotationAdjustments fixedRotationAdjustments, - IBinder shareableActivityToken) { + IBinder shareableActivityToken, boolean launchedFromBubble) { this.token = token; this.assistToken = assistToken; this.shareableActivityToken = shareableActivityToken; @@ -640,6 +643,7 @@ public final class ActivityThread extends ClientTransactionHandler compatInfo); mActivityOptions = activityOptions; mPendingFixedRotationAdjustments = fixedRotationAdjustments; + mLaunchedFromBubble = launchedFromBubble; init(); } @@ -3542,6 +3546,7 @@ public final class ActivityThread extends ClientTransactionHandler activity.mPendingOptions = r.mActivityOptions; r.mActivityOptions = null; } + activity.mLaunchedFromBubble = r.mLaunchedFromBubble; activity.mCalled = false; if (r.isPersistable()) { mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 4e140a8d3f45d..579863016c932 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -10194,13 +10194,8 @@ public class Notification implements Parcelable *

The shortcut activity will be used when the bubble is expanded. This will display * the shortcut activity in a floating window over the existing foreground activity.

* - *

When the shortcut is displayed in a bubble, there will be an intent - * extra set on the activity, {@link Intent#EXTRA_IS_BUBBLED} - * with {@code true}. You may check this in the onCreate of your activity via: - * - *

-             * boolean isBubbled = getIntent().getBooleanExtra(Intent.EXTRA_IS_BUBBLED, false);
-             * 
+ *

When the activity is launched from a bubble, + * {@link Activity#isLaunchedFromBubble()} will return with {@code true}. *

* *

If the shortcut has not been published when the bubble notification is sent, @@ -10231,13 +10226,8 @@ public class Notification implements Parcelable * app content in a floating window over the existing foreground activity. The intent * should point to a resizable activity.

* - *

When the activity is displayed in a bubble, there will be an intent - * extra set on the activity, {@link Intent#EXTRA_IS_BUBBLED} - * with {@code true}. You may check this in the onCreate of your activity via: - * - *

-             * boolean isBubbled = getIntent().getBooleanExtra(Intent.EXTRA_IS_BUBBLED, false);
-             * 
+ *

When the activity is launched from a bubble, + * {@link Activity#isLaunchedFromBubble()} will return with {@code true}. *

* * @throws NullPointerException if intent is null. diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java index 9f8fcc15b747b..e281a02681845 100644 --- a/core/java/android/app/servertransaction/LaunchActivityItem.java +++ b/core/java/android/app/servertransaction/LaunchActivityItem.java @@ -72,6 +72,7 @@ public class LaunchActivityItem extends ClientTransactionItem { private ProfilerInfo mProfilerInfo; private IBinder mAssistToken; private IBinder mShareableActivityToken; + private boolean mLaunchedFromBubble; /** * It is only non-null if the process is the first time to launch activity. It is only an * optimization for quick look up of the interface so the field is ignored for comparison. @@ -96,7 +97,8 @@ public class LaunchActivityItem extends ClientTransactionItem { ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo, mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState, mPendingResults, mPendingNewIntents, mActivityOptions, mIsForward, mProfilerInfo, - client, mAssistToken, mFixedRotationAdjustments, mShareableActivityToken); + client, mAssistToken, mFixedRotationAdjustments, mShareableActivityToken, + mLaunchedFromBubble); client.handleLaunchActivity(r, pendingActions, null /* customIntent */); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } @@ -120,7 +122,8 @@ public class LaunchActivityItem extends ClientTransactionItem { List pendingNewIntents, ActivityOptions activityOptions, boolean isForward, ProfilerInfo profilerInfo, IBinder assistToken, IActivityClientController activityClientController, - FixedRotationAdjustments fixedRotationAdjustments, IBinder shareableActivityToken) { + FixedRotationAdjustments fixedRotationAdjustments, IBinder shareableActivityToken, + boolean launchedFromBubble) { LaunchActivityItem instance = ObjectPool.obtain(LaunchActivityItem.class); if (instance == null) { instance = new LaunchActivityItem(); @@ -128,7 +131,8 @@ public class LaunchActivityItem extends ClientTransactionItem { setValues(instance, intent, ident, info, curConfig, overrideConfig, compatInfo, referrer, voiceInteractor, procState, state, persistentState, pendingResults, pendingNewIntents, activityOptions, isForward, profilerInfo, assistToken, - activityClientController, fixedRotationAdjustments, shareableActivityToken); + activityClientController, fixedRotationAdjustments, shareableActivityToken, + launchedFromBubble); return instance; } @@ -136,7 +140,7 @@ public class LaunchActivityItem extends ClientTransactionItem { @Override public void recycle() { setValues(this, null, 0, null, null, null, null, null, null, 0, null, null, null, null, - null, false, null, null, null, null, null); + null, false, null, null, null, null, null, false); ObjectPool.recycle(this); } @@ -166,6 +170,7 @@ public class LaunchActivityItem extends ClientTransactionItem { dest.writeStrongInterface(mActivityClientController); dest.writeTypedObject(mFixedRotationAdjustments, flags); dest.writeStrongBinder(mShareableActivityToken); + dest.writeBoolean(mLaunchedFromBubble); } /** Read from Parcel. */ @@ -183,7 +188,8 @@ public class LaunchActivityItem extends ClientTransactionItem { in.readTypedObject(ProfilerInfo.CREATOR), in.readStrongBinder(), IActivityClientController.Stub.asInterface(in.readStrongBinder()), - in.readTypedObject(FixedRotationAdjustments.CREATOR), in.readStrongBinder()); + in.readTypedObject(FixedRotationAdjustments.CREATOR), in.readStrongBinder(), + in.readBoolean()); } public static final @NonNull Creator CREATOR = @@ -293,7 +299,8 @@ public class LaunchActivityItem extends ClientTransactionItem { List pendingResults, List pendingNewIntents, ActivityOptions activityOptions, boolean isForward, ProfilerInfo profilerInfo, IBinder assistToken, IActivityClientController activityClientController, - FixedRotationAdjustments fixedRotationAdjustments, IBinder shareableActivityToken) { + FixedRotationAdjustments fixedRotationAdjustments, IBinder shareableActivityToken, + boolean launchedFromBubble) { instance.mIntent = intent; instance.mIdent = ident; instance.mInfo = info; @@ -314,5 +321,6 @@ public class LaunchActivityItem extends ClientTransactionItem { instance.mActivityClientController = activityClientController; instance.mFixedRotationAdjustments = fixedRotationAdjustments; instance.mShareableActivityToken = shareableActivityToken; + instance.mLaunchedFromBubble = launchedFromBubble; } } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 2c77372d6345b..e7a2be7b11aeb 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -6192,16 +6192,6 @@ public class Intent implements Parcelable, Cloneable { */ public static final String EXTRA_UNSTARTABLE_REASON = "android.intent.extra.UNSTARTABLE_REASON"; - /** - * A boolean extra indicating whether an activity is bubbled. Set on the shortcut or - * pending intent provided for the bubble. If the extra is not present or false, then it is not - * bubbled. - * - * @see android.app.Notification.Builder#setBubbleMetadata(Notification.BubbleMetadata) - * @see android.app.Notification.BubbleMetadata.Builder#Builder(String) - */ - public static final String EXTRA_IS_BUBBLED = "android.intent.extra.IS_BUBBLED"; - // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Intent flags (see mFlags variable). diff --git a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java index 1a06789e729f9..75da0bfba5812 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java +++ b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java @@ -111,6 +111,7 @@ class TestUtils { private IBinder mAssistToken; private IBinder mShareableActivityToken; private FixedRotationAdjustments mFixedRotationAdjustments; + private boolean mLaunchedFromBubble; LaunchActivityItemBuilder setIntent(Intent intent) { mIntent = intent; @@ -207,13 +208,18 @@ class TestUtils { return this; } + LaunchActivityItemBuilder setLaunchedFromBubble(boolean launchedFromBubble) { + mLaunchedFromBubble = launchedFromBubble; + return this; + } + LaunchActivityItem build() { return LaunchActivityItem.obtain(mIntent, mIdent, mInfo, mCurConfig, mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mProcState, mState, mPersistentState, mPendingResults, mPendingNewIntents, mActivityOptions, mIsForward, mProfilerInfo, mAssistToken, null /* activityClientController */, mFixedRotationAdjustments, - mShareableActivityToken); + mShareableActivityToken, mLaunchedFromBubble); } } } diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java index 0808186b21c5f..269d8424a78f3 100644 --- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java +++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java @@ -298,7 +298,8 @@ public class ActivityThreadClientTest { null /* pendingResults */, null /* pendingNewIntents */, null /* activityOptions */, true /* isForward */, null /* profilerInfo */, mThread /* client */, null /* asssitToken */, - null /* fixedRotationAdjustments */, null /* shareableActivityToken */); + null /* fixedRotationAdjustments */, null /* shareableActivityToken */, + false /* launchedFromBubble */); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index 9ef3fb54fb356..abe1f7179dda0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -154,6 +154,7 @@ public class BubbleExpandedView extends LinearLayout { } try { options.setTaskAlwaysOnTop(true); + options.setLaunchedFromBubble(true); if (!mIsOverflow && mBubble.hasMetadataShortcutId()) { options.setApplyActivityFlagsForBubbles(true); mTaskView.startShortcutActivity(mBubble.getShortcutInfo(), @@ -163,7 +164,6 @@ public class BubbleExpandedView extends LinearLayout { // Apply flags to make behaviour match documentLaunchMode=always. fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT); fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); - fillInIntent.putExtra(Intent.EXTRA_IS_BUBBLED, true); if (mBubble != null) { mBubble.setIntentActive(); } diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 27e0ffc73d9d1..91a66acf433df 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -978,7 +978,6 @@ public class LauncherAppsService extends SystemService { // Flag for bubble to make behaviour match documentLaunchMode=always. intents[0].addFlags(FLAG_ACTIVITY_NEW_DOCUMENT); intents[0].addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); - intents[0].putExtra(Intent.EXTRA_IS_BUBBLED, true); } intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index d13babc166ebb..bbf34ca87964b 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -560,6 +560,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // The locusId associated with this activity, if set. private LocusId mLocusId; + // Whether the activity was launched from a bubble. + private boolean mLaunchedFromBubble; + private SizeConfigurationBuckets mSizeConfigurations; /** @@ -4139,7 +4142,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + boolean getLaunchedFromBubble() { + return mLaunchedFromBubble; + } + private void setOptions(@NonNull ActivityOptions options) { + mLaunchedFromBubble = options.getLaunchedFromBubble(); mPendingOptions = options; if (options.getAnimationType() == ANIM_REMOTE_ANIMATION) { mPendingRemoteAnimation = options.getRemoteAnimationAdapter(); diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index bfbc10aee7a72..bdde3692ef534 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -826,7 +826,8 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { r.getSavedState(), r.getPersistentSavedState(), results, newIntents, r.takeOptions(), dc.isNextTransitionForward(), proc.createProfilerInfoIfNeeded(), r.assistToken, activityClientController, - r.createFixedRotationAdjustmentsIfNeeded(), r.shareableActivityToken)); + r.createFixedRotationAdjustmentsIfNeeded(), r.shareableActivityToken, + r.getLaunchedFromBubble())); // Set desired final state. final ActivityLifecycleItem lifecycleItem; diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java index 65671959e8846..c9d5fa473ea89 100644 --- a/services/core/java/com/android/server/wm/SafeActivityOptions.java +++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS; import static android.Manifest.permission.START_TASKS_FROM_RECENTS; +import static android.Manifest.permission.STATUS_BAR_SERVICE; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; @@ -278,6 +279,19 @@ public class SafeActivityOptions { Slog.w(TAG, msg); throw new SecurityException(msg); } + + // If launched from bubble is specified, then ensure that the caller is system or sysui. + if (options.getLaunchedFromBubble() && callingUid != Process.SYSTEM_UID) { + final int statusBarPerm = ActivityTaskManagerService.checkPermission( + STATUS_BAR_SERVICE, callingPid, callingUid); + if (statusBarPerm == PERMISSION_DENIED) { + final String msg = "Permission Denial: starting " + getIntentString(intent) + + " from " + callerApp + " (pid=" + callingPid + + ", uid=" + callingUid + ") with launchedFromBubble=true"; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + } } private String getIntentString(Intent intent) {