1 /* 2 * Copyright (C) 2018 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 17 package com.android.systemui.statusbar.notification.interruption; 18 19 import static android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED; 20 import static android.provider.Settings.Global.HEADS_UP_OFF; 21 22 import static com.android.systemui.statusbar.StatusBarState.SHADE; 23 import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD; 24 import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA; 25 import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR; 26 import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.HUN_SNOOZE_BYPASSED_POTENTIALLY_SUPPRESSED_FSI; 27 28 import android.app.Notification; 29 import android.app.NotificationManager; 30 import android.database.ContentObserver; 31 import android.hardware.display.AmbientDisplayConfiguration; 32 import android.os.Handler; 33 import android.os.PowerManager; 34 import android.service.notification.StatusBarNotification; 35 36 import androidx.annotation.NonNull; 37 38 import com.android.internal.annotations.VisibleForTesting; 39 import com.android.internal.logging.UiEvent; 40 import com.android.internal.logging.UiEventLogger; 41 import com.android.systemui.dagger.SysUISingleton; 42 import com.android.systemui.dagger.qualifiers.Main; 43 import com.android.systemui.plugins.statusbar.StatusBarStateController; 44 import com.android.systemui.settings.UserTracker; 45 import com.android.systemui.statusbar.StatusBarState; 46 import com.android.systemui.statusbar.notification.NotifPipelineFlags; 47 import com.android.systemui.statusbar.notification.collection.NotificationEntry; 48 import com.android.systemui.statusbar.policy.BatteryController; 49 import com.android.systemui.statusbar.policy.DeviceProvisionedController; 50 import com.android.systemui.statusbar.policy.HeadsUpManager; 51 import com.android.systemui.statusbar.policy.KeyguardStateController; 52 import com.android.systemui.util.EventLog; 53 import com.android.systemui.util.settings.GlobalSettings; 54 import com.android.systemui.util.time.SystemClock; 55 import com.android.wm.shell.bubbles.Bubbles; 56 57 import java.util.ArrayList; 58 import java.util.List; 59 import java.util.Optional; 60 61 import javax.inject.Inject; 62 63 /** 64 * Provides heads-up and pulsing state for notification entries. 65 */ 66 @SysUISingleton 67 public class NotificationInterruptStateProviderImpl implements NotificationInterruptStateProvider { 68 private static final String TAG = "InterruptionStateProvider"; 69 private static final boolean ENABLE_HEADS_UP = true; 70 private static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up"; 71 72 private final List<NotificationInterruptSuppressor> mSuppressors = new ArrayList<>(); 73 private final StatusBarStateController mStatusBarStateController; 74 private final KeyguardStateController mKeyguardStateController; 75 private final PowerManager mPowerManager; 76 private final AmbientDisplayConfiguration mAmbientDisplayConfiguration; 77 private final BatteryController mBatteryController; 78 private final HeadsUpManager mHeadsUpManager; 79 private final NotificationInterruptLogger mLogger; 80 private final NotifPipelineFlags mFlags; 81 private final KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider; 82 private final UiEventLogger mUiEventLogger; 83 private final UserTracker mUserTracker; 84 private final DeviceProvisionedController mDeviceProvisionedController; 85 private final SystemClock mSystemClock; 86 private final GlobalSettings mGlobalSettings; 87 private final EventLog mEventLog; 88 private final Optional<Bubbles> mBubbles; 89 90 @VisibleForTesting 91 protected boolean mUseHeadsUp = false; 92 93 public enum NotificationInterruptEvent implements UiEventLogger.UiEventEnum { 94 @UiEvent(doc = "FSI suppressed for suppressive GroupAlertBehavior") 95 FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR(1235), 96 97 @UiEvent(doc = "FSI suppressed for suppressive BubbleMetadata") 98 FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA(1353), 99 100 @UiEvent(doc = "FSI suppressed for requiring neither HUN nor keyguard") 101 FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD(1236), 102 103 @UiEvent(doc = "HUN suppressed for old when") 104 HUN_SUPPRESSED_OLD_WHEN(1237), 105 106 @UiEvent(doc = "HUN snooze bypassed for potentially suppressed FSI") 107 HUN_SNOOZE_BYPASSED_POTENTIALLY_SUPPRESSED_FSI(1269); 108 109 private final int mId; 110 NotificationInterruptEvent(int id)111 NotificationInterruptEvent(int id) { 112 mId = id; 113 } 114 115 @Override getId()116 public int getId() { 117 return mId; 118 } 119 } 120 121 @Inject NotificationInterruptStateProviderImpl( PowerManager powerManager, AmbientDisplayConfiguration ambientDisplayConfiguration, BatteryController batteryController, StatusBarStateController statusBarStateController, KeyguardStateController keyguardStateController, HeadsUpManager headsUpManager, NotificationInterruptLogger logger, @Main Handler mainHandler, NotifPipelineFlags flags, KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider, UiEventLogger uiEventLogger, UserTracker userTracker, DeviceProvisionedController deviceProvisionedController, SystemClock systemClock, GlobalSettings globalSettings, EventLog eventLog, Optional<Bubbles> bubbles)122 public NotificationInterruptStateProviderImpl( 123 PowerManager powerManager, 124 AmbientDisplayConfiguration ambientDisplayConfiguration, 125 BatteryController batteryController, 126 StatusBarStateController statusBarStateController, 127 KeyguardStateController keyguardStateController, 128 HeadsUpManager headsUpManager, 129 NotificationInterruptLogger logger, 130 @Main Handler mainHandler, 131 NotifPipelineFlags flags, 132 KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider, 133 UiEventLogger uiEventLogger, 134 UserTracker userTracker, 135 DeviceProvisionedController deviceProvisionedController, 136 SystemClock systemClock, 137 GlobalSettings globalSettings, 138 EventLog eventLog, 139 Optional<Bubbles> bubbles) { 140 mPowerManager = powerManager; 141 mBatteryController = batteryController; 142 mAmbientDisplayConfiguration = ambientDisplayConfiguration; 143 mStatusBarStateController = statusBarStateController; 144 mKeyguardStateController = keyguardStateController; 145 mHeadsUpManager = headsUpManager; 146 mLogger = logger; 147 mFlags = flags; 148 mKeyguardNotificationVisibilityProvider = keyguardNotificationVisibilityProvider; 149 mUiEventLogger = uiEventLogger; 150 mUserTracker = userTracker; 151 mDeviceProvisionedController = deviceProvisionedController; 152 mSystemClock = systemClock; 153 mGlobalSettings = globalSettings; 154 mEventLog = eventLog; 155 mBubbles = bubbles; 156 ContentObserver headsUpObserver = new ContentObserver(mainHandler) { 157 @Override 158 public void onChange(boolean selfChange) { 159 final boolean wasUsing = mUseHeadsUp; 160 final boolean settingEnabled = HEADS_UP_OFF 161 != mGlobalSettings.getInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_OFF); 162 mUseHeadsUp = ENABLE_HEADS_UP && settingEnabled; 163 mLogger.logHeadsUpFeatureChanged(mUseHeadsUp); 164 if (wasUsing != mUseHeadsUp) { 165 if (!mUseHeadsUp) { 166 mLogger.logWillDismissAll(); 167 mHeadsUpManager.releaseAllImmediately(); 168 } 169 } 170 } 171 }; 172 173 if (ENABLE_HEADS_UP) { 174 mGlobalSettings.registerContentObserverSync( 175 mGlobalSettings.getUriFor(HEADS_UP_NOTIFICATIONS_ENABLED), 176 true, 177 headsUpObserver); 178 mGlobalSettings.registerContentObserverSync( 179 mGlobalSettings.getUriFor(SETTING_HEADS_UP_TICKER), true, 180 headsUpObserver); 181 } 182 headsUpObserver.onChange(true); // set up 183 } 184 185 @Override addSuppressor(NotificationInterruptSuppressor suppressor)186 public void addSuppressor(NotificationInterruptSuppressor suppressor) { 187 mSuppressors.add(suppressor); 188 } 189 190 @Override removeSuppressor(NotificationInterruptSuppressor suppressor)191 public void removeSuppressor(NotificationInterruptSuppressor suppressor) { 192 mSuppressors.remove(suppressor); 193 } 194 195 @Override shouldBubbleUp(NotificationEntry entry)196 public boolean shouldBubbleUp(NotificationEntry entry) { 197 final StatusBarNotification sbn = entry.getSbn(); 198 199 if (!canAlertCommon(entry, false)) { 200 return false; 201 } 202 203 if (!canAlertAwakeCommon(entry, false)) { 204 return false; 205 } 206 207 if (!entry.canBubble()) { 208 mLogger.logNoBubbleNotAllowed(entry); 209 return false; 210 } 211 212 if (entry.getBubbleMetadata() == null 213 || (entry.getBubbleMetadata().getShortcutId() == null 214 && entry.getBubbleMetadata().getIntent() == null)) { 215 mLogger.logNoBubbleNoMetadata(entry); 216 return false; 217 } 218 219 return true; 220 } 221 222 223 @Override shouldHeadsUp(NotificationEntry entry)224 public boolean shouldHeadsUp(NotificationEntry entry) { 225 return checkHeadsUp(entry, true); 226 } 227 228 @Override checkHeadsUp(NotificationEntry entry, boolean log)229 public boolean checkHeadsUp(NotificationEntry entry, boolean log) { 230 if (mStatusBarStateController.isDozing()) { 231 return shouldHeadsUpWhenDozing(entry, log); 232 } else { 233 return shouldHeadsUpWhenAwake(entry, log); 234 } 235 } 236 237 /** 238 * When an entry was added, should we launch its fullscreen intent? Examples are Alarms or 239 * incoming calls. 240 */ 241 @Override shouldLaunchFullScreenIntentWhenAdded(NotificationEntry entry)242 public boolean shouldLaunchFullScreenIntentWhenAdded(NotificationEntry entry) { 243 FullScreenIntentDecision decision = getFullScreenIntentDecision(entry); 244 logFullScreenIntentDecision(entry, decision); 245 return decision.shouldLaunch; 246 } 247 248 // Given whether the relevant entry was suppressed by DND, and the full screen intent launch 249 // decision independent of the DND decision, returns the combined FullScreenIntentDecision that 250 // results. If the entry was suppressed by DND but the decision otherwise would launch the 251 // FSI, then it is suppressed *only* by DND, whereas (because the DND decision happens before 252 // all others) if the entry would not otherwise have launched the FSI, DND is the effective 253 // suppressor. 254 // 255 // If the entry was not suppressed by DND, just returns the given decision. 256 @NonNull getDecisionGivenSuppression(FullScreenIntentDecision decision, boolean suppressedByDND)257 private FullScreenIntentDecision getDecisionGivenSuppression(FullScreenIntentDecision decision, 258 boolean suppressedByDND) { 259 if (suppressedByDND) { 260 return decision.shouldLaunch 261 ? FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND 262 : FullScreenIntentDecision.NO_FSI_SUPPRESSED_BY_DND; 263 } 264 return decision; 265 } 266 267 @Override getFullScreenIntentDecision(@onNull NotificationEntry entry)268 public FullScreenIntentDecision getFullScreenIntentDecision(@NonNull NotificationEntry entry) { 269 if (entry.getSbn().getNotification().fullScreenIntent == null) { 270 if (entry.isStickyAndNotDemoted()) { 271 return FullScreenIntentDecision.NO_FSI_SHOW_STICKY_HUN; 272 } 273 return FullScreenIntentDecision.NO_FULL_SCREEN_INTENT; 274 } 275 276 // Boolean indicating whether this FSI would have been suppressed by DND. Because we 277 // want to be able to identify when something would have shown an FSI if not for being 278 // suppressed, we need to keep track of this value for future decisions. 279 boolean suppressedByDND = false; 280 281 // Never show FSI when suppressed by DND 282 if (entry.shouldSuppressFullScreenIntent()) { 283 suppressedByDND = true; 284 } 285 286 // Never show FSI if importance is not HIGH 287 if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) { 288 return getDecisionGivenSuppression(FullScreenIntentDecision.NO_FSI_NOT_IMPORTANT_ENOUGH, 289 suppressedByDND); 290 } 291 292 // If the notification has suppressive GroupAlertBehavior, block FSI and warn. 293 StatusBarNotification sbn = entry.getSbn(); 294 if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) { 295 // b/231322873: Detect and report an event when a notification has both an FSI and a 296 // suppressive groupAlertBehavior, and now correctly block the FSI from firing. 297 return getDecisionGivenSuppression( 298 FullScreenIntentDecision.NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR, 299 suppressedByDND); 300 } 301 302 // If the notification has suppressive BubbleMetadata, block FSI and warn. 303 Notification.BubbleMetadata bubbleMetadata = sbn.getNotification().getBubbleMetadata(); 304 if (bubbleMetadata != null && bubbleMetadata.isNotificationSuppressed()) { 305 // b/274759612: Detect and report an event when a notification has both an FSI and a 306 // suppressive BubbleMetadata, and now correctly block the FSI from firing. 307 return getDecisionGivenSuppression( 308 FullScreenIntentDecision.NO_FSI_SUPPRESSIVE_BUBBLE_METADATA, 309 suppressedByDND); 310 } 311 312 // Notification is coming from a suspended package, block FSI 313 if (entry.getRanking().isSuspended()) { 314 return getDecisionGivenSuppression(FullScreenIntentDecision.NO_FSI_SUSPENDED, 315 suppressedByDND); 316 } 317 318 // If the screen is off, then launch the FullScreenIntent 319 if (!mPowerManager.isInteractive()) { 320 return getDecisionGivenSuppression(FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE, 321 suppressedByDND); 322 } 323 324 // If the device is currently dreaming, then launch the FullScreenIntent 325 // We avoid using IDreamManager#isDreaming here as that method will return false during 326 // the dream's wake-up phase. 327 if (mStatusBarStateController.isDreaming()) { 328 return getDecisionGivenSuppression(FullScreenIntentDecision.FSI_DEVICE_IS_DREAMING, 329 suppressedByDND); 330 } 331 332 // If the keyguard is showing, then launch the FullScreenIntent 333 if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) { 334 return getDecisionGivenSuppression(FullScreenIntentDecision.FSI_KEYGUARD_SHOWING, 335 suppressedByDND); 336 } 337 338 // If the notification should HUN, then we don't need FSI 339 // Because this is not the heads-up decision-making point, and checking whether it would 340 // HUN, don't log this specific check. 341 if (checkHeadsUp(entry, /* log= */ false)) { 342 return getDecisionGivenSuppression(FullScreenIntentDecision.NO_FSI_EXPECTED_TO_HUN, 343 suppressedByDND); 344 } 345 346 // If notification won't HUN and keyguard is showing, launch the FSI. 347 if (mKeyguardStateController.isShowing()) { 348 if (mKeyguardStateController.isOccluded()) { 349 return getDecisionGivenSuppression( 350 FullScreenIntentDecision.FSI_KEYGUARD_OCCLUDED, 351 suppressedByDND); 352 } else { 353 // Likely LOCKED_SHADE, but launch FSI anyway 354 return getDecisionGivenSuppression(FullScreenIntentDecision.FSI_LOCKED_SHADE, 355 suppressedByDND); 356 } 357 } 358 359 // The device is not provisioned, launch FSI. 360 if (!mDeviceProvisionedController.isDeviceProvisioned()) { 361 return getDecisionGivenSuppression(FullScreenIntentDecision.FSI_NOT_PROVISIONED, 362 suppressedByDND); 363 } 364 365 // The current user hasn't completed setup, launch FSI. 366 if (!mDeviceProvisionedController.isCurrentUserSetup()) { 367 return getDecisionGivenSuppression(FullScreenIntentDecision.FSI_USER_SETUP_INCOMPLETE, 368 suppressedByDND); 369 } 370 371 // Detect the case determined by b/231322873 to launch FSI while device is in use, 372 // as blocked by the correct implementation, and report the event. 373 return getDecisionGivenSuppression(FullScreenIntentDecision.NO_FSI_NO_HUN_OR_KEYGUARD, 374 suppressedByDND); 375 } 376 377 @Override logFullScreenIntentDecision(NotificationEntry entry, FullScreenIntentDecision decision)378 public void logFullScreenIntentDecision(NotificationEntry entry, 379 FullScreenIntentDecision decision) { 380 final int uid = entry.getSbn().getUid(); 381 final String packageName = entry.getSbn().getPackageName(); 382 switch (decision) { 383 case NO_FULL_SCREEN_INTENT: 384 // explicitly prevent logging for this (frequent) case 385 return; 386 case NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR: 387 mEventLog.writeEvent(0x534e4554, "231322873", uid, 388 "groupAlertBehavior"); 389 mUiEventLogger.log(FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR, uid, 390 packageName); 391 mLogger.logNoFullscreenWarning(entry, 392 decision + ": GroupAlertBehavior will prevent HUN"); 393 return; 394 case NO_FSI_SUPPRESSIVE_BUBBLE_METADATA: 395 mEventLog.writeEvent(0x534e4554, "274759612", uid, 396 "bubbleMetadata"); 397 mUiEventLogger.log(FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA, uid, 398 packageName); 399 mLogger.logNoFullscreenWarning(entry, 400 decision + ": BubbleMetadata may prevent HUN"); 401 return; 402 case NO_FSI_NO_HUN_OR_KEYGUARD: 403 mEventLog.writeEvent(0x534e4554, "231322873", uid, 404 "no hun or keyguard"); 405 mUiEventLogger.log(FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD, uid, packageName); 406 mLogger.logNoFullscreenWarning(entry, 407 decision + ": Expected not to HUN while not on keyguard"); 408 return; 409 default: 410 if (decision.shouldLaunch) { 411 mLogger.logFullscreen(entry, decision.name()); 412 } else { 413 mLogger.logNoFullscreen(entry, decision.name()); 414 } 415 } 416 } shouldHeadsUpWhenAwake(NotificationEntry entry, boolean log)417 private boolean shouldHeadsUpWhenAwake(NotificationEntry entry, boolean log) { 418 StatusBarNotification sbn = entry.getSbn(); 419 420 if (!mUseHeadsUp) { 421 if (log) mLogger.logNoHeadsUpFeatureDisabled(); 422 return false; 423 } 424 425 if (!canAlertCommon(entry, log)) { 426 return false; 427 } 428 429 if (!canAlertHeadsUpCommon(entry, log)) { 430 return false; 431 } 432 433 if (!canAlertAwakeCommon(entry, log)) { 434 return false; 435 } 436 437 final boolean isSnoozedPackage = isSnoozedPackage(sbn); 438 final boolean hasFsi = sbn.getNotification().fullScreenIntent != null; 439 440 // Assume any notification with an FSI is time-sensitive (like an alarm or incoming call) 441 // and ignore whether HUNs have been snoozed for the package. 442 if (isSnoozedPackage && !hasFsi) { 443 if (log) mLogger.logNoHeadsUpPackageSnoozed(entry); 444 return false; 445 } 446 447 boolean inShade = mStatusBarStateController.getState() == SHADE; 448 boolean bubblesCanShowNotification = 449 mBubbles.isPresent() && mBubbles.get().canShowBubbleNotification(); 450 if (entry.isBubble() && inShade && bubblesCanShowNotification) { 451 if (log) mLogger.logNoHeadsUpAlreadyBubbled(entry); 452 return false; 453 } 454 455 if (entry.shouldSuppressPeek()) { 456 if (log) mLogger.logNoHeadsUpSuppressedByDnd(entry); 457 return false; 458 } 459 460 if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) { 461 if (log) mLogger.logNoHeadsUpNotImportant(entry); 462 return false; 463 } 464 465 boolean inUse = mPowerManager.isScreenOn() && !mStatusBarStateController.isDreaming(); 466 467 if (!inUse) { 468 if (log) mLogger.logNoHeadsUpNotInUse(entry); 469 return false; 470 } 471 472 if (shouldSuppressHeadsUpWhenAwakeForOldWhen(entry, log)) { 473 return false; 474 } 475 476 for (int i = 0; i < mSuppressors.size(); i++) { 477 if (mSuppressors.get(i).suppressAwakeHeadsUp(entry)) { 478 if (log) mLogger.logNoHeadsUpSuppressedBy(entry, mSuppressors.get(i)); 479 return false; 480 } 481 } 482 483 if (isSnoozedPackage) { 484 if (log) { 485 mLogger.logHeadsUpPackageSnoozeBypassedHasFsi(entry); 486 final int uid = entry.getSbn().getUid(); 487 final String packageName = entry.getSbn().getPackageName(); 488 mUiEventLogger.log(HUN_SNOOZE_BYPASSED_POTENTIALLY_SUPPRESSED_FSI, uid, 489 packageName); 490 } 491 492 return true; 493 } 494 495 if (log) mLogger.logHeadsUp(entry); 496 return true; 497 } 498 499 /** 500 * Whether or not the notification should "pulse" on the user's display when the phone is 501 * dozing. This displays the ambient view of the notification. 502 * 503 * @param entry the entry to check 504 * @return true if the entry should ambient pulse, false otherwise 505 */ shouldHeadsUpWhenDozing(NotificationEntry entry, boolean log)506 private boolean shouldHeadsUpWhenDozing(NotificationEntry entry, boolean log) { 507 if (!mAmbientDisplayConfiguration.pulseOnNotificationEnabled(mUserTracker.getUserId())) { 508 if (log) mLogger.logNoPulsingSettingDisabled(entry); 509 return false; 510 } 511 512 if (mBatteryController.isAodPowerSave()) { 513 if (log) mLogger.logNoPulsingBatteryDisabled(entry); 514 return false; 515 } 516 517 if (!canAlertCommon(entry, log)) { 518 if (log) mLogger.logNoPulsingNoAlert(entry); 519 return false; 520 } 521 522 if (!canAlertHeadsUpCommon(entry, log)) { 523 if (log) mLogger.logNoPulsingNoAlert(entry); 524 return false; 525 } 526 527 if (entry.shouldSuppressAmbient()) { 528 if (log) mLogger.logNoPulsingNoAmbientEffect(entry); 529 return false; 530 } 531 532 if (entry.getRanking().getLockscreenVisibilityOverride() 533 == Notification.VISIBILITY_PRIVATE) { 534 if (log) mLogger.logNoPulsingNotificationHiddenOverride(entry); 535 return false; 536 } 537 538 if (entry.getImportance() < NotificationManager.IMPORTANCE_DEFAULT) { 539 if (log) mLogger.logNoPulsingNotImportant(entry); 540 return false; 541 } 542 if (log) mLogger.logPulsing(entry); 543 return true; 544 } 545 546 /** 547 * Common checks between regular & AOD heads up and bubbles. 548 * 549 * @param entry the entry to check 550 * @param log whether or not to log the results of these checks 551 * @return true if these checks pass, false if the notification should not alert 552 */ canAlertCommon(NotificationEntry entry, boolean log)553 private boolean canAlertCommon(NotificationEntry entry, boolean log) { 554 for (int i = 0; i < mSuppressors.size(); i++) { 555 if (mSuppressors.get(i).suppressInterruptions(entry)) { 556 if (log) { 557 mLogger.logNoAlertingSuppressedBy(entry, mSuppressors.get(i), 558 /* awake */ false); 559 } 560 return false; 561 } 562 } 563 564 if (entry.getRanking().isSuspended()) { 565 if (log) { 566 mLogger.logNoAlertingAppSuspended(entry); 567 } 568 return false; 569 } 570 571 if (mKeyguardNotificationVisibilityProvider.shouldHideNotification(entry)) { 572 if (log) mLogger.logNoAlertingNotificationHidden(entry); 573 return false; 574 } 575 576 return true; 577 } 578 579 /** 580 * Common checks for heads up notifications on regular and AOD displays. 581 * 582 * @param entry the entry to check 583 * @param log whether or not to log the results of these checks 584 * @return true if these checks pass, false if the notification should not alert 585 */ canAlertHeadsUpCommon(NotificationEntry entry, boolean log)586 private boolean canAlertHeadsUpCommon(NotificationEntry entry, boolean log) { 587 StatusBarNotification sbn = entry.getSbn(); 588 589 // Don't alert notifications that are suppressed due to group alert behavior 590 if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) { 591 if (log) mLogger.logNoAlertingGroupAlertBehavior(entry); 592 return false; 593 } 594 595 if (entry.hasJustLaunchedFullScreenIntent()) { 596 if (log) mLogger.logNoAlertingRecentFullscreen(entry); 597 return false; 598 } 599 600 return true; 601 } 602 603 /** 604 * Common checks between alerts that occur while the device is awake (heads up & bubbles). 605 * 606 * @param entry the entry to check 607 * @return true if these checks pass, false if the notification should not alert 608 */ canAlertAwakeCommon(NotificationEntry entry, boolean log)609 private boolean canAlertAwakeCommon(NotificationEntry entry, boolean log) { 610 StatusBarNotification sbn = entry.getSbn(); 611 612 for (int i = 0; i < mSuppressors.size(); i++) { 613 if (mSuppressors.get(i).suppressAwakeInterruptions(entry)) { 614 if (log) { 615 mLogger.logNoAlertingSuppressedBy(entry, mSuppressors.get(i), /* awake */ true); 616 } 617 return false; 618 } 619 } 620 return true; 621 } 622 isSnoozedPackage(StatusBarNotification sbn)623 private boolean isSnoozedPackage(StatusBarNotification sbn) { 624 return mHeadsUpManager.isSnoozed(sbn.getPackageName()); 625 } 626 shouldSuppressHeadsUpWhenAwakeForOldWhen(NotificationEntry entry, boolean log)627 private boolean shouldSuppressHeadsUpWhenAwakeForOldWhen(NotificationEntry entry, boolean log) { 628 final Notification notification = entry.getSbn().getNotification(); 629 if (notification == null) { 630 return false; 631 } 632 633 final long when = notification.getWhen(); 634 final long now = mSystemClock.currentTimeMillis(); 635 final long age = now - when; 636 637 if (age < MAX_HUN_WHEN_AGE_MS) { 638 return false; 639 } 640 641 if (when <= 0) { 642 // Some notifications (including many system notifications) are posted with the "when" 643 // field set to 0. Nothing in the Javadocs for Notification mentions a special meaning 644 // for a "when" of 0, but Android didn't even exist at the dawn of the Unix epoch. 645 // Therefore, assume that these notifications effectively don't have a "when" value, 646 // and don't suppress HUNs. 647 if (log) mLogger.logMaybeHeadsUpDespiteOldWhen(entry, when, age, "when <= 0"); 648 return false; 649 } 650 651 if (notification.fullScreenIntent != null) { 652 if (log) mLogger.logMaybeHeadsUpDespiteOldWhen(entry, when, age, "full-screen intent"); 653 return false; 654 } 655 656 if (notification.isForegroundService()) { 657 if (log) mLogger.logMaybeHeadsUpDespiteOldWhen(entry, when, age, "foreground service"); 658 return false; 659 } 660 661 if (notification.isUserInitiatedJob()) { 662 if (log) mLogger.logMaybeHeadsUpDespiteOldWhen(entry, when, age, "user initiated job"); 663 return false; 664 } 665 666 if (log) mLogger.logNoHeadsUpOldWhen(entry, when, age); 667 final int uid = entry.getSbn().getUid(); 668 final String packageName = entry.getSbn().getPackageName(); 669 mUiEventLogger.log(NotificationInterruptEvent.HUN_SUPPRESSED_OLD_WHEN, uid, packageName); 670 return true; 671 } 672 673 public static final long MAX_HUN_WHEN_AGE_MS = 24 * 60 * 60 * 1000; 674 } 675