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