1 /* 2 * Copyright (C) 2014 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.stack; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ValueAnimator; 22 import android.util.Property; 23 import android.view.View; 24 import android.view.animation.Interpolator; 25 26 import com.android.keyguard.KeyguardSliceView; 27 import com.android.systemui.Interpolators; 28 import com.android.systemui.R; 29 import com.android.systemui.statusbar.NotificationShelf; 30 import com.android.systemui.statusbar.StatusBarIconView; 31 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; 32 import com.android.systemui.statusbar.notification.row.ExpandableView; 33 34 import java.util.ArrayList; 35 import java.util.HashSet; 36 import java.util.Stack; 37 38 /** 39 * An stack state animator which handles animations to new StackScrollStates 40 */ 41 public class StackStateAnimator { 42 43 public static final int ANIMATION_DURATION_STANDARD = 360; 44 public static final int ANIMATION_DURATION_WAKEUP = 500; 45 public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448; 46 public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464; 47 public static final int ANIMATION_DURATION_SWIPE = 260; 48 public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220; 49 public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150; 50 public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 550; 51 public static final int ANIMATION_DURATION_HEADS_UP_APPEAR_CLOSED 52 = (int) (ANIMATION_DURATION_HEADS_UP_APPEAR 53 * HeadsUpAppearInterpolator.getFractionUntilOvershoot()); 54 public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 300; 55 public static final int ANIMATION_DURATION_PULSE_APPEAR = 56 KeyguardSliceView.DEFAULT_ANIM_DURATION; 57 public static final int ANIMATION_DURATION_BLOCKING_HELPER_FADE = 240; 58 public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80; 59 public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32; 60 public static final int ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE = 48; 61 public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2; 62 public static final int ANIMATION_DELAY_HEADS_UP = 120; 63 public static final int ANIMATION_DELAY_HEADS_UP_CLICKED= 120; 64 private static final int MAX_STAGGER_COUNT = 5; 65 66 private final int mGoToFullShadeAppearingTranslation; 67 private final int mPulsingAppearingTranslation; 68 private final ExpandableViewState mTmpState = new ExpandableViewState(); 69 private final AnimationProperties mAnimationProperties; 70 public NotificationStackScrollLayout mHostLayout; 71 private ArrayList<NotificationStackScrollLayout.AnimationEvent> mNewEvents = 72 new ArrayList<>(); 73 private ArrayList<View> mNewAddChildren = new ArrayList<>(); 74 private HashSet<View> mHeadsUpAppearChildren = new HashSet<>(); 75 private HashSet<View> mHeadsUpDisappearChildren = new HashSet<>(); 76 private HashSet<Animator> mAnimatorSet = new HashSet<>(); 77 private Stack<AnimatorListenerAdapter> mAnimationListenerPool = new Stack<>(); 78 private AnimationFilter mAnimationFilter = new AnimationFilter(); 79 private long mCurrentLength; 80 private long mCurrentAdditionalDelay; 81 82 private ValueAnimator mTopOverScrollAnimator; 83 private ValueAnimator mBottomOverScrollAnimator; 84 private int mHeadsUpAppearHeightBottom; 85 private boolean mShadeExpanded; 86 private ArrayList<ExpandableView> mTransientViewsToRemove = new ArrayList<>(); 87 private NotificationShelf mShelf; 88 private float mStatusBarIconLocation; 89 private int[] mTmpLocation = new int[2]; 90 StackStateAnimator(NotificationStackScrollLayout hostLayout)91 public StackStateAnimator(NotificationStackScrollLayout hostLayout) { 92 mHostLayout = hostLayout; 93 mGoToFullShadeAppearingTranslation = 94 hostLayout.getContext().getResources().getDimensionPixelSize( 95 R.dimen.go_to_full_shade_appearing_translation); 96 mPulsingAppearingTranslation = 97 hostLayout.getContext().getResources().getDimensionPixelSize( 98 R.dimen.pulsing_notification_appear_translation); 99 mAnimationProperties = new AnimationProperties() { 100 @Override 101 public AnimationFilter getAnimationFilter() { 102 return mAnimationFilter; 103 } 104 105 @Override 106 public AnimatorListenerAdapter getAnimationFinishListener() { 107 return getGlobalAnimationFinishedListener(); 108 } 109 110 @Override 111 public boolean wasAdded(View view) { 112 return mNewAddChildren.contains(view); 113 } 114 115 @Override 116 public Interpolator getCustomInterpolator(View child, Property property) { 117 if (mHeadsUpAppearChildren.contains(child) && View.TRANSLATION_Y.equals(property)) { 118 return Interpolators.HEADS_UP_APPEAR; 119 } 120 return null; 121 } 122 }; 123 } 124 isRunning()125 public boolean isRunning() { 126 return !mAnimatorSet.isEmpty(); 127 } 128 startAnimationForEvents( ArrayList<NotificationStackScrollLayout.AnimationEvent> mAnimationEvents, long additionalDelay)129 public void startAnimationForEvents( 130 ArrayList<NotificationStackScrollLayout.AnimationEvent> mAnimationEvents, 131 long additionalDelay) { 132 133 processAnimationEvents(mAnimationEvents); 134 135 int childCount = mHostLayout.getChildCount(); 136 mAnimationFilter.applyCombination(mNewEvents); 137 mCurrentAdditionalDelay = additionalDelay; 138 mCurrentLength = NotificationStackScrollLayout.AnimationEvent.combineLength(mNewEvents); 139 // Used to stagger concurrent animations' delays and durations for visual effect 140 int animationStaggerCount = 0; 141 for (int i = 0; i < childCount; i++) { 142 final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i); 143 144 ExpandableViewState viewState = child.getViewState(); 145 if (viewState == null || child.getVisibility() == View.GONE 146 || applyWithoutAnimation(child, viewState)) { 147 continue; 148 } 149 150 if (mAnimationProperties.wasAdded(child) && animationStaggerCount < MAX_STAGGER_COUNT) { 151 animationStaggerCount++; 152 } 153 initAnimationProperties(child, viewState, animationStaggerCount); 154 viewState.animateTo(child, mAnimationProperties); 155 } 156 if (!isRunning()) { 157 // no child has preformed any animation, lets finish 158 onAnimationFinished(); 159 } 160 mHeadsUpAppearChildren.clear(); 161 mHeadsUpDisappearChildren.clear(); 162 mNewEvents.clear(); 163 mNewAddChildren.clear(); 164 } 165 initAnimationProperties(ExpandableView child, ExpandableViewState viewState, int animationStaggerCount)166 private void initAnimationProperties(ExpandableView child, 167 ExpandableViewState viewState, int animationStaggerCount) { 168 boolean wasAdded = mAnimationProperties.wasAdded(child); 169 mAnimationProperties.duration = mCurrentLength; 170 adaptDurationWhenGoingToFullShade(child, viewState, wasAdded, animationStaggerCount); 171 mAnimationProperties.delay = 0; 172 if (wasAdded || mAnimationFilter.hasDelays 173 && (viewState.yTranslation != child.getTranslationY() 174 || viewState.zTranslation != child.getTranslationZ() 175 || viewState.alpha != child.getAlpha() 176 || viewState.height != child.getActualHeight() 177 || viewState.clipTopAmount != child.getClipTopAmount() 178 || viewState.dark != child.isDark())) { 179 mAnimationProperties.delay = mCurrentAdditionalDelay 180 + calculateChildAnimationDelay(viewState, animationStaggerCount); 181 } 182 } 183 adaptDurationWhenGoingToFullShade(ExpandableView child, ExpandableViewState viewState, boolean wasAdded, int animationStaggerCount)184 private void adaptDurationWhenGoingToFullShade(ExpandableView child, 185 ExpandableViewState viewState, boolean wasAdded, int animationStaggerCount) { 186 if (wasAdded && mAnimationFilter.hasGoToFullShadeEvent) { 187 child.setTranslationY(child.getTranslationY() + mGoToFullShadeAppearingTranslation); 188 float longerDurationFactor = (float) Math.pow(animationStaggerCount, 0.7f); 189 mAnimationProperties.duration = ANIMATION_DURATION_APPEAR_DISAPPEAR + 50 + 190 (long) (100 * longerDurationFactor); 191 } 192 } 193 194 /** 195 * Determines if a view should not perform an animation and applies it directly. 196 * 197 * @return true if no animation should be performed 198 */ applyWithoutAnimation(ExpandableView child, ExpandableViewState viewState)199 private boolean applyWithoutAnimation(ExpandableView child, ExpandableViewState viewState) { 200 if (mShadeExpanded) { 201 return false; 202 } 203 if (ViewState.isAnimatingY(child)) { 204 // A Y translation animation is running 205 return false; 206 } 207 if (mHeadsUpDisappearChildren.contains(child) || mHeadsUpAppearChildren.contains(child)) { 208 // This is a heads up animation 209 return false; 210 } 211 if (NotificationStackScrollLayout.isPinnedHeadsUp(child)) { 212 // This is another headsUp which might move. Let's animate! 213 return false; 214 } 215 viewState.applyToView(child); 216 return true; 217 } 218 calculateChildAnimationDelay(ExpandableViewState viewState, int animationStaggerCount)219 private long calculateChildAnimationDelay(ExpandableViewState viewState, 220 int animationStaggerCount) { 221 if (mAnimationFilter.hasGoToFullShadeEvent) { 222 return calculateDelayGoToFullShade(viewState, animationStaggerCount); 223 } 224 if (mAnimationFilter.customDelay != AnimationFilter.NO_DELAY) { 225 return mAnimationFilter.customDelay; 226 } 227 long minDelay = 0; 228 for (NotificationStackScrollLayout.AnimationEvent event : mNewEvents) { 229 long delayPerElement = ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING; 230 switch (event.animationType) { 231 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD: { 232 int ownIndex = viewState.notGoneIndex; 233 int changingIndex = 234 ((ExpandableView) (event.mChangingView)).getViewState().notGoneIndex; 235 int difference = Math.abs(ownIndex - changingIndex); 236 difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE, 237 difference - 1)); 238 long delay = (DELAY_EFFECT_MAX_INDEX_DIFFERENCE - difference) * delayPerElement; 239 minDelay = Math.max(delay, minDelay); 240 break; 241 } 242 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT: 243 delayPerElement = ANIMATION_DELAY_PER_ELEMENT_MANUAL; 244 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE: { 245 int ownIndex = viewState.notGoneIndex; 246 boolean noNextView = event.viewAfterChangingView == null; 247 ExpandableView viewAfterChangingView = noNextView 248 ? mHostLayout.getLastChildNotGone() 249 : (ExpandableView) event.viewAfterChangingView; 250 if (viewAfterChangingView == null) { 251 // This can happen when the last view in the list is removed. 252 // Since the shelf is still around and the only view, the code still goes 253 // in here and tries to calculate the delay for it when case its properties 254 // have changed. 255 continue; 256 } 257 int nextIndex = viewAfterChangingView.getViewState().notGoneIndex; 258 if (ownIndex >= nextIndex) { 259 // we only have the view afterwards 260 ownIndex++; 261 } 262 int difference = Math.abs(ownIndex - nextIndex); 263 difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE, 264 difference - 1)); 265 long delay = difference * delayPerElement; 266 minDelay = Math.max(delay, minDelay); 267 break; 268 } 269 default: 270 break; 271 } 272 } 273 return minDelay; 274 } 275 calculateDelayGoToFullShade(ExpandableViewState viewState, int animationStaggerCount)276 private long calculateDelayGoToFullShade(ExpandableViewState viewState, 277 int animationStaggerCount) { 278 int shelfIndex = mShelf.getNotGoneIndex(); 279 float index = viewState.notGoneIndex; 280 long result = 0; 281 if (index > shelfIndex) { 282 float diff = (float) Math.pow(animationStaggerCount, 0.7f); 283 result += (long) (diff * ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE * 0.25); 284 index = shelfIndex; 285 } 286 index = (float) Math.pow(index, 0.7f); 287 result += (long) (index * ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE); 288 return result; 289 } 290 291 /** 292 * @return an adapter which ensures that onAnimationFinished is called once no animation is 293 * running anymore 294 */ getGlobalAnimationFinishedListener()295 private AnimatorListenerAdapter getGlobalAnimationFinishedListener() { 296 if (!mAnimationListenerPool.empty()) { 297 return mAnimationListenerPool.pop(); 298 } 299 300 // We need to create a new one, no reusable ones found 301 return new AnimatorListenerAdapter() { 302 private boolean mWasCancelled; 303 304 @Override 305 public void onAnimationEnd(Animator animation) { 306 mAnimatorSet.remove(animation); 307 if (mAnimatorSet.isEmpty() && !mWasCancelled) { 308 onAnimationFinished(); 309 } 310 mAnimationListenerPool.push(this); 311 } 312 313 @Override 314 public void onAnimationCancel(Animator animation) { 315 mWasCancelled = true; 316 } 317 318 @Override 319 public void onAnimationStart(Animator animation) { 320 mWasCancelled = false; 321 mAnimatorSet.add(animation); 322 } 323 }; 324 } 325 onAnimationFinished()326 private void onAnimationFinished() { 327 mHostLayout.onChildAnimationFinished(); 328 329 for (ExpandableView transientViewsToRemove : mTransientViewsToRemove) { 330 transientViewsToRemove.getTransientContainer() 331 .removeTransientView(transientViewsToRemove); 332 } 333 mTransientViewsToRemove.clear(); 334 } 335 336 /** 337 * Process the animationEvents for a new animation 338 * 339 * @param animationEvents the animation events for the animation to perform 340 */ processAnimationEvents( ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents)341 private void processAnimationEvents( 342 ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents) { 343 for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) { 344 final ExpandableView changingView = (ExpandableView) event.mChangingView; 345 if (event.animationType == 346 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) { 347 348 // This item is added, initialize it's properties. 349 ExpandableViewState viewState = changingView.getViewState(); 350 if (viewState == null || viewState.gone) { 351 // The position for this child was never generated, let's continue. 352 continue; 353 } 354 viewState.applyToView(changingView); 355 mNewAddChildren.add(changingView); 356 357 } else if (event.animationType == 358 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) { 359 if (changingView.getVisibility() != View.VISIBLE) { 360 removeTransientView(changingView); 361 continue; 362 } 363 364 // Find the amount to translate up. This is needed in order to understand the 365 // direction of the remove animation (either downwards or upwards) 366 // upwards by default 367 float translationDirection = -1.0f; 368 if (event.viewAfterChangingView != null) { 369 float ownPosition = changingView.getTranslationY(); 370 if (changingView instanceof ExpandableNotificationRow 371 && event.viewAfterChangingView instanceof ExpandableNotificationRow) { 372 ExpandableNotificationRow changingRow = 373 (ExpandableNotificationRow) changingView; 374 ExpandableNotificationRow nextRow = 375 (ExpandableNotificationRow) event.viewAfterChangingView; 376 if (changingRow.isRemoved() 377 && changingRow.wasChildInGroupWhenRemoved() 378 && !nextRow.isChildInGroup()) { 379 // the next row isn't actually a child from a group! Let's 380 // compare absolute positions! 381 ownPosition = changingRow.getTranslationWhenRemoved(); 382 } 383 } 384 int actualHeight = changingView.getActualHeight(); 385 // there was a view after this one, Approximate the distance the next child 386 // travelled 387 ExpandableViewState viewState = 388 ((ExpandableView) event.viewAfterChangingView).getViewState(); 389 translationDirection = ((viewState.yTranslation 390 - (ownPosition + actualHeight / 2.0f)) * 2 / 391 actualHeight); 392 translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f); 393 394 } 395 changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR, 396 0 /* delay */, translationDirection, false /* isHeadsUpAppear */, 397 0, () -> removeTransientView(changingView), null); 398 } else if (event.animationType == 399 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) { 400 if (Math.abs(changingView.getTranslation()) == changingView.getWidth() 401 && changingView.getTransientContainer() != null) { 402 changingView.getTransientContainer().removeTransientView(changingView); 403 } 404 } else if (event.animationType == NotificationStackScrollLayout 405 .AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED) { 406 ExpandableNotificationRow row = (ExpandableNotificationRow) event.mChangingView; 407 row.prepareExpansionChanged(); 408 } else if (event.animationType == NotificationStackScrollLayout 409 .AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR) { 410 // This item is added, initialize it's properties. 411 ExpandableViewState viewState = changingView.getViewState(); 412 mTmpState.copyFrom(viewState); 413 if (event.headsUpFromBottom) { 414 mTmpState.yTranslation = mHeadsUpAppearHeightBottom; 415 } else { 416 mTmpState.yTranslation = 0; 417 changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_APPEAR_CLOSED, 418 true /* isHeadsUpAppear */); 419 } 420 mHeadsUpAppearChildren.add(changingView); 421 mTmpState.applyToView(changingView); 422 } else if (event.animationType == NotificationStackScrollLayout 423 .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR || 424 event.animationType == NotificationStackScrollLayout 425 .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) { 426 mHeadsUpDisappearChildren.add(changingView); 427 Runnable endRunnable = null; 428 // We need some additional delay in case we were removed to make sure we're not 429 // lagging 430 int extraDelay = event.animationType == NotificationStackScrollLayout 431 .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK 432 ? ANIMATION_DELAY_HEADS_UP_CLICKED 433 : 0; 434 if (changingView.getParent() == null) { 435 // This notification was actually removed, so we need to add it transiently 436 mHostLayout.addTransientView(changingView, 0); 437 changingView.setTransientContainer(mHostLayout); 438 mTmpState.initFrom(changingView); 439 mTmpState.yTranslation = 0; 440 // We temporarily enable Y animations, the real filter will be combined 441 // afterwards anyway 442 mAnimationFilter.animateY = true; 443 mAnimationProperties.delay = extraDelay + ANIMATION_DELAY_HEADS_UP; 444 mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR; 445 mTmpState.animateTo(changingView, mAnimationProperties); 446 endRunnable = () -> removeTransientView(changingView); 447 } 448 float targetLocation = 0; 449 boolean needsAnimation = true; 450 if (changingView instanceof ExpandableNotificationRow) { 451 ExpandableNotificationRow row = (ExpandableNotificationRow) changingView; 452 if (row.isDismissed()) { 453 needsAnimation = false; 454 } 455 StatusBarIconView icon = row.getEntry().icon; 456 if (icon.getParent() != null) { 457 icon.getLocationOnScreen(mTmpLocation); 458 float iconPosition = mTmpLocation[0] - icon.getTranslationX() 459 + ViewState.getFinalTranslationX(icon) + icon.getWidth() * 0.25f; 460 mHostLayout.getLocationOnScreen(mTmpLocation); 461 targetLocation = iconPosition - mTmpLocation[0]; 462 } 463 } 464 465 if (needsAnimation) { 466 // We need to add the global animation listener, since once no animations are 467 // running anymore, the panel will instantly hide itself. We need to wait until 468 // the animation is fully finished for this though. 469 long removeAnimationDelay = changingView.performRemoveAnimation( 470 ANIMATION_DURATION_HEADS_UP_DISAPPEAR + ANIMATION_DELAY_HEADS_UP, 471 extraDelay, 0.0f, true /* isHeadsUpAppear */, targetLocation, 472 endRunnable, getGlobalAnimationFinishedListener()); 473 mAnimationProperties.delay += removeAnimationDelay; 474 } else if (endRunnable != null) { 475 endRunnable.run(); 476 } 477 } 478 mNewEvents.add(event); 479 } 480 } 481 removeTransientView(ExpandableView viewToRemove)482 public static void removeTransientView(ExpandableView viewToRemove) { 483 if (viewToRemove.getTransientContainer() != null) { 484 viewToRemove.getTransientContainer().removeTransientView(viewToRemove); 485 } 486 } 487 animateOverScrollToAmount(float targetAmount, final boolean onTop, final boolean isRubberbanded)488 public void animateOverScrollToAmount(float targetAmount, final boolean onTop, 489 final boolean isRubberbanded) { 490 final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop); 491 if (targetAmount == startOverScrollAmount) { 492 return; 493 } 494 cancelOverScrollAnimators(onTop); 495 ValueAnimator overScrollAnimator = ValueAnimator.ofFloat(startOverScrollAmount, 496 targetAmount); 497 overScrollAnimator.setDuration(ANIMATION_DURATION_STANDARD); 498 overScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 499 @Override 500 public void onAnimationUpdate(ValueAnimator animation) { 501 float currentOverScroll = (float) animation.getAnimatedValue(); 502 mHostLayout.setOverScrollAmount( 503 currentOverScroll, onTop, false /* animate */, false /* cancelAnimators */, 504 isRubberbanded); 505 } 506 }); 507 overScrollAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); 508 overScrollAnimator.addListener(new AnimatorListenerAdapter() { 509 @Override 510 public void onAnimationEnd(Animator animation) { 511 if (onTop) { 512 mTopOverScrollAnimator = null; 513 } else { 514 mBottomOverScrollAnimator = null; 515 } 516 } 517 }); 518 overScrollAnimator.start(); 519 if (onTop) { 520 mTopOverScrollAnimator = overScrollAnimator; 521 } else { 522 mBottomOverScrollAnimator = overScrollAnimator; 523 } 524 } 525 cancelOverScrollAnimators(boolean onTop)526 public void cancelOverScrollAnimators(boolean onTop) { 527 ValueAnimator currentAnimator = onTop ? mTopOverScrollAnimator : mBottomOverScrollAnimator; 528 if (currentAnimator != null) { 529 currentAnimator.cancel(); 530 } 531 } 532 setHeadsUpAppearHeightBottom(int headsUpAppearHeightBottom)533 public void setHeadsUpAppearHeightBottom(int headsUpAppearHeightBottom) { 534 mHeadsUpAppearHeightBottom = headsUpAppearHeightBottom; 535 } 536 setShadeExpanded(boolean shadeExpanded)537 public void setShadeExpanded(boolean shadeExpanded) { 538 mShadeExpanded = shadeExpanded; 539 } 540 setShelf(NotificationShelf shelf)541 public void setShelf(NotificationShelf shelf) { 542 mShelf = shelf; 543 } 544 } 545