diff options
author | 2025-02-19 16:48:53 -0800 | |
---|---|---|
committer | 2025-02-20 11:23:33 -0800 | |
commit | a6c1538861f8af3f21dae2cb8a2cd50d189e67ec (patch) | |
tree | eae554388e53b4d2a933476efb5bc7e0930440dd | |
parent | 567e8a795366388863a8e0d72412c110de9aa141 (diff) |
Don't set bubble launch flags on non-chat shortcut bubbles
Previously the isShortcutBubble path was only for chat bubbles using
shortcuts, however, with bubble anything we allow bubbling shortcuts
on launcher. These were still being tagged as "launch from bubble"
when they shouldn't be.
This CL only sets those flags if it's a chat bubble. If it's a
shortcut bubble from bubble anything, only set the multiple task flag
which is consistent with intent/pendingIntent app bubbles.
This CL also adds a bunch of verification in the BubbleTaskViewListener
test for the flags set on intents / activity options.
It seems like there are some issues with robolectric and PendingIntents.
I couldn't figure out a way to capture PendingIntents in robolectric, so
I modified the code to always use a fillInIntent & the flags are checked
on that instead.
Flag: EXEMPT bug fix
Test: atest BubbleTaskViewListenerTest
Bug: 395145961
Bug: 395178844
Change-Id: Ia01f740ab80d46f8b6533d9f87ba319a259bd5e0
5 files changed, 179 insertions, 26 deletions
diff --git a/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml b/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml index 95cd1c72a2af..800ea7446b6e 100644 --- a/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml +++ b/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml @@ -2,6 +2,7 @@ package="com.android.wm.shell.multivalenttests"> <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS"/> + <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT"/> <application android:debuggable="true" android:supportsRtl="true" > <uses-library android:name="android.test.runner" /> diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewListenerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewListenerTest.kt index 9ebc3d78b3a7..3aefcd5ec6c0 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewListenerTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewListenerTest.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.bubbles +import android.app.ActivityOptions import android.app.Notification import android.app.PendingIntent import android.content.ComponentName @@ -24,6 +25,8 @@ import android.content.Intent import android.content.pm.ShortcutInfo import android.graphics.drawable.Icon import android.os.UserHandle +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule import android.service.notification.NotificationListenerService.Ranking import android.service.notification.StatusBarNotification import android.view.View @@ -33,6 +36,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.android.internal.protolog.ProtoLog +import com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_ANYTHING import com.android.wm.shell.R import com.android.wm.shell.bubbles.Bubbles.BubbleMetadataFlagListener import com.android.wm.shell.common.TestShellExecutor @@ -41,6 +45,7 @@ import com.android.wm.shell.taskview.TaskViewController import com.android.wm.shell.taskview.TaskViewTaskController import com.google.common.truth.Truth.assertThat import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito @@ -48,6 +53,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.mock @@ -61,6 +67,9 @@ import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class BubbleTaskViewListenerTest { + @get:Rule + val setFlagsRule = SetFlagsRule() + private val context = ApplicationProvider.getApplicationContext<Context>() private var taskViewController = mock<TaskViewController>() @@ -155,9 +164,22 @@ class BubbleTaskViewListenerTest { } getInstrumentation().waitForIdleSync() - // ..so it's pending intent-based, and launches that + // ..so it's pending intent-based, so the pending intent should be active assertThat(b.isPendingIntentActive).isTrue() - verify(taskViewController).startActivity(any(), eq(pendingIntent), any(), any(), any()) + + val intentCaptor = argumentCaptor<Intent>() + val optionsCaptor = argumentCaptor<ActivityOptions>() + + verify(taskViewController).startActivity(any(), + eq(pendingIntent), + intentCaptor.capture(), + optionsCaptor.capture(), + any()) + val intentFlags = intentCaptor.lastValue.flags + assertThat((intentFlags and Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0).isTrue() + assertThat((intentFlags and Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0).isTrue() + assertThat(optionsCaptor.lastValue.launchedFromBubble).isTrue() + assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue() } @Test @@ -178,12 +200,52 @@ class BubbleTaskViewListenerTest { } getInstrumentation().waitForIdleSync() - assertThat(b.isPendingIntentActive).isFalse() - verify(taskViewController).startShortcutActivity(any(), eq(shortcutInfo), any(), any()) + val optionsCaptor = argumentCaptor<ActivityOptions>() + + assertThat(b.isPendingIntentActive).isFalse() // not triggered for shortcut chats + verify(taskViewController).startShortcutActivity(any(), + eq(shortcutInfo), + optionsCaptor.capture(), + any()) + assertThat(optionsCaptor.lastValue.launchedFromBubble).isTrue() + assertThat(optionsCaptor.lastValue.isApplyActivityFlagsForBubbles).isTrue() + assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue() } + @EnableFlags(FLAG_ENABLE_BUBBLE_ANYTHING) @Test - fun onInitialized_appBubble() { + fun onInitialized_shortcutBubble() { + val shortcutInfo = ShortcutInfo.Builder(context) + .setId("mockShortcutId") + .build() + + val b = createShortcutBubble(shortcutInfo) + bubbleTaskViewListener.setBubble(b) + + assertThat(b.isChat).isFalse() + assertThat(b.isShortcut).isTrue() + assertThat(b.shortcutInfo).isNotNull() + + getInstrumentation().runOnMainSync { + bubbleTaskViewListener.onInitialized() + } + getInstrumentation().waitForIdleSync() + + val optionsCaptor = argumentCaptor<ActivityOptions>() + + assertThat(b.isPendingIntentActive).isFalse() // chat only triggers setting it active + verify(taskViewController).startShortcutActivity(any(), + eq(shortcutInfo), + optionsCaptor.capture(), + any()) + assertThat(optionsCaptor.lastValue.launchedFromBubble).isFalse() // chat only + assertThat(optionsCaptor.lastValue.isApplyActivityFlagsForBubbles).isFalse() // chat only + assertThat(optionsCaptor.lastValue.isApplyMultipleTaskFlagForShortcut).isTrue() + assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue() + } + + @Test + fun onInitialized_appBubble_intent() { val b = createAppBubble() bubbleTaskViewListener.setBubble(b) @@ -194,11 +256,83 @@ class BubbleTaskViewListenerTest { } getInstrumentation().waitForIdleSync() - assertThat(b.isPendingIntentActive).isFalse() - verify(taskViewController).startActivity(any(), any(), anyOrNull(), any(), any()) + val intentCaptor = argumentCaptor<Intent>() + val optionsCaptor = argumentCaptor<ActivityOptions>() + + assertThat(b.isPendingIntentActive).isFalse() // chat only triggers setting it active + verify(taskViewController).startActivity(any(), + any(), + intentCaptor.capture(), + optionsCaptor.capture(), + any()) + + assertThat((intentCaptor.lastValue.flags + and Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0).isTrue() + assertThat(optionsCaptor.lastValue.launchedFromBubble).isFalse() // chat only + assertThat(optionsCaptor.lastValue.isApplyActivityFlagsForBubbles).isFalse() // chat only + assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue() } @Test + fun onInitialized_appBubble_pendingIntent() { + val b = createAppBubble(usePendingIntent = true) + bubbleTaskViewListener.setBubble(b) + + assertThat(b.isApp).isTrue() + + getInstrumentation().runOnMainSync { + bubbleTaskViewListener.onInitialized() + } + getInstrumentation().waitForIdleSync() + + val intentCaptor = argumentCaptor<Intent>() + val optionsCaptor = argumentCaptor<ActivityOptions>() + + assertThat(b.isPendingIntentActive).isFalse() // chat only triggers setting it active + verify(taskViewController).startActivity(any(), + any(), + intentCaptor.capture(), + optionsCaptor.capture(), + any()) + + assertThat((intentCaptor.lastValue.flags + and Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0).isTrue() + assertThat(optionsCaptor.lastValue.launchedFromBubble).isFalse() // chat only + assertThat(optionsCaptor.lastValue.isApplyActivityFlagsForBubbles).isFalse() // chat only + assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue() + } + + @Test + fun onInitialized_noteBubble() { + val b = createNoteBubble() + bubbleTaskViewListener.setBubble(b) + + assertThat(b.isNote).isTrue() + + getInstrumentation().runOnMainSync { + bubbleTaskViewListener.onInitialized() + } + getInstrumentation().waitForIdleSync() + + val intentCaptor = argumentCaptor<Intent>() + val optionsCaptor = argumentCaptor<ActivityOptions>() + + assertThat(b.isPendingIntentActive).isFalse() // chat only triggers setting it active + verify(taskViewController).startActivity(any(), + any(), + intentCaptor.capture(), + optionsCaptor.capture(), + any()) + + assertThat((intentCaptor.lastValue.flags + and Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0).isTrue() + assertThat(optionsCaptor.lastValue.launchedFromBubble).isFalse() // chat only + assertThat(optionsCaptor.lastValue.isApplyActivityFlagsForBubbles).isFalse() // chat only + assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue() + } + + + @Test fun onInitialized_preparingTransition() { val b = createAppBubble() bubbleTaskViewListener.setBubble(b) @@ -416,13 +550,24 @@ class BubbleTaskViewListenerTest { assertThat(isNew).isTrue() } - private fun createAppBubble(): Bubble { + private fun createAppBubble(usePendingIntent: Boolean = false): Bubble { val target = Intent(context, TestActivity::class.java) target.setPackage(context.packageName) + if (usePendingIntent) { + // Robolectric doesn't seem to play nice with PendingIntents, have to mock it. + val pendingIntent = mock<PendingIntent>() + whenever(pendingIntent.intent).thenReturn(target) + return Bubble.createAppBubble(pendingIntent, mock<UserHandle>(), + mainExecutor, bgExecutor) + } return Bubble.createAppBubble(target, mock<UserHandle>(), mock<Icon>(), mainExecutor, bgExecutor) } + private fun createShortcutBubble(shortcutInfo: ShortcutInfo): Bubble { + return Bubble.createShortcutBubble(shortcutInfo, mainExecutor, bgExecutor) + } + private fun createNoteBubble(): Bubble { val target = Intent(context, TestActivity::class.java) target.setPackage(context.packageName) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index d9489287ff42..313d151aeab7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -364,7 +364,7 @@ public class Bubble implements BubbleViewProvider { @ShellMainThread Executor mainExecutor, @ShellBackgroundThread Executor bgExecutor) { return new Bubble(intent, user, - /* key= */ getAppBubbleKeyForApp(ComponentUtils.getPackageName(intent), user), + /* key= */ getAppBubbleKeyForApp(intent.getIntent().getPackage(), user), mainExecutor, bgExecutor); } 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 f9fef9b45c39..2c2451cab999 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 @@ -237,18 +237,24 @@ public class BubbleExpandedView extends LinearLayout { Context context = mContext.createContextAsUser( mBubble.getUser(), Context.CONTEXT_RESTRICTED); + Intent fillInIntent = new Intent(); + fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); PendingIntent pi = PendingIntent.getActivity( context, /* requestCode= */ 0, - mBubble.getIntent().addFlags(FLAG_ACTIVITY_MULTIPLE_TASK), - PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT, + mBubble.getIntent(), + // Needs to be mutable for the fillInIntent + PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT, /* options= */ null); - mTaskView.startActivity(pi, /* fillInIntent= */ null, options, - launchBounds); + mTaskView.startActivity(pi, fillInIntent, options, launchBounds); } else if (!mIsOverflow && isShortcutBubble) { ProtoLog.v(WM_SHELL_BUBBLES, "startingShortcutBubble=%s", getBubbleKey()); - options.setLaunchedFromBubble(true); - options.setApplyActivityFlagsForBubbles(true); + if (mBubble.isChat()) { + options.setLaunchedFromBubble(true); + options.setApplyActivityFlagsForBubbles(true); + } else { + options.setApplyMultipleTaskFlagForShortcut(true); + } mTaskView.startShortcutActivity(mBubble.getShortcutInfo(), options, launchBounds); } else { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewListener.java index a38debb702dc..63d713495177 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewListener.java @@ -129,27 +129,28 @@ public class BubbleTaskViewListener implements TaskView.Listener { Context context = mContext.createContextAsUser( mBubble.getUser(), Context.CONTEXT_RESTRICTED); - Intent fillInIntent = null; + Intent fillInIntent = new Intent(); + fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); // First try get pending intent from the bubble PendingIntent pi = mBubble.getPendingIntent(); if (pi == null) { - // If null - create new one + // If null - create new one based on the bubble intent pi = PendingIntent.getActivity( context, /* requestCode= */ 0, - mBubble.getIntent() - .addFlags(FLAG_ACTIVITY_MULTIPLE_TASK), - PendingIntent.FLAG_IMMUTABLE - | PendingIntent.FLAG_UPDATE_CURRENT, + mBubble.getIntent(), + // Needs to be mutable for the fillInIntent + PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT, /* options= */ null); - } else { - fillInIntent = new Intent(pi.getIntent()); - fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); } mTaskView.startActivity(pi, fillInIntent, options, launchBounds); } else if (isShortcutBubble) { - options.setLaunchedFromBubble(true); - options.setApplyActivityFlagsForBubbles(true); + if (mBubble.isChat()) { + options.setLaunchedFromBubble(true); + options.setApplyActivityFlagsForBubbles(true); + } else { + options.setApplyMultipleTaskFlagForShortcut(true); + } mTaskView.startShortcutActivity(mBubble.getShortcutInfo(), options, launchBounds); } else { |