• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 package com.android.wm.shell.windowdecor.common
17 
18 import android.annotation.DimenRes
19 import android.app.ActivityManager.RunningTaskInfo
20 import android.content.Context
21 import android.content.pm.ActivityInfo
22 import android.content.pm.PackageManager
23 import android.graphics.Bitmap
24 import android.os.LocaleList
25 import android.os.UserHandle
26 import androidx.tracing.Trace
27 import com.android.internal.annotations.VisibleForTesting
28 import com.android.launcher3.icons.BaseIconFactory
29 import com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT
30 import com.android.launcher3.icons.IconProvider
31 import com.android.wm.shell.R
32 import com.android.wm.shell.common.UserProfileContexts
33 import com.android.wm.shell.shared.annotations.ShellBackgroundThread
34 import com.android.wm.shell.sysui.ShellCommandHandler
35 import com.android.wm.shell.sysui.ShellController
36 import com.android.wm.shell.sysui.ShellInit
37 import com.android.wm.shell.sysui.UserChangeListener
38 import java.io.PrintWriter
39 import java.util.concurrent.ConcurrentHashMap
40 
41 /**
42  * A utility and cache for window decoration UI resources.
43  */
44 class WindowDecorTaskResourceLoader(
45     shellInit: ShellInit,
46     private val shellController: ShellController,
47     private val shellCommandHandler: ShellCommandHandler,
48     private val userProfilesContexts: UserProfileContexts,
49     private val iconProvider: IconProvider,
50     private val headerIconFactory: BaseIconFactory,
51     private val veilIconFactory: BaseIconFactory,
52 ) {
53     constructor(
54         context: Context,
55         shellInit: ShellInit,
56         shellController: ShellController,
57         shellCommandHandler: ShellCommandHandler,
58         userProfileContexts: UserProfileContexts,
59     ) : this(
60         shellInit,
61         shellController,
62         shellCommandHandler,
63         userProfileContexts,
64         IconProvider(context),
65         headerIconFactory = context.createIconFactory(R.dimen.desktop_mode_caption_icon_radius),
66         veilIconFactory = context.createIconFactory(R.dimen.desktop_mode_resize_veil_icon_size),
67     )
68 
69     /**
70      * A map of task -> resources to prevent unnecessary binder calls and resource loading
71      * when multiple window decorations need the same resources, for example, the app name or icon
72      * used in the header and menu.
73      */
74     @VisibleForTesting
75     val taskToResourceCache = ConcurrentHashMap<Int, AppResources>()
76     /**
77      * Keeps track of existing tasks with a window decoration. Useful to verify that requests to
78      * get resources occur within the lifecycle of a window decoration, otherwise it'd be possible
79      * to load a tasks resources into memory without a future signal to clean up the resource.
80      * See [onWindowDecorClosed].
81      */
82     private val existingTasks = mutableSetOf<Int>()
83 
84     /**
85      * A map of task -> localeList to keep track of the language of app name that's currently
86      * cached in |taskToResourceCache|.
87      */
88     @VisibleForTesting
89     val localeListOnCache = ConcurrentHashMap<Int, LocaleList>()
90 
91     init {
92         shellInit.addInitCallback(this::onInit, this)
93     }
94 
onInitnull95     private fun onInit() {
96         shellCommandHandler.addDumpCallback(this::dump, this)
97         shellController.addUserChangeListener(object : UserChangeListener {
98             override fun onUserChanged(newUserId: Int, userContext: Context) {
99                 // No need to hold on to resources for tasks of another profile.
100                 taskToResourceCache.clear()
101             }
102         })
103     }
104 
105     /** Returns the user readable name for this task. */
106     @ShellBackgroundThread
getNamenull107     fun getName(taskInfo: RunningTaskInfo): CharSequence {
108         checkWindowDecorExists(taskInfo)
109         val cachedResources = taskToResourceCache[taskInfo.taskId]
110         val localeListActiveOnCacheTime = localeListOnCache[taskInfo.taskId]
111         if (cachedResources != null &&
112             taskInfo.getConfiguration().getLocales().equals(localeListActiveOnCacheTime)) {
113             return cachedResources.appName
114         }
115         val resources = loadAppResources(taskInfo)
116         taskToResourceCache[taskInfo.taskId] = resources
117         localeListOnCache[taskInfo.taskId] = taskInfo.getConfiguration().getLocales()
118         return resources.appName
119     }
120 
121     /** Returns the icon for use by the app header and menus for this task. */
122     @ShellBackgroundThread
getHeaderIconnull123     fun getHeaderIcon(taskInfo: RunningTaskInfo): Bitmap {
124         checkWindowDecorExists(taskInfo)
125         val cachedResources = taskToResourceCache[taskInfo.taskId]
126         if (cachedResources != null) {
127             return cachedResources.appIcon
128         }
129         val resources = loadAppResources(taskInfo)
130         taskToResourceCache[taskInfo.taskId] = resources
131         localeListOnCache[taskInfo.taskId] = taskInfo.getConfiguration().getLocales()
132         return resources.appIcon
133     }
134 
135     /** Returns the icon for use by the resize veil for this task. */
136     @ShellBackgroundThread
getVeilIconnull137     fun getVeilIcon(taskInfo: RunningTaskInfo): Bitmap {
138         checkWindowDecorExists(taskInfo)
139         val cachedResources = taskToResourceCache[taskInfo.taskId]
140         if (cachedResources != null) {
141             return cachedResources.veilIcon
142         }
143         val resources = loadAppResources(taskInfo)
144         taskToResourceCache[taskInfo.taskId] = resources
145         localeListOnCache[taskInfo.taskId] = taskInfo.getConfiguration().getLocales()
146         return resources.veilIcon
147     }
148 
149     /** Called when a window decoration for this task is created. */
onWindowDecorCreatednull150     fun onWindowDecorCreated(taskInfo: RunningTaskInfo) {
151         existingTasks.add(taskInfo.taskId)
152     }
153 
154     /** Called when a window decoration for this task is closed. */
onWindowDecorClosednull155     fun onWindowDecorClosed(taskInfo: RunningTaskInfo) {
156         existingTasks.remove(taskInfo.taskId)
157         taskToResourceCache.remove(taskInfo.taskId)
158         localeListOnCache.remove(taskInfo.taskId)
159     }
160 
checkWindowDecorExistsnull161     private fun checkWindowDecorExists(taskInfo: RunningTaskInfo) {
162         check(existingTasks.contains(taskInfo.taskId)) {
163             "Attempt to obtain resource for non-existent decoration"
164         }
165     }
166 
loadAppResourcesnull167     private fun loadAppResources(taskInfo: RunningTaskInfo): AppResources {
168         Trace.beginSection("$TAG#loadAppResources")
169         try {
170             val pm = userProfilesContexts.getOrCreate(taskInfo.userId).packageManager
171             val activityInfo = getActivityInfo(taskInfo, pm)
172             val appName = pm.getApplicationLabel(activityInfo.applicationInfo)
173             val appIconDrawable = iconProvider.getIcon(activityInfo)
174             val badgedAppIconDrawable = pm.getUserBadgedIcon(appIconDrawable, taskInfo.userHandle())
175             val appIcon = headerIconFactory.createIconBitmap(badgedAppIconDrawable, /* scale= */ 1f)
176             val veilIcon = veilIconFactory.createScaledBitmap(appIconDrawable, MODE_DEFAULT)
177             return AppResources(appName = appName, appIcon = appIcon, veilIcon = veilIcon)
178         } finally {
179             Trace.endSection()
180         }
181     }
182 
getActivityInfonull183     private fun getActivityInfo(taskInfo: RunningTaskInfo, pm: PackageManager): ActivityInfo {
184         return pm.getActivityInfo(taskInfo.component(), /* flags= */ 0)
185     }
186 
componentnull187     private fun RunningTaskInfo.component() = baseIntent.component!!
188 
189     private fun RunningTaskInfo.userHandle() = UserHandle.of(userId)
190 
191     data class AppResources(val appName: CharSequence, val appIcon: Bitmap, val veilIcon: Bitmap)
192 
193     private fun dump(pw: PrintWriter, prefix: String) {
194         val innerPrefix = "$prefix  "
195         pw.println("${prefix}$TAG")
196         pw.println(innerPrefix + "appResourceCache=$taskToResourceCache")
197         pw.println(innerPrefix + "existingTasks=$existingTasks")
198     }
199 
200     companion object {
201         private const val TAG = "AppResourceProvider"
202     }
203 }
204 
205 /** Creates an icon factory with the provided [dimensions]. */
Contextnull206 fun Context.createIconFactory(@DimenRes dimensions: Int): BaseIconFactory {
207     val densityDpi = resources.displayMetrics.densityDpi
208     val iconSize = resources.getDimensionPixelSize(dimensions)
209     return BaseIconFactory(this, densityDpi, iconSize)
210 }
211