diff options
10 files changed, 490 insertions, 134 deletions
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopTaskToFrontReason.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopTaskToFrontReason.aidl new file mode 100644 index 000000000000..78c4c35727de --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopTaskToFrontReason.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2025 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.shared.desktopmode; + +parcelable DesktopTaskToFrontReason;
\ No newline at end of file diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopTaskToFrontReason.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopTaskToFrontReason.kt new file mode 100644 index 000000000000..4a24af74b212 --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopTaskToFrontReason.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2025 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.shared.desktopmode + +import android.os.Parcel +import android.os.Parcelable + +/** Reason for moving a task to front in Desktop Mode. */ +enum class DesktopTaskToFrontReason : Parcelable { + UNKNOWN, + TASKBAR_TAP, + ALT_TAB, + TASKBAR_MANAGE_WINDOW; + + override fun describeContents(): Int { + return 0 + } + + override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeString(name) + } + + companion object { + @JvmField + val CREATOR = object : Parcelable.Creator<DesktopTaskToFrontReason> { + override fun createFromParcel(parcel: Parcel): DesktopTaskToFrontReason { + return parcel.readString()?.let { valueOf(it) } ?: UNKNOWN + } + + override fun newArray(size: Int) = arrayOfNulls<DesktopTaskToFrontReason>(size) + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt index 68bdbd1758b3..702c67473db2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt @@ -407,7 +407,7 @@ class DesktopModeEventLogger { * @property taskX x-coordinate of the top-left corner * @property taskY y-coordinate of the top-left corner * @property minimizeReason the reason the task was minimized - * @property unminimizeEvent the reason the task was unminimized + * @property unminimizeReason the reason the task was unminimized */ data class TaskUpdate( val instanceId: Int, @@ -499,6 +499,14 @@ class DesktopModeEventLogger { FrameworkStatsLog .DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNMINIMIZE_TASK_LAUNCH ), + APP_HANDLE_MENU_BUTTON( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNMINIMIZE_APP_HANDLE_MENU_BUTTON + ), + TASKBAR_MANAGE_WINDOW( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNMINIMIZE_TASKBAR_MANAGE_WINDOW + ), } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt index 2dd89c790b58..df4b1c4a66ec 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt @@ -41,6 +41,7 @@ import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterRe import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON @@ -297,7 +298,12 @@ class DesktopModeLoggerTransitionObserver( when { // new tasks added previousTaskInfo == null -> { - desktopModeEventLogger.logTaskAdded(currentTaskUpdate) + // The current task is now visible while before it wasn't - this might be the + // result of an unminimize action. + val unminimizeReason = getUnminimizeReason(transition, taskInfo) + desktopModeEventLogger.logTaskAdded( + currentTaskUpdate.copy(unminimizeReason = unminimizeReason) + ) Trace.setCounter( Trace.TRACE_TAG_WINDOW_MANAGER, VISIBLE_TASKS_COUNTER_NAME, @@ -359,13 +365,24 @@ class DesktopModeLoggerTransitionObserver( return null } + private fun getUnminimizeReason(transition: IBinder, taskInfo: TaskInfo): UnminimizeReason? { + val unminimizingTask = desktopTasksLimiter.getOrNull()?.getUnminimizingTask(transition) + if (unminimizingTask?.taskId == taskInfo.taskId) { + return unminimizingTask.unminimizeReason + } + return null + } + private fun buildTaskUpdateForTask( taskInfo: TaskInfo, visibleTasks: Int, minimizeReason: MinimizeReason? = null, + unminimizeReason: UnminimizeReason? = null, ): TaskUpdate { val screenBounds = taskInfo.configuration.windowConfiguration.bounds val positionInParent = taskInfo.positionInParent + // We can't both minimize and unminimize the same task in one go. + assert(minimizeReason == null || unminimizeReason == null) return TaskUpdate( instanceId = taskInfo.taskId, uid = taskInfo.effectiveUid, @@ -375,6 +392,7 @@ class DesktopModeLoggerTransitionObserver( taskY = positionInParent.y, visibleTaskCount = visibleTasks, minimizeReason = minimizeReason, + unminimizeReason = unminimizeReason, ) } 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 172410d0482c..b25c80c18fda 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 @@ -89,6 +89,7 @@ import com.android.wm.shell.compatui.isTransparentTask import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType @@ -115,6 +116,7 @@ import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useDesktopOverrideDensity import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource +import com.android.wm.shell.shared.desktopmode.DesktopTaskToFrontReason import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_UNDEFINED import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT @@ -753,12 +755,16 @@ class DesktopTasksController( * Desktop task limit, so [remoteTransition] should also handle any such minimize change. */ @JvmOverloads - fun moveTaskToFront(taskId: Int, remoteTransition: RemoteTransition? = null) { + fun moveTaskToFront( + taskId: Int, + remoteTransition: RemoteTransition? = null, + unminimizeReason: UnminimizeReason, + ) { val task = shellTaskOrganizer.getRunningTaskInfo(taskId) if (task == null) { - moveBackgroundTaskToFront(taskId, remoteTransition) + moveBackgroundTaskToFront(taskId, remoteTransition, unminimizeReason) } else { - moveTaskToFront(task, remoteTransition) + moveTaskToFront(task, remoteTransition, unminimizeReason) } } @@ -767,7 +773,11 @@ class DesktopTasksController( * desktop. If outside of desktop and want to launch a background task in desktop, use * [moveBackgroundTaskToDesktop] instead. */ - private fun moveBackgroundTaskToFront(taskId: Int, remoteTransition: RemoteTransition?) { + private fun moveBackgroundTaskToFront( + taskId: Int, + remoteTransition: RemoteTransition?, + unminimizeReason: UnminimizeReason, + ) { logV("moveBackgroundTaskToFront taskId=%s", taskId) val wct = WindowContainerTransaction() wct.startTask( @@ -776,7 +786,13 @@ class DesktopTasksController( .apply { launchWindowingMode = WINDOWING_MODE_FREEFORM } .toBundle(), ) - startLaunchTransition(TRANSIT_OPEN, wct, taskId, remoteTransition = remoteTransition) + startLaunchTransition( + TRANSIT_OPEN, + wct, + taskId, + remoteTransition = remoteTransition, + unminimizeReason = unminimizeReason, + ) } /** @@ -786,7 +802,11 @@ class DesktopTasksController( * Desktop task limit, so [remoteTransition] should also handle any such minimize change. */ @JvmOverloads - fun moveTaskToFront(taskInfo: RunningTaskInfo, remoteTransition: RemoteTransition? = null) { + fun moveTaskToFront( + taskInfo: RunningTaskInfo, + remoteTransition: RemoteTransition? = null, + unminimizeReason: UnminimizeReason = UnminimizeReason.UNKNOWN, + ) { logV("moveTaskToFront taskId=%s", taskInfo.taskId) // If a task is tiled, another task should be brought to foreground with it so let // tiling controller handle the request. @@ -801,6 +821,7 @@ class DesktopTasksController( launchingTaskId = taskInfo.taskId, remoteTransition = remoteTransition, displayId = taskInfo.displayId, + unminimizeReason = unminimizeReason, ) } @@ -810,6 +831,7 @@ class DesktopTasksController( launchingTaskId: Int?, remoteTransition: RemoteTransition? = null, displayId: Int = DEFAULT_DISPLAY, + unminimizeReason: UnminimizeReason = UnminimizeReason.UNKNOWN, ): IBinder { val taskIdToMinimize = addAndGetMinimizeChanges( @@ -825,8 +847,8 @@ class DesktopTasksController( excludeTaskId = launchingTaskId, reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH, ) - if (remoteTransition == null) { - val t = + val t = + if (remoteTransition == null) { desktopMixedTransitionHandler.startLaunchTransition( transitionType = transitionType, wct = wct, @@ -834,27 +856,29 @@ class DesktopTasksController( minimizingTaskId = taskIdToMinimize, exitingImmersiveTask = exitImmersiveResult.asExit()?.exitingTask, ) - taskIdToMinimize?.let { addPendingMinimizeTransition(t, it, MinimizeReason.TASK_LIMIT) } - exitImmersiveResult.asExit()?.runOnTransitionStart?.invoke(t) - return t + } else if (taskIdToMinimize == null) { + val remoteTransitionHandler = OneShotRemoteHandler(mainExecutor, remoteTransition) + transitions.startTransition(transitionType, wct, remoteTransitionHandler).also { + remoteTransitionHandler.setTransition(it) + } + } else { + val remoteTransitionHandler = + DesktopWindowLimitRemoteHandler( + mainExecutor, + rootTaskDisplayAreaOrganizer, + remoteTransition, + taskIdToMinimize, + ) + transitions.startTransition(transitionType, wct, remoteTransitionHandler).also { + remoteTransitionHandler.setTransition(it) + } + } + if (taskIdToMinimize != null) { + addPendingMinimizeTransition(t, taskIdToMinimize, MinimizeReason.TASK_LIMIT) + } + if (launchingTaskId != null && taskRepository.isMinimizedTask(launchingTaskId)) { + addPendingUnminimizeTransition(t, displayId, launchingTaskId, unminimizeReason) } - if (taskIdToMinimize == null) { - val remoteTransitionHandler = OneShotRemoteHandler(mainExecutor, remoteTransition) - val t = transitions.startTransition(transitionType, wct, remoteTransitionHandler) - remoteTransitionHandler.setTransition(t) - exitImmersiveResult.asExit()?.runOnTransitionStart?.invoke(t) - return t - } - val remoteTransitionHandler = - DesktopWindowLimitRemoteHandler( - mainExecutor, - rootTaskDisplayAreaOrganizer, - remoteTransition, - taskIdToMinimize, - ) - val t = transitions.startTransition(transitionType, wct, remoteTransitionHandler) - remoteTransitionHandler.setTransition(t) - taskIdToMinimize.let { addPendingMinimizeTransition(t, it, MinimizeReason.TASK_LIMIT) } exitImmersiveResult.asExit()?.runOnTransitionStart?.invoke(t) return t } @@ -1731,7 +1755,10 @@ class DesktopTasksController( val requestedTaskInfo = shellTaskOrganizer.getRunningTaskInfo(requestedTaskId) if (requestedTaskInfo?.isFreeform == true) { // If requested task is an already open freeform task, just move it to front. - moveTaskToFront(requestedTaskId) + moveTaskToFront( + requestedTaskId, + unminimizeReason = UnminimizeReason.APP_HANDLE_MENU_BUTTON, + ) } else { moveBackgroundTaskToDesktop( requestedTaskId, @@ -1894,6 +1921,16 @@ class DesktopTasksController( if (useDesktopOverrideDensity()) { wct.setDensityDpi(task.token, DESKTOP_DENSITY_OVERRIDE) } + // The task that is launching might have been minimized before - in which case this is an + // unminimize action. + if (taskRepository.isMinimizedTask(task.taskId)) { + addPendingUnminimizeTransition( + transition, + task.displayId, + task.taskId, + UnminimizeReason.TASK_LAUNCH, + ) + } // Desktop Mode is showing and we're launching a new Task: // 1) Exit immersive if needed. desktopImmersiveController.exitImmersiveIfApplicable( @@ -2206,6 +2243,22 @@ class DesktopTasksController( } } + private fun addPendingUnminimizeTransition( + transition: IBinder, + displayId: Int, + taskIdToUnminimize: Int, + unminimizeReason: UnminimizeReason, + ) { + desktopTasksLimiter.ifPresent { + it.addPendingUnminimizeChange( + transition, + displayId = displayId, + taskId = taskIdToUnminimize, + unminimizeReason, + ) + } + } + private fun addPendingAppLaunchTransition( transition: IBinder, launchTaskId: Int, @@ -2883,9 +2936,13 @@ class DesktopTasksController( } } - override fun showDesktopApp(taskId: Int, remoteTransition: RemoteTransition?) { + override fun showDesktopApp( + taskId: Int, + remoteTransition: RemoteTransition?, + toFrontReason: DesktopTaskToFrontReason, + ) { executeRemoteCallWithTaskPermission(controller, "showDesktopApp") { c -> - c.moveTaskToFront(taskId, remoteTransition) + c.moveTaskToFront(taskId, remoteTransition, toFrontReason.toUnminimizeReason()) } } @@ -2980,6 +3037,15 @@ class DesktopTasksController( private val APP_HANDLE_DRAG_HOLD_CUJ_TIMEOUT_MS: Long = TimeUnit.SECONDS.toMillis(10L) private const val TAG = "DesktopTasksController" + + private fun DesktopTaskToFrontReason.toUnminimizeReason(): UnminimizeReason = + when (this) { + DesktopTaskToFrontReason.UNKNOWN -> UnminimizeReason.UNKNOWN + DesktopTaskToFrontReason.TASKBAR_TAP -> UnminimizeReason.TASKBAR_TAP + DesktopTaskToFrontReason.ALT_TAB -> UnminimizeReason.ALT_TAB + DesktopTaskToFrontReason.TASKBAR_MANAGE_WINDOW -> + UnminimizeReason.TASKBAR_MANAGE_WINDOW + } } /** Defines interface for classes that can listen to changes for task resize. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt index 204b39645248..81b136dd1569 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt @@ -31,6 +31,7 @@ import com.android.internal.jank.InteractionJankMonitor import com.android.internal.protolog.ProtoLog import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.sysui.UserChangeListener @@ -73,6 +74,7 @@ class DesktopTasksLimiter( val taskId: Int, var transitionInfo: TransitionInfo? = null, val minimizeReason: MinimizeReason? = null, + val unminimizeReason: UnminimizeReason? = null, ) /** @@ -83,20 +85,39 @@ class DesktopTasksLimiter( return minimizeTransitionObserver.getMinimizingTask(transition) } + /** + * Returns the task being unminimized in the given transition if that transition is a pending or + * active unminimize transition. + */ + fun getUnminimizingTask(transition: IBinder): TaskDetails? { + return minimizeTransitionObserver.getUnminimizingTask(transition) + } + // TODO(b/333018485): replace this observer when implementing the minimize-animation private inner class MinimizeTransitionObserver : TransitionObserver { private val pendingTransitionTokensAndTasks = mutableMapOf<IBinder, TaskDetails>() private val activeTransitionTokensAndTasks = mutableMapOf<IBinder, TaskDetails>() + private val pendingUnminimizeTransitionTokensAndTasks = mutableMapOf<IBinder, TaskDetails>() + private val activeUnminimizeTransitionTokensAndTasks = mutableMapOf<IBinder, TaskDetails>() fun addPendingTransitionToken(transition: IBinder, taskDetails: TaskDetails) { pendingTransitionTokensAndTasks[transition] = taskDetails } + fun addPendingUnminimizeTransitionToken(transition: IBinder, taskDetails: TaskDetails) { + pendingUnminimizeTransitionTokensAndTasks[transition] = taskDetails + } + fun getMinimizingTask(transition: IBinder): TaskDetails? { return pendingTransitionTokensAndTasks[transition] ?: activeTransitionTokensAndTasks[transition] } + fun getUnminimizingTask(transition: IBinder): TaskDetails? { + return pendingUnminimizeTransitionTokensAndTasks[transition] + ?: activeUnminimizeTransitionTokensAndTasks[transition] + } + override fun onTransitionReady( transition: IBinder, info: TransitionInfo, @@ -104,10 +125,11 @@ class DesktopTasksLimiter( finishTransaction: SurfaceControl.Transaction, ) { val taskRepository = desktopUserRepositories.current - handleMinimizeTransition(taskRepository, transition, info) + handleMinimizeTransitionReady(taskRepository, transition, info) + handleUnminimizeTransitionReady(transition) } - private fun handleMinimizeTransition( + private fun handleMinimizeTransitionReady( taskRepository: DesktopRepository, transition: IBinder, info: TransitionInfo, @@ -131,6 +153,12 @@ class DesktopTasksLimiter( this@DesktopTasksLimiter.minimizeTask(taskToMinimize.displayId, taskToMinimize.taskId) } + private fun handleUnminimizeTransitionReady(transition: IBinder) { + val taskToUnminimize = + pendingUnminimizeTransitionTokensAndTasks.remove(transition) ?: return + activeUnminimizeTransitionTokensAndTasks[transition] = taskToUnminimize + } + /** * Returns whether the Task [taskDetails] is being reordered to the back in the transition * [info], or is already invisible. @@ -173,6 +201,11 @@ class DesktopTasksLimiter( pendingTransitionTokensAndTasks.remove(merged)?.let { taskToTransfer -> pendingTransitionTokensAndTasks[playing] = taskToTransfer } + + activeUnminimizeTransitionTokensAndTasks.remove(merged) + pendingUnminimizeTransitionTokensAndTasks.remove(merged)?.let { taskToTransfer -> + pendingUnminimizeTransitionTokensAndTasks[playing] = taskToTransfer + } } override fun onTransitionFinished(transition: IBinder, aborted: Boolean) { @@ -184,6 +217,8 @@ class DesktopTasksLimiter( } } pendingTransitionTokensAndTasks.remove(transition) + activeUnminimizeTransitionTokensAndTasks.remove(transition) + pendingUnminimizeTransitionTokensAndTasks.remove(transition) } } @@ -277,6 +312,21 @@ class DesktopTasksLimiter( } /** + * Add a pending unminimize transition change to allow tracking unminimizing transitions / + * tasks. + */ + fun addPendingUnminimizeChange( + transition: IBinder, + displayId: Int, + taskId: Int, + unminimizeReason: UnminimizeReason, + ) = + minimizeTransitionObserver.addPendingUnminimizeTransitionToken( + transition, + TaskDetails(displayId, taskId, unminimizeReason = unminimizeReason), + ) + + /** * Returns the minimized task from the list of visible tasks ordered from front to back with the * new task placed in front of other tasks. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl index 54f031293486..ae4c2773215b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl @@ -22,6 +22,7 @@ import android.os.Bundle; import android.window.RemoteTransition; import com.android.wm.shell.desktopmode.IDesktopTaskListener; import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource; +import com.android.wm.shell.shared.desktopmode.DesktopTaskToFrontReason; /** * Interface that is exposed to remote callers to manipulate desktop mode features. @@ -38,12 +39,13 @@ interface IDesktopMode { void hideStashedDesktopApps(int displayId); /** - * 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); + * 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, + in DesktopTaskToFrontReason toFrontReason); /** Get count of visible desktop tasks on the given display */ int getVisibleTaskCount(int displayId); @@ -66,4 +68,4 @@ interface IDesktopMode { /** Start a transition when launching an intent in desktop mode */ void startLaunchIntentTransition(in Intent intent, in Bundle options, in int displayId); -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt index a9ebcef9bd98..2e9d6d95eebb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt @@ -49,6 +49,7 @@ import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterRe import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON @@ -795,6 +796,38 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { ) } + @Test + fun onTransitionReady_taskIsBeingUnminimized_logsTaskUnminimized() { + transitionObserver.isSessionActive = true + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM, id = 1)) + val taskInfo2 = createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2) + val transitionInfo = + TransitionInfoBuilder(TRANSIT_TO_FRONT, /* flags= */ 0) + .addChange(createChange(TRANSIT_TO_FRONT, taskInfo2)) + .build() + `when`(desktopTasksLimiter.getUnminimizingTask(any())) + .thenReturn( + DesktopTasksLimiter.TaskDetails( + taskInfo2.displayId, + taskInfo2.taskId, + unminimizeReason = UnminimizeReason.TASKBAR_MANAGE_WINDOW, + ) + ) + + callOnTransitionReady(transitionInfo) + + verify(desktopModeEventLogger, times(1)) + .logTaskAdded( + eq( + DEFAULT_TASK_UPDATE.copy( + instanceId = 2, + visibleTaskCount = 2, + unminimizeReason = UnminimizeReason.TASKBAR_MANAGE_WINDOW, + ) + ) + ) + } + /** Simulate calling the onTransitionReady() method */ private fun callOnTransitionReady(transitionInfo: TransitionInfo) { val transition = mock<IBinder>() 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 6c4f043a4f39..471565462340 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 @@ -104,6 +104,7 @@ import com.android.wm.shell.desktopmode.DesktopImmersiveController.ExitResult import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason import com.android.wm.shell.desktopmode.DesktopTasksController.DesktopModeEntryExitTransitionListener import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition import com.android.wm.shell.desktopmode.DesktopTasksController.TaskbarDesktopTaskListener @@ -1150,6 +1151,16 @@ class DesktopTasksControllerTest : ShellTestCase() { fun launchIntent_taskInDesktopMode_transitionStarted() { setUpLandscapeDisplay() val freeformTask = setUpFreeformTask() + whenever( + desktopMixedTransitionHandler.startLaunchTransition( + eq(TRANSIT_OPEN), + any(), + anyOrNull(), + anyOrNull(), + anyOrNull(), + ) + ) + .thenReturn(Binder()) controller.startLaunchIntentTransition( freeformTask.baseIntent, @@ -1672,6 +1683,16 @@ class DesktopTasksControllerTest : ShellTestCase() { fun moveTaskToFront_postsWctWithReorderOp() { val task1 = setUpFreeformTask() setUpFreeformTask() + whenever( + desktopMixedTransitionHandler.startLaunchTransition( + eq(TRANSIT_TO_FRONT), + any(), + eq(task1.taskId), + anyOrNull(), + anyOrNull(), + ) + ) + .thenReturn(Binder()) controller.moveTaskToFront(task1, remoteTransition = null) @@ -1704,6 +1725,46 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun moveTaskToFront_minimizedTask_marksTaskAsUnminimized() { + val transition = Binder() + val freeformTask = setUpFreeformTask() + taskRepository.minimizeTask(DEFAULT_DISPLAY, freeformTask.taskId) + whenever( + desktopMixedTransitionHandler.startLaunchTransition( + eq(TRANSIT_TO_FRONT), + any(), + eq(freeformTask.taskId), + anyOrNull(), + anyOrNull(), + ) + ) + .thenReturn(transition) + + controller.moveTaskToFront(freeformTask, unminimizeReason = UnminimizeReason.ALT_TAB) + + val task = desktopTasksLimiter.getUnminimizingTask(transition) + assertThat(task).isNotNull() + assertThat(task?.taskId).isEqualTo(freeformTask.taskId) + assertThat(task?.unminimizeReason).isEqualTo(UnminimizeReason.ALT_TAB) + } + + @Test + fun handleRequest_minimizedFreeformTask_marksTaskAsUnminimized() { + val transition = Binder() + // Create a visible task so we stay in Desktop Mode when minimizing task under test. + setUpFreeformTask().also { markTaskVisible(it) } + val freeformTask = setUpFreeformTask() + taskRepository.minimizeTask(DEFAULT_DISPLAY, freeformTask.taskId) + + controller.handleRequest(transition, createTransition(freeformTask, TRANSIT_OPEN)) + + val task = desktopTasksLimiter.getUnminimizingTask(transition) + assertThat(task).isNotNull() + assertThat(task?.taskId).isEqualTo(freeformTask.taskId) + assertThat(task?.unminimizeReason).isEqualTo(UnminimizeReason.TASK_LAUNCH) + } + + @Test fun moveTaskToFront_remoteTransition_usesOneshotHandler() { setUpHomeTask() val freeformTasks = List(MAX_TASK_LIMIT) { setUpFreeformTask() } @@ -1734,8 +1795,18 @@ class DesktopTasksControllerTest : ShellTestCase() { fun moveTaskToFront_backgroundTask_launchesTask() { val task = createTaskInfo(1) whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null) + whenever( + desktopMixedTransitionHandler.startLaunchTransition( + eq(TRANSIT_OPEN), + any(), + anyOrNull(), + anyOrNull(), + anyOrNull(), + ) + ) + .thenReturn(Binder()) - controller.moveTaskToFront(task.taskId, remoteTransition = null) + controller.moveTaskToFront(task.taskId, unminimizeReason = UnminimizeReason.UNKNOWN) val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN) assertThat(wct.hierarchyOps).hasSize(1) @@ -1758,7 +1829,7 @@ class DesktopTasksControllerTest : ShellTestCase() { ) .thenReturn(Binder()) - controller.moveTaskToFront(task.taskId, remoteTransition = null) + controller.moveTaskToFront(task.taskId, unminimizeReason = UnminimizeReason.UNKNOWN) val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN) assertThat(wct.hierarchyOps.size).isEqualTo(2) // launch + minimize @@ -4026,6 +4097,16 @@ class DesktopTasksControllerTest : ShellTestCase() { setUpLandscapeDisplay() val task = setUpFreeformTask() val taskToRequest = setUpFreeformTask() + whenever( + desktopMixedTransitionHandler.startLaunchTransition( + eq(TRANSIT_TO_FRONT), + any(), + eq(taskToRequest.taskId), + anyOrNull(), + anyOrNull(), + ) + ) + .thenReturn(Binder()) runOpenInstance(task, taskToRequest.taskId) verify(desktopMixedTransitionHandler) .startLaunchTransition(anyInt(), any(), anyInt(), anyOrNull(), anyOrNull()) @@ -4759,7 +4840,7 @@ class DesktopTasksControllerTest : ShellTestCase() { ) .thenReturn(transition) - controller.moveTaskToFront(task.taskId, remoteTransition = null) + controller.moveTaskToFront(task.taskId, unminimizeReason = UnminimizeReason.UNKNOWN) verify(mMockDesktopImmersiveController) .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId), any()) @@ -4791,7 +4872,7 @@ class DesktopTasksControllerTest : ShellTestCase() { ) .thenReturn(transition) - controller.moveTaskToFront(task.taskId, remoteTransition = null) + controller.moveTaskToFront(task.taskId, unminimizeReason = UnminimizeReason.UNKNOWN) verify(mMockDesktopImmersiveController) .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId), any()) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt index acfe1e9fd5a2..e85901bbd9d4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt @@ -45,6 +45,7 @@ import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer @@ -192,14 +193,10 @@ class DesktopTasksLimiterTest : ShellTestCase() { val task = setUpFreeformTask() markTaskHidden(task) - desktopTasksLimiter - .getTransitionObserver() - .onTransitionReady( - /* transition= */ Binder(), - TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), - /* startTransaction= */ StubTransaction(), - /* finishTransaction= */ StubTransaction(), - ) + callOnTransitionReady( + Binder(), + TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), + ) assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isFalse() } @@ -212,14 +209,10 @@ class DesktopTasksLimiterTest : ShellTestCase() { markTaskHidden(task) addPendingMinimizeChange(pendingTransition, taskId = task.taskId) - desktopTasksLimiter - .getTransitionObserver() - .onTransitionReady( - /* transition= */ taskTransition, - TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), - /* startTransaction= */ StubTransaction(), - /* finishTransaction= */ StubTransaction(), - ) + callOnTransitionReady( + taskTransition, + TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), + ) assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isFalse() } @@ -231,14 +224,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { markTaskVisible(task) addPendingMinimizeChange(transition, taskId = task.taskId) - desktopTasksLimiter - .getTransitionObserver() - .onTransitionReady( - transition, - TransitionInfoBuilder(TRANSIT_OPEN).build(), - /* startTransaction= */ StubTransaction(), - /* finishTransaction= */ StubTransaction(), - ) + callOnTransitionReady(transition, TransitionInfoBuilder(TRANSIT_OPEN).build()) assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isFalse() } @@ -250,14 +236,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { markTaskHidden(task) addPendingMinimizeChange(transition, taskId = task.taskId) - desktopTasksLimiter - .getTransitionObserver() - .onTransitionReady( - transition, - TransitionInfoBuilder(TRANSIT_OPEN).build(), - /* startTransaction= */ StubTransaction(), - /* finishTransaction= */ StubTransaction(), - ) + callOnTransitionReady(transition, TransitionInfoBuilder(TRANSIT_OPEN).build()) assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isTrue() } @@ -268,14 +247,10 @@ class DesktopTasksLimiterTest : ShellTestCase() { val task = setUpFreeformTask() addPendingMinimizeChange(transition, taskId = task.taskId) - desktopTasksLimiter - .getTransitionObserver() - .onTransitionReady( - transition, - TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), - /* startTransaction= */ StubTransaction(), - /* finishTransaction= */ StubTransaction(), - ) + callOnTransitionReady( + transition, + TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), + ) assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isTrue() } @@ -293,14 +268,10 @@ class DesktopTasksLimiterTest : ShellTestCase() { taskInfo = task setStartAbsBounds(bounds) } - desktopTasksLimiter - .getTransitionObserver() - .onTransitionReady( - transition, - TransitionInfo(TRANSIT_OPEN, TransitionInfo.FLAG_NONE).apply { addChange(change) }, - /* startTransaction= */ StubTransaction(), - /* finishTransaction= */ StubTransaction(), - ) + callOnTransitionReady( + transition, + TransitionInfo(TRANSIT_OPEN, TransitionInfo.FLAG_NONE).apply { addChange(change) }, + ) assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isTrue() assertThat(desktopTaskRepo.removeBoundsBeforeMinimize(taskId = task.taskId)) @@ -316,15 +287,14 @@ class DesktopTasksLimiterTest : ShellTestCase() { desktopTasksLimiter .getTransitionObserver() .onTransitionMerged(mergedTransition, newTransition) - desktopTasksLimiter .getTransitionObserver() - .onTransitionReady( - newTransition, - TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), - /* startTransaction= */ StubTransaction(), - /* finishTransaction= */ StubTransaction(), - ) + .onTransitionMerged(mergedTransition, newTransition) + + callOnTransitionReady( + newTransition, + TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), + ) assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isTrue() } @@ -521,14 +491,10 @@ class DesktopTasksLimiterTest : ShellTestCase() { val task = setUpFreeformTask() addPendingMinimizeChange(transition, taskId = task.taskId) - desktopTasksLimiter - .getTransitionObserver() - .onTransitionReady( - transition, - TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), - /* startTransaction= */ StubTransaction(), - /* finishTransaction= */ StubTransaction(), - ) + callOnTransitionReady( + transition, + TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), + ) desktopTasksLimiter.getTransitionObserver().onTransitionStarting(transition) @@ -549,14 +515,10 @@ class DesktopTasksLimiterTest : ShellTestCase() { val task = setUpFreeformTask() addPendingMinimizeChange(transition, taskId = task.taskId) - desktopTasksLimiter - .getTransitionObserver() - .onTransitionReady( - transition, - TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), - /* startTransaction= */ StubTransaction(), - /* finishTransaction= */ StubTransaction(), - ) + callOnTransitionReady( + transition, + TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), + ) desktopTasksLimiter.getTransitionObserver().onTransitionStarting(transition) @@ -578,14 +540,10 @@ class DesktopTasksLimiterTest : ShellTestCase() { val task = setUpFreeformTask() addPendingMinimizeChange(mergedTransition, taskId = task.taskId) - desktopTasksLimiter - .getTransitionObserver() - .onTransitionReady( - mergedTransition, - TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), - /* startTransaction= */ StubTransaction(), - /* finishTransaction= */ StubTransaction(), - ) + callOnTransitionReady( + mergedTransition, + TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), + ) desktopTasksLimiter.getTransitionObserver().onTransitionStarting(mergedTransition) @@ -634,14 +592,57 @@ class DesktopTasksLimiterTest : ShellTestCase() { val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build() - desktopTasksLimiter - .getTransitionObserver() - .onTransitionReady( - transition, - transitionInfo, - /* startTransaction= */ StubTransaction(), - /* finishTransaction= */ StubTransaction(), + callOnTransitionReady(transition, transitionInfo) + + assertThat(desktopTasksLimiter.getMinimizingTask(transition)) + .isEqualTo( + createTaskDetails( + taskId = task.taskId, + transitionInfo = transitionInfo, + minimizeReason = MinimizeReason.TASK_LIMIT, + ) + ) + } + + @Test + fun getUnminimizingTask_noPendingTransition_returnsNull() { + val transition = Binder() + + assertThat(desktopTasksLimiter.getMinimizingTask(transition)).isNull() + } + + @Test + fun getUnminimizingTask_pendingTaskTransition_returnsTask() { + val transition = Binder() + val task = setUpFreeformTask() + addPendingUnminimizeChange( + transition, + taskId = task.taskId, + unminimizeReason = UnminimizeReason.TASKBAR_TAP, + ) + + assertThat(desktopTasksLimiter.getUnminimizingTask(transition)) + .isEqualTo( + createTaskDetails( + taskId = task.taskId, + unminimizeReason = UnminimizeReason.TASKBAR_TAP, + ) ) + } + + @Test + fun getUnminimizingTask_activeTaskTransition_returnsTask() { + val transition = Binder() + val task = setUpFreeformTask() + addPendingMinimizeChange( + transition, + taskId = task.taskId, + minimizeReason = MinimizeReason.TASK_LIMIT, + ) + val transitionInfo = + TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build() + + callOnTransitionReady(transition, transitionInfo) assertThat(desktopTasksLimiter.getMinimizingTask(transition)) .isEqualTo( @@ -665,15 +666,46 @@ class DesktopTasksLimiterTest : ShellTestCase() { taskId: Int, transitionInfo: TransitionInfo? = null, minimizeReason: MinimizeReason? = null, - ) = DesktopTasksLimiter.TaskDetails(displayId, taskId, transitionInfo, minimizeReason) + unminimizeReason: UnminimizeReason? = null, + ) = + DesktopTasksLimiter.TaskDetails( + displayId, + taskId, + transitionInfo, + minimizeReason, + unminimizeReason, + ) + + private fun callOnTransitionReady( + transition: IBinder, + info: TransitionInfo, + startTransaction: SurfaceControl.Transaction = StubTransaction(), + finishTransaction: SurfaceControl.Transaction = StubTransaction(), + ) = + desktopTasksLimiter + .getTransitionObserver() + .onTransitionReady(transition, info, startTransaction, finishTransaction) - fun addPendingMinimizeChange( + private fun addPendingMinimizeChange( transition: IBinder, displayId: Int = DEFAULT_DISPLAY, taskId: Int, minimizeReason: MinimizeReason = MinimizeReason.TASK_LIMIT, ) = desktopTasksLimiter.addPendingMinimizeChange(transition, displayId, taskId, minimizeReason) + private fun addPendingUnminimizeChange( + transition: IBinder, + displayId: Int = DEFAULT_DISPLAY, + taskId: Int, + unminimizeReason: UnminimizeReason, + ) = + desktopTasksLimiter.addPendingUnminimizeChange( + transition, + displayId, + taskId, + unminimizeReason, + ) + private fun markTaskVisible(task: RunningTaskInfo) { desktopTaskRepo.updateTask(task.displayId, task.taskId, isVisible = true) } |