• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.phone;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.Context;
22 import android.content.res.Resources;
23 import android.graphics.Region;
24 import android.util.Pools;
25 
26 import androidx.collection.ArraySet;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 import com.android.systemui.Dumpable;
30 import com.android.systemui.R;
31 import com.android.systemui.plugins.statusbar.StatusBarStateController;
32 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
33 import com.android.systemui.statusbar.StatusBarState;
34 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
35 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
36 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
37 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
38 import com.android.systemui.statusbar.policy.ConfigurationController;
39 import com.android.systemui.statusbar.policy.HeadsUpManager;
40 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
41 
42 import java.io.FileDescriptor;
43 import java.io.PrintWriter;
44 import java.util.ArrayList;
45 import java.util.HashSet;
46 import java.util.List;
47 import java.util.Stack;
48 
49 /**
50  * A implementation of HeadsUpManager for phone and car.
51  */
52 public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
53         VisualStabilityManager.Callback, OnHeadsUpChangedListener {
54     private static final String TAG = "HeadsUpManagerPhone";
55 
56     @VisibleForTesting
57     final int mExtensionTime;
58     private final KeyguardBypassController mBypassController;
59     private final GroupMembershipManager mGroupMembershipManager;
60     private final List<OnHeadsUpPhoneListenerChange> mHeadsUpPhoneListeners = new ArrayList<>();
61     private final int mAutoHeadsUpNotificationDecay;
62     // TODO (b/162832756): remove visual stability manager when migrating to new pipeline
63     private VisualStabilityManager mVisualStabilityManager;
64     private boolean mReleaseOnExpandFinish;
65 
66     private boolean mTrackingHeadsUp;
67     private HashSet<String> mSwipedOutKeys = new HashSet<>();
68     private HashSet<NotificationEntry> mEntriesToRemoveAfterExpand = new HashSet<>();
69     private HashSet<String> mKeysToRemoveWhenLeavingKeyguard = new HashSet<>();
70     private ArraySet<NotificationEntry> mEntriesToRemoveWhenReorderingAllowed
71             = new ArraySet<>();
72     private boolean mIsExpanded;
73     private boolean mHeadsUpGoingAway;
74     private int mStatusBarState;
75     private AnimationStateHandler mAnimationStateHandler;
76     private int mHeadsUpInset;
77 
78     // Used for determining the region for touch interaction
79     private final Region mTouchableRegion = new Region();
80 
81     private final Pools.Pool<HeadsUpEntryPhone> mEntryPool = new Pools.Pool<HeadsUpEntryPhone>() {
82         private Stack<HeadsUpEntryPhone> mPoolObjects = new Stack<>();
83 
84         @Override
85         public HeadsUpEntryPhone acquire() {
86             if (!mPoolObjects.isEmpty()) {
87                 return mPoolObjects.pop();
88             }
89             return new HeadsUpEntryPhone();
90         }
91 
92         @Override
93         public boolean release(@NonNull HeadsUpEntryPhone instance) {
94             mPoolObjects.push(instance);
95             return true;
96         }
97     };
98 
99     ///////////////////////////////////////////////////////////////////////////////////////////////
100     //  Constructor:
101 
HeadsUpManagerPhone(@onNull final Context context, StatusBarStateController statusBarStateController, KeyguardBypassController bypassController, GroupMembershipManager groupMembershipManager, ConfigurationController configurationController)102     public HeadsUpManagerPhone(@NonNull final Context context,
103             StatusBarStateController statusBarStateController,
104             KeyguardBypassController bypassController,
105             GroupMembershipManager groupMembershipManager,
106             ConfigurationController configurationController) {
107         super(context);
108         Resources resources = mContext.getResources();
109         mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time);
110         mAutoHeadsUpNotificationDecay = resources.getInteger(
111                 R.integer.auto_heads_up_notification_decay);
112         statusBarStateController.addCallback(mStatusBarStateListener);
113         mBypassController = bypassController;
114         mGroupMembershipManager = groupMembershipManager;
115 
116         updateResources();
117         configurationController.addCallback(new ConfigurationController.ConfigurationListener() {
118             @Override
119             public void onDensityOrFontScaleChanged() {
120                 updateResources();
121             }
122 
123             @Override
124             public void onOverlayChanged() {
125                 updateResources();
126             }
127         });
128     }
129 
setup(VisualStabilityManager visualStabilityManager)130     void setup(VisualStabilityManager visualStabilityManager) {
131         mVisualStabilityManager = visualStabilityManager;
132     }
133 
setAnimationStateHandler(AnimationStateHandler handler)134     public void setAnimationStateHandler(AnimationStateHandler handler) {
135         mAnimationStateHandler = handler;
136     }
137 
updateResources()138     private void updateResources() {
139         Resources resources = mContext.getResources();
140         mHeadsUpInset =
141                 resources.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height)
142                         + resources.getDimensionPixelSize(R.dimen.heads_up_status_bar_padding);
143     }
144 
145     ///////////////////////////////////////////////////////////////////////////////////////////////
146     //  Public methods:
147 
148     /**
149      * Add a listener to receive callbacks onHeadsUpGoingAway
150      */
addHeadsUpPhoneListener(OnHeadsUpPhoneListenerChange listener)151     void addHeadsUpPhoneListener(OnHeadsUpPhoneListenerChange listener) {
152         mHeadsUpPhoneListeners.add(listener);
153     }
154 
155     /**
156      * Gets the touchable region needed for heads up notifications. Returns null if no touchable
157      * region is required (ie: no heads up notification currently exists).
158      */
getTouchableRegion()159     @Nullable Region getTouchableRegion() {
160         NotificationEntry topEntry = getTopEntry();
161 
162         // This call could be made in an inconsistent state while the pinnedMode hasn't been
163         // updated yet, but callbacks leading out of the headsUp manager, querying it. Let's
164         // therefore also check if the topEntry is null.
165         if (!hasPinnedHeadsUp() || topEntry == null) {
166             return null;
167         } else {
168             if (topEntry.isChildInGroup()) {
169                 final NotificationEntry groupSummary =
170                         mGroupMembershipManager.getGroupSummary(topEntry);
171                 if (groupSummary != null) {
172                     topEntry = groupSummary;
173                 }
174             }
175             ExpandableNotificationRow topRow = topEntry.getRow();
176             int[] tmpArray = new int[2];
177             topRow.getLocationOnScreen(tmpArray);
178             int minX = tmpArray[0];
179             int maxX = tmpArray[0] + topRow.getWidth();
180             int height = topRow.getIntrinsicHeight();
181             mTouchableRegion.set(minX, 0, maxX, mHeadsUpInset + height);
182             return mTouchableRegion;
183         }
184     }
185 
186     /**
187      * Decides whether a click is invalid for a notification, i.e it has not been shown long enough
188      * that a user might have consciously clicked on it.
189      *
190      * @param key the key of the touched notification
191      * @return whether the touch is invalid and should be discarded
192      */
shouldSwallowClick(@onNull String key)193     boolean shouldSwallowClick(@NonNull String key) {
194         HeadsUpManager.HeadsUpEntry entry = getHeadsUpEntry(key);
195         return entry != null && mClock.currentTimeMillis() < entry.mPostTime;
196     }
197 
onExpandingFinished()198     public void onExpandingFinished() {
199         if (mReleaseOnExpandFinish) {
200             releaseAllImmediately();
201             mReleaseOnExpandFinish = false;
202         } else {
203             for (NotificationEntry entry : mEntriesToRemoveAfterExpand) {
204                 if (isAlerting(entry.getKey())) {
205                     // Maybe the heads-up was removed already
206                     removeAlertEntry(entry.getKey());
207                 }
208             }
209         }
210         mEntriesToRemoveAfterExpand.clear();
211     }
212 
213     /**
214      * Sets the tracking-heads-up flag. If the flag is true, HeadsUpManager doesn't remove the entry
215      * from the list even after a Heads Up Notification is gone.
216      */
setTrackingHeadsUp(boolean trackingHeadsUp)217     public void setTrackingHeadsUp(boolean trackingHeadsUp) {
218         mTrackingHeadsUp = trackingHeadsUp;
219     }
220 
221     /**
222      * Notify that the status bar panel gets expanded or collapsed.
223      *
224      * @param isExpanded True to notify expanded, false to notify collapsed.
225      */
setIsPanelExpanded(boolean isExpanded)226     void setIsPanelExpanded(boolean isExpanded) {
227         if (isExpanded != mIsExpanded) {
228             mIsExpanded = isExpanded;
229             if (isExpanded) {
230                 mHeadsUpGoingAway = false;
231             }
232         }
233     }
234 
235     @Override
isEntryAutoHeadsUpped(String key)236     public boolean isEntryAutoHeadsUpped(String key) {
237         HeadsUpEntryPhone headsUpEntryPhone = getHeadsUpEntryPhone(key);
238         if (headsUpEntryPhone == null) {
239             return false;
240         }
241         return headsUpEntryPhone.isAutoHeadsUp();
242     }
243 
244     /**
245      * Set that we are exiting the headsUp pinned mode, but some notifications might still be
246      * animating out. This is used to keep the touchable regions in a reasonable state.
247      */
setHeadsUpGoingAway(boolean headsUpGoingAway)248     void setHeadsUpGoingAway(boolean headsUpGoingAway) {
249         if (headsUpGoingAway != mHeadsUpGoingAway) {
250             mHeadsUpGoingAway = headsUpGoingAway;
251             for (OnHeadsUpPhoneListenerChange listener : mHeadsUpPhoneListeners) {
252                 listener.onHeadsUpGoingAwayStateChanged(headsUpGoingAway);
253             }
254         }
255     }
256 
isHeadsUpGoingAway()257     boolean isHeadsUpGoingAway() {
258         return mHeadsUpGoingAway;
259     }
260 
261     /**
262      * Notifies that a remote input textbox in notification gets active or inactive.
263      *
264      * @param entry             The entry of the target notification.
265      * @param remoteInputActive True to notify active, False to notify inactive.
266      */
setRemoteInputActive( @onNull NotificationEntry entry, boolean remoteInputActive)267     public void setRemoteInputActive(
268             @NonNull NotificationEntry entry, boolean remoteInputActive) {
269         HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(entry.getKey());
270         if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) {
271             headsUpEntry.remoteInputActive = remoteInputActive;
272             if (remoteInputActive) {
273                 headsUpEntry.removeAutoRemovalCallbacks();
274             } else {
275                 headsUpEntry.updateEntry(false /* updatePostTime */);
276             }
277         }
278     }
279 
280     /**
281      * Sets whether an entry's menu row is exposed and therefore it should stick in the heads up
282      * area if it's pinned until it's hidden again.
283      */
setMenuShown(@onNull NotificationEntry entry, boolean menuShown)284     public void setMenuShown(@NonNull NotificationEntry entry, boolean menuShown) {
285         HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.getKey());
286         if (headsUpEntry instanceof HeadsUpEntryPhone && entry.isRowPinned()) {
287             ((HeadsUpEntryPhone) headsUpEntry).setMenuShownPinned(menuShown);
288         }
289     }
290 
291     /**
292      * Extends the lifetime of the currently showing pulsing notification so that the pulse lasts
293      * longer.
294      */
extendHeadsUp()295     public void extendHeadsUp() {
296         HeadsUpEntryPhone topEntry = getTopHeadsUpEntryPhone();
297         if (topEntry == null) {
298             return;
299         }
300         topEntry.extendPulse();
301     }
302 
303     ///////////////////////////////////////////////////////////////////////////////////////////////
304     //  HeadsUpManager public methods overrides and overloads:
305 
306     @Override
isTrackingHeadsUp()307     public boolean isTrackingHeadsUp() {
308         return mTrackingHeadsUp;
309     }
310 
311     @Override
snooze()312     public void snooze() {
313         super.snooze();
314         mReleaseOnExpandFinish = true;
315     }
316 
addSwipedOutNotification(@onNull String key)317     public void addSwipedOutNotification(@NonNull String key) {
318         mSwipedOutKeys.add(key);
319     }
320 
removeNotification(@onNull String key, boolean releaseImmediately, boolean animate)321     public boolean removeNotification(@NonNull String key, boolean releaseImmediately,
322             boolean animate) {
323         if (animate) {
324             return removeNotification(key, releaseImmediately);
325         } else {
326             mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
327             boolean removed = removeNotification(key, releaseImmediately);
328             mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(true);
329             return removed;
330         }
331     }
332 
333     ///////////////////////////////////////////////////////////////////////////////////////////////
334     //  Dumpable overrides:
335 
336     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)337     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
338         pw.println("HeadsUpManagerPhone state:");
339         dumpInternal(fd, pw, args);
340     }
341 
342     @Override
shouldExtendLifetime(NotificationEntry entry)343     public boolean shouldExtendLifetime(NotificationEntry entry) {
344         // We should not defer the removal if reordering isn't allowed since otherwise
345         // these won't disappear until reordering is allowed again, which happens only once
346         // the notification panel is collapsed again.
347         return mVisualStabilityManager.isReorderingAllowed() && super.shouldExtendLifetime(entry);
348     }
349 
350     ///////////////////////////////////////////////////////////////////////////////////////////////
351     //  VisualStabilityManager.Callback overrides:
352 
353     @Override
onChangeAllowed()354     public void onChangeAllowed() {
355         mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
356         for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) {
357             if (isAlerting(entry.getKey())) {
358                 // Maybe the heads-up was removed already
359                 removeAlertEntry(entry.getKey());
360             }
361         }
362         mEntriesToRemoveWhenReorderingAllowed.clear();
363         mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(true);
364     }
365 
366     ///////////////////////////////////////////////////////////////////////////////////////////////
367     //  HeadsUpManager utility (protected) methods overrides:
368 
369     @Override
createAlertEntry()370     protected HeadsUpEntry createAlertEntry() {
371         return mEntryPool.acquire();
372     }
373 
374     @Override
onAlertEntryRemoved(AlertEntry alertEntry)375     protected void onAlertEntryRemoved(AlertEntry alertEntry) {
376         mKeysToRemoveWhenLeavingKeyguard.remove(alertEntry.mEntry.getKey());
377         super.onAlertEntryRemoved(alertEntry);
378         mEntryPool.release((HeadsUpEntryPhone) alertEntry);
379     }
380 
381     @Override
shouldHeadsUpBecomePinned(NotificationEntry entry)382     protected boolean shouldHeadsUpBecomePinned(NotificationEntry entry) {
383         boolean pin = mStatusBarState == StatusBarState.SHADE && !mIsExpanded;
384         if (mBypassController.getBypassEnabled()) {
385             pin |= mStatusBarState == StatusBarState.KEYGUARD;
386         }
387         return pin || super.shouldHeadsUpBecomePinned(entry);
388     }
389 
390     @Override
dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args)391     protected void dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args) {
392         super.dumpInternal(fd, pw, args);
393         pw.print("  mBarState=");
394         pw.println(mStatusBarState);
395         pw.print("  mTouchableRegion=");
396         pw.println(mTouchableRegion);
397     }
398 
399     ///////////////////////////////////////////////////////////////////////////////////////////////
400     //  Private utility methods:
401 
402     @Nullable
getHeadsUpEntryPhone(@onNull String key)403     private HeadsUpEntryPhone getHeadsUpEntryPhone(@NonNull String key) {
404         return (HeadsUpEntryPhone) mAlertEntries.get(key);
405     }
406 
407     @Nullable
getTopHeadsUpEntryPhone()408     private HeadsUpEntryPhone getTopHeadsUpEntryPhone() {
409         return (HeadsUpEntryPhone) getTopHeadsUpEntry();
410     }
411 
412     @Override
canRemoveImmediately(@onNull String key)413     protected boolean canRemoveImmediately(@NonNull String key) {
414         if (mSwipedOutKeys.contains(key)) {
415             // We always instantly dismiss views being manually swiped out.
416             mSwipedOutKeys.remove(key);
417             return true;
418         }
419 
420         HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(key);
421         HeadsUpEntryPhone topEntry = getTopHeadsUpEntryPhone();
422 
423         return headsUpEntry == null || headsUpEntry != topEntry || super.canRemoveImmediately(key);
424     }
425 
426     ///////////////////////////////////////////////////////////////////////////////////////////////
427     //  HeadsUpEntryPhone:
428 
429     protected class HeadsUpEntryPhone extends HeadsUpManager.HeadsUpEntry {
430 
431         private boolean mMenuShownPinned;
432 
433         /**
434          * If the time this entry has been on was extended
435          */
436         private boolean extended;
437 
438         /**
439          * Was this entry received while on keyguard
440          */
441         private boolean mIsAutoHeadsUp;
442 
443 
444         @Override
isSticky()445         public boolean isSticky() {
446             return super.isSticky() || mMenuShownPinned;
447         }
448 
setEntry(@onNull final NotificationEntry entry)449         public void setEntry(@NonNull final NotificationEntry entry) {
450             Runnable removeHeadsUpRunnable = () -> {
451                 if (!mVisualStabilityManager.isReorderingAllowed()
452                         // We don't want to allow reordering while pulsing, but headsup need to
453                         // time out anyway
454                         && !entry.showingPulsing()) {
455                     mEntriesToRemoveWhenReorderingAllowed.add(entry);
456                     mVisualStabilityManager.addReorderingAllowedCallback(HeadsUpManagerPhone.this,
457                             false  /* persistent */);
458                 } else if (mTrackingHeadsUp) {
459                     mEntriesToRemoveAfterExpand.add(entry);
460                 } else if (mIsAutoHeadsUp && mStatusBarState == StatusBarState.KEYGUARD) {
461                     mKeysToRemoveWhenLeavingKeyguard.add(entry.getKey());
462                 } else {
463                     removeAlertEntry(entry.getKey());
464                 }
465             };
466 
467             setEntry(entry, removeHeadsUpRunnable);
468         }
469 
470         @Override
updateEntry(boolean updatePostTime)471         public void updateEntry(boolean updatePostTime) {
472             mIsAutoHeadsUp = mEntry.isAutoHeadsUp();
473             super.updateEntry(updatePostTime);
474 
475             if (mEntriesToRemoveAfterExpand.contains(mEntry)) {
476                 mEntriesToRemoveAfterExpand.remove(mEntry);
477             }
478             if (mEntriesToRemoveWhenReorderingAllowed.contains(mEntry)) {
479                 mEntriesToRemoveWhenReorderingAllowed.remove(mEntry);
480             }
481             mKeysToRemoveWhenLeavingKeyguard.remove(mEntry.getKey());
482         }
483 
484         @Override
setExpanded(boolean expanded)485         public void setExpanded(boolean expanded) {
486             if (this.expanded == expanded) {
487                 return;
488             }
489 
490             this.expanded = expanded;
491             if (expanded) {
492                 removeAutoRemovalCallbacks();
493             } else {
494                 updateEntry(false /* updatePostTime */);
495             }
496         }
497 
setMenuShownPinned(boolean menuShownPinned)498         public void setMenuShownPinned(boolean menuShownPinned) {
499             if (mMenuShownPinned == menuShownPinned) {
500                 return;
501             }
502 
503             mMenuShownPinned = menuShownPinned;
504             if (menuShownPinned) {
505                 removeAutoRemovalCallbacks();
506             } else {
507                 updateEntry(false /* updatePostTime */);
508             }
509         }
510 
511         @Override
reset()512         public void reset() {
513             super.reset();
514             mMenuShownPinned = false;
515             extended = false;
516             mIsAutoHeadsUp = false;
517         }
518 
extendPulse()519         private void extendPulse() {
520             if (!extended) {
521                 extended = true;
522                 updateEntry(false);
523             }
524         }
525 
526         @Override
compareTo(AlertEntry alertEntry)527         public int compareTo(AlertEntry alertEntry) {
528             HeadsUpEntryPhone headsUpEntry = (HeadsUpEntryPhone) alertEntry;
529             boolean autoShown = isAutoHeadsUp();
530             boolean otherAutoShown = headsUpEntry.isAutoHeadsUp();
531             if (autoShown && !otherAutoShown) {
532                 return 1;
533             } else if (!autoShown && otherAutoShown) {
534                 return -1;
535             }
536             return super.compareTo(alertEntry);
537         }
538 
539         @Override
calculateFinishTime()540         protected long calculateFinishTime() {
541             return mPostTime + getDecayDuration() + (extended ? mExtensionTime : 0);
542         }
543 
getDecayDuration()544         private int getDecayDuration() {
545             if (isAutoHeadsUp()) {
546                 return getRecommendedHeadsUpTimeoutMs(mAutoHeadsUpNotificationDecay);
547             } else {
548                 return getRecommendedHeadsUpTimeoutMs(mAutoDismissNotificationDecay);
549             }
550         }
551 
isAutoHeadsUp()552         private boolean isAutoHeadsUp() {
553             return mIsAutoHeadsUp;
554         }
555     }
556 
557     public interface AnimationStateHandler {
setHeadsUpGoingAwayAnimationsAllowed(boolean allowed)558         void setHeadsUpGoingAwayAnimationsAllowed(boolean allowed);
559     }
560 
561     /**
562      * Listener to register for HeadsUpNotification Phone changes.
563      */
564     public interface OnHeadsUpPhoneListenerChange {
565         /**
566          * Called when a heads up notification is 'going away' or no longer 'going away'.
567          * See {@link HeadsUpManagerPhone#setHeadsUpGoingAway}.
568          */
onHeadsUpGoingAwayStateChanged(boolean headsUpGoingAway)569         void onHeadsUpGoingAwayStateChanged(boolean headsUpGoingAway);
570     }
571 
572     private final StateListener mStatusBarStateListener = new StateListener() {
573         @Override
574         public void onStateChanged(int newState) {
575             boolean wasKeyguard = mStatusBarState == StatusBarState.KEYGUARD;
576             boolean isKeyguard = newState == StatusBarState.KEYGUARD;
577             mStatusBarState = newState;
578             if (wasKeyguard && !isKeyguard && mKeysToRemoveWhenLeavingKeyguard.size() != 0) {
579                 String[] keys = mKeysToRemoveWhenLeavingKeyguard.toArray(new String[0]);
580                 for (String key : keys) {
581                     removeAlertEntry(key);
582                 }
583                 mKeysToRemoveWhenLeavingKeyguard.clear();
584             }
585             if (wasKeyguard && !isKeyguard && mBypassController.getBypassEnabled()) {
586                 ArrayList<String> keysToRemove = new ArrayList<>();
587                 for (AlertEntry entry : mAlertEntries.values()) {
588                     if (entry.mEntry != null && entry.mEntry.isBubble() && !entry.isSticky()) {
589                         keysToRemove.add(entry.mEntry.getKey());
590                     }
591                 }
592                 for (String key : keysToRemove) {
593                     removeAlertEntry(key);
594                 }
595             }
596         }
597 
598         @Override
599         public void onDozingChanged(boolean isDozing) {
600             if (!isDozing) {
601                 // Let's make sure all huns we got while dozing time out within the normal timeout
602                 // duration. Otherwise they could get stuck for a very long time
603                 for (AlertEntry entry : mAlertEntries.values()) {
604                     entry.updateEntry(true /* updatePostTime */);
605                 }
606             }
607         }
608     };
609 }
610