Move task to fullscreen or freeeform on transition

Register DesktopTasksController as a transition handler.
When a shell transition is executed, check if a task should move from
freeform to fullscreen or vice versa.

Bug: 261234278
Test: atest DesktopTasksControllerTest
Change-Id: Ifb7106553d0740e1001ad9f6f8818a8e1fa41a4e
This commit is contained in:
Ats Jenk
2022-12-08 11:27:44 -08:00
parent 6fbc134335
commit a1f8da1f66
2 changed files with 220 additions and 10 deletions

View File

@@ -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)

View File

@@ -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) {