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