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