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