Merge "Add moving a task from recents to desktop" into main
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index fb0a1ab..12bbd51 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -522,14 +522,16 @@
             RecentsTransitionHandler recentsTransitionHandler,
             MultiInstanceHelper multiInstanceHelper,
             @ShellMainThread ShellExecutor mainExecutor,
-            Optional<DesktopTasksLimiter> desktopTasksLimiter) {
+            Optional<DesktopTasksLimiter> desktopTasksLimiter,
+            Optional<RecentTasksController> recentTasksController) {
         return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController,
                 displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
                 dragAndDropController, transitions, enterDesktopTransitionHandler,
                 exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler,
                 dragToDesktopTransitionHandler, desktopModeTaskRepository,
                 desktopModeLoggerTransitionObserver, launchAdjacentController,
-                recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter);
+                recentsTransitionHandler, multiInstanceHelper,
+                mainExecutor, desktopTasksLimiter, recentTasksController.orElse(null));
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 6e45397..ef384c7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -70,6 +70,7 @@
 import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
 import com.android.wm.shell.draganddrop.DragAndDropController
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.recents.RecentTasksController
 import com.android.wm.shell.recents.RecentsTransitionHandler
 import com.android.wm.shell.recents.RecentsTransitionStateListener
 import com.android.wm.shell.shared.DesktopModeStatus
@@ -118,6 +119,7 @@
     private val multiInstanceHelper: MultiInstanceHelper,
     @ShellMainThread private val mainExecutor: ShellExecutor,
     private val desktopTasksLimiter: Optional<DesktopTasksLimiter>,
+    private val recentTasksController: RecentTasksController?
 ) :
     RemoteCallable<DesktopTasksController>,
     Transitions.TransitionHandler,
@@ -293,24 +295,49 @@
         taskId: Int,
         wct: WindowContainerTransaction = WindowContainerTransaction()
     ): Boolean {
-        shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToDesktop(task, wct) }
-            ?: return false
+        shellTaskOrganizer.getRunningTaskInfo(taskId)?.let {
+            moveToDesktop(it, wct)
+        } ?: moveToDesktopFromNonRunningTask(taskId, wct)
         return true
     }
 
-    /** Move a task to desktop */
+    private fun moveToDesktopFromNonRunningTask(
+        taskId: Int,
+        wct: WindowContainerTransaction
+    ): Boolean {
+        recentTasksController?.findTaskInBackground(taskId)?.let {
+            KtProtoLog.v(
+                WM_SHELL_DESKTOP_MODE,
+                "DesktopTasksController: moveToDesktopFromNonRunningTask taskId=%d",
+                taskId
+            )
+            // TODO(342378842): Instead of using default display, support multiple displays
+            val taskToMinimize =
+                bringDesktopAppsToFrontBeforeShowingNewTask(DEFAULT_DISPLAY, wct, taskId)
+            addMoveToDesktopChangesNonRunningTask(wct, taskId)
+            // TODO(343149901): Add DPI changes for task launch
+            val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct)
+            addPendingMinimizeTransition(transition, taskToMinimize)
+            return true
+        } ?: return false
+    }
+
+    private fun addMoveToDesktopChangesNonRunningTask(
+        wct: WindowContainerTransaction,
+        taskId: Int
+    ) {
+        val options = ActivityOptions.makeBasic()
+        options.launchWindowingMode = WINDOWING_MODE_FREEFORM
+        wct.startTask(taskId, options.toBundle())
+    }
+
+    /**
+     * Move a task to desktop
+     */
     fun moveToDesktop(
         task: RunningTaskInfo,
         wct: WindowContainerTransaction = WindowContainerTransaction()
     ) {
-        if (!DesktopModeStatus.canEnterDesktopMode(context)) {
-            KtProtoLog.w(
-                WM_SHELL_DESKTOP_MODE,
-                "DesktopTasksController: Cannot enter desktop, " +
-                    "display does not meet minimum size requirements"
-            )
-            return
-        }
         if (Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task)) {
             KtProtoLog.w(
                 WM_SHELL_DESKTOP_MODE,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index d8f2c02..863202d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -446,6 +446,25 @@
         return null;
     }
 
+    /**
+     * Find the background task that match the given taskId.
+     */
+    @Nullable
+    public ActivityManager.RecentTaskInfo findTaskInBackground(int taskId) {
+        List<ActivityManager.RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks(
+                Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE,
+                ActivityManager.getCurrentUser());
+        for (int i = 0; i < tasks.size(); i++) {
+            final ActivityManager.RecentTaskInfo task = tasks.get(i);
+            if (task.isVisible) {
+                continue;
+            }
+            if (taskId == task.taskId) {
+                return task;
+            }
+        }
+        return null;
+    }
     public void dump(@NonNull PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + TAG);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index ac67bd1..cf6cea2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.desktopmode
 
+import android.app.ActivityManager.RecentTaskInfo
 import android.app.ActivityManager.RunningTaskInfo
 import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
 import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
@@ -47,13 +48,16 @@
 import android.view.WindowManager.TRANSIT_TO_BACK
 import android.view.WindowManager.TRANSIT_TO_FRONT
 import android.window.DisplayAreaInfo
+import android.window.IWindowContainerToken
 import android.window.RemoteTransition
 import android.window.TransitionRequestInfo
 import android.window.WindowContainerToken
 import android.window.WindowContainerTransaction
+import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_LAUNCH_TASK
 import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT
 import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK
 import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
+import android.window.WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID
 import androidx.test.filters.SmallTest
 import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
 import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
@@ -78,6 +82,7 @@
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createSplitScreenTask
 import com.android.wm.shell.draganddrop.DragAndDropController
+import com.android.wm.shell.recents.RecentTasksController
 import com.android.wm.shell.recents.RecentsTransitionHandler
 import com.android.wm.shell.recents.RecentsTransitionStateListener
 import com.android.wm.shell.shared.DesktopModeStatus
@@ -93,6 +98,7 @@
 import com.android.wm.shell.transition.Transitions.TransitionHandler
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
+import java.util.Optional
 import org.junit.After
 import org.junit.Assume.assumeTrue
 import org.junit.Before
@@ -115,7 +121,6 @@
 import org.mockito.kotlin.atLeastOnce
 import org.mockito.kotlin.capture
 import org.mockito.quality.Strictness
-import java.util.Optional
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
 import org.mockito.Mockito.`when` as whenever
@@ -154,6 +159,7 @@
     @Mock lateinit var multiInstanceHelper: MultiInstanceHelper
     @Mock lateinit var desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver
     @Mock lateinit var desktopModeVisualIndicator: DesktopModeVisualIndicator
+    @Mock lateinit var recentTasksController: RecentTasksController
 
     private lateinit var mockitoSession: StaticMockitoSession
     private lateinit var controller: DesktopTasksController
@@ -233,6 +239,7 @@
             multiInstanceHelper,
             shellExecutor,
             Optional.of(desktopTasksLimiter),
+            recentTasksController
         )
     }
 
@@ -622,7 +629,7 @@
         controller.moveToDesktop(task)
         val wct = getLatestMoveToDesktopWct()
         assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
-            .isEqualTo(WINDOWING_MODE_FREEFORM)
+                .isEqualTo(WINDOWING_MODE_FREEFORM)
     }
 
     @Test
@@ -643,14 +650,17 @@
     }
 
     @Test
-    fun moveToDesktop_deviceNotSupported_doesNothing() {
-        val task = setUpFullscreenTask()
+    fun moveToDesktop_nonRunningTask_launchesInFreeform() {
+        whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
 
-        // Simulate non compatible device
-        doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val task = createTaskInfo(1)
 
-        controller.moveToDesktop(task)
-        verifyWCTNotExecuted()
+        whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
+
+        controller.moveToDesktop(task.taskId)
+        with(getLatestMoveToDesktopWct()){
+            assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM)
+        }
     }
 
     @Test
@@ -666,6 +676,17 @@
     }
 
     @Test
+    fun moveToDesktop_deviceNotSupported_doesNothing() {
+        val task = setUpFullscreenTask()
+
+        // Simulate non compatible device
+        doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+
+        controller.moveToDesktop(task)
+        verifyWCTNotExecuted()
+    }
+
+    @Test
     fun moveToDesktop_deviceNotSupported_deviceRestrictionsOverridden_taskIsMovedToDesktop() {
         val task = setUpFullscreenTask()
 
@@ -1834,6 +1855,20 @@
     assertThat(op.pendingIntent?.intent?.component).isEqualTo(intent.component)
 }
 
+private fun WindowContainerTransaction.assertLaunchTaskAt(
+    index: Int,
+    taskId: Int,
+    windowingMode: Int
+) {
+    val keyLaunchWindowingMode = "android.activity.windowingMode"
+
+    assertIndexInBounds(index)
+    val op = hierarchyOps[index]
+    assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_LAUNCH_TASK)
+    assertThat(op.launchOptions?.getInt(LAUNCH_KEY_TASK_ID)).isEqualTo(taskId)
+    assertThat(op.launchOptions?.getInt(keyLaunchWindowingMode, WINDOWING_MODE_UNDEFINED))
+            .isEqualTo(windowingMode)
+}
 private fun WindowContainerTransaction?.anyDensityConfigChange(
     token: WindowContainerToken
 ): Boolean {
@@ -1841,3 +1876,7 @@
         change.key == token.asBinder() && ((change.value.configSetMask and CONFIG_DENSITY) != 0)
     } ?: false
 }
+private fun createTaskInfo(id: Int) = RecentTaskInfo().apply {
+    taskId = id
+    token = WindowContainerToken(mock(IWindowContainerToken::class.java))
+}