• 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 
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