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