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