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.app.ActivityTaskManager.INVALID_TASK_ID 21 import android.app.TaskInfo 22 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM 23 import android.content.Context 24 import android.os.IBinder 25 import android.os.SystemProperties 26 import android.os.Trace 27 import android.util.SparseArray 28 import android.view.SurfaceControl 29 import android.view.WindowManager 30 import android.view.WindowManager.TRANSIT_OPEN 31 import android.window.TransitionInfo 32 import android.window.TransitionInfo.FLAG_MOVED_TO_TOP 33 import androidx.annotation.VisibleForTesting 34 import androidx.core.util.containsKey 35 import androidx.core.util.forEach 36 import androidx.core.util.isEmpty 37 import androidx.core.util.isNotEmpty 38 import androidx.core.util.plus 39 import androidx.core.util.putAll 40 import com.android.internal.protolog.ProtoLog 41 import com.android.wm.shell.ShellTaskOrganizer 42 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason 43 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason 44 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.FocusReason 45 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason 46 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate 47 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason 48 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP 49 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW 50 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON 51 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT 52 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON 53 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT 54 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG 55 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE 56 import com.android.wm.shell.shared.TransitionUtil 57 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus 58 import com.android.wm.shell.sysui.ShellInit 59 import com.android.wm.shell.transition.Transitions 60 import java.util.Optional 61 import kotlin.jvm.optionals.getOrNull 62 63 /** 64 * A [Transitions.TransitionObserver] that observes transitions and the proposed changes to log 65 * appropriate desktop mode session log events. This observes transitions related to desktop mode 66 * and other transitions that originate both within and outside shell. 67 */ 68 class DesktopModeLoggerTransitionObserver( 69 context: Context, 70 shellInit: ShellInit, 71 private val transitions: Transitions, 72 private val desktopModeEventLogger: DesktopModeEventLogger, 73 private val desktopTasksLimiter: Optional<DesktopTasksLimiter>, 74 private val shellTaskOrganizer: ShellTaskOrganizer, 75 ) : Transitions.TransitionObserver { 76 77 init { 78 if (DesktopModeStatus.canEnterDesktopMode(context)) { 79 shellInit.addInitCallback(this::onInit, this) 80 } 81 } 82 83 // A sparse array of visible freeform tasks and taskInfos 84 private val visibleFreeformTaskInfos: SparseArray<TaskInfo> = SparseArray() 85 86 // Caching the taskInfos to handle canceled recents animations, if we identify that the recents 87 // animation was cancelled, we restore these tasks to calculate the post-Transition state 88 private val tasksSavedForRecents: SparseArray<TaskInfo> = SparseArray() 89 90 // Caching whether the previous transition was exit to overview. 91 private var wasPreviousTransitionExitToOverview: Boolean = false 92 93 // Caching whether the previous transition was exit due to screen off. This helps check if a 94 // following enter reason could be Screen On 95 private var wasPreviousTransitionExitByScreenOff: Boolean = false 96 97 private var focusedFreeformTask: TaskInfo? = null 98 99 @VisibleForTesting var isSessionActive: Boolean = false 100 101 fun onInit() { 102 transitions.registerObserver(this) 103 SystemProperties.set( 104 VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY, 105 VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY_DEFAULT_VALUE, 106 ) 107 desktopModeEventLogger.logTaskInfoStateInit() 108 } 109 110 override fun onTransitionReady( 111 transition: IBinder, 112 info: TransitionInfo, 113 startTransaction: SurfaceControl.Transaction, 114 finishTransaction: SurfaceControl.Transaction, 115 ) { 116 // this was a new recents animation 117 if (info.isExitToRecentsTransition() && tasksSavedForRecents.isEmpty()) { 118 ProtoLog.v( 119 WM_SHELL_DESKTOP_MODE, 120 "DesktopModeLogger: Recents animation running, saving tasks for later", 121 ) 122 // TODO (b/326391303) - avoid logging session exit if we can identify a cancelled 123 // recents animation 124 125 // when recents animation is running, all freeform tasks are sent TO_BACK temporarily 126 // if the user ends up at home, we need to update the visible freeform tasks 127 // if the user cancels the animation, the subsequent transition is NONE 128 // if the user opens a new task, the subsequent transition is OPEN with flag 129 tasksSavedForRecents.putAll(visibleFreeformTaskInfos) 130 } 131 132 // figure out what the new state of freeform tasks would be post transition 133 var postTransitionVisibleFreeformTasks = getPostTransitionVisibleFreeformTaskInfos(info) 134 135 // A canceled recents animation is followed by a TRANSIT_NONE transition with no flags, if 136 // that's the case, we might have accidentally logged a session exit and would need to 137 // revaluate again. Add all the tasks back. 138 // This will start a new desktop mode session. 139 if ( 140 info.type == WindowManager.TRANSIT_NONE && 141 info.flags == 0 && 142 tasksSavedForRecents.isNotEmpty() 143 ) { 144 ProtoLog.v( 145 WM_SHELL_DESKTOP_MODE, 146 "DesktopModeLogger: Canceled recents animation, restoring tasks", 147 ) 148 // restore saved tasks in the updated set and clear for next use 149 postTransitionVisibleFreeformTasks += tasksSavedForRecents 150 tasksSavedForRecents.clear() 151 } 152 153 // identify if we need to log any changes and update the state of visible freeform tasks 154 identifyLogEventAndUpdateState( 155 transition = transition, 156 transitionInfo = info, 157 preTransitionVisibleFreeformTasks = visibleFreeformTaskInfos, 158 postTransitionVisibleFreeformTasks = postTransitionVisibleFreeformTasks, 159 newFocusedFreeformTask = getNewFocusedFreeformTask(info), 160 ) 161 wasPreviousTransitionExitToOverview = info.isExitToRecentsTransition() 162 } 163 164 override fun onTransitionStarting(transition: IBinder) {} 165 166 override fun onTransitionMerged(merged: IBinder, playing: IBinder) {} 167 168 override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {} 169 170 fun onTaskVanished(taskInfo: RunningTaskInfo) { 171 // At this point the task should have been cleared up due to transition. If it's not yet 172 // cleared up, it might be one of the edge cases where transitions don't give the correct 173 // signal. 174 if (visibleFreeformTaskInfos.containsKey(taskInfo.taskId)) { 175 val postTransitionFreeformTasks: SparseArray<TaskInfo> = SparseArray() 176 postTransitionFreeformTasks.putAll(visibleFreeformTaskInfos) 177 postTransitionFreeformTasks.remove(taskInfo.taskId) 178 ProtoLog.v( 179 WM_SHELL_DESKTOP_MODE, 180 "DesktopModeLogger: processing tasks after task vanished %s", 181 postTransitionFreeformTasks.size(), 182 ) 183 identifyLogEventAndUpdateState( 184 transition = null, 185 transitionInfo = null, 186 preTransitionVisibleFreeformTasks = visibleFreeformTaskInfos, 187 postTransitionVisibleFreeformTasks = postTransitionFreeformTasks, 188 newFocusedFreeformTask = null, 189 ) 190 } 191 } 192 193 // Returns null if there was no change in focused task 194 private fun getNewFocusedFreeformTask(info: TransitionInfo): TaskInfo? { 195 val freeformWindowChanges = 196 info.changes 197 .filter { it.taskInfo != null && it.requireTaskInfo().taskId != INVALID_TASK_ID } 198 .filter { it.requireTaskInfo().isFreeformWindow() } 199 return freeformWindowChanges 200 .findLast { change -> 201 change.hasFlags(FLAG_MOVED_TO_TOP) || change.mode == TRANSIT_OPEN 202 } 203 ?.taskInfo 204 } 205 206 private fun getPostTransitionVisibleFreeformTaskInfos( 207 info: TransitionInfo 208 ): SparseArray<TaskInfo> { 209 // device is sleeping, so no task will be visible anymore 210 if (info.type == WindowManager.TRANSIT_SLEEP) { 211 return SparseArray() 212 } 213 214 // filter changes involving freeform tasks or tasks that were cached in previous state 215 val changesToFreeformWindows = 216 info.changes 217 .filter { it.taskInfo != null && it.requireTaskInfo().taskId != INVALID_TASK_ID } 218 .filter { 219 it.requireTaskInfo().isFreeformWindow() || 220 visibleFreeformTaskInfos.containsKey(it.requireTaskInfo().taskId) 221 } 222 223 val postTransitionFreeformTasks: SparseArray<TaskInfo> = SparseArray() 224 // start off by adding all existing tasks 225 postTransitionFreeformTasks.putAll(visibleFreeformTaskInfos) 226 227 // the combined set of taskInfos we are interested in this transition change 228 for (change in changesToFreeformWindows) { 229 val taskInfo = change.requireTaskInfo() 230 231 // check if this task existed as freeform window in previous cached state and it's now 232 // changing window modes 233 if ( 234 visibleFreeformTaskInfos.containsKey(taskInfo.taskId) && 235 visibleFreeformTaskInfos.get(taskInfo.taskId).isFreeformWindow() && 236 !taskInfo.isFreeformWindow() 237 ) { 238 postTransitionFreeformTasks.remove(taskInfo.taskId) 239 // no need to evaluate new visibility of this task, since it's no longer a freeform 240 // window 241 continue 242 } 243 244 // check if the task is visible after this change, otherwise remove it 245 if (isTaskVisibleAfterChange(change)) { 246 postTransitionFreeformTasks.put(taskInfo.taskId, taskInfo) 247 } else { 248 postTransitionFreeformTasks.remove(taskInfo.taskId) 249 } 250 } 251 252 ProtoLog.v( 253 WM_SHELL_DESKTOP_MODE, 254 "DesktopModeLogger: taskInfo map after processing changes %s", 255 postTransitionFreeformTasks.size(), 256 ) 257 258 return postTransitionFreeformTasks 259 } 260 261 /** 262 * Look at the [TransitionInfo.Change] and figure out if this task will be visible after this 263 * change is processed 264 */ 265 private fun isTaskVisibleAfterChange(change: TransitionInfo.Change): Boolean = 266 when { 267 TransitionUtil.isOpeningType(change.mode) -> true 268 TransitionUtil.isClosingType(change.mode) -> false 269 // change mode TRANSIT_CHANGE is only for visible to visible transitions 270 change.mode == WindowManager.TRANSIT_CHANGE -> true 271 else -> false 272 } 273 274 /** 275 * Log the appropriate log event based on the new state of TasksInfos and previously cached 276 * state and update it 277 */ 278 private fun identifyLogEventAndUpdateState( 279 transition: IBinder?, 280 transitionInfo: TransitionInfo?, 281 preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>, 282 postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>, 283 newFocusedFreeformTask: TaskInfo?, 284 ) { 285 if ( 286 postTransitionVisibleFreeformTasks.isEmpty() && 287 preTransitionVisibleFreeformTasks.isNotEmpty() && 288 isSessionActive 289 ) { 290 // Sessions is finishing, log task updates followed by an exit event 291 identifyAndLogTaskUpdates( 292 transition, 293 transitionInfo, 294 preTransitionVisibleFreeformTasks, 295 postTransitionVisibleFreeformTasks, 296 newFocusedFreeformTask, 297 ) 298 299 desktopModeEventLogger.logSessionExit(getExitReason(transitionInfo)) 300 isSessionActive = false 301 } else if ( 302 postTransitionVisibleFreeformTasks.isNotEmpty() && 303 preTransitionVisibleFreeformTasks.isEmpty() && 304 !isSessionActive 305 ) { 306 // Session is starting, log enter event followed by task updates 307 isSessionActive = true 308 desktopModeEventLogger.logSessionEnter(getEnterReason(transitionInfo)) 309 310 identifyAndLogTaskUpdates( 311 transition, 312 transitionInfo, 313 preTransitionVisibleFreeformTasks, 314 postTransitionVisibleFreeformTasks, 315 newFocusedFreeformTask, 316 ) 317 } else if (isSessionActive) { 318 // Session is neither starting, nor finishing, log task updates if there are any 319 identifyAndLogTaskUpdates( 320 transition, 321 transitionInfo, 322 preTransitionVisibleFreeformTasks, 323 postTransitionVisibleFreeformTasks, 324 newFocusedFreeformTask, 325 ) 326 } 327 328 // update the state to the new version 329 visibleFreeformTaskInfos.clear() 330 visibleFreeformTaskInfos.putAll(postTransitionVisibleFreeformTasks) 331 focusedFreeformTask = newFocusedFreeformTask 332 } 333 334 /** Compare the old and new state of taskInfos and identify and log the changes */ 335 private fun identifyAndLogTaskUpdates( 336 transition: IBinder?, 337 transitionInfo: TransitionInfo?, 338 preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>, 339 postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>, 340 newFocusedFreeformTask: TaskInfo?, 341 ) { 342 postTransitionVisibleFreeformTasks.forEach { taskId, taskInfo -> 343 val focusChangedReason = getFocusChangedReason(taskId, newFocusedFreeformTask) 344 val currentTaskUpdate = 345 buildTaskUpdateForTask( 346 taskInfo, 347 postTransitionVisibleFreeformTasks.size(), 348 focusChangedReason = focusChangedReason, 349 ) 350 val previousTaskInfo = preTransitionVisibleFreeformTasks[taskId] 351 when { 352 // new tasks added 353 previousTaskInfo == null -> { 354 // The current task is now visible while before it wasn't - this might be the 355 // result of an unminimize action. 356 val unminimizeReason = getUnminimizeReason(transition, taskInfo) 357 desktopModeEventLogger.logTaskAdded( 358 currentTaskUpdate.copy(unminimizeReason = unminimizeReason) 359 ) 360 Trace.setCounter( 361 Trace.TRACE_TAG_WINDOW_MANAGER, 362 VISIBLE_TASKS_COUNTER_NAME, 363 postTransitionVisibleFreeformTasks.size().toLong(), 364 ) 365 SystemProperties.set( 366 VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY, 367 postTransitionVisibleFreeformTasks.size().toString(), 368 ) 369 } 370 focusChangedReason != null -> 371 desktopModeEventLogger.logTaskInfoChanged(currentTaskUpdate) 372 // old tasks that were resized or repositioned 373 // TODO(b/347935387): Log changes only once they are stable. 374 buildTaskUpdateForTask( 375 previousTaskInfo, 376 postTransitionVisibleFreeformTasks.size(), 377 focusChangedReason = focusChangedReason, 378 ) != currentTaskUpdate -> 379 desktopModeEventLogger.logTaskInfoChanged(currentTaskUpdate) 380 } 381 } 382 383 // find old tasks that were removed 384 preTransitionVisibleFreeformTasks.forEach { taskId, taskInfo -> 385 if (!postTransitionVisibleFreeformTasks.containsKey(taskId)) { 386 // The task is no longer visible, it might have been minimized, get the minimize 387 // reason (if any) 388 val minimizeReason = getMinimizeReason(transition, transitionInfo, taskInfo) 389 val taskUpdate = 390 buildTaskUpdateForTask( 391 taskInfo, 392 postTransitionVisibleFreeformTasks.size(), 393 minimizeReason, 394 ) 395 desktopModeEventLogger.logTaskRemoved(taskUpdate) 396 Trace.setCounter( 397 Trace.TRACE_TAG_WINDOW_MANAGER, 398 VISIBLE_TASKS_COUNTER_NAME, 399 postTransitionVisibleFreeformTasks.size().toLong(), 400 ) 401 SystemProperties.set( 402 VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY, 403 postTransitionVisibleFreeformTasks.size().toString(), 404 ) 405 } 406 } 407 } 408 409 private fun getMinimizeReason( 410 transition: IBinder?, 411 transitionInfo: TransitionInfo?, 412 taskInfo: TaskInfo, 413 ): MinimizeReason? { 414 if (transitionInfo?.type == Transitions.TRANSIT_MINIMIZE) { 415 return MinimizeReason.MINIMIZE_BUTTON 416 } 417 val minimizingTask = 418 transition?.let { desktopTasksLimiter.getOrNull()?.getMinimizingTask(transition) } 419 if (minimizingTask?.taskId == taskInfo.taskId) { 420 return minimizingTask.minimizeReason 421 } 422 return null 423 } 424 425 private fun getUnminimizeReason(transition: IBinder?, taskInfo: TaskInfo): UnminimizeReason? { 426 val unminimizingTask = 427 transition?.let { desktopTasksLimiter.getOrNull()?.getUnminimizingTask(transition) } 428 if (unminimizingTask?.taskId == taskInfo.taskId) { 429 return unminimizingTask.unminimizeReason 430 } 431 return null 432 } 433 434 private fun getFocusChangedReason( 435 taskId: Int, 436 newFocusedFreeformTask: TaskInfo?, 437 ): FocusReason? { 438 val newFocusedTask = newFocusedFreeformTask ?: return null 439 if (taskId != newFocusedTask.taskId) return null 440 return if (newFocusedTask != focusedFreeformTask) FocusReason.UNKNOWN else null 441 } 442 443 private fun buildTaskUpdateForTask( 444 taskInfo: TaskInfo, 445 visibleTasks: Int, 446 minimizeReason: MinimizeReason? = null, 447 unminimizeReason: UnminimizeReason? = null, 448 focusChangedReason: FocusReason? = null, 449 ): TaskUpdate { 450 val screenBounds = taskInfo.configuration.windowConfiguration.bounds 451 val positionInParent = taskInfo.positionInParent 452 // We can't both minimize and unminimize the same task in one go. 453 assert(minimizeReason == null || unminimizeReason == null) 454 return TaskUpdate( 455 instanceId = taskInfo.taskId, 456 uid = taskInfo.effectiveUid, 457 taskHeight = screenBounds.height(), 458 taskWidth = screenBounds.width(), 459 taskX = positionInParent.x, 460 taskY = positionInParent.y, 461 visibleTaskCount = visibleTasks, 462 minimizeReason = minimizeReason, 463 unminimizeReason = unminimizeReason, 464 focusReason = focusChangedReason, 465 ) 466 } 467 468 /** Get [EnterReason] for this session enter */ 469 private fun getEnterReason(transitionInfo: TransitionInfo?): EnterReason { 470 val enterReason = 471 when { 472 transitionInfo?.type == WindowManager.TRANSIT_WAKE 473 // If there is a screen lock, desktop window entry is after dismissing keyguard 474 || 475 (transitionInfo?.type == WindowManager.TRANSIT_TO_BACK && 476 wasPreviousTransitionExitByScreenOff) -> EnterReason.SCREEN_ON 477 transitionInfo?.type == TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP -> 478 EnterReason.APP_HANDLE_DRAG 479 transitionInfo?.type == TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON -> 480 EnterReason.APP_HANDLE_MENU_BUTTON 481 transitionInfo?.type == TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW -> 482 EnterReason.APP_FROM_OVERVIEW 483 transitionInfo?.type == TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT -> 484 EnterReason.KEYBOARD_SHORTCUT_ENTER 485 // NOTE: the below condition also applies for EnterReason quickswitch 486 transitionInfo?.type == WindowManager.TRANSIT_TO_FRONT -> EnterReason.OVERVIEW 487 // Enter desktop mode from cancelled recents has no transition. Enter is detected on 488 // the 489 // next transition involving freeform windows. 490 // TODO(b/346564416): Modify logging for cancelled recents once it transition is 491 // changed. Also see how to account to time difference between actual enter time 492 // and 493 // time of this log. Also account for the missed session when exit happens just 494 // after 495 // a cancelled recents. 496 wasPreviousTransitionExitToOverview -> EnterReason.OVERVIEW 497 transitionInfo?.type == WindowManager.TRANSIT_OPEN -> 498 EnterReason.APP_FREEFORM_INTENT 499 else -> { 500 ProtoLog.w( 501 WM_SHELL_DESKTOP_MODE, 502 "Unknown enter reason for transition type: %s", 503 transitionInfo?.type, 504 ) 505 EnterReason.UNKNOWN_ENTER 506 } 507 } 508 wasPreviousTransitionExitByScreenOff = false 509 return enterReason 510 } 511 512 /** Get [ExitReason] for this session exit */ 513 private fun getExitReason(transitionInfo: TransitionInfo?): ExitReason = 514 when { 515 transitionInfo?.type == WindowManager.TRANSIT_SLEEP -> { 516 wasPreviousTransitionExitByScreenOff = true 517 ExitReason.SCREEN_OFF 518 } 519 // TODO(b/384490301): differentiate back gesture / button exit from clicking the close 520 // button located in the window top corner. 521 transitionInfo?.type == WindowManager.TRANSIT_TO_BACK -> ExitReason.TASK_MOVED_TO_BACK 522 transitionInfo?.type == WindowManager.TRANSIT_CLOSE -> ExitReason.TASK_FINISHED 523 transitionInfo?.type == TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG -> ExitReason.DRAG_TO_EXIT 524 transitionInfo?.type == TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON -> 525 ExitReason.APP_HANDLE_MENU_BUTTON_EXIT 526 527 transitionInfo?.type == TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT -> 528 ExitReason.KEYBOARD_SHORTCUT_EXIT 529 530 transitionInfo?.isExitToRecentsTransition() == true -> 531 ExitReason.RETURN_HOME_OR_OVERVIEW 532 transitionInfo?.type == Transitions.TRANSIT_MINIMIZE -> ExitReason.TASK_MINIMIZED 533 else -> { 534 ProtoLog.w( 535 WM_SHELL_DESKTOP_MODE, 536 "Unknown exit reason for transition type: %s", 537 transitionInfo?.type, 538 ) 539 ExitReason.UNKNOWN_EXIT 540 } 541 } 542 543 /** Adds tasks to the saved copy of freeform taskId, taskInfo. Only used for testing. */ 544 @VisibleForTesting 545 fun addTaskInfosToCachedMap(taskInfo: TaskInfo) { 546 visibleFreeformTaskInfos.set(taskInfo.taskId, taskInfo) 547 } 548 549 /** Sets the focused task - only used for testing. */ 550 @VisibleForTesting 551 fun setFocusedTaskForTesting(taskInfo: TaskInfo) { 552 focusedFreeformTask = taskInfo 553 } 554 555 private fun TransitionInfo.Change.requireTaskInfo(): RunningTaskInfo = 556 this.taskInfo ?: throw IllegalStateException("Expected TaskInfo in the Change") 557 558 private fun TaskInfo.isFreeformWindow(): Boolean = this.windowingMode == WINDOWING_MODE_FREEFORM 559 560 private fun TransitionInfo.isExitToRecentsTransition(): Boolean = 561 this.type == WindowManager.TRANSIT_TO_FRONT && 562 this.flags == WindowManager.TRANSIT_FLAG_IS_RECENTS 563 564 companion object { 565 @VisibleForTesting const val VISIBLE_TASKS_COUNTER_NAME = "desktop_mode_visible_tasks" 566 @VisibleForTesting 567 const val VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY = 568 "debug.tracing." + VISIBLE_TASKS_COUNTER_NAME 569 const val VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY_DEFAULT_VALUE = "0" 570 } 571 } 572