From c8ee653eef8bd1ef7e865bfc84a69e1d4d80c46d Mon Sep 17 00:00:00 2001 From: Vania Desmonda Date: Wed, 19 Feb 2025 16:00:55 +0000 Subject: [PATCH] Launch app pair when one of them is in freeform. Call launchAppPair when one of the tasks is in freeform to prevent only launching one app to the side and choosing the DesktopWallpaperActivity as the other app pair. Fixes: 394917143 Flag: com.android.window.flags.enable_desktop_windowing_mode Test: atest NexusLauncherRoboTests Change-Id: I3967473fa648f2b245ac8b4f16e6112f6d8f077f --- .../quickstep/util/AppPairsController.java | 20 ++++-- .../quickstep/util/AppPairsControllerTest.kt | 68 +++++++++++++++++++ 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java index f20d7a551d..8385485253 100644 --- a/quickstep/src/com/android/quickstep/util/AppPairsController.java +++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java @@ -26,6 +26,7 @@ import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT; +import static com.android.systemui.shared.recents.utilities.Utilities.isFreeformTask; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_NONE; import static com.android.wm.shell.shared.split.SplitScreenConstants.getIndex; @@ -69,6 +70,7 @@ import com.android.quickstep.views.TaskContainer; import com.android.quickstep.views.TaskView; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.system.InteractionJankMonitorWrapper; +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition; import java.util.Arrays; @@ -337,10 +339,12 @@ public class AppPairsController { * c) App B is on-screen, but App A isn't. * d) Neither is on-screen. * - * If the user tapped an app pair while inside a single app, there are 3 cases: - * a) The on-screen app is App A of the app pair. - * b) The on-screen app is App B of the app pair. - * c) It is neither. + * If the user tapped an app pair while a fullscreen or freeform app is visible on screen, + * there are 4 cases: + * a) At least one of the apps in the app pair is in freeform windowing mode. + * b) The on-screen app is App A of the app pair. + * c) The on-screen app is App B of the app pair. + * d) It is neither. * * For each case, we call the appropriate animation and split launch type. */ @@ -422,6 +426,14 @@ public class AppPairsController { foundTasks -> { Task foundTask1 = foundTasks[0]; Task foundTask2 = foundTasks[1]; + + if (DesktopModeStatus.canEnterDesktopMode(context) && (isFreeformTask( + foundTask1) || isFreeformTask(foundTask2))) { + launchAppPair(launchingIconView, + CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR); + return; + } + boolean task1IsOnScreen; boolean task2IsOnScreen; if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) { diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt index ee70e0a9d3..76d36d3658 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt @@ -16,7 +16,9 @@ package com.android.quickstep.util +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.content.Context +import android.content.res.Resources import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.launcher3.apppairs.AppPairIcon import com.android.launcher3.logging.StatsLogManager @@ -28,6 +30,7 @@ import com.android.quickstep.TopTaskTracker import com.android.quickstep.TopTaskTracker.CachedTaskInfo import com.android.systemui.shared.recents.model.Task import com.android.systemui.shared.recents.model.Task.TaskKey +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_33_66 import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50 import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_66_33 @@ -54,6 +57,7 @@ import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class AppPairsControllerTest { @Mock lateinit var context: Context + @Mock lateinit var resources: Resources @Mock lateinit var splitSelectStateController: SplitSelectStateController @Mock lateinit var statsLogManager: StatsLogManager @@ -109,6 +113,8 @@ class AppPairsControllerTest { doNothing() .whenever(spyAppPairsController) .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) + whenever(mockAppPairIcon.context.resources).thenReturn(resources) + whenever(DesktopModeStatus.canEnterDesktopMode(mockAppPairIcon.context)).thenReturn(false) } @Test @@ -391,6 +397,68 @@ class AppPairsControllerTest { .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_TOP_OR_LEFT)) } + @Test + fun handleAppPairLaunchInApp_freeformTask1IsOnScreen_shouldLaunchAppPair() { + whenever(DesktopModeStatus.canEnterDesktopMode(mockAppPairIcon.context)).thenReturn(true) + /// Test launching apps 1 and 2 from app pair + whenever(mockTaskKey1.getId()).thenReturn(1) + whenever(mockTaskKey2.getId()).thenReturn(2) + // Task 1 is in freeform windowing mode + mockTaskKey1.windowingMode = WINDOWING_MODE_FREEFORM + // ... and app 1 is already on screen + if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) { + whenever(mockCachedTaskInfo.topGroupedTaskContainsTask(eq(1))).thenReturn(true) + } else { + whenever(mockCachedTaskInfo.taskId).thenReturn(1) + } + + // Trigger app pair launch, capture and run callback from findLastActiveTasksAndRunCallback + spyAppPairsController.handleAppPairLaunchInApp( + mockAppPairIcon, + listOf(mockItemInfo1, mockItemInfo2), + ) + verify(splitSelectStateController) + .findLastActiveTasksAndRunCallback(any(), any(), callbackCaptor.capture()) + val callback: Consumer> = callbackCaptor.value + callback.accept(arrayOf(mockTask1, mockTask2)) + + // Verify that launchAppPair was called + verify(spyAppPairsController, times(1)).launchAppPair(any(), any()) + verify(spyAppPairsController, never()) + .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) + } + + @Test + fun handleAppPairLaunchInApp_freeformTask2IsOnScreen_shouldLaunchAppPair() { + whenever(DesktopModeStatus.canEnterDesktopMode(mockAppPairIcon.context)).thenReturn(true) + /// Test launching apps 1 and 2 from app pair + whenever(mockTaskKey1.getId()).thenReturn(1) + whenever(mockTaskKey2.getId()).thenReturn(2) + // Task 2 is in freeform windowing mode + mockTaskKey1.windowingMode = WINDOWING_MODE_FREEFORM + // ... and app 2 is already on screen + if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) { + whenever(mockCachedTaskInfo.topGroupedTaskContainsTask(eq(2))).thenReturn(true) + } else { + whenever(mockCachedTaskInfo.taskId).thenReturn(2) + } + + // Trigger app pair launch, capture and run callback from findLastActiveTasksAndRunCallback + spyAppPairsController.handleAppPairLaunchInApp( + mockAppPairIcon, + listOf(mockItemInfo1, mockItemInfo2), + ) + verify(splitSelectStateController) + .findLastActiveTasksAndRunCallback(any(), any(), callbackCaptor.capture()) + val callback: Consumer> = callbackCaptor.value + callback.accept(arrayOf(mockTask1, mockTask2)) + + // Verify that launchAppPair was called + verify(spyAppPairsController, times(1)).launchAppPair(any(), any()) + verify(spyAppPairsController, never()) + .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) + } + @Test fun handleAppPairLaunchInApp_shouldLaunchAppPairNormallyWhenUnrelatedSingleAppIsFullscreen() { // Test launching apps 1 and 2 from app pair