Loading libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerTest.kt +86 −12 Original line number Diff line number Diff line Loading @@ -51,6 +51,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.window.flags.Flags.FLAG_ROOT_TASK_FOR_BUBBLE import com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR import com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR_TO_FLOATING_TRANSITION import com.android.wm.shell.Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE Loading @@ -76,6 +77,7 @@ 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.splitscreen.SplitScreenController import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit Loading @@ -88,6 +90,8 @@ import com.android.wm.shell.transition.Transitions.TransitionHandler import com.android.wm.shell.unfold.ShellUnfoldProgressProvider import com.google.common.truth.Truth.assertThat import com.google.common.util.concurrent.MoreExecutors.directExecutor import java.util.Optional import java.util.concurrent.Executor import org.junit.After import org.junit.Assume.assumeTrue import org.junit.Before Loading @@ -102,11 +106,10 @@ import org.mockito.kotlin.eq import org.mockito.kotlin.isA import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.stub import org.mockito.kotlin.verify import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters import java.util.Optional import java.util.concurrent.Executor /** Tests for [BubbleController]. * Loading @@ -123,14 +126,16 @@ class BubbleControllerTest(flags: FlagsParameterization) { private val context = ApplicationProvider.getApplicationContext<Context>() private val uiEventLoggerFake = UiEventLoggerFake() private val bubbleAppInfoProvider = FakeBubbleAppInfoProvider() private val unfoldProgressProvider = FakeShellUnfoldProgressProvider() private val displayImeController = mock<DisplayImeController>() private val displayInsetsController = mock<DisplayInsetsController>() private val userManager = mock<UserManager>() private val splitScreenController = mock<SplitScreenController>() private val taskStackListener = mock<TaskStackListenerImpl>() private val transitions = mock<Transitions>() private val taskViewTransitions = mock<TaskViewTransitions>() private val bubbleAppInfoProvider = FakeBubbleAppInfoProvider() private val unfoldProgressProvider = FakeShellUnfoldProgressProvider() private val userManager = mock<UserManager>() private val windowManager = mock<WindowManager>() private lateinit var bubbleController: BubbleController private lateinit var bubblePositioner: BubblePositioner Loading @@ -142,7 +147,6 @@ class BubbleControllerTest(flags: FlagsParameterization) { private lateinit var displayController: DisplayController private lateinit var imeListener: ImeListener private lateinit var bubbleTransitions: BubbleTransitions private lateinit var shellTaskOrganizer: ShellTaskOrganizer private var isStayAwakeOnFold = false Loading Loading @@ -175,7 +179,7 @@ class BubbleControllerTest(flags: FlagsParameterization) { val realWindowManager = context.getSystemService<WindowManager>()!! val realDefaultDisplay = realWindowManager.defaultDisplay // Tests don't have permission to add our window to windowManager, so we mock it :( val windowManager = mock<WindowManager> { windowManager.stub { // But we do want the metrics from the real one on { currentWindowMetrics } doReturn realWindowManager.currentWindowMetrics on { defaultDisplay } doReturn realDefaultDisplay Loading @@ -196,7 +200,7 @@ class BubbleControllerTest(flags: FlagsParameterization) { bgExecutor, ) shellTaskOrganizer = val shellTaskOrganizer = ShellTaskOrganizer( mock<ShellInit>(), ShellCommandHandler(), Loading @@ -222,14 +226,12 @@ class BubbleControllerTest(flags: FlagsParameterization) { createBubbleController( bubbleData, windowManager, shellTaskOrganizer, bubbleLogger, bubblePositioner, mainExecutor, bgExecutor, ) bubbleController.asBubbles().setSysuiProxy(mock<SysuiProxy>()) // Flush so that proxy gets set mainExecutor.flushAll() val insetsChangedListenerCaptor = argumentCaptor<ImeListener>() verify(displayInsetsController) Loading Loading @@ -458,6 +460,50 @@ class BubbleControllerTest(flags: FlagsParameterization) { assertThat(bubbleController.hasStableBubbleForTask(777)).isFalse() } @EnableFlags(FLAG_ROOT_TASK_FOR_BUBBLE) @Test fun shouldBeAppBubble_parentTaskMatchesBubbleRootTask_returnsTrue() { val bubbleController = createBubbleControllerWithRootTask(bubbleRootTaskId = 777) val taskInfo = ActivityManager.RunningTaskInfo().apply { parentTaskId = 777 } assertThat(bubbleController.shouldBeAppBubble(taskInfo)).isTrue() } @EnableFlags(FLAG_ROOT_TASK_FOR_BUBBLE) @Test fun shouldBeAppBubble_parentTaskDoesNotMatchesBubbleRootTask_returnsFalse() { val bubbleController = createBubbleControllerWithRootTask(bubbleRootTaskId = 123) val taskInfo = ActivityManager.RunningTaskInfo().apply { parentTaskId = 456 } assertThat(bubbleController.shouldBeAppBubble(taskInfo)).isFalse() } @DisableFlags(FLAG_ROOT_TASK_FOR_BUBBLE) @Test fun shouldBeAppBubble_taskIsSplitting_returnsFalse() { val sideStageRootTask = 5 splitScreenController.stub { on { isTaskRootOrStageRoot(sideStageRootTask) } doReturn true } val taskInfo = ActivityManager.RunningTaskInfo().apply { // Task is running in split-screen mode. parentTaskId = sideStageRootTask // Even though the task was previously marked as an app bubble, // it should not be considered a bubble when in split-screen mode. isAppBubble = true } assertThat(bubbleController.shouldBeAppBubble(taskInfo)).isFalse() } @DisableFlags(FLAG_ROOT_TASK_FOR_BUBBLE) @Test fun shouldBeAppBubble_isAppBubbleNotSplitting_returnsTrue() { val taskInfo = ActivityManager.RunningTaskInfo().apply { isAppBubble = true } assertThat(bubbleController.shouldBeAppBubble(taskInfo)).isTrue() } @EnableFlags(FLAG_ENABLE_BUBBLE_BAR) @Test fun expandStackAndSelectBubbleForExistingTransition_reusesExistingBubble() { Loading Loading @@ -839,6 +885,7 @@ class BubbleControllerTest(flags: FlagsParameterization) { private fun createBubbleController( bubbleData: BubbleData, windowManager: WindowManager, shellTaskOrganizer: ShellTaskOrganizer, bubbleLogger: BubbleLogger, bubblePositioner: BubblePositioner, mainExecutor: TestShellExecutor, Loading Loading @@ -901,13 +948,40 @@ class BubbleControllerTest(flags: FlagsParameterization) { resizeChecker, HomeIntentProvider(context), bubbleAppInfoProvider, { Optional.empty() }, { Optional.of(splitScreenController) }, Optional.of(unfoldProgressProvider), { isStayAwakeOnFold }, ) bubbleController.setInflateSynchronously(true) bubbleController.onInit() bubbleController.asBubbles().setSysuiProxy(mock<SysuiProxy>()) // Flush so that proxy gets set mainExecutor.flushAll() return bubbleController } private fun createBubbleControllerWithRootTask(bubbleRootTaskId: Int): BubbleController { val shellTaskOrganizer = mock<ShellTaskOrganizer>() val bubbleController = createBubbleController( bubbleData, windowManager, shellTaskOrganizer, bubbleLogger, bubblePositioner, mainExecutor, bgExecutor, ) val rootTaskListener = argumentCaptor<ShellTaskOrganizer.TaskListener>().let { captor -> verify(shellTaskOrganizer).createRootTask(any(), captor.capture()) captor.lastValue } val bubbleRootTask = ActivityManager.RunningTaskInfo().apply { taskId = bubbleRootTaskId } rootTaskListener.onTaskAppeared(bubbleRootTask, null /* leash */) return bubbleController } Loading libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt +30 −12 Original line number Diff line number Diff line Loading @@ -26,9 +26,11 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.filters.SmallTest import com.android.wm.shell.Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.taskview.TaskView import com.google.common.truth.Truth.assertThat import com.google.common.util.concurrent.MoreExecutors.directExecutor import java.util.Optional import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith Loading @@ -49,8 +51,17 @@ class BubbleTaskViewTest(flags: FlagsParameterization) { private val context = ApplicationProvider.getApplicationContext<Context>() private val componentName = ComponentName(context, "TestClass") private val taskView = mock<TaskView>() private val bubbleTaskView = BubbleTaskView(taskView, directExecutor()) private val runningTaskInfo = ActivityManager.RunningTaskInfo() private val splitScreenController = mock<SplitScreenController>() private val taskView = mock<TaskView> { on { taskInfo } doReturn runningTaskInfo } private val bubbleTaskView = BubbleTaskView( taskView, executor = directExecutor(), splitScreenController = { Optional.of(splitScreenController) }, ) @Test fun onTaskCreated_updatesState() { Loading Loading @@ -80,38 +91,45 @@ class BubbleTaskViewTest(flags: FlagsParameterization) { } @Test fun cleanup_invalidTaskId_removesTask() { fun cleanup_noTaskCreated_removesTask() { bubbleTaskView.cleanup() verify(taskView, never()).unregisterTask() verify(taskView).removeTask() } @Test fun cleanup_validTaskId_removesTask() { fun cleanup_regularBubbleTask_removesTask() { bubbleTaskView.listener.onTaskCreated(123 /* taskId */, componentName) bubbleTaskView.cleanup() verify(taskView, never()).unregisterTask() verify(taskView).removeTask() } @Test fun cleanup_noneFullscreenTask_removesTask() { fun cleanup_taskTransitioningToSplitScreen_unregistersTask() { val sideStageRootTask = 5 splitScreenController.stub { on { isTaskRootOrStageRoot(sideStageRootTask) } doReturn true } runningTaskInfo.apply { parentTaskId = sideStageRootTask // Task is running in split-screen mode. } bubbleTaskView.listener.onTaskCreated(123 /* taskId */, componentName) bubbleTaskView.cleanup() verify(taskView, never()).unregisterTask() verify(taskView).removeTask() verify(taskView).unregisterTask() verify(taskView, never()).removeTask() } @Test fun cleanup_fullscreenTask_removesOrUnregistersTask() { val fullScreenTaskInfo = ActivityManager.RunningTaskInfo().apply { fun cleanup_taskTransitioningToFullscreen_removesOrUnregistersTask() { runningTaskInfo.apply { configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN } taskView.stub { on { taskInfo } doReturn fullScreenTaskInfo } bubbleTaskView.listener.onTaskCreated(123 /* taskId */, componentName) bubbleTaskView.cleanup() Loading libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +15 −1 Original line number Diff line number Diff line Loading @@ -36,6 +36,7 @@ import static com.android.wm.shell.bubbles.Bubbles.DISMISS_NO_LONGER_BUBBLE; import static com.android.wm.shell.bubbles.Bubbles.DISMISS_PACKAGE_REMOVED; import static com.android.wm.shell.bubbles.Bubbles.DISMISS_SHORTCUT_REMOVED; import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_CHANGED; import static com.android.wm.shell.bubbles.util.BubbleUtils.isBubbleToSplit; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES_NOISY; import static com.android.wm.shell.transition.Transitions.TRANSIT_BUBBLE_CONVERT_FLOATING_TO_BAR; Loading Loading @@ -422,7 +423,7 @@ public class BubbleController implements ConfigurationChangeListener, context, organizer, mTaskViewController, syncQueue); TaskView taskView = new TaskView(context, mTaskViewController, taskViewTaskController); return new BubbleTaskView(taskView, mainExecutor); return new BubbleTaskView(taskView, mainExecutor, splitScreenController); } }; mExpandedViewManager = BubbleExpandedViewManager.fromBubbleController(this); Loading Loading @@ -1470,6 +1471,19 @@ public class BubbleController implements ConfigurationChangeListener, return mAppBubbleRootTaskInfo != null && taskInfo.parentTaskId == mAppBubbleRootTaskInfo.taskId; } // Skip treating the task as an app bubble if it's transitioning from bubble to split. // In BubblesTransitionObserver#removeBubbleIfLaunchingToSplit, a WCT is applied to set // LaunchNextToBubble=false. Then TaskViewTaskController#notifyTaskRemovalStarted is called, // which triggers this check. However, the isAppBubble flag is only updated during the next // Task#fillTaskInfo by the WM core, so the flag we are currently processing is still true. // Later, TaskViewTransitions#onExternalDone unblocks the animation. Without this check, // DefaultMixedHandler could misinterpret the OPEN change as a bubble-enter transition, // incorrectly re-creating the bubble instead of completing the split-screen transition. if (isBubbleToSplit(taskInfo, mSplitScreenController)) { return false; } return taskInfo.isAppBubble; } Loading libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt +11 −2 Original line number Diff line number Diff line Loading @@ -21,7 +21,11 @@ import android.app.ActivityTaskManager.INVALID_TASK_ID import android.content.ComponentName import androidx.annotation.VisibleForTesting import com.android.wm.shell.bubbles.util.BubbleUtils.isBubbleToFullscreen import com.android.wm.shell.bubbles.util.BubbleUtils.isBubbleToSplit import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.taskview.TaskView import dagger.Lazy import java.util.Optional import java.util.concurrent.Executor /** Loading @@ -29,7 +33,12 @@ import java.util.concurrent.Executor * * [delegateListener] allows callers to change listeners after a task has been created. */ class BubbleTaskView(val taskView: TaskView, executor: Executor) { class BubbleTaskView @JvmOverloads constructor( val taskView: TaskView, executor: Executor, private val splitScreenController: Lazy<Optional<SplitScreenController>> = Lazy { Optional.empty() }, ) { /** Whether the task is already created. */ var isCreated = false Loading Loading @@ -109,7 +118,7 @@ class BubbleTaskView(val taskView: TaskView, executor: Executor) { */ fun cleanup() { val task = taskView.taskInfo if (task.isBubbleToFullscreen()) { if (task.isBubbleToFullscreen() || task.isBubbleToSplit(splitScreenController)) { taskView.unregisterTask() } else { taskView.removeTask() Loading Loading
libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerTest.kt +86 −12 Original line number Diff line number Diff line Loading @@ -51,6 +51,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.window.flags.Flags.FLAG_ROOT_TASK_FOR_BUBBLE import com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR import com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR_TO_FLOATING_TRANSITION import com.android.wm.shell.Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE Loading @@ -76,6 +77,7 @@ 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.splitscreen.SplitScreenController import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit Loading @@ -88,6 +90,8 @@ import com.android.wm.shell.transition.Transitions.TransitionHandler import com.android.wm.shell.unfold.ShellUnfoldProgressProvider import com.google.common.truth.Truth.assertThat import com.google.common.util.concurrent.MoreExecutors.directExecutor import java.util.Optional import java.util.concurrent.Executor import org.junit.After import org.junit.Assume.assumeTrue import org.junit.Before Loading @@ -102,11 +106,10 @@ import org.mockito.kotlin.eq import org.mockito.kotlin.isA import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.stub import org.mockito.kotlin.verify import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters import java.util.Optional import java.util.concurrent.Executor /** Tests for [BubbleController]. * Loading @@ -123,14 +126,16 @@ class BubbleControllerTest(flags: FlagsParameterization) { private val context = ApplicationProvider.getApplicationContext<Context>() private val uiEventLoggerFake = UiEventLoggerFake() private val bubbleAppInfoProvider = FakeBubbleAppInfoProvider() private val unfoldProgressProvider = FakeShellUnfoldProgressProvider() private val displayImeController = mock<DisplayImeController>() private val displayInsetsController = mock<DisplayInsetsController>() private val userManager = mock<UserManager>() private val splitScreenController = mock<SplitScreenController>() private val taskStackListener = mock<TaskStackListenerImpl>() private val transitions = mock<Transitions>() private val taskViewTransitions = mock<TaskViewTransitions>() private val bubbleAppInfoProvider = FakeBubbleAppInfoProvider() private val unfoldProgressProvider = FakeShellUnfoldProgressProvider() private val userManager = mock<UserManager>() private val windowManager = mock<WindowManager>() private lateinit var bubbleController: BubbleController private lateinit var bubblePositioner: BubblePositioner Loading @@ -142,7 +147,6 @@ class BubbleControllerTest(flags: FlagsParameterization) { private lateinit var displayController: DisplayController private lateinit var imeListener: ImeListener private lateinit var bubbleTransitions: BubbleTransitions private lateinit var shellTaskOrganizer: ShellTaskOrganizer private var isStayAwakeOnFold = false Loading Loading @@ -175,7 +179,7 @@ class BubbleControllerTest(flags: FlagsParameterization) { val realWindowManager = context.getSystemService<WindowManager>()!! val realDefaultDisplay = realWindowManager.defaultDisplay // Tests don't have permission to add our window to windowManager, so we mock it :( val windowManager = mock<WindowManager> { windowManager.stub { // But we do want the metrics from the real one on { currentWindowMetrics } doReturn realWindowManager.currentWindowMetrics on { defaultDisplay } doReturn realDefaultDisplay Loading @@ -196,7 +200,7 @@ class BubbleControllerTest(flags: FlagsParameterization) { bgExecutor, ) shellTaskOrganizer = val shellTaskOrganizer = ShellTaskOrganizer( mock<ShellInit>(), ShellCommandHandler(), Loading @@ -222,14 +226,12 @@ class BubbleControllerTest(flags: FlagsParameterization) { createBubbleController( bubbleData, windowManager, shellTaskOrganizer, bubbleLogger, bubblePositioner, mainExecutor, bgExecutor, ) bubbleController.asBubbles().setSysuiProxy(mock<SysuiProxy>()) // Flush so that proxy gets set mainExecutor.flushAll() val insetsChangedListenerCaptor = argumentCaptor<ImeListener>() verify(displayInsetsController) Loading Loading @@ -458,6 +460,50 @@ class BubbleControllerTest(flags: FlagsParameterization) { assertThat(bubbleController.hasStableBubbleForTask(777)).isFalse() } @EnableFlags(FLAG_ROOT_TASK_FOR_BUBBLE) @Test fun shouldBeAppBubble_parentTaskMatchesBubbleRootTask_returnsTrue() { val bubbleController = createBubbleControllerWithRootTask(bubbleRootTaskId = 777) val taskInfo = ActivityManager.RunningTaskInfo().apply { parentTaskId = 777 } assertThat(bubbleController.shouldBeAppBubble(taskInfo)).isTrue() } @EnableFlags(FLAG_ROOT_TASK_FOR_BUBBLE) @Test fun shouldBeAppBubble_parentTaskDoesNotMatchesBubbleRootTask_returnsFalse() { val bubbleController = createBubbleControllerWithRootTask(bubbleRootTaskId = 123) val taskInfo = ActivityManager.RunningTaskInfo().apply { parentTaskId = 456 } assertThat(bubbleController.shouldBeAppBubble(taskInfo)).isFalse() } @DisableFlags(FLAG_ROOT_TASK_FOR_BUBBLE) @Test fun shouldBeAppBubble_taskIsSplitting_returnsFalse() { val sideStageRootTask = 5 splitScreenController.stub { on { isTaskRootOrStageRoot(sideStageRootTask) } doReturn true } val taskInfo = ActivityManager.RunningTaskInfo().apply { // Task is running in split-screen mode. parentTaskId = sideStageRootTask // Even though the task was previously marked as an app bubble, // it should not be considered a bubble when in split-screen mode. isAppBubble = true } assertThat(bubbleController.shouldBeAppBubble(taskInfo)).isFalse() } @DisableFlags(FLAG_ROOT_TASK_FOR_BUBBLE) @Test fun shouldBeAppBubble_isAppBubbleNotSplitting_returnsTrue() { val taskInfo = ActivityManager.RunningTaskInfo().apply { isAppBubble = true } assertThat(bubbleController.shouldBeAppBubble(taskInfo)).isTrue() } @EnableFlags(FLAG_ENABLE_BUBBLE_BAR) @Test fun expandStackAndSelectBubbleForExistingTransition_reusesExistingBubble() { Loading Loading @@ -839,6 +885,7 @@ class BubbleControllerTest(flags: FlagsParameterization) { private fun createBubbleController( bubbleData: BubbleData, windowManager: WindowManager, shellTaskOrganizer: ShellTaskOrganizer, bubbleLogger: BubbleLogger, bubblePositioner: BubblePositioner, mainExecutor: TestShellExecutor, Loading Loading @@ -901,13 +948,40 @@ class BubbleControllerTest(flags: FlagsParameterization) { resizeChecker, HomeIntentProvider(context), bubbleAppInfoProvider, { Optional.empty() }, { Optional.of(splitScreenController) }, Optional.of(unfoldProgressProvider), { isStayAwakeOnFold }, ) bubbleController.setInflateSynchronously(true) bubbleController.onInit() bubbleController.asBubbles().setSysuiProxy(mock<SysuiProxy>()) // Flush so that proxy gets set mainExecutor.flushAll() return bubbleController } private fun createBubbleControllerWithRootTask(bubbleRootTaskId: Int): BubbleController { val shellTaskOrganizer = mock<ShellTaskOrganizer>() val bubbleController = createBubbleController( bubbleData, windowManager, shellTaskOrganizer, bubbleLogger, bubblePositioner, mainExecutor, bgExecutor, ) val rootTaskListener = argumentCaptor<ShellTaskOrganizer.TaskListener>().let { captor -> verify(shellTaskOrganizer).createRootTask(any(), captor.capture()) captor.lastValue } val bubbleRootTask = ActivityManager.RunningTaskInfo().apply { taskId = bubbleRootTaskId } rootTaskListener.onTaskAppeared(bubbleRootTask, null /* leash */) return bubbleController } Loading
libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt +30 −12 Original line number Diff line number Diff line Loading @@ -26,9 +26,11 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.filters.SmallTest import com.android.wm.shell.Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.taskview.TaskView import com.google.common.truth.Truth.assertThat import com.google.common.util.concurrent.MoreExecutors.directExecutor import java.util.Optional import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith Loading @@ -49,8 +51,17 @@ class BubbleTaskViewTest(flags: FlagsParameterization) { private val context = ApplicationProvider.getApplicationContext<Context>() private val componentName = ComponentName(context, "TestClass") private val taskView = mock<TaskView>() private val bubbleTaskView = BubbleTaskView(taskView, directExecutor()) private val runningTaskInfo = ActivityManager.RunningTaskInfo() private val splitScreenController = mock<SplitScreenController>() private val taskView = mock<TaskView> { on { taskInfo } doReturn runningTaskInfo } private val bubbleTaskView = BubbleTaskView( taskView, executor = directExecutor(), splitScreenController = { Optional.of(splitScreenController) }, ) @Test fun onTaskCreated_updatesState() { Loading Loading @@ -80,38 +91,45 @@ class BubbleTaskViewTest(flags: FlagsParameterization) { } @Test fun cleanup_invalidTaskId_removesTask() { fun cleanup_noTaskCreated_removesTask() { bubbleTaskView.cleanup() verify(taskView, never()).unregisterTask() verify(taskView).removeTask() } @Test fun cleanup_validTaskId_removesTask() { fun cleanup_regularBubbleTask_removesTask() { bubbleTaskView.listener.onTaskCreated(123 /* taskId */, componentName) bubbleTaskView.cleanup() verify(taskView, never()).unregisterTask() verify(taskView).removeTask() } @Test fun cleanup_noneFullscreenTask_removesTask() { fun cleanup_taskTransitioningToSplitScreen_unregistersTask() { val sideStageRootTask = 5 splitScreenController.stub { on { isTaskRootOrStageRoot(sideStageRootTask) } doReturn true } runningTaskInfo.apply { parentTaskId = sideStageRootTask // Task is running in split-screen mode. } bubbleTaskView.listener.onTaskCreated(123 /* taskId */, componentName) bubbleTaskView.cleanup() verify(taskView, never()).unregisterTask() verify(taskView).removeTask() verify(taskView).unregisterTask() verify(taskView, never()).removeTask() } @Test fun cleanup_fullscreenTask_removesOrUnregistersTask() { val fullScreenTaskInfo = ActivityManager.RunningTaskInfo().apply { fun cleanup_taskTransitioningToFullscreen_removesOrUnregistersTask() { runningTaskInfo.apply { configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN } taskView.stub { on { taskInfo } doReturn fullScreenTaskInfo } bubbleTaskView.listener.onTaskCreated(123 /* taskId */, componentName) bubbleTaskView.cleanup() Loading
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +15 −1 Original line number Diff line number Diff line Loading @@ -36,6 +36,7 @@ import static com.android.wm.shell.bubbles.Bubbles.DISMISS_NO_LONGER_BUBBLE; import static com.android.wm.shell.bubbles.Bubbles.DISMISS_PACKAGE_REMOVED; import static com.android.wm.shell.bubbles.Bubbles.DISMISS_SHORTCUT_REMOVED; import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_CHANGED; import static com.android.wm.shell.bubbles.util.BubbleUtils.isBubbleToSplit; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES_NOISY; import static com.android.wm.shell.transition.Transitions.TRANSIT_BUBBLE_CONVERT_FLOATING_TO_BAR; Loading Loading @@ -422,7 +423,7 @@ public class BubbleController implements ConfigurationChangeListener, context, organizer, mTaskViewController, syncQueue); TaskView taskView = new TaskView(context, mTaskViewController, taskViewTaskController); return new BubbleTaskView(taskView, mainExecutor); return new BubbleTaskView(taskView, mainExecutor, splitScreenController); } }; mExpandedViewManager = BubbleExpandedViewManager.fromBubbleController(this); Loading Loading @@ -1470,6 +1471,19 @@ public class BubbleController implements ConfigurationChangeListener, return mAppBubbleRootTaskInfo != null && taskInfo.parentTaskId == mAppBubbleRootTaskInfo.taskId; } // Skip treating the task as an app bubble if it's transitioning from bubble to split. // In BubblesTransitionObserver#removeBubbleIfLaunchingToSplit, a WCT is applied to set // LaunchNextToBubble=false. Then TaskViewTaskController#notifyTaskRemovalStarted is called, // which triggers this check. However, the isAppBubble flag is only updated during the next // Task#fillTaskInfo by the WM core, so the flag we are currently processing is still true. // Later, TaskViewTransitions#onExternalDone unblocks the animation. Without this check, // DefaultMixedHandler could misinterpret the OPEN change as a bubble-enter transition, // incorrectly re-creating the bubble instead of completing the split-screen transition. if (isBubbleToSplit(taskInfo, mSplitScreenController)) { return false; } return taskInfo.isAppBubble; } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt +11 −2 Original line number Diff line number Diff line Loading @@ -21,7 +21,11 @@ import android.app.ActivityTaskManager.INVALID_TASK_ID import android.content.ComponentName import androidx.annotation.VisibleForTesting import com.android.wm.shell.bubbles.util.BubbleUtils.isBubbleToFullscreen import com.android.wm.shell.bubbles.util.BubbleUtils.isBubbleToSplit import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.taskview.TaskView import dagger.Lazy import java.util.Optional import java.util.concurrent.Executor /** Loading @@ -29,7 +33,12 @@ import java.util.concurrent.Executor * * [delegateListener] allows callers to change listeners after a task has been created. */ class BubbleTaskView(val taskView: TaskView, executor: Executor) { class BubbleTaskView @JvmOverloads constructor( val taskView: TaskView, executor: Executor, private val splitScreenController: Lazy<Optional<SplitScreenController>> = Lazy { Optional.empty() }, ) { /** Whether the task is already created. */ var isCreated = false Loading Loading @@ -109,7 +118,7 @@ class BubbleTaskView(val taskView: TaskView, executor: Executor) { */ fun cleanup() { val task = taskView.taskInfo if (task.isBubbleToFullscreen()) { if (task.isBubbleToFullscreen() || task.isBubbleToSplit(splitScreenController)) { taskView.unregisterTask() } else { taskView.removeTask() Loading