diff options
2 files changed, 220 insertions, 10 deletions
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 b075b14fb0a4..3341470efe4d 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 @@ -17,12 +17,20 @@ package com.android.wm.shell.desktopmode import android.app.ActivityManager -import android.app.WindowConfiguration import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME +import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.app.WindowConfiguration.WindowingMode import android.content.Context -import android.view.WindowManager +import android.os.IBinder +import android.view.SurfaceControl +import android.view.WindowManager.TRANSIT_CHANGE +import android.view.WindowManager.TRANSIT_OPEN +import android.view.WindowManager.TRANSIT_TO_FRONT +import android.window.TransitionInfo +import android.window.TransitionRequestInfo import android.window.WindowContainerTransaction import androidx.annotation.BinderThread import com.android.internal.protolog.common.ProtoLog @@ -51,7 +59,7 @@ class DesktopTasksController( private val transitions: Transitions, private val desktopModeTaskRepository: DesktopModeTaskRepository, @ShellMainThread private val mainExecutor: ShellExecutor -) : RemoteCallable<DesktopTasksController> { +) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler { private val desktopMode: DesktopModeImpl @@ -69,6 +77,7 @@ class DesktopTasksController( { createExternalInterface() }, this ) + transitions.addHandler(this) } /** Show all tasks, that are part of the desktop, on top of launcher */ @@ -81,7 +90,7 @@ class DesktopTasksController( // Execute transaction if there are pending operations if (!wct.isEmpty) { if (Transitions.ENABLE_SHELL_TRANSITIONS) { - transitions.startTransition(WindowManager.TRANSIT_TO_FRONT, wct, null /* handler */) + transitions.startTransition(TRANSIT_TO_FRONT, wct, null /* handler */) } else { shellTaskOrganizer.applyTransaction(wct) } @@ -101,11 +110,11 @@ class DesktopTasksController( // Bring other apps to front first bringDesktopAppsToFront(wct) - wct.setWindowingMode(task.getToken(), WindowConfiguration.WINDOWING_MODE_FREEFORM) + wct.setWindowingMode(task.getToken(), WINDOWING_MODE_FREEFORM) wct.reorder(task.getToken(), true /* onTop */) if (Transitions.ENABLE_SHELL_TRANSITIONS) { - transitions.startTransition(WindowManager.TRANSIT_CHANGE, wct, null /* handler */) + transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */) } else { shellTaskOrganizer.applyTransaction(wct) } @@ -121,10 +130,10 @@ class DesktopTasksController( ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToFullscreen: %d", task.taskId) val wct = WindowContainerTransaction() - wct.setWindowingMode(task.getToken(), WindowConfiguration.WINDOWING_MODE_FULLSCREEN) + wct.setWindowingMode(task.getToken(), WINDOWING_MODE_FULLSCREEN) wct.setBounds(task.getToken(), null) if (Transitions.ENABLE_SHELL_TRANSITIONS) { - transitions.startTransition(WindowManager.TRANSIT_CHANGE, wct, null /* handler */) + transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */) } else { shellTaskOrganizer.applyTransaction(wct) } @@ -181,6 +190,80 @@ class DesktopTasksController( return mainExecutor } + override fun startAnimation( + transition: IBinder, + info: TransitionInfo, + startTransaction: SurfaceControl.Transaction, + finishTransaction: SurfaceControl.Transaction, + finishCallback: Transitions.TransitionFinishCallback + ): Boolean { + // This handler should never be the sole handler, so should not animate anything. + return false + } + + override fun handleRequest( + transition: IBinder, + request: TransitionRequestInfo + ): WindowContainerTransaction? { + // Check if we should skip handling this transition + val task: ActivityManager.RunningTaskInfo? = request.triggerTask + val shouldHandleRequest = + when { + // Only handle open or to front transitions + request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> false + // Only handle when it is a task transition + task == null -> false + // Only handle standard type tasks + task.activityType != ACTIVITY_TYPE_STANDARD -> false + // Only handle fullscreen or freeform tasks + task.windowingMode != WINDOWING_MODE_FULLSCREEN && + task.windowingMode != WINDOWING_MODE_FREEFORM -> false + // Otherwise process it + else -> true + } + + if (!shouldHandleRequest) { + return null + } + + val activeTasks = desktopModeTaskRepository.getActiveTasks() + + // Check if we should switch a fullscreen task to freeform + if (task?.windowingMode == WINDOWING_MODE_FULLSCREEN) { + // If there are any visible desktop tasks, switch the task to freeform + if (activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) { + ProtoLog.d( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController#handleRequest: switch fullscreen task to freeform," + + " taskId=%d", + task.taskId + ) + return WindowContainerTransaction().apply { + setWindowingMode(task.token, WINDOWING_MODE_FREEFORM) + } + } + } + + // CHeck if we should switch a freeform task to fullscreen + if (task?.windowingMode == WINDOWING_MODE_FREEFORM) { + // If no visible desktop tasks, switch this task to freeform as the transition came + // outside of this controller + if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) { + ProtoLog.d( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController#handleRequest: switch freeform task to fullscreen," + + " taskId=%d", + task.taskId + ) + return WindowContainerTransaction().apply { + setWindowingMode(task.token, WINDOWING_MODE_FULLSCREEN) + setBounds(task.token, null) + } + } + } + return null + } + /** Creates a new instance of the external interface to pass to another process. */ private fun createExternalInterface(): ExternalInterfaceBinder { return IDesktopModeImpl(this) 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 de2473b8deba..9a92879bde1f 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 @@ -17,10 +17,18 @@ package com.android.wm.shell.desktopmode import android.app.ActivityManager.RunningTaskInfo +import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME +import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN +import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED +import android.os.Binder import android.testing.AndroidTestingRunner +import android.view.WindowManager +import android.view.WindowManager.TRANSIT_OPEN +import android.view.WindowManager.TRANSIT_TO_FRONT +import android.window.TransitionRequestInfo import android.window.WindowContainerTransaction import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER import androidx.test.filters.SmallTest @@ -29,6 +37,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.never import com.android.dx.mockito.inline.extended.StaticMockitoSession import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.TestRunningTaskInfoBuilder import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask @@ -37,9 +46,11 @@ import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeT import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions +import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import org.junit.After +import org.junit.Assume.assumeTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -79,6 +90,7 @@ class DesktopTasksControllerTest : ShellTestCase() { desktopModeTaskRepository = DesktopModeTaskRepository() whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks } + whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() } controller = createController() @@ -221,6 +233,114 @@ class DesktopTasksControllerTest : ShellTestCase() { assertThat(controller.getTaskWindowingMode(999)).isEqualTo(WINDOWING_MODE_UNDEFINED) } + @Test + fun handleRequest_fullscreenTask_freeformVisible_returnSwitchToFreeformWCT() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + + val freeformTask = setUpFreeformTask() + markTaskVisible(freeformTask) + val fullscreenTask = createFullscreenTask() + + val result = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + assertThat(result?.changes?.get(fullscreenTask.token.asBinder())?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + } + + @Test + fun handleRequest_fullscreenTask_freeformNotVisible_returnNull() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + + val freeformTask = setUpFreeformTask() + markTaskHidden(freeformTask) + val fullscreenTask = createFullscreenTask() + assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull() + } + + @Test + fun handleRequest_fullscreenTask_noOtherTasks_returnNull() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + + val fullscreenTask = createFullscreenTask() + assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull() + } + + @Test + fun handleRequest_freeformTask_freeformVisible_returnNull() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + + val freeformTask1 = setUpFreeformTask() + markTaskVisible(freeformTask1) + + val freeformTask2 = createFreeformTask() + assertThat(controller.handleRequest(Binder(), createTransition(freeformTask2))).isNull() + } + + @Test + fun handleRequest_freeformTask_freeformNotVisible_returnSwitchToFullscreenWCT() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + + val freeformTask1 = setUpFreeformTask() + markTaskHidden(freeformTask1) + + val freeformTask2 = createFreeformTask() + val result = + controller.handleRequest( + Binder(), + createTransition(freeformTask2, type = TRANSIT_TO_FRONT) + ) + assertThat(result?.changes?.get(freeformTask2.token.asBinder())?.windowingMode) + .isEqualTo(WINDOWING_MODE_FULLSCREEN) + } + + @Test + fun handleRequest_freeformTask_noOtherTasks_returnSwitchToFullscreenWCT() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + + val task = createFreeformTask() + val result = controller.handleRequest(Binder(), createTransition(task)) + assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode) + .isEqualTo(WINDOWING_MODE_FULLSCREEN) + } + + @Test + fun handleRequest_notOpenOrToFrontTransition_returnNull() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + + val task = + TestRunningTaskInfoBuilder() + .setActivityType(ACTIVITY_TYPE_STANDARD) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN) + .build() + val transition = createTransition(task = task, type = WindowManager.TRANSIT_CLOSE) + val result = controller.handleRequest(Binder(), transition) + assertThat(result).isNull() + } + + @Test + fun handleRequest_noTriggerTask_returnNull() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + assertThat(controller.handleRequest(Binder(), createTransition(task = null))).isNull() + } + + @Test + fun handleRequest_triggerTaskNotStandard_returnNull() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + val task = TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build() + assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull() + } + + @Test + fun handleRequest_triggerTaskNotFullscreenOrFreeform_returnNull() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + + val task = + TestRunningTaskInfoBuilder() + .setActivityType(ACTIVITY_TYPE_STANDARD) + .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW) + .build() + assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull() + } + private fun setUpFreeformTask(): RunningTaskInfo { val task = createFreeformTask() whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) @@ -254,7 +374,7 @@ class DesktopTasksControllerTest : ShellTestCase() { private fun getLatestWct(): WindowContainerTransaction { val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) - if (Transitions.ENABLE_SHELL_TRANSITIONS) { + if (ENABLE_SHELL_TRANSITIONS) { verify(transitions).startTransition(anyInt(), arg.capture(), isNull()) } else { verify(shellTaskOrganizer).applyTransaction(arg.capture()) @@ -263,12 +383,19 @@ class DesktopTasksControllerTest : ShellTestCase() { } private fun verifyWCTNotExecuted() { - if (Transitions.ENABLE_SHELL_TRANSITIONS) { + if (ENABLE_SHELL_TRANSITIONS) { verify(transitions, never()).startTransition(anyInt(), any(), isNull()) } else { verify(shellTaskOrganizer, never()).applyTransaction(any()) } } + + private fun createTransition( + task: RunningTaskInfo?, + @WindowManager.TransitionType type: Int = TRANSIT_OPEN + ): TransitionRequestInfo { + return TransitionRequestInfo(type, task, null /* remoteTransition */) + } } private fun WindowContainerTransaction.assertReorderAt(index: Int, task: RunningTaskInfo) { |