• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.systemui.statusbar.notification.row.icon
18 
19 import android.annotation.WorkerThread
20 import android.app.ActivityManager
21 import android.app.Flags
22 import android.app.Flags.notificationsRedesignThemedAppIcons
23 import android.content.Context
24 import android.content.pm.PackageManager.NameNotFoundException
25 import android.graphics.Color
26 import android.graphics.drawable.ColorDrawable
27 import android.graphics.drawable.Drawable
28 import android.os.UserHandle
29 import android.util.Log
30 import com.android.internal.R
31 import com.android.launcher3.icons.BaseIconFactory
32 import com.android.launcher3.icons.BaseIconFactory.IconOptions
33 import com.android.launcher3.icons.BitmapInfo
34 import com.android.launcher3.icons.mono.MonoIconThemeController
35 import com.android.launcher3.util.UserIconInfo
36 import com.android.systemui.Dumpable
37 import com.android.systemui.dagger.SysUISingleton
38 import com.android.systemui.dump.DumpManager
39 import com.android.systemui.shade.ShadeDisplayAware
40 import com.android.systemui.statusbar.notification.collection.NotifCollectionCache
41 import com.android.systemui.util.asIndenting
42 import com.android.systemui.util.withIncreasedIndent
43 import dagger.Module
44 import dagger.Provides
45 import java.io.PrintWriter
46 import javax.inject.Inject
47 import javax.inject.Provider
48 
49 /** A provider used to cache and fetch app icons used by notifications. */
50 interface AppIconProvider {
51     /**
52      * Loads the icon corresponding to [packageName] into cache, or fetches it from there if already
53      * present. This should only be called from the background.
54      */
55     @Throws(NameNotFoundException::class)
56     @WorkerThread
57     fun getOrFetchAppIcon(
58         packageName: String,
59         context: Context,
60         withWorkProfileBadge: Boolean = false,
61         themed: Boolean = false,
62     ): Drawable
63 
64     /**
65      * Mark all the entries in the cache that are NOT in [wantedPackages] to be cleared. If they're
66      * still not needed on the next call of this method (made after a timeout of 1s, in case they
67      * happen more frequently than that), they will be purged. This can be done from any thread.
68      */
69     fun purgeCache(wantedPackages: Collection<String>)
70 }
71 
72 @SysUISingleton
73 class AppIconProviderImpl
74 @Inject
75 constructor(@ShadeDisplayAware private val sysuiContext: Context, dumpManager: DumpManager) :
76     AppIconProvider, Dumpable {
77     init {
78         dumpManager.registerNormalDumpable(TAG, this)
79     }
80 
81     private class NotificationIcons(context: Context?, fillResIconDpi: Int, iconBitmapSize: Int) :
82         BaseIconFactory(context, fillResIconDpi, iconBitmapSize) {
83 
84         init {
85             if (notificationsRedesignThemedAppIcons()) {
86                 // Initialize the controller so that we can support themed icons.
87                 mThemeController =
88                     MonoIconThemeController(
ctxnull89                         colorProvider = { ctx ->
90                             val res = ctx.resources
91                             intArrayOf(
92                                 /* background */ res.getColor(R.color.materialColorPrimary),
93                                 /* icon */ res.getColor(R.color.materialColorSurfaceContainerHigh),
94                             )
95                         }
96                     )
97             }
98         }
99     }
100 
101     private val iconFactory: BaseIconFactory
102         get() {
103             val isLowRam = ActivityManager.isLowRamDeviceStatic()
104             val res = sysuiContext.resources
105             val iconSize: Int =
106                 res.getDimensionPixelSize(
107                     if (isLowRam) R.dimen.notification_small_icon_size_low_ram
108                     else R.dimen.notification_small_icon_size
109                 )
110             return NotificationIcons(sysuiContext, res.configuration.densityDpi, iconSize)
111         }
112 
113     private val cache = NotifCollectionCache<Drawable>()
114 
getOrFetchAppIconnull115     override fun getOrFetchAppIcon(
116         packageName: String,
117         context: Context,
118         withWorkProfileBadge: Boolean,
119         themed: Boolean,
120     ): Drawable {
121         // Add a suffix to distinguish the app installed on the work profile, since the icon will
122         // be different.
123         val key = packageName + if (withWorkProfileBadge) WORK_SUFFIX else ""
124 
125         return cache.getOrFetch(key) {
126             fetchAppIcon(packageName, context, withWorkProfileBadge, themed)
127         }
128     }
129 
130     @WorkerThread
fetchAppIconnull131     private fun fetchAppIcon(
132         packageName: String,
133         context: Context,
134         withWorkProfileBadge: Boolean,
135         themed: Boolean,
136     ): Drawable {
137         val pm = context.packageManager
138         val icon = pm.getApplicationInfo(packageName, 0).loadUnbadgedIcon(pm)
139 
140         val options =
141             IconOptions().apply {
142                 setUser(userIconInfo(context, withWorkProfileBadge))
143                 setBitmapGenerationMode(BaseIconFactory.MODE_HARDWARE)
144                 // This color will not be used, but we're just setting it so that the icon factory
145                 // doesn't try to extract colors from our bitmap (since it won't work, given it's a
146                 // hardware bitmap).
147                 setExtractedColor(Color.BLUE)
148             }
149         val badgedIcon = iconFactory.createBadgedIconBitmap(icon, options)
150         val creationFlags = if (themed) BitmapInfo.FLAG_THEMED else 0
151         return badgedIcon.newIcon(sysuiContext, creationFlags)
152     }
153 
userIconInfonull154     private fun userIconInfo(context: Context, withWorkProfileBadge: Boolean): UserIconInfo {
155         val userId = context.userId
156         return UserIconInfo(
157             UserHandle.of(userId),
158             if (withWorkProfileBadge) UserIconInfo.TYPE_WORK else UserIconInfo.TYPE_MAIN,
159         )
160     }
161 
purgeCachenull162     override fun purgeCache(wantedPackages: Collection<String>) {
163         // We don't know from the packages if it's the work profile app or not, so let's just keep
164         // both if they're present in the cache.
165         cache.purge(wantedPackages.flatMap { listOf(it, "$it$WORK_SUFFIX") })
166     }
167 
dumpnull168     override fun dump(pwOrig: PrintWriter, args: Array<out String>) {
169         val pw = pwOrig.asIndenting()
170 
171         pw.println("cache information:")
172         pw.withIncreasedIndent { cache.dump(pw, args) }
173 
174         val iconFactory = iconFactory
175         pw.println("icon factory information:")
176         pw.withIncreasedIndent {
177             pw.println("fullResIconDpi = ${iconFactory.fullResIconDpi}")
178             pw.println("iconSize = ${iconFactory.iconBitmapSize}")
179         }
180     }
181 
182     companion object {
183         const val TAG = "AppIconProviderImpl"
184         const val WORK_SUFFIX = "|WORK"
185     }
186 }
187 
188 class NoOpIconProvider : AppIconProvider {
189     companion object {
190         const val TAG = "NoOpIconProvider"
191     }
192 
getOrFetchAppIconnull193     override fun getOrFetchAppIcon(
194         packageName: String,
195         context: Context,
196         withWorkProfileBadge: Boolean,
197         themed: Boolean,
198     ): Drawable {
199         Log.wtf(TAG, "NoOpIconProvider should not be used anywhere.")
200         return ColorDrawable(Color.WHITE)
201     }
202 
purgeCachenull203     override fun purgeCache(wantedPackages: Collection<String>) {
204         Log.wtf(TAG, "NoOpIconProvider should not be used anywhere.")
205     }
206 }
207 
208 @Module
209 class AppIconProviderModule {
210     @Provides
211     @SysUISingleton
provideImplnull212     fun provideImpl(realImpl: Provider<AppIconProviderImpl>): AppIconProvider =
213         if (Flags.notificationsRedesignAppIcons()) {
214             realImpl.get()
215         } else {
216             NoOpIconProvider()
217         }
218 }
219