1 /* <lambda>null2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.wm.shell.desktopmode 18 19 import android.app.ActivityManager.RunningTaskInfo 20 import android.os.IBinder 21 import android.view.SurfaceControl 22 import android.view.WindowManager.TRANSIT_TO_BACK 23 import android.window.TransitionInfo 24 import android.window.WindowContainerTransaction 25 import androidx.annotation.VisibleForTesting 26 import com.android.wm.shell.ShellTaskOrganizer 27 import com.android.wm.shell.protolog.ShellProtoLogGroup 28 import com.android.wm.shell.shared.DesktopModeStatus 29 import com.android.wm.shell.transition.Transitions 30 import com.android.wm.shell.transition.Transitions.TransitionObserver 31 import com.android.wm.shell.util.KtProtoLog 32 33 /** 34 * Limits the number of tasks shown in Desktop Mode. 35 * 36 * This class should only be used if 37 * [com.android.window.flags.Flags.enableDesktopWindowingTaskLimit()] is true. 38 */ 39 class DesktopTasksLimiter ( 40 transitions: Transitions, 41 private val taskRepository: DesktopModeTaskRepository, 42 private val shellTaskOrganizer: ShellTaskOrganizer, 43 ) { 44 private val minimizeTransitionObserver = MinimizeTransitionObserver() 45 46 init { 47 transitions.registerObserver(minimizeTransitionObserver) 48 } 49 50 private data class TaskDetails (val displayId: Int, val taskId: Int) 51 52 // TODO(b/333018485): replace this observer when implementing the minimize-animation 53 private inner class MinimizeTransitionObserver : TransitionObserver { 54 private val mPendingTransitionTokensAndTasks = mutableMapOf<IBinder, TaskDetails>() 55 56 fun addPendingTransitionToken(transition: IBinder, taskDetails: TaskDetails) { 57 mPendingTransitionTokensAndTasks[transition] = taskDetails 58 } 59 60 override fun onTransitionReady( 61 transition: IBinder, 62 info: TransitionInfo, 63 startTransaction: SurfaceControl.Transaction, 64 finishTransaction: SurfaceControl.Transaction 65 ) { 66 val taskToMinimize = mPendingTransitionTokensAndTasks.remove(transition) ?: return 67 68 if (!taskRepository.isActiveTask(taskToMinimize.taskId)) return 69 70 if (!isTaskReorderedToBackOrInvisible(info, taskToMinimize)) { 71 KtProtoLog.v( 72 ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, 73 "DesktopTasksLimiter: task %d is not reordered to back nor invis", 74 taskToMinimize.taskId) 75 return 76 } 77 this@DesktopTasksLimiter.markTaskMinimized( 78 taskToMinimize.displayId, taskToMinimize.taskId) 79 } 80 81 /** 82 * Returns whether the given Task is being reordered to the back in the given transition, or 83 * is already invisible. 84 * 85 * <p> This check can be used to double-check that a task was indeed minimized before 86 * marking it as such. 87 */ 88 private fun isTaskReorderedToBackOrInvisible( 89 info: TransitionInfo, 90 taskDetails: TaskDetails 91 ): Boolean { 92 val taskChange = info.changes.find { change -> 93 change.taskInfo?.taskId == taskDetails.taskId } 94 if (taskChange == null) { 95 return !taskRepository.isVisibleTask(taskDetails.taskId) 96 } 97 return taskChange.mode == TRANSIT_TO_BACK 98 } 99 100 override fun onTransitionStarting(transition: IBinder) {} 101 102 override fun onTransitionMerged(merged: IBinder, playing: IBinder) { 103 mPendingTransitionTokensAndTasks.remove(merged)?.let { taskToTransfer -> 104 mPendingTransitionTokensAndTasks[playing] = taskToTransfer 105 } 106 } 107 108 override fun onTransitionFinished(transition: IBinder, aborted: Boolean) { 109 KtProtoLog.v( 110 ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, 111 "DesktopTasksLimiter: transition %s finished", transition) 112 mPendingTransitionTokensAndTasks.remove(transition) 113 } 114 } 115 116 /** 117 * Mark a task as minimized, this should only be done after the corresponding transition has 118 * finished so we don't minimize the task if the transition fails. 119 */ 120 private fun markTaskMinimized(displayId: Int, taskId: Int) { 121 KtProtoLog.v( 122 ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, 123 "DesktopTasksLimiter: marking %d as minimized", taskId) 124 taskRepository.minimizeTask(displayId, taskId) 125 } 126 127 /** 128 * Add a minimize-transition to [wct] if adding [newFrontTaskInfo] brings us over the task 129 * limit. 130 * 131 * @param transition the transition that the minimize-transition will be appended to, or null if 132 * the transition will be started later. 133 * @return the ID of the minimized task, or null if no task is being minimized. 134 */ 135 fun addAndGetMinimizeTaskChangesIfNeeded( 136 displayId: Int, 137 wct: WindowContainerTransaction, 138 newFrontTaskInfo: RunningTaskInfo, 139 ): RunningTaskInfo? { 140 KtProtoLog.v( 141 ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, 142 "DesktopTasksLimiter: addMinimizeBackTaskChangesIfNeeded, newFrontTask=%d", 143 newFrontTaskInfo.taskId) 144 val newTaskListOrderedFrontToBack = createOrderedTaskListWithGivenTaskInFront( 145 taskRepository.getActiveNonMinimizedTasksOrderedFrontToBack(displayId), 146 newFrontTaskInfo.taskId) 147 val taskToMinimize = getTaskToMinimizeIfNeeded(newTaskListOrderedFrontToBack) 148 if (taskToMinimize != null) { 149 wct.reorder(taskToMinimize.token, false /* onTop */) 150 return taskToMinimize 151 } 152 return null 153 } 154 155 /** 156 * Add a pending minimize transition change, to update the list of minimized apps once the 157 * transition goes through. 158 */ 159 fun addPendingMinimizeChange(transition: IBinder, displayId: Int, taskId: Int) { 160 minimizeTransitionObserver.addPendingTransitionToken( 161 transition, TaskDetails(displayId, taskId)) 162 } 163 164 /** 165 * Returns the maximum number of tasks that should ever be displayed at the same time in Desktop 166 * Mode. 167 */ 168 fun getMaxTaskLimit(): Int = DesktopModeStatus.getMaxTaskLimit() 169 170 /** 171 * Returns the Task to minimize given 1. a list of visible tasks ordered from front to back and 172 * 2. a new task placed in front of all the others. 173 */ 174 fun getTaskToMinimizeIfNeeded( 175 visibleFreeformTaskIdsOrderedFrontToBack: List<Int>, 176 newTaskIdInFront: Int 177 ): RunningTaskInfo? { 178 return getTaskToMinimizeIfNeeded( 179 createOrderedTaskListWithGivenTaskInFront( 180 visibleFreeformTaskIdsOrderedFrontToBack, newTaskIdInFront)) 181 } 182 183 /** Returns the Task to minimize given a list of visible tasks ordered from front to back. */ 184 fun getTaskToMinimizeIfNeeded( 185 visibleFreeformTaskIdsOrderedFrontToBack: List<Int> 186 ): RunningTaskInfo? { 187 if (visibleFreeformTaskIdsOrderedFrontToBack.size <= getMaxTaskLimit()) { 188 KtProtoLog.v( 189 ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, 190 "DesktopTasksLimiter: no need to minimize; tasks below limit") 191 // No need to minimize anything 192 return null 193 } 194 val taskToMinimize = 195 shellTaskOrganizer.getRunningTaskInfo( 196 visibleFreeformTaskIdsOrderedFrontToBack.last()) 197 if (taskToMinimize == null) { 198 KtProtoLog.e( 199 ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, 200 "DesktopTasksLimiter: taskToMinimize == null") 201 return null 202 } 203 return taskToMinimize 204 } 205 206 private fun createOrderedTaskListWithGivenTaskInFront( 207 existingTaskIdsOrderedFrontToBack: List<Int>, 208 newTaskId: Int 209 ): List<Int> { 210 return listOf(newTaskId) + 211 existingTaskIdsOrderedFrontToBack.filter { taskId -> taskId != newTaskId } 212 } 213 214 @VisibleForTesting 215 fun getTransitionObserver(): TransitionObserver { 216 return minimizeTransitionObserver 217 } 218 }