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