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