Loading libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerTest.kt +186 −47 Original line number Diff line number Diff line Loading @@ -17,24 +17,31 @@ package com.android.wm.shell.bubbles import android.app.ActivityManager import android.app.TaskInfo import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.pm.LauncherApps import android.content.pm.ShortcutInfo import android.content.res.Resources import android.graphics.Insets import android.graphics.Rect import android.graphics.drawable.Icon import android.os.Handler import android.os.UserHandle import android.os.UserManager import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import android.platform.test.flag.junit.SetFlagsRule import android.view.IWindowManager import android.view.InsetsSource import android.view.InsetsState import android.view.SurfaceControl import android.view.WindowInsets import android.view.WindowManager import android.view.WindowManager.TRANSIT_CHANGE import android.window.TransitionInfo import android.window.WindowContainerToken import androidx.core.content.getSystemService import androidx.test.core.app.ApplicationProvider import androidx.test.filters.SmallTest Loading @@ -42,6 +49,7 @@ import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.protolog.ProtoLog import com.android.internal.statusbar.IStatusBarService import com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR import com.android.wm.shell.Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE import com.android.wm.shell.R import com.android.wm.shell.ShellTaskOrganizer Loading @@ -62,12 +70,16 @@ import com.android.wm.shell.common.TestSyncExecutor import com.android.wm.shell.draganddrop.DragAndDropController import com.android.wm.shell.shared.TransactionPool import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper import com.android.wm.shell.shared.bubbles.DeviceConfig import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.taskview.TaskViewRepository import com.android.wm.shell.taskview.TaskViewTaskController import com.android.wm.shell.taskview.TaskViewTransitions import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TRANSIT_CONVERT_TO_BUBBLE import com.android.wm.shell.transition.Transitions.TransitionHandler import com.google.common.truth.Truth.assertThat import com.google.common.util.concurrent.MoreExecutors.directExecutor import java.util.Optional Loading @@ -79,11 +91,9 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.isA import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify Loading @@ -103,13 +113,15 @@ class BubbleControllerTest(flags: FlagsParameterization) { @get:Rule val setFlagsRule = SetFlagsRule(flags) private val bubbleTransitions = mock<BubbleTransitions>() private val context = ApplicationProvider.getApplicationContext<Context>() private val uiEventLoggerFake = UiEventLoggerFake() private val displayImeController = mock<DisplayImeController>() private val displayInsetsController = mock<DisplayInsetsController>() private val userManager = mock<UserManager>() private val taskStackListener = mock<TaskStackListenerImpl>() private val transitions = mock<Transitions>() private val taskViewTransitions = mock<TaskViewTransitions>() private val bubbleAppInfoProvider = FakeBubbleAppInfoProvider() private lateinit var bubbleController: BubbleController private lateinit var bubblePositioner: BubblePositioner Loading @@ -120,6 +132,23 @@ class BubbleControllerTest(flags: FlagsParameterization) { private lateinit var eduController: BubbleEducationController private lateinit var displayController: DisplayController private lateinit var imeListener: ImeListener private lateinit var bubbleTransitions: BubbleTransitions private lateinit var shellTaskOrganizer: ShellTaskOrganizer private val deviceConfigFolded = DeviceConfig( windowBounds = Rect(0, 0, 700, 1000), isLargeScreen = false, isSmallTablet = false, isLandscape = false, isRtl = false, insets = Insets.of(10, 20, 30, 40), ) private val deviceConfigUnfolded = deviceConfigFolded.copy( windowBounds = Rect(0, 0, 1400, 2000), isLargeScreen = true, isSmallTablet = true, ) @Before fun setUp() { Loading @@ -144,7 +173,7 @@ class BubbleControllerTest(flags: FlagsParameterization) { on { getDisplayLayout(anyInt()) } doReturn DisplayLayout(context, realDefaultDisplay) } bubblePositioner = BubblePositioner(context, windowManager) bubblePositioner = BubblePositioner(context, deviceConfigFolded) bubbleData = BubbleData( Loading @@ -156,6 +185,27 @@ class BubbleControllerTest(flags: FlagsParameterization) { bgExecutor, ) shellTaskOrganizer = ShellTaskOrganizer( mock<ShellInit>(), ShellCommandHandler(), null, Optional.empty(), Optional.empty(), TestSyncExecutor(), ) bubbleTransitions = BubbleTransitions( context, transitions, shellTaskOrganizer, mock<TaskViewRepository>(), bubbleData, taskViewTransitions, bubbleAppInfoProvider ) bubbleController = createBubbleController( bubbleData, Loading Loading @@ -394,11 +444,24 @@ class BubbleControllerTest(flags: FlagsParameterization) { assertThat(bubbleController.hasStableBubbleForTask(777)).isFalse() } @EnableFlags(FLAG_ENABLE_BUBBLE_BAR) @Test fun expandStackAndSelectBubbleForExistingTransition_reusesExistingBubble() { assumeTrue(BubbleAnythingFlagHelper.enableCreateAnyBubble()) val bubble = createBubble("key", taskId = 123) // switch to bubble bar mode because the transition currently requires bubble layer view bubblePositioner.update(deviceConfigUnfolded) bubblePositioner.isShowingInBubbleBar = true getInstrumentation().runOnMainSync { bubbleController.setLauncherHasBubbleBar(true) bubbleController.registerBubbleStateListener(FakeBubblesStateListener()) } val taskInfo = ActivityManager.RunningTaskInfo().apply { taskId = 123 baseActivity = COMPONENT } val bubble = createAppBubble(taskInfo) getInstrumentation().runOnMainSync { bubbleData.notificationEntryUpdated( bubble, Loading @@ -406,7 +469,6 @@ class BubbleControllerTest(flags: FlagsParameterization) { true /* showInShade= */, ) } val taskInfo = ActivityManager.RunningTaskInfo().apply { taskId = 123 } getInstrumentation().runOnMainSync { bubbleController.expandStackAndSelectBubbleForExistingTransition( Loading @@ -415,55 +477,138 @@ class BubbleControllerTest(flags: FlagsParameterization) { ) {} } verify(bubbleTransitions).startLaunchNewTaskBubbleForExistingTransition( eq(bubble), /* bubble */ any(), /* expandedViewManager */ any(), /* factory */ any(), /* positioner */ any(), /* stackView */ anyOrNull(), /* layerView */ any(), /* iconFactory */ any(), /* inflateSync */ any(), /* transition */ any(), /* onInflatedCallback */ ) assertThat(bubbleData.selectedBubble).isEqualTo(bubble) assertThat(bubbleController.isStackExpanded).isTrue() } @EnableFlags(FLAG_ENABLE_BUBBLE_BAR) @Test fun expandStackAndSelectBubbleForExistingTransition_newBubble() { assumeTrue(BubbleAnythingFlagHelper.enableCreateAnyBubble()) // switch to bubble bar mode because the transition currently requires bubble layer view bubblePositioner.update(deviceConfigUnfolded) bubblePositioner.isShowingInBubbleBar = true getInstrumentation().runOnMainSync { bubbleController.setLauncherHasBubbleBar(true) bubbleController.registerBubbleStateListener(FakeBubblesStateListener()) } val taskInfo = ActivityManager.RunningTaskInfo().apply { baseActivity = COMPONENT taskId = 123 token = mock<WindowContainerToken>() } var transitionHandler: TransitionHandler? = null getInstrumentation().runOnMainSync { bubbleController.expandStackAndSelectBubbleForExistingTransition( transitionHandler = bubbleController.expandStackAndSelectBubbleForExistingTransition( taskInfo, mock(), /* transition */ ) {} } val bubble = argumentCaptor<Bubble>().let { bubbleCaptor -> verify(bubbleTransitions).startLaunchNewTaskBubbleForExistingTransition( bubbleCaptor.capture(), /* bubble */ any(), /* expandedViewManager */ any(), /* factory */ any(), /* positioner */ any(), /* stackView */ anyOrNull(), /* layerView */ any(), /* iconFactory */ any(), /* inflateSync */ any(), /* transition */ any(), /* onInflatedCallback */ assertThat(transitionHandler).isNotNull() val leash = SurfaceControl.Builder().setName("taskLeash").build() val transitionInfo = TransitionInfo(TRANSIT_CONVERT_TO_BUBBLE, 0) val change = TransitionInfo.Change(taskInfo.token, leash).apply { setTaskInfo(taskInfo) mode = TRANSIT_CHANGE } transitionInfo.addChange(change) transitionInfo.addRoot(TransitionInfo.Root(0, mock(), 0, 0)) getInstrumentation().runOnMainSync { transitionHandler!!.startAnimation(mock(), transitionInfo, mock(), mock(), mock()) } assertThat(bubbleData.getBubbleInStackWithKey("key_app_bubble:123")).isNotNull() } @EnableFlags(FLAG_ENABLE_BUBBLE_BAR) @Test fun convertExpandedBubbleToBar_startsConvertingToBar() { val bubble = createBubble("key") bubble.setShouldAutoExpand(true) getInstrumentation().runOnMainSync { bubbleController.inflateAndAdd( bubble, /* suppressFlyout= */ true, /* showInShade= */ true ) } assertThat(bubbleData.hasBubbles()).isTrue() assertThat(bubbleData.isExpanded).isTrue() assertThat(bubbleData.selectedBubble).isEqualTo(bubble) assertThat(bubble.taskView).isNotNull() bubblePositioner.update(deviceConfigUnfolded) bubblePositioner.isShowingInBubbleBar = true getInstrumentation().runOnMainSync { bubbleController.setLauncherHasBubbleBar(true) bubbleController.registerBubbleStateListener(FakeBubblesStateListener()) } assertThat(bubble.isConvertingToBar).isTrue() } @EnableFlags(FLAG_ENABLE_BUBBLE_BAR) @Test fun convertExpandedBubbleToBar_updatesTaskViewParent() { val bubble = createBubble("key") bubble.setShouldAutoExpand(true) getInstrumentation().runOnMainSync { bubbleController.inflateAndAdd( bubble, /* suppressFlyout= */ true, /* showInShade= */ true ) } assertThat(bubbleData.hasBubbles()).isTrue() assertThat(bubbleData.isExpanded).isTrue() assertThat(bubbleData.selectedBubble).isEqualTo(bubble) assertThat(bubble.taskView).isNotNull() assertThat(bubble.taskView.parent.parent).isEqualTo(bubble.expandedView) bubblePositioner.update(deviceConfigUnfolded) bubblePositioner.isShowingInBubbleBar = true getInstrumentation().runOnMainSync { bubbleController.setLauncherHasBubbleBar(true) bubbleController.registerBubbleStateListener(FakeBubblesStateListener()) } assertThat(bubble.taskView.parent).isEqualTo(bubble.bubbleBarExpandedView) } @EnableFlags(FLAG_ENABLE_BUBBLE_BAR) @Test fun convertExpandedBubbleToBar_screenOff_doesNotCollapse() { val bubble = createBubble("key") bubble.setShouldAutoExpand(true) getInstrumentation().runOnMainSync { bubbleController.inflateAndAdd( bubble, /* suppressFlyout= */ true, /* showInShade= */ true ) bubbleCaptor.lastValue } assertThat(bubble.taskId).isEqualTo(123) assertThat(bubble.key).isEqualTo("key_app_bubble:123") assertThat(bubbleData.hasBubbles()).isTrue() assertThat(bubbleData.isExpanded).isTrue() assertThat(bubbleData.selectedBubble).isEqualTo(bubble) assertThat(bubble.taskView).isNotNull() assertThat(bubble.taskView.parent.parent).isEqualTo(bubble.expandedView) bubblePositioner.update(deviceConfigUnfolded) bubblePositioner.isShowingInBubbleBar = true getInstrumentation().runOnMainSync { bubbleController.setLauncherHasBubbleBar(true) bubbleController.registerBubbleStateListener(FakeBubblesStateListener()) } bubbleController.broadcastReceiver.onReceive(context, Intent(Intent.ACTION_SCREEN_OFF)) assertThat(bubbleData.isExpanded).isTrue() } private fun createBubble(key: String, taskId: Int = 0): Bubble { Loading @@ -485,6 +630,10 @@ class BubbleControllerTest(flags: FlagsParameterization) { return bubble } private fun createAppBubble(taskInfo: TaskInfo): Bubble { return Bubble.createTaskBubble(taskInfo, UserHandle.of(0), null, mainExecutor, bgExecutor) } private fun createBubbleController( bubbleData: BubbleData, windowManager: WindowManager, Loading Loading @@ -514,16 +663,6 @@ class BubbleControllerTest(flags: FlagsParameterization) { BubblePersistentRepository(context), ) val shellTaskOrganizer = ShellTaskOrganizer( mock<ShellInit>(), ShellCommandHandler(), null, Optional.empty(), Optional.empty(), TestSyncExecutor(), ) val resizeChecker = ResizabilityChecker { _, _, _ -> true } val bubbleController = Loading Loading @@ -553,13 +692,13 @@ class BubbleControllerTest(flags: FlagsParameterization) { mainExecutor, mock<Handler>(), bgExecutor, mock<TaskViewTransitions>(), mock<Transitions>(), taskViewTransitions, transitions, SyncTransactionQueue(TransactionPool(), mainExecutor), mock<IWindowManager>(), resizeChecker, HomeIntentProvider(context), FakeBubbleAppInfoProvider(), bubbleAppInfoProvider, { Optional.empty() }, ) bubbleController.setInflateSynchronously(true) Loading libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubblesStateListener.kt 0 → 100644 +32 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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 com.android.wm.shell.bubbles import com.android.wm.shell.shared.bubbles.BubbleBarLocation import com.android.wm.shell.shared.bubbles.BubbleBarUpdate /** A fake implementation of [Bubbles.BubbleStateListener] */ class FakeBubblesStateListener : Bubbles.BubbleStateListener { override fun onBubbleStateChange(update: BubbleBarUpdate?) {} override fun animateBubbleBarLocation(location: BubbleBarLocation?) {} override fun onDragItemOverBubbleBarDragZone(location: BubbleBarLocation) {} override fun onItemDraggedOutsideBubbleBarDropZone() {} } libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt +2 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.wm.shell.bubbles.bar import android.animation.AnimatorTestRule import android.app.Activity import android.app.ActivityManager import android.content.ComponentName import android.content.Context import android.graphics.Insets import android.graphics.Outline Loading Loading @@ -342,6 +343,7 @@ class BubbleBarAnimationHelperTest { whenever(taskViewTaskController.taskInfo).thenReturn(taskInfo) val bubble = FakeBubbleFactory.createChatBubble(context, key) val bubbleTaskView = BubbleTaskView(taskView, mainExecutor) bubbleTaskView.listener.onTaskCreated(/* taskId= */ 1, ComponentName("package", "class")) FakeBubbleFactory.createExpandedView( context, Loading libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt +2 −11 Original line number Diff line number Diff line Loading @@ -271,20 +271,11 @@ class BubbleBarExpandedViewTest { regionSamplingProvider, ) // the task view should be removed from its parent assertThat(taskView.taskView.parent).isNull() // the task view should be added to the expanded view assertThat(taskView.taskView.parent).isEqualTo(expandedView) var animated = false expandedView.animateExpansionWhenTaskViewVisible { animated = true } assertThat(animated).isFalse() // send an invisible signal to simulate the surface getting destroyed expandedView.onContentVisibilityChanged(false) // send a visible signal to simulate a new surface getting created expandedView.onContentVisibilityChanged(true) assertThat(taskView.taskView.parent).isEqualTo(expandedView) assertThat(animated).isTrue() } Loading libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt +2 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.wm.shell.bubbles.bar import android.animation.AnimatorTestRule import android.content.ComponentName import android.content.Context import android.content.pm.LauncherApps import android.graphics.Insets Loading Loading @@ -500,6 +501,7 @@ class BubbleBarLayerViewTest { testBubblesList.add(it) } val bubbleTaskView = FakeBubbleTaskViewFactory(context, mainExecutor).create() bubbleTaskView.listener.onTaskCreated(/* taskId= */ 1, ComponentName("package", "class")) val bubbleBarExpandedView = FakeBubbleFactory.createExpandedView( context, Loading Loading
libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerTest.kt +186 −47 Original line number Diff line number Diff line Loading @@ -17,24 +17,31 @@ package com.android.wm.shell.bubbles import android.app.ActivityManager import android.app.TaskInfo import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.pm.LauncherApps import android.content.pm.ShortcutInfo import android.content.res.Resources import android.graphics.Insets import android.graphics.Rect import android.graphics.drawable.Icon import android.os.Handler import android.os.UserHandle import android.os.UserManager import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import android.platform.test.flag.junit.SetFlagsRule import android.view.IWindowManager import android.view.InsetsSource import android.view.InsetsState import android.view.SurfaceControl import android.view.WindowInsets import android.view.WindowManager import android.view.WindowManager.TRANSIT_CHANGE import android.window.TransitionInfo import android.window.WindowContainerToken import androidx.core.content.getSystemService import androidx.test.core.app.ApplicationProvider import androidx.test.filters.SmallTest Loading @@ -42,6 +49,7 @@ import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.protolog.ProtoLog import com.android.internal.statusbar.IStatusBarService import com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR import com.android.wm.shell.Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE import com.android.wm.shell.R import com.android.wm.shell.ShellTaskOrganizer Loading @@ -62,12 +70,16 @@ import com.android.wm.shell.common.TestSyncExecutor import com.android.wm.shell.draganddrop.DragAndDropController import com.android.wm.shell.shared.TransactionPool import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper import com.android.wm.shell.shared.bubbles.DeviceConfig import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.taskview.TaskViewRepository import com.android.wm.shell.taskview.TaskViewTaskController import com.android.wm.shell.taskview.TaskViewTransitions import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TRANSIT_CONVERT_TO_BUBBLE import com.android.wm.shell.transition.Transitions.TransitionHandler import com.google.common.truth.Truth.assertThat import com.google.common.util.concurrent.MoreExecutors.directExecutor import java.util.Optional Loading @@ -79,11 +91,9 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.isA import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify Loading @@ -103,13 +113,15 @@ class BubbleControllerTest(flags: FlagsParameterization) { @get:Rule val setFlagsRule = SetFlagsRule(flags) private val bubbleTransitions = mock<BubbleTransitions>() private val context = ApplicationProvider.getApplicationContext<Context>() private val uiEventLoggerFake = UiEventLoggerFake() private val displayImeController = mock<DisplayImeController>() private val displayInsetsController = mock<DisplayInsetsController>() private val userManager = mock<UserManager>() private val taskStackListener = mock<TaskStackListenerImpl>() private val transitions = mock<Transitions>() private val taskViewTransitions = mock<TaskViewTransitions>() private val bubbleAppInfoProvider = FakeBubbleAppInfoProvider() private lateinit var bubbleController: BubbleController private lateinit var bubblePositioner: BubblePositioner Loading @@ -120,6 +132,23 @@ class BubbleControllerTest(flags: FlagsParameterization) { private lateinit var eduController: BubbleEducationController private lateinit var displayController: DisplayController private lateinit var imeListener: ImeListener private lateinit var bubbleTransitions: BubbleTransitions private lateinit var shellTaskOrganizer: ShellTaskOrganizer private val deviceConfigFolded = DeviceConfig( windowBounds = Rect(0, 0, 700, 1000), isLargeScreen = false, isSmallTablet = false, isLandscape = false, isRtl = false, insets = Insets.of(10, 20, 30, 40), ) private val deviceConfigUnfolded = deviceConfigFolded.copy( windowBounds = Rect(0, 0, 1400, 2000), isLargeScreen = true, isSmallTablet = true, ) @Before fun setUp() { Loading @@ -144,7 +173,7 @@ class BubbleControllerTest(flags: FlagsParameterization) { on { getDisplayLayout(anyInt()) } doReturn DisplayLayout(context, realDefaultDisplay) } bubblePositioner = BubblePositioner(context, windowManager) bubblePositioner = BubblePositioner(context, deviceConfigFolded) bubbleData = BubbleData( Loading @@ -156,6 +185,27 @@ class BubbleControllerTest(flags: FlagsParameterization) { bgExecutor, ) shellTaskOrganizer = ShellTaskOrganizer( mock<ShellInit>(), ShellCommandHandler(), null, Optional.empty(), Optional.empty(), TestSyncExecutor(), ) bubbleTransitions = BubbleTransitions( context, transitions, shellTaskOrganizer, mock<TaskViewRepository>(), bubbleData, taskViewTransitions, bubbleAppInfoProvider ) bubbleController = createBubbleController( bubbleData, Loading Loading @@ -394,11 +444,24 @@ class BubbleControllerTest(flags: FlagsParameterization) { assertThat(bubbleController.hasStableBubbleForTask(777)).isFalse() } @EnableFlags(FLAG_ENABLE_BUBBLE_BAR) @Test fun expandStackAndSelectBubbleForExistingTransition_reusesExistingBubble() { assumeTrue(BubbleAnythingFlagHelper.enableCreateAnyBubble()) val bubble = createBubble("key", taskId = 123) // switch to bubble bar mode because the transition currently requires bubble layer view bubblePositioner.update(deviceConfigUnfolded) bubblePositioner.isShowingInBubbleBar = true getInstrumentation().runOnMainSync { bubbleController.setLauncherHasBubbleBar(true) bubbleController.registerBubbleStateListener(FakeBubblesStateListener()) } val taskInfo = ActivityManager.RunningTaskInfo().apply { taskId = 123 baseActivity = COMPONENT } val bubble = createAppBubble(taskInfo) getInstrumentation().runOnMainSync { bubbleData.notificationEntryUpdated( bubble, Loading @@ -406,7 +469,6 @@ class BubbleControllerTest(flags: FlagsParameterization) { true /* showInShade= */, ) } val taskInfo = ActivityManager.RunningTaskInfo().apply { taskId = 123 } getInstrumentation().runOnMainSync { bubbleController.expandStackAndSelectBubbleForExistingTransition( Loading @@ -415,55 +477,138 @@ class BubbleControllerTest(flags: FlagsParameterization) { ) {} } verify(bubbleTransitions).startLaunchNewTaskBubbleForExistingTransition( eq(bubble), /* bubble */ any(), /* expandedViewManager */ any(), /* factory */ any(), /* positioner */ any(), /* stackView */ anyOrNull(), /* layerView */ any(), /* iconFactory */ any(), /* inflateSync */ any(), /* transition */ any(), /* onInflatedCallback */ ) assertThat(bubbleData.selectedBubble).isEqualTo(bubble) assertThat(bubbleController.isStackExpanded).isTrue() } @EnableFlags(FLAG_ENABLE_BUBBLE_BAR) @Test fun expandStackAndSelectBubbleForExistingTransition_newBubble() { assumeTrue(BubbleAnythingFlagHelper.enableCreateAnyBubble()) // switch to bubble bar mode because the transition currently requires bubble layer view bubblePositioner.update(deviceConfigUnfolded) bubblePositioner.isShowingInBubbleBar = true getInstrumentation().runOnMainSync { bubbleController.setLauncherHasBubbleBar(true) bubbleController.registerBubbleStateListener(FakeBubblesStateListener()) } val taskInfo = ActivityManager.RunningTaskInfo().apply { baseActivity = COMPONENT taskId = 123 token = mock<WindowContainerToken>() } var transitionHandler: TransitionHandler? = null getInstrumentation().runOnMainSync { bubbleController.expandStackAndSelectBubbleForExistingTransition( transitionHandler = bubbleController.expandStackAndSelectBubbleForExistingTransition( taskInfo, mock(), /* transition */ ) {} } val bubble = argumentCaptor<Bubble>().let { bubbleCaptor -> verify(bubbleTransitions).startLaunchNewTaskBubbleForExistingTransition( bubbleCaptor.capture(), /* bubble */ any(), /* expandedViewManager */ any(), /* factory */ any(), /* positioner */ any(), /* stackView */ anyOrNull(), /* layerView */ any(), /* iconFactory */ any(), /* inflateSync */ any(), /* transition */ any(), /* onInflatedCallback */ assertThat(transitionHandler).isNotNull() val leash = SurfaceControl.Builder().setName("taskLeash").build() val transitionInfo = TransitionInfo(TRANSIT_CONVERT_TO_BUBBLE, 0) val change = TransitionInfo.Change(taskInfo.token, leash).apply { setTaskInfo(taskInfo) mode = TRANSIT_CHANGE } transitionInfo.addChange(change) transitionInfo.addRoot(TransitionInfo.Root(0, mock(), 0, 0)) getInstrumentation().runOnMainSync { transitionHandler!!.startAnimation(mock(), transitionInfo, mock(), mock(), mock()) } assertThat(bubbleData.getBubbleInStackWithKey("key_app_bubble:123")).isNotNull() } @EnableFlags(FLAG_ENABLE_BUBBLE_BAR) @Test fun convertExpandedBubbleToBar_startsConvertingToBar() { val bubble = createBubble("key") bubble.setShouldAutoExpand(true) getInstrumentation().runOnMainSync { bubbleController.inflateAndAdd( bubble, /* suppressFlyout= */ true, /* showInShade= */ true ) } assertThat(bubbleData.hasBubbles()).isTrue() assertThat(bubbleData.isExpanded).isTrue() assertThat(bubbleData.selectedBubble).isEqualTo(bubble) assertThat(bubble.taskView).isNotNull() bubblePositioner.update(deviceConfigUnfolded) bubblePositioner.isShowingInBubbleBar = true getInstrumentation().runOnMainSync { bubbleController.setLauncherHasBubbleBar(true) bubbleController.registerBubbleStateListener(FakeBubblesStateListener()) } assertThat(bubble.isConvertingToBar).isTrue() } @EnableFlags(FLAG_ENABLE_BUBBLE_BAR) @Test fun convertExpandedBubbleToBar_updatesTaskViewParent() { val bubble = createBubble("key") bubble.setShouldAutoExpand(true) getInstrumentation().runOnMainSync { bubbleController.inflateAndAdd( bubble, /* suppressFlyout= */ true, /* showInShade= */ true ) } assertThat(bubbleData.hasBubbles()).isTrue() assertThat(bubbleData.isExpanded).isTrue() assertThat(bubbleData.selectedBubble).isEqualTo(bubble) assertThat(bubble.taskView).isNotNull() assertThat(bubble.taskView.parent.parent).isEqualTo(bubble.expandedView) bubblePositioner.update(deviceConfigUnfolded) bubblePositioner.isShowingInBubbleBar = true getInstrumentation().runOnMainSync { bubbleController.setLauncherHasBubbleBar(true) bubbleController.registerBubbleStateListener(FakeBubblesStateListener()) } assertThat(bubble.taskView.parent).isEqualTo(bubble.bubbleBarExpandedView) } @EnableFlags(FLAG_ENABLE_BUBBLE_BAR) @Test fun convertExpandedBubbleToBar_screenOff_doesNotCollapse() { val bubble = createBubble("key") bubble.setShouldAutoExpand(true) getInstrumentation().runOnMainSync { bubbleController.inflateAndAdd( bubble, /* suppressFlyout= */ true, /* showInShade= */ true ) bubbleCaptor.lastValue } assertThat(bubble.taskId).isEqualTo(123) assertThat(bubble.key).isEqualTo("key_app_bubble:123") assertThat(bubbleData.hasBubbles()).isTrue() assertThat(bubbleData.isExpanded).isTrue() assertThat(bubbleData.selectedBubble).isEqualTo(bubble) assertThat(bubble.taskView).isNotNull() assertThat(bubble.taskView.parent.parent).isEqualTo(bubble.expandedView) bubblePositioner.update(deviceConfigUnfolded) bubblePositioner.isShowingInBubbleBar = true getInstrumentation().runOnMainSync { bubbleController.setLauncherHasBubbleBar(true) bubbleController.registerBubbleStateListener(FakeBubblesStateListener()) } bubbleController.broadcastReceiver.onReceive(context, Intent(Intent.ACTION_SCREEN_OFF)) assertThat(bubbleData.isExpanded).isTrue() } private fun createBubble(key: String, taskId: Int = 0): Bubble { Loading @@ -485,6 +630,10 @@ class BubbleControllerTest(flags: FlagsParameterization) { return bubble } private fun createAppBubble(taskInfo: TaskInfo): Bubble { return Bubble.createTaskBubble(taskInfo, UserHandle.of(0), null, mainExecutor, bgExecutor) } private fun createBubbleController( bubbleData: BubbleData, windowManager: WindowManager, Loading Loading @@ -514,16 +663,6 @@ class BubbleControllerTest(flags: FlagsParameterization) { BubblePersistentRepository(context), ) val shellTaskOrganizer = ShellTaskOrganizer( mock<ShellInit>(), ShellCommandHandler(), null, Optional.empty(), Optional.empty(), TestSyncExecutor(), ) val resizeChecker = ResizabilityChecker { _, _, _ -> true } val bubbleController = Loading Loading @@ -553,13 +692,13 @@ class BubbleControllerTest(flags: FlagsParameterization) { mainExecutor, mock<Handler>(), bgExecutor, mock<TaskViewTransitions>(), mock<Transitions>(), taskViewTransitions, transitions, SyncTransactionQueue(TransactionPool(), mainExecutor), mock<IWindowManager>(), resizeChecker, HomeIntentProvider(context), FakeBubbleAppInfoProvider(), bubbleAppInfoProvider, { Optional.empty() }, ) bubbleController.setInflateSynchronously(true) Loading
libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubblesStateListener.kt 0 → 100644 +32 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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 com.android.wm.shell.bubbles import com.android.wm.shell.shared.bubbles.BubbleBarLocation import com.android.wm.shell.shared.bubbles.BubbleBarUpdate /** A fake implementation of [Bubbles.BubbleStateListener] */ class FakeBubblesStateListener : Bubbles.BubbleStateListener { override fun onBubbleStateChange(update: BubbleBarUpdate?) {} override fun animateBubbleBarLocation(location: BubbleBarLocation?) {} override fun onDragItemOverBubbleBarDragZone(location: BubbleBarLocation) {} override fun onItemDraggedOutsideBubbleBarDropZone() {} }
libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt +2 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.wm.shell.bubbles.bar import android.animation.AnimatorTestRule import android.app.Activity import android.app.ActivityManager import android.content.ComponentName import android.content.Context import android.graphics.Insets import android.graphics.Outline Loading Loading @@ -342,6 +343,7 @@ class BubbleBarAnimationHelperTest { whenever(taskViewTaskController.taskInfo).thenReturn(taskInfo) val bubble = FakeBubbleFactory.createChatBubble(context, key) val bubbleTaskView = BubbleTaskView(taskView, mainExecutor) bubbleTaskView.listener.onTaskCreated(/* taskId= */ 1, ComponentName("package", "class")) FakeBubbleFactory.createExpandedView( context, Loading
libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt +2 −11 Original line number Diff line number Diff line Loading @@ -271,20 +271,11 @@ class BubbleBarExpandedViewTest { regionSamplingProvider, ) // the task view should be removed from its parent assertThat(taskView.taskView.parent).isNull() // the task view should be added to the expanded view assertThat(taskView.taskView.parent).isEqualTo(expandedView) var animated = false expandedView.animateExpansionWhenTaskViewVisible { animated = true } assertThat(animated).isFalse() // send an invisible signal to simulate the surface getting destroyed expandedView.onContentVisibilityChanged(false) // send a visible signal to simulate a new surface getting created expandedView.onContentVisibilityChanged(true) assertThat(taskView.taskView.parent).isEqualTo(expandedView) assertThat(animated).isTrue() } Loading
libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt +2 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.wm.shell.bubbles.bar import android.animation.AnimatorTestRule import android.content.ComponentName import android.content.Context import android.content.pm.LauncherApps import android.graphics.Insets Loading Loading @@ -500,6 +501,7 @@ class BubbleBarLayerViewTest { testBubblesList.add(it) } val bubbleTaskView = FakeBubbleTaskViewFactory(context, mainExecutor).create() bubbleTaskView.listener.onTaskCreated(/* taskId= */ 1, ComponentName("package", "class")) val bubbleBarExpandedView = FakeBubbleFactory.createExpandedView( context, Loading