Loading libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerTest.kt +46 −2 Original line number Diff line number Diff line Loading @@ -320,7 +320,51 @@ class BubbleControllerTest(flags: FlagsParameterization) { } } private fun createBubble(key: String): Bubble { @Test fun hasStableBubbleForTask_whenBubbleIsCollapsed_returnsTrue() { val taskId = 777 val bubble = createBubble("key", taskId) getInstrumentation().runOnMainSync { bubbleData.notificationEntryUpdated( bubble, true /* suppressFlyout */, true /* showInShade= */, ) } assertThat(bubbleController.hasStableBubbleForTask(taskId)).isTrue() } @Test fun hasStableBubbleForTask_whenBubbleInTransition_returnsFalse() { val taskId = 777 val bubble = createBubble("key", taskId).apply { preparingTransition = mock() } getInstrumentation().runOnMainSync { bubbleData.notificationEntryUpdated( bubble, true /* suppressFlyout */, true /* showInShade= */, ) } assertThat(bubbleController.hasStableBubbleForTask(taskId)).isFalse() } @Test fun hasStableBubbleForTask_noBubble_returnsFalse() { val bubble = createBubble("key", taskId = 123) getInstrumentation().runOnMainSync { bubbleData.notificationEntryUpdated( bubble, true /* suppressFlyout */, true /* showInShade= */, ) } assertThat(bubbleController.hasStableBubbleForTask(777)).isFalse() } private fun createBubble(key: String, taskId: Int = 0): Bubble { val icon = Icon.createWithResource(context.resources, R.drawable.bubble_ic_overflow_button) val shortcutInfo = ShortcutInfo.Builder(context, "fakeId").setIcon(icon).build() val bubble = Loading @@ -330,7 +374,7 @@ class BubbleControllerTest(flags: FlagsParameterization) { /* desiredHeight= */ 0, Resources.ID_NULL, "title", /* taskId= */ 0, taskId, "locus", /* isDismissable= */ true, directExecutor(), Loading libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +6 −0 Original line number Diff line number Diff line Loading @@ -1374,6 +1374,12 @@ public class BubbleController implements ConfigurationChangeListener, return mBubbleData.hasBubbles() || mBubbleData.isShowingOverflow(); } /** Returns whether the given task is a non-transient bubble. */ public boolean hasStableBubbleForTask(int taskId) { final Bubble bubble = mBubbleData.getBubbleInStackWithTaskId(taskId); return bubble != null && bubble.getPreparingTransition() == null; } public boolean isStackExpanded() { return mBubbleData.isExpanded(); } Loading libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +3 −2 Original line number Diff line number Diff line Loading @@ -1168,9 +1168,10 @@ public abstract class WMShellModule { static AppHandleAndHeaderVisibilityHelper provideAppHandleAndHeaderVisibilityHelper( @NonNull DisplayController displayController, @NonNull DesktopModeCompatPolicy desktopModeCompatPolicy, @NonNull DesktopState desktopState) { @NonNull DesktopState desktopState, Optional<BubbleController> bubbleController) { return new AppHandleAndHeaderVisibilityHelper(displayController, desktopModeCompatPolicy, desktopState); desktopModeCompatPolicy, desktopState, bubbleController); } @WMSingleton Loading libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/AppHandleAndHeaderVisibilityHelper.kt +14 −0 Original line number Diff line number Diff line Loading @@ -21,12 +21,14 @@ import android.app.WindowConfiguration import android.view.Display import android.view.WindowManager import android.window.DesktopExperienceFlags.ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY import com.android.wm.shell.bubbles.BubbleController import com.android.wm.shell.common.DisplayController import com.android.wm.shell.desktopmode.DesktopWallpaperActivity.Companion.isWallpaperTask import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy import com.android.wm.shell.shared.desktopmode.DesktopState import com.android.wm.shell.splitscreen.SplitScreenController import java.util.Optional /** * Resolves whether, given a task and its associated display that it is currently on, to show the Loading @@ -36,6 +38,7 @@ class AppHandleAndHeaderVisibilityHelper ( private val displayController: DisplayController, private val desktopModeCompatPolicy: DesktopModeCompatPolicy, private val desktopState: DesktopState, private val bubbleController: Optional<BubbleController>, ) { var splitScreenController: SplitScreenController? = null Loading Loading @@ -90,11 +93,22 @@ class AppHandleAndHeaderVisibilityHelper ( return false } } // Bubble tasks reset alwaysOnTop when reordering a task to the bottom to hide its task view // in TaskViewTransitions#setTaskViewVisible, so we need to explicitly check here. fun ActivityManager.RunningTaskInfo.isBubble(): Boolean = if (BubbleAnythingFlagHelper.enableCreateAnyBubbleWithForceExcludedFromRecents()) { bubbleController.map { it.hasStableBubbleForTask(taskId) }.orElse(false) } else { false } return desktopState.canEnterDesktopModeOrShowAppHandle && !isWallpaperTask(taskInfo) && taskInfo.windowingMode != WindowConfiguration.WINDOWING_MODE_PINNED && taskInfo.activityType == WindowConfiguration.ACTIVITY_TYPE_STANDARD && !taskInfo.configuration.windowConfiguration.isAlwaysOnTop && !taskInfo.isBubble() } private fun allowedForDisplay(display: Display): Boolean { Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +64 −0 Original line number Diff line number Diff line Loading @@ -66,13 +66,16 @@ import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction import com.android.wm.shell.recents.RecentsTransitionStateListener import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.util.StubTransaction import com.google.common.truth.Truth.assertThat import java.util.function.Consumer import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue import kotlinx.coroutines.ExperimentalCoroutinesApi import org.junit.Assume.assumeTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith Loading Loading @@ -1292,6 +1295,67 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest verify(decoration, times(1)).setIsRecentsTransitionRunning(true) } @Test fun testOnTaskOpening_expandedBubbleTask_skipsWindowDecorationCreation() { val taskInfo = createTask(windowingMode = WINDOWING_MODE_MULTI_WINDOW).apply { // Bubble task is launched with ActivityOptions#setTaskAlwaysOnTop // in BubbleTaskViewListener#onInitialized. configuration.windowConfiguration.setAlwaysOnTop(true) } mockBubbleController.stub { on { hasStableBubbleForTask(taskInfo.taskId) } doReturn true } val isWindowDecorCreated = desktopModeWindowDecorViewModel.onTaskOpening( taskInfo, SurfaceControl(), /* taskSurface */ StubTransaction(), /* startT */ StubTransaction(), /* finishT */ ) assertThat(isWindowDecorCreated).isFalse() } @Test fun testOnTaskChanging_collapsedBubbleTask_skipsWindowDecorationCreation() { assumeTrue(BubbleAnythingFlagHelper.enableCreateAnyBubbleWithForceExcludedFromRecents()) val taskInfo = createTask(windowingMode = WINDOWING_MODE_MULTI_WINDOW) mockBubbleController.stub { on { hasStableBubbleForTask(taskInfo.taskId) } doReturn true } desktopModeWindowDecorViewModel.onTaskChanging( taskInfo, SurfaceControl(), /* taskSurface */ StubTransaction(), /* startT */ StubTransaction(), /* finishT */ ) assertThat(windowDecorByTaskIdSpy.contains(taskInfo.taskId)).isFalse() } @Test fun testOnTaskChanging_convertTaskToBubble_destroysWindowDecoration() { assumeTrue(BubbleAnythingFlagHelper.enableCreateAnyBubbleWithForceExcludedFromRecents()) val taskInfo = createTask(windowingMode = WINDOWING_MODE_MULTI_WINDOW) mockBubbleController.stub { on { hasStableBubbleForTask(taskInfo.taskId) } doReturn true } val mockDecoration = mock<DesktopModeWindowDecoration>() windowDecorByTaskIdSpy.put(taskInfo.taskId, mockDecoration) desktopModeWindowDecorViewModel.onTaskChanging( taskInfo, SurfaceControl(), /* taskSurface */ StubTransaction(), /* startT */ StubTransaction(), /* finishT */ ) verify(mockDecoration).close() } private fun createOpenTaskDecoration( @WindowingMode windowingMode: Int, taskSurface: SurfaceControl = SurfaceControl(), Loading Loading
libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerTest.kt +46 −2 Original line number Diff line number Diff line Loading @@ -320,7 +320,51 @@ class BubbleControllerTest(flags: FlagsParameterization) { } } private fun createBubble(key: String): Bubble { @Test fun hasStableBubbleForTask_whenBubbleIsCollapsed_returnsTrue() { val taskId = 777 val bubble = createBubble("key", taskId) getInstrumentation().runOnMainSync { bubbleData.notificationEntryUpdated( bubble, true /* suppressFlyout */, true /* showInShade= */, ) } assertThat(bubbleController.hasStableBubbleForTask(taskId)).isTrue() } @Test fun hasStableBubbleForTask_whenBubbleInTransition_returnsFalse() { val taskId = 777 val bubble = createBubble("key", taskId).apply { preparingTransition = mock() } getInstrumentation().runOnMainSync { bubbleData.notificationEntryUpdated( bubble, true /* suppressFlyout */, true /* showInShade= */, ) } assertThat(bubbleController.hasStableBubbleForTask(taskId)).isFalse() } @Test fun hasStableBubbleForTask_noBubble_returnsFalse() { val bubble = createBubble("key", taskId = 123) getInstrumentation().runOnMainSync { bubbleData.notificationEntryUpdated( bubble, true /* suppressFlyout */, true /* showInShade= */, ) } assertThat(bubbleController.hasStableBubbleForTask(777)).isFalse() } private fun createBubble(key: String, taskId: Int = 0): Bubble { val icon = Icon.createWithResource(context.resources, R.drawable.bubble_ic_overflow_button) val shortcutInfo = ShortcutInfo.Builder(context, "fakeId").setIcon(icon).build() val bubble = Loading @@ -330,7 +374,7 @@ class BubbleControllerTest(flags: FlagsParameterization) { /* desiredHeight= */ 0, Resources.ID_NULL, "title", /* taskId= */ 0, taskId, "locus", /* isDismissable= */ true, directExecutor(), Loading
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +6 −0 Original line number Diff line number Diff line Loading @@ -1374,6 +1374,12 @@ public class BubbleController implements ConfigurationChangeListener, return mBubbleData.hasBubbles() || mBubbleData.isShowingOverflow(); } /** Returns whether the given task is a non-transient bubble. */ public boolean hasStableBubbleForTask(int taskId) { final Bubble bubble = mBubbleData.getBubbleInStackWithTaskId(taskId); return bubble != null && bubble.getPreparingTransition() == null; } public boolean isStackExpanded() { return mBubbleData.isExpanded(); } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +3 −2 Original line number Diff line number Diff line Loading @@ -1168,9 +1168,10 @@ public abstract class WMShellModule { static AppHandleAndHeaderVisibilityHelper provideAppHandleAndHeaderVisibilityHelper( @NonNull DisplayController displayController, @NonNull DesktopModeCompatPolicy desktopModeCompatPolicy, @NonNull DesktopState desktopState) { @NonNull DesktopState desktopState, Optional<BubbleController> bubbleController) { return new AppHandleAndHeaderVisibilityHelper(displayController, desktopModeCompatPolicy, desktopState); desktopModeCompatPolicy, desktopState, bubbleController); } @WMSingleton Loading
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/AppHandleAndHeaderVisibilityHelper.kt +14 −0 Original line number Diff line number Diff line Loading @@ -21,12 +21,14 @@ import android.app.WindowConfiguration import android.view.Display import android.view.WindowManager import android.window.DesktopExperienceFlags.ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY import com.android.wm.shell.bubbles.BubbleController import com.android.wm.shell.common.DisplayController import com.android.wm.shell.desktopmode.DesktopWallpaperActivity.Companion.isWallpaperTask import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy import com.android.wm.shell.shared.desktopmode.DesktopState import com.android.wm.shell.splitscreen.SplitScreenController import java.util.Optional /** * Resolves whether, given a task and its associated display that it is currently on, to show the Loading @@ -36,6 +38,7 @@ class AppHandleAndHeaderVisibilityHelper ( private val displayController: DisplayController, private val desktopModeCompatPolicy: DesktopModeCompatPolicy, private val desktopState: DesktopState, private val bubbleController: Optional<BubbleController>, ) { var splitScreenController: SplitScreenController? = null Loading Loading @@ -90,11 +93,22 @@ class AppHandleAndHeaderVisibilityHelper ( return false } } // Bubble tasks reset alwaysOnTop when reordering a task to the bottom to hide its task view // in TaskViewTransitions#setTaskViewVisible, so we need to explicitly check here. fun ActivityManager.RunningTaskInfo.isBubble(): Boolean = if (BubbleAnythingFlagHelper.enableCreateAnyBubbleWithForceExcludedFromRecents()) { bubbleController.map { it.hasStableBubbleForTask(taskId) }.orElse(false) } else { false } return desktopState.canEnterDesktopModeOrShowAppHandle && !isWallpaperTask(taskInfo) && taskInfo.windowingMode != WindowConfiguration.WINDOWING_MODE_PINNED && taskInfo.activityType == WindowConfiguration.ACTIVITY_TYPE_STANDARD && !taskInfo.configuration.windowConfiguration.isAlwaysOnTop && !taskInfo.isBubble() } private fun allowedForDisplay(display: Display): Boolean { Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +64 −0 Original line number Diff line number Diff line Loading @@ -66,13 +66,16 @@ import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction import com.android.wm.shell.recents.RecentsTransitionStateListener import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.util.StubTransaction import com.google.common.truth.Truth.assertThat import java.util.function.Consumer import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue import kotlinx.coroutines.ExperimentalCoroutinesApi import org.junit.Assume.assumeTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith Loading Loading @@ -1292,6 +1295,67 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest verify(decoration, times(1)).setIsRecentsTransitionRunning(true) } @Test fun testOnTaskOpening_expandedBubbleTask_skipsWindowDecorationCreation() { val taskInfo = createTask(windowingMode = WINDOWING_MODE_MULTI_WINDOW).apply { // Bubble task is launched with ActivityOptions#setTaskAlwaysOnTop // in BubbleTaskViewListener#onInitialized. configuration.windowConfiguration.setAlwaysOnTop(true) } mockBubbleController.stub { on { hasStableBubbleForTask(taskInfo.taskId) } doReturn true } val isWindowDecorCreated = desktopModeWindowDecorViewModel.onTaskOpening( taskInfo, SurfaceControl(), /* taskSurface */ StubTransaction(), /* startT */ StubTransaction(), /* finishT */ ) assertThat(isWindowDecorCreated).isFalse() } @Test fun testOnTaskChanging_collapsedBubbleTask_skipsWindowDecorationCreation() { assumeTrue(BubbleAnythingFlagHelper.enableCreateAnyBubbleWithForceExcludedFromRecents()) val taskInfo = createTask(windowingMode = WINDOWING_MODE_MULTI_WINDOW) mockBubbleController.stub { on { hasStableBubbleForTask(taskInfo.taskId) } doReturn true } desktopModeWindowDecorViewModel.onTaskChanging( taskInfo, SurfaceControl(), /* taskSurface */ StubTransaction(), /* startT */ StubTransaction(), /* finishT */ ) assertThat(windowDecorByTaskIdSpy.contains(taskInfo.taskId)).isFalse() } @Test fun testOnTaskChanging_convertTaskToBubble_destroysWindowDecoration() { assumeTrue(BubbleAnythingFlagHelper.enableCreateAnyBubbleWithForceExcludedFromRecents()) val taskInfo = createTask(windowingMode = WINDOWING_MODE_MULTI_WINDOW) mockBubbleController.stub { on { hasStableBubbleForTask(taskInfo.taskId) } doReturn true } val mockDecoration = mock<DesktopModeWindowDecoration>() windowDecorByTaskIdSpy.put(taskInfo.taskId, mockDecoration) desktopModeWindowDecorViewModel.onTaskChanging( taskInfo, SurfaceControl(), /* taskSurface */ StubTransaction(), /* startT */ StubTransaction(), /* finishT */ ) verify(mockDecoration).close() } private fun createOpenTaskDecoration( @WindowingMode windowingMode: Int, taskSurface: SurfaceControl = SurfaceControl(), Loading