Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a1f8da1f authored by Ats Jenk's avatar Ats Jenk
Browse files

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
parent 6fbc1343
Loading
Loading
Loading
Loading
+91 −8
Original line number Diff line number Diff line
@@ -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)
+129 −2
Original line number Diff line number Diff line
@@ -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) {