diff --git a/core/api/current.txt b/core/api/current.txt index 35129d5d00a31..aad6b37c44eca 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -3960,6 +3960,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(); @@ -11222,7 +11223,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 63e90100b6b45..04dd04cf03d2f 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 f0d5a893bb96c..45120b694b625 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; @@ -6790,6 +6793,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 35890c811428b..8ff14b0ad28f4 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -594,6 +594,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; @@ -613,7 +616,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; @@ -634,6 +637,7 @@ public final class ActivityThread extends ClientTransactionHandler compatInfo); mActivityOptions = activityOptions; mPendingFixedRotationAdjustments = fixedRotationAdjustments; + mLaunchedFromBubble = launchedFromBubble; init(); } @@ -3549,6 +3553,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 0ca0b85215684..a60f1ca3cdcd8 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -10205,13 +10205,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, @@ -10242,13 +10237,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 a482b8c189cd6..9ad017c5f9c68 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -6177,16 +6177,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 631d86660dcf6..e0724fd27cf4d 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -561,6 +561,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; /** @@ -4218,7 +4221,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) {