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