1 /* 2 * 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.WindowConfiguration.WINDOWING_MODE_FREEFORM 21 import android.window.DesktopModeFlags 22 import com.android.internal.protolog.ProtoLog 23 import com.android.wm.shell.freeform.TaskChangeListener 24 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE 25 26 /** Manages tasks handling specific to Android Desktop Mode. */ 27 class DesktopTaskChangeListener(private val desktopUserRepositories: DesktopUserRepositories) : 28 TaskChangeListener { 29 onTaskOpeningnull30 override fun onTaskOpening(taskInfo: RunningTaskInfo) { 31 logD("onTaskOpening for taskId=%d, displayId=%d", taskInfo.taskId, taskInfo.displayId) 32 val desktopRepository: DesktopRepository = 33 desktopUserRepositories.getProfile(taskInfo.userId) 34 if (!isFreeformTask(taskInfo) && desktopRepository.isActiveTask(taskInfo.taskId)) { 35 desktopRepository.removeTask(taskInfo.displayId, taskInfo.taskId) 36 return 37 } 38 if (isFreeformTask(taskInfo) && !desktopRepository.isActiveTask(taskInfo.taskId)) { 39 desktopRepository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible) 40 } 41 } 42 onTaskChangingnull43 override fun onTaskChanging(taskInfo: RunningTaskInfo) { 44 logD("onTaskChanging for taskId=%d, displayId=%d", taskInfo.taskId, taskInfo.displayId) 45 val desktopRepository: DesktopRepository = 46 desktopUserRepositories.getProfile(taskInfo.userId) 47 // TODO: b/394281403 - with multiple desks, it's possible to have a non-freeform task 48 // inside a desk, so this should be decoupled from windowing mode. 49 // Also, changes in/out of desks are handled by the [DesksTransitionObserver], which has 50 // more specific information about the desk involved in the transition, which might be 51 // more accurate than assuming it's always the default/active desk in the display, as this 52 // method does. 53 // Case 1: When the task change is from a task in the desktop repository which is now 54 // fullscreen, 55 // remove the task from the desktop repository since it is no longer a freeform task. 56 if (!isFreeformTask(taskInfo) && desktopRepository.isActiveTask(taskInfo.taskId)) { 57 desktopRepository.removeTask(taskInfo.displayId, taskInfo.taskId) 58 } else if (isFreeformTask(taskInfo)) { 59 // If the task is already active in the repository, then moves task to the front, 60 // else adds the task. 61 desktopRepository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible) 62 } 63 } 64 65 // This method should only be used for scenarios where the task info changes are not propagated 66 // to 67 // [DesktopTaskChangeListener#onTaskChanging] via [TransitionsObserver]. 68 // Any changes to [DesktopRepository] from this method should be made carefully to minimize risk 69 // of race conditions and possible duplications with [onTaskChanging]. onNonTransitionTaskChangingnull70 override fun onNonTransitionTaskChanging(taskInfo: RunningTaskInfo) { 71 // TODO: b/367268953 - Propagate usages from FreeformTaskListener to this method. 72 logD( 73 "onNonTransitionTaskChanging for taskId=%d, displayId=%d", 74 taskInfo.taskId, 75 taskInfo.displayId, 76 ) 77 } 78 onTaskMovingToFrontnull79 override fun onTaskMovingToFront(taskInfo: RunningTaskInfo) { 80 logD("onTaskMovingToFront for taskId=%d, displayId=%d", taskInfo.taskId, taskInfo.displayId) 81 val desktopRepository: DesktopRepository = 82 desktopUserRepositories.getProfile(taskInfo.userId) 83 // When the task change is from a task in the desktop repository which is now fullscreen, 84 // remove the task from the desktop repository since it is no longer a freeform task. 85 if (!isFreeformTask(taskInfo) && desktopRepository.isActiveTask(taskInfo.taskId)) { 86 desktopRepository.removeTask(taskInfo.displayId, taskInfo.taskId) 87 } 88 if (isFreeformTask(taskInfo)) { 89 // If the task is already active in the repository, then it only moves the task to the 90 // front. 91 desktopRepository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible) 92 } 93 } 94 onTaskMovingToBacknull95 override fun onTaskMovingToBack(taskInfo: RunningTaskInfo) { 96 val desktopRepository: DesktopRepository = 97 desktopUserRepositories.getProfile(taskInfo.userId) 98 if (!desktopRepository.isActiveTask(taskInfo.taskId)) return 99 logD("onTaskMovingToBack for taskId=%d, displayId=%d", taskInfo.taskId, taskInfo.displayId) 100 desktopRepository.updateTask(taskInfo.displayId, taskInfo.taskId, /* isVisible= */ false) 101 } 102 onTaskClosingnull103 override fun onTaskClosing(taskInfo: RunningTaskInfo) { 104 logD("onTaskClosing for taskId=%d, displayId=%d", taskInfo.taskId, taskInfo.displayId) 105 val desktopRepository: DesktopRepository = 106 desktopUserRepositories.getProfile(taskInfo.userId) 107 if (!desktopRepository.isActiveTask(taskInfo.taskId)) return 108 // TODO: b/370038902 - Handle Activity#finishAndRemoveTask. 109 if ( 110 !DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue || 111 desktopRepository.isClosingTask(taskInfo.taskId) 112 ) { 113 // A task that's vanishing should be removed: 114 // - If it's closed by the X button which means it's marked as a closing task. 115 desktopRepository.removeClosingTask(taskInfo.taskId) 116 desktopRepository.removeTask(taskInfo.displayId, taskInfo.taskId) 117 } else { 118 desktopRepository.updateTask(taskInfo.displayId, taskInfo.taskId, isVisible = false) 119 desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId) 120 } 121 } 122 isFreeformTasknull123 private fun isFreeformTask(taskInfo: RunningTaskInfo): Boolean = 124 taskInfo.windowingMode == WINDOWING_MODE_FREEFORM 125 126 private fun logD(msg: String, vararg arguments: Any?) { 127 ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) 128 } 129 130 companion object { 131 private const val TAG = "DesktopTaskChangeListener" 132 } 133 } 134