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