• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2022 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.permissioncontroller.privacysources
18 
19 import android.app.Notification
20 import android.app.NotificationChannel
21 import android.app.NotificationManager
22 import android.app.PendingIntent
23 import android.app.PendingIntent.FLAG_IMMUTABLE
24 import android.app.PendingIntent.FLAG_ONE_SHOT
25 import android.app.PendingIntent.FLAG_UPDATE_CURRENT
26 import android.app.job.JobInfo
27 import android.app.job.JobParameters
28 import android.app.job.JobScheduler
29 import android.app.job.JobService
30 import android.app.role.RoleManager
31 import android.content.BroadcastReceiver
32 import android.content.ComponentName
33 import android.content.Context
34 import android.content.Context.MODE_PRIVATE
35 import android.content.Intent
36 import android.content.Intent.EXTRA_COMPONENT_NAME
37 import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
38 import android.content.Intent.FLAG_RECEIVER_FOREGROUND
39 import android.content.SharedPreferences
40 import android.content.pm.PackageInfo
41 import android.content.pm.PackageManager
42 import android.os.Build
43 import android.os.Bundle
44 import android.provider.DeviceConfig
45 import android.provider.Settings.ACTION_NOTIFICATION_LISTENER_DETAIL_SETTINGS
46 import android.provider.Settings.EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME
47 import android.safetycenter.SafetyCenterManager
48 import android.safetycenter.SafetyCenterManager.EXTRA_SAFETY_SOURCE_ID
49 import android.safetycenter.SafetyCenterManager.EXTRA_SAFETY_SOURCE_ISSUE_ID
50 import android.safetycenter.SafetyEvent
51 import android.safetycenter.SafetySourceData
52 import android.safetycenter.SafetySourceIssue
53 import android.service.notification.StatusBarNotification
54 import android.util.Log
55 import androidx.annotation.ChecksSdkIntAtLeast
56 import androidx.annotation.GuardedBy
57 import androidx.annotation.RequiresApi
58 import androidx.annotation.VisibleForTesting
59 import androidx.annotation.WorkerThread
60 import com.android.modules.utils.build.SdkLevel
61 import com.android.permissioncontroller.Constants
62 import com.android.permissioncontroller.Constants.KEY_LAST_NOTIFICATION_LISTENER_NOTIFICATION_SHOWN
63 import com.android.permissioncontroller.Constants.NOTIFICATION_LISTENER_CHECK_NOTIFICATION_ID
64 import com.android.permissioncontroller.Constants.PERIODIC_NOTIFICATION_LISTENER_CHECK_JOB_ID
65 import com.android.permissioncontroller.PermissionControllerStatsLog
66 import com.android.permissioncontroller.PermissionControllerStatsLog.PRIVACY_SIGNAL_ISSUE_CARD_INTERACTION
67 import com.android.permissioncontroller.PermissionControllerStatsLog.PRIVACY_SIGNAL_ISSUE_CARD_INTERACTION__ACTION__CARD_DISMISSED
68 import com.android.permissioncontroller.PermissionControllerStatsLog.PRIVACY_SIGNAL_ISSUE_CARD_INTERACTION__ACTION__CLICKED_CTA1
69 import com.android.permissioncontroller.PermissionControllerStatsLog.PRIVACY_SIGNAL_ISSUE_CARD_INTERACTION__PRIVACY_SOURCE__NOTIFICATION_LISTENER
70 import com.android.permissioncontroller.PermissionControllerStatsLog.PRIVACY_SIGNAL_NOTIFICATION_INTERACTION
71 import com.android.permissioncontroller.PermissionControllerStatsLog.PRIVACY_SIGNAL_NOTIFICATION_INTERACTION__ACTION__DISMISSED
72 import com.android.permissioncontroller.PermissionControllerStatsLog.PRIVACY_SIGNAL_NOTIFICATION_INTERACTION__ACTION__NOTIFICATION_SHOWN
73 import com.android.permissioncontroller.PermissionControllerStatsLog.PRIVACY_SIGNAL_NOTIFICATION_INTERACTION__PRIVACY_SOURCE__NOTIFICATION_LISTENER
74 import com.android.permissioncontroller.R
75 import com.android.permissioncontroller.permission.utils.KotlinUtils
76 import com.android.permissioncontroller.permission.utils.Utils
77 import com.android.permissioncontroller.permission.utils.Utils.getSystemServiceSafe
78 import com.android.permissioncontroller.privacysources.SafetyCenterReceiver.RefreshEvent
79 import java.lang.System.currentTimeMillis
80 import java.util.Random
81 import java.util.concurrent.TimeUnit.DAYS
82 import java.util.function.BooleanSupplier
83 import kotlinx.coroutines.Dispatchers.Default
84 import kotlinx.coroutines.GlobalScope
85 import kotlinx.coroutines.Job
86 import kotlinx.coroutines.launch
87 import kotlinx.coroutines.sync.Mutex
88 import kotlinx.coroutines.sync.withLock
89 
90 private val TAG = "NotificationListenerCheck"
91 private const val DEBUG = false
92 const val SC_NLS_SOURCE_ID = "AndroidNotificationListener"
93 @VisibleForTesting const val SC_NLS_DISABLE_ACTION_ID = "disable_nls_component"
94 
95 /** Device config property for whether notification listener check is enabled on the device */
96 @VisibleForTesting
97 const val PROPERTY_NOTIFICATION_LISTENER_CHECK_ENABLED = "notification_listener_check_enabled"
98 
99 /**
100  * Device config property for time period in milliseconds after which current enabled notification
101  * listeners are queried
102  */
103 private const val PROPERTY_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS =
104     "notification_listener_check_interval_millis"
105 
106 private val DEFAULT_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS = DAYS.toMillis(1)
107 
108 private fun isNotificationListenerCheckFlagEnabled(): Boolean {
109     // TODO: b/249789657 Set default to true after policy exemption + impact analysis
110     return DeviceConfig.getBoolean(
111         DeviceConfig.NAMESPACE_PRIVACY,
112         PROPERTY_NOTIFICATION_LISTENER_CHECK_ENABLED,
113         false
114     )
115 }
116 
117 /**
118  * Get time in between two periodic checks.
119  *
120  * Default: 1 day
121  *
122  * @return The time in between check in milliseconds
123  */
getPeriodicCheckIntervalMillisnull124 private fun getPeriodicCheckIntervalMillis(): Long {
125     return DeviceConfig.getLong(
126         DeviceConfig.NAMESPACE_PRIVACY,
127         PROPERTY_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS,
128         DEFAULT_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS
129     )
130 }
131 
132 /**
133  * Flexibility of the periodic check.
134  *
135  * 10% of [.getPeriodicCheckIntervalMillis]
136  *
137  * @return The flexibility of the periodic check in milliseconds
138  */
getFlexForPeriodicCheckMillisnull139 private fun getFlexForPeriodicCheckMillis(): Long {
140     return getPeriodicCheckIntervalMillis() / 10
141 }
142 
143 /**
144  * Minimum time in between showing two notifications.
145  *
146  * This is just small enough so that the periodic check can always show a notification.
147  *
148  * @return The minimum time in milliseconds
149  */
getInBetweenNotificationsMillisnull150 private fun getInBetweenNotificationsMillis(): Long {
151     return getPeriodicCheckIntervalMillis() - (getFlexForPeriodicCheckMillis() * 2.1).toLong()
152 }
153 
154 /** Notification Listener Check requires Android T or later */
155 @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU)
checkNotificationListenerCheckSupportednull156 private fun checkNotificationListenerCheckSupported(): Boolean {
157     return SdkLevel.isAtLeastT()
158 }
159 
160 /**
161  * Returns {@code true} when Notification listener check is supported, feature flag enabled and
162  * Safety Center enabled
163  */
164 @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU)
checkNotificationListenerCheckEnablednull165 private fun checkNotificationListenerCheckEnabled(context: Context): Boolean {
166     return checkNotificationListenerCheckSupported() &&
167         isNotificationListenerCheckFlagEnabled() &&
168         getSystemServiceSafe(context, SafetyCenterManager::class.java).isSafetyCenterEnabled
169 }
170 
getSafetySourceIssueIdFromComponentNamenull171 private fun getSafetySourceIssueIdFromComponentName(componentName: ComponentName): String {
172     return "notification_listener_${componentName.flattenToString()}"
173 }
174 
175 /**
176  * Show notification that double-guesses the user if they really wants to grant notification
177  * listener permission to an app.
178  *
179  * <p>A notification is scheduled periodically, or on demand
180  *
181  * <p>We rate limit the number of notification we show and only ever show one notification at a
182  * time.
183  *
184  * <p>As there are many cases why a notification should not been shown, we always schedule a
185  * {@link #addNotificationListenerNotificationIfNeeded check} which then might add a notification.
186  *
187  * @param context Used to resolve managers
188  * @param shouldCancel If supplied, can be used to interrupt long-running operations
189  */
190 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
191 @VisibleForTesting
192 class NotificationListenerCheckInternal(
193     context: Context,
194     private val shouldCancel: BooleanSupplier?
195 ) {
196     private val parentUserContext = Utils.getParentUserContext(context)
197     private val random = Random()
198     private val sharedPrefs: SharedPreferences =
199         parentUserContext.getSharedPreferences(NLS_PREFERENCE_FILE, MODE_PRIVATE)
200 
201     // Don't initialize until used. Delegate used for testing
202     @VisibleForTesting
<lambda>null203     val exemptPackagesDelegate = lazy {
204         getExemptedPackages(
205             getSystemServiceSafe(parentUserContext, RoleManager::class.java),
206             parentUserContext
207         )
208     }
209     @VisibleForTesting val exemptPackages: Set<String> by exemptPackagesDelegate
210 
211     companion object {
212         @VisibleForTesting const val NLS_PREFERENCE_FILE = "nls_preference"
213         private const val KEY_ALREADY_NOTIFIED_COMPONENTS = "already_notified_services"
214 
215         @VisibleForTesting const val SC_NLS_ISSUE_TYPE_ID = "notification_listener_privacy_issue"
216         @VisibleForTesting
217         const val SC_SHOW_NLS_SETTINGS_ACTION_ID = "show_notification_listener_settings"
218 
219         private const val SYSTEM_PKG = "android"
220 
221         private const val SYSTEM_AMBIENT_AUDIO_INTELLIGENCE =
222             "android.app.role.SYSTEM_AMBIENT_AUDIO_INTELLIGENCE"
223         private const val SYSTEM_UI_INTELLIGENCE = "android.app.role.SYSTEM_UI_INTELLIGENCE"
224         private const val SYSTEM_AUDIO_INTELLIGENCE = "android.app.role.SYSTEM_AUDIO_INTELLIGENCE"
225         private const val SYSTEM_NOTIFICATION_INTELLIGENCE =
226             "android.app.role.SYSTEM_NOTIFICATION_INTELLIGENCE"
227         private const val SYSTEM_TEXT_INTELLIGENCE = "android.app.role.SYSTEM_TEXT_INTELLIGENCE"
228         private const val SYSTEM_VISUAL_INTELLIGENCE = "android.app.role.SYSTEM_VISUAL_INTELLIGENCE"
229 
230         // This excludes System intelligence roles
231         private val EXEMPTED_ROLES =
232             arrayOf(
233                 SYSTEM_AMBIENT_AUDIO_INTELLIGENCE,
234                 SYSTEM_UI_INTELLIGENCE,
235                 SYSTEM_AUDIO_INTELLIGENCE,
236                 SYSTEM_NOTIFICATION_INTELLIGENCE,
237                 SYSTEM_TEXT_INTELLIGENCE,
238                 SYSTEM_VISUAL_INTELLIGENCE
239             )
240 
241         /** Lock required for all public methods */
242         private val nlsLock = Mutex()
243 
244         /** lock for shared preferences writes */
245         private val sharedPrefsLock = Mutex()
246 
247         private val sourceStateChangedSafetyEvent =
248             SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build()
249     }
250 
251     /**
252      * Check for enabled notification listeners and notify user if needed.
253      *
254      * <p>Always run async inside a {@NotificationListenerCheckJobService} via coroutine.
255      */
256     @WorkerThread
getEnabledNotificationListenersAndNotifyIfNeedednull257     suspend fun getEnabledNotificationListenersAndNotifyIfNeeded(
258         params: JobParameters,
259         service: NotificationListenerCheckJobService
260     ) {
261         nlsLock.withLock {
262             try {
263                 getEnabledNotificationListenersAndNotifyIfNeededLocked()
264                 service.jobFinished(params, false)
265             } catch (e: Exception) {
266                 Log.e(TAG, "Could not check for notification listeners", e)
267                 service.jobFinished(params, true)
268             } finally {
269                 service.clearJob()
270             }
271         }
272     }
273 
274     @Throws(InterruptedException::class)
getEnabledNotificationListenersAndNotifyIfNeededLockednull275     private suspend fun getEnabledNotificationListenersAndNotifyIfNeededLocked() {
276         val enabledComponents: List<ComponentName> = getEnabledNotificationListeners()
277 
278         // Clear disabled but previously notified components from notified components data
279         removeDisabledComponentsFromNotifiedComponents(enabledComponents)
280         val notifiedComponents =
281             getNotifiedComponents().mapNotNull { ComponentName.unflattenFromString(it) }
282 
283         // Filter to unnotified components
284         val unNotifiedComponents = enabledComponents.filter { it !in notifiedComponents }
285         var sessionId = Constants.INVALID_SESSION_ID
286         while (sessionId == Constants.INVALID_SESSION_ID) {
287             sessionId = random.nextLong()
288         }
289         if (DEBUG) {
290             Log.d(
291                 TAG,
292                 "Found ${enabledComponents.size} enabled notification listeners. " +
293                     "${notifiedComponents.size} already notified. ${unNotifiedComponents.size} " +
294                     "unnotified, sessionId = $sessionId"
295             )
296         }
297 
298         throwInterruptedExceptionIfTaskIsCanceled()
299 
300         postSystemNotificationIfNeeded(unNotifiedComponents, sessionId)
301         sendIssuesToSafetyCenter(enabledComponents, sessionId)
302     }
303 
304     /**
305      * Get the [components][ComponentName] which have enabled notification listeners for the
306      * parent/context user. Excludes exempt packages.
307      *
308      * @throws InterruptedException If [.shouldCancel]
309      */
310     @Throws(InterruptedException::class)
getEnabledNotificationListenersnull311     private fun getEnabledNotificationListeners(): List<ComponentName> {
312         // Get all enabled NotificationListenerService components for primary user. NLS from managed
313         // profiles are never bound.
314         val enabledNotificationListeners =
315             getSystemServiceSafe(parentUserContext, NotificationManager::class.java)
316                 .enabledNotificationListeners
317 
318         // Filter to components not in exempt packages
319         val enabledNotificationListenersExcludingExemptPackages =
320             enabledNotificationListeners.filter { !exemptPackages.contains(it.packageName) }
321 
322         if (DEBUG) {
323             Log.d(
324                 TAG,
325                 "enabledNotificationListeners=$enabledNotificationListeners\n" +
326                     "enabledNotificationListenersExcludingExemptPackages=" +
327                     "$enabledNotificationListenersExcludingExemptPackages"
328             )
329         }
330 
331         throwInterruptedExceptionIfTaskIsCanceled()
332         return enabledNotificationListenersExcludingExemptPackages
333     }
334 
335     /** Get all the exempted packages. */
getExemptedPackagesnull336     private fun getExemptedPackages(roleManager: RoleManager, context: Context): Set<String> {
337         val exemptedPackages: MutableSet<String> = HashSet()
338         exemptedPackages.add(SYSTEM_PKG)
339         EXEMPTED_ROLES.forEach { role -> exemptedPackages.addAll(roleManager.getRoleHolders(role)) }
340         exemptedPackages.addAll(NotificationListenerPregrants(context).pregrantedPackages)
341         return exemptedPackages
342     }
343 
344     @VisibleForTesting
getNotifiedComponentsnull345     fun getNotifiedComponents(): MutableSet<String> {
346         return sharedPrefs.getStringSet(KEY_ALREADY_NOTIFIED_COMPONENTS, mutableSetOf<String>())!!
347     }
348 
removeDisabledComponentsFromNotifiedComponentsnull349     suspend fun removeDisabledComponentsFromNotifiedComponents(
350         enabledComponents: Collection<ComponentName>
351     ) {
352         sharedPrefsLock.withLock {
353             val enabledComponentsStringSet =
354                 enabledComponents.map { it.flattenToShortString() }.toSet()
355             val notifiedComponents = getNotifiedComponents()
356             // Filter to only components that have enabled listeners
357             val enabledNotifiedComponents =
358                 notifiedComponents.filter { enabledComponentsStringSet.contains(it) }.toSet()
359             sharedPrefs
360                 .edit()
361                 .putStringSet(KEY_ALREADY_NOTIFIED_COMPONENTS, enabledNotifiedComponents)
362                 .apply()
363         }
364     }
365 
markComponentAsNotifiednull366     suspend fun markComponentAsNotified(component: ComponentName) {
367         sharedPrefsLock.withLock {
368             val notifiedComponents = getNotifiedComponents()
369             notifiedComponents.add(component.flattenToShortString())
370             sharedPrefs
371                 .edit()
372                 .putStringSet(KEY_ALREADY_NOTIFIED_COMPONENTS, notifiedComponents)
373                 .apply()
374         }
375     }
376 
removeFromNotifiedComponentsnull377     suspend fun removeFromNotifiedComponents(packageName: String) {
378         sharedPrefsLock.withLock {
379             val notifiedComponents = getNotifiedComponents()
380             val filteredServices =
381                 notifiedComponents
382                     .filter {
383                         val notifiedComponentName = ComponentName.unflattenFromString(it)
384                         return@filter notifiedComponentName?.packageName != packageName
385                     }
386                     .toSet()
387             if (filteredServices.size < notifiedComponents.size) {
388                 sharedPrefs
389                     .edit()
390                     .putStringSet(KEY_ALREADY_NOTIFIED_COMPONENTS, filteredServices)
391                     .apply()
392             }
393         }
394     }
395 
removeFromNotifiedComponentsnull396     suspend fun removeFromNotifiedComponents(component: ComponentName) {
397         val componentNameShortString = component.flattenToShortString()
398         sharedPrefsLock.withLock {
399             val notifiedComponents = getNotifiedComponents()
400             val componentRemoved = notifiedComponents.remove(componentNameShortString)
401             if (componentRemoved) {
402                 sharedPrefs
403                     .edit()
404                     .putStringSet(KEY_ALREADY_NOTIFIED_COMPONENTS, notifiedComponents)
405                     .apply()
406             }
407         }
408     }
409 
getLastNotificationShownTimeMillisnull410     private fun getLastNotificationShownTimeMillis(): Long {
411         return sharedPrefs.getLong(KEY_LAST_NOTIFICATION_LISTENER_NOTIFICATION_SHOWN, 0)
412     }
413 
updateLastShownNotificationTimenull414     private suspend fun updateLastShownNotificationTime() {
415         sharedPrefsLock.withLock {
416             sharedPrefs
417                 .edit()
418                 .putLong(KEY_LAST_NOTIFICATION_LISTENER_NOTIFICATION_SHOWN, currentTimeMillis())
419                 .apply()
420         }
421     }
422 
423     @Throws(InterruptedException::class)
postSystemNotificationIfNeedednull424     private suspend fun postSystemNotificationIfNeeded(
425         components: List<ComponentName>,
426         sessionId: Long
427     ) {
428         val componentsInternal = components.toMutableList()
429 
430         // Don't show too many notification within certain timespan
431         if (
432             currentTimeMillis() - getLastNotificationShownTimeMillis() <
433                 getInBetweenNotificationsMillis()
434         ) {
435             if (DEBUG) {
436                 Log.d(
437                     TAG,
438                     "Notification not posted, within " +
439                         "$DEFAULT_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS ms"
440                 )
441             }
442             return
443         }
444 
445         // Check for existing notification first, exit if one already present
446         if (getCurrentlyShownNotificationLocked() != null) {
447             if (DEBUG) {
448                 Log.d(TAG, "Notification not posted, previous notification has not been dismissed")
449             }
450             return
451         }
452 
453         // Get a random package and resolve package info
454         var pkgInfo: PackageInfo? = null
455         var componentToNotifyFor: ComponentName? = null
456         while (pkgInfo == null || componentToNotifyFor == null) {
457             throwInterruptedExceptionIfTaskIsCanceled()
458 
459             if (componentsInternal.isEmpty()) {
460                 if (DEBUG) {
461                     Log.d(TAG, "Notification not posted, no unnotified enabled listeners")
462                 }
463                 return
464             }
465 
466             componentToNotifyFor = componentsInternal[random.nextInt(componentsInternal.size)]
467             try {
468                 if (DEBUG) {
469                     Log.d(
470                         TAG,
471                         "Attempting to get PackageInfo for " + componentToNotifyFor.packageName
472                     )
473                 }
474                 pkgInfo =
475                     Utils.getPackageInfoForComponentName(parentUserContext, componentToNotifyFor)
476             } catch (e: PackageManager.NameNotFoundException) {
477                 if (DEBUG) {
478                     Log.w(TAG, "${componentToNotifyFor.packageName} not found")
479                 }
480                 componentsInternal.remove(componentToNotifyFor)
481             }
482         }
483 
484         createPermissionReminderChannel()
485         createNotificationForNotificationListener(componentToNotifyFor, pkgInfo, sessionId)
486 
487         // Mark as notified, since we don't get the on-click
488         markComponentAsNotified(componentToNotifyFor)
489     }
490 
491     /** Create the channel the notification listener notifications should be posted to. */
createPermissionReminderChannelnull492     private fun createPermissionReminderChannel() {
493         val permissionReminderChannel =
494             NotificationChannel(
495                 Constants.PERMISSION_REMINDER_CHANNEL_ID,
496                 parentUserContext.getString(R.string.permission_reminders),
497                 NotificationManager.IMPORTANCE_LOW
498             )
499 
500         val notificationManager =
501             getSystemServiceSafe(parentUserContext, NotificationManager::class.java)
502 
503         notificationManager.createNotificationChannel(permissionReminderChannel)
504     }
505 
506     /**
507      * Create a notification reminding the user that a package has an enabled notification listener.
508      * From this notification the user can directly go to Safety Center to assess issue.
509      *
510      * @param componentName the [ComponentName] of the Notification Listener
511      * @param pkg The [PackageInfo] for the [ComponentName] package
512      */
createNotificationForNotificationListenernull513     private suspend fun createNotificationForNotificationListener(
514         componentName: ComponentName,
515         pkg: PackageInfo,
516         sessionId: Long
517     ) {
518         val pkgLabel = Utils.getApplicationLabel(parentUserContext, pkg.applicationInfo!!)
519         val uid = pkg.applicationInfo!!.uid
520 
521         val deletePendingIntent =
522             getNotificationDeletePendingIntent(parentUserContext, componentName, uid, sessionId)
523         val clickPendingIntent =
524             getSafetyCenterActivityPendingIntent(parentUserContext, componentName, uid, sessionId)
525 
526         val title =
527             parentUserContext.getString(R.string.notification_listener_reminder_notification_title)
528         val text =
529             parentUserContext.getString(
530                 R.string.notification_listener_reminder_notification_content,
531                 pkgLabel
532             )
533 
534         val (appLabel, smallIcon, color) =
535             KotlinUtils.getSafetyCenterNotificationResources(parentUserContext)
536 
537         val b: Notification.Builder =
538             Notification.Builder(parentUserContext, Constants.PERMISSION_REMINDER_CHANNEL_ID)
539                 .setLocalOnly(true)
540                 .setContentTitle(title)
541                 .setContentText(text)
542                 // Ensure entire text can be displayed, instead of being truncated to one line
543                 .setStyle(Notification.BigTextStyle().bigText(text))
544                 .setSmallIcon(smallIcon)
545                 .setColor(color)
546                 .setAutoCancel(true)
547                 .setDeleteIntent(deletePendingIntent)
548                 .setContentIntent(clickPendingIntent)
549 
550         if (appLabel.isNotEmpty()) {
551             val appNameExtras = Bundle()
552             appNameExtras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, appLabel)
553             b.addExtras(appNameExtras)
554         }
555 
556         val notificationManager =
557             getSystemServiceSafe(parentUserContext, NotificationManager::class.java)
558         notificationManager.notify(
559             componentName.flattenToString(),
560             NOTIFICATION_LISTENER_CHECK_NOTIFICATION_ID,
561             b.build()
562         )
563 
564         if (DEBUG) {
565             Log.d(
566                 TAG,
567                 "Notification listener check notification shown with component=" +
568                     "${componentName.flattenToString()}, uid=$uid, sessionId=$sessionId"
569             )
570         }
571 
572         PermissionControllerStatsLog.write(
573             PRIVACY_SIGNAL_NOTIFICATION_INTERACTION,
574             PRIVACY_SIGNAL_NOTIFICATION_INTERACTION__PRIVACY_SOURCE__NOTIFICATION_LISTENER,
575             uid,
576             PRIVACY_SIGNAL_NOTIFICATION_INTERACTION__ACTION__NOTIFICATION_SHOWN,
577             sessionId
578         )
579         updateLastShownNotificationTime()
580     }
581 
582     /** @return [PendingIntent] to safety center */
getNotificationDeletePendingIntentnull583     private fun getNotificationDeletePendingIntent(
584         context: Context,
585         componentName: ComponentName,
586         uid: Int,
587         sessionId: Long
588     ): PendingIntent {
589         val intent =
590             Intent(
591                     parentUserContext,
592                     NotificationListenerCheckNotificationDeleteHandler::class.java
593                 )
594                 .apply {
595                     putExtra(EXTRA_COMPONENT_NAME, componentName)
596                     putExtra(Constants.EXTRA_SESSION_ID, sessionId)
597                     putExtra(Intent.EXTRA_UID, uid)
598                     flags = FLAG_RECEIVER_FOREGROUND
599                     identifier = componentName.flattenToString()
600                 }
601         return PendingIntent.getBroadcast(
602             context,
603             0,
604             intent,
605             FLAG_ONE_SHOT or FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE
606         )
607     }
608 
609     /** @return [PendingIntent] to safety center */
getSafetyCenterActivityPendingIntentnull610     private fun getSafetyCenterActivityPendingIntent(
611         context: Context,
612         componentName: ComponentName,
613         uid: Int,
614         sessionId: Long
615     ): PendingIntent {
616         val intent =
617             Intent(Intent.ACTION_SAFETY_CENTER).apply {
618                 putExtra(EXTRA_SAFETY_SOURCE_ID, SC_NLS_SOURCE_ID)
619                 putExtra(
620                     EXTRA_SAFETY_SOURCE_ISSUE_ID,
621                     getSafetySourceIssueIdFromComponentName(componentName)
622                 )
623                 putExtra(EXTRA_COMPONENT_NAME, componentName)
624                 putExtra(Constants.EXTRA_SESSION_ID, sessionId)
625                 putExtra(Intent.EXTRA_UID, uid)
626                 flags = FLAG_ACTIVITY_NEW_TASK
627                 identifier = componentName.flattenToString()
628             }
629         return PendingIntent.getActivity(
630             context,
631             0,
632             intent,
633             FLAG_ONE_SHOT or FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE
634         )
635     }
636 
637     /**
638      * Get currently shown notification. We only ever show one notification per profile group. Also
639      * only show notifications on the parent user/profile due to NotificationManager only binding
640      * non-managed NLS.
641      *
642      * @return The notification or `null` if no notification is currently shown
643      */
getCurrentlyShownNotificationLockednull644     private fun getCurrentlyShownNotificationLocked(): StatusBarNotification? {
645         val notifications =
646             getSystemServiceSafe(parentUserContext, NotificationManager::class.java)
647                 .activeNotifications
648 
649         return notifications.firstOrNull { it.id == NOTIFICATION_LISTENER_CHECK_NOTIFICATION_ID }
650     }
651 
652     /** Remove any posted notifications for this feature */
removeAnyNotificationnull653     internal fun removeAnyNotification() {
654         cancelNotification()
655     }
656 
657     /** Remove notification if present for a package */
removeNotificationsForPackagenull658     internal fun removeNotificationsForPackage(pkg: String) {
659         val notification: StatusBarNotification = getCurrentlyShownNotificationLocked() ?: return
660         val notificationComponent = ComponentName.unflattenFromString(notification.tag)
661         if (notificationComponent == null || notificationComponent.packageName != pkg) {
662             return
663         }
664         cancelNotification(notification.tag)
665     }
666 
667     /** Remove notification if present for a [ComponentName] */
removeNotificationsForComponentnull668     internal fun removeNotificationsForComponent(component: ComponentName) {
669         val notification: StatusBarNotification = getCurrentlyShownNotificationLocked() ?: return
670         val notificationComponent = ComponentName.unflattenFromString(notification.tag)
671         if (notificationComponent == null || notificationComponent != component) {
672             return
673         }
674         cancelNotification(notification.tag)
675     }
676 
cancelNotificationnull677     private fun cancelNotification(notificationTag: String) {
678         getSystemServiceSafe(parentUserContext, NotificationManager::class.java)
679             .cancel(notificationTag, NOTIFICATION_LISTENER_CHECK_NOTIFICATION_ID)
680     }
681 
cancelNotificationnull682     private fun cancelNotification() {
683         getSystemServiceSafe(parentUserContext, NotificationManager::class.java)
684             .cancel(NOTIFICATION_LISTENER_CHECK_NOTIFICATION_ID)
685     }
686 
sendIssuesToSafetyCenternull687     internal fun sendIssuesToSafetyCenter(
688         safetyEvent: SafetyEvent = sourceStateChangedSafetyEvent
689     ) {
690         val enabledComponents = getEnabledNotificationListeners()
691         var sessionId = Constants.INVALID_SESSION_ID
692         while (sessionId == Constants.INVALID_SESSION_ID) {
693             sessionId = random.nextLong()
694         }
695         sendIssuesToSafetyCenter(enabledComponents, sessionId, safetyEvent)
696     }
697 
sendIssuesToSafetyCenternull698     private fun sendIssuesToSafetyCenter(
699         enabledComponents: List<ComponentName>,
700         sessionId: Long,
701         safetyEvent: SafetyEvent = sourceStateChangedSafetyEvent
702     ) {
703         val pendingIssues = enabledComponents.mapNotNull { createSafetySourceIssue(it, sessionId) }
704         val dataBuilder = SafetySourceData.Builder()
705         pendingIssues.forEach { dataBuilder.addIssue(it) }
706         val safetySourceData = dataBuilder.build()
707         val safetyCenterManager =
708             getSystemServiceSafe(parentUserContext, SafetyCenterManager::class.java)
709         safetyCenterManager.setSafetySourceData(SC_NLS_SOURCE_ID, safetySourceData, safetyEvent)
710     }
711 
712     /**
713      * @param componentName enabled [NotificationListenerService]
714      * @return safety source issue, shown as the warning card in safety center. Null if unable to
715      *   create safety source issue
716      */
717     @VisibleForTesting
createSafetySourceIssuenull718     fun createSafetySourceIssue(componentName: ComponentName, sessionId: Long): SafetySourceIssue? {
719         val pkgInfo: PackageInfo
720         try {
721             pkgInfo = Utils.getPackageInfoForComponentName(parentUserContext, componentName)
722         } catch (e: PackageManager.NameNotFoundException) {
723             if (DEBUG) {
724                 Log.w(TAG, "${componentName.packageName} not found")
725             }
726             return null
727         }
728         val pkgLabel = Utils.getApplicationLabel(parentUserContext, pkgInfo.applicationInfo!!)
729         val safetySourceIssueId = getSafetySourceIssueIdFromComponentName(componentName)
730         val uid = pkgInfo.applicationInfo!!.uid
731 
732         val disableNlsPendingIntent =
733             getDisableNlsPendingIntent(
734                 parentUserContext,
735                 safetySourceIssueId,
736                 componentName,
737                 uid,
738                 sessionId
739             )
740 
741         val disableNlsAction =
742             SafetySourceIssue.Action.Builder(
743                     SC_NLS_DISABLE_ACTION_ID,
744                     parentUserContext.getString(
745                         R.string.notification_listener_remove_access_button_label
746                     ),
747                     disableNlsPendingIntent
748                 )
749                 .setWillResolve(true)
750                 .setSuccessMessage(
751                     parentUserContext.getString(
752                         R.string.notification_listener_remove_access_success_label
753                     )
754                 )
755                 .build()
756 
757         val notificationListenerDetailSettingsPendingIntent =
758             getNotificationListenerDetailSettingsPendingIntent(
759                 parentUserContext,
760                 componentName,
761                 uid,
762                 sessionId
763             )
764 
765         val showNotificationListenerSettingsAction =
766             SafetySourceIssue.Action.Builder(
767                     SC_SHOW_NLS_SETTINGS_ACTION_ID,
768                     parentUserContext.getString(
769                         R.string.notification_listener_review_app_button_label
770                     ),
771                     notificationListenerDetailSettingsPendingIntent
772                 )
773                 .build()
774 
775         val actionCardDismissPendingIntent =
776             getActionCardDismissalPendingIntent(parentUserContext, componentName, uid, sessionId)
777 
778         val title =
779             parentUserContext.getString(R.string.notification_listener_reminder_notification_title)
780         val summary =
781             parentUserContext.getString(R.string.notification_listener_warning_card_content)
782 
783         return SafetySourceIssue.Builder(
784                 safetySourceIssueId,
785                 title,
786                 summary,
787                 SafetySourceData.SEVERITY_LEVEL_INFORMATION,
788                 SC_NLS_ISSUE_TYPE_ID
789             )
790             .setSubtitle(pkgLabel)
791             .addAction(disableNlsAction)
792             .addAction(showNotificationListenerSettingsAction)
793             .setOnDismissPendingIntent(actionCardDismissPendingIntent)
794             .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DEVICE)
795             .build()
796     }
797 
798     /** @return [PendingIntent] for remove access button on the warning card. */
getDisableNlsPendingIntentnull799     private fun getDisableNlsPendingIntent(
800         context: Context,
801         safetySourceIssueId: String,
802         componentName: ComponentName,
803         uid: Int,
804         sessionId: Long
805     ): PendingIntent {
806         val intent =
807             Intent(context, DisableNotificationListenerComponentHandler::class.java).apply {
808                 putExtra(EXTRA_SAFETY_SOURCE_ISSUE_ID, safetySourceIssueId)
809                 putExtra(EXTRA_COMPONENT_NAME, componentName)
810                 putExtra(Constants.EXTRA_SESSION_ID, sessionId)
811                 putExtra(Intent.EXTRA_UID, uid)
812                 flags = FLAG_RECEIVER_FOREGROUND
813                 identifier = componentName.flattenToString()
814             }
815 
816         return PendingIntent.getBroadcast(context, 0, intent, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)
817     }
818 
819     /** @return [PendingIntent] to Notification Listener Detail Settings page */
getNotificationListenerDetailSettingsPendingIntentnull820     private fun getNotificationListenerDetailSettingsPendingIntent(
821         context: Context,
822         componentName: ComponentName,
823         uid: Int,
824         sessionId: Long
825     ): PendingIntent {
826         val intent =
827             Intent(ACTION_NOTIFICATION_LISTENER_DETAIL_SETTINGS).apply {
828                 flags = FLAG_ACTIVITY_NEW_TASK
829                 identifier = componentName.flattenToString()
830                 putExtra(
831                     EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME,
832                     componentName.flattenToString()
833                 )
834                 putExtra(Constants.EXTRA_SESSION_ID, sessionId)
835                 putExtra(Intent.EXTRA_UID, uid)
836                 putExtra(Constants.EXTRA_IS_FROM_SLICE, true)
837             }
838         return PendingIntent.getActivity(context, 0, intent, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)
839     }
840 
getActionCardDismissalPendingIntentnull841     private fun getActionCardDismissalPendingIntent(
842         context: Context,
843         componentName: ComponentName,
844         uid: Int,
845         sessionId: Long
846     ): PendingIntent {
847         val intent =
848             Intent(context, NotificationListenerActionCardDismissalReceiver::class.java).apply {
849                 putExtra(EXTRA_COMPONENT_NAME, componentName)
850                 putExtra(Constants.EXTRA_SESSION_ID, sessionId)
851                 putExtra(Intent.EXTRA_UID, uid)
852                 flags = FLAG_RECEIVER_FOREGROUND
853                 identifier = componentName.flattenToString()
854             }
855         return PendingIntent.getBroadcast(context, 0, intent, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)
856     }
857 
858     /** If [.shouldCancel] throw an [InterruptedException]. */
859     @Throws(InterruptedException::class)
throwInterruptedExceptionIfTaskIsCancelednull860     private fun throwInterruptedExceptionIfTaskIsCanceled() {
861         if (shouldCancel != null && shouldCancel.asBoolean) {
862             throw InterruptedException()
863         }
864     }
865 }
866 
867 /** Checks if a new notification should be shown. */
868 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
869 class NotificationListenerCheckJobService : JobService() {
870     private var notificationListenerCheckInternal: NotificationListenerCheckInternal? = null
871     private val jobLock = Object()
872 
873     /** We currently check if we should show a notification, the task executing the check */
874     @GuardedBy("jobLock") private var addNotificationListenerNotificationIfNeededJob: Job? = null
875 
onCreatenull876     override fun onCreate() {
877         super.onCreate()
878         if (DEBUG) Log.d(TAG, "Nls privacy job created")
879         if (!checkNotificationListenerCheckEnabled(this)) {
880             // NotificationListenerCheck not enabled. End job.
881             return
882         }
883 
884         notificationListenerCheckInternal =
885             NotificationListenerCheckInternal(
886                 this,
887                 BooleanSupplier {
888                     synchronized(jobLock) {
889                         val job = addNotificationListenerNotificationIfNeededJob
890                         return@BooleanSupplier job?.isCancelled ?: false
891                     }
892                 }
893             )
894     }
895 
896     /**
897      * Starts an asynchronous check if a notification listener notification should be shown.
898      *
899      * @param params Not used other than for interacting with job scheduling
900      * @return `false` if another check is already running, or if SDK Check fails (below T)
901      */
onStartJobnull902     override fun onStartJob(params: JobParameters): Boolean {
903         if (DEBUG) Log.d(TAG, "Nls privacy job started")
904         if (!checkNotificationListenerCheckEnabled(this)) {
905             // NotificationListenerCheck not enabled. End job.
906             return false
907         }
908 
909         synchronized(jobLock) {
910             if (addNotificationListenerNotificationIfNeededJob != null) {
911                 if (DEBUG) Log.d(TAG, "Job already running")
912                 return false
913             }
914             addNotificationListenerNotificationIfNeededJob =
915                 GlobalScope.launch(Default) {
916                     notificationListenerCheckInternal
917                         ?.getEnabledNotificationListenersAndNotifyIfNeeded(
918                             params,
919                             this@NotificationListenerCheckJobService
920                         )
921                         ?: jobFinished(params, true)
922                 }
923         }
924         return true
925     }
926 
927     /**
928      * Abort the check if still running.
929      *
930      * @param params ignored
931      * @return false
932      */
onStopJobnull933     override fun onStopJob(params: JobParameters): Boolean {
934         var job: Job?
935         synchronized(jobLock) {
936             job =
937                 if (addNotificationListenerNotificationIfNeededJob == null) {
938                     return false
939                 } else {
940                     addNotificationListenerNotificationIfNeededJob
941                 }
942         }
943         job?.cancel()
944         return false
945     }
946 
clearJobnull947     fun clearJob() {
948         synchronized(jobLock) { addNotificationListenerNotificationIfNeededJob = null }
949     }
950 }
951 
952 /** On boot set up a periodic job that starts checks. */
953 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
954 class SetupPeriodicNotificationListenerCheck : BroadcastReceiver() {
955 
onReceivenull956     override fun onReceive(context: Context, intent: Intent) {
957         if (!checkNotificationListenerCheckSupported()) {
958             // Notification Listener Check not supported. Exit.
959             return
960         }
961 
962         if (isProfile(context)) {
963             // Profile parent handles child profiles too.
964             return
965         }
966 
967         val jobScheduler = getSystemServiceSafe(context, JobScheduler::class.java)
968         if (jobScheduler.getPendingJob(PERIODIC_NOTIFICATION_LISTENER_CHECK_JOB_ID) == null) {
969             val job =
970                 JobInfo.Builder(
971                         PERIODIC_NOTIFICATION_LISTENER_CHECK_JOB_ID,
972                         ComponentName(context, NotificationListenerCheckJobService::class.java)
973                     )
974                     .setPeriodic(getPeriodicCheckIntervalMillis(), getFlexForPeriodicCheckMillis())
975                     .build()
976             val scheduleResult = jobScheduler.schedule(job)
977             if (scheduleResult != JobScheduler.RESULT_SUCCESS) {
978                 Log.e(
979                     TAG,
980                     "Could not schedule periodic notification listener check $scheduleResult"
981                 )
982             } else if (DEBUG) {
983                 Log.i(TAG, "Scheduled periodic notification listener check")
984             }
985         }
986     }
987 }
988 
989 /** Handle the case where the notification is swiped away without further interaction. */
990 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
991 class NotificationListenerCheckNotificationDeleteHandler : BroadcastReceiver() {
onReceivenull992     override fun onReceive(context: Context, intent: Intent) {
993         if (!checkNotificationListenerCheckSupported()) {
994             return
995         }
996 
997         val componentName =
998             Utils.getParcelableExtraSafe<ComponentName>(intent, EXTRA_COMPONENT_NAME)
999         val sessionId =
1000             intent.getLongExtra(Constants.EXTRA_SESSION_ID, Constants.INVALID_SESSION_ID)
1001         val uid = intent.getIntExtra(Intent.EXTRA_UID, -1)
1002 
1003         GlobalScope.launch(Default) {
1004             NotificationListenerCheckInternal(context, null).markComponentAsNotified(componentName)
1005         }
1006         if (DEBUG) {
1007             Log.d(
1008                 TAG,
1009                 "Notification listener check notification declined with component=" +
1010                     "${componentName.flattenToString()} , uid=$uid, sessionId=$sessionId"
1011             )
1012         }
1013         PermissionControllerStatsLog.write(
1014             PRIVACY_SIGNAL_NOTIFICATION_INTERACTION,
1015             PRIVACY_SIGNAL_NOTIFICATION_INTERACTION__PRIVACY_SOURCE__NOTIFICATION_LISTENER,
1016             uid,
1017             PRIVACY_SIGNAL_NOTIFICATION_INTERACTION__ACTION__DISMISSED,
1018             sessionId
1019         )
1020     }
1021 }
1022 
1023 /** Disable a specified Notification Listener Service component */
1024 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
1025 class DisableNotificationListenerComponentHandler : BroadcastReceiver() {
onReceivenull1026     override fun onReceive(context: Context, intent: Intent) {
1027         if (DEBUG) Log.d(TAG, "DisableComponentHandler.onReceive $intent")
1028         val componentName =
1029             Utils.getParcelableExtraSafe<ComponentName>(intent, EXTRA_COMPONENT_NAME)
1030         val sessionId =
1031             intent.getLongExtra(Constants.EXTRA_SESSION_ID, Constants.INVALID_SESSION_ID)
1032         val uid = intent.getIntExtra(Intent.EXTRA_UID, -1)
1033 
1034         GlobalScope.launch(Default) {
1035             if (DEBUG) {
1036                 Log.d(
1037                     TAG,
1038                     "DisableComponentHandler: disabling $componentName," +
1039                         "uid=$uid, sessionId=$sessionId"
1040                 )
1041             }
1042 
1043             val safetyEventBuilder =
1044                 try {
1045                     val notificationManager =
1046                         getSystemServiceSafe(context, NotificationManager::class.java)
1047                     disallowNlsLock.withLock {
1048                         notificationManager.setNotificationListenerAccessGranted(
1049                             componentName,
1050                             /* granted= */ false,
1051                             /* userSet= */ true
1052                         )
1053                     }
1054 
1055                     SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED)
1056                 } catch (e: Exception) {
1057                     Log.w(TAG, "error occurred in disabling notification listener service.", e)
1058                     SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED)
1059                 }
1060 
1061             val safetySourceIssueId: String? = intent.getStringExtra(EXTRA_SAFETY_SOURCE_ISSUE_ID)
1062             val safetyEvent =
1063                 safetyEventBuilder
1064                     .setSafetySourceIssueId(safetySourceIssueId)
1065                     .setSafetySourceIssueActionId(SC_NLS_DISABLE_ACTION_ID)
1066                     .build()
1067 
1068             NotificationListenerCheckInternal(context, null).run {
1069                 removeNotificationsForComponent(componentName)
1070                 removeFromNotifiedComponents(componentName)
1071                 sendIssuesToSafetyCenter(safetyEvent)
1072             }
1073             PermissionControllerStatsLog.write(
1074                 PRIVACY_SIGNAL_ISSUE_CARD_INTERACTION,
1075                 PRIVACY_SIGNAL_ISSUE_CARD_INTERACTION__PRIVACY_SOURCE__NOTIFICATION_LISTENER,
1076                 uid,
1077                 PRIVACY_SIGNAL_ISSUE_CARD_INTERACTION__ACTION__CLICKED_CTA1,
1078                 sessionId
1079             )
1080         }
1081     }
1082 
1083     companion object {
1084         private val disallowNlsLock = Mutex()
1085     }
1086 }
1087 
1088 /* A Safety Center action card for a specified component was dismissed */
1089 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
1090 class NotificationListenerActionCardDismissalReceiver : BroadcastReceiver() {
onReceivenull1091     override fun onReceive(context: Context, intent: Intent) {
1092         if (DEBUG) Log.d(TAG, "ActionCardDismissalReceiver.onReceive $intent")
1093         val componentName =
1094             Utils.getParcelableExtraSafe<ComponentName>(intent, EXTRA_COMPONENT_NAME)
1095         val sessionId =
1096             intent.getLongExtra(Constants.EXTRA_SESSION_ID, Constants.INVALID_SESSION_ID)
1097         val uid = intent.getIntExtra(Intent.EXTRA_UID, -1)
1098 
1099         GlobalScope.launch(Default) {
1100             if (DEBUG) {
1101                 Log.d(
1102                     TAG,
1103                     "ActionCardDismissalReceiver: $componentName dismissed," +
1104                         "uid=$uid, sessionId=$sessionId"
1105                 )
1106             }
1107             NotificationListenerCheckInternal(context, null).run {
1108                 removeNotificationsForComponent(componentName)
1109                 markComponentAsNotified(componentName)
1110             }
1111         }
1112         PermissionControllerStatsLog.write(
1113             PRIVACY_SIGNAL_ISSUE_CARD_INTERACTION,
1114             PRIVACY_SIGNAL_ISSUE_CARD_INTERACTION__PRIVACY_SOURCE__NOTIFICATION_LISTENER,
1115             uid,
1116             PRIVACY_SIGNAL_ISSUE_CARD_INTERACTION__ACTION__CARD_DISMISSED,
1117             sessionId
1118         )
1119     }
1120 }
1121 
1122 /**
1123  * If a package gets removed or the data of the package gets cleared, forget that we showed a
1124  * notification for it.
1125  */
1126 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
1127 class NotificationListenerPackageResetHandler : BroadcastReceiver() {
onReceivenull1128     override fun onReceive(context: Context, intent: Intent) {
1129         val action = intent.action
1130         if (
1131             action != Intent.ACTION_PACKAGE_DATA_CLEARED &&
1132                 action != Intent.ACTION_PACKAGE_FULLY_REMOVED
1133         ) {
1134             return
1135         }
1136 
1137         if (!checkNotificationListenerCheckEnabled(context)) {
1138             return
1139         }
1140 
1141         if (isProfile(context)) {
1142             if (DEBUG) {
1143                 Log.d(TAG, "NotificationListenerCheck only supports parent profile")
1144             }
1145             return
1146         }
1147 
1148         val data = requireNotNull(intent.data)
1149         val pkg: String = data.schemeSpecificPart
1150 
1151         if (DEBUG) Log.i(TAG, "Reset $pkg")
1152 
1153         GlobalScope.launch(Default) {
1154             NotificationListenerCheckInternal(context, null).run {
1155                 removeNotificationsForPackage(pkg)
1156                 removeFromNotifiedComponents(pkg)
1157                 sendIssuesToSafetyCenter()
1158             }
1159         }
1160     }
1161 }
1162 
1163 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
1164 class NotificationListenerPrivacySource : PrivacySource {
1165     override val shouldProcessProfileRequest: Boolean = false
1166 
safetyCenterEnabledChangednull1167     override fun safetyCenterEnabledChanged(context: Context, enabled: Boolean) {
1168         NotificationListenerCheckInternal(context, null).run { removeAnyNotification() }
1169     }
1170 
rescanAndPushSafetyCenterDatanull1171     override fun rescanAndPushSafetyCenterData(
1172         context: Context,
1173         intent: Intent,
1174         refreshEvent: RefreshEvent
1175     ) {
1176         if (!isNotificationListenerCheckFlagEnabled()) {
1177             return
1178         }
1179 
1180         val safetyRefreshEvent = getSafetyCenterEvent(refreshEvent, intent)
1181 
1182         NotificationListenerCheckInternal(context, null).run {
1183             sendIssuesToSafetyCenter(safetyRefreshEvent)
1184         }
1185     }
1186 }
1187