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