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.recents 18 19 import android.app.ActivityManager.RunningTaskInfo 20 import android.app.WindowConfiguration.WINDOWING_MODE_PINNED 21 import android.os.IBinder 22 import android.util.ArrayMap 23 import android.view.SurfaceControl 24 import android.view.WindowManager.TRANSIT_CHANGE 25 import android.window.DesktopModeFlags 26 import android.window.TransitionInfo 27 import com.android.internal.protolog.ProtoLog 28 import com.android.wm.shell.Flags.enableShellTopTaskTracking 29 import com.android.wm.shell.ShellTaskOrganizer 30 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_OBSERVER 31 import com.android.wm.shell.shared.TransitionUtil 32 import com.android.wm.shell.sysui.ShellCommandHandler 33 import com.android.wm.shell.sysui.ShellInit 34 import com.android.wm.shell.transition.Transitions 35 import dagger.Lazy 36 import java.io.PrintWriter 37 import java.util.StringJoiner 38 import java.util.concurrent.Executor 39 40 /** 41 * A [Transitions.TransitionObserver] that observes shell transitions, tracks the visible tasks 42 * and notifies listeners whenever the visible tasks change (at the start and end of a transition). 43 * 44 * This can be replaced once we have a generalized task repository tracking visible tasks. 45 */ 46 class TaskStackTransitionObserver( 47 shellInit: ShellInit, 48 private val shellTaskOrganizer: Lazy<ShellTaskOrganizer>, 49 private val shellCommandHandler: ShellCommandHandler, 50 private val transitions: Lazy<Transitions>, 51 ) : Transitions.TransitionObserver, ShellTaskOrganizer.TaskVanishedListener { 52 53 // List of currently visible tasks sorted in z-order from top-most to bottom-most, only used 54 // when Flags.enableShellTopTaskTracking() is enabled. 55 private var visibleTasks: MutableList<RunningTaskInfo> = mutableListOf() 56 private val pendingCloseTasks: MutableList<RunningTaskInfo> = mutableListOf() 57 // Set of listeners to notify when the visible tasks change 58 private val taskStackTransitionObserverListeners = 59 ArrayMap<TaskStackTransitionObserverListener, Executor>() 60 // Used to filter out leaf-tasks 61 private val leafTaskFilter: TransitionUtil.LeafTaskFilter = TransitionUtil.LeafTaskFilter() 62 63 init { 64 shellInit.addInitCallback(::onInit, this) 65 } 66 67 fun onInit() { 68 shellTaskOrganizer.get().addTaskVanishedListener(this) 69 shellCommandHandler.addDumpCallback(::dump, this) 70 transitions.get().registerObserver(this) 71 72 // TODO(346588978): We need to update the running tasks once the ShellTaskOrganizer is 73 // registered since there is no existing transition (yet) corresponding for the already 74 // visible tasks 75 } 76 77 /** 78 * This method handles transition ready when only 79 * DesktopModeFlags.ENABLE_TASK_STACK_OBSERVER_IN_SHELL is set. 80 */ 81 private fun onDesktopOnlyFlagTransitionReady(info: TransitionInfo) { 82 for (change in info.changes) { 83 if (change.flags and TransitionInfo.FLAG_IS_WALLPAPER != 0) { 84 continue 85 } 86 87 val taskInfo = change.taskInfo 88 if (taskInfo == null || taskInfo.taskId == -1) { 89 continue 90 } 91 92 // Find the first task that is opening, this should be the one at the front after 93 // the transition 94 if (TransitionUtil.isOpeningType(change.mode)) { 95 notifyOnTaskMovedToFront(taskInfo) 96 break 97 } else if (change.mode == TRANSIT_CHANGE) { 98 notifyOnTaskChanged(taskInfo) 99 } 100 } 101 } 102 103 /** 104 * This method handles transition ready when Flags.enableShellTopTaskTracking() is set. 105 */ 106 private fun onShellTopTaskTrackerFlagTransitionReady(info: TransitionInfo) { 107 ProtoLog.v(WM_SHELL_TASK_OBSERVER, "Transition ready: %d", info.debugId) 108 109 // Filter out non-leaf tasks (we will likely need them later, but visible task tracking 110 // is currently used only for visible leaf tasks) 111 val changesReversed = mutableListOf<TransitionInfo.Change>() 112 for (change in info.changes) { 113 if (!leafTaskFilter.test(change)) { 114 // Not a leaf task 115 continue 116 } 117 changesReversed.add(0, change) 118 } 119 120 // We iterate the change list in reverse order because changes are sorted top to bottom and 121 // we want to update the lists such that the top most tasks are inserted at the front last 122 var notifyChanges = false 123 for (change in changesReversed) { 124 val taskInfo = change.taskInfo 125 if (taskInfo == null || taskInfo.taskId == -1) { 126 // Not a valid task 127 continue 128 } 129 130 if (TransitionUtil.isClosingMode(change.mode)) { 131 ProtoLog.v(WM_SHELL_TASK_OBSERVER, "\tClosing task=%d", taskInfo.taskId) 132 133 // Closing task's visibilities are not committed until after the transition 134 // completes, so track such tasks so that we can notify on finish 135 if (!pendingCloseTasks.any { it.taskId == taskInfo.taskId }) { 136 pendingCloseTasks.add(taskInfo) 137 } 138 } else if (TransitionUtil.isOpeningMode(change.mode) 139 || TransitionUtil.isOrderOnly(change)) { 140 ProtoLog.v(WM_SHELL_TASK_OBSERVER, "\tOpening task=%d", taskInfo.taskId) 141 142 // Remove from pending close tasks list if it's being opened again 143 pendingCloseTasks.removeIf { it.taskId == taskInfo.taskId } 144 // Move the task to the front of the visible tasks list 145 visibleTasks.removeIf { it.taskId == taskInfo.taskId } 146 visibleTasks.add(0, taskInfo) 147 notifyChanges = true 148 } 149 } 150 151 // TODO(346588978): We should verify the task list has actually changed before notifying 152 // (ie. starting an activity that's already top-most would result in no visible change) 153 if (notifyChanges) { 154 updateVisibleTasksList("transition-start") 155 } 156 } 157 158 private fun updateVisibleTasksList(reason: String) { 159 // This simply constructs a list of visible tasks, where the always-on-top tasks are moved 160 // to the front of the list in-order, to ensure that they match the visible z order 161 val orderedVisibleTasks = mutableListOf<RunningTaskInfo>() 162 var numAlwaysOnTop = 0 163 for (info in visibleTasks) { 164 if (info.windowingMode == WINDOWING_MODE_PINNED 165 || info.configuration.windowConfiguration.isAlwaysOnTop) { 166 orderedVisibleTasks.add(numAlwaysOnTop, info) 167 numAlwaysOnTop++ 168 } else { 169 orderedVisibleTasks.add(info) 170 } 171 } 172 visibleTasks = orderedVisibleTasks 173 174 dumpVisibleTasks(reason) 175 notifyVisibleTasksChanged(visibleTasks) 176 } 177 178 override fun onTransitionReady( 179 transition: IBinder, 180 info: TransitionInfo, 181 startTransaction: SurfaceControl.Transaction, 182 finishTransaction: SurfaceControl.Transaction 183 ) { 184 if (enableShellTopTaskTracking()) { 185 onShellTopTaskTrackerFlagTransitionReady(info) 186 } else if (DesktopModeFlags.ENABLE_TASK_STACK_OBSERVER_IN_SHELL.isTrue) { 187 onDesktopOnlyFlagTransitionReady(info) 188 } 189 } 190 191 override fun onTransitionStarting(transition: IBinder) {} 192 193 override fun onTransitionMerged(merged: IBinder, playing: IBinder) {} 194 195 override fun onTransitionFinished(transition: IBinder, aborted: Boolean) { 196 if (!enableShellTopTaskTracking()) { 197 return 198 } 199 200 if (pendingCloseTasks.isNotEmpty()) { 201 // Update the visible task list based on the pending close tasks 202 for (change in pendingCloseTasks) { 203 visibleTasks.removeIf { 204 it.taskId == change.taskId 205 } 206 } 207 updateVisibleTasksList("transition-finished") 208 } 209 } 210 211 override fun onTaskVanished(taskInfo: RunningTaskInfo?) { 212 if (!enableShellTopTaskTracking()) { 213 return 214 } 215 ProtoLog.v(WM_SHELL_TASK_OBSERVER, "Task vanished: task=%d", taskInfo?.taskId) 216 pendingCloseTasks.removeIf { it.taskId == taskInfo?.taskId } 217 if (visibleTasks.any { it.taskId == taskInfo?.taskId }) { 218 visibleTasks.removeIf { it.taskId == taskInfo?.taskId } 219 updateVisibleTasksList("task-vanished") 220 } 221 } 222 223 /** 224 * Adds a new task stack observer. 225 */ 226 fun addTaskStackTransitionObserverListener( 227 taskStackTransitionObserverListener: TaskStackTransitionObserverListener, 228 executor: Executor 229 ) { 230 taskStackTransitionObserverListeners[taskStackTransitionObserverListener] = executor 231 } 232 233 /** 234 * Removes an existing task stack observer. 235 */ 236 fun removeTaskStackTransitionObserverListener( 237 taskStackTransitionObserverListener: TaskStackTransitionObserverListener 238 ) { 239 taskStackTransitionObserverListeners.remove(taskStackTransitionObserverListener) 240 } 241 242 private fun notifyOnTaskMovedToFront(taskInfo: RunningTaskInfo) { 243 if (enableShellTopTaskTracking()) { 244 return 245 } 246 taskStackTransitionObserverListeners.forEach { (listener, executor) -> 247 executor.execute { listener.onTaskMovedToFrontThroughTransition(taskInfo) } 248 } 249 } 250 251 private fun notifyOnTaskChanged(taskInfo: RunningTaskInfo) { 252 if (enableShellTopTaskTracking()) { 253 return 254 } 255 taskStackTransitionObserverListeners.forEach { (listener, executor) -> 256 executor.execute { listener.onTaskChangedThroughTransition(taskInfo) } 257 } 258 } 259 260 private fun notifyVisibleTasksChanged(visibleTasks: List<RunningTaskInfo>) { 261 taskStackTransitionObserverListeners.forEach { (listener, executor) -> 262 executor.execute { listener.onVisibleTasksChanged(visibleTasks) } 263 } 264 } 265 266 fun dump(pw: PrintWriter, prefix: String) { 267 pw.println("${prefix}$TAG:") 268 269 if (visibleTasks.isEmpty()) { 270 pw.println("$prefix visibleTasks=[]") 271 } else { 272 val stringJoiner = StringJoiner(",\n\t", "[\n\t", "\n]") 273 visibleTasks.forEach { 274 stringJoiner.add("id=${it.taskId} cmp=${it.baseIntent.component}") 275 } 276 pw.println("$prefix visibleTasks=$stringJoiner") 277 } 278 } 279 280 /** Dumps the set of visible tasks to protolog */ 281 private fun dumpVisibleTasks(reason: String) { 282 if (!WM_SHELL_TASK_OBSERVER.isEnabled) { 283 return 284 } 285 ProtoLog.v(WM_SHELL_TASK_OBSERVER, "\tVisible tasks (%s)", reason) 286 for (task in visibleTasks) { 287 ProtoLog.v(WM_SHELL_TASK_OBSERVER, "\t\ttaskId=%d package=%s", task.taskId, 288 task.baseIntent.component?.packageName) 289 } 290 } 291 292 /** Listener to use to get updates regarding task stack from this observer */ 293 interface TaskStackTransitionObserverListener { 294 /** Called when a task is moved to front. */ 295 fun onTaskMovedToFrontThroughTransition(taskInfo: RunningTaskInfo) {} 296 /** Called when the set of visible tasks have changed. */ 297 fun onVisibleTasksChanged(visibleTasks: List<RunningTaskInfo>) {} 298 /** Called when a task info has changed. */ 299 fun onTaskChangedThroughTransition(taskInfo: RunningTaskInfo) {} 300 } 301 302 companion object { 303 const val TAG = "TaskStackTransitionObserver" 304 } 305 } 306