| package com.android.systemui.statusbar.notification.interruption |
| |
| import android.app.Notification |
| import android.app.Notification.VISIBILITY_SECRET |
| import android.content.BroadcastReceiver |
| import android.content.Context |
| import android.content.Intent |
| import android.content.IntentFilter |
| import android.database.ContentObserver |
| import android.net.Uri |
| import android.os.Handler |
| import android.os.UserHandle |
| import android.provider.Settings |
| import com.android.keyguard.KeyguardUpdateMonitor |
| import com.android.keyguard.KeyguardUpdateMonitorCallback |
| import com.android.systemui.CoreStartable |
| import com.android.systemui.broadcast.BroadcastDispatcher |
| import com.android.systemui.dagger.SysUISingleton |
| import com.android.systemui.dagger.qualifiers.Main |
| import com.android.systemui.plugins.statusbar.StatusBarStateController |
| import com.android.systemui.statusbar.NotificationLockscreenUserManager |
| import com.android.systemui.statusbar.StatusBarState |
| import com.android.systemui.statusbar.SysuiStatusBarStateController |
| import com.android.systemui.statusbar.notification.collection.ListEntry |
| import com.android.systemui.statusbar.notification.collection.NotificationEntry |
| import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider |
| import com.android.systemui.statusbar.policy.KeyguardStateController |
| import com.android.systemui.util.ListenerSet |
| import com.android.systemui.util.asIndenting |
| import com.android.systemui.util.settings.GlobalSettings |
| import com.android.systemui.util.settings.SecureSettings |
| import com.android.systemui.util.withIncreasedIndent |
| import dagger.Binds |
| import dagger.Module |
| import dagger.multibindings.ClassKey |
| import dagger.multibindings.IntoMap |
| import java.io.PrintWriter |
| import java.util.function.Consumer |
| import javax.inject.Inject |
| |
| /** Determines if notifications should be visible based on the state of the keyguard. */ |
| interface KeyguardNotificationVisibilityProvider { |
| /** |
| * Determines if the given notification should be hidden based on the current keyguard state. |
| * If a [Consumer] registered via [addOnStateChangedListener] is invoked, the results of this |
| * method may no longer be valid and should be re-queried. |
| */ |
| fun shouldHideNotification(entry: NotificationEntry): Boolean |
| |
| /** Registers a listener to be notified when the internal keyguard state has been updated. */ |
| fun addOnStateChangedListener(listener: Consumer<String>) |
| |
| /** Unregisters a listener previously registered with [addOnStateChangedListener]. */ |
| fun removeOnStateChangedListener(listener: Consumer<String>) |
| } |
| |
| /** Provides a [KeyguardNotificationVisibilityProvider] in [SysUISingleton] scope. */ |
| @Module(includes = [KeyguardNotificationVisibilityProviderImplModule::class]) |
| object KeyguardNotificationVisibilityProviderModule |
| |
| @Module |
| private interface KeyguardNotificationVisibilityProviderImplModule { |
| @Binds |
| fun bindImpl(impl: KeyguardNotificationVisibilityProviderImpl): |
| KeyguardNotificationVisibilityProvider |
| |
| @Binds |
| @IntoMap |
| @ClassKey(KeyguardNotificationVisibilityProvider::class) |
| fun bindStartable(impl: KeyguardNotificationVisibilityProviderImpl): CoreStartable |
| } |
| |
| @SysUISingleton |
| private class KeyguardNotificationVisibilityProviderImpl @Inject constructor( |
| context: Context, |
| @Main private val handler: Handler, |
| private val keyguardStateController: KeyguardStateController, |
| private val lockscreenUserManager: NotificationLockscreenUserManager, |
| private val keyguardUpdateMonitor: KeyguardUpdateMonitor, |
| private val highPriorityProvider: HighPriorityProvider, |
| private val statusBarStateController: SysuiStatusBarStateController, |
| private val broadcastDispatcher: BroadcastDispatcher, |
| private val secureSettings: SecureSettings, |
| private val globalSettings: GlobalSettings |
| ) : CoreStartable(context), KeyguardNotificationVisibilityProvider { |
| private val showSilentNotifsUri = |
| secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS) |
| private val onStateChangedListeners = ListenerSet<Consumer<String>>() |
| private var hideSilentNotificationsOnLockscreen: Boolean = false |
| |
| override fun start() { |
| readShowSilentNotificationSetting() |
| keyguardStateController.addCallback(object : KeyguardStateController.Callback { |
| override fun onUnlockedChanged() { |
| notifyStateChanged("onUnlockedChanged") |
| } |
| |
| override fun onKeyguardShowingChanged() { |
| notifyStateChanged("onKeyguardShowingChanged") |
| } |
| }) |
| keyguardUpdateMonitor.registerCallback(object : KeyguardUpdateMonitorCallback() { |
| override fun onStrongAuthStateChanged(userId: Int) { |
| notifyStateChanged("onStrongAuthStateChanged") |
| } |
| }) |
| |
| // register lockscreen settings changed callbacks: |
| val settingsObserver: ContentObserver = object : ContentObserver(handler) { |
| override fun onChange(selfChange: Boolean, uri: Uri?) { |
| if (uri == showSilentNotifsUri) { |
| readShowSilentNotificationSetting() |
| } |
| if (isLockedOrLocking) { |
| notifyStateChanged("Settings $uri changed") |
| } |
| } |
| } |
| |
| secureSettings.registerContentObserverForUser( |
| Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, |
| settingsObserver, |
| UserHandle.USER_ALL) |
| |
| secureSettings.registerContentObserverForUser( |
| Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, |
| true, |
| settingsObserver, |
| UserHandle.USER_ALL) |
| |
| globalSettings.registerContentObserver(Settings.Global.ZEN_MODE, settingsObserver) |
| |
| secureSettings.registerContentObserverForUser( |
| Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, |
| settingsObserver, |
| UserHandle.USER_ALL) |
| |
| // register (maybe) public mode changed callbacks: |
| statusBarStateController.addCallback(object : StatusBarStateController.StateListener { |
| override fun onStateChanged(newState: Int) { |
| notifyStateChanged("onStatusBarStateChanged") |
| } |
| override fun onUpcomingStateChanged(state: Int) { |
| notifyStateChanged("onStatusBarUpcomingStateChanged") |
| } |
| }) |
| broadcastDispatcher.registerReceiver(object : BroadcastReceiver() { |
| override fun onReceive(context: Context, intent: Intent) { |
| if (isLockedOrLocking) { |
| // maybe public mode changed |
| notifyStateChanged(intent.action!!) |
| } |
| } |
| }, IntentFilter(Intent.ACTION_USER_SWITCHED)) |
| } |
| |
| override fun addOnStateChangedListener(listener: Consumer<String>) { |
| onStateChangedListeners.addIfAbsent(listener) |
| } |
| |
| override fun removeOnStateChangedListener(listener: Consumer<String>) { |
| onStateChangedListeners.remove(listener) |
| } |
| |
| private fun notifyStateChanged(reason: String) { |
| onStateChangedListeners.forEach { it.accept(reason) } |
| } |
| |
| override fun shouldHideNotification(entry: NotificationEntry): Boolean = when { |
| // Keyguard state doesn't matter if the keyguard is not showing. |
| !isLockedOrLocking -> false |
| // Notifications not allowed on the lockscreen, always hide. |
| !lockscreenUserManager.shouldShowLockscreenNotifications() -> true |
| // User settings do not allow this notification on the lockscreen, so hide it. |
| userSettingsDisallowNotification(entry) -> true |
| // Entry is explicitly marked SECRET, so hide it. |
| entry.sbn.notification.visibility == VISIBILITY_SECRET -> true |
| // if entry is silent, apply custom logic to see if should hide |
| shouldHideIfEntrySilent(entry) -> true |
| else -> false |
| } |
| |
| private fun shouldHideIfEntrySilent(entry: ListEntry): Boolean = when { |
| // Show if high priority (not hidden) |
| highPriorityProvider.isHighPriority(entry) -> false |
| // Ambient notifications are hidden always from lock screen |
| entry.representativeEntry?.isAmbient == true -> true |
| // [Now notification is silent] |
| // Hide regardless of parent priority if user wants silent notifs hidden |
| hideSilentNotificationsOnLockscreen -> true |
| // Parent priority is high enough to be shown on the lockscreen, do not hide. |
| entry.parent?.let(::shouldHideIfEntrySilent) == false -> false |
| // Show when silent notifications are allowed on lockscreen |
| else -> false |
| } |
| |
| private fun userSettingsDisallowNotification(entry: NotificationEntry): Boolean { |
| fun disallowForUser(user: Int) = when { |
| // user is in lockdown, always disallow |
| keyguardUpdateMonitor.isUserInLockdown(user) -> true |
| // device isn't public, no need to check public-related settings, so allow |
| !lockscreenUserManager.isLockscreenPublicMode(user) -> false |
| // entry is meant to be secret on the lockscreen, disallow |
| entry.ranking.lockscreenVisibilityOverride == Notification.VISIBILITY_SECRET -> true |
| // disallow if user disallows notifications in public |
| else -> !lockscreenUserManager.userAllowsNotificationsInPublic(user) |
| } |
| val currentUser = lockscreenUserManager.currentUserId |
| val notifUser = entry.sbn.user.identifier |
| return when { |
| disallowForUser(currentUser) -> true |
| notifUser == UserHandle.USER_ALL -> false |
| notifUser == currentUser -> false |
| else -> disallowForUser(notifUser) |
| } |
| } |
| |
| override fun dump(pw: PrintWriter, args: Array<out String>) = pw.asIndenting().run { |
| println("isLockedOrLocking=$isLockedOrLocking") |
| withIncreasedIndent { |
| println("keyguardStateController.isShowing=${keyguardStateController.isShowing}") |
| println("statusBarStateController.currentOrUpcomingState=" + |
| "${statusBarStateController.currentOrUpcomingState}") |
| } |
| println("hideSilentNotificationsOnLockscreen=$hideSilentNotificationsOnLockscreen") |
| } |
| |
| private val isLockedOrLocking get() = |
| keyguardStateController.isShowing || |
| statusBarStateController.currentOrUpcomingState == StatusBarState.KEYGUARD |
| |
| private fun readShowSilentNotificationSetting() { |
| val showSilentNotifs = |
| secureSettings.getBoolForUser(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, |
| true, UserHandle.USER_CURRENT) |
| hideSilentNotificationsOnLockscreen = !showSilentNotifs |
| } |
| } |