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 package com.android.launcher3.taskbar 17 18 import android.app.ActivityManager.RunningTaskInfo 19 import android.app.WindowConfiguration 20 import androidx.annotation.VisibleForTesting 21 import com.android.launcher3.Flags.enableRecentsInTaskbar 22 import com.android.launcher3.model.data.AppInfo 23 import com.android.launcher3.model.data.ItemInfo 24 import com.android.launcher3.model.data.WorkspaceItemInfo 25 import com.android.launcher3.statehandlers.DesktopVisibilityController 26 import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController 27 import com.android.quickstep.RecentsModel 28 import com.android.window.flags.Flags.enableDesktopWindowingMode 29 import com.android.window.flags.Flags.enableDesktopWindowingTaskbarRunningApps 30 import java.io.PrintWriter 31 32 /** 33 * Provides recent apps functionality, when the Taskbar Recent Apps section is enabled. Behavior: 34 * - When in Fullscreen mode: show the N most recent Tasks 35 * - When in Desktop Mode: show the currently running (open) Tasks 36 */ 37 class TaskbarRecentAppsController( 38 private val recentsModel: RecentsModel, 39 // Pass a provider here instead of the actual DesktopVisibilityController instance since that 40 // instance might not be available when this constructor is called. 41 private val desktopVisibilityControllerProvider: () -> DesktopVisibilityController?, 42 ) : LoggableTaskbarController { 43 44 // TODO(b/335401172): unify DesktopMode checks in Launcher. 45 val canShowRunningApps = 46 enableDesktopWindowingMode() && enableDesktopWindowingTaskbarRunningApps() 47 48 // TODO(b/343532825): Add a setting to disable Recents even when the flag is on. 49 var isEnabled: Boolean = enableRecentsInTaskbar() || canShowRunningApps 50 @VisibleForTesting 51 set(isEnabledFromTest){ 52 field = isEnabledFromTest 53 } 54 55 // Initialized in init. 56 private lateinit var controllers: TaskbarControllers 57 58 private var apps: Array<AppInfo>? = null 59 private var allRunningDesktopAppInfos: List<AppInfo>? = null 60 private var allMinimizedDesktopAppInfos: List<AppInfo>? = null 61 62 private val desktopVisibilityController: DesktopVisibilityController? 63 get() = desktopVisibilityControllerProvider() 64 65 private val isInDesktopMode: Boolean 66 get() = desktopVisibilityController?.areDesktopTasksVisible() ?: false 67 68 val runningApps: Set<String> 69 get() { 70 if (!isEnabled || !isInDesktopMode) { 71 return emptySet() 72 } 73 return allRunningDesktopAppInfos?.mapNotNull { it.targetPackage }?.toSet() ?: emptySet() 74 } 75 76 val minimizedApps: Set<String> 77 get() { 78 if (!isInDesktopMode) { 79 return emptySet() 80 } 81 return allMinimizedDesktopAppInfos?.mapNotNull { it.targetPackage }?.toSet() 82 ?: emptySet() 83 } 84 85 fun init(taskbarControllers: TaskbarControllers) { 86 controllers = taskbarControllers 87 } 88 89 fun onDestroy() { 90 apps = null 91 } 92 93 /** Stores the current [AppInfo] instances, no-op except in desktop environment. */ 94 fun setApps(apps: Array<AppInfo>?) { 95 this.apps = apps 96 } 97 98 /** Called to update hotseatItems, in order to de-dupe them from Recent/Running tasks later. */ 99 // TODO(next CL): add new section of Tasks instead of changing Hotseat items 100 fun updateHotseatItemInfos(hotseatItems: Array<ItemInfo?>): Array<ItemInfo?> { 101 if (!isEnabled || !isInDesktopMode) { 102 return hotseatItems 103 } 104 val newHotseatItemInfos = 105 hotseatItems 106 .filterNotNull() 107 // Ignore predicted apps - we show running apps instead 108 .filter { itemInfo -> !itemInfo.isPredictedItem } 109 .toMutableList() 110 val runningDesktopAppInfos = 111 allRunningDesktopAppInfos?.let { 112 getRunningDesktopAppInfosExceptHotseatApps(it, newHotseatItemInfos.toList()) 113 } 114 if (runningDesktopAppInfos != null) { 115 newHotseatItemInfos.addAll(runningDesktopAppInfos) 116 } 117 return newHotseatItemInfos.toTypedArray() 118 } 119 120 private fun getRunningDesktopAppInfosExceptHotseatApps( 121 allRunningDesktopAppInfos: List<AppInfo>, 122 hotseatItems: List<ItemInfo> 123 ): List<ItemInfo> { 124 val hotseatPackages = hotseatItems.map { it.targetPackage } 125 return allRunningDesktopAppInfos 126 .filter { appInfo -> !hotseatPackages.contains(appInfo.targetPackage) } 127 .map { WorkspaceItemInfo(it) } 128 } 129 130 private fun getDesktopRunningTasks(): List<RunningTaskInfo> = 131 recentsModel.runningTasks.filter { taskInfo: RunningTaskInfo -> 132 taskInfo.windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM 133 } 134 135 // TODO(b/335398876) fetch app icons from Tasks instead of AppInfos 136 private fun getAppInfosFromRunningTasks(tasks: List<RunningTaskInfo>): List<AppInfo> { 137 // Early return if apps is empty, since we then have no AppInfo to compare to 138 if (apps == null) { 139 return emptyList() 140 } 141 val packageNames = tasks.map { it.realActivity?.packageName }.distinct().filterNotNull() 142 return packageNames 143 .map { packageName -> apps?.find { app -> packageName == app.targetPackage } } 144 .filterNotNull() 145 } 146 147 /** Called to update the list of currently running apps, no-op except in desktop environment. */ 148 fun updateRunningApps() { 149 if (!isEnabled || !isInDesktopMode) { 150 return controllers.taskbarViewController.commitRunningAppsToUI() 151 } 152 val runningTasks = getDesktopRunningTasks() 153 val runningAppInfo = getAppInfosFromRunningTasks(runningTasks) 154 allRunningDesktopAppInfos = runningAppInfo 155 updateMinimizedApps(runningTasks, runningAppInfo) 156 controllers.taskbarViewController.commitRunningAppsToUI() 157 } 158 159 private fun updateMinimizedApps( 160 runningTasks: List<RunningTaskInfo>, 161 runningAppInfo: List<AppInfo>, 162 ) { 163 val allRunningAppTasks = 164 runningAppInfo 165 .mapNotNull { appInfo -> appInfo.targetPackage?.let { appInfo to it } } 166 .associate { (appInfo, targetPackage) -> 167 appInfo to 168 runningTasks 169 .filter { it.realActivity?.packageName == targetPackage } 170 .map { it.taskId } 171 } 172 val minimizedTaskIds = runningTasks.associate { it.taskId to !it.isVisible } 173 allMinimizedDesktopAppInfos = 174 allRunningAppTasks 175 .filterValues { taskIds -> taskIds.all { minimizedTaskIds[it] ?: false } } 176 .keys 177 .toList() 178 } 179 180 override fun dumpLogs(prefix: String, pw: PrintWriter) { 181 pw.println("$prefix TaskbarRecentAppsController:") 182 pw.println("$prefix\tisEnabled=$isEnabled") 183 pw.println("$prefix\tcanShowRunningApps=$canShowRunningApps") 184 // TODO(next CL): add more logs 185 } 186 } 187