Loading libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +4 −2 Original line number Diff line number Diff line Loading @@ -522,14 +522,16 @@ public abstract class WMShellModule { 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 Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +38 −11 Original line number Diff line number Diff line Loading @@ -70,6 +70,7 @@ import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksLi 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 Loading Loading @@ -118,6 +119,7 @@ class DesktopTasksController( private val multiInstanceHelper: MultiInstanceHelper, @ShellMainThread private val mainExecutor: ShellExecutor, private val desktopTasksLimiter: Optional<DesktopTasksLimiter>, private val recentTasksController: RecentTasksController? ) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler, Loading Loading @@ -293,24 +295,49 @@ class DesktopTasksController( 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, Loading libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +19 −0 Original line number Diff line number Diff line Loading @@ -446,6 +446,25 @@ public class RecentTasksController implements TaskStackListenerCallback, 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); Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +47 −8 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -47,13 +48,16 @@ import android.view.WindowManager.TRANSIT_OPEN 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 Loading @@ -78,6 +82,7 @@ import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFulls 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 Loading @@ -93,6 +98,7 @@ import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_DESKTOP_MODE 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 Loading @@ -115,7 +121,6 @@ import org.mockito.kotlin.anyOrNull 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 Loading Loading @@ -154,6 +159,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @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 Loading Loading @@ -233,6 +239,7 @@ class DesktopTasksControllerTest : ShellTestCase() { multiInstanceHelper, shellExecutor, Optional.of(desktopTasksLimiter), recentTasksController ) } Loading Loading @@ -643,14 +650,17 @@ class DesktopTasksControllerTest : ShellTestCase() { } @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 Loading @@ -665,6 +675,17 @@ class DesktopTasksControllerTest : ShellTestCase() { verifyWCTNotExecuted() } @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() Loading Loading @@ -1834,6 +1855,20 @@ private fun WindowContainerTransaction.assertPendingIntentAt(index: Int, intent: 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 { Loading @@ -1841,3 +1876,7 @@ private fun WindowContainerTransaction?.anyDensityConfigChange( 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)) } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +4 −2 Original line number Diff line number Diff line Loading @@ -522,14 +522,16 @@ public abstract class WMShellModule { 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 Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +38 −11 Original line number Diff line number Diff line Loading @@ -70,6 +70,7 @@ import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksLi 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 Loading Loading @@ -118,6 +119,7 @@ class DesktopTasksController( private val multiInstanceHelper: MultiInstanceHelper, @ShellMainThread private val mainExecutor: ShellExecutor, private val desktopTasksLimiter: Optional<DesktopTasksLimiter>, private val recentTasksController: RecentTasksController? ) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler, Loading Loading @@ -293,24 +295,49 @@ class DesktopTasksController( 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, Loading
libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +19 −0 Original line number Diff line number Diff line Loading @@ -446,6 +446,25 @@ public class RecentTasksController implements TaskStackListenerCallback, 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); Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +47 −8 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -47,13 +48,16 @@ import android.view.WindowManager.TRANSIT_OPEN 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 Loading @@ -78,6 +82,7 @@ import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFulls 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 Loading @@ -93,6 +98,7 @@ import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_DESKTOP_MODE 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 Loading @@ -115,7 +121,6 @@ import org.mockito.kotlin.anyOrNull 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 Loading Loading @@ -154,6 +159,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @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 Loading Loading @@ -233,6 +239,7 @@ class DesktopTasksControllerTest : ShellTestCase() { multiInstanceHelper, shellExecutor, Optional.of(desktopTasksLimiter), recentTasksController ) } Loading Loading @@ -643,14 +650,17 @@ class DesktopTasksControllerTest : ShellTestCase() { } @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 Loading @@ -665,6 +675,17 @@ class DesktopTasksControllerTest : ShellTestCase() { verifyWCTNotExecuted() } @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() Loading Loading @@ -1834,6 +1855,20 @@ private fun WindowContainerTransaction.assertPendingIntentAt(index: Int, intent: 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 { Loading @@ -1841,3 +1876,7 @@ private fun WindowContainerTransaction?.anyDensityConfigChange( 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)) }