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 17 package com.android.systemui.statusbar.notification.row.icon 18 19 import android.annotation.WorkerThread 20 import android.app.Flags 21 import android.content.Context 22 import android.content.pm.ApplicationInfo 23 import android.os.UserManager 24 import android.service.notification.StatusBarNotification 25 import android.util.Log 26 import com.android.systemui.Dumpable 27 import com.android.systemui.dagger.SysUISingleton 28 import com.android.systemui.dump.DumpManager 29 import com.android.systemui.statusbar.notification.collection.NotifCollectionCache 30 import com.android.systemui.util.asIndenting 31 import com.android.systemui.util.withIncreasedIndent 32 import dagger.Module 33 import dagger.Provides 34 import java.io.PrintWriter 35 import javax.inject.Inject 36 import javax.inject.Provider 37 38 /** 39 * A provider used to cache and fetch information about which icon should be displayed by 40 * notifications. 41 */ 42 interface NotificationIconStyleProvider { 43 /** 44 * Determines whether the [notification] should display the app icon instead of the small icon. 45 * This can result in a binder call, and therefore should only be called from the background. 46 */ 47 @WorkerThread shouldShowAppIconnull48 fun shouldShowAppIcon(notification: StatusBarNotification, context: Context): Boolean 49 50 /** 51 * Whether the [notification] is coming from a work profile app, and therefore should display 52 * the briefcase badge. 53 */ 54 fun shouldShowWorkProfileBadge(notification: StatusBarNotification, context: Context): Boolean 55 56 /** 57 * Mark all the entries in the cache that are NOT in [wantedPackages] to be cleared. If they're 58 * still not needed on the next call of this method (made after a timeout of 1s, in case they 59 * happen more frequently than that), they will be purged. This can be done from any thread. 60 */ 61 fun purgeCache(wantedPackages: Collection<String>) 62 } 63 64 @SysUISingleton 65 class NotificationIconStyleProviderImpl 66 @Inject 67 constructor(private val userManager: UserManager, dumpManager: DumpManager) : 68 NotificationIconStyleProvider, Dumpable { 69 init { 70 dumpManager.registerNormalDumpable(TAG, this) 71 } 72 73 private val cache = NotifCollectionCache<Boolean>() 74 75 override fun shouldShowAppIcon(notification: StatusBarNotification, context: Context): Boolean { 76 return cache.getOrFetch(notification.packageName) { 77 val packageContext = notification.getPackageContext(context) 78 !belongsToHeadlessSystemApp(packageContext) 79 } 80 } 81 82 @WorkerThread 83 private fun belongsToHeadlessSystemApp(context: Context): Boolean { 84 val info = context.applicationInfo 85 if (info != null) { 86 if ((info.flags and ApplicationInfo.FLAG_SYSTEM) == 0) { 87 // It's not a system app at all. 88 return false 89 } else { 90 // If there's no launch intent, it's probably a headless app. Check for both 91 // direct-aware and -unaware intents; otherwise this will almost certainly fail 92 // for notifications posted before unlocking. 93 val packageLaunchIntent = 94 context.packageManager.getLaunchIntentForPackage( 95 info.packageName, 96 /* includeDirectBootUnaware= */ true, 97 ) 98 return packageLaunchIntent == null 99 } 100 } else { 101 // If for some reason we don't have the app info, we don't know; best assume it's 102 // not a system app. 103 return false 104 } 105 } 106 107 override fun shouldShowWorkProfileBadge( 108 notification: StatusBarNotification, 109 context: Context, 110 ): Boolean { 111 val packageContext = notification.getPackageContext(context) 112 // UserManager already caches this, so we don't need to. 113 return userManager.isManagedProfile(packageContext.userId) 114 } 115 116 override fun purgeCache(wantedPackages: Collection<String>) { 117 cache.purge(wantedPackages) 118 } 119 120 override fun dump(pwOrig: PrintWriter, args: Array<out String>) { 121 val pw = pwOrig.asIndenting() 122 pw.println("cache information:") 123 pw.withIncreasedIndent { cache.dump(pw, args) } 124 } 125 126 companion object { 127 const val TAG = "NotificationIconStyleProviderImpl" 128 } 129 } 130 131 class NoOpIconStyleProvider : NotificationIconStyleProvider { 132 companion object { 133 const val TAG = "NoOpIconStyleProvider" 134 } 135 shouldShowAppIconnull136 override fun shouldShowAppIcon(notification: StatusBarNotification, context: Context): Boolean { 137 Log.wtf(TAG, "NoOpIconStyleProvider should not be used anywhere.") 138 return true 139 } 140 shouldShowWorkProfileBadgenull141 override fun shouldShowWorkProfileBadge( 142 notification: StatusBarNotification, 143 context: Context, 144 ): Boolean { 145 Log.wtf(TAG, "NoOpIconStyleProvider should not be used anywhere.") 146 return false 147 } 148 purgeCachenull149 override fun purgeCache(wantedPackages: Collection<String>) { 150 Log.wtf(TAG, "NoOpIconStyleProvider should not be used anywhere.") 151 } 152 } 153 154 @Module 155 class NotificationIconStyleProviderModule { 156 @Provides 157 @SysUISingleton provideImplnull158 fun provideImpl( 159 realImpl: Provider<NotificationIconStyleProviderImpl> 160 ): NotificationIconStyleProvider = 161 if (Flags.notificationsRedesignAppIcons()) { 162 realImpl.get() 163 } else { 164 NoOpIconStyleProvider() 165 } 166 } 167