blob: c956a2ea18368fb22df0b4b9b03e8b302bdb76df [file] [log] [blame]
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
}
}