1 /*
2  * Copyright (C) 2023 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 package com.android.systemui.statusbar.notification.interruption
17 
18 import android.content.pm.PackageManager
19 import android.hardware.display.AmbientDisplayConfiguration
20 import android.os.Handler
21 import android.os.PowerManager
22 import android.util.Log
23 import com.android.app.tracing.traceSection
24 import com.android.internal.annotations.VisibleForTesting
25 import com.android.internal.logging.UiEventLogger
26 import com.android.internal.logging.UiEventLogger.UiEventEnum
27 import com.android.systemui.dagger.qualifiers.Main
28 import com.android.systemui.plugins.statusbar.StatusBarStateController
29 import com.android.systemui.settings.UserTracker
30 import com.android.systemui.statusbar.notification.collection.NotificationEntry
31 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.Decision
32 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.FullScreenIntentDecision
33 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionSuppressor.EventLogData
34 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE
35 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK
36 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE
37 import com.android.systemui.statusbar.notification.shared.NotificationAvalancheSuppression
38 import com.android.systemui.statusbar.policy.BatteryController
39 import com.android.systemui.statusbar.policy.DeviceProvisionedController
40 import com.android.systemui.statusbar.policy.HeadsUpManager
41 import com.android.systemui.statusbar.policy.KeyguardStateController
42 import com.android.systemui.util.EventLog
43 import com.android.systemui.util.settings.GlobalSettings
44 import com.android.systemui.util.settings.SystemSettings
45 import com.android.systemui.util.time.SystemClock
46 import com.android.wm.shell.bubbles.Bubbles
47 import java.util.Optional
48 import javax.inject.Inject
49 
50 class VisualInterruptionDecisionProviderImpl
51 @Inject
52 constructor(
53     private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
54     private val batteryController: BatteryController,
55     deviceProvisionedController: DeviceProvisionedController,
56     private val eventLog: EventLog,
57     private val globalSettings: GlobalSettings,
58     private val headsUpManager: HeadsUpManager,
59     private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
60     keyguardStateController: KeyguardStateController,
61     private val logger: VisualInterruptionDecisionLogger,
62     @Main private val mainHandler: Handler,
63     private val powerManager: PowerManager,
64     private val statusBarStateController: StatusBarStateController,
65     private val systemClock: SystemClock,
66     private val uiEventLogger: UiEventLogger,
67     private val userTracker: UserTracker,
68     private val avalancheProvider: AvalancheProvider,
69     private val systemSettings: SystemSettings,
70     private val packageManager: PackageManager,
71     private val bubbles: Optional<Bubbles>
72 ) : VisualInterruptionDecisionProvider {
73 
74     init {
75         check(!VisualInterruptionRefactor.isUnexpectedlyInLegacyMode())
76     }
77 
78     interface Loggable {
79         val uiEventId: UiEventEnum?
80         val eventLogData: EventLogData?
81     }
82 
83     private class DecisionImpl(
84         override val shouldInterrupt: Boolean,
85         override val logReason: String
86     ) : Decision
87 
88     private data class LoggableDecision
89     private constructor(
90         val decision: DecisionImpl,
91         override val uiEventId: UiEventEnum? = null,
92         override val eventLogData: EventLogData? = null
93     ) : Loggable {
94         companion object {
95             val unsuppressed =
96                 LoggableDecision(DecisionImpl(shouldInterrupt = true, logReason = "not suppressed"))
97 
suppressednull98             fun suppressed(legacySuppressor: NotificationInterruptSuppressor, methodName: String) =
99                 LoggableDecision(
100                     DecisionImpl(
101                         shouldInterrupt = false,
102                         logReason = "${legacySuppressor.name}.$methodName"
103                     )
104                 )
105 
106             fun suppressed(suppressor: VisualInterruptionSuppressor) =
107                 LoggableDecision(
108                     DecisionImpl(shouldInterrupt = false, logReason = suppressor.reason),
109                     uiEventId = suppressor.uiEventId,
110                     eventLogData = suppressor.eventLogData
111                 )
112         }
113     }
114 
115     private class FullScreenIntentDecisionImpl(
116         val entry: NotificationEntry,
117         private val fsiDecision: FullScreenIntentDecisionProvider.Decision
118     ) : FullScreenIntentDecision, Loggable {
119         var hasBeenLogged = false
120 
121         override val shouldInterrupt
122             get() = fsiDecision.shouldFsi
123 
124         override val wouldInterruptWithoutDnd
125             get() = fsiDecision.wouldFsiWithoutDnd
126 
127         override val logReason
128             get() = fsiDecision.logReason
129 
130         val shouldLog
131             get() = fsiDecision.shouldLog
132 
133         val isWarning
134             get() = fsiDecision.isWarning
135 
136         override val uiEventId
137             get() = fsiDecision.uiEventId
138 
139         override val eventLogData
140             get() = fsiDecision.eventLogData
141     }
142 
143     private val fullScreenIntentDecisionProvider =
144         FullScreenIntentDecisionProvider(
145             deviceProvisionedController,
146             keyguardStateController,
147             powerManager,
148             statusBarStateController
149         )
150 
151     private val legacySuppressors = mutableSetOf<NotificationInterruptSuppressor>()
152     private val conditions = mutableListOf<VisualInterruptionCondition>()
153     private val filters = mutableListOf<VisualInterruptionFilter>()
154 
155     private var started = false
156 
startnull157     override fun start() {
158         check(!started)
159 
160         addCondition(PeekDisabledSuppressor(globalSettings, headsUpManager, logger, mainHandler))
161         addCondition(PulseDisabledSuppressor(ambientDisplayConfiguration, userTracker))
162         addCondition(PulseBatterySaverSuppressor(batteryController))
163         addFilter(PeekPackageSnoozedSuppressor(headsUpManager))
164         addFilter(PeekAlreadyBubbledSuppressor(statusBarStateController, bubbles))
165         addFilter(PeekDndSuppressor())
166         addFilter(PeekNotImportantSuppressor())
167         addCondition(PeekDeviceNotInUseSuppressor(powerManager, statusBarStateController))
168         addFilter(PeekOldWhenSuppressor(systemClock))
169         addFilter(PulseEffectSuppressor())
170         addFilter(PulseLockscreenVisibilityPrivateSuppressor())
171         addFilter(PulseLowImportanceSuppressor())
172         addFilter(BubbleNotAllowedSuppressor())
173         addFilter(BubbleNoMetadataSuppressor())
174         addFilter(HunGroupAlertBehaviorSuppressor())
175         addFilter(HunJustLaunchedFsiSuppressor())
176         addFilter(AlertAppSuspendedSuppressor())
177         addFilter(AlertKeyguardVisibilitySuppressor(keyguardNotificationVisibilityProvider))
178 
179         if (NotificationAvalancheSuppression.isEnabled) {
180             addFilter(
181                 AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
182                         uiEventLogger)
183             )
184             avalancheProvider.register()
185         }
186         started = true
187     }
188 
addLegacySuppressornull189     override fun addLegacySuppressor(suppressor: NotificationInterruptSuppressor) {
190         legacySuppressors.add(suppressor)
191     }
192 
removeLegacySuppressornull193     override fun removeLegacySuppressor(suppressor: NotificationInterruptSuppressor) {
194         legacySuppressors.remove(suppressor)
195     }
196 
addConditionnull197     override fun addCondition(condition: VisualInterruptionCondition) {
198         conditions.add(condition)
199         condition.start()
200     }
201 
202     @VisibleForTesting
removeConditionnull203     override fun removeCondition(condition: VisualInterruptionCondition) {
204         conditions.remove(condition)
205     }
206 
addFilternull207     override fun addFilter(filter: VisualInterruptionFilter) {
208         filters.add(filter)
209         filter.start()
210     }
211 
212     @VisibleForTesting
removeFilternull213     override fun removeFilter(filter: VisualInterruptionFilter) {
214         filters.remove(filter)
215     }
216 
makeUnloggedHeadsUpDecisionnull217     override fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision =
218         traceSection("VisualInterruptionDecisionProviderImpl#makeUnloggedHeadsUpDecision") {
219             check(started)
220 
221             return if (statusBarStateController.isDozing) {
222                     makeLoggablePulseDecision(entry)
223                 } else {
224                     makeLoggablePeekDecision(entry)
225                 }
226                 .decision
227         }
228 
makeAndLogHeadsUpDecisionnull229     override fun makeAndLogHeadsUpDecision(entry: NotificationEntry): Decision =
230         traceSection("VisualInterruptionDecisionProviderImpl#makeAndLogHeadsUpDecision") {
231             check(started)
232 
233             return if (statusBarStateController.isDozing) {
234                     makeLoggablePulseDecision(entry).also { logDecision(PULSE, entry, it) }
235                 } else {
236                     makeLoggablePeekDecision(entry).also { logDecision(PEEK, entry, it) }
237                 }
238                 .decision
239         }
240 
makeLoggablePeekDecisionnull241     private fun makeLoggablePeekDecision(entry: NotificationEntry): LoggableDecision =
242         checkConditions(PEEK)
243             ?: checkFilters(PEEK, entry)
244             ?: checkSuppressInterruptions(entry)
245             ?: checkSuppressAwakeInterruptions(entry)
246             ?: checkSuppressAwakeHeadsUp(entry)
247             ?: LoggableDecision.unsuppressed
248 
249     private fun makeLoggablePulseDecision(entry: NotificationEntry): LoggableDecision =
250         checkConditions(PULSE)
251             ?: checkFilters(PULSE, entry)
252             ?: checkSuppressInterruptions(entry)
253             ?: LoggableDecision.unsuppressed
254 
255     override fun makeAndLogBubbleDecision(entry: NotificationEntry): Decision =
256         traceSection("VisualInterruptionDecisionProviderImpl#makeAndLogBubbleDecision") {
257             check(started)
258 
259             return makeLoggableBubbleDecision(entry)
260                 .also { logDecision(BUBBLE, entry, it) }
261                 .decision
262         }
263 
makeLoggableBubbleDecisionnull264     private fun makeLoggableBubbleDecision(entry: NotificationEntry): LoggableDecision =
265         checkConditions(BUBBLE)
266             ?: checkFilters(BUBBLE, entry)
267             ?: checkSuppressInterruptions(entry)
268             ?: checkSuppressAwakeInterruptions(entry)
269             ?: LoggableDecision.unsuppressed
270 
271     private fun logDecision(
272         type: VisualInterruptionType,
273         entry: NotificationEntry,
274         loggableDecision: LoggableDecision
275     ) {
276         logger.logDecision(type.name, entry, loggableDecision.decision)
277         logEvents(entry, loggableDecision)
278     }
279 
makeUnloggedFullScreenIntentDecisionnull280     override fun makeUnloggedFullScreenIntentDecision(
281         entry: NotificationEntry
282     ): FullScreenIntentDecision =
283         traceSection(
284             "VisualInterruptionDecisionProviderImpl#makeUnloggedFullScreenIntentDecision"
285         ) {
286             check(started)
287 
288             val couldHeadsUp = makeUnloggedHeadsUpDecision(entry).shouldInterrupt
289             val fsiDecision =
290                 fullScreenIntentDecisionProvider.makeFullScreenIntentDecision(entry, couldHeadsUp)
291             return FullScreenIntentDecisionImpl(entry, fsiDecision)
292         }
293 
logFullScreenIntentDecisionnull294     override fun logFullScreenIntentDecision(decision: FullScreenIntentDecision) =
295         traceSection("VisualInterruptionDecisionProviderImpl#logFullScreenIntentDecision") {
296             check(started)
297 
298             if (decision !is FullScreenIntentDecisionImpl) {
299                 Log.wtf(TAG, "FSI decision $decision was not created by this class")
300                 return
301             }
302 
303             if (decision.hasBeenLogged) {
304                 Log.wtf(TAG, "FSI decision $decision has already been logged")
305                 return
306             }
307 
308             decision.hasBeenLogged = true
309 
310             if (!decision.shouldLog) {
311                 return
312             }
313 
314             logger.logFullScreenIntentDecision(decision.entry, decision, decision.isWarning)
315             logEvents(decision.entry, decision)
316         }
317 
logEventsnull318     private fun logEvents(entry: NotificationEntry, loggable: Loggable) {
319         loggable.uiEventId?.let { uiEventLogger.log(it, entry.sbn.uid, entry.sbn.packageName) }
320         loggable.eventLogData?.let {
321             eventLog.writeEvent(0x534e4554, it.number, entry.sbn.uid, it.description)
322         }
323     }
324 
checkSuppressInterruptionsnull325     private fun checkSuppressInterruptions(entry: NotificationEntry) =
326         legacySuppressors
327             .firstOrNull { it.suppressInterruptions(entry) }
<lambda>null328             ?.let { LoggableDecision.suppressed(it, "suppressInterruptions") }
329 
checkSuppressAwakeInterruptionsnull330     private fun checkSuppressAwakeInterruptions(entry: NotificationEntry) =
331         legacySuppressors
332             .firstOrNull { it.suppressAwakeInterruptions(entry) }
<lambda>null333             ?.let { LoggableDecision.suppressed(it, "suppressAwakeInterruptions") }
334 
checkSuppressAwakeHeadsUpnull335     private fun checkSuppressAwakeHeadsUp(entry: NotificationEntry) =
336         legacySuppressors
337             .firstOrNull { it.suppressAwakeHeadsUp(entry) }
<lambda>null338             ?.let { LoggableDecision.suppressed(it, "suppressAwakeHeadsUp") }
339 
checkConditionsnull340     private fun checkConditions(type: VisualInterruptionType) =
341         conditions
342             .firstOrNull { it.types.contains(type) && it.shouldSuppress() }
<lambda>null343             ?.let { LoggableDecision.suppressed(it) }
344 
checkFiltersnull345     private fun checkFilters(type: VisualInterruptionType, entry: NotificationEntry) =
346         filters
347             .firstOrNull { it.types.contains(type) && it.shouldSuppress(entry) }
<lambda>null348             ?.let { LoggableDecision.suppressed(it) }
349 }
350 
351 private const val TAG = "VisualInterruptionDecisionProviderImpl"
352