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