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:
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user