summaryrefslogtreecommitdiff
path: root/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
blob: cff42f29a62689d559c3d060cd6323cdbf30a44f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
package com.android.systemui.statusbar.notification.interruption

import android.app.Notification
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.settings.GlobalSettings
import com.android.systemui.util.settings.SecureSettings
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
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 (statusBarStateController.getCurrentOrUpcomingState()
                        == StatusBarState.KEYGUARD) {
                    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 onUpcomingStateChanged(state: Int) {
                notifyStateChanged("onStatusBarUpcomingStateChanged")
            }
        })
        broadcastDispatcher.registerReceiver(object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                if (statusBarStateController.getCurrentOrUpcomingState()
                        == StatusBarState.KEYGUARD) {
                    // 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.
        statusBarStateController.getCurrentOrUpcomingState() != StatusBarState.KEYGUARD -> 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
        // 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)
        }
    }

    private fun readShowSilentNotificationSetting() {
        val showSilentNotifs =
                secureSettings.getBoolForUser(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS,
                        true, UserHandle.USER_CURRENT)
        hideSilentNotificationsOnLockscreen = !showSilentNotifs
    }
}