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

Commit e9905d68 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add remoteTransition for moveTaskToFront(), and apply minimize reparent" into main

parents dbe1fc5c 68628867
Loading
Loading
Loading
Loading
+56 −17
Original line number Diff line number Diff line
@@ -82,6 +82,7 @@ import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler
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
@@ -562,10 +563,20 @@ class DesktopTasksController(
            )
    }

    /** Move a task to the front */
    fun moveTaskToFront(taskId: Int) {
    /**
     * Move a task to the front, using [remoteTransition].
     *
     * Note: beyond moving a task to the front, this method will minimize a task if we reach the
     * Desktop task limit, so [remoteTransition] should also handle any such minimize change.
     */
    @JvmOverloads
    fun moveTaskToFront(taskId: Int, remoteTransition: RemoteTransition? = null) {
        val task = shellTaskOrganizer.getRunningTaskInfo(taskId)
        if (task == null) moveBackgroundTaskToFront(taskId) else moveTaskToFront(task)
        if (task == null) {
            moveBackgroundTaskToFront(taskId, remoteTransition)
        } else {
            moveTaskToFront(task, remoteTransition)
        }
    }

    /**
@@ -573,12 +584,10 @@ class DesktopTasksController(
     * desktop. If outside of desktop and want to launch a background task in desktop, use
     * [moveBackgroundTaskToDesktop] instead.
     */
    private fun moveBackgroundTaskToFront(taskId: Int) {
    private fun moveBackgroundTaskToFront(taskId: Int, remoteTransition: RemoteTransition?) {
        logV("moveBackgroundTaskToFront taskId=%s", taskId)
        val wct = WindowContainerTransaction()
        // TODO: b/342378842 - Instead of using default display, support multiple displays
        val taskToMinimize: RunningTaskInfo? =
            addAndGetMinimizeChangesIfNeeded(DEFAULT_DISPLAY, wct, taskId)
        val runOnTransit = immersiveTransitionHandler
            .exitImmersiveIfApplicable(wct, DEFAULT_DISPLAY)
        wct.startTask(
@@ -587,26 +596,56 @@ class DesktopTasksController(
                launchWindowingMode = WINDOWING_MODE_FREEFORM
            }.toBundle(),
        )
        val transition = transitions.startTransition(TRANSIT_OPEN, wct, null /* handler */)
        addPendingMinimizeTransition(transition, taskToMinimize)
        val transition = startLaunchTransition(TRANSIT_OPEN, wct, taskId, remoteTransition)
        runOnTransit?.invoke(transition)
    }

    /** Move a task to the front */
    fun moveTaskToFront(taskInfo: RunningTaskInfo) {
    /**
     * Move a task to the front, using [remoteTransition].
     *
     * Note: beyond moving a task to the front, this method will minimize a task if we reach the
     * Desktop task limit, so [remoteTransition] should also handle any such minimize change.
     */
    @JvmOverloads
    fun moveTaskToFront(taskInfo: RunningTaskInfo, remoteTransition: RemoteTransition? = null) {
        logV("moveTaskToFront taskId=%s", taskInfo.taskId)
        val wct = WindowContainerTransaction()
        wct.reorder(taskInfo.token, true /* onTop */, true /* includingParents */)
        val runOnTransit = immersiveTransitionHandler.exitImmersiveIfApplicable(
            wct, taskInfo.displayId)
        val taskToMinimize =
            addAndGetMinimizeChangesIfNeeded(taskInfo.displayId, wct, taskInfo.taskId)

        val transition = transitions.startTransition(TRANSIT_TO_FRONT, wct, null /* handler */)
        addPendingMinimizeTransition(transition, taskToMinimize)
        val transition =
            startLaunchTransition(TRANSIT_TO_FRONT, wct, taskInfo.taskId, remoteTransition)
        runOnTransit?.invoke(transition)
    }

    private fun startLaunchTransition(
        transitionType: Int,
        wct: WindowContainerTransaction,
        taskId: Int,
        remoteTransition: RemoteTransition?,
    ): IBinder {
        val taskToMinimize: RunningTaskInfo? =
            addAndGetMinimizeChangesIfNeeded(DEFAULT_DISPLAY, wct, taskId)
        if (remoteTransition == null) {
            val t = transitions.startTransition(transitionType, wct, null /* handler */)
            addPendingMinimizeTransition(t, taskToMinimize)
            return t
        }
        if (taskToMinimize == null) {
            val remoteTransitionHandler = OneShotRemoteHandler(mainExecutor, remoteTransition)
            val t = transitions.startTransition(transitionType, wct, remoteTransitionHandler)
            remoteTransitionHandler.setTransition(t)
            return t
        }
        val remoteTransitionHandler =
            DesktopWindowLimitRemoteHandler(
                mainExecutor, rootTaskDisplayAreaOrganizer, remoteTransition, taskToMinimize.taskId)
        val t = transitions.startTransition(transitionType, wct, remoteTransitionHandler)
        remoteTransitionHandler.setTransition(t)
        addPendingMinimizeTransition(t, taskToMinimize)
        return t
    }

    /**
     * Move task to the next display.
     *
@@ -2031,9 +2070,9 @@ class DesktopTasksController(
            }
        }

        override fun showDesktopApp(taskId: Int) {
        override fun showDesktopApp(taskId: Int, remoteTransition: RemoteTransition?) {
            executeRemoteCallWithTaskPermission(controller, "showDesktopApp") { c ->
                c.moveTaskToFront(taskId)
                c.moveTaskToFront(taskId, remoteTransition)
            }
        }

+7 −2
Original line number Diff line number Diff line
@@ -35,8 +35,13 @@ interface IDesktopMode {
    /** @deprecated this is no longer supported. */
    void hideStashedDesktopApps(int displayId);

    /** Bring task with the given id to front */
    oneway void showDesktopApp(int taskId);
    /**
    * Bring task with the given id to front, using the given remote transition.
    *
    * <p> Note: beyond moving a task to the front, this method will minimize a task if we reach the
    * Desktop task limit, so {@code remoteTransition} should also handle any such minimize change.
    */
    oneway void showDesktopApp(int taskId, in @nullable RemoteTransition remoteTransition);

    /** Get count of visible desktop tasks on the given display */
    int getVisibleTaskCount(int displayId);
+100 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.wm.shell.desktopmode.minimize

import android.os.IBinder
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.RemoteTransition
import android.window.TransitionInfo
import android.window.TransitionInfo.Change
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.transition.OneShotRemoteHandler
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TransitionHandler

/**
 * Handles transitions that result in hitting the Desktop window limit, by performing some
 * preparation work and then delegating to [remoteTransition].
 *
 * This transition handler prepares the leash of the minimizing change referenced by
 * [taskIdToMinimize], and then delegates to [remoteTransition] to perform the actual transition.
 */
class DesktopWindowLimitRemoteHandler(
    mainExecutor: ShellExecutor,
    private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
    remoteTransition: RemoteTransition,
    private val taskIdToMinimize: Int,
    ) : TransitionHandler {

    private val oneShotRemoteHandler = OneShotRemoteHandler(mainExecutor, remoteTransition)
    private var transition: IBinder? = null

    /** Sets the transition that will be handled - this must be called before [startAnimation]. */
    fun setTransition(transition: IBinder) {
        this.transition = transition
        oneShotRemoteHandler.setTransition(transition)
    }

    override fun handleRequest(
        transition: IBinder,
        request: TransitionRequestInfo
    ): WindowContainerTransaction? {
        this.transition = transition
        return oneShotRemoteHandler.handleRequest(transition, request)
    }

    override fun startAnimation(
        transition: IBinder,
        info: TransitionInfo,
        startTransaction: SurfaceControl.Transaction,
        finishTransaction: SurfaceControl.Transaction,
        finishCallback: Transitions.TransitionFinishCallback
    ): Boolean {
        if (transition != this.transition) return false
        val minimizeChange = findMinimizeChange(info, taskIdToMinimize) ?: return false
        // Reparent the minimize change back to the root task display area, so the minimizing window
        // isn't shown in front of other windows. We do this here in Shell since Launcher doesn't
        // have access to RootTaskDisplayAreaOrganizer.
        applyMinimizeChangeReparenting(info, minimizeChange, startTransaction)
        return oneShotRemoteHandler.startAnimation(
            transition, info, startTransaction, finishTransaction, finishCallback)
    }

    private fun applyMinimizeChangeReparenting(
        info: TransitionInfo,
        minimizeChange: Change,
        startTransaction: SurfaceControl.Transaction,
    ) {
        val taskInfo = minimizeChange.taskInfo ?: return
        if (taskInfo.isFreeform && TransitionUtil.isOpeningMode(info.type)) {
            rootTaskDisplayAreaOrganizer.reparentToDisplayArea(
                taskInfo.displayId, minimizeChange.leash, startTransaction)
        }
    }

    private fun findMinimizeChange(
        info: TransitionInfo,
        taskIdToMinimize: Int,
    ): Change? =
        info.changes.find { change ->
            change.taskInfo?.taskId == taskIdToMinimize && change.mode == TRANSIT_TO_BACK }
}
+39 −8
Original line number Diff line number Diff line
@@ -97,6 +97,7 @@ import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreef
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createSplitScreenTask
import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler
import com.android.wm.shell.desktopmode.persistence.Desktop
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
import com.android.wm.shell.draganddrop.DragAndDropController
@@ -122,6 +123,7 @@ import java.util.function.Consumer
import java.util.Optional
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import kotlin.test.assertIs
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlinx.coroutines.CoroutineScope
@@ -1338,7 +1340,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
    val task1 = setUpFreeformTask()
    setUpFreeformTask()

    controller.moveTaskToFront(task1)
    controller.moveTaskToFront(task1, remoteTransition = null)

    val wct = getLatestWct(type = TRANSIT_TO_FRONT)
    assertThat(wct.hierarchyOps).hasSize(1)
@@ -1350,7 +1352,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
    setUpHomeTask()
    val freeformTasks = (1..MAX_TASK_LIMIT + 1).map { _ -> setUpFreeformTask() }

    controller.moveTaskToFront(freeformTasks[0])
    controller.moveTaskToFront(freeformTasks[0], remoteTransition = null)

    val wct = getLatestWct(type = TRANSIT_TO_FRONT)
    assertThat(wct.hierarchyOps.size).isEqualTo(2) // move-to-front + minimize
@@ -1358,12 +1360,41 @@ class DesktopTasksControllerTest : ShellTestCase() {
    wct.assertReorderAt(1, freeformTasks[1], toTop = false)
  }

  @Test
  fun moveTaskToFront_remoteTransition_usesOneshotHandler() {
    setUpHomeTask()
    val freeformTasks = List(MAX_TASK_LIMIT) { setUpFreeformTask() }
    val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
    whenever(
      transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture())
    ).thenReturn(Binder())

    controller.moveTaskToFront(freeformTasks[0], RemoteTransition(TestRemoteTransition()))

    assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.value)
  }

  @Test
  fun moveTaskToFront_bringsTasksOverLimit_remoteTransition_usesWindowLimitHandler() {
    setUpHomeTask()
    val freeformTasks = List(MAX_TASK_LIMIT + 1) { setUpFreeformTask() }
    val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
    whenever(
      transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture())
    ).thenReturn(Binder())

    controller.moveTaskToFront(freeformTasks[0], RemoteTransition(TestRemoteTransition()))

    assertThat(transitionHandlerArgCaptor.value)
      .isInstanceOf(DesktopWindowLimitRemoteHandler::class.java)
  }

  @Test
  fun moveTaskToFront_backgroundTask_launchesTask() {
    val task = createTaskInfo(1)
    whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)

    controller.moveTaskToFront(task.taskId)
    controller.moveTaskToFront(task.taskId, remoteTransition = null)

    val wct = getLatestWct(type = TRANSIT_OPEN)
    assertThat(wct.hierarchyOps).hasSize(1)
@@ -1376,12 +1407,12 @@ class DesktopTasksControllerTest : ShellTestCase() {
    val task = createTaskInfo(1001)
    whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null)

    controller.moveTaskToFront(task.taskId)
    controller.moveTaskToFront(task.taskId, remoteTransition = null)

    val wct = getLatestWct(type = TRANSIT_OPEN)
    assertThat(wct.hierarchyOps.size).isEqualTo(2) // launch + minimize
    wct.assertReorderAt(0, freeformTasks[0], toTop = false)
    wct.assertLaunchTaskAt(1, task.taskId, WINDOWING_MODE_FREEFORM)
    wct.assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM)
    wct.assertReorderAt(1, freeformTasks[0], toTop = false)
  }

  @Test
@@ -3372,7 +3403,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
      .exitImmersiveIfApplicable(any(), eq(task.displayId))).thenReturn(runOnStartTransit)
    whenever(transitions.startTransition(any(), any(), anyOrNull())).thenReturn(transition)

    controller.moveTaskToFront(task.taskId)
    controller.moveTaskToFront(task.taskId, remoteTransition = null)

    verify(mockDesktopFullImmersiveTransitionHandler)
      .exitImmersiveIfApplicable(any(), eq(task.displayId))
@@ -3388,7 +3419,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
      .exitImmersiveIfApplicable(any(), eq(task.displayId))).thenReturn(runOnStartTransit)
    whenever(transitions.startTransition(any(), any(), anyOrNull())).thenReturn(transition)

    controller.moveTaskToFront(task.taskId)
    controller.moveTaskToFront(task.taskId, remoteTransition = null)

    verify(mockDesktopFullImmersiveTransitionHandler)
      .exitImmersiveIfApplicable(any(), eq(task.displayId))
+167 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.wm.shell.desktopmode.minimize

import android.app.ActivityManager
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.os.Binder
import android.os.IBinder
import android.testing.AndroidTestingRunner
import android.view.SurfaceControl.Transaction
import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.IRemoteTransition
import android.window.RemoteTransition
import androidx.test.filters.SmallTest
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.transition.TransitionInfoBuilder
import com.android.wm.shell.transition.Transitions
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.mock
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever

@SmallTest
@RunWith(AndroidTestingRunner::class)
class DesktopWindowLimitRemoteHandlerTest {

    private val shellExecutor = TestShellExecutor()
    private val transition: IBinder = Binder()

    private val rootTaskDisplayAreaOrganizer = mock<RootTaskDisplayAreaOrganizer>()
    private val remoteTransition = mock<RemoteTransition>()
    private val iRemoteTransition = mock<IRemoteTransition>()
    private val startT = mock<Transaction>()
    private val finishT = mock<Transaction>()
    private val finishCallback = mock<Transitions.TransitionFinishCallback>()

    @Before
    fun setUp() {
        whenever(remoteTransition.remoteTransition).thenReturn(iRemoteTransition)
        whenever(iRemoteTransition.asBinder()).thenReturn(mock(IBinder::class.java))
    }

    private fun createRemoteHandler(taskIdToMinimize: Int) =
        DesktopWindowLimitRemoteHandler(
            shellExecutor, rootTaskDisplayAreaOrganizer, remoteTransition, taskIdToMinimize)

    @Test
    fun startAnimation_dontSetTransition_returnsFalse() {
        val minimizeTask = createDesktopTask()
        val remoteHandler = createRemoteHandler(taskIdToMinimize = minimizeTask.taskId)

        assertThat(remoteHandler.startAnimation(transition,
            createMinimizeTransitionInfo(minimizeTask), startT, finishT, finishCallback)
        ).isFalse()
    }

    @Test
    fun startAnimation_noMinimizeChange_returnsFalse() {
        val remoteHandler = createRemoteHandler(taskIdToMinimize = 1)
        remoteHandler.setTransition(transition)
        val info = createToFrontTransitionInfo()

        assertThat(
            remoteHandler.startAnimation(transition, info, startT, finishT, finishCallback)
        ).isFalse()
    }

    @Test
    fun startAnimation_correctTransition_returnsTrue() {
        val minimizeTask = createDesktopTask()
        val remoteHandler = createRemoteHandler(taskIdToMinimize = minimizeTask.taskId)
        remoteHandler.setTransition(transition)
        val info = createMinimizeTransitionInfo(minimizeTask)

        assertThat(
            remoteHandler.startAnimation(transition, info, startT, finishT, finishCallback)
        ).isTrue()
    }

    @Test
    fun startAnimation_noMinimizeChange_doesNotReparentMinimizeChange() {
        val remoteHandler = createRemoteHandler(taskIdToMinimize = 1)
        remoteHandler.setTransition(transition)
        val info = createToFrontTransitionInfo()

        remoteHandler.startAnimation(transition, info, startT, finishT, finishCallback)

        verify(rootTaskDisplayAreaOrganizer, times(0))
            .reparentToDisplayArea(anyInt(), any(), any())
    }

    @Test
    fun startAnimation_hasMinimizeChange_reparentsMinimizeChange() {
        val minimizeTask = createDesktopTask()
        val remoteHandler = createRemoteHandler(taskIdToMinimize = minimizeTask.taskId)
        remoteHandler.setTransition(transition)
        val info = createMinimizeTransitionInfo(minimizeTask)

        remoteHandler.startAnimation(transition, info, startT, finishT, finishCallback)

        verify(rootTaskDisplayAreaOrganizer).reparentToDisplayArea(anyInt(), any(), any())
    }

    @Test
    fun startAnimation_noMinimizeChange_doesNotStartRemoteAnimation() {
        val minimizeTask = createDesktopTask()
        val remoteHandler = createRemoteHandler(taskIdToMinimize = minimizeTask.taskId)
        remoteHandler.setTransition(transition)
        val info = createToFrontTransitionInfo()

        remoteHandler.startAnimation(transition, info, startT, finishT, finishCallback)

        verify(iRemoteTransition, times(0)).startAnimation(any(), any(), any(), any())
    }

    @Test
    fun startAnimation_hasMinimizeChange_startsRemoteAnimation() {
        val minimizeTask = createDesktopTask()
        val remoteHandler = createRemoteHandler(taskIdToMinimize = minimizeTask.taskId)
        remoteHandler.setTransition(transition)
        val info = createMinimizeTransitionInfo(minimizeTask)

        remoteHandler.startAnimation(transition, info, startT, finishT, finishCallback)

        verify(iRemoteTransition).startAnimation(any(), any(), any(), any())
    }

    private fun createDesktopTask() =
        TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build()

    private fun createToFrontTransitionInfo() =
        TransitionInfoBuilder(TRANSIT_TO_FRONT)
            .addChange(TRANSIT_TO_FRONT,
                TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build())
            .build()

    private fun createMinimizeTransitionInfo(minimizeTask: ActivityManager.RunningTaskInfo) =
        TransitionInfoBuilder(TRANSIT_TO_FRONT)
            .addChange(TRANSIT_TO_FRONT,
                TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build())
            .addChange(TRANSIT_TO_BACK, minimizeTask)
            .build()
}